28BYJ-48步进电机驱动

28BYJ-48步进电机驱动

介绍

参考《HAL快速入门与项目实战(基于DshanMCU-F407)_V1.0》

参考 步进电机

参考 步进电机原理

步进电机控制特点:

  1. 它是通过输入脉冲信号来进行控制的。
  2. 电机的总转动角度由输入脉冲数决定。
  3. 电机的转速由脉冲信号频率决定。

步进电机结构

1

步进电机控制的基本概念

2

步进电机驱动拍数设置

3

步进电机驱动参数设置

4

ULN2003驱动电路

5

ULN2003驱动拍数设定

6

步进电机驱动程序

假设读者已有STM32定时器和PWM的相关概念

driver_28BYJ_48.h

#ifndef __DRIVER_28BYJ_48_H__
#define __DRIVER_28BYJ_48_H__

#ifdef __cplusplus
extern "C" {
#endif

typedef enum
{
    STEP_MODE_HALF = 0x00,
    STEP_MODE_FULL = 0x01  
} motor_step_mode_t;

uint8_t a_28byj_48_init(TIM_HandleTypeDef *tim_handle);

uint8_t a_28byj_48_rotate(motor_step_mode_t step_mode, uint16_t time_delay_us, uint8_t direction);

uint8_t a_28byj_48_rotation_clockwise(motor_step_mode_t step_mode, uint16_t speed);

uint8_t a_28byj_48_rotation_counterclockwise(motor_step_mode_t step_mode, uint16_t time_delay_us);

void a_28byj_48_continuous_rotation(motor_step_mode_t step_mode, float speed_rpm, uint8_t direction);

uint8_t a_28byj_48_stop(void);

void a_28byj_48_toggle_direction(uint8_t *current_direction);

uint8_t a_28byj_48_position_control(motor_step_mode_t step_mode, int32_t target_position, int32_t *current_position, uint16_t time_delay_us);

void a_28byj_48_lock_position(void);

uint16_t a_28byj_48_set_speed(motor_step_mode_t step_mode, float speed_rpm);

uint8_t a_28byj_48_step_angle(motor_step_mode_t step_mode, float angle, uint8_t direction);

uint8_t a_28byj_48_microstep_drive(motor_step_mode_t step_mode, uint16_t microstep_count, uint16_t time_delay_us, uint8_t direction);

uint8_t a_28byj_48_accel_decel_drive(motor_step_mode_t step_mode, uint16_t total_steps, float max_speed_rpm, uint16_t acceleration, uint16_t deceleration, uint8_t direction);

#ifdef __cplusplus
}
#endif

#endif

driver_28BYJ_48.c

#include "driver_28BYJ_48.h"
#include "delay.h"

/* Timer handle and GPIO definitions */
static TIM_HandleTypeDef a_28byj_48_tim_handle;

#define MOTOR_28BYJ_48_IN1 TIM_CHANNEL_1
#define MOTOR_28BYJ_48_IN2 TIM_CHANNEL_2
#define MOTOR_28BYJ_48_IN3 TIM_CHANNEL_3
#define MOTOR_28BYJ_48_IN4 TIM_CHANNEL_4

#define MOTOR_28BYJ_48_GPIO_Port GPIOD
#define MOTOR_28BYJ_48_GPIO_Pin_IN1 GPIO_PIN_12
#define MOTOR_28BYJ_48_GPIO_Pin_IN2 GPIO_PIN_13
#define MOTOR_28BYJ_48_GPIO_Pin_IN3 GPIO_PIN_14
#define MOTOR_28BYJ_48_GPIO_Pin_IN4 GPIO_PIN_15

uint8_t current_step = 0;

/* Step sequences */
const uint8_t step_sequence_full[4] = {
    0x01, /* 0b0001 IN1 */
    0x02, /* 0b0010 IN2 */
    0x04, /* 0b0100 IN3 */
    0x08  /* 0b1000 IN4 */
};

const uint8_t step_sequence_half[8] = {
    0x01, /* 0b0001 IN1 */
    0x03, /* 0b0011 IN1 + IN2 */
    0x02, /* 0b0010 IN2 */
    0x06, /* 0b0110 IN2 + IN3 */
    0x04, /* 0b0100 IN3 */
    0x0C, /* 0b1100 IN3 + IN4 */
    0x08, /* 0b1000 IN4 */
    0x09  /* 0b1001 IN4 + IN1 */
};

/* Initialization */
uint8_t a_28byj_48_init(TIM_HandleTypeDef *tim_handle) {
    a_28byj_48_tim_handle = *tim_handle;
    TIM_OC_InitTypeDef sConfig = {0};

    /* Configure timer for 1 MHz frequency */
    a_28byj_48_tim_handle.Init.Prescaler = (SystemCoreClock / 1000000) - 1;
    a_28byj_48_tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    a_28byj_48_tim_handle.Init.Period = 100 - 1; /* 10 kHz PWM frequency */
    a_28byj_48_tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    a_28byj_48_tim_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_PWM_Init(&a_28byj_48_tim_handle) != HAL_OK) {
        return 1; /* Initialization failed */
    }

    sConfig.OCMode = TIM_OCMODE_PWM1;
    sConfig.Pulse = 0; /* Default off */
    sConfig.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfig.OCFastMode = TIM_OCFAST_ENABLE;

    if (HAL_TIM_PWM_ConfigChannel(&a_28byj_48_tim_handle, &sConfig, MOTOR_28BYJ_48_IN1) != HAL_OK ||
        HAL_TIM_PWM_ConfigChannel(&a_28byj_48_tim_handle, &sConfig, MOTOR_28BYJ_48_IN2) != HAL_OK ||
        HAL_TIM_PWM_ConfigChannel(&a_28byj_48_tim_handle, &sConfig, MOTOR_28BYJ_48_IN3) != HAL_OK ||
        HAL_TIM_PWM_ConfigChannel(&a_28byj_48_tim_handle, &sConfig, MOTOR_28BYJ_48_IN4) != HAL_OK) {
        return 2; /* Channel configuration failed */
    }

    HAL_TIM_PWM_Start(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN1);
    HAL_TIM_PWM_Start(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN2);
    HAL_TIM_PWM_Start(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN3);
    HAL_TIM_PWM_Start(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN4);

    return 0; /* Initialization success */
}

/* Update PWM signals for motor control */
void a_28byj_48_update_pwm(uint8_t step_value, uint16_t speed) 
{
    __HAL_TIM_SET_COMPARE(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN1, (step_value & 0x01) ? speed : 0);
    __HAL_TIM_SET_COMPARE(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN2, (step_value & 0x02) ? speed : 0);
    __HAL_TIM_SET_COMPARE(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN3, (step_value & 0x04) ? speed : 0);
    __HAL_TIM_SET_COMPARE(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN4, (step_value & 0x08) ? speed : 0);
}

/* Stop the motor */
uint8_t a_28byj_48_stop(void) 
{
    /* Set all PWM outputs to 0 to stop the motor */
    __HAL_TIM_SET_COMPARE(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN1, 0);
    __HAL_TIM_SET_COMPARE(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN2, 0);
    __HAL_TIM_SET_COMPARE(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN3, 0);
    __HAL_TIM_SET_COMPARE(&a_28byj_48_tim_handle, MOTOR_28BYJ_48_IN4, 0);

    return 0;
}

/* Rotate the motor */
uint8_t a_28byj_48_rotate(motor_step_mode_t step_mode, uint16_t time_delay_us, uint8_t direction)
{
    const uint8_t *step_sequence = step_mode == STEP_MODE_HALF ? step_sequence_half : step_sequence_full;
    uint8_t step_count = step_mode == STEP_MODE_HALF ? 8 : 4;

    if (direction) {
        current_step = (current_step + 1) % step_count; /* Clockwise */
    } else {
        current_step = (current_step - 1 + step_count) % step_count; /* Counterclockwise */
    }

    uint8_t step_value = step_sequence[current_step];
    a_28byj_48_update_pwm(step_value, 100);

    /* Clamp delay between 560 and 9000 us */
    time_delay_us = (time_delay_us < 560) ? 560 : (time_delay_us > 9000 ? 9000 : time_delay_us);

    delay_us(time_delay_us);

    return 0;
}

/* Lock the motor at the current position */
void a_28byj_48_lock_position(void)
{
    uint8_t step_value = current_step < 4 ? step_sequence_full[current_step] : step_sequence_half[current_step];
    a_28byj_48_update_pwm(step_value, 100);
}

/**
 * a_28byj_48_set_speed
 * Set the speed of the motor by adjusting the delay between steps.
 * 
 * @param step_mode: The step mode (STEP_MODE_FULL or STEP_MODE_HALF).
 * @param speed_rpm: Desired speed in revolutions per minute (RPM).
 * 
 * @return time_delay_us: Computed delay in microseconds for the desired speed.
 * 
 * 通过参数 RPM 来设置目标转速(转/分钟),通过目标转速计算出不同步进模式下,每步需要的时间
 * 
 * 每步延时的时间(每步的目标时间 ,微秒)= 每分钟的微秒数 / 每分钟的步数 = (60 * 10^6) / (RPM * 每圈步数)
 */
uint16_t a_28byj_48_set_speed(motor_step_mode_t step_mode, float speed_rpm)
{
    float steps_per_revolution = step_mode == STEP_MODE_HALF ? 4096.0 : 2048.0;
    uint16_t time_delay_us = (uint16_t)((60.0 * 1000000.0) / (speed_rpm * steps_per_revolution));

    if (time_delay_us < 560) {
        time_delay_us = 560;
    } else if (time_delay_us > 9000) {
        time_delay_us = 9000;
    }

    return time_delay_us;
}

/**
 * a_28byj_48_step_angle
 * Rotate the motor by a specific angle.
 * 
 * @param step_mode: The step mode (STEP_MODE_FULL or STEP_MODE_HALF).
 * @param angle: The angle to rotate in degrees.
 * @param direction: Direction of rotation (1 for clockwise, 0 for counterclockwise).
 * 
 * @return 0 if successful, 1 if an error occurs.
 * 
 * 28BYJ-48 是4相8拍步进电机,其内部结构决定了步进电机内部转子每步旋转的角度(步进角,从一相旋转到相邻的下一相的角度)为11.25°
 * 28BYJ-48 电机内部有一个减速齿轮组,该减速比为64:1 这意味着电机的内部转子需要旋转 64 圈,才能让外部输出轴旋转 1 圈
 * 
 * 28BYJ-48 电机的步进角为 11.25°。
 * 每转需要的基本步数(未经过减速)为 360° / 11.25° = 32步
 * 由于 28BYJ-48 的减速比为 64:1,输出轴每转一圈需要内部转子转动 64 圈。
 * 因此,经过减速后,输出轴每转一圈需要的步数为 32 步/圈 * 64 圈 = 2048 步
 * 
 * 在整步模式下,步进电机的完整旋转步数就是 2048 步。
 * 
 * 在半步模式下,每次步进电机的角度是整步模式的一半,即 5.625°
 * 每个完整步分为两个半步,每转需要的基本步数(未经过减速)为 360° / 5.625° = 64步
 * 经过 64:1 的减速比后,输出轴每转一圈需要的步数为 64 步/圈 * 64 圈 = 4096 步
 * 
 * 在半步模式下,步进电机的完整旋转步数就是 4096 步。
 * 
 * 减速比的存在是为了增加输出轴的精度和力矩,通过 64:1 的减速比后,输出轴可以达到 整部模式下 0.17578125° 的精度,半步模式下 0.087890625° 的精度,但是这会牺牲旋转的速度
 * 
 */
uint8_t a_28byj_48_step_angle(motor_step_mode_t step_mode, float angle, uint8_t direction) {
    const uint8_t *step_sequence = step_mode == STEP_MODE_HALF ? step_sequence_half : step_sequence_full;
    uint8_t step_count = step_mode == STEP_MODE_HALF ? 8 : 4;
    float steps_per_revolution = step_mode == STEP_MODE_HALF ? 4096.0 : 2048.0;

    uint16_t total_steps = (uint16_t)((angle / 360.0) * steps_per_revolution);

    for (uint16_t i = 0; i < total_steps; i++) {
        if (direction) {
            current_step = (current_step + 1) % step_count;
        } else {
            current_step = (current_step - 1 + step_count) % step_count;
        }

        uint8_t step_value = step_sequence[current_step];
        a_28byj_48_update_pwm(step_value, 100);
        delay_us(1000);
    }

    return 0;
}

/* Continuous motor rotation */
void a_28byj_48_continuous_rotation(motor_step_mode_t step_mode, float speed_rpm, uint8_t direction)
{
    uint16_t time_delay_us = a_28byj_48_set_speed(step_mode, speed_rpm);

    while (1) {
        a_28byj_48_rotate(step_mode, time_delay_us, direction);
    }
}

/* Toggle motor direction */
void a_28byj_48_toggle_direction(uint8_t *current_direction)
{
    *current_direction = (*current_direction == 1) ? 0 : 1;
}

uint8_t a_28byj_48_rotation_clockwise(motor_step_mode_t step_mode, uint16_t time_delay_us) {
    return a_28byj_48_rotate(step_mode, time_delay_us, 1);
}

uint8_t a_28byj_48_rotation_counterclockwise(motor_step_mode_t step_mode, uint16_t time_delay_us) {
    return a_28byj_48_rotate(step_mode, time_delay_us, 0);
}


