Skip to main content
michaelmccartyeng
Associate III
April 4, 2014
Question

Controlling servo with tim1 close but it does not work

  • April 4, 2014
  • 16 replies
  • 2218 views
Posted on April 04, 2014 at 16:00

Hello All,

I'm trying to control a servo. I found code on a site that was in Russian, and google translate did not help me much to understand it. Basically we want a period of 20ms and we want to be high during at least 1ms of that 20ms for start position up to 2ms for end position of the servo. I'm using tim1 and using PA.7. The original example was using a pin that I'm using for something else. My systemclock is ''uint32_t SystemCoreClock = 168000000;''. I dont know why they enable the int, I didnt find the int handler anywhere. Any help would be appreciated, I just discovered how servos work this week and cant believe I didn't use them before. Now I just have to add support for at least one to my project.

void initServo(void){
/*
* Servo min(1ms) = 1580
* Servo max(2ms) = 3160
*
*/
TIM_BDTRInitTypeDef bdtr;
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* TIM1 clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
/* GPIOE clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
/* TIM1 channel 1 pin (PA.7) configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Connect TIM pins to AF2 */
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_TIM1);
TIM_TimeBaseStructInit (&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = SystemCoreClock/5000000; // pers 6
TIM_TimeBaseStructure.TIM_Period = 31600; // 20ms for servo period
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1 , &TIM_TimeBaseStructure);
// PWM1 Mode configuration: Channel1
// Edge -aligned; not single pulse mode
TIM_OCStructInit (& TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC1Init(TIM1 , &TIM_OCInitStructure);
TIM_BDTRStructInit(&bdtr);
bdtr.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
TIM_BDTRConfig(TIM1, &bdtr);
// Enable Timer Interrupt and Timer
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); //
TIM_Cmd(TIM1 , ENABLE);
TIM_SetCompare1(TIM1, 2370); // 2370 = 1.5ms - for Servo
}

    This topic has been closed for replies.

    16 replies

    michaelmccartyeng
    Associate III
    April 4, 2014
    Posted on April 04, 2014 at 16:09

    
     /**
    * @brief 
    * @param None
    * @retval None
    */
    void setServo(int pos){
    // Servo min(1ms) = 1580
    // Servo max(2ms) = 3160
    if(pos > 3160){
    pos = 3160;
    }
    if(pos < 1580){
    pos = 1580;
    }
    TIM_SetCompare1(TIM1, pos);
    }

    Setting it back to use PE.9 it seems to work on in initServo(Above). But the setServo function does not update it. 
     So I guess its just something to do with me changing the pin to A7, and I'm not calling some required re init function for the TIM1. 
    

    Tesla DeLorean
    Guru
    April 4, 2014
    Tesla DeLorean
    Guru
    April 4, 2014
    Posted on April 04, 2014 at 18:31

    /* TIM1 Main Output Enable */

    TIM_CtrlPWMOutputs(TIM1, ENABLE);

    Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
    michaelmccartyeng
    Associate III
    April 4, 2014
    Posted on April 04, 2014 at 19:02

    Hey Clive,

    I abandoned the code I was using that was hard to understand for the code you linked to. That code is awesome and easy to understand. I had already set aside tim3 for 4 more pwm channels so I adapted your code to TIM3. All four channels are working ! The only thing I'm having issues with now is my system uses all unsigned 32 bit integers. So instead of mapping -90 to 90 I would rather map 0 to I try to subtract 90 from whatever value I input which should work but the float is always zero. With the mapping it should be -90 = 0, 0 = 90, 90 = 180, with your +/- 90 on the left mapped to 0to180 on the right. Any idea why it does not work ?

    // value is my u32 input 0 to 180
    float v=0; 
    v = 90-(float)value;
    servo_angle[0] = v;

    I guess I should just refactor everything to work with 0 to 180 u32 ? Hate to mess something up though.
    michaelmccartyeng
    Associate III
    April 4, 2014
    Posted on April 04, 2014 at 19:04

    Never mind it does seem to be working, just the debugger seems to always show the float as 0.

    Thanks Clive !

    Tesla DeLorean
    Guru
    April 4, 2014
    Posted on April 04, 2014 at 19:40

    Yes, I tried to make it clean and concise, I think my original F1 version used integers. People want/need different things, so adapt to suit

    For 0..180.0

    TIM_SetCompare1(TIM4, 600 + (int)(servo_angle[0] * 10.0f) ); // PD.12

    integer

    TIM_SetCompare1(TIM4, 600 + (servo_angle_int[0] * 10) ); // PD.12

    integer, tenths of degrees, ie 0 .. 1800

    TIM_SetCompare1(TIM4, 600 + servo_angle_tenths[0] ); // PD.12

    [DEAD LINK /public/STe2ecommunities/mcu/Lists/STM32VLDiscovery/Flat.aspx?RootFolder=/public/STe2ecommunities/mcu/Lists/STM32VLDiscovery/Servo%20control%20using%20STM32VLDiscovery&FolderCTID=0x01200200770978C69A1141439FE559EB459D758000491D59B8574F8049B5DFA3E8B21CBA51&currentviews=2099]https://my.st.com/public/STe2ecommunities/mcu/Lists/STM32VLDiscovery/Flat.aspx?RootFolder=%2Fpublic%2FSTe2ecommunities%2Fmcu%2FLists%2FSTM32VLDiscovery%2FServo%20control%20using%20STM32VLDiscovery&FolderCTID=0x01200200770978C69A1141439FE559EB459D758000491D59B8574F8049B5DFA3E8B21CBA51¤tviews=2099

    Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
    michaelmccartyeng
    Associate III
    April 4, 2014
    Posted on April 04, 2014 at 20:15

    TIM_SetCompare1(TIM3, 600 + servo_angle_tenths[0] ); // PB.4
    TIM_SetCompare2(TIM3, 600 + servo_angle_tenths[1] ); // PB.5
    TIM_SetCompare3(TIM3, 600 + servo_angle_tenths[2] ); // PB.0
    TIM_SetCompare4(TIM3, 600 + servo_angle_tenths[3] ); // PB.1

    I implemented the ''tenths'' way. Exactly what I needed ! The comments say 600us to 2400us (600+1800). I thought servos worked from 1ms to 2ms ? I can see how the 20ms is used to use time division multiplexing to span out to multiple different servos on a single line. If the output from the stm32 were split by a external IC in divisions of 2ms we could control 10 servos on a single line. I dont know if such a chip exists but that would be neat. Thanks a bunch Clive, great code. I basically just did a replace of TIM4 with TIM3 and setup the gpio and everything worked. Her it is in case anyone needs 4 servos driven from TIM3

    volatile u32 servo_angle_tenths[4] = { 0,0,0,0 }; // 0 to 1800
    /**
    * @brief initialize the servo 
    * 
    * @param None
    * @retval None
    */
    void initServo(void){
    RCC_Configuration_Servo();
    GPIO_Configuration_Servo();
    NVIC_Configuration_Servo();
    TIM3_Configuration_Servo();
    }
    /**************************************************************************************/
    // Integers, or scaled integers (*10) will be more efficient here
    void TIM3_IRQHandler(void)
    {
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
    {
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    // minimum high of 600 us for -90 degrees, with +90 degrees at 2400 us, 10 us per degree
    // timer timebase set to us units to simplify the configuration/math
    TIM_SetCompare1(TIM3, 600 + servo_angle_tenths[0] ); // PB.4
    TIM_SetCompare2(TIM3, 600 + servo_angle_tenths[1] ); // PB.5
    TIM_SetCompare3(TIM3, 600 + servo_angle_tenths[2] ); // PB.0
    TIM_SetCompare4(TIM3, 600 + servo_angle_tenths[3] ); // PB.1
    }
    }
    /**************************************************************************************/
    void RCC_Configuration_Servo(void)
    {
    /* --------------------------- System Clocks Configuration -----------------*/
    /* TIM3 clock enable */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    /* GPIOD clock enable */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    }
    /**************************************************************************************/
    void NVIC_Configuration_Servo(void)
    {
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Enable the TIM3 gloabal Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    }
    /**************************************************************************************/
    void GPIO_Configuration_Servo(void)
    {
    GPIO_InitTypeDef GPIO_InitStructure;
    /*-------------------------- GPIO Configuration ----------------------------*/
    /* GPIOD Configuration: in output push-pull */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_4 | GPIO_Pin_1 | GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    /* Connect TIM3 pins to AF2 */
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_TIM3);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_TIM3);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource1, GPIO_AF_TIM3);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource0, GPIO_AF_TIM3);
    }
    /**************************************************************************************/
    void TIM3_Configuration_Servo(void)
    {
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    /* Time base configuration - SystemCoreClock = 168000000 for 168 MHz board */
    TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t) (((SystemCoreClock / 1000000) / 2) - 1); // Shooting for 1 MHz, (1us)
    TIM_TimeBaseStructure.TIM_Period = 20000 - 1; // 1 MHz / 20000 = 50 Hz (20ms)
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    /* Enable TIM3 Preload register on ARR */
    TIM_ARRPreloadConfig(TIM3, ENABLE);
    /* TIM PWM1 Mode configuration: Channel */
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 1500; // Servo Top-Center
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    /* Output Compare PWM1 Mode configuration: Channel1 PB.4 */
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
    /* Output Compare PWM1 Mode configuration: Channel2 PB.5 */
    TIM_OC2Init(TIM3, &TIM_OCInitStructure);
    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
    /* Output Compare PWM1 Mode configuration: Channel3 PB.0 */
    TIM_OC3Init(TIM3, &TIM_OCInitStructure);
    TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
    /* Output Compare PWM1 Mode configuration: Channel4 PB.1 */
    TIM_OC4Init(TIM3, &TIM_OCInitStructure);
    TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
    /* TIM Interrupts enable */
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
    /* TIM3 enable counter */
    TIM_Cmd(TIM3, ENABLE);
    }

    michaelmccartyeng
    Associate III
    April 4, 2014
    Posted on April 04, 2014 at 20:23

    Never mind, I finally found the specs of the servo here

    http://www.servodatabase.com/servo/towerpro/sg90

    Basic Information
    Modulation: Analog
    Torque: 
    4.8V:
    0 oz-in (1.80 kg-cm)
    Speed: 
    4.8V:
    0.10 sec/60°
    Weight: 0.32 oz (9.0 g)
    Dimensions: 
    Length:
    0.91 in (1 mm)
    Width:
    0.48 in (2 mm)
    Height:
    1.14 in (0 mm)
    Motor Type: 3-pole
    Gear Type: Plastic
    Rotation/Support: Bushing
    Additional Specifications
    Rotational Range: 180°
    Pulse Cycle: ? (add)
    Pulse Width: 500-2400 µs
    Connector Type: ? (add)

    Tesla DeLorean
    Guru
    April 4, 2014
    Posted on April 04, 2014 at 20:39

    The comments say 600us to 2400us (600+1800). I thought servos worked from 1ms to 2ms ?

    It clearly depends on the models in question, I'm not a user of servos, but I found several for which specification were available that fell under this criterion, and it makes a lot of sense from an implementation point of view. I'm more of a signals/data guy.

    http://www.servocity.com/html/hs-325hb_bb_deluxe.html#.Uz77FSjyBaQ

    This servo can operate 180° when given a pulse signal ranging from 600usec to 2400usec. Since most R/C controllers cannot generate this wide of signal range, you will need to use our

    http://www.servocity.com/html/180o_servo_stretcher.html

    for 180° operation.

    Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
    michaelmccartyeng
    Associate III
    April 5, 2014
    Posted on April 05, 2014 at 17:30

    Can I use TIM1, looking over my code I was already using TIM3. TIM1 is the only one with pins that actually work out well. I remember from the documentation that TIM1 works differently from the others, I think its more ''high speed''. I changed your code to use TIM1 and it keeps halting in startup_stm32f40_41xxx.s in the FPU_IRQHandler ? I think I'm not defining the correct Int handler, or the correct INT

    void initServo(void){
    RCC_Configuration_Servo();
    GPIO_Configuration_Servo();
    NVIC_Configuration_Servo();
    TIM1_Configuration_Servo();
    }
    /**************************************************************************************/
    // Integers, or scaled integers (*10) will be more efficient here
    void TIM1_IRQHandler(void)
    {
    if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
    {
    TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
    // minimum high of 600 us for -90 degrees, with +90 degrees at 2400 us, 10 us per degree
    // timer timebase set to us units to simplify the configuration/math
    TIM_SetCompare1(TIM1, 600 + servo_angle_tenths[0] ); // PE.9
    TIM_SetCompare2(TIM1, 600 + servo_angle_tenths[1] ); // PE.11
    TIM_SetCompare3(TIM1, 600 + servo_angle_tenths[2] ); // PE.13
    TIM_SetCompare4(TIM1, 600 + servo_angle_tenths[3] ); // PE.14
    }
    }
    /**************************************************************************************/
    void RCC_Configuration_Servo(void)
    {
    /* --------------------------- System Clocks Configuration -----------------*/
    /* TIM1 clock enable */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
    /* GPIOD clock enable */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
    }
    /**************************************************************************************/
    void NVIC_Configuration_Servo(void)
    {
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Enable the TIM1 gloabal Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_TIM10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    }
    /**************************************************************************************/
    void GPIO_Configuration_Servo(void)
    {
    GPIO_InitTypeDef GPIO_InitStructure;
    /*-------------------------- GPIO Configuration ----------------------------*/
    /* GPIOD Configuration: in output push-pull */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_11 | GPIO_Pin_13 | GPIO_Pin_14;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOE, &GPIO_InitStructure);
    /* Connect TIM1 pins to AF2 */
    GPIO_PinAFConfig(GPIOE, GPIO_PinSource9, GPIO_AF_TIM1);
    GPIO_PinAFConfig(GPIOE, GPIO_PinSource11, GPIO_AF_TIM1);
    GPIO_PinAFConfig(GPIOE, GPIO_PinSource13, GPIO_AF_TIM1);
    GPIO_PinAFConfig(GPIOE, GPIO_PinSource14, GPIO_AF_TIM1);
    }
    /**************************************************************************************/
    void TIM1_Configuration_Servo(void)
    {
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    /* Time base configuration - SystemCoreClock = 168000000 for 168 MHz board */
    TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t) (((SystemCoreClock / 1000000) / 2) - 1); // Shooting for 1 MHz, (1us)
    TIM_TimeBaseStructure.TIM_Period = 20000 - 1; // 1 MHz / 20000 = 50 Hz (20ms)
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
    /* Enable TIM1 Preload register on ARR */
    TIM_ARRPreloadConfig(TIM1, ENABLE);
    /* TIM PWM1 Mode configuration: Channel */
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 1500; // Servo Top-Center
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    /* Output Compare PWM1 Mode configuration: Channel1 PB.4 */
    TIM_OC1Init(TIM1, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
    /* Output Compare PWM1 Mode configuration: Channel2 PB.5 */
    TIM_OC2Init(TIM1, &TIM_OCInitStructure);
    TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
    /* Output Compare PWM1 Mode configuration: Channel3 PB.0 */
    TIM_OC3Init(TIM1, &TIM_OCInitStructure);
    TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);
    /* Output Compare PWM1 Mode configuration: Channel4 PB.1 */
    TIM_OC4Init(TIM1, &TIM_OCInitStructure);
    TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);
    /* TIM Interrupts enable */
    TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
    /* TIM1 enable counter */
    TIM_Cmd(TIM1, ENABLE);
    }