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 " 表⽰选择外部晶振做为 HSE

5.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. 代码编写

cpp 复制代码
代码块 
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,因为发送要等待TC==1,这样会
导致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 9

10 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)

  1. 参数说明

◦ 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 的 USART)

  1. **CPU 这边:写 TDR,走 APB 时钟**

STM32F1 的 USART 挂载在 APB2(USART1)/APB1(USART2/3)。

CPU 内核通过总线,**以 APB 时钟速度**,往 `USART_DR(HAL里叫TDR)` 写入 1 字节数据。

这个动作只是一次寄存器写入,是 CPU 总线操作,非常快。

  1. **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 里面的数据搬运进移位寄存器,然后置 `TXE=1`,通知 CPU:可以写下一个字节了。

重点:

移位寄存器在慢悠悠按波特率往外发 bit 的时候,CPU 完全可以继续往 TDR 填下一包数据(只要 TXE 标志允许)。

也就是:

移位发 bit(波特时钟驱动) 和 CPU 写 TDR(内核 / APB 总线)是并行的,不是等发完这一整帧 CPU 才能写第二个。

举个例子(115200,1 字节 10bit:起始 + 8 数据 + 停止)

发 1 字节大概需要 10/115200 ≈ 86.8us。

但是 CPU 写 TDR 寄存器,72M APB 下,只需要**几个 ns**就写完了。

只要 TXE 标志及时置 1,CPU 可以连续填充,不需要等待一帧发送完成。

和你之前代码的关联

  • 轮询发送 `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

. 函数概述

以阻塞⽅式发送指定数量的数据元素,逐元素等待、逐元素写寄存器。

  1. 函数原型

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 )。

细节:

• 这两个函数就是阻塞式串⼝收发数据的函数

cpp 复制代码
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;
cpp 复制代码
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**;RXNE=1 代表硬件收到数据放到 DR 寄存器了,这时才去读 DR 拿数据。


一层一层往下拆解

1. 调用 `UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout)`

参数含义:

  • `huart`:串口句柄

  • `UART_FLAG_RXNE`:要等待的标志,就是 SR 寄存器里的 RXNE 位

  • `RESET`:我们要等到【这个标志不等于 RESET,也就是变成 SET (1)】

>

> 函数逻辑:只要标志 == RESET(0),就一直循环等,超时就退出返回失败。

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 里第一个寄存器)

  1. `& FLAG`:和 RXNE 对应的掩码做按位与

  2. 判断结果是不是等于掩码:

  • 等于 → RXNE 位是 1,返回 true(SET)

  • 不等于 → RXNE 位是 0,返回 false(RESET)

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 位,也就是二进制 `100000`(0x20),对应 SR 的 bit5。

6. 超时机制

`UART_WaitOnFlagUntilTimeout` 不是死等:

  • 拿进入函数时的系统滴答时间 `tickstart`

  • while 每一轮都会对比当前时间,如果超过设定的 Timeout 时间,直接跳出循环,返回非 HAL_OK,上层就知道接收超时失败。

你这里写 `HAL_MAX_DELAY`,就是无限等,永远不会超时。

完整流程串起来(接收 1 字节)

  1. 调用 `HAL_UART_Receive`

  2. 进入等待函数,不停读 `huart->Instance->SR`,检查 bit5(RXNE)

  3. 外部发数据过来,串口硬件收到,硬件自动把 RXNE 置 1

  4. `__HAL_UART_GET_FLAG` 检测到 RXNE=1,跳出 while 循环

  5. HAL 底层代码去读取 `huart->Instance->DR`,把数据拿出来存到 `recv_byte`

  6. ✅ 读 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才是全部发完

加延时就会出现数据丢失的情况

cpp 复制代码
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. 如 上图 中 ⑤ ⑦ 标识区域,是数据发送区域,包括数据区和发送按钮。

  5. 如 上图 中 ⑥ ⑧ 标识区域,是数据接收区域,包括接收到的数据显⽰区域和显⽰区域清除按钮。

8. 下载验证

8.1 ⽰例验证

1. 把编译好的程序下载到开发板并复位。

  1. 上位机按照 下图 所⽰配置( 串⼝号请根据电脑识别结果选择 )。3. 输⼊任何测试字符串,点击 " 发送 " 按钮,开始发送测试数据,可以看到如 下图 接收显⽰区域所⽰的每次发送都有⼀条相同的数据回传。9. 源码分析

9.1 Usart1物理地址

cpp 复制代码
#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 结构体初始化

cpp 复制代码
代码块 
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 */
}
cpp 复制代码
代码块 
/**
* @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等相关的

cpp 复制代码
代码块 
/**
* @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_Init

UART_SetConfig(huart); // 在这⾥

9.2.3 USART1时钟使能

我必须看到USAER1被使能,开始⼯作,并且,必须保证配套的GPIOx也必须被使能

cpp 复制代码
代码块 
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(huart->Instance==USART1)
{
/* 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 阻塞接收

核⼼代码就两⾏,搞定就能懂⾮常简单

cpp 复制代码
代码块 
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.未来期许:

在下期我们将讲述串口的重定向工作,也就是第一个通讯模块串口的完结篇,我们在本章节主要讲述了串口初始化的底层代码,收发函数的参数配置,我们在后续的通讯模块会时刻拿出串口来进行对比。

相关推荐
糖糖单片机设计1 小时前
基于 STM32 的智能书桌控制系统
stm32·单片机·嵌入式硬件
Soneswy1 小时前
C++基础篇——类与对象(上)
开发语言·c++
张小姐的猫1 小时前
【MySQL】表的约束
android·linux·服务器·数据库·c++·mysql·adb
逐步前行1 小时前
嵌入式图形库-LVGL v9.6-环境搭建(Windows+VSCode模拟)
单片机·嵌入式硬件
kiracrimson1 小时前
STL简单引入:把数据结构做成模板
开发语言·数据结构·c++
lhbweilai1 小时前
分析FreeRTOS内核对象API的源码实现(六)
c语言·stm32·单片机
HQB20261 小时前
# EG1190|屹晶微电子 115V 外置 MOS 异步降压 DC‑DC 控制器(零功耗使能) ## 一、产品概述
嵌入式硬件·硬件工程
comyccccc1 小时前
PID扩展板布局布线全解析:高效设计六大优势学习笔记
单片机·嵌入式硬件
传感器与混合集成电路1 小时前
DLM8676计数控制集成电路DC-DC一体化与RS422隔离接口设计
fpga开发