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驱动,函数内部也得按照标准实现对应的接口。
我要赚赏金
