树莓派——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接地
SCLI2C时钟线
SDAI2C地址线
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], &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的调用失败。

以上是关于树莓派——TSL2561获取光强数值(C语言)的主要内容,如果未能解决你的问题,请参考以下文章

树莓派可以用c语言吗

树莓派瞎玩~6~控制GPIO之C语言

树莓派C语言系列实验——实验一 树莓派4B系统安装及使用

树莓派如何查看即时网速返回具体数值

树莓派Pico C/C++语言开发

树莓派Pico C/C++语言开发