STM32
直播中

徐开

11年用户 914经验值
擅长:MEMS/传感技术
私信 关注
[问答]

怎样采用STM32F103驱动ULN2003去控制28BYJ-48步进电机运转呢

怎样采用STM32F103驱动ULN2003去控制28BYJ-48步进电机运转呢?步进电机程序该如何去改进呢?

回帖(1)

王桂芳

2021-12-20 09:58:36
上节代码问题

上节代码应该是能搜到的控制ULN2003驱动步进28BYJ-48最通用的方法了,但是上节代码的执行会导致整个系统进行阻塞。如果电机运转10圈可能导致41s的阻塞时间,这对于任何系统工程都是致命的。
在这41s中整个cpu都在改变IO口状态和delay_ms中循环,主要流程如下:
          A相 --> delay_us(1000) --> AB相 --> delay_us(1000) --> B相 --> delay_us(1000) --> BC相 --> delay_us(1000) --.... --> delay_us(1000) --> A相 --> ....   其他任务得不到及时执行,这对于CPU是一种浪费,同时极大的影响系统及时性。
分析原因主要是由于delay_us(1000)造成的。
  改进改进

  本节将仍然采用八拍(天龙八步),即1-2相驱动方式驱动,电机旋转函数中将不再使用delay_us(1000),而是将每步相位的保持时间放在定时器函数中进行,基本可以实现异步控制步进电机,同时执行其他程序进行。本教程采用STM32F103驱动ULN2003控制步进电机28BYJ-48运转。
  改进步进电机程序

  


  • 在step_motor.h中进行端口宏定义:

  
/* 步进电机1参数宏 */
#define LA PAout(1)     /* A相 */
#define LB PAout(2)     /* B相 */
#define LC PAout(3)     /* C相 */
#define LD PAout(4)     /* D相 */


/* A相 */
#define LA_GPIO_PORT    GPIOA
#define LA_GPIO_PIN     GPIO_Pin_1
#define LA_GPIO_CLK     RCC_APB2Periph_GPIOA
/* B相 */
#define LB_GPIO_PORT    GPIOA
#define LB_GPIO_PIN     GPIO_Pin_2
#define LB_GPIO_CLK     RCC_APB2Periph_GPIOA
/* C相 */
#define LC_GPIO_PORT    GPIOA
#define LC_GPIO_PIN     GPIO_Pin_3
#define LC_GPIO_CLK     RCC_APB2Periph_GPIOA
/* D相 */
#define LD_GPIO_PORT    GPIOA
#define LD_GPIO_PIN     GPIO_Pin_4
#define LD_GPIO_CLK     RCC_APB2Periph_GPIOA



  • 在step_motor.c中初始化端口,代码如下:


