嵌入式Linux驱动开发(I2C专题)(四)

编写APP直接访问EEPROM

参考资料:

  • Linux驱动程序: drivers/i2c/i2c-dev.c
  • I2C-Tools-4.2: https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/
  • AT24cxx.pdf

1. 硬件连接

  • STM32MP157的I2C模块连接方法
    嵌入式Linux驱动开发(I2C专题)(四)_第1张图片
  • IMX6ULL的I2C模块连接方法
    嵌入式Linux驱动开发(I2C专题)(四)_第2张图片

2. AT24C02访问方法

2.1 设备地址

从芯片手册上可以知道,AT24C02的设备地址跟它的A2、A1、A0引脚有关:
嵌入式Linux驱动开发(I2C专题)(四)_第3张图片

打开I2C模块的原理图(这2个文件是一样的):

  • STM32MP157\开发板配套资料\原理图\04_Extend_modules(外设模块)\eeprom.zip\i2c_eeprom_module_v1.0.pdf
  • IMX6ULL\开发板配套资料\原理图\Extend_modules\eeprom.zip\i2c_eeprom_module_v1.0.pdf
  • 如下:
    嵌入式Linux驱动开发(I2C专题)(四)_第4张图片
    从原理图可知,A2A1A0都是0,所以AT24C02的设备地址是:0b1010000,即0x50。

2.2 写数据

嵌入式Linux驱动开发(I2C专题)(四)_第5张图片

2.3 读数据

可以读1个字节,也可以连续读出多个字节。
连续读多个字节时,芯片内部的地址会自动累加。
当地址到达存储空间最后一个地址时,会从0开始。
嵌入式Linux驱动开发(I2C专题)(四)_第6张图片

3. 使用I2C-Tools的函数编程

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "i2cbusses.h"
#include 

int main(int argc, char **argv)
{
	unsigned char dev_addr = 0x50;		//设备地址
	unsigned char mem_addr = 0;			//设备内部存储地址
	unsigned char buf[32];

	int file;
	char filename[20];
	unsigned char *str;

	int ret;

	struct timespec req;
	
	if (argc != 3 && argc != 4)
	{
		printf("Usage:\n");
		printf("write eeprom: %s  w string\n", argv[0]);
		printf("read  eeprom: %s  r\n", argv[0]);
		return -1;
	}

	file = open_i2c_dev(argv[1][0]-'0', filename, sizeof(filename), 0);
	if (file < 0)
	{
		printf("can't open %s\n", filename);
		return -1;
	}

	if (set_slave_addr(file, dev_addr, 1))		//分配设备地址
	{
		printf("can't set_slave_addr\n");
		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;
			}
			// wait tWR(10ms)
			nanosleep(&req, NULL);
			
			mem_addr++;
			str++;
		}
		ret = i2c_smbus_write_byte_data(file, mem_addr, 0); // string end char
		if (ret)
		{
			printf("i2c_smbus_write_byte_data err\n");
			return -1;
		}
	}
	else		//读操作
	{
		// read
		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;
	
}

4. 编译

4.1 在Ubuntu设置交叉编译工具链

  • STM32MP157

    export ARCH=arm
    export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
    export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
    
  • IMX6ULL

    export ARCH=arm
    export CROSS_COMPILE=arm-linux-gnueabihf-
    export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
    

4.2 使用I2C-Tools的源码

嵌入式Linux驱动开发(I2C专题)(四)_第7张图片

4.3 编译

为IMX6ULL编译时,有如下错误:
嵌入式Linux驱动开发(I2C专题)(四)_第8张图片
这是因为IMX6ULL的工具链自带的include目录中,没有smbus.h。

需要我们自己提供这个头文件,解决方法:

  • 提供头文件:
    嵌入式Linux驱动开发(I2C专题)(四)_第9张图片

  • 修改Makefile指定头文件目录

    all:
    	$(CROSS_COMPILE)gcc -I ./include -o at24c02_test at24c02_test.c i2cbusses.c smbus.c
    	
    

4.4 上机测试

以下命令在开发板中执行。

  • 挂载NFS

    • vmware使用NAT(假设windowsIP为192.168.1.100)

      [root@100ask:~]# mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 
      192.168.1.100:/home/book/nfs_rootfs /mnt
      
    • vmware使用桥接,或者不使用vmware而是直接使用服务器:假设Ubuntu IP为192.168.1.137

      [root@100ask:~]#  mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
      
  • 复制、执行程序

[root@100ask:~]# cp /mnt/at24c02_test   /bin
[root@100ask:~]# at24c02_test 0 w www.100ask.net
[root@100ask:~]# at24c02_test 0 r
get data: www.100ask.net

五、通用驱动i2c-dev分析

参考资料:

  • Linux驱动程序: drivers/i2c/i2c-dev.c
  • I2C-Tools-4.2: https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/
  • AT24cxx.pdf

1. 回顾字符设备驱动程序

嵌入式Linux驱动开发(I2C专题)(四)_第10张图片
怎么编写字符设备驱动程序?

  • 确定主设备号
  • 创建file_operations结构体
    • 在里面填充drv_open/drv_read/drv_ioctl等函数
  • 注册file_operations结构体
    • register_chrdev(major, &fops, name)
  • 谁调用register_chrdev?在入口函数调用
  • 有入口自然就有出口
    • 在出口函数unregister_chrdev
  • 辅助函数(帮助系统自动创建设备节点)
    • class_create
    • device_create

2. i2c-dev.c注册过程分析

2.1 register_chrdev的内部实现

嵌入式Linux驱动开发(I2C专题)(四)_第11张图片

2.2 i2c-dev驱动的注册过程

嵌入式Linux驱动开发(I2C专题)(四)_第12张图片

3. file_operations函数分析

i2c-dev.c的核心:

static const struct file_operations i2cdev_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= i2cdev_read,
	.write		= i2cdev_write,
	.unlocked_ioctl	= i2cdev_ioctl,
	.compat_ioctl	= compat_i2cdev_ioctl,
	.open		= i2cdev_open,
	.release	= i2cdev_release,
};

主要的系统调用:open, ioctl:
嵌入式Linux驱动开发(I2C专题)(四)_第13张图片
要理解这些接口,记住一句话:APP通过I2C Controller与I2C Device传输数据。

3.1 i2cdev_open

嵌入式Linux驱动开发(I2C专题)(四)_第14张图片

3.2 i2cdev_ioctl: I2C_SLAVE/I2C_SLAVE_FORCE

嵌入式Linux驱动开发(I2C专题)(四)_第15张图片

3.3 i2cdev_ioctl: I2C_RDWR

嵌入式Linux驱动开发(I2C专题)(四)_第16张图片

3.4 i2cdev_ioctl: I2C_SMBUS

嵌入式Linux驱动开发(I2C专题)(四)_第17张图片

3.5 总结

嵌入式Linux驱动开发(I2C专题)(四)_第18张图片

你可能感兴趣的:(Linux,驱动以及裸机,linux,驱动开发,运维)