Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 12 of 12

Thread: Quadrature encoder mode Teensy 4.1

  1. #1
    Junior Member
    Join Date
    Feb 2023
    Location
    Cudgee, Victoria Australia
    Posts
    7

    Quadrature encoder mode Teensy 4.1

    Hi all,
    Very new here.
    As a hobbyist stretching back to the 80's, I have dealt with EPROM erasers, ZIF sockets and lines of EPROMS. I have sat there gobsmacked at processors like Z80/NSC800, 8085 SDK from Intel, built a bunch of 8051 and Phillips automotive derivatives - PCB80C552 - plus dabbled with the 6809 and the Texas TMS320C325/6 and Motorola DSP56002 hosted in a PC ISA bus (old type).

    More recently (2009) have had my heart set on the Microchip PIC32 until I discovered the STM32 range, Cube-MX and Cube-IDE and then found the Teensy4.1 on a YouTube video. The guy raved about it.

    Now I have ordered several from this site, and I have my hands on one that I bought locally in AU to do some initial playing. Out of the box, it can toggle a pin at 10Mhz

    The development of embedded systems in 40 years has been amazing. The effort put into the tooling is stunning and so affordable (almost nothing).

    Teensy4.1 made me get into the Arduino IDE, and that's where I am now - beginning again.

    At last, the question.
    My question is related to using the quadrature encoder hardware in Teensy4.1. Many STM32 breeds have an 'encoder mode' which was deceptively easy to enable (in Cube-MX), and it worked - great fun, especially with the use of a 32-bit counter (TIM2 on the STM32F401 etc.). My search on the Arduino libraries under Teensy has a couple of quadrature encoder/decoder examples based on interrupts and phase detection logic. What I wanted to ask is where I can find some examples or documentation on setting up Teensy4.1 counter hardware in encoder mode, run-able from the Arduino IDE? It looks like there are up to 4 of these quadrature-capable counters.

    (I got there in the end)
    Steve

  2. #2
    Senior Member BriComp's Avatar
    Join Date
    Apr 2014
    Location
    Cheltenham, UK
    Posts
    1,244
    Have you seen this?

  3. #3
    Junior Member
    Join Date
    Feb 2023
    Location
    Cudgee, Victoria Australia
    Posts
    7
    Thanks, BriComp,
    I had a scan just now. My intuition is that each channel only has 16-bit timers for this function. In the early days, I fiddled with the 16-bit encoder mode counters on the STM32 series, trying to catch the overflow on each encoder rotation or 16-bit count, until I found the 32-bit timer (TIM2), which made it so easy.
    With 5 micron resolution and 32 bits on my (milling machine) linear scale, I could theoretically measure 21 kilometres of linear motion. With 16 bits, every 341 mm, I'd have to bump a counter variable to keep track of the count at the same time as keeping note of the direction. It was doable if untidy (I never really liked what I had done). A 32-bit timer makes the problem go away. Meantime, the Teensy4.1 processor is so attractive. What a time to be alive...
    Steve

  4. #4
    Senior Member BriComp's Avatar
    Join Date
    Apr 2014
    Location
    Cheltenham, UK
    Posts
    1,244
    Some more info for you @Clinker8 has fully instrumented his lathe using Teensy. You can see some of his entries starting in this stream.

    EDIT: I think @Luni has expanded his library (or can be expanded) to give int64 output.
    Last edited by BriComp; 03-14-2023 at 05:19 PM.

  5. #5
    Junior Member
    Join Date
    Feb 2023
    Location
    Cudgee, Victoria Australia
    Posts
    7
    Quote Originally Posted by BriComp View Post
    Some more info for you @Clinker8 has fully instrumented his lathe using Teensy. You can see some of his entries starting in this stream.

    EDIT: I think @Luni has expanded his library (or can be expanded) to give int64 output.
    Thanks for the link(s)! I'll have an in-depth read. There's a lot more goodness in there than just the encoder material, at a cursory glance.

    Kind regards,
    Steve

  6. #6
    Senior Member
    Join Date
    May 2022
    Posts
    298
    Quote Originally Posted by Steve_AU View Post
    Thanks for the link(s)! I'll have an in-depth read. There's a lot more goodness in there than just the encoder material, at a cursory glance.

    Kind regards,
    Steve
    Yes, I am using int64 counters for my electronic lead screw in my lathe. @luni was gracious (and talented enough) to modify his encoder library. I can report it is working well on two different lathes. I am also using the same library to read standard glass or magnetic scales for position. My ELS is not intended to be a CNC, but merely a means to eliminate gear changing. I do intend to upgrade its features, which is a work in progress. The Teensy has been a dream to work with, and this forum has been very helpful. If you have questions, don't hesitate to ask. Can't guarantee I will know the answer, but it is likely someone else might.

  7. #7
    Junior Member
    Join Date
    Feb 2023
    Location
    Cudgee, Victoria Australia
    Posts
    7
    Quote Originally Posted by clinker8 View Post
    Yes, I am using int64 counters for my electronic lead screw in my lathe. @luni was gracious (and talented enough) to modify his encoder library. I can report it is working well on two different lathes. I am also using the same library to read standard glass or magnetic scales for position. My ELS is not intended to be a CNC, but merely a means to eliminate gear changing. I do intend to upgrade its features, which is a work in progress. The Teensy has been a dream to work with, and this forum has been very helpful. If you have questions, don't hesitate to ask. Can't guarantee I will know the answer, but it is likely someone else might.
    Beautiful stuff! I'll be surely taking you up on that. I have to go away on work-related tasks for a few days, so I'll be offline for a bit.

    My first desire is to measure scales reliably (I have a VEVOR DRO and such, but that's just the start) |My real thinking is to converge on a CNC-type function (I had a go at this back in 1991 with far less capable tools). I have a G-Code parser and such that I have written for the STM32 series (well, it started life on a Z80). Lots of bits and pieces that need to be converged and too many rabbit holes that beckon.

    I can't help myself with the Teensy4.1. Just have to look at it and get the feel of it. So much fun stuff - e.g. link the indexing head with the milling machine for gear cutting and possibly have an electronic feed screw on the lathe for thread cutting as you have. Maybe built a router..... Way too many ideas, and not enough life to do them all.

    Steve

  8. #8
    Senior Member
    Join Date
    May 2022
    Posts
    298
    Quote Originally Posted by Steve_AU View Post
    Beautiful stuff! I'll be surely taking you up on that. I have to go away on work-related tasks for a few days, so I'll be offline for a bit.

    My first desire is to measure scales reliably (I have a VEVOR DRO and such, but that's just the start) |My real thinking is to converge on a CNC-type function (I had a go at this back in 1991 with far less capable tools). I have a G-Code parser and such that I have written for the STM32 series (well, it started life on a Z80). Lots of bits and pieces that need to be converged and too many rabbit holes that beckon.

    I can't help myself with the Teensy4.1. Just have to look at it and get the feel of it. So much fun stuff - e.g. link the indexing head with the milling machine for gear cutting and possibly have an electronic feed screw on the lathe for thread cutting as you have. Maybe built a router..... Way too many ideas, and not enough life to do them all.

    Steve
    Scales are easy. I was surprised at how well it seemed to work. A scale is merely a linear quadrature encoder. Just count pulses and multiply the count by your calibration factor. Oh, and check for double counts and stuff like that. About 30 LOC with lots of blank lines, for an axis. When you get back I can give you a simple example.

  9. #9
    Junior Member
    Join Date
    Feb 2023
    Location
    Cudgee, Victoria Australia
    Posts
    7
    Hi Clinker8,
    I'm back from the trip away and spending some time porting some of my code across the Teensy4.1. I wondered if it is possible to get access to the actual interrupt handler attached to Serial1?

    From what I can see, the interrupt and buffering of same, is hidden. Each call to serial1Event() to check on the availability of characters effectively polls the state of the buffer from the main loop. I want to fill my own circular buffer from the incoming interrupt as I did on the STM32. Is there any more information at that level that you know about?

    I do have a workable sequence running on Teensy4.1, but if there is too much idle time in the main loop, [like pulsing a led for 50mS], it doesn't work predictably.

    I'd like to emulate what I have done on the other processors if I can OR understand the FIFO/interrupt workings in the Arduino code much better.

    In my other code, the main loop can be blocked indefinitely, while the interrupt fills up the buffer and a 'buffer_count()' function which runs on each char interrupt de-asserts RTS, telling the host to stop at a defined char count. In this case on Teensy, if the main loop blocks, it doesn't work as I might hope.
    Stevo

  10. #10
    Junior Member
    Join Date
    Feb 2023
    Location
    Cudgee, Victoria Australia
    Posts
    7
    I decided to let Teensy manage RTS. That seems to have fixed the blocking problems and I'll just rearrange things to deal with it that way for now.
    Stevo

  11. #11
    Senior Member
    Join Date
    May 2022
    Posts
    298
    Quote Originally Posted by Steve_AU View Post
    Hi Clinker8,
    I'm back from the trip away and spending some time porting some of my code across the Teensy4.1. I wondered if it is possible to get access to the actual interrupt handler attached to Serial1?

    From what I can see, the interrupt and buffering of same, is hidden. Each call to serial1Event() to check on the availability of characters effectively polls the state of the buffer from the main loop. I want to fill my own circular buffer from the incoming interrupt as I did on the STM32. Is there any more information at that level that you know about?

    I do have a workable sequence running on Teensy4.1, but if there is too much idle time in the main loop, [like pulsing a led for 50mS], it doesn't work predictably.

    I'd like to emulate what I have done on the other processors if I can OR understand the FIFO/interrupt workings in the Arduino code much better.

    In my other code, the main loop can be blocked indefinitely, while the interrupt fills up the buffer and a 'buffer_count()' function which runs on each char interrupt de-asserts RTS, telling the host to stop at a defined char count. In this case on Teensy, if the main loop blocks, it doesn't work as I might hope.
    Stevo
    I'm afraid, I'm not following you. I'm directly counting encoder pulses, via the library, and not doing anything with Serial1. Since you haven't posted any code, both me and the rest of the forum can only guess at what you are doing. Pulsing a LED using the delay function is not good, you may know that. At the top of the page is a Rule, perhaps you missed it.

    Forum Rule: Always post complete source code & details to reproduce any issue!

    That being said, I have been guilty of not providing source code at times. Either the attachment is too large, or proprietary, or I don't have permission to post it. But I do try to post pertinent bits, and when I can, complete sections of a problem area. It could be something in your setup, or a misunderstanding of how something does work, but we won't know about it, unless you post the code. Generally speaking, using Serial.printx inside an interrupt is bad news. Sometimes you can get away with it - but most of the time it is the recipe for disaster... As for FIFOs, I will let other members comment, as they are far better coders than I am. I know why they are good, and I have designed systems with them, but nothing to do with a Teensy.

  12. #12
    Junior Member
    Join Date
    Feb 2023
    Location
    Cudgee, Victoria Australia
    Posts
    7
    Hi Clinker8,
    Sorry about the vagueness. I'm dealing with the porting of some already-written code. This is not about encoders or really about an 'issue'. Perhaps I should have begun another thread! It's more about understanding how Teensy deals with serial data incoming and the way Teensy Serial (UART) handlers are written. So it's not even about an issue particularly, it's about understanding the serial port buffering.

    The pertinent code is post-able, but that's not my worry, as I have it all working. The call to 'Serial1Event' now happens at the top of the loop. The blipping of the led is in the main loop, not in an interrupt. (Yes, I realise interrupts are to be exited ASAP!)

    My interest is how the serial port (UART) interrupt and FIFO buffering is being managed (out of sight to me).
    I'll post my code here anyway so you can see what is.

    The first lot is to just expose the loop and the call to serial1event() just to shine a light on that part.

    The rest is a port of the code as it stands so far, for context.

    What I would love to do is set out each module into a separate file. Can't see how to do that with Arduino IDE. (yet) VSCode and PlatformIO might be a better way.
    Kind regards,
    Stevo

    Code:
    //------------------------------------------------------------------------------
    // local module variables
    //------------------------------------------------------------------------------
    int LED = 13;  // pin 13 is the orange LED
    char block[256];
    char cmdln[256];
    //------------------------------------------------------------------------------
    // kick-off code
    //------------------------------------------------------------------------------
    void setup() {
      pinMode(LED, OUTPUT);
      Serial.begin(115200);
      Serial1.begin(115200, SERIAL_8N1);
      Serial1.setRX(0);
      Serial1.setTX(1);
      Serial1.attachRts(2);
      event_queue_init(); // setup an event queue
      machine_init();    // initliase the machine with 3 axes.
    }
    //------------------------------------------------------------------------------
    // main loop()
    //------------------------------------------------------------------------------
    void loop() {
      serial1Event();         // check for the arrival of any chars
      if (line_count) {       // if we have a new line of chars terminated by CRLF
        buff_read(cmdln);     // read them out of the cirular buffer into a null terminated linear char array
        parse_command(cmdln); // parse this into commands data and hash 
        blip_led(100);        // this emulates a time delay/wait on the machine function (100us)
      }
    }
    1200 more lines
    ...
    Code:
    #include<TeensyThreads.h>#include<errno.h>
    //----------------------------------------------------------------------------
    // message_32 defines
    //----------------------------------------------------------------------------
    #define MESSAGE_ERROR_DESC_LENGTH 32
    #define MAX_XML_TAG 16                  // 16 chars of XML tag (keep them short!!)
    #define COMMAND_LINE_LENGTH 256         // maximum value of a line including terminator chars
    #define UART_BUFFER_SIZE 1024           // max circular buffer
    #define CHAR_COUNT_LOWER_THRESHOLD 256  //
    #define CHAR_COUNT_UPPER_THRESHOLD 768  //
    // gc_parser defines
    #define MAX_ARGS 32
    #define COMMAND_LENGTH 256
    #define COMMENT_LENGTH 128
    #define ERROR_DESC_LENGTH 64
    // event_queue defines
    #define MAX_EVENT_QUEUE 64                                    // up to 64 events in a queue
    #define UPPER_EVENT_BUFFER_THRESHOLD 3* MAX_EVENT_QUEUE /4  // set 'full' above this threshold
    #define LOWER_EVENT_BUFFER_THRESHOLD MAX_EVENT_QUEUE /4      // reset 'full' below this threshold
    #define MAX_EVENT_PARAMS 3                                    // up to 3 integer parameters per event
    #define MAX_EVENT_ID 16                                       // 16 chars in an event id
    #define MAX_COEFFS 3
    #define MAX_WORLDS 16
    #define NAME_WIDTH 16
    #define GC_PER_BLOCK 4
    #define MC_PER_BLOCK 4
    
    #define MESSAGE_ERROR_DESC_LENGTH 32
    #define TRUE -1
    #define FALSE 0
    #define MAX_XML_TAG 16  // 16 chars of XML tag (keep them short!!)
    
    //---------------------------------------------------------------------------------------
    // cmd formats
    #define GET_MOVE 10
    #define GET_MODE_BITS 11
    #define GET_PID 14
    #define GET_ANALOG 15
    #define GET_PORT 17
    #define GET_ENCODER_POS 19
    #define GET_ENCODER_CNT 20
    //
    #define GET_FLOW_CONTROL_STATE 0
    #define GET_PING_REPLY 22
    #define GET_CAL_FACTORS 25
    #define GET_HASH 29
    //#define GET_SPI_1               30
    //#define GET_SPI_2               31
    #define GET_I_VALUE 32
    
    #define PING_SECRET 1234
    //
    #define GET_PROCESSOR_NAME 40    // The 'GET' prefix indicates a command that expects a reply (the host will wait up to 250mS)
    #define GET_PROGRAM_VERSION 41
    #define GET_MACHINE_NAME 42
    #define GET_MACHINE_POSITION_XYZ 43
    #define GET_MACHINE_POSITION_UVW 44
    #define GET_MACHINE_POSITION_ABC 45
    #define GET_MACHINE_STATE 46
    #define GET_MACHINE_ERRORS 47
    #define GET_GCODE_ERRORS 48
    #define GET_MESSAGE_ERRORS 49
    #define GET_UART_BUFFER_COUNT 50
    #define GET_EVENT_QUEUE_COUNT 51
    #define GET_UART_LINE_COUNT 52
    #define GET_EVENT_QUEUE_ITEM 53
    #define GET_EVENT_QUEUE_STATUS 54
    #define GET_XML_ERRORS 55
    #define GET_CONSOLE_ERRORS 56
    #define GET_EVENT_QUEUE_NEXT_ITEM 57
    #define GET_EXEC_CLEAR_EVENTS 58
    #define GET_EVENT_QUEUE_NEXT 59
    #define GET_SET_TIMER3_RATE 70
    
    #define PUT_RESET_XYZ 60
    #define PUT_RESET_ABC 61
    #define PUT_RESET_UVW 62
    //
    #define GET_EXEC_GCODE 6000
    //
    #define PUT_MODE_CLEAR 5000   // The 'PUT' prefix indicates a command that doesn't expect a reply (the host won't wait becuse it doesn't expect a reply)
    #define PUT_MODE_VERBOSE 5001
    #define PUT_MODE_PAUSED 5002
    #define PUT_MODE_DEBUG 5003
    #define PUT_MODE_SINGLE_STEP 5004
    #define PUT_MODE_LOG 5005
    #define PUT_MODE_RUN 5006
    #define PUT_MODE_STOP 5007
    #define PUT_MODE_SYSTEM_HALT 5008
    #define PUT_MODE_GLOBAL_OVERRIDE 5009
    #define PUT_MODE_TEST 5010
    #define PUT_CLEAR_GCODE_ERRORS 5011
    #define PUT_CLEAR_MACHINE_ERRORS 5012
    #define PUT_CLEAR_MESSAGE_ERRORS 5013
    #define PUT_TEST_FUNCTION 5014
    #define PUT_SET_TARE 5015
    #define PUT_RESET_TARE 5016
    #define PUT_MACHINE_FUNCTION 5017
    #define PUT_SYSTEM_RESET 5018
    #define PUT_EVENT_QUEUE_ITEM 5019
    #define PUT_EVENT_QUEUE_GCODE 5020
    
    //------------------------------------------------------------------------------
    // axis constants
    //------------------------------------------------------------------------------
    #define N_AXES 9
    #define N_ORIGINS 32
    #define N_TOOLS 24
    #define NAME_WIDTH 16
    //------------------------------------------------------------------------------
    // step ratios for microstepping
    //------------------------------------------------------------------------------
    #define STEP_BASE 200   // native stepper resolution = 200 steps per rev (1.8 degrees per step)
    #define STEP_400 400    // 400 steps per rev
    #define STEP_800 800    // ...
    #define STEP_1000 1000  // this probably plenty. (0.36 degrees per step)
    #define STEP_1600 1600  // ...
    #define STEP_2000 2000
    #define STEP_3200 3200
    #define STEP_4000 4000
    #define STEP_5000 5000
    #define STEP_6400 6400
    #define STEP_8000 8000
    #define STEP_10000 10000
    #define STEP_12800 12800
    #define STEP_20000 20000
    #define STEP_25000 25000
    //------------------------------------------------------------------------------
    // lathe axis constants
    //------------------------------------------------------------------------------
    #define DEFAULT_AXIS_CLASS ac_null
    #define DEFAULT_MACHINE_UNITS mu_millimeters
    #define DEFAULT_STEPS_REV STEP_400
    #define DEFAULT_LEAD_SCREW_PITCH 3.0
    #define DEFAULT_GEAR_RATIO 7.2
    //----------------------------------------------------------------------------
    // structs and typedefs
    //----------------------------------------------------------------------------
    typedef union {
      int bits;
      struct
      {
        unsigned float_error : 1;
        unsigned integer_error : 1;
        unsigned parameter_error : 1;
        unsigned unknown_xml_tag : 1;
        unsigned unknown_value : 1;
        unsigned parse_error : 1;
        unsigned invalid_char : 1;
        unsigned value_error : 1;
        unsigned missing_xml_tag : 1;
        unsigned missing_field : 1;
        unsigned unexpected_field : 1;
        unsigned unknown_xml_cmd : 1;
        unsigned missing_xml_msg : 1;
        unsigned missing_xml_cmd : 1;
        unsigned missing_xml_data : 1;
        unsigned missing_xml_hash : 1;
        unsigned hash_mismatch : 1;
      };
    } T_xml_errors;  // xml parser/format errors
    
    typedef union {
      int bits;
      struct
      {
        unsigned overrun : 1;
        unsigned parity : 1;
        unsigned idle : 1;
        unsigned noise : 1;
        unsigned line_too_long : 1;
      };
    } T_console_errors;  // console (UART) errors
    
    typedef union {
      int bits;
      struct
      {
        unsigned missing_block : 1;
        unsigned block_too_long : 1;
        unsigned unknown_gc_token : 1;
        unsigned bad_float_format : 1;
        unsigned bad_integer_format : 1;
        unsigned too_many_arguments : 1;
        unsigned missing_comment_brace : 1;
      };
    } T_gc_parse_errors;
    //------------------------------------------------------------------------------
    // parser context for gcode.
    //------------------------------------------------------------------------------
    typedef struct
    {
      T_gc_parse_errors gc_errors;          // error flags
      char last_error_desc[64];             // last error
      char comments[COMMENT_LENGTH];        // last comment
      int argc;                             // number of gcode tokens in gcode block.
      int g;                                // count of G fields
      int m;                                // count of M fields
      char *args[MAX_ARGS];                 // list of pointers to gcodes and values in gc_codeparams[].
      char gc_codeblock[COMMAND_LENGTH];    // incomimg gcode command line.
      char gc_codestrings[COMMAND_LENGTH];  // cleaned up and parsed gcode fields
      char gc_codeparams[COMMAND_LENGTH];   // full list of all commands and parameters in a block
    } T_gcparser;
    // enumerated list of event class(es).
    typedef enum { ec_null,
                   ec_move_axis,
                   ec_select_tool,
                   ec_adjust_parameter } T_event_class;
    // enumerated list of machine event(s)
    typedef enum { me_null,
                   me_move,
                   me_traverse,
                   me_select_tool,
                   me_test,
                   me_set_pid,
                   me_get_data,
                   me_exec_gcode } T_machine_events;
    // enumerated list of possible machine function mode(s)
    typedef enum { fm_null,
                   fm_paused,
                   fm_single_step,
                   fm_jog,
                   fm_manual,
                   fm_wait_for_tool } T_function_mode;
    //
    typedef union {
      int bits;
      struct
      {
        unsigned a_changed : 1;
        unsigned b_changed : 1;
        unsigned c_changed : 1;
        unsigned d_changed : 1;
        unsigned e_changed : 1;
        unsigned f_changed : 1;
        unsigned g_changed : 1;
        unsigned h_changed : 1;
        unsigned i_changed : 1;
        unsigned j_changed : 1;
        unsigned k_changed : 1;
        unsigned l_changed : 1;
        unsigned m_changed : 1;
        unsigned n_changed : 1;
        unsigned o_changed : 1;
        unsigned p_changed : 1;
        unsigned q_changed : 1;
        unsigned r_changed : 1;
        unsigned s_changed : 1;
        unsigned t_changed : 1;
        unsigned u_changed : 1;
        unsigned v_changed : 1;
        unsigned w_changed : 1;
        unsigned x_changed : 1;
        unsigned y_changed : 1;
        unsigned z_changed : 1;
      };
    } T_field_flags;
    //------------------------------------------------------------------------------
    // An 'event' is something synchronous that may be initiated by the system.
    // Typically, 'something' we want the controller to do in an ordered sequential way.
    // example: move an axis, attach a tool, adjust a parameter etc.
    //------------------------------------------------------------------------------
    typedef struct
    {
      int line_number;                //
      int gcode_param[GC_PER_BLOCK];  // active gcodes per block
      int mcode_param[MC_PER_BLOCK];  // active mcodes per block
      int tool_select;                // new tool
      float feed_rate;                //
      float speed;                    //
      int dwell_time;                 //
      float radius;                   //
      int coord_index;                // relative coordinates index
      int op_mode;                    // new mode
      float xyz[MAX_EVENT_PARAMS];    // 'x,y,z' stored here.
      float abc[MAX_EVENT_PARAMS];    // 'a,b,c' stored here.
      float uvw[MAX_EVENT_PARAMS];    // 'u,v,w' stored here.
      T_field_flags field_flags;
    } T_event;
    //------------------------------------------------------------------------------
    // booleans for setting the state of an 'event_queue'
    //------------------------------------------------------------------------------
    typedef union {
      int bits;
      struct
      {
        unsigned busy : 1;     // 'busy' means an event is underway and we have to wait until the event is ended.
        unsigned paused : 1;   // 'paused' means we are temporarily halting the whole process.
        unsigned fault : 1;    // 'fault' means the system has struck a significnt event that needs operator intervention.
        unsigned waiting : 1;  // 'waiting' for user input.(active prompt)
        unsigned ready : 1;    // 'ready' indicates all dependant fuctions have finished and ready for the next event (eg x,y,z axes - this is an AND function)
        unsigned full : 1;     // 'full' indicates that the queue_buffer is full - don't post any new event sequences.
      };
    } T_queue_state;
    //------------------------------------------------------------------------------
    // scale and offsets used for each move.
    //------------------------------------------------------------------------------
    typedef struct
    {
      float scale;  // y = mx + c  -  integer step calculation, plus offset
      float offset;
    } T_scalar_coeffs;  // these are used to calculate the number of steps required to move a calibrated distance (mm or inches)
    //------------------------------------------------------------------------------
    // 'T_event_queue' maintains a list of the events in an array buffer, say, 32 events long. (must be a power of 2)
    // 'head' points to the last event pushed (buffered & ready for processing, - it's the last event to be extracted from the queue).
    // 'tail' points to the first event pushed (and will be the first event in order, extracted from the queue).
    // 'count' calculates the number of events left in the queue.
    // 'busy' determines if an event is being run currently.
    // 'full' stops new events being posted to the queue.
    // How it works:
    // This structure uses a virtual method table concept plus an array[] of events, set into global memory.
    // This format enables functions (methods) with a dot '.' operator syntax.
    // Examples:
    // 1) event_queue.add(an_event);         // copies an event structure into an array of event structures.
    // 2) an_event = event_queue.next();     // retrieves a pointer to an event from the next one in the buffered array.
    // 3) event_count = event_queue.count(); // retrieves the current number of events in the queue.
    //------------------------------------------------------------------------------
    typedef struct
    {
      T_event events[MAX_EVENT_QUEUE];            // circular buffer of events in an array. (must be a power of 2)
      T_function_mode function_mode;              // e.g. fm_paused, fm_single_step, fm_jog etc
      T_queue_state state;                        // 'busy', 'paused', 'halted', 'full' etc.
      T_scalar_coeffs scalar_coeffs[MAX_COEFFS];  // array of coordinates used.
      int total;                                  //
      int head;                                   // 'head is incremented and the next event is posted to the head of the buffer.
      int tail;                                   // 'tail' is where the next event is taken from in the circular buffer.
      T_event *(*next)(void);                     // 'next' gets the next event from the list if one is available. returns 'null' otherwise.
      void (*add)(T_event *e);                    // 'add' appends a new event to the buffer unless buffer is (near)ly full.
      int (*count)(void);                         // 'count' calculates and returns the number of events in the (circular) buffer.
    } T_event_queue;
    //------------------------------------------------------------------------------
    // enumerated type of the kinds of axes we can have and how they are measured.
    // ac_null   no effect
    // ac dist   calibrated in distance values; mm or inch
    // ac_speed  calibrated in speed  - RPM
    // ac_angle  calibrated in angles. (degrees or radians)
    //------------------------------------------------------------------------------
    typedef enum { ac_null,
                   ac_dist,
                   ac_speed,
                   ac_angle } T_axis_class;
    //------------------------------------------------------------------------------
    typedef enum { mu_null,
                   mu_millimeters,
                   mu_inches } T_machine_units;
    //------------------------------------------------------------------------------
    typedef enum { mc_null,
                   mc_program_stop,
                   mc_optional_stop,
                   mc_spindle_on,
                   mc_spindle_off,
                   mc_tool_change,
                   mc_end_program } T_machine_m_codes;
    //------------------------------------------------------------------------------
    typedef struct
    {
      char name[NAME_WIDTH];
      float pt[3];  // three floats that can be aliased as xyz, abc, uvw.
    } T_origin_pt;
    //------------------------------------------------------------------------------
    typedef union {
      int bits;
      struct
      {
        unsigned use_ramp : 1;       //
        unsigned modal : 1;          //
        unsigned abs_mode : 1;       //
        unsigned use_tool_comp : 1;  //
        unsigned units_type : 2;     //
        unsigned changed : 1;        // whether the machine has changed any parameter.
        unsigned enabled : 1;        // global enabled for the machine to run.
      };
    } T_machine_state;
    //------------------------------------------------------------------------------
    typedef struct
    {
      float ramp_steps[3];
      float ramp_gradient[3];
      float ramp_distance[3];
      float upper_threshold[3];
      float lower_threshold[3];
    } T_ramp_envelope;
    //------------------------------------------------------------------------------
    typedef struct
    {
      float leadscrew_pitch;   // typically 3.0mm ballscrew
      float gear_ratio;        // typically 7.2:1 on the hercus lathe (timing belt ratio)
      float steps_rev;         // typically 1000 (base of 200 steps per rev)
      float distance_perstep;  // mm per step / inches per step.
      float offset_comp;       //
      float scale_comp;        //
    } T_axis_cal;              //
    //------------------------------------------------------------------------------
    // A 'T_axis' is a collection of structured data sent to each axis over SPI_1.
    typedef struct
    {
      char name[NAME_WIDTH];      //
      T_axis_class axis_class;    // type of axis ac_null, ac_dist, ac_speed, ac_angle, used to interpret T_axis data
      T_axis_cal axis_cal;        // how to calibrate this axis (using distance, speed, angle)
      float current_position;     // position measurement units
      int current_step;           // position in 'steps'
      float move_speed;           //
      float traverse_speed;       //
      float acceleration_factor;  //
    } T_axis;                     //
    //------------------------------------------------------------------------------
    
    typedef struct
    {
      float radius;
      float height;
      float xyz_offset[3];
      float xyz_comp[3];
    } T_tool_params;
    //------------------------------------------------------------------------------
    typedef struct
    {
      char name[NAME_WIDTH];
      T_tool_params params;
    } T_tool;
    //------------------------------------------------------------------------------
    typedef struct
    {
      char name[NAME_WIDTH];           //
      T_machine_state state;           // current machine state with boolean and bit fields
      T_ramp_envelope ramp_envelope;   // how to describe a ramp speed envelope
      T_axis axes[N_AXES];             // axis parameters
      T_tool tools[N_TOOLS];           // array of tools (with tool parameters)
      T_origin_pt origins[N_ORIGINS];  // array of relative origins - (offsets)
      int gcode;                       // current G code
      int mcode;                       // current M code
      int tool_index;                  //
      int origin_index;                //
      int dwell_time;                  //
      float spindle_speed;             //
      float surface_speed;             //
      float move_rate;                 //
      float traverse_rate;             //
      float feed_rate;                 //
      float xyz_position[3];           // 3d 'xyz' position
      float xyz_target[3];             //
      float abc_position[3];           // 3d 'abc' position
      float abc_target[3];             //
      float uvw_position[3];           // 3d 'uvw' position
      float uvw_target[3];             //
    } T_machine;                       // the defintion of the 'machine' with axes and a host of variables
    //------------------------------------------------------------------------------
    // instances of typedef-ed types
    //------------------------------------------------------------------------------
    T_console_errors console_errors;
    T_xml_errors xml_errors;
    T_gcparser gc_parser;
    T_event_queue event_queue;
    T_machine machine;
    //--------------------------------------------------------------------------------
    // module variables for message handling
    //--------------------------------------------------------------------------------
    int rx_ctr;                                 // keep track of the length of a message.
    unsigned char rx_buffer[UART_BUFFER_SIZE];  // circular buffer, usually 1024 or 2048 chars (power of 2)
    unsigned char rx_char;                      // latest character read from the uart
    int buffer_head = 0;                        // pointer to last charcter of the message (usually the null terminator).
    int buffer_tail = 0;                        // pointer to last charcter of the message (usually the null terminator).
    int line_count = 0;                         // count of 'received lines' - the number of null terminated messages in circular buffer
    int buffer_count = 0;                       // chars in circular buffer.
    //------------------------------------------------------------------------------
    // local module variables
    //------------------------------------------------------------------------------
    int LED = 13;  // pin 13 is the orange LED
    char block[256];
    char cmdln[256];
    //------------------------------------------------------------------------------
    // kick-off code
    //------------------------------------------------------------------------------
    void setup() {
      pinMode(LED, OUTPUT);
      Serial.begin(115200);
      Serial1.begin(115200, SERIAL_8N1);
      Serial1.setRX(0);
      Serial1.setTX(1);
      Serial1.attachRts(2);
      event_queue_init(); // setup an event queue
      machine_init();    // initliase the machine with 3 axes.
    }
    //------------------------------------------------------------------------------
    // main loop()
    //------------------------------------------------------------------------------
    void loop() {
      serial1Event();         // check for the arrival of any chars
      if (line_count) {       // if we have a new line of chars terminated by CRLF
        buff_read(cmdln);     // read them out of the cirular buffer into a null terminated linear char array
        parse_command(cmdln); // parse this into commands data and hash 
        blip_led(100);        // this emulates a time delay/wait on the machine function (100us)
      }
    }
    //--------------------------------------------------------------------------------
    // take the block of data in the xml markup and see what to do with it
    //--------------------------------------------------------------------------------
    char cmd[32];
    char data[256];
    char hash[32];
    void parse_command(char *commandline) {
      int command;
      strcpy_xml_string("cmd", commandline, cmd);
      strcpy_xml_string("data", commandline, data);
      strcpy_xml_string("hash", commandline, hash);
      command = atol(cmd);
      switch (command) {
        case GET_EXEC_GCODE:
          {
            unpack_gcode_block(data);
          }
          break;
        case GET_PING_REPLY:
          {
            host_reply(GET_PING_REPLY, "1234");
          }
          break;
        case GET_MACHINE_POSITION_XYZ:
          {
            host_reply(GET_MACHINE_POSITION_XYZ, "<xyz>1.01,2.03,4.04</xyz>");
          }
          break;
      }
    }
    //------------------------------------------------------------------------------
    // gen_hash() generates the 'unique' hash of an argument string.
    // Note: 
    // This is a very simple integrity check and, over time, may have collisions (duplicates) or repeats, if the string is long enough.
    // However, each hash is 'unique' to the current buffer and, very likely, any others in close time proximity.
    // It indicates the integrity of a particular message using a 16 bit binary number (65536 different values).
    //------------------------------------------------------------------------------
    unsigned int gen_hash(const char *cp) {
      unsigned long int uhash = 5381;  // seed the hash - although we use a 32 bit integer, we are only interested in the right most 16  bits
      while (*cp) {
        uhash = 33 * uhash ^ (unsigned char)*cp++;  // accumulate hash value of the string (this algorithm was found online and is plagiarised here).
        uhash &= 0x0000ffff;                        // limit result to 16 bits
      }
      return (uhash);  // return the 'hash value' of the argument string '*cp' as the low 16 bits of a 32 bit integer.
    }
    //------------------------------------------------------------------------------
    // host_reply() appropriately formats a reply to the hosting PC.
    // Two argument fields are supplied.
    // a) <cmd>3067</cmd> an integer value. 'cmd' mirrors the sent command constant, to which we are replying.
    // b) <data>345452</data>, a string value. 'data' is the replied data value as a string.
    // The xml tag 'data' may contain a parseable string with separators. Example: <data>f1,f2,f3,f4</data>
    // This procedure concatenates a 'hash' value as an itegrity check. Example: <hash>30778</hash>
    //------------------------------------------------------------------------------
    char msgbuff[384];  // these are global variables. If there are too many large local variables, we risk
    char hashbuff[64];  // running out of stack cpace. i.e. no 'malloc()', just statically allocated globals.
    void host_reply(int cmd, const char *data) {
      sprintf(msgbuff, "<msg><cmd>%d</cmd><data>%s</data></msg>", cmd, data);  // format the reply message
      sprintf(hashbuff, "<hash>%d</hash>", gen_hash(msgbuff));                 // take its 'hash'
      strcat(msgbuff, hashbuff);                                               // append the hash value to the message
      Serial1.println(msgbuff);                                                // output the message with a 'LF + CR' pair
    }
    //------------------------------------------------------------------------------
    // checking and reading the serial event data attached to serial1
    //------------------------------------------------------------------------------
    void serial1Event() {
      char rx_char;
      while (Serial1.available()) {
        rx_char = (char)Serial1.read();
        if (isgraph(rx_char) || (rx_char == 32) || (rx_char == 10))                  // throw away 'funny' characters i.e. only allow printable chars or a new-line.
        {                                                                            //
          rx_buffer[buffer_head++] = rx_char;                                        // store rx_char into the circular buffer and increment the buffer head
          buffer_head &= (UART_BUFFER_SIZE - 1);                                     // perform 'rollover' arithmetic on the buffer head index
          if (++rx_ctr > COMMAND_LINE_LENGTH - 4) console_errors.line_too_long = 1;  // note if the command length is longer than 254 chars
          if (rx_char == '\n')                                                       // is the rx_char a new line? end of frame....
          {                                                                          //
            buffer_head--;                                                           // prepare to overwrite the LF char with a null.
            buffer_head &= (UART_BUFFER_SIZE - 1);                                   // perform circular 'rollover' arithemetic on the buffer_head index
            rx_buffer[buffer_head] = '\0';                                           // overwrite the LF char with a null
            rx_ctr = 0;                                                              // zero the rx_ctr ( the received string length)
            line_count++;                                                            // signal that there is another command in the buffer
          }                                                                          //
          buffer_count = input_buffer_charcount();                                   // input to manage buffer handshake
                                                                                     //      if (buffer_count > CHAR_COUNT_UPPER_THRESHOLD) RTS_OFF;               // disable communication if buffer is "near full".
        }
      }
    }
    //------------------------------------------------------------------------------
    // input_buffer_charcount()
    // Returns the number of characters in the circular receive buffer.
    //------------------------------------------------------------------------------
    unsigned int input_buffer_charcount(void) {
      unsigned int char_count;
      if (buffer_head >= buffer_tail) char_count = buffer_head - buffer_tail;
      else char_count = (UART_BUFFER_SIZE - buffer_tail) + buffer_head;
      return (char_count);
    }
    //------------------------------------------------------------------------------
    // buff_read()....
    // :Copies the source strs from the input circular buffer to the target linear buffer (the *s argument).
    // :Expects 'null' terminated strings in the rx_buffer circular buffer.
    // :Then cycles around the buffer to copy out a string until a null is found.
    // :Also returns a pointer to the start of the source string.
    //------------------------------------------------------------------------------
    char *buff_read(char *s) {
      register char *c = s;                     // make a local copy of the target pointer.
      while (rx_buffer[buffer_tail])            // examine the the string until 'null' is found
      {                                         // register pointer 'c' points to the far string.
        *c++ = rx_buffer[buffer_tail++];        //
        buffer_tail &= (UART_BUFFER_SIZE - 1);  // RULE: for this to work, UART_BUFFER_SIZE must be an integral power of 2
      }                                         // eg, 256, 512, 1024 etc.otherwise you need to do some pointer arithmetic
      *c = '\0';                                // Null terminate the copied string just in case.
      if (line_count) line_count--;             // if we have a non zero line_count, decrement the count.
      buffer_count = input_buffer_charcount();
      // if (buffer_count < CHAR_COUNT_LOWER_THRESHOLD) RTS_ON;
      return (s);  // return the pointer to the string, as per standard str() functions
    }
    //------------------------------------------------------------------------------
    // blip_led()
    // :holds the led on pin 13 'ON' for 'duration' microseconds.
    //------------------------------------------------------------------------------
    void blip_led(int duration) {
      digitalWrite(LED, HIGH);
      delayMicroseconds(duration);
      digitalWrite(LED, LOW);
    }
    //------------------------------------------------------------------------------
    // convert_string()
    // : copies a C++ string into a normal char[] array and null terminates it.
    //------------------------------------------------------------------------------
    void convert_string(String inp, char *target) {
      int i;
      int len = inp.length();
      for (i = 0; i < len; i++) {
        *target++ = inp[i];
      }
      *target = '\0';
    }
    //------------------------------------------------------------------------------
    // Non invasive version of 'get_xml_string()'
    // strcpy_xml_string copies a null terminated substring that exists between the outer xml tags
    // e.g. // from "<xmltag>this string will be returned</xmltag>"" it returns "this string will be returned" as a null terminated string.
    // This function does not affect the source string.
    // The tag or token, can be a string of any printable alphabetical ascii no more than 16 characters long.
    // If the "<tag>"" doesn't exist or is not bounded by a complementary "</tag>", the function returns a '\0' delimiter to *tgt pointer of the target string
    // TO DO: Limit the max number of chars allowrd to be moved (like strncpy).
    //------------------------------------------------------------------------------
    void strcpy_xml_string(const char *tag, char *src, char *tgt) {
      char start_tag[MAX_XML_TAG + 2];  // local storage
      char end_tag[MAX_XML_TAG + 3];    // for tag braces "<>" and "</>" plus the tag string itself
      char *st;                         // pointer to start tag <>
      char *et;                         // pointer to end tag   </>
      char *xml_body_start;             // pointer to start of xml body
      char *xml_body_end;               // pointer to end of xml body
      st = start_tag;                   // assign pointers to buffers
      et = end_tag;                     //
      strcpy(st, "<");                  // construct both the start and end tags
      strcat(st, tag);
      strcat(st, ">");
      strcpy(et, "</");
      strcat(et, tag);
      strcat(et, ">");
      if ((strstr(src, st) != NULL) && (strstr(src, et) != NULL))  // verify that both tags are present in the arg *src string.
      {
        xml_body_start = strstr(src, st) + strlen(st);  // point to xml body start.
        xml_body_end = strstr(xml_body_start, et);      // point to xml body end.
        while (xml_body_start != xml_body_end)          // perform a 'strcpy' on the partial substring
        {
          *tgt++ = *xml_body_start++;
        }
      }
      *tgt = '\0';  // always set NULL at the end. If we fail to find a string, it sets the first element of 'tgt[0]' to NULL.
    }
    //------------------------------------------------------------------------------
    // GCode parser section.
    //------------------------------------------------------------------------------
    void unpack_gcode_block(char *s) {
      int i;
      T_event event;
      strncpy(gc_parser.gc_codeblock, s, 256);
      extract_gcode_comments(gc_parser.gc_codeblock, gc_parser.comments);
      strip_white_space(gc_parser.gc_codestrings, gc_parser.gc_codeblock);
      extract_gcode_commands(&gc_parser);
      clear_event(&event);
      gc_parser.gc_errors.bits = 0;
      gc_parser.g = 0;                          // G command count set to 0. Note that a maximum of 4 G values can be set in one gcode block.
      gc_parser.m = 0;                          // M command count set to 0. Note that a maximum of 4 M values can be set in one gcode block.
      strcpy(gc_parser.last_error_desc, "\0");  // put a null at the first element of the error description
    
      for (i = 0; i < gc_parser.argc; i++) {
        eval_parameter_string(gc_parser.args[i], &event);  // build event parameters by rippling through the gcode block
      }
      if (gc_parser.gc_errors.bits == 0) event_queue.add(&event);  // if there are no errors, add the event to the event_queue
    }
    //------------------------------------------------------------------------------
    // Evaluates each parameter in a GCode block
    //------------------------------------------------------------------------------
    void eval_parameter_string(char *s, T_event *evt) {
      char *c;
      c = s;
      while (*c) {
        if (isalpha(*c)) {
          switch (*c) {
            case 'A':
              {
                evt->abc[0] = safe_float(c + 1);
                evt->field_flags.a_changed = 1;
              }
              break;
            case 'B':
              {
                evt->abc[1] = safe_float(c + 1);
                evt->field_flags.b_changed = 1;
              }
              break;
            case 'C':
              {
                evt->abc[2] = safe_float(c + 1);
                evt->field_flags.c_changed = 1;
              }
              break;
            case 'D':
              {
                evt->field_flags.d_changed = 1;
              }
              break;
            case 'E':
              {
                evt->field_flags.e_changed = 1;
              }
              break;
            case 'F':
              {
                evt->feed_rate = safe_float(c + 1);
                evt->field_flags.f_changed = 1;
              }
              break;
            case 'G':
              {
                evt->gcode_param[gc_parser.g++] = safe_int(c + 1);
                evt->field_flags.g_changed = 1;
              }
              break;
            case 'H':
              {
                evt->field_flags.h_changed = 1;
              }
              break;
            case 'I':
              {
                evt->field_flags.i_changed = 1;
              }
              break;
            case 'J':
              {
                evt->field_flags.j_changed = 1;
              }
              break;
            case 'K':
              {
                evt->field_flags.k_changed = 1;
              }
              break;
            case 'L':
              {
                evt->field_flags.l_changed = 1;
              }
              break;
            case 'M':
              {
                evt->mcode_param[gc_parser.m++] = safe_int(c + 1);
                evt->field_flags.m_changed = 1;
              }
              break;
            case 'N':
              {
                evt->line_number = safe_int(c + 1);
                evt->field_flags.n_changed = 1;
              }
              break;
            case 'O':
              {
                evt->field_flags.o_changed = 1;
              }
              break;
            case 'P':
              {
                evt->dwell_time = safe_int(c + 1);
                evt->field_flags.p_changed = 1;
              }
              break;
            case 'Q':
              {
                evt->field_flags.q_changed = 1;
              }
              break;
            case 'R':
              {
                evt->radius = safe_float(c + 1);
                evt->field_flags.r_changed = 1;
              }
              break;
            case 'S':
              {
                evt->speed = safe_float(c + 1);
                evt->field_flags.s_changed = 1;
              }
              break;
            case 'T':
              {
                evt->tool_select = safe_int(c + 1);
                evt->field_flags.t_changed = 1;
              }
              break;
            case 'U':
              {
                evt->uvw[0] = safe_float(c + 1);
                evt->field_flags.u_changed = 1;
              }
              break;
            case 'V':
              {
                evt->uvw[1] = safe_float(c + 1);
                evt->field_flags.v_changed = 1;
              }
              break;
            case 'W':
              {
                evt->uvw[2] = safe_float(c + 1);
                evt->field_flags.w_changed = 1;
              }
              break;
            case 'X':
              {
                evt->xyz[0] = safe_float(c + 1);
                evt->field_flags.x_changed = 1;
              }
              break;
            case 'Y':
              {
                evt->xyz[1] = safe_float(c + 1);
                evt->field_flags.y_changed = 1;
              }
              break;
            case 'Z':
              {
                evt->xyz[2] = safe_float(c + 1);
                evt->field_flags.z_changed = 1;
              }
              break;
            default:
              {
                gc_parser.gc_errors.unknown_gc_token = 1;
                gc_strerror();
              };  // unknown gc token
          }
        }
        c++;
      }
    }
    //------------------------------------------------------------------------------
    // Comment signifiers "%" or "//"  - all chars to the right of the token are treated as a comment
    // ...% This is a comment.
    // ...// This is a comment.
    // comment specifier "(" and ")" the field in between () is a comment
    // ...(This is a comment).
    // comment specifier "{" and "}" the field in between {} is a comment
    // ...{This is a comment}.
    //------------------------------------------------------------------------------
    int extract_gcode_comments(char *source, char *target) {
      char *s;
      char *t;
      char *n;
      char *a;
      char *b;
    
      s = source;           // make a local copy of the source pointer
      t = target;           // make a local copy of the target pointer
      a = strstr(s, "//");  // test to see if the '//' token symbol is present in the source
      if (a) {
        n = a;
        strncpy(t, a + 2, COMMENT_LENGTH);  // copy the comment contents out of the source string. Skip the comment symbol "%" or "//"
        *(n) = '\0';                        // overwrite the / symbol with a null (0) in the source string (*n points to the first elementof the token in the source string)
        return (-1);
      }
      a = strstr(s, "%");  // test to see if the '%' token symbol is present in the source
      if (a) {
        n = a;                              // make a copy of the pointer to the comment symbol
        strncpy(t, a + 1, COMMENT_LENGTH);  // copy the comment contents out of the source string. Skip the comment symbol "%" or "//"
        *(n) = '\0';                        // overwrite the % symbol with a null (0) in the source string (*n points to the first elementof the token in the source string)
        return (-1);
      }
      a = strstr(s, "(");  // test to see if the '(' token symbol is present in the source
      if (a) {
        b = strstr(s, ")");  // test to see if ')' is present
        if (b) {
          *(b) = '\0';  // replace ')' with '\0' (null) to allow comment copy within (------)
          strncpy(t, a + 1, COMMENT_LENGTH);
          *(a) = '\0';  // replace '(' with '\0' (null) to truncate comment out of source string.
          return (-1);
        }
      }
      a = strstr(s, "{");  // test to see if the '{' token symbol is present in the source
      if (a) {
        b = strstr(s, "}");  // test to see if '}' is present
        if (b) {
          *(b) = '\0';  // replace '}' with '\0' (null) to allow comment copy within {------}
          strncpy(t, a + 1, COMMENT_LENGTH);
          *(a) = '\0';  // replace '{' with '\0' (null) to truncate comment out of source string.
          return (-1);
        }
      }
      return (0);
    }
    //------------------------------------------------------------------------------
    // A GCode block starts as a null terminated string and consists of a list of
    // letters (A..Z) as commands, with numeric value arguments associated with each letter.
    // As an example "X4.2".  Here the variable 'X' has a value of '4.2' in a decimal format.
    // This function copies a source string into target string and inserts null
    // terminators after each parameter (just befor the next alphabetic character).
    // Gcode parameters are now in the form of shorter strings "X10.0"  "M00"  "G01"  "Z34.9"
    // Each alphabetic value and associated argument value have a separator of 'null' terminating them.
    // Summary:
    // In "extract_gcode_commands()" the source GCode block is transformed into an array
    // of (sub)strings constructed from the original supplied string.
    // Because of the way the GC block is now formatted, parsing each block element is simplified
    // by examining the first character, then selecting the right command based on the alphabetic
    // character, incrementing the char pointer to point to the argument, then converting the argument value.
    // Most arrguments are in decimal format, however the G, M, T, F values are integers.
    //------------------------------------------------------------------------------
    int extract_gcode_commands(T_gcparser *gcp) {
      int f, i;
      char *t;
      char *s;
      s = gcp->gc_codestrings;  // a block of gcode
      t = gcp->gc_codeparams;   // an array of strings, each a gcode command within the block
      i = 0;
      f = 0;
      while (*s)  // start iterating through source string
      {
        if (isalpha(*s))       // if we find an alpha char
        {                      // insert a null before it to terminate a preceeding field.
          if (f) *t++ = 0;     // (but only after the first time through...)
          f = 1;               // flip the first time flag...
          gcp->args[i++] = t;  // add each pointer in an array[] (pointers to strings)
        }
        *t++ = toupper(*s++);  // otherwise (by default) copy source char into dest; (also in uppercase).
      }
      *(t) = '\0';    // make sure to null terminate the last field.
      gcp->argc = i;  // report the number of parameter fields in the block
      return (0);
    }
    //------------------------------------------------------------------------------
    // "strip_white_space"
    // Excludes controls chars or white space from source string, as it copies source to target.
    // Why? Because we need to have each command and its arguments running together with no gaps or separators.
    // e.g. 'X 1.2 G 01 M  6' becomes 'X1.2G01M6'
    //------------------------------------------------------------------------------
    void strip_white_space(char *target_string, char *source_string) {
      char *s;
      char *t;
      s = source_string;
      t = target_string;
      if (strlen(s) == 0) *t = '\0';
      while (*s) {
        while (isvalidchar(*s) == 0) s++;  // exclude spaces or other specific chars
        *t++ = *s++;
      }
      *t = '\0';
    }
    //------------------------------------------------------------------------------
    int isvalidchar(char c)  // is the argument char in a list of valid chars?
    {
      if (isspace(c)) return (false);
      else return (true);
    }
    //------------------------------------------------------------------------------
    void gc_strerror() {
      if (gc_parser.gc_errors.missing_block) { snprintf(gc_parser.last_error_desc, ERROR_DESC_LENGTH, "missing gc block on line %s", gc_parser.args[0]); }
      if (gc_parser.gc_errors.block_too_long) { snprintf(gc_parser.last_error_desc, ERROR_DESC_LENGTH, "block too long on line %s", gc_parser.args[0]); }
      if (gc_parser.gc_errors.unknown_gc_token) { snprintf(gc_parser.last_error_desc, ERROR_DESC_LENGTH, "unknown gc token on line %s", gc_parser.args[0]); }
      if (gc_parser.gc_errors.bad_float_format) { snprintf(gc_parser.last_error_desc, ERROR_DESC_LENGTH, "bad float format on line %s", gc_parser.args[0]); }
      if (gc_parser.gc_errors.bad_integer_format) { snprintf(gc_parser.last_error_desc, ERROR_DESC_LENGTH, "bad integer format on line %s", gc_parser.args[0]); }
      if (gc_parser.gc_errors.too_many_arguments) { snprintf(gc_parser.last_error_desc, ERROR_DESC_LENGTH, "too many arguments on line %s", gc_parser.args[0]); }
      if (gc_parser.gc_errors.missing_comment_brace) { snprintf(gc_parser.last_error_desc, ERROR_DESC_LENGTH, "missing comment brace on line %s", gc_parser.args[0]); }
    }
    //------------------------------------------------------------------------------
    // converts string to a integer value with error detection.
    //------------------------------------------------------------------------------
    int safe_int(char *c) {
      char *end;
      int v = 0;
      v = (int)strtol(c, &end, 10);
      if (*end != '\0' || errno != 0) {
        gc_parser.gc_errors.bad_integer_format = 1;
        gc_strerror();  // convert error to string
        errno = 0;
      }
      return (v);
    }
    //------------------------------------------------------------------------------
    // converts string to a float value with error detection.
    //------------------------------------------------------------------------------
    float safe_float(char *c) {
      char *end;
      float f;
      f = (float)strtod(c, &end);
      if (*end != '\0' || errno != 0) {
        gc_parser.gc_errors.bad_float_format = 1;
        gc_strerror();  // convert gcode error to string.
        errno = 0;
      }
      return (f);
    }
    //------------------------------------------------------------------------------
    // Tests to see if there are characters in the string that would support a float format.
    //------------------------------------------------------------------------------
    int isfloatstr(char *s) {
      register char *c;
      c = s;
      while (*c) {
        if ((isdigit(*c)) || (*c == '.') || (*c == '+') || (*c == '-') || (*c == ' ') || (*c == 'E') || (*c == 'e')) c++;
        else return (false);
      }
      return (true);
    }
    //------------------------------------------------------------------------------
    void event_queue_init(void) {
      int i;
      event_queue.state.busy = 0;
      event_queue.state.paused = 0;
      event_queue.state.fault = 0;
      event_queue.state.full = 0;
      event_queue.head = 0;
      event_queue.tail = 0;
      event_queue.total = 0;
      event_queue.function_mode = fm_null;
      event_queue.count = &get_event_count;
      event_queue.add = &add_event;
      event_queue.next = &get_next;
      for (i = 0; i < MAX_EVENT_QUEUE; i++) {
        clear_event(&event_queue.events[i]);
      }
    }
    //------------------------------------------------------------------------------
    void clear_event(T_event *evt) {
      int k;
      evt->line_number = -1;
      evt->gcode_param[0] = -1;
      evt->gcode_param[1] = -1;
      evt->gcode_param[2] = -1;
      evt->gcode_param[3] = -1;
      evt->mcode_param[0] = -1;
      evt->mcode_param[1] = -1;
      evt->mcode_param[2] = -1;
      evt->mcode_param[3] = -1;
      evt->coord_index = -1;
      evt->dwell_time = 0;
      evt->feed_rate = 0.0;
      evt->speed = 0.0;
      evt->op_mode = -1;
      evt->tool_select = -1;
      evt->field_flags.bits = 0;  // clear all the bits asociated with fields passed in a gcode block
      for (k = 0; k < MAX_EVENT_PARAMS; k++) {
        evt->xyz[k] = 0.0;
        evt->abc[k] = 0.0;
        evt->uvw[k] = 0.0;
      }
    }
    //------------------------------------------------------------------------------
    // *get_next(); method returns a pointer to the next available 'T_event' in 'event_queue.events[]' buffer.
    // At reset, tail = 0 is used to return the first 'next'.
    // After incrementing the tail pointer we wrap the array boundary by anding it with (MAX_EVENT_QUEUE-1).
    // MAX_EVENT_QUEUE must be a power of 2 for this to work. 8,16,32,64,128 etc.
    // Note: currently it's 64 (1/06/2022)
    // Syntax: newevent = event_queue.next();
    // Side Effect: Resets the 'full' flag of the event queue if the event count is below a lower threshold.
    // i.e. the buffer WILL accept new events.
    //------------------------------------------------------------------------------
    T_event *get_next(void) {
      T_event *p;
      if (event_queue.count() > 0) {
        p = &event_queue.events[event_queue.tail];
        event_queue.tail++;
        event_queue.tail &= (MAX_EVENT_QUEUE - 1);
        if (event_queue.count() < LOWER_EVENT_BUFFER_THRESHOLD) event_queue.state.full = 0;
      } else p = NULL;
      return (p);
    }
    //------------------------------------------------------------------------------
    void copy_event_to_machine(T_event *evt) {
    }
    //------------------------------------------------------------------------------
    void copy_next_event_to_machine(void) {
      T_event *e;
      if ((event_queue.state.paused == 0) && (event_queue.state.busy == 0) && (event_queue.state.fault == 0))  // copy the next event to the machine if 'paused' is false
      {
        e = event_queue.next();  // if next() is skipped, the buffer keeps the last event in the queue
        if (e != NULL)           // if there is an event in the queue, event 'e' will not be NULL
        {
          if (e->field_flags.x_changed) machine.xyz_position[0] = e->xyz[0];  // only update the values that have changed or are modal.
          if (e->field_flags.y_changed) machine.xyz_position[1] = e->xyz[1];
          if (e->field_flags.z_changed) machine.xyz_position[2] = e->xyz[2];
          machine.state.changed = 1;
        }
      }
    }
    //------------------------------------------------------------------------------
    // pushes a T_event onto the event_queue circular buffer.
    // no returned value.
    // Intially the head pointer will be 0.
    // The first push will be to event_queue.events[0].
    // After setting up the event content at [0], the head pointer is incremented to 1 ready for the
    // next store operation.
    // The head pointer wraps around the array boundary by anding with (MAX_EVENT_QUEUE-1).
    // MAX_EVENT_QUEUE must be a power of 2 for this to work. 8,16,32,64,128 etc.
    // syntax: event_queue.add(T_event *new_event);
    // side effect: Sets the 'full' flag of the event queue if the event_queue.count() is above an upper threshold.
    // i.e. the buffer will NOT accept new events.
    //------------------------------------------------------------------------------
    void add_event(T_event *evt) {
      int i;
      event_queue.events[event_queue.head].field_flags.bits = evt->field_flags.bits;
      event_queue.events[event_queue.head].line_number = evt->line_number;
      event_queue.events[event_queue.head].gcode_param[0] = evt->gcode_param[0];
      event_queue.events[event_queue.head].gcode_param[1] = evt->gcode_param[1];
      event_queue.events[event_queue.head].gcode_param[2] = evt->gcode_param[2];
      event_queue.events[event_queue.head].gcode_param[3] = evt->gcode_param[3];
      event_queue.events[event_queue.head].mcode_param[0] = evt->mcode_param[0];
      event_queue.events[event_queue.head].mcode_param[1] = evt->mcode_param[1];
      event_queue.events[event_queue.head].mcode_param[2] = evt->mcode_param[2];
      event_queue.events[event_queue.head].mcode_param[3] = evt->mcode_param[3];
      event_queue.events[event_queue.head].tool_select = evt->tool_select;
      event_queue.events[event_queue.head].feed_rate = evt->feed_rate;
      event_queue.events[event_queue.head].speed = evt->speed;
      event_queue.events[event_queue.head].radius = evt->radius;
      event_queue.events[event_queue.head].dwell_time = evt->dwell_time;
      event_queue.events[event_queue.head].coord_index = evt->coord_index;
      for (i = 0; i < MAX_EVENT_PARAMS; i++) {
        event_queue.events[event_queue.head].xyz[i] = evt->xyz[i];
        event_queue.events[event_queue.head].abc[i] = evt->abc[i];
        event_queue.events[event_queue.head].uvw[i] = evt->uvw[i];
      }
      event_queue.head++;
      event_queue.head &= (MAX_EVENT_QUEUE - 1);
      if (event_queue.count() > UPPER_EVENT_BUFFER_THRESHOLD) event_queue.state.full = 1;
    }
    //------------------------------------------------------------------------------
    // get_event_count calculates the number of events in the circular queue buffer.
    // side effects: none
    // syntax:  some_value = event_queue.count();
    //------------------------------------------------------------------------------
    int get_event_count(void) {
      unsigned int event_count;
      if (event_queue.head >= event_queue.tail) event_count = event_queue.head - event_queue.tail;
      else event_count = (MAX_EVENT_QUEUE - event_queue.tail) + event_queue.head;
      return (event_count);
    }
    //------------------------------------------------------------------------------
    //
    //------------------------------------------------------------------------------
    void machine_init(void) {
      int i;
      machine.state.changed = 0;
      machine.state.enabled = 0;
      for (i = 0; i < N_AXES; i++) {
        sprintf(machine.axes[i].name, "axis#%d", i);
        machine.axes[i].axis_cal.offset_comp = 0.0;
        machine.axes[i].axis_cal.scale_comp = 1.0;
        machine.axes[i].axis_cal.gear_ratio = DEFAULT_GEAR_RATIO;
        machine.axes[i].axis_cal.leadscrew_pitch = DEFAULT_LEAD_SCREW_PITCH;
        machine.axes[i].axis_cal.steps_rev = DEFAULT_STEPS_REV;
        machine.axes[i].axis_class = DEFAULT_AXIS_CLASS;
        machine.axes[i].axis_cal.distance_perstep = 0.0;
        machine.axes[i].acceleration_factor = 1.0;
      }
    
      for (i = 0; i < N_AXES; i++) {
        // machine.ramp_envelope.lower_threshold[i]  = 0.0;   // each axis has an associated ramp calculation structure
        // machine.ramp_envelope.upper_threshold[i]  = 0.0;
        // machine.ramp_envelope.ramp_distance[i]    = 0.0;
        // machine.ramp_envelope.ramp_gradient[i]    = 0.0;
        // machine.ramp_envelope.ramp_steps[i]       = 0.0;
      }
    
      for (i = 0; i < N_TOOLS; i++)  // initialize multiple tools
      {
        sprintf(machine.tools[i].name, "tool#%d", i);
        machine.tools[i].params.xyz_comp[0] = 0.0;
        machine.tools[i].params.xyz_comp[1] = 0.0;
        machine.tools[i].params.xyz_comp[2] = 0.0;
        machine.tools[i].params.xyz_offset[0] = 0.0;
        machine.tools[i].params.xyz_offset[1] = 0.0;
        machine.tools[i].params.xyz_offset[2] = 0.0;
        machine.tools[i].params.radius = 0.0;
        machine.tools[i].params.height = 0.0;
      }
      // initialize multiple origins.
      // These are 'offsets' relative to the absolute coordinates of each set of axes
      // They will give relative coordinates in each case.
      for (i = 0; i < N_ORIGINS; i++)  // initialize multiple origins. thse are offsets relative to the absolute coordinates
      {
        sprintf(machine.origins[i].name, "org#%d", i);
        machine.origins[i].pt[0] = 0.0;
        machine.origins[i].pt[1] = 0.0;
        machine.origins[i].pt[2] = 0.0;
      }
    
      machine.move_rate = 0.0;  // initialize all the operating parameters of the machine.
      machine.traverse_rate = 0.0;
      machine.feed_rate = 0.0;
      machine.spindle_speed = 0.0;
      machine.surface_speed = 0.0;
      machine.tool_index = 0;
      machine.origin_index = 0;
      machine.state.use_ramp = 0;
      machine.state.modal = 0;
      machine.state.abs_mode = 0;
      machine.state.use_tool_comp = 0;
      machine.state.abs_mode = 0;
      machine.state.units_type = DEFAULT_MACHINE_UNITS;
    
      machine.xyz_position[0] = 0.0;  // initialize all the absolute positions of each axis
      machine.xyz_position[1] = 0.0;
      machine.xyz_position[2] = 0.0;
      machine.xyz_target[0] = 0.0;
      machine.xyz_target[1] = 0.0;
      machine.xyz_target[2] = 0.0;
      machine.abc_position[0] = 0.0;
      machine.abc_position[1] = 0.0;
      machine.abc_position[2] = 0.0;
      machine.abc_target[0] = 0.0;
      machine.abc_target[1] = 0.0;
      machine.abc_target[2] = 0.0;
      machine.uvw_position[0] = 0.0;
      machine.uvw_position[1] = 0.0;
      machine.uvw_position[2] = 0.0;
      machine.uvw_target[0] = 0.0;
      machine.uvw_target[1] = 0.0;
      machine.uvw_target[2] = 0.0;
    }
    //------------------------------------------------------------------------------
    
    

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •