第七周 PCF8591 程序设计复盘

  1. /* 头文件声明区 */
    #include <STC15F2K60S2.H>//单片机寄存器专用头文件
    #include <Init.h>//初始化底层驱动专用头文件
    #include <Led.h>//Led底层驱动专用头文件
    #include <Key.h>//按键底层驱动专用头文件
    #include <Seg.h>//数码管底层驱动专用头文件
    #include <Uart.h>//串口底层驱动专用头文件
    #include “iic.h”

    /* 变量声明区 */
    unsigned char Key_Val,Key_Down,Key_Old,Key_Up;//按键专用变量
    unsigned char Key_Slow_Down;//按键减速专用变量
    unsigned char Seg_Buf[8] = {10,10,10,10,10,10,10,10};//数码管显示数据存放数组
    unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0};//数码管小数点数据存放数组
    unsigned char Seg_Pos;//数码管扫描专用变量
    unsigned int Seg_Slow_Down;//数码管减速专用变量
    unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};//Led显示数据存放数组
    unsigned char Uart_Slow_Down;//串口减速专用变量
    unsigned char Uart_Recv[10];//串口接收数据储存数组 默认10个字节 若接收数据较长 可更改最大字节数
    unsigned char Uart_Recv_Index;//串口接收数组指针
    unsigned char Uart_Send[10];//串口接收数据储存数组 默认10个字节 若发送数据较长 可更改最大字节数
    float V;
    float V_out;
    unsigned char Seg_Mode;
    bit output_mode;
    unsigned char led_mode;
    unsigned char seg_disp_mode;

    /* 键盘处理函数 */
    void Key_Proc()
    {
    if(Key_Slow_Down) return;
    Key_Slow_Down = 1;//键盘减速程序

    Key_Val = Key_Read();//实时读取键码值
    Key_Down = Key_Val & (Key_Old ^ Key_Val);//捕捉按键下降沿
    Key_Up = ~Key_Val & (Key_Old ^ Key_Val);//捕捉按键上降沿
    Key_Old = Key_Val;//辅助扫描变量
    
    
    switch(Key_Down)
    {
      case 4:
    		Seg_Mode ^= 1;
    	break;
      case 5:
    		output_mode ^= 1;
    	if(output_mode == 0)
    		V_out = 2.0;
    	else
    		V_out = V;
    	break;
    	case 6:
    		led_mode ^= 1;
    	break;
    	case 7:
    		seg_disp_mode ^= 1;
    	
    	if(seg_disp_mode == 1)
    	{
    	  Seg_Buf[0] = 10;
    		Seg_Buf[1] = 10;
    		Seg_Buf[2] = 10;
    		Seg_Buf[3] = 10;
    		Seg_Buf[4] = 10;
    		Seg_Buf[5] = 10;
    		Seg_Buf[6] = 10;
    		Seg_Buf[7] = 10;
    		Seg_Point[5] = 0;
    	}
    	break;
    }
    

    }

    /* 信息处理函数 */
    void Seg_Proc()
    {
    if(Seg_Slow_Down) return;
    Seg_Slow_Down = 1;//数码管减速程序

     if(seg_disp_mode == 1) return;
    switch(Seg_Mode)
    {	
    	case 0:
      V = Ad_Read(0x43) / 51.0;//RB2滑动变阻器 0-255 所以要除以51.0
      Seg_Buf[0] = 12;
      Seg_Buf[1] = 10;
      Seg_Buf[2] = 10;
      Seg_Buf[3] = 10;
      Seg_Buf[4] = 10;
    	Seg_Buf[5] = (unsigned int)V % 10;
    	Seg_Buf[6] = (unsigned int)(V * 10) %10;
    	Seg_Buf[7] = (unsigned int)(V * 100) % 10;
    	Seg_Point[5] = 1;
    	break;
    	case 1:
    		
    	Seg_Buf[0] = 13;
    	Seg_Buf[1] = 10;
      Seg_Buf[2] = 10;
      Seg_Buf[3] = 10;
      Seg_Buf[4] = 10;
    	Seg_Buf[5] = (unsigned int)V_out % 10;
    	Seg_Buf[6] = (unsigned int)(V_out * 10) % 10;
    	Seg_Buf[7] = (unsigned int)(V_out * 100) % 10;
    	Seg_Point[5] = 1;
    	break;
    }
    

    }

    /* 其他显示函数 */
    void Led_Proc()
    {

    Da_Write(V_out * 51.0);//电压输出
    if (led_mode == 0)
    {
    	if(Seg_Mode == 0)
     { 
       ucLed[0] = 1;
    	 ucLed[1] = 0;
     }
     else
     {	  
    	 ucLed[1] = 1;
    	 ucLed[0] = 0;
     }
     if( V_out < 1.5 || (V_out >= 2.5 && V_out <= 3.5) )
    	ucLed[2] = 0;
    else
    	ucLed[2] = 1;
    
    if(output_mode == 0)
    	ucLed[3] = 0;
    else
    	ucLed[3] = 1;
    

    }
    else
    {
    ucLed[0] = 0;
    ucLed[1] = 0;
    ucLed[2] = 0;
    ucLed[3] = 0;
    }
    }

    /* 串口处理函数 */
    void Uart_Proc()
    {
    if(Uart_Slow_Down) return;
    Uart_Slow_Down = 1;//串口减速程序

    }

    /* 定时器0中断初始化函数 */
    void Timer0Init(void) //1毫秒@12.000MHz
    {
    AUXR &= 0x7F; //定时器时钟12T模式
    TMOD &= 0xF0; //设置定时器模式
    TL0 = 0x18; //设置定时初始值
    TH0 = 0xFC; //设置定时初始值
    TF0 = 0; //清除TF0标志
    TR0 = 1; //定时器0开始计时
    ET0 = 1; //定时器中断0打开
    EA = 1; //总中断打开
    }

    /* 定时器0中断服务函数 */
    void Timer0Server() interrupt 1
    {
    if(++Key_Slow_Down == 10) Key_Slow_Down = 0;//键盘减速专用
    if(++Seg_Slow_Down == 500) Seg_Slow_Down = 0;//数码管减速专用
    if(++Uart_Slow_Down == 200) Uart_Slow_Down = 0;//串口减速专用
    if(++Seg_Pos == 8) Seg_Pos = 0;//数码管显示专用
    Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
    Led_Disp(Seg_Pos,ucLed[Seg_Pos]);
    }

    /* 串口1中断服务函数 */
    void Uart1Server() interrupt 4
    {
    if(RI == 1) //串口接收数据
    {
    Uart_Recv[Uart_Recv_Index] = SBUF;
    Uart_Recv_Index++;
    RI = 0;
    }
    }

    /* Main */
    void main()
    {
    System_Init();
    Timer0Init();
    UartInit();
    while (1)
    {
    Key_Proc();
    Seg_Proc();
    Led_Proc();
    Uart_Proc();
    }
    }

  2. 变量命名不规范! ( ̄~ ̄;)

