Quantcast
Channel: Arduino Forum - Latest topics
Viewing all articles
Browse latest Browse all 15287

PID controller with Feedback?

$
0
0

I am working on a project and would like to change my current feedback to use PID.
Right now the PWM duty cycle of pin 3 is increased or decreased based on the output of a current sensor. There is a global variable that is adjusted in the gui of the program.

I tried putting in the basic PID example, but I can't figure out how to constrain it to the IV variable. When I put the PID in instead of my code it goes straight to the max PWM duty cycle, which causes to high a voltage drop and stops the function based on other parameters.

My code for the function is:

// Runs the main discharge function
void RunDischarge() {
  
  if (isrunning==true) {
    if (voltageFull>VV/100)
     {
       
        /* // How to use feedback from variable IV.  PWM, Output needs to be the duty cycle that gets IV
        Input = Icurrent;
        myPID.Compute();
        analogWrite(PWM, Output);
        */
       
         if (Icurrent<IV)
        {
         zz++;
         analogWrite(PWM, zz);
        }
     }
       else if (Icurrent>IV)
       {
         zz--;
         analogWrite(PWM, zz);
       
    }
   else if (voltageFull<VV/100)
    {
      zz=0;
      analogWrite(PWM, zz);
     isrunning=false;

    }
  }

} // end runDischarge

And because someone will eventually ask for the entire code... It is messy, just sayin....

/*
    Code Version 1.0
    WMH Racing Battery Wizard
    Written by Andrew Sarratore
    Date: 10/28/2023
    Code Version 1.1
    Add getCurrentAverage()
    add getFVoltageAverage()
    add getCalculations()
    add Program()
    Changes to the "Run Program" Menu
    Date:12/11/2023
    Code Version 1.2
    add getPVoltageAverage
    add more readings to the dishcharge display
    Code Version 1.3
    Date:12/27/2023
    add self calibrate VCC
    add mAH reading, untested
    add PID_v1.h library, still need to write PID code for the PWM value and replace current feedback loop
    Code Version 1.4
    Date:12/30/2023
    Add IR struct - Untested
    Add getIR function - still need to write the menu and page function
    Code Version 1.4.2
    Date:1/4/24
    Calibrate initial QOV based on VCC during setup
    Fixed averages to be floats instead of int...
    Added code for PID, but just shoots to max value

    Other items still needed
    Data Return page
    Voltage values every 15 seconds - look into graphing this, would be cool.  
    
    
*/
    
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include "XPT2046_Touchscreen.h"
#include "Math.h"
#include "PID_v1.h"
 
// Define SPI pins for both display and touch
#define TFT_CS 10
#define TFT_DC 9
#define TFT_MOSI 11
#define TFT_CLK 13
#define TFT_RST 8
#define TFT_MISO 12
#define TS_CS 7
#define ROTATION 1
#define Isens A0
#define VFsens A1
#define VPsens A2
#define PWM 3

char currentPage;
 

 
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC/RST
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
XPT2046_Touchscreen ts(TS_CS);
 
// calibration values
float xCalM = -0.09, yCalM = -0.06; // gradients
float xCalC = 329.36, yCalC = 248.13; // y axis crossing points
 
int8_t blockWidth = 20; // block size
int8_t blockHeight = 20;
int16_t blockX = 0, blockY = 0; // block position (pixels)
 
class ScreenPoint {
public:
int16_t x;
int16_t y;
 
// default constructor
ScreenPoint(){
}
 
ScreenPoint(int16_t xIn, int16_t yIn){
x = xIn;
y = yIn;
}
};

class Button {
  public:

    int x;
    int y;
    int width;
    int height;
    char *text;

    Button(){
    }

