对象式单片机外部模块驱动编写详解——DAC8552为例

  • 对象式驱动原理
  • DAC8552基本介绍
  • DAC8552驱动抽象
  • 源码文件及其解释
  • 参考资料

具体的代码和例程请参照以下GitHub仓库,记得给我star哦!
https://github.com/xdu-zhangfan/drivers

对象式驱动原理

对象式驱动,借鉴面向对象编程的思想,是将一个具体的外部器件抽象为一个模块,并使用一个函数族实现对于这个外部器件的操作。
——沃兹基硕德(1970.1.1-?),当代面向HelloWorld编程宗师级人物

上面这段话看上去很高级,其实说人话就是:写个驱动,让“我操作具体的器件”变成“用几个函数操作一个结构体”。如果把这几个函数全部写在一个文件里面,加一定的标志体现他们的同一性(比如一个共同的函数前缀),这就叫函数族;然后定义一个结构体,里面包含所有操作器件的时候需要用到的参数,这就叫描述结构体

DAC8552基本介绍

DAC8552是由TI推出的一款16位DAC模块,支持不同下拉等级的掉电模式(不知道有啥用),使用SPI兼容的协议通信,又是阴间24位SPI,具体时序如下:(出自官方数据手册第5页)

明显,这就是一个SYNC(FPGA)兼容SPI(MCU)的协议。SCLK对应SPI的时钟线,在空闲的时候(也就是DIN没有数据的时候),SCLK保持高电平。由此可见,SPI协议的时钟极性应该为高电平(CPOL=1)。同样地,在SYNC拉低之后,DAC8552在时钟的下降沿进行数据采样(如图所示,DIN应该在SCLK下降沿的时候保持稳定),因此实在第一个时钟沿进行采样(CPHA=0)。

至于这24个位到底啥意思,就参照官方数据手册第14页:

咋用,同样在第14页:

还有一个对于Power Down模式下输出阻抗选择的表,也在第14页:

OK,现在让我们分析一下,这个DAC在干啥。

首先,DAC的作用是“将离散信号用某种方式转换成模拟信号”,说人话就是把某个值转成某个电压,值和电压之间一一对应。那么,如果要实现一个DAC模块的驱动,我们需要关注的就是:怎么样把这个值传进去这个模块?

带着这个疑问,我们在数据手册的这几张图表里面找我们需要的信息。第一张时序图里面,只告诉了我们这几根信号线的时序:SPI,24位,MSB First,时钟极性为1(时钟关闭时高电平),时钟相位为0(第一个边沿进行数据读取)。但是传输的这24位的数据中,每一位都代表了什么内容?这个问题可是一点儿也没有提到;第二张,Figure-43,给了一张简表,但是详细内容还是放在了Table-1

我:Table-1,让我康康!
Table-1:哥不要呀!!!

首先,D23D22都是保留位,也就是置0就可以了;D21D20Load ALoad B。根据Description的内容,哪个位被置1,就装载哪个通道的DAC。也就是说,如果想要一个通道输出一个模拟值,不仅要写这个通道的Buffer(缓冲区),还要Load这个通道,具体做法就是在Load A或者Load B这个位置1。D19是“Don‘t care”,不用管,置0就可以了。D18是缓冲区选择,也就是说,当这个位是0的时候,Data的值就会被装到A通道的缓冲区中;当这个位是1的时候,Data的值就会被装到B通道的缓冲区之中。到现在为止,怎么把值传给DAC8552就已经很明确了:构造一个24位的数据,作为一次SPI的发送数据,在这个24位数据的相应位置填入相应的控制位和16位数据,然后通过SPI发送给DAC8552就可以了。

