本示例改写用事件标志组来进行任务同步,用任务通知来代替事件标志组,其余代码与原示例代码基本相同。
1.任务函数
#include "stm32f10x.h" // Device header
#include "appTask.h"
#include "Delay.h"
#include "Serial.h"
static char pcToPrint[80]; //待打印内容缓冲区
xQueueHandle xQueuePrint; //消息队列句柄
static TaskHandle_t LED0TaskHandle = NULL;//任务LED0Task任务句柄
TaskHandle_t LED1TaskHandle = NULL;//任务LED1task任务句柄
static TaskHandle_t printTaskHandle = NULL;//任务printTask任务句柄
static TaskHandle_t KeyTaskHandle = NULL;//任务按键句柄
/**********************************************************************
函 数 名:LED0Task
功 能:LED0闪烁,指示程序正在运行
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 3
**********************************************************************/
static void LED0Task(void *pvParameters)
{
while(1)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_4))); //LED0闪烁
vTaskDelay(pdMS_TO_TICKS(500));//每秒闪烁1次
}
}
/**********************************************************************
函 数 名:LED1Task
功 能:检测事件标志位,bit2,bit1和bit0同时置位时点亮LED1,并输出串口信息
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 3
**********************************************************************/
static void LED1Task(void *pvParameters)
{
uint32_t uNotifyValue; //用于保存任务通知值
uint8_t evnFlag1,evnFlag2,evnFlag3; //3个事件到来的标志
evnFlag1=evnFlag2=evnFlag3= 0;
while(1)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_5,Bit_SET); //LED1熄灭
/*检测事件标志组bit2,bit1和bit0,均置位点亮LED1并发送相应的信息到串口,3个事件标志位同时置位函数才返回,不满足则无限制阻塞任务,
退出函数时清除3个事件标志位*/
xTaskNotifyWait(0x00,0xffffffff,&uNotifyValue,portMAX_DELAY);
if(uNotifyValue & 0x01) evnFlag1=1;
if(uNotifyValue & 0x02) evnFlag2=1;
if(uNotifyValue & 0x04) evnFlag3=1;
if(evnFlag1 && evnFlag2 && evnFlag3)
{
evnFlag1=evnFlag2=evnFlag3=0;
//生成待打印出信息
sprintf(pcToPrint,"按键0事件,按键1事件,TIM2中断事件均发生 \r\n\r\n");
//打印信息,所有发送串口的信息不能直接输出,通过队列发送给串口守护任务
xQueueSendToBack(xQueuePrint,pcToPrint,0);
}
GPIO_WriteBit(GPIOA,GPIO_Pin_5,Bit_RESET); //LED1点亮
vTaskDelay(pdMS_TO_TICKS(1000));//点亮1s
}
}
/**********************************************************************
函 数 名:printTask
功能说明:串口守护任务使用了一个FreeRTOS队列来对串口实现串行化访问,该守护任务是唯一能够直接访问串口的任务。
串口守护任务大部分时间都在阻塞态等待队列中有消息到来,当一个消息到达时,
串口守护任务仅简单地将接收到的消息发送到串口上,然后又返回阻塞态,继续等待下一条消息的到来。
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 3
**********************************************************************/
void printTask(void *pvParameters)
{
char pcTowrite[80]; //缓存从队列接受到的数据
while(1)
{
/*当队列为空,即没有字符需要输出时间,阻塞超时时间为portMAX_DELAY,任务将进入无期限等待
状态,可以不检测队列读取函数的返回值*/
xQueueReceive(xQueuePrint,pcTowrite,portMAX_DELAY);
printf("%s",pcTowrite);
}
}
/**********************************************************************
函 数 名:KeyTask
功能说明:高优先级任务,任务运行需要获取信号量,并将信息送往串口
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 4
**********************************************************************/
static void KeyTask(void *pvParameters)
{
uint8_t keyValue; //键值
while(1)
{
keyValue = Key_getNum(); //获取键值
if(keyValue == 1)
{
sprintf(pcToPrint,"按键0按下,置位bit0...... \r\n\r\n");
//打印信息,所有发送串口的信息不能直接输出,通过队列发送给串口守护任务
xQueueSendToBack(xQueuePrint,pcToPrint,0);
//设置事件标志组bit0位为1
xTaskNotify(LED1TaskHandle,0x01,eSetBits);
}
else if(keyValue == 2)
{
sprintf(pcToPrint,"按键1按下,置位bit1...... \r\n\r\n");
//打印信息,所有发送串口的信息不能直接输出,通过队列发送给串口守护任务
xQueueSendToBack(xQueuePrint,pcToPrint,0);
//设置事件标志组bit1位为1
xTaskNotify(LED1TaskHandle,0x02,eSetBits);
}
else if(keyValue == 3)
{
//Key2按下,在TIM2中断中,设置事件标志组bit2为1
TIM_Cmd(TIM2,ENABLE);//开启定时器
sprintf(pcToPrint,"按键2按下,启动定时器2 ...... \r\n\r\n");
//打印信息,所有发送串口的信息不能直接输出,通过队列发送给串口守护任务
xQueueSendToBack(xQueuePrint,pcToPrint,0);
}
vTaskDelay(pdMS_TO_TICKS(100)); //阻塞100ms
}
}
2.TIM函数
#include "stm32f10x.h" // Device header
#include "Timer.h"
#include "appTask.h"
#include "Serial.h"
BaseType_t xHighPriorityTaskWoken = pdFALSE ; //退出中断后进行任务切换
extern TaskHandle_t LED1TaskHandle ;//任务LED1task任务句柄
void Timer_Init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);//开启内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=9999 ;//ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=7200 -1 ;//PSC
//100ms
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//中断使能 更新中断
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除更新中断请求位
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
//TIM_Cmd(TIM2,ENABLE);//开启定时器
}
void TIM2_IRQHandler(void)
{
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_Update)==SET)
{
//中断发生后,事件标志组bit2置1
xTaskNotifyFromISR(LED1TaskHandle,0x04,eSetBits,&xHighPriorityTaskWoken);
//如果有更高优先级的任务,则退出中断后执行任务切换
portYIELD_FROM_ISR(xHighPriorityTaskWoken);
//关闭定时器2
TIM_Cmd(TIM2,DISABLE);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
}
}
3.任务创建
/**********************************************************************
函 数 名:appStartTask
功能说明:任务开始函数,用于创建其他函数并且开启调度器
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
**********************************************************************/
void appStartTask(void)
{
//创建一个长度为2,队列项大小可以容纳待输出字符的队列
xQueuePrint = xQueueCreate(2,sizeof(pcToPrint));
if(xQueuePrint)
{
taskENTER_CRITICAL(); /*进入临界段,关中断*/
xTaskCreate(LED0Task,"LED0Task",128,NULL,3,&LED0TaskHandle);
xTaskCreate(LED1Task,"LED1Task",128,NULL,3,&LED1TaskHandle);
xTaskCreate(KeyTask,"KeyTask",128,NULL,3,&KeyTaskHandle);
xTaskCreate(printTask,"printTask",128,NULL,4,&printTaskHandle);
taskEXIT_CRITICAL(); /*退出临界段,关中断*/
vTaskStartScheduler();/*开启调度器*/
}
}
4.下载测试
测试结果与之前的示例一样。
5.总结
使用任务通知可以提高运行速度和减少RAM的消耗,可用于轻量级的使用场所下替换二值信号量,计数信号量,事件标志组等。