目录
前言
在使用esp-idf v5.0.1框架开发28BYJ-48步进电机驱动时,发现使用延时产生脉冲驱动uln2003,其最快延时到10ms,再小一点都无法驱动步进电机,gpio口也没有输出,相比之下51单片机使用延时可以正常的工作,esp32驱动的步进电机太慢了。其具体原因尚不明白,后来采用定时器产生脉冲,最终得以解决。以下内容则包含硬件通用定时器的简单驱动程序。(注:使用的v5.0.1版本,低版本可能无法使用)
一、API库
使用组件库:
#include "driver/gptimer.h"
可参考IDF中的gptimr的例程
二、gpio和timer初始化
1.配置GPIO
五线四相步进电机需使用4个gpio口,选用如下,可自定义
#define MOTOR_PIN_A 14
#define MOTOR_PIN_B 27
#define MOTOR_PIN_C 26
#define MOTOR_PIN_D 25
接下来配置初始化gpio,及全局变量:
// 目标步数(剩余步数)
static uint32_t target_step = 0;
// 电机方向
static int8_t direction = -1;
// 定义步进电机的时序(一二相励磁方式)
static const uint8_t step_sequence[] = {
(1<<0)|(0<<1)|(0<<2)|(0<<3), // Step 1
(1<<0)|(1<<1)|(0<<2)|(0<<3), // Step 2
(0<<0)|(1<<1)|(0<<2)|(0<<3), // Step 3
(0<<0)|(1<<1)|(1<<2)|(0<<3), // Step 4
(0<<0)|(0<<1)|(1<<2)|(0<<3),
(0<<0)|(0<<1)|(1<<2)|(1<<3),
(0<<0)|(0<<1)|(0<<2)|(1<<3),
(1<<0)|(0<<1)|(0<<2)|(1<<3)
};
/**
* @brief 步进电机初始化函数,初始化GPIO口
*
*/
void motor_gpio_init()
{
ESP_LOGI(TAG,"configured motor GPIO !\n");
gpio_config_t io_conf;
// 禁用中断
io_conf.intr_type = GPIO_INTR_DISABLE;
// 设置为输出模式
io_conf.mode = GPIO_MODE_OUTPUT;
// 设置输出电平为低电平
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
// 设置输出电平为高电平
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
// 配置GPIO
io_conf.pin_bit_mask = (1ULL << MOTOR_PIN_A) | (1ULL << MOTOR_PIN_B) |
(1ULL << MOTOR_PIN_C) | (1ULL << MOTOR_PIN_D);
gpio_config(&io_conf);
}
2.配置硬件定时器
定时器初始化:
//定时器操作句柄
gptimer_handle_t gptimer = NULL;
void step_timer_init()
{
ESP_LOGI(TAG, "Create timer handle");
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1000000, // 1MHz, 1 tick=1us
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
gptimer_event_callbacks_t cbs = {
.on_alarm = step_timer_inr,
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_LOGI(TAG, "Start timer, stop it at alarm event");
gptimer_alarm_config_t alarm_config1 = {
.reload_count = 0,
.alarm_count = 10000, // period = 10ms
.flags.auto_reload_on_alarm = true,
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config1));
ESP_ERROR_CHECK(gptimer_start(gptimer));
}
定时器中断回调:
static bool IRAM_ATTR step_timer_inr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *args)
{
BaseType_t high_task_awoken = pdFALSE;
// 相序下标
static int8_t phase_index = 0;
switch(direction) {
case 1: { // 顺时针旋转
phase_index--;
if(phase_index < 0) {
phase_index = 7;
}
gpio_set_level(MOTOR_PIN_A, (step_sequence[phase_index] >> 0) & 0x1);
gpio_set_level(MOTOR_PIN_B, (step_sequence[phase_index] >> 1) & 0x1);
gpio_set_level(MOTOR_PIN_C, (step_sequence[phase_index] >> 2) & 0x1);
gpio_set_level(MOTOR_PIN_D, (step_sequence[phase_index] >> 3) & 0x1);
target_step--;
if(target_step <= 0) {
direction = -1;
}
break;
}
case 0: { //逆时针旋转
phase_index++;
if(phase_index >= 8) {
phase_index = 0;
}
gpio_set_level(MOTOR_PIN_A, (step_sequence[phase_index] >> 0) & 0x1);
gpio_set_level(MOTOR_PIN_B, (step_sequence[phase_index] >> 1) & 0x1);
gpio_set_level(MOTOR_PIN_C, (step_sequence[phase_index] >> 2) & 0x1);
gpio_set_level(MOTOR_PIN_D, (step_sequence[phase_index] >> 3) & 0x1);
target_step--;
if(target_step <= 0) {
direction = -1;
}
break;
}
default : break;
}
// return whether we need to yield at the end of ISR
return (high_task_awoken == pdTRUE);
}
三、编写驱动接口
写了两个步进电机控制函数:
// 设置步进电机步数及方向
void step_set_steps(uint32_t step, int8_t dire)
{
// 步数乘以相序节拍
target_step = step * 8;
direction = dire;
}
// 设置步进电机转速 (freq ms)
void step_update_freq(uint16_t freq)
{
ESP_LOGI(TAG, "Stop timer");
ESP_ERROR_CHECK(gptimer_stop(gptimer));
ESP_LOGI(TAG, "Start timer, update alarm value ");
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = freq * 1000, // period = freq ms
.flags.auto_reload_on_alarm = true,
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
ESP_ERROR_CHECK(gptimer_start(gptimer));
}
总结
使用以上程序只能简单驱动电机旋转,实际应用还得根据需求作出调整和优化,现只是简单记录学习成果。
江畔何人初见月?江月何年初照人?