Začátečník C - ADC převodník ATmega8 - hodnota příliš lítá

piityy - jestli si myslel proměnné volty a ampery, tak ty nejsou desetibitové. V nich může být maximálně číslo 16x 10bitů, což nevím zrovna kolik bitů to je, ale víc než deset určitě. Maximální desetibitová hodnota je 3FF, krát 16 je 3FF0 což má šíři 0b11111111110000 - nechce se mi to počítat. Ale víc než 10 to je :slight_smile: (něco mi řiká, že to bude 14 bitů, pač 10bit krát 16 je to samé jako shiftnout 10bitů o 4 vlevo, takže 14 :wink: )
tak, přecejenom jsem si dal tu práci, a spočítal jsem to. :slight_smile:

ADSC jsem už na ADCSRA přepsal. Situace se náramně zlepšila, ale furt se mi to nezdá. Asi se mzas hodím zdroják, protože tam bude někde schovaný bagr ještě jeden. Hodnoty sice začly být mnohem stabilnější (protože už to správně čeká na ADSC), ale furt mi to nepřipadá, jak ±1LSB. Na displeji se mi to mele občas o ± 0.01V z čehož vyplývá, že promenna v_cnt lítá o 1*(1024/512) = 2 (!!) cože? Jak to tak počítám, tak nyní už je to chyba jen ±2LSB… Dá už se tohle brát jako nezlepšitelné a hotové?

Stale merias na AD vstupe pomocou internej referencie zasumene napatie z napajania?

“na AD vstupe pomocou internej referencie zasumene napatie z napajania” - krkolomné. Snad to chápu dobře:
Interní referenci 2.56V mám furt. Přece nebudu dělat něco zvenčí. Protože to co bych dal z venku, by mělo 10x větší teplotní závislost, než to co je v tom AVR.
Zašuměné napětí - co s tim furt máš? Mám tam potenciometr, a za nim RC filtr 1k/100n. Divil bych se, kdyby to nepochytalo dostatečně bordel z napájení. A navíc ten potenciometr jsem přepojil na stejný napájení jako ADC. ZA tu indukčnost 56uH. A filtr 56uH/100n + 1k/100n to musí filtrovat jako blázen. Zkusim dát větší kond do toho RC článku.

To máš pravdu, ale když je předhazuješ pro zobrazení na disp., tak předpokládám, že už v nich je průměr oněch 16ti hodnot (nikoli suma), tedy číslo ve stejném rozsahu, jako je rozsah A/D z jehož měřených hodnot ten průměr děláš. Ikdyby tam byla suma, tak můžeš udělat o 2 posuny vpravo více :slight_smile:

2LSB už je dobrý výsledek.

Co se týká šumu, tek ten se ti tam nachytá ani nevíš jak. Stačí dráty od potenciometru (slouží jako anténa), vedle rozsvícená lapička se spořivkou a už to tam lítá. Navíc to blokování, co tam máš není bůh ví co. Můžu ti odsimulovat frekvenční charakteristiky a uvidíš, že 100Hz z usměrněnýho zdroje to rozhodně nevyčistí :wink:

Mam s tym to, ze to tak mas zapojene na scheme, ktoru si sem vycapil!
Ak je to zapojene inak, schemu si oprav/vymen.
Ako referencia pre AD sa pouziva Ucc pre pripady merania rozvazenia mostika. Aj ked s 10b AD to moc velky vyznam nema - neda sa pouzit na tenzometre (najcastejsi pripad potreby merania v mostiku). Ak je napajacie napatie MCU totozne s napajanim mostika, absolutne Ti nevadi ziadna teplotna zavislost napajacieho napatia. Preto zmeny na Ucc sa neprejavia (v rozumnych medziach cca 1-2% - 4 dolne digity 10b AD zodpovedaju cca 2%) na nameranej hodnote. Merat mostik pomocou internej (i keby bola neviem ako super teplotne nezavisla) referencie nie je skratka dobre riesenie. No a podla tej schemy, co tu je umiestnena sa jedna o presne ten isty problem merania. Ucc bude poskakovat (i cca 0.1ms piky) v zavislosti od spotreby MCU a od tvrdosti zdroja. A to mozes kludne merat - bude sa to prejavovat tou neustalenostou nameranej hodnoty. A v zasade tento problem nemusi riesit ani to, ze si ten potenciometer das na Ucc AD. Ak bude Ucc AD “dostatocne” poskakovat (napr o 40mV), bude Ti namerana hodnota poskakovat kludne o 8 digitov. Vsetko zavisi od filtracie a este viac od stability zdroja.
Tak to s tym furt mam. :slight_smile:

Ak pouzivam AD prevodnik v ATmega, tak mi namerana hodnota nelieta. Pevne sedi, az pokial sa Uvst nezmeni o vyse 4/5 LSB. Prejavuje sa to napr. tak, ze ak suctujem 16 prevodov, pri postupnej zmene Uvst sa namerana hodnota v “jednom okamihu” zmeni o 16. Na rozhodovacej urovni zodpovedajucej sirke cca 1/5LSB hodnota mierne poskakuje. Ako Uvst som pouzil procesny kalibrator, ktory je pravidelne ciachovany v statnej skusobni. Ak Ti to skace viac, niekde mas problem. Mozno sw, mozno zeme, mozno kto vie co. Al aj keby Ti to skakalo o digit, tak mas v podstate dobry vysledok. Vyskusane na niekolkych desiatkach kusov.
Deliaci pomer a ostatne okolnosti som uvadzal vyssie.

RC kombinacia 1k/100n filtruje “ako blazen” od 1591Hz (-3dB) vyssie. To znamena, ze sum s frekvenciou 3.183kHz tlmi iba 2x

piityy: 100Hz? Za stabilizátorem? Co jsem se měl jednou možnost podívat na průběh za 7805 na osciloskopu, tak ta hodnota má naprosto miniaturní zvlnění, a 100Hz je tam neznatelné. Určitě dost neznatelné na to, aby to ten ADC registroval. ale možná to filtrováí podceňuju… :slight_smile:

Martin: vim jak se pocia RC filtr. Ja ten filtr delal kvuli tomu,. aby brzdil VF bordel z logiky, ne 100Hz ze zdroje. Ale po přečtení toho tvého posledního příspěvku, jsem přišel na nápad: jdu si sehnat pořádně tvrdý zdroj 5V. Ono totiž ten LED multiplex za to napájení docela tahá… sice je tam stabík, ale před ním je jen 7V (což je asi dost málo), a 7V poskytuje nějaký čínský adaptér, který asi taky moc tvrdý zrovna nebude.

Tak tomu moc nerozumím. Prommenná v_cnt obsahuje součet 16 vzorků. Jestliže se rozdíly mezi vzorky mohou lišit o 1LSB převodníku, v součtu to může dělat až 16. Vrátím-li se na začátek, kde píšeš, že Uref odpovídá hodnota 51,2V potom 1LSB dělá 51,2V/1024 = 0,05V. Takže chyba 0,01V je pouhá 1/5LSB! To je ovšem nesmysl vzhledem k 3místnému převodu. Asi to přeblikává o ±0,1V. To by byly ty 2 LSB a to také odpovídá numerickému výpočtu.
Povšiml jsem si následujícího:

//2.56V~51.2V 2.56V~2.56A volty = v_cnt >> 5; //prumeruj volty (deleni 16ti a jeste dvema)
Nejnižší bit proměnné v_cnt odpovídá 1/16LSB ADC. Dělením 16 se změní váha nejnižšího bitu na 1LSB a dalším dělením 2 pak na 2LSB. Výsledek bude zatížen chybou 2 LSB způseobenou numerickým zaokrouhlením při dělení.

Chtěl bych ještě upozornit na záludnost operátoru >>. Znamená posun bitů vpravo, nikoli dělení, ačkoliv každý bude tvrdit, že je to totéž. Operace není definována pro čísla typu signed. Bity, které zprava vypadnou, jsou zahozeny. Tím vzniká numeriká chyba. V případě dělení např. 32 některé překladače provádí zaokrouhlení podílu na rozdíl od >>.
Pokud by v_cnt a volty byly definovány jako unsigned, mohlo by se to zapsat takto:
volty = (v_cnt+16)>>5;
16 je polovina dělitele a způsobuje zaokrouhlení podílu tedy zmenšení chyby na 1LSB.
Jinak to vypadá, že ADC už pracuje korektně.

