大家好,欢迎来到IT知识分享网。
TSL2561获取光强
一、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,就应该将命令寄存器设置为B,即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的调用失败。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/151071.html