这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » 软件与操作系统 » 瑞萨已适配的PWM驱动分析

共7条 1/1 1 跳转至

瑞萨已适配的PWM驱动分析

助工
2024-11-02 18:11:31     打赏

PWM驱动通用结构

   通过对pwm框架层代码的阅读,我们可以获得如下pwm驱动模板:

struct pwm_param
{
    struct rt_device_pwm            pwm_device;
    
    // 驱动内部维护参数
};

static rt_err_t drv_pwm_control(struct rt_device_pwm *device, int cmd, void *arg)
{
    struct rt_pwm_configuration *configuration = (struct rt_pwm_configuration *)arg;
    struct pwm_param *pwm_device = (struct pwm_param *)device->parent.user_data;

    switch (cmd)
    {
    case PWM_CMD_ENABLE: // PWM模块使能
        return pwm_enable(pwm_device, configuration, RT_TRUE);
    case PWM_CMD_DISABLE: // PWM模块禁用
        return drv_pwm_enable(pwm_device, configuration, RT_FALSE);
    case PWM_CMD_GET: // 读取PWM信息
        return drv_pwm_get(pwm_device, configuration);
    case PWM_CMD_SET: // 设置PWM信息
        return drv_pwm_set(pwm_device, configuration);
    default:
        return -RT_EINVAL;
    }

    return RT_EOK;
}

static struct rt_pwm_ops drv_ops =
{
    drv_pwm_control
};


int rt_hw_pwm_init(void)
{
    struct pwm_param *pwm = (struct pwm_param *)rt_malloc(sizeof(struct pwm_param));
    char *name = "pwm0";

    // 硬件资源初始化

    // 注册pwm设备
    return rt_device_pwm_register(&pwm->pwm_device, name, &drv_ops, pwm);
}
INIT_BOARD_EXPORT(rt_hw_pwm_init);

瑞萨适配的PWM驱动分析

注册接口

#define PWM_DRV_INITIALIZER(num)        \
    {                                   \
        .name = "pwm"#num ,             \
        .g_cfg = &g_timer##num##_cfg,   \
        .g_ctrl = &g_timer##num##_ctrl, \
        .g_timer = &g_timer##num,       \
    }

static struct ra_pwm ra6m4_pwm_obj[BSP_PWMS_NUM] =
{
#ifdef BSP_USING_PWM0
    [BSP_PWM0_INDEX] = PWM_DRV_INITIALIZER(0),
#endif
#ifdef BSP_USING_PWM1
    [BSP_PWM1_INDEX] = PWM_DRV_INITIALIZER(1),
#endif
#ifdef BSP_USING_PWM2
    [BSP_PWM2_INDEX] = PWM_DRV_INITIALIZER(2),
#endif
#ifdef BSP_USING_PWM3
    [BSP_PWM3_INDEX] = PWM_DRV_INITIALIZER(3),
#endif
#ifdef BSP_USING_PWM4
    [BSP_PWM4_INDEX] = PWM_DRV_INITIALIZER(4),
#endif
#ifdef BSP_USING_PWM5
    [BSP_PWM5_INDEX] = PWM_DRV_INITIALIZER(5),
#endif
#ifdef BSP_USING_PWM6
    [BSP_PWM6_INDEX] = PWM_DRV_INITIALIZER(6),
#endif
#ifdef BSP_USING_PWM7
    [BSP_PWM7_INDEX] = PWM_DRV_INITIALIZER(7),
#endif
#ifdef BSP_USING_PWM8
    [BSP_PWM8_INDEX] = PWM_DRV_INITIALIZER(8),
#endif
#ifdef BSP_USING_PWM9
    [BSP_PWM9_INDEX] = PWM_DRV_INITIALIZER(9),
#endif
};

static rt_err_t drv_pwm_control(struct rt_device_pwm *, int, void *);
static struct rt_pwm_ops drv_ops =
{
    drv_pwm_control
};

static rt_err_t drv_pwm_control(struct rt_device_pwm *device, int cmd, void *arg)
{
    struct rt_pwm_configuration *configuration = (struct rt_pwm_configuration *)arg;
    struct ra_pwm *pwm_device = (struct ra_pwm *)device->parent.user_data;

    /**
     * There's actually only one GPT timer with 10 channels. In this case, the
     * timer is separated into 10 PWM devices, so each device has only one
     * channel.
     */
    if (configuration->channel != 0)
    {
        return -RT_EINVAL;
    }

    switch (cmd)
    {
    case PWM_CMD_ENABLE:
        return drv_pwm_enable(pwm_device, configuration, RT_TRUE);
    case PWM_CMD_DISABLE:
        return drv_pwm_enable(pwm_device, configuration, RT_FALSE);
    case PWM_CMD_GET:
        return drv_pwm_get(pwm_device, configuration);
    case PWM_CMD_SET:
        return drv_pwm_set(pwm_device, configuration);
    default:
        return -RT_EINVAL;
    }

    return RT_EOK;
}

int rt_hw_pwm_init(void)
{
    rt_err_t ret = RT_EOK;
    rt_err_t rt_err = RT_EOK;
    fsp_err_t fsp_err = FSP_SUCCESS;

    for (int i = 0; i < BSP_PWMS_NUM; i++)
    {
        fsp_err = R_GPT_Open(ra6m4_pwm_obj[i].g_ctrl,
                             ra6m4_pwm_obj[i].g_cfg);

        rt_err = rt_device_pwm_register(&ra6m4_pwm_obj[i].pwm_device,
                                        ra6m4_pwm_obj[i].name,
                                        &drv_ops,
                                        &ra6m4_pwm_obj[i]);

        if (fsp_err != FSP_SUCCESS || rt_err != RT_EOK)
        {
            ret = -RT_ERROR;
        }
    }

    return ret;
}