Table-2:哥,还有我……
我:那就连你也一起康了!!!(喜

等等,怎么还有两个位没有讲:D17D16?因为这两个位不重要。观察Table-1可知,发送和装载DAC数据的时候,这俩都置0就可以了。那如果不是0呢?OK,现在看Table-2:Power Down输出阻抗。这两个位决定了一个通道Power Down(十分抱歉,本人翻译能力有限,实在找不到很好的翻译)时的输出阻抗是10K、100K还是高阻。而且,在进行这两个位的设置的时候是不能传数据的。说人话就是:如果这两个位不同时为0,则Data是无效的

DAC8552驱动抽象

在正式开始驱动编写的庞大工程之前,我们首先发出写驱动的哲学三问:

我们从哪里来?
我们是谁?
我们要到哪里去?
我要干嘛?
模块有啥功能?
我要实现模块的啥功能?

一般来说,写驱动实现自己想要的功能即可。比如在这个DAC8552的驱动编写过程中,明显,写入A通道和B通道的缓冲区之后可以不进行Load。也就是说,在写入缓冲区之后的某个时间再进行Load也是可以被允许的。但是,有必要吗?写都写进去了,不输出算个啥操作?一般这种外挂的DAC模块,写入和输出都是同时进行的。如果需要A通道和B通道在装载之后同一时间输出,也可以。也就是说,这个驱动要实现的基本功能就是:写入一个值并且立即输出、写入两个值并且同时输出。

那么,要怎么实现三个基本功能呢?我的方法是直接写三个函数,分别写入A、B和AB通道的值。当然,写入A、B通道的值也可以用同一个函数,然后用一个独立值选择到底是写入A还是写入B。

但是,首先咱们得有对于SPI的抽象封装:

// Abstract HAL library to DAC8552 driver space
static uint32_t dac8552_send_recv_data(dac8552_HandleTypeDef *dev_handle, uint32_t tx_data)
{uint32_t rx_data;HAL_SPI_TransmitReceive(dev_handle->dac8552_hspi, (uint8_t *)&tx_data, (uint8_t *)&rx_data, 1, HAL_MAX_DELAY);return rx_data;
}

为啥要有这个封装?因为这个封装表明了HAL_SPI_TransmitReceive函数并不是我这个DAC8552驱动库里面的函数,但是这个函数是底层的接口(HAL库函数),所以需要用一个函数将底层接口包装到我的驱动库,作为调用底层驱动的唯一途径。

这样做主要有两个好处:减少重复代码和便于移植。

对系统底层的操作进行抽象化封装,可以减少重复的代码。试想一下,如果操作不是这么简单的SPI通信,而是在进行SPI通信的时候还要设置一大堆管脚区分系统状态的话(点名AD9910,一堆管脚,复杂得很),这个底层接口函数必然不会这么简单。如果在进行每一个对器件的操作都写这么多代码,你绝对活不过35岁,就更别说面临什么中年危机了好伐……

第二个理由同样是为了让本就寿元无多的硬件工程师们多保留一点寿元。如果发现一个器件,底层协议用的是IIC,但是高层的数据抽象很像。如果有一个底层封装,那么,在移植驱动的时候就不需要把所有的对于SPI的读写换成IIC的读写,而是直接修改这个函数就可以了。这样大大减少了移植的时间成本和出错可能。

然后就是对于DAC8552设备的抽象。这里直接放代码:

// dac8552.h
// for output impedance power down selection, independent codes
#define DAC8552_OUTIMP_PDN_NONE 0
#define DAC8552_OUTIMP_PDN_10K 1
#define DAC8552_OUTIMP_PDN_100K 2
#define DAC8552_OUTIMP_PDN_HZ 3
typedef struct
{SPI_HandleTypeDef *dac8552_hspi; // HAL SPI interface of DAC8552unsigned char out_imp_pdn;      // output impedance, independentunsigned short int out_value_a; // output value, effective when [out_imp_pdn == 0]unsigned short int out_value_b; // output value, effective when [out_imp_pdn == 0]
} dac8552_HandleTypeDef;

在这个描述结构体中,包含了A、B两个通道的值和Power Down模式输出阻抗的选择,还有一个SPI的HAL库描述结构体指针。这几个值构成了最基本的对于DAC8552的描述,解决了两个大问题:传到哪里?传什么数据?

如下是一个函数的实例:

int dac8552_set_value_a(dac8552_HandleTypeDef *dev_handle)
{dac8552_send_recv_data(dev_handle, (unsigned int)(((1 << 20) |(0 << 18) |(dev_handle->out_imp_pdn << 16) |(dev_handle->out_value_a))));return 0;
}

这个函数肉眼可见地简洁。其中,(1 << 20)是设置Load A,(0 << 18)是指定写入A通道缓冲区。(dev_handle->out_imp_pdn << 16)是进行Power Down的模式更改, (dev_handle->out_value_a)是要写入的数据。等等,为啥Power Down和数据同时设置?一方面,根据数据手册,在设置这两个位不同时为零的时候,数据无效。也就是说,这个在硬件逻辑上已经可以实现了,不需要软件在进行处理。另一方面,从逻辑上讲,也应该大概率不会出现Power Down和数据同时设置的时候数据有效的情况,除非编程者对这个器件真的很不熟悉很不熟悉才会这样做的。综上所述,就直接在函数里面把这两位加上就可以了,没必要再做判断啥的,纯纯给自己找事儿。

源码文件及其解释

// dac8552.h
#ifndef DAC8552
#define DAC8552#include "main.h"// for output impedance power down selection, independent codes
#define DAC8552_OUTIMP_PDN_NONE 0
#define DAC8552_OUTIMP_PDN_10K 1
#define DAC8552_OUTIMP_PDN_100K 2
#define DAC8552_OUTIMP_PDN_HZ 3
typedef struct
{SPI_HandleTypeDef *dac8552_hspi; // HAL SPI interface of DAC8552unsigned char out_imp_pdn;      // output impedance, independentunsigned short int out_value_a; // output value, effective when [out_imp_pdn == 0]unsigned short int out_value_b; // output value, effective when [out_imp_pdn == 0]
} dac8552_HandleTypeDef;int dac8552_init(dac8552_HandleTypeDef *dev_handle, SPI_HandleTypeDef *dev_hspi);
int dac8552_set_value_a(dac8552_HandleTypeDef *dev_handle);
int dac8552_set_value_b(dac8552_HandleTypeDef *dev_handle);
int dac8552_set_value_ab(dac8552_HandleTypeDef *dev_handle);#endif

对于Power Down模式的选择,提供了4个值。DAC8552_OUTIMP_PDN_NONE对应正常模式,也就是不进行Power Down;DAC8552_OUTIMP_PDN_10KDAC8552_OUTIMP_PDN_100KDAC8552_OUTIMP_PDN_HZ则是分别对应了10K、100K和高阻的输出阻抗。当设置这三个输出阻抗的时候,Data的值是无效的。

// dac8552.c
#include "dac8552.h"// Initialize DAC8552
int dac8552_init(dac8552_HandleTypeDef *dev_handle, SPI_HandleTypeDef *dev_hspi)
{if (dev_handle == NULL){return -1;}memset(dev_handle, 0, sizeof(dac8552_HandleTypeDef));dev_handle->dac8552_hspi = dev_hspi;return 0;
}// Abstract HAL library to DAC8552 driver space
static uint32_t dac8552_send_recv_data(dac8552_HandleTypeDef *dev_handle, uint32_t tx_data)
{uint32_t rx_data;HAL_SPI_TransmitReceive(dev_handle->dac8552_hspi, (uint8_t *)&tx_data, (uint8_t *)&rx_data, 1, HAL_MAX_DELAY);return rx_data;
}// send DAC value to DAC8552
int dac8552_set_value_a(dac8552_HandleTypeDef *dev_handle)
{dac8552_send_recv_data(dev_handle, (unsigned int)(((1 << 20) |(0 << 18) |(dev_handle->out_imp_pdn << 16) |(dev_handle->out_value_a))));return 0;
}int dac8552_set_value_b(dac8552_HandleTypeDef *dev_handle)
{dac8552_send_recv_data(dev_handle, (unsigned int)((2 << 20) |(1 << 18) |(dev_handle->out_imp_pdn << 16) |(dev_handle->out_value_b)));return 0;
}int dac8552_set_value_ab(dac8552_HandleTypeDef *dev_handle)
{dac8552_send_recv_data(dev_handle, (unsigned int)(((0 << 20) |(0 << 18) |(dev_handle->out_imp_pdn << 16) |(dev_handle->out_value_a))));dac8552_send_recv_data(dev_handle, (unsigned int)(((3 << 20) |(1 << 18) |(dev_handle->out_imp_pdn << 16) |(dev_handle->out_value_b))));return 0;
}

dac8552_set_value_ab中,先写入A的数据,但是不进行Load A,所以有(0 << 20);之后写B通道并且同时Load A和Load B,因此有(3 << 20),即两位同时设置。

参考资料

TI官方DAC8552介绍页面

对象式单片机外部模块驱动编写详解——DAC8552为例相关推荐

  1. 集成式单片机外部模块驱动编写详解——AD5689为例

    集成式单片机外部模块驱动编写详解--AD5689为例 集成式驱动原理 AD5689基本介绍 AD5689驱动抽象及源码解释 源码 参考资料 具体的代码和例程请参照以下GitHub仓库,记得给我star ...

  2. python中selenium模块驱动谷歌详解

    python中selenium模块驱动谷歌详解 Selenium的介绍.配置和调用 Selenium(浏览器自动化测试框架) 是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中 ...

  3. c语言51单片机外部中断,51单片机外部中断0实例详解

    今天就以51单片机的外部中断0为例,来简单讲解一下单片机中断的用法. 1 51单片机的中断源 51单片机共有5个中断源,分别为:外部中断0 定时器0中断 外部中断1 定时器1中断 串口中断 每一个中断 ...

  4. 继电器写法c语言单片机,单片机驱动继电器详解

    原标题:单片机驱动继电器详解 之前说过,单片机的拉电流和灌电流有限,即输出驱动能力有限,要驱动继电器这类大功率的器件该怎么办呢,答案很简单:用三极管.器件参数该如何确定呢? 手上有一个HFD23的5V ...

  5. linux 设备驱动 ppt,linux设备驱动开发详解讲座ppt

    PPT内容 这是linux设备驱动开发详解讲座ppt下载,主要介绍了设备驱动简介:建立和运行模块:字符驱动:调试技术:并发和竞争:分配内存:硬件通讯:中断处理:块设备驱动,欢迎点击下载. 嵌入式Lin ...

  6. 《Linux设备驱动开发详解(第2版)》隆重出版

    Linux设备驱动开发详解(第2版)(前一版狂销3万册,畅销书最新升级) [新品] 点击看大图     基本信息 * 作者: 宋宝华       * 出版社:人民邮电出版社     * ISBN:97 ...

  7. 《linux设备驱动开发详解》笔记——15 linux i2c驱动

    <linux设备驱动开发详解>笔记--15 linux i2c驱动 15.1 总体结构 如下图,i2c驱动分为如下几个重要模块 核心层core,完成i2c总线.设备.驱动模型,对用户提供s ...

  8. 单片机c语言常用的语句有几条,单片机C语言常用语句详解

    <单片机C语言常用语句详解>由会员分享,可在线阅读,更多相关<单片机C语言常用语句详解(22页珍藏版)>请在人人文库网上搜索. 1.C51编程中常见语句的总结.首先,C51定义 ...

  9. 【SD卡】关于DJYOS下SD卡驱动开发详解

    关于DJYOS下SD卡驱动开发详解 王建忠 2011/6/21 1      开发环境及说明 硬件平台:tq2440(CPU: s3c2440) 操作系统:DJYOS1.0.0 1.1    说明 T ...

最新文章

  1. Python即Python解释器的发展史
  2. 【译】一份通俗易懂的React.js基础指南-2018
  3. pls-00302: 必须声明 组件_vue学习手册-单文件组件使用
  4. git怎么上传文件到别人的仓库_Git将本地代码上传至远程仓库
  5. 年度数据与月度数据转化_山东工会新媒体传播力指数2020年12月数据、年度数据出炉...
  6. Codeforces 235C Cyclical Quest (后缀自动机)
  7. C++ 多线程教程及实现方法
  8. 利用Python自动发送电子邮件
  9. [蓝桥杯][2019年第十届真题]后缀表达式(正解!!)
  10. c语言 如何连接两个程序,C语言连接两个链表程序
  11. Linux下用户、组、权限操作
  12. python编程常用英语单词_Python编程中的英语单词整数
  13. [转载] python将int转为string_python – 在Pandas中将列名从int转换为string
  14. 写java一般用什么系统字体_JavaSE中使用非系统自带的字体
  15. MVC个层次之间的联系
  16. k3梅林刷官改变砖_K3 op强刷回官改变砖,TTL救砖也不行,请大神分析下是不是要换内存了...
  17. 漫画解读软件开发模式
  18. 查看计算机启动项命令,启动项指令命令有哪些?教你设置电脑Windows开机启动项命令...
  19. wsgw_android,衍宏2015最美经纪人 投票查看
  20. 定制 Windows 10 安装程序

热门文章

  1. 【做饭】论如何把肉炒硬
  2. 计算机中k,K和b,B以及kb,kB,Kb,KB的区别
  3. 网传黄牛是这样抢小米手机和电视的
  4. DTLS-SRTP协议学习
  5. 私域社群运营—朋友圈运营策略
  6. 【网络编程】网络基础知识
  7. CGB2104-Day04
  8. 普通用户的计算机接入运营商,运营商应对互联网P2P应用的策略-通信/网络-与非网...
  9. 区块链实现学生信息征信系统:交互演示
  10. wangEditor配置首行缩进包括缩进及取消缩进功能