SMBus 是 I2C 协议的一个子集,具体见完全开发手册
参考资料:
⚫ Linux 内核文档: Documentation\i2c\smbus-protocol.rst
⚫ SMBus 协议:
⚫ http://www.smbus.org/specs/
⚫ SMBus_3_0_20141220.pdf
⚫ I2CTools:
https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/
硬件结构
只需要两根线
SCL
SDA
主设备和从设备都能修改SDA这条线上的电平【也都能改SCL的,原理一样的】
两个设备控制一条线的电压,不能直接输出电压,会短路。
所以两个设备控制的是mos管,只要设备置1 使mos管接地,就能拉低电压。
加一个上拉电阻,让电平维持在高电平。
所以,只有俩人都撒手,才是高电平,只要有一个人扯着,就是低电平。
IIC的信号动作定义
两个设备都能控制线上的电平,这样的线居然只需要两根就能多设备通信了。
那两根线,各自有【低电平】【高电平】【向低电平跳变】【向高电平跳变】这几种情况。可以组合使用。
通信需要传数据,而且需要信号来做格式区分。串口是用起始位,数据,[可有可无的校验位],停止位。 所以别的通信的基础操作也是这三个吧。【IIC如果非要校验,是通过 多发一帧数据 来实现的,真奢侈啊】
SCL在高电平的时候 SDA的数据才是有效的。
SCL在低电平的时候,SDA要变换电平。而且从设备如果没准备好数据,就会主动拉低SCL,让主设备知道要等着。
SCL在跳变的时候不管事,没啥作用,就是要跳变。。因为只有当两边都撒手,电压才能为1的,所以只有当SCL为1的时候才能确认SDA的数据。
无论读写都需要主设备发起start信号并指定目标设备地址,而且设备都要先回应,然后一方读/写之后另一方要回应。
串口是一对一的,不像总线有那么多设备,所以串口没收到很快就能发现,不需要靠发送回应数据进行确认。
但是,在IIC通信中,起始信号是必须的,结束信号和应答信号都不是必需的。 也就是说,虽然没有应答信号就会确认不了,但你不在乎的话就没关系。
大部分时间是主设备在控制SCL进行数据传输,
从设备要应答的原理就是:在需要应答的时候,主设备撒手了SDA和SCL,如果从设备没在 那也SCL和SDA都被撒手了,那SCL为1读到了为1的SDA,就是凉凉;如果从设备在线,那就会主动把SDA拉低,所以应答信号是低电平。
应用到数据读写,需要规定一些发送内容的信息格式
上一小节提到的读写格式只是最基础的读写动作。而实际上在进行读写的时候还必须要指定数据的寄存器地址。所以地址本身也是数据,需要被发送过去。
要读从设备里的数据,还得指定从设备里的数据寄存器的地址。所以要先发一个写指令,向那个设备发去它的寄存器的地址。然后再开始新的起始信号,读取从设备传回的数据。
I2C系统的重要结构体
举例:设备地址为 0x50 的 EEPROM,要读取它里面存储地址为 0x10 的一个字节,
应该构造几个 i2c_msg?要构造 2 个 i2c_msg
c) 第一个 i2c_msg 表示写操作,把要访问的存储地址 0x10 发给设备
d) 第二个 i2c_msg 表示读操作
代码如下
这个例子要发送两次开始指令,所以需要定义两个msg变量来发送。
第0个的buf是主设备要求的目标寄存器地址,是写命令
第1个的buf是一个存储用的变量,保存接收到的data
在终端查看IIC设备情况
列出所有的IIC总线。上图显示有0 1 2 三个IIC总线
在第0个IIC总线上 挂着3个设备,字母UU表示在地址0x1a 0x22 的设备已经有驱动程序了 直接显示地址0x1e的那个设备是没有对应驱动程序的。【总之,设备地址是到手了】
然后按照模块的数据手册去操作寄存器就行
下图里,Usage后面是信息格式,[ ]里的是可选设置
【注意:下面提到的两个指令是SMBus协议里的指令】【SMBus对于IIC就好像CANopen对于CAN。SMBus协议多限制了一些电压条件,而且必须在一次性传输多个数据之前先发一帧数据说清楚”总数据的长度“然后再发送具体数据】
我们用写指令 通过0号I2CBUS 往0x1e这个地址挂载的设备里的 0号寄存器 写入0x4 让这个从设备进行复位。
我们用读指令 通过0号I2CBUS 从0x1e这个地址挂载的设备里的 0xc号寄存器 读取数据,如果要一次性读俩字节,就加个w
下面这个指令是IIC的基础指令
在写代码的时候使用IIC通信
不过,有封装好的可以用。。
原文链接:https://blog.csdn.net/qq_42635852/article/details/125944715
————————————————
编写APP直接访问EEPROM
[(98条消息) Linux系统驱动之编写APP直接访问EEPROM_韦东山的博客-CSDN博客]
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <i2c/smbus.h>
#include "i2cbusses.h"
#include <time.h>
/* ./at24c02 <i2c_bus_number> w "100ask.taobao.com"
* ./at24c02 <i2c_bus_number> r
*/
int main(int argc, char **argv)
{
unsigned char dev_addr = 0x50;
unsigned char mem_addr = 0;
unsigned char buf[32];
int file;
char filename[20];
unsigned char *str;
int ret;
struct timespec req;
if (argc != 3 && argc != 4)
{
printf("Usage:\n");
printf("write eeprom: %s <i2c_bus_number> w string\n", argv[0]);
printf("read eeprom: %s <i2c_bus_number> r\n", argv[0]);
return -1;
}
file = open_i2c_dev(argv[1][0]-'0', filename, sizeof(filename), 0);
if (file < 0)
{
printf("can't open %s\n", filename);
return -1;
}
if (set_slave_addr(file, dev_addr, 1))
{
printf("can't set_slave_addr\n");
return -1;
}
if (argv[2][0] == 'w')//第2个字符串的第0个字节,0 1 2的2
{
// write str: argv[3]
str = argv[3];
req.tv_sec = 0;
req.tv_nsec = 20000000; /* 20ms */
while (*str)
{
// mem_addr, *str
// mem_addr++, str++
ret = i2c_smbus_write_byte_data(file, mem_addr, *str);
if (ret)
{
printf("i2c_smbus_write_byte_data err\n");
return -1;
}
// wait tWR(10ms)
nanosleep(&req, NULL);
mem_addr++;
str++;
}
ret = i2c_smbus_write_byte_data(file, mem_addr, 0); // string end char
if (ret)
{
printf("i2c_smbus_write_byte_data err\n");
return -1;
}
}
else
{
// read
ret = i2c_smbus_read_i2c_block_data(file, mem_addr, sizeof(buf), buf);
if (ret < 0)
{
printf("i2c_smbus_read_i2c_block_data err\n");
return -1;
}
buf[31] = '\0';
printf("get data: %s\n", buf);
}
return 0;
}
(https://blog.csdn.net/thisway_diy/article/details/119906374?ops_request_misc=&request_id=&biz_id=102&utm_term=%E7%BC%96%E5%86%99APP%E7%9B%B4%E6%8E%A5%E8%AE%BF%E9%97%AEEEPROM&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-0-119906374.nonecase&spm=1018.2226.3001.4187)