我们在stm32f103c8t6单片机上验证RTOS二值信号量与计数信号量,利用stm32cube进行RTOS的配置。裸机的时钟源默认是 SysTick,但是开启 FreeRTOS 后,FreeRTOS会占用 SysTick (用来生成1ms 定时,用于任务调度),所以我们开启TIM2当做裸机的时钟源,为其他总线提供另外的时钟源。
验证的功能比较简单,选择V1 版本的内核完全够用。
1.验证思路
创建一个二值信号量,按下 KEY1 则放入信号量,按下 KEY2 获取信号量
2.需要函数
创建二值信号量 SemaphoreHandle_t xSemaphoreCreateBinary( void )
参数:无 返回值: 成功,返回对应二值信号量的句柄; 失败,返回 NULL 。
释放二值信号量 BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )
参数: xSemaphore:要释放的信号量句柄
返回值: 成功,返回 pdPASS ; 失败,返回 errQUEUE_FULL。
获取二值信号量 BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
参数:xSemaphore:要获取的信号量句柄
xTicksToWait:超时时间,0 表示不超时,portMAX_DELAY表示卡死等待;
返回值:成功,返回 pdPASS ;失败,返回 errQUEUE_FULL 。
3. 二值信号量的说明
二值信号量相当于一个长度为1,大小为零的队列,只有0和1两种状态,通常情况下,我们用它来进行互斥访问或任务同步。
互斥访问:我的理解是,比如说信号量是一个玩具,A拿走了,B就没得玩了;A要是把玩具放回去,B才可以拿着玩。
任务同步:我的理解,就是因为什么,所以什么。比如你去电影院,要先去买票才可以看电影。只有A的发生才可以导致B的发生。
SYS
RCC
GPIO
PA0对应按键1,PA1对应按键2。
RTOS RTOS版本选择
在Tasks and Queues中配置我们的2个任务
其实就是相当于stm32cube帮我们调用了并封装了生成任务函数xTaskCreate(),生成队列函数xQueueCreate函数。
两个任务的名字分别是
TaskGive,TaskTake
两个任务的入口函数名字分别是
StartTaskGive, StartTaskTake;
各参数的配置相同,如下图
任务1和2除了任务名字和入口函数不同,其余的都相同
在Times and Semapores里
选择Binary semaphores(二值信号),生成二值信号配置如下图所示
usart.c
#include "stdio.h"
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
同时打开“魔术棒”,勾选Use MicroLIB,点击OK。这样就可以进行串口打印了。
freertos.c
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
# include "stdio.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
osThreadId TaskGiveHandle;
osThreadId TaskTakeHandle;
osSemaphoreId myBinarySemHandle;
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartTaskGive(void const * argument);
void StartTaskTake(void const * argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/* GetIdleTaskMemory prototype (linked to static allocation support) */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );
/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
*ppxIdleTaskStackBuffer = &xIdleStack[0];
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
/* place for user code */
}
/* USER CODE END GET_IDLE_TASK_MEMORY */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* Create the semaphores(s) */
/* definition and creation of myBinarySem */
osSemaphoreDef(myBinarySem);
myBinarySemHandle = osSemaphoreCreate(osSemaphore(myBinarySem), 1);
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* definition and creation of TaskGive */
osThreadDef(TaskGive, StartTaskGive, osPriorityNormal, 0, 128);
TaskGiveHandle = osThreadCreate(osThread(TaskGive), NULL);
/* definition and creation of TaskTake */
osThreadDef(TaskTake, StartTaskTake, osPriorityNormal, 0, 128);
TaskTakeHandle = osThreadCreate(osThread(TaskTake), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
}
/* USER CODE BEGIN Header_StartTaskGive */
/**
* @brief Function implementing the TaskGive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskGive */
void StartTaskGive(void const * argument)
{
/* USER CODE BEGIN StartTaskGive */
/* Infinite loop */
for(;;)
{
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
if (xSemaphoreGive(myBinarySemHandle) == pdTRUE)
printf("二值信号量放入成功\r\n");
else
printf("二值信号量放入失败\r\n");
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskGive */
}
/* USER CODE BEGIN Header_StartTaskTake */
/**
* @brief Function implementing the TaskTake thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskTake */
void StartTaskTake(void const * argument)
{
/* USER CODE BEGIN StartTaskTake */
/* Infinite loop */
for(;;)
{
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
if (xSemaphoreTake(myBinarySemHandle, portMAX_DELAY ) == pdTRUE)
printf("二值信号量取出成功\r\n");
else
printf("二值信号量取出失败\r\n");
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskTake */
}
需要说明的是
osSemaphoreDef(myBinarySem);
myBinarySemHandle = osSemaphoreCreate(osSemaphore(myBinarySem), 1);
这两个函数相当于stm32cube封装了创建二值信号量函数SemaphoreHandle_t xSemaphoreCreateBinary( void ),代表着二值信号量创建成功,同时该他在开始的时候还给二值信号量赋值唯一。
来看看以下运行的结果
开始的时候,我按下复位键,然后按key1,接着按key2
可以发现,按下key1后,信号写入失败,为什么呢?
这是因为我们在前面就说过,二值信号量其实就是一个长度为1,大小为零的队列,只有0和1两种状态。而且由于myBinarySemHandle = osSemaphoreCreate(osSemaphore(myBinarySem), 1);的写入,导致在一开始的时候,二值信号量就自动被赋值为1,作为队列为1的二值信号量已经写满了,无法再写进去了。所以按下key1,写入失败,但按下key2写入成功。
那么又什么解决办法吗?当然有了
把myBinarySemHandle = osSemaphoreCreate(osSemaphore(myBinarySem), 1);这个语句换一下,换成myBinarySemHandle = xSemaphoreCreateBinary();就可以了,这样就不会自动赋值1了。
下图是替换过后按下key1和key2的图
再来分析一下xSemaphoreTake(myBinarySemHandle, portMAX_DELAY )
portMAX_DELAY表示卡死等待,啥意思呢?就是说,你给我下了取出信号的命令,但是我去取的时候发现没有信号,咋办呢?我就等,等到你下一次放入信号,不用你同意,我直接拿走,等你想在再用按键取走的时候,不好意思,我已经拿走了,你没法拿了,除非你在放入信号。
先按下key2,取走信号,因为开始没有信号,所以死等。等你按下key1放入信号我就直接取走。看似你存了信号,实际已经没了,所以你还可以在存储信号。
1. 验证的思路
按下 KEY1 则放入信号量,按下 KEY2 获取信号量。
2.所需函数
创建计数信号量SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
uxMaxCount:可以达到的最大计数值
uxInitialCount:创建信号量时分配给信号量的计数值
返回值: 成功,返回对应计数型信号量的句柄; 失败,返回 NULL 。
释放计数信号量 BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )
参数: xSemaphore:要释放的信号量句柄
返回值: 成功,返回 pdPASS ; 失败,返回 errQUEUE_FULL。
获取计数信号量 BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
参数:xSemaphore:要获取的信号量句柄
xTicksToWait:超时时间,0 表示不超时,portMAX_DELAY表示卡死等待;
返回值:成功,返回 pdPASS ;失败,返回 errQUEUE_FULL 。
3. 计数信号量的说明
计数信号量相当于队列长度大于1 的队列,长度具体为多少,是你自己设定的
同样得到了,他所放的信号数值也不能超过你设置的最大值。用法上和二值信号量很相似。这里我们所用的释放和获取计数信号量函数和二值信号量那里一样。
SYS
RCC
GPIO
PA0对应按键1,PA1对应按键2。
RTOS
在Tasks and Queues中配置我们的2个任务
其实就是相当于stm32cube帮我们调用了并封装了生成任务xTaskCreate(),生成队列xQueueCreate函数。
两个任务的名字分别是
TaskGive,TaskTake
两个任务的入口函数名字分别是
StartTaskGive, StartTaskTake;
各参数的配置相同,如下图
任务1和2除了任务名字和入口函数不同,其余的都相同
在Config parameters里,将USE_COUNTING_SEMAPHORES配置为Enablde,否则无法开启计数量
接着在Times and Semapores里
选择Counting semaphores(二值信号),配置计数最大值为3,如下图所示
usart.c
#include "stdio.h"
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
同时打开“魔术棒”,勾选Use MicroLIB,点击OK。这样就可以进行串口打印了。
freertos.c
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
# include "stdio.h"
osThreadId TaskGiveHandle;
osThreadId TaskTakeHandle;
osSemaphoreId myCountingSemHandle;
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartTaskGive(void const * argument);
void StartTaskTake(void const * argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/* GetIdleTaskMemory prototype (linked to static allocation support) */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );
/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
*ppxIdleTaskStackBuffer = &xIdleStack[0];
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
/* place for user code */
}
/* USER CODE END GET_IDLE_TASK_MEMORY */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* Create the semaphores(s) */
/* definition and creation of myCountingSem */
osSemaphoreDef(myCountingSem);
// myCountingSemHandle = osSemaphoreCreate(osSemaphore(myCountingSem), 3);
myCountingSemHandle = xSemaphoreCreateCounting(3, 0);
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* definition and creation of TaskGive */
osThreadDef(TaskGive, StartTaskGive, osPriorityNormal, 0, 128);
TaskGiveHandle = osThreadCreate(osThread(TaskGive), NULL);
/* definition and creation of TaskTake */
osThreadDef(TaskTake, StartTaskTake, osPriorityNormal, 0, 128);
TaskTakeHandle = osThreadCreate(osThread(TaskTake), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
}
/* USER CODE BEGIN Header_StartTaskGive */
/**
* @brief Function implementing the TaskGive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskGive */
void StartTaskGive(void const * argument)
{
/* USER CODE BEGIN StartTaskGive */
/* Infinite loop */
for(;;)
{
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
if (xSemaphoreGive(myCountingSemHandle) == pdTRUE)
printf("计数信号量放入成功\r\n");
else
printf("计数信号量放入失败\r\n");
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskGive */
}
/* USER CODE BEGIN Header_StartTaskTake */
/**
* @brief Function implementing the TaskTake thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskTake */
void StartTaskTake(void const * argument)
{
/* USER CODE BEGIN StartTaskTake */
/* Infinite loop */
for(;;)
{
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
if (xSemaphoreTake(myCountingSemHandle, 0 ) == pdTRUE)
printf("计数信号量取出成功\r\n");
else
printf("计数信号量取出失败\r\n");
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskTake */
}
这里要说一下,如果是我们在生成二值计数量的时候,完全依赖stm32cube,那么生成的函数就有这么一行代码
myCountingSemHandle = osSemaphoreCreate(osSemaphore(myCountingSem), 3);
意思就是,这个计数量函数我生成,返回值是句柄,同时我把信号量给你填满了(因为我们配置的时候最大计数值就是3,因此这里填满了就是3个),所以你这个时刻就不能再进入信号量了,你只能取出,等有空位了,你再进。
所以我们进行了改写,把这这局程序替换了一下,让他从0开始,一开始就不给他信号量
myCountingSemHandle = xSemaphoreCreateCounting(3, 0);
下图是我用替换后的代码,并连续按了四下key1和四下key2的结果。从图中我们可以看出来,因为最多只能输入3个信号量,所以当我按下连续按四下key1时,第四次是没法放入信号的。同理,因为最多只能储存3个信号量,所以当我按下连续按四下key2时,第四次是没法取出信号的。