1
\$\begingroup\$

I am getting full scale ADC value from 0 to 16348. I am trying to convert it to 4-20mA and then to 0-25 bar pressure, as I am ultimately measuring pressure.

I am using the y = mx+c equation but failing miserably. Can any one please guide so that I can learn something.

I am trying something like

4 = m*0 +c
c = 4.

20 = m*16348 +4.

m = 0.0009789.

My controller is not supporting floating points...hence it's creating trouble for me. Can anyone help me out>?

\$\endgroup\$
  • 2
    \$\begingroup\$ Are you certain that 4mA corresponds to 0 count and 20mA corresponds to 16383 count? \$\endgroup\$ – Ignacio Vazquez-Abrams Apr 10 '15 at 5:01
  • \$\begingroup\$ Try writing it as y = x * mn / md + c. Then mn = 16 and md = 16348. Be sure to do the multiplication before the division. \$\endgroup\$ – The Photon Apr 10 '15 at 5:12
6
\$\begingroup\$

Since your microcontroller doesn't support floating point, then you need to scale the numbers up and then divide back down. To do so, you will need to use long (32-bit) arithmetic.

For the ADC, 4 ma = 0 and 20 ma = 16384. Therefore the difference 16 ma is also represented by 16384.

If we take the full scale reading of the bar graph, 25, and divide by 16384, we get: 0.001526. But we can't use that value directly since it is floating point. So instead we take one million, and do the same thing. 1000000 / 16384 = 61.03 Now we have a number close enough to an integer which we can work with.

If we know take the reading from the ADC, multiply by 61, and divide by 40000 (which is 1000000 / 25), then we will have a number in the range 0-25. To try this out, if we take a value equal to half the range (16384/2), then for 8192 * 61 / 40000 we get 12.49 or half of 25 (but since its integer division, it will be rounded down to 12)

If, for some other application, you wanted an integer value in scaled hundreds (i.e. multiplied by 100), you could divide by 400 instead of 40000 and get 1249 (representing 12.49).

So the code is something like this (I've used casting to make sure all the necessary calculation are done as longs)

#define CONSTANT1 = (1000000L / 16384L)       // 61    
#define CONSTANT2 = (1000000L / 25L)          // 40000

unsigned short ADC_value;
unsigned short bar_value;

ADC_value = get_ADC();
bar_value = (unsigned char)(((unsigned long)ADC_Value * CONSTANT1) / CONSTANT2);
|improve this answer|||||
\$\endgroup\$
2
\$\begingroup\$

It's not very clear what you are doing, so let's go step by step.

So, the sensor gives a current, where 0-25 Bar correspond to 4-20mA.

$$I_{sens}=16mA\cdot \frac{p}{25 Bar} +4mA$$

Now, the ADC itself has an input range, e.g. 0-5V and converts it to an integer number. I don't know how you get 16348, the next matching value would be 16383, which is binary 111111'11111111. (That's the maximum value of a 14 bit ADC)

So, you need a resistor like R=250 Ohm connected to your sensor output (to ground), which converts the current to 1-5V

Now, the formula is

$$V_{ADC}=4V\cdot \frac{p}{25 Bar}+1V$$

and this is mapped to the ADC range

$$N_{ADC}=\frac{16383\cdot 16}{20} \cdot \frac{p}{25 Bar}+\frac{16383\cdot 4}{20}$$ and its reverse $$p=\left(N_{ADC}-\frac{16383\cdot 4}{20}\right)\cdot\frac{20\cdot 25Bar}{16383\cdot 16}$$

or after cleaning up: $${p={\frac{125\cdot N_{ADC}-409575}{262128}}Bar}$$

This is the best you can get, however, the resolution is just 1 Bar, because p is an integer. And as said in the comments, it's important to do the division as the very last step.

The resolution is poor, though the dynamic range is 16383/20*16=13106 (That's the number of steps the ADC maps your input range to). For a better resolution, you may go for milli-Bar:

$${p={\frac{1000\cdot(125\cdot N_{ADC}-409575)}{262128}}millibar}$$

Again, do the multiplication first. And: The largest value of the numberator is 1,638,300,000, which occupies 31 bit. So, you have to ensure your microcontroller does this calculation with 32bit integer values.

Some ideas:

  • If you don't like this large numbers, you may use 16384 instead of 16383. This is only a small deviation but may allow more cancellations in the fraction. Also, if you will never measure 25Bar, try a resistor which gives the maximum voltage already at 24 bar. This value also allows for more cancelations.

  • Some microcontrollers e.g. from microchip allow to give min/max reference voltages, e.g. you can connect two voltages defining the upper and lower limit of the ADC. With 1V and 5V, this will map the 1-5V from the resistor to the full 14bit of the ADC, giving you the full 16384 steps, not just 80% of it.

  • The last point can also be achieved by an operation amplifier.

EDIT:

As the values need a maximum of 31 bit, you should use a signed 32bit integer value (which is 1bit for sign and 31 for the number). If no sensor is connected / the ADC receives less than 1V, you get a negative value for the pressure which can be said to be invalid.

If you use unsigned 32bit integers, you get some rubbish in this case and can not distinct between valid and invalid value.

|improve this answer|||||
\$\endgroup\$
1
\$\begingroup\$

I'm guessing that you are actually getting a value 0 to 16383 (3FFFh) as is standard for a 14 bit ADC. It seems quite strange that your ADC would give value zero for 4mA, but that's what you tell us, so I'll assume you have some special circuit doing this for you.

Converting to milliampere as a middle step is nonsense, that's a representation used by humans and not needed by your software, unless you wish to display the current on a display or something. It seems you don't need that middle step, all it will do is cause rounding errors.

Same thing with floating point: in most cases you shouldn't be using float numbers, because chances are that they are terribly slow. They are always slower than integers, but especially so if there is no FPU on board.

So what you should be doing is: when programming, stop thinking like you do when doing maths. Floating point is reserved for the cases where you need accuracy and more advanced math, such as trigonometry. Your program will work perfectly fine with raw integers in 95% of the cases.

To the problem at hand, you have an integer value 0 to 16383 which you want to convert to 0-25 bar. Using y = mx + c gives you 25 = m*16838 + 0, m = 25/16838. So by multiplying any ADC read with 25/16383 you'll get the bar value, ie adc_read * 25 / 16383.

We also need to multiply by 1000 to get greater resolution, millibar instead of bar. So Apparently the largest value we can ever encounter in this case is 16383 (max ADC read) multiplied with 25*1000 = 159*10^6. This won't fit in a 16 bit integer, so we need to use 32 bit arithmetic.

uint32_t adc_read = ADC_DATA_REGISTER;
uint8_t millibar = (uint8_t)(adc_read * 25ul * 1000ul / 16383ul);

And that's it. Assuming that 0 actually corresponds to 4mA.

|improve this answer|||||
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.