简介:针对51单片机汇编语言中AD 0809模数转换与DA0832数模转换实验,这份附件压缩包提供了可运行的Keil5工程及参考代码注释,适合电子、自动化、计算机等专业正在学习单片机接口编程的初学者和开发者。包内共10个文件,以uvproj工程文件、asm汇编源码、obj目标文件、lst列表文件等为主,整体仅16KB,结构紧凑,便于直接载入Keil5查看、编译与调试。通过对照源码中的初始化、启动转换、读取结果及输出模拟信号等关键程序段,学习者可以快速掌握AD/DA转换器的控制时序和寄存器配置方法,同时加深对汇编语言硬件编程的理解。目前已有3226人学习或下载,说明该实验方案对同类课程设计与课外实践有较好的参考价值。包内含有完整的工程配置、源程序及构建记录,适合作为实验报告撰写、代码移植或排错分析的基础。
1. 为什么这个实验值得静下心来弄明白
做51单片机汇编实验做到第五个,基本就到了一个分水岭:前面玩LED、数码管、按键,本质上都是数字信号的“搬砖”,高电平亮灯、低电平灭灯,逻辑再绕也绕不出0和1。但AD0809和DA0832这两个芯片一进来,情况就变了——你要开始跟“模拟量”打交道了。
AD0809是8位逐次逼近型模数转换器,负责把电位器旋出来的电压(模拟量)变成单片机认识的8位二进制数(数字量);DA0832是8位数模转换器,方向正好反过来,把数字量变成模拟电压输出。一个是输入侧的“翻译官”,一个是输出侧的“翻译官”,两者配合起来,正好构成了一条完整的“模拟—数字—模拟”信号链路。你可以用AD0809采集电压,在数码管上显示对应的数字量,再用DA0832把数字量还原成模拟电压,整个过程像极了音频设备里的录音和回放,只是精度低一些而已。
这个实验的价值在于:它第一次让你直观看到“同一个物理量”在模拟域和数字域之间来回转换的完整过程。很多初学者学完AD和DA理论后,脑子里只有课本上的框图和公式,真到了实验板上却不知道芯片怎么被“使能”、时序怎么对齐、为什么读出来的数据满量程飘。这篇博文就是围绕这些实操痛点展开的,我把完整的汇编参考代码、硬件连接要点以及调试中容易翻车的细节都整理出来了。
适合谁看呢?正在做51单片机课程设计、准备电子竞赛入门、或者纯粹想搞懂“单片机到底怎么感知外部世界”的读者,都可以花十分钟读完,然后照着代码跑一遍实验。
2. AD0809与DA0832的工作逻辑,先用大白话讲透
2.1 AD0809:8路模拟开关加逐次逼近
AD0809内部结构可以拆成两块:前端是一个8选1模拟开关,IN0到IN7八路输入,通过地址线A、B、C选择到底采哪一路;后端是逐次逼近型ADC,核心是一个比较器、一个逐次逼近寄存器和一个DAC网络。
逐次逼近的整个过程,打个比方:猜一个0到255之间的数,先猜128,比较器告诉你“猜高了”,那就把128砍掉一半取64,再比,再折半……8次之后,二分查找完成,SAR寄存器里的值就是转换结果。所以AD0809转换一次固定需要8个时钟周期,加上一些前后时序,典型转换时间在100微秒左右(取决于CLOCK引脚提供的频率,一般接500kHz到1MHz)。
这个芯片最值得注意的细节是它的输出端。转换完成后,结果并不是直接怼在并行总线上,而是藏在内部的三态锁存器里,只有当OE(输出使能)引脚拉高,数据才会放到D0-D7数据线上。这就给了系统设计很大的灵活性——多片外设可以共享同一组数据总线,谁被选中谁才放数据,不会互相打架。
2.2 DA0832:8位二进制到模拟电流的输出
DA0832是一个基于R-2R梯形电阻网络的乘法型DAC,输入8位数字量,输出的是电流,不是电压。Iout1和Iout2是两个互补电流输出端,满足“Iout1 + Iout2 = 常数”的关系。要让电流变成可用的电压信号,必须在外部接运算放大器做电流到电压的转换,典型接法是Iout1接运放反相输入端,Iout2接地(或接同相端),反馈电阻Rfb可以用芯片内部的,也可以外接。
DA0832有三种工作模式:直通、单缓冲、双缓冲。直通模式下数字量一变,输出立刻跟着变,适合对时序不敏感的场景;双缓冲模式多一级锁存,适合多片DAC需要同步更新输出的场合。实验里我们用单缓冲就够了,CS和XFER接在一起,WR作为写信号,锁存一次输出一次。
2.3 两者共用的“片选—锁存—运算”时序
51单片机的P0口是数据和地址复用总线,实验板上通常用74LS373锁存低8位地址,P2口的高位地址作为片选译码输入。AD0809和DA0832挂在这条总线上,靠地址区分:往某个地址写数据,相当于给DA0832送数字量;从某个地址读数据,相当于把AD0809的转换结果取回来。
理解这个“地址即操作”的思维方式,是这个实验的核心。后面的代码里你会看到,MOVX @DPTR, A和MOVX A, @DPTR并不是普通的存储器读写,它们背后是一整套读/写/锁存/使能控制信号的时序组合。掌握了这一点,以后挂LCD、挂SRAM、挂并口传感器都是一通百通。
3. 硬件连接与实验板上的关键细节
3.1 地址译码是怎么来的
典型实验板的连接方式是这样的:AD0809的ALE和START并在一起接译码电路的写信号,OE接读信号,EOC接单片机P3.3(或经过反相器后接外部中断),CLOCK由ALE输出分频获取(ALE频率是晶振的1/6,如果是12MHz晶振就是2MHz,需要再分频到500kHz左右,有的板子直接用74LS74二分频加四分频)。地址线A、B、C接P0.0-P0.2或P2.0-P2.2,用来选通道。
为了方便讲解,我假设实验板上的地址分配如下:
| 设备 | 地址范围 | 操作 |
|---|---|---|
| AD0809 IN0通道 | 0x7FF8 | 写:启动转换;读:读取结果 |
| AD0809 IN1通道 | 0x7FF9 | 写:启动转换;读:读取结果 |
| DA0832 | 0x7FFF | 写:输出数字量 |
这个地址不是凭空来的,它是P2.7作为片选线、P2.0-P2.2作为通道选择、再配合WR和RD信号组合出来的结果。你实际用的时候不必死记地址,关键看原理图里CEDEC引脚接了哪根线。
3.2 参考电压的接法直接影响测量精度
AD0809的VREF(+)和VREF(-)决定了转换的量程。如果VREF(+)接+5V、VREF(-)接地,那么输入0V对应数字量0x00,输入5V对应0xFF,1LSB对应约19.6mV。
这里有个新手经常忽略的坑:如果实验板上只用+5V电源直接给VREF(+)供电,而电源本身纹波较大,测出来的数据就会在低几位跳来跳去。想看到更稳定的结果,VREF(+)最好经过一个标准的TL431基准源或者至少多串一级RC滤波。当然这只是优化,纯实验环境不处理也能跑,只是数据会“活泼”一些。
3.3 DA0832输出端的运放电路
DA0832的输出电流范围大概是0到2.55mA(满量程时,参考电压5V、反馈电阻约2kΩ的情况下),经过运放后输出电压公式大致是:Vout = -(数字量 / 255)× VREF。如果是单极性输出,Iout2接运放同相端且接地,输出是0到-5V(反相);想得到0到+5V,后端需要再加一级反相放大。
实验常见的做法是用两个运放组成两级:第一级做电流转电压,第二级反相并把幅度调到0到+5V区间。有的实验箱直接用单运放配合正反馈做非反相输出,也行。重点是:用万用表量输出端之前,务必确认运放电源已经接好,并且运放输出不能直接短路到地。
4. 汇编参考代码拆解:每一段都在干什么
4.1 主流程与循环框架
实验的主流程不复杂:初始化栈指针、选通AD0809的IN0通道、启动转换、等待EOC变低、读取结果、把结果同时送到数码管显示和DA0832输出,然后循环往复。但用汇编写出来,每一步都要抠得很细。
下面是一份精简但完整的核心循环代码,压缩包里有注释更全的版本:
; 主程序入口 MAIN: MOV SP, #60H ; 栈指针初始化 MOV P0, #0FFH ; P0口准备输入 MOV P2, #0FFH ; P2口准备输入 LOOP: ; ---------- AD0809 启动转换 ---------- MOV DPTR, #7FF8H ; 指向IN0通道地址 MOVX @DPTR, A ; 写任意值,产生WR脉冲,START启动 + ALE锁存通道 ; ---------- 等待转换结束 ---------- WAIT: JB P3.3, WAIT ; EOC为高电平表示“转换中”,等待变低 ; ---------- 读取转换结果 ---------- MOVX A, @DPTR ; 产生RD脉冲,OE使能,数据总线上的结果存入A ; ---------- 结果送数码管显示 ---------- MOV B, #10H ; 分离高4位和低4位(可以用其他方式) ; 这里简化直接调用显示子程序 LCALL DISPLAY ; ---------- 结果送DA0832输出 ---------- MOV DPTR, #7FFFH ; 指向DA0832地址 MOVX @DPTR, A ; 数字量写入DA0832,输出对应模拟电压 SJMP LOOP ; 无限循环这一段看起来简单,但启动转换那个指令MOVX @DPTR, A是精髓。51单片机的MOVX指令执行时,WR信号会自动拉低再拉高,正好给AD0809的START和ALE制造出一个低脉冲。用一句话记住这个设计:写地址=启动转换,读地址=取结果,这两个操作背后都是靠MOVX指令自动产生的读/写信号去控制芯片的。
4.2 中断还是查询?EOC的两种处理方式
上面的代码用的是查询方式:死等P3.3变化。100微秒的转换时间对单片机来说不长,但对整个系统来说,CPU在这段时间里什么都干不了,这在做实时系统时是极大的浪费。
更优雅的做法是把EOC接到外部中断0或中断1,转换完成后芯片主动通知CPU。中断服务程序里只做一件事:读取转换结果并启动下一次转换。这样主程序可以腾出手来刷新显示、扫描按键、甚至跑PID算法。
实验阶段我建议先把查询方式跑通,因为查询方式逻辑简单、时序清晰,适合观察每一步。中断方式留到进阶阶段再改,那属于“优化思维”的训练。
4.3 数码管显示和DA0832输出共用一个数据
这个实验里,AD采集到的数字量通常要同时做两件事:显示出来给人看,送DA0832输出给外部设备。这就引出一个细节——数据处理的方向要一致。
如果数码管直接显示0x00到0xFF的十六进制数,那是最简单的,但这不符合人类直觉。很多实验要求显示十进制电压值,比如“3.27V”,那就需要二进制转BCD码,再查七段码表送数码管。而DA0832那边要的却是原始二进制,不需要十进制转换。
所以代码里常见的做法是:采集到的A值,一份原样给DA0832,一份复制到显示缓冲区,由显示子程序去做查表转码。两个输出互不干扰、共用一份数据源。这个“一份数据,多路复用”的思路在真实项目里非常普遍。
4.4 代码里的几个细节坑
第一,MOVX前要保证DPTR指向正确的地址,尤其是循环里反复切换AD地址和DA地址时,千万别忘了重新赋值DPTR,否则你会读到上次通道的残留数据或者把数据写到错误外设。
第二,AD0809启动之前,最好把上一次读到的数据先保存起来(比如放到内部RAM缓冲区),因为一旦启动新转换,内部锁存器会被刷新,如果没来得及读取,上一轮数据就丢了。
第三,等待EOC的循环一定要加超时退出机制。真实世界里芯片可能损坏、杜邦线可能松动,如果EOC永远不变化,程序就死在死循环里。可以加一个计数器,比如循环10000次还没等到就报错或跳过,这种防御性编程习惯是嵌入式工程师的基本修养。
5. 调试心得:我在这个实验上踩过的坑
5.1 坑1:启动转换信号保持时间不够
有一次调实验,AD0809死活不出结果,数码管乱跳,用示波器去看START引脚才发现脉冲太窄。AD0809的数据手册要求START信号高电平至少保持100ns,而某些51单片机的MOVX指令产生的写脉冲宽度完全取决于ALE分频后的时钟,如果单片机运行频率过高、写周期太短,就可能不达标。
解决办法很朴实:降低工作频率,或者用软件延时“拉长”启动信号。比如先对地址写一次,加几个NOP,再写一次;有的板子甚至用两个端口引脚手动模拟START时序,不用MOVX。这类问题在Proteus仿真里不容易暴露,一旦上真板子就现原形,所以遇到AD不工作,第一步永远先量信号宽度。
5.2 坑2:数码管动态扫描和AD读取互相抢时间
数码管动态扫描需要每隔几毫秒切换位选和段选,否则人眼能看出闪烁;AD转换也需要及时读取,否则下一轮转换会把没读走的结果覆盖掉。如果主循环里“扫描一轮8位数码管”耗时过长,AD那边的数据早被覆盖了,而你读到的其实是2轮前的“陈年旧货”。
我当时的解决思路是在定时器中断里做显示扫描,主循环专心做AD采集和DA输出。这样两者的时间片彻底分离,互不干扰。实验做完后我把这个“中断刷新显示,主循环干正事”的框架留了下来,之后好几个设计都直接复用,效率提升非常明显。
5.3 坑3:参考电压不分压导致采样值整体偏高
还有一次采集电位器电压,明明旋到中间位置,数据却总在0xE0以上晃。排查半天发现,VREF(+)直接接了电源,而电源实际电压是5.12V,电位器那一路负载又把分压比拉偏了。AD0809连接模拟输入时,信号源阻抗不能太高,否则采样电容充放电时间不够,结果会偏大偏小不定。
这种情况下,正确的做法是在模拟输入端加一个电压跟随器(运放缓冲),降低信号源阻抗;同时VREF(+)用精密基准源供电。实验板上的电位器一般直接接AD输入问题不大,但如果你外接的是高阻传感器(比如光敏电阻、热敏电阻),这一坑几乎必踩。
5.4 坑4:总线复用导致的外设打架
51单片机P0口上往往挂了不止一个外设,AD0809、DA0832、LCD1602甚至存储器都共享总线。如果地址译码器设计得不好,比如两个外设的片选地址重叠或者有漏洞,就会出现“写DA0832结果AD0809也收到了数据”的怪毛病。这种问题示波器量起来信号都对,唯一的排查办法就是对照原理图逐个地址核对译码逻辑。
从工程角度讲,外设多的系统强烈建议用CPLD或逻辑门做严格的地址译码,确保每个地址只激活一个外设。
6. 从实验到实战:这几行代码还能怎么延伸
6.1 改用定时器中断做固定频率采样
实验里是“转换完就显示,显示完就再转换”,频率不固定,完全看代码跑多快。真实的数据采集系统要求固定采样率,比如每秒采样1000次,这时候就要用定时器中断定时触发ADC启动。
实现方式:定时器中断服务程序里写AD0809启动指令,主循环里只做数据处理。这样采样间隔严格等于定时器溢出周期,傅里叶分析、数字滤波才有意义。这个改动看似简单,但思维模式从“查询驱动”变成了“中断驱动”,是嵌入式系统设计思维的一次升级。
6.2 软件滤波:让数据不再乱跳
AD0809的8位分辨率虽然不高,但采集工业现场的电压时,噪声还是会把低1-2位数据打得乱跳。最简单的软件滤波是限幅滤波(上次值和这次值差距超过阈值就丢弃),进阶一点用中值滤波(连续采5次取中间值),再进阶用滑动平均(保存最近16个采样值求平均)。
我个人的建议是:8位分辨率的数据不要上来就做太复杂的滤波,先用中值滤波,同时把多次采样平均的结果绘制成曲线,看看数据分布再决定。
6.3 数据标定:从“数字量”反推“物理量”
实验板上你看到的数字量0x00到0xFF,对应的是0到5V。但实际传感器输出往往不是标准0-5V,比如温度传感器可能输出0.5V到3.5V对应0到100摄氏度。这时候需要做一个线性标定:采集两个已知物理量对应的数字量(比如冰水混合物的0摄氏度和沸水的100摄氏度),解出斜率和截距,以后每次采集到数字量都代入“物理量 = 斜率 × 数字量 + 截距”换算。
这个过程用到的是初中数学,但跑在单片机里就会遇到“小数怎么表示”的问题。汇编里没有浮点数,你要么用定点数(扩大倍数存整数),要么查表。就因为这个原因,很多人做完AD实验后立刻转向C语言开发,不是汇编做不到,而是工程效率摆在那里。
就我个人而言,这个AD0809和DA0832组合实验最大的价值,不是让你记住某个地址等于多少、某个引脚怎么接,而是帮你建立起来“数据从模拟世界出发,经数字化处理,再回到模拟世界”这条完整的思维链路。后头你哪怕换用STM32自带的12位ADC、换用SPI接口的DAC芯片,思考问题的框架依然是这套。压缩包里放了两份可直接编译的汇编工程,一份查询方式、一份中断方式,还有它们在Proteus里的仿真图,照着连一遍,比看十遍文档都管用。
本文还有配套的精品资源,点击获取