FreeRTOS_认识信号量

上次我们认识了队列,这次实验我们使用两个实验来分别认识二值信号量和计数信号量。本次实验采用STM32F103ZET6主芯片的开发板,使用HAL库开发。

信号量可以用于进程间的通信,它和互斥量都是基于队列的基本数据结构,信号量又分为二值信号量计数信号量

二值信号量

二值信号量就是只有一个项的队列,这个队列要么是空的,要么是满的,所以可以相当于0和1两种信号。二值信号量就像一个标志,适合用于进程间的同步通信。

注意:二值信号量通信时一方只能发送,另一方只能接收信息,不能修改。

二值信号量的使用示例

认识二值信号的各种函数

  1. 创建二值信号:osSemaphoreNew();这是一个宏函数,这个函数用于创建一个二值信号,我们可以不用具体认识这个函数,因为我们是在CobeMX中可视化地创建二值信号的,当我们创建好后,会自动调用这个函数。
  2. 释放二值信号量:xSemaphoreGive();在二值函数被创建后是无法直接使用的,需要先将这个二值信号量释放,相当于刚创建的二值信号量是0,释放就相当于使其有效,也就是将其置为1。这个函数也可以用于释放计数信号量和互斥量。这个函数有4个参数:第一个参数是填入想要释放的句柄;第二个是需要向队列写入的数据,在二值信号这里配置为NULL;第三个是等待的节拍数,在二值信号这里不用等待,所以是宏定义常量semGIVE_BLOCK_TIME,即为0;第四个是表示写入队列的方向,这里函数直接定义为宏常量:queueSEND_TO_BACK,。如果函数返回pdTRUE表示释放成功,反之返回pdFALSE。
  3. 释放二值信号量的ISR版本为:xSemaphoreGiveFromISR();这个函数一共有两个参数,一个参数:xQueue表示传入参量的句柄;另一个参数:pxHigherPriorityTaskWoken是返回数据的指针,如果释放信号导致一个高等级任务解锁,返回pdTRUE,反之返回pdFALSE。如果返回pdTRUE,我们应该重新进行任务调度,使高等级的任务优先执行,这就需要在退出ISR之前调用portYIELD_FROM_ISR()函数使任务重新调用。
  4. 获取二值信号:xSemaphoreTake();这个函数的两个参数分别是:xSemaphore:用于表示信号的句柄(这个函数还可以用于计数信号量和互斥量);xBlockTime:等待时间。

认识这些基本函数后,我们开始进行第一个关于二值信号量的实验。

实验一:认识二值信号量

实验过程:创建一个二值信号量,使用ADC触发一个500ms的数据采集,然后释放信号,用一个任务接收这个信号,然后在LCD上显示。

CobeMX设置

这里我们使用到LCD与上次列表的相同,可以直接查看:https://mp.csdn.net/mp_blog/creation/editor/130230340

这里再配置定时器,使用定时器3定时500ms作为ADC的外部触发信号,具体设置如图:

 

图 1 TIM3设置

然后使用ADC1的通道5作为输入通道(PA5),使用右对齐,外部触发源选择Timer 3 Trigger Out event。要在中断模式下进行ADC数据采集还需要打开ADC1中断.。具体配置如图:

 

图 2 ADC1设置

完成设置后再设置FreeRTOS,这里启动FreeRTOS,接口选择CMSIS_V2,其他参数都保持默认,在Tasks and Queues页面设置任务(将默认任务修改):

 

图 3 FreeRTOS任务设置

在Timer and Semaphores页面设置一个二值信号,如图所示:

 

图 4 二值信号量设置

这些设置完成后再开启ADC1的中断,将其抢占优先级配置为4(只要小于5都可以)。

代码实现

这里的LCD部分和之前设置一样,可以看列表那的操作。

在主函数中我们需要开启ADC1的中断和TIM3的时钟,这里附上主函数的代码:

int main(void)

