2020-05-04
md
I²C Light Sensor using a Seeeduino XIAO
Seeeduino XIAO Serial Communication Interfaces (SERCOM)-> <-Overview of the SAMD21 Arm Cortex-M0+ Based Seeeduino XIAO

All 11 I/O pins of the Seeduino XIAO can be used as 12-bits analogue inputs. In section 7.2 a previous post, I showed how one of these pins could be connected to voltage divider made from a fixed resistor and a light dependant resistor (LDR) to measure light levels. In this post, the I²C capabilities of the XIAO will be exploited to make the light measure available to a Raspberry Pi which notoriously lacks analogue inputs.

In the previous post, instead of sending the light levels to a Raspberry Pi, they were displayed on an OLED display. I thought it could be worthwhile to continue to display the light levels on an LCD, independently of the Raspberry Pi. This is a bit tricky because the display is a I²C slave device and the Xiao I²C is also in slave mode to communicate with the Raspberry Pi which, as far as I know, can only act as master. The problem, of course, is that two slaves cannot communicate directly with each other. The last section of this post shows how to get around this difficulty using the U8g2 software I²C capabilities.

Table of contents

  1. I²C on the Raspberry Pi
  2. I²C Circuit
  3. Data From the Raspberry Pi to the Seeeduino XIAO Using I²C
    1. The Seeeduino XIAO as an I²C Slave Receiving Data
    2. The Raspberry Pi as the I²C Master Sending Data
  4. Data From the Seeeduino XIAO to the Raspberry Pi Using I²C
    1. The Seeeduino XIAO as an I²C Slave Sending Data
    2. The Raspberry Pi as the I²C Master Requesting Data
  5. A 12 Bit I²C Light Sensor
  6. A 12 Bit I²C Light Sensor With Local Display

I²C on the Raspberry Pi toc

By default, the hardware I²C bus controller is not enabled in Raspbian Buster Lite. It must be enabled by including one, and only one, of the following three lines in the /boot/config.txt configuration file.

dtparam=i2c_arm=on dtoverlay=i2c-rtc,ds3231 dtoverlay=i2c1

Usually, the first dtparam line is used unless an I²C real-time clock is also connected to the I²C bus. In that case, the second line which enables the I²C bus controller and loads a driver is used. Of course ds3231 could be replaced by any supported RTC chip. The last option enables the I²C bus controller as the first one, but it offers more flexibility.

woopi@goldserver:~ $ dtoverlay -h i2c1 Name: i2c1 Info: Change i2c1 pin usage. Not all pin combinations are usable on all platforms - platforms other then Compute Modules can only use this to disable transaction combining. Usage: dtoverlay=i2c1,<param>=<val> Params: pins_2_3 Use pins 2 and 3 (default) pins_44_45 Use pins 44 and 45 combine Allow transactions to be combined (default "yes")

Without options, the third line enables the hardware I²C bus controller on the standard I/O pins just like the first line.

The Raspberry Pi must be rebooted to activate the I²C controller. Indeed, until the added line is removed from the configuration file, the /dev/i2c-1 device will be available each time the Pi is booted. If you only want to experiment with the I²C bus for a short while, the controller can be added manually:

woopi@goldserver:~ $ sudo dtoverlay i2c1 woopi@goldserver:~ $ sudo dtoverlay -l Overlays (in load order): 0: i2c1 woopi@goldserver:~ $ ls /dev/i2* /dev/i2c-1

Once the I²C device /dev/i2c-1, the i2cdetect utility can be used to scan the I²C bus.

woopi@goldserver:~ $ ls /dev/i2* /dev/i2c-1 woopi@goldserver:~ $ sudo i2cdetect -y 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --

As can be seen, there are currently no I²C devices attached to the bus. If i2cdetect is not available on the Raspberry Pi, it is easily installed with the command sudo apt install i2c-tools -y. By the way, if the default user is part of the i2c group, it will not be necessary to use the sudo prefix.

Since Python is the "recommended" language for the Raspberry Pi, I created a virtual Python3 environment in order to install the prerequisite smbus module often used to communicate over the I²C bus with Python. See the section 11. Working Directories for a short presentation of how I work with virtual environments in Raspbian Buster.

