[经验] STM32H7S78-DK 开发套件三周目评测:简单声音采集保存之SD 卡录音保存和状态显示

ccccccc@   2024-11-11 00:31 楼主
最后一个测试周目是基于 STM32H7 微控制器的音频采集与处理系统,能够采集音频信号,并将其以 WAV 格式保存至 SD 卡。采用 LED 闪烁来为用户提供各种状态反馈,包括录音状态、错误信息等。

二、项目模块结构

(一)错误处理与 LED 显示模块

wd_002736uaaquky88qdydy55.png
  • 功能:统一处理系统中的各类错误情况,并通过控制 LED 的不同闪烁模式向用户反馈相应信息。同时,也用于设置正常录音状态下的 LED 显示模式。
  • 代码实现
// 定义不同的闪烁模式
#define LED_OFF 0
#define LED_ON 1
#define LED_BLINK_FAST 2
#define LED_BLINK_SLOW 3
#define LED_ALTERNATE_BLINK 4
// 定义错误码对应的闪烁模式
#define ERROR_MOUNT_SD_CARD LED_BLINK_FAST
#define ERROR_OPEN_FILE LED_BLINK_FAST
#define ERROR_ADC_DMA_INIT LED_BLINK_FAST
#define RECORDING_ON LED_ON
#define RECORDING_OFF LED_OFF
#define ERROR_LONG_PRESS LED_BLINK_FAST
// 错误处理函数,通过LED闪烁显示错误
void handleError(uint8_t errorMode) {
switch (errorMode) {
case ERROR_MOUNT_SD_CARD:
case ERROR_OPEN_FILE:
case ERROR_ADC_DMA_INIT:
case ERROR_LONG_PRESS:
// 设置LED闪烁模式来表示错误
setLEDMode(errorMode);
break;
default:
break;
}
}
// 设置LED的闪烁模式
void setLEDMode(uint8_t mode) {
switch (mode) {
case LED_OFF:
HAL_GPIO_WritePin(GPIO_PIN_1, GPIO_PIN_RESET); // LD1(绿色)关闭
HAL_GPIO_WritePin(GPIO_PIN_5, GPIO_PIN_RESET); // LD2(橙色)关闭
HAL_GPIO_WritePin(GPIO_PIN_2, GPIO_PIN_SET); // LD3(红色)关闭
break;
case LED_ON:
if (mode == RECORDING_ON) {
HAL_GPIO_WritePin(GPIO_PIN_1, GPIO_PIN_SET); // LD1(绿色)打开,表示录音中
HAL_GPIO_WritePin(GPIO_PIN_5, GPIO_PIN_RESET); // LD2(橙色)关闭
HAL_GPIO_WritePin(GPIO_PIN_2, GPIO_PIN_SET); // LD3(红色)关闭
}
break;
case LED_BLINK_FAST:
// 实现快速闪烁逻辑(使用LD2橙色LED)
for (int i = 0; i < 10; i++) {
HAL_GPIO_TogglePin(GPIO_PIN_5, GPIO_PIN_SET);
HAL_Delay(100);
}
break;
case LED_BLINK_SLOW:
// 实现慢速闪烁逻辑(使用LD2橙色LED)
for (int i = 0; i < 5; i++) {
HAL_GPIO_TogglePin(GPIO_PIN_5, GPIO_PIN_SET);
HAL_Delay(500);
}
break;
case LED_ALTERNATE_BLINK:
// 实现交替闪烁逻辑
for (int i = 0; i < 5; i++) {
HAL_GPIO_TogglePin(GPIO_PIN_1, GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIO_PIN_5, GPIO_PIN_SET);
HAL_Delay(300);
}
break;
}
}

 

在这个模块中,handleError函数接收错误模式参数,并调用setLEDMode函数来控制 LED 的闪烁模式。不同的错误或状态对应不同的闪烁模式,如ERROR_MOUNT_SD_CARD等错误码对应LED_ALTERNATE_BLINK模式,而录音开启状态RECORDING_ON对应LED_ON模式,通过LED1(红灯)和LED2(绿灯)的亮灭或LED3(用于特殊状态显示)的闪烁来向用户传达信息。

(二)音频采集模块

  • 功能:利用 STM32 的 ADC(轮询方式)启动音频信号采集过程。
  • 代码实现
// 初始化音频采集
void InitAudioCapture() {
if (HAL_ADC_Start(&hadc1)!= HAL_OK) {
handleError(ERROR_ADC_START);
}
}

 

InitAudioCapture函数用于初始化音频采集功能。它主要负责启动 STM32 芯片上的模数转换器(ADC)。如果 ADC 启动成功,那么后续就可以开始采集音频信号;如果启动失败,则通过调用handleError函数来处理错误,这里的错误处理方式是根据预定义的错误模式(ERROR_ADC_START)来控制 LED 闪烁,向用户提示 ADC 启动出现问题。

(三)WAV 文件生成模块

  • 功能:生成符合 WAV 格式标准的文件头。
  • 代码实现
