# How to control a servo motor using STM32F103C8T6 timers?

I am trying to control the position of a servo motor using an STM32F103C8T6 MCU (blue pill). The problem I am facing is that the servo does not move to different positions when given different values (in a infinite loop.)

I am using bare-metal programming so that I can understand the basic at hardware level.

Here is my code:

// ------------ THIS Code WORKS ------------------------------------------

void delayMs(void);
void systick_init(void);
void delayMillis(int);
void writeMicroseconds(int);

int main(void){

RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; //enable clock for TIM4
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN; //enable clock for PORTB and enable AFIO

//cofigure PB6 as pwm output CNF6 = 10 (alternate fnc output push pull) MODE6 = 11 (output 50MHz)
GPIOB->CRL = 0x4B444444;//(1<<25)|(1<<24)|(1<<27)|(0<<26);

//configure pwm as active high and enable the signal
TIM4->CCER |= 1;

//timer config for pwm mode
//CH1 > OC1M = 110 & ocipe = 1
TIM4->CCMR1 |= (1<<6)|(1<<5)|(1<<3);

//Timer config for PSC and ARR
// clk = 72000000 : need 50Hz => 50 = 72000000/x/20000; => x = 72
TIM4->PSC = 72 - 1;
TIM4->ARR = 20000;

//enable all registers by UG bit
TIM4->EGR |= 1;
TIM4->CR1 |= 1; //enable timer CEN = 1;

writeMicroseconds(1500); // SERVO gets set to 90° perfectly

}
void writeMicroseconds(int32_t pwm){
TIM4->CCR1 = pwm;
}

//systick timer config
void systick_init(void){
SysTick->CTRL = 0;
SysTick->VAL = 0;

SysTick->CTRL = 5;
}

//single millisecond control config
void delayMs(void){
SysTick->VAL = 0;
while((SysTick->CTRL & 0x00010000) == 0);
}

//given millisecond delay fnc
void delayMillis(int t){
for(;t>0;t--){
delayMs();
}
}

// ------------ THIS Code DOES NOT ------------------------------------------

void delayMs(void);
void systick_init(void);
void delayMillis(int);
void writeMicroseconds(int);

int main(void){

RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; //enable clock for TIM4
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN; //enable clock for PORTB and enable AFIO

//cofigure PB6 as pwm output CNF6 = 10 (alternate fnc output push pull) MODE6 = 11 (output 50MHz)
GPIOB->CRL = 0x4B444444;//(1<<25)|(1<<24)|(1<<27)|(0<<26);

//configure pwm as active high and enable the signal
TIM4->CCER |= 1;

//timer config for pwm mode
//CH1 > OC1M = 110 & ocipe = 1
TIM4->CCMR1 |= (1<<6)|(1<<5)|(1<<3);

//Timer config for PSC and ARR
// clk = 72000000 : need 50Hz => 50 = 72000000/x/20000; => x = 72
TIM4->PSC = 72 - 1;
TIM4->ARR = 20000;

//enable all registers by UG bit
TIM4->EGR |= 1;
TIM4->CR1 |= 1; //enable timer CEN = 1;

while(1){ // In this loop the servo gets fixed at one position and then doesn't move at all! even though I Change CCR1 value.
writeMicroseconds(1500);
delayMillis(500);
writeMicroseconds(1050);
delayMillis(500);
writeMicroseconds(1500);
delayMillis(500);
writeMicroseconds(2000);
delayMillis(500);
writeMicroseconds(1500);
delayMillis(500);
writeMicroseconds(1050);
delayMillis(500);
}

}
void writeMicroseconds(int32_t pwm){
TIM4->CCR1 = pwm;
}

//systick timer config
void systick_init(void){
SysTick->CTRL = 0;
SysTick->VAL = 0;

SysTick->CTRL = 5;
}

//single millisecond control config
void delayMs(void){
SysTick->VAL = 0;
while((SysTick->CTRL & 0x00010000) == 0);
}

//given millisecond delay fnc
void delayMillis(int t){
for(;t>0;t--){
delayMs();
}
}



I did this using the reference manual available in Keil MDK also here - Chapter 15. In the manual under PWM mode, an approach is given which I followed.

This is what is happening:

The servo gets fixed at a certain PWM (probably the fist value given to CCR1 initially). Even though I change the value of CCR1 register in the while loop, the servo stays fixed.

I tried with individual PWM widths and it works fine. But I want to change the PWM width in a loop - whatever value I pass to writeMicroseconds() , the servo should get to that position; that's not happening in my code.

There is no particular reference to do this. I did it only on the basis of the manual and now I don't understand what I did wrong.

• Check the output with a scope or cheap logic analyzer. Try changing it 10x more slowly. Try it using the HAL and then examine the source of that in detail to understand differences vs what it does. – Chris Stratton Dec 14 '20 at 17:22
• I wonder who has set the clock to 72 MHz - such code is not visible. Try blinking a LED first at some correct rate, like 1 Hz first to see where the problem could be. – Justme Dec 14 '20 at 17:41
• @ChrisStratton Actually the servo works well when I don't put a while loop for different PWM values. When TIM4->CCR1 = 1500 is out of the while loop, the servo holds the position and its true position because I have calibrated it at that position. The only problem is that I am unable to change values of PWM width in the while loop(cant see my servo moving to those positions). As I want the servo to keep repeating the position sequence. – Sajil Dec 15 '20 at 6:38
• Again, try making the changes of commanded pulse width happen with at least 10 more time in between them, and use a scope or logic analyzer to verify what it actually happening. Just because you say the servo "works" when given one and only one setting doesn't mean that you are accomplishing what you think you are in terms of signals - these things are pretty tolerant, so your frame rate could be very different from what you intend and they would still "work" but be unable to be updated as quickly as you are trying to. – Chris Stratton Dec 15 '20 at 6:40
• @Justme I have a default clock set to 72MHz because I tried initially with reference to my XTAL 8MHz and didn't work. Debugging found that clock was set to 72MHz. Everything is fine with timers, because my servo works when given one single PWM value. I'll add the code when servo works and when not. – Sajil Dec 15 '20 at 6:41