skip to Main Content

I am trying to achieve communication between an AVR128DB28 microcontroller and an SD card over SPI.I’m programming it in Microchip Studio, programming is done through an MPLAB SNAP over UPDI. The program is in C.

My program looks like this:

/*
 * AVR128_sdcardTEST.c
 *
 * Created: 15.04.2024 14:52:31
 * Author : Komputer_3
 */ 

#include <avr/io.h>
#include <avr/delay.h>
#include <stdint-gcc.h>

#define SPI_PORT    PORTA
#define MOSI        PIN4_bm
#define MISO        PIN5_bm
#define SCK         PIN6_bm
#define CS          PIN7_bm

#define CS_ENABLE()     SPI_PORT.OUTCLR = CS
#define CS_DISABLE()    SPI_PORT.OUTSET = CS

#define CMD0        0
#define CMD0_ARG    0x00000000
#define CMD0_CRC    0x94

uint8_t SPI_transfer(uint8_t data){
    SPI0.DATA = data;
    while (!(SPI0.INTFLAGS & SPI_IF_bm));
    return SPI0.DATA;
}

void SDC_powerUp(void){
    CS_DISABLE();
    _delay_ms(1);
    for(uint8_t i=0; i<10; i++){
        SPI_transfer(0xff);
    }
    CS_DISABLE();
    SPI_transfer(0xff);
}

void SDC_command(uint8_t cmd, uint32_t arg, uint8_t crc){
    SPI_transfer((cmd & 127) | 64); //0b01xxxxxx
    
    SPI_transfer((uint8_t)(arg >> 24));
    SPI_transfer((uint8_t)(arg >> 16));
    SPI_transfer((uint8_t)(arg >> 8));
    SPI_transfer((uint8_t)(arg));
    
    SPI_transfer(crc | 1);  //0bxxxxxxx1
}

uint8_t SDC_readRes1(){
    uint8_t i = 0, res1;
    // keep polling until actual data received
    while((res1 = SPI_transfer(0xFF)) == 0xFF){ // ERROR: returns 0 instead of 1
        i++;

        // if no data received for 8 bytes, break
        if(i > 8) break;
    }

    return res1;
}

uint8_t SD_goIdle(){
    // assert chip select
    SPI_transfer(0xFF);
    CS_ENABLE();
    SPI_transfer(0xFF);

    // send CMD0
    SDC_command(CMD0, CMD0_ARG, CMD0_CRC);

    // read response
    uint8_t res1 = SDC_readRes1();

    // deassert chip select
    SPI_transfer(0xFF);
    CS_DISABLE();
    SPI_transfer(0xFF);

    return res1;
}

int main(void)
{
    _PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA,  (CLKCTRL.OSCHFCTRLA & (~CLKCTRL_FRQSEL_gm)) | CLKCTRL_FRQSEL_24M_gc);
    
    SPI_PORT.DIRSET = MOSI | SCK | CS;
    SPI_PORT.DIRCLR = MISO;
    
    PORTD.DIRSET = PIN1_bm | PIN2_bm | PIN3_bm | PIN4_bm | PIN5_bm;
    
    SPI0.CTRLA =    (1 << SPI_MASTER_bp)    |       // Tryb Master
                    (0 << SPI_DORD_bp)      |       /* kierunek wysylania bitow: 0-MSB, 1-LSB */
                    (1 << SPI_CLK2X_bp)     |       /* podwojenie predkosci */
                    SPI_PRESC_DIV4_gc       |       /* Preskaler */
                    SPI_ENABLE_bm;                  /* wlacz SPI */
    SPI0.CTRLB = SPI_SSD_bm | SPI_MODE_0_gc;
    /* Select Slave Disable pin is set, according to he docs I linked, 
    when you are not using multi-master mode. Since I'm not, the CS/SS 
    pin is not under control of the SPI register and instead I control 
    it by manually changing the pins state: the CS_ENABLE and DISABLE 
    are #defined as PORTA.OUTSET = PIN7_bm and opposite */
    
    _delay_ms(500);
    
    SDC_powerUp();
    uint8_t res1 = SD_goIdle();
    if(res1 == 1){
        PORTD.OUTSET = PIN5_bm;
    }
    else{
        PORTD.OUTSET = res1 & 0b00001111;
        _delay_ms(1500);
        PORTD.OUTCLR = 0b00001111;
        PORTD.OUTSET = (res1 >> 4) & 0b00001111;
    }
    
    while (1) 
    {
    }
}


It’s an adapted version of this website’s code:
http://www.rjhcoding.com/avrc-sd-interface-1.php

This here is the problem part:

int8_t SDC_readRes1(){
    uint8_t i = 0, res1;
    // keep polling until actual data received
    while((res1 = SPI_transfer(0xFF)) == 0xFF){ // ERROR: returns 0 instead of 1
        i++;

        // if no data received for 8 bytes, break
        if(i > 8) break;
    }

    return res1;
}

In short, sending the CMD0 command to the SD card, which changes the card’s state into Idle, should produce an R1 response format with the value of 0x01, or 0b00000000. the leftmost bit is always 0, rightmost one is "Is idle state", and the rest are error flags. The return is 0.

In case you need it, here’s a link to the micro’s datasheet:
https://www.microchip.com/en-us/product/avr128db28#document-table (it’s a link to a site with pdf download)

