Location>code7788 >text

07 Input Capture and Encoder Interface

Popularity:332 ℃/2024-08-02 15:05:32

preamble

Timers and output comparisons were introduced earlier, and this section focuses on input capture to measure input frequency and PWM duty cycle, followed by an introduction to the encoder interface.

I. Input Capture

1.What is Input Capture

When the input pin has a specified level jump, the value in counter CNT is saved in CCR, this is called input capture.

2. Input Capture Measurement Frequency

We can measure the frequency by getting the value of the input, and here are two ways to calculate it:

2.1 Frequency measurement

For a gate time T, the rising edge is counted to get the count value N. The frequency is then = N/T.

2.2 Perimetry

At a standard frequency fc, the rising edge is counted to get the count value N. The frequency is then: fc / N.

3. Internal structure of input capture

The next step is to understand the internal structure of the input capture. Only if we understand the internal structure, it will be easy for us to write the code, the following figure shows the internal structure of the input capture:

img

As you can see, the level is input from the GPIO port, then through the input capture unit, the trigger level is detected, and if it is the set level it saves the value in the CNT into the CCR, and then the value in the CNT is cleared to 0 by configuring the slave mode.

Once we understand the structure we probably know how to configure input capture.

First configure the external inputs, i.e. the GPIO ports, then configure the timer as input capture is an operating mode of the timer, then configure the slave mode and use Reset from the slave mode to clear the counter CNT, and that's how you configure the input capture.

We can calculate the frequency by reading the value in the CCR.

4. Software realization

After the previous understanding of the hardware structure, we can use the software to realize one by one, first of all to open the clock, the clock is the most important part of the stm32, if you do not open the clock, even if the configuration is no way to run.

4.1 Turning on the clock

Here we can get from the above hardware analysis, in fact, is only to turn on two clocks, one is the clock of the GPIO, the other is the clock of the TIM, the input capture of this function is in the TIM, so open the clock of the TIM, the input capture and the output comparison is also open.

So the code here is simpler, I am using TIM3 as the timer for input capture here, so the code to turn it on is as follows:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

4.2 Configuring GPIO Ports

The GPIO port is used as an input port for external signals, so it is important to set the input mode for it, so what is the best mode to choose? Here you can look up the manual:

img

You can see that the GPIO is configured as a floating input, but in fact, it can also be configured as a pull-up input, floating input is a good capture of the level change, but no pull-up or pull-down resulting in not very stable, here because it is a level jump, even if you use the pull-up, when the input is a high level will be jumped to a low level, and then to a high level, so here you can choose to pull-up or pull-down! So here you can choose either pull-up or pull-down.

If you're not sure exactly what this jump change is, you can choose to float the input and get a little better that way.

Here it's just a matter of configuring the GPIO ports as normal:

GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // Pull-up inputs are used here
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6; // Use pull-up input here.
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // Use pull-up input here.
GPIO_Init(GPIOA, &GPIO_InitStruct).

Here is channel 1 of TIM3 being used.

4.3 Configuring TIM

Now you need to configure the TIM, in the configuration of the GPIO that is configured TIM3 channel 1, so this has to be configured TIM3.

If you choose TIM2, then the pins corresponding to TIM2 are configured above.

The configuration method here is the same as the timer one before:

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};

TIM_InternalClockConfig(TIM3); // use internal clock

TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // no division
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; // count upwards
TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1; // note here
TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1; // frequency
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // Repetition counter, advanced timer only function
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct).

The auto-reload register here is filled with a maximum value because the input capture here is counting the change level with CNT, and when the value in CNT is the same as the value in the auto-reload register ARR, it makes the counter clear 0 and then sends a signal, and if there is still a change in level here after it gets to the value in the ARR, is it not rounded off.

So here to prevent this problem, we set the value in the ARR to the maximum value, to prevent the occurrence of the value to the automatic reloading of the value when there are still changes, so use a maximum value to prevent overflow.

4.4 Configuring Input Capture

This is also configured using a structure of type:

