CubeMx配置SD卡+RTC实时时钟+fatfs文件系统

目录

一、SDIO卡简介

1. ​​​​FATFS简介​

 二、创建工程

软件版本

二、keil代码 

三、下载验证


一、SDIO卡简介

​SDIO(Secure Digital Input Output)​​ 是一种基于SD卡物理接口扩展的输入输出技术,在兼容传统SD存储功能的同时,通过扩展协议支持外设连接(如Wi-Fi、蓝牙、GPS等),可配置为高速4位总线模式(最高50MHz),早期广泛应用于移动设备扩展功能,现逐渐被USB等接口替代,但在嵌入式领域仍用于低成本、紧凑型设备的模块化扩展。

在STM32开发中,SD卡的​​块写入(Block Write)​​和​​FATFS文件系统​​是两种不同层级的操作方式,它们的核心区别在于​​抽象层级​​和​​功能目标​​。

 1. ​​​​SD卡硬件

 

2. ​​FATFS简介

FATFS是一个为嵌入式系统设计的轻量级文件系统模块,支持FAT12、FAT16、FAT32及exFAT格式,提供标准的文件操作接口(如打开、读写、目录管理),兼容性强且资源占用低,适用于单片机等资源受限环境。其核心通过逻辑扇区地址(LBA)抽象底层存储介质(如SD卡),开发者需对接底层块读写函数以实现物理驱动。

 3. ​​​​FATFS文件写入 vs. 直接块写入的区别

​FATFS写入​​:调用f_write(&file, buffer, size, &bytes_written),数据自动按文件系统规则分配扇区并更新FAT表。

块写入​​:调用HAL_SD_WriteBlocks(&hsd, buffer, sector_num, block_count),直接向指定物理扇区写入数据块,需自行记录存储位置。

对比维度FATFS文件写入直接块写入(Block Write)​
功能定位​文件级操作(文件/目录管理)物理扇区级操作(无文件系统结构)
​操作单位​按字节/数据流写入(自动管理扇区分配)按固定块(如512字节)写入
结构管理​维护FAT表、目录项等元数据无文件系统结构,需手动管理数据存储逻辑
​接口复杂度​高层API(f_openf_writef_close底层驱动函数(如HAL_SD_WriteBlocks
​性能开销需处理文件系统元数据(略低效率)直接操作扇区(更高效率,无额外开销)
适用场景需文件管理(如日志存储、多文件存取)裸数据存储(如原始传感器数据流)

 

 二、创建工程

软件版本

cubemx6.1.2 MDK5.33.0.0 芯片用的是stm32f103zet6

1. 打开 STM32CubeMX 软件,新建工程

2.选择MCU这里以STM32F103ZET6为例  

 3.  SDIO参数配置

选择 SD 4 bits Wide bus 四线SD模式,时钟分频系数设置为:6

4.  DMA Settings配置

5.  FATFS配置

勾选SD Card,并在code page选择中文

6.  USART1配置

如图芯片引脚所示:

PA9     ------> USART1_TX
PA10   ------> USART1_RX

 7.  DMA配置

8.  RTC配置

9.  NVIC Settings配置

10.  NVIC 配置

11.  SYS 配置 

12.  RCC 配置 

13.  时钟树配置 

14.  代码生成 

输入项目名和项目路径(路径中不可包含中文) 

二、keil代码 

在usart.c中先添加头文件"stdio.h"再在底部添加打印重定向函数如下所示

#include "usart.h"

/* USER CODE BEGIN 0 */
#include "stdio.h"          //头文件
/* USER CODE END 0 */


/* USER CODE BEGIN 1 */
int fputc(int ch, FILE *f) //打印重定向
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
    return ch;
}
 
/**
  * @brief 重定向c库函数getchar,scanf到USARTx
  * @retval None
  */
int fgetc(FILE *f)
{
    uint8_t ch = 0;
    HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
    return ch;
}

/* USER CODE END 1 */
在stm32f1xx_it.c底部添加回调函数如下所示
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */
uint8_t Buffer[1];
/* USER CODE END TD */


/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        HAL_UART_Transmit(&huart1, (uint8_t *)Buffer, 1, 0xffff);
        HAL_UART_Receive_IT(&huart1, (uint8_t *)Buffer, 1);
    }
}

