一、模块简介
大气压力传感器使用I2C接口进行通讯,模块J1接口连接到IMX6魔法师Cortex-A系列底板的P1接口。
图1.1 大气压力传感器
大气压力传感器是可以检测温度和大气压力,采用压阻效应技术。压阻效应是指当半导体受到应力作用时,由于应力引起能带的变化,能谷的能量移动,使其电阻率发生变化的现象。
大气压力传感器具有稳定的电磁兼容性、高精度、线性性以及稳定性特点,可以应用在导航系统、航海系统、天气检测等方面。
二、操作步骤
1、线路连接
首先将大气压力传感器的J1接口通过连接线连接到魔法师Cortex-A系列底板的P1接口;然后使用串口线将魔法师Cortex-A系列底板的串口COM1与电脑连接,如果电脑没有串口,请使用USB转串口模块连接电脑的USB接口;最后连接魔法师Cortex-A系列底板的电源线。
2、测试模块
打开超级终端或者其他串口工具,设置波特率为115200,数据位为8,奇偶校验为无,停止位为1,数据流控制为无,设置完成后点击确定连接串口。打开平台的电源开关,待平台启动后,在串口终端中输入命令,进入模块目录:
root@imx6dlsabresd:~# cd vendor/test/module/BMP180/
执行测试程序:
root@imx6dlsabresd:/vendor/test/module/BMP180 # ./install.sh
3、运行结果
运行测试程序后,会在终端上打印出温度(temperature)和压力(BMP180)值。
终端运行结果: temp=26.4℃ press = 101.63Kpa
用手指触摸模块芯片,可观察到温度变化。
三、硬件原理
1、模块接口
大气压力传感器接口为一个5V电源引脚,一个SCL引脚,一个SDA引脚。该模块接口对应魔法师Cortex-A系列底板的P1接口。
图1.3 大气压力传感器接口原理图
2、大气压力传感器原理
本模块上通过BMP芯片读取温度和压力值。SCL引脚是I2C时钟引脚,SDA引脚是I2C数据引脚,XCLR是重置引脚(未使用),EOC是转换完毕状态引脚(未使用)。本模块操作简单,使用时直接通过I2C接口传输指令即可。具体操作指令请查看程序源码或芯片手册。
图1.4 大气压力传感器原理图
四、Linux下I2C驱动框架
I2C驱动代码在内核中分布如下图所示:
图4.1 I2C驱动代码目录
i2c-core.c实现了i2c核心功能,i2c-dev.c实现了与应用的交互接口。至于algos里面主要实现的是总线适配,busses主要就是一些控制器相关的和gpio模拟的i2c代码。
Linux的I2C体系结构符合Linux总线-设备-驱动开发模型,总体分为3个组成部分:
I2C核心:将与具体适配器无关的代码以及探测设备,检测设备地址的上层代码提取到一块,为I2C总线驱动和设备驱动提供注册、注销以及通信方法,负责I2C驱动与I2C设备的匹配工作。
I2C总线驱动:I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。
I2C设备驱动:I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。Linux I2C驱动架构图如下图所示:
图4.2 Linux i2c驱动架构图
其中adaptor是i2c的总线驱动,驱动CPU内部的i2c适配器的工作,在内核引入设备树之后,这部分的工作变为修改设备树了,设备树内容下个章节会讲到。dev和driver,client是i2c的设备驱动,提供用户层统一的API,这部分的代码是通用的。
五、IMX6 I2C设备树分析
分析imx6设备树文件imx6dl-sabresd.dts,包括它包含的.dtsi文件"imx6qdl.dtsi"、"imx6dl.dtsi"和"imx6qdl-sabresd.dtsi"。提取i2c相关信息,可以发现imx6dl处理器上有三条i2c总线(在"imx6qdl.dtsi"文件中),i2c0引用的是i2c1这个属性内容,“&”作用相当于C语言里面的指针。我们就以i2c0为例,带领读者分析一下imx6dl 的i2c设备树,最终找到i2c0所使用gpio引脚。
i2c0 = &i2c1;
i2c1 = &i2c2;
i2c2 = &i2c3;
继续查找i2c1,在"imx6qdl.dtsi"文件中有如下内容:
i2c1: i2c@021a0000 {
#address-cells = ;
#size-cells = ;
compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
reg = ;
interrupts = ;
clocks = ;
status = "disabled";
};
它定义了&i2c1节点(也就是i2c0)的各个属性及内容,具体属性含义可参考内核文档手册Documentation/devicetree/bindings/i2c/下各个txt文档。
继续搜索&i2c1节点,看在别的地方还有没有补充说明它的,发现在“imx6qdl-sabresd.dtsi”中还有&i2c1属性的一些内容,
&i2c1 {
clock-frequency = ;
pinctrl-names = "default";
pinctrl-0 = ;
status = "okay";
};
&i2c1里面的子节点都是挂载在它上面的设备,pinctrl-0 = ;表明pinctrl-0属性的内容引用的是pinctrl_i2c1。搜索pinctrl_i2c1,得到内容如下:
pinctrl_i2c1: i2c1grp {
fsl,pins = ;
};
MX6QDL_PAD_CSI0_DAT8__I2C1_SDA和MX6QDL_PAD_CSI0_DAT9__I2C1_SCL都是定义在“imx6dl-pinfunc.h”中的宏定义,它们为五组数据,加上后面的0x4001b8b1,组成六组数据供设备树解析代码处理,具体的解析过程需要详细阅读内核源代码。我们也可以通过名字看到,该宏定义的作用就是将CSI0_DAT8配置为I2C1的SDA,将CSI0_DAT9配置为I2C1的SCL。
我们打开imx6dl处理器的数据手册IMX6SDLRM.pdf,搜索CSI0_DAT8和CSI0_DAT9可以看到:
CSI0_DAT8和CSI0_DAT9引脚是可以配置成I2C1的SDA和SCL的。
六、源码分析
bmp085_test.c文件是大气压力传感器的数据采集代码,该代码使用Linux系统调用函数open、ioctl、read和write操作i2c设备节点/dev/i2c-0,实现对传感器模块数据的读写。
源代码里面封装了两个函数read_BMP085和write_BMP085具体负责读写操作。
/**********************************************
*函数名 :read_BMP085
*描述 :读取传感器数据
*输入参数 :
* @int fd 文件描述符
* @void *buff 缓存地址
* @unsigned char addr 数据地址
* @size_t count 读取子节数
*输出参数 :*buff
*返回值 :出错:-1; 成功:返回读取的字节数
***********************************************/
static int read_BMP085(int fd, void *buff, unsigned char addr, size_t count)
{
int res;
if(write(fd,&addr,1)!=1){ //发送读取地址
printf("write_BMP085 err on line %d\n",__LINE__);
return -1;
}
res=read(fd,buff,count); //读取count个字节数据数据到buff缓存区
return res;
}
/**********************************************
*函数名 :write_BMP085
*描述 :向模块写入数据
*输入参数 :
* @int fd 文件描述符
* @unsigned char data 写入数据
* @unsigned char addr 写入地址
* @size_t count 写入子节数
*返回值 :出错:-1; 成功:写入的字节数
***********************************************/
static int write_BMP085(int fd, unsigned char data, unsigned char addr, size_t count)
{
int res;
char sendbuffer[count+1];
sendbuffer[0] = addr;
sendbuffer[1] = data;
res=write(fd,sendbuffer,count + 1);
return res;
}
在main()函数中,首先使用open()函数打开I2C_DEV
/*设备节点名字*/
#define I2C_DEV "/dev/i2c-0"
fd = open(I2C_DEV, O_RDWR); //打开i2c设备节点
if(fd < 0){
printf("####i2c test device open failed####\n");
return (-1);
}
然后使用ioctl进行操作配置,传感器模块设备地址位为7bit,先使用I2C_TENBIT命令设置i2c设备地址位长度为7bit(即参数为0,参数为1则设置为10bit),然后使用I2C_SLAVE命令设置从设备地址(这里定义宏CHIP_ADDR为从设备地址)
if(-1 == ioctl(fd,I2C_TENBIT,0)){ //设置地址位长度为 7bit
printf("ioctl error on line %d\n",__LINE__);
return (-1);
}
if(-1 == ioctl(fd,I2C_SLAVE,CHIP_ADDR)){ //设置从设备地址
printf("ioctl error on line %d\n",__LINE__);
return (-1);
}
然后使用bmp085Init()函数获取校正系数的值。
/* 获取校正系数 */
bmp085Init();
/**********************************************
*函数名 :bmp085Init
*描述 :获取校正系数
*输入参数 :无
*返回值 :无
***********************************************/
void bmp085Init(void)
{
unsigned char buf[PAGE_SIZE];
read_BMP085(fd,buf,0xAA,2);
ac1 = buf[0] 13;
x2 = (b1 * (b6 * b6 >> 12)) >> 16;
x3 = ((x1 + x2) + 2) >> 2;
b4 = (ac4 * (unsigned long) (x3 + 32768)) >> 15;
b7 = ((unsigned long) up - b3) * (50000 >> OSS);
if( b7 < 0x80000000)
p = (b7 * 2) / b4 ;
else
p = (b7 / b4) * 2;
x1 = (p >> 8) * (p >> 8);
x1 = (x1 * 3038) >> 16;
x2 = (-7357 * p) >> 16;
press = p + ((x1 + x2 + 3791) >> 4);
printf("temp = %5.1f ℃\t press = %5.2f Kpa\r",(float)temp/10,(float)press/1000);
fflush(stdout);
usleep(500*1000);
}
这里使用while(1)加usleep()方法,不停的读取数据,想要终止程序的话可以使用CTRL+C组合键强制退出该程序。
七、源码编译
先进入到测试程序目录中,再执行make命令。
root@uptech:~# cd /home/magic/module/BMP180/test
root@uptech:~# make clean
root@uptech:~# make
编译成功后会在当前目录下生成BMP180_test文件。