typedef struct
{

  uint16_t TIM_Channel; /*! < Specifies the TIM channel.
  The value of this parameter can be @ref TIM_Channel */

  uint16_t TIM_ICPolarity; /*! < Specifies the active edge of the input signal.
  The value of this parameter can be @ref TIM_Input_Capture_Polarity */

  uint16_t TIM_ICSelection; /*! < Specifies the input.
  The value of this parameter can be @ref TIM_Input_Capture_Selection */

  uint16_t TIM_ICPrescaler; /*! < Specifies the input capture prescaler.
  The value of this parameter can be @ref TIM_Input_Capture_Prescaler */

  uint16_t TIM_ICFilter; /*! < Specifies the input capture filter.
  This parameter can be a number between 0x0 and 0xF */
} TIM_ICInitTypeDef.

first parameterTIM_ChannelIt's selecting the channel, and this is set by the pin mentioned before:

img

img

img

For example, if TIM3_CH1 is set in the previous section, then fill in hereTIM_Channel_1

second parameterTIM_ICPolarityThis parameter selects the effective signal, which can be selected fromTIM_ICPolarity_RisingHigh level trigger.TIM_ICPolarity_FallingLow Level Trigger.

Third parameterTIM_ICSelectionDesignated inputs with direct signal connectionsTIM_ICSelection_DirectTIintersectionTIM_ICSelection_IndirectTI. This needs to be taken to a chart:

img

As you can see from this, we have made a difference by selecting theTI1FP1It can be connected to two input channels, one is connected to input channel 1 and the other is connected to channel 2. Directly configured to input channel 1 is called direct mode, and if it is connected to input channel 2 it is called cross mode.

Fourth parameterTIM_ICPrescalerIt's the distributor.

The fifth parameterTIM_ICFilterThis parameter selects the resolution of the filter.

Once we understand the structure we can configure what's in the structure, here I'm going to use timer channel 1 and input channel 1 directly, so the mode is direct connection:

TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter = 0xF;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; // Effective level high
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; // direct connection
TIM_ICInit(TIM3, &TIM_ICInitStruct);

Here's another example, I'm still on timer channel 1, and the input channel has become channel 2, so this selects crossover mode with the following code:

TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter = 0xF;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; // Effective level high
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_IndirectTI; // crossover
TIM_ICInit(TIM3, &TIM_ICInitStruct);

This allows you to get the number of active levels by reading the CCR value in input channel 2.

We can use this to calculate the PWM frequency and PWM duty cycle, one input channel to get the PWM frequency and another channel to get the PWM duty cycle.

The method for two settings for one channel is as follows:

TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter = 0xF;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; // Effective level high
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; // direct connection
TIM_ICInit(TIM3, &TIM_ICInitStruct);

TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter = 0xF;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Falling; // Effective level low
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_IndirectTI; // crossover
TIM_ICInit(TIM3, &TIM_ICInitStruct);

A channel has a straight and a crossover respectively, and then one is active high and one is active low.

4.5 Configuring Slave Mode

We know from the previous section that we need to configure a slave mode to allow the slave mode to execute theResetThe operation is to clear the value in the CNT to zero, which can be achieved by using only two functions, the first of which is theTIM_SelectInputTrigger, this function is selecting the trigger source, in this diagram:

img

It can be seen that there are four signals after the edge detection circuit, which areTI1FP1TI2FP2TI3FP3TI4FP4, these signals can trigger the slave mode, we first use this timer after the input channel, and then through theTIM_SelectSlaveModefunction to set the slave mode.

For example, if we set the TIM3 timer above, and are using TIM3_CH1, then the setup function for this would be:

TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);

Again, let's say that here is CH2 of TIM2, that sets the function to:

TIM_SelectInputTrigger(TIM2, TIM_TS_TI2FP2);
TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);

Here's the signal.TIxFPxHas nothing to do with what the timer is, only the channel. stm32f103c8t6 can only be configured to capture 4 inputs, so here x is from 1 to 4.

4.6 Enabling Timers

Without going into detail on this one, it's just a matter of usingTIM_Cmd()function can be enabled, the code is as follows:

TIM_Cmd(TIMx, ENABLE);

5. Input Capture Measurement Frequency and Duty Cycle

