ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32 串口通信(1):阻塞

STM32 串口通信(1):阻塞 STM32 串口通信1阻塞一、前言在 STM32 嵌入式开发中串口UART是最常用的通信接口之一。自己平时工作也不接触串口底层用也都是早就写好的为了进一步学习和理解计划出一个串口系列的学习记录笔记。这是本系列文章的第1篇作为整个系列的基线版本。后续每篇文章将在本篇基础上逐步增加复杂度本篇阻塞查询→ 2.中断→ 3.中断缓冲→ 4.DMA每篇都是对前一篇的改进最终呈现一条完整的嵌入式串口架构演进路线。二、UART 协议基础2.1 什么是 UARTUARTUniversal Asynchronous Receiver/Transmitter通用异步收发器是一种异步串行通信协议。所谓异步意味着收发双方不共享时钟信号而是通过约定相同的波特率来同步。与之对比的是 SPI/I2C 等同步通信有专用时钟线 SCK/SCL特性UART异步SPI同步I2C同步时钟线无SCKSCL数据线TX, RX 两根MOSI, MISOSDA双向最少引脚23或 42通信方向全双工全双工半双工多设备点对点一主多从多主多从2.2 UART 帧格式UART 以帧为单位传输数据。以发送A 0x41 0b0100 0001 为例┌──────────────────────────────────────────────────────────────┐ │ 以字符 A (0x41, LSB first) 为例的完整波形 │ │ │ TX 引脚电平 start D01 D1~D50 (连续低) D61 D70 stop │ ┌─────┐ ┌──┐ ┌──┐ ┌─────┐ │ 高 (1) │ │ │ │ │ │ │ │ │ │ │ │ | │ │ │ │ │ 低 (0) │ └─────┘ └─────────────────────────────────────┘ └─────┘ │ │ └───────────────────────────────────────────────────────────────────┘ │ ↑ ↑ ↑ ↑ ↑ ↑ │ 空闲 起始位 数据位 (LSB 先发) 校验位 停止位 空闲 │ (高电平) (拉低) (每 bit 为高1 或低0) (无) (高电平)(高电平) │ └──────────────────────────────────────────────────────────────┘每一帧的具体字段说明字段长度说明起始位1 位TX 线从空闲高电平拉低并保持一个位周期通知接收方数据要来了数据位5~9 位实际数据通常 8 位LSB 先发最低有效位在前。每位持续一个位周期高1 / 低0校验位0/1 位可选无校验(N)、奇校验(O)、偶校验(E)停止位0.5/1/1.5/2 位TX 线恢复高电平并保持表示帧结束。之后可立即发下一帧回到起始位也可保持空闲本项目的配置是 8N18 位数据 无校验 1 位停止位。2.3 波特率与位时序波特率Baud Rate定义每秒传输的符号数。对 UART 而言1 波特 1 bit/s。波特率 115200 bps 每位时间 Tbit 1 / 115200 ≈ 8.68 μs 每帧时间 Tframe Tbit × (1 8 0 1) 8.68 × 10 ≈ 86.8 μs 理论最大吞吐量 115200 / 10 11520 字节/秒波特率产生STM32 USART 的波特率由专用时钟和分频器产生USART 时钟源APB2 84MHz→ USARTDIV 分频器 → 发送/接收移位时钟 对于 USART1挂载在 APB2 84MHz BRR 分频值 USART_CLK / (16 × BaudRate) 84,000,000 / (16 × 115200) 84,000,000 / 1,843,200 ≈ 45.5729... BRR 寄存器 (DIV_Mantissa 4) | DIV_Fraction (45 4) | 9 小数部分 0.5729 × 16 ≈ 9 0x2D92.4 异步采样原理由于收发方不共享时钟只能靠约定波特率接收方需要在每个位时间内多次采样来确定接收数据通过配置 USART_CR1 寄存器中的 OVER8 位来选择采样方法主要是16倍和8倍过采样在 16 倍过采样模式下OverSampling UART_OVERSAMPLING_16检测到起始位下降沿后等待 8 个采样周期半个位时间到达位中点在第 8、9、10 个采样点各取一次值三取二判决之后每 16 个采样周期采样一次后续位三、STM32 USART 外设详解3.1 USART 模块框图┌─────────────────────────┐ │ USART1 │ │ │ APB2 总线 ←────→ ┌────┤ ┌──────────┐ │ │ │ │ 发送移位 │←─── TX (PB6) TX/RX 控制 │ │ │ 寄存器 │ │ 中断/DMA │ │ └──────────┘ │ 标志寄存器 │ │ ┌──────────┐ │ │ │ │ 接收移位 │───→ RX (PB7) RXNE/TXE 中断 ───→│ │ │ 寄存器 │ │ │ │ └──────────┘ │ │ │ ┌──────────┐ │ │ │ │ 波特率 │ │ └────┤ │ 发生器 │←─── APB2 CLK │ └──────────┘ │ │ ┌──────────┐ │ │ │ 控制 │ │ │ │ 寄存器组 │ │ │ └──────────┘ │ └─────────────────────────┘3.2 关键寄存器USART_SR状态寄存器地址USART1_BASE 0x00位标志含义5RXNEReceive Data Register Not Empty —收到新数据可读取 DR6TCTransmit Complete — 发送完成移位寄存器空闲7TXETransmit Data Register Empty —发送缓冲区空可写入 DR8LBDLIN Break Detection9CTSClear To SendUSART_DR数据寄存器地址USART1_BASE 0x04位 [8:0] — 发送/接收数据9 位模式下使用位 8 位 [31:9] — 保留读为 0注意同一地址读 接收数据写 发送数据。USART_BRR波特率寄存器地址USART1_BASE 0x08位 [15:4] — DIV_Mantissa整数部分 位 [3:0] — DIV_Fraction小数部分精度 1/163.3 HAL 库数据结构UART_HandleTypeDef是 HAL 层的核心句柄typedefstruct{USART_TypeDef*Instance;// 寄存器基地址如 USART1_BASEUART_InitTypeDef Init;// 配置参数uint8_t*pTxBuffPtr;// 发送缓冲区指针中断/DMA 用uint16_tTxXferSize;// 发送总字节数uint16_tTxXferCount;// 剩余待发字节数uint8_t*pRxBuffPtr;// 接收缓冲区指针uint16_tRxXferSize;// 接收总字节数uint16_tRxXferCount;// 剩余待收字节数uint16_tMask;// 数据掩码8/9 位模式uint32_tFifoMode;// FIFO 模式F4 不支持DMA_HandleTypeDef*hdmatx;// 发送 DMA 句柄DMA_HandleTypeDef*hdmarx;// 接收 DMA 句柄HAL_LockTypeDef Lock;// 互斥锁__IO HAL_UART_StateTypeDef gState;// 发送状态机__IO HAL_UART_StateTypeDef RxState;// 接收状态机__IOuint32_tErrorCode;// 错误码}UART_HandleTypeDef;Init结构体展开typedefstruct{uint32_tBaudRate;// 波特率如 115200uint32_tWordLength;// 数据位UART_WORDLENGTH_8B / _9Buint32_tStopBits;// 停止位STOPBITS_1 / _0_5 / _2 / _1_5uint32_tParity;// 校验PARITY_NONE / _ODD / _EVENuint32_tMode;// 模式MODE_TX / _RX / _TX_RXuint32_tHwFlowCtl;// 硬件流控HWCONTROL_NONE / _RTS / _CTS / _RTS_CTSuint32_tOverSampling;// 过采样OVERSAMPLING_16 / _8}UART_InitTypeDef;3.4 本项目的硬件配置详解huart1.InstanceUSART1;// USART1 外设基址 0x40011000huart1.Init.BaudRate115200;// 115200 bpshuart1.Init.WordLengthUART_WORDLENGTH_8B;// 8 位数据位huart1.Init.StopBitsUART_STOPBITS_1;// 1 位停止位huart1.Init.ParityUART_PARITY_NONE;// 无校验huart1.Init.ModeUART_MODE_TX_RX;// 收/发使能huart1.Init.HwFlowCtlUART_HWCONTROL_NONE;// 无硬件流控huart1.Init.OverSamplingUART_OVERSAMPLING_16;// 16 倍过采样发送字符 A0x41 0b01000001LSB 先发的逻辑分析仪波形 位: start D01 D10 D20 D30 D40 D50 D61 D70 stop ┌────┐ ┌──┐ ┌──┐ ┌────┐ │ │ │ │ │ │ │ │ ───────┘ └──┘ └──────────────────────────────────────────────┘ └──┘ └──── ↑空闲 ↑ ↑ ↑ (高) 8.68μs 86.8μs 空闲(高)四、什么是阻塞查询模式阻塞查询Blocking Polling是最简单、最直观的串口通信方式。CPU 停下所有工作死等串口数据收到就处理处理完继续等。对应到代码中就是一个死循环while(1){/* ① 阻塞等待接收 — CPU 在此处空转 */HAL_UART_Receive(huart1,byte,1,1000);/* ② 收到数据后阻塞发送回去 */HAL_UART_Transmit(huart1,byte,1,1000);/* ③ 回到 ① */}五、硬件配置参数值MCUSTM32F407IGTx, Cortex-M4 168MHzUSARTUSART1TX 引脚PB6 (复用功能 AF7)RX 引脚PB7 (复用功能 AF7)波特率115200, 8N1中断未使能NVIC 中无 USART1 中断配置时钟树HSE (8MHz) → PLL (×42) → SYSCLK 168MHz │ ├─ AHB 168MHz │ ├─ APB1 42MHz (÷4) │ └─ APB2 84MHz (÷2) ← USART1 时钟源 └─ Cortex-M4 168MHz六、完整代码分析6.1 全局变量/* USER CODE BEGIN PTD */uint8_trx_byte;// 接收缓冲区1 字节uint8_ttxbuf[16];// 开机提示缓冲区/* USER CODE END PTD */6.2 外设初始化usart.cvoidMX_USART1_UART_Init(void){huart1.InstanceUSART1;huart1.Init.BaudRate115200;huart1.Init.WordLengthUART_WORDLENGTH_8B;huart1.Init.StopBitsUART_STOPBITS_1;huart1.Init.ParityUART_PARITY_NONE;huart1.Init.ModeUART_MODE_TX_RX;huart1.Init.HwFlowCtlUART_HWCONTROL_NONE;huart1.Init.OverSamplingUART_OVERSAMPLING_16;HAL_UART_Init(huart1);}6.3 GPIO 与中断配置MspInitvoidHAL_UART_MspInit(UART_HandleTypeDef*uartHandle){__HAL_RCC_USART1_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();GPIO_InitStruct.PinGPIO_PIN_6|GPIO_PIN_7;GPIO_InitStruct.ModeGPIO_MODE_AF_PP;GPIO_InitStruct.PullGPIO_NOPULL;GPIO_InitStruct.SpeedGPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.AlternateGPIO_AF7_USART1;HAL_GPIO_Init(GPIOB,GPIO_InitStruct);/* USART1 中断初始化区域 — 此处为空*/}关键设计点MspInit 中没有配置 USART1 的 NVIC 中断。阻塞查询模式完全依靠 CPU 轮询寄存器不需要中断介入。6.4 主函数main.cintmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();/* 开机发送 Test\r\n 提示 */memcpy(txbuf,Test\r\n,16);HAL_UART_Transmit(huart1,txbuf,strlen((char*)txbuf),1000);while(1){HAL_StatusTypeDef StaFlagHAL_UART_Receive(huart1,rx_byte,1,1000);if(HAL_OKStaFlag){/* 收到数据 → 原样发送回去回环 */HAL_UART_Transmit(huart1,rx_byte,1,1000);}elseif(HAL_TIMEOUTStaFlag){HAL_UART_Transmit(huart1,2\n,2,1000);}elseif(HAL_ERRORStaFlag){HAL_UART_Transmit(huart1,3\n,2,1000);}}}七、总结✅ 方案优点优点说明代码极简无缓冲区、无中断、无回调逻辑直观代码即流程图无竞态没有中断无需同步调试方便线性执行单步可跟踪零额外 RAM只需几个字节变量❌ 方案缺点缺点严重度说明CPU 利用率极低99.9% 时间在空转无法多任务CPU 被串口独占响应延迟大发送长数据时无法接收无缓冲处理期间来的数据全丢功耗高CPU 无法进入休眠CPU 利用率定量168MHz 主频有效计算利用率 0.14%八、后续系列计划┌─────────────────────────────────────────────┐ │ 本篇阻塞查询 │ │ 问题CPU 空转、无缓冲 │ ├─────────────────────────────────────────────┤ │ 下一篇中断接收 │ │ 改进中断通知CPU 等待时可做其他事 │ │ 不足无缓冲ISR 必须立即处理 │ ├─────────────────────────────────────────────┤ │ 第三篇中断 环形缓冲区 │ │ 改进ISR 存缓冲 → 主循环处理 │ │ 不足收/发共用缓冲可能冲突 │ ├─────────────────────────────────────────────┤ │ 第四篇DMA 传输 │ │ 改进DMA 零拷贝CPU 完全解放 │ └─────────────────────────────────────────────┘UART收发均采用阻塞在实际工作中几乎不可能用到主要以学习理解原理为主:维度评价学习价值⭐⭐⭐⭐⭐ 理解串口最底层工作方式的最佳起点实用价值⭐ 仅可做硬件验证推荐场景学习理解、硬件自检、快速验证串口通断核心要点阻塞查询的缺点是CPU 空转。后续所有改进方案本质都在回答同一个问题——如何让 CPU 少空转。下一篇预告中断接收发送 —— 告别 CPU 空转让中断来通知我们有数据到了。项目代码基于 STM32F407 HAL 库USART1 (PB6/PB7, 115200-8N1)
返回列表