/* This file is part of MANTIS OS, Operating System for Nymph. See http://mantis.cs.colorado.edu/ Copyright (C) 2003 University of Colorado, Boulder */ /** * @File: PAR_ADboard_CurrentMonitor.c * @Brief: A file to test interface board w/4ea ads1112 adc's * @Author: jwm * @Date: feb-07 * * Required Files: * sprintff.c formats floating point, and other 'sprintf' forms to buffer * i2c_support.c local, segregated subroutines for running the MOS i2c * uart.c 'Aaron Beech's version that actually allows 'RAW_MODE' without mos header * par_cal.h NEW: location where the actual PAR wand calibrations are loaded from * NOTE: This file will need to be built prior to making this routine * It will consist of the ..... * * Modifications * * HIGHLY ADAPTED VERSION OF PAR_ADboard * USED ONLY TO SAMPLE JUST 1 CHANNEL, DIFFERENTIALLY, WITH GAIN * TO BUILD-UP A SHUNT CURRENT MONITOR.... * Version-1 27Aug07, jwm * - Disabled LEVEL sensor stuff not needed for above...may remove * - nwands=1, nsensors=1 * - Added multiple samples in output message * - Disabled multiple sensors in sampling....swapped for above... * - removed 'wandSpecific' and put structs inline * - double indexed data: vraw, vdc * - disabled the radio to save power * Version-2 28Aug07, jwm * - Enabled 'CONTINUOUS_SAMPLING' via the Global: conversion_mode * to speed things up, esp. when single-channel sampling mode. * * * The reason this was done was to allow us (Ramesh/I) to measure the * current draw of the MOS 'Tsoil' application in comparison to the TOS * based old Tsoil application because it appears that the MOS code * style is drawing too much current, and thereby draining batteries * excessively as has been observed in the forest by Lynette. */ #include "mos.h" #include "command_daemon.h" #include "printf.h" #include "com.h" #include "led.h" #include "dev.h" #include "avr-i2c.h" #include "avr-eeprom.h" //devines the DEV_AVR_EEPROM_SEEK ioctl #include "avr-adc.h" #include "adc.h" // Declarations //int sprintff( char *, const char *, ...); //static char debugbuf[256]; // local buffer for all // LED Masks #define YELLOW 1 #define GREEN 2 #define RED 4 // Calibration Information / Ouput Type Definition // enum { RAW =1, // These must be different bits to allow 'both' output MV =2, LINEAR=4, POLY =8 }; // ---------------------------------------------------- // *** EDIT THESE DEFINITIONS TO CONTROL OPERATIONS *** // ---------------------------------------------------- //#define PRINT_HEADER //uncomment to have just the values reported #define CAL_METHOD LINEAR // include all format desired. ie... RAW|MV|LINEAR|POLY #define POLLING_RATE 1 // mSeconds between polling cycles #define SAMPLING_TIME_FUDGE 8 // wild guess at mS eaten during i2c sampling // used to adjust the POLLING_RATE sleep time #define NSAMPLES_PER_MESSAGE 1 #define WANDSENSORS 1 // 3 pars per wand #define NWANDS 1 #define ADC_RATE 30 // Sampling frequency can be: // 15 (default), 30, 60, 240 hz // see adsRange structure #define ADC_GAIN 3 // ADC internal gain setting can be 0,1,2,3 // see adsPGA structure // 0 = gain1, 0-2.048 (in se mode) // 1 = gain2, 0-1.024 // 2 = gain4, 0-0.512 // 3 = gain8, 0-0.256vdc #define PAR_SENSOR_ENABLE // Comment out to disable the PAR sampling #define DEBUG // Comment out to disable garbage //#define SHOW_ADS_CALLS #define SHOW_SEC_TICKS #define CBUFSZ 128 // Buffer size for output message, note 12pars need at least 120chars //---------------------------------------- // ADS1112 characteristics / Calibrations, and // PAR/Level Calibration stuff //---------------------------------------- #include "ads1112_defs.h" /* ADC Calibrations and the structure definition * The 'specific' file contains the actual values * "adsCals[]" that are for the mote to be programmed.... */ struct adsCalStuff{ char *name; // Name on the Wand uint8_t adsAddr; // I2C addr...the way we named them it should be same a i2c-x // Note they all better be different!!! float offset; // measured values: float gain; // per 'Reference / (Reading-Offset) }; // PAR & Level Calibration Definitions.... // Specific for individual sensors // The offsets for pars will be 0, and only taken up in the adc cal. above // The offsets for levels will be specific for the ADC channels struct adsChanStuff{ char *name; // Name, actually number, labeled on the par diode uint8_t adsNum; // Serves both as ADC index and I2C addr. uint8_t channel; // See enum float offset; float gain; float gain2; }; //#include "wandSpecificCals.h" // This is where they're at! //---------------------------------------- // File: "wandSpecificCals.h" // ADS1112 Specific Calibrations for 'THIS MOTE' // // SPECIAL CASE: FOR CURRENT-SENSING APPLICATION!!! // //---------------------------------------- struct adsCalStuff adsCals[4] = { {"48-8", ADC0, 0.0000, 1.0000 }, // First 3-PAR ADC {"4A-8", ADC1, 0.0004, 1.0003 }, {"4C-8", ADC2, 0.0020, 1.0001 }, {"4E-8", ADC3, 0.0017, 1.0000 } }; // PAR Specific Calibrations struct adsChanStuff parCoefs[4] = { {"iload1", 0, CHAN_DIFF0, -0.052, 192.641, 0}, //1st diff ADC {"iload2", 1, CHAN_SE1, 0.0, 1, 0}, {"iload3", 2, CHAN_DIFF0, 0.0, 1, 0}, {"iload4", 3, CHAN_DIFF0, 0.0, 1, 0} }; // Globals //--------- static char cbuf[ CBUFSZ ]; int calType = CAL_METHOD; uint8_t adc_rate, adc_gain; // Cfg masks for the ads uint8_t size1=1; uint8_t waitmS; uint8_t adsSEChan[3] = { 2,3,1 }; // 'inp1/0' bit patterns 0=2,1=3,2=1 uint8_t adsDFChan[2] = { 0,1 }; // 'inp1/0' bit patterns, differential channels float calibrated, fval; int16_t vraw[NWANDS][NSAMPLES_PER_MESSAGE]; float vadc[NWANDS][NSAMPLES_PER_MESSAGE]; float bitResolution; // All inputs will be the same gain setting, and freq. // so this can be universal also... uint16_t pollWaitmS; long iSecond=0; uint16_t subSec=0,loopmS; uint8_t conversion_mode = CONTINUOUS_CONVERSION; //uint8_t conversion_mode = SINGLE_CONVERSION; // Definitions // ----------- // routines here void parwand_ads1112(); void sample_adcChannels( uint8_t, uint8_t, struct adsChanStuff *); void report_results( char *, uint8_t, uint8_t, struct adsChanStuff * ); void report_ADSconfig( char *, uint8_t, struct adsChanStuff *); void report_ADCcals( char *, uint8_t, struct adsCalStuff *); void command_adc_sampling(uint8_t, struct adsChanStuff *); // routines elsewhere int sprintff( char *, const char *, ...); void i2c_disable_ack(); //======================================================================== void start (void) { // mos_thread_new (parwand_ads1112, MOS_COMMANDER_STACK_SIZE + 256, PRIORITY_NORMAL); mos_thread_new (parwand_ads1112, MOS_COMMANDER_STACK_SIZE + 120, PRIORITY_NORMAL); } void parwand_ads1112() { uint8_t hz; uint16_t maxVal; uint8_t nbytes; // 'sizeof number of bytes to i2c read // ---------- // Setups // MOS: turn radio off, and suspend state the lowest possible power. // Establish the resolution values based on gains. These used in vraw calc. mos_thread_set_suspend_state(SUSPEND_STATE_SLEEP); // for lowest power com_mode(IFACE_RADIO, IF_OFF); // to turn off the radio // down to about 10microA nbytes = 3; switch( ADC_RATE ) { // this sets the adc_rate value for it's configuration reg. case 30: hz=30; adc_rate = READ30HZ; maxVal=adsRange[1].maxVal; break; case 60: hz=60; adc_rate = READ60HZ; maxVal=adsRange[2].maxVal; break; case 240: hz=240; adc_rate = READ240HZ; maxVal=adsRange[3].maxVal; break; default: case 15: hz=15; adc_rate = READ15HZ; maxVal=adsRange[0].maxVal; break; } switch( ADC_GAIN ) { // this sets the adc_gain value case 1: adc_gain = 2; break; case 2: adc_gain = 4; break; case 3: adc_gain = 8; break; default: case 0: adc_gain = 1; break; } // Determine sleep times between polling a set of adcs withing the sampling loop, // and between the overall polling rate waitmS = (1000 / hz ); // How long to wait between sampling ads pollWaitmS = (waitmS+SAMPLING_TIME_FUDGE)*WANDSENSORS; if( POLLING_RATE > pollWaitmS ) pollWaitmS = POLLING_RATE - pollWaitmS; else pollWaitmS = 0; loopmS = SAMPLING_TIME_FUDGE + waitmS; bitResolution = 2.048 / (float) maxVal; bitResolution /= (float) adc_gain; // I2C Setup dev_ioctl(DEV_AVR_I2C, I2C_SET_BRR, 50); i2c_disable_ack(); // Continue working if 'missing' reports #ifdef DEBUG printf("\n\rPAR Sensor Sampling: %d wands, %d pars", NWANDS,WANDSENSORS); printf("\n\rOutput Calibration Set= %s %s %s %s", calType&RAW? "raw" : "", calType&MV? "mv" : "", calType&LINEAR? "linear" : "", calType&POLY? "poly" : ""); printf("\n\rSampling waitmS = %d mSec (1000/hz + 7)",waitmS); printf("\tPolling sleepmS= %d mSec",pollWaitmS); printf("\n\rADC\tSampling Freq = %d, (Cfg Mask = %x), Conversion_Mode=%x",hz,adc_rate,conversion_mode); printf("\n\r\tGain = %d, (Cfg Mask = %x),",adc_gain,ADC_GAIN); sprintff(cbuf,"\n\r\tMaximum ADC counts = %d, bitResolution = %.7f",maxVal,bitResolution); printf("%s",cbuf); report_ADCcals( "adcCals:", (uint8_t) NWANDS, &adsCals[0] ); // short changing not showing levels here report_ADSconfig( "PAR Config", (NWANDS*WANDSENSORS), &parCoefs[0] ); mos_thread_sleep( 5000 ); // let them read it! #endif // if continuous, then fire the adc's here.... if( conversion_mode==CONTINUOUS_CONVERSION ) command_adc_sampling( NWANDS, &parCoefs[0] ); // ----------- // Main Loop with sleep delay // while( TRUE ) { // printf("\n\r"); // =================================== // --- PAR SAMPLING --- // See sampling routine.... // // mos_led_display(GREEN); // green during sampling sample_adcChannels( WANDSENSORS, NWANDS, &parCoefs[0] ); report_results( "i", NSAMPLES_PER_MESSAGE, NWANDS, &parCoefs[0] ); // --- END of SAMPLING LOOP --- // mos_led_display(0); // turn off LEDs if( pollWaitmS ) mos_thread_sleep( pollWaitmS ); // sleep until next....note some inaccuracy // because RATE isn't perfectly adjusted by time taken // in sampling above..... } } //---------------------------------------- // Send the VRAW + anything else to the serial port // Requires: 'calType' declared above (raw,mv,etc.) // 'vraw[]' declared and loaded above // 'vadc[]' declared and loaded above // void report_results( char *header, uint8_t samples_per_message, uint8_t nwands, struct adsChanStuff *adsArray) { uint8_t ix, isample; char *cp; struct adsChanStuff *ads=adsArray; #ifdef SHOW_SEC_TICKS // This is funky and only for a guideline to try to have a 'time-tag' in // seconds heading to the message...... if( !pollWaitmS ) { // increment decimal mS, and check for wrap if( (subSec += loopmS) >= 1000 ) { ++iSecond; subSec=0; } } #endif // Done Sampling all Channels, Report results // ------------------------------------------ // Depending upon what's requested.... // if( calType & RAW ) { for( ix=0, cp=cbuf; ix<nwands; ++ix ) { for( isample=0; isample<samples_per_message; ++isample ) { sprintff(cp," %04x",vraw[ix][isample]); cp+=8; } } #ifdef SHOW_SEC_TICKS printf("\n\r%l.%03d ",iSecond,subSec); #else printf("\n\r"); #endif #ifdef PRINT_HEADER printf("%sRAW:%s",header,cbuf); #else printf("%s",cbuf); #endif } if( calType & MV ) { for( ads=adsArray, ix=0, cp=cbuf; ix<nwands; ++ix, ++ads ) { for( isample=0; isample<samples_per_message; ++isample ) { sprintff(cp,"%7.4f",vadc[ix][isample]); cp+=8; } } #ifdef SHOW_SEC_TICKS printf("\n\r%l.%03d ",iSecond,subSec); #else printf("\n\r"); #endif #ifdef PRINT_HEADER printf("%sVDC:%s",header,cbuf); #else printf("%s",cbuf); #endif } // QUESTION...do we want to calibrate on the raw values or 'vdc'????? if( calType & LINEAR ) { for( ads=adsArray, ix=0, cp=cbuf; ix<nwands; ++ix, ++ads ) { for( isample=0; isample<samples_per_message; ++isample ) { calibrated = (vadc[ix][isample] *ads->gain ) + ads->offset; sprintff(cp,"%7.4f ",calibrated); cp+=8; } } #ifdef SHOW_SEC_TICKS printf("\n\r%l.%03d ",iSecond,subSec); #else printf("\n\r"); #endif #ifdef PRINT_HEADER printf("%s1st:%s",header,cbuf); #else printf("%s",cbuf); #endif } if( calType & POLY ) { for( ads=adsArray, ix=0, cp=cbuf; ix<nwands; ++ix, ++ads ) { for( isample=0; isample<samples_per_message; ++isample ) { fval = vadc[ix][isample]; calibrated = (fval * ads->gain ) + ads->offset; calibrated = fval * ads->gain + ads->offset; calibrated+= fval * fval * ads->gain2; sprintff(cp,"%7.4f",calibrated); cp+=8; } } #ifdef SHOW_SEC_TICKS printf("\n\r%l.%03d ",iSecond,subSec); #else printf("\n\r"); #endif #ifdef PRINT_HEADER printf("%s2nd:%s",header,cbuf); #else printf("%s",cbuf); #endif } } #ifdef DEBUG //---------------------------------------- // Displays to Console what the ADS structure Settings Are void report_ADSconfig( char *header, uint8_t nchannels, struct adsChanStuff *adsArray) { uint8_t ix; struct adsChanStuff *ads=adsArray; // Report What the ADS Settings Are // printf("\n\r%s",header); printf("\n\rIx Name adcIx i2c ADC-Ch Offset Gain Gain2"); for( ix=0; ix<nchannels; ++ix, ++ads ) { printf("\n\r%2d %s %d %02x %x ", ix, ads->name, ads->adsNum, adsCals[ads->adsNum].adsAddr, ads->channel); sprintff(cbuf, "%.2f %.2f %.2f", ads->offset, ads->gain, ads->gain2); printf("%s",cbuf); } } //---------------------------------------- void report_ADCcals( char *header, uint8_t nadcs, struct adsCalStuff *adsArray) { uint8_t ix; struct adsCalStuff *adccal=adsArray; // Report What the ADS Calibrations are // printf("\n\r%s.....ADC Calibrations:",header); printf("\n\rIx name i2c Offset Gain "); for( ix=0; ix<nadcs; ++ix, ++adccal ) { printf("\n\r%2d %s %02x ", ix,adccal->name, adccal->adsAddr); sprintff(cbuf, "%.5f %.5f", adccal->offset, adccal->gain); printf("%s",cbuf); } } #endif //---------------------------------------- void sample_adcChannels( uint8_t sensors_per_wand, uint8_t nwands, struct adsChanStuff *adsArray) { uint8_t ix, adsChan, isample, npolls, setCfg; uint8_t arrayEntry; struct adsChanStuff *ads=adsArray, *ads0=adsArray; uint8_t retval; // Reply from I2C driver: #bytes uint8_t ADSreply[3]; // Reply from ADS1112, output 16bits + config uint8_t i2cAddr; // double loop goes through each of the adc's for each of 'n' sample sets #ifdef SHOW_ADS_CALLS printf("\n\r"); #endif // for( adsChan=0; adsChan<sensors_per_wand; ) // DO LATER FOR MULTI-CHAN for( isample=0; isample<NSAMPLES_PER_MESSAGE; ) { // Command each ADC to read one wand channel // This will happen either once for each adc when 'continuous' and // that will be done in the 'setups', or else done here when we // wand to command each and every single sample // if( conversion_mode!=CONTINUOUS_CONVERSION ) { command_adc_sampling( NWANDS, &parCoefs[0] ); } // Delay for anticpated sampling time period and then mos_thread_sleep(waitmS); // Read the ADC results // for( ads=ads0, ix=0; ix<nwands; ++ix, ads+=sensors_per_wand ) for( ads=ads0, ix=0; ix<nwands; ++ix, ++ads ) { ADSreply[0] = ADSreply[1] = ADSreply[2] = 0; // arrayEntry= isample + ix*sensors_per_wand; arrayEntry= isample + ix; i2cAddr = adsCals[ads->adsNum].adsAddr; // Set I2C Address dev_ioctl(DEV_AVR_I2C, I2C_DEST_ADDR, i2cAddr); npolls=0; do { ++npolls; // keep track of how many reads retval = dev_read(DEV_AVR_I2C, &ADSreply[0], 3); } while (ADSreply[2]&0x80); vraw[ix][arrayEntry]= (ADSreply[0]<<8) + ADSreply[1]; vadc[ix][arrayEntry] = (float)vraw[ix][arrayEntry] * bitResolution; vadc[ix][arrayEntry] = (vadc[ix][arrayEntry] - adsCals[ads->adsNum].offset ) * adsCals[ads->adsNum].gain; #ifdef SHOW_ADS_CALLS printf(" Read %d:",isample); // report index, replied cfgreg, and #polls required printf("%d/%d-%x-%d",ix,arrayEntry,i2cAddr,npolls); #endif } ++isample; } return; } //---------------------------------------- void command_adc_sampling(uint8_t nwands, struct adsChanStuff *adsArray) { // common rate/gain/conversion-rate settings uint8_t config_mask = adc_rate | ADC_GAIN | conversion_mode; uint8_t ix, adsChan, setCfg; struct adsChanStuff *ads=adsArray, *ads0=adsArray; uint8_t i2cAddr; uint8_t retval; // Reply from I2C driver: #bytes // Command each ADC to read one wand channel // This will happen either once for each adc when 'continuous' // or else done singly depending upon the 'conversion_mode' // for( ads=ads0, ix=0; ix<nwands; ++ix, ads+=sensors_per_wand ) for( ads=ads0, ix=0; ix<nwands; ++ix, ++ads ) { i2cAddr = adsCals[ads->adsNum].adsAddr; // Set I2C Address dev_ioctl(DEV_AVR_I2C, I2C_DEST_ADDR, i2cAddr); // Set the cfgCommand ADS Channel, setCfg = config_mask | ads->channel; retval = dev_write(DEV_AVR_I2C, &setCfg, size1); #ifdef SHOW_ADS_CALLS printf("\nSampling Setup: "); sprintff(cbuf," %d-%x-%x-%2d-%d",ix,i2cAddr,setCfg,ads->channel,retval); printf("%s",cbuf); #endif } return; } //======================================================================