i.MX Power Profiling: Triple-range Smart Current Sensor (Prototype)

Document created by David G Dicarlo Employee on Nov 30, 2017Last modified by David G Dicarlo Employee on Feb 12, 2018
Version 13Show Document
  • View in full screen mode

This is preliminary info on a triple-range "smart" current sensor. It features a Kinetis KL05Z with three current sense amplifiers. It allows measurement currents from ~4A down to double digit micro-amps in three ranges.

 

One of the biggest improvements over this dual-range measurement system is that the on-board microcontroller allows near-simultaneous measurement of all instrumented rails on a board. The dual range profiler can only make one measurement at a time. 

 

These are intended to be used with a microncontroller board as act as a trigger and data aggregator. This aggregator could also be used to reprogram the sensor. 

 

When averaging each ADC measurement 250 times (in software, though the ADC does have a hardware average feature), a maxim measurement cycle (including some interim printf's) is 60mS, which includes three current measurements (about 42mS) and the rail voltage measurement. The three current measurements occur when the forced current was in the uA range. (This simple auto-ranging is done by measuring at the highest range first, then the next lower, until the lowest valid measurement is made.)

 

The series resistance added by the smart sensor when in run mode (highest current range) is under 13 milliOhms as measured with 4-point probes and a Keysight B2902B SMU. There was no significant heating of Q1 (DMN2005UFG) observed when forcing constant 3A (max of B2902B) for an extended period of time (minutes).

 

Sample output from source code below when forcing 90uA:

... too Low: 0.004262 A, switching to low1 ==> 
... too Low: 0.000065 A, switching to low2 ==>
Current = 0.000093 A        Current Measure Time = 0.042079 sec
RailV = 0.000 V
                    Total Measure Time = 0.059815 sec

A "power oscilloscope" can be made by triggering measurements at regular intervals and presenting the results graphically....

 

Schematic (shield schematic at bottom of post; still needs to be prototyped):

 

Board Layout, Top:

 

Board Layout, Bottom:

 

Prototype photos (this is an earlier rev than the schematic/layout above):

 

 

FRDM-KL05Z board modified into a programmer using one of the first rev boards which had an error on it. A very small board with the FFC connector has been designed to fit over the SWD header.

 

Code for the on-line MBED compiler:

/******************************************************************************
*
*   MIT License (https://spdx.org/licenses/MIT.html)
*   Copyright 2017-2018 NXP
*
*   MBED code for KL05Z-based "smart" current sensor board, basic testing
*   of functions via UART (connected via FRDM board and OpenSDA USB virtual
*   COM port).
*
*   Eventual goal is to have each smart sensor communicate over I2C to an
*   aggregator board (FRDM board with a custom shield), allowing 1-10 power
*   supply rails to be instrumented. Extra credit effort is to support
*   sensors and aggregator with sigrok...
*
*   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"

// These will be GPIO for programming I2C address... 
// not yet implemented, using as test pins...
DigitalOut addr0(PTA3);
DigitalOut addr1(PTA4);
DigitalOut addr2(PTA5);
DigitalOut addr3(PTA6);

// 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

// 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...

// PTB3/4 can be used as UART or I2C...
// For easier development with one smart sensor, we are using UART here...
Serial uart(PTB3, PTB4); // tx, rx

long int count=0;
int n=25;               // global number of averages for each measurement
int i, temp;
bool repeat=true;       // flag indicating whether measurements should repeat or not
const float vref = 3.3; // set vref for use in calculations...
float delay=0.25;       // default delay between measurement
bool gui = false;       // flag for controlling human vs machine readable output
bool statistics = false;// flag for outputting min and max along with average (GUI mode only)

void enableHighRange(){
    LOW_ENABLE = 0;     // short both low current shunts, close Q1
    wait_us(5);         // 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(250);       // wait for B2902A settling...
}

void enableLow1Range(){
    LOW1 = 0; LOW2 = 1; // disconnect LOW2 shunt so LOW1 can measure
    wait_us(5);         // 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(250);       // wait for B2902A settling...
}

void enableLow2Range(){
    LOW1 = 1; LOW2 = 0; // disconnect LOW1 shunt so LOW2 can measure
    wait_us(5);         // 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(500);       // wait for B2902A settling...
}

void enableRailV(){
    VRAIL_MEAS = 1;     // turn on Q7, to enable R3-R4 voltage divider   
    wait_us(125);       // 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...
}
void disableRailV(){
    VRAIL_MEAS = 0;     // turn off Q7, disabling R3-R4 voltage divider   
}

// this function measures current, autoranging as necessary
// to get the best measurement...
void measureAuto(){
    Timer t;
    float itemp;
    float tempI=0;
    float imin = 1.0;   // used to keep track of the minimum...
    float imax = 0;     // used to keep track of the maximum...
    t.start();          // use timer to see how long things take...
    enableHighRange();  // this should already be the case, but do it anyway...
    for (i = 0; i < n; i++){
        itemp = HIGH_ADC;   // read HIGH range sense amp output
        if (statistics && itemp>imax) imax = itemp; // update max if necessary
        if (statistics && itemp<imin) imin = itemp; // update min if necessary
        tempI += itemp;     // add current sample to running sum
    }
    tempI = tempI/n *vref/0.8;  // compute average we just took...
    if (gui) uart.printf("=> %5.3f ", tempI);
    if (statistics && gui) uart.printf("[%5.3f/%5.3f]   ", imin*vref/0.8, imax*vref/0.8);

    // if current is below this threshold, use LOW1 to measure...
    if (tempI < 0.060) {
        if (!gui) uart.printf("... too Low: %f A, switching to low1 ==>\r\n", tempI);
        tempI=0;
        enableLow1Range();      // change FETs to enable LOW1 measurement...
        imin = 1.0; imax = 0;
        for (i = 0; i < n; i++){
            itemp = LOW1_ADC;   // read LOW1 sense amp output
            if (statistics && itemp>imax) imax = itemp; // update max if necessary
            if (statistics && itemp<imin) imin = itemp; // update min if necessary
            tempI += itemp; // add current sample to running sum
        }
        tempI = tempI/n *vref/0.05/1000;    // compute average we just took...
        if (gui) uart.printf("%6.4f ", tempI);
        if (statistics && gui) uart.printf("[%6.4f/%6.4f]   ", imin*vref/0.05/1000, imax*vref/0.05/1000);

        // if current is below this threshold, use LOW2 to measure...
        if (tempI < 0.0009){
            if (!gui) uart.printf("... too Low: %f A, switching to low2 ==>\r\n", tempI);
            tempI=0;
            enableLow2Range();  // change FETs to enable LOW1 measurement...
            imin = 1.0; imax = 0;
            for (i = 0; i < n; i++){
                itemp = LOW2_ADC;   // read LOW2 sense amp output
                if (statistics && itemp>imax) imax = itemp; // update max if necessary
                if (statistics && itemp<imin) imin = itemp; // update min if necessary
                tempI += itemp; // add current sample to running sum
            }
            tempI = tempI/n *vref/2/1000;   // compute average we just took...
            if (gui) uart.printf("%8.6f ", tempI);
            if (statistics && gui) uart.printf("[%8.6f/%8.6f]   ", imin*vref/2/1000, imax*vref/2/1000);
        }
    }
    t.stop();   // stop the timer to see how long it took do do this...
    enableHighRange();
    if (!gui) uart.printf("\r\nCurrent = %f A        Current Measure Time = %f sec\r\n", tempI, t.read());
}   

// the autoranging should really be done with functions that return values, as should the
// functions below... This would make for shorter and more elegant code, but the author
// is a bit of a pasta programmer...
void measureHigh(){
    float highI=0;
    enableHighRange();
    for (i = 0; i < n; i++){
        highI += HIGH_ADC;
    }
    highI = highI/n;
    uart.printf("HIghI = %f A\r\n", vref*highI/0.8);
}

void measureLow1(){
    float low1I=0;
    enableLow1Range();
    for (i = 0; i < n; i++){
        low1I += LOW1_ADC;
    }
    enableHighRange();
    low1I = low1I/n;
    uart.printf("low1I = %f A\r\n", vref*low1I/0.05/1000);
}

void measureLow2(){
    float low2I=0;
    enableLow2Range();
    for (i = 0; i < n; i++){
        low2I += LOW2_ADC;
    }
    enableHighRange();
    low2I = low2I/n;
    uart.printf("low2I = %f A\r\n", vref*low2I/2/1000);
}

// measure the rail voltage, default being with
// a divide by 2 resistor divider
// It has to be switched out when not in use or it will
// add to the measured current, at least in the low ranges...
void measureRailV(){
    float railv=0;
    float mult = vref*2;   // since divide by 2, we can measure up to 6.6V...
    float vmin = 5; float vmax = 0;
    float vtemp;
    enableRailV();  // switch FETs so divider is connected...
    for (i = 0; i < n; i++){
        vtemp = VRAIL_ADC;  // read voltage at divider output...
        if (statistics && vtemp>vmax) vmax = vtemp; // update max if necessary
        if (statistics && vtemp<vmin) vmin = vtemp; // update min if necessary
        railv += vtemp;     // add current sample to running sum
    }
    disableRailV();     // now disconnect the voltage divider
    railv = railv/n;    // compute average (note this is in normalized ADC [0..1])
                        // Convert to voltage by multiplying by "mult"
    if (!gui) uart.printf("RailV = %5.3f V ", mult*railv);
    if (gui) uart.printf("%5.3f ", mult*railv);
    if (statistics && gui) uart.printf("[%5.3f/%5.3f]  ", mult*vmin, mult*vmax);
    uart.printf("\r\n");
}   

// not sure how useful this function is...   
void measureAll(){
    measureHigh();
    measureLow1();
    measureLow2();
    measureRailV();   
}

// 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();
}

// main...
int main()
{
    // set up basic conditions...
    Timer m;
    uart.baud(115200);
    enableHighRange();  // default state - only HIGH sense amp in circuit, no divider
   
    // signal that we're alive...
    uart.printf("Hello World!\r\n");
   
    // configure the trigger interrupt...
    trigger.rise(&triggerIn);
   
    while (true) {
        count++;
        wait(delay);
       
        if (repeat){        // if repeat flag is set, keep making measurements...
            m.reset();      // reset and start timer...
            m.start();
            measureAuto();  // measuring current using auto-ranging...
            measureRailV(); // measure rail voltage...
            m.stop();       // stop the timer.
            if (!gui) uart.printf("                   Total Measure Time = %f sec", m.read());
            if (!gui) uart.printf("\r\n\r\n");
        }

        // see if there are any characters in the receive buffer...
        // this is how we change things on the fly...
       
        // Commands (single keystroke... it's easier)
        //  t = one shot automeasure
        //  v = measure volt
        //  h = one shot high measure
        //  k = one shot LOW1 measure
        //  l = one shot LOW2 measure (letter l)
        //  r = toggle repeat
        //  R = turn off repeat
        //  + = faster repeat rate
        //  - = slower repeat rate
        //  = = set repeat rate to 0.25 sec
        //  g = use human readable text output
        //  G = use compressed text format for GUI
        //  s = turn statistics output off
        //  S = turn statistics output on (only in GUI mode)
        //  n = decrease number of averages for each measurement
        //  N = increase number of averages for each measurement
        //
        // these were for testing FET switching...
        //  1 = LOW_ENABLE = 0 (the number 1)
        //  2 = LOW1 = 0
        //  3 = LOW2 = 0
        //  4 = VRAIL_MEAS = 0
        //  ! = LOW_ENABLE = 1
        //  @ = LOW1 = 1
        //  # = LOW2 = 1
        //  $ = VRAIL_MEAS = 1    
        if (uart.readable()){
            temp = uart.getc();
            if (temp==(int) 't') {
                if (!gui) uart.printf("Keyboard trigger: ");
                measureAuto();
                measureRailV();
                //measureAll();
            }
            if (temp==(int) 'v') {
                uart.printf("Keyboard trigger: ");
                measureRailV();
            }
            if (temp==(int) 'h') {
                uart.printf("Keyboard trigger: ");
                measureHigh();
            }
            if (temp==(int) 'k') {
                uart.printf("Keyboard trigger: ");
                measureLow1();
            }
            if (temp==(int) 'l') {
                uart.printf("Keyboard trigger: ");
                measureLow2();
            }
           
            if (temp==(int) '1') {
                LOW_ENABLE = 0;
                uart.printf("Keyboard trigger: LowEnable = %d\r\n", 0);
            }
            if (temp==(int) '2') {
                LOW1 = 0;
                uart.printf("Keyboard trigger: LOW1 = %d\r\n", 0);
            }
            if (temp==(int) '3') {
                LOW2 = 0;
                uart.printf("Keyboard trigger: LOW2 = %d\r\n", 0);
            }
            if (temp==(int) '4') {
                VRAIL_MEAS = 0;
                uart.printf("Keyboard trigger: VRAILMEAS = %d\r\n", 0);
            }
            if (temp==(int) '!') {
                LOW_ENABLE = 1;
                uart.printf("Keyboard trigger: LowEnable = %d\r\n", 1);
            }
            if (temp==(int) '@') {
                LOW1 = 1;
                uart.printf("Keyboard trigger: LOW1 = %d\r\n", 1);
            }
            if (temp==(int) '#') {
                LOW2 = 1;
                uart.printf("Keyboard trigger: LOW2 = %d\r\n", 1);
            }
            if (temp==(int) '$') {
                VRAIL_MEAS = 1;
                uart.printf("Keyboard trigger: VRAILMEAS = %d\r\n", 1);
            }

            if (temp==(int) 'r') {
                repeat = !repeat;
                uart.printf("Keyboard trigger: repeat toggle: %s \r\n", repeat ? "true" : "false");
            }
            if (temp==(int) 'R') repeat = false;

            if (temp==(int) '+') {
                delay -= 0.05;
                if (delay<0.05) delay = 0.05;
            }
            if (temp==(int) '-') {
                delay += 0.05;
                if (delay>1) delay = 1;
            }
            if (temp==(int) '=') delay = 0.25;

            if (temp==(int) 'g') gui = false;
            if (temp==(int) 'G') gui = true;

            if (temp==(int) 's') statistics = false;
            if (temp==(int) 'S') statistics = true;

            if (temp==(int) 'n') {
                n -= 25;
                if (n<25) n = 25;
            }
            if (temp==(int) 'N') {
                n += 25;
                if (n>1000) n = 1000;
            }
            if (temp==(int) 'N' || temp==(int) 'n') uart.printf("/r/n/r/n Averages = %d \r\n\r\b", n);

         }

    }
}

 

FRDM Shield schematic, accommodates up to 9 smart sensors:

Outcomes