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模块时需注意。
我要赚赏金
