找回密码
 立即注册
查看: 2177|回复: 43

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

[复制链接]

该用户从未签到

45

主题

2920

回帖

6564

积分

超级版主

积分
6564
发表于 2023-10-17 00:31:54 | 显示全部楼层 |阅读模式
本帖最后由 梁工 于 2023-10-17 21:31 编辑

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

本电容表测试几十pF、几百pF的电容误差比较大,可能到5%,超过1nF的误差一般2%以下。
截图202310170018403766.jpg

截图202310170019141226.jpg

截图202310170021538818.jpg
分别为测量318pF、100pF、330nF、22nF、330nF
截图202310170023422333.jpg
截图202310170024237263.jpg
截图202310170025463932.jpg
截图202310170026499887.jpg
截图202310170027306694.jpg

分别为测量10uF、100uF。

截图202310170028284212.jpg
截图202310170029066433.jpg

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

截图202310170029542635.jpg
截图202310170030227584.jpg
截图202310170030597149.jpg

电路原理图PDF、PCB、程序C源码:
69-OLED128x64-I2C-STC8H8K64U-电容表-充放电版.rar (245.61 KB, 下载次数: 179)





回复 送花

使用道具 举报

该用户从未签到

550

主题

9234

回帖

1万

积分

管理员

积分
13942
发表于 2023-10-17 07:44:38 | 显示全部楼层
下载开源程序包解压缩后如下:
截图202310170741182972.jpg

截图202310170743209840.jpg

CAN 原理实战8课时
线上视频授课:
10月/30号11月/1号:14:00 ~ 17:00;  
腾讯会议号885-5858-2739; (安装腾讯会议软件后,输入会议号即可)

参会学习立即【免费+包邮USB2组独立的CAN核心功能实验板
                                               模拟的CAN收发器您自己补上

https://www.stcaimcu.com/forum.php?mod=viewthread&tid=4526&extra=&page=1
请帮忙转发给可能需要:从0开始了解CAN同学/同事/老师/研发人员
回复 支持 反对 送花

使用道具 举报

该用户从未签到

550

主题

9234

回帖

1万

积分

管理员

积分
13942
发表于 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[5];
u16        DischargeTimeCal;
u16        DischargeTime;
u8        CapCntH;
u8        index;
u16        LimitTime;
u16        UpdateTime;
u16        ChargeCnt;
u8        DotState;        //小数点位置: 0:不显示小数点,1:第一位后,2:第二位后,3:第三位后

u8 xdata tmp[24];


