一、前言
本文是系列文章的第3篇,介绍如何利用内核中现有的i2c bus驱动,在最少的代码量上,做到对slaver设备的控制。基于现有的i2c bus驱动,我们可直接在应用层编写slaver设备的控制代码!如此,大大降低了设备驱动的开发难度。当然,工资还是得照样领!
本文开发板基于Amlogic T972平台, Android 9.0
二、系列文章
第1篇 :不写一行代码(一):实现安卓基于GPIO的LED设备驱动
第2篇 :不写一行代码(二):实现安卓基于PWM的LED设备驱动
第3篇:不写一行代码(三):实现安卓基于i2c bus的Slaver设备驱动
三、准备工作
3.1 挑选I2C引脚
从芯片datasheet,结合开发板可用插座脚,如下所示,可挑选GPIOZ_1(SDA)、GPIOZ_2(SCL)作为实验对象
图一:Amlogic T972 Datasheet
图二:开发板J31引出的两排引脚
3.2 测试设备:QMI8658C
找了半天,没有更简单的i2c模块(例如基于I2C接口的热传感器,寄存器会很简单),只有一个六轴陀螺仪模块,所以就将就用吧!
- VCC 3.3V – 红线
- GND – 黑线
- SCL2 – 黄线,连接GPIOZ_2(排针J31的PIN9)
- SDA2 – 绿线 ,连接GPIOZ_1(排针J31的PIN8)
- 白色和蓝色未使用
四、编写设备树节点
4.1 查找MUX
默认情况下,GPIOZ_1和GPIOZ_2是普通的GPIO引脚,无法作为I2C引脚来使用,所以我们需要修改这两个引脚的MUX配置,这些配置都是原厂已经写好的,我们只需找出来,然后填入到i2c1的控制器配置中即可。
//文件:common\arch\arm\boot\dts\amlogic\mesontl1.dtsi
i2c1_z_pins:i2c1_z {
mux {
groups = "i2c1_sda_z",
"i2c1_sck_z";
function = "i2c1";
bias-pull-up;
drive-strength = <3>;
};
};
/*
* i2c sleep mode1: set the gpio the input
* in this scene, pull up power is off
*/
i2c1_z_pins_slp_input:i2c1_z_slp_input {
mux {
groups = "GPIOZ_1", "GPIOZ_2";
function = "gpio_periphs";
input-enable;
bias-disable;
};
};
4.2 修改i2c1引脚配置
4.2.1 修改前
&i2c1 {
status = "okay";
clock-frequency = <300000>;
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c1_h_pins>; //未修改前,i2c1模块MUX连接的PIN脚不是GPIOZ_1和GPIOZ_2
pinctrl-1 = <&i2c1_h_pins_slp_input>;//未修改前,i2c1模块MUX连接的PIN脚不是GPIOZ_1和GPIOZ_2
// …… 略 ……
//添加测试所用设备的DTS节点描述
i2c_tof_geo@6a {
compatible = "test_i2c, i2c";
reg = <0x6a>;
status = "okay";
};
};
4.2.2 修改后
&i2c1 {
status = "okay";
clock-frequency = <300000>;
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c1_z_pins>; //选用GPIOZ_1和GPIOZ_2的PIN脚配置,GPIOZ_1和GPIOZ_2将连通i2c1控制器模块
pinctrl-1 = <&i2c1_z_pins_slp_input>;
// …… 略 ……
//添加测试所用设备的DTS节点描述,测试设备的slaver address=0x6A
i2c_tof_geo@6a {
compatible = "test_i2c, i2c";
reg = <0x6a>;
status = "okay";
};
};
五、编译、烧录dt.img
参考系列文章第一篇(第四节):不写一行代码(一):实现安卓基于GPIO的LED设备驱动
5.1 烧录后效果
(1)烧录后,通过sys-fs系统,切换到如下目录,可以看到设备1-006a,也就是i2c-1的第1个设备,地址为0x6A
x301:/sys # cd bus/i2c/devices/i2c-1/
x301:/sys/bus/i2c/devices/i2c-1 # ls
1-006a delete_device device i2c-dev name new_device of_node power subsystem uevent
(2) 继续进入1-006a设备的目录下,查看其设备树节点名称,可以看到:i2c_tof_geo
x301:/sys/bus/i2c/devices/i2c-1 # cd 1-006a/
x301:/sys/bus/i2c/devices/i2c-1/1-006a # ls
modalias name of_node power subsystem uevent
x301:/sys/bus/i2c/devices/i2c-1/1-006a # cat of_node/name
i2c_tof_geo
x301:/sys/bus/i2c/devices/i2c-1/1-006a #
图示:
六、编写test程序
6.1 创建文件
如下在AOSP代码的development目录下:
(1)创建目录,姑且就叫 i2c-slaver
(2)创建 Android.mk 和 i2c-slaver.c 文件
szhou@bc04:~/T972/android_x301/source/t962x3-t972-android9.0/development$ tree i2c-slaver/
i2c-slaver/
├── Android.mk
└── i2c-slaver.c
0 directories, 2 files
szhou@bc04:~/T972/android_x301/source/t962x3-t972-android9.0/development$
6.2 源码:Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := i2c-slaver
LOCAL_SRC_FILES := i2c-slaver.c
include $(BUILD_EXECUTABLE)
6.3 源码:i2c-slaver.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/string.h>
static int i2c_trans(int fd, __u8 rw, __u8 cmd, __u32 size, union i2c_smbus_data *data)
{
struct i2c_smbus_ioctl_data args;
args.read_write = rw;//0:I2C_SMBUS_WRITE, 1:I2C_SMBUS_READ
args.command = cmd;
args.size = size;
args.data = data;
return ioctl(fd,I2C_SMBUS,&args);
}
static int i2c_write(int fd, __u8 cmd, __u8 value)
{
union i2c_smbus_data data;
data.byte = value;
return i2c_trans(fd,I2C_SMBUS_WRITE,cmd, I2C_SMBUS_BYTE_DATA, &data);
}
static int i2c_read(int fd, __u8 cmd, __u32 size, union i2c_smbus_data *data)
{
__u32 i=0;
if (i2c_trans(fd,I2C_SMBUS_READ,cmd, size, data))
{
printf("i2c_trans failed\n");
return -1;
}
else
{
printf("\n");
for(i=0; i<size; i++){
printf("data[%d]=0x%02X \t", i, data->block[i]);
}
printf("\n");
return 0;
}
}
int main()
{
int fd;
union i2c_smbus_data data;
//打开i2c-1总线控制器
fd = open("/dev/i2c-1", O_RDWR);
if (fd == -1)
{
printf("zs, open i2c failed. \n");
goto fail;
}
//访问Slaver地址为0x6A的设备
if (ioctl(fd, I2C_SLAVE, 0x6A) < 0)
{
printf("zs, open i2c I2C_SLAVE failed. \n");
goto fail;
}
//发送CMD初始化测试用的i2c-slaver设备,是一个陀螺仪,各位请换成自己的i2c设备
i2c_write(fd, 0x06, 0xB0);
usleep(300000);
i2c_write(fd, 0x02, 0x60);
i2c_write(fd, 0x03, 0x24);
i2c_write(fd, 0x08, 0x83);
//读取陀螺仪的X、Y、Z轴的原始数据,各2bytes,一共6bytes
while(i2c_read(fd, 0x35, 6, &data) != -1){
usleep(3*1000*1000); //每3秒读取一次
}
fail:
close(fd);
return 0;
}
6.4 编译test程序
- 注意:使用局部编译前,一般需要对整个源码做一次完整编译!
- 生成位置:Install: out/target/product/x301/system/bin/i2c-slaver
编译步骤:
(1) source build/envsetup.sh
(2) lunch your-board
(3) make i2c-slaver
编译打印
root@d185403d1e6f:/home/builder/android_x301/source/t962x3-t972-android9.0# make i2c-slaver
// …… 略 ……
[100% 6/6] Install: out/target/product/x301/system/bin/i2c-slaver
#### build completed successfully (01:48 (mm:ss)) ####
root@d185403d1e6f:/home/builder/android_x301/source/t962x3-t972-android9.0#
七、测试
7.1 部署 i2c-slaver
(1)ADB 推送i2c-slaver到板子上
Z:\T972\android_x301\source\t962x3-t972-android9.0\out\target\product\x301\system\bin>adb push i2c-slaver /data/
i2c-slaver: 1 file pushed, 0 skipped. 0.9 MB/s (16016 bytes in 0.016s)
(2)添加可执行权限
Z:\T972\android_x301\source\t962x3-t972-android9.0\out\target\product\x301\system\bin>adb shell chmod 777 /data/i2c-slaver
图示如下:
7.2 运行 i2c-slaver
因为打印默认是输出到串口的,所以运行部分,我们在串口上执行,当然,你也可以通过adb执行,之后dmesg查看打印
130|:/data # ./i2c-slaver
// …… 略 ……
data[0]=0x20 data[1]=0xD4 data[2]=0xF5 data[3]=0x4A data[4]=0xF4 data[5]=0xBF
[17541.246189@2]- [dhd-wlan0] wl_run_escan : LEGACY_SCAN sync ID: 176, bssidx: 0
data[0]=0x20 data[1]=0xCE data[2]=0xF5 data[3]=0x4D data[4]=0xF4 data[5]=0xB9
// …… 略 ……
data[0]=0x20 data[1]=0xF7 data[2]=0xF1 data[3]=0x99 data[4]=0xF9 data[5]=0x6E
^C
130|:/data #
运行效果的图示如下,通过晃动陀螺仪模块,可以看到它的值会大幅变动,如果静置的话,则值能基本保持不变或变化很小
图示如下: