用面向对象思想封装IIC、AT24C64驱动

一. 简述 使用面向对象的编程思想封装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_init void (*iic_start)(const struct iic_type*); //iic_start void (*iic_stop)(const struct iic_type*); //iic_stop uint8_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);//发送nack else 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*); //初始化iic uint8_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存储器的封装,最终对外仅提供一个操作对象接口,大大提高了代码的复用性以及封装性。

清锋3D打印智能工厂,引领全球弹性体3D打印批量生产
Wi-Fi 6如何帮助您放心连接物联网设备
碧桂园未来五年内将在机器人领域投入至少800亿元
亚马逊的无人机送货何时才能成功?
手机拍摄神奇“微观”画面 华为nova5系列带来不同凡响的视觉体验
用面向对象思想封装IIC、AT24C64驱动
三星将于8月7日在纽约推出Galaxy Note 10该机采用了后置三摄设计
喇叭防水透气膜是一种新型的高分子防水材料
ZigBee遇强敌?智能照明技术协议新生代蓝牙5.0、Macbee登场
串口数据流抓包监控监视侦听监测获取PC软件和串口设备
MOS管小电流发热应该如何解决
苹果明年超越RIM成全球第二大智能手机厂商
NVIDIA A100,中国顶级云服务提供商和系统制造商的上佳之选
谷歌收购移动可视化搜索公司Plink
3D打印尚未成熟 4D打印异军突起
虹科教您 | 利用UBIQUITY路由器实现对PLC的远程控制——以西门子S7-1200为例
家用交流稳压器原理 如何实现交流电120VLED驱动改成220V使用
完善语音识别 Intel为Broadwell处理器配备专用DSP
苹果iphone12绿屏闪屏的原因是什么
美国国家安全局正在追踪比特币用户