A 10bit-ADC based on AttinyX4 with I2C connection!

Note: All the files here

Contents

Principle

In this week’s assignment I will try to develop a board that is able to read data from a 5V analog sensor and that could send data over I2C to a Raspberry Pi. This component would be mainly used to read an analog pressure sensor that I will use in my final project, as part of the monitoring part of it. Now, the intention within the final project would be for it to be connected to a Raspberry Pi and therefore, there are some considerations about the Pi and it’s voltage levels have to be taken into account:

  • The Raspberry Pi does not have ADC and can output either 5V or 3.3V power supply, although the I2C should be done with 3.3V reference
  • The Sensor needs a 5V input, and outputs signal at the same maximum level
  • The microcontroller can work at both 5V or 3.3V, but if powered at 3.3V, the maximum ADC reading should be the same

Before going into the design phase, I will detail below some learnings about how to perform the voltage level changes in these situations.

Changing Voltage Levels

Whenever we are working with different type of sensors and microcontrollers, it is very common to find that we would need to perform some voltage level adaptations for them not to kill eachother. Since I am not an expert (not even close to it) in electronics, I found this explanatory article in Hackaday that talks about how to make voltage changes properly.

For now, I will focus on the Step-Down level-shifter, but more coming for the bi-directional level shifters in future assignments and for my final project.

A Step down level shifter, in the shape of a voltage divider is nothing else than two-stages of resistors connected in series that, merely because of the ratio between them, they are able to make a level shift in voltage. Having read the document above, a voltage divider it’s not the optimal solution for fast rise times, but in my case I will not be reading nor powering the sensor in a in a high speed way. The ratio between the voltages can very simply be calculated as follows:

Also, another useful solution that I will be using in this assignment will be a 5-3.3V Voltage Regulator such as this one, available in the Fablab.

Design

Having considered the above, the AttinyX4 should be dealing with either one of the two voltage levels: 3.3V or 5V, and for this time being, the decision (thanks to the recommendations of Guillem and Victor) will be to power the Attiny with 3.3V, having lowered down the power input itself and the sensor signal output. The only thing to mention is that there won’t be any step up in this solution, so the whole board has to be fed by a 5V power supply and so then the sensor. Therefore, the I2C bus that arrives to the board will be weird in its own, since the Vcc will be 5V, but SCL and SDA lines will be done with 3.3V that the Pi can understand.

The whole schematic (in Kicad) can be seen below:

I included in the design the following:

  • A 4 male header for the analog sensor connection, with Vcc at 5V, GND and two ADC channels (with a voltage divider)
  • An ISP for the chip programming
  • A Grove I2C connector
  • An Attiny84 (was meant to be Attiny44, but for some reason I took the 84 instead by mistake)
  • A green LED as an indicator
  • A 20MHz external resonator
  • Some jumpers to make the routing easier

For the Grove and the 4 male header, I added in Kicad my own footprints. For this, I copied the Fab library and saved it in the new Kicad format: a directory with .pretty extension and the different modules with * .kicad_mod extension:

FabOscar.pretty:

  • 1x04SMD.kicad_mod
  • I2C_Grove.kicad_mod

The footprint for the I2C Grove:


And the 1x04 SMD Male Header:


Also, taking into account the available resistors in the lab, I added the following resistors to the voltage divider, giving me a maximum input voltage for the ADC of 3,19V. This means that if we power the chip at 3.3V we could still be able to read the voltage, but if we want a precise measurement, we should also connect the AREF PIN (the reference for the voltage measurement) to the same output (therefore existing three voltage divider stages):


Routing done in Kicad, I exported it as an SVG and created the PNG files from Iknscape:


And the PNG files for the traces:


And the cuts:


The board is milled in a Modela MDX-20 and the traces are generated with fab.modules (the modela is directly hooked up to the computer and the fab.modules works just fine for it):


And below a picture of the board soldered with all the components on it:


Programming the board

After using the multimeter to check for continuity and shorts (explained in previous assignments) I will test the board via software. For this and in general, I will be using again Platformio, but since Platformio is based on a series of boards in a * .json files, there is a bit of confusion and I had to figure out how to properly set the Clocking speeds.

Note: For more information about platflormio, visit their website and/or my Electronics Design Week