The implementation of the code is described above, and here is how to get the PWM frequency and PWM duty cycle if you use the input capture.

The code implementation of this is relatively simple, is set to input capture mode, and then write two conversion functions can be:

#include ""

void IC_Init()
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
    TIM_ICInitTypeDef TIM_ICInitStruct = {0}; TIM_TimeBaseInitTypeDef TIM_ICInitStruct = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7; // TIM3_CH2 is used here.
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // TIM3_CH2 is used here.
    GPIO_Init(GPIOA, &GPIO_InitStruct).

    TIM_InternalClockConfig(TIM3).

    TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStruct.
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStruct.
    TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1; TIM_TimeBaseInitStruct.
    TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1; TIM_TimeBaseInitStruct.
    TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; TIM_TimeBaseInitStruct.
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct).

    TIM_ICInitStruct.TIM_Channel = TIM_Channel_2; // select channel 2 for direct mode
    TIM_ICInitStruct.TIM_ICFilter = 0xF; TIM_ICInitStruct.
    TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStruct.
    TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStruct.
    TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; // Direct connection mode
    TIM_ICInit(TIM3, &TIM_ICInitStruct).

    TIM_ICInitStruct.TIM_Channel = TIM_Channel_1; // cross mode to select channel 1
    TIM_ICInitStruct.TIM_ICFilter = 0xF; TIM_ICInitStruct.
    TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Falling; // Here you need to split the effective level with the above setting, otherwise there will be a problem that can't be run, I've tried it, it has to be separated, otherwise the test can't be done.
    TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStruct.
    TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_IndirectTI; // cross mode
    TIM_ICInit(TIM3, &TIM_ICInitStruct).

    TIM_SelectInputTrigger(TIM3, TIM_TS_TI2FP2); // cross mode; TIM_ICInit(TIM3, &TIM_ICInitStruct); // cross mode
    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);

    TIM_Cmd(TIM3, ENABLE);
}

uint32_t IC_Get_Freq()
{
    // Convert to frequency
    return 1000000 / (TIM_GetCapture2(TIM3) + 1); // Divide the frequency set by the input capture by the standard clock of 72MHz to get 1000000, then divide this frequency by the acquired N to get the frequency.
}

uint32_t IC_Get_Pulse()
{
    // Convert to duty cycle
    return ((TIM_GetCapture1(TIM3) + 1) * 100) / IC_Get_Freq(); // This algorithm was talked about at PWM Duty Cycle. I won't repeat it here.
}

This gives you the frequency and duty cycle of the PWM.

II. Encoder Interface

1. What is an encoder interface

The encoder interface receives signals from an incremental (quadrature) encoder and automatically controls the CNT self-increment or self-decrement based on the quadrature signal pulses generated by the encoder rotation.

Below is a diagram of a quadrature encoder:

img

As you can see, with this waveform of the encoder, the waveforms of items A and B are 90 degrees apart, so these two waveforms together can be divided into positive and negative.

2. Hardware structure of the encoder interface

We can implement the encoder using the input capture or interrupts we learned earlier, but with an encoder interface set up in the stm32f103c8t6, we can implement the encoder test directly using hardware.

The hardware structure is as follows:

img

As you can see it's very simple, just add a compiler interface configuration on top of the previous one.

The function used is:

void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);

This function will be able to set up the encoder interface, first set up the GPIO port, because we need two inputs for this encoder, so we need to set up two pins as input pins, and then configure the timer, configure the input capture after configuring as a timer, after that, we will use the above function to configure the encoder interface.

first parameterTIMxis the timer number.

second parameterTIM_EncoderModeIt's the usage mode, you can choose from the following ones:

paradigm account for
TIM_EncoderMode_TI1 Using TI1FP1 as an encoder setup
TIM_EncoderMode_TI2 Using the TI2FP2 as an encoder setup
TIM_EncoderMode_TI12 Using TI1FP1 and TI2FP2 as encoder settings

Third parameterTIM_IC1Polarityis the choice of whether TI1FP1 is a positive or negative term, and the fourth parameterTIM_IC2PolarityAs with the third parameter, it is the selection of TI2FP2.

