FireRedASR Pro从零部署基于Keil5的嵌入式端音频采集固件开发如果你正在为FireRedASR Pro这样的语音识别系统寻找一个稳定可靠的音频前端自己动手在嵌入式设备上实现音频采集是个不错的选择。今天我们就来聊聊怎么用大家熟悉的Keil5 MDK开发环境给STM32芯片写一个音频采集固件。整个过程就像搭积木从配置芯片的“耳朵”ADC/I2S开始到让数据“跑起来”DMA最后打包发送出去。我会带你一步步走完并提供一个可以直接用的Keil工程示例。1. 开发前的准备工作在开始写代码之前我们需要把“工地”平整好把必要的工具和材料准备齐全。这能避免很多后续的麻烦。1.1 搭建Keil5开发环境首先你得有一个Keil MDK。如果你还没有安装可以去ARM官网下载安装包。安装过程就是一路“Next”注意安装路径不要有中文和空格。安装完成后别忘了最关键的一步安装对应你芯片型号的Device Family PackDFP。比如你用的是STM32F4系列就需要在Keil的Pack Installer里找到并安装“Keil::STM32F4xx_DFP”。这一步相当于给Keil5装上了识别和编译你芯片的“驱动程序”。1.2 准备硬件与工程框架硬件上你需要一块STM32开发板比如STM32F407 Discovery或Nucleo系列一个麦克风模块。麦克风分模拟和数字两种模拟麦克风输出模拟信号需要接芯片的ADC引脚数字麦克风如INMP441通常使用I2S接口直接输出数字音频流音质和抗干扰性更好我们这里以它为例。软件上我建议直接从ST官方的HAL库例程开始。在STM32CubeMX里选择你的芯片型号配置好时钟、I2S、DMA和UART然后生成一个Keil工程。这个生成的工程已经包含了所有底层硬件初始化代码是我们最好的起点能省去大量繁琐的配置工作。2. 配置音频采集的硬件模块硬件配置是固件的基石。这一步的目标是告诉芯片用哪个“耳朵”接口听声音听到的数据放哪里以及怎么高效地搬运这些数据。2.1 初始化I2S接口接收数字音频数字麦克风通过I2S总线传输数据。在STM32CubeMX中你需要启用一个SPI/I2S外设并将其模式设置为“I2S Philips Standard”角色设为“Master Receiver”。主时钟MCK是否输出取决于你的麦克风是否需要。最关键的是配置时钟参数音频采样率比如16kHz、数据位宽16位或32位和时钟极性。这些参数必须和你的麦克风模块规格一致。CubeMX会根据你的设置自动计算分频系数生成正确的初始化代码。2.2 配置DMA实现自动搬运让CPU一个个去读I2S数据寄存器效率太低了。DMA直接存储器访问才是幕后英雄它能在不打扰CPU的情况下自动把I2S接收到的数据搬到我们指定的内存数组中。在CubeMX中找到对应I2S外设的DMA请求添加一个DMA流方向设为外设到存储器模式设为循环模式Circular。这样当数组存满后DMA会自动回到开头重新填充实现连续不断的录音缓冲。2.3 设置音频数据缓冲区我们在内存里开辟一片区域作为音频数据的“中转仓库”。通常定义两个缓冲区Buffer比如audio_buffer[2][BUFFER_SIZE]采用乒乓缓冲机制。DMA正在填充Buffer A时我们的程序可以处理已经填满的Buffer B当Buffer A填满DMA会自动切换到Buffer B程序则开始处理Buffer A。如此循环实现采集和处理的并行。#define AUDIO_BUFFER_SIZE 512 // 每个缓冲区大小根据采样率和处理周期定 int16_t audio_buffer[2][AUDIO_BUFFER_SIZE]; volatile uint8_t current_buffer 0; // 指向当前DMA正在填充的缓冲区索引 // DMA传输完成一半中断半传输完成和传输完成中断回调函数 void HAL_I2S_RxHalfCpltCallback(I2S_HandleTypeDef *hi2s) { // 此时前半部分缓冲区已满可以处理 audio_buffer[current_buffer^1] 的前半部分 ready_to_process 1; process_buffer_id current_buffer ^ 1; process_half 0; } void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s) { // 此时后半部分缓冲区已满可以处理 audio_buffer[current_buffer^1] 的后半部分 ready_to_process 1; process_buffer_id current_buffer ^ 1; process_half 1; }3. 实现音频数据处理与发送数据采集进来后通常是原始的PCM流数据量较大。为了减轻传输压力我们常常需要先压缩一下然后再发送给上位机或服务器。3.1 集成ADPCM压缩算法ADPCM是一种简单的有损压缩算法能将16位PCM数据压缩到4位压缩比为4:1在保证可懂度的同时大幅减少数据量。你可以在网上找到开源的ADPCM编码库如adpcm.c和adpcm.h。将其添加到你的Keil工程中然后在DMA传输完成的中断回调函数里对刚采集到的一帧PCM数据调用编码函数。#include “adpcm.h” void Process_Audio_Buffer(int16_t *pcm_data, uint32_t size, uint8_t *encoded_output) { static adpcm_state_t encoder_state; // 初始化状态如果需要 // ... // 对pcm_data进行ADPCM编码结果存入encoded_output adpcm_coder(pcm_data, encoded_output, size, encoder_state); }3.2 通过UART发送音频数据对于调试或低速传输UART是最简单直接的方式。你需要先初始化一个UART外设如USART1设置好波特率如115200。在数据处理函数中将压缩后的ADPCM数据加上一个简单的帧头比如包含帧序号和数据长度然后通过HAL_UART_Transmit或HAL_UART_Transmit_IT中断模式发送出去。typedef struct { uint16_t sync_word; // 同步字如0xAA55 uint16_t seq_num; // 帧序号 uint16_t data_len; // 本帧数据长度 uint8_t data[]; // ADPCM数据 } audio_frame_t; void Send_Audio_Frame(uint8_t *adpcm_data, uint16_t len) { static uint16_t sequence 0; uint16_t frame_size sizeof(audio_frame_t) len; uint8_t *tx_buffer malloc(frame_size); audio_frame_t *frame (audio_frame_t*)tx_buffer; frame-sync_word 0xAA55; frame-seq_num sequence; frame-data_len len; memcpy(frame-data, adpcm_data, len); HAL_UART_Transmit(huart1, tx_buffer, frame_size, 1000); free(tx_buffer); }3.3 使用LWIP通过以太网发送进阶如果数据量大或需要网络接入可以使用LWIP库通过以太网发送。这比UART复杂需要配置MAC、PHY初始化LWIP协议栈并创建一个网络任务如果使用RTOS。你可以选择用UDP协议以较低的延迟发送到指定IP端口或者用TCP协议建立可靠连接。在网络任务中将封装好的音频帧通过udp_sendto或tcp_write函数发送。4. 整合与调试完整的Keil工程示例把所有模块像拼图一样组合起来并解决实际运行中可能出现的问题是整个开发过程中最有挑战也最有成就感的部分。4.1 工程文件结构与主循环设计一个清晰的工程结构有助于管理。你的Keil工程目录可能包含/DriversSTM32 HAL库文件。/Middlewares可能包含LWIP、ADPCM编码库等。/Src和/Inc你的主要应用代码。main.c程序入口。在main.c的主循环中逻辑应该清晰简洁int main(void) { HAL_Init(); SystemClock_Config(); MX_I2S2_Init(); // 初始化I2S MX_DMA_Init(); // 初始化DMA MX_USART1_UART_Init(); // 初始化UART // MX_LWIP_Init(); // 如果使用网络 // 启动I2S接收DMA开始自动搬运数据到audio_buffer HAL_I2S_Receive_DMA(hi2s2, (uint16_t*)audio_buffer, AUDIO_BUFFER_SIZE*2); while (1) { // 检查是否有缓冲区就绪标志 if (ready_to_process) { ready_to_process 0; // 1. 对就绪的缓冲区进行ADPCM编码 Process_Audio_Buffer((int16_t*)audio_buffer[process_buffer_id][process_half ? (AUDIO_BUFFER_SIZE/2) : 0], AUDIO_BUFFER_SIZE/2, encoded_buffer); // 2. 发送编码后的数据 Send_Audio_Frame(encoded_buffer, ADPCM_ENCODED_SIZE(AUDIO_BUFFER_SIZE/2)); // 如果使用LWIP在这里调用网络发送函数 } // 可以在这里加入看门狗喂狗、指示灯闪烁等维护性任务 HAL_Delay(1); } }4.2 常见问题与调试技巧第一次运行很可能不会一帆风顺。如果收不到数据首先用逻辑分析仪或示波器检查I2S的WS字选、SCK时钟和SD数据线是否有信号时序是否正确。如果数据全是零或噪声检查麦克风供电和初始化序列某些数字麦克风需要写寄存器配置。DMA传输问题可以检查缓冲区地址对齐、数据宽度是否匹配。利用好调试工具。可以在DMA传输完成中断里设置断点查看缓冲区数据是否被正确填充。通过UART打印一些调试信息如缓冲区索引、数据校验和也非常有帮助。对于实时性要求高的场景注意中断处理函数要尽可能短把耗时的压缩和发送操作放到主循环中。5. 总结与下一步走完这一趟你应该已经得到了一个能够稳定采集音频并通过串口输出的STM32固件了。整个过程的核心思路就是“配置硬件、自动搬运、处理发送”。用CubeMX配置底层硬件大大降低了门槛而乒乓缓冲和DMA中断的配合则是保证实时性的关键。这个固件作为FireRedASR Pro的前端已经可以工作了。你可以把它和电脑上的Python脚本连接起来脚本负责接收串口数据、解码ADPCM并发送给识别引擎。如果想更进一步可以考虑在固件端集成更复杂的算法比如VAD语音活动检测来只在有声音的时候发送数据或者尝试更高效的压缩算法。也可以把UART传输升级为Wi-Fi或4G Cat.1这样的无线方式让设备真正摆脱线缆的束缚。动手试试把这些想法变成现实吧。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
FireRedASR Pro从零部署:基于Keil5的嵌入式端音频采集固件开发
FireRedASR Pro从零部署基于Keil5的嵌入式端音频采集固件开发如果你正在为FireRedASR Pro这样的语音识别系统寻找一个稳定可靠的音频前端自己动手在嵌入式设备上实现音频采集是个不错的选择。今天我们就来聊聊怎么用大家熟悉的Keil5 MDK开发环境给STM32芯片写一个音频采集固件。整个过程就像搭积木从配置芯片的“耳朵”ADC/I2S开始到让数据“跑起来”DMA最后打包发送出去。我会带你一步步走完并提供一个可以直接用的Keil工程示例。1. 开发前的准备工作在开始写代码之前我们需要把“工地”平整好把必要的工具和材料准备齐全。这能避免很多后续的麻烦。1.1 搭建Keil5开发环境首先你得有一个Keil MDK。如果你还没有安装可以去ARM官网下载安装包。安装过程就是一路“Next”注意安装路径不要有中文和空格。安装完成后别忘了最关键的一步安装对应你芯片型号的Device Family PackDFP。比如你用的是STM32F4系列就需要在Keil的Pack Installer里找到并安装“Keil::STM32F4xx_DFP”。这一步相当于给Keil5装上了识别和编译你芯片的“驱动程序”。1.2 准备硬件与工程框架硬件上你需要一块STM32开发板比如STM32F407 Discovery或Nucleo系列一个麦克风模块。麦克风分模拟和数字两种模拟麦克风输出模拟信号需要接芯片的ADC引脚数字麦克风如INMP441通常使用I2S接口直接输出数字音频流音质和抗干扰性更好我们这里以它为例。软件上我建议直接从ST官方的HAL库例程开始。在STM32CubeMX里选择你的芯片型号配置好时钟、I2S、DMA和UART然后生成一个Keil工程。这个生成的工程已经包含了所有底层硬件初始化代码是我们最好的起点能省去大量繁琐的配置工作。2. 配置音频采集的硬件模块硬件配置是固件的基石。这一步的目标是告诉芯片用哪个“耳朵”接口听声音听到的数据放哪里以及怎么高效地搬运这些数据。2.1 初始化I2S接口接收数字音频数字麦克风通过I2S总线传输数据。在STM32CubeMX中你需要启用一个SPI/I2S外设并将其模式设置为“I2S Philips Standard”角色设为“Master Receiver”。主时钟MCK是否输出取决于你的麦克风是否需要。最关键的是配置时钟参数音频采样率比如16kHz、数据位宽16位或32位和时钟极性。这些参数必须和你的麦克风模块规格一致。CubeMX会根据你的设置自动计算分频系数生成正确的初始化代码。2.2 配置DMA实现自动搬运让CPU一个个去读I2S数据寄存器效率太低了。DMA直接存储器访问才是幕后英雄它能在不打扰CPU的情况下自动把I2S接收到的数据搬到我们指定的内存数组中。在CubeMX中找到对应I2S外设的DMA请求添加一个DMA流方向设为外设到存储器模式设为循环模式Circular。这样当数组存满后DMA会自动回到开头重新填充实现连续不断的录音缓冲。2.3 设置音频数据缓冲区我们在内存里开辟一片区域作为音频数据的“中转仓库”。通常定义两个缓冲区Buffer比如audio_buffer[2][BUFFER_SIZE]采用乒乓缓冲机制。DMA正在填充Buffer A时我们的程序可以处理已经填满的Buffer B当Buffer A填满DMA会自动切换到Buffer B程序则开始处理Buffer A。如此循环实现采集和处理的并行。#define AUDIO_BUFFER_SIZE 512 // 每个缓冲区大小根据采样率和处理周期定 int16_t audio_buffer[2][AUDIO_BUFFER_SIZE]; volatile uint8_t current_buffer 0; // 指向当前DMA正在填充的缓冲区索引 // DMA传输完成一半中断半传输完成和传输完成中断回调函数 void HAL_I2S_RxHalfCpltCallback(I2S_HandleTypeDef *hi2s) { // 此时前半部分缓冲区已满可以处理 audio_buffer[current_buffer^1] 的前半部分 ready_to_process 1; process_buffer_id current_buffer ^ 1; process_half 0; } void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s) { // 此时后半部分缓冲区已满可以处理 audio_buffer[current_buffer^1] 的后半部分 ready_to_process 1; process_buffer_id current_buffer ^ 1; process_half 1; }3. 实现音频数据处理与发送数据采集进来后通常是原始的PCM流数据量较大。为了减轻传输压力我们常常需要先压缩一下然后再发送给上位机或服务器。3.1 集成ADPCM压缩算法ADPCM是一种简单的有损压缩算法能将16位PCM数据压缩到4位压缩比为4:1在保证可懂度的同时大幅减少数据量。你可以在网上找到开源的ADPCM编码库如adpcm.c和adpcm.h。将其添加到你的Keil工程中然后在DMA传输完成的中断回调函数里对刚采集到的一帧PCM数据调用编码函数。#include “adpcm.h” void Process_Audio_Buffer(int16_t *pcm_data, uint32_t size, uint8_t *encoded_output) { static adpcm_state_t encoder_state; // 初始化状态如果需要 // ... // 对pcm_data进行ADPCM编码结果存入encoded_output adpcm_coder(pcm_data, encoded_output, size, encoder_state); }3.2 通过UART发送音频数据对于调试或低速传输UART是最简单直接的方式。你需要先初始化一个UART外设如USART1设置好波特率如115200。在数据处理函数中将压缩后的ADPCM数据加上一个简单的帧头比如包含帧序号和数据长度然后通过HAL_UART_Transmit或HAL_UART_Transmit_IT中断模式发送出去。typedef struct { uint16_t sync_word; // 同步字如0xAA55 uint16_t seq_num; // 帧序号 uint16_t data_len; // 本帧数据长度 uint8_t data[]; // ADPCM数据 } audio_frame_t; void Send_Audio_Frame(uint8_t *adpcm_data, uint16_t len) { static uint16_t sequence 0; uint16_t frame_size sizeof(audio_frame_t) len; uint8_t *tx_buffer malloc(frame_size); audio_frame_t *frame (audio_frame_t*)tx_buffer; frame-sync_word 0xAA55; frame-seq_num sequence; frame-data_len len; memcpy(frame-data, adpcm_data, len); HAL_UART_Transmit(huart1, tx_buffer, frame_size, 1000); free(tx_buffer); }3.3 使用LWIP通过以太网发送进阶如果数据量大或需要网络接入可以使用LWIP库通过以太网发送。这比UART复杂需要配置MAC、PHY初始化LWIP协议栈并创建一个网络任务如果使用RTOS。你可以选择用UDP协议以较低的延迟发送到指定IP端口或者用TCP协议建立可靠连接。在网络任务中将封装好的音频帧通过udp_sendto或tcp_write函数发送。4. 整合与调试完整的Keil工程示例把所有模块像拼图一样组合起来并解决实际运行中可能出现的问题是整个开发过程中最有挑战也最有成就感的部分。4.1 工程文件结构与主循环设计一个清晰的工程结构有助于管理。你的Keil工程目录可能包含/DriversSTM32 HAL库文件。/Middlewares可能包含LWIP、ADPCM编码库等。/Src和/Inc你的主要应用代码。main.c程序入口。在main.c的主循环中逻辑应该清晰简洁int main(void) { HAL_Init(); SystemClock_Config(); MX_I2S2_Init(); // 初始化I2S MX_DMA_Init(); // 初始化DMA MX_USART1_UART_Init(); // 初始化UART // MX_LWIP_Init(); // 如果使用网络 // 启动I2S接收DMA开始自动搬运数据到audio_buffer HAL_I2S_Receive_DMA(hi2s2, (uint16_t*)audio_buffer, AUDIO_BUFFER_SIZE*2); while (1) { // 检查是否有缓冲区就绪标志 if (ready_to_process) { ready_to_process 0; // 1. 对就绪的缓冲区进行ADPCM编码 Process_Audio_Buffer((int16_t*)audio_buffer[process_buffer_id][process_half ? (AUDIO_BUFFER_SIZE/2) : 0], AUDIO_BUFFER_SIZE/2, encoded_buffer); // 2. 发送编码后的数据 Send_Audio_Frame(encoded_buffer, ADPCM_ENCODED_SIZE(AUDIO_BUFFER_SIZE/2)); // 如果使用LWIP在这里调用网络发送函数 } // 可以在这里加入看门狗喂狗、指示灯闪烁等维护性任务 HAL_Delay(1); } }4.2 常见问题与调试技巧第一次运行很可能不会一帆风顺。如果收不到数据首先用逻辑分析仪或示波器检查I2S的WS字选、SCK时钟和SD数据线是否有信号时序是否正确。如果数据全是零或噪声检查麦克风供电和初始化序列某些数字麦克风需要写寄存器配置。DMA传输问题可以检查缓冲区地址对齐、数据宽度是否匹配。利用好调试工具。可以在DMA传输完成中断里设置断点查看缓冲区数据是否被正确填充。通过UART打印一些调试信息如缓冲区索引、数据校验和也非常有帮助。对于实时性要求高的场景注意中断处理函数要尽可能短把耗时的压缩和发送操作放到主循环中。5. 总结与下一步走完这一趟你应该已经得到了一个能够稳定采集音频并通过串口输出的STM32固件了。整个过程的核心思路就是“配置硬件、自动搬运、处理发送”。用CubeMX配置底层硬件大大降低了门槛而乒乓缓冲和DMA中断的配合则是保证实时性的关键。这个固件作为FireRedASR Pro的前端已经可以工作了。你可以把它和电脑上的Python脚本连接起来脚本负责接收串口数据、解码ADPCM并发送给识别引擎。如果想更进一步可以考虑在固件端集成更复杂的算法比如VAD语音活动检测来只在有声音的时候发送数据或者尝试更高效的压缩算法。也可以把UART传输升级为Wi-Fi或4G Cat.1这样的无线方式让设备真正摆脱线缆的束缚。动手试试把这些想法变成现实吧。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。