当前位置: 首页 > news >正文

STM32上使用HAL库完美实现驱动MAX98357声卡模块(I2S+DMA+音频环形缓冲区)

一、概述

        MAX98357是一个性能非常好的声卡功放芯片,但是由于该芯片是使用I²S串行总线来驱动的,而STM32只有F4/F7/H7等高端系列才有I²S外设所以目前网上STM32驱动MAX98357的资料还是比较少的,基本上都是ESP32的。

        再者就是我们要把I²S 和 I²C 的区别开来,它们是为完全不同的目的而设计两种通信协议I²S 是用于传输音频数据的,I²C 是一般用于控制芯片的。

详细解释:

I²S - 音频传输专家

I²S 的唯一目标就是无损地传输数字音频。它的设计非常简单直接,有3根总线::

  • SD: 传输实际的音频数据(比如左声道的16位样本,然后是右声道的16位样本)。

  • SCK: 为每个数据位提供一个时钟脉冲。

  • WS: 告诉接收端当前传输的是左声道数据还是右声道数据。当WS为低电平时通常表示左声道,高电平表示右声道。

 I²C - 控制总线管家

I²C 是为系统中各种芯片之间的短距离、低速通信而设计的。它就像一个系统管家,负责询问和配置各个部件。I²C有2根总线:

  • SDA: 用于传输和接收数据。这根线是双向的。

  • SCL: 由主设备产生,同步数据传输。

二、硬件连接

        经过查询STM32资料,我们可以知道STM32的I²S是基于SPI实现的,那我就拿STM32F429来举个例子,通过查询STM32F429 的数据手册和参考手册:

STM32F429的SPI和I²S的引脚定义总结表:

I2S 外设信号引脚选项复用 AF备注
I2S1 (SPI1)WS (LRCK)PA4 / PB12AF5帧同步信号
CK (SCK)PB3 / PA5AF5时钟
SD (SDI/SDO)PA7 / PB5AF5数据线
MCKPC4AF5主时钟输出(可选)
I2S2 (SPI2)WSPB9 / PB12AF5帧同步信号
CKPB10 / PB13AF5时钟
SDPB15 / PC3AF5数据线
MCKPC6AF5主时钟输出
I2S3 (SPI3)WSPA4 / PA15AF6帧同步信号
CKPB3AF6时钟
SDPB5AF6数据线
MCKPC7AF6主时钟输出
I2S2ext / I2S3extSD_EXT(全双工 Rx)PC2(I2S2ext) / PC11(I2S3ext)AF6专用于接收通道

STM32F429的DMA1和DMA2的 请求映射表:

下面就是STM32F429的复用功能映射表:

        例如:我们采用STM32F429I2S2 来驱动我们的MAX98357,那么通过查询上面的表格,我们可以清楚的总结出来以下关键的软硬件参数配置

 GPIO引脚和复用功能使用:

I2S2 (SPI2)WSPB9 / PB12AF5帧同步信号
CKPB10 / PB13AF5时钟
SDPB15 / PC3AF5数据线
MCKPC6AF5主时钟输出

DMA通道和数据流使用: DMA1  通道0  数据流4

三、I2S驱动代码编写

1.定义I2S和DMA句柄

I2S_HandleTypeDef hi2s2;  
DMA_HandleTypeDef hdma_spi2_tx;  

2.配置和初始化I2S

// I2S2初始化函数
void MX_I2S2_Init(void)
{__HAL_RCC_SPI2_CLK_ENABLE();hi2s2.Instance = SPI2;hi2s2.Init.Mode = I2S_MODE_MASTER_TX;hi2s2.Init.Standard = I2S_STANDARD_PHILIPS;hi2s2.Init.DataFormat = I2S_DATAFORMAT_16B;hi2s2.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_22K;hi2s2.Init.CPOL = I2S_CPOL_LOW;hi2s2.Init.ClockSource = I2S_CLOCK_PLL;hi2s2.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;if (HAL_I2S_Init(&hi2s2) != HAL_OK){printf("HAL_I2S_Init Error !\r\n");while(1);}
}

3.编写I2S的初始化的MSP回调函数


void HAL_I2S_MspInit(I2S_HandleTypeDef* hi2s)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if (hi2s->Instance == SPI2) {/* 使能外设时钟 */__HAL_RCC_SPI2_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/* 2. 配置I2S3引脚 (WS, SCK, SD, MCK)  */GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_15;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* 3. 配置DMA */__HAL_RCC_DMA1_CLK_ENABLE();hdma_spi2_tx.Instance = DMA1_Stream4;hdma_spi2_tx.Init.Channel = DMA_CHANNEL_0;hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE;hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_spi2_tx.Init.Mode = DMA_NORMAL;
//		hdma_spi2_tx.Init.Mode = DMA_CIRCULAR;hdma_spi2_tx.Init.Priority = DMA_PRIORITY_HIGH;hdma_spi2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if (HAL_DMA_Init(&hdma_spi2_tx) != HAL_OK) {printf("HAL_DMA_Init Error !\r\n");while(1);}__HAL_LINKDMA(hi2s, hdmatx, hdma_spi2_tx);/* 配置中断优先级并使能 */HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn);}
}

4.编写DMA中断服务函数

/* DMA IRQ Handler(放在 stm32f4xx_it.c 或同文件) */void DMA1_Stream4_IRQHandler(void)
{HAL_DMA_IRQHandler(&hdma_spi2_tx);
}

5.编写I2S传输回调函数

void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{/* 半完成回调(双缓冲/分段填充时使用)*/(void)hi2s;
}/* 可选:I2S/ DMA 回调(当需要在回调中填充下一个缓冲区时使用) */
void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s)
{/* 完成回调(如果需要)*/(void)hi2s;
}

6.编写音频生成代码(正弦波声音)


/* 生成正弦波(立体声交织):* 参数:*   buffer: 指向 int16_t 数组,长度 = frames*2*   frames: 要生成的帧数(每帧含 L 和 R)*   amplitude: 0.0 - 1.0*   frequency: 正弦频率(Hz)*   sample_rate: 采样率(Hz)*/
void generate_sine_wave(int16_t* buffer, uint32_t frames, float amplitude, float frequency, uint32_t sample_rate)
{for (uint32_t n = 0; n < frames; n++){float t = (float)n / (float)sample_rate;float sample_f = amplitude * sinf(2.0f * PI * frequency * t);/* 限幅 */if (sample_f > 1.0f) sample_f = 1.0f;if (sample_f < -1.0f) sample_f = -1.0f;int16_t sample = (int16_t)(sample_f * 32767.0f);/* 交织到左右声道,这里左右相同(单声道复制为立体声) */buffer[2 * n]     = sample; /* Left */buffer[2 * n + 1] = sample; /* Right */}
}

当然,如果你的MCU的RAM空间足够那你也可以测试下生成个《欢乐颂》音频:


// 生成单个音符的函数
uint32_t generate_note(int16_t* buffer, float frequency, uint32_t duration, float amplitude, uint32_t sample_rate)
{// 添加淡入淡出效果,避免爆音uint32_t fade_duration = duration / 10; // 淡入淡出时间为音符时长的10%if (fade_duration < 50) fade_duration = 50; // 最小淡入淡出时间for (uint32_t i = 0; i < duration; i++) {// 计算正弦波样本float sample = amplitude * sin(2 * PI * frequency * i / sample_rate);// 应用淡入淡出效果float fade_factor = 1.0f;if (i < fade_duration) {// 淡入fade_factor = (float)i / fade_duration;} else if (i > duration - fade_duration) {// 淡出fade_factor = (float)(duration - i) / fade_duration;}buffer[i] = (int16_t)(sample * fade_factor * 32767);
//		printf("%x ",buffer[i]);}return duration;
}// 生成《欢乐颂》旋律的函数
void generate_ode_to_joy(int16_t* buffer, uint32_t frames, float amplitude, uint32_t sample_rate)
{uint32_t position = 0;// 第一小节: E E F Gposition += generate_note(buffer + position, NOTE_E4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_E4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_F4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_G4, QUARTER_NOTE, amplitude, sample_rate);// 第二小节: G F E Dposition += generate_note(buffer + position, NOTE_G4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_F4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_E4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_D4, QUARTER_NOTE, amplitude, sample_rate);// 第三小节: C C D Eposition += generate_note(buffer + position, NOTE_C4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_C4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_D4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_E4, QUARTER_NOTE, amplitude, sample_rate);// 第四小节: E D D (延长)position += generate_note(buffer + position, NOTE_E4, HALF_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_D4, HALF_NOTE, amplitude, sample_rate);// 第五小节: E E F Gposition += generate_note(buffer + position, NOTE_E4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_E4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_F4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_G4, QUARTER_NOTE, amplitude, sample_rate);// 第六小节: G F E Dposition += generate_note(buffer + position, NOTE_G4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_F4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_E4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_D4, QUARTER_NOTE, amplitude, sample_rate);// 第七小节: C C D Eposition += generate_note(buffer + position, NOTE_C4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_C4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_D4, QUARTER_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_E4, QUARTER_NOTE, amplitude, sample_rate);// 第八小节: D C C (延长)position += generate_note(buffer + position, NOTE_D4, HALF_NOTE, amplitude, sample_rate);position += generate_note(buffer + position, NOTE_C4, HALF_NOTE, amplitude, sample_rate);// 填充剩余部分为静音while (position < frames) {buffer[position++] = 0;}
}

   7.编写测试用例

void MAX98357_Test(void)
{static uint8_t PLAY = 1;uint32_t ret = 0;uint32_t CNT = 0;int16_t *p = NULL; uint16_t buffSize = 0;MX_I2S2_Init();audio_buffer = (int16_t *)mymalloc(SRAMEX,AUDIO_BUFFER_SIZE*2);if(audio_buffer == NULL){printf("audio_buffer = NULL\r\n");while(1);}
//	generate_sine_wave((int16_t *)audio_buffer, AUDIO_BUFFER_SIZE,AUDIO_AMPLITUDE, 1000.0f, 22050);	//生成 1kHz 正弦,采样率 44100,振幅 AUDIO_AMPLITUDEgenerate_ode_to_joy(audio_buffer,AUDIO_BUFFER_SIZE,AUDIO_AMPLITUDE,22050);	// 生成《欢乐颂》旋律p = audio_buffer;CNT = AUDIO_BUFFER_SIZE;printf("audio_buffer is OK !(size=%d)\r\n",AUDIO_BUFFER_SIZE);while(1){if(KEY0_ON == Key_Scan(0)) PLAY = 1;if(PLAY){printf("Play music!\r\n");for(uint32_t i=0;i<CNT;){// 启动I2S DMA传输if(CNT - i > I2S_DMA_MAX_TX_SIZE){buffSize =  I2S_DMA_MAX_TX_SIZE;}else{buffSize = CNT - i;}HAL_I2S_Transmit_DMA(&hi2s2, (uint16_t*)p, buffSize);//HAL_I2S_Transmit(&hi2s2, (uint16_t*)p, buffSize,HAL_MAX_DELAY);//printf("i=%d buffSize=%d %p\r\n",i,buffSize,p);while(HAL_I2S_STATE_READY != HAL_I2S_GetState(&hi2s2) && PLAY){ret++;if(KEY0_ON == Key_Scan(0)) PLAY = 0;}if(PLAY == 0) {HAL_I2S_DMAStop(&hi2s2);printf("Stop music!ret=%d\r\n",ret);break;}p += buffSize;i += buffSize;	ret = 0;}p = audio_buffer; PLAY = 0;}LED0_TOGGLE();delay_ms(20);}
}

测试效果视频:

MAX98357测试视频

测试的STM32F429_MAX98357驱动代码下载:

【免费】STM32F429-MAX98357驱动代码资源-CSDN下载https://download.csdn.net/download/qq_34885669/92260957

使用逻辑分析仪查看I2S输出44K采样率的波形:

四、DMA环形缓冲区实现

        马上更新。。。。

http://www.dtcms.com/a/580950.html

相关文章:

  • 【React】打卡笔记,入门学习03:useState、useEffect、useRef、useMemo
  • M|烟花 (1995)
  • 平顶山网站建设2022年黄台片区
  • 人工智能的未来之路:华为全栈技术链与AI Agent应用实践
  • 基于openresty反向代理、dns劫持、实现对http请求、响应内容抓包
  • 智能体的范式革命:华为全栈技术链驱动下一代AI Agent
  • AI 边缘计算:决胜未来
  • 【Linux】网络层协议
  • 深入解析 WPF 中的 DataTemplateSelector:动态模板选择的艺术
  • svn: E155000:
  • 【C++】:C++基于微服务的即时通讯系统(2)
  • Apple Pay 与 Google Pay 开发与结算全流程文档
  • Babylon.js相机交互:从 ArcRotateCamera 输入禁用说起
  • 安徽工程建设信息网站进皖企业wordpress优酷视频插件
  • git推送操作时报错error: failed to push some refs
  • 小程序弱网 / 无网场景下 CacheManager 离线表单与拍照上传解决方案
  • 邹平做网站公司一般的美工可以做网站吗
  • Vue3.4 Effect 作用域 API 与 React Server Components 实战解析
  • 基于改进TransUNet的港口船只图像分割系统研究
  • LeetCode 4. 寻找两个正序数组的中位数(困难)
  • 宇宙的几何诗篇:当空间本身成为运动的主角
  • Javascript函数之函数的基本使用以及封装?
  • 力扣 寻找两个正序数组的中位数
  • 文库类网站建设建议及经验上海高风险区域最新
  • 建设工程自学网站网站建设及管理使用情况汇报
  • Java 多线程同步机制深度解析:从 synchronized 到 Lock
  • AR眼镜在核电操作智能监护应用技术方案|阿法龙XR云平台
  • Rust 练习册 :Nth Prime与素数算法
  • 杭州网站建设机构win7做网站服务器卡
  • 算法基础篇:(三)基础算法之枚举:暴力美学的艺术,从穷举到高效优化