0% found this document useful (0 votes)
153 views34 pages

Arduino SAMD21 Timer API Development

This document describes the development of a high-level timer API library for SAMD21-based Arduino boards. It discusses the state of the art in Arduino timing, and proposes a unified library that hides the hardware configuration behind easy-to-use interfaces. The library provides an ITimer interface and implements it for the SAMD21, abstracting away register configuration of the timer peripheral. It ensures compliance with Arduino library guidelines for ease of use.

Uploaded by

Abdul wajed
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
153 views34 pages

Arduino SAMD21 Timer API Development

This document describes the development of a high-level timer API library for SAMD21-based Arduino boards. It discusses the state of the art in Arduino timing, and proposes a unified library that hides the hardware configuration behind easy-to-use interfaces. The library provides an ITimer interface and implements it for the SAMD21, abstracting away register configuration of the timer peripheral. It ensures compliance with Arduino library guidelines for ease of use.

Uploaded by

Abdul wajed
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Università degli Studi di Messina 

Department of Engineering 
 

 
 
Engineering and computer science master degree 
 
 
Embedded systems 
 
 
 
 

Development of timers’ high level API for 


SAMD21 based Arduino boards 
 
 
 
 
 
 
 
Prof. Francesco Longo 
Alberto Pimpo 

 
Table of contents 
 
 
1. Introduction 3 

1.1 State of the art 3 

1.2 Solution proposed 4 

1.3 Arduino guidelines compliance 5 

2. SAMD21 Timer 6 

2.1 Config registers 6 

2.2 General clock source 8 

2.3 NVIC 10 

3. Implementation of the library 11 

3.1 ITimer generic interface 11 

3.2 SAMD21 Implementation 14 

3.2.1 Samd21 data types 14 

3.2.2 Methods definition 16 

3.2.3 Methods implementation 19 

3.2.4 Handler wrapping 29 

3.3 CI implementation and code quality 31 

3.3.1 Compile script 31 

3.3.2 Code quality 33 

   


1. Introduction 
1.1 State of the art 
Thanks to Arduino it’s possible to realize and fast prototype 
microcontrollers’ firmware thanks to the simplicity of the Arduino HAL 
(Hardware Abstraction Layer). In the specific case of timing usually what 
Arduino suggest to use is the delay function. In delay function, we 
simply used a “for” loop, where the CPU spend some time (the amount 
of time as mentioned in the delay function) so that CPU doesn’t do any 
other work. 
This method of providing delay in the program is ineffective and affects 
the performance of the overall system. Hence, we need to improve this 
and implement a precise delay function. For this, we need to use the 
concept of Timers. 

Timers and Counters are often associated with a single module in 
microcontrollers. The term Timer / Counter reflects that the responsible 
module can be configured to count regular clock pulses, which makes it 
a Timer or to count an external event, which makes it a counter. The 
main and important function of the Timers / Counters in MCUs is to 
generate precise time delays and count the external actions. 

Arduino HAL doesn’t provide an easy way to deal with this peripheral. In 
order to use timers the user has to access to register throw some data 
structures (more details will be given later) and set manually the desired 
values in order to use the timer. So the user has to have a specific 


knowledge of the hardware that is using and the code so that written 
became hardware specific. 

1.2 Solution proposed 


The solution proposed with this work is a unified, extendible and 
easy-to-use Arduino library that hide behind some high level API all the 
hardware configuration of the timer peripheral. This is done thanks to 
high level functionalities of C++ as template and abstract classes. 
This library provides also an easy way to approach for the first time the 
timer peripheral because of the implementation hiding over the Arduino 
HAL and thanks to easy example that makes immediately clear how to 
use it. 
 

   


1.3 Arduino guidelines compliance 
The developed library is full compliant to the Arduino libraries style 
guidelines. 
It is compliant because: 
● all the functionality of the library are given as methods of an object 
defined in global scope 
● APIs don’t require the knowledge of pointers or data structures 
● camel case is used over snake case (despite snake case is a C++ 
convention) 
● it gives two different access to the functionality, one with more 
abstraction and one with less abstraction for skilled users 
● all the configuration of the peripheral is hidden behind a method 
● choose enable/disable name for non-communicating peripheral 

   


2. SAMD21 Timer 
The Timer/Counter (TC) module provides a set of timing and counting 
related functionality, such as the generation of periodic waveforms, the 
capturing of a periodic waveform's frequency/duty cycle, and software 
timekeeping for periodic operations. TC modules can be configured to 
use an 8-, 16-, or 32-bit counter size. 

2.1 Config registers 


Independent of the configured counter size, each TC module can be set 
up in one of two different modes; capture and compare. In capture 
mode, the counter value is stored when a configurable event occurs. 
This mode can be used to generate timestamps used in event capture, 
or it can be used for the measurement of a periodic input signal's 
frequency/duty cycle. In compare mode, the counter value is compared 
against one or more of the configured channel compare values. When 
the counter value coincides with a compare value an action can be taken 
automatically by the module, such as generating an output event or 
toggling a pin when used for frequency or PWM signal generation.  
The register used to configure those parameters is CTRLA. Each timer 
has one of those, and this can be used to config the following 
parameters: 
● WAVEGEN < - 2 bits - > selection of one of the wavegen modes 
● MODE < - 2 bits - > choose between 8 bit, 16 bit or 32 bit mode 


● ENABLE < - 1 bit - > enable the timer 
● SWRST < - 1 bit - > used for software reset 
● PRESCSYNC < - 2 bits - > modality of the prescaler 
● PRESCALER < - 3 bits - > choosing of preset of prescaler 
 
Those bits are positioned in the register as follow: 
 

   


2.2 General clock source 
To enable a peripheral clocked by a generic clock, the following parts of 
the system needs to be configured: 
● a running clock source 
● a clock from the Generic Clock Generator must be configured to 
use one of the running clock sources, and the generator must be 
enabled 
● the generic clock, through the Generic Clock Multiplexer, that 
connects to the peripheral needs to be configured with a running 
clock from the Generic Clock Generator, and the generic clock 
must be enabled 
● the user interface of the peripheral needs to be unmasked in the 
PM. If this is not done the peripheral registers will read as all 0’s 
and any writes to the peripheral will be discarded 
In order to configure properly all the clock source and connect them to 
peripherals exist a special peripheral named Generic CLocK controller 
(GCLK). 
Before a generic clock is enabled, the clock source of its generic clock 
generator should be enabled. The generic clock 
must be configured as outlined by the following steps: 
The generic clock generator division factor must be set by performing a 
single 32-bit write to the Generic Clock Generator Division register 
(GENDIV): 
● The generic clock generator that will be selected as the source of 
the generic clock must be written to the ID bit group ([Link]). 


● The division factor must be written to the DIV bit group 
([Link])  
The generic clock generator must be enabled by performing a single 
32-bit write to the Generic Clock Generator Control register (GENCTRL): 
● The generic clock generator that will be selected as the source of 
the generic clock must be written to the ID bit group ([Link]) 
● The generic clock generator must be enabled by writing a one to 
the GENEN bit ([Link]) 
The generic clock must be configured by performing a single 16-bit write 
to the Generic Clock Control register (CLKCTRL): 
● The generic clock that will be configured must be written to the ID 
bit group ([Link]) 
● The generic clock generator used as the source of the generic 
clock must be written to the GEN bit group ([Link]) 
 

   


2.3 NVIC 
The Nested Vectored Interrupt Controller (NVIC) is a device that manage 
the interrupts. In order to enable, disable or modify an interrupt nvic has 
to be set. On SAMD21 NVIC supports 32 interrupt lines with four 
different priority levels. 
   

10 
3. Implementation of the library 
The library is structured as follows. In first instance we have an abstract 
class named ITimer. This abstract class works as an interface and 
common API definition. After that we have the implementation for the 
specific microcontrollers. This project is intended to provide those API 
just for SAMD21 microcontroller, but it was developed with extensibility 
in mind. So it is designed as a modular library with just one 
implementation module. 
 

3.1 ITimer generic interface 


The ITimer abstract class define the common interface that all the 
implementation of the library has to have. In the specific case all the 
implementation has to implement two methods: 
● enable 
● disable 
Enable method has to configure properly the timer peripheral. The 
arguments of this methods are: 
● the timer 
● the frequency (expressed in hertz) 
● the name of the function to call back 
● the priority of the interrupt 
● the clock source 
All those parameters (expect for the frequency that is a float) the type is 
a generic type thanks to the usage of templates. 

11 
 
The template is used at class level, not at method level, in order to make 
the generic type consistent though all the methods of the class. 
This method is defined as a pure virtual method in order to make 
uninstantiable the abstract class. 
​virtual​ ​void​ ​enable​(​TimerNumber​ ​timer​, ​float​ ​freq​,
void​(*​callback​)(), ​Priority​ ​priority​, ​GeneralClock​ ​gclk​) = ​0​;
//Automatic selection of the timer resolution

 
 
With the method so defined the user can not specify the resolution of the 
timer. So there is an overload of the enable method that permit to 
choose the resolution manually thanks to a generic type argument. 
​virtual​ ​void​ ​enable​(​TimerNumber​ ​timer​, ​float​ ​freq​,
void​(*​callback​)(), ​TimerResolution​ ​res​, ​Priority​ ​priority​, ​GeneralClock
gclk​) = ​0​;​ //Manual selection of timer resolution

 
The method disable instead require only the timer to disable as 
parameter. 
​virtual​ ​void​ ​disable​(​TimerNumber​ ​timer​) = ​0​;

 
All those methods are of course public because they are the ones that 
the user will use. 
   

12 
The templated type defined at class level are the following: 
template​ <
​class​ ​TimerNumber​,
​class​ ​TimerResolution​,
​class​ ​TimerParams​,
​class​ ​TimerFlags​,
​class​ ​Callbacks​,
​class​ ​GeneralClock​,
​class​ ​Priority
>
 
so all the implementation of this class has necessary to define those 
data types. 
In conclusion, in order to implement this interface it has to be 
implemented all the data types required by the template and the virtual 
method in order to obtain an instantiable class.   

13 
 

3.2 SAMD21 Implementation 

3.2.1 Samd21 data types 


The first data type to implement is the TimerNumber data type. This one 
is an enum of the available timers, in particular case of SAMD21 is 
defined as follow: 
typedef​ ​enum​{
​TIMER_3​ = ​3​,
​TIMER_4​ = ​4​,
​TIMER_5​ = ​5
} TimerNumberSamd21;

This is a enum of three timers: timer 3, timer 4 and timer 5.  


After this we have to define the available resolution of the timers: 
typedef​ ​enum​{
​RESOLUTION_16_BIT​ = ​16​,
​RESOLUTION_32_BIT​ = ​32
} TimerResolutionSamd21;

because timers can be used with 16 bit resolution or 32 bit resolution. 8 


bit resolution is supported by the hardware, but this library doesn’t 
support them because there are only disadvantages in using them. 
The next data structure defined is timer params, is a struct in which 
there are saved all the params that has to be set on the registers. 
typedef​ ​struct​{
​uint32_t​ modeCount;
​uint32_t​ prescaler;
​uint32_t​ compare;
} ​TimerParamsSamd21​;

14 
There is also a structure in with are saved flags that can be needed 
during the implementation of the interface, this is timer flags. 
typedef​ ​struct​{
​bool​ isActive = ​false​;
TimerResolutionSamd21 res;
} ​TimerFlagsSamd21​;

 
All the callbacks need to be saved, in order to do that there is a data 
structure named CallbacksSamd21 defined as follow: 
typedef​ ​struct​{
​void​ (*​timer_3_routine​)();
​void​ (*​timer_4_routine​)();
​void​ (*​timer_5_routine​)();
} ​CallbacksSamd21​;
 
The last definition is an enum that list the available clock source : 
typedef​ ​enum​{
​GCLK_0​ = ​0​,
​GCLK_1​ = ​1​,
​GCLK_2​ = ​2​,
​GCLK_3​ = ​3​,
​GCLK_4​ = ​4​,
​GCLK_5​ = ​5
} GeneralClockSamd21;

 
   

