RaspberryPi reading analog input using an AtTiny through i2c

The raspberrypi has some GPIO (General Purpose Input Output) pins. That’s great for experimenting with electronics for example sensors and actuators. It’s totally different than an Arduino in many respects, but that’s something they have in common. Some of the pins have special functions. For example SPI, I2C, UART …

There is a breakboard adapter for all the GPIO pins with a ribbon cable that you can order from the US. That’s cool, but ordering stuff from abroad can be expensive. And the pins look somehow like good old IDE. So I soldered an adapter myself and bought an IDE cable. Well, some pins worked, and some didn’t… Enough for the first round of experimenting, but it took a while to find out what’s going on. I just assumed that all the wires of the IDE cable were connected which for some reason was not the case.

But something is missing that the arduino offers: analog. Before I really needed analog sensing capabilities, I found an article, describing a hack to read analog input by measuring the time it takes to discharge a capacitor through the resistance you want to measure. Immediately, I tried it myself with a photo resistor. The author warned, that the timings with the python script are not really accurate, and that the correct values for the components would have to be calculated. The Values I got were fluctuating wildly, and I couldn’t really see a difference with the brightness in all that noise.

So I looked for something more accurate. I still have some AtTiny’s and they have analog inputs. But SPI is the only means of communication they support in hardware. Last week, I implemented uart receiving capabilities in software, but this time I was looking for i2c. I found a library that seemed to fit, but at first I didn’t get it to work. After a lot of experimenting and searching on the internet, I found out that I have to set the fuses for the internal PLL clock rather than the default internal RC oscillator. Actually the fuse calculator is a great tool for finding out the correct values for the fuses. The uart implementation initially also suffered from clock problems. I solved them by connecting a crystal as an accurate clock source. But this time I had also a stepper motor driver connected, and thus the pins of the AtTiny were exhausted. Another thing I learned while experimenting was that if I send a commad over i2c to the attiny while it is executing the steps for the stepper motor, i2c communication will be broken on the AtTiny until the next reset. When probed with i2cdetect, it seemed to respond on all possible addresses.

The AtTiny has 10 bit analog inputs, but I configered the reading to 8bit precision, because it was a little bit easier, and for the light sensitive resistor, that’s accurate enough. The values I get are much more meaningful than what I got with the capacitor discharge method.

I will make the code available on github, once the whole project is advanced a little bit further. Until then, here are the important parts:

code on the attiny:

// This program is free software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License, or (at your option) any later
// version.

// This program is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE.  See the GNU General Public License for more details.

// This program will serve as an interface between the raspberrypi and the easystepper
// as well as provide an analog input. Communication between the raspi and the attiny will be I2C.
// Created by Richard Ulrich <richi@paraeasy.ch>
// Inspired by :
// http://www.schmalzhaus.com/EasyDriver/index.html
// http://arduino.cc/playground/Code/USIi2c
// http://auv.co.za/blog/attiny45quadraturedecoder
// http://correll.cs.colorado.edu/?p=1801

// ATMEL ATTINY45 / ATTINY85
//                                    +-/-+
// PCINT5/!RESET/ADC0/dW        PB5  1|    |8  Vcc
// PCINT3/XTAL1/CLKI/!OC1B/ADC3 PB3  2|    |7  PB2 SCK/USCK/ADC1/T0/INTO/PCINT2
// PCINT4/XTAL2/CLKO/OC1B/ADC2  PB4  3|    |6  PB1 MISO/D0/OC0B/OC1A/PCINT1            pwm1
//                              GND  4|    |5  PB0 MOSI/D1/SDA/AIN0/!OC0A/AREF/PCINT0  pwm0
//                                    +----+
//                       +-/-+
//                      1|    |8  Vcc
// stepper step      <- 2|    |7 <-  I2C_SCL
//               LDR -> 3|    |6  -> stepper direction
//               GND    4|    |5 <-> I2C_SDA
//                       +----+

