ccccccc@ 发表于 2024-10-31 17:34

# STM32H7S78-DK 开发套件三周目评测:简单声音采集保存之使用 SD 卡读写的实现与分析

该项目包括了保存录音、按键控制、串口控制、频率成分分析与分类等功能,逐步进行完成。

首先进行sd读写的开。在嵌入式开发中,尤其在无操作系统的裸机环境下,SD卡是实现大容量数据存储的重要组件。

## 1. 硬件连接

在开始CubeMX配置前,需要先了解引脚的配置。



### 1.1 数据引脚连接


- **D0 - D3**:将SD卡的数据引脚D0-D3分别连接到STM32的PC8、PC9、PC10、PC11。
- **CMD**:将SD卡的CMD引脚连接到STM32的PD2,用于命令传输。
- **CLK**:将SD卡的CLK引脚连接到STM32的PC12,提供时钟信号。
- **SD_Detect**:将SD卡的SD_Detect引脚连接至GPIO(PM14),用于检测SD卡插入状态。

具体连接示例代码:

```c
// 配置引脚,在CubeMX中生成的初始化代码中可见
void MX_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // Enable clock for GPIOC and GPIOD
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_GPIOM_CLK_ENABLE();// 使能PM14引脚所在的GPIO时钟

    // 配置SDMMC引脚:CMD (PD2), CLK (PC12), D0 (PC8), D1 (PC9), D2 (PC10), D3 (PC11)
    GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_SDMMC1;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
   
    // 配置SD_Detect引脚 (PM14)
    GPIO_InitStruct.Pin = GPIO_PIN_14;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP; // 内部上拉电阻
    HAL_GPIO_Init(GPIOM, &GPIO_InitStruct);
}
```

## 2. CubeMX 配置与代码实现

CubeMX能够简化SD卡和文件系统的配置,我们将通过它来生成SD卡的初始化代码,并使用FATFS文件系统以实现文件操作。这里选择FATFS文件系统方式除了是可以直接配置简单快捷外,使用eprom还有一些缺点:
1.没有文件系统,sd卡在电脑等设备无法读取。
2.读写速度慢、读写区域小。

### 2.1 CubeMX 配置步骤

#### 2.1.1 SDMMC 接口设置

1. 在CubeMX中打开**Peripherals -> SDMMC1**接口。
2. 将其模式设置为**4-bit**模式,以提升数据传输速率。
3. 在**Clock Configuration**中将SDMMC1的时钟频率调整为48MHz,以保证传输速率和稳定性。

#### 2.1.2 启用 FATFS 文件系统

1. 在**Middlewares**中启用FATFS支持,选择SDMMC1作为接口,以实现基于文件系统的操作。
2. 生成初始化代码,其中`fatfs.c`文件包含了文件系统初始化和读写的基本接口。

### 2.2 基本代码示例

接下来使用FATFS文件系统在SD卡上进行文件创建、写入和读取:

```c
#include "fatfs.h"
#include "sdmmc.h"
#include <stdio.h>

void SD_Test() {
    FATFS fs;
    FIL file;
    FRESULT res;
    UINT bw;

    // 挂载文件系统
    res = f_mount(&fs, "0:", 1);
    if (res == FR_OK) {
      // 创建并写入文件
      res = f_open(&file, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE);
      if (res == FR_OK) {
            char writeData[] = "Hello, SD Card!";
            f_write(&file, writeData, sizeof(writeData), &bw);
            f_close(&file);
      }

      // 读取文件
      res = f_open(&file, "0:/test.txt", FA_READ);
      if (res == FR_OK) {
            char readData;
            f_read(&file, readData, sizeof(readData), &bw);
            printf("Read data: %s\n", readData);
            f_close(&file);
      }

      // 卸载文件系统
      f_mount(NULL, "0:", 1);
    }
}
```

## 3. 不同 SD 卡读写方式与代码分析
当然在不同场景和需求下,对sd卡的读写操作应该有适当改变,才能提高效率、节省资源。

### 3.1 基于文件系统的标准读写