woopi@goldserver:~ $ mkvenv xiao_i2c creating virtual environment /home/woopi/xiao_i2c updating virtual environment /home/woopi/xiao_i2c Cache entry deserialization failed, entry ignored Cache entry deserialization failed, entry ignored woopi@goldserver:~ $ ve xiao_i2c (xiao_i2c) woopi@goldserver:~ $ pip install smbus Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple Collecting smbus Using cached https://www.piwheels.org/simple/smbus/smbus-1.1.post2-cp37-cp37m-linux_armv7l.whl (43 kB) Installing collected packages: smbus Successfully installed smbus-1.1.post2 (xiao_i2c) woopi@goldserver:~ $

If you do not want to use a virtual environment, I believe that the required smbus module can be loaded into the default Python 3 installation with the bash command sudo apt install python3-smbus.

I²C Circuit toc

The hardware I²C controllers of both devices will be used. I²C is a two-wire bus but the slave and master must also share a common ground so a 3 wire connection must be made between the XIAO and the Raspberry Pi. It could not be simpler: connect the grounds together, connect the clock signals (SCL) together, and connect the data signals (SDA) together.

Because the Raspberry Pi 3 has integrated pull-up resistors on the I²C bus, none needed to be added. Not shown is the USB cable from a desktop computer to the XIAO which provides power to the latter as well as enabling serial communication and firmware downloads (or uploads depending on the point of view).

Data From the Raspberry Pi to the Seeeduino XIAO Using I²C toc

Although not typical of I²C data flows, I decided to start by having the Raspberry Pi send data to the XIAO for the simple reason that one of the first sources on the subject of I²C communication I read was the Master Writer / Slave Receiver tutorial by Nicholas Zambetti on the Arduino site. That tutorial, like many others on the Web, uses two Arduinos but it is not too difficult to use a Raspberry Pi as the master as shown below.

The Seeeduino XIAO as an I²C Slave Receiving Data toc

The first step is to verify that the XIAO can be used as an I²C slave. In the following sketch, the XIAO will echo to the Arduino IDE serial monitor the string and integer data sent by the I²C master. The sketch is quite simple.

/* * i2c_slave_rx.ino */ #include <Wire.h> #define I2C_SLAVE_ADDRESS  4 #define BAUD 115200 void setup() {  // waiting for the serial monitor    Serial.begin(BAUD);  while (!Serial) delay(10);  // setup the Wire libray with device as I²C slave  Wire.begin(I2C_SLAVE_ADDRESS);    // join i2c bus  Wire.onReceive(receiveEvent);     // assign data received handler  Serial.println("\nI2C Slave");  Serial.print("Listening "); } void loop() {  delay(100);  Serial.print("."); } // received data block handler void receiveEvent(int count) {  int reg = Wire.read();  Serial.printf("\nReceived %d bytes for register %d: \"", count-1, reg);  // loop through all but the last byte  while(Wire.available() > 1) {    char c = Wire.read(); // receive each byte as a character    if (c >= ' ')                     Serial.print(c);                   // print the received character c    else      Serial.printf("(#%d)", (byte) c);  // print received ord(c) - should not occur  }  int x = Wire.read();        // receive the last byte as an integer  Serial.printf("%d\"", x);   // print the integer }

The sketch must be running on the XIAO so that the Raspberry Pi will find the XIAO as a I²C device. I will come back to the sketch later.

The Raspberry Pi as the I²C Master Sending Data toc

Once the sketch is running on the XIAO, it was possible to check that it showed up as a device on the Raspberry Pi I²C bus.

woopi@goldserver:~ $ ls /dev/i2* /dev/i2c-1 woopi@goldserver:~ $ i2cdetect -y 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- 04 -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --

As can be seen, the XIAO was found at the address specified in the Arduino sketch running on the XIAO. It is now possible to create a Python script will repeatedly send a string followed by a small integer over the I²C bus.

(xiao_i2c) woopi@goldserver:~/xiao_i2c $ nano i2c_tx.py

''' i2c_tx.py ''' import smbus # XIAO Slave Addresses XIAO_ADDRESS = 0x04 # Create the I²C bus I2Cbus = smbus.SMBus(1) x = 0 # This function converts a string to an array of bytes. def ConvertStringToBytes(src): converted = [] for b in src: converted.append(ord(b)) converted.append(x) return converted counter = 0 while True: BytesToSend = ConvertStringToBytes("x is ") x = (x+1) % 256 I2Cbus.write_i2c_block_data(XIAO_ADDRESS, 0x01, BytesToSend) counter = counter + 1 print('.', end='', flush=True) if counter % 80 == 0: print('')

