prettyprint

2024年10月10日 星期四

[Raspberry Pi Pico SDK] Stepper motor driver and its rotation angle, speed and direction controlled by LVGL UI

 本文章介紹利用Raspberry Pi Pico 微處理器,以GPIO 控制步進馬達(28BYJ-48 與ULN2003馬達驅動器),以LVGL 使用者介面控制馬達的選轉速度、方向與角度。

一、28BYJ-48步進順序

  • full step:

相對應的程式碼:


  • half step:



相對應的程式碼:



二、LVGL使用者介面:


  • 設定旋轉速度RPM:1~12
  • 設定步進馬達角度0度位置(set 0 pos button)
  • 旋轉任意角度。
  • 順時針或反時針旋轉360度。
詳細操作過程請參閱成果展示,文末附有詳細程式碼。

三、成果展示



四、程式碼



  • picow_lvgl_step_motor.c(主程式碼)
#include <stdio.h>
#include "pico/stdlib.h"
#include "steppermotor.h"
#include "pico_lvgl.h"

// TFT PIO setting
PIO TFT_PIO = pio0;
#define TFT_SM 0
#define TFT_SDI_GPIO 9
#define TFT_CSX_DCX_SCK_GPIO 6 // CSX=8, DCX=7, SCK=6, SIDE_SET

uint16_t motor_pos=0;  
static uint16_t new_motor_pos = 0;
static uint8_t rpm_value=10;
static lv_obj_t* bar_label;
static lv_obj_t *motor_pos_arc;
static lv_obj_t * arc_label;

void inc_button_event_cb(lv_event_t *e) {
    lv_obj_t * arc = lv_event_get_target(e);
    lv_obj_t * bar = lv_event_get_user_data(e);
    int32_t v = lv_bar_get_value(bar);
    rpm_value=v+1;
    if (rpm_value > 12) rpm_value=12;
    lv_bar_set_value(bar, rpm_value, LV_ANIM_ON);
    lv_label_set_text_fmt(bar_label, "%d", rpm_value);
    stepperMotor_set_speed(rpm_value);
}

void dec_button_event_cb(lv_event_t *e) {
    lv_obj_t * arc = lv_event_get_target(e);
    lv_obj_t * bar = lv_event_get_user_data(e);
    int32_t v = lv_bar_get_value(bar);
    rpm_value=v-1;
    if (rpm_value <=0) rpm_value=1;
    lv_bar_set_value(bar, rpm_value, LV_ANIM_ON);
    lv_label_set_text_fmt(bar_label, "%d", rpm_value);
    stepperMotor_set_speed(rpm_value);
}

void onc_revo_c_event_cb(lv_event_t *e) {
    stepperMotor_rotate_angle(360, true);
}

void onc_revo_cc_event_cb(lv_event_t *e) {
    stepperMotor_rotate_angle(360, false);
}

void set_pos_zero_event_cb(lv_event_t *e) {
    new_motor_pos=motor_pos=0;
    lv_arc_set_value(motor_pos_arc, 0);
    lv_label_set_text_fmt(arc_label,"%d  \xC2\xB0",0);
}

static void motor_pos_changed_event_cb(lv_event_t * e)
{  
    lv_obj_t * arc = lv_event_get_target(e);
    lv_obj_t * label = lv_event_get_user_data(e);
    new_motor_pos=lv_arc_get_value(arc);
    lv_label_set_text_fmt(label, "%d \xC2\xB0", new_motor_pos);    
}

static void bar_value_event_cb(lv_event_t * e)
{
    lv_obj_t * bar = lv_event_get_target(e);
    lv_obj_t * label = lv_event_get_user_data(e);
    lv_label_set_text_fmt(label, "%d", lv_bar_get_value(bar));
}

