H44基于51单片机ADDA综合仿真proteus设计,keil程序,c语言,AT89C51
作者:单片机仿真   类别:51单片机仿真    日期:2026-11-10 13:00:09    阅读:7 次   消耗积分:0 分

H44 用一片 51 单片机把 AD 和 DA 摆进同一张图:DS18B20 单总线测温、ADC0832 三线时序采电位器电压、DAC0832 直通模式把 P1 上的数还原成模拟电压推喇叭,ULN2003A 驱动四相步进电机由定时器中断走 8 拍,全程用整数定点数绕开浮点,附 Keil 程序、Proteus 仿真与设计报告。

H44基于51单片机ADDA综合仿真

01 设计任务

功能介绍:

用一块 51 单片机带四个模块,一头采模拟量、一头出模拟量,中间还要转个电机:

1、测温:DS18B20 单总线采环境温度,LCD 第一行显示 T: 041 C;

2、测电压:ADC0832 第八通道采电位器 RV3 上的 0~5V,LCD 第二行显示 AD: 4.1 V,转电位器数值跟着变;

3、DA 输出:把采到的 AD 值直接送到 P1 口,经 DAC0832 还原成模拟电压,运放驱动喇叭输出;

4、电机控制:K1 正转、K2 反向、K3 停止,ULN2003A 驱动四相步进电机连续旋转。

 特别注意:这是全系列第一个把 AD 和 DA 摆在同一张图里的项目。前面 H36、H41 用过 ADC0832 单独测电压,H23、H37 单独玩过步进电机,但都是"各干各的"——而这一篇里,AD 采进来的数字量会原封不动地从 P1 口送出去,再被 DAC0832 变回模拟电压。

程序里其实只有两句:一句把 ADC0832 通道 0 的读数取进变量 adc,一句把这个变量原样赋值给 P1 口。

"采进来、再送出去",这就是 AD/DA 综合的全部含义。 剩下的测温、显示、电机,都是为了把这张图填满、让它在答辩时看得过去。


02 硬件:六块板子挤在一张图里

先看资料目录,一共八样东西:

资料目录ADDA综合仿真proteus整机原理图
模块器件引脚干什么
单片机AT89C51晶振 24MHz,C1/C2 22pF主控
液晶LCD1602(LM016L)数据 P0(经 RP1 上拉),RS/RW/E = P2.0/P2.1/P2.2两行显示温度、电压
测温DS18B20DQ = P3.4,R1 4.7k 上拉单总线温度
ADADC0832CS/CLK/DIO = P2.5/P2.6/P2.7,CH0 接 RV38 位串行模数转换
DADAC0832 + OPAMPD0~D7 = P1.0~P1.78 位模数转换,驱动喇叭
电机ULN2003A + 四相步进电机IN1~IN4 = P3.0~P3.3达林顿驱动
按键K1/K2/K3P3.5/P3.6/P3.7,另一端接地正转/反向/停止

三个从图上才能看出来的细节:

一是 P0 还是得上拉。 P0 口送 LCD 数据,中间串了一个 RP1(RESPACK-8 排阻)——P0 是开漏结构,不上拉就驱不动。这条规矩在 H43 秒表里也踩过,凡是用 P0 都跑不掉。

二是 DAC0832 工作在直通模式。 看它的控制脚:CS、WR1、WR2、XFER全部接地,ILE 接 VCC。这意味着它根本不需要"写信号"——P1 口上是什么数,输出电流立刻就是什么数,没有锁存、没有握手。所以程序里 P1 = adc; 这一句写完就生效了,不用管时序。

如果哪天要"单片机算好一组波形、再让 DA 按节奏输出",就得把 WR 接回单片机做锁存控制,那时候才有写时序可言。教学演示用直通最省事。

三是 ADC0832 的 DI 和 DO 并成了一根线。 图上第 5 脚 DI 和第 6 脚 DO 短接在一起,接同一个网络标号 AD_IO——所以才可能只用 P2.7 一根线就完成"送命令 + 收数据"。这是 ADC0832 的常规玩法:它本来就不是标准的四线 SPI,DI/DO 分时复用,省一根 IO。

03 温度:DS18B20 和一个"放大 100 倍"的技巧

先看工程本身,只有一个 main.c,编译下来 data=36.1、code=1724,0 错 0 警告:

Keil工程编译结果

code 才 1.7KB,AT89C51 的 4K 空间用不到一半——这一个工程把 LCD 驱动、单总线、SPI 时序、步进电机全塞进去了,51 的代码空间其实比想象中宽裕,紧张的是 RAM(data=36.1 字节,128 字节里也才用三成)。

