STM32F103C8T6串口实验:发送hello、中断接收和DMA
这次实验还是用上次的C8T6板。要求是让板子一直发送hello windows!,电脑发一个#后暂停,发*后继续,再分别用轮询、中断和DMA来做。
串口程序不长,不过实际接线时还是遇到了问题。下面把配置、主要代码和接板后的结果记一下。
一、实验准备
- STM32F103C8T6核心板
- Horco DAP下载器,带串口功能
- CubeMX、Keil,HAL库
- 杜邦线、面包板
- 电脑上的串口接收窗口
最开始接的是一只独立串口模块,但电脑没有识别到它。后来发现串口模块很热好像烧了,就换成DAP自带的串口,电脑显示为COM4。下载和串口通信可以共用这只DAP,但它们接的是不同的引脚。
| C8T6 | DAP | 用途 |
|---|---|---|
| PA9 | RXD | 板子发送,电脑接收 |
| PA10 | TXD | 电脑发送,板子接收 |
| GND | GND | 共地 |
| PA13 | SWDIO/TMS | 下载程序 |
| PA14 | SWCLK/TCK | 下载程序 |
TX和RX要交叉接。这样做实验才不烧芯。
二、串口参数和CubeMX配置
USART1选择异步模式,参数如下:
| 参数 | 设置 |
|---|---|
| 波特率 | 115200 |
| 数据位 | 8位 |
| 停止位 | 1位 |
| 校验 | 无 |
| 硬件流控 | 无 |
| TX/RX | PA9、PA10 |
SYS里面保留Serial Wire。时钟沿用原工程的内部HSI,通过PLL配置成64MHz:
HSI 8MHz / 2 × 16 = 64MHz APB1 = 32MHz APB2 = 64MHzUSART1用APB2时钟。这样不用依赖外部晶振,三个版本都保持相同的时钟设置。
中断版需要打开USART1的中断。DMA版添加USART1_TX,对应DMA1_Channel4,方向选Memory to Peripheral,模式选Normal,内存地址递增,外设地址不递增,数据宽度都选Byte。DMA中断和USART1中断都要打开。
三个工程分别放在01_HAL_Polling、02_HAL_Interrupt、03_HAL_DMA里。都可以直接打开Keil工程,配套的.ioc用来查看或重建配置。
三、先用轮询发送hello
发送的内容是下面这个数组:
staticuint8_thello[]="hello windows!\r\n";这里用了英文感叹号,再加回车换行,一共16字节。轮询版每次发送一个字节,发送前检查有没有收到电脑的字符:
if(ch=='#')g_running=0U;elseif(ch=='*')g_running=1U;发送时用HAL_UART_Transmit()。每发完一个字节,主循环继续检查接收状态。暂停时也要检查输入,否则发了#后就收不到恢复用的*了。
默认TX_DELAY_MS是0,没有额外等待。烧录后,电脑选COM4、115200、8N1,就能看到hello一直往下滚。
后面把TX_DELAY_MS改成50做对照。无间隔时,约2秒收到20929字节;加50ms间隔后,约2.037秒收到641字节,接收速度降到约315 Byte/s,变化很明显。
四、改成中断方式
中断版先挂载一个字节的接收:
HAL_UART_Receive_IT(&huart1,&rx_byte,1U);USART1中断入口调用HAL处理函数:
voidUSART1_IRQHandler(void){HAL_UART_IRQHandler(&huart1);}收到一个字节后,HAL进入接收完成回调。里面处理#、*,然后再调用一次HAL_UART_Receive_IT(),准备收下一个字节。重新挂载这一步不能漏。
发送也改为HAL_UART_Transmit_IT()。发完当前一行后,回调清除忙标志,主循环再决定是否发送下一行。中断里只处理简单状态,没有放延时或整段阻塞发送。
实际测试时,发#后接收计数停住,发*后继续增加。下面是暂停时的截图:
窗口里之前的文字还在,但是近约0.5秒接收显示0字节,总计数停在416604。这说明暂停的是后面的发送,并不是把历史内容清空。
恢复测试也保留了日志:中断版发*后,约0.5秒收到5697字节,内容符合hello加回车换行的格式。
五、DMA连续发送
DMA版准备了256字节的缓冲,相当于16行hello。启动发送的主要调用是:
HAL_UART_Transmit_DMA(&huart1,dma_data,sizeof(dma_data));用Normal模式时,一组数据发完就结束,下一组由主循环重新启动。缓冲只在初始化时填好,发送过程中不修改。电脑发来的控制字符仍然用中断接收。
这里除了DMA中断,还要打开USART1中断。DMA搬完最后一个字节后,HAL还要等USART真正发送结束;如果只开DMA中断,发送状态可能一直忙,后面就不能正常续发。
收到#后,不启动下一组,当前256字节先发完。所以DMA版暂停可能比中断版稍晚一些。115200下,一组256字节的理论发送时间约22.22ms。
DMA先测了115200,之后把板子和电脑都改成460800再测一次。测试结束后,工程中的默认参数已恢复115200。
六、上板测试结果
三个版本都通过了Keil和GNU Arm编译,Keil是0错误、0警告。接板测试前备份了原来的64KiB Flash,每次烧录后也做了回读比较。
| 方式 | 采样时间 | 接收字节数 | 接收速度 |
|---|---|---|---|
| 轮询115200 | 2.0021s | 20929 | 10453.3 Byte/s |
| 中断115200 | 2.0031s | 22913 | 11438.5 Byte/s |
| DMA115200 | 2.0028s | 23041 | 11504.5 Byte/s |
| DMA460800 | 2.0002s | 92097 | 46042.8 Byte/s |
四组测试都能暂停和恢复。表里的速度是电脑收到的字节数除以采样时间,不是用波形测出来的波特率。截图中的总计数来自更长时间运行,和表中单次采样的数量不同。
115200的8N1串口,每字节占10位,理论载荷上限是11520 Byte/s。中断和DMA的实测速度比较接近这个值,轮询版因为逐字节查询和调用,稍慢一些。
七、这次遇到的问题
第一个问题是串口选错了。独立模块没有被识别,电脑上出现的COM4其实属于DAP。最开始信号线接在独立模块上,却用COM4测试,当然收不到数据。换到DAP的RXD、TXD并交叉连接后才正常。
第二个是接收窗口有时停在半个单词,例如末尾只显示he。串口数据是分批读进窗口的,下一批到来后会继续显示,不能只凭这一小段就判断丢字节。检查原始接收数据后,本次采样符合重复格式。
还有一点要区分:CH340的TTL串口和RS-232电平不是同一种东西。RS-232需要电平转换,不能直接接到STM32引脚。不接VCC和不接GND也不是一回事,模块如果由USB供电,可以不用另接VCC;单端通信仍需要共同的地参考。
八、文件传输和波形测量
文件传输按8N1估算:
理论时间 = 文件字节数 × 10 / 波特率115200下,1MiB至少约91.02秒,10MiB约15.17分钟,100MiB约151.70分钟。实际还会受软件间隔、协议开销等影响。收到文件后需要比较大小和SHA-256。双电脑RS-232传文件这部分还没有实际做,报告里保留了接线和操作步骤。
Keil位波形也还没有取得光标读数。可以用0x55辅助观察,按跨越位数除以光标时间差计算波特率。115200的理论位宽是8.680556μs,但不能把这个数直接填成实测值。
参考资料
配置和函数说明参考了下面两篇教程,实验记录采用准备、配置、代码、接板现象的写法。本次代码和接收数据对应自己的C8T6实验。就酱子把。
- HAL库 STM32CubeMX教程四:UART串口通信详解
- HAL库 STM32CubeMX教程十一:DMA串口发送接收
- ST官方STM32CubeF1
- Keil USART1仿真说明