![]() |
|
Atmel AVR, SPI a C - Printable Version +- Audioweb.cz (https://www.audioweb.cz) +-- Forum: Ostatní (https://www.audioweb.cz/forumdisplay.php?fid=16) +--- Forum: Smetiště (https://www.audioweb.cz/forumdisplay.php?fid=21) +--- Thread: Atmel AVR, SPI a C (/showthread.php?tid=16062) Pages:
1
2
|
Atmel AVR, SPI a C - Dohnalik - 22. 4. 2012 Zdravím. Mám takový dotaz na programování rozhraní SPI pro AVR v jazyce C. Kód: unsigned char SPI_WriteRead(unsigned char dataout)
{
SPDR = dataout; //zapis do prenosoveho registru
while(!(SPSR & (1<<SPIF))); //cekani na dokonceni prenaseni
PORTB = (1<<PB2); //SS pin log 0
_delay_us(1); //zpozdeni 1us
PORTB = ~(1<<PB2); //SS pin log 1
}V kódu výše je jednoduchá metoda zápisu přes SPI do slave zařízení (samotné nastavení SPI registrů řeším v metodě main). A teď mám pár otázek. PB2 je pin slave select, čili když chci zapisovat musím jej nastavit na log 0. Proč se toto děje až po zápisu do přenosového registru SPDR? Proč mi nefunguje, když PB2 nastavím na log 0 již před samotným zápisem do registru? Dále proč je tam to zpoždění 1us a proč to bez něj někdy funguje a někdy ne? Je to tak, že se SPDR funguje jako nějaká cache do zařízení se data odešlou až po nastavení SS na log0? Za každou odpověď díky. Atmel AVR, SPI a C - luta - 22. 4. 2012 Aby si oslovil dané zařízení (slave), musíš mu vystavit CS pin většinou do log0. Tento můžeš ovládat libovolným pinem AVR.. tedy : nastavím CS na low, počkám nějakou us odešlu data do SPDR, SPI začne lít data do zařízení dle nastavené rychlosti, polarity hodin atd.. jak jsou data nalita tj přestane cyklit ten while(), zvednu CS na high.. konec Předpokladem je správně nastevené SPI, nastavení SCK a MOSI pinu jako výstup a popř nastavení MISO jako vstup, pokud je použito zpětné čtení ze zařízení při přenosu.. k té tvé funkci.. pozor na to, že přepisuješ celý PORTB ! zápis PORTB = 1 << PB2 nastaví na PORTB všude nuly krom PB2 kde je 1 tedy správně je PORTB |= 1 << PB2; to samo pro vynulování PB2 PORTB &= ~(1<<PB2) případně negaci PB2 PORTB^=1<<PB2; možná proto ti to nefungovalo..jakoby si tvrdým zápisem změnil celý PORTB pokud je na něm celé SPI a tím vytvořil první hranu sck/ mosi bit před odesláním a proč ti to nastavování SS funguje takto na konci? protože po ukončení této funkce máš CS pin zařízení stále v log0..pokud máš na SPI jen jedno zařízení, toto stále tím pádem naslouchá..máš přehozeny ty komenty log0 vs log1 je opačně sem se trochu rozepsal :-) Atmel AVR, SPI a C - Dohnalik - 22. 4. 2012 DÍKY! Konečně mi to někdo vysvětlil, od všech, kterých jsem se ptal, jsem dostal odpověď že to takhle funguje, tak to tak píšou. Když už tu jsi, tak se ještě zeptám, jaký je rozdíl mezi unsigned char a například int? Kód bude soužit pro komunikaci s tím CS8416, který po zapsání adresy potřebuje ještě R/!W bit, takže asi udělám dvě metody, jednu pro zápis a druhou pro čtení. ![]() Mohlo by to vypadat takhle, co ty na to? Jen doplním, že procesor je ATmega328 (PB5 - SCK, PB4 - MISO, PB3 - MOSI, PB2 !(SS)). A mohlo by to fungovat i tak, že by SS na slave zařízení trvale uzemnil? Zařízení mám jedno. Protože dřív mi to dělalo dost bugr. Kód: #ifndef F_CPU
#define F_CPU 1000000UL
#endif
#include <avr/io.h>
#include <util/delay.h>
unsigned char SPI_Write(unsigned char dataout)
{
PORTB |= (1<<PB2);
_delay_us(1);
SPDR = 0b00100000;
SPDR = dataout;
while(!(SPSR & (1<<SPIF)));
PORTB &= ~(1<<PB2);
return 0;
}
unsigned char SPI_Read()
{
unsigned char datain;
PORTB |= (1<<PB2);
_delay_us(1);
SPDR = 0b00100001;
datain = SPDR;
while(!(SPSR & (1<<SPIF)));
PORTB &= ~(1<<PB2);
return datain;
}
int main(void)
{
DDRB = (1<<PB3)|(1<<PB5)|(1<<PB2);
DDRD = 0x3E
PORTB &= ~(1<<PB2);
SPCR = (1<<SPE)|(1<<MSTR);
//samotny kod
return 0;
}Atmel AVR, SPI a C - luta - 22. 4. 2012 tak to vezmu postupně: unsigned char = na AVR 8bit proměnná 0 - 255 hodnota desitkově unsigned int = na AVR 16bit proměnná 0- 65535 rozsah desitkově uděláš-li unsigned int b = 0xAABB; unsigned char a = b; pak v a = 0xBB; protože to ořeže Nastuduj si základy Cad funkce... až na pár chybek by to mohlo jít.. Pouze máš zase opačně natavení CS :-) nastavuješ to tam zase na log1 na začátku funkce a na log0 na konci funkce a to je blbě.. funkce na zápis musí mít mezi plněním SPDR ten while! lepší je udělat si funkci zvlášť tam nastavit CS pin, zavolat 2x SPI_write, která musí mít vždy čekání while!(blabla SPIF) funkce na read ti asi fungovat nebude. Bacha na to, že tím že ukončíš posílání dat na SPDR ukončíš hodiny SPI..tudíž musíš poslat jednou ten bajt s Adresou + RW a po něm posílat třeba 0xFF dělat jakoby zbytečný zápis, aby si klokoval hodiny a mohl číst SPDR příjem... slave totiž očekává SCK aby mohl vysílat.. tedy : Kód: unsigned char SPI_Write(unsigned char dataout){
SPDR = dataout;
while(!(SPSR & (1<<SPIF)));
return SPDR;
}tuto funkci si voláš pak jak rutinou pro read tak i write..tedy např Kód: unsigned char readCS8416(void){
unsigned char read;
PORTB&=~(1<<PB2); // CS do log0!
_delay_us(1);
SPI_Write( 0b00100001); // ted jeste nevi ze ma vysilat takze poslu povel ze chci data
read = SPI_Write(0xFF); // repete pro prijem bajtu aby mel CS8416 hodiny
PORTB|=1<<PB2; // CS do log1!
return read;
}Pokud CS8416 vrací víc než jeden bajt, musíš plnit pole hodnot tedy zacyklit to read = SPI_Write(0xFF); a plnit pole předané funkci ukazatelem někdy trvalé uzemnění CS funguje, jindy to dělá problém..tim CS v klidu do log1 si ten slave třeba vyresetuje SPI registry Jinak do datasheetu jsem nekoukal, takže vycházím jen z těch obrázků co tu máš mimochodem, u těch AVR není od věci používat typy uint8_t , int8_t uint16_t , int16_t uint32_t , int32_t případně uint64_t , int64_t člověk se v tom líp orientuje a nemusí třeba přemýšlet, že avr má int 16bit narozdíl od jiných platforem Atmel AVR, SPI a C - Dohnalik - 22. 4. 2012 No já se učím za pochodu, vím, je to chyba, ale nikdy nepoberu hromadu teorie Spletl jsem si funkce zápisu a mazání.Takže pokud to dobře chápu, abych mohl z integráče číst, musím do něj zároveň i zapisovat aby měl SCK, ale je jedno co, protože jsem mu předtím poslal read bit, takže data na MOSI bude ignorovat. O to se stará tato funkce: Kód: read = SPI_Write(0xFF)Když jí tedy používám, tak do SPI zároveň zapisuju, zároveň čtu a ukládám do proměnné read? Pokud to tak je, tak moc díky za pomoc, protože to konečně chápu
Atmel AVR, SPI a C - luta - 22. 4. 2012 přesně tak :-) je to vidět i na tom obrázku.. SCK clockuje zatímco MCU už vysílá třeba samou log1 na MOSI a slave vrací přes MISO data..maj tam těch dat více.. MSB --- LSB MSB --- LSB je to taková vlastnost SPI, master vždy začíná přenos a diktuje SCK hodiny..pokud nevysílá , pak ani slave nemůže odesílat data ještě si dej pozor na správné nastavení polarity hodin CPOL a úrovní sběrnice tj CPHA v registrech SPI... jinak by ti to nechodilo..případně ještě to, že posílá MSB první a až poslední LSB.. pokud dobře koukám..ale to už si z hlavy nepamatuju, zda a jak to jde nastavit v registrech Atmel AVR, SPI a C - Dohnalik - 22. 4. 2012 Ano ano, na to si dám pozor, CPOL je tady 0 a CPHA taky, pokud dobře vidím, takže to nemusím řešit. Ještě jedou díky a až se mi bude chtít tak to napíšu komplet. Jinak MSB/LSB se nastavuje pomocí DODR v registru SPCR - 1 = LSB první, 0 = MSB první.Kód: #ifndef F_CPU
#define F_CPU 1000000UL
#endif
#include <avr/io.h>
#include <util/delay.h>
unsigned char SPI_c(unsigned char dataout)
{
SPDR = dataout;
while(!(SPSR & (1<<SPIF)));
return SPDR;
}
unsigned char SPI_read()
{
unsigned char read;
PORTB &= ~(1<<PB2);
_delay_us(1);
SPI_c(0b00100001);
read = SPI_c(0xFF);
PORTB |=(1<<PB2);
return read;
}
unsigned char SPI_write(unsigned char data)
{
PORTB &= ~(1<<PB2);
_delay_us(1);
SPI_c(0b00100000);
SPI_c(data);
PORTB |=(1<<PB2);
return 0;
}
int main(void)
{
DDRB = (1<<PB3)|(1<<PB5)|(1<<PB2);
DDRD = 0x3E;
PORTB &= ~(1<<PB2);
SPCR = (1<<SPE)|(1<<MSTR);
SPI_write(0x04); //adresa registru v CS4816 (control register)
SPI_write(1<<7)|(1<<3); //zapis do registru CS8416 (zapnuti obvodu a nastaveni vstupu)
//??? jak urcim registr, ze ktereho chci cist?
unsigned char clock = SPI_read(); //cteni pres SPI
return 0;
}Tak jsem na to koukl, napsal jsem to takto, ale co myslíš, jak sakra určit registr CS8416, ze kterého chci data číst??? Prostě mi to do hlavy neleze, ono to totiž není napsané ani v tom grafu. Atmel AVR, SPI a C - luta - 23. 4. 2012 tak jsem koukl do toho datasheetu. Je to jednoduchý jako facka :-) Citace:shows the operation of the control port in SPI Mode. To write to a register, bring CS low. The firstChceš-li někam zapsat do některého registru pak: CS low odešlu adresni bajt zarizeni + WRITE bit odešlu adresu MAP registru - tj adresu toho registru kam zapisuji např SPDR = 0x00 tj MAP = 0x00 odešlu bajt pro zapis do daneho registru hodím CS na high.. exit Kód: To read a register, the MAP has to be set to the correct address by executing a partial write cycle which
finishes (CS high) immediately after the MAP byte. To begin a read, bring CS low, send out the chip address
and set the read/write bit (R/W) high. The next falling edge of CCLK will clock out the MSB of the addressed
register (CDOUT will leave the high impedance state). The MAP automatically increments, so data for suc-
cessive registers will appear consecutively.CS low odešlu adresni bajt + bit ve stavu WRITE! odešlu bajt MAP tj adresu registru odkud chci začíst číst CS high počkám us CS low odešlu adresu zařízení + bit READ! začínám číst data tim dummy zápisem 0xff na sběrnici a clockuji mu tím hodiny.. vyčtu bajtů kolik chci protože MAP registr je autoinkrementační hodím CS na high konec ta autoinkremetnace registru by měla fungovat snad i u zápisu :-) tedy funkce ta na čtení by mohla být zhruba např Kód: void CS8416read(uint8_t *data,uint8_t data_size,uint8_t addr){
uint8_t i;
CSLow();
_delay_us(1);
SPI_write(00100000); // zapisuji
SPI_write(addr); // nastaveni MAP registru
CSHigh();
_delay_us(1);
CSLow();
_delay_us(1);
SPI_write(00100001); // prikaz cteni
if(data!=NULL)
for(i=0;i<data_size;i++) data[i] = SPI_write(0xFF); // ctu data od adresy "addr" pocet dat je data_size
CSHigh();
}PS: píšu to naslepo CSLow() a CSHigh je pseudokod - nechce se mi furt vypisovat ty porty Atmel AVR, SPI a C - Dohnalik - 23. 4. 2012 Že já si to nepřečetl dřív, vycházel jsem z toho grafu Tak to zase překopu.Mám ještě dotaz, autoinkrementační znamená, že se registry postupně posunují dále k vyššímu? A ještě, copak je to „NULL“? ![]() //můžu ještě jednu otravnou otázku? Jak použít debounce bez vyplýtvání timeru? Potřebuju celý ten DAC zapínat jedním tlačítkem. Atmel AVR, SPI a C - luta - 26. 4. 2012 jo přesně to je autoinkrementace..jen pošleš poč. adresu. NULL znamená,že testuje zda ukazatel data ukazuje někam na nějakou adresu.. je to ekvivalentní zápis if(!data).. tj test zda si mu předal nějaké pole..správně by tam mohl bejt i test, zda data_size!=0 nebo-li !data_size.. je to takovej standard v ošetření vstupních parametrů.. ikdyž dost záleží i na kompilátoru jak se zachová.. debounce bez timeru? moc nevím co tím myslíš.. ošetření tlačítka proti zákmitům pomocí smyčky, nebo uplně něco jinyho co mě uniká? :-) jinak needituj ty staré příspěvky, ale piš nové, pokud je to víc jak 24h, jinak mi nepřijde upozornění a je to spíš náhoda, že se občas mrknu :-) Atmel AVR, SPI a C - Dohnalik - 26. 4. 2012 Ano ano, ošetření zákmitů tlačítka - aby se mi to pořád nevypínalo a nezapínalo při držení tlačítka. Mám tuto smyčku, ale že bych ji chápal, to se opravu říct nedá Jak to reálně použít? Co napsat za ni? Kód: while(true){
if (tacitko!=predStav)
break;
predStav=tlacitko;
}Atmel AVR, SPI a C - luta - 26. 4. 2012 no existuje mnoho způsobů.. jde o to zda ti vadí či nevadí nějaky to čekání ve smyčce.. a zda chceš s držením tlačítka inkrementovat po nějaké době či jen ingorovat tlačítko..ignorace tj čekání na puštění je jednoduchá.. Kód: while(1){
// main hlavni smycka ...
if( !(PINB & 1<<PB4) ){ // v log0
stisknuto++;
_delay_ms(10);
while( !(PINB & 1<<PB4) ); // v log0 pak cekej dokud se neuvolni
}
// .. pro dalsi tlacitka...
// lcd prekresleni atd
_delay_ms(20);
}pokud chceš něco sofistikovanějšího tak příklad třeba viz moje zdrojáky palubního počítače PP_light ze stránek http://luta.7u.cz/files/PP_light.zip tam to funguje ale jinak a nevím zda je to pro začátek jednoduchý.. v podstatě tam běží wachdog timer a polling fce na tlačítka která je i vymaskuje pokud se stisklo častěji v nějakém intervalu..tj pokud jej držíš, jakoby se mačká autoinkremetuje třeba po 200ms ... Atmel AVR, SPI a C - Dohnalik - 26. 4. 2012 Díky, ještě, že tu jsi, nikdo jiný by mi to tak dobře nevysvětlil. Půjdu to přepsat i pro vypínání stiskem tlačítka (jak dosáhne stisknuto hodnoty 2, nějaká bool proměnná se nastaví na false a stisknuto se vynuluje, tedy doufám). Chci to jen pro zapínání DACu, nic sofistikovaného nepotřebuju. Počítám, že to ještě bude procesor uspávat a uloží případné změny v nastavení (poslední zvolený vstup...) do EEPROM. Jen mám ještě dotaz, k čemu tam vlastně slouží ty delaye? Nebo alespoň ten 20 ms. V hlavní smyčce by mi celkem vadil.
Atmel AVR, SPI a C - luta - 26. 4. 2012 v uspání tě probudí interrupt a v něm testuješ tlačítko.. i to je v těch zdrojákách co jsem hodil odkaz.. ten 20ms je částečně na zákmity a refresh LCD.. pokud ti proběhnutí main trvá déle tak to tam ani nemusí být.. jen bys musel přidat něco na způsob stisknuto , pak nastav dekrementační proměnou a tu dekrementuj automaticky v cyklu main.. když stiskne znova a dekrementace neproběhla tzn asi zákmit tak se tlačítko nevyhodnotí.. dekrementace něco jako if(zpozdeniTlac)zpozdeniTlac--; kousek z funkce z mejch nezveřejněnejch zdrojáků např Kód: if(bit_is_clear(KEY_PIN,KEY_OK) && delay_ok == 0)
{
keys->keys|=OK;
delay_ok = KEY_DELAY;
}
if(delay_ok)delay_ok--;Atmel AVR, SPI a C - Dohnalik - 29. 4. 2012 Tak jsem tu dnes zůstal do noci a psal a psal. Nakonec jsem ta tlačítka vyřešil jinak (jednoduše mi tam jak delay, tak nic nedělání a čekání na puštění tlačítka, zavazelo). Použil jsem timer0 pomocí kterého každou 1ms přičítám do long proměnné 1. No a mělo by to fungovat takto: Kód: if(!(PIND & 1<<PD1) && predchoziStav == 0 && tlac - cas > 200){ // Zapnutí.
if (zapnuti == 1)
zapnuti = 0;
else
zapnuti = 1;
cas = tlac;
}
predchoziStav = !(PIND & 1<<PD1);zapnuti je prostě zapnutí, pokdu je jedna, celé zařízení se rozběhne , tlac je právě ta proměnná, do které je každou 1ms přičítána 1. 200 je vstupní zpoždění 200ms.Ale mám na tebe ještě další otázečky (doufám, že už poslední). Vytvořil jsem si funkce pro zápis a čtení SPI dle tvých rad. Kód: uint8_t SPI(uint8_t data){
SPDR = data;
while(!(SPSR & (1<<SPIF))); // Počkej na dokončení přenosu.
return SPDR;
}
uint8_t CS8416Read(uint8_t addr, uint8_t data){
PORTB &= ~(1<<PB2);
_delay_us(1);
SPI(0b00100000); // Adresa čipu a Write bit.
SPI(addr); // Adresa registru, ze kterého chci číst.
PORTB |= (1<<PB2);
_delay_us(1);
PORTB &= ~(1<<PB2);
_delay_us(1);
SPI(0b00100001); // Adresa čipu a Read bit.
data = SPI(0xFF); // Čtu data.
PORTB |= (1<<PB2);
return data;
}
void CS8416Write(uint8_t addr, uint8_t data){
PORTB &= ~(1<<PB2);
_delay_us(1);
SPI(0b00100000); // Adresa čipu a Write bit.
SPI(addr); // Adresa registru, do kterého chci zapisovat.
SPI(data); //Zápis.
PORTB |= (1<<PB2);
}Teď ovšem nastal stav, kdy potřebuju pracovat jen s jednou buňkou v v MAP registru. Dá se to napsat takto? Kód: CS8416Write(0x05, (1<<7)|(1<<2)|(1<<1)); // Nastavení audio výstupu CS8416 na I2S master.V registru 05h potřebuju zapsat 1 na pin 7, 2 a 1, mám to správně? Moje další otázka se týká čtení. Kód: if (CS8416Read(0x0B, (1<<0))){ // Pokud je samplovací frekvence menší než 88,1 KHz, nastaví PDUR.
CS8416Write(0x00, !(1<<3));
}
else{
CS8416Write(0x00, (1<<3));Tady tato funkce by měla číst z registru 0B a kontrolovat pozici nultého bitu. Pokud je zde 1, zapíše do 00h na pozici třetího bitu 0, jinak 1. Bude to takto fungovat? Zatím moc díky, už to mám snad celé napsané, jen čekám, než mi přijde programátor a zbytek součástek na dostavění modulu s CS8416.
Atmel AVR, SPI a C - luta - 29. 4. 2012 ty tlačítka se mi moc nechtějí luštit ..přejdu k tomu dalšímu.. asi vznikla trochu mejlka s tím jak pracovat s daty vs bity :-) tohle skutecne nastaví PB2 bit a ostatní nezmění a výš proč? Kód: PORTB |= (1<<PB2);Kód: PORTB = PORTB | (1<<PB2);Z toho ti už snad bude mnohé jasné.. pokud chceš změnit bit registru toho SPDIF dekodéru, tak musíš daný bajt vyčíst, změnit bit a celý bajt tam zas nahrát.. jo ještě detail..na to jsem dřív uplně zapomněl.. ten pin SS portu toho SPI rozhraní musíš inicializovat jako výstup nebo alespoň zapnout pull-up, aby nebyl jako plovoucí vstup a nepadal do log. 0. Teď jsem s tím pomáhal známému a nakonec to bylo tímto, že občas SPI nechodilo..nevím zda je to tak u všech (asi jo) nebo jen u atmega644 co použil.. edit: Kód: uint8_t CS8416Read(uint8_t addr, uint8_t data)takže ani to testování bitu přes tu funkci pro čtení tak nejde..viz výše práce s bity.. musíš vyčíst bajt a ten až testovat .. Atmel AVR, SPI a C - Dohnalik - 29. 4. 2012 Ano SS tak mám nastaven, takže padání do slave nehrozí. S tím vyčítáním, změnou(čtením) a opětovným zápisem si ještě užiju, no snad na to nějak přijdu ![]() //OK, tu funkci upravím. Pole jsem smazal, jelikož jsem ho nepotřeboval (i když si teď zase říkám, že...no uvidím). Teď jdu pomalu spát a díky za pomoc. Atmel AVR, SPI a C - luta - 29. 4. 2012 no pokud to píšeš na slepo a nemáš prozatím žádné zkušenosti , tak si ještě užiješ :-) ale každý nějak začínal. edit: není zač :-) Atmel AVR, SPI a C - Dohnalik - 1. 5. 2012 Tak, našel jsem tady toto. Pokud jsem to správně pochopil, tak stačí, abych daný registr, který chci upravit, nahrál do proměnné, řekněme třeba CSreg. Ale jak bitově upravím obsah této proměnné? Stačí CSreg |= 0b00010000; (chci změnit 4tý bit na 1 a zbytek nechat), nebo to mám napsat CSreg |= (1<<4); ? Pokud se nepletu, měly by tyto zápisy být totožné. Nebo to mám udělat nějak jinak? Můžeš mě prosím nakopnout na tu správnou cestu?
Atmel AVR, SPI a C - luta - 1. 5. 2012 jo je to to samo co jsem ti psal vejš na příkladu s portem.. nejdřív vyčíst bajt, pak vymaskovat bit, pak ten bajt zas zapsat.. |