Thread Tools
This thread is privately moderated by MGeo, who may elect to delete unwanted replies.
Jan 06, 2019, 06:35 AM
Registered User
MGeo's Avatar
Discussion

Input Capture Hardware Timer example with Interrupt


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):
Code:
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.
  }
}
Timer initialization code (note that it uses high level interrupt attach but also some low level register bit manipulation for init):
Code:
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//In this file the timers for reading the receiver pulses and for creating the output ESC pulses are set.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//More information can be found in these two videos:
//STM32 for Arduino - Connecting an RC receiver via input capture mode: https://youtu.be/JFSFbSg0l2M
//STM32 for Arduino - Electronic Speed Controller (ESC) - STM32F103C8T6: https://youtu.be/Nju9rvZOjVQ
/////////////////////////////////////////////////////////////////////////////////////////////////////////
void timer_setup(void) {
  Timer2.attachCompare1Interrupt(handler_channel_1);
  Timer2.attachCompare2Interrupt(handler_channel_2);
  Timer2.attachCompare3Interrupt(handler_channel_3);
  Timer2.attachCompare4Interrupt(handler_channel_4);
  TIMER2_BASE->CR1 = TIMER_CR1_CEN;
  TIMER2_BASE->CR2 = 0;
  TIMER2_BASE->SMCR = 0;
  TIMER2_BASE->DIER = TIMER_DIER_CC1IE | TIMER_DIER_CC2IE | TIMER_DIER_CC3IE | TIMER_DIER_CC4IE;
  TIMER2_BASE->EGR = 0;
  TIMER2_BASE->CCMR1 = 0b100000001; //Register is set like this due to a bug in the define table (30-09-2017)
  TIMER2_BASE->CCMR2 = 0b100000001; //Register is set like this due to a bug in the define table (30-09-2017)
  TIMER2_BASE->CCER = TIMER_CCER_CC1E | TIMER_CCER_CC2E | TIMER_CCER_CC3E | TIMER_CCER_CC4E;
  TIMER2_BASE->PSC = 71;
  TIMER2_BASE->ARR = 0xFFFF;
  TIMER2_BASE->DCR = 0;

  Timer3.attachCompare1Interrupt(handler_channel_5);
  Timer3.attachCompare2Interrupt(handler_channel_6);
  TIMER3_BASE->CR1 = TIMER_CR1_CEN;
  TIMER3_BASE->CR2 = 0;
  TIMER3_BASE->SMCR = 0;
  TIMER3_BASE->DIER = TIMER_DIER_CC1IE | TIMER_DIER_CC2IE;
  TIMER3_BASE->EGR = 0;
  TIMER3_BASE->CCMR1 = 0b100000001; //Register is set like this due to a bug in the define table (30-09-2017)
  TIMER3_BASE->CCMR2 = 0;
  TIMER3_BASE->CCER = TIMER_CCER_CC1E | TIMER_CCER_CC2E;
  TIMER3_BASE->PSC = 71;
  TIMER3_BASE->ARR = 0xFFFF;
  TIMER3_BASE->DCR = 0;

  TIMER4_BASE->CR1 = TIMER_CR1_CEN | TIMER_CR1_ARPE;
  TIMER4_BASE->CR2 = 0;
  TIMER4_BASE->SMCR = 0;
  TIMER4_BASE->DIER = 0;
  TIMER4_BASE->EGR = 0;
  TIMER4_BASE->CCMR1 = (0b110 << 4) | TIMER_CCMR1_OC1PE |(0b110 << 12) | TIMER_CCMR1_OC2PE;
  TIMER4_BASE->CCMR2 = (0b110 << 4) | TIMER_CCMR2_OC3PE |(0b110 << 12) | TIMER_CCMR2_OC4PE;
  TIMER4_BASE->CCER = TIMER_CCER_CC1E | TIMER_CCER_CC2E | TIMER_CCER_CC3E | TIMER_CCER_CC4E;
  TIMER4_BASE->PSC = 71;
  TIMER4_BASE->ARR = 5000;
  TIMER4_BASE->DCR = 0;
  TIMER4_BASE->CCR1 = 1000;

  TIMER4_BASE->CCR1 = 1000;
  TIMER4_BASE->CCR2 = 1000;
  TIMER4_BASE->CCR3 = 1000;
  TIMER4_BASE->CCR4 = 1000;
  pinMode(PB6, PWM);
  pinMode(PB7, PWM);
  pinMode(PB8, PWM);
  pinMode(PB9, PWM);
}
Last edited by MGeo; Jan 06, 2019 at 07:13 AM.
Sign up now
to remove ads between posts
Jan 06, 2019, 06:36 AM
Registered User
MGeo's Avatar
My test setup:
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/debu...link-on-board/)
Segger Ozone debugger (https://www.segger.com/products/deve...link-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).

Code:
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

}
Jan 06, 2019, 06:36 AM
Registered User
MGeo's Avatar
Incidentially, in my simple output example above I attempted to understand the pin name aliases by searching through the files. I found that for analog output 0 for my Nucleo-F103RB variant should be PA_0 per https://github.com/stm32duino/Arduin...ariant.cpp#L78

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.