void setp_motor_control_pad() {
    motor_pos_arc = lv_arc_create(lv_scr_act());
    arc_label = lv_label_create(motor_pos_arc);
    lv_obj_set_style_text_font(arc_label, &lv_font_montserrat_18, 0);
    lv_obj_center(arc_label);
    lv_obj_set_style_bg_color(motor_pos_arc, lv_color_hex(0xff0000), LV_PART_KNOB);
    lv_obj_set_size(motor_pos_arc, 200,200);
    lv_obj_center(motor_pos_arc);
    lv_arc_set_bg_angles(motor_pos_arc, 0, 360);
    lv_arc_set_range(motor_pos_arc, 0, 360);
    lv_arc_set_rotation(motor_pos_arc, 270);
    lv_arc_set_value(motor_pos_arc, 0);
    lv_obj_add_event_cb(motor_pos_arc, motor_pos_changed_event_cb, LV_EVENT_VALUE_CHANGED, arc_label);

    lv_obj_t *rpm = lv_obj_create(lv_scr_act());
    lv_obj_set_style_pad_all(rpm, 1, 0);
    lv_obj_set_size(rpm, 100, 300);
    lv_obj_align(rpm, LV_ALIGN_TOP_LEFT, 10,10);
    lv_obj_t *rpm_bar = lv_bar_create(rpm);
    bar_label = lv_label_create(rpm_bar);
    lv_obj_center(bar_label);
    lv_obj_set_style_text_color(bar_label, lv_color_white(), 0);
    lv_obj_add_event_cb(rpm_bar, bar_value_event_cb, LV_EVENT_VALUE_CHANGED, bar_label);
    lv_obj_set_size(rpm_bar, 30, 180);
    lv_obj_align(rpm_bar, LV_ALIGN_TOP_MID, 0, 30);
    lv_bar_set_range(rpm_bar, 0, 12);
    lv_bar_set_value(rpm_bar, rpm_value, LV_ANIM_ON);
    lv_obj_t* rpm_label = lv_label_create(rpm);
    
    lv_obj_set_size(rpm_label, 40,40);
    lv_obj_align(rpm_label, LV_ALIGN_TOP_MID, 0,0);
    lv_label_set_text(rpm_label, "RPM");
    lv_obj_t *inc_button = lv_btn_create(rpm);
    lv_obj_t *dec_button = lv_btn_create(rpm);
    lv_obj_align(inc_button, LV_ALIGN_BOTTOM_MID, +20, -5);
    lv_obj_align(dec_button, LV_ALIGN_BOTTOM_MID, -20, -5);
    lv_obj_set_size(inc_button, 36,36);
    lv_obj_set_size(dec_button, 36,36);
    lv_obj_t *btn_label = lv_label_create(inc_button);
    lv_obj_center(btn_label);
    lv_label_set_text(btn_label, LV_SYMBOL_PLUS);
    btn_label = lv_label_create(dec_button);
    lv_obj_center(btn_label);
    lv_label_set_text(btn_label, LV_SYMBOL_MINUS);

    lv_obj_add_event_cb(inc_button, inc_button_event_cb, LV_EVENT_CLICKED, rpm_bar);
    lv_obj_add_event_cb(dec_button, dec_button_event_cb, LV_EVENT_CLICKED, rpm_bar);

    lv_event_send(motor_pos_arc, LV_EVENT_VALUE_CHANGED, NULL);
    lv_event_send(rpm_bar, LV_EVENT_VALUE_CHANGED, NULL);

    lv_obj_t *one_revo_c = lv_btn_create(lv_scr_act());
    lv_obj_t *one_revo_cc = lv_btn_create(lv_scr_act());
    lv_obj_t *set_pos_zero = lv_btn_create(lv_scr_act());
    lv_obj_set_size(one_revo_c, 100, 50);
    lv_obj_set_size(one_revo_cc, 100, 50);
    lv_obj_set_size(set_pos_zero, 100, 50);
    lv_obj_align(one_revo_c, LV_ALIGN_TOP_RIGHT, -10, 10);
    lv_obj_align(one_revo_cc, LV_ALIGN_TOP_RIGHT, -10, 100);
    lv_obj_align(set_pos_zero, LV_ALIGN_BOTTOM_RIGHT, -10, -10);
    btn_label = lv_label_create(one_revo_c);
    lv_label_set_text(btn_label, "Clockwise");
    lv_obj_center(btn_label);
    btn_label = lv_label_create(one_revo_cc);
    lv_label_set_text(btn_label, "CounterClock");
    lv_obj_center(btn_label);
    btn_label = lv_label_create(set_pos_zero);
    lv_label_set_text(btn_label, "set 0 pos");
    lv_obj_center(btn_label);
    lv_obj_add_event_cb(one_revo_c, onc_revo_c_event_cb, LV_EVENT_CLICKED, rpm_bar);
    lv_obj_add_event_cb(one_revo_cc, onc_revo_cc_event_cb, LV_EVENT_CLICKED, rpm_bar);
    lv_obj_add_event_cb(set_pos_zero, set_pos_zero_event_cb, LV_EVENT_CLICKED, rpm_bar);
}


