增量型旋转编码器作为输入器件广泛用于各种设备,例如汽车音响的音量调节,收音机频率调节,示波器上的旋钮。但遗憾的是在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
温湿度传感器在你乘坐或等候地铁时提供了舒适的环境
智慧工厂数据采集监控方案,自动化实现就是这么简单
IT安全设备市场规模稳步增长,企业纷纷引入网络安全技术及设备
如何创建更安全的Web服务器
4U工控机主板插槽的类型都有哪些
基于Sensor API如何添加增量型旋转编码器的驱动
Synopsys推出业界首个物理感知的RTL设计系统
到底该不该让家里的小孩玩VR?
在CtrlSim中加入电机控制几个example
电源滤波器的组成及衡量参数
2018年将是联络中心分化瓦解的一年
微雪电子树莓派 Zero WH(套餐B)介绍
植物可穿戴传感器 可检测植物的蒸腾作用
功率半导体,成长惊人!
荣耀20S将于9月4日发布该机主打荣耀最强自拍手机
如何制作简单的音频放大器
如何实现一个可编程LED驱动器
英伟达Orin算法库主要三类算法
消息称三星准备Mini LED背光LCD电视量产 斥资400亿韩元于越南建立50余条产线
TDK紧凑型金属氧化物圆片压敏电阻的特点性能及应用