[其他ST产品] STM32串口(USART)详解

[复制链接]
1071|19
 楼主| 回复就哭哭 发表于 2023-7-25 12:24 | 显示全部楼层 |阅读模式
概述:
串口的重点难点要点就是几个标志位

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

重点重点重点:下面这看明白了我相信你就是真的明白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,所以通过这个标志位,可以实现在字节流的传输方式中实现帧传输

评论

———————————————— 版权声明:本文为CSDN博主「__void」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/Newmamahaha/article/details/125431488  发表于 2023-7-25 12:24
 楼主| 回复就哭哭 发表于 2023-7-25 12:24 | 显示全部楼层
我们常常容易混淆通讯方式和通讯协议,很多初学者分不清这些东西,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条线,配置串口参数的环节,我们只关心:波特率、停止位、校验位,但是标准库的源码中,对串口这一章的配置是比较复杂的,我只是挑主要的看了个大概,并没有每一行都理清楚,所以本节不分析串口初始化源码
 楼主| 回复就哭哭 发表于 2023-7-25 12:25 | 显示全部楼层
下面梳理一下大概的流程:

使能 RX 和 TX 引脚 GPIO 时钟和 USART 时钟
初始化 GPIO,并将 GPIO 复用到 USART 上
配置 USART 参数
配置中断控制器并使能 USART 接收中断
使能 USART
在 USART 接收中断服务函数实现数据接收和发送
第一和第二基本没啥难度了,对引脚的初始化,第三就是配置波特率等等,串口最重要的是,下面这个,只要掌握了这个,就算玩明白了,那我们就来详细的看一下几个标志位
 楼主| 回复就哭哭 发表于 2023-7-25 12:25 | 显示全部楼层
标志位!对串口最重要的就是几个发送和接收的标志位,以及发送和接收函数

9099664bf4eac5d57c.png

672264bf4eb555aa1.png
 楼主| 回复就哭哭 发表于 2023-7-25 12:25 | 显示全部楼层
  1. //USART初始化结构体
  2. typedef struct {
  3.     uint32_t USART_BaudRate;            // 波特率
  4.     uint16_t USART_WordLength;          // 字长
  5.     uint16_t USART_StopBits;            // 停止位
  6.     uint16_t USART_Parity;              // 校验位
  7.     uint16_t USART_Mode;                // USART模式
  8.     uint16_t USART_HardwareFlowControl; // 硬件流控制
  9. } USART_InitTypeDef;
 楼主| 回复就哭哭 发表于 2023-7-25 12:25 | 显示全部楼层
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来设置,该结构体内容也只有在同步模式才需要设置。
 楼主| 回复就哭哭 发表于 2023-7-25 12:26 | 显示全部楼层
下面是从野火例程白**过来的初始化函数,写的很好很精髓,很香只能说,可以参考一下
  1. /**
  2. * 串口宏定义,不同的串口挂载的总线和IO不一样,移植时需要修改这几个宏
  3. */

  4. // 串口1-USART1
  5. #define  DEBUG_USARTx                   USART1
  6. #define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
  7. #define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
  8. #define  DEBUG_USART_BAUDRATE           115200

  9. // USART GPIO 引脚宏定义
  10. #define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
  11. #define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd

  12. #define  DEBUG_USART_TX_GPIO_PORT       GPIOA
  13. #define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
  14. #define  DEBUG_USART_RX_GPIO_PORT       GPIOA
  15. #define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10

  16. #define  DEBUG_USART_IRQ                USART1_IRQn
  17. #define  DEBUG_USART_IRQHandler         USART1_IRQHandler

  18. static void NVIC_Configuration(void)
  19. {
  20.     NVIC_InitTypeDef NVIC_InitStructure;

  21.     /* 嵌套向量中断控制器组选择 */
  22.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

  23.     /* 配置USART为中断源 */
  24.     NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
  25.     /* 抢断优先级为1 */
  26.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  27.     /* 子优先级为1 */
  28.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  29.     /* 使能中断 */
  30.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  31.     /* 初始化配置NVIC */
  32.     NVIC_Init(&NVIC_InitStructure);
  33. }

  34. void USART_Config(void)
  35. {
  36.     GPIO_InitTypeDef GPIO_InitStructure;
  37.     USART_InitTypeDef USART_InitStructure;

  38.     // 打开串口GPIO的时钟
  39.     DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);

  40.     // 打开串口外设的时钟
  41.     DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);

  42.     // 将USART Tx的GPIO配置为推挽复用模式
  43.     GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
  44.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  45.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  46.     GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  47.     // 将USART Rx的GPIO配置为浮空输入模式
  48.     GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
  49.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  50.     GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);

  51.     // 配置串口的工作参数
  52.     // 配置波特率
  53.     USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
  54.     // 配置 针数据字长
  55.     USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  56.     // 配置停止位
  57.     USART_InitStructure.USART_StopBits = USART_StopBits_1;
  58.     // 配置校验位
  59.     USART_InitStructure.USART_Parity = USART_Parity_No ;
  60.     // 配置硬件流控制
  61.     USART_InitStructure.USART_HardwareFlowControl =
  62.         USART_HardwareFlowControl_None;
  63.     // 配置工作模式,收发一起
  64.     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  65.     // 完成串口的初始化配置
  66.     USART_Init(DEBUG_USARTx, &USART_InitStructure);

  67.     // 串口中断优先级配置
  68.     NVIC_Configuration();

  69.     // 使能串口接收中断
  70.     USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);

  71.     // 使能串口
  72.     USART_Cmd(DEBUG_USARTx, ENABLE);
  73. }

 楼主| 回复就哭哭 发表于 2023-7-25 12:26 | 显示全部楼层