- **优点**:CubeMX自动生成FATFS代码,简化了文件操作流程,适用于低频存储。比如传感器的数据记录等。
- **缺点**:由于文件系统需要索引和管理结构,写入速度会受影响。
```c
void SD_WriteRead_FileSystem() {
    FATFS fs;
    FIL file;
    UINT bw, br;
    char writeData[] = "File System Write Test";
    char readData;

    // 挂载文件系统
    f_mount(&fs, "0:", 1);

    // 写入文件
    f_open(&file, "0:/filesystem.txt", FA_CREATE_ALWAYS | FA_WRITE);
    f_write(&file, writeData, sizeof(writeData), &bw);
    f_close(&file);

    // 读取文件
    f_open(&file, "0:/filesystem.txt", FA_READ);
    f_read(&file, readData, sizeof(readData), &br);
    f_close(&file);

    printf("Read Data: %s\n", readData);
    f_mount(NULL, "0:", 1);
}
```

### 3.2 扇区级块读写

- **优点**:直接操作扇区避免了文件系统开销,适合高频数据写入。比如音视频、网络文件等。
- **缺点**:需要手动管理数据位置和扇区分配,编程难度大。

```c
void SD_WriteBlock(uint32_t sector, uint8_t *data, uint32_t count) {
    if (HAL_SD_WriteBlocks(&hsd1, data, sector, count, HAL_MAX_DELAY) == HAL_OK) {
      printf("Block write success!\n");
    }
}

void SD_ReadBlock(uint32_t sector, uint8_t *data, uint32_t count) {
    if (HAL_SD_ReadBlocks(&hsd1, data, sector, count, HAL_MAX_DELAY) == HAL_OK) {
      printf("Block read success!\n");
    }
}
```

### 3.3 缓存 + 文件系统混合模式

- **优点**:使用缓存减少频繁写入,提升写入效率,适合音频等连续数据存储。
- **缺点**:占用较多RAM资源,需考虑系统内存限制。

```c
#define BUFFER_SIZE 512
uint8_t cacheBuffer;

void SD_WriteCache(const char *data) {
    static uint32_t offset = 0;

    while (*data) {
      cacheBuffer = *data++;
      if (offset >= BUFFER_SIZE) {
            SD_WriteBlock(0, cacheBuffer, BUFFER_SIZE / 512); // 以块为单位写入
            offset = 0;
      }
    }
}
```

## 4. 音频数据存储的优化方案
考虑到录音是一个时效性很强的操作,所以对存储效率要求很高,下面是两个优化的方案。
### 4.1 双缓冲技术

设置两个缓冲区,交替写入SD卡以减小写入延时。

```c
uint8_t bufferA;
uint8_t bufferB;
volatile uint8_t *currentBuffer = bufferA;

void Audio_WriteToSD() {
    if (currentBuffer == bufferA) {
      SD_WriteBlock(0, bufferA, BUFFER_SIZE / 512);
      currentBuffer = bufferB;
    } else {
      SD_WriteBlock(0, bufferB, BUFFER_SIZE / 512);
      currentBuffer = bufferA;
    }
}
```

### 4.2 文件分段存储

音频数据按时间或大小分段存储,避免大文件操作带来的开销。

```c
void SaveAudioSegment(uint8_t *data, size_t size, uint8_t segmentNum) {
    char filename;
    sprintf(filename, "

audio_segment_%d.wav", segmentNum);
    f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE);
    f_write(&file, data, size, &bw);
    f_close(&file);
}
```

cc1989summer 发表于 2024-11-2 13:03

<p></p>


<p>哥们,你这帖子在那干讲,没开发板跑的实物图啊。{:1_131:}</p>

ccccccc@ 发表于 2024-11-7 09:57

cc1989summer 发表于 2024-11-2 13:03
哥们,你这帖子在那干讲,没开发板跑的实物图啊。

<p>后面加,哈哈哈</p>
页: [1]
查看完整版本: # STM32H7S78-DK 开发套件三周目评测:简单声音采集保存之使用 SD 卡读写的实现与分析