1. 串口通信与定时器协同工作原理
在嵌入式系统中,串口通信和定时器是两个最基础也最重要的外设模块。当我们需要实现周期性数据采集或定时控制时,将定时器与串口结合使用是常见方案。以STM32为例,定时器2(TIM2)作为通用定时器,具有以下特性:
- 16位自动重装载计数器
- 可编程预分频器(1-65536分频)
- 4个独立通道(输入捕获/输出比较/PWM生成)
- 支持向上、向下、中央对齐计数模式
串口1(USART1)则是STM32最常用的全双工串行通信接口,支持异步通信和单线半双工通信。当两者协同工作时,定时器负责精确的时间基准生成,串口负责数据收发,形成完整的定时通信系统。
关键提示:定时器中断优先级应高于串口中断,避免通信时序被打乱。在CubeMX配置时,建议将TIM2中断优先级设置为0,USART1中断优先级设置为1。
2. 硬件环境搭建与CubeMX配置
2.1 最小系统搭建
以STM32F103C8T6(蓝桥杯常用开发板)为例,需要准备:
- 开发板核心电路(含晶振和复位电路)
- USB转TTL模块(CH340G或CP2102)
- 杜邦线若干
- LED指示灯(用于状态显示)
接线示意图:
code复制STM32F103C8T6 USB转TTL模块
PA9(TX) ------> RX
PA10(RX) ------> TX
GND ------> GND
2.2 CubeMX关键配置步骤
-
时钟树配置:
- HSE选择外部晶振(8MHz)
- 系统时钟设为72MHz
- APB1定时器时钟设为72MHz(TIM2挂载在APB1)
-
TIM2参数设置:
c复制Prescaler = 71 // 实际分频系数=71+1=72 Counter Mode = Up // 向上计数 Period = 4999 // 自动重载值 auto-reload = Enable // 使能自动重载计算公式:
code复制定时周期 = (Prescaler+1)*(Period+1)/TIMx时钟频率 = 72*5000/72MHz = 0.005秒(5ms) -
USART1异步模式配置:
- 波特率:115200
- 字长:8位
- 停止位:1位
- 无校验位
- 使能接收和发送
3. 代码实现与功能逻辑
3.1 定时器中断服务函数
在stm32f1xx_it.c中自动生成的中断服务函数会调用HAL库的通用回调函数,我们需要在用户代码区重写回调函数:
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint16_t timer_count = 0;
if(htim->Instance == TIM2) // 判断触发源
{
timer_count++;
// 每200次中断(1秒)执行一次
if(timer_count >= 200)
{
timer_count = 0;
uint8_t msg[] = "Timer2 timeout!\r\n";
HAL_UART_Transmit(&huart1, msg, sizeof(msg)-1, HAL_MAX_DELAY);
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); // 翻转LED状态
}
}
}
3.2 主程序初始化流程
c复制int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
MX_USART1_UART_Init();
// 启动定时器中断
HAL_TIM_Base_Start_IT(&htim2);
// 发送启动信息
uint8_t welcome[] = "System initialized\r\n";
HAL_UART_Transmit(&huart1, welcome, sizeof(welcome)-1, 100);
while (1)
{
// 主循环可添加其他任务
__WFI(); // 进入低功耗模式,等待中断唤醒
}
}
4. 调试技巧与常见问题
4.1 波特率不匹配问题
症状:接收端显示乱码
解决方法:
- 确认双方波特率设置一致(115200、8N1)
- 检查系统时钟配置是否正确
- 用示波器测量实际波特率
4.2 定时器中断不触发
排查步骤:
- 检查TIM2时钟是否使能
c复制
__HAL_RCC_TIM2_CLK_ENABLE(); - 确认NVIC中断已配置
- 检查自动重载值是否合理(避免为0)
4.3 数据丢失问题
优化方案:
- 使用DMA传输替代中断方式
c复制
HAL_UART_Transmit_DMA(&huart1, data, length); - 增加发送完成标志检查
c复制while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);
5. 进阶应用:可变周期通信
通过修改自动重载寄存器(ARR)实现动态调整通信间隔:
c复制void Adjust_TIM2_Period(uint16_t new_period)
{
TIM_HandleTypeDef *htim = &htim2;
// 停止定时器
HAL_TIM_Base_Stop_IT(htim);
// 修改ARR值
htim->Instance->ARR = new_period - 1;
// 重新启动
HAL_TIM_Base_Start_IT(htim);
}
调用示例:
c复制Adjust_TIM2_Period(9999); // 改为10ms间隔
6. 性能优化建议
-
中断优化:
- 保持中断服务函数简洁
- 避免在中断中进行复杂计算
- 使用标志位+主循环处理模式
-
电源管理:
c复制// 在while(1)中使用低功耗模式 HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI); -
错误处理:
c复制void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { // 重新初始化串口 MX_USART1_UART_Init(); } }
在实际项目中,我曾遇到定时器中断偶尔丢失的问题,后来发现是中断服务函数执行时间过长导致。解决方法是将数据处理移到主循环,中断只设置标志位。这个经验告诉我,嵌入式开发中要时刻考虑实时性要求
