ARTICLE DETAIL

资讯详情

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

STM32 USART 标准库完全指南——从串口发送到Printf重定向

STM32 USART 标准库完全指南——从串口发送到Printf重定向 写在最前为什么是USART如果说GPIO是STM32的手脚那USART就是它的嘴巴和耳朵——USART让单片机能够说话和倾听。USARTUniversal Synchronous Asynchronous Receiver Transmitter通用同步异步收发器最常用的形态就是串口。它的意义在于调试利器当你不知道程序跑到了哪里、变量的值是什么时串口打印是最好的显微镜通信基础GPS模块、蓝牙模块、WiFi模块、很多传感器都通过串口通信简单可靠只需要两根线TX和RX就能实现双向通信今天我们就把USART从里到外学透。一、USART是什么——从对话说起1.1 最朴素的理解想象两个人打电话一个人说话发送一个人听接收他们事先约定好语速波特率、语言格式数据帧格式这样才能保证对方听得懂USART串口通信就是这样两个设备通过一根TX线发送和一根RX线接收互相对话。发送方按约定的节奏把数据一位一位地发出去接收方按相同的节奏一位一位地收进来。1.2 关键术语一网打尽在开始编程之前先搞清楚这几个串口黑话术语通俗解释常见值波特率Baud Rate每秒传输的符号个数。在串口通信中1个符号1个bit所以波特率比特率。可以理解为说话速度9600、115200、921600起始位Start Bit数据传输开始的信号。拉低数据线表示我要开始发数据了1位固定数据位Data Bits真正要传输的数据的位数。就像每个字由几个字母组成8位最常用或9位停止位Stop Bits数据传输结束的信号。拉高数据线表示这一字节发完了1位最常用或2位校验位Parity Bit检错机制。通过额外加一位让数据中1的个数为奇数奇校验或偶数偶校验无校验最常用、奇校验、偶校验一个完整数据帧的样子以8位数据、无校验、1位停止位为例空闲状态高电平 起始位低电平1位 数据位从低位到高位依次发送8位 停止位高电平1位 空闲状态回到高电平记忆串口传输的节奏就像摩斯电码——有规律的高低变化代表0和1收发双方用相同的速度波特率来理解这些变化。二、USART的内部结构——从框图看本质2.1 简化版框图┌─────────────────────────────────────┐ │ USART │ │ ┌─────────┐ ┌──────────────┐ │ TX引脚 ◄─────────┼──┤ 发送 │ │ 波特率 │ │ │ │ 移位寄存器│◄──┤ 发生器 │ │ RX引脚 ◄─────────┼──┤ 接收 │ │ (波特率) │ │ │ │ 移位寄存器│ └──────────────┘ │ │ └─────────┘ │ │ ▲ ▲ │ │ │ │ │ │ ┌────┴────┴────┐ │ │ │ 数据寄存器 │ │ │ │ (DR寄存器) │ │ │ └─────────────┘ │ │ ▲ │ │ │ │ │ ┌────┴────┐ │ │ │ 控制逻辑 │ │ │ │ (CR1/CR2)│ │ │ └─────────┘ │ └─────────────────────────────────────┘ │ ▼ 与CPU总线连接2.2 三大核心部件① 波特率发生器由内部时钟分频得到想要的波特率。F1系列用APB2USART1或APB1USART2/3时钟作为输入。② 发送/接收移位寄存器并行CPU读写转串行引脚输出的翻译官。CPU把一字节数据写入数据寄存器移位寄存器再把它一位一位地推出去。③ 数据寄存器DRCPU和USART之间交接数据的地方。你往USART_DR写数据USART自动发送你读USART_DR就拿到接收到的数据。三、标准库编程——五步走3.1 编程五步骤使用USART的标准流程text1. 使能USART时钟和GPIO时钟 2. 配置GPIO为复用功能TX推挽输出RX浮空输入或上拉输入 3. 配置USART参数波特率、数据位、停止位、校验位等 4. 使能USART 5. 在程序中发送或接收数据3.2 第一步使能时钟c#include stm32f10x_rcc.h // USART1挂在APB2总线上GPIOA也挂在APB2 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 注USART2和USART3挂在APB1总线上用RCC_APB1PeriphClockCmd // RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);⚠️重要不同USART挂载的总线不同USART1APB2最高72MHzUSART2、USART3APB1最高36MHz3.3 第二步配置GPIO为复用功能USART的TX和RX引脚需要配置为复用推挽输出TX和复用功能输入RXc#include stm32f10x_gpio.h GPIO_InitTypeDef GPIO_InitStructure; // 配置TX引脚PA9复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 配置RX引脚PA10浮空输入或上拉输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; // 浮空输入推荐 // 或 GPIO_Mode_IPU上拉输入噪声较大的环境 GPIO_Init(GPIOA, GPIO_InitStructure);USART1常用引脚映射不同型号可能不同以手册为准USARTTX引脚RX引脚USART1PA9默认/ PB6重映射PA10默认/ PB7重映射USART2PA2PA3USART3PB10PB113.4 第三步配置USART参数c#include stm32f10x_usart.h USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate 115200; // 波特率 USART_InitStructure.USART_WordLength USART_WordLength_8b; // 数据位8位 USART_InitStructure.USART_StopBits USART_StopBits_1; // 停止位1位 USART_InitStructure.USART_Parity USART_Parity_No; // 无校验 USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; // 无硬件流控 USART_InitStructure.USART_Mode USART_Mode_Tx | USART_Mode_Rx; // 同时使能发送和接收 USART_Init(USART1, USART_InitStructure);各参数的可选值详解参数1USART_BaudRate直接写入想要的波特率数值如9600、115200、921600。⚠️注意波特率不能无限高它受系统时钟限制。一般115200是最常用的黄金波特率——速度够快且稳定。参数2USART_WordLength— 数据位长度参数值说明USART_WordLength_8b8位数据最常用USART_WordLength_9b9位数据较少用用于多机通信等参数3USART_StopBits— 停止位个数参数值说明USART_StopBits_11位停止位最常用USART_StopBits_0_50.5位停止位USART_StopBits_22位停止位USART_StopBits_1_51.5位停止位参数4USART_Parity— 校验位参数值说明USART_Parity_No无校验最常用USART_Parity_Even偶校验USART_Parity_Odd奇校验参数5USART_HardwareFlowControl— 硬件流控参数值说明USART_HardwareFlowControl_None无流控最常用USART_HardwareFlowControl_RTS使能RTS流控USART_HardwareFlowControl_CTS使能CTS流控USART_HardwareFlowControl_RTS_CTSRTS和CTS都使能 什么是硬件流控就是通过额外的引脚RTS/CTS通知对方我准备好了你发吧或我还没处理完稍等。防止数据丢失。参数6USART_Mode— 工作模式参数值说明USART_Mode_Tx仅使能发送USART_Mode_Rx仅使能接收USART_Mode_Tx | USART_Mode_Rx收发都使能最常用3.5 第四步使能USARTcUSART_Cmd(USART1, ENABLE);3.6 第五步发送和接收发送单个字节阻塞方式cvoid USART_SendByte(USART_TypeDef* USARTx, uint8_t data) { // 等待发送数据寄存器为空即上一个数据已发送完成 while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) RESET); // 发送数据 USART_SendData(USARTx, data); }发送字符串cvoid USART_SendString(USART_TypeDef* USARTx, char* str) { while (*str) { USART_SendByte(USARTx, *str); } }接收单个字节阻塞方式cuint8_t USART_ReceiveByte(USART_TypeDef* USARTx) { // 等待接收数据寄存器非空即收到数据 while (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) RESET); // 读取数据 return USART_ReceiveData(USARTx); }发送数据流程CPU → TX引脚一句话概括CPU把数据放到发货窗口硬件自动把它从TX引脚搬出去。textCPU写DR → 数据到发送DR → 自动转移到发送移位寄存器 → 按波特率从TX一位位发出关键点TXE标志发送DR空发送前必须等它为1否则新数据会覆盖旧数据数据从DR到移位寄存器是硬件自动完成的不需要CPU参与每一位的发送速度由波特率发生器控制代码对应cwhile (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); // 等窗口空 USART_SendData(USART1, data); // 放数据到窗口接收数据流程RX引脚 → CPU一句话概括硬件从RX引脚一位位收拼成一字节放到收货窗口CPU来取。textRX收到起始位 → 按波特率采样数据位 → 拼成一字节到接收DR → RXNE置位 → CPU读DR取走关键点RXNE标志接收DR非空收到数据后置1表示窗口有货CPU读DR时硬件自动清除RXNE标志如果上一字节没被读走新数据又来了 →溢出错误ORE→ 数据丢失代码对应cwhile (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) RESET); // 等有数据 data USART_ReceiveData(USART1); // 从窗口取货速记对照表方向流程关键标志标志含义何时置位发送CPU → DR → 移位寄存器 → TXTXE发送DR空了DR数据转入移位寄存器时接收RX → 移位寄存器 → DR → CPURXNE接收DR非空收到一字节存入DR时核心理解DR是CPU和USART硬件之间的交接台。发送时CPU把货放上交接台硬件自动搬走接收时硬件把货放上交接台CPU来取。四、实战串口发送Hello World4.1 硬件连接以STM32F103C8T6为例USART1的默认引脚是TXPA9→ 连接到USB转TTL模块的RXRXPA10→ 连接到USB转TTL模块的TXGND→ 连接到USB转TTL模块的GND⚠️注意交叉连接STM32的TX接模块的RXSTM32的RX接模块的TX就像两个人面对面你的右手握对方的左手。4.2 完整代码c#include stm32f10x.h // 硬件定义 #define USARTx USART1 #define USARTx_CLK RCC_APB2Periph_USART1 #define USARTx_GPIO_PORT GPIOA #define USARTx_GPIO_CLK RCC_APB2Periph_GPIOA #define USARTx_TX_PIN GPIO_Pin_9 #define USARTx_RX_PIN GPIO_Pin_10 // 发送单个字节 void USART_SendByte(USART_TypeDef* USARTx, uint8_t data) { while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) RESET); USART_SendData(USARTx, data); } // 发送字符串 void USART_SendString(USART_TypeDef* USARTx, char* str) { while (*str) { USART_SendByte(USARTx, *str); } } // 简单延时 void delay(uint32_t count) { while (count--); } int main(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 1. 使能时钟 RCC_APB2PeriphClockCmd(USARTx_GPIO_CLK | USARTx_CLK, ENABLE); // 2. 配置GPIOTX复用推挽RX浮空输入 GPIO_InitStructure.GPIO_Pin USARTx_TX_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(USARTx_GPIO_PORT, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin USARTx_RX_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(USARTx_GPIO_PORT, GPIO_InitStructure); // 3. 配置USART参数 USART_InitStructure.USART_BaudRate 115200; 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_Tx | USART_Mode_Rx; USART_Init(USARTx, USART_InitStructure); // 4. 使能USART USART_Cmd(USARTx, ENABLE); // 5. 主循环定时发送 while (1) { USART_SendString(USARTx, Hello STM32!\r\n); delay(1000000); } }4.3 代码解读关键点1等待TXE标志cwhile (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) RESET);TXETransmit Data Register Empty标志表示发送数据寄存器空了可以往里面写新数据了。如果不等它直接连续发送会导致数据覆盖。关键点2\r\n换行在串口助手中\r回车和\n换行一起用才能正确换行。只用\n在某些终端会显示为只换行不回车。五、进阶重定向printf到串口如果每次都用USART_SendString来打印写起来太麻烦。我们可以把标准库的printf函数重定向到串口这样就能像写电脑程序一样方便地打印了。5.1 实现原理printf最终会调用底层的一个函数来输出单个字符。我们只需实现这个函数——在标准库中叫fputc。5.2 完整实现c// 需要包含标准库头文件 #include stdio.h // 重定向fputcprintf最终会调用这个函数 int fputc(int ch, FILE *f) { // 将字符ch发送到串口 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }5.3 工程配置关键使用printf需要勾选Use MicroLIB微库否则会报错Keil MDK操作步骤点击魔术棒Options for Target切换到Target标签页在Code Generation区域勾选Use MicroLIB点击OK保存MicroLIB是ARM专门为嵌入式设备裁剪的C标准库体积小适合单片机使用。5.4 使用示例配置完成后就可以直接用了cint main(void) { // ... USART初始化代码同上 ... int count 0; while (1) { printf(count %d, 温度 %.2f°C\r\n, count, 26.5); delay(1000000); } }串口助手会输出textcount 0, 温度 26.50°C count 1, 温度 26.50°C count 2, 温度 26.50°C ...六、进阶中断方式接收不阻塞CPU阻塞方式接收while循环等标志位有一个致命问题CPU被卡住什么事都干不了。在实际项目中我们更常用中断方式接收。6.1 中断接收的编程步骤text1. 初始化USART同上 2. 使能USART接收中断 3. 配置NVIC嵌套向量中断控制器 4. 编写中断服务函数6.2 完整代码实现c#include stm32f10x.h uint8_t received_data 0; volatile uint8_t new_data_flag 0; // volatile防止被编译器优化 int main(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 1~3步使能时钟、配置GPIO、配置USART参数同前面略 // ... // 使能USART接收中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 配置NVIC NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 0; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; // 子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); // 使能USART USART_Cmd(USART1, ENABLE); while (1) { if (new_data_flag) { new_data_flag 0; // 处理接收到的数据回显发回去 printf(收到: %c\r\n, received_data); } // 在这里CPU可以放心干别的事 } } // USART1中断服务函数中断向量表里固定名称 void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { received_data USART_ReceiveData(USART1); new_data_flag 1; // 清除中断标志读取DR寄存器会自动清除RXNE标志 } }6.3 中断方式的核心优势方式CPU占用适用场景阻塞接收轮询100%等待简单测试程序中断接收仅当有数据时才处理实际项目开发七、标准库USART函数完整速查7.1 初始化与配置函数功能参数说明USART_Init初始化USART传入USART_InitTypeDef结构体指针USART_Cmd使能/失能USARTENABLE/DISABLEUSART_ITConfig使能/失能指定中断USART_IT_RXNE/TXE/TC等ENABLE/DISABLEUSART_DMACmd使能/失能DMA传输USART_DMAReq_Tx/RxENABLE/DISABLE7.2 数据收发函数功能说明USART_SendData发送一字节写入DR寄存器USART_ReceiveData接收一字节读取DR寄存器7.3 标志位与状态函数功能常用标志USART_GetFlagStatus获取标志位状态USART_FLAG_TXE发送寄存器空、USART_FLAG_RXNE接收寄存器非空、USART_FLAG_TC发送完成USART_ClearFlag清除标志位同上USART_GetITStatus获取中断状态USART_IT_RXNE、USART_IT_TXE、USART_IT_TCUSART_ClearITPendingBit清除中断挂起位同上7.4 常用标志位详解标志位宏含义用途USART_FLAG_TXE发送数据寄存器空发送数据前等待此标志为SET表示可以写新数据USART_FLAG_TC发送完成表示最后一个数据已从移位寄存器发出可用于一帧数据发送完的确认USART_FLAG_RXNE接收数据寄存器非空收到数据后此标志置位表示可以读取DRUSART_FLAG_IDLE空闲线路检测检测到线路空闲时置位可用于判断一帧数据结束USART_FLAG_ORE溢出错误接收缓冲区满又有新数据时产生需清除USART_FLAG_NE噪声错误信号受干扰时产生USART_FLAG_FE帧错误停止位不正确时产生USART_FLAG_PE校验错误校验位不对时产生八、常见问题与避坑指南Q1串口助手没收到数据或收到乱码可能原因解决方法波特率不一致检查代码和串口助手设置的波特率是否一致TX/RX接反了检查连接STM32的TX接模块的RXRX接模块的TX共地问题STM32的GND和USB转TTL模块的GND必须连在一起时钟未开启检查是否调用了RCC_APB2PeriphClockCmd使能USART时钟GPIO模式错误TX用AF_PPRX用IN_FLOATING或IPUQ2使用printf报错错误现象解决方法undefined symbol __stdout勾选Use MicroLIBundefined symbol fputc在代码中实现int fputc(int ch, FILE *f)函数编译报错FILE未定义包含头文件#include stdio.hQ3接收到的数据是乱码检查波特率设置检查数据位、停止位、校验位是否与串口助手一致如果使用了printf检查fputc中的USART是否与初始化的是同一个Q4中断方式收不到数据检查USART_ITConfig是否已调用检查NVIC是否已配置且使能检查中断服务函数名称是否正确F103系列USART1的中断函数名是USART1_IRQHandler检查总中断是否开启__enable_irq()一般启动文件已开九、总结今天我们学到了USART的本质串行通信的一种方式用一根TX线发送一根RX线接收数据帧格式起始位 数据位 校验位可选 停止位编程五步走时钟 → GPIO → USART参数 → 使能 → 收发 数据收发流程三种收发方式阻塞发送最常用简单可靠阻塞接收只适合简单测试中断接收实际项目首选printf重定向让串口打印像电脑编程一样方便
返回列表