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配置与驱动解耦

相关推荐
张小姐的猫4 分钟前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生30 分钟前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.112 小时前
linux基础IO
linux·运维·服务器
BelongPanda4 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯4 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_777998344 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器
yunwei374 小时前
eBPF 教程:检查 exec 后真正安装的可执行镜像
linux·开源
笑锝没心没肺5 小时前
yum update 报错:GPG key retrieval failed: [Errno 14] HTTPS Error 404 - Not Found
linux·运维·服务器
AAA@峥5 小时前
CentOS Stream8 基于 Packstack 搭建 OpenStack 云平台全流程实战
linux·centos·openstack
汤永红5 小时前
gtk-x11-2.0.so.0
linux·运维·服务器