树莓派(4B)编写i2c驱动读取eeprom(AT24C02)

首先是修改设备树

在设备树里面,其引脚已经定义好了,只需要在其添加具体设备信息

	i2c0_pins: i2c0 {
		brcm,pins = <0 1>;
		brcm,function = <BCM2835_FSEL_ALT0>;
		brcm,pull = <BCM2835_PUD_UP>;
	};

	i2c1_pins: i2c1 {
		brcm,pins = <2 3>;
		brcm,function = <BCM2835_FSEL_ALT0>;
		brcm,pull = <BCM2835_PUD_UP>;
	};

	i2c3_pins: i2c3 {
		brcm,pins = <4 5>;
		brcm,function = <BCM2835_FSEL_ALT5>;
		brcm,pull = <BCM2835_PUD_UP>;
	};

	i2c4_pins: i2c4 {
		brcm,pins = <8 9>;
		brcm,function = <BCM2835_FSEL_ALT5>;
		brcm,pull = <BCM2835_PUD_UP>;
	};

	i2c5_pins: i2c5 {
		brcm,pins = <12 13>;
		brcm,function = <BCM2835_FSEL_ALT5>;
		brcm,pull = <BCM2835_PUD_UP>;
	};

	i2c6_pins: i2c6 {
		brcm,pins = <22 23>;
		brcm,function = <BCM2835_FSEL_ALT5>;
		brcm,pull = <BCM2835_PUD_UP>;
	};

	i2s_pins: i2s {
		brcm,pins = <18 19 20 21>;
		brcm,function = <BCM2835_FSEL_ALT0>;
	};

brcm,pull = <BCM2835_PUD_UP>; 为设置引脚的上拉。说明文档在/Documentation/devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt

&i2c1 {
	pinctrl-names = "default";
	pinctrl-0 = <&i2c1_pins>;
	clock-frequency = <100000>;
};

修改其为

&i2c1 {
	pinctrl-names = "default";
	pinctrl-0 = <&i2c1_pins>;
	clock-frequency = <100000>;
	status ="okay";
	at24c02@50{
		compatible = "at24c02,bcm";
		reg = <0x50>;
		status ="okay";
	};
};

其中reg = <0x50>是因为其器件地址开始为0x50(这里是使用的正点原子板子上的eeprom,eeprom连线如下);从器件地址是8位,前7位为器件起始地址,最后一位是读写位。在写reg属性时不考虑该位,即去掉R/W位,则为1010000就相当于0x50;
在这里插入图片描述

在这里插入图片描述

编译设备树,然后加载设备树,会出现以下信息。
在这里插入图片描述
1-0050中的1表示i2c1,0050表示从机地址
我在加载设备树的时候,/sys/bus/i2c/devices/下没有设备信息,
解决办法:
sudo raspi-config
在这里插入图片描述
进入第5项 5 Interfacing options…
在这里插入图片描述
再进入P5,使能i2C,
在这里插入图片描述
使能i2c后,可能会报w1_master_driver w1_bus_master1: Attaching one wire slave 00.e00000000000 crc e9 [ 359.406475] w1_master_driver w1_bus_master1: Family 0 for 00.e00000000000.e9 is not registered.
解决方法是修改config.txt文件

# 以64位读取内核
arm_64bit=1
# 想要以ARMV8的模式启动,设置此选项
arm_control=0x200
# 内核的名字
kernel=kernel8.img

# u-boot进行引导kernel时延迟几秒
boot_delay=1

# 关闭蓝牙功能
# See /boot/overlays/README for all available options
dtoverlay=disable-bt
# 开启音频snd_bcm2835
dtparam=audio=on

我的内核镜像是设置的kernel8.img,64位系统。
其内核编译方法可参考链接: link

编写驱动程序

#include <linux/init.h>    
#include <linux/module.h>  
#include <linux/moduleparam.h> 
#include <linux/fs.h>             
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>