For this board, the wiring with all the stuff goes like this (note that the Raspberry Pi here is for the assignment on Interface):


Initial tests and setup

Firstly, I programmed the board to do nothing than blinking the led, and check that everything is OK. This code below works just fine, blinking the LED, but not at the desired speed:

#include <Arduino.h>

#define LED 7

int _delay = 100;


void setup() {
  pinMode(LED, OUTPUT);

}

void loop() {

    digitalWrite(LED, LOW);   // turn the LED on (HIGH is the voltage level)
    delay(_delay);
    digitalWrite(LED, HIGH);
    delay(_delay);
}

Three things have to be taking into account:

  • That the fuse set to read the external resonator is correct (lfuse). I firstly used the MakeFile with this line of code to flash the fuses:
	avrdude -p t84 -P usb -c usbtiny -U lfuse:w:0x5E:m

And then verified that Platformio is not modifying the fuses in the programming phase. For this I used AVR Fuses (thanking again Guillem for the help), I used AVR Fuses to check, which is a very explanatory way to view the fuses in a connected chip:


Here, we can see a list of Fuses and they can be verified, read or programmed. Doing the verification before and after the programming with Platformio, doesn’t make the fuses change in this verification.

  • That the code is compiled to use a clock speed of 20MHz. This is done via avr-g++ (compiler) and avr-gcc via the compiler message: -DF_CPU=20000000L. In Platformio, the default is to use the 8MHz as specified in the AVR chips Platformio documentation. If we want to compile it, the only way for now is to create a custom board (more on how to do it here) and use a * .json file like in the custom boards directory:
{
  "build": {
    "core": "arduino",
    "extra_flags": "-DARDUINO_AVR_ATTINYX4 -DF_CPU=20000000L",
    "f_cpu": "20000000L",
    "mcu": "attiny84",
    "variant": "tiny14"
  },
  "frameworks": [
    "arduino"
  ],
  "name": "Generic ATTiny84",
  "upload": {
    "extra_flags": "-e",
    "maximum_ram_size": 512,
    "maximum_size": 8192,
    "protocol": "usbtiny"
  },
  "url": "http://www.atmel.com/devices/ATTINY84.aspx",
  "vendor": "Generic ATTiny"
}

Now, we found that the compiler message f_cpu is not being sent properly, but I sent -DF_CPU=20000000L in the extra_flags part.

  • That there is no CLK divider. Here, I think things get tricky becase when we flash initially the chip, we send the lfuse:0x5e:
	avrdude -p t84 -P usb -c usbtiny -U lfuse:w:0x5E:m

Which if we check with AVR Fuses, is setting the CLOCK divider to 8 internally (the lfuse is 0x5e below):


Now, we can either do any of these two things: either use this fuse instead for the external clock without clock divider:

	avrdude -p t84 -P usb -c usbtiny -U lfuse:w:0xDE:m

Or in the code, set the fuse itself with this:

void setup(){
  //
  // set clock divider to /1
  //
  CLKPR = (1 << CLKPCE);
  CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
}

(From Neil’s code).

Reading the sensor

Now, we can go through reading the sensor itself, simply by using the analogRead function in Arduino and setting up the analogReference(EXTERNAL) for the reading. I have the sensor calibration at hand, so I include it in the result, and since I don’t have any way to communicate via Serial to the board, I make the green LED blink differently depending on the pressure readings:

#include <Arduino.h>

#define LED 7
#define SENSOR 1

int _delay = 100;
float Va = 5;
int readSensor = 0;
float Vs = 0;
float pressure = 0;

void setup() {
  pinMode(LED, OUTPUT);
  analogReference(EXTERNAL);

   //
   // set clock divider to /1
   //
   CLKPR = (1 << CLKPCE);
   CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);

}

void loop() {
	readSensor = analogRead(SENSOR);
	
	Vs = ((float) readSensor  + 0.5 ) / 1024.0 * 5.0;
	pressure = (Vs*687.8/Va - 18.77)*1000/1e5; // in bar
	
	_delay = 1000-6000*(pressure-1);

    digitalWrite(LED, LOW);   // turn the LED on (HIGH is the voltage level)
    delay(_delay);
    digitalWrite(LED, HIGH);
    delay(_delay);

}

Finally, check the networking week to see the result of the sensor reading!