STM32串口轮询收发实战教程
7-1 轮询模式数据收发1. ⽬标本⽰例将会演⽰ UART 阻塞模式数据发送、接收接⼝的使⽤。这类接⼝是最简单、直观的⾮常适合 第⼀个⼊⻔类⽰例。由于是接⼝是阻塞模式就必须以轮询的⽅式进⾏使⽤。 本⽰例将实现以下串⼝数据收、发逻辑1. 串⼝配置为波特率 115200bps 8 位数据奇校验停⽌位 1 ⽆流控。2. 使⽤串⼝阻塞模式接⼝接收串⼝上位机发送的测试数据。3. 使⽤串⼝阻塞模式接⼝将接收到的数据回传到串⼝上位机。4. 使⽤绿⾊ LED 指⽰灯闪烁状态表⽰串⼝正在进⾏数据传输。2. 原理图分析1. 需要⼀个可以与电脑连接的串⼝。如 下图 所⽰是开发板的串⼝电路该电路使⽤了⼀个 USB 转USART 的芯⽚ CH340K 。这个转换电路就等效于 “ USB 转 UART TTL ” 模块已经在电路板上实现了转换功能就不需要再使⽤额外的转换模块。原因你的电脑尤其是笔记本已经没有原⽣串⼝了但开发板的主控芯⽚如STM32只认识TTL串⼝电平信号⼆者⽆法直接对话所以需要⼀个“翻译官”我们的板子上有usb转换成串口协议的CH340K模块通过电脑 USB 与开发板的连接⽰意图如 下图 所⽰。虽然电脑使⽤的是 USB 接⼝但对于软件应⽤ 来讲其实是串⼝这是为了解决现在多数电脑都没有标配串⼝的问题。3. 需求分析1. 需要⼀个绿⾊ LED 指⽰灯⽤于指⽰串⼝数据交互状态2. 需要使⽤ USART 阻塞模式接收串⼝上位机发送的数据3. 需要使⽤ USART 阻塞模式发送数据到串⼝上位机⼯具4. 学习⽬标1. 掌握串⼝基本参数的配置⽅法 CubeMX 相关配置以及代码实现 2. 掌握串⼝阻塞模式发送接⼝的使⽤⽅法3. 掌握串⼝阻塞模式接收接⼝的使⽤⽅法4. 掌握串⼝上位机⼯具软件的使⽤⽅法5. ⼯程配置5.1 使⽤ CubeMX 创建⼯程我们开发板的预留的仿真器调试接⼝是 SWD ( Serial Wire Debug ) 在如下图中 所标识区域 选择 “ Serial Wire ” 。 注意! 这⼀步⾮常重要.如果没有修改这⾥, 使⽤了默认的 No Debug, 第⼀次烧录程序可以成功, 但是后续烧录程序就会烧录失败 DAP vs SWD DAP Link是⼀种调试器硬件⽽SWD是⼀种调试通信协议DAP Link通过SWD协议与⽬标芯 ⽚通信。• DAP 全称是 Debug Access Port 调试访问端⼝是⼀个物理仿真器• SWD 全称是 Serial Wire Debug (串⾏线调试)• 两者的关系类似DAP考试的时候坐在座位上的同学本⼈SWD⽤来传递纸条的通道约定好的⼿势或者暗号当在 CubeMX 中错误配置或禁⽌ debug 接⼝ 如 JTAG / SWD 时烧录完程序后后导致⽆法通过调试器连接⽬标 STM32 设备。就是说DAP LINK下载器需要在 SWD的协议下才能发挥作用5.2 开启外部时钟源在如下图所标识区域选择 “ Crystal / Ceramic Resonator ” 表⽰选择外部晶振做为 HSE5.3 配置时钟树我们开发板上是⼀个 8MHz 的 HSE 晶振如下图标识区域所⽰需要选通 8MHz 晶振作为时钟 源并将系统运⾏最⼤频率配置到 72MHz 。5.4 配置USART1在通信部分选择 USART1 选择异步通信模式 STM32CubeMX 默认就选择了对应的针脚为什么直接回答使能了串⼝就会⾃动配置对应的管脚 ⻅附录《针脚复⽤清单》在串口这里我们配置异步通信这个也是串口模块常用的通信配置GPIO配置使⽤默认的即可现场确认参数模式配置 USART1 相关参数如上所⽰串⼝的基本参数配置项各配置项的含义如下• 图中 ① 标识区域配置为异步模式 ( Asynchronous ) 。 STM32 的 USART 还⽀持很多其他的模式最核⼼的 UART 通信模式就选择 Asynchronous 模式。• 图中 ② 标识区域不使⽤硬件流控制信号只使⽤了 Rx / Tx 信号。• 图中 ③ 标识区域配置波特率为 115200 这个波特率是⼀个⾮常常⻅的值。• 图中 ④ 标识区域数据⻓度配置为 9Bits 其中 8Bits 是数据 1Bits 是校验位。• 图中 ⑤ 标识区域配置为奇校验模式 Odd 。• 图中 ⑥ 标识区域配置停⽌位占 1 位 ( 时间⻓度 )。• 图中 ⑦ 标识区域配置为数据收发模式STM32 的 USART 还⽀持配置成 “只接收” 或 “只发送” 模式通常直接配置为收发模式 Receive and Transmit 即可。• 图中 ⑧ 标识区域使⽤默认配置值即可。过采样 OverSampling 是 USART 接收器⽤来检测和识别输⼊信号的技术。它决定了 USART 在⼀个位周期内对接收信号进⾏多少次采样来判断该位的逻辑状态。5.5 配置指⽰ LED 灯就选择LED1吧这个不解释5.6 项⽬配置并⽣成6. 代码编写代码块 1 /* USER CODE BEGIN PV */uint8_t recv_byte 0; uint32_t cnt 0; /* USER CODE END PV */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { if (HAL_OK HAL_UART_Receive(huart1, recv_byte, 1, HAL_MAX_DELAY)) { // HAL_Delay(100); // bug测试 // 这⾥有坑为了看到效果不建议设置为HAL_MAX_DELAY因为发送要等待TC1这样会 导致HAL_UART_Receive接收变慢但是发送端是⼀直在发送的 // 这样可能会丢失数据设置为0直接交给硬件然后⽴即返回进⾏接收 HAL_UART_Transmit(huart1, recv_byte, 1, 0); } c nt; if (cnt 10) // 收到10个字节就翻转⼀下 { cnt 0; HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_8); } / * USER CODE END WHILE */ /* USER CODE BEGIN 3 */ }这里是收发10次收发以后进行电平的反转细节就是发送的时候不可以用HAL_MAX_DELAY因为这样会导致接收的时间不够数据接受不完整。6.1.2 HAL_UART_Receive 函数HAL库函数 HAL_UART_Receive 的主要核⼼逻辑如下在接收数据个数还未达到期望值 ( Size ) 时通过轮询等待接收缓冲⾮空标志 RXNE 置位(1)⼀旦置位(1)即从 DR 寄存器取数并按字⻓ / 校验掩码提取有效位 9 位⽆校验⽤ 0x01FF 9 位或8 位有校验⽤ 0x00FF 8 位有校验⽤ 0x007F 写⼊⽤⼾缓冲区、移动指针并递减计数直⾄接收完成。该函数的具体功能与⽤法✨ 函数 HAL_UART_Receive 说明 1. 函数概述以阻塞⽅式接收指定数量的数据元素 逐个元素等待、逐个元素读取 。2 3 4 5 6 7 8 910 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 2 . 函数原型HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef*huart,uint8_t *pData,uint16_t Size,uint32_t Timeout)3. 参数说明◦ huart UART 句柄需已完成初始化并处于就绪状态。◦ pData 接收缓冲区指针。 9 位⽆校验时按 uint16_t* 使⽤否则按uint8_t* 使⽤。◦ Size 要接收的数据元素个数 不是字节数 。 9 位⽆校验时是 u16 元素个数其它情形是 u8 元素个数。◦ Timeout 超时时间 毫秒 。与 HAL_GetTick() 配合判定HAL_MAX_DELAY 表⽰⽆限等待。4 . 返回值◦ HAL_OK 接收成功 读满 Size 个元素 。◦ HAL_TIMEOUT 在等待 RXNE 标志期间超时。◦ HAL_BUSY 接收状态⾮就绪 已有接收过程正在进⾏ 。◦ HAL_ERROR ⼊参⾮法 pData NULL 或 Size 0 。第一个参数 是串口的句柄pDate是输入的参数数组Size是传入的字节长度Timeout是经过的最长时间长度我们读取完数据寄存器DR会自动清空这个步骤是硬件帮我们完成的。## 拆开讲STM32 F1 的 USART1. **CPU 这边写 TDR走 APB 时钟**STM32F1 的 USART 挂载在 APB2USART1/APB1USART2/3。CPU 内核通过总线**以 APB 时钟速度**往 USART_DRHAL里叫TDR 写入 1 字节数据。这个动作只是一次寄存器写入是 CPU 总线操作非常快。2. **USART 移位发送波特率时钟**USART 内部有一个波特率发生器由 APB 时钟 BRR 分频得到产生**移位时钟**。移位寄存器按照这个波特时钟一位一位把 bit 推到 TX 引脚比如 9600 波特就是每约 104us 出 1bit。两个时钟源来源都源自 APB但频率完全解耦APB 时钟比如 72M是给寄存器访问用波特时钟是 APB 经过 BRR 分频后的低速时钟9600、115200。为什么可以分开跑发送缓存机制USART 发送有两级缓冲TDR(DR) ←→ 移位寄存器1. CPU 写数据到 TDR只要 TDR 不为空TXE标志位就清零2. 一旦**移位寄存器空了硬件自动把 TDR 里面的数据搬运进移位寄存器然后置 TXE1通知 CPU可以写下一个字节了。重点移位寄存器在慢悠悠按波特率往外发 bit 的时候CPU 完全可以继续往 TDR 填下一包数据只要 TXE 标志允许。也就是移位发 bit波特时钟驱动 和 CPU 写 TDR内核 / APB 总线是并行的不是等发完这一整帧 CPU 才能写第二个。### 举个例子1152001 字节 10bit起始 8 数据 停止发 1 字节大概需要 10/115200 ≈ 86.8us。但是 CPU 写 TDR 寄存器72M APB 下只需要**几个 ns**就写完了。只要 TXE 标志及时置 1CPU 可以连续填充不需要等待一帧发送完成。## 和你之前代码的关联- 轮询发送 HAL_UART_Transmit里面循环等 TXE等 TDR 空才继续填- 如果你理解这个两级缓冲就能明白为什么 USART 支持**连续发送、流水线**中断发送就是利用 TXE 中断在移位发送的同时 CPU 提前填充 TDR。## 容易踩的误区❌ 不是两套完全独立的晶振**根时钟都是 APB**但是经过分频之后**工作节拍相互独立**寄存器写入和 bit 移位可以同时进行互不等待。❌ 不是写 TDR 就直接送到 TX 引脚中间隔了移位寄存器这就是并行的根源。cpu向TDR写入的时候和usart移位的时钟是不一样的可以分别进行工作,就是向DR写入的速率是APB2决定的速度很快向串口发送的移位寄存器里面写是是经过分频的速度慢统一编制和寄存器的顺序严格对应6.1.3 HAL_UART_Transmit 函数库函数 HAL_UART_Transmit 的主要核⼼逻辑如下根据配置选择按 uint16_t 9 位⽆校验掩码 0x01FF 或 uint8_t 其余情况掩码0xFF 发送每次发送循环先等待 TXE 标志置位再将当前数据写⼊ DR 寄存器并递减计数全部写完后再等待 TC 标志表⽰发送完成。该函数的具体功能与⽤法 ✨ 函数 HAL_UART_Transmit 说明 1. 函数概述以阻塞⽅式发送指定数量的数据元素逐元素等待、逐元素写寄存器。2. 函数原型HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef*huart,const uint8_t *pData,uint16_t Size,uint32_t Timeout)3 . 参数说明◦ huart UART 句柄需已初始化且处于就绪状态。◦ pData 待发送数据缓冲区指针。 9 位⽆校验时按 const uint16_t* 解析否则按 const uint8_t* 。◦ Size 要发送的数据元素个数 ⾮字节数 。 9 位⽆校验时是 u16 元素数其余是 u8 元素数。◦ Timeout 超时时间毫秒。与 HAL_GetTick() 配合判定HAL_MAX_DELAY 表⽰⽆限等待。4 . 返回值◦ HAL_OK 成功发送完 Size 个元素并等待 TC 完成。◦ HAL_TIMEOUT 等待 TXE 或 TC 期间超时。◦ HAL_BUSY 发送状态⾮就绪 已有发送正在进⾏ 。◦ HAL_ERROR ⼊参⾮法 pData NULL 或 Size 0 。细节• 这两个函数就是阻塞式串⼝收发数据的函数typedef struct { uint32_t BaudRate; // 波特率 uint32_t WordLength; // 数据位长度 uint32_t StopBits; // 停止位 uint32_t Parity; // 校验位 uint32_t Mode; // 收发模式 uint32_t HwFlowCtl; // 硬件流控 uint32_t OverSampling; // 过采样 } UART_InitTypeDef;typedef struct { __IO uint32_t SR; /*! USART 状态寄存器地址偏移0x00 */ __IO uint32_t DR; /*! USART 数据寄存器地址偏移0x04 */ __IO uint32_t BRR; /*! USART 波特率寄存器地址偏移0x08 */ __IO uint32_t CR1; /*! USART 控制寄存器1地址偏移0x0C */ __IO uint32_t CR2; /*! USART 控制寄存器2地址偏移0x10 */ __IO uint32_t CR3; /*! USART 控制寄存器3地址偏移0x14 */ __IO uint32_t GTPR; /*! USART 保护时间与预分频寄存器地址偏移0x18 */ } USART_TypeDef;这个就是我们的句柄定义成的结构体在这个其中我们关注第一个和第二个结构体第一个结构体里面定义着串口的各种寄存器第二个结构体里面是我们要配置的各种参数✅8 位无校验 / 9 位模式0xFF读取全部低 8 位 • ✅8 位带奇偶校验0x7F剔除最高位的校验位只拿 7 位有效数据HAL_UART_Receive(huart1, recv_byte, 1, HAL_MAX_DELAY) 阻塞读一个字节本质就是**循环等待 SR 寄存器的 RXNE 标志位第 5 位变成 1**RXNE1 代表硬件收到数据放到 DR 寄存器了这时才去读 DR 拿数据。---## 一层一层往下拆解### 1. 调用 UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout)参数含义- huart串口句柄- UART_FLAG_RXNE要等待的标志就是 SR 寄存器里的 RXNE 位- RESET我们要等到【这个标志不等于 RESET也就是变成 SET (1)】 函数逻辑只要标志 RESET0就一直循环等超时就退出返回失败。### 2. 里面的 while 循环while ((__HAL_UART_GET_FLAG(huart, Flag) ? SET : RESET) Status)翻译成大白话只要 __HAL_UART_GET_FLAG 判断出来 RXNE 是 0没收到数据就一直卡在 while 循环里。### 3. 宏 __HAL_UART_GET_FLAG#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)-Instance-SR (__FLAG__)) (__FLAG__))拆解1. __HANDLE__-Instance-SR访问串口状态寄存器 SR就是前面 USART_TypeDef 里第一个寄存器2. __FLAG__和 RXNE 对应的掩码做按位与3. 判断结果是不是等于掩码- 等于 → RXNE 位是 1返回 trueSET- 不等于 → RXNE 位是 0返回 falseRESET### 4. RXNE 是什么图里的方框RXNE 是 SR 寄存器的第 5 位- **RXNE 1**移位寄存器已经把收到的数据搬到 DR 数据寄存器里了现在 DR 里有有效数据可以读了- **RXNE 0**还没有收到新数据DR 是空的 重点特性**一旦你读取 DR 寄存器硬件会自动把 RXNE 清零**准备接收下一个字节。### 5. 宏定义关系#define UART_FLAG_RXNE ((uint32_t)USART_SR_RXNE)#define USART_SR_RXNE_Pos (5U)#define USART_SR_RXNE_Msk (0x1UL USART_SR_RXNE_Pos)#define USART_SR_RXNE USART_SR_RXNE_Msk就是把 1 左移 5 位也就是二进制 1000000x20对应 SR 的 bit5。### 6. 超时机制UART_WaitOnFlagUntilTimeout 不是死等- 拿进入函数时的系统滴答时间 tickstart- while 每一轮都会对比当前时间如果超过设定的 Timeout 时间直接跳出循环返回非 HAL_OK上层就知道接收超时失败。你这里写 HAL_MAX_DELAY就是无限等永远不会超时。# 完整流程串起来接收 1 字节1. 调用 HAL_UART_Receive2. 进入等待函数不停读 huart-Instance-SR检查 bit5RXNE3. 外部发数据过来串口硬件收到硬件自动把 RXNE 置 14. __HAL_UART_GET_FLAG 检测到 RXNE1跳出 while 循环5. HAL 底层代码去读取 huart-Instance-DR把数据拿出来存到 recv_byte6. ✅ 读 DR 这个动作硬件自动清除 RXNE 标志准备收下一个字节# 和你上一段代码的关联跳出等待之后马上就会执行你上一张图那段判断判断是 9 位 / 8 位无校验 / 8 位带校验然后用 0xFF 或者 0x7F 掩码从 DR 里把有效数据摘出来丢掉校验位。# 补充两个容易踩坑的知识点1. RXNE 清零方式读 DR 自动清零这是硬件行为不用你手动写寄存器清2. 阻塞接收的缺点卡在 while 循环的时候CPU 啥别的事情都干不了这就是阻塞结论如果SR寄存器里面是RESET结果是1就是等效SET!RESET(state)就退出while循环如果是SET就一直循环UART_WaitOnFlagUntilTimeout有这个函数就也不是死等和上面的逻辑是一样的一直检测sr的第七位是否是1最后还需要等待tc标志位是1才是全部发完加延时就会出现数据丢失的情况int fgetc(FILE *f) { uint8_t ch 0; while (1) { if(HAL_UART_Receive(huart1, ch, 1, HAL_MAX_DELAY) ! HAL_OK) { return EOF; } // 忽略 \r只保留 \n if (ch ! \r) { return (int)ch; } } }while(1) 死循环目的**过滤掉回车符 \r**很多串口工具Windows换行是 \r\n 两个连续字节回车 换行- \r CR 回车ASCII 0x0D- \n LF 换行ASCII 0x0A这段代码逻辑**收到 \r 就丢掉继续循环等下一个字符其他字符直接返回**效果Windows 发的 \r\n经过这个函数之后\r 被吞掉上层程序只会收到 \n统一换行符避免识别两行换行。7. 串⼝上位机⼯具使⽤介绍7.1.1 软件安装下载链接https://sourceforge.net/projects/comtool/ 该软件是免安装的将软件包解压缩开即可。如 下图 所⽰解压缩后 comtool_windows_vxx / comtool / comtool.exe 就是可执⾏⽂件双击即可打开软件。7.1.2 第⼀次启动配置1. 将语⾔修改为中⽂如 下图 所⽰选择 “中⽂ 简体” 。2. 将不使⽤的⻚⾯关掉如 下图 所⽰可以将 “协议”、“终端”、“图标” ⻚⾯关闭掉只留下 “收发” ⻚⾯。7.1.3 ⼯作界⾯介绍1. 如 上图 中 ① 标识区域是串⼝参数的配置区域学习过串⼝基础知识后这些参数⼀⽬了然。2. 如 上图 中 ② 标识区域是串⼝接收设置区域例如数据格式 ⼆进制、字符串 ⾃动换⾏ 显⽰时间戳。3. 如 上图 中 ③ 标识区域是串⼝发送设置区域数据格式 ⼆进制、字符串 转义字符⾃动解 析循环发送等。4. 如 上图 中 ④ 标识区域是历史发送数据缓存区域。 5. 如 上图 中 ⑤ ⑦ 标识区域是数据发送区域包括数据区和发送按钮。6. 如 上图 中 ⑥ ⑧ 标识区域是数据接收区域包括接收到的数据显⽰区域和显⽰区域清除按钮。8. 下载验证8.1 ⽰例验证1. 把编译好的程序下载到开发板并复位。2. 上位机按照 下图 所⽰配置 串⼝号请根据电脑识别结果选择 。3. 输⼊任何测试字符串点击 “ 发送 ” 按钮开始发送测试数据可以看到如 下图 接收显⽰区域所⽰的每次发送都有⼀条相同的数据回传。9. 源码分析9.1 Usart1物理地址#define USART1 ((USART_TypeDef *)USART1_BASE)#define USART1_BASE (APB2PERIPH_BASE 0x00003800UL) #define APB2PERIPH_BASE (PERIPH_BASE 0x00010000UL) #define PERIPH_BASE 0x40000000UL /*! Peripheral base address in the alias region */ typedef struct { __IO uint32_t SR; /*! USART Status register, Address offset: 0x00 */ __IO uint32_t DR; /*! USART Data register, Address offset: 0x04 */ __IO uint32_t BRR; /*! USART Baud rate register, Address offset: 0x08 */ __IO uint32_t CR1; /*! USART Control register 1, Address offset: 0x0C */ __IO uint32_t CR2; /*! USART Control register 2, Address offset: 0x10 */ __IO uint32_t CR3; /*! USART Control register 3, Address offset: 0x14 */ __IO uint32_t GTPR; /*! USART Guard time and prescaler register, Address offset: 0x18 */ } USART_TypeDef;串口是片上外设和我们之前讲的统一地址偏置原则是相符合的9.2 初始化9.2.1 结构体初始化代码块 static void MX_USART1_UART_Init(void) { /* USER CODE BEGIN USART1_Init 0 */ /* USER CODE END USART1_Init 0 */ /* USER CODE BEGIN USART1_Init 1 */ /* USER CODE END USART1_Init 1 */ huart1.Instance USART1; /* UART 寄存器基 地址 USART1 */ huart1.Init.BaudRate 115200; /* 波特率 115200 */ huart1.Init.WordLength UART_WORDLENGTH_9B; /* 字⻓ 9 位含校 验有效数据 8 位 */ huart1.Init.StopBits UART_STOPBITS_1; /* 停⽌位 1 位 */ huart1.Init.Parity UART_PARITY_ODD; /* 奇校验 */ huart1.Init.Mode UART_MODE_TX_RX; /* 收发使能 */ huart1.Init.HwFlowCtl UART_HWCONTROL_NONE; /* ⽆硬件流控 */ huart1.Init.OverSampling UART_OVERSAMPLING_16; /* 16 倍过采样 */ f (HAL_UART_Init(huart1) ! HAL_OK) /* 调⽤ HAL 初始 化失败则进⼊错误处理 */ { Error_Handler(); } / * USER CODE BEGIN USART1_Init 2 */ /* USER CODE END USART1_Init 2 */ }代码块 /** * brief UART 句柄结构体定义 * HAL 库使⽤该结构体管理 UART 外设的所有通信状态和参数 */ typedef struct __UART_HandleTypeDef { USART_TypeDef *Instance; /*! UART 寄存器基地址指针指 向 USART1/USART2/... 外设寄存器 */ UART_InitTypeDef Init; /*! UART 通信参数配置波特率、 数据位、校验位、停⽌位等 */ const uint8_t *pTxBuffPtr; /*! 指向 UART 发送缓冲区的指针 要发送的数据源 */ uint16_t TxXferSize; /*! UART 本次发送的总字节数 */ __IO uint16_t TxXferCount; /*! UART 当前剩余待发送字节数 递减计数器 */ uint8_t *pRxBuffPtr; /*! 指向 UART 接收缓冲区的指针 存放接收到的数据 */ uint16_t RxXferSize; /*! UART 本次接收的总字节数 */ __IO uint16_t RxXferCount; /*! UART 当前剩余待接收字节数 递减计数器 */ __IO HAL_UART_RxTypeTypeDef ReceptionType; /*! 当前正在进⾏的接收类型单次 接收/连续接收 */ __IO HAL_UART_RxEventTypeTypeDef RxEventType; /*! 接收事件类型⽤于 DMA 传输 完成事件等 */ DMA_HandleTypeDef *hdmatx; /*! UART 发送 DMA 句柄指针使 ⽤ DMA 发送时有效 */ DMA_HandleTypeDef *hdmarx; /*! UART 接收 DMA 句柄指针使 ⽤ DMA 接收时有效 */ HAL_LockTypeDef Lock; /*! 资源锁定对象防⽌ HAL 函数 被重⼊中断嵌套冲突 */ __IO HAL_UART_StateTypeDef gState; /*! UART 全局状态包括发送状 态 例如 HAL_UART_STATE_READY / BUSY / TIMEOUT 等 */ __IO HAL_UART_StateTypeDef RxState; /*! UART 接收状态 例如 HAL_UART_STATE_READY / BUSY_RX / TIMEOUT 等 */ __IO uint32_t ErrorCode; /*! UART 错误码记录发⽣过的错 误类型如溢出、帧错误、噪声等 */ #if (USE_HAL_UART_REGISTER_CALLBACKS 1) /* 以下为回调函数指针当启⽤回调注册功能时可⽤ */ void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*! UART 发送半完成回调DMA 半传输中断 */ void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart); /*! UART 发送完成回调 */ void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*! UART 接收半完成回调DMA 半传输中断 */ void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart); /*! UART 接收完成回调 */ void (* ErrorCallback)(struct __UART_HandleTypeDef *huart); /*! UART 错误回调 */ void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart); /*! UART 中⽌操作完成回调 */ void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); /*! UART 中⽌发送完成回调 */ void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart); /*! UART 中⽌接收完成回调 */ void (* WakeupCallback)(struct __UART_HandleTypeDef *huart); /*! UART 唤醒回调低功耗模式唤醒 */ void (* RxEventCallback)(struct __UART_HandleTypeDef *huart, uint16_t Pos); /*! UART 接收事件回调⽤于中断接收时每收到⼀个字节触发 */ void (* MspInitCallback)(struct __UART_HandleTypeDef *huart); /*! UART 硬件初始化回调MSP 层 */ void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); /*! UART 硬件反初始化回调MSP 层 */ #endif /* USE_HAL_UART_REGISTER_CALLBACKS */ } UART_HandleTypeDef我们现在就重点关心结构体里面的不看DMA等相关的代码块 /** * brief UART 初始化结构体⽤于配置 UART 通信参数 */ typedef struct { uint32_t BaudRate; /*! 波特率计算公式IntegerDivider PCLK / (16 * BaudRate) */ uint32_t WordLength; /*! 数据位数可选 ref UART_Word_Length */ uint32_t StopBits; /*! 停⽌位数量可选 ref UART_Stop_Bits */ uint32_t Parity; /*! 校验模式⽆/奇/偶可选 ref UART_Parity note 使能校验时校验位插⼊ MSB 位置 */ uint32_t Mode; /*! 收发模式使能可选 ref UART_Mode */ uint32_t HwFlowCtl; /*! 硬件流控使能可选 ref UART_Hardware_Flow_Control */ uint32_t OverSampling; /*! 过采样倍数8 或 16可选 ref UART_Over_Sampling */ } UART_InitTypeDef;这个是句柄中第二个结构体定义9.2.2 标志位选项设置调1到2个标志位选项设置寄存器的过程即可现场随便找HAL_UART_InitUART_SetConfig(huart); // 在这⾥9.2.3 USART1时钟使能我必须看到USAER1被使能开始⼯作并且必须保证配套的GPIOx也必须被使能代码块 void HAL_UART_MspInit(UART_HandleTypeDef* huart) { GPIO_InitTypeDef GPIO_InitStruct {0}; if(huart-InstanceUSART1) { /* USER CODE BEGIN USART1_MspInit 0 */ /* USER CODE END USART1_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); // 使能串⼝时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能串⼝匹配的时钟 /**USART1 GPIO Configuration PA9 ------ USART1_TX PA10 ------ USART1_RX */ GPIO_InitStruct.Pin GPIO_PIN_9; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); GPIO_InitStruct.Pin GPIO_PIN_10; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); /* USER CODE BEGIN USART1_MspInit 1 */ /* USER CODE END USART1_MspInit 1 */ } }它复用的GPIO不会在MX_GPIO里面初始化会在串口初始化里面进行这样也是减少耦合性9.3 阻塞接收核⼼代码就两⾏搞定就能懂⾮常简单代码块 if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) ! HAL_OK) . 字⻓ 9B含校验帧结构为 8位数据 1位校验有效数据在 DR[7:0] * 2. 字⻓ 8B 且⽆校验帧结构为 8位数据有效数据在 DR[7:0] if ((huart-Init.WordLength UART_WORDLENGTH_9B) || ((huart-Init.WordLength UART_WORDLENGTH_8B) (huart-Init.Parity UART_PARITY_NONE)))10. 轮询模式使⽤⽅法总结11.未来期许在下期我们将讲述串口的重定向工作也就是第一个通讯模块串口的完结篇我们在本章节主要讲述了串口初始化的底层代码收发函数的参数配置我们在后续的通讯模块会时刻拿出串口来进行对比。