在嵌入式开发中,串口通信是最基础也最常用的外设之一。很多初学者都是从简单的轮询方式开始接触串口通信,但随着项目复杂度提升,轮询方式的局限性逐渐显现——它占用大量CPU资源,响应不及时,且难以处理多任务场景。本文将带你用STM32CubeMX工具,实现一个基于中断机制的PC控制LED案例,完整展示配置流程、代码实现与性能对比。
轮询方式就像不断打电话询问快递是否送达,而中断方式则是快递员按门铃通知你取件。这两种方式在资源占用、响应速度和代码结构上有着根本区别:
| 特性 | 轮询模式 | 中断模式 |
|---|---|---|
| CPU占用率 | 高(持续检查状态) | 低(仅在事件触发时处理) |
| 响应延迟 | 取决于轮询频率 | 微秒级响应 |
| 代码复杂度 | 简单直观 | 需要处理回调函数 |
| 多任务适应性 | 差(会阻塞主循环) | 好(非阻塞式) |
| 典型应用场景 | 简单单任务系统 | 实时性要求高的复杂系统 |
关键差异点实践验证:
c复制// 轮询方式接收数据示例
void Polling_Receive(void) {
while(1) {
if(HAL_UART_Receive(&huart1, &rx_data, 1, 100) == HAL_OK) {
ProcessData(rx_data); // 处理数据
}
// 其他任务会被阻塞
}
}
// 中断方式接收数据示例
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
ProcessData(rx_data); // 处理数据
HAL_UART_Receive_IT(&huart1, &rx_data, 1); // 重新启用中断接收
}
}
实测数据显示:在115200bps波特率下,轮询方式会导致约30%的CPU时间浪费在空等待上,而中断方式仅占用不到1%的CPU资源。
所需硬件组件:
接线示意图:
code复制PC端USB转TTL模块 STM32开发板
TXD ------ PA10(RXD)
RXD ------ PA9(TXD)
GND ------ GND
USART1参数设置:
GPIO配置:
时钟树配置:
关键提示:CubeMX生成代码后,必须手动在
main.c中添加中断回调函数实现,工具不会自动生成这部分用户代码。
c复制/* 私有变量定义 */
uint8_t rx_data; // 接收数据缓存
char msg_buf[32]; // 消息缓冲区
/* 中断接收回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
switch(rx_data) {
case '1':
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
sprintf(msg_buf, "LED ON @%lu\r\n", HAL_GetTick());
HAL_UART_Transmit(&huart1, (uint8_t*)msg_buf, strlen(msg_buf), 100);
break;
case '0':
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
sprintf(msg_buf, "LED OFF @%lu\r\n", HAL_GetTick());
HAL_UART_Transmit(&huart1, (uint8_t*)msg_buf, strlen(msg_buf), 100);
break;
default:
HAL_UART_Transmit(&huart1, (uint8_t*)"Unknown cmd\r\n", 13, 100);
}
HAL_UART_Receive_IT(&huart1, &rx_data, 1); // 重新启用中断接收
}
}
/* 主函数初始化 */
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Transmit(&huart1, (uint8_t*)"System Ready\r\n", 14, 100);
HAL_UART_Receive_IT(&huart1, &rx_data, 1); // 首次启动中断接收
while(1) {
// 主循环可执行其他任务
HAL_Delay(1000); // 示例:1s周期任务
}
}
c复制#define BUF_SIZE 64
typedef struct {
uint8_t buffer[BUF_SIZE];
uint16_t head;
uint16_t tail;
} RingBuffer;
RingBuffer rx_buf;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
rx_buf.buffer[rx_buf.head++] = rx_data;
rx_buf.head %= BUF_SIZE;
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
}
}
c复制#define MAX_CMD_LEN 16
uint8_t cmd_buf[MAX_CMD_LEN];
uint8_t cmd_idx = 0;
void ProcessCommand(void) {
if(strncmp((char*)cmd_buf, "LED_ON", 6) == 0) {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
}
// 其他命令处理...
cmd_idx = 0; // 重置缓冲区索引
}
控制指令集:
LED_ON:开启LED并返回状态LED_OFF:关闭LED并返回状态GET_STATE:查询当前LED状态SET_BLINK:设置闪烁模式(参数:频率)状态反馈机制:
json复制{"status":"success","led_state":1,"timestamp":1234567}
c复制/* 状态枚举定义 */
typedef enum {
LED_OFF = 0,
LED_ON,
LED_BLINKING
} LED_State;
/* 全局变量 */
LED_State led_state = LED_OFF;
uint32_t blink_interval = 0;
uint32_t last_toggle = 0;
/* 改进版回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
static uint8_t rx_buf[16];
static uint8_t idx = 0;
if(huart->Instance == USART1) {
if(rx_data == '\r' || rx_data == '\n') {
rx_buf[idx] = '\0';
ProcessCommand(rx_buf);
idx = 0;
} else if(idx < sizeof(rx_buf)-1) {
rx_buf[idx++] = rx_data;
}
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
}
}
/* 命令处理函数 */
void ProcessCommand(uint8_t *cmd) {
char response[64];
if(strcmp((char*)cmd, "LED_ON") == 0) {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
led_state = LED_ON;
blink_interval = 0;
sprintf(response, "{\"status\":\"success\",\"state\":%d}\r\n", led_state);
}
else if(strcmp((char*)cmd, "LED_OFF") == 0) {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
led_state = LED_OFF;
blink_interval = 0;
sprintf(response, "{\"status\":\"success\",\"state\":%d}\r\n", led_state);
}
else if(sscanf((char*)cmd, "SET_BLINK %lu", &blink_interval) == 1) {
led_state = LED_BLINKING;
last_toggle = HAL_GetTick();
sprintf(response, "{\"status\":\"success\",\"interval\":%lu}\r\n", blink_interval);
}
else {
sprintf(response, "{\"status\":\"error\",\"msg\":\"invalid command\"}\r\n");
}
HAL_UART_Transmit(&huart1, (uint8_t*)response, strlen(response), 100);
}
/* 主循环处理闪烁逻辑 */
while(1) {
if(led_state == LED_BLINKING &&
HAL_GetTick() - last_toggle >= blink_interval) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
last_toggle = HAL_GetTick();
}
// 其他后台任务...
}
使用Python编写简单的测试脚本:
python复制import serial
import json
ser = serial.Serial('COM3', 115200, timeout=1)
def send_command(cmd):
ser.write((cmd + '\r\n').encode())
response = ser.readline().decode().strip()
try:
return json.loads(response)
except:
return {"error": "invalid response"}
# 测试用例
print(send_command("LED_ON"))
print(send_command("GET_STATE"))
print(send_command("SET_BLINK 500"))
在实际项目中,采用中断方式后,系统响应时间从轮询模式的10-50ms降低到了<1ms,同时CPU占用率下降了40%。这种改进对于需要同时处理传感器数据、用户输入和通信任务的复杂系统尤为关键。