linux系统框图 一、IIC协议介绍
发布时间:2022-10-28 15:40:13 所属栏目:Linux 来源:
导读: 一、IIC协议介绍
说起IIC,搞单片机,嵌入式的那肯定是接触的比较多的。串口、IIC、SPI这3个协议在单片机阶段应该是用比较多的,很多的外设模块linux系统框图,芯片都是串口、IIC、SPI等协议与主控芯片
说起IIC,搞单片机,嵌入式的那肯定是接触的比较多的。串口、IIC、SPI这3个协议在单片机阶段应该是用比较多的,很多的外设模块linux系统框图,芯片都是串口、IIC、SPI等协议与主控芯片
|
一、IIC协议介绍 说起IIC,搞单片机,嵌入式的那肯定是接触的比较多的。串口、IIC、SPI这3个协议在单片机阶段应该是用比较多的,很多的外设模块linux系统框图,芯片都是串口、IIC、SPI等协议与主控芯片进行通信,完成逻辑开发。 在Linux系统驱动层使用IIC其实本质上与单片机没什么差别的,最终反正是和芯片进行交互通信,本身IIC协议并不难,但是在Linux下为了标准化,加了很多框架导致理解上就觉得复杂,听起来xxx子系统就很神秘高大上,其实这是因为对Linux驱动框架不熟悉,只要把框架流程能梳理清楚,那么整体就变得简单了。 IIC协议在物理连接上比较简单,只有两条线: SDA(串行数据线)和 SCL(串行时钟线) ,比较省IO口。 其中的SDA数据线是双向的,根据时序发送数据和接收数据时主机会对应的切换自身的输入输出模式。 SDA数据线是传输数据信号的,时钟线是用来控制什么时候发送数据信号,搞懂规则两边设备就可以通过IIC协议进行通讯。 从连线上可以得知,IIC属于串行协议,每一次的时钟信号只能单方面发送一个字节数据。 IIC协议又称为IIC总线。 为什么叫总线? 这个可不是随便乱叫的。 除了IIC总线外,常见的总线还有SPI总线,USB总线等等。 如果对IIC,SPI不了解,可能对总线这个概念不清楚,但USB大家应该是都见过的,如果电脑的USB口不够用,我们可以买集线器扩展USB接口,将集线器插在电脑USB口之后,集线器上面可以插入多个USB设备:比如,USB鼠标,USB键盘,U盘,USB-网卡等等。 对于电脑而言,它怎么知道自己在和哪一个USB设备通讯?如何区分的?这就是依靠地址,每个设备都有自己的设备地址进行匹配。 所以,从这里可以看出, 可以挂载多个设备的协议才能叫总线, 通过总线就可以省下不少的IO口。 IIC协议既然叫IIC总线上,所以IIC总线上也可以挂载多个设备,区分设备的办法也是依靠地址;发起IIC协议的单片机称为主机,把挂接在总线上的其他设备都作为从设备。 I2C总线数据传输速率在标准模式下可达 100kbit/s,快速模式下可达 400kbit/s,高速模式下可达 3.4Mbit/s。一般通过 I2C 总线接口可编程时钟来实现传输速率的调整。I2C 总线上的主设备与从设备之间以字节(8 位)为单位进行双向的数据传。 二、IIC 总线协议 学习IIC总线主要是要搞清楚几个信号: 起始信号,停止信号,应答信号,非应答信号。 在空闲状态下(默认情况下):SCL 和 SDA 都保持着高电平。 起始信号: 总线在空闲状态时,SCL和 SDA 都保持着高电平,当 SCL 为高电平而 SDA 由高到低的跳变,表示产生一个起始条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他 2C 器件无法访问总线。 停止信号: 当 SCL 为高而 SDA 由低到高的跳变,表示产生一个停止条件。 应答信号: 每个字节传输完成后的下一个时钟信号,在 SCL 高电平期间,SDA 为低,则 表示一个应 答信号。 非应答信号: 每个字节传输完成后的下一个时钟信号,在 SCL 高电平期间,SDA 为高,则表示一个应 答信号。 注意: 起始和结束信号总是由主设备产生。 IIC 总线的数据传输时序图: #打卡不停更# Linux下IIC驱动编写,介绍IIC子系统框架的使用-开源基础软件社区 数据位传输时序图: #打卡不停更# Linux下IIC驱动编写,介绍IIC子系统框架的使用-开源基础软件社区 应答信号时序图: #打卡不停更# Linux下IIC驱动编写,介绍IIC子系统框架的使用-开源基础软件社区 开始信号与停止信号时序图: #打卡不停更# Linux下IIC驱动编写,介绍IIC子系统框架的使用-开源基础软件社区 根据上面的时序,可以通过IO口模拟出IIC时序,下面贴出IIC模拟时序代码: 三、IIC子系统3.1 框架介绍 在Linux下为了方便驱动的规范,驱动的维护,移植等多种原因,Linux内核设计一套IIC子系统框架,方便大家按照规范编写驱动,只要是按照标准框架写的驱动,在任何Linux系统上都可以跑(只要底层适配好了)。 这对驱动的移植而言就很方便。 IIC总线框架是基于平台总线驱动模型设计的,将驱动端、设备端分开。 设备端完成总线信息配置,设备信息配置;而驱动端完成与底层IIC设备通信,完成数据交互。 从整体框架来讲,IIC子系统最底层的是IIC适配器框架,IIC适配器一般是由厂家提供,一般CPU支持几个IIC总线接口,就注册几个适配器;这个适配器框架里,提供了CPU的IIC总线寄存器操作代码,也就是适配器提供最底层的函数(读写字节)。 用户拿到Linux内核之后,需要针对某一个芯片(例如:eeprom-AT24C02)编写驱动时,首先要根据AT24C02与CPU上的IIC总线接线情况,在通过适配器接口获取这个总线的指针(这个指针就是指向这个IIC总线的适配器结构),然后注册设备端,当IIC驱动端也注册成功之后,设备端的信息会传递给驱动端,驱动就得到了这个IIC总线对应的适配器结构。 接下来在IIC驱动层调用了Linux内核提供的IIC读写函数与设备通信时,这些函数的参数里就要传入适配器指针。 (因为通过这个适配器指针可以调用到IIC总线底层的读写函数,最终完成设备通信)。 大致框图如下: #打卡不停更# Linux下IIC驱动编写,介绍IIC子系统框架的使用-开源基础软件社区 下面介绍IIC总线提供的一些函数。 3.2 设备层相关函数 设备层相关函数: 1)获取 i2c_adapter 的内存地址 struct i2c_adapter *i2c_get_adapter(int nr) 作用:1)获取 i2c_adapter 结构地址 2)增加 i2c_adapter 结构的引用计数,防止使用过程中被移除。 nr:就是适配器的总线编号: i2c0, --- 就是 0 i2c1, --- 就是 1 返回:指针适配器结构的首地址,失败返回 NULL。 3)减少 i2c_adapter 引用计数 使用 i2c_get_adapter 后都需要使用这个函数。 i2c_put_adapter(struct i2c_adapter *adap) 4)注册 I2C 设备 struct i2c_client *i2c_new_probed_device(struct i2c_adapter *adap, struct i2c_board_info *info, unsigned short const *addr_list, int (*probe)(struct i2c_adapter *, unsigned short addr)) 功能:向内核注册一个 I2C 设备 参数: adap :i2c_client 所依赖的适配器指针,adap 则通过 i2c_get_adapter 函数获取 info:i2c 设备的描述信息 addr_list:i2c 设备可能出现的地址表,是一个数组。 probe:探测到 i2c 设备时候会执行回调函数,一般是 NULL。 3.3 驱动层相关函数 注册 iic 驱动 int i2c_add_driver(struct i2c_driver *driver) 注册 I2C 设备驱动,driver 是已经填充好的 struct i2c_driver 结构指针 一般写在模块的初始化代码。 注销 iic 驱动 void i2c_del_driver(struct i2c_driver *driver) 注销 I2C 设备驱动, 一般写在模块的出口处。 I2C 核心层提供的标准发收函数: int i2c_master_send(struct i2c_client *client, const char *buf , int count) //发送函数 功能:发送数据给真正的硬件设备。 参数: client:指针 I2C 设备结构的指针。 buf:发送的数据指针 count:发送的字符数量 返回发送的字节数,失败返回-1。 注意:此函数只是实现标准 IIC 的写协议,不代表具体器件写协议。 如:要写数据给 AT24C02 ,从内部地址 10 开始写,应该怎么写。 标准的读取数据函数 int i2c_master_recv(struct i2c_client *client, char *buf ,int count) 功能:从硬件中读取数据 参数: client:指针 I2C 设备结构的指针。 buf:存放数据指针 count:要读的字节数量 注意:此函数只是实现标准 IIC 的读协议,不代表具体器件读协议。 比如,对 24c02 进行读操作,先使用 i2c_master_send 发送内部地址,然后调用 i2c_master_recv 函数读数据。 收发一体函数 int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 功能:这个函数是 I2C 传输函数,收发一体的函数。 参数: adap:指针 I2C 适配器结构的指针,这个指针是使用 i2c_client 中的 adapter 指针。 msgs:存放数据指针 num: 要传输的 struct i2c_msg 数量。 注销 IIC 适配器 int i2c_del_adapter(struct i2c_adapter *adap) 获取适配器 struct i2c_adapter *i2c_get_adapter(int nr) 功能: 根据注册时绑定的总线编号,获取 IIC 适配器结构体 参数: nr 总线编号 返回值: IIC 适配器结构 static int i2c_register_adapter(struct i2c_adapter *adap) //注册 IIC 适配器,该函数在下面两个函数里已经调用 int i2c_add_adapter(struct i2c_adapter *adapter) //声明并注册 i2c 适配器,使用动态总线编号 int i2c_add_numbered_adapter(struct i2c_adapter *adap) //声明并注册 i2c 适配器,使用静态总线编号 以下是 smbus 总线的操作函数,smbus 是系统管理总线,可以看成是 IIC 总线的一个子集,它的规范大部 分都是基于 I2C 标准。所以,大部分的 IIC 器件也可以使用 smbus 总线来操作。非常合适操作那些有内部地址 的器件。 单字节读函数 s32 i2c_smbus_read_byte_data(const struct i2c_client *client,u8 command): 功能:指定地址的单字节读 参数: client 结构指针。 command:内部地址。 返回值:读到的数据,失败返回负数。 单字节写函数 s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value) 功能:指定地址的单字节写入数据 参数: client 结构指针。 command:内部地址 value:要写入的数据 返回值:写如的数据,失败返回负数。 读指定长函数 如果器件存在页写问题,建议使用这个函数,循环操作。 s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client, u8 command, u8 length, u8 *values) 功能:从指定地址开始读取指定长度的数据。 参数: client 结构指针 command:内部地址 length:要读的数据长度 value:存放数据的指针 返回值:读到的数据,失败返回负数。 写指定长度函数 s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client, u8 command, u8 length, u8 *values) 功能:从指定地址开始读取指定长度的数据 参数: client 结构指针 command:内部地址 length:要写的数据长度 value:源数据指针 返回值:写入的数据,失败返回负数。 四、驱动代码案例4.1 IIC适配器注册案例代码 介绍如何自己注册IIC适配器。 #include #include #include #include #include #include #include #include #include #include #include #include #include /*--------------------------IIC底层时序--------------------------------*/ /* 定义IIC需要使用的寄存器 */ static volatile unsigned int *GPD1_CON=NULL; static volatile unsigned int *GPD1_DAT=NULL; #define IIC_OUTPUT_MODE_SET() {*GPD1_CON&=~(0xF<<4*2);*GPD1_CON|=0x1<<4*2;} #define IIC_INPUT_MODE_SET() {*GPD1_CON&=~(0xF<<4*2);} #define IIC_SCL(x) if(x){*GPD1_DAT|=1<<3;}else{*GPD1_DAT&=~(1<<3);} #define IIC_SDA_OUT(x) if(x){*GPD1_DAT|=1<<2;}else{*GPD1_DAT&=~(1<<2);} #define IIC_SDA_IN (*GPD1_DAT&1<<2) /* 函数功能: IIC总线初始化 硬件连接: SCL-GPD1_3 SDA-GPD1_2 */ static void IIC_Init(void) { /*1. 将物理地址转换为虚拟地址*/ GPD1_CON=ioremap(0x114000C0,4); GPD1_DAT=ioremap(0x114000C4,4); if(GPD1_CON==NULL||GPD1_DAT==NULL) { printk("物理地址转换为虚拟地址出现问题!\n"); } /*2. 配置寄存器*/ *GPD1_CON&=~(0xF<<4*3); *GPD1_CON|=0x1<<4*3; *GPD1_CON&=~(0xF<<4*2); *GPD1_CON|=0x1<<4*2; *GPD1_DAT|=1<<2; *GPD1_DAT|=1<<3; } /* 函数功能: 起始信号 */ static void IIC_START(void) { IIC_OUTPUT_MODE_SET(); //配置输出模式 IIC_SCL(1); IIC_SDA_OUT(1); udelay(2); IIC_SDA_OUT(0); udelay(2); IIC_SCL(0);//告诉从机,通信开始(主机将要给从机发送数据)。 } /* 函数功能: 停止信号 */ static void IIC_STOP(void) { IIC_OUTPUT_MODE_SET(); //配置输出模式 IIC_SCL(0); IIC_SDA_OUT(0); udelay(2); IIC_SCL(1); udelay(2); IIC_SDA_OUT(1); } /* 函数功能: 获取从机发给主机的应答 返回值 : 0表示获取成功,1表示获取失败 目的: 读取总线上一位数据的值。这一位数据的正确值0 */ static u8 IIC_GetACK(void) { u8 cnt=0; IIC_INPUT_MODE_SET(); //输入模式 IIC_SDA_OUT(1); //上拉 IIC_SCL(0); //告诉从机主机需要数据 udelay(2); IIC_SCL(1); //告诉从机主机正在读数据 while(IIC_SDA_IN) //等待应答 { cnt++; udelay(1); if(cnt>=250)return 1; } IIC_SCL(0); //告诉从机,主机准备发送数据 return 0; } /* 函数功能: 主机给从机发送应答 函数参数: 1(非应答) 0(应答) 目的: 发送一位数据 */ static void IIC_SendAck(u8 ack) { IIC_OUTPUT_MODE_SET(); IIC_SCL(0); //告诉从机,主机将要发送数据 if(ack){IIC_SDA_OUT(1);} else {IIC_SDA_OUT(0);} udelay(2); IIC_SCL(1); udelay(2); IIC_SCL(0); } /* 函数功能: 发送一个字节数据 函数参数: data将要发送数据 */ static void IIC_WriteOneByte(u8 data) { u8 i; IIC_OUTPUT_MODE_SET(); for(i=0;i<8;i++) { IIC_SCL(0); if(data&0x80){IIC_SDA_OUT(1);} else {IIC_SDA_OUT(0);} udelay(2); IIC_SCL(1); udelay(2); data<<=1; } IIC_SCL(0); } /* 函数功能: 读取一个字节数据 返回值 : 读取成功的数据 */ static u8 IIC_ReadOneByte(void) { u8 data=0,i=0; IIC_INPUT_MODE_SET(); for(i=0;i<8;i++) { IIC_SCL(0); udelay(2); IIC_SCL(1); data<<=1; if(IIC_SDA_IN)data|=0x01; udelay(2); } IIC_SCL(0); return data; } /* 函数功能: iic_adapter写一个字节 函数参数: data:写入的字节数据 addr:存放位置(0~255) */ static void iic_adapter_WriteOneByte(u8 dev_addr,u8 data,u8 addr) { IIC_START(); IIC_WriteOneByte(dev_addr<<1|0x0); //发送设备地址 IIC_GetACK(); //获取应答 IIC_WriteOneByte(addr); //发送存放数据的地址 IIC_GetACK(); //获取应答 IIC_WriteOneByte(data); //发送实际要存放的数据 IIC_GetACK(); //获取应答 IIC_STOP(); //发送停止信号 } /* 函数功能: 指定位置读取指定数量的数据 函数参数: addr: 从哪里开始读取数据 len : 读取多长的数据 *p : 存放数据的缓冲区 0x38<<1 */ static void iic_adapter_ReadData(u8 dev_addr,u8 addr,u8 len,u8 *p) { u16 i; IIC_START(); IIC_WriteOneByte(dev_addr<<1|0x0); //发送设备地址(写) IIC_GetACK(); //获取应答 IIC_WriteOneByte(addr); //发送存放数据的地址(即将读取数据的地址) IIC_GetACK(); //获取应答 IIC_START(); IIC_WriteOneByte(dev_addr<<1|0x1); //发送设备地址(读) IIC_GetACK(); //获取应答 for(i=0;inr); printk("从机的设备地址:0x%X\n",msgs[0].addr); printk("寄存器的起始地址:%d\n",msgs[0].buf[0]); */ int i; if(num==1) /*写*/ { if(msgs[0].len==0)return 0; iic_adapter_ReadData(msgs[0].addr,msgs[0].buf[0],msgs[0].len-1,&msgs[0].buf[1]); /* printk("发送的数据长度:%d\n",msgs[0].len-1); printk("实际发送数据:"); for(i=1;i 4.2 IIC设备端代码 介绍如何获取注册适配器结构,完成设备端信息填充,完成注册。 这里代码是以触摸屏的驱动为例。 #include #include #include #include #include #include #include #include static struct i2c_client *i2cClient = NULL; static unsigned short i2c_addr_list[0xFF]= { 0x38,0x46,I2C_CLIENT_END };//地址队列 /* IIC标准地址是7位 011 :厂家提供的地址 1000:厂家提供的源码里分析获取的 */ /* *把什么设备信息告诉驱动? *1. 告诉驱动我是哪条总线 *2. 告诉驱动:我的设备地址是多少 *3. 申明一个名字用来找到驱动端(完成匹配) */ static int __init i2c_dev_init(void) { struct i2c_adapter *i2c_adap;//获取到的总线存放在这个结构体 struct i2c_board_info i2c_info;//设备描述结构体,里面存放着欲设备的名字还有地址 /*1. 根据总线编号获取IIC控制器*/ i2c_adap = i2c_get_adapter(9); /*要使用IIC_1号总线*/ /*2. 清空结构体*/ memset(&i2c_info,0,sizeof(struct i2c_board_info)); /*3. 名称的赋值*/ strlcpy(i2c_info.type,"touch_name",I2C_NAME_SIZE); /*触摸屏的中断信号检测IO口编号*/ i2c_info.irq=EXYNOS4_GPX1(6); /*4. 创建IIC设备--客户端*/ i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL); if(i2cClient==NULL) { printk("地址匹配出现错误!\n"); return 0; } /*5. 设置adapter\增加使用计数*/ i2c_put_adapter(i2c_adap); printk("i2c_dev_init!!\n"); return 0; } static void __exit i2c_dev_exit(void)//平台设备端的出口函数 { printk(" i2c_dev_exit ok!!\n"); /*注销设备*/ i2c_unregister_device(i2cClient); /*释放设备,减少使用计数*/ i2c_release_client(i2cClient); /*释放结构体空间*/ kfree(i2cClient); } module_init(i2c_dev_init); module_exit(i2c_dev_exit); MODULE_LICENSE("GPL"); 4.3 驱动层代码 #include #include #include #include #include #include #include #include #include #include static const struct i2c_device_id i2c_id[] = { {"touch_name",0},//设备端的名字,0表示不需要私有数据 }; static struct i2c_client *touch_client=NULL; /*工作队列处理的结构体*/ static struct work_struct tiny4412_touch_work; /* 工作队列处理函数 */ static void touch_work_func(struct work_struct *work) { /*1. 读取触摸屏信息*/ u8 buff[7]; i2c_smbus_read_i2c_block_data(touch_client,0x00,7,buff); if(buff[2]&0xF) { printk("按下的点数量:%d\n",buff[2]&0xF); printk("X_0坐标:%d\n",(buff[3]&0xF)<<8|buff[4]); printk("Y_0坐标:%d\n",(buff[5]&0xF)<<8|buff[6]); } else { printk("触摸屏松开!\n"); } } /* 触摸屏中断处理函数 */ irqreturn_t irq_handler_touch(int irq, void *dev) { /*调度工作(计划让CPU执行这个工作)*/ schedule_work(&tiny4412_touch_work); /*正常情况下: 是在中断服务函数里面*/ return IRQ_HANDLED; /*表示中断已经处理过了*/ } static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *device_id)//匹配成功时调用 { printk("驱动端IIC匹配的地址=0x%x\n",client->addr); touch_client=client; /*保存地址*/ /*1. 读取厂商ID*/ u8 ID; i2c_smbus_read_i2c_block_data(client,0xA3,1,&ID); printk("厂商ID:%d\n",ID); /*2. 读取电源寄存器状态*/ u8 State; i2c_smbus_read_i2c_block_data(client,0xA5,1,&State); printk("电源寄存器状态:%d\n",State); /*3. 初始化工作*/ INIT_WORK(&tiny4412_touch_work,touch_work_func); /*4. 完成中断的注册*/ int err=request_irq(gpio_to_irq(client->irq),irq_handler_touch,IRQ_TYPE_EDGE_BOTH,client->name,NULL); if(err!=0) { printk("中断注册失败!\n"); return -1; } printk("提示: 触摸屏中断注册成功!\n"); return 0; } static int i2c_remove(struct i2c_client *client) { printk("触摸屏驱动端资源卸载成功!\n"); /*1. 释放中断号*/ free_irq(gpio_to_irq(client->irq),NULL); return 0; } struct i2c_driver i2c_drv = { .driver = { .name = "i2c_drv", .owner = THIS_MODULE, }, .probe = i2c_probe, //探测函数 .remove = i2c_remove, //资源卸载 .id_table = i2c_id, //里面有一个名字的参数用来匹配设备端名字 }; /*iic驱动端*/ static int __init i2c_drv_init(void) { i2c_add_driver(&i2c_drv);//向iic总线注册一个驱动 return 0; } static void __exit i2c_drv_exit(void)//平台设备端的出口函数 { i2c_del_driver(&i2c_drv); } module_init(i2c_drv_init); module_exit(i2c_drv_exit); MODULE_LICENSE("GPL"); (编辑:航空爱好网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
站长推荐


