FreeRTOS学习笔记——二值信号量的应用(串口数据收发)
一、概述
二值信号量跟互斥信号量非常相似,区别是互斥信号量拥有优先级继承机制,而二值信号量没有。因此二值信号量更适用于同步(任务与任务或任务与中断的同步)。
信号量API函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时候由于信号量无效而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一个信号量上,那么优先级最高的任务优先获得信号量,这样当信号量有效的时候,高优先级的任务就会解除阻塞状态。
二值信号量其实就是只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的,正好就是二值的。任务和中断使用这个特殊的队列时不用在乎队列中存了什么消息,只需要知道这个队列是满的还是空的即可。可以利用这个机制来完成人物与终端之间的同步。
二、应用
二值信号量的一种使用方法:
1、创建二值信号量(新版的创建二值信号量的API函数,创建后二值信号量默认是空的);
2、中断服务函数中释放二值信号量(如果没有释放,任务中获取二值信号量超时,获取失败);
3、任务中获取二值信号量(在阻塞时间等待二值信号量释放,进入阻塞状态,把CPU让给其他任务);
三、相关的API函数
1.二值信号量创建函数:
| 函数 | 描述 |
| vSemaphoreCreateBinary() | 老版本的API函数,动态创建二值信号量 |
| xSemaphoreCreateBinary() | 新版本的API函数,动态创建二值信号量 |
| xSemaphoreCreateBinaryStatic() | 静态创建二值信号量 |
这里只介绍最常用的xSemaphoreCreateBinary()函数,定义如下:
SemaphoreHandle_t xSemaphoreCreateBinary(void) 参数:无 返回值:NULL——二值信号量创建失败,其他值:创建成功的二值信号量的句柄
2.释放信号量
| 函数 | 描述 |
| xSemaphoreGive() | 任务级信号量释放函数 |
| xSemaphoreGiveFromISR() | 中断级信号量释放函数 |
1. xSemaphoreGive() BaseType_t xSemaphoreGive(xSemaphore) 参数:xSemaphore:要释放的信号量句柄 返回值:pdPASS:释放信号量成功;errQUEUE_FULL:释放信号量失败
2. xSemaphoreGiveFromISR()
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t * pxHigherPriorityTaskWoken)
参数:xSemaphore:要释放的信号量句柄
pxHigherPriorityTaskWoken:标记退出此函数以后是否进行任务切换,当此值为pdTRUE的时候,则在退出中断服务函数之前一定要进行一次任务切换。
返回值:pdPASS:释放信号量成功
errQUEUE_FULL:释放信号量失败
3.获取信号量
| 函数 | 描述 |
| xSemaphoreTake() | 任务级获取信号量函数 |
| xSemaphoreTakeFromISR() | 中断级获取信号量函数 |
1. BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xBlockTime)
参数:xSemaphore:要获取的信号量句柄;xBlockTime:阻塞时间
返回值:pdTRUE:获取信号量成功;pdFALSE:超时,获取信号量失败。
2. BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,BaseType_t pxHigherPriorityTaskWoken)
参数:xSemaphore:要获取的信号量句柄;
pxHigherPriorityTaskWoken: 标记退出此函数后是否进行任务切换,当此值为pdTRUE的时候,则在退出中断服务函数前一定要进行一次任务切换;
四、实验:
平台:GD32E507ZET6开发板
实验目的:开发板与PC机通过串口通讯,PC机上的串口助手定时发送数据到开发板上,开发板接收到数据后原封不动地转发回PC机。
1. 步骤:
1.移植FreeRTOS; 2.初始化串口(配置中断的优先级,使能TX和RX引脚的时钟,使能USART0的时钟,配置波特率,数据位长度,停止位,校验位等,使能输出,使能输入,使能串口,使能接收中断和空闲中断); 3.创建开始任务,开始任务的工作是创建二值信号量,创建串口任务; 4.串口中断服务函数中判断是接收缓冲区非空中断还是空闲中断,为什么要开启这两个中断呢?因为没接收到一个字节的数据就会触发前者,而当一连串字符接收完成后则会触发后者,开启后者就可以接收不定长的数据。当触发的中断是接收缓冲区非空的中断时,读取数据,而当触发的中断是空闲中断时,说明结束完一帧数据,此时就可以释放信号量; 5、串口任务的工作是获取信号量,当获取到信号量成功后,就可以把接收到的数据原封不动地发送出去。
2. 代码:
main.c
#include "include.h"
#include "gd32e507z_eval.h"
void Start_Task(void * parameter);
#define START_STK_SIZE 120
#define START_TASK_PRIO 1
TaskHandle_t START_TASK_Handle;
#define LED_STK_SIZE 120
#define LED_TASK_PRIO 2
TaskHandle_t LED_TASK_Handle;
#define USART_STK_SIZE 500
#define USART_TASK_PRIO 3
TaskHandle_t USART_TASK_Handle;
SemaphoreHandle_t BinarySemaphore;
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
/* configure 4 bits pre-emption priority */
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
systick_config();
/* initialize LED */
LED_Init();
/* initialize USART0 */
/* configure NVIC and systick */
nvic_irq_enable(USART0_IRQn, 0, 0);//中断优先级一定要配,不然中断就没反应
USART_Config(usart0,115200,USART_WL_8BIT,USART_STB_1BIT,USART_PM_NONE);
usart_interrupt_enable(USART0,USART_INT_RBNE);
usart_interrupt_enable(USART0,USART_INT_IDLE);
xTaskCreate(Start_Task,"Start_Task",START_STK_SIZE,NULL,START_TASK_PRIO,&START_TASK_Handle);
vTaskStartScheduler();
while(1){
}
}
void Start_Task(void * parameter)
{
BinarySemaphore = xSemaphoreCreateBinary();
xTaskCreate(USART_Task,"USART_Task",USART_STK_SIZE,NULL,USART_TASK_PRIO,&USART_TASK_Handle);
vTaskDelete(NULL);
}
usart.c
#include "usart.h"
#include "string.h"
#include "semphr.h"
/***************************************
* USART0_TX -- PA9 USART0_RX -- PA10
* USART1_TX -- PA2 USART1_RX -- PA3
* USART2_TX -- PB10 USART2_RX -- PB11
* UART3_TX -- PC10 UART3_RX -- PC11
* UART4_TX -- PC12 UART4_RX -- PD2
* USART5_TX -- PC6 USART5_RX -- PC7
****************************************/
static rcu_periph_enum USART_CLK[USARTn] = {RCU_USART0,RCU_USART1,RCU_USART2,RCU_UART3,RCU_UART4,RCU_USART5};
static rcu_periph_enum USART_GPIO_CLK[USARTn] = {USART0_GPIO_CLK,USART1_GPIO_CLK,USART2_GPIO_CLK,UART3_GPIO_CLK,UART4_GPIO_CLK,USART5_GPIO_CLK};
static uint32_t USARTx_TX_PORT[USARTn] = {GPIOA,GPIOA,GPIOB,GPIOC,GPIOC,GPIOC};
static uint32_t USARTx_TX_PIN[USARTn] = {GPIO_PIN_9,GPIO_PIN_2,GPIO_PIN_10,GPIO_PIN_10,GPIO_PIN_12,GPIO_PIN_6};
static uint32_t USARTx_RX_PORT[USARTn] = {GPIOA,GPIOA,GPIOB,GPIOC,GPIOD,GPIOC};
static uint32_t USARTx_RX_PIN[USARTn] = {GPIO_PIN_10,GPIO_PIN_3,GPIO_PIN_11,GPIO_PIN_11,GPIO_PIN_2,GPIO_PIN_7};
static uint32_t USARTx[USARTn] = {USART0,USART1,USART2,UART3,UART4,USART5};
void USART_Config(uint8_t com,uint32_t Baudrate,uint32_t WL,uint32_t STB,uint32_t Parity)
{
/* enable GPIO clock */
rcu_periph_clock_enable(USART_GPIO_CLK[com]);
/* enable USART clock */
rcu_periph_clock_enable(USART_CLK[com]);
/* connect port to USARTx_Tx */
gpio_init(USARTx_TX_PORT[com], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, USARTx_TX_PIN[com]);
/* connect port to USARTx_Rx */
gpio_init(USARTx_RX_PORT[com], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, USARTx_RX_PIN[com]);
/* USART configure */
usart_deinit(USARTx[com]);
usart_word_length_set(USARTx[com], WL);
usart_stop_bit_set(USARTx[com], STB);
usart_parity_config(USARTx[com], Parity);
usart_baudrate_set(USARTx[com], Baudrate);
usart_receive_config(USARTx[com], USART_RECEIVE_ENABLE);
usart_transmit_config(USARTx[com], USART_TRANSMIT_ENABLE);
usart_enable(USARTx[com]);
}
void USART_Transmit_String(uint32_t usart_periph,uint8_t * str)
{
uint32_t len = strlen((char *)str);
for(int i = 0;i < len;i++)
{
usart_data_transmit(usart_periph,*str);
while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
str++;
}
}
void USART_Task(void * parameter)
{
BaseType_t err = pdPASS;
uint8_t data[100] = "Welcom to Shenzhen\r\n";
while(1)
{
if(BinarySemaphore != NULL)
{
err = xSemaphoreTake(BinarySemaphore,portMAX_DELAY);
if(err == pdPASS)
{
memcpy(data,REV_DATA,REV_LEN);
USART_Transmit_String(USART0,data);
memset(data,0,sizeof(data));
memset(REV_DATA,0,sizeof(REV_DATA));
REV_LEN = 0;
}
}
else
{
vTaskDelay(10);
}
}
}
usart.h
#ifndef _USART_H_
#define _USART_H_
#include "include.h"
#define USARTn 6
#define USART0_GPIO_CLK RCU_GPIOA
#define USART1_GPIO_CLK RCU_GPIOA
#define USART2_GPIO_CLK RCU_GPIOB
#define UART3_GPIO_CLK RCU_GPIOC
#define UART4_GPIO_CLK (RCU_GPIOC|RCU_GPIOD)
#define USART5_GPIO_CLK RCU_GPIOC
typedef enum
{
usart0 = 0,
usart1,
usart2,
uart3,
uart4,
usart5,
}COMx;
void USART_Config(uint8_t com,uint32_t Baudrate,uint32_t WL,uint32_t STB,uint32_t Parity);
void USART_Transmit_String(uint32_t usart_periph,uint8_t * str);
void USART_Task(void * parameter);
#endif
include.h
#ifndef _INCLUDE_H_ #define _INCLUDE_H_ #include "gd32e50x.h" #include "systick.h" #include "led.h" #include "usart.h" #include "FreeRTOSConfig.h" #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "semphr.h" extern SemaphoreHandle_t BinarySemaphore; extern uint8_t REV_DATA[1000]; extern uint32_t REV_LEN; #endif
gd32e50x_it.c
void USART0_IRQHandler(void)
{
uint8_t data;
if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE))
{
data = usart_data_receive(USART0);
REV_DATA[REV_LEN++]= data;
// usart_interrupt_flag_clear(USART0,USART_INT_FLAG_RBNE);
}
if(usart_flag_get(USART0,USART_FLAG_IDLE)!=RESET)
{
xSemaphoreGiveFromISR(BinarySemaphore,&xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
//以下两行代码作用是清除空闲标志,先读取状态寄存器,在读取数据寄存器
GET_BITS(USART_STAT0(USART0), 0U, 8U);
GET_BITS(USART_DATA(USART0), 0U, 8U);
}
}
3. 实验现象:
PC端串口助手没发送一串字符串都可以收到相同的一串字符串:


浙公网安备 33010602011771号