Arduino Zero (SAMD21) Timed Interrupt & CPU Speed

Go To Last Post
22 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello All,

 

I set up my uC to start a timed interrupt. Now i though i set it up as 48 MHz and a divide factor of 3 and for the timer a prescaler of 64 which means 250.000 interrupts every second. Now if i want an interrupt every 1 ms i set the compare value to 250. Yet it doesn't behave the way i want it to do. In my code i calculate how many times the timer should count in order to move at a certain speed. My previous code, which uses micros(), works fine except that the interrupt is to slow. I'll show you the code below but my questions are:

 

  • How do i set the CPU to run at 48 MHz? 
  • How do i make sure the timer runs at the desired speed/interrupt time?

 

void InitializeRobot() {
  // Set The CPU To Run At Maximum = 48 MHz & Wait For Sync Status Idle
  //GCLK->GENCTRL.reg = GCLK_GENCTRL_SRC_DFLL48M;
  //SYSCTRL->OSC32K.reg = SYSCTRL_OSC32K_ENABLE;

  PM->CPUSEL.reg = PM_CPUSEL_CPUDIV_DIV1;                                       // Select cpu division (48 MHz)
  PM->APBCMASK.reg |= PM_APBCMASK_TCC1;                                         // Enable peripheral clock for timer 1
  while (GCLK->STATUS.bit.SYNCBUSY) { };

  // Set The Global Clock: Improve Duty Cycle, Enable And Clock Selection
  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(4)                                        // Set general clock 4
                      | GCLK_GENCTRL_IDC                                        // Improve duty cycle (50 % / 50 %)
                      | GCLK_GENCTRL_GENEN                                      // Enable general clock
                      | GCLK_GENCTRL_SRC_DFLL48M;                               // Set OSC32K as source for the clock (32.768 kHz High accuracy)      DFLL48M
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(4)                                          // Select generic clock 4
                      | GCLK_GENDIV_DIV(3);                                     // Set clock division (48/3 = 16 MHz)
  while (GCLK->STATUS.bit.SYNCBUSY) { };

  // Feed Clock To Timer, Select Clock 4 & Enable Clock
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_GEN_GCLK4                                    // Select general clock 4
                      | GCLK_CLKCTRL_ID_TCC0_TCC1                               // Feed clock to timer
                      | GCLK_CLKCTRL_CLKEN;                                     // Enable general clock
  while (GCLK->STATUS.bit.SYNCBUSY) { };                                        // Wait for general clock to be synced
  //while (GCLK->CLKCTRL.reg & GCLK_CLKCTRL_CLKEN) { };

  // Configure The Timed Interrupt For MotorControl
  TCC1->INTENCLR.reg = TCC_INTENCLR_MC0;                                        //
  TCC1->CTRLA.reg &= ~(TCC_CTRLA_ENABLE);                                       // Disable the timer
  while (TCC1->SYNCBUSY.bit.ENABLE) { };                                        // Wait till timer is disabled

  PORT_IOBUS->Group[0].OUT.reg |= PORT_PA08;
  TCC1->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV64                                  // Set timer division factor
                    | TCC_CTRLA_PRESCSYNC_GCLK;                                 // Timer presynchronization
  TCC1->WAVE.reg = TCC_WAVE_WAVEGEN_MFRQ;                                       // Set the wavegeneration to match frequency
  while (TCC1->SYNCBUSY.bit.WAVE) { };
  TCC1->COUNT.reg = 0;                                                          // Initialize count to 0
  //while (TCC1->SYNCBUSY.bit.COUNT) { };
  TCC1->CC[0].reg = 250;                                                        // Set compare value
  while (TCC1->SYNCBUSY.bit.CC0) { };                                           // Wait for compare synchronization done

  PORT_IOBUS->Group[0].OUT.reg |= PORT_PA09;
  // Period is ignored in MFRQ
  //TCC1->PER.reg = 100;
  //while (TCC1->SYNCBUSY.bit.PER) { };

  TCC1->INTFLAG.reg = TCC_INTFLAG_MC0;                                          //
  TCC1->INTENSET.reg = TCC_INTENSET_MC0;                                        //
  TCC1->CTRLA.reg |= TCC_CTRLA_ENABLE;                                          // Enable the timer
  //while (TCC1->SYNCBUSY.bit.ENABLE) { };
  PORT_IOBUS->Group[0].OUT.reg |= PORT_PA20;

  // Set The Timed Interrupt Priority To The Highest & Enable
  NVIC_ClearPendingIRQ(TCC1_IRQn);                                              // Clear pending for timer interrupt
  NVIC_SetPriority(TCC1_IRQn, 0);                                               // Set interrupt to highest priority
  NVIC_EnableIRQ(TCC1_IRQn);                                                    // Enable the interrupt event handler

  // Configure External Interrupt For Remote X-Direction
  //attachInterrupt(2, InterruptHardwareX, CHANGE);
  LeftMotor._ConfigureAxis(1, 1, 1, 1.8, 5, 5);
  LeftMotor._Move(5, 0, 1, 0);
  // Configure External Interrupt For Remote Y-Direction;
  //attachInterrupt(3, InterruptHardwareX, CHANGE);
};

 

Kind Regards,

 

 

Bob

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Bob

 

The Zero board runs at 48MHz without any need to set speed in software.

 

I haven't tried timers in ARM processors. You could try to output timer interrupts to a pin, and verify frequency on that pin.

 

My experiences with pin change interrupts on the Zero, is that interrupts on pins 2 and 4 does not work. No issues with pins 3 and 5..13.

 

Good luck!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

peteralarsen wrote:

Hi Bob

 

The Zero board runs at 48MHz without any need to set speed in software.

 

I haven't tried timers in ARM processors. You could try to output timer interrupts to a pin, and verify frequency on that pin.

 

My experiences with pin change interrupts on the Zero, is that interrupts on pins 2 and 4 does not work. No issues with pins 3 and 5..13.

 

Good luck!

Could you maybe tell me on how to do it anyway? I would like to know how so.

About the timer, with the timer i set the outluts manually so im not using pwm signal or anything. And even the built in LED doesnt seem to work properly.

Thank you for your response!

Kind regards,

Bob

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Alright, so i'm a bit confused. If i'm correct the 48 MHz signal is transferred to the general clock which has a division factor of 3. This results in the fact that the timer gets a 16 MHz signal. Now with a prescaler of 8 and if i want it to run every 10 us. I need to set the CC value to: 

 

16.000.000 / 8 = 2.000.000, this means 2 milion times per second. 1 tick is at 0.5 us. so 10 us / 0.5 us = 20 ticks. And setting my count value to 100000 would toggle the LED at 1 second. However it already works at a value of 10.000 which is something i absolutely don't understand so what am i missing? The code for the LED is shown below, the code for the timer setup is as shown in the first post except that i changed the prescaler and the CC0 value. 

 

void TCC1_Handler() {
  NVIC_ClearPendingIRQ(TCC1_IRQn);                                              // Clear pending for timer interrupt

  // Check for overflow (OVF) interrupt
  if (TCC1->INTFLAG.bit.OVF && TCC1->INTENSET.bit.OVF)
  {
    TCC1->INTFLAG.reg = TCC_INTFLAG_OVF;                                        // Clear the OVF interrupt flag
  }

  // Check for match counter 0 (MC0) interrupt
  if (TCC1->INTFLAG.bit.MC0 && TCC1->INTENSET.bit.MC0)
  {
    LedCount++;
    if (LedCount >= 10000) {
        if (BuiltInLed) {
          digitalWrite(LED_BUILTIN, LOW);
        } else {
          digitalWrite(LED_BUILTIN, HIGH);
        }
        BuiltInLed = !BuiltInLed;
        LedCount = 0;
    };

    LeftMotor._ControlSync();
    TCC1->INTFLAG.reg = TCC_INTFLAG_MC0;                                        // Clear the MC0 interrupt flag
  }
};

 

Kind Regards,

 

 

Bob

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The Zero board runs at 48MHz without any need to set speed in software.