    void initButtonP(int xPos, int yPos, int butWidth, int butHeight, char *butText){
      x = xPos;
      y = yPos;
      width = butWidth;
      height = butHeight;
      text = butText;
      renderP();
    }
    void initButtonM(int xPos, int yPos, int butWidth, int butHeight, char *butText){
      x = xPos;
      y = yPos;
      width = butWidth;
      height = butHeight;
      text = butText;
      renderM();
    }
     void initButtonN(int xPos, int yPos, int butWidth, int butHeight, char *butText){
      x = xPos;
      y = yPos;
      width = butWidth;
      height = butHeight;
      text = butText;
      renderN();
    }
    void renderP(){
      tft.fillRect(x,y,width,height,ILI9341_GREEN);
      tft.setCursor(x+5,y+5);
      tft.setTextSize(2);
      tft.setTextColor(ILI9341_WHITE);
      tft.print(text);
    }
    void renderM(){
      tft.fillRect(x,y,width,height,ILI9341_RED);
      tft.setCursor(x+5,y+5);
      tft.setTextSize(2);
      tft.setTextColor(ILI9341_WHITE);
      tft.print(text);
    }
    void renderN(){
      tft.fillRect(x,y,width,height,ILI9341_BLUE);
      tft.setCursor(x+5,y+5);
      tft.setTextSize(2);
      tft.setTextColor(ILI9341_WHITE);
      tft.print(text);
    }
    bool isClicked(ScreenPoint sp) {
      if ((sp.x >= x) && (sp.x <= (x+width)) && (sp.y >= y) && (sp.y <= (y+height))){
        return true;
      }
      else {
        return false;
      }
    }
};

unsigned const int numChan = 5;
unsigned int chan;
struct ChannelData {
  float startVoltage;
  float endVoltage;
  float voltageDrop;
  float internalResistance;
};
ChannelData Channels[numChan]; 

//Touch Screen
ScreenPoint getScreenCoords(int16_t x, int16_t y){
int16_t xCoord = round((x * xCalM) + xCalC);
int16_t yCoord = round((y * yCalM) + yCalC);
if(xCoord < 0) xCoord = 0;
if(xCoord >= tft.width()) xCoord = tft.width() - 1;
if(yCoord < 0) yCoord = 0;
if(yCoord >= tft.height()) yCoord = tft.height() - 1;
return(ScreenPoint(xCoord, yCoord));
}


// I can probably make an array of the following button variables...
Button btnIP;
Button btnIM;
Button btnVP;
Button btnVM;
Button btnMI;
Button btnMV;
Button btnMM;
Button btnCT;
Button btnRP;
Button btnPN;
Button btnMN;
Button btnRN;

int IV = 10;
float VV = 750.00;
unsigned long StartTime;
unsigned long CurrentTime;

//PID
double Setpoint;
double Input;
double Output;
//Specify the links and initial tuning parameters
double Kp=2, Ki=0, Kd=0;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);


// Current Variables
float VCC;//=4.967; //measure the value(Measure value not needed with the VCC calibration code)
float QOV; // Quiescent Output of the Current Sensor(need to auto calibrate this as well)
float sens=0.04; //sensitivity of the current sensor
float iavg; // analogRead(Isens)/IN
float Icurrent; // Current Reading in Amps
float Icorrected; //iavg-IOFF
float voltageI; // iavg translated to voltage
float IOFF=0; // Offset to the raw read to zero current
int IN=10; // Sample number to average current
int mAH;

// Battery Voltage Variables (may be able to make a struct or class here as well)
int ValueR1F=9988;//Measured value
int ValueR2F=5104;//Measured value
int ValueR1P=9999;//Measured value
int ValueR2P=5082;//Measured value
int VFN=10; // Sample number for average of analogRead(VFsens) Full voltage
int VPN=10;// Sample number for average of analogRead(VPsens) Partial voltage
float vfavg; // Average of the analogRead(VFsens)/VFN - Full Voltage
float vpavg;
float Fvoltage;
float Pvoltage;
float voltageFull;
float voltagePartial;
boolean isrunning=false;
int zz=0;// variable used for analogWrite(PWM,zz)
float internalResistanceAVG=0;


//===================================================================SETUP=========================================================================================
void setup() {
Serial.begin(115200);
 
// avoid chip select contention
pinMode(TS_CS, OUTPUT);
digitalWrite(TS_CS, HIGH);
pinMode(TFT_CS, OUTPUT);
pinMode(Isens, INPUT);
pinMode(VFsens, INPUT);
pinMode(VPsens, INPUT);
pinMode(PWM, OUTPUT);
digitalWrite(TFT_CS, HIGH);

tft.begin();
tft.setRotation(ROTATION);
tft.fillScreen(ILI9341_BLACK);
ts.begin();
ts.setRotation(ROTATION);
//calibrateTouchScreen(); Leaving here for first run of the dispaly to get coordinates to add
//btnMV.initButtonP(0,50,180,25,"Discharge"); (caused issues initating buttons in the setup, so moved to the functions of the page they belong to)
//btnMI.initButtonP(0,100,180,25,"Voltage");
//btnVP.initButtonP(175,125,20,25,"+");
//btnVM.initButtonM(175,200,80,25,"Main Menu");
getCalculations();
calibrateQOV();
//initialize the variables we're linked to
  Input = Icurrent;
  Setpoint = 100;

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
currentPage = '0'; //  Indicates that we are at Home Screen
Home();
}

//unsigned long lastFrame = millis();

//==================================================================END SETUP=====================================================================


