STM32串口(USART)深入解析

概述:

串口的重点难点要点就是几个标志位

===========================================================================================

重点重点重点:下面这看明白了我相信你就是真的明白32的串口工作原理了,最后面也有,这个实在很重要,所以头尾都放一份,提到的框图和和函数在下文,往下拉就有

USART_FLAG_TXE:

注意左上角紫色的框,以及发送数据的流程,当我们去调用函数**USART_SendData(pUSARTx,ch);的时候,一个字节的数据就会被写入发送数据寄存器(TDR),注意USART_FLAG_TXE这个标志位,不发送数据的时候,闲着的时候都是0,当检测到数据写入发送数据寄存器TDR的时候,就会立刻将USART_FLAG_TXE置1,然后数据移位寄存器(TSR)就开始从TDR寄存器中取数据,一位一位的取走发送出去,当被取完的时候,发送数据寄存器TDR也就为空了,所以此时USART_FLAG_TXE**标志位就会被置0 ,所以也是我们while检测这个标志的原因

USART_FLAG_TC:

我们来假设一个过程,当我们调用**USART_SendData(pUSARTx,ch)发送一个字节的数据的时候,此时数据移位寄存器(TSR)就会立刻从数据发送寄存器(TDR)中开始搬运数据并一位一位发送出去,但是受限于波特率,搬运的速度会远大于发送的速率,而只要搬运完了,USART_FLAG_TXE这个标志位就会立刻被置0,我们while循环检测这个标志,此时就会被唤醒,看我上面发送字符串的函数就能看出来,下一个数据立刻又被写入发送数据寄存器(TDR),然后移位数据寄存器又开始搬运了,注意注意:此时移位数据寄存器中,上一个字节的数据还不一定发送完成呢,所以会为空吗?不会,所以所以:USART_FLAG_TC**这个标志位的作用是:检测数据移位寄存器是否为空,当开始发送数据,数据移位寄存器从数据发送寄存器中搬运数据,这个标志位就会被置1,当所有数据发送完成,移位寄存器为空时,就会被置0,所以通过这个标志位,可以实现在字节流的传输方式中实现帧传输

===========================================================================

我们常常容易混淆通讯方式和通讯协议,很多初学者分不清这些东西,USART(串口)可以理解为一种通讯的方式或者说通讯的桥梁,那通信协议呢,打个比方,两个人事先约定好,你说1,我就说2,听到你说2之后,我就接3……像这样是通讯协议,那具体你们是打电话说,还是见面说,都行,在这个例子中,使用电话或者线下见面直接说就是通讯方式,可以理解为串口/wifi/以太网,而你们之间的约定就是通讯协议,串口比较常用,通常用来调试输入信息,以及数据接收和发送,有很多的嵌入式设备都是依靠串口发送和接收的。

串口又分为TTL、RS232、RS485,那具体这些有什么区别呢?其实都一样,都是接3条线,只是TTL串口就是单片机上的串口,5V代表1,0V代表0,这样传输的时候,抗干扰能力很弱,远距离的时候,传输的波形会失真,所以后来发明了RS232和RS485,使用的是差分信号,RS232大概就是15V表示1,-15V表示0,这样传输的时候波形抗干扰就会很强,传输距离就远,差不多是这意思,下面就详细了解一下STM32的USART的使用

为什么这一节不详细分析USART的源码呢,因为我们平时使用的时候,很少见给串口搞同步的,还有什么流控之类的,也没具体去了解,到目前为止,串口的功能都裁剪的就剩下使用3条线,配置串口参数的环节,我们只关心:波特率、停止位、校验位,但是标准库的源码中,对串口这一章的配置是比较复杂的,我只是挑主要的看了个大概,并没有每一行都理清楚,所以本节不分析串口初始化源码

下面梳理一下大概的流程:

  1. 使能 RX 和 TX 引脚 GPIO 时钟和 USART 时钟
  2. 初始化 GPIO,并将 GPIO 复用到 USART
  3. 配置 USART 参数
  4. 配置中断控制器并使能 USART 接收中断
  5. 使能 USART
  6. USART 接收中断服务函数实现数据接收和发送

