syntax-highlighter

Wednesday, January 30, 2013

Start of CNC Firmware Running on the Teensy 3.0

I've been intermittently working on some CNC firmware with the goal of getting it to run on everything from an Arduino to a standard PC.  It's a fun project because it's very resource constrained but involves a number of different parts, like GCode parsing, asynchcronous programming, portability and motion control for non-Cartesian machines.

In the interests of portability I've been writing the various components in plain ANSI C (except the comments, I like my double slashes!).  By doing this I expect the code to be portable across a wide variety of platforms, from AVRs to the Propoeller child to ARM to PCs, with minimal effort.

My hopes are that the firmware will conform to the NIST GCode specification as closely as is manageable.  That said, some sacrifices will have to made as the spec calls for around 20k of addressable space for expressions, which exceeds the total RAM of Arduinos and even the Teensy 3.0.  However I hope to get mostly spec-compliant in terms of order of operations, expressions (if not the addressable space) and features.  Where possible I am also allowing the specific capabilities to be determined by preprocessor macros.

I've done a bit of work on the GCode interpreter, getting some test code together that parses GCode expressions (this led to my Mathematical Expression Parser in C) as well as an arbitrary dimension DDA implementation.  To date these have been just simple tests, nothing actually running on real hardware.  I've also done some early tests on doing feedrate optimization and

However today I made some good progress on actually getting some real firmware started.  I got my early GCode interpreter stripped down and abstracted out all the hardware-specific stuff into a hardware layer.  I then wrote the hardware abstraction layer for the Teensy 3.0 version of the Arduino environment and fired up the code, using the following string to test with:


N1229.0(This is a comment)G01x110.0   y330.0 M7 F20.0(MSG: another comment)

And it worked!


The screenshot above shows the GCode interpreter code loaded up in the Arduino environment, the Teensy loader application that programs the Teensy and the serial output of the parser. Note that this is not just reading the commands linearly, it is fully parsing the input, splitting it into distinct commands, sorting the commands by operation precedence (as described in the NIST spec) and finally calling back to the Arduino sketch with each command.

Parsing the string and calling back takes about 250 us (the remaining time taken is up by serial communications) so more than fast enough to fill up a lookahead buffer.  Total RAM used is about 5k, so this won't run on an Uno but does run handily on the Teensy 3.0. Ditto goes for the flash at 33Kb currently. This is pretty hefty compared to other firmware, but I think portability and cleanness makes up for that.

The next steps will be to get the kinematic/motion control stuff fleshed out.  I think I should be able to use by DDA implementation running with the Teensy timers pretty easily.  Then it's just tweaking and adding features...

Thursday, January 10, 2013

Periodic Interrupt Timers on the Teensy 3.0 (Freescale MK20DX128)

I recently ordered a Teensy 3.0 and today it finally arrived!  It definitely is teensy.  Anyway, after soldering on headers and popping it in a breadboard, I got it running with the blink example.  It took some time, but my issue was downloading directly from the Teensy loader page (which does not support Teensy3.0) rather than from the PJRC Teensy forums. To be fair, the Teensy loader page does have a notice at the top about this, but I missed it.

After getting the software, I was able to write a standard Arduino sketch to blink the LED connected to pin 13.  The code is below:

void setup(){
   pinMode(13, OUTPUT);
}

void loop(){
   digitalWrite( 13, HIGH );
   delay( 100 );
   digitalRead( 13, LOW );
   delay( 100 )
}

It's pretty nice that the Teensy3 works like a regular Arduino, but with more pins, at a higher clock rate, in 32 bits and with heaps of extra peripherals.  However my end goal is to use the board as a CNC controller for my ongoing firmware project.  This will make extensive use of timed interrupts to control steppers, so I thought I'd try to get timers working.  Of course, the Teensy3 is not a Atmel uC, so everything changes at this point and, with the help of this forum post to get started, I had to dive into the manual (available from here) for the Freescale MK20DX128 that the Teensy3 is based upon.

