FREERTOS学习五--信号量Semaphore

文章介绍了信号量在任务间同步和资源管理中的作用,包括二值信号量和计数信号量的概念。二值信号量用于简单的同步,如婴儿饿了的信号;计数信号量用于管理有限资源,如停车位。文中还详细阐述了创建、等待、释放和删除信号量的相关函数,并提供了二值信号量和计数信号量的使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

  队列主要用在任务之间传递数据,当然也可以是任务与中断之间传递数据。

如果只是为了传递一种状态,比如婴儿饿了,他就开始哭,这时候他妈妈就要过来喂奶了。这个哭声就是一个信号,他妈妈只要收到信号了就喂奶。这个信号量只表示饿与不饿,叫二值信号量。二值信号量主要用于任务之间的同步,或者中断与任务之间的同步。

如果是为了统筹共享的资源,比如停车场有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);
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值