PWM算是一个很基础的功能了,在LED灯控制上,为了保证高负荷时pwm灯不会出现明显闪烁感,一般都会直接采用硬件pwm的方式实现呼吸灯之类的效果。而在电机控制上,通过多路PWM信号的组合,可以做出精确的控制效果。RTT也支持这些功能,且瑞萨RA系列已经适配了这套框架,因此我们也需要查看RTT的PWM框架部分,以更加清晰的认识RTT PWM这块的设计。
PWM框架
代码位置
\components\drivers\misc\rt_drv_pwm.c
说来也挺奇怪的,pwm框架的源码命名方式有些无厘头,乍一看名字,还以为是某颗IC的pwm驱动,个人认为最好是和adc,dac一样,直接命名为pwm.c更合适,但实际上,这是PWM的实现框架层所在的位置。
对接驱动层的接口
PWM驱动注册入口
#ifdef RT_USING_DEVICE_OPS static const struct rt_device_ops pwm_device_ops = { RT_NULL, RT_NULL, RT_NULL, _pwm_read, _pwm_write, _pwm_control }; #endif /* RT_USING_DEVICE_OPS */ rt_err_t rt_device_pwm_register(struct rt_device_pwm *device, const char *name, const struct rt_pwm_ops *ops, const void *user_data) { rt_err_t result = RT_EOK; rt_memset(device, 0, sizeof(struct rt_device_pwm)); #ifdef RT_USING_DEVICE_OPS device->parent.ops = &pwm_device_ops; #else device->parent.init = RT_NULL; device->parent.open = RT_NULL; device->parent.close = RT_NULL; device->parent.read = _pwm_read; device->parent.write = _pwm_write; device->parent.control = _pwm_control; #endif /* RT_USING_DEVICE_OPS */ device->parent.type = RT_Device_Class_PWM; device->ops = ops; device->parent.user_data = (void *)user_data; result = rt_device_register(&device->parent, name, RT_DEVICE_FLAG_RDWR); return result; }
PWM模块,对驱动的要求是注册时,需要提供一个struct rt_device_pwm *的设备句柄,并构造一个struct rt_pwm_ops *的控制接口集。而对应用暴露的接口,仅仅留了read,write,control三个接口。个人理解是,由于这些接口并不是直接暴露给应用去调用的(虽然应用也可以通过read,write,control去调用),且PWM只有所有参数配置完后才能使能,因此不需要实现init,open的函数,而close函数,其实也可以通过control函数间接实现,因此也可以省略。
PWM读接口
static rt_ssize_t _pwm_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) { rt_err_t result = RT_EOK; struct rt_device_pwm *pwm = (struct rt_device_pwm *)dev; rt_uint32_t *pulse = (rt_uint32_t *)buffer; struct rt_pwm_configuration configuration = {0}; configuration.channel = (pos > 0) ? (pos) : (-pos); if (pwm->ops->control) { result = pwm->ops->control(pwm, PWM_CMD_GET, &configuration); if (result != RT_EOK) { return 0; } *pulse = configuration.pulse; } return size; }
有点神奇的是,pwm_read接口,居然仅仅是读pwm的脉宽信息,而不是读取pwm的当前配置。
PWM写接口
static rt_ssize_t _pwm_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) { rt_err_t result = RT_EOK; struct rt_device_pwm *pwm = (struct rt_device_pwm *)dev; rt_uint32_t *pulse = (rt_uint32_t *)buffer; struct rt_pwm_configuration configuration = {0}; configuration.channel = (pos > 0) ? (pos) : (-pos); if (pwm->ops->control) { result = pwm->ops->control(pwm, PWM_CMD_GET, &configuration); if (result != RT_EOK) { return 0; } configuration.pulse = *pulse; result = pwm->ops->control(pwm, PWM_CMD_SET, &configuration); if (result != RT_EOK) { return 0; } } return size; }
看到write接口,我大致猜到了为何read接口读取的是脉宽信息了,因为在设置时,也不需要动到周期信息,因此没必要在读的时候读取周期信息。另外,可能RTT PWM框架也有单独封装一个读取所有配置的接口。
PWM控制接口
static rt_err_t _pwm_control(rt_device_t dev, int cmd, void *args) { rt_err_t result = RT_EOK; struct rt_device_pwm *pwm = (struct rt_device_pwm *)dev; struct rt_pwm_configuration *configuration = (struct rt_pwm_configuration *)args; switch (cmd) { case PWMN_CMD_ENABLE: configuration->complementary = RT_TRUE; break; case PWMN_CMD_DISABLE: configuration->complementary = RT_FALSE; break; default: if (pwm->ops->control) result = pwm->ops->control(pwm, cmd, args); break; } return result; }
单纯的看control接口,可以发现这个设置有些怪怪的,PWMN_CMD_ENABLE 和 PWMN_CMD_DISABLE 仅仅是设置了传入数据的参数,那为何要把这个放到传入数据里面来,直接在上层改configuration->complementary的值不就好了吗?
而如果去掉这个接口,会发现,这个control就是调用了驱动的control接口,而control接口需要实现什么功能,未知,只能通过后面的进一步分析才能弄明白。
对接应用层接口
PWM使能入口
rt_err_t rt_pwm_enable(struct rt_device_pwm *device, int channel) { rt_err_t result = RT_EOK; struct rt_pwm_configuration configuration = {0}; if (!device) { return -RT_EIO; } /* Make it is positive num forever */ configuration.channel = (channel > 0) ? (channel) : (-channel); /* If channel is a positive number (0 ~ n), it means using normal output pin. * If channel is a negative number (0 ~ -n), it means using complementary output pin. */ if (channel > 0) { result = rt_device_control(&device->parent, PWMN_CMD_DISABLE, &configuration); } else { result = rt_device_control(&device->parent, PWMN_CMD_ENABLE, &configuration); } result = rt_device_control(&device->parent, PWM_CMD_ENABLE, &configuration); return result; }
PWM打开接口,最大的亮点是备注,说明通道为正的时候是正常的pwm输出,而如果通道是负数,则认为是对应正数通道的互补输出。另外,驱动需要实现PWM_CMD_ENABLE命令,参数为struct rt_pwm_configuration中的complementary变量。
PWM关闭入口
rt_err_t rt_pwm_disable(struct rt_device_pwm *device, int channel) { rt_err_t result = RT_EOK; struct rt_pwm_configuration configuration = {0}; if (!device) { return -RT_EIO; } /* Make it is positive num forever */ configuration.channel = (channel > 0) ? (channel) : (-channel); /* If channel is a positive number (0 ~ n), it means using normal output pin. * If channel is a negative number (0 ~ -n), it means using complementary output pin. */ if (channel > 0) { result = rt_device_control(&device->parent, PWMN_CMD_DISABLE, &configuration); } else { result = rt_device_control(&device->parent, PWMN_CMD_ENABLE, &configuration); } result = rt_device_control(&device->parent, PWM_CMD_DISABLE, &configuration); return result; }
PWM关闭接口,与打开接口相对应,此接口需要驱动适配PWM_CMD_DISABLE功能,具体功能为关闭对应pwm通路功能。
PWM设置配置入口
rt_err_t rt_pwm_set(struct rt_device_pwm *device, int channel, rt_uint32_t period, rt_uint32_t pulse) { rt_err_t result = RT_EOK; struct rt_pwm_configuration configuration = {0}; if (!device) { return -RT_EIO; } configuration.channel = (channel > 0) ? (channel) : (-channel); configuration.period = period; configuration.pulse = pulse; result = rt_device_control(&device->parent, PWM_CMD_SET, &configuration); return result; }
这个函数基本上可以认为是一次性设置对应pwm所有参数的接口(不包括使能关闭),而这个数据需要驱动自行维护。
PWM设置周期入口
rt_err_t rt_pwm_set_period(struct rt_device_pwm *device, int channel, rt_uint32_t period) { rt_err_t result = RT_EOK; struct rt_pwm_configuration configuration = {0}; if (!device) { return -RT_EIO; } configuration.channel = (channel > 0) ? (channel) : (-channel); configuration.period = period; result = rt_device_control(&device->parent, PWM_CMD_SET_PERIOD, &configuration); return result; }
由于驱动自行维护pwm的信息,因此可以单独设置pwm周期。
PWM设置脉宽入口
rt_err_t rt_pwm_set_pulse(struct rt_device_pwm *device, int channel, rt_uint32_t pulse) { rt_err_t result = RT_EOK; struct rt_pwm_configuration configuration = {0}; if (!device) { return -RT_EIO; } configuration.channel = (channel > 0) ? (channel) : (-channel); configuration.pulse = pulse; result = rt_device_control(&device->parent, PWM_CMD_SET_PULSE, &configuration); return result; }
由于驱动自行维护pwm的信息,因此可以单独设置pwm脉宽。
PWM设置死区时间入口
rt_err_t rt_pwm_set_dead_time(struct rt_device_pwm *device, int channel, rt_uint32_t dead_time) { rt_err_t result = RT_EOK; struct rt_pwm_configuration configuration = {0}; if (!device) { return -RT_EIO; } configuration.channel = (channel > 0) ? (channel) : (-channel); configuration.dead_time = dead_time; result = rt_device_control(&device->parent, PWM_CMD_SET_DEAD_TIME, &configuration); return result; }
死区时间在互补模式时有效,主要是为了防止互补模式的两个通道同时导通。
PWM设置相位入口
rt_err_t rt_pwm_set_phase(struct rt_device_pwm *device, int channel, rt_uint32_t phase) { rt_err_t result = RT_EOK; struct rt_pwm_configuration configuration = {0}; if (!device) { return -RT_EIO; } configuration.channel = (channel > 0) ? (channel) : (-channel); configuration.phase = phase; result = rt_device_control(&device->parent, PWM_CMD_SET_PHASE, &configuration); return result; }
个人理解所谓的相位就是PWM高电平出现在整个周期里的位置。在互补模式时才有意义,单通道时,相位没有任何作用。
PWM获取配置入口
static rt_err_t rt_pwm_get(struct rt_device_pwm *device, struct rt_pwm_configuration *cfg) { rt_err_t result = RT_EOK; if (!device) { return -RT_EIO; } result = rt_device_control(&device->parent, PWM_CMD_GET, cfg); return result; }
如果应用不维护脉宽,周期,相位,死区时间等信息的话,此接口就非常有必要了。
总结
平时用RTT的PWM,用的比较多的是单路PWM输出,而且PWM官方提供的PWM操作指引,也仅仅提到了单路PWM输出模式。而实际上,RTT的PWM框架是支持PWM互补模式的,这就需要我们通过查看PWM驱动文件的方式来查看。
另外,由于官方提供的文档并未说明支持互补输出,死区控制之类的功能,不少芯片即使支持这些功能,也不一定有适配这样的功能,在使用PWM模块时需注意。