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:
- Erase sequence error – An error in the sequence of erase commands occurred.
- Illegal command – incorrect command code.
- Erase reset – An erase sequence was cleared before executing because an out of erase sequence command was received.
- In idle state – the card is reset and idle.
2
Answers
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:
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:
This is by all means a lousy hack, but if it works...
All of that is inside the SDC_goIdle() function:
I also disabled the speed doubler in SPI settings:
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.I didn’t change any code on
SPI_transfer()
,SDC_command()
andSDC_readRes1()
. I trim down the unnecessary delay before and after selection and deselection of CS pin as those are totally unnecessary. I didn’t callSDC_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.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.