1. at24c02-用户态-io读写
本着一切皆文件的理念,在linux下可使用IO函数进行at24c02的读写操作,通常是按单字节进行读写,由于at24c02数据存储量并不多,对性能影响较弱。因此本文将率先进行单字节读写示例。当然按页读写也是要实现的,万一需要呢。
在读写前势必要引用一下头文件:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
以及根据at24c02的特性定义一些宏:
#define AT24C02_PAGE_SIZE 16 /* 页大小 */
#define AT24C02_PAGE_MASK (AT24C02_PAGE_SIZE - 1) /* 页掩码 */
#define AT24C02_PAGE_SHIFT 4 /* 页移位 */
为了便于i2c设备节点的open操作,也进行了初步的封装:
static int drv_i2c_open(int adapter_nr)
{
int fd;
char filename[20];
/* open such as "/dev/i2c-%d" device node */
sprintf(filename, "/dev/i2c-%d", adapter_nr);
fd = open(filename, O_RDWR);
if (fd >= 0) {
return fd;
}
/* open such as "/dev/i2c/%d" device node */
if (errno == ENOENT) {
filename[8] = '/';
fd = open(filename, O_RDWR);
}
return fd;
}
如上,drv_i2c_open
函数考虑了"/dev/i2c-%d"和"/dev/i2c/%d"的设备节点结构,对于提高代码适应性有一定作用。
1.1. at24c02单字节读写
1.1.1. at24c02单字节读
由于at24c02读操作没有延迟,因此可直接进行IO读写,如下:
int drv_i2c_read8(int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, uint8_t * const data)
{
int fd;
int ret = 0;
if (NULL == data)
return -EINVAL;
ret = fd = drv_i2c_open(adapter_nr);
if (ret < 0)
return ret;
do {
if ((ret = ioctl(fd, I2C_SLAVE, i2c_addr)) < 0)
break;
if ((ret = write(fd, ®_addr, 1)) != 1)
break;
if ((ret = read(fd, data, 1)) != 1)
break;
} while (0);
close(fd);
return ret;
}
如上,首先使用drv_i2c_open
打开i2c设备节点,其次使用ioctl设置设备地址,再次使用write函数设置需要操作的寄存器地址,最后使用read函数读取1字节数据。
通常这么底层的函数不会暴露给用户,用户也不必按字节一个一个读,因此做一层封装:
int drv_i2c_read_bytes(int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, uint8_t * const data,
const uint8_t cnt)
{
uint8_t i;
int ret = 0;
for (i = 0; i < cnt; i++) {
ret = drv_i2c_read8(adapter_nr, i2c_addr,
reg_addr + i, data + i);
if (ret < 0)
break;
}
return ret;
}
如上,只需要用户设置i2c-bus编号、i2c设备地址、寄存器地址以及读回数据量即可轻松完成读操作。
1.1.2. at24c02单字节写
写操作就略显麻烦,由于at24c02单次写操作完毕后,需要给足够的时间at24c02进行数据同步。因此封装一个重试机制,在规定时间以及次数内无法写成功则放弃,参考如下:
static int i2c_try_write(int fd, uint8_t * data, uint8_t data_len,
uint8_t retry_times)
{
int ret = 0;
while (retry_times--) {
ret = write(fd, data, data_len);
if (ret < 0) {
/* ENXIO通常是芯片内部忙,无法响应外部操作 */
if (ENXIO == errno) {
usleep(1000); /* 每次休眠1ms */
continue;
}
perror("write");
break;
} else if (ret != data_len) {
ret = -1;
perror("write");
break;
}
break;
}
return ret;
}
如上,设定retry_times参数可指定重试次数,上述代码每次重试间隔为1ms。其原因在于运行平台的任务调度的间隔也正好是1ms(1000Hz),可降低因休眠时间过短导致CPU盲等待,实际使用时可参考具体平台略加修改。
由于是写操作,那么就没有读操作那么多流程上的事情了。读操作首先要写寄存器来设定数据读取位置,然后读数据,需要分两步走。而写操作可在待写寄存器后直接追加数据,一步完成,如下:
int drv_i2c_write8(const int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, const uint8_t data)
{
int fd;
int ret = 0;
ret = fd = drv_i2c_open(adapter_nr);
if (ret < 0)
return ret;
do {
uint8_t cnt = 5; /* 重试次数 */
uint8_t msg_payload[] = { reg_addr, data };
if ((ret = ioctl(fd, I2C_SLAVE, i2c_addr)) < 0)
break;
ret = i2c_try_write(fd, msg_payload, sizeof(msg_payload), cnt);
if (ret < 0)
break;
} while (0);
close(fd);
return ret;
}
同样,便于操作,也封装了这个函数如下:
int drv_i2c_write_bytes(const int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, uint8_t * const data,
const uint8_t cnt)
{
uint8_t i;
int ret = 0;
for (i = 0; i < cnt; i++) {
ret = drv_i2c_write8(adapter_nr, i2c_addr,
reg_addr + i, *(data + i));
if (ret < 0)
break;
}
return ret;
}
使用方法与drv_i2c_read_bytes类似。
1.1.3. at24c02单字节综合测试
测试方法很简单,从at24c02的0x11寄存开始,使用写函数写入一句不要脸的情话:"I Love You, Baby !",然后使用读函数读出来,如成功读出则测试合格,示例代码如下:
int adapter_nr = 1; /* probably dynamically determined */
int ret;
uint8_t reg = 0x11; /* Device register to access */
uint8_t i2c_addr = 0x50; /* Device address to access */
uint8_t buf[] = "I Love You, Baby !";
/* 恢复待写区域 */
system("i2ctransfer -y 1 w255@0x50 0x10 0xff=");
system("i2ctransfer -y 1 w255@0x50 0x20 0xff=");
ret = drv_i2c_write_bytes(adapter_nr, i2c_addr,
reg, buf, sizeof(buf));
if (ret < 0) {
printf("ERROR HANDLING: i2c transaction failed\n");
}
usleep(10000);
system("i2cdump -y 1 0x50 b");
memset(buf, 0, sizeof(buf));
ret = drv_i2c_read_bytes(adapter_nr, i2c_addr, reg,
buf, sizeof(buf));
if (ret < 0) {
printf("ERROR HANDLING: i2c transaction failed\n");
}
buf[sizeof(buf) - 1] = '\0';
printf("reg %d -> %s\n", reg, buf);
虽然,情话不要脸,但实验还是成功的,如下:
# ./at24c02a_app_io
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
10: ff 49 20 4c 6f 76 65 20 59 6f 75 2c 20 42 61 62 .I Love You, Bab
20: 79 20 21 00 ff ff ff ff ff ff ff ff ff ff ff ff y !.............
30: 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
50: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
70: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
90: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
f0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
reg 17 -> I Love You, Baby !
1.2. at24c02按页读写
上述使用单字节读写at24c02效率明显不高,同时反复的进行文件的打开和关闭,专治强迫症患者。因此可考虑按页读写at24c02。
1.2.1. at24c02按页读
同样,由于at24c02读没有延迟,因此可放心大胆的随意读。
int drv_i2c_read(int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, uint8_t * const data,
const uint8_t data_len)
{
int fd;
uint8_t addr = reg_addr;
int ret = 0;
if (NULL == data)
return -EINVAL;
ret = fd = drv_i2c_open(adapter_nr);
if (ret < 0)
return ret;
do {
if ((ret = ioctl(fd, I2C_SLAVE, i2c_addr)) < 0)
break;
if ((ret = write(fd, &addr, 1)) != 1)
break;
if ((ret = read(fd, data, data_len)) != data_len)
ret = -EINVAL;
} while (0);
close(fd);
return ret;
}
如上,读操作与drv_i2c_read8如出一辙,不再赘述。
1.2.2. at24c02按页写
写操作就比较麻烦了,同时要考虑写延迟和换页问题,但聪明如我怎么可能搞不定呢。
int drv_i2c_write(const int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, uint8_t * const data,
const uint8_t data_len)
{
int fd;
int ret = 0;
int loop;
if (NULL == data)
return -EINVAL;
ret = fd = drv_i2c_open(adapter_nr);
if (ret < 0)
return ret;
/* 计算跨页次数 */
loop = (reg_addr & AT24C02_PAGE_MASK) + data_len;
loop = (loop + AT24C02_PAGE_MASK) / AT24C02_PAGE_SIZE;
do {
int i;
if ((ret = ioctl(fd, I2C_SLAVE, i2c_addr)) < 0)
break;
for (i = 0; i < loop; i++) {
int j;
uint8_t cnt = 5; /* 重试次数 */
uint8_t start_addr;
uint8_t xfer_len;
uint8_t xfer_cnt;
uint8_t *xfer_offset;
uint8_t transfer_data[AT24C02_PAGE_SIZE + 1];
/* 首次以reg_addr为准,后续则以页起止地址为准 */
if (0 == i) {
start_addr = reg_addr;
} else {
start_addr = (reg_addr + i * AT24C02_PAGE_SIZE);
start_addr &= ~AT24C02_PAGE_MASK;
}
/* 已传输数据量 */
xfer_cnt = start_addr - reg_addr;
/* 计算本次需要传输的数据量 */
xfer_len = AT24C02_PAGE_SIZE -
(start_addr & AT24C02_PAGE_MASK);
/* 最后一次少于一页则按实际情况传输 */
if ((data_len - xfer_cnt) < AT24C02_PAGE_SIZE)
xfer_len = data_len - xfer_cnt;
xfer_offset = data + xfer_cnt;
/* 填充寄存器地址以及待写数据 */
transfer_data[0] = start_addr;
for (j = 1; j <= xfer_len; j++)
transfer_data[j] = *(xfer_offset + j - 1);
/* 写入 */
ret = i2c_try_write(fd, transfer_data,
xfer_len + 1, cnt);
if (ret < 0)
break;
}
} while (0);
close(fd);
return ret;
}
本函数的关键点在于第一页和最后一页的处理,第一页可能并不在页头部,因此首次写的位置和长度需要额外处理;而最后一页可能数据量不足一页,处理不慎可能会覆盖后续数据。
1.2.3. at24c02按页读写综合测试
测试方法同样简单,也是从at24c02的0x11寄存开始,不过本次写入一句更不要脸的情话:"I Love You more than I can say!",由于这句情话正好略过页头,完整写一页并且最后一页只写一个数据。一句情话同时满足三个场景的测试要求,妙啊!
int adapter_nr = 1; /* probably dynamically determined */
int ret;
uint8_t reg = 0x11; /* Device register to access */
uint8_t i2c_addr = 0x50; /* Device address to access */
uint8_t buf[] = "I Love You more than I can say!";
/* 恢复待写区域 */
system("i2ctransfer -y 1 w255@0x50 0x10 0xff=");
system("i2ctransfer -y 1 w255@0x50 0x20 0xff=");
ret = drv_i2c_write(adapter_nr, i2c_addr, reg,
buf, sizeof(buf));
if (ret < 0) {
printf("ERROR HANDLING: i2c transaction failed\n");
}
usleep(10000);
system("i2cdump -y 1 0x50 b");
memset(buf, 0, sizeof(buf));
ret = drv_i2c_read(adapter_nr, i2c_addr, reg, buf, sizeof(buf));
if (ret < 0) {
printf("ERROR HANDLING: i2c transaction failed\n");
}
buf[sizeof(buf) - 1] = '\0';
printf("reg %d -> %s\n", reg, buf);
如上,虽然,“我爱你在心口难开”,但实验的成功是必须的,如下:
# ./at24c02a_app_io
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
10: ff 49 20 4c 6f 76 65 20 59 6f 75 20 6d 6f 72 65 .I Love You more
20: 20 74 68 61 6e 20 49 20 63 61 6e 20 73 61 79 21 than I can say!
30: 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
50: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
70: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
90: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
f0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
reg 17 -> I Love You more than I can say!
1.3. 小结
使用IO函数进行at24c02的读写操作显然是最容易理解的也比较容易,但没有了设备驱动操作的灵魂,略显不伦不类。完整代码如下:
/* aarch64-linux-gnu-gcc at24c02a_app_io.c -o at24c02a_app_io -Wall */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#define AT24C02_PAGE_SIZE 16 /* 页大小 */
#define AT24C02_PAGE_MASK (AT24C02_PAGE_SIZE - 1) /* 页掩码 */
#define AT24C02_PAGE_SHIFT 4 /* 页移位 */
static int drv_i2c_open(int adapter_nr)
{
int fd;
char filename[20];
/* open such as "/dev/i2c-%d" device node */
sprintf(filename, "/dev/i2c-%d", adapter_nr);
fd = open(filename, O_RDWR);
if (fd >= 0) {
return fd;
}
/* open such as "/dev/i2c/%d" device node */
if (errno == ENOENT) {
filename[8] = '/';
fd = open(filename, O_RDWR);
}
return fd;
}
static int i2c_try_write(int fd, uint8_t * data, uint8_t data_len,
uint8_t retry_times)
{
int ret = 0;
while (retry_times--) {
ret = write(fd, data, data_len);
if (ret < 0) {
/* ENXIO通常是芯片内部忙,无法响应外部操作 */
if (ENXIO == errno) {
usleep(1000); /* 每次休眠1ms */
continue;
}
perror("write");
break;
} else if (ret != data_len) {
ret = -1;
perror("write");
break;
}
break;
}
return ret;
}
int drv_i2c_write8(const int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, const uint8_t data)
{
int fd;
int ret = 0;
ret = fd = drv_i2c_open(adapter_nr);
if (ret < 0)
return ret;
do {
uint8_t cnt = 5; /* 重试次数 */
uint8_t msg_payload[] = { reg_addr, data };
if ((ret = ioctl(fd, I2C_SLAVE, i2c_addr)) < 0)
break;
ret = i2c_try_write(fd, msg_payload, sizeof(msg_payload), cnt);
if (ret < 0)
break;
} while (0);
close(fd);
return ret;
}
int drv_i2c_read8(int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, uint8_t * const data)
{
int fd;
int ret = 0;
if (NULL == data)
return -EINVAL;
ret = fd = drv_i2c_open(adapter_nr);
if (ret < 0)
return ret;
do {
if ((ret = ioctl(fd, I2C_SLAVE, i2c_addr)) < 0)
break;
if ((ret = write(fd, ®_addr, 1)) != 1)
break;
if ((ret = read(fd, data, 1)) != 1)
break;
} while (0);
close(fd);
return ret;
}
int drv_i2c_write_bytes(const int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, uint8_t * const data,
const uint8_t cnt)
{
uint8_t i;
int ret = 0;
for (i = 0; i < cnt; i++) {
ret = drv_i2c_write8(adapter_nr, i2c_addr,
reg_addr + i, *(data + i));
if (ret < 0)
break;
}
return ret;
}
int drv_i2c_read_bytes(int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, uint8_t * const data,
const uint8_t cnt)
{
uint8_t i;
int ret = 0;
for (i = 0; i < cnt; i++) {
ret = drv_i2c_read8(adapter_nr, i2c_addr,
reg_addr + i, data + i);
if (ret < 0)
break;
}
return ret;
}
int drv_i2c_write(const int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, uint8_t * const data,
const uint8_t data_len)
{
int fd;
int ret = 0;
int loop;
if (NULL == data)
return -EINVAL;
ret = fd = drv_i2c_open(adapter_nr);
if (ret < 0)
return ret;
/* 计算跨页次数 */
loop = (reg_addr & AT24C02_PAGE_MASK) + data_len;
loop = (loop + AT24C02_PAGE_MASK) / AT24C02_PAGE_SIZE;
do {
int i;
if ((ret = ioctl(fd, I2C_SLAVE, i2c_addr)) < 0)
break;
for (i = 0; i < loop; i++) {
int j;
uint8_t cnt = 5; /* 重试次数 */
uint8_t start_addr;
uint8_t xfer_len;
uint8_t xfer_cnt;
uint8_t *xfer_offset;
uint8_t transfer_data[AT24C02_PAGE_SIZE + 1];
/* 首次以reg_addr为准,后续则以页起止地址为准 */
if (0 == i) {
start_addr = reg_addr;
} else {
start_addr = (reg_addr + i * AT24C02_PAGE_SIZE);
start_addr &= ~AT24C02_PAGE_MASK;
}
/* 已传输数据量 */
xfer_cnt = start_addr - reg_addr;
/* 计算本次需要传输的数据量 */
xfer_len = AT24C02_PAGE_SIZE -
(start_addr & AT24C02_PAGE_MASK);
/* 最后一次少于一页则按实际情况传输 */
if ((data_len - xfer_cnt) < AT24C02_PAGE_SIZE)
xfer_len = data_len - xfer_cnt;
xfer_offset = data + xfer_cnt;
/* 填充寄存器地址以及待写数据 */
transfer_data[0] = start_addr;
for (j = 1; j <= xfer_len; j++)
transfer_data[j] = *(xfer_offset + j - 1);
/* 写入 */
ret = i2c_try_write(fd, transfer_data,
xfer_len + 1, cnt);
if (ret < 0)
break;
}
} while (0);
close(fd);
return ret;
}
int drv_i2c_read(int adapter_nr, const uint8_t i2c_addr,
const uint8_t reg_addr, uint8_t * const data,
const uint8_t data_len)
{
int fd;
uint8_t addr = reg_addr;
int ret = 0;
if (NULL == data)
return -EINVAL;
ret = fd = drv_i2c_open(adapter_nr);
if (ret < 0)
return ret;
do {
if ((ret = ioctl(fd, I2C_SLAVE, i2c_addr)) < 0)
break;
if ((ret = write(fd, &addr, 1)) != 1)
break;
if ((ret = read(fd, data, data_len)) != data_len)
ret = -EINVAL;
} while (0);
close(fd);
return ret;
}
int main(void)
{
int adapter_nr = 1; /* probably dynamically determined */
int ret;
uint8_t reg = 0x11; /* Device register to access */
uint8_t i2c_addr = 0x50; /* Device address to access */
{
uint8_t buf[] = "I Love You, Baby !";
/* 恢复待写区域 */
system("i2ctransfer -y 1 w255@0x50 0x10 0xff=");
system("i2ctransfer -y 1 w255@0x50 0x20 0xff=");
ret = drv_i2c_write_bytes(adapter_nr, i2c_addr,
reg, buf, sizeof(buf));
if (ret < 0) {
printf("ERROR HANDLING: i2c transaction failed\n");
}
usleep(10000);
system("i2cdump -y 1 0x50 b");
memset(buf, 0, sizeof(buf));
ret = drv_i2c_read_bytes(adapter_nr, i2c_addr, reg,
buf, sizeof(buf));
if (ret < 0) {
printf("ERROR HANDLING: i2c transaction failed\n");
}
buf[sizeof(buf) - 1] = '\0';
printf("reg %d -> %s\n", reg, buf);
}
{
uint8_t buf[] = "I Love You more than I can say!";
/* 恢复待写区域 */
system("i2ctransfer -y 1 w255@0x50 0x10 0xff=");
system("i2ctransfer -y 1 w255@0x50 0x20 0xff=");
ret = drv_i2c_write(adapter_nr, i2c_addr, reg,
buf, sizeof(buf));
if (ret < 0) {
printf("ERROR HANDLING: i2c transaction failed\n");
}
usleep(10000);
system("i2cdump -y 1 0x50 b");
memset(buf, 0, sizeof(buf));
ret = drv_i2c_read(adapter_nr, i2c_addr, reg, buf, sizeof(buf));
if (ret < 0) {
printf("ERROR HANDLING: i2c transaction failed\n");
}
buf[sizeof(buf) - 1] = '\0';
printf("reg %d -> %s\n", reg, buf);
}
return 0;
}
email:MingruiZhou@outlook.com
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。