Once the script is created, it can be launched. Quickly, dots should appear on the screen after each chunk of data is sent to the XIAO over the I²C bus.

(xiao_i2c) woopi@goldserver:~/xiao_i2c $ python i2c_tx.py ................................................................................ ................................................................................ ................................................................................ ................................................................................

If a virtual environment is not used, the appropriate command would be python3 i2c_tx.py. Here is what the XIAO displays in the Arduino IDE serial monitor. Here is the output from the XIAO sent to the Arduino IDE serial monitor.

I2C Slave Listening ..................................................... Received 6 bytes for register 1: "x is 0" Received 6 bytes for register 1: "x is 1" Received 6 bytes for register 1: "x is 2" Received 6 bytes for register 1: "x is 3" Received 6 bytes for register 1: "x is 4" Received 6 bytes for register 1: "x is 5" Received 6 bytes for register 1: "x is 6" Received 6 bytes for register 1: "x is 7" Received 6 bytes for register 1: "x is 8" Received 6 bytes for register 1: "x is 9" Received 6 bytes for register 1: "x is 10" Received 6 bytes for register 1: "x is 11"

If the XIAO sketch is examined, it is clear that the handler is told that 7 bytes have been received not 6. The first byte identifies the I²C register to which the following data stream is written. I have arbitrarily set the register value at 1 in the Python script. The XIAO I²C bus is not a hardware device with multiple registers such as a real-time clock, and that first destination address can be ignored. If the smbus function write_byte had been used in the Python script to write the data one bite at a time, there would be no destination register address, but it would have become necessary to send a guard byte (say 0x00) to signal the end of the string.

To stop the sender script, press the CtrlC keyboard combination.

.................^CTraceback (most recent call last): File "i2c_tx.py", line 46, in <module> I2Cbus.write_i2c_block_data(XIAO_ADDRESS, 0x01, BytesToSend) KeyboardInterrupt (xiao_i2c) woopi@goldserver:~/xiao_i2c $

The two main references for the Arduino sketch and Python script are Wire Slave Receiver by Nicholas Zambetti already mentioned and Raspberry Pi to Arduino I²C Communication by Mike Murray. My thanks to both these sources.

Data From the Seeeduino XIAO to the Raspberry Pi Using I²C toc

The XIAO is more likely to be a peripheral device that will be sending data to the Rapsberry Pi. It will nevertheless remain an I²C slave and only send data to the Raspberry Pi in response to a request from the later which remains the I²C master.

The Seeeduino XIAO as an I²C Slave Sending Data toc

At first glance, the sketch appears simpler than the previous one. Actually it is rather similar with the exception that a handler for the Wire.onRequest event is assigned instead of the Wire.onReceive event. The Raspberry Pi will initiate the transaction by requesting a specific number of bytes from the XIAO and the latter will respond by sending a random 16 bit integer as two distinct bytes.

/* * i2c_slave_tx.ino */ #include <Wire.h> #define I2C_SLAVE_ADDRESS  4 #define BAUD 115200 void setup() {  // start serial port waiting up to 20 seconds  Serial.begin(BAUD);  unsigned long startserial = millis();  while (!Serial && (millis() - startserial < 20000)) delay(10); // Waiting for Serial Monitor      // setup the Wire libray with device as I²C slave  Wire.begin(I2C_SLAVE_ADDRESS);    // join i2c bus  Wire.onRequest(requestEvent);     // assign data request handler  Serial.println("\nI2C Slave");  Serial.print("Waiting for data request "); } void loop() {  delay(100);  Serial.print("."); } // request data block handler void requestEvent(void) {  int x = 128 + random(4023 - 128);       // generate random 16 bit value in the 128..4023 range  Wire.write((x >> 8) & 0xFF);            // send higer 8 bits of data  Wire.write(x & 0xFF);                   // send lower 8 bits of data  Serial.printf("\nSent data %d\n", x);   // print the integer }

