第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)
{
回一个信息();
}
}

简单来说就是每间隔一段时间就触发一次定时器中断函数

适合周期性需要被调用的函数 并且每个函数的执行时间不能超过一个定时器周期

如果image.png

执行时间过长 就会导致image.png

执行被耽误 ## 基于状态机

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来说, 其实本质上也属于一种轮询,很类似状态机设计模式 但是这个属于系统层面的调用 速度非常快 image.png 两个大任务,这次调用一下,等会调用另一个

切换的时间相当短

互斥操作

对于在多线程中 可能会涉及到一个线程需要读取一个参数 而另一个线程需要修改一个参数

这时候如果同时发生,会导致数据的错乱,所以 的概念应运而生

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++; ④ 加一
}

我感觉怎么没什么用