第一和第二基本没啥难度了,对引脚的初始化,第三就是配置波特率等等,串口最重要的是,下面这个,只要掌握了这个,就算玩明白了,那我们就来详细的看一下几个标志位

标志位!对串口最重要的就是几个发送和接收的标志位,以及发送和接收函数


//USART初始化结构体
typedef struct {
    uint32_t USART_BaudRate;            // 波特率
    uint16_t USART_WordLength;          // 字长
    uint16_t USART_StopBits;            // 停止位
    uint16_t USART_Parity;              // 校验位
    uint16_t USART_Mode;                // USART模式
    uint16_t USART_HardwareFlowControl; // 硬件流控制
} USART_InitTypeDef;
  • USART_BaudRate: 波特率设置。一般设置为2400、9600、19200、115200。标准库函数会根据设定值计算得到USARTDIV值,从而设置USART_BRR寄存器值。

  • USART_WordLength: 数据帧字长,可选8位或9位。它设定USART_CR1寄存器的M位的值。如果没有使能奇偶校验控制,一般使用8数据位;如果使能了奇偶校验则一般设置为9数据位。

  • USART_StopBits: 停止位设置,可选0.5个、1个、1.5个和2个停止位,它设定USART_CR2寄存器的STOP[1:0]位的值,一般我们选择1个停止位。

  • USART_Parity: 奇偶校验控制选择,可选USART_Parity_No(无校验)、USART_Parity_Even(偶校验)以及USART_Parity_Odd(奇校验),它设定USART_CR1寄存器的PCE位和PS位的值。

  • USART_Mode: USART模式选择,有USART_Mode_Rx和USART_Mode_Tx,允许使用逻辑或运算选择两个,它设定USART_CR1寄存器的RE位和TE位。

  • USART_HardwareFlowControl: 硬件流控制选择,只有在硬件流控制模式才有效,可选有⑴使能RTS、⑵使能CTS、⑶同时使能RTS和CTS、⑷不使能硬件流。

  • 当使用同步模式时需要配置SCLK引脚输出脉冲的属性,标准库使用一个时钟初始化结构体USART_ClockInitTypeDef来设置,该结构体内容也只有在同步模式才需要设置。

  • 下面是从野火例程白嫖过来的初始化函数,写的很好很精髓,很香只能说,可以参考一下

    /**
    * 串口宏定义,不同的串口挂载的总线和IO不一样,移植时需要修改这几个宏
    */
    
    // 串口1-USART1
    #define  DEBUG_USARTx                   USART1
    #define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
    #define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
    #define  DEBUG_USART_BAUDRATE           115200
    
    // USART GPIO 引脚宏定义
    #define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
    #define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
    
    #define  DEBUG_USART_TX_GPIO_PORT       GPIOA
    #define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
    #define  DEBUG_USART_RX_GPIO_PORT       GPIOA
    #define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10
    
    #define  DEBUG_USART_IRQ                USART1_IRQn
    #define  DEBUG_USART_IRQHandler         USART1_IRQHandler
    
    static void NVIC_Configuration(void)
    {
        NVIC_InitTypeDef NVIC_InitStructure;
    
        /* 嵌套向量中断控制器组选择 */
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
        /* 配置USART为中断源 */
        NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
        /* 抢断优先级为1 */
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
        /* 子优先级为1 */
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
        /* 使能中断 */
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        /* 初始化配置NVIC */
        NVIC_Init(&NVIC_InitStructure);
    }
    
    void USART_Config(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
        USART_InitTypeDef USART_InitStructure;
    
        // 打开串口GPIO的时钟
        DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
    
        // 打开串口外设的时钟
        DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
    
        // 将USART Tx的GPIO配置为推挽复用模式
        GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
    
        // 将USART Rx的GPIO配置为浮空输入模式
        GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
    
        // 配置串口的工作参数
        // 配置波特率
        USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
        // 配置 针数据字长
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        // 配置停止位
        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(DEBUG_USARTx, &USART_InitStructure);
    
        // 串口中断优先级配置
        NVIC_Configuration();
    
        // 使能串口接收中断
        USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
    
        // 使能串口
        USART_Cmd(DEBUG_USARTx, ENABLE);
    }
    
    

    梳理一下流程:

    1. 引脚配置,引脚使能
    2. 因为串口接收是可以触发中断的,轮询的方式太low了,所以配置串口接收中断
    3. USART参数配置
    4. 使能串口,使能接收中断
    5. 提供中断服务函数

    然后下面就可以愉快的接收和发送了

    我们先来分析5,中断服务函数,以例程中的串口为例,

    申明:很多代码呢,我本人学的时候就是用野火的学的,写的时候也参考他们的,所以现在回顾,他们的代码非常好啊,而且流程就是这样,你说我原创博客就该全部自己写,没必要的其实,那我怎么写也是这么写,很多代码是野火例程中抄过来的,不喜勿看

    void DEBUG_USART_IRQHandler(void)
    {
        uint8_t ucTemp;
        if (USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET) {
            ucTemp = USART_ReceiveData( DEBUG_USARTx );
            USART_SendData(DEBUG_USARTx,ucTemp);
        }
    }
    

    USART_IT_RXNE:接收数据寄存器满标志,再简单分析,首先,当接收到数据的时候,就会立刻进入到这个函数,但是这里为什么要一直检测这个标志位呢?USART_IT_RXNE的取值只有SET和RESET两种,当接收数据完成,也就说8个bit都接收了,代表一个字节的数据接收完成,就能跳出while循环,调用了**USART_ReceiveData()**这个函数读取数据,实际上这个函数是读取DR寄存器,读DR寄存器会将这个标志位清0,所以此处没有手动清0,当然手动清0也可以,我真的服了去百度上搜这个标志位,我就想搜这玩意的作用,真的是很难搜到,搜索结果真是花里胡哨牛头不对马嘴

    接收的比较简单,还不明白的看下面,发送函数的分析

    /*****************  发送一个字符 **********************/
    void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
    {
        /* 发送一个字节数据到USART */
        USART_SendData(pUSARTx,ch);
    
        /* 等待发送数据寄存器为空 */
        while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
    }
    
    /*****************  发送字符串 **********************/
    void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
    {
        unsigned int k=0;
        do {
            Usart_SendByte( pUSARTx, *(str + k) );
            k++;
        } while (*(str + k)!='\0');
    
        /* 等待发送完成 */
        while (USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET) {
        }
    }
    

    重点重点重点:下面这看明白了我相信你就是真的明白32的串口工作原理了

    USART_FLAG_TXE:

    注意左上角紫色的框,以及发送数据的流程,当我们去调用函数**USART_SendData(pUSARTx,ch);的时候,一个字节的数据就会被写入发送数据寄存器(TDR),注意USART_FLAG_TXE这个标志位,不发送数据的时候,闲着的时候都是0,当检测到数据写入发送数据寄存器TDR的时候,就会立刻将USART_FLAG_TXE置1,然后数据移位寄存器(TSR)就开始从TDR寄存器中取数据,一位一位的取走发送出去,当被取完的时候,发送数据寄存器TDR也就为空了,所以此时USART_FLAG_TXE**标志位就会被置0 ,所以也是我们while检测这个标志的原因

    USART_FLAG_TC:

    我们来假设一个过程,当我们调用**USART_SendData(pUSARTx,ch)发送一个字节的数据的时候,此时数据移位寄存器(TSR)就会立刻从数据发送寄存器(TDR)中开始搬运数据并一位一位发送出去,但是受限于波特率,搬运的速度会远大于发送的速率,而只要搬运完了,USART_FLAG_TXE这个标志位就会立刻被置0,我们while循环检测这个标志,此时就会被唤醒,看我上面发送字符串的函数就能看出来,下一个数据立刻又被写入发送数据寄存器(TDR),然后移位数据寄存器又开始搬运了,注意注意:此时移位数据寄存器中,上一个字节的数据还不一定发送完成呢,所以会为空吗?不会,所以所以:USART_FLAG_TC**这个标志位的作用是:检测数据移位寄存器是否为空,当开始发送数据,数据移位寄存器从数据发送寄存器中搬运数据,这个标志位就会被置1,当所有数据发送完成,移位寄存器为空时,就会被置0,所以通过这个标志位,可以实现在字节流的传输方式中实现帧传输

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32串口(USART)深入解析

    发表评论