Porting some high res timing routines from arduino DUE SAM3XAE Cortex M3 to teensy3.6

Status
Not open for further replies.

educa

Active member
Hello,


Excuse my direct approach, but I am somebody who is interested in developing software with teensy 3.6
The reason why I would like to use teensy 3.6 is because of the very high speed and capabilities of this board + quite some ram available.
This is NOT for making the next generation of "blink the led". :)


My name is Bart Libert and I want to write my own gcode interpreter for laser cutter. This is high speed stepper signal generation (preferably upto 100kHz steprates) with 10x interpolation, so with a virtual 1Mhz steprate.
I already wrote a fully functioning prototype for arduino due (SAM3XAE Arm Cortex-M3) which does use a few important features, but Arduino DUE is EOL and I prefer to use a more supported product.


The things I would have to be able to do are:


1) I need to be able to direcly set a pin HIGH or LOW without using the library (for pure speed purposes, because digitalWrite eats too many cycles)


2) 1 hardware pwm signal with preferably a +-20kHz frequency. I know this can be setup using library code but it would be very nice to be able to
directly set this using registers (for best possible speed)


3) I need 3 (and if possible 4) IO pins which can be set high and low by a timer. On arduino due there are multiple channels to a timer and there I managed to do this. The fact is that I want 3 (or 4) independent counters which are by default stopped. Then when I start the counter, it must SET a pin (whichever pin it is on the teensy, I'll adapt the connection wires) and then when it reaches a certain counter value, the timer should lower the pin signal, set the counter back to 0 and stop the counter. So it should
- On initialisation the timer (connected to a hardware pin) should be set at a counter value of 0 and the corresponding hardware pin should be LOW + the counter must NOT start running.
- At a certain point in my program I want to give the counter the command to start and when it starts, it should SET its corresponding hardware pin high, start counting and when the counter reaches a certain compare value, it should set the hardware pin LOW again, zero the counter value and stop the timer counter.

This above is needed 3 (or even better 4) times and they should run independent from eachother. On the Arduino DUE I used Timer 2 channel 0,1 and 2 for that and thes came out as digital pins 5,3 and 11




Can anyone please tell me if the teensy 3.6 is capable of doing this? I allready downloaded the CPU manual (a whopping 2237 pages) and have to say it does not look a lot like the Atmel SAM3XAE which is also arm. So probably the complete peripheral set is regulated slightly different?
















To proove that I really don't as without knowing what I say, this is the code I used on arduino DUE for the initialisation of the timers and pwm
================================================================================================================================================


Code:
  //Prepare timers that will help generate the pulses for xStep, yStep and laser shoot.
  pmc_set_writeprotect(false);  //Turn off write protection on Power Management Controller
  pmc_enable_periph_clk(TC6_IRQn);  // Enable the  peripheral clock by IRQ for Timer 2 channel 0 : X step pulse
  pmc_enable_periph_clk(TC7_IRQn);  // Enable the  peripheral clock by IRQ for Timer 2 channel 1 : Y step pulse
  pmc_enable_periph_clk(TC8_IRQn);  // Enable the  peripheral clock by IRQ for Timer 2 channel 2 : Laser shoot pulse


  //Specific preparation for Timer 2 channel 0 (X step pulse)
  TC_Configure(TC2, 0, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK1 | TC_CMR_ACPA_SET | TC_CMR_ACPC_CLEAR | 64); // Configure the timer on its specified channel. Timer is stopped after config and must be started in code.
  TC_SetRA(TC2, 0, 1); // Set the counter value where the output of  TIOA goes high in register RA (datasheet p.867)
  TC_SetRC(TC2, 0, 420); //Set the counter value (3ms) where the output of TIOA goes low and the counter is reset in register RC (datasheet p.867)
  REG_PIOC_ABSR |= PIO_ABSR_P25;     // Switch the multiplexer to peripheral B for TIOA6 (pin 5 on due)
  REG_PIOC_PDR |= PIO_ABSR_P25;     // Disable PIO for TIOA6 pin 5 on due


  //Specific preparation for Timer 2 channel 1 (Y step pulse)
  TC_Configure(TC2, 1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK1 | TC_CMR_ACPA_SET | TC_CMR_ACPC_CLEAR | 64); // Configure the timer on its specified channel. Timer is stopped after config and must be started in code.
  TC_SetRA(TC2, 1, 1); // Set the counter value where the output of  TIOA goes high in register RA (datasheet p.867)
  TC_SetRC(TC2, 1, 420); //Set the counter value (6ms) where the output of TIOA goes low and the counter is reset in register RC (datasheet p.867)
  REG_PIOC_ABSR |= PIO_ABSR_P28;     // Switch the multiplexer to peripheral B for TIOA7 (pin 3 on due)
  REG_PIOC_PDR |= PIO_ABSR_P28;     // Disable PIO for TIOA7 pin 3 on due


  //Specific preparation for Timer 2 channel 2 (Laser shoot pulse)
  TC_Configure(TC2, 2, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK1 | TC_CMR_ACPA_SET | TC_CMR_ACPC_CLEAR | 64); // Configure the timer on its specified channel. Timer is stopped after config and must be started in code.
  TC_SetRA(TC2, 2, 1); // Set the counter value where the output of  TIOA goes high in register RA (datasheet p.867)
  TC_SetRC(TC2, 2, 125925); //Set the counter value (9ms) where the output of TIOA goes low and the counter is reset in register RC (datasheet p.867)
  REG_PIOD_ABSR |= PIO_ABSR_P7;     // Switch the multiplexer to peripheral B for TIOA8 (pin 11 on due)
  REG_PIOD_PDR |= PIO_ABSR_P7;     // Disable PIO for TIOA8 pin 11 on due


  //Specific preparation to enable a 20kHz PWM signal on digital pin 34
  pmc_enable_periph_clk (PWM_INTERFACE_ID) ;  // turn on clocking to PWM unit
  PWMC_ConfigureChannel (PWM, 0, 1, 0, 0) ; // PWM channel 0, clock = MCLK/2 = 42MHz
  PWMC_SetPeriod (PWM, 0, 2100) ;  // Channel 0 period = 2100 pwm clocks (20kHz)
  PWMC_SetDutyCycle (PWM, 0, 80 * 2100 / 100) ; // Channel 0 duty set to 80%
  PWMC_EnableChannel (PWM, 0) ;   // enable channel 0
  PIOC->PIO_PDR = 1 << 2 ; // disable PIO control on PC2 (pin34) by setting bit 2 of the PIO_PDR register for PIO controller PIOC
  PIOC->PIO_IDR = 1 << 2 ; // disable PIO interrupts on PC2 (pin34) by setting bit 2 of the PIO_IDR register for PIO controller PIOC
  PIOC->PIO_ABSR |= 1 << 2 ; // switch PC2 (pin34) to B peripheral, which carries PWM channel 0 ?


  //Prepare the Xdir and Ydir pins for startup and make sure they are low
  pinMode(xDir, OUTPUT);
  g_APinDescription[xDir].pPort -> PIO_CODR = g_APinDescription[xDir].ulPin; //xDir low
  pinMode(yDir, OUTPUT);
  g_APinDescription[yDir].pPort -> PIO_CODR = g_APinDescription[yDir].ulPin; //yDir low


And then enabling a pin for a certain duration by enabling the timer is done like this
======================================================================================


Code:
TC2->TC_CHANNEL[2].TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG; // let laser shoot with a timer regulated signal
 
1) I need to be able to direcly set a pin HIGH or LOW without using the library (for pure speed purposes, because digitalWrite eats too many cycles)

