• Part 1.
    From the linear scaling perspective two values have to be supplied to scale the reading into Engineering Units. For example an analog input returns a value from 0.0 to 1.0. if it’s connected to a pressure sensor, the 0.0 may represent -.1 psig and the 1.0 represents 59.8 psig.
    A field technician would secure a process and use a calibrated pressure to apply 0.0 psig, record the a number of analog input values and then apply a known calibration pressure and record a number of analog input values. Using two calibration values is the minimum required. Three point or more is sometimes used. Usually the software sets up a step by step procedure to do this and then performs a linear regression to calculate and record the slope and intercept in the logger. It would be valuable to record these values in one location in the data logger to be used to provide readings in engineering units, and be available for the next scheduled calibration.
    Typically the sensor is selected so that typical values fall below 80% of the sensor’s full range. The resolution is determined by the number of bits in the analog to digital converter. Say a pressure sensor has a full scale range of 100 psig. Then using a 12-bit ADC the resolution is 100/4096 psig. 80% of 100 psig is 80 psig and is usually used as the largest calibration pressure. This avoids placing too much pressure on the sensor and damaging it.
    Once the calibration has been performed an object method is used to read the analog input expressed in engineering units.
    Let’s set up an object to calibrate and scale the analog inputs into engineering units and write it to a file on the SD card as a stringified JSON. I hope Flash GURUs can supply us with a way to do this using FLASH memory.
    The exact entries will vary with the uP hardware and the pins you want to use.

    var AnalogReadObj1={
      calibrations:[
        {pin:'A0',slope:10.0,Intercept:5.0},
        {pin:'A1',slope:50.0,Intercept:-25.0},
        {pin:'A2',slope:1.0,Intercept:0.0},
        {pin:'A3',slope:1.0,Intercept:0.0},
        {pin:'A4',slope:1.0,Intercept:0.0}
      ]
    };//end AnalogReadObj1
    
    function saveCalibration(calibration) {
      var Q;
      var sss=JSON.stringify(calibration);
     if (Q===undefined) {
      Q = E.openFile("Mycalibrations.cal", "w");
      Q.write(sss);
      Q.close();
      Q = undefined;
     }//endif
    }//end doLog
    
    saveCalibration(AnalogReadObj1); 
    console.log(require("fs").readFileSync("­Mycalibrations.cal")); 
    
    

    The left WebIDE screen:

    >echo(0);
    {"calibrations":[{"pin":"A0","slope":10,­"Intercept":5},{"pin":"A1","slope":50,"I­ntercept":-25},{"pin":"A2","slope":1,"In­tercept":0},{"pin":"A3","slope":1,"Inter­cept":0},{"pin":"A4","slope":1,"Intercep­t":0}]}
    =undefined
    
    

    Now that the pattern has been saved how to use it?
    First read it as a string into a variable for example sss.

    var sss=(require("fs").readFileSync("Mycalib­rations.cal")); 
    console.log(sss);
    
    

    Apply the string to the constructor and use the calibrations

    var sss=(require("fs").readFileSync("Mycalib­rations.cal")); 
    //console.log(sss);
    
    //The constructor for AnalogReadObj
    function AnalogReadObj(ARO){
      this.A=JSON.parse(ARO);
    }//end AnalogReadObj
    //Methods for AnalogReadObj
    AnalogReadObj.prototype.SanalogRead=func­tion(Arg){
        for(var i=0;i in this.A.calibrations;i++){
    //      console.log(i,Arg,this.A.calibrations[i]­.pin);
         if(Arg===this.A.calibrations[i].pin){
      return( analogRead(Arg)*this.A.calibrations[i].s­lope+this.A.calibrations[i].Intercept);
         }//endif
        }//next i
        return -99;
    };//end SanalogRead
    
    AnalogReadObj.prototype.SetAnalog=functi­on(Arg){
        for(var i=0;i in this.A.calibrations;i++)
         pinMode(this.A.calibrations[i].pin, 'analog');
    };//end SetAnalog
    
    //create an instance of AnalogReadObject using the string data contained in 
    //the variable sss
    var ScaledAR=new AnalogReadObj(sss);
    //console.log(ScaledAR.calibrations);
    // setup the pins for analog
    ScaledAR.SetAnalog();
    // Now do a scaled read of pin A0
    console.log("A0= ",ScaledAR.SanalogRead('A0'));
    // Now do a scaled read of pin A1
    console.log("A1= ",ScaledAR.SanalogRead('A1'));
    // Now do a scaled read of pin A2
    console.log("A2= ",ScaledAR.SanalogRead('A2'));
    
    

    The left side of the WebIDE:

    >echo(0);
    A0=  12.72961013199
    A1=  12.70809491111
    A2=  0.72095826657
    =undefined
    >
    
    

    2 Attachments

  • A Object to Calibrate and Scale Analog Inputs to Engineering Units( In three installments)
    Part 2 Linear Regression

    Now to add a method to perform a calibration.
    This is done by collecting data and performing a linear regression.
    Look at the numerical example in the following link:
    https://en.wikipedia.org/wiki/Simple_lin­ear_regression
    The X values are the raw analog readings and the Y values are the values from the calibration device. We need five sums:
    Sx=Sum of Xi values,
    Sy= Sum of Yi values,
    Sxx= Sum of the product Xi*Xi,
    Sxy= Sum of the product Xi*Yi, and the
    Syy =Sum of the product Yi*Yi.

    The slope is given by:
    Slope = (N*Sxy - Sx*Sy)/( NSxx - Sx Sx),
    And the Intercept is given by:
    Intercept = Sy/N - (Slope * Sx) / N, where
    N = the number of samples.

    A linear regression object is defined as follows:

    //The constructor for LinRegObj
    function LinRegObj(a,nn){
      this.Sx=0;
      this.Sy=0;
      this.Sxy=0;
      this.Sxx=0;
      this.Syy=0;
      this.N=0;
      this.A=a;
      this.NN=nn;
      this.slope=0;
      this.intercept=0;
    }//end LinRegObj
    //Methods for LinRegObj
    //LinRegObj.prototype.TakeData=function(­y){
    LinRegObj.prototype.TakeData=function(x,­y){  for(i=0;i<this.NN;i++){
    //    var x=analogRead(this.A.pin);
        this.Sx+=x;
        this.Sy+=y;
        this.Sxy+=(x*y);
        this.Sxx+=(x*x);
        this.Syy+=(y*y);
        this.N++;
      }//next i
    };//endTakeData
    
    LinRegObj.prototype.Calculate=function()­{
     this.slope=(this.N*this.Sxy-this.Sx*this­.Sy)/
       (this.N*this.Sxx - this.Sx*this.Sx);
     this.intercept=this.Sy/this.N-(this.slop­e*this.Sx)/this.N;
    //this.A.slope=this.slope;
    //this.A.Intercept=this.intercept;
    };//end Calculate
    //end LinRegObj methods
    
    //test example calculation
    var Q=new LinRegObj(1,5);
    Q.TakeData(0,0);
    Q.TakeData(1,80);
    Q.Calculate();
    console.log("Slope= ",Q.slope);
    console.log("Intercept= ",Q.intercept);
    
    

    The object is commented to omit actual analogRead() and the X values are simulated to test the math.
    The left WebIDE screen displays the following correct answer:

    >echo(0);
    Slope=  80
    Intercept=  0
    =undefined
    >
    Part 3 will follow soon.
    
  • **A Object to Calibrate and Scale Analog Inputs to Engineering Units( In three installments)
    Part 3:
    **
    Let’s add two fields to the calibration file, name and units.

    var AnalogReadObj1={
      calibrations:[
        {name:"Inlet",units:"psig",pin:'A0',slop­e:10.0,Intercept:5.0},
        {name:"Outlet",units:"psig",pin:'A1',slo­pe:50.0,Intercept:-25.0},
        {name:"Motor",units:"Amps",pin:'A2',slop­e:1.0,Intercept:0.0},
        {name:"Supply",units:"Volts",pin:'A3',sl­ope:1.0,Intercept:0.0},
        {name:"Flow",units:"Liters/minute",pin:'­A4',slope:1.0,Intercept:0.0}
      ]
    };//end AnalogReadObj1
    
    function saveCalibration(calibration) {
      var Q;
      var sss=JSON.stringify(calibration);
     if (Q===undefined) {
      Q = E.openFile("Mycalibrations.cal", "w");
      Q.write(sss);
      Q.close();
      Q = undefined;
     }//endif
    }//end doLog
    
    saveCalibration(AnalogReadObj1); 
    console.log(require("fs").readFileSync("­Mycalibrations.cal"));
    
    

    The left screen:

    >echo(0);
    {"calibrations":[{"name":"Inlet","units"­:"psig","pin":"A0","slope":10,"Intercept­":5},
    {"name":"Outlet","units":"psig","pin":"A­1","slope":50,"Intercept":-25},
    {"name":"Motor","units":"Amps","pin":"A2­","slope":1,"Intercept":0},
    {"name":"Supply","units":"Volts","pin":"­A3","slope":1,"Intercept":0},
    {"name":"Flow","units":"Liters/minute","­pin":"A4","slope":1,"Intercept":0}]}
    =undefined
    > 
    
    

    The code that knits the two objects together and implements a two point calibration.
    The calibration is save to the calibration file on the SD card followed by the reading of the calibrated analog inputs in engineering units (EU).
    Button 1 on the Espruino board is used to step through the steps. Entering A= selects the channel to calibrate. During the calibration entering B= is used to set the EU values needed for calibration. If A=-1 exits calibration, and proceeds to reading the analog inputs after each press of button1.

    var sss=(require("fs").readFileSync("Mycalib­rations.cal")); 
    //console.log(sss);
    
    //The constructor for AnalogReadObj
    function AnalogReadObj(ARO){
      this.A=JSON.parse(ARO);
    }//end AnalogReadObj
    //Methods for AnalogReadObj
    
    AnalogReadObj.prototype.saveCalibration=­function() {
      var Q;
      var sss=JSON.stringify(this.A);
      console.log("Saving calibrations");
      console.log(this.A);
     if (Q===undefined) {
      Q = E.openFile("Mycalibrations.cal", "w");
      Q.write(sss);
      Q.close();
      Q = undefined;
     }//endif
    };//saveCalibration
    
    
    AnalogReadObj.prototype.SanalogRead=func­tion(Arg){
        for(var i=0;i in this.A.calibrations;i++){
    //      console.log(i,Arg,this.A.calibrations[i]­.pin);
         if(Arg===this.A.calibrations[i].pin){
      return( analogRead(Arg)*this.A.calibrations[i].s­lope+this.A.calibrations[i].Intercept);
         }//endif
        }//next i
        return -99;
    };//end SanalogRead
    
    AnalogReadObj.prototype.getName=function­(Arg){
        for(var i=0;i in this.A.calibrations;i++){
    //      console.log(i,Arg,this.A.calibrations[i]­.pin);
         if(Arg===this.A.calibrations[i].pin){
          return( this.A.calibrations[i].name);
         }//endif
        }//next i
        return -99;
    };//end SanalogRead
    
    AnalogReadObj.prototype.getUnits=functio­n(Arg){
        for(var i=0;i in this.A.calibrations;i++){
    //      console.log(i,Arg,this.A.calibrations[i]­.pin);
         if(Arg===this.A.calibrations[i].pin){
          return( this.A.calibrations[i].units);
         }//endif
        }//next i
        return -99;
    };//end SanalogRead
    
    AnalogReadObj.prototype.SetAnalog=functi­on(Arg){
        for(var i=0;i in this.A.calibrations;i++)
         pinMode(this.A.calibrations[i].pin, 'analog');
    };//end SetAnalog
    //end methods for AnalogReadObj
    
    //The constructor for LinRegObj
    function LinRegObj(a,nn){
      this.Sx=0;
      this.Sy=0;
      this.Sxy=0;
      this.Sxx=0;
      this.Syy=0;
      this.N=0;
      this.A=a;
      this.NN=nn;
      this.slope=0;
      this.intercept=0;
    }//end LinRegObj
    //Methods for LinRegObj
    LinRegObj.prototype.TakeData=function(y)­{
    //LinRegObj.prototype.TakeData=function(­x,y){
      for(i=0;i<this.NN;i++){
        var x=analogRead(this.A.pin);
        this.Sx+=x;
        this.Sy+=y;
        this.Sxy+=(x*y);
        this.Sxx+=(x*x);
        this.Syy+=(y*y);
        this.N++;
      }//next i
    };//endTakeData
    
    LinRegObj.prototype.Calculate=function()­{
     this.slope=(this.N*this.Sxy-this.Sx*this­.Sy)/
       (this.N*this.Sxx - this.Sx*this.Sx);
     this.intercept=this.Sy/this.N-(this.slop­e*this.Sx)/this.N;
     this.A.slope=this.slope;
     this.A.Intercept=this.intercept;
    };//end Calculate
    
    LinRegObj.prototype.ListChannels=functio­n(T){
      for(var i=0;i in T;i++){
        console.log(i,","+T[i].name+","+T[i].uni­ts);
      }//next i
    };//end List Channels
    //end LinRegObj methods
    var A=0;//used to select channel -1 exits calibration sequence
    var B=0;//used to input EU values during calibration
    
    //create an instance of AnalogReadObject using the string data contained in 
    //the variable sss
    var ScaledAR=new AnalogReadObj(sss);
    //console.log(ScaledAR.calibrations);
    // setup the pins for analog
    ScaledAR.SetAnalog();
    //test example calculation
    var Q=new LinRegObj(ScaledAR.A.calibrations[0],5);­// 5 samples to take
    //SelectChannel();
    console.log("Press button on Espruino board to start");
    var state=10;
    
    function doButton(){
      if (digitalRead(BTN1) === 1){
        switch (state){
          case 10:
           Q.ListChannels(ScaledAR.A.calibrations);­
           console.log(' ');
           console.log("Select channel by number");
           console.log("Type A=<number>; in left pane");
           console.log("Type A=-1; in the left pane to exit calibrations");
           console.log("Then press button on Espruino board");
           console.log(' ');
           state=20;
          break;
    
          case 20:
           if(A<0){
            console.log("Exit");
            ScaledAR.saveCalibration();
            state=200;
            return;
           }
           if(A in ScaledAR.A.calibrations){
            console.log("Calibrating ",ScaledAR.A.calibrations[A].name);
            console.log("Apply low value (0.0) input to sensor");
            console.log("Enter the engineering units (EU) by typing in left pane");
            console.log("B=<EU>;");
            console.log("Then press button on the Espruino board");
           }//endif A in Q
           state=30;
          break;
    
          case 30:
           Q.TakeData(B);
           console.log("Calibrating ",ScaledAR.A.calibrations[A].name);
           console.log("Apply high value (80% FS) input to sensor");
           console.log("Enter the engineering units (EU) by typing in left pane");
           console.log("B=<EU>;");
           console.log("Then press button on the Espruino board");
           state=40;
          break;
    
          case 40:
           Q.TakeData(B);
           Q.Calculate();
           console.log("Calibrating ",ScaledAR.A.calibrations[A].name);
           console.log("Slope= ",Q.slope);
           console.log("Intercept= ",Q.intercept);
           console.log("Press button on the Espruino board");
           state=10;
          break;
          case 200:
           console.log(' ');
           // Now do a scaled read of pin A0
           console.log(ScaledAR.getName('A0')," A0= ",ScaledAR.SanalogRead('A0').toFixed(2)+­" ",ScaledAR.getUnits('A0'));
           // Now do a scaled read of pin A1
           console.log(ScaledAR.getName('A1')," A1= ",ScaledAR.SanalogRead('A1').toFixed(2)+­" ",ScaledAR.getUnits('A1'));
           // Now do a scaled read of pin A2
           console.log(ScaledAR.getName('A2')," A2= ",ScaledAR.SanalogRead('A2').toFixed(2)+­" ",ScaledAR.getUnits('A2'));
           // Now do a scaled read of pin A3
           console.log(ScaledAR.getName('A3')," A3= ",ScaledAR.SanalogRead('A3').toFixed(2)+­" ",ScaledAR.getUnits('A3'));
           // Now do a scaled read of pin A4
           console.log(ScaledAR.getName('A4')," A4= ",ScaledAR.SanalogRead('A4').toFixed(2)+­" ",ScaledAR.getUnits('A4'));
           console.log("Press button on Espruino board");
          return;
    
          default:
        }//end switch
      }//end BTN1
    }//end doButton
    
    setWatch(doButton, BTN1, true);
    
    

    The left pane view:

    Press button on Espruino board to start
    =undefined
    0 ,Inlet,psig
    1 ,Outlet,psig
    2 ,Motor,Amps
    3 ,Supply,Volts
    4 ,Flow,Liters/minute
     
    Select channel by number
    Type A=<number>; in left pane
    Type A=-1; in the left pane to exit calibrations
    Then press button on Espruino board
     A=0;
    Calibrating  Inlet
    Apply low value (0.0) input to sensor
    Enter the engineering units (EU) by typing in left pane
    B=<EU>;
    Then press button on the Espruino board
    >B=0;
    =0
    Calibrating  Inlet
    Apply high value (80% FS) input to sensor
    Enter the engineering units (EU) by typing in left pane
    B=<EU>;
    Then press button on the Espruino board
    >B=80;
    =80
    Calibrating  Inlet
    Slope=  100.13897386465
    Intercept=  -0.03419722673
    Press button on the Espruino board
    0 ,Inlet,psig
    1 ,Outlet,psig
    2 ,Motor,Amps
    3 ,Supply,Volts
    4 ,Flow,Liters/minute
    Xxxxxxxxxxxxxxxxxxxx
    A=-1; exits the calibration sequence.
    Xxxxxxxxxx
    >A=-1
    =-1
    Exit
    Saving calibrations
    {
      "calibrations": [
        {
          "name": "Inlet",
          "units": "psig",
          "pin": "A0",
          "slope": 100.13897386465, "Intercept": -0.03419722673 },
        {
          "name": "Outlet",
          "units": "psig",
          "pin": "A1",
          "slope": 50, "Intercept": -25 },
        {
          "name": "Motor",
          "units": "Amps",
          "pin": "A2",
          "slope": 1, "Intercept": 0 },
        {
          "name": "Supply",
          "units": "Volts",
          "pin": "A3",
          "slope": 1, "Intercept": 0 },
        {
          "name": "Flow",
          "units": "Liters/minute",
          "pin": "A4",
          "slope": 1, "Intercept": 0 }
       ]
     }
     
    Inlet  A0=  79.94  psig
    Outlet  A1=  12.00  psig
    Motor  A2=  0.72  Amps
    Supply  A3=  0.69  Volts
    Flow  A4=  0.82  Liters/minute
    Press button on Espruino board
    
    

    Testing was performed using five 1k resistors wired in series. One end connects to GND and the other end to +3.3V pins. A wire from the analog input pin can then be connected to connections along the resistor chain.


    2 Attachments

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

A Object to Calibrate and Scale Analog Inputs to Engineering Units( In three installments)

Posted by Avatar for ClearMemory041063 @ClearMemory041063

Actions