// ------------------------------------------------------------------------- // SmartPSU3 software used in PSU Pre 2016 // // Schematics designators reference to version 1.0A // See http://nihtila.com/projects/modules/smart-psu-3/ for more information // // 27.9.2016 Initial version // ------------------------------------------------------------------------- #include #include // -------------------------------------------------------------------- // User defined settings - change these according to your schematics // -------------------------------------------------------------------- #define DEL_STARTUP 400 // startup delay #define DEL_ERRBLINKL 500 // error blinking pattern, long delay #define DEL_ERRBLINKS 250 // error blinking pattern, short delay #define DEL_ERRBLINKEND 1000 // error blinking pattern, end of the pattern // Undervoltage limits [absolute value in mV] #define LIM_VAP_V 13500 // nominal voltage 14 V #define LIM_VAN_V 13500 // nominal voltage -14 V #define LIM_VD_V 6500 // nominal voltage 7 V // Overcurrent limit [absolute value in mA] // set according to used transformers #define LIM_VAP_I 210 #define LIM_VAN_I 210 #define LIM_VD_I 600 // Overtemperature limit [degree celsius] #define LIM_T 80 // silicon #define LIM_CASE_T 50 // case // Resistor values [ohm] (refer to schematics) // VAP #define R5 22000 #define R7 22000 #define R15 10000 #define R16 3010 // VAN (Note: assumes that R11 and R18 are unpopulated, R24 = 0, and R23=R25) #define R26 10000 #define R17 10000 #define R23_R25 3010 // VD #define R6 22000 #define R8 22000 #define R19 3010 #define R20 3010 // +5VD supply [mV] // This is measured, typical value is 5062 when R28 = 220 and R27 = 665 #define P5VD 5140 // -------------------------------------------------------------------- // Definitions - do not change these! // -------------------------------------------------------------------- // Pre-calculate ADC limit values for voltage, current, and temperature limits; // calculates what the set limit value in mV/mA/T corresponds in ADC value (0...1023). // This makes runtime comparison more efficient, allowing faster response for // fault condition and turning outputs off. #define LIMVAL_VAP_V ((int)((unsigned long)(LIM_VAP_V)*R16/(R16+R15)*1023/P5VD)) #define LIMVAL_VAP_I ((int)((unsigned long)(LIM_VAP_I)*R7/5000*1023/P5VD)) #define LIMVAL_VAP_T ((int)((unsigned long)(LIM_T)*R6/1000*1023/P5VD)) #define LIMVAL_VAN_V ((int)((unsigned long)(LIM_VAN_V)*R23_R25/(R17+R23_R25)*1023/P5VD)) #define LIMVAL_VAN_I ((int)((unsigned long)(LIM_VAN_I)*R26/2000*1023/P5VD)) #define LIMVAL_VD_V ((int)((unsigned long)(LIM_VD_V)*R20/(R19+R20)*1023/P5VD)) #define LIMVAL_VD_I ((int)((unsigned long)(LIM_VD_I)*R8/5000*1023/P5VD)) #define LIMVAL_VD_T ((int)((unsigned long)(LIM_T)*R5/1000*1023/P5VD)) #define LIMVAL_CASE_T ((int)((unsigned long)LIM_CASE_T*2)) // Numerator and denominator for conversion factor; these are used to calculate // the actual voltage/current/temperature from ADC values. Basically, these together // are reciprocal of the above formulas which are used for conversion the other way. // These are long data type to allow for calculations. However, no checks have been // really done regarding in what case one can expect overflows! But it shouldn't happen. #define CONVNUM_VAP_V ((unsigned long)(P5VD)*(R16+R15)/1023) #define CONVNUM_VAP_I ((unsigned long)5*1000*P5VD/1023) #define CONVNUM_VAP_T ((unsigned long)1000*P5VD/1023) #define CONVNUM_VAN_V ((unsigned long)(P5VD)*(R17+R23_R25)/1023) #define CONVNUM_VAN_I ((unsigned long)2*1000*P5VD/1023) #define CONVNUM_VD_V ((unsigned long)(P5VD)*(R19+R20)/1023) #define CONVNUM_VD_I ((unsigned long)5*1000*P5VD/1023) #define CONVNUM_VD_T ((unsigned long)1000*P5VD/1023) #define CONVNUM_CASE_T ((unsigned long)1) #define CONVDENOM_VAP_V ((unsigned long)(R16)) #define CONVDENOM_VAP_I ((unsigned long)(R7)) #define CONVDENOM_VAP_T ((unsigned long)(R6)) #define CONVDENOM_VAN_V ((unsigned long)(R23_R25)) #define CONVDENOM_VAN_I ((unsigned long)(R26)) #define CONVDENOM_VD_V ((unsigned long)(R20)) #define CONVDENOM_VD_I ((unsigned long)(R8)) #define CONVDENOM_VD_T ((unsigned long)(R5)) #define CONVDENOM_CASE_T ((unsigned long)(2)) // Defines if limit is of type "under" (undervoltage) or "over" (overcurrent, overtemperature) #define UNDER 0 #define OVER 1 // Pin assignments #define PIN_LED_VAP_V 4 #define PIN_LED_VAP_I 5 #define PIN_LED_VAN_V 6 #define PIN_LED_VAN_I 7 #define PIN_LED_VD_V 8 #define PIN_LED_VD_I 9 #define PIN_LED_T 10 #define PIN_LED_FRONT 3 #define PIN_REL 2 #define PIN_SW 12 #define PIN_CASE_T 11 #define NO_CHANNELS 9 #define I_CASE_T 8 // Struct where all channel related data is stored. Only meas is changed in runtime, all other // are constants defined above. struct channelSt { unsigned int meas; // last ADC measurement value [0...1023] const int limit; // corresponding limit value [0...1023] const boolean limitSign; // UNDER/OVER - error is triggered when measured under or over the limit const byte ledPin; // fault led pin const char ref[6]; // channel reference, 5-char string, used in serial monitor const char unit[3]; // unit of meas/limit, 2-char string, used in serial monitor const unsigned int convNum; // conversion factor numerator const unsigned int convDenom; // conversion factor denominator const byte errLedL; // no of long blinks in fault condition const byte errLedS; // no of short blinks in fault condition } channels[] = { {0, LIMVAL_VAP_T, OVER, PIN_LED_T, "VA+ T", "C ", CONVNUM_VAP_T, CONVDENOM_VAP_T, 1, 3}, {0, LIMVAL_VAP_I, OVER, PIN_LED_VAP_I, "VA+ I", "mA", CONVNUM_VAP_I, CONVDENOM_VAP_I, 1, 2}, {0, LIMVAL_VAP_V, UNDER, PIN_LED_VAP_V, "VA+ V", "mV", CONVNUM_VAP_V, CONVDENOM_VAP_V, 1, 1}, {0, LIMVAL_VD_T, OVER, PIN_LED_T, "VD T ", "C ", CONVNUM_VD_T, CONVDENOM_VD_T, 3, 3}, {0, LIMVAL_VD_V, UNDER, PIN_LED_VD_V, "VD V ", "mV", CONVNUM_VD_V, CONVDENOM_VD_V, 3, 1}, {0, LIMVAL_VD_I, OVER, PIN_LED_VD_I, "VD I ", "mA", CONVNUM_VD_I, CONVDENOM_VD_I, 3, 2}, {0, LIMVAL_VAN_V, UNDER, PIN_LED_VAN_V, "VA- V", "mV", CONVNUM_VAN_V, CONVDENOM_VAN_V, 2, 1}, {0, LIMVAL_VAN_I, OVER, PIN_LED_VAN_I, "VA- I", "mA", CONVNUM_VAN_I, CONVDENOM_VAN_I, 2, 2}, {0, LIMVAL_CASE_T, OVER, PIN_LED_T, "CaseT", "C ", CONVNUM_CASE_T, CONVDENOM_CASE_T, 1, 0} }; // define these pins as constants instead of only #defines const byte pinRel = PIN_REL; // output relay drive const byte pinLedFr = PIN_LED_FRONT; // front panel led const byte pinSW = PIN_SW; // on-board pushbutton const byte pinTSensor = PIN_CASE_T; //byte ind = 7; unsigned long temp; // Note: no search for DS1820 temperature sensor used for case temperature is performed. // If sensor is not connected or there is an issue, it will cause an error when reading // temperature. OneWire tSensor(PIN_CASE_T); // -------------------------------------------------------------------- // the setup function runs once when you press reset or power the board void setup() { //wdt_disable(); byte addr[8]; // Initialisations // Output relay pin digitalWrite(pinRel, LOW); pinMode(pinRel, OUTPUT); // Fault leds, turn them all on for a while for(int i = 0; i < 8; i++) { pinMode(channels[i].ledPin, OUTPUT); digitalWrite(channels[i].ledPin, LOW); } // Front panel led on pinMode(pinLedFr, OUTPUT); digitalWrite(pinLedFr, LOW); // Button digitalWrite(pinSW, INPUT_PULLUP); // AD-converter analogReference(DEFAULT); // Serial communication Serial.begin(9600); // tSensor.search(addr); Serial.print("1-Wire address: "); Serial.print(addr[0], HEX); Serial.print(addr[1], HEX); Serial.print(addr[2], HEX); Serial.print(addr[3], HEX); Serial.print(addr[4], HEX); Serial.print(addr[5], HEX); Serial.print(addr[6], HEX); Serial.print(addr[7], HEX); Serial.write('\n'); // Trigger DS1820 temperature conversion to avoid error after first readout tSensor.reset(); tSensor.skip(); tSensor.write(0x44); // This would turn the outputs off due to inrush current - there must be an initial // delay when output monitoring is off. Hardware current limit still prevents real // problems. //checkLimits(); // Outputs on digitalWrite(pinRel, HIGH); // Blink front led during startup delay for(int i = DEL_STARTUP/200; i > 0; i--) { delay(100); digitalWrite(pinLedFr, HIGH); delay(100); digitalWrite(pinLedFr, LOW); } // LEDs off (except front LED) for(byte i = 0; i < 8; i++) { digitalWrite(channels[i].ledPin, HIGH); } // goes to loop() where output limits are polled } // the loop function runs over and over again forever void loop() { unsigned int i; // In normal operation, the program continuously loops this one checkLimits(); // If button is pushed. // Short button push sends measurement information of all ADC channels via serial. // Long button push toggles output relay. // Note: response to error condition is slow while button is pushed. if(digitalRead(pinSW) == LOW) { // send information of all channels for(i = 0; i < NO_CHANNELS; i++) { sendChMeas(i); } i = 0; // while button is pushed down.. while(digitalRead(pinSW) == LOW) { delay(10); // if button was pushed for approximately 2 seconds, toggle output relay if(i == 200) { if(digitalRead(pinRel) == HIGH) digitalWrite(pinRel, LOW); else digitalWrite(pinRel, HIGH); i = 0; } i++; // Monitor outputs while button is pushed. However, the delay above makes // response slower. checkLimits(); } } } void readChannel(byte id) { byte data[2]; unsigned int tempRaw; // Special case for DS1820 if(id == I_CASE_T) { // If sensor is not busy - meaning conversion is complete if(tSensor.read() == 0xFF) { // read first two bytes of data (no CRC check!) tSensor.reset(); tSensor.skip(); tSensor.write(0xBE); data[0] = tSensor.read(); data[1] = tSensor.read(); // Process data tempRaw = (data[1] << 8) + data[0]; // Test if sign is negative if(tempRaw & 0x8000) { tempRaw = (tempRaw ^ 0xFFFF) + 1; } // Final value, ignoring fractional part channels[I_CASE_T].meas = tempRaw; // start another conversion tSensor.reset(); tSensor.skip(); tSensor.write(0x44); } } // The rest are measured with ADC else { // Measure ADC channel channels[id].meas = analogRead(id); } } void sendChMeas(byte id) { // Update measurement readChannel(id); Serial.write(channels[id].ref); Serial.write(": "); temp = (unsigned long)(channels[id].meas) * channels[id].convNum / channels[id].convDenom; Serial.print((unsigned int)(temp)); Serial.write(" "); Serial.write(channels[id].unit); Serial.write('\n'); } // Measure all ADC channels, check limits, and trigger a fault if a value is not within limits. // Note that no error checks are performed, for example if ADC reading is clearly faulty or // temperature sensor is not connected. void checkLimits() { // Go through all channels for(byte i = 0; i < NO_CHANNELS; i++) { // measure readChannel(i); // limitSign denotes if limit is under(voltage) or over(current or temperature), // and therefore if measurement value should be larger or smaller than limit. // If meas is not within limit, denote fault condition. if(channels[i].limitSign == UNDER) { if(channels[i].meas < channels[i].limit) { fault(i, channels[i].meas); } } else { // OVER if(channels[i].meas > channels[i].limit) { fault(i, channels[i].meas); } } } } void fault(byte faultId, unsigned int faultVal) { byte i = NO_CHANNELS; byte i_blinkL = 0, i_blinkS = 0; unsigned int val; // Outputs off digitalWrite(pinRel, LOW); // Fault channel led on digitalWrite(channels[faultId].ledPin, LOW); // Wait if button is pressed down while(digitalRead(pinSW) == LOW); delay(300); // Loop until button is pressed (or power toggled). Blink front led to denote an output // fault, and keep performing measurements and sending data via serial. Button response // is slow and inconsistend due to delays but it is not an issue here as "button recovery" // is basically only used when testing the device. // There will be a set of long and short blinks to denote the fault. First there are 1-4 // long blinks for channels 1-3 (VAP, VAN, VD) or case temperature, followed by 0-3 short // blinks for voltage, current, or temperature (case temperature is only one long blink // and no short blinks). // Blink counters: 2* because one blink is two toggles and +1 so that it ends // led being off. i_blinkL = 2* channels[faultId].errLedL +1; i_blinkS = 2* channels[faultId].errLedS -1; // led off digitalWrite(pinLedFr, HIGH); while(digitalRead(pinSW) == HIGH) { if(i_blinkL == 0) { if(i_blinkS == 0) { // Blink counters: 2* because one blink is two toggles and +1 so that it ends // led being off. i_blinkL = 2* channels[faultId].errLedL; i_blinkS = 2* channels[faultId].errLedS; // long pause before new blinking set delay(DEL_ERRBLINKEND); // Send information what channel caused the fault and what was the measured value. // Placed here just due to timing purposes, to not mix the blinking pattern. Serial.write("Fault: "); Serial.write(channels[faultId].ref); Serial.write(" - "); Serial.print(faultVal); Serial.write(" "); Serial.write(channels[faultId].unit); Serial.write('\n'); // Led on - ready for new blinking pattern //digitalWrite(pinLedFr, LOW); } else { // short pause delay(DEL_ERRBLINKS); // Toggle front led if(digitalRead(pinLedFr) == LOW) { digitalWrite(pinLedFr, HIGH); } else { digitalWrite(pinLedFr, LOW); } // decrease counter i_blinkS--; } } else { // long pause delay(DEL_ERRBLINKL); // Toggle front led if(digitalRead(pinLedFr) == LOW) { digitalWrite(pinLedFr, HIGH); } else { digitalWrite(pinLedFr, LOW); } // decrease counter i_blinkL--; } // Keep measuring channels and sending the current values sendChMeas(i); if(i == 0) i = NO_CHANNELS-1; else i--; //if(digitalRead(pinSW) == LOW) { //wdt_enable(WDTO_2S); //while(1); //} } // Button was pressed, wait until it is released while(digitalRead(pinSW) == LOW); delay(300); // Fault channel led off digitalWrite(channels[faultId].ledPin, HIGH); // Ensure front led is on digitalWrite(pinLedFr, LOW); // Return back to fault() and then to loop(). Outputs are still off and need to be // turned back on with a long press. If there is an undervoltage or overtemperature // situation, the program will go back to checkLimits() and fault() immediately. }