Skip to content

Timer Encoder

Elisson Araújo edited this page Nov 22, 2024 · 1 revision

Timer Encoder

Quadrature Encoder Interface library location:

└── lib
    └── MotionControl
        └── PID_Controller
            └── Encoder
                └── QEI
                    ├── QEI.cpp
                    └── QEI.h

The encoder interface using timers is done by setting up the registers as specified by the resources provided by STMicroelectronics, the application note AN4013 which describes how to use the encoder interface and the reference manual RM0410 which specifies the registers.

Below is the steps to configure a timer in encoder mode from the AN4013 application note:

TIM configuration in encoder mode

1. Select and configure the timer input:
    • Input selection:
        – TI1 connected to TI1FP1 CC1S='01' in TIMx_CCMR1 register;
        – TI2 connected to TI2FP2 CC2S='01' in TIMx_CCMR1 register.
    • Input polarity:
        – CC1P='0' and CC1NP='0'(CCER register, TI1FP1 non-inverted, TI1FP1=TI1);
        – CC2P='0' and CC2NP='0'(CCER register, TI1FP2 non-inverted, TI1FP2= TI2).
2. Select the encoder mode:
    • Encoder mode1 (resolution X2 on TI2): SMS=’001’ in TIMx_SMCR register;
    • Encoder mode2 (resolution X2 on TI1): SMS=’010' in TIMx_SMCR register;
    • Encoder mode3 (resolution X4 on TI1 and TI2): SMS=’011’ in TIMx_SMCR register.
3. Enable the timer counter:
    • Set the counter enable bit, CEN='1' in TIMx_CR1 register.

For an explanation on how it is done on our code, below is a walkthrough of the encoder interface initialisation code for the wheel motor number 1, as present on the QEI.cpp code:


void QEI::init_enc_timer1() // PE_9 PA_9 - Encoder of M1

Declaration of the initialisation method, it doesn't have parameters but there is some information to note on this line, first the "timer1" specification on the method name indicates that we are using the Timer1 for the motor M1, second the PE_9 and PA_9 ports specified in the code line comment which indicates the ports being used.

A STM32xx Series device has various built in timers, and for the encoder interface it needs to be selected a General-purpose or advanced timer, so the choice of which timer to use was based on this requirement and if the timer itself was not being used and the ports were available.

The encoder has 2 signals namely PhaseA and PhaseB. The Phase A and B output signals are connected to the encoder interface to compute the frequency and then determine the velocity and the position, for the M1 encoder, since we're using Timer1, PhaseA and PhaseB needs to be connected to Timer1 channel 1 (TI1) and channel 2 (TI2) respectively, the timer's counter is incremented or decremented for each transition on both inputs TI1 and TI2 and the direction of the motor depends on whether Phase A leads Phase B, or Phase B leads Phase A.

PE_9 and PA_9 were the ports selected which have connection to Timer1 TI1 and TI2 respectively, the resource used to look to which ports a timer's channel was connected to, was the mbedOS' PeripheralPins.c file for the F767ZI.


// Enable registers manipulation for Timer1, GPIOA and GPIOE
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

The RCC->APB2ENR syntax refers to acessing the Reset and clock control (RCC) register named APB2ENR. the RCC_AHBxENR and RCC_APBxENRy are peripheral clock enable registers, each peripheral clock can be enabled by the xxxxEN bit of the RCC_AHBxENR or RCC_APBxENRy registers. When the peripheral clock is not active, the peripheral registers read or write accesses are not supported.

For the RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; line of code, we are assigning to the RCC register named APB2ENR the value from a bitwise or operation between the register APB2ENR value and the RCC_APB2ENR_TIM1EN macro.

RCC_APB2ENR_TIM1EN is a macro defined in the mbed library, it has the bit value of 0b1 in the bit position for the TIM1EN on the APB2ENR register. The picture below show the memory positions for the APB2ENR register, where the TIM1EN is located at bit position 0, the RCC_APB2ENR_TIM1EN macro has value:

RCC_APB2ENR_TIM1EN = 0b 0000 0000 0000 0000 0000 0000 0000 0001

RCC_APB2ENR

The following lines enable the General-purpose I/Os (GPIO) E and GPIOA peripheral clock also using mbed macros, GPIOE and GPIOA are enable since the ports selected are PE and PA. and as per the reference manual specification, to enable the clocks the bit value at the selected register memory position needs to be 0b1.


// // GPIO Init for E ports 9
GPIOE->MODER |= GPIO_MODER_MODER9_1;        // Alternate function mode MODERy: 10
GPIOE->OTYPER |= GPIO_OTYPER_OT_9;          // Output open-drain OTy: 1
GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9_0; // Medium Speed OSPEEDRy: 01
// PUPDR - No pull-up, pull-down PUPDRy: 00
GPIOE->AFR[1] |= GPIO_AFRH_AFRH1_0;         // Alternate function 1 AFRy: 0001

// // GPIO Init for A ports 9
GPIOA->MODER |= GPIO_MODER_MODER9_1;        // Alternate function mode MODERy: 10
GPIOA->OTYPER |= GPIO_OTYPER_OT_9;          // Output open-drain OTy: 1
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9_0; // Medium Speed OSPEEDRy: 01
// PUPDR - No pull-up, pull-down PUPDRy: 00
GPIOA->AFR[1] |= GPIO_AFRH_AFRH1_0;         // Alternate function 1 AFRy: 0001

This code initializes the ports PE_9 and PA_9, now we are acessing GPIO registers, for the following configuration:

MODER:
Port mode register.
These bits are written by software to configure the I/O mode.
    00: Input mode (reset state)
    01: General purpose output mode 
    10: Alternate function mode
    11: Analog mode
OTYPER
Port output type register.
These bits are written by software to configure the I/O output type.
    0: Output push-pull (reset state) 
    1: Output open-drain
OSPEEDR
port output speed register
These bits are written by software to configure the I/O output speed.
    00: Low speed
    01: Medium speed 
    10: High speed
    11: Very high speed
PUPDR
pull-up/pull-down register
These bits are written by software to configure the I/O pull-up or pull-down
    00: No pull-up, pull-down 
    01: Pull-up
    10: Pull-down
    11: Reserved
AFR
alternate function register
Every port has a 4 bit memory giving each port 16 alternate function possibilities,
each port character group (GPIOA, GPIOB, GPIOC...) has 16 ports and a since each
register is a 32 bit value the alternate function register is split in alternate
low (GPIOx->AFR[0]) for ports 0-7, and alternate high (GPIOx->AFR[1]) for ports 8-15.

All the information above and more can be found in the reference manual RM0410.

The bit values written for each register are:

MODER = 0b10 (Alternate Function Mode)

OTYPER = 0b1 (Output open-drain)

OSPEEDR = 0b01 (Medium speed)

PUPDR = 0b00 (No pull-up, pull-down)

AFR = Port function for timer channel

For the Alternate function bit value, a port can have multiple function, and here is selected the function which corresponds to the channel of the timer selected, we used the PeripheralPins.c file for the F767ZI to see the alternate function value wanted for each pin.

For writting the register bit values a mbedOS macro was also used, however note that some of these register values require a multiple bit value which may not be clear on how to use the macro based on previous block of code explanation. The macro when referring to multiple bit values, has an index at the end for the value bit position, example:

AFR_HIGH

The image above is the memory bit position for the AFR High (GPIOx->AFR[1]) register, if we want for example to write for the pin 9 the alternate function 6 (AFR9 = 0b0110) using the mbed macros:

GPIO_AFRH_AFRH1_0 = 0b 0000 0000 0000 0000 0000 0000 0001 0000

GPIO_AFRH_AFRH1_1 = 0b 0000 0000 0000 0000 0000 0000 0010 0000

GPIO_AFRH_AFRH1_2 = 0b 0000 0000 0000 0000 0000 0000 0100 0000

GPIO_AFRH_AFRH1_3 = 0b 0000 0000 0000 0000 0000 0000 1000 0000

Note that AFRH1 == AFR9 (AFRH0 == AFR8 ...), H for high:

GPIO_AFRH_AFRH0_0 = 0b 0000 0000 0000 0000 0000 0000 0000 0001

GPIO_AFRH_AFRH2_0 = 0b 0000 0000 0000 0000 0000 0001 0000 0000

So, if we want to write for the pin 9 the alternate function 6:

GPIOx->AFR[1] |= GPIO_AFRH_AFRH1_1 | GPIO_AFRH_AFRH1_2


// in Tigers we trust
TIM1->CR1 = 0;
TIM1->CR2 = 0;
TIM1->PSC = 0;
TIM1->ARR = 0xFFFF;
TIM1->RCR = 0;
// IC1 mapped to TI1, IC2 mapped to TI2, filter = 0x0F on both, no prescaler
TIM1->CCMR1 = TIM_CCMR1_CC1S_0 | (0x0F << 4) | TIM_CCMR1_CC2S_0 | (0x0F << 12);
TIM1->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E; // enable input capture, rising polarity
TIM1->DIER = 0;
TIM1->SMCR = 3; // encoder mode 3, count all flanks
TIM1->CR1 = TIM_CR1_CEN;

In this block of code we are writting the timer registers.

For better explanation lets will split in two block, first the registers written following the steps to configure a timer in encoder mode, from the AN4013 application note:

1. Select and configure the timer input:
    • Input selection:
        – TI1 connected to TI1FP1 CC1S='01' in TIMx_CCMR1 register;
        – TI2 connected to TI2FP2 CC2S='01' in TIMx_CCMR1 register.

TIM1->CCMR1 = TIM_CCMR1_CC1S_0 | (0x0F << 4) | TIM_CCMR1_CC2S_0 | (0x0F << 12);
    • Input polarity:
        – CC1P='0' and CC1NP='0'(CCER register, TI1FP1 non-inverted, TI1FP1=TI1);
        – CC2P='0' and CC2NP='0'(CCER register, TI1FP2 non-inverted, TI1FP2= TI2).
2. Select the encoder mode:
    • Encoder mode1 (resolution X2 on TI2): SMS=’001’ in TIMx_SMCR register;
    • Encoder mode2 (resolution X2 on TI1): SMS=’010' in TIMx_SMCR register;
    • Encoder mode3 (resolution X4 on TI1 and TI2): SMS=’011’ in TIMx_SMCR register.

TIM1->SMCR = 3; // encoder mode 3, count all flanks
3. Enable the timer counter:
    • Set the counter enable bit, CEN='1' in TIMx_CR1 register.

TIM1->CR1 = TIM_CR1_CEN;

For Vss-Embedded

https://www.st.com/resource/en/reference_manual/dm00135183-stm32f446xx-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf
https://github.com/ARMmbed/mbed-os/blob/master/targets/TARGET_STM/TARGET_STM32F4/TARGET_STM32F446xE/TARGET_NUCLEO_F446RE/PeripheralPins.c
Clone this wiki locally