基于Sensor API如何添加增量型旋转编码器的驱动

网友投稿 179 2024-01-25

增量型旋转编码器作为输入器件广泛用于各种设备,例如汽车音响的音量调节,收音机频率调节,示波器上的旋钮但遗憾的是在Zephyr中并没有增量型旋转编码器的驱动,本文将基于现有的Sensor API, 说明如何添加增量型旋转编码器的驱动,本文不对驱动操作硬件的实现细节进行说明。

增量型旋转编码器硬件要点 本文使用的是KY-040旋转编码器,详细信息见文末参考 button引脚是一个对地的开关,按压时接地 旋转时A/B输出有相差的正交脉冲 旋转一圈产生固定数量的脉冲 旋转时一个脉冲内旋转轴可以有多个停留位置,例如1,2,4.

驱动 驱动API选择 比较好的做法是为旋转编码器抽象新的驱动API,但新的API要进入Zephyr的主分支过程是非常漫长的,同时旋转编码器抽象API需要涵盖众多类型因此我选用了现有的Senser API来对增量类型旋转编码器的API。

增量类型旋转编码器的按压就是一个简单的button,用gpio就可以处理,因此旋转编码器的驱动就只处理旋转编码器的旋转理解为是一个角度的传感器,正反转为转动方向,转动的距离就是角度,这里使用sensor API的SENSOR_CHAN_ROTATION来对其进行操控。

设备树绑定 设备树绑定是对旋转编码器的硬件进行抽象,一个增量式旋转编码器与旋转相关的的硬件特性有如下信息: 输入引脚A/B 旋转一圈产生的脉冲 一个脉冲周期的稳妥数量 创建dts/bindings/sensor/rotary-encoder.yaml内容如下

description: | Sensor driver for the relative-axis rotary encoder compatible: “rotary-encoder” properties:

label: type: string required: true a-gpios: type: phandle-array required: true description: A pin for the encoder

b-gpios: type: phandle-array required: true description: B pin for the encoder ppr: type: int description: Pulse Per Revolution

required: false spp: type: int description: | Number of steps (stable states) per period 1: Full-period mode (default)

2: Half-period mode 4: Quarter-period mode required: false 驱动代码 从设备树中获取硬件信息 创建管理数据变量和读取硬件信息 struct encoder_config {

const char *a_label; const uint8_t a_pin; const uint8_t a_flags; const char *b_label; const uint8_t b_pin;

const uint8_t b_flags; const uint8_t ppr; const uint8_t spp; }; //创建管理数据和配置数据的宏 #define ENCODER_INST(n)

struct encoder_data encoder_data_##n; const struct encoder_config encoder_cfg_##n = { .a_label = DT_INST_GPIO_LABEL(n, a_gpios),

.a_pin = DT_INST_GPIO_PIN(n, a_gpios), .a_flags = DT_INST_GPIO_FLAGS(n, a_gpios), .b_label = DT_INST_GPIO_LABEL(n, b_gpios),

.b_pin = DT_INST_GPIO_PIN(n, b_gpios), .b_flags = DT_INST_GPIO_FLAGS(n, b_gpios), COND_CODE_0(DT_INST_NODE_HAS_PROP(n, ppr), (1), (DT_INST_PROP(n, ppr))),

COND_CODE_0(DT_INST_NODE_HAS_PROP(n, spp), (SPP_FULL), (DT_INST_PROP(n, spp))), }; //根据设备树对node进行初始化,会从设备树中读取硬件信息放在struct encoder_config变量中

DT_INST_FOREACH_STATUS_OKAY(ENCODER_INST) 驱动初始化 在启动的POST_KERNEL阶段会调用encoder_init对驱动进行初始化 int encoder_init(const struct device *dev)

{ // GPIO的配置 // GPIO中断安装 // 旋转编码器GPIO初始化状态读取 // 驱动初始化状态设置 // 驱动线程创建 // 使能中断 } //注册驱动 DEVICE_AND_API_INIT(encoder_##n, DT_INST_LABEL(n), encoder_init, &encoder_data_##n, &encoder_cfg_##n,

POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &encoder_driver_api); 驱动流程 旋转编码器依靠脉冲触发GPIO中断,中断通知thread

进行处理 static void encoder_a_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)