Moc hezká fičurka… :slight_smile: Doteď jsem se vždycky přemejšlel, jak donutit překladač, aby při posledním posunu použil rotaci přes C a podle něj pak výsledek upravil, toto mě tedy nenapadlo…

Technik: Samozřejmě to mělo být ±0.1V. Displej mi zobrazuje jako “00.0”. Takže jsem se zas upsal.
Záludnost operáturu >> ? NEvim jak ostatní programátoři, ale já vím co je posuv vpravo.
“Není definován pro signed” - myslím si že vím docela přesně, co se stane když použiju >> nebo << na signed proměnnou. Znaménko se uchovává v MSB, 7mém bitu. (u signed char což je 8bit proměnná). Ale tu v podstatě nastává menší problém. Takže asi jo, je to záludné. AVR mají celkem 5 instrukcí pro “<< a >>”: ROL, ROR, LSL, LSR, ASR.
ROL/ROR j rotace, kdy C->B7->B6…B0->C (a obráceně)
LSL a LSR: 0->B7…B0->C (a obráceně)
a ASR je právě ta výjmka. Překladač by právě mohl při použití operátoru “>>” použít tu instrukci ASR, která by správně shiftnula vpravo, a zachovala znaménko v B7. Jediná záludnost je ta, jestli překladač použije ROR/LSR nebo to správnější ASR. Škoda právě, že do AVR neudělali ASL. (to samé, ale na druhou stranu, i když by to bylo trochu složitější, protože B6 by se musel posunout do C, a B7 zachovat, tam by to bylo trošku ošemetné. (Stejná škoda, že AVR nemají rychlé instrukce dělení…)
Takže závěrem téhle menší úvahy bych řekl, že při použití “<<” na signed proměnou je výsledek jasný co se stane, a při použití “>>” to záleží jak si to přebere compiler.

To je právě jedna z věcí, co se mi na C pro AVR nelíbí. Nikdy nemáš jisté, co s tím kde překladač provede.
Třeba mi vrtá hlavou ještě tohle, na což bych rád znal odpověď:
Zápis třeba:

PORTA  = (1<<PA0)|(1<<PA5);

zde by měl překladač při překladu vyhodnotit výraz " (1<<PA0)|(1<<PA5);" do konstantní hodnoty, a tu pak přes LDI a OUT nacpat do PORTA. Tohle je podle mě docel pochopitelné, ale nyní už tápu:

PORTA |=  (1<<PA0)|(1<<PA5);

Co zde udělá překladač? Napadají mě 3 možnosti:
ta jedna blbá, která by pak znefunkčnila program, je to, že by udělal to samé co v předchozím případě, což je asi blbost.
Druhá možnost:do pomocného registru si nasype hodnotu PORTA, kterou pak přes instrukci ORI (logiocal or with immediate) opravdu “oroval” s tou konstantou, a pak zpět navrátil hodnotu do PORTA. To je ta podle mě nejpravděpodobnější varianta.
a ta 3tí, nejmíň pravděpodobná situace: překladač by mohl použít instrukci SBI (Set Bit in I/O register). Zde by právě mohl tímto překladač občas ušetřit čas mcu, ale záleží, kolik bitů by se muselo nastavit. Pokud bychom nastavovali ty dva bity jak jsem dával příklad, program by měl vypadat asi takhle:

in R16, PORTA
ori R16, 0b00100001
out PORTA, R16

nebo ta rychlejší varianta:
sbi PORTA, 5
sbi PORTA, 0

Sice už je to mírně offtopic, ale dík za vysvětlení. Docela mi to pomůže při dalším programování.

EDIT: Můžete mi ještě podat přesnější vysvětlení toho zaokrouhlování?

volty = (v_cnt+16)>>5;

Jako jak polovina dělitele? Co to udělá?

Operátor >> a << je vzdy posun bitů. Byte ovšem nelze vždy chápat jako číslo 0 až 255 nebo -128 až 127. Obecně je to uspořádaná osmice bitů, jejichž význam může být jakýkoliv (např. barva, písnemo, pixely,…), jak si programátor vzpomene. Proto né každá aritmetcká nebo logická instrukce bude mít s obecně definovaným bytem smysl.
Jeli byte unsigned char pak B7 má váhu 128, B6 64 atd a posunem doprava se hodnoty bitů přesunou na pozici s nižší váhou a výsledek je smysluplná polovina.
U signed char se znamínkový bit po posunu dostane na B6 s váhou 64, což z matematického hlediska je pitomost, a tudíž to nemá smysl. Ani norma ANSI/ISO nepředepisuje co s tím, takže tvůrci překladačů toto mohu řešit po svém.
Překlad operátoru >> by měl být vždy instrukcí LSR. Instrukce ASR nepřichází v úvahu a už vůbec né ROR. Carry není známé. Jak bychom pak mohli přemísťovat jednotlivé bity uvnitř byte, když by >> se přeložil jako děleni 2 instr ASR?
“Škoda právě, že do AVR neudělali ASL.” - Ale ona tam je! ASR a ASL je dělení a násobení signed byte 2, takže ASL Rx = ADD Rx,Rx a korktně generuje přetečení OV pro signed a Cy pro unsigned byte. ROL a ROR se používají jen pro řetězení byte. Např při shiftu nebo dělení wordu, intu, long intu atd.

PORTA |=  (1<<PA0)|(1<<PA5);

Přesnš jak píšeš, použije 2. nebo 3. variantu podle toho, co bude pro něj výhodnější, též dle nastavení překladače. Jen bych dodal, že překladač ještě k té 2. variantě pravděpodobně přidá CLI a na konec SEI, protože s portem se může manipulovat i v přerušení.
Pokud použiješ některý z MCU nové generace, např mega88 místo mega8, měl by překladač generovat efektivnější kód:

LDI R16,(1<<PA0)|(1<<PA5); OUT PINA,R16
A je to úplně super, bez konfliktu s přerušením.

volty = (v_cnt+16)>>5;

Přepíšu to algebraicky:
volty = (v_cnt + 16) - 32 = v_cnt/32 + 16/32 = v_cnt/32 + 0,5
Chyba, která vznikne po dělení je dána (zbytkem po dělení)/dělitel. Zbytek je vždy menší než dělitel, takže chyba je vždy menší než 1. Jestliže je v našem případě zbytek 16 a více, potom podíl má blíže k hodnotě o 1 vyšší. Jeli zbytek menší než 16, lze brát podíl jak je. Protože se zbytek vždy zahodí, přičítám polovinu dělitele k dělenci, čím se svýší podíl o 1 v případě, že by podíl měl zbytek 16 a více.

Překladač použije kombinaci možnosti 2 a 3 v závislosti na optimalizacích a počtu nastavovaných bitů. Pokud půjse o 1 bit, provede sbi/cbi, kdyz o vic bitů, použije se in, ori/ani, out.
Ostatně si to můžeš napsat a podívat se co překladač vytvořil :wink:

Při obyčejném posunu je výsledek pouze oříznutý (zaokrouhlen na nejnižší celé číslo).
Pkud bys normálně dostal výsledek x.50 - x.99, číslo bude oříznuto na x. Právě ta část, od které chceš výsledek zvětšený o 1, je polovina dělitele. Když ji před dělením přičteš, dostaneš výsledek (x+1).0 - (x+1).49 A to je to, co potřebuješ. Po oříznutí vznikne správný zaokrouhlený výsledek.
Teď to popíšu v desítkový soustavě pro snažší pochopení, ale ve dvojkový tu funguje stejně.
312/26=12
325/26= 12.5 => 12 = nezaokrouhleno
337/26=12.96 => 12 = nezaokrouhleno
338/26=13
350/26=13.46 => 13
351/26=13.5 => 13 = nezaokrouhleno

Když přičteš polovinu dělitele:
(312+13)/26 = 12.5 => 12 správně
(325+13)/26 = 13.0 => 13 správně
(337+13)/26 = 13.46 => 13 správně
(338+13)/26 = 13.5 => 13 správně
(350+13)/26 = 13.96 => 13 správně
(351+13)/26 = 14 => správně

Tak teď jsem si všiml, že jsem napsal 2 pitomosti:
Na začátku zůstal nadpis “Jan16 napsal:” nemá tam být.
A příklad

LDI R16,(1<<PA0)|(1<<PA5); OUT PINA,R16
není funkce OR portu ale NEG pinů. Tímto se omlovám za chyby.

ASL - tam fyzicky jako instrukce NEní.
ADD Rx,Rx - to mě nenapadlo. se signed sjem v assembleru nikdy nedělal, takže jsem o tom ani neuvažoval.

V tom tvém příkladu dole buď máš chybu, nebo to nechápu.

nemá tam být spíš (v_cnt + 16) / 32 ?

Takže když do toho svojeho výpočtu přidám ještě to +16, bude to fungovat lépe? (zkusím to)

LDI R16,(1<<PA0)|(1<<PA5); OUT PINA,R16
Co je tohle za blbost? Jaký NEG ?
Já myslel že PINA je registr pouze pro čtení. Tohle mi teda vysvětli víc, to mě teda zajmá docela.

Datasheet ATMega88. str.72:

Další vychytávka… škoda, že běžný uživatel, co nemá po nocích jako povinnou četbu datasheety, se o takovýchto věsech jen tak nedozví… :frowning:

LDI R16,(1<<PA0)|(1<<PA5); OUT PINA,R16
Teď mě tak napadá, že toto nelze použít jako OR/AND. Překladač přeci nemůže v době překladu tušit, jesli když tam nastavuju “1”, jesli už tam náhodou není. V tom případě by to pak tou negací pěkně pohnojil.

ASL tam opravdu není, lze pro krásu dodefinovat macrem. Nemá to ale smysml. Instrukce LSL, ASL, ADD Rx,Rx mají stejný kód i význam. Aritmetický shift doleva je stejný jako logický. Zprava doplňuje nuly.

Máš pravdu, má.

LDI R16,(1<<PA0)|(1<<PA5); OUT PINA,R16
Není to blbost, viz datasheet ATmega88, kapitola 12.2.2

Neplatí pro mature product.

hm… pěkné… já ale používám jen mega8 (ne mega88) z důvodů finančních. Sice to vypadá divně, že se snažim ušetřit na kde čem, ale kdybyste měli “příjmy” jak já, pochopili byste to. :slight_smile:

Mimochodem, v čem se liší ta mega88 od té mega8 ? Kro mtéhle vychytávky s PINx?
A já dycky v assembleru negoval pomocí EOR. (xor)
Nikdy sem nepochopil také instrukce NEG a COM. (snad si ty jména pamatuju dobře)
Mimochodem, jak se vůbec v céčku negují bity?
znám jakýsi operátor tilda ~ , který cosi neguje, ale nevim, jak ho mám použít když chci negovat třeba PORTA bit 5.
EDIT: cosi neguje … neguje celý byte/word/dword, podle toho, co má negovat.

Negace pinu v C:

if(PINA & 1<<PA5) { // PA5 je "1" PORTA &= ~(1<<PA5); } else { //PA5 je "0" PORTA |= 1<<PA5; }
nebo jednodušeji

PORTA = PINA ^ (1<<PB5);

Tohle mě napadlo až teď, vyzkoušené to nemám, ale nevidím důvod aby to nechodilo…

Ahá. Takže jako xx mám psát podmínku, aby překladač z toho vyrobil půlkilometrový kód s nevím čím. To je to poslední řešení, na který jsem myslel.
No, co dělá operátor ^ střecha jsem si musel najít sám… super, je to XOR. Taže v C se to dělá stejně jak v ASM. Pomocí xorování.
Akorát bych to upravil na
PORTA = PORTA ^ (1<<PB5)
Tahle “negační” schopnost xoru je mi známá už od mých začátků programování, kdy jsem na to někde náhodou narazil, a od té doby to používám.
pokud se libovolný bit xoruje s 1čkou, výsledkem je negace bitu.

A | B | A xor B 0 | 0 | 0 0 | 1 | 1 1 | 0 | 1 1 | 1 | 0

Takže jo, tvůj nápad je správný, a mnou již 1000x ověřený, funguje to.

Já to běžně nepoužíval, protože jsem ve většině případů v tý podmínce dělal i něco jinýho. Známý mi je to taky již dávno, používám to ale obvykle v jiných souvislostech.
Tu úpravu můžeš použít pouze tehdy, pokud tě nezajímá, co je na pinu ve skutečnosti.