我目前正在将使用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_argument
,send_command
,semaphore.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 numbers0x01 - 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 has0xfe
and0xff
"swapped". That is,0xFF
is the end of the even numbers and0xFE
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进行内存操作。