//======================================================================LOOP======================================================================
void loop() {
  ScreenPoint sp;
  // limit frame rate
//while((millis() - lastFrame) < 20); (caused issues with millis function to get mAH, may need to revisit as this helped some flickering)
//lastFrame = millis();

getFVoltageAverage();
getPVoltageAverage();
getCurrentAverage();
getCalculations();
//getAccurateVoltage();
SerialData();

// Home Screen
if(currentPage=='0'){
    
if (ts.touched()) {
  TS_Point p = ts.getPoint();
  sp = getScreenCoords(p.x, p.y);
if(btnMV.isClicked(sp)){
    currentPage='1'; // Go to Discharge Current
     tft.fillScreen(ILI9341_BLACK);
     discharge();
   }
else if(btnMI.isClicked(sp)){
    currentPage='2'; // Go to Cutoff Voltage
     tft.fillScreen(ILI9341_BLACK);
     cutoff();
   }

   else if(btnCT.isClicked(sp)){
    currentPage='3'; // Go to Calibrate Screen
     tft.fillScreen(ILI9341_BLACK);
     calibrateTouchScreen();
   }

   else if(btnRP.isClicked(sp)){
    currentPage='4'; // Go to Run Program
     tft.fillScreen(ILI9341_BLACK);
     program();
   }
   //add menu item for getIR page ('5') - will have to adjust other buttons to make room
  }
}


// Discharge Current
if(currentPage=='1'){
   tft.setCursor(100,150);
    tft.print(IV);
    tft.setCursor(190,150);
    tft.print("A");
if (ts.touched()) {
  TS_Point p = ts.getPoint();
  sp = getScreenCoords(p.x, p.y);
  if(btnIP.isClicked(sp)){
    IV++;
    tft.fillRect(100,150,125,50,ILI9341_BLACK);
    delay(100);
    }
    else if (btnIM.isClicked(sp)){
      IV--;
      tft.fillRect(100,150,125,50,ILI9341_BLACK);
     delay(100);
    }
    else if (btnMM.isClicked(sp)){
      currentPage='0';
      Home();
    }
   
    
  }
}

// Cutoff Voltage
if(currentPage=='2'){
   tft.setCursor(100,150);
    tft.print(VV/100);
    tft.setCursor(210,150);
    tft.print("V");
if (ts.touched()) {
  TS_Point p = ts.getPoint();
  sp = getScreenCoords(p.x, p.y);
  if(btnIP.isClicked(sp)){
    VV++;
    tft.fillRect(100,150,100,50,ILI9341_BLACK);
    delay(50);
    }
    else if (btnIM.isClicked(sp)){
      VV--;
      tft.fillRect(100,150,100,50,ILI9341_BLACK);
     delay(50);
    }
    else if (btnMM.isClicked(sp)){
      currentPage='0';
      Home();
    }
   
    
  }
}

 
// Run Discharge (Surely I can move some of this to the page function, but when moved it caused the screen to refresh every loop)
if(currentPage=='4'){
    tft.setCursor(0,50);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE,ILI9341_BLACK);
    tft.print("Battery:");
    tft.setCursor(0,75);
    tft.print("Cutoff ");
    tft.print(VV/100);
    tft.print("V");
    tft.setTextSize(2);
    tft.setCursor(0,100);
    tft.print("Actual ");
    tft.print(voltageFull);
    tft.setCursor(145,100);
    tft.print("V");
    tft.setCursor(160,50);
    tft.print("Current:");
    tft.setCursor(160,75);
    tft.print("Rate ");
    tft.print(IV);
    tft.print("A");
    tft.setCursor(160,100);
    tft.print("Actual ");
    tft.print(Icurrent,1);
    tft.setCursor(305,100);
    tft.print("A");
    tft.setTextSize(1);
    tft.setCursor(0,120);
    tft.print("Cell 1  ");
    tft.setCursor(45,120);
    tft.print(voltageFull-voltagePartial);
    tft.setCursor(80,120);
    tft.print("V");
    tft.setCursor(0,130);
    tft.print("Cell 2  ");
    tft.setCursor(45,130);
    tft.print(voltagePartial);
    tft.setCursor(80,130);
    tft.print("V");
    tft.setCursor(160,120);
    tft.print("Time");
    tft.setCursor(160,130);
    tft.fillRect(160,130,50,10,ILI9341_BLACK);
    tft.print(CurrentTime);
    tft.setCursor(240,120);
    tft.print("mAH");
    tft.setCursor(240,130);
    tft.print(mAH);
    if (isrunning==true) {
      btnPN.initButtonM(90,15,140,25,"RUNNING");
      RunDischarge();
    }
         else
    {
      tft.setTextColor(ILI9341_WHITE,ILI9341_GREEN);
      btnPN.initButtonP(90,15,140,25,"    RUN");
    }
    if (ts.touched()) {
  TS_Point p = ts.getPoint();
  sp = getScreenCoords(p.x, p.y);
    if (btnMN.isClicked(sp)){
      currentPage='0';
      Home();
    }
      else if (btnRN.isClicked(sp)){
        zz=0;
        analogWrite(PWM, zz);
        isrunning=false;
        }
     else if (btnPN.isClicked(sp)){
       StartTime=millis();
       isrunning=true;
        
        }
    
   
  }   
}
// Page 5 is for getIR function, not written the menu or buttons for this page yet
//if(currentPage=='5'){
//}
delay(0);
} 
//===========================================END LOOP=============================================
// Home Screen CurrentPage=0
void Home(){
    tft.fillScreen(ILI9341_BLACK);
    tft.setCursor(50,0);
    tft.setTextColor(ILI9341_RED);
    tft.setTextSize(4);
    tft.print("MAIN MENU");
    tft.setTextColor(	0x07FF);
    tft.setTextSize(3);
    tft.setCursor(0,50);
    tft.print("1.");
    tft.setCursor(0,100);
    tft.print("2.");
    tft.setCursor(0,150);
    tft.print("3.");
    tft.setCursor(0,200);
    tft.print("4.");
    btnMV.initButtonP(50,50,200,25,"Discharge Rate");
    btnMI.initButtonP(50,100,200,25,"Voltage Cutoff");
    btnCT.initButtonP(50,150,200,25,"Calibrate Screen");
    btnRP.initButtonP(50,200,200,25,"Run Discharge");
    // add button for getIR function page, adjust button heights to fit, or make 2 colums and adjust width...
}
// Discharge Current Selection CurrentPage=1
void discharge(){
    btnIM.initButtonM(80,75,80,60,"-");
    btnIP.initButtonP(160,75,80,60,"+");
    btnMM.initButtonM(90,200,140,25," Main Menu");
    tft.setCursor(50,0);
    tft.setTextSize(4);
    tft.setTextColor(ILI9341_YELLOW);
    tft.print("DISCHARGE");
    tft.setCursor(70,40);
    tft.print("CURRENT");
    tft.setCursor(100,150);
    tft.print(IV);
    tft.setCursor(190,150);
    tft.print("A");
}// End discharge

// Cutoff Voltage Selection CurrentPage=2
void cutoff(){
   
    btnIM.initButtonM(80,75,80,60,"-");
    btnIP.initButtonP(160,75,80,60,"+");
    btnMM.initButtonM(90,200,140,25," Main Menu");
    tft.setCursor(80,0);
    tft.setTextSize(4);
    tft.setTextColor(ILI9341_BLUE);
    tft.print("VOLTAGE");
    tft.setCursor(95,40);
    tft.print("CUTOFF");
}// End Cutoff

