IIC 子系统

Last edited
Last updated July 15, 2023
Pages
Tags

1. IIC 总线协议

1.1 IIC 总线特点

notion image
  • IIC 有两根线:
    • SCL:时钟线
    • SDA:数据线
  • IIC 有四种信号
    • 起始信号(start):SCL 是高电平,SDA 下降沿
    • 终止信号(stop):SCL 高电平,SDA 上升沿
    • 应答信号(ack):第 9 个周期,SDA 是低电平
    • 非应答信号(nack):第 9 个周期,SDA 维持高电平
  • IIC 总线特点:半双工同步串行
  • IIC 总线的速率:
    • 低速:100K,全速:400K,高速:1M,3.4M
    • 注意:GPIO 模拟 IIC 不在这个速率范围

1.2 IIC 总线的时序

写时序
notion image
Start + 7bit 从机地址(高位在前低位在后)+ 1bit 写 (0)+ ack(从机→主机)+ 寄存器的地址(不同芯片的寄存器地址不同,有 8 位也有 16 位,会拆分成高 8 位和低 8 位)+ ack + 向寄存器中写的数据(8bit)+ ack + Stop
读时序
notion image
Start + 7bit 从机地址(高位在前低位在后)+ 1bit 写 (0)+ ack(从机→主机)+ 寄存器的地址(不同芯片的寄存器地址不同,有 8 位也有 16 位,会拆分成高 8 位和低 8 位)+ ack + Start + 7 bit 从机地址(高位在前低位在后) + 1bit 读(1)+ ack + 8bit 的数据位 + NACK + Stop
notion image
Start+7bit从机地址(高位在前低位在后)+1bit写(0)+ack(从机给主机发)+寄存器的地址(不同芯片寄存器地址不一样,有的是8bit址,也有16bit,如果是16bit,会拆分成高8bit和低8bit)+ack+start+7bit从机地址(高位在前低位在后)+1bit读(1)+ack+8bit的数据位+ACK+.....+NACK+stop

2. IIC 总线驱动

2.1 层次图

notion image

2.2 将核心层和总线驱动层配置进内核

核心层配置:
  1. 找到核心层代码目录:内核顶层目录 /drivers/i2c
  1. 内核顶层目录执行 make menuconfig
  1. > Device Drivers > I2C support ->-*-I2C support
  1. 保存退出
配置总线驱动层:
  1. 找到iic总线驱动层代码目录:内核顶层目录 /drivers/i2c/busses
  1. 内核顶层目录执行 make menuconfig
  1. > Device Drivers > I2C support > I2C Hardware Bus support->
    1. <*> STMicroelectronics STM32F7 I2C support
  1. 保存退出
编译:
  1. 内核顶层目录下执行 make uImage LOADADDR=0XC2000000
  1. cp 内核层目录 /arch/arm/boot/uImage ~/tftpboot
  1. 重启开发板

3. IIC 设备驱动层 API