你的命名:
float V;
float V_out;
unsigned char Seg_Mode;
bit output_mode;
unsigned char led_mode;
unsigned char seg_disp_mode;

标准答案的命名:
bit Seg_Disp_Mode; // 数码管显示模式
bit Output_Mode; // 电压输出模式
bit Led_Enable_Flag = 1; // Led使能标志位
bit Seg_Enable_Flag = 1; // 数码管使能标志位
float Voltage_Output; // 实时输出电压值
float Voltage; // 实时读取电压值

问题:

  • V 和 V_out 太简短,不够清晰!应该用 Voltage 和 Voltage_Output
  • Seg_Mode 应该是 bit 类型,不是 unsigned char
  • led_mode 和 seg_disp_mode 的含义不清楚,应该叫 Led_Enable_Flag 和 Seg_Enable_Flag
  • 命名风格不统一:有的用下划线,有的用驼峰
  1. 变量没有初始化! ( ̄へ ̄)

你的代码: 所有变量都没初始化

标准答案:
bit Led_Enable_Flag = 1; // 默认使能
bit Seg_Enable_Flag = 1; // 默认使能

问题: 虽然全局变量会自动初始化为0,但显式初始化能让代码意图更清晰!

  1. 核心逻辑问题 - 电压输出计算位置错误! ( ` ω´ )

你的代码:
// 在 Key_Proc() 中
case 5:
output_mode ^= 1;
if(output_mode == 0)
V_out = 2.0;
else
V_out = V; // 问题:V可能还没读取!
break;

标准答案:
// 在 Seg_Proc() 中统一处理
Voltage = AD_Read(0x43) / 51.0; // 先读取
Voltage_Output = Output_Mode?Voltage:2; // 再计算输出

问题: 你在按键处理中计算输出电压,但此时 V 可能还没更新!标准答案是在 Seg_Proc() 中每次都重新计算,这样更实时!

  1. DA输出参数错误! ( ゚Д゚)

你的代码:
Da_Write(V_out); // 错误!

标准答案:
DA_Write(Voltage_Output*51); // 正确!

问题: PCF8591的DA转换需要0-255的值,而不是0-5V的浮点数!你需要乘以51转换!

  1. 数码管显示计算不够精确! ( ̄_ ̄)

你的代码:
Seg_Buf[5] = (unsigned int)V % 10;
Seg_Buf[6] = (unsigned int)(V * 10) % 10; // 问题在这里!
Seg_Buf[7] = (unsigned int)(V * 100) % 10;

标准答案:
Seg_Buf[5] = (unsigned char)Voltage;
Seg_Buf[6] = (unsigned int)(Voltage * 100) / 10 % 10; // 更精确!
Seg_Buf[7] = (unsigned int)(Voltage * 100) % 10;

问题: 你的第6位计算方式会有精度问题!标准答案先乘100再除10,避免了浮点数精度损失!

  1. LED控制逻辑冗余! ( ̄ε ̄*)

你的代码:
if(Seg_Mode == 0)
{
ucLed[0] = 1;
ucLed[1] = 0;
}
else
{
ucLed[1] = 1;
ucLed[0] = 0;
}

标准答案:
for(i=0;i<2;i++)
ucLed[i] = (i==Seg_Disp_Mode); // 简洁优雅!

问题: 你的代码太冗余了!标准答案用一行就搞定了,这才是专业的写法!

  1. LED2判断使用了错误的变量! ( ` ω´ )

