This is my latest contraption. I made an AC power supply that can be controlled by the Yats test automatization software or any other tool that can send commands via a serial port. It provides 4 mains sockets that are controlled either through RS232 or manually using buttons. There is one isolated 220V and one 110V socket on the front panel. On the back panel, there is one isolated 220V socket and one non-isolated 220V socket. The 110V transformer is rated for 100W. The 220V isolation transformer is capable of 50W, which is shared between the two sockets. Most of the components are available on E-Bay.
I planned to include a panel ammeter as well but I overestimated the space in the enclosure and had to give it up. To sum up, this PSU is an awesome tool because it
- can be fully controlled from a PC running automated tests
- provides isolated voltage, lowering the risk of electric shock or equipment damage during measurements, servicing etc.
- has separate fuses for the transformers
- one of the outputs should be able to handle a reasonable current, probably 10A. I should measure the temperature of the heatsink first
- is completely silent – only solid state relays (SSR) are used. I have previously used mechanical relays in automatic testing, but they are pain to live with
I used an Atmega 168V board. The older version of the Arduino IDE installed on my PC seems not to support this MCU but after some tweaking programming the chip became possible. The MCU is more than adequate for the COM port control application, but its flash and RAM resources were too low to support a W5100 Ethernet module. In the future I should replace the chip with pin-to-pin compatible Atmega 328 and implement control via Ethernet.
The PC control interface supports the following commands:
- GET – prints the current output status. GET <output number> prints only the status of the given output
- SET <output number> <ON/OFF> – set output status
- OFF <output number> – turn off output, multiple output numbers may be passed. If no output numbers are given, all are turned off
- ON <output number> … – pretty much the same as OFF command
- DEFAULTS – print default status for all outputs. The default states are applied when the PSU is turned on. The user can override that by keeping any button pressed during power-on
- DEFAULT <output number> <ON/OFF> – change the default state for an output
- DEFAULT SAVE – saves the current status as defaults
I used a piece of laminated flooring as the bottom layer holding the SSRs and transformers because it is sturdy, easy to work with, and most importantly – was there when I needed something to mount the parts on. It is also used to hold the front sockets in place.
The heat-shrunk part in the middle is an isolated 5V DC power supply module for the control board.
A lot nicer with the cover on:
I need to figure out how to put labels on the plastic panels.
Source code:
#include <SerialCommand.h> #include <util/crc16.h> #include <avr/eeprom.h> #include <avr/io.h> #include <avr/wdt.h> #define versionString (F("PS v1.00")) #define ok (F("\r\nOK\r\n")) #define error F("\r\nERROR\r\n") #define ON "ON" #define OFF "OFF" SerialCommand SCmd; // The SerialCommand object #define numRelays 4 const int relayPins[numRelays] = { 3, //D3 - PWM capable 5, //D5 PWM 6, //D6 PWM 10, //B2 PWM }; const int invertRelayPins[numRelays] = { 0, 0, 0, 0, }; #define KEY_LOW 600 #define KEY_HIGH 900 const int keys[numRelays] = { A5, //C5 A4, //C4 A3, //C3 A2, }; #define MAX_KEY_COUNTER 80 byte keyCounters[numRelays] = {0, 0, 0, 0}; byte startup_values[numRelays] = {0, 0, 0, 0}; const byte default_startup_values[numRelays] = {0, 0, 0, 0}; void setup() { bool keyPressed = false; // skip loading defaults if any key is pressed // Clear the reset bit MCUSR &= ~_BV(WDRF); // Disable the WDT WDTCSR |= _BV(WDCE) | _BV(WDE); WDTCSR = 0; // configure relay pins to output, OFF for (int i = 0; i < numRelays; i++){ pinMode(relayPins[i], OUTPUT); // turn the internal pull up for key ADC digitalWrite(keys[i], HIGH); } // Setup callbacks for SerialCommand commands SCmd.addCommand("VERSION", cmd_version); SCmd.addCommand("HELP", cmd_help); SCmd.addCommand("?", cmd_help); SCmd.addCommand("OFF", cmd_off); // output off SCmd.addCommand("ON", cmd_on); // output on SCmd.addCommand("GET", cmd_get); // get status SCmd.addCommand("SET", cmd_set); // set status SCmd.addCommand("defaults", cmd_print_defaults); // print default relayIndex status SCmd.addCommand("default", cmd_set_default); // set default relayIndex status SCmd.addCommand("reset", cmd_reset); // reset MCU SCmd.addDefaultHandler(cmd_unrecognized); // Handler for command that isn't matched (says "ERROR") Serial.begin(115200); Serial.print(versionString); Serial.print(F("\r\n")); for (int i = 0; i < numRelays; i++){ int sensorValue = analogRead(keys[i]); if (sensorValue < KEY_LOW) { keyCounters[i] = MAX_KEY_COUNTER + 1; // ignore further pressing of the key - otherwise would turn on its channel keyPressed = true; } } if (keyPressed == false) { load_eeprom(); } else { Serial.print(F("Key pressed - not loading saved defaults\r\n")); } } void loop() { SCmd.readSerial(); // We don't do much, just process serial commands for (int i = 0; i < numRelays; i++){ int sensorValue = analogRead(keys[i]); if (sensorValue < KEY_LOW) { if (keyCounters[i] > MAX_KEY_COUNTER){ continue; } keyCounters[i]++; if (keyCounters[i] == MAX_KEY_COUNTER) { int newStatus = !getRelay(i); setRelay(i, newStatus); } } else if (sensorValue > KEY_HIGH){ keyCounters[i] = 0; } } delayMicroseconds(200); } void load_eeprom() { unsigned int crcStored; eeprom_read_block((void*)&startup_values, (void*)0, sizeof(startup_values)); eeprom_read_block((void*)&crcStored, (void*)sizeof(startup_values), sizeof(crcStored)); if (crc((byte*) &startup_values, sizeof(startup_values)) != crcStored) { Serial.print(F("EEPROM load error\r\n")); memcpy(startup_values, default_startup_values, sizeof(startup_values)); save_eeprom(); } for (int i = 0; i < numRelays; i++) { setRelay(i, startup_values[i]); } } void save_eeprom() { unsigned int crcStored = crc((byte*) &startup_values, sizeof(startup_values)); eeprom_write_block((void*)&startup_values, (void*)0, sizeof(startup_values)); eeprom_write_block((void*)&crcStored, (void*)sizeof(startup_values), sizeof(crcStored)); } void cmd_version(){ Serial.print(versionString); Serial.print(ok); } void cmd_help(){ Serial.print(F("GET print output status\r\n")); Serial.print(F("GET <output number> print output status\r\n")); Serial.print(F("SET <output number> <ON/OFF> set output status\r\n")); Serial.print(F("OFF <output number> same as SET <number> OFF\r\n")); Serial.print(F("OFF all outputs off\r\n")); Serial.print(F("ON <output number> same as SET <number> ON\r\n")); Serial.print(F("DEFAULTS print default status for all outputs\r\n")); Serial.print(F("DEFAULT <output number> <ON/OFF> set output status after power-on\r\n")); Serial.print(F("DEFAULT SAVE save the current status as power-on status\r\n")); Serial.print(F(" (defaults are not applied if a button is pressed during power on)\r\n")); Serial.print(F("VERSION print firmware version\r\n")); Serial.print(F("RESET reset controller\r\n")); Serial.print(F("\r\n")); Serial.print(F("<output number> 1..")); Serial.print(numRelays); Serial.print(F("\r\n")); Serial.print(F("www.veiverys.com")); Serial.print(ok); } void cmd_off(){ char *arg = SCmd.next(); if (arg == NULL) { // all off setRelay(-1, 0); Serial.print(ok); return; } do { int relayIndex = atoi(arg) - 1; if (relayIndex >= 0 && relayIndex < numRelays){ setRelay(relayIndex, 0); } else { Serial.print(error); return; } arg = SCmd.next(); } while (arg); Serial.print(ok); } void cmd_on(){ char *arg = SCmd.next(); if (arg == NULL) { Serial.print(error); return; } do { int relayIndex = atoi(arg) - 1; if (relayIndex >= 0 && relayIndex < numRelays){ setRelay(relayIndex, 1); } else { Serial.print(error); return; } arg = SCmd.next(); } while (arg); Serial.print(ok); } void cmd_get(){ char *arg = SCmd.next(); if (arg != NULL) { int relayIndex = atoi(arg) - 1; if (relayIndex >= 0 && relayIndex < numRelays){ int status = getRelay(relayIndex); Serial.print(relayIndex+1); Serial.print(F(":")); if (status) { Serial.print(ON); } else { Serial.print(OFF); } Serial.print(ok); return; } else { Serial.print(error); return; } } // relay number not given or invalid for (int i = 0; i < numRelays; i++){ int status = getRelay(i); if (i != 0) { Serial.print(F(",")); } if (status){ Serial.print(ON); } else { Serial.print(OFF); } } Serial.print(ok); } void cmd_set(){ char *arg1, *arg2; arg1 = SCmd.next(); arg2 = SCmd.next(); if (arg1 != NULL && arg2 != NULL) { int relayIndex = atoi(arg1) - 1; if (relayIndex >= 0 && relayIndex < numRelays) { int action; if (strncasecmp(arg2, ON, 2) == 0) { action = 1; } else if (strncasecmp(arg2, OFF, 3) == 0) { action = 0; } else { Serial.print(error); return; } setRelay(relayIndex, action); Serial.print(ok); return; } } Serial.print(error); } void cmd_print_defaults(){ // print default relayIndex status int i; for (i = 0; i < numRelays; i++) { bool status = startup_values[i] != 0; if (i != 0) { Serial.print(F(",")); } if (status){ Serial.print(ON); } else { Serial.print(OFF); } } Serial.print(ok); } void cmd_set_default(){ // syntax: DEFAULT <relayIndex number> <ON/OFF> char *arg1, *arg2; arg1 = SCmd.next(); arg2 = SCmd.next(); if (arg1 != NULL && arg2 != NULL) { int relayIndex = atoi(arg1) - 1; if (relayIndex >= 0 && relayIndex < numRelays) { int action; if (strncasecmp(arg2, ON, 2) == 0) { action = 1; } else if (strncasecmp(arg2, OFF, 3) == 0) { action = 0; } else { Serial.print(error); return; } startup_values[relayIndex] = action; save_eeprom(); Serial.print(ok); return; } } else if (arg1 != NULL) { if (strncasecmp(arg1, "save", 4) == 0) { for (int i = 0; i < numRelays; i++) { startup_values[i] = getRelay(i); } save_eeprom(); Serial.print(ok); return; } } Serial.print(error); } void cmd_save(){ Serial.print("saves!"); for (int i = 0; i < numRelays; i++) { startup_values[i] = getRelay(i); } save_eeprom(); Serial.print(ok); } void cmd_reset(){ wdt_enable(WDTO_1S); } // This gets set as the default handler, and gets called when no other command matches. void cmd_unrecognized() { Serial.print(error); } // -1 - all, 0..N-1 - individual void setRelay(int relayNumber, bool on) { if (relayNumber == -1) { // all relays for (int i = 0; i < numRelays; i++){ if (invertRelayPins[i]){ digitalWrite(relayPins[i], (on)?LOW:HIGH); } else { digitalWrite(relayPins[i], (on)?HIGH:LOW); } } return; } if (relayNumber >= 0 && relayNumber < numRelays) { if (invertRelayPins[relayNumber]){ digitalWrite(relayPins[relayNumber], (on)?LOW:HIGH); } else { digitalWrite(relayPins[relayNumber], (on)?HIGH:LOW); } return; } Serial.print(error); } // returns false if relay is off. Relays are numbered from 0 bool getRelay(int relayNumber) { if (relayNumber >= 0 && relayNumber < numRelays) { if (invertRelayPins[relayNumber]){ return (digitalRead(relayPins[relayNumber]) == 0); } else { return (digitalRead(relayPins[relayNumber]) == 1); } } Serial.print(error); return false; } unsigned int crc (byte *data, int length) { int i; unsigned int crc; crc = 0xFFFF; for (i = 0; i < length; i++) { crc = _crc16_update (crc, data[i]); } return crc; }
The buttons are connected to ADC pins and the analog values are read, although at the moment the control is on/off only.