做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[device_id].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[device_id].SpiCtlMode = ctl_mode;
_spi_device_info[device_id].SpiTriggerMode = trigger_mode;
}
else
return 1;
return 0;
}
当使用上述三个函数接口对SPI外设进行注册和配置后,就可以独立操作每一个外设了,下面是一个SPI交换数据的DA例子。
void Reigster_TLV5620_Drv(struct SPI_Driver *TLV5620_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或者资源时,全部使用函数接口或者函数指针方式代替那部分功能,这样就实现了模块通用。
欢迎大家交流。