firefly-rk3288j开发板–linux I2C实验之eeprom驱动

1 准备工作

开发板:aio-rk3288j
SDK版本:rk3288_linux_release_20210304
下载工具:Linux_Upgrade_Tool_v2.1
内核版本:4.4.194
文件系统:buildroot
Ubuntu版本:18.04
交叉编译工具:gcc version 6.3.1 20170404

2 硬件原理图

2.1 开发板I2C接口

2.2 EEPROM模块原理图

3 修改设备树文件

3.1 驱动参数配置

I2C 的参数配置最主要就是 I2C 频率的配置,可配 I2C frequency 除了与芯片有关外,主要是由 I2C SCL rise time 决定的,因为 I2C 协议标准里面对上升沿和下降沿时间有规定要求特别是上升沿时间,如果超 过了协议规定的最大值,则 I2C 通讯可能失败,下面是协议里面规定的最大最小值范围,下图表示了二 者之间的关系:

上升沿 Tr 和下降沿 Tf,需要用示波器测量,参考下面示图:

3.2 配置DTS

设备树文件位于内核kernel/arch/arm/boot/dts目录下,我们需要打开rk3288.dtsi、rk3288-linux.dtsi、rk3288-firefly-port.dtsi、rk3288-firefly-aio.dtsi.对于i2c设备只需要打开rk3288-firefly-aio.dtsi文件,添加i2c4设备节点:

&i2c4 {status = "okay";//i2c-scl-rising-time-ns = <400>;//i2c-scl-falling-time-ns = <20>;clock-frequency = <100000>;at24c64: at24c64@50 {compatible = "firefly,24c64";reg = <0x50>;};
};

clock-frequency: 默认 frequency 为 100k 可不配置,其它 I2C 频率需要配置,最大可配置频率由 i2c- scl-rising-time-ns 决定;例如配置 400k,clock-frequency=<400000>。
i2c-scl-rising-time-ns:SCL 上升沿时间由硬件决定,改变上拉电阻可调节该时间,需通过示波器量 测, 参考上图;例如测得 SCL 上升沿 400ns,i2c-scl-rising-time-ns=<400>。(默认可以不配置,但必须保证当前的上升沿时间不能超过所配置频率下的 I2C 标准所定义的最大上升沿时间)
i2c-scl-falling-time-ns: SCL 下降沿时间, 一般不变, 等同于 i2c-sda-falling-time-ns。(默认也可以不配置)
编译内核,输入如下命令
./build.sh kernel
./build.sh updateimg

4 API函数

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

5 AT24C64驱动编写

