痛陈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接收了。 |