基于PCF8591的DA转换应用:51单片机模拟电压输出与波形生成
简介一份基于PCF8591芯片的DA转换完整工程面向电子专业学生、嵌入式开发者和单片机初学者适合学习数字信号转模拟电压的实现方法。项目通过I2C总线配置芯片清晰演示了从寄存器写入到模拟输出的全过程涵盖DA转换基本原理、PCF8591的引脚功能与操作注意事项。通过阅读源码可了解器件地址、控制命令字的含义。资料包共15个文件主程序用C语言编写另有编译生成的烧录文件、Keil工程配置、列表文件、目标文件以及多个备份整体大小仅31KB便于下载和对照学习。已有1276人浏览学习本工程。借助工程实例能够掌握I2C时序配置、控制命令发送与数值写入流程理解参考电压和硬件连接对输出精度的影响代码结构简洁稍作引脚修改即可移植到其他单片机既适合课程设计也适合入门实操。 很多人做单片机项目时最常用的模拟量输出手段就是PWM。但PWM本质上是数字脉冲真要获得平滑的模拟电压后面还得加滤波电路占面积不说响应还慢。我这次用的是PCF8591这颗带I2C接口的8位ADC/DAC芯片直接通过DA转换输出真正的模拟电压写一次数据就能更新一次输出做程控基准源、简易信号发生器、电机调速预置电压都非常方便。这篇文章就围绕DA_PCF8591的DA转换功能从芯片原理、硬件连接、软件时序到实测波形完整拆一遍全程以51单片机平台为例。PCF8591在电子市场上很常见价格便宜很多传感器模块板上也会集成这颗芯片但大多数教程只讲它的ADC采集功能把DA转换部分一笔带过。我踩过坑之后发现其实PCF8591的DA转换一点都不复杂关键点就那几个控制字节的DAC使能位、参考电压的一致性、I2C时序的稳定性再就是对输出精度的合理预期。搞清楚了这些你就能在几分钟内让板子输出一路稳定的模拟电压。1. 项目概述与核心需求解析1.1 在51单片机上做DA转换为什么选中PCF8591选择PCF8591做DA转换最现实的原因就三个成本低、接线省、代码复用方便。很多51单片机学习板上没有独立的DAC芯片而PWMDAC需要额外搭配积分电路或者专用的PWM转模拟芯片不仅增加物料成本调试时还要反复调整滤波参数。PCF8591只需要两根线SCL和SDA接单片机就能同时拥有4路ADC和1路DAC这也是它在教学和工控领域能长期存在的原因。另外一颗8位DAC能输出0到Vref之间的256级电压对于大多数控制类应用已经够用。比如用来设定比较器阈值、给变频器提供模拟给定信号、做电子负载的恒压设定精度要求并不是16位级别的8位完全可以胜任。若你需要的是高精度低纹波输出那直接绕开这颗芯片去选DAC8560这类专用芯片更合适。也就是说PCF8591的定位就是“够用、便宜、好上手”。1.2 项目所需外设与资源清单这次项目用到的资源非常少整理如下主控STC89C52RC工作电压5VI2C由GPIO模拟DAC模块PCF8591模块A0、A1、A2、A3地址引脚均接地I2C地址为0x48基准电压直接用模块上的VCC5.0V作为Vref输出负载纯阻性负载1kΩ电阻到地用于测量和观察测量工具万用表一块、示波器一台没有示波器可用万用表直流挡逐步验证软件工具Keil C51、STC-ISP烧录工具这里特别说一句PCF8591的I2C地址不是固定的由A0、A1、A2三个引脚的电平决定7位地址就是0x48加上引脚电平对应的偏移量。A0A1A2全接地时是0x48在写代码时8位地址就是0x90写方向读方向是0x91。很多人在这部分混淆了7位地址和8位地址导致通信失败。2. PCF8591的DA转换原理与关键寄存器2.1 芯片内部结构与I2C通信基础PCF8591内部集成了模拟多路开关、采样保持电路、8位逐次逼近型ADC、8位DAC以及I2C总线接口。我们在用到DA转换时核心路径是通过I2C总线把8位数字量写入DAC数据寄存器DAC转换器将这个数字量映射为对应的模拟电压从AOUT引脚输出。整个过程不需要额外的外部锁存器或信号调理电路这也是集成式DAC芯片最大的优势。I2C通信层面PCF8591遵循标准I2C协议支持100kHz标准模式和400kHz快速模式。用单片机GPIO模拟I2C时我的经验是数据线切换前后必须加延时尤其在5V系统上运行在较高的总线速度下没有适当的延时很容易出现数据位错乱。PCF8591的从机地址是固定的7位地址读操作和写操作通过最低位区分写地址是0x90读地址是0x91。I2C总线上每个设备靠地址区分正因为PCF8591支持3位硬件地址配置最多可以在同一条总线上挂8颗PCF8591扩展出8路DA输出这在多路模拟输出的场景下非常实用。2.2 控制字节逐位拆解DAC使能位是重头戏PCF8591每一次写操作都由两部分组成先是控制字节再是DA数据字节ADC模式下是输入通道选择。控制字节是一个8位寄存器每一位的含义如下位名称说明bit7D/A转换标志置1表示当前是DA转换置0表示ADC转换bit6DAC输出使能置1使能DAC模拟输出置0输出脚AOUT为高阻态bit5、bit4模拟输入模式00表示四路单端输入10表示差分输入11表示二差分输入bit3、bit2自动增量标志和ADC通道选择用于ADC通道选择bit1、bit0ADC通道选择低两位与bit3配合选择输入通道这里最容易踩的坑是bit6。如果控制字节没有把bit6置1即使你往DAC数据寄存器里写了数据AOUT引脚也不会输出任何电压或者输出高阻态。很多人调了很久发现没有电压输出最后查出来的原因往往就是这里。DA转换的控制字节最常用的就是0x40也就是bit6置1其余位保持默认。在写代码时你只需要用一条语句PCF8591_DA_Out(value);内部先写控制字节0x40再写数据字节。如果后续要同时使用ADC功能控制字节可以按需改成0x44、0x45等但DA转换和ADC是独立操作控制字节中的DAC使能位并不会影响ADC功能可以放心同时使用。2.3 输出电压与参考电压的数学关系8位DAC的输出电压公式非常直接[ V_{out} V_{ref} \times \frac{D}{256} ]其中D是写入DAC寄存器的数字量范围从0到255Vref是参考电压。举个例子当Vref为5.0V时如果你写入数据128理论输出电压就是5.0乘以128除以256等于2.5V。写入255时输出约为4.98V略低于5V这是因为8位DAC的最大输出是255/256倍的Vref。这个公式意味着两件事。第一Vref必须稳定Vref如果波动输出电压就会跟着波动想要稳定输出就要用精密基准源而不直接拿VCC。第二输出分辨率是Vref除以256对应5V基准下每档约19.5mV这个精度水平适合大多数粗略控制场景但如果你希望调整毫伏级别的电压就需要换更高分辨率的DAC芯片了。实际测试时Vref为5.0V写入数据200实测AOUT对GND电压是3.91V在误差范围内和理论值3.906V符合得很好。这说明只要I2C配置正确这颗芯片的DA转换线性度值得信赖。3. 硬件连接与设计要点3.1 引脚定义与最小系统搭建PCF8591模块的引脚数量少、定义明确连接起来非常容易。下面是标准接线表PCF8591引脚功能接到单片机SCLI2C时钟线P3.6或任意GPIOSDAI2C数据线P3.7或任意GPIOVCC电源正极5VGND电源地GNDAOUTDA模拟输出接负载或万用表VREF参考电压输入5V或外部基准AGND模拟地GNDEXT外部时钟选择接GND使用内部振荡器需要注意两点一是EXT引脚这个引脚是选择外部时钟还是内部时钟的模块上通常默认接地表示使用内部振荡器。如果你用的是裸芯片而不是模块一定要把EXT接到GND否则芯片内部振荡器不工作I2C操作不会有任何响应。二是上拉电阻PCF8591的SCL和SDA是开漏输出总线上必须有上拉电阻模块上一般已经焊好如果是裸芯片需要自己接4.7kΩ上拉电阻到VCC。AOUT和VREF之间没有直接关系VREF决定的是输出电压满量程AOUT才是实际模拟输出脚。有人把VREF悬空结果输出乱跳就是因为参考电压不稳定。3.2 参考电压Vref的选取与精度分析参考电压选5V还是2.5V直接影响DA输出范围。如果直接用VCC作为Vref好处是接线简单但坏处是VCC可能会随着负载变化产生波动尤其在同时驱动继电器、数码管等大电流外设时VCC上的毛刺会耦合到模拟输出上导致输出的电压在小范围内抖动。我测试过一次给板子同时加了一路继电器切换AOUT上能看到大约30mV的跳动非常影响体验。要改善这个问题最简单的方法是给Vref单独接一个稳压基准源。常用方案是AMS1117-2.5或者TL431接一个2.5V或者3.3V的基准这样DA输出范围变成0到2.5V虽然幅度变小了但稳定性大幅提升。实际项目里如果你只需要0到2.5V以内的信号完全建议使用外部基准而不是VCC。使用外部基准时需要注意基准源输出的驱动能力有限只允许接在VREF引脚上不要去给其他负载供电。实测中Vref接2.5V基准写入255输出电压2.49V稳定性很好万用表读数基本不跳。这说明基准源的选型比芯片本身对精度的影响还要大因为DA转换器本身只是把数字量按比例分压参考电压的精度直接线性影响最终输出精度。3.3 输出驱动能力与负载匹配PCF8591的AOUT引脚内部是一个8位电阻分压网络加运放输出它能提供的电流非常有限数据手册上一般标注输出阻抗在几十欧姆到几百欧姆之间。直接用它驱动大电流负载比如驱动喇叭或者LED灯珠会发现电压被拉低甚至输出波形严重畸变。这是因为负载阻抗太低超出了DAC的输出能力。解决办法是在AOUT后面加一个电压跟随器用常见的LM358、LM324单电源运放即可。电压跟随器的输入阻抗极高不会影响DA输出输出阻抗极低能提供几毫安的驱动能力足以驱动大多数后续电路。我的实际接法就是AOUT接到LM358的输入端运放输出端接到负载实测负载从10kΩ变成1kΩ输出电压几乎不变效果立竿见影。如果负载本身是容性负载比如直接接一个1uF电容到地PCF8591也会表现出不稳定的现象输出电压建立变慢甚至出现轻微振荡。加运放缓冲之后这个问题也被隔离掉了所以在设计时一定要考虑负载性质不能想当然地把DAC输出直接硬接负载。4. 软件实现从I2C驱动到DA波形输出4.1 51单片机上的I2C驱动实现用GPIO模拟I2C第一步是把时钟线SCL和数据线SDA配置成准双向IO口。写高电平时直接把引脚置1写低电平时置0。下面是我常用的I2C底层驱动已经在多个项目里验证过标准速度和快速模式都能正常工作。sbit SCL P3^6; sbit SDA P3^7; void I2C_Delay(void) { _nop_(); _nop_(); _nop_(); _nop_(); } void I2C_Start(void) { SDA 1; SCL 1; I2C_Delay(); SDA 0; I2C_Delay(); SCL 0; } void I2C_Stop(void) { SDA 0; SCL 1; I2C_Delay(); SDA 1; I2C_Delay(); } void I2C_SendByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { SDA (dat 0x80) ? 1 : 0; dat 1; SCL 1; I2C_Delay(); SCL 0; I2C_Delay(); } }注意I2C_Start和I2C_Stop的时序SDA必须在SCL为高电平时产生下降沿或上升沿这是起始和停止条件的定义。很多人在模拟I2C时先拉低SCL再拉低SDA导致从设备把普通数据位当成起始条件通信错乱。这个顺序必须按照标准I2C协议来。I2C_Delay用的是空指令在STC89C52RC的12MHz晶振下实际延时约几百纳秒满足100kHz模式的要求。如果你用的单片机频率更高或者更低可以适当调整延时数量。我测试过STC15系列主频24MHz时同样代码也能正常工作只是建议延时多一些更稳定。4.2 PCF8591 DA转换写操作完整流程DA转换写操作的完整流程是先发I2C起始条件接着发送设备地址0x90写方向等待从机应答然后发送控制字节0x40使能DAC输出等待应答再发送待转换的数据字节等待应答最后发送停止条件。整个过程只需要调用几个底层函数。void PCF8591_DA_Out(unsigned char dat) { I2C_Start(); I2C_SendByte(0x90); // 设备地址写方向 I2C_WaitAck(); I2C_SendByte(0x40); // 控制字节bit61 使能DAC输出 I2C_WaitAck(); I2C_SendByte(dat); // 待转换的8位数据 I2C_WaitAck(); I2C_Stop(); }代码里的I2C_WaitAck是等待从机拉低SDA产生应答信号。PCF8591在接收到每个字节后都会正常应答如果发现主机发完地址后一直收不到应答大概率是地址不对或者硬件连接有问题比如A0A1A2接地后写地址应该是0x90而不是0x48这个坑我已经替大家踩过了。关于控制字节强调一下0x40是DAC使能且工作于普通模式如果你想在DA输出的同时让ADC工作在自动增量模式可以改成0x44。但这个位域的调整不影响DAC部分DAC输出始终由bit6控制。控制字节是每一位独立的开关互不干扰理解这一点就不会在配置上畏手畏脚。4.3 输出锯齿波、三角波与正弦波的实现做DA转换的最终目标通常是输出波形而不只是一个固定电压。借助PCF8591在程序里改一个循环就能输出规则波形。锯齿波最简单让数据从0递增到255然后跳回0如此往复while (1) { for (i 0; i 256; i) { PCF8591_DA_Out(i); delay_ms(2); } }三角波则是先递增后递减同样用循环即可。更复杂的正弦波需要准备一个查找表。我直接在程序里内嵌一个256点的正弦表数据预先算好运行时循环查表输出const unsigned char sine_table[256] { 128, 131, 134, 137, 141, 144, ..., 128 }; while (1) { for (i 0; i 256; i) { PCF8591_DA_Out(sine_table[i]); delay_ms(1); } }波形输出频率由每两个数据点之间的延时决定。比如256点、每点延时1ms输出基频大约是1000除以256再乘以1约3.9Hz。要提高频率就得把延时缩短或者减少点数。用示波器实测锯齿波时能看到明显的256级阶梯每级台阶高度约为19.5mV这个阶梯感是8位DAC固有特性不要误以为电路有问题。如果你的目的是更平滑的波形可以外接一个RC低通滤波器时间常数取相邻输出间隔的5倍左右能有效滤掉台阶。5. 实测记录与问题排查5.1 使用万用表与示波器验证DA输出写好代码烧录后第一件事是验证固定电压输出而不是直接看波形。这是我最常推荐的调试顺序。先让程序输出一个固定数据比如100然后用万用表测AOUT对GND的电压。理论上5V基准下应该输出1.953V实测误差只要在几十毫伏以内都属于正常。确认AOUT电压正常后再验证程控改变输出。写一个按键切换代码每按一次按键输出数据加10记录万用表电压变化看是否按步进递增。这一步能确认I2C写操作的数据通路没有问题也能侧面验证控制字节配置正确。最后用示波器观察动态波形。把程序改成锯齿波输出示波器探头接AOUT和GND应能看到标准的阶梯锯齿波。如果波形失真严重或者根本没有波形可能是AOUT管脚没接触好或者示波器探头档位选错了。示波器观察时建议使用直流耦合带宽限制可以打开避免高频噪声干扰观察效果。5.2 常见问题与排查方法速查表在实际调试过程中我收集了以下几种常见问题整理成速查表供参考问题现象可能原因解决方法输出电压始终为0V控制字节bit6未置1确认控制字节为0x40输出电压始终接近Vref数据总是写入255检查数据变量是否被覆盖I2C发地址后无应答7位地址和8位地址混淆写地址用0x90不是0x48输出电压跳动明显Vref不稳定改用外部基准源或加去耦电容DAC输出带了负载电压跌落AOUT驱动能力不足加电压跟随器锯齿波出现台阶不平滑8位DAC固有量化效应加RC低通滤波读DAC寄存器一直是旧值没有触发新的写操作或伪读重新写入控制字节再做一次伪读操作还要特别提醒一点PCF8591是一个I2C设备如果总线上还挂了其他I2C设备要注意不要地址冲突也要注意每个设备的写操作之间留足够时间。有的人在总线时序不对时会得到间歇性的错误输出这时候不要频繁换芯片先用示波器抓I2C波形检查起始条件、地址字节、应答位是否正常往往一眼就能看出问题。5.3 关于精度和稳定性的几个实用经验最后分享几个和精度与稳定性相关的实操经验。第一PCF8591输出端并一个小电容在AOUT和GND之间是有益的一般用0.1uF就能滤掉大部分高频噪声但不建议用太大否则输出响应会变慢。第二如果你观察到输出电压在某个档位附近出现不稳定的跳变优先检查电源地线是否干净I2C总线上是否有干拢。第三PCF8591模块上如果有电位器或者跳线注意区分这是给ADC输入用的和DAC输出没有关系别被板子上的标注带偏。根据我个人修过不少模块的经验PCF8591的DA转换功能只要控制字节写对了剩下的基本就是I2C通信的问题。如果你把I2C的波形用示波器抓出来对照时序图一行一行看很快就能定位问题。调通了DA输出之后再做ADC采集、闭环控制整条链路就都熟门熟路了。这个项目做完的效果就是一个几十块钱的51开发板加一个几块钱的PCF8591模块就能够输出任意预设电压和简单波形做教学演示或者常规控制方案完全够用。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →