STM32 串口通信实战(包含串口、中断、DMA)
本文介绍了基于STM32F103C8T6的串口通信开发实践,包括基础配置、中断控制和DMA传输三种实现方式。通过STM32CubeMX完成硬件初始化,使用Keil MDK-ARM进行开发,实现了115200bps波特率的串口通信。实验内容涵盖:1)无延时连续数据发送;2)通过中断方式响应上位机控制指令("#"暂停/"*"继续);3)采用DMA完成高速数据传输
STM32 串口通信实战(包含串口、中断、DMA)
一、目的
- 掌握 STM32CubeMX 与 Keil 的协同开发流程,熟悉 HAL 库串口配置方法。
- 实现 STM32 向上位机连续发送数据,观察无延时发送时的数据传输情况。
- 通过中断方式实现上位机指令控制(“#”暂停、“*”继续),理解中断响应机制。
- 采用 DMA 方式完成高速串口发送,对比 CPU 占用率差异。
- 利用 Keil 软件仿真逻辑分析仪验证串口波特率时序正确性。
二、环境
| 类别 | 具体参数 |
|---|---|
| 硬件 | STM32F103C8T6 核心板、USB 转TTL模块、杜邦线 |
| 软件 | STM32CubeMX 、Keil MDK-ARM 5、串口调试助手 |
| 通信参数 | 波特率 115200bps、数据位 8 位、停止位 1 位、无校验位、无硬件流控 |
三、原理
1. 串口通信原理
串口通过异步通信方式传输数据,每帧数据包含 1 位起始位(低电平)、8 位数据位、1 位停止位(高电平),无校验位时共 10 位。波特率 115200bps 表示每秒传输 115200 位,单个位传输时间约 8.68μs。
2. 中断原理
开启 USART 接收中断后,当上位机发送数据时,STM32 会触发中断,进入中断服务函数处理指令(如“#”暂停发送、“*”继续发送),避免主循环阻塞,提升响应速度。
3. DMA 原理
DMA(直接存储器访问)可在内存与外设间直接传输数据,无需 CPU 干预。串口 DMA 发送时,CPU 仅需初始化 DMA 通道和缓冲区,后续数据传输由 DMA 自动完成,适合高速连续发送场景。
四、步骤与实现
1. 基础环境配置(STM32CubeMX)
1.1 芯片与时钟配置
-
打开 STM32CubeMX,选择芯片“STM32F103C8T6”。
-
配置时钟:RCC 选择“HSE 外部晶振”,系统时钟(SYSCLK)设为 72MHz,USART1 挂载于 APB2 总线,时钟频率 72MHz。

1.2 USART1 配置
-
引脚配置:PA9 设为“USART1_TX”(复用推挽输出),PA10 设为“USART1_RX”(浮空输入)。
-
串口参数:模式选择“异步通信(Asynchronous)”,波特率 115200bps,数据位 8 位,停止位 1 位,校验位“无”,流控“无”。

1.3 中断与 DMA 补充配置
-
中断配置:进入“NVIC Settings”,勾选“USART1 global interrupt”,抢占优先级 1,子优先级 1。
-
DMA 配置:进入“DMA Settings”,添加“USART1_TX”通道(DMA1_Channel4),方向“内存到外设”,模式“循环模式”,内存地址自增使能,数据宽度“字节”。

1.4 生成工程
选择“Keil MDK-ARM”作为 IDE,勾选“Generate peripheral initialization as a pair of .c/.h files per peripheral”,生成工程并打开。
2. 基础串口通信实现
无延时连续发送+上位机指令控制
代码实现(main.c):
#include "main.h"
#include <string.h>
UART_HandleTypeDef huart1;
uint8_t rx_data; // 接收上位机指令的缓冲区(1字节)
uint8_t send_flag = 1; // 发送使能标志(1:发送,0:暂停)
uint8_t send_data[] = "hello windows!\r\n";
int main(void) {
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
while (1) {
// 非阻塞接收上位机指令,超时时间 10ms(避免主循环阻塞)
if (HAL_UART_Receive(&huart1, &rx_data, 1, 10) == HAL_OK) {
if (rx_data == '#') {
send_flag = 0; // 接收“#”,暂停发送
} else if (rx_data == '*') {
send_flag = 1; // 接收“*”,继续发送
}
}
// 根据标志位控制发送
if (send_flag) {
UART_SendString(send_data);
HAL_Delay(50); // 加短延时,避免发送过于密集
}
}
}
实验现象:
-
发送“*”:上位机连续接收“hello windows!”;
-
发送“#”:上位机接收停止;再次发送“

3. 中断与 DMA 方式实现
3.1 :串口中断方式控制
代码实现(main.c + stm32f1xx_it.c):
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stm32f1xx_hal_usart.h"
#include "stdio.h"
#include "string.h"
uint8_t rx_buf; // 存储接收的字符
uint8_t send_en = 1; // 发送使能标志,1为允许,0为暂停
char re_a = '#';
char send_buf[] = "hello windows! \r\n";
// 启动串口中断接收(单次接收1字节)
void UART_StartReceiveIT(void) {
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
}
UART_HandleTypeDef huart1;
// 串口接收中断回调函数(接收完成后自动调用)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart == &huart1)
{
// 根据接收的字符控制发送使能
if (rx_buf == '#')
{
send_en = 0; // 收到“#”,暂停发送
// 发送单个字符'#'
HAL_UART_Transmit(&huart1, (uint8_t*)&re_a, 1, 100);
}
else if (rx_buf == '*')
{
send_en = 1; // 收到“*”,继续发送
}
// 重新启动接收中断,确保能持续接收数据
// 使用do-while循环确保中断能重新启动
do
{
if (HAL_UART_Receive_IT(&huart1, &rx_buf, 1) == HAL_OK)
{
break;
}
} while (1);
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
UART_StartReceiveIT(); // 初始化后启动中断接收
while (1) {
if (send_flag) {
HAL_UART_Transmit(&huart1, send_data, strlen((char*)send_data), 0xFFFF);
HAL_Delay(10);
}
}
}
// stm32f1xx_it.c 部分(中断服务函数)
#include "usart.h"
void USART1_IRQHandler(void) {
// 调用 HAL 库中断处理函数,间接触发回调
HAL_UART_IRQHandler(&huart1);
}
实验现象:
与任务二(2)功能一致,但中断方式响应速度更快(上位机发送指令后,STM32 可立即响应)。
3.2 :串口 DMA 连续发送
代码实现(main.c):
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_tx;
// 定义要发送的字符串
#define SEND_STR "hello world\r\n"
// 字符串长度(不含结束符)
#define STR_LEN (sizeof(SEND_STR) - 1)
// 发送缓冲区大小(建议为字符串长度的整数倍,此处用4倍避免频繁中断)
#define TX_BUFFER_SIZE (STR_LEN * 4)
uint8_t tx_buffer[TX_BUFFER_SIZE];
int main(void) {
HAL_Init();
SystemClock_Config();
MX_DMA_Init(); // DMA 初始化(CubeMX 自动生成)
MX_USART1_UART_Init();// USART1 初始化
// 填充 DMA 发送缓冲区(循环复制字符串)
for (uint16_t i = 0; i < sizeof(tx_buffer); i++) {
tx_buffer[i] = SEND_STR[i % TX_LEN];
}
// 启动 DMA 循环发送(无需 CPU 干预)
HAL_UART_Transmit_DMA(&huart1, tx_buffer, sizeof(tx_buffer));
while (1) {
// 主循环空闲,DMA 自动完成数据传输
}
}
// DMA 发送完成回调函数(可选,循环模式下自动重启)
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if (huart == &huart1) {
// 可在此处更新 tx_buffer 数据(循环模式下发送不中断)
}
}
实验现象:
上位机连续、无丢失接收“hello world”,即使波特率提升至 1Mbps 仍稳定传输。
4. 任务四:Keil 软件仿真分析波特率
4.1 仿真配置步骤
-
打开 Keil 工程,点击
Debug → Start/Stop Debug Session进入仿真模式。 -
打开逻辑分析仪:
View → Analysis Windows → Logic Analyzer。 -
添加串口 TX 引脚:点击逻辑分析仪界面的
Setup,在“Signal”栏输入PORTA.9(USART1_TX 引脚),“Display Type”选择“Bit”,点击“Add”。 -
启动仿真:点击
Run按钮,等待 2~3 秒后点击Stop,观察时序波形。 -
其他keil配置:

4.2 波特率计算
- 波形分析:1 个字符(如“h”,ASCII 码 0x68)包含 1 位起始位(低电平)、8 位数据位、1 位停止位,共 10 位。
- 时间测量:在逻辑分析仪波形上,测量 1 个字符的总传输时间
T(10 位 / 115200bps ≈ 86.8μs)。 - 计算实际波特率:
波特率 = 10 / T(实测值与理论值误差 <1%)。
仿真结果:
实测 1 个字符传输时间约 87μs,计算波特率 ≈ 114943bps,误差 <0.2%,符合设计要求。
五、总结
- 数据丢失原因:无延时连续发送时,STM32 代码执行速度远高于串口传输速度,导致发送缓冲区溢出,需通过
HAL_Delay()或 DMA 避免。 - 中断与 DMA 对比:中断适合响应上位机指令(低延迟),DMA 适合高速连续传输(零 CPU 占用)。
- 仿真验证价值:Keil 软件仿真可无需示波器,直接观察串口时序,验证波特率正确性,降低硬件调试成本。
六、附录
-
硬件接线图: STM32 PA9(TX)→ USB 转串口 RX、PA10(RX)→ USB 转串口 TX、共地引脚。

-
串口助手配置图:标注端口号、波特率、数据位/停止位/校验位设置。

仿真验证价值*:Keil 软件仿真可无需示波器,直接观察串口时序,验证波特率正确性,降低硬件调试成本。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)