Timer interrupt to vibrate coin-type vibrator using Seeeduino Xiao

Hello everyone~
I am working on a wearable band using Seeeduino Xiao that vibrates based on serial inputs (including values of PWM and time : analogWrite() and millis()). I used to use millis() to make a precise time vibration. But my boss wants to use “Timer Interrupts” really making VERY PRECISE TIME. Well, I see some examples about BlinkLED and Hello World using TimerTC3 and TimerTcc0.
Can anyone kindly show me an example to solve it?
Thank you in advance for your help.

There is an sketch, isrBlink, in the
File->Examples for Seeeduino XIAO->Timer TC3 directory
and another in the
File->Examples for SeeeduinoXIAO->Timer TCC0 directory

Here’s my take on the TC3 example. For purposes of illustration I use 100 microsecond toggle interval. I’m sure yours will be longer, but note that the resolution is a microsecond. See Footnote.

/* 
 *  From the XIAO board ISRBlink example sketch
 *  
 *  Changed output pin to something we can see on a 'scope
 *  to verify timing interval.
 *  
 *  davekw7x
 *  April, 2022
 *  
 */
#include <TimerTC3.h>

const int outPin = 6; // This is the pin that is affected
int toggleUs = 100;   // Toggle the output every 100 usec

void setup() 
{   
    pinMode(outPin, OUTPUT);    
    TimerTc3.initialize(toggleUs);
    TimerTc3.attachInterrupt(timerIsr);
} // End of setup()
 
void loop()
{
  
  // Whatever your "real" application does repeatedly
  
} // End of loop()

bool ledState;
void timerIsr()
{    
    digitalWrite(outPin, ledState);
    ledState = !ledState;
} // End  of timerIsr

Regards,

Dave

Footnote
There will always be a jitter of a few microseconds due to variable latency that depends on what the processor happens to be doing when the interrupt occurs, but the average will be “very precise” to meet your boss’s requirement—I hope.

Thank you Dave for prompt reply and example code.
I am very new about this especially on timer interrupt. So I am not sure about how to apply timer interrupt on my project.
What I would like to do is that triggerring Timer Interrupt at certain time to activate actuator besides time based rutine like every 100 usec. I used millis() function to ON and OFF at certain time / period based on serial inputs.
Is it possible to trigger Timer Interrupt when there is serial inputs (e.g. if (Serial.available()) {})?
Can I enable pwm function like timer1.pwm (shown in TimerOne & TimerThree Arduino Libraries) using TimerTC3.h?
I appreciate you in advance for your fruitful advice.

I wouldn’t want to hurt anyone’s feelings (especiallly your boss’s), but it looks to me like you (and your boss) are treating a timer interrupt as a solution looking for a problem.

What I mean is this:

Define your complete program. (In Arduino terms: What are you doing in your loop() function?) In other words: Show me your loop().

What is it, exactly, that requires “precise” timing? I mean, if you are responding to human input from a serial port, what is it that requires sub-millisecond timing precision?

Now, as to the details of using the timer interrupt for something other than continuous toggling of a pin at a fixed interval:

Don’t initialize the timer and don’t attach the ISR in the setup() function.

Initialize the timer and attach the ISR whenever serial input (user command) requires it. (Or whenever what other condition indicates you need to start the timer.)

The ISR itself could simply activate your actuator after the designated interval and, perhaps, set a “flag” (give a particular value to a variable that can be monitored in your loop) so that further action can be taken.

The ISR can also “detach” the ISR so that no further interrupts will occur until it is initialized and attached again by later user commands (or other conditions occur). Note that you can even have different ISRs that you attach under different conditions to do different things, depending on what it is, exactly, that your program needs to do.

Also…
You can enable the interrupt whenever the heck you want to (from some condition detected in your loop() or whatever). You can do whatever the heck you want to do in an ISR. Enable a pwm function, or anything else.

On the other hand, this same action might be handled in the loop() function without introducing interrupts. I mean, there is nothing “wrong” with using interrupts, but interrupts aren’t always the “best” solution. (There are certain drawbacks, but I see no value in delving into the philosophy of programming with interrupts here.)

Bottom line: General programming advice can probably be readily obtained from the Arduino forum. I’m thinking Seeed forum bandwidth is more aptly used to give examples and solutions regarding Seeed-specific hardware and software “issues.”

[/Begin Disclaimer]
I have no connection to Seeed or its forum administration. I am a (fairly new) user who would like to share information with other new and not-so-new users.
I am hanging around here because I learn something every time I visit. Every single time.
[/End Disclaimer]

Regards,

Dave

Good morning Dave~
Thank you for your detailed comments.
I guess that my boss was saying non-polling system. I used millis() keep comparing time in a loop() to start and finishing actuator. I think this is one reason to use ISR reserving time to start and finish job based on user input. I am not sure it is possible or not .

