Skip to content

Create examples for HardwareTimer library #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
All-in-one setPWM
This example shows how to configure a PWM with HardwareTimer in one single function call.
PWM is generated on `LED_BUILTIN` if available.
No interruption callback used: PWM is generated by hardware.
Once configured, there is no CPU load.
*/

#if defined(LED_BUILTIN)
#define pin LED_BUILTIN
#else
#define pin D2
#endif

void setup()
{
// no need to configure pin, it will be done by HardwareTimer configuration
// pinMode(pin, OUTPUT);

// Automatically retrieve TIM instance and channel associated to pin
// This is used to be compatible with all STM32 series automatically.
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
uint32_t channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));


// Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished.
HardwareTimer *MyTim = new HardwareTimer(Instance);

// Configure and start PWM
// MyTim->setPWM(channel, pin, 5, 10, NULL, NULL); // No callback required, we can simplify the function call
MyTim->setPWM(channel, pin, 5, 10); // 5 Hertz, 10% dutycycle
}


void loop()
{
/* Nothing to do all is done by hardware. Even no interrupt required. */
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
Frequency and dutycycle measurement
This example shows how to configure HardwareTimer to measure external signal frequency and dutycycle.
The input pin will be connected to 2 channel of the timer, one for rising edge the other for falling edge.
Each time a rising edge is detected on the input pin, hardware will save counter value into one of the CaptureCompare register.
Each time a falling edge is detected on the input pin, hardware will save counter value into the other CaptureCompare register.
External signal (signal generator for example) should be connected to `D2`.

*/

#define pin D2

uint32_t channelRising, channelFalling;
volatile uint32_t FrequencyMeasured, DutycycleMeasured, LastPeriodCapture = 0, CurrentCapture, HighStateMeasured;
uint32_t input_freq = 0;
HardwareTimer *MyTim;

/**
@brief Input capture interrupt callback : Compute frequency and dutycycle of input signal

*/
void TIMINPUT_Capture_Rising_IT_callback(HardwareTimer*)
{
CurrentCapture = MyTim->getCaptureCompare(channelRising);
/* frequency computation */
if (CurrentCapture > LastPeriodCapture)
{
FrequencyMeasured = input_freq / (CurrentCapture - LastPeriodCapture);
DutycycleMeasured = (HighStateMeasured * 100) / (CurrentCapture - LastPeriodCapture);
}
else if (CurrentCapture <= LastPeriodCapture)
{
/* 0x1000 is max overflow value */
FrequencyMeasured = input_freq / (0x10000 + CurrentCapture - LastPeriodCapture);
DutycycleMeasured = (HighStateMeasured * 100) / (0x10000 + CurrentCapture - LastPeriodCapture);
}

LastPeriodCapture = CurrentCapture;
}


/**
@brief Input capture interrupt callback : Compute frequency and dutycycle of input signal

*/
void TIMINPUT_Capture_Falling_IT_callback(HardwareTimer*)
{
/* prepare DutyCycle computation */
CurrentCapture = MyTim->getCaptureCompare(channelFalling);

if (CurrentCapture > LastPeriodCapture)
{
HighStateMeasured = CurrentCapture - LastPeriodCapture;
}
else if (CurrentCapture <= LastPeriodCapture)
{
/* 0x1000 is max overflow value */
HighStateMeasured = 0x10000 + CurrentCapture - LastPeriodCapture;
}
}


void setup()
{
Serial.begin(115200);

// Automatically retrieve TIM instance and channelRising associated to pin
// This is used to be compatible with all STM32 series automatically.
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
channelRising = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));

// channelRisings come by pair for TIMER_INPUT_FREQ_DUTY_MEASUREMENT mode:
// channelRising1 is associated to channelFalling and channelRising3 is associated with channelRising4
switch (channelRising) {
case 1:
channelFalling = 2;
break;
case 2:
channelFalling = 1;
break;
case 3:
channelFalling = 4;
break;
case 4:
channelFalling = 3;
break;
}

// Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished.
MyTim = new HardwareTimer(Instance);

// Configure rising edge detection to measure frequency
MyTim->setMode(channelRising, TIMER_INPUT_FREQ_DUTY_MEASUREMENT, pin);

// With a PrescalerFactor = 1, the minimum frequency value to measure is : TIM counter clock / CCR MAX
// = (SystemCoreClock) / 65535
// Example on Nucleo_L476RG with systemClock at 80MHz, the minimum frequency is around 1,2 khz
// To reduce minimum frequency, it is possible to increase prescaler. But this is at a cost of precision.
// The maximum frequency depends on processing of both interruptions and thus depend on board used
// Example on Nucleo_L476RG with systemClock at 80MHz the interruptions processing is around 10 microseconds and thus Max frequency is around 100kHz
uint32_t PrescalerFactor = 1;
MyTim->setPrescaleFactor(PrescalerFactor);
MyTim->setOverflow(0x10000); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover
MyTim->attachInterrupt(channelRising, TIMINPUT_Capture_Rising_IT_callback);
MyTim->attachInterrupt(channelFalling, TIMINPUT_Capture_Falling_IT_callback);

MyTim->resume();

// Compute this scale factor only once
input_freq = MyTim->getTimerClkFreq() / MyTim->getPrescaleFactor();
}


void loop()
{
/* Print frequency and dutycycle measured on Serial monitor every seconds */
Serial.print((String)"Frequency = " + FrequencyMeasured);
Serial.println((String)" Dutycycle = " + DutycycleMeasured);
delay(1000);
}
68 changes: 68 additions & 0 deletions examples/Peripherals/HardwareTimer/InputCapture/InputCapture.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
Input capture
This example shows how to configure HardwareTimer in inputcapture to measure external signal frequency.
Each time a rising edge is detected on the input pin, hardware will save counter value into CaptureCompare register.
External signal (signal generator for example) should be connected to `D2`.
Measured frequency is displayed on Serial Monitor.
*/

#define pin D2

uint32_t channel;
volatile uint32_t FrequencyMeasured, LastCapture = 0, CurrentCapture;
uint32_t input_freq = 0;
HardwareTimer *MyTim;

void InputCapture_IT_callback(HardwareTimer*)
{
CurrentCapture = MyTim->getCaptureCompare(channel);
/* frequency computation */
if (CurrentCapture > LastCapture) {
FrequencyMeasured = input_freq / (CurrentCapture - LastCapture);
}
else if (CurrentCapture <= LastCapture) {
/* 0x1000 is max overflow value */
FrequencyMeasured = input_freq / (0x10000 + CurrentCapture - LastCapture);
}
LastCapture = CurrentCapture;
}


void setup()
{
Serial.begin(115200);

// Automatically retrieve TIM instance and channel associated to pin
// This is used to be compatible with all STM32 series automatically.
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));

// Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished.
MyTim = new HardwareTimer(Instance);

// Configure rising edge detection to measure frequency
MyTim->setMode(channel, TIMER_INPUT_CAPTURE_RISING, pin);

// With a PrescalerFactor = 1, the minimum frequency value to measure is : TIM counter clock / CCR MAX
// = (SystemCoreClock) / 65535
// Example on Nucleo_L476RG with systemClock at 80MHz, the minimum frequency is around 1,2 khz
// To reduce minimum frequency, it is possible to increase prescaler. But this is at a cost of precision.
// The maximum frequency depends on processing of the interruption and thus depend on board used
// Example on Nucleo_L476RG with systemClock at 80MHz the interruption processing is around 4,5 microseconds and thus Max frequency is around 220kHz
uint32_t PrescalerFactor = 1;
MyTim->setPrescaleFactor(PrescalerFactor);
MyTim->setOverflow(0x10000); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover
MyTim->attachInterrupt(channel, InputCapture_IT_callback);
MyTim->resume();

