1.在代码中我会将整个工程分为三层:硬件层,中间层,应用层:
1.主要是硬件的初始化,以及从硬件中采集数据。初始化没什么好讲的,我们主要讲如何接受采集的数据。
为了提高程序可读性和修改硬件接口简单,我们定义一些硬件接口时,使用宏定义来替换硬件接口:
#define NFC_12M_CLK_PORT GPIOC
#define NFC_12M_CLK_PIN GPIO_Pin_10
#define NFC_RST_PORT GPIOA
#define NFC_RST_PIN GPIO_Pin_4
2.硬件层就是我们的传感器,我们需要将传感器中的数据读出来。我们知道,读数据其实就是将DR(data resigster)中的数据放到一个变量中。
3.一般采集数据有两种方式:一种通过中断触发后然后读数据(例如串口数据在中断中接收),另一种通过主动轮询读数据(例如ADC数据采集(非中断))。
4.为了达到分层效果,意味着我的bsp.c和bsp.h这两个文件不能包括任何外部的头文件(由开发人员自己写的),只要保证这种效果,分层自然开始了。
5.当然我们这里的数据要如何发送给中间层去简单的处理,这里还是分中断方式和非中断方式:中断方式采用函数回调的方法,非中断方式采用返回值的方法。
1.这一层是对采集到的原始数据做处理的,将采集到的冰冷的数据进行处理,得到不同的信号,状态。
2.同理这一层我们也不包含任何自己写的外部头文件,只要参杂了任意自定义的头文件那么就表示分层失败。(这一点只能说尽量实现,我有尝试过,如果强制的不包含硬件层的.h文件,会出现很多带参函数,降低程序效率,以及一些可读性,中断回调可以做到不去和硬件层勾搭在一起,而比如IO状态,ADC采集,我们还是需要将数据传到中间层,这时候还是用宏定义来替换硬件层接口)
3.这一层一个入口(硬件层采集到的数据),一个出口(信号,状态等等)。
4.如果应用层有多个应用需要去读中间层返回值,那么我们这时候就不能用单一函数去返回处理出来的状态,而要用一个全局变量去接处理出来的状态,然后大家可以通过访问这个全局变量(为了提高代码可读性,这个全局变量使用枚举赋值)知道自己相应的要做什么(比如按键按下,长按开灯,短按电机转起来)。当如果只要一个应用层去读中间层的状态,那么我们直接让中间层处理完毕的数据返回即可。
如果我们当前中间层需要反馈出不同的状态量(比如多个按键的长按短按),如果用多个u8类型也是可以的,其实这时候我们完全可以用一个u8类型的位域,处理4个按键,每个按键占用两个bit(可以表示4种状态)。并且像位域,结构体他们都让程序中的数据看起来更简洁。
5.中间层一般涉及到的难点应该也能叫做算法:比如ADC滤波,串口数据队列
1.这一层可以说是最难写的,需要将所有的中间层或者硬件层的数据组织起来,如果组织不好将会导致写出来的应用到处是bug,或者后面开发时需求一改,整个应用层逻辑全部打乱。
2.当然这一层我们一般都是调用中间层,然后将中间层返回的状态做最终的处理。比如就点亮一个普通的灯,这个灯可能会读很多状态,按键按下开灯,再按下关灯,ADC采集到低电压灯闪烁,档位调节改变亮度等等。
3.例如我需要处理串口接收到的数据(TDE模块),我需要获取串口中断发生的情况,需要用到定时器计数,需要用到串口中间层处理完毕后的数据。 当然我还需要一些功能函数
4.如果遇到更改IC的情况,只要我们硬件层和中间层做好的分离,我们只要稍微改动硬件层就可以,因为从目前逻辑来看,中间层实际也不和硬件直接相关。
之前看到过一句这样的话:如果想让各个层级之间分离,只需要在两个层之间加一个层就行,如果还不行就再加一层。这句话意思就是为了解除代码层之间的关联,我们需要适当添加代码层。(之前看到国际空间站为了能够停靠各个国家的飞船,他们就搞了一些接口来连接飞船。我们假设国际空间站的接口是圆形的,而其他国家飞船是各种形状,各种口径的接口,为了能够连接,就需要中间搞一个接口)
5.如果我们在写应用层的时候,发现了统一的处理应用,我们也可以打包,让应用层也具有可移植性
1.最后我们初始化硬件,调用应用层就可以了,中间层已经被应用层调用过一遍了。
最主要的我们还是要想好如何将硬件层能尽快的和中间层分开,在linux中用用统一的接口去屏蔽底层操作如我这篇文章写的(嵌入式驱动分层_他日仙界再相逢,一声道友尽沧桑的博客-CSDN博客)。当然这种方法并不好,我对硬件和驱动分离非常痴狂正在摸索最优的解决方案。
做法其实有很多种,我之前的办法是传参,但是传多了会感觉很烦。为了达到和linux类似的效果。我这里想到了一个不错的方法,那就是用宏来代替底层的一些函数,这样节约空间,执行效率也变高了。
void led_pin(u8 value)
{
P1^1 = value;//51输出高低电平
GPIO_SetBits(GPIOA, GPIO_Pin_1, value);//stm32输出高低电平
}
//我们把上面这个函数设置在BSP层中,当然我们可以调用
//但是为了屏蔽,我们就要再套一层,而再套一层增加消耗,降低效率
//这时候我们可以使用宏来屏蔽,当然宏是写在中间层的
#define LED1(value) led_pin(value)
//同理按键也用类似的方法
//按键屏蔽某个脚可以更加直接
#define KEY() P1^1
#define KEY() GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1)
也许有人会说,这样和调用底层函数没啥区别,确实,但是有天我要移植到其他工程,那我只需要把中间层的头文件宏改一下就可以了,换成其他硬件的函数就可以了。
中断采集的数据,例如串口,使用回调,有返回值我们也可以用宏替换(例如ADC数据采集)
目前遇到最多的事情就是各种替换IC,器件,设备,有些东西是完全兼容的,但是往往很多都是不兼容的。
比如IC替换,所有底层都要重新,如果本来就是自己写的代码还好,要是别人写的(一般这个人都离职了),代码没有分层,那就只能重写。
外围器件,设备替换,基本都是完全兼容(当然这种一般都是会完全兼容,因为不可能重写画板,评估,仅仅是找到更便宜的器件了,或停产);但是不排除看似兼容实际不同的,这就非常坑了,如果代码写得乱,根本不敢大改,因为根本没时间。