Servos and Attiny chips
A while ago PileOfStuff was coding a model railway track switch with the aim of semi-automating the process using a servo and a microcontroller.
He started with an ATTiny85 and was using one of my programming PCBs that I sent to him for that purpose. At one point of frustration in the project he abandoned the ATTiny85 in favour of an Arduino Nano and THREW THE PROGRAMMING PCB AWAY!
Well, here in Tasmania the locals went wild! To redress the balance, I was naturally "forced" to spend hours at the bench attempting to reproduce the servo requirements of a model railway track switch with an ATTiny85 using the discarded PCB as a starting point.
The conventional wisdom concerning servo jitter is as follows:
- buy a more expensive servo you cheapskate!
- put a decent size (e.g. 470uF) electrolytic capacitor across VCC and GND close to the servo
- put a toroidal iron core in the circuit for the servo wires to loop around to suppress transients
- use code suitable for the chip (e.g. this code for ATTiny85)
- after sweeping the servo, detach the servo to stop jitter
- adjust time/delays/ISR/something-else™ to "finesse" the jitter
- twist servo wires together to suppress transients
- have a separate power supply for the servo (common ground)
- set the fuse bits to change the clock speed of the Attiny85
- do the following at some point (?!)
- cli(); // disable interrupts
- digitalWrite(ServoPin, HIGH);
- // use micros() for delay
- digitalWrite(ServoPin, LOW);
- sei(); // enable interrupts
- don't use an ATTiny85
- Make sure that the ATTiny85 is running 8MHz internal clock (burn bootloader)
- Slap a 470uF capacitor close to the servo (common practise for me anyway)
- Use this library for the ATTiny85
- After each servo sweep use detach and then re-attach the servo
- Have a separate power supply for the servo
I will admit that there may be a possible problem with detaching the servo for some applications, as the servo may need to resist torque changes depending on the use. In this case, the servo is horizontally deployed with no external impressed forces, it should stay put. Also, a sleeping ATTiny85 doesn't fight back when you change the position anyway, and this thing mostly sleeps!
The setup works fine, and the code is as follows:
/* Attiny85 based servo code to adjust then switch tracks on a model railway. μC is asleep until button is pressed. If a short press (adjusted by variable "buttondelay") then the track just switches. If a longpress then user can adjust left and right "limits" for the movement. The EEProm is employed to "permanently" keep track (pun intended) of the right and left positions for the servo. When the adjustments are made they are stored and can be retrieved when powered up again. OneCircuit www.onecircuit.blogspot.com Sunday 4 April 17:42:57 AEST 2021 */ // Servo_ATTinyCore.h is part of the ATTinyCore family // found at https://github.com/SpenceKonde/ATTinyCore
#include <Servo_ATTinyCore.h> #include <avr/sleep.h> #include <avr/interrupt.h> #include <EEPROM.h> Servo myservo; // create servo object int potpin = A1; // potentiometer adjustment pin int theaverage = 0; // average pot readings to reduce servo "jitter" int leftpos = 256; // default left position (256 = 45°/180*1023) int rightpos = 767; // default right position (767 = 135°/180*1023) int buttondelay = 600; // longpress delay int leftbutton = PB4; // pins for button connection int rightbutton = PB3; volatile boolean leftpress = false; // which button pressed volatile boolean rightpress = false; boolean eepromwritten = false; // have we used EEProm? int ledpin = PB1; // debug LED for button pushing indication /* to combat "jitter" read the analog pin 25 times and then average the result - this should give the reading some "weight" which adds stability */ int readpin() { for (int i = 0; i < 25; i++) { theaverage = theaverage + analogRead(potpin); } theaverage = theaverage / 25; return theaverage; } // button pressed! ...but which one? ISR(PCINT0_vect) { if (digitalRead(leftbutton)) leftpress = true; else if (digitalRead(rightbutton)) rightpress = true; } // do we have values stored? First EEProm location is // "0" for no and "1" for yes boolean checkeeprom() { int writ = 0; boolean used = false; EEPROM.get(0, writ); if (writ == 0) used = false; if (writ == 1) used = true; return used; } // standard sleep routines - goodnight μC void sleep() { GIMSK |= _BV(PCIE); // Enable Pin Change Interrupts PCMSK |= _BV(PCINT3) | _BV(PCINT4); // Use PB3/4 as interrupt pins ADCSRA &= ~_BV(ADEN); // ADC off set_sleep_mode(SLEEP_MODE_PWR_DOWN); // replaces above statement sleep_enable(); // Sets the Sleep Enable bit in the MCUCR Register (SE BIT) sei(); // Enable interrupts sleep_cpu(); // sleep cli(); // Disable interrupts PCMSK &= ~_BV(PCINT3) | _BV(PCINT4); // Turn off the interrupt pins sleep_disable(); // Clear SE bit ADCSRA |= _BV(ADEN); // ADC on delay(50); // setlle sei(); // Enable interrupts } /* map the ADC (0-1023) result to degrees (0-180) detaching at the end is a contentious way of removing "jitter", but "sleep" effectively detaches anyway */ void movetheservo(int movepos) { myservo.attach(0); movepos = map(movepos, 0, 1023, 0, 180); myservo.write(movepos); delay(600); // generous movement time, adjust as you wish myservo.detach(); } void setup() { pinMode(potpin, INPUT); pinMode(ledpin, OUTPUT); // debug LED to output eepromwritten = checkeeprom(); if (eepromwritten) { // if data is present, recall it EEPROM.get(2, leftpos); EEPROM.get(4, rightpos); movetheservo(rightpos); // or left, you choose?! } else if (!eepromwritten) { movetheservo(rightpos); // default if no EEProm } delay(100); // settle time, get ready! } // left button pressed, short press = change // tracks, long press = adjust servo position void goleftbutton() { // debug LED flashing digitalWrite(PB1, HIGH); delay(50); digitalWrite(PB1, LOW); delay(50); digitalWrite(PB1, HIGH); delay(50); digitalWrite(PB1, LOW); delay(buttondelay); while (digitalRead(leftbutton) == HIGH) { theaverage = readpin(); leftpos = theaverage; movetheservo(leftpos); EEPROM.put(2, leftpos); if (!eepromwritten) { EEPROM.put(0, 1); eepromwritten = true; } } movetheservo(leftpos); } // right button pressed, short press = change // tracks, long press = adjust servo position void gorightbutton() { // debug LED flashing digitalWrite(PB1, HIGH); delay(50); digitalWrite(PB1, LOW); delay(buttondelay); while (digitalRead(rightbutton) == HIGH) { theaverage = readpin(); rightpos = theaverage; movetheservo(rightpos); EEPROM.put(4, rightpos); if (!eepromwritten) { EEPROM.put(0, 1); eepromwritten = true; } } movetheservo(rightpos); } // sleep little one, and if button pressed then // check which one and react accordingly void loop() { sleep(); if (leftpress) { goleftbutton(); leftpress = false; // reset button press } else if (rightpress) { gorightbutton(); rightpress = false; // reset button press } }
Sketch uses 2712 bytes (33%) of program storage space. Maximum is 8192 bytes.
Global variables use 48 bytes (9%) of dynamic memory, leaving 464 bytes for local variables. Maximum is 512 bytes.
Tasmanian honour restored?
No comments:
Post a Comment