Control Servo Motors with STM32 Microcontroller

Kunal Salvi
4 min readJun 23, 2020

--

Bare metal STM32 driver to control hobby servo motors.

I’m sure by now you’ve used a hobby servo such as SG90 or MG995 with an Arduino compatible board in at least one of your projects. Adding a library and calling a few functions gets the job done. But you have no idea how the hardware is configured to produce that exact signal that turns the shaft to the desired angle. So in this post, I’ll go through the steps that configure the hardware timer of the STM32F103C8T6 microcontroller and generate the appropriate signal.

Servo Motor Theory:

Fig 1: Servo PWM Control Signal

Let’s simplify the operation of servos. The PWM pin takes a 50 Hz PWM signal with duty-cycle ranging from 5% to 10% i.e., for a 20 ms signal 0 degrees is 1.5 ms, -90 degrees is 1 ms and +90 degrees in 2 ms. There is a complex feedback system that achieves the proper mechanical rotation, but it is left to the curiosity of the reader.

Let’s Code:

So now we know what to achieve: A 50 Hz PWM signal with duty-cycle ranging from 5% to 10%.

Note: Here the angles are mapped like this: 0 deg = 1 ms, 90 deg = 1.5 ms and 180 deg = 2 ms.

Let's create a PWM signal on channel 1 of timer TIM1. Registers required to be configured are:

  1. RCC_APB2ENR
  2. GPIOA_CRH
  3. TIM1_PSC
  4. TIM1_ARR
  5. TIM1_CCMR1
  6. TIM1_BDTR
  7. TIM1_CCER
  8. TIM1_CR1
  9. TIM1_EGR
  10. TIM1_CR1

Starting with RCC_APB2ENR register. As we are using timer TIM1 and port GPIOA in alternate function mode, bits 0, 2, and 11 need to be set. These bits are already defined in the library stm32f10x.h. So we set the RCC_APB2ENR_IOPAEN, RCC_APB2ENR_AFIOEN, and RCC_APB2ENR_TIM1EN bits. Now to we need to configure the pin and the port on which the PWM signal will be generated. To achieve this, the bits 3:0 of GPIOA_CRH register should be set with 0b1101 i.e., GPIO_CRH_MODE8_1, GPIO_CRH_MODE8_0 and, GPIO_CRH_CNF8_1. TIM1 needs to be configured to produce the PWM signal. Registers TIM1_PSC and TIM_ARR determine the frequency and the timer overflow value. Calculations are shown below:

Fig 2: Calculations for Frequency
Fig 3: Calculation for duty-cycle

After calculations, we get, TIM1_PSC = 73 and TIM1_ARR = 20000. In TIM_CCMR1 register, set TIM_CCMR1_OC1M_1 and TIM_CCMR1_OC1M_2 to configure the timer in PWM1 mode and activate preload register by setting TIM_CCMR1_OC1PE. Set Main Output Enable (MOE bit) by setting TIM_BDTR_MOE in the TIM_BDTR register. Enable Capture-Compare register by setting TIM_CCER_CC1E bit in TIM_CCER register. Set Auto-reload preload enable bit by setting TIM_CR1_ARPE in the TIM1_CR1 register and Update generation bit by setting TIM_EGR_UG in the TIM1_EGR register. Finally, enable the timer by setting TIM_CR1_CEN in the TIM1_CR1 register.

Code Snippet :

Servo Init Function:

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN |  RCC_APB2ENR_TIM1EN;
GPIOA->CRH |= GPIO_CRH_CNF8_1 | GPIO_CRH_MODE8_1 | GPIO_CRH_MODE8_0;
TIM1->PSC = 71;
TIM1->ARR = 20000;
TIM1->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1PE;
TIM1->BDTR |= TIM_BDTR_MOE;
TIM1->CCER |= TIM_CCER_CC1E;
TIM1->CR1 |= TIM_CR1_ARPE;
TIM1->EGR |= TIM_EGR_UG;
TIM1->CR1 |= TIM_CR1_CEN;

Servo Update Function:

TIM1->CCR1 = 5.556*angle + 1000;

Adding Modularity to the Function:

To use any of the available hardware timers, TIM_TypeDef can be used.

void Servo_1_Init(TIM_TypeDef *timer){}

Conclusion: Controlling servos is easy and coding their drivers in Embedded C gives the programmer a closer look at the hardware which the code interacts with. As an update of CCRx registers does not require CPU interference, duty-cycle updated quickly.

For more such code snippets and libraries, take a look at my GitHub.

--

--

Kunal Salvi
Kunal Salvi

Written by Kunal Salvi

Embedded Systems Engineer | Roboticist | Researcher | Innovator

Responses (1)