ARTICLE DETAIL

资讯详情

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

STM32F103C8T6与CubeMX串口UART配置实战:从原理到中断与DMA接收

STM32F103C8T6与CubeMX串口UART配置实战:从原理到中断与DMA接收 上周帮同事调一块板子芯片就是STM32F103C8T6他用STM32CubeMX把USART1配成了异步模式波特率115200生成代码烧进去串口助手发什么板子都没反应。我拿逻辑分析仪量了一下TXD引脚连波形都没有。查了十几分钟才发现他在CubeMX的Pinout视图里把PA9配成了GPIO_Output串口复用功能根本没启用。这种问题不是个例很多刚上手CubeMX的人都会在某个环节漏掉一步。这一篇学习笔记我把基于STM32F103C8T6和STM32CubeMX实现UART串口通信数据收发的完整过程写下来包括配置细节、代码实现、中断收发的常见坑以及我实际调试中积累的排查思路。无论你是刚接触STM32还是已经写过几个Demo但串口通信总出问题都建议完整看完尤其注意中断接收那一节那是很多人卡住的地方。1. 为什么是“C8T6 CubeMX”这套组合解决了我什么问题1.1 芯片选型F103C8T6的能力边界STM32F103C8T6这块芯片在2024年依然活跃在各类项目和教学板里不是没有原因的。Cortex-M3内核主频最高72MHz64KB Flash20KB SRAM片上集成3个USARTC8T6封装实际可用USART1、USART2、USART3、2个SPI、2个I2C、多个定时器和一个12位ADC。对一个学习串口通信的入门项目来说性能完全溢出。更重要的是这颗芯片的USART1引脚默认映射在PA9TX和PA10RX这两个引脚是5V容忍的FT引脚在实际接线时不容易烧引脚。当然你仍然需要确认板子的电平转换电路标准核心板上的USB转串口芯片通常是CH340或CP2102它们和STM32之间一般已经做好了电平匹配你只需要用一根USB线连电脑即可调试不需要额外接USB转TTL模块。国产替代方面现在市面上有很多号称PIN TO PIN兼容F103C8T6的芯片比如GD32F103、MM32F103、Air32F103等。如果你用的是这些替代芯片CubeMX里依然选择STM32F103C8T6来生成工程代码基本可以跑。但要注意有的替代芯片的USART外设寄存器在细节上有差异尤其是波特率自动校准这类高级功能建议量产项目还是用原厂芯片验证过再替换。学习阶段无所谓哪个便宜买哪个。1.2 CubeMX的价值图形化配置省了一堆手工查寄存器的时间STM32CubeMX是ST官方提供的图形化初始化代码生成工具。它的核心价值不是帮你写业务逻辑而是把外设初始化、时钟树配置、引脚复用映射、中断优先级这些繁琐且容易出错的底层工作变成在界面上的几次点击。举一个直观的例子在标准库时代你要初始化一个USART需要查参考手册设置GPIO模式、AFIO重映射、USART_CR1/CR2/CR3寄存器、波特率分频寄存器还要计算USARTDIV任何一个位配置错串口输出就是一屏乱码。CubeMX把这些全部生成了HAL库函数里HAL_UART_Init()会读取你图形界面配置的参数并写入硬件寄存器。但这里有一个很多人误解的地方CubeMX不是万能的它只负责生成外设初始化代码数据的收发逻辑、帧协议解析、错误处理这些仍然要你自己在main.c里写。所以正确的心态是把CubeMX当成一个效率工具而不是替代你理解UART协议的“黑匣子”。你在图形界面点选波特率115200底层对应的是BRR寄存器里写入的具体分频值。如果你不理解“波特率是如何从系统时钟分频出来的”后面遇到乱码、误码率过高时你会完全不知道从哪里排查。1.3 开发环境准备清单在开始配置之前先把环境准备好。以下是我试过多次比较稳定的组合IDEKeil MDK 5.x 或 STM32CubeIDE二选一。如果你只是跟着本文学习我建议用CubeIDE因为调试器集成度好不需要额外配置下载算法如果你公司或学校指定Keil那也完全没问题下面讲的代码逻辑是通用的。STM32CubeMX版本6.x以上。低版本5.x也能用只是部分界面布局不同。固件包在CubeMX的Help - Manage Embedded Software Packages里安装STM32CubeF1系列固件包版本建议选最新的稳定版。安装完成后新建工程时才能搜索到F103C8T6。烧录工具ST-Link V2或者板载ST-Link这是最省心的方式。如果你手里只有USB转TTL模块也可以通过串口ISP方式烧录但需要操作BOOT0引脚步骤多一点本文不展开。串口调试助手任意一款都行推荐Serial Assistant分普通版和高级版或MobaXterm内置的串口终端。尽量选择支持HEX发送和HEX显示的因为有时候你需要用16进制数据验证帧协议。逻辑分析仪可选十几块钱的24MHz 8通道逻辑分析仪就够用配PC端软件可以抓UART波形这是排查串口问题最有效的工具强烈建议备一个。需要说明的是如果你用的是带板载ST-Link的开发板比如常见的“STM32F103C8T6最小系统板”加一个ST-Link烧录器接线方式一般已经默认了SWD四线制也就是SWDIO、SWCLK、GND、3.3V这一套配合CubeMX里SYS配置为Serial Wire后就能正常下载调试。2. CubeMX配置串口的完整链路每一步背后的硬件逻辑2.1 新建工程时的引脚与时钟设置打开CubeMX在MCU Selector里搜索STM32F103C8T6双击进入工程配置界面。这里我建议你养成一个习惯任何项目都先配置SYS和RCC这两个大类再去处理具体外设。SYS - Debug选择Serial Wire。这一步很多人不理解说“我不需要在线调试选Disable行不行”。实际上F103系列如果不启用Serial WireSWD引脚PA13/PA14默认是普通GPIO你的ST-Link在下载过一次程序之后第二次可能就连接不上芯片了只能通过BOOT0拉高进入ISP模式擦除Flash来恢复。我见过太多因为忘了这一步导致“芯片锁死”的求助帖。RCC - High Speed ClockHSE选择Crystal/Ceramic Resonator。如果你的板子上有8MHz晶振选这个之后时钟树才能把系统时钟倍频到72MHz。如果选Disable系统只能跑HSI的内部8MHz时钟不是不能用但对后面波特率误差、定时器精度都会有影响。标准F103C8T6核心板一般都有8MHz晶振建议开启。如果没有晶振就保持Disable并且后续时钟树里也不要倍频到过高频率HSI直接作为系统时钟即可。2.2 USART1参数配置波特率、数据位、停止位与NVIC左侧Categories列表点击Connectivity - USART1Mode选择Asynchronous异步模式。异步模式就是标准的UARTTX发送RX接收双方各用一根线靠约定波特率同步。同步模式Synchronous则是带时钟线的USART用于和具有同步接口的外设通信本文不涉及。Parameter Settings里的参数默认有个陷阱Baud Rate115200 Bits/s这是约定俗成的调试波特率。你也可以选9600、57600、460800等只要上位机和下位机一致就可以。但要注意F103的USART1挂载在APB2总线上当系统时钟为72MHz时APB2时钟是72MHz在这个频率下115200的波特率分频误差非常小。如果你把系统时钟配置成不常见的频率比如56MHz那么某些标准波特率尤其是115200的倍频分频值会产生超过1%的误差就会偶发乱码。Word Length8 Bits含奇偶校验位时选择9 Bits。这里默认8就对了。ParityNone无校验。学习阶段不建议开启奇偶校验因为你每发一个字节会多一个校验位接收端配置不对反而容易丢数据。Stop Bits1。默认即可。Data DirectionReceive and Transmit。如果只需要发送也可以只勾选Transmit但建议直接全开。然后在NVIC Settings选项卡里勾选USART1 global interrupt。这一步很容易被忽略——很多人配置完USART参数后忘了使能中断使能位结果代码里调用HAL_UART_Receive_IT()却永远进不了中断回调查了半天查不到原因。USART1的全局中断使能位在NVIC里对应的是IRQn编号CubeMX会在生成的stm32f1xx_it.c里帮你写好USART1_IRQHandler你不需要修改这个文件只需要保证NVIC里勾选了使能。2.3 时钟树波特率准不准就看这里时钟树Clock Configuration是CubeMX里看起来最唬人的界面但理解起来其实就一句话多个时钟源经过PLL倍频、分频后分别提供给AHB、APB1、APB2总线。对于F103C8T6标准配置是HSE 8MHz - PLLMUL x9 - SYSCLK 72MHz - AHB Prescaler ÷1 - APB1 Prescaler ÷2得到36MHz - APB2 Prescaler ÷1得到72MHz。为什么APB1要÷2因为APB1总线的最高频率限制为36MHz你如果让APB1跑72MHz芯片会不稳定。USART2和USART3挂在APB1上而USART1挂在APB2上。记住USART1的时钟源是APB2不是APB1这一点对于理解“为什么我的USART1波特率算出来的值和预期不一样”非常关键。波特率的计算公式是BaudRate PCLK / (16 * USARTDIV)。在72MHz的APB2时钟下115200波特率对应的USARTDIV约为39.0625。HAL库会自动完成分频值计算针对标准波特率有专门的算法但如果你的APB2时钟不是72MHz而是比如没有配置时钟树时的HSI 8MHzHAL库依然会按8MHz计算分频值波特率仍然可以设置为115200因为分频值可以计算出来。不过当PCLK与目标波特率之间无法整除时误差就会增大。F103的USART硬件本身允许最多约2%的误差容限超过这个值就会随机出现错误字节。所以配置时钟树不是为了好看是为了让外设工作在额定的误差范围内。2.4 生成代码后第一件事看懂main.c里的初始化顺序配置完成后点击Project Manager设置工程名、IDE类型如果使用Keil选MDK-ARMToolchain/IDE选择MDK-ARM V5或V6Minimun Heap Size建议改成0x300768字节因为后续printf重定向可能需要使用heap。然后点击右上角GENERATE CODE生成工程。生成后打开main.c你会看到CubeMX已经帮你写好了这样的初始化流程int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 用户代码区 while (1) { } }这个顺序是有讲究的先初始化HAL库HAL_Init内部会设置SysTick再配置系统时钟SystemClock_Config这是所有外设时钟的来源然后GPIO、USART1。你后续添加用户代码的位置应该在“用户代码区”的BEGIN/END注释之间因为如果你再次打开CubeMX重新生成代码只有这两个注释之间的代码会被保留。如果写在注释外面重新生成的时候会被覆盖掉这种“代码凭空消失”的坑我掉进去过不止一次。3. 三种收发方式的代码实现从轮询到中断再谈DMA3.1 查表式收发HAL_UART_Transmit 与 HAL_UART_Receive 的满阻塞用法串口收发最简单的方式是阻塞式也叫轮询方式。HAL库提供两个核心函数HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);发送函数会阻塞当前程序直到所有数据都发完或超时。例如你想发送一串字符串uint8_t tx_buf[] Hello STM32\r\n; HAL_UART_Transmit(huart1, tx_buf, strlen((char *)tx_buf), 1000);注意这里必须用strlen计算长度如果你写成sizeof(tx_buf)会连字符串结尾的\0一起发送。虽然通常也没什么问题但如果你在帧协议里用\0表示命令结束符这就成了严重的Bug。接收函数是阻塞式的程序会卡在HAL_UART_Receive()这一行直到收到指定数量的字节或超时返回。例如uint8_t rx_buf[8]; HAL_UART_Receive(huart1, rx_buf, 8, 5000);这段代码在收到完整8字节前不会往下执行。如果上位机只发了3个字节程序就会一直卡在接收函数里直到5秒超时后才跳过。阻塞方式的缺点是显而易见的如果你的主循环里还要跑LED闪烁、按键扫描、传感器读取任何一个串口等待都会把整个程序堵死。但阻塞方式也有它的价值在系统初始化阶段、排错阶段或者在一个纯粹的“串口转网口透传”Demo里它足够简单可靠。而且HAL_UART_Transmit在很多HAL中断回调函数里是不能调用的会造成死锁那时你反而需要用阻塞的寄存器操作或环形缓冲去发送数据。如果你是初学我建议先用阻塞方式打通收发链路——用串口助手发一个字节板子收到后原样返回。这个Demo能通说明硬件、CubeMX配置、串口工具链路全部正常后面再上中断也不迟。3.2 中断接收与回调机制接收单个字节和处理粘包问题的思路中断接收和阻塞接收最大的不同在于你不必在某个函数里干等数据。硬件收到一个字节会触发USART1中断CPU跳转到中断服务函数执行完回调后继续返回主程序。这需要你充分理解CubeMX生成的代码是怎么串联起来的。先看CubeMX生成了什么。在stm32f1xx_it.c里有一个USART1_IRQHandlervoid USART1_IRQHandler(void) { HAL_UART_IRQHandler(huart1); }HAL_UART_IRQHandler就是HAL库的中断总入口它会读取USART1的状态寄存器判断当前中断事件是接收到了数据、发送完成了、还是产生了错误然后调用对应的弱函数weak function。默认的弱函数是空的你需要自己重写它。这个函数就是HAL_UART_RxCpltCallback。典型的中断接收单个字节代码如下uint8_t rx_byte 0; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 启动第一次中断接收接收1个字节 HAL_UART_Receive_IT(huart1, rx_byte, 1); while (1) { // 主循环可以干别的事 } } // 在main.c末尾编写回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { // 对rx_byte进行处理 // 重新启动中断接收否则只能收到一次 HAL_UART_Receive_IT(huart1, rx_byte, 1); } }这里有一个被无数人忽略的细节HAL_UART_Receive_IT函数每次只能指定接收固定的字节数达到目标字节数后才触发一次回调。这意味着如果你指定接收10字节上位机只发了3字节回调永远不会执行。于是很多人写了一个版本之后发现“中断接收只能收一次”或者“必须凑够指定字节数才触发”就开始怀疑自己的设置。解决这个问题的常见思路是每次只启动中断接收1个字节收到一个字节后就在回调里重新启动下一次接收。这样串口每个字节都能进入回调你可以自己定义帧格式、判断帧头帧尾这个思路也被称为“字节流式中断接收”。它的缺点也很明显每收一个字节就触发一次中断若波特率是115200且数据量大频繁的入栈出栈会占用一定的CPU但这个量级对Cortex-M3来说完全不是问题。另一种升级方案是DMA 空闲中断IDLE Line Interrupt后面会讲到。在学习和多数应用场景下单字节中断接收够用了。3.3 printf 重定向把串口当作调试的“眼睛”调试嵌入式程序printf是最直观的输出方式。HAL库默认没有实现printf的底层字符输出你需要自己把printf的输出“重定向”到串口。在Keil中使用标准库或MicroLIB时做法略有不同。Keil MicroLIB的写法最常见#include stdio.h int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }在Project - Options for Target - Target标签页勾选Use MicroLIB然后就可以直接使用printf(温度%.2f°C\n, temp)。MicroLIB体积小省略了完整的stdio实现对嵌入式开发足够。缺点是浮点打印的格式化可能会更耗资源但也不至于不能用。如果是GCC工具链比如STM32CubeIDE需要实现_write系统调用#include sys/unistd.h int _write(int file, char *ptr, int len) { HAL_UART_Transmit(huart1, (uint8_t *)ptr, len, 0xFFFF); return len; }写完重定向后建议在主循环里打印一条“System Init Done”来验证。如果串口助手显示乱码优先查波特率设置或时钟树其次是检查板子上的晶振频率到底是8MHz还是25MHz不要想当然。这里有个非常实用的经验在中断回调里不要直接调用printf因为printf内部会调用HAL_UART_Transmit阻塞发送如果此时主循环也在使用USART1可能出现资源竞争严重时会导致中断一直卡住HAL锁死。正确的做法是在中断回调里只设置标志位或者把待打印数据拷贝到缓冲区等到主循环非中断上下文里再打印。4. 实际调试中的经典问题与排查链路4.1 现象一串口助手显示乱码或无数据从硬件接线开始查串口不通的第一反应不要是改代码先排除硬件链路。我排查串口问题时一般按这个顺序来用镊子短接USB转串口芯片的TXD和RXD也就是自发自收。如果串口助手里发送的数据能原样返回说明USB转串口模块和电脑驱动是好的。注意操作时别短路到其他引脚。确认板子的USB转串口芯片型号。很多F103C8T6核心板上的USB口就是USB转串口功能不需要另接模块但有些板子的USB口只是供电必须外接USB转TTL。具体要看原理图。如果核心板用的主控是STM32F103C8T6本身那么USB口能不能用于串口调试取决于板子是否有额外的CH340等芯片。我见过不少新手把Type-C口插上电脑以为能直接出串口结果那个口只是供电口Null。接线规则开发板的TX一定要接USB转TTL模块的RX开发板的RX接模块的TXGND接GND。有人会问TX接TX行不行——理论上电平也能触发但收发是错位的会表现为“能收到数据但内容完全错误”。记住交叉连接才是正道。用万用表量一下TXD引脚的静态电平正常情况下串口空闲电平应为高3.3V左右。如果测量为0V检查是否进入了某个外设的复用冲突或者芯片根本没跑起来。如果是乱码排查优先级为波特率设置不一致 - 时钟树配置错误APB2不是72MHz - 晶振频率和CubeMX设置不一致 - TX/RX接反通常是“有点数据但全是乱码”。4.2 现象二中断回调不触发状态寄存器到底该怎么查中断回调不触发是串口中断模式最经典的坑。它通常由以下三个原因之一造成NVIC中断没有使能。回到CubeMX确认USART1 global interrupt已勾选生成代码后进入stm32f1xx_it.c确认USART1_IRQHandler存在。在初始化后忘记调用HAL_UART_Receive_IT()。这是最隐蔽的一个很多人配置完CubeMX生成代码直接烧录发现主循环在跑但串口怎么发都没反应。原因是你虽然开启了NVIC但你从未调用HAL_UART_Receive_IT()去通知硬件“我现在准备接收数据了”。USART中断接收是“一次性的”你必须每次接收后重新启动。初始化代码里加一行HAL_UART_Receive_IT(huart1, rx_byte, 1);才能开始第一轮接收。中断回调里没有再次调用HAL_UART_Receive_IT()。函数名“RxCpltCallback”给人的暗示是“接收完成后再处理”很多初学者在回调里处理完数据忘了重新启动接收导致整个程序只能收到一次数据。如果你不确定到底进没进中断在回调入口设置一个GPIO翻转或者断点通过观察波形判断。也可以用调试器在线仿真全速运行时在回调中加断点看能否停下来。注意断点方式在实时性要求高的场景下会改变行为但排查这种问题足够。4.3 现象三接收数据错位不定长数据帧的协议设计上位机发来的是不定长的数据帧比如一帧可能包含5~20个字节怎么判断什么时候一帧结束了这是串口通信中最常见的需求。最简单的方案是固定帧长法约定每个命令都是8字节不足的用0x00补齐。接收端只要收满8字节就处理一帧。这个方案简单但浪费带宽并且不适合真正的命令帧场景。更通用的是帧头帧尾法约定帧头0xA5帧尾0x5A。接收端逐字节接收用一个有限状态机FSM解析typedef enum { WAIT_HEADER, WAIT_DATA, WAIT_TAIL } FrameState; FrameState state WAIT_HEADER; uint8_t rx_buf[64]; uint8_t rx_len 0; void ParseByte(uint8_t byte) { switch (state) { case WAIT_HEADER: if (byte 0xA5) { state WAIT_DATA; rx_len 0; } break; case WAIT_DATA: if (byte 0x5A) { // 完整帧rx_buf里存的是上一帧数据 ProcessFrame(rx_buf, rx_len); state WAIT_HEADER; } else { rx_buf[rx_len] byte; if (rx_len sizeof(rx_buf)) rx_len 0; // 防溢出 } break; } }这个方法在数据区里如果出现0x5A会被误判为帧尾所以更严谨的协议要加转义符或者用长度字段。工程上更常用的做法是“帧头长度数据校验”长度字段告诉接收端后面还有多少字节接收完指定的数据字节后解析这种方式对不定长帧支持较好也不怕数据区出现帧尾字节。串口帧协议本身就是一门学问建议你从简单的做起一步一步增加复杂度。5. 进阶一档DMA收发与低功耗/高吞吐场景的取舍5.1 DMA模式配置和代码改动如果你需要接收大量连续数据比如以1Mbps的波特率接收传感器上报每100ms就有几百字节数据到达这时单字节中断接收可能会导致频繁中断CPU绝大部分时间都在为中断奔波。DMA直接存储器访问模式可以解决这个问题USART硬件收到数据后由DMA控制器直接把数据搬运到内存缓冲区搬运完才触发一次完成中断CPU完全不用干预。CubeMX里配置DMA很简单在USART1配置界面点击DMA Settings选项卡点击Add选择USART1_RXDMA Request为USART1_RXDirection设为Peripheral To MemoryMode选择Normal或Circular。如果你希望接收缓冲区满后自动清零继续接收下一轮选Circular模式如果你希望收到固定长度后触发回调然后重新初始化新一轮接收选Normal模式。生成代码后初始化里会自动多出MX_DMA_Init()函数而且必须在MX_USART1_UART_Init()之前调用CubeMX会处理好顺序但你手动改代码时要注意这个顺序。然后在main.c里启动接收#define RX_BUF_SIZE 128 uint8_t rx_buf[RX_BUF_SIZE]; HAL_UART_Receive_DMA(huart1, rx_buf, RX_BUF_SIZE);当接收缓冲区收满128字节后HAL_UART_RxCpltCallback会被调用。但注意这里存在和前面中断方式一样的问题如果上位机只发了50字节未满128字节回调永远不会触发。针对这类不定长数据F103的做法是借助USART的空闲中断IDLE。当一帧数据停止发送超过一个字节时间后USART会产生空闲事件。在串口上表现为“一段时间没有新的数据到达”。你可以在USART1_IRQHandler里检测IDLE标志然后读取DMA当前剩余计数计算出本次接收到的有效字节数。这是目前做串口不定长DMA接收最主流的方案。因为F103的HAL库对IDLE中断封装不充分实际运用中需要自己操作寄存器门槛稍高这里不展开代码先说思路。5.2 什么时候不建议用DMADMA不是万能的。在以下场景我反而建议放弃DMA回到中断方式极低功耗场景DMA无法在低功耗模式Sleep/Stop下工作你需要用中断外部事件唤醒MCU。数据帧非常短的场景如果服务器和板子之间的通信基本是“一个字节命令一个字节应答”DMA的高速搬运优势体现不出来反而需要常驻128字节缓冲区浪费内存增加了代码复杂度。调试初期DMA接收不触发回调的问题日志输出去追查能让你心态崩溃。先把阻塞和中断方式跑通了确认协议没问题再抽象出DMA层效率会高很多。我的实践体会是做项目别为了“技术听起来高级”就堆DMA。C8T6只有20KB SRAMDMA缓冲区和协议解析缓冲区都会占用内存。先按最朴素的方式把功能跑通然后根据实际性能瓶颈决定要不要优化。6. 写在最后几个串口调试的个人习惯调试串口这么多年我的几个工作习惯基本固定下来了。第一串口助手的配置和板子固件保持一致。硬件复位后先发一个“AT”这类简单的无校验命令看能否收到预期回复不要在协议解析还没写好时就测试复杂命令。第二J-Link或ST-Link调试器在线调试时如果串口数据接收出现卡顿检查调试器是否占用了串口的引脚复用。比如ST-Link V2的SWIM引脚在有的板子上会占用PA9/PA10附近的功能引脚偶尔会影响到USART1的外部电路。第三区分“板子没跑起来”和“串口没配置好”。最简单的办法是让板子上电后在主循环里持续以1Hz频率发送一个固定字符串。如果串口助手能周期性收到这个字符串说明基础链路没问题问题出在应用逻辑如果收不到再从硬件、CubeMX工程级开始查。第四多看数据手册里的时钟树和波特率误差表。HAL库帮你隐藏了太多寄存器细节但对一个合格的嵌入式工程师来说知道波特率误差的来源、知道时钟树如何影响外设工作频率是必须的基本功。遇到串口偶尔乱码时先算算你的PCLK和目标波特率的USARTDIV小数的误差再决定要不要换一个波特率。这一篇就记到这里。如果你想继续往下学下一步强烈建议自己写一个串口环形缓冲区把中断队列协议解析串起来彻底搞懂这里面的数据流再去看DMA和IDLE基本上所有串口问题都难不到你了。
返回列表