本帖最后由 21ic小喇叭 于 2017-2-7 13:32 编辑
固件库 相信来看本文的各位对单片机都有或多或少的了解,那么固件库一词应该是十分熟悉的了。 秉承序章中客观分析的原则,我们来总结一下当前对于固件库这个词的各种理解,同时提出几个较为准确的用词。
从固件库的英文名称Firmware Library可以看出,它代表的是“固化”的代码. 那么从广义上来说,由提供者写好并保证正确性的前提下,被用户使用的代码都可以叫作固件库。 以keil环境为例,我们的启动代码、C语言标准库、寄存器定义都可以算是固件库。
而目前最流行的对于固件库的解释来自于STM32的固件库。 ST官方在寄存器定义的基础上进行了进一步封装,提供了用于初始化及操作寄存器的函数。 开发人员可以快速上手这款芯片,把大部分精力用于驱动开发和业务逻辑, 可以说这是STM32如此知名的重要原因。 在前面的内容中,我们已经实现了一个8位并行IO口外设,并将它复制两份接入了51单片机的总线中。
通过0x8000-8x8001两个寄存器可以控制PortA,通过0x8002-0x8003两个寄存器可以控制PortB。 那么我们作为“硬件厂商”要为广大开发者提供便利,自然需要对这些内容进行一定的抽象, 使得C语言代码中访问寄存器的方法便于理解,契合芯片手册的内容。
在这里我们使用标准C语言配合C51的存储关键字对两套IO口的外设寄存器进行封装, 并基于此运行一个简单的回环测试程序(不算循环和初始化只有一行代码)。 希望大家可以通过这个简单的例子感受到驱动开发的思路。 在后续的例子中,随着应用系统的扩大, 我们还会增加各种层级的库(如类似STM32的固件库、HAL、驱动框架、UI框架等), 这些库的设计思想是嵌入式一个重要的门槛,将它们融入你的设计,你的开发速度会大大提高。 希望大家可以通过这些内容全方面的了解嵌入式开发中的底层内容,做到胸有成竹。
代码讲解: 从手册(其实就是硬件那一节中写的说明)中可以看出,这个外设的寄存器由2个8位寄存器组成, 偏移量为0x0000的寄存器为数据寄存器,偏移量为0x8001的寄存器为控制寄存器,其中最高位控制IO口方向,其他7位预留。 那么对于它的操作有读数据、写数据、设置方向等。
由这些分析我们可以发现,用类(class)来描述这个外设是很恰当的。 在这种简单的应用中,面向对象思想可以说是最适合组织代码的, 当然复杂场景中也一样,但是需要库的设计者懂得更多的设计模式,投入更多精力。
那么我们如何用C语言来实现面向对象呢?由于C语言没有从语法层面上支持访问控制(public private), 也就是说我们不能强制用户(库调用者)使用接口函数来访问外设, 那么我们只能退而求其次,为用户提供接口函数,同时不强制使用。 这样用户在访问寄存器时既可以使用接口函数,也可以手动访问寄存器实现, 当然也可以混着来(前提是知道自己在干什么)。
当然,如果有了操作系统,我们可以通过系统线程实现一个真正意义上的访问控制, 可以参考linux的设备驱动思路(POSIX接口),或者windows的句柄系统。 当然后续的内容中也会实现这样的框架,有兴趣的朋友可以在顶楼找到联系方式联系我。 这里由于功能简单,我们只实现数据结构部分(结构体的定义,外设常量的定义), 对于该外设的封装,以及接口函数的实现留给大家作为练手项目。
那么请看代码
<div>#include <reg52.h>
#include <absacc.h> // For Reference
*----------------------------------------------------------------------------*/
/* Global Data Type */
typedef unsigned char uint8_s;
/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
/* Firmware Library */
typedef struct io_port_reg_mode{
uint8_s Reserve:7;
uint8_s InOut:1;
}IO_Reg_Mode;
#define IO_MODE_OUT 0
#define IO_MODE_IN 1
typedef struct io_port_reg{
uint8_s Data;
IO_Reg_Mode Mode;
}IO_Reg;
IO_Reg volatile xdata * const IOA_Reg = (IO_Reg volatile xdata *)0x8000;
IO_Reg volatile xdata * const IOB_Reg = (IO_Reg volatile xdata *)0x8002;
/*----------------------------------------------------------------------------*/
int main(void)
{
/* Initial */
IOA_Reg->Mode.InOut = IO_MODE_OUT;
IOB_Reg->Mode.InOut = IO_MODE_IN;
/* Main Loop */
for(;;)
{
IOA_Reg->Data = IOB_Reg->Data;
}
}</div><div>
</div>
这里使用了位域对控制寄存器进行封装,同时将数据寄存器和控制寄存器定义到一个结构体中,作为该外设的寄存器结构。
对于PortA和PortB两个端口的寄存器指针常量定义,xdata是C51中的扩展关键字,对应我们硬件中所连接的总线。 volatile是C语言中代表“不优化”的关键字,用这个关键字修饰的变量不会被编译器以“没意义、没作用”为由进行优化, 而是在任何时候都与C语言代码保持一致,对于外设寄存器的定义使用这个关键字是一个很好的习惯。
关于代码相信大家在学习过程中已经见了很多了,所以在这里只讲解上面两点, 如果大家有什么意见建议,回帖见哦~我将会进行修改补充,感谢大家的支持!
|