Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PIC24 as voltmeter/ammeter for solar system - fluctuating readings

Status
Not open for further replies.

albad

New Member
Hi Guys,
This part of my project involves measuring solar panel voltage and current flow into a battery.

I've done all the coding and used a voltage divider (120k and 22k resistors) to get the correct ADC input levels, and for the ammeter part am simply measuring the voltage drop over a 1 ohm resistor in the system.

My problem arises from the fact the panel voltage is a sinusoidal but around a constant voltage (at that insolation level).
A regular multimeter in DC V mode says e.g. "13.7v", but an oscilloscope shows it varies by 400mv, centered on that value.

I want my PIC voltmeter to read 13.7v and stay there, but instead it (accurately) varies from 13.5 -> 13.9v in time with the panel
I dont want this.

Why does my multimeter read it as a stable voltage, and how can I adapt my PIC system to do the same?

I have the same problem when using it as an ammeter. The multimeter gives a constant 10mA, my PIC varies (but more substantially than before).

Thanks.
 
You have another thread dealing with the multimeter side of the post so lets keep that side of it there.

For the voltage display using a PIC I would average several reading over time to give you a stable display. The time and sample rate depends on the frequency. There may be other methods.

The problem is mostly the same for the ammeter. Did you know that maxim/dallas (and others) make battery management chips. They directly read the voltage and can keep track of current/charge. For the $3 or $5 I would let one of them do the work.

EDIT: I am far from an expert on this.
 
Last edited:
16 or 32 bit accumulator
16 bit value called filter
8 bit value called shift

accumulator -= filter;
accumulator += adc_reading;
filter = accumulator>>shift;

The higher "shift" is the higher the time constant of the filter. The first time you take a reading you should set accumulator to that value after shifting it left "shift" places
 
thanks for the responses.

noggin, i tried using your code, but get an incomplatible type error after declaring them as int (tried other declarations too).

have set the three types as int arrays of the lengths you specified

the last line gives an invalid operands to binary >> error

please help!
 
much easier to help if you post your code and put [ code] and [ /code] tags around them (without the spaces)
 
Last edited:
Ive just adapted microchip's voltmeter tutorial program to the adc channel i need:

Code:
#include "system.h"


/*****************************************************************************
 * Module Constants Definitions 
 *****************************************************************************/
//ADC channels numbers
#define ADC_TEMPERATURE  ADC_TEMP_CHAN
//#define ADC_VOLTAGE      ADC_VOLT_CHAN

// Delay after input switching
#define ADC_SWITCH_DELAY   400

// Temperature scale switching period
#define ADC_CELSIUS_DELAY   100

// Number position
#define ADC_POS_NUMBER      10

// Temperature scale sign position
#define ADC_POS_SCALE       15

// Memory sign position
#define ADC_POS_MEM         13

// Reference voltage, mV
#define AVCC                3300

/*****************************************************************************
 * Arrays: _voltage_str and _temperature_str
 *
 * Overview: These arrays contains voltage and temperature strings.
 *
 *****************************************************************************/
char _voltage_str[16] =     "Voltage =      V";
char _temperature_str[16] = "Temp =         C";


unsigned char 	_uADCState;
unsigned int	_uADCWait;
// Period to switch temperature scale
unsigned int	_uADCCelsiusWait;
// Current temperature scale
unsigned char 	_uADCCelsius;
// Buffer to filter temperature sensor value
unsigned int 	_uADCTemperature;
// Switch to get temperature data from EEPROM rather than from ADC
unsigned char 	_uADCFromMemory;

/*****************************************************************************
* Function: ADCInit
*
* Preconditions: None.
*
* Overview: This function initiates ADC and state mashine.
*
* Input: None.
*
* Output: None.
*
******************************************************************************/
void ADCInit(){
	
	AD1CON1 = 0x80E4;				//Turn on, auto sample start, auto-convert
	AD1CON2 = 0;					//AVdd, AVss, int every conversion, MUXA only
	AD1CON3 = 0x1F05;				//31 Tad auto-sample, Tad = 5*Tcy
    
	AD1CHS = 0x0002;

	TRISBbits.TRISB2 = 1;
	TRISBbits.TRISB3 = 1;
	AN_VOLT_PIN = 0;			//Disable digital input on AN5
	AN_TEMP_PIN = 0;          //Disable digital input on AN4
	AD1CSSL = 0;					//No scanned inputs
    _uADCWait = ADC_SWITCH_DELAY;
    _uADCCelsiusWait = ADC_CELSIUS_DELAY;
    _uADCState = 1;
    _uADCCelsius = 0;               //Show celsius scale  
    _uADCFromMemory = 0;            //Show current temperature
}

