Blink and Dim Led: code example for TCC counter on 33 IOT

Hi folks,

I’m really enthusiastic about sharing personnal code example for 33 IOT. Took me days to achieve those simple things even with official documentation!

First thing that is mandatory for my 2 examples, and not the easiest part for SAMD21 coding:

set_PM is used to power on the clock generator n°1 (GCLK0) and linked it to the desired counter, here TCC2 (and TC3, wich shares the settings);

set_LED makes the job to put 33IOT LED_BUILTIN on line with counter through “WO{1] signal”, meaning I/O port will redirect further wave generation on that pin. Dimming, blinking and others will only rely on specific wave generation, so on TCC2 settings;
Finally, busy_TCC will be used to force the system wait for clock synchronization when setting up.

Note I find this way elegant and usefull: no interrupt is used whereas enable/disable and full setting are possible at any moment in code (I imagined it in a way to debug by lighting, then use it for entertainment). If you have proposal about harware requirement improve, feel free to comment!

I generally put that 3 functions in a bottom of my code (after loop() ) as once set-up, care must be taken as any change can strongly impact into register (and shuffle experiments hardly pay in informatic i found) :

// basic settings, common to BLINK and DIM examples

void set_PM() {   // allume le Power Mngt et le générateur de clock

  REG_PM_APBCMASK |= PM_APBCMASK_TCC2; // Enable TCC2 bus clock
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN       // Enable horloge GCLK
                     | GCLK_CLKCTRL_GEN_GCLK0   //  GCLK0 comme générateur pour GCLK (valeur : 0)
                     | GCLK_CLKCTRL_ID_TCC2_TC3; // Lie TCC2 à l'horloge (valeur : 0x1B)
}

void set_LED() { //pin18 (relié à BUILT_IN) est pin PA17  

  PORT->Group[0].PINCFG[17].bit.PMUXEN = 1; // n registre [0..31]
  PORT->Group[0].PMUX[8].bit.PMUXO = 4; // n registre [0...15] impair PMUXO pair PMUXE
}

bool busy_TCC() { // can indicate an issue between peripheral. at home, I generally see no more than one '.'
 
   if (TCC2->SYNCBUSY.bit.ENABLE = 1) {
    Serial.print(".");
    return 1;
  }
  else {
    return 0;
  }
}

then come TCC2 settings. First code example plays blink by setting the period. Smaller the period is, quicker the blinking.
As value _PER ranges from 0 to 0xFFFF (16bit long), you can use plenty of fine tuning.
Pragmatically, I focus on 4 main values:

  • 200 or under: not visible difference with ON, so not the original idea;
  • 512 -1024 (very quick);
  • 4096 (twice a second approx);
  • 0x7FFF (half a 16 bit, almost 2sec ON, then 2sec OFF);

use Blink_Led( _PER , _sens ); at any time in your code. _sens is a boolean, 0 stop blinking whatever the _PER value is.

// TCC2 settings for blink

void set_TCC() { // TCC2 mode NFRQ avec TOP = PER = 16 bit (65535)
  
  TCC2->CTRLA.bit.PRESCALER = 7; // Prescaler clock = /DIV1024
  TCC2->CTRLA.bit.PRESCSYNC = 1;
  TCC2->WAVE.bit.WAVEGEN = 0; // Normal frequency mode

  busy_TCC(); // manufacturer recommanded trick to clock sync.
}

void Blink_Led(uint16_t& _PER,  bool _sens) { // allow to set blink (with _PER value) and ON/OFF (with _marche)
  
  TCC2->CTRLA.bit.ENABLE = 0;
  busy_TCC();
  TCC2->PER.bit.PER = _PER;
  TCC2->CTRLA.bit.ENABLE = _sens;
  busy_TCC();
}

With blink, wave generation is symetric, so the ON time and the OFF time of the LED will always be equal. Using some “eye-friendly” time between 2 repetitions allows sweet display.

A little more effort allows to generate non symetric signals. Single Slope PWM leans on 2 values: PER (period) wich is fixed to max (0xFFFF or 16bit) in my code and reflect the "100% to compare to " , and CC[1] wich represents the light setting.

put 0xFFFF into _dim setting, and Led will power ON at full power.
For small values like 512 or less, Led is barely visible (a setting may lead to power OFF Green led and improves global visibility of settings).
Consider it the same way you could have played with AnalogWrite(Pin, PWM) but with a 16 bit range.

use Dim_Led( _dim , _sens ); at any time in your code. _sens is a boolean, 0 stop dimming whatever the _dim value is.

// TCC2 settings for DIM

void set_TCC() { // 
  
  TCC2->CTRLA.bit.PRESCALER = 0; /Prescaler Clock = /DIV1
  TCC2->CTRLA.bit.PRESCSYNC = 0;
  TCC2->CTRLA.bit.CPTEN1 = 1;
  TCC2->WAVE.bit.WAVEGEN = 2; //NPWM Set à match (CC1) Clear à TOP (PER)
  TCC2->PER.bit.PER = 0xFFFF;
  TCC2->CC[1].bit.CC = 0xFFFF; // initial set-up : light 100%
  
  busy_TCC();
}

void Dim_Led(int _dim, bool _sens) { // _dim value set "enlightenment" of Led
  
  TCC2->CTRLA.bit.ENABLE = 0;
  busy_TCC();
  TCC2->CC[1].bit.CC = _dim;
  TCC2->CTRLA.bit.ENABLE = _sens;
  busy_TCC();
}

to use in my projects (with additionnal time, I will turn it into librairy), I wrap initial setting into setup function:

uint16_t _w = 512; // _w will be passed into blink or dim function

void setup() {
  // put your setup code here, to run once:
  // call the first function to initiate peripherals
  set_PM();   set_LED();   set_TCC();

  // use Dim_Led()  or Blink_Led() according to TCC setting
  Dim_Led(_w, true);
  // OR:
 Blink_Led(_w, true);

 // for the example, an infinite loop is initiated into setup. code won't go further
  while(1);
}

void loop() {
//
}

// put here basic settings, common to BLINK and DIM examples

// put here TCC setting you choose between BLINK or DIM. as both use TCC2, they can't be used at the same time!

and voila !

Cherry on a cake, a simple loop that bit-logically increments the value of dimming. Mainly used during my final experiments, I obtained a very satisfaying effect of flashing.

// to use with DIM settings

void LOOP(int _time) { // makes the Led flashes. use _time settings in microseconds (Let's say 5 for a beginning)
 
   for (byte u = 1; u < 17; u++) {
    int w = 1 << u;
    Dim_Led(w, true);
    delay(u * _time); // spend more timee on higher _dim value, as eye won't be very sensible to little values
  }
}

Hope you’ll find intersting !