According to the forum post, the timer to use is one of the (4?) Periodic Interrupt Timers (PITs).  As expected, these have a number of control registers. Registers listed with an [N], e.g. PIT_LDVAL[N] should have an appropriate timer index substituted, like PIT_LDVAL2.  Here are the registers:
  • SIM_SCGC6 - Enables/disables clock used by PIT timers, not exactly clear on the details, set to SIM_SCGC6_PIT in the forum post example.
  • PIT_MCR - Enables and disables the PIT timers. Writing zero enables the timers and writing 1 disables them.
  • PIT_LDVAL[N] - Sets the timer count value.  Apparently the timer runs at 50MHz, so toggling timer 2 every second should set PIT_LDVAL2 to 0x2fa080 (hex for 50,000,000).  Visually, this appears to be around a second.
  • PIT_TCTRL[N] - Bit zero (TEN in the manual) enables (set to 1) or disables (set to zero) the timer. Bit one (TIE in the manual) enables (set to 1) or disables (set to zero) interrupts that can be generated by the timer.
  • PIT_TFLG[N] - Flag to indicate timer waiting.  Set to one to start timer and at the end of every called interrupt routine, otherwise interrupts will stop. 
Finally, interupts must be enabled. Again I'm not clear on the details, but calling NVIC_ENABLE_IRQ( IRC_PIT_CH[N] ) results in the interupt "void pit[N]_isr(void){}" being called.  Although it seems like the chip should have four timers, I only succeeded in getting timers 0, 1, and 2 working properly with interrupts, testing with index 3 gave a linker error in the Arduino software.

Anyway, here's the code for my tests:

#define TIE 0x2
#define TEN 0x1

void pit0_isr(void){
  digitalWrite( 13, !digitalRead(13) );
  PIT_TFLG0 = 1; 
}

void setup(){
  pinMode(13,OUTPUT);
  SIM_SCGC6 |= SIM_SCGC6_PIT;
  PIT_MCR = 0x00;
  NVIC_ENABLE_IRQ(IRQ_PIT_CH0);
  PIT_LDVAL0 = 0x2faf080;
  PIT_TCTRL0 = TIE;
  PIT_TCTRL0 |= TEN;
  PIT_TFLG0 |= 1;
}

void loop(){
  delay(2000);
}

Hope this helps someone get up to speed, and perhaps serves as a reference for me later on.

Thursday, January 3, 2013

CMake add_subdirectory link errors

This is probably something that everyone already knows, but it took me a few hours to figure out...

I'm currently working on a project that uses CMake to build several libraries and executables all in the same project.  Everything worked fine on OS-X, but when I tried to build on Linux I kept getting linker errors when compiling executable using functions and objects from interdependent libraries that I thought were being linked correctly via the target_link_libraries() command.  Each library was compiled by CMake using the add_subdirectory() command.

I found out that the issue is that you need to specify dependencies between the libraries themselves, not just between executables and libraries.  This was a bit of a surprise, but just adding the target_link_libraries() command to the CMakeLists.txt file for each library and listing the dependencies fixed the problem.

Simplified DDA/Bresenham-like algorithm for line drawing in 2D and 3D

Implementing Bresenham's line drawing algorithm is a pain and has some drawbacks for motion control applications. In particular, it relies on swapping endpoints of the line-segments to achieve specific preconditions and has eight configurations (in 2D alone!) that must be implemented to draw arbitrarily oriented lines.  This has big disadvantages for implementation in high dimension (6D in my case) and even bigger disadvantages for motion control, where you want to move from point A to point B, not arbitrarily (and incorrectly) assume you're already at B and then move backward to A because, hey, order doesn't matter!

I recently ran across Cris Luengo's blog post on a Bresenham-equivalent algorithm in arbitrary dimension based on floating point rounding.  This is likely to be faster on modern machines than the integer only version, simply because i) floating point ops and casting are cheap and ii) branches are expensive, which is effectively the opposite of the case when the original Bresenham algorithm was developed.  He claims the method reproduces the original Bresenham algorithm's plotted pixels, although in my tests I seem to get occasional errant pixels even with double precision.  It may well be an error on my part.

Regardless, this post gave me the idea of just implementing Bresenham's algorithm in a parametric form, accumulating error and advancing pixels in both (all) coordinates in the same manner.  This is considerably more costly in 2D, where you double the operations required, but the relative cost decreases as the dimension increases. Since I'm targeting 6D, the relative cost is only 1/5th, and the simplicity of implementation and lack of endpoint switching more than makes up for it.

I've put up simple C++ code for 2D and 3D integer-only line drawing using a test implementation that is freely available for any use.  It draws 50 random lines with the integer only algorithm described above into the green channel and the same 50 lines with the floating point rounding method (in double precision) into the red channel. The results are below, if the algorithms matched exactly, all the lines would be yellow, with no red or green pixels anywhere.


It looks okay at first, but if you zoom in and look carefully you can see a few red and green pixels around. Regardless, they both do a good job of plotting lines, and if my code is off by a step or two occasionally it's not the end of the world.