smart sensor code initial version
Dependencies: mbed-src-KL05Z-smart-sensor
Diff: kl05-smart-sensor.cpp
- 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