Categories
Statistics
Flag Counter
Since 08.08.2014
Counts only, if "DNT = disabled".

Your IP is 18.117.184.236
ec2-18-117-184-236.us-east-2
Info
Valid HTML 4.01 Transitional Creative Commons Lizenzvertrag
rss
เราจะทำแบบวิศวกรผู้ยิ่งใหญ่
We love the King
25. December 2024
YOU RATED THIS ...
avg = 0.0 ,  n = 0


Arduino-SupplyMod.php    26864 Bytes    19-08-2024 10:00:37


Arduino/Genuino "SUPPLYMOD"


A Voltage Supply, 20V, 2A, controlled by an Arduino Nano






The assembled prototype. And yes, it reaches even 21 Volts, but we like the digit 9 ...



Do not expect Specifcations as known from a Rohde & Schwarz™ Power Supply. Instead, this Project serves to learn about Serial Communication with e.g. a Python GUI. Even so it has a good Long-Term Stability, the absolut value of the output voltage mayst be slightly off by some Millivolts. And the Current Limiting is done by Software ...




✈ Motivation




You can never have enough Power Supplies. Students often order cheap things from the land of the rising sun. But they do fail soon, because students are shy and order based on (lowest) price. Therefore we developped an instructeable thing which shall last longer. Much longer. You even can control it with a Python GUI. (As I have now a lot of time developping Software :-)




✈ The Design




To make things easy, we used a EPS-65S-24 (24 V, 2.7 A Switching Power Supply from MEAN WELL). It has an expected lifetime (MTBF) of 959'100 hrs (> 100 years) and an efficiecy of 90 %. It delivers 24 V with a ripple of 240 mVpp. By that, we get rid of a huge transformer. (And have a relatively smooth input voltage).

MeanWell-EPS-65S-24
The workhorse. Picture courtesy of Distrelec.
Arduino Supplymod
The brain. Arduino Nano with some 'co-workers'



The Block Diagram of the regulation looks like this :


Supplymod


The Voltage is linear regulated with an TIP3055 (NPN-Pass-Transistor, complementary of TIP2955) which is mounted on a heatsink (1.8 K/W). As it has good thermal contact to the case, we mayst add the case to the surface area of it.

The Setpoint is set with a Rotary Encoder using Interrupt Service Routines (connected to Pin2 and Pin3). We use an ADR4540 (Ultralow Noise, High Accuracy Voltage Reference) to produce a stable 4.096 V Reference Voltage. This Voltage is then fed to an AD8400AR (1 Channel Digital Potentiometer). This Potentiometer has 256 steps. Therefore we can set the Output Voltage in 6.25 * 4.096 / 256 = 100 mV Steps. This may look poor, but in practice it deems sufficient.

A 24LC01 (1K I2C™ Serial EEPROM) is used to store the output voltage, so that when you switch it on again, it will produce the same Output Voltage. Pressing the knob will save the current Voltage Setpoint to the Eeprom.

The Current Limiting is done by the Arduino. As it is always aware of the Power delivered to the Load (INA260), it can reduce the Voltage, when an Over Power Situation occurs. That's the Plan.

As a display, we use an OLED, 128 x 64 pixels, from https://www.displaymodule.com

Be aware, that the Arduino/Genuino™ Nano uses the (hard to find) Mini USB Type B connector.


Some impressions from 'inside' :


View inside ... not much to be seen :-)
The "Pre-Regulator" has a built-in fuse


7 mm Copper Spacer align the PCB
Frontpanel, which actually does most of the job




✈ The Heatsink • Safe Operating Area




The Heatsink was chosen because of it's form-factor. It is a SK 81 75 SA from Fischer Elektronik. It has a thermal resistance of 2.5 K/W. With it, we can dissipate about 30 Watts. If you do the math, a curve similiar to the one below shows, that the dangerous zone is the voltage range up to approx. 9 Volts. In that region, the current must be lowered in order not to self-destruct.



Maximum permissible Current as a function of Output Voltage, aka Safe Operating Area




✈ Downloads








✈ Test Sketch for Arduino/Genuino Nano



Double click on code to select ...

