找回密码
 立即注册
首页 业界区 业界 龙芯2k0300 - 走马观碑组编码器驱动移植

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

洪势 4 小时前
在《龙芯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.png
1.1 工作原理

编码器的线数,是说编码器转一圈输出多少个脉冲,如果一个编码器是1024线,说明这个编码器转一圈对应的信号线会输出1024个脉冲,A/B两相转一圈发出的脉冲数一样的,不过存在90°相位差。
由于我使用的编码器带有方向输出引脚,因此:

  • 通过对A相脉冲计数,通过读取单位时间t的脉冲信号的数量,就可以计算出转速;
  • 通过判断方向引脚的高低电平,就可以知道是正转还是反转。
编码器码盘旋转一周A/B相输出的脉冲数目为N,在时间T内统计到的有效脉冲数目为S,那么转速为:

\[n = \frac{S}{NT} \times 60\]
1.2 硬件接线

2.png
3.png

1.2.1 左电机编码器

编码器一共引出6个引脚,左电机编码器与龙芯2K0300开发板的连接关系需严格对应,连接表如下:
引脚编号编码器引脚含义连接的龙芯GPIOPIN1GND必须可靠接地,否则可能出现显示异常GNDPIN23.3~5V直流供电3.3VPIN3AA 相输出,CMOS 电平GPIO67(SPI2_CS)PIN4Dir方向输出GPIO72(CAN2_RX)PIN5BB 相输出,CMOS 电平——PIN6nc悬空——1.2.2 右电机编码器

右电机编码器与龙芯2K0300开发板的连接关系需严格对应,连接表如下:
编码器引脚含义连接的龙芯GPIOPIN1GND必须可靠接地,否则可能出现显示异常GNDPIN23.3~5V直流供电3.3VPIN3AA 相输出,CMOS 电平GPIO64(SPI2_CLK)PIN4Dir方向输出GPIO73(CAN2_TX)PIN5BB 相输出,CMOS 电平——PIN6nc悬空——二、编码器设备驱动

2.1 编码器驱动

接下来我们在driver目录下创建子目录encoder_driver;
  1. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/driver$ mkdir encoder_driver
  2. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/encoder_driver$ cd encoder_driver
复制代码
目录结构如下:
  1. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/encoder_driver$ tree .
  2. .
  3. ├── Makefile
  4. └── encoder_driver.c
