西安优秀的集团门户网站建设服务商,网站空间一年多少钱,移动端软件开发,wordpress远程 媒体库一、概述 无论是新手还是大佬#xff0c;基于STM32单片机的开发#xff0c;使用STM32CubeMX都是可以极大提升开发效率的#xff0c;并且其界面化的开发#xff0c;也大大降低了新手对STM32单片机的开发门槛。 本文主要讲述STM32芯片FreeRTOS信号标志、互斥锁和信号…一、概述 无论是新手还是大佬基于STM32单片机的开发使用STM32CubeMX都是可以极大提升开发效率的并且其界面化的开发也大大降低了新手对STM32单片机的开发门槛。 本文主要讲述STM32芯片FreeRTOS信号标志、互斥锁和信号量的配置及其相关知识。
二、软件说明 STM32CubeMX是ST官方出的一款针对ST的MCU/MPU跨平台的图形化工具支持在Linux、MacOS、Window系统下开发其对接的底层接口是HAL库另外习惯于寄存器开发的同学们也可以使用LL库。STM32CubeMX除了集成MCU/MPU的硬件抽象层另外还集成了像RTOS文件系统USB网络显示嵌入式AI等中间件这样开发者就能够很轻松的完成MCU/MPU的底层驱动的配置留出更多精力开发上层功能逻辑能够更进一步提高了嵌入式开发效率。 演示版本 6.1.0
三、FreeRTOS功能简介 嵌入式初学者一般使用的是裸机开发而大多数所谓的进阶课程就是使用操作系统开发。其实两者并不存在很大的差距使用操作系统更多是在裸机开发的基础上限制在操作系统要求的框架下进行开发同时需要留意操作系统的一些特性以防止出现问题。具体操作系统开发与裸机开发的区别如下
维度FreeRTOSRTOS 开发裸机开发轮询 / 中断驱动任务管理多任务并行抢占式 / 协作式自动调度单线程轮询 中断处理手动协调任务优先级实时性高可精确控制任务执行顺序和响应时间依赖中断优先级和轮询顺序复杂场景易卡顿系统复杂度适合复杂逻辑如多外设、通信协议、用户界面适合简单逻辑如单一传感器采集、LED 控制代码结构模块化任务独立通过 IPC 通信线性代码 全局变量耦合度高资源占用需额外内存栈空间、内核数据结构资源占用极小仅代码和必要数据开发成本学习成本较高需理解 RTOS 概念门槛低适合快速实现简单功能可维护性任务隔离性好扩展新功能更方便功能扩展可能需修改全局逻辑维护困难 提到操作系统第一反应也是大家最早接触的应该就是Windows系统了当然新生代可能第一接触的是苹果的IOS系统或华为的鸿蒙系统但由于Windows的交互友好性让大家很难感知到它的存在而学习了嵌入式后又知道了Linux这个天花板般的操作系统存在虽然是个开源系统但其体量也让大多数人忘而生畏从而使操作系统蒙上一层神秘的面纱。而今天的主角FreeRTOS作为小体量且开源的操作系统正是敲开操作系统神秘大门的砖头带着我们了解其中的奥妙。 FreeRTOS 是一款开源实时操作系统RTOS专为嵌入式系统设计尤其适用于资源受限的微控制器MCU。由 Richard Barry 开发并发布首个版本V1.0最初名为 FreeRTOS Kernel旨在提供轻量级、可移植的多任务处理能力。早期版本以代码简洁、易于移植为特点迅速在嵌入式社区流行。2012 年成立 Real Time Engineers Ltd 公司推动 FreeRTOS 商业化推出付费技术支持和扩展组件如文件系统、TCP/IP 栈。逐步支持更多硬件平台如 ARM Cortex-M、ESP32、RISC-V 等并构建生态系统包括中间件和工具链。2016 年亚马逊AWS收购 FreeRTOS将其纳入 IoT 战略推出 AWS IoT Greengrass for FreeRTOS强化物联网IoT连接能力如 MQTT、OTA 升级。最新版本截至 2025 年为 V202212.00持续优化实时性能、安全性和云集成并提供免费的认证服务如功能安全认证 IEC 61508。 FreeRTOS 以实时性、轻量级、可配置性为核心功能模块包括
任务调度器
抢占式调度支持多任务按优先级运行高优先级任务可中断低优先级任务确保实时响应。协作式调度可选任务主动释放控制权适合对实时性要求不高的场景。支持 任务优先级最多 32 级可配置和 时间片轮转同优先级任务分时执行。
任务间通信IPC
队列Queue任务 / 中断间传递数据支持先进先出FIFO或优先级队列。信号量Semaphore包括二进制信号量、计数信号量用于资源同步与互斥如 mutex 互斥信号量。事件组Event Group实现任务间多事件同步。
内存管理
提供多种内存分配策略 – 静态分配编译时分配固定内存适合关键任务避免内存碎片。 – 动态分配运行时动态申请内存类似 C 语言 malloc需注意碎片问题。支持用户自定义内存管理方案。
定时器与中断管理
软件定时器基于系统时钟的周期性或一次性定时器支持回调函数。中断安全接口允许在中断服务程序ISR中安全访问 RTOS 资源如队列、信号量。
可配置性与移植性
通过头文件FreeRTOSConfig.h配置内核参数如任务数量、栈大小、调度器行为灵活适配不同硬件。提供标准接口移植到新平台只需实现少量汇编代码如上下文切换。
生态与扩展组件
中间件集成文件系统如 FATFS、TCP/IP 栈LwIP、USB 协议栈、图形界面GUI等。物联网支持通过 AWS IoT 组件实现设备与云端通信支持 MQTT、TLS 加密、设备管理等。安全与认证提供功能安全版本如 FreeRTOS-Safety通过 IEC 61508、ISO 26262 等认证适合工业、汽车电子等场景。 除了FreeRTOS其他还有很多其他优秀的嵌入式操作系统其中就包括很多人学校里会学到的uC/OS-IIFreeRTOS与其他常见的嵌入式操作系统对比如下
维度FreeRTOSuC/OS-IIRTXARMRIOT OS许可证开源GPLv2修改需开源/ 商业许可开源需购买商业许可用于产品商业许可需授权开源BSD-2-Clause实时性抢占式调度微秒级响应抢占式调度支持优先级继承抢占式调度支持 CMSIS-RTOS 标准抢占式 协作式适合 IoT 低功耗代码复杂度简洁核心代码约 10k 行 C 语言模块化设计代码量较大集成于 Keil 工具链抽象层完善面向 IoT轻量级10KB 内存生态系统丰富AWS IoT、中间件、社区支持成熟工业、汽车领域案例多与 ARM 工具链深度整合Keil、IAR专注 IoT支持传感器网络和低功耗协议资源占用极小ROM: ~4KBRAM: ~1KB中等ROM: ~10KBRAM: ~2KB中等依赖组件数量极轻量适合 8/16 位 MCU典型应用IoT 设备、消费电子、工业控制医疗设备、航空航天、汽车电子嵌入式系统开发ARM Cortex-M 系列物联网边缘设备、传感器节点 FreeRTOS 凭借轻量、开源、易移植的特性成为嵌入式领域最流行的 RTOS 之一尤其适合 IoT 和中小型实时系统。其与 AWS 的深度整合进一步强化了物联网能力而功能安全认证版本则拓展了工业和汽车电子市场。相比裸机开发它能显著提升复杂系统的设计效率和实时性但需权衡资源占用和学习成本。对于开发者而言若项目需要多任务调度、实时响应或未来扩展FreeRTOS 是理想选择若需求简单或资源受限裸机开发仍具优势。
四、FreeRTOS配置及应用
4.1 FreeRTOS配置说明 具体参考《【工具使用】STM32CubeMX-FreeRTOS操作系统-基础篇》这里就不再赘述了。
4.2 接口说明 使用CubeMX生成的工程会将FreeRTOS的接口再封装一层统一接口CMSIS-RTOS这是 ARM 定义的一套 RTOS 抽象层标准旨在通过统一接口屏蔽不同 RTOS 的差异。这里我们先来认识几个比较常用的接口。
4.2.1 信号标志
1. osSignalSet - 设置线程的信号标志
int32_t osSignalSet(osThreadId thread_id, int32_t signals);向指定线程thread_id的信号标志寄存器中 按位设置 一个或多个信号标志signals。信号标志是线程的私有资源每个线程有一组独立的标志位通常是 32 位整数用于表示异步事件的发生。 参数 thread_id目标线程的 ID通过 osThreadCreate 或 osThreadGetId 获取。 signals要设置的信号标志如 0x01、0x02 等可通过按位或组合多个标志。 返回值 osOK设置成功。 osErrorOS操作失败如线程 ID 无效。
2. osSignalClear - 清除线程的信号标志
int32_t osSignalClear(osThreadId thread_id, int32_t signals);从指定线程的信号标志寄存器中 按位清除 一个或多个信号标志signals。返回值为清除前的标志位状态可用于判断哪些标志被清除前已设置。 参数 thread_id目标线程的 ID。 signals要清除的信号标志与 osSignalSet 中的标志位对应。 返回值 清除前的信号标志值若成功。 0x80000000参数无效如线程 ID 不存在。
注通常在目标线程处理完信号后调用避免重复处理同一事件。清除操作是线程安全的由RTOS内核保证原子性。
3. osSignalWait - 等待线程的信号标志
osEvent osSignalWait(int32_t signals, uint32_t millisec);使当前线程进入 阻塞状态等待自身的信号标志满足指定条件。可指定等待所有标志位均被设置signals 非零按位与匹配或任意一个标志位被设置signals 为 0。 参数 signals 非零值等待所有指定位均被设置如 signals0x03 表示等待标志 0 和 1 同时置位。 0等待任意一个标志位被设置。 millisec超时时间单位毫秒0 表示永不超时。 返回值 osEvent 结构体 -status状态码如 osEventSignal 表示信号触发osEventTimeout 表示超时。 -value.signals实际触发的信号标志值仅当 .status 为 osEventSignal 时有效。
使用流程
当前线程调用 osSignalWait 阻塞等待信号。其他线程通过 osSignalSet 触发信号。若满足条件或超时当前线程恢复运行通过返回值判断事件类型并处理。
4.2.2 互斥锁
1. 创建互斥锁osMutexCreate
osMutexCreate(const osMutexDef_t *mutex_def)创建并初始化一个互斥锁。 参数 mutex_def互斥锁定义通过 osMutex(name) 获取地址。 返回值 osMutexId互斥锁 ID非 NULL 表示成功 NULL创建失败如内存不足。
2. 等待获取互斥锁osMutexWait
osMutexWait(osMutexId mutex_id, uint32_t millisec)等待获取互斥锁阻塞当前任务直到获取成功或超时。 参数 mutex_id互斥锁 ID由 osMutexCreate 返回 millisec超时时间单位毫秒 0不等待立即返回 osWaitForever无限等待需包含头文件 cmsis_os.h。 返回值 osOK成功获取锁 osErrorTimeout超时未获取 osErrorParameter参数无效如 mutex_id 为 NULL。
注优先级继承若 RTOS 支持如 FreeRTOS低优先级任务持有锁时高优先级任务等待会临时提升前者优先级避免优先级反转。递归锁同一任务可多次调用 osMutexWait但需对应次数的 osMutexRelease 释放。
3. 释放互斥锁osMutexRelease
osMutexRelease(osMutexId mutex_id)释放已持有的互斥锁。 参数 mutex_id互斥锁 ID。 返回值 osOK释放成功 osErrorResource调用者未持有该锁非法释放 osErrorParameter参数无效。 注必须由持有锁的任务调用中断服务程序ISR不可调用需用信号量替代。释放后等待该锁的任务中优先级最高的任务将被唤醒。
4. 删除互斥锁osMutexDelete
osMutexDelete(osMutexId mutex_id)删除一个不再使用的互斥锁释放相关资源。 参数 mutex_id互斥锁 ID。 返回值 osOK删除成功 osErrorResource锁仍被持有无法删除 osErrorParameter参数无效。
注删除前需确保 没有任务持有该锁 且 无任务在等待该锁否则会导致不可预测行为。动态分配的互斥锁需删除以释放内存静态分配的锁可省略删除由用户手动管理内存。
特性互斥锁Mutex信号量Semaphore用途保护独占资源一对一控制资源计数一对多递归性支持同一任务可重入不支持优先级继承支持部分 RTOS不支持中断安全否需用信号量替代是通过 xxxFromISR 函数
4.2.3 信号量
1. 信号量对象定义宏osSemaphoreDef 和 osSemaphore
#if defined (osObjectsExternal) // 外部定义如在其他文件中声明
#define osSemaphoreDef(name) \
extern const osSemaphoreDef_t os_semaphore_def_##name
#else // 本地定义#if (configSUPPORT_STATIC_ALLOCATION 1) // 静态内存分配#define osSemaphoreDef(name) \const osSemaphoreDef_t os_semaphore_def_##name { 0, NULL }#define osSemaphoreStaticDef(name, control) \const osSemaphoreDef_t os_semaphore_def_##name { 0, (control) }#else // 动态内存分配默认#define osSemaphoreDef(name) \const osSemaphoreDef_t os_semaphore_def_##name { 0 }#endif
#endif#define osSemaphore(name) \
os_semaphore_def_##name // 获取信号量对象地址声明或定义信号量对象的结构体osSemaphoreDef_t。 关键细节 静态分配configSUPPORT_STATIC_ALLOCATION1需手动指定内存如 osSemaphoreStaticDef 传入控制块地址适合内存受限场景。 动态分配RTOS 自动管理内存使用更灵活。 命名规则信号量对象名为 os_semaphore_def_通过 osSemaphore(name) 访问其地址。
2. 创建信号量osSemaphoreCreate
osSemaphoreId osSemaphoreCreate(const osSemaphoreDef_t *semaphore_def, int32_t count);初始化信号量对象指定初始可用资源数 count。 参数 semaphore_def信号量定义结构体指针通过 osSemaphore(name) 获取。 count初始可用资源数 二进制信号量count 1初始可用。 计数信号量count NN 为资源总数。 返回值 成功信号量 ID非 NULL。 失败NULL如内存不足。
3. 等待信号量osSemaphoreWait
int32_t osSemaphoreWait(osSemaphoreId semaphore_id, uint32_t millisec);获取信号量消耗一个令牌若资源不可用则阻塞等待。 参数 semaphore_id信号量 ID由 osSemaphoreCreate 返回。 millisec超时时间单位毫秒 0不等待立即返回当前状态。 osWaitForever无限等待需 RTOS 支持。 返回值 osOK成功。 其他其他超时等异常具体参考源码的定义
typedef enum {osOK 0, /// function completed; no error or event occurred.osEventSignal 0x08, /// function completed; signal event occurred.osEventMessage 0x10, /// function completed; message event occurred.osEventMail 0x20, /// function completed; mail event occurred.osEventTimeout 0x40, /// function completed; timeout occurred.osErrorParameter 0x80, /// parameter error: a mandatory parameter was missing or specified an incorrect object.osErrorResource 0x81, /// resource not available: a specified resource was not available.osErrorTimeoutResource 0xC1, /// resource not available within given time: a specified resource was not available within the timeout period.osErrorISR 0x82, /// not allowed in ISR context: the function cannot be called from interrupt service routines.osErrorISRRecursive 0x83, /// function called multiple times from ISR with same object.osErrorPriority 0x84, /// system cannot determine priority or thread has illegal priority.osErrorNoMemory 0x85, /// system is out of memory: it was impossible to allocate or reserve memory for the operation.osErrorValue 0x86, /// value of a parameter is out of range.osErrorOS 0xFF, /// unspecified RTOS error: run-time error but no other error message fits.os_status_reserved 0x7FFFFFFF /// prevent from enum down-size compiler optimization.
} osStatus;4. 释放信号量osSemaphoreRelease
osStatus osSemaphoreRelease(osSemaphoreId semaphore_id);释放信号量归还一个令牌唤醒等待该信号量的线程。 参数 semaphore_id信号量 ID。 返回值 osOK成功。 其他同等待信号量
5. 删除信号量osSemaphoreDelete
osStatus osSemaphoreDelete(osSemaphoreId semaphore_id);销毁信号量对象释放其占用的内存。 参数 semaphore_id信号量 ID。 返回值 osOK成功需确保无线程等待该信号量。 其他同等待信号量
注删除前需确保没有线程正在等待该信号量否则可能导致不可预测行为。动态分配的信号量需删除以释放内存静态分配的信号量需手动管理生命周期。
特性信号量互斥锁典型用途资源计数、线程同步互斥访问需优先级继承计数支持支持计数N ≥ 1仅二进制N 1优先级继承不支持支持避免优先级反转释放者任意线程可释放仅持有锁的线程可释放
4.3 应用配置 在之前线程、延时、定时器篇中配置的基础上再使能信号标志、互斥锁、信号量的功能。并且因为信号量、互斥锁这些更多应用在两个或多个线程之间为了演示其一般的应用方式这里需要多配置几个线程。因为这里用到信号量的资源计数功能所以要在CubeMX中使能USE_COUNTING_SEMAPHORES的功能启用信号量计数。互斥锁需要使能USE_MUTEXES这里一般默认是使能的。
4.4 代码实现 4.4.1 信号标志 信号标志实现一个功能响应线程B和C的按键信号在线程A中显示不同的灯。这里让按下KEY_R键闪LED_ACT按下KEY_W键闪LED_READY。因为按键在不同的线程里而闪灯在同一个线程中所以实际显示效果是按下按键后会延迟闪灯
/* USER CODE BEGIN 4 */
#define LEDACT_EVENT (1 0)
#define LEDREADY_EVENT (1 1)
osThreadId LEDThreadID;/* 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 *//* 获取当前线程ID */LEDThreadID osThreadGetId();/* Infinite loop */for(;;){osEvent ret osSignalWait(LEDACT_EVENT | LEDREADY_EVENT, 500);if (ret.value.signals LEDACT_EVENT){HAL_GPIO_WritePin(LED_ACT_GPIO_Port, LED_ACT_Pin, GPIO_PIN_RESET);osDelay(500);HAL_GPIO_WritePin(LED_ACT_GPIO_Port, LED_ACT_Pin, GPIO_PIN_SET);osDelay(500);}if (ret.value.signals LEDREADY_EVENT){HAL_GPIO_WritePin(LED_READY_GPIO_Port, LED_READY_Pin, GPIO_PIN_RESET);osDelay(500);HAL_GPIO_WritePin(LED_READY_GPIO_Port, LED_READY_Pin, GPIO_PIN_SET);osDelay(500);}}/* USER CODE END 5 */
}/* USER CODE BEGIN Header_StartTask03 */
/**
* brief Function implementing the myTask03 thread.
* param argument: Not used
* retval None
*/
/* USER CODE END Header_StartTask03 */
void StartTask03(void const * argument)
{/* USER CODE BEGIN StartTask03 *//* Infinite loop */for(;;){if (GPIO_PIN_RESET HAL_GPIO_ReadPin(KEY_R_GPIO_Port, KEY_R_Pin)){/* 经典5ms消抖 */osDelay(5);if (GPIO_PIN_RESET HAL_GPIO_ReadPin(KEY_R_GPIO_Port, KEY_R_Pin)){/* 直到放开才算完成一次按键 */while (GPIO_PIN_RESET HAL_GPIO_ReadPin(KEY_R_GPIO_Port, KEY_R_Pin));osSignalSet(LEDThreadID, LEDACT_EVENT);}}}/* USER CODE END StartTask03 */
}/* USER CODE BEGIN Header_StartTask04 */
/**
* brief Function implementing the myTask04 thread.
* param argument: Not used
* retval None
*/
/* USER CODE END Header_StartTask04 */
void StartTask04(void const * argument)
{/* USER CODE BEGIN StartTask04 *//* Infinite loop */for(;;){if (GPIO_PIN_RESET HAL_GPIO_ReadPin(KEY_W_GPIO_Port, KEY_W_Pin)){/* 经典5ms消抖 */osDelay(5);if (GPIO_PIN_RESET HAL_GPIO_ReadPin(KEY_W_GPIO_Port, KEY_W_Pin)){/* 直到放开才算完成一次按键 */while (GPIO_PIN_RESET HAL_GPIO_ReadPin(KEY_W_GPIO_Port, KEY_W_Pin));osSignalSet(LEDThreadID, LEDREADY_EVENT);}}}/* USER CODE END StartTask04 */
}4.4.2 互斥锁 互斥锁实现一个功能保护一个临界资源的使用即保护共享资源防止重入的问题。这里为了直观演示且增大复现的概率把单片机的主频调成1MHz让循环的时间拉长然后对比下加互斥锁和不加互斥锁的区别。
/* USER CODE BEGIN Header_StartTask02 */
/**
* brief Function implementing the myTask02 thread.
* param argument: Not used
* retval None
*/
#define BUFFSIZE (2000)
uint32_t ShareBuff[BUFFSIZE];/* 互斥锁相关定义 */
osMutexDef(FlashLEDMute);
osMutexId FlashLEDMuteID;/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{/* USER CODE BEGIN StartTask02 */uint32_t i 0;FlashLEDMuteID osMutexCreate(osMutex(FlashLEDMute));/* Infinite loop */for(;;){//if (osOK osMutexWait(FlashLEDMuteID, 500)){for (i 0; i BUFFSIZE; i){ShareBuff[i] 1;}for (i 0; i BUFFSIZE; i){if (1 ! ShareBuff[i])break;}if (i BUFFSIZE){HAL_GPIO_WritePin(LED_READY_GPIO_Port, LED_READY_Pin, GPIO_PIN_RESET);}else{HAL_GPIO_WritePin(LED_READY_GPIO_Port, LED_READY_Pin, GPIO_PIN_SET);}//osMutexRelease(FlashLEDMuteID);}osDelay(1);}/* USER CODE END StartTask02 */
}/* USER CODE BEGIN Header_StartTask03 */
/**
* brief Function implementing the myTask03 thread.
* param argument: Not used
* retval None
*/
/* USER CODE END Header_StartTask03 */
void StartTask03(void const * argument)
{/* USER CODE BEGIN StartTask03 *//* Infinite loop */for(;;){//if (osOK osMutexWait(FlashLEDMuteID, 500)){for (uint32_t i 0; i BUFFSIZE; i){ShareBuff[i] 2;}//osMutexRelease(FlashLEDMuteID);}osDelay(1);}/* USER CODE END StartTask03 */
}下面是加了互斥锁后
/* USER CODE BEGIN Header_StartTask02 */
/**
* brief Function implementing the myTask02 thread.
* param argument: Not used
* retval None
*/
#define BUFFSIZE (2000)
uint32_t ShareBuff[BUFFSIZE];/* 互斥锁相关定义 */
osMutexDef(FlashLEDMute);
osMutexId FlashLEDMuteID;/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{/* USER CODE BEGIN StartTask02 */uint32_t i 0;FlashLEDMuteID osMutexCreate(osMutex(FlashLEDMute));/* Infinite loop */for(;;){if (osOK osMutexWait(FlashLEDMuteID, 500)){for (i 0; i BUFFSIZE; i){ShareBuff[i] 1;}for (i 0; i BUFFSIZE; i){if (1 ! ShareBuff[i])break;}if (i BUFFSIZE){HAL_GPIO_WritePin(LED_READY_GPIO_Port, LED_READY_Pin, GPIO_PIN_RESET);}else{HAL_GPIO_WritePin(LED_READY_GPIO_Port, LED_READY_Pin, GPIO_PIN_SET);}osMutexRelease(FlashLEDMuteID);}osDelay(1);}/* USER CODE END StartTask02 */
}/* USER CODE BEGIN Header_StartTask03 */
/**
* brief Function implementing the myTask03 thread.
* param argument: Not used
* retval None
*/
/* USER CODE END Header_StartTask03 */
void StartTask03(void const * argument)
{/* USER CODE BEGIN StartTask03 *//* Infinite loop */for(;;){if (osOK osMutexWait(FlashLEDMuteID, 500)){for (uint32_t i 0; i BUFFSIZE; i){ShareBuff[i] 2;}osMutexRelease(FlashLEDMuteID);}osDelay(1);}/* USER CODE END StartTask03 */
}如果这里单看StartTask02任务里的操作是完全看不出有什么问题的这就是使用操作系统与裸机开发最大的区别。操作系统是存在多线程的线程之间的轮转方式除了通过一些osDelay延时接口或信号量等等待接口对当前任务进行让出外还有固定的时间片轮转机制也就是当一个线程运行达到一个时间片的长度时一个时间片一般就是操作系统设置的时基这里是1ms会跳出查找是否有同等优先级的其他线程需要运行如果有会执行轮转。因此当线程中执行大量操作且同其他线程共用同一个资源时就容易在资源使用的边界被其他线程误修改且当中的时序不可控最终表现出来就是现象很随机问题概率性出现。这就是嵌入式经常讲的重入问题。所以在操作系统中为了防止这种共享资源应用异常可以使用互斥锁对该资源进行保护防止使用过程中异常切出。
4.4.3 信号量 信号量实现一个功能按照按键按下的次数闪烁对应的次数。这里设置按多少次KEY_W键亮LED_READY灯多少次最大次数为5次。不过这个应用方式用得不是很恰当因为创建信号量的时候初始就存在5个信号量需要消耗完再等按键新释放的信号量跟一开始的设计想法不大一样不过这里用来学习接口的用法也足够了。
/* USER CODE BEGIN Header_StartTask02 */
/**
* brief Function implementing the myTask02 thread.
* param argument: Not used
* retval None
*/
osSemaphoreDef(FlashLED);
osSemaphoreId FlashLEDID;/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{/* USER CODE BEGIN StartTask02 *//* Infinite loop */for(;;){/* 一直等直到有接收到信号量再执行 */if (osOK osSemaphoreWait(FlashLEDID, osWaitForever)){/* 1s消耗一个信号量 */HAL_GPIO_WritePin(LED_READY_GPIO_Port, LED_READY_Pin, GPIO_PIN_RESET);osDelay(500);HAL_GPIO_WritePin(LED_READY_GPIO_Port, LED_READY_Pin, GPIO_PIN_SET);osDelay(500);}}/* USER CODE END StartTask02 */
}/* USER CODE BEGIN Header_StartTask03 */
/**
* brief Function implementing the myTask03 thread.
* param argument: Not used
* retval None
*/
/* USER CODE END Header_StartTask03 */
void StartTask03(void const * argument)
{/* USER CODE BEGIN StartTask03 *//* 最多闪5下 */FlashLEDID osSemaphoreCreate(osSemaphore(FlashLED), 5);/* Infinite loop */for(;;){if (GPIO_PIN_RESET HAL_GPIO_ReadPin(KEY_W_GPIO_Port, KEY_W_Pin)){/* 经典5ms消抖 */osDelay(5);if (GPIO_PIN_RESET HAL_GPIO_ReadPin(KEY_W_GPIO_Port, KEY_W_Pin)){/* 直到放开才算完成一次按键 */while (GPIO_PIN_RESET HAL_GPIO_ReadPin(KEY_W_GPIO_Port, KEY_W_Pin));osSemaphoreRelease(FlashLEDID);}}}/* USER CODE END StartTask03 */
}五、注意事项
1、使用操作系统要注意共享资源的使用如果存在两个任务同时读写同一个共用内存时要留意是否存在重入问题如果需要保护该临界资源可以通过互斥锁的方式进行保护。 2、信号量在创建的时候设置最大资源个数时会同步创建同样多的资源数。
六、相关链接
对于刚入门的小伙伴可以先看下STM32CubeMX的基础使用及Keil的基础使用。 【工具使用】STM32CubeMX-基础使用篇 【工具使用】Keil5软件使用-基础使用篇 【工具使用】STM32CubeMX-FreeRTOS操作系统-基础篇