4

unsigned int と float 値を乗算しても適切な値を取得できない理由を理解しようとしています。

65535*0.1 のようなことをすると期待どおりに動作しますが、float にメモリからの 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

編集: 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;
}
4

3 に答える 3

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.

double ではなく float を使用するため、1.5F と 1.5 に関するコメントは一般的には有効ですが、ここでは関係ありません。次に、浮動小数点値、単精度を生成し、浮動小数点演算を行っています。コンパイラはそこで近道をとらず、浮動小数点数に変換し、乗算を行ってから元に戻します。

私の 16 進数表示ルーチンと 10 進数表示ルーチン (シリアル端末で出力するように変更) を使用すると、ここでも同じ出力が生成されます。浮動小数点演算は問題ではないようです。

浮動小数点のパフォーマンスがキラーかどうかを確認するためにこのタスクを開始しましたが、テスト方法によってタイミングが変わります。固定小数点と比較して、浮動小数点を使用したコードでは 157 倍も時間がかかりました。serialSegments() の呼び出しをそのままにして、シリアル ポートの代わりにダミー ルーチンを呼び出すと、フロートの速度が 3 倍遅くなります。また、2 つの異なる方法を構築し、異なる浮動小数点ルーチンのセットを使用する 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(値); hexstring16(バルブ); } }

失敗しません。serialSegments() は 0 から 9999 までの 10 進数しか切り捨てないため、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

ベクトル.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)で実行されました。

于 2012-02-07T05:08:21.003 に答える
1

サフィックスのない浮動小数点定数はdoublenot型floatです。

接尾辞を使用fしてfloatリテラルを指定します。たとえば、0.1f

atmega8 のような MCU には浮動小数点ユニットがなく、実装によってすべての浮動小数点演算をファームウェアに実装する必要があるため、これは大きなオーバーヘッドになる可能性があります。

atmega8 のような小さなデバイスでは、通常float、FPU がないと CPU サイクルが非常に高価になるため、操作の使用を避けようとします。

これで、実装が次のような式を正しく変換しない理由はなくなりました。

calc = sense * 1.0;

whencalcsenseuint16_tタイプです。

于 2012-02-05T21:41:13.843 に答える
0
  1. 乗算 65535*0.1 は、コンパイラによって最適化および事前計算されているため機能し、6554 に変換されます。
  2. calcおよびsense変数はタイプuint16_tであり、関数analogToTemp()は同じタイプであり、そのタイプも返します。この関数内の実行時の計算はuint16_tです。float を使用して実行し、整数部分だけに切り捨てて、関数のuint16 _t 結果にキャストする必要があります。
于 2012-02-06T10:39:37.853 に答える