Networking and Communications¶
This week I made two of my boards talk via I2C.
The boards in question are the devboard I made for the electronics production week and the hall effect board from the input devices week.
I did pretty much the same while teaming up with Anna, Kerstin and Lars, the other students from HRW FabLab, for our group assignment.
You can find our page right here.
First tests¶
To start off I wired up my two boards via their SCL and SDA pins as well as supplying power and sharing ground.
I then tried uploading an example sketch I found on the documentation page for Wire.h, a library that simplifies the use of I2C in the Arduino IDE.
Here is the code I used:
Controller Reader Sketch
// Wire Controller Reader
// by Nicholas Zambetti [http://www.zambetti.com](http://www.zambetti.com)
// Demonstrates use of the Wire library
// Reads data from an I2C/TWI peripheral device
// Refer to the "Wire Peripheral Sender" example for use with this
// Created 29 March 2006
// This example code is in the public domain.
#include <Wire.h>
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
Serial.begin(9600); // start serial for output
}
void loop() {
Wire.requestFrom(8, 6); // request 6 bytes from peripheral device #8
while (Wire.available()) { // peripheral may send less than requested
char c = Wire.read(); // receive a byte as character
Serial.print(c); // print the character
}
delay(500);
}
Peripheral Sender Sketch
// Wire Peripheral Sender
// by Nicholas Zambetti [http://www.zambetti.com](http://www.zambetti.com)
// Demonstrates use of the Wire library
// Sends data as an I2C/TWI peripheral device
// Refer to the "Wire Master Reader" example for use with this
// Created 29 March 2006
// This example code is in the public domain.
#include <Wire.h>
void setup() {
Wire.begin(8); // join i2c bus with address #8
Wire.onRequest(requestEvent); // register event
}
void loop() {
delay(100);
}
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
Wire.write("hello "); // respond with message of 6 bytes
// as expected by master
}
Uploading to my devboard was business as usual but programming an ATTiny is not as trivial as plugging in a USB cable.
Check out my documentation for the input devices week to see how I did it.
After uploading the code I checked the serial monitor to see if everything worked so far.
Turns out it did not…
For a while I couldn’t figure out what went wrong.
I checked all connections with a multimeter, wiggled every cable, even tried rewriting the code myself but nothing I did seemed to help.
Eventually I came across Adrián Torres’ project where he designed an ATTiny412 based board that communicates with multiple nodes via I2C.
His board featured pullup resistors on the SCL and SDA pins which got me thinking that I missed something crucial when skimming over the I2C spec at 2AM with half a braincell left.
I added some resistors to my devboard the quick and dirty way but, let’s be honest, the board couldn’t get much uglier than it already was.
Problem solved.
Sending sensor values¶
As you probably could have guessed my goal is to have the ATTiny board send values from the hall effect sensor to my devboard.
Small problem though: I2C works on a byte by byte basis and the ATTiny412 has a 10-bit analog to digital converter.
This means that I have to chop my sensor values into byte sized chunks and reassemble them on the receiving end.
During research about how to tackle this problem I came across this tutorial from the wandering engineer.
I modified the example code a little and came up with this:
Hall Sender
#include <Wire.h>
void setup() {
pinMode(0, INPUT);
Wire.begin(2);
Wire.onRequest(requestEvent);
}
void loop() {
delay(10);
}
void requestEvent() {
int16_t value = analogRead(0);
byte array[2];
array[0] = (value >> 8) & 0xFF;
array[1] = value & 0xFF;
Wire.write(array, 2);
}
Dev Receiver
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
}
void loop() {
delay(10);
int16_t value;
byte a,b;
Wire.requestFrom(2, 2);
a = Wire.read();
b = Wire.read();
value = a;
value = value << 8 | b;
Serial.println(value);
}
Bitshifting and masking is kind of new to me so I’ll try to explain it with an example.
Imagine the sensor had given us a value of 4660
, which is 00010010 00110100
in binary.
If we shift those bits to the right by 8 bits we get 00000000 00010010
.
A binary AND operation with 11111111
will then completely remove the first byte leaving us with 00010010
, which is the upper byte.
Doing the same operation without shifting the bits results in the lower byte, 00110100
.
Those two bytes can be stored in an array of bytes that is then transferred via I2C.
On the receiving end those bytes then are reassembled.
The upper byte, which will arrive first, can be stored in a variable.
At the end of that variable, the lower byte is then added.
This is done by shifting the upper byte to the left by 8 bits and performing a binary OR operation with the lower byte.
00010010 << 8 = 00010010 00000000
00010010 00000000 | 00110100 = 00010010 00110100
With that explanation out of the way I can finally show you my results:
Looking good. Not the PCBs, but the delivery of values…
Time for new PCBs¶
At some point I realized that my first PCBs were kind of flawed.
Connecting multiple devices to the I2C bus didn’t really work all that well because the boards didn’t have the ability for daisy chaining.
To change this I once again took inspiration from Adrián Torres’ project and went with flat ribbon cables and IDC connectors.
Besides finally being able to use the XIAO RP2040 for my devboard, I also remembered the pullup resistors for SDA and SCL.
Daisy chaining the I2C bus is done via a 2x2 SMD pin header, while the other connections to I/O pins pretty much stayed the same.
I also added some mounting holes in the corners of the board.
Now I “only” need to change the hall sensor board and actually manufacture them but more on that later.
Until then, stay hydrated! :)