Site and Contents © 2020

Barn Door on AVR

05 Dec

Over on Nick Touran’s blog about the barn door tracking code, someone asked a question about making it work on an Arduino; that was a question that piqued my curiosity a bit, but didn’t go one step further until I was challenged again by a friend locally if I could go one better and put it on an ATtiny85. And given that it was a horrible weekend for weather and that we’re under curfew restrictions (still) then it was a better way to pass a few hours than sitting in front of the TV (but if you’ve ever sat in front of Spanish TV for long enough, anything is better than sitting in front of the TV!)

Before we get any further, I have to admit that I love the 85, it’s a great chip for small projects, pretty well featured and when it comes in a package like the Digispark, it can be dropped in to something in a matter of minutes and the whole thing can fit in a matchbox.

One thing I found with adapting this code, was that there was no reliable and quick method on any of the controllers I’ve got in an AVR flavour, so that’s the x4/x5 (Tiny), the x8’s (Uno and Nanos), the 2560 (Mega) to run the code as well as dynamically adjust the interrupts to come in at the right time.

Instead I had to look for another way, and that came down to the runner() function, which holds the processing at a specific point until the microsecond count gets there and then fires off the drive step. Simple, really, but not as robust as an interrupt.

In testing it, it was about 1ms a minute slower than using an ESP8266, which over the course of an hour would give a drift of about 1/4 of a mm of drive insertion, which wasn’t too bad, but that was at a room temperature of around 24ºC.

The kicker came when cold testing (out on the balcony last night it was just over 9ºC) by having the control board in the freezer. Then it started to slow down considerably. One thing I don’t like about the AVR based dev boards like the Arduino is that depending on the quality of the board is that sometimes the crystals are very approximate, and the timing drifts can vary from a few seconds a day to a few seconds an hour. My original Uno which I bought way back when (still have, but the power regulator is burnt) would drop about 1 second every six minutes at room temperature, and as it got colder it’d drop more. Unless connected to a real time clock or other accurate time signal source, I really wouldn’t use an AVR based board for time critical work; the ESP and Cortex based stuff seem a lot tighter in this department. I don’t have enough resources kicking about to test what sort of drift I’d get on an AVR based board but just enough use on them to know there are some.

So, yes, this code can run on an Arduino Uno/Nano/Mega, but probably isn’t the best choice of board for it though as the code running isn’t as efficient and also different chips have different caveats to them (like different boards having different amounts of interrupt pins). Then there’s the things like the power regulators, on a Wemos Mini D1 it seems possible to pass the 5v from an USB input to the 5v pin at whatever current the 5v is at and power off the stepper from there, but if popping power in to the Vin pin of an Arduino board (I’m not sure if the 5v is pass through from USB or not, never checked) the regulators may not push sufficient current out to handle a stepper motor (even something like the 28BYJ), so just adding in a power circuit for the stepper motor and driver would add another layer of complexity for it, so between accurate timings (and on an 8 pin like the 85, there’s no free pins to be able to pass a timer in to the clock) and sufficient juice (remember when I said about the power regulator on my original Uno being cooked?) to keep the magic smoke in it could end up more complex than what you’d need. Then the other thing I don’t like with the Uno/Mega is the headers, it’s either make a shield (with the oddly aligned headers on the board, it’s fun in a masochistic sort of way) or use Duponts which should be punishable by law, or sticking things in to breadboards (which again aren’t good as these things don’t handle current that well). Building a little header board for a Wemos D1 is so much easier.

So, yes, whilst a compensating barn door tracker can be built on an AVR based platform, the thing is, should it really be done? That’s up to you to decide, but personally I’d say that there are better tools for the job.

Now I’ve got the syntax highlighting working here, here is some code in Arduino-C (if it was for my own use, it’d be a lot less portable than this) for it, but this is pinned out for a Digispark type board, as the only reason this came about was being challenged to stick it on an 85…

