diff --git a/examples/Peripherals/HardwareTimer/All-in-one_setPWM/All-in-one_setPWM.ino b/examples/Peripherals/HardwareTimer/All-in-one_setPWM/All-in-one_setPWM.ino new file mode 100644 index 0000000..b80ea63 --- /dev/null +++ b/examples/Peripherals/HardwareTimer/All-in-one_setPWM/All-in-one_setPWM.ino @@ -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. */ +} \ No newline at end of file diff --git a/examples/Peripherals/HardwareTimer/Frequency_Dutycycle_measurement/Frequency_Dutycycle_measurement.ino b/examples/Peripherals/HardwareTimer/Frequency_Dutycycle_measurement/Frequency_Dutycycle_measurement.ino new file mode 100644 index 0000000..ab08db0 --- /dev/null +++ b/examples/Peripherals/HardwareTimer/Frequency_Dutycycle_measurement/Frequency_Dutycycle_measurement.ino @@ -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); +} \ No newline at end of file diff --git a/examples/Peripherals/HardwareTimer/InputCapture/InputCapture.ino b/examples/Peripherals/HardwareTimer/InputCapture/InputCapture.ino new file mode 100644 index 0000000..0137e76 --- /dev/null +++ b/examples/Peripherals/HardwareTimer/InputCapture/InputCapture.ino @@ -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); +} \ No newline at end of file diff --git a/examples/Peripherals/HardwareTimer/PWM_FullConfiguration/PWM_FullConfiguration.ino b/examples/Peripherals/HardwareTimer/PWM_FullConfiguration/PWM_FullConfiguration.ino new file mode 100644 index 0000000..4285eee --- /dev/null +++ b/examples/Peripherals/HardwareTimer/PWM_FullConfiguration/PWM_FullConfiguration.ino @@ -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. */ +} \ No newline at end of file diff --git a/examples/Peripherals/HardwareTimer/Timebase_callback/Timebase_callback.ino b/examples/Peripherals/HardwareTimer/Timebase_callback/Timebase_callback.ino new file mode 100644 index 0000000..4d57fa6 --- /dev/null +++ b/examples/Peripherals/HardwareTimer/Timebase_callback/Timebase_callback.ino @@ -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. */ +} \ No newline at end of file