## Low level arithmetics implementation on ATmega328

34 posts / 0 new
Author
Message

Dear All,

Let's consider this case: "V(v)=ADC*V(ref)/1024;"  to convert the "uint16_t word " into voltage readings. The floating point variables are not recommended as I have read older posts here.

So what is left in my understanding, is to try to translate this equation into bitshift operations, instead of using (*/ divide operators).

Any reasonable ways to program this? in a generic and maintainable way would be very helpful.

This topic has a solution.

work in progress...

Last Edited: Fri. Sep 28, 2018 - 04:42 PM

Dave_Zeb. wrote:
So what is left in my understanding, is to try to translate this equation into bitshift operations

No need for that, as that is what the C compiler does for you!  Just write your code using easy to understand math, let the compiler do the heavy lifting for you.

Jim

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get \$5 free gold/silver https://www.onegold.com/join/713...

The key thing (to keep things integer) is not to set Vref to be 5.0 (assuming 5V operation) but something like 5000 (ie 5V in millivolts). Then, however small ADC actually is you'll probably still come out with some integer value because what you are representing in "V(v)" is mV not volts. All you have to remember is that when you finally present this to the user put a '.' in the right place. So if the V(v) is 1234 show it as "1.234" and if it is 13 show it as "0.013" etc.

In this case YOu place the '.' so no float math is involved.