// Compute this scale factor only once
input_freq = MyTim->getTimerClkFreq() / MyTim->getPrescaleFactor();
}


void loop()
{
/* Print frequency measured on Serial monitor every seconds */
Serial.println((String)"Frequency = " + FrequencyMeasured);
delay(1000);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
PWM full configuration
This example shows how to fully configure a PWM with HardwareTimer.
PWM is generated on `LED_BUILTIN` if available.
PWM is generated by hardware: no CPU load.
Nevertheless, in this example both interruption callback are used on Compare match (Falling edge of PWM1 mode) and update event (rising edge of PWM1 mode).
Those call back are used to toggle a second pin: `pin2`.
Once configured, there is only CPU load for callbacks executions.
*/

// 'pin' PWM will be mangaed automatically by hardware whereas 'pin2' PWM will be managed by software through interrupt callback
#if defined(LED_BUILTIN)
#define pin LED_BUILTIN

#if LED_BUILTIN == D3
#error LED_BUILTIN == D3
#else
#define pin2 D3
#endif

#else
#define pin D2
#define pin2 D3
#endif

void Update_IT_callback(HardwareTimer*)
{ // Update event correspond to Rising edge of PWM when configured in PWM1 mode
digitalWrite(pin2, LOW); // pin2 will be complementary to pin
}

void Compare_IT_callback(HardwareTimer*)
{ // Compare match event correspond to falling edge of PWM when configured in PWM1 mode
digitalWrite(pin2, HIGH);
}

void setup()
{
// No need to configure pin, it will be done by HardwareTimer configuration
// pinMode(pin, OUTPUT);

// Need to configure pin2, as it is not managed by HardwareTimer
pinMode(pin2, OUTPUT);

// Automatically retrieve TIM instance and channel associated to pin
// This is used to be compatible with all STM32 series automatically.
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
uint32_t channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));


// Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup function is finished.
HardwareTimer *MyTim = new HardwareTimer(Instance);

MyTim->setMode(channel, TIMER_OUTPUT_COMPARE_PWM1, pin);
// MyTim->setPrescaleFactor(8); // Due to setOverflow with MICROSEC_FORMAT, prescaler will be computed automatically based on timer input clock
MyTim->setOverflow(100000, MICROSEC_FORMAT); // 10000 microseconds = 10 milliseconds
MyTim->setCaptureCompare(channel, 50, PERCENT_COMPARE_FORMAT); // 50%
MyTim->attachInterrupt(Update_IT_callback);
MyTim->attachInterrupt(channel, Compare_IT_callback);
MyTim->resume();
}


void loop()
{
/* Nothing to do all is done by hardware. Even no interrupt required. */
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Timebase callback
This example shows how to configure HardwareTimer to execute a callback at regular interval.
Callback toggles pin.
Once configured, there is only CPU load for callbacks executions.
*/

#if defined(LED_BUILTIN)
#define pin LED_BUILTIN
#else
#define pin D2
#endif

void Update_IT_callback(HardwareTimer*)
{ // Toggle pin. 10hz toogle --> 5Hz PWM
digitalWrite(pin, !digitalRead(pin));
}


void setup()
{
#if defined(TIM1)
TIM_TypeDef *Instance = TIM1;
#else
TIM_TypeDef *Instance = TIM2;
#endif

// Instantiate HardwareTimer object. Thanks to 'new' instanciation, HardwareTimer is not destructed when setup() function is finished.
HardwareTimer *MyTim = new HardwareTimer(Instance);

// configure pin in output mode
pinMode(pin, OUTPUT);

MyTim->setMode(2, TIMER_OUTPUT_COMPARE); // In our case, channekFalling is configured but not really used. Nevertheless it would be possible to attach a callback to channel compare match.
MyTim->setOverflow(10, HERTZ_FORMAT); // 10 Hz
MyTim->attachInterrupt(Update_IT_callback);
MyTim->resume();
}


void loop()
{
/* Nothing to do all is done by hardware. Even no interrupt required. */
}