I have been searching ISR examples that have ISR in setup() without detaching ISR in loop().
Can you please show me any examples?
It will be great if you show me any simple examples initializing and detaching ISR in loop().
So, I can upgrade my skill even though it is quite stressful. :slight_smile:

The reason why I am writing this thread in Seeed is that I am the one suggesting Seeeduino Xiao to my team. We have the prototype with Seeeduino Xiao now.
And it is difficult to find some examples for Seeeduino and SAMD21.
Thank you agin for your time and good advise.

OK, I made a little toy program that does the following:

Looks at the serial input to see if a user has pressed the ‘Enter’ key. Every time the user does this, it generates a 12.345 millisecond pulse. (Nominal value using Timer interrupt. Of course, it isn’t exactly 12345 microseconds because of a little overhead, but I hope you get the idea.) It prints out the system time (milliseconds since the system was reset) that the user input was detected.

Now, independent of whether input is received, a “heartbeat” is generated by the XIAO built-in LED. This does not use timer interrupts or anything other than the system millis() function. It doesn’t need to be any more precise than that. In fact the actual period is just as close to 1 second as it would be if we , somehow, used interrupts. (The millis() function is, itself, driven by its own timer interrupt behind the scenes.)

/* 
 *  Example using Timer interrupt to generate  
 *  a "precision" output pulse on user command.
 *  
 *  Independent of everything else that's going on,
 *  I use the XIAO built-in LED as a "heartbeat,"
 *  giving a 100 ms flash every second.
 *
 *  davekw7x
 *  April, 2022
 *  
 */

#include <TimerTC3.h>  // Part of XIAO board package

const int output_pin = 6; // This is the pin that is affected

void setup() 
{
  Serial.begin(115200);
  while (!Serial){}
  Serial.println("\nTimer interrupt test compiled on " __DATE__ " at " __TIME__ "\n");
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, 1); // Turn it off initially
  pinMode(output_pin, OUTPUT);    
} // End of setup()

void loop()
{
  uint32_t now_ms = millis();
  // Make it blink at appropriate times
  serviceLed(now_ms);
  
  // serviceCommand() returns 0 if there's nothing to do
  // serviceCommand() returns a positive value to generate a pulse
  int32_t pulse_us = serviceCommand();
  
  // "Normally, I would put the pulse stuff in its own separate source
  // file, but for illustration, I'll make it visible in the main loop
  if (pulse_us) {
    digitalWrite(output_pin, 1);
    TimerTc3.initialize(pulse_us);
    TimerTc3.attachInterrupt(timerIsr);
    updateDisplay(now_ms);
  }
  
} // End of loop()

// I'll just print the time the user told it to generate a pulse.  In general, it might
// do a lot of stuff...
void updateDisplay(uint32_t now)
{
  Serial.printf("%10lu: Pulse\n", now);
} // End of updateDisplay


//
// For a "real" application, I usually put interrupt stuff in
// its own separate source file
//
static volatile bool led_state = false;

// We don't need this in this example, but in general, it might
// be useful
bool getLedState(void)
{
  bool retval;
  noInterrupts();
  retval = led_state;
  interrupts();
  return retval;
} // End of getLedState()

// These could be variables and you could have functions to
// let the user change the cadence, but I'll just set up
// a short blink every second.
const int32_t on_time = 100;
const int32_t off_time = 900;


// The XIAO builtin LED is active-low
#define LED_ON 0
#define LED_OFF 1

void serviceLed(uint32_t now)
{
  static int32_t led_interval = off_time;
  static uint32_t old_ms = 0;
  if ((int32_t)(now - old_ms) >= led_interval) {
    old_ms += (int32_t)led_interval;
    led_state = !led_state;
    if (led_state) {
      digitalWrite(LED_BUILTIN, LED_ON); // Turn on Active-low LED
      led_interval = on_time; // Leave  it on for this long
    }
    else {
      digitalWrite(LED_BUILTIN, LED_OFF); // Turn it off
      led_interval = off_time; // Leave it off for this long
    }
    
  }
} // End of serviceLed()

// "Normally," I would (probably) get user input and see if it's a
// valid 32-bit integer and return that value.  Or, there may
// be different commands that would need to be parsed.
//
// For illustration, I'll just return a fixed value if and when
// the user presses 'Enter'
// I'll return zero if anything else has  happened. (Or not happened.)
//
int32_t serviceCommand(void)
{
  char inch;
  int32_t retval = 0;
  if (Serial.available()) {
    inch = Serial.read();
    if (inch == '\r') {
      retval = 12345; // A 12.345 ms pulse
    }
  }
  return retval;
} // End of serviceCommand()

/*******************************************************/
//----This is what I think you mightbe interested in---
//
// Turn off the output pin and disable further Tc3
// interrupts.
//
void timerIsr()
{    
    digitalWrite(output_pin, 0);
    TimerTc3.detachInterrupt();
} // End  of timerIsr

Regards,

Dave

Thank a lot. Dave!!
I think I can do something based on your sample code.
Have a good weekend!!
Best regards
James