Code:
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
George
Jan 06, 2019, 06:37 AM
Registered User
MGeo's Avatar
Tracing through my simple example in Ozone debugger, I can see that analogWrite to A0 is utilizing TIM2 as expected since PA0 pin is default TIM2_CH1 in the STM32F103 datasheet.

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
Jan 06, 2019, 06:39 AM
Registered User
MGeo's Avatar
In STM32CubeMX tool we can quickly find that TIM3_CH1 maps to PA6 by default, so I will connect a jumper from my known good 1Khz / 25% duty cycle PWM output on PA0/A0 to PA6 on the Nucleo board.

Below 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/Arduin...1xx_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.
Jan 06, 2019, 06:46 AM
Registered User
MGeo's Avatar
I went off looking for Input Capture HAL examples, the CubeMX F1 package comes with an example /Projects/STM3210E_EVAL/Examples/TIM/TIM_Input_Capture that I studied and have attempted to employ here.

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:
Code:
#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) {}
}
Test sketch terminal output:
Code:
Initializing...

TIM3->CR1: 1
TIM3->CR2: 0
TIM3->DIER: 1
TIM3->SR: 0
TIM3->CNT: 86A
TIM3->PSC: 3F
TIM3->ARR: 270F

TIM4->CR1: 0
TIM4->CR2: 0
TIM4->DIER: 0
TIM4->SR: 0
TIM4->CNT: 0
TIM4->PSC: 0
TIM4->ARR: 0
My test setup, a Nucleo-F103RB with a jumper connecting PWM output A0 to PA6 (TIM3_CH1) input.
Jan 06, 2019, 06:50 AM
Registered User
MGeo's Avatar
Good progress tonight. In an attempt to validate my TIM config approach, I used STM32CubeMX to set up a project based on Nucleo-F103RB, configuring TIM3 for input capture on Channel 1. I copied the project config code into my test sketch.

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:
Code:
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) {}
}
Terminal output from sketch, showing TIM3 updating as expected:
Code:
Initializing...

TIM2->CR1: 1
TIM2->CR2: 0
TIM2->DIER: 0
TIM2->SR: 3
TIM2->CNT: 2B
TIM2->PSC: F9
TIM2->ARR: FE

TIM3->CR1: 1
TIM3->CR2: 0
TIM3->DIER: 6
TIM3->SR: 1
TIM3->CNT: 18F2
TIM3->PSC: 3F
TIM3->ARR: FFFF

loop
loop
loop
loop
...
Ozone screen cap showing interrupt routine firing (stops at breakpoint within handler):
Jan 06, 2019, 06:53 AM
Registered User
MGeo's Avatar
You can see in the code above that I am toggling a digital output pin in the interrupt handler to change the pin's state every time the handler fires. So if we hook the logic analyzer up to pin PA6 (PWM pulse stream input to TIM3_CH1) and D15 (digital output toggled by interrupt handler) we should be able to see if things work as we expect. This would be D15 changing state on every rising edge of PA6. In the attached capture you can see this is exactly what we see.

White Trace: Pin PA6 = PWM pulse stream as input into TIM3_CH1
Orange Trace: Pin D15 = digital output toggled by interrupt handler
Jan 06, 2019, 06:54 AM
Registered User
MGeo's Avatar
The program I'm hoping to port over measures the pulse width on an input pin using a single TIM channel by flipping the CC1P bit within TIMx CCER register inside the interrupt handler. I've added this logic to the test program and can now successfully measure the pulse width, outputting the result to serial terminal output.

Code:
#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) {}
}
Terminal output from sketch
Code:
Initializing...

TIM2->CR1: 1
TIM2->CR2: 0
TIM2->DIER: 0
TIM2->SR: 3
TIM2->CNT: 2D
TIM2->PSC: F9
TIM2->ARR: FE

TIM3->CR1: 1
TIM3->CR2: 0
TIM3->DIER: 6
TIM3->SR: 1
TIM3->CNT: 18F2
TIM3->PSC: 3F
TIM3->ARR: FFFF

channel_1(mSec): 0.25
channel_1(mSec): 0.25
channel_1(mSec): 0.25
channel_1(mSec): 0.25
Logic analyzer capture of input pulse and interrupt handler test output.
Jan 06, 2019, 06:57 AM
Registered User
MGeo's Avatar
Next and maybe final task is to move this example over to F3 to verify portability of Input Capture with Interrupt example across families. I wanted to port a math intensive application over to this HAL based core so I can methodically move from F1 variants over to F3/F4 variants that have FPU.

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
Code:
// 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

}
Logic analyzer on Nucleo-F302R8 PB8 (white trace)
Jan 06, 2019, 06:58 AM
Registered User
MGeo's Avatar
My Nucleo-F303RE arrived so I've switched my example over to F303RE.

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/Arduin...M32/issues/146
HAL vs LL vs SPL - https://blog.domski.pl/spl-vs-hal-wh...ibrary-part-2/

George

