如何实现IIC驱动封装以及AT24CXX存储器的封装

简述
    iic(inter-integrated circuit)其实是iicbus简称,它是一种串行通信总线,使用多主从架构,在stm32开发中经常见到。
    关于iic可以参考之前发的一篇文章:《通信协议 iic 与 spi 最全对比》来了解。
    使用面向对象的编程思想封装iic驱动,将iic的属性和操作封装成一个库,在需要创建一个iic设备时只需要实例化一个iic对象即可,本文是基于stm32和hal库做进一步封装的。
    底层驱动方法不重要,封装的思想很重要。在完成对iic驱动的封装之后借助继承特性实现at24c64存储器的驱动开发,仍使用面向对象的思想封装at24c64驱动。
iic驱动面向对象封装     iic.h头文件主要是类模板的定义,具体如下:
                                      //定义iic类typedef struct iic_type{//属性 gpio_typedef *gpiox_scl; //gpio_scl所属的gpio组(如:gpioa) gpio_typedef *gpiox_sda; //gpio_sda所属的gpio组(如:gpioa)uint32_t gpio_scl; //gpio_scl的io引脚(如:gpio_pin_0)uint32_t gpio_sda; //gpio_sda的io引脚(如:gpio_pin_0)//操作void (*iic_init)(const struct iic_type*); //iic_initvoid (*iic_start)(const struct iic_type*); //iic_startvoid (*iic_stop)(const struct iic_type*); //iic_stopuint8_t (*iic_wait_ack)(const struct iic_type*); //iic_wait_ack,返回wait失败或是成功void (*iic_ack)(const struct iic_type*); //iic_ack,iic发送ack信号void (*iic_nack)(const struct iic_type*); //iic_nack,iic发送nack信号void (*iic_send_byte)(const struct iic_type*,uint8_t); //iic_send_byte,入口参数为要发送的字节uint8_t (*iic_read_byte)(const struct iic_type*,uint8_t); //iic_send_byte,入口参数为是否要发送ack信号void (*delay_us)(uint32_t); //us延时}iic_typedef; iic.c源文件主要是类模板具体操作函数的实现,具体如下:                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         //设置sda为输入模式static void sda_in(const struct iic_type* iic_type_t){ uint8_t io_num = 0; //定义io num号switch(iic_type_t->gpio_sda) {case gpio_pin_0: io_num = 0;break;case gpio_pin_1: io_num = 1;break; case gpio_pin_2: io_num = 2;break; case gpio_pin_3: io_num = 3;break;case gpio_pin_4: io_num = 4;break; case gpio_pin_5: io_num = 5;break; case gpio_pin_6: io_num = 6;break; case gpio_pin_7: io_num = 7;break;case gpio_pin_8: io_num = 8;break; case gpio_pin_9: io_num = 9;break;case gpio_pin_10: io_num = 10;break;case gpio_pin_11: io_num = 11;break; case gpio_pin_12: io_num = 12;break;case gpio_pin_13: io_num = 13;break;case gpio_pin_14: io_num = 14;break; case gpio_pin_15: io_num = 15;break; } iic_type_t->gpiox_sda->moder&=~(3gpiox_sda->moder|=0gpio_sda) {case gpio_pin_0: io_num = 0;break;case gpio_pin_1: io_num = 1;break; case gpio_pin_2: io_num = 2;break; case gpio_pin_3: io_num = 3;break;case gpio_pin_4: io_num = 4;break; case gpio_pin_5: io_num = 5;break; case gpio_pin_6: io_num = 6;break; case gpio_pin_7: io_num = 7;break;case gpio_pin_8: io_num = 8;break; case gpio_pin_9: io_num = 9;break;case gpio_pin_10: io_num = 10;break;case gpio_pin_11: io_num = 11;break; case gpio_pin_12: io_num = 12;break;case gpio_pin_13: io_num = 13;break;case gpio_pin_14: io_num = 14;break; case gpio_pin_15: io_num = 15;break; } iic_type_t->gpiox_sda->moder&=~(3gpiox_sda->moder|=1gpiox_scl,iic_type_t->gpio_scl,gpio_pin_set); //设置scl为高电平 }else{ hal_gpio_writepin(iic_type_t->gpiox_scl,iic_type_t->gpio_scl,gpio_pin_reset); //设置scl为低电平 }}//设置sda电平static void iic_sda(const struct iic_type* iic_type_t,int n){if(n == 1) { hal_gpio_writepin(iic_type_t->gpiox_sda,iic_type_t->gpio_sda,gpio_pin_set); //设置sda为高电平 }else{ hal_gpio_writepin(iic_type_t->gpiox_sda,iic_type_t->gpio_sda,gpio_pin_reset); //设置sda为低电平 }}//读取sda电平static uint8_t read_sda(const struct iic_type* iic_type_t){return hal_gpio_readpin(iic_type_t->gpiox_sda,iic_type_t->gpio_sda); //读取sda电平}//iic初始化static void iic_init_t(const struct iic_type* iic_type_t){ gpio_inittypedef gpio_initure;//根据gpio组初始化gpio时钟if(iic_type_t->gpiox_scl == gpioa || iic_type_t->gpiox_sda == gpioa) { __hal_rcc_gpioa_clk_enable(); //使能gpioa时钟 }if(iic_type_t->gpiox_scl == gpiob || iic_type_t->gpiox_sda == gpiob) { __hal_rcc_gpiob_clk_enable(); //使能gpiob时钟 }if(iic_type_t->gpiox_scl == gpioc || iic_type_t->gpiox_sda == gpioc) { __hal_rcc_gpioc_clk_enable(); //使能gpioc时钟 }if(iic_type_t->gpiox_scl == gpiod || iic_type_t->gpiox_sda == gpiod) { __hal_rcc_gpiod_clk_enable(); //使能gpiod时钟 }if(iic_type_t->gpiox_scl == gpioe || iic_type_t->gpiox_sda == gpioe) { __hal_rcc_gpioe_clk_enable(); //使能gpioe时钟 } if(iic_type_t->gpiox_scl == gpioh || iic_type_t->gpiox_sda == gpioh) { __hal_rcc_gpioh_clk_enable(); //使能gpioh时钟 } //gpio_scl初始化设置 gpio_initure.pin=iic_type_t->gpio_scl; gpio_initure.mode=gpio_mode_output_pp; //推挽输出 gpio_initure.pull=gpio_pullup; //上拉 gpio_initure.speed=gpio_speed_freq_very_high; //快速 hal_gpio_init(iic_type_t->gpiox_scl,&gpio_initure);//gpio_sda初始化设置 gpio_initure.pin=iic_type_t->gpio_sda; gpio_initure.mode=gpio_mode_output_pp; //推挽输出 gpio_initure.pull=gpio_pullup; //上拉 gpio_initure.speed=gpio_speed_freq_very_high; //快速 hal_gpio_init(iic_type_t->gpiox_sda,&gpio_initure);//scl与sda的初始化均为高电平 iic_scl(iic_type_t,1); iic_sda(iic_type_t,1);}//iic startstatic void iic_start_t(const struct iic_type* iic_type_t){ sda_out(iic_type_t); //sda线输出 iic_sda(iic_type_t,1); iic_scl(iic_type_t,1); iic_type_t->delay_us(4); iic_sda(iic_type_t,0); //start:when clk is high,data change form high to low iic_type_t->delay_us(4); iic_scl(iic_type_t,0); //钳住i2c总线,准备发送或接收数据 }//iic stopstatic void iic_stop_t(const struct iic_type* iic_type_t){ sda_out(iic_type_t); //sda线输出 iic_scl(iic_type_t,0); iic_sda(iic_type_t,0); //stop:when clk is high data change form low to high iic_type_t->delay_us(4); iic_scl(iic_type_t,1); iic_sda(iic_type_t,1); //发送i2c总线结束信号 iic_type_t->delay_us(4); }//iic_wait_ack 返回hal_ok表示wait成功,返回hal_error表示wait失败static uint8_t iic_wait_ack_t(const struct iic_type* iic_type_t) //iic_wait_ack,返回wait失败或是成功{ uint8_t ucerrtime = 0; sda_in(iic_type_t); //sda设置为输入 iic_sda(iic_type_t,1);iic_type_t->delay_us(1); iic_scl(iic_type_t,1);iic_type_t->delay_us(1);while(read_sda(iic_type_t)) { ucerrtime++;if(ucerrtime>250) { iic_type_t->iic_stop(iic_type_t);return hal_error; } } iic_scl(iic_type_t,0);//时钟输出0 return hal_ok; }//产生ack应答static void iic_ack_t(const struct iic_type* iic_type_t) { iic_scl(iic_type_t,0); sda_out(iic_type_t); iic_sda(iic_type_t,0); iic_type_t->delay_us(2); iic_scl(iic_type_t,1); iic_type_t->delay_us(2); iic_scl(iic_type_t,0);}//产生nack应答static void iic_nack_t(const struct iic_type* iic_type_t) { iic_scl(iic_type_t,0); sda_out(iic_type_t); iic_sda(iic_type_t,1); iic_type_t->delay_us(2); iic_scl(iic_type_t,1); iic_type_t->delay_us(2); iic_scl(iic_type_t,0);}//iic_send_byte,入口参数为要发送的字节static void iic_send_byte_t(const struct iic_type* iic_type_t,uint8_t txd) { uint8_t t = 0; sda_out(iic_type_t); iic_scl(iic_type_t,0);//拉低时钟开始数据传输for(t=0;t>7); txd delay_us(2); iic_scl(iic_type_t,0); iic_type_t->delay_us(2); } }//iic_send_byte,入口参数为是否要发送ack信号static uint8_t iic_read_byte_t(const struct iic_type* iic_type_t,uint8_t ack) { uint8_t i,receive = 0; sda_in(iic_type_t);//sda设置为输入for(i=0;idelay_us(2); iic_scl(iic_type_t,1); receiveiic_nack(iic_type_t);//发送nackelse iic_type_t->iic_ack(iic_type_t); //发送ack return receive;}//实例化一个iic1外设,相当于一个结构体变量,可以直接在其他文件中使用iic_typedef iic1 = { .gpiox_scl = gpioa, //gpio组为gpioa .gpiox_sda = gpioa, //gpio组为gpioa .gpio_scl = gpio_pin_5, //gpio为pin5 .gpio_sda = gpio_pin_6, //gpio为pin6 .iic_init = iic_init_t, .iic_start = iic_start_t, .iic_stop = iic_stop_t, .iic_wait_ack = iic_wait_ack_t, .iic_ack = iic_ack_t, .iic_nack = iic_nack_t, .iic_send_byte = iic_send_byte_t, .iic_read_byte = iic_read_byte_t, .delay_us = delay_us //需自己外部实现delay_us函数};     上述就是iic驱动的封装,由于没有应用场景暂不测试其实用性,待下面atc64的驱动缝缝扎黄写完之后一起测试使用。
atc64xx驱动封装实现     at24cxx.h头文件主要是类模板的定义,具体如下:
                                                          // 以下是共定义个具体容量存储器的容量#define at24c01 127#define at24c02 255#define at24c04 511#define at24c08 1023#define at24c16 2047#define at24c32 4095#define at24c64 8191 //8kbytes#define at24c128 16383#define at24c256 32767 //定义at24cxx类typedef struct at24cxx_type{//属性 u32 eep_type; //存储器类型(存储器容量)//操作 iic_typedef iic; //iic驱动uint8_t (*at24cxx_readonebyte)(const struct at24cxx_type*,uint16_t); //指定地址读取一个字节void (*at24cxx_writeonebyte)(const struct at24cxx_type*,uint16_t,uint8_t); //指定地址写入一个字节void (*at24cxx_writelenbyte)(uint16_t,uint32_t,uint8_t); //指定地址开始写入指定长度的数据uint32_t (*at24cxx_readlenbyte)(uint16_t,uint8_t); //指定地址开始读取指定长度数据void (*at24cxx_write)(uint16_t,uint8_t *,uint16_t); //指定地址开始写入指定长度的数据void (*at24cxx_read)(uint16_t,uint8_t *,uint16_t); //指定地址开始写入指定长度的数据void (*at24cxx_init)(const struct at24cxx_type*); //初始化iicuint8_t (*at24cxx_check)(const struct at24cxx_type*); //检查器件}at24cxx_typedef;extern at24cxx_typedef at24c_64; //外部声明实例化at24cxx对象     at24cxx.c源文件主要是类模板具体操作函数的实现,具体如下:
                                                                                                                                                                                                                                                                                                  //在at24cxx指定地址读出一个数据//readaddr:开始读数的地址 //返回值 :读到的数据static uint8_t at24cxx_readonebyte_t(const struct at24cxx_type* at24cxx_type_t,uint16_t readaddr){ uint8_t temp=0; at24cxx_type_t->iic.iic_start(&at24cxx_type_t->iic); //根据at的型号发送不同的地址if(at24cxx_type_t->eep_type > at24c16) { at24cxx_type_t->iic.iic_send_byte(&at24cxx_type_t->iic,0xa0); //发送写命令 at24cxx_type_t->iic.iic_wait_ack(&at24cxx_type_t->iic); at24cxx_type_t->iic.iic_send_byte(&at24cxx_type_t->iic,readaddr>>8);//发送高地址 }else at24cxx_type_t->iic.iic_send_byte(&at24cxx_type_t->iic,0xa0+((readaddr/256)iic); at24cxx_type_t->iic.iic_send_byte(&at24cxx_type_t->iic,readaddr%256); //发送低地址 at24cxx_type_t->iic.iic_wait_ack(&at24cxx_type_t->iic); at24cxx_type_t->iic.iic_start(&at24cxx_type_t->iic); at24cxx_type_t->iic.iic_send_byte(&at24cxx_type_t->iic,0xa1); //进入接收模式 at24cxx_type_t->iic.iic_wait_ack(&at24cxx_type_t->iic); temp=at24cxx_type_t->iic.iic_read_byte(&at24cxx_type_t->iic,0); at24cxx_type_t->iic.iic_stop(&at24cxx_type_t->iic);//产生一个停止条件 return temp;}//在at24cxx指定地址写入一个数据//writeaddr :写入数据的目的地址 //datatowrite:要写入的数据static void at24cxx_writeonebyte_t(const struct at24cxx_type* at24cxx_type_t,uint16_t writeaddr,uint8_t datatowrite){ at24cxx_type_t->iic.iic_start(&at24cxx_type_t->iic); if(at24cxx_type_t->eep_type > at24c16) { at24cxx_type_t->iic.iic_send_byte(&at24cxx_type_t->iic,0xa0); //发送写命令 at24cxx_type_t->iic.iic_wait_ack(&at24cxx_type_t->iic); at24cxx_type_t->iic.iic_send_byte(&at24cxx_type_t->iic,writeaddr>>8);//发送高地址 }else at24cxx_type_t->iic.iic_send_byte(&at24cxx_type_t->iic,0xa0+((writeaddr/256)iic); at24cxx_type_t->iic.iic_send_byte(&at24cxx_type_t->iic,writeaddr%256); //发送低地址 at24cxx_type_t->iic.iic_wait_ack(&at24cxx_type_t->iic); at24cxx_type_t->iic.iic_send_byte(&at24cxx_type_t->iic,datatowrite); //发送字节 at24cxx_type_t->iic.iic_wait_ack(&at24cxx_type_t->iic); at24cxx_type_t->iic.iic_stop(&at24cxx_type_t->iic);//产生一个停止条件 at24cxx_type_t->iic.delay_us(10000); }//在at24cxx里面的指定地址开始写入长度为len的数据//该函数用于写入16bit或者32bit的数据.//writeaddr :开始写入的地址 //datatowrite:数据数组首地址//len :要写入数据的长度2,4static void at24cxx_writelenbyte_t(uint16_t writeaddr,uint32_t datatowrite,uint8_t len){ uint8_t t;for(t=0;t>(8*t))&0xff); } }//在at24cxx里面的指定地址开始读出长度为len的数据//该函数用于读出16bit或者32bit的数据.//readaddr :开始读出的地址 //返回值 :数据//len :要读出数据的长度2,4static uint32_t at24cxx_readlenbyte_t(uint16_t readaddr,uint8_t len){ uint8_t t; uint32_t temp=0;for(t=0;tiic);//iic初始化}//检查器件,返回0表示检测成功,返回1表示检测失败static uint8_t at24cxx_check_t(const struct at24cxx_type* at24cxx_type_t) { uint8_t temp; temp = at24cxx_type_t->at24cxx_readonebyte(at24cxx_type_t,at24cxx_type_t->eep_type);//避免每次开机都写at24cxx if(temp == 0x33)return 0; else//排除第一次初始化的情况 { at24cxx_type_t->at24cxx_writeonebyte(at24cxx_type_t,at24cxx_type_t->eep_type,0x33); temp = at24cxx_type_t->at24cxx_readonebyte(at24cxx_type_t,at24cxx_type_t->eep_type);if(temp==0x33)return 0; }return 1; }//实例化at24cxx对象at24cxx_typedef at24c_64={ .eep_type = at24c64, //存储器类型(存储器容量)//操作 .iic={ .gpiox_scl = gpioa, .gpiox_sda = gpioa, .gpio_scl = gpio_pin_5, .gpio_sda = gpio_pin_6, .iic_init = iic_init_t, .iic_start = iic_start_t, .iic_stop = iic_stop_t, .iic_wait_ack = iic_wait_ack_t, .iic_ack = iic_ack_t, .iic_nack = iic_nack_t, .iic_send_byte = iic_send_byte_t, .iic_read_byte = iic_read_byte_t, .delay_us = delay_us }, //iic驱动 .at24cxx_readonebyte = at24cxx_readonebyte_t, //指定地址读取一个字节 .at24cxx_writeonebyte = at24cxx_writeonebyte_t,//指定地址写入一个字节 .at24cxx_writelenbyte = at24cxx_writelenbyte_t, //指定地址开始写入指定长度的数据 .at24cxx_readlenbyte = at24cxx_readlenbyte_t, //指定地址开始读取指定长度数据 .at24cxx_write = at24cxx_write_t, //指定地址开始写入指定长度的数据 .at24cxx_read = at24cxx_read_t, //指定地址开始读取指定长度的数据 .at24cxx_init = at24cxx_init_t, //初始化iic .at24cxx_check = at24cxx_check_t //检查器件};     简单分析:可以看出at24cxx类中包含了iic类的成员对象,这是一种包含关系,因为没有属性上的一致性因此谈不上继承。
    之所以将iic的类对象作为at24cxx类的成员是因为at24cxx的实现需要调用iic的成员方法,iic相当于at24cxx更下层的驱动,因此采用包含关系更合适。
    因此我们在使用at24cxx的时候只需要实例化at24cxx类对象就行了,因为iic包含在at24cxx类中间,因此不需要实例化iic类对象,对外提供了较好的封装接口。下面我们看具体的调用方法。
