打印

spi在dma模式下读写sd卡

[复制链接]
8203|20
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
jynkelan|  楼主 | 2008-4-23 15:51 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
请教大家,
 /****************************************************************************
Desripton   :read block in dma mode
Fuctionname :ReadBlockInDMA()
Input        :None

Return      :None
*****************************************************************************/
u8 ReadBlockInDMA(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
    //u32 i = 0;
    DMA_InitTypeDef  DMA_InitStructure;
    u8 rvalue = MSD_RESPONSE_FAILURE;
  
    /* MSD chip select low */
    MSD_CS_LOW();
    

    /*initial dma channel 2*/
    DMA_DeInit(DMA_Channel2);

    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)SPI1_DR_Address;
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)pBuffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = NumByteToRead;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA_Channel2, &DMA_InitStructure);
    
    
    SPI_DMACmd(SPI1, SPI_DMAReq_Rx, ENABLE);
   
    /* Send CMD17 (MSD_READ_SINGLE_BLOCK) to read one block */
    
    

    MSD_SendCmd(MSD_READ_SINGLE_BLOCK, ReadAddr, 0xFF);
    if (!MSD_GetResponse(MSD_RESPONSE_NO_ERROR))
    {
    /* Now look for the data token to signify the start of the data */
        
        if (!MSD_GetResponse(MSD_START_DATA_SINGLE_BLOCK_READ))
        {
                

          
            DMA_Cmd(DMA_Channel2,ENABLE);
            
            while(!DMA_GetFlagStatus(DMA_FLAG_TC2))
            {
                
            }

        }    
    }
    /* Get CRC bytes (not really needed by us, but required by MSD) */
  
    MSD_ReadByte();
    MSD_ReadByte();

    /* Set response value to success */
    rvalue = MSD_RESPONSE_NO_ERROR;
    DMA_Cmd(DMA_Channel2, DISABLE);
    /* MSD chip select high */
    MSD_CS_HIGH();

    /* Send dummy byte: 8 Clock pulses of delay */
    MSD_WriteByte(DUMMY);


    /* Returns the reponse */
    return rvalue;
}

/******************************************************************************
Description  : Write byte in dma mode
Fuctionname  : WriteBlockInDMA(u8 *pBuffer,u32 WriteAddr, u16 NumByteToWrite)
Input         : pBuffer -- data buffer for send
               WriteAddr -- address to write
               NumByteToWrite -- data length of the data to write
Output       : None

*********************************************************************************/
u8 WriteBlockInDMA(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
    //u32 i = 0;
    DMA_InitTypeDef  DMA_InitStructure;
    u8 rvalue = MSD_RESPONSE_FAILURE;

    /* MSD chip select low */
    MSD_CS_LOW();

    /*initial the spi1 in write mode*/
    /*initial dma channel 3*/
    DMA_DeInit(DMA_Channel3);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)SPI1_DR_Address;
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)pBuffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = NumByteToWrite;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA_Channel3, &DMA_InitStructure);
    SPI_DMACmd(SPI1, SPI_DMAReq_Tx, ENABLE);
   // SPI_Cmd(SPI1, ENABLE);
    MSD_SendCmd(MSD_SET_BLOCKLEN,512,0x00); 
    MSD_ReadByte();
    MSD_ReadByte();
    /* Send CMD24 (MSD_WRITE_BLOCK) to write multiple block */
    MSD_SendCmd(MSD_WRITE_BLOCK, WriteAddr, 0xFF);

    /* Check if the MSD acknowledged the write block command: R1 response (0x00: no errors) */
    if (!MSD_GetResponse(MSD_RESPONSE_NO_ERROR))
    {
         /* Send a dummy byte */
        MSD_WriteByte(DUMMY);
        /* Send the data token to signify the start of the data */
        MSD_WriteByte(0xFE);
        /* Write the block data to MSD : write count data by block */
        DMA_Cmd(DMA_Channel3, ENABLE);
        while(!DMA_GetFlagStatus(DMA_FLAG_TC3));
        /* Put CRC bytes (not really needed by us, but required by MSD) */
        MSD_ReadByte();
        MSD_ReadByte();
        /* Read data response */
        if (MSD_GetDataResponse() == MSD_DATA_OK)
        {
            rvalue = MSD_RESPONSE_NO_ERROR;
        }
  }
    
    /* MSD chip select high */
    MSD_CS_HIGH();

    /* Send dummy byte: 8 Clock pulses of delay */
    MSD_WriteByte(DUMMY);

    DMA_Cmd(DMA_Channel3, DISABLE);
    /* Returns the reponse */
    return rvalue;
}
  这是分别用dma读写sd卡的读和写一个block的程序,在程序中调用到这俩个程序时,dma读子程序在while(!DMA_GetFlagStatus(DMA_FLAG_TC2)){}这一直等待,也就是说dma传输没有完成。而调用写子程序是完全没问题的,也就是dma时钟等问题以不是问题,不知道还有什么没考虑到的?????
 郁闷!!!!!
       
沙发
Lxueqiang| | 2008-4-23 16:54 | 只看该作者

资料很有用!

珍藏

使用特权

评论回复
板凳
jynkelan|  楼主 | 2008-4-24 11:21 | 只看该作者

版主帮忙啊。。。

 帮主帮忙分析一下,是不是卡的响应时钟与DMA时钟匹配有问题????

使用特权

评论回复
地板
jynkelan|  楼主 | 2008-4-24 14:40 | 只看该作者

我先自己分析一下

  难道是时钟同步问题?因为我们知道读sd卡时每从SPI总线上读一个字节的数据都要先进行时钟同步,这点从MSD_ReadByte()函数可以看出。也就是读一个字节数据前得向spi总线发数据0xff,这是必须的,如果省掉这一步,读数据是不能成功的。问题是不是出在这,也就是说当我们用dma来读SPI->DR中的数据时,这是dma硬件并没有向spi总线发送同步信号,而在SPI->DR中得不到正确的读数而导致dma读取不成功??

使用特权

评论回复
5
jynkelan|  楼主 | 2008-4-24 14:45 | 只看该作者

补充

 而向sd卡写数据时并不需要额外的时钟同步,所以dma写能正确进行。

使用特权

评论回复
6
jynkelan|  楼主 | 2008-4-24 15:54 | 只看该作者

更正

 看来我的理解还是有误,发0xff只是为了启动时钟clock,而实质上dma也是可以启动时钟的。。。。。。问题还是找不到啊。。。。

使用特权

评论回复
7
jynkelan|  楼主 | 2008-4-25 09:01 | 只看该作者

???

???????

使用特权

评论回复
8
lidawei1| | 2008-4-25 16:44 | 只看该作者

读时要用两个DMA通道,一个发,一个收。

不发的话无时钟信号,怎么能收到?

使用特权

评论回复
9
jynkelan|  楼主 | 2008-4-26 12:46 | 只看该作者

不发的话无时钟信号,怎么能收到?

   这个spi的dma应该硬件能开启时钟,也就是说DMA_Cmd(DMA_Channel2,ENABLE);后spi就会有时钟信号,但在我这事实却不是这样,开启dma通道2后spi1并没有时钟信号。????

使用特权

评论回复
10
lidawei1| | 2008-4-26 19:09 | 只看该作者

spi时钟

SPI收发是同时进行的,共用一个时钟,主模式发时会开启时钟,收时不会,与DMA无关。DMA只是在接收缓冲区非空或发送缓冲区空时传数据。主模式SPI数据发送过程从“当一字节写进发送缓冲器时,发送过程开始”,即要启动主模式SPI传输,应有一字节写进发送缓冲器,对于发送的DMA操作,当设置好DMA后,因SPI的txe为1,故会触发DMA传一个字节到DR,此操作会开启主模式SPI的发送及接收过程,故“写子程序是完全没问题的”,但对于读操作,如果只设置读的DMA,因触发读DMA的条件是rxne为1,即必须接收缓冲区非空,此条件在只设置读DMA的情况下无法达到,因没有一个字节写进发送缓冲器,也就没有数据收到了,故出现“dma读子程序在while(!DMA_GetFlagStatus(DMA_FLAG_TC2)){}这一直等待”的情况,可见,要完成读DMA操作,也应先写数据到DR中,可通过同时开启另一个DMA通道用于SPI的发送来完成。

使用特权

评论回复
11
jynkelan|  楼主 | 2008-4-28 09:29 | 只看该作者

我也想到了

    但这样的话也就是在DMA读时,如果我要连续读512字节,也得发512次0xff作为时钟信号。这样用dma还会有速度优势吗?
  

使用特权

评论回复
12
jynkelan|  楼主 | 2008-4-28 09:49 | 只看该作者

看来以前是对stm32spi的dma时钟理解有误

  感谢10楼,程序修改如下,接受成功,但在11楼提到的问题还是可以继续讨论一下。
/****************************************************************************
Desripton   :read block in dma mode
Fuctionname :ReadBlockInDMA()
Input        :None

Return      :None
*****************************************************************************/
u8 ReadBlockInDMA(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
    u32 i = 0;
    DMA_InitTypeDef  DMA_InitStructure;
    u8 rvalue = MSD_RESPONSE_FAILURE;
  
    /* MSD chip select low */
    MSD_CS_LOW();
    

    /*initial dma channel 2*/
    DMA_DeInit(DMA_Channel2);

    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)SPI1_DR_Address;
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)pBuffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = NumByteToRead;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA_Channel2, &DMA_InitStructure);
    
    
    SPI_DMACmd(SPI1, SPI_DMAReq_Rx, ENABLE);
   
    /* Send CMD17 (MSD_READ_SINGLE_BLOCK) to read one block */
    
    

    MSD_SendCmd(MSD_READ_SINGLE_BLOCK, ReadAddr, 0xFF);
    if (!MSD_GetResponse(MSD_RESPONSE_NO_ERROR))
    {
    /* Now look for the data token to signify the start of the data */
        
        if (!MSD_GetResponse(MSD_START_DATA_SINGLE_BLOCK_READ))
        {
                

          
            DMA_Cmd(DMA_Channel2,ENABLE);
            for(i=0 ;i<NumByteToRead; i++)     //发时钟信号,接收同时进  
                                               //行
            {
                SPI1->DR = 0xff;
                while(_SPI_GetFlagStatus(SPI1,SPI_FLAG_TXE) == RESET);
             }
            while(!DMA_GetFlagStatus(DMA_FLAG_TC2))
            {
                
            }

        }    
    }
    /* Get CRC bytes (not really needed by us, but required by MSD) */
  
    MSD_ReadByte();
    MSD_ReadByte();

    /* Set response value to success */
    rvalue = MSD_RESPONSE_NO_ERROR;
    DMA_Cmd(DMA_Channel2, DISABLE);
    /* MSD chip select high */
    MSD_CS_HIGH();

    /* Send dummy byte: 8 Clock pulses of delay */
    MSD_WriteByte(DUMMY);


    /* Returns the reponse */
    return rvalue;
}

使用特权

评论回复
13
lidawei1| | 2008-4-28 15:56 | 只看该作者

速度优势问题

使用DMA并不是为了获得速度,而是充分利用等待的空闲时间,所以我前面说再开启另一个DMA通道用于SPI的发送,发送的同时接收的DMA通道也在工作,完成数据的接收,不需CPU干预,CPU可完成其他任务。要说速度,我就算不用DMA,也照样可在约18026个CPU时钟周期内将512byte数据传到SD卡,或在17234(最快曾达到16401)个CPU时钟周期内将512byte数据从SD卡读到内存,此时速度约为512/(18026/72)=2.049(m/s)、512/(17234/72)=2.13(m/s),已非常接近2.25m/s的理论值。顺便说一下,SD卡的读写速度,除了与此处CPU与SD卡交换数据的速度有关外,还与以下2个操作密切相关:
读时:
    /* Now look for the data token to signify the start of the data */
   if (!MSD_GetResponse(MSD_START_DATA_SINGLE_BLOCK_READ))
写时:
    /* Read data response */
    if (MSD_GetDataResponse() == MSD_DATA_OK)
此2操作花费的时间是惊人的,读时约需等10xxxCPU时钟,接近传512byte数据时间的2/3,写时约需等1xxxxx周期,是传512byte数据时间的10倍以上!仔细思考一下也是合理的,因写时会执行擦除操作,闪存的擦除时间是以ms计的。

使用特权

评论回复
14
jynkelan|  楼主 | 2008-4-28 18:19 | 只看该作者

节约cpu

  楼上的说得很有道理,通过逻辑分析议抓数据后可以看到 
 /* Now look for the data token to signify the start of the data */
   if (!MSD_GetResponse(MSD_START_DATA_SINGLE_BLOCK_READ))
写时:
    /* Read data response */
    if (MSD_GetDataResponse() == MSD_DATA_OK)确实占用较长的时间,不过接近读512字节的2/3时间还得看不同的卡,但是如果在dma开启后有dma控制器产生时钟信号好像很合理吧,因为放开速度不说,这样在dma开启后还得靠cpu发512字节的时钟信号,这样的操作好像并没有节省多少cpu时间,只有在开启dma后由dma控制器主动发时钟信号,这样在读512字节的数据时完全可以不用cpu来干预,因而才能真正地达到用cpu来处理其它任务的目的,不知道芯片设计的时候是怎么考虑的,当然如果存在其它编程算法而不是像12楼上那种方法来实现那到是有可能的?

使用特权

评论回复
15
lidawei1| | 2008-4-28 20:45 | 只看该作者

开两个DMA通道的目的就是为了让CPU放开手脚干其他事而不用

SPI发送时,开DMA通道2用于将数据从内存搬到DR,
SPI接收时,开DMA通道3用于将数据从DR搬到内存,再开DMA通道2用于将DUMMY搬到DR,此2个DMA一开,数据便源源不断从SPI读入内存,不用CPU干预,更不用
            for(i=0 ;i<NumByteToRead; i++)     //发时钟信号,接收同时进  
                                               //行
            {
                SPI1->DR = 0xff;
                while(_SPI_GetFlagStatus(SPI1,SPI_FLAG_TXE) == RESET);
             }

