FreeRTOS 是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制。FreeRTOS 提供了实时操作系统所需的所有功能,包括资源管理、同步、任务通信等。
FreeRTOS 是用 C 和汇编来写的,其中绝大部分都是用 C 语言编写的,只有极少数的与处理器密切相关的部分代码才是用汇编写的,FreeRTOS 结构简洁,可读性很强!最主要的是非常适合初次接触嵌入式实时操作系统学生、嵌入式系统开发人员和爱好者学习。
最新版本 V9.0.0(2016年),尽管现在 FreeRTOS 的版本已经更新到 V10.4.1 了,但是我们还是选择 V9.0.0,因为内核很稳定,并且网上资料很多,因为 V10.0.0 版本之后是亚马逊收购了FreeRTOS之后才出来的版本,主要添加了一些云端组件,一般采用 V9.0.0 版本足以。
3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置
4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire
在 System Core
中选择 SYS
,对 Timebase Source
进行设置,选择 TIM1
作为HAL库的时基(除了 SysTick
外都可以)。
在基于STM32 HAL的项目中,一般需要维护的 “时基” 主要有2个:
而这些 “时基” 该去如何维护,主要分为两种情况考虑:
裸机运行:
可以通过 SysTick
(滴答定时器)或 (TIMx
)定时器 的方式来维护 SYS Timebase Source
,也就是HAL库中的 uwTick
,这是HAL库中维护的一个全局变量。在裸机运行的情况下,我们一般选择默认的 SysTick
(滴答定时器) 方式即可,也就是直接放在 SysTick_Handler()
中断服务函数中来维护。
带OS运行:
前面提到的 SYS Timebase Source
是STM32的HAL库中的新增部分,主要用于实现 HAL_Delay()
以及作为各种 timeout 的时钟基准。
在使用了OS(操作系统)之后,OS的运行也需要一个时钟基准(简称“时基”),来对任务和时间等进行管理。而OS的这个 时基 一般也都是通过 SysTick
(滴答定时器) 来维护的,这时就需要考虑 “HAL的时基” 和 “OS的时基” 是否要共用 SysTick
(滴答定时器) 了。
如果共用SysTick,当我们在CubeMX中选择启用FreeRTOS之后,在生成代码时,CubeMX一定会报如下提示:
强烈建议用户在使用FreeRTOS的时候,不要使用
SysTick
(滴答定时器)作为 “HAL的时基”,因为FreeRTOS要用,最好是要换一个!!!如果共用,潜在一定风险。
在 Middleware
中选择 FREERTOS
设置,并选择 CMSIS_V1
接口版本
CMSIS是一种接口标准,目的是屏蔽软硬件差异以提高软件的兼容性。RTOS v1使得软件能够在不同的实时操作系统下运行(屏蔽不同RTOS提供的API的差别),而RTOS v2则是拓展了RTOS v1,兼容更多的CPU架构和实时操作系统。因此我们在使用时可以根据实际情况选择,如果学习过程中使用STM32F1、F4等单片机时没必要选择RTOS v2,更高的兼容性背后时更加冗余的代码,理解起来比较困难。
在 Config parameters
进行具体参数配置。
Kernel settings:
Enabled
:RTOS使用抢占式调度器;Disabled:RTOS使用协作式调度器(时间片)。1000
,即周期就是1ms。RTOS系统节拍中断的频率,单位为HZ。128
Words,那么真正的堆栈大小就是 128*4 = 512 Byte。Enabled
空闲任务放弃CPU使用权给其他同优先级的用户任务。Memory management settings:
Dynamic/Static
支持动态/静态内存申请heap_4
。Hook function related definitions:
Run time and task stats gathering related definitions:
Co-routine related definitions:
Software timer definitions:
Interrupt nesting behaviour configuration:
要想使用任务通知必须在 Config parameters
中把 USE_TASK_NOTIFICATIONS
选择 Enabled
来使能。
Dynamic
动态内存创建在 System Core
中选择 GPIO
设置。
在右边图中找到按键对应引脚,选择 GPIO_Input
。
查看 STM32CubeMX学习笔记(6)——USART串口使用
输入项目名和项目路径
选择应用的 IDE 开发环境 MDK-ARM V5
每个外设生成独立的 ’.c/.h’
文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
点击 GENERATE CODE 生成代码
FreeRTOS 从 V8.2.0 版本开始提供任务通知这个功能,每个任务都有 一个 32 位 的通知值,在大多数情况下,任务通知可以 替代二值信号量、计数信号量、事件组,也可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值)。
相对于以前使用 FreeRTOS 内核通信的资源,必须创建队列、二进制信号量、计数信号量或事件组的情况,使用任务通知显然更灵活。按照 FreeRTOS 官方的说法,使用任务通知比通过信号量等 ICP 通信方式解除阻塞的任务要快 45%,并且更加省 RAM 内存空间(使用 GCC 编译器,-o2 优化级别),任务通知的使用无需创建队列。 想要使用任务通知,必须将
FreeRTOSConfig.h
中的宏定义configUSE_TASK_NOTIFICATIONS
设置为1
,其实FreeRTOS 默认是为 1 的,所以任务通知是默认使能的。
FreeRTOS 提供以下几种方式发送通知给任务 :
- 发送通知给任务, 如果有通知未读,不覆盖通知值。
- 发送通知给任务,直接覆盖通知值。
- 发送通知给任务,设置通知值的一个或者多个位 ,可以当做事件组来使用。
- 发送通知给任务,递增通知值,可以当做计数信号量使用。
通过对以上任务通知方式的合理使用,可以在一定场合下替代 FreeRTOS 的信号量,队列、事件组等。
当然,凡是都有利弊,不然的话 FreeRTOS 还要内核的 IPC 通信机制干嘛,消息通知虽然处理更快,RAM 开销更小,但也有以下限制 :
- 只能有一个任务接收通知消息,因为必须指定接收通知的任务。
- 只有等待通知的任务可以被阻塞,发送通知的任务,在任何情况下都不会因为发送失败而进入阻塞态。
由于任务通知的数据结构包含在任务控制块中,只要任务存在,任务通知数据结构就已经创建完毕,可以直接使用,所以使用的时候很是方便。
任务通知可以在任务中向指定任务发送通知,也可以在中断中向指定任务发送通知,FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量 ulNotifiedValue 就是这个通知值。只有在任务中可以等待通知,而不允许在中断中等待通知。如果任务在等待的通知暂时无效,任务会根据用户指定的阻塞超时时间进入阻塞状态,我们可以将等待通知的任务看作是消费者;其它任务和中断可以向等待通知的任务发送通知,发送通知的任务和中断服务函数可以看作是生产者,当其他任务或者中断向这个任务发送任务通知,任务获得通知以后,该任务就会从阻塞态中解除,这与 FreeRTOS 中内核的其他通信机制一致。
向指定的任务发送一个任务通知,带有通知值并且用户可以指定通知值的发送方式。该函数可以在中断函数中使用。
函数 | int32_t osSignalSet (osThreadId thread_id, int32_t signal) |
---|---|
参数 | thread_id: 接收通知的任务ID signal: 任务通知值,一般按位操作数字,一个事件用一个类似这样的值表示(即0x0001,0x0002,0x0004,0x0008,0x0010…) |
返回值 | 错误码 |
用于实现等待任务通知,根据用户指定的参数的不同,可以灵活的用于实现轻量级的消息队列队列、二值信号量、计数信号量和事件组功能,并带有超时等待。函数不允许在中断函数中使用。
函数 | osEvent osSignalWait (int32_t signals, uint32_t millisec) |
---|---|
参数 | signals: 接收完成后等待被清零的数据位,比如一个任务状态切换,由四个不同的事件分别影响着,通知osSignalSet中的signal分别为0x0001,0x0002,0x0004,0x0008,那么接收端osSignalWait 中的signals应该为0x000F(0x0001|0x0002|0x0004|0x0008) millisec: 等待超时时间,单位为系统节拍周期。宏 pdMS_TO_TICKS 用于将单位毫秒转化为系统节拍数 |
返回值 | 错误码 |
按下按键1触发任务通知到接收任务1Receive1Task,按下按键2触发任务通知到接收任务2Receive2Task。
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include
#include
/* 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 ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;
osThreadId defaultTaskHandle;
osThreadId SendHandle;
osThreadId Receive1Handle;
osThreadId Receive2Handle;
/* USER CODE BEGIN PV */
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位 0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位 1
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
void StartDefaultTask(void const * argument);
void SendTask(void const * argument);
void Receive1Task(void const * argument);
void Receive2Task(void const * argument);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* 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 defaultTask */
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
/* definition and creation of Send */
osThreadDef(Send, SendTask, osPriorityIdle, 0, 128);
SendHandle = osThreadCreate(osThread(Send), NULL);
/* definition and creation of Receive1 */
osThreadDef(Receive1, Receive1Task, osPriorityIdle, 0, 128);
Receive1Handle = osThreadCreate(osThread(Receive1), NULL);
/* definition and creation of Receive2 */
osThreadDef(Receive2, Receive2Task, osPriorityIdle, 0, 128);
Receive2Handle = osThreadCreate(osThread(Receive2), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
/* Start scheduler */
osKernelStart();
/* We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART1 Initialization Function
* @param None
* @retval None
*/
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/**
* Enable DMA controller clock
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel4_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
/* DMA1_Channel5_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, LED_G_Pin|LED_B_Pin|LED_R_Pin, GPIO_PIN_SET);
/*Configure GPIO pin : KEY2_Pin */
GPIO_InitStruct.Pin = KEY2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : KEY1_Pin */
GPIO_InitStruct.Pin = KEY1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : LED_G_Pin LED_B_Pin LED_R_Pin */
GPIO_InitStruct.Pin = LED_G_Pin|LED_B_Pin|LED_R_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/* USER CODE BEGIN 4 */
/**
* @brief 重定向c库函数printf到USARTx
* @retval None
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
/**
* @brief 重定向c库函数getchar,scanf到USARTx
* @retval None
*/
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
/* USER CODE END 4 */
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN 5 */
/* Infinite loop */
for(;;)
{
osDelay(1);
}
/* USER CODE END 5 */
}
/* USER CODE BEGIN Header_SendTask */
/**
* @brief Function implementing the Send thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_SendTask */
void SendTask(void const * argument)
{
/* USER CODE BEGIN SendTask */
/* Infinite loop */
for(;;)
{
//如果 KEY1 被按下
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_SET)
{
printf("KEY1 down\n");
/* 触发一个事件 1 */
osSignalSet(Receive1Handle, KEY1_EVENT);
}
//如果 KEY2 被按下
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_SET)
{
printf("KEY2 down\n");
/* 触发一个事件 2 */
osSignalSet(Receive2Handle, KEY2_EVENT);
}
osDelay(100);
}
/* USER CODE END SendTask */
}
/* USER CODE BEGIN Header_Receive1Task */
/**
* @brief Function implementing the Receive1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Receive1Task */
void Receive1Task(void const * argument)
{
/* USER CODE BEGIN Receive1Task */
osEvent event;
/* Infinite loop */
for(;;)
{
event = osSignalWait(KEY1_EVENT, /* 接收任务感兴趣的事件 */
osWaitForever); /* 指定超时事件,一直等 */
if(event.status == osEventSignal) //如果接收到通知
{
if(event.value.signals & KEY1_EVENT)//接收的通知为KEY1_EVENT
{
printf("Receive1Task\n");
}
}
}
/* USER CODE END Receive1Task */
}
/* USER CODE BEGIN Header_Receive2Task */
/**
* @brief Function implementing the Receive2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Receive2Task */
void Receive2Task(void const * argument)
{
/* USER CODE BEGIN Receive2Task */
osEvent event;
/* Infinite loop */
for(;;)
{
event = osSignalWait(KEY2_EVENT, /* 接收任务感兴趣的事件 */
osWaitForever); /* 指定超时事件,一直等 */
if(event.status == osEventSignal) //如果接收到通知
{
if(event.value.signals & KEY2_EVENT)//接收的通知为KEY2_EVENT
{
printf("Receive2Task\n");
}
}
}
/* USER CODE END Receive2Task */
}
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM1 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM1) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
链接:https://pan.baidu.com/s/1qS0uYFJ1YgMi2G0nAVaB8g 提取码:r53a
用户代码要加在 USER CODE BEGIN N
和 USER CODE END N
之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。
• 由 Leung 写于 2022 年 1 月 5 日
• 参考:CMSIS中的FreeRTOS第一篇——任务通知:osSignalSet和osSignalWait
FreeRTOS记录(五、FreeRTOS任务通知)
STM32CubeIDE(十一):FreeRTOS选项中Disable、CMSIS_V1和CMSIS_V2的区别
HAL库中的 SYS Timebase Source 和 SysTick_Handler()