EDIT:

After a power on reset (unplugging and plugging to power) the card responds with 0b00010111. From left to right, the set flags are:

  1. Erase sequence error – An error in the sequence of erase commands occurred.
  2. Illegal command – incorrect command code.
  3. Erase reset – An erase sequence was cleared before executing because an out of erase sequence command was received.
  4. In idle state – the card is reset and idle.

2

Answers


  1. Chosen as BEST ANSWER

    I have pseudo-fixed the issue, if you encounter the same problem the chances of this being the fix are, in my opinion, slim to none. Nonetheless, here's what I did:

    Signal spy

    Using a logic analyzer I discovered that while the probe from SDC_readRes1() returns 0x3F, an SPI_transfer(0xFF) call after it generates a 0x01 response. So, I changed the SDC_readRes1() to this:

    uint8_t SDC_readRes1(){
        uint8_t i = 0, res1;
        // keep polling until actual data received
        while((res1 = SPI_transfer(0xFF))/* == 0xFF*/){ // commented out the == 0xFF
            i++;
            if(res1 == 0x01){
                return res1;
            }
            // if no data received for 8 bytes, break
            if(i > 8) break;
        }
    
        return res1;
    }
    

    This is by all means a lousy hack, but if it works...
    All of that is inside the SDC_goIdle() function:

    uint8_t SD_goIdle(){
        // assert chip select
        SPI_transfer(0xFF);
        CS_ENABLE();
        SPI_transfer(0xFF);
    
        // send CMD0
        SDC_command(CMD0, CMD0_ARG, CMD0_CRC);
    
        // read response
        uint8_t res1 = SDC_readRes1();
    
        // deassert chip select
        SPI_transfer(0xFF); // This is what showed me the skipped 0x01 response
        CS_DISABLE();
        SPI_transfer(0xFF);
    
        return res1;
    }
    

    I also disabled the speed doubler in SPI settings:

        SPI0.CTRLA =    (1 << SPI_MASTER_bp)    |       // Tryb Master
                        (0 << SPI_DORD_bp)      |       /* kierunek wysylania bitow: 0-MSB, 1-LSB */
                        (0 << SPI_CLK2X_bp)     |       /* Speed Doubler */
                        SPI_PRESC_DIV4_gc       |       /* Preskaler */
                        SPI_ENABLE_bm;                  /* wlacz SPI */
    

  2. I couldn’t reproduced what you experienced. I don’t have an AVR128DB28, but I run your code on an ATtiny3227, running at clock speed of 20MHz and 3.3V (as SD card is 3.3V device) and SPI is clocked at 5MHz. I’m able to get the R1 return with 0x01 consistently. Here is the screen capture of the output from a logic analyser.

    SPI data capture

    I didn’t change any code on SPI_transfer(), SDC_command() and SDC_readRes1(). I trim down the unnecessary delay before and after selection and deselection of CS pin as those are totally unnecessary. I didn’t call SDC_powerUp() as there is already a _delay_ms(500) from power-up and it more than sufficient for the voltage to go up to the level for sending CMD0. Overall I have more tighten timing than your code.

    #include <avr/io.h>
    #include <util/delay.h>
    
    #define CMD0        0
    #define CMD0_ARG    0x00000000UL
    #define CMD0_CRC    0x94
    
    static uint8_t SPI_master_init() {
    
        PORTA.OUTSET = PIN4_bm;                     // Set SS pin high to prevent accidential trigger
        PORTA.DIR |= (PIN1_bm | PIN3_bm | PIN4_bm); // Set MOSI, SCK, and SS as OUTPUT
        PORTA.DIRCLR = PIN2_bm;                     // Set MISO as INPUT
        SPI0.CTRLA = 
            SPI_MASTER_bm |           // Master mode
            SPI_PRESC_DIV4_gc |       // SPI_CLOCK = F_CPU / 4
            SPI_ENABLE_bm &           // Enable SPI
            (~SPI_DORD_bm);           // MSB first
        SPI0.CTRLB = SPI_MODE_0_gc;   // no buffer, SSD bit disabled, mode 0
    
    }
    
    uint8_t SPI_transfer(uint8_t data)
    {
      // same as OP's code
    }
    
    void SDC_command(uint8_t cmd, uint32_t arg, uint8_t crc){
        // same as OP's code
    }
    
    uint8_t SDC_readRes1(){
        // same as OP's code 
    }
    
    int main() {
    
        _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, !CLKCTRL_PEN_bm);    // disable prescaler
    
        SPI_master_init();
        
        _delay_ms(250);
    
        PORTA.OUTCLR = PIN4_bm;                 // enable chip select
        SDC_command(CMD0, CMD0_ARG, CMD0_CRC);  // send soft-reset cmd0
        uint8_t res1 = SDC_readRes1();          // get r1 reply
        PORTA.OUTSET = PIN4_bm;                 // disable chip select
    
        while(1) {
    
        }
    
        return 0;
    
    }
    

    One thing I noticed that the SD card connection diagram on http://www.rjhcoding.com/avrc-sd-interface-1.php shown an SD with 8-pin which is quite misleading if not totally wrong, SD card has 9-pin and the correct connection should be as shown in http://elm-chan.org/docs/mmc/mmc_e.html#pinout.

    The SD Card that I used for the test is a quite old Kingston 4GB SDHC Class 4.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search