smart sensor code initial version

Dependencies:   mbed-src-KL05Z-smart-sensor

Revision:
0:10bf1bb6d2b5
Child:
1:587e0346abca
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kl05-smart-sensor.cpp	Tue Mar 26 10:37:02 2019 +0000
@@ -0,0 +1,561 @@
+/****************************************************************************************
+* 
+*   MIT License (https://spdx.org/licenses/MIT.html)
+*   Copyright 2018 NXP
+*
+*   MBED code for KL05Z-based "smart" current sensor, which measures current in 
+*   three ranges. Intended to be used with an aggregator board which triggers sensors
+*   on all instrumented rails and then sequentially reads the data from each out over I2C.
+*   
+*   Because there is no crystal on the board, need to edit source mbed-dev library
+*   to use internal oscillator with pound-define:
+*   change to "#define CLOCK_SETUP     0" in file:
+*   mbed-dev/targets/TARGET_Freescale/TARGET_KLXX/TARGET_KL05Z/device/system_MKL05Z4.c
+*
+****************************************************************************************/
+
+#include <mbed.h>
+
+#define USEI2CNOTUART 0
+
+// set things up...
+#if (USEI2CNOTUART == 1) 
+I2CSlave slave(PTB4, PTB3);
+#else
+Serial uart(PTB3, PTB4); // tx, rx
+#endif
+
+// These will be used for identifying smart sensor build options:
+// voltage range (0-3.3V, 0-6.6V, and 12V), and 
+// current range (high: 4A max, and low: 1.65A max)
+// (default pin pulls are pull up...)
+// But this still needs to be implemented per schematic... 
+DigitalIn gpio0(PTA3); // R8
+DigitalIn C_RANGE(PTA4); // R9
+DigitalIn V_RANGE0(PTA5); // R10
+DigitalIn V_RANGE1(PTA6); // R11
+
+// configure pins for measurements...
+// analog inputs from sense amps and rail voltage (divider)...
+AnalogIn HIGH_ADC(PTB10);
+AnalogIn VRAIL_ADC(PTB11);
+AnalogIn LOW1_ADC(PTA9);
+AnalogIn LOW2_ADC(PTA8);
+// outputs which control switching FETs...
+DigitalOut VRAIL_MEAS(PTA7);    // turns on Q7, connecting voltage divider 
+DigitalOut LOW_ENABLE(PTB0);    // turns on Q4, turning off Q1, enabling low measurement
+DigitalOut LOW1(PTB2);          // turns on Q5, turning off Q2, disconnecting shunt R1
+DigitalOut LOW2(PTB1);          // turns on Q6, turning off Q3, disconnecting shunt R2
+
+
+
+// set initial, default I2C listening address...
+// same one for all sensors so we don't need to individually program each one...
+int address = 0x48 << 1;
+// buffers for I2C communication
+char buf[15], inbuf[10];
+char obuf[10], cbuf[10]; // another buf for compressed output...
+
+// variables...
+int i, j, n=0;
+bool waiting;
+bool big_data = false; // flag to save time during ISR 
+                        // only process uncompressed data if explicitly called for...
+
+// these unions enable converting float val to bytes for transmission over I2C...
+union u_tag {
+     char b[4];
+     float fval;
+     int ival;
+   } u, v;
+
+//union u_current {
+//  float high;
+//  float mid;
+//  float low;
+//} current;
+float current[3];
+
+// define measurement result and status variables...
+float measurement1;
+float measurement2;
+char status=0;
+//int n_meas=25; // number of averages when measuring...
+int n_meas=1; // number of averages when measuring...
+float vref =3.3;
+float factor_H  = vref / 0.8;
+float factor_L1 = vref / (0.05 * 1000);
+float factor_L2 = vref / (2 * 1000);
+
+int wait_mbbb = 5;
+int wait_high = 250;
+int wait_low1 = 250;
+int wait_low2 = 500;
+int wait_vrail = 200;
+
+Timer timer;
+float timestamp;
+
+/***********************************************************************************
+*
+*  FUNCTIONS FOR MEASURING CURRENT AND VOLTAGE
+*
+************************************************************************************/
+
+void enableHighRange(){
+    LOW_ENABLE = 0;     // short both low current shunts, close Q1
+    wait_us(wait_mbbb);         // delay for FET to settle... (make before break)
+    LOW1 = 0; LOW2 = 0; // connect both shunts to make lower series resistance
+    VRAIL_MEAS = 0;     // disconnect rail voltage divider    
+    wait_us(wait_high);       // wait for B2902A settling... 
+}
+
+void enableLow1Range(){
+    LOW1 = 0; LOW2 = 1; // disconnect LOW2 shunt so LOW1 can measure
+    wait_us(wait_mbbb);         // delay for FET to settle... (make before break)
+    LOW_ENABLE = 1;     // unshort low current shunts, open Q1
+    VRAIL_MEAS = 0;     // disconnect rail voltage divider
+    wait_us(wait_low1);       // wait for B2902A settling...
+}
+
+void enableLow2Range(){
+    LOW1 = 1; LOW2 = 0; // disconnect LOW1 shunt so LOW2 can measure
+    wait_us(wait_mbbb);         // delay for FET to settle... (make before break)
+    LOW_ENABLE = 1;     // unshort low current shunts, open Q1
+    VRAIL_MEAS = 0;     // disconnect rail voltage divider    
+    wait_us(wait_low2);       // wait for B2902A settling...
+}
+
+void enableRailV(){
+    VRAIL_MEAS = 1;     // turn on Q7, to enable R3-R4 voltage divider    
+    wait_us(wait_vrail);       // wait for divider to settle... 
+                        // Compensation cap can be used to make 
+                        // voltage at ADC a "square wave" but it is
+                        // rail voltage and FET dependent. Cap will
+                        // need tuning if this wait time is to be 
+                        // removed/reduced. 
+                        //
+                        // So, as it turns out, this settling time and 
+                        // compensation capacitance are voltage dependent
+                        // because of the depletion region changes in the 
+                        // FET. Reminiscent of grad school and DLTS. 
+                        // Gotta love device physics...
+}
+
+// when a divider is present, turn it off to remove the current it draws...
+void disableRailV(){
+    VRAIL_MEAS = 0;     // turn off Q7, disabling R3-R4 voltage divider    
+}
+
+// measure high range current...
+float measureHigh(int nbMeas){
+    float highI=0;
+    enableHighRange();
+    for (i = 0; i < nbMeas; i++){
+        highI += HIGH_ADC;
+    }
+    highI = factor_H * highI/nbMeas;
+    timestamp = timer.read();
+    return highI;
+}
+
+// mesaure mid range current...
+float measureLow1(bool autorange, int nbMeas){
+    float low1I=0;
+    if (!autorange) enableLow1Range();
+    for (i = 0; i < nbMeas; i++){
+        low1I += LOW1_ADC;
+    }
+    if (!autorange) enableHighRange();
+    low1I = factor_L1 * low1I/nbMeas;
+    timestamp = timer.read();
+    return low1I;
+}
+
+// measure low range current...
+float measureLow2(bool autorange, int nbMeas){
+    float low2I=0;
+    if (!autorange) enableLow2Range();
+    for (i = 0; i < nbMeas; i++){
+        low2I += LOW2_ADC;
+    }
+    if (!autorange) enableHighRange();
+    low2I = factor_L2 * low2I/nbMeas;
+    timestamp = timer.read();
+    return low2I;
+}
+
+// this function measures current, autoranging as necessary 
+// to get the best measurement...
+// hard coded values for switching ranges needs to be made 
+// dynamic so 4.125A/1.65A ranges can be used...
+#if (USEI2CNOTUART == 1) 
+float measureAutoI(){
+    float tempI;
+    enableHighRange();  // this should already be the case, but do it anyway...
+    tempI = measureHigh();
+    status = 1;
+    // if current is below this threshold, use LOW1 to measure... 
+    if (tempI < 0.060) {
+        enableLow1Range();
+        tempI = measureLow1(false); // call function 
+        status = 2;
+        // if current is below this threshold, use LOW2 to measure...
+        if (tempI < 0.0009){
+            enableLow2Range();  // change FETs to enable LOW2 measurement...
+            tempI = measureLow2(false);
+            status = 3;
+        }
+    enableHighRange();
+    }
+    return tempI;
+}    
+#else
+float measureAutoI_uart(int nbMeas){
+//void measureAutoI_uart(int nbMeas){
+    float tempI;
+    
+    enableHighRange();  // this should already be the case, but do it anyway...
+    current[0] = 0;
+    current[1] = 0;
+    current[2] = 0;
+    tempI = measureHigh(nbMeas);
+//    uart.printf("\r\nnb samples: %d - measureHigh:%f A", nbMeas, tempI);
+    current[0] = tempI;
+    status = 1;
+    // if current is below this threshold, use LOW1 to measure... 
+    if (tempI < 0.060) {
+        enableLow1Range();
+        tempI = measureLow1(false, nbMeas); // call function 
+//        uart.printf("\r\nnb samples: %d - measureLow1:%f A", nbMeas, tempI);
+        current[1] = tempI;
+        status = 2;
+        // if current is below this threshold, use LOW2 to measure...
+        if (tempI < 0.0009){
+            enableLow2Range();  // change FETs to enable LOW2 measurement...
+            tempI = measureLow2(false, nbMeas);
+//            uart.printf("\r\nnb samples: %d - measureLow2:%f A", nbMeas, tempI);
+            current[2] = tempI;
+            status = 3;
+        }
+    enableHighRange();
+    }
+    return tempI;
+}
+#endif
+
+
+// measure the rail voltage, default being with 
+// need to add logic for 5V/12V/arbitraryV range...
+#if (USEI2CNOTUART == 1) 
+float measureRailV(){
+    float railv=0;
+    enableRailV();  // switch FETs so divider is connected...
+    for (i = 0; i < n_meas; i++){
+        railv += VRAIL_ADC;  // read voltage at divider output...
+    }
+    disableRailV();     // now disconnect the voltage divider
+    railv = vref * (railv/n_meas);    // compute average 
+                        // Convert to voltage by multiplying by "mult"
+    timestamp = timer.read();
+    if (vref==12.0) railv = railv * 0.24770642201;
+    return railv;
+}    
+#else
+float measureRailV_uart(int nbMeas){
+    float railv=0;
+    enableRailV();  // switch FETs so divider is connected...
+    for (i = 0; i < nbMeas; i++){
+        railv += VRAIL_ADC;  // read voltage at divider output...
+    }
+    disableRailV();     // now disconnect the voltage divider
+    railv = vref * (railv/nbMeas);    // compute average 
+                        // Convert to voltage by multiplying by "mult"
+    if (vref==12.0) railv = railv * 0.24770642201;
+//    uart.printf("\r\nnb samples: %d - measureRailV:%f V\n", nbMeas, railv);
+    return railv;
+}
+#endif
+/***********************************************************************************
+*
+*  INTERRUPT SERVICE ROUTINE
+* 
+************************************************************************************/
+#if (USEI2CNOTUART == 1) 
+// measurements are only taken during ISR, triggered by aggregator on IRQ line...
+// this could have been implemented differently, but this was simple...
+// If coulomb counting is desired, this code would probably need to change...
+void interrupt_service(){
+    // make measurement... (this is currently just a placeholder...)
+    status = 0; // clear status byte.. allow measurement functions to modify...
+    measurement1 = measureAutoI();   
+    measurement2 = measureRailV();   
+    n += 10; //increment interrupt counter...
+    
+    // prepare data for transport, in the event that aggregator asks for short format... 
+    
+    // compressed data format, 4 bytes total, with a status nibble
+    // Each byte has form:  (s*128) + (digit1*10) + (digit2), which fits into 8 bits
+    // Each value is composed of two bytes with form above, first three digits are 
+    // the mantissa and the last digit is the exponent. Two values is four bytes, so 
+    // that allows four status bits to be included.    
+    sprintf(buf, "%4.2e", measurement1);
+    buf[10] = (buf[0]-48)*10 + (buf[2]-48); // no decimal, we use fixed point... 
+    buf[11] = (buf[3]-48)*10 + (buf[7]-48); // no 'e', and no exp sign, since we know that's negative...
+    sprintf(buf, "%4.2e", measurement2);
+    buf[12] = (buf[0]-48)*10 + (buf[2]-48); // no decimal, we use fixed point... 
+    buf[13] = (buf[3]-48)*10 + (buf[7]-48); // no 'e', and no exp sign, since we know that's negative...
+                
+    // add in the four status bits... 
+    buf[10] = buf[10] | (status & 1<<3)<<4;
+    buf[11] = buf[11] | (status & 1<<2)<<5;
+    buf[12] = buf[12] | (status & 1<<1)<<6;
+    buf[13] = buf[13] | (status & 1<<0)<<7;
+
+    // Convert each 32-bit floating point measurement value into 4 bytes 
+    // using union, so we can send bytes over I2C...
+    u.fval = measurement1; 
+    v.fval = measurement2;
+    
+    // now fill the buffers with the stuff generated above so it can be sent over I2C:
+    
+    // stuff latest measurement float values into bytes of buf for next transmission...
+    // buffer format: 4 bytes = (float) V, 4 bytes = (float) I, 1 byte status
+    for (j=0; j<4; j++) buf[j] = u.b[j]; // voltage
+    for (j=0; j<4; j++) buf[j+4] = v.b[j]; // current
+    buf[8] = status; 
+
+    // transfer compressed measurement data to output buffers...
+//    for (j=0; j<9; j++) obuf[j] = buf[j];
+//    for (j=0; j<4; j++) cbuf[j] = buf[j+10];
+    for (j=0; j<9; j++) obuf[j] = j*10;
+    for (j=0; j<4; j++) cbuf[j] = j*10+10;
+    
+} //ISR
+#endif
+#if (USEI2CNOTUART == 0)
+// input used for triggering measurement...
+// will eventually need to be set up as an interrupt so it minimizes delay before measurement
+InterruptIn trigger(PTA0);        // use as a trigger to make measurement...
+
+// test function to see if trigger pin is being hit...
+// intended for use later to do timed triggering of measurements...
+void triggerIn(){
+    uart.printf("You're triggering me! \r\n");
+    //measureAll();
+}
+#endif
+
+/***********************************************************************************
+*
+*  MAIN CODE
+*
+************************************************************************************/
+
+// main...
+int main() {
+
+  int sensor = 0;
+  float volt = 0;
+  float curr_sensor = 0;
+  int begin=0, end=0;
+  
+  timer.reset();
+  timer.start();
+  
+   buf[0] = 0;
+
+#if (USEI2CNOTUART == 0)
+   uart.baud(115200);
+   uart.printf("Hello World!\r\n");  
+
+   uart.printf("\r\n\n........FROM SENSOR.......\n\n");
+   
+
+   // turn on pull ups for option resistors, since resistors pull down pins
+   C_RANGE.mode(PullUp);
+   V_RANGE0.mode(PullUp);
+   V_RANGE1.mode(PullUp);
+   // change calculation multipliers according to option resistors:
+   i = V_RANGE1*2 + V_RANGE0;
+   if (i==1) vref = 6.6;
+   if (i==2) vref = 12.0;
+   if (C_RANGE==0) {
+        factor_H  = vref / 2.0;
+        factor_L1 = vref / (0.15 * 1000);
+        factor_L2 = vref / (15 * 1000);
+   }
+   
+   uart.printf("\r\nfactor_H: %f", factor_H);
+   uart.printf("\r\nfactor_L1: %f", factor_L1);
+   uart.printf("\r\nfactor_L2: %f", factor_L2);
+
+  while (1) {
+//    // configure the trigger interrupt...
+//    uart.printf("\ntrigger irq...");
+//    trigger.rise(&triggerIn);
+////    uart.printf("\r\nCalling measureAutoI...");
+////    uart.printf("\nmeasureAutoI:%f", measureAutoI());
+
+int nb_samples = 1;
+//    for(int nb_samples=1; nb_samples <=25; nb_samples++) {
+
+//        t.reset();
+//        t.start();
+        begin = timer.read_us();
+        volt = measureRailV_uart(nb_samples);
+        curr_sensor = measureAutoI_uart(nb_samples);
+        end = timer.read_us();
+//        uart.printf("\r\nTotal Measure Time = %d us", end-begin);
+        uart.printf("\r\n%d %d %f %f %f %f %f %f", end-begin, sensor, timestamp, volt, curr_sensor, current[0], current[1], current[2]);
+//        t.stop();
+//        uart.printf("\r\nTotal Measure Time = %f s", t.read());
+//        uart.printf("\r\nTotal Measure Time = %f ms", t.read_ms());
+//        uart.printf("\r\nTotal Measure Time = %f us", t.read_us());
+//        wait_us(100);
+//    }
+  }
+
+   
+ #else  
+   
+   wait_us(200);   // wait before reassigning SWD pin so as to not get locked out...
+   DigitalIn my_select(PTA2); // this is the individual line to each sensor...
+
+   
+   while (my_select) {
+       // wait here until aggregator signals us for address reassignment...  
+   } // end while
+
+   // Need to wait to set up I2C until after we've come out of wait loop above...
+   // Setting up the I2C earlier starts it listening on the bus even if it's not 
+   // being polled, which means that multiple sensors will respond, hanging the bus...
+   slave.frequency(400000); // go as fast as possible...
+   slave.address(address);  // listen on the default address...
+   
+   while (!my_select) {
+      // listen for new address, then repeat it back aggregator...  
+      waiting = true;
+      while (waiting && !my_select){
+         int i = slave.receive();
+         switch (i) {
+            case I2CSlave::WriteAddressed:
+               slave.read(buf, 1);
+               // we just got our new address, provided my_select subsequently changes...
+               waiting = false;
+               break;
+            case I2CSlave::ReadAddressed:
+               slave.write(buf, 1); 
+               // write back our new address to confirm we go it...
+               waiting = false;
+               break;
+         }
+       }
+   } // end while, waiting for address reassignment...  
+
+   // we fell out of loop above, so now change our I2C address to the newly assigned one...
+   // this newly assigned address will not change until we're reset...
+   slave.address(buf[0]);
+   
+   // enable interrupts, need to wait until after getting new I2C address, 
+   // since we cannot respond until we have our new address...
+   InterruptIn triggerIRQ(PTA0);        // this is the ganged interrupt signal to all sensors
+   triggerIRQ.rise(&interrupt_service); // attach the service routine...
+   
+   // make sure we can receive at the new address...
+   // this isn't absolutely necessary, but it's a good check...
+   // if this is removed, the corresponding write in the aggregator code needs to go, too
+   waiting = true;
+   while (waiting){
+      i = slave.receive();
+      switch (i) {
+         case I2CSlave::ReadAddressed:
+            slave.write(buf, 1);
+            waiting = false;
+            break;
+         case I2CSlave::WriteAddressed:
+            slave.read(buf, 1);
+            waiting = false;
+            break;
+      }
+   }
+   
+
+/******************************************************************************/
+   // this is the main loop: 
+   // We just sit here and wait for I2C commands and triggers on IRQ line...
+   //
+   // A triggerIRQ causes measurements in ISR, aggregator must wait at least  
+   // long enough for it to finish before reading back the result(s).   
+   //
+   // results are sent in 9 byte packets: 4 for voltage, 4 for current, and one status,
+   // where voltage and current are floats in units of V and A. Status byte will be 
+   // packed with something later, yet to be defined. 
+   // 
+   // What should be implemented are additional things like setting and reading
+   // back the delays in the GPIO control functions, turning on and off averaging
+   // so we can see what the min and max values are (which also helps tell if we
+   // don't have enough delay in the GPIO functions), and possibly other stuff 
+   // not thought of yet... Definitely not an exercise for this pasta programmer...
+   //
+   while (1) {
+         i = slave.receive();
+         switch (i) {
+            case I2CSlave::ReadAddressed:
+              if (my_select){   // if high, send uncompressed format...             
+                slave.write(obuf, 9);
+                waiting = false;
+              } else {          // if low, send compressed format...
+                slave.write(cbuf, 4);
+                waiting = false;
+              }
+              break;
+            case I2CSlave::WriteAddressed:
+//               slave.read(inbuf, 1);
+//               waiting = false;
+//               break;
+              if (my_select){   // if high, receive two bytes...             
+                 slave.read(inbuf, 2);
+                 waiting = false;
+                 // if we're here, we've recieved two words, so we update the 
+                 // appropriate parameter.
+                 switch (inbuf[0]) {
+                    case 0:
+                        wait_mbbb = inbuf[1];
+                        break;
+                    case 1:
+                        wait_high = inbuf[1]*8;
+                        break;
+                    case 2:
+                        wait_low1 = inbuf[1]*8;
+                        break;
+                    case 3:
+                        wait_low2 = inbuf[1]*8;
+                        break;
+                    case 4: 
+                        wait_vrail = inbuf[1]*8;
+                        break;
+                    case 5:
+                        n_meas = inbuf[1];
+                        break;
+                 } // switch
+                 // and since we're still here, place the new values
+                 // in obuf so we can read back all paramters values
+                 obuf[0] = wait_mbbb;
+                 obuf[1] = wait_high/8;
+                 obuf[2] = wait_low1/8;
+                 obuf[3] = wait_low2/8;
+                 obuf[4] = wait_vrail/8;
+                 obuf[5] = n_meas;
+                 obuf[6] = 0;
+                 obuf[7] = 0;
+                 obuf[8] = 0;
+              } else {        ;// if low, receive one byte...
+                slave.read(inbuf, 1);
+                waiting = false;
+              }
+         } // switch         
+   }  // while(1)
+ #endif  
+   
+}
\ No newline at end of file