#include <inttypes.h>
#include "usiTwiSlave.h"
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>

uint8_t  EEMEM i2cSlaveAddr = 0x10; // Default address
uint16_t EEMEM stepDuration = 50;   // Default step duration

int main(void)
{
	_delay_ms(100); // give the master some time to grab the i2c bus
	sei(); 			// enable interrupts
	if(eeprom_read_byte(&i2cSlaveAddr) < 7 || eeprom_read_byte(&i2cSlaveAddr) > 77)
		eeprom_write_byte(&i2cSlaveAddr, 0x10);
	usiTwiSlaveInit(eeprom_read_byte(&i2cSlaveAddr)); // initialize i2c

	// Set Port B pins 3 and 1 as output
	DDRB = (1 << PB3) | (1 << PB1);

	// Prepare for analog input
	// Configure ADMUX register
	ADMUX = (1 << ADLAR)| // 8bit precision
			(1 << MUX1) | // Use ADC2 or PB4 pin for Vin
			(0 << REFS0)| // set refs0 and 1 to 0 to use Vcc as Vref
			(0 << REFS1);

  	//Configure ADCSRA register
	ADCSRA = (1 << ADEN)| //set ADEN bit to 1 to enable the ADC
     		 (0 << ADSC); //set ADSC to 0 to make sure no conversions are happening

	// the main loop
	while(1)
	{
		// set ADSC pin to 1 in order to start reading the AIN value
		ADCSRA |= (1 << ADSC);
		while(((ADCSRA >> ADSC) & 1))
			; // do nothing until the ADSC pin returns back to 0;
		const uint8_t analogval = ADCH; //for 8 bit precision we can just read ADCH

		if(usiTwiDataInReceiveBuffer())
		{
			const uint8_t cmd = usiTwiReceiveByte();

			switch(cmd)
			{
				case 0xA1: // change the i2c address (sends 1 byte)
				{
					uint8_t recv = usiTwiReceiveByte();
					eeprom_write_byte(&i2cSlaveAddr, recv);
					usiTwiSlaveInit(recv); // initialize i2c
					break;
				}
				case 0xA2: // set the duration of a single step (sends 2 bytes)
				{
					uint16_t dur = usiTwiReceiveByte() << 8;
					dur         += usiTwiReceiveByte();
					eeprom_write_word(&stepDuration, dur);
					break;
				}
				case 0xB1: // move stepper forward   (sends 2 bytes)
					PORTB |= (1 << PB1); // stepper direction forward
				case 0xB2: // move stepper backwards (sends 2 bytes)
				{
					uint16_t steps  = usiTwiReceiveByte() << 8;
					steps          += usiTwiReceiveByte();
					uint16_t dur2   = eeprom_read_word(&stepDuration) / 2;
					for(uint8_t i=0; i<steps; ++i)
					{
						PORTB |= (1 << PB3);
						for(uint16_t j=0; j<dur2; ++j)
							_delay_ms(1);

						PORTB &= ~(1 << PB3);
						for(uint16_t j=0; j<dur2; ++j)
							_delay_ms(1);
					}
					PORTB &= ~(1 << PB1); // stepper direction low
					break;
				}
				case 0xC1: // query analog value of ADC2 with 8 bit (expects 1 byte)
				{
					// send the value from the analog input
					usiTwiTransmitByte(analogval);
					break;
				}
			}; // switch
			continue;
		}

		_delay_ms(10);
	}

	return 1;
}

client code on the raspberrypi:

#! python

import smbus, time

