[PYTHON] Use "TSL2561 Illuminance Sensor Module Manufacturer Part Number: TSL2561" manufactured by Strawberry Linux on Raspberry pi 3 (trial and error)

This is a struggle record for using the "TSL2561 Illuminance Sensor Module (Manufacturer Part Number: TSL2561)" manufactured by Strawberry Linux on the Rasberry pi 3. There are some articles about sensors using the TSL2561 chip, but they didn't work or were python3. So I tried trial and error with python2.

By the way ... I'm new to python for half a month, new to electronic work, and have little knowledge ... I hope it will be helpful while keeping that in mind.

For the time being, the summary of the parts necessary for use is here.

Physical wiring

It is necessary to solder the pin header to put the delivered sensor on the breadboard. After that, wire while looking at the included paper. Raspi's GPIO pin configuration is not difficult, as it will come up as soon as you search.

Sensor 1pin (GND)-> Raspi 6th pin Sensor 2pin (SDA)-> Raspi pin 3 Sensor 3pin (SCL)-> Raspi 5th pin Sensor 4pin (VDD)-> Raspi pin 1

It is like this. TSL2561.png

Enable I2C on Raspberry pi

I'm not sure what I2C is. However, according to the wiki, it seems to be a standard for connecting various devices with a chain like scsi. Anyway, enable it for use with raspbian.

Open the OS config menu from the ssh console and set.

$ sudo raspi-config

Select the menu in the order of "9 Advenced Options"-> "A7 I2C". You will be asked "Would you like the ARM I2C interface to be enabled?", So select yes. You will be asked "Would you like the I2C kernel module to be loaded by default?", So select yes.

Then edit /boot/config.txt as follows:

$ sudo vi /boot/config.txt
...Added the following contents
dtparam=i2c_arm=on

In addition, edit / etc / modules as follows.

$ sudo vi /etc/modules
...Added the following contents
snd-bcm2835
i2c-dev

After completing the settings, restart Raspy. Make sure the kernel module is loaded with the lsmod command after a reboot.

$ lsmod
...
i2c_dev                 6709  0 
snd_bcm2835            21342  0 
...

Install tools

Install the commands to use I2C and the python library (maybe).

$ sudo apt-get install i2c-tools python-smbus

Address confirmation

When the wiring is completed and the tools are installed, check with the command whether the sensor module is recognized.

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

It seems to be recognized at address 0x39.

... so far, it's been fine.

Try it anyway

First of all, when I tried it with reference to this article, although the value came out, it did not seem to be linked to light and dark. http://qiita.com/masato/items/1dd5bed82b19477b45d8

So, when I investigated further, I arrived at this article, but it is python3. http://shimobayashi.hatenablog.com/entry/2015/07/27/001708

Hmmm, can't I access it with the default python2?

Access I2C from python

If you read the above article and the source of git, it seems that you should use the smbus library to access the I2C device from python. However, I'm not sure how to use it, so I'll try it by trial and error.

According to the attachment from Strawberry Linux

The sensor starts operation by writing 0x03 to the internal register 0xA0. When 2 bytes are read from the internal address 0xAC, this becomes the raw data of the brightness of the visible light sensor (16 bits, the lower byte comes first). When 2 bytes are read from the internal address 0xAE, this becomes the raw data of the infrared sensor (16 bits, the lower byte comes first).

There is. ... Hmm Nanno Kotcha.

Anyway, Raspi seems to recognize TSL2561 at 0x39, so I wondered if I should read or write 0xA0, 0xAC, 0xAE from there, but the problem is how to do it.

When I searched variously with smbus, I came across the following site. http://raspberrypi.stackexchange.com/questions/8469/meaning-of-cmd-param-in-write-i2c-block-data http://www.raspberry-projects.com/pi/programming-in-python/i2c-programming-in-python/using-the-i2c-interface-2

I'm not good at English, so I read diagonally around the sample source and try and error by intuition. As a result of various trials, it seems that you can read or write the specified internal address by doing the following.

bus		= smbus.SMBus(1)
bus.write_i2c_block_data(0x39, 0xA0, [0x03])
bus.read_i2c_block_data(0x39, 0xAC ,2)

The first smbus.SMBus (1) is creating an object to access the bus, maybe. '1' seems to be the same as the 1 specified at the end of "sudo i2cdetect -y 1", and it seems to specify the X of the "i2c-X" file under / dev /.

Use the bus.read_i2c_block_data method to read the data.

bus.read_i2c_block_data (I2C address, internal address, number of data bytes to read)

Use the write_i2c_block_data method to write data.

write_i2c_block_data (I2C address, internal address, data array to write)

With this, when I wrote the sample source, some values like that came out. When you hold your hand over it, the value changes in response. I feel like I can go!

#!/usr/bin/python -u
# -*- coding: utf-8 -*-
import smbus
import time
bus	= smbus.SMBus(1)
bus.write_i2c_block_data(0x39, 0x80, [0x03])

while True:
	data 	= self.bus.read_i2c_block_data(self.address, 0xAC ,2)
	raw 	= data[1] << 8 | data[0]
	print "Ambient light: " + str(raw)

	data 	= self.bus.read_i2c_block_data(self.address, 0xAE ,2)
	raw 	= data[1] << 8 | data[0]
	print "Infrared: " + str(raw)

	time.sleep(1.0)

Read the manufacturer's data sheet

Since it seems that raw data can be obtained, we will try to calculate the illuminance (Lux). Again, according to the attachment from Strawberry Linux,

Please refer to the manufacturer's data sheet for an algorithm that converts the raw data of visible light and infrared sensors into illuminance (lux).

There is. Where is the manufacturer! As a result of various googles while thinking, the following URL seems to be a data sheet with a TAOS chip.

https://cdn-shop.adafruit.com/datasheets/TSL2561.pdf

Cool! !! (Hematemesis) Is it an English document, fight, fight, scared heart ~ .... I manage to read diagonally while asking myself what I am doing to GW. Then, on page 23, there was "Calculating Lux", and I found the formula that I saw in the git source.

Try to classify 1

I see, I understand the flow, so let's classify it.

#!/usr/bin/python -u
# -*- coding: utf-8 -*-

import smbus
import time

#From "TSL2561 Illuminance Sensor Module" by Strawberry Linux
#Class to acquire data by I2C (1)
# https://strawberry-linux.com/catalog/items?code=12561
# 2016-05-03 Boyaki Machine
class SL_TSL2561:
	def __init__(self, address, channel):
		self.address	= address
		self.channel	= channel
		self.bus		= smbus.SMBus(self.channel)
		self.bus.write_i2c_block_data(self.address, 0x80, [0x03])
		time.sleep(0.5)

	def getVisibleLightRawData(self):
		data 	= self.bus.read_i2c_block_data(self.address, 0xAC ,2)
		raw 	= data[1] << 8 | data[0]	#16bit with lower byte first
		return raw

	def getInfraredRawData(self):
		data 	= self.bus.read_i2c_block_data(self.address, 0xAE ,2)
		raw 	= data[1] << 8 | data[0]	#16bit with lower byte first
		return raw

	def getLux(self):
		#Acquisition of raw sensor data
		VLRD = getVisibleLightRawData()
		IRRD = getInfraredRawData()

		#Don't divide by zero
		if (float(VLRD) == 0):
			ratio = 9999
		else:
			ratio = (IRRD / float(VLRD))

		#Lux calculation
		if ((ratio >= 0) & (ratio <= 0.52)):
			lux = (0.0315 * VLRD) - (0.0593 * VLRD * (ratio**1.4))
		elif (ratio <= 0.65):
			lux = (0.0229 * VLRD) - (0.0291 * IRRD)
		elif (ratio <= 0.80):
			lux = (0.0157 * VLRD) - (0.018 * IRRD)
		elif (ratio <= 1.3):
			lux = (0.00338 * VLRD) - (0.0026 * IRRD)
		elif (ratio > 1.3):
			lux = 0

		return lux 

if __name__ == "__main__":
	sensor 	= SL_TSL2561(0x39,1) 
	while True:
		print "Lux : " + str(sensor.getLux())
		time.sleep(1.0)

It's pretty simple, but reading the git source makes it more complicated and long. What's the difference?

Try trial and error

When reading the git source, Gain and Scale appear before the illuminance calculation. After investigating what the heck was, I found the description of "high gain (16 ×)" in the "Timing Register" on page 15 of the data sheet, and carefully reread this area.

Apparently, this sensor has a specification that expands the sensing range by combining "Gain" and "Integration Time". Does it mean that the sensitivity will be 16 times higher with Height Gain? Also, if the integration time is changed from the default 402ms to 101ms and 13.7ms, the integration time of the sensing value will be shortened, and the sensitivity will decrease (= the output value will decrease). Does it feel like the exposure time is getting shorter?

Based on this information, change Gain and Integration Time variously and see the output of raw data. First, it was found that when set to High Gain, a value of approximately 16 times was obtained. It was also found that when the Integration Time was set to 101ms and 13.7ms, the values of 101/402 and 13.7 / 402 were obtained.

For example, in Hyuga during May, in the default state (Gain is 1, Integration Time is 402ms), the output of visible light will be 65535 (maximum value of 16bit), and the correct value cannot be obtained. In such a case, it seems that the specification is to set the Integration Time short and obtain a valid value.

However, at the time of High Gain, if there was a certain amount of light (near the window during the day), there was a problem that the raw data of both visible light and infrared light was fixed at 5047, which could not be solved. It works fine in low light.

Anyway, it seems that you need to scale the raw data before doing the illuminance calculation because the output changes depending on the setting. What I didn't really understand here was which setting the calculation formula in the data sheet was based on. Looking at the git source, when it is Low Gain, the raw data is multiplied by 16. Write some sample code to get the actual measurement data and compare it with the following site.

http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12117474762

As a result, I concluded that High Gain was the base. I wonder if it is written somewhere in the data sheet. .. ..

Try to classify 2

Rebuild the class based on trial and error. We have prepared a method that allows you to change Gain and Integration Time.

#!/usr/bin/python -u
# -*- coding: utf-8 -*-

import smbus
import time

