梁工 发表于 2023-10-17 00:31:54

51 开源 电容表: 8H8K64U比较器+OLED12864-I2C 实现




51 开源 电容表:
8H8K64U比较器+OLED12864-I2C 实现,最大量程25mF
电路简单,工作可靠,读数稳定,测量的电容量就是在普通的振荡电路中的容量。
赶工做出来的,测试有问题可以反馈给我。
两个键,短按放开为【档位+】、【档位-】,长按任一键超过1秒读数归0并保存。
测电容方法很多,不同的方法、不同的测试电压、测试频率其容量会有差别。
本电容表测试几十pF、几百pF的电容误差比较大,可能到5%,
超过1nF的误差一般2%以下。





分别为测量330nF、100pF、330pF、22nF、330nF(再重复测一次)






分别为测量10uF、100uF。




分别为测量470uF、3300uF、10000uF





电路原理图PDF、PCB、程序C源码:








神农鼎 发表于 2023-10-17 07:44:38

下载开源程序包解压缩后如下:




【CAN 原理及实战,8课时】
线上视频授课:10月/30号和11月/1号:14:00 ~ 17:00;
腾讯会议号:885-5858-2739; (安装腾讯会议软件后,输入会议号即可)
参会学习立即【免费+包邮送】USB转2组独立的CAN核心功能实验板
                                             模拟的CAN收发器您自己补上
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=4526&extra=&page=1
请帮忙转发给可能需要:从0开始了解CAN的 同学/同事/老师/研发人员

神农鼎 发表于 2023-10-17 07:46:06

//main-电容表充放电版-V2E.c 中的程序
/* ---技术交流: www.STCAIMCU.com ------------------------------------*/
/* ---资料下载: www.STCAI.com -----------------------------------------*/
/* --- Tel: 86-0513-55012928,55012929,55012966 ---------------------*/
/* If you want to use the program or the program referenced in the article,   */
   please specify in which data and procedures from STC    */


      #include      "config.h"
      #include      "OLED128x64.h"
      #include      "EEPROM.h"

/**************************************************
main-电容表充放电版-V2E.c增加EEPROM保存0点读书。

用STC8H8K64U-TSSOP20的比较器做的电容表,5档量程,用OLED显示。
电阻      范围            放电(2欧姆)
1M    0.001 nF ~   2.5 uF    1ms
100K   0.01 nF ~    25 uF    1ms
10K   0.1 nF ~   250 uF    4ms
1K      1 nF ~2500 uF    40ms
100R   0.01 uF ~ 25000 uF    400ms

P3.0--按键2, 准双向口,低有效。档位+/归0。
P3.2--按键1, 准双向口,低有效。档位-/归0。
P3.3--Timer1门控输入+INT1外部中断,准双向口,高电平计数, 低电平停止计数,下降沿中断。
P3.4--模拟比较器输出,推挽输出。
P3.5--放电控制,推挽输出,高有效。
P3.6--模拟比较器-输入, 高阻.
P3.7--模拟比较器+输入, 高阻.
P1.0--OLED12864-SCL 开漏。
P1.1--OLED12864-SDA 开漏。
P1.3--RELAY1,推挽输出,高有效。 100R, 20mF档( 0.01uF~20.00mF)。
P5.4--RELAY2,推挽输出,高有效。 1K,    2mF档(0.001uF~2.000mF)。
P1.4--RELAY3,推挽输出,高有效。 10K, 200uF档(0.1nF~200.0uF)。
P1.5--RELAY4,推挽输出,高有效。 100K, 20uF档( 0.01nF~20.00uF)。RELAY1~4均断开则是2uF档(0.001nF~2.000uF).
P1.6 P1.7--24MHz晶振。高阻.

Timer1工作于12T模式, 测量充电时间, 电容 C = t/R.

**************************************************/


/********************* IO、变量、常量定义 *******************************/
sbit      P_KEY1                         = P3^2;      //按键1, 准双向口,低有效。
sbit      P_KEY2                         = P3^0;      //按键1, 准双向口,低有效。
sbit      P_RELAY1               = P1^3;      //RELAY1,推挽输出,高有效。 100R, 20mF档( 0.01uF~20.00mF)。
sbit      P_RELAY2               = P5^4;      //RELAY2,推挽输出,高有效。 1K,    2mF档(0.001uF~2.000mF)。
sbit      P_RELAY3               = P1^4;      //RELAY3,推挽输出,高有效。 10K, 200uF档(0.1nF~200.0uF)。
sbit      P_RELAY4               = P1^5;      //RELAY4,推挽输出,高有效。 100K, 20uF档( 0.01nF~20.00uF)。RELAY1~4均断开则是2uF档(0.001nF~2.000uF).
sbit      P_DISCHARGE               = P3^5;      //放电控制,推挽输出,高有效。

#define      K_LEVEL_UP                1
#define      K_LEVEL_DOWN      2
#define      K_ZERO                        3

u8      key_state, KeyCode, KeyHoldCnt;

bit      B_end;
bit      B_neg;
bit      B_zero;
u8      cnt_50ms;
u8      level;                //档位: 0:2uF, 1:20uF, 2:200uF, 3:2mF, 4:20mF
u32      C_zero;
u16      DischargeTimeCal;
u16      DischargeTime;
u8      CapCntH;
u8      index;
u16      LimitTime;
u16      UpdateTime;
u16      ChargeCnt;
u8      DotState;      //小数点位置: 0:不显示小数点,1:第一位后,2:第二位后,3:第三位后

u8 xdata tmp;


/********************* 函数声明 *******************************/
void      DEC_x_xxx(u16 j);
void      DEC_xx_xx(u16 j);
void      DEC_xxx_x(u16 j);
void      DEC_xxxx(u16 j);
void      Timer1_config(void);
void      Compare_Config(void);      //比较器初始化
void      ClearCapacitance(void);      //清除电容显示值
void      ShowLevel(void);                // 显示档位
u16                MODBUS_CRC16(u8 *p, u8 n);



//                         2uF      20uF    200uF   2mF   20mF
float code T_C_Sale={ 1.0321f, 1.0091f, 1.0098f, 1.0060f, 1.0125f};      // 电容修正系数, 根据实际测试情况调整


