单片机学习!


目录

 前言

一、串口发送配置步骤

二、详细步骤

2.1 RCC开启USART和GPIO时钟

2.2 GPIO初始化

2.3 配置USART

2.4 开启USART

2.5 总初始化代码

三、接收数据

3.1 查询方法

3.2 中断方法

3.2.1 中断配置

3.2.2 接收函数

3.3 发送+接收函数总代码

总结


 前言

        上篇博文介绍了串口发送的代码设计。本文主要介绍USART初始化配置、USART串口发送和接收的基础内容。


一、串口发送配置步骤

初始化流程,可以从基本结构图来梳理:

第一步,开启时钟,把需要用的USART和GPIO的时钟打开。

第二步,GPIO初始化,把TX配置成复用输出,RX配置成输入。

第三步,配置USART,直接使用一个结构体就可以把所有需要的参数都配置好。

第四步,如果只需要发送的功能,就直接开启USART,初始化就结束了。如果需要接收的功能,可能还需要配置中断,那就在开启USART之前,再加上ITCongfig和NVIC的代码就行了。

得益于库函数的封装,内部各种细节问题就不需要再关心了。

初始化完成之后

  • 如果要发送数据,调用一个发送的函数就行了;
  • 如果要接收数据,就调用接收的函数;
  • 如果要获取发送和接收的状态,就调用获取标志位的函数。
  • 以上就是USART外设的使用思路。

    二、详细步骤

    2.1 RCC开启USART和GPIO时钟

            第一步开启时钟USART和GPIO的时钟。

    代码示例:

    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA时钟
    

            开启USART1的时钟,这里USART1是APB2的外设,其他都是APB1的外设。然后还需要开启GPIO的时钟,看引脚定义表,USART1的TX是PA9,RX是PA10.

    2.2 GPIO初始化

            第二步初始化GPIO引脚。在引脚定义表里可以找到USART1的TX复用在了PA9;USART1的RX复用在了PA10,所以这里初始化GPIOA的Pin_9和Pin_10

    代码示例:

    	GPIO_InitTypeDef GPIO_InitStruct;
    	GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式选复用推挽输出
    	GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9
    	GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
    
        GPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;//引脚模式,上拉输入
    	GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10;//引脚选择Pin_10
    	GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA

            引脚模式的配置选择:

  • TX引脚是USART外设控制的输出引脚,所以要选复用推挽输出。
  • RX引脚是USART外设数据的输入引脚,所以要选择输入模式。
  •         输入模式并不分什么普通输入,复用输入。一根线只能有一个输出,但可以有多个输入。所以输入脚、外设和GPIO都可以同时用。一般RX配置是浮空输入或者上拉输入,因为串口波形空闲状态是高电平,所以不使用下拉输入。这里引脚模式的配置可以参考手册GPIO那一节推荐的配置表。

            本章程序需要实现数据发送和数据接收,所以

  • TX引脚初始化模式选择GPIO_Mode_AF_PP复用推挽输出模式;
  • RX引脚初始化模式选择GPIO_Mode_IPU上拉输入模式。
  • 2.3 配置USART

            第三步初始化USART

    代码示例:

    	USART_InitTypeDef USART_InitStructure;
    	USART_InitStructure.USART_BaudRate = 9600;//波特率
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
    	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;;//串口模式
    	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
    	USART_InitStructure.USART_WordLength =USART_WordLength_8b; //字长
    
    	USART_Init(USART1,&USART_InitStructure);
    

            USART_BaudRate 波特率,可以直接写一个波特率的数值。这里给9600,写完数值之后USART_Init函数内部会自动算好9600对应的分频系数,然后写到BRR寄存器。


            USART_HardwareFlowControl 硬件流控制,这个参数的取值可以是

  • USART_HardwareFlowControl_None不使用流控;
  • USART_HardwareFlowControl_CTS只用CTS;
  • USART_HardwareFlowControl_RTS只用RTS;
  • USART_HardwareFlowControl_RTS_CTS是CTS和RTS都使用。
  • 这里不使用流控,所以选择USART_HardwareFlowControl_None这个参数。


            USART_Mode串口模式,参数有

  • USART_Mode_Tx是Tx发送模式;
  • USART_Mode_Rx是Rx接收模式。
  • 如果既需要接收又需要发送,那就用或符号把Tx和Rx或起来。
  • 代码示例:

    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;

    这里程序需要发送和接收功能,所以选择USART_Mode_Tx | USART_Mode_Rx,就是同时开启发送和接收的部分。


            USART_Parity 校验位,参数有

  • USART_Parity_No无校验;
  • USART_Parity_Odd奇校验;
  • USART_Parity_Even偶校验。
  • 这里不需要校验,所以选择USART_Parity_No无校验。


            USART_StopBits停止位,参数可以选择

  • USART_StopBits_0_5  是0.5位停止位;
  • USART_StopBits_1      是1位停止位;
  • USART_StopBits_1_5  是1.5位停止位;
  • USART_StopBits_2      是2位停止位。
  • 这里选择USART_StopBits_1参数,就是1位停止位。


            USART_WordLength 字长,参数可以选择

  • USART_WordLength_8b八位字长;
  • USART_WordLength_9b九位字长。
  • 因为不需要校验,前面设置了无校验参数,这里就选择USART_WordLength_8b字长为8位。

            以上结构体参数的初始化就完成了,对串口的配置是9600波特率、无流控、发送+接收模式、无校验位、1位停止位、八位字长。

    2.4 开启USART

            第四步,开启USART,调用USART_Cmd函数,给指定的通道USART1使能。

    代码示例:

        USART_Cmd(USART1,ENABLE);

    2.5 总初始化代码

    代码示例:

    void Serial_Init(void)
    {
    	//第一步开启时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO的时钟
    
    	
    	//第二步初始化GPIO引脚
    	GPIO_InitTypeDef GPIO_InitStruct;
    	GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式
    	GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9
    	GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
    
    	GPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;//引脚模式
    	GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10;//引脚选择Pin_10
    	GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
    	
    	
    	//第三步初始化USART
    	USART_InitTypeDef USART_InitStructure;
    	USART_InitStructure.USART_BaudRate = 9600;//波特率
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
    	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式
    	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
    	USART_InitStructure.USART_WordLength =USART_WordLength_8b; //字长
    
    	USART_Init(USART1,&USART_InitStructure);
    	
    	USART_Cmd(USART1,ENABLE);
    	
    }
    

    三、接收数据

            以上串口接收的代码已经配置差不多了。对于串口接收来说,可以使用查询和中断两种方法,如果使用查询,那初始化就结束了。如果使用中断,那还需要开启中断,配置NVIC。

    下文将对查询和中断的方法分别举例。

    3.1 查询方法

            查询的流程是在主函数里不断判断RXNE标志位,如果RXNE标志位置1了,就说明收到数据了,那再调用ReceiveData函数读取DR寄存器就可以了。

    代码示例:

    if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)
    {
        RxData = USART_ReceiveData(USART1);
    }	

            目前接收到的一个字节就已经在RxData里了。还需要分析一下清除标志位的问题,查看手册:

            当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位。如果 USART_CR1寄存器中的RXNEIE为1,则产生中断。对USART_DR的读操作可以将该位清零。RXNE位也可以通过写入0来清除,只有在多缓存通讯中才推荐这种清除程序。

            手册中表示读USART_DR可以自动清零标志位,所以上述代码读完USART_DR就不需要再清除标志位了。

            

    3.2 中断方法

    3.2.1 中断配置

            使用中断方法首先需要在初始化里加上开启中断的代码。

    代码示例:

        USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    	
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    	
    	NVIC_InitTypeDef NVIC_InitStructure;
    	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//中断通道
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1; 
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
    	NVIC_Init(&NVIC_InitStructure);
    

            第一步,开启RXNE标志位到NVIC的输出,也就是配置USART1的接收中断使能,之后就可以配置NVIC了。

    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);

            配置中断分组:

        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

            初始化NVIC的USART1通道:

        NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//中断通道
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1; 
        NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
        NVIC_Init(&NVIC_InitStructure);

    代码配置:使能中断通道为USART1全局中断,先占优先级为1,从优先级也为1,最后将结构体地址放入NVIC_Init函数。


            以上配置使RXNE标志位一但置1了,就会向NVIC申请中断,之后就可以在中断函数里接收数据。

    中断接收初始化配置总代码:

    void Serial_Init(void)
    {
    	//第一步开启时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO的时钟
    
    	
    	//第二步初始化GPIO引脚
    	GPIO_InitTypeDef GPIO_InitStruct;
    	GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式
    	GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9
    	GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
    
    	GPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;//引脚模式
    	GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10;//引脚选择Pin_10
    	GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
    	
    	
    	//第三步初始化USART
    	USART_InitTypeDef USART_InitStructure;
    	USART_InitStructure.USART_BaudRate = 9600;//波特率
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
    	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式
    	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
    	USART_InitStructure.USART_WordLength =USART_WordLength_8b; //字长
    	USART_Init(USART1,&USART_InitStructure);
    	
    	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//配置USART中断
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组
    
    	NVIC_InitTypeDef NVIC_InitStructure;//初始化NVIC的USART1通道
    	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//中断通道
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1; 
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
    	NVIC_Init(&NVIC_InitStructure);
    
    	USART_Cmd(USART1,ENABLE);
    	
    }

    3.2.2 接收函数

    代码示例:

    uint8_t Serial_RxData;//接收到的数据
    uint8_t Serial_RxFlag;//接收到数据的标志位
    
    uint8_t Serial_GetRxFlag(void)
    {
    	if(Serial_RxFlag == 1)
    	{
    		Serial_RxFlag = 0;
    		return 1;
    	}
    	return 0;
    }
    
    uint8_t Serial_GetRxData(void)
    {
    	return Serial_RxData;
    }	
    
    
    void USART1_IRQHandler(void)
    {
    	if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
    	{
    		Serial_RxData = USART_ReceiveData(USART1);
    		Serial_RxFlag = 1;
    		USART_ClearITPendingBit(USART1,USART_IT_RXNE); 
    	}
    }
    
    
        if(Serial_GetRxFlag()==1)
        {
            RxData = Serial_GetRxData();
            Serial_SendByte(RxData);
    	}				


            Serial_GetRxFlag函数实现功能为变量Serial_RxData读后自动清除标志位Serial_RxFlag。

    uint8_t Serial_GetRxFlag(void)
    {
        if(Serial_RxFlag == 1)
        {
            Serial_RxFlag = 0;
            return 1;
        }
        return 0;
    }


            Serial_GetRxData函数的功能为返回接收到的数据。

    uint8_t Serial_GetRxData(void)
    {
    	return Serial_RxData;
    }	


            USART1_IRQHandler函数是把数据进行了一次转存,最终还是要扫描查询Serial_RxFlag来接收数据。

    void USART1_IRQHandler(void)
    {
    	if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
    	{
    		Serial_RxData = USART_ReceiveData(USART1);
    		Serial_RxFlag = 1;/
    		USART_ClearITPendingBit(USART1,USART_IT_RXNE); 
    	}
    }

            先判断标志位:如果RXNE确实置1了,就进入if。

        if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)

            将接收到的数据读取给自定义变量Serial_RxData,读完后置一个自定义的标志位Serial_RxFlag。

            Serial_RxData = USART_ReceiveData(USART1);
            Serial_RxFlag = 1;

            在if里可以直接读取DR,执行一些操作。if里是否需要清除标志位可以看有没有读取DR:

  • 如果读取了DR,就可以自动清除标志位;
  • 如果没读取DR,就需要手动清除标志位。
  • 示例里直接清除一下标志位

            USART_ClearITPendingBit(USART1,USART_IT_RXNE); 

            以上中断接收和变量的封装就完成了!想中断接收数据就可以直接调用函数来使用:

    if(Serial_GetRxFlag()==1)
    {
    	RxData = Serial_GetRxData();//目前接收到的一个字节就已经在RxData里了
    	Serial_SendByte(RxData);//把接收到的数据回传到电脑
    }	

    目前这个程序只支持一个字节的接收。

    3.3 发送+接收函数总代码

    发送接收总代码示例:

    //发送数据的函数
    void Serial_SendByte(uint8_t Byte)
    {
    	USART_SendData(USART1,Byte);
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); 
    }
    
    //发送一个数组的函数
    void Serial_SendArray(uint8_t *Array,uint16_t Length) 
    {
    	uint16_t i;
    	for(i = 0 ; i < Length ; i++)
    	{
    		Serial_SendByte(Array[i]);
    	}
    }
    
    //发送字符串的函数
    void Serial_SendString(char *String)
    	uint8_t i;
    	for(i = 0;String[i] != '\0';i++)
    	{
    		Serial_SendByte(String[i]);
    	}
    }
    
    //这个函数的返回值是X的Y次方
    uint32_t Serial_Pow(uint32_t X,uint32_t Y)
    	uint32_t Result = 1;
    	while(Y--)
    	{
    		Result *= X;
    	}
    	return Result;
    }
    
    
    //发送一个数字,能显示字符串形式数字的函数。
    void Serial_SendNumber(uint32_t Number,uint8_t Length)
    {
    	uint8_t i;
    	for(i = 0;i < Length;i++)
    	{
    		Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) %10 + '0');
    	}
    }
    
    //printf函数重定向到串口
    int fputc(int ch,FILE *f)
    {
    	Serial_SendByte(ch);
    	return ch;
    }
    
    //函数实现Serial_RxData变量读后自动清除标志位Serial_RxFlag的功能
    uint8_t Serial_GetRxFlag(void)
    {
    	if(Serial_RxFlag == 1)
    	{
    		Serial_RxFlag = 0;
    		return 1;
    	}
    	return 0;
    }
    
    uint8_t Serial_GetRxData(void)
    {
    	return Serial_RxData;
    }	
    
    //函数用于数据转存,最终还是要扫描查询Serial_RxFlag来接收数据。
    void USART1_IRQHandler(void)
    {
    	if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
    	{
    		Serial_RxData = USART_ReceiveData(USART1);
    		Serial_RxFlag = 1;
    		
    		USART_ClearITPendingBit(USART1,USART_IT_RXNE); 
    	}
    }

            发送部分的函数设计详解可以看上一篇博文STM32 USART串口发送_stm32 uart初始化是怎么设置cr3-CSDN博客https://blog.csdn.net/Echo_cy_/article/details/142794600?spm=1001.2014.3001.5501


    总结

             以上就是今天要讲的内容,本文仅仅简单介绍了USART初始化配置以及一些配置代码的细节。

    作者:Echo_cy_

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 USART串口接收

    发表回复