STM32L4 SPI HAL_SPI_TransmitReceive函数bug与接收用上DMA的方法

2019-07-20 02:11发布

本帖最后由 huohua1991 于 2019-4-17 17:05 编辑

昨天我发表的 http://www.openedv.com/forum.php ... 1676&extra=page%3D1 帖子中
痛陈STM32L4 SPI总线的坑,今天发现HAL库的SPI HAL_SPI_TransmitReceive函数的bug,
此函数原型是HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout)
在这函数中
[mw_shl_code=c,true]
if ((pTxData == NULL) || (pRxData == NULL) || (Size == 0U))
{
    errorcode = HAL_ERROR;
    goto error;
}
[/mw_shl_code]
就是说在Size大于0的情况下,pTxData和pRxData两者都不为空才能执行。之所以是这样,是因为SPI作为主端接收一字节数据之前一定要发送一字节效数据,
否则就接收不到(SPI状态寄存器SR的RXNE不置位),只能解释SPI作为主端时只能向DR寄存器写数据才能启动总线时钟。因此我用LL库重写此函数:
[mw_shl_code=c,true]
void SD_SPI_WriteReadBuffer(unsigned char *pu8TxBuf,unsigned short u16TxLen,unsigned char *pu8RxBuf,unsigned short u16RxLen)
{
    unsigned char txen = 0U, rxen = 0U;
    if(((pu8TxBuf == (void*)0) && (pu8RxBuf == (void*)0)) ||
       ((u16TxLen == 0U) && (u16RxLen == 0U))){
        return;
    }
   
    if(pu8TxBuf != (void*)0){
        txen = 1U;
    }
   
    if(pu8RxBuf != (void*)0){
        rxen = 1U;
        if(u16RxLen > 1){
            LL_SPI_SetRxFIFOThreshold(SD_SPI_Periph,LL_SPI_RX_FIFO_TH_HALF);
        }
    }
   
    while((u16TxLen > 0U) || (u16RxLen > 0U)){
        while(!(SD_SPI_Periph->SR & SPI_SR_TXE));
        if(txen == 1U){
            if(u16TxLen == 0){
                SD_SPI_Periph->DR = (unsigned short)0xFFFF;
                txen = 0;
            }else if(u16TxLen > 1U){
                 SD_SPI_Periph->DR = *((unsigned short*)pu8TxBuf);
                 pu8TxBuf += sizeof(unsigned short);
                 u16TxLen -= 2U;
            }else{
                 *((volatile unsigned char*)&SD_SPI_Periph->DR) = *pu8TxBuf;
                 pu8TxBuf++;
                 u16TxLen--;
            }
        }else{
            SD_SPI_Periph->DR = (unsigned short)0xFFFF;
        }
        
        if(rxen == 1U){
            while(!(SD_SPI_Periph->SR & SPI_SR_RXNE));
            if(u16RxLen == 0){
                (void)(*((volatile unsigned char *)&SD_SPI_Periph->DR));
                rxen = 0;
            }else if(u16RxLen > 1U){
                *((unsigned short*)pu8RxBuf) = (unsigned short)(SD_SPI_Periph->DR);
                pu8RxBuf += sizeof(unsigned short);
                u16RxLen -= 2U;
                if(u16RxLen <= 1){
                    LL_SPI_SetRxFIFOThreshold(SD_SPI_Periph,LL_SPI_RX_FIFO_TH_QUARTER);
                }
            }else{
                *pu8RxBuf = (unsigned char)(SD_SPI_Periph->DR);
                pu8RxBuf++;
                u16RxLen--;
            }
        }
    }
   
    while(LL_SPI_GetTxFIFOLevel(SD_SPI_Periph) != LL_SPI_TX_FIFO_EMPTY);
    while(LL_SPI_IsActiveFlag_BSY(SD_SPI_Periph));
    while(LL_SPI_GetRxFIFOLevel(SD_SPI_Periph) != LL_SPI_RX_FIFO_EMPTY){
        (void)(*((volatile unsigned char *)&SD_SPI_Periph->DR));
    }
}
[/mw_shl_code]
这样发送缓存和接收缓存两者有一为空也正常执行传输操作。
顺着此思路便能解决SPI接收时用上DMA功能,就是SPI用DMA接收数据时,同时启用SPI DMA发送数据(只是发送无效数据)。
以下是我在潘多拉板子的SD SPI驱动增加DMA功能的读块操作(使用到LL库):
[mw_shl_code=c,true]
#define SD_USE_DMA       1
static unsigned char su8TmpBuffer[512] = {0};
static unsigned char su8CmdBuffer[8] = {0};


static unsigned char SD_SPI_WriteReadByte(unsigned char u8Byte)
{
    unsigned char res = 0U;
    while(!(SD_SPI_Periph->SR & SPI_SR_TXE));
    *((volatile unsigned char*)&SD_SPI_Periph->DR) = u8Byte;
    while(!(SD_SPI_Periph->SR & SPI_SR_RXNE));
    res = (unsigned char)(SD_SPI_Periph->DR);
    while(LL_SPI_GetTxFIFOLevel(SD_SPI_Periph) != LL_SPI_TX_FIFO_EMPTY);
    while(LL_SPI_IsActiveFlag_BSY(SD_SPI_Periph));
    while(LL_SPI_GetRxFIFOLevel(SD_SPI_Periph) != LL_SPI_RX_FIFO_EMPTY){
        (void)(*((volatile unsigned char *)&SD_SPI_Periph->DR));
    }
    return res;
}

