接上一节,我们已经学习了FreeRTOS任务创建函数xTaskCreate()各个参数的含义。这一节通过创建两个LED点灯任务,进一步理解FreeRTOS任务的创建和运行过程。
一、确定LED的点亮电平
编写程序之前,首先需要确定自己开发板上的LED是高电平点亮还是低电平点亮。
我使用的开发板LED采用低电平点亮:
GPIO_ResetBits(GPIOE, GPIO_Pin_3);
GPIO输出低电平,LED点亮。
GPIO_SetBits(GPIOE, GPIO_Pin_3);
GPIO输出高电平,LED熄灭。
如果使用的开发板是高电平点亮,则需要将上述两个操作互换。
二、编写LED任务函数
这里分别控制单片机上的两个LED灯,并给两个LED设置不同的闪烁周期。
为了避免两个LED的控制代码耦合在一起,我分别创建两个独立任务。
任务1
void task1(void *pvParameters)
{
while(1)
{
vTaskDelay(pdMS_TO_TICKS(300));
GPIO_ResetBits(GPIOE, GPIO_Pin_3);
vTaskDelay(pdMS_TO_TICKS(300));
GPIO_SetBits(GPIOE, GPIO_Pin_3);
}
}
任务1控制PE3引脚连接的LED。
任务运行后,先延时300ms,然后输出低电平点亮LED;再次延时300ms,输出高电平熄灭LED。之后程序返回循环开头,不断重复执行。
任务2
void task2(void *pvParameters)
{
while(1)
{
vTaskDelay(pdMS_TO_TICKS(1000));
GPIO_ResetBits(GPIOE, GPIO_Pin_4);
vTaskDelay(pdMS_TO_TICKS(1000));
GPIO_SetBits(GPIOE, GPIO_Pin_4);
}
}
任务2控制PE4引脚连接的LED。
任务2的延时时间为1000ms,因此它的闪烁速度比任务1更慢。
三、vTaskDelay()函数
vTaskDelay()用于让当前任务进入阻塞状态。
例如:
vTaskDelay(pdMS_TO_TICKS(300));
表示当前任务阻塞300ms。
任务进入阻塞状态后不会继续占用CPU。在任务1延时期间,FreeRTOS可以调度任务2或者其他已经进入就绪状态的任务运行。
vTaskDelay()原本接收的参数单位是系统节拍Tick,而不是固定的毫秒数。
例如:
vTaskDelay(300);
表示延时300个系统节拍。只有在系统节拍频率为1000Hz时,300个Tick才等于300ms。
因此,程序中推荐使用:
pdMS_TO_TICKS(300)
将毫秒转换为系统节拍,这样即使以后修改FreeRTOS系统节拍频率,延时时间仍然可以保持正确。
四、创建两个LED任务
任务函数编写完成后,在main()函数中使用xTaskCreate()创建两个任务。
int main(void)
{
BaseType_t task1_result;
BaseType_t task2_result;
/* LED初始化 */
LED_Init();
/* 创建任务1 */
task1_result = xTaskCreate(
task1, //任务函数
"task1", //任务名称
128, //任务栈大小
NULL, //任务参数
1, //任务优先级
NULL //任务句柄
);
/* 创建任务2 */
task2_result = xTaskCreate(
task2,
"task2",
128,
NULL,
1,
NULL
);
/* 判断两个任务是否创建成功 */
if((task1_result == pdPASS) &&
(task2_result == pdPASS))
{
/* 启动任务调度器 */
vTaskStartScheduler();
}
while(1)
{
}
}
这里使用两个BaseType_t类型的变量,分别保存两个任务的创建结果。
task1_result = xTaskCreate(…);
task2_result = xTaskCreate(…);
如果任务创建成功,xTaskCreate()会返回pdPASS。
如果创建失败,通常说明FreeRTOS堆空间不足,无法为任务分配任务控制块和任务栈空间。
两个任务创建完成后,通过下面的代码判断两个任务是否都创建成功:
if((task1_result == pdPASS) &&
(task2_result == pdPASS))
只有两个任务都创建成功,才调用:
vTaskStartScheduler();
启动FreeRTOS任务调度器。
五、任务开始运行
执行vTaskStartScheduler()后,FreeRTOS开始进行任务调度。
任务1和任务2的优先级都设置为1:
uxPriority = 1;
两个任务的优先级相同,但是它们在执行vTaskDelay()后都会进入阻塞状态。
例如,任务1进入延时后,CPU不会停在任务1中等待,而是可以继续执行任务2。任务2进入延时后,CPU又可以运行其他处于就绪状态的任务。
因此,两个LED任务虽然写在不同的函数中,却可以同时按照各自的延时时间运行。
程序下载到单片机后,可以看到:
PE3连接的LED以300ms为间隔改变亮灭状态;
PE4连接的LED以1000ms为间隔改变亮灭状态。
两个LED由两个独立的任务控制,彼此之间不会直接影响。
六、总结
这一节创建了两个简单的LED点灯任务。
整个程序的基本流程为:
编写任务函数;
使用xTaskCreate()创建任务;
判断任务是否创建成功;
调用vTaskStartScheduler()启动任务调度器;
FreeRTOS根据任务状态调度两个LED任务运行。
通过这个实验可以直观地看到,FreeRTOS并不是让一个任务执行结束后再执行另一个任务,而是在任务阻塞、就绪和运行等状态之间进行切换,从而实现多个任务的并发运行。