// Run Program CurrentPage=4
void program(){
    btnPN.initButtonP(90,15,140,25,"    RUN");
    btnMN.initButtonN(90,200,140,25," Main Menu");
    btnRN.initButtonM(90,150,140,25,"   STOP   ");
}// End Program

void calibrateTouchScreen(){
TS_Point p;
int16_t x1,y1,x2,y2;
 
tft.fillScreen(ILI9341_BLACK);
// wait for no touch
while(ts.touched());
tft.drawFastHLine(10,20,20,ILI9341_RED);
tft.drawFastVLine(20,10,20,ILI9341_RED);
while(!ts.touched());
delay(50);
p = ts.getPoint();
x1 = p.x;
y1 = p.y;
tft.drawFastHLine(10,20,20,ILI9341_BLACK);
tft.drawFastVLine(20,10,20,ILI9341_BLACK);
delay(500);
while(ts.touched());
tft.drawFastHLine(tft.width() - 30,tft.height() - 20,20,ILI9341_RED);
tft.drawFastVLine(tft.width() - 20,tft.height() - 30,20,ILI9341_RED);
while(!ts.touched());
delay(50);
p = ts.getPoint();
x2 = p.x;
y2 = p.y;
tft.drawFastHLine(tft.width() - 30,tft.height() - 20,20,ILI9341_BLACK);
tft.drawFastVLine(tft.width() - 20,tft.height() - 30,20,ILI9341_BLACK);
 
int16_t xDist = tft.width() - 40;
int16_t yDist = tft.height() - 40;
 
// translate in form pos = m x val + c
// x
xCalM = (float)xDist / (float)(x2 - x1);
xCalC = 20.0 - ((float)x1 * xCalM);
// y
yCalM = (float)yDist / (float)(y2 - y1);
yCalC = 20.0 - ((float)y1 * yCalM);

currentPage='0';
Home();
/* // Serial print the actual coordinates from the touch calibrate, enter into the global variables, for first run of the screen
Serial.print("x1 = ");Serial.print(x1);
Serial.print(", y1 = ");Serial.print(y1);
Serial.print("x2 = ");Serial.print(x2);
Serial.print(", y2 = ");Serial.println(y2);
Serial.print("xCalM = ");Serial.print(xCalM);
Serial.print(", xCalC = ");Serial.print(xCalC);
Serial.print("yCalM = ");Serial.print(yCalM);
Serial.print(", yCalC = ");Serial.println(yCalC);
*/ 
}// END Calibrate




//  Current Average
void getCurrentAverage() {
  int ii; float rawIRead;
  for(int ii=0; ii<IN; ii++)
  {
    analogRead(Isens);
    rawIRead+=analogRead(Isens);
  }
    iavg=rawIRead/IN;
    Icorrected=(iavg-IOFF);
}// end getCurrent

//   Full voltage average (2s voltage if a 2s battery or 1s voltage for a 1s battery)
  void getFVoltageAverage() {
  int jj; float rawVFRead;
  for(int jj=0; jj<VFN; jj++)
  {
    analogRead(VFsens);
    rawVFRead+=analogRead(VFsens);
  }
    vfavg=rawVFRead/(VFN);
}// end getFVoltage

//  Partial Voltage average (only applies for 2s, this is cell 2 voltage)
void getPVoltageAverage() {
  int kk; float rawVPRead; 
  for(int kk=0; kk<VPN; kk++)
  {
    analogRead(VPsens);
    rawVPRead+=analogRead(VPsens);
  }
    vpavg=rawVPRead/(VPN);
}//  end Partial Voltage

// Runs the main discharge function
void RunDischarge() {
  
  if (isrunning==true) {
    if (voltageFull>VV/100)
     {
       
        /* // How to use feedback from variable IV.  PWM, Output needs to be the duty cycle that gets IV
        Input = Icurrent;
        myPID.Compute();
        analogWrite(PWM, Output);
        */
       
         if (Icurrent<IV)
        {
         zz++;
         analogWrite(PWM, zz);
        }
     }
       else if (Icurrent>IV)
       {
         zz--;
         analogWrite(PWM, zz);
       
    }
   else if (voltageFull<VV/100)
    {
      zz=0;
      analogWrite(PWM, zz);
     isrunning=false;

    }
  }

} // end runDischarge



void getCalculations() {
  getCurrentAverage();
  voltageI=Icorrected*(VCC/1023);
  Icurrent=abs((voltageI-QOV)/sens);
  getFVoltageAverage();
  Fvoltage=vfavg*(VCC/1023);
  voltageFull=((Fvoltage*(ValueR1F+ValueR2F))/ValueR2F);
  getPVoltageAverage();
  Pvoltage=vpavg*(VCC/1023);
  voltagePartial=((Pvoltage*(ValueR1P+ValueR2P))/ValueR2P);
  VCC=(getAccurateVoltage())/1000;
  //QOV=VCC/2;// in theory... QOV actually equals voltageI with no current...
  if (isrunning==true) {
  CurrentTime=(millis()-StartTime)/1000;
  }
  else if (isrunning==false) {
    CurrentTime=0;// Need to change to keep the previous runtime around, but have a reset data button to zero time
  }
mAH=((Icurrent/3.6)*CurrentTime);

}// end Calculations

// calibrates the VCC using the bandgap ref - Something is amiss though, resoltion is 30mV instead of 3mV - need to work on this
float getAccurateVoltage() {
  int vv; float rawgetVoltage;
  for (vv=0; vv<10; vv++)
  {
    getVoltage();
    rawgetVoltage+=getVoltage();
  }
  return rawgetVoltage/10;
}
// Read the voltage of the battery the Arduino is currently running on (in millivolts)
float getVoltage() {
    const long InternalReferenceVoltage = 1085; // Adjust this value to your boards specific internal BG voltage x1000
    ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
    ADCSRA |= _BV( ADSC ); // Start a conversion 
    while( ( (ADCSRA & (1<<ADSC)) != 0 ) ); // Wait for it to complete
    float results = (((InternalReferenceVoltage * 1024) / ADC) + 5); // Scale the value; calculates for straight line value
    return results; 
}// end VCC calibration


//Internal Resistance Measurement (This is untested and need menu and page written)
void getIR() {
 if (isrunning==false) {
   for (int chan=0; chan<(numChan-1); chan++)
   {
    getCalculations();
    Channels[chan].startVoltage=voltageFull; // record unloaded voltage of battery
    zz=51; // Will be appx 20A with 2s, may split this into if statements based on current battery voltage
    analogWrite(PWM, zz);//Turn Mosfets on
    delay(10);//delay to stabalize - may need to fine tune this delay, but the shorter the better
    getCalculations();
    Channels[chan].endVoltage=voltageFull; // record voltage of battery under load
    zz=0;//
    analogWrite(PWM, zz);//Turn off the mosfets
    Channels[chan].voltageDrop=Channels[chan].endVoltage-Channels[chan].startVoltage;// Voltage drop from the load
    Channels[chan].internalResistance=(Channels[chan].voltageDrop/Icurrent)*1000;//Ohms Law V=IR, R=V/I, Readings in mΩ
    delay(2000);// Allow for stablization between readings - may need to fine tune this delay, but start on the high side
   }
    for (int chan=0; chan<(numChan-1); chan++) {
   internalResistanceAVG=+Channels[chan].internalResistance;// run the readings numChan times and average the IR
  }
 }//End if
}//end getIR

void calibrateQOV() {
  getCalculations();
  QOV=(iavg*VCC)/1023;
}


void SerialData() {
  getCalculations();
  Serial.println("----------------------------------------");
  Serial.println();
  Serial.print(VCC);
  Serial.print("V VCC ");
  Serial.println();
  Serial.print(Icorrected    );
  Serial.println();
  Serial.print(Icurrent);
  Serial.print(" Amps ");
  Serial.println();
  Serial.print(voltageFull);
  Serial.print(" V Full  ");
  Serial.print(vfavg);
  Serial.print("  ACT  ");
  Serial.print(analogRead(VFsens));
  Serial.println();  
  Serial.print(voltagePartial);
  Serial.print("   ");
  Serial.print(" V Partial   ");
  Serial.print(vpavg);
  Serial.print("   ACT  ");
  Serial.print(analogRead(VPsens));
  Serial.println();
 // Serial.println(getAccurateVoltage());
  Serial.println();
 }

1 post - 1 participant

Read full topic


Viewing all articles
Browse latest Browse all 15287

Trending Articles