背景
脉冲编码器,在硬件上最常见的就是旋钮,在旋钮旋转时,对外接出的两个管脚依次产生低电平并依次返回高电平,电平下降或上升的先后顺序,代表旋钮旋转的方向。由于管脚中断间隔时间一般都比较长(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)
我要赚赏金
