[英]Unexpected float behaviour in C with AVR atmega8
我一直在試圖弄清楚為什么我不能通過將一個無符號int與一個float值相乘而得到一個有意義的值。
像65535 * 0.1這樣的操作可以按預期工作,但是將浮點數與內存中的uint相乘會產生瘋狂的值。 我有一個讀取ADC並返回uin16_t的函數。 使用這個值,我將其打印到一個4位數的led顯示器上,它工作正常。
將相同的值乘以1.0會得到完全不同的結果(對於我的顯示來說太大了,所以我真的不知道它是什么)。
我的代碼在下面,但爭用區域在main()的底部。 任何幫助都會很棒。 謝謝
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;
}
生成文件:
# 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
編輯:好的,我已經簡化了代碼
#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;
}
無后綴的浮點常量的類型為double
not float
。
使用f
后綴具有float
文字,例如0.1f
這可能會產生巨大的開銷,因為atmega8之類的MCU沒有浮點單元,並且所有浮點操作都必須通過實現方式在固件中實現。
對於像atmega8這樣的小型設備,通常會嘗試避免使用float
運算,因為沒有FPU會導致CPU周期非常昂貴。
現在,沒有理由沒有實現可以正確地翻譯如下表達式:
calc = sense * 1.0;
當calc
和sense
為uint16_t
類型時。
不完全是您的代碼,也許足夠接近,也許不是。
首先,當我顯示輸出並將它們與行進行比較時:
val = (unsigned int)(i++ * 1.5);
...
val = i+(i>>1); i++;
結果是一樣的。 拆卸也顯示了一些東西。 首先是AVR-GCC
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.
使用浮點數而不是雙精度數,因此,關於1.5F與1.5的注釋通常是完全有效的,但此處不相關。 其次,它產生浮點值,單精度並進行浮點數學運算,編譯器在此處沒有捷徑,而是轉換為浮點,然后進行乘法然后轉換回去。
使用我的十六進制顯示例程和十進制顯示例程(修改為在串行終端上輸出),在這里它再次產生相同的輸出,浮點數學似乎不是問題。
我開始執行此任務,以查看浮點性能是否是殺手,,但實際上,時間取決於我如何對其進行測試。 與固定點相比,帶有浮點的代碼要花費多達157倍的時間。 如果我不留任何對serialSegments()的調用,但是讓它調用一個虛擬例程而不是串行端口,則它的速度要慢3倍。 我還建立了兩種不同的方式,並引入了使用一組不同的浮點例程的libc / m,C編譯器選擇的浮點例程比/ usr / lib64中的libc / libm.a慢7倍。 / avr / lib /目錄。 一旦添加了串行端口等待時間和其他延遲,您可能不會注意到時間上的差異,因此,此實驗表明浮動非常痛苦,即使您的代碼對時間敏感,也可能不是吸煙者,我們在說一些毫秒。
除了下面的代碼,我也嘗試過此操作:
for(i = 0; i <9999; i ++){vala =(unsigned int)(i * 1.5); valb = i +(i >> 1); i ++; if(vala!= valb){hexstring16(i); hexstring16(vala); hexstring16(valb); }}
沒錯 我限制為9999,因為serialSegments()僅將小數從0切成9999。現在,您的循環超出了十進制,達到65535,但是您會發現在沒有浮點數的情況下會引起問題,對嗎?
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;
}
假人
.globl dummy
dummy:
ret
向量
.globl _start
_start:
rjmp reset
reset:
rcall main
1:
rjmp 1b
.globl dummy
dummy:
ret
生成文件
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
所有這些都在arduino pro mini(atmega328p)上運行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.