There is another change in the sketch which is rather important and thus worth pointing out. The setup function does not wait indefinitely for the Serial object to be created. After 20 seconds the rest of the script executed even without Serial in place. That way, the script can be executed without the XIAO being connected to the desktop computer. The numerous Serial.print statements will not cause a problem. It was then possible to verify that the 5 volt output from the Raspberry Pi GPIO header (pin 2 or 4) can power the XIAO through its 5 volt input pin.

The Raspberry Pi as the I²C Master Requesting Data toc

As can be seen, the script is simple.

''' i2c_rx.py ''' import smbus import time # XIAO Slave Addresses XIAO_ADDRESS = 0x04 # Create the I²C bus I2Cbus = smbus.SMBus(1) while True: data = I2Cbus.read_i2c_block_data(XIAO_ADDRESS, 0x00, 2) value = (data[0] << 8) + data[1]; print("Received value {}".format(value)) time.sleep(5)

This is the output when running the script on the Raspberry Pi.

(xiao_i2c) woopi@goldserver:~/xiao_i2c $ python i2c_rx.py Received value 2896 Received value 1066 Received value 2360 Received value 742 Received value 1448 Received value 3926 ^CTraceback (most recent call last): File "i2c_rx.py", line 37, in time.sleep(5) KeyboardInterrupt

And this is the serial output from the other end.

I2C Slave Waiting for data request ................................................................ Sent data 2896 .................................................. Sent data 1066 .................................................. Sent data 2360 .................................................. Sent data 742 .................................................. Sent data 1448 ................................................... Sent data 3926 ................

Testing shows that the I²C register parameter in the read_i2c_block_data() function, which was set at 0x00 in the script, has no impact. Again the XIAO has not I²C register. As for the byte count parameter, as long as it is at least as large as the data begin sent by the slave, the function should work. However, if the 2 is replaced with a 1 in the function there will be an error because the list returned by the function, which is assigned to data, will contain only one element.

(xiao_i2c) woopi@goldserver:~/xiao_i2c $ python i2c_rx.py Traceback (most recent call last): File "i2c_rx.py", line 35, in value = (data[0] << 8) + data[1]; IndexError: list index out of range

A 12 Bit I²C Light Sensor toc

It is a simple matter to transform the previous example into a useful project. Instead of generating random values to send to the Raspberry Pi over the I²C bus, the data sent will be voltage levels across a voltage divider in which one resistor is a light dependant resistor. This method of measuring light levels with the Seeeduino XIAO has already been investigated so there is nothing really new here.

The sketch to run on the XIAO is almost the same as that presented above. The random data generator is replace by a simple analogue read of an input signal connected to the voltage divider.

/* * i2c_slave_ldr_sensor.ino */ #include <Wire.h> #define BAUD 115200             // Baud for serial port (/dev/ttyACM0 on Linux) #define I2C_SLAVE_ADDRESS  4    // arbitray choice in range (0x03 to 0x77) different from other devices on bus #define LDR 3                   // Analog input pin void setup() {  Serial.begin(BAUD);  Wire.begin();  while (!Serial) delay(10); // Waiting for Serial Monitor    pinMode(LDR, INPUT);  analogReadResolution(ADC_RESOLUTION);  // #define ADC_RESOLUTION 12 in header file       Serial.println("\nI2C Slave");  Serial.print("Waiting for data request ");  Wire.begin(I2C_SLAVE_ADDRESS);    // join i2c bus  Wire.onRequest(requestEvent);     // assign data request handler } void loop() {  delay(100);  Serial.print("."); } // request data block handler void requestEvent(void) {  int level = analogRead(LDR);                  // LDR value between 0 and 4095 = (2^12)-1  Wire.write((level >> 8) & 0xFF);              // send higer 8 bits of data  Wire.write(level & 0xFF);                     // send lower 8 bits of data  Serial.printf("\nLight level: %d\n", level);  // print the LDR value }

Similarly, the Python script on the Raspberry Pi is almost the same as before. The only new element, is that three light level measurements are made and the average is printed out.

''' i2c_light_sensor.py ''' import smbus import time # XIAO Slave Addresses XIAO_ADDRESS = 0x04 # Create the I²C bus I2Cbus = smbus.SMBus(1) # average of 3 readings of the LDR def readSensor(): value = 0; for i in range(3): data = I2Cbus.read_i2c_block_data(XIAO_ADDRESS, 0x00, 2) value = value + (data[0] << 8) + data[1]; return value // 3 while True: print("LDR value {}".format(readSensor())) time.sleep(5)

