这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » 软件与操作系统 » rtthread脉冲编码器框架解析

共5条 1/1 1 跳转至

rtthread脉冲编码器框架解析

助工
2024-10-22 20:25:22     打赏

背景

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





关键词: rtthread     脉冲编码器     框架     解析    

专家
2024-10-23 00:27:43     打赏
2楼

谢谢分享


专家
2024-10-23 06:46:47     打赏
3楼

学习一下


专家
2024-10-23 19:46:39     打赏
4楼

感谢分享


菜鸟
2024-10-25 09:57:44     打赏
5楼

好文,谢谢分享。


共5条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]