本文章介紹利用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
)