ARTICLE DETAIL

资讯详情

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

STC8H DMA串口通信实战:从波特率到中断优先级的5个避坑指南

STC8H DMA串口通信实战:从波特率到中断优先级的5个避坑指南 1. 为什么STC8H的DMA串口通信值得折腾1.1 一个真实项目把我逼到了DMA这条路上先说背景。前阵子做一块采集板单片机用STC8H8K64U需要把传感器数据以115200波特率往外面发数据量不算大但频率很高一秒钟要出几百包数据每包几十字节。最开始用传统的中断发送主循环里不断查询TI标志然后塞SBUF刚开始还能撑住后面把显示、按键扫描、数据处理逻辑全塞进去之后串口发送就开始偶尔丢字节甚至出现数据帧错位。排查了一晚上最终定位到问题不在波特率而在CPU被串口发送拖累了。这时候我才下决心把串口通信改成DMA方式。STC8H这一系列的单片机DMA模块并不是花架子它是真正能把数据从内存搬到串口发送寄存器、或者从串口接收寄存器搬到内存的硬件通道。数据搬运过程不需要CPU逐字节干预搬运完了给你一个中断。对于串口通信来说这意味着发送一包数据时CPU完全解放可以继续跑业务逻辑接收数据时即使数据来得比较密也不会因为CPU正在处理中断而来不及读SBUF导致溢出。我需要提前强调一句STC8H的DMA和STM32的DMA在概念上很像但寄存器细节、配置流程完全不一样。很多从STM32转过来的人照着HAL库的思路去配STC8H的DMA第一反应就是找不到DMA_HandleTypeDef。这很正常STC8H的DMA配置更接近寄存器级操作用官方库函数做起来也不复杂但是有几个坑是官方例程里不会直接告诉你的。1.2 115200为什么成了“分水岭”很多人觉得串口波特率嘛9600能跑115200也能跑不就是换个数字实际上波特率一旦上到115200字符间隔就只有大约86.8微秒。在传统中断接收方式下每收到一个字节就要触发一次串口中断CPU进中断、读SBUF、清标志、存数据这一套操作下来几十个微秒就没了。如果是9600波特率一个字节间隔867微秒CPU有大把时间慢慢处理但到了115200如果中断响应延迟稍微大一点或者被其他高优先级中断打断SBUF里的数据就会被下一个字节覆盖这就是经典的溢出丢数据。DMA接收恰好能解决这个问题串口接收寄存器每收到一个字节硬件会直接通过DMA通道把它搬运到内存缓冲区完全不需要CPU参与。CPU只需要在帧接收完成比如空闲中断触发后去缓冲区里取数据。这就把“高频小数据量的CPU中断压力”转成了“低频大数据量的内存操作”压力小得多。不过DMA也不是万能药配置不好出的问题比传统方式还隐蔽。下面这5个坑是我在115200波特率下实测踩出来的每个都附带了排查思路和解决方案。2. 坑1波特率算不准数据全白费2.1 115200波特率计算公式与误差源头STC8H的串口波特率发生器可以选择定时器1或定时器2官方推荐用定时器2做波特率发生器因为定时器2是16位自动重装精度更高而且不占用定时器0和定时器1这两个常用定时器。定时器2做波特率发生器时计算公式是波特率 SYSclk / (65536 - 重装值) / 4这个式子很多人在计算时会漏掉最后的除以4导致配置出来的波特率高了好几倍串口收到全是乱码。以24MHz主频为例目标115200波特率65536 - 重装值 24000000 / 115200 / 4 52.0833由于重装值必须是整数只能取52那么实际波特率 24000000 / 52 / 4 115384.6误差约0.16%。串口通信双方只要误差在2%以内一般都能正常通信0.16%是完全没问题的。但问题来了如果你的系统主频不是整数或者你用了内部IRC振荡器而没有做频率校准误差就会叠加。STC8H内部IRC的精度在出厂时是±1%在温度变化后可能到±2%如果本身晶振频率就偏了再叠加波特率计算的取整误差115200下很容易出乱码。2.2 定时器2重装值的配置细节我实测的配置代码如下// 默认24MHz主频115200波特率 #define FOSC 24000000UL #define BRT (65536 - (FOSC / 115200 / 4)) void UART1_Init(void) { S1CON 0x50; // 8位数据位、无校验、开启接收 S1CON | 0x40; // 波特率发生器使用定时器2 AUXR | 0x01; // 定时器2作为波特率发生器 T2L BRT; // 写入重装值低8位 T2H BRT 8; AUXR | 0x10; // 定时器2开始运行 }注意这里有个老STC用户都踩过的坑S1CON的bit7是SM0bit6是SM1配置成0x50也就是0101 0000其中SM00、SM11对应模式18位UART可变波特率。如果你写成0x40也就是SM00、SM10那就成了模式0同步移位寄存器模式根本不能用来做异步串口通信。我见过有人初始化串口收发乱码查来查去最后发现是模式配置错了。还有一个细节定时器2工作在波特率发生器模式下时你不需要开定时器2中断也不需要写定时器2的中断服务函数。如果开了反而会在每次定时器溢出时反复进中断干扰系统运行。2.3 实测误差数据我分别用24MHz内部IRC、11.0592MHz外部晶振做了测试使用逻辑分析仪抓取实际波形数据如下主频配置理论波特率实测波特率误差通信结果24MHz内部IRC1152001153840.16%稳定22.1184MHz外部晶振1152001152000.00%稳定11.0592MHz内部IRC未校准115200114048-1.00%偶发乱码第三组数据充分说明了一个问题主频不准比波特率计算取整的误差可怕得多。如果产品对通信稳定性要求高建议直接用11.0592MHz或22.1184MHz晶振这两个频率能精确分频出115200完全不碰误差问题。注意使用内部IRC时一定要先校准。STC8H内部有IRTRIM寄存器官方下载软件里也有频率校准功能实测校准后误差能控制在±0.3%以内。3. 坑2DMA通道配置看着对跑起来数据全错3.1 发送DMA的源地址和目的地址STC8H的DMA串口发送功能核心思路是把内存缓冲区源地址中的数据搬到串口发送寄存器SBUF目的地址搬完触发中断。听起来简单但配置地址时有几个致命细节。第一源地址必须是数据缓冲区地址递增方向可选择目的地址必须是SBUF寄存器对应的映射地址而且地址不能递增。如果目的地址配了递增数据会被写到一个不断往后跳的地址上实际上根本没有数据进SBUF串口自然什么都不发。第二STC8H的DMA有多个通道每个通道对应不同的外设映射。以STC8H8K64U为例串口1发送DMA和串口1接收DMA是独立通道不能混用。配置发送通道却把接收标志位打开了DMA就不会按预期触发。这是我实测可用的发送DMA初始化代码#define DMA_UR1T_BUF_SIZE 64 u8 xdata UART1_TX_BUF[DMA_UR1T_BUF_SIZE]; void DMA_UART1_TX_Init(void) { DMA_UR1T_CFG 0x00; // 软件触发、传输完成后不自动重新装载 DMA_UR1T_STA 0x00; DMA_UR1T_AMT (u16)(DMA_UR1T_BUF_SIZE - 1); // 传输总字节数-1 DMA_UR1T_AH (u16)((u16)UART1_TX_BUF[0] 8); // 源地址高字节 DMA_UR1T_AL (u16)((u16)UART1_TX_BUF[0]); // 源地址低字节 DMA_UR1T_DH (u16)((u16)S1SBUF 8); // 目的地址高字节 DMA_UR1T_DL (u16)((u16)S1SBUF); // 目的地址低字节 DMA_UR1T_CFG | 0x80; // 使能DMA }注意AMT寄存器存的是“传输字节数减1”如果缓冲区配置了64字节而AMT写的是64DMA会多传一个字节把缓冲区外面的一位数据也发送掉。这个“减1”的细节在STM32里是直接用数据长度换到STC8H上很多人第一次都会写错。3.2 发送完成标志的两个判断时机DMA把数据搬完不等于串口已经把数据全部发完了。DMA搬运数据到SBUF的速度极快但SBUF的数据是一位一位往外发送的在115200波特率下一个字节需要约86.8微秒。如果你的代码在DMA传输完成中断里立刻把TX缓冲区改了或者立刻操作串口发送寄存器很可能最后一个字节还没发完缓冲区的数据就被覆盖了。我的做法是双保险判断先查DMA传输完成标志再查串口发送空闲状态。发送一包数据的函数这样写void UART1_DMA_Send(u8 *buf, u16 len) { memcpy(UART1_TX_BUF, buf, len); // 先把数据复制到DMA缓冲区 DMA_UR1T_STA 0x00; // 清除DMA状态标志 DMA_UR1T_AMT (u16)(len - 1); // 重设传输长度 DMA_UR1T_CR 0x01; // 触发DMA启动软件触发 while (!(DMA_UR1T_STA 0x01)); // 等待DMA传输完成标志 while (!(S1CON 0x02)); // 等待串口发送移位寄存器空闲TI标志 S1CON ~0x02; // 清除TI标志 }有人会问直接用TI标志判断不行吗理论上可以但TI标志是串口发送完成中断标志它置位时表示SBUF里的数据已经全部移出。问题在于DMA触发启动后第一个字节进入SBUF还需要一小段时间如果你在DMA还没开始搬运时就查询TI可能读到的是上一次发送留下的旧标志导致误判。所以先等DMA搬完再等TI置位是最稳妥的顺序。4. 坑3接收端DMA空闲中断的相爱相杀4.1 DMA接收不知道一帧数据何时结束DMA接收数据时硬件做的唯一事情是“收到一个字节就往内存搬一个字节”。它不知道一帧数据从哪里开始、到哪里结束。如果接收不定长数据帧你必须在应用层自己判断帧边界。常用方案有两个。第一种是固定帧长比如每帧固定64字节DMA接收完64字节触发传输完成中断直接处理缓冲区。这种方案最简单但灵活性差。第二种是空闲中断串口在一段时间内没有收到新数据时触发空闲中断此时认为一帧数据接收完毕。第二种方案适合不定长数据帧也是实战中用的最多的方式。STC8H的串口空闲中断标志是S1CON寄存器里的IDLE位bit4。当串口接收线上出现持续一个字节时间的空闲电平后硬件会自动置位这个标志并触发中断需要开启相关中断使能。4.2 空闲中断DMA接收的经典配置我这里给出一个完整可用的初始化流程#define DMA_UR1R_BUF_SIZE 256 u8 xdata UART1_RX_BUF[DMA_UR1R_BUF_SIZE]; void DMA_UART1_RX_Init(void) { DMA_UR1R_CFG 0x00; DMA_UR1R_STA 0x00; DMA_UR1R_AMT (u16)(DMA_UR1R_BUF_SIZE - 1); // 最大接收长度 DMA_UR1R_AH (u16)((u16)S1SBUF 8); // 源地址SBUF DMA_UR1R_AL (u16)((u16)S1SBUF); DMA_UR1R_DH (u16)((u16)UART1_RX_BUF[0] 8); // 目的地址接收缓冲区 DMA_UR1R_DL (u16)((u16)UART1_RX_BUF[0]); DMA_UR1R_CFG | 0x80; // 使能DMA } void UART1_Interrupt_Init(void) { S1CON ~0x10; // 先清除IDLE标志 S1CON | 0x01; // 使能串口接收中断RI S1CON | 0x04; // 使能空闲中断IDLEIE IE2 | 0x01; // 使能串口1中断总开关 EA 1; }串口1中断服务函数里区分RI和IDLEvoid UART1_ISR(void) interrupt 4 { u8 tmp; if (S1CON 0x10) // IDLE标志 { S1CON ~0x10; // 清除IDLE标志 // 当前DMA剩余字节数计算实际接收长度 u16 remain DMA_UR1R_AMT_DAT 1; // 注意这里要看数据手册的具体寄存器名 u16 recv_len DMA_UR1R_BUF_SIZE - remain; // 处理recv_len长度的数据然后重新启动DMA接收 ProcessFrame(UART1_RX_BUF, recv_len); // 重新配置DMA接收缓冲区地址和长度 DMA_UR1R_STA 0x00; DMA_UR1R_AMT (u16)(DMA_UR1R_BUF_SIZE - 1); DMA_UR1R_CFG | 0x80; } if (S1CON 0x01) // RI标志 { S1CON ~0x01; // 清除RI标志 } }4.3 一个让你第一帧就错位的清零顺序问题这个坑我花了一个下午才找到原因。第一次用DMA空闲中断接收上位机发来的第一帧数据解析出来是错位的表现为前两个字节丢失或者顺序错乱但后续数据帧全部正常。排查过程是先怀疑DMA配置检查寄存器没发现问题再怀疑波特率误差逻辑分析仪抓出来的波形没问题最后试着在第一帧前加了一个空延时居然正常了。这时候我才意识到问题出在“清除IDLE标志”和“重新启动DMA”的顺序上。第一次空闲中断触发后我在中断服务函数里先处理数据再清IDLE标志然后重新配置DMA。问题在于上位机是连续发送数据的第一帧结束触发IDLE中断CPU进入中断服务函数执行处理逻辑可能需要几十微秒如果这时上位机的第二帧数据已经到了而DMA的AMT寄存器还没有重新配置那么第二帧的前几个字节会被DMA继续写入旧的缓冲区位置等中断函数重新配置完DMA后新数据才对上位——结果就是第二帧数据头部混进了几个旧字节。正确的顺序是进入IDLE中断后第一件事就是停掉DMA接收或立即重新配置DMA缓冲区再慢慢处理数据。处理完数据后确认DMA已经处于空闲状态再启动下一轮接收。if (S1CON 0x10) { S1CON ~0x10; // 先清IDLE DMA_UR1R_CFG ~0x80; // 关DMA接收立刻停止搬运 // 此时再慢慢处理数据、读取剩余字节数 ... DMA_UR1R_STA 0x00; DMA_UR1R_CFG | 0x80; // 重新开启DMA接收 }提示任何涉及DMA重新配置的操作都建议先关闭DMA使能位配置完成后再打开。不然在配置过程中DMA可能继续响应外设请求导致数据写到错误的位置。5. 坑4中断优先级与临界区把DMA“搞死”5.1 串口中断和DMA中断的优先级分配STC8H的中断系统支持两级优先级低级和高级可以通过相关中断优先级寄存器配置。DMA串口发送完成中断、串口空闲中断、串口接收中断如果同时触发优先级处理不好就会出问题。我的分配原则是空闲中断设为最高优先级因为它代表一帧数据的边界处理不及时会导致帧错位或丢帧。DMA发送完成中断设为次高优先级它只影响发送效率晚一点处理不会丢数据最多是下一包数据不能及时发出去。串口接收中断RI在这种场景下可以保持低优先级因为DMA已经帮我们把数据搬到内存了RI只是作为辅助判断即使晚一点处理也不怕。配置优先级时不要在主程序里频繁开关EA总中断。有人习惯在进入临界区时直接EA0出来EA1如果在DMA传输过程中关掉EA超过一定时间串口接收的IDLE中断无法响应硬件上数据已经在SBUF里等DMA搬运但DMA可能因为这个原因没有及时响应请求结果就是数据虽然到了SBUF但最终被下一个字节覆盖形成隐性丢数据。5.2 实测一个不小心关掉EA导致的连续丢包我踩过的具体场景是在ADC采样代码里为了保证多通道采样切换的同步性在读取ADC结果时用了EA0、EA1的方式关中断。ADC采样转换时间不长大概十几微秒当时觉得没问题。但在115200波特率下一个字节的传输间隔是86.8微秒左右16个字节的帧间隔大约1.4毫秒理论上十几微秒的关中断根本不影响。但问题出在DMA发送触发上。我当时的DMA发送是软件触发方式一旦触发DMA进入“繁忙”状态开始一直把缓冲区数据搬运到SBUF。如果在这个搬运过程中EA0的时间过长DMA传输完成中断不能及时响应整个发送流程会停在“等待DMA传输完成标志”的循环里。由于while循环一直在跑CPU被占死ADC代码根本没机会执行整个系统像死机一样。解决方法是把DMA发送完成的中断优先级调高同时在等待DMA传输完成的while循环里加入超时退出机制避免万一DMA异常时程序卡死u16 timeout 0; while (!(DMA_UR1T_STA 0x01)) { if (timeout 60000) { // 超时强制退出并重新初始化DMA DMA_UART1_TX_Init(); return; } }这个超时时间按115200波特率和最大64字节的缓冲区计算60毫秒足够DMA发完所有数据。实测中正常流程根本不会进入超时分支但这个保护让我在后续调试其他问题时少了很多莫名其妙的“假死”。6. 坑5DMA通道资源共享ADC也来凑热闹6.1 STC8H的DMA通道与外设映射关系STC8H系列单片机内部的DMA通道数量有限而且多个外设之间可能映射到同一个DMA通道。以STC8H8K64U为例DMA通道1可以对应串口1接收也可以对应ADC转换结果读取DMA通道2可以对应串口1发送也可以对应SPI发送。如果你在配置串口DMA的同时想用ADC的DMA自动搬运采样结果就很可能撞车。我做采集板的时候恰好需要同时使用串口1的DMA发送和ADC的多通道DMA采集。结果就是两个功能单独测试都正常合在一起后串口发送的数据偶发错乱ADC采样值也不稳定。排查后发现STC8H的DMA通道选择是通过P_SW2相关的特殊功能寄存器配置的。我在初始化ADC时把DMA通道1切换到了ADC方向但串口1接收用的也是DMA通道1两个外设抢同一个通道数据自然混乱。6.2 解决方案明确通道分配并在初始化时统一规划解决方案有两种。第一种查数据手册的DMA通道映射表把串口发送和ADC采集分配到不同的DMA通道。第二种如果DMA通道实在不够用ADC放弃DMA方式改用定时器中断读取ADC结果。我的做法是串口1接收用DMA通道1串口1发送用DMA通道2ADC采集改用定时器中断方式。虽然ADC每次转换结束后的读取仍需要CPU参与但ADC转换频率可以控制得比较低比如1kHz采样率CPU占用非常小完全不影响串口DMA通信。这里有一个通用经验使用DMA前先把项目里所有需要DMA的外设列一个清单查数据手册确认每个外设能用的DMA通道规划好通道分配后再写代码。千万不要边写边配置等调试时发现通道冲突排查成本比写代码高得多。6.3 实测案例串口DMA与定时器中断ADC同时工作在115200波特率下串口1 DMA发送空闲中断接收同时ADC用定时器0中断触发采样采样率1kHz系统运行一整天没有出现串口丢帧或ADC数据错乱。这里的关键点是定时器0中断优先级设为低避免频繁打断DMA发送流程。ADC中断服务函数里只做“读ADC结果并保存到数组”这一件事数据处理放到主循环。串口空闲中断和DMA中断的优先级高于ADC中断。事后来看这个配置不算最优但胜在稳定可控。如果ADC采样率也需要很高比如10kHz以上那就得考虑换用其他DMA通道或者换用DMA通道更多的型号。7. 完整体验115200波特率下的实测数据7.1 测试环境与方法硬件STC8H8K64U核心板主频24MHz外部11.0592MHz晶振方案作为对比测试USB转TTL串口模块。 软件Keil C251官方库函数加寄存器直接操作。 测试方法PC端上位机通过串口发送定长数据帧单片机DMA接收并在空闲中断后原样回传回传使用DMA发送。逻辑分析仪抓取波形计算实际波特率和传输时间。7.2 三种收发组合的实测对比收发方式115200下1KB数据发送耗时CPU占用情况丢帧情况传统查询发送约278ms高CPU全程占用主循环任务多时偶发丢帧传统中断发送约178ms取决于中断处理效率中频繁进中断高负载时偶发溢出DMA发送约89ms极低搬运期间CPU可做其他事稳无丢帧从表格能看出DMA发送不仅CPU占用低传输总时间也最短。原因是传统发送方式每次发一个字节都要经过“查询标志-送数据-再查询”的循环CPU指令周期浪费严重而DMA可以以接近硬件极限的速度连续搬运数据。在接收侧我用PC端连续发送100万字节随机数据单片机DMA接收并回传PC端校验接收内容结果如下数据量帧长波特率丢帧数误码数100MB6411520000100MB12811520000100MB25611520000这是在我优化完上述所有坑点之后的结果。在优化之前同样的测试流程在256字节帧长下出现了若干次帧错位根因就是第四节说的空闲中断清零顺序问题。7.3 DMA方式下的CPU占用实测我在主循环里加了一个GPIO翻转操作用示波器测量GPIO翻转频率来估算CPU空闲率。传统查询发送时GPIO翻转频率约为400kHzDMA发送时同一段代码里GPIO翻转频率可以达到1.2MHz。换句话说DMA发送模式下CPU有大量空闲时间可以用来跑传感器数据处理、显示刷新等逻辑这就是DMA最大的价值。8. 常用问题排查速查表这里整理我在调试STC8H DMA串口通信过程中遇到过的典型问题按现象分类方便现场排查。故障现象可能原因排查与解决办法串口输出乱码波特率误差过大、主频不匹配检查定时器2重装值用逻辑分析仪实测波特率外部晶振优先内部IRC先校准DMA发送不启动触发源未开启、DMA通道配置错误检查DMA配置寄存器确认触发源选择正确软件触发方式要手动写触发位发送数据最后一个字节丢失DMA完成后立刻改了缓冲区或关了串口先等DMA完成再等TI标志置位再清标志接收数据首字节缺失或错位IDLE中断处理顺序不对进入IDLE中断先关DMA接收或先清标志再处理数据高负载下偶发丢帧关EA的临界区过长避免在串口收发期间长时间关总中断DMA中断优先级适当调高DMA与ADC同时使用异常DMA通道冲突查数据手册的DMA映射表规划通道分配必要时ADC改用定时器中断读取程序运行一段时间后“假死”DMA等待循环没有超时保护所有等待DMA完成的循环加超时退出超时后重新初始化DMA需要说明的是排查DMA问题时不要一上来就怀疑单片机坏了或者DMA模块有问题。我遇到的大多数问题最终定位都是配置顺序、标志清零顺序或者资源冲突这一类逻辑问题。逻辑分析仪是排查串口问题最有力的工具它能直接看到每个字节的实际波形和时间间隔比靠眼睛数串口调试助手的十六进制数据高效太多。9. 代码工程结构与关键初始化汇总9.1 工程文件划分建议STC8H的DMA串口通信代码建议按模块拆分文件便于维护和复用uart_dma.c串口初始化、DMA初始化、发送接收函数。uart_dma.h函数声明、缓冲区大小宏定义。main.c业务逻辑、数据帧解析、应用层调用。缓冲区建议使用xdata关键字因为STC8H的DMA在访问xdata空间时效率更高而且xdata空间容量大适合放串口缓冲区。默认的data空间只有128字节放一个256字节的接收缓冲区根本不现实。9.2 完整的初始化顺序初始化顺序会影响系统稳定性我的推荐顺序是配置系统主频内部IRC或外部晶振。初始化定时器2作为波特率发生器。初始化串口1工作模式。初始化DMA发送通道。初始化DMA接收通道。配置中断优先级。开启总中断EA。这里特别提醒一点DMA接收通道初始化之后如果暂时不需要接收数据可以先不使能DMA等需要接收时再打开。否则DMA上电后默认状态不确定某些寄存器残留值可能导致DMA立即开始搬运非法内存地址的数据。9.3 一个通用发送函数的完整实现下面这个函数是我在当前项目里实际使用的兼容DMA发送和失败超时保护void UART1_DMA_SendFrame(u8 *buf, u16 len) { u16 timeout 0; if (len 0 || len DMA_UR1T_BUF_SIZE) { return; } memcpy(UART1_TX_BUF, buf, len); // 确保上一次发送已经完成 while (DMA_UR1T_STA 0x01) { if (timeout 60000) { break; } } DMA_UR1T_STA 0x00; DMA_UR1T_AMT (u16)(len - 1); DMA_UR1T_CR 0x01; // 软件触发启动 timeout 0; while (!(DMA_UR1T_STA 0x01)) { if (timeout 60000) { DMA_UART1_TX_Init(); // 超时重新初始化 return; } } while (!(S1CON 0x02)) // 等待TI { if (timeout 60000) { break; } } S1CON ~0x02; }这个函数在115200波特率下发送64字节数据实测耗时约6毫秒其中大部分时间是在等待串口移位寄存器把最后一个字节发完。10. 最后再分享一个调试小技巧调试STC8H的DMA串口通信我强烈建议先跑通“串口接收中断DMA发送”的组合再过渡到“DMA接收空闲中断”的组合。原因是接收部分一旦开了DMA数据流就是连续的、不受控的排查问题时很难判断是数据没到、DMA没搬、还是空闲中断没触发。而发送部分相对可控先用传统方式接收把发送DMA调稳定再处理接收DMA整个过程的排错成本会低很多。另外官方数据手册里的DMA章节有一张外设通道映射表我建议把它打印出来贴在工位上。STC8H的DMA功能不复杂但寄存器多且命名相近DMA_UR1T和DMA_UR1R外观上几乎一样配置错了调试起来很费时间。我踩过洞以后的习惯是每次写DMA相关寄存器前先对照映射表在代码注释里写明“当前配置的是哪个通道、对应哪个外设、方向是什么”这样即使过了几个月再回头改代码也不会一头雾水。还有一个调试利器是多用逻辑分析仪。它不仅能看串口波形还能抓DMA触发信号和中断回调引脚的电平变化。我在代码里加了几个调试用的GPIO翻转点比如DMA传输完成时翻转一个引脚、空闲中断触发时翻转另一个引脚用逻辑分析仪同时抓这些引脚和串口TX/RX线整个数据流向一目了然。这个习惯帮我节省了非常多排查时间实测下来比反复在调试助手里看十六进制数据高效得多。毕竟串口调试助手只能告诉你“数据错了”但逻辑分析仪能告诉你是哪一步开始错的、为什么错。
返回列表