后面的驱动适配的分析,基本上都按照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处理部分的应用层程序无法通用,而这可能是一个优化点。