Use digitalWriteFast()

2) 1 hardware pwm signal with preferably a +-20kHz frequency. I know this can be setup using library code but it would be very nice to be able to
directly set this using registers (for best possible speed)

Use analogWriteFrequency() and analogWrite(). Details here:

https://www.pjrc.com/teensy/td_pulse.html

3) I need 3 (and if possible 4) IO pins which can be set high and low by a timer. ... it must SET a pin (whichever pin it is on the teensy, I'll adapt the connection wires) and then when it reaches a certain counter value, the timer should lower the pin signal, set the counter back to 0 and stop the counter.

For this you're going to need to directly manipulate the FTM timer registers. They're documented in chapter 45 starting on page 1127.

These timers can operate in one of many possible modes. The main info you need is table 45-3 on page 1147.

You could configure the MOD register so the timer counts up to a certain value, then resets at to zero. Then the normal output compare might work, to set the pin at a particular value and clear it when the timer rolls back to zero. There's also a special "combine PWM" mode where 2 channels are linked, so the pin can be set and cleared at any arbitrary point, but from your description of having the timer go back to zero it sounds like you probably only need the simpler mode.

The FTM timer chapter is huge, but something to understand is the timers actually have 2 modes. The default TPM mode is fairly simple, pretty much as you'd expect a 16 bit timer to work, with the modes from that table. In TPM mode, the timer is controlled by just half a dozen registers, plus a pair of registers for each pin (or "channel" in TPM/FTM lingo). The "MODE" register has a FTMEN bit that switches the timer from this fairly simple TPM mode to full FTM mode, which adds a ton of special PWM features for motor and 3 phase power control. Most of the chapter is about those special FTM mode features.

You might look at the PulsePosition library. It uses the output compare feature to generate special waveforms. It does this by using the normal compare mode, and then an interrupt from the compare causes the code to quickly reprogram the channel before the pin need to change (compare) again. Obviously that approach is limited to pulses no smaller than the interrupt response time, but it allows compare to generate arbitrary waveforms without resorting to the more complex combine mode.

It's also possible to have DMA automatically reprogram the PWM, for a different pulse width on every cycle without CPU overhead. The audio library uses this, so you might look at the PWM output code if you're interested in that approach.


Can anyone please tell me if the teensy 3.6 is capable of doing this? I allready downloaded the CPU manual (a whopping 2237 pages) and have to say it does not look a lot like the Atmel SAM3XAE which is also arm. So probably the complete peripheral set is regulated slightly different?

Yup, the hardware is easily capable of this, but the peripherals are completely different from Atmel's SAM chip (which is itself completely different from Atmel's SAMD and several of their other chips).

