51单片机基础-ADC模数转换

第十九章 ADC模数转换实验

1. 导入

51内核标准机型没有片上ADC,要采集模拟量(电位器、光敏、电压等),需外接ADC芯片。本章选用常见、接线简单的串行ADC------ADC0832(2路8位,类SPI时序),实现模拟电压采样,并将结果通过串口或数码管显示。

你将掌握:

  • ADC基本概念:量程、分辨率、参考电压、采样频率。
  • ADC0832的时序与三线接口(CS/CLK/DI/DO)。
  • 基于GPIO"位 banging"实现ADC读数。
  • 采样值转电压、简单滤波与显示。

提示:若你更偏好I²C器件,可用PCF8591,复用上一章I²C框架实现ADC输入(本章末给出简要参考)。


2. 基础概念速览

  • 分辨率:ADC0832为8位,数字范围0~255。量化步进为 Vref/255。
  • 参考电压(Vref):通常等于芯片VCC(5V),也可外接基准。输出码值约为 Code ≈ Vin / Vref × 255。
  • 单端/差分:本章使用单端模式(SGL=1),通道CH0或CH1相对GND测量。

3. 硬件设计

3.1 器件与引脚

  • ADC0832 主要引脚:
    • CS:片选(低有效)
    • CLK:串行时钟
    • DI:数据输入(给ADC发送控制位)
    • DO:数据输出(ADC输出转换结果)
    • CH0/CH1:模拟输入通道
    • VCC/GND:电源与地
    • Vref:参考电压(不接时=VCC,很多模块已内部连到VCC)

3.2 连接示例(与51最小系统)

  • 控制线(可用任意IO,以下以 P1.0~P1.3 为例):
    • P1.0 → CS
    • P1.1 → CLK
    • P1.2 → DI
    • P1.3 ← DO
  • 模拟输入:
    • 电位器中点 → CH0
    • 电位器两端 → VCC 与 GND
  • 供电与参考:
    • VCC → +5V,GND → 地;Vref 默认接VCC(多数模块已默认处理)
  • 去耦:ADC0832 VCC与GND处放置0.1µF电容。

注意:DO为输入到单片机的引脚;DI为单片机输出给ADC的引脚。


4. 软件设计(ADC0832驱动)

4.1 原理与时序要点

  • 以CS拉低开始通信,CLK空闲为低。
  • 发送三位控制:Start=1,SGL/DIFF=1(单端),ODD/SIGN=通道选择(CH0=0,CH1=1)。
  • 然后再打一个对齐时钟,之后在接下来的8个时钟上,ADC0832在DO上输出8位数据(MSB→LSB)。
  • 结束时CS拉高。

4.2 完整代码:ADC0832读取 + 串口打印 + 可选数码管显示

c 复制代码
#include <reg52.h>
#include <intrins.h>

/* --------- 引脚定义(按需调整端口) --------- */
sbit ADC_CS  = P1^0;
sbit ADC_CLK = P1^1;
sbit ADC_DI  = P1^2;
sbit ADC_DO  = P1^3;

sbit LED = P1^7;   // 指示灯(可选)

/* --------- 公用延时与串口 --------- */
void delay_us_inline() { _nop_(); _nop_(); _nop_(); _nop_(); }
void delay_ms(unsigned int ms){
    unsigned int i, j;
    for(i=0;i<ms;i++) for(j=0;j<125;j++);
}

void uart_init(){
    TMOD |= 0x20;           // T1方式2
    TH1 = 0xFD; TL1 = 0xFD; // 9600bps @11.0592MHz
    TR1 = 1;
    SCON = 0x50;            // 8N1,允许接收
    EA = 1; ES = 0;         // 此处不使用串口中断
}
void uart_send_byte(unsigned char ch){ SBUF = ch; while(!TI); TI=0; }
void uart_send_str(const char* s){ while(*s) uart_send_byte(*s++); }
void uart_send_uint(unsigned int v){
    char buf[6]; char i=0;
    if(v==0){ uart_send_byte('0'); return; }
    while(v){ buf[i++] = (v%10)+'0'; v/=10; }
    while(i--) uart_send_byte(buf[i]);
}

/* --------- ADC0832 基础IO时序 --------- */
static void adc_clk_pulse(){
    ADC_CLK = 1; delay_us_inline();
    ADC_CLK = 0; delay_us_inline();
}

/* 发送一位到DI(在CLK上升沿采样) */
static void adc_send_bit(bit b){
    ADC_DI = b ? 1 : 0; delay_us_inline();
    ADC_CLK = 1; delay_us_inline();
    ADC_CLK = 0; delay_us_inline();
}

/* 读取一位(MSB->LSB),在CLK上升沿后读DO */
static unsigned char adc_read_bit(){
    unsigned char bitv;
    ADC_CLK = 1; delay_us_inline();
    bitv = ADC_DO ? 1 : 0;
    ADC_CLK = 0; delay_us_inline();
    return bitv;
}

/* 读取指定通道(0或1),返回0~255 */
unsigned char adc0832_read(unsigned char ch){
    unsigned char i, val = 0;

    /* 开始通信 */
    ADC_CS  = 1; ADC_CLK = 0; ADC_DI = 1;  // 默认空闲
    ADC_CS  = 0; delay_us_inline();

    /* 控制三位:Start=1, SGL=1, ODD=ch */
    adc_send_bit(1);     // Start
    adc_send_bit(1);     // SGL=1 单端
    adc_send_bit(ch?1:0);// ODD/SIGN=通道选择

    /* 对齐时钟(丢弃一个空位) */
    adc_clk_pulse();

    /* 读取8位数据(MSB->LSB) */
    for(i=0;i<8;i++){
        val <<= 1;
        val |= adc_read_bit();
    }

    /* 结束通信 */
    ADC_CS = 1;
    return val;
}

/* 简单多次平均(抗抖动) */
unsigned char adc0832_read_avg(unsigned char ch, unsigned char n){
    unsigned int acc = 0;
    unsigned char i;
    if(n==0) n=1;
    for(i=0;i<n;i++){
        acc += adc0832_read(ch);
        delay_ms(2);
    }
    return (unsigned char)(acc / n);
}

/* --------- 可选:共阴数码管显示0~255(三位) --------- */
/* 段码(0~9) */
unsigned char code seg_code[10] = {
  0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F
};
/* 显示一个三位数,P0为段选,P2.0~P2.2为位选低有效 */
void seg_show_3d(unsigned char val){
    unsigned char b = val % 10;
    unsigned char t = (val / 10) % 10;
    unsigned char h = (val / 100) % 10;

    // 百位
    P0 = (h==0)?0x00:seg_code[h];   // 百位为0时可熄灭,避免前导0
    P2 = (P2 & 0xF8) | 0x06;        // 0b110 选择位0(示例)
    delay_ms(1);

    // 十位
    P0 = (h==0 && t==0)?0x00:seg_code[t];
    P2 = (P2 & 0xF8) | 0x05;        // 选择位1
    delay_ms(1);

    // 个位
    P0 = seg_code[b];
    P2 = (P2 & 0xF8) | 0x03;        // 选择位2
    delay_ms(1);
}

/* --------- 主程序:读取并显示 --------- */
void main(){
    unsigned char raw;
    unsigned int mV;

    uart_init();
    uart_send_str("ADC0832 Demo Start\r\n");

    ADC_CS = 1; ADC_CLK = 0; ADC_DI = 1;

    while(1){
        /* 读取CH0,取8次平均 */
        raw = adc0832_read_avg(0, 8);

        /* 转电压:5V系统 → mV = raw * 5000 / 255 */
        mV = (unsigned int)raw * 5000 / 255;

        /* 串口输出 */
        uart_send_str("RAW=");
        uart_send_uint(raw);
        uart_send_str("  U=");
        uart_send_uint(mV);
        uart_send_str(" mV\r\n");

        /* 可选:三位数码管显示原始值0~255 */
        // 快速扫描多次以提升稳定性
        {
            unsigned char k;
            for(k=0;k<60;k++) seg_show_3d(raw);
        }

        /* 指示灯闪烁 */
        LED = !LED;
    }
}