#include <linux/module.h>//模块加载卸载函数
#include <linux/kernel.h>//内核头文件
#include <linux/types.h>//数据类型定义
#include <linux/fs.h>//file_operations结构体
#include <linux/device.h>//class_create等函数
#include <linux/ioctl.h>
#include <linux/kernel.h>/*包含printk等操作函数*/
#include <linux/of.h>/*设备树操作相关的函数*/
#include <linux/gpio.h>/*gpio接口函数*/
#include <linux/of_gpio.h>
#include <linux/platform_device.h>/*platform device*/
#include <linux/i2c.h> /*i2c相关api*/
#include <linux/delay.h> /*内核延时函数*/
#include <linux/slab.h> /*kmalloc、kfree函数*/
#include <linux/cdev.h>/*cdev_init cdev_add等函数*/
#include <asm/gpio.h>/*gpio接口函数*/
#include <asm/uaccess.h>/*__copy_from_user 接口函数*/#define  DEVICE_NAME     "eeprom"
#define  DEVICE_SIZE     256  typedef struct
{struct device_node *node;//设备树节点struct cdev cdev;//定义一个cdev结构体struct class *class;//创建一个at24cxx类struct device *device;//创建一个at24cxx设备 该设备是需要挂在at24cxx类下面的int major;//主设备号dev_t  dev_id;struct i2c_client   *client; /*适配器 probe函数中会填充此变量*//*使用SMBUS协议方式读写*/int use_smbus;int use_smbus_write;struct mutex lock;
}at24xx_typdef;static at24xx_typdef at24cxx_dev;//定义一个AT24Cxx设备
static unsigned char io_limit = 128; /*一次最多读取128字节*/
static unsigned char write_timeout = 25;/*i2c通信超时时间*/
static unsigned char at24cxx_page_size = 8;/*at24cxx 每页8字节*/static ssize_t at24_eeprom_read(at24xx_typdef *at24, char *buf,unsigned offset, size_t count)
{struct i2c_msg msg[2];u8 msgbuf[2];struct i2c_client *client;unsigned long timeout, read_time;int status, i;memset(msg, 0, sizeof(msg));/*获取设备信息*/client = at24->client;if (count > io_limit)count = io_limit;if (at24->use_smbus) {/* Smaller eeproms can work given some SMBus extension calls */if (count > I2C_SMBUS_BLOCK_MAX)count = I2C_SMBUS_BLOCK_MAX;} else {i = 0;msgbuf[i++] = offset;/*读取首地址*//* msg[0]为发送要读取的首地址 */msg[0].addr = client->addr;msg[0].buf = msgbuf;msg[0].len = i;/* msg[1]读取数据 */msg[1].addr = client->addr;msg[1].flags = I2C_M_RD;msg[1].buf = buf;        /* 读取数据缓冲区*/msg[1].len = count;      /* 读取数据长度 */}/*超时时间设置为write_timeout毫秒*/timeout = jiffies + msecs_to_jiffies(write_timeout);do {read_time = jiffies;if (at24->use_smbus) /*使用SMBUS协议*/{status = i2c_smbus_read_i2c_block_data_or_emulated(client, offset,count, buf);} else /*普通I2C协议*/{status = i2c_transfer(client->adapter, msg, 2);if (status == 2)status = count;}if (status == count)return count;/* REVISIT: at HZ=100, this is sloooow */msleep(1);} while (time_before(read_time, timeout));   return -ETIMEDOUT;
}static ssize_t at24_eeprom_write(at24xx_typdef *at24,  char *buf,unsigned offset, size_t count)
{struct i2c_client *client;struct i2c_msg msg;ssize_t status = 0;unsigned long timeout, write_time;int i = 0;/*获取设备信息*/client = at24->client;/* 最大写入数据是1页 */if (count > at24cxx_page_size)count = at24cxx_page_size;/*msg.buf申请内存*/msg.buf = kmalloc(count+2,GFP_KERNEL);if(!msg.buf)return -ENOMEM;/* 不使用SMBUS协议  需要填充msg */if (!at24->use_smbus) {msg.addr = client->addr;msg.flags = 0;           /*标记为写数据*/msg.buf[i++] = offset;   /*写的起始地址*/memcpy(&msg.buf[i], buf, count);msg.len = i + count;     /*写数据的长度*/}timeout = jiffies + msecs_to_jiffies(write_timeout);do {write_time = jiffies;if (at24->use_smbus_write) {switch (at24->use_smbus_write) {case I2C_SMBUS_I2C_BLOCK_DATA:status = i2c_smbus_write_i2c_block_data(client,offset, count, buf);break;case I2C_SMBUS_BYTE_DATA:status = i2c_smbus_write_byte_data(client,offset, buf[0]);break;}if (status == 0)status = count;} else {status = i2c_transfer(client->adapter, &msg, 1);if (status == 1)status = count;}if (status == count){kfree(msg.buf);return count;}               /* REVISIT: at HZ=100, this is sloooow */msleep(1);} while (time_before(write_time, timeout));kfree(msg.buf);return -ETIMEDOUT;
}static int at24cxx_open(struct inode *inode, struct file *filp)
{/*使用普通I2C模式读写*/  at24cxx_dev.use_smbus = 0;at24cxx_dev.use_smbus_write = 0; filp->private_data = &at24cxx_dev;  printk("open at24cxx success\n");return 0;
}static int at24cxx_release(struct inode* inode ,struct file *filp)
{return 0;
}static int at24cxx_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{   int ret = -EINVAL;char *buffer;/*缓冲区*/unsigned char pages;/*页数*/unsigned char num;/*不足一页剩下的字节数*/unsigned char pos = filp->f_pos;/*写入的地址*/int i = 0; at24xx_typdef *dev = (at24xx_typdef *)filp->private_data;printk("w_count = %d w_pos = %d\n",count,pos);buffer =(char *)kmalloc(count,GFP_KERNEL);if (!buffer)return -ENOMEM;pages = count / at24cxx_page_size;num = count % at24cxx_page_size;/*将需要写入的数据拷贝到内核空间的缓冲区*/ret = copy_from_user((void *)buffer,buf,count);mutex_lock(&dev->lock);for(i = 0; i < pages; i++){ret = at24_eeprom_write(dev,&buffer[i*at24cxx_page_size],pos,at24cxx_page_size);if(ret < 0) {printk("at24cxx write error\n");kfree(buffer);return ret;}pos += 8;}if(num){   ret = at24_eeprom_write(dev,&buffer[i*at24cxx_page_size],pos,num);if(ret < 0) {printk("at24cxx write error\n");kfree(buffer);return ret;}}   mutex_unlock(&dev->lock);    /*释放缓冲区内存*/kfree(buffer);return 0;
}static ssize_t at24cxx_read(struct file *filp,char __user *buf, size_t count,loff_t *f_pos)
{int ret = -EINVAL;char *buffer;/*数据缓存区*/unsigned char pos = filp->f_pos; /*读取位置*/at24xx_typdef *dev = (at24xx_typdef *)filp->private_data;  printk("r_count = %d  r_pos = %d\n",count,pos);buffer =(char *)kmalloc(count,GFP_KERNEL);if(!buffer)return -ENOMEM;mutex_lock(&dev->lock);ret = at24_eeprom_read(dev,buffer,pos,count);if(ret < 0 ){printk("at24cxx read error\n");kfree(buffer);return ret;}/*将读取到的数据返回用户层*/ret = copy_to_user(buf,(void *)buffer,ret);mutex_unlock(&dev->lock);/*释放缓冲区内存*/kfree(buffer);return 0;
}loff_t at24cxx_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 = DEVICE_SIZE - offset;break;  default:printk("cmd not supported\n");break;}if(pos < 0 || pos > DEVICE_SIZE){   printk("error: pos > DEVICE_SIZE !\n");ret = -EINVAL;return ret;}file->f_pos = pos;ret = offset;   return ret;
}static struct file_operations at24cxx_fops={.owner      = THIS_MODULE,.open       = at24cxx_open,.write      = at24cxx_write,.read       = at24cxx_read,.release    = at24cxx_release,.llseek      = at24cxx_llseek,
};static int at24cxx_probe(struct i2c_client *client,const struct i2c_device_id *id)
{int ret = -1;const char *string = NULL;at24xx_typdef *dev = &at24cxx_dev;printk("at24cxx probe!\n"); /*获取设备节点*/at24cxx_dev.node = of_find_node_by_path("/i2c@ff160000/at24c64@50");if(at24cxx_dev.node == NULL){printk("find node by path fialed!\r\n"); return -1;}/*读取at24cxx设备节点的compatible属性值*/ret = of_property_read_string(at24cxx_dev.node,"compatible",&string);if(ret == 0){printk("%s\n",string);}/*申请设备号*/alloc_chrdev_region(&at24cxx_dev.dev_id,0,1,DEVICE_NAME);/*初始化一个cdev*/cdev_init(&at24cxx_dev.cdev,&at24cxx_fops);/*向cdev中添加一个设备*/cdev_add(&at24cxx_dev.cdev,at24cxx_dev.dev_id,1);/*创建一个eeprom_class类*/at24cxx_dev.class = class_create(THIS_MODULE, "eeprom_class");if(at24cxx_dev.class == NULL){printk("class_create failed\r\n");return -1;}/*在eeprom_class类下创建一个eeprom_class设备*/at24cxx_dev.device = device_create(at24cxx_dev.class, NULL, at24cxx_dev.dev_id, NULL, DEVICE_NAME);/*每个设备都会分配一个client*/at24cxx_dev.client = client;printk("slave address is %x\n",client->addr);mutex_init(&dev->lock);return  0;
}static int at24cxx_remove(struct i2c_client *client)
{  printk("at24cxx remove!\n"); /*删除at24cxx类*/cdev_del(&at24cxx_dev.cdev);/*释放at24cxx设备号*/unregister_chrdev_region(at24cxx_dev.dev_id, 1);/*注销at24cxx设备*/device_destroy(at24cxx_dev.class, at24cxx_dev.dev_id);/*注销at24cxx类*/class_destroy(at24cxx_dev.class);return 0;
}static const struct of_device_id at24cxx_of_match[] = {{.compatible = "firefly,24c64"},{},
};static const struct i2c_device_id at24cxx_id[] = {{ "xxxx", 0 },{},
};static struct i2c_driver at24cxx_driver = {.driver = {.owner = THIS_MODULE,.name = "firefly,24c64",.of_match_table = at24cxx_of_match,},.probe = at24cxx_probe,.remove  = at24cxx_remove,  .id_table    = at24cxx_id,
};static int __init at24cxx_init(void)
{return i2c_add_driver(&at24cxx_driver);
}static void at24cxx_exit(void)
{i2c_del_driver(&at24cxx_driver);printk("module exit ok\n");
}module_init(at24cxx_init);
module_exit(at24cxx_exit);MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("at24cxx driver");
MODULE_AUTHOR("lsjml2022");

6 编写测试App

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <limits.h>
#include <asm/ioctls.h>
#include <time.h>
#include <pthread.h>
#include<string.h>void print_data(const char *title, char *dat,int count)
{int i = 0; printf(title);for(i = 0; i < count; i++) {printf(" 0x%x", dat[i]);}printf("\n");
}int main(int argc, char *argv[])
{int fd,i,ret;int count = 128;char offset = 0;char writebuf[128],readbuf[128];/*判断传入的参数是否合法*/if(argc != 2){printf("Usage:error!\n");return -1;}/*解析传入的参数*/offset =atoi(argv[1]);printf("offset = %d\n",offset);/*打开设备文件*/fd = open("/dev/eeprom",O_RDWR);if(fd < 0){printf("open eeprom fail fd = %d\n",fd); close(fd);return fd;}/*缓存数组赋值*/memset(writebuf, 0xaa, sizeof(writebuf));/*写入数据*/ lseek(fd,offset, SEEK_SET);ret = write(fd, writebuf, sizeof(writebuf));if(ret < 0){printf("write to at24c64 error\n");close(fd);return ret;}/*打印数据*/print_data("write to at24c64:", writebuf, count);/*读取数据*/ret = lseek(fd,offset,SEEK_SET);printf("lseek = %d\n",ret);memset(readbuf, 0, sizeof(readbuf));ret = read(fd, readbuf,  count);if(ret < 0){printf("read from at24c64 error\n");close(fd);return ret;}/*打印数据*/print_data("read from at24c64:",readbuf,count);/*比较写入数据与读出数据是否一致*/ret = memcmp(readbuf, writebuf,  count);if(ret){printf("Writing data is different from reading data...\n");}else{printf("Write data is the same as read data...\n");}close(fd);return 0;
}

