信号量可以用来进行资源管理和任务同步,FreeRTOS中信号量又分为二值信号量、计算型信号量、互斥信号量和递归互斥信号量。
0x01 二值信号量
二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的,任务和中断不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的,可以利用这个机制完成任务与中断之间的同步。
1. 创建二值信号量
vSemaphoreCreateBinary
vSemaphoreCreateBinary( SemaphoreHandle_t xSemaphore )
- xSemaphore :保存创建成功的二值信号量
此函数是老版本FreeRTOS创建二值信号量函数,新版本已经不再使用。
xSemaphoreCreateBinary:
SemaphoreHandle_t xSemaphoreCreateBinary( void )
此函数是新版本FreeRTOS中统一使用此函数来创建二值信号量,使用此函数创建二值信号量的话,信号量所需要的内存是有FreeRTOS的内存管理部分动态分配的,此函数创建好的二值信号量默认是空的,也就是说创建好的二值信号量使用函数xSemaphoreTask是获取不到的。
xSemaphoreCreateBinaryStatic
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )
- pxSemaphoreBuffer :保存信号量结构体
此函数创建二值信号量所需要的内存需要由用户分配
2. 释放信号量
xSemaphoreGive
xSemaphoreGive( SemaphoreHandle_t xSemaphore )
- xSemaphore:要释放的信号量句柄
此函数用于释放二值信号量、计数型信号量或互斥信号量
xSemaphoreGiveFromISR
xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
)
- xSemaphore:要释放的信号量句柄
- BaseType_t *pxHigherPriorityTaskWoken:标记退出此函数以后是否进行任务切换,此值为pdTRUE的时候在退出中断之前一定要进行一次任务切换
此函数只能用来释放二值信号量和计数型信号量
3. 获取信号量
xSemaphoreTake
xSemaphoreTake(
* SemaphoreHandle_t xSemaphore,
* TickType_t xBlockTime
* )
- SemaphoreHandle_t xSemaphore:要获取信号量的句柄
- TickType_t xBlockTime:阻塞时间
此函数用于获取二值信号量、计数型信号量或互斥信号量
xSemaphoreTakeFromISR
xSemaphoreTakeFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
)
- SemaphoreHandle_t xSemaphore:要获取信号量的句柄
- BaseType_t *pxHigherPriorityTaskWoken:标记退出此函数以后是否进行任务切换,等于pdTURE一定进行任务切换
4. 验证
设计一个通过串口发送指定的指令来控制开发版上的LED1和BEEP开关的实验。指令如下:
LED1ON:打开LED1
LED1OFF:关闭LED1
BEEFON:打开BEEF
BEEFOFF:关闭BEEF
设计三个任务,功能如下:
start_task:用来创建其他2个任务
task1_task:控制LED0闪烁,提示系统正在运行
DataProcess_task:指令处理任务,根据接收到的指令来控制不同的外设
实验中还创建一个二值信号量BinarySemaphore用于完成串口中断和任务DataProcess_task之间的同步。
DataProcess_task函数
void DataProcess_task(void *pvParameters)
{
u8 len = 0;
u8 CommandValue = COMMANDERR;
BaseType_t err = pdFALSE;
u8 *CommandStr;
POINT_COLOR = BLUE;
while(1)
{
if(BinarySemaphore!=NULL)
{
err=xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //获取信号量
if(err==pdTRUE) //获取信号量成功
{
len=USART_RX_STA&0x3fff; //得到此次接收到的数据长度
CommandStr=mymalloc(SRAMIN,len+1); //申请内存
sprintf((char*)CommandStr,"%s",USART_RX_BUF);
CommandStr[len]='\0'; //加上字符串结尾符号
LowerToCap(CommandStr,len); //将字符串转换为大写
CommandValue=CommandProcess(CommandStr); //命令解析
if(CommandValue!=COMMANDERR)
{
LCD_Fill(10,90,210,110,WHITE); //清除显示区域
LCD_ShowString(10,90,200,16,16,CommandStr); //在LCD上显示命令
printf("命令为:%s\r\n",CommandStr);
switch(CommandValue) //处理命令
{
case LED1ON:
LED1=0;
break;
case LED1OFF:
LED1=1;
break;
case BEEPON:
BEEP=0;
break;
case BEEPOFF:
BEEP=1;
break;
}
}
else
{
printf("无效的命令,请重新输入!!\r\n");
}
USART_RX_STA=0;
memset(USART_RX_BUF,0,USART_REC_LEN); //串口接收缓冲区清零
myfree(SRAMIN,CommandStr); //释放内存
}
}
else if(err==pdFALSE)
{
vTaskDelay(10); //延时10ms,也就是10个时钟节拍
}
}
}
DataProcess_task是用来申请获取信号量,如果申请到了,就往下执行,如果没事申请到,就会一直阻塞,申请到之后,对数据做处理,然后做出相应的回应。
串口1中断服务程序
//串口1中断服务程序
void USART1_IRQHandler(void)
{
u32 timeout=0;
u32 maxDelay=0x1FFFF;
BaseType_t xHigherPriorityTaskWoken;
HAL_UART_IRQHandler(&UART1_Handler); //调用HAL库中断处理公用函数
timeout=0;
while (HAL_UART_GetState(&UART1_Handler) != HAL_UART_STATE_READY)//等待就绪
{
timeout++;超时处理
if(timeout>maxDelay) break;
}
timeout=0;
while(HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE) != HAL_OK)//一次处理完成之后,重新开启中断并设置RxXferCount为1
{
timeout++; //超时处理
if(timeout>maxDelay) break;
}
//释放二值信号量
if((USART_RX_STA&0x8000)&&(BinarySemaphore!=NULL))//接收到数据,并且二值信号量有效
{
xSemaphoreGiveFromISR(BinarySemaphore,&xHigherPriorityTaskWoken); //释放二值信号量
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
}
}
串口1中断服务程序是用来接收串口发送的数据,如果接收完毕,将会释放二值信号量,DataProcess_task才能往下执行,如果没有接收完数据或者没有接收到数据,DataProcess_task会一直阻塞。