/**
* @name: Step_Motor_Init
* @description: 步进电机初始化端口
* @param {*}
* @return {*}
*/
void Step_Motor_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    RCC_APB2PeriphClockCmd(LA_GPIO_CLK | LB_GPIO_CLK | LC_GPIO_CLK | LD_GPIO_CLK, ENABLE);


    /* A相端口初始化 */
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Pin = LA_GPIO_PIN;
    GPIO_Init(LA_GPIO_PORT, &GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin = LB_GPIO_PIN;
    GPIO_Init(LA_GPIO_PORT, &GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin = LC_GPIO_PIN;
    GPIO_Init(LA_GPIO_PORT, &GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin = LD_GPIO_PIN;
    GPIO_Init(LA_GPIO_PORT, &GPIO_InitStruct);


    GPIO_ResetBits(LA_GPIO_PORT, LA_GPIO_PIN);
    GPIO_ResetBits(LB_GPIO_PORT, LB_GPIO_PIN);
    GPIO_ResetBits(LC_GPIO_PORT, LC_GPIO_PIN);
    GPIO_ResetBits(LD_GPIO_PORT, LD_GPIO_PIN);
}



  • 在step_motor.c中宏定义或者枚举类型定义每个IO口或者是电机各个相的软件接通位。   
         比如:
       



    • A相位定义为bit0,当A相导通的时候状态对应0x01;
    • B相位定义为bit1,当B相导通时位0x02;以此类推定义C相和D相;



/* 私有类型定义------------------------------------------------- */
typedef enum _PIN_BIT
{
    PLA = 0x01,
    PLB = 0x02,
    PLC = 0x04,
    PLD = 0x08,
} Pin_Bit;



  • 在step_motor.h中进行类型定义,做StepMotor_t类型的结构体对步进电机需要的参数进行封装,方便后续使用:


/* 类型定义 ------------------------------------------ */
STRUCT(StepMotor_t)
{
    /*
     *state: bit0  0 表示电机处于非运行;     1 表示开启运行
     *       bit1  0 表示电机正转       1 反转   
     */
    uint8_t state;
    /* 每步时间片 */
    uint16_t step_slice;
    /* 总步数 4096为一圈*/
    u32 step_num;
   
    void (*run)(StepMotor_t *motor);


};



  • 在step_motor.c中定义StepMotor全局变量,并在step_motor.h中声明:


/* 变量定义 ---------------------------------------------------- */
StepMotor_t StepMotor = {0x01, 1200, 8192, Step_Motor_Start_InTimer};


增加定时器程序

  


  • 新建tim.h和tim.c对定时器程序进行编程
  • 在tim.h中定义硬件定时器宏


/* 宏定义 ----------------------------------------------- */
#define TIMx                        TIM3
#define TIMx_APBxClock_Func         RCC_APB1PeriphClockCmd
#define TIMx_CLK_EN                 RCC_APB1Periph_TIM3
#define TIMx_IRQn                   TIM3_IRQn
#define TIMx_IRQnHandler            TIM3_IRQHandler



  • 定义软件定时器类型,用于封装需要操作的数据


/* 类型定义 --------------------------------------------- */
STRUCT(Timer_t)
{
    /*
     * state: 可以复用,通过判断bit0;
     *        bit0 0表示非运行, 1表示运行
     */
    uint8_t state;  
    uint16_t psc;
    uint16_t arr;
    /* 中断次数 */
    u32 times;
};



  • 在tim.c中定义软件定时器Timer变量,可以先赋初始值,后面会在调用之前进行改变;


/* 变量定义 --------------------------------------------- */
Timer_t Timer = {0, 72, 0xffff, 8};



  • 硬件定时器初始化


/* 函数定义 --------------------------------------------- */
void TIM_Configuration(uint16_t psc, uint16_t arr)
{
        TIMx_APBxClock_Func(TIMx_CLK_EN, ENABLE);


        /* 时机单元设置 */
        TIM_TimeBaseInitTypeDef TIM_TBInitStruct;
        TIM_TBInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TBInitStruct.TIM_Period = arr - 1;
        TIM_TBInitStruct.TIM_Prescaler = psc - 1;
        TIM_TBInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInit(TIMx, &TIM_TBInitStruct);


        /* 开中断 */
        TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE);


        NVIC_InitTypeDef NVIC_InitStruct;


        /* 中断设置 */
        NVIC_InitStruct.NVIC_IRQChannel = TIMx_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);


        /* 清除中断标志位 */
        TIM_ClearITPendingBit(TIMx, TIM_IT_Update);
}



  • 硬件定时器中断处理函数,在中断中进行任务回调,进行逻辑处理。


/* 函数定义 --------------------------------------------- */
void TIM_Configuration(uint16_t psc, uint16_t arr)
{
        TIMx_APBxClock_Func(TIMx_CLK_EN, ENABLE);


        /* 时机单元设置 */
        TIM_TimeBaseInitTypeDef TIM_TBInitStruct;
        TIM_TBInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TBInitStruct.TIM_Period = arr - 1;
        TIM_TBInitStruct.TIM_Prescaler = psc - 1;
        TIM_TBInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInit(TIMx, &TIM_TBInitStruct);


        /* 开中断 */
        TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE);


        NVIC_InitTypeDef NVIC_InitStruct;


        /* 中断设置 */
        NVIC_InitStruct.NVIC_IRQChannel = TIMx_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);


        /* 清除中断标志位 */
        TIM_ClearITPendingBit(TIMx, TIM_IT_Update);
}



  • 在step_motor.c中定义回调函数,进行处理,设置或者改变相位, 即每次硬件定时器函数中都会进行步进电机相位状态的改变:


/**
* @name: Step_Motor_Handler
* @description: 在TIM IRQ中进行处理,设置/改变相位状态值
* @param {*}
* @return {*}
*/
void Step_Motor_Handler(void)
{
    if(Timer.times)
    {
        Timer.times--;
        Set_PhaseState(&StepMotor);
    }
    else        /* 任务完成 */
    {
        printf("Timer timing over n");
        TIM_Cmd(TIMx, DISABLE);        /* 关闭硬件定时器 */
        Timer.state &= ~0x01;        /* 软件定时器置0非运行态,可以其他程序调用  */
        StepMotor.state &= ~0x01;        /* motor状态为置0,非运行 */
        Step_Motor_Stop(&StepMotor);        /* 电机各个相位置0 */
    }
}



  • 同时定义相位设置函数,在回调函数中调用改变电机相位:


/**
* @name: Set_PhaseState
* @description: 定时器中断中调用,设置电机相位状态
* @param {*}
* @return {*}
*/
void Set_PhaseState(StepMotor_t *motor)
{
    /* 保存上次的相位数组下标 */
    static uint8_t i = 0;
    /* 判断正反转 */
    if(!(motor->state & 0x02))
    {
        LA = (uint8_t)((steps&PLA) >> 0);
        LB = (uint8_t)((steps&PLB) >> 1);
        LC = (uint8_t)((steps&PLC) >> 2);
        LD = (uint8_t)((steps&PLD) >> 3);
    }
    else
    {
        LA = (uint8_t)((steps[7-i]&PLA) >> 0);
        LB = (uint8_t)((steps[7-i]&PLB) >> 1);
        LC = (uint8_t)((steps[7-i]&PLC) >> 2);
        LD = (uint8_t)((steps[7-i]&PLD) >> 3);
    }


    i++;
    if(i >= 8)
    {
        i = 0;
    }  
}


  • 在step_motor.c中设置步进电机运行函数,设置软件定时器和硬件定时器参数:


/**
* @name: Step_Motor_Start_InTimer
* @description: 步进电机开始运行。设置软件定时器和硬件定时器,并开启硬件定时器功能。
* @param {StepMotor_t} *motor
* @return {*}
*/
void Step_Motor_Start_InTimer(StepMotor_t *motor)
{
    /* 运行开始,电机开启运行置0 */
    motor->state &= ~0x01;
    /* 软件定时器设置 */
    Timer.state |= 0x01;    /* 软件定时器开启 */
    Timer.arr = motor->step_slice;  
    Timer.times = motor->step_num + 1;
    /* 硬件定时器设置 */
    TIM_SetAutoreload(TIMx, Timer.arr);
    printf("step motor start ... n");
    TIM_Cmd(TIMx, ENABLE);
    /* 产生中断事件 */
    TIM_GenerateEvent(TIMx, TIM_EventSource_Update);
}


结果显示

  

  • 在main.c中调用


int main(void)
{
        uint32_t t = 0;


        initSysTick();
        NVIC_PriorityGroupConfig(2);
        Usart1_Init(115200);
        LED1_Init();
        Step_Motor_Init();
        TIM_Configuration(72, 20000);


        printf("Hardware init ok.n");
        for(;;)
        {
                t++;


                /* 开启步进电机任务,可以在其他任务中进行 */
                if(0 == t % 1500)
                        StepMotor.state |= 0x01;
                               
                if(StepMotor.state & 0x01)
                {
                        StepMotor.run(&StepMotor);       
                }
               
                /* 其他任务 */
                if(t % 100 == 1)
                {
                        LED1_Toggle();
                        printf("led togglen");
                }
               
                if(t >= 2000)
                        t = 0;
                delay_ms(10);
        }
}


  • 在串口中可以看到:

      

      

    这样就可以在使用ULN2003控制28BYJ-48的同时,STM32也同时执行其他任务,而不是阻塞在步进电机执行当中41s了。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分