// 生成WAV文件头
void generateWavHeader(FIL *file) {
uint32_t dataSize = 0;
uint32_t byteRate = SAMPLE_RATE * NUM_CHANNELS * (BITS_PER_SAMPLE / 8);
uint32_t blockAlign = NUM_CHANNELS * (BITS_PER_SAMPLE / 8);
// RIFF 块
const char riffHeader[] = {'R', 'I', 'F', 'F'};
f_write(file, riffHeader, sizeof(riffHeader), NULL);
f_write(file, &dataSize, sizeof(dataSize), NULL);
const char waveFormat[] = {'W', 'A', 'V', 'E'};
f_write(file, waveFormat, sizeof(waveFormat), NULL);
// fmt 子块
const char fmtHeader[] = {'f', 'm', 't', ' '};
f_write(file, fmtHeader, sizeof(fmtHeader), NULL);
const uint32_t fmtChunkSize = 16;
f_write(file, &fmtChunkSize, sizeof(fmtChunkSize), NULL);
const uint16_t audioFormat = 1;
f_write(file, &audioFormat, sizeof(audioFormat), NULL);
f_write(file, &NUM_CHANNELS, sizeof(NUM_CHANNELS), NULL);
f_write(file, &SAMPLE_RATE, sizeof(SAMPLE_RATE), NULL);
f_write(file, &byteRate, sizeof(byteRate), NULL);
f_write(file, &blockAlign, sizeof(blockAlign), NULL);
f_write(file, &BITS_PER_SAMPLE, sizeof(BITS_PER_SAMPLE), NULL);
// data 子块
const char dataHeader[] = {'d', 'a', 't', 'a'};
f_write(file, dataHeader, sizeof(dataHeader), NULL);
f_write(file, &dataSize, sizeof(dataSize), NULL);
}

 

该模块根据定义好的音频参数(如采样率SAMPLE_RATE、声道数NUM_CHANNELS、每个样本的位数BITS_PER_SAMPLE)生成 WAV 文件头。此函数在录音保存到 SD 卡的过程中被调用。

(四)录音保存到 SD 卡模块

wd_002736avewlzokmvkklyxz.png
  • 功能:将采集到的音频数据保存为 WAV 格式文件到 SD 卡,并以日期时间命名文件,同时处理文件操作过程中的错误情况。
  • 代码实现
 

此模块首先尝试挂载 SD 卡文件系统,在这一过程中若挂载失败(即f_mount函数返回值不为FR_OK),则通过handleError函数以ERROR_MOUNT_SD_CARD对应的 LED 闪烁模式向用户提示错误信息。挂载成功后,接着会根据当前日期和时间来创建文件名。之后尝试打开此文件,若文件打开操作失败(也就是f_open函数返回值不为FR_OK),同样会调用handleError函数,同时卸载文件系统。当文件成功打开后,会生成符合 WAV 格式的文件头。在录音过程中,使用轮询 ADC 转换是否完成,完成后获取其值并将音频数据逐个写入文件。待录音结束后,根据已写入文件的音频数据量来更新 WAV 文件头中的数据大小信息,最后关闭文件,并卸载 SD 卡文件系统。

(五)按键控制模块

wd_002736m08dxmd9tqxdqqz0.png
  • 功能:实现按键防抖功能,并通过按键控制录音的开始、停止、暂停等操作,同时在出现异常按键情况(如长按)时通过 LED 显示错误信息。
  • 代码实现
// 更完善的按键防抖和长按检测
#define DEBOUNCE_COUNT 5
#define LONG_PRESS_TIME 1000 // 长按时间阈值(单位:毫秒)
uint8_t buttonDebounceCounter = 0;
uint32_t buttonPressStartTime = 0;
// 假设这里的 BUTTON_Pin 就是控制 B2 按键的引脚(PC13)
#define BUTTON_Pin GPIO_PIN_13
#define BUTTON_PORT GPIOC
uint8_t debounceButton(uint16_t GPIO_Pin) {
uint8_t currentState = HAL_GPIO_ReadPin(BUTTON_PORT, BUTTON_Pin); // 读取B2按键引脚状态
if (currentState == button.previousState) {
if (currentState == GPIO_PIN_SET) { // 按下为高电平
buttonDebounceCounter++;
if (buttonDebounceCounter >= DEBOUNCE_COUNT) {
if (HAL_GetTick() - buttonPressStartTime > LONG_PRESS_TIME) {
handleError(ERROR_LONG_PRESS);
return GPIO_PIN_SET; // 返回高电平表示长按
}
}
} else {
buttonDebounceCounter = 0;
buttonPressStartTime = HAL_GetTick();
}
} else {
buttonDebounceCounter = 0;
button.previousState = currentState;
}
return 0;
}
// 按键中断处理
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == BUTTON_Pin && debounceButton(GPIO_Pin) == GPIO_PIN_SET) { // 检测到B2按下
switch (buttonAction) {
case BUTTON_PRESS_SHORT:
recording =!recording;
setLEDMode(recording? RECORDING_ON : RECORDING_OFF);
if (recording) {
SaveAudioToSDCard();
}
break;
default:
break;
}
}
}

 

在debounceButton函数中,实现了按键防抖和长按检测功能。当检测到按键按下且经过防抖处理后,如果按键按下时间超过LONG_PRESS_TIME阈值,判定为长按操作,调用handleError函数以ERROR_LONG_PRESS对应的 LED 闪烁模式提示用户。在HAL_GPIO_EXTI_Callback函数中,处理短按按键操作,根据按键状态切换录音状态,并通过setLEDMode函数设置相应的 LED 显示模式(录音开启时为RECORDING_ON模式,录音停止时为RECORDING_OFF模式),同时在录音开始时调用SaveAudioToSDCard函数开始保存音频。
 
实际操作时候,保存文件一直有问题,保存的文件打开音频有些失真,应该是采样率的问题,还需要修改调试一下。。。

回复评论 (1)

采样率设置的不合适么,保存的文件打开音频有些失真

点赞  2024-11-12 07:26
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复