/*****************************************************************************
* Function: ADCProcessEvents
*
* Preconditions: ADCInit must be called before.
*
* Overview: This is a state mashine to grab analog data from potentiometer
* and temperature sensor.
*
* Input: None.
*
* Output: None.
*
******************************************************************************/
void ADCProcessEvents(){
unsigned long Result;
unsigned long rlt;

    switch(_uADCState){

        case 1:          // Convert result for potentiometer
          // Wait for conversion to complete
          while(!AD1CON1bits.DONE);          
          Result = (long) ADC1BUF0;   
  
          Result = (Result*AVCC)/(1024/0.64);

// new part not working
int accumulator [16];
int filter [16];
int shift [8];
accumulator = accumulator-filter;
accumulator = accumulator+AD1CHS;
filter = accumulator>>shift;

          ADCShortToString((int)Result, 2, _voltage_str+ADC_POS_NUMBER);
          // Sweep least significant digit
          _voltage_str[ADC_POS_NUMBER+4] = ' ';
		  AD1CHS = ADC_TEMPERATURE;
          _uADCState++;
          break;
 
        case 3:          // Convert result for temperature  
          // Wait for conversion to complete
          while(!AD1CON1bits.DONE);

          Result = (long) ADC1BUF0;

          // filter temperature Value
          // _uADCTemperature = 15/16*_uADCTemperature + 1/16*New Sample   
          _uADCTemperature -= _uADCTemperature>>4;
          _uADCTemperature += Result;
          Result = _uADCTemperature>>4;

          // Read temperature stored into EEPROM  
          _temperature_str[ADC_POS_MEM] = ' ';  
          if(ADCIsFromMemory()){
            Result = ADCLoadTemperature();          
            _temperature_str[ADC_POS_MEM] = 'M';
          }

          if(0 == _uADCCelsiusWait--){
            _uADCCelsiusWait = ADC_CELSIUS_DELAY;
            _uADCCelsius ^= 1;
            if(_uADCCelsius)
                _temperature_str[ADC_POS_SCALE]='C';
            else
                _temperature_str[ADC_POS_SCALE]='F';
          }
          if(_uADCCelsius)
              ADCShortToString((int)Result,3,_temperature_str+ADC_POS_NUMBER);
          else{
              // Convert Celsius temperatures into Fahrenheit
              // Begin by multiplying the Celsius temperature by 9. 
              // Divide the answer by 5. 
              // Now add 32. 
              Result *= 9; Result /= 5; Result += 320;
              ADCShortToString((int)Result, 3,_temperature_str+ADC_POS_NUMBER);
          }  
          // Switch input to potentiometer
          AD1CHS = 0x0002;
          _uADCState++; break;

        case 2:
        case 4:
            // Delay slot between channel switching
            if(0 == _uADCWait--){
                _uADCWait = ADC_SWITCH_DELAY;
                _uADCState++;
            }
            break;
        break;
        default: 
           _uADCState = 1;
    }
}

/*****************************************************************************
 * Function: ADCShortToString
 *
 * Preconditions : None.
 *
 * Overview: The function converts integer into string.
 *
 * Input: Value - value to be converted; DotPos - dot position ( can be 
 * between 0 and 3, DOTPOS_NONE = 4, if equals DOTPOS_TRAIL_ZEROS = -1 will not
 * put a dot and insert leading zeros); Buffer - receives the result string
 *
 * Output: None.
 *
 *****************************************************************************/
void ADCShortToString(int Value, char DotPos, char* Buffer){
char Result;
char Pos;

    // Clean Buffer (4 digits + Dot)
    for(Pos = 0; Pos < 5; Pos++) Buffer[Pos] = ' ';

    Pos = 0;
    if(Pos == DotPos){ *Buffer++ = '0';*Buffer++ = '.';}
    Pos++;
    Result = Value/1000;
    Value -= 1000*Result;
    if(Result) *Buffer++ = Result + '0';
    else if(Pos >= DotPos) *Buffer++ = '0';

    if(Pos == DotPos) *Buffer++ = '.'; 
	Pos++;
    Result = Value/100;
    Value -= 100*Result;
    if(Result) *Buffer++ = Result + '0';
    else if(Pos >= DotPos) *Buffer++ = '0';

    if(Pos == DotPos) *Buffer++ = '.'; 
	Pos++;
    Result = Value/10;
    Value -= 10*Result;
    if(Result) *Buffer++ = Result + '0';
    else if(Pos >= DotPos) *Buffer++ = '0';

    if(Pos == DotPos) *Buffer++ = '.'; 
	Pos++;
    //Result = Value/10;
    //Value -= 10*Result;
    if(Value) *Buffer++ = Value + '0';
    else if(Pos >= DotPos) *Buffer++ = '0';
}			    

/*****************************************************************************
 * Function: ADCStoreTemperature()
 *
 * Preconditions: SPIMPolInit and EEPROMInit must be called before.
 *
 * Overview: The function stores the current temperature into EEPROM.
 *
 * Input: None.
 *
 * Output: None.
 *
 *****************************************************************************/
void ADCStoreTemperature(){
unsigned Temp;
    // Get temperature
    Temp = _uADCTemperature>>4;
    // Write MS byte into EEPROM address = 0
    EEPROMWriteByte(Hi(Temp),0);
    // Write LS byte into EEPROM address = 1
    EEPROMWriteByte(Lo(Temp),1);
}