class AttinyStepper:
	def __init__(self, i2cSlaveAddr = 0x10, stepDuration = 50, i2cBusNbr = 1):
		self.i2cSlaveAddr = i2cSlaveAddr
		self.stepDuration = stepDuration
		self.i2c = smbus.SMBus(i2cBusNbr)
		# set the step duration
		self.i2c.write_byte(self.i2cSlaveAddr, 0xA2)
		self.i2c.write_byte(self.i2cSlaveAddr, self.stepDuration >> 8)
		self.i2c.write_byte(self.i2cSlaveAddr, self.stepDuration & 0xFF)

	def changeSlaveAddress(self, newAddr):
		self.i2c.write_byte(self.i2cSlaveAddr, 0xA1)
		self.i2c.write_byte(self.i2cSlaveAddr, newAddr)

	def stepsForward(self, numSteps):
		self.i2c.write_byte(self.i2cSlaveAddr, 0xB1)
		self.i2c.write_byte(self.i2cSlaveAddr, numSteps >> 8)
		self.i2c.write_byte(self.i2cSlaveAddr, numSteps & 0xFF)

	def stepsBackward(self, numSteps):
		self.i2c.write_byte(self.i2cSlaveAddr, 0xB2)
		self.i2c.write_byte(self.i2cSlaveAddr, numSteps >> 8)
		self.i2c.write_byte(self.i2cSlaveAddr, numSteps & 0xFF)

	def readAnalog(self):
		self.i2c.write_byte(self.i2cSlaveAddr, 0xC1)
		lightval = self.i2c.read_byte(self.i2cSlaveAddr)
		return lightval

	def __repr__(self):
		print "attiny interfacing to the easy stepper driver at i2c address %d" % self.i2cSlaveAddr

Posted

in

, ,

by

Comments

6 responses to “RaspberryPi reading analog input using an AtTiny through i2c”

  1. Daniel Avatar
    Daniel

    Hey Richi,
    First of all: Nice Code example =)
    I would like to use it so could you mail me the code?
    I can’t copy it from this site due to some layout problems =/
    (Your Code is wider than your code-window)
    Greetings Daniel

    1. ulrichard Avatar

      Hi Daniel,
      the project is still not finished, but I also keep adding new things to it.
      Anyway, the code is on github. The relevant parts outlined in this post are:
      https://github.com/ulrichard/jungleroom/tree/master/attiny_stepper
      https://github.com/ulrichard/jungleroom/blob/master/AttinyStepper.py

      Have fun, and please report back what you did with it.

      Rgds
      Richard

  2. […] let’s sniff a communication that works better. In a former blog post I described how I read the value from a light sensitive resistor from the RaspberryPi through i2c […]

  3. loom Avatar
    loom

    Hi,

    this tutorial is great, but could you write some more about how to set the fuses and why and so on ? 🙂

    > the fuses for the internal PLL clock rather than the default internal RC oscillator.

    1. ulrichard Avatar

      Hi Loom,
      sorry for not being specific enough. setting the fuses is part of the flash target in https://github.com/ulrichard/jungleroom/blob/master/attiny_stepper/CMakeLists.txt

      # for fuse settings see http://www.engbedded.com/fusecalc/
      SET(LFUSE 0xe1)
      SET(HFUSE 0xdd)
      SET(EFUSE 0xff)

      ADD_CUSTOM_TARGET(flash
      avrdude -P ${AVRDUDE_PORT} -p ${AVRDUDE_DEVICE} -c stk500v2 -Uflash:w:tinypi.hex -Ulfuse:w:${LFUSE}:m -Uhfuse:w:${HFUSE}:m -Uefuse:w:${EFUSE}:m
      DEPENDS tinypi.elf
      )

      In fact, http://www.engbedded.com/fusecalc/ is great for finding out how to set the fuses.
      As to the why, I have to admit, I don’t know or remember exactly. There are so many options to choose from regarding the clock. If I remember right, the RC clock was just not accurate enough, while the PLL clock source is really good on the AtTinies.

  4. loom Avatar
    loom

    Hi,

    Vielen Dank für die ausführliche Hilfe 🙂

    P.s.Normalerwiese missbrauche ich Kommentarfelder nicht als Forum 😉

Leave a Reply

Your email address will not be published. Required fields are marked *