B站账号:小光学嵌入式


  • 大家好哇!我是小光,嵌入式爱好者,一个想要成为系统架构师的大二学生。
  • 最近开始系统性补习STM32基础知识,规划有:串口通信,Github,Ucos等等。
  • 今天总结一下串口通信之stm32-USART。

一.原理讲解

请跳转->串口通信————UART、I2C、SPI详解(总结篇

二.USART和UART的区别

UART:universal asynchronous receiver and transmitter通用异步收/发器

USART:universal synchronous asynchronous receiver and transmitter通用同步/异步收/发器
大家能看出来USART和UART相比较,可以实现同步,就是说USART相对UART的区别之一就是能提供主动时钟

三.具体固件库实现

1.USART初始化

根据USART的外设配置,我们可以得知,全双工模式,发送端设置为复用推挽输出,接收端设置为浮空输入或上拉输入。
初始化步骤:

  1. 使能USART使用引脚时钟
    使能串口使用所在引脚
  2. GPIO初始化
    初始化串口所在IO
  3. 接收中断初始化
    设置中断优先级和通道
  4. 初始化USART
    初始化USART,波特率、数据格式、奇偶校验位、收发模式等等。
  5. 开启中断
    USART_ITConfig();//开启串口接受中断
  6. 使能串口
    USART_Cmd();
void uart_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);    //使能USART1,GPIOA时钟

    //USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9

  //USART1_RX      GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;        //子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器

   //USART 初始化设置

    USART_InitStructure.USART_BaudRate = bound;//串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;    //收发模式

  USART_Init(USART1, &USART_InitStructure); //初始化串口1
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
  USART_Cmd(USART1, ENABLE);                    //使能串口1 

}

2.串口收发数据

串口发送一般不会用到中断发送,接收会用到中断接收,因为串口通信在没有用到主动时钟的时候,他是异步通信的。
我们先认识一下固件库中的这几个函数:

void USART_SendData();//发送数据到串口,DR
uint_16_t USART_ReceiveData();//接收数据,从DR读取接收到的数据

FlagStatus USART_GetFlagStatus();//获取状态标志位
void USART_ClearFlag();//清除状态标志位
ITStatus USART_GetITStatus();//获取中断状态标志位
void USART_ClearITPendingBit();//清除中断状态标志位

我们通过上面这些函数就可以完成一次成功的数据发送与接收。
下面是一个例子,完成了两个STM32之间的传输数据:

1.主机发送

发送时我们封装一个函数:
步骤:

  1. USART_ClearFlag()清除标志位
  2. USART_SendData()每次发送一个字符
  3. USART_GetFlagStatus()等待发送完成
  4. USART_ClearFlag()清楚标志位,然后继续发送知道截至标志
//串口发送字符串
void USART_SendString(USART_TypeDef* USARTx, char *DataString)
{
    int i = 0;
    USART_ClearFlag(USARTx,USART_FLAG_TC);                                        //发送字符前清空标志位(否则缺失字符串的第一个字符)
    while(DataString[i] != '\0')                                                //字符串结束符
    {
        USART_SendData(USARTx,DataString[i]);                                    //每次发送字符串的一个字符
        while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == 0);                    //等待数据发送成功
        USART_ClearFlag(USARTx,USART_FLAG_TC);                                    //发送字符后清空标志位
        i++;
    }
}

2.从机接收数据

在接收的时候,我们使用中断接收,所以我们就要编写中断服务函数:void USARTx_IRQHandler(void)这个名字一定不能变。
接收数据步骤:

  1. USART_GetITStatus()判断是否有数据
  2. USART_ReceiveData()读取寄存器中的数据,存放到我们定义的USART_ReceiveString
  3. if(USART_ReceiveString[Receive_sum-2] == '\r' && USART_ReceiveString[Receive_sum-1] == '\n' )回车换行作为结束标志位,当接收完成后根据接收到的数据USART_ReceiveString直接执行相应的操作。
char USART_ReceiveString[20];                                                    //接收PC端发送过来的字符
int Receive_Flag = 0;                                                            //接收消息标志位
int Receive_sum = 0;                                                            //数组下标
void USART1_IRQHandler(void)
{
    if(USART_GetITStatus(USART1,USART_IT_RXNE) == 1)                            //USART_FLAG_RXNE判断数据,== 1则有数据
    {
        if(Receive_sum > 49)                                                    //数组能存放50个字节的数据                
        {
            USART_ReceiveString[49] = '\0';                                        //数据字节超过50位时,将最后一位设置为\0    
            Receive_Flag = 1;                                                    //接收标志位置1,停止接收数据
            Receive_sum = 0;                                                    //数组下标置0
        }

        if(Receive_Flag == 0)                                                    //接收标志位等于0,开始接收数据
        {
            USART_ReceiveString[Receive_sum] = USART_ReceiveData(USART1);        //通过USART1串口接收字符
            Receive_sum++;                                                        //数组下标++
        }

        if(Receive_sum >= 2)                                                    //数组下标大于2
        {
            if(USART_ReceiveString[Receive_sum-2] == '\r' && USART_ReceiveString[Receive_sum-1] == '\n' )
            {
                USART_ReceiveString[Receive_sum-1] = '\0';                        
                USART_ReceiveString[Receive_sum-2] = '\0';
                Receive_Flag = 1;                                                //接收标志位置1,停止接收数据
                Receive_sum = 0;                                                //数组下标置0


                if(strcmp(USART_ReceiveString,"hello") == 0)
                {
                    LED0 = !LED0;
                }
                if(strcmp(USART_ReceiveString,"world") == 0)
                {
                    LED1 = !LED1;
                }
            }        
        }
        USART_ClearITPendingBit(USART1,USART_IT_RXNE);                            //接收后先清空标志位
    }
}

我们可以通过调整Receive_Flag的值来让它开始或者停止接收。
我用这个做了实验,完成了简单的两个板子之间的通信。

总结

今天复习了串口通信中的USART,把之前串口接收中断没有搞懂的搞懂了,之后还有SPI和I2C通信。都会用代码实现并且实验它。
继续加油!!!