-
• #2
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_linear_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.slope*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.
-
• #3
**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',slope:10.0,Intercept:5.0}, {name:"Outlet",units:"psig",pin:'A1',slope:50.0,Intercept:-25.0}, {name:"Motor",units:"Amps",pin:'A2',slope:1.0,Intercept:0.0}, {name:"Supply",units:"Volts",pin:'A3',slope: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":"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}]} =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("Mycalibrations.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=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( analogRead(Arg)*this.A.calibrations[i].slope+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=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].units); }//endif }//next i return -99; };//end SanalogRead AnalogReadObj.prototype.SetAnalog=function(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.slope*this.Sx)/this.N; this.A.slope=this.slope; this.A.Intercept=this.intercept; };//end Calculate LinRegObj.prototype.ListChannels=function(T){ for(var i=0;i in T;i++){ console.log(i,","+T[i].name+","+T[i].units); }//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
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.
The left WebIDE screen:
Now that the pattern has been saved how to use it?
First read it as a string into a variable for example sss.
Apply the string to the constructor and use the calibrations
The left side of the WebIDE:
2 Attachments