用 FreeRTOS 重建“按键+LED“小系统
我们做一个能交互的小系统板子上一个按键、一盏灯串口发命令能控制灯的闪烁速度按键长按能切换模式所有状态都通过串口打印出来。麻雀虽小五脏俱全。这个 demo 会用上前 9 篇的几乎所有知识点任务创建、多任务调度、osDelay/osDelayUntil、参数传递、任务控制挂起/恢复、状态管理、安全 printf。做完它你就真正会用 FreeRTOS 了而不只是认识它。一、需求拆解一个能交互的小系统先把功能列清楚再设计任务。功能需求1.LED 闪烁默认每 500ms 翻转一次。2.串口命令控制串口发送fast/slow/stop/start分别把闪烁速度调到 100ms / 1000ms / 停止 / 恢复。3.按键模式切换按键短按切换慢闪/快闪长按 1 秒切换运行/停止。4.状态上报每次状态变化串口打印当前模式和速度。硬件蓝丸板资源引脚用途板载 LEDPC13输出指示板载按键——蓝丸板没有板载按键我们用串口命令模拟按键或外接一个按键到 PA0下拉按下接 3V3为简化本篇用串口命令 一个虚拟按键标志实现不依赖物理按键所有读者都能复现。二、任务划分三个任务各司其职根据需求划分三个任务任务职责周期/触发优先级LedTask按当前速度翻转 LED周期速度可变osPriorityNormalCommandTask解析串口命令更新模式阻塞等待命令osPriorityAboveNormalStatusTask周期上报当前状态每 2 秒osPriorityBelowNormal设计要点•CommandTask优先级最高保证命令响应快实时性•LedTask中等优先级稳定闪烁•StatusTask最低不抢占其它任务• 三个任务通过全局状态变量通信本篇用最简方案第三系列会改用队列/通知。系统状态定义/** * file app_main.c * brief 按键LED 小系统串口命令控制 LED 闪烁 * date 2026-07-15 * version V1.0 - 初版创建 */ #include cmsis_os2.h #include main.h #include stdio.h #include string.h /* 运行模式 */ typedef enum { MODE_STOP 0, /* 停止 */ MODE_SLOW, /* 慢闪 1000ms */ MODE_FAST /* 快闪 100ms */ } RunMode_t; /* 全局系统状态被多任务读写本篇先不加锁第22篇起讲同步 */ static volatile RunMode_t g_mode MODE_SLOW; static volatile uint32_t g_led_speed 1000; /* 当前闪烁周期 ms */ static volatile uint8_t g_cmd_ready 0U; /* 串口收到新命令标志 */ static char g_cmd_buf[16]; /* 命令缓冲 */注意volatile这些变量被多个任务访问volatile保证编译器不把它们优化到寄存器每次都重新读内存。但volatile不能保证原子性复杂场景仍需锁——这是第 22 篇的主题。本篇状态简单单字节/单枚举的读写基本原子暂可接受。三、LedTask按速度闪烁/** * brief LED 闪烁任务 * * details 根据 g_led_speed 控制闪烁周期g_mode 为 MODE_STOP 时不翻转。 * 用 osDelay 而非 osDelayUntil因为周期是动态变化的命令会改速度。 * * param argument: 未使用 * note 每次循环都重新读 g_led_speed及时响应速度变化 */ static void LedTask(void *argument) { (void)argument; for (;;) { if (g_mode ! MODE_STOP) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); osDelay(g_led_speed); } else { /* 停止模式灭灯低频检查状态 */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); /* 灭高电平 */ osDelay(100); } } }为什么这里用osDelay而不是osDelayUntil因为速度会变——刚调osDelay(100)到一半命令把速度改成 1000下次循环自动用新值。osDelayUntil适合固定周期动态周期用osDelay更简单。四、CommandTask解析串口命令这个任务负责接收串口命令并更新状态。为了让 demo 可复现我们用最简方式接收一个全局缓冲 标志实际工程用 DMA 空闲中断第四系列讲。先在 main 里配置 USART1 接收中断简化用轮询方式接收单字符拼成命令/** * brief 串口命令解析任务 * * details 轮询接收 USART1 字符拼成命令字符串后解析。 * 支持命令fast/slow/stop/start/longpress模拟长按。 * 收到回车(\r或\n)视为命令结束。 * * note 本篇用轮询简化第四系列改用 DMA空闲中断队列。 * 命令解析后更新 g_mode 和 g_led_speed。 */ static void CommandTask(void *argument) { static char buf[16]; static uint8_t idx 0; uint8_t ch; (void)argument; printf([SYS] commands: fast / slow / stop / start / longpress\r\n); for (;;) { /* 非阻塞尝试接收 1 字节超时 50ms */ if (HAL_UART_Receive(huart1, ch, 1U, 50U) HAL_OK) { if (ch \r || ch \n) { if (idx 0U) { buf[idx] \0; HandleCommand(buf); idx 0U; } } else if (idx (sizeof(buf) - 1U)) { buf[idx] (char)ch; } } osDelay(1); /* 让出 CPU避免忙等 */ } } /** * brief 执行一条命令 * param cmd: 命令字符串小写以\0结尾 */ static void HandleCommand(const char *cmd) { if (strcmp(cmd, fast) 0) { g_mode MODE_FAST; g_led_speed 100; printf([CMD] modeFAST, speed100ms\r\n); } else if (strcmp(cmd, slow) 0) { g_mode MODE_SLOW; g_led_speed 1000; printf([CMD] modeSLOW, speed1000ms\r\n); } else if (strcmp(cmd, stop) 0) { g_mode MODE_STOP; printf([CMD] modeSTOP\r\n); } else if (strcmp(cmd, start) 0) { g_mode (g_led_speed 500U) ? MODE_FAST : MODE_SLOW; printf([CMD] moderesume, speed%lums\r\n, (unsigned long)g_led_speed); } else if (strcmp(cmd, longpress) 0) { /* 模拟长按在运行/停止间切换 */ g_mode (g_mode MODE_STOP) ? MODE_SLOW : MODE_STOP; printf([KEY] longpress - mode%d\r\n, (int)g_mode); } else { printf([CMD] unknown: %s\r\n, cmd); } }五、StatusTask周期上报/** * brief 状态周期上报任务 * details 每 2 秒打印一次当前模式和速度用于长期监控。 * 用 osDelayUntil 实现绝对周期这里是固定周期适合 osDelayUntil。 */ static void StatusTask(void *argument) { uint32_t wake; static const char *mode_str[] {STOP, SLOW, FAST}; (void)argument; wake osKernelGetTickCount(); for (;;) { wake 2000; printf([STATUS] mode%s, speed%lums, tick%lu\r\n, mode_str[(int)g_mode], (unsigned long)g_led_speed, (unsigned long)osKernelGetTickCount()); osDelayUntil(wake); } }六、组装创建任务并启动/** * brief 创建系统全部任务 */ static void App_StartSystem(void) { osThreadAttr_t attr {0}; attr.name LedTask; attr.stack_size 256; attr.priority osPriorityNormal; osThreadNew(LedTask, NULL, attr); attr.name CommandTask; attr.stack_size 512; /* 含 strcmp/printf给大点 */ attr.priority osPriorityAboveNormal; /* 命令响应优先 */ osThreadNew(CommandTask, NULL, attr); attr.name StatusTask; attr.stack_size 384; /* 含 printf */ attr.priority osPriorityBelowNormal; osThreadNew(StatusTask, NULL, attr); } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); /* printf 重定向已在 uart_printf.c 实现 */ osKernelInitialize(); App_StartSystem(); osKernelStart(); while (1) { } }栈大小是按第 07 篇的方法估的LedTask 不含 printf256 够CommandTask 和 StatusTask 含 printf给 384~512。实际用osThreadGetStackSpace验证过余量充足。七、串口交互实录下载运行后打开串口助手 115200-8-N-1输入命令注意勾选加回车换行[SYS] commands: fast / slow / stop / start / longpress [STATUS] modeSLOW, speed1000ms, tick2000 [STATUS] modeSLOW, speed1000ms, tick4000 slow ← 用户输入 slow板子灯每秒闪一次 [CMD] modeSLOW, speed1000ms [STATUS] modeSLOW, speed1000ms, tick6000 fast ← 用户输入 fast灯变快闪 [CMD] modeFAST, speed100ms [STATUS] modeFAST, speed100ms, tick8000 longpress ← 模拟长按灯停 [KEY] longpress - mode0 [STATUS] modeSTOP, speed100ms, tick10000 start ← 恢复 [CMD] moderesume, speed100ms [STATUS] modeFAST, speed100ms, tick12000 hello ← 未知命令 [CMD] unknown: hello串口设置USART1115200-8-N-1。观察点1. 上电默认 SLOW灯每秒闪一次2. 输入fast后灯明显加快状态打印同步变化3.longpress让灯停start恢复体现模式切换4. 未知命令有明确反馈不崩溃5.[STATUS]每 2 秒严格打印一次osDelayUntil 的功劳。板上现象PC13 灯随命令变化闪烁速度输入 stop 后灯灭输入 start 后恢复。八、知识点回扣这个 demo 用了前 9 篇的什么这个小系统是基础篇的总复习。逐条对应用到的设计来自哪篇osThreadNew创建多任务第 04 篇三个任务不同优先级命令任务抢占第 05 篇osDelay动态周期和osDelayUntil固定周期的选用第 06 篇任务栈按职责给含 printf 的给大第 07 篇configTOTAL_HEAP_SIZE等配置支撑三个任务第 08 篇printf 重定向 任务名前缀第 09 篇全局状态变量 volatile 跨任务共享第 01、05 篇引出第 22 篇深入模式枚举状态机雏形第 01 篇状态机思想的应用做完这个 demo你应该能感受到RTOS 把一个交互系统拆成了三个清晰的、互不阻塞的任务。这在裸机的while(1)里也能实现但命令解析会拖慢 LED 闪烁、状态上报会打断命令接收。RTOS 让三者真正独立。

