蓝桥杯单片机——西风版第八讲PCF8591芯片笔记
读 PCF8591 其他通道:直接改AD_Read()的参数为0x400x410x42(对应 AIN0~AIN2)。读其他 I²C 外设:先改底层的 I²C 设备地址(0x90→对应外设地址),再改AD_Read()的参数为该外设的控制字节。核心原则:不同外设的 “I²C 地址” 和 “控制字节规则” 不同,必须对照外设手册改这两个关键值。
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();
}
三、对照左侧的通信时序图理解
- S(START):
IIC_Start(),通信开始。 - ADDRESS + W(0x90):
IIC_SendByte(0x90),主机说 “我要写”。 - A(ACK from PCF8591):
IIC_WaitAck(),芯片点头 “收到,你说”。 - CONTROL BYTE(0x41):
IIC_SendByte(0x41),主机说 “启用 DAC”。 - A(ACK from PCF8591):
IIC_WaitAck(),芯片点头 “好的,DAC 已启用”。 - DATA BYTE(addr):
IIC_SendByte(addr),主机说 “输出这个值对应的电压”。 - A(ACK from PCF8591):
IIC_WaitAck(),芯片点头 “收到,正在输出”。 - 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的测量模式
四、小白实操建议(避坑)
-
先确认硬件连接:要读的外设必须接对 PCF8591 的对应通道(比如 AIN0 接光敏,就用 0x40)。
-
先查外设手册:非 PCF8591 外设,先找 datasheet 确认 2 个关键值:
-
设备的 I²C 读写地址(比如 0xA0/0xA1)
-
控制字节的含义(比如 “读通道 1” 对应的 16 进制值)
-
-
测试验证:改完参数后,用数码管显示数值,看是否有合理变化(比如拧电位器,数值跟着变)。
总结
-
读 PCF8591 其他通道:直接改
AD_Read()的参数为0x40/0x41/0x42(对应 AIN0~AIN2)。 -
读其他 I²C 外设:先改底层的 I²C 设备地址(0x90→对应外设地址),再改
AD_Read()的参数为该外设的控制字节。 -
核心原则:不同外设的 “I²C 地址” 和 “控制字节规则” 不同,必须对照外设手册改这两个关键值。
更多推荐



所有评论(0)