432 lines
14 KiB
Python
432 lines
14 KiB
Python
# The MIT License (MIT)
|
|
#
|
|
# Copyright (c) 2017 Dean Miller for Adafruit Industries
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
# THE SOFTWARE.
|
|
"""
|
|
`adafruit_as726x`
|
|
====================================================
|
|
|
|
Driver for the AS726x spectral sensors
|
|
|
|
* Author(s): Dean Miller
|
|
"""
|
|
|
|
import time
|
|
from adafruit_bus_device.i2c_device import I2CDevice
|
|
from micropython import const
|
|
|
|
try:
|
|
import struct
|
|
except ImportError:
|
|
import ustruct as struct
|
|
|
|
__version__ = "0.0.0-auto.0"
|
|
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_AS726x.git"
|
|
|
|
_AS726X_ADDRESS = const(0x49)
|
|
|
|
_AS726X_HW_VERSION = const(0x00)
|
|
_AS726X_FW_VERSION = const(0x02)
|
|
_AS726X_CONTROL_SETUP = const(0x04)
|
|
_AS726X_INT_T = const(0x05)
|
|
_AS726X_DEVICE_TEMP = const(0x06)
|
|
_AS726X_LED_CONTROL = const(0x07)
|
|
|
|
# for reading sensor data
|
|
_AS7262_V_HIGH = const(0x08)
|
|
_AS7262_V_LOW = const(0x09)
|
|
_AS7262_B_HIGH = const(0x0A)
|
|
_AS7262_B_LOW = const(0x0B)
|
|
_AS7262_G_HIGH = const(0x0C)
|
|
_AS7262_G_LOW = const(0x0D)
|
|
_AS7262_Y_HIGH = const(0x0E)
|
|
_AS7262_Y_LOW = const(0x0F)
|
|
_AS7262_O_HIGH = const(0x10)
|
|
_AS7262_O_LOW = const(0x11)
|
|
_AS7262_R_HIGH = const(0x12)
|
|
_AS7262_R_LOW = const(0x13)
|
|
|
|
_AS7262_V_CAL = const(0x14)
|
|
_AS7262_B_CAL = const(0x18)
|
|
_AS7262_G_CAL = const(0x1C)
|
|
_AS7262_Y_CAL = const(0x20)
|
|
_AS7262_O_CAL = const(0x24)
|
|
_AS7262_R_CAL = const(0x28)
|
|
|
|
# hardware registers
|
|
_AS726X_SLAVE_STATUS_REG = const(0x00)
|
|
_AS726X_SLAVE_WRITE_REG = const(0x01)
|
|
_AS726X_SLAVE_READ_REG = const(0x02)
|
|
_AS726X_SLAVE_TX_VALID = const(0x02)
|
|
_AS726X_SLAVE_RX_VALID = const(0x01)
|
|
|
|
_AS7262_VIOLET = const(0x08)
|
|
_AS7262_BLUE = const(0x0A)
|
|
_AS7262_GREEN = const(0x0C)
|
|
_AS7262_YELLOW = const(0x0E)
|
|
_AS7262_ORANGE = const(0x10)
|
|
_AS7262_RED = const(0x12)
|
|
_AS7262_VIOLET_CALIBRATED = const(0x14)
|
|
_AS7262_BLUE_CALIBRATED = const(0x18)
|
|
_AS7262_GREEN_CALIBRATED = const(0x1C)
|
|
_AS7262_YELLOW_CALIBRATED = const(0x20)
|
|
_AS7262_ORANGE_CALIBRATED = const(0x24)
|
|
_AS7262_RED_CALIBRATED = const(0x28)
|
|
|
|
_AS726X_NUM_CHANNELS = const(6)
|
|
|
|
# pylint: disable=too-many-instance-attributes
|
|
# pylint: disable=too-many-public-methods
|
|
class Adafruit_AS726x:
|
|
"""AS726x spectral sensor.
|
|
|
|
:param ~busio.I2C i2c_bus: The I2C bus connected to the sensor
|
|
"""
|
|
|
|
MODE_0 = 0b00
|
|
"""Continuously gather samples of violet, blue, green and yellow. Orange and red are skipped
|
|
and read zero."""
|
|
|
|
MODE_1 = 0b01
|
|
"""Continuously gather samples of green, yellow, orange and red. Violet and blue are skipped
|
|
and read zero."""
|
|
|
|
MODE_2 = 0b10 # default
|
|
"""Continuously gather samples of all colors"""
|
|
|
|
ONE_SHOT = 0b11
|
|
"""Gather a single sample of all colors and then stop"""
|
|
|
|
GAIN = (1, 3.7, 16, 64)
|
|
|
|
INDICATOR_CURRENT_LIMITS = (1, 2, 4, 8)
|
|
|
|
DRIVER_CURRENT_LIMITS = (12.5, 25, 50, 100)
|
|
|
|
def __init__(self, i2c_bus):
|
|
self._driver_led = False
|
|
self._indicator_led = False
|
|
self._driver_led_current = Adafruit_AS726x.DRIVER_CURRENT_LIMITS.index(12.5)
|
|
self._indicator_led_current = Adafruit_AS726x.INDICATOR_CURRENT_LIMITS.index(1)
|
|
self._conversion_mode = Adafruit_AS726x.MODE_2
|
|
self._integration_time = 0
|
|
self._gain = Adafruit_AS726x.GAIN.index(1)
|
|
self.buf2 = bytearray(2)
|
|
|
|
self.i2c_device = I2CDevice(i2c_bus, _AS726X_ADDRESS)
|
|
|
|
# reset device
|
|
self._virtual_write(_AS726X_CONTROL_SETUP, 0x80)
|
|
|
|
# wait for it to boot up
|
|
time.sleep(1)
|
|
|
|
# try to read the version reg to make sure we can connect
|
|
version = self._virtual_read(_AS726X_HW_VERSION)
|
|
|
|
# TODO: add support for other devices
|
|
if version != 0x40:
|
|
raise ValueError(
|
|
"device could not be reached or this device is not supported!"
|
|
)
|
|
|
|
self.integration_time = 140
|
|
self.conversion_mode = Adafruit_AS726x.MODE_2
|
|
self.gain = 64
|
|
|
|
@property
|
|
def driver_led(self):
|
|
"""True when the driver LED is on. False otherwise."""
|
|
return self._driver_led
|
|
|
|
@driver_led.setter
|
|
def driver_led(self, val):
|
|
val = bool(val)
|
|
if self._driver_led == val:
|
|
return
|
|
self._driver_led = val
|
|
enable = self._virtual_read(_AS726X_LED_CONTROL)
|
|
enable &= ~(0x1 << 3)
|
|
self._virtual_write(_AS726X_LED_CONTROL, enable | (val << 3))
|
|
|
|
@property
|
|
def indicator_led(self):
|
|
"""True when the indicator LED is on. False otherwise."""
|
|
return self._indicator_led
|
|
|
|
@indicator_led.setter
|
|
def indicator_led(self, val):
|
|
val = bool(val)
|
|
if self._indicator_led == val:
|
|
return
|
|
self._indicator_led = val
|
|
enable = self._virtual_read(_AS726X_LED_CONTROL)
|
|
enable &= ~(0x1)
|
|
self._virtual_write(_AS726X_LED_CONTROL, enable | val)
|
|
|
|
@property
|
|
def driver_led_current(self):
|
|
"""The current limit for the driver LED in milliamps. One of:
|
|
|
|
- 12.5 mA
|
|
- 25 mA
|
|
- 50 mA
|
|
- 100 mA"""
|
|
return self._driver_led_current
|
|
|
|
@driver_led_current.setter
|
|
def driver_led_current(self, val):
|
|
if val not in Adafruit_AS726x.DRIVER_CURRENT_LIMITS:
|
|
raise ValueError("Must be 12.5, 25, 50 or 100")
|
|
if self._driver_led_current == val:
|
|
return
|
|
self._driver_led_current = val
|
|
state = self._virtual_read(_AS726X_LED_CONTROL)
|
|
state &= ~(0x3 << 4)
|
|
state = state | (Adafruit_AS726x.DRIVER_CURRENT_LIMITS.index(val) << 4)
|
|
self._virtual_write(_AS726X_LED_CONTROL, state)
|
|
|
|
@property
|
|
def indicator_led_current(self):
|
|
"""The current limit for the indicator LED in milliamps. One of:
|
|
|
|
- 1 mA
|
|
- 2 mA
|
|
- 4 mA
|
|
- 8 mA"""
|
|
return self._indicator_led_current
|
|
|
|
@indicator_led_current.setter
|
|
def indicator_led_current(self, val):
|
|
if val not in Adafruit_AS726x.INDICATOR_CURRENT_LIMITS:
|
|
raise ValueError("Must be 1, 2, 4 or 8")
|
|
if self._indicator_led_current == val:
|
|
return
|
|
self._indicator_led_current = val
|
|
state = self._virtual_read(_AS726X_LED_CONTROL)
|
|
state &= ~(0x3 << 1)
|
|
state = state | (Adafruit_AS726x.INDICATOR_CURRENT_LIMITS.index(val) << 4)
|
|
self._virtual_write(_AS726X_LED_CONTROL, state)
|
|
|
|
@property
|
|
def conversion_mode(self):
|
|
"""The conversion mode. One of:
|
|
|
|
- `MODE_0`
|
|
- `MODE_1`
|
|
- `MODE_2`
|
|
- `ONE_SHOT`"""
|
|
return self._conversion_mode
|
|
|
|
@conversion_mode.setter
|
|
def conversion_mode(self, val):
|
|
val = int(val)
|
|
assert self.MODE_0 <= val <= self.ONE_SHOT
|
|
if self._conversion_mode == val:
|
|
return
|
|
self._conversion_mode = val
|
|
state = self._virtual_read(_AS726X_CONTROL_SETUP)
|
|
state &= ~(val << 2)
|
|
self._virtual_write(_AS726X_CONTROL_SETUP, state | (val << 2))
|
|
|
|
@property
|
|
def gain(self):
|
|
"""The gain for the sensor"""
|
|
return self._gain
|
|
|
|
@gain.setter
|
|
def gain(self, val):
|
|
if val not in Adafruit_AS726x.GAIN:
|
|
raise ValueError("Must be 1, 3.7, 16 or 64")
|
|
if self._gain == val:
|
|
return
|
|
self._gain = val
|
|
state = self._virtual_read(_AS726X_CONTROL_SETUP)
|
|
state &= ~(0x3 << 4)
|
|
state |= Adafruit_AS726x.GAIN.index(val) << 4
|
|
self._virtual_write(_AS726X_CONTROL_SETUP, state)
|
|
|
|
@property
|
|
def integration_time(self):
|
|
"""The integration time in milliseconds between 2.8 and 714 ms"""
|
|
return self._integration_time
|
|
|
|
@integration_time.setter
|
|
def integration_time(self, val):
|
|
val = int(val)
|
|
if not 2.8 <= val <= 714:
|
|
raise ValueError("Out of supported range 2.8 - 714 ms")
|
|
if self._integration_time == val:
|
|
return
|
|
self._integration_time = val
|
|
self._virtual_write(_AS726X_INT_T, int(val / 2.8))
|
|
|
|
def start_measurement(self):
|
|
"""Begin a measurement.
|
|
|
|
This will set the device to One Shot mode and values will not change after `data_ready`
|
|
until `start_measurement` is called again or the `conversion_mode` is changed."""
|
|
state = self._virtual_read(_AS726X_CONTROL_SETUP)
|
|
state &= ~(0x02)
|
|
self._virtual_write(_AS726X_CONTROL_SETUP, state)
|
|
|
|
self.conversion_mode = self.ONE_SHOT
|
|
|
|
def read_channel(self, channel):
|
|
"""Read an individual sensor channel"""
|
|
return (self._virtual_read(channel) << 8) | self._virtual_read(channel + 1)
|
|
|
|
def read_calibrated_value(self, channel):
|
|
"""Read a calibrated sensor channel"""
|
|
val = bytearray(4)
|
|
val[0] = self._virtual_read(channel)
|
|
val[1] = self._virtual_read(channel + 1)
|
|
val[2] = self._virtual_read(channel + 2)
|
|
val[3] = self._virtual_read(channel + 3)
|
|
return struct.unpack("!f", val)[0]
|
|
|
|
@property
|
|
def data_ready(self):
|
|
"""True if the sensor has data ready to be read, False otherwise"""
|
|
return (self._virtual_read(_AS726X_CONTROL_SETUP) >> 1) & 0x01
|
|
|
|
@property
|
|
def temperature(self):
|
|
"""The temperature of the device in Celsius"""
|
|
return self._virtual_read(_AS726X_DEVICE_TEMP)
|
|
|
|
@property
|
|
def violet(self):
|
|
"""Calibrated violet (450nm) value"""
|
|
return self.read_calibrated_value(_AS7262_VIOLET_CALIBRATED)
|
|
|
|
@property
|
|
def blue(self):
|
|
"""Calibrated blue (500nm) value"""
|
|
return self.read_calibrated_value(_AS7262_BLUE_CALIBRATED)
|
|
|
|
@property
|
|
def green(self):
|
|
"""Calibrated green (550nm) value"""
|
|
return self.read_calibrated_value(_AS7262_GREEN_CALIBRATED)
|
|
|
|
@property
|
|
def yellow(self):
|
|
"""Calibrated yellow (570nm) value"""
|
|
return self.read_calibrated_value(_AS7262_YELLOW_CALIBRATED)
|
|
|
|
@property
|
|
def orange(self):
|
|
"""Calibrated orange (600nm) value"""
|
|
return self.read_calibrated_value(_AS7262_ORANGE_CALIBRATED)
|
|
|
|
@property
|
|
def red(self):
|
|
"""Calibrated red (650nm) value"""
|
|
return self.read_calibrated_value(_AS7262_RED_CALIBRATED)
|
|
|
|
@property
|
|
def raw_violet(self):
|
|
"""Raw violet (450nm) 16-bit value"""
|
|
return self.read_channel(_AS7262_VIOLET)
|
|
|
|
@property
|
|
def raw_blue(self):
|
|
"""Raw blue (500nm) 16-bit value"""
|
|
return self.read_channel(_AS7262_BLUE)
|
|
|
|
@property
|
|
def raw_green(self):
|
|
"""Raw green (550nm) 16-bit value"""
|
|
return self.read_channel(_AS7262_GREEN)
|
|
|
|
@property
|
|
def raw_yellow(self):
|
|
"""Raw yellow (570nm) 16-bit value"""
|
|
return self.read_channel(_AS7262_YELLOW)
|
|
|
|
@property
|
|
def raw_orange(self):
|
|
"""Raw orange (600nm) 16-bit value"""
|
|
return self.read_channel(_AS7262_ORANGE)
|
|
|
|
@property
|
|
def raw_red(self):
|
|
"""Raw red (650nm) 16-bit value"""
|
|
return self.read_channel(_AS7262_RED)
|
|
|
|
def _read_u8(self, command):
|
|
"""read a single byte from a specified register"""
|
|
buf = self.buf2
|
|
buf[0] = command
|
|
with self.i2c_device as i2c:
|
|
i2c.write(buf, end=1)
|
|
i2c.readinto(buf, end=1)
|
|
return buf[0]
|
|
|
|
def __write_u8(self, command, abyte):
|
|
"""Write a command and 1 byte of data to the I2C device"""
|
|
buf = self.buf2
|
|
buf[0] = command
|
|
buf[1] = abyte
|
|
with self.i2c_device as i2c:
|
|
i2c.write(buf)
|
|
|
|
def _virtual_read(self, addr):
|
|
"""read a virtual register"""
|
|
while True:
|
|
# Read slave I2C status to see if the read buffer is ready.
|
|
status = self._read_u8(_AS726X_SLAVE_STATUS_REG)
|
|
if (status & _AS726X_SLAVE_TX_VALID) == 0:
|
|
# No inbound TX pending at slave. Okay to write now.
|
|
break
|
|
# Send the virtual register address (setting bit 7 to indicate a pending write).
|
|
self.__write_u8(_AS726X_SLAVE_WRITE_REG, addr)
|
|
while True:
|
|
# Read the slave I2C status to see if our read data is available.
|
|
status = self._read_u8(_AS726X_SLAVE_STATUS_REG)
|
|
if (status & _AS726X_SLAVE_RX_VALID) != 0:
|
|
# Read data is ready.
|
|
break
|
|
# Read the data to complete the operation.
|
|
data = self._read_u8(_AS726X_SLAVE_READ_REG)
|
|
return data
|
|
|
|
def _virtual_write(self, addr, value):
|
|
"""write a virtual register"""
|
|
while True:
|
|
# Read slave I2C status to see if the write buffer is ready.
|
|
status = self._read_u8(_AS726X_SLAVE_STATUS_REG)
|
|
if (status & _AS726X_SLAVE_TX_VALID) == 0:
|
|
break # No inbound TX pending at slave. Okay to write now.
|
|
# Send the virtual register address (setting bit 7 to indicate a pending write).
|
|
self.__write_u8(_AS726X_SLAVE_WRITE_REG, (addr | 0x80))
|
|
while True:
|
|
# Read the slave I2C status to see if the write buffer is ready.
|
|
status = self._read_u8(_AS726X_SLAVE_STATUS_REG)
|
|
if (status & _AS726X_SLAVE_TX_VALID) == 0:
|
|
break # No inbound TX pending at slave. Okay to write data now.
|
|
|
|
# Send the data to complete the operation.
|
|
self.__write_u8(_AS726X_SLAVE_WRITE_REG, value)
|
|
|
|
|
|
# pylint: enable=too-many-instance-attributes
|
|
# pylint: enable=too-many-public-methods
|