首先是修改设备树
在设备树里面,其引脚已经定义好了,只需要在其添加具体设备信息
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 = ® /* 要发送的数据,也就是寄存器地址(要读取的寄存器地址)*/
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开始