**
蓝桥杯单片机备赛指南第十三讲:IIC 总线与PCF8591 AD DA 转换
**
1. IIC 总线与PCF8591 硬件原理
1.1 IIC 通信协议(软件模拟)
IIC (Inter-Integrated Circuit) 是一种双线串行总线。
-
SCL (P2.0):时钟线。
-
SDA (P2.1):数据线。
时序核心(死记硬背):
-
起始(Start):SCL 高期间,SDA 下降沿。
-
停止(Stop):SCL 高期间,SDA 上升沿。
-
应答(ACK):发送8 位后,第9 个时钟周期SDA 被拉低。
1.2 PCF8591 芯片详解
-
设备地址:写地址
0x90,读地址0x91。 -
控制字(Control Byte):
-
通道选择:Bit 0-1 (00=AIN0, 01=AIN1, 10=AIN2, 11=AIN3)。
-
DA 使能:Bit 6 (1=开启模拟输出)。
-
常用控制字:
-
0x01: 读光敏电阻(AIN1)。 -
0x03: 读电位器Rb2 (AIN3)。 -
0x40: 仅启用DAC 输出。
-
-
2. 进阶底层驱动模块
2.1 基础外设驱动(LED.c更新版)
重点:增加了Beep和Relay,且所有函数均采用static变量+ 位运算,确保互不干扰。
#include "LED.h"
// LED 单灯控制 (非破坏性)
void LED_Disp(unsigned char addr, bit enable)
{
static unsigned char Temp = 0x00;
static unsigned char Temp_Old = 0xFF;
if(enable)
Temp |= (0x01 << addr);
else
Temp &= ~(0x01 << addr);
if(Temp != Temp_Old)
{
P0 = ~Temp; // 共阳极,取反输出
P2 = P2 & 0x1F | 0x80; // 打开 Y4 (LED)
P2 &= 0x1F; // 锁存
Temp_Old = Temp;
}
}
// 蜂鸣器控制 (非破坏性)
void Beep(bit enable)
{
static unsigned char Temp = 0x00; // 0x00 表示初始关闭状态 (假设 ULN2003 输入0为关)
static unsigned char Temp_Old = 0xFF;
// 蜂鸣器通常接在 P0.6 (0x40)
if(enable)
Temp |= 0x40; // 置 1 开启
else
Temp &= ~0x40; // 置 0 关闭
if(Temp != Temp_Old)
{
P0 = Temp; // 直接输出 (ULN2003 驱动)
P2 = P2 & 0x1F | 0xA0; // 打开 Y5 (蜂鸣器/继电器)
P2 &= 0x1F;
Temp_Old = Temp;
}
}
// 继电器控制 (非破坏性)
void Relay(bit flag)
{
static unsigned char temp = 0x00;
static unsigned char temp_old = 0xff;
// 继电器通常接在 P0.4 (0x10)
if(flag)
temp |= 0x10;
else
temp &= ~0x10;
if(temp != temp_old)
{
P0 = temp;
P2 = P2 & 0x1F | 0xA0; // 打开 Y5
P2 &= 0x1F;
temp_old = temp;
}
}
2.2 IIC 与PCF8591 读写(iic.c补全)
官方提供的代码包通常只有基础时序,必须熟练默写以下两个函数。
#include "iic.h"
#include "intrins.h"
// ... (I2CStart, I2CStop, I2CSendByte 等基础时序略,参考官方文件) ...
// === 1. PCF8591 A/D 读取函数 ===
// addr: 通道号 (0x01 光敏, 0x03 电位器)
unsigned char Ad_Read(unsigned char addr)
{
unsigned char temp;
I2CStart();
I2CSendByte(0x90); // 写设备地址
I2CWaitAck();
I2CSendByte(addr); // 写控制字 (选择通道)
I2CWaitAck();
I2CStart(); // 重新起始
I2CSendByte(0x91); // 读设备地址
I2CWaitAck();
temp = I2CReceiveByte(); // 读取数据
I2CSendAck(1); // 发送非应答 (1),结束读取
I2CStop();
return temp;
}
// === 2. PCF8591 D/A 写入函数 ===
// dat: 输出电压对应的数字量 (0~255)
void Da_Write(unsigned char dat)
{
I2CStart();
I2CSendByte(0x90); // 写设备地址
I2CWaitAck();
I2CSendByte(0x40); // 写控制字 (0x40 = 启用 DAC)
I2CWaitAck();
I2CSendByte(dat); // 发送 DAC 数据
I2CWaitAck();
I2CStop();
}
3. 功能设计要求(电压采集装置)
基于**《蓝桥杯模块训练- PCF8591.pdf》**原题整理。
3.1 数码管显示
系统包含两个界面,通过S4 切换。
-
电压显示界面(界面U):
-
内容:显示电位器RB2 (AIN3) 的实时电压。
-
格式:
U 3.41(保留两位小数)。 -
细节:提示符’U’ 在第1 位,数据在6-8 位,中间熄灭。
-
-
电压输出界面(界面F):
-
内容:显示当前DAC 输出端的电压值。
-
格式:
F 2.50。 -
细节:提示符’F’ 在第1 位。
-
3.2 按键逻辑
-
S4 (界面切换):在
U(显示) 和F(输出) 界面间循环。 -
S5 (模式切换):切换DAC 的输出模式。
-
模式1 (跟随):输出电压= RB2 输入电压。
-
模式2 (固定):输出电压固定为2.0V。
-
-
S6 (LED 开关):开启或关闭LED 指示功能。关闭时所有LED 熄灭。
3.3 LED 指示逻辑(修正版)
根据PDF 任务书要求:
-
L1:指示当前在电压显示界面。
-
L2:指示当前在电压输出界面。
-
L3:当测量电压$V >= 3.5V$或$2.5V>V >= 1.5V$时点亮。
-
L4:DAC 输出固定电压(2.0V)时,L4 熄灭,DAC 输出电压跟 随 RB2 电位器输出电压变化时,L4 点亮。
- S6 限制:若S6 关闭了指示功能,L1-L4 必须全部熄灭。
4. 核心代码解答( main.c)
#include <STC15F2K60S2.H>
#include "Init.h"
#include "Key.h"
#include "LED.h"
#include "Seg.h"
#include "iic.h"
// === 变量声明 ===
typedef unsigned char u8;
typedef unsigned int u16;
u8 Key_Slow_Down = 0;
u16 Seg_Slow_Down = 0;
u8 LED_Seg_Pos = 0; // 数码管/LED 扫描指针
// 显存与状态
u8 Seg_Buf[8] = {10,10,10,10,10,10,10,10};
u8 Seg_Point[8] = {0,0,0,0,0,0,0,0};
u8 LED_Buf[8] = {0,0,0,0,0,0,0,0}; // LED 状态缓存 (1亮 0灭)
u8 Key_Val, Key_Old, Key_Down, Key_Up;
// 业务标志位
bit Seg_Mode = 0; // 0:电压显示界面(U), 1:电压输出界面(F)
bit Mode1_Flag = 0; // DAC模式: 0=固定2.0V, 1=跟随RB2
bit LED_Flag = 1; // LED总开关: 1=开, 0=关 (默认开)
bit Seg_Flag = 1; // 数码管总开关 (题目未要求按键控制,保持常开)
float Voltage = 0; // 测量电压 (RB2)
float Output_Voltage = 2.0; // DAC 输出电压
// === 按键逻辑 ===
void Key_Proc()
{
if(Key_Slow_Down) return;
Key_Slow_Down = 1;
Key_Val = Key_Read();
Key_Down = Key_Val & (Key_Val ^ Key_Old);
Key_Old = Key_Val;
switch(Key_Down)
{
case 4: // S4: 界面切换
Seg_Mode ^= 1;
break;
case 5: // S5: DAC 输出模式切换
Mode1_Flag ^= 1;
break;
case 6: // S6: LED 功能开关
LED_Flag ^= 1;
break;
}
}
// === 数据处理与显示 ===
void Seg_Proc()
{
u8 i;
if(Seg_Slow_Down) return;
Seg_Slow_Down = 1;
// 1. 读取 A/D (RB2 -> AIN3)
// 算法: Val / 255.0 * 5.0 => Val / 51.0
Voltage = Ad_Read(0x03) / 51.0;
// 2. 处理 D/A 输出
if(Mode1_Flag)
Output_Voltage = Voltage; // 跟随模式
else
Output_Voltage = 2.0; // 固定模式
Da_Write((u8)(Output_Voltage * 51.0)); // 写入 DAC
// 3. 数码管显示更新
if(Seg_Flag)
{
// 熄灭无关位
Seg_Buf[1]=Seg_Buf[2]=Seg_Buf[3]=Seg_Buf[4]=10;
// 设置小数点 (第5位点亮,对应 xxx.xx)
for(i=0; i<8; i++) Seg_Point[i] = 0;
Seg_Point[5] = 1;
if(Seg_Mode == 0) // 界面 U: 显示测量电压
{
Seg_Buf[0] = 17; // 'U' (假设字模 17)
Seg_Buf[5] = (u8)Voltage; // 个位
Seg_Buf[6] = (u8)(Voltage*10)%10; // 十分位
Seg_Buf[7] = (u16)(Voltage*100)%10; // 百分位
}
else // 界面 F: 显示输出电压
{
Seg_Buf[0] = 16; // 'F' (假设字模 16)
Seg_Buf[5] = (u8)Output_Voltage;
Seg_Buf[6] = (u8)(Output_Voltage*10)%10;
Seg_Buf[7] = (u16)(Output_Voltage*100)%10;
}
}
else
{
for(i=0; i<8; i++) Seg_Buf[i] = 10;
}
}
// === LED 逻辑控制 ===
void LED_Proc()
{
u8 i;
if(LED_Flag) // 总开关打开
{
// L1/L2: 界面指示 (互斥)
LED_Buf[0] = !Seg_Mode; // Mode=0(U) -> L1亮
LED_Buf[1] = Seg_Mode; // Mode=1(F) -> L2亮
// L3/L4/L5: 电压范围指示 (基于测量电压 Voltage)
LED_Buf[2] = (Voltage < 1.5);
LED_Buf[3] = (Voltage >= 1.5 && Voltage < 2.5);
LED_Buf[4] = (Voltage >= 2.5);
// L6-L8: 熄灭
LED_Buf[5] = LED_Buf[6] = LED_Buf[7] = 0;
}
else // 总开关关闭
{
for(i=0; i<8; i++) LED_Buf[i] = 0;
}
}
// === 定时器中断 (驱动层) ===
void Timer0_Server() interrupt 1
{
// ... 重装载代码 (1ms) ...
if(++Key_Slow_Down == 10) Key_Slow_Down = 0;
if(++Seg_Slow_Down == 200) Seg_Slow_Down = 0;
// 硬件扫描
if(++LED_Seg_Pos == 8) LED_Seg_Pos = 0;
// 驱动数码管
Seg_Disp(LED_Seg_Pos, Seg_Buf[LED_Seg_Pos], Seg_Point[LED_Seg_Pos]);
// 驱动 LED (调用更新后的非破坏性驱动)
LED_Disp(LED_Seg_Pos, LED_Buf[LED_Seg_Pos]);
}
void main()
{
// System_Init(); // 需包含初始化
// Timer0_Init(); // 需包含定时器初始化
while(1)
{
Key_Proc();
Seg_Proc();
LED_Proc();
}
}
5. 总结速查表(Cheat Sheet)
| 模块 | 关键代码/公式 | 备注 |
|---|---|---|
| 电压计算 | V = Val / 51.0 |
255对应5V,系数为51 |
| DAC 输出 | Val = V * 51.0 |
逆运算写入PCF8591 |
| 通道地址 | 0x03(电位器), 0x01(光敏) |
一定要看电路图确认 |
| DAC 控制字 | 0x40 |
写数据前必须发送此控制字 |
| LED 逻辑 | LED_Buf[0] = !Seg_Mode |
利用逻辑非运算实现互斥指示 |
| 非破坏驱动 | `P2 & 0x1F | 0x80` |