/* //////////////////////////////////////////////////////////////////

  ARDUINO/Genuino Project "SUPPLYMOD", a 20V, 2A Power Supply
  https://www.changpuak.ch/electronics/Arduino-SupplyMod.php
  Software Version 1.0
  19.05.2020 by ALEXANDER SSE FRANK

  USES LIBRARY FROM Matthew Brush mbrush@codebrainz.ca, (C) 2018
  https://github.com/codebrainz/ina260

////////////////////////////////////////////////////////////////// */

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>
#include <INA260.h>
static INA260 ina260(0);
double value = 0.0 ;


// DISPLAY

#define OLED_MOSI  A2
#define OLED_CLK   A1
#define OLED_DC    5
#define OLED_CS    4
#define OLED_RESET A3

// DAC

#define DAC_CS     7
#define DAC_DATA   6
#define DAC_CLCK   8
int DAC_VAL = 205 ;
int MAX_DAC_VAL = 210 ;   // 21.0 Volts+

// ROTARY ENCODER
const int RotaryEncoder1 = A0 ;   // PRESSED
const int RotaryEncoder2 = 2 ;
const int RotaryEncoder3 = 3 ;
volatile boolean LEFT = false ;
volatile boolean RIGHT = false ;
volatile boolean READY = true ;

// SERIAL COMMUNICATION
byte B[20] ;                 // holds User Input from Serial
int pointer = 0 ;

// EEPROM
#define EEPROM_24C01_I2CADDR 0x50

Adafruit_SH1106 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

#if (SH1106_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SH1106.h!");
#endif


float Volt = 20.000 ;                     // UNIT IS V
float Current = 1.601 ;                   // UNIT IS A
float CALVolt = 0.000 ;
float CALCurrent = 0.002 ;
boolean OutputON = true ;
float MAXCurent = 2.1 ;


// /////////////////////////////////////////////////////////////
// EEPROM ROUTINES
// /////////////////////////////////////////////////////////////


void Save()
{
  Wire.beginTransmission(EEPROM_24C01_I2CADDR); 
  Wire.write(0x00);
  Wire.write(DAC_VAL);
  Wire.endTransmission();  
}

void Load()
{
  Wire.beginTransmission(EEPROM_24C01_I2CADDR); 
  Wire.write(0x00);
  Wire.endTransmission();  
  Wire.requestFrom(EEPROM_24C01_I2CADDR,1);
  delay(1) ;
  if (Wire.available()) DAC_VAL = Wire.read();
  if (DAC_VAL == 0xFF) DAC_VAL = 0 ;
}


// /////////////////////////////////////////////////////////////
// Serial Communication Routines
// /////////////////////////////////////////////////////////////


void ShowInputBuffer()
{
  // FOR DEBUG REASON ONLY :-)
  for (int i = 0; i < 10; i++)
  {
    Serial.print("B[") ;
    Serial.print(i, DEC) ;
    Serial.print("] = ") ;
    Serial.println(B[i]) ;
  }
}

void FlushInputBuffer()
{
  while (Serial.available())
  {
    B[19] = Serial.read() ;
  }
  for (int i = 0; i < 20; i++) B[i] = 32 ;
}


void CheckForSerialInput()
{
  if (Serial.available())
  {
    B[pointer] = Serial.read() ;
    pointer += 1 ;
    if (pointer > 19) pointer = 0 ; // EMERGENCY BREAK
  }
}


