EEPROM (Electrically Erasable Programmable read only memory),指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。
编写一个应用程序利用iic协议对EEPROM进行访问。
一、首先按如图所示的方式接好拓展版和EEPROM。
二、从EEPROM的芯片手册中确定设备地址
由图可知,设备地址由下图前七位确定,由原理图可知,它的地址为0x50
三、看设备挂在哪个iic下
借助i2cdecect工具可以查看E2PROOM所在插槽对应芯片的哪一个I2C控制器。
输入i2cdetect -y 0或者i2cdetect -y 1查看地址为50的设备在哪,可以看到,这个E2PROOM设备所在的插槽对应的是i2c总线0。
用i2cdetect -l可以查看设备有几个i2c总线。
三、如何读写这个E2PROOM?
对于本实验的E2PROOM,型号为AT24C02,其对应的存储空间为(256*8):
写操作:想要写数据,首先要发出START信号,然后发出设备地址,然后发送存储的地址(对应的寄存器),然后发送数据。
读操作:对于Current Address Read,发出start信号,若不发送地址,可以从当前该芯片内部默认保存的地址上得到一个数据。
还可以如下图所示操作:先发出start信号,再发出设备地址,然后发出存储地址(寄存器),再发出start信号,然后再发出设备地址,就能读到该地址上的数据,为了读取更方便,通常采用Sequential Read连续读操作,再进行如上述所言操作之后读到了第一个数据,从机会发送一个ACK应答信号,然后可以继续读该地址的第二个…第N个数据。
四、编写程序
首先我们要交叉编译好i2c-tools接下来就是编写应用程序了。首先我们可以参考i2c-tools源码里的头文件
因为我们接收到的参数都是字符串类型数据,可以用 -‘0’ 的方式将其转为整型
argv[1]-'0'
用source insight 参照i2ctools里的源码写了以下例程,访问EEPROOM。
#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"
//要达到以下目标
/* ./at24c02 <i2c_bus_number> w "100ask.taobao.com"
* ./at24c02 <i2c_bus_number> r
*/
int main(int argc,char **argv) /*为什么是两个星星,因为char*是字符串*/
{ /*而*argv和argv[]一样,所以定义一个字符串数组*/
unsigned char dev_addr = 0x50; //设置设备地址
unsigned char mem_addr = 0; //设置存储空间的地址,从0开始读
unsigned char buf[32]; //只读32个字节
int file;
char filename[20];
int* str;
if(argc != 3 && argc != 4)/*当输入的参数个数不是3或者4的时候,也就是不符合要求*/
{
printf("Usage:\n");
printf("write eeprom: %s /dev/i2c-0|1|2 w string\n",argv[0]);//命令格式的提示信息
printf("read eeprom: %s /dev/i2c-0|1|2 r \n",argv[0]);
return -1;
}
file = open_i2c_dev(argv[1][0]-'0', filename, sizeof(filename), 0); //打开iic设备
//若打开失败,输出错误信息
if(file < 0)
{
printf("can't open %s\n",filename);
return -1;
}
//设置地址
if(set_slave_addr(file, dev_addr, 1)) //为什么设为1是强制设置
{
printf("can't set_slave_addr %s\n",filename);
return -1;
}
//读写数据
if(argv[2][0] == 'w')
{
//write str argv[3]
str = argv[3];
while(*str)
{ //写数据
i2c_smbus_write_byte_data(file, mem_addr,*str);
mem_addr++;
str++;
}
i2c_smbus_write_byte_data(file, mem_addr,0);
}
else
{
//读数据
i2c_smbus_read_i2c_block_data(file, mem_addr, sizeof(buf),buf);
buf[31]='\0';//设置最后一个字符为空字符,结束字符串
printf("get data: %s\n",buf);
}
return 0;
}
下面的all是一种约定,认为目标 all 可以同时做很多事儿,也就能同时生成多个目标~
$符号有三种用法:1.变量引用;2.函数调用;3.shell命令
在这里引入CROSS_COMPILE变量,指定交叉编译。
all:
$(CROSS_COMPILE)gcc -I ./include -o myi2capp myi2capp.c i2cbusses.c smbus.c
$(CROSS_COMPILE)gcc的意思:定义要使用的编译器,非交叉编译的场合,CROSS_COMPILE为空,所以使用的就是gcc,交叉编译时(如在x86 PC上编译在ARM上运行的软件),CROSS_COMPILE会定义为类似于arm_linux_gnu_的值,这时会使用交叉编译器(如arm_linux_gnu_gcc).
一般来说,如果是交叉编译,会先设置工具链,在这里这时候CROSS_COMPILE不为空,使用交叉编译。
设置好交叉编译工具链后
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask-imx6ull-sdk/TooChain/gcc-1inaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
这时候CROSS_COMPILE这个参数不为0,使用交叉编译工具链编译,编译好以后把运行程序拷贝到网络文件系统~/nfs_rootfs,并把它放到板子里的bin文件夹下,就可以在板子上运行了,但是运行的时候报错了
可以用i2cget查看错误,可以看到,在这里0x50这个设备地址上的第0个字节成功写入了数据(0x6c),而第1个字节上失败了(0xff),为了排查错误,去看eeprom的手册。
可以发现,在发出停止信号后,EEPROM进入一个写循环,在循环里需要耗费tWR的时间,在这段时间,所有的输入被禁止, 搜索发现这个twr是十毫秒。
在程序里写数据的循环里添加一个10ms的等待指令即可,用nanosleep函数,可以在Linux环境下查看一下man手册,即man nanosleep,看看需要哪个头文件,以及怎么使用该函数。
另外,上面的程序也没有对使用了错误的i2c总线进行报错。
需要跳转至下面两个函数内,去查看发生错误后的返回值是什么。
i2c_smbus_write_byte_data/i2c_smbus_read_i2c_block_data,例如下面这个函数说的错误的时候返回一个小于0的整型数。故需要定义一个整型数,当它小于0,则打印错误信息。
__s32 i2c_smbus_read_i2c_block_data(int file, __u8 command, __u8 length,
__u8 *values)
{
union i2c_smbus_data data;
int i, err;
if (length > I2C_SMBUS_BLOCK_MAX)
length = I2C_SMBUS_BLOCK_MAX;
data.block[0] = length;
err = i2c_smbus_access(file, I2C_SMBUS_READ, command,
length == 32 ? I2C_SMBUS_I2C_BLOCK_BROKEN :
I2C_SMBUS_I2C_BLOCK_DATA, &data);
if (err < 0)
return err;
for (i = 1; i <= data.block[0]; i++)
values[i-1] = data.block[i];
return data.block[0];
}
最终的代码如下:
#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) /*为什么是两个星星,因为char*是字符串*/
{ /*而*argv和argv[]一样,所以定义一个字符串数组*/
unsigned char dev_addr = 0x50; //设置设备地址
unsigned char mem_addr = 0; //设置存储空间的地址,从0开始读
unsigned char buf[32]; //只读32个字节
int file;
char filename[20];
unsigned char* str;
struct timespec req;
int ret; //定义一个用于接收读写数据是否成功的变量
if(argc != 3 && argc != 4)/*当输入的参数个数不是3或者4的时候,也就是不符合要求*/
{
printf("Usage:\n");
printf("write eeprom: %s /dev/i2c-0|1|2 w string\n",argv[0]);//命令格式的提示信息
printf("read eeprom: %s /dev/i2c-0|1|2 r \n",argv[0]);
return -1;
}
file = open_i2c_dev(argv[1][0]-'0', filename, sizeof(filename), 0); //打开iic设备
//若打开失败,输出错误信息
if(file < 0)
{
printf("can't open %s\n",filename);
return -1;
}
//设置地址
if(set_slave_addr(file, dev_addr, 1)) //为什么设为1是强制设置
{
printf("can't set_slave_addr %s\n",filename);
return -1;
}
//读写数据
if(argv[2][0] == 'w')
{
//write str argv[3]
str = argv[3];
req.tv_sec = 0;
req.tv_nsec = 20000000;//20ms
while(*str)
{ //写数据
ret = i2c_smbus_write_byte_data(file, mem_addr,*str);
if(ret)
{
printf("i2c_smbus_write_byte_data err\n");
return -1;
}
//等待时间间隔tWR(10ms)
naosleep(&req,NULL);
mem_addr++;
str++;
}
ret = i2c_smbus_write_byte_data(file, mem_addr,0);
if(ret)
{
printf("i2c_smbus_write_byte_data err\n");
return -1;
}
}
else
{
//读数据
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;
}
最后的结果:写入lyl字符,能读出来,当用了设备不在的iic总线时(程序里设置了设备地址为0x50),会报错。