ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ADuC845程序调试指南:从工程结构到ADC采样与二次开发

ADuC845程序调试指南:从工程结构到ADC采样与二次开发

简介:这份资源面向使用 ADuC845 微控制器的嵌入式开发者,尤其适合需要高精度模拟处理与混合信号控制示例的初学者和工程师。压缩包共 37 个文件,约 52KB,以 c 源文件与 h 头文件为主体,配套 obj、lst 编译中间文件及 hex 烧录文件、uv2 工程配置,覆盖 PWM、ADC、DAC、定时器、键盘扫描与 LCD12864 显示等模块,便于直接导入 Keil 环境编译验证。已有 208 人学习下载。代码围绕脉宽调制的频率与占空比设置、中断初始化与服务例程、ADC 参考电压与采样时间配置展开,并包含 I/O 操作、串行通信与定时器底层驱动,读者可据此理解各外设的寄存器配置流程与模块间协作方式,快速搭建自己的嵌入式测量与控制程序框架。

1. 拿到一份 ADuC845 程序,先别急着烧录

翻硬盘翻出一份标着 ADuC845 的工程包,很多人第一反应是找烧录器、接串口、点下载。我见过不止一个同行走这一步就卡住了:代码编译能过,烧进去没反应,示波器一量晶振都没起振。ADuC845 这颗芯片属于 8051 内核加高精度 Σ-Δ ADC 的混合信号单片机,程序能不能跑起来,一半取决于代码本身,另一半取决于时钟、复位、串口下载这几根线有没有接对。这份程序包适合正在做小信号采集、称重、温度变送器这类模拟前端的人,也适合手上有一块老开发板、想把 8051 和 ADC 一起摸一遍的开发者。下面按「这堆文件是什么 → 怎么让它跑起来 → 怎么改成自己的」的顺序拆。

2. 工程结构拆解:从启动文件到 ADC 采样主循环

2.1 目录里都有什么

拿到一个 8051 系工程,先看目录再动编译器。ADuC845 的典型工程包一般长这样,不同人整理习惯不同,但骨架大同小异:

ADuC845_Project/ ├── STARTUP.A51 # 启动代码,初始化堆栈、清零 RAM ├── main.c # 主流程:初始化 + 采样循环 ├── adc845.c / adc845.h # ADC 配置与读取封装 ├── uart.c / uart.h # 串口收发,用于调试打印 ├── timer.c / timer.h # 定时器,给采样定节拍 ├── lcd1602.c # 本地显示(可选) ├── ADuC845.H # 寄存器定义头文件,SFR 全在这里 └── project.uvproj # Keil 工程文件

ADuC845.H是整个工程的地基,所有特殊功能寄存器(SFR)的地址映射都在里面。这份头文件如果和芯片型号对不上,后面全是玄学问题——比如你写ADC0CON编译器不报错,但地址偏了一位,配置写进去等于没写。我一般拿到工程第一件事就是打开这个头文件,确认ADC0CON、ADCMODE、SF(Sinc 滤波器阶数)这些关键寄存器的地址和手册一致。

启动文件STARTUP.A51通常不用改,但要知道它干了什么:上电后清零内部 RAM、设置堆栈指针、跳转到main。如果你在main之前就想用全局变量,得确认启动代码里的 RAM 清零范围覆盖到了你的变量区,否则第一次读到的可能是随机值。

2.2 主循环的骨架

主程序的结构基本是「初始化 → 死循环采样 → 处理 → 输出」。下面这段是这类工程里最常见的写法,我按自己的习惯加了注释:

#include <ADuC845.H> void main(void) { /* 1. 时钟与看门狗配置:先关看门狗,避免初始化阶段被复位 */ WDCON = 0x00; // 关闭看门狗 POWCON = 0x00; // 电源控制寄存器,按手册默认值 /* 2. 串口初始化,波特率 9600,用于调试输出 */ SCON = 0x50; // 串口模式1,允许接收 TMOD |= 0x20; // 定时器1 工作在模式2(8位自动重装) TH1 = 0xFD; // 11.0592MHz 晶振下 9600 波特率的初值 TL1 = 0xFD; TR1 = 1; // 启动定时器1 /* 3. ADC 初始化:主通道、增益、滤波器阶数 */ ADC0CON = 0x07; // 使能主ADC,增益1,单端输入 ADCMODE = 0x03; // 连续转换模式,启用斩波 SF = 0x50; // Sinc 滤波器阶数,决定输出速率与噪声 /* 4. 主循环:等转换完成,读结果,打印 */ while (1) { while (!(ADCSTAT & 0x80)); // 等待 ADC 转换完成标志位 unsigned long code = ADC0DAT; // 读24位结果(分三次读或一次读) printf("ADC=%lu\r\n", code); // 串口输出,便于观察 } }

这段代码的逻辑很直白,但有几个参数值得说清楚。SF寄存器控制 Sinc 滤波器的阶数,阶数越高噪声越低、输出速率越慢,做称重传感器时通常取 0x50 到 0xFF 之间,具体看你的采样率要求。ADCMODE里的斩波位能抑制失调漂移,代价是转换时间翻倍。ADC0DAT是 24 位结果寄存器,8051 是 8 位机,读的时候要么分三次读字节,要么用编译器扩展的 32 位访问,这里写成一次读是依赖 Keil 的 SFR 扩展,实际移植到别的编译器要拆开。

2.3 中断与采样节拍

轮询等标志位在低速采样时够用,但采样率一上去就会拖慢主循环。工程里如果有timer.c,多半是用定时器中断来定采样节拍:

/* 定时器0 中断服务函数,每 10ms 触发一次采样 */ void timer0_isr(void) interrupt 1 { TH0 = 0xDC; // 重装初值,11.0592MHz 下约 10ms TL0 = 0x00; sample_flag = 1; // 置标志,主循环里处理 }

中断里只置标志、不做耗时操作,这是 8051 上的血泪经验。ADC 读取、串口打印这些动作放在主循环里做,中断服务函数越短越好,否则高优先级中断会挤掉低优先级,采样节拍就乱了。TH0/TL0的重装值按晶振频率算,11.0592MHz 下 10ms 定时大约是 0xDC00,换成 12MHz 晶振这个值就得重算,直接抄会偏。

3. 跑起来:编译、下载与串口验证

3.1 编译环境与工程配置

ADuC845 的工程基本都在 Keil C51 下编译,工程文件是.uvproj。打开后先检查三处配置,这三处错了编译能过但跑不起来:

配置项正确值错了会怎样
Device 选型ADuC845(或对应型号)寄存器地址映射错,SFR 写入无效
晶振频率与实际板子一致(常见 11.0592MHz)串口波特率、定时器全偏
输出格式HEX 文件下载器找不到可烧录文件

Device 选型在 Keil 的 Options for Target → Device 里改,选错型号最典型的症状是ADuC845.H里的寄存器名编译报「未定义」,或者定义了但地址不对。晶振频率在 Target 选项卡里填,它只影响编译器的时序计算和串口初值参考,不改变实际硬件,但填错了你按它算的波特率初值就会通信乱码。

3.2 下载线与串口线的接法

ADuC845 支持通过 UART 串口下载程序,这是它比很多 8051 方便的地方,不用专用仿真器。接线就三根:TXD、RXD、GND,加上下载时把 PSEN 或特定引脚拉低进入 bootloader 模式(具体引脚看手册的下载模式章节)。常见翻车点是 TXD/RXD 交叉接反——单片机的 TXD 接 USB 转串口的 RXD,反过来接就收不到任何回应。

下载工具一般用芯片厂商提供的串口下载软件,选好 COM 口、波特率(下载时通常用固定波特率,比如 9600 或 19200),点下载后给板子重新上电,让芯片在复位时进入 bootloader。如果一直提示「等待握手」,先量晶振有没有起振,再确认下载模式引脚电平对不对。

3.3 串口验证:先让它说话

程序烧进去后,别急着接传感器。先让串口打印一句固定字符串,确认「芯片在跑、串口通、波特率对」这三件事同时成立:

/* 上电后先打印一行,验证最小系统 */ void uart_send_string(unsigned char *str) { while (*str) { SBUF = *str; // 写入发送缓冲 while (!TI); // 等发送完成 TI = 0; // 手动清标志 str++; } } /* main 里初始化后调用 */ uart_send_string("ADuC845 ready\r\n");

TI标志必须手动清零,这是 8051 串口的固定动作,忘了清第二次发送就卡死。串口助手收到这行字,说明时钟、复位、串口三块都正常,后面 ADC 读数不对就只需要查模拟部分,排查范围一下子缩小。这一步我每次必做,省下来的调试时间比写这行代码多得多。

4. 核心模块拆解:ADC 采样链路与寄存器配置

4.1 Σ-Δ ADC 的配置顺序

ADuC845 的 ADC 是 Σ-Δ 架构,配置有先后顺序,顺序错了转换结果就是垃圾。合理的顺序是:先设ADC0CON选通道和增益,再设SF定滤波器,最后设ADCMODE启动转换。下面这段封装把顺序固定下来:

/* 初始化主 ADC:通道0,增益1,连续转换 */ void adc_init(unsigned char channel, unsigned char gain, unsigned char sf_val) { ADC0CON = 0x00; // 先清零,避免残留配置 ADC0CON |= (1 << 7); // 使能主 ADC ADC0CON |= (gain & 0x07); // 增益位,0=1倍,1=2倍... ADC0CON |= (channel & 0x0F) << 3; // 通道选择 SF = sf_val; // 滤波器阶数 ADCMODE = 0x03; // 连续转换 + 斩波使能 }

gain参数对应内部 PGA 的放大倍数,做小信号时用 2 倍或 4 倍能把分辨率吃满,但输入不能超过参考电压除以增益。channel选差分还是单端由ADC0CON里另一位决定,这里只给了通道号,实际工程里要按接线方式补上差分位。sf_val越大输出速率越低,24 位结果的有效位数越高,做静态称重可以往大了取,做动态信号就得降下来。

4.2 读 24 位结果的正确姿势

24 位结果寄存器ADC0DAT在 8 位机上不能一次读干净,分字节读的时候要注意顺序和符号扩展:

/* 读取24位ADC结果,返回有符号32位 */ long adc_read(void) { unsigned char hi, mid, lo; long result; while (!(ADCSTAT & 0x80)); // 等转换完成 hi = ADC0DAT >> 16; // 高字节 mid = (ADC0DAT >> 8) & 0xFF; // 中字节 lo = ADC0DAT & 0xFF; // 低字节 result = ((long)hi << 16) | ((long)mid << 8) | lo; if (result & 0x800000) // 24位有符号数,手动符号扩展 result |= 0xFF000000; return result; }

符号扩展这一步是新手最容易漏的。24 位结果最高位是符号位,不扩展的话负电压读出来会变成一个巨大的正数,称重传感器受压和受拉读数完全对不上。ADCSTAT的 bit7 是转换完成标志,轮询它比用中断简单,低速采样够用。

4.3 参考电压与校准

ADuC845 支持内部参考和外部参考,工程里如果没显式配置,默认走内部 2.5V 参考。做高精度测量时我一般用外部基准,温漂小。校准分两步:零点校准(短接输入读偏移)和满量程校准(接基准电压读增益误差),把两个值存到 Flash 或 EEPROM 里,上电时加载。工程包里如果有calib.c,多半就是干这个的,没有的话自己补一个也不复杂,关键是校准值要掉电保存,否则每次上电都得重新校。

5. 避坑与排查:五个真实踩过的坑

5.1 烧录后无反应,晶振不起振

现象:下载软件提示成功,但串口没有任何输出,示波器测晶振引脚是平的。原因通常是晶振负载电容不匹配,或者下载模式引脚在上电后没恢复。解决:先换 22pF 到 33pF 的负载电容试,再确认下载模式引脚在正常运行时是悬空或拉高,不是一直被拉低。有些板子用跳线帽选下载模式,烧完忘了拔跳线帽,芯片一直停在 bootloader 里。

5.2 串口能发不能收

现象:单片机打印正常,但发命令给它没反应。原因多半是SCON里接收使能位没开,或者接收中断没配。解决:确认SCON = 0x50里的 bit4(REN)为 1,用中断接收的话还要开ES和总中断EA。另外检查波特率,发送和接收用的是同一个定时器,发送正常不代表接收端的采样点也对,晶振偏差大时接收更容易出错。

5.3 ADC 读数一直满量程

现象:不管输入接什么,读出来都是 0x7FFFFF 附近。原因通常是输入引脚悬空或参考电压没接,Σ-Δ ADC 输入悬空时会饱和。解决:把输入短接到地,看读数是否接近 0;如果还是满量程,查ADC0CON的通道选择位是不是选到了一个没接线的通道。还有一种情况是增益设太大,小信号被放大到超过参考电压,读数自然顶死。

5.4 定时器中断里采样导致数据错乱

现象:采样值偶尔跳变,规律性出现。原因是在中断里直接读 ADC 结果,而 ADC 转换还没完成,读到了旧数据或半更新数据。解决:中断里只置标志,主循环里等ADCSTAT完成位再读。如果非要在中断里读,至少确认转换完成标志已置位,并且读操作本身不被更高优先级中断打断。

5.5 换编译器后 SFR 访问失效

现象:Keil 下正常,换到别的 C51 编译器后 ADC 配置写不进去。原因是不同编译器对 SFR 的扩展语法不同,ADC0DAT这种 24 位寄存器在 Keil 里可以当 32 位访问,别的编译器可能只认 8 位。解决:把 24 位读操作拆成三个字节指针访问,用volatile unsigned char指向寄存器地址,不依赖编译器扩展。移植性这东西,平时不觉得,换环境时就是后悔药。

6. 二次开发:把采样工程改成自己的变送器

拿到这份程序,多数人最终目的是把它改成自己的采集板。改造的切入点有三个:换传感器接口、改通信协议、加数据处理。

换传感器接口最直接,adc_init里的通道和增益参数就是给你改的。称重传感器接差分通道,增益取 64 或 128;温度 PT100 接单端,增益取 2 到 4。改完记得重做零点和满量程校准,校准值存 EEPROM 的固定地址,上电加载。

改通信协议时,把uart_send_string那套裸串口输出换成带帧头帧尾的协议帧,比如0xAA + 长度 + 数据 + 校验 + 0x55,上位机好解析。发送用中断加环形缓冲,别在主循环里死等TI,采样率高了会丢节拍。

加数据处理可以在主循环里做滑动平均或中值滤波,24 位数据做 8 点滑动平均,RAM 够用,效果立竿见影。下面是个最小实现:

/* 8点滑动平均,输入为 adc_read 的返回值 */ long moving_average(long new_val) { static long buf[8] = {0}; static unsigned char idx = 0; long sum = 0; unsigned char i; buf[idx] = new_val; idx = (idx + 1) & 0x07; // 环形索引 for (i = 0; i < 8; i++) sum += buf[i]; return sum >> 3; // 除以8,移位代替除法 }

buf用static放在内部 RAM,8 个long占 32 字节,ADuC845 的 RAM 够。idx用位与代替取模,8051 上除法慢,移位快。滤波窗口大小按你的信号带宽调,窗口越大越平滑但响应越慢,做静态测量可以加到 16 点。

验证改造是否成功,我习惯用「已知输入法」:接一个基准电压源,从 0 到满量程取五个点,看读数线性度和重复性,再断电重启看校准值有没有正确加载。这套流程走一遍,比在示波器上盯半天管用。

从那以后我每次改这类采集工程,都强制先跑一遍「串口打印 + 短接输入读零点」的最小验证,再动传感器和协议。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表