int main()
{
    stdio_init_all();
    stepperMotor_init(18,19,20,21); //No need to use contiguous GPIO pins
    stepperMotor_set_speed(rpm_value);
    stepperMotor_rotate_angle(360, true);  

    pico_lvgl_tft_init(TFT_PIO, TFT_SM, TFT_SDI_GPIO, TFT_CSX_DCX_SCK_GPIO);
    tft_set_orientation(TFT_ORIENTATION_LANDSCAPE);
   
    pico_lvgl_display_init(5);
    pico_lvgl_xpt2046_init();
    setp_motor_control_pad();

    while (1) {
        lv_timer_handler();
        sleep_ms(5);

        if (new_motor_pos > motor_pos) {
            stepperMotor_rotate_angle(new_motor_pos - motor_pos, true);
        } else {
            stepperMotor_rotate_angle(motor_pos-new_motor_pos, false);
        }
        if (new_motor_pos != motor_pos)
            motor_pos = new_motor_pos; 
  
    }    
}

  

  • CMakeLists.txt
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)

# == DO NEVER EDIT THE NEXT LINES for Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
    set(USERHOME $ENV{USERPROFILE})
else()
    set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.0.0)
set(toolchainVersion 13_2_Rel1)
set(picotoolVersion 2.0.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
    include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico_w CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

project(picow_lvgl_step_motor C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(picow_lvgl_step_motor picow_lvgl_step_motor.c )

pico_set_program_name(picow_lvgl_step_motor "picow_lvgl_step_motor")
pico_set_program_version(picow_lvgl_step_motor "0.1")

# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(picow_lvgl_step_motor 1)
pico_enable_stdio_usb(picow_lvgl_step_motor 0)

# Add the standard library to the build
target_link_libraries(picow_lvgl_step_motor
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(picow_lvgl_step_motor PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

# Add any user requested libraries
add_subdirectory(GPIO_StepperMotor)
target_link_libraries(picow_lvgl_step_motor
    gpio_stepper_motor
)
add_subdirectory(pico_lvgl)
target_link_libraries(picow_lvgl_step_motor
    pico_lvgl
)

pico_add_extra_outputs(picow_lvgl_step_motor)


  

stepper motor gpio driver code:

  • steppermotor.c

#include <stdio.h>
#include "pico/stdlib.h"
#include "steppermotor.h"

#define MAX_SPEED_RPM               12
#define MIN_SPEED_RPM               0.1 

#define STEPS_PER_REVOLUTION_FULL     2048  // 28BYJ-48 STEPPER MOTOR
#define STEPS_PER_REVOLUTION_HALF     4096
#define SETP_MOTOR_MIN_SPEED_DELAY_US 2500
#define STEP_MOTOR_TYPE               3        // half or full step
static uint32_t step_seq[8]= {0,0,0,0,0,0,0,0};  // full step :4, half step :8
static int8_t step_pos=0;
static uint32_t stepMotor_step_delay=SETP_MOTOR_MIN_SPEED_DELAY_US;
static uint32_t stepMotor_pins;
static uint16_t stepsPerRevolution;
static uint8_t totalSteps=4;

void stepperMotor_init(uint8_t in1,uint8_t in2,uint8_t in3,uint8_t in4) {
    stepMotor_pins = 1<<in1 | 1<<in2 | 1 << in3 | 1<< in4;
    gpio_init_mask(stepMotor_pins);
    gpio_set_dir_masked(stepMotor_pins, stepMotor_pins);
    #if STEP_MOTOR_TYPE == 1
        step_seq[0] = 1 << in1;
        step_seq[1] = 1 << in2;
        step_seq[2] = 1 << in3;
        step_seq[3] = 1 << in4;
        totalSteps=4;
        stepsPerRevolution=STEPS_PER_REVOLUTION_FULL;
    #elif STEP_MOTOR_TYPE == 2
        step_seq[0] = 1 << in1 | 1 << in2;
        step_seq[1] = 1 << in2 | 1 << in3;
        step_seq[2] = 1 << in3 | 1 << in4;
        step_seq[3] = 1 << in4 | 1 << in1;
        totalSteps=4;
        stepsPerRevolution=STEPS_PER_REVOLUTION_FULL;
    #elif STEP_MOTOR_TYPE == 3
        step_seq[0] = 1 << in1;
        step_seq[1] = 1 << in1 | 1 << in2;
        step_seq[2] = 1 << in2;
        step_seq[3] = 1 << in2 | 1 << in3;
        step_seq[4] = 1 << in3;
        step_seq[5] = 1 << in3 | 1 << in4;
        step_seq[6] = 1 << in4;
        step_seq[7] = 1 << in4 | 1 << in1;
        totalSteps=8;
        stepsPerRevolution=STEPS_PER_REVOLUTION_HALF;
    #else 
        #error STEP_MOTOR_TORGUE value only 1,2 or 3.
    #endif 
    gpio_put_masked(stepMotor_pins, step_seq[step_pos]);
    

}

/* speed range: 0.1~12 rpm*/
void stepperMotor_set_speed(float rpm) {
    if (rpm < MIN_SPEED_RPM) rpm = MIN_SPEED_RPM;
    if (rpm > MAX_SPEED_RPM) rpm = MAX_SPEED_RPM;
    stepMotor_step_delay = 60*1000*1000/(stepsPerRevolution*rpm);
}

void stepperMotor_rotate_angle(uint16_t angle, bool clockwise) {
    uint16_t steps = angle*stepsPerRevolution/360;  // angle to steps
    if (clockwise) {
        for (int i=0; i < steps; i++) {
            step_pos = (++step_pos)%totalSteps;
            gpio_put_masked(stepMotor_pins, step_seq[step_pos]);
            sleep_us(stepMotor_step_delay);
        }
    } else {
        for (int i=0; i < steps; i++) {
            step_pos = (--step_pos+totalSteps)%totalSteps;
            gpio_put_masked(stepMotor_pins, step_seq[step_pos]);
            sleep_us(stepMotor_step_delay);
   
        }
        
    } 
    //gpio_put_masked(stepMotor_pins, 0);
}


  
  • stepermotor.h
#ifndef _STEP_MOTOR_
#define _STEP_MOTOR_
void stepperMotor_init(uint8_t in1,uint8_t in2,uint8_t in3,uint8_t in4);
void stepperMotor_rotate_angle(uint16_t angle, bool clockwise);
void stepperMotor_set_speed(float rpm);

#endif //_STEP_MOTOR
  
  • CMakeLists.txt
add_library(gpio_stepper_motor INTERFACE)

target_sources(gpio_stepper_motor INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/steppermotor.c
)

target_include_directories(gpio_stepper_motor INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(gpio_stepper_motor INTERFACE
        pico_stdlib
)