{

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();

  MX_FSMC_Init();

  MX_ADC1_Init();

  MX_TIM3_Init();

  /* USER CODE BEGIN 2 */

       LCD_Init();

       LCD_ShowString(10, 0, 280, 16, 16, (uint8_t *)"this is the damo five!");

       HAL_ADC_Start_IT(&hadc1);  // 打开ADC中断

       HAL_TIM_Base_Start(&htim3);  // 打开定时器3

  /* USER CODE END 2 */

  osKernelInitialize();  /* Call init function for freertos objects (in freertos.c) */

  MX_FREERTOS_Init();

  osKernelStart();

  while (1)

  {

  }

}

完成这些配置后,我们进入freertos.c文件实现函数主功能:

先在头文件处添加我们会用到几个头文件:

#include "lcd.h"

#include "semphr.h"

实现ADC的中断回调函数:HAL_ADC_ConvCpltCallback();在这个函数中我们要将ADC采集数据的获取和释放信号量以便于任务Task_Show()读取数据。具体代码如下:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)

{

       if(hadc->Instance == ADC1)

       {

              adc_value = HAL_ADC_GetValue(hadc);  // 获取ADC的电压值

              BaseType_t HighTaskWoken= pdFALSE;

              if(myBinSem_DateReadyHandle != NULL)

              {

                     xSemaphoreGiveFromISR(myBinSem_DateReadyHandle,&HighTaskWoken);

                     portYIELD_FROM_ISR(HighTaskWoken);  // 进行任务调度

              }

       }

}

完成后我们实现任务Task_Show()对数据的读取和显示,xSemaphoreTake()这个函数用于读取数据,然后实现数据转化为电压值(单位为mv)。这部分代码较简单,这里直接附上代码:

void Task_Show(void *argument)

{

  /* USER CODE BEGIN Task_Show */

  /* Infinite loop */

  for(;;)

  {

    // 获取ADC的值

              if(xSemaphoreTake(myBinSem_DateReadyHandle,portMAX_DELAY) == pdTRUE)

              {

                     uint32_t tempValue = adc_value;

                     uint32_t valt = 3300 * tempValue;  // 将这个值转换为电压值

                     valt = valt>>12;

                     LCD_ShowxNum(0, 60, valt, 5, 16, 0);

              }

  }

  /* USER CODE END Task_Show */

}

这样就完成了整个代码的书写,烧录进开发板后可以看到电压值的变化,将其分别接到3.3v和0v,可以看到电压值的显示基本准确:

 

 

 

 

计数信号量

计数信号量就像一个餐厅一样,它可以同时容纳多个数据,计数信号量被创建时可以设置初值和最大值,一般设置这两个相等。最大值就是餐厅最多容纳的客人量,当餐厅坐满人后,剩下的数据就没办法存入了,必须等有客人离开才可以。

这里的客人其实就是访问的ISR或任务。

当有客人进店,其实就是获取信号量。

在有任务申请信号量时,可以设置等待时间,在等待时,任务进入阻塞状态。

当有客人离开,其实就是释放信号量。

二值信号量的使用示例

认识二值信号的各种函数

  1. 创建计量信号:osSemaphoreNew();这是一个宏函数,这个函数和创建二值信号的一样。
  2. 释放计量信号量:xSemaphoreGive();这个函数和释放二值信号的一样。释放信号就是释放资源,计数信号量的计数值加一,表示可用资源加一。
  3. 获取计量信号:xSemaphoreTake();这个函数和释放二值信号的一样。释放信号就是释放资源,计数信号量的计数值减一,表示可用资源减一。

认识这些基本函数后,我们开始进行第二个关于计数信号量的实验。

实验二:认识计数信号量

实验过程:创建一个计数信号量(初值为5),使用TIM的计时功能,当计时达到3s时,将信号量释放一个。当按下KEY1时,创建一个计量信号;利用LCD展示当前的剩余信号量和能否写入信号量。

CobeMX设置

这个实验和前一个实验基本配置都是LCD,然后打开TIM7,设置计数3s,具体配置如图:

 

图 5 TIM7设置

然后在NVIC中打开TIM7的中断,删除上一个实验使用的TIM3和ADC1。

加入一个按键KEY1,设置为GPIO_OutPut模式。

在freertos中Tasks and Queues页面设置任务将默认任务改成myTask_Checkln,具体配置如图:

 

图 6 freertos任务设置

在Timer and Semaphores页面设置一个计数信号量,如图所示:

 

图 7 freertos计数信号量设置

这些设置完成后就可以生成代码了。

代码实现

这里的LCD部分和之前设置一样,可以使用之前操作。

先在主函数中打开LCD的初始化和TIM7的中断,再测试LCD显示,主函数代码如下:

int main(void)

{

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();

  MX_FSMC_Init();

  MX_TIM7_Init();

       LCD_Init();

       LCD_ShowString(10, 0, 280, 16, 16, (uint8_t *)"this is the damo five!");

       HAL_TIM_Base_Start_IT(&htim7);  // 打开定时器7

  osKernelInitialize();  /* Call init function for freertos objects (in freertos.c) */

  MX_FREERTOS_Init();

  osKernelStart();

  while (1)

  {

  }

}

完成后进入freertos.c

在其中先实现TIM中断回调函数,在这个函数中主要完成对信号量的释放,具体代码如下:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  /* USER CODE BEGIN Callback 0 */



  /* USER CODE END Callback 0 */

  if (htim->Instance == TIM6) {

    HAL_IncTick();

  }

  /* USER CODE BEGIN Callback 1 */

       if (htim->Instance == TIM7)

       {

              if(Sem_TablesHandle != NULL)

              {

                     BaseType_t HighTaskWoken= pdFALSE;

                     xSemaphoreGiveFromISR(Sem_TablesHandle,&HighTaskWoken);  // 释放信号量

                     portYIELD_FROM_ISR(HighTaskWoken);  // 进行任务调度

              }

       }

  /* USER CODE END Callback 1 */

}

注意:直接使用这个代码时会报错,提示在main函数和freertos里面都定义了一次,我们只需要将main函数中的这个回调函数注释掉,main函数中的回调函数就是开启TIM6作为系统时钟,我在上面代码处已经将其添加,所以直接注释就好。

接下来我们在任务中实现按下KEY1获取一个信号量并将信号量的个数实时显示在LCD上。具体代码如下:

void Task_Checkln(void *argument)

{

  /* USER CODE BEGIN Task_Checkln */

       UBaseType_t freeSpace = 0;

       // 获取计量信号量初始剩余空间

       UBaseType_t qSpaces = uxQueueSpacesAvailable(Sem_TablesHandle);

       LCD_ShowString(10, 20, 280, 16, 16, (uint8_t *)"QueueSpaces = ");

       LCD_ShowxNum(120, 20, qSpaces, 2, 16, 0);     

  /* Infinite loop */

  for(;;)

  {

   

              // 读取是否按下KEY1,如果按下,使当前读取的桌子数减一

              GPIO_PinState key_State= HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);

              if(key_State == GPIO_PIN_RESET)

              {

                    

                     BaseType_t result = xSemaphoreTake(Sem_TablesHandle,100);  // 设置等待时间为100ms

                     if(result == pdTRUE)

                     {

                            LCD_ShowString(10, 40, 280, 16, 16, (uint8_t *)"Check in OK ");

                     }

                     else

                     {

                            LCD_ShowString(10, 40, 280, 16, 16, (uint8_t *)"Check in fail ");

                     }

                     vTaskDelay(200);  // 延时加消除抖动

              }

              // 当前餐桌数,等待读取的消息数

              freeSpace = uxSemaphoreGetCount(Sem_TablesHandle);

              LCD_ShowString(10, 60, 280, 16, 16, (uint8_t *)"freeSpace = ");

              LCD_ShowxNum(100, 60, freeSpace, 2, 16, 0);

 

  }

  /* USER CODE END Task_Checkln */

}

将其烧录进开发板,我们可以看到当成功获取一个信号量,LCD上显示Check in OK,如果其达到最大信号值,就会显示Check in fail。当等待3s后就会释放一个信号量。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值