Gallery, Projects and General > Project Logs
Mill Electrotrickery Part 1 - Stepping out with Arduino
<< < (5/7) > >>
RodW:
There are plenty of examples of Arduino CNC. There is a G-Code interpreter available for it (open source) and there are some custom boards for 3D printers if you want a small CNC controller with the stepper drivers built in on eBy for about $130. I am not that keen on converting any of my stuff to CNC but would like some push button convenience on the rotary table and maybe a power lifter to wind the Z axis up and down as the Seig is pretty manual to adjust. I reall have enough to learn about this hobby without learning how to drive 3d design software.

As Kwackers says, the biggest problem is scope creep once you start thinking about interfacing CPU's with the outside world!
RodW:
Well, I have finally sorted out how the field navigation will work but still have not much to show for it yet. I have coded different procedures for different field types (with a few more to go but they are just variants of what is already done).

So each field has a structure that defines where it lives on the screen and data it needs. For a number it looks something like this:

--- Code: ---typedef struct  getnumstr_t
{
int x;                       // X ordinate
int y;                       // Y ordinate
        char width;                  // Field width
        char prec;                   // Field Precision
        const char PROGMEM *prompt;  // prompt
        double  *num;                // Number 
}; // Field that enters a string value

--- End code ---

This is not quite how I wanted it to be but the Arduino does not support sprintf() for doubles and floats and you have to use the AVR function dtostrf() instead hence the values for width and precision.

For a list of string options (think choose from a list like "Clockwise" or "Anticlockwise"), it looks like this:


--- Code: ---typedef struct getoptionstr_t
{
int x;                        // X ordinate
int y;                        // Y ordinate
        const char PROGMEM *prompt;   // Prompt
char **values;                // NULL terminated array of values to display to choose from
}; // Field that scrolls though a list of values

--- End code ---

Then these field types have their own function to enter the data

--- Code: ---char *getnum(getnumstr_t *p);
char *getstr(getstr_t *p);
char *getoptstr(getoptionstr_t *p);
--- End code ---

And the screen will be made up of a linked list of these fields held in a structure like this


--- Code: ---typedef struct screenfield_t
{
        void *userdata;        // Pointer to field structure
        char fieldType;        // Field Type for UserData
        screenfield_t *prev;   // Previous field, NULL for first
        screenfield_t *next;   // Next field, NULL for last
}; // Screen Field type definition

--- End code ---

So to process several fields shown on the display at one time, it will look like this


--- Code: ---screenfield_t *despatchfield(screenfield_t *p, char fieldtype)
{
   char *c; 
   switch(p->fieldType){
       case FIELD_OPTION:
                 c = getoptstr((getoptionstr_t *) p->userdata);
                 break;
       case FIELD_STRING:
                 c = getstr((getstr_t *) p->userdata);
                 break;     
       case FIELD_NUMBER:
                 c = getnum((getnumstr_t *) p->userdata);
                 break;                   
       default:
             return NULL;
   }
  if(!c)
    return(p->prev;
  else
    return p->next; 
}
--- End code ---

The list of field types might shrink before I am done but presently, I though they would be


--- Code: ---// These define the type of field a given screenfield_t UserData pointer is pointing to
#define FIELD_NONE    0    // No Field
#define FIELD_OPTION  1    // Select from a list of text based options
#define FIELD_STRING  2    // String field
#define FIELD_NUMBER  3    // Number Field
#define FIELD_DEGREE  4    // Degree Field, decimal, Maxiumum = 359.999
#define FIELD_DMS     5    // Degrees, Minutes, Seconds, Maximum =  359°59"59'
#define FIELD_ANYKEY  6    // Press any Key
#define FIELD_YESNO   7    // Yes or No?
#define FIELD_BOOL    8    // True or False?

--- End code ---

There was some fairly heavy thinking in this which I had been avoiding for a while so I am pleased that I now have  a plan of attack. I still have to do field types 4-8 but 7 and 8 are simply calls to field type 1.  4 and 5 are variants of  field type 3 so the heavy lifting is done...

Defining a numeric field looks something like this:


--- Code: ---     char  FNumStr[] PROGMEM = { "Number:" } ;             // Store the Prompt in Flash memory with the program code
     double fnum  = 121.23;                                // Define a variable to edit
     getnumstr_t num = {0 , 0, 7, 2, FNumStr, &fnum,};     // define the field data structure

--- End code ---

I think the code should be pretty compact. I debated whether to work with floats and doubles but decided that I could put up with the extra memory usage as working solely in integers would just get confusing and lead to some convoluted code. I figured once we get into dividing we will want to use fractions in the maths somewhere!

Give me another week and I might have some data entry stuff to show of for my efforts and finally I might start playing with stepper hardware again.
kwackers:
If you're using floats be careful of accumulating rounding errors.

I went with ints for mine but that was because I wanted the motor drive to occur inside an interrupt which means I can do other non time-dependent things in the mainloop (like updating the display and scanning the keyboard etc). To run inside interrupts at the sorts of rates that are realistically required meant efficient int code was needed. (For a 40mhz PIC18 at any rate).

I have some code for a six axis driver I wrote for a high end PIC (or arduino). Again I had to go with ints to get all six axis to drive at a decent speed (50khz I think was my target) and again using interrupts.
The G code interpreter I started writing for it was float based though, this ran in the main loop and kept a buffered list of data for the motor driver to consume.
I never got as far as writing the IO system for this because I was going to go with a graphical display (to see toolpaths etc) but the low end graphic displays I checked out were just too slow imo to be useful and the decent stuff cost as much as a low end PC!
RodW:
Thanks Kwackers, I am onto the problems around data types. I started off just dealing with inputting an numeric string and convert it on return to deal with this but then decided to pass numeric values. My original plan was to use a picture field for data entry as found in some higher end programming languages and look for a decimal point to decide if it was an int or a float. Then I decided the picture might as well be a format string for printf() but because the AVR does not support %f in format strings by default I had to scratch the idea. I can still do this based on the precision parameter required for the dtostrf() to do the same. Eg. If prec == 0, do some fancy footwork with pointers and return an int by reference otherwise return a float. For the input routines I then thought I could get away with casting values back to an int once the value was read. I will wait and see how it pans out.

As far as interrupts go, I have one available as the screen shield stole one of the interrupt ports  and the platform lets you to hook onto the timer interrupt which sounds more useful for this project. I have been careful not to write any blocking code ( eg, no calls to delay()).

I did do some tests with the stepper measuring the number of nanoseconds required to change the port state and measure the processing overhead and it was pretty amazing. I can't quite remember but it was something like 23 nanoseconds from memory but don't quote me until I fire up a PC instead of my iPad to look it up.

Anyway, I appreciate your input as it makes me think. While I was writing this, I realised that I could return either a long or a float by reference as they are both 4 bytes long! That might be pretty cool to enter both longs and floats with the same procedure! Casting a long back to an int would be much safer way to go.
Noitoen:
I was going to re invent the wheel by designing a dividing controller with a PIC18F452 micro controller and then I found this: http://www.worldofward.com/ Looks like an interesting project.
Navigation
Message Index
Next page
Previous page

Go to full version