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
2
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
3
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.
4
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
5
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
6
● 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:
7
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]).
8
● 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])
9
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, ¶ms, 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, ¶ms, 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