PCF8591 芯片在板子上的作用 🎛️

PCF8591 是一款8 位模数 / 数模转换芯片,核心作用是帮单片机 “看懂” 模拟世界、“控制” 模拟设备:

  • 模数转换(ADC):把温度、光照、电压这类连续变化的模拟信号,转换成单片机能处理的 0~255 数字值。

  • 数模转换(DAC):把单片机输出的数字值,转换成模拟电压,用来控制 LED 亮度、蜂鸣器音调等。

它通过 I²C 总线和单片机通信,只需要两根线(SDA/SCL)就能实现多路模拟信号的采集和输出,非常节省单片机引脚。

补充说明

  • 地址 0x90/0x91 是 PCF8591 的默认 I²C 地址,如果板子上有地址跳线,地址可能会变化,需要根据硬件修改代码里的地址值。

  • 读取到的数字值(0~255)只是 ADC 的原始结果,需要结合传感器的 datasheet 才能换算成有意义的物理单位(如℃、lux)。

先搞懂 I²C 协议是什么 🤔

I²C(也叫 IIC)是一种串行通信协议,就像单片机和外设(比如传感器、显示屏)之间的 “对话规则”。

  • 它只需要两根线:

    • SDA:数据线(Serial Data),负责传数据

    • SCL:时钟线(Serial Clock),负责同步节奏

  • 通信规则:

    • 总线空闲时,SDA 和 SCL 都保持高电平(1)

    • 数据只能在 SCL 为低电平时改变,SCL 为高电平时保持稳定,这样接收方才好采样

unsigned char Ad_Read(unsigned char addr)

一、先搞懂几个关键概念
  • 地址 0x90 / 0x91

    • 0x90:二进制 10010000,表示 “主机向 PCF8591 写数据”(写模式)。

    • 0x91:二进制 10010001,表示 “主机从 PCF8591 读数据”(读模式)。

  • addr 参数:用来指定要读取 PCF8591 上的哪一个 ADC 通道(AIN0~AIN3)。

二、逐行解读 Ad_Read 函数
unsigned char Ad_Read(unsigned char addr)
{
    unsigned char temp;
​
    // 1. 发送I2C开始条件
    IIC_Start();
​
    // 2. 发送PCF8591的写地址(0x90),告诉芯片“我要给你发数据了”
    IIC_SendByte(0x90);
    // 3. 等待PCF8591应答,确认它收到了地址
    IIC_WaitAck();
​
    // 4. 发送要读取的ADC通道地址(addr),告诉芯片“我要读这个通道的数据”
    IIC_SendByte(addr);
    // 5. 再次等待PCF8591应答,确认它收到了通道号
    IIC_WaitAck();
​
    // 6. 再次发送I2C开始条件(重复起始条件),切换通信方向为“读”
    IIC_Start();
    // 7. 发送PCF8591的读地址(0x91),告诉芯片“现在我要读数据了”
    IIC_SendByte(0x91);
    // 8. 等待PCF8591应答,确认它准备好发送数据
    IIC_WaitAck();
​
    // 9. 接收PCF8591返回的AD转换结果(一个字节)
    temp = IIC_RecByte();
    // 10. 发送非应答(NACK),告诉芯片“数据读完了,不用再发了”
    IIC_SendAck(1);
​
    // 11. 发送I2C停止条件,结束本次通信
    IIC_Stop();
​
    // 12. 返回读取到的AD值
    return temp;
}

void Da_Write(unsigned char addr)


一、先搞懂这个函数在干嘛

Da_Write(unsigned char addr) 是用来控制 PCF8591 的 DAC 输出模拟电压的函数。

  • 输入参数 addr:是一个 0~255 的数字值,对应 DAC 输出的 0~5V 电压。
  • 输出:PCF8591 的 AOUT 引脚会输出一个和 addr 成正比的模拟电压。

二、逐行解读 Da_Write 函数

