rodw
Well-Known Member
- Joined
- Dec 2, 2012
- Messages
- 1,146
- Reaction score
- 340
EDIT: Latest Sketch (V9) is on post #155: http://www.homemodelenginemachinist.com/showpost.php?p=262782&postcount=155
This is a substantial upgrade that supports ramping stepper speeds up and down and also saves defaults in the EEPROM so you don't have to recompile it if you change your hardware.
You will need to copy the header file into your Arduino libaries so it is included It is a standard Arduino library from here http://playground.arduino.cc/Code/EEPROMWriteAnything
The last version before this upgrade is:
post #108: http://www.homemodelenginemachinist.com/showpost.php?p=262304&postcount=108
The first version with REQUIRED libraries and links are on Post #20:
http://www.homemodelenginemachinist.com/showpost.php?p=262061&postcount=39
---------------------------------------------------------------------------------------------------------------
As promised I thought I'd share some stuff I've learnt about in relation to Arduino programming with particular reference to Rotary Table controllers You see, Chuck Fellowes inspired me ages ago but being still in the workforce, I jut have never had the time to get this project off the ground despite putting in some really solid stints on programming before running out of time and found to was to hard to pick up where I left off each time. Now that TorontoBuilder working on his project and there is some interest, I thought I'd share what I've learnt as it might inspire some one to pick up where I left off.
Manage your memory
So the first thing to know with the Arduino is quite tiny in relation to its dynamic memory that is used to store variables. You MUST store string data (eg text data) in Program memory or you will run out of room, the stack will overflow and things get unpredictable. Ask me how I know?
Do it like this:
If you don't do this, the Arduino will store it in program memory, and copy the data into dynamic memory at runtime. Just remember that PROGMEM cannot be altered.
As your program grows in size, pay careful attention to the Arduino output.
Pick your numbers
The next thing to consider is the size of numbers that you need to deal with. Using floating point arithmetic is costly in terms of both memory usage and execution time. In dividing head dealing with discrete 360 degrees per revolution and steppers with a finite number of steps per revolution, there is no need to use floating point maths. an integer is not large enough if somebody (like me) uses a microstepping drive (like the Geckos which have 2000 steps per rev). Go with a long data type (4 byte value). There is also no reason no deal with negative values as we have a direction pin on the stepper controller to tell us which direction to rotate. So why not work with unsigned longs which can handle up to 4,294,967,295 that should be enough!
Just be aware, that if you take a big number away from a smaller number, unsigned numbers cannot show a negative number, so validate your data carefully!
Also, when you stop and think about measuring an angle, the base units we use are degrees, minutes and seconds. If you internally work in seconds, there is no need to use floating point maths at all. There are only 1296000 seconds in a circle!
Use the timer Interrupt to drive our stepper
Typically, most stepper code I see uses delay() for stepper pulses. This is a bad idea as it can halt the arduino from doing its own internal housekeeping during the delay. There is a much better way to achieve the same thing and that is to attach to the timer interrupt. Interrupts let you pass off processing to the CPU outside of your main program logic, hence their name. So we simply tell the CPU a time period to fire and it will magically run a procedure for you while you are doing other stuff. The key here is to keep your interrupt service routine (ISR) a short as possible. There is an included ISR example sketch for the Arduino that flashes the LED on the board which you can modify easily to drive a stepper controller. Here is a working (but trivial example) configured for the Gecko microstepper. Divide the values (4000 and 8000) by 10 if you are using a Polou driver.
Yeh, I know I said never to use delay() but this is just a simple test sketch (It will also blink the LED.
Store configuration in EEPROM
I hate hard coding configuration data. The Arduino has an EEPROM which can be thought of a a tiny hard drive. The UNO has 1024 bytes of read/write EEPROM. Why not store your global data in there? This is pretty easy if you create global structure that contains all of your config data and include a signature in the first few characters. That way, you can look in the EEPROM and decide if there is data to retrieve or it is empty.
This link explains how.
http://playground.arduino.cc/Code/EEPROMLoadAndSaveSettings
I've found this to be really easy to enable.
Use a Data Entry Library that lets you build a form for data entry.
Arduinos are not often required to tell you whats happening on a built in display, much less let you key data in. I've had a lot of trouble finding a good data entry library. I started to write my own and had it pretty complete but was plagued by keyboard bounce issues on my Freetronics keyboard/display board. Then I found a good one for the Freetronics board. It was still not perfect as it was missing two data types; Angles (in degrees, minutes and seconds) and a routine that allowed you to enter arbitrary numbers without thousands of keyboard presses!
Something like this:
and this:
the beauty of this library is that you can build forms containing several fields that are treated as one complete unit. This substantially reduces coding overheads. Here is my demo form containing all supported data types
Order by the menu
Life becomes really easy if you can use a menu system that allows you to define the complete interface before coding anything If you set up a Todo procedure, you can then progressively replace each to do procedure with live code. Something like this:
And a simple stub for each menu procedure
Phew, Thats probably enough for one post. More to come!
This is a substantial upgrade that supports ramping stepper speeds up and down and also saves defaults in the EEPROM so you don't have to recompile it if you change your hardware.
You will need to copy the header file into your Arduino libaries so it is included It is a standard Arduino library from here http://playground.arduino.cc/Code/EEPROMWriteAnything
The last version before this upgrade is:
post #108: http://www.homemodelenginemachinist.com/showpost.php?p=262304&postcount=108
The first version with REQUIRED libraries and links are on Post #20:
http://www.homemodelenginemachinist.com/showpost.php?p=262061&postcount=39
---------------------------------------------------------------------------------------------------------------
As promised I thought I'd share some stuff I've learnt about in relation to Arduino programming with particular reference to Rotary Table controllers You see, Chuck Fellowes inspired me ages ago but being still in the workforce, I jut have never had the time to get this project off the ground despite putting in some really solid stints on programming before running out of time and found to was to hard to pick up where I left off each time. Now that TorontoBuilder working on his project and there is some interest, I thought I'd share what I've learnt as it might inspire some one to pick up where I left off.
Manage your memory
So the first thing to know with the Arduino is quite tiny in relation to its dynamic memory that is used to store variables. You MUST store string data (eg text data) in Program memory or you will run out of room, the stack will overflow and things get unpredictable. Ask me how I know?
Do it like this:
Code:
const char item_Eggs[] PROGMEM = "Eggs";
As your program grows in size, pay careful attention to the Arduino output.
Code:
Sketch uses 13,532 bytes (41%) of program storage space. Maximum is 32,256 bytes.
Global variables use 470 bytes (22%) of dynamic memory, leaving 1,578 bytes for local variables. Maximum is 2,048 bytes.
Pick your numbers
The next thing to consider is the size of numbers that you need to deal with. Using floating point arithmetic is costly in terms of both memory usage and execution time. In dividing head dealing with discrete 360 degrees per revolution and steppers with a finite number of steps per revolution, there is no need to use floating point maths. an integer is not large enough if somebody (like me) uses a microstepping drive (like the Geckos which have 2000 steps per rev). Go with a long data type (4 byte value). There is also no reason no deal with negative values as we have a direction pin on the stepper controller to tell us which direction to rotate. So why not work with unsigned longs which can handle up to 4,294,967,295 that should be enough!
Just be aware, that if you take a big number away from a smaller number, unsigned numbers cannot show a negative number, so validate your data carefully!
Also, when you stop and think about measuring an angle, the base units we use are degrees, minutes and seconds. If you internally work in seconds, there is no need to use floating point maths at all. There are only 1296000 seconds in a circle!
Use the timer Interrupt to drive our stepper
Typically, most stepper code I see uses delay() for stepper pulses. This is a bad idea as it can halt the arduino from doing its own internal housekeeping during the delay. There is a much better way to achieve the same thing and that is to attach to the timer interrupt. Interrupts let you pass off processing to the CPU outside of your main program logic, hence their name. So we simply tell the CPU a time period to fire and it will magically run a procedure for you while you are doing other stuff. The key here is to keep your interrupt service routine (ISR) a short as possible. There is an included ISR example sketch for the Arduino that flashes the LED on the board which you can modify easily to drive a stepper controller. Here is a working (but trivial example) configured for the Gecko microstepper. Divide the values (4000 and 8000) by 10 if you are using a Polou driver.
Code:
#include <TimerOne.h>
#define stepPin 4
#define dirPin 5
#define CLOCKWISE HIGH
#define ANTICLOCKWISE LOW
int dir = CLOCKWISE; // Clockwise
int state = LOW;
int steps = 8000;
int ctr = 0;
void setup()
{
// Initialize the digital pin as an output.
// Pin 13 has an LED connected on most Arduino boards
pinMode(13, OUTPUT);
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);
digitalWrite(dirPin, HIGH);
digitalWrite(stepPin, state);
Timer1.initialize(40); // set a timer of length in microseconds (or 0.1 sec - or 10Hz
Timer1.attachInterrupt( timerIsr ); // attach the service routine here
}
void loop()
{
// Main code loop
// TODO: Put your regular (non-ISR) logic here
int i;
if(!steps && ctr < 10){
delay(200);
dir = dir ^ 1;
digitalWrite(dirPin, dir);
if(dir == CLOCKWISE)
steps = 8000;
else
steps = 4000;
ctr++;
}
}
/// --------------------------
/// Custom ISR Timer Routine
/// --------------------------
void timerIsr()
{
// Toggle LED
if(steps){
digitalWrite( 13, digitalRead( 13 ) ^ 1 );
state = state ^ 1;
digitalWrite( stepPin, state );
steps--;
}
}
Yeh, I know I said never to use delay() but this is just a simple test sketch (It will also blink the LED.
Store configuration in EEPROM
I hate hard coding configuration data. The Arduino has an EEPROM which can be thought of a a tiny hard drive. The UNO has 1024 bytes of read/write EEPROM. Why not store your global data in there? This is pretty easy if you create global structure that contains all of your config data and include a signature in the first few characters. That way, you can look in the EEPROM and decide if there is data to retrieve or it is empty.
This link explains how.
http://playground.arduino.cc/Code/EEPROMLoadAndSaveSettings
I've found this to be really easy to enable.
Use a Data Entry Library that lets you build a form for data entry.
Arduinos are not often required to tell you whats happening on a built in display, much less let you key data in. I've had a lot of trouble finding a good data entry library. I started to write my own and had it pretty complete but was plagued by keyboard bounce issues on my Freetronics keyboard/display board. Then I found a good one for the Freetronics board. It was still not perfect as it was missing two data types; Angles (in degrees, minutes and seconds) and a routine that allowed you to enter arbitrary numbers without thousands of keyboard presses!
Something like this:
Code:
DegreeField degreeField(mainForm, "Rotation", 360, DEGREEFIELD_READ_WRITE);
Code:
GetIntField stepField(mainForm, "Number of steps", 9999, GETINTFIELD_READ_WRITE);
the beauty of this library is that you can build forms containing several fields that are treated as one complete unit. This substantially reduces coding overheads. Here is my demo form containing all supported data types
Code:
Form mainForm(lcd);
TextField welcomeField(mainForm, "Form example", "v1.0");
TimeField timeField(mainForm, "Time since reset", 24, TIMEFIELD_READ_ONLY);
DegreeField degreeField(mainForm, "Rotation", 360, DEGREEFIELD_READ_WRITE);
IntField volumeField(mainForm, "Volume", 0, 100, 5, 85, "%");
BoolField ledField(mainForm, "Status LED", "On", "Off", true);
ListField ingredient(mainForm, " <Main Menu> ", ingredients);
GetIntField stepField(mainForm, "Number of steps", 9999, GETINTFIELD_READ_WRITE);
Order by the menu
Life becomes really easy if you can use a menu system that allows you to define the complete interface before coding anything If you set up a Todo procedure, you can then progressively replace each to do procedure with live code. Something like this:
Code:
void setupMenus()
{
g_menuLCD.MenuLCDSetup();
MENU_ACTION_CALLBACK_FUNC func);
p_menuEntryRoot = new MenuEntry(menuEntries[0], NULL, NULL);
g_menuManager.addMenuRoot( p_menuEntryRoot );
g_menuManager.addChild( new MenuEntry(menuEntries[1], NULL, HomeSetCallback) );
g_menuManager.addChild( new MenuEntry(menuEntries[2], NULL, HomeGotoCallback ) );
g_menuManager.addChild( new MenuEntry(menuEntries[3], (void *) &g_menuManager, MenuEntry_BackCallbackFunc) );
g_menuManager.addSibling( new MenuEntry(menuEntries[4], NULL, NULL ) );
..................
..................
..................
..................
g_menuManager.addSibling( new MenuEntry( menuEntries[32], NULL, CreditsCallback) );
//Make sure the menu is drawn correctly after all changes are done
about();
g_menuManager.DrawMenu();
}
Code:
// Define Device Backlash Parameters eg. Mechanical number of steps required to take up backlash
void EditDevBackLashCallback( char* pMenuText, void *pUserData )
{
ToDo();
g_isDisplaying = true;
}
Code:
void ToDo( void)
{
g_menuLCD.ClearLCD();
g_menuLCD.getLCD()->setCursor( 0,0 );
g_menuLCD.getLCD()->print("To Do");
g_menuLCD.getLCD()->setCursor( 0,1 );
flashscreen();
}
Last edited: