2012-02-05 20 views
4

符号なし整数に浮動小数点値を掛けて賢明な値を得ることができない理由を理解しようとしています。AVR atmega8でのCでの予期せぬフロート動作

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; 
} 

のMakefile: EDIT

# 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 

OKは、私が簡略化されたコードやや

#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; 
} 
+0

私はatmega328pへのアクセス権がないので、例えば、ADCからのバイトオーダーについて確信していますか?私はsimavrをつかむことができると思います:P –

+0

私はavrのツールチェーンを構築する必要がありますか? hhaha –

+1

Ahmed、私は[スクリプト](https:// github。com/regomodo/ccgcc-Scripts/blob/master/avr-build.sh) – regomodo

答えて

1

未対応浮動小数点定数はdoubleではなく、floatです。

使用fサフィックスはatmega8は、浮動小数点ユニットを持っていないようfloatリテラル、例えば、0.1f

これはMCUのように巨大なオーバーヘッドを作ることができる持っていると、すべての浮動小数点演算は、ファームウェアに実装する必要があります実装。

atmega8のような小さなデバイスでは、通常、FPUなしでfloatの操作を使用しないでください。CPUサイクルが非常に高価です。 calcsenseuint16_tタイプであるとき

calc = sense * 1.0;

は今実装がまったく正しくような式を翻訳しないだろう理由はありません。

+0

それでは分数を削除して整数計算をするために数値をスケールアップするのですか?私は人々がatmega8sで浮動小数点演算を行うのを見たことを誓う。だから、前者が実際にコンパイルされていたので、私は単純な65535 * 0.1でなく、(uint16_t)val * 1.0を行う理由がありますか? – regomodo

+0

calc = sense * 1.0はまさに私がここでかなり困惑している理由です。私は異なるマシン(Ubuntuと私自身のスクリプト)を使って異なるマシン上に作成された2つの異なるコンパイラで試しました。 – regomodo

+2

'65535 * 0.1'はコンパイル時に実行されますが、実行時にatmegaで実行される'(uint16_t)val * 1.0'では実行されません。それ以外の場合、浮動小数点演算は 'avr-gcc 'によってサポートされますが、固定小数点演算を使用するほうが優れています。 – ouah

0
  1. 乗算65535 * 0.1作品、それが最適化され、事前に計算コンパイラによって、それは変数が uint16_t型のである6554.
  2. カルク感覚に変換して、そしてあなたの関数analogToTempされているため、 ()は同じタイプで、そのタイプも返します。この関数内のランタイム計算はuint16_tです。これはfloatで行われ、整数部分に切り捨てられ、関数の結果としてuint16にキャストされます。
+0

これは私が最初にやっていたことです。私は2つのvars、v&tempをタイプfloatとして持っていました。それらはuint16_tとしてキャストされた計算に使用されました。 analogToTemp()内のコメントアウトされたセクションから、何かを取得しようとしていたことがわかります。 – regomodo

+0

float <=> int変換とキャストに間違っています。プロジェクト全体を削除し、新しいテストを作成し、AVR Studioで段階的にデバッグする簡単な基本変換とキャストコマンドを使用すれば、そのコンセプトを誤解する可能性があります。また、Stackoverflowについても間違った概念があります。これはディスカッションフォーラムではなく、人々は報酬を手伝います。これまでのところ私はあなたが誰にも+1を与えたとは見ません。あなたはする必要はありませんが、私は何人かの人々が何のためにも多くの努力をしたことを知っています。 FAQを読んでください。 – avra

+0

私はSOがポイントベースのシステムを使用していることに感謝しますが、Redditのカルマのように少しばかげているようです。 OTを取得すると、私はすでにfloat-opsを切り捨て、整数計算を実行するために自分の値をスケーリングしました。私は私のアームプロジェクトで私の浮動小数点演算を保つつもりだと思う。 – regomodo

1

あなたのコードは正確ではありませんが、十分に近いかもしれません。

まず第一に、私は出力を表示し、ラインにこれ​​らを比較:

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.5対1.5Fについてのコメントは、ここでは関係はかなり一般的には有効ではなくではありません。次に浮動小数点値、単精度を生成して浮動小数点数を計算していますが、コンパイラはそこにショートカットを作成せず、浮動小数点数に変換してから乗算を行います。

私の16進数表示ルーチンと小数点表示ルーチン(シリアルターミナルでの出力に変更)を使用すると、ここでも同じ出力が生成されますが、浮動小数点演算は問題ではありません。

私は浮動小数点のパフォーマンスがキラーであるかどうかを確認するためにこのタスクを開始しましたが、タイミングはテスト方法によって変わります。固定小数点と比較して浮動小数点のコードの方が157倍も長くかかりました。私がserialSegments()の呼び出しを終了し、シリアルポートではなくダミールーチンを呼び出すと、floatの速度は3倍遅くなります。また、2つの異なる方法を構築し、異なる組の浮動小数点ルーチンを使用するlibc/mを取得しました.Cコンパイラによって選択された浮動小数点ルーチンは、/ usr/lib64にあるlibc/libm.aよりも7倍遅かった/ avr/lib /ディレクトリ。シリアルポートやその他の遅延を待つ時間を追加すると、タイミングの違いに気づかないかもしれません。フロートがかなり痛いということを実証しながら、コードがタイミングに敏感であっても、おそらく喫煙銃ではありません。ミリ秒。 I以下のコードに加えて

もこの試みのための:

(i = 0; I < 9999; iは++) { ヴァラ=(unsigned int型)(iは1.5 *)。 valb = i +(i >> 1);私は+ +; if(vala!= valb) { hexstring16(i); hexstring16(vala); hexstring16(valb); } }

いいえ、失敗しません。 serialSegments()は0から9999まで小数点以下を切り捨てるだけなので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; 
} 

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 

これはすべてarduino pro mini(atmega328p)で実行されました。

+0

見ていただきありがとうございます。興味のないところでは、x(a/b/c)のすべての割り当てとシフトは何ですか? – regomodo

+0

は16ビットタイマのスナップショットを撮っていましたが、最後に2つの8ビット部分を組み合わせて16ビットカウントにしました。 –

+0

私は、浮動小数点のソリューションと浮動小数点を使用するのに長い時間が来た特定の乗数の固定小数点のソリューションの違いを時間を計るためにタイマを使用していました。 –

関連する問題