3

我目前正在努力用非阻塞 DMA 实现替换 SSP 上的 SD 卡驱动程序的阻塞忙等待实现。但是,实际上没有写入任何字节,即使一切似乎都按计划进行(从未发现错误条件)。

首先是一些代码(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_argument,send_commandsemaphore.take())都可以正常工作(其中大部分来自工作中的忙等待 SD 实现。我当然不能保证 100% 没有错误,但它们似乎工作正常。)。
  • 由于我正在调试它,因此有很多printfs 和硬编码的SSP2变量。这些当然是暂时的。
  • 我主要将此用作示例代码。

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

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

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

我的代码有什么问题,或者我在哪里可以找到这个问题的原因?在花了更多时间在这之后,我想承认我真的不知道如何让这个工作,任何帮助表示赞赏!

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

  • 数据实际上是写入 SD 卡。但是,似乎第一个写入的块丢失了。我想在write函数中完成了一些需要更早完成的设置。
  • 字节以非常奇怪(且错误)的顺序放置:我基本上交替所有偶数,然后是所有奇数。因此,我首先得到偶数0x00 - 0xFE,然后是所有奇数0x01 - 0xFF(写入字节的总数似乎是正确的,但缺少的第一个块除外)。然而,这个序列甚至有一个例外:每个块包含 2 个这样的序列(序列是 256 字节,块是 512),但是每个块中的第一个序列已经0xfe0xff交换”了。也就是说,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 作为内存。

4

1 回答 1

1

当我终于拿到逻辑分析仪时,我得到了更多信息,并且能够解决这些问题。

我的代码中有一些小错误,但导致此行为的错误是我没有在0xFE块之前发送“开始块”令牌(堵塞。当我将这些添加到传输缓冲区时,一切都已成功写入!

所以这个修复如下:

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;

    //...
}

附带说明一下,没有写入第一个块的原因仅仅是因为我没有等到设备准备好才发送第一个块。这样做可以解决问题。

于 2014-05-04T18:04:48.123 回答