This is just a "proof of concept" script. In practice, the script is too brittle; a minimum of error handling needs to be introduced.


A 12 Bit I²C Light Sensor With Local Display toc

In my previous post about the Seeeduino XIAO, the light levels as measured by the LDR were shown on an I²C display. I wanted to do something similar here, but there is a complication. The I²C bus is already being used to send the LDR measurements to the Raspberry Pi. The XIAO hardware I²C controller is thus started in slave mode. However the OLED display is itself an I²C bus slave which means only an I²C bus master can send data to it. It was not clear from the little bit of research I did on the subject that the XIAO could alternate between being a bus slave and a bus master. And, nominally there is only one I²C on the Xiao (this is not quite true and the possibility of adding a second bus will be discussed in a later post). So for a first attempt at this, I took the easy way out and connected the display to two other I/O lines and used the U8g2 software I²C driver.

As can be seen, pins A6 and A7, which can be TX and RX signals of a UART, are used to talk to the display, but any other two pins out of the seven that were free could have been used. When using the hardware I²C bus, u8x8 object was created without specifying the SDA and SCL pins.

// Using hardware IIC bus (SDA: 4, SCL: 5) with noname 128x64 0.96" OLED display U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE);

However, in this script using a software I²C bus, the SDA and SCL pins need to be specified.

#define SOFT_I2C_SDA 6 // I/O pin connected to the display I²C SDA pin #define SOFT_I2C_SCL 7 // I/O pin connected to the display I²C SCL pin // Using software IIC bus with noname 128x64 0.96" OLED display U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/*clock*/ SOFT_I2C_SCL, /*data*/ SOFT_I2C_SDA, /* reset=*/ U8X8_PIN_NONE);

Here is the complete sketch.

/* * i2c_slave_ldr_sensor.ino */ #include <Wire.h> #include <U8g2lib.h> #define BAUD          115200    // Baud for serial port (/dev/ttyACM0 on Linux) #define I2C_SLAVE_ADDRESS  4    // arbitray choice in range (0x03 to 0x77) different from other devices on bus #define LDR                3    // Analog input pin #define LDR_DELAY       1000    // Delay (ms) between readings of LDR value #define LDR_VALUES_SIZE   10    // Number of old values retained #define REFRESH_DELAY   2000    // Delay (ms) between updates of LDR value on display #define SOFT_I2C_SDA       6    // I/O pin connecete to the display I²C SDA pin #define SOFT_I2C_SCL       7    // I/O pin connecete to the display I²C SCL pin // Using software IIC bus with noname 128x64 0.96" OLED display U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/*clock*/ SOFT_I2C_SCL, /*data*/ SOFT_I2C_SDA, /* reset=*/ U8X8_PIN_NONE); // variables used for rolling average LDR value int ldrValues[LDR_VALUES_SIZE] {0}; int ldrIndex = 0; int ldrCount = 0; int ldrSum = 0; // adds the a new LDR value and returns the new average int addLdrValue(int newValue) {  ldrSum = ldrSum - ldrValues[ldrIndex] + newValue;  ldrValues[ldrIndex] = newValue;  if (ldrCount < LDR_VALUES_SIZE) ldrCount++;  ldrIndex = (ldrIndex + 1) % LDR_VALUES_SIZE;  return (int) ldrSum / ldrCount; } // returns the last LDR value added to queue int lastLdrValue(void) {  if (ldrCount < 1) {    return -1;    } else {    int i = ldrIndex - 1;    if (i < 0) i = LDR_VALUES_SIZE - 1;    return ldrValues[i];  }      } void setup() {  // Start serial port waiting up to 20 seconds  Serial.begin(BAUD);  unsigned long startserial = millis();  while (!Serial && (millis() - startserial < 20000)) delay(10);  // setup the Wire libray with device as I²C slave  Wire.begin(I2C_SLAVE_ADDRESS);    // join i2c bus  Wire.onRequest(requestEvent);     // assign data request handler  // setup the analog input for the LDR voltage divider  pinMode(LDR, INPUT);  analogReadResolution(ADC_RESOLUTION);  // #define ADC_RESOLUTION 12 in header file     // setup the OLED display  u8x8.begin();  u8x8.setPowerSave(0);  u8x8.setFont(u8x8_font_chroma48medium8_r);      Serial.println("Test of I²C light sensor with concurrent display using software I²C");  Serial.printf("Hardware I²C bus: SDA: A%d, SCL: A%d - XIAO is a bus slave\n", SDA, SCL);    Serial.printf("Software I²C bus: SDA: A%d, SCL: A%d - XIAO is a bus master\n", SOFT_I2C_SDA, SOFT_I2C_SCL);    Serial.print("Waiting for data request "); } int avgLdrLevel; char buf[200]; unsigned long ldrReadTime = 0; unsigned long refreshTime = 0; void loop(){  if (millis() - ldrReadTime >= LDR_DELAY) {    avgLdrLevel = addLdrValue(analogRead(LDR));    ldrReadTime = millis();  }  if (millis() - refreshTime >= REFRESH_DELAY) {    sprintf(buf, "LDR level: %d", avgLdrLevel);    u8x8.clearLine(0);    u8x8.drawString(0,0, buf);    Serial.printf("Last:%d Average:%d\n", lastLdrValue(), avgLdrLevel);    refreshTime = millis();  } } // request data block handler void requestEvent(void) {  int level = avgLdrLevel;                      // LDR value between 0 and 4095 = (2^12)-1  Wire.write((level >> 8) & 0xFF);              // send higer 8 bits of data  Wire.write(level & 0xFF);                     // send lower 8 bits of data }

