FreeRTOS操作系统
1.FreeRTOS简介
FreeRTOS是一种专门为单片机开发的实时操作系统,核心思想是将系统划分为多个独立的任务,每个任务都有自己的代码和堆栈空间,可以独立运行。通过任务管理器,可以创建、删除、挂起、恢复和切换任务。任务的调度是由FreeRTOS内核完成的,它使用优先级和时间片轮转等调度算法,确保高优先级任务得到及时执行。在CubeMX上给我们提供了很多关于FreeRTOS操作系统的图形化配置,帮助我们更好的使用操作系统。
2.任务管理
任务是FreeRTOS中的基本运行单位,对于任务的管理提供了许多的函数,下面我们来看看这些函数。
osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument)
该函数为使用动态或静态的方法来创建一个任务,第一个参数是引用由osThreadDef定义的任务,第二个参数是任务入口函数形参,对于返回值成功返回任务Id错误返回0。
osStatus osThreadTerminate (osThreadId thread_id)
删除任务,参数是想要删除任务的id,返回值错误码。
osStatus osKernelStart (void)
开启调度器,因为在创建任务时任务只是被创建了还没有开始执行,所以我们要启用调度器来使任务开始执行,返回值错误码就是系统定义的宏值用来表示该函数执行的状态。
osStatus osDelay (uint32_t millisec)
相对延时函数,用来对任务进行延时,延时的任务将进入阻塞态让出CPU资源,参数要延时的毫秒数,返回值状态码。
osStatus osDelayUntil (uint32_t *PreviousWakeTime, uint32_t millisec)
绝对延时函数,用来时间要求精确的要周期性执行的任务,第一个参数是上次离开阻塞态的时间用来作为一个参考点来进行精确的时间延时,第二个参数要延时的时间毫秒数。
osStatus osThreadSuspend (osThreadId thread_id)
挂起任务,用来暂停挂起该任务,被挂起的任务将不会占用CPU资源,参数任务id,返回值错误码。
osStatus osThreadSuspendAll (void)
挂起所有任务,返回值错误码。
osStatus osThreadResumeAll (void)
恢复所用任务,可以将被挂起的任务恢复,返回值错误码。
osThreadState osThreadGetState(osThreadId thread_id)
获取任务状态,参数想要获取状态的任务id,返回值为一个枚举类型,内容如下:
/* Thread state returned by osThreadGetState */
typedef enum
{
osThreadRunning = 0x0, /* A thread is querying the state of itself, so must be running. */
osThreadReady = 0x1 , /* The thread being queried is in a read or pending ready list. */
osThreadBlocked = 0x2, /* The thread being queried is in the Blocked state. */
osThreadSuspended = 0x3, /* The thread being queried is in the Suspended state, or is in theBlocked state with an infinite time out. */
osThreadDeleted = 0x4, /* The thread being queried has been deleted, but its TCB has not yetbeen freed. */
osThreadError = 0x7FFFFFFF
} osThreadState;
我们通过CubeMX来进行基本的图形化配置创建多个任务来实现用按键控制LED灯。

void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN StartDefaultTask *//* Infinite loop */for(;;){if(Key_Value == KEY_UP){Key_Value = 0;printf("LED1 Suspend\n");osThreadSuspend(LED1Handle);}else if(Key_Value == KEY_DOWN){Key_Value = 0;printf("LED1 Running\n");osThreadResume(LED1Handle);}else if(Key_Value == KEY_LEFT){Key_Value = 0;printf("LED2 Suspend\n");osThreadSuspend(LED2Handle);}else if(Key_Value == KEY_RIGHT){Key_Value = 0;printf("LED2 Running\n");osThreadResume(LED2Handle);}osDelay(100);}/* USER CODE END StartDefaultTask */
}/* USER CODE BEGIN Header_TaskLED1 */
/**
* @brief Function implementing the LED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_TaskLED1 */
void TaskLED1(void const * argument)
{/* USER CODE BEGIN TaskLED1 *//* Infinite loop */for(;;){HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);osDelay(200);}/* USER CODE END TaskLED1 */
}/* USER CODE BEGIN Header_TaskLED2 */
/**
* @brief Function implementing the LED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_TaskLED2 */
void TaskLED2(void const * argument)
{/* USER CODE BEGIN TaskLED2 *//* Infinite loop */for(;;){HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);osDelay(1000);}/* USER CODE END TaskLED2 */
}
3.互斥量
互斥量支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。
osMutexId osMutexCreate (const osMutexDef_t *mutex_def)
创建一个互斥量,参数引用由osMutexDef定义的互斥量,返回值返回互斥量id。
osMutexId osRecursiveMutexCreate (const osMutexDef_t *mutex_def)
创建一个递归互斥量,参数引用由osMutexDef定义的互斥量,返回值创建成功返回互斥量id失败则返回0。
osStatus osMutexDelete (osMutexId mutex_id)
用来删除一个互斥量,参数互斥量id,返回值错误码。
osStatus osMutexWait (osMutexId mutex_id, uint32_t millisec)
获取互斥量,递归互斥量不是由该函数获取,第一个参数互斥量id,第二个参数等待时间,返回值错误码,可以使用portMAX_DELAY为没有超时时间一直等待。
osStatus osRecursiveMutexWait (osMutexId mutex_id, uint32_t millisec)
用于获取递归互斥量,第一个参数互斥量id,第二个参数等待超时时间。返回值错误码。
osStatus osMutexRelease (osMutexId mutex_id)
释放互斥量,参数互斥量id,返回值错误码。
osStatus osRecursiveMutexRelease (osMutexId mutex_id)
释放递归互斥量,参数互斥量id,返回值错误码。
互斥量可以用来解决共享资源的的访问问题,下面来看相应的程序:
void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN StartDefaultTask *//* Infinite loop */for(;;){if(Key_Value == KEY_UP){Key_Value = 0;printf("LED1 Suspend\n");osThreadSuspend(LED1Handle);}else if(Key_Value == KEY_DOWN){Key_Value = 0;printf("LED1 Running\n");osThreadResume(LED1Handle);}else if(Key_Value == KEY_LEFT){Key_Value = 0;printf("LED2 Suspend\n");osThreadSuspend(LED2Handle);}else if(Key_Value == KEY_RIGHT){Key_Value = 0;printf("LED2 Running\n");osThreadResume(LED2Handle);}osDelay(100);}/* USER CODE END StartDefaultTask */
}/* USER CODE BEGIN Header_TaskLED1 */
/**
* @brief Function implementing the LED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_TaskLED1 */
void TaskLED1(void const * argument)
{/* USER CODE BEGIN TaskLED1 */osMutexWait(myMutex01Handle,portMAX_DELAY);//等待互斥锁printf("LED1 Running\n");osMutexRelease(myMutex01Handle);//释放互斥锁/* Infinite loop */for(;;){HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);osDelay(200);}/* USER CODE END TaskLED1 */
}/* USER CODE BEGIN Header_TaskLED2 */
/**
* @brief Function implementing the LED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_TaskLED2 */
void TaskLED2(void const * argument)
{/* USER CODE BEGIN TaskLED2 */osMutexWait(myMutex01Handle,portMAX_DELAY);//等待互斥锁printf("LED2 Running\n");osMutexRelease(myMutex01Handle);//释放互斥锁/* Infinite loop */for(;;){HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);osDelay(1000);}
4.消息队列
消息队列是一种用于任务间通信的同步机制。它提供了一种简单、可靠的方式,让任务之间可以传递数据和消息,以实现资源共享和协同工作。消息队列的基本原理是,一个任务可以将一个消息发送到消息队列中,而另一个任务则可以从消息队列中接收该消息。发送和接收消息的任务可以是同一个任务,也可以是不同的任务。当消息队列为空时,接收任务会被挂起,直到有新的消息到达为止。同样对于消息队列也有着很多的函数下面我们来看看:
osMessageQId osMessageCreate (const osMessageQDef_t *queue_def, osThreadId thread_id)
创建一个队列,第一个参数引用由osMessage定义的队列,第二个参数线程id或NULL,返回值消失队列id。
osStatus osMessageDelete (osMessageQId queue_id)
删除队列,被删除的队列便不能被使用了,参数消息队列id,返回值错误码。
osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec)
向队列尾部发送一个队列消息,消息以拷贝的形式入队,第一个参数消息队列id,第二个参数要发送的数据数据大小4个字节,第三个参数超时时间。
osEvent osMessageGet (osMessageQId queue_id, uint32_t millisec)
从队列中获取一个消息并且把消息从队列中删除,第一个参数消息队列id,第二个参数超时时间,返回值osEvent结构体里面的信息。
osEvent osMessagePeek (osMessageQId queue_id, uint32_t millisec)
也是从队列中接收一个数据但是并不把数据从队列中删除,第一个参数时队列id,第二个参数是超时时间,返回值osEvent结构体中里面的值。
uint32_t osMessageWaiting(osMessageQId queue_id)
查询队列中有效数据的个数,参数队列id,返回值个数。
对于消息队列的我们可以实现两个任务间的通信,我们来用程序实现一下:

/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN StartDefaultTask */osEvent ret;/* Infinite loop */for(;;){if(Key_Value == KEY_UP){Key_Value = 0;ret.status = osMessagePut(QueueLED1Handle,'A',portMAX_DELAY);if(ret.status != osOK){printf("send failed!\n");}}else if(Key_Value == KEY_DOWN){Key_Value = 0;ret.status = osMessagePut(QueueLED2Handle,'B',portMAX_DELAY);if(ret.status != osOK){printf("send failed!\n");}}osDelay(100);}/* USER CODE END StartDefaultTask */
}/* USER CODE BEGIN Header_TaskLED1 */
/**
* @brief Function implementing the LED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_TaskLED1 */
void TaskLED1(void const * argument)
{/* USER CODE BEGIN TaskLED1 */osMutexWait(myMutex01Handle,portMAX_DELAY);//等待互斥锁printf("LED1 Running\n");osMutexRelease(myMutex01Handle);//释放互斥锁osEvent ret;/* Infinite loop */for(;;){ret = osMessageGet(QueueLED1Handle,osWaitForever); //等待接收数据,osWaitForever一直等待if(ret.status == osEventMessage) //判断osEvent结构体中的status结构体变量是消息事件嘛{printf("LED1Task recv data:%c\n",ret.value.v); //若是的,则将osEvent中的value联合体中的v打印出来}else{printf("error:%#x\n",ret.status); //若不是,则将osEvent中的status结构体变量所得到的值什么事件打印出来}HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);osDelay(200);}/* USER CODE END TaskLED1 */
}/* USER CODE BEGIN Header_TaskLED2 */
/**
* @brief Function implementing the LED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_TaskLED2 */
void TaskLED2(void const * argument)
{/* USER CODE BEGIN TaskLED2 */osMutexWait(myMutex01Handle,portMAX_DELAY);//等待互斥锁printf("LED2 Running\n");osMutexRelease(myMutex01Handle);//释放互斥锁osEvent ret;/* Infinite loop */for(;;){ret = osMessageGet(QueueLED2Handle,osWaitForever); //等待接收数据,osWaitForever一直等待if(ret.status == osEventMessage) //判断osEvent结构体中的status结构体变量是消息事件嘛{printf("LED2Task recv data:%c\n",ret.value.v); //若是的,则将osEvent中的value联合体中的v打印出来}else{printf("error:%#x\n",ret.status); //若不是,则将osEvent中的status结构体变量所得到的值什么事件打印出来}HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);osDelay(1000);}/* USER CODE END TaskLED2 */
}
5.信号量
信号量是一种用于多任务协调和同步的机制。它可以用来管理共享资源的访问,避免多个任务同时访问同一个资源而导致的冲突和竞争条件。FreeRTOS中的信号量是一个计数器,可以被多个任务共享。它有两种类型:二进制信号量和计数信号量。二进制信号量只有两个状态:0和1。它可以用来实现互斥访问,即只允许一个任务访问共享资源。当一个任务获取到二进制信号量后,其他任务就无法获取该信号量,直到该任务释放信号量为止。计数信号量可以有多个状态,取决于初始化时的计数值。它可以用来实现资源的共享访问,即允许多个任务同时访问共享资源,但有一定的限制。当计数信号量的值为0时,任务需要等待,直到有其他任务释放信号量为止。当计数信号量的值大于0时,任务可以获取信号量并继续执行。下面我们来看看有关信号量的函数:
osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count)
创建一个信号量,第一个参数引用由osSemahoreDef定义的信号量,第二个参数信号量的数量。返回值成功返回信号量id失败返回0。
osStatus osSemaphoreDelete (osSemaphoreId semaphore_id)
删除信号量,参数信号量id,返回值错误码。
osStatus osSemaphoreRelease (osSemaphoreId semaphore_id)
释放信号量,对于二值信号量和计数信号量都是加1,参数信号量id,返回值错误码。
int32_t osSemaphoreWait (osSemaphoreId semaphore_id, uint32_t millisec)
获取信号量,对于二值信号量和计数信号量都是减1,第一个参数信号量id,第二个参数超时时间,返回值有效资源个数。
二值信号量可以用来调节多个任务之间相应执行程序,下面我们来使用二值信号量来实现两个LED灯和蜂鸣器依次点亮熄灭响不响:

void TaskBEEP(void const * argument)
{/* USER CODE BEGIN TaskBEEP */osMutexWait(myMutex01Handle,portMAX_DELAY);//等待互斥锁printf("BEEP Running\n");osMutexRelease(myMutex01Handle);//释放互斥锁/* Infinite loop */for(;;){osSemaphoreWait(BinarySem3Handle,osWaitForever); //获取二值信号量3的值,所以该二值信号量变成0,即上锁LED_Control(LED2,OFF);BEEP_Control(ON);osDelay(200);BEEP_Control(OFF);osDelay(800);osSemaphoreRelease(BinarySem1Handle); //释放二值信号量1的值,所以该二值信号量变成1,即解锁,解锁后后面的进程就可以获取该二值信号量了osDelay(100);}/* USER CODE END TaskBEEP */
}/* USER CODE BEGIN Header_TaskLED1 */
/**
* @brief Function implementing the LED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_TaskLED1 */
void TaskLED1(void const * argument)
{/* USER CODE BEGIN TaskLED1 */osMutexWait(myMutex01Handle,portMAX_DELAY);//等待互斥锁printf("LED1 Running\n");osMutexRelease(myMutex01Handle);//释放互斥锁/* Infinite loop */for(;;){osSemaphoreWait(BinarySem1Handle,osWaitForever); //获取二值信号量1的值,所以该二值信号量变成0,即上锁LED_Control(LED1,ON);osDelay(1000);osSemaphoreRelease(BinarySem2Handle); //释放二值信号量2的值,所以该二值信号量变成1,即解锁,解锁后后面的进程就可以获取该二值信号量了osDelay(100);}/* USER CODE END TaskLED1 */
}/* USER CODE BEGIN Header_TaskLED2 */
/**
* @brief Function implementing the LED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_TaskLED2 */
void TaskLED2(void const * argument)
{/* USER CODE BEGIN TaskLED2 */osMutexWait(myMutex01Handle,portMAX_DELAY);//等待互斥锁printf("LED2 Running\n");osMutexRelease(myMutex01Handle);//释放互斥锁/* Infinite loop */for(;;){osSemaphoreWait(BinarySem2Handle,osWaitForever); //获取二值信号量2的值,所以该二值信号量变成0,即上锁LED_Control(LED1,OFF);LED_Control(LED2,ON);osDelay(1000);osSemaphoreRelease(BinarySem3Handle); //释放二值信号量3的值,所以该二值信号量变成1,即解锁,解锁后后面的进程就可以获取该二值信号量了osDelay(100);}
对于计数信号量,通过获取和释放信号量来可以实现计数信号量的值的减小和增加,来控制共享资源的使用。

void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN StartDefaultTask *//* Infinite loop */for(;;){osDelay(100);}/* USER CODE END StartDefaultTask */
}/* USER CODE BEGIN Header_PutTask */
/**
* @brief Function implementing the PUT thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_PutTask */
void PutTask(void const * argument)
{/* USER CODE BEGIN PutTask */osStatus ret;/* Infinite loop */for(;;){if(Key_Value == KEY_UP) //车辆出车位,车位增加{Key_Value = 0;ret = osSemaphoreRelease(CountingSem1Handle);//释放信号量,信号量加1if(ret == osOK){printf("车位数量为:%d\n",osSemaphoreGetCount(CountingSem1Handle));//得到计数信号量的值}else{printf("车位数量为:%d\n",osSemaphoreGetCount(CountingSem1Handle));//得到计数信号量的值}}osDelay(100);}/* USER CODE END PutTask */
}/* USER CODE BEGIN Header_GetTask */
/**
* @brief Function implementing the GET thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_GetTask */
void GetTask(void const * argument)
{/* USER CODE BEGIN GetTask */int32_t ret;/* Infinite loop */for(;;){if(Key_Value == KEY_DOWN)//车辆进车位,车位减少{Key_Value = 0;ret = osSemaphoreWait(CountingSem1Handle,osWaitForever);//获取信号量,没有获取一直等待if(ret == osOK){printf("车位数量为:%d\n",osSemaphoreGetCount(CountingSem1Handle));//得到计数信号量的值}else{printf("车位数量为:%d\n",osSemaphoreGetCount(CountingSem1Handle));//得到计数信号量的值}}osDelay(100);}
