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.

PIC ADC scaling/conversion help please

augustinetez

Active Member
Not being very good in the math department, could someone point in the direction of solving an ADC scaling/conversion problem please.

Scenario (this carries across from my voltage leveling post a little while ago):-

Input to a PIC ADC using the 3.3V rails as the ADC reference.

The ADC raw values span 35 to 937

When the input voltage to the leveling circuit is at the center of it's range, the ADC value is 480

What I need to solve:-

A dead band of 5 ADC counts either side of 480 ie 475 - 485 = dead band, this could be increased slightly if it helps with calculations.

Above and below this dead band, I need to compute a number from 10 - 2500 in increments of 10 from the remaining counts (10 to 2500 above dead band and 10 to 2500 below) and also determine if it is above or below the dead band with an above/below dead band indicator bit.

The result from any calculation to = 0 when in the dead band.

This is to add/subtract from the frequency control word of a DDS chip using an existing analogue circuit.

I have spent several days trawling through this and various other forums without success.

And yes, of course, I'm still doing it all in assembler. :eek:
 

Pommie

Well-Known Member
Most Helpful Member
This is my attempt in C,
Code:
  if(valADC<=480-5){
    newVal=((480-5)-valADC)*(2500-10)/(480-5)+10;
  }else if(valADC>=480+5){
    newVal=(valADC-(480+5))*(2500-10)/(480+5)+10;
  }else{
    newVal=0;
  }
BTW, your middle value is 486 (937+35)/2 if I'm understanding you correctly.

Mike.
P.S. I could put it in a spreadsheet if that helps.
Edit, the above isn't right as the range is wrong.
 
Last edited:

Pommie

Well-Known Member
Most Helpful Member
If you want to play around with it in excel to get your head around it then the formula is,
excel.png

I.E. =IF(A5<(B$1-5),((B$1-5)-A5)*(2500)/(B$2)+10,IF(A5>=(B$1+5),(A5-(B$1+5))*(2500)/(B$2)+10,0))

This is basically using nested IFs to do the same thing.

Mike.
 

Pommie

Well-Known Member
Most Helpful Member
Terry, another possibility (probably more implementable in assembly) is to store a table of 226 words. Your chip (16F1827 I assume) has 4K of 14 bit words. A single page (255 words) could hold a 226 word table (amount above/below your mid point) and the flash read routines could retrieve the correct value. I'm not sure how you store words in assembly - it used to be DW but things have "progressed". Just a thought.

mike.
 

rjenkinsgb

Well-Known Member
Most Helpful Member
My take on it:

It does not use floating point or division, which should make it very fast and compact compared to a floating point based solution.

It's also spread out a lot more than it needs to be, for clarity and comments..

C:
int16_t ADCResult; // for testing only - use your input value

int16_t adc_val;
int32_t tempval;

int16_t sign;


// Remove offset
adc_val = ADCResult - 480;

// Extract the sign bit
sign = 0;
if ( adc_val & 0x8000 ) sign = 1;

// Move the absolute value to the 32 bit temp, also removing the dead zone
if ( adc_val < -5 )
    tempval = -3L - adc_val;
else if ( adc_val > 5)
    tempval = adc_val - 3
else
    tempval = 0;

if ( tempval ) {
// Only do the rest of the calcs if there is a value to work with

// Scale it; to 0-210 using a fractional multiplication and only integers.
// ratio of input to output, * 2^20
//  to shift the result by 20 bits at the same time; scale factor 591000
    tempval *= 591000;

// Remove the 20 bit scaling
    tempval >>= 20;

// Add in the minimum output value
    tempval += 1;
// And make it units of 10
    tempval *= 10;

// Limit to 2500
    if( tempval > 2500 )
        tempval = 2500;

}

// tempval has the numeric result.
// sign is 1 if it's a negative result.


It's a slight compromise in that it is symmetrical around the zero offset point.
35 give 2500 (-)
34 gives 2490 (-)

However, at the top of the scale it reaches 2500 with an input of 925 rather than 937
If that's critical, try adding a couple of lines to use a scale of 580446 if the sign bit is clear, or the existing value if it's set.
 

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
augustinetez .. I think they missed the bit about ASM..
You may be better off in Oshonsoft... I wrote a Map() function some time back in basic.
I actuality did a little Math library for myself..

However!! The map function ONLY worked for positive numbers..
 

augustinetez

Active Member
Thanks for the input so far, been out of the house for longer than I expected.

Re Ian's comment of doing it in the Oshonsoft compiler, that would work as I did the same with his bargraph code to see if it would translate back to asm - it worked fine.

Have a few things I have to get done, so will read through this all properly later on.
 

augustinetez

Active Member
BTW, your middle value is 486 (937+35)/2 if I'm understanding you correctly.
Possible - I wrote a small file to actually display the raw ADC values from the test jig, but I'm running a 5V LCD on 3.3V.
While the electronics run fine at 3.3V, the backlight not so much and is quite dim and it looked like 480 on the display.

is to store a table of 226 words. Your chip (16F1827 I assume)
Actually 250 values between 10 and 2500 and yes 16F1827.

However, at the top of the scale it reaches 2500 with an input of 925 rather than 937
If that's critical,
I do really wish my brain could absorb all this C stuff, I did sort of understand the flow of it.

It is not critical critical if that makes any sense - the input to all this is a regular pot which when centered is the 'dead band', all I need to make sure of is that the knob can be turned through the bulk of the distance either side of center rather than having a large bit at the end that does nothing.

Off to see if I can do a small mod to the test jig to run the backlight off 5V and play with Mike's formula in Excel to see if I can make sense of it.

As an aside, how easy/hard would it be to convert RJ's bit of C code across to Oshonsoft basic? I might have a play and see what happens.
 

rjenkinsgb

Well-Known Member
Most Helpful Member
For Mike's routine you need floating point maths.
For mine you would need a 32 bit integer multiply, but no floating point.

Probably the simplest "precision" method in assembly is to mix the various approaches:

Remove the offset and get the absolute value, as in the first half of mine.

If it's non zero, then use a lookup table to convert the input range to 0-249 output
Add one and multiply by ten (shift left once, save that, shift left two more times and add the two parts).


Or if the end limits are not too critical - remove the offset & get the absolute value etc. then return zero if it's in the dead zone.

That should give you a value somewhere around 0-512; just set it to 498 if it's greater than that, add 2 (to get the minimum 10 output) and multiply by 5 (shift left twice and add the original).

That gives 10-2500 with a fractional dead zone at either extreme end, but next to no maths.
 

Buk

Active Member
Effectively you are converting an unsigned 10-bit value to a signed 9-bit and then multiplying by 10.
(Plus some fudge factors, which may or may not be important to your application.

And that can be done in 3 opcodes (depending on your flavour of cpu).

In pseudo asm:

Code:
in regN, $ADC
shr regN, 1
sub regN, 256
mult regN, 10

This gives
Code:
 0 : -2560
  1 : -2560
  2 : -2550
  3 : -2550
  4 : -2540
  5 : -2540
  ...
 32 : -2400
 33 : -2400
 34 : -2390
 35 : -2390
 36 : -2380
 37 : -2380
  ...
 508 :   -20
 509 :   -20
 510 :   -10
 511 :   -10
 512 :     0
 513 :     0
 514 :    10
 515 :    10
 516 :    20
 517 :    20
 518 :    30
 519 :    30
...
 934 :  2110
 935 :  2110
 936 :  2120
 937 :  2120
 938 :  2130
 939 :  2130
 ...
1019 :  2530
1020 :  2540
1021 :  2540
1022 :  2550
1023 :  2550

which might be close enough to your specification
 

Nigel Goodwin

Super Moderator
Most Helpful Member
Possible - I wrote a small file to actually display the raw ADC values from the test jig, but I'm running a 5V LCD on 3.3V.
While the electronics run fine at 3.3V, the backlight not so much and is quite dim and it looked like 480 on the display.
It's not the backlight, that's just an LED that you can easily give more current, it's the LCD panel itself that doesn't like too low a drive voltage.

I've tried to run them at 3.3V as well, some aren't too bad, but some are dreadful.
 

augustinetez

Active Member
Quick update - I've got exactly nowhere so far.

Answered my own question about converting RJ's bit of C code to Basic - of course it won't, Basic can't do negative numbers (Basic for micro's that is). Yes, I know there are work-arounds but that just gets messy.

Still to look at RJ's second suggestion (post #11)

Mike's excel formula just gave me a headache and a load of spots before my eyes. :)

And BUK's method doesn't condense the results to be within the actual ADC range of 35 - 937.

Got to spend a load of time doing my day job (filling in paperwork for the Govt) over the next week, so will let this idle for a while and see if a miraculous brainwave strikes :banghead: :stop:
 

Buk

Active Member
And BUK's method doesn't condense the results to be within the actual ADC range of 35 - 937.
If you feed my method 237, you get -2380.
If you feed it 937, you get 2120.

If you want to get closer to your spec, add 37 to the input and multiply by 11 instead of 10.

Ie:
Perl:
 sub x{
     my $n = shift;
     $n += 37;
     $n >>= 1;
     $n -= 256;
     $n *= 11;
     return $n
}

Which gives
Code:
 37 => -2409
 38 => -2409
 39 => -2398
 ...
 472 =>  -22
473 =>  -11
474 =>  -11
475 =>    0
476 =>    0
477 =>   11
478 =>   11
479 =>   22
...
934 => 2519
935 => 2530
936 => 2530
937 => 2541
 

Buk

Active Member
If you want to get closer to your spec, add 37 to the input and multiply by 11 instead of 10.
To get closer still, add a second fudge factor by subtracting 40 from the previous result and rounding to the nearest 10:
Perl:
sub x{
    my $n = shift;
    $n += 37;
    $n >>= 1;
    $n -= 256;
    $n *= 11;
    $n -= 40
    int( $n/10 ) * 10;
}

Gives:
Code:
 37 => -2440
 38 => -2440
 39 => -2430
 40 => -2430
 41 => -2420
 ...
 478 =>  -20
479 =>  -10
480 =>  -10
481 =>    0
482 =>    0
483 =>    0
484 =>    0
485 =>   10
486 =>   10
487 =>   20
...
932 => 2460
933 => 2470
934 => 2470
935 => 2490
936 => 2490
937 => 2500
 

Pommie

Well-Known Member
Most Helpful Member
Terry,
A little simpler approach (I hope).
1. get your ADC value.
2. if above 491 calculate ADC-492 - this allows for the dead zone.
3. if below 483 calculate 482-ADC
4. if neither 2 or 3 true then calculated value=0
5. if not zero then lookup value in a table.

The table would ideally be 512 (14 bit) words in the last two pages of flash (I think mpasm had DW but might only have been for 18 series chips) but could be a huge table of retlw values which would require 4 pages of flash (1/4 of your memory). I can supply the table in either format if you want to try this path.

Mike.
 

Pommie

Well-Known Member
Most Helpful Member
Terry, I just tried using DW in MPLAB 8.92 and it works.
I placed this in the code,
Code:
    org 0xe00
    DW  5,11,16,22,27,33,39,44,50,55,61,67,72,78,83,89
    DW  95,100,106,111,117,123,128,134,139,145,151,156,162,167,173,178
    DW  184,190,195,201,206,212,218,223,229,234,240,246,251,257,262,268
    DW  274,279,285,290,296,302,307,313,318,324,329,335,341,346,352,357
    DW  363,369,374,380,385,391,397,402,408,413,419,425,430,436,441,447
    DW  453,458,464,469,475,480,486,492,497,503,508,514,520,525,531,536
    DW  542,548,553,559,564,570,576,581,587,592,598,604,609,615,620,626
    DW  631,637,643,648,654,659,665,671,676,682,687,693,699,704,710,715
    DW  721,727,732,738,743,749,755,760,766,771,777,782,788,794,799,805
    DW  810,816,822,827,833,838,844,850,855,861,866,872,878,883,889,894
    DW  900,906,911,917,922,928,934,939,945,950,956,961,967,973,978,984
    DW  989,995,1001,1006,1012,1017,1023,1029,1034,1040,1045,1051,1057,1062,1068,1073
    DW  1079,1085,1090,1096,1101,1107,1112,1118,1124,1129,1135,1140,1146,1152,1157,1163
    DW  1168,1174,1180,1185,1191,1196,1202,1208,1213,1219,1224,1230,1236,1241,1247,1252
    DW  1258,1263,1269,1275,1280,1286,1291,1297,1303,1308,1314,1319,1325,1331,1336,1342
    DW  1347,1353,1359,1364,1370,1375,1381,1387,1392,1398,1403,1409,1414,1420,1426,1431
    DW  1437,1442,1448,1454,1459,1465,1470,1476,1482,1487,1493,1498,1504,1510,1515,1521
    DW  1526,1532,1538,1543,1549,1554,1560,1565,1571,1577,1582,1588,1593,1599,1605,1610
    DW  1616,1621,1627,1633,1638,1644,1649,1655,1661,1666,1672,1677,1683,1689,1694,1700
    DW  1705,1711,1717,1722,1728,1733,1739,1744,1750,1756,1761,1767,1772,1778,1784,1789
    DW  1795,1800,1806,1812,1817,1823,1828,1834,1840,1845,1851,1856,1862,1868,1873,1879
    DW  1884,1890,1895,1901,1907,1912,1918,1923,1929,1935,1940,1946,1951,1957,1963,1968
    DW  1974,1979,1985,1991,1996,2002,2007,2013,2019,2024,2030,2035,2041,2046,2052,2058
    DW  2063,2069,2074,2080,2086,2091,2097,2102,2108,2114,2119,2125,2130,2136,2142,2147
    DW  2153,2158,2164,2170,2175,2181,2186,2192,2197,2203,2209,2214,2220,2225,2231,2237
    DW  2242,2248,2253,2259,2265,2270,2276,2281,2287,2293,2298,2304,2309,2315,2321,2326
    DW  2332,2337,2343,2348,2354,2360,2365,2371,2376,2382,2388,2393,2399,2404,2410,2416
    DW  2421,2427,2432,2438,2444,2449,2455,2460,2466,2472,2477,2483,2488,2494,2500,2500
    DW  2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500
    DW  2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500
    DW  2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500
    DW  2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500,2500
and it assembled.
An inspection of the memory (in simulator) showed,
mplab.png

Which looks like it worked.
The assembly code to retrieve the values is on page 106 of the datasheet.
HTH.

Mike.
Edit, I assumed the default radix is decimal, if not I can produce the table anyway you like.
 

Latest threads

New Articles From Microcontroller Tips

Top