#From "TSL2561 Illuminance Sensor Module" by Strawberry Linux
#Class to get data in I2C
# https://strawberry-linux.com/catalog/items?code=12561
# 2016-05-03 Boyaki Machine
class SL_TSL2561:
	def __init__(self, address, channel):
		self.address	= address
		self.channel	= channel
		self.bus		= smbus.SMBus(self.channel)
		self.gain 		= 0x00			# 0x00=normal, 0x10=×16
		self.integrationTime 	= 0x02	# 0x02=402ms, 0x01=101ms, 0x00=13.7ms
		self.scale 		= 1.0

		#Initialization of sensor settings
		self.setLowGain()
		self.setIntegrationTime('default')

	def powerOn(self):
		self.bus.write_i2c_block_data(self.address, 0x80, [0x03])
		time.sleep(0.5)

	def powerOff(self):
		self.bus.write_i2c_block_data(self.address, 0x80, [0x00])

	#Set to High Gain(16 times more sensitive?)
	def setHighGain(self):
		#When set to High Gain, raw data may not be obtained properly.
		#Investigation of the cause required(5047 Fixed value)	
		self.gain 	= 0x10
		data 		= self.integrationTime | self.gain
		self.bus.write_i2c_block_data(self.address, 0x81, [data])
		self.calcScale()

	# Low Gain(default)Set to
	def setLowGain(self):
		self.gain 	= 0x00
		data 		= self.integrationTime | self.gain
		self.bus.write_i2c_block_data(self.address, 0x81, [data])
		self.calcScale()

	#Setting the time to integrate (time for one sensing?)
	# val = shor, middle, logn(default)
	def setIntegrationTime(self, val):
		if val=='short':
			self.integrationTime 	= 0x00	# 13.7ms scale=0.034
		elif val=='middle':
			self.integrationTime 	= 0x01 	# 101ms	 scale=0.252
		else:
			self.integrationTime 	= 0x02  # defaultVal 402ms  scale=1.0
		data = self.integrationTime | self.gain
		self.bus.write_i2c_block_data(self.address, 0x81, [data])
		self.calcScale()

	def getVisibleLightRawData(self):
		data 	= self.bus.read_i2c_block_data(self.address, 0xAC ,2)
		raw 	= data[1] << 8 | data[0]	#16bit with lower byte first
		return raw

	def getInfraredRawData(self):
		data 	= self.bus.read_i2c_block_data(self.address, 0xAE ,2)
		raw 	= data[1] << 8 | data[0]	#16bit with lower byte first
		return raw

	def getRawData(self):
		data 	= self.bus.read_i2c_block_data(self.address, 0xAC ,4)
		VL 		= data[1] << 8 | data[0]	#Visible light 16 bits, lower byte first
		IR 		= data[3] << 8 | data[2]	#Infrared 16bit, lower byte first
		return (VL,IR)

	def calcScale(self):
		_scale = 1.0
		#Scale by integrationTime
		if self.integrationTime == 0x01:	# middle
			_scale = _scale / 0.252
		elif self.integrationTime == 0x00:	# short
			_scale = _scale / 0.034
		
		#Scale by gain
		if self.gain == 0x00 :				# gain 1
			_scale = _scale * 16.0

		self.scale = _scale

	def getLux(self):
		#Acquisition of raw sensor data
		raw  = self.getRawData()

		#Implementation to output an error when 65535
		if raw[0] == 65535 or raw[1] == 65535:
			return "Range Over"

		#Scale raw data with sensor settings
		VLRD = raw[0] * self.scale
		IRRD = raw[1] * self.scale

		#Don't divide by zero
		if (float(VLRD) == 0):
			ratio = 9999
		else:
			ratio = (IRRD / float(VLRD))

		#Lux calculation
		if ((ratio >= 0) & (ratio <= 0.52)):
			lux = (0.0315 * VLRD) - (0.0593 * VLRD * (ratio**1.4))
		elif (ratio <= 0.65):
			lux = (0.0229 * VLRD) - (0.0291 * IRRD)
		elif (ratio <= 0.80):
			lux = (0.0157 * VLRD) - (0.018 * IRRD)
		elif (ratio <= 1.3):
			lux = (0.00338 * VLRD) - (0.0026 * IRRD)
		elif (ratio > 1.3):
			lux = 0

		return lux 


if __name__ == "__main__":
	sensor 	= SL_TSL2561(0x39,1) 
	sensor.powerOn()
	# sensor.setHighGain()
	sensor.setIntegrationTime('default')
	while True:
		print "Lux : " + str(sensor.getLux())
		time.sleep(1.0)

At the end

By using the above class, you can get such a value. However, the problem of High Gain has not been solved. I can't confirm if the implementation is correct in the first place ...

Looking at the sample source of the data sheet, when accessing the internal address (Is it a register?), It is 0xAX or 0x8X. Looking at the specifications of "Register Set", only 0h to Fh are written. If you guess from the source whether it is 0xA or 0x8, I think that you are specifying the type of the value to be acquired, but I do not know the truth. No matter which address you specify, the same value will be obtained, so I suspect that python is converting it to good. Please let me know if anyone knows.

If you check the raw data value as appropriate and switch the Gain or Integration Time settings when the limit value is approached, you can seamlessly measure a wide range.

If its helpful then im happy.

Recommended Posts

Use "TSL2561 Illuminance Sensor Module Manufacturer Part Number: TSL2561" manufactured by Strawberry Linux on Raspberry pi 3 (trial and error)
Use "TSL2561 Illuminance Sensor Module (Manufacturer Part Number: TSL2561)" manufactured by Strawberry Linux with Rasberry pi 3 (Summary)
Use "MPU-9250 9-axis sensor module (manufacturer part number: MPU-9250)" manufactured by Strawberry Linux with Rasberry pi 3.
A memo to simply use the illuminance sensor TSL2561 with Raspberry Pi 2
Use the Grove sensor on the Raspberry Pi
Use Grove-Temperature & Humidity Sensor (DHT11) on Raspberry Pi
Using the digital illuminance sensor TSL2561 with Raspberry Pi
Ubuntu 20.04 on raspberry pi 4 with OpenCV and use with python
Use NeoPixel on Raspberry Pi