文章目录
本人学习完韦老师的视频,因此来复习巩固,写以笔记记之。
韦老师的课比较难,第一遍不知道在说什么,但是坚持看完一遍,再来复习,基本上就水到渠成了。
看完视频复习的同学观看最佳!
基于 IMX6ULL-PRO
参考视频 Linux快速入门到精通视频
参考资料:01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板.pdf
一、I2C 应用编程
1-1 I2C协议
I2C硬件框架如下图所示
在一个芯片内部,有一个或多个 I2C 控制器;在一个I2C控制器上,可以连接一个或多个I2C设备;I2C 总线只需要 2 条线:时钟线 SCL 、数据线 SDA;在 I2C 总线的SCL 、 SDA 线上,都有上拉电阻。
以I2C 接口的存储设备 AT24C02 为例:
① APP:
提出要求:把字符串**“www.100ask.net”** 写入AT24C02地址16开始的地方
② AT24C02 驱动:
◼ 它知道 AT24C02 要求的地址、数据格式
◼ 它知道发出什么信号才能让 AT24C02 执行擦除、烧写工作
◼ 它知道怎么判断数据是否烧写成功
◼ 它构造好一系列的数据,发给 I2C 控制器
③ I2C 控制器驱动:
◼ 它根据 I2C 协议发出各类信号: I2C 设备地址、 I2C 存储地址、数据
◼ 它根据 I2C 协议判断
IIC总线协议
I2C协议中数据传输的单位是字节,也就是8位。但是要用到9个时钟;前面8个时钟用来传输8数据,第9个时钟用来传输回应信号。传输时,先传输最高位(MSB)。
主芯片通过一根 SDA 线既可以把数据发给从设备,也可以从SDA 上读取数据,连接SDA线的引脚里面必然有两个引脚(发送引脚和接收引脚)。
① 当某一个芯片不想影响 SDA 线时,那就不驱动这个三极管
② 想让SDA 输出高电平,双方都不驱动三极管 (SDA 通过上拉电阻变为高电平)不然就会变为高阻态。
③ 想让SDA 输出低电平,就驱动三极管
ACK应答信号为低电平的原因?
数据传输,当主设备发送完8位以后,第9位为ACK应答信号,此时主设备不驱动三极管,而要发应答信号时,从设备驱动三极管,即当A=1;B=0时,此时输出为SDA为低电平。
为何SCL 也要使用上拉电阻?
在第 9 个时钟之后,如果有某一方需要更多的时间来处理数据,它可以一直驱动三极管把 SCL 拉低。当SCL为低电平时候,大家都不应该使用 IIC 总线,只有当SCL从低电平变为高电平的时候,IIC总线才能被使用。当它就绪后,就可以不再驱动三极管,这是上拉电阻把SCL 变为高电平,其他设备就可以继续使用I2C总线了。
1-2 SMBus协议
SMBus: System Management Bus,系统管理总线。
SMBus 为系统和电源管理这样的任务提供了一条控制总线,使用 SMBus 的系统,设备之间发送和接收消息都是通过 SMBus ,而不是使用单独的控制线,这样可以节省设备的管脚数。
SMBus 是基于I2C协议的,SMBus 要求更严格,SMBus是I2C协议的子集。
(1) REPEATED START Condition( 重复发出S信号)
在写和读设备之间,可以不发出P信号,而是直接发出Sr信号进行读写设备的切换。
(2) SMBus Receive Byte
1-3 I2C系统的重要结构体
(1) i2c_adapter结构体
使用一句话概括I2C 传输: APP通过I2C Controller 与I2C Device传输数据。
首先确定使用哪个I2C Controller?使用 i2c_adapter
可以表示I2C Controller,中的i2c_algorithm结构体中有主设备的传输函数来进行发送数据。
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);
I2C主设备传输函数。该函数用于在总线上执行一系列 I2C 消息传输操作,返回成功处理的消息数量,或者在出错时返回负值。
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags,
char read_write,u8 command, int size, union i2c_smbus_data *data);
该函数用于在总线上执行 SMBus 消息传输操作,根据给定的参数执行读取或写入操作,并返回操作结果。
(2) i2c_client结构体
I2C总线上的设备使用i2c_client
结构体来表示一个I2C Device。一个I2C Device ,一定有设备地址。
(3) i2c_msg结构体
传输的数据用i2c_msg
来表示
举例:设备地址为0x50 的 EEPROM ,要读取它里面存储地址为 0x10 的一个字节,
应该构造几个 i2c_msg ?要构造 2 个 i2c_msg
- 第一个 i2c_msg表示写操作,把要访问的存储地址 0x10发给设备
- 第二个 i2c_msg表示读操作
u8 d ata_addr = 0x10;
i8 data;
struct i2c_msg msgs[2];
msgs[0].addr = 0x50;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &data_addr;
msgs[1].addr = 0x50;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 1;
msgs[1].buf = &data;
总结:
1-4 使用I2C Tools访问I2C设备
这部分看视频即可,视频中的源码是自己需要下载I2C Tools的源码才能找到。
使用I2C Tools 操作传感器 AP3216C(板子的右下角),AP3216C的设备地址是 0x1E
AP3216C是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:
① 复位:往寄存器0 写入 0x4
② 使能:往寄存器0 写入 0x3
③ 读光强:读寄存器0xC 、 0xD得到2字节的光强
④ 读距离:读寄存器0xE 、 0xF得到2字节的距离值
检测总线bus0上是否有I2C设备
i2cdetect -y 0
查询板子上有多少条I2C总线
i2cdetect -l
(1) 使用 SMBus 协议
复位:使用 SMBus 协议,在0号总线上的设备0x1e的0x0地址写入强制0x4
i2cset -f -y 0 0x1e 0 0x4
使能:在0号总线上的设备0x1e的0x0地址写入强制0x3
i2cset -f -y 0 0x1e 0 0x3
读光强:读寄存器 0xC 、0xD得到2字节的光强
i2cget -f -y 0 0x1e 0xc w
读距离:读寄存器 0xE 、0xF得到 2 字节的距离值
i2cget -f -y 0 0x1e 0xe w
(2) 使用 I2C 协议
使用下面指令,依次完成上述操作
i2ctransfer -f -y 0 w2@0x1e 0 0x4
i2ctransfer -f -y 0 w2@0x1e 0 0x3
i2ctransfer -f -y 0 w1@0x1e 0xc r2
i2ctransfer -f -y 0 w1@0x1e 0xe r2
(3) I2C Tools访问I2C设备的2种方式
I2C Tools 可以通过SMBus来访问 I2C 设备,也可以使用一般的I2C协议来访问 I2C 设备。
① 怎么指定 I2C控制器?
i2c dev.c 为每个I2C控制器 (I2C Bus 、I2C Adapter) 都生成一个设备节点: /dev/i2c 0 、/dev/i2c 1等等
open 某个 /dev/i2c X 节点,就是去访问该 I2C 控制器下的设备;
② 怎么指定 I2C设备?
通过ioctl 指定 I2C 设备的地址
③ 怎么传输数据?
一般的 I2C 方式: ioctl(file, I2C_RDWR, &rdwr)
SMBus 方式: ioctl(file, I2C_SMBUS, &args)
1-5 编写APP直接访问EEPROM
根据实验和外设做实验即可,注意的地方有,
① 首先EEPROM白板对齐扩展板,防止插反。
② 插入对应的I2C插排,别插到SPI去了
③ 输出i2c检测指令,查看0x50地址位于那条总线上,如下图所示我的就是位于bus0总线上(与视频就不同)
i2cdetect -y 0
i2cdetect -y 1
代码解析
/* ./at24c02 <i2c_bus_number> w "100ask.taobao.com"
* ./at24c02 <i2c_bus_number> r
*/
int main(int argc, char **argv)
{
unsigned char dev_addr = 0x50; /*i2c设备地址 片外地址*/
unsigned char mem_addr = 0; /*存储地址 从0地址开始 片内地址*/
unsigned char buf[32];
int file;
char filename[20];
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;
}
/*argv[1]代表是第二个字符串 argv[1][0]代表第二个字符串中的第一个字符*/
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'){ /*写操作*/
/*write str: argv[3]*/
unsigned char *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;
}
实验结果