I’m trying to put together a minimum Input Capture hardware timer example using Interrupts to measure an input pulse stream (single pulse stream for now, with 6 total inputs to eventually be captured for my intended application.
I’ll document my efforts here in this thread.
George
Background
I’m hoping to gain enough understanding of this core and STM32 timer control to port this flight controller application (http://www.brokking.net/ymfc-32_main.html). This was originally developed around a slightly earlier version of Roger’s libmaple based STM32duino core (http://www.brokking.net/YMFC-32_downloads.html). It makes use some lower level bit manipulation of TIM2-4 for interrupt based input capture of pulse inputs as well as hardware PWM control of outputs. The application uses TIM2 and TIM3 (input capture of pilot commands from an RC receiver) and also TIM4 (PWM outputs to quadcopter motor controllers).
Typical input capture interrupt handler (toggles the input capture edge direction within interrupt to get enough input channels (6 pulse capture inputs are needed):
void handler_channel_1(void) { //This function is called when channel 1 is captured.
if (0b1 & GPIOA_BASE->IDR >> 0) { //If the receiver channel 1 input pulse on A0 is high.
channel_1_start = TIMER2_BASE->CCR1; //Record the start time of the pulse.
TIMER2_BASE->CCER |= TIMER_CCER_CC1P; //Change the input capture mode to the falling edge of the pulse.
}
else { //If the receiver channel 1 input pulse on A0 is low.
channel_1 = TIMER2_BASE->CCR1 - channel_1_start; //Calculate the total pulse time.
if (channel_1 < 0)channel_1 += 0xFFFF; //If the timer has rolled over a correction is needed.
TIMER2_BASE->CCER &= ~TIMER_CCER_CC1P; //Change the input capture mode to the rising edge of the pulse.
}
}
Arduino IDE 1.85, running on Win 10 laptop.
STM32 Arduino “Official” core V1.3.0, installed per https://github.com/stm32duino/wiki/wiki/Getting-Started
Nucleo-F103RB board, ST-Link replaced by J-Link firmware (https://www.segger.com/products/debug-p … -on-board/)
Segger Ozone debugger (https://www.segger.com/products/develop … -debugger/)
Saleae Logic analyzer (https://www.saleae.com/)
Some jumper wires
Also:
Source compiled using “Debug (-g)” option
First up is to generate a periodic pulse waveform on the Nucleo that I can feed in as input into a Timer Input Capture pin using a jumper wire. I’ll use the analogWrite function for that purpose. Below is the test sketch and its output as measured on the logic analyzer. The pulse output looks as expected, a 1KHz pulse stream with a 25% positive PWM (= 63/255).
const int testPwmOutputPin = PA0; // PA_0,D46/A0 <<-- works
//const int testPwmOutputPin = A0; // PA_0,D46/A0 <<-- works
//const int testPwmOutputPin = PA_0; // PA_0,D46/A0 <<-- does not work
void setup() {
Serial.begin(115200);
Serial.println("Initializing...");
// initialize digital pins
pinMode(LED_BUILTIN, OUTPUT);
pinMode(testPwmOutputPin, OUTPUT);
analogWrite(testPwmOutputPin, 63);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
But testing revealed that A0 works, PA0 also works, but PA_0 compiles fine but I get no PWM output. Is this to be expected? It seems to not line up with the example I was using from here (https://www.stm32duino.com/viewtopic.php?p=34555#p34555).
See previous example code definitions.
const int testPwmOutputPin = PA0; // PA_0,D46/A0 <<-- works
//const int testPwmOutputPin = A0; // PA_0,D46/A0 <<-- works
//const int testPwmOutputPin = PA_0; // PA_0,D46/A0 <<-- does not work
So for my input capture examples I will target TIM3 and TIM4 and hope that there are no other behind the scenes conflicting use of these two hardware timers.
Thanks,
George

Here is a pic to show what this looks like in STM32CubeMX:

This post (https://www.stm32duino.com/viewtopic.php?p=44629#p44629) tells me that HAL are available at sketch level, and that some HAL driver are enabled by default. The HAL conf is available in the variant. For example for Nucleo F103RB is at https://github.com/stm32duino/Arduino_C … hal_conf.h In this file I can see that TIM has been enabled for my board with #define HAL_TIM_MODULE_ENABLED so I think I am good to go with TIM3/TIM4. I expected this but good to know how to confirm.
IMPORTANT TO REMEMBER FOR FUTURE USE –
If the HAL module you need is not enabled, one can create a “build_opt.h” file at sketch level then add the module you want enabled:
“-DHAL_CRC_MODULE_ENABLED -DHAL_XXX_MODULE_ENABLED …” etc.
[MGeo – Sat Oct 06, 2018 10:34 am] –
If anyone knows a useful work-around please advise.
use something like imgur.com or flickr.com or tinypic.com
PA0 is not equal to PA_0.
This compile using PA_0 because it’s also an uint32_t as requested by analogWrite( uint32_t ulPin, uint32_t ulValue ) ;
My test sketch (per above) will have a background heartbeat LED blink functionality using the delay() function. I’m hoping that delay uses SysTick as system timer and does not use one of the F103RB timers (TIM1, TIM2, TIM3, TIM4).
George
https://www.st.com/content/ccc/resource … 105879.pdf
If I’m understanding things correctly, I think I need to do the steps highlighted below in blue on TIM3 to be able to capture the TIM2 generated pulse stream I’ve generated on PA0 using analogWrite() as shown up above. I’m not sure about “ Configure these TIM pins in Alternate function mode using HAL_GPIO_Init()” and how I need to do this on an F1.
65.2 TIM Firmware driver API description
65.2.2 How to use this driver
1. Initialize the TIM low level resources by implementing the following functions
depending from feature used :
Time Base : HAL_TIM_Base_MspInit()
Input Capture : HAL_TIM_IC_MspInit()
Output Compare : HAL_TIM_OC_MspInit()
PWM generation : HAL_TIM_PWM_MspInit()
One-pulse mode output : HAL_TIM_OnePulse_MspInit()
Encoder mode output : HAL_TIM_Encoder_MspInit()
2. Initialize the TIM low level resources :
a. Enable the TIM interface clock using __TIMx_CLK_ENABLE();
b. TIM pins configuration
Enable the clock for the TIM GPIOs using the following function:
__GPIOx_CLK_ENABLE();
Configure these TIM pins in Alternate function mode using HAL_GPIO_Init();
3. The external Clock can be configured, if needed (the default clock is the internal clock
from the APBx), using the following function: HAL_TIM_ConfigClockSource, the clock
configuration should be done before any start function.
4. Configure the TIM in the desired functioning mode using one of the initialization
function of this driver:
HAL_TIM_Base_Init: to use the Timer to generate a simple time base
HAL_TIM_OC_Init and HAL_TIM_OC_ConfigChannel: to use the Timer to
generate an Output Compare signal.
HAL_TIM_PWM_Init and HAL_TIM_PWM_ConfigChannel: to use the Timer to
generate a PWM signal.
HAL_TIM_IC_Init and HAL_TIM_IC_ConfigChannel: to use the Timer to measure
an external signal.
HAL_TIM_OnePulse_Init and HAL_TIM_OnePulse_ConfigChannel: to use the
Timer in One Pulse Mode.
HAL_TIM_Encoder_Init: to use the Timer Encoder Interface.
5. Activate the TIM peripheral using one of the start functions depending from the feature
used:
Time Base : HAL_TIM_Base_Start(), HAL_TIM_Base_Start_DMA(),
HAL_TIM_Base_Start_IT()
Input Capture : HAL_TIM_IC_Start(), HAL_TIM_IC_Start_DMA(),
HAL_TIM_IC_Start_IT()
Output Compare : HAL_TIM_OC_Start(), HAL_TIM_OC_Start_DMA(),
HAL_TIM_OC_Start_IT()
PWM generation : HAL_TIM_PWM_Start(), HAL_TIM_PWM_Start_DMA(),
HAL_TIM_PWM_Start_IT()
One-pulse mode output : HAL_TIM_OnePulse_Start(),
HAL_TIM_OnePulse_Start_IT()
Encoder mode output : HAL_TIM_Encoder_Start(),
HAL_TIM_Encoder_Start_DMA(), HAL_TIM_Encoder_Start_IT().
6. The DMA Burst is managed with the two following functions:
HAL_TIM_DMABurst_WriteStart() HAL_TIM_DMABurst_ReadStart()
I put together a minimal test sketch below.
TIM3
I started with use of STM Core timer.c based functions to initialize TIM3, stealing code from an earlier hardware timer LED blink example elsewhere on this forum (https://www.stm32duino.com/viewtopic.php?p=49394#p49394).
Next I used the CubeMX F1 TIM_Input_Capture example as a guide for initialization of TIM4. I think I’m doing something wrong with how I am defining TIM4. I dropped in a TIM3/TIM4 register dump Serial.print function, I can see that TIM3 gets properly initialized, but nothing happens with TIM4. Same is true stepping through the code with Ozone debugger.
At this point I’m scratching my head as to why, so I’m posting what I have so far seeking any advice.
Thanks,
George
Minimal test sketch:
#define TIMER_PERIOD TIM3
#define TIMER_INPUT_CAPTURE TIM4
// Handle for stimer
static stimer_t TIM_Handle;
// Timer handle declaration
static TIM_HandleTypeDef TimHandle;
// Timer Input Capture Configuration Structure declaration
TIM_IC_InitTypeDef sICConfig;
const int PwmOutputPin = PA0; // PA_0,D46/A0 -- USES TIM2
const int CaptureInputPin = PA6;
const int TestOutputPin = D7; // PA_8,D7
extern "C" void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
UNUSED(htim);
digitalWrite(TestOutputPin, !digitalRead(LED_BUILTIN));
}
void setup() {
Serial.begin(115200);
Serial.println("Initializing...");
Serial.println();
// initialize digital pins
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PwmOutputPin, OUTPUT);
pinMode(TestOutputPin, OUTPUT);
// initialize the input capture pin
// ??? --- I'm not sure if this is enough, do I need to configure my input pin as an 'Input Capture' alternate function --- ???
pinMode(CaptureInputPin, INPUT);
// create a 1KHz 25% (256/4 - 1) duty PWM on testPwmOutputPin
// NOTE - uses TIM associated with output pin
analogWrite(PwmOutputPin, (64 - 1));
// ------- Period TIM3 Setup -------------------------
// Set Period timer TIM instance
TIM_Handle.timer = TIM3;
// Period timer set to 10ms
TimerHandleInit(&TIM_Handle, 10000 - 1, ((uint32_t)(getTimerClkFreq(TIM3) / (1000000)) - 1));
//attachIntHandle(&TIM_Handle, blink);
Print_TIM3_Regs();
// ------- Input Capture TIM4 Setup -------------------------
// Set Input Capture TIM instance
TimHandle.Instance = TIM4;
// Initialize TIM peripheral (prescale of (64-1) and 64MHz clock gives 1us tick)
TimHandle.Init.Period = 0xFFFF;
TimHandle.Init.Prescaler = (64 - 1);
TimHandle.Init.ClockDivision = 0;
TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TimHandle.Init.RepetitionCounter = 0;
TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if(HAL_TIM_IC_Init(&TimHandle) != HAL_OK)
_Error_Handler("HAL TIM IC Init error", 1);
// Configure the Input Capture of channel 1
sICConfig.ICPolarity = TIM_ICPOLARITY_RISING;
sICConfig.ICSelection = TIM_ICSELECTION_DIRECTTI;
sICConfig.ICPrescaler = TIM_ICPSC_DIV1;
sICConfig.ICFilter = 0;
if(HAL_TIM_IC_ConfigChannel(&TimHandle, &sICConfig, TIM_CHANNEL_1) != HAL_OK)
_Error_Handler("HAL TIM IC Channel Config error", 2);
// Start the Input Capture in interrupt mode
if(HAL_TIM_IC_Start_IT(&TimHandle, TIM_CHANNEL_2) != HAL_OK)
_Error_Handler("HAL TIM_IC_Start_IT error", 3);
Print_TIM4_Regs();
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
void Print_TIM3_Regs() {
Serial.print("TIM3->CR1: "); Serial.println(TIM3->CR1, HEX);
Serial.print("TIM3->CR2: "); Serial.println(TIM3->CR2, HEX);
Serial.print("TIM3->DIER: "); Serial.println(TIM3->DIER, HEX);
Serial.print("TIM3->SR: "); Serial.println(TIM3->SR, HEX);
Serial.print("TIM3->CNT: "); Serial.println(TIM3->CNT, HEX);
Serial.print("TIM3->PSC: "); Serial.println(TIM3->PSC, HEX);
Serial.print("TIM3->ARR: "); Serial.println(TIM3->ARR, HEX);
Serial.println();
}
void Print_TIM4_Regs() {
Serial.print("TIM4->CR1: "); Serial.println(TIM4->CR1, HEX);
Serial.print("TIM4->CR2: "); Serial.println(TIM4->CR2, HEX);
Serial.print("TIM4->DIER: "); Serial.println(TIM4->DIER, HEX);
Serial.print("TIM4->SR: "); Serial.println(TIM4->SR, HEX);
Serial.print("TIM4->CNT: "); Serial.println(TIM4->CNT, HEX);
Serial.print("TIM4->PSC: "); Serial.println(TIM4->PSC, HEX);
Serial.print("TIM4->ARR: "); Serial.println(TIM4->ARR, HEX);
while(1) {}
}
void _Error_Handler(const char * msg, int val) {
Serial.print("Error: ");
Serial.print(msg);
Serial.print(" Err: ");
Serial.println(val);
while(1) {}
}
This is some code that uses just the raw registers … it won’t compile for you but it will give you and idea what the minimum setup is requried for PA0 and TIM2_CH1.
/*
fabooh - TIM2CH1 input capture from PA0
*/
serial_default_t<115200, F_CPU, TX_PIN, RX_PIN> Serial;
volatile uint16_t chan1;
PA_0 PA0;
static void input_timer_setup()
{
pinMode(PA0, INPUT_PULLDOWN);
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
NVIC_EnableIRQ(TIM2_IRQn); // Enable TIM2 IRQ
TIM2->CR1 = 0; // disable TIM2, required to configure
TIM2->CR2 = 0;
TIM2->DCR = 0;
TIM2->EGR = 0;
TIM2->SMCR = 0;
TIM2->CCMR1 = // CC1 channel is configured as input, IC1 is mapped on TI1.
TIM_CCMR1_CC1S_0
// digital filter 8 CLK_INT samples required to trigger
| (0b011 << TIM_CCMR1_IC1F_Pos)
;
TIM2->CCMR2 = 0;
TIM2->CCER = TIM_CCER_CC1E; // input capture enable for CCR1 rising
TIM2->PSC = (F_CPU/1000000)-1; // make each TIM2 CNT tick is 1 microsecond
TIM2->ARR = 0xFFFF; // count from 0 - 65535, then wrap
TIM2->DIER = TIM_DIER_CC1IE; // interrupt enable on CCR1 input
TIM2->CR1 = TIM_CR1_CEN; // enable TIM2
}
void setup() {
Serial.begin(115200);
input_timer_setup();
delay(1);
}
volatile unsigned new_capture=0;
void loop() {
delay(100);
if ( new_capture ) {
Serial << millis() << "," << chan1 << endl;
new_capture=0;
}
}
static void handle_tim2ch1_inputcapture(void) {
static uint16_t chan1_start;
// if PA0 is high, grab start time & setup falling capture
// in either case, we have to flip capture bit to time next edge
if ( GPIOA->IDR & 0x1 ) {
chan1_start = TIM2->CCR1;
TIM2->CCER |= TIM_CCER_CC1P; // capture on falling edge
}
else {
new_capture=1;
chan1 = TIM2->CCR1 - chan1_start;
TIM2->CCER &= ~TIM_CCER_CC1P; // capture on rising edge
}
// NOTE: reading CCR1 clears the TIM2->SR CC1IF flag
// that means we don't have to clear it explicitly
}
// override the default TIM2 interrupt handler
// and look and handle TIM2CH1 input capture events
extern "C" void TIM2_IRQHandler()
{
if ( TIM2->SR & TIM_SR_CC1IF ) {
handle_tim2ch1_inputcapture();
}
}
I now am able to get the TIM3 register values to update. I’m still not quite clear what I had wrong, I think I failed to read the HAL manual close enough and was failing to activate the TIM peripheral correctly, so any register change attempts failed to update the hardware.
At any rate I’m able to enable TIM3 in capture mode. I set a breakpoint in my HAL_TIM_IC_CaptureCallback() in Ozone, it now looks to be firing! Next I’ll hook the logic analyzer up and see if things are firing as expected (remembering that PWM out on PA0 is jumpered to PA6 / TIM3_CH1).
George
STM32CubeMX project config (right click on image and open in new tab makes it easier to see):

Test sketch:
static TIM_HandleTypeDef htim3;
const int PwmOutputPin = PA0; // PA_0,D46/A0 -- USES TIM2
const int CaptureInputPin = PA6;
const int TestOutputPin = D15;
extern "C" void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
UNUSED(htim);
digitalWrite(TestOutputPin, !digitalRead(TestOutputPin));
}
void setup() {
Serial.begin(115200);
Serial.println("Initializing...");
Serial.println();
// initialize digital pins
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PwmOutputPin, OUTPUT);
pinMode(TestOutputPin, OUTPUT);
// initialize the input capture pin
// ??? --- I'm not sure if this is enough, do I need to configure my input pin as an 'Input Capture' alternate function --- ???
pinMode(CaptureInputPin, INPUT);
// create a 1KHz 25% (256/4 - 1) duty PWM on testPwmOutputPin
// NOTE - uses TIM associated with output pin
analogWrite(PwmOutputPin, (64 - 1));
Print_TIM2_Regs();
// ------- Input Capture TIM3 Setup -------------------------
// TIM3_IRQn interrupt configuration
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_IC_InitTypeDef sConfigIC;
htim3.Instance = TIM3;
htim3.Init.Prescaler = (64 - 1);
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFF;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
TIM3->CR1 = TIM_CR1_CEN;
TIM3->DIER = TIM_DIER_CC1IE | TIM_DIER_CC2IE;
TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E;
Print_TIM3_Regs();
}
void loop() {
Serial.println("loop");
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
void Print_TIM2_Regs() {
Serial.print("TIM2->CR1: "); Serial.println(TIM2->CR1, HEX);
Serial.print("TIM2->CR2: "); Serial.println(TIM2->CR2, HEX);
Serial.print("TIM2->DIER: "); Serial.println(TIM2->DIER, HEX);
Serial.print("TIM2->SR: "); Serial.println(TIM2->SR, HEX);
Serial.print("TIM2->CNT: "); Serial.println(TIM2->CNT, HEX);
Serial.print("TIM2->PSC: "); Serial.println(TIM2->PSC, HEX);
Serial.print("TIM2->ARR: "); Serial.println(TIM2->ARR, HEX);
Serial.println();
}
void Print_TIM3_Regs() {
Serial.print("TIM3->CR1: "); Serial.println(TIM3->CR1, HEX);
Serial.print("TIM3->CR2: "); Serial.println(TIM3->CR2, HEX);
Serial.print("TIM3->DIER: "); Serial.println(TIM3->DIER, HEX);
Serial.print("TIM3->SR: "); Serial.println(TIM3->SR, HEX);
Serial.print("TIM3->CNT: "); Serial.println(TIM3->CNT, HEX);
Serial.print("TIM3->PSC: "); Serial.println(TIM3->PSC, HEX);
Serial.print("TIM3->ARR: "); Serial.println(TIM3->ARR, HEX);
Serial.println();
}
void _Error_Handler(const char * msg, int val) {
Serial.print("Error: ");
Serial.print(msg);
Serial.print(" Err: ");
Serial.println(val);
while(1) {}
}
White Trace: Pin PA6 = PWM pulse stream as input into TIM3_CH1
Orange Trace: Pin D15 = digital output toggled by interrupt handler

#define HAL_WAY 0
static TIM_HandleTypeDef htim3;
const int PwmOutputPin = PA0; // PA_0,D46/A0 -- USES TIM2
const int CaptureInputPin = PA6;
const int TestOutputPin = D15;
volatile int32_t channel_1_start;
volatile int32_t channel_1;
extern "C" void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
UNUSED(htim);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6)) {
// if (0b1 & GPIOA->IDR >> 6) { // If the receiver channel 1 input pulse on A6 is high.
channel_1_start = TIM3->CCR1; // Record the start time of the pulse.
TIM3->CCER |= TIM_CCER_CC1P; // Change the input capture mode to the falling edge of the pulse.
}
else { // If the receiver channel 1 input pulse on A0 is low.
channel_1 = TIM3->CCR1 - channel_1_start; // Calculate the total pulse time.
if (channel_1 < 0)channel_1 += 0xFFFF; // If the timer has rolled over a correction is needed.
TIM3->CCER &= ~TIM_CCER_CC1P; // Change the input capture mode to the rising edge of the pulse.
}
//digitalWrite(TestOutputPin, !digitalRead(TestOutputPin)); // the Arduino way (works)
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8); // the HAL way (also works)
}
void setup() {
Serial.begin(115200);
Serial.println("Initializing...");
Serial.println();
// initialize digital pins
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PwmOutputPin, OUTPUT);
pinMode(TestOutputPin, OUTPUT);
// initialize the input capture pin
// ??? --- I'm not sure if this is enough, do I need to configure my input pin as an 'Input Capture' alternate function --- ???
pinMode(CaptureInputPin, INPUT);
// create a 1KHz 25% (256/4 - 1) duty PWM on testPwmOutputPin
// NOTE - uses TIM associated with output pin
analogWrite(PwmOutputPin, (64 - 1));
Print_TIM2_Regs();
// ------- Input Capture TIM3 Setup -------------------------
// TIM3_IRQn interrupt configuration
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_IC_InitTypeDef sConfigIC;
htim3.Instance = TIM3;
htim3.Init.Prescaler = (64 - 1);
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFF;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
//sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
TIM3->CR1 = TIM_CR1_CEN;
TIM3->DIER = TIM_DIER_CC1IE | TIM_DIER_CC2IE;
//TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E;
TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC1P;
Print_TIM3_Regs();
}
void loop() {
float ch1_msec = ((float) channel_1 / 1000.0);
Serial.print("channel_1(mSec): "); Serial.println(ch1_msec);
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(500);
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(500);
}
void Print_TIM2_Regs() {
Serial.print("TIM2->CR1: "); Serial.println(TIM2->CR1, HEX);
Serial.print("TIM2->CR2: "); Serial.println(TIM2->CR2, HEX);
Serial.print("TIM2->DIER: "); Serial.println(TIM2->DIER, HEX);
Serial.print("TIM2->SR: "); Serial.println(TIM2->SR, HEX);
Serial.print("TIM2->CNT: "); Serial.println(TIM2->CNT, HEX);
Serial.print("TIM2->PSC: "); Serial.println(TIM2->PSC, HEX);
Serial.print("TIM2->ARR: "); Serial.println(TIM2->ARR, HEX);
Serial.println();
}
void Print_TIM3_Regs() {
Serial.print("TIM3->CR1: "); Serial.println(TIM3->CR1, HEX);
Serial.print("TIM3->CR2: "); Serial.println(TIM3->CR2, HEX);
Serial.print("TIM3->DIER: "); Serial.println(TIM3->DIER, HEX);
Serial.print("TIM3->SR: "); Serial.println(TIM3->SR, HEX);
Serial.print("TIM3->CNT: "); Serial.println(TIM3->CNT, HEX);
Serial.print("TIM3->PSC: "); Serial.println(TIM3->PSC, HEX);
Serial.print("TIM3->ARR: "); Serial.println(TIM3->ARR, HEX);
Serial.println();
}
void _Error_Handler(const char * msg, int val) {
Serial.print("Error: ");
Serial.print(msg);
Serial.print(" Err: ");
Serial.println(val);
while(1) {}
}
I’m using a Nucleo-F302R8 I have available. First make sure I can generate a PWM output on a pin with a hardware timer like we did on F1. I needed to move Serial to Serial2 to get the serial monitor working. I’ve stared at variant.h for F302R8 vs F103RB but it is not yet obvious to me why that is, but it works so moving on. I’ll use PB8 to generate the pwm output, which looks to map to TIM16_CH1 (TIM16, TIM17 are single channel only on ‘F302).
Simple test sketch for F302R8
// From PeripheralPins.c
// {PB_8, TIM16, STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF1_TIM16, 1, 0)}, // TIM16_CH1
const int testPwmOutputPin = PB8; // PB_8 = D15
#define MySerial Serial2
void setup() {
MySerial.begin(115200);
MySerial.println("Initializing...");
// initialize digital pins
pinMode(LED_BUILTIN, OUTPUT);
pinMode(testPwmOutputPin, OUTPUT);
analogWrite(testPwmOutputPin, 63);
}
void loop() {
MySerial.println("loop");
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(500); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(500); // wait for a second
}
It took me some time to weed through the HAL, datasheet and reference manual info to move my example from F103RB to F303RE, more work than I would have had hoped. After a good bit of reading and use of CubeMX tool to understand differences and key initialization steps, I finally have my example working on Nucleo-F303RE demo board with a jumper from PWM output pin to a TIM input capture pin. I’ve learned that F1 handles alternate pin function assignment in a different manner than the rest of the STM32 family, also that F303 runs at 72MHz instead of 64MHz, etc. But the basic structure of the F1 example is more or less intact, things are now working with the code below.
So I think at this point I’ve now accomplished my learning goal. I probably have some duplicate initialization steps in my code that the core might also be duplicating, and there is some work that could be done by the core’s timer.c functions. At this point I’ll declare this one [SOLVED] and will move on to porting my application.
Some helpful links for future reference:
STM Core HardwareTimer under construction – https://github.com/stm32duino/Arduino_C … issues/146
HAL vs LL vs SPL – https://blog.domski.pl/spl-vs-hal-which … ry-part-2/
George
Arduino test sketch for Nucleo-F303RE:
#define HAL_WAY 0
// {PB_5, TIM17, STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF10_TIM17, 1, 0)}, // TIM17_CH1
static TIM_HandleTypeDef htim17;
// From PeripheralPins.c
const int PwmOutputPin = PB8; // PB_8 = D15, uses TIM16_CH1
const int CaptureInputPin = PB5; // D4, also TIM17_CH1 Alternate Function
const int TestOutputPin = PA7; // D11
volatile int32_t channel_1_start;
volatile int32_t channel_1;
extern "C" void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
UNUSED(htim);
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)) {
// if (0b1 & GPIOB->IDR >> 5) { // If the receiver channel 1 input pulse on PB5 is high.
channel_1_start = TIM17->CCR1; // Record the start time of the pulse.
TIM17->CCER |= TIM_CCER_CC1P; // Change the input capture mode to the falling edge of the pulse.
}
else { // If the receiver channel 1 input pulse on PB5 is low.
channel_1 = TIM17->CCR1 - channel_1_start; // Calculate the total pulse time.
if (channel_1 < 0)channel_1 += 0xFFFF; // If the timer has rolled over a correction is needed.
TIM17->CCER &= ~TIM_CCER_CC1P; // Change the input capture mode to the rising edge of the pulse.
}
digitalWrite(TestOutputPin, !digitalRead(TestOutputPin)); // the Arduino way (works)
//HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_15); // the HAL way (also works)
}
void setup() {
Serial.begin(115200);
Serial.print("Initializing...");
Serial.println(getTimerClkFreq(TIM1));
Serial.println();
// initialize digital pins
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PwmOutputPin, OUTPUT);
pinMode(TestOutputPin, OUTPUT);
// initialize the input capture pin
// ??? --- I'm not sure if this is enough, do I need to configure my input pin as an 'Input Capture' alternate function --- ???
pinMode(CaptureInputPin, INPUT);
// create a 1KHz 25% (256/4 - 1) duty PWM on testPwmOutputPin
// NOTE - uses TIM associated with output pin
analogWrite(PwmOutputPin, (64 - 1));
Print_TIM16_Regs();
// ------- Input Capture TIM17 Setup -------------------------
RCC_PeriphCLKInitTypeDef PeriphClkInit;
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_TIM17;
PeriphClkInit.Tim17ClockSelection = RCC_TIM17CLK_HCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
GPIO_InitTypeDef GPIO_InitStruct;
// Peripheral clock enable
__HAL_RCC_TIM17_CLK_ENABLE();
// TIM17 GPIO Configuration
// PB5 --> TIM17_CH1
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF10_TIM17;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// TIM17 interrupt Init
HAL_NVIC_SetPriority(TIM1_TRG_COM_TIM17_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM1_TRG_COM_TIM17_IRQn);
TIM_IC_InitTypeDef sConfigIC;
htim17.Instance = TIM17;
htim17.Init.Prescaler = (72 - 1);
htim17.Init.CounterMode = TIM_COUNTERMODE_UP;
htim17.Init.Period = 0xFFFF;
htim17.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim17.Init.RepetitionCounter = 0;
htim17.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim17) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_Init(&htim17) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim17, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
TIM17->CR1 = TIM_CR1_CEN;
TIM17->DIER = TIM_DIER_CC1IE | TIM_DIER_CC2IE;
TIM17->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E;
//TIM17->CCER = TIM_CCER_CC1E | TIM_CCER_CC1P;
Print_TIM17_Regs();
}
void loop() {
float ch1_msec = ((float) channel_1 / 1000.0);
Serial.print("channel_1(mSec): "); Serial.println(ch1_msec);
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(500);
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(500);
}
void Print_TIM17_Regs() {
Serial.print("TIM17->CR1: "); Serial.println(TIM17->CR1, HEX);
Serial.print("TIM17->CR2: "); Serial.println(TIM17->CR2, HEX);
Serial.print("TIM17->DIER: "); Serial.println(TIM17->DIER, HEX);
Serial.print("TIM17->SR: "); Serial.println(TIM17->SR, HEX);
Serial.print("TIM17->CNT: "); Serial.println(TIM17->CNT, HEX);
Serial.print("TIM17->PSC: "); Serial.println(TIM17->PSC, HEX);
Serial.print("TIM17->ARR: "); Serial.println(TIM17->ARR, HEX);
Serial.println();
}
void Print_TIM16_Regs() {
Serial.print("TIM16->CR1: "); Serial.println(TIM16->CR1, HEX);
Serial.print("TIM16->CR2: "); Serial.println(TIM16->CR2, HEX);
Serial.print("TIM16->DIER: "); Serial.println(TIM16->DIER, HEX);
Serial.print("TIM16->SR: "); Serial.println(TIM16->SR, HEX);
Serial.print("TIM16->CNT: "); Serial.println(TIM16->CNT, HEX);
Serial.print("TIM16->PSC: "); Serial.println(TIM16->PSC, HEX);
Serial.print("TIM16->ARR: "); Serial.println(TIM16->ARR, HEX);
Serial.println();
}
void _Error_Handler(const char * msg, int val) {
Serial.print("Error: ");
Serial.print(msg);
Serial.print(" Err: ");
Serial.println(val);
while(1) {}
}
I also chased down the call stack that finally calls my capture interrupt handler in my sketch. When TIM3 CC hw interrupt fires we go through:
TIM3_IRQHandler() (timer.c:1075) –>
HAL_TIM_IRQHandler() (stm32f1xx_hal_tim.c:2784) –>
HAL_TIM_IC_CaptureCallback() (my sketch ino)
You can see the call stack in the middle right panel of the Ozone screen cap below (right click the image into a new browser tab to see it better).
I’m finding it difficult to know what combinations of HAL commands do what and how to verify that it is doing what I expect without staring at the HAL code plus the data sheets and reference manuals. This would seem to defeat the purpose of a hardware abstraction layer. I may explore LL functions as they appear to be more direct and verifiable.
George