bool useAutoShutdown = true; long shutdownMinutes = 60; //Mechanical Stuff, these relate to my tracker. double lengthCm = 22.885;//Distance From Centre of Hinge to Centre of Drive double heightAtDrive = 1.10;//Height at Drive when closed double heightAtH1 = 0.65;//Height at measuring point from centre of hinge when closed double hingeToH1 = 4.50;//Distance from measuring point to centre of hinge when closed. double rotationsPerCm = 10; double doubleStepsPerRotation = 2048.0;//Not what I've mine set to, as on the motors I have, this will over run slightly per revolution, it's closer to 2038 on mine. float theta0 = 0.0; #define radsPerSecond 7.292115e-05 //Pins const int totalPins = 4; //Pins based on a Digispark pin out, these would have to be changed to accommodate whatever board you are using. const int drivePin1 = PB0; const int drivePin2 = PB1; const int drivePin3 = PB2; const int drivePin4 = PB3; const int modePin = PB4; int drivePins[totalPins] = {drivePin1, drivePin2, drivePin3, drivePin4}; //Timings double stepInterval = 0.001; double totalSeconds = 0.0; double elapsedTime = 0.0; unsigned long newStepInterval = 0; unsigned long cycles = 0; unsigned long currentTime = 0; unsigned long nextCallback = 0; long internalShutdownMinutes = 330; unsigned long shutdownSeconds = 0; unsigned long lastDebounce = 0; float debounceFactor = 0.2; //Steps volatile int stepDelta = 1; volatile int stepNumber = 0; int *currentStep = 0; unsigned long totalSteps = 0; const int numberOfSteps = 8; int driveSteps[numberOfSteps][totalPins] = { {1, 0, 0, 0}, {1, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 1}, {0, 0, 0, 1}, {1, 0, 0, 1} }; //Modes const int STOPPED = 0; const int NORMAL = 1; const int REWINDING = 2; const int FAST_REWINDING = 3; volatile unsigned short currentMode = STOPPED; //CPU long cyclesPerSecond = 0; //Internal Switches bool autoStop = true; bool isRunning = false; void setLowPins() { for (int i = 0; i < totalPins; i ++) { digitalWrite(drivePins[i], LOW); } } void resetDefaults() { currentTime = 0; lastDebounce = 0; totalSteps = 0; elapsedTime = 0; totalSeconds = 0; } void toggleMode() { if ((cycles - lastDebounce) < 0.2 * cyclesPerSecond) { return; } if (currentMode == NORMAL) { currentMode = STOPPED; setLowPins(); } else if (currentMode == REWINDING) { currentMode = STOPPED; setLowPins(); if (autoStop == false) { totalSeconds = 0; elapsedTime = 0; totalSteps = 0; currentStep = 0; autoStop = true; } } else if (currentMode == STOPPED) { currentMode = NORMAL; nextCallback = cycles + 100000; isRunning = true; } lastDebounce = cycles; } void doStep(int *currentStep) { for (int i = 0; i < totalPins; i ++) { if (currentStep[i] == 1) { digitalWrite(drivePins[i], HIGH); } else { digitalWrite(drivePins[i], LOW); } } } float boltInsertion(double totalSeconds) { float calc = lengthCm * radsPerSecond / pow(cos(theta0 + radsPerSecond * totalSeconds), 2); return calc; } void stepMotor() { switch (currentMode) { case NORMAL : stepDelta = 1; stepNumber %= numberOfSteps; stepInterval = 1.0 / (rotationsPerCm * boltInsertion(totalSeconds) * 2 * doubleStepsPerRotation); break; case REWINDING : stepInterval = 0.0025; stepDelta = -2; if (autoStop == true && totalSteps <= 0) { currentMode = STOPPED; setLowPins(); } if (stepNumber < 0) { stepNumber += numberOfSteps; } break; case STOPPED : stepInterval = 0.1; break; } if (currentMode != STOPPED) { totalSeconds += stepInterval; newStepInterval = stepInterval * 1000000; currentStep = driveSteps[stepNumber]; doStep(currentStep); if (currentMode == NORMAL) { elapsedTime = totalSeconds; } stepNumber += stepDelta; totalSteps += stepDelta; } currentTime = micros(); nextCallback = currentTime + newStepInterval - (currentTime - nextCallback); } void setPins() { for (int i = 0; i < totalPins; i ++) { pinMode(drivePins[i], OUTPUT); } setLowPins(); pinMode(modePin, INPUT_PULLUP); } void setDefaults() { float distance = (lengthCm - hingeToH1); theta0 = atan((heightAtDrive - heightAtH1) / distance); cyclesPerSecond = F_CPU; long _shutdownMinutes = internalShutdownMinutes; if (useAutoShutdown == true && shutdownMinutes < internalShutdownMinutes) { _shutdownMinutes = shutdownMinutes; } shutdownSeconds = (_shutdownMinutes * 60); } void setup() { // put your setup code here, to run once: setDefaults(); setPins(); attachInterrupt(digitalPinToInterrupt(modePin), toggleMode, FALLING); byte buttonState = digitalRead(modePin); if (buttonState == LOW) { autoStop = false; currentMode = REWINDING; } currentMode = NORMAL; isRunning = true; } void runner() { cycles = micros(); do { cycles = micros(); } while (cycles < nextCallback); stepMotor(); if (currentMode == NORMAL && elapsedTime >= shutdownSeconds) { currentMode = STOPPED; setLowPins(); isRunning = false; } } void loop() { // put your main code here, to run repeatedly: if (isRunning == true) { runner(); } else { cycles = micros(); yield(); } }

Share This Post :