DIY Programmable Isolated AC Power Supply

By | May 9, 2013

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.

psu-1

psu-2

The heat-shrunk part in the middle is an isolated 5V DC power supply module for the control board.

psu-3

A lot nicer with the cover on:

psu-6

psu-5

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.

Leave a Reply

Your email address will not be published.