#define AT24C02_CNT 1
#define AT24C02_NAME "at24c02"
#define AT24C02_SIZE 256   /*  AT24C02 为256字节     */

/* gpioled设备结构体 */
struct at24c02_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	void *private_data; /* 私有数据,一般会设置为 i2c_client ,在与设备树匹配成功后,会在probe函数传入i2c_client*/
	struct mutex lock;
};


static struct at24c02_dev at24c02dev;


static unsigned char io_limit = 128; /*一次最多读取128字节*/
static unsigned char write_timeout = 25; /*i2c通信超时时间*/
static unsigned char at24c02_page_size = 8; /*at24c02 每页8字节*/

/* 读取at24c02的N个寄存器值*/
static int at24c02_read_regs(struct at24c02_dev *dev, u8 reg, void *val, int len){
	int status;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	struct i2c_msg msg[2];

	unsigned long timeout,read_time;

	memset(msg, 0, sizeof(msg));  /* 将msg清零 */

	if(len > io_limit)
		len = io_limit;

	/*  msg[0]发送要读取的寄存器首地址  */
	msg[0].addr = client->addr;   /*从机地址,也就是at24c02器件地址*/
	msg[0].flags = 0;     /*  标记为发送数据*/
	msg[0].buf = &reg;    /*  要发送的数据,也就是寄存器地址(要读取的寄存器地址)*/
	msg[0].len = 1;       /*要发送的寄存器地址长度为1*/

	/*  msg[1]读取寄存器数据  */
	msg[1].addr = client->addr;   /*从机地址,也就是at24c02地址*/
	msg[1].flags = I2C_M_RD;     /*  表示读数据*/
	msg[1].buf = val;    /*  存储从寄存器读取到的数据*/
	msg[1].len = len;       /*要读取的寄存器长度*/

	timeout = jiffies + msecs_to_jiffies(write_timeout);

	do{
		read_time =jiffies;
		status = i2c_transfer(client->adapter, &msg[0], 2);
		if (status ==2)
			return  len;
		msleep(1);
		
	}while(time_before(read_time,timeout));
    return -ETIMEDOUT;


}


/*向at24c02写N个数据 */
static int at24c02_write_regs(struct at24c02_dev *dev, u8 reg, u8 *buf, int len){

	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	struct i2c_msg msg;

	ssize_t status =0;

	unsigned long timeout,write_time;
	int i=0;

	if(len>at24c02_page_size )
		len =at24c02_page_size;

	msg.buf = kmalloc(len+2,GFP_KERNEL);
	if(!msg.buf)
		return -ENOMEM;
	
	/*构建要发送的数据,也就是寄存器首地址+实际的数据*/
	/*  msg[0]发送要读取的寄存器首地址  */
	msg.addr = client->addr;   /*从机地址,也就是at24c02地址*/
	msg.flags = 0;     /*  表示为要发送的数据,也就是从机地址*/
	msg.buf[i++] = reg;    /*  要发送的数据,寄存器首地址+实际的数据*/
	memcpy(&msg.buf[i],buf,len);
	msg.len = len+1;       /*要发送数据长度长度*/

	timeout = jiffies + msecs_to_jiffies(write_timeout);
	do {
		write_time = jiffies;

		status = i2c_transfer(client->adapter, &msg, 1);
				if (status == 1){
					kfree(msg.buf);
					return status ;
				}
				msleep(1);
		}while(time_before(write_time, timeout));
	 kfree(msg.buf);
	return -ETIMEDOUT;

}