主函数main调用测试     在main函数中直接使用at24c_64来完成所有操作,下面结合代码来看:
                                #include at24cxx.h //为了确定at24c_64的成员方法和引用操作对象at24c_64int main(void){/************省略其他初始化工作****************///第一步:调用对象初始化方法来初始化at24c64 at24c_64.at24cxx_init(&at24c_64);//第二步:调用对象检测方法来检测at24c64 if(at24c_64.at24cxx_check(&at24c_64) == 0) {printf(at24c64检测成功); }else{printf(at24c64检测失败); }return 0;}     可以看出所有的操作都是通过at24c_64对象调用完成的,在我们初始化好at24c_64对象之后就可以放心大胆的调用其成员方法,这样封装的好处就是一个设备对外只提供一个对象接口,简洁明了。
总结     本文详细介绍了面向对象方法实现iic驱动封装以及at24cxx存储器的封装,最终对外仅提供一个操作对象接口,大大提高了代码的复用性以及封装性。


智能温室大棚与传统温室大棚的区别是什么
菜鸟客服背后的诱惑究竟是真人还是机器人?
常用电烙铁的种类
Cherry发布“Viola”轴键盘,体验线性手感
让我们一起来看看Magic Leap怎么挽回一点口碑
如何实现IIC驱动封装以及AT24CXX存储器的封装
每日一课 | 在智慧灯杆视觉技术中的实例分割技术简介
5G和AI将变革许多细分领域和行业
疑似RTX2070Ti显卡曝光 显存竟是7.5GB
超级电容器原理及电特性详细分析
ARM核MCU对模拟信号隔离采集的新方案
【安防】维安达斯智慧校园一键报警系统
使用78M6610+PSU能量测量处理器进行系统内校准
趣探实验室 | 信号分析——LPDDR4内存协议分析仪
安防企业竞逐智慧工地 科技助力工地高质量管理
分析师预计苹果AirPods Max明年销量数百万
如何用投影机来组建家庭影院
轴承座磨损原因及修复工艺
解决方案 | 全氮化镓65W充电器充电宝二合一 大容量 大功率
Linux多线程与同步