对象结构体
struct i2c_driver { //与设备匹配成功执行 int (*probe)(struct i2c_client *client, const struct i2c_device_id *id); //设备分离时执行 int (*remove)(struct i2c_client *client); //设置名字匹配和设备树匹配 struct device_driver driver; //设置id_table匹配 const struct i2c_device_id *id_table; }; struct device_driver { const char *name; const struct of_device_id *of_match_table; };
给对象分配空间并且初始化
int i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { return 0; } int i2c_remove(struct i2c_client *client) { return 0; } struct i2c_driver i2c_drv={ .probe=i2c_probe, .remove=i2c_remove, .driver={ .name="si7006", .of_match_table=设备树匹配表名, }, };
注册
#define i2c_add_driver(struct i2c_driver *driver) \ i2c_register_driver(THIS_MODULE, driver)
注销
void i2c_del_driver(struct i2c_driver *driver)
一键注册宏
#define module_i2c_driver(__i2c_driver) \ module_driver(__i2c_driver, i2c_add_driver, \ i2c_del_driver) #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);

4. IIC 设备驱动编程实例

5. IIC 设备树的编写

5.1 硬件连接图

notion image
i2c1 的设备树节点已经封装好了在 stm32mp151.dtsi 文件,只需要编写设备树需要做的事情:
  1. 设置 pf14/pf15 复用为 i2c 功能
  1. i2c1 设备树节点内部添加 si7006 的设备树节点
  1. si7006 设备树节点内部指定从机地址为 0x40

5.2 查看编写好的 i2c1 控制器设备树节点

通过参考别的板子的已经写好的设备树文件学习如何编写 i2c1 节点信息,如下是 STM32MP151.dtsi 的代码片段:
i2c1: i2c@40012000 { compatible = "st,stm32mp15-i2c";//厂商信息 reg = <0x40012000 0x400>;//地址信息 interrupt-names = "event", "error"; interrupts-extended = <&exti 21 IRQ_TYPE_LEVEL_HIGH>, <&intc GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>; clocks = <&rcc I2C1_K>;//使能时钟 resets = <&rcc I2C1_R>; //复位使能 #address-cells = <1>; //子节点reg属性描述地址的u32是1个 #size-cells = <0>; //子节点中没有u32描述地址大小 dmas = <&dmamux1 33 0x400 0x80000001>, <&dmamux1 34 0x400 0x80000001>; dma-names = "rx", "tx"; power-domains = <&pd_core>; st,syscfg-fmp = <&syscfg 0x4 0x1>; wakeup-source; i2c-analog-filter; status = "disabled";//i2c1不使能 };

5.3 补充 i2c1 设备树节点以及添加 si7006 子节点

查看帮助手册:
手册相关内容节选
Required properties (per bus) ----------------------------- - #address-cells - should be <1>. Read more about addresses below. - #size-cells - should be <0>. - compatible - name of I2C bus controller Optional properties (per bus) ----------------------------- These properties may not be supported by all drivers. However, if a driver wants to support one of the below features, it should adapt these bindings. - clock-frequency frequency of bus clock in Hz. - pinctrl add extra pinctrl to configure SCL/SDA pins to GPIO function for bus recovery, call it "gpio" or "recovery" (deprecated) state Required properties (per child device) -------------------------------------- - compatible name of I2C slave device - reg One or many I2C slave addresses.
参照手册,在设备树文件添加设备树节点(添加在 / 外面)
&i2c1 { // "default"表示默认工作状态"sleep"低功耗工作状态 pinctrl-names = "default", "sleep"; // 0表示pinctrl-name的索引号,指的是对"default"状态设置 pinctrl-0 = <&i2c1_pins_b>; // 1表示pinctrl-name的索引号,指的是对"sleep"状态设置 pinctrl-1 = <&i2c1_sleep_pins_b>; i2c-scl-rising-time-ns = <100>;//设置时钟线上升沿时间为100ns i2c-scl-falling-time-ns = <7>;//下降沿时间7ns status = "okay"; /delete-property/dmas; /delete-property/dma-names; si7006@40{ compatible="hqyj,si7006"; reg=<0X40>;//从机地址 }; };

6. 数据收发相关的 API

6.1 struct i2c_clent

当 i2c 设备驱动匹配设备树节点成功,在内核中就会存在一个 struct i2c_client 结构体,结构体内部保存的是设备树节点的信息以及总线驱动相关的信息。
struct i2c_client { unsigned short flags;//保存读写标志位的标志 unsigned short addr; //要通信的从机的从机地址 char name[I2C_NAME_SIZE];//设备名 struct i2c_adapter *adapter;//用于索引总线驱动的一个指针 struct device dev; //存放设备信息的对象 struct list_head detected;//用于构成链表 };

6.2 消息传输函数 i2c_transfer

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
功能:用于传输消息
参数:
  • adap:练习总线驱动的指针 client → adapter
  • msgs:传输的消息
  • num:传输的消息个数
返回值:成功返回发送的消息个数,失败返回错误码

6.3 消息结构体 i2c_msg

struct i2c_msg 结构体用于主机和从机之间的数据传输,根据 msg 内部的读写标志位判断是读还是写。
struct i2c_msg { __u16 addr; //从机地址 client->addr __u16 flags;//读写标志位 1(读消息) 0(消息) __u16 len; //读写的数据长度 __u8 *buf; //存放消息数据的buf };

6.4 消息结构体的封装

封装消息结构体时要遵循一个原则:有几个起始信号就要有几条消息
写消息:start + 7bit 从机地址(高位在前低位在后)+ 1 bit 写(0)+ ack (从机发给主机)+ 寄存器的地址(不同芯片寄存器地址不一样,有的是 8bit 址,也有 16bit,会拆分成高 8bit 和低 8bit)+ ack + 向寄存器中写的数据(8bit)+ ack + stop
char w_buf[]={reg,value}; struct i2c_msg w_msg={ .addr=client.addr, .flags=0, .len=sizeof(w_buf), .buf=w_buf, };
读消息:start+7bit 从机地址(高位在前低位在后)+ 1bit写(0)+ ack(从机给主机发)+寄存器的地址(不同芯片寄存器地址不一样,有的是8bit址,也有 16bit,如果是 16bit,会拆分成高 8bit 和低8bit) + ack + start + 7bit 从机地址(高位在前低位在后)+ 1bit 读(1)+ ack + 8bit 的数据位+ NACK + stop
char r_buf[]={reg}; char value; struct i2c_msg r_msg[]={ [0]={ .addr=client->addr, .flags=0, .len=1, .buf=r_buf, }, [1]={ .addr=client->addr, .flags=1, .len=1, .buf=&value, }, }

7. 基于 i2c 子系统编写驱动读取温湿度数据

7.1 温湿度的寄存器地址

参考文档 Table 11,获取湿度的寄存器地址为 0xE5 ,获取温度的寄存器地址为 0xE3

7.2 温度和湿度的读写时序

notion image

7.3 温度和湿度的处理

湿度数据计算:
温度数据计算:

7.4 读取温度和湿度的实例