/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int at24c02_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &at24c02dev; /* 设置私有数据 */
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t at24c02_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = -EINVAL;
	int i;
    char *buffer;/*数据缓存区*/
    unsigned char pos = filp->f_pos; /*读取位置*/
	struct at24c02_dev *dev = filp->private_data;
	buffer =(char *)kmalloc(cnt,GFP_KERNEL);

	for(i=0;i<=cnt;i++)
		buffer[i]=0;     /*给字符串添加结束符*/
    if(!buffer)
    	return -ENOMEM;

    mutex_lock(&dev->lock);
	
	ret = at24c02_read_regs(dev,pos, buffer, cnt);
	if(ret < 0 )
   	{
       printk("at24c02 read error\n");
	   kfree(buffer);
	   return ret;
   	}
	printk("read %s\r\n",buffer);
	copy_to_user(buf, (void *)buffer, cnt);
	kfree(buffer);
	mutex_unlock(&dev->lock);
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t at24c02_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = -EINVAL;
	char *buffer; /*缓冲区*/
	unsigned char pages; /*页数*/
	unsigned char num; /*不足一页剩下的字节数*/
	unsigned char pos = filp->f_pos; /*写入的地址*/
	int i=0;

	struct at24c02_dev *dev = filp->private_data;

	buffer = (char *)kmalloc(cnt,GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	pages = cnt / at24c02_page_size;
	num = cnt % at24c02_page_size;
	ret = copy_from_user((void *)buffer, buf, cnt);

	mutex_lock(&dev->lock);
	for(i=0;i<pages;i++){
		ret = at24c02_write_regs(dev,pos,&buffer[i*at24c02_page_size],at24c02_page_size);
		if(ret <0)
			{
			printk("at24c02 write error\n");
			kfree(buffer);
			return ret;
		}
		pos += 8;
	}
	if(num){
		ret = at24c02_write_regs(dev,pos,&buffer[i*at24c02_page_size],num);
		if(ret <0)
			{
			printk("at24c02 write error\n");
			kfree(buffer);
			return ret;
		}
	}
	mutex_unlock(&dev->lock);	
   /*释放缓冲区内存*/
   kfree(buffer);
  // devm_kfree(&dev->client->dev,buffer);
  return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int at24c02_release(struct inode *inode, struct file *filp){
	return 0;
}
loff_t at24c02_llseek(struct file *file, loff_t offset, int whence)
{
	loff_t ret,pos,oldpos;
	oldpos = file->f_pos;
	switch (whence) 
	{
		case SEEK_SET:
		    pos = offset; 
			break;
		case SEEK_CUR:
	        pos = oldpos + offset;
			break;
		case SEEK_END:
	        pos = AT24C02_SIZE - offset;
			break;	
		default:
		    printk("cmd not supported\n");
			break;
	}
	
	if(pos < 0 || pos > AT24C02_SIZE)
	{ 	
		printk("error: pos > AT24C02_SIZE !\n");
		ret = -EINVAL;
		return ret;
	}
    file->f_pos = pos;
	ret = offset;	
	return ret;
}



/* 设备操作函数 */
static struct file_operations at24c02_fops = {
	.owner = THIS_MODULE,
	.open = at24c02_open,
	.read = at24c02_read,
	.write = at24c02_write,
	.release = at24c02_release,
	.llseek = at24c02_llseek,
};

static int at24c02_probe(struct i2c_client *client,
								const struct i2c_device_id *id)
{
	int ret = 0;
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	at24c02dev.major = 0;

	/* 搭建字符设备驱动框架,  */ 
	/* 1、注册字符设备 */
	if (at24c02dev.major ){
		at24c02dev.devid = MKDEV(at24c02dev.major,0);
		ret = register_chrdev_region(at24c02dev.devid,AT24C02_CNT,AT24C02_NAME);
	}else{
		ret = alloc_chrdev_region(&at24c02dev.devid,0,AT24C02_CNT,AT24C02_NAME);
		at24c02dev.major = MAJOR(at24c02dev.devid);
	}
	if (ret < 0){
		printk("at24c02 chrdev_region error");
		goto fail_devid;
	}

	/* 2、初始化cdev */
	at24c02dev.cdev.owner = THIS_MODULE;
	cdev_init(&at24c02dev.cdev, &at24c02_fops);
	
	/* 3、添加一个cdev */
	ret = cdev_add(&at24c02dev.cdev,at24c02dev.devid,AT24C02_CNT);
	if (ret < 0){
		goto fail_cdev;
	}

	/* 4、创建类 */
	at24c02dev.class = class_create(THIS_MODULE, AT24C02_NAME);
	if (IS_ERR(at24c02dev.class)) {
		ret =  PTR_ERR(at24c02dev.class);
		goto fail_class;
	}

	/* 5、创建设备 */
	at24c02dev.device = device_create(at24c02dev.class, NULL, at24c02dev.devid, NULL, AT24C02_NAME);
	if (IS_ERR(at24c02dev.device)) {
		ret =  PTR_ERR(at24c02dev.device);
		goto fail_device;
	}

	at24c02dev.private_data = client;
	return 0;		

fail_device:
	class_destroy(at24c02dev.class);
fail_class:
	cdev_del(&at24c02dev.cdev);
fail_cdev:
	unregister_chrdev_region(at24c02dev.devid,AT24C02_CNT);
fail_devid:
	return ret;

}
static int at24c02_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	cdev_del(&at24c02dev.cdev);/*  删除cdev */
	unregister_chrdev_region(at24c02dev.devid, AT24C02_CNT); /* 注销设备号 */

	device_destroy(at24c02dev.class, at24c02dev.devid);
	class_destroy(at24c02dev.class);
	return 0;
}
/*   传统匹配表    */
static struct i2c_device_id at24c02_id[] = {
	{"at24c02",0},
	{}
};
	
