【RISC-V 全志-V821 PERF2B开发板评测试用试用体验】步进电机驱动
# 【RISC-V 全志-V821 PERF2B开发板评测试用试用体验】步进电机驱动本文介绍了 RISC-V 全志-V821 PERF2B 开发板驱动步进电机的相关测试。
## 介绍
V821 SDK 内置了步进电机驱动,支持驱动两路步进电机。
电路连接如下
!(//m.qjumbo.com/forum/data/attachment/forum/202508/21/111309jbigp4vyl1h9lv99.jpg)
## 驱动配置
### 内核驱动配置
终端执行 `make kernel_menuconfig` 进入内核配置驱动,前往路径
```bash
Allwinner BSP--->
Device Drivers--->
Misc Devices Drivers--->
<*> Allwinner gpio motor 2803 driver
```
!(https://docs.aw-ol.com/assets/images/image-20241230135951568-629341423517d6ad4c59ad35154b60d3.png)
### 设备树配置
步进电机的设备树配置如下,只需要修改对应的 IO 到对应的引脚即可:
```c
/{
gpio-motor@0 {
compatible = "allwinner,gpio-motor";
ab-pin-black = <&pio PD 12 GPIO_ACTIVE_HIGH>;
ab-pin-yellow = <&pio PD 13 GPIO_ACTIVE_HIGH>;
ab-pin-brown = <&pio PD 14 GPIO_ACTIVE_HIGH>;
ab-pin-blue = <&pio PD 15 GPIO_ACTIVE_HIGH>;
cd-pin-black = <&pio PD 16 GPIO_ACTIVE_HIGH>;
cd-pin-yellow = <&pio PD 17 GPIO_ACTIVE_HIGH>;
cd-pin-brown = <&pio PD 18 GPIO_ACTIVE_HIGH>;
cd-pin-blue = <&pio PD 19 GPIO_ACTIVE_HIGH>;
status = "okay";
};
};
```
正常配置后会生成节点 `/dev/sunxi_gpio_motor`
!(https://docs.aw-ol.com/assets/images/image-20241230140227535-22c133958216adc2445b1165b36078b7.png)
## 模块使用
GPIO Motor 支持使用普通文件 I/O 读写或 IOCTL 控制
### 流程图
!(//m.qjumbo.com/forum/data/attachment/forum/202508/21/111338gv2vsg2t8lbd628g.jpg)
### 代码
```c
#include <errno.h>
#include <fcntl.h>
#include <linux/serial.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
/* dir mode define */
#define DIR_UP 0x01
#define DIR_DOWN 0x02
#define DIR_AB_SHIFT 0x00
#define DIR_RIGHT 0x01
#define DIR_LEFT 0x02
#define DIR_CD_SHIFT 0x02
typedef struct sunxi_motor_device_status {
int x; /* X-coordinate step of the motor device */
int y; /* Y-coordinate step of the motor device */
int dir; /* Direction of movement of the motor device */
int status; /* Motor device status */
} sunxi_motor_device_status_t;
typedef struct sunxi_motor_cmd {
int dir; /* Direction of rotation for the motor command */
int ab_step; /* Number of steps to move the motor in up and down dir */
int ab_time_per_step; /* Time duration for each microstep in up and down dir */
int cd_step; /* Number of steps to move the motor in left and right dir */
int cd_time_per_step; /* Time duration for each microstep in left and right dir */
int block; /* block flag for motor commands */
} sunxi_motor_cmd_t;
/* ioctl define */
#define MOTOR_DRV_MAGICNUM 'm'
#define MOTOR_DRV_SET_STOP _IO(MOTOR_DRV_MAGICNUM, 0)
#define MOTOR_DRV_SET_STOP_AB_IO(MOTOR_DRV_MAGICNUM, 1)
#define MOTOR_DRV_SET_STOP_CD_IO(MOTOR_DRV_MAGICNUM, 2)
#define MOTOR_DRV_SET_START _IO(MOTOR_DRV_MAGICNUM, 3)
#define MOTOR_DRV_SET_BLOCK _IOW(MOTOR_DRV_MAGICNUM, 4, int)
#define MOTOR_DRV_SET_PARA _IOW(MOTOR_DRV_MAGICNUM, 5, sunxi_motor_para_t)
#define MOTOR_DRV_SET_CMD _IOW(MOTOR_DRV_MAGICNUM, 6, sunxi_motor_cmd_t)
#define MOTOR_DRV_GET_CMD _IOR(MOTOR_DRV_MAGICNUM, 7, sunxi_motor_cmd_t)
#define MOTOR_DRV_GET_STATUS \
_IOR(MOTOR_DRV_MAGICNUM, 8, sunxi_motor_device_status_t)
#define MOTOR_DRV_SET_STATUS \
_IOR(MOTOR_DRV_MAGICNUM, 9, sunxi_motor_device_status_t)
void show_usage(void)
{
printf("Usage: gpio_motor <mode> <direction> <steps> <time_per_step> <block>\n");
printf(" - mode:\n");
printf(" 0: Control via file IO\n");
printf(" 1: Control via IOCTL\n");
printf(" 2: Terminate non-blocking movement\n");
printf(" 3: Get movement parameters\n");
printf(" - direction:\n");
printf(" 1: DIR_UP\n");
printf(" 2: DIR_DOWN\n");
printf(" 3: DIR_RIGHT\n");
printf(" 4: DIR_LEFT\n");
printf(" 5: DIR_LEFT_UP\n");
printf(" 6: DIR_RIGHT_UP\n");
printf(" 7: DIR_LEFT_DOWN\n");
printf(" 8: DIR_RIGHT_DOWN\n");
printf(" - block:\n");
printf(" 0: Non-blocking mode\n");
printf(" 1: Blocking mode\n");
printf("\nExample:\n");
printf("gpio_motor 0 1 100 1 1\n");
printf(" - Use file system mode\n");
printf(" - Direction: up\n");
printf(" - Move 100 steps\n");
printf(" - Time per step: 1000us\n");
printf(" - Blocking mode\n");
}
int main(int argc, char *argv[])
{
int fd, mode = 0;
sunxi_motor_device_status_t status;
sunxi_motor_cmd_t cmd;
if (argc != 6) {
show_usage();
return 1;
}
mode = atoi(argv);
cmd.ab_step = atoi(argv);
cmd.ab_time_per_step = atoi(argv);
cmd.cd_step = atoi(argv);
cmd.cd_time_per_step = atoi(argv);
cmd.block = atoi(argv);
switch (atoi(argv)) {
case 1:
cmd.dir = DIR_UP << DIR_AB_SHIFT;
break;
case 2:
cmd.dir = DIR_DOWN << DIR_AB_SHIFT;
break;
case 3:
cmd.dir = DIR_RIGHT << DIR_CD_SHIFT;
break;
case 4:
cmd.dir = DIR_LEFT << DIR_CD_SHIFT;
break;
case 5:
cmd.dir = (DIR_LEFT << DIR_CD_SHIFT) | (DIR_UP << DIR_AB_SHIFT);
break;
case 6:
cmd.dir = (DIR_RIGHT << DIR_CD_SHIFT) | (DIR_UP << DIR_AB_SHIFT);
break;
case 7:
cmd.dir = (DIR_LEFT << DIR_CD_SHIFT) | (DIR_DOWN << DIR_AB_SHIFT);
break;
case 8:
cmd.dir = (DIR_RIGHT << DIR_CD_SHIFT) | (DIR_DOWN << DIR_AB_SHIFT);
break;
default:
cmd.dir = 0;
break;
}
fd = open("/dev/sunxi_gpio_motor", O_RDWR);
if (fd == -1) {
perror("Failed to open the device node");
return 1;
}
if (mode == 0) {
if (write(fd, &cmd, sizeof(sunxi_motor_cmd_t)) < 0) {
perror("Failed to write to the device...");
close(fd);
return -1;
}
} else if (mode == 1) {
if (ioctl(fd, MOTOR_DRV_SET_CMD, &cmd) == -1) {
perror("Failed to perform ioctl MOTOR_DRV_SET_DATA");
close(fd);
return errno;
}
} else if (mode == 2) {
if (ioctl(fd, MOTOR_DRV_SET_STOP, NULL) == -1) {
perror("Failed to perform ioctl MOTOR_DRV_GET_DATA");
close(fd);
return errno;
}
} else {
if (read(fd, &status, sizeof(sunxi_motor_device_status_t)) !=
sizeof(sunxi_motor_device_status_t)) {
perror("Failed to read status from the device node");
close(fd);
return 1;
}
printf("x: %d\n", status.x);
printf("y: %d\n", status.y);
printf("dir: %d\n", status.dir);
printf("status: %d\n", status.status);
}
if (read(fd, &status, sizeof(sunxi_motor_device_status_t)) !=
sizeof(sunxi_motor_device_status_t)) {
perror("Failed to read status from the device node");
close(fd);
return 1;
}
printf("x: %d\n", status.x);
printf("y: %d\n", status.y);
printf("dir: %d\n", status.dir);
printf("status: %d\n", status.status);
close(fd);
return 0;
}
```
### 关键结构体与常量
电机运动方向常量 (`DIR_\*`):
- `DIR_UP`、`DIR_DOWN`、`DIR_RIGHT`、`DIR_LEFT` 分别表示电机的基本运动方向。
- `DIR_AB_SHIFT` 和 `DIR_CD_SHIFT` 是用于控制电机在 A/B(垂直方向)和 C/D(水平方向)上的运动位移。
电机状态结构体 (`sunxi_motor_device_status_t`):
- 保存电机的状态信息,包括当前的 `x` 和 `y` 坐标、运动方向以及电机的状态。
```c
typedef struct sunxi_motor_device_status {
int x; /* 电机在 X 方向的步数 */
int y; /* 电机在 Y 方向的步数 */
int dir; /* 电机的运动方向 */
int status; /* 电机的状态 */
} sunxi_motor_device_status_t;
```
电机命令结构体 (`sunxi_motor_cmd_t`):
- 包含了控制电机的参数,如方向、步数、每步时间、以及是否阻塞等。
```c
typedef struct sunxi_motor_cmd {
int dir; /* 电机命令的运动方向 */
int ab_step; /* 电机在垂直方向的步数 */
int ab_time_per_step; /* 电机在垂直方向每步的时间 */
int cd_step; /* 电机在水平方向的步数 */
int cd_time_per_step; /* 电机在水平方向每步的时间 */
int block; /* 是否阻塞的标志 */
} sunxi_motor_cmd_t;
```
IOCTL 命令定义:
- 用于与电机驱动交互的各种 IOCTL 命令:
- `MOTOR_DRV_SET_CMD` 用来发送电机命令。
- `MOTOR_DRV_GET_CMD` 用来获取当前电机命令。
- `MOTOR_DRV_SET_STOP` 和其他命令用来停止电机或设置电机状态。
```c
#define MOTOR_DRV_MAGICNUM 'm'
#define MOTOR_DRV_SET_STOP _IO(MOTOR_DRV_MAGICNUM, 0)
#define MOTOR_DRV_SET_STOP_AB_IO(MOTOR_DRV_MAGICNUM, 1)
#define MOTOR_DRV_SET_STOP_CD_IO(MOTOR_DRV_MAGICNUM, 2)
#define MOTOR_DRV_SET_START _IO(MOTOR_DRV_MAGICNUM, 3)
#define MOTOR_DRV_SET_BLOCK _IOW(MOTOR_DRV_MAGICNUM, 4, int)
#define MOTOR_DRV_SET_PARA _IOW(MOTOR_DRV_MAGICNUM, 5, sunxi_motor_para_t)
#define MOTOR_DRV_SET_CMD _IOW(MOTOR_DRV_MAGICNUM, 6, sunxi_motor_cmd_t)
#define MOTOR_DRV_GET_CMD _IOR(MOTOR_DRV_MAGICNUM, 7, sunxi_motor_cmd_t)
#define MOTOR_DRV_GET_STATUS _IOR(MOTOR_DRV_MAGICNUM, 8, sunxi_motor_device_status_t)
#define MOTOR_DRV_SET_STATUS _IOR(MOTOR_DRV_MAGICNUM, 9, sunxi_motor_device_status_t)
```
### 主函数流程
**设置电机命令:**
根据传入的 `direction` 参数,计算出对应的运动方向,使用移位操作(例如 `DIR_UP`、`DIR_DOWN` 等)来设置电机的方向。方向信息会根据 `DIR_AB_SHIFT` 或 `DIR_CD_SHIFT` 放入 `cmd.dir`。
**打开设备文件:**
通过 `open()` 函数打开 `/dev/sunxi_gpio_motor` 设备文件。如果打开失败,会打印错误信息并退出程序。
**根据模式执行操作:**
根据 `mode` 参数的不同,程序执行不同的操作:
- **模式 0(文件 I/O)**:通过 `write()` 将电机命令写入设备。
- **模式 1(IOCTL)**:通过 `ioctl()` 发送电机命令。
- **模式 2(停止电机)**:使用 `MOTOR_DRV_SET_STOP` 命令停止电机。
- **模式 3(获取电机状态)**:使用 `MOTOR_DRV_GET_STATUS` 命令获取电机状态,并打印出来。
**读取电机状态:**
执行命令后,程序会通过 `MOTOR_DRV_GET_STATUS` IOCTL 命令读取电机的状态,并输出当前的 `x`、`y` 坐标、方向和状态。
**关闭设备:**
最后,程序使用 `close()` 函数关闭设备文件。
### Demo
- **模式 0**:文件系统模式。让电机以 100 步向上移动,每步 1000 微秒,阻塞模式:
```text
gpio_motor 0 1 100 1000 1
```
- **模式 1**:IOCTL 模式。与文件系统模式类似,但使用 IOCTL 控制电机:
```text
gpio_motor 1 1 100 1000 1
```
- **模式 2**:停止电机:
```text
gpio_motor 2 1 0 0 0
```
- **模式 3**:获取电机当前状态:
```text
gpio_motor 3 0 0 0 0
```
详见:(https://docs.aw-ol.com/docs/soc/v821/sys_module/motor) .
### 效果
!(//m.qjumbo.com/forum/data/attachment/forum/202508/21/111358qh7ko6644xb2eh6b.gif)
## 总结
本文介绍了 RISC-V 全志-V821 PERF2B 开发板驱动步进电机的相关测试,为全志 V821 系列产品的开发设计和快速应用提供了参考。
页:
[1]