树莓派——TSL2561获取光强数值(C语言)
Posted Shemesz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树莓派——TSL2561获取光强数值(C语言)相关的知识,希望对你有一定的参考价值。
一、TSL2561光强传感器
TSL2561是一款高速、低功耗、宽量程、可编程灵活配置的光强度数字转换芯片,特性如下。适合利用树莓派开发板或STM32型单片机来进行编程开发。了解详细芯片手册点击 : datasheet
◇ 可编程配置许可的光强度上下阈值,当实际光照度超过该阈值时给出中断信号;
◇ 数字输出符合标准的SMBus(TSL2560)和I2C(TSL2561)总线协议;
◇ 模拟增益和数字输出时间可编程控制;
◇ 1.25 mm×1.75 mm超小封装,在低功耗模式下,功耗仅为0.75 mW;
◇ 自动抑制50 Hz/60 Hz的光照波动。
- 引脚功能
引脚 | 功能 |
---|---|
VIN | 电源供电(3.3V) |
GND | 接地 |
SCL | I2C时钟线 |
SDA | I2C地址线 |
INT | 中断控制 |
-
内部结构和工作原理
TSL256x 是第二代周围环境光强度传感器,其内部结构如下图所示。*通道0和通道1是两个光敏二极管,其中通道0对可见光和红外线都敏感,而通道1仅对红外线敏感。积分式A/D转换器对流过光敏二极管的电流进行积分,并转换为数字量,在转换结来后将转换结果存入芯片内部通道0和通道1各自的寄存器中。当一个积分周期完成之后,积分式A/D转换器将自动开始下一个积分转换过程。微控制器和TSL2560可通过标准的SMBus(System Management Bus)V1.1或V2.0实现,TSL2561则可通过I2C总线协议访问。
-
硬件设计
TSL2561能够通过I2C总线访问,所以硬件接口电路很简单。假如所选用的微控制器带有I2C总线控制器,则将该总线的时钟线和数据线直接和TSL2561的I2C总线的SCL和SDA分别相连;假如微控制器内部没有上拉电阻,则还需要再用2个上拉电阻接到总线上。假如微控制器不带I2C总线控制器,则将TSL2561的I2C总线的SCL和SDA和普通I/O口连接即可:但编程时需要模拟I2C总线的时序来访问TSL2561,INT引脚接微控制器的外部中断。硬件连接如下图所示。
-
软件设计
微控制器能够通过I2C总线协议对TSL2561进行读写。写数据时,先发送器件地址,然后发送要写的数据。TSL2561的写操作过程如下:先发送一组器件地址;然后写命令码,命令码是指定接下来写寄存器的地址00h~0fh和写寄存器的方式,是以字节、字或块(几个字)为单位进行写操作的:最后发送要写的数据,根据前而命令码规定写寄存器的方式,能够连续发送要写的数据,内部写寄存器会自动加1。 -
TSL2561模块与树莓派的连接
直接按照以上引脚图去逐一连接芯片的四个管脚(除去INT)即可,其中电源引脚应接在3.3V引脚上
二、使能内核I2C驱动模块
TSL2561数据传输的原理遵循IIC(I2C)总线协议,仅依靠一条时钟线和一条数据总线即可完成光强数据的传输。有关I2C总线协议的详细内容见上篇博客:I2C总线
1. 配置内核启动后自动加载I2C驱动
pi@raspberrypi:~ $ sudo raspi-config
该配置会将/boot/config.txt 文件中的下面这个选项打开:
dtparam=i2c_arm=on
2.安装i2c的相关驱动
重启树莓派之后会发现系统启动之后会自动安装i2c的相关驱动
pi@raspberrypi:~ $ sudo reboot
pi@raspberrypi:~ $ sudo apt-get install i2c-tools
pi@raspberrypi:~ $ lsmod | grep i2c
查看设备地址命令:
pi@raspberrypi:~ $ sudo i2cdetect -y 1
具体如图
使用i2cdetect命令可以查看到SHT21温湿度传感器设备地址0x39。
三、TSL2561寄存器的访问
对TSL256x的控制是通过对其内部的16个寄存器的读写来实现的,其地址如下表所列。TSL2561启动、寄存器访问、数据的读取都是通过写命令控制字的方法来实现的,TSL2561的用户手册里面给出了对应寄存器的名称、用途和访问方法:
上面是TSL2561内部所有寄存器的类型以及对应的地址,而读取光强仅需利用其中的命令寄存器(command)、控制寄存器(control)和数据寄存器(Ch,Dh,Eh,Fh)。数据寄存器中的值经过位运算和加法运算之后,便可生成对应ADC通道(ADC channel)内的采样值,所以没必要单独介绍数据寄存器,直接套公式即可,即:
- Channel_0 = DATA0HIGH<<8 + DATA0LOW;
- Channel_1 = DATA1HIGH<<8 + DATA1LOW;
1.命令寄存器
- CMD设置为1才可以正常访问
- ADDRESS位有3位,对应着上一张图片里面数据寄存器的地址。
例如要访问数据寄存器Ch,就应该将命令寄存器设置为10001100B,即0x8c,当不需要访问数据寄存器时,ADDRESS直接写为0000B(0x0)即可。由此可见,命令寄存器在TSL2561内部的地址是0x80。
2.控制寄存器
- TSL2561的启动取决于控制寄存器中的POWER位,其他位是保留位,无需考虑操作,直接置0即可。
- POWER位置为11B,即0x03是启动
- POWER位置为00B,即0x00是关闭
3.光强度值的计算
写入控制寄存器控制字使得TSL2561成功启动,并且正常读取到四个数据寄存器中的值之后,就可以按照用户手册中的计算公式进行光强计算了:
TSL2561有两种封装类型,我使用的芯片属于图中所述的第二种,所以计算光强时就使用第二种封装类型里面的公式就行了,芯片的封装类型在购买来之前的包装袋上有说明。
四、C语言获取光强代码
程序代码主要基于用户空间使用i2c_dev来进行编程,详情了解点击 i2c_dev 博客
(1)代码模块
根据寄存器的功能不同,单独设计功能模块,便于函数在其他地方的调用,也使主函数更加简洁.
- 打开设备对应节点模块
- 启动/关停模块
- 读写模块
- 光强计算模块
- 主函数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <errno.h>
#include <time.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define TSL2561_I2C_ADDR 0x39 /* 设备地址为0x39 */
#define CONTROL_REG 0x80 /* 命令寄存器在TSL2561内部的地址是0x80 */
#define REG_COUNT 4 /* 寄存器数量 */
#define POWER_UP 0x03 /* 上电 */
#define POWER_DOWN 0x00 /* 断电 */
#define ON 1 /*用于启动或关闭*/
#define OFF 0
/*datasheet中4个数据寄存器的地址是依次递增,所以运用枚举即可*/
enum
{
/* Channel_0 = DATA0HIGH<<8 + DATA0LOW */
DATA0LOW = 0x8c,
DATA0HIGH,
/* Channel_1 = DATA1HIGH<<8 + DATA1LOW */
DATA1LOW,
DATA1HIGH,
};
int s_tsl_fd = -1;
static const int regs_addr[REG_COUNT]={DATA0LOW, DATA0HIGH, DATA1LOW, DATA1HIGH};
/*初始化模块:打开对应的设备节点*/
int tsl2561_init(void)
{
if( (s_tsl_fd = open("/dev/i2c-1", O_RDWR)) < 0 )
{
printf("open /dev/i2c-1 error!\\n");
return -1;
}
printf("open /dev/i2c-1 successfully! s_tsl_fd = %d\\n", s_tsl_fd);
return s_tsl_fd;
}
/*启动模块:上电/断电*/
int tsl2561_power(int cmd)
{
struct i2c_msg msg;
struct i2c_rdwr_ioctl_data data;
unsigned char buf[2];
/*设置 i2c_msg 的结构体*/
msg.addr = TSL2561_I2C_ADDR; /*从机地址*/
msg.flags = 0; /*读写标志*/
msg.len = 1; /*数据长度*/
msg.buf = buf; /*msg里的buf为指向buf[]的数据指针*/
/*设置 i2c_rdwr_ioctl_data 的结构体*/
data.msgs = &msg; /*msgs为指向 i2c_msgs 的指针*/
data.nmsgs = 1; /*消息个数*/
/*写入命令寄存器地址,开始i2c层通信*/
msg.buf[0] = CONTROL_REG;
if( ioctl(s_tsl_fd, I2C_RDWR, &data) < 0 )
{
printf("%s() ioctl failure: %s\\n", __func__, strerror(errno));
return -1;
}
/*通过ON/OFF进行上电/断电*/
if(cmd)
{
msg.buf[0] = POWER_UP;
}
else
{
msg.buf[0] = POWER_DOWN;
}
/*再次写入命令,进行通信*/
if( ioctl(s_tsl_fd, I2C_RDWR, &data) < 0 )
{
printf("%s() ioctl failure: %s\\n", __func__, strerror(errno));
return -1;
}
return 0;
}
/*读写模块:写入命令字,读取寄存器数据*/
int tsl2561_read_reg(unsigned char regaddr, unsigned char *regval)
{
struct i2c_msg msg;
struct i2c_rdwr_ioctl_data data;
unsigned char buf[2];
msg.addr= TSL2561_I2C_ADDR;
msg.flags=0;
msg.len= 1;
msg.buf= buf;
msg.buf[0] = regaddr;
data.nmsgs= 1;
data.msgs= &msg;
if( ioctl(s_tsl_fd, I2C_RDWR, &data) < 0 )
{
printf("%s() ioctl failure: %s\\n", __func__, strerror(errno));
return -1;
}
memset(buf, 0, sizeof(buf));
msg.addr= TSL2561_I2C_ADDR;
msg.flags=I2C_M_RD;
msg.len= 1;
msg.buf= buf;
data.nmsgs= 1;
data.msgs= &msg;
if( ioctl(s_tsl_fd, I2C_RDWR, &data) < 0 )
{
printf("%s() ioctl failure: %s\\n", __func__, strerror(errno));
return -1;
}
*regval = msg.buf[0];
return 0;
}
/*光强获取模块:启动tsl2561,读写数据寄存器,拿到值进行光强计算 */
float tsl2561_get_lux( float *lux)
{
int i;
unsigned char reg_data[REG_COUNT];
unsigned char buf;
int chn0_data = 0;
int chn1_data = 0;
float div = 0.0;
//float lux = 0.0;
tsl2561_power(ON);
sleep(1);
for(i=0; i<REG_COUNT; i++)
{
tsl2561_read_reg(regs_addr[i], ®_data[i]); /*
将定义的全局变量数组regs_addr[REG_COUNT]利用循环依次传入,拿到的数据依次填入定义的局部变量reg_data[REG_COUNT]*/
}
/*将拿到的数据套公式计算*/
chn0_data = reg_data[1]*256 + reg_data[0]; /* Channel0 = DATA0HIGH<<8 + DATA0LOW */
chn1_data = reg_data[3]*256 + reg_data[2]; /* channel1 = DATA1HIGH<<8 + DATA1LOW */
if( chn0_data<=0 || chn1_data<0 )
{
*lux = 0.0;
goto OUT;
}
div = (float)chn1_data / (float)chn0_data;
if( div>0 && div<=0.5 )
*lux = 0.304*chn0_data-0.062*chn0_data*pow(div,1.4);
else if( div>0.5 && div<=0.61 )
*lux = 0.0224*chn0_data-0.031*chn1_data;
else if( div>0.61 && div<=0.8 )
*lux = 0.0128*chn0_data-0.0153*chn1_data;
else if( div>0.8 && div<=1.3 )
*lux = 0.00146*chn0_data-0.00112*chn1_data;
else if( div>1.3 )
*lux = 0.0;
//printf("TSLl2561 get lux: [%.3f]\\n", *lux);
OUT:
tsl2561_power(OFF);
return 0;
}
int main(int argc, char **argv)
{
float lux = 0.0;
tsl2561_init();
tsl2561_get_lux(&lux);
printf("TSLl2561 get lux: [%.3f]\\n", lux);
return 0;
}
- 运行结果
(2)注意事项
- 编译加-lm
使用math.h中声明的库函数还有一点特殊之处,gcc命令行必须加-lm选项,因为数学函数位于libm.so库文件中(这些库文件通常位于/lib目录下),-lm选项告诉编译器,我们程序中用到的数学函数要到这个库文件里找。 - 打印文件描述符的值
调用open()打开文件而返回的文件描述符的值,打印观察是否为正常值,如果是012,那么系统则会报错不能使用ioctl(),因为因为0是标准输入,1是标准输出,2是标准出错,文件描述符的正确与否会导致相关API的调用失败。
以上是关于树莓派——TSL2561获取光强数值(C语言)的主要内容,如果未能解决你的问题,请参考以下文章