- 打卡等级:偶尔看看III
- 打卡总天数:54
- 最近打卡:2025-05-01 09:07:55
管理员
- 积分
- 15609
|
发表于 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);
}
|
|