It sound like you're determined to do everything with direct register access. You certainly can, and for some of this you really much do so. But for things like direct pin access, maybe save yourself some pain and just use digitalWriteFast()?
 
Thank you for this informative reply Paul.
I will begin by studying that nice chapter 45. I managed to understand a lot of this stuff on SAM3 , so hopefully I can find good info there + in sourcecode of the libraries you mentioned. If things are not clear I hope that I can ask for a little more info, but I wouldn't find it fair to ask more now without doing some study myself ;-)

I also have a 3.6 on order to be delivered soon (yes, I'm very new, but I don't want to stay at arduino DUE which is discontinued + I intend to document all my work so others can benefit later too). Teensy looks extremely capable.


PS. Maybe you expect this answer, but if you suggest me to use digitalWriteFast(), then that is just a wrapper around the real code with pin parameters I guess. It might sound strange, but if I can do it in 1 or 2 cycles then I rather not waste 10 :) . At 180Mhz (and I hope overclocked at 240 too) I'm pretty sure that the teensy will handle all my code in maybe 2 or 3 % CPU usage (without interpolation), but then the intention is to rise the interpolation until the CPU is almost 95% saturated. It gives a lot better result when lasercutting with higher interpolations, so optimizing the core is rewarding, because 2 cycles wasted at 20x interpolation = 40 wasted ;)

On the other hand I just read that digitalWriteFast uses only 1 cycle to set or reset a pin? In that case it makes NO sense for me to go play with registers ;) for that. Is the compiler optimizing this complete code maybe ?
 
Status
Not open for further replies.
Back
Top