In the previous section, the Raspberry Pi averaged three consecutive readings. In this newer version, I decided that the XIAO had plenty of power to do the averaging on its own. So if you look at the loop() function, you will see that two things are being done at regular intervals. The voltage at the LDR junction is being read once a second and a running average value, avgLdrLevel is updated at each reading. Less frequently, every two seconds, the current average value is displayed on the OLED screen. Requests for the current average LDR value from the Raspberry Pi are being answered in the background as they come in.

The function that updates the average LDR value, addLdrValue is pretty simple. The latest values read from the LDR are kept in a FIFO (first in, first out) queue. The size of the queue is defined by the LDR_VALUES_SIZE macro. When a new value is read from the sensor, it replaces the oldest value in the queue and then the average of all the valid values is returned. The counter ldrCount keeps track of the number of valid entries in the queue. Once the queue is filled, ldrCount=LDR_VALUES_SIZE will remain true. There is no need to calculate the average ex nihilo. Instead, there is a running sum of all valid values in the queue, ldrSum which gets updated at each new reading. It is not much of a saving if the queue is short because ldrCount additions are being replaced with one addition and one subtraction, but I think it looks neat and when the buffer is made larger, the averaging will make the sensor a better low light level indicator for the home automation system.

The Raspberry Pi script can be slightly simplified because there is no longer any need to do any averaging at this end. The other change made to the script improves its reliability. By enclosing the I²C request in a try except block, execution is not interrupted should the sensor be disconnected.

''' light_sensor.py ''' import smbus import time # XIAO Slave Addresses XIAO_ADDRESS = 0x04 # Create the I²C bus I2Cbus = smbus.SMBus(1) def readSensor(): try: data = I2Cbus.read_i2c_block_data(XIAO_ADDRESS, 0x00, 2) return (data[0] << 8) + data[1]; except OSError as e: print(str(e)) while True: print("Average LDR value {}".format(readSensor())) time.sleep(5)

Do not forget to enable the /dev/i2c-1 controller and to start the Python virtual environment, if you are using the latter, before executing the script.

(xiao_i2c) woopi@goldserver:~/xiao_i2c $ python light_sensor.py Average LDR value 1035 Average LDR value 1069 Average LDR value 1222 Average LDR value 1970 Average LDR value 2577 Average LDR value 2567 ... disconnecting the USB cable to the XIAO ... [Errno 121] Remote I/O error Average LDR value None [Errno 121] Remote I/O error Average LDR value None ... connecting the Raspberry Pi 5 volt output to the XIAO 5 volt input ... don't forget the 20 seconds wait while opening the serial port times out ... Average LDR value 709 Average LDR value 704 Average LDR value 704 Average LDR value 699

While the software I²C bus does work, it is noticeably slower. I can envision four ways of avoiding this problem.

Seeeduino XIAO Serial Communication Interfaces (SERCOM)-> <-Overview of the SAMD21 Arm Cortex-M0+ Based Seeeduino XIAO