十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STC32G双串口中断模式收发实现与踩坑指南

STC32G双串口中断模式收发实现与踩坑指南 简介面向 STC32G 单片机开发者的串口中断通信示例演示串口1与串口2在中断模式下与电脑进行数据收发测试解决波特率配置、中断服务程序编写与双串口并发收发的调试问题适用于工业控制、设备通信、数据采集等需要稳定串口交互的场景。资源以 rar 压缩包形式提供共 10 个文件大小约 25KB包含 Keil 工程文件uvproj/uvopt、C 源文件、汇编源文件、编译生成的 hex 烧录文件以及 m51 地址映射文件工程可在 Keil 中直接打开、编译并烧录到目标板验证。特别之处在于同一功能同时提供 C 语言与汇编两套工程方便对照学习底层寄存器操作与高级语言实现的差异对理解 UART 中断机制和提升调试能力很有帮助。资源已有 553 人学习下载适合正在学习 STC32G 串口通信、或准备在项目中用中断方式处理多路串口数据收发的开发者参考。1. 用中断模式跑通STC32G串口1和串口2比轮询快在哪我第一次用STC32G做双串口小模块在串口调试助手点发送开发板毫无反应排查下来不是CH340驱动也不是接线而是中断标志没处理干净。后来翻开官方这套STC32G-DEMO-CODE-220311kw里的“10-串口1串口2中断模式与电脑收发测试”把main.c和UART1-UART2.asm对照着读才意识到STC32G的串口中断处理顺序和普通8051有不少细节要注意。下面用这个例程作为骨架来梳理串口1和串口2如何在中断模式下工作怎么配置寄存器、怎么写中断服务函数、怎么与电脑完成收发测试以及双串口同时收发时会踩到的坑。适合正在入门STC32G、想把轮询改成中断收发的开发者也适合想快速用官方demo做硬件验证的工程师。2. 串口1/串口2中断模式寄存器配置SCON、AUXR与定时器2波特率初值2.1 STC32G串口与经典8051的异同经典8051只有一个串口控制逻辑集中在SCON和SBUFSTC32G保留了这个体系同时增加了一路UART2。串口1的SCON、SBUF、中断标志TI/RI跟传统8051是同一套名字串口2则对应S2CON、S2BUF以及S2TI/S2RI。编写程序时最需要注意的是中断模式下寄存器配置顺序不能乱不能像查询模式那样直接在循环里等TI。因为STC32G是32位内核外设访问速度比标准8051快不少如果端口配置和中断使能做到一半被打断很容易出现“看起来初始化了实际没生效”的情况。另一个容易忽略的是引脚映射。STC32G通过P_SW1和P_SW2可以切换串口的引脚位置默认UART1在P3.0/P3.1UART2在P3.6/P3.7。如果之前用过的板子把UART1切到其他引脚直接烧demo就会收不到数据。所以拿到示例代码后第一件事是看main.c里有没有写P_SW1、P_SW2的赋值语句确认当前串口接在哪组引脚上。2.2 中断模式所需寄存器速查寄存器关键位作用本示例建议值AUXRS1ST2/T2x12/T2R选择定时器2分频并启动S1ST21, T2x121, T2R1SCONSM0/SM1/REN/TI/RI串口1工作模式与收发控制0x50S2CONS2SM0/S2SM1/S2REN/S2TI/S2RI串口2控制0x50IEEA/ES总中断和串口1中断EA1, ES1IE2ES2串口2中断使能ES21SCON赋0x50的含义是把SM0/SM1设为01即8位UART模式1同时置REN1允许接收。S2CON中同样有S2REN位位置也在bit4所以0x50同样适用。TI/RI和S2TI/S2RI分别在两个串口的对应位置位后必须由软件清这是后面中断函数里最关键的步骤。2.3 定时器2作波特率发生器时的初值计算STC32G的串口1和串口2都可以使用定时器2作为波特率发生器。在1T模式下初值计算公式为RCAP2 65536 - SYSclk / (4 * Baud)以24MHz、波特率115200为例RCAP2 65536 - 24000000/(4*115200) ≈ 65536 - 52.08取52得到0xFFCC。误差约0.16%在实际串口通信容差内短线收发稳定。如果系统时钟是11.0592MHz则初值为0xFFE8误差为0更适合高波特率长帧传输。系统时钟波特率1T模式下初值实际误差11.0592MHz1152000xFFE8012MHz1152000xFFE60.16%24MHz1152000xFFCC0.16%24MHz96000xFD4F0.16%注意如果换用内部IRC而不是外部晶振系统频率本身可能偏差1%高波特率下会出现“电脑收了几个字节后开始乱码”。串口调试助手显示乱码时先确认系统时钟源是否和初值计算时一致。提示波特率初值写入顺序也有讲究。先写T2L/T2H再置位T2R否则定时器可能以错误初值先启动一次产生首个字节波特率异常。2.4 main.c中的初始化代码以官方demo风格写两个初始化函数注意这里复用定时器2作为两个串口的波特率发生器所以UART2初始化里没有重新配置T2。void UART1_Init(void) { SCON 0x50; /* 模式1: 8位UART, REN1 允许接收 */ AUXR | S1ST2; /* 选定时器2做串口1波特率发生器 */ T2L 0xCC; T2H 0xFF; /* 24MHz/115200 重载值 */ AUXR | T2R | T2x12; /* 启动定时器2并设为1T模式 */ ES 1; /* 开串口1中断 */ EA 1; } void UART2_Init(void) { S2CON 0x50; /* 串口2 模式1, S2REN1 */ /* 根据STC32G.H头文件确认S2ST2置位复用T2作为波特率发生器 */ IE2 | ES2; /* 开串口2中断 */ }参数说明AUXR | S1ST2选择定时器2而不是定时器1代码里使用STC32G.H头文件中的符号位具体位位置因型号略有差异编译前最好在头文件里确认一次。T2L/T2H是高低位分开写入的重载初值因为STC32G的T2是16位计数器不能一次写完整16位。AUXR | T2R | T2x12中的T2R是定时器2运行控制位T2x12决定是1T还是12T模式两者同时置位才符合上面算初值时的前提。3. 中断服务函数里拆收发TI、RI标志与双串口缓冲区设计3.1 串口1中断服务函数的标准写法STC32G的串口1中断向量在Keil C251工程中对应interrupt 4。中断触发来源有两个发送完成TI和接收完成RI。查询模式里经常用while(!TI)等待中断模式则是在ISR中分别判断这两个标志。volatile uint8_t uart1_rx[256]; volatile uint8_t uart1_rx_head 0; volatile uint8_t uart1_rx_tail 0; void UART1_Handler(void) interrupt 4 { if (RI) { RI 0; /* 先清标志再取数 */ uint8_t ch SBUF; uint8_t next (uint8_t)(uart1_rx_head 1); if (next ! uart1_rx_tail) { /* 防止覆盖未消费数据 */ uart1_rx[uart1_rx_head] ch; uart1_rx_head next; } } if (TI) { TI 0; /* 如果要发送下一帧在这里写SBUF */ } }逻辑说明RI置一表示SBUF收到完整字节读取SBUF后必须软件清零否则会反复进入中断。注意先清RI再读SBUF避免后台程序与中断竞争时读到旧数据。写入环形缓冲时用next ! tail判断满留一格区分空和满满了则丢弃当前字节避免缓冲区指针错乱。3.2 串口2的中断入口与S2CON标志处理串口2的中断在C251编译器里对应interrupt 16向量地址是0x0083。它的标志位不是TI/RI而是S2CON的低两位S2TI与S2RI。处理逻辑和串口1几乎一样只是寄存器名和数据缓冲寄存器不同。部分初学者会把S2CON赋值成0x90其实模式1应该用0x500x90是模式3的配置。void UART2_Handler(void) interrupt 16 { if (S2CON 0x01) { /* S2RI */ S2CON ~0x01; /* 清接收标志 */ uint8_t ch S2BUF; uart2_rx[uart2_rx_head] ch; uart2_rx_head 0xFF; } if (S2CON 0x02) { /* S2TI */ S2CON ~0x02; /* 清发送标志 */ } }注意上面这个例子简化了缓冲区满判断实际工程中应当和串口1一样使用多点判断。S2BUF是串口2的数据寄存器读取它的动作不会自动清S2RI必须软件S2CON ~0x01。如果漏掉这一步串口2只会收到第一个字节。3.3 接收缓冲区用环形结构而不是数组累加如果ISR只是不断往数组下标写入主循环处理完再把下标清零两个UART同时到达时很容易互相踩。环形缓冲区则让生产者和消费者互相解耦。下面是一个最简单的环形缓冲区实现typedef struct { uint8_t buf[256]; volatile uint8_t head; volatile uint8_t tail; } ringbuf_t; uint8_t rb_is_full(ringbuf_t *rb) { return (uint8_t)(rb-head 1) rb-tail; } void rb_put(ringbuf_t *rb, uint8_t ch) { if (!rb_is_full(rb)) { rb-buf[rb-head] ch; } }参数说明head指向下一个要写入的位置tail指向下一个要读出的位置。判断满的条件是head1等于tail这样“空”用headtail表示。缓冲区容量256字节刚好用uint8_t溢出回绕省去取模运算。实际项目里如果主循环解析速度跟不上可以把缓冲区扩大到512或1024字节也可以增加一个溢出计数器定位问题。3.4 发送中断的状态机避免忙等但又不能一直开中断“中断模式收发测试”最常见的误区是发送仍然写成while(!TI)高波特率长数据下会卡死主循环。更合理的做法是启动一次发送其余内容在TI中断里继续排队。下面给出串口1发送中断的状态机片段volatile uint8_t *uart1_tx_ptr; volatile uint16_t uart1_tx_len, uart1_tx_i; volatile uint8_t uart1_tx_busy 0; void uart1_send_async(uint8_t *buf, uint16_t len) { while (uart1_tx_busy); /* 等上一帧发完 */ uart1_tx_ptr buf; uart1_tx_len len; uart1_tx_i 0; uart1_tx_busy 1; SBUF buf[0]; /* 触发第一次TI */ } void UART1_Handler(void) interrupt 4 { if (TI) { TI 0; if (uart1_tx_i uart1_tx_len) { SBUF uart1_tx_ptr[uart1_tx_i]; /* 发下一字节 */ } else { uart1_tx_busy 0; /* 发完关闭状态 */ } } }逻辑说明uart1_send_async先判断上一帧是否发完再把指针、长度和下标保存到全局变量写第一个字节触发TI中断。TI中断里每发送完一个字节就写下一个直到下标越界把tx_busy清0。这个状态机比在中断里循环发送一整段数据要安全因为串口发送本身是逐字节慢速过程循环等待TI会让其他中断饿死。串口2的发送中断同理把SBUF换成S2BUF把TI换成S2CON.1即可。项目串口1串口2数据寄存器SBUFS2BUF接收标志RIS2CON.0发送标志TIS2CON.1中断号C251416中断使能位ESIE2中的ES24. 与电脑收发测试CH340驱动、串口调试助手和波特率误差排查4.1 硬件连接USB转串口模块的TXD/RXD交叉电脑与STC32G通信一般通过USB转串口模块常见方案是CH340或FTDI芯片。接线时把模块的RXD接到单片机TXD模块TXD接单片机RXD必须共地。如果UART1通过P_SW1切换到了P3.6/P3.7注意重新编译后的hex要对应新的引脚位置否则板子上的丝印可能与实际引脚不一致。CH340驱动需要安装Windows设备管理器里能看到CH340的COM口后才能继续。有些Win10版本会自动装驱动如果显示“正在安装设备”但最终没有生成COM口去官网下载官方驱动重装。常见的“串口烧写失败”问题里有相当一部分是设备管理器里根本没有这个COM口而不是STC-ISP设置不对。4.2 串口调试助手参数与测试流程串口调试助手的选择不少XCOM、SSCOM、友善串口助手都可以。联调参数按初始化代码来波特率115200数据位8停止位1无校验位。注意如果MCU用的是12MHz外部晶振115200初值有0.16%误差短帧目测稳定长帧偶尔出错这时把波特率降到9600更稳妥。测试步骤下载示例hex到STC32G确认电源和串口引脚。打开串口调试助手选择对应COM口并打开。发送十六进制数据观察接收区是否有回显或转发。如果实现的是双串口桥接另一个串口助手连到UART2像操作普通串口一样直接发数据。示例代码通常会把串口1收到的数据原样回发同时把串口2收到的数据转发到串口1。这样测试时最简单的是在UART1的串口助手里发送任意字符看是否在UART2的串口助手里出现相同数据反之亦然。若两侧都通说明中断模式收发链路完整。4.3 收不到数据时按中断链路由近到远排查如果打开助手后发数据无响应按下面顺序检查。现象可能原因处理发送无反应ISR无动作EA或ES没打开检查初始化中EA1只收到第一个字符RI清位位置错误确认ISR最先清RI再读SBUF收到乱码波特率初值或T2x12不对用逻辑分析仪测TXD波形周期打开串口提示被占用调试助手重复打开COM口关闭另一个串口软件下载后再上电无响应烧写时序或驱动问题使用STC-ISP冷启动下载如果手头有示波器或逻辑分析仪直接测TXD脚是否有电平翻转比猜测快得多。没有示波器时点发送刹那用万用表测TXD平均电压有波形变化说明数据从CPU发出问题在USB转串口侧或接线交叉。4.4 一个简单的Python联调脚本串口调试助手适合手点但要测1000帧有没有丢包写成脚本更好。下面用Python pyserial做一个简单的回环测试发送方等待接收方原样回显import serial ser serial.Serial(COM5, 115200, timeout0.5) ok 0 fail 0 for i in range(1000): payload bACK%04d % i ser.write(payload) echo ser.read(len(payload)) if echo payload: ok 1 else: fail 1 ser.close() print(ok%d fail%d % (ok, fail))逻辑说明脚本循环发送带序号的数据每次等待长度相同的回显并对比。如果fail不为0说明中断接收缓冲区丢字节或主循环回发逻辑不及时。串口对象里timeout设为0.5秒是为了防止串口没回包时整个测试卡死。这个脚本同样适用于9600或者115200只要和固件配置一致。5. 双串口同时收发冲突排查从状态位检测到环形缓冲区5.1 中断优先级配置让串口1处理不被串口2打断UART1和UART2同时收到数据时单片机同一时刻只能运行一个ISR。默认情况下中断不会嵌套如果对实时性要求高可以通过中断优先级寄存器把串口1设置为高优先级。STC32G的中断优先级控制分布在IP和IP2寄存器中串口1对应IP里的相关位串口2对应IP2里的相关位。配置示例IP | 0x10; /* 串口1中断设为高优先级 */ IP2 | 0x10; /* 串口2中断设为低优先级 */注意不同型号的IP位定义可能不同最好在STC32G.H头文件里查具体位名比如PS1、PS2。设置优先级后串口2的ISR可能会被串口1的ISR打断因此两个ISR里要注意不能使用同一个非重入函数处理数据否则会破坏缓冲区现场。5.2 从状态位检测到丢帧计数很多例程只用“查询TI状态位”来判断发送是否完成单串口时没问题双串口同时跑时如果两个ISR共用一个忙碌标志可能出现一个串口等另一个串口释放。排查技巧是增加丢帧计数在环形缓冲区满或发送忙碌时累加一个变量联调结束后读出来看。volatile uint16_t uart1_overrun_cnt 0; volatile uint16_t uart2_overrun_cnt 0; void rb_put(ringbuf_t *rb, uint8_t ch, uint16_t *overrun) { if (!rb_is_full(rb)) { rb-buf[rb-head] ch; } else { (*overrun); } }这样在ISR里调用rb_put(u1_rb, ch, uart1_overrun_cnt)当丢帧发生时计数器会增长。把这两个变量通过串口打印出来就能判断是主循环处理太慢还是中断频率太高。如果丢帧多优先增大缓冲区其次降低中断优先级最后考虑在主循环里采用双缓冲结构。5.3 串口1和串口2相互转发时的自锁问题当例程把UART1收到的数据从UART2发出同时把UART2收到的数据从UART1发出时要注意不能产生回环如果UART1和UART2在硬件上被一根线连在一起数据会无限循环。常见做法是调试时只开一个方向的转发或者利用状态位检测发送方向不让同一条数据在两条串口之间重复传递。验证方法是在电脑上开两个串口助手A连UART1B连UART2A发送带唯一序号的数据B收到后手动回一个不同的帧观察A收到的是否只有B发的那一帧。连续跑几百帧仍然应当没有重复帧。如果出现重复数据基本可以确定中断中的TI/RI标志被清晚了。本文还有配套的精品资源点击获取
返回列表