首页 > 基础资料 博客日记
龙芯2k0300 - 走马观碑组编码器驱动移植
2026-04-04 11:00:02基础资料围观1次
在《龙芯2k0300 - 走马观碑组第21届智能汽车竞赛软硬件设计》中我们使用了LQ_1024线方向mini编码器。
一、LQ_1024线方向mini编码器
龙邱科技mini系列编码器具有以下特点:
● 分辨率有:A/B相正交输出256线、A/B相正交输出1024线、步进脉冲 + 方向输出512线、......;
●旋转速度高,最高转速可达10000rpm;
● 宽广的工作温度范围:-40℃ ~ +125℃。
● 抗扰性好。本产品采用霍尔检测技术,属于无接触检测,传感器运行不受灰尘或其它杂物影响,很好克服了基于光学检测原理的缺点;
● 体积小巧。直径D:14mm、高H:18mm、轴径:3mm。
如果你对编码器一点都不了解的话可以先参考这篇文章:《STM32F103霍尔编码器测速》。
龙邱科技提供的编码器,有带方向输出引脚和不带方向输出引脚两种,这里我使用的是正交A/B相1024线方向mini编码器,也就是说我使用的这个编码器除了有A/B输出引脚外,还有单独的方向输出引脚,这样我们就不用通过A/B相信号变化的先后顺序来判断运动方向。
1.1 工作原理
编码器的线数,是说编码器转一圈输出多少个脉冲,如果一个编码器是1024线,说明这个编码器转一圈对应的信号线会输出1024个脉冲,A/B两相转一圈发出的脉冲数一样的,不过存在90°相位差。
由于我使用的编码器带有方向输出引脚,因此:
- 通过对
A相脉冲计数,通过读取单位时间t的脉冲信号的数量,就可以计算出转速; - 通过判断方向引脚的高低电平,就可以知道是正转还是反转。
编码器码盘旋转一周A/B相输出的脉冲数目为N,在时间T内统计到的有效脉冲数目为S,那么转速为:
1.2 硬件接线


1.2.1 左电机编码器
编码器一共引出6个引脚,左电机编码器与龙芯2K0300开发板的连接关系需严格对应,连接表如下:
| 引脚编号 | 编码器引脚 | 含义 | 连接的龙芯GPIO |
|---|---|---|---|
| PIN1 | GND | 必须可靠接地,否则可能出现显示异常 | GND |
| PIN2 | 3.3~5V | 直流供电 | 3.3V |
| PIN3 | A | A 相输出,CMOS 电平 | GPIO67(SPI2_CS) |
| PIN4 | Dir | 方向输出 | GPIO72(CAN2_RX) |
| PIN5 | B | B 相输出,CMOS 电平 | —— |
| PIN6 | nc | 悬空 | —— |
1.2.2 右电机编码器
右电机编码器与龙芯2K0300开发板的连接关系需严格对应,连接表如下:
| 编码器引脚 | 含义 | 连接的龙芯GPIO | |
|---|---|---|---|
| PIN1 | GND | 必须可靠接地,否则可能出现显示异常 | GND |
| PIN2 | 3.3~5V | 直流供电 | 3.3V |
| PIN3 | A | A 相输出,CMOS 电平 | GPIO64(SPI2_CLK) |
| PIN4 | Dir | 方向输出 | GPIO73(CAN2_TX) |
| PIN5 | B | B 相输出,CMOS 电平 | —— |
| PIN6 | nc | 悬空 | —— |
二、编码器设备驱动
2.1 编码器驱动
接下来我们在driver目录下创建子目录encoder_driver;
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/driver$ mkdir encoder_driver
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/encoder_driver$ cd encoder_driver
目录结构如下:
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/encoder_driver$ tree .
.
├── Makefile
└── encoder_driver.c
2.1.1 encoder_driver
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "pulse_encoder"
// 寄存器定义
#define PWM_ENCODER_LOW_BUFFER 0x4
#define PWM_ENCODER_FULL_BUFFER 0x8
#define PWM_ENCODER_CTRL 0xC
// 控制寄存器位定义
#define PWM_ENCODER_EN (1 << 0) // 计数器使能
#define PWM_ENCODER_CAPTE (1 << 8) // 测量脉冲使能 (编码器模式)
// ioctl 命令
#define ENCODER_IOC_MAGIC 'E'
#define ENCODER_GET_COUNT _IOR(ENCODER_IOC_MAGIC, 1, struct encoder_counts)
struct encoder_counts {
long long count; // 脉冲计数
int direction; // 旋转方向 (1: 正转, -1: 反转)
};
struct encoder_device {
void __iomem *base; // 定时器寄存器基地址
int dir_gpio; // 方向引脚
int ppr; // 编码器每转脉冲数
struct miscdevice miscdev;
};
static struct encoder_device *g_encoder;
// 读取计数值和方向
static long encoder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct encoder_device *enc = g_encoder;
struct encoder_counts cnt;
void __user *argp = (void __user *)arg;
u32 full_buffer;
unsigned long flags;
if (cmd != ENCODER_GET_COUNT)
return -ENOTTY;
local_irq_save(flags);
// 1. 读取硬件计数值 (32位)
full_buffer = readl(enc->base + PWM_ENCODER_FULL_BUFFER);
// 2. 读取方向GPIO电平
cnt.direction = gpio_get_value(enc->dir_gpio) ? 1 : -1;
// 3. 可选:将硬件计数值转换为更有意义的脉冲数
// 公式基于代码中的参考:100000000 / FULL_BUFFER / 200
// 这里的计算需要根据你的定时器时钟频率调整,此处直接返回原始值
cnt.count = full_buffer;
local_irq_restore(flags);
if (copy_to_user(argp, &cnt, sizeof(cnt)))
return -EFAULT;
return 0;
}
static const struct file_operations encoder_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = encoder_ioctl,
};
// 驱动入口
static int encoder_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct resource *res;
u32 ctrl;
int ret;
// 分配设备结构体
g_encoder = devm_kzalloc(dev, sizeof(*g_encoder), GFP_KERNEL);
if (!g_encoder)
return -ENOMEM;
// 1. 获取并映射寄存器地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "Failed to get memory resource\n");
return -ENOENT;
}
g_encoder->base = devm_ioremap(dev, res->start, resource_size(res));
if (!g_encoder->base) {
dev_err(dev, "Failed to ioremap\n");
return -ENOMEM;
}
// 2. 获取方向GPIO
g_encoder->dir_gpio = of_get_named_gpio(np, "dir-gpios", 0);
if (!gpio_is_valid(g_encoder->dir_gpio)) {
dev_err(dev, "Invalid dir-gpios in device tree\n");
return -EINVAL;
}
ret = devm_gpio_request(dev, g_encoder->dir_gpio, "encoder_dir");
if (ret) return ret;
ret = gpio_direction_input(g_encoder->dir_gpio);
if (ret) return ret;
// 3. 获取PPR (每转脉冲数)
if (of_property_read_u32(np, "rotary-encoder,steps-per-period", &g_encoder->ppr))
g_encoder->ppr = 20; // 默认值
// 4. 配置定时器为编码器模式
ctrl = readl(g_encoder->base + PWM_ENCODER_CTRL);
ctrl |= PWM_ENCODER_EN; // 使能计数器
ctrl |= PWM_ENCODER_CAPTE; // 使能捕获/编码器模式
writel(ctrl, g_encoder->base + PWM_ENCODER_CTRL);
// 5. 注册MISC设备
g_encoder->miscdev.minor = MISC_DYNAMIC_MINOR;
g_encoder->miscdev.name = DEVICE_NAME;
g_encoder->miscdev.fops = &encoder_fops;
ret = misc_register(&g_encoder->miscdev);
if (ret) {
dev_err(dev, "Failed to register misc device\n");
return ret;
}
dev_info(dev, "Encoder driver loaded, PPR=%d, base=0x%p\n",
g_encoder->ppr, g_encoder->base);
return 0;
}
static int encoder_remove(struct platform_device *pdev)
{
if (g_encoder) {
misc_deregister(&g_encoder->miscdev);
// 禁用编码器模式
writel(0, g_encoder->base + PWM_ENCODER_CTRL);
}
return 0;
}
static const struct of_device_id encoder_of_match[] = {
{ .compatible = "pulse-dir-encoder" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, encoder_of_match);
static struct platform_driver encoder_driver = {
.probe = encoder_probe,
.remove = encoder_remove,
.driver = {
.name = "pulse_dir_encoder_v2",
.of_match_table = encoder_of_match,
},
};
module_platform_driver(encoder_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Pulse/Direction Encoder Driver using PWM Timer (No Interrupt)");
2.1.2 Makefile
# 内核源码路径
KERNELDIR ?= /opt/2k0300/build-2k0300/workspace/linux-6.12
# 当前驱动目录
PWD := $(shell pwd)
# 交叉编译工具链
CROSS_COMPILE := loongarch64-linux-gnu-
# 架构
ARCH := loongarch
# 所需文件夹
BUILD_DIR := build
KO_DIR:=ko
SRC_DIR:=src
# 编译目标
obj-m := encoder.o
vl53l0x-y := encoder_driver.o \
# 编译规则
ll: prepare compile move_files
# 提前创建目录
prepare:
@mkdir -p $(BUILD_DIR) $(KO_DIR)
@echo "📂 \033[32m已创建目录\033[0m"
compile:
@echo "📂 开始编译驱动模块"
# 关键:指定内核路径并传递正确的编译参数
make -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
# 移动文件
move_files:
# 移动中间文件(.o/.mod.o/.mod.c/.cmd等)到build目录
@find . -type f \
-not -path "./$(BUILD_DIR)/*" -not -path "./$(KO_DIR)/*" \
\( -name '*.o' -o -name '*.mod.o' -o -name '*.mod.c' -o -name '.*.cmd' -o -name 'modules.order' -o -name 'Module.symvers' \) \
! -name '*.ko' -exec mv -t $(BUILD_DIR)/ {} +
# 移动 ko 模块到 ko 目录
@find . -type f \
-not -path "./$(BUILD_DIR)/*" -not -path "./$(KO_DIR)/*" \
-name '*.ko' -exec mv -t $(KO_DIR)/ {} +
clean:
@echo "🧹 清理编译产物"
make -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean
rm -rf *.ko *.o *.mod.o *.mod.c *.symvers *.order .*.cmd .tmp_versions build ko
2.2 新增设备节点
encoder1 {
compatible = "pulse-dir-encoder";
pinctrl-names = "default";
// 请根据你的硬件连接,正确配置引脚复用
// pinctrl-0 = <&encoder_pins>;
// 方向引脚:PIN4 (Dir) 连接到 GPIO72 (CAN2_RX)
dir-gpios = <&gpa0 72 GPIO_ACTIVE_HIGH>;
// 编码器的A相信号连接到PWM定时器,这里指定定时器对应的寄存器地址
// 你需要根据实际连接的PWM通道,填写正确的基地址
reg = <0 0x1611b000 0 0x10>;
// 可选:编码器每转脉冲数 (PPR),应用程序会用到
rotary-encoder,steps-per-period = <1024>;
};
三、应用程序
3.1 源码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define ENCODER_IOC_MAGIC 'E'
#define ENCODER_GET_COUNT _IOR(ENCODER_IOC_MAGIC, 1, struct encoder_counts)
struct encoder_counts {
long long count;
int direction;
};
int main() {
int fd = open("/dev/pulse_encoder", O_RDONLY);
if (fd < 0) {
perror("open");
return -1;
}
struct encoder_counts cnt;
long long last_count = 0;
while (1) {
if (ioctl(fd, ENCODER_GET_COUNT, &cnt) < 0) {
perror("ioctl");
break;
}
long long delta = cnt.count - last_count;
last_count = cnt.count;
printf("Count: %lld, Dir: %d, Delta: %lld\n",
cnt.count, cnt.direction, delta);
usleep(50000); // 20Hz采样
}
close(fd);
return 0;
}
3.2 Makefile
3.3 编译应用程序
四、测试
4.1 烧录设备树
4.2 烧录驱动
4.3 应用程序测试
参考文字
[1] 龙邱科技Mini编码器.pdf
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签:
相关文章
最新发布
- 基于 RO2 humble 配置 robosense Helios 32(速腾) & xsense mti 300
- 手撕 Transformer (3):编码器的实现
- Fedora 43物理机部署复盘
- 龙芯2k0300 - 走马观碑组编码器驱动移植
- AI 输出 Token 优化:文言文极简模式的实践
- Dijkstra算法简介
- [网络协议/文件] `SMB` 协议:一种Windows主流的局域网网络文件共享协议
- 【EF Core】直接更新数据
- 【OpenClaw】通过 Nanobot 源码学习架构---(3)AgentLoop
- 200 行 Python 代码,从零手搓极简 Agent,吃透智能体核心原理!

