何为总线? 百度百科给出的定义为:总线(Bus)是指计算机组件间规范化的交换数据(data)的方式,即以一种通用的方式为各组件提供数据传送和控制逻辑。
在嵌入式编程中,IIC总线和SPI总线驱动是用的比较多的,在裸机编程中,如果有多个SPI/IIC设备,我们可能每个设备的驱动都要写一遍,这样增加了代码的冗余度,亦不便于管理。
所以我们可以将这个驱动协议抽象提取,以指针化的方式编写协议驱动,然后每个设备只要初始化几个硬件接口函数(因为最终还是要驱动GPIO的),然后注册(传入相应的设备指针)对应的设备 即可。
先以IIC协议设备为例说明:
//----------i2c对接接口函数指针------- typedef struct { unsigned char trans_mode;//传输模式,0:默认是从MSB开始传输(通用型),1:从LSB开始传输(特别型) void (*gpio_init)(void); void (*sda_mode)(unsigned char ); unsigned char (*sda_in)(void); void (*sda_out)(unsigned char ); void (*sck_out)(unsigned char ); void (*delay_us)(unsigned int); void (*delay_ms)(unsigned int); }i2c_st; //-----------i2c标准接口--------- void iic_start(i2c_st *const i2c_me); void iic_stop(i2c_st *const i2c_me); unsigned char iic_wait_ack(i2c_st *const i2c_me); void iic_ack(i2c_st *const i2c_me); void iic_nack(i2c_st *const i2c_me); void iic_delayms(i2c_st *const i2c_me,unsigned short int s); void iic_sendbyte(i2c_st *const i2c_me,unsigned char tbyte); unsigned char iic_readbyte(i2c_st *const i2c_me,unsigned char ack); void iic_init(i2c_st *const i2c_me); unsigned char data_bitreverse(unsigned char data) { static unsigned char temp_data=0; for(unsigned char i=0;i<8;i++)//MSB { temp_data<<=1; temp_data^=data&0x01; data>>=1; } return temp_data; } //--------------标准I2C协议框架---------------------------- void iic_start(i2c_st *const i2c_me) { i2c_me->sda_mode(1); i2c_me->sda_out(1); i2c_me->sck_out(1); i2c_me->delay_us(4); i2c_me->sda_out(0); i2c_me->delay_us(4); i2c_me->sck_out(0); } void iic_stop(i2c_st *const i2c_me) { i2c_me->sda_mode(1);//sda线输出 i2c_me->sck_out(0); i2c_me->sda_out(0);//STOP:when CLK is high DATA change form low to high i2c_me->delay_us(4); i2c_me->sck_out(1); i2c_me->sda_out(1);//发送I2C总线结束信号 i2c_me->delay_us(4); } unsigned char iic_wait_ack(i2c_st *const i2c_me) { unsigned char ErrTime=0; i2c_me->sda_mode(0); //SDA设置为输入 i2c_me->sda_out(1); i2c_me->delay_us(1); i2c_me->sck_out(1); i2c_me->delay_us(1); while(i2c_me->sda_in()) { if(ErrTime++>250) { iic_stop(i2c_me); return 1; } } i2c_me->sck_out(0);//时钟输出0 return 0; } void iic_ack(i2c_st *const i2c_me) { i2c_me->sck_out(0); i2c_me->sda_mode(1); i2c_me->sda_out(0); i2c_me->delay_us(2); i2c_me->sck_out(1); i2c_me->delay_us(2); i2c_me->sck_out(0); } void iic_nack(i2c_st *const i2c_me) { i2c_me->sck_out(0); i2c_me->sda_mode(1); i2c_me->sda_out(1); i2c_me->delay_us(2); i2c_me->sck_out(1); i2c_me->delay_us(2); i2c_me->sck_out(0); } void iic_delayms(i2c_st *const i2c_me,unsigned short int s) { i2c_me->delay_ms(s); } void iic_sendbyte(i2c_st *const i2c_me,unsigned char tbyte) { unsigned char t,temp_tbyte=tbyte; if(i2c_me->trans_mode) //LSB模式 temp_tbyte=data_bitreverse(tbyte);//位序颠倒 //默认是MSB位开始传输 i2c_me->sda_mode(1); i2c_me->sck_out(0); //拉低时钟开始数据传输 for(t=0;t<8;t++) { i2c_me->sda_out((temp_tbyte&0x80)>>7); temp_tbyte<<=1; i2c_me->delay_us(2); i2c_me->sck_out(1); i2c_me->delay_us(2); i2c_me->sck_out(0); i2c_me->delay_us(2); } } unsigned char iic_readbyte(i2c_st *const i2c_me,unsigned char ack) { unsigned char i,receive=0; i2c_me->sda_mode(0); //SDA设置为输入 for(i=0;i<8;i++ ) { i2c_me->sck_out(0); i2c_me->delay_us(2); i2c_me->sck_out(1); receive<<=1; if(i2c_me->sda_in()) { receive++; } i2c_me->delay_us(2); } if (!ack) iic_nack(i2c_me);//发送nACK else iic_ack(i2c_me); //发送ACK return receive; } void iic_init(i2c_st *const i2c_me) { i2c_me->gpio_init(); i2c_me->sck_out(1); i2c_me->sda_out(1); }此上就是一份IIC的驱动协议框架了,可以看到是不直接跟硬件层关联的,全部以函数指针抽象出来了。
下面就是驱动实际硬件设备,如应用最多的EEPROM驱动24C02;
typedef enum { AT_NULL=0, AT24C01, /*127*/ AT24C02, /*255*/ AT24C04, /*511*/ AT24C08, /*1023*/ AT24C16 , /*2047*/ AT24C32 , /*4095*/ AT24C64 , /*8191*/ AT24C128, /*16383*/ AT24C256, /*32767*/ }at24c_type; typedef struct { at24c_type type; i2c_st i2c; }at24c_device_st;//多设备然后实现协议要求的接口函数,就是关联硬件层:
void at24c_gpio_init(void) { GPIO_InitType GPIO_InitStructure; RCC_APB2PeriphClockCmd(AT24CXX_PCC,ENABLE); GPIO_InitStructure.GPIO_Pins = AT24CXX_SCL_PIN|AT24CXX_SDA_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT_PP; //推挽输出 GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz; GPIO_Init(AT24CXX_PORT, &GPIO_InitStructure); } void at24c_sda_mode(unsigned char s) { if(0!=s) { AT24C_SDA_OUT(); } else { AT24C_SDA_IN(); } } void at24c_sda_out(unsigned char s) { AT24C_SDA(s); } void at24c_sck_out(unsigned char s) { AT24C_SCL(s); } unsigned char at24c_sda_read(void) { return AT24C_READ_SDA(); }然后注册设备以及初始化的对接:
/*设备句柄*/ at24c_device_st at24c02= { .i2c.trans_mode =0,//MSB开始传输 .i2c.gpio_init = at24c_gpio_init, .i2c.sda_mode = at24c_sda_mode, .i2c.sda_in = at24c_sda_read, .i2c.sda_out = at24c_sda_out, .i2c.sck_out = at24c_sck_out, .i2c.delay_us = xdelay_us, .i2c.delay_ms = xdelay_ms, };注册挂载函数
void at24c_device_register(at24c_type at_type,at24c_device_st *const at24c_device) { at24c_device->type=at_type; iic_init(&at24c_device->i2c); //挂载I2C }EPPROM的读写函数片段
unsigned char at24c_readbyte(at24c_device_st *const at24c_device,unsigned short int reg_addr) { unsigned char temp=0; iic_start(&at24c_device->i2c); if(at24c_device->type>AT24C16) { iic_sendbyte(&at24c_device->i2c,0XA0); //发送写命令 iic_wait_ack(&at24c_device->i2c); iic_sendbyte(&at24c_device->i2c,reg_addr>>8);//发送高地址 iic_wait_ack(&at24c_device->i2c); } else { iic_sendbyte(&at24c_device->i2c,0XA0+((reg_addr/256)<<1)); //发送器件地址0XA0,写数据 } iic_wait_ack(&at24c_device->i2c); iic_sendbyte(&at24c_device->i2c,reg_addr%256); //发送低地址 iic_wait_ack(&at24c_device->i2c); iic_start(&at24c_device->i2c); iic_sendbyte(&at24c_device->i2c,0XA1); //进入接收模式 iic_wait_ack(&at24c_device->i2c); temp=iic_readbyte(&at24c_device->i2c,0); iic_stop(&at24c_device->i2c); return temp; } void at24c_writebyte(at24c_device_st *const at24c_device,unsigned short int waddr,unsigned char wdata) { iic_start(&at24c_device->i2c); if(at24c_device->type>AT24C16) { iic_sendbyte(&at24c_device->i2c,0XA0); //发送写命令 iic_wait_ack(&at24c_device->i2c); iic_sendbyte(&at24c_device->i2c,waddr>>8);//发送高地址 iic_wait_ack(&at24c_device->i2c); } else { iic_sendbyte(&at24c_device->i2c,0XA0+((waddr/256)<<1)); //发送器件地址0XA0,写数据 } iic_wait_ack(&at24c_device->i2c); iic_sendbyte(&at24c_device->i2c,waddr%256); //发送低地址 iic_wait_ack(&at24c_device->i2c); iic_sendbyte(&at24c_device->i2c,wdata); //发送字节 iic_wait_ack(&at24c_device->i2c); iic_stop(&at24c_device->i2c);//产生一个停止条件 iic_delayms(&at24c_device->i2c,10); }最后在main里面初始化语句函数即可
at24c_device_register(AT24C02,&at24c02);假如我们还需要再初始化一个IIC设备,如显示驱动芯片TM1640,其为非标IIC,根据手册它的的数据传输顺序是从低位传输。由于在抽象协议里面我们已经加了对这个的兼容,所以声明的时候,只要将其标志置位就行。
如下:
tm1640_i2c= { .trans_mode =1,//LSB开始传输 .gpio_init = tm1640_gpio_init, // .sda_mode = tm1640_sda_mode, .sda_in = tm1640_sda_read, .sda_out = tm1640_sda_out, .sck_out = tm1640_sck_out, .delay_us = delay_us, .delay_ms = delay_ms, };然后实现接口函数
//-----------------需要实现的对接IIC协议接口的函数----------------- void tm1640_gpio_init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(TM_PCC|RCC_APB2Periph_AFIO,ENABLE ); PWR_BackupAccessCmd(ENABLE);/* 允许修改RTC和后备寄存器*/ RCC_LSEConfig(RCC_LSE_OFF); /* 关闭外部低速时钟,PC14+PC15可以用作普通IO*/ GPIO_InitStructure.GPIO_Pin = TM_SCL_PIN|TM_SDA_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(TM_PORT, &GPIO_InitStructure); PWR_BackupAccessCmd(DISABLE);/* 禁止修改RTC和后备寄存器*/ } void tm1640_sda_mode(unsigned char s) { if(0!=s) { SDA3_OUT; } else { SDA3_IN; } } void tm1640_sda_out(unsigned char s) { IIC_SDA3(s); } void tm1640_sck_out(unsigned char s) { IIC_SCL3(s); } unsigned char tm1640_sda_read(void) { return READ_SDA3; }读写函数(这里只有写操作)
void tm1640_writeregister(unsigned char reg_data) { iic_start(&tm1640_i2c); iic_sendbyte(&tm1640_i2c,reg_data);//写寄存器数据 iic_stop(&tm1640_i2c); } void tm1640_writedata(unsigned char waddr,unsigned char wdata) { iic_start(&tm1640_i2c); iic_sendbyte(&tm1640_i2c,ADDR_CMD|waddr); iic_sendbyte(&tm1640_i2c,wdata); iic_stop(&tm1640_i2c); }然后声明初始化即可:
void tm1640_init(void) { iic_init(&tm1640_i2c); tm1640_cfg_init(); tm1640_writedata(8,0x00); tm1640_writedata(9,0x00); tm1640_writendatas(0,0x00,8); tm1640_writeregister(DIS_CMD|0x0F);//开显示,并且亮度最大 }这样明显少了许多不必要的重复步骤,实际只需要实现几个接口函数即可,对于其他的如SPI也可以抽象提取。