Login Join Maker Pro
Or sign in with

# CPU Power Supply Mod

Modification to a CPU power supply to make it into a bench top unit.

### Project Log

Howdy!

This project came out of wanting to etch my first useful PCB (up to now all the PCBs I've made have been tests). I have a old CPU power supply that's been laying around for awhile, so I decided to convert it to a PSU I could use on my bench.

First I started googling around for info on it, and found that it uses a standard computer PSU pin-out called ATX 2.x. The wikipedia article on it is here: https://en.wikipedia.org/wiki/ATX#Power_supply.

The specifications for the power supply are as follows:
• +3.3VDC/17A
• +5VDC/22A
• +5VDC/2A (Standby)
• +12VDC/18A
• -12VDC/0.8A
• 300W Max power
I'm planing to make all the circuitry handle about 3A maximum current draw on each voltage (I'm going to fuse it for 2.5A for now though). If I ever want to be able to draw more current then that (which is not likely), I guess will just have to modify it some more.
=========================================

So the first order of business - draw a schematic. Here it is:

I decided to use a PIC18F1220 for this project because I have quite a few laying around. A 1602 LCD will be used for the user interface – I'll need some way of telling the user which voltages are switched on. Three switches will work for user control (Select/up/down – or something like that).

P4 is a jumper (it connects PGD to ON1 and PGC to ON2) and is there for when the PIC is programmed – Since I ran out of I/Os I had to use the two pins that are for programming the PIC. And when the PIC is programmed, I don't want anything screwing up the communication between the computer and the PIC, so P4 should be unjumped while programming (is that even a word? ).
=========================================

Here's the PCB layout:

Yes, I know that the fuses overlap other parts - The plan is to have the fuses mounted on the back side of the circuit board and the rest of the parts on top. The relays are just some high current ones that I salvaged from a used circuit board (I think they where out of a broken fryer to be exact).

Unfortunately, I will have to solder about eight jumpers on where I couldn't figure out how to run a trace (not shown here, but they can be seen in the KiCAD file as un-routed traces). I guess I should have tried to optimize it a little more, but space was kind of lacking...
=========================================

On to etching!

The first photo is right after the toner transfer - As you can see, there are quite a few spots were the toner did not stick ideally. The next photo is after touching up with a sharpie.

I think I may have rushed the toner transferring part a little, and that's why it didn't turn out so good - I could have redone it, but I didn't want to use up any more of my all to little supply of glossy paper.

So after etching I realized that sharpie is probably not the best resist – it appeared that some had come off during the process... Here's a photo of the PCB after patching the broken traces with solder (sorry I forgot to take a photo right after etching. ):

I guess the next step will be drilling the holes on the PCB – But unfortunately my drill bits haven't arrived yet, so I guess I will have to call it for now.

=========================================
10/7/15

Well the drill bit's came in - here's a photo after drilling:

I had some trouble with the drill bit's breaking but with some help, I got it worked out here: https://www.electronicspoint.com/threads/drill-bits-for-homemade-pcbs.275652/page-3 - Thanks to all who contributed!

I also decided to try and put a silkscreen on the board:

I used the same method as I did for the copper side. But as you can see, on the right side of the left board the transfer moved and blurred out. It doesn't really matter much though, because this was more of a test to see if I could do it and the relays will cover it up.

=========================================
Next step - Soldering!

Everything soldered up pretty nicely. I used stranded wires for the LCD connections so it could be lifted backwards if I need to get at the parts under it. I decided to mount a switch instead of a jumper on P4 which should make it a little less painful to program it.

Everything was going good until I realized that somewhere along the line the connector for the power supply got mirrored on the PCB! I guess I will just have to hand wire it.

=========================================
10/13/15

So I opened up the power supply case to cut out some wires that I don't need for this project, and looky what I found:

I guess this power supply has had a lot of on hours - as I remember though, the computer I pulled it from still worked so I think I will just over look that for now...