要点说明:

  • 代码按"相邻时钟上升沿读DO"的方式取数,确保时序满足;如遇个别模块对时序更敏感,可在delay_us_inline()中增加若干_nop_()
  • 若电位器全行程对应05V,原始值一般覆盖0255;若Vref≠5V(或模块内接不同参考),换算电压时将5000替换为实际mV。
  • 简单平均能抑制抖动;更稳定可用滑动平均/中位数滤波。

5. 测试与应用

  • 接好电位器,转动观察串口的RAW与电压变化,数码管显示0~255动态变化。
  • 应用拓展:
    • 阈值开关:raw > 200 打开风扇/蜂鸣器。
    • 映射PWM:raw映射为占空比,实现旋钮控亮度/转速。
    • 双通道:将传感器接CH1,调用adc0832_read_avg(1, n)

6. 误差与优化

  • 参考误差:Vref漂移直接影响读数,建议稳定供电或用基准源。
  • 地线与噪声:模拟地尽量干净,采样线短且靠近地回路。
  • 采样阻抗:信号源阻抗过高会导致充采时间不足,必要时在输入端并小电容(与源阻抗构成RC)或用缓冲运放。
  • 标定:用已知电压点做两点或多点标定,修正线性误差。

7. 可选方案:PCF8591(I²C ADC)简述

若你已完成I²C章节,可用PCF8591替代ADC0832:

  • 硬件:SDA/SCL上拉4.7kΩ;A0A2设地址;AIN0AIN3为四路单端输入。
  • 关键流程:写控制字(选择通道与模式)→ 读一个"前导无效字节"→ 再读有效转换数据。
  • 读单通道伪码:
    • I²C Start → 写设备地址(写) → 写控制字(0x40 | 通道) → ReStart → 写设备地址(读)
    • 读丢弃字节 → 读有效字节并NACK → Stop

这样可与AT24C02共用I²C总线,方便多外设并联。


8. 小结

  • 硬件:ADC0832三线接口简单、IO占用少;电位器作为可变电压源测试直观。
  • 软件:位banging发送Start/SGL/通道选择,读8位数据;加入平均滤波与显示。
  • 能力:完成从模拟量获取→数字化→显示/控制的完整链路,可扩展到多传感器与闭环控制。

常见问题与排查

  • 读数固定0或255:CS/CLK/DI/DO接错或接反;DO未设为输入;Vref/AGND异常。
  • 抖动大:加平均滤波、检查电源与地、缩短模拟线。
  • 电压不准:Vref非5V但仍按5V换算;供电不稳;缺少标定。
  • 数码管串扰:加入"消隐"与位选关闭步骤,或放入定时器中断扫描。

相关推荐
eddy-原5 小时前
运维自动化与监控体系综合实践作业
运维·自动化·1024程序员节
顾晨阳——6 小时前
GPIO总结
单片机·嵌入式硬件·gpio
嵌入式老牛6 小时前
【无标题】
单片机·嵌入式硬件·rtc
IT阳晨。6 小时前
【STM32】看门狗
stm32·嵌入式硬件
mftang6 小时前
IO 开漏模式的特征和STM32 IO开路模式的配置和应用
stm32·嵌入式硬件·开漏模式
普中科技7 小时前
【普中DSP28335开发攻略】-- 第 9 章 蜂鸣器实验
单片机·嵌入式硬件·蜂鸣器·dsp28335·ccs·普中科技
瀚高PG实验室7 小时前
Linux环境下编译C语言使用libpq连接瀚高数据库
1024程序员节·瀚高数据库
ShiMetaPi8 小时前
操作【GM3568JHF】FPGA+ARM异构开发板 使用指南:串口
arm开发·单片机·嵌入式硬件·fpga开发·rk3568
点灯小铭8 小时前
基于单片机的两路PWM信号输出及频率占空比相位差调节系统
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业