你的代码:
if( V_out < 1.5 || (V_out >= 2.5 && V_out <= 3.5) ) // 用的是输出值
ucLed[2] = 0;

标准答案:
ucLed[2] = !(Voltage < 1.5 || (Voltage >= 2.5 && Voltage < 3.5)); // 用的是读取值

问题: LED2应该根据读取的电压值来判断,而不是输出的电压值!而且标准答案用了取反,逻辑更清晰!

  1. 包含了不必要的串口代码! ( ̄~ ̄;)

你的代码: 包含了 Uart.h、Uart_Proc()、Uart1Server() 等串口相关代码

标准答案: 完全没有串口相关代码

问题: 这个项目不需要串口功能,你加了一堆无用代码!

  1. 数码管字符显示不一致! ( ̄_ ̄)

你的代码:
case 0:
Seg_Buf[0] = 12; // 显示什么?
case 1:
Seg_Buf[0] = 13; // 显示什么?

标准答案:
case 0:
Seg_Buf[0] = 11; // U - 表示电压显示
case 1:
Seg_Buf[0] = 12; // F - 表示输出显示

问题: 你的字符编码和标准答案不一样,可能显示不正确!


:bullseye: 改进建议 (* ̄︶ ̄)

优先级1 - 必须修改(影响功能):

  1. 修改 DA_Write 参数:Da_Write(V_out*51);
  2. 重构电压输出计算逻辑:把 V_out 的计算移到 Seg_Proc() 中
  3. 修改 LED2 判断变量:从 V_out 改为 V
  4. 修改数码管显示计算:第6位改为 (unsigned int)(V * 100) / 10 % 10

优先级2 - 建议修改(代码质量):

  1. 重命名变量:使用更清晰的命名
  2. 简化 LED 控制逻辑:使用循环和三元运算符
  3. 删除串口相关代码:不需要的功能
  4. 添加变量初始化:显式初始化标志位

优先级3 - 可选优化(代码风格):

  1. 统一缩进:使用Tab或统一空格数
  2. 添加更多注释:说明每个变量和函数的用途
  3. 使用 for 循环清空显示:更简洁