/************************* 中级功能 *************************** */
/**
 * a_28byj_48_position_control
 * Move the motor to a specific position in steps.
 *
 * @param step_mode: The step mode (STEP_MODE_FULL or STEP_MODE_HALF).
 * @param target_position: The desired position in steps.
 * @param current_position: Pointer to the current position in steps.
 * @param time_delay_us: Delay between steps for speed control.
 *
 * @return 0 if successful, 1 if an error occurs.
 */
uint8_t a_28byj_48_position_control(motor_step_mode_t step_mode, int32_t target_position, int32_t *current_position, uint16_t time_delay_us)
{
    int32_t steps_to_move = target_position - *current_position; /* Calculate the difference */
    uint8_t direction = (steps_to_move > 0) ? 1 : 0; /* Determine direction */
    steps_to_move = abs(steps_to_move); /* Get the absolute number of steps to move */

    for (int32_t i = 0; i < steps_to_move; i++) {
        /* Rotate one step in the determined direction */
        a_28byj_48_rotate(step_mode, time_delay_us, direction);
        /* Update current position */
        *current_position += (direction ? 1 : -1); 
    }

    return 0;
}


/**
 * a_28byj_48_microstep_drive
 * Drive the motor in microstep mode for smoother motion.
 *
 * @param step_mode: The step mode (STEP_MODE_FULL or STEP_MODE_HALF).
 * @param microstep_count: Number of microsteps to perform.
 * @param time_delay_us: Delay between microsteps for speed control.
 * @param direction: Direction of rotation (1 for clockwise, 0 for counterclockwise).
 *
 * @return 0 if successful, 1 if an error occurs.
 */
uint8_t a_28byj_48_microstep_drive(motor_step_mode_t step_mode, uint16_t microstep_count, uint16_t time_delay_us, uint8_t direction)
{
    /* Example: Divide each step into 16 microsteps */
    uint16_t microsteps_per_step = 16;
    uint16_t total_steps = microstep_count / microsteps_per_step;

    for (uint16_t step = 0; step < total_steps; step++) {
        for (uint16_t microstep = 0; microstep < microsteps_per_step; microstep++) {
            /* Perform a fraction of the step */
            a_28byj_48_rotate(step_mode, time_delay_us / microsteps_per_step, direction);
        }
    }

    return 0;
}


/**
 * a_28byj_48_accel_decel_drive
 * Drive the motor with acceleration and deceleration.
 *
 * @param step_mode: The step mode (STEP_MODE_FULL or STEP_MODE_HALF).
 * @param total_steps: Total number of steps to move.
 * @param max_speed_rpm: Maximum speed in RPM.
 * @param acceleration: Steps to accelerate to max speed.
 * @param deceleration: Steps to decelerate from max speed.
 * @param direction: Direction of rotation (1 for clockwise, 0 for counterclockwise).
 *
 * @return 0 if successful, 1 if an error occurs.
 */
uint8_t a_28byj_48_accel_decel_drive(motor_step_mode_t step_mode, uint16_t total_steps, float max_speed_rpm, uint16_t acceleration, uint16_t deceleration, uint8_t direction)
{
    uint16_t time_delay_us;
    uint16_t current_speed_step = 0;

    /* Accelerate */
    for (uint16_t step = 0; step < acceleration && step < total_steps; step++) {
        current_speed_step++;
        time_delay_us = a_28byj_48_set_speed(step_mode, (max_speed_rpm * current_speed_step) / acceleration);
        a_28byj_48_rotate(step_mode, time_delay_us, direction);
    }

    /* Constant speed */
    for (uint16_t step = acceleration; step < total_steps - deceleration; step++) {
        a_28byj_48_rotate(step_mode, time_delay_us, direction);
    }

    /* Decelerate */
    for (uint16_t step = 0; step < deceleration && step < total_steps; step++) {
        current_speed_step--;
        time_delay_us = a_28byj_48_set_speed(step_mode, (max_speed_rpm * current_speed_step) / deceleration);
        a_28byj_48_rotate(step_mode, time_delay_us, direction);
    }

    return 0;
}

总结

  1. 首先需要熟悉步进电机28BYJ-48的机构,工作原理。驱动拍的工作特点。驱动的步进角公式会计算。

  2. 驱动库编写需要针对不同驱动电路的设计,驱动电路的使用工作原理。ULN2003驱动电路特点,输入和输出是反相,输入高电平,驱动输出端拉低,驱动绕组励磁工作。

  3. 能力有限,有错误地方欢迎指正。