More accurately, "the Arduino Zero initialization code already configures the system clock to 48MHz."   (in startup.c: https://github.com/arduino/Ardui... )

 

If you've managed to bypass that code somehow, or if you adjust the appropriate registers AFTER the Arduino thinks it is done, then you can certainly change the clock away from 48MHz.

 

 

If i'm correct the 48 MHz signal is transferred to the general clock which has a division factor of 3.

Which "General Clock" are you talking about?  Looking at various code, there is:

(from startup.c:)

GCLK1: 32kHz out.  input 32kHz from external crystal

GCLK0: 48MHz out, uses DFLL to get 48MHz from input GCLK1

GCLK3: 8MHz out, input from  internal 8MHz Osc.

(from your code:)
GCLK4: 16MHz, input from DFLL (should still be 48MHz), div by 3.

 

I dunno.  Your logic sounds fine and the code looks ok, but I find the whole GCLK system to be poorly explained "black magic" (like something was defined in hardware that made sense, and then some software person did a poor translation.  I see this in most data sheet descriptions of "fractional baud rate divisors" as well, but at least there I understand both "ends" can can come up with an alternate model that makes sense to me...)

I'd try dumping out the final values of some of those config registers (TCC1->CTRLA, GCLK->CLKCTRL, etc), to make sure they end up looking the way you think you set them.  (note that reading the GLCK registers appears to be "tricky"!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Note that "GCLK" = "Generic Clock"

 

westfw wrote:
I find the whole GCLK system to be poorly explained "black magic"

Do you mean the GCLK hardware, or its support in software?

(or both?)

 

I don't have a problem with the hardware side; I found it really useful & flexible - especially compared to certain competitors.

Trying to understand the ASF support was a different matter - until I finally realised how it all fitted together.

 

I think that the whole GCLK system is inherited from AVR32 - dunno if you'd find it  better documented there ... ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,

 

I've been doing some testing and now i set the division factor to 8. I though this would give me 2.000.000 ticks per second so if i set the CC value to 200 i would expect an update time of 100 us. However if i set the count for the led to 5.000 to toggle it will toggle at about a second. I really don't understand this. Do any of you have an idea what i'm doing wrong or maybe what i'm missing in my calculation? 

 

Kind regards,

 

 

Bob 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Do you mean the GCLK hardware, or its support in software?

 Probably the documentation and thus the software.  And the inconsistencies.  (ie, only some gclks can have other gclks as their input?  Or is that that only some gclks can be an input for other gclks?)   (And did you know that the SAMD21, and SAMD10 RTC are clocked by a GCLK, but the SAMC21 and SAMD51 aren't?   grr.)  Like I said, it seems like there was some reasonably powerful hardware idea that was poorly explained to some software-type person who didn't really understand it, but was the documentation-writer's primary source.   And the the SW examples were written by someone else, based on the documentation.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's very difficult to debug your program remotely. Could you post a simplified version, let's say a blink program, so that we can test it?

I have a xplained mini board with a SAM D10, I'm willing to give it a try, but only if you post a minimal version.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Most (all?) of this the code seems to be just direct access to the SAM registers - so nothing to do with Arduino?

 

Might be better moved to the SAM D (Cortex-M) forum ... ?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was reading this and asking myself "is this actually about Arduino or SAM?" but the fact that the Arduino startup code may have some influence in setting 48 MHz I think I'll leave the thread here for now.
.
Personally I wouldn't "mix and match". I'd either do everything "Arduino" or completely "bare metal" but I wouldn't mix so one might influence the other.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

fair point.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

One could argue that Arduino is doing the basics required to get he chip operational so it’s doing much the same as a ‘bare metal’ implementation. Point being - is the Arduino any more or less bare metal?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
I was reading this and asking myself "is this actually about Arduino or SAM?" but the fact that the Arduino startup code may have some influence in setting 48 MHz I think I'll leave the thread here for now. . Personally I wouldn't "mix and match". I'd either do everything "Arduino" or completely "bare metal" but I wouldn't mix so one might influence the other.

 

I don't think i'm actually mixing things up, maybe just the digitalWrite function for the built in led which i will also be setting in the 'bare metal' way. 

 

El Tangas wrote:

It's very difficult to debug your program remotely. Could you post a simplified version, let's say a blink program, so that we can test it?

I have a xplained mini board with a SAM D10, I'm willing to give it a try, but only if you post a minimal version.

 

I wouldn't know how to make this more simplistic as the all the code in my opinion is required to setup the timer and the interrupt event is posted a few posts below it. So if you would like to test it that would be great but i don't know how to make this more simplistic. 

 

Thank you all for your replies! 

 

Kind regards,

 

 

Bob

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've checked the datasheet and the SAMD21 runs by default from the 8 MHz internal oscillator, divided by 8. This is a divisor internal to OSC8M controlled from the "System Controller" and not from the "Power Manager". So changing the PM divisor can't set the chip to 48MHz by itself.

 

You need to power up the 48MHz oscillator. And it needs a reference oscillator to work with any kind of precision (one of the 32kHz oscillators or the 8MHz one). All this requires a lot of setup and apparently can be done in a vast number of different ways.

 

I suppose the initialization routine from the Arduino somehow enables the 48MHz, but who knows what method they used?

 

Personally, I would write everything from scratch, so that I know exactly what is going on.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

El Tangas wrote:
but who knows what method they used?

The answer to that would be "everyone". AFAIK all the Arduino code is totally open and available for anyone to read.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The answer is everyone that bothers to read an decipher the code. I'd rather reinvent the wheel in this particular case.

 

The reason is that there are many ways to get the 48MHz. I wouldn't bet the one they use is the best for a particular application.

And I'm very bad at deciphering other people's code. I admit to this weaknesssad I'd rather read the massive datasheet of the SAM D.

Last Edited: Mon. Feb 19, 2018 - 03:53 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I found a "core" for SAMD21 and started by looking at wiring.c (the real core of Arduino) where I found:

 

https://github.com/arduino/Ardui...

 

The comment there hopefully identifies the next stop on the journey (and also confirms some of the "hearsay" in this thread - that it runs it at 48). So next stop..

 

https://github.com/arduino/Ardui...

 

Everything from line 56 to line 272 (roughly) seems to be involved (and now I know why people baulk at the complexity of Cortex!!)

 

I guess someone who's interested need to sit an study all that alongside a datasheet and see if it's possible to decipher exactly what's going on?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
the 48MHz oscillator (sic?)

You mean the DFLL48 ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, is that the one the Arduino initialization uses? (edit: it is, I just took a look at the source posted above) The DPLL96M could also be used, I guess.

Last Edited: Mon. Feb 19, 2018 - 08:26 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There was this previous discussion.   I ended up with a considerably simpler clock initialization (for SAMD10)

 

http://www.avrfreaks.net/forum/s...

 

I still don't know why one would pick FLL vs PLL :-(

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Meanwhile I also wrote a test program that sets the MCU to 24MHz (I didn't want to set wait states), using my SAMD10 xplained mini.

This one uses OSC8M as reference and the DFLL48 divided by 2 to clock the CPU. To check that everything was fine, I also output a 1MHz signal derived from the 48MHz one to PA17.

 

#include "sam.h"

void set_OSC8M ();
void set_GCLKGEN4();
void set_DFLL48();
void set_24MHz();
void set_1MHz_test();

int main(void)
{
    /* Initialize the SAM system */
    SystemInit();
	set_OSC8M();
	set_GCLKGEN4();
	set_DFLL48();
	set_24MHz();
    /* Generate a 1MHz square wave for testing on PA17 */
	set_1MHz_test();
	
    while (1);
}

void set_OSC8M(){
	SYSCTRL->OSC8M.bit.PRESC = 0x00;
}

/* Program clock generator 4 */
void set_GCLKGEN4(){
	/* Configure divisor to generate 32kHz (divide 8MHz by 250) */
	GCLK->GENDIV.reg = GCLK_GENDIV_DIV(250) | GCLK_GENDIV_ID(4);
	/* Configure OSC8M as source and enable clock generator */
	GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSC8M | GCLK_GENCTRL_ID(4);
	/* Connect clock generator to clock (DFLL48 reference) and enable */
	GCLK->CLKCTRL.reg = GCLK_CLKCTRL_GEN(4) | GCLK_CLKCTRL_ID_DFLL48 | GCLK_CLKCTRL_CLKEN;
}

void set_DFLL48(){
	/* Start the DFLL48 */
	while (!(SYSCTRL->PCLKSR.bit.DFLLRDY));
	SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_ENABLE;
	/* Set multiplier to 1500 for 48MHz and a (optional?) step value for fine locking */
	while (!(SYSCTRL->PCLKSR.bit.DFLLRDY));
	SYSCTRL->DFLLMUL.reg = SYSCTRL_DFLLMUL_MUL(1500) | SYSCTRL_DFLLMUL_FSTEP(256);
	/* Get coarse lock calibrated value from ROM */
	uint32_t coarse_cal_val = (*(uint32_t*) FUSES_DFLL48M_COARSE_CAL_ADDR) & FUSES_DFLL48M_COARSE_CAL_Msk;
	/* Align value correctly for use in the DFLLVAL register */
	coarse_cal_val >>= 16;
	/* Get fine lock calibrated value from ROM (optional?) */
	uint32_t fine_cal_val = (*(uint32_t*) FUSES_DFLL48M_FINE_CAL_ADDR) & FUSES_DFLL48M_FINE_CAL_Msk;
	/* Merge coarse and fine calibration values into DFLLVAL register */
	while (!(SYSCTRL->PCLKSR.bit.DFLLRDY));	
	SYSCTRL->DFLLVAL.reg = coarse_cal_val | fine_cal_val;
	/* Set the DFLL48 to close loop mode and bypass coarse locking */
	while (!(SYSCTRL->PCLKSR.bit.DFLLRDY));
	SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_MODE | SYSCTRL_DFLLCTRL_ENABLE | SYSCTRL_DFLLCTRL_BPLCKC;
	/* Wait for fine lock */
	while (!(SYSCTRL->PCLKSR.bit.DFLLLCKF));
}

/* Program clock generator 0 to take the DFLL48 clock as input */
void set_24MHz(){
	/* Configure divisor to generate 24MHz (divide 48MHz by 2) */
	GCLK->GENDIV.reg = GCLK_GENDIV_DIV(2) | GCLK_GENDIV_ID(0);
	/* Configure DFLL48 as source and enable clock generator */
	GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(0);
	/* Generator 0 is always connected to the CPU clock, so we are done here */
}

/* Program clock generator 3 to take the DFLL48 clock as input and generate 1MHz output */
void set_1MHz_test(){
	/* Configure divisor to generate 1MHz (divide 48MHz by 48) */
	GCLK->GENDIV.reg = GCLK_GENDIV_DIV(48) | GCLK_GENDIV_ID(3);
	/* Configure DFLL48 as source, enable clock generator and clock external output */
	GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(3) | GCLK_GENCTRL_OE;
	
	/* Pin PA17 can be connected to output the generator 3 signal */
	PORT->Group[0].WRCONFIG.reg =	/* select PA17. This is in the high half (16 + 1) */
									PORT_WRCONFIG_HWSEL | PORT_WRCONFIG_PINMASK(1 << 1) |
									/* write both pin configuration and pin multiplexing */
									PORT_WRCONFIG_WRPINCFG | PORT_WRCONFIG_WRPMUX |
									/* multiplexer: enable and connect to option H, which is the generic clock generator */
									PORT_WRCONFIG_PMUXEN | PORT_WRCONFIG_PMUX(PORT_PMUX_PMUXE_H_Val);
	/* Set PA17 as output */
	PORT->Group[0].DIRSET.reg = 1 << 17;
}

 

This is the obtained square wave, it's pretty accurate according to my (relatively cheap) scope, about 0.5% error.

 

 

I wonder if the OP is still following? There is already lots of useful info in this thread.