信号量可以用来进行资源管理和任务同步,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会一直阻塞。