|
这里假设你了解12864液晶屏的驱动,本文档不打算教会你如何驱动12864,它只是解释这份代码,如何以一种不一样的方式封装驱动。
简单的说,任何外设驱动,说到底无非就是io时序而已。
纯c代码实现,以满足绝大多数单片机直接使用;但是它使用结构体和函数指针实现了一个仿面向对象 类的封装;
概括地说,它抽象了两个互相隔离的,平行的结构体,一个是实现mcu gpio和 12864引脚的绑定,是为 驱动结构体。
另一个是 通过写命令,写数据 等操作12864的 操作结构体。
首先简单说一下封装的几个主要目的:
要在程序结构(层次结构,数据结构)上最大程度表达事物对象本身的逻辑结构;这样才可以“面向对象”地去看待,使程序的可读性和易理解程度得到加强;
它要实现类的 继承性,子层次结构。
为了防止复杂度,最大可能性减少不必要的层次嵌套;
以下是程序中使用的相关结构体——理解了它们,就可以理解整个结构,无须太在乎实现代码细节。能写12864的人都可以自己实现。
typedef struct
{
pVoidFunVoid initial;
pVoidFunU8 SCK;
pVoidFunU8 SDA;
pVoidFunU8 CS;
pVoidFunU8 RES;
pVoidFunU8 A0;
}Serial_12864;
typedef struct
{
pVoidFunVoid initial;
pVoidFunU8 data[8];
pVoidFunU8 CS;
pVoidFunU8 RES;
pVoidFunU8 A0;
pVoidFunU8 WR;
pVoidFunU8 RD;
}Parallel_80_12864;
typedef union
{
Serial_12864 Serial_Mode;
Parallel_80_12864 Para_80_Mode;
}LCD12864_Driver;
typedef enum
{
NOT_SET_YET = 0,
SERIAL_12864 = 1,
PARA_80_12864 = 2,
PARA_68_12864 = 3,
}LCD12864_DriveMode;
typedef struct
{
LCD12864_DriveMode mode;
LCD12864_Driver driver;
}LCD12864;
Driver.
Driver是这样一个东西。它把每个IO的写高写低(一个函数)绑定在一个 函数指针里,然后把所有这些函数指针封装成一个结构体,也就是最开始的两个
Serial_12864 和 Parallel_80_12864 驱动(因为12864的读写方式有一种串行,两种并行,这里只做了其中一种。但就此结构设计,要新增一种是非常容易的事情,而且对原有改动很小。)
我们对12864的任何操作都是以操作这些时序完成的,因此,我们必须有一个承载这些IO写状态的结构;
接下来,我们需要另一套结构,它是为了在一个更高的层次上抽象12864的所有操作。
typedef struct
{
void (*WriteCommand)(void *driver,U8 Command);
void (*WriteData)(void *driver,U8 Data);
void (*PowerOn)(void *driver);
void *driver;
}Op12864;
这个地方我非常不爽,因为结构体,我一直实在想不出有什么办法,可以让它像类一样,各成员之间直接互相访问。
比如说,让writecommand可以直接访问 driver,那就好了~~
不过不要紧,让梦想打八折。
多带一个参数也不是太要命的事情~~~
这里解释一下为什么用void*
这是为了让这个结构体能以更开放的姿态承载各种各样不一样定义的 driver结构体——比如前面我介绍的那一种就是我实现的一种,你要是喜欢你也可以自己定义一种。
最后剩下一个问题
如何沟通这两个结构体——专业或者说装逼点的说法是,怎么连接 驱动结构体 和 操作结构体(Op12864)
驱动结构体 和 操作结构体 是两个独立平行的结构。这是为了最大层度减少 驱动 和 操作 这两个层面之间的耦合关系。也就是说,你爱怎么改是你的事,反正我这边照用不误。
因此,另一个角度来说,它们也就是 兵不识将,将不识兵。所以这个地方,一个核心的地方是使用了void*这个通用指针。
所以在连接 两个结构体的时候,必须让驱动结构的实现方,根据自己的结构体,来实现 这个连接函数,或者叫做 注册函数。
这里抽取这个函数作为例子
// 这个函数是一定不能封进实现里的;
// 所以 12864_Api,Op12864也是一个它的消费者....
Op12864 LCD12864_Reg_2_Op12864(void *lcd)
{
Op12864 op;
LCD12864 *plcd = (LCD12864 *)lcd;
op.driver = plcd;
if(isLCD12864_Regist(*plcd) == True)
(*plcd->driver.Para_80_Mode.initial)();
op.WriteCommand = LCD12864_WriteCommand;
op.WriteData = LCD12864_WriteData;
op.PowerOn = LCD12864_PowerOn;
return op;
}
至于剩下的,大家伙就自己看程序吧。程序还很简单,只是一个画国际象棋的demo函数。
过后我要在这个基础上,写一个 多级菜单界面,当然那就是另一回事了,敬请期待。
|
|