├── test.py ├── README.md └── spectrometer.py /test.py: -------------------------------------------------------------------------------- 1 | # Script to test spectrometer.py 2 | import spectrometer as spect 3 | import time 4 | import datetime 5 | 6 | """ 7 | #spectrometer members: 8 | def init() 9 | def boardPresent() 10 | def hwVersion() 11 | def swVersion() 12 | def temperatures() 13 | def setBlueLED(state) 14 | def shutterLED(device,state) 15 | def setLEDDriveCurrent(current) 16 | def setIntegrationTime(time) 17 | def setGain(gain) 18 | def readRAW() 19 | def readCAL() 20 | """ 21 | 22 | fname = '/home/pi/python/' # Directory for results data 23 | 24 | def newFile(fname): 25 | 26 | # Open a unique file for the results data 27 | t = datetime.datetime.now() 28 | 29 | year = str(t.year) 30 | month = str(t.month) 31 | day = str(t.day) 32 | hour = str(t.hour) 33 | min = str(t.minute) 34 | 35 | fname = fname + 'spectrumdata' + year + month.zfill(2) + day.zfill(2) + "time" + hour.zfill(2) + 'h' + min.zfill(2) + '.txt' 36 | fh = open (fname,'w') 37 | 38 | return fh 39 | 40 | # Test read of all calibrated and raw values 41 | def ReadAllData(): 42 | 43 | duration = 3600 * 20 # Desired time in secs for total run. 3600 secs per hour. 60 secs per minute. 44 | sleepPeriod = 60 * 10 # Sleep seconds between passes (interval between measurements) 45 | procTime = 6 # Board compute time per cycle (1 pass of RAW and CAL data fetch) 46 | passes = int(duration / (sleepPeriod + procTime)) 47 | print ("Test duration(s): " +str(duration) + " Sleep period: " + str(sleepPeriod) + " Number of Passes: " +str(passes) ) 48 | 49 | print ("Start time: " + str(datetime.datetime.now())) 50 | 51 | fhandle = newFile(fname) 52 | 53 | count = 0 54 | while (count < passes): 55 | 56 | print (count) 57 | 58 | RAWvalues = spect.readRAW() 59 | CALvalues = spect.readCAL() 60 | 61 | string = "" 62 | for val in RAWvalues: 63 | string = string + str(val) + ',' 64 | 65 | for val in CALvalues: 66 | string = string + str(val) + ',' 67 | 68 | string = string + "\n" 69 | fhandle.write(string) 70 | print (string) 71 | 72 | time.sleep(sleepPeriod) 73 | count = count +1 74 | 75 | print ("End time: " + str(datetime.datetime.now())) 76 | fhandle.close() 77 | 78 | 79 | 80 | 81 | 82 | # ---- main() ----- 83 | 84 | #spect.init() 85 | #spect.hwVersion() 86 | ReadAllData() 87 | 88 | 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AS7265x-spectrometer 2 | Python module to drive the SparkFun Triad Spectroscopy Sensor 3 | https://www.sparkfun.com/products/15050 4 | 5 | ## Board physical properties 6 | 7 | The board consists of 3 devices, each with 6 sensors with passbands ranging from UV to IR. 8 | As can be seen from the diagram, the passbands overlap. The sensors names (A-F, G-L, R-W) and their pass frequencies are therefore somewhat jumbled, especially between the ’51 and ’52. 9 | The 3 on-board LEDs peak at ~400nm, 475nm and 875nm. The built-in calibration should be used to optimize the sensor frequency response for the passband of interest. 10 | 11 | ![alt text](https://i.postimg.cc/T3DrFKcw/spectrum.png) 12 | 13 | Running outside in California on a sunny day in February: 14 | 15 | ![alt text](https://i.postimg.cc/rp2155rT/Dawnto-Dusk-CAL.png) 16 | 17 | ## Use 18 | 19 | Python 2.7 for Raspberry Pi 3B 20 | 21 | test.py instantiates the module and demonstrates use of members. 22 | 23 | ## APIs implemented 24 | 25 | - init() 26 | ``` 27 | Initialize board with default settings (factory reset) 28 | Input variables: none 29 | Legal input values: none 30 | Returns: none 31 | ``` 32 | - boardPresent() 33 | ``` 34 | Return device present or not 35 | Input variables: none 36 | Legal input values: none 37 | Returns: Boolean. True, False 38 | ``` 39 | - hwVersion() 40 | ``` 41 | Return system hardware version 42 | Input variables: none 43 | Legal input values: none 44 | Returns: tuple of ints (device type, hardware version) 45 | ``` 46 | - temperatures() 47 | ``` 48 | Return current temperatures of all 3 devices as a list 49 | Input variables: none 50 | Legal input values: none 51 | Returns: [int, int, int] 52 | ``` 53 | - setBlueLED(state) 54 | ``` 55 | Set master blue LED state (device 1 on IND line) 56 | Input variables: state (Bool) 57 | Legal input values: True, False 58 | Returns: Bool. True if OK. 59 | ``` 60 | - shutterLED(device,state) 61 | ``` 62 | Switch on/off shutter individual LEDs attached to sensor DRV lines 63 | Input variables: device (String), state (Bool) 64 | Legal input values: device {"AS72651","AS72652","AS72653"}, state{True, False} 65 | ``` 66 | - setLEDDriveCurrent(current) 67 | ``` 68 | Set LED drive current for all shutter LEDs together 69 | Input variables: current (Int) 70 | Legal input values: 0, 1, 2, 3 where b00=12.5mA; b01=25mA; b10=50mA; b11=100mA 71 | Returns: Bool. True if OK. 72 | ``` 73 | - setIntegrationTime(time) 74 | ``` 75 | Set integration time for all sensors together 76 | Input variables: time (Int) 77 | Legal input values: 0 to 255 78 | Returns: Bool. True if OK. 79 | ``` 80 | 81 | - setGain(gain) 82 | ``` 83 | Set sensor gains for all devices together 84 | Input variables: gain (Int) 85 | Legal input values: 0, 1, 2, 3 where b00=1x; b01=3.7x; b10=16x; b11=64x 86 | Returns: Bool. True if OK. 87 | ``` 88 | - readRAW() 89 | ``` 90 | Read all 18 RAW values together 91 | Input variables: none 92 | Legal input values: none 93 | Returns: [Int] list of 18 Int values sorted into ascending frequency order 94 | ``` 95 | - readCAL() 96 | ``` 97 | Read all 18 calibrated values together 98 | Input variables: none 99 | Legal input values: none 100 | Returns: [Int] list of 18 Int values sorted into ascending frequency order 101 | ``` 102 | -------------------------------------------------------------------------------- /spectrometer.py: -------------------------------------------------------------------------------- 1 | # Python 2.7 module for the SparkFun Triad Spectroscopy board 2 | # Feb 2019. Version 1 3 | 4 | 5 | from smbus import SMBus # Module for I2C 6 | import time 7 | 8 | # ---- Globals / Constants ----- 9 | 10 | I2C_ADDR = 0x49 11 | 12 | STATUS_REG = 0x00 13 | WRITE_REG = 0x01 14 | READ_REG = 0x02 15 | 16 | TX_VALID = 0x02 17 | RX_VALID = 0x01 18 | 19 | POLLING_DELAY = 0.05 # 50mS delay to prevent swamping the slave's I2C port 20 | i2c = SMBus(1) # Indicates /dev/i2c-1 21 | 22 | 23 | # ---- Low level functions ----- 24 | 25 | # Low level function to read a Master register over I2C 26 | # Input variables: addr (Int) 27 | # Legal input values: n/a 28 | # Returns: data (int) 29 | def readReg(addr): 30 | 31 | status = i2c.read_byte_data(I2C_ADDR,STATUS_REG) # Do a dummy read to ensure FIFO queue is empty 32 | if ((status & RX_VALID) != 0): # There is data to be read 33 | incoming = i2c.read_byte_data(I2C_ADDR,READ_REG) # Read it to clear the queue and dump it 34 | 35 | while (1): 36 | 37 | status = i2c.read_byte_data(I2C_ADDR,STATUS_REG) # Poll Slave Status register 38 | 39 | if ((status & TX_VALID) == 0): # Wait for OK to transmit 40 | break 41 | 42 | time.sleep(POLLING_DELAY) # Polling delay to avoid drowning Slave 43 | 44 | i2c.write_byte_data(I2C_ADDR, WRITE_REG, addr) # send to Write register the Virtual Register address 45 | 46 | while (1): 47 | status = i2c.read_byte_data(I2C_ADDR,STATUS_REG) # Poll Slave Status register 48 | 49 | if ((status & RX_VALID) != 0): # Wait for data to be present 50 | break 51 | time.sleep(0.05) # Polling delay to avoid drowning Slave 52 | 53 | data = i2c.read_byte_data(I2C_ADDR,READ_REG) # Finally pick up the data 54 | 55 | return data 56 | 57 | # Low level function to write to a Master register over I2C 58 | # Input variables: addr (Int), data (Int) 59 | # Legal input values: n/a 60 | # Returns: none 61 | def writeReg(addr,data): 62 | 63 | while (1): 64 | 65 | status = i2c.read_byte_data(I2C_ADDR,STATUS_REG) # Poll Slave Status register 66 | 67 | if ((status & TX_VALID) == 0): # Wait for OK to transmit 68 | break 69 | 70 | time.sleep(POLLING_DELAY) 71 | 72 | i2c.write_byte_data(I2C_ADDR, WRITE_REG, addr | 0x80) # Send Virtual Register address to Write register 73 | 74 | while (1): 75 | status = i2c.read_byte_data(I2C_ADDR,STATUS_REG) # Poll Slave Status register 76 | 77 | if ((status & TX_VALID) == 0): # Ready for the write 78 | break 79 | 80 | time.sleep(POLLING_DELAY) 81 | 82 | i2c.write_byte_data(I2C_ADDR,WRITE_REG,data) # Do the write 83 | return 84 | 85 | # Calibrated data comes back as IEEE754 encoded number (sign/mantissa/fraction). Need to convert to a float. Spec page 27. 86 | # Input variables: [Int] list of 4 Ints 87 | # Legal input values: n/a 88 | # Returns: Float 89 | def IEEE754toFloat(valArray): 90 | 91 | c0 = valArray[0] 92 | c1 = valArray[1] 93 | c2 = valArray[2] 94 | c3 = valArray[3] 95 | 96 | fullChannel = (c0 <<24) | (c1 << 16) | (c2 << 8) | (c3) 97 | 98 | sign = ((1 << 31) & (fullChannel)) >> 31 99 | sign = (-1) ** sign 100 | 101 | exponent = (fullChannel >> 23) & (0xff) 102 | frac = fullChannel & 0x7fffff # filter out all but bottom 22 bits (fraction) 103 | 104 | accum = 1 105 | 106 | for bit in range(22,-1,-1): 107 | if (frac & (1<