【凌鸥LKS32】应用篇(一)·Timer定时器模拟任务调度

发布时间:2026/9/15 8:56:55

【凌鸥LKS32】应用篇(一)·Timer定时器模拟任务调度 目录1. 任务处理方式1.1 裸机系统1.1.1 轮询系统1.1.2 前后台系统1.2 多任务系统2. 时间片轮转2.1 定时器2.2 外设初始化2.3 任务调度设计2.4 按键任务设计2.5 LED任务设计2.6 主函数1. 任务处理方式1.1 裸机系统在裸机系统中系统的主体就是 main 函数里面顺序执行的无限循环这个无限循环里面 CPU 按照顺序完成各种事情。对于裸机的任务1.1.1 轮询系统轮询系统即是在裸机编程的时候先初始化好相关的硬件然后让主程序在一个死循环里面不断循环顺序地做各种事情int main(void) { //硬件初始化 Init(); //..... while(1) { test1();//任务1 test2();//任务2 test3();//任务3 } }轮询系统是一种非常简单的软件结构通常只适用于那些只需要顺序执行代码且不需要外部事件来驱动的就能完成的事情。对应上述代码如果只是实现 LED 翻转串口输出液晶显示等这些操作那么使用轮询系统将会非常完美。但是如果加入了按键操作等需要检测外部信号的事件用来模拟紧急报警那么整个系统的实时响应能力就不会那么好了。假设test3 是按键扫描当外部按键被按下相当于一个警报这个时候需要立马响应并做紧急处理而这个时候程序刚好执行到 test1要命的是 test1需要执行的时间比较久久到按键释放之后都没有执行完毕那么当执行到 test3的时候就会丢失掉一次事件。足见轮询系统只适合顺序执行的功能代码当有外部事件驱动时实时性就会降低。1.1.2 前后台系统相比轮询系统前后台系统是在轮询系统的基础上加入了中断。外部事件的响应在中断里面完成事件的处理还是回到轮询系统中完成中断在这里我们称为前台main 函数里面的无限循环我们称为后台int flag1 0; int flag2 0; int flag3 0; int main(void) { /* 硬件相关初始化 */ HardWareInit(); /* 无限循环 */ for (;;) { if (flag1) { /* 处理事情 1 */ DoSomething1(); } if (flag2) { /* 处理事情 2 */ DoSomething2(); } if (flag3) { /* 处理事情 3 */ DoSomething3(); } } } void ISR1(void) { /* 置位标志位 */ flag1 1; /* 如果事件处理时间很短则在中断里面处理 如果事件处理时间比较长在回到前台处理 */ DoSomething1(); } void ISR2(void) { /* 置位标志位 */ flag2 1; /* 如果事件处理时间很短则在中断里面处理 如果事件处理时间比较长在回到前台处理 */ DoSomething2(); } void ISR3(void) { /* 置位标志位 */ flag3 1; /* 如果事件处理时间很短则在中断里面处理 如果事件处理时间比较长在回到前台处理 */ DoSomething3(); }在顺序执行后台程序的时候如果有中断来临那么中断会打断后台程序的正常执行流转而去执行中断服务程序在中断服务程序里面标记事件如果事件要处理的事情很简短则可在中断服务程序里面处理如果事件要处理的事情比较多则返回到后台程序里面处理。虽然事件的响应和处理是分开了但是事件的处理还是在后台里面顺序执行的但相比轮询系统前后台系统确保了事件不会丢失再加上中断具有可嵌套的功能这可以大大的提高程序的实时响应能力。在大多数的中小型项目中前后台系统运用的好堪称有操作系统的效果。1.2 多任务系统在多任务系统中我们根据功能的不同把整个系统分割成一个个独立的且无法返回的函数这个函数我们称为任务。在多任务系统中两个任务不断切换的即两个变量的波形是完全一样的就好像 CPU 在同时干两件事一样相比前后台系统多任务系统的事件响应也是在中断中完成的但是事件的处理是在任务中完成的。在多任务系统中任务跟中断一样也具有优先级优先级高的任务会被优先执行。当一个紧急的事件在中断被标记之后如果事件对应的任务的优先级足够高就会立马得到响应。相比前后台系统多任务系统的实时性又被提高了int flag1 0; int flag2 0; int flag3 0; int main(void) { /* 硬件相关初始化 */ HardWareInit(); /* OS 初始化 */ RTOSInit(); /* OS 启动开始多任务调度不再返回 */ RTOSStart(); } void ISR1(void) { /* 置位标志位 */ flag1 1; } void ISR2(void) { /* 置位标志位 */ flag2 2; } void ISR3(void) { /* 置位标志位 */ flag3 1; } void DoSomething1(void) { /* 无限循环不能返回 */ for (;;) { /* 任务实体 */ if (flag1) { } } } void DoSomething2(void) { /* 无限循环不能返回 */ for (;;) { /* 任务实体 */ if (flag2) { } } } void DoSomething3(void) { /* 无限循环不能返回 */ for (;;) { /* 任务实体 */ if (flag3) { } } }每个任务都是独立的互不干扰的且具备自身的优先级它由操作系统调度管理。加入操作系统后我们在编程的时候不需要精心地去设计程序的执行流不用担心每个功能模块之间是否存在干扰。加入了操作系统我们的编程反而变得简单了。整个系统随之带来的额外开销就是操作系统占据的那一丁点的 FLASH 和 RAM。现如今单片机的 FLASH 和 RAM 是越来越大完全足以抵挡 RTOS 那点开销。无论是裸机系统中的轮询系统、前后台系统和多任务系统我们不能一锤子的敲定孰优孰劣它们是不同时代的产物在各自的领域都还有相当大的应用价值只有合适才是最好。有关这三者的软件模型区别模型事件响应事件处理特点轮询系统主程序主程序轮询响应事件轮询处理事件前后台系统中断主程序实时响应事件轮询处理事件多任务系统中断任务实时响应事件实时处理事件2. 时间片轮转这里我们通过定时器用来模拟多任务系统的任务调度机制创建一个结构体数组描述任务{就绪标志, 倒计时, 周期, 函数指针}其实际上仍是裸机的操作方式没有上下文切换和优先级抢占机制通过给每个任务分配时间时间完成后切换下一个任务以此来实现任务调度。2.1 定时器首先先来完成整个任务调度的核心设置一个1ms的定时器仿照RTOS的节拍周期统一由 1ms tick 驱动各任务节拍一致void UTimer_Task_Init(void) { TIM_TimerInitTypeDef TIM_InitStruct; TIM_TimerStrutInit(TIM_InitStruct); TIM_InitStruct.Timer_CH0_WorkMode TIMER_OPMode_CMP; // 比较模式 TIM_InitStruct.Timer_CH0Output 0; // 不输出波形 TIM_InitStruct.Timer_CH1_WorkMode TIMER_OPMode_CMP; TIM_InitStruct.Timer_CH1Output 0; TIM_InitStruct.Timer_TH 48000; // 周期重载值 TIM_InitStruct.Timer_CMP0 24000; // 比较值0未用输出可忽略 TIM_InitStruct.Timer_CMP1 24000; // 比较值1 TIM_InitStruct.Timer_FLT 0; TIM_InitStruct.Timer_ClockDiv TIMER_CLK_DIV2; // 时钟2分频 TIM_InitStruct.Timer_IRQEna Timer_IRQEna_ZC; // 使能中断 TIM_TimerInit(UTIMER3, TIM_InitStruct); TIM_TimerCmd(UTIMER3, ENABLE); // 启动定时器 NVIC_SetPriority(TIMER3_IRQn, 1); // 优先级设为 1较高 NVIC_EnableIRQ(TIMER3_IRQn); // 使能 UTIMER3 中断 }中断服务函数进行持续累加extern volatile u16 nTimeCnt1ms;// 全局毫秒计数器 extern uint8_t task_flag; // 任务调度使能开关 void TIMER3_IRQHandler(void) { UTIMER3_IF | TIMER_ZC_IF; // 写1清0清除中断标志 nTimeCnt1ms; // 全局毫秒计数器1 if (task_flag 1) { // 任务调度使能开关 TaskRemarks(); // 执行所有任务的倒计时 } }2.2 外设初始化初始化LED和按键下面会对这两个外设设计任务功能void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_StructInit(GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode GPIO_Mode_OUT; GPIO_InitStruct.GPIO_Pin LED1_PIN; GPIO_Init(LED1_PORT, GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode GPIO_Mode_OUT; GPIO_InitStruct.GPIO_Pin LED2_PIN; GPIO_Init(LED2_PORT, GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode GPIO_Mode_OUT; GPIO_InitStruct.GPIO_Pin LED3_PIN; GPIO_Init(LED3_PORT, GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode GPIO_Mode_OUT; GPIO_InitStruct.GPIO_Pin LED4_PIN; GPIO_Init(LED4_PORT, GPIO_InitStruct); LED_All_OFF();//初始化阶段led灯全关闭 } void KEY_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_StructInit(GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode GPIO_Mode_IN; GPIO_InitStruct.GPIO_Pin KEY_POWER_PIN; GPIO_Init(KEY_POWER_PORT, GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode GPIO_Mode_IN; GPIO_InitStruct.GPIO_Pin KEY_TEMP_PIN; GPIO_Init(KEY_TEMP_PORT, GPIO_InitStruct); }宏定义部分根据自己引脚修改#define KEY_POWER_PORT GPIO2 #define KEY_POWER_PIN GPIO_Pin_4 #define KEY_POWER_SOURCE GPIO_PinSource_4 #define KEY_POWER_SCAN() GPIO_ReadInputDataBit(KEY_POWER_PORT, KEY_POWER_PIN) #define KEY_TEMP_PORT GPIO0 #define KEY_TEMP_PIN GPIO_Pin_11 #define KEY_TEMP_SOURCE GPIO_PinSource_11 #define KEY_TEMP_SCAN() GPIO_ReadInputDataBit(KEY_TEMP_PORT, KEY_TEMP_PIN) #define LED1_PIN GPIO_Pin_8 #define LED1_PORT GPIO1 #define LED1_ON GPIO_ResetBits(LED1_PORT, LED1_PIN) #define LED1_OFF GPIO_SetBits(LED1_PORT, LED1_PIN) #define LED2_PIN GPIO_Pin_7 #define LED2_PORT GPIO1 #define LED2_ON GPIO_ResetBits(LED2_PORT, LED2_PIN) #define LED2_OFF GPIO_SetBits(LED2_PORT, LED2_PIN) #define LED3_PIN GPIO_Pin_6 #define LED3_PORT GPIO1 #define LED3_ON GPIO_ResetBits(LED3_PORT, LED3_PIN) #define LED3_OFF GPIO_SetBits(LED3_PORT, LED3_PIN) #define LED4_PIN GPIO_Pin_5 #define LED4_PORT GPIO1 #define LED4_ON GPIO_ResetBits(LED4_PORT, LED4_PIN) #define LED4_OFF GPIO_SetBits(LED4_PORT, LED4_PIN) #define LED1_Toggle() do { if (GPIO_ReadOutputDataBit(LED1_PORT, LED1_PIN)) \ LED1_ON; else LED1_OFF; } while(0) #define LED2_Toggle() do { if (GPIO_ReadOutputDataBit(LED2_PORT, LED2_PIN)) \ LED2_ON; else LED2_OFF; } while(0) #define LED3_Toggle() do { if (GPIO_ReadOutputDataBit(LED3_PORT, LED3_PIN)) \ LED3_ON; else LED3_OFF; } while(0) #define LED4_Toggle() do { if (GPIO_ReadOutputDataBit(LED4_PORT, LED4_PIN)) \ LED4_ON; else LED4_OFF; } while(0)2.3 任务调度设计这里我创建两个任务采用枚举类型方便后续任务扩展typedef enum _TASK_LIST { TASK_KEY 0, TASK_LED, //后续想要添加别的任务直接在这里加即可 TASKS_MAX } TASK_LIST;然后分别对任务赋值相关参数默认都不运行任务周期根据宏定义自行选择这里我就10ms的任务#define TASK_PERIOD_1MS 1 #define TASK_PERIOD_2MS 2 #define TASK_PERIOD_5MS 5 #define TASK_PERIOD_10MS 10 #define TASK_PERIOD_20MS 20 #define TASK_PERIOD_30MS 30 #define TASK_PERIOD_40MS 40 #define TASK_PERIOD_50MS 50 #define TASK_PERIOD_60MS 60 #define TASK_PERIOD_100MS 100 #define TASK_PERIOD_360MS 360 #define TASK_PERIOD_500MS 500 #define TASK_PERIOD_1000MS 1000 typedef struct _TASK_COMPONENTS { uint8_t Run; // 运行标志 uint16_t Timer; // 当前倒计时 uint16_t ItvTime; // 任务周期 void (*TaskHook)(void); // 函数指针指向任务函数 } TASK_COMPONENTS; static TASK_COMPONENTS TaskComps[] { {false, TASK_PERIOD_10MS, TASK_PERIOD_10MS, TaskKey}, // 按键 10ms {false, TASK_PERIOD_10MS, TASK_PERIOD_10MS, TaskLED}, // LED 10ms };其中两个任务索引可以先创建空的下面在做补充void TaskProcess(void) { } void TaskLED(void) { }任务框架搭载完了我们这里开始实现任务调度的核心函数首先初始化任务调度器根据枚举循环调用写入任务参数uint8_t TaskInit(void) { uint8_t i; for (i 0; i TASKS_MAX; i) { TaskComps[i].Timer TaskComps[i].ItvTime; // 每个任务的定时器装载其间隔值 TaskComps[i].Run false; // 清除运行标志暂不执行 } return 1; }然后更新任务定时器当周期到时置位运行标志若标志位置位在定时器每1ms循环调用每次调用任务时间减1若是减到0则重装载定时器置运行标志位void TaskRemarks(void) { uint8_t i; for (i 0; i TASKS_MAX; i) { if (TaskComps[i].Timer) // 若定时器未到0 { TaskComps[i].Timer--; // 倒计时减1 if (TaskComps[i].Timer 0) // 减到0说明一个周期到了 { TaskComps[i].Timer TaskComps[i].ItvTime; // 重装定时器 TaskComps[i].Run true; // 置位运行标志等待执行 } } } }最后主函数循环调用如下函数执行所有待运行的任务void TaskProcess(void) { uint8_t i; for (i 0; i TASKS_MAX; i) { if (TaskComps[i].Run) // 若该任务被标记为可运行 { TaskComps[i].TaskHook(); // 调用任务函数指针实际执行任务 TaskComps[i].Run false; // 执行完毕清除运行标志 } } }2.4 按键任务设计首先初始化按键枚举方便管理typedef enum { KEY_TEMP, KEY_POWER, KEY_COMB, KEY_TYPE_MAX } keyType_e; extern keyType_e g_keyEvent; // 按键事件然后创建按键检测函数keyType_e g_keyEvent KEY_TYPE_MAX; // 初始无事件 //按键检测函数判断哪个那件被按下 keyType_e Detect_Key(void) { if ((KEY_POWER_SCAN() Bit_RESET) (KEY_TEMP_SCAN() Bit_RESET)) //双按键按下 { return KEY_COMB; } if (KEY_POWER_SCAN() Bit_RESET) //电源键 { return KEY_POWER; } if (KEY_TEMP_SCAN() Bit_RESET) //模式选择键 { return KEY_TEMP; } return KEY_TYPE_MAX; //无按键边界值 }完善按键任务函数循环扫描按键任务void TaskKey(void) { static keyType_e key_last KEY_TYPE_MAX; // 上一次扫描值 static uint8_t stable_cnt 0; // 连续相同计数 keyType_e key_now Detect_Key(); // 读取当前按键 if (key_now key_last) { if (stable_cnt 255) stable_cnt; } else { stable_cnt 0; key_last key_now; } if (stable_cnt 2 key_now ! KEY_TYPE_MAX g_keyEvent KEY_TYPE_MAX) { g_keyEvent key_now; // 投递事件等待LED任务取走 } }2.5 LED任务设计做一个简单封装#include led_app.h #include delay.h void LED_All_ON(void) { LED1_ON; LED2_ON; LED3_ON; LED4_ON; } void LED_All_OFF(void) { LED1_OFF; LED2_OFF; LED3_OFF; LED4_OFF; } void LED_All_Toggle(void) { LED1_Toggle(); LED2_Toggle(); LED3_Toggle(); LED4_Toggle(); }LED通过读取按键键值的变换实现LED灯的变换操作void TaskLED(void) { if (g_keyEvent KEY_TYPE_MAX) return; // 无事件直接退出 switch (g_keyEvent) { case KEY_POWER: LED1_Toggle(); break; case KEY_TEMP: LED2_Toggle(); break; case KEY_COMB: LED_All_Toggle(); break; default: break; } g_keyEvent KEY_TYPE_MAX; // 事件已处理清零允许下一次投递 }2.6 主函数主函数非常简单就调用任务函数即可#include lks32mc07x_lib.h #include hardware_init.h #include task.h #include led_app.h #include key_app.h uint8_t task_flag 0;// 任务调度使能开关 int main(void) { Hardware_init(); TaskInit(); // 任务调度器初始化装载各任务周期 task_flag 1; // 使能任务调度1ms中断里才会调用TaskRemarks while(1) { TaskProcess(); } }最终实现的功能按键单独按下实现一颗LED电平反转两个按键同时按下实现全部LED电平反转由于无法上传视频实验现象可以自行测试。完整工程凌鸥LKS32MC071系列单片机·Timer仿造RTOS实现任务调度实验资源-CSDN下载凌鸥LKS32_时光の尘的博客-CSDN博客
延伸阅读

更多相关文章

2026/9/15 8:51:54

Ryzen AI MAX+395不是显卡:Windows 11统一内存与AI加速原理

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

2026/9/15 9:01:57

本地部署大模型+WorkBuddy办公提效实战指南

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

2026/9/15 9:01:57

别让错误的追高方式,毁了孩子的膝盖和身高

刷到一条 "孩子这样吃能长高",钙片立刻加购物车;听邻居说 "吊单杠管用",拉伸器当天下单;老人说 "骨头汤补钙",排骨一周炖三回。一顿操作猛如虎,钱花了不少,孩子身…

2026/9/15 9:01:57

单片机毕设项目|单片机毕业设计|基于52单片机的无线开关设计

第一章 绪论1.1 研究背景与意义近年来,物联网(Internet of Things,IoT)技术以蓬勃之势融入人们的生活,推动着传统家居向智能化方向快速转型。随着 5G 通信技术的普及、云计算与大数据分析能力的提升,智能家…

2026/9/15 8:56:55

STM32软件SPI驱动ST7735R显示屏实战指南

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

2026/9/15 4:54:30

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/15 0:01:16

AI英语单词APP开发:自适应学习算法与移动端优化实践

1. 项目概述 作为一名在移动应用开发领域摸爬滚打多年的老手,我最近完成了一个AI英语单词APP的开发项目。这个项目将传统单词记忆方法与现代AI技术相结合,打造了一款能够智能适应不同用户学习习惯的英语学习工具。 市面上大多数单词APP都存在一个通病&a…

2026/9/15 0:01:16

Flutter与OpenHarmony结合开发手语学习APP实战

1. 项目背景与核心价值作为一名同时接触过Flutter和OpenHarmony的开发者,最近我完成了一个基于Flutter for OpenHarmony的手语学习APP实战项目。这个项目最大的特点在于实现了跨平台框架与国产操作系统深度结合的创新实践——用Flutter开发的应用能完美运行在OpenHa…

2026/9/15 0:01:16

六个月成为机器人工程师:从ROS2到SLAM的实战路径

1. 六个月的紧迫感从哪来:先搞清楚你要成为哪种机器人工程师说实话,六个月的期限并不是一个宽松的时间线。市面上任何一本正经的机器人学教材都超过五百页,ROS2的官方文档可以翻到你怀疑人生,再加上ABB、KUKA这些工业机器人厂家动…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/14 13:53:59

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/14 11:22:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码