Stuck in pic18f4520 project for charging controller

Thread Starter

PIYUSH SONI

Joined Nov 15, 2013
32
]Hi,
I am PIYUSH. I am working on a project to control the charger using pic microcontroller. I am using pic 18f4520 MCU and easy pic5 development board. I have taken 2 ADC channel and displaying the equivalent voltage on 4*16 LCD. Actually i want to display the difference of both channel in percentage.Help me so that i can solve my problem. I am also attaching my program code:-

Rich (BB code):
sbit LCD_RS at RB5_bit;
 sbit LCD_EN at RB4_bit;
 sbit LCD_D4 at RB3_bit;
 sbit LCD_D5 at RB2_bit;
 sbit LCD_D6 at RB1_bit;
 sbit LCD_D7 at RB0_bit;
 sbit LCD_RS_Direction at TRISB5_bit;
 sbit LCD_EN_Direction at TRISB4_bit;
 sbit LCD_D4_Direction at TRISB3_bit;
 sbit LCD_D5_Direction at TRISB2_bit;
 sbit LCD_D6_Direction at TRISB1_bit;
 sbit LCD_D7_Direction at TRISB0_bit;

 char message1[] = "SPG SYSTEMS";
 char message2[] = "-----------";
 char message3[] = "DEVELOPED BY:-";
 char message4[] = "SPG INFOTECH LTD";
 char message5[] = "CH1=      VOLTS";
 char message6[] = "CH2=      VOLTS";
 char message7[] = "CHARGER OFF";
 char message8[] = "CHARGER ON";


 sbit Relay  at RA5_bit;
 sbit Buzzer at RA4_bit;
 char *temp = "00000";
 char *temps = "00000";
 unsigned int adc_value;
 unsigned int adc_valueV;
 unsigned int adc_values;
 unsigned int adc_valueVs;
 unsigned char VOLTS;

 void main()
  {
  OSCCON = 0b01111110;
  ANSEL =  0b00000011;
  ADCON0 = 0b00001000;
  ADCON1 = 0b00000000;
  CMCON = 7 ;
  TRISA = 0b00000011;
  TRISB = 0b00000000;
  Lcd_Init();

  Lcd_Cmd(_LCD_CLEAR);
  Lcd_Cmd(_LCD_CURSOR_OFF);
  Lcd_Out(1,3,message1);
  Lcd_Out(2,3,message2);
  Lcd_Out(3,-3,message3);
  Lcd_Out(4,-3,message4);

  Delay_ms(3000);
  Lcd_Cmd(_LCD_CLEAR);
  Lcd_Out(1,1,message5);
  Lcd_Out(2,1,message6);
  Lcd_Out(4,1,message7);
  Lcd_Out(4,1,message8);

  do
 {
    Delay_ms(1);
    Lcd_Out(1,1,message5);
    Lcd_Out(2,1,message6);
    adc_value = ADC_READ(1);
    Delay_ms(25);
    adc_valueV = adc_value*4.89;
    temp[0] = adc_valueV/1000 + 48;
    temp[1] = '.';
    temp[2] = (adc_valueV/100)%10 + 48;
    temp[3] = (adc_valueV/10)%10 + 48;
    temp[4] = adc_valueV%10 + 48;
    Lcd_Out(1,5,temp);


    adc_values = ADC_READ(0);
    Delay_ms(25);
    adc_valueVs  = adc_values*4.89 ;
    temps[0] = adc_valueVs/1000 + 48;
    temps[1] = '.';
    temps[2] = (adc_valueVs/100)%10 + 48;
    temps[3] = (adc_valueVs/10)%10 + 48;
    temps[4] = adc_valueVs%10 + 48;
    Lcd_Out(2,5,temps);
    Delay_ms(500);

   if((adc_valueV/666 >2.0) || (adc_valueVs/666 >2.0))
    {
     Relay = 0;

     Lcd_Out(4,0,message7);
     Lcd_Cmd(_LCD_CLEAR);
     Lcd_Out(4,0,message7);

     Buzzer =1 ;

     }
    else
    {
     Relay = 1;

     Lcd_Out(4,0,message8);
      Lcd_Cmd(_LCD_CLEAR);
     Lcd_Out(4,0,message8);
     Buzzer =0 ;
     }
    }while(1);

   }
/*Plz help me to get the difference between two adc channel.:)
 
Last edited by a moderator:

ErnieM

Joined Apr 24, 2011
8,377
Hi PIYUSH. Welcome to the forums!

It may just be early so I'm still on my first cup of coffee but your code is rather incomprehensible in a way usually reserved for assembly code.

No clue what compiler was used.

Not one single comment.

Any printed strings are obscured and defined well away from their place of usage.

OK, guessing this is MikroC so that puts an unsigned int at 2 bytes, which is large enough to handle 5 (or 4.89) times 10 bits (did you think to check that?).

If I guess you are defineing the difference for percentage as (V-Vs)/Vs I would do this:

Rich (BB code):
    unsigned int percentage;
    percentage = ( (adc_valueV - adc_valueVs) * 100 ) / adc_valueVs)
Note the multiplication is forced to happen before the division by the parenthesis. That's so a small difference doesn't get lost in the integer representation.

If you wish more digits in the answer use 1000 or 10,000 as the constant multiplier to pull out more digits. Then you need insert the decimal point when you convert to a string.

You are left with the task of converting the number into a string. That's always a messy task.
 

THE_RB

Joined Feb 11, 2008
5,438
ErnieM covered some of them already, but here's some more;

adc_valueVs = adc_values*4.89;
this tries to use a float value, but both variables are 16bit unsigned int. The MikroC PRO compiler will round 4.89 to the integer to fit the variable, so 4.89 becomes 4. I'm sure that's not what you wanted.

temps[0] = adc_valueVs/1000 + 48;
this is equally poor. The largest value you can have in adc_valueVs is 1023*4 or 4092, so doing an integer /1000 will really reduce the data resolution to the point of failure.
ie; anything in the range 0-999 after /1000 will = 0
Anything 1000-1999 after /1000 will = 1

The O.P. really needs to think about the data scaling and truncating during the math processes and also check that against the integer size of variables.
 

ErnieM

Joined Apr 24, 2011
8,377
adc_valueVs = adc_values*4.89;
this tries to use a float value, but both variables are 16bit unsigned int. The MikroC PRO compiler will round 4.89 to the integer to fit the variable, so 4.89 becomes 4. I'm sure that's not what you wanted.
I do not believe this is what happens. I barely use MikroC but do have a copy so I set about seeing the effect of a float on an integer.

Generally C will coerce values of several types into a common type that will fit them all. I say "generally" as this may vary on different compilers, especially those for embedded use to keep math operations light and tight.

In this case with adc_values (unsigned int) and 4.89 (clearly a float constant) the common type is a float. Once the result is obtained *that* result is converted back to the "lvalue" (the left side of the =), here an unsigned int.

To test this I set adc_values to 10 and gave MikroC this task: it returned 48
 

THE_RB

Joined Feb 11, 2008
5,438
My apologies to the OP for the error, ErnieM is right.

I just tested and doing
a = b * 4.89;
where a and b are 16bit unsigned int causes the compiler to link in 800+ ROM words of 32bit and float functions, then from looking at the generated assembler it actually does something like this;
b convert to float
b * 4.89 is done as float mult
b convert from float back to unsigned int

I never do it that way as I consider that bad coding practice to do; unsigned int var * float constant. Even though the compiler might save your bacon it is still good practice (especuially in micros) to have real good control of variable sizes and the desired math functions and undertsanding when/where the variables might overflow.
 

Thread Starter

PIYUSH SONI

Joined Nov 15, 2013
32
Thanks all of you for ur suggestion. ErnieM you are right about percentage. I have already done that but the fact is that i am not getting the output. One thing i wanna i ask you i have used the following code:

Rich (BB code):
 if((adc_valueV/666 >2.0) || (adc_valueVs/666 >2.0))
Actually i am new in embedded and not aware of the above code. In my project i want to make the charger off when it reach 2 volts. In the above code i have divided adc value by 666 and when i change this value then the whole program changes. Can you let me know why we have to divide adc_value by any int value.
 

MrChips

Joined Oct 2, 2009
30,618
We are trying to tell you that as an embedded systems programmer you have to pay attention to data types and arithmetic.

Do not mix integers with floats unless you are sure of the consequences.
Do some of the arithmetic yourself in order to get the right results.

For example

(adc_valueV/666) > 2.0

can be replaced with

adc_valueV > 1332

This is a lot more accurate and a lot more efficient.
 

ErnieM

Joined Apr 24, 2011
8,377
Thanks all of you for ur suggestion. ErnieM you are right about percentage. I have already done that but the fact is that i am not getting the output. One thing i wanna i ask you i have used the following code:

Rich (BB code):
 if((adc_valueV/666 >2.0) || (adc_valueVs/666 >2.0))
Actually i am new in embedded and not aware of the above code. In my project i want to make the charger off when it reach 2 volts. In the above code i have divided adc value by 666 and when i change this value then the whole program changes. Can you let me know why we have to divide adc_value by any int value.
Earlier you defined:
Rich (BB code):
 adc_valueV = adc_value*4.89;
I would have to ask how you derived this expression. Then I would have to ask where the devil did the 666 come from?

If you tell me 1) what reference voltage the A2D uses and B) what divider is used from the analog inputs (and ONE is a fair answer) I can aid you in defining these expressions.

(Since I am actually quite psychic I am sensing you have no divided (1:1) and a 5 V reference. But you need to confirm that and thus confirm my awesomeness.)

The best technique you should gain from this is to do ALL your math in integer variables. Now before you say "but I need better resolution" remember that .01 volt is also 10 millivolts.

One of these is an integer. One is not. C has not a clue what unit your work is using, and you are completely free to pick one that works to your advantage.

Another useful thing is to divide tasks into "run time" and "compile time."

"run time" is what your program does inside the PIC. Tasks have a cost in both time and memory.

"compile time" is what happens in your PC when you hit "Build." It has zero cost on your PIC.

So instead of converting the A2D count into volts (or millivolts) and comparing that to the limit 2... instead convert 2 into a count (at compile time) and in your PIC code compare this count to the reading out of the A2D.

Can't beat that for speed.

Of course, you can't display the count directly either as it is fairly meaningless.
 

Thread Starter

PIYUSH SONI

Joined Nov 15, 2013
32
Yaa, i have taken a 5v reference and you are right about divider too. I have taken 666 only to adjust the adc value to 2 volt. Now i am getting the difference b/w 2 channel and right now iam able to get the required o/p on the lcd.
Thanx for ur useful information.

I have taken 4.89 as i am using Vref= 5 v and 10 channel adc.
2^10=1023
so i have to calculate voltage step value i.e, Vref/1023= 4.89 mv.
 

ErnieM

Joined Apr 24, 2011
8,377
Great, I did a back of the envelope calculation and got your same 4.89 constant so I figured I'd used your same scheme by coincidence.

Now what does this 666 come from? I'm lost about that. When you take the raw A2D count and multiply by 4.89 you get an integer number equal to the voltage in millivolts.

Since 2000 represents 2000 mV = 2V... why not compare adc_valueV directly to 2000 instead of something else?
 

THE_RB

Joined Feb 11, 2008
5,438
Agreed!

There are standard systems for getting "volts" result from a 5v Vref ADC. Or even tenths of volts or hundredths of volts.

Like this;
unsigned long voltsH; // requires 32bit variable
voltsH = (ADCtotal * 500) / 1024;

voltsH reads in hundredths of volts. So 4.00v will equal "400", and 2.01v. equals "201" etc.
 
Top