/* USER CODE END 1 */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "fatfs.h"
#include "rtc.h"
#include "sdio.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>    //添加头文件stdio.h
#include <string.h>   //添加头文件string.h
#include <time.h>     //添加头文件time.h
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
#define RX_BUFFER_SIZE 1024                  // 串口接收缓冲区大小
volatile uint8_t rxBuffer[RX_BUFFER_SIZE];  // DMA接收缓冲区
volatile uint16_t rxLength = 0;            // 实际接收数据长度
volatile uint8_t fileIsOpen = 0;          // 文件打开状态标志
FATFS fs;                         // FatFs文件系统对象
FIL file;                        // 文件对象
FRESULT f_res;                  // 文件操作结果
UINT fnum;                     // 文件读写计数器
// RTC相关变量
RTC_TimeTypeDef currentTime;
RTC_DateTypeDef currentDate;
char timeStamp[32];            // 时间戳缓冲区
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void processReceivedData(void);
void FormatTimestamp(void);    // 新增时间格式化函数
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART1) {
        rxLength = Size;       // 保存接收数据长度
        processReceivedData(); // 处理接收到的数据
        // 重新启动DMA接收
        HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, RX_BUFFER_SIZE);
    }
}
/* USER CODE END 0 */


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_SDIO_SD_Init();
  MX_USART1_UART_Init();
  MX_FATFS_Init();
  MX_RTC_Init();
  /* USER CODE BEGIN 2 */
  if(HAL_RTC_GetTime(&hrtc, &currentTime, RTC_FORMAT_BIN) != HAL_OK) {
      printf("RTC初始化失败!\r\n");
      Error_Handler();
  }
printf("\r\n****** 串口数据记录系统启动 ******\r\n");

// 挂载文件系统
f_res = f_mount(&fs, "0:", 1);
if(f_res != FR_OK) {
    printf("SD卡挂载失败: %d\r\n", f_res);
    Error_Handler();
}

// 文件打开逻辑
f_res = f_open(&file, "0:sd_test.txt", FA_WRITE | FA_OPEN_ALWAYS);
if(f_res == FR_OK) {
// 移动指针到文件末尾实现追加写入
    f_res = f_lseek(&file, f_size(&file));
    if(f_res != FR_OK) {
        printf("文件指针定位失败: %d\r\n", f_res);
        Error_Handler();
    }
    printf("成功打开日志文件(追加模式)\r\n");
} else {
    printf("文件打开失败: %d\r\n", f_res);
    Error_Handler();
}
fileIsOpen = 1;

// 启动DMA接收(带空闲中断)
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, RX_BUFFER_SIZE);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
void FormatTimestamp(void)
{
// 获取精确时间(包含日期)
    HAL_RTC_GetTime(&hrtc, &currentTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &currentDate, RTC_FORMAT_BIN);
    
// 格式示例:[2023-08-20 14:30:45]
    snprintf(timeStamp, sizeof(timeStamp), "[%04d-%02d-%02d %02d:%02d:%02d] ",
            currentDate.Year + 2000,  // RTC年份通常从2000开始
            currentDate.Month,
            currentDate.Date,
            currentTime.Hours,
            currentTime.Minutes,
            currentTime.Seconds);
}

void processReceivedData(void)
{
    if(rxLength > 0 && fileIsOpen) {
        // 生成时间戳
        FormatTimestamp();
        
        // 写入日志的三部分
        const char newline[] = "\r\n";
        
        // 1. 写入时间戳
        f_res = f_write(&file, timeStamp, strlen(timeStamp), &fnum);
        if(f_res != FR_OK) goto write_error;
        
        // 2. 写入接收数据
        f_res = f_write(&file, (const void*)rxBuffer, rxLength, &fnum);
        if(f_res != FR_OK) goto write_error;
        
        // 3. 写入换行符
        f_res = f_write(&file, newline, sizeof(newline)-1, &fnum);
        if(f_res != FR_OK) goto write_error;
        
        // 同步文件系统
        f_res = f_sync(&file);
        if(f_res != FR_OK) {
            printf("同步错误: %d\r\n", f_res);
            Error_Handler();
        }
        
        // 串口回显带格式的数据
        HAL_UART_Transmit(&huart1, (uint8_t*)timeStamp, strlen(timeStamp), 100);
      //HAL_UART_Transmit(&huart1, rxBuffer, rxLength, 100);
        HAL_UART_Transmit(&huart1, (uint8_t*)newline, sizeof(newline)-1, 100);
        HAL_UART_Transmit(&huart1, (uint8_t*)rxBuffer, rxLength, 100);
        rxLength = 0;
        return;
        
write_error:
        printf("文件写入失败: %d\r\n", f_res);
        Error_Handler();
    }
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)  //修改错误处理函数部分
{
  /* USER CODE BEGIN Error_Handler_Debug */
    // 安全关闭文件系统
    if(fileIsOpen) {
        f_close(&file);
        f_mount(NULL, "0:", 1);
        fileIsOpen = 0;
    }
    
    // 错误提示音(可以自定义LED灯啥的)
//    HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET);
//    HAL_Delay(1000);
//    HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET);
   
    printf("!! 系统进入安全模式 !!\r\n");
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

三、下载验证

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值