Linux学习笔记9_编写APP直接访问EEPROM

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),会报错。
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值