void EvaluateSerialInput()
{
  // *IDN?
  if ((B[0]==42)&&(B[1]==73)&&(B[2]==68)&&(B[3]==78)&&(B[4]==63))
  {
    Serial.println("Supplymod V2.0 by Changpuak.ch (C) 07/2020") ;
    FlushInputBuffer() ;
    pointer = 0 ;
  }
  // IOUT?
  else if ((B[0]==73)&&(B[1]==79)&&(B[2]==85)&&(B[3]==84)&&(B[4]==63))
  {
    Serial.print(Current, 4) ;
    Serial.println(" A") ;
    FlushInputBuffer() ;
    pointer = 0 ;
  }
  // POUT?
  else if ((B[0]==80)&&(B[1]==79)&&(B[2]==85)&&(B[3]==84)&&(B[4]==63))
  {
    Serial.print(Current*Volt, 4) ;
    Serial.println(" W") ;
    FlushInputBuffer() ;
    pointer = 0 ;
  }
  // VOUT?
  else if ((B[0]==86)&&(B[1]==79)&&(B[2]==85)&&(B[3]==84)&&(B[4]==63))
  {
    Serial.print(Volt, 4) ;
    Serial.println(" V") ;
    FlushInputBuffer() ;
    pointer = 0 ;
  }
  // SAVE!
  else if ((B[0]==83)&&(B[1]==65)&&(B[2]==86)&&(B[3]==69)&&(B[4]==33))
  {
    Save() ;
    Serial.println("OK") ;
    FlushInputBuffer() ;
    pointer = 0 ;
  }
  // ISETx.xxx
  
  // VSETxx.xx
  else if ((B[0]==86)&&(B[1]==83)&&(B[2]==69)&&(B[3]==84)&&(B[4]==58))
  {
  // VOLTAGE BELOW 10.0 VOLTS
  // CHECK IF THERE ARE ENOUGH DIGITS
  if ((B[5]>47)&&(B[6]==46)&&(B[7]>47))
  {
    // CHECK IF THERE ARE DIGITS
    if ((B[5]<58)&&(B[7]<58))
    {
      DAC_VAL = 10*(B[5]-48)+(B[7]-48) ;
      UpDateDac() ;
      Serial.println("OK") ;
      FlushInputBuffer() ;
      pointer = 0 ;
    }
  }
  // VOLTAGE ABOVE 9.9999 VOLTS
  // CHECK IF THERE ARE ENOUGH DIGITS
  if ((B[5]>47)&&(B[6]>47)&&(B[7]==46)&&(B[8]>47))
  {
    // CHECK IF THERE ARE DIGITS
    if ((B[5]<58)&&(B[6]<58)&&(B[8]<58))
    {
    DAC_VAL = 100*(B[5]-48)+10*(B[6]-48)+(B[8]-48) ;
    UpDateDac() ;
    Serial.println("OK") ;
    FlushInputBuffer() ;
    pointer = 0 ;
    }
  }
  }
  else
  {
  // THROW AWAY GARBAGE FROM SERIAL INPUT
  if((B[0]!=42)&&(B[0]!=73)&&(B[0]!=86)&&(B[0]!=32)&&(B[0]!=80)&&(B[0]!=83)) 
    {
    Serial.println("SYNTAX ERROR. UNKNOWN COMMAND.") ;
    FlushInputBuffer() ;
    pointer = 0 ;
    }
  }
}


// /////////////////////////////////////////////////////////////////////
// SUBROUTINES DISPLAY.
// /////////////////////////////////////////////////////////////////////


void DisplayValue(float WERT)
{
  if (abs(WERT) < 10.000) display.print(" ") ;
  display.print(WERT, 3) ;
}


void UpDateDisplay()
{
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("****  SUPPLYMOD  ****");
  display.drawLine(0, 12, 128, 12, WHITE);
  display.setTextSize(2) ;

  if (OutputON)
  {
    // VOLTAGE
    display.setCursor(18, 20) ;
    DisplayValue(Volt) ;
    display.print(" V") ;
    // CURRENT
    display.setCursor(18, 44) ;
    DisplayValue(Current) ;
    display.print(" A") ;
  }
  else
  {
    display.setCursor(4, 32) ;
    display.print("OUTPUT OFF") ;
  }

  display.display() ;
}


// /////////////////////////////////////////////////////////////////////
// SUBROUTINES DAC
// /////////////////////////////////////////////////////////////////////


void UpDateDac()
{
  // CHECK FOR OVERFLOW
  if(DAC_VAL > MAX_DAC_VAL) DAC_VAL = MAX_DAC_VAL ;
  // Addr = 00
  digitalWrite(DAC_DATA, LOW) ;
  digitalWrite(DAC_CS, LOW) ;
  // ADDRESS
  digitalWrite(DAC_CLCK, HIGH) ; digitalWrite(DAC_CLCK, LOW) ;
  digitalWrite(DAC_CLCK, HIGH) ; digitalWrite(DAC_CLCK, LOW) ;
  // DATA
  shiftOut(DAC_DATA, DAC_CLCK, MSBFIRST, DAC_VAL) ;
  digitalWrite(DAC_CS, HIGH) ;
}


