前言:随着工作年限的延长,已经不能满足于刚毕业时单纯的学习某一个知识点就能解决问题了,在工作中需要对行业的标准以及发展沉淀并积累。嵌入式开发同样如此,不能满足于功能的实现,而是整个系统架构的设计,因此操作系统原理的掌握以及分层思想是尤为重要的,近期通过学习韦东山老师的裸机与RTOS再次强化了软件设计思想,因此特通过此博客进行记录。
韦东山老师学习资料链接
一、裸机编程思想
1.1 回想
刚参加工作的时候,看到同事编写的一个逻辑代码,当时感觉就是太牛了,而我当时只是处于while(1)的死循环中,人家不仅有模块的概念,还有分层的概念,每个模块之间独立,层与层之间相互解耦,代码阅读起来非常舒服并且很好维护,更重要的是模拟了任务的调度,当时的项目是传感器的网关,主要功能是通过无线局域网LoRa采集批量传感器的信号,再将数据通过TCP传至后台。
1.2 逻辑编程的几种常见方式
以下例子是来自于韦东山老师课程中,仅供学习使用。
假设有两个任务:
- 给小孩喂饭
- 回复同事信息
1.2.1 轮询方式
void main()
{
while (1)
{
喂一口饭();
回一个信息();
}
}
缺点:函数之间相互有影响,若一个任务用时过长,另外一个任务无法得到及时的处理。
1.2.2 事件驱动方式
- 概念:事件是一个宽泛的概念,什么叫事件?可以是:按下了按键、串口接收到了数据、模块产生了中断、某个全局变量被设置了。
- 什么叫事件驱动?当某个事件发生时,才调用对应函数,这就叫事件驱动。
- 例子:孩子喊叫时,再给他喂一口饭;同事发来信息电脑响时,再回复一个信息。
void main()
{
while (1)
{
if (get_key)
process_key();
}
}
void key_isr() /* 孩子喊叫触发中断a */
{
key = xxx;
get_key = 1;
}
void b_isr() /* 同事发来信息触发中断b */
{
回一个信息();
}
优点:当这两个中断函数执行得都很快,这种编程方式很好。
缺点:如果a、b中断同时发生,就会互相影响:
- 两个中断,同一时间只能处理一个
- 如果当前中断处理时间比较长,就会影响到另一个中断的处理
1.2.3 改进的事件驱动方式
对于中断的处理,原则是"尽快"。否则会影响到其他中断,导致其他中断的处理被延迟、甚至丢失。
如果某些中断的处理确实比较慢,怎么办?看如下代码,需要在中断中置标志,而在中断外部轮询处理具体的事情。
void main()
{
while (1)
{
if (crying == 1)
喂一口饭();
if (get_msg == 1)
回一个信息();
}
}
void a_isr() /* 孩子喊叫触发中断a */
{
crying = 1;
}
void b_isr() /* 同事发来信息触发中断b */
{
get_msg = 1;
}
优点:可以解决中断的相应问题:中断处理很快,不会导致别的中断被延迟、丢失。
缺点:中断触发的后续处理退化为轮询方式。
1.2.4 定时器的事件驱动方式
这种方法就是前面提到的那位大神的任务调度方式,经过韦老师的讲解,来了个大通透。
上述例子中只有两个任务,如果有更多的任务,很多有经验的工程师会使用定时器来驱动:
- 设置一个定时器,比如每1ms产生一次中断
- 对于函数A,可以设置它的执行周期,比如每1ms执行一次
- 对于函数B,可以设置它的执行周期,比如每2ms执行一次
- 对于函数C,可以设置它的执行周期,比如每3ms执行一次
注意:1ms、2ms、3ms只是假设,你可根据实际情况调整。
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(void)
{
while(1)
{}
}
void timer_isr()
{
int i;
/* timers数组里每个成员的expire都减1 */
for (i = 0; i < 3; i++)
{
times[i].remain--;
}
/* 如果times数组里的某个成员的expire等于0;
* 1.调用其函数
* 2.回复expire为period
*/
for (i =0; i < 3; i++)
{
if (timers[i].remain == 0)
{
timers[i].function();
timers[i].remain = timers[i].period;
}
}
}
上述例子中有三个函数:A、B、C。根据它们运行时消耗的时间调整运行周期,也可以达到比较好的效果。
但是,一旦某个函数执行的时间超长,就会有如下后果:
- 影响其他函数
- 延误整个时间基准
解决方法在下一篇给出。