void Da_Write(unsigned char addr)
{
    // 1. 发送I2C开始条件,告诉所有设备“通信要开始了”
    IIC_Start();

    // 2. 发送PCF8591的写地址(0x90),告诉芯片“我要给你发数据了”
    IIC_SendByte(0x90);
    // 3. 等待PCF8591应答,确认它收到了地址
    IIC_WaitAck();

    // 4. 发送控制字节(0x41),告诉芯片“我要启用DAC功能”
    IIC_SendByte(0x41);
    // 5. 再次等待PCF8591应答,确认它收到了控制指令
    IIC_WaitAck();

    // 6. 发送DAC的值(addr),告诉芯片“你要输出的电压对应的数字值是这个”
    IIC_SendByte(addr);
    // 7. 再次等待PCF8591应答,确认它收到了DAC值
    IIC_WaitAck();

    // 8. 发送I2C停止条件,结束本次通信
    IIC_Stop();
}

三、对照左侧的通信时序图理解

  1. S(START)IIC_Start(),通信开始。
  2. ADDRESS + W(0x90)IIC_SendByte(0x90),主机说 “我要写”。
  3. A(ACK from PCF8591)IIC_WaitAck(),芯片点头 “收到,你说”。
  4. CONTROL BYTE(0x41)IIC_SendByte(0x41),主机说 “启用 DAC”。
  5. A(ACK from PCF8591)IIC_WaitAck(),芯片点头 “好的,DAC 已启用”。
  6. DATA BYTE(addr)IIC_SendByte(addr),主机说 “输出这个值对应的电压”。
  7. A(ACK from PCF8591)IIC_WaitAck(),芯片点头 “收到,正在输出”。
  8. P(STOP)IIC_Stop(),通信结束。

四、关键细节补充(小白必看)

1. 控制字节 0x41 是什么意思?

0x41 的二进制是 0100 0001,对应 PCF8591 的控制字节:

  • 0100:固定的 DAC 启用位。
  • 0001:启用 DAC 输出,并关闭 ADC 通道(因为我们现在只用 DAC 功能)。

2. addr 和输出电压的关系?

PCF8591 的 DAC 是 8 位的,所以:

  • 输出电压 = addr / 255 * 参考电压(通常是 5V)。
  • 比如:addr = 102 → 102 / 255 * 5V = 2V,芯片就会输出 2V。

3. 为什么可以连续改变 DAC 值?

如图注所说:“6 到 7 步可以一直循环进行,只要没有重新 I2C 开始信号,或者结束信号,DAC 输出就一直是最后一个输出的值。”

  • 意思是:你可以在一次 IIC_Start() 之后,连续多次调用 IIC_SendByte(addr),每次发送新的 addr,芯片的输出电压就会立刻跟着变,不需要每次都重新发开始和停止条件,这样效率更高。

五、和之前 Ad_Read 的对比

函数名 功能 通信方向 关键字节
Ad_Read 读取 ADC 采集的电压 读(R) 地址 0x91,控制字节 0x43
Da_Write 控制 DAC 输出的电压 写(W) 地址 0x90,控制字节 0x41

使用方法

1. 头文件引入(PCF8591 的驱动入口)

#include "iic.h"//数模转换底层驱动头文件
  • 这个头文件里包含了我们之前聊的 AD_Read()(读 ADC)、DA_Write()(写 DAC)等操作 PCF8591 的函数,是和芯片通信的 “桥梁”。

2. 核心变量(存储 PCF8591 采集 / 输出的电压)

float Voltage;//实时电压变量(PCF8591 ADC采集到的电压)
float Voltage_Output;//实时电压输出变量(要通过PCF8591 DAC输出的电压)

3. ADC 读取(PCF8591 采集模拟电压)

Voltage = AD_Read(0x43) / 51.0;//实时读取RB2电压数据

4. DAC 输出(PCF8591 输出模拟电压)

DA_Write(Voltage_Output);//电压输出

这是代码里PCF8591 的写操作

  • DA_Write() 是底层驱动函数(对应 PCF8591 的 DAC 功能),作用是把 Voltage_Output(比如 2V)转换成对应的数字值,通过 I²C 发给 PCF8591,芯片再把这个数字值转换成模拟电压,从 AOUT 引脚输出。

  • 比如:Voltage_Output=2V → 底层会把 2V 换算成 2*51=102(因为 5V 对应 255,2V 对应 102),然后发给 PCF8591,芯片就会从 AOUT 引脚输出 2V 模拟电压。

AD_Read(0x43)如果是读其他外设呢,怎么改

我用小白能听懂的方式,告诉你怎么修改 AD_Read() 的参数,适配不同外设 / 通道,核心是搞懂PCF8591 的控制字节规则👇


一、先搞懂 0x43 是什么(PCF8591 控制字节)

AD_Read() 的参数本质是 PCF8591 的控制字节(8 位),二进制格式如下:

it7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 0   1/0   1/0   1/0   0     1     1     1 └─┘   └─────┬─────┘     └─────────┬─────────┘ 固定0     通道选择位         功能配置位

  • bit0-bit2(低 3 位):固定为 111(对应十进制 7),表示启用 ADC、自动增量模式(读一次后通道自动 + 1)。

  • bit4-bit6(中间 3 位):决定读取哪个 ADC 通道(AIN0~AIN3),这是修改的核心!


二、不同通道的参数对照表(直接抄用)

PCF8591 有 4 路模拟输入(AIN0~AIN3),对应不同外设(比如光敏、热敏、电位器),参数如下:

要读取的外设 / 通道 控制字节(16 进制) 对应二进制 说明
AIN0(比如光敏) 0x40 0100 0000 读第 0 路模拟信号
AIN1(比如热敏) 0x41 0100 0001 读第 1 路模拟信号
AIN2(比如电位器) 0x42 0100 0010 读第 2 路模拟信号
AIN3(你的 RB2) 0x43 0100 0011 读第 3 路模拟信号(原代码)
举例修改:
  • 想读 AIN0(光敏传感器):AD_Read(0x40)

  • 想读 AIN1(热敏传感器):AD_Read(0x41)

  • 想读 AIN2(电位器):AD_Read(0x42)

  • 想读 AIN3(原 RB2):AD_Read(0x43)(不变)


三、如果不是 PCF8591,而是其他 I²C 外设?

如果要读的不是 PCF8591,而是其他 I²C 外设(比如温湿度传感器、另一个 ADC 芯片),修改逻辑分 2 步:

步骤 1:修改 I²C 设备地址(底层 iic.h 里改)

不同外设的 I²C 地址不同:

  • PCF8591 默认地址:0x90(写)/ 0x91(读)

  • 比如 BH1750(光敏):0xA0(写)/ 0xA1(读)

  • 比如 DS18B20(温度,注:不是 I²C 但举例):地址是 0x28

需要在 AD_Read() 底层函数里,把 0x90/0x91 改成对应外设的 I²C 地址。

步骤 2:修改控制字节(AD_Read() 的参数)

不同外设的 “控制字节” 规则不同:

  • PCF8591:控制字节是 “通道选择 + 功能配置”(如 0x43)

  • BH1750:控制字节是 “测量模式”(如 0x01 表示连续高精度测量)

  • 其他 ADC 芯片:控制字节可能是 “分辨率 + 通道”

举例:读 BH1750 光敏传感器(替换 PCF8591)
// 原代码(读PCF8591 AIN3)
Voltage = AD_Read(0x43) / 51.0;
​
// 修改后(读BH1750)
// 1. 先改底层 AD_Read 里的地址为 0xA0/0xA1
// 2. 再改参数为 BH1750 的控制字节
unsigned int light = AD_Read(0x01); // 0x01 是BH1750的测量模式

四、小白实操建议(避坑)
  1. 先确认硬件连接:要读的外设必须接对 PCF8591 的对应通道(比如 AIN0 接光敏,就用 0x40)。

  2. 先查外设手册:非 PCF8591 外设,先找 datasheet 确认 2 个关键值:

    • 设备的 I²C 读写地址(比如 0xA0/0xA1)

    • 控制字节的含义(比如 “读通道 1” 对应的 16 进制值)

  3. 测试验证:改完参数后,用数码管显示数值,看是否有合理变化(比如拧电位器,数值跟着变)。


总结
  1. 读 PCF8591 其他通道:直接改 AD_Read() 的参数为 0x40/0x41/0x42(对应 AIN0~AIN2)。

  2. 读其他 I²C 外设:先改底层的 I²C 设备地址(0x90→对应外设地址),再改 AD_Read() 的参数为该外设的控制字节。

  3. 核心原则:不同外设的 “I²C 地址” 和 “控制字节规则” 不同,必须对照外设手册改这两个关键值。


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