news 2026/10/26 14:05:18

STM32裸机录音回放实战:ADC/DAC与SD卡WAV存储

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32裸机录音回放实战:ADC/DAC与SD卡WAV存储

简介:这份资源面向具备一定嵌入式基础的STM32开发者,提供一套可直接参考的录音机程序实现方案,帮助读者理解音频采集、编码存储与文件系统集成的完整链路。压缩包共160个文件,以68个.h头文件与65个.c源文件为主体,涵盖ADC配置、FAT文件系统、JPEG与ILI93xx液晶驱动等模块,另附12张png截图、若干txt说明及Keil工程文件,整体约845KB,结构紧凑便于按模块查阅。资源已有1862人学习下载,配套「实验45 录音机实验」的示例代码与工程配置,可对照学习采样率设置、内存管理策略、SD卡文件读写及按键控制逻辑,适合作为课程实验或小型音频项目的起步参考,也能为调试录音质量与实时性提供排错思路。

1. 一块 STM32 最小系统板,怎么把声音录进去再放出来

手头有块 STM32 最小系统板,旁边躺着麦克风模块和一张 SD 卡,很多人第一反应是“这玩意儿能录音?”。能,而且不用操作系统,裸机就能跑。这个项目干的事很朴素:ADC 定时采样麦克风模拟信号,把 PCM 数据写进 SD 卡,按键触发开始和停止,录完再读出来送 DAC 播放。整套东西跑在 STM32F103 这类常见芯片上,外设用到 ADC、DAC、SDIO 或 SPI、定时器和 GPIO,属于典型的“外设组合拳”练手项目。适合刚学完外设驱动、想找一个能把多个模块串起来的完整例程的开发者,也适合想拿它当语音采集前端做二次开发的人。下面按“它怎么组织、怎么跑起来、哪里容易翻车、怎么改”的顺序拆。

2. 工程结构拆解:从采样到回放的模块分工

2.1 目录与文件职责

这类裸机录音机工程通常按“驱动层 + 应用层 + 板级配置”三层组织,目录不会太深,但每个文件职责要清楚。下面是一个典型布局,文件名可能因工程而异,但角色基本对应:

project/ ├── Core/ │ ├── Src/ │ │ ├── main.c # 初始化外设、主循环、按键状态机 │ │ ├── adc.c # ADC 采样配置与 DMA 搬运 │ │ ├── dac.c # DAC 输出配置与定时触发 │ │ ├── tim.c # 采样率定时器 │ │ ├── sd_diskio.c # SD 卡底层读写 │ │ └── recorder.c # 录音/回放业务逻辑 │ └── Inc/ │ └── recorder.h # 对外接口与缓冲区定义 ├── Drivers/ │ ├── CMSIS/ # 内核头文件 │ └── STM32F1xx_HAL_Driver/ # HAL 库 └── Middlewares/ └── FatFs/ # 文件系统

main.c只做初始化和调度,不写业务细节;recorder.c才是核心,负责“采一批、存一批、放一批”的循环。把业务从main.c里抽出来,是这类项目能不能二次开发的分水岭——很多人把所有逻辑堆在main的while(1)里,改一个采样率要翻半天。

2.2 采样链路:定时器触发 ADC + DMA

录音的关键是采样率稳定。用软件延时去读 ADC,采样间隔会被中断和文件写入拖得忽长忽短,回放时声音发飘。常见做法是定时器更新事件触发 ADC 转换,DMA 把结果搬进环形缓冲区,CPU 只在半满和全满中断里处理数据。

/* tim.c:采样率定时器,假设系统时钟 72MHz */ void MX_TIM3_Init(void) { TIM_HandleTypeDef htim3; htim3.Instance = TIM3; /* 72MHz / (71+1) = 1MHz 计数频率 */ htim3.Init.Prescaler = 71; /* 1MHz / (999+1) = 1kHz 更新频率,即 1kHz 采样率 */ htim3.Init.Period = 999; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; HAL_TIM_Base_Init(&htim3); /* 把 TIM3 的 TRGO 接到 ADC 的外部触发源 */ TIM_MasterConfigTypeDef sMasterConfig = {0}; sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig); }

Prescaler和Period两个参数直接决定采样率,公式是采样率 = 定时器时钟 / ((Prescaler+1) * (Period+1))。想改成 8kHz 语音采样,就把Period调到 124,或者改预分频,别去动系统时钟。ADC 侧要配置成外部触发、连续转换关闭、DMA 循环模式,这样每次定时器溢出才转一次,数据由 DMA 自动填进缓冲区,CPU 不参与搬运。

2.3 存储链路:FatFs 写 WAV

裸机存音频最省事的格式是 WAV,因为它就是 44 字节文件头加裸 PCM,不需要编码器。工程里挂 FatFs,录音开始时先写一个占位头,录完回填数据长度字段。核心写入逻辑大致如下:

/* recorder.c:把 DMA 缓冲区里的 PCM 追加到 WAV 文件 */ FRESULT recorder_write_chunk(recorder_t *rec, const int16_t *buf, uint32_t samples) { UINT written; FRESULT res; /* PCM 是 16 位有符号,WAV 数据区按小端字节序存 */ res = f_write(&rec->file, buf, samples * sizeof(int16_t), &written); if (res != FR_OK || written != samples * sizeof(int16_t)) { return FR_DISK_ERR; /* 写失败,通常是卡慢或缓冲区太小 */ } rec->data_bytes += written; return FR_OK; }

f_write的返回值一定要判,SD 卡在连续写入时偶发忙等,写不进去还继续累加长度,最后文件头里的长度和实际数据对不上,播放器直接报文件损坏。缓冲区大小建议不小于 512 字节,和 SD 卡扇区对齐,能明显减少写放大。

2.4 回放链路:DAC + 定时器同步

回放是录音的逆过程:从文件读一块 PCM 到缓冲区,定时器以同样采样率触发 DAC 转换,DMA 把缓冲区逐个送进 DAC 数据寄存器。采样率必须和录音时一致,否则音调会变——录的时候 8kHz、放的时候 16kHz,声音会快一倍且发尖,这是最常见的“回放异常”来源。DAC 输出后接一个 RC 低通滤波再进功放或耳机,裸接会有明显的高频噪声。

3. 跑起来:从零到听见第一段录音

3.1 硬件连接与供电检查

先把线接对再谈代码。麦克风模块的输出接 ADC 某个通道(比如 PA0),模块供电电压要和 ADC 参考电压匹配,常见是 3.3V;SD 卡用 SPI 模式接线最省引脚,CS、SCK、MISO、MOSI 四根线加电源地;DAC 输出接 PA4 或 PA5,后面串一个 1k 电阻加 100nF 电容到地做低通,再进耳机。供电上有个血泪经验:SD 卡写入瞬间电流能到 100mA 级别,如果和 MCU 共用一条细线,电压会瞬间跌落导致复位,现象是“录到一半板子重启”。单独给 SD 卡供电路径加个电容,或者用独立稳压,能省掉大量排查时间。

3.2 时钟与 FatFs 配置

STM32F103 跑 72MHz 需要正确配置外部晶振和 PLL,HAL 里用SystemClock_Config生成。FatFs 的ffconf.h里几个开关必须打开:_USE_MKFS用于首次格式化卡、_FS_READONLY设为 0、_USE_LFN按需开长文件名。SPI 模式下 SD 卡初始化要降速,先 400kHz 以下完成识别,再切到高速,否则部分卡在冷启动时握手失败。

/* sd_diskio.c:SPI 模式下的初始化速率切换 */ static DSTATUS sd_initialize(BYTE pdrv) { /* 上电后先低速发 80 个时钟,让卡进入就绪态 */ spi_set_speed(SPI_SPEED_LOW); /* <= 400kHz */ if (sd_send_cmd0() != SD_OK) return STA_NOINIT; if (sd_send_cmd8() != SD_OK) { /* 部分卡不支持,忽略 */ } if (sd_acmd41_loop() != SD_OK) return STA_NOINIT; /* 识别成功后再提速,否则读写会超时 */ spi_set_speed(SPI_SPEED_HIGH); return 0; }

3.3 编译、烧录与首次录音验证

用 Keil 或 STM32CubeIDE 打开工程,确认芯片型号和下载器配置,编译无警告后烧录。上电后先看串口有没有打印“SD init ok”,没有就查接线和供电。按键按下开始录音,LED 常亮表示正在写卡;再按停止,LED 灭。把卡拔下来插电脑,应该能看到一个.wav文件,用系统播放器直接能放。第一次验证不要录太长,5 秒足够,先确认链路通,再逐步加时长压测。

提示:首次使用新卡建议先在工程里调用f_mkfs格式化一次,很多“写不进去”的问题其实是卡上是 exFAT 或未格式化。

4. 避坑与排查:录音机最容易翻车的五个地方

4.1 录出来的文件能打开但全是噪声

现象是 WAV 头正常、时长也对,播放全是沙沙声。原因多半是 ADC 通道配错或麦克风模块没有偏置电压。驻极体麦克风需要偏置电阻供电,模块输出是交流小信号,直接进 ADC 会落在负半轴被削掉。解决是确认模块有偏置电路,ADC 采样后做去直流处理,或者硬件上加隔直电容和偏置到 VCC/2。

4.2 录音中途停止,文件长度对不上

现象是录到几十秒后卡住,文件头里的数据长度小于实际写入量。原因是 SD 卡写入延迟超过缓冲区耗尽时间,f_write返回错误但代码没处理。解决是加大 DMA 双缓冲、把f_write的返回值纳入状态机,写失败时停止录音并回填正确长度,而不是继续累加。

4.3 回放速度明显不对

现象是声音变快变尖或变慢变沉。原因是录音和回放的定时器周期不一致,或者回放时用了不同的 DAC 触发源。解决是把采样率定义成宏,录音和回放共用同一个值,改一处即可,别在两处各写一个数字。

4.4 按键一按就复位

现象是按下录音键瞬间板子重启。原因是按键没有消抖,或者按键中断里做了耗时操作,加上 SD 卡上电瞬间的电流冲击。解决是按键用 10ms 定时扫描消抖,中断里只置标志位,实际动作放到主循环;同时检查电源走线。

4.5 换一张卡就不认

现象是原来那张卡能用,换卡后初始化失败。原因是不同卡的初始化时序和容量协议有差异,SPI 模式下高速切换时机太早。解决是把初始化降速阶段拉长,ACMD41轮询加超时重试,并在失败时打印卡的 OCR 寄存器值辅助判断。

5. 二次开发:把录音机改成可用的语音采集前端

5.1 改造方向与验证方法

这个工程的价值不在“能录能放”,而在它是一条完整的模拟信号采集到文件落盘的链路。往上加东西很自然:把 WAV 换成原始 PCM 流做实时传输、在采样中断里加一个简单的阈值检测做按键音触发、或者把 16 位单声道改成 8 位压缩降低存储压力。改采样率时记住一个约束:定时器周期和 DAC 回放必须同步改,改完用一段固定频率的正弦信号做回环验证,录进去再放出来,用示波器看频率是否一致,比耳朵听靠谱。

改造点改动位置验证方式
采样率调整tim.c的 Prescaler/Period正弦回环测频
加触发录音ADC 完成回调里做阈值判断拍手触发看文件是否生成
换存储介质sd_diskio.c替换为 Flash 或外部 SPI Flash写入后读回比对
加增益控制采样后乘系数并限幅观察波形是否削顶

5.2 一个具体技巧:双缓冲切换点别放在文件写入里

我一开始图省事,在 DMA 半满中断里直接调f_write,结果录音偶尔出现周期性咔哒声。后来把中断里的事改成只切换缓冲区指针并置标志,真正的写卡放到主循环,咔哒声消失。这个习惯从那以后我一直保留:中断里只做搬运和置位,任何可能阻塞的操作都挪到主循环,哪怕多一个标志变量。如果你也在这个工程上继续加功能,先把这条守住,能少走很多弯路。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 14:04:40

Git与GDB调试及进程管理的高效开发实践

1. 项目概述&#xff1a;当Git遇上GDB与进程管理在代码开发的世界里&#xff0c;版本控制和调试就像程序员的左右手。我经历过无数次深夜调试时&#xff0c;因为忘记保存修改前的版本而陷入困境&#xff1b;也遇到过在多进程环境下调试时&#xff0c;被复杂的进程关系搞得晕头转…

作者头像 李华
网站建设 2026/10/26 14:04:15

Skills系统:模块化设计加速AI专业化开发

1. 项目概述&#xff1a;Skills系统如何让AI快速专业化在AI应用开发领域&#xff0c;让通用模型快速具备专业能力一直是开发者面临的挑战。nanobot的Skills系统提供了一套优雅的解决方案&#xff0c;通过模块化设计让AI能够像搭积木一样快速组合不同领域的专业能力。这套系统最…

作者头像 李华
网站建设 2026/10/26 14:03:56

Linux命令行基础:高效操作与系统管理入门

1. Linux基础指令入门&#xff1a;为什么需要掌握命令行&#xff1f;在图形化界面大行其道的今天&#xff0c;很多初学者可能会疑惑&#xff1a;为什么还要学习这些看似晦涩的Linux命令&#xff1f;我刚开始接触Linux时也有同样的困惑&#xff0c;直到有一次服务器图形界面崩溃…

作者头像 李华
网站建设 2026/10/26 14:02:01

FPGA交通灯控制系统:并行状态机与紧急优先硬件实现

简介&#xff1a;本资源是一套基于FPGA实现的十字路口交通信号灯控制系统完整工程&#xff0c;面向数字电路与Verilog初学者、嵌入式系统课程设计学生及FPGA入门实践者&#xff0c;解决多状态时序控制、数码管动态倒计时显示、主支干道通行优先级调度等典型数字系统设计问题。压…

作者头像 李华
网站建设 2026/10/26 14:00:43

基于YOLO的工业纸箱检测系统开发全流程解析

1. 项目概述&#xff1a;基于深度学习的纸箱检测系统纸箱检测在物流仓储、智能制造等领域有着广泛的应用场景。传统的人工检测方式效率低下且容易出错&#xff0c;而基于计算机视觉的自动化检测方案正在逐步取代人工。这个项目采用YOLO系列算法的最新版本&#xff08;包括v5/v8…

作者头像 李华
网站建设 2026/10/26 13:59:38

大模型考点精讲:从Token、部署、微调到Agent应用

这段时间正好在帮几个零基础的朋友梳理大模型相关的考核重点&#xff0c;发现大家的第一反应就是想冲去下载模型、调API&#xff0c;结果卡在最基本的概念上。所谓大模型技术与应用&#xff0c;与其说是一门编程课&#xff0c;不如说是一场概念工程——你得先弄清楚Token、上下…

作者头像 李华