Elektroda.pl
Elektroda.pl
X
Elektroda.pl
Proszę, dodaj wyjątek dla www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

Moja walka z SD Card, Atmega, AVR GCC - Problem rozwiązany

02 Lut 2010 13:00 7177 16
  • Poziom 10  
    Buduję projekt, w którym jest obsługiwana karta SD (po SPI oczywiście).
    Jestem na etapie rozpracowywania obsługi i trafiłem na taki problem:

    Mam testowo kilka kart 16MB, 128MB, 128MB, 2GB, 2GB
    Otóż karty reagują na rozkazy zadziwiająco różnie (ale powtarzalnie !)

    1. Inicjalizacja
    a) Wysyłam FF dla rozpędzenia karty (jakieś 15 razy)
    b) CMD0 (odp 0x01)
    c) CMD1 (odp 0x00)

    Wszystkie karty startują i odpowiadają 0x00 na CMD1 choć niektóre starsze najpierw jeszcze
    raz lub dwa podają 0x01. Żadna nie wymaga ACMD41.

    2. Proszę o CID
    a) CMD10 (odp 0x00 - starsze 0x01 0x00)
    b) wysyłam FF i odbieram CID

    I tu właśnie jest problem którego nie rozumiem:

    - wg mnie nic w tym ciągu ciekawego nie widać (jest powtarzalny dla karty jednak)
    - karty 2GB nie rozumieją CMD10: odpowiedź w kółko 0x07, 0xFF, 0xFF (sandisk) lub 0x1C, 0xFF, 0xFF (nokia)

    To nie są SDHC bo na nich nie pisze nic takiego. O co chodzi ?
  • Poziom 19  
    Proponuje zebys pokazal program :p
  • Poziom 10  
    Jeśli to coś da to proszę .....

    funkcja inicjalizacji:
    Code:

    uint8_t ini_SD(void)
        {
        char i,status;
       
        unselect_card();
        for(i=0; i < 10; i++) SPI_sendchar(0xFF);
        select_card();
        for(i=0; i < 2; i++) SPI_sendchar(0xFF);
       
        i=0;
        do   {   
            status = Command(0x40,0,0,0x95);   // CMD0 -> R1  0x00=success, 0x01=idle; Tylko tu potrzebne CRC wyliczone
            if(i++ > 255) return 1;         // fail
       }
        while(status != 0x01);
           
        i=0;
        do    {
           status = Command(0x41,0,0,0xFF);   // CMD1 -> R1; Dummy CRC
       if(i++ > 255)            // Jeśli nie da rady CMD1 to może ACMD41 ?
           {
           i=0;
           do    {
              Command(0x77,0,0,0xFF);         // APP_CMD; Wstęp do ACMD
          status = Command(0x69,0,0,0xFF);   // ACMD41 = APP_CMD + CMD41 -> R1; Dummy CRC
          if(i++ > 255) return 1;         // fail
          }
           while(status != 0x00);                    
           }
       }
        while(status != 0x00);      
       
        SPCR &= ~((1 << SPR1) | (1 << SPR0));    // Clock Frequency: f/4
        SPSR |= (1 << SPI2X);          // Doubled Clock Frequency: f/2
       
        return 0;
        }



    funkcja odczytu CID
    Code:

    uint8_t sd_info(void)
    {
        uint8_t b, i, status;                          
        select_card();

        i=0;
        do   {   
            status = Command(0x4A,0,0,0xFF);   // CMD10 -> R1  0x00=success
            LCD_puthexU08(status);
            if(i++ > 100) return 1;         // fail
       }
        while(status != 0x00);
        
        for(uint8_t i = 0; i < 18; ++i)
       {
       b = SPI_sendchar(0xFF);
       LCD_puthexU08(b);
            }

        unselect_card();                           
        return 1;
    }


    Inne funkcje wykorzystywane:
    Code:

    char SPI_sendchar(char chr)
        {
        SPDR = chr;
        while(!(SPSR & (1<<SPIF)));
        return SPDR;
        }


    char Command(char cmd, uint16_t ArgH, uint16_t ArgL, char crc )
        {
        SPI_sendchar(0xFF);
        SPI_sendchar(cmd);
        SPI_sendchar((uint8_t)(ArgH >> 8));
        SPI_sendchar((uint8_t)ArgH);
        SPI_sendchar((uint8_t)(ArgL >> 8));
        SPI_sendchar((uint8_t)ArgL);
        SPI_sendchar(crc);
        SPI_sendchar(0xFF);
        return SPI_sendchar(0xFF);
        }
  • Poziom 34  
    Po wysłaniu poleceń GO_IDLE_STATE(0) oraz SEND_OP_COND(1) warto wysłać CRC_ON_OFF(59). Nie jestem pewien, czy odpowiedź R1 zawsze przychodzi jako drugi bajt po poleceniu - warto wstawić pętlę np 16 przebiegów, jeśli pojawi się bajt różny od 0xFF to ten właśnie zwrócić i przerwać. Polecenia wysyłać jako pojedyncze transakcje: przed poleceniem select_card(), po poleceniu unselect_card() oraz dodatkowe 8 cykli, aby karta mogła się zsynchronizować. Wysyłać ACMD_SEND_OP_COND nie ma sensu, gdyż to o ile pamiętam zostało przewidziane dla trybu SD, a wtedy komunikacja wygląda bardzo inaczej (inny format odpowiedzi, transmisja po przewodzie poleceń etc...)

    Mój kawałek kodu, o ile pamiętam to działający:
    Code:
       // inicjalizacja karty - 160 impulsów zegara
    
       uint8_t i;
       for(i=20; i; i--)
          sd_phy_serialget();
       uint8_t retry;
       uint8_t r1=0;
       retry = 10;
       while(1)
       {
          sd_select();
          sd_phy_serialget();
          sd_phy_command(SD_CMD_GO_IDLE_STATE, 0);
          r1 = sd_phy_getr1();
          sd_unselect();
          sd_phy_serialget();
          if(r1 == 0x01)
             break;
          if(--retry == 0)
             return 0;
          wait(20);
       }

       retry = 50;
       while(1)
       {
          // inicjalizacja karty
          sd_select();
          sd_phy_command(SD_CMD_SEND_OP_COND, 0);
          r1 = sd_phy_getr1();
          sd_unselect();
          sd_phy_serialget();
          if(r1 == 0)
             break;
            if(--retry == 0)
             return 0;

          for(i=10; i; i--)
             sd_phy_serialget();
          wait(20);
       }
          
       sd_select();
    #ifdef SD_CONF_GENERATE_CRC
       sd_phy_command(SD_CMD_CRC_ON_OFF, 1);
    #else
       sd_phy_command(SD_CMD_CRC_ON_OFF, 0);
    #endif
       r1 = sd_phy_getr1();
       sd_unselect();
       sd_phy_serialget();
       
       // pobranie opisu karty
       sd_select();
       sd_phy_command(SD_CMD_SEND_CSD, 0);
       sd_phy_getr1();
       sd_phy_getblock(&sd_obj.csd, sizeof(sd_obj.csd));
       sd_unselect();
       sd_phy_serialget();
       
       uint8_t CSizeMult = ((sd_obj.csd[9]<<1)&0x6) | (sd_obj.csd[10]>>7);
       uint16_t CSize = ((sd_obj.csd[6]<<10)&0xC00) | (sd_obj.csd[7]<<2) | (sd_obj.csd[8]>>6);
       uint8_t ReadBlLen = sd_obj.csd[5] & 0xF;
       sd_obj.sector_count = (uint32_t)(CSize+1)<<(CSizeMult+ReadBlLen-7);


    -- edit: z tego, czego nie widać, gdyż nie zamieściłem: sd_phy_getblock oprócz pobierania bloku oczekuje najpierw na początek bloku, na końcu pobiera crc. Wszystkie polecenia w moim kodzie mają poprawną sumę kontrolną, która jest wyliczana przy transmisji.
  • Poziom 10  
    Co do CRC to według datashetów tylko dla CMD0 jest wymagane i domyślnie zawsze potem kontrola jest wyłączona ale sprawdzę.

    Co do osobnych transakcji to też spróbuję choć wszelkie opisy nic o tym nie mówią.

    R1 jako drugi bajt bo w opisach czytałem że po crc jest wymagane wysłać co najmniej 8 cykli zegara.

    A wracając do tematu: jak pisałem karty mi odpowiadają OK na inicjalizację wszystkie.
    Te 2 szt 2GB dają w odpowiedzi na prośbę o CID w R1 bit Illegal command.

    Spróbuję jednak tych podpowiedzi choć twój kod w zasadzie oprócz select-unselect to się nie różni.
  • Poziom 10  
    Rozwiązanie:

    1. Zrobiłem zgodnie z radą sekwencję rozkazu:

    select
    rozkaz
    odczyt wszystkich odpowiedzi FF
    odczyt odpowiedzi
    unselect

    SEND_CID po odczycie odp. 0x00 trzeba jeszcze odczytać dane
    a potem dopiero unselect

    2. Wymieniłem kabelki
    Karta - Procek na krótsze

    Wszystko zaczęło działać.

    Co ciekawe nawet długie podpięte równolegle kable do programatora nie przeszkadzają
    i nie muszę ich odpinać (mam kartę przez bufor LVC541). Jedynie jedna przy podpiętych
    kabelkach czasem przekłamuje ale tylko na początku.

    Więc dzięki za naprowadzenie. Najbardziej jednak pomógł mi logger którego ktoś wczoraj umieścił w Projektach. Tam się przyjrzałem mocno obsłudze karty.
  • Poziom 10  
    Ostateczne Rozwiązanie kwestii SD

    Poprzedni optymizm był przedwczesny, okazało się że część kart przekłamuje transmisję.
    Poszedłem po rozum do głowy i rozpracowałem sprawę dokładnie: okazało się że gubią się "1" w ciągach bitów przy przejściu z "0" na "1" stąd wywnioskowałem że problem nie jest programistyczny a elektroniczny.

    Najpierw wyrzuciłem bufor LVC541 i wstawiłem zwykłe dzielniki tak jak pół świata proponuje - bez zmian. Poszukałem w specyfikacjach i odkryłem układ LVC4245 zaprojektowany specjalnie jako translator szyny 3,3V na 5V i na odwrót. Ponieważ układ ma kierunek sterowalny ale tylko dla całości układu dałem takie dwa. Jeden na DO -> MISO i drugi na SCK->CLK, MOSI->DI, CS-CS.

    Wynik ? 100% idealna transmisja. natychmiastowe odpowiedzi kart, bez żadnego czarowania dodatkowymi (oprócz specyfikacji) powtórzeniami rozkazów itp.

    Mam taki wniosek. Jeśli komuś bez tego działało wcześniej to trafił na szczęśliwą kartę.

    BTW. Maxim też produkuje taki bufor i to dwukierunkowy ale jest 10 razy droższy. Nie bez powodu się je produkuje bo do czego by były potrzebne jak by wystarczyło parę oporników ?
  • Poziom 10  
    Mam podobny problem więc pytam: Washu, pomogło wyłącznie skrócenie kabli, czy modyfikacje kodu też? Ja mam takie 20cm + kilka cm ścieżki na płytce. Inizjalizację robie podobnie jak Ty - i też dostaje w odpowiedzi na GO_IDLE_STATE (0x40) - 0x00, więc karty ze mną "gadają". Czasami uda się otrzymać prawidłowe potwierdzenia R1 również na SEND_OP_COND (0x41) i SET_BLOCKLEN (0x50). Ale przeczytanie CID, czy CSD już nie wyrabia.
    Ew. może coś sknociłem w elekrtyce (montaż na płytce uniwersalnej): Karta jest podpięta do ARMA - LPC2138, linie DI, DO, CLK, CS, pady 8 i 9 mam podpięte przez oporniki 10k do +3.3, zasilanie 3.3V(dużo prądu w elektrowni jest, więc na pewno to nie problem zbyt małej jego ilości), spięte do masy przez kondensator 100n, GND tam gdzie ma być, jest, a Write Protected przez rezystor 10k do zasilania.
  • Poziom 27  
    kol washu ten bufor "zmienia" sygnał z TTL 5V na TTL 3,3V więc stosowanie go dla transmisi MOSI,SCK, CS jest jak najbardziej celowe natomiast buforowanie linii MISO nie jest niczym uzasadnione bo zmieniasz poziom sygnału z TTL 3,3 V na TTL 3,3 V. Poziom logicznej 1 i 0 z 3,3 V mieści się w zakresie który zostanie poprawnie interpretowany dla napięć 5V więc buforując tę linię, generalnie niczego nie poprawiłeś a zużyłeś dodatkowy układ scalony a dodatkowo w zależności od sposobu jaki masz rozwiązany dla linii OE i CS dla sterowania buforem od uP, możesz w skrajnie błędnej sytuacji osiągnąć stałe obciążenie linii MISO jakimś poziomem logicznym który to w przypadku używania na magistrali SPI więcej niż 1 urządzenie , spowodować kolizję poziomów logicznych.
  • Poziom 10  
    @Rpal
    Okazuje się że nie prawda. 90% przekłamań, utraty bitów myło na linii MISO. Bufor ma 2 kierunki pracy, zmienia poziom "1" 3,3V na "1" 5V też i empirycznie sprawdziłem że przestało przekłamywać. Jak napisałem Wstępnie użyłem 2 układy na 2 kierunki pracy. Co do kolizji urządzeń na linii: bufor ma sterowanie przejściem w stan wysokiej impedancji. Proponuję datasheeta bo twoje wszystkie wątpliwości są tam opisane.

    @hilbercik
    Wszystko pomagało ale dla paru kart. Dopiero odpowiednie buforowanie uzdrowiło WSZYSTKIE karty, a w międzyczasie nazbierałem ich z 10 sztuk.
    Na GO_IDLE_STATE odpowiedź ma być 0x01 czyli "idle state" potem ma być
    ACMD41. CMD1 czyli SEND_OP_COND nie jest przez producentów polecane.
    SET_BLOCKLEN nie potrzebne bo domyślnie blok i tak jest 512b.

    Wrzucę jutro przetrenowaną przeze mnie procedurę ale teraz i tak jestem przekonany że to sprawa nieprawidłowego narastania sygnałów bez buforowania.
  • Poziom 27  
    washu napisał:
    zmienia poziom "1" 3,3V na "1" 5V też i empirycznie sprawdziłem że przestało przekłamywać.

    Bardzo jestem ciekaw kolego jak uzyskałeś tę zamianę z 3,3 na 5 bo zakładam że zasilasz bufor z napięcia 3,3V, może jakiś schemat ? Datasheet-a umiem mimo wątłych oczu jeszcze przestudiować co i tobie polecam, zwłaszcza analizę tabeli dopuszczalnych napięć wejściowych. Wątpliwości żadnych nie miałem o czym przekonasz się czytając jeszcze raz zdanie warunkowe które wcześniej zamieściłem. Generalnie to się cieszę że ci działa, PZDR
  • Poziom 29  
    washu widzę że też sam próbujesz rozmawiać z kartą zamiast stosować gotowe biblioteki - i bardzo dobrze :)

    Może mi podpowiesz, mi po CMD1 karta zwraca błąd illegal command, nie wiedzieć czemu. Nie wysyłam w tym miejscu prawidłowej CRC, podobno nie trzeba, a po za tym dostał bym wtedy command crc error a nie illegal command. Jest to karta MMC, ACMD41 nie próbowałem wysyłać.

    /edit

    Problem już nieaktualny, miałem literówkę w programie :oops: MMC i SD odpalam i działają ładnie. Odczyt CID i CSD działa, ale... wiele dziwnych akrobacji widziałem, ale takiego liczenia pojemnosci karty na podstawie READ_BL_LEN, C_SIZE i C_SIZE_MULT z rejestru CSD to jeszcze nie 8-O Ktoś się chyba BARDZO nudził, zarwałem nockę a i tak nie udało mi się dojść z tym do ładu. Może bawiłeś się w to?
  • Poziom 10  
    Niestety, zakładam w programie wielkość i to mi wystarcza.
    Można odczytać rejestry a można spróbować odczytać jakieś dalekie dane i sprawdzić odpowiedź. Co do danych z CSD to się nie bawiłem, w CID zawsze jest rozmiar ale trzeba go mądrze interpretować bo to ciąg tekstowy. W 90%
    występuje coś w stylu ...SDNNNXX.... gdzie NNN - rozmiar, XX - jednostka.

    Wracam po urlopie w przyszłym tygodniu i uzupełnię wszystkie niewiadome jeśli będę umiał.


    25.02.2010: Wskoczyłem na chwilę do pracy i jeszcze raz napiszę do rpal
    LVC4245 to driver/bufor szyn o logicznej "1" 5V i 3,3V.
    Układ ma 2 porty, Port A 5V i Port B 3,3V. Kierunek przepływu danych jest ustalany wejściem DIR. Układ zasilany jest z dwóch źródeł zasilania by znać napięcie logicznych "1" po obu stronach. Jeśli ustawię DIR na kierunek B->A to port B działa jako wejście danych z logiczną jedynką 3,3V a port A jako wyjście z logiczną jedynką 5V. (a nie 3,3V). Układ separuje oba swoje napięcia zasilania. W Datashecie nie jest to explicite napisane ale w tabeli napięć stoi: Port B - napięcie typowe Vcc (i chodzi o VccB) Port A napięcie typowe Vcc (i chodzi o VccA)
  • Poziom 10  
    Podobne więc będzie w tym temacie:
    Jak się upewnić że karta wykonała ERASE ?

    Kasuję kartę standartowymi CMD32,CMD33,CMD38.

    1. Czasem na CMD38 dostaję odpowiedź 0xC0, potem 0x00. Tego nie rozumiem zupełnie.
    Mimo szukania po sieci nie zorientowałem się skąd się bierze. Może tam być z tego co
    wiem 0xFF co najwyżej...

    2. Mimo że karta odpowie już 0x00 to jak ją za szybko wyjmę okazyję się być
    nie sformatowana cała a tylko początek. Jak poczekam parę sekund to nie zważając
    na pinkt 1. formatuje się dobrze.
  • Poziom 9  
    Witam,

    funkcja ERASE bardzo mnie interesuje, bo jakoś nie mogę zmusić żadnej karty do porządnego wykasowania się, żeby potem móc pisać do niej szybko, bez konieczności czekania na auto-kasowanie zapisywanych sektorów.
    Czy mógłbyś podać żródło funkcji erase ? Chętnie porównam ze swoją wersją, bo chyba się zaciąłem bezpowrotnie ;-)
  • Poziom 10  
    Code:

    unsigned char SD_erase(uint32_t startBlock, uint32_t totalBlocks)
        {
        unsigned char response;
        uint16_t retry=0;

        response = SD_sendCommand(ERASE_BLOCK_START_ADDR, startBlock<<9);
        if(response != 0x00) return response;

        response = SD_sendCommand(ERASE_BLOCK_END_ADDR,(startBlock + totalBlocks - 1)<<9);
        if(response != 0x00) return response;

        response = SD_sendCommand(ERASE_SELECTED_BLOCKS, 0);

        unselect_card();
        SPI_transmit(0xff);                                                 //just spend 8 clock cycle delay before reasserting the CS line
        select_card();                                                      //re-asserting the CS line to verify if card is still busy

        while(!SPI_receive())                                               //wait for SD card to complete writing and get idle
            if(retry++ > 0xfffe)
                {
                unselect_card();
                return 1;
                }

        return response;
        }

  • Poziom 9  
    Dzięki za podpowiedź, ale niestety jak nie działało tak nie działa. Podobne procedury zresztą już testowałem. Widzę jakąś zależność od producenta karty. Kingston u mnie przynajmniej udaje, że się kasuje, ale skasowany chyba jednak nie jest, za to SunDisk w ogóle nie chce tego polecenia przyjąć.

    Będę próbował dalej, a chwilowo powiększyłem bufor danych dla karty (bo zapisuję dane napływające w czasie rzeczywistym) i to na razie pomogło.