在嵌入式开发的世界里,精准的时间控制是实现复杂功能的关键。传统的延时函数会让 CPU 陷入 “死循环”,无法高效处理其他任务。而 STM32 的定时器就像给 CPU 配备了智能闹钟,既能实现精确计时,又能让 CPU 并行处理多个任务。今天,我们就通过一个有趣的实验,用定时器实现每隔 5 秒串口发送信息,同时让 LED 每隔 2 秒闪烁,体验多任务并发的魅力!

一、实验前的准备:工欲善其事,必先利其器

1. 硬件装备

  • STM32 开发板:选用经典的 STM32F103C8T6 开发板,它丰富的定时器资源和 GPIO 引脚,是本次实验的绝佳选择。
  • USB 转串口模块:采用 CH340 芯片的模块,负责搭建开发板与电脑之间的通信桥梁,方便我们通过串口助手查看发送的数据。
  • 杜邦线:若干,用于连接开发板、USB 转串口模块以及 LED 对应的 GPIO 引脚(这里假设 LED 连接到 PA5 引脚)。
  • 电脑:安装 Windows 系统,准备好开发所需的软件。

2. 软件工具

  • STM32CubeMX:图形化配置神器,帮我们轻松搞定 STM32 外设初始化,本次使用 [具体版本]。
  • Keil MDK:专业的集成开发环境,用于编写、编译和调试代码,版本号为 [具体版本]。
  • 串口助手:选择 sscom 5.13.1 版本,实时接收 STM32 发送的数据。
  • CH340 驱动程序:确保 USB 转串口模块能在电脑上正常工作。

二、STM32CubeMX 配置:搭建实验的基石

1. 新建工程

打开 STM32CubeMX,点击 “File”→“New Project”,在芯片选择界面搜索 “STM32F103C8T6”,选中后点击 “Start Project”,一个崭新的工程就创建好啦!

2. 定时器配置:给 CPU 设定闹钟

  • TIM2(5 秒定时器)
    • 在 “Pinout & Configuration” 选项卡中,展开 “Timers” 找到 “TIM2”。
    • 将 “Counter Mode” 设为 “Up Counter”(向上计数模式)。
    • 在 “Parameter Settings” 里,把 “Prescaler (PSC)” 设为 7199,“Counter Period (ARR)” 设为 49999。这样计算下来,定时器计数到目标值刚好耗时 5 秒,就像设定好了一个 5 秒的闹钟。
    • 切换到 “NVIC Settings”,勾选 “TIM2 global interrupt”,开启定时器 2 的全局中断,让 CPU 能收到闹钟提醒。
  • TIM3(2 秒定时器):参照 TIM2 的配置方法,将 “Prescaler (PSC)” 设为 7199,“Counter Period (ARR)” 设为 19999,实现 2 秒定时,并在 “NVIC Settings” 中开启中断。

3. USART1 配置:建立通信通道

找到 “USART1”,配置为异步通信模式:波特率 115200bps,8 位数据位,1 位停止位,无校验位,不使用硬件流控制。这样,开发板就能和电脑顺畅 “对话” 了。

4. LED 引脚配置:点亮小灯

假设 LED 连接在 PA5 引脚,将其 “Mode” 设为 “推挽输出(Output Push Pull)”,“Speed” 设为 “中速(Medium Speed)”,为 LED 闪烁做好准备。

5. 时钟树配置:提供稳定动力

在 “Clock Configuration” 选项卡中,选择 8MHz 的外部高速时钟(HSE),通过 PLL 将时钟倍频到 72MHz,为整个系统提供稳定的 “心跳”。

6. 生成工程代码

在 “Project Manager” 中设置好工程信息,选择 “MDK-ARM” 作为工具链。在 “Code Generator” 里勾选生成初始化文件,点击 “Generate Code”,Keil MDK 工程代码就自动生成啦!

三、Keil MDK 代码编写:赋予系统灵魂

1. 打开工程

在 Keil MDK 中打开刚刚生成的工程文件(.uvprojx),准备编写代码。

2. 中断回调函数:响应闹钟提醒

在 “main.c” 文件中添加定时器中断回调函数:


// 定时器2中断回调函数(5秒定时)

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {

if (htim == &htim2) {

char tx_buffer[] = "hello windows!\r\n";

HAL_UART_Transmit(&huart1, (uint8_t*)tx_buffer, sizeof(tx_buffer), 1000);

}

}

// 定时器3中断回调函数(2秒定时)

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {

if (htim == &htim3) {

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 翻转LED引脚电平

}

}

这两个函数就像是闹钟响起后的 “处理员”,定时器 2 定时时间到,就通过串口发送 “hello windows!”;定时器 3 时间到,就翻转 LED 引脚电平,让 LED 闪烁。

3. 启动定时器:开启定时任务

在main函数中,初始化完外设后,启动定时器 2 和定时器 3:


int main(void) {

HAL_Init();

MX_GPIO_Init();

MX_USART1_UART_Init();

MX_TIM2_Init();

MX_TIM3_Init();

// 启动定时器2和定时器3

HAL_TIM_Base_Start_IT(&htim2);

HAL_TIM_Base_Start_IT(&htim3);

while (1) {

// CPU可在此期间处理其他任务,如传感器数据采集

}

}

4. 编译工程

点击 Keil MDK 工具栏的 “编译(Build)” 按钮,检查代码语法。如果编译通过,就会生成可烧录的.hex 文件。

四、实验验证:见证成果的时刻

1. 硬件连接

  • 将 USB 转串口模块的 TX 引脚连到开发板的 PA9(USART1_TX),RX 引脚连到 PA10(USART1_RX),GND 与开发板 GND 相连。
  • 确保 LED 对应的 PA5 引脚连接正确。

2. 烧录程序

用 ST-Link、J-Link 等工具,把生成的.hex 文件烧录到开发板中。

3. 运行与观察

打开串口助手,设置好波特率等参数,选择对应的串口号。给开发板上电,就能看到串口助手每隔 5 秒收到 “hello windows!”,开发板上的 LED 每隔 2 秒闪烁一次,多任务并发完美实现!

五、无定时器方案对比:凸显定时器优势

1. 轮询方式

修改main函数代码为轮询方式:


int main(void) {

HAL_Init();

MX_GPIO_Init();

MX_USART1_UART_Init();

uint32_t send_time = 0;

uint32_t led_time = 0;

while (1) {

uint32_t current_time = HAL_GetTick();

// 5秒串口发送任务

if (current_time - send_time >= 5000) {

char tx_buffer[] = "hello windows!\r\n";

HAL_UART_Transmit(&huart1, (uint8_t*)tx_buffer, sizeof(tx_buffer), 1000);

send_time = current_time;

}

// 2秒LED闪烁任务

if (current_time - led_time >= 2000) {

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);

led_time = current_time;

}

}

}

虽然功能实现了,但 CPU 一直在查询时间,资源被大量占用,时间精度也不好。

2. 状态机方式

通过状态变量和转移条件编写代码(代码略),比轮询灵活些,但代码复杂,时间精度同样不理想。

对比下来,定时器在精准计时和多任务并发上的优势一目了然!

结语

通过这次实验,我们掌握了 STM32 定时器实现多任务并发的方法,也看到了定时器相比传统方式的巨大优势。在实际项目中,合理运用定时器,能让我们的嵌入式系统更加高效、智能。如果你在实验过程中遇到问题,或者有新的想法,欢迎在评论区交流讨论,一起探索更多嵌入式开发的奇妙世界!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