DS18B20测温模块

DS18B20 的读温度流程是固定的四步:

先复位,发一条 0xCC,再发 0x44 启动一次温度转换;硬等 750ms 之后重新复位、重新发 0xCC,发 0xBE 读暂存器,最后连读两个字节,先读到的记成 tl、后读到的记成 th。

0xCC 跳过 ROM 是省事写法:DS18B20 每次通信都要先"点名"(匹配 ROM 命令 0x55 + 8 字节 ID),总线上只有一颗时直接跳过点名,能省 8 个字节的收发。要是想挂多颗测温,就必须老老实实读 ID 了。

真正值得学的是后面这段——它没有用 float。

DS18B20 回来的 16 位原始值,低 4 位是小数(分辨率 0.0625℃),高 12 位是整数。教科书做法是 temp = raw * 0.0625; 得到一个 float——但 51 上跑浮点是要拿命换的:一个 float 乘法编译出来几百字节、执行上百个机器周期。

这份程序的做法是先把结果放大 100 倍存成整数:

先把 t[0] 的低 4 位单独留下当小数部分,t[0] 右移两位,小数部分乘 6 记成 y,再用 t[1] 乘 100 加上 y,装进温度变量。

× 6 是 0.0625 × 100 = 6.25 取整的近似——用一次整数乘法换掉了浮点库。返回值是"放大 100 倍的摄氏度",41℃ 就返回 4100。主程序里 ReadTemperature()/100 拿整数部分,%100 拿小数部分,要几位取几位。

这就是嵌入式里最常用的定点数思路:约定一个缩放倍数,全程用整数算,只在显示的最后一步才"假装"有小数点。

一个副作用得说清楚:显示是 3 位(buf[0]~buf[2] 拼百十个位),所以 41℃ 会显示成 041。不难看,但也谈不上好看——想显示 41.0 就得改成"两位整数 + 一位小数"的拼法,那是纯显示层的活,跟测温无关。

这几行真正干活的是两次调用:一次读温度,一次把返回值除以 100 取整数部分赋给显示变量,赋值前后各夹了一条中断控制语句。

读温度前后把总中断关了。 因为 DS18B20 的单总线时序是微秒级的,靠 _nop_() 和循环延时凑出来的——如果读到一半被定时器中断打断(这个工程里 T1 每 2ms 就进一次中断,中断里还要跑一遍步进电机),时隙就被拉长了,数据直接读错。

凡是"时序靠软件延时凑"的器件(单总线、红外 1838、DHT11),读写前后都建议关中断。 这是比"多写几行注释"更实在的防御。

04 AD:三线时序和一个除法

ADC0832模数转换模块

ADC0832 是 8 位、串行、单通道(这里是 CH0)。它的时序跟标准 SPI 不一样,得按它自己的节奏来:

一次转换分两段:前三个时钟脉冲把起始位、单端模式位和通道号从 DIO 送出去,中途把 DIO 交还给芯片,后 8 个时钟脉冲把转换结果一位一位收进一个字节,最后拉高片选收尾。

三个关键点:

第一,前三拍是"我说话",第四拍开始"它说话"。 DIO 是双向线,前 3 个下降沿单片机往里送(起始位、模式位、通道号),第 4 个下降沿之后单片机必须把自己置成输入,否则两边打架。代码里 ADC0832_DIO = 1; 这一句看着像"送个 1",其实是在释放总线。

第二,高位在前。dat <<= 1; dat |= DIO; 是先移再收,所以第一个收到的位最后落在 bit7。写反了(先收再移)得到的就是位反序的数——读出来永远对不上。

第三,CS 拉高才算一次转换结束。 少了这一句,下次转换的起始位会被当成上一次的延续。

采一次 AD 存进 adc,把这个数直接写给 P1,再按 50 除以 255 换算成一个两位整数,拆出十位和个位,用两次 LCD 定位写填进第二行。

为什么是 *50/255 而不是 *500/255? 因为 LCD 上预置的模板是 AD:   . V,小数点已经烧在屏幕上了,程序只需要往小数点两边各填一位数字。所以 4.1V 是把 41 拆成 4 和 1 分别写进第 6 格和第 8 格。

50/255 的物理含义是:8 位 ADC 满量程 255 对应 5.00V,所以每 1 个 LSB ≈ 0.0196V,乘 50 就是"伏的十倍"。 显示精度 0.1V,跟 8 位 ADC 的实际分辨率(19.6mV)刚好匹配——再多显示一位就是自欺欺人。

