目录
1、函数 vSemaphoreCreateBinary ()
2、函数 xSemaphoreCreateBinary()::动态创建二值信号量
3、函数 xSemaphoreCreateBinaryStatic():静态创建二值信号量
1、函数 xSemaphoreGive():任务级信号量释放函数
信号量是操作系统中重要的一部分,信号量一般用来进行资源管理和任务同步, FreeRTOS
中信号量又分为二值信号量、 计数型信号量、互斥信号量和递归互斥信号量。不同的信号量其
应用场景不同,但有些应用场景是可以互换着使用的。
信号量常常用于控制对共享资源的访问和任务同步。举一个很常见的例子,某个停车场有
100 个停车位,这 100 个停车位大家都可以用,对于大家来说这 100 个停车位就是共享资源。
假设现在这个停车场正常运行,你要把车停到这个这个停车场肯定要先看一下现在停了多少车
了?还有没有停车位?当前停车数量就是一个信号量,具体的停车数量就是这个信号量值,当
这个值到 100 的时候说明停车场满了。停车场满的时你可以等一会看看有没有其他的车开出停
车场,当有车开出停车场的时候停车数量就会减一,也就是说信号量减一,此时你就可以把车
停进去了,你把车停进去以后停车数量就会加一,也就是信号量加一。 这就是一个典型的使用
信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。再看另外一个案例:
使用公共电话,我们知道一次只能一个人使用电话,这个时候公共电话就只可能有两个状态:
使用或未使用,如果用电话的这两个状态作为信号量的话,那么这个就是二值信号量。
信号量用于控制共享资源访问的场景相当于一个上锁机制, 代码只有获得了这个锁的钥匙
才能够执行。
上面我们讲了信号量在共享资源访问中的使用,信号量的另一个重要的应用场合就是任务
同步,用于任务与任务或中断与任务之间的同步。 在执行中断服务函数的时候可以通过向任务
发送信号量来通知任务它所期待的事件发生了, 当退出中断服务函数以后在任务调度器的调度
下同步的任务就会执行。在编写中断服务函数的时候我们都知道一定要快进快出,中断服务函
数里面不能放太多的代码,否则的话会影响的中断的实时性。 裸机编写中断服务函数的时候一
般都只是在中断服务函数中打个标记,然后在其他的地方根据标记来做具体的处理过程。在使
用 RTOS 系统的时候我们就可以借助信号量完成此功能, 当中断发生的时候就释放信号量,中
断服务函数不做具体的处理。具体的处理过程做成一个任务,这个任务会获取信号量,如果获
取到信号量就说明中断发生了,那么就开始完成相应的处理,这样做的好处就是中断执行时间
非常短。 这个例子就是中断与任务之间使用信号量来完成同步,当然了, 任务与任务之间也可
以使用信号量来完成同步。
二值信号量通常用于互斥访问或同步, 二值信号量和互斥信号量非常类似,但是还是有一
些细微的差别, 互斥信号量拥有优先级继承机制, 二值信号量没有优先级继承。 因此二值信号
另更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问,
有关互斥信号量的内容后面会专门讲解,本节只讲解二值信号量在同步中的应用。
和队列一样,信号量 API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时
候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一
一个信号量上的话那么优先级最高的哪个任务优先获得信号量, 这样当信号量有效的时候高优
先级的任务就会解除阻塞状态。
二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空
的,这不正好就是二值的吗? 任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。
在实际应用中通常会使用一个任务来处理 MCU 的某个外设,比如网络应用中,一般最简
单的方法就是使用一个任务去轮询的查询 MCU 的 ETH(网络相关外设,如 STM32 的以太网
MAC)外设是否有数据,当有数据的时候就处理这个网络数据。这样使用轮询的方式是很浪费
CPU 资源的,而且也阻止了其他任务的运行。最理想的方法就是当没有网络数据的时候网络任
务就进入阻塞态,把 CPU 让给其他的任务,当有数据的时候网络任务才去执行。现在使用二值
信号量就可以实现这样的功能,任务通过获取信号量来判断是否有网络数据,没有的话就进入
阻塞态,而网络中断服务函数(大多数的网络外设都有中断功能,比如 STM32 的 MAC 专用 DMA
中断,通过中断可以判断是否接收到数据)通过释放信号量来通知任务以太网外设接收到了网络
数据,网络任务可以去提取处理了。 网络任务只是在一直的获取二值信号量,它不会释放信号
量,而中断服务函数是一直在释放信号量,它不会获取信号量。在中断服务函数中发送信号量
可以使用函数 xSemaphoreGiveFromISR(), 也可以使用任务通知功能来替代二值信号量,而且使
用任务通知的话速度更快,代码量更少,有关任务通知的内容后面会有专门的章节介绍。
使用二值信号量来完成中断与任务同步的这个机制中,任务优先级确保了外设能够得到及
时的处理,这样做相当于推迟了中断处理过程。 也可以使用队列来替代二值信号量,在外设事
件的中断服务函数中获取相关数据,并将相关的数据通过队列发送给任务。如果队列无效的话
任务就进入阻塞态,直至队列中有数据,任务接收到数据以后就开始相关的处理过程。
由于任务函数一般都是一个大循环,所以在任务做完相关的处理以后就会再次调用函数
xSemaphoreTake()获取信号量。在执行完第三步以后二值信号量就已经变为无效的了,所以任务
将再次进入阻塞态,和第一步一样,直至中断再次发生并且调用函数 xSemaphoreGiveFromISR()
释放信号量。
同队列一样,要想使用二值信号量就必须先创建二值信号量。
此函数是老版本 FreeRTOS 中的创建二值信号量函数,新版本已经不再使用了, 新版本的
FreeRTOS 使用 xSemaphoreCreateBinary()来替代此函数,所以不多讲这个函数,现在用不着。
此函数是 vSemaphoreCreateBinary()的新版本,新版本的 FreeRTOS 中统一用此函数来创建
二值信号量。 使用此函数创建二值信号量的话信号量所需要的 RAM 是由 FreeRTOS 的内存管
理部分来动态分配的。此函数创建好的二值信号量默认是空的。此函数也是个宏, 具体创建过程是由函数xQueueGenericCreate()来完成的。
无
此函数也是创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的
RAM 需要由用户来分配,此函数是个宏,具体创建过程是通过函数 xQueueGenericCreateStatic()
来完成的。
释放信号量的函数有两个
此函数用于释放二值信号量、计数型信号量或互斥信号量, 此函数是一个宏,真正释放信
号量的过程是由函数 xQueueGenericSend()来完成的。
此函数用于在中断中释放信号量, 此函数只能用来释放二值信号量和计数型信号量,绝对
不能用来在中断服务函数中释放互斥信号量! 此函数是一个宏,真正执行的是函数
xQueueGiveFromISR()。
获取信号量也有两个函数
此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信
号量的过程是由函数 xQueueGenericReceive ()来完成的。
获取信号量的过程其实就是读取队列的过程,只是这里并不是为了读取队列中的消息。在
第十三章讲解函数 xQueueGenericReceive()的时候说过如果队列为空并且阻塞时间为 0 的话就
立即返回 errQUEUE_EMPTY,表示队列满。如果队列为空并且阻塞时间不为 0 的话就将任务
添加到延时列表中。 如果队列不为空的话就从队列中读取数据(获取信号量不执行这一步),数
据读取完成以后还需要将队列结构体成员变量 uxMessagesWaiting 减一, 然后解除某些因为入
队而阻塞的任务,最后返回 pdPASS 表示出对成功。 互斥信号量涉及到优先级继承,处理方式
不同,后面讲解互斥信号量的时候在详细的讲解。
此函数用于在中断服务函数中获取信号量, 此函数用于获取二值信号量和计数型信号量,
绝 对 不 能 使 用 此 函 数 来 获 取 互 斥 信 号 量 ! 此 函 数 是 一 个 宏 , 真 正 执 行 的 是 函 数xQueueReceiveFromISR ()。
注:在中断中获取信号量真正使用的是函数 xQueueReceiveFromISR (),这个函数就是中断级
出队函数! 当队列不为空的时候就拷贝队列中的数据(用于信号量的时候不需要这一步),然后
将队列结构体中的成员变量 uxMessagesWaiting 减一,如果有任务因为入队而阻塞的话就解除
阻塞态,当解除阻塞的任务拥有更高优先级的话就将参数 pxHigherPriorityTaskWoken 设置为
pdTRUE,最后返回 pdPASS 表示出队成功。 如果队列为空的话就直接返回 pdFAIL 表示出队失
败! 这个函数还是很简单的。
功能实现:串口助手发送数据,单片机接收到后,会释放信号量,在主程序中,判断信号量是否存在,不存在,熄灭LED1,存在会点亮LED1.
- /* 获取信号量任务参数 */
-
- //任务优先级
- #define SemaphoreTask_PRIO 0
- //任务堆栈大小
- #define SemaphoreTask_SIZE 128
- //任务句柄
- TaskHandle_t SemaphoreTask_Handler;
- //任务函数
- void SemaphoreTask(void *pvParameters);
-
-
- //二值信号量任务
- void SemaphoreTask(void *pvParameters)
- {
- BaseType_t err=pdFALSE;
- while(1)
- {
- if(Semaphore!=NULL)
- {
- err=xSemaphoreTake(Semaphore,portMAX_DELAY); //获取信号量
- if(err == pdTRUE)//信号量获取成功
- {
- GPIO_ResetBits(GPIOE,GPIO_Pin_5);//点亮LED1
- }
- else if(err == pdFALSE)//信号量获取失败
- {
- GPIO_SetBits(GPIOE,GPIO_Pin_5);//熄灭LED1
- }
- }
- vTaskDelay(800);
- }
- }
一个变量四个作用
兄弟们,好好研究一下这串接收中断代码,非常优秀,一个变量可以有四种控制,这就是底层c语言的逻辑,异常强大。
- #define USART_RX_Len 128 //获取数据的最大长度
- extern SemaphoreHandle_t Semaphore; //二值信号量句柄
- uint16_t USART_RX_State=0; //获取数据的状态,以及对分支语句的操作标记
- u8 USART_RX_StateBuff[USART_RX_Len];//接收数据缓冲区
-
- void USART1_IRQHandler(void) //串口1中断服务程序
- {
- u8 Res;
- BaseType_t xHigherPriorityTaskWoken;
- if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)//接收中断
- {
- Res =USART_ReceiveData(USART1); //读取接收到的数据
-
- if((USART_RX_State&0x8000) == 0)//接收未完成
- {
- if(USART_RX_State&0x4000) //若是接收到0x0d:\r
- {
- if(Res != 0x0a)USART_RX_State=0; //接收失败,重新接收 0x0a:\n
- else USART_RX_State|=0x8000; //接收完成
- }
- else
- {
- if(Res == 0x0d) USART_RX_State |= 0x4000;//标记,使其进入到 if 语句
- else
- {
- USART_RX_StateBuff[USART_RX_State] = Res;//把数据放在缓冲区
- USART_RX_State ++ ;
- if(USART_RX_State>(USART_RX_Len-1))USART_RX_State=0;//接收空间区太小
- }
- }
- }
- }
-
- if((USART_RX_State&0x8000)&&(Semaphore!=NULL))//接收到数据,并且二值信号量有效
- {
- xSemaphoreGiveFromISR(Semaphore,&xHigherPriorityTaskWoken); //释放二值信号量
- portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
- }
- }