ATmega8 5V voltmeter, ADC prevod na znaky, desatinné miesta

Zdravím. Moja otázka je skôr pre programátorov ako pre elektrikárov. Chcem vytvoriť jednoduchý voltmeter pomocou ATmegi8 s výstupom na znakový LCD. Programujem v C-ečku. Rozsah ma byť od 0 do 5V, takže hodnotu z ADC registra mi treba premeniť na znaky, ktoré potom pošlem na LCD. Aby som dosiahol výsledne napätie, používam vzorec s datasheetu:
V = ADC * Vref / 1024
Vref = 5V. Hodnoty z registrov ADCL a ADCH som vložil do premennej typu int. Vynásobím to 5-kou a následne vydelím 1024. Vide mi cele číslo tzv. hodnota napätia vo voltoch, ktoru potom pošlem na LCD.
Ako dostanem zvyšné desatinne hodnoty napätia? Premenne typu float nechcem použivať, lebo výsledný kód je príliš veľký. Podobný problém som videl vyriešený pomocou funkcie modulo, ale mne to vždy vypisuje chybné hodnoty.
Ďakujem za pomoc.

:arrow_right: administrator: přesunuto z "Elektronika s mikrokontroléry, procesory"

:arrow_right: administrator: přejmenováno z "ATmega8 voltmeter, ADC prevod na znaky"

Pokud potřebuješ pouze jedno desetinné místo… tak je postup nejjednodušší, místo Vref = 5 použiješ Vref = 50 (v tomto případě můžeš nechat ADCL a ADCH v INTu: (1023*50)<(2^16-1)) výsledek “V” bude pak v desítkové soustavě ve tvaru XX, ten naformátuješ na LCD do tvaru X.X přidáním desetinné tečky.

Pokud potřebuješ více desetinných míst, můžeš postupovat podobně …
ale pokud nechceš používat proměnnou větší než int, “vybereš” si potřebná desetinná místa z dolních 10bitů výsledku “V” před dělením 1024.

Pokud chceš použít atmega8 jen jako voltmetr, tak se vůbec nemusíš obávat používat float a to i kdybys použil všechny ADC vstupy a na 6(v DIP) resp. 8 voltmetrů.
Dále nerozumím proč by nemělo fungovat použití modula - běžně to tak dělám a žádné problémy s tím nemám.

Schválně jsem si napsal prográmek pro zobrazení napětí na ADC vstupu na LCD ve voltech jak spoužitím float, tak bez něj.
Rozdíl ve velikosti kódu byl 56 Bytů - konkrétně 1810 a 1866 B.

Pro int je vzorec: V = ADCW * 5000/ 1024 hodnotu pak dostaneš v mV

int U;
while (1)
{
U=(ADCW*5000)/1024;
sprintf(lcd_buffer,"%i,%03u",U/1000,U%1000);
lcd_puts(lcd_buffer);
}

pro float je ten původní: V = ADC * 5/ 1024

float U;
while (1)
{
U=(ADCW*5)/1024;
sprintf(lcd_buffer,"%.3f",U );
lcd_puts(lcd_buffer);
}

Jinak inspiraci můžeš čerpat i tady https://forum.mcontrollers.com/t/ako-zobrazit-desatinne-cislo-na-displeji-s-hd44780-v-c/962/1

No jo :slight_smile: … ale ty při použití INTu máš jako argumenty ve sprintf další výpočty … které používat nemusíš, když se to udělá “chytře” … pak samozřejmě paměťová náročnost u INTu zbytečně roste v místech kde u FLOATu ne… a rozdíl je pak jen těch 56B :slight_smile:

Další věc je časová náročnost algoritmu s použitím float . . . . tam bude rozdíl dramatičtější, a proto ho je nutné uvažovat. Jinak dělit číslem 2^x způsobem, jakým jsi použil, je opět nehorázné plýtvání časem (výkonem mpc, energií, atd…), pokud samozřejmě není kompiler dostatečně chytrý a nepřevede si to na rotaci … o čemž ale trochu pochybuji

Zajímalo by mě jak je časově i paměťově náročný sprintf? Většinou jsem se setkal s tím, že pokud je to možné (a potřebné kvůli úspoře), tak je nutné to udělat jinak… tipnul bych si, že většina z těch 1800B je právě díky sprintf?

Co se týče toho dělení, compiler bych tak nepodceňoval, jinak tady jsou časy a velikosti v B jednotlivých příkazů - je tp pro atmega8 a pro interních 8MHz:

U1=(ADCW5000)/1024; // 67 cyklu 8.38 us pro float 42 cyklu 5.25 us pro int
U1= (ADCW
5000)>>10; // 67 cyklu 8.38 us pro float 42 cyklu 5.25 us pro int

  sprintf(lcd_buffer,"%i,%03u",U/1000,U%1000);//3341 cyklu   417.63 us      1122 B
  sprintf(lcd_buffer,"%.3f",U1 );  //979 cyklu      122.38 us      1000 B
  itoa(U,lcd_buffer);                                                       //118 cyklu      14.75 us         102 B

z čehož vyplývá několik věcí:

  1. Autoři compilerů nejsou hloupí
    2.časový rozdíl operace s int, nebo float je 3 us!!!(při pomalých 8MHz) tak nevím proč to někteří pořád řeší
  2. je pravda,že použití itoa místo sprintf ušetří cca 90% času i paměti - ale v tomto konkrétním příkladu znamená za prvé psát další kód pro vložení desetinné čárky do řetězce a za druhé problém v případě kdy vypočítaná hodnota je menší než 1000 - to se pak zobrazí místo 0,850 ,850 resp. místo 0,052 , 52
    navíc každé další volání sprintf znamená nárůst paměti už jen o parametry - čas samozřejmě zůstává stejný.

Přiznám se výsledky mě překvapily, ale určitě neodradily od používání knihony stdio.h neb jsem zatím ještě neměl žádný problém jak s časem tak s nedostatkem paměti (v mcu :slight_smile: )

edit:
přeloženo Codevisionem, časy měřeny v debuggeru AVR studia

Dobře nebudu ho tolik podceňovat, já vždycky místo dělení rotuju, takže mi to bylo tak nějak volný :slight_smile:

Koukat na to jako na ztrátu 3us není nejvhodnější, spíše se na to podívejme takto: při použití INTu ušetřím více než 50% času … tzn. že můžu například generovat nebo zpracovávat signál 2x rychleji, pokud již nemohu/nechci zvedat frekvenci, je to hodně významné …další věc je enrgetická úspora … pokud to poběží na baterie, ty vydrží 2x déle, což je opět výrazný rozdíl. . . dobré by bylo i srovnání LONG × FLOAT …

Tady mi nějak nesedí ty cykly/čas . . . že by to s FLOATem běhalo 3x rychleji než s INTem? To je hodně divné . . . stálo by za to zjistit důvod, proč to tak je, jinak si každý “normálně” :slight_smile: uvažující člověk musí zákonitě myslet, že je někde chyba :slight_smile:

Další kód pro vložení desetinné čárky (i nuly, pokud je “výsledek” menší než nula) je v porovnání s paměťovou náročností sprintf téměř zanedbatelný …

Musím se omluvit - skoro všechno je jinak :frowning:
za prvé výraz: float U;
U=(ADCW*5)/1024;

má být: U=(float)(ADCW*5)/1024;

jinak bylo vlastně počítáno s int

skutečné časy tedy jsou: 948 cyklů/118,5 us pro float
42 cyklů /5,25 us pro int
to už je fakt rozdíl

a pak další chyba - v překladači jsem pro sprintf neměl povolený float, takže to “%.3f” ignoroval

skutečnost je tedy: sprintf(lcd_buffer,"%.3f",U );//8137 cyklu 1,017 ms 3578 B

to už je lepší použít ftoa(U,3,lcd_buffer); // 2482 cyklů 310 us 1522 B

ale držme se původního zadání

hlavní smyčka by vypadala:

while(1)
{
sprintf(lcd_buffer,"%.3f",(float)(read_adc(1)*5)/1024 ); //1.24 ms read_adc() je funkce pro převod
lcd_ puts(lcd_buffer); //řádově **desítky ms ** nemluvě o čase pro AD převod, nejlépe v několika vzorcich
}

čímž chci jenom poukázat, že úspora necelé ms tu nehraje až takovou roli

toť celý voltmetr v CV - funkce pro AD převod a obsluhu LCD jsou z knihoven CV

a co se týká spotřeby - vždy je možno procesor uspat a probudit třeba kždou sekundu - dig. multimetry jsou snad ještě pomalejší

budu rád, když sem někdo hodí variantu s int, itoa a vkládáním des. čárky a nul do řetězce

To se stává i zkušenějším programátorům :slight_smile:
Teď je jasně vidět, že používání floatu nebo sprintfu (a podobných vylomenin - viz třeba knihovny pro nové STM8 - tam je podobných věcí spousta) na “běžných” 8-bitech se rovná sebevraždě, pokud nechceš aby to jenom nefloatovalo, nesprinfovalo, apod. :slight_smile: