前言
队列主要用在任务之间传递数据,当然也可以是任务与中断之间传递数据。
如果只是为了传递一种状态,比如婴儿饿了,他就开始哭,这时候他妈妈就要过来喂奶了。这个哭声就是一个信号,他妈妈只要收到信号了就喂奶。这个信号量只表示饿与不饿,叫二值信号量。二值信号量主要用于任务之间的同步,或者中断与任务之间的同步。
如果是为了统筹共享的资源,比如停车场有100个停车位,停一辆车就少一个空位,走一辆车就多一个空位,这也是一个信号量,叫计数信号量。
一、信号量是什么?
信号量,理解字面意思就可以了,就是描述信号的量。其实信号量也是一个消息队列,二值信号量就相当于一个深度为1但是没有数据的消息队列
二、与信号量相关的函数
//1、创建信号量函数,二值信号量与计数信号量都是这个函数来创建
osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count);
//2、等待并获取信号量
int32_t osSemaphoreWait (osSemaphoreId semaphore_id, uint32_t millisec);
//3、释放信号量
osStatus osSemaphoreRelease (osSemaphoreId semaphore_id);
//4、删除信号量
osStatus osSemaphoreDelete (osSemaphoreId semaphore_id);
//5、获得信号量的值
uint32_t osSemaphoreGetCount(osSemaphoreId semaphore_id)
函数讲解与分析
1、创建信号量函数
/**
* @brief 创建并初始化一个信号量
* @param 需要创建的信号量
* @param 计数的值
* @retval 返回信号量ID
*/
osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count)
{
//如果同时支持动态分配内存和静态分配内存
#if( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
osSemaphoreId sema;
//判断信号量的控制块,如果不是空的,表示要使用静态分配内存,就用静态创建函数
if (semaphore_def->controlblock != NULL){
if (count == 1) { //如果计数值是1,表示创建二值信号量
return xSemaphoreCreateBinaryStatic( semaphore_def->controlblock );
}
else {
//如果计数值不是1,且配置了使用计数信号量,就创建计数信号量
#if (configUSE_COUNTING_SEMAPHORES == 1 )
return xSemaphoreCreateCountingStatic( count, count, semaphore_def->controlblock );
#else
return NULL;
#endif
}
}
else {//如果信号量的控制块是空的,就用动态方式创建函数
if (count == 1) { //计数值为1,那就创建二值信号量
vSemaphoreCreateBinary(sema);
return sema;
}
else {//计数值不是1,就创建计数信号量
//创建计数信号量的前提是在配置里面使能了计数信号量
#if (configUSE_COUNTING_SEMAPHORES == 1 )
return xSemaphoreCreateCounting(count, count);
#else
return NULL;
#endif
}
}
//如果仅支持静态创建函数
#elif ( configSUPPORT_STATIC_ALLOCATION == 1 )
if(count == 1) {//计数值==1,就创建二值信号量
return xSemaphoreCreateBinaryStatic( semaphore_def->controlblock );
}
else
{
//计数值不等于1,就创建计数信号量
#if (configUSE_COUNTING_SEMAPHORES == 1 )
return xSemaphoreCreateCountingStatic( count, count, semaphore_def->controlblock );
#else
return NULL;
#endif
}
#else // 如果只支持动态创建函数
osSemaphoreId sema;
if (count == 1) {//计数值为1,创建二值信号量
vSemaphoreCreateBinary(sema);
return sema;
}
else {//计数值不为1,创建计数信号量
#if (configUSE_COUNTING_SEMAPHORES == 1 )
return xSemaphoreCreateCounting(count, count);
#else
return NULL;
#endif
}
#endif
}
2、等待并获取信号量
/**
* @brief 获取信号量
* @param 信号量ID
* @param 超时时间
* @retval 取到了就返回osOK,没取到就报错
*/
int32_t osSemaphoreWait (osSemaphoreId semaphore_id, uint32_t millisec)
{
TickType_t ticks;
portBASE_TYPE taskWoken = pdFALSE;
//没有信号量ID,直接报错
if (semaphore_id == NULL) {
return osErrorParameter;
}
ticks = 0;
if (millisec == osWaitForever) { //如果是一直等,tick值设置为最大
ticks = portMAX_DELAY;
}
else if (millisec != 0) {//如果是等某个时间,把ms时间转换成tick
ticks = millisec / portTICK_PERIOD_MS;
if (ticks == 0) {
ticks = 1;
}
}
if (inHandlerMode()) { //如果是中断模式,就从中断中取信号量
if (xSemaphoreTakeFromISR(semaphore_id, &taskWoken) != pdTRUE) {
return osErrorOS;
}
portEND_SWITCHING_ISR(taskWoken);
}
//如果不是中断模式,也就是任务模式
else if (xSemaphoreTake(semaphore_id, ticks) != pdTRUE) {
return osErrorOS;
}
return osOK;
}
3、释放信号量
/**
* @brief 释放一个信号量
* @param 信号量ID
* @retval 返回状态
*/
osStatus osSemaphoreRelease (osSemaphoreId semaphore_id)
{
osStatus result = osOK;
portBASE_TYPE taskWoken = pdFALSE;
//中断模式释放信号量
if (inHandlerMode()) {
if (xSemaphoreGiveFromISR(semaphore_id, &taskWoken) != pdTRUE) {
return osErrorOS;
}
portEND_SWITCHING_ISR(taskWoken);
}
else {//任务模式释放信号量
if (xSemaphoreGive(semaphore_id) != pdTRUE) {
result = osErrorOS;
}
}
return result;
}
4、删除信号量
/**
* @brief 删除一个信号量
* @param 被删除的信号量ID
* @retval status code that indicates the execution status of the function.
*/
osStatus osSemaphoreDelete (osSemaphoreId semaphore_id)
{
//中断模式不能删除信号量
if (inHandlerMode()) {
return osErrorISR;
}
vSemaphoreDelete(semaphore_id);
return osOK;
}
5、获取信号量的计数值
/**
* @brief Returns the current count value of a counting semaphore
* @param semaphore_id semaphore_id ID obtained by \ref osSemaphoreCreate.
* @retval count value
*/
uint32_t osSemaphoreGetCount(osSemaphoreId semaphore_id)
{
return uxSemaphoreGetCount(semaphore_id);
}
三、信号量的使用
二值信号量的使用
二值信号量主要用于任务之间的通知,比如在任务StartTask02中按下一个按键,就释放一个信号量。
void StartTask02(void const * argument)
{
for(;;)
{
if(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(20);
while(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET);
if(osSemaphoreRelease(myBinarySem01Handle)!= osOK)
{
} else{
osThreadSuspendAll();
printf("send BinarySem sucess! \r\n");
osThreadResumeAll();
}
}
osDelay(1);
}
}
建一个任务来取二值信号量,取到信号量就改变一下LED的状态
void StartTask03(void const * argument)
{
for(;;)
{
if(osSemaphoreWait(myBinarySem01Handle,osWaitForever)!=osOK)
{
printf("get BinarySem fialed! \r\n");
}else{
printf("get BinarySem success! \r\n");
HAL_GPIO_TogglePin(LED_B_GPIO_Port,LED_B_Pin);
}
osDelay(1);
}
}
二值信号量的使用需要注意一点,在创建好二值信号量之后,里面就有信号了,所以上面的实例,还没按下按键,只要单片机复位一下LED灯的状态就改变了,也会打印信息说get到二值信号量,实际工程应用的时候需要注意。
计数信号量的使用
计数信号量主要用来统筹资源,或者统计资源的,比如统计停车场的停车位,建立一个计数信号了,计数值为10,相当于停车场有10个停车位,进来一辆车,停车位就少一个,出去一辆车停车位就增加一个。因此可以建立两个任务,一个任务用来模拟进停车场,一个任务用来模拟出停车场。
先建立一个计数信号量,建立一个计数值为10的计数信号量,相当于一个停车场有10个空位
osSemaphoreDef(myCountingSem01);
myCountingSem01Handle = osSemaphoreCreate(osSemaphore(myCountingSem01), 10);
再建立一个任务,用按键模拟出停车场,按一下按键出一辆车,同时读出计数值的大小
void StartTask02(void const * argument)
{
uint16_t m;
for(;;)
{
if(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(20);
while(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET);
if(osSemaphoreRelease(myCountingSem01Handle)!= osOK)
{
} else{
osThreadSuspendAll();
m = osSemaphoreGetCount(myCountingSem01Handle);
printf("m = %d \r\n",m);
printf("send BinarySem sucess! \r\n");
osThreadResumeAll();
}
}
osDelay(1);
}
}
再建一个任务,用按键模拟进停车场,按一下按键进一辆车,计数值就自动-1
void StartTask03(void const * argument)
{
uint16_t n;
for(;;)
{
if(HAL_GPIO_ReadPin(KEY_3_GPIO_Port,KEY_3_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(20);
while(HAL_GPIO_ReadPin(KEY_3_GPIO_Port,KEY_3_Pin) == GPIO_PIN_RESET);
if(osSemaphoreWait(myCountingSem01Handle,osWaitForever)!=osOK)
{
printf("get BinarySem fialed! \r\n");
}else{
printf("get BinarySem success! \r\n");
n = osSemaphoreGetCount(myCountingSem01Handle);
printf("n = %d \r\n",n);
HAL_GPIO_TogglePin(LED_B_GPIO_Port,LED_B_Pin);
}
}
osDelay(1);
}
}