文章目录
一、IIC总线
管脚输出模式:开漏模式
两根线:SCL串行时钟线、SDA串行数据线
信号:
起始信号:SCL高电平期间,SDA产生一个下降沿
结束信号:SCL高电平期间,SDA产生一个上升沿
非应答信号:在第九个时钟周期SDA是高电平
应答信号:在第九个时钟周期SDA是低电平
读写协议:
速率问题:
100KHz(低速) 400KHz(全速) 3.4MHz(高速)
本次使用的开发板:
100KHz(低速) 400KHz(全速) 1MHz(快速)
控制器支持最大速率1M,但是从机不一定支持
特点:
同步,串行,具有应答机制的半双工的总线协议
- 注:从机地址7、8、10位
- 寄存器有可能8位和16位(高8位在前应答、低8位在后应答)
IIC控制器和模拟IIC
IIC在多主机多从机通信时,如果设置为推挽,可能会出现短路
外接上拉电阻还可以提高外围驱动能力,因为电路板的电流非常微弱
输入 上拉输入 下拉输入 模拟输入
推挽输出 开漏输出 复用推挽 复用开漏
二、IIC子系统
核心层:
linux5.10.61/drivers/i2c/
总线驱动层
linux5.10.61/drivers/i2c/bussess
----该目录下是各厂商的总线驱动层
如果在该目录下没有找到与自己板子匹配的文件,可以使用grep命令寻找与自己板子相关的内容在那个文件中
-n 显示行号
-R 递归地读取每个目录下的所有文件,并且会跟随所有的符号链接
可以看出应该打开 i2c-stm32f7.c 文件
(一)将总线驱动和核心层选配到内核中
1.配置总线驱动:make menuconfig
-> Device Drivers
-> I2C support
-> I2C Hardware Bus support
<*>STMicroelectronics STM32F7 I2C support
2.配置核心层驱动:make menuconfig
-> Device Drivers
-> I2C support
-*- I2C support
(二)IIC设备驱动的API
1.分配并初始化对象
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;
//父类
const struct i2c_device_id *id_table;
//1.idtable匹配方式
}
struct device_driver {
const char *name; //name在i2c驱动中不能用于匹配了,但是必须填充
const struct of_device_id *of_match_table; //2.设备树匹配方式
}
2.注册
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
3.注销
void i2c_del_driver(struct i2c_driver *driver);
4.一键注册注销的宏
module_i2c_driver(变量名);
IIC只有两种匹配方式,idtable和设备树匹配,name在IIC中不能用于匹配,但是必须填充
(三)IIC驱动代码示例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
int si7006_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
int si7006_remove(struct i2c_client *client)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
struct of_device_id oftable[] = {
{ .compatible = "hqyj,si7006" },
{ }
};
struct i2c_driver si7006 = {
.probe = si7006_probe,
.remove = si7006_remove,
.driver = {
.name = "si7006",
.of_match_table = oftable,
}
};
module_i2c_driver(si7006);
MODULE_LICENSE("GPL");
(四)设备树节点
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>;//2.#号开头对子节点成员的修饰
#size-cells = <0>;
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"; //1.控制器是否使能
};
1. 检查IIC是否使能
cd /proc/device-tree/soc/i2c/
2. 查看帮助文档编写自己的设备树节点
- 注:一般管脚休眠状态一般将其设置为anlog模拟输入状态,使其处于低功耗状态
- 注:描述子节点信息就将属性写在子节点中
&i2c1{
pinctrl-names = "default","sleep";
pinctrl-0 = <&i2c1_pins_b>; //设置default工作状态的管脚复用
pinctrl-1 = <&i2c1_sleep_pins_b>; //设置sleep休眠状态管脚复用
clock-frequency = <400000>; //指定i2c总线速率为400k,缺省值就是100K
i2c-scl-rising-time-ns = <185>; //scl上升沿的时间
i2c-scl-falling-time-ns = <20>; //scl下降沿的时间
status = "okay"; //将控制器使能
si7006@40{
compatible = "hqyj,si7006"; //和驱动匹配的字符串
reg = <0x40>; //从机地址
};
};
- 注:总线驱动层将设备树中的属性进行解析
(五)消息的封装和发送
1. 消息封装和发送结构体
(1)i2c_client结构体
,drives由该结构体描述,即该结构体继承于drives
当i2c的驱动和设备树匹配成功时会创建i2c_client结构体,并将i2c_client结构体变量的地址传递给了probe函数。
struct i2c_client{
(2)i2c_msg结构体
消息结构体,总线驱动和设备驱动传输数据的格式。
2. 消息结构体封装
根据协议
有多少个起始信号就有多少个消息,消息的长度以字节来表示
3. 消息结构体的发送
i2c_transfer
4. 读写的函数封装
三、代码示例
(一)读取电子串号和固件版本号
(二)读取温湿度
-
注:内核空间不允许浮点运算,因此读取到数据后需要将其在用户空间转换
-
注:IIC控制si7006设备包含几个部分:
-
管脚复用
-
时钟频率
-
从机地址