第2章 单片机程序设计模式 | 百问网
逻辑设计模式
![[Excalidraw/Drawing 2024-12-05 22.40.35.excalidraw]]
前面三种方法都无法解决降低两种耗时函数之间的影响。
第四种方法可以解决,但是实践困难
轮询设计模式
1 2 3 4 5 6 7 8 9
| // 经典单片机程序: 轮询 void main() { while (1) { 喂一口饭(); 回一个信息(); } }
|
就是while循环按照从上到下的顺序依次调用函数
缺点:
如果一个函数时间太长就会导致下一个函数长时间无法被执行
前后台
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // 前后台程序 void main() { while (1) { // 后台程序 喂一口饭(); } }
// 前台程序 void 滴_中断() { 回一个信息(); }
|
其实就是stm32中的中断程序
如果这个中断信息被触发,就会暂停while循环中的函数,直接执行中断函数
这个场景的优点是触发特别及时。
缺点:
和轮询设计模式一样,如果中断程序执行过久,就会导致原本的函数被阻塞很长时间
更大的缺点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // 前后台程序 void main() { while (1) { // 后台程序 } }
// 前台程序 void 滴_中断() { 回一个信息(); }
// 前台程序 void 啊_中断() { 喂一口饭(); }
|
如果同时有 滴 啊 就会导致两个两个函数触发受到影响,先去执行 滴 就会耽误 啊
定时器驱动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| // 前后台程序: 定时器驱动 void main() { while (1) { // 后台程序 } }
// 前台程序: 每1分钟触发一次中断 void 定时器_中断() { static int cnt = 0; cnt++; if (cnt % 2 == 0) { 喂一口饭(); } else if (cnt % 5 == 0) { 回一个信息(); } }
|
简单来说就是每间隔一段时间就触发一次定时器中断函数
适合周期性需要被调用的函数
并且每个函数的执行时间不能超过一个定时器周期
如果
执行时间过长
就会导致
执行被耽误
基于状态机
1 2 3 4 5 6 7 8 9
| // 状态机 void main() { while (1) { 喂一口饭(); 回一个信息(); } }
|
main函数还是轮询
重点在轮询的函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| void 喂一口饭(void) { static int state = 0; switch (state) { case 0: { /* 舀饭 */ /* 进入下一个状态 */ state++; break; } case 1: { /* 喂饭 */ /* 进入下一个状态 */ state++; break; } case 2: { /* 舀菜 */ /* 进入下一个状态 */ state++; break; } case 3: { /* 喂菜 */ /* 恢复到初始状态 */ state = 0; break; } } }
void 回一个信息(void) { static int state = 0;
switch (state) { case 0: { /* 查看信息 */ /* 进入下一个状态 */ state++; break; } case 1: { /* 打字 */ /* 进入下一个状态 */ state++; break; } case 2: { /* 发送 */ /* 恢复到初始状态 */ state = 0; break; } } }
|
我的理解就是把一个大任务拆成了几个小任务
以这个函数为例:
这里总共有两个大任务
![[Excalidraw/Drawing 2024-12-05 23.00.22.excalidraw]]
把大任务拆成几个小任务,要求保证小任务的执行时间不能太长,在轮询中,每次只执行两个大任务中的一个小任务
多任务系统
多任务模式
裸机在多任务处理上,终究是时间长,当有两个函数需要被调用时,会有一个函数被耽误
而对于rtos来说,
其实本质上也属于一种轮询,很类似状态机设计模式
但是这个属于系统层面的调用
速度非常快

两个大任务,这次调用一下,等会调用另一个
切换的时间相当短
互斥操作
对于在多线程中
可能会涉及到一个线程需要读取一个参数
而另一个线程需要修改一个参数
这时候如果同时发生,会导致数据的错乱,所以 锁 的概念应运而生
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| // RTOS程序 int g_canuse = 1;
void uart_print(char *str) { if (g_canuse) { g_canuse = 0; printf(str); g_canuse = 1; } }
task_A() { while (1) { uart_print("0123456789\n"); } }
task_B() { while (1) { uart_print("abcdefghij"); } }
void main() { // 创建2个任务 create_task(task_A); create_task(task_B); // 启动调度器 start_scheduler(); }
|
nnd这块的的互斥真抽象
多看几遍吧
两种进行互斥的方法
1 2 3 4 5 6 7 8 9
| void uart_print(char *str) { if( g_canuse ) ① { g_canuse = 0; ② printf(str); ③ g_canuse = 1; ④ } }
|
第二种
1 2 3 4 5 6 7 8 9
| void uart_print(char *str) { g_canuse--; ① 减一 if( g_canuse == 0 ) ② 判断 { printf(str); ③ 打印 } g_canuse++; ④ 加一 }
|
我感觉怎么没什么用