背景
脉冲编码器,在硬件上最常见的就是旋钮,在旋钮旋转时,对外接出的两个管脚依次产生低电平并依次返回高电平,电平下降或上升的先后顺序,代表旋钮旋转的方向。由于管脚中断间隔时间一般都比较长(ms级别),因此可以通过GPIO中断检测的方式去实现脉冲检测。也有一部分mcu厂商(已知的有nxp,st,新塘半导体,华大)把这种检测做到单独的硬件脉冲检测单元中,具体配置就得看厂商规格了,但不管怎么配置,对于软件来说,无非就是使能脉冲检测,然后在产生中断时读取脉冲计数,判断后续需要的操作,另外在不使用的时候调用关闭接口禁用模块。
RTT适配的脉冲检测框架
源码路径:components/drivers/misc/pulse_encoder.c
驱动注册入口
#ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops pulse_encoder_ops = { rt_pulse_encoder_init, rt_pulse_encoder_open, rt_pulse_encoder_close, rt_pulse_encoder_read, RT_NULL, rt_pulse_encoder_control }; #endif rt_err_t rt_device_pulse_encoder_register(struct rt_pulse_encoder_device *pulse_encoder, const char *name, void *user_data) { struct rt_device *device; RT_ASSERT(pulse_encoder != RT_NULL); RT_ASSERT(pulse_encoder->ops != RT_NULL); device = &(pulse_encoder->parent); device->type = RT_Device_Class_Miscellaneous; device->rx_indicate = RT_NULL; device->tx_complete = RT_NULL; #ifdef RT_USING_DEVICE_OPS device->ops = &pulse_encoder_ops; #else device->init = rt_pulse_encoder_init; device->open = rt_pulse_encoder_open; device->close = rt_pulse_encoder_close; device->read = rt_pulse_encoder_read; device->write = RT_NULL; device->control = rt_pulse_encoder_control; #endif device->user_data = user_data; return rt_device_register(device, name, RT_DEVICE_FLAG_RDONLY | RT_DEVICE_FLAG_STANDALONE); }
由注册接口可以知道,脉冲编码器框架并不需要实现写接口,因为实际上也没啥可写的,因为这种模块就是检测模块,不是输出模块。
初始化接口
static rt_err_t rt_pulse_encoder_init(struct rt_device *dev) { struct rt_pulse_encoder_device *pulse_encoder; pulse_encoder = (struct rt_pulse_encoder_device *)dev; if (pulse_encoder->ops->init) { return pulse_encoder->ops->init(pulse_encoder); } else { return -RT_ENOSYS; } }
从接口实现上来看,初始化接口也没什么好设置的,仅仅是要求驱动把资源初始化好就行,没有任何附带参数。
打开接口
static rt_err_t rt_pulse_encoder_open(struct rt_device *dev, rt_uint16_t oflag) { struct rt_pulse_encoder_device *pulse_encoder; pulse_encoder = (struct rt_pulse_encoder_device *)dev; if (pulse_encoder->ops->control) { return pulse_encoder->ops->control(pulse_encoder, PULSE_ENCODER_CMD_ENABLE, RT_NULL); } else { return -RT_ENOSYS; } }
与初始化接口一样,但传入的参数是RT_NULL让人觉得有些奇怪,因为其实常见的旋钮工作方式有两种,一种叫全波旋钮,从波形上来说,这种旋钮旋转一格,两个管脚的电平会先后下降后再先后上升。另一种叫半波旋钮,在旋转一格时,两个管脚的波形要么先后下降,要么先后上升。这里指定乘RT_NULL,我只能猜可能是维持当前设置的目的,个人理解上换成pulse_encoder->type会更加合适。
控制接口
static rt_err_t rt_pulse_encoder_control(struct rt_device *dev, int cmd, void *args) { rt_err_t result; struct rt_pulse_encoder_device *pulse_encoder; result = RT_EOK; pulse_encoder = (struct rt_pulse_encoder_device *)dev; switch (cmd) { case PULSE_ENCODER_CMD_CLEAR_COUNT:// 计数清零,可能是用于计数异常的时候的恢复操作 result = pulse_encoder->ops->clear_count(pulse_encoder); break; case PULSE_ENCODER_CMD_GET_TYPE: // 获取目前检测的工作模式,其实个人认为大部分场景下没必要上报此信息。 // 因为这是和旋钮波形强相关的配置,除非业务需要知道,一般应用不会关注这块 *(enum rt_pulse_encoder_type *)args = pulse_encoder->type; break; case PULSE_ENCODER_CMD_ENABLE: case PULSE_ENCODER_CMD_DISABLE: // 使能禁用编码器,需要注意的是,args是工作模式 result = pulse_encoder->ops->control(pulse_encoder, cmd, args); break; default: result = -RT_ENOSYS; break; } return result; }
读接口
static rt_ssize_t rt_pulse_encoder_read(struct rt_device *dev, rt_off_t pos, void *buffer, rt_size_t size) { struct rt_pulse_encoder_device *pulse_encoder; pulse_encoder = (struct rt_pulse_encoder_device *)dev; if (pulse_encoder->ops->get_count) { *(rt_int32_t *)buffer = pulse_encoder->ops->get_count(pulse_encoder); } return 1; }
读接口,也比较简单,就是从驱动拿到目前的计数值。
关闭接口
static rt_err_t rt_pulse_encoder_close(struct rt_device *dev) { struct rt_pulse_encoder_device *pulse_encoder; pulse_encoder = (struct rt_pulse_encoder_device *)dev; if (pulse_encoder->ops->control) { return pulse_encoder->ops->control(pulse_encoder, PULSE_ENCODER_CMD_DISABLE, RT_NULL); } else { return -RT_ENOSYS; } }
关闭上,其实没啥好区分的,不管工作在什么模式,关闭后都是禁用了脉冲编码器的检测模块,因此没必要指定传递什么模式。
Add on
分析了这么多,其实会发现,好像还漏了一个接口,脉冲编码中断上报好像没有。从目前开源出来的RTT的脉冲编码实现代码上看,虽然各家在驱动上实现上都有中断处理,但并没有添加向用户层主动上报的接口,也许是驱动适配人员不清楚RTT有一套统一的中断回调函数入口,也许是驱动适配人员认为脉冲编码器,只需要轮询检测就可以做到用户无感了吧。
总结
总体来说,脉冲编码器的框架算是很简单了。而分析完后,我们也基本上可以获得一个相对标准的驱动实现模板。
struct pulse_encoder_device { struct rt_pulse_encoder_device pulse_encoder; // TODO: 驱动内部参数 }; typedef struct pulse_encoder_device pulse_enccoder_device_t; static pulse_enccoder_device_t pulse_encoder_obj[] = { #ifdef BSP_USING_PULSE_ENCODER1 { //驱动内部参数初始值 }, #endif #ifdef BSP_USING_PULSE_ENCODER2 { }, #endif }; rt_err_t pulse_encoder_init(struct rt_pulse_encoder_device *pulse_encoder) { rt_err_t result = RT_EOK; switch(pulse_encoder->type) { case SINGLE_PHASE_PULSE_ENCODER: // TODO: 半波旋钮使能 break; case AB_PHASE_PULSE_ENCODER: // TODO: 全波旋钮使能 break; default: result = -RT_ERROR; break; } return result; } rt_err_t pulse_encoder_clear_count(struct rt_pulse_encoder_device *pulse_encoder) { // TODO: 清除计数器实现 return RT_EOK; } rt_int32_t pulse_encoder_get_count(struct rt_pulse_encoder_device *pulse_encoder) { rt_uint32_t count; // TODO: 获取计数值实现 // count = ; return count; } rt_err_t pulse_encoder_control(struct rt_pulse_encoder_device *pulse_encoder, rt_uint32_t cmd, void *args) { rt_err_t result; result = RT_EOK; switch (cmd) { case PULSE_ENCODER_CMD_ENABLE: result = pulse_encoder->ops->init(pulse_encoder); // 使能入口 break; case PULSE_ENCODER_CMD_DISABLE: result = pulse_encoder_close(pulse_encoder); // 关闭入口 break; default: result = -RT_ENOSYS; break; } return result; } // TODO:个人认为还需要实现中断回调函数,主要是通知应用旋钮事件发生 static const struct rt_pulse_encoder_ops _ops = { .init = pulse_encoder_init, .get_count = pulse_encoder_get_count, .clear_count = pulse_encoder_clear_count, .control = pulse_encoder_control, }; int rt_hw_pulse_encoder_init(void) { int i; int result; result = RT_EOK; for (i = 0; i < sizeof(pulse_encoder_obj) / sizeof(pulse_encoder_obj[0]); i++) { pulse_encoder_obj[i].pulse_encoder.type = AB_PHASE_PULSE_ENCODER; pulse_encoder_obj[i].pulse_encoder.ops = &_ops; pulse_encoder_obj[i].pulse_encoder.parent.user_data = &(pulse_encoder_obj[i]); if (rt_device_pulse_encoder_register(&pulse_encoder_obj[i].pulse_encoder, pulse_encoder_obj[i].name, &pulse_encoder_obj[i]) != RT_EOK) { LOG_E("%s register failed", pulse_encoder_obj[i].name); result = -RT_ERROR; } } return result; } INIT_BOARD_EXPORT(rt_hw_pulse_encoder_init)