Linux下的ULN2003驱动板与28BYJ-48步进电机驱动移植

一、实验概述

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平台电机控制开发的参考模板。

关键技术要点

  1. 四相八拍励磁时序的精确实现

  2. 高精度定时器确保步进间隔

  3. ioctl接口设计提供灵活控制

  4. 并发保护机制保证稳定性

  5. 设备树GPIO配置与驱动解耦

相关推荐
洛菡夕2 小时前
nginx核心功能
linux·nginx
原来是猿2 小时前
Linux - 基础IO【下】
linux·运维·服务器
xyd陈宇阳3 小时前
面向网络协议初学者的入门指南
linux·运维·网络协议
_DCG_3 小时前
用户态和内核态的区别
linux
肖恭伟3 小时前
QtCreator Linux ubuntu24.04问题集合
linux·windows·qt
兮动人3 小时前
Linux 云服务器部署 OpenClaw 全攻略:从环境搭建到 QQ 机器人集成
linux·服务器·机器人·openclaw
linux修理工3 小时前
使用 nextcloud.occ 重置用户密码
linux·运维·服务器
toradexsh3 小时前
基于 NXP iMX8MP ARM平台安装测试 Openclaw
linux·docker·arm·nxp·openclaw
ZhengEnCi3 小时前
L1C-VMware创建CentOS虚拟机完全指南 🚀
linux·centos