1 ...editoval Dohnalik (22. 4. 2012 12:58)

Téma: Atmel AVR, SPI a C

Zdravím. Mám takový dotaz na programování rozhraní SPI pro AVR v jazyce C.

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.

2 ...editoval luta (29. 4. 2012 22:55)

Re: Atmel AVR, SPI a C

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 smile

Web

3 ...editoval Dohnalik (22. 4. 2012 15:06)

Re: Atmel AVR, SPI a C

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í.

https://lh4.googleusercontent.com/-EVasXMdzMXY/T5P9UQXfirI/AAAAAAAABsQ/hVarqupqWlU/s794/cs%2520spi.jpg

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.


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

4 ...editoval luta (22. 4. 2012 16:02)

Re: Atmel AVR, SPI a C

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 C

ad 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 :


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ř

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

Web

5

Re: Atmel AVR, SPI a C

No já se učím za pochodu, vím, je to chyba, ale nikdy nepoberu hromadu teorie smile  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:

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 smile

6 ...editoval luta (22. 4. 2012 16:19)

Re: Atmel AVR, SPI a C

přesně tak smile 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

Web

7 ...editoval Dohnalik (22. 4. 2012 21:01)

Re: Atmel AVR, SPI a C

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. smile Jinak MSB/LSB se nastavuje pomocí DODR v registru SPCR - 1 = LSB první, 0 = MSB první.

#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.

8 ...editoval luta (23. 4. 2012 13:41)

Re: Atmel AVR, SPI a C

tak jsem koukl do toho datasheetu. Je to jednoduchý jako facka smile

shows the operation of the control port in SPI Mode. To write to a register, bring CS low. The first
seven bits on CDIN form the chip address and must be 0010000. The eighth bit is a read/write indicator
(R/W), which should be low to write. The next eight bits include the 7-bit Memory Address Pointer (MAP),
which is set to the address of the register that is to be updated. The next eight bits are the data which will
be placed into the register designated by the MAP. During writes, the CDOUT output stays in the Hi-Z state.
It may be externally pulled high or low with a 47 kΩ resistor, if desired.

Chceš-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

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.

Pokud chci číst tak

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 smile tedy funkce ta na čtení by mohla být zhruba např

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

Web

9 ...editoval Dohnalik (26. 4. 2012 17:21)

Re: Atmel AVR, SPI a C

Že já si to nepřečetl dřív, vycházel jsem z toho grafu smile 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“? smile

//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.

10 ...editoval luta (26. 4. 2012 18:30)

Re: Atmel AVR, SPI a C

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á? smile

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 smile

Web

11 ...editoval Dohnalik (26. 4. 2012 19:33)

Re: Atmel AVR, SPI a C

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á smile Jak to reálně použít? Co napsat za ni?

while(true){
 if (tacitko!=predStav)
 break;
 predStav=tlacitko;
 }

12 ...editoval luta (26. 4. 2012 20:11)

Re: Atmel AVR, SPI a C

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á..

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 ...

Web

13

Re: Atmel AVR, SPI a C

Díky, ještě, že tu jsi, nikdo jiný by mi to tak dobře nevysvětlil. smile 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.

14

Re: Atmel AVR, SPI a C

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ř

    if(bit_is_clear(KEY_PIN,KEY_OK) && delay_ok == 0)
    {
        keys->keys|=OK;
        delay_ok = KEY_DELAY;
    }

    if(delay_ok)delay_ok--;

Web

15

Re: Atmel AVR, SPI a C

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:

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 smile, 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.

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?

    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í.

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. smile

16 ...editoval luta (29. 4. 2012 23:34)

Re: Atmel AVR, SPI a C

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 smile
tohle skutecne nastaví PB2 bit a ostatní nezmění a výš proč?

PORTB |= (1<<PB2); 

protože je to skrácenej zápis tohohle, přičemž | je bitovej OR:

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:

uint8_t CS8416Read(uint8_t addr, uint8_t data)

máš zbytečně vstupní parametr funkce data..v té nic nepředáváš...ten bajt můžeš vrátit lokálně vytvořenou proměnnou data ve funkci.. Já tam totiž měl ukazatel na pole, takže jsem to využil ..

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 ..

Web

17 ...editoval Dohnalik (29. 4. 2012 23:36)

Re: Atmel AVR, SPI a C

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 smile
//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.

18 ...editoval luta (30. 4. 2012 12:59)

Re: Atmel AVR, SPI a C

no pokud to píšeš na slepo a nemáš prozatím žádné zkušenosti , tak si ještě užiješ smile ale každý nějak začínal.
edit:
není zač smile

Web

19 ...editoval Dohnalik (1. 5. 2012 21:33)

Re: Atmel AVR, SPI a C

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? smile

20

Re: Atmel AVR, SPI a C

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..

Web