嵌入式常用裸机编程框架
作者:互联网
RTOS嵌入式系统框架进阶
第一章 嵌入式常用裸机编程框架
第二章 面向对象编程
文章目录
前言
学习韦东山老师的七天物联网实战,以其课程笔记为骨,记录一下学习的过程,可能会加入一些自己的感想。
最后欢迎点赞,评论交流!
1 轮询方式
常用的嵌入式程序框架设计方案如下所述:
// 常用模式
void main()
{
while (1)
{
Task(A);
Task(B);
}
}
函数之间互相有影响,若函数A耗时特别长,尤其包含一些无意义的delay函数进行耗时,那么B任务还得必须等待A任务执行完,这样的话B任务的执行情况就很不好。
2 事件驱动方式
2.1 普通事件驱动方式
我们在看芯片的datasheet的时候,尤其是新手,总会混淆事件和中断的概念,尤其是在配置寄存器的时候,有时某些操作会产生事件或中断,中断我们理解是cpu放下当前任务去执行其他任务,那事件是什么却有时候不是很明白?
其实事件是一个宽泛的概念,什么叫事件?可以是:按下了按键、串口接收到了数据、模块产生了中断、某个全局变量被设置了。事件就是发生了某事,至于怎么去处理它得看你的软硬件如何设置。
那什么叫事件驱动?就是当某个事件发生时,才调用对应函数,这就叫事件驱动。
void main()
{
while (1)
{
if (Flag_a)
process_key();
}
}
void a_isr() /* 事件a产生中断 */
{
key = xxx;
Flag_a = 1;
}
void b_isr() /* 事件b产生中断 */
{
Task(b);
}
其实这种方式本质上还是采用轮询的方式,只不过使用了一些中断的技巧,但是b_isr()中进行了任务处理的一些函数,一般我们不这么使用中断,因为如果Task(b)耗时比较严重的话,cpu一直处于b任务的话,可能a中断的处理就会收到影响,比如a多次产生中断,但cpu一直在task(b)中,导致最后cpu应该就只处理一次a函数,所以一般我们常用以下的方式来改进事件驱动型框架。
2.2 改进事件驱动方式
void main()
{
while (1)
{
if (Flag_a == 1)
Task(a);
if (Flag_b == 1)
Task(b);
}
}
void a_isr() /* 中断a */
{
Flag_a = 1;
}
void b_isr() /* 中断b */
{
Flag_b = 1;
}
改进型函数只在中断中修改标识符,处理速度就会快很多,不会导致别的中断被延迟、丢失。
但本质上还是轮询的框架。
3 常用事件驱动方式:定时器
3.1 “时间片”框架
“时间片”是嵌入式常听的一个概念,即原来的整个while(1)大循环切割成很多小的时间片,每过一个时间片就切换一次执行的任务,这样的话每个任务都在同时执行,不用等待。
例如ABC三个任务,A周期1ms,B周期2ms,C周期3ms,时间片为1ms,即第一个1ms,A执行,B剩余1ms,C剩余2ms;第二个1ms,A执行,B执行,C剩余1ms;第三个1ms,A执行,B剩余1ms,C执行…
代码如下:
typedef struct soft_timer {
int remain;
int period;
void (*function)(void);
}soft_timer, *p_soft_timer;
static soft_timer timers[] = {
{1, 1, A},
{2, 2, B},
{3, 3, C},
};
void main()
{
while (1)
{
}
}
void timer_isr()
{
int i;
/* timers数组里每个成员的expire都减一 */
for (i = 0; i < 3; i++)
timers[i].remain--;
/* 如果timers数组里某个成员的expire等于0:
* 1. 调用它的函数
* 2. 恢复expire为period
*/
for (i = 0; i < 3; i++)
{
if (timers[i].remain == 0)
{
timer[i].function();
timers[i].remain = timers[i].period;
}
}
}
上述例子中有三个函数:A、B、C。根据它们运行时消耗的时间调整运行周期,也可以达到比较好的效果。
但是,一旦某个函数执行的时间超长加入超过1ms(一个时间片的长度),这样这个时间片内即使所有时间都用来处理这个任务时间也不够,因此等到下次定时器中断时就不能正常处理原来的ABC任务,就会有如下后果:
- 影响其他函数
- 延误整个时间基准
3.2 改进“时间片”框架
此处的改进并没有进行特别的改进,只不过把任务执行的函数放到主循环中,本质仍是轮询算法,若任务函数执行时间过长 ,仍然会有以下的弊端:
- 影响其他函数
- 延误整个时间基准
typedef struct soft_timer {
int remain;
int period;
void (*function)(void);
}soft_timer, *p_soft_timer;
static soft_timer timers[] = {
{1, 1, A},
{2, 2, B},
{3, 3, C},
};
void main()
{
int i;
while (1)
{
/* 如果timers数组里某个成员的expire等于0:
* 1. 调用它的函数
* 2. 恢复expire为period
*/
for (i = 0; i < 3; i++)
{
if (timers[i].remain == 0)
{
timer[i].function();
timers[i].remain = timers[i].period;
}
}
}
}
void timer_isr()
{
int i;
/* timers数组里每个成员的expire都减一 */
for (i = 0; i < 3; i++)
if (timers[i].remain)
timers[i].remain--;
}
3.2 “时间片”+“状态机”框架
若非要用“裸机”的思想处理时间片的所说的难点,需要引入“状态机”的概念,其内核思想是:将耗时的任务进行拆分,确保拆分后的任务可以在每个时间片中完成所需功能。
- 状态机函数示例如下
void task_A(void)
{
static int state = 0;
switch (state)
{
case 0: /* 开始 */
{
/* TA1 */
state++;
return;
}
case 1: /* 拆分后任务片 */
{
/* TA2 */
state++;
return;
}
case 2:
{
/* TA3 */
state++;
return;
}
}
}
void task_B(void)
{
static int state = 0;
switch (state)
{
case 0: /* 开始 */
{
/* TB1 */
state++;
return;
}
case 1:
{
/* TB2 */
state++;
return;
}
case 2:
{
/* TB3 */
state++;
return;
}
}
}
void main()
{
while (1)
{
task_A();
task_B();
}
}
但状态机拆分程序有以下难点:
- 比较麻烦
- 有些复杂的程序无法拆分为状态机
基于裸机的程序框架无法完美地解决这类问题:复杂的、很耗时的多个函数。
4 RTOS实时操作系统
假设要调用两个任务A、B,AB执行的时间都很长,可以使用以下两种办法来解决:1、使用裸机程序时可以把AB函数改造为"状态机",2、可以使用RTOS。
这两种方法的核心都是"分时复用":
-
分时:函数A运行一小段时间,函数B再运行一小段时间
-
复用:复用CPU
二者的核心思想类似,只不过因为RTOS会创建属于任务本身的堆栈,可以保存切换任务时的程序执行现场,因此不用我们再对任务进行人为的拆分。
// RTOS程序
Task_A()
{
while (1)
{
FuncA();
}
}
Task_B()
{
while (1)
{
FuncB();
}
}
void main()
{
create_task(Task_A);
create_task(Task_B);
start_scheduler();
while (1)
{
sleep();
}
}
总结
例如:以上就是今天总结的内容,本文仅仅简单介绍了嵌入式常用的系统框架,方便我们的后续使用。标签:Task,函数,void,编程,timer,嵌入式,裸机,state,timers 来源: https://blog.csdn.net/gudao07/article/details/123135603