{ struct encoder_data *drv_data = CONTAINER_OF(cb, struct encoder_data, a_gpio_cb); enable_int(drv_data-》dev, false);

drv_data-》intpin = 0b10; //通知发生中断 k_sem_give(&drv_data-》gpio_sem); } static void encoder_b_gpio_callback(const struct device *dev, struct gpio_callback *cb,

uint32_t pins) { struct encoder_data *drv_data = CONTAINER_OF(cb, struct encoder_data, b_gpio_cb);

enable_int(drv_data-》dev, false); drv_data-》intpin = 0b01; //通知发生中断 k_sem_give(&drv_data-》gpio_sem);

} static void encoder_thread(void *dev_ptr, void *p2, void *p3) { while (1) { //等待中断通知 k_sem_take(&drv_data-》gpio_sem, K_FOREVER);

//根据A/B GPIO level情况判断正反旋转 //更新旋转数据 //通过trigger handle通过应用层 if (drv_data-》handler) { drv_data-》handler(dev, drv_data-》trigger);

} //使能中断 enable_int(dev, true); } } 驱动接口实现 sensor的接口有5个, 详细参考旋转编码器只用实现其中的2个既可以 旋转编码器是主动输出型设备,无需软件触发,因此可以不必实现channel_fetch,只用实现trigger_set用于注册触发时的callback,实现channel_get用于在callback时从driver获取旋转的角度既可以。

12static int encoder_trigger_set(const struct device *dev, const struct sensor_trigger *trig, sensor_trigger_handler_t handler)

{ struct encoder_data *drv_data = dev-》data; enable_int(dev, false); drv_data-》trigger = trig; drv_data-》handler = handler;

enable_int(dev, true); return 0; } static int encoder_channel_get(const struct device *dev, enum sensor_channel chan,

struct sensor_value *val) { struct encoder_data *drv_data = dev-》data; const struct encoder_config *drv_cfg = dev-》config;

int32_t acc; if (chan != SENSOR_CHAN_ROTATION) { return -ENOTSUP; } acc = drv_data-》pulses; val-》val1 = acc * FULL_ANGLE / (drv_cfg-》ppr * drv_cfg-》spp);

val-》val2 = acc * FULL_ANGLE - val-》val1 * (drv_cfg-》ppr * drv_cfg-》spp); if (val-》val2) { val-》val2 *= 1000000;

val-》val2 /= (drv_cfg-》ppr * drv_cfg-》spp); } return 0; } static const struct sensor_driver_api encoder_driver_api = {

.trigger_set = encoder_trigger_set, .channel_get = encoder_channel_get, }; 驱动使用 添加设备树节点 在板子的dts中添加旋转编码器的设备树节点:

gpio1.22和gpio1.23是旋转编码器连接旋转编码器的A/B引脚旋转编码器旋转一圈有15个脉冲,每个脉冲下有2个稳定状态 input_encoder: rotary_encoder { compatible = “rotary-encoder”;

status = “okay”; label = “INPUT_ENCODER”; a-gpios = 《&gpio1 22 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)》;

b-gpios = 《&gpio1 23 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)》; ppr = 《15》; spp = 《2》; }; 使用代码 void encoder_callback(const struct device *dev,

struct sensor_trigger *trigger) { struct sensor_value val; //旋转编码器旋转发生,从驱动读出旋转过的角度 sensor_channel_get(dev, SENSOR_CHAN_ROTATION, &val);

printk(“current %d.%d ”, val.val1, val.val2); } void main(void) { struct device *dev; //获取旋转编码器device

dev = device_get_binding(“INPUT_ENCODER”); //注册trigger callback,当旋转发生时将调用encoder_callback sensor_trigger_set(dev, NULL, encoder_callback);

} 以上测试测序编译完后跑起来的效果 current 12.0 current 24.0 current 36.0 current 48.0 current 36.0 current 24.0

current 12.0 current 0.0 current -12.0 current -24.0 current -36.0 current -48.0 参考 https://zh.wikipedia.org/wiki/%E6%97%8B%E8%BD%89%E7%B7%A8%E7%A2%BC%E5%99%A8

https://elixir.bootlin.com/linux/latest/source/Documentation/devicetree/bindings/input/rotary-encoder.txt

https://www.epitran.it/ebayDrive/datasheet/25.pdf 编辑:jq

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:现代异步存储访问API探索:libaio、io_uring和SPDK
下一篇:庖丁解牛:Apache APISIX 3.0与Kong 3.0功能背后的趋势
相关文章

 发表评论

暂时没有评论,来抢沙发吧~