Я пытаюсь установить связь между микроконтроллером 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. Слева направо установленные флаги:
@hcheung SS Disable — это когда соответствующий вывод SS является входом и предотвращает случайное переключение LOW в режим SLAVE. Как результат это не имеет значения. Тем не менее, было бы неплохо прочитать ошибки, я думаю, что это не сработало, и решение заключалось в использовании только вывода SS в качестве вывода (как в старых AVR).
Пожалуйста, отредактируйте свой вопрос, чтобы уточнить и добавить новую информацию. Комментарии не для этого, это не форум.
Я не смог воспроизвести то, что вы испытали. У меня нет 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 фиктивных тактовых сигнала, необходимые перед использованием? Или они оказались ненужными?
Нет такой необходимости, я не знаю, зачем автор блога это делает. Прочтите техническое описание и спецификацию, не доверяйте на 100% тому, что видели в Интернете....
Я прочитал это Руководство по эксплуатации SD-карты, на странице 22, таблица 4.6, «Время настройки входного напряжения» составляет максимум 250 мс. Судя по всему, он имеет те же характеристики, что и карта Sandisk.
Я псевдо-исправил проблему, и если вы столкнулись с той же проблемой, шансы на то, что это будет исправление, на мой взгляд, практически равны нулю. Тем не менее, вот что я сделал:
Используя логический анализатор, я обнаружил, что хотя зонд из 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 WaveForms (программа для Analog Discovery 2, которую я использую) показывает связь SPI следующим образом: masterByte1 | подчиненныйByte1Response, masterByte2 | подчиненныйByte2Reponse... и так далее. <br>Что касается части «игнорирования флагов ошибок», то если этот сигнал 0x3F действительно был флагами ошибок, то следующий сигнал опроса 0xFF будет генерировать ответ либо 0x00, либо 0xFF, а не 0x01.
Вы не описали проблему и не задали вопрос. В любом случае,
SPI0.CTRLB = SPI_SSD_bm | SPI_MODE_0_gc;
мне кажется неправильным: при установке SPI_SSD_bm он активирует «Отключение выбора подчиненного устройства», что означает, что он НЕ использует контакт CS, но ваш код, очевидно, использует CS_ENABLE.