/*****************************************************************************
 * Function: ADCLoadTemperature()
 *
 * Preconditions:  SPIMPolInit and EEPROMInit must be called before.
 *
 * Overview: The function returns temperature stored by ADCStoreTemperature
 * into EEPROM.
 *
 * Input: None.
 *
 * Output: Temperature value read from EEPROM.
 *
 *****************************************************************************/
unsigned ADCLoadTemperature(){
unsigned Temp;
    // Read MS byte from EEPROM address = 0
    Temp =  EEPROMReadByte(0);
    Temp = (Temp<<8)&0xff00;
    // Read LS byte from EEPROM address = 1
    Temp |= (EEPROMReadByte(1)&0x00ff);
    // If there's not valid value replace it with 25.0 C
    if(Temp > 990)
        Temp = 250;
    return Temp;
}

/*****************************************************************************
 * Function: ADCSetFromMemory
 *
 * Preconditions: None.
 *
 * Overview: The function toggles switch to display temperature stored into EEPROM
 * rather than current one.
 *
 * Input: None.
 *
 * Output: None.
 *
 *****************************************************************************/
void ADCSetFromMemory(){
    _uADCFromMemory ^=1; // Toggle switch
}

/*****************************************************************************
 * Function: ADCIsFromMemory
 *
 * Preconditions: None.
 *
 * Overview: The function returns mode of EEPROM displaying.
 *
 * Input: None.
 *
 * Output: It returns zero if the current temperature is displyed.
 *
 *****************************************************************************/
unsigned char ADCIsFromMemory(){
    return _uADCFromMemory;
}

/*****************************************************************************
 * EOF
 *****************************************************************************/

this is then called from another "vbanner" program to make it appear on the screen, which is called from the main file.
 
Last edited:
int accumulator [16];
int filter [16];
int shift [8];

These are not 16 or 8 bit values, these are arrays of integers.
 
i have identified a mistake, and changed AD1CHS to AD1BUF0, and now I get a varying value on my screen, using accumulator as my "result".

still have the same problem as before though, where my value wont stabilise like I want it to, any ideas?

*i changed my arrays into "long" for the 16's and int for the 8, thanks
 
Last edited:
The accumulator is not the result, filter is. Accumulator will vary just as much as the adc reading does, however, it is much larger so its variation, percentage wise, is smaller. When you shift the value right by using the >> operator, you basically divide the variation by 2^shift
 
Right back were you started. :)

Go back to the start of the thread and read the posts again. The problem is the same as is the solution.

This is a bit of code I use to illustrate how to calculate an
average value. The code noggin provided works too and is much shorter. This may be easier to understand.

Code:
    // format humidity
    { 
      static unsigned char hum[10];  // array of read values
      static unsigned char humPt=0; // index into above array
      unsigned char idx;
      
      /// take reading here and place in humidity
      humindity = ........ ;
      // place reading in array
      hum[humPt++]=humidity;
      humPt = humPt%10;  // cycle from 0 to 9
      
      humidity=0;  //recalc humidity as ave of last 10 readings
      for(idx=0;idx<10;idx++)    // sum the readings
      {
         humidity +=hum[idx];
      }
      humidity=humidity/10;  
      fprintf(_H_USER, "%02i%%H ", humidity );  
    }
 
its still varying by the same amount as before, even using filter as my result to change into the char string to be outputted.

it what you mentioned here in the code already or does it need to be added (i dont really understand it)

The first time you take a reading you should set accumulator to that value after shifting it left "shift" places
 
nope, seems to work fine initially, varying by .1 instead of .5

then when i turn the panel off and the voltage goes below 10 then if comes back up again!
i really cant be arsed anymore

i realise ive got my other thread running about the voltmeter, so can it please be deleted so i can have an answer in here about whether its possible to stabilise the reading with simple hardware?

i know it would be simpler to do with a pre-made chip, but the idea of this project is to show that i can do this with the hardware given
 
YAZOO! it works, changing the ststic unsigned into a long

i love you both, noggin and 3v0, (in a manly, man hug way)
 
I looked at this post and thought "volt/ammeter for solar system" was going to be something to measure current flowing between Jupiter and the Sun, something with solar wind and magnetic fields... or something...
 
albad,
i am designing an ammeter for my SOLAR POWER SYSTEM. I've already sensed & measured the LOAD VOLTAGES through the internal adc of pic16f877 now i want to measure the LOAD CURRENT & then i will be displaying the POWER BEING USED on a LCD DISPLAY.
I will be grateful to you if you share the CURRENT SENSING circuit with me.
i am using a 12V battery, system is capable of driving 1kva load & load voltages are 220vac.
My Email address is:
engr.babar_khan@hotmail.com
I am waiting impatiently for your reply as i am falling short of time. The deadline for my project is near so please please please reply me soon.
Thanks in advance.
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top