梳理一下流程:

引脚配置,引脚使能
因为串口接收是可以触发中断的,轮询的方式太low了,所以配置串口接收中断
USART参数配置
使能串口,使能接收中断
提供中断服务函数
然后下面就可以愉快的接收和发送了
 楼主| 回复就哭哭 发表于 2023-7-25 12:26 | 显示全部楼层
我们先来分析5,中断服务函数,以例程中的串口为例,

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

  1. void DEBUG_USART_IRQHandler(void)
  2. {
  3.     uint8_t ucTemp;
  4.     if (USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET) {
  5.         ucTemp = USART_ReceiveData( DEBUG_USARTx );
  6.         USART_SendData(DEBUG_USARTx,ucTemp);
  7.     }
  8. }
 楼主| 回复就哭哭 发表于 2023-7-25 12:26 | 显示全部楼层
USART_IT_RXNE:接收数据寄存器满标志,再简单分析,首先,当接收到数据的时候,就会立刻进入到这个函数,但是这里为什么要一直检测这个标志位呢?USART_IT_RXNE的取值只有SET和RESET两种,当接收数据完成,也就说8个bit都接收了,代表一个字节的数据接收完成,就能跳出while循环,调用了**USART_ReceiveData()**这个函数读取数据,实际上这个函数是读取DR寄存器,读DR寄存器会将这个标志位清0,所以此处没有手动清0,当然手动清0也可以,我真的服了去百度上搜这个标志位,我就想搜这玩意的作用,真的是很难搜到,搜索结果真是花里胡哨牛头不对马嘴
 楼主| 回复就哭哭 发表于 2023-7-25 12:27 | 显示全部楼层
接收的比较简单,还不明白的看下面,发送函数的分析
976964bf4f17e90b6.png
 楼主| 回复就哭哭 发表于 2023-7-25 12:27 | 显示全部楼层
  1. /*****************  发送一个字符 **********************/
  2. void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
  3. {
  4.     /* 发送一个字节数据到USART */
  5.     USART_SendData(pUSARTx,ch);

  6.     /* 等待发送数据寄存器为空 */
  7.     while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
  8. }

  9. /*****************  发送字符串 **********************/
  10. void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
  11. {
  12.     unsigned int k=0;
  13.     do {
  14.         Usart_SendByte( pUSARTx, *(str + k) );
  15.         k++;
  16.     } while (*(str + k)!='\0');

  17.     /* 等待发送完成 */
  18.     while (USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET) {
  19.     }
  20. }
 楼主| 回复就哭哭 发表于 2023-7-25 12:27 | 显示全部楼层
重点重点重点:下面这看明白了我相信你就是真的明白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检测这个标志的原因
 楼主| 回复就哭哭 发表于 2023-7-25 12:27 | 显示全部楼层
USART_FLAG_TC:
我们来假设一个过程,当我们调用**USART_SendData(pUSARTx,ch)发送一个字节的数据的时候,此时数据移位寄存器(TSR)就会立刻从数据发送寄存器(TDR)中开始搬运数据并一位一位发送出去,但是受限于波特率,搬运的速度会远大于发送的速率,而只要搬运完了,USART_FLAG_TXE这个标志位就会立刻被置0,我们while循环检测这个标志,此时就会被唤醒,看我上面发送字符串的函数就能看出来,下一个数据立刻又被写入发送数据寄存器(TDR),然后移位数据寄存器又开始搬运了,注意注意:此时移位数据寄存器中,上一个字节的数据还不一定发送完成呢,所以会为空吗?不会,所以所以:USART_FLAG_TC**这个标志位的作用是:检测数据移位寄存器是否为空,当开始发送数据,数据移位寄存器从数据发送寄存器中搬运数据,这个标志位就会被置1,当所有数据发送完成,移位寄存器为空时,就会被置0,所以通过这个标志位,可以实现在字节流的传输方式中实现帧传输
zhuomuniao110 发表于 2023-7-25 21:46 | 显示全部楼层
看了一下都是以前的老库,新的都是LL或者HAL。
Clyde011 发表于 2023-12-29 07:16 | 显示全部楼层

要把纹波电流平均分布
公羊子丹 发表于 2023-12-29 08:09 | 显示全部楼层

发射出过量的电磁干扰(EMI)
万图 发表于 2023-12-29 09:12 | 显示全部楼层

典型的转换时间大约是50ns
Uriah 发表于 2023-12-29 10:15 | 显示全部楼层

这种方法可以使散热体积和表面面积增大两倍多
帛灿灿 发表于 2023-12-29 12:11 | 显示全部楼层

负载环能以直流方式汲取能量
您需要登录后才可以回帖 登录 | 注册

本版积分规则

29

主题

436

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部