PWM input capture programming
PWM capture
The purpose is to measure the frequency and duty cycle of the PWM wave input to a specific pin.
The following is the circuit diagram of the PWM part:
PWM is generated by the XL555 chip and is connected to PA15 by the sliding rheostat R40. Different resistance values of the sliding rheostat correspond to different frequencies of the PWM wave. The same principle applies to the next one.
You can see that the functions of the PA15 pins on the board are: TIM2_CH1 and TIM8_CH1. We use TIM_CH1 on the board to capture PWM. PB4 pin we use TIM3_CH1 to capture PWM. (Both use basic timers)
By observing the waveforms generated by the two XL555s with an oscilloscope, we get that the frequency range of the available waveforms they generate is roughly: 700HZ-23KHZ. The duty cycle is about fifty percent.
Single-channel PWM capture programming
Programming idea:
Set the pin to a rising edge interrupt, that is, an interrupt will be generated every time it encounters the rising edge of the square wave.
When an interrupt is generated, it will enter the interrupt processing function and thus the callback function.
What should be done in the callback function? To get the CNT value. CNT is actually the travel time of a clock. We get this time on the previous rising edge, then clear it, and get this time on the next rising edge, which is the length of time between the two rising edges. So the CNT value is the period of a PWM wave, and its frequency can be obtained through the period.
To facilitate our calculation of the period, we can set CNT to 1 microsecond plus one. Then the value of CNT represents how many microseconds, corresponding to how many HZ frequency.
step:
- [Template] As a project for STM32CUBEMX generated code;
- Configure PA15 of TIM2 as TIM2 CH1 input capture;
- According to the requirements, configure the frequency division value of TIM2_CH1. It is recommended to configure it to count once every 1us;
- Port tim.c and tim.h to [Programming Project]
4.1 main.c contains #include" tim.h"
4.2 Add tim.c and stm32g4 HAL library functions to the project;
4.3 Start the TIM module in stm32g4xx hal_conf.h ;
4.4 In stm32g4xx it.c, transplant the TIM2_IRQHandler interrupt service function;
4.5 In the main function, call the MX TIM2 lnit) timer initialization function and HAL_TIM_IC_Start_IT&htim2, TIM_CHANNEL 1) Start the timer 2 capture function; 4.6 In the
HAL_TIM_IC_CaptureCallback callback function, obtain the CNT value , calculate the frequency of PWM!
Configure the pins as follows:
Then configure its prescaler value:
If you want to configure the prescaler value so that its frequency is equal to 1MHZ, you must first know what the frequency of TIMER2 is.
In the data sheet of STM32G431 we can find:
It can be seen that TIMER2 is a timer mounted under the APB1 bus.
Then in CUBE's clock tree we can see:
For the timers under the APB1 bus, their frequencies are all 80MHZ.
So we set the prescaler value to 79, which can be divided into 1MHZ, which is one microsecond.
Counter Period: It is a timing period. This determines how long CNT will increase until it overflows and then automatically returns to zero.
So we try to set this period as large as possible to prevent CNT from automatically overflowing and returning to zero. Since it is 32-bit, its maximum value is 0xffffffff, so it can be configured as 0xffffffff.
Finally, generate the code (since TIMER2 is mounted on the bus, there is no need to initialize its clock, because it has been initialized before)
First enable the pwm input capture interrupt in the main function:
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
Write an interrupt callback function in the template project:
//PWM捕获中断的回调函数
u32 tim2_cnt1;
u32 f40=0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
//获取CNT的值
tim2_cnt1=__HAL_TIM_GetCounter(&htim2);
__HAL_TIM_SetCounter(&htim2,0);//获取cnt值之后就把他清零
f40=1000000/tim2_cnt1;//周期的倒数就是频率
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
Display the F40 value on the LCD screen to observe the changing square wave frequency.
Dual PWM capture programming
To set up another PWM capture
, you need to set up PB4:
the basic settings are the same. The only thing you need to pay attention to is that the Counter Period of PB4 is 16 bits, and the maximum number is 65535, which is 0xffff. This should not be the same as the one above. If it is the same, the setting is wrong. There is nothing else to pay attention to. Just generate the code directly.
Then perform code migration and transplant the code of the template project to the programming project.
After adding the initialization function of timer3 in the main function and turning on the pwm input capture interrupt of timer3channel1,
rewrite the callback function:
//PWM捕获中断的回调函数
u32 tim2_cnt1,tim3_cnt1;
u32 f40=0,f30;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
//获取CNT的值
if(htim==&htim2)
{
tim2_cnt1=__HAL_TIM_GetCounter(&htim2);
__HAL_TIM_SetCounter(&htim2,0);//获取cnt值之后就把他清零
f40=1000000/tim2_cnt1;//周期的倒数就是频率
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
if(htim==&htim3)
{
tim3_cnt1=__HAL_TIM_GetCounter(&htim3);
__HAL_TIM_SetCounter(&htim3,0);//获取cnt值之后就把他清零
f30=1000000/tim3_cnt1;//周期的倒数就是频率
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
}
Measure the frequency and duty cycle of a single PWM
The measurement frequency is actually the measurement period.
Measuring the duty cycle is to measure how long the high level lasts in the entire cycle.
Programming ideas:
- The first rising edge interrupt starts timing (clearing the counter) and changes to a falling edge interrupt.
- Falling edge interrupt, obtain the CNT value T1 of the counter, and change it to rising edge interrupt.
- The second rising edge interrupt obtains the counter's CNT value T2, and the PWM frequency can be obtained through T2. The duty cycle of PWM can be obtained through T1/T2
So after a set of operations, we actually get two times, one is the high level time, and the other is the entire cycle time.
This is enough for us to calculate the duty cycle.
So the difficulty of programming now is how to change the polarity of the interrupt:
you can see the following in his tim initialization function::
void MX_TIM3_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {
0};
TIM_IC_InitTypeDef sConfigIC = {
0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 79;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 65535;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_IC_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
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();
}
}
A structure that initializes tim is defined: TIM_IC_InitTypeDef sConfigIC = {0};
click on the definition of this structure to see:
typedef struct
{
uint32_t ICPolarity; /*!< Specifies the active edge of the input signal.
This parameter can be a value of @ref TIM_Input_Capture_Polarity */
uint32_t ICSelection; /*!< Specifies the input.
This parameter can be a value of @ref TIM_Input_Capture_Selection */
uint32_t ICPrescaler; /*!< Specifies the Input Capture Prescaler.
This parameter can be a value of @ref TIM_Input_Capture_Prescaler */
uint32_t ICFilter; /*!< Specifies the input capture filter.
This parameter can be a number between Min_Data = 0x0 and Max_Data = 0xF */
} TIM_IC_InitTypeDef;
The first one in this structure is the polarity of the interrupt, the second one is the input channel, and the last two are frequency division and filtering respectively.
After the initialization function, a function is used HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig)
to reference this structure, and then tim is configured. So let's look at the definition of this function:
HAL_StatusTypeDef HAL_TIM_IC_ConfigChannel(TIM_HandleTypeDef *htim, TIM_IC_InitTypeDef *sConfig, uint32_t Channel)
{
/* Check the parameters */
assert_param(IS_TIM_CC1_INSTANCE(htim->Instance));
assert_param(IS_TIM_IC_POLARITY(sConfig->ICPolarity));
assert_param(IS_TIM_IC_SELECTION(sConfig->ICSelection));
assert_param(IS_TIM_IC_PRESCALER(sConfig->ICPrescaler));
assert_param(IS_TIM_IC_FILTER(sConfig->ICFilter));
/* Process Locked */
__HAL_LOCK(htim);
if (Channel == TIM_CHANNEL_1)
{
/* TI1 Configuration */
TIM_TI1_SetConfig(htim->Instance,
sConfig->ICPolarity,
sConfig->ICSelection,
sConfig->ICFilter);
/* Reset the IC1PSC Bits */
htim->Instance->CCMR1 &= ~TIM_CCMR1_IC1PSC;
/* Set the IC1PSC value */
htim->Instance->CCMR1 |= sConfig->ICPrescaler;
}
else if (Channel == TIM_CHANNEL_2)
{
/* TI2 Configuration */
assert_param(IS_TIM_CC2_INSTANCE(htim->Instance));
TIM_TI2_SetConfig(htim->Instance,
sConfig->ICPolarity,
sConfig->ICSelection,
sConfig->ICFilter);
/* Reset the IC2PSC Bits */
htim->Instance->CCMR1 &= ~TIM_CCMR1_IC2PSC;
/* Set the IC2PSC value */
htim->Instance->CCMR1 |= (sConfig->ICPrescaler << 8U);
}
else if (Channel == TIM_CHANNEL_3)
{
/* TI3 Configuration */
assert_param(IS_TIM_CC3_INSTANCE(htim->Instance));
TIM_TI3_SetConfig(htim->Instance,
sConfig->ICPolarity,
sConfig->ICSelection,
sConfig->ICFilter);
/* Reset the IC3PSC Bits */
htim->Instance->CCMR2 &= ~TIM_CCMR2_IC3PSC;
/* Set the IC3PSC value */
htim->Instance->CCMR2 |= sConfig->ICPrescaler;
}
else
{
/* TI4 Configuration */
assert_param(IS_TIM_CC4_INSTANCE(htim->Instance));
TIM_TI4_SetConfig(htim->Instance,
sConfig->ICPolarity,
sConfig->ICSelection,
sConfig->ICFilter);
/* Reset the IC4PSC Bits */
htim->Instance->CCMR2 &= ~TIM_CCMR2_IC4PSC;
/* Set the IC4PSC value */
htim->Instance->CCMR2 |= (sConfig->ICPrescaler << 8U);
}
__HAL_UNLOCK(htim);
return HAL_OK;
}
It is written that if it is channel 1, execute:
if (Channel == TIM_CHANNEL_1)
{
/* TI1 Configuration */
TIM_TI1_SetConfig(htim->Instance,
sConfig->ICPolarity,
sConfig->ICSelection,
sConfig->ICFilter);
/* Reset the IC1PSC Bits */
htim->Instance->CCMR1 &= ~TIM_CCMR1_IC1PSC;
/* Set the IC1PSC value */
htim->Instance->CCMR1 |= sConfig->ICPrescaler;
}
If it is channel 2 or 3, execute the following program.
Let's just look at channel 1: the function
is called here TIM_TI1_SetConfig
and sConfigIC
the various parameters of the structure are passed in.
So let’s take a look at TIM_TI1_SetConfig
how the function operates inside, and then pass sConfigIC
in the parameters of the structure:
void TIM_TI1_SetConfig(TIM_TypeDef *TIMx, uint32_t TIM_ICPolarity, uint32_t TIM_ICSelection,
uint32_t TIM_ICFilter)
{
uint32_t tmpccmr1;
uint32_t tmpccer;
/* Disable the Channel 1: Reset the CC1E Bit */
TIMx->CCER &= ~TIM_CCER_CC1E;
tmpccmr1 = TIMx->CCMR1;
tmpccer = TIMx->CCER;
/* Select the Input */
if (IS_TIM_CC2_INSTANCE(TIMx) != RESET)
{
tmpccmr1 &= ~TIM_CCMR1_CC1S;
tmpccmr1 |= TIM_ICSelection;
}
else
{
tmpccmr1 |= TIM_CCMR1_CC1S_0;
}
/* Set the filter */
tmpccmr1 &= ~TIM_CCMR1_IC1F;
tmpccmr1 |= ((TIM_ICFilter << 4U) & TIM_CCMR1_IC1F);
/* Select the Polarity and set the CC1E Bit */
tmpccer &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP);
tmpccer |= (TIM_ICPolarity & (TIM_CCER_CC1P | TIM_CCER_CC1NP));
/* Write to TIMx CCMR1 and CCER registers */
TIMx->CCMR1 = tmpccmr1;
TIMx->CCER = tmpccer;
}
You can see that various registers are operated in this. In the following lines, the bits of the interrupt polarity register are configured:
tmpccer |= (TIM_ICPolarity & (TIM_CCER_CC1P | TIM_CCER_CC1NP));
TIMx->CCER = tmpccer;
TIM_ICPolarity
Just interrupt the polarity. This sentence tmpccer
operates on this variable, and then finally tmpccer
assigns it to TIMx->CCER
this register.
So we only TIMx->CCER
need to operate this register to operate the interrupt polarity.
Click to see TIMx->CCER
the instructions:
__IO uint32_t CCER; /*!< TIM capture/compare enable register, Address offset: 0x20 */
You can see that the official comment is that CCER is the input capture enable register.
We consulted the manual and
saw that CCER is a sixteen-bit register (although STM32 is 32-bit, sometimes some registers are also set to 16-bit in order to save memory).
Which one do we need to configure?
The manual states that bit1, which is the penultimate bit, configures the interrupt polarity (Polarity).
What is said here is that if CC1NP is 0, it is PWM input mode. Then in input mode, if CC1P is 0, it is a rising edge interrupt. If CC1P is 1, it is a falling edge interrupt.
So we only need to change the CC1P bit (because we are using the PWM input mode, CC1NP is already initialized to 0 during initialization).
Start programming:
TIM2 in the callback function can be written like this:
if(htim==&htim2)
{
if(tim2_state==0)//第一个上升沿产生,开始计时
{
__HAL_TIM_SetCounter(&htim2,0);//把CNT清零
TIM2->CCER|=0x02; //下降沿中断,就是要把CC1P置为1
tim2_state=1;//等待下降沿产生
}
else if(tim2_state==1)//第一个下降沿产生,获取T1的值,就是高电平的时长
{
tim2_cnt1=__HAL_TIM_GetCounter(&htim2);//获取CNT的值
TIM2->CCER &=~0x02;
tim2_state=2;
}
else if(tim2_state==2)//第二个上升沿产生,获取T2的值,就是整个周期
{
tim2_cnt2=__HAL_TIM_GetCounter(&htim2);//获取CNT的值
f40=1000000/tim2_cnt2;//周期的倒数就是频率
d40=tim2_cnt1*100.0f/tim2_cnt2;//计算占空比
tim2_state=0;
}
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
Measure the frequency and duty cycle of two PWM channels
Just apply the above code to TIMER3 and write it again:
//PWM捕获中断的回调函数
u32 tim2_cnt1,tim3_cnt1,tim2_cnt2,tim3_cnt2;
u32 f40=0,f30=0;
float d40=0,d30=0;
u8 tim2_state=0,tim3_state=0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
//获取CNT的值
if(htim==&htim2)
{
if(tim2_state==0)//第一个上升沿产生,开始计时
{
__HAL_TIM_SetCounter(&htim2,0);//把CNT清零
TIM2->CCER|=0x02; //下降沿中断,就是要把CC1P置为1
tim2_state=1;//等待下降沿产生
}
else if(tim2_state==1)//第一个下降沿产生,获取T1的值,就是高电平的时长
{
tim2_cnt1=__HAL_TIM_GetCounter(&htim2);//获取CNT的值
TIM2->CCER &=~0x02;
tim2_state=2;
}
else if(tim2_state==2)//第二个上升沿产生,获取T2的值,就是整个周期
{
tim2_cnt2=__HAL_TIM_GetCounter(&htim2);//获取CNT的值
f40=1000000/tim2_cnt2;//周期的倒数就是频率
d40=tim2_cnt1*100.0f/tim2_cnt2;
tim2_state=0;
}
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
if(htim==&htim3)
{
if(tim3_state==0)//第一个上升沿产生,开始计时
{
__HAL_TIM_SetCounter(&htim3,0);//把CNT清零
TIM3->CCER|=0x02; //下降沿中断,就是要把CC1P置为1
tim3_state=1;//等待下降沿产生
}
else if(tim3_state==1)//第一个下降沿产生,获取T1的值,就是高电平的时长
{
tim3_cnt1=__HAL_TIM_GetCounter(&htim3);//获取CNT的值
TIM3->CCER &=~0x02;
tim3_state=2;
}
else if(tim3_state==2)//第二个上升沿产生,获取T2的值,就是整个周期
{
tim3_cnt2=__HAL_TIM_GetCounter(&htim3);//获取CNT的值
f30=1000000/tim3_cnt2;//周期的倒数就是频率
d30=tim3_cnt1*100.0f/tim3_cnt2;
tim3_state=0;
}
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
}