本文对STM32的SPI3读写SD卡进行讨论,读写使用直接模式,DMA模式,和DMA中断模式,并尝试对各种模式进行比较 一般来讲,使用DMA模式的优点在于,节省主程序资源,在直接对CPU/MCU编程的环境中,DMA模式类似于线程的效果。尤其在对实时处理比较严格的环境,如视频播放或采集,DMA会发挥重要作用,主程序更着眼于进行资源分配和流程控制。
使用SPI的DMA功能,需要首先对相关寄存器初始化 SPI3使用DMA2的Channel1接收数据,Channel2发送数据,(参照STM32参考手册) rcc->ahbenr |= 0x02; //enable dma2 spi3->cr2 = 0x3; // enable spi3 dma 使用DMA的中断功能,需要对NVIC和系统向量表vtor初始化 这里只使用SPI3接受中断,DMA2的Channel1,IRQ56 nvic->iser.at[1] = 0x01000000; //enable IRQ56 scb->vtor = (unsigned int)(&__Vectors) & 0x3FFFFF80;
直接读写模式的特点就是结构简单,易于调试,但数据传输效率相对较低,并且完全占用主程序资源 SPI操作MOSI写数据的同时采样MISO,所以读写操作是同一个函数 unsigned char sd_bytedata(unsigned char cmd) { unsigned char output; while (((spi3->sr)&0x2)==0) {} //TXE empty spi3->dr = cmd; while (((spi3->sr)&0x1)==0) {} //RXNE empty output = (unsigned char)(spi3->dr); return output; }
DMA模式的优点在于,释放主程序资源,并且读取效率高,但相对设定复杂,调试困难 基于DMA的参数特性,可以将SPI的读写操作分开处理,以节省资源 DMA接受,读操作,接收缓存的内存指针自动增移,发送缓存为固定,数据长度相同 dma2->ch1.ccr = 0x0080; //MINC:yes P->M dma2->ch1.cndtr = cnt; dma2->ch1.cpar = (unsigned int)&(spi3->dr); dma2->ch1.cmar = (unsigned int)rtn; dma2->ch2.ccr = 0x0010; //MINC:no M->P dma2->ch2.cndtr = cnt; dma2->ch2.cpar = (unsigned int)&(spi3->dr); dma2->ch2.cmar = (unsigned int)cmd; DMA发送,写操作,发送缓存的内存指针自动增移,接收缓存为固定,数据长度相同 dma2->ch1.ccr = 0x0000; //MINC:no P->M dma2->ch1.cndtr = cnt; dma2->ch1.cpar = (unsigned int)&(spi3->dr); dma2->ch1.cmar = (unsigned int)rtn; dma2->ch2.ccr |= 0x0090; //MINC:yes M->P dma2->ch2.cndtr = cnt; dma2->ch2.cpar = (unsigned int)&(spi3->dr); dma2->ch2.cmar = (unsigned int)cmd; 主程序通过监视DMA的EVENT事件标志进行流程控制 while ((dma2->isr & 0x20) == 0); //check finish event while ((dma2->isr & 0x02) == 0); //check finish event 停止DMA,清除事件控制器,消除对下次操作影响 dma2->ifcr = 0xFF; //clear event
中断模式可以在中断函数中加入流程控制逻辑,更多节省主程序资源,对于像SD卡操作逻辑,在没有SDIO专用控制器帮助的情况下,DMA中断模式似乎是必须的选择。但程序难度增加,调试更加困难,尤其在纠错处理方面,需要更大的精力投入。 中断函数由于没有参数,犹如数据缓冲,及控制变量最好设置成全局变量 使用中断函数不再监视事件寄存器,流程控制可以使用用户变量,定义各个状态变量 unsigned int dma_inv_state; #define SD_DMA_DAT_ONCE 0x1 #define SD_DMA_RESPONSE 0x2 #define SD_DMA_DATATOKEN 0x4 #define SD_DMA_READDATA 0x10 #define SD_DMA_WRITEDATA 0x20 #define SD_DMA_COMPLETE 0x100 DMA读写操作前对中断标志设置 if (dma_inv_state) { dma2->ch1.ccr |= 0x2; // interrupt setup } 中断处理程序首先等待发送结束,然后清除事件,否则中断会不断被触发 然后可以进行用户的流程操作 while ((dma2->isr & 0x20) == 0); //check finish event channel2 sd_stop_dma(); if (dma_inv_state & SD_DMA_RESPONSE) { } else if (dma_inv_state & SD_DMA_DATATOKEN) { } else if (dma_inv_state & SD_DMA_READDATA) { } else if (dma_inv_state & SD_DMA_COMPLETE) { } 主程序中对状态变量进行监控 while (dma_inv_state & SD_DMA_DAT_ONCE);
本程序实现DMA对双缓冲的读入,读入SD卡中的所有页,主程序对缓存页执行CRC校对 while ((ii<sd_inf->page)||(cc+ee<ii)) { if (buf_free) { rtn = sd_read_pagedata_dma_inv_sss(addr, dbf, 514); ii++; } } if (buf_done) { if (buf_done&0x1) { dbf = common_buf1; } else { dbf = common_buf2; } jj = crc16(dbf, 512); if (((jj>>8)==dbf[512])&&((jj&0xFF)==dbf[513])) { } buf_free |= (buf_done&0x1)?0x1:0x2; buf_done &= (buf_done&0x1)?(~0x1):(~0x2); } if (buf_proc) { if (dma_inv_state == 0x0) { buf_done = buf_proc; buf_proc = 0x0; } } } |