Pin toggle on timer interrupt

Hi, I’m trying to port some code to generate a high-frequency (~100kHz) square wave, but not sure to begin.

On the AVR/Arduino I’m using, you can set up an output pin to toggle when a timer overflows.

I’ve looked at the datasheet for the STM32F103x8 but don’t see documentation for actual registers to control the timers. Also not sure where to find low-level documentation on the core (moving to photon when it arrives), which timers are off-limits, where/how to enter the code configuring the timers, etc etc…

is the square wave always 50% duty cycle?
If so the “tone()” function may work for you…
otherwise there is a great tutorial on setting custom PWM frequencies, just search it
I used peekay’s IntervalTimer library… I am not at 100kHz… Buy he has a nice explanation of which timer is used for what?

Yes, the duty cycle is always 50%. I saw tone() but no mention of its maximum frequency in the documentation. I’ll take a look at the library

if you have a scope just type it in a see what happens…

I’ve been experimenting with the SparkIntervalTimer library, but haven’t managed to get a solid signal yet. I adapted the sample code listed below.

This is getting me closer and produces a square wave at 106.3kHz. Unfortunately, there is a lot of jitter in the signal which I’d imagine has to do with the fact that it is interrupt driven and there are other long-running interrupt handlers executing at any given time. This is why I was wondering if there wasn’t an option in the GPIO architecture to have the timer toggle an output pin when it overflows like on Atmel chips–no ISR is involved.

I suppose the next thing to try would be tweaking the base PWM frequency


// in SparkIntervalTimer.h
// setting the SIT clock to 10MHz instead of 1MHz    
const uint16_t SIT_PRESCALERu = (uint16_t)(SystemCoreClock / 10000000) - 1;

#include "SparkIntervalTimer.h"
IntervalTimer myTimer;
void blinkLED(void);
const uint8_t ledPin = D7;		// LED for first Interval Timer
void setup(void) {
  pinMode(ledPin, OUTPUT);
  myTimer.begin(blinkLED, 47, uSec, TIMER4);
  
}
int ledState = LOW;
// Callback for Timer 1
void blinkLED(void) {
  if (ledState == LOW) {
    ledState = HIGH;
	PIN_MAP[ledPin].gpio_peripheral->BSRR = PIN_MAP[ledPin].gpio_pin; // LED High
  }
  else {
    ledState = LOW;
    PIN_MAP[ledPin].gpio_peripheral->BRR = PIN_MAP[ledPin].gpio_pin; // LED low
  }
}
void loop(void) { }

Folks, I am working with @mdma to create a new timer API for the core/photon which will support the timer Output Compare mode. This mode produces an interrupt on overflow which can be used for outputting to a pin. This is currently the method used in the Tone() portion of the firmware. :smile:

@peekay123 That sounds like just the ticket! I’ll try to do some digging under the hood and see if I can modify tone() to work in the frequency range I need.

Changing the PWM frequency according to https://gist.github.com/Sperryfreak01/a4282fce7c64f44036ae works well enough in the mean time.

@zyzzyva, PWM uses an auto-reload timer feature (hardware). Tone uses Output Capture and reloads the timer/duty cycle in software. Tone uses the PIN_MAP structure to store its reload value but you can do your own thang :stuck_out_tongue_winking_eye:

@peekay123 Happy to find this thread and hear about the new timer API. Any update on the status of the API? Coming from the embedded microcontroller world it would be greatly helpful to have timers. Will we be able to tie interrupts to the timer overflow or only pin output? Thanks!

@lige, @mdma is helping with some aspects of the library. Once the new Photon firmware is released (super soon), I will release an interim version of the library for folks to test and port their code. There is lots of work to do yet but hey, that’s half the fun!

@peekay123 This is great news! I will watch git for the new firmware and check out your library. Thanks!

1 Like

Look cool @peekay123. I’ve used the similar library that I guess it’s modeled after on the Teensy with success.

Question: How can one guarantee atomicity, or create semaphores or something to control timing. Take the example above. Say I put some code in loop to check ledState… what would happen?

void blinkLED(void) {
  if (ledState == LOW) {
    ledState = HIGH;
    PIN_MAP[ledPin].gpio_peripheral->BSRR = PIN_MAP[ledPin].gpio_pin; // LED High
  }
  else {
    ledState = LOW;
    PIN_MAP[ledPin].gpio_peripheral->BRR = PIN_MAP[ledPin].gpio_pin; // LED low
  }
}
void loop(void) {
    if (ledState == LOW) {
       // DO SOMETHING
    }
}

What mechanism could I use to guarantee that the timer wouldn’t fire between the test for ledState == LOW and the // DO SOMETHING?

@rvnash, the library has a function to enable/disable the interrupt for a timer. The library will be open-source so you could modify the code as required. I would also declare ledState as a volatile to avoid exactly this kind of issue.

Will disable/enable functions keep the timer running, or will it stop the timer, and restart it? Or will the enable start the timer over from scratch? I will look myself and change to suit myself, but I’m wondering which behavior you chose. What I would want is for the interrupt to be suppressed during the execution of the ‘if’ statement, but for the timer to keep running. If it would have fired during the ‘if’ statement, the reenable should fire it immediately.

@rvnash, the timer will NOT stop, only the interrupt will be disabled. You can stop and release the timer as well. It is modeled after the great Teensy library but with extras just for the Particle devices. And a lot more to come.

Sounds great. Still I would hate to miss an interrupt if it occurs during the disable period. Will the library keep track of that and fire immediately on re-enablement? I recognize the fact that the disabled period will need to be as short as possible, certainly much shorter than the timer interval.

@rvnash, ISR timing and disabling of the interrupt is a classic issue. I believe the interrupt flag bit is reset in the ISR (in the library code) so it if fires during the servicing, it should cause a trigger once you enable the interrupts and exit. This should not be how you plan your ISR code though!