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
Leave a Reply