Reading the 10bit-ADC with I2C from a Pi!
Note: All the files here
- I2C protocol
- Board Tests
- Communication via I2C
- Final Project task
Very simply, the Inter-integrated Circuit (I2C) Protocol is a protocol intended to allow multiple slave digital integrated circuits (chips) to communicate with one or more master chips. Like the Serial Peripheral Interface (SPI), it is only intended for short distance communications within a single device. Compared to UART (asynchronous RX/TX) where devices need to talk with agreed data clock speed, SPI (the one of MOSI/MISO/SCK.. ), where the number of wires rises with the number of devices, I2C is more convenient since it allows for several masters to be in the same system and for only the masters to be able to drive the data line high. This means that no slave will be able to lock the line in case other is talking:
Image Source: NXP semiconductors
So, I2C bus consists of two signals: SCL and SDA. SCL is the clock signal, and SDA is the data signal. The clock signal is always generated by the current bus master and both lines are pulled up, and therefore the lines are driven high when not used by any slave. Normal values for the pull up resistors could be around 5kΩ. The actual protocol works with messages broken up into two types of frame: an address frame, where the master indicates the slave to which the message is being sent, and one or more data frames, which are 8-bit data messages passed from master to slave or vice versa. Data is placed on the SDA line after SCL goes low, and is sampled after the SCL line goes high.
Image Source: Sparkfun
For the I2C protocol on a Attiny device, we need to look into some short of solucion as in here (more on this below).
Notes on Clock-Stretching
Clock stretching is a technique used when the slave is not able to provide the data, either because it’s not ready or because it’s busy with other things. In these situation, it is possible to do clock stretching. Put simply: normally, the clock is driven by the master devices and slaves simply put data on the bus, or take data off the bus in response to the master’s clock pulses. At any point in the data transfer process, an addressed slave can hold the SCL line low after the master releases it. The master is required to refrain from additional clock pulses or data transfer until such time as the slave releases the SCL line.
However, there are some implementations in the Raspberry Pi that don’t allow clock-stretching with the slaves. Sometimes if the AttinyX4 is run at 8MHz, it can provoke this problem, but it’s not guaranteed that one can get away with higher clock speeds. Nevertheless and for this reason, an external resonator with 20MHz will be used.
In this section I will detail the process I followed to obtain readings from the board using a Raspberry Pi. The board was already tested in the Input Devices week and here I will focus on reading it via I2C.
Communication via I2C
The communication between the AttinyX4 and the Raspberry Pi will be done over I2C. The Raspberry Pi I will be using is a model 3, and the pinout can be found in this link.
I will be connecting the 5V power supply to the I2C grove connector, and the SDA, SCL and GND lines to the grove connector ones. This connection right now is done via jumper cable, but it will be substituted by a Raspberry Pi Hat as part of my final project.
For reference, these connectors are in the board:
Setting up the Raspberry Pi
First thing, I will detail my workflow to set up the Raspberry Pi with a MAC. First thing is to download and mount a Raspbian version with Etcher onto the Pi. This can be done easily under the instructions of the official rasperry pi documentation.
Next, would be to connect to the Pi via ssh or VNC (thanks again Victor for the guidance). These are network communication ways to interact with the Pi without the use of a keyboard and a screen attached to the Pi.
- SSH: stands for Secure Shell and it’s a secure way to connect to the Pi’s command line (and really to any known IP address on our same network). It can also redirect programs through the screen via output redirection.
- VNC: stands for Virtual Network Computing and with it we will have access to the Pi’s screen and use the origin’s keyboard and mouse. This will be the procedure I will be using, with VNC Viewer for MAC.
Now, for both these procedures, we need the Pi to be connected to the our same network in order to use it’s IP. In order to discover the Pi’s address, we can use a command like this in MAC:
Where we are using
awk in order to retrieve the host network (normally something like 192.168.0.1/24). Then, this is used by a network scanner such as nmap to find a device with a MAC address that contains the
b8:27:eb part of the Pi.
This command would give us something like
192.168.0.133 and it can be used for us to connect to the Pi.
Next, we need to turn on the Pi’s I2C with:
And then going to Interface > I2C > Enable I2C. Next, we need to install a couple of libraries (depending on the Pi’s version) for the Pi to detect and interact with the I2C interface. Normally, they are present in the newest versions, but in case not, we can activate it through this command.
Finally, as a last check, we need to install
i2c-tools in the Pi:
And with it we can perform a first check on the Pi’s network, if anything connected (for this test I connected a SHT31 temperature sensor, which address is normally 0x44):
Now we are all set up with the Pi’s network and we can move on to the Attiny84.
Attiny I2C library
Now, the Attiny has no TWI (two wire interface) in hardware for the I2C communication, however, they come with a USI (Universal Serial Interface) that can be used to implement via software a TWI interface compatible with I2C (page 121 of the datasheet) and also explained in a very complex way in this Atmel ANN.
For the Attiny Slave side, I will be using this implementation for the USI as a TWI in the Attiny84. There is a good example in this repository that reads a photoresistor with an Attiny and sends it over via I2C, onto which I will be basing my code.
A picture of the setup is shown below:
Through the FabISP we will be programming the board as specified above, and reading the sensor using a Python code in the Raspberry Pi. The values we will be reading are 10-bits (the resolution of the ADC in the tiny) and therefore we should be splitting them in at least 4 bytes: 2 bytes is the case used by the example, but if we want to send full resolution, we need to go from 16 bits to 32 (not possible to go to 24 since it’s not power of 2). With this operation we can split the values into 4 bytes and store each of the in the variable
We also need to specify the address in the code and set it up (I chose the address 13). Also, I include below the definition of the i2c registers and the library initialisation as a SLAVE:
Finally, the library comes with an interrupt callback under I2C request to send the data over I2C. This function will be triggered everytime the master requests a value and will send the value needed:
We need to time this properly between both of them, so that the Pi receives the data in order. For that, I will be taking averages of the measurements in the attiny with a smoothing function, using a timer to take these measurements:
WiringPi and SM.bus libraries
I tested two libraries for the I2C communication: WiringPi and SM.bus. Both of them connect properly to the I2C and read the data, but I finally used SM.bus for the final example (I found it more robust, but very likely I am not doing it that well with the WiringPi). Nevertheless, I detail below both workflows for reference:
The code for it is below, assuming 4 packs of data MSB (most significant first):
In order to compile and execute the program, we need to use g++ (gnu C compiler) and link it with WiringPi (important!) in the terminal
Next, when we run it in the terminal:
The code is below, with the same 4 packs assumption MSB:
And then run it (no need to compile it since python is an interpreted language):
With SM.Bus, the results in DPa (I know, weird units) are:
Which are very representative of a normal sea level atmospheric pressure (~100kPa)!
As a final note, below, we find how the result is built:
Where 40 corresponds to 40 « 8 and 10240 (to be summed to the last value).
Final Project task
Note: All the designs below are available here
Here, I will detail the process followed to mill and design a Raspberry Pi Hat with 4 I2C Grove connectors that will connect to the different elements on my Final Project.
Design in KiCad
The schematic is pretty simple. I will be using the already created I2C connectors from previous assignments, as well as the generic 2x20 header:
The PCB layout looks like:
Then, the different milling strategies are exported to png. For the traces:
For the inner cuts (remember these ones first!):
For the outter cut:
These are cut in the Modela MDX-20, with the following result after soldering: