在STM32上以高频率写入MicroSD时出现磁盘错误
过去几天,我一直在通过MicroPeta的 这份指南,研究在microSD上保存一个txt文件的多种做法。非常有用,运行也很顺利,但我的目标是实现约10 kHz的保存频率。
我可能并不需要那么高的频率,但我在努力找出与已有CAN网络(包含若干传感数据)的集成时的边缘情况。我还计划改用二进制文件方案,并可能进一步了解NanoPB的实现,但现在我想做一个直观的调试,形式是保存时间戳的txt文件,粒度为每100微秒。为此,真正起作用的主要技术是ping pong缓冲区(来回切换的缓冲区),并把它放到定时器中断里。
最后一次成功生成的txt文件是一段1 分钟的记录,生成了一个8 MB的文件,行数几乎与预期相符,每100微秒写一行,只有在少数时刻跳过了一些。我的结论是,中断会改变缓冲区指针,SD会从第二个缓冲区开始写入,这解释了发生跳过的时间点恰好对应一个缓冲区能存储的时间范围。可以说,合理。
问题出现在我尝试通过再增加一个用于ping pong的缓冲区来解决时。不是标准做法,我知道,这基本等于给一条街多加一条车道,结果当然会带来更拥堵。但我现在并没有看到它的问题。突然之间,SD不再是确定性的。似乎是在完全随机的时间(当然我也无法指出确切原因),写入一个缓冲区时返回FR_DISK_ERR,这当然会让代码崩溃。没有重试机制,我也不认为应该有,因为这是一个非常反复的问题,应该有明确的解决办法。
以下是代码的主要部分(我仍然在用CubeIDE):
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef struct tim{
uint8_t day;//1-31
uint8_t month;//1-12
uint8_t year;//since 1900
uint8_t hour;//0-23
uint8_t minute;//0-59
uint8_t second;//0-59
uint16_t millis;//0-999
uint8_t micro100;//0-9
}Datetime;
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define BUF_SIZE 2048
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;
DMA_HandleTypeDef hdma_spi1_rx;
DMA_HandleTypeDef hdma_spi1_tx;
TIM_HandleTypeDef htim2;
/* USER CODE BEGIN PV */
//SD
FATFS fs;
FIL fil;
FRESULT res;
UINT bw;
//Timer
char time[15];
Datetime now;
uint32_t lastSyncTime;
//Write Control
uint8_t writing = 0;
uint16_t sync_counter = 0;
uint8_t arrayA[BUF_SIZE], arrayB[BUF_SIZE], arrayC[BUF_SIZE];
uint8_t* bufferPool[3] = {arrayA, arrayB, arrayC};
uint8_t* currentArray = arrayA;
volatile uint8_t fillIdx = 0;
volatile uint8_t saveIdx = 0;
volatile uint8_t buffersReady = 0;
uint16_t array_index = 0;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_SPI1_Init(void);
static void MX_TIM2_Init(void);
/* USER CODE BEGIN PFP */
uint8_t TimToStr(char* iso8601, Datetime time);
void TimInit();
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_SPI1_Init();
MX_FATFS_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
TimInit();
HAL_Delay(500);
res = f_mount(&fs, "", 1);
res = f_open(&fil, "timer_test10.txt", FA_OPEN_ALWAYS | FA_WRITE | FA_READ);
res = f_lseek(&fil, fil.fsize);
f_puts("HEADER: Same test as 07, but now we use a 2048 buffer size.\n", &fil);
HAL_TIM_Base_Start_IT(&htim2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(writing){
if (HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY) {
writing = 0;
}
}
if(buffersReady>0 && !writing){
res = f_write(&fil, bufferPool[saveIdx], BUF_SIZE, &bw);
if(res!= FR_OK){
break;
}
saveIdx = (saveIdx + 1) % 3;
buffersReady--;
}
if (HAL_GetTick() - lastSyncTime > 5000 && !writing) { // sync every 5 seconds
f_sync(&fil);
lastSyncTime = HAL_GetTick();
}
}
/* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == TIM2){ //10kHz
now.micro100++;
if(now.micro100>9){
now.millis++;
now.micro100 = 0;
}
if(now.millis>999){
now.second++;
now.millis = 0;
}
if(now.second>59){
now.minute++;
now.second = 0;
}
if(now.minute>59){
now.hour++;
now.minute=0;
}
uint8_t len = TimToStr(time, now);
if(array_index+len >= BUF_SIZE){
fillIdx = (fillIdx + 1) % 3; // Move to next buffer in the circle
currentArray = bufferPool[fillIdx];
array_index = 0;
buffersReady++;
}
memcpy((currentArray+array_index),time,len);
array_index+=len;
}
}
uint8_t TimToStr(char* iso8601, Datetime time){//00:00:00.0000
iso8601[0] = time.hour/10 + '0';
iso8601[1] = time.hour%10 + '0';
iso8601[2] = ':';
iso8601[3] = time.minute/10 + '0';
iso8601[4] = time.minute%10 + '0';
iso8601[5] = ':';
iso8601[6] = time.second/10 + '0';
iso8601[7] = time.second%10 + '0';
iso8601[8] = '.';
iso8601[9] = time.millis/100 + '0';
iso8601[10] = (time.millis%100)/10 + '0';
iso8601[11] = time.millis%10 + '0';
iso8601[12] = time.micro100 + '0';
iso8601[13] = '\n';
iso8601[14] = '\0';
return 15;
}
void TimInit(){
now.micro100 = 0;
now.millis = 0;
now.second = 0;
now.minute = 0;
now.hour = 0;
}
/* USER CODE END 4 */
下面来解释一些变量和常量。首先,SPI被设置为在72MHz时钟下的最大速度,即18MB/s。最大扇区大小为4096,这是唯一能让我的代码运行的原因(我不理解原因),而定时器中断以10kHz运行。缓冲区大小2048,是生成8MB文件时有效的大小,512、1024或 4096均不可行。
我觉得这可能与簇大小之类的因素产生某种“共振”。我也尝试使用DMA,但由于MicroPeta的库和SPI通信在很大程度上依赖于监听回应,这并没有带来太大帮助,于是我把它移除了,结果也没有影响。writing变量是那个时期的遗留物,但不应该影响逻辑。
非常感谢你的帮助!如果你有任何问题请提问;如果你有比在等式中再增加一个第三缓冲区更好的解决方案,我也很乐意调整方案。
解决方案
Found the issue, the problem was the variable buffersReady which could increase at a very rapid rate. A test I made creating new variables, loadBufCount and saveBufCount gave me that, with a low buffer size, I lost about half of the packages at the one minute timestamp. So by then, buffersReady could be any number between 0 and 255 buffers ready to load, which crashed the sd's capacity to write. When I just changed it to be either 0或1 the code worked, but allowed these packages to be lost. I still haven't seen a way to achieve my goal properly, but I'm nearly there. As I'm working with STM32F103C8T6, it has 20kB of RAM and I'm pushing it to the limit with these buffers, so it's not quite the solution I'd like, but my initial question was solved.