相关新闻

海底混响非瑞利特性:神经网络参数反演与端到端估计

海底混响非瑞利特性:神经网络参数反演与端到端估计

简介:这份PDF面向水声信号处理、鱼雷自导与目标识别方向的研究生及工程技术人员,围绕海底混响的非瑞利统计特性展开,重点解决瑞利分布模型在强散射体或非均匀海底环境下拟合失准的问题。文中基于单元散射理论对比瑞利分布与K分布模型&#xf…

2026/10/4 13:21:56 阅读更多 →
鸿蒙Flutter插件适配实战:为sanitize_filename补齐FusedPlugin通道

鸿蒙Flutter插件适配实战:为sanitize_filename补齐FusedPlugin通道

1. 项目背景与适配目标 1.1 为什么一个纯 Dart 三库也需要做鸿蒙化适配 先把这个项目的基本盘讲清楚。sanitize_filename 这个库,名字直译就是“清洗文件名”,它的用途很简单:当你需要把用户随意输入的字符串变成合法文件名时,它…

2026/10/4 14:06:36 阅读更多 →
Windows 10/11安装JDK8:环境变量配置与多版本共存指南

Windows 10/11安装JDK8:环境变量配置与多版本共存指南

1. 先搞清楚:为什么现在还有人要装 JDK8打开任何一个后端招聘帖,或者是老项目的部署文档,你会发现一个很奇怪的现象:2024 年了,新项目都在讨论 JDK17、JDK21,可偏偏有一大批系统还在 Windows 上用着 JDK8。…