/*  设备树匹配表   */
static struct of_device_id at24c02_of_match[] ={
	{.compatible ="at24c02,bcm"},
	{}
};
static struct i2c_driver at24c02_driver = {
	.probe = at24c02_probe,
	.remove = at24c02_remove,
	.driver = {
		.name = "at24c02",
		.owner = THIS_MODULE,
		/*使用设备树用of_match_table匹配*/
		.of_match_table = of_match_ptr(at24c02_of_match),
	},
	/*未使用设备树使用id_table匹配*/
	.id_table =at24c02_id,
};

static int __init at24c02_init(void){
	int ret = 0;
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/*1、先创建i2c设备*/
	ret = i2c_add_driver(&at24c02_driver);
	return ret;

}

static void __exit at24c02_exit(void){
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	i2c_del_driver(&at24c02_driver);
}


module_init(at24c02_init);
module_exit(at24c02_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("gongzu");
MODULE_DESCRIPTION("AT24C02 driver");

app.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#include "linux/input.h"
/***************************************************************
*argc:应用程序个数
*argv[]:具体的参数内容,字符串形式
*./at24c02app <filename>
*./at24c02app /dev/at24c02
***************************************************************/

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */


int main(int argc, char *argv[])
{
	int fd;
	int err = 0;
	char *filename;
	char *read_value;
	char *write_value;
	char offset = 0;
	if (argc != 5) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if (fd < 0) {
		printf("Can't open file %s\r\n", filename);
		return -1;
	}
	
	offset =atoi(argv[4]);
	lseek(fd,offset,SEEK_SET);
	
	if(atoi(argv[2]) == 1){
		read_value=(char*)calloc(atoi(argv[3]), sizeof(char));
		err = read(fd,read_value,atoi(argv[3]));
		if(err ==0){
			printf("read success %s\r\n",read_value);
		}
		free(read_value);
	}
	else if(atoi(argv[2]) == 2){
		write_value=(char*)calloc(atoi(argv[3]), sizeof(char));
		write(fd, argv[3],strlen(argv[3]));
		free(write_value);
	}
		
	
	close(fd);
	return 0;
}

在编写驱动的时候踩的坑:读取的at24c02数据,在利用printk打印时,他在输出指定长度的数据后,会随机输出,后面才想到读取到的数据不是字符串,需要手动添加结束符。

编译运行结果示意图
sudo ./app /dev/at24c02 1 1 0
1 1 0分别是读,读1位,从0开始
sudo ./app /dev/at24c02 2 1 0
2 1 0分别是写,写1位,从0开始
在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值