tag 标签: 软件分层

相关博文
  • 热度 26
    2013-3-19 16:28
    1761 次阅读|
    9 个评论
           做MCU开发已经快将近三年了,多多少少理解了一些东西,我现在来讲讲关于如何封转模块和如何合理的分层。         从学MCU开始,就从论坛上和身边的学长听到了“模块化”一词,这一路走来,总算是摸了一些门道,或许是我实践的不够,由于工作上软硬件都要做,做软件也做的不算太多。从最初封装外设开始,用一个C文件和H文件来集成一个外设的所有功能,到现在的分层包装。其实这些都是软件工程上的东西,只不过对于我们这些学信息工程的人来说没有接触。        当然包装的越好的模块消耗不必要的资源就会越多,对于有限的MCU来说,适当即可。来看一个简单的SPI外设的例子:     struct SPI_Driver {       //-------controller mode -----//      HANDLER01 SPI_Init;      HANDLER03 RdBusyFlag;      HANDLER03 WrBusyFlag;     //----------io mode-----------//      HANDLER02 SPI_Clk;      HANDLER03 SPI_InputData;      HANDLER02 SPI_OutputData;      HANDLER02 SPI_Cs; }; //spi max clk fre is 1000000 hz. struct SPI_Device {      struct SPI_Driver SpiDriver_mode;      struct head_list  SpiHead_list;      UINT16 SpiNum;      UINT16 SpiData;      UINT8  SpiCtlMode; //spi 模式.      UINT8  SpiTriggerMode; //spi trigger mode .for example up or down.      UINT8  SpiSendFSM; //send fsm.     UINT8  SpiRECEFSM; //receive fsm. };   //函数声明区 UINT8 ConfigSpiModule(UINT8 num, UINT32 fcy); UINT8 RegisterSpiDeviceDriver(UINT8 device_id, struct SPI_Driver *spi_operation); UINT8 SetSpiDeviceMode(UINT8 device_id, UINT8 ctl_mode, UINT8 trigger_mode); UINT8 SpiDeviceRdData(UINT8 device_id, UINT16 *data, UINT8 data_bit_num); UINT8 SpiDeviceWrData(UINT8 device_id, UINT16 *data, UINT8 data_bit_num);   这是SPI协议层的头文件,我们通过一些变量来描述该协议所具有的属性和所需要的功能,有点类似于类的包装。由于目前多数MCU都是有SPI模块的,为了构成通用,用函数指针来分别表示需要的函数接口,如果是用IO模拟SPI设备,则将用到的IO功能用函数指针来表示。         通过对SPI的理解,我们将模块包装成可以同时管理多个SPI设备,而第对他们有序的操作,由于每种SPI外设可能在读取数据位数和距离命令方面有区别,所以我列出了上述几个函数接口。       下面实现模块注册和配置函数。   UINT8 ConfigSpiModule(UINT8 num, UINT32 fcy) { _spi_num = num; _spi_fcy = fcy; _spi_delay_count = _spi_fcy / SPI_MAX_FCY; _spi_device_info = (struct SPI_Device*)My_malloc(sizeof(struct SPI_Device) * num); if(_spi_device_info == 0) return 1; My_memset((UINT8 *)_spi_device_info, sizeof(struct SPI_Device) * num, 0x00);   return 0;   //error return. }   /* register spi device driver. */ UINT8 RegisterSpiDeviceDriver(UINT8 device_id, struct SPI_Driver *spi_operation) { if(device_id _spi_num) { _spi_device_info .SpiDriver_mode = *spi_operation; } else return 1; return 0; } /* set spi device mode. */ UINT8 SetSpiDeviceMode(UINT8 device_id, UINT8 ctl_mode, UINT8 trigger_mode) { if(device_id _spi_num) { _spi_device_info .SpiCtlMode = ctl_mode; _spi_device_info .SpiTriggerMode = trigger_mode; } else return 1; return 0; }   当使用上述三个函数接口对SPI外设进行注册和配置后,就可以独立操作每一个外设了,下面是一个SPI交换数据的DA例子。   void Reigster_TLV5620_Drv(struct SPI_Driver *TLV 5620_operation, HANDLER02 LdacPort,   HANDLER02 LoadPort, UINT8 driver_mode) { _TLV5620_Info.LdacPort = LdacPort; _TLV5620_Info.LoadPort = LoadPort;   RegisterSpiDeviceDriver(TLV5620_DEVICE_ID, TLV5620_operation); SetSpiDeviceMode(TLV5620_DEVICE_ID, driver_mode, DOWN_TRIGGER); TLV5620_ref_voltage = REF_VOLTAGE;       //defalut value. }   这样就完成了外设DA的配置了,只需要使用这个读数据函数 SpiDeviceWrData(TLV5620_DEVICE_ID, temp, 11); //11为11位数,即通过spi传输数据位数 ,就可以将数据读到temp的变量中了。   这样一来我的这个SPI模块无论在哪里都可以使用了,实现了通用型。   当然讲了那么多的废话,其实包装模块就是抽取共性,当开始编程时,感觉到代码中有重复的东西,你可以不妨试试抽取共性,然后包装成模块,当需要使用底层的IO或者资源时,全部使用函数接口或者函数指针方式代替那部分功能,这样就实现了模块通用。   欢迎大家交流。  
  • 热度 40
    2013-2-24 17:48
    5646 次阅读|
    13 个评论
    模块化编程简明教程 林德光 ldeguang1991@gmail.com      模块化编程是嵌入式一门很重要的编程能力。对于一个稍微复杂的系统来说,编程时我们不可能把它全部写在一个 .c 文件里,这样很导致文件超级长,不利于调试,从审美角度来说,也显得毫无章法。更明智的做法就是采用模块化编程。下面就模块化编程进行简单讲解。(注:平台 win7 ,环境 keil4 ,处理器 STC1T )      先给一个大致的印象。      一个 demo 工程文件下的文件夹如图        这种编程做法的优点就是结构清晰,逻辑性好。下面进行演示。      首先,新建一个文件夹(这里我放在桌面的 demo 文件夹) 在 demo 文件夹下新建五个文件夹 COMM 、 HARDWARE 、 OUTPUT 、 SYSTEM 、 USER (这只是我常用的做法,你也可以做相应修改) 其中, USER        : 用于存放用户文件:包括工程项目文件、主函数文件; SYSTEM      : 用于存放系统资源文件,如 uart,timer 等; HARDWARE    : 用于存放板载硬件资源,如 DS18b20,tube,key,lcd 等; COMM        : 用于存放通讯协议文件,如 I2C,SPI 等; OUTPUT      : 用于存放编译过程产生的“垃圾文件”,如,目标文件 (.obj) ,链接文                 件 (.lst) , hex 文件等。        然后,打开 keil4 ,新建工程,保存在 USER 文件夹下,如图,      接下来的芯片选择等我就不多说了,你懂的……      新建 demo.c ,用于编写主函数 , 保存在 USER 目录下。现在 USER 目录下文件有:      现在假设我们要加入 LED 模块,那么我们就在 HARDWARE 文件下新建 led 文件夹, 打开 led 文件夹,新建 led.h , led.c ,如图:      这里说明一下, .h 文件一般负责“声明”函数和配置, .c 文件负责“定义”函数。      分别编写 .h 和 .c 文件,这里我简单 demo 一下:      led.h      led.c   现在我们编写好了模块文件,重新打开 demo 工程,首先进行文件组织, 在 Groups 下删掉 Source Group1 ,新建 USER 、 HARDWARE ,(其他的文件夹就不新建了, demo 工程没有用到)      选中 Groups 下 USER ,在 Files 框下点击 Add Files ,添加进 USER 文件夹下的 .c 文件, 选中 Groups 下 HARDWARE ,在 Files 框下点击 Add Files ,添加进 USER 文件夹下的 .c 文件, 添加完,就这样…… 弄完就点 OK ,返回到主界面, 可以看到文件结构树已经跟我们一开始新建的文件夹结构统一了,如图(其他文件夹用不到,先忽略) 接着,设置中间过程产生的文件的放置位置   编写主函数,编译: 编译之后,可以发现编译器编译出错,说找不到 led.h 于是,我们还必须进行一项设置(编译路径) 编译路径我们添加进去了,再重新编译, 可以看到没有报错,(一个警告是因为我们编写的另外一个函数 LED_OFF() 函数没有调用),如果想不要这个警告可以这样设置, 大功告成,以后添加模块这可以按这个步骤就可以很快构建自己的工程文件了。