Hi guys.. I am capturing voltage values from ADC & would like to display the voltage value captures on the LCD with minimum 1 decimal place. Does anyone has some tips to do the math in assembler?
(First of all, with a title of "binary fraction multiplication in ... assembler" you only scare people away! )
So, in the simplest case you want to do something like Actual_ADC_Reading * Max_Voltage / MAX_ADC_VALUE
(To minimize errors, first multiply, then divide. You will need the corresponding math library routines for multiplication and division.)
Let's assume Max_Voltage is 5V. MAX_ADC_VALUE is 255 for 8-bit mode, 1023 for 10-bit mode, and 4095 for 12-bit mode.
If you want one decimal place, then Max_Voltage should become 50 (or, if you need two decimal places, 500). Then, the integer number you get will have one (or two, for the 500 case) implied decimal place(s).
When you convert the number to a string so you can print it on your LCD, simply make the string long enough for the decimal places you want (by adding the correct number of leading zeros, when needed) and then, add the decimal point one (or two) place(s) before the end of the string. (As a reference, see the AddDecimalPoint routine in here.)
Hope this helps.
Hi Tony, thanks for the tips.
I will have to divide the max voltage by the total steps to get voltage per steps as the multiplier for the ADC values captured. The multiplier will probably be 0.something. I would need to know how to represent the 0.something in assembler and the subsequent calculations.
No need to mess with floating-point math. As any fraction 0.x can be represented as a a ratio (e.g., x/10, using as many digits as needed for the required precision,) I don't really see with what you're having problems. The above should still work. Just do the math in a different order than you had thought about it. Instead of voltage/step * steps to find the voltage corresponding to the A/D reading value, do it as shown already.
(Otherwise, I don't yet understand what it is you're trying to do.)
The ADC result is given by the expression:
N = Vi * Nmax / Vr
Vi is the direct input voltage to the ADC channel,
The reference voltage Vr is likely to equal Vdd.
Nmax = 1024 for 10-bit mode, or 4096 for 12-bit mode.
The result N has the range 0 to Nmax-1.
Since you require a decimal result (rather than binary), you would multiply by a factor of 100 to achieve two decimal places for the displayed result.
100 * Vi = N * 100 * Vdd / Nmax
For 10-bit mode, with Vdd = 5 volts, you would require the following calculation to obtain the scaled binary result, which would then need to be converted to an ASCII numeric display sequence.
100 * Vi = N * 500 / 1024 or N * 250 / 512
16 x 16 bit unsigned multiplication will be used for the first part of the calculation. The 32-bit result will then be shifted 9 positions to the right to achieve the "power of two" division process. This would give a result between 0 and 499, to be converted to the numeric ASCII sequence.
The attachment has subroutines to cater for the unsigned multiplication, and for the ASCII conversion. There are two conversion rotines shown, one with left justification, and the other with right justification. These routines could potentially be simplified since you would require only three display digits, rather than five digits for the full 16-bit range.
Hi bigmac, as always the savior.. thanks for the reference code. I will check it out.
MY, have you checked out the suggested answers?
We would like to know how is the project going!
after going through the code.. I couldn't understand a part if my multiplier if 128 & multiplicand is 0.14. How do I relate this equation into the code?
to make math in assembler, usually in a fixed point you must change your mind.
First of all I assume you have to do simple calculations like those BigMac suggests to you, i.e. operations on 8 or 16 bit numbers which means an integer number ranging 0 to 2^16-1, i.e 0 to 65535. When you use a fixed point calculation you already know if this bigger number is an integer or represent a value in decimal or centesimal or millesimal format: I mean, if this represents a voltage, it may represent a value as large as 65535V or lower as 6.5535V if a single bit represents 1V o 100uV or any else in the middle or even higher or lower!.
If you have to multiply it for a fractional value B, where B= 0.14 in your example, you must multiply your 16 bit number for another 16 bit constant, taking the higher 2 MsB of your 4 Byte result, i.e. 16 out of 32 bit.
You must take in mind that in this case 2^16 (65536 dec or $10000) represents 1.0 (the unity). In your example 0.14 would be represented by a binary number in 16bit format equal to (65536*0.14)dec = 9175dec or $23D7
Expanding your example C= A*B, where A=128, B=0.14, in 16*16 bit multiplication this would give:
A=$0080, B=$23D7, C=$(0080*23D7)= $0011.EB80
where $0011 represents the integer result, in fact $0011= 17dec or INT(128*0.14) and $EB80 the fractional part you may use or discard depending on your needs. In this case the fractional part is very near 1, in fact it means 0.92 and represent quite a large error compared to the integer part: 0.92/17= 0.054 or 5.4%. For a good precision your multiplicand "A" must be as high as possible to have a result with good precision where the discarded bits in the multiplication would be little meaningful. You could have done this by adding 2 dummy lsB (00) on the multiplicand to do $8000*$23D7. Your result would have been $11EB8000 or $11.EB8000 which is higher in precision than before: this is specially useful if you have needed to use this result as an intermediate 16 bit operand ($11EB) for another calculation.
With a little care you may achieve a precision in the order of 0.05 to 0.01% at the end of all your calculations which is more than enough for any voltmeter based on a mCU embedded ADC.
Than you will finally need a routine which transforms your binary/hexadecimal result in a 3 to 5 digit decimal number, which is “CONVERT3” or “CONVERT4” from BigMac code snippet
Retrieving data ...