7 编译驱动程序和测试APP

7.1 编译驱动程序

KDIR:=/rk3288_linux/rk3288_linux_release_20220607/kernel
obj-m:=at24c64.o
PWD:=$(shell pwd)
all:$(MAKE) -C $(KDIR) M=$(PWD)
clean:rm -rf *.ko *.order *.symvers *.cmd *.o *.mod.c *.tmp_versions .*.cmd .tmp_versions

输入如下命令编译出驱动模块文件:
make -j8

编译成功后会生成一个.ko文件拷贝到开发板上并加载

7.2 编译测试App

输入如下命令编译测试 eepromApp.c 这个测试程序:
arm-linux-gnueabihf-gcc eepromApp.c -o eepromApp
编译成功以后就会生成 eepromApp 这个应用程序

7.3 运行测试

编译出来的.ko 和 eepromApp 这两个文件拷贝到/lib/modules/4.4.194目录中,重启开发板,进入目录/lib/modules/4.4.194中输入加载.ko驱动模块:
insmod at24c64.ko

驱动加载成功以后就可以使用eepromApp软件来测试驱动是否正常,输入如下命令:
./eepromApp /dev/eeprom

rmmod at24c64.ko //卸载驱动模块

firefly-rk3288j开发板--linux I2C实验之eeprom驱动相关推荐

  1. firefly-rk3288j开发板--linux NFC实验之RC522驱动

    firefly-rk3288j开发板–linux NFC实验之RC522驱动 1 准备工作 开发板:aio-rk3288j SDK版本:rk3288_linux_release_20210304 下载 ...

  2. 【Renesas RA6M4开发板之I2C(模拟)驱动ssd1306 OLED屏幕】

    [Renesas RA6M4开发板之I2C(模拟)驱动ssd1306 OLED屏幕] 1.0 OLED 1.1产品特性: 1.2产品参数: 2. RT-theard配置 2.1 硬件需求 2.2 软件 ...

  3. OK6410开发板linux系统下的SPI驱动和测试

    OK6410下的SPI驱动是可以用的,但是飞凌把它作为其它用途了,我们修改一些代码才能在/dev目录下创建SPI的设备节点文件 Step1:打开arch/arm/mach_s3c64XX/mach_m ...

  4. 迅为IMX6ULL开发板Linux I2C设备驱动编写流程-信息描述

    1 .不使用设备树文件 当开始编写 I2C 设备驱动时,首先要添加设备信息.先来看一下在不使用设备树,使用平台文件时, 如何在平台文件中添加 I2C 设备信息. 在平台文件中通过 i2c_board_ ...

  5. 迅为IMX6ULL开发板Linux学习教程

    1800+页使用手册(持续更新)+入门视频教程+实战视频教程 关注VX公众号:迅为电子 ,  回复 :终结者,免费获取产品资料  让教程更细致,终结入门难! 所有教程由迅为原创,是迅为工作多年的工程师 ...

  6. firefly-rk3288开发板Linux驱动——AT24C02 E2PROM驱动

    一.Linux I2C设备体系 Linux源码中I2C驱动目录介绍: 目录/文件 介绍 i2c-core.c I2C核心功能以及proc/bus/i2c*接口 i2c-dev.c I2C适配器的设备文 ...

  7. 【迅为iMX6Q】开发板 Linux 5.15.71 RTL8211E 以太网驱动适配

    相关参考 [迅为iMX6Q]开发板 u-boot 2022.04 SD卡 启动 [迅为iMX6Q]开发板 u-boot 2020.04 RTL8211E 以太网驱动适配 [迅为iMX6Q]开发板 Li ...

  8. NUC980开发板Linux系统EC20模块 移植 串口 PPP拨号

    NUC980开发板Linux系统EC20模块 移植 串口 PPP拨号 1. EC20模块连接 2. Linux内核配置 3. 交叉编译PPP 4. 拨号脚本 5. 进行拨号 1. EC20模块连接 在 ...

  9. 基于全志A33开发板linux系统移植学习记录(Boot0)

    基于全志A33开发板linux系统移植学习记录 第一章 Boot0基于ARMGCC的编译与修改 文章目录 基于全志A33开发板linux系统移植学习记录 前言 一.全志A33简介以及上电引导流程 二. ...

