| 碧海潮生's profile碧海潮生的小屋PhotosBlogLists | Help |
|
May 25 设计笔记——接口与实现“有时候,花点时间把编程问题分解成几个部分往往是解决它的最快方法。” —— 《C Expert Programming》 一个大型程序通常是由许多小模块组成的。 这些模块给出了程序中使用的函数、宏和数据结构。 1 结构化设计 初学程序设计的时,我们总是喜欢把所有的程序全部写在一个.c文件里。 对于小规模(几百行)的程序,这样做是可行的,维护起来也不会有太大的难度。 但是当程序的代码量达到几千行,甚至上万行时,维护起来就会力不从心了。 这时候,就需要依赖所谓的“结构化”程序设计方法了。 “结构化”程序设计,可以分为语句、函数、文件三个层面。 语句层面,所有的流程都可以化为三种控制结构:顺序、选择和循环。 函数层面,应把可重用的代码段封装成函数,并仔细斟酌功能和接口。 文件层面,就是本章所要阐述的重点——可重用软件模块设计。 2 一个例子 先举个例子说明,这是一个测试AT89S52中Watchdog Timer的小程序。 目录结构如下: wdt | +-+-platform 平台适配层 | | + +-+-platform.h 数据类型定义 | | + +-+-platform.c 临界区信号量 | +-+-uart UART驱动 | | + +-+-uart.h UART接口声明 | | + +-+-uart_cfg.h UART参数配置 | | + +-+-uart.c UART实现文件 | +-+-wdt WDT驱动 | | + +-+-wdt.h WDT接口声明 | | + +-+-wdt.c WDT实现文件 | +-+-ex1 主程序 | +-+-main.c 这个程序包含8个文件,可分为3个模块和1个主程序。 每个模块都包含.h文件和一个.c文件。 .h文件用于保存程序的声明(declaration),称为头(header)文件。 .c文件用于保存程序的实现(implementation),称为定义(definition)文件。 本程序包含的3个模块都是可重用的, 它们分别是platform(平台适配层)、uart(UART驱动)和wdt(WDT驱动)。 其中,platform模块定义了基本数据类型和临界区信号量; uart模块封装了串口操作;wdt模块则封装了Watchdog操作。 这些模块都是经过测试,正确无误的。因此,本程序的main.c显得简洁明了: #include <stdio.h> #include "..\platform\platform.h" #include "..\uart\uart.h" #include "..\wdt\wdt.h" void main() { uart_init(UART_BAUD_PRELOAD(115200)); puts("system reset ok!\n"); wdt_init(); while (1) wdt_feed(); } 3 接口 一个模块由两部分组成:接口和实现。 接口指明模块要做什么,它声明了该模块可用的标识符、类型和例程; 实现指明模块是如何完成其接口声明的目标的。 客户调用程序是使用某个模块的一段代码。 客户调用程序导入接口;而实现导出接口。 客户调用程序只需要了解接口即可。 接口与实现只需一次编写和调试就可多次使用,这就是可重用性。 接口只需要指明客户调用程序可使用的标识符即可, 应尽可能地隐藏一些无关的表示细节和算法, 这样客户调用程序可以不必依赖于特定的实现细节,即减少了它们之间的耦合。 接口在头(.h)文件中声明,该文件声明了客户调用程序可以使用的宏、类型、 数据结构、变量以及例程。 用户通过预处理指令#include导入接口。 4 实现 实现导出接口。它定义了必要的变量和函数,以提供接口所规定的功能。 一个实现揭示了表示的细节和接口给出的特定行为的算法。 理想情况是:客户调用程序根本不需要了解这些细节。 实现是由一个或多个定义(.c)文件提供的。 一个实现必须提供其导出的接口所指定的功能。 实现应包含接口的.h文件,以保证它的定义与接口的声明是一致的。 5 设计原则 1> 使用软件平台适配层(platform.c/platform.h)屏蔽软件平台(如RTOS)之间的区别。 这样,同一硬件平台(如MCU,外设等)下的某个设备驱动代码(接口和实现)适用于各个软件平台, 而不需要进行改动。 例如,样例中的uart驱动代码在无操作系统、使用RTX-51 Tiny和Small RTOS时是完全一样的。 2> 使用接口与实现分离的设计方法,尽量使函数接口独立于实现。 这样,对于不同的硬件平台,实现(如uart.c)是必须改动的, 而接口(如uart.h)则可以(或尽量)保持不变, 参数可作小改动。同时,应该根据硬件的功能和应用的要求不同添加或裁减某些接口。 例如,若硬件支持CTS/RTS握手信号,则uart驱动中应提供形如: void uart_set_rts_ctrl(int type); int uart_get_cts_status(void); 的函数接口。 3> 某些参数的计算可以作为宏定义在头文件(如uart.h)中, 这样既能提供较为灵活接口,又能使代码拥有高效的执行效率。 例如uart.h中定义了: #define UART_BAUD_PRELOAD(baud) ((uint16)(OSC_FREQ/(baud)/(OSC_PER_INST*16))) 这样,在以常数调用uart_init()时嵌入UART_BAUD_PRELOAD宏,可将大量运算工作转移到编译时,如: uart_init(UART_BAUD_PRELOAD(115200)); 4> 在每种设备驱动程序中,接口都有重要与次要之分。某些最基本的接口是必须实现的。 例如uart接口中的: void uart_init(void); int uart_rx_char(void); int uart_tx_char(int ch); 这三个函数是必须实现的。 uart_enable() uart_disable() void uart_rx_clear(void); void uart_tx_clear(void); 这四个函数/宏的重要性次之。 int uart_rx_buff(char *buff, int length); int uart_tx_buff(const char *buff, int length); int uart_tx_str(const char *str); 这三个函数可以由用户基于uart_rx_char()和uart_tx_char()实现, 驱动程序中完全可以不予提供,因此重要最低。 必须实现的接口以及接口的重要性应在设计文档中说明。 5> 某些简单的功能应使用宏代替函数实现,以减低不必要的函数调用开销。 例如uart.h中的: #define uart_enable() (ES = 1) #define uart_disable() (ES = 0) 6> 同一个接口函数可以采用不同的方式实现,以提供不同的性能(如对存储器的要求、执行速度等)。 相同的接口名称也可以根据不同需要改动参数的类型和个数以适应不同的应用。 例如,样例中的uart(u ver)微版本提供了一种不同的实现方式。 使用更少的代码量,提供更快的运行速度。且只实现了三个最基本的函数接口: void uart_init(void); int uart_rx_char(void); int uart_tx_char(int ch); 7> 应在程序中使用注释,格式和内容请参见样例代码。 8> 应规定代码版本号规则,并采用统一的命名规范打包存档。 6 结语 设计良好的通用模块库很少,其中一些可使用的库都很平庸且缺少标准。 之所以写下这若干章的内容,意图就是想在熟悉各种芯片的同时, 为设计可重用的设备驱动模块库建立一套规则。 参考文献: [1] 高质量程序设计指南——C/C++语言,林锐 [2] C Expert Programming,Peter Van Der Linden [3] C Interfaces and Implementations,David R. Hanson TrackbacksThe trackback URL for this entry is: http://jx-kingwei.spaces.live.com/blog/cns!F7A152EB74B9576E!1498.trak Weblogs that reference this entry
|
|
|