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:
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:
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 */