最新文章

  1. Spark入门系列(二)| 1小时学会RDD编程
  2. seaborn使用violinplot函数可视化小提琴图、使用stripplot函数添加抖动数据点(jittered points)、显示数据的稠密程度
  3. 《LeetCode力扣练习》第15题 C语言版 (做出来就行,别问我效率。。。。)
  4. 处理器后面的字母含义_电脑天天用,但CPU后缀的一个字母你知道代表这什么吗?...
  5. Excel:5种判断奇数和偶数的方法
  6. 真格量化——商品期权基本策略
  7. 2017php行情,2017年蔬菜行情特点及未来蔬菜价格走势分析
  8. Oracle学习总结2-数据处理
  9. Visual C++ 时尚编程百例002(MFC窗口)
  10. [转载] 生成对角矩阵 numpy.diag
  11. 《Objective-C函数速查实例手册》——导读
  12. Java实现“斐波那契数列”的方法(循环,递归,优化递归)
  13. Java的keytool命令
  14. 【高项】第4章 项目整体管理与变更管理【知识点精华笔记】
  15. python中histogram_python – 了解Pillow中的histogram()
  16. 电脑安装win10系统
  17. 【NFC】手机手环模拟门禁卡
  18. 电商数仓:用户行为数据仓库(一)数据仓库建设和技术选型
  19. STM32硬件I2C的一点心得(AT24C32C和AT24C64C)
  20. JDBC学习笔记(1)---B站尚硅谷宋红康

热门文章

  1. Mac上一款最受欢迎的SSH客户端
  2. 小米无线AR眼镜探索版细节汇总
  3. 查看文章影响因子的插件_查询文献可实时显示影响因子与分区排名的2个强大浏览器插件...
  4. matlab绘制垂线(x轴或y轴)
  5. python,全自动获取图片,并下载你喜欢的美女图片
  6. 格密码LLL算法:如何解决最短向量SVP问题(2)
  7. 若语句char a = ‘\72‘; 则变量a包含几个字符?‘\72‘是否在ASCII值的范围之内?
  8. HTML页面格式化(CSS)
  9. Typhon之异常错误克星TLSException
  10. itan:ServletContext简介