/********************* 函数声明 *******************************/
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[5]={ 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[0] = ((u32 *)&tmp)[0];        //   2uF
                C_zero[1] = ((u32 *)&tmp)[1];        //  20uF
                C_zero[2] = ((u32 *)&tmp)[2];        // 200uF
                C_zero[3] = ((u32 *)&tmp)[3];        //   2mF
                C_zero[4] = ((u32 *)&tmp)[4];        //  20mF
                if(C_zero[0] >= 1000)        C_zero[0] = 200;        //0点异常
                if(C_zero[1] >= 1000)        C_zero[0] = 20;                //0点异常
                if(C_zero[2] >= 1000)        C_zero[0] = 2;                //0点异常
                if(C_zero[3] >= 1000)        C_zero[0] = 1;                //0点异常
                if(C_zero[4] >= 1000)        C_zero[0] = 1;                //0点异常
        }
        else   //EEPROM记录不正确,则默认0点
        {
                C_zero[0] = 200;        //   2uF        放电管MOSFET截止时DS极有超过200pF电容,所以会有一个200多的读数。
                C_zero[1] = 20;                //  20uF
                C_zero[2] = 2;                // 200uF
                C_zero[3] = 1;                //   2mF
                C_zero[4] = 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[level] = j;        //电容归0

                                                ((u32 *)&tmp)[0] = C_zero[0];
                                                ((u32 *)&tmp)[1] = C_zero[1];
                                                ((u32 *)&tmp)[2] = C_zero[2];
                                                ((u32 *)&tmp)[3] = C_zero[3];
                                                ((u32 *)&tmp)[4] = C_zero[4];
                                                crc = MODBUS_CRC16(tmp,  20);
                                                tmp[20] = (u8)(crc % 256);
                                                tmp[21] = (u8)(crc / 256);
                                                EEPROM_SectorErase(0);
                                                EEPROM_write_n(0, tmp, 22);                // 保存EEPROM
                                        }

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

                                                 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[6] = 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[1] = j / 1000 + '0';
        tmp[2] = '.';
        tmp[3] = (j % 1000) / 100 + '0';
        tmp[4] = (j % 100) / 10 + '0';
        tmp[5] =  j % 10 + '0';
}
//将数字显示成 xx.xx
void        DEC_xx_xx(u16 j)
{
        if(DotState != 2)                //当前小数点不是第2位,则先清除显示
        {
                ClearCapacitance();        //清除电容显示值
                DotState = 2;
        }
        tmp[1] = j / 1000 + '0';
        tmp[2] = (j % 1000) / 100 + '0';
        tmp[3] = '.';
        tmp[4] = (j % 100) / 10 + '0';
        tmp[5] =  j % 10 + '0';
        if(tmp[1] == '0')        tmp[1] = ' ';
}
//将数字显示成 xxx.x
void        DEC_xxx_x(u16 j)
{
        if(DotState != 3)                //当前小数点不是第3位,则先清除显示
        {
                ClearCapacitance();        //清除电容显示值
                DotState = 3;
        }
        tmp[1] = j / 1000 + '0';
        tmp[2] = (j % 1000) / 100 + '0';
        tmp[3] = (j % 100) / 10 + '0';
        tmp[4] = '.';
        tmp[5] =  j % 10 + '0';
        if(tmp[1] == '0')
        {
                tmp[1] = ' ';
                if(tmp[2] == '0')        tmp[2] = ' ';
        }
}

/************************ 比较器配置 ****************************/
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[3:0]所选择的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[3:0]所选择的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);
}




回复 支持 反对 送花

使用道具 举报

该用户从未签到

550

主题

9234

回帖

1万

积分

管理员

积分
13942
发表于 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++;
                }
        }
}





回复 支持 反对 送花

使用道具 举报

该用户从未签到

550

主题

9234

回帖

1万

积分

管理员

积分
13942
发表于 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;                //重新允许中断
}



回复 支持 反对 送花

使用道具 举报

  • TA的每日心情
    奋斗
    昨天 08:38
  • 签到天数: 144 天

    [LV.7]常住居民III

    8

    主题

    77

    回帖

    996

    积分

    高级会员

    积分
    996
    发表于 2023-10-17 08:41:04 | 显示全部楼层
    赞,
    回复 送花

    使用道具 举报

    该用户从未签到

    45

    主题

    2920

    回帖

    6564

    积分

    超级版主

    积分
    6564
     楼主| 发表于 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,大电感不好做)。
    回复 支持 2 反对 0 送花

    使用道具 举报

    该用户从未签到

    550

    主题

    9234

    回帖

    1万

    积分

    管理员

    积分
    13942
    发表于 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课时】,教学视频,制作中,后续直接看视频回放

    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    昨天 09:39
  • 签到天数: 138 天

    [LV.7]常住居民III

    0

    主题

    15

    回帖

    946

    积分

    高级会员

    积分
    946
    发表于 2023-11-6 08:48:59 | 显示全部楼层
    期待梁工的“数字电桥”

    点评

    电路设计好了,正在画PCB。  详情 回复 发表于 2023-11-6 11:05
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    45

    主题

    2920

    回帖

    6564

    积分

    超级版主

    积分
    6564
     楼主| 发表于 2023-11-6 11:05:41 | 显示全部楼层
    rz12345 发表于 2023-11-6 08:48
    期待梁工的“数字电桥”

    电路设计好了,正在画PCB。
    回复 支持 反对 送花

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

    GMT+8, 2024-4-29 02:59 , Processed in 0.082341 second(s), 69 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

    快速回复 返回顶部 返回列表