DMA写入SD卡(SSP)无法写入字节。

3

我目前正在将使用SSP的阻塞忙等待实现SD卡驱动程序替换为非阻塞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();
    }
}

关于代码和设置有几点需要注意:
  • 本文针对搭载FreeRTOS的lpc4088开发板编写
  • 所有的 SD_xxx 定义都是有条件地选择正确的引脚(我需要在我的开发环境中使用SSP2,在最终产品中使用SSP0)
  • 此片段中未定义的所有外部函数(例如 pack_argumentsend_commandsemaphore.take()等)都已知能够正常工作(大多数来自正在工作的忙等SD卡实现。当然,我不能保证它们100%无bug,但它们似乎运行正常)。
  • 因为我正在调试过程中,所以有很多 printf 和硬编码的 SSP2 变量。这些显然只是临时的。
  • 我主要使用了 这个 作为示例代码。

现在我已经尝试了以下几件事:

  • 使用忙等方式而不是DMA进行写入。如前所述,我从一个可以工作的实现开始,因此我知道问题必须出在DMA实现上,而不是其他地方。
  • mem->mem 而不是 mem->sd 进行写入,以消除SSP外设。 mem->mem 工作正常,因此问题必须在DMA设置的SSP部分。
  • 检查是否调用了ISR。它们确实被调用:首先是DMA IRS用于终端计数中断,然后是SSP2 IRS被调用。所以ISR(可能)已经正确设置了。
  • 整体SD卡内容的二进制转储,以查看内容是否可能被写入错误位置。结果:通过DMA发送的内容在SD卡上没有出现过(我对代码进行任何更改都会这样做。没有一个可以将数据写入SD卡)。
  • 在SSP IRS中增加了长时间(~1-2秒)的超时,通过重复请求字节从SD卡中获取字节,以确保没有超时问题(例如,在SD卡有机会处理所有内容之前,我尝试读取字节)。但这并没有改变结果。

不幸的是,由于缺乏硬件工具,我还无法验证字节是否实际发送到数据线上。

我的代码有什么问题,或者我可以在哪里找到此问题的原因?在花费比我愿意承认的时间后,我真的不知道如何使其正常工作,任何帮助都将不胜感激!

更新:我进行了更多的测试,因此得到了更多的结果。以下结果是通过编写4个512字节块获得的。每个块包含不断增加的数字模256。因此,每个块包含从0到255的2个序列。结果如下:

  • Data is actually written to the SD card. However, it seems that the first block written is lost. I suppose there is some setup done in the write function that needs to be done earlier.
  • The bytes are put in a very weird (and wrong) order: I basically get alternating all even numbers followed by all odd numbers. Thus I first get even numbers 0x00 - 0xFE and then all odd numbers 0x01 - 0xFF (total number of written bytes seems to be correct, with the exception of the missing first block). However, there's even one exception in this sequence: each block contains 2 of these sequences (sequence is 256 bytes, block is 512), but the first sequence in each block has 0xfe and 0xff "swapped". That is, 0xFF is the end of the even numbers and 0xFE is the end of the odd series. I have no idea what kind of black magic is going on here. Just in case I've done something dumb here's the snippet that writes the bytes:

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

这里是原始二进制转储文件(链接)。请注意,该精确模式是完全可复制的:到目前为止,每次我尝试时都得到了完全相同的模式。

更新2:不确定是否相关,但我使用sdram进行内存操作。


1
添加了“嵌入式”标签以扩大受众范围。 - Thomas Matthews
1个回答

1
当我终于拿到逻辑分析仪时,我得到了更多信息并能够解决这些问题。
我的代码中有一些小错误,但导致这种行为的错误是我没有在块之前发送“开始块”标记(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;

    //...
}

作为一个旁注,第一个块没有被写出来的原因只是因为我在发送第一个块之前没有等待设备准备好。这样做修复了问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接