顺带说个 8 位 ADC 的天花板:5V 量程 ÷ 256 ≈ 19.6mV。也就是说输入电压变化小于 20mV,读数是不会动的。想让显示"每毫伏都跳",只能换 12 位的 ADS7825/ADC0834 那类,或者加运放做前置放大(把 0~0.5V 放大 10 倍再进 ADC)。

05 DA:P1 = adc 这一行

DAC0832数模转换模块

整个 DA 部分就一条赋值:把 AD 采到的 adc 原样送给 P1 口。

DAC0832 的 8 个数据脚 DI0~DI7 全接在 P1.0~P1.7 上。因为工作在直通模式(前面 02 节说过,控制脚全接地),P1 口上出现什么数,IOUT 就输出对应的电流,经运放转成电压,直接推喇叭。

所以这句 P1 = adc; 的实际效果是:电位器 RV3 转一点 → ADC 读数变 → P1 变 → DAC 输出电压跟着变 → 喇叭的直流偏置跟着变。

有同学会问:喇叭怎么不响?

因为这是直流电平,不是音频信号。喇叭(尤其 Proteus 里的 SP 模型)要的是"变化的电压"才推得动纸盆,一个恒定的直流电平只会让它停在某个位置。要想听见声音,得让 P1 按某个频率不停变化——比如输出一个方波:

主循环里跑两个 for:一个把 P1 从 0 数到 254,一个从 255 数回去,每送一个数停 1ms,于是在 P1 上滚出一升一降的锯齿。

这就是最基本的 DA 波形发生器——Proteus 里接个示波器就能看到锯齿波。想让它唱歌,把这段换成"按频率表查表输出正弦波"就行,那是 H48 音乐播放器干的事(用蜂鸣器 + 方波,原理相通)。

这里还有一个容易被忽略的精度问题:DA 的输入直接来自 AD 的输出,AD 的量化误差会原样传到 DA 上。8 位 AD 在 5V 下分辨 19.6mV,那么 DA 输出的电压也有 19.6mV 的台阶——相当于"采一遍、再还一遍",中间白白多了一次量化。

真实工程里如果要"模拟进、模拟出"(比如音频直通),绝不会走"ADC→单片机→DAC"这条路,而是让信号直接走模拟开关或运放。这条路的价值在于教学:它把"数字量"这个中间环节暴露得最清楚。

06 电机:8 拍表和 2ms 中断

ULN2003A步进电机驱动模块三个独立按键

四相步进电机的 8 拍顺序表是固定的,这份程序用的通电顺序是:

八拍顺序没有写成 if-else,而是做成一张 8 个元素的 code 数组,下标 0~7 依次对应八拍,每个元素是一个字节,写的是这一拍哪几相通电的组合。

把它拆成四位的 A/B/C/D(bit3~bit0)看,就是一张"哪一相通电"的表:

序号值通电相说明
00x08D单相
10x09D+A双相
20x01A单相
30x03A+B双相
40x02B单相
50x06B+C双相
60x04C单相
70x0CC+D双相

一次走一步、八步一个循环,就是 8 拍(也叫半步)方式。相比 4 拍方式,它每步角度减半、运行更平顺,代价是同样转速下中断次数翻倍。

注意表里存的是"正逻辑",而程序输出时取了反:

正转时取当前拍那个字节直接按位取反,反转时改为从表尾往前取再取反,结果送 P3 的低四位。

因为 ULN2003A 是"输入高电平 → 输出拉低"的达林顿反相器,单片机给高,电机那头反而是低。取反之后逻辑才对得上。这跟 H37 用三极管驱动是同一个道理,只不过达林顿把 7 路和续流二极管都集成进去了,一片就能推一整台四相电机。

初始化里把 T1 设成方式 1、装好初值,开 T1 中断和总中断再启动;中断服务函数一进来先重装初值,然后调一次步进驱动函数,把当前的 motor_mode 传进去。

电机必须挂在中断里,不能放主循环。 因为主循环末尾有个 Delay_1ms(50)(等 ADC 和温度刷新),要是把步进放主循环,每 50ms 才走一步,电机转起来是"一顿一顿"的。放中断里,2ms 一步,转速稳定、也不受主循环忙闲影响。

这里有个细节值得点出来:初始化时给的是 65536-1000(1ms),中断里重装却是 65536-20000(20ms,却注释成 2ms)。两处对不上,实际中断周期是 20ms 而不是 2ms——从图上的转速看也是这样,转得不快。这种"初值和重装值不一致"的问题在51程序里非常常见,看别人的代码时,别信注释,去算数。

(顺带一句:STEP_C 和 STEP_D 都写成了 temp & 0x04,D 相应该用 0x08。这是笔误,少一相依然能转,只是力矩小一点、噪声大一点。)

按键是三个独立按键,低电平有效,靠 P3 内部上拉:

扫描函数靠一个静态标记做去重,再看三个键哪个为低:延时 5ms 消抖,按下的键号作为返回值 1、2、3,一个都没按就返回 0。

key_up 这个静态标志是边沿检测:按住不放只算一次,必须全松手才能再触发。三个键各管一个状态:

按键动作motor_mode
K1 启动正转1
K2 反向1↔2 互切(停止状态下按也能进正转)1 或 2
K3 停止停0

motor_mode == 0 时 SETP_MOTOR_Run 里 if(mode>0) 不成立,一句话都不执行——但 temp 还保持着上一次的值,四相通电状态不变。所以"停止"其实是"保持最后一相的电流",电机被锁住、不会自由转动。真要彻底断电,得在停止时把四相全清 0。

07 排错清单

1、LCD 全黑或全亮:先查 P0 有没有 RP1 上拉,再查 VEE(第 3 脚)上的对比度电位器有没有调——LM016L 必须把 VEE 拉到合适电平才显示字;

2、LCD 有字但全是方块:初始化序列不对,0x38(8 位两行)要连发三次再发 0x0C(开显示不显光标)。这一步不能省,也不能只发一次;

3、温度一直显示 000 或 085:DS18B20 的 DQ 上拉电阻(R1 4.7k)没接,或者初始化时隙不对(delay_18B20(80) 那个 480μs 的低电平必须够长);85℃ 是它的上电默认值,说明启动了转换但没等到结果;

4、温度正确但偶尔跳变:读温度前后没有关中断(EA=0),被 T1 中断打断了单总线时序。见 03 节;

5、AD 读数不随电位器变化:ADC0832 的 CS 没接对引脚(P2.5),或者 DI/DO 没有并在一起——DI 和 DO 分开接、程序里又只用一个引脚,收到的永远是 0;

6、AD 读数对但电压显示不对:*50/255 写成 *5/255 会永远显示 0.几伏;写成 *500/255 会显示 41 而不是 4.1(因为小数点已经预置在屏幕上了,见 04 节);

7、电机不转:先看 ULN2003A 的 COM 有没有接 VCC,再看 motor_mode 是不是还停在 0(没按 K1);如果按了 K1 有轻微"哒哒"声但不转,是相序表错了或某相接反;

8、电机转速明显偏慢:定时器初值和中断里重装值不一致(本篇就是 1ms 初值、20ms 重装),按实际重装值算转速,别信注释。

08 总结

这个项目把"模拟量怎么进、怎么出"讲完了:

· AD 入口:ADC0832 三线时序——起始位、通道选择、8 拍读数,DI/DO 一根线分时复用;

· DA 出口:DAC0832 直通模式,P1 上有数就出电压,连写信号都不用;

· 定点数:温度放大 100 倍存整数、AD 用 *50/255 换伏,全程绕开浮点;

· 时序保护:微秒级时序的器件(单总线)读写前后关中断;

· 中断驱动:步进电机挂定时器,别放主循环。

如果只记一句话:P1 = adc; 这一行就是 AD/DA 的全部——采进来的是数,送出去的也是数,中间没有魔法。

下一篇继续,往更复杂的通信协议上走。

欢迎关注,持续更新!

09 文件日期修改

如果你想把文件修改为最新的日期,又不想新建工程,那么你可以参考下面的文章。

"如何不打开程序的情况下更改proteus和keil程序的日期?"

10 资料下载

全套资料包含 Keil 工程(C 语言源码 + hex 文件)、Proteus 仿真工程、设计报告、程序流程图、讲解视频,以及编译截图和仿真截图。下载后可直接打开运行,也可用于课程设计、毕业设计参考。

点我下载全套资料 →

也可以关注公众号「单片机仿真」,回复 H44 获取下载入口。公众号内还有 A / T / H 全系列 400 多个单片机仿真实例,回复「目录」可查看完整清单。

同类设计合集:H 系列单片机仿真合集目录(含设计报告、视频讲解、流程图);全部系列合集总目录

版权所有,转载本站文章请注明出处:proteus单片机
上一篇:H43基于51单片机秒表proteus设计,keil程序,c语言,AT89C51
下一篇:H44基于51单片机ADDA综合仿真proteus设计,keil程序,c语言,AT89C51
${comment['nickname']}   ${comment['createtime']}
  
       
${comment.content}
${reply.nickname} 回复 ${comment.nickname}    ${reply.createtime}
     
  
回复内容:${reply.content}