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