INIT_BOARD_EXPORT(rt_hw_pwm_init);

   从代码上看,注册接口基本上与分析的驱动框架模板一致,唯一的区别为,模板只注册了一个pwm设备,但瑞萨适配了多个PWM模块。而经过上面代码模板的梳理,这里需要关注的也就仅仅限于几个部分了,一个是ra6m4_pwm_obj,很明显,这个部分是需要瑞萨的RASC生成的。另一部分与模板一致,PWM的开关读写接口,此部分需要针对性分析实现。

     另外,PWM_DRV_INITIALIZER这个参数,属于RASC生成的部分,也就是说,我们在基于瑞萨框架适配pwm驱动,其实就是生成PWM_DRV_INITIALIZER中对应的那些参数。

开关PWM接口

    瑞萨驱动的开关PWM接口合并成了一个,通过enable标志位去做开关的识别。

static rt_err_t drv_pwm_enable(struct ra_pwm *device,
                               struct rt_pwm_configuration *configuration,
                               rt_bool_t enable)
{
    fsp_err_t err = FSP_SUCCESS;

    if (enable)
    {
        err = R_GPT_Start(device->g_ctrl);
    }
    else
    {
        err = R_GPT_Stop(device->g_ctrl);
    }

    return (err == FSP_SUCCESS) ? RT_EOK : -RT_ERROR;
}

    从代码上看,开关接口直接调用了瑞萨自己编写的软件适配层的开关接口,所使用到的参数也是RASC生成的参数。

读接口

static rt_err_t drv_pwm_get(struct ra_pwm *device,
                            struct rt_pwm_configuration *configuration)
{
    timer_info_t info;
    if (R_GPT_InfoGet(device->g_ctrl, &info) != FSP_SUCCESS)
        return -RT_ERROR;

    configuration->pulse =
        _convert_counts_ns(device->g_cfg->source_div, device->g_cfg->duty_cycle_counts);
    configuration->period =
        _convert_counts_ns(device->g_cfg->source_div, info.period_counts);
    configuration->channel = device->g_cfg->channel;

    return RT_EOK;
}

    虽然应用层只使用了占空比,但是驱动层将占空比,周期和通道信息都上报上去了。

写接口

static rt_err_t drv_pwm_set(struct ra_pwm *device,
                            struct rt_pwm_configuration *conf)
{
    uint32_t counts;
    fsp_err_t fsp_erra;
    fsp_err_t fsp_errb;
    rt_err_t rt_err;
    uint32_t pulse;
    uint32_t period;
    struct rt_pwm_configuration orig_conf;

    rt_err = drv_pwm_get(device, &orig_conf);
    if (rt_err != RT_EOK)
    {
        return rt_err;
    }

    /* Pulse cannot last longer than period. */
    period = conf->period;
    pulse = (period >= conf->pulse) ? conf->pulse : period;

    /* Not to set period again if it's not changed. */
    if (period != orig_conf.period)
    {
        counts = _convert_ns_counts(device->g_cfg->source_div, period);
        fsp_erra = R_GPT_PeriodSet(device->g_ctrl, counts);
        if (fsp_erra != FSP_SUCCESS)
        {
            return -RT_ERROR;
        }
    }

    /* Two pins of a channel will not be separated. */
    counts = _convert_ns_counts(device->g_cfg->source_div, pulse);
    fsp_erra = R_GPT_DutyCycleSet(device->g_ctrl, counts, GPT_IO_PIN_GTIOCA);
    fsp_errb = R_GPT_DutyCycleSet(device->g_ctrl, counts, GPT_IO_PIN_GTIOCB);
    if (fsp_erra != FSP_SUCCESS || fsp_errb != FSP_SUCCESS)
    {
        return -RT_ERROR;
    }

    return RT_EOK;
}

    从驱动实现上看,瑞萨依次做了如下操作:1. 获取当前PWM的配置 2. 对比当前配置与需要设置配置的周期信息,若不一致,则设置新的周期 3. 设置A相占空比 4. 设置B相占空比。

     而从最后两步,我们可以发现,瑞萨的PWM应该都是A B相成对设计的,当然可能可以只用一相。而不像市面上不少带PWM方案,实际上每组PWM都是单独存在的情况,个人理解这种做法是为了有更好的适配性。

总结

   至此,PWM模块的分析已经完毕。从代码上看,不知道为何RTT的pwm模块的封装与之前adc,dac之类的封装不一致,其他模块都是将实现按不同函数入口封装在一个结构体中。而pwm框架是将实现封装至结构体中的函数实现中,也就是说,适配PWM驱动,函数内部也得按照标准实现对应的接口。







关键词: 瑞萨     适配     驱动     pwm    

专家
2024-11-02 22:06:43     打赏
2楼

感谢分享


专家
2024-11-02 22:08:21     打赏
3楼

感谢分享


专家
2024-11-02 22:09:58     打赏
4楼

感谢分享


专家
2024-11-04 07:55:48     打赏
5楼

感谢分享


助工
2024-11-04 21:17:39     打赏
6楼

Daplink如何配置RTT?


菜鸟
2024-11-05 11:31:32     打赏
7楼

感谢分享,非常有意义


共7条 1/1 1 跳转至

回复

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