树莓派——TSL2561获取光强数值(C语言)

树莓派——TSL2561获取光强数值(C语言)TSL2561 获取光强一 TSL2561 光强传感器二 使能内核 I2C 驱动模块一 TSL2561 光强传感器 TSL2561 是一款高速 低功耗 宽量程 可编程灵活配置的光强度数字转换芯片

大家好,欢迎来到IT知识分享网。

一、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], &reg_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

(0)
上一篇 2025-03-15 18:10
下一篇 2025-03-15 18:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信