嵌入式软件架构一

前言:随着工作年限的延长,已经不能满足于刚毕业时单纯的学习某一个知识点就能解决问题了,在工作中需要对行业的标准以及发展沉淀并积累。嵌入式开发同样如此,不能满足于功能的实现,而是整个系统架构的设计,因此操作系统原理的掌握以及分层思想是尤为重要的,近期通过学习韦东山老师的裸机与RTOS再次强化了软件设计思想,因此特通过此博客进行记录。
韦东山老师学习资料链接

一、裸机编程思想
1.1 回想
刚参加工作的时候,看到同事编写的一个逻辑代码,当时感觉就是太牛了,而我当时只是处于while(1)的死循环中,人家不仅有模块的概念,还有分层的概念,每个模块之间独立,层与层之间相互解耦,代码阅读起来非常舒服并且很好维护,更重要的是模拟了任务的调度,当时的项目是传感器的网关,主要功能是通过无线局域网LoRa采集批量传感器的信号,再将数据通过TCP传至后台。
1.2 逻辑编程的几种常见方式

以下例子是来自于韦东山老师课程中,仅供学习使用。

假设有两个任务:

  • 给小孩喂饭
  • 回复同事信息

1.2.1 轮询方式

void main()
{
	while (1)
    {
        喂一口饭();
        回一个信息();
    }
}

缺点:函数之间相互有影响,若一个任务用时过长,另外一个任务无法得到及时的处理。
1.2.2 事件驱动方式

  1. 概念:事件是一个宽泛的概念,什么叫事件?可以是:按下了按键、串口接收到了数据、模块产生了中断、某个全局变量被设置了。
  2. 什么叫事件驱动?当某个事件发生时,才调用对应函数,这就叫事件驱动。
  3. 例子:孩子喊叫时,再给他喂一口饭;同事发来信息电脑响时,再回复一个信息。
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。根据它们运行时消耗的时间调整运行周期,也可以达到比较好的效果。
但是,一旦某个函数执行的时间超长,就会有如下后果:

  • 影响其他函数
  • 延误整个时间基准

解决方法在下一篇给出。

你可能感兴趣的:(嵌入式软件架构学习,架构,单片机,嵌入式硬件)