STM32+Clion多线程开发
目录
创建多线程
freertos.c
main.cpp
main_app.h
二值信号量
相关API介绍
(1) osSemaphoreCreate
(2)osSemaphoreDelete
(3)osSemaphoreRelease
(4)osSemaphoreWait
实际使用
创建信号量(freertos.c)
在头文件中外部引用(freertos_inc.h)
main.c
关于clion使用printf,参考【教程】手把手教你用Clion进行STM32开发【如何优雅の进行嵌入式开发】 – 知乎 (zhihu.com)
创建多线程
本文的程序通过STM32CubeMX+Clion完成,使用单片机为Stm32f411CEU6
为了让自己的程序和生成的程序分开,方便后续的管理,我我们可以新建一个Src目录或者User目录,然后在目录里新建main_app.cpp/main_app.h,如果有其他传感器功能的实现或者算法的实现可以再新建一个目录放到里面,新加的目录记得在CMakelist里添加路径。
在include和file里都要添加,尤其是file,不添加也不会报错容易漏。
然后要知道程序是在哪里运行的,不在main.c中而是在下面这个文件里core/src/freertos.c,在这里写上自己的主程序函数就可以成功进入
freertos.c
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
main_app();
vTaskDelete(defaultTaskHandle);
/* USER CODE END StartDefaultTask */
}
接下来创建第一个线程,这里使用cmsis_os.h,记得包含到头文件里,然后按照以下格式创建即可,要注意的就是设置的栈大小和优先级,还有这个任务main_app执行完后就结束了,所以后续需要使用的变量或者类,不要放到函数中而是要放在前面,创建全局变量并初始化。
main.cpp
SensorManager sensorManager;//全局变量,记得初始化,在这里直接初始化也可以
osThreadId_t SensorTaskHandle;
void SensorTask(void *pvParameters) {
for(;;) {
LED_B_TogglePin;
osDelay(1000);
}
}
osThreadId_t LCDTaskHandle;
void LCDTask(void *pvParameters) {
for(;;) {
LED_R_TogglePin;
osDelay(1000);
}
}
void main_app()
{
const osThreadAttr_t SensorTask_attributes = {
.name = "SensorTask",
.stack_size = 128 * 8,//栈空间
.priority = (osPriority_t) osPriorityNormal,
};
SensorTaskHandle = osThreadNew(SensorTask, nullptr, &SensorTask_attributes);
const osThreadAttr_t LCDTask_attributes = {
.name = "LCDTask",
.stack_size = 128 * 8,
.priority = (osPriority_t) osPriorityNormal,
};
LCDTaskHandle = osThreadNew(LCDTask, nullptr, &LCDTask_attributes);
// 启动调度器
printf("end\n");
}
main_app.h
#ifndef STM32_DEMO2_F4_MAIN_APP_H
#define STM32_DEMO2_F4_MAIN_APP_H
#ifdef __cplusplus
extern "C" {
#endif
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "cmsis_os.h"
#include "main.h"
#include "gpio.h"
#include "usart.h"
void main_app(void);
#ifdef __cplusplus
}
#endif
#endif //STM32_DEMO2_F4_MAIN_APP_H
二值信号量
在多线程时当多个线程同时访问一个变量时,可能会造成冲突,比如当两个线程同时使用printf时,出现冲突,结果就是打印乱码,因此需要使用信号量来避免这种情况,在使用串口的问题中,我使用二值信号量。
相关API介绍
(1) osSemaphoreCreate
用于创建一个二值信号量,并返回一个ID。
函数 osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count)
参数 semaphore_def: 引用由osSemaphoreDef定义的信号量 count: 信号量数量
返回值 成功返回信号量ID,失败返回0
(2)osSemaphoreDelete
用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。如果有任务阻塞在该信号量上,那么不要删除该信号量。
函数 osStatus osSemaphoreDelete (osSemaphoreId semaphore_id)
参数 semaphore_id: 信号量ID
返回值 错误码
(3)osSemaphoreRelease
用于释放信号量的宏。释放的信号量对象必须是已经被创建的,可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数 xSemaphoreCreateRecursiveMutex() 创建的递归互斥量。可用在中断服务程序中。
函数 osStatus osSemaphoreRelease (osSemaphoreId semaphore_id)
参数 semaphore_id: 信号量ID
返回值 错误码
(4)osSemaphoreWait
用于获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。可用在中断服务程序中。
函数 int32_t osSemaphoreWait (osSemaphoreId semaphore_id, uint32_t millisec)
参数 semaphore_id: 信号量ID millisec:等待信号量可用的最大超时时间,单位为 tick(即系统节拍周期)。 如果宏 INCLUDE_vTaskSuspend 定义为 1 且形参 xTicksToWait 设置为 portMAX_DELAY ,则任务将一直阻塞在该信号量上(即没有超时时间)
返回值 错误码
实际使用
创建信号量(freertos.c)
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
osSemaphoreId sem_uart6_rx;
osSemaphoreId sem_uart6_tx;
/* USER CODE END PD */
void MX_FREERTOS_Init(void) {
// Create a semaphore for USB RX, and start with no tokens by removing the starting one.
osSemaphoreDef(sem_usb_rx);
sem_uart6_rx = osSemaphoreNew(1, 0, osSemaphore(sem_usb_rx));
// Create a semaphore for USB TX
osSemaphoreDef(sem_usb_tx);
sem_uart6_tx = osSemaphoreNew(1, 1, osSemaphore(sem_usb_tx));
/* USER CODE END RTOS_SEMAPHORES */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
}
在头文件中外部引用(freertos_inc.h)
#ifndef STM32_DEMO2_F4_FREERTOS_INC_H
#define STM32_DEMO2_F4_FREERTOS_INC_H
#ifdef __cplusplus
extern "C" {
#endif
// List of semaphores
extern osSemaphoreId sem_uart6_rx;
extern osSemaphoreId sem_uart6_tx;
// List of Tasks
/*--------------------------------- System Tasks -------------------------------------*/
extern osThreadId_t defaultTaskHandle; // Usage: 4000 Bytes stack
/*---------------------------------- User Tasks --------------------------------------*/
extern osThreadId_t SensorTaskHandle; // Usage: 4000 Bytes stack
extern osThreadId_t LCDTaskHandle; // Usage: 4000 Bytes stack
/*---------------- 24.1K (used) / 64K (for FreeRTOS) / 128K (total) ------------------*/
#ifdef __cplusplus
}
#endif
#endif //STM32_DEMO2_F4_FREERTOS_INC_H
main.c
osThreadId_t SensorTaskHandle;
void SensorTask(void *pvParameters) {
osSemaphoreAcquire(sem_uart6_tx,osWaitForever);
printf("enter SensorTask\n");
osSemaphoreRelease(sem_uart6_tx);
for(;;) {
LED_B_TogglePin;
osDelay(1000);
}
}
osThreadId_t LCDTaskHandle;
void LCDTask(void *pvParameters) {
osSemaphoreAcquire(sem_uart6_tx,osWaitForever);
printf("enter LCDTask\n");
osSemaphoreRelease(sem_uart6_tx);
for(;;) {
LED_R_TogglePin;
osDelay(1000);
}
}
void main_app()
{
const osThreadAttr_t SensorTask_attributes = {
.name = "SensorTask",
.stack_size = 128 * 8,//栈空间
.priority = (osPriority_t) osPriorityNormal,
};
SensorTaskHandle = osThreadNew(SensorTask, nullptr, &SensorTask_attributes);
const osThreadAttr_t LCDTask_attributes = {
.name = "LCDTask",
.stack_size = 128 * 8,
.priority = (osPriority_t) osPriorityNormal,
};
LCDTaskHandle = osThreadNew(LCDTask, nullptr, &LCDTask_attributes);
// 启动调度器
printf("end\n");
}
关于clion使用printf,参考【教程】手把手教你用Clion进行STM32开发【如何优雅の进行嵌入式开发】 – 知乎 (zhihu.com)
作者:稚晖教信徒