SD_Error SD_ReadBlock(unsigned int u32Sector,unsigned char *pu8Buff,unsigned short u16BlockSize)
{
    unsigned int u32Cnt;
    SD_Error status = SD_RESPONSE_FAILURE;

    if((pu8Buff == (void*)0) || (u16BlockSize == 0)){
        return SD_RESPONSE_NO_ERROR;
    }
    if(u16BlockSize > 512U){
        return SD_DATA_OTHER_ERROR;
    }
    if(su8SDCardType != SD_CARD_TYPE_V2HC){
        u32Sector <<= 9;
    }
#if (SD_USE_DMA)
    memset(su8TmpBuffer,SD_DUMMY_BYTE,u16BlockSize);
    LL_DMA_DisableChannel(SD_SPI_DMA,SD_SPI_DMA_RX_CHANNEL);
    LL_DMA_DisableChannel(SD_SPI_DMA,SD_SPI_DMA_TX_CHANNEL);
    LL_DMA_SetMemoryAddress(SD_SPI_DMA,SD_SPI_DMA_RX_CHANNEL,(unsigned int)pu8Buff);
    LL_DMA_SetMemoryAddress(SD_SPI_DMA,SD_SPI_DMA_TX_CHANNEL,(unsigned int)su8TmpBuffer);
    LL_DMA_SetDataLength(SD_SPI_DMA,SD_SPI_DMA_RX_CHANNEL,u16BlockSize);
    LL_DMA_SetDataLength(SD_SPI_DMA,SD_SPI_DMA_TX_CHANNEL,u16BlockSize);
#endif
    su8CmdBuffer[0] = (unsigned char)(SD_CMD_READ_SINGLE_BLOCK | 0x40); //CMD
    su8CmdBuffer[1] = (unsigned char)(u32Sector >> 24); //Arg[0]
    su8CmdBuffer[2] = (unsigned char)(u32Sector >> 16); //Arg[1]
    su8CmdBuffer[3] = (unsigned char)(u32Sector >> 8); //Arg[2]
    su8CmdBuffer[4] = (unsigned char)(u32Sector & 0xFF); //Arg[3]
    su8CmdBuffer[5] = 0xFF; //CRC
    SD_CS_High;
    SD_SPI_WriteReadByte(SD_DUMMY_BYTE);
    SD_CS_Low;
    SD_SPI_WriteReadBuffer(su8CmdBuffer,6,(void*)0,0);
    u32Cnt = 0xFFF;
    while((SD_SPI_WriteReadByte(SD_DUMMY_BYTE) != SD_RESPONSE_NO_ERROR) && u32Cnt){
        u32Cnt--;
    }
    if(u32Cnt != 0){
        u32Cnt = 0xFFFF;
        while((SD_SPI_WriteReadByte(SD_DUMMY_BYTE) != SD_START_DATA_SINGLE_BLOCK_READ) && u32Cnt){
            u32Cnt--;
        }
        if(u32Cnt != 0){
#if (SD_USE_DMA)
            LL_DMA_EnableChannel(SD_SPI_DMA,SD_SPI_DMA_RX_CHANNEL);
            LL_DMA_EnableChannel(SD_SPI_DMA,SD_SPI_DMA_TX_CHANNEL);
            LL_SPI_EnableDMAReq_RX(SD_SPI_Periph);
            LL_SPI_EnableDMAReq_TX(SD_SPI_Periph);
            while(!SD_SPI_DMA_TX_TC_IsActive());
            while(!SD_SPI_DMA_RX_TC_IsActive());
            while(LL_SPI_GetTxFIFOLevel(SD_SPI_Periph) != LL_SPI_TX_FIFO_EMPTY);
            while(LL_SPI_IsActiveFlag_BSY(SD_SPI_Periph));
            while(LL_SPI_GetRxFIFOLevel(SD_SPI_Periph) != LL_SPI_RX_FIFO_EMPTY){
                (void)(*((volatile unsigned char *)&SD_SPI_Periph->DR));
            }
            SD_SPI_DAM_RX_ClearGIFlag();
            SD_SPI_DAM_TX_ClearGIFlag();
            LL_DMA_DisableChannel(SD_SPI_DMA,SD_SPI_DMA_RX_CHANNEL);
            LL_DMA_DisableChannel(SD_SPI_DMA,SD_SPI_DMA_TX_CHANNEL);
            LL_SPI_DisableDMAReq_RX(SD_SPI_Periph);
            LL_SPI_DisableDMAReq_TX(SD_SPI_Periph);
#else
            SD_SPI_WriteReadBuffer((void*)0,0,pu8Buff,u16BlockSize);
#endif
        }
        /* Get CRC bytes (not really needed by us, but required by SD) */
        SD_SPI_WriteReadByte(SD_DUMMY_BYTE);
        SD_SPI_WriteReadByte(SD_DUMMY_BYTE);
        /* Set response value to success */
        status = SD_RESPONSE_NO_ERROR;
    }

    SD_CS_High;
    SD_SPI_WriteReadByte(SD_DUMMY_BYTE);

    return status;
}

[/mw_shl_code]
SPI用DMA发送数据就不用同时打开DMA接收了。


友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。