DMA запись на SD-карту (SSP) не записывает байты

В настоящее время я работаю над заменой блокирующей реализации драйвера занятости SD-карты на SSP неблокирующей реализацией DMA. Тем не менее, на самом деле нет записанных байтов, хотя кажется, что все идет по плану (условия ошибок никогда не обнаруживаются).

Сначала немного кода (C ++):

(Отказ от ответственности: я все еще новичок во встроенном программировании, поэтому код, вероятно, не на должном уровне)

namespace SD {
bool initialize() {
//Setup SSP and detect SD card
//... (removed since not relevant for question)

//Setup DMA
LPC_SC->PCONP |= (1UL << 29);
LPC_GPDMA->Config = 0x01;
//Enable DMA interrupts
NVIC_EnableIRQ(DMA_IRQn);
NVIC_SetPriority(DMA_IRQn, 4);
//enable SSP interrupts
NVIC_EnableIRQ(SSP2_IRQn);
NVIC_SetPriority(SSP2_IRQn, 4);
}

bool write (size_t block, uint8_t const * data, size_t blocks) {
//TODO: support more than one block
ASSERT(blocks == 1);

printf("Request sd semaphore (write)\n");
sd_semaphore.take();
printf("Writing to block " ANSI_BLUE "%d" ANSI_RESET "\n", block);

memcpy(SD::write_buffer, data, BLOCKSIZE);//Start the write
uint8_t argument[4];
reset_argument(argument);
pack_argument(argument, block);
if (!send_command(CMD::WRITE_BLOCK, CMD_RESPONSE_SIZE::WRITE_BLOCK, response, argument)){
return fail();
}

assert_cs();
//needs 8 clock cycles
delay8(1);

//reset pending interrupts
LPC_GPDMA->IntTCClear = 0x01 << SD_DMACH_NR;
LPC_GPDMA->IntErrClr = 0x01 << SD_DMACH_NR;

LPC_GPDMA->SoftSReq = SD_DMA_REQUEST_LINES;

//Prepare channel
SD_DMACH->CSrcAddr = (uint32_t)SD::write_buffer;
SD_DMACH->CDestAddr = (uint32_t)&SD_SSP->DR;
SD_DMACH->CLLI = 0;
SD_DMACH->CControl = (uint32_t)BLOCKSIZE
| 0x01 << 26 //source increment
| 0x01 << 31; //Terminal count interrupt

SD_SSP->DMACR = 0x02; //Enable ssp write dma

SD_DMACH->CConfig = 0x1  //enable
| SD_DMA_DEST_PERIPHERAL << 6
| 0x1 << 11 //mem to peripheral
| 0x1 << 14 //enable error interrupt
| 0x1 << 15; //enable terminal count interrupt
return true;
}
}
extern "C" __attribute__ ((interrupt)) void DMA_IRQHandler(void) {
printf("dma irq\n");
uint8_t channelBit = 1 << SD_DMACH_NR;
if (LPC_GPDMA->IntStat & channelBit) {
if (LPC_GPDMA->IntTCStat & channelBit) {
printf(ANSI_GREEN "terminal count interrupt\n" ANSI_RESET);
LPC_GPDMA->IntTCClear = channelBit;
}
if (LPC_GPDMA->IntErrStat & channelBit) {
printf(ANSI_RED "error interrupt\n" ANSI_RESET);
LPC_GPDMA->IntErrClr = channelBit;
}
SD_DMACH->CConfig = 0;

SD_SSP->IMSC = (1 << 3);

}
}

extern "C" __attribute__ ((interrupt)) void SSP2_IRQHandler(void) {
if (SD_SSP->MIS & (1 << 3)) {
SD_SSP->IMSC &= ~(1 << 3);
printf("waiting until idle\n");
while(SD_SSP->SR & (1UL << 4));

//Stop transfer token
//I'm not sure if the part below up until deassert_cs is necessary.
//Adding or removing it made no difference.
SPI::send(0xFD);

{
uint8_t response;
unsigned int timeout = 4096;
do {
response = SPI::receive();
} while(response != 0x00 && --timeout);
if (timeout == 0){
deassert_cs();
printf("fail");
return;
}
}

//Now wait until the device isn't busy anymore
{
uint8_t response;
unsigned int timeout = 4096;
do {
response = SPI::receive();
} while(response != 0xFF && --timeout);
if (timeout == 0){
deassert_cs();
printf("fail");
return;
}
}
deassert_cs();
printf("idle\n");
SD::sd_semaphore.give_from_isr();
}
}

Несколько замечаний о коде и настройке:

  • Написано для lpc4088 с FreeRTOS
  • Все SD_xxx определяет условно определяет, чтобы выбрать правильные выводы (мне нужно использовать SSP2 в моей настройке dev, SSP0 для конечного продукта)
  • Все внешние функции, которые не определены в этом фрагменте (например, pack_argument, send_command, semaphore.take() и т.д.), как известно, работают правильно (большинство из них происходит от работающей реализации SD с занятым ожиданием. Конечно, я не могу на 100% гарантировать, что они без ошибок, но, похоже, они работают правильно.).
  • Так как я нахожусь в процессе отладки, есть много printfс и жестко закодированы SSP2 переменные. Это конечно временно.
  • Я в основном использовал этот в качестве примера кода.

Сейчас я уже попробовал следующие вещи:

  • Пишите без прямого доступа к памяти, используя занятое ожидание по SSP. Как упоминалось ранее, я начал с работающей реализации этого, так что я знаю, что проблема должна быть в реализации DMA, а не где-то еще.
  • Написать от mem->mem вместо mem->sd устранить периферию SSP. mem->mem работал нормально, поэтому проблема должна быть в части SSP установки DMA.
  • Проверяется, называются ли ISR. Они: сначала вызывается IRS DMA для прерывания подсчета терминала, а затем вызывается IRS SSP2. Таким образом, IRS (вероятно) настроены правильно.
  • Сделал двоичный дамп всего содержимого sd, чтобы посмотреть, не было ли это содержимое записано в неправильном месте. Результат: содержимое, отправленное через DMA, не было нигде на SD-карте (я делал это с любым изменением, внесенным в код. Ни один из них не получил данные на SD-карте).
  • Добавлен длинный (~ 1-2 секунды) тайм-аут в IRS SSP, несколько раз запрашивая байты с SD-карты, чтобы убедиться, что не было проблемы тайм-аута (например, я пытался прочитать байты до того, как SD-карта имела возможность обрабатывать все). Это не изменило результат вообще.

К сожалению, из-за отсутствия аппаратных средств я пока не смог проверить, действительно ли байты передаются по линиям данных.

Что не так с моим кодом, или где я могу найти причину этой проблемы? Потратив гораздо больше времени на это, я хотел бы признать, что я действительно не знаю, как заставить это работать, и любая помощь приветствуется!

ОБНОВЛЕНИЕ: я сделал намного больше тестирования, и таким образом я получил еще некоторые результаты. Результаты ниже я получил написав 4 блока по 512 байт. Каждый блок содержит модуль 256 постоянно растущих чисел. Таким образом, каждый блок содержит 2 последовательности, идущие от 0 до 255. Результаты:

  • Данные фактически записываются на SD-карту. Однако, кажется, что первый записанный блок потерян. Я полагаю, что в write функция, которая должна быть сделана ранее.
  • Байты расположены в очень странном (и неправильном) порядке: в основном я чередую все четные числа, за которыми следуют все нечетные числа. Таким образом я сначала получаю четные числа 0x00 - 0xFE а потом все нечетные числа 0x01 - 0xFF (общее количество записанных байтов представляется правильным, за исключением отсутствующего первого блока). Однако в этой последовательности есть даже одно исключение: каждый блок содержит 2 из этих последовательностей (последовательность — 256 байтов, блок — 512), но первая последовательность в каждом блоке имеет 0xfe а также 0xff «Поменялись местами». То есть, 0xFF конец четных чисел и 0xFE это конец нечетного ряда. Я понятия не имею, что за черная магия здесь происходит. На всякий случай я сделал что-то глупое, вот фрагмент кода, который записывает байты:

    uint8_t block[512];
    for (int i = 0; i < 512; i++) {
    block[i] = (uint8_t)(i % 256);
    }
    if (!SD::write(10240, block, 1)) { //this one isn't actually written
    WARN("noWrite", proc);
    }
    if (!SD::write(10241, block, 1)) {
    WARN("noWrite", proc);
    }
    if (!SD::write(10242, block, 1)) {
    WARN("noWrite", proc);
    }
    if (!SD::write(10243, block, 1)) {
    WARN("noWrite", proc);
    }
    

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

Update2: Не уверен, что это актуально, но я использую SDRAM для памяти.

3

Решение

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

В моем коде было несколько небольших ошибок, но ошибка, вызвавшая такое поведение, заключалась в том, что я не отправлял токен «start block» (0xFE) до блока, и я не отправил 16-битный (фиктивный) crc после блока. Когда я добавил их в буфер передачи, все было написано успешно!

Итак, это исправление было следующим:

bool write (size_t block, uint8_t const * data, size_t blocks) {
//TODO: support more than one block
ASSERT(blocks == 1);

printf("Request sd semaphore (write)\n");
sd_semaphore.take();
printf("Writing to block " ANSI_BLUE "%d" ANSI_RESET "\n", block);

SD::write_buffer[0] = 0xFE; //start block

memcpy(&SD::write_buffer[1], data, BLOCKSIZE);

SD::write_buffer[BLOCKSIZE + 1] = 0; //dummy crc
SD::write_buffer[BLOCKSIZE + 2] = 0;

//...
}

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

1

Другие решения


По вопросам рекламы [email protected]