- Important Change from v1.6.0
- Why do we need this SAMD_TimerInterrupt library
- Changelog
- Prerequisites
- Installation
- Packages' Patches
- Libraries' Patches
- HOWTO Fix
Multiple DefinitionsLinker Error - New from v1.0.0
- Usage
- Examples
- 1. Argument_None
- 2. Argument_None_uS
- 3. ISR_16_Timers_Array
- 4. ISR_RPM_Measure
- 5. ISR_Timer_Complex_Ethernet
- 6. ISR_Timer_Complex_WiFiNINA
- 7. RPM_Measure
- 8. SwitchDebounce
- 9. TimerInterruptTest
- 10. TimerInterruptLEDDemo
- 11. Change_Interval
- 12. ISR_16_Timers_Array_Complex
- 13. RepeatedAttachInterrupt_uS
- 14. multiFileProject New
- Example ISR_16_Timers_Array_Complex
- Debug Terminal Output Samples
- 1. ISR_Timer_Complex_WiFiNINA on Arduino SAMD21 SAMD_NANO_33_IOT using WiFiNINA
- 2. TimerInterruptTest on Adafruit SAMD51 ITSYBITSY_M4
- 3. Argument_None on Arduino SAMD21 SAMD_NANO_33_IOT
- 4. ISR_16_Timers_Array on Arduino SAMD21 SAMD_NANO_33_IOT
- 5. Change_Interval on Arduino SAMD21 SAMD_NANO_33_IOT
- 6. RepeatedAttachInterrupt_uS on SAMD21 SEEED_XIAO_M0
- Debug
- Troubleshooting
- Issues
- TO DO
- DONE
- Contributions and Thanks
- Contributing
- License
- Copyright
Please have a look at HOWTO Fix Multiple Definitions Linker Error
Why do we need this SAMD_TimerInterrupt library
This library enables you to use Interrupt from Hardware Timers on an SAMD-based board, such as SAMD21 Nano-33-IoT, Adafruit SAMD51 Itsy-Bitsy M4, SparkFun SAMD51 MicroMod, etc.
As Hardware Timers are rare, and very precious assets of any board, this library now enables you to use up to 16 ISR-based Timers, while consuming only 1 Hardware Timer. Timers' interval is very long (ulong millisecs).
Now with these new 16 ISR-based timers, the maximum interval is practically unlimited (limited only by unsigned long miliseconds) while the accuracy is nearly perfect compared to software timers.
The most important feature is they're ISR-based timers. Therefore, their executions are not blocked by bad-behaving functions / tasks. This important feature is absolutely necessary for mission-critical tasks.
The ISR_Timer_Complex example will demonstrate the nearly perfect accuracy compared to software timers by printing the actual elapsed millisecs of each type of timers.
Being ISR-based timers, their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet and Blynk services. You can also have many (up to 16) timers to use.
This non-being-blocked important feature is absolutely necessary for mission-critical tasks.
You'll see blynkTimer Software is blocked while system is connecting to WiFi / Internet / Blynk, as well as by blocking task in loop(), using delay() function as an example. The elapsed time then is very unaccurate
Imagine you have a system with a mission-critical function, measuring water level and control the sump pump or doing something much more important. You normally use a software timer to poll, or even place the function in loop(). But what if another function is blocking the loop() or setup().
So your function might not be executed, and the result would be disastrous.
You'd prefer to have your function called, no matter what happening with other functions (busy loop, bug, etc.).
The correct choice is to use a Hardware Timer with Interrupt to call your function.
These hardware timers, using interrupt, still work even if other functions are blocking. Moreover, they are much more precise (certainly depending on clock frequency accuracy) than other software timers using millis() or micros(). That's necessary if you need to measure some data requiring better accuracy.
Functions using normal software timers, relying on loop() and calling millis(), won't work if the loop() or setup() is blocked by certain operation. For example, certain function is blocking while it's connecting to WiFi or some services.
The catch is your function is now part of an ISR (Interrupt Service Routine), and must be lean / mean, and follow certain rules. More to read on:
- Arduino SAMD21 (ZERO, MKR, NANO_33_IOT, etc.).
- Adafruit SAM21 (Itsy-Bitsy M0, Metro M0, Feather M0, Gemma M0, etc.).
- Adafruit SAM51 (Itsy-Bitsy M4, Metro M4, Grand Central M4, Feather M4 Express, etc.).
- Seeeduino SAMD21/SAMD51 boards (SEEED_WIO_TERMINAL, SEEED_FEMTO_M0, SEEED_XIAO_M0, Wio_Lite_MG126, WIO_GPS_BOARD, SEEEDUINO_ZERO, SEEEDUINO_LORAWAN, SEEED_GROVE_UI_WIRELESS, etc.)
- Sparkfun SAMD21 boards such as SparkFun_RedBoard_Turbo, SparkFun_Qwiic_Micro, etc.
- Sparkfun SAMD51 boards such as SparkFun_SAMD51_Thing_Plus, SparkFun_SAMD51_MicroMod, etc.
-
Inside the attached function, delay() won’t work and the value returned by millis() will not increment. Serial data received while in the function may be lost. You should declare as volatile any variables that you modify within the attached function.
-
Typically global variables are used to pass data between an ISR and the main program. To make sure variables shared between an ISR and the main program are updated correctly, declare them as volatile.
Arduino IDE 1.8.19+for Arduino.Arduino SAMD core 1.8.13+for SAMD ARM Cortex-M0+ boards.Adafruit SAMD core 1.7.10+for SAMD ARM Cortex-M0+ and M4 boards (Nano 33 IoT, etc.).Seeeduino SAMD core 1.8.2+for SAMD21/SAMD51 boards (XIAO M0, Wio Terminal, etc.).Sparkfun SAMD core 1.8.1+for SAMD21/SAMD51 boards (SparkFun_RedBoard_Turbo, SparkFun_SAMD51_Thing_Plus, etc.).Blynk library 1.0.1.to use with some examples. Don't use Blynk beta versions.
- To use with some examples, depending on which Ethernet card you're using:
Ethernet_Generic library v2.1.0+for W5100, W5200 and W5500/WIZ550io/WIZ850io/USR-ES1 with Wiznet W5500 chip.EthernetENC library v2.0.2+for ENC28J60.. New and Better
UIPEthernet library v2.0.11+for ENC28J60.
- To use with some examples
SimpleTimer libraryfor ISR_16_Timers_Array and ISR_16_Timers_Array_Complex examples. New to avoid error witholdSparkfun core.
The best and easiest way is to use Arduino Library Manager. Search for SAMD_TimerInterrupt, then select / install the latest version. You can also use this link for more detailed instructions.
Another way to install is to:
- Navigate to SAMD_TimerInterrupt page.
- Download the latest release
SAMD_TimerInterrupt-master.zip. - Extract the zip file to
SAMD_TimerInterrupt-masterdirectory - Copy whole
SAMD_TimerInterrupt-masterfolder to Arduino libraries' directory such as~/Arduino/libraries/.
- Install VS Code
- Install PlatformIO
- Install SAMD_TimerInterrupt library by using Library Manager. Search for SAMD_TimerInterrupt in Platform.io Author's Libraries
- Use included platformio.ini file from examples to ensure that all dependent libraries will installed automatically. Please visit documentation for the other options and examples at Project Configuration File
To be able to compile, run and automatically detect and display BOARD_NAME on Arduino SAMD (Nano-33-IoT, etc) boards, you have to copy the whole Arduino SAMD Packages_Patches directory into Arduino SAMD directory (~/.arduino15/packages/arduino/hardware/samd/1.8.13).
Supposing the Arduino SAMD version is 1.8.13. Now only one file must be copied into the directory:
~/.arduino15/packages/arduino/hardware/samd/1.8.13/platform.txt
Whenever a new version is installed, remember to copy this files into the new version directory. For example, new version is x.yy.zz
This file must be copied into the directory:
~/.arduino15/packages/arduino/hardware/samd/x.yy.zz/platform.txt
Supposing the Arduino SAMD version is 1.8.9. These files must be copied into the directory:
~/.arduino15/packages/arduino/hardware/samd/1.8.9/platform.txt~/.arduino15/packages/arduino/hardware/samd/1.8.9/cores/arduino/Arduino.h
Whenever a new version is installed, remember to copy these files into the new version directory. For example, new version is x.yy.z
These files must be copied into the directory:
~/.arduino15/packages/arduino/hardware/samd/x.yy.z/platform.txt~/.arduino15/packages/arduino/hardware/samd/x.yy.z/cores/arduino/Arduino.h
This is mandatory to fix the notorious Arduino SAMD compiler error. See Improve Arduino compatibility with the STL (min and max macro)
...\arm-none-eabi\include\c++\7.2.1\bits\stl_algobase.h:243:56: error: macro "min" passed 3 arguments, but takes just 2 min(const _Tp& __a, const _Tp& __b, _Compare __comp) Whenever the above-mentioned compiler error issue is fixed with the new Arduino SAMD release, you don't need to copy the Arduino.h file anymore.
To be able to compile, run and automatically detect and display BOARD_NAME on Adafruit SAMD (Itsy-Bitsy M4, etc) boards, you have to copy the whole Adafruit SAMD Packages_Patches directory into Adafruit samd directory (~/.arduino15/packages/adafruit/hardware/samd/1.7.9).
Supposing the Adafruit SAMD core version is 1.7.9. This file must be copied into the directory:
~/.arduino15/packages/adafruit/hardware/samd/1.7.9/platform.txt~/.arduino15/packages/adafruit/hardware/samd/1.7.9/cores/arduino/Print.h~/.arduino15/packages/adafruit/hardware/samd/1.7.9/cores/arduino/Print.cpp
Whenever a new version is installed, remember to copy this file into the new version directory. For example, new version is x.yy.zz This file must be copied into the directory:
~/.arduino15/packages/adafruit/hardware/samd/x.yy.zz/platform.txt~/.arduino15/packages/adafruit/hardware/samd/x.yy.zz/cores/arduino/Print.h~/.arduino15/packages/adafruit/hardware/samd/x.yy.zz/cores/arduino/Print.cpp
To be able to compile, run and automatically detect and display BOARD_NAME on Seeeduino SAMD (XIAO M0, Wio Terminal, etc) boards, you have to copy the whole Seeeduino SAMD Packages_Patches directory into Seeeduino samd directory (~/.arduino15/packages/Seeeduino/hardware/samd/1.8.2).
Supposing the Seeeduino SAMD core version is 1.8.2. This file must be copied into the directory:
~/.arduino15/packages/Seeeduino/hardware/samd/1.8.2/platform.txt~/.arduino15/packages/Seeeduino/hardware/samd/1.8.2/cores/arduino/Arduino.h~/.arduino15/packages/Seeeduino/hardware/samd/1.8.2/cores/arduino/Print.h~/.arduino15/packages/Seeeduino/hardware/samd/1.8.2/cores/arduino/Print.cpp
Whenever a new version is installed, remember to copy this file into the new version directory. For example, new version is x.yy.zz This file must be copied into the directory:
~/.arduino15/packages/Seeeduino/hardware/samd/x.yy.zz/platform.txt~/.arduino15/packages/Seeeduino/hardware/samd/x.yy.zz/cores/arduino/Arduino.h~/.arduino15/packages/Seeeduino/hardware/samd/x.yy.zz/cores/arduino/Print.h~/.arduino15/packages/Seeeduino/hardware/samd/x.yy.zz/cores/arduino/Print.cpp
Notes: These patches are totally optional and necessary only when you use the related Ethernet library and get certain error or issues.
If your application requires 2K+ HTML page, the current Ethernet library must be modified if you are using W5200/W5500 Ethernet shields. W5100 is not supported for 2K+ buffer. If you use boards requiring different CS/SS pin for W5x00 Ethernet shield, for example ESP32, ESP8266, nRF52, etc., you also have to modify the following libraries to be able to specify the CS/SS pin correctly.
To fix Ethernet library, just copy these following files into the Ethernet library directory to overwrite the old files:
To fix EthernetLarge library, just copy these following files into the EthernetLarge library directory to overwrite the old files:
To fix Ethernet2 library, just copy these following files into the Ethernet2 library directory to overwrite the old files:
To add UDP Multicast support, necessary for the UPnP_Generic library:
- To fix
Ethernet3 library, just copy these following files into theEthernet3 librarydirectory to overwrite the old files:
To be able to compile and run on nRF52 boards with ENC28J60 using UIPEthernet library, you have to copy these following files into the UIPEthernet utility directory to overwrite the old files:
To fix ESP32 compile error, just copy the following file into the ESP32 cores/esp32 directory (e.g. ./arduino-1.8.12/hardware/espressif/cores/esp32) to overwrite the old file:
The current library implementation, using xyz-Impl.h instead of standard xyz.cpp, possibly creates certain Multiple Definitions Linker error in certain use cases.
You can include .hpp
// Can be included as many times as necessary, without `Multiple Definitions` Linker Error #include "SAMDTimerInterrupt.hpp" //https://github.com/khoih-prog/SAMD_TimerInterrupt // Can be included as many times as necessary, without `Multiple Definitions` Linker Error #include "SAMD_ISR_Timer.hpp" //https://github.com/khoih-prog/SAMD_TimerInterrupt in many files. But be sure to use the following .h files in just 1 .h, .cpp or .ino file, which must not be included in any other file, to avoid Multiple Definitions Linker Error
// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error #include "SAMDTimerInterrupt.h" //https://github.com/khoih-prog/SAMD_TimerInterrupt // To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error #include "SAMD_ISR_Timer.h" //https://github.com/khoih-prog/SAMD_TimerInterrupt Now with these new 16 ISR-based timers (while consuming only 1 hardware timer), the maximum interval is practically unlimited (limited only by unsigned long miliseconds). The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers Therefore, their executions are not blocked by bad-behaving functions / tasks. This important feature is absolutely necessary for mission-critical tasks.
The ISR_Timer_Complex example will demonstrate the nearly perfect accuracy compared to software timers by printing the actual elapsed millisecs of each type of timers. Being ISR-based timers, their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet and Blynk services. You can also have many (up to 16) timers to use. This non-being-blocked important feature is absolutely necessary for mission-critical tasks. You'll see blynkTimer Software is blocked while system is connecting to WiFi / Internet / Blynk, as well as by blocking task in loop(), using delay() function as an example. The elapsed time then is very unaccurate
Before using any Timer, you have to make sure the Timer has not been used by any other purpose.
// Depending on the board, you can select SAMD21 Hardware Timer from TC3-TCC // SAMD21 Hardware Timer from TC3 or TCC // SAMD51 Hardware Timer only TC3 SAMDTimer ITimer0(TIMER_TC3); void TimerHandler0(void) { // Doing something here inside ISR } #define TIMER0_INTERVAL_MS 1000 // 1s = 1000ms void setup() { .... // Interval in microsecs if (ITimer0.attachInterruptInterval(TIMER0_INTERVAL_MS * 1000, TimerHandler0)) Serial.println("Starting ITimer0 OK, millis() = " + String(millis())); else Serial.println("Can't set ITimer0. Select another freq. or timer"); } // Depending on the board, you can select SAMD21 Hardware Timer from TC3-TCC // SAMD21 Hardware Timer from TC3 or TCC // SAMD51 Hardware Timer only TC3 SAMDTimer ITimer0(TIMER_TC3); // Init SAMD_ISR_Timer // Each SAMD_ISR_Timer can service 16 different ISR-based timers SAMD_ISR_Timer ISR_Timer; void TimerHandler(void) { ISR_Timer.run(); } #define HW_TIMER_INTERVAL_MS 50L #define TIMER_INTERVAL_2S 2000L #define TIMER_INTERVAL_5S 5000L #define TIMER_INTERVAL_11S 11000L #define TIMER_INTERVAL_101S 101000L // In SAMD, avoid doing something fancy in ISR, for example complex Serial.print with String() argument // The pure simple Serial.prints here are just for demonstration and testing. Must be eliminate in working environment // Or you can get this run-time error / crash void doingSomething2s() { // Doing something here inside ISR } void doingSomething5s() { // Doing something here inside ISR } void doingSomething11s() { // Doing something here inside ISR } void doingSomething101s() { // Doing something here inside ISR } void setup() { .... // Interval in microsecs if (ITimer.attachInterruptInterval(HW_TIMER_INTERVAL_MS * 1000, TimerHandler)) { lastMillis = millis(); Serial.println("Starting ITimer OK, millis() = " + String(lastMillis)); } else Serial.println("Can't set ITimer correctly. Select another freq. or interval"); // Just to demonstrate, don't use too many ISR Timers if not absolutely necessary // You can use up to 16 timer for each ISR_Timer ISR_Timer.setInterval(TIMER_INTERVAL_2S, doingSomething2s); ISR_Timer.setInterval(TIMER_INTERVAL_5S, doingSomething5s); ISR_Timer.setInterval(TIMER_INTERVAL_11S, doingSomething11s); ISR_Timer.setInterval(TIMER_INTERVAL_101S, doingSomething101s); } - Argument_None
- Argument_None_uS.
- ISR_16_Timers_Array
- ISR_RPM_Measure
- ISR_Timer_Complex_Ethernet
- ISR_Timer_Complex_WiFiNINA
- RPM_Measure
- SwitchDebounce
- TimerInterruptTest
- TimerInterruptLEDDemo
- Change_Interval. New
- ISR_16_Timers_Array_Complex.
- RepeatedAttachInterrupt_uS.
- multiFileProject. New
Example ISR_16_Timers_Array_Complex
The following is the sample terminal output when running example ISR_Timer_Complex_WiFiNINA on SAMD_NANO_33_IOT using Built-in WiFiNINA to demonstrate the accuracy of ISR Hardware Timer, especially when system is very busy. The ISR timer is programmed for 2s, is activated exactly after 2.000s !!!
While software timer, programmed for 2s, is activated after 7.937s !!!. Then in loop(), it's also activated every 3s.
Starting ISR_Timer_Complex_WiFiNINA on SAMD_NANO_33_IOT SAMDTimerInterrupt v1.7.0 CPU Frequency = 48 MHz [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Starting ITimer OK, millis() = 810 Start Blynk [1571] WiFiNINA Firmware Version: 1.4.1 [1572] Con2:HueNet1 2s: Delta ms = 2000 [5439] ConW OK [5439] IP:192.168.2.98 [5439] ___ __ __ / _ )/ /_ _____ / /__ / _ / / // / _ \/ '_/ /____/_/\_, /_//_/_/\_\ /___/ v0.6.1 on NANO_33_IOT [5440] BlynkArduinoClient.connect: Connecting to account.duckdns.org:8080 [5673] Ready (ping: 6ms). IP = 192.168.2.98 2s: Delta ms = 2000 blynkDoingSomething2s: Delta programmed ms = 2000, actual = 7937 2s: Delta ms = 2000 2s: Delta ms = 2000 5s: Delta ms = 5000 blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3002 2s: Delta ms = 2000 blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3002 2s: Delta ms = 2000 5s: Delta ms = 5000 2s: Delta ms = 2000 blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3002 2s: Delta ms = 2000 blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3004 2s: Delta ms = 2000 5s: Delta ms = 5000 2s: Delta ms = 2000 11s: Delta ms = 11000 blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3002 2s: Delta ms = 2000 5s: Delta ms = 5000 blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3003 2s: Delta ms = 2000 2s: Delta ms = 2000 blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3002 2s: Delta ms = 2000 5s: Delta ms = 5000 blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3004 2s: Delta ms = 2000 11s: Delta ms = 11000 2s: Delta ms = 2000 blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3002 5s: Delta ms = 5000 2s: Delta ms = 2000 The following is the sample terminal output when running example TimerInterruptTest on Adafruit SAMD51 ITSYBITSY_M4 to demonstrate how to start/stop Hardware Timers.
Starting TimerInterruptTest on ITSYBITSY_M4 SAMDTimerInterrupt v1.7.0 CPU Frequency = 48 MHz [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 120 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 0x4101c000 , TC3 = 0x 0x4101c000 Starting ITimer1 OK, millis() = 1820 ITimer0: millis() = 2820, delta = 1000 ITimer0: millis() = 3820, delta = 1000 ITimer0: millis() = 4820, delta = 1000 Stop ITimer0, millis() = 5001 Start ITimer0, millis() = 10002 ITimer0: millis() = 11002, delta = 1000 ITimer0: millis() = 12002, delta = 1000 ITimer0: millis() = 13002, delta = 1000 ITimer0: millis() = 14002, delta = 1000 ITimer0: millis() = 15002, delta = 1000 Stop ITimer0, millis() = 15003 Start ITimer0, millis() = 20004 ITimer0: millis() = 21004, delta = 1000 ITimer0: millis() = 22004, delta = 1000 ITimer0: millis() = 23004, delta = 1000 ITimer0: millis() = 24004, delta = 1000 ITimer0: millis() = 25004, delta = 1000 Stop ITimer0, millis() = 25005 Start ITimer0, millis() = 30006 ITimer0: millis() = 31006, delta = 1000 ITimer0: millis() = 32006, delta = 1000 ITimer0: millis() = 33006, delta = 1000 ITimer0: millis() = 34006, delta = 1000 ITimer0: millis() = 35006, delta = 1000 Stop ITimer0, millis() = 35007 Start ITimer0, millis() = 40008 ITimer0: millis() = 41008, delta = 1000 ITimer0: millis() = 42008, delta = 1000 ITimer0: millis() = 43008, delta = 1000 ITimer0: millis() = 44008, delta = 1000 ITimer0: millis() = 45008, delta = 1000 Stop ITimer0, millis() = 45009 Start ITimer0, millis() = 50010 ITimer0: millis() = 51010, delta = 1000 ITimer0: millis() = 52010, delta = 1000 ITimer0: millis() = 53010, delta = 1000 ITimer0: millis() = 54010, delta = 1000 ITimer0: millis() = 55010, delta = 1000 Stop ITimer0, millis() = 55011 Start ITimer0, millis() = 60012 ITimer0: millis() = 61012, delta = 1000 ITimer0: millis() = 62012, delta = 1000 ITimer0: millis() = 63012, delta = 1000 ITimer0: millis() = 64012, delta = 1000 ITimer0: millis() = 65012, delta = 1000 Stop ITimer0, millis() = 65013 Start ITimer0, millis() = 70014 ITimer0: millis() = 71014, delta = 1000 ITimer0: millis() = 72014, delta = 1000 ITimer0: millis() = 73014, delta = 1000 ITimer0: millis() = 74014, delta = 1000 ITimer0: millis() = 75014, delta = 1000 Stop ITimer0, millis() = 75015 Start ITimer0, millis() = 80016 ITimer0: millis() = 81016, delta = 1000 ITimer0: millis() = 82016, delta = 1000 ITimer0: millis() = 83016, delta = 1000 ITimer0: millis() = 84016, delta = 1000 ITimer0: millis() = 85016, delta = 1000 Stop ITimer0, millis() = 85017 Start ITimer0, millis() = 90018 ITimer0: millis() = 91018, delta = 1000 The following is the sample terminal output when running example Argument_None on Arduino SAMD21 SAMD_NANO_33_IOT to demonstrate how to start/stop Multiple Hardware Timers.
Starting Argument_None on SAMD_NANO_33_IOT SAMDTimerInterrupt v1.7.0 CPU Frequency = 48 MHz [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Starting ITimer1 OK, millis() = 910 F_CPU (MHz) = 48 TC_Timer::startTimer _Timer = 0x42002000, TCC0 = 0x42002000 Starting ITimer1 OK, millis() = 911 ITimer0: millis() = 1410, delta = 500 ITimer0: millis() = 1910, delta = 500 ITimer0: millis() = 2410, delta = 500 ITimer0: millis() = 2910, delta = 500 ITimer1: millis() = 2911, delta = 2000 ITimer0: millis() = 3410, delta = 500 ITimer0: millis() = 3910, delta = 500 ITimer0: millis() = 4410, delta = 500 ITimer0: millis() = 4910, delta = 500 ITimer1: millis() = 4911, delta = 2000 ITimer0: millis() = 5410, delta = 500 ITimer0: millis() = 5910, delta = 500 ITimer0: millis() = 6410, delta = 500 ITimer0: millis() = 6910, delta = 500 ITimer1: millis() = 6911, delta = 2000 ITimer0: millis() = 7410, delta = 500 ITimer0: millis() = 7910, delta = 500 ITimer0: millis() = 8410, delta = 500 ITimer0: millis() = 8910, delta = 500 ITimer1: millis() = 8911, delta = 2000 ITimer0: millis() = 9410, delta = 500 ITimer0: millis() = 9910, delta = 500 ITimer0: millis() = 10410, delta = 500 ITimer0: millis() = 10910, delta = 500 ITimer1: millis() = 10911, delta = 2000 ITimer0: millis() = 11410, delta = 500 ITimer0: millis() = 11910, delta = 500 The following is the sample terminal output when running example ISR_16_Timers_Array on Arduino SAMD21 SAMD_NANO_33_IOT to demonstrate the accuracy of ISR Hardware Timer, especially when system is very busy or blocked. The 16 independent ISR timers are programmed to be activated repetitively after certain intervals, is activated exactly after that programmed interval !!!
While software timer, programmed for 2s, is activated after 10.000s in loop()!!!.
In this example, 16 independent ISR Timers are used, yet utilized just one Hardware Timer. The Timer Intervals and Function Pointers are stored in arrays to facilitate the code modification.
Starting ISR_16_Timers_Array on SAMD_NANO_33_IOT SAMDTimerInterrupt v1.7.0 CPU Frequency = 48 MHz [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Starting ITimer OK, millis() = 1421 1s: Delta ms = 1000, ms = 2421 1s: Delta ms = 1000, ms = 3421 2s: Delta ms = 2000, ms = 3421 1s: Delta ms = 1000, ms = 4421 3s: Delta ms = 3000, ms = 4421 1s: Delta ms = 1000, ms = 5421 2s: Delta ms = 2000, ms = 5421 4s: Delta ms = 4000, ms = 5421 1s: Delta ms = 1000, ms = 6421 5s: Delta ms = 5000, ms = 6421 1s: Delta ms = 1000, ms = 7421 2s: Delta ms = 2000, ms = 7421 3s: Delta ms = 3000, ms = 7421 6s: Delta ms = 6000, ms = 7421 1s: Delta ms = 1000, ms = 8421 7s: Delta ms = 7000, ms = 8421 1s: Delta ms = 1000, ms = 9421 2s: Delta ms = 2000, ms = 9421 4s: Delta ms = 4000, ms = 9421 8s: Delta ms = 8000, ms = 9421 1s: Delta ms = 1000, ms = 10421 3s: Delta ms = 3000, ms = 10421 9s: Delta ms = 9000, ms = 10421 1s: Delta ms = 1000, ms = 11421 2s: Delta ms = 2000, ms = 11421 5s: Delta ms = 5000, ms = 11421 10s: Delta ms = 10000, ms = 11421 simpleTimerDoingSomething2s: Delta programmed ms = 2000, actual = 10000 1s: Delta ms = 1000, ms = 12421 11s: Delta ms = 11000, ms = 12421 1s: Delta ms = 1000, ms = 13421 2s: Delta ms = 2000, ms = 13421 3s: Delta ms = 3000, ms = 13421 4s: Delta ms = 4000, ms = 13421 6s: Delta ms = 6000, ms = 13421 12s: Delta ms = 12000, ms = 13421 1s: Delta ms = 1000, ms = 14421 13s: Delta ms = 13000, ms = 14421 1s: Delta ms = 1000, ms = 15421 2s: Delta ms = 2000, ms = 15421 7s: Delta ms = 7000, ms = 15421 14s: Delta ms = 14000, ms = 15421 1s: Delta ms = 1000, ms = 16421 3s: Delta ms = 3000, ms = 16421 5s: Delta ms = 5000, ms = 16421 15s: Delta ms = 15000, ms = 16421 1s: Delta ms = 1000, ms = 17421 2s: Delta ms = 2000, ms = 17421 4s: Delta ms = 4000, ms = 17421 8s: Delta ms = 8000, ms = 17421 16s: Delta ms = 16000, ms = 17421 1s: Delta ms = 1000, ms = 18421 1s: Delta ms = 1000, ms = 19421 2s: Delta ms = 2000, ms = 19421 3s: Delta ms = 3000, ms = 19421 6s: Delta ms = 6000, ms = 19421 9s: Delta ms = 9000, ms = 19421 1s: Delta ms = 1000, ms = 20421 1s: Delta ms = 1000, ms = 21421 2s: Delta ms = 2000, ms = 21421 4s: Delta ms = 4000, ms = 21421 5s: Delta ms = 5000, ms = 21421 10s: Delta ms = 10000, ms = 21421 simpleTimerDoingSomething2s: Delta programmed ms = 2000, actual = 10000 1s: Delta ms = 1000, ms = 22421 3s: Delta ms = 3000, ms = 22421 7s: Delta ms = 7000, ms = 22421 1s: Delta ms = 1000, ms = 23421 2s: Delta ms = 2000, ms = 23421 11s: Delta ms = 11000, ms = 23421 1s: Delta ms = 1000, ms = 24421 1s: Delta ms = 1000, ms = 25421 2s: Delta ms = 2000, ms = 25421 3s: Delta ms = 3000, ms = 25421 4s: Delta ms = 4000, ms = 25421 6s: Delta ms = 6000, ms = 25421 8s: Delta ms = 8000, ms = 25421 12s: Delta ms = 12000, ms = 25421 1s: Delta ms = 1000, ms = 26421 5s: Delta ms = 5000, ms = 26421 1s: Delta ms = 1000, ms = 27421 2s: Delta ms = 2000, ms = 27421 13s: Delta ms = 13000, ms = 27421 1s: Delta ms = 1000, ms = 28421 3s: Delta ms = 3000, ms = 28421 9s: Delta ms = 9000, ms = 28421 1s: Delta ms = 1000, ms = 29421 2s: Delta ms = 2000, ms = 29421 4s: Delta ms = 4000, ms = 29421 7s: Delta ms = 7000, ms = 29421 14s: Delta ms = 14000, ms = 29421 1s: Delta ms = 1000, ms = 30421 1s: Delta ms = 1000, ms = 31421 2s: Delta ms = 2000, ms = 31421 3s: Delta ms = 3000, ms = 31421 5s: Delta ms = 5000, ms = 31421 6s: Delta ms = 6000, ms = 31421 10s: Delta ms = 10000, ms = 31421 15s: Delta ms = 15000, ms = 31421 simpleTimerDoingSomething2s: Delta programmed ms = 2000, actual = 10000 1s: Delta ms = 1000, ms = 32421 1s: Delta ms = 1000, ms = 33421 2s: Delta ms = 2000, ms = 33421 4s: Delta ms = 4000, ms = 33421 8s: Delta ms = 8000, ms = 33421 16s: Delta ms = 16000, ms = 33421 1s: Delta ms = 1000, ms = 34421 3s: Delta ms = 3000, ms = 34421 11s: Delta ms = 11000, ms = 34421 The following is the sample terminal output when running example Change_Interval on Arduino SAMD21 SAMD_NANO_33_IOT to demonstrate how to change Timer Interval on-the-fly
Starting Change_Interval on SAMD_NANO_33_IOT SAMDTimerInterrupt v1.7.0 CPU Frequency = 48 MHz [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Starting ITimer OK, millis() = 1131 Time = 10001, TimerCount = 17 Time = 20002, TimerCount = 37 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Changing Interval, Timer = 1000 Time = 30003, TimerCount = 47 Time = 40004, TimerCount = 57 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Changing Interval, Timer = 500 Time = 50005, TimerCount = 77 Time = 60006, TimerCount = 97 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Changing Interval, Timer = 1000 Time = 70007, TimerCount = 107 Time = 80008, TimerCount = 117 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Changing Interval, Timer = 500 Time = 90009, TimerCount = 137 Time = 100010, TimerCount = 157 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Changing Interval, Timer = 1000 Time = 110011, TimerCount = 167 Time = 120012, TimerCount = 177 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Changing Interval, Timer = 500 Time = 130013, TimerCount = 197 Time = 140014, TimerCount = 217 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Changing Interval, Timer = 1000 Time = 150015, TimerCount = 227 Time = 160016, TimerCount = 237 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Changing Interval, Timer = 500 Time = 170017, TimerCount = 257 Time = 180018, TimerCount = 277 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TC_Timer::startTimer _Timer = 0x 42002c00 , TC3 = 0x 42002c00 Changing Interval, Timer = 1000 Time = 190019, TimerCount = 287 Time = 200020, TimerCount = 297 The following is the sample terminal output when running example RepeatedAttachInterrupt_uS on SEEED_XIAO_M0 to demonstrate how to change Timer Interval on-the-fly
Starting RepeatedAttachInterrupt_uS on SEEED_XIAO_M0 SAMDTimerInterrupt v1.7.0 CPU Frequency = 48 MHz [TISR] _period = 19995 , frequency = 50.01 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TCC_Timer::startTimer _Timer = 0x 42002000 , TCC0 = 0x 42002000 [TISR] SAMD21 TCC period = 19995 , _prescaler = 16 [TISR] _compareValue = 59984 myClockTimer (19995) = 19993 <========== OK===============================<=======OK [TISR] _period = 19995 , frequency = 50.01 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TCC_Timer::startTimer _Timer = 0x 42002000 , TCC0 = 0x 42002000 [TISR] SAMD21 TCC period = 19995 , _prescaler = 16 [TISR] _compareValue = 59984 myClockTimer (19995) = 19993 <========== OK===============================<=======OK [TISR] _period = 19995 , frequency = 50.01 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TCC_Timer::startTimer _Timer = 0x 42002000 , TCC0 = 0x 42002000 [TISR] SAMD21 TCC period = 19995 , _prescaler = 16 [TISR] _compareValue = 59984 myClockTimer (19995) = 19993 <========== OK===============================<=======OK [TISR] _period = 19000 , frequency = 52.63 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TCC_Timer::startTimer _Timer = 0x 42002000 , TCC0 = 0x 42002000 [TISR] SAMD21 TCC period = 19000 , _prescaler = 16 [TISR] _compareValue = 56999 myClockTimer (19000) = 18998 [TISR] _period = 20005 , frequency = 49.99 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TCC_Timer::startTimer _Timer = 0x 42002000 , TCC0 = 0x 42002000 [TISR] SAMD21 TCC period = 20005 , _prescaler = 64 [TISR] _compareValue = 15002 myClockTimer (20005) = 20002 [TISR] _period = 30000 , frequency = 33.33 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TCC_Timer::startTimer _Timer = 0x 42002000 , TCC0 = 0x 42002000 [TISR] SAMD21 TCC period = 30000 , _prescaler = 64 [TISR] _compareValue = 22498 myClockTimer (30000) = 29996 [TISR] _period = 19995 , frequency = 50.01 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TCC_Timer::startTimer _Timer = 0x 42002000 , TCC0 = 0x 42002000 [TISR] SAMD21 TCC period = 19995 , _prescaler = 16 [TISR] _compareValue = 59984 myClockTimer (19995) = 19993 <========== OK===============================<=======OK [TISR] _period = 30000 , frequency = 33.33 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TCC_Timer::startTimer _Timer = 0x 42002000 , TCC0 = 0x 42002000 [TISR] SAMD21 TCC period = 30000 , _prescaler = 64 [TISR] _compareValue = 22498 myClockTimer (30000) = 29997 [TISR] _period = 19995 , frequency = 50.01 [TISR] SAMDTimerInterrupt: F_CPU (MHz) = 48 , TIMER_HZ = 48 [TISR] TCC_Timer::startTimer _Timer = 0x 42002000 , TCC0 = 0x 42002000 [TISR] SAMD21 TCC period = 19995 , _prescaler = 16 [TISR] _compareValue = 59984 Debug is enabled by default on Serial.
You can also change the debugging level (TIMERINTERRUPT_LOGLEVEL) from 0 to 4
// These define's must be placed at the beginning before #include "SAMD_TimerInterrupt.h" // _TIMERINTERRUPT_LOGLEVEL_ from 0 to 4 // Don't define _TIMERINTERRUPT_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system. #define TIMER_INTERRUPT_DEBUG 0 #define _TIMERINTERRUPT_LOGLEVEL_ 0If you get compilation errors, more often than not, you may need to install a newer version of the core for Arduino boards.
Sometimes, the library will only work if you update the board core to the latest version because I am using newly added functions.
Submit issues to: SAMD_TimerInterrupt issues
- Search for bug and improvement.
- Similar features for remaining Arduino boards such as SAM-DUE
- Basic hardware timers for SAMD21 and SAMD51.
- More hardware-initiated software-enabled timers
- Longer time interval
- Similar features for remaining Arduino boards such as ESP32, ESP8266, STM32, nRF52, mbed-nRF52, Teensy, etc.
- Add Table of Contents
- Fix
multiple-definitionslinker error - Add support to many more boards, such as
SAMD21E1xA,SAMD21G1xAandSAMD21J1xA - Optimize library code by using
reference-passinginstead ofvalue-passing - Optimize code for setInterval() of SAMD21 TC3
Many thanks for everyone for bug reporting, new feature suggesting, testing and contributing to the development of this library.
- Use some code from the Tamasa's ZeroTimer Library.
- Use some code from the Dennis van Gils' SAMD51_InterruptTimer Library.
- Thanks to generationmake to report the issue Doesn't compile with Adafruit Feather M0 #2 leading to new release v1.3.1.
- Thanks to thiagothimotti to report the interesting hard-to-find bug in Bug when going from a >20000us period to a <20000us period. The timer period become 4 times greater. #3 leading to new release v1.4.0.
- Thanks to generationmake to make a PR in change variable period from unsigned long to float #7 leading to new release v1.5.0.
- Thanks to Alexander Golovanov to propose a PR in Add more SAMD21 #10 leading to the support of many new boards in new release v1.6.0
- Thanks to Will Powell to report the bug in Multiple Definition Error Not fixed by swapping src_cpp or src_h with src #9 leading to new release v1.6.0.
- Thanks to Dave Hooper to report the bug and propose the fix in setInterval on a running timer results in a period significantly longer than the specified period #17 leading to new release v1.7.0.
![]() ⭐️ Tamasa | ![]() Dennis van Gils | ![]() generationmake | ![]() thiagothimotti | ![]() generationmake | ![]() Alexander Golovanov |
![]() Will Powell | ![]() Dave Hooper |
If you want to contribute to this project:
- Report bugs and errors
- Ask for enhancements
- Create issues and pull requests
- Tell other people about this library
- The library is licensed under MIT
Copyright 2020- Khoi Hoang







