本文章介紹利用Raspberry Pi Pico 微處理器,以GPIO 控制步進馬達(28BYJ-48 與ULN2003馬達驅動器),以LVGL 使用者介面控制馬達的選轉速度、方向與角度。
一、28BYJ-48步進順序
- full step:
- 設定旋轉速度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 )