首页 > 基础资料 博客日记

龙芯2k0300 - 走马观碑组编码器驱动移植

2026-04-04 11:00:02基础资料围观1

这篇文章介绍了龙芯2k0300 - 走马观碑组编码器驱动移植,分享给大家做个参考,收藏极客资料网收获更多编程知识

在《龙芯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/B1024线方向mini编码器,也就是说我使用的这个编码器除了有A/B输出引脚外,还有单独的方向输出引脚,这样我们就不用通过A/B相信号变化的先后顺序来判断运动方向。

1.1 工作原理

编码器的线数,是说编码器转一圈输出多少个脉冲,如果一个编码器是1024线,说明这个编码器转一圈对应的信号线会输出1024个脉冲,A/B两相转一圈发出的脉冲数一样的,不过存在90°相位差。

由于我使用的编码器带有方向输出引脚,因此:

  • 通过对A相脉冲计数,通过读取单位时间t的脉冲信号的数量,就可以计算出转速;
  • 通过判断方向引脚的高低电平,就可以知道是正转还是反转。

编码器码盘旋转一周A/B相输出的脉冲数目为N,在时间T内统计到的有效脉冲数目为S,那么转速为:

\[n = \frac{S}{NT} \times 60 \]

1.2 硬件接线

img

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


文章来源:https://www.cnblogs.com/zyly/p/19820444
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云