When both are selected forward or reverse, it is the same as in the following figure of Token:

img

The counter increments itself when TI1 has a rising level and TI2 is still low, and it also increments itself when TI2 has a rising level and TI1 is high.

When one of TI1FP1 or TI2FP2 is an inverse term it becomes the following tensors:

img

In the forward direction when TI1 has a rising level and TI2 is still low, the counter is self-incrementing, but in the reverse direction it just becomes self-decrementing.

That is to say that when a positive self-increase is transferred to a negative it becomes a self-decrease.

3. Software realization

Once the hardware structure has been talked about, it is time to use code to make an implementation of one of the functions.

3.1 Clock on

Turning on the clock here will also only need to turn on the clock of the GPIO and the clock of the timer, because the encoder interface is also an additional function of the timer, so it is only necessary to turn on the clock of the GPIO and the clock of the timer, and do not need to turn on the additional clock.

3.2 GPIO Port Configuration

Here still need to configure the GPIO port, because it needs the input of the external encoder, the pin selected here is the same as the timer's input capture and output comparison, you need to check if the pin's multiplexing function is on the TIM timer's channel, if not then it can't be used.

Here, channel 1 and channel 2 of timer 3 are selected, and the corresponding pins are PA6 and PA7.

The configuration code is as follows:

GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);

3.3 Configuring Timers

Configuration method is also the same as above, open the TIM3 timer, and then configure a series of operations, but here we need to change, prescaler PSC here do not need to frequency, only need to fill in the 1-1 can be 1 is not frequency, here is the encoder interface hardware to carry out the operation can be, and then the automatic reloading registers ARR only need to configure the maximum can also do not need to be specified by the TIM use! Internal clock, this clock is also provided by the encoder interface.

TIM_TimeBaseTypeDef TIM_TimeBaseStruct = {0};
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_Period = 65536 - 1;
TIM_TimeBaseStruct.TIM_Prescaler = 1 - 1;
TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct);

3.4 Configuring Input Capture

It is important to note here that there are some things that do not need to be configured in this configuration, only two parameters need to be configured, and the ones that do need to be configured are the channelsTIM_Channeland filter resolutionTIM_ICFilterThen nothing else needs to be configured.

So here you need to use a function to assign a default value to the structure:

TIM_ICInitTypeDef TIM_ICInitStruct = {0};
TIM_ICStructInit(&TIM_ICInitStruct);

TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter = 0x0F;
TIM_Init(TIM3, &TIM_ICInitStruct);

TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;
TIM_ICInitStruct.TIM_ICFilter = 0x0F;
TIM_Init(TIM3, &TIM_ICInitStruct);

Here may be more curious why those are not configured, such as pre-scaler and effective level and reverse, this is because in the configuration of the encoder interface that has been configured, do not need to be configured, the encoder interface will capture a clock for this input, so there is no need to crossover, and then reverse is also, also that a function can be configured.

3.5 Configuring the encoder interface

This can be configured using the same function described above:

TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

Here I have it all set to not reverse, for convenience.

3.6 Enabling Timers

Finally enable the timer can be, enable the method introduced many times before, here will not pull out the code, the following complete code there.

3.7 Complete Code

I'll post the full code here for convenience:

void Encoder_Init()
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct = {0};
    TIM_ICInitTypeDef TIM_ICInitStruct = {0};
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStruct.TIM_Period = 65536 - 1;
    TIM_TimeBaseStruct.TIM_Prescaler = 1 - 1;
    TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct);
    
    TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStruct.TIM_ICFilter = 0x0F;
    TIM_ICInit(TIM3, &TIM_ICInitStruct);
    
    TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;
    TIM_ICInitStruct.TIM_ICFilter = 0x0F;
    TIM_ICInit(TIM3, &TIM_ICInitStruct);
    
    TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    
    TIM_Cmd(TIM3, ENABLE);
}

summarize

General-purpose timer operation is completed, later if you have the opportunity to give you an introduction to the advanced timer, advanced timer can operate three brushless motors, and so on the back of the time I do a drone will be used to the advanced timer.