Не удалось установить связь AVR128DB28 с SD-картой через SPI

Я пытаюсь установить связь между микроконтроллером AVR128DB28 и SD-картой через SPI. Я программирую его в Microchip Studio, программирование выполняется через MPLAB SNAP через UPDI. Программа написана на языке C.

Моя программа выглядит так:

/*
 * 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) 
    {
    }
}


Это адаптированная версия кода этого сайта: http://www.rjhcoding.com/avrc-sd-interface-1.php
Вот проблема:

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

Короче говоря, отправка команды CMD0 на SD-карту, которая переводит состояние карты в состояние ожидания, должна привести к формату ответа R1 со значением 0x01 или 0b00000000. самый левый бит всегда равен 0, самый правый — «Состояние простоя», а остальные — это флаги ошибок. Возврат равен 0.

Если вам это нужно, вот ссылка на техническое описание микро: https://www.microchip.com/en-us/product/avr128db28#document-table (это ссылка на сайт с возможностью скачивания в формате pdf)

Обновлено:

После сброса при включении питания (отключение и подключение к источнику питания) карта отвечает 0b00010111. Слева направо установленные флаги:

  1. Ошибка последовательности стирания. Произошла ошибка в последовательности команд стирания.
  2. Недопустимая команда – неверный код команды.
  3. Сброс стирания — последовательность стирания была удалена перед выполнением, поскольку была получена команда об окончании последовательности стирания.
  4. В состоянии ожидания – карта сброшена и простаивает.

Вы не описали проблему и не задали вопрос. В любом случае, SPI0.CTRLB = SPI_SSD_bm | SPI_MODE_0_gc; мне кажется неправильным: при установке SPI_SSD_bm он активирует «Отключение выбора подчиненного устройства», что означает, что он НЕ использует контакт CS, но ваш код, очевидно, использует CS_ENABLE.

hcheung 20.04.2024 04:41

@hcheung SS Disable — это когда соответствующий вывод SS является входом и предотвращает случайное переключение LOW в режим SLAVE. Как результат это не имеет значения. Тем не менее, было бы неплохо прочитать ошибки, я думаю, что это не сработало, и решение заключалось в использовании только вывода SS в качестве вывода (как в старых AVR).

KIIV 20.04.2024 16:19

Пожалуйста, отредактируйте свой вопрос, чтобы уточнить и добавить новую информацию. Комментарии не для этого, это не форум.

the busybee 22.04.2024 21:29
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
95
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я не смог воспроизвести то, что вы испытали. У меня нет AVR128DB28, но я запускаю ваш код на ATtiny3227, работающем на тактовой частоте 20 МГц и 3,3 В (поскольку SD-карта является устройством с напряжением 3,3 В), а SPI работает на частоте 5 МГц. Я могу стабильно получать возврат R1 с помощью 0x01. Вот снимок экрана вывода логического анализатора.

Я не менял код на SPI_transfer(), SDC_command() и SDC_readRes1(). Я сокращаю ненужную задержку до и после выбора и отмены выбора контакта CS, поскольку они совершенно не нужны. Я не вызывал SDC_powerUp(), так как _delay_ms(500) уже есть при включении питания и этого более чем достаточно, чтобы напряжение поднялось до уровня для отправки CMD0. В целом у меня более сжатые сроки, чем у вашего кода.

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

}

Я заметил одну вещь: схема подключения SD-карты на http://www.rjhcoding.com/avrc-sd-interface-1.php показывает SD с 8-контактным разъемом, что вводит в заблуждение, если не совсем неверно, SD Карта имеет 9-контактный разъем, и правильное подключение должно быть таким, как показано в http://elm-chan.org/docs/mmc/mmc_e.html#pinout.

SD-карта, которую я использовал для теста, — довольно старая Kingston 4 ГБ SDHC Class 4.

Если вы не вызывали SDC_powerUp(), то как ваша карта получила 74 фиктивных тактовых сигнала, необходимые перед использованием? Или они оказались ненужными?

n0rmi 29.04.2024 09:01

Нет такой необходимости, я не знаю, зачем автор блога это делает. Прочтите техническое описание и спецификацию, не доверяйте на 100% тому, что видели в Интернете....

hcheung 29.04.2024 13:26

Я прочитал это Руководство по эксплуатации SD-карты, на странице 22, таблица 4.6, «Время настройки входного напряжения» составляет максимум 250 мс. Судя по всему, он имеет те же характеристики, что и карта Sandisk.

hcheung 29.04.2024 13:30
Ответ принят как подходящий

Я псевдо-исправил проблему, и если вы столкнулись с той же проблемой, шансы на то, что это будет исправление, на мой взгляд, практически равны нулю. Тем не менее, вот что я сделал:

Используя логический анализатор, я обнаружил, что хотя зонд из SDC_readRes1() возвращает 0x3F, вызов SPI_transfer(0xFF) после него генерирует ответ 0x01. Итак, я изменил SDC_readRes1() на это:

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

Это, конечно, паршивый хак, но если он сработает...
Все это находится внутри функции SDC_goIdle():

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

Я также отключил удвоитель скорости в настройках SPI:

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

0x3F означает, что срабатывают все флаги ошибок. Просто игнорируя возвращаемое значение 0x3F, вы игнорируете ошибку. Кроме того, SDC_Command отправила CMD+4 байта+CRC, но, глядя на ваш захват данных, она отправила CMD+9 байт+CRC плюс ведущий 0x00 перед CMD, как это возможно? Вам нужно проверить одну вещь (о которой вы никогда не упоминали): какое напряжение вы подаете на AVR128DB28? а на какой тактовой частоте у тебя работает AVR128DB28? SD-карта должна иметь напряжение 3,3 В, поэтому вам нужно запустить AVR128DB28 с напряжением 3,3 В (и на тактовой частоте, на которой он может работать при напряжении 3,3 В).

hcheung 30.04.2024 02:17

@hcheung WaveForms (программа для Analog Discovery 2, которую я использую) показывает связь SPI следующим образом: masterByte1 | подчиненныйByte1Response, masterByte2 | подчиненныйByte2Reponse... и так далее. <br>Что касается части «игнорирования флагов ошибок», то если этот сигнал 0x3F действительно был флагами ошибок, то следующий сигнал опроса 0xFF будет генерировать ответ либо 0x00, либо 0xFF, а не 0x01.

n0rmi 30.04.2024 08:19

Другие вопросы по теме