一、实验概述
1.1 实验背景
本实验将进一步探索RK3568平台的电机控制领域。通过移植ULN2003驱动板和28BYJ-48步进电机驱动,掌握GPIO输出控制、PWM调速等关键技术。
1.2 硬件环境
-
主控芯片:RK3568(ARM64架构,Linux5.10内核)
-
驱动芯片:ULN2003A(达林顿晶体管阵列,500mA驱动能力)
-
步进电机:28BYJ-48(5V四相五线步进电机,减速比1:64,步进角5.625°/64)
-
硬件接线:
|-----------|----------|-----------|--------|
| ULN2003引脚 | RK3568引脚 | GPIO号(参考) | 功能说明 |
| IN1 | GPIO3_B0 | gpio-88 | 电机A相控制 |
| IN2 | GPIO3_C5 | gpio-101 | 电机B相控制 |
| IN3 | GPIO0_C0 | gpio-8 | 电机C相控制 |
| IN4 | GPIO3_B6 | gpio-94 | 电机D相控制 |
| VCC | 5V电源 | - | 电机驱动电源 |
| GND | GND | - | 共地连接 |
注意事项:
-
ULN2003的COM端接5V(内部续流二极管公共端)
-
确保电源能够提供至少500mA电流
-
信号线可串联100Ω电阻限流保护
1.3 ULN2003驱动原理
ULN2003是高压大电流达林顿晶体管阵列,每个通道可提供500mA电流,内置续流二极管适合驱动感性负载。在步进电机驱动中,它充当功率放大器,将RK3568的GPIO信号(3.3V)转换为电机所需的电流驱动。
1.4 28BYJ-48电机特性
根据规格书,28BYJ-48主要参数如下:
-
相数:4相
-
驱动方式:1-2相励磁(四相八拍)
-
步进角:5.625°/64(经减速后输出轴步进角)
-
减速比:1/64
-
电压:5VDC
-
线圈电阻:200Ω±7%/相
计算公式:
-
电机转一圈所需脉冲数:64×64=409664×64=4096个脉冲
-
输出轴转速(rpm)与脉冲频率(pps)关系:n=f×604096n =4096f×60
二、设备树配置
2.1 设备树文件修改路径
cd /home/alientek/rk3568_linux5.10_sdk/kernel
vim arch/arm64/boot/dts/rockchip/rk3568-atk-evb1-ddr4-v10.dtsi
2.2 添加GPIO引脚配置
// 在根节点下添加步进电机节点
stepper_motor: stepper-motor {
compatible = "atk,stepper-motor-uln2003";
pinctrl-names = "default";
pinctrl-0 = <&stepper_motor_pins>;
// 定义四相控制GPIO,顺序对应 IN1~IN4
gpios = <&gpio3 RK_PB0 GPIO_ACTIVE_HIGH>, // IN1: GPIO3_B0
<&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>, // IN2: GPIO3_C5
<&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>, // IN3: GPIO0_C0
<&gpio3 RK_PB6 GPIO_ACTIVE_HIGH>; // IN4: GPIO3_B6
// 电机参数(可选)
steps-per-revolution = <4096>; // 完整一圈所需步数
status = "okay";
};
2.3 配置pinctrl引脚复用
// 在pinctrl节点内添加
&pinctrl {
stepper_motor {
stepper_motor_pins: stepper-motor-pins {
rockchip,pins =
<3 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>, // GPIO3_B0
<3 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>, // GPIO3_C5
<0 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>, // GPIO0_C0
<3 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; // GPIO3_B6
};
};
};
2.4 确保GPIO控制器已启用
&gpio0 {
status = "okay";
};
&gpio3 {
status = "okay";
};
设备树修改成功
三、步进电机驱动开发
驱动代码与引脚无关,使用设备树中获取的GPIO即可。以下为完整驱动代码(与之前相同,但确保compatible字符串与设备树匹配)。
3.1 完整驱动代码
cpp
/*
* RK3568 ULN2003 步进电机驱动
* 文件名: stepper_motor_driver.c
* 支持28BYJ-48四相五线步进电机,四相八拍驱动模式
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/hrtimer.h>
#define DRIVER_NAME "stepper-motor"
#define DEVICE_NAME "stepper"
#define CLASS_NAME "motor"
/* 四相八拍励磁时序表 (1-2相励磁) */
static const int step_sequence[8][4] = {
{1, 0, 0, 0}, // A
{1, 1, 0, 0}, // AB
{0, 1, 0, 0}, // B
{0, 1, 1, 0}, // BC
{0, 0, 1, 0}, // C
{0, 0, 1, 1}, // CD
{0, 0, 0, 1}, // D
{1, 0, 0, 1} // DA
};
/* 电机控制命令 (ioctl) */
#define MOTOR_IOC_MAGIC 'M'
#define MOTOR_SET_DIRECTION _IOW(MOTOR_IOC_MAGIC, 1, int) // 设置方向: 0正转 1反转
#define MOTOR_SET_SPEED _IOW(MOTOR_IOC_MAGIC, 2, int) // 设置速度: 脉冲间隔(us)
#define MOTOR_SET_STEPS _IOW(MOTOR_IOC_MAGIC, 3, int) // 设置步数: 正数正转,负数反转
#define MOTOR_START _IO(MOTOR_IOC_MAGIC, 4) // 启动运动
#define MOTOR_STOP _IO(MOTOR_IOC_MAGIC, 5) // 停止运动
#define MOTOR_GET_STATUS _IOR(MOTOR_IOC_MAGIC, 6, int) // 获取状态
/* 电机设备结构体 */
struct motor_device {
struct platform_device *pdev;
struct cdev cdev;
struct device *device;
dev_t devno;
/* GPIO资源 */
int gpios[4];
/* 运动控制参数 */
int direction; // 当前方向: 0正转 1反转
int step_index; // 当前步序索引 (0-7)
int target_steps; // 目标步数 (0表示连续旋转)
int current_steps; // 已走步数
int step_interval; // 步脉冲间隔(us)
/* 定时器和同步 */
struct hrtimer timer; // 高精度定时器
spinlock_t lock; // 自旋锁保护共享数据
bool running; // 是否在运行
bool stop_flag; // 停止标志
};
static struct class *motor_class = NULL;
static int motor_major = 0;
/* 设置电机相位 (根据当前步序) */
static void motor_set_phase(struct motor_device *motor)
{
int i;
int idx = motor->step_index;
for (i = 0; i < 4; i++) {
gpio_set_value(motor->gpios[i], step_sequence[idx][i]);
}
}
/* 定时器回调函数 - 产生步进脉冲 */
static enum hrtimer_restart motor_timer_callback(struct hrtimer *timer)
{
struct motor_device *motor = container_of(timer, struct motor_device, timer);
unsigned long flags;
bool need_restart = false;
spin_lock_irqsave(&motor->lock, flags);
if (!motor->running || motor->stop_flag) {
spin_unlock_irqrestore(&motor->lock, flags);
return HRTIMER_NORESTART;
}
/* 更新步序索引 (根据方向) */
if (motor->direction == 0) {
motor->step_index = (motor->step_index + 1) % 8;
} else {
motor->step_index = (motor->step_index + 7) % 8; // 反向
}
/* 设置输出相位 */
motor_set_phase(motor);
/* 更新已走步数 */
if (motor->target_steps > 0) {
motor->current_steps++;
/* 检查是否达到目标步数 */
if (motor->current_steps >= motor->target_steps) {
motor->running = false;
dev_dbg(&motor->pdev->dev, "Target steps reached: %d\n",
motor->target_steps);
spin_unlock_irqrestore(&motor->lock, flags);
return HRTIMER_NORESTART;
}
need_restart = true;
} else {
/* 连续旋转模式 */
need_restart = true;
}
spin_unlock_irqrestore(&motor->lock, flags);
/* 重启定时器 */
if (need_restart) {
hrtimer_forward_now(timer, ns_to_ktime(motor->step_interval * 1000));
return HRTIMER_RESTART;
}
return HRTIMER_NORESTART;
}
/* 启动电机 */
static void motor_start(struct motor_device *motor)
{
unsigned long flags;
spin_lock_irqsave(&motor->lock, flags);
if (motor->running) {
spin_unlock_irqrestore(&motor->lock, flags);
return;
}
motor->running = true;
motor->stop_flag = false;
motor->current_steps = 0;
/* 设置初始相位 */
motor_set_phase(motor);
/* 启动定时器 */
hrtimer_start(&motor->timer,
ns_to_ktime(motor->step_interval * 1000),
HRTIMER_MODE_REL);
spin_unlock_irqrestore(&motor->lock, flags);
dev_info(&motor->pdev->dev, "Motor started, direction=%d, interval=%d us\n",
motor->direction, motor->step_interval);
}
/* 停止电机 */
static void motor_stop(struct motor_device *motor)
{
unsigned long flags;
spin_lock_irqsave(&motor->lock, flags);
motor->running = false;
motor->stop_flag = true;
/* 关闭所有相位 */
gpio_set_value(motor->gpios[0], 0);
gpio_set_value(motor->gpios[1], 0);
gpio_set_value(motor->gpios[2], 0);
gpio_set_value(motor->gpios[3], 0);
spin_unlock_irqrestore(&motor->lock, flags);
/* 等待定时器停止 */
hrtimer_cancel(&motor->timer);
dev_info(&motor->pdev->dev, "Motor stopped\n");
}
/* 字符设备文件操作: open */
static int motor_open(struct inode *inode, struct file *file)
{
struct motor_device *motor = container_of(inode->i_cdev,
struct motor_device, cdev);
file->private_data = motor;
return 0;
}
/* 字符设备文件操作: release */
static int motor_release(struct inode *inode, struct file *file)
{
struct motor_device *motor = file->private_data;
/* 确保电机停止 */
motor_stop(motor);
return 0;
}
/* 字符设备文件操作: ioctl */
static long motor_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct motor_device *motor = file->private_data;
int value, ret = 0;
unsigned long flags;
switch (cmd) {
case MOTOR_SET_DIRECTION:
ret = get_user(value, (int __user *)arg);
if (ret)
return -EFAULT;
if (value != 0 && value != 1)
return -EINVAL;
spin_lock_irqsave(&motor->lock, flags);
motor->direction = value;
spin_unlock_irqrestore(&motor->lock, flags);
dev_info(&motor->pdev->dev, "Direction set to %s\n",
value ? "reverse" : "forward");
break;
case MOTOR_SET_SPEED:
ret = get_user(value, (int __user *)arg);
if (ret)
return -EFAULT;
if (value < 500 || value > 50000) // 限制范围: 500us ~ 50ms
return -EINVAL;
spin_lock_irqsave(&motor->lock, flags);
motor->step_interval = value;
spin_unlock_irqrestore(&motor->lock, flags);
dev_info(&motor->pdev->dev, "Speed set to %d us/step\n", value);
break;
case MOTOR_SET_STEPS:
ret = get_user(value, (int __user *)arg);
if (ret)
return -EFAULT;
spin_lock_irqsave(&motor->lock, flags);
if (value > 0) {
motor->direction = 0;
motor->target_steps = value;
} else if (value < 0) {
motor->direction = 1;
motor->target_steps = -value;
} else {
motor->target_steps = 0; // 连续旋转
}
spin_unlock_irqrestore(&motor->lock, flags);
dev_info(&motor->pdev->dev, "Target steps set to %d\n", value);
break;
case MOTOR_START:
motor_start(motor);
break;
case MOTOR_STOP:
motor_stop(motor);
break;
case MOTOR_GET_STATUS:
value = motor->running ? 1 : 0;
ret = put_user(value, (int __user *)arg);
break;
default:
return -EINVAL;
}
return ret;
}
/* 文件操作结构体 */
static const struct file_operations motor_fops = {
.owner = THIS_MODULE,
.open = motor_open,
.release = motor_release,
.unlocked_ioctl = motor_ioctl,
};
/* probe函数: 设备匹配时调用 */
static int motor_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct motor_device *motor;
enum of_gpio_flags flags;
int ret, i;
dev_info(&pdev->dev, "Stepper motor probe start\n");
/* 分配设备结构体内存 */
motor = devm_kzalloc(&pdev->dev, sizeof(struct motor_device), GFP_KERNEL);
if (!motor)
return -ENOMEM;
motor->pdev = pdev;
platform_set_drvdata(pdev, motor);
/* 1. 从设备树获取GPIO */
for (i = 0; i < 4; i++) {
motor->gpios[i] = of_get_named_gpio_flags(node, "gpios", i, &flags);
if (!gpio_is_valid(motor->gpios[i])) {
dev_err(&pdev->dev, "Invalid GPIO %d\n", i);
return -EINVAL;
}
/* 申请GPIO并初始化输出为低电平 */
ret = gpio_request_one(motor->gpios[i], GPIOF_OUT_INIT_LOW,
devm_kasprintf(&pdev->dev, GFP_KERNEL,
"motor-phase%d", i));
if (ret < 0) {
dev_err(&pdev->dev, "Failed to request GPIO %d\n", motor->gpios[i]);
goto fail_gpio;
}
}
/* 2. 初始化电机参数 */
motor->direction = 0;
motor->step_index = 0;
motor->target_steps = 0;
motor->step_interval = 2000; // 默认2ms/步 (约73rpm)
motor->running = false;
motor->stop_flag = false;
/* 3. 初始化同步机制 */
spin_lock_init(&motor->lock);
hrtimer_init(&motor->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
motor->timer.function = motor_timer_callback;
/* 4. 分配设备号 */
if (motor_major) {
motor->devno = MKDEV(motor_major, 0);
ret = register_chrdev_region(motor->devno, 1, DEVICE_NAME);
} else {
ret = alloc_chrdev_region(&motor->devno, 0, 1, DEVICE_NAME);
motor_major = MAJOR(motor->devno);
}
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate devno\n");
goto fail_gpio;
}
/* 5. 初始化字符设备 */
cdev_init(&motor->cdev, &motor_fops);
motor->cdev.owner = THIS_MODULE;
ret = cdev_add(&motor->cdev, motor->devno, 1);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to add cdev\n");
goto fail_region;
}
/* 6. 创建设备类 (如果不存在) */
if (!motor_class) {
motor_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(motor_class)) {
ret = PTR_ERR(motor_class);
dev_err(&pdev->dev, "Failed to create class\n");
goto fail_cdev;
}
}
/* 7. 创建设备节点 */
motor->device = device_create(motor_class, &pdev->dev,
motor->devno, NULL, DEVICE_NAME);
if (IS_ERR(motor->device)) {
ret = PTR_ERR(motor->device);
dev_err(&pdev->dev, "Failed to create device\n");
goto fail_class;
}
dev_info(&pdev->dev, "Stepper motor driver probed successfully\n");
dev_info(&pdev->dev, "Device node: /dev/%s\n", DEVICE_NAME);
return 0;
fail_class:
if (motor_class) {
class_destroy(motor_class);
motor_class = NULL;
}
fail_cdev:
cdev_del(&motor->cdev);
fail_region:
unregister_chrdev_region(motor->devno, 1);
fail_gpio:
for (i--; i >= 0; i--)
gpio_free(motor->gpios[i]);
return ret;
}
/* remove函数: 设备移除时调用 */
static int motor_remove(struct platform_device *pdev)
{
struct motor_device *motor = platform_get_drvdata(pdev);
int i;
/* 停止电机 */
motor_stop(motor);
/* 释放设备节点 */
if (motor->device)
device_destroy(motor_class, motor->devno);
/* 删除字符设备 */
cdev_del(&motor->cdev);
/* 注销设备号 */
unregister_chrdev_region(motor->devno, 1);
/* 释放GPIO */
for (i = 0; i < 4; i++) {
if (gpio_is_valid(motor->gpios[i]))
gpio_free(motor->gpios[i]);
}
dev_info(&pdev->dev, "Stepper motor driver removed\n");
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id motor_of_match[] = {
{ .compatible = "atk,stepper-motor-uln2003" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, motor_of_match);
/* 平台驱动结构体 */
static struct platform_driver motor_driver = {
.probe = motor_probe,
.remove = motor_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = motor_of_match,
.owner = THIS_MODULE,
},
};
module_platform_driver(motor_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("RK3568 ULN2003 Stepper Motor Driver for 28BYJ-48");
MODULE_VERSION("1.0");
3.2 驱动编译配置
将驱动文件命名为stepper_motor_driver.c,放入drivers/misc/目录:
cpp
cd /home/alientek/rk3568_linux5.10_sdk/kernel/drivers/misc
cp /path/to/stepper_motor_driver.c .
修改drivers/misc/Kconfig,添加菜单项:
cpp
vim Kconfig
# 在文件末尾添加
cpp
config STEPPER_MOTOR
tristate "ULN2003 Stepper Motor Driver for 28BYJ-48"
depends on OF_GPIO
help
This driver supports 28BYJ-48 stepper motor with ULN2003 driver board.
It provides ioctl interface for direction, speed and step control.

修改drivers/misc/Makefile,添加编译项:
cpp
vim Makefile
# 添加
obj-$(CONFIG_STEPPER_MOTOR) += stepper_motor_driver.o
3.3 内核配置
cpp
cd /home/alientek/rk3568_linux5.10_sdk/kernel
export ARCH=arm64
export CROSS_COMPILE=/home/alientek/rk3568_linux5.10_sdk/buildroot/output/rockchip_atk_dlrk3568/host/bin/aarch64-buildroot-linux-gnu-
make menuconfig
在menuconfig中按路径:Device Drivers → Misc devices → ULN2003 Stepper Motor Driver for 28BYJ-48 勾选为*(编译进内核)。
或者在.config中直接添加:
cpp
echo "CONFIG_STEPPER_MOTOR=y" >> .config
3.4 保存配置并编译
cpp
# 将配置同步到默认配置文件
cp .config arch/arm64/configs/rockchip_linux_defconfig
# 编译内核
cd /home/alientek/rk3568_linux5.10_sdk
./build.sh kernel
# 烧写boot镜像
sudo ./rkflash.sh boot


验证
3.6验证步骤
完成内核编译烧写后,请按以下步骤验证驱动是否正常工作:
检查驱动加载日志
cpp
dmesg | grep stepper
期望输出:
cpp
[ 5.123456] stepper-motor stepper-motor: Stepper motor probe start
[ 5.123789] stepper-motor stepper-motor: GPIO 88 requested
[ 5.124012] stepper-motor stepper-motor: GPIO 101 requested
[ 5.124234] stepper-motor stepper-motor: GPIO 8 requested
[ 5.124456] stepper-motor stepper-motor: GPIO 94 requested
[ 5.125678] stepper-motor stepper-motor: Stepper motor driver probed successfully
[ 5.125901] stepper-motor stepper-motor: Device node: /dev/stepper
如果没有出现probe success,说明设备树匹配或GPIO申请失败。
3.6.1 处理 GPIO 冲突(可选)
冲突信息表明 rm500u-modem 驱动(可能是 4G/5G 模块)也试图使用 GPIO3_B0。若您的系统不需要该模块,可将其在设备树中禁用;若需要,则需修改其 GPIO 引脚配置。
查找冲突节点:
grep -r "rm500u-modem" /home/alientek/rk3568_linux5.10_sdk/kernel/arch/arm64/boot/dts/rockchip/
找到对应的设备树文件,查看其 gpios 属性,修改为其他未使用的引脚,或添加 status = "disabled"; 禁用该节点。
示例修改(如不需要该模块):
dts
&rm500u_modem { status = "disabled";};
修改后重新编译内核烧写,冲突消失。
检查设备节点
cpp
ls -l /dev/stepper
应显示:
cpp
crw------- 1 root root 242, 0 Mar 9 10:00 /dev/stepper
主设备号(242)可能不同,只要有节点即可。

查看GPIO调试信息
如果内核启用了CONFIG_DEBUG_FS,可以查看GPIO状态:
cpp
mount -t debugfs none /sys/kernel/debug
cat /sys/kernel/debug/gpio | grep motor
应看到四个motor-phaseX的GPIO条目,初始状态为lo(低电平)。

手动测试单个引脚
可以通过操作/sys/class/gpio临时测试每个引脚(需知道GPIO编号):
cpp
# 以GPIO3_B0为例(编号88)
echo 88 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio88/direction
echo 1 > /sys/class/gpio/gpio88/value # 拉高
echo 0 > /sys/class/gpio/gpio88/value # 拉低
若手边有LED或万用表,可观察对应引脚电平变化。测试完记得echo 88 > /sys/class/gpio/unexport。
如果面试中被问到这个问题,可以这样回答:
"通过
sysfs导出 GPIO 后,内核会为该 GPIO 分配资源并创建文件接口。如果不执行unexport,该 GPIO 会一直处于被占用状态,导致其他进程或驱动无法使用,也无法重新导出。这是一种资源泄漏,但系统重启后会释放。因此,测试完成后应及时unexport以释放资源。"
同时可以补充说明 export/unexport 的内部机制,展示对 gpiolib 的理解。
6.5 运行测试程序
编译并运行测试程序,观察电机是否转动:
adb传输可执行文件

cpp
# 编译测试程序(若未编译)
cd /home/alientek/rk3568_linux5.10_sdk
source buildroot/output/rockchip_atk_dlrk3568/host/environment-setup
aarch64-buildroot-linux-gnu-gcc motor_test.c -o motor_test
# 将motor_test拷贝到开发板并执行
./motor_test /dev/stepper fwd 2000
电机应开始正转。用stop命令停止。

驱动能够正确响应 ioctl 命令,基本功能正常。但程序执行时间太短,无法观察到电机转动。
6.6 排查常见问题
-
无probe日志 :检查设备树
compatible是否与驱动匹配(应为"atk,stepper-motor-uln2003")。 -
GPIO 申请失败 :可能引脚被其他驱动占用,检查
dmesg | grep gpio。 -
设备节点未创建 :检查
/sys/class/motor目录是否存在,驱动加载时是否调用class_create成功。 -
电机不转但 GPIO 有输出:检查ULN2003供电(5V)和接线,确认电机线圈完好。
若以上验证均通过,则驱动移植成功。
四、应用程序测试
4.1 测试程序代码
cpp
/*
* 步进电机测试程序
* 文件名: motor_test.c
* 编译: aarch64-buildroot-linux-gnu-gcc motor_test.c -o motor_test
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#define MOTOR_IOC_MAGIC 'M'
#define MOTOR_SET_DIRECTION _IOW(MOTOR_IOC_MAGIC, 1, int)
#define MOTOR_SET_SPEED _IOW(MOTOR_IOC_MAGIC, 2, int)
#define MOTOR_SET_STEPS _IOW(MOTOR_IOC_MAGIC, 3, int)
#define MOTOR_START _IO(MOTOR_IOC_MAGIC, 4)
#define MOTOR_STOP _IO(MOTOR_IOC_MAGIC, 5)
#define MOTOR_GET_STATUS _IOR(MOTOR_IOC_MAGIC, 6, int)
void print_usage(char *prog)
{
printf("Usage: %s <device> <command> [args]\n", prog);
printf("Commands:\n");
printf(" fwd <speed_us> - 正转连续旋转\n");
printf(" rev <speed_us> - 反转连续旋转\n");
printf(" step <steps> <speed> - 旋转指定步数\n");
printf(" stop - 停止电机\n");
printf(" status - 获取运行状态\n");
printf("\nSpeed range: 500-50000 us/step\n");
printf("Example: %s /dev/stepper fwd 2000\n", prog);
}
int main(int argc, char *argv[])
{
int fd, ret, status;
if (argc < 3) {
print_usage(argv[0]);
return 1;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return 1;
}
if (strcmp(argv[2], "fwd") == 0) {
if (argc < 4) {
print_usage(argv[0]);
close(fd);
return 1;
}
int speed = atoi(argv[3]);
ioctl(fd, MOTOR_SET_DIRECTION, 0);
ioctl(fd, MOTOR_SET_SPEED, speed);
ioctl(fd, MOTOR_SET_STEPS, 0); // 连续旋转
ioctl(fd, MOTOR_START);
printf("Motor forward started at %d us/step\n", speed);
} else if (strcmp(argv[2], "rev") == 0) {
if (argc < 4) {
print_usage(argv[0]);
close(fd);
return 1;
}
int speed = atoi(argv[3]);
ioctl(fd, MOTOR_SET_DIRECTION, 1);
ioctl(fd, MOTOR_SET_SPEED, speed);
ioctl(fd, MOTOR_SET_STEPS, 0); // 连续旋转
ioctl(fd, MOTOR_START);
printf("Motor reverse started at %d us/step\n", speed);
} else if (strcmp(argv[2], "step") == 0) {
if (argc < 5) {
print_usage(argv[0]);
close(fd);
return 1;
}
int steps = atoi(argv[3]);
int speed = atoi(argv[4]);
ioctl(fd, MOTOR_SET_SPEED, speed);
ioctl(fd, MOTOR_SET_STEPS, steps);
ioctl(fd, MOTOR_START);
printf("Motor running %d steps at %d us/step\n", steps, speed);
} else if (strcmp(argv[2], "stop") == 0) {
ioctl(fd, MOTOR_STOP);
printf("Motor stopped\n");
} else if (strcmp(argv[2], "status") == 0) {
ioctl(fd, MOTOR_GET_STATUS, &status);
printf("Motor status: %s\n", status ? "running" : "stopped");
} else {
print_usage(argv[0]);
}
close(fd);
return 0;
}
4.2 编译测试程序
cpp
cd /home/alientek/rk3568_linux5.10_sdk
source buildroot/output/rockchip_atk_dlrk3568/host/environment-setup
aarch64-buildroot-linux-gnu-gcc motor_test.c -o motor_test
4.3 板端测试步骤
将编译好的motor_test拷贝到开发板,执行:
cpp
# 1. 查看驱动加载日志
dmesg | grep stepper
# 2. 检查设备节点
ls -l /dev/stepper
# 3. 正转测试 (2000us/step ≈ 73rpm)
./motor_test /dev/stepper fwd 2000
sleep 10
./motor_test /dev/stepper stop
# 4. 反转测试
./motor_test /dev/stepper rev 2000
sleep 10
./motor_test /dev/stepper stop
# 5. 定步长测试 (旋转1圈 = 4096步)
./motor_test /dev/stepper step 4096 2000
# 6. 加减速测试 (逐步提高速度)
for speed in 5000 3000 2000 1500 1000 800; do
echo "Testing speed: $speed us/step"
./motor_test /dev/stepper step 4096 $speed
sleep 2
done

五、常见问题与解决方案
5.1 电机不转动
|---------|-------------|------------------------|
| 现象 | 可能原因 | 解决方案 |
| 电机无声、不转 | GPIO未正确输出 | 检查dmesg日志,确认GPIO申请成功 |
| | 电源供电不足 | 确保5V电源能提供至少500mA电流 |
| | ULN2003连接错误 | 核对IN1~IN4与GPIO对应关系 |
5.2 电机抖动或失步
|--------|--------|----------------------|
| 现象 | 可能原因 | 解决方案 |
| 电机抖动不转 | 速度过快 | 增大step_interval值降低转速 |
| | 励磁时序错误 | 检查step_sequence数组定义 |
| | 负载过大 | 减小负载或增大驱动电流 |
5.3 编译错误
错误:implicit declaration of function 'hrtimer_init'
解决 :添加头文件 #include <linux/hrtimer.h>
错误:未知字段 'unlocked_ioctl' 在结构体中
解决 :确认内核版本,5.10内核使用unlocked_ioctl而非ioctl
六、扩展实验建议
6.1 添加PWM调速
可结合RK3568的PWM子系统实现更精确的调速:
cpp
pwm15: pwm@fe700000 {
compatible = "rockchip,rk3568-pwm", "rockchip,rk3328-pwm";
reg = <0x0 0xfe700000 0x0 0x10>;
#pwm-cells = <3>;
pinctrl-names = "default";
pinctrl-0 = <&pwm15m0_pins>;
clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>;
clock-names = "pwm", "pclk";
status = "disabled";
};
6.2 添加位置反馈
配合编码器或限位开关实现闭环控制。
6.3 多电机同步控制
扩展驱动支持多个步进电机协同运动。
七、总结
本教程详细介绍了在RK3568平台上移植ULN2003驱动板和28BYJ-48步进电机的完整流程,并针对新指定的GPIO引脚(GPIO3_B0、GPIO3_C5、GPIO0_C0、GPIO3_B6)进行了设备树配置。通过字符设备驱动提供ioctl控制接口,实现了步进电机的正反转、调速和定步长控制。本驱动已在理论验证,可作为RK3568平台电机控制开发的参考模板。
关键技术要点:
-
四相八拍励磁时序的精确实现
-
高精度定时器确保步进间隔
-
ioctl接口设计提供灵活控制
-
并发保护机制保证稳定性
-
设备树GPIO配置与驱动解耦