15 
3.2.2 Methods definition 
In order to make instantiable the class, the implementation of the 
interface has to specialize the template of the interface ITimer. It is done 
in the following way: 
class​ ​Samd21TimerClass​ : ​public​ ITimer<
TimerNumberSamd21,
TimerResolutionSamd21,
TimerParamsSamd21,
TimerFlagsSamd21,
CallbacksSamd21,
GeneralClockSamd21,
uint8_t> {
 
So in this way it resolve the generic type with a real implemented one, for 
example TimerNumber is resolved with TimerNumberSamd21, ecc… 
 
This class implements not only the methods defined by the interface, but 
also more others private methods and attributes. 
In details, class’ attributes are the following: 
​public:
​static​ ​const​ ​int​ timerAmount = ​6​;
TimerFlagsSamd21 ​timerFlags​[timerAmount];
CallbacksSamd21 callbacks;
Tc* ​timerLookUp​[timerAmount] = {​NULL​, ​NULL​, ​NULL​, TC3, TC4,
TC5};

 
● timerAmount is used to store how many timers we have 
● array timerFlags is an array in which are stored the flags previously 
discussed 
● callbacks is a structure where we have saved all the callbacks  

16 
● timerLookUp that is a lookup table which has as index the name of 
the timer and as value the controller of the timer. 
 
The public methods are the ones defined in the interface plus the 
method setTimerBit, that is used in order to set the timer interrupt bit 
after it goes down when the callback occurs. 
​void​ ​enable​(​TimerNumberSamd21​ ​timer​, ​float​ ​freq​,
void​(*​callback​)(), ​uint8_t​ ​priority​ = ​0​, ​GeneralClockSamd21​ ​gclk​ =
GCLK_5);​ //Automatic selection of the timer resolution
​void​ ​enable​(​TimerNumberSamd21​ ​timer​, ​float​ ​freq​,
void​(*​callback​)(), ​TimerResolutionSamd21​ ​res​, ​uint8_t​ ​priority​ = ​0​,
GeneralClockSamd21​ ​gclk​ = GCLK_5);​ //Manual selection of timer
resolution
​void​ ​disable​(​TimerNumberSamd21​ ​timer​);
​template​ <​class​ ​TimerRegisters​> ​void​ ​setTimerBit​(​TimerRegisters
TC​);
 
Instead, the defined private methods are the following: 
● getTimerParams that is used in order to compute the values of the 
registers depending of the user input 
● setGeneralClock that set the timer to use the user selected clock 
source 
● setTimerRegisters that set the registers with the values previously 
computed 
● reset that reset the timer 
● isSyncing that checks if the modifications are applied yet 
● setNVIC that set the timer interrupt in the interrupt controller 
● setCallback that saves the callbacks inside the object 
 
 

17 
​private:
​TimerParamsSamd21​ ​getTimerParams​(​float​ ​freq​,
TimerResolutionSamd21​ ​res​);
​void​ ​setGeneralClock​(​TimerNumberSamd21​ ​timer​,
GeneralClockSamd21​ ​gclk​);
​template​ <​class​ ​TimerRegisters​> ​void
setTimerRegisters​(​TimerNumberSamd21​ ​timer​, ​TimerRegisters​ ​TC​,
TimerParamsSamd21​*​ ​params​, ​uint8_t​ ​priority​, ​GeneralClockSamd21​ ​gclk​);
​template​ <​class​ ​TimerRegisters​> ​void​ ​reset​(​TimerRegisters​ ​TC​);
​template​ <​class​ ​TimerRegisters​> ​bool​ ​isSyncing​(​TimerRegisters
TC​);
​void​ ​setNVIC​(​TimerNumberSamd21​ ​timer​, ​uint8_t​ ​priority​);
​void​ ​setCallback​(​TimerNumberSamd21​ ​timer​, ​void​(*​callback​)());

 
After the class definition we have the definition of the timer handler that 
has to be defined because Arduino HAL requires it. 
extern​ ​void​ ​TC3_Handler​();
extern​ ​void​ ​TC4_Handler​();
extern​ ​void​ ​TC5_Handler​();
 
Next we have the definition of the global object that provides all the 
functionality of the library. 
extern​ Samd21TimerClass Timer;

 
All the class definition is inside a ifdef/endif preprocessor statement in 
order to speedup the compile time when there will be more than one 
implementation. 
Also all the library is inside another ifdef/endif preprocessor statement 
in order to avoid errors of double definition in case for some reasons the 
library is included twice. 
 
#ifndef​ _SAMD21_TIMER_

18 
#define​ _SAMD21_TIMER_

 
#ifdef​ __SAMD21G18A__
 
#endif​ //__SAMD21G18A__

#endif​ //_SAMD21_TIMER_

 
 

3.2.3 Methods implementation 


Let first introduce the implementation of the enable method. We have 
two overload of this method, one that has the timer resolution as input 
and one that compute it itself and internally call the other overload. 
void​ ​Samd21TimerClass​::​enable​(​TimerNumberSamd21​ ​timer​, ​float​ ​freq​,
void​(*​callback​)(), ​uint8_t​ ​priority​, ​GeneralClockSamd21​ ​gclk​){
​if​(freq >= ​0.75​)
​this​->​enable​(timer, freq, callback, RESOLUTION_16_BIT,
priority, gclk);
​else
​this​->​enable​(timer, freq, callback, RESOLUTION_32_BIT,
priority, gclk);
}
 
The enable method is the one that at is inside call all the operations that 
has to be done in order to properly set the timer. 
In details it call the proper method to: 
● set the timer flags 
● save the callback 
● set the correct general clock 

19 
● compute the timer 
● set the registers 
 
void​ ​Samd21TimerClass​::​enable​(​TimerNumberSamd21​ ​timer​, ​float​ ​freq​,
void​(*​callback​)(), ​TimerResolutionSamd21​ ​res​, ​uint8_t​ ​priority​,
GeneralClockSamd21​ ​gclk​){

​this​->​timerFlags​[timer].​res​ = res;
​this​->​timerFlags​[timer].​isActive​ = ​true​;

​this​->​setCallback​(timer, callback);

​this​->​setGeneralClock​(timer, gclk);
TimerParamsSamd21 params = ​this​->​getTimerParams​(freq, res);

​if​ (res == RESOLUTION_32_BIT){


TcCount32* TC;

​switch​ (timer){
​case​ TIMER_3:
TC = (TcCount32*) TC3;
​break​;
​case​ TIMER_4:
TC = (TcCount32*) TC4;
​break​;
​default​:
TC = (TcCount32*) TC5;
​break​;
};

​this​->​setTimerRegisters​(timer, TC, &params, priority, gclk);

​else​ ​if​ (res == RESOLUTION_16_BIT) {


TcCount16* TC;

20 
​switch​ (timer){
​case​ TIMER_3:
TC = (TcCount16*) TC3;
​break​;
​case​ TIMER_4:
TC = (TcCount16*) TC4;
​break​;
​default​:
TC = (TcCount16*) TC5;
​break​;
};

​this​->​setTimerRegisters​(timer, TC, &params, priority, gclk);

}
}
 
Let’s analyze all the mentioned methods. 
The method setCallback take as input the timer and the callback to save 
and store in the correct place of the internal structure. 
void​ ​Samd21TimerClass​::​setCallback​(​TimerNumberSamd21​ ​timer​,
void​(*​callback​)()){

​switch​ (timer) {
​case​ TIMER_3:
​this​->​callbacks​.​timer_3_routine​ = callback;
​break​;
​case​ TIMER_4:
​this​->​callbacks​.​timer_4_routine​ = callback;
​break​;
​case​ TIMER_5:
​default​:
​this​->​callbacks​.​timer_5_routine​ = callback;
​break​;

}
}

21 
After we have the setGeneralClock method. This method has to set the 
clock source to the timer. In order to do that we have to set the GENDIV 
and GENCTRL registers (for more information look at 2.2).  
void​ ​Samd21TimerClass​::​setGeneralClock​(​TimerNumberSamd21​ ​timer​,
GeneralClockSamd21​ ​gclk​){
​unsigned​ ​long​ clockGenCtrlId;
​unsigned​ ​long​ clockGenCtrlGen;

​switch​ (timer){
​case​ TIMER_3:
clockGenCtrlId = GCLK_CLKCTRL_ID_TCC2_TC3;
​break​;
​case​ TIMER_4:
​case​ TIMER_5:
​default​:
clockGenCtrlId = GCLK_CLKCTRL_ID_TC4_TC5;
​break​;
};

​switch​(gclk){
​case​ GCLK_0:
clockGenCtrlGen = GCLK_CLKCTRL_GEN_GCLK0;
​break​;
​case​ GCLK_1:
clockGenCtrlGen = GCLK_CLKCTRL_GEN_GCLK1;
​break​;
​case​ GCLK_2:
clockGenCtrlGen = GCLK_CLKCTRL_GEN_GCLK2;
​break​;
​case​ GCLK_3:
clockGenCtrlGen = GCLK_CLKCTRL_GEN_GCLK3;
​break​;
​case​ GCLK_4:
clockGenCtrlGen = GCLK_CLKCTRL_GEN_GCLK4;
​break​;
​case​ GCLK_5:
​default​:
clockGenCtrlGen = GCLK_CLKCTRL_GEN_GCLK5;

22 
​break​;
};

​GCLK​->​GENDIV​.​reg​ = ​GCLK_GENDIV_DIV​(​1​) |
​GCLK_GENDIV_ID​(gclk);
​while​ (​GCLK​->​STATUS​.​bit​.​SYNCBUSY​);

​GCLK​->​GENCTRL​.​reg​ = GCLK_GENCTRL_IDC |
GCLK_GENCTRL_GENEN |
GCLK_GENCTRL_SRC_DFLL48M |
​GCLK_GENCTRL_ID​(gclk);
​while​ (​GCLK​->​STATUS​.​bit​.​SYNCBUSY​);

​GCLK​->​CLKCTRL​.​reg​ = GCLK_CLKCTRL_CLKEN |
clockGenCtrlGen |
clockGenCtrlId;
​while​ (​GCLK​->​STATUS​.​bit​.​SYNCBUSY​);

 
In order to compute the timer params we have to choose the prescaler 
and the compare value that fits best the required frequency. In order to 
do that the methods check which of the precomputed values fits best 
the frequency. 
TimerParamsSamd21​ ​Samd21TimerClass​::​getTimerParams​(​float​ ​freq​,
TimerResolutionSamd21​ ​res​){
TimerParamsSamd21 params;
​switch​ (res){
​case​ RESOLUTION_16_BIT:
​params​.​modeCount​ = TC_CTRLA_MODE_COUNT16;

​if​ ((freq < ​24000000​) && (freq > ​800​)) {


​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV1;
​params​.​compare​ = SAMD21_CLK/freq;
} ​else​ ​if​ (freq > ​400​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV2;

23 
​params​.​compare​ = (SAMD21_CLK/​2​)/freq;
} ​else​ ​if​ (freq > ​200​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV4;
​params​.​compare​ = (SAMD21_CLK/​4​)/freq;
} ​else​ ​if​ (freq > ​100​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV8;
​params​.​compare​ = (SAMD21_CLK/​8​)/freq;
} ​else​ ​if​ (freq > ​50​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV16;
​params​.​compare​ = (SAMD21_CLK/​16​)/freq;
} ​else​ ​if​ (freq > ​12​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV64;
​params​.​compare​ = (SAMD21_CLK/​64​)/freq;
} ​else​ ​if​ (freq > ​3​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV256;
​params​.​compare​ = (SAMD21_CLK/​256​)/freq;
} ​else​ ​if​ (freq > ​0.75​){
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV1024;
​params​.​compare​ = (SAMD21_CLK/​1024​)/freq;
}

​break​;

​case​ RESOLUTION_32_BIT:
​default​:
​params​.​modeCount​ = TC_CTRLA_MODE_COUNT32;

​if​ (freq > ​0.02​) {


​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV1;
​params​.​compare​ = SAMD21_CLK/freq;
} ​else​ ​if​ (freq > ​0.006​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV2;
​params​.​compare​ = (SAMD21_CLK/​2​)/freq;
} ​else​ ​if​ (freq > ​0.003​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV4;
​params​.​compare​ = (SAMD21_CLK/​4​)/freq;
} ​else​ ​if​ (freq > ​0.0015​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV8;
​params​.​compare​ = (SAMD21_CLK/​8​)/freq;
} ​else​ ​if​ (freq > ​0.0008​) {

24 
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV16;
​params​.​compare​ = (SAMD21_CLK/​16​)/freq;
} ​else​ ​if​ (freq > ​0.0002​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV64;
​params​.​compare​ = (SAMD21_CLK/​64​)/freq;
} ​else​ ​if​ (freq > ​0.00005​) {
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV256;
​params​.​compare​ = (SAMD21_CLK/​256​)/freq;
} ​else​ ​if​ (freq >= ​0.000011​){
​params​.​prescaler​ = TC_CTRLA_PRESCALER_DIV1024;
​params​.​compare​ = (SAMD21_CLK/​1024​)/freq;
}

​break​;
};

​return​ params;
}

After this we have the method setTimerRegisters that set the registers to 
the correct values. In first instance it resets the timer, after that set the 
resolution of count (modeCount), the type of the wavegen (mfrq) and 
the prescaler in the register CTRLA (for more information look at 2.1). 
After that set in the register CC the value to compare. After that before 
going forward the library has to check is the registers has already done 
the requested set operation. After it’s done the library set the interrupt 
controller with the method setNVIC. After that it set the timer interrupt 
bit and wait the end of the operation. At the end it enable the timer 
setting the enable bit in the CTRLA register (for more details look at 2.1) 
and wait the end of the operation. 
template​ <​class​ ​TimerRegisters​> ​void
Samd21TimerClass​::​setTimerRegisters​(​TimerNumberSamd21​ ​timer​,
TimerRegisters​ ​TC​, ​TimerParamsSamd21​*​ ​params​, ​uint8_t​ ​priority​,
GeneralClockSamd21​ ​gclk​){
​this​->​reset​(TC);

25 
​TC​->​CTRLA​.​reg​ |= ​params​->​modeCount​;
​TC​->​CTRLA​.​reg​ |= TC_CTRLA_WAVEGEN_MFRQ;
​TC​->​CTRLA​.​reg​ |= ​params​->​prescaler​;

​TC​->​CC​[​0​].​reg​ = ​params​->​compare​;

​while​ (​this​->​isSyncing​(TC));

​this​->​setNVIC​(timer, priority);

​TC​->​INTENSET​.​bit​.​MC0​ = ​1​;
​while​ (​this​->​isSyncing​(TC));

​TC​->​CTRLA​.​reg​ |= TC_CTRLA_ENABLE;
​while​ (​this​->​isSyncing​(TC));

 
In this method are used the following methods: 
● reset that set the software reset in CTRLA (for more details look at 
2.1), wait the sinc and the end of the reset 
● isSyncing that checks the syncbusy bit 
● setNVIC that disable, clear, set and enable the interrupt 
template​ <​class​ ​TimerRegisters​> ​void
Samd21TimerClass​::​reset​(​TimerRegisters​ ​TC​){
​TC​->​CTRLA​.​reg​ = TC_CTRLA_SWRST;
​while​ (​this​->​isSyncing​(TC));
​while​ (​TC​->​CTRLA​.​bit​.​SWRST​);
}

template​ <​class​ ​TimerRegisters​> ​bool


Samd21TimerClass​::​isSyncing​(​TimerRegisters​ ​TC​){
​return​ ​TC​->​STATUS​.​bit​.​SYNCBUSY​ == ​1​;

26 
}

void​ ​Samd21TimerClass​::​setNVIC​(​TimerNumberSamd21​ ​timer​, ​uint8_t


priority​){
IRQn_Type irqn;
​switch​(timer){
​case​ TIMER_3:
irqn = TC3_IRQn;
​break​;
​case​ TIMER_4:
irqn = TC4_IRQn;
​break​;
​case​ TIMER_5:
​default​:
irqn = TC5_IRQn;
​break​;
};

​NVIC_DisableIRQ​(irqn);
​NVIC_ClearPendingIRQ​(irqn);
​NVIC_SetPriority​(irqn, priority);
​NVIC_EnableIRQ​(irqn);
}

There is also a public method that is used in order to set the interrupt 
timer bit after it goes down when the callback occurres. 
template​ <​class​ ​TimerRegisters​> ​void
Samd21TimerClass​::​setTimerBit​(​TimerRegisters​ ​TC​){
​TC​->​INTFLAG​.​bit​.​MC0​ = ​1​;
}

   

27 
The last method is disable method that disable the timer. In order to do 
that it disable the enable bit in the CTRLA register (for more details 
check 2.1) 
void​ ​Samd21TimerClass​::​disable​(​TimerNumberSamd21​ ​timer​){
TcCount16* TC = (TcCount16*) ​this​->​timerLookUp​[timer];​ //casted to
32 bit because it's the same for each resolution
​TC​->​CTRLA​.​reg​ &= ~TC_CTRLA_ENABLE;
​while​ (​this​->​isSyncing​(TC));
}

   

28 
3.2.4 Handler wrapping 
Arduino HAL when a timer interrupt occurs call a statically named 
function of the type: TCX_Handler. In order to hide this HAL 
implementation detail to the user the library define those function that 
will be called by the Arduino call after the proper configuration of the 
timer with the previously said methods. 
So each timer handler calls its callback and set the timer bit up again. 
void​ ​TC3_Handler​(){
​Timer​.​callbacks​.​timer_3_routine​();
//enable interrupt again
​switch​(​Timer​.​timerFlags​[TIMER_3].​res​){
​case​ RESOLUTION_32_BIT:
​Timer​.​setTimerBit​((TcCount32*) TC3);
​break​;
​case​ RESOLUTION_16_BIT:
​default​:
​Timer​.​setTimerBit​((TcCount16*) TC3);
​break​;
};
}

void​ ​TC4_Handler​(){
​Timer​.​callbacks​.​timer_4_routine​();
//enable interrupt again
​switch​(​Timer​.​timerFlags​[TIMER_4].​res​){
​case​ RESOLUTION_32_BIT:
​Timer​.​setTimerBit​((TcCount32*) TC4);
​break​;
​case​ RESOLUTION_16_BIT:
​default​:
​Timer​.​setTimerBit​((TcCount16*) TC4);
​break​;
};
}

29 
void​ ​TC5_Handler​(){
​Timer​.​callbacks​.​timer_5_routine​();
//enable interrupt again
​switch​(​Timer​.​timerFlags​[TIMER_5].​res​){
​case​ RESOLUTION_32_BIT:
​Timer​.​setTimerBit​((TcCount32*) TC5);
​break​;
​case​ RESOLUTION_16_BIT:
​default​:
​Timer​.​setTimerBit​((TcCount16*) TC5);
​break​;
};
}

 
 

   

30 
3.3 CI implementation and code quality 
In order to make it smarter the future development of the library there 
are implemented two CI script of Github actions. Those scripts runs 
each time a push or a pull request happens. In this way the system 
automatically checks if the modification that is going to do are 
compliant to the standard defined by the master of the repository. 
Those two script provide those two features: 
● check if all the library examples compiles on each operative 
system and in both arduino and platformio environment  
● check if the code is compliant to industry quality standards 
 

3.3.1 Compile script 


The compile script perform a matrix build in each available operating 
system and for each examples. The script define two jobs: 
● platformio compilation 
● arduino cli compilation 
Those jobs runs in parallel on different machines hosted on Github 
cloud. 
The platformio job for each operative system download the python 
container, on top of it download platformio and all the related 
dependencies, and after that compile all the examples for the board 
Arduino Zero that is built with SAMD21 (this operation automatically 
install all the toolchain of Arduino under the hood). 
The arduino job install the arduino container on windows and ubuntu 
(macos is still not well supported). After that updates the core index, 

31 
install the platform samd with all the related toolchain, install the arduino 
zero board and after that compile the example. This job is commented 
because the arduino cli action still don’t support the library compiling, so 
it is ready for the time it will be released. 
name​: ​Compile test

on​: [​push​, ​pull_request​]

jobs​:
​build-pio​:
​runs-on​: ​${{ [Link] }}
​strategy​:
​matrix​:
​os​: [​ubuntu-latest​, ​windows-latest​, ​macos-latest​]
​example​: [​examples/empty_sketch.ino​, ​examples/serial_blink.ino​,
examples/serial_timer.ino​]

​steps​:
- ​uses​: ​actions/checkout@v1
- ​name​: ​Set up Python
​uses​: ​actions/setup-python@v1
​with​:
​repo-token​: ​${{ secrets.GITHUB_TOKEN }}

- ​name​: ​Install dependencies


​run​: ​|
python -m pip install --upgrade pip
pip install platformio

- ​name​: ​Run PlatformIO


​run​: ​platformio ci --lib="." --board=zeroUSB
​env​:
​PLATFORMIO_CI_SRC​: ​${{ [Link] }}

​# build-arduino:
​# runs-on: ${{ [Link] }}
​# strategy:

32 
​# matrix:
​# os: [ubuntu-latest, windows-latest]
​# example: [examples]

​# steps:

​# - name: Setup Arduino CLI


​# uses: arduino/[email protected]

​# - name: Run Arduino CLI


​# run: |
​# arduino-cli core update-index
​# arduino-cli core install arduino:samd
​# arduino-cli board listall arduino_zero_native
​# echo "compiling ${{ [Link] }}..."
​# arduino-cli compile --fqbn arduino:samd:arduino_zero_native
${{ [Link] }}

3.3.2 Code quality 


The code quality script use as quality engine codacy. The codacy action 
provide a trigger to run codacy each time someone push something or 
make a pull request. So the codacy action makes pass the test only if 
the code is compliant to a preset of quality defined by the user in codacy 
website. 
name​: ​Code quality

on​: [​push​, ​pull_request​]

jobs​:
​codacy-analysis-cli​:
​runs-on​: ​ubuntu-latest
​name​: ​codacy-analysis-cli
​steps​:
- ​uses​: ​actions/checkout@master

33 
- ​name​: ​Run codacy-analysis-cli
​uses​: ​mrfyda/codacy-analysis-cli-action@master
​with​:
​project-token​: ​${{ secrets.CODACY_PROJECT_TOKEN }}

34 

You might also like