2026/10/4 14:07:05 阅读更多 →

最新新闻

3D打印Pre-IPO估值30亿背后:设备、材料与应用生态的决胜点

3D打印Pre-IPO估值30亿背后:设备、材料与应用生态的决胜点

刚刷到这条融资消息的时候,我第一反应不是“哇,30亿”,而是“Pre-IPO”这三个字比数字本身更有意思。苏州,3D打印,投前估值30亿,这几个词叠在一起,基本能确定一件事:这个行业已经从“…

2026/10/5 14:00:21 阅读更多 →
Shell脚本“No such file”报错排查与数组传参实践

Shell脚本“No such file”报错排查与数组传参实践

连载数据库巡检脚本的时候,最让人头疼的不是 SQL 写得有问题,而是 Shell 脚本本身莫名其妙地给你来一个 “No such file or directory”,然后整个任务就在那干瞪眼。这个报错长得特别像文件路径不存在,但你去查文件、查目录&#…

2026/10/5 14:00:21 阅读更多 →
OpenShell:Windows上的macOS Dock式任务栏增强工具

OpenShell:Windows上的macOS Dock式任务栏增强工具

1. OpenShell 不是 Shell,而是 Windows 上的“类 macOS Dock”视觉层很多人第一次看到 OpenShell 这个名字,下意识会以为它是某种 Linux 或 macOS 风格的终端替代品——毕竟名字里带 “Shell”,又和 WSL、Linux、macOS 这些词高频共现。但事实…

2026/10/5 14:00:21 阅读更多 →
RabbitMQ必须装Erlang?Windows下Erlang/OTP安装与版本匹配全攻略

RabbitMQ必须装Erlang?Windows下Erlang/OTP安装与版本匹配全攻略

很多第一次接触RabbitMQ的人,大概率都经历过同一个场面:高高兴兴下载了 rabbitmq-server-4.3.6.exe ,双击之后还没看到安装界面,就先弹出一个提示——"请先安装Erlang/OTP"。那一刻你会想:这个Erlang到底是…

2026/10/5 14:00:21 阅读更多 →
【Windows包管理器Scoop迁移避坑指南:两种方案彻底解决C盘空间焦虑】

【Windows包管理器Scoop迁移避坑指南:两种方案彻底解决C盘空间焦虑】

导语:C盘飘红、系统重装、换新电脑——Scoop迁移总是伴随着环境变量混乱、软件无法启动、shim失效等一连串问题。本文提供两种经过实战验证的迁移方案,帮你一次性彻底搞定Scoop迁移。 目录 为什么Scoop迁移这么难?迁移前的准备工作方案一&am…

2026/10/5 14:00:21 阅读更多 →
BranchIP:自适应等变计算驱动的原子间势能建模新范式

BranchIP:自适应等变计算驱动的原子间势能建模新范式

1. 项目概述:为什么“自适应等变计算”正在重构原子间势能建模的底层逻辑BranchIP这个名字乍看像某个冷门开源库的代号,但拆开来看——Branch(分支)、IP(Interatomic Potential,原子间势能)——…

2026/10/5 13:59:21 阅读更多 →

日新闻

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:22 阅读更多 →
AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

1. 从“plugins”这个词说起:它到底在解决什么问题如果你最近在折腾 AI 编程工具,尤其是 Cursor、Codex CLI、Claude Code 这类带 CLI 的编辑器或命令行助手,那你大概率绕不开一个词——plugins。这个词本身不新鲜,从浏览器到 IDE…

2026/10/5 0:00:23 阅读更多 →
第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:23 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 5:06:42 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 1:10:22 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 3:06:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 11:40:45 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 20:14:29 阅读更多 →