一、引言
在嵌入式系统开发中,STM32 微控制器以其高性能、低功耗和丰富的外设资源受到广泛应用。而 FreeRTOS 作为一个开源的实时操作系统,为开发者提供了任务管理、内存管理、中断管理等强大的功能,使得复杂的嵌入式系统开发变得更加高效和可靠。软件定时器是 FreeRTOS 提供的一个重要特性,它允许开发者在不使用硬件定时器的情况下实现定时功能,这在资源有限的系统中尤为有用。本文将详细介绍基于 STM32 HAL 库和 FreeRTOS 的软件定时器的使用方法。
二、STM32 HAL 库与 FreeRTOS 简介
2.1 STM32 HAL 库
STM32 HAL(Hardware Abstraction Layer)库是 ST 公司为 STM32 微控制器提供的一套硬件抽象层库。它的主要目的是简化开发者对 STM32 硬件的操作,通过提供统一的 API 接口,使得开发者可以在不同型号的 STM32 微控制器上快速移植代码。HAL 库封装了底层的寄存器操作,提供了高级的函数调用,如 GPIO 操作、定时器配置、串口通信等,大大提高了开发效率。
2.2 FreeRTOS
FreeRTOS 是一个开源的、轻量级的实时操作系统内核,适用于各种嵌入式系统。它提供了任务管理、队列、信号量、互斥量等内核服务,支持多任务并发执行。FreeRTOS 的特点包括可裁剪性强、占用资源少、实时性高、易于移植等。在嵌入式系统中,使用 FreeRTOS 可以更好地管理系统资源,提高系统的稳定性和可靠性。
三、FreeRTOS 软件定时器概述
3.1 软件定时器的概念
软件定时器是 FreeRTOS 提供的一种机制,它允许开发者在不使用硬件定时器的情况下实现定时功能。软件定时器基于 FreeRTOS 的系统时钟节拍(tick)运行,通过设置定时器的周期和回调函数,当定时器到期时,会自动调用回调函数执行相应的操作。软件定时器可以是一次性的(只执行一次回调函数),也可以是周期性的(每隔一定时间执行一次回调函数)。
3.2 软件定时器的优点
- 节省硬件资源:在一些资源有限的系统中,硬件定时器的数量可能不足,使用软件定时器可以在不占用硬件定时器的情况下实现定时功能。
- 灵活性高:软件定时器的周期和回调函数可以在运行时动态调整,开发者可以根据实际需求灵活配置定时器。
- 易于实现:软件定时器的实现相对简单,开发者只需要创建定时器、设置周期和回调函数,然后启动定时器即可。
3.3 软件定时器的局限性
- 精度相对较低:由于软件定时器基于 FreeRTOS 的系统时钟节拍运行,其定时精度受到系统时钟节拍的影响。如果系统时钟节拍设置得较大,定时器的精度会相应降低。
- 受任务调度影响:软件定时器的回调函数是在任务上下文中执行的,因此其执行时间会受到任务调度的影响。如果系统中存在高优先级任务长时间占用 CPU,定时器的回调函数可能会延迟执行。
四、开发环境搭建
4.1 硬件平台
本文以 STM32F4 Discovery 开发板为例进行介绍,该开发板采用了 STM32F407VG 微控制器,具有丰富的外设资源,适合进行嵌入式系统开发。
4.2 软件开发工具
- Keil MDK:Keil MDK 是一款专业的 ARM 微控制器开发工具,支持 STM32 系列微控制器的开发。它提供了集成开发环境(IDE)、编译器、调试器等功能,方便开发者进行代码编写、编译和调试。
- STM32CubeMX:STM32CubeMX 是 ST 公司提供的一款图形化配置工具,它可以帮助开发者快速配置 STM32 微控制器的外设和时钟,生成初始化代码。使用 STM32CubeMX 可以大大减少手动配置代码的工作量,提高开发效率。
4.3 安装 FreeRTOS
在 STM32CubeMX 中配置工程时,可以选择添加 FreeRTOS 操作系统。具体步骤如下:
- 打开 STM32CubeMX,选择相应的芯片型号。
- 在 “Pinout & Configuration” 选项卡中,配置系统时钟和外设。
- 点击 “Middleware” 选项卡,选择 “FreeRTOS”,并根据需要配置 FreeRTOS 的参数,如任务数量、堆大小等。
- 点击 “Project Manager” 选项卡,配置工程的输出路径和工具链,然后生成代码。
五、FreeRTOS 软件定时器的使用步骤
5.1 创建软件定时器
在 FreeRTOS 中,使用 xTimerCreate
函数来创建软件定时器。该函数的原型如下:
TimerHandle_t xTimerCreate(const char * const pcTimerName,TickType_t xTimerPeriod,UBaseType_t uxAutoReload,void * pvTimerID,TimerCallbackFunction_t pxCallbackFunction
);
pcTimerName
:定时器的名称,用于调试和日志记录,可为NULL
。xTimerPeriod
:定时器的周期,以系统时钟节拍为单位。uxAutoReload
:定时器的模式,pdTRUE
表示周期性定时器,pdFALSE
表示一次性定时器。pvTimerID
:定时器的标识符,可用于在回调函数中区分不同的定时器。pxCallbackFunction
:定时器到期时调用的回调函数。
示例代码如下:
#include "FreeRTOS.h"
#include "timers.h"// 定时器回调函数
void vTimerCallback(TimerHandle_t xTimer) {// 定时器到期时执行的操作
}// 创建定时器
TimerHandle_t xTimer = xTimerCreate("MyTimer",pdMS_TO_TICKS(1000), // 定时器周期为 1 秒pdTRUE, // 周期性定时器(void *)0, // 定时器标识符vTimerCallback // 定时器回调函数
);
5.2 启动软件定时器
创建定时器后,需要使用 xTimerStart
函数来启动定时器。该函数的原型如下:
BaseType_t xTimerStart(TimerHandle_t xTimer,TickType_t xTicksToWait
);
xTimer
:要启动的定时器句柄。xTicksToWait
:如果定时器服务任务处于阻塞状态,等待定时器服务任务解锁的最大时间,以系统时钟节拍为单位。如果设置为 0,表示不等待。
示例代码如下:
if (xTimer != NULL) {// 启动定时器if (xTimerStart(xTimer, 0) != pdPASS) {// 启动定时器失败}
}
5.3 停止软件定时器
如果需要停止定时器的运行,可以使用 xTimerStop
函数。该函数的原型如下:
BaseType_t xTimerStop(TimerHandle_t xTimer,TickType_t xTicksToWait
);
参数含义与 xTimerStart
函数相同。
示例代码如下:
if (xTimer != NULL) {// 停止定时器if (xTimerStop(xTimer, 0) != pdPASS) {// 停止定时器失败}
}
5.4 删除软件定时器
当不再需要某个定时器时,可以使用 xTimerDelete
函数来删除定时器。该函数的原型如下:
BaseType_t xTimerDelete(TimerHandle_t xTimer,TickType_t xTicksToWait
);
参数含义与 xTimerStart
函数相同。
示例代码如下:
if (xTimer != NULL) {// 删除定时器if (xTimerDelete(xTimer, 0) != pdPASS) {// 删除定时器失败}
}
5.5 改变软件定时器的周期
在定时器运行过程中,可以使用 xTimerChangePeriod
函数来改变定时器的周期。该函数的原型如下:
BaseType_t xTimerChangePeriod(TimerHandle_t xTimer,TickType_t xNewPeriod,TickType_t xTicksToWait
);
xNewPeriod
:新的定时器周期,以系统时钟节拍为单位。
示例代码如下:
if (xTimer != NULL) {// 改变定时器周期为 2 秒if (xTimerChangePeriod(xTimer, pdMS_TO_TICKS(2000), 0) != pdPASS) {// 改变定时器周期失败}
}
六、基于 STM32 HAL 库和 FreeRTOS 软件定时器的示例代码
6.1 代码功能概述
本示例代码将创建一个周期性的软件定时器,每隔 1 秒翻转一次 LED 灯的状态。
6.2 代码实现
#include "stm32f4xx_hal.h"
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"// 定义 LED 引脚
#define LED_PIN GPIO_PIN_12
#define LED_PORT GPIOD// 定时器回调函数
void vTimerCallback(TimerHandle_t xTimer) {HAL_GPIO_TogglePin(LED_PORT, LED_PIN);
}// 系统时钟配置函数
void SystemClock_Config(void);
// GPIO 初始化函数
static void MX_GPIO_Init(void);
// FreeRTOS 初始化函数
static void MX_FREERTOS_Init(void);int main(void) {HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_FREERTOS_Init();// 启动调度器vTaskStartScheduler();// 如果调度器启动失败,程序将执行到这里while (1) {}
}void SystemClock_Config(void) {RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** 初始化 CPU, AHB 和 APB 总线时钟*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {Error_Handler();}/** 初始化 CPU, AHB 和 APB 总线时钟*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) {Error_Handler();}
}static void MX_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStruct = {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOD_CLK_ENABLE();/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET);/*Configure GPIO pin : LED_PIN */GPIO_InitStruct.Pin = LED_PIN;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(LED_PORT, &GPIO_InitStruct);
}static void MX_FREERTOS_Init(void) {// 创建一个周期性定时器,周期为 1000 个 tickTimerHandle_t xTimer = xTimerCreate("MyTimer",pdMS_TO_TICKS(1000), // 定时器周期为 1 秒pdTRUE, // 周期性定时器(void *)0, // 定时器标识符vTimerCallback // 定时器回调函数);if (xTimer != NULL) {// 启动定时器if (xTimerStart(xTimer, 0) != pdPASS) {// 启动定时器失败}}
}void Error_Handler(void) {// 错误处理函数while(1) {}
}
6.3 代码解释
- 定时器回调函数:
vTimerCallback
函数在定时器到期时被调用,在该函数中,使用HAL_GPIO_TogglePin
函数翻转 LED 灯的状态。 - 系统时钟配置:
SystemClock_Config
函数用于配置系统时钟,这里使用内部高速时钟(HSI)作为系统时钟源。 - GPIO 初始化:
MX_GPIO_Init
函数用于初始化 LED 引脚,将其配置为推挽输出模式。 - FreeRTOS 初始化:
MX_FREERTOS_Init
函数用于创建和启动软件定时器。
七、注意事项
7.1 定时器回调函数的执行时间
定时器回调函数是在任务上下文中执行的,因此其执行时间应尽量短,避免长时间占用 CPU,影响其他任务的执行。如果回调函数需要执行较长时间的操作,建议将操作封装成一个任务,在回调函数中发送消息或信号量来触发该任务的执行。
7.2 定时器服务任务的优先级
FreeRTOS 有一个专门的定时器服务任务,用于处理定时器的到期事件。定时器服务任务的优先级应根据实际需求进行设置,一般建议设置为较高的优先级,以确保定时器事件能够及时处理。
7.3 系统时钟节拍的设置
系统时钟节拍的设置会影响定时器的精度。如果需要较高的定时精度,应将系统时钟节拍设置得较小,但同时会增加 CPU 的开销。因此,需要根据实际需求进行权衡。
八、总结
本文详细介绍了基于 STM32 HAL 库和 FreeRTOS 的软件定时器的使用方法。通过使用 FreeRTOS 软件定时器,开发者可以在不使用硬件定时器的情况下实现定时功能,节省硬件资源,提高系统的灵活性。同时,本文还给出了一个基于 STM32F4 Discovery 开发板的示例代码,帮助开发者更好地理解和应用软件定时器。在实际开发中,开发者应根据具体需求合理使用软件定时器,并注意定时器回调函数的执行时间、定时器服务任务的优先级和系统时钟节拍的设置等问题。