要使用SensorTile实现对SD卡的数据写入
使用过程中发现写入的数据和实际差别很大
测试发现有一部分数据已经丢失
大概的流程是准备要写的数据,数据准备好后会触发中断
中断里设置写入标志,主while中判断写入标志为1时写入数据
后来在中断里加一个计数器,又在写入函数里加一个计数器
操作完以后比较2个计数发现写入计数比中断计数少很多
怀疑是SD写入速率过慢,数据还没写完第二个中断就已经出来了
在配置里把SPI的速率调到最高,已经达到40MHz
测试写入速率还是很慢
用示波器测量一下发现每个字节的间隔非常大
估计是代码里有延时或者其它判断造成的延时
顺着代码找到一处
f_write一次性写入8K字节的数据
在写数据时调用了SensorTile_sd.c文件里的BSP_SD_WriteBlocks函数
数据被拆分成多个512字节的block
每个block的512字节通过for循环将数据循环写入
/* Write the block data to SD : write count data by block */ for (counter = 0; counter < BlockSize; counter++) { /* Send the pointed byte */ SD_IO_WriteByte(*pData);
/* Point to the next location where the byte read will be saved */ pData++; }
|
SensorTile.c的SD_IO_WriteByte函数调用了SD_IO_SPI_Write函数
SD_IO_SPI_Write函数调用了stm32l4xx_hal_spi.c的HAL_SPI_Transmit函数
static void SD_IO_SPI_Write(uint8_t Value) { HAL_StatusTypeDef status = HAL_OK;
status = HAL_SPI_Transmit(&SPI_SD_Handle, (uint8_t*) &Value, 1, SpixTimeout);
/* Check the communication status */ if(status != HAL_OK) { /* Execute user timeout callback */ SD_IO_SPI_Error(); } }
|
HAL_SPI_Transmit函数允许一次写入多个字节数据
而实际SD_IO_SPI_Write函数都是通过HAL_SPI_Transmit写入一个字节
512个字节就需要调用512次HAL_SPI_Transmit函数
而HAL_SPI_Transmit函数里边有很多代码,每个字节都要做这些判断显然很浪费时间
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_tTimeout) { uint32_t tickstart = HAL_GetTick(); HAL_StatusTypeDef errorcode = HAL_OK;
assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE(hspi->Init.Direction));
/* Process Locked */ __HAL_LOCK(hspi);
if(hspi->State != HAL_SPI_STATE_READY) { errorcode = HAL_BUSY; goto error; }
if((pData == NULL ) || (Size == 0)) { errorcode = HAL_ERROR; goto error; }
/* Set the transaction information */ hspi->State = HAL_SPI_STATE_BUSY_TX; hspi->ErrorCode = HAL_SPI_ERROR_NONE; hspi->pTxBuffPtr = pData; hspi->TxXferSize = Size; hspi->TxXferCount = Size; hspi->pRxBuffPtr = (uint8_t *)NULL; hspi->RxXferSize = 0; hspi->RxXferCount = 0;
/* Configure communication direction : 1Line */ if(hspi->Init.Direction == SPI_DIRECTION_1LINE) { SPI_1LINE_TX(hspi); }
/* Reset CRC Calculation */ if(hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE) { SPI_RESET_CRC(hspi); }
/* Check if the SPI is already enabled */ if((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE) { /* Enable SPI peripheral */ __HAL_SPI_ENABLE(hspi); }
/* Transmit data in 16 Bit mode */ if(hspi->Init.DataSize > SPI_DATASIZE_8BIT) { /* Transmit data in 16 Bit mode */ while (hspi->TxXferCount > 0) { /* Wait until TXE flag is set to send data */ if((hspi->Instance->SR & SPI_FLAG_TXE) == SPI_FLAG_TXE) { hspi->Instance->DR = *((uint16_t *)hspi->pTxBuffPtr); hspi->pTxBuffPtr += sizeof(uint16_t); hspi->TxXferCount--; } else { /* Timeout management */ if((Timeout == 0) || ((Timeout != HAL_MAX_DELAY) && ((HAL_GetTick()-tickstart) >= Timeout))) { errorcode = HAL_TIMEOUT; goto error; } } } } /* Transmit data in 8 Bit mode */ else { while (hspi->TxXferCount > 0) { /* Wait until TXE flag is set to send data */ if((hspi->Instance->SR & SPI_FLAG_TXE) == SPI_FLAG_TXE) { if(hspi->TxXferCount > 1) { /* write on the data register in packing mode */ hspi->Instance->DR = *((uint16_t*)hspi->pTxBuffPtr); hspi->pTxBuffPtr += sizeof(uint16_t); hspi->TxXferCount -= 2; } else { *((__IO uint8_t*)&hspi->Instance->DR) = (*hspi->pTxBuffPtr++); hspi->TxXferCount--; } } else { /* Timeout management */ if((Timeout == 0) || ((Timeout != HAL_MAX_DELAY) && ((HAL_GetTick()-tickstart) >= Timeout))) { errorcode = HAL_TIMEOUT; goto error; } } } }
/* Enable CRC Transmission */ if(hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE) { hspi->Instance->CR1|= SPI_CR1_CRCNEXT; }
/* Check the end of the transaction */ if(SPI_EndRxTxTransaction(hspi,Timeout) != HAL_OK) { hspi->ErrorCode = HAL_SPI_ERROR_FLAG; }
/* Clear overrun flag in 2 Lines communication mode because received is not read */ if(hspi->Init.Direction == SPI_DIRECTION_2LINES) { __HAL_SPI_CLEAR_OVRFLAG(hspi); }
if(hspi->ErrorCode != HAL_SPI_ERROR_NONE) { errorcode = HAL_ERROR; }
error: hspi->State = HAL_SPI_STATE_READY; /* Process Unlocked */ __HAL_UNLOCK(hspi); return errorcode; }
|
解决办法是在SensorTile.c里添加2个函数,实现多个字节的写入
void SD_IO_WriteByteNBytes(uint8_t *pData, uint16_t Size) { /* Send the byte */ SD_IO_SPI_WriteNBytes(pData , Size); } static void SD_IO_SPI_WriteNBytes(uint8_t *pData, uint16_t Size) { HAL_StatusTypeDef status = HAL_OK;
status = HAL_SPI_Transmit(&SPI_SD_Handle, pData , Size , SpixTimeout);
/* Check the communication status */ if(status != HAL_OK) { /* Execute user timeout callback */ SD_IO_SPI_Error(); } }
|
然后在BSP_SD_WriteBlocks通过调用一次SD_IO_WriteByteNBytes函数实现512个字节的快速写入
uint8_t BSP_SD_WriteBlocks(uint32_t* p32Data, uint64_t Sector, uint16_t BlockSize, uint32_tNumberOfBlocks) { uint32_t counter = 0, offset = 0; uint8_t rvalue = MSD_ERROR; uint8_t *pData = (uint8_t *)p32Data;
if(SD_CardType != HIGH_CAPACITY_SD_CARD) { Sector *= BlockSize; }
/* Data transfer */ while (NumberOfBlocks--) { /* Send CMD24 (SD_CMD_WRITE_SINGLE_BLOCK) to write blocks and Check if the SD acknowledged the write block command: R1 response (0x00: no errors) */ if (SD_IO_WriteCmd(SD_CMD_WRITE_SINGLE_BLOCK, Sector + offset, 0xFF, SD_RESPONSE_NO_ERROR) !=HAL_OK) { return MSD_ERROR; }
/* Send dummy byte */ SD_IO_WriteByte(SD_DUMMY_BYTE);
/* Send the data token to signify the start of the data */ SD_IO_WriteByte(SD_START_DATA_SINGLE_BLOCK_WRITE);
/* Write the block data to SD : write count data by block */ // for (counter = 0; counter < BlockSize; counter++) // { // /* Send the pointed byte */ // SD_IO_WriteByte(*pData); // // /* Point to the next location where the byte read will be saved */ // pData++; // } SD_IO_WriteByteNBytes(pData,BlockSize); /* Set next write address */ if(SD_CardType != HIGH_CAPACITY_SD_CARD) { offset += BlockSize; } else { offset += 1; }
/* Put CRC bytes (not really needed by us, but required by SD) */ SD_IO_ReadByte(); SD_IO_ReadByte();
/* Read data response */ if (SD_GetDataResponse() == SD_DATA_OK) { /* Set response value to success */ rvalue = MSD_OK; } else { /* Set response value to failure */ rvalue = MSD_ERROR; } }
/* Send dummy byte: 8 Clock pulses of delay */ SD_IO_WriteDummy();
/* Returns the reponse */ return rvalue; }
|
以为这样改问题就可以解决就大错特错了
测试时发现问题依旧
经过无数次调试发现问题出现在SD_GetDataResponse函数上
BSP_SD_WriteBlocks每次写过都要判断一下SD_GetDataResponse的返回值
/* Read data response */ if (SD_GetDataResponse() == SD_DATA_OK) { /* Set response value to success */ rvalue = MSD_OK; }
|
SD_GetDataResponse有这样一句话 /* Wait null data */ while (SD_IO_ReadByte() == 0)
读SPI数据,如果为0就一直读
SD_IO_ReadByte最后也是执行的HAL_SPI_TransmitReceive函数
我在while中加一个计数器,发现每次都要执行200次以上才能跳出while循环
很多时候都能达到1万多次
函数太复杂了,没法改
看一下别的工程,去STM32L476G_EVAL里边看下
一看不要紧,差点没被气死
这是stm32l476g_eval_sd.c里的BSP_SD_WriteBlocks函数,短短几行就能实现这个功能
uint8_t BSP_SD_WriteBlocks(uint32_t *pData, uint64_t WriteAddr, uint32_t BlockSize, uint32_t NumOfBlocks) { if(HAL_SD_WriteBlocks(&uSdHandle, pData, WriteAddr, BlockSize, NumOfBlocks) != SD_OK) { return MSD_ERROR; } else { return MSD_OK; } }
|
赶紧把STM32L476G_EVAL里的SD移植到SensorTile里
在stm32l4xx_hal_conf.h文件中把#define HAL_SD_MODULE_ENABLED注释去掉
添加stm32l4xx_hal_sd.c和stm32l4xx_ll_sdmmc.c到工程的STM32L4xx_HAL_Driver目录
复制stm32l476g_eval_sd.c的内容到SensorTile_sd.c
同样的方法复制.h文件
编译,通过
如果认为这样就把功能完成就大错特错了
在修改SD的I/O口时发现STM32L476G_EVAL使用的是SDMMC模块
使用之根数据线那种,不是SensorTile里的SPI
费半天劲还要改回来
再测试时如果像这样在Wait null data前加一个50ms的延时结果MISO引脚抬起的时候也是在延时完执行了SD_IO_ReadByte()后
HAL_Delay(50); /* Wait null data */ while (SD_IO_ReadByte() == 0) { i++; }
/* Return response */ return response;
|
最后尝试可以把延时设置2ms正好,2ms = 512byte 8k才32ms时间已经非常短了
如果觉得这样问题就解决那就大错特错了,测试时发现问题依旧
费半天劲还要改回来
既然标准库里的函数太复杂那就试试LL库
添加头文件#include "stm32l4xx_ll_spi.h"
把SD_IO_WriteByte和SD_IO_ReadByte里的SPI读写函数改成LL库的
/** * @brief Writes a byte on the SD. * @param Data: byte to send. * @retval None */ void SD_IO_WriteByte(uint8_t Data) { /* Send the byte */ LL_SPI_TransmitData8(SPI_SD_Handle.Instance, Data); }
/** * @brief Reads a byte from the SD. * @param None * @retval The received byte. */ uint8_t SD_IO_ReadByte(void) { uint8_t data = 0;
/* Get the received data */ data = LL_SPI_ReceiveData8(SPI_SD_Handle.Instance);
/* Return the shifted data */ return data; }
|
全部改完后以为这样能成就大错特错了
发现通信根本不正常,原来是LL库在发送和接收前需要对某寄存器进行判断
/** * @brief Writes a byte on the SD. * @param Data: byte to send. * @retval None */ void SD_IO_WriteByte(uint8_t Data) { // SD_IO_SPI_Write(Data); // return; /* Wait until TXE flag is set to send data */ while((SPI_SD_Handle.Instance->SR & SPI_FLAG_TXE) != SPI_FLAG_TXE); /* Send the byte */ LL_SPI_TransmitData8(SPI_SD_Handle.Instance, Data); }
/** * @brief Reads a byte from the SD. * @param None * @retval The received byte. */ uint8_t SD_IO_ReadByte(void) { uint8_t data = 0; // return SD_IO_SPI_Read(); while((SPI_SD_Handle.Instance->SR & SPI_FLAG_TXE) != SPI_FLAG_TXE); LL_SPI_TransmitData8(SPI_SD_Handle.Instance, 0xff); while((SPI_SD_Handle.Instance->SR & SPI_FLAG_RXNE) != SPI_FLAG_RXNE); /* Get the received data */ data = LL_SPI_ReceiveData8(SPI_SD_Handle.Instance);
/* Return the shifted data */ return data; }
|
改完判断后通信正常,试一下速度还没有原来的快