Unexpected behavior of float in C with AVR atmega8

I was trying to figure out why I cannot get a reasonable value from multiplying an unsigned int with a float value.

Doing something like 65535 * 0.1 works as expected, but multiplying a float with a uint from memory creates insane values. I have a function that reads the ADC and returns uin16_t. With this value, I print it onto a 4-digit LED display that works great.
Multiplying the same value by 1.0 returns something completely different (it is too large for my display, so I really don't know what it is).

My code is below, but the discussion area is at the bottom in main (). Any help would be great. Thanks

main.c:

#include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> #include <stdint.h> #define BAUD 9600 #include <util/setbaud.h> #define DISP_BRIGHT_CMD 'z' #define DISP_RESET 'v' #define ADC_AVG 3 volatile uint8_t hi,lo; volatile uint16_t result; ISR(ADC_vect) { lo = ADCL; hi = ADCH; MCUCR &= ~_BV(SE); //Clear enable sleep } void initSerial(void) { // set baud rate UBRR0H = UBRRH_VALUE; UBRR0L = UBRRL_VALUE; // set frame format UCSR0C |= (0x3 << UCSZ00); // 8n1 // set enable tx/rx UCSR0B = _BV(RXEN0) | _BV(TXEN0); } void initADC(void) { // AVCC and ADC0 ADMUX = _BV(REFS0); // Enable, div128, + 1st setup ADCSRA |= _BV(ADEN)|_BV(ADSC)|_BV(ADPS2)|_BV(ADPS1)|_BV(ADPS0)|_BV(ADIE); } uint16_t readADC(void) { uint16_t average=0; // Start Conversion ADCSRA |= _BV(ADSC); for (char i=0;i<ADC_AVG;i++) { MCUCR |= _BV(SE); ADCSRA |= _BV(ADSC); __asm volatile("sleep"); MCUCR &= ~_BV(SE); result = (hi<<8); result |= lo; average += result; } average /= ADC_AVG; return average; } void sendByte(char val) { while (! (UCSR0A & (1<<UDRE0)) ); //wait until tx is complete UDR0 = val; } /* * Convert voltage to temperature based on a negative coefficient for MAX6613 */ uint16_t analogToTemp(uint16_t val) { uint16_t temp; //v = 5 * (val/1023.0); //temp = (1.8455 - (5.0*(val/1023.0)))/0.01123; temp = (1.8455 - (5.0*(val/1023.0)))*89; //temp = val * M_PI; //v = 5 * ( val/1024); //temp = (2 - v) * 89; return temp; } void initDisplay() { sendByte(DISP_RESET); sendByte(DISP_BRIGHT_CMD); sendByte(0); } void serialSegments(uint16_t val) { // 4 digit display sendByte(val / 1000); sendByte((val / 100) % 10); sendByte((val / 10) % 10); sendByte(val % 10); } int main(void) { uint16_t calc=0,sense=0; DDRB |= _BV(DDB5); PORTB |= _BV(PORTB5); initSerial(); initADC(); initDisplay(); sei(); MCUCR |= (1 << SM0); // Setting sleep mode to "ADC Noise Reduction" MCUCR |= (1 << SE); // Sleep enable for(;;) { //PORTB ^= _BV(PORTB5); if (calc>=9999){ // I can't see the real value. Max val on display is 9999 //if (sense>=330){ PORTB |= _BV(PORTB5); } else { PORTB &= ~_BV(PORTB5); } sense = readADC(); //calc = sense*1.0; // refuses to calculate properly calc = analogToTemp(sense); // a bunch of zeroes //calc = 65535*0.1; // a-ok serialSegments(calc); _delay_ms(500); serialSegments(sense); _delay_ms(500); } return 0; } 

Makefile:

 # AVR-GCC Makefile PROJECT=Temp_Display SOURCES=main.c CC=avr-gcc OBJCOPY=avr-objcopy MMCU=atmega328p OSC_HZ=16000000UL OPTIMISATION=2 PORT=/dev/ttyUSB0 CFLAGS=-mmcu=${MMCU} -std=gnu99 -Wall -O${OPTIMISATION} -DF_CPU=${OSC_HZ} -lm -lc ${PROJECT}.hex: ${PROJECT}.out ${OBJCOPY} -j .text -O ihex ${PROJECT}.out ${PROJECT}.hex avr-size ${PROJECT}.out $(PROJECT).out: $(SOURCES) ${CC} ${CFLAGS} -I./ -o ${PROJECT}.out ${SOURCES} program: ${PROJECT}.hex stty -F ${PORT} hupcl avrdude -V -F -c arduino -p m168 -b 57600 -P ${PORT} -U flash:w:${PROJECT}.hex clean: rm -f ${PROJECT}.out rm -f ${PROJECT}.hex 

EDIT: Ok, I simplified the code a bit

 #include <avr/io.h> #include <util/delay.h> #include <stdint.h> #define BAUD 9600 #include <util/setbaud.h> #define DISP_BRIGHT_CMD 'z' #define DISP_RESET 'v' void initSerial(void) { // set baud rate UBRR0H = UBRRH_VALUE; UBRR0L = UBRRL_VALUE; // set frame format UCSR0C |= (0x3 << UCSZ00); // 8n1 // set enable tx/rx UCSR0B = _BV(TXEN0); } void sendByte(char val) { while (! (UCSR0A & (1<<UDRE0)) ); //wait until tx is complete UDR0 = val; } void initDisplay() { sendByte(DISP_RESET); sendByte(DISP_BRIGHT_CMD); sendByte(0); } void serialSegments(uint16_t val) { // 4 digit display sendByte(val / 1000); sendByte((val / 100) % 10); sendByte((val / 10) % 10); sendByte(val % 10); } int main(void) { uint16_t i=0,val; DDRB |= _BV(DDB5); initSerial(); initDisplay(); for(;;) { val = (uint16_t)(i++ * 1.5); serialSegments(i); _delay_ms(500); serialSegments(val); _delay_ms(500); if (val > 9999){ PORTB |= _BV(PORTB5); } else { PORTB &= ~_BV(PORTB5); } } return 0; } 
+4
source share
3 answers

An unstable floating point constant is of type double not float .

Use the suffix f to have a float literal, e.g. 0.1f

This can lead to huge overhead since MCUs such as atmega8 do not have a floating point unit, and all floating point operations must be implemented in the firmware through implementation.

With small devices like atmega8, usually try to avoid using float operations, since without FPUs they are very expensive in the processor cycle.

Now there is no reason for the implementation to not correctly translate an expression like:

calc = sense * 1.0;

when calc and sense are of type uint16_t .

+1
source

Not really your code, maybe close enough, or maybe not.

First of all, when I show the output and compare it with the lines:

 val = (unsigned int)(i++ * 1.5); ... val = i+(i>>1); i++; 

The result is the same. Disassembly also reveals some things. Avr-gcc first

 avr-gcc --version avr-gcc (GCC) 4.3.4 Copyright (C) 2008 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

uses float does not double, so comments about 1.5F vs 1.5 are quite acceptable in general, but not relevant here. Secondly, it produces floating point values, single precision and performs math with floating point, the compiler did not use the shortcut there, it is converted to float, whether it is then multiplied.

Using my hexadecimal mapping procedure and your decimal display routine (modified for output to a serial terminal), here it again gives the same result, floating point math does not seem to be a problem.

I started this task to see if floating point performance is a killer, but it depends on how I test it. floating-point code required 157 times more than fixed-point code. If I stay in the serialSegments () call, but for this call, instead of a dummy routine, instead of the serial port, it will be 3 times slower for float. I also built two different methods and pulled into libc / m, which used a different set of floating point routines, the floating point programs selected by the C compiler were 7 times slower than libc / libm.a, sitting in / usr / lib 64 / avr / lib /. When you add the wait to the serial port and other delays, you may not notice the time difference, therefore this experiment, demonstrating that the float is rather painful, probably is not a smoking gun, even if your code is time sensitive, we are talking about a few milliseconds .

In addition to the code below, I also tried this:

for (i = 0; i <9999; i ++) {vala = (unsigned int) (i * 1.5); valb = i + (i → 1); I ++; if (Vala! = valb) {hexstring16 (i); hexstring16 (Shaft); hexstring16 (valb); }}

Not. I limited myself to 9999 because serialSegments () only beats decimal numbers from 0 to 9999. Now your loop goes beyond this value to 65535, but you will see that the problem is with the problem without a float, right?

avr.c

 #define UCSRA (*((volatile unsigned char *)(0xC0))) #define UDR (*((volatile unsigned char *)(0xC6))) #define TCCR0A (*((volatile unsigned char *)(0x44))) #define TCCR0B (*((volatile unsigned char *)(0x45))) #define TCNT0 (*((volatile unsigned char *)(0x46))) #define TCCR1A (*((volatile unsigned char *)(0x80))) #define TCCR1B (*((volatile unsigned char *)(0x81))) #define TCNT1L (*((volatile unsigned char *)(0x84))) #define TCNT1H (*((volatile unsigned char *)(0x85))) void dummy ( unsigned int ); void uart_putc ( unsigned char c ) { while(1) if(UCSRA&0x20) break; UDR=c; } void hexstring16 ( unsigned int d ) { unsigned int rb; unsigned int rc; rb=16; while(1) { rb-=4; rc=(d>>rb)&0xF; if(rc>9) rc+=0x37; else rc+=0x30; uart_putc(rc); if(rb==0) break; } uart_putc(0x0D); uart_putc(0x0A); } #ifdef SEGMENTS void sendByte(char val) { uart_putc(0x30+val); } void serialSegments(unsigned int val) { // 4 digit display dummy(val / 1000); dummy((val / 100) % 10); dummy((val / 10) % 10); dummy(val % 10); } //void serialSegments(unsigned int val) { //// 4 digit display //sendByte(val / 1000); //sendByte((val / 100) % 10); //sendByte((val / 10) % 10); //sendByte(val % 10); //uart_putc(0x0D); //uart_putc(0x0A); //} #else void serialSegments(unsigned int val) { dummy(val); } //void serialSegments(unsigned int val) //{ //hexstring(val); //} #endif int main(void) { unsigned int i,val; volatile unsigned int xal,xbl,xcl; volatile unsigned int xah,xbh,xch; hexstring16(0x1234); TCCR1A = 0x00; TCCR1B = 0x05; xal=TCNT1L; xah=TCNT1H; for(i=0;i<9999;) { val = (unsigned int)(i++ * 1.5); //serialSegments(val); //hexstring16(val); dummy(val); } xbl=TCNT1L; xbh=TCNT1H; for(i=0;i<9999;) { val = i+(i>>1); i++; //serialSegments(val); //hexstring16(val); dummy(val); } xcl=TCNT1L; xch=TCNT1H; xal|=xah<<8; xbl|=xbh<<8; xcl|=xch<<8; hexstring16(xal); hexstring16(xbl); hexstring16(xcl); hexstring16(xbl-xal); hexstring16(xcl-xbl); return 0; } 

dummy.s

 .globl dummy dummy: ret 

vectors.s

 .globl _start _start: rjmp reset reset: rcall main 1: rjmp 1b .globl dummy dummy: ret 

Makefile

 all : avrone.hex avrtwo.hex avrone.hex : avr.c dummy.s avr-as dummy.s -o dummy.o avr-gcc avr.c dummy.o -o avrone.elf -mmcu=atmega328p -std=gnu99 -Wall -O2 -DSEGMENTS avr-objdump -D avrone.elf > avrone.list avr-objcopy avrone.elf -O ihex avrone.hex a vrtwo.hex : avr.c vectors.s avr-as vectors.s -o vectors.o avr-as dummy.s -o dummy.o avr-gcc -c avr.c -o avrtwo.o -mmcu=atmega328p -std=gnu99 -Wall -O2 -nostartfiles avr-ld vectors.o avrtwo.o -o avrtwo.elf libc.a libm.a avr-objdump -D avrtwo.elf > avrtwo.list avr-objcopy avrtwo.elf -O ihex avrtwo.hex clean : rm -f *.hex rm -f *.elf 

All of this was running on arduino pro mini (atmega328p).

+1
source
  • Multiplication 65535 * 0.1 works because it is optimized and pre-calculated by the compiler, so it translates to 6554.
  • calc and meaning are of type uint16_t , and your analogToTemp () function has the same type and returns that type. The calculation of the runtime inside this function is performed using uint16_t . This should be done using float, and then truncated to the integer part and executed as a result of the uint16 _t function.
0
source

Source: https://habr.com/ru/post/1394909/


All Articles