后面的驱动适配的分析,基本上都按照RTT对接驱动的框架分析,瑞萨已适配框架代码分析,以及启明6M5适配驱动模块的顺序去看。之所以这么操作,是因为从上至下看接口,能够对框架的设计思路有更深的理解,也能更好的梳理出适配新平台时的操作模板。
RTT的DAC框架
RTT DAC框架的源码位于:\components\drivers\misc\dac.c
由于ADC和DAC本质上是一类设备,因此个人认为,从暴露的接口上,dac和adc都应该是一致的,除了ADC是读电压值,DAC是设置电压值以外。
暴露给驱动的接口
DAC注册接口
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops dac_ops =
{
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
_dac_write,
_dac_control,
};
#endif
rt_err_t rt_hw_dac_register(rt_dac_device_t device, const char *name, const struct rt_dac_ops *ops, const void *user_data)
{
rt_err_t result = RT_EOK;
RT_ASSERT(ops != RT_NULL && ops->convert != RT_NULL);
device->parent.type = RT_Device_Class_DAC;
device->parent.rx_indicate = RT_NULL;
device->parent.tx_complete = RT_NULL;
#ifdef RT_USING_DEVICE_OPS
device->parent.ops = &dac_ops;
#else
device->parent.init = RT_NULL;
device->parent.open = RT_NULL;
device->parent.close = RT_NULL;
device->parent.read = RT_NULL;
device->parent.write = _dac_write;
device->parent.control = _dac_control;
#endif
device->ops = ops;
device->parent.user_data = (void *)user_data;
result = rt_device_register(&device->parent, name, RT_DEVICE_FLAG_RDWR);
return result;
}从注册接口上看,DAC的设计逻辑确实和ADC设计逻辑一致。唯一的区别是,DAC是写,ADC是读。
按框架暴露给应用的接口
DAC写接口
static rt_ssize_t _dac_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
rt_err_t result = RT_EOK;
rt_size_t i;
struct rt_dac_device *dac = (struct rt_dac_device *)dev;
rt_uint32_t *value = (rt_uint32_t *)buffer;
for (i = 0; i < size; i += sizeof(int))
{
result = dac->ops->convert(dac, pos + i, value);
if (result != RT_EOK)
{
return 0;
}
value++;
}
return i;
}和ADC类似,DAC也可以实现同一DAC连写多个channel的功能。只是目前自己工作经历上暂未遇到支持一个DAC给多个dac管脚输出不同电压的功能,不确定这种实现是否合理。
DAC控制接口
static rt_err_t _dac_control(rt_device_t dev, int cmd, void *args)
{
rt_err_t result = -RT_EINVAL;
rt_dac_device_t dac = (struct rt_dac_device *)dev;
if (cmd == RT_DAC_CMD_ENABLE && dac->ops->enabled)
{
result = dac->ops->enabled(dac, (rt_uint32_t)args);
}
else if (cmd == RT_DAC_CMD_DISABLE && dac->ops->enabled)
{
result = dac->ops->disabled(dac, (rt_uint32_t)args);
}
else if (cmd == RT_DAC_CMD_GET_RESOLUTION && dac->ops->get_resolution)
{
rt_uint8_t resolution = dac->ops->get_resolution(dac);
if(resolution != 0)
{
*((rt_uint8_t*)args) = resolution;
LOG_D("resolution: %d bits", resolution);
result = RT_EOK;
}
}
return result;
}不知道为何,对驱动层暴露的接口,开关dac接口变成了两个,一个enable,一个disable。个人理解是,这块最好统一,要么暴露一个enable,通过传参的方式是能或关闭,要么暴露两个接口,不需要传参,否则有编码风格差异的问题。
另外,很遗憾,control层并未要求驱动暴露最大输出电压的接口,这也基本上说明目前的框架,并不能实现应用层直接设置电压的需求。
暴露给应用的接口
rt_dac_write
rt_err_t rt_dac_write(rt_dac_device_t dev, rt_uint32_t channel, rt_uint32_t value)
{
RT_ASSERT(dev);
return dev->ops->convert(dev, channel, &value);
}从接口上来说,这个接口直接向下设置寄存器值,意味着应用层需要知道dac的参考电平,dac的精度才能把电压换算成寄存器值。而我的个人理解为,应用层并不需要关心寄存器值是啥,而是关心设下去的电压值是多少,这部分本就该驱动内部实现的。而在后面的分析中也会发现,dac驱动居然没有对应用实现直接设置电压的接口,这是一个问题点。
rt_dac_enable
rt_err_t rt_dac_enable(rt_dac_device_t dev, rt_uint32_t channel)
{
rt_err_t result = RT_EOK;
RT_ASSERT(dev);
if (dev->ops->enabled != RT_NULL)
{
result = dev->ops->enabled(dev, channel);
}
else
{
result = -RT_ENOSYS;
}
return result;
}这个功能很直观,就是使能dac的某个通道。
rt_dac_disable
rt_err_t rt_dac_disable(rt_dac_device_t dev, rt_uint32_t channel)
{
rt_err_t result = RT_EOK;
RT_ASSERT(dev);
if (dev->ops->disabled != RT_NULL)
{
result = dev->ops->disabled(dev, channel);
}
else
{
result = -RT_ENOSYS;
}
return result;
}同DAC enable,只是这个接口的作用是关闭dac的某个通道。
总结
DAC驱动到此已经分析完毕,遗憾的是,在DAC框架中,并未像ADC那样实现应用层直接设置DAC电压的接口,虽然RTT的官方文档也没推荐应用层直接读ADC电压。因此应用层仅能设置寄存器值,也就导致不同的MCU,还得在应用层自行维护最大输出电压和精度信息,带来的副作用就是,更换MCU,DAC处理部分的应用层程序无法通用,而这可能是一个优化点。
我要赚赏金
