Superb Lab Power Supply

97,733

592

116

Introduction: Superb Lab Power Supply

From my point of view one of the best ways to get started in electronics is to build your own laboratory power supply. In this instructable I have tried to collect all the necessary steps so that anyone can construct his or her own.

All the parts of the assembly are directly orderable in digikey, ebay, amazon or aliexpress except the meter circuit. I made a custom meter circuit shield for Arduino able to measure up to 36V - 4A, with a resolution of 10mV - 1mA that can be used for other projects also.

The power supply has the following features:

  • Nominal Voltage: 24V.
  • Nominal Current: 3A.
  • Output Voltage Ripple: 0.01% (According to the specs of the power supply circuit kit).
  • Voltage measurement resolution: 10mV.
  • Current measurement resolution: 1mA.
  • CV and CC modes.
  • Over current protection.
  • Over voltage protection.

Step 1: Parts and Wiring Diagram

Apart from the Image, I have attached the file WiringAndParts.pdf to this step. The document describes all the functional parts, icluding the ordering link, of the bench power supply and how to connect them.

The mains voltage comes in through an IEC panel connector (10) that has a built in fussible holder, there is a power switch in the front panel (11) that breaks the circuit formed from the IEC connector to the transformer (9).

The transformer (9) outputs 21VAC. The 21 VAC go directly to the power supply circuit (8). The output of the power supply circuit (8) goes directly to the IN terminal of the meter circuit (5).

The OUT terminal of the meter circuit (5) is connected directly to the positive and negative binding posts (4) of the power supply. The meter circuit measures both voltage and current (high side), and can enable or disable the connection between in and out.

Cables, in general use scrap cables you have in house. You can check the internet for appropriate AWG gauge for 3A but, in general the thumb rule of 4A/mm² works, specially for short cables. For the mains voltage wiring (120V or 230V) use appropriately isolated cables, 600V in USA, 750V in Europe.

The series pass transistor of the power supply circuit (Q4) (12) has been wired instead of been soldered to allow an easy installation of the heatsink (13).

The original 10K potentiometers of the power supply circuit has been replaced with multiturn models (7), this makes possible a precise adjustment of the output voltage and current.

The arduino board of the meter circuit is powered using a power jack cable (6) that comes from the power supply circuit (8). The power supply board has been modified to obtain 12V instead of 24V.

The positive pin of the CC LED from the power supply circuit is wired to the mode connector of the Meter Circuit. This allow it to know when to display CC or CV mode.

There are two buttons wired to the meter circuit (3). The Off button “red” disconnects the output voltage. The On button “black” connects the output voltage and resets OV or OC errors.

There are two potentiometers wired to the meter circuit (2). One sets the OV threshold and the other sets the OC threshold. These potentiometers do not need to be multiturn, I have used the original potentiometers from the power supply circuit.

The 20x4 I2C alphanumeric LCD (1) is wired to the meter circuit. It shows the present information about output voltage, output current, OV setpoint, OC setpoint and status.

Step 2: Power Supply Circuit Kit

I bought this kit that is rated as 30V, 3A:
http://www.ebay.com/itm/Vkmaker-0-30V-2mA-3A-Adju...

I am attaching an assembly guide I found in the Internet and an image of the Schematic. Briefly:

The circuit is a linear power supply.

Q4 and Q2 are a Darlington array and form the series pass transistor, it is controlled by the operational amplifiers to maintain the voltage and the current at the desired value.

The current is measured by R7, adding this resistance in the low side makes the ground of the power supply circuit and the output ground different.

The circuit drives a LED that turns on when the constant current mode is on.

The circuit incorporates the Graeth bridge to rectify the AC input. The AC input is also used to generate a negative biasing voltage to reach 0V.

There is no thermal protection in this circuit, so appropriate dimensioning of the heatsink is very important.

The circuit has a 24V output for an “optional” fan. I have substituted the 7824 regulator with a 7812 regulator to get 12V for the Arduino board of the meter circuit.

I have not assembled the LED, instead I have used this signal to indicate the meter circuit if the power supply is in CC or CV.

Step 3: Power Supply Circuit Kit Assembling

In this circuit all parts are through hole. In general you must start with the smallest ones.

  • Solder all the resistors.
  • Solder the rest of the components.
  • Use pliers when bending diodes leads to avoid breaking them.
  • Bend the leads of the DIP8 TL081 op amps.
  • Use heatsink compound in when assembling heatsinks.

Step 4: Meter Circuit Design and Schematic

The circuit is a shield for Arduino UNO compatible with R3 versions. I have designed it with parts available at digikey.com.

The output of the vkmaker power supply circuit kit is connected to the IN terminal block and the OUT terminal block goes directly to the binding posts of the power supply.

R4 is a shunt resistor in the positive rail valued 0.01ohm, it has a voltage drop proportional to the current oputput. The differential voltage R4 is wired directly to RS+ and RS- pins of IC1. The maximum voltage drop at maximum current output is 4A*0.01ohm = 40mV.

R2, R3 and C2 form a ~15Hz filter to avoid noise.

IC1 is a high side current amplifier: MAX44284F. It is based in a chopped operational amplifier that makes it able to get a very low input offset voltage, 10uV at maximum at 25ºC. At 1mA the voltage drop in R4 is 10uV, equal the maximum input offset voltage.

The MAX44284F has a voltage gain of 50V/V so the output voltage, SI signal, at the maximum current of 4A, will value 2V.

The maximum common mode input voltage of MAX44284F is 36V, this limits the input voltage range to 36V.

R1 and C1 form a filter to suppress 10KHz and 20KHz unwanted signals that can appear due to the architecture of device, it is recommended in page 12 the of datasheet.

R5, R6 and R7 are a high impedance voltage divider of 0.05V/V. R7 with C4 form a ~5Hz filter to avoid noise. The voltage divider is placed after R4 to measure the real output voltage after the voltage drop.

IC3 is MCP6061T operational amplifier, it forms a voltage follower to isolate the high impedance voltage divider. The maximum input bias current is 100pA at room temperature, this current is negligible to the impedance of the voltage divider. At 10mV the voltage at the input of IC3 is 0.5mV, much bigger than its input offset voltage: 150uV at maximum.

The output of IC3, SV signal, has a voltage of 2V at 40V input voltage (the maximum possible is 36V due to IC1). SI and SV signals are wired to IC2. IC2 is an MCP3422A0, a dual channel I2C sigma delta ADC. It has an internal voltage reference of 2.048V, selectable voltage gain of 1, 2, 4, or 8V/V and selectable number of 12, 14, 16 or 18bits.

For this circuit I am using a fixed gain of 1V/V and a fixed resolution of 14bits. SV, and SI signals are not differential so the negative pin of each input must be grounded. That means that the number of available LSBs are going to be half.

As the internal voltage reference is 2.048V and the effective number of LSB are 2^13, the ADC values will be: 2LSB per each 1mA in the case of current and 1LSB per each 5mV in the case of voltage.

X2 is the connector for the ON push button. R11 prevents the Arduino pin input from static discharges and R12 is a pull-up resistor that makes 5V when unpressed and ~0V when pressed. I_ON signal.

X3 is the connector for the OFF push button. R13 prevents the Arduino pin input from static discharges and R14 is a pull-up resistor that makes 5V when unpressed and ~0V when pressed. I_OFF signal.

X5 is the connector for the overcurrent protection set point potentiometer. R15 prevents the Arduino input pin from static discharges and R16 prevents the +5V rail from a short circuit. A_OC signal.

X6 is the connector for the overvoltage protection set point potentiometer. R17 prevents the Arduino input pin from static discharges and R18 prevents the +5V rail from a short circuit. A_OV signal.

X7 ins an external input that is used to get the constant current or constant voltage mode of the power supply. As it can have many input voltages it is made using Q2, R19, and R20 as a voltage level shifter. I_MOD signal.

X4 is the connector of the external LCD, it is just a connection of the 5V rail, GND and I2C SCL-SDA lines.

I2C lines, SCL and SDA, are shared by IC2(the ADC) and the external LCD, they are pulled up by R9 and R10.

R8 and Q1 form the driver of K1 relay. K1 connects the output voltage when powered. With 0V in -CUT the relay is unpowered, and with 5V in -CUT the relay is powered. D3 is the free wheeling diode to suppress negative voltages when cutting the voltage of relay coil.

Z1 is a Transient Voltage Suppressor with a nominal voltage of 36V.

Step 5: Meter Circuit PCB

I have used the free version of Eagle for both the schematic and the PCB. The PCB is 1.6 thick double sided design that has a separate ground plane for the analog circuit and the digital circuit. The design is pretty simple. I got a dxf file from the Internet with the for the outline dimension and the position of the Arduino pinhead connectors.

I am posting the following files:

  • Original eagle files: 00002A.brd and 00002A.sch.
  • Gerber files: 00002A.zip.
  • And the BOM(Bill Of Materials) + assembly guide: BOM_Assemby.pdf.

I ordered the PCB to PCBWay (www.pcbway.com). The price was amazingly low: $33, including shipping, for 10 boards that arrived in less than a week. I can share the remaining boards with my friends or use them in other projects.

There is a mistake in the design, I put a via touching the silkscreen in the 36V legend.

Step 6: Meter Circuit Assembling

Although most of parts are SMT in this board, it can be assembled with a regular soldering iron. I have used a Hakko FX888D-23BY, fine tip tweezers, some solder wick, and a 0.02 solder.

  • After receiving the parts the best idea is to sort them, I have sorted capacitors and resistors and stapled the bags.
  • First assemble the small parts, starting with resistors and capacitors.
  • Assemble R4 (0R1) starting with one of the four leads.
  • Solder the rest of parts, in general for SOT23, SOIC8, etc. the best way is to apply solder in one pad first, solder the part in its place and then solder the rest of the leads. Sometimes solder can join many pads together, in this case you can use flux and solder wick to remove the solder and clean the gaps.
  • Assemble the rest of through hole components.

Step 7: Arduino Code

I have attached the file DCmeter.ino. All the program is included in this file apart from the LCD library “LiquidCrystal_I2C”. The code is highly customizable, especially the shape of progress bars and the messages displayed.

As all arduino codes it has the setup() function executed first time and the loop() function executed continuously.

The setup function configures the display, including the specials chars for the progress bar, inits the MCP4322 state machine and sets up the relay and the LCD backlight for first time.

There is no interrupts, in each iteration the loop function does the following steps:

