1. IIC 总线协议1.1 IIC 总线特点1.2 IIC 总线的时序2. IIC 总线驱动2.1 层次图2.2 将核心层和总线驱动层配置进内核3. IIC 设备驱动层 API4. IIC 设备驱动编程实例5. IIC 设备树的编写5.1 硬件连接图5.2 查看编写好的 i2c1 控制器设备树节点5.3 补充 i2c1 设备树节点以及添加 si7006 子节点6. 数据收发相关的 API6.1 struct i2c_clent6.2 消息传输函数 i2c_transfer6.3 消息结构体 i2c_msg6.4 消息结构体的封装7. 基于 i2c 子系统编写驱动读取温湿度数据7.1 温湿度的寄存器地址7.2 温度和湿度的读写时序7.3 温度和湿度的处理7.4 读取温度和湿度的实例
1. IIC 总线协议
1.1 IIC 总线特点
- 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 总线的时序
写时序
Start + 7bit 从机地址(高位在前低位在后)+ 1bit 写 (0)+ ack(从机→主机)+ 寄存器的地址(不同芯片的寄存器地址不同,有 8 位也有 16 位,会拆分成高 8 位和低 8 位)+ ack + 向寄存器中写的数据(8bit)+ ack + Stop
读时序
Start + 7bit 从机地址(高位在前低位在后)+ 1bit 写 (0)+ ack(从机→主机)+ 寄存器的地址(不同芯片的寄存器地址不同,有 8 位也有 16 位,会拆分成高 8 位和低 8 位)+ ack + Start + 7 bit 从机地址(高位在前低位在后) + 1bit 读(1)+ ack + 8bit 的数据位 + NACK + Stop
Start+7bit从机地址(高位在前低位在后)+1bit写(0)+ack(从机给主机发)+寄存器的地址(不同芯片寄存器地址不一样,有的是8bit址,也有16bit,如果是16bit,会拆分成高8bit和低8bit)+ack+start+7bit从机地址(高位在前低位在后)+1bit读(1)+ack+8bit的数据位+ACK+.....+NACK+stop
2. IIC 总线驱动
2.1 层次图
2.2 将核心层和总线驱动层配置进内核
核心层配置:
- 找到核心层代码目录:内核顶层目录
/drivers/i2c
- 内核顶层目录执行
make menuconfig
> Device Drivers > I2C support ->-*-I2C support
- 保存退出
配置总线驱动层:
- 找到iic总线驱动层代码目录:内核顶层目录
/drivers/i2c/busses
- 内核顶层目录执行
make menuconfig
> Device Drivers > I2C support > I2C Hardware Bus support->
<*> STMicroelectronics STM32F7 I2C support
- 保存退出
编译:
- 内核顶层目录下执行
make uImage LOADADDR=0XC2000000
- cp 内核层目录
/arch/arm/boot/uImage ~/tftpboot
- 重启开发板
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 硬件连接图
i2c1 的设备树节点已经封装好了在
stm32mp151.dtsi
文件,只需要编写设备树需要做的事情:- 设置
pf14/pf15
复用为i2c
功能
- 在
i2c1
设备树节点内部添加si7006
的设备树节点
- 在
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 温度和湿度的读写时序
7.3 温度和湿度的处理
湿度数据计算:
温度数据计算: