STM32驱动ULN2003控制步进电机28BYJ-48之定时器异步

上节代码问题

传送门:STM32控制ULN2003驱动步进电机28BYJ-48最基础方法
上节代码应该是能搜到的控制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运转。传送门:STM32控制ULN2003步进电机最基础方法

改进步进电机程序

  • 在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 TIMx_IRQnHandler(void)
{
     
    if(TIM_GetITStatus(TIMx, TIM_IT_Update) == SET)
    {
     
        Step_Motor_Handler();

        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[i]&PLA) >> 0);
        LB = (uint8_t)((steps[i]&PLB) >> 1);
        LC = (uint8_t)((steps[i]&PLC) >> 2);
        LD = (uint8_t)((steps[i]&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 toggle\n");
		}
		
		if(t >= 2000)
			t = 0;
		delay_ms(10);
	}
}
  • 在串口中可以看到:
    STM32驱动ULN2003控制步进电机28BYJ-48之定时器异步_第1张图片
    这样就可以在使用ULN2003控制28BYJ-48的同时,STM32也同时执行其他任务,而不是阻塞在步进电机执行当中41s了。

以上代码传送门:STM32驱动ULN控制步进电机天龙八步之定时器异步


喜欢请点个赞哦,谢谢!欢迎指正

你可能感兴趣的:(STM32,stm32,单片机,嵌入式)