// Power source measure unit
#include <Wire.h>
#include <EEPROM.h>
// pins discr.
#define OVER_CURRENT 2
#define OFF_BTN 3
#define ON_BTN 4
#define B1_BTN 5
#define B2_BTN 6
#define B3_BTN 7
#define OUT_REL 8
#define FAN 9
#define STB 10
#define DAT_TX 11
#define DAT_RX 12
#define CLK 13
#define SEN_REL 14
#define LINE_MODE 16
#define TEST 17
// pins analog
#define U_DROP 1
#define T_RAD 7
#define T_TRANS 6
// display constants
#define displayMode 0b00000111 // 11 digits, 11 segments
#define dataSettings 0x40 // Data write & read mode settings
#define incrementAddress 0x00 // Increment address after data has been written mode
#define fixedAddress 0x04 // Fixed address mode
#define LEDregister 0x00000001 // LED data send
#define addressSettings 0xC0 // Address settings command
#define startAddress 0x00 // start address of ram memory
#define endAddress 0x0F // end address of ram memory
#define displayControl 0x8F // Display settings ON/OFF
// adc constants
#define ADR_ASD1115 0b01001000 //address of ads1115
// cooperating with ADC
byte writeBuf[3];
byte readBuf[3];
// SPU's ADC values
int TempTransBuf[10];
int TempTransSum;
int TempTransADC;
int TempRadBuf[10];
int TempRadSum;
int TempRadADC;
int UdropADC;
byte i,j;
// Fan speed values
byte speed1, speed2, speed3;
// output booleans
bool output, protect, extSense, line_md;
// button statuses
bool btON_tap, btON_hold;
byte btON_timer;
bool bt1_tap, bt1_hold;
byte bt1_timer;
bool bt2_tap, bt2_hold;
byte bt2_timer;
bool bt3_tap, bt3_hold;
byte bt3_timer;
// 1 sec counter
byte tick1S;
bool int1S;
// 3.6 sec counter
byte tick36S;
bool int36S;
// blinker
byte blinker_timer;
bool blinker;
// ADC cooperate values
byte ADCchannel;
int UsenADC;
int UsetADC;
int IsenADC;
int IsetADC;
int ResultADC;
bool EnblADC;
byte ADCtimer;
long OutVoltage, OutCurrent;
// current display index
byte screen;
// errors
bool ErrOverheat, ErrReg, OverTask;
byte UdropErrTimer, UsetErrTimer, OverTaskTimer;
// setup indicate
bool setdispU, _setdispU, setdispI, _setdispI;
byte setdisptimer;
int _UsetADC, _IsetADC;
// counters
unsigned long CurrentCounter, PowerCounter;
// for EEPROM store values
byte storeTimer;
bool storeEnbl;
// display data
byte symbolReg;
const byte symbols[44] =
{
//xxx0xxx0 000000x0
//cbajfge- -----idh
0b11101010, 0b00000010, // 0
0b11000000, 0b00000000, // 1
0b01100110, 0b00000010, // 2
0b11100100, 0b00000010, // 3
0b11001100, 0b00000000, // 4
0b10101100, 0b00000010, // 5
0b10101110, 0b00000010, // 6
0b11101000, 0b00000000, // 7
0b11101110, 0b00000010, // 8
0b11101100, 0b00000010, // 9
0b10000010, 0b00000010, // 10, U
0b11101110, 0b00000000, // 11, A
0b00101110, 0b00000010, // 12, E
0b00000110, 0b00000000, // 13, r
0b00000000, 0b00000000, // 14, blank
0b10001010, 0b00000010, // 15, W 1/2
0b11000010, 0b00000010, // 16, W 2/2
0b00000100, 0b00000000, // 17, -
0b10001110, 0b00000000, // 18, h
0b00100000, 0b00000000, // 19, - on upper
0b01101100, 0b00000000, // 20, degree
0b00001110, 0b00000010 // 21, t
};
long PrevTime;
//-------------------------------------------------------------------------------
void setup() {
pinMode(OVER_CURRENT, INPUT_PULLUP);
pinMode(OFF_BTN, INPUT_PULLUP);
pinMode(ON_BTN, INPUT_PULLUP);
pinMode(B1_BTN, INPUT_PULLUP);
pinMode(B2_BTN, INPUT_PULLUP);
pinMode(B3_BTN, INPUT_PULLUP);
pinMode(OUT_REL, OUTPUT);
pinMode(FAN, OUTPUT);
pinMode(STB, OUTPUT);
pinMode(DAT_TX, OUTPUT);
pinMode(DAT_RX, INPUT);
pinMode(CLK, OUTPUT);
pinMode(SEN_REL, OUTPUT);
pinMode(LINE_MODE, OUTPUT);
pinMode(TEST, OUTPUT);
output = 0;
protect = 0;
extSense = 0;
storeTimer = 0;
storeEnbl = 0;
line_md = 0;
btON_tap = 0; btON_hold = 0;
bt1_tap = 0; bt1_hold = 0;
bt2_tap = 0; bt2_hold = 0;
bt3_tap = 0; bt3_hold = 0;
symbolReg = 0;
setdispU = 0; _setdispU = 0; setdispI = 0; _setdispI = 0;
ADCchannel = 0;
screen = 0;
TCCR2A = 0; //TMR2 normal mode
TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20; // prescale 1024, overflow each 1/(16000000/1024/256=61Hz) = 16.4mS
TIMSK2 = 1<<TOIE2; // enable overflow interrupt
TCNT2 = 0; // reset counter
EICRA = 1<<ISC11 | 1<<ISC01; // Falling edge generate interrupt
EIMSK = 1<<INT1 | 1<<INT0; // Enable interrupt
CurrentCounter = EEPROM_long_read(0);
PowerCounter = EEPROM_long_read(4);
Serial.begin(9600);
Wire.begin(); // begin I2C
// initialize vfd display
initDisplay();
}
//--------------------------------------------------------------------------------
void loop() {
if (int1S)
{
// Over task timer
if ( (OverTask)&&(OverTaskTimer<255) ) OverTaskTimer++; else OverTaskTimer = 0;
// Udrop error timer
int1S = 0;
if (UdropErrTimer<255) UdropErrTimer ++;
// Temperature measure filtering
j++;
if (j>9) j = 0;
TempTransBuf[j] = analogRead(T_TRANS);
TempRadBuf[j] = analogRead(T_RAD);
TempTransSum = TempTransBuf[0];
TempRadSum = TempRadBuf[0];
for (i = 1; i <= 9; i++)
{
TempTransSum = TempTransSum + TempTransBuf[i];
TempRadSum = TempRadSum + TempRadBuf[i];
}
TempTransADC = TempTransSum / 10;
TempRadADC = TempRadSum / 10;
// Linear regulator drop voltage measure
UdropADC = analogRead(U_DROP);
if (0) // serial U drop and temperature display
{
Serial.print("U drop: ");
Serial.print(UdropADC);
Serial.print("; Trans_temp: ");
Serial.print(((TempTransADC - 105)*10) / 2);
Serial.print("; Rad_temp: ");
Serial.println(((TempRadADC - 105)*10) / 2);
}
if (0) // serial Voltage and Current display
{
Serial.print("Usen=");
//Serial.print(GetOutVoltage(UsenADC));
Serial.print(UsenADC);
Serial.print("; Uset=");
//Serial.print(GetSetVoltage(UsetADC));
Serial.print(UsetADC);
Serial.print("; Isen=");
//Serial.print(GetOutCurrent(IsenADC));
Serial.print(IsenADC);
Serial.print("; Iset=");
//Serial.println(GetSetCurrent(IsetADC));
Serial.println(IsetADC);
}
if (0) // serial capacity display
{
Serial.print("mA*h= ");
Serial.print(CurrentCounter/1000);
Serial.print(" mW*h= ");
Serial.println(PowerCounter/1000);
}
}
if (int36S)
{
int36S = 0;
if (output)
{
CurrentCounter = CurrentCounter + OutCurrent;
if (CurrentCounter > 999999999) CurrentCounter = 0;
PowerCounter = PowerCounter + ((OutCurrent * OutVoltage)/1000);
if (PowerCounter > 999999999) PowerCounter = 0;
}
//Serial.println(millis()-PrevTime); PrevTime = millis();
}
if (EnblADC) {EnblADC = 0; GetADC();}
if (storeEnbl) {storeEnbl=0; SaveData();}
//buttons, output, LED
//if ( (!(digitalRead(ON_BTN)))&&(!(ErrOverheat || ErrReg)) ) {output = 1;}
if ( ErrOverheat || ErrReg || (OverTaskTimer>10) ) {digitalWrite(OUT_REL,LOW); output = 0;}
if (output) digitalWrite(OUT_REL,HIGH); else digitalWrite(OUT_REL,LOW);
if (output) {if (!(digitalRead(OVER_CURRENT))) updateLED(0b11111101); else updateLED(0b11111011);} else updateLED(0b11111110);
if (line_md) {if (OverTask) updateSymbol(2,blinker); else updateSymbol(2,1);} else updateSymbol(2,0);
if (btON_tap)
{
btON_tap = 0;
if (!(ErrOverheat || ErrReg || OverTask)) {output = 1;}
}
if (btON_hold)
{
btON_hold = 0;
if (!(output))
{if (line_md) line_md = 0; else line_md = 1;}
if (line_md) {digitalWrite(LINE_MODE,HIGH);} else {digitalWrite(LINE_MODE,LOW);}
}
if (bt1_tap)
{
bt1_tap = 0;
switch (screen)
{
case 1: screen = 2; break;
case 2: screen = 1; break;
default: screen = 1; break;
}
}
if (bt1_hold)
{
bt1_hold = 0;
if (protect) protect = 0; else protect = 1;
updateSymbol(0,protect);
}
if (bt2_tap)
{
bt2_tap = 0;
switch (screen)
{
case 0: screen = 3; break;
case 3: screen = 0; break;
default: screen = 0; break;
}
}
if (bt2_hold)
{
bt2_hold = 0;
if (extSense) {extSense = 0; digitalWrite(SEN_REL,LOW);}
else {extSense = 1; digitalWrite(SEN_REL,HIGH);}
}
if (bt3_tap)
{
bt3_tap = 0;
switch (screen)
{
case 4: screen = 5; break;
case 5: screen = 6; break;
case 6: screen = 4; break;
default: screen = 6; break;
}
}
if (bt3_hold)
{
bt3_hold = 0;
if ( (screen == 5)||(screen == 6) ) {CurrentCounter = 0; PowerCounter = 0;}
else screen = 7;
}
//Fan control
if (TempTransADC < 215) speed1 = 0;
else if (TempTransADC > 245) speed1 = 255;
else speed1 = map(TempTransADC,215,245,40,255); // Ttrans (55...70)
if (TempRadADC < 185) speed2 = 0;
else if (TempRadADC > 245) speed2 = 255;
else speed2 = map(TempRadADC,185,245,40,255); // Trad (40...70)
if (IsenADC < 15528) speed3 = 0;
else if (IsenADC > 21739) speed3 = 255;
else speed3 = map(IsenADC,15528,21739,40,255); // I load (2500...3500)
analogWrite(FAN,GetMax(speed1, speed2, speed3));
//Setup display
if ( abs(UsetADC - _UsetADC) > 100)
{
_UsetADC = UsetADC;
setdisptimer = 0;
setdispU = 1;
//Serial.print("Set U "); Serial.println(UsetADC);
}
if (setdispU != _setdispU) {_setdispU = setdispU; updateSymbol(1,((setdispU | setdispI)&&(screen == 0)));}
if ( abs(IsetADC - _IsetADC) > 100)
{
_IsetADC = IsetADC;
setdisptimer = 0;
setdispI = 1;
//Serial.print("Set I "); Serial.println(IsetADC);
}
if (setdispI != _setdispI) {_setdispI = setdispI; updateSymbol(1,((setdispU | setdispI)&&(screen == 0)));}
//Errors
if ( (TempTransADC>185) || (TempRadADC>185) ) ErrOverheat = 1; // Trans>90, Rad>90
if ( (TempTransADC<245) && (TempRadADC<245) ) ErrOverheat = 0; // Trans<70, Rad<70
if ( (UdropADC > 200) && ( (UdropADC < 360)||(line_md) ) ) UdropErrTimer = 0; // Udrop>1 and (Udrop<1.8 OR line mode); 1.45 - optimal
if ( (UdropErrTimer > 5)||(UsetErrTimer > 12) ) ErrReg = 1; else ErrReg = 0;
if (!(line_md)) OverTask = 0;
else {OverTask = ( ((37000-UsetADC)*IsetADC) > 187500000 );} // Udrop=(37000-U); U=Uadc; I=Iadc*0.16; Udrop*I > 30W
// display
if ( ErrOverheat || ErrReg ) DisplayErr();
else
{
if (screen == 0) Display0(); // U + I
if (screen == 1) Display1(); // U
if (screen == 2) Display2(); // I
if (screen == 3) Display3(); // W
if (screen == 4) Display4(); // R
if (screen == 5) Display5(); // W*h
if (screen == 6) Display6(); // A*h
if (screen == 7) Display7(); // T'C
}
}
//-------------------------------------------------------------------------------
void DisplayErr()
{
putSymbol(12,1,0,0,0);
putSymbol(13,2,0,0,0);
putSymbol(13,3,0,0,0);
for (i=4; i<=10; i++) putSymbol(14,i,0,0,0);
}
void Display0()
{
long value;
if (setdispU) value = GetSetVoltage(UsetADC);
else {if (output) value = OutVoltage; else value = 0;}
putSymbol(((value/10000)%10),1,setdispU,0,0);
putSymbol(((value/1000)%10),2,setdispU,1,0);
putSymbol(((value/100)%10),3,setdispU,0,0);
putSymbol(10,4,setdispU,1,1);
putSymbol(14,5,setdispU,0,0);
if (setdispI) value = GetSetCurrent(IsetADC);
else value = OutCurrent;
if ( (OverTask)&&(blinker) ) for (i=6; i<=10; i++) putSymbol(14,i,0,0,0);
else
{
putSymbol(((value/1000)%10),6,setdispI,1,0);
putSymbol(((value/100)%10),7,setdispI,0,0);
putSymbol(((value/10)%10),8,setdispI,0,0);
putSymbol(11,9,setdispI,0,0);
putSymbol(14,10,setdispI,0,0);
}
}
void Display1()
{
long value;
if (output) value = OutVoltage; else value = 0;
putSymbol(((value/10000)%10),1,0,0,0);
putSymbol(((value/1000)%10),2,0,1,0);
putSymbol(((value/100)%10),3,0,0,0);
putSymbol(((value/10)%10),4,0,0,0);
putSymbol(((value)%10),5,0,0,0);
putSymbol(10,6,0,1,1);
value = GetSetVoltage(UsetADC);
putSymbol(((value/10000)%10),7,0,0,0);
putSymbol(((value/1000)%10),8,0,1,0);
putSymbol(((value/100)%10),9,0,0,0);
putSymbol(((value/10)%10),10,0,0,0);
}
void Display2()
{
int value;
value = OutCurrent;
putSymbol(((value/1000)%10),1,0,1,0);
putSymbol(((value/100)%10),2,0,0,0);
putSymbol(((value/10)%10),3,0,0,0);
putSymbol(((value)%10),4,0,0,0);
putSymbol(11,5,0,1,1);
value = GetSetCurrent(IsetADC);
putSymbol(((value/1000)%10),6,0,1,0);
putSymbol(((value/100)%10),7,0,0,0);
putSymbol(((value/10)%10),8,0,0,0);
putSymbol(((value)%10),9,0,0,0);
putSymbol(14,10,0,0,0);
}
void Display3()
{
int value;
value = (OutCurrent * OutVoltage)/100000;
putSymbol(14,1,0,0,0);
putSymbol(14,2,0,0,0);
putSymbol(((value/1000)%10),3,0,0,0);
putSymbol(((value/100)%10),4,0,0,0);
putSymbol(((value/10)%10),5,0,1,0);
putSymbol(((value)%10),6,0,0,0);
putSymbol(14,7,0,0,0);
putSymbol(14,8,0,0,0);
putSymbol(15,9,0,0,0);
putSymbol(16,10,0,0,0);
}
void Display4()
{
long value;
putSymbol(14,1,0,0,0);
if (OutCurrent > 0)
{
value = (OutVoltage * 10)/OutCurrent;
if ( value > 99999 ) value = 99999;
putSymbol(((value/10000)%10),2,0,0,0);
putSymbol(((value/1000)%10),3,0,0,0);
putSymbol(((value/100)%10),4,0,0,0);
putSymbol(((value/10)%10),5,0,1,0);
putSymbol(((value)%10),6,0,0,0);
}
else
{
putSymbol(17,2,0,0,0);
putSymbol(17,3,0,0,0);
putSymbol(17,4,0,0,0);
putSymbol(17,5,0,1,0);
putSymbol(17,6,0,0,0);
}
putSymbol(14,7,0,0,0);
putSymbol(5,8,0,0,0);
putSymbol(19,9,0,0,0);
putSymbol(2,10,0,0,0);
}
void Display5()
{
long value;
value = (PowerCounter)/1000;
putSymbol(((value/100000)%10),1,0,0,0);
putSymbol(((value/10000)%10),2,0,0,0);
putSymbol(((value/1000)%10),3,0,1,0);
putSymbol(((value/100)%10),4,0,0,0);
putSymbol(((value/10)%10),5,0,0,0);
putSymbol(((value)%10),6,0,0,0);
putSymbol(14,7,0,0,0);
putSymbol(15,8,0,0,0);
putSymbol(16,9,0,0,0);
putSymbol(18,10,1,0,0);
}
void Display6()
{
long value;
value = (CurrentCounter)/1000;
putSymbol(((value/100000)%10),1,0,0,0);
putSymbol(((value/10000)%10),2,0,0,0);
putSymbol(((value/1000)%10),3,0,1,0);
putSymbol(((value/100)%10),4,0,0,0);
putSymbol(((value/10)%10),5,0,0,0);
putSymbol(((value)%10),6,0,0,0);
putSymbol(14,7,0,0,0);
putSymbol(14,8,0,0,0);
putSymbol(11,9,0,0,0);
putSymbol(18,10,1,0,0);
}
void Display7()
{
int value;
value = (((TempTransADC - 105)*10) / 2);
putSymbol(21,1,0,0,0);
putSymbol(((value/100)%10),2,0,0,0);
putSymbol(((value/10)%10),3,0,1,0);
putSymbol(((value)%10),4,0,0,0);
putSymbol(20,5,0,0,0);
value = (((TempRadADC - 105)*10) / 2);
putSymbol(13,6,1,0,0);
putSymbol(((value/100)%10),7,0,0,0);
putSymbol(((value/10)%10),8,0,1,0);
putSymbol(((value)%10),9,0,0,0);
putSymbol(20,10,0,0,0);
}
//-------------------------------------------------------------------------------
// read
long EEPROM_long_read(int addr) {
byte raw[4];
for(byte i = 0; i < 4; i++) raw[i] = EEPROM.read(addr+i);
long &num = (long&)raw;
return num;
}
// store
void EEPROM_long_write(int addr, long num) {
byte raw[4];
(long&)raw = num;
for(byte i = 0; i < 4; i++) EEPROM.write(addr+i, raw[i]);
}
// save
void SaveData()
{
updateLED(0b11111000);
EEPROM_long_write(0,CurrentCounter);
EEPROM_long_write(4,PowerCounter);
}
//-------------------------------------------------------------------------------
ISR(TIMER2_OVF_vect) //timer 2 interrupt
{
// ADC read run
ADCtimer++;
if (ADCtimer>6) {EnblADC = 1; ADCtimer = 0;}
// 1 sec. intervals
tick1S++;
if (tick1S>60) {tick1S = 0; int1S = 1;}
// 3,6 sec. intervals
tick36S++;
if (tick36S>219) {tick36S = 0; int36S = 1;}
// blinker
blinker_timer++;
if (blinker_timer>18) {blinker_timer = 0; if (blinker) blinker=0; else blinker=1;} // >300mS
// store interval, AC off
if (!(digitalRead(OFF_BTN))) storeTimer++; else storeTimer = 0;
if (storeTimer == 122) {storeEnbl = 1;}// 2000mS
// button ON manage
if (digitalRead(ON_BTN)) if ( (btON_timer>6) && (btON_timer<122) ) {btON_tap = 1; btON_hold = 0;}// >100mS, <2000mS
if (btON_timer==122) {btON_tap = 0; btON_hold = 1;}// =2000mS
if (!(digitalRead(ON_BTN))) {if (btON_timer<255) btON_timer++;} else btON_timer = 0;
// button 1 manage
if (digitalRead(B1_BTN)) if ( (bt1_timer>6) && (bt1_timer<122) ) {bt1_tap = 1; bt1_hold = 0;}// >100mS, <2000mS
if (bt1_timer==122) {bt1_tap = 0; bt1_hold = 1;}// =2000mS
if (!(digitalRead(B1_BTN))) {if (bt1_timer<255) bt1_timer++;} else bt1_timer = 0;
// button 2 manage
if (digitalRead(B2_BTN)) if ( (bt2_timer>6) && (bt2_timer<122) ) {bt2_tap = 1; bt2_hold = 0;}// >100mS, <2000mS
if (bt2_timer==122) {bt2_tap = 0; bt2_hold = 1;}// =2000mS
if (!(digitalRead(B2_BTN))) {if (bt2_timer<255) bt2_timer++;} else bt2_timer = 0;
// button 3 manage
if (digitalRead(B3_BTN)) if ( (bt3_timer>6) && (bt3_timer<122) ) {bt3_tap = 1; bt3_hold = 0;}// >100mS, <2000mS
if (bt3_timer==122) {bt3_tap = 0; bt3_hold = 1;}// =2000mS
if (!(digitalRead(B3_BTN))) {if (bt3_timer<255) bt3_timer++;} else bt3_timer = 0;
//setup value display
if (setdisptimer<255) setdisptimer++;
if (setdisptimer==183) {setdispU=0; setdispI=0;} // =3000mS
}
ISR(INT1_vect) //INT1 interrupt, OFF button, AC off
{
digitalWrite(OUT_REL,LOW);
output = 0;
}
ISR(INT0_vect) //INT0 interrupt, overcurrent input
{
if ( (digitalRead(ON_BTN)) && protect ) {digitalWrite(OUT_REL,LOW); output = 0;}
}
byte GetMax(byte val1, byte val2, byte val3)
{
byte result = val1;
if (val2 > result) result = val2;
if (val3 > result) result = val3;
return result;
}
void GetADC()//
{
readBuf[0] = 0; // ask for conversion register
Wire.beginTransmission(ADR_ASD1115); // ask for ADC
Wire.write(readBuf[0]); // pointer to register
Wire.endTransmission();
Wire.requestFrom(ADR_ASD1115, 2); // repeat start
readBuf[1] = Wire.read(); //
readBuf[2] = Wire.read(); //
Wire.endTransmission();
ResultADC = readBuf[1] << 8 | readBuf[2]; // get result
switch(ADCchannel)
{
case 0: UsenADC = ResultADC; OutVoltage = GetOutVoltage(UsenADC); break;
case 1: UsetADC = ResultADC; break;
case 2: IsenADC = ResultADC; OutCurrent = GetOutCurrent(IsenADC); break;
case 3: IsetADC = ResultADC; break;
}
if ( ((UsenADC-UsetADC)>1000)||((IsenADC-IsetADC)>1000) ) {if (UsetErrTimer < 255) UsetErrTimer++;} else UsetErrTimer = 0;
ADCchannel = ADCchannel + 1;
if (ADCchannel>3) ADCchannel = 0;
writeBuf[0] = 1; // ask for config register
writeBuf[1] = ( 0b11000010 | (ADCchannel << 4) ); // start convertion at ±4.096V
writeBuf[2] = 0b01000011; // 32 SPS, disable comparator
Wire.beginTransmission(ADR_ASD1115); // ask for ADC
Wire.write(writeBuf[0]); // register
Wire.write(writeBuf[1]); // bit's 15:8
Wire.write(writeBuf[2]); // bit's 7:0
Wire.endTransmission();
}
//-------------------------------------------------------------------------------
long GetOutVoltage(long data)
{
int mult;
data = (data - 93);
if ((data > 32000)||(data < 0)) {data = 0; return data;}
if (data < 500) mult = 10549;
else mult = map(data,500,30500,10500,10484);
data = data * mult;
data = data / 10000;
return data;
}
//-------------------------------------------------------------------------------
long GetSetVoltage(long data)
{
int mult;
data = (data - 107);
if ((data > 32000)||(data < 0)) {data = 0; return data;}
if (data < 500) mult = 10549;
else mult = map(data,500,30500,10542,10532);
data = data * mult;
data = data / 10000;
return data;
}
//-------------------------------------------------------------------------------
long GetOutCurrent(long data)
{
int mult;
data = (data - 121);
if ((data > 32000)||(data < 0)) {data = 0; return data;}
if (data < 600) mult = 16367;
else mult = map(data,600,31000,16311,16175);
data = data * mult;
data = data / 100000;
return data;
}
//-------------------------------------------------------------------------------
long GetSetCurrent(long data)
{
int mult;
data = (data - 143);
if ((data > 32000)||(data < 0)) {data = 0; return data;}
if (data < 600) mult = 25719;//16367;
else mult = map(data,600,31000,16220,15940);
data = data * mult;
data = data / 100000;
return data;
}
// sending commands to display
void sendCommand(unsigned int command, boolean data)
{
digitalWrite(CLK, HIGH);
digitalWrite(STB, LOW);
for(int i = 0; i < 8; i++)
{
if(bitRead(command, i) & 0x01)
{
digitalWrite(DAT_TX, HIGH);
}else{
digitalWrite(DAT_TX, LOW);
}
digitalWrite(CLK, LOW);
digitalWrite(CLK, HIGH);
}
if(data) digitalWrite(STB, HIGH);
}
// sending data to display
void sendData(byte data, boolean last)
{
for(byte index = 0; index < 8; index++)
{
if(bitRead(data, index) & 0x01)
{ digitalWrite(DAT_TX, HIGH); }
else
{ digitalWrite(DAT_TX, LOW); }
digitalWrite(CLK, LOW);
digitalWrite(CLK, HIGH);
}
if(last) digitalWrite(STB, HIGH);
}
// set ram address
void setAddress(unsigned int address, boolean data)
{
sendCommand(addressSettings | (address & 0x1F), data);
}
// cleaning of display memory
void clearDisplay()
{
sendCommand((dataSettings | incrementAddress), true);
setAddress(startAddress, false);
for(int i = 0; i <= endAddress; i++)
{
sendData(0x00, false);
}
digitalWrite(STB, HIGH);
}
// routines for initialize display
void initDisplay()
{
delay (200);
// clear display ram memory
clearDisplay();
// set display mode to 5 digits, 16 segments
sendCommand(displayMode, true);
// set display on and minimum dimming
sendCommand(displayControl, true);
}
// update fixed memory addresses
void updateFixedAddres(int address, int data)
{
sendCommand((dataSettings | fixedAddress), true);
setAddress(address, false);
sendData(data, true);
}
// write symbol data to memory
void putSymbol(byte symIndex, byte posit, bool upcom, bool dot, bool dncom)
{
byte symbol;
sendCommand((dataSettings | incrementAddress), true);
setAddress(posit * 2, false);
symbol = symbols[(symIndex*2)];
if (upcom) symbol |= 1<<4;
sendData(symbol, false);
symbol = symbols[(symIndex*2)+1];
if (dot) symbol |= 1;
if (dncom) symbol |= 1<<2;
sendData(symbol, true);
}
// update LED status
void updateLED(int data)
{
sendCommand((dataSettings | LEDregister), false);
sendData(data, true);
}
// additional symbol of VFD updating
void updateSymbol(byte posit, bool value)
{
if (value) symbolReg |= (1<<posit+2); else symbolReg &= ~(1<<posit+2);
updateFixedAddres(0, symbolReg);
}
//-------------------------------------------------------------------------------
со временем — переделаю общение по I2C, что бы без библиотеки, и связь с дисплеем на SPI, а не ногодрыг.
«Построить наиболее универсальный лабораторный блок» — БП и стартерные батареи заряжать и предусилители питать мягко говоря не лучший вариант. Плохая идея и плохое решение.
Я не со зла — просто удивляет с каким упорством обсуждаестя начие вч шумов. Времена изменились — теперь в основном цифровая техника и цифровая обработка сигнала. И допустимый уровень этих шумов никак не влияет на работу эл техники. А радиэфир так засорен спектральными помехами так, что общий уровень э/м шумов в сравнении с 30тыми годами прошлого века возрос на 2 порядка. И ничего с этим не поделать, надо только принять.
А если вас устраивают ВЧ пульсации в 50 мВ, зачем вообще линейный стабилизатор? Зачем трансформатор на 50 Гц? Возьмите обычный импульсный БП, добавьте к нему набор регулируемого импульсного стабилизатора с экраном и микропроцессорным управлением, неоднократно тут обозревавшийся, и будут вам ваши 50 мВ, а может даже и ниже. А если уж мы ставим линейный стабилизатор, то я на выходе ожидают максимум милливольты пульсаций.
Кстати, по поводу ВЧ пульсаций — если вам интересна тема КВ-приемников (особенно регенеративных), вряд ли вы их сможете питать от такого БП.
На общий выход, или между импульсником и линейником?
Надо внимательно проверять монтаж и схему.
Сразу бросается в глаза питание модуля индикации от основной обмотки трансформатора. Выход моста данного источника подключается на общий проводник и сразу образуется один из источников проблемы
p.s. возможно, земляной контур и действительно неправильно разведен, в дополнение к вышесказанному.
А как у прибора со стабильностью В и А?
Ну что за люди «зачем делать, возьми да скопируй»
Я как раз делал предрегулятор на частоте сети (точнее, на 100 Гц), основной его недостаток — это большие токи через обмотку, выпрямитель и конденсатор на небольших напряжениях. То есть, трансформатор надо брать с большим запасом, чтобы не нагревался (или применять активное охлаждение). Также, очень желательно использовать дроссель приличной индуктивности, чтобы спектр помех свести в более низкочастотную область, где линейный стабилизатор с ними хорошо справится. Ну и трансформатор «трещит» от него :)
Низкочастотный пререгулятор имеет недостатки, это так. Наверное, есть смысл первую ступень делать полноценной импульсной. Сетевой импульсник не очень нравится, так как такой БП будет иметь сильную связь с сетью через Y-конденсатор. А поскольку в домашней сети заземления нет, то все устройства с импульсниками больше или меньше бьются током, что для лабораторного БП не очень хорошо. Поэтому я бы взял обычный транс, затем низковольтный импульный пререгулятор, затем линейный каскад. Что-то подобное было сделано в ранней версии этого БП, с автором проекта я долгое время переписывался, пост-регулятор сделан по мотивам PSL-3604 (EEVblog).
А если все же делать предрегулятор импульсным, я бы рассмотрел вариант использования аналога корректора мощности, у него в некотором смысле схожая задача — поддерживать на выходе определенное напряжение. Только напряжение это теперь будет изменяемым, а не постоянным, как в сетевых БП (ну и надо подумать, как правильно все сделать, т.к. напряжение на входе может быть как меньше, так и больше выходного, возможно, понадобится SEPIC). Также корректор имитирует активную нагрузку, что снизит нагрев обмотки трансформатора и диодного моста. А с пульсациями на частоте 100 Гц уже сможет и линейный преобразователь справиться.
И если вдруг решитесь на разработку такого (или не такого) БП, рассмотрите вариант с двуполярным выходом — понятно, что можно взять два отдельных блока и их соединить, но один блок с двуполярным выходом будет попроще, а потребность в двуполярном питании сейчас возникает часто.
Единственное, было бы хорошо поточнее измерять потребляемый нагрузкой ток. Это часто нужно при отладке малопотребляющих устройств. Но тут лучше использовать внешний прибор, к тому же, в зависимости от специфики нагрузки может понадобиться интегрирование по большому интервалу. Для этого можно было бы поставить внешний точный АЦП.
Ставить хороший опорный источник можно лишь из соображений низкого ТКН, так как абсолютная точность не интересует, она калибруется. Но это тянет за собой целую цепочку изменений: нужны термостабильные резисторы и ОУ с малым дрейфом. А так тут все довольно хорошо сбалансировано: LP2951 имеет типовой ТКН 20 ppm/градус (против 5 ppm у REF19x), резисторы я ставил 25 ppm 0.1%, ОУ тоже в комнатных условиях дает похожий вклад.
ККМ выполняет несколько иную функцию — он имеет в своем составе перемножитель, с помощью которого модулируется потребляемый ток пропорционально мгновенному значению входного напряжения. Для пререглятора эта функция не сильно нужна. Тут лучше подойдет обычный регулируемый step-down.
Про двухполярный БП думал. Проще всего, конечно, сделать link между двумя PSL-3604, там ведь есть изолированный USB, вместо него можно просто соединить UART-ы. Но пока не добрался, так как у меня всего один БП, второй экземпляр уже второй год дособрать не могу за ненужностью. На скорую руку сделал вот такой, после чего энтузиазм угас — проблема ведь как-то решена. Планировал сделать еще прецизионный БП, что-то похожее на Keithley 2450, но работы много, а толку в любительских условиях мало. Электронную нагрузку тоже начинал проектировать, но забросил. Чем дальше, тем меньше что-то надо. Старость. Из заброшенных проектов можно целую аллею памяти построить :)
Просто если уж ставить импульсный преобразователь перед линейным, почему бы не возложить на него еще и такую задачу, ведь она ему вполне по силам.
Понимаю =) И, по большому счету, для многих задач такого будет действительно достаточно. Но я вот себе собрал двуполярный 0… 35 В и уже несколько раз пользовался обеими половинками — то надо было 60 В снять, то усилитель запитать.
И тем не менее, у вас весьма много и полноценно проработанных проектов)
Сейчас мой предрегулятор без нагрузки дает на выходе «пилу», резкий подъем и плавный спад. под нагрузкий — то же, но с меньшим периодом.
Может это быть из-за малой индуктивности?
Но если у человека есть 100% точный источник, почему бы и не поделится?
А послать в гугол — может каждый :)
Решение с автоматическим подключением нужной обмотки весьма интересное.
этому решению лет 50
Мог бы и не заморачиватся, а коммутировать отводы симисторами и упростить выпрямитель, а так — уважение за труд
Это бывает надо, например, для быстрого перехода из СС в CV.
Ну и чем при такой частоте пульсации могут помешать? Всё тоже что и в звуковом усилителе.А в цифровой форме анализ Фурье сигнала все ваши пульсации на постоянной частоте отсекает на раз-два.
При чём тут гидролокация.Это скорее для радио или в измерительной технике.
Но все же шумы есть, пока борюсь
В вашем случае после предрегулятора нужен хороший пассивный ФНЧ 3-го или 5-го порядка (то есть, 2 или 3 конденсатора плюс 1 или 2 дросселя) и правильное разведение земли.
>В вашем случае после предрегулятора нужен хороший пассивный ФНЧ
Для проектирования которого надо бы какой-никакой опыт (или оборудование для экспериментов), иначе влететь в резонанс на какой нибудь гармонике раз плюнуть.
у меня вмультиметре вли большой и корпус там тоже большой
Ссылки на дешевые флюки тоже приветствуются, у них вообще бывает что либо дешевое?
а вообще, полурабочие Fluke 8840A или 45 стоят от $30 плюс доставка, но надо мониторить, как и все на ибее.
и то и другое нужно мониторить, покупал себе год назад мультиметр настольный, по асашай free, в москву посредником $55 вышло.
vfd индикаторы б/у для мультиметров видел от $20.
ссылок не будет, потому что все дешевое надо искать, сходу только втридорога можно купить.
разве только просто 7 сегментная трубка как у автора
у меня валяется несколько штук от музцентров и подобного, врят ли под блок питания покатит
зато при помощи чпу фрезера и лазера можно сделать крутые панели со светящимися символами и даже самими сегментами
вчера в ютубе увидел, мужик делает панели для полетного симулятора
выглядят вообще нереально круто, надписи подсвечены, линии там всякие от ручек управления, даже стрелки приборные у самопальных приборов и шкалы подсвечены
Я соединил их вместе, что бы тянуть меньше проводов. Так делает большинство :)
Я думал, что когда переделаю обмен на SPI, мега будет слать инфу на дисплей через 11-ю ногу, а читать — через 12.
Изначально задумывалось больше органов управления, с задействованием PT6312, но потом — захотелось меньшый корпус, и пришлось оптимизировать.
Боюсь, что 2 В запаса для стабилизатора — маловато, надо чуть больше.
Полагаю, надежда на то, что стабилизатор подавит все пульсации от ИИП — заблуждение, скорее всего они легко проходят через него. Надо поставить после ИИП фильтр с катушками (на схеме их не увидел). Всё-таки 50 мВ — многовато для такой сложной схемы.
На мой взгляд, компьютерный радиатор с вентилятором должен торчать наружу — будет гораздо эффективное охлаждение (если тепловая мощность выделяется в десятки Ватт).
В целом, поздравляю, мало кто сегодня способен сотворить такой прибор.
Обычный параметрический стабилизатор отлично снимет низкочастотные 100Гц, — но ВЧ будут пролезать легко. Для борьбы с ними путем электронной фильтрации нужно городить ВЧ схемы с вч транзисторами.
Кардинальное решение для данного источника — полное отключение импульсника на малых токах. Микроконтроллер в схеме есть — программно выключаем импульсник, открываем полностью его транзистор. Если используемый стабилизатор позволяет сделать 100% ШИМ на входе, то это реализуется просто «врезкой» сигнала в цепь обратной связи. При этом надо будет следить за рассеиваемой мощностью на линейнике и переключаться на импульсник при превышении какого-то порога.
А «расположение» это больше мои рассуждения, чем реальный совет — у автора источник уже сделан и он вряд-ли будет его так кардинально перелопачивать. Разве что фильтрующий дроссель после импульсника поставить.
Забавно, но потом у проекта было расширение — к корпусу припаян АТХ БП, отмодденый в тот-же фром-фактор. В нем реализован инвертер ±30В 10А. Потом была эпопея по прикручиванию регулятора 1503 на этот гибридный БП. В итоге, «переключатель типа» на 1503 выбирает режим работы устройства — полностью линейный 30В 1А, гибридный 30В 3А (с разрешением работы DC/DC) и третий, с запуском импульсного источника. Потом было начал расширять его на добавление регулируемого изолированного +300В 1.5А, да «наигрался».
Итак, замечания по конструкции.
Основное — очень захламлена face. Крутилками напряжения и тока пользоваться невозможно. Да-да, невозможно. Как первая мера, можно поставить тонкие-высокие ручки, но это все-равно полумера. Нет места для пальцев.
Второе — количество кнопок зашкаливает. Не спорю, они нужны, но «не так».
Третье — для работы с БП удобно иметь индикацию 3х величин — напряжения, тока и мощности. Для работы с аккумуляторами — напряжения, тока и емкости. Всё остальное лишь утяжеляет интерфейс.
Четвертое — выходные соединители. Купите что дорогое, а? Или, на крайний случай, поставьте «советские». Весь этот китацкий пластик — зря выброшенные деньги. Себе же делаете, сделайте нормально. У вас ток большой, «пластик» раздолбается очень быстро.
И вообще, доступ к крутилкам через провода?… короче, face надо переделывать. Начните проектировку со стандартного — разъемы внизу, крутилки по правой стороне, индикатор слева.
Это называется «лицевая панель» :)
По передней панели: очень хотелось ВЛИ, поэтому потенциометры сбоку никак. Там еще внутри есть стойки, скрепляющие верх и низ корпуса, поэтому по краях — только светодиоды. да и им пришлось повыгибать ноги.
Крутилки — очень легко оперируются одним пальцем. А высокие тонкие ручки — не умею искать на али.
ВЛИ, конечно, красив, но полезная площадь индикации относительно габаритов корпуса — очень мала. Отобразить еще одно значение — будет сложно. Разве что — раз в 1..2 секунды переключать изображение
По поводу точности АЦП — используете ли вы усреднение нескольких измеренных значений? Если нет, и скорость АЦП позволяет — используйте. У меня в БП тоже 16-битный АЦП из серии ADS, я усредняю 32 или 64 сэмпла по каждому каналу. В итоге показания «прыгают» на 1 мВ при максимуме в 35 В.
По переходу на СС — попробую еще, чуть попозже
есть у вас что то вот такого уровня исполнения? вот с таким мануалом?
А сзади у меня так:
forum.cxem.net/index.php?/topic/158963-переделка-блока-питания-eltek-smps-1000-si-24v/page/5/&tab=comments#comment-2345034
там же и схемы есть
Я бы вот только в затвор полевика предрегулятора стабилитрон поставил, на 12-15 Вольт параллельно R105. А то там напряжение до -35 В может быть, а транзистор только до +-20 В обычно на затворе допускает.
Странно, что никто не заметил этой грубейшей ошибки;)
ЛиТценДрат (нем. Litzen + Draht — пряди и провод)
В спойлере «Реализовано 8 режимов индикации» на фотках на заднем плане виден монитор ресурсов, не подскажете, что за проект?
Отдельное спасибо за «Блок живлення»!
По-моему даже более круто.
Ведь правильно «Джерело живлення», так?
А вообще на Украине есть такого же типа сайт, только на украинском?
Джерело — это по-русски «источник».
Так что оба варианта правильны.
За самодельщину плюсую, но передняя панель не фонтан, не эргономичная.
ИМХО. Я для себя всегда делаю БП вертикальные, меньше места на столе занимают.
Не подумайте, что я «умничаю», упаси бог, да и отговорить вас не пытаюсь. Вскройте имеющиеся у вас блоки, выпишите названия контроллеров и погуглите, написав примерно следующее: «переделка компьютерного блока питания в лабораторный tl494». Без кавычек, а вместо tl494 пишите, что там у вас есть/стоит в БП. Также, вместо лабораторный можно попробовать написать регулируемый. И вот уже потом можно оценивать, а стоит ли вообще — это делать… Примерно так.
dic.academic.ru/searchall.php?SWord=ИЖЕ
aliexpress.com/item/32891103974.html
И еще:
sdelaysam-svoimirukami.ru/3871-laboratornyy-istochnik-pitaniya-iz-bp-kompyutera.html
Есть ещё вариант дорогие блоки DPS серии…