// /////////////////////////////////////////////////////////////////////
// SUBROUTINES INA 260
// /////////////////////////////////////////////////////////////////////


void UpDateINA260()
{
  ina260.readCurrentRegisterInAmps(value) ;
  Current = abs(value + CALCurrent) ;
  // Current = fabs(value  +  CALCurrent)   ; 
  ina260.readBusVoltageRegisterInVolts(value) ;
  Volt = value + CALVolt ;
}


// /////////////////////////////////////////////////////////////////////
// S E T U P
// /////////////////////////////////////////////////////////////////////


void setup()
{
  Serial.begin(115200) ;

  Wire.begin() ;

  // DAC PINS
  pinMode(DAC_CS, OUTPUT );
  pinMode(DAC_DATA, OUTPUT );
  pinMode(DAC_CLCK, OUTPUT );

  // INIT OLED
  display.begin(SH1106_SWITCHCAPVCC);

  // SHOW STARTUP SCREEN
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("****  SUPPLYMOD  ****");
  display.drawLine(0, 12, 128, 12, WHITE);
  display.setTextSize(1);
  display.setCursor(0, 21);
  display.println("20V, 2A POWER SUPPLY");
  display.setCursor(0, 33);
  display.println("FOR LABORATORY USE.");
  display.setCursor(0, 45);
  display.println("(C) ETH QUANTUMOPTICS");
  display.setCursor(0, 57);
  display.println("BUILT 25.07.2020");
  display.display();
  delay(999) ;

  ina260.begin() ;

  /*
  AVG_1 = 0b000,
  AVG_4 = 0b001,
  AVG_16 = 0b010,
  AVG_64 = 0b011,
  AVG_128 = 0b100,
  AVG_256 = 0b101,
  AVG_512 = 0b110,
  AVG_1024 = 0b111,
  VBUSCT_140US = 0b000,
  VBUSCT_204US = 0b001,
  VBUSCT_332US = 0b010,
  VBUSCT_588US = 0b011,
  VBUSCT_1_1MS = 0b100,
  VBUSCT_2_116MS = 0b101,
  VBUSCT_4_156MS = 0b110,
  VBUSCT_8_244MS = 0b0111,
  ISHCT_140US = 0b000,
  ISHCT_204US = 0b001,
  ISHCT_332US = 0b010,
  ISHCT_588US = 0b011,
  ISHCT_1_1MS = 0b100,
  ISHCT_2_116MS = 0b101,
  ISHCT_4_156MS = 0b110,
  ISHCT_8_244MS = 0b111,
  */

  INA260::ConfigurationRegister configReg = {0};
  configReg.avg = INA260::AVG_16 ;
  configReg.vbusct = INA260::VBUSCT_2_116MS ;
  configReg.ishct = INA260::ISHCT_8_244MS ;
  configReg.mode = INA260::MODE_ISH_VBUS_CONTINUOUS ;
  ina260.writeConfigurationRegister(configReg) ;


  delay(999) ;

  Load() ;
  UpDateDac() ;

  FlushInputBuffer() ;

  delay(999) ;
  
  pinMode(RotaryEncoder1, INPUT_PULLUP);
  pinMode(RotaryEncoder2, INPUT_PULLUP);
  pinMode(RotaryEncoder3, INPUT_PULLUP);
  // YELLOW
  attachInterrupt(digitalPinToInterrupt(RotaryEncoder2), RotaryEncoderISR2, FALLING);
  // GREEN
  attachInterrupt(digitalPinToInterrupt(RotaryEncoder3), RotaryEncoderISR3, FALLING);

  Serial.println("Supplymod V2.0 by Changpuak.ch (C) 07/2020") ;
  UpDateINA260() ;
  Serial.print("Voltage : ") ;
  Serial.print(Volt, 4) ;
  Serial.println(" V") ;
  Serial.print("Current : ") ;
  Serial.print(Current, 4) ;
  Serial.println(" A") ;
  Serial.println("Device ready.\n") ;

  delay(3000);
}


// /////////////////////////////////////////////////////////////////////
// M A I N L O O P
// /////////////////////////////////////////////////////////////////////


void loop()
{
  // EVALUATE ROTARY ENCODER
  if (LEFT)
  {
    READY = false ;
    DAC_VAL -= 1 ;
    if (DAC_VAL < 0) DAC_VAL = 0 ;
    UpDateDac() ;
    LEFT = false ;
    RIGHT = false ;
    READY = false ;
    delay(149) ;
    READY = true ;
  }

  if (RIGHT)
  {
    READY = false ;
    DAC_VAL += 1 ;
    if (DAC_VAL > MAX_DAC_VAL) DAC_VAL = MAX_DAC_VAL ;
    UpDateDac() ;
    LEFT = false ;
    RIGHT = false ;
    READY = false ;
    delay(149) ;
    READY = true ;
  }
  if(Current >= MAXCurent)
  {
    DAC_VAL -= 1 ;
    if (DAC_VAL < 0) DAC_VAL = 0 ;
    UpDateDac() ;
  }
  UpDateINA260() ;
  UpDateDisplay() ;
  // Check Serial Input
  CheckForSerialInput() ;
  EvaluateSerialInput() ;
  // SAVE VALUE WHEN KNOB IS PRESSED
  if(digitalRead(RotaryEncoder1)==0) Save() ;

  // Serial.println(Volt,4);
}


// /////////////////////////////////////////////////////////////////////
// INTERRUPT SERVICE ROUTINES
// /////////////////////////////////////////////////////////////////////


void RotaryEncoderISR3()
{
  // YELLOW
  if(READY)
  {
  LEFT = false ;
  RIGHT = false ;
  byte autre = digitalRead(RotaryEncoder3) ;
  if (autre > 0) LEFT = true ;
  if (autre < 1) RIGHT = true ;
  }
}

void RotaryEncoderISR2()
{
  // GREEN
  if(READY)
  {
  LEFT = false ;
  RIGHT = false ;
  byte autre = digitalRead(RotaryEncoder2) ;
  if (autre > 0) RIGHT = true ;
  if (autre < 1) LEFT = true ;
  }
}


// /////////////////////////////////////////////////////////////
// END OF FILE.
// /////////////////////////////////////////////////////////////





✈ Remote Control of the Supplymod








Remote Control with e.g. HTerm 0.8.5 from Tobias Hammer




✈ Performance





Ramping up the AD8400 Potentiometer

The line is strictly monotonic rising and offers no surprises. Even with a "low-cost 8 Bit Potentiometer", one can build a useable D/A Converter. We used the 10 kΩ version here. (The output of the Power Supply was unloaded, but the voltage does not change if loaded.). Also visible is the lower limit of approx. 53 mV - much better than a LM317 approach. The error is slightly over the "± 1 LSB" specifications from AD, especially at the upper end.



Long Term Drift

We see, that after about 5000 s, the voltage remains constant. Obviously this is a warm-up issue. As the absolut drift is below 2 mV, we may neglect that. Measured with the built-in INA260.



Response to Load Changes

The Power Supply needs about 2 ms to regulate for jumps in load changes. This is mainly caused by the relatively large capacitor C12 (10nF).




✈ Credits




We would like to thank the team of TARGET 3001! as well as the team of Beta Layout for ultrafast and very professional support.





✈ Errata




Luca informed me, that the abs() value of a double does not work. Please note his solution of using fabs() in line 291. This mayst depend on the compiler you use.

Grazie mille !




✈ Share your thoughts



The webmaster does not read these comments regularely. Urgent questions should be send via email. Ads or links to completely uncorrelated things will be removed.


Your Browser says that you allow tracking. Mayst we suggest that you check that DNT thing ?

 
t1 = 6740 d

t2 = 298 ms

★ ★ ★  Copyright © 2006 - 2024 by changpuak.ch  ★ ★ ★

Impressum