使用特权

评论回复
16
jynkelan|  楼主 | 2008-4-29 09:09 | 只看该作者

明白了

   就是收的时候并不是只开一个通道2,而是通道2和通道3一起开,发送则只开一个通道即可。

使用特权

评论回复
17
jynkelan|  楼主 | 2008-4-29 11:08 | 只看该作者

谢谢,程序如下

u8 ReadBlockInDMA(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
    //u32 i = 0;
    //u16 j = 0; 
    DMA_InitTypeDef  DMA_InitStructure;
    u8 rvalue = MSD_RESPONSE_FAILURE;
   // u16 *Enp1Addr = NULL;   /*断点1地址*/
    /* MSD chip select low */
    MSD_CS_LOW();
    

    /*initial dma channel 2*/
    DMA_DeInit(DMA_Channel2);
    DMA_DeInit(DMA_Channel3);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)SPI1_DR_Address;
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)pBuffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = NumByteToRead;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA_Channel2, &DMA_InitStructure);
    
    
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)DuumyClock;  //512字节的dummy
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
    DMA_Init(DMA_Channel3, &DMA_InitStructure);
    SPI_DMACmd(SPI1, SPI_DMAReq_Tx, ENABLE);
   
    SPI_DMACmd(SPI1, SPI_DMAReq_Rx, ENABLE);
//    DMA_ClearFlag(DMA_FLAG_TC2); 
    /* Send CMD17 (MSD_READ_SINGLE_BLOCK) to read one block */
    
    

    MSD_SendCmd(MSD_READ_SINGLE_BLOCK, ReadAddr, 0xFF);
    if (!MSD_GetResponse(MSD_RESPONSE_NO_ERROR))
    {
    /* Now look for the data token to signify the start of the data */
        
        if (!MSD_GetResponse(MSD_START_DATA_SINGLE_BLOCK_READ))
        {    
                       
            DMA_Cmd(DMA_Channel3,ENABLE);   
            DMA_Cmd(DMA_Channel2,ENABLE);
//             for(i=0; i<NumByteToRead; i++)
//             {  
//                
//                SPI1->DR = DUMMY;
//                while((SPI1->SR&SPI_FLAG_TXE) == (u16)RESET);
               // while(SPI_GetFlagStatus(SPI1,SPI_FLAG_TXE) == RESET);
//                while(SPI_GetFlagStatus(SPI1,SPI_FLAG_RXNE) == RESET)
//                {
//
//                }
//                *pBuffer = SPI1->DR;
//                 pBuffer++;
//             }
//          

            
            while(!DMA_GetFlagStatus(DMA_FLAG_TC3));
            
            while(!DMA_GetFlagStatus(DMA_FLAG_TC2));
            
            DMA_ClearFlag(DMA_FLAG_TC2); 

        }    
    }
    /* Get CRC bytes (not really needed by us, but required by MSD) */
  
    MSD_ReadByte();
    MSD_ReadByte();

    /* Set response value to success */
    rvalue = MSD_RESPONSE_NO_ERROR;
    DMA_Cmd(DMA_Channel2, DISABLE);
    DMA_Cmd(DMA_Channel3, DISABLE);
    /* MSD chip select high */
    MSD_CS_HIGH();

    /* Send dummy byte: 8 Clock pulses of delay */
    MSD_WriteByte(DUMMY);


    /* Returns the reponse */
    return rvalue;
}
 优先级很重要,收应高于发。

使用特权

评论回复
18
lidawei1| | 2008-4-29 12:04 | 只看该作者

那个DUMMY一个就够了

无需512字节,设置为地址不增加

使用特权

评论回复
19
jynkelan|  楼主 | 2008-4-30 09:58 | 只看该作者

实质效果一样

   对,这样简单一点,发送512次就行了,谢谢提醒!

使用特权

评论回复
20
ifree64| | 2008-9-25 22:15 | 只看该作者

好资料,收藏了。

有一个问题请教:

在代码中有
while(!DMA_GetFlagStatus(DMA_FLAG_TC3));
while(!DMA_GetFlagStatus(DMA_FLAG_TC2));
两句,意思是DMA传输如果没有完成就继续等待。那么这样DMA的优势是否就没有体现出来呢?

DMA的本意应该是把CPU从数据传输的忙等待中解放出来,可以腾出时间来做其他事情,但是如果向上面那样使用不是好像就和直接CPU忙等待读取一样了吗?因为while循环使得CPU还是在等着DMA传输完毕。

不知道我说得对不对。是不是DMA要配合操作系统的任务管理加上阻塞才有它该有的性能优势呢?谢谢!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

15

主题

30

帖子

0

粉丝