#define HAL_WAY
static TIM_HandleTypeDef htim3;
const int PwmOutputPin = PA0; // PA_0,D46/A0 -- USES TIM2
const int CaptureInputPin = PA6;
const int TestOutputPin = D15;
volatile int32_t channel_1_start;
volatile int32_t channel_1;
extern "C" void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
UNUSED(htim);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6)) {
// if (0b1 & GPIOA->IDR >> 6) { // If the receiver channel 1 input pulse on A6 is high.
channel_1_start = TIM3->CCR1; // Record the start time of the pulse.
TIM3->CCER |= TIM_CCER_CC1P; // Change the input capture mode to the falling edge of the pulse.
}
else { // If the receiver channel 1 input pulse on A0 is low.
channel_1 = TIM3->CCR1 - channel_1_start; // Calculate the total pulse time.
if (channel_1 < 0)channel_1 += 0xFFFF; // If the timer has rolled over a correction is needed.
TIM3->CCER &= ~TIM_CCER_CC1P; // Change the input capture mode to the rising edge of the pulse.
}
#ifdef HAL_WAY
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8); // the HAL way (also works)
#else
digitalWrite(TestOutputPin, !digitalRead(TestOutputPin)); // the Arduino way (works)
#endif
}
void setup() {
Serial.begin(115200);
Serial.print("Initializing...");
Serial.println(getTimerClkFreq(TIM1));
Serial.println();
// initialize digital pins
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PwmOutputPin, OUTPUT);
pinMode(TestOutputPin, OUTPUT);
// initialize the input capture pin
// ??? --- I'm not sure if this is enough, do I need to configure my input pin as an 'Input Capture' alternate function --- ???
pinMode(CaptureInputPin, INPUT);
// create a 1KHz 25% (256/4 - 1) duty PWM on testPwmOutputPin
// NOTE - uses TIM associated with output pin
analogWrite(PwmOutputPin, (64 - 1));
Print_TIM2_Regs();
// ------- Input Capture TIM3 Setup -------------------------
GPIO_InitTypeDef GPIO_InitStruct;
/*
// TIM3 GPIO Configuration
// PB4 -> TIM3_CH1
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
__HAL_AFIO_REMAP_TIM3_PARTIAL();
*/
// TIM3 interrupt Init
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_IC_InitTypeDef sConfigIC;
htim3.Instance = TIM3;
htim3.Init.Prescaler = (64 - 1);
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFF;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
#ifdef HAL_WAY
__HAL_TIM_ENABLE_IT(&htim3, TIM_IT_CC1 | TIM_IT_CC2);
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
HAL_TIM_IC_Start (&htim3, TIM_CHANNEL_1);
#else
TIM3->CR1 = TIM_CR1_CEN;
TIM3->DIER = TIM_DIER_CC1IE | TIM_DIER_CC2IE;
//TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E;
TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC1P;
#endif
Print_TIM3_Regs();
}
void loop() {
float ch1_msec = ((float) channel_1 / 1000.0);
Serial.print("channel_1(mSec): "); Serial.println(ch1_msec);
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(500);
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(500);
}
void Print_TIM2_Regs() {
Serial.print("TIM2->CR1: "); Serial.println(TIM2->CR1, HEX);
Serial.print("TIM2->CR2: "); Serial.println(TIM2->CR2, HEX);
Serial.print("TIM2->DIER: "); Serial.println(TIM2->DIER, HEX);
Serial.print("TIM2->CCER: "); Serial.println(TIM2->CCER, HEX);
Serial.print("TIM2->SR: "); Serial.println(TIM2->SR, HEX);
Serial.print("TIM2->CNT: "); Serial.println(TIM2->CNT, HEX);
Serial.print("TIM2->PSC: "); Serial.println(TIM2->PSC, HEX);
Serial.print("TIM2->ARR: "); Serial.println(TIM2->ARR, HEX);
Serial.println();
}
void Print_TIM3_Regs() {
Serial.print("TIM3->CR1: "); Serial.println(TIM3->CR1, HEX);
Serial.print("TIM3->CR2: "); Serial.println(TIM3->CR2, HEX);
Serial.print("TIM3->DIER: "); Serial.println(TIM3->DIER, HEX);
Serial.print("TIM3->CCER: "); Serial.println(TIM3->CCER, HEX);
Serial.print("TIM3->SR: "); Serial.println(TIM3->SR, HEX);
Serial.print("TIM3->CNT: "); Serial.println(TIM3->CNT, HEX);
Serial.print("TIM3->PSC: "); Serial.println(TIM3->PSC, HEX);
Serial.print("TIM3->ARR: "); Serial.println(TIM3->ARR, HEX);
Serial.println();
}
void _Error_Handler(const char * msg, int val) {
Serial.print("Error: ");
Serial.print(msg);
Serial.print(" Err: ");
Serial.println(val);
while(1) {}
}
#define HAL_WAY
static TIM_HandleTypeDef htim3;
const int PwmOutputPin = PA0; // PA_0,D46/A0 -- USES TIM2
const int CaptureInputPin = PB4;
const int TestOutputPin = D15;
volatile int32_t channel_1_start;
volatile int32_t channel_1;
extern "C" void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
UNUSED(htim);
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)) {
// if (0b1 & GPIOA->IDR >> 6) { // If the receiver channel 1 input pulse on A6 is high.
channel_1_start = TIM3->CCR1; // Record the start time of the pulse.
TIM3->CCER |= TIM_CCER_CC1P; // Change the input capture mode to the falling edge of the pulse.
}
else { // If the receiver channel 1 input pulse on A0 is low.
channel_1 = TIM3->CCR1 - channel_1_start; // Calculate the total pulse time.
if (channel_1 < 0)channel_1 += 0xFFFF; // If the timer has rolled over a correction is needed.
TIM3->CCER &= ~TIM_CCER_CC1P; // Change the input capture mode to the rising edge of the pulse.
}
#ifdef HAL_WAY
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8); // the HAL way (also works)
#else
digitalWrite(TestOutputPin, !digitalRead(TestOutputPin)); // the Arduino way (works)
#endif
}
void setup() {
Serial.begin(115200);
Serial.print("Initializing...");
Serial.println(getTimerClkFreq(TIM1));
Serial.println();
// initialize digital pins
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PwmOutputPin, OUTPUT);
pinMode(TestOutputPin, OUTPUT);
// initialize the input capture pin
// ??? --- I'm not sure if this is enough, do I need to configure my input pin as an 'Input Capture' alternate function --- ???
pinMode(CaptureInputPin, INPUT);
// create a 1KHz 25% (256/4 - 1) duty PWM on testPwmOutputPin
// NOTE - uses TIM associated with output pin
analogWrite(PwmOutputPin, (64 - 1));
Print_TIM2_Regs();
// ------- Input Capture TIM3 Setup -------------------------
GPIO_InitTypeDef GPIO_InitStruct;
/*
// TIM3 GPIO Configuration
// PB4 -> TIM3_CH1
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
*/
__HAL_AFIO_REMAP_TIM3_PARTIAL();
// TIM3 interrupt Init
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_IC_InitTypeDef sConfigIC;
htim3.Instance = TIM3;
htim3.Init.Prescaler = (64 - 1);
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFF;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
#ifdef HAL_WAY
__HAL_TIM_ENABLE_IT(&htim3, TIM_IT_CC1 | TIM_IT_CC2);
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
HAL_TIM_IC_Start (&htim3, TIM_CHANNEL_1);
#else
TIM3->CR1 = TIM_CR1_CEN;
TIM3->DIER = TIM_DIER_CC1IE | TIM_DIER_CC2IE;
//TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E;
TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC1P;
#endif
Print_TIM3_Regs();
}
void loop() {
float ch1_msec = ((float) channel_1 / 1000.0);
Serial.print("channel_1(mSec): "); Serial.println(ch1_msec);
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(500);
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(500);
}
void Print_TIM2_Regs() {
Serial.print("TIM2->CR1: "); Serial.println(TIM2->CR1, HEX);
Serial.print("TIM2->CR2: "); Serial.println(TIM2->CR2, HEX);
Serial.print("TIM2->DIER: "); Serial.println(TIM2->DIER, HEX);
Serial.print("TIM2->CCER: "); Serial.println(TIM2->CCER, HEX);
Serial.print("TIM2->SR: "); Serial.println(TIM2->SR, HEX);
Serial.print("TIM2->CNT: "); Serial.println(TIM2->CNT, HEX);
Serial.print("TIM2->PSC: "); Serial.println(TIM2->PSC, HEX);
Serial.print("TIM2->ARR: "); Serial.println(TIM2->ARR, HEX);
Serial.println();
}
void Print_TIM3_Regs() {
Serial.print("TIM3->CR1: "); Serial.println(TIM3->CR1, HEX);
Serial.print("TIM3->CR2: "); Serial.println(TIM3->CR2, HEX);
Serial.print("TIM3->DIER: "); Serial.println(TIM3->DIER, HEX);
Serial.print("TIM3->CCER: "); Serial.println(TIM3->CCER, HEX);
Serial.print("TIM3->SR: "); Serial.println(TIM3->SR, HEX);
Serial.print("TIM3->CNT: "); Serial.println(TIM3->CNT, HEX);
Serial.print("TIM3->PSC: "); Serial.println(TIM3->PSC, HEX);
Serial.print("TIM3->ARR: "); Serial.println(TIM3->ARR, HEX);
Serial.println();
}
void _Error_Handler(const char * msg, int val) {
Serial.print("Error: ");
Serial.print(msg);
Serial.print(" Err: ");
Serial.println(val);
while(1) {}
}
I dug out an old homebrew AVR ATTiny84 based RC pulse simulator, the AVR device is programmed to generate 6 separate sequential 1-2mSec pulses every 20 mSec (50 Hz) to simulate an old-school RC receiver. Similar to the Channel 1-6 Servo Signal pulses below. I hooked the 6 channels of the simulator up to my Nucleo-F103RB board on TIM3 channel 1-4 and TIM4 channel 1-2 pins per the datasheet.
Studying HAL_TIM_IRQHandler I can see that the handler uses __HAL_TIM_GET_IT_SOURCE() to determine the TIM interrupt source. In the routine htim->Channel is used to save the active TIM channel before calling HAL_TIM_IC_CaptureCallback(), so I can use it in my channel pulse width calculations.


//#define HAL_WAY
/*
I.) ATMEL ATTINY84/ARDUINO ppm generator test rig pinout-------------------*
+--\/--+
VCC 1 | | 14 GND
CH1 (D0) PB0 2 | | 13 AREF (D10)
CH2 (D1) PB1 3 | | 12 PA1 (D9) SoftSerial Tx
PB3 4 | | 11 PA2 (D8) SoftSerial Rx
CH3 (D2) PB2 5 | | 10 PA3 (D7)
CH4 (D3) PA7 6 | | 9 PA4 (D6) CH6
(*PPMout)--> ppm (D4) PA6 7 | | 8 PA5 (D5) CH5
+------+
II.) STM32 Nucleo-F103RB default peripheral pins---------------------------*
PA0 = WKUP / USART2_CTS / ADC12_IN0 / TIM2_CH1_ETR
PA1 = USART2_RTS / ADC12_IN1 / TIM2_CH2
PA2 = USART2_TX(9) / ADC12_IN2 / TIM2_CH3
PA3 = USART2_RX / ADC12_IN3 / TIM2_CH4
PA6 = SPI1_MISO / ADC12_IN6 / TIM3_CH1
PA7 = SPI1_MOSI / ADC12_IN7 / TIM3_CH2
PB0 = ADC12_IN8 / TIM3_CH3
PB1 = ADC12_IN9 / TIM3_CH4
PB6 = I2C1_SCL / TIM4_CH1
PB7 = I2C1_SDA / TIM4_CH2
PB8 = TIM4_CH3
PB9 = TIM4_CH4
---------------------------------------------------------------------------*/
static TIM_HandleTypeDef htim3;
static TIM_HandleTypeDef htim4;
const int PwmOutputPin = PA0; // PA_0,D46/A0 -- USES TIM2
const int CaptureInputPin1 = PA6;
const int CaptureInputPin2 = PA7;
const int CaptureInputPin3 = PB0;
const int CaptureInputPin4 = PB1;
const int CaptureInputPin5 = PB6;
const int CaptureInputPin6 = PB7;
const int TestOutputPin = D15;
volatile int32_t channel_1_start, channel_1;
volatile int32_t channel_2_start, channel_2;
volatile int32_t channel_3_start, channel_3;
volatile int32_t channel_4_start, channel_4;
volatile int32_t channel_5_start, channel_5;
volatile int32_t channel_6_start, channel_6;
extern "C" void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
long address;
address = (long) htim->Instance;
switch (address)
{
case TIM3_BASE :
digitalWrite(TestOutputPin, !digitalRead(TestOutputPin)); // the Arduino way (works)
break;
case TIM4_BASE :
digitalWrite(TestOutputPin, !digitalRead(TestOutputPin)); // the Arduino way (works)
break;
default :
break;
}
if (htim->Instance == TIM3) {
switch(htim->Channel)
{
case HAL_TIM_ACTIVE_CHANNEL_1 :
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6)) { // if the receiver channel 1 input pulse on A6 is high
channel_1_start = TIM3->CCR1; // record the start time of the pulse
TIM3->CCER |= TIM_CCER_CC1P; // change the input capture mode to the falling edge of the pulse
} else { // if the receiver channel 1 input pulse on A6 is low
channel_1 = TIM3->CCR1 - channel_1_start; // calculate the total pulse time
if (channel_1 < 0)channel_1 += 0xFFFF; // if the timer has rolled over a correction is needed
TIM3->CCER &= ~TIM_CCER_CC1P; // change the input capture mode to the rising edge of the pulse
}
break;
case HAL_TIM_ACTIVE_CHANNEL_2 :
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7)) { // if the receiver channel 2 input pulse on A7 is high
channel_2_start = TIM3->CCR2; // record the start time of the pulse
TIM3->CCER |= TIM_CCER_CC2P; // change the input capture mode to the falling edge of the pulse
} else { // if the receiver channel 2 input pulse on A7 is low
channel_2 = TIM3->CCR2 - channel_2_start; // calculate the total pulse time
if (channel_2 < 0)channel_2 += 0xFFFF; // if the timer has rolled over a correction is needed
TIM3->CCER &= ~TIM_CCER_CC2P; // change the input capture mode to the rising edge of the pulse
}
break;
case HAL_TIM_ACTIVE_CHANNEL_3 :
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)) { // if the receiver channel 3 input pulse on B0 is high
channel_3_start = TIM3->CCR3; // record the start time of the pulse
TIM3->CCER |= TIM_CCER_CC3P; // change the input capture mode to the falling edge of the pulse
} else { // if the receiver channel 3 input pulse on B0 is low
channel_3 = TIM3->CCR3 - channel_3_start; // calculate the total pulse time
if (channel_3 < 0)channel_3 += 0xFFFF; // if the timer has rolled over a correction is needed
TIM3->CCER &= ~TIM_CCER_CC3P; // change the input capture mode to the rising edge of the pulse
}
break;
case HAL_TIM_ACTIVE_CHANNEL_4 :
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)) { // if the receiver channel 4 input pulse on B1 is high
channel_4_start = TIM3->CCR4; // record the start time of the pulse
TIM3->CCER |= TIM_CCER_CC4P; // change the input capture mode to the falling edge of the pulse
} else { // if the receiver channel 4 input pulse on B4 is low
channel_4 = TIM3->CCR4 - channel_4_start; // calculate the total pulse time
if (channel_4 < 0)channel_4 += 0xFFFF; // if the timer has rolled over a correction is needed
TIM3->CCER &= ~TIM_CCER_CC4P; // change the input capture mode to the rising edge of the pulse
}
break;
default :
break;
}
} else if (htim->Instance == TIM4) {
switch(htim->Channel)
{
case HAL_TIM_ACTIVE_CHANNEL_1 :
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6)) { // if the receiver channel 5 input pulse on B6 is high
channel_5_start = TIM4->CCR1; // record the start time of the pulse
TIM4->CCER |= TIM_CCER_CC1P; // change the input capture mode to the falling edge of the pulse
} else { // if the receiver channel 5 input pulse on B6 is low
channel_5 = TIM4->CCR1 - channel_5_start; // calculate the total pulse time
if (channel_5 < 0)channel_5 += 0xFFFF; // if the timer has rolled over a correction is needed
TIM4->CCER &= ~TIM_CCER_CC1P; // change the input capture mode to the rising edge of the pulse
}
break;
case HAL_TIM_ACTIVE_CHANNEL_2 :
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)) { // if the receiver channel 6 input pulse on B7 is high
channel_6_start = TIM4->CCR2; // record the start time of the pulse
TIM4->CCER |= TIM_CCER_CC2P; // change the input capture mode to the falling edge of the pulse
} else { // if the receiver channel 6 input pulse on B7 is low
channel_6 = TIM4->CCR2 - channel_6_start; // calculate the total pulse time
if (channel_6 < 0)channel_6 += 0xFFFF; // if the timer has rolled over a correction is needed
TIM4->CCER &= ~TIM_CCER_CC2P; // change the input capture mode to the rising edge of the pulse
}
break;
default :
break;
}
}
#ifdef HAL_WAY
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8); // the HAL way (also works)
#else
digitalWrite(TestOutputPin, !digitalRead(TestOutputPin)); // the Arduino way (works)
#endif
}
void setup() {
Serial.begin(115200);
Serial.print("Initializing...");
Serial.println(getTimerClkFreq(TIM1));
Serial.println();
// initialize digital pins
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PwmOutputPin, OUTPUT);
pinMode(TestOutputPin, OUTPUT);
// initialize the input capture pins
pinMode(CaptureInputPin1, INPUT);
pinMode(CaptureInputPin2, INPUT);
pinMode(CaptureInputPin3, INPUT);
pinMode(CaptureInputPin4, INPUT);
pinMode(CaptureInputPin5, INPUT);
pinMode(CaptureInputPin6, INPUT);
// create a 1KHz 25% (256/4 - 1) duty PWM on testPwmOutputPin
// NOTE - uses TIM associated with output pin
analogWrite(PwmOutputPin, (64 - 1));
Print_TIM2_Regs();
// ------- Input Capture TIM3 Setup -------------------------
GPIO_InitTypeDef GPIO_InitStruct;
// TIM3 interrupt Init
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_IC_InitTypeDef sConfigIC;
htim3.Instance = TIM3;
htim3.Init.Prescaler = (64 - 1);
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFF;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_4) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
#ifdef HAL_WAY
__HAL_TIM_ENABLE_IT(&htim3, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4);
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
HAL_TIM_IC_Start (&htim3, TIM_CHANNEL_1);
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_FALLING);
HAL_TIM_IC_Start (&htim3, TIM_CHANNEL_2);
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_3, TIM_INPUTCHANNELPOLARITY_FALLING);
HAL_TIM_IC_Start (&htim3, TIM_CHANNEL_3);
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_4, TIM_INPUTCHANNELPOLARITY_FALLING);
HAL_TIM_IC_Start (&htim3, TIM_CHANNEL_4);
#else
TIM3->DIER = TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE | TIM_DIER_CC4IE;
TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC1P | TIM_CCER_CC2E | TIM_CCER_CC2P | TIM_CCER_CC3E | TIM_CCER_CC3P | TIM_CCER_CC4E | TIM_CCER_CC4P;
TIM3->CR1 = TIM_CR1_CEN;
#endif
Print_TIM3_Regs();
// ------- Input Capture TIM3 Setup -------------------------
// TIM3 interrupt Init
HAL_NVIC_SetPriority(TIM4_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM4_IRQn);
htim4.Instance = TIM4;
htim4.Init.Prescaler = (64 - 1);
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 0xFFFF;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_Init(&htim4) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim4, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_IC_ConfigChannel(&htim4, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
TIM4->DIER = TIM_DIER_CC1IE | TIM_DIER_CC2IE;
TIM4->CCER = TIM_CCER_CC1E | TIM_CCER_CC1P | TIM_CCER_CC2E | TIM_CCER_CC2P;
TIM4->CR1 = TIM_CR1_CEN;
Print_TIM4_Regs();
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(500);
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(500);
float ch1_msec = ((float) channel_1 / 1000.0);
float ch2_msec = ((float) channel_2 / 1000.0);
float ch3_msec = ((float) channel_3 / 1000.0);
float ch4_msec = ((float) channel_4 / 1000.0);
float ch5_msec = ((float) channel_5 / 1000.0);
float ch6_msec = ((float) channel_6 / 1000.0);
Serial.print("channel_1(mSec): "); Serial.print(ch1_msec);
Serial.print(" | channel_2(mSec): "); Serial.print(ch2_msec);
Serial.print(" | channel_3(mSec): "); Serial.print(ch3_msec);
Serial.print(" | channel_4(mSec): "); Serial.print(ch4_msec);
Serial.print(" | channel_5(mSec): "); Serial.print(ch5_msec);
Serial.print(" | channel_6(mSec): "); Serial.println(ch6_msec);
}
void Print_TIM2_Regs() {
Serial.print("TIM2->CR1: "); Serial.println(TIM2->CR1, HEX);
Serial.print("TIM2->CR2: "); Serial.println(TIM2->CR2, HEX);
Serial.print("TIM2->DIER: "); Serial.println(TIM2->DIER, HEX);
Serial.print("TIM2->CCER: "); Serial.println(TIM2->CCER, HEX);
Serial.print("TIM2->SR: "); Serial.println(TIM2->SR, HEX);
Serial.print("TIM2->CNT: "); Serial.println(TIM2->CNT, HEX);
Serial.print("TIM2->PSC: "); Serial.println(TIM2->PSC, HEX);
Serial.print("TIM2->ARR: "); Serial.println(TIM2->ARR, HEX);
Serial.println();
}
void Print_TIM3_Regs() {
Serial.print("TIM3->CR1: "); Serial.println(TIM3->CR1, HEX);
Serial.print("TIM3->CR2: "); Serial.println(TIM3->CR2, HEX);
Serial.print("TIM3->DIER: "); Serial.println(TIM3->DIER, HEX);
Serial.print("TIM3->CCER: "); Serial.println(TIM3->CCER, HEX);
Serial.print("TIM3->SR: "); Serial.println(TIM3->SR, HEX);
Serial.print("TIM3->CNT: "); Serial.println(TIM3->CNT, HEX);
Serial.print("TIM3->PSC: "); Serial.println(TIM3->PSC, HEX);
Serial.print("TIM3->ARR: "); Serial.println(TIM3->ARR, HEX);
Serial.println();
}
void Print_TIM4_Regs() {
Serial.print("TIM4->CR1: "); Serial.println(TIM4->CR1, HEX);
Serial.print("TIM4->CR2: "); Serial.println(TIM4->CR2, HEX);
Serial.print("TIM4->DIER: "); Serial.println(TIM4->DIER, HEX);
Serial.print("TIM4->CCER: "); Serial.println(TIM4->CCER, HEX);
Serial.print("TIM4->SR: "); Serial.println(TIM4->SR, HEX);
Serial.print("TIM4->CNT: "); Serial.println(TIM4->CNT, HEX);
Serial.print("TIM4->PSC: "); Serial.println(TIM4->PSC, HEX);
Serial.print("TIM4->ARR: "); Serial.println(TIM4->ARR, HEX);
Serial.println();
}
void _Error_Handler(const char * msg, int val) {
Serial.print("Error: ");
Serial.print(msg);
Serial.print(" Err: ");
Serial.println(val);
while(1) {}
}