复制代码
2.1.1 encoder_driver
  1. #include <linux/module.h>
  2. #include <linux/platform_device.h>
  3. #include <linux/of.h>
  4. #include <linux/of_gpio.h>
  5. #include <linux/io.h>
  6. #include <linux/miscdevice.h>
  7. #include <linux/fs.h>
  8. #include <linux/uaccess.h>
  9. #define DEVICE_NAME "pulse_encoder"
  10. // 寄存器定义
  11. #define PWM_ENCODER_LOW_BUFFER   0x4
  12. #define PWM_ENCODER_FULL_BUFFER  0x8
  13. #define PWM_ENCODER_CTRL         0xC
  14. // 控制寄存器位定义
  15. #define PWM_ENCODER_EN    (1 << 0)   // 计数器使能
  16. #define PWM_ENCODER_CAPTE (1 << 8)   // 测量脉冲使能 (编码器模式)
  17. // ioctl 命令
  18. #define ENCODER_IOC_MAGIC 'E'
  19. #define ENCODER_GET_COUNT _IOR(ENCODER_IOC_MAGIC, 1, struct encoder_counts)
  20. struct encoder_counts {
  21.     long long count;    // 脉冲计数
  22.     int direction;      // 旋转方向 (1: 正转, -1: 反转)
  23. };
  24. struct encoder_device {
  25.     void __iomem *base; // 定时器寄存器基地址
  26.     int dir_gpio;       // 方向引脚
  27.     int ppr;            // 编码器每转脉冲数
  28.     struct miscdevice miscdev;
  29. };
  30. static struct encoder_device *g_encoder;
  31. // 读取计数值和方向
  32. static long encoder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  33. {
  34.     struct encoder_device *enc = g_encoder;
  35.     struct encoder_counts cnt;
  36.     void __user *argp = (void __user *)arg;
  37.     u32 full_buffer;
  38.     unsigned long flags;
  39.     if (cmd != ENCODER_GET_COUNT)
  40.         return -ENOTTY;
  41.     local_irq_save(flags);
  42.    
  43.     // 1. 读取硬件计数值 (32位)
  44.     full_buffer = readl(enc->base + PWM_ENCODER_FULL_BUFFER);
  45.    
  46.     // 2. 读取方向GPIO电平
  47.     cnt.direction = gpio_get_value(enc->dir_gpio) ? 1 : -1;
  48.    
  49.     // 3. 可选:将硬件计数值转换为更有意义的脉冲数
  50.     //    公式基于代码中的参考:100000000 / FULL_BUFFER / 200
  51.     //    这里的计算需要根据你的定时器时钟频率调整,此处直接返回原始值
  52.     cnt.count = full_buffer;
  53.    
  54.     local_irq_restore(flags);
  55.     if (copy_to_user(argp, &cnt, sizeof(cnt)))
  56.         return -EFAULT;
  57.    
  58.     return 0;
  59. }
  60. static const struct file_operations encoder_fops = {
  61.     .owner = THIS_MODULE,
  62.     .unlocked_ioctl = encoder_ioctl,
  63. };
  64. // 驱动入口
  65. static int encoder_probe(struct platform_device *pdev)
  66. {
  67.     struct device *dev = &pdev->dev;
  68.     struct device_node *np = dev->of_node;
  69.     struct resource *res;
  70.     u32 ctrl;
  71.     int ret;
  72.     // 分配设备结构体
  73.     g_encoder = devm_kzalloc(dev, sizeof(*g_encoder), GFP_KERNEL);
  74.     if (!g_encoder)
  75.         return -ENOMEM;
  76.     // 1. 获取并映射寄存器地址
  77.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  78.     if (!res) {
  79.         dev_err(dev, "Failed to get memory resource\n");
  80.         return -ENOENT;
  81.     }
  82.    
  83.     g_encoder->base = devm_ioremap(dev, res->start, resource_size(res));
  84.     if (!g_encoder->base) {
  85.         dev_err(dev, "Failed to ioremap\n");
  86.         return -ENOMEM;
  87.     }
  88.     // 2. 获取方向GPIO
  89.     g_encoder->dir_gpio = of_get_named_gpio(np, "dir-gpios", 0);
  90.     if (!gpio_is_valid(g_encoder->dir_gpio)) {
  91.         dev_err(dev, "Invalid dir-gpios in device tree\n");
  92.         return -EINVAL;
  93.     }
  94.    
  95.     ret = devm_gpio_request(dev, g_encoder->dir_gpio, "encoder_dir");
  96.     if (ret) return ret;
  97.     ret = gpio_direction_input(g_encoder->dir_gpio);
  98.     if (ret) return ret;
  99.     // 3. 获取PPR (每转脉冲数)
  100.     if (of_property_read_u32(np, "rotary-encoder,steps-per-period", &g_encoder->ppr))
  101.         g_encoder->ppr = 20; // 默认值
  102.    
  103.     // 4. 配置定时器为编码器模式
  104.     ctrl = readl(g_encoder->base + PWM_ENCODER_CTRL);
  105.     ctrl |= PWM_ENCODER_EN;      // 使能计数器
  106.     ctrl |= PWM_ENCODER_CAPTE;   // 使能捕获/编码器模式
  107.     writel(ctrl, g_encoder->base + PWM_ENCODER_CTRL);
  108.    
  109.     // 5. 注册MISC设备
  110.     g_encoder->miscdev.minor = MISC_DYNAMIC_MINOR;
  111.     g_encoder->miscdev.name = DEVICE_NAME;
  112.     g_encoder->miscdev.fops = &encoder_fops;
  113.    
  114.     ret = misc_register(&g_encoder->miscdev);
  115.     if (ret) {
  116.         dev_err(dev, "Failed to register misc device\n");
  117.         return ret;
  118.     }
  119.     dev_info(dev, "Encoder driver loaded, PPR=%d, base=0x%p\n",
  120.              g_encoder->ppr, g_encoder->base);
  121.     return 0;
  122. }
  123. static void encoder_remove(struct platform_device *pdev)
  124. {
  125.     if (g_encoder) {
  126.         misc_deregister(&g_encoder->miscdev);
  127.         // 禁用编码器模式
  128.         writel(0, g_encoder->base + PWM_ENCODER_CTRL);
  129.     }
  130. }
  131. static const struct of_device_id encoder_of_match[] = {
  132.     { .compatible = "pulse-dir-encoder" },
  133.     { /* sentinel */ }
  134. };
  135. MODULE_DEVICE_TABLE(of, encoder_of_match);
  136. static struct platform_driver encoder_driver = {
  137.     .probe = encoder_probe,
  138.     .remove = encoder_remove,
  139.     .driver = {
  140.         .name = "pulse_dir_encoder_v2",
  141.         .of_match_table = encoder_of_match,
  142.     },
  143. };
  144. module_platform_driver(encoder_driver);
  145. MODULE_LICENSE("GPL");
  146. MODULE_DESCRIPTION("Pulse/Direction Encoder Driver using PWM Timer (No Interrupt)");
复制代码
2.1.2 Makefile

[code]# 内核源码路径KERNELDIR ?= /opt/2k0300/build-2k0300/workspace/linux-6.12# 当前驱动目录PWD := $(shell pwd)# 交叉编译工具链CROSS_COMPILE := loongarch64-linux-gnu-# 架构ARCH := loongarch# 所需文件夹BUILD_DIR := buildKO_DIR:=koSRC_DIR:=src# 编译目标obj-m := encoder.oencoder-y := encoder_driver.o \# 编译规则ll: prepare compile move_files# 提前创建目录prepare:        @mkdir -p $(BUILD_DIR) $(KO_DIR)        @echo "
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册