我目前正致力于通过非阻塞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();
}
}
关于代码和设置的一些评论:
SD_xxx
定义都是条件定义,用于选择正确的引脚(我需要在我的开发设置中使用SSP2,最终产品使用SSP0)pack_argument
,send_command
,semaphore.take()
等)已知正常工作(其中大部分来自工作繁忙等待SD实施。我当然不能100%保证它们没有漏洞,但它们似乎正常工作。) printf
和硬编码SSP2
变量。这些当然是暂时的。现在我已经尝试了以下事项:
mem->mem
而不是mem->sd
写信以消除SSP外围设备。 mem->mem
工作正常,因此问题必须出在DMA设置的SSP部分。不幸的是,由于缺少硬件工具,我还没有能够验证字节是否实际通过数据线发送。
我的代码有什么问题,或者我在哪里可以找到导致此问题的原因?花了更多的时间在这之后,我想承认我真的不知道如何让这个工作,任何帮助表示赞赏!
更新:我做了更多的测试,因此我得到了更多的结果。下面的结果我写了4个512字节的块。每个块包含不断增加的数字模块256.因此每个块包含从0到255的2个序列。结果:
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);
}
here是原始二进制转储。请注意,这个确切的模式是完全可重现的:到目前为止,每次我尝试这个时,我都得到了完全相同的模式。
Update2:不确定它是否相关,但我使用sdram作为内存。
答案 0 :(得分:1)
当我终于掌握逻辑分析仪时,我获得了更多信息,并且能够解决这些问题。
我的代码中有一些小错误,但导致此行为的错误是我没有发送&#34;启动块&#34;在块之前令牌(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;
//...
}
作为旁注,第一个块未被写入的原因仅仅是因为我在发送第一个块之前没有等到设备准备就绪。这样做可以解决问题。