Get the value of all the input signals I_ON, I_OFF, A_OC, A_OV and I_MOD. I_ON, and I_OFF are debounced. A_OC and A_OV are read directly from the Arduino´s ADC and filtered using the median part of the last three measurements. I_MOD is read directly without debouncing.

Control the turn on time of the backlight.

Execute the MCP3422 state machine. Each 5ms it polls the MCP3422 to see if the last conversion finished and if so it start the next, successively gets the value of voltage and current present at the output.

If there are fresh values of output voltage and current from the MCP3422 state machine, updates the status of the power supply based on the measurements and updates the display.

There is a double buffer implementation for faster updating the display.

The following macros can be adjusted for other projects:

MAXVP: Maximum OV in 1/100V units.

MAXCP: Maximum OC in 1/1000A units.

DEBOUNCEHARDNESS: Number of iterations with a consecutive value to guess it is correct for I_ON and I_OFF.

LCD4x20 or LCD2x16: Compilation for 4x20 or 2x16 display, the 2x16 option is not implemented yet.

The 4x20 implementation shows the following information: In the first row the output voltage and the output current. In the second row a progress bar representing the output value relative to protection set point for both voltage and current. Int the third row the current setpoint for overvoltage protection and overcurrent protection. In the fourth row the current status of the power supply: CC ON (On in constant current mode), CV ON (On in constant voltage mode), OFF, OV OFF (Off showing that the power supply went off because of a OV), OC OFF (Off showing that the power supply went off because of a OC).

I have made this file for designing the chars of the progress bars: https://drive.google.com/open?id=1ych5bmo9lfsu44W...

Step 8: Thermal Issues

Using the right heatsink is very important in this assembly because the power supply circuit is not self protected against overheat.

According to datasheet the 2SD1047 transistor has a junction to case thermal resistance of Rth-j,c = 1.25ºC/W.

According to this web calculator: http://www.myheatsinks.com/calculate/thermal-resi... the thermal resistance of the heatsink I have purchased is Rth-hs,air = 0.61ºC/W. I will assume that the actual value is lower because the heatsink is attached to the case and the heat can be dissipated that way too.

According to the ebay seller, the thermal conductivity of the isolator sheet I have purchased is K = 20.9W/(mK). With this, with a thickness of 0.6mm, the thermal resistance is: R = L/K = 2.87e-5(Km2)/W. So, the thermal resistance case to heatsink of the isolator for the 15mm x 15mm surface of the 2SD1047 is: Rth-c,hs = 0.127ºC/W. You can find a guide for these calculations here: http://www.myheatsinks.com/calculate/thermal-resi...

The maximum allowable power for 150ºC in the junction and 25ºC in the air is: P = (Tj - Ta) / (Rth-j,c + Rth-hs,air + Rth-c,hs) = (150 - 25) / (1.25 + 0.61 + 0.127) = 63W.

The output voltage of the transformer is 21VAC at full load, that makes an average of 24VDC after diodes and filtering. So the maximum dissipation will be P = 24V * 3A = 72W. Taking into account that the thermal resistance of the heatsink is a little bit lower due to the metal enclosure dissipation, I have assumed it is enough.

Step 9: Enclosure

The enclosure, including shipping, is the most expensive part of the power supply. I found this model in ebay, from Cheval, a Thay manufacturer: http://www.chevalgrp.com/standalone2.php. In fact, the ebay seller was from Thailand.

This box has a very good value for money and arrived pretty well packaged.

Step 10: Mechanizing Front Panel

The best option for mechanizing and engraving the front panel is using a router like this https://shop.carbide3d.com/products/shapeoko-xl-k... or making a custom plastic cover with PONOKO, for example. But as I do not have the router and I did not wanted to spend much money I decided to make it the old way: Cutting, trimming with file and using transfer letters for the text.

I have attached an Inkscape file with the stencil: frontPanel.svg.

  • Cut the stencil.
  • Cover the panel with painter tape.
  • Glue the stencil to the painter tape. I have used a glue stick.
  • Mark the position of drills.
  • Drill holes to allow the fret saw or coping saw blade get into the internal cuts.
  • Cut all the shapes.
  • Trim with a File. In the case of round holes for potentiometers and binding posts it is not necessary to use the saw before filing. In the case of the display hole the file trimming must be the best possible because this edges ar going to be seen.
  • Remove the stencil and the painter tape.
  • Mark the position of the texts with a pencil.
  • Transfer the letters.
  • Remove the pencil markings with an eraser.

Step 11: Mechanizing Back Pannel

  • Mark the position of the heatsink, including the hole for the power transistor and the position of the holding screws.
  • Mark the hole for accessing the heatsink from the interior of the power supply enclosure, I have used the insulator as a reference.
  • Mark the hole for the IEC connector.
  • Drill the contour of the shapes.
  • Drill the holes for the screws.
  • Cut the shapes with cutting pliers.
  • Trim the shapes with a file.

Step 12: Assembling Front Panel

  • Strip out a multiconductor cable from scrap to get cables.
  • Build the LCD assembly soldering the I2C to parallel interface.
  • Build the “molex connector”, wire and shrinkable tube assembly for: potentiometers, pushbuttons and LCD. Remove any protuberance in potentiometers.
  • Remove the pointer ring of knobs.
  • Cut the rod of potentiometers to the size of the knob. I have used a piece of cardboard as a gauge.
  • Attach the push buttons and power button.
  • Assemble the potentiometers and install the knobs, the multiturn potentiometers I have bought have a ¼ inch shaft and the one turn models have a 6mm shaft. I have used washers as spacers to trim the distance of potentiometers.
  • Screw the binding posts.
  • Put double sided tape in the LCD and stick it to the panel.
  • Solder the positive and negative wires to the binding posts.
  • Assemble the GND terminal lug in the green binding post.

Step 13: Assembling Back Panel

  • Screw the heatsink to the back panel, although paint is a thermal isolator, I have put heatsink grease to increase the heat transfer from the heatsink to the enclosure.
  • Assemble the IEC connector.
  • Position the adhesive spacers using the power supply kit circuit.
  • Screw the power transistor and the insulator, there must be thermal grease in each surface.
  • Assemble the 7812 for powering the arduino, it is facing the case to allow heat dissipation, using one of the screws that hold the heatsink. I should have used a plastic washer like this http://www.ebay.com/itm/100PCS-TO-220-Transistor-... but I ended up using the same insulator as the power transistor and a bent piece of the case.
  • Wire the power transistor and the 7812 to the power supply circuit.

Step 14: Final Assembly and Wiring

  • Mark and drill the holes for the transformer.
  • Assemble the transformer.
  • Stick the adhesive legs of the enclosure.
  • Stick the DC meter circuit using adhesive spacers.
  • Scrape the paint to screw the GND lug.
  • Build the mains voltage wire assemblies, all the terminations are 3/16” Faston. I have used shrinkable tube to isolate the terminations.
  • Cut the front part of the holder of the enclosure in the right side to get space for the power pushbutton.
  • Connect all wires according to assembly guide.
  • Instal the Fuse (1A).
  • Put the output voltage potentiometer (the VO potentiometer), to the minimum CCW and adjust the output voltage the closest possible to zero volts using the multiturn fine adjusting potentiometer of the vkmaker power supply circuit.
  • Assemble the enclosure.

Step 15: Improvements and Further Working

Improvements:

  • Use grower style washers to avoid screws get loose with vibration, specially the vibration from the transformer.
  • Paint the front panel with transparent varnish to prevent letters to be wiped out.

Further working:

  • Add a usb connector like this: http://www.ebay.com/itm/Switchcraft-EHUSBBABX-USB-... in the back panel. Useful for upgrading code without disassembly or for making a small ATE controlling the On Off functions, get status and measuring using a PC.
  • Make the 2x16 LCD compilation of code.
  • Make a new power supply circuit, instead of using the vkmaker kit, with digital control of the output voltage and current.
  • Perform the adequate tests to characterize the power supply.

Power Supply Contest

First Prize in the
Power Supply Contest

2 People Made This Project!

Recommendations

  • For the Home Contest

    For the Home Contest
  • Big and Small Contest

    Big and Small Contest
  • Game Design: Student Design Challenge

    Game Design: Student Design Challenge

116 Comments

0
elamurugu1959
elamurugu1959

Tip 8 days ago

Very nice circuit I,like this.

0
vhalulic
vhalulic

18 days ago

Hi Daniel.
Yours VA meter is exellent. but U/I regulator is to bad.

0
pityka72
pityka72

3 months ago

Hello!
Can someone help? We really need the LiquidCrystal_I2C library. Maybe someone can share it? Thanks!

0
Kraythorne
Kraythorne

Question 2 years ago

How would you adapt this design to give you positive and negative outputs (i.e. as used by Op Amps and some ICs). Would a variable -24/0/+24 output be achievable?

0
karunart
karunart

2 years ago

I assembled but Can not get LCD to display any characters .
Both 1602 and 2004 lcds behave same. Evan though I change the address to 0x27
Lcds get initialized but no charactors on display.

I manged to fix the issue, It was a problem with ADC (Without working ADC the display is blank).

1
MickM51
MickM51

Question 3 years ago

Hi
Ive built your 0-30V 3Amp PSU design and i love it.

Some of the changes ive made is to add a

3D printed dual 40mm fan duct to cool the heatsink (i found the heatsink was getting really really hot if running a load at over 2 amps)

2 X 2SD1047 transistors in parallel to control the output volt/amp (again to help with the heat when running a load over 2 amps )

ive added a LM35 temp sensor to the heatsink and connected it to Arduino pin A2, but im struggling to add the sensor to the code.

I want to move the "OFF", "CV ON" "CV OFF" text left so its next to the STATUS text "example= STATUS: CV OFF" and then have the LM35 temp value displayed 4th row right justified in the space where the CV OFF text was.

I would also like to then add code to disable the power output relay if the LM35 temp value goes above 60 deg centigrade.

Can you guide/help me please to making these changes

Regards
Mick

PSU-Pic1.jpgPSU-Pic2.jpg
2
MickM51
MickM51

Answer 3 years ago

Worked it out after scratching my head for a while, if anyone would like my code changes, LM35 temp sensor mount .stl and the .stl files for the heatsink fan duct just let me know.
Fixes / Mods
1. Fixed OVP and OCP potentiometer range
2. Fixed "Output OFF" button not responding if OVP setting below 17.9 volts
3. installed LM35 temp sensor to Arduino ADC input 2
4. designed LM35 heatsink mount and 40mm dual fan duct to cool heatsink as overheats above 2amps on output
5. Added temp display to LCD display (see picture, bottom left of display)

