蓝桥杯第七周第一讲

**

蓝桥杯单片机备赛指南第十三讲: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更新版)

重点:增加了BeepRelay,且所有函数均采用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 切换。

  1. 电压显示界面(界面U)

    • 内容:显示电位器RB2 (AIN3) 的实时电压。

    • 格式U 3.41(保留两位小数)。

    • 细节:提示符’U’ 在第1 位,数据在6-8 位,中间熄灭。

  2. 电压输出界面(界面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 任务书要求:

  1. L1:指示当前在电压显示界面

  2. L2:指示当前在电压输出界面

  3. L3:当测量电压$V >= 3.5V$或$2.5V>V >= 1.5V$时点亮。

  4. 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`