Next I made a chassis out of the metal from an old popcorn can (it's tin so it solders nicely), and attached the circuit board to the power supply. Here's a photo:

Photo two is a nut I soldered to the chassis so I could attach the circuit board to it with screws (aka make it easy to take apart and troubleshoot when it doesn't work ). I had to get out the weller 140W soldering gun to make the joints as my regular soldering iron just couldn't cut the mustard.

It lives!!

The display looks kind of weak but it may just be a bad LCD.

Next step will be to write some code.

=========================================
10/29/15

I guess the next step isn't going to be writing code - instead it will be trouble shooting the PSU!

So after playing around with a little code, things just didn't seam to be working right. I decided to check the standby voltage, and to my great surprise it read about 2.8VDC (not 5V as expected)! So I hooked it up to the scope (being careful to hook ground up in the right spot ) and here's what I got:

Look liked the fault of a seriously bad capacitor to me! So I wrote down the values of all the caps that looked suspicious and bought some new ones from digikey (of much higher quality).

After putting the new caps in, I turned it on and the problem seamed to still be there... After some more experimenting I found out that all the screws that hold the circuit board in place have to be in for the unit to work (they connect to the metal chassis completing the ground plane). It now works great - all the voltages are nice and flat (the LCD works a lot better too).

=========================================
11/14/2015

Well, I finished up the code. Here's the main part of it:
Code:
/***********************************************
* File:   Main.c                              *
* Author: Supercap2F                          *
*                                             *
* Created on October 9, 2015, 5:18 PM         *
***********************************************/
/***********************************************
* Included files                              *
***********************************************/
#include <xc.h>
#include "CPU_Power_Supply_Mod.h"
#include "Hitachi_LCD.h" // .h file with all display functions
#include "Bitmaps.c"     // c file with all the CGRAM data in it
/***********************************************
* Definitions                                 *
***********************************************/

/***********************************************
* Device configuration                        *
***********************************************/
#pragma config OSC=INTIO2 // internal oscillator, port function on RA6 and RA7
#pragma config FSCM=OFF   //
#pragma config IESO=OFF   //
#pragma config PWRT=ON    // power up timer enabled
#pragma config BOR=OFF    // brown out reset off **MAY NEED TO BE CHANGED**
#pragma config WDT=OFF    // watch dog timer off
#pragma config MCLRE=ON   // MCLR pin is for reseting
#pragma config LVP=OFF    // low voltage programming is off
#pragma config STVR=ON    // stack overflow will cause a reset

#define COME_FROM_DEFAULT 0
#define COME_FROM_PAIR 1
#define CALL_PAIR_MENU 2
#define RETURN_TO_DEFAULT 3

/***********************************************
* Function Definitions                        *
***********************************************/
void DefaultMenu(void);
void StandbyMenu(void);
unsigned char OptMenu(unsigned char z);
void PairMenu();

/***************************************************************
* Program entry point - Main Function                         *
***************************************************************/
int main()
{

/***********************************************
* Oscillator Configuration                    *
***********************************************/
OSCCONbits.IRCF0=1; // set main clock to 8MHz
OSCCONbits.IRCF1=1;
OSCCONbits.IRCF2=1;

/***********************************************
* I/O Setup                                   *
***********************************************
* RA0 - SW1    | RA1 - SW2  | RA2 - PN    |   *
* RA3 - PG     | RA4 - SW3  | RA6 - SDI   |   *
* RA7 - LCHCLK | RB0 - -12V | RB1 - +12V  |   *
* RB2 - SFTCLK | RB3 - eLCD | RB4 - rsLCD |   *
* RB5 - rwLCD  | RB6 - +5V  | RB7 - +3.3V |   *
*                                             *
***********************************************/
TRISA=0b00010011;  // set I/O ports to either input (1) or output (0)
LATA=0b00000000;   // clear outputs
ADCON1=0b11111111; // make all outputs/inputs digital

TRISB=0b00000000; //  set I/O ports to either input (1) or output (0)
LATB=0b00000000;  // clear outputs

PN=1;
RL12V=1;
RLn12V=1;
RL5V=1;
RL3V3=1;

/***********************************************
* interrupt Setup                             *
***********************************************/
RCONbits.IPEN=0;     //Turn off interrupt priority

/***********************************************
* Timer0 Setup                                *
***********************************************
* Timer0 is for any blinking character on the *
* screen. The TMR0IF flag is set when ever    *
* the display should blink.                   *
***********************************************/
TMR0L=0;             // clear the timer0 count
TMR0H=0;             //
T0CON=0b10010011;    // setup timer0 so that:
//      it is a 16 bit timer
//      it counts on clock cycles
//      it uses a pre-scaler set to 128 so the timer rolls over every 1.04s
INTCONbits.TMR0IF=0; // clear the timer 0 flag bit

/***********************************************
* Main Code                                   *
***********************************************/
LCDwrcmd(0b00111100); // 8 bit mode
LCDwrcmd(0b00000110); // address counter will increment
LCDwrcmd(0b00001100); // cursor off display on blink off
LCDwrcmd(0b00000001); // clear display
__delay_ms(2);

StandbyMenu();     // first call the standby function so the power supply will turn on in standby mode
while(1)           // repeatedly call default menu
DefaultMenu(); //

}

/***************************************************************
* StandbyMenu Function - This function is called when ever    *
* the power supply needs to go into standby mode.             *
***************************************************************/
void StandbyMenu(void)
{
PN=1;                           // set the power supply to standby mode
LCDwrcmd(0b00000001);           // clear the display
__delay_ms(2);                  // delay for 2mS to wait for last command to register
LCDwrstring("    STANDBY    "); // write standby to the screen
while(1) {                      // loop (almost) forever
if(SW1 || SW2 || SW3){      // check if any switches where pressed
__delay_ms(12);          // debounce
while(SW1 || SW2 || SW3); // wait for switches to be let off of
PN=0;                // turn on power supply
return;              // return to where the function was called from
}
}
}

/***************************************************************
* DefaultMenu Function - In this function you can turn off/on *
* individual voltages.                                        *
***************************************************************/
void DefaultMenu(void)
{
/***********************************************
* Variable Definitions                        *
***********************************************/
int x,y;    // random variables
int last=0; // variable the keeps track of the last cursor state

struct MenuDisplay {             // structure that holds general information
unsigned char Addr[5];       // addresses of each place the cursor can be
unsigned char CurrentSelect; // current place where the cursor is
unsigned char ONorOFF[5];    // variable that keeps track of each voltages on/off state
unsigned char Pair;
};

struct MenuDisplay MenuItem = {  // create the structure
0x04,0x09,0x0F,0x45,0x4C,    // fill up the Addr variable
0,                           // fill up the CurrentSelect variable
0,0,0,0,0,                    // fill up the ONorOFF variable
0b00000101
};

/***********************************************
* Setup the LCD                               *
***********************************************/
for(y=0;y<8;y++) // this loop loads the custom characters into the CGRAM
for(x=0;x<7;x++)
{
LCDwrcmd((0b01000000|x)|(y<<3));
LCDwrchar(Default_Menu_Bitmaps[y][x]);
}
LCDsetaddr(0); // set address to start - else it will still write to the CGRAM

LCDwrstring("12V=/ 5V=/ 3V3=/"); // default menu screen (all outputs off)
LCDsetaddr(0x40);                //
LCDwrstring("-12V=/      MENU"); //

/***********************************************
* This loop scans the keyboard and the TMR0   *
* flag, and updates the screen/relay states   *
* accordingly                                 *
***********************************************/
while(1) {
if(SW3==1) {        // check to see if the move right key was pressed
__delay_ms(12); // debounce if it looks like it was pressed
if(SW3==1) {    // if it was pressed
LCDsetaddr(MenuItem.Addr[MenuItem.CurrentSelect]); // set the cursor to the current address
if(MenuItem.CurrentSelect==4)                      // if the current address is the menu option
LCDwrstring("MENU");                           //    set it to "MENU" to make sure it's not highlighted
else                                               //    when current selection is switched
LCDwrchar(MenuItem.ONorOFF[MenuItem.CurrentSelect] ? 0x00:0x2F); // else
//    make sure either the power symbol or the '/' symbol
//    aren't highlighted (according to if the voltage is
//    on or off)

MenuItem.CurrentSelect++;     // change where the cursor is
if(MenuItem.CurrentSelect==5) // if it equals five (i.e. if it's off the screen)
MenuItem.CurrentSelect=0; //     then move it to the first place on the screen
while(SW3==1);                // wait for the switch to be let off of before moving forward in the code
}
}

if(SW2==1) {        // check to see if the move left key was pressed
__delay_ms(12); // debounce if it looks like it was pressed
if(SW2==1) {    // if it was pressed
LCDsetaddr(MenuItem.Addr[MenuItem.CurrentSelect]); // set the cursor to the current address
if(MenuItem.CurrentSelect==4)                      // if the current address is the menu option
LCDwrstring("MENU");                           //    set it to "MENU" to make sure it's not highlighted
else                                               //    when current selection is switched
LCDwrchar(MenuItem.ONorOFF[MenuItem.CurrentSelect] ? 0x00:0x2F); // else
//    make sure either the power symbol or the '/' symbol
//    aren't highlighted (according to if the voltage is
//    on or off)

if(MenuItem.CurrentSelect==0) // if the current selection is zero
MenuItem.CurrentSelect=5; //     set it to 5 so a negative number is not created
MenuItem.CurrentSelect--;     // change where the cursor is
while(SW2==1);                // wait for the switch to be let off of before moving forward in the code
}
}

if(SW1==1) {        // check to see if the select key was pressed
__delay_ms(12); // debounce if it looks like it was pressed
if(SW1==1) {    // if it was pressed
if(MenuItem.CurrentSelect==4) { // if the current selection is "MENU"
while(SW1);                 // then wait for the switch to be let off of
RL12V=1;                    // turn off all relays
RLn12V=1;                   //
RL5V=1;                     //
RL3V3=1;                    //
if(OptMenu(COME_FROM_DEFAULT)==CALL_PAIR_MENU) // call menu function, and if pair menu was selected
PairMenu();             // call pair menu
return;                     // return - i.e. reset this function
}
else {                                                               // else
MenuItem.ONorOFF[MenuItem.CurrentSelect]=~MenuItem.ONorOFF[MenuItem.CurrentSelect]; // invert the current state
// of the selected voltage
LCDsetaddr(MenuItem.Addr[MenuItem.CurrentSelect]);               // set the cursor to the current selection
LCDwrchar(MenuItem.ONorOFF[MenuItem.CurrentSelect] ? 0x00:0x2F); // depending on what state the selections is
// being changed to, put up the correct symbol
switch(MenuItem.CurrentSelect) {                                 // this switch turns on/off the appropriate
case 0:                                                      // relay based upon the selection
RL12V=~MenuItem.ONorOFF[MenuItem.CurrentSelect];         //
break;                                                   //
case 1:                                                      //
RL5V=~MenuItem.ONorOFF[MenuItem.CurrentSelect];          //
break;                                                   //
case 2:                                                      //
RL3V3=~MenuItem.ONorOFF[MenuItem.CurrentSelect];         //
break;                                                   //
case 3:                                                      //
RLn12V=~MenuItem.ONorOFF[MenuItem.CurrentSelect];        //
break;                                                   //
}
}
while(SW1==1); // wait for the switch to be let off of before moving forward in the code
}
}

if(INTCONbits.TMR0IF) {           // if the TMR0 flag is set (i.e. the cursor needs to change state)
LCDsetaddr(MenuItem.Addr[MenuItem.CurrentSelect]); // set the cursor to the current address
if(MenuItem.CurrentSelect==4)                      // if it's on "MENU"
if(last)                                       //    invert it based upon it's last state
LCDwrstring("MENU");                       //
else {                                         //
LCDwrchar(0x02);                           //
LCDwrchar(0x03);                           //
LCDwrchar(0x04);                           //
LCDwrchar(0x05);                           //
}
else                                               // if it's on anything besides "MENU"
if(MenuItem.ONorOFF[MenuItem.CurrentSelect])   //    invert it based upon it's last state
LCDwrchar(last ? 0:1);                     //
else                                           //
LCDwrchar(last ? 0x2F:0x06);               //

last=last ? 0:1;     // invert the last variable so the last cursor state can be kept track of
INTCONbits.TMR0IF=0; // reset the TMR0 flag
}
}
while(1);
}

/***************************************************************
* OptMenu Function - This is the base menu function. It       *
* displays the options based upon where is was called from.   *
***************************************************************/
unsigned char OptMenu(unsigned char z)
{
/***********************************************
* Variable Definitions                        *
***********************************************/
int x;        // random support variable
int update=1; // this variable is set when the screen needs to be updated

const unsigned char AlternateDisplay[]="INDIV On/Off"; // alternate menu option

struct MenuDisplay {            //
unsigned char CurrentItem;  // holds the current selected option
unsigned char Items[3][13]; // holds the names of all the options
};
struct MenuDisplay Menu ={
0,                     // put zero in the current item variable
"Standby",             // name of option one
"All On/Off",          // name of option two (assuming it's not reloaded)
"Back"                 // name of option three
};

/***********************************************
* Main Code                                   *
***********************************************/
if(z==COME_FROM_PAIR)                         // if this function was called from the pair menu function
for(x=0;x<12;x++)                         // load the alternate menu option
Menu.Items[1][x]=AlternateDisplay[x]; //

LCDwrcmd(0b00000001); // clear display
__delay_ms(2);        // wait for the display to be cleared

while(1) { // main code loop

if(update){                                    // if the display needs to be updated
LCDsetaddr(0x00);                          // set the address to the start of the display
LCDwrchar(Menu.CurrentItem+1+0x30);        // write the current menu items number
LCDwrstring(": ");                         //
LCDwrstring(Menu.Items[Menu.CurrentItem]); // write the current option to the display
LCDwrstring("        ");                   // clear the rest of the line
LCDsetaddr(0x0F);                          // write the left arrow character on the last character space
LCDwrchar(0x7F);                           //
LCDsetaddr(0x40);                          // reset the address to the second line

if(Menu.CurrentItem<2) {                         // if the second option should not be a blank line
LCDwrchar(Menu.CurrentItem+2+0x30);          //     write the option after the current item to the second line
LCDwrstring(": ");                           //
LCDwrstring(Menu.Items[Menu.CurrentItem+1]); //
LCDwrstring("        ");                     //     clear the rest of the line
}                                                //
else                                             // else if the second line should be blank
LCDwrstring("                ");             //     clear the line
update=0;                                        // reset the update flag
}
if(SW3){                        // if the move down key was pressed
__delay_ms(12);             // debounce
if(SW3) {                   // if it's still pressed
if(Menu.CurrentItem==2) // if the current item is the last option
Menu.CurrentItem=0; //     reset to the first option
else                    // else
Menu.CurrentItem++; //     increment the current selection
update=1;               // set the update flag so the display will show the new change
while(SW3);             // wait for the switch to be let off of
}
}
if(SW2){                        // if the move up key was pressed
__delay_ms(12);             // debounce
if(SW2) {                   // if it's still pressed
if(Menu.CurrentItem==0) // if the current item is the first option
Menu.CurrentItem=2; //     set it to the last option
else                    // else
Menu.CurrentItem--; //     de-increment the current selection
update=1;               // set the update flag so the display will show the new change
while(SW2);             // wait for the switch to be let off of
}
}
if(SW1){                                    // if the select switch was pressed
__delay_ms(12);                         // debounce
if(SW1) {                               // if it's still pressed
while(SW1);                         // wait for it to be let off of
switch(Menu.CurrentItem) {          // see what is selected
case 0:                         // if it's the standby option
StandbyMenu();              //     call the standby function
return(0);                  //     return to the function that called this function
case 1:                         // if it's the All On/Off/INDIV On/Off option
if(z==COME_FROM_PAIR)       //     check what fucntion called this one
return(RETURN_TO_DEFAULT); //  if it was the pair menu return with a message to return to default
else                        //     if was called by the default menu function return with a message to
return(CALL_PAIR_MENU); //     call the pair menu function
case 2:                         // if it's the back option
return(0);                  //     return
}
}
}
}
return(0);
}

/***************************************************************
* PairMenu Function                                            *
***************************************************************/
void PairMenu()
{
/***********************************************
* Variable Definitions                        *
***********************************************/
static bit OnOrOff;       // 1 is on
static bit CurrentSelect; // if 1, then the selection is "MENU".
static bit Last;          // if 1, then the text is inverted

/***********************************************
* Setup the display and relays                *
***********************************************/
OnOrOff=0;       // clear each variable or it will have it's value from last time
CurrentSelect=0; //
Last=0;          //
RL5V=1;          // turn off all relays
RL12V=1;         //
RL3V3=1;         //
RLn12V=1;        //

LCDsetaddr(0x00);                // write the menu to the display
LCDwrstring("All Voltages:/  "); //
LCDsetaddr(0x40);                //
LCDwrstring("            MENU"); //

/***********************************************
* Main Code Loop                              *
***********************************************/
while(1) {
if(INTCONbits.TMR0IF) {          // if the blink flag is set
if(CurrentSelect) {          // if the current selection is "MENU"
LCDsetaddr(0x4C);        //     set the address to the start of the "MENU" text
if(Last) {               //     if the text should be inverted
LCDwrchar(0x02);     //         write "MENU" inverted to the display
LCDwrchar(0x03);     //
LCDwrchar(0x04);     //
LCDwrchar(0x05);     //
}                        //
else                     //     else
LCDwrstring("MENU"); //         write "MENU" not inverted on the display
}                            //
else {                       // else the selection is the power symbol
LCDsetaddr(0x0D);        //     set the address to where the power symbol is
if(Last)                 //     if it should be inverted
LCDwrchar(OnOrOff ? 0x01:0x06);//then write the right symbol based upon if the voltages are on or off
else                     //     else if it shouldn't be inverted
LCDwrchar(OnOrOff ? 0x00:'/');// then write the right symbol based upon if the voltages are on or off
}                            //
Last=~Last;                  // reset the last inverted state
INTCONbits.TMR0IF=0;         // clear the flag
}

if(SW3 || SW2) {                 // if the move up or move down button was pressed
__delay_ms(12);              // debounce
if(SW3 || SW2) {             // if there still pressed
if(CurrentSelect) {      // if the current selection is menu
LCDsetaddr(0x4C);    //     write "MENU" to make sure it's not inverted when the selection is changed
LCDwrstring("MENU"); //
}                        //
else {                   // else if the selection is the power symbol
LCDsetaddr(0x0D);    //     write the appropriate symbol to make sure it's not inverted when the selection is
LCDwrchar(OnOrOff ? 0x00:'/');//changed
}                        //

CurrentSelect=~CurrentSelect; // swap the current selection
}                                 //
while(SW3 || SW2);                // wait for the switch to be let off of
}

if(SW1) {                   // if the select switch was pressed
__delay_ms(12);         // debounce
if(SW1) {               // if SW1 is still pressed
LCDsetaddr(0x0D);   // set the address to where the power symbol is
if(CurrentSelect) { // if the current selection is "MENU"
while(SW1);     // wait for the switch to be let off of
if(OptMenu(COME_FROM_PAIR)==RETURN_TO_DEFAULT) { // if the user selects INDIV from OptMenu
RL5V=1;     // turn off all relays
RL12V=1;    //
RL3V3=1;    //
RLn12V=1;   //
return;     // return to the INDIV menu
}
LCDsetaddr(0x00);                // if the user wants to continue on this menu
LCDwrstring("All Voltages:");    //    redraw the menu
LCDwrchar(OnOrOff ? 0x00:'/');   //
LCDwrstring("  ");               //
LCDsetaddr(0x40);                //
LCDwrstring("            MENU"); //

}
else {                // if the current selection is the power symbol
RL5V=~RL5V;       // switch the state of all the relays
RL12V=~RL12V;     //
RL3V3=~RL3V3;     //
RLn12V=~RLn12V;   //
OnOrOff=~OnOrOff; // and switch the state of the OnOrOff variable
if(Last)          // redraw the display to reflect the changes
LCDwrchar(OnOrOff ? 0x01:0x06); //
else              //
LCDwrchar(OnOrOff ? 0x00:'/'); //
}                     //
while(SW1);           // wait for the switch to be let off of
}
}
}
return;
}
(This isn't the whole code, as there are a bunch of different files - they can be found on Github though)

Here's a photo of the power supply's main screen:

And the finished power supply above my desk:

I'm pretty happy about how it turned out. It doesn't look very good from the front though - I'll probably make a cover for it some time in the future.

=========================================
6/15/2016

I 3D printed a front cover for the power supply, and with a little paint it looks pretty good:

(The 3D model of it can also be found on the github project page)

Thanks for reading!!

P.S. All the design files for the project are on GitHub: https://github.com/Supercap2F/CPU-Power-Supply-Mod

Added by
Supercap2F
Views
3,638
Watchers
1
Last update