IMG_2.jpg
0
danielrp
danielrp

Reply 2 years ago

Hi MickM51.
The mods are really nice, I am really impressed with the fans duct. Sorry for not answering before about the FW mods, I've had very busy times recently. Anyways, you didn't need any help, congrats!! I noticed the OVP problem with the OFF button but didn't fix it yet.
Daniel.

0
MickM51
MickM51

Reply 2 years ago

Hi Danielrp
Below is my mod'd code if it helps anyone

#include <Wire.h>
#include <LiquidCrystal_I2C.h> //Library: https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads/
//Display: http://www.ebay.com/itm/Yellow-2004-20X4-IIC-I2C-TWI-Character-LCD-Display-Module-For-Arduino-/172181792364?hash=item2816d5a66c:g:VooAAOSwMVFXIC-3
/////////////////////////////////////////////////////////////////////////////////////
//Defines:
/////////////////////////////////////////////////////////////////////////////////////
//Conditional build:
#define LCD4x20
//#define LCD2x16
//LCD display parameters
#ifdef LCD4x20
#define N_ROWS 4u
#define N_COLS 20u
#endif //LCD4x20
#ifdef LCD2x16
#define N_ROWS 2u
#define N_COLS 16u
#endif //LCD4x20
#define LCD_TBL 300000u //Time that backlight remain active
#define TURN_OFF_BACKLIGHT
//General parameters
#define MAXVP 2800u //Maximum OV in 1/100V units
#define MAXCP 3100u //Maximum OC in 1/1000A units
#define DEBOUNCEHARDNESS 10u
//Macros
//Relay
#define RELAY_ON digitalWrite(2, HIGH); \
pinMode(2, OUTPUT)
#define RELAY_OFF digitalWrite(2, LOW); \
pinMode(2, OUTPUT)
#define IS_RELAY_ON (HIGH == digitalRead(2))
//Aruduino builtin LED
#define STATUS_ON digitalWrite(LED_BUILTIN, HIGH); \
pinMode(LED_BUILTIN, OUTPUT)
#define STATUS_OFF digitalWrite(LED_BUILTIN, LOW); \
pinMode(LED_BUILTIN, OUTPUT)
//MODE Pin
#define INIT_MODE pinMode(12, INPUT) //Config MODE pin
#define IS_MODE_CV (HIGH == digitalRead(12)) //true if mode CV
//ON pushbutton
#define INIT_PON pinMode(4, INPUT) //Config ON pushbutton pin
#define IS_PON_ON (LOW == digitalRead(4)) //true if ON pushed
//OFF pushbutton
#define INIT_POFF pinMode(7, INPUT) //Config OFF pushbutton pin
#define IS_POFF_ON (LOW == digitalRead(7)) //true if OFF pushed
//OC potentiometer
#define READ_ADC_CP() analogRead(0)
//OV potentiometer
#define READ_ADC_VP() analogRead(1)
/* Define the analogue pin used to read the temperature sensor (A0) */
#define LM35Pin 2
/* Stores the current temperature reading */
//MCP3422
#define MCP3422_CH1 0x00
#define MCP3422_CH2 0x01
//#define MCP3422_SR 0x00 //240 SPS (12bit)
#define MCP3422_SR 0x01 //60 SPS (14bit)
//#define MCP3422_SR 0x02 //15 SPS (16bit)
//#define MCP3422_SR 0x03 //3.75 SPS (18bit)
#define MCP3422_GAIN 0x00 //x1
//#define MCP3422_GAIN 0x01 //x2
//#define MCP3422_GAIN 0x02 //x4
//#define MCP3422_GAIN 0x03 //x8
#define MCP3422_CR_STARTONESHOT 0x80 // /RDY bit = 1, /O/C bit = 0
#define MCP3422_CR_READY 0x80 // /RDY bit mask
#define MCP3422_NCH 2u //Number of channels available
#define MCP3422_ADD 0x68 //Slave address
#define MCP3422_TP 5u //Number of msec between pollings to MCP3422
/////////////////////////////////////////////////////////////////////////////////////
//Constants:
/////////////////////////////////////////////////////////////////////////////////////
//Special chars for progress bar
const uint8_t a6x8u8ProgressBarChars[6][8] =
{
{0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00},
{0x00, 0x00, 0x10, 0x10, 0x10, 0x15, 0x00, 0x00},
{0x00, 0x00, 0x08, 0x08, 0x08, 0x1D, 0x00, 0x00},
{0x00, 0x00, 0x04, 0x04, 0x04, 0x15, 0x00, 0x00},
{0x00, 0x00, 0x02, 0x02, 0x02, 0x17, 0x00, 0x00},
{0x00, 0x00, 0x01, 0x01, 0x01, 0x15, 0x00, 0x00}
};
//Status messages
const char strStatusCC_ON[6] = {"CC ON"};
const char strStatusCV_ON[6] = {"CV ON"};
const char strStatusOFF[4] = {"OFF"};
const char strStatusOV_OFF[7] = {"OV OFF"};
const char strStatusOC_OFF[7] = {"OC OFF"};
/////////////////////////////////////////////////////////////////////////////////////
//Types:
/////////////////////////////////////////////////////////////////////////////////////
//MCP3422 machine states
enum eMCP3422ReadStates_t
{
eMS_WaitCurrent = 0,
eMS_WaitVoltage = 1
};
//Status values
enum eStatusValues_t
{
eStatus_CC_ON = 0,
eStatus_CV_ON = 1,
eStatus_OFF = 2,
eStatus_OV_OFF = 3,
eStatus_OC_OFF = 4
};
/////////////////////////////////////////////////////////////////////////////////////
//Variables:
/////////////////////////////////////////////////////////////////////////////////////
//Display
//Double buffer..
#ifdef LCD4x20
char displayBufferA[N_ROWS][N_COLS] =
{
{'V', 'O', '=', '0', '0', '.', '0', '0', 'V', ' ', ' ', 'C', 'O', '=', '0', '.', '0', '0', '0', 'A'},
{'0', '0', '0', '0', '0', '0', '0', '0', '0', ' ', ' ', '0', '0', '0', '0', '0', '0', '0', '0', '0'},
{'O', 'V', 'P', '=', '0', '.', '0', '0', 'V', ' ', ' ', 'O', 'C', 'P', '=', '0', '.', '0', '0', 'A'},
{'T', 'M', 'P', '=', ' ', ' ', ' ', ' ', 'C', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'O', 'F', 'F'}
};
char displayBufferB[N_ROWS][N_COLS]=
{
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'}
};
#endif //LCD4x20
#ifdef LCD2x16
char displayBufferA[N_ROWS][N_COLS] =
{// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
{'V', '=', '0', '0', '.', '0', '0', ' ', '0', '0', '0', '0', '0', '0', '0', '0'},
{'C', '=', '0', '.', '0', '0', '0', ' ', '0', '0', '0', '0', '0', '0', '0', '0'}
};
char displayBufferB[N_ROWS][N_COLS]=
{
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'}
};
#endif //LCD4x20
//Display class
#ifdef LCD4x20
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Addr, En, Rw, Rs, d4, d5, d6, d7, backlighpin, polarity (As descrived in the ebay link but with 0x3F address instead of 0x20)
#endif //LCD2x16
#ifdef LCD2x16
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Addr, En, Rw, Rs, d4, d5, d6, d7, backlighpin, polarity (As descrived in the ebay link but with 0x3F address instead of 0x20)
#endif //LCD2x16
uint32_t u32BackLightTimer = 0; //Timer for backlight
bool bBackLightOn = false; //Backlight state
//MCP3422 state machine
uint32_t u32MCP3422Timer = 0; //Polling timer
eMCP3422ReadStates_t MCP3422States = eMS_WaitCurrent;
//Status value
eStatusValues_t eStatusValue = eStatus_OFF;
//General variables
uint16_t u16VOValue = 0; //Voltage output value
uint16_t u16COValue = 0; //Current output value
uint16_t u16VPValue = 0; //Voltage protect value
uint16_t u16CPValue = 0; //Current protect value
uint16_t u16HSTValue = 0; //Heatsink temp value
uint16_t a3u16VP[3u] = {0, 0, 0}; //Array for filtering voltage protect
uint16_t a3u16CP[3u] = {0, 0, 0}; //Array for filtering current protect
uint8_t u8Index_a3u16VP = 0; //Index of a3u16VP
uint8_t u8Index_a3u16CP = 0; //Index of a3u16CP
bool bOnPressed = false; //State of On push button
bool bLastOnPressed = false; //Last state of On push button, used for flange detection
bool bOffPressed = false; //State of Off push button
bool bLastOffPressed = false; //Last state of Off push button, used for flange detection
uint8_t u8OnDebounce = 10; //Debounce counter for On push button
uint8_t u8OffDebounce = 10; //Debounce counter for Off push button
bool bMODECV = false; //Mode of power supply, true: constant voltage, false: constant current
uint32_t u32MCP3422Value = 0; //Last conversion value from MCP3422
bool bMCP3422ValIsPositive = true; //Last conversion sign, in this circuit it's always positive
bool bValuesOK = false; //Indicates in the main loop if the last execution of MCP3422_Machine updated u16VOValue and u16COValue
/////////////////////////////////////////////////////////////////////////////////////
//First execution:
/////////////////////////////////////////////////////////////////////////////////////
void setup()
{
RELAY_OFF;
setupDisplay();
Wire.setClock(400000); //Speed for both display and MCP3422
restartBackLight();
MCP3422_MachineInit();
/* Set the analogue reference used by the ADC inputs
to the internal 1.1V reference */
analogReference(DEFAULT);
//Serial.begin(9600); // ==>> activate for debug
}
/////////////////////////////////////////////////////////////////////////////////////
//Loop..
/////////////////////////////////////////////////////////////////////////////////////
void loop()
{
updateInputs();
refreshBackLight();
bValuesOK = MCP3422_Machine();
if(true == bValuesOK)
{
updateStatus();
updateDisplayBuffer();
updateDisplay();
}
hstemp();
}
void hstemp(void)
{
float HeatSinkAverage = 0;
for (int hsi=0; hsi < 50; hsi++) {
HeatSinkAverage = HeatSinkAverage + (analogRead(LM35Pin) / 1024.0 *5000 / 10);
}
u16HSTValue = HeatSinkAverage / 50 * 100;
}
/////////////////////////////////////////////////////////////////////////////////////
//Functions:
/////////////////////////////////////////////////////////////////////////////////////
//Starts a one shot conversion in MCP3422. Gain and sample rate/nbits are giben in defines.
//u8Channel: Chanel of MCP3422 to be read
//return: true if every thing was ok
bool MCP3422_StartConversion(uint8_t u8Channel)
{
bool bIsOK = true;
uint8_t u8ConfigRegister = MCP3422_CR_STARTONESHOT | (MCP3422_SR << 2u) | MCP3422_GAIN; //Set defined parameters
//Check channel range
if((MCP3422_NCH - 1u) < u8Channel)
{
bIsOK = false;
}
if(true == bIsOK)
{
//Set channel
u8ConfigRegister |= (u8Channel << 5u);
//Write Config Register
//Wire.begin();
Wire.beginTransmission(MCP3422_ADD);
Wire.write(u8ConfigRegister);
if (0 != Wire.endTransmission()) //0 = success in Wire library
{
bIsOK = false;
}
}
return bIsOK;
}
//Checks if the last conversion in MCP3422 is finished, if so updates the output value.
//u32Value: Output variable for ADC value.
//bIsPositive: Output variable for sign: true = positive
//return: true if the conversion was finished and there is data available
bool MCP3422_IsDataAndRead(uint32_t &u32Value, bool &bIsPositive)
{
bool bIsData = false;
uint8_t u8ByteCounter = 0;
uint8_t a4u8RecData[4];
//Wire.begin();
Wire.requestFrom(MCP3422_ADD, 4u);
a4u8RecData[3] |= MCP3422_CR_READY; //Force to NOT ready
for (u8ByteCounter = 0; 4u > u8ByteCounter; u8ByteCounter++)
{
a4u8RecData[u8ByteCounter] = Wire.read();
}
if(MCP3422_CR_READY != (MCP3422_CR_READY & a4u8RecData[3]))
{//RDY bit = 0 -> conversion finished, data is valid
bIsData = true;
}
if(true == bIsData)
{
//Formatting according to page 22 of datasheet
//Mode 12 bit
#if(0x00 == MCP3422_SR)
if(0x08 == (0x08 & a4u8RecData[0]))
{
bIsPositive = false;
}
else
{
bIsPositive = true;
}
u32Value = ((a4u8RecData[0] & 0x07) << 8u) | a4u8RecData[1];
if(false == bIsPositive)
{
u32Value = ~u32Value - 1; //Two's complement
}
#endif //(0x00 == MCP3422_SR)
//Mode 14 bit
#if(0x01 == MCP3422_SR)
if(0x20 == (0x20 & a4u8RecData[0]))
{
bIsPositive = false;
}
else
{
bIsPositive = true;
}
u32Value = ((a4u8RecData[0] & 0x1F) << 8u) | a4u8RecData[1];
if(false == bIsPositive)
{
u32Value = ~u32Value - 1; //Two's complement
}
#endif //(0x01 == MCP3422_SR)
//Mode 16 bit
#if(0x02 == MCP3422_SR)
if(0x80 == (0x80 & a4u8RecData[0]))
{
bIsPositive = false;
}
else
{
bIsPositive = true;
}
u32Value = ((a4u8RecData[0] & 0x7F) << 8u) | a4u8RecData[1];
if(false == bIsPositive)
{
u32Value = ~u32Value - 1; //Two's complement
}
#endif //(0x02 == MCP3422_SR)
//Mode 18 bit
#if(0x03 == MCP3422_SR)
if(0x02 == (0x02 & a4u8RecData[0]))
{
bIsPositive = false;
}
else
{
bIsPositive = true;
}
u32Value = ((a4u8RecData[0] & 0x01) << 16u) | (a4u8RecData[1] << 8u) | a4u8RecData[2];
if(false == bIsPositive)
{
u32Value = ~u32Value - 1; //Two's complement
}
#endif //(0x03 == MCP3422_SR)
}
return bIsData;
}
//Inits the MCP3422 state machine, must be executed before the periodic execution of MCP3422_Machine
void MCP3422_MachineInit(void)
{
MCP3422_StartConversion(MCP3422_CH1); //Start reading the output current value
MCP3422States = eMS_WaitCurrent; //Next state, wait for current to be read
u32MCP3422Timer = millis(); //Update the counter for first timming
}
//Checks if the last conversion in MCP3422 is finished, if so updates the output value.
//u32Value: Output variable for ADC value.
//bIsPositive: Output variable for sign: true = positive
//return: true if the conversion of voltage and current was finished and there is data available
bool MCP3422_Machine(void)
{
bool bCurrentVoltageMeasured = false;
if(millis() > (u32MCP3422Timer + MCP3422_TP))
{//MCP3422_TP msec elapsed
u32MCP3422Timer = millis();
switch(MCP3422States)
{
case eMS_WaitCurrent:
{
if(MCP3422_IsDataAndRead(u32MCP3422Value, bMCP3422ValIsPositive))
{//Conversion ended, value and next state
u16COValue = (uint16_t)(u32MCP3422Value >> 1); //Save output current value (dividing by 2 gets 1/1000A units)
MCP3422_StartConversion(MCP3422_CH2); //Start reading the output voltage value
MCP3422States = eMS_WaitVoltage; //Next state, wait for voltage to be read
}
break;
}
case eMS_WaitVoltage:
{
if(MCP3422_IsDataAndRead(u32MCP3422Value, bMCP3422ValIsPositive))
{//Conversion ended, update value and next state
u16VOValue = (uint16_t)(u32MCP3422Value >> 1); //Save output voltage value (dividing by 2 gets 1/100V units)
MCP3422_StartConversion(MCP3422_CH1), //Start reading the output current value
MCP3422States = eMS_WaitCurrent; //Next state, wait for current to be read
bCurrentVoltageMeasured = true; //in this execution u16VOValue and u16COValue are fresh
}
break;
}
}
}
return bCurrentVoltageMeasured;
}
//Updates the state and value of all inputs except MCP3422
void updateInputs(void)
{
//Init inputs
INIT_MODE;
INIT_PON;
INIT_POFF;
//Debouncing of pushbuttons...
//On
if(IS_PON_ON)
{
if(DEBOUNCEHARDNESS <= u8OnDebounce)
{//DEBOUNCEHARDNESS times consecutively read true
bOnPressed = true;
}
else
{
u8OnDebounce++;
}
}
else
{
u8OnDebounce = 0;
bOnPressed = false;
}
//Off
if(IS_POFF_ON)
{
if(DEBOUNCEHARDNESS <= u8OffDebounce)
{//DEBOUNCEHARDNESS times consecutively read true
bOffPressed = true;
}
else
{
u8OffDebounce++;
}
}
else
{
u8OffDebounce = 0;
bOffPressed = false;
}
//Update mode CC or CV directly reading the input pin
if(IS_MODE_CV)
{
bMODECV = true;
}
else
{
bMODECV = false;
}
//Voltage protect. Read the arudino adc input, scale it to MAXVP, update the circular stack of three with the last value.
a3u16VP[u8Index_a3u16VP] = (uint16_t)((float)(READ_ADC_VP()) * (((float)(MAXVP))/965.0)); //Analog in travel ~(0 - 965)lsb. At 965 -> MAXVP
if(MAXVP < a3u16VP[u8Index_a3u16VP]) //Limit
{
a3u16VP[u8Index_a3u16VP] = MAXVP;
}
u8Index_a3u16VP ++;
if(3u < u8Index_a3u16VP)
{
u8Index_a3u16VP = 0;
}
//Voltage protect. Read the mid point from the circular stack.
u16VPValue = getMidPointOfThreeuInt16(a3u16VP);
//Current protect. Read the arudino adc input, scale it to MAXCP, update the circular stack of three with the last value.
a3u16CP[u8Index_a3u16CP] = (uint16_t)((float)(READ_ADC_CP()) * (((float)(MAXCP))/965.0)); //Analog in travel ~(0 - 965)lsb. At 965 -> MAXCP
if(MAXCP < a3u16CP[u8Index_a3u16CP]) //Limit
{
a3u16CP[u8Index_a3u16CP] = MAXCP;
}
u8Index_a3u16CP ++;
if(3u < u8Index_a3u16CP)
{
u8Index_a3u16CP = 0;
}
//Current protect. Read the mid point from the circular stack.
u16CPValue = getMidPointOfThreeuInt16(a3u16CP);
}
//Setup LCD i2c address, LCD format, set up progress bar chars.
void setupDisplay(void)
{
uint8_t charIndex = 0;
lcd.begin(N_COLS, N_ROWS);
//Write progress bar chars.
for(charIndex = 0; charIndex < 6; charIndex++)
{
lcd.createChar(charIndex, (uint8_t *)a6x8u8ProgressBarChars[charIndex]);
}
}
//Turns the backlight on if it was off and reload the timing
void restartBackLight(void)
{
if(false == bBackLightOn)
{//Turn on backlight if it was off
lcd.setBacklight(HIGH);
}
bBackLightOn = true;
//Reload the timer
u32BackLightTimer = millis() + LCD_TBL;
}
//Automatic turn off backlight, must be executed periodically
void refreshBackLight(void)
{
if(true == bBackLightOn)
{
if(millis() > u32BackLightTimer)
{//Turn off backlight if it was on and the time elapsed
bBackLightOn = false;
#ifdef TURN_OFF_BACKLIGHT
lcd.setBacklight(LOW);
#endif
}
}
}
//Updates eStatusValue, turns on or off the relay and modifies displayBufferA
//depending on current values of measurements.
void updateStatus(void)
{
eStatusValues_t eLastStatusValue = eStatusValue;
if((false == bLastOffPressed) && (true == bOffPressed))
{//Rising flange in Off pushbutton
RELAY_OFF; //Turn off output
eStatusValue = eStatus_OFF;
restartBackLight(); //Backlight on with any pulsation of on or off
}
else if((false == bLastOnPressed) && (true == bOnPressed))
{//Rising flange in On pushbutton
RELAY_ON; //Turn on output
restartBackLight(); //Backlight on with any pulsation of on or off
}
bLastOnPressed = bOnPressed; //Update last state
bLastOffPressed = bOffPressed; //Update last state
if(u16VOValue > u16VPValue)
{//Overvoltage
eStatusValue = eStatus_OV_OFF;
RELAY_OFF; //Turn off output
}
if(u16COValue > u16CPValue)
{//Overvoltage
eStatusValue = eStatus_OC_OFF;
RELAY_OFF; //Turn off output
}
if(IS_RELAY_ON)
{//CC or CV
if(bMODECV)
{
eStatusValue = eStatus_CV_ON;
}
else
{
eStatusValue = eStatus_CC_ON;
}
}
//Write message in displayBufferA
switch(eStatusValue)
{
case eStatus_OFF:
{
writeStringStatus(strStatusOFF);
break;
}
case eStatus_CV_ON:
{
writeStringStatus(strStatusCV_ON);
break;
}
case eStatus_CC_ON:
{
writeStringStatus(strStatusCC_ON);
break;
}
case eStatus_OV_OFF:
{
writeStringStatus(strStatusOV_OFF);
break;
}
case eStatus_OC_OFF:
{
writeStringStatus(strStatusOC_OFF);
break;
}
}
if(eLastStatusValue != eStatusValue)
{//Backlight on if there has been a change in status
restartBackLight();
}
}
//Modifies displayBufferA with current values of measurements
void updateDisplayBuffer(void)
{
writeN_DOT_NNN(u16COValue, 0u, 14u);
writeNN_DOT_NN(u16VOValue, 0u, 3u);
writeN_DOT_NN(u16CPValue/10u, 2u, 15u);
writeNN_DOT_N(u16VPValue/10u, 2u, 4u);
writeNN_DOT_N(u16HSTValue/10u, 3u, 4u);
drawProgressBar(u16CPValue, u16COValue, 1u, 11u, 9u);
drawProgressBar(u16VPValue, u16VOValue, 1u, 0u, 9u);
}
//Browses displayBufferA. If any character is different in displayBufferA than displayBufferB
//writes it to displayBufferB and the LCD. Updates the possition of the cursor when neccesBary.
void updateDisplay(void)
{
uint8_t u8Rows = 0;
uint8_t u8Columns = 0;
bool bMustSetCuror = true;
for(u8Rows = 0; u8Rows < N_ROWS; u8Rows++)
{
for(u8Columns = 0; u8Columns < N_COLS; u8Columns++)
{
//Enters here for each character in displayBufferA
if(displayBufferA[u8Rows][u8Columns] != displayBufferB[u8Rows][u8Columns])
{
//A new character must be written
if(true == bMustSetCuror)
{
//Cursor must be updated because last character was not written
lcd.setCursor(u8Columns, u8Rows);
}
bMustSetCuror = false;
lcd.write(displayBufferA[u8Rows][u8Columns]);
displayBufferB[u8Rows][u8Columns] = displayBufferA[u8Rows][u8Columns];
}
else
{
//No character to be written
bMustSetCuror = true;
}
}
//Update cursor in new row
bMustSetCuror = true;
}
}
//Writes strText right justified in the last line.
//strText: text to write must be a c string with null terminator.
void writeStringStatus(const char* strText)
{
uint8_t u8Size = 0;
uint8_t u8DisplayPos = 9u; //Next char after STATUS:
//1º Clear the text
for(; N_COLS > u8DisplayPos; u8DisplayPos++)
{
displayBufferA[3u][u8DisplayPos] = ' ';
}
//2º Write the text
u8DisplayPos = 19u; //Starts at the end of the line
u8Size = strlen(strText); //Get the size of the string
do
{
u8Size--;
displayBufferA[3u][u8DisplayPos] = strText[u8Size];
u8DisplayPos--;
}while((0 != u8Size) && (7u <= u8DisplayPos)); //Ends at the end of string or before reaching "STATUS:" string
}
//Writes a N.NNN fixed dot number in displayBufferA.
//u16Value: Number to be written, 0 = 0.000 to 9999 = 9.999.
//u8Line: Start possition line in displayBufferA.
//u8Column: Start possition column in displayBufferA.
void writeN_DOT_NNN(uint16_t u16Value, uint8_t u8Line, uint8_t u8Column)
{
//Limit
if(9999u < u16Value)
{
u16Value = 9999u;
}
displayBufferA[u8Line][u8Column + 4] = (u16Value % 10) + 0x30; //4º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 3] = (u16Value % 10) + 0x30; //3º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 2] = (u16Value % 10) + 0x30; //2º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 1] = '.'; //Dot
displayBufferA[u8Line][u8Column] = (u16Value % 10) + 0x30; //1º digit
}
//Writes a NN.NN fixed dot number in displayBufferA.
//u16Value: Number to be written, 0 = 00.00 to 9999 = 99.99.
//u8Line: Start possition line in displayBufferA.
//u8Column: Start possition column in displayBufferA.
void writeNN_DOT_NN(uint16_t u16Value, uint8_t u8Line, uint8_t u8Column)
{
//Limit
if(9999u < u16Value)
{
u16Value = 9999u;
}
displayBufferA[u8Line][u8Column + 4] = (u16Value % 10) + 0x30; //4º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 3] = (u16Value % 10) + 0x30; //3º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 2] = '.'; //Dot
displayBufferA[u8Line][u8Column + 1] = (u16Value % 10) + 0x30; //2º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column] = (u16Value % 10) + 0x30; //1º digit
}
//Writes a NN.N fixed dot number in displayBufferA.
//u16Value: Number to be written, 0 = 00.0 to 999 = 99.9.
//u8Line: Start possition line in displayBufferA.
//u8Column: Start possition column in displayBufferA.
void writeNN_DOT_N(uint16_t u16Value, uint8_t u8Line, uint8_t u8Column)
{
//Limit
if(999u < u16Value)
{
u16Value = 999u;
}
displayBufferA[u8Line][u8Column + 3] = (u16Value % 10) + 0x30; //3º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 2] = '.'; //Dot
displayBufferA[u8Line][u8Column + 1] = (u16Value % 10) + 0x30; //2º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column] = (u16Value % 10) + 0x30; //1º digit
u16Value /= 10u;
}
//Writes a N.NN fixed dot number in displayBufferA.
//u16Value: Number to be written, 0 = 0.00 to 999 = 9.99.
//u8Line: Start possition line in displayBufferA.
//u8Column: Start possition column in displayBufferA.
void writeN_DOT_NN(uint16_t u16Value, uint8_t u8Line, uint8_t u8Column)
{
//Limit
if(999u < u16Value)
{
u16Value = 999u;
}
displayBufferA[u8Line][u8Column + 3] = (u16Value % 10) + 0x30; //3º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 2] = (u16Value % 10) + 0x30; //2º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 1] = '.'; //Dot
displayBufferA[u8Line][u8Column] = (u16Value % 10) + 0x30; //1º digit
u16Value /= 10u;
}
//Draws a progress bar in displayBufferA.
//u16Top: Full scale value.
//u16Value: Progress relative to u16Top.
//u8Line: Start possition line in displayBufferA.
//u8Column: Start possition column in displayBufferA.
//u8Size: Number of digits used by the progress bar.
void drawProgressBar(uint16_t u16Top, uint16_t u16Value, uint8_t u8Line, uint8_t u8Column, uint8_t u8Size)
{
// Progress bar characters:
// 0 1 2 3 4 5
//
//
// X X X X X
// X X X X X
// X X X X X
// X X X X X X X X X X X X X X X X X X
//
//
uint16_t u16NumberOfPositions = 0;
uint16_t u16Position = 0; //Position within the current number of positions
uint8_t u8Digit = 0; //Column where the value is
uint8_t u8CharIndex = 0; //Position in the digit - special char to put in u8Digit
uint8_t u8ColIndex = 0; //Column counter
u16NumberOfPositions = u8Size * 5u; //There are five positions in each character
u16Position = (uint16_t)(((float)u16Value * (float)u16NumberOfPositions)/(float)u16Top); //Position within the current number of positions
u8Digit = (uint8_t)(u16Position / 5u); //Column where the value is
u8CharIndex = 1u + (uint8_t)(u16Position % 5u); //Position in the digit - special char to put in u8Digit
if((u8Digit >= u8Size) || (0 == u16Top))
{//Limit for operations with top lower than value
u8Digit = u8Size - 1;
u8CharIndex = 5u;
}
//First, set the 0 char (flat line) across whole progress bar
for(u8ColIndex = u8Column; u8ColIndex < (u8Column + u8Size); u8ColIndex++)
{
displayBufferA[u8Line][u8ColIndex] = char(0);
}
//Second, write the charcter 1, 2, 3, 4 o 5 in the specific position
displayBufferA [u8Line][u8Column + u8Digit] = char(u8CharIndex);
}
//Returns the mid point of a array of three uint16_t. Useful to reduce noise..
//pa3u16Data input data
uint16_t getMidPointOfThreeuInt16(uint16_t *pa3u16Data)
{
uint16_t u16RetVal = 0;
if(pa3u16Data[0] < pa3u16Data[1])
{
if(pa3u16Data[1] < pa3u16Data[2])
{
u16RetVal = pa3u16Data[1];
}
else
{
if(pa3u16Data[0] < pa3u16Data[2])
{
u16RetVal = pa3u16Data[2];
}
else
{
u16RetVal = pa3u16Data[0];
}
}
}
else
{
if(pa3u16Data[1] < pa3u16Data[2])
{
if(pa3u16Data[0] < pa3u16Data[2])
{
u16RetVal = pa3u16Data[0];
}
else
{
u16RetVal = pa3u16Data[2];
}
}
else
{
u16RetVal = pa3u16Data[1];
}
}
return u16RetVal;
}
//EOF

0
OviB
OviB

Reply 2 years ago

Hi, I would like your moded (temp gauge) code if you're willing to share it as you stated before. Please let me know Thank you.

1
MickM51
MickM51

Reply 2 years ago

#include <Wire.h>
#include <LiquidCrystal_I2C.h> //Library: https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads/
//Display: http://www.ebay.com/itm/Yellow-2004-20X4-IIC-I2C-TWI-Character-LCD-Display-Module-For-Arduino-/172181792364?hash=item2816d5a66c:g:VooAAOSwMVFXIC-3
/////////////////////////////////////////////////////////////////////////////////////
//Defines:
/////////////////////////////////////////////////////////////////////////////////////
//Conditional build:
#define LCD4x20
//#define LCD2x16
//LCD display parameters
#ifdef LCD4x20
#define N_ROWS 4u
#define N_COLS 20u
#endif //LCD4x20
#ifdef LCD2x16
#define N_ROWS 2u
#define N_COLS 16u
#endif //LCD4x20
#define LCD_TBL 300000u //Time that backlight remain active
#define TURN_OFF_BACKLIGHT
//General parameters
#define MAXVP 2800u //Maximum OV in 1/100V units
#define MAXCP 3100u //Maximum OC in 1/1000A units
#define DEBOUNCEHARDNESS 10u
//Macros
//Relay
#define RELAY_ON digitalWrite(2, HIGH); \
pinMode(2, OUTPUT)
#define RELAY_OFF digitalWrite(2, LOW); \
pinMode(2, OUTPUT)
#define IS_RELAY_ON (HIGH == digitalRead(2))
//Aruduino builtin LED
#define STATUS_ON digitalWrite(LED_BUILTIN, HIGH); \
pinMode(LED_BUILTIN, OUTPUT)
#define STATUS_OFF digitalWrite(LED_BUILTIN, LOW); \
pinMode(LED_BUILTIN, OUTPUT)
//MODE Pin
#define INIT_MODE pinMode(12, INPUT) //Config MODE pin
#define IS_MODE_CV (HIGH == digitalRead(12)) //true if mode CV
//ON pushbutton
#define INIT_PON pinMode(4, INPUT) //Config ON pushbutton pin
#define IS_PON_ON (LOW == digitalRead(4)) //true if ON pushed
//OFF pushbutton
#define INIT_POFF pinMode(7, INPUT) //Config OFF pushbutton pin
#define IS_POFF_ON (LOW == digitalRead(7)) //true if OFF pushed
//OC potentiometer
#define READ_ADC_CP() analogRead(0)
//OV potentiometer
#define READ_ADC_VP() analogRead(1)
/* Define the analogue pin used to read the temperature sensor (A0) */
#define LM35Pin 2
/* Stores the current temperature reading */
//MCP3422
#define MCP3422_CH1 0x00
#define MCP3422_CH2 0x01
//#define MCP3422_SR 0x00 //240 SPS (12bit)
#define MCP3422_SR 0x01 //60 SPS (14bit)
//#define MCP3422_SR 0x02 //15 SPS (16bit)
//#define MCP3422_SR 0x03 //3.75 SPS (18bit)
#define MCP3422_GAIN 0x00 //x1
//#define MCP3422_GAIN 0x01 //x2
//#define MCP3422_GAIN 0x02 //x4
//#define MCP3422_GAIN 0x03 //x8
#define MCP3422_CR_STARTONESHOT 0x80 // /RDY bit = 1, /O/C bit = 0
#define MCP3422_CR_READY 0x80 // /RDY bit mask
#define MCP3422_NCH 2u //Number of channels available
#define MCP3422_ADD 0x68 //Slave address
#define MCP3422_TP 5u //Number of msec between pollings to MCP3422
/////////////////////////////////////////////////////////////////////////////////////
//Constants:
/////////////////////////////////////////////////////////////////////////////////////
//Special chars for progress bar
const uint8_t a6x8u8ProgressBarChars[6][8] =
{
{0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00},
{0x00, 0x00, 0x10, 0x10, 0x10, 0x15, 0x00, 0x00},
{0x00, 0x00, 0x08, 0x08, 0x08, 0x1D, 0x00, 0x00},
{0x00, 0x00, 0x04, 0x04, 0x04, 0x15, 0x00, 0x00},
{0x00, 0x00, 0x02, 0x02, 0x02, 0x17, 0x00, 0x00},
{0x00, 0x00, 0x01, 0x01, 0x01, 0x15, 0x00, 0x00}
};
//Status messages
const char strStatusCC_ON[6] = {"CC ON"};
const char strStatusCV_ON[6] = {"CV ON"};
const char strStatusOFF[4] = {"OFF"};
const char strStatusOV_OFF[7] = {"OV OFF"};
const char strStatusOC_OFF[7] = {"OC OFF"};
/////////////////////////////////////////////////////////////////////////////////////
//Types:
/////////////////////////////////////////////////////////////////////////////////////
//MCP3422 machine states
enum eMCP3422ReadStates_t
{
eMS_WaitCurrent = 0,
eMS_WaitVoltage = 1
};
//Status values
enum eStatusValues_t
{
eStatus_CC_ON = 0,
eStatus_CV_ON = 1,
eStatus_OFF = 2,
eStatus_OV_OFF = 3,
eStatus_OC_OFF = 4
};
/////////////////////////////////////////////////////////////////////////////////////
//Variables:
/////////////////////////////////////////////////////////////////////////////////////
//Display
//Double buffer..
#ifdef LCD4x20
char displayBufferA[N_ROWS][N_COLS] =
{
{'V', 'O', '=', '0', '0', '.', '0', '0', 'V', ' ', ' ', 'C', 'O', '=', '0', '.', '0', '0', '0', 'A'},
{'0', '0', '0', '0', '0', '0', '0', '0', '0', ' ', ' ', '0', '0', '0', '0', '0', '0', '0', '0', '0'},
{'O', 'V', 'P', '=', '0', '.', '0', '0', 'V', ' ', ' ', 'O', 'C', 'P', '=', '0', '.', '0', '0', 'A'},
{'T', 'M', 'P', '=', ' ', ' ', ' ', ' ', 'C', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'O', 'F', 'F'}
};
char displayBufferB[N_ROWS][N_COLS]=
{
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'}
};
#endif //LCD4x20
#ifdef LCD2x16
char displayBufferA[N_ROWS][N_COLS] =
{// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
{'V', '=', '0', '0', '.', '0', '0', ' ', '0', '0', '0', '0', '0', '0', '0', '0'},
{'C', '=', '0', '.', '0', '0', '0', ' ', '0', '0', '0', '0', '0', '0', '0', '0'}
};
char displayBufferB[N_ROWS][N_COLS]=
{
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'}
};
#endif //LCD4x20
//Display class
#ifdef LCD4x20
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Addr, En, Rw, Rs, d4, d5, d6, d7, backlighpin, polarity (As descrived in the ebay link but with 0x3F address instead of 0x20)
#endif //LCD2x16
#ifdef LCD2x16
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Addr, En, Rw, Rs, d4, d5, d6, d7, backlighpin, polarity (As descrived in the ebay link but with 0x3F address instead of 0x20)
#endif //LCD2x16
uint32_t u32BackLightTimer = 0; //Timer for backlight
bool bBackLightOn = false; //Backlight state
//MCP3422 state machine
uint32_t u32MCP3422Timer = 0; //Polling timer
eMCP3422ReadStates_t MCP3422States = eMS_WaitCurrent;
//Status value
eStatusValues_t eStatusValue = eStatus_OFF;
//General variables
uint16_t u16VOValue = 0; //Voltage output value
uint16_t u16COValue = 0; //Current output value
uint16_t u16VPValue = 0; //Voltage protect value
uint16_t u16CPValue = 0; //Current protect value
uint16_t u16HSTValue = 0; //Heatsink temp value
uint16_t a3u16VP[3u] = {0, 0, 0}; //Array for filtering voltage protect
uint16_t a3u16CP[3u] = {0, 0, 0}; //Array for filtering current protect
uint8_t u8Index_a3u16VP = 0; //Index of a3u16VP
uint8_t u8Index_a3u16CP = 0; //Index of a3u16CP
bool bOnPressed = false; //State of On push button
bool bLastOnPressed = false; //Last state of On push button, used for flange detection
bool bOffPressed = false; //State of Off push button
bool bLastOffPressed = false; //Last state of Off push button, used for flange detection
uint8_t u8OnDebounce = 10; //Debounce counter for On push button
uint8_t u8OffDebounce = 10; //Debounce counter for Off push button
bool bMODECV = false; //Mode of power supply, true: constant voltage, false: constant current
uint32_t u32MCP3422Value = 0; //Last conversion value from MCP3422
bool bMCP3422ValIsPositive = true; //Last conversion sign, in this circuit it's always positive
bool bValuesOK = false; //Indicates in the main loop if the last execution of MCP3422_Machine updated u16VOValue and u16COValue
/////////////////////////////////////////////////////////////////////////////////////
//First execution:
/////////////////////////////////////////////////////////////////////////////////////
void setup()
{
RELAY_OFF;
setupDisplay();
Wire.setClock(400000); //Speed for both display and MCP3422
restartBackLight();
MCP3422_MachineInit();
/* Set the analogue reference used by the ADC inputs
to the internal 1.1V reference */
analogReference(DEFAULT);
//Serial.begin(9600); // ==>> activate for debug
}
/////////////////////////////////////////////////////////////////////////////////////
//Loop..
/////////////////////////////////////////////////////////////////////////////////////
void loop()
{
updateInputs();
refreshBackLight();
bValuesOK = MCP3422_Machine();
if(true == bValuesOK)
{
updateStatus();
updateDisplayBuffer();
updateDisplay();
}
hstemp();
}
void hstemp(void)
{
float HeatSinkAverage = 0;
for (int hsi=0; hsi < 50; hsi++) {
HeatSinkAverage = HeatSinkAverage + (analogRead(LM35Pin) / 1024.0 *5000 / 10);
}
u16HSTValue = HeatSinkAverage / 50 * 100;
}
/////////////////////////////////////////////////////////////////////////////////////
//Functions:
/////////////////////////////////////////////////////////////////////////////////////
//Starts a one shot conversion in MCP3422. Gain and sample rate/nbits are giben in defines.
//u8Channel: Chanel of MCP3422 to be read
//return: true if every thing was ok
bool MCP3422_StartConversion(uint8_t u8Channel)
{
bool bIsOK = true;
uint8_t u8ConfigRegister = MCP3422_CR_STARTONESHOT | (MCP3422_SR << 2u) | MCP3422_GAIN; //Set defined parameters
//Check channel range
if((MCP3422_NCH - 1u) < u8Channel)
{
bIsOK = false;
}
if(true == bIsOK)
{
//Set channel
u8ConfigRegister |= (u8Channel << 5u);
//Write Config Register
//Wire.begin();
Wire.beginTransmission(MCP3422_ADD);
Wire.write(u8ConfigRegister);
if (0 != Wire.endTransmission()) //0 = success in Wire library
{
bIsOK = false;
}
}
return bIsOK;
}
//Checks if the last conversion in MCP3422 is finished, if so updates the output value.
//u32Value: Output variable for ADC value.
//bIsPositive: Output variable for sign: true = positive
//return: true if the conversion was finished and there is data available
bool MCP3422_IsDataAndRead(uint32_t &u32Value, bool &bIsPositive)
{
bool bIsData = false;
uint8_t u8ByteCounter = 0;
uint8_t a4u8RecData[4];
//Wire.begin();
Wire.requestFrom(MCP3422_ADD, 4u);
a4u8RecData[3] |= MCP3422_CR_READY; //Force to NOT ready
for (u8ByteCounter = 0; 4u > u8ByteCounter; u8ByteCounter++)
{
a4u8RecData[u8ByteCounter] = Wire.read();
}
if(MCP3422_CR_READY != (MCP3422_CR_READY & a4u8RecData[3]))
{//RDY bit = 0 -> conversion finished, data is valid
bIsData = true;
}
if(true == bIsData)
{
//Formatting according to page 22 of datasheet
//Mode 12 bit
#if(0x00 == MCP3422_SR)
if(0x08 == (0x08 & a4u8RecData[0]))
{
bIsPositive = false;
}
else
{
bIsPositive = true;
}
u32Value = ((a4u8RecData[0] & 0x07) << 8u) | a4u8RecData[1];
if(false == bIsPositive)
{
u32Value = ~u32Value - 1; //Two's complement
}
#endif //(0x00 == MCP3422_SR)
//Mode 14 bit
#if(0x01 == MCP3422_SR)
if(0x20 == (0x20 & a4u8RecData[0]))
{
bIsPositive = false;
}
else
{
bIsPositive = true;
}
u32Value = ((a4u8RecData[0] & 0x1F) << 8u) | a4u8RecData[1];
if(false == bIsPositive)
{
u32Value = ~u32Value - 1; //Two's complement
}
#endif //(0x01 == MCP3422_SR)
//Mode 16 bit
#if(0x02 == MCP3422_SR)
if(0x80 == (0x80 & a4u8RecData[0]))
{
bIsPositive = false;
}
else
{
bIsPositive = true;
}
u32Value = ((a4u8RecData[0] & 0x7F) << 8u) | a4u8RecData[1];
if(false == bIsPositive)
{
u32Value = ~u32Value - 1; //Two's complement
}
#endif //(0x02 == MCP3422_SR)
//Mode 18 bit
#if(0x03 == MCP3422_SR)
if(0x02 == (0x02 & a4u8RecData[0]))
{
bIsPositive = false;
}
else
{
bIsPositive = true;
}
u32Value = ((a4u8RecData[0] & 0x01) << 16u) | (a4u8RecData[1] << 8u) | a4u8RecData[2];
if(false == bIsPositive)
{
u32Value = ~u32Value - 1; //Two's complement
}
#endif //(0x03 == MCP3422_SR)
}
return bIsData;
}
//Inits the MCP3422 state machine, must be executed before the periodic execution of MCP3422_Machine
void MCP3422_MachineInit(void)
{
MCP3422_StartConversion(MCP3422_CH1); //Start reading the output current value
MCP3422States = eMS_WaitCurrent; //Next state, wait for current to be read
u32MCP3422Timer = millis(); //Update the counter for first timming
}
//Checks if the last conversion in MCP3422 is finished, if so updates the output value.
//u32Value: Output variable for ADC value.
//bIsPositive: Output variable for sign: true = positive
//return: true if the conversion of voltage and current was finished and there is data available
bool MCP3422_Machine(void)
{
bool bCurrentVoltageMeasured = false;
if(millis() > (u32MCP3422Timer + MCP3422_TP))
{//MCP3422_TP msec elapsed
u32MCP3422Timer = millis();
switch(MCP3422States)
{
case eMS_WaitCurrent:
{
if(MCP3422_IsDataAndRead(u32MCP3422Value, bMCP3422ValIsPositive))
{//Conversion ended, value and next state
u16COValue = (uint16_t)(u32MCP3422Value >> 1); //Save output current value (dividing by 2 gets 1/1000A units)
MCP3422_StartConversion(MCP3422_CH2); //Start reading the output voltage value
MCP3422States = eMS_WaitVoltage; //Next state, wait for voltage to be read
}
break;
}
case eMS_WaitVoltage:
{
if(MCP3422_IsDataAndRead(u32MCP3422Value, bMCP3422ValIsPositive))
{//Conversion ended, update value and next state
u16VOValue = (uint16_t)(u32MCP3422Value >> 1); //Save output voltage value (dividing by 2 gets 1/100V units)
MCP3422_StartConversion(MCP3422_CH1), //Start reading the output current value
MCP3422States = eMS_WaitCurrent; //Next state, wait for current to be read
bCurrentVoltageMeasured = true; //in this execution u16VOValue and u16COValue are fresh
}
break;
}
}
}
return bCurrentVoltageMeasured;
}
//Updates the state and value of all inputs except MCP3422
void updateInputs(void)
{
//Init inputs
INIT_MODE;
INIT_PON;
INIT_POFF;
//Debouncing of pushbuttons...
//On
if(IS_PON_ON)
{
if(DEBOUNCEHARDNESS <= u8OnDebounce)
{//DEBOUNCEHARDNESS times consecutively read true
bOnPressed = true;
}
else
{
u8OnDebounce++;
}
}
else
{
u8OnDebounce = 0;
bOnPressed = false;
}
//Off
if(IS_POFF_ON)
{
if(DEBOUNCEHARDNESS <= u8OffDebounce)
{//DEBOUNCEHARDNESS times consecutively read true
bOffPressed = true;
}
else
{
u8OffDebounce++;
}
}
else
{
u8OffDebounce = 0;
bOffPressed = false;
}
//Update mode CC or CV directly reading the input pin
if(IS_MODE_CV)
{
bMODECV = true;
}
else
{
bMODECV = false;
}
//Voltage protect. Read the arudino adc input, scale it to MAXVP, update the circular stack of three with the last value.
a3u16VP[u8Index_a3u16VP] = (uint16_t)((float)(READ_ADC_VP()) * (((float)(MAXVP))/965.0)); //Analog in travel ~(0 - 965)lsb. At 965 -> MAXVP
if(MAXVP < a3u16VP[u8Index_a3u16VP]) //Limit
{
a3u16VP[u8Index_a3u16VP] = MAXVP;
}
u8Index_a3u16VP ++;
if(3u < u8Index_a3u16VP)
{
u8Index_a3u16VP = 0;
}
//Voltage protect. Read the mid point from the circular stack.
u16VPValue = getMidPointOfThreeuInt16(a3u16VP);
//Current protect. Read the arudino adc input, scale it to MAXCP, update the circular stack of three with the last value.
a3u16CP[u8Index_a3u16CP] = (uint16_t)((float)(READ_ADC_CP()) * (((float)(MAXCP))/965.0)); //Analog in travel ~(0 - 965)lsb. At 965 -> MAXCP
if(MAXCP < a3u16CP[u8Index_a3u16CP]) //Limit
{
a3u16CP[u8Index_a3u16CP] = MAXCP;
}
u8Index_a3u16CP ++;
if(3u < u8Index_a3u16CP)
{
u8Index_a3u16CP = 0;
}
//Current protect. Read the mid point from the circular stack.
u16CPValue = getMidPointOfThreeuInt16(a3u16CP);
}
//Setup LCD i2c address, LCD format, set up progress bar chars.
void setupDisplay(void)
{
uint8_t charIndex = 0;
lcd.begin(N_COLS, N_ROWS);
//Write progress bar chars.
for(charIndex = 0; charIndex < 6; charIndex++)
{
lcd.createChar(charIndex, (uint8_t *)a6x8u8ProgressBarChars[charIndex]);
}
}
//Turns the backlight on if it was off and reload the timing
void restartBackLight(void)
{
if(false == bBackLightOn)
{//Turn on backlight if it was off
lcd.setBacklight(HIGH);
}
bBackLightOn = true;
//Reload the timer
u32BackLightTimer = millis() + LCD_TBL;
}
//Automatic turn off backlight, must be executed periodically
void refreshBackLight(void)
{
if(true == bBackLightOn)
{
if(millis() > u32BackLightTimer)
{//Turn off backlight if it was on and the time elapsed
bBackLightOn = false;
#ifdef TURN_OFF_BACKLIGHT
lcd.setBacklight(LOW);
#endif
}
}
}
//Updates eStatusValue, turns on or off the relay and modifies displayBufferA
//depending on current values of measurements.
void updateStatus(void)
{
eStatusValues_t eLastStatusValue = eStatusValue;
if((false == bLastOffPressed) && (true == bOffPressed))
{//Rising flange in Off pushbutton
RELAY_OFF; //Turn off output
eStatusValue = eStatus_OFF;
restartBackLight(); //Backlight on with any pulsation of on or off
}
else if((false == bLastOnPressed) && (true == bOnPressed))
{//Rising flange in On pushbutton
RELAY_ON; //Turn on output
restartBackLight(); //Backlight on with any pulsation of on or off
}
bLastOnPressed = bOnPressed; //Update last state
bLastOffPressed = bOffPressed; //Update last state
if(u16VOValue > u16VPValue)
{//Overvoltage
eStatusValue = eStatus_OV_OFF;
RELAY_OFF; //Turn off output
}
if(u16COValue > u16CPValue)
{//Overvoltage
eStatusValue = eStatus_OC_OFF;
RELAY_OFF; //Turn off output
}
if(IS_RELAY_ON)
{//CC or CV
if(bMODECV)
{
eStatusValue = eStatus_CV_ON;
}
else
{
eStatusValue = eStatus_CC_ON;
}
}
//Write message in displayBufferA
switch(eStatusValue)
{
case eStatus_OFF:
{
writeStringStatus(strStatusOFF);
break;
}
case eStatus_CV_ON:
{
writeStringStatus(strStatusCV_ON);
break;
}
case eStatus_CC_ON:
{
writeStringStatus(strStatusCC_ON);
break;
}
case eStatus_OV_OFF:
{
writeStringStatus(strStatusOV_OFF);
break;
}
case eStatus_OC_OFF:
{
writeStringStatus(strStatusOC_OFF);
break;
}
}
if(eLastStatusValue != eStatusValue)
{//Backlight on if there has been a change in status
restartBackLight();
}
}
//Modifies displayBufferA with current values of measurements
void updateDisplayBuffer(void)
{
writeN_DOT_NNN(u16COValue, 0u, 14u);
writeNN_DOT_NN(u16VOValue, 0u, 3u);
writeN_DOT_NN(u16CPValue/10u, 2u, 15u);
writeNN_DOT_N(u16VPValue/10u, 2u, 4u);
writeNN_DOT_N(u16HSTValue/10u, 3u, 4u);
drawProgressBar(u16CPValue, u16COValue, 1u, 11u, 9u);
drawProgressBar(u16VPValue, u16VOValue, 1u, 0u, 9u);
}
//Browses displayBufferA. If any character is different in displayBufferA than displayBufferB
//writes it to displayBufferB and the LCD. Updates the possition of the cursor when neccesBary.
void updateDisplay(void)
{
uint8_t u8Rows = 0;
uint8_t u8Columns = 0;
bool bMustSetCuror = true;
for(u8Rows = 0; u8Rows < N_ROWS; u8Rows++)
{
for(u8Columns = 0; u8Columns < N_COLS; u8Columns++)
{
//Enters here for each character in displayBufferA
if(displayBufferA[u8Rows][u8Columns] != displayBufferB[u8Rows][u8Columns])
{
//A new character must be written
if(true == bMustSetCuror)
{
//Cursor must be updated because last character was not written
lcd.setCursor(u8Columns, u8Rows);
}
bMustSetCuror = false;
lcd.write(displayBufferA[u8Rows][u8Columns]);
displayBufferB[u8Rows][u8Columns] = displayBufferA[u8Rows][u8Columns];
}
else
{
//No character to be written
bMustSetCuror = true;
}
}
//Update cursor in new row
bMustSetCuror = true;
}
}
//Writes strText right justified in the last line.
//strText: text to write must be a c string with null terminator.
void writeStringStatus(const char* strText)
{
uint8_t u8Size = 0;
uint8_t u8DisplayPos = 9u; //Next char after STATUS:
//1º Clear the text
for(; N_COLS > u8DisplayPos; u8DisplayPos++)
{
displayBufferA[3u][u8DisplayPos] = ' ';
}
//2º Write the text
u8DisplayPos = 19u; //Starts at the end of the line
u8Size = strlen(strText); //Get the size of the string
do
{
u8Size--;
displayBufferA[3u][u8DisplayPos] = strText[u8Size];
u8DisplayPos--;
}while((0 != u8Size) && (7u <= u8DisplayPos)); //Ends at the end of string or before reaching "STATUS:" string
}
//Writes a N.NNN fixed dot number in displayBufferA.
//u16Value: Number to be written, 0 = 0.000 to 9999 = 9.999.
//u8Line: Start possition line in displayBufferA.
//u8Column: Start possition column in displayBufferA.
void writeN_DOT_NNN(uint16_t u16Value, uint8_t u8Line, uint8_t u8Column)
{
//Limit
if(9999u < u16Value)
{
u16Value = 9999u;
}
displayBufferA[u8Line][u8Column + 4] = (u16Value % 10) + 0x30; //4º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 3] = (u16Value % 10) + 0x30; //3º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 2] = (u16Value % 10) + 0x30; //2º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 1] = '.'; //Dot
displayBufferA[u8Line][u8Column] = (u16Value % 10) + 0x30; //1º digit
}
//Writes a NN.NN fixed dot number in displayBufferA.
//u16Value: Number to be written, 0 = 00.00 to 9999 = 99.99.
//u8Line: Start possition line in displayBufferA.
//u8Column: Start possition column in displayBufferA.
void writeNN_DOT_NN(uint16_t u16Value, uint8_t u8Line, uint8_t u8Column)
{
//Limit
if(9999u < u16Value)
{
u16Value = 9999u;
}
displayBufferA[u8Line][u8Column + 4] = (u16Value % 10) + 0x30; //4º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 3] = (u16Value % 10) + 0x30; //3º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 2] = '.'; //Dot
displayBufferA[u8Line][u8Column + 1] = (u16Value % 10) + 0x30; //2º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column] = (u16Value % 10) + 0x30; //1º digit
}
//Writes a NN.N fixed dot number in displayBufferA.
//u16Value: Number to be written, 0 = 00.0 to 999 = 99.9.
//u8Line: Start possition line in displayBufferA.
//u8Column: Start possition column in displayBufferA.
void writeNN_DOT_N(uint16_t u16Value, uint8_t u8Line, uint8_t u8Column)
{
//Limit
if(999u < u16Value)
{
u16Value = 999u;
}
displayBufferA[u8Line][u8Column + 3] = (u16Value % 10) + 0x30; //3º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 2] = '.'; //Dot
displayBufferA[u8Line][u8Column + 1] = (u16Value % 10) + 0x30; //2º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column] = (u16Value % 10) + 0x30; //1º digit
u16Value /= 10u;
}
//Writes a N.NN fixed dot number in displayBufferA.
//u16Value: Number to be written, 0 = 0.00 to 999 = 9.99.
//u8Line: Start possition line in displayBufferA.
//u8Column: Start possition column in displayBufferA.
void writeN_DOT_NN(uint16_t u16Value, uint8_t u8Line, uint8_t u8Column)
{
//Limit
if(999u < u16Value)
{
u16Value = 999u;
}
displayBufferA[u8Line][u8Column + 3] = (u16Value % 10) + 0x30; //3º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 2] = (u16Value % 10) + 0x30; //2º digit
u16Value /= 10u;
displayBufferA[u8Line][u8Column + 1] = '.'; //Dot
displayBufferA[u8Line][u8Column] = (u16Value % 10) + 0x30; //1º digit
u16Value /= 10u;
}
//Draws a progress bar in displayBufferA.
//u16Top: Full scale value.
//u16Value: Progress relative to u16Top.
//u8Line: Start possition line in displayBufferA.
//u8Column: Start possition column in displayBufferA.
//u8Size: Number of digits used by the progress bar.
void drawProgressBar(uint16_t u16Top, uint16_t u16Value, uint8_t u8Line, uint8_t u8Column, uint8_t u8Size)
{
// Progress bar characters:
// 0 1 2 3 4 5
//
//
// X X X X X
// X X X X X
// X X X X X
// X X X X X X X X X X X X X X X X X X
//
//
uint16_t u16NumberOfPositions = 0;
uint16_t u16Position = 0; //Position within the current number of positions
uint8_t u8Digit = 0; //Column where the value is
uint8_t u8CharIndex = 0; //Position in the digit - special char to put in u8Digit
uint8_t u8ColIndex = 0; //Column counter
u16NumberOfPositions = u8Size * 5u; //There are five positions in each character
u16Position = (uint16_t)(((float)u16Value * (float)u16NumberOfPositions)/(float)u16Top); //Position within the current number of positions
u8Digit = (uint8_t)(u16Position / 5u); //Column where the value is
u8CharIndex = 1u + (uint8_t)(u16Position % 5u); //Position in the digit - special char to put in u8Digit
if((u8Digit >= u8Size) || (0 == u16Top))
{//Limit for operations with top lower than value
u8Digit = u8Size - 1;
u8CharIndex = 5u;
}
//First, set the 0 char (flat line) across whole progress bar
for(u8ColIndex = u8Column; u8ColIndex < (u8Column + u8Size); u8ColIndex++)
{
displayBufferA[u8Line][u8ColIndex] = char(0);
}
//Second, write the charcter 1, 2, 3, 4 o 5 in the specific position
displayBufferA [u8Line][u8Column + u8Digit] = char(u8CharIndex);
}
//Returns the mid point of a array of three uint16_t. Useful to reduce noise..
//pa3u16Data input data
uint16_t getMidPointOfThreeuInt16(uint16_t *pa3u16Data)
{
uint16_t u16RetVal = 0;
if(pa3u16Data[0] < pa3u16Data[1])
{
if(pa3u16Data[1] < pa3u16Data[2])
{
u16RetVal = pa3u16Data[1];
}
else
{
if(pa3u16Data[0] < pa3u16Data[2])
{
u16RetVal = pa3u16Data[2];
}
else
{
u16RetVal = pa3u16Data[0];
}
}
}
else
{
if(pa3u16Data[1] < pa3u16Data[2])
{
if(pa3u16Data[0] < pa3u16Data[2])
{
u16RetVal = pa3u16Data[0];
}
else
{
u16RetVal = pa3u16Data[2];
}
}
else
{
u16RetVal = pa3u16Data[1];
}
}
return u16RetVal;
}
//EOF

0
sgianim
sgianim

Reply 2 years ago

Hi. Can you send me your code? I experienced a weird situation, my LCD act crazy, show strange characters. And please tell me what lib use and IDE. Thanks.

0
Ricotjuh
Ricotjuh

Question 2 years ago

I have a question, and I am curious if this affects more people.
Does the LCD backlight only turn on when an error occurs?
Because I think it is also useful that the backlight turns on when you turn one of the potentiometers.
Or sometimes you might want to have the backlight always active?

I also notice that when the backlight goes out, a number of digits of voltage and current are not stable. When the backlight comes on, it does.
More people experiencing this?

Screenshot_20210114-195524__01.jpgScreenshot_20210114-195530__01.jpg
1
iraqfallujah8888
iraqfallujah8888

Question 2 years ago

Hi i have a question
before plan to make this. first i appreciate for your awesome post!!
question = about transformer. i can't found exact model but power supply kit's input is 24V, input current 3A. why are you recommend 5A transformer?

0
danielrp
danielrp

Reply 2 years ago

Hi,
the current that the circuit demands from the transformer has peaks above 3A every semi cycle when the load is near 3A. This is because the smoothing capacitor is getting charged just at the top of every semi cycle "as fast as possible". The graphic here: https://www.changpuak.ch/electronics/power_supply_... shows the current peaks in green.
Having a 3A transformer will work probably alright, but it probably would be saturating causing more noise, etc.
Cheers.
Daniel.

0
iraqfallujah8888
iraqfallujah8888

Reply 2 years ago

i need more study haha thank you for the reply & Happy New Years!

Hi daniel, I see on the Eagle PCB you have S1? are GND and AGND connected or different net? Because I see the target joined. Thanks Daniel

0
Hypic
Hypic

Question 2 years ago

Hello,
I like your project. I would like to ask that can I bought one or two meter circuit pcb panel from you?
Regards,
Hypic

0
barrow4491
barrow4491

Question 4 years ago on Step 15

hi Daniel

I am having an issue when compiling the file. i get this error on line144

"POSITIVE WAS NOT DECLARED IN THIS SCOPE" any ideas please

Jim