/*************************** 主程序 *************************************/
void main(void)
{
      u8      i;
      u16      crc;
      u32      j;

      P_SW2 = 0x80;
      XOSCCR = 0xc0;                        //启动外部晶振
      while (!(XOSCCR & 1));      //等待时钟稳定
      CLKDIV = 0x00;                        //时钟不分频
      CKSEL = 0x01;                        //选择外部晶振

      P_KEY1                = 1;      //按键1, 准双向口,低有效。
      P_KEY2                = 1;      //按键1, 准双向口,低有效。
      P_RELAY1      = 0;      //RELAY1,推挽输出,高有效。 100R, 20mF档( 0.01uF~20.00mF)。
      P_RELAY2      = 0;      //RELAY2,推挽输出,高有效。 1K,    2mF档(0.001uF~2.000mF)。
      P_RELAY3      = 0;      //RELAY3,推挽输出,高有效。 10K, 200uF档(0.1nF~200.0uF)。
      P_RELAY4      = 0;      //RELAY4,推挽输出,高有效。 100K, 20uF档( 0.01nF~20.00uF)。RELAY1~4均断开则是2uF档(0.001nF~2.000uF).
      P_DISCHARGE      = 1;      //放电控制,推挽输出,高有效。

      P3n_standard(0x0f);                //P3.0~P3.3设置为准双向口

      P1n_pure_input(0xc0);      //设置P1.7 P1.6为高阻输入,晶振
      P3n_push_pull(Pin5);      //放电控制, 推挽输出,高有效。
      P1n_push_pull(0x38);      //继电器驱动, 推挽输出,高有效。
      P5n_push_pull(0x10);      //继电器驱动, 推挽输出,高有效。

      OLED_config();

      printf_ascii_10x24(40,5,"");
      FillAll(0);
      printf_ascii_6x8(0, 0, "-------C METER-------");

      level = 2;                //上电默认档位,档位: 档位: 0:2uF, 1:20uF, 2:200uF, 3:2mF, 4:20mF
      ShowLevel();      //显示档位

      Compare_Config();
      Timer1_config();
      EA = 1;                //打开总中断

      EEPROM_read_n(0,tmp,22);      //读EEPROM
      if(MODBUS_CRC16(tmp,22) == 0)      //记录正确
      {
                C_zero = ((u32 *)&tmp);      //   2uF
                C_zero = ((u32 *)&tmp);      //20uF
                C_zero = ((u32 *)&tmp);      // 200uF
                C_zero = ((u32 *)&tmp);      //   2mF
                C_zero = ((u32 *)&tmp);      //20mF
                if(C_zero >= 1000)      C_zero = 200;      //0点异常
                if(C_zero >= 1000)      C_zero = 20;                //0点异常
                if(C_zero >= 1000)      C_zero = 2;                //0点异常
                if(C_zero >= 1000)      C_zero = 1;                //0点异常
                if(C_zero >= 1000)      C_zero = 1;                //0点异常
      }
      else   //EEPROM记录不正确,则默认0点
      {
                C_zero = 200;      //   2uF      放电管MOSFET截止时DS极有超过200pF电容,所以会有一个200多的读数。
                C_zero = 20;                //20uF
                C_zero = 2;                // 200uF
                C_zero = 1;                //   2mF
                C_zero = 1;                //20mF
      }

      DotState = 5;      //给一个非法值,第一次显示就会给定正确值

      while(1)      //1ms节拍
      {
                OLED_delay_ms(1);
                if(++cnt_50ms == 50)
                {
                        cnt_50ms = 0;

                        i = key_state;
                        B0 = P_KEY1;
                        B1 = P_KEY2;
                        key_state = ~B & 0x03;
                        B = (i ^ key_state) & i;      //键释放
                        if((B != 0) && (KeyHoldCnt < 16))
                        {
                              if(B1)      KeyCode = K_LEVEL_UP;                //档位变大
                              if(B0)      KeyCode = K_LEVEL_DOWN;                //档位变小
                        }
                        if(key_state != 0)      //键按着
                        {
                              if(++KeyHoldCnt == 100)      KeyHoldCnt = 99;
                              if(KeyHoldCnt == 16)      KeyCode = K_ZERO;
                        }
                        else      KeyHoldCnt = 0;

                        if(KeyCode != 0)
                        {
                                       if(KeyCode == K_ZERO)      B_zero = 1;      //长按1秒归0
                              else if(KeyCode == K_LEVEL_UP)                        //档位变大
                              {
                                        if(++level >= 5)      level = 0;
                                        ShowLevel();      //显示档位
                              }
                              else if(KeyCode == K_LEVEL_DOWN)                //档位变小
                              {
                                        if(--level >= 5)      level = 4;
                                        ShowLevel();      //显示档位
                              }
                              KeyCode = 0;
                        }
                }

                if(LimitTime != 0)      LimitTime--;      //限时时间倒计时
                if(UpdateTime != 0)      UpdateTime--;      //更新时间倒计时


                if(index == 0)      //启动
                {
                        TR1 = 0;
                        CapCntH = 0;
                        TH1 = 0;
                        TL1 = 0;
                        LimitTime= 2500;      //限时2500ms
                        UpdateTime = 500;      //500ms更新一次, 超过按实际时间更新
                        ChargeCnt= 0;                //充电次数计数
                        index = 1;                        //下一步
                }
                else if(index == 1)                //开始放电
                {
                        P_DISCHARGE = 1;                //开始放电
                                 if(level <= 1)      DischargeTime = 1;      //2uF 20uF档放电时间1ms, 放电电阻2R.
                        else if(level == 2)      DischargeTime = 1 + DischargeTimeCal;      //200uF档放电时间1ms+修正时间(100uF增加1.6ms, 即8倍RC值), 根据充电时间来修正, 充电时间越长电容越大则放电时间也越长, 保证放点时间为6*2*C以上.
                        else if(level == 3)      DischargeTime = 10 + DischargeTimeCal;      //2mF档放电时间 10ms+修正时间(100uF增加1.6ms, 即8倍RC值), 根据充电时间来修正, 充电时间越长电容越大则放电时间也越长, 保证放点时间为6*2*C以上.
                        else if(level == 4)      DischargeTime = 50 + DischargeTimeCal;      // 20mF档放电时间 50ms+修正时间(100uF增加1.6ms, 即8倍RC值), 根据充电时间来修正, 充电时间越长电容越大则放电时间也越长, 保证放点时间为6*2*C以上.
                        index = 2;                //下一步
                }
                else if(index == 2)                //等待放电结束
                {
                        if(DischargeTime != 0)      //放电时间未到
                        {
                              if(--DischargeTime == 0)      //放电结束
                              {
                                        P_DISCHARGE = 0;      //开始充电
                                        NOP(12);
                                        TR1 = 1;      // Timer1开始计时
                                        IE1 = 0;      // 外中断1标志位
                                        EX1 = 1;      // 外部中断1允许
                                        index = 3;      //下一步
                              }
                        }
                }
                else if(index == 3)                //等待充电结束
                {
                        if(B_end)      //充电结束
                        {
                              B_end = 0;
                              TR1 = 0;                        // 停止计数
                              EX1 = 0;                        // 外部中断1禁止
                              P_DISCHARGE = 1;      // 放电
                              ChargeCnt++;                //充电次数+1
                              if(UpdateTime != 0)      index = 1;      //更新时间未到, 则重复充放电一次
                              else                //更新时间到
                              {
                                        index = 0;      //下一步重新开始
                                        j = ((u32)CapCntH << 16) + ((u32)TH1 << 8) + (u32)TL1;                //读计数值
                                        j = j / 2 / ChargeCnt;      //计算一个脉冲的时间(充电时间) , 分辨率1us(计数器始终为2MHz,周期为0.2us)
                                        if(B_zero)      // 归0
                                        {
                                                B_zero = 0;
                                                C_zero = j;      //电容归0

                                                ((u32 *)&tmp) = C_zero;
                                                ((u32 *)&tmp) = C_zero;
                                                ((u32 *)&tmp) = C_zero;
                                                ((u32 *)&tmp) = C_zero;
                                                ((u32 *)&tmp) = C_zero;
                                                crc = MODBUS_CRC16(tmp,20);
                                                tmp = (u8)(crc % 256);
                                                tmp = (u8)(crc / 256);
                                                EEPROM_SectorErase(0);
                                                EEPROM_write_n(0, tmp, 22);                // 保存EEPROM
                                        }

                                        j = j - C_zero;      //0点修正
                                        if(j & 0x80000000)      j = -j, B_neg = 1, tmp = '-';      // 负数
                                        else                              B_neg = 0, tmp = ' ';                        // 正数
                                        j = (u32)((float)j * T_C_Sale);                //校准系数

                                                 if(level == 2)      DischargeTimeCal = (u16)(j / 600000UL);      //200uF档 修正放电时间(100uF增加1.6ms, 即8倍RC值)
                                        else if(level == 3)      DischargeTimeCal = (u16)(j /60000UL);      //2mF档 修正放电时间(100uF增加1.6ms, 即8倍RC值)
                                        else if(level == 4)      DischargeTimeCal = (u16)(j /   6000UL);      // 20mF档 修正放电时间(100uF增加1.6ms, 即8倍RC值)
                                        else                              DischargeTimeCal = 0;

                                        if(level == 0)      //2uF档, 1pF~2uF
                                        {
                                                if(j >= 1000000UL)      // 0.001~2.000uF
                                                {
                                                      DEC_x_xxx((u16)(j / 1000));
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                                else if(j >= 100000UL)      // 0.1~200.0nF
                                                {
                                                      DEC_xxx_x((u16)(j / 100));
                                                      printf_ascii_6x8(114, 6, "nF");
                                                }
                                                else if(j >= 10000UL)      // 0.01~20.00nF
                                                {
                                                      DEC_xx_xx((u16)(j / 10));
                                                      printf_ascii_6x8(114, 6, "nF");
                                                }
                                                else                //0.001~2.000nF
                                                {
                                                      DEC_x_xxx((u16)j);
                                                      printf_ascii_6x8(114, 6, "nF");
                                                }
                                        }

                                        else if(level == 1)      //20uF档, 0.01nF~20uF
                                        {
                                                if(j >= 1000000UL)      // 0.01~20.00uF
                                                {
                                                      DEC_xx_xx((u16)(j / 1000));
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                                else if(j >= 100000UL)      // 0.001~2.000uF
                                                {
                                                      DEC_x_xxx((u16)(j / 100));
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                                else if(j >= 10000UL)      // 0.1~200.0nF
                                                {
                                                      DEC_xxx_x((u16)(j / 10));
                                                      printf_ascii_6x8(114, 6, "nF");
                                                }
                                                else                //0.01~20.00nF
                                                {
                                                      DEC_xx_xx((u16)j);
                                                      printf_ascii_6x8(114, 6, "nF");
                                                }
                                        }

                                        else if(level == 2)      //200uF档, 0.1nF~200uF
                                        {
                                                if(j >= 1000000UL)      // 0.1~200.0uF
                                                {
                                                      DEC_xxx_x((u16)(j / 1000));
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                                else if(j >= 100000UL)      // 0.01~20.00uF
                                                {
                                                      DEC_xx_xx((u16)(j / 100));
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                                else if(j >= 10000UL)      // 0.001~2.000uF
                                                {
                                                      DEC_x_xxx((u16)(j / 10));
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                                else                //0.1~200.0nF
                                                {
                                                      DEC_xxx_x((u16)j);
                                                      printf_ascii_6x8(114, 6, "nF");
                                                }
                                        }
                                        else if(level == 3)      //2mF档, 0.001uF~2.000mF
                                        {
                                                if(j >= 1000000UL)      // 0.001mF~2.000mF
                                                {
                                                      DEC_x_xxx((u16)(j / 1000));
                                                      printf_ascii_6x8(114, 6, "mF");
                                                }
                                                else if(j >= 100000UL)      // 0.1~200.0uF
                                                {
                                                      DEC_xxx_x((u16)(j / 100));
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                                else if(j >= 10000UL)      // 0.01~20.00uF
                                                {
                                                      DEC_xx_xx((u16)(j / 10));
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                                else                //0.001~2.000uF
                                                {
                                                      DEC_x_xxx((u16)j);
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                        }
                                        else if(level == 4)      //20mF档, 10nF~20mF
                                        {
                                                if(j >= 1000000UL)      // 0.01~20.00mF
                                                {
                                                      DEC_xx_xx((u16)(j / 1000));
                                                      printf_ascii_6x8(114, 6, "mF");
                                                }
                                                else if(j >= 100000UL)      // 0.001mF~2.000mF
                                                {
                                                      DEC_x_xxx((u16)(j / 100));
                                                      printf_ascii_6x8(114, 6, "mF");
                                                }
                                                else if(j >= 10000UL)      // 0.1~200.0uF
                                                {
                                                      DEC_xxx_x((u16)(j / 10));
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                                else                //0.01~20.00uF
                                                {
                                                      DEC_xx_xx((u16)j);
                                                      printf_ascii_6x8(114, 6, "uF");
                                                }
                                        }
                                        tmp = 0;
                                        printf_ascii_10x24(40,5,tmp);
                              }
                        }
                        else if(LimitTime == 0)      //超时了
                        {
                              index = 0;      //下一步重新开始
                              ClearCapacitance();      //清除电容显示值
                        }
                }
      }
}



void      ClearCapacitance(void)      //清除电容显示值
{
      printf_ascii_6x8(40, 5, "            ");
      printf_ascii_6x8(40, 6, "            ");
      printf_ascii_6x8(40, 7, "            ");
}

void      ShowLevel(void)                // 显示档位
{
      P_RELAY1 = 0;      //RELAY1,推挽输出,高有效。 100R, 20mF档( 0.01uF~20.00mF)。
      P_RELAY2 = 0;      //RELAY2,推挽输出,高有效。 1K,    2mF档(0.001uF~2.000mF)。
      P_RELAY3 = 0;      //RELAY3,推挽输出,高有效。 10K, 200uF档(0.1nF~200.0uF)。
      P_RELAY4 = 0;      //RELAY4,推挽输出,高有效。 100K, 20uF档( 0.01nF~20.00uF)。RELAY1~4均断开则是2uF档(0.001nF~2.000uF).
      DischargeTimeCal = 0;
      index = 0;

               if(level == 0)      printf_ascii_6x8(0, 5, "2uF"), printf_ascii_6x8(0, 7, "R 1M");                                        //   1M,   2uF档。      RELAY1~4均断开则是2uF档(0.001nF~2.000uF).
      else if(level == 1)      printf_ascii_6x8(0, 5, " 20uF"), printf_ascii_6x8(0, 7, "R 100K"), P_RELAY4 = 1;      // 100K,   2mF档。
      else if(level == 2)      printf_ascii_6x8(0, 5, "200uF"), printf_ascii_6x8(0, 7, "R 10K "), P_RELAY3 = 1;      //10K,20mF档。
      else if(level == 3)      printf_ascii_6x8(0, 5, "2mF"), printf_ascii_6x8(0, 7, "R 1K"), P_RELAY2 = 1;      //   1K,   2mF档。
      else if(level == 4)      printf_ascii_6x8(0, 5, " 20mF"), printf_ascii_6x8(0, 7, "R 100R"), P_RELAY1 = 1;      // 100R,20mF档。

      ClearCapacitance();      //清除电容显示值
}


//将数字显示成 x.xxx
void      DEC_x_xxx(u16 j)
{
      if(DotState != 1)                //当前小数点不是第1位,则先清除显示
      {
                ClearCapacitance();      //清除电容显示值
                DotState = 1;
      }
      tmp = j / 1000 + '0';
      tmp = '.';
      tmp = (j % 1000) / 100 + '0';
      tmp = (j % 100) / 10 + '0';
      tmp =j % 10 + '0';
}
//将数字显示成 xx.xx
void      DEC_xx_xx(u16 j)
{
      if(DotState != 2)                //当前小数点不是第2位,则先清除显示
      {
                ClearCapacitance();      //清除电容显示值
                DotState = 2;
      }
      tmp = j / 1000 + '0';
      tmp = (j % 1000) / 100 + '0';
      tmp = '.';
      tmp = (j % 100) / 10 + '0';
      tmp =j % 10 + '0';
      if(tmp == '0')      tmp = ' ';
}
//将数字显示成 xxx.x
void      DEC_xxx_x(u16 j)
{
      if(DotState != 3)                //当前小数点不是第3位,则先清除显示
      {
                ClearCapacitance();      //清除电容显示值
                DotState = 3;
      }
      tmp = j / 1000 + '0';
      tmp = (j % 1000) / 100 + '0';
      tmp = (j % 100) / 10 + '0';
      tmp = '.';
      tmp =j % 10 + '0';
      if(tmp == '0')
      {
                tmp = ' ';
                if(tmp == '0')      tmp = ' ';
      }
}

/************************ 比较器配置 ****************************/
void      Compare_Config(void)      //比较器初始化
{
      u8      i;
      CMPCR1 = 0;
      CMPCR2 = 24;                //比较结果变化延时周期数, 0~63
      CMPCR1 |= (1<<7);      //1: 允许比较器,   0:关闭比较器
      CMPCR1 |= (0<<5);      //1: 允许上升沿中断, 0: 禁止
      CMPCR1 |= (0<<4);      //1: 允许下降沿中断, 0: 禁止
      CMPCR1 |= (0<<3);      //输入正极性选择, 0: 选择外部P3.7做正输入,         1: 由ADC_CHS所选择的ADC输入端做正输入.
      CMPCR1 |= (0<<2);      //输入负极性选择, 0: 选择内部BandGap电压BGv做负输入, 1: 选择外部P3.6做输入
      CMPCR1 |= (1<<1);      //1: 允许比较结果输出到IO(P3.4或P4.1),0: 比较结果禁止输出到IO
      CMPCR2 |= (0<<7);      //1: 比较器结果输出IO取反, 0: 不取反
      CMPCR2 |= (0<<6);      //0: 允许内部0.1uF滤波,    1: 关闭

      P_SW2 |= 0x80;                //SFR enable
      i = 0;
      i |= (0<<6);      //比较器迟滞输入选择: 0: 0mV,1: 10mV, 2: 20mV, 3: 30mV
      i |= (0<<2);      //输入负极性选择, 0: 选择P3.6做输入,   1: 选择内部BandGap电压BGv做负输入.
      i |=0;                //输入正极性选择, 0: 选择P3.7做输入,   1: 选择P5.0做输入,2: 选择P5.1做输入,3: 选择ADC输入(由ADC_CHS所选择的ADC输入端做正输入).
      CMPEXCFG = i;

      CMPO_P34();                              //结果输出到P3.4.
//      CMPO_P41();                              //结果输出到P4.1.
      P3n_push_pull(Pin4);      //P3.4设置为推挽输出
      P3n_pure_input(0xc0);      //设置要做ADC的IO做高阻输入(P3.7 P3.6)
}




//========================================================================
// 函数: void      Timer1_config(void)
// 描述: 初始化Timer1。
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2010-12-15
// 备注:
//========================================================================

void      Timer1_config(void)
{
      Timer1_AsTimer();
      Timer1_16bit();
      Timer1_12T();
      Timer1_Gate_INT1_P33();      //INT1高电平计时
      TH1= 0;
      TL1= 0;
      CapCntH = 0;
      ET1 = 1;                        //允许T1中断

      IT1 = 1;      // 外部中断1下降沿中断
}

/********************* INT1中断函数 *************************/
void INT1_ISR (void) interrupt 2
{
      TR1 = 0;
      P_DISCHARGE = 1;      //放电
      B_end = 1;
}


//========================================================================
// 函数: void timer1 (void) interrupt 3
// 描述: Timer1中断函数,模拟PWM的核心函数。
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2010-12-15
// 备注:
//========================================================================
void timer1_ISR (void) interrupt 3
{
      CapCntH++;
}


//========================================================================
// 函数: u16      MODBUS_CRC16(u8 *p, u8 n)
// 描述: 计算CRC16函数.
// 参数: *p: 要计算的数据指针.
//      n: 要计算的字节数.
// 返回: CRC16值.
// 版本: V1.0, 2022-3-18 梁工
//========================================================================
u16      MODBUS_CRC16(u8 *p, u8 n)
{
      u8      i;
      u16      crc16;

      crc16 = 0xffff;      //预置16位CRC寄存器为0xffff(即全为1)
      do
      {
                crc16 ^= (u16)*p;                //把8位数据与16位CRC寄存器的低位相异或,把结果放于CRC寄存器
                for(i=0; i<8; i++)                //8位数据
                {
                        if(crc16 & 1)      crc16 = (crc16 >> 1) ^ 0xA001;      //如果最低位为0,把CRC寄存器的内容右移一位(朝低位),用0填补最高位,
                                                                                                                        //再异或多项式0xA001
                        else      crc16 >>= 1;                                                      //如果最低位为0,把CRC寄存器的内容右移一位(朝低位),用0填补最高位
                }
                p++;
      }while(--n != 0);
      return      (crc16);
}




神农鼎 发表于 2023-10-17 07:47:42

//OLED128x64-SSD1306-I2C-V30.c 中的程序
/* ---技术交流: www.STCAIMCU.com ------------------------------------*/
/* ---资料下载: www.STCAI.com -----------------------------------------*/
/* --- Tel: 86-0513-55012928,55012929,55012966 ---------------------*/
/* If you want to use the program or the program referenced in your article,
    please specify in which data and procedures from STC    */

#include      "config.h"
#include      "ASCII5x7.h"
#include      "ASCII-10x24.h"

/*************      功能说明      **************
淘宝买的OLED屏驱动程序. 驱动IC为SSD1306, I2C接口.
支持中文英文混显, 支持10x24点大数字显示.
******************************************/

//************************************************************************************************

/*      定义接口      */
                                        // 1--GND                              D2      SDAout                D/C --> SA0 从机地址最低位, 模块接到0了
                                        // 2--VCC      3.3V                E ----> low                RES --> 复位脚, 低电平复位
sbit SCL = P1^0;      // 3--D0      SCL                        R/W --> low
sbit SDA = P1^1;      // 4--D1      SDAin                CS ---> low

#define SLAW      0x78      /* 从机地址写 */
#define SLAR      0x79      /* 从机地址读 */

//========================================================================
// 描述: 延时函数
// 参数: dly 延时时间ms, 1~65535.
// 返回: 无
//========================================================================
void      OLED_delay_ms(u16 ms)      // 1~65535
{
      u16 i;
      do
      {
                i = MAIN_Fosc / 10000;                //STC8系列
                while(--i)      ;
      }while(--ms);
}


/****************************/

void      OLED_I2C_Delay(void)
{
      u8      dly;
      dly = (MAIN_Fosc * 2) / 3000000UL;      //周期数: 6+3*n,   I2C位时间调整, (Fosc/1000000) * n / 3 = (MAIN_Fosc * n) / 3000000UL, n为us, 3为while(--dly)的周期数
      while(--dly)      ;
}

#define      OLED_I2C_NOP()                NOP(3)                // 至少加3个NOP @24MHz, 至少加4个NOP @36MHz, 至少加5个NOP @44MHz, 否则显示不正常 STC8H8K64U


/****************************/
void OLED_I2C_Start(void)               //start the I2C, SDA High-to-low when SCL is high
{
      SDA = 1;
//      OLED_I2C_Delay();
      SCL = 1;
      OLED_I2C_Delay();
      SDA = 0;
      OLED_I2C_Delay();
      SCL = 0;
//      OLED_I2C_Delay();
}


void OLED_I2C_Stop(void)                //STOP the I2C, SDA Low-to-high when SCL is high
{
      SDA = 0;
      OLED_I2C_Delay();
      SCL = 1;
      OLED_I2C_Delay();
      SDA = 1;
      OLED_I2C_Delay();
}


/********** 写一个字节并读出应答放F0(正确应答F0=0, 错误F0=1) ******************/
void OLED_I2C_WriteAbyte(u8 dat)                //write a byte to I2C      速度: 24MHZ, 12T/bit, 107+8=115T, 4.8us/byte.
{
      B = dat;

      SDA = B7;      OLED_I2C_NOP();      SCL = 1;      OLED_I2C_NOP();      SCL = 0;      OLED_I2C_NOP();
      SDA = B6;      OLED_I2C_NOP();      SCL = 1;      OLED_I2C_NOP();      SCL = 0;      OLED_I2C_NOP();
      SDA = B5;      OLED_I2C_NOP();      SCL = 1;      OLED_I2C_NOP();      SCL = 0;      OLED_I2C_NOP();
      SDA = B4;      OLED_I2C_NOP();      SCL = 1;      OLED_I2C_NOP();      SCL = 0;      OLED_I2C_NOP();
      SDA = B3;      OLED_I2C_NOP();      SCL = 1;      OLED_I2C_NOP();      SCL = 0;      OLED_I2C_NOP();
      SDA = B2;      OLED_I2C_NOP();      SCL = 1;      OLED_I2C_NOP();      SCL = 0;      OLED_I2C_NOP();
      SDA = B1;      OLED_I2C_NOP();      SCL = 1;      OLED_I2C_NOP();      SCL = 0;      OLED_I2C_NOP();
      SDA = B0;      OLED_I2C_NOP();      SCL = 1;      OLED_I2C_NOP();      SCL = 0;      OLED_I2C_NOP();
/*
      SDA = B7;      OLED_I2C_NOP();      SCL = 1;      NOP(2);      SCL = 0;      OLED_I2C_NOP();
      SDA = B6;      OLED_I2C_NOP();      SCL = 1;      NOP(2);      SCL = 0;      OLED_I2C_NOP();
      SDA = B5;      OLED_I2C_NOP();      SCL = 1;      NOP(2);      SCL = 0;      OLED_I2C_NOP();
      SDA = B4;      OLED_I2C_NOP();      SCL = 1;      NOP(2);      SCL = 0;      OLED_I2C_NOP();
      SDA = B3;      OLED_I2C_NOP();      SCL = 1;      NOP(2);      SCL = 0;      OLED_I2C_NOP();
      SDA = B2;      OLED_I2C_NOP();      SCL = 1;      NOP(2);      SCL = 0;      OLED_I2C_NOP();
      SDA = B1;      OLED_I2C_NOP();      SCL = 1;      NOP(2);      SCL = 0;      OLED_I2C_NOP();
      SDA = B0;      OLED_I2C_NOP();      SCL = 1;      NOP(2);      SCL = 0;      OLED_I2C_NOP();
*/

      SDA = 1;
      OLED_I2C_NOP();
      SCL = 1;
      OLED_I2C_NOP();
      F0 = SDA;
      SCL = 0;
//      OLED_I2C_NOP();
}


void      OLED_WriteCMD(u8 cmd)      // 写命令cmd
{
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                //Slave address,SA0=0
      if(!F0)
      {
                OLED_I2C_WriteAbyte(0x00);      //Write command, C0=0, D/C=0, follow by six '0'
                if(!F0)
                {
                        OLED_I2C_WriteAbyte(cmd);      //command
                }
      }
      OLED_I2C_Stop();
}


//========================================================================
// 函数: void Set_Dot_Addr_LCD(int x,int y)
// 描述: 设置在LCD的真实坐标系上的X、Y点对应的RAM地址
// 参数: x               X轴坐标
//               y               Y轴坐标
// 返回: 无
// 备注: 仅设置当前操作地址,为后面的连续操作作好准备
// 版本:
//      2007/04/10      First version
//========================================================================
void Set_Dot_Addr(u8 x, u8 y)      //x为横向的点0~127, y为纵向的页0~7
{
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                              //Slave address,SA0=0
      OLED_I2C_WriteAbyte(0x00);                              //Write command, C0=0, D/C=0, follow by six '0'
      OLED_I2C_WriteAbyte(0xb0 + y);                        //设置页0~7
      OLED_I2C_WriteAbyte(0x00);                              //Write command, C0=0, D/C=0, follow by six '0'
      OLED_I2C_WriteAbyte((x >> 4)| 0x10);      //设置列0~127 高nibble
      OLED_I2C_WriteAbyte(0x00);                              //Write command, C0=0, D/C=0, follow by six '0'
      OLED_I2C_WriteAbyte(x & 0x0f);                        //设置列0~127 低nibble
      OLED_I2C_Stop();

}


//******************************************
void FillPage(u8 y, u8 color)                //Fill Page LCD RAM, y为页码 0~7
{
      u8 i;
      Set_Dot_Addr(0,y);
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                //Slave address,SA0=0
      if(!F0)
      {
                OLED_I2C_WriteAbyte(0x40);      //write data,C0=0, D/C=1, follow by six '0'
                if(!F0)
                {
                        for(i=0; i<128; i++)      OLED_I2C_WriteAbyte(color);
                }
      }
      OLED_I2C_Stop();
}

//******************************************
void FillAll(u8 color)                //Fill all LCD RAM, color为填充的颜色
{
      u8 i;
      for(i=0; i<8; i++)      FillPage(i,color);
}

//******************************************
void OLED_config(void)                //初始化函数
{
//      P1.0--OLED12864-SCL 开漏
//      P1.1--OLED12864-SDA 开漏
      P1n_open_drain(Pin1+Pin0);

      SCL = 1;
      SDA = 1;
      OLED_delay_ms(10);

      OLED_WriteCMD(0xAE);   //Set Display Off

      OLED_WriteCMD(0xd5);   //display divide ratio/osc. freq. mode
      OLED_WriteCMD(0x80);   //

      OLED_WriteCMD(0xA8);   //multiplex ration mode:63
      OLED_WriteCMD(0x3F);

      OLED_WriteCMD(0xD3);   //Set Display Offset
      OLED_WriteCMD(0x00);

      OLED_WriteCMD(0x40);   //Set Display Start Line

      OLED_WriteCMD(0x8D);   //Set Display Offset
      OLED_WriteCMD(0x14);

      OLED_WriteCMD(0xA1);   //Segment Remap

      OLED_WriteCMD(0xC8);   //Sst COM Output Scan Direction

      OLED_WriteCMD(0xDA);   //common pads hardware: alternative
      OLED_WriteCMD(0x12);

      OLED_WriteCMD(0x81);   //contrast control
      OLED_WriteCMD(0xCF);

      OLED_WriteCMD(0xD9);            //set pre-charge period
      OLED_WriteCMD(0xF1);

      OLED_WriteCMD(0xDB);   //VCOM deselect level mode
      OLED_WriteCMD(0x40);            //set Vvcomh=0.83*Vcc

      OLED_WriteCMD(0xA4);   //Set Entire Display On/Off

      OLED_WriteCMD(0xA6);   //Set Normal Display

      FillAll(0);
      OLED_WriteCMD(0xAF);   //Set Display On

}


//******************************************
/*
void WriteAscii_6x8(u8 x, u8 y, u8 chr)      //向指定位置写一个ASCII码字符, x为横向的点0~127, y为纵向的页0~7, chr为要写的字符
{
      u8 code *p;
      u8 i;

      if(x > (128-5))      return;
      if(y >= 8)                return;
      p = (u16)chr * 6 + ASCII5x7;

      Set_Dot_Addr(x,y);
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                //Slave address,SA0=0
      if(!F0)
      {
                OLED_I2C_WriteAbyte(0x40);      //write data,C0=0, D/C=1, follow by six '0'
                if(!F0)
                {
                        for(i=0; i<6; i++)
                        {
                              OLED_I2C_WriteAbyte(*p);
                              p++;
                        }
                }
      }
      OLED_I2C_Stop();
}
*/


/************ 打印ASCII字符串 *************************/
void      printf_ascii_6x8(u8 x, u8 y, u8 *ptr)      //x为横向的点0~127, y为纵向的页0~7, *ptr为要打印的字符串指针.打印21个ASCII码(21*6+9=135个字节)耗时625us@44MHZ, 652us@36MHz, 850us@24MHz
{
    u8 c;
      u8      i;
      u8 code *p;

      if(y >= 8)      return;
      Set_Dot_Addr(x,y);
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                //Slave address,SA0=0
      if(!F0)
      {
                OLED_I2C_WriteAbyte(0x40);      //write data,C0=0, D/C=1, follow by six '0'
                if(!F0)
                {
                        for (;;)
                        {
                              c = *ptr;
                              if(c == 0)                break;      //遇到停止符0结束
                              if(c >= 0xa0)      break;      //非ASCII码结束

                              p = (u16)c * 6 + ASCII5x7;
                              for(i=0; i<6; i++)
                              {
                                        OLED_I2C_WriteAbyte(*p);
                                        p++;
                              }
                              x += 6;
                              ptr++;
                              if(x > (128-5))      break;
                        }
                }
      }
      OLED_I2C_Stop();
}

//******************************************
/*
void WriteAscii_8x16(u8 x, u8 y, u8 chr)      //向指定位置写一个ASCII码字符, x为横向的点0~127, y为纵向的页0~7, chr为要写的字符
{
      u8 code *p;
      u8 i;

      if(x > (128-8))                return;
      if(y > 6)                        return;
               if(chr == 'A')                chr = 0;
      else if(chr == 'V')                chr = 1;
      else if(chr == 'W')                chr = 2;
      else      return;

      p = (u16)chr * 16 + ASCII8x16;

      Set_Dot_Addr(x,y);
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                //Slave address,SA0=0
      if(!F0)
      {
                OLED_I2C_WriteAbyte(0x40);      //write data,C0=0, D/C=1, follow by six '0'
                if(!F0)
                {
                        for(i=0; i<8; i++)
                        {
                              OLED_I2C_WriteAbyte(*p);
                              p++;
                        }
                }
      }
      OLED_I2C_Stop();

      Set_Dot_Addr(x,y+1);
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                //Slave address,SA0=0
      if(!F0)
      {
                OLED_I2C_WriteAbyte(0x40);      //write data,C0=0, D/C=1, follow by six '0'
                if(!F0)
                {
                        for(i=0; i<8; i++)
                        {
                              OLED_I2C_WriteAbyte(*p);
                              p++;
                        }
                }
      }
      OLED_I2C_Stop();
}
*/


//******************************************
void WriteAscii_10x24(u8 x, u8 y, u8 chr)      //向指定位置写一个ASCII码字符, x为横向的点0~127, y为纵向的页0~7, chr为要写的字符
{
      u8 code *p;
      u8 i;

      if(x > (128-10))      return;
      if(y >= 6)                        return;
      p = (u16)chr * 30 + ASCII10x24;

      Set_Dot_Addr(x,y);
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                //Slave address,SA0=0
      if(!F0)
      {
                OLED_I2C_WriteAbyte(0x40);      //write data,C0=0, D/C=1, follow by six '0'
                if(!F0)
                {
                        for(i=0; i<10; i++)
                        {
                              OLED_I2C_WriteAbyte(*p);
                              p++;
                        }
                }
      }
      OLED_I2C_Stop();

      Set_Dot_Addr(x,y+1);
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                //Slave address,SA0=0
      if(!F0)
      {
                OLED_I2C_WriteAbyte(0x40);      //write data,C0=0, D/C=1, follow by six '0'
                if(!F0)
                {
                        for(i=0; i<10; i++)
                        {
                              OLED_I2C_WriteAbyte(*p);
                              p++;
                        }
                }
      }
      OLED_I2C_Stop();

      Set_Dot_Addr(x,y+2);
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                //Slave address,SA0=0
      if(!F0)
      {
                OLED_I2C_WriteAbyte(0x40);      //write data,C0=0, D/C=1, follow by six '0'
                if(!F0)
                {
                        for(i=0; i<10; i++)
                        {
                              OLED_I2C_WriteAbyte(*p);
                              p++;
                        }
                }
      }
      OLED_I2C_Stop();
}

//******************************************
void WriteDot_3x3(u8 x, u8 y)      //向指定位置写一个小数点,x为横向的点0~127, y为纵向的页0~7
{
      if(x > (128-3))      return;
      if(y >= 8)                return;

      Set_Dot_Addr(x,y);
      OLED_I2C_Start();
      OLED_I2C_WriteAbyte(SLAW);                //Slave address,SA0=0
      if(!F0)
      {
                OLED_I2C_WriteAbyte(0x40);      //write data,C0=0, D/C=1, follow by six '0'
                if(!F0)
                {
                        OLED_I2C_WriteAbyte(0x38);
                        OLED_I2C_WriteAbyte(0x38);
                        OLED_I2C_WriteAbyte(0x38);
                }
      }
      OLED_I2C_Stop();
}

//************ 打印ASCII 10x24英文字符串 *************************
void      printf_ascii_10x24(u8 x, u8 y, u8 *ptr)      //x为横向的点0~127, y为纵向的页0~7, *ptr为要打印的字符串指针, 间隔2点.
{
    u8 c;

      for (;;)
      {
      c = *ptr;
                if(c == 0)                return;      //遇到停止符0结束
                if((c >= '0') && (c <= '9'))                        //ASCII码
                {
                        WriteAscii_10x24(x,y,c-'0');
                        x += 12;
                        ptr++;
                }
                else if(c == '.')
                {
                        WriteDot_3x3(x,y+2);
                        x += 6;
                        ptr++;
                }
                else if(c == ' ')      //显示空格
                {
                        WriteAscii_10x24(x,y,11);
                        x += 12;
                        ptr++;
                }
                else if(c == '-')      //显示空格
                {
                        WriteAscii_10x24(x,y,10);
                        x += 12;
                        ptr++;
                }
      }
}





神农鼎 发表于 2023-10-17 07:50:26

//EEPROM.c 中的程序
/* ---技术交流: www.STCAIMCU.com ------------------------------------*/
/* ---资料下载: www.STCAI.com -----------------------------------------*/
/* --- Tel: 86-0513-55012928,55012929,55012966 ---------------------*/
/* If you want to use the program or the program referenced in the article,   */
   please specify in which data and procedures from STC    */

//      本程序是STC系列的内置EEPROM读写程序。
#include "config.h"
#include "eeprom.h"

//========================================================================
// 函数: void      ISP_Disable(void)
// 描述: 禁止访问ISP/IAP.
// 参数: non.
// 返回: non.
// 版本: V1.0, 2012-10-22
//========================================================================
void      DisableEEPROM(void)
{
      ISP_CONTR = 0;                        //禁止ISP/IAP操作
      IAP_TPS   = 0;
      ISP_CMD   = 0;                        //去除ISP/IAP命令
      ISP_TRIG= 0;                        //防止ISP/IAP命令误触发
      ISP_ADDRH = 0xff;                //清0地址高字节
      ISP_ADDRL = 0xff;                //清0地址低字节,指向非EEPROM区,防止误操作
}

//========================================================================
// 函数: void EEPROM_read_n(u16 EE_address,u8 *DataAddress,u16 number)
// 描述: 从指定EEPROM首地址读出n个字节放指定的缓冲.
// 参数: EE_address:读出EEPROM的首地址.
//       DataAddress: 读出数据放缓冲的首地址.
//       number:      读出的字节长度.
// 返回: non.
// 版本: V1.0, 2012-10-22
//========================================================================
void EEPROM_read_n(u16 EE_address,u8 *DataAddress,u16 number)
{
      EA = 0;                //禁止中断
      ISP_CONTR = ISP_EN;                                                //允许ISP/IAP操作
      IAP_TPS = (u8)((MAIN_Fosc+500000L) / 1000000L);      //工作频率设置
      ISP_READ();                                                                //送字节读命令,命令不需改变时,不需重新送命令
      do
      {
                ISP_ADDRH = EE_address / 256;                //送地址高字节(地址需要改变时才需重新送地址)
                ISP_ADDRL = EE_address % 256;                //送地址低字节
                ISP_TRIG();                                                      //先送5AH,再送A5H到ISP/IAP触发寄存器,每次都需要如此
                                                                                        //送完A5H后,ISP/IAP命令立即被触发启动
                                                                                        //CPU等待IAP完成后,才会继续执行程序。
                _nop_();
                _nop_();
                _nop_();
                *DataAddress = ISP_DATA;                        //读出的数据送往
                EE_address++;
                DataAddress++;
      }while(--number);

      DisableEEPROM();
      EA = 1;                //重新允许中断
}

/******************** 扇区擦除函数 *****************/
//========================================================================
// 函数: void EEPROM_SectorErase(u16 EE_address)
// 描述: 把指定地址的EEPROM扇区擦除.
// 参数: EE_address:要擦除的扇区EEPROM的地址.
// 返回: non.
// 版本: V1.0, 2013-5-10
//========================================================================
void EEPROM_SectorErase(u16 EE_address)
{
      EA = 0;                //禁止中断
                                                                                        //只有扇区擦除,没有字节擦除,512字节/扇区。
                                                                                        //扇区中任意一个字节地址都是扇区地址。
      ISP_ADDRH = EE_address / 256;                        //送扇区地址高字节(地址需要改变时才需重新送地址)
      ISP_ADDRL = EE_address % 256;                        //送扇区地址低字节
      ISP_CONTR = ISP_EN;                                                //允许ISP/IAP操作
      IAP_TPS = (u8)(MAIN_Fosc / 1000000L);      //工作频率设置
      ISP_ERASE();                                                      //送扇区擦除命令,命令不需改变时,不需重新送命令
      ISP_TRIG();
      _nop_();
      _nop_();
      _nop_();
      DisableEEPROM();
      EA = 1;                //重新允许中断
}

//========================================================================
// 函数: void EEPROM_write_n(u16 EE_address,u8 *DataAddress,u16 number)
// 描述: 把缓冲的n个字节写入指定首地址的EEPROM.
// 参数: EE_address:写入EEPROM的首地址.
//       DataAddress: 写入源数据的缓冲的首地址.
//       number:      写入的字节长度.
// 返回: non.
// 版本: V1.0, 2012-10-22
//========================================================================
void EEPROM_write_n(u16 EE_address,u8 *DataAddress,u16 number)
{
      EA = 0;                //禁止中断

      ISP_CONTR = ISP_EN;                                                //允许ISP/IAP操作
      IAP_TPS = (u8)(MAIN_Fosc / 1000000L);      //工作频率设置
      ISP_WRITE();                                                      //送字节写命令,命令不需改变时,不需重新送命令
      do
      {
                ISP_ADDRH = EE_address / 256;                //送地址高字节(地址需要改变时才需重新送地址)
                ISP_ADDRL = EE_address % 256;                //送地址低字节
                ISP_DATA= *DataAddress;                        //送数据到ISP_DATA,只有数据改变时才需重新送
                ISP_TRIG();
                _nop_();
                _nop_();
                _nop_();
                EE_address++;
                DataAddress++;
      }while(--number);

      DisableEEPROM();
      EA = 1;                //重新允许中断
}



NTC 发表于 2023-10-17 08:41:04

赞,

梁工 发表于 2023-10-17 10:42:46

制作提示:
1、档位放电电阻切换使用磁簧继电器,不用MOSFET,D、S极电容大,影响测量,放电管用MOSFET实在是没有办法的事。磁簧继电器隔离性能好,寿命长(近1亿次),无噪音,体积小,驱动电流小(IO直接驱动),触点电阻几十毫欧,触点电容可忽略。
2、档位电阻使用5色环金属膜电阻,1%误差。
3、使用外部24M晶振,读数稳定。
4、电容表供电使用5V,78M05稳压的,输入至少7.2V,一般7.2~15V。磁簧继电器需要5V驱动,5V也能使模拟比较器工作的动态电压更大(充电终止电压3.16V)。
5、关于精度,理论上只要给定参考电压 U*(1-1/e)=U*0.63212 精确,即R4、R5的分压比精确,则测量就会很准确,不需要另外标定。当然,有标准电容标定一下更好。
   测量1nF以下的小电容,特别是几十PF的小电容误差可能较大,达到5%,因为用1M电阻充放电,放电管的D、S电容跟电压敏感,还有电路的分布电容影响,往往不是读数归0就能消除的。
   有些种类的电容公分电压有关系,电压不同电容量不同,可以认为增加偏压再测量。
   本电容表测量时先将电容放电至0V,再充电到3.16V,所以测出的电容量就是电容在1~3V电压时的电容量,只要工作中电容的电压差不多,则测量的电容就是其工作时的容量,正是我所需要的。
   如果用数字电桥测量对比,会发现有的电容容量偏差比较大,那是因为数字电桥的测量方法不同,激励信号比较小(一般是交流200mV或200mV),比如测量MOSFET的DS电容会比我的表大好多,因为DS电容在0电压比在几V时大了几倍。同一个电容、同一个数字电桥在不同的测试频率和电压,电容都会不同,甚至相差比较大,因为电容还是频率的函数。

测量电容有好多方法:
1、数字电桥,可以测量好多参数,这个比较好,但是电路复杂,程序更复杂,正在设计电路。
2、容抗法,大部分数字万用表附加的测电容功能都是,但是只能测到20uF更大的就是200uF,因为大电容的容抗太小了不好处理,万用表用的400Hz正弦波测试的,测小电容可以用。
3、谐振法,网上有N多电路例子,就是用外接模拟比较器+LC振荡测频率再计算L或C,这个还可以的,但是对制作要求高点,参考电感和参考电容要有好的性能。谐振的热蠕动比较讨厌。
4、充放电(或方波振荡)法,就是本表用的,简单、稳定,可以测量从pF到几十mF,对于大部分工程的使用都合适了。
5、线性三角波激励测量,这个我实际打板测试过,可以,但是电路稍复杂,需要标定,可以测量10PF~40mF,还可以测电感(1uH~200mH,大电感不好做)。

神农鼎 发表于 2023-10-24 12:53:01

【USB 原理及实战,16课时】,视频教学已完美完成 ,大学标准课程 !
【10月/9号,10月/11号】USB基本原理教学视频, 已上传
【10月/16号的 USB-HID 通信 实战】教学视频超级完美, 已上传
             是对着协议和代码一行一行的讲解,认真听的都说会了
【10月/18号下午的 USB-CDC虚拟串口 实战】教学视频, 已上传
             USB-CDC虚拟串口 / 就是最简单最强大的串口
             是对着协议和代码一行一行的讲解,认真听的都说会了
请帮忙转发给可能需要:从0开始了解 USB 的 同学/同事/老师/研发人员
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=4526&extra=&page=1
=========================================
【CAN 原理及实战,8课时】,教学视频,制作中,后续直接看视频回放

rz12345 发表于 2023-11-6 08:48:59

期待梁工的“数字电桥”{:lol:}

梁工 发表于 2023-11-6 11:05:41

rz12345 发表于 2023-11-6 08:48
期待梁工的“数字电桥”

电路设计好了,正在画PCB。
页: [1] 2 3 4 5 6
查看完整版本: 51 开源 电容表: 8H8K64U比较器+OLED12864-I2C 实现