But what AVR is this and what C compiler. As long as you have a few K you can probably afford floating point any way. In avr-gcc for example the core arihtmetic for float (+-/*) costs about 700 bytes of flash. To add printing support (ie support of %f in printf()) costs a bit over 2K. In a 4K+ micro you probably have the room for this. If you are talking about a 0.5K/1k/2K micro you may have to stick to integer only.

There is a tradoff between "general and maintainable" and code size / speed.

I would do it with integer math.

With integer math you only have integers, no fractions, but you can use fixed point math with integers.

Because you do not have fractions, you can easily loose resolution with division, so be carefull with that.

An example:

The ADC has an 10 bit resolution.

But we want as much resolution as we can manage in our 16 bit.

(Some of the ADC's have an option to read the 10 bits left justified, then no shift is needed).

The goal is to use all of our 16 available bits.

Vref and "1024" are both constants. So you can calculate a single division factor in advance, or you can let the compiler (More accurate: Preprocessor) do that for you.

Note that the value "1023 / Vref " is a somewhat arbitrary value.

You of course have to correct it for the 6 bit shift ( so "32736/ Vref" would be more appropriate)

Then you can tweak your division constant for the result you want.

Do you want a result with a resolution of 10mV or 100mV?

What resolution does make sense with the ADC value you have?

By starting by left shifting the ADC the final division will by by a relatively big number. this is good because you can then also change that number for calibration purposes.

C does not do rounding, and it will always calculate the floor.

If you want a rounded result, then add half of the division factor to the ADC value.

```#define ADC_VREF 5

```

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com

sorry forgot to mention the AVR Device is ATmega328P, and the Compiler is "AVR-GCC". Actually I would go more for towards trying to add this support, in case I upgrade the AVR later on, to have the support in my pocket already.

I have uncommented the suitable lines inside "makefile":

```## for floating-point printf
LDFLAGS += -Wl,-u,vfprintf -lprintf_flt -lm

## for smaller printf
LDFLAGS += -Wl,-u,vfprintf -lprintf_min```

question 1.) should I add also #include<stdio.h> and #include<stdlib.h> and #include<float.h>?

2.) usual prinf("% f ", var), will do the job?= to send them via USART channel?

3.) ADC in my case is "word" so uint16_t, typecasting yes/no?

work in progress...

AS7 has simple boxes to tick for printf_flt support.

I strongly recommend doing your maths in straightforward f-p.   And using the library printf_flt.

You have 32kB of Flash.   Your code will be simple to read and easy to maintain.

As an interesting exercise.    Follow through message #4 carefully.

There is nothing wrong with doing integer maths.    Almost essential on  a Tiny2313.

Just be careful.

David.

Last Edited: Wed. Sep 26, 2018 - 02:19 PM

I go straight to 32-bit math (not expensive since the 328 has a hardware multiplier).  Let's say you want a value in millivolts:

```#define Vrefmv 5000UL  // for Vref = 5V - set to your Vref value
#define mN ((65536UL*Vrefmv)/1024UL)

Vmv = (uint16_t)((ADC * mN) >> 16);
```

Here you set your Vrefmv to your Vref value in millivolts, and you set a multiplier mN (which stands for the numerator of 'm' in the standard line equation y = mx + b).

The >> 16 will, at least for AVR gcc, get optimized to a simple grab of the high 2 bytes of the 4 byte intermediate calculation - very fast, no actual shifting involved.

Try it out by calculator with an ADC reading of 512 to confirm it works; you will get 1/2 your Vrefmv (so 2500 for a Vrefmv of 5000).

Last Edited: Wed. Sep 26, 2018 - 03:14 PM

question 1.) should I add also #include<stdio.h> and #include<stdlib.h> and #include<float.h>?

2.) usual prinf("% f ", var), will do the job?= to send them via USART channel?

3.) ADC in my case is "word" so uint16_t, typecasting yes/no?

1) You include any .h because you use something it provides. Otherwise don't bother. (I suppose there's no harm #include'ing headers from which you use nothing but it will slightly delay the build - especially if one header cascades to include loads of others)

2) print() will only direct to the UART (in avr-gcc) is you have done the FDEV_SETUP_STREAM thing to associate stdout/(stderr) with a FILE stream device that ultimately attaches to a uart-putchar() that you implement.

3) Always use the type that is appropriate. As you say "ADC" is ultimately a pointer to a uint16_t location so you shouldn't need any typecasting as such (that already happens in the macro behind "ADC") but clearly if you assign the value to something it should be wide enough to accept the value. So:

`uint16_t reading = ADC;`

probably makes more sense than:

`uint8_t reading = ADC;`

However you could, of course, do something like:

`float reading = ADC;`

and by the rules of C promotion there will be an implicit promotion from uint16_t to float.

i tried this solution and if only I could get rid of that front "0" character  and put a comma, after the  second to make it 4.9..... instead of 04995.... I tried to bitshift (1<<V_mv), to get rig of the "0" did not work.

please have a look in console.

```potentiometer readings(mV)=04995                LDR-Sensor readings(a.u.)=00369         LDR-mod-Sensor readings(a.u.)=00076

work in progress...

Go on.   You have very sensibly chosen to use the proven library printf()

Then posted reams of pointless output with no mention of your format string in the actual printf() statement.

You can format your output in any way that you desire.    But you have not said what you desire.

Left padding with spaces is the default.   Why did you choose to pad with zeros?

David.

Last Edited: Wed. Sep 26, 2018 - 06:33 PM

I'm not following you.  You mean that, for example, you want 04995 to be shown as 4.995 ?  That all depends on how >>you<< create the display string -- and we do not see the code.

On a side topic, remember that while you certainly can do the calculations in "millivolts", an AVR ADC is only 10 bits so showing more than three significant digits is just noise.

Here is a code fragment for the way I do it with integer calculations, but display with a decimal point.  Note that the e.g. %2u takes care of suppressing the leading 0.

```				else
{
// Report in hundredths of percent,
//  of the value in thousandths of percent
sprintf (line3, "%2u.%02u%% %8p     ",
worknum/1000,(worknum%1000)/10,
flash_trip_names[theproc.trip_action]);
}
```

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Wed. Sep 26, 2018 - 06:38 PM

This equation can be done in fixed point logic - don't get me wrong fixed point is not floating point.

sth like that I did in:

My method is (for a full scale value of 5V):

```ADCD result:                   0000 00xx xxxx xxxx
multiply by 5:                 0000 0Mrr rrrr rrrr
copy high bit, shift by 2:     0000 000M ;first digit
mask remainder:                0000 00rr rrrr rrrr
multiply by 10:                0000 0Mrr rrrr rrrr
copy high bit, shift by 2:     0000 000M ;second digit
mask remainder:                0000 00rr rrrr rrrr
multiply by 10:                0000 0Mrr rrrr rrrr
... repeat for as many decimals as you need```

This reply has been marked as the solution.

Like others said.

Print to a string, and then shitf the characters a bit around.

Someling like:

```

char buf [10];
buf[0] = buf[1];
buf[1] = buf[2];
buf[2] = '.';

// Print buf to uart.

```

If you have a value of 05995 which represents a voltage of 5.995V then you are already working with "fixed point".

You may also want to consider that floating point calculations can be faster than 32 bit calculations.

With 32 bit integers, you have to multiply 2 rows of 4 bytes = 8 multiplicatatons for the result.

With floating point you only have a mantissa of 24 bit = 2*3 = 6 multiplications (and an addition) for the result.

The problem with floating point is that you have to link in a few kb for the libraries, and floating point support for printing your numbers.

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com

Paulvdh wrote:
You may also want to consider that floating point calculations can be faster than 32 bit calculations.

Can be?  In certain instances, yes, I guess.

Paulvdh wrote:
With 32 bit integers, you have to multiply 2 rows of 4 bytes = 8 multiplicatatons for the result. With floating point you only have a mantissa of 24 bit = 2*3 = 6 multiplications (and an addition) for the result.

In that described case?  Without further evidence, I'll call that "fake news".  Show a source fragment.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

theusch wrote:
Without further evidence, I'll call that "fake news".

Experiments last year show evidence for your postulate. ;)

https://www.avrfreaks.net/commen...

https://www.avrfreaks.net/commen...

Paulvdh wrote:
So a float multiplication is faster than an uint32_multiplication.

That is you commenting on Brian Fairchild work that kicked off that thread.  But read further into that thread, after Brian re-ran the tests:

As I would expect, float mult is ~3x of 32-bit integer.  Yes, there are fewer raw MUL.  But there is normalization and denormalization and NaN checking and similar.

Now I need to take the links to try to find the David Prentice work...

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

with David Prentice results, but no direct 32-bit multiply

david.prentice wrote:
Hello ImageCraft v8.02 running @ 16MHz cycles secs flops OPS/MHz [iter] operation 2000 0.000125 8000000.5 500000 [1000] overhead loops 373523 0.023345 42835.4 2677.2 [1000] fp adds 365363 0.022835 43792.1 2737 [1000] fp mults 984502 0.061531 16251.9 1015.7 [1000] fp divs 7690263 0.480641 2080.6 130 [1000] log 6827695 0.426731 2343.4 146.5 [1000] sqrt 18968368 1.185523 843.5 52.7 [1000] pow 197055 0.012316 81195.6 5074.7 [1000] y=mx+b using longs 825247 0.051578 19388.1 1211.8 [1000] y=mx+b using floats Hello GCC 20100110 volatile running @16.000MHz cycles secs flops OPS/MHz [iter] operation 1000 0.000063 15999999.0 999999.9 [1000] overhead loops 124235 0.007765 128788.2 8049.3 [1000] fp adds 152630 0.009539 104828.7 6551.8 [1000] fp mults 494280 0.030893 32370.3 2023.1 [1000] fp divs 2347665 0.146729 6815.3 426.0 [1000] log 494094 0.030881 32382.5 2023.9 [1000] sqrt 496094 0.031006 32252.0 2015.7 [1000] pow 85043 0.005315 188140.1 11758.8 [1000] y=mx+b using longs 278897 0.017431 57368.9 3585.6 [1000] y=mx+b using floats Hello CodeVision v2.04.9a running @16.000MHz cycles secs flops OPS/MHz [iter] operation 2000 0.000125 7999999.5 500000.0 [1000] overhead loops 142573 0.008911 112223.2 7014.0 [1000] fp adds 223662 0.013979 71536.5 4471.0 [1000] fp mults 789300 0.049331 20271.1 1266.9 [1000] fp divs 3716299 0.232269 4305.4 269.1 [1000] log 8271666 0.516979 1934.3 120.9 [1000] sqrt 7665039 0.479065 2087.4 130.5 [1000] pow 118052 0.007378 135533.5 8470.8 [1000] y=mx+b using longs 332208 0.020763 48162.6 3010.2 [1000] y=mx+b using floats

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

In the old days a divide took more time than a multiply.  So instead of having 1. / 1022, we would define a constant cdiv = 1. / 1022. and then do a multiply by cdiv instead of a divide by 1022.  I dont know if that is still the case, or if gcc is smart enough to do that for us.

Isn't this just a perfect example of where you should use fixed-point math?

The ADC reading is an integer. Vref is, if you consider its accuracy, a fixed-point number with a limited number of places to the right of the DP. And 1024 is an integer.

#1 This forum helps those that help themselves

#2 All grounds are not created equal

#3 How have you proved that your chip is running at xxMHz?

#4 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

My example used 1022.  It could have been 833.  This trick was used when the divide by a constant was used multiple times in a loop or in various other places, so all the divides could be replaced by a multiply, and the divide done once.

I think Brian's comment is right.  Vref can be made a bigger integer to get all the significant figures, like Cliff suggested, and there you go.  Problem solved.

I'm still curious, though.  Does a double divide take more time than a double multiply in gcc.  It must.

MarkThomas wrote:

...  Does a double divide take more time than a double multiply in gcc.  It must.

While the speed/efficiency of arithmetic operations for a toolchain is important and may indeed be critical in certain apps (e.g., transforming every pixel) in this case a microsecond or two one way or the other, or even many microseconds, probably isn't that important.  The ADC isn't all that fast, and any reasonable implementation will only update the display a few times per second.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

BTW, if we go back to OP's OP:

Dave_Zeb. wrote:

Note that if one encodes V(ref) as tenths of volts, up to 6.4V being 64, then all this 32x32+32 is slower than FPxFP=>FP is moot as the whole operation can be done in 16 bits without any loss of accuracy.  Only if the precision of V(ref) >>must<< be specified finer than that would it matter, and even then would be down in the noise of the stability of the Vref and the signal and the AVcc and similar.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

I did my google and nearly everyone says a divide takes more clock cycles than a multiply.  One fellow said this:

Yes, many CPUs can perform multiplication in 1 or 2 clock cycles but division always takes longer (although FP division is sometimes faster than integer division).

If you look at this answer you will see that division can exceed 24 cycles.

MarkThomas comes through with another dumb question he already knew the answer to.

It is true that it probably doesn't matter in the long run, unless you are doing a lot of complicated double arithmetic like I do inverting 9x9 sets of equations.  Still, probably doesn't matter.  Besides, how many people solve Maxwell's equations on a microcontroller that takes a cluster of 255 nodes with math co-processors to get an answer in less than a second, like I used to do.

MarkThomas wrote:

I did my google and nearly everyone says a divide takes more clock cycles than a multiply.  One fellow said this:

It does. There are a number of shortcuts for a multiply but, AFAIK, no-one has ever devised any for division.

#1 This forum helps those that help themselves

#2 All grounds are not created equal

#3 How have you proved that your chip is running at xxMHz?

#4 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

theusch wrote:

BTW, if we go back to OP's OP:

Dave_Zeb. wrote:

Note that if one encodes V(ref) as tenths of volts, up to 6.4V being 64, then all this 32x32+32 is slower than FPxFP=>FP is moot as the whole operation can be done in 16 bits without any loss of accuracy.  Only if the precision of V(ref) >>must<< be specified finer than that would it matter, and even then would be down in the noise of the stability of the Vref and the signal and the AVcc and similar.

Yes, and once you are limiting the Vref precision to 6 bits, then 8 bits is plenty good enough from the ADC.  Then instead of any dividing or right-shifting you can just toss out the low byte of the 16-bit multiply for your result.  That is minimalist!  Probably a rather rare situation, but the mental exercise is fun in any case.

Hi everyone, good to be back here. Adding here some info regardless of the Vref. So the on-board ADC hardware is initialized , with the Analog Reference on (AV)cc and external capacitor (10µF) on AREF pin.

work in progress...

Hi Guys ^^),

decided to tune in with some fresh info regarding my issue, and our collective efforts to solve it. I have change the code as discuss above using the sprintf() function and the buffer.  As in the code below.

```void data_console_out(void)
{
char buffer[16];
double Voltage_potti;

sprintf(buffer, "%0.5f", Voltage_potti);

printString(buffer); // this prints to UART

printString("\t\t");
printString("\t\t");
printString("\r\n");
}
```

this snippet is just a function called in main, to do some laundry.

```#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <USART.h>
#include <avr/power.h>
#include <AVR-ATmega-328p-PU-Hardware-connections-Bay.h>
#include <Functions-Prototypes.h>
#include <In-Lining-Functions.h>
//---------------------------Functions--------------------------//
ISR(TIMER2_COMPA_vect) // ISR routine to override the actual 8 bit led display status.
{
}

ISR(INT0_vect) // ISR routine for user input aka Button L.
{
printString("Test No1");
}

ISR(INT1_vect) // ISR routine for user input aka Button R.
{
printString("Test No2");
}

int main(void){
//----------------------Initializations-------------------------//
LED_DDR|=0xff; //LED PORT register setup for output mode
SPEAKER_DDR|=(1<<SPEAKER_CONN_BAY); //speaker output enable
BUTTON_PORT|=(1<<BUTTON_L_CONN_BAY)|(1<<BUTTON_R_CONN_BAY); //internal pull-ups for button-1/2
initUSART(); // power-up USART
init_sys_interrupts();
init_Timer_0(); // power-up Timer-Counter-0
init_Timer_1(); // power-up Timer-Counter-1
init_Timer_2(); // power-up Timer-Counter-2
printString("\t\tAVR Device initializing.........................");
printString("\r\n");
clock_prescale_set(clock_div_2); // processor clock speed
//---------------------------Event loop-------------------------//
while(1)
{
sei();
OCR0A=253;
OCR1B=500;
OCR1A=55000;
OCR2A=252;
data_console_out();
}
return(0);
}
```

Finally the results on behalf of the function data_console_out() below:

```

well, it seems to work

work in progress...

Dave_Zeb. wrote:
external capacitor (10µF) on AREF pin.

10uF is quite large, and could be a problem if you change the Aref with in the program, as it could take a LOOOONNNNGGG time to charge/discharge the ref cap.

I normally use a 100nF cap there, but choose wisely its value for your purpose.

Jim

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get \$5 free gold/silver https://www.onegold.com/join/713...

I am quite confused with your latest printout.  How can your calculations come up with different calculated voltage values from the same ADC reading ("ADC" in your code)???  Something isn't right.  Have you shown the complete program in your latest post?  We don't see the ...sampler routine.  Who knows what else?  initADC() ?  surely that is important.  The symptoms smack of "free running".

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

you mean this? :

```potentiometer readings(mV)=4.9804 <<this             LDR-Sensor readings(a.u.)=00387<<< this         LDR-mod-Sensor readings(a.u.)=00079 <<< and this?
```

```static inline void init_ADC()
{
}```

the sampler() routine:

``` uint16_t adc_Channel_Sampler(uint8_t channel)
{
{
}
}
```

work in progress...

This is very much a question of personal style.

1.   use whitespace around operators

3.   format neatly and indent blocks.

Otherwise I am sure that your code is functional if a little verbose.

I would avoid print statements inside an ISR()

Quite honestly,   you can scale the ADCW value to voltage in one statement.

The execution time or code size is not important.

Simple neat code is easy to read and maintain.

You should choose a style that you feel most comfortable with.   It is YOUR project.

David.

Hi David,  and thanks for your feedback.

3.   format neatly and indent blocks. <<  example would be helpful

Your adc_Channel_Sampler() does NOT test for completion. << should i add a a statement  in function which explicitly tests if ADSC bit is set?

``` while((ADCSRA & (1<<ADSC))==0) // wait until conversion is finalized. << this is not the check you mentioned?
{
}```

work in progress...

Dave_Zeb. wrote:
format neatly and indent blocks. <<  example would be helpful
Well one obvious one is this:

```int main(void){
//----------------------Initializations-------------------------//
LED_DDR|=0xff; //LED PORT register setup for output mode
SPEAKER_DDR|=(1<<SPEAKER_CONN_BAY); //speaker output enable
BUTTON_PORT|=(1<<BUTTON_L_CONN_BAY)|(1<<BUTTON_R_CONN_BAY); //internal pull-ups for button-1/2
initUSART(); // power-up USART
init_sys_interrupts();
init_Timer_0(); // power-up Timer-Counter-0
init_Timer_1(); // power-up Timer-Counter-1
init_Timer_2(); // power-up Timer-Counter-2
printString("\t\tAVR Device initializing.........................");
printString("\r\n");
clock_prescale_set(clock_div_2); // processor clock speed
//---------------------------Event loop-------------------------//
while(1)
{
sei();
OCR0A=253;
OCR1B=500;
OCR1A=55000;
OCR2A=252;
data_console_out();
}
return(0);
}```

The indentation in that is all over the place. the code sequence would be much clearer as:

```int main(void){
//----------------------Initializations-------------------------//
LED_DDR|=0xff; //LED PORT register setup for output mode
SPEAKER_DDR|=(1<<SPEAKER_CONN_BAY); //speaker output enable
BUTTON_PORT|=(1<<BUTTON_L_CONN_BAY)|(1<<BUTTON_R_CONN_BAY); //internal pull-ups for button-1/2
initUSART(); // power-up USART
init_sys_interrupts();
init_Timer_0(); // power-up Timer-Counter-0
init_Timer_1(); // power-up Timer-Counter-1
init_Timer_2(); // power-up Timer-Counter-2
printString("\t\tAVR Device initializing.........................");
printString("\r\n");
clock_prescale_set(clock_div_2); // processor clock speed
//---------------------------Event loop-------------------------//
while(1)
{
sei();
OCR0A=253;
OCR1B=500;
OCR1A=55000;
OCR2A=252;
data_console_out();
}
return(0);
}```

but that would still benefit from white space:

```int main(void){
//----------------------Initializations-------------------------//
LED_DDR |= 0xff; //LED PORT register setup for output mode
SPEAKER_DDR |= (1 << SPEAKER_CONN_BAY); //speaker output enable
BUTTON_PORT |= (1 << BUTTON_L_CONN_BAY) | (1 << BUTTON_R_CONN_BAY); //internal pull-ups for button-1/2
initUSART(); // power-up USART
init_sys_interrupts();
init_Timer_0(); // power-up Timer-Counter-0
init_Timer_1(); // power-up Timer-Counter-1
init_Timer_2(); // power-up Timer-Counter-2
printString("\t\tAVR Device initializing.........................");
printString("\r\n");
clock_prescale_set(clock_div_2); // processor clock speed
//---------------------------Event loop-------------------------//
while(1)
{
sei();
OCR0A = 253;
OCR1B = 500;
OCR1A = 55000;
OCR2A = 252;
data_console_out();
}
return(0);
}```

All these things simply make the code easier to read both for you and the maintainer.