Arduino test sketch for Nucleo-F303RE:
Code:
#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) {}
}
Serial terminal output:
Code:
Initializing...72000000

TIM16->CR1: 1
TIM16->CR2: 0
TIM16->DIER: 0
TIM16->SR: 3
TIM16->CNT: 50
TIM16->PSC: 119
TIM16->ARR: FE

TIM17->CR1: 1
TIM17->CR2: 0
TIM17->DIER: 2
TIM17->SR: 1
TIM17->CNT: 1A55
TIM17->PSC: 47
TIM17->ARR: FFFF

channel_1(mSec): 0.25
channel_1(mSec): 0.25
channel_1(mSec): 0.25
channel_1(mSec): 0.25
channel_1(mSec): 0.25
channel_1(mSec): 0.25
Logic capture:
Jan 06, 2019, 07:00 AM
Registered User
MGeo's Avatar
I went back to the Nucleo-F103RB and clean up some of the direct register bit manipulation I was doing, moving it over to HAL functions in keeping with the HAL way. It took some searching of the HAL code but eventually got it working.

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

Code:
#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) {}
}
Code:
Initializing...64000000

TIM2->CR1: 1
TIM2->CR2: 0
TIM2->DIER: 0
TIM2->CCER: 1
TIM2->SR: 1F
TIM2->CNT: 74
TIM2->PSC: F9
TIM2->ARR: FE

TIM3->CR1: 1
TIM3->CR2: 0
TIM3->DIER: 6
TIM3->CCER: 3
TIM3->SR: 1
TIM3->CNT: 1E8A
TIM3->PSC: 3F
TIM3->ARR: FFFF

channel_1(mSec): 0.25
channel_1(mSec): 0.25
channel_1(mSec): 0.25
Last edited by MGeo; Jan 06, 2019 at 07:14 AM.
Jan 06, 2019, 07:15 AM
Registered User
MGeo's Avatar
Successful remap of TIM3 Channel 1 input from default PA6 pin to alternate PB4 pin using __HAL_AFIO_REMAP_TIM3_PARTIAL():

Code:
#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) {}
}
Last edited by MGeo; Jan 06, 2019 at 07:38 AM.
Jan 06, 2019, 07:15 AM
Registered User
MGeo's Avatar
Next on my list is a four input channel example on a single TIM on an F103RB.

I dug out an old homebrew AVR ATTiny84 based RC pulse simulator (https://www.rcgroups.com/forums/show...0&postcount=21), 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.

Code:
//#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) {}
}
Code:
Initializing...64000000

TIM2->CR1: 1
TIM2->CR2: 0
TIM2->DIER: 0
TIM2->CCER: 1
TIM2->SR: 1F
TIM2->CNT: 64
TIM2->PSC: F9
TIM2->ARR: FE

TIM3->CR1: 1
TIM3->CR2: 0
TIM3->DIER: 1E
TIM3->CCER: 3333
TIM3->SR: 1
TIM3->CNT: 2003
TIM3->PSC: 3F
TIM3->ARR: FFFF

TIM4->CR1: 1
TIM4->CR2: 0
TIM4->DIER: 6
TIM4->CCER: 33
TIM4->SR: 1
TIM4->CNT: 1EE8
TIM4->PSC: 3F
TIM4->ARR: FFFF

channel_1(mSec): 1.11 | channel_2(mSec): 1.20 | channel_3(mSec): 1.30 | channel_4(mSec): 1.39 | channel_5(mSec): 1.50 | channel_6(mSec): 1.60
channel_1(mSec): 1.11 | channel_2(mSec): 1.20 | channel_3(mSec): 1.30 | channel_4(mSec): 1.39 | channel_5(mSec): 1.50 | channel_6(mSec): 1.60
channel_1(mSec): 1.11 | channel_2(mSec): 1.20 | channel_3(mSec): 1.30 | channel_4(mSec): 1.39 | channel_5(mSec): 1.50 | channel_6(mSec): 1.59
Jan 06, 2019, 03:13 PM
Registered User
MGeo's Avatar
Next up, to reduce wiring count I'm switching to single wire PPM input stream from my PPM pulse generator. The Rx is normally powered at +5V, and output voltage at the servo out pins is not specified, so I plan to use TIM4 inputs, which unlike TIM2 and TIM3 pins are 5V tolerant. I've chosen TIM_CH1 for input, which is found on PB6.

I'm also adding the MPU-6050 sensor which is wired to I2C1 alternate pins SDA on PB9 and SCL on PB8. The default pins for I2C1 are PB7 (SDA) and PB6 (SCL).


Quick Reply
Message:
Thread Tools

Similar Threads
Category Thread Thread Starter Forum Replies Last Post
Question Available hardware or examples of removable tail feathers valkrider Electric Sailplanes 0 Mar 12, 2015 10:56 AM
Question PIC32 Input Capture Problem/Question Superluminal DIY Electronics 3 Aug 18, 2010 06:21 AM
Anyone else having problems with little kids interrupting? Lynxman Micro Helis 33 Jul 05, 2004 10:55 PM