使用i2s遇到的问题
在使用i2s的时候原本以为用dma把数据搬运过去就万事大吉,但是搬运过去后喇叭播放听起来十分的难听。
i2s主要由
SCK(串行时钟):也叫位时钟(BCLK),每个时钟脉冲对应数据线的一位数据。
WS(字选择):也叫左右声道时钟(LRCK),用于选择左右声道。标准飞利浦模式下,WS=0表示左声道,WS=1表示右声道。
SD(串行数据):用于传输实际的音频数据。
有时还有MCLK(主时钟),用于为编解码器等提供参考时钟,但并非必需。
有四种工作模式,标准飞利浦模式,LSB左声道对齐,MSB右声道对齐,PCM模式
标准飞利浦模式
L-low

LSB
L-high
、
MSB
L-high


PCM

我使用的kf32a156,标准飞利浦模式,dma1发送 16位 18k
这款芯片在启动i2s前需要先发送一时钟数据启动i2s,如果不发送就会

导致我如果直接使用音频数据会左右声道颠倒,目前解决方法是,在音频数据前添加0x0000

这里可以看到ws采样率引脚提前一个时钟周期
i2s+dma初始化
init 代码
/**
* @brief: I2S init
* @param[in] None
* @param[out] None
* @retval : None
*/
void i2s_init(void)
{
/*chongzhi clock*/
/*fuwei dingshiqi T0T3*/
RST_SFR->CTL1 |= (uint32_t)1 << 25;
/*clean*/
RST_SFR->CTL1 &= ~((uint32_t)1 << 25);
/*shineng shizhong */
PCLK_SFR->CTL1 |= (uint32_t)1 << 25;
/*spi2peizhi*/
SPI1_SFR->CTLR &= ~(3 << 25 /*Clock frequency division selection in I2S mode */
| 3 << 22 /*I2S standard*/
| 3 << 20 /*clean master mode */
| 1 << 18 /*clean MCK mode*/
| 1 << 16 /*clean i2s mode*/
| 3 << 14 /*cs*/
| 3 << 12 /*clean bit */
| 1 << 9 /*clean edge*/
| 1 << 8 /*clean polarity*/
| 1 << 4 /*sclk*/
| 7 << 1 /*sclk / 4 */
| 1 << 27 /*pcm polarity*/
| 1 << 24 /*pcm frame synchronizer*/
| 1 << 0 /*clean enable*/
);
SPI1_SFR->CTLR |= (0 << 25 /*Clock frequency division selection in I2S mode */
| 2 << 22 /*I2S standard z*/
| 2 << 20 /*master mode */
| 0 << 18 /* MCK mode z*/
| 1 << 16 /* i2s mode*/
| 1 << 14 /*cs*/
| 1 << 12 /*16 bit */
// | 1 << 9 /*clean edge*/
// | 1 << 8 /*clean polarity*/
| 1 << 6 /*MSB*/
| 0 << 4 /*sclk*/
| 0 << 1 /*sclk / 4 */
| 0 << 27 /*pcm polarity*/
| 0 << 24 /*pcm frame synchronizer*/
// | 1 << 0 /*clean enable*/
);
/*baud*/
SPI1_SFR->BRGR &= ~(1 << 25 /*I2S clock accuracy fine-tuning bit */
| 1 << 24 /*The main device clock output is enabled */
| 0xFF << 16 /*I2S pre-division register (valid only for I2S)*/
// | 0xFFFF << 0 /**/
);
SPI1_SFR->BRGR |= (0 << 25 /*I2S clock accuracy fine-tuning bit */
| 0 << 24 /*The main device clock output is enabled */
| 0x68 << 16 /*I2S pre-division register (valid only for I2S)*/
// | 0x59 <<0/**/
);
/*shineng zhongduan*/
// SPI1_SFR->STR |= 1 << 14; /*RNEIE*/
// SPI1_SFR->STR |= 1 << 12; /*ROVFIE*/
/*enable*/
i2s_dma_init();
// SPI1_SFR->CTLR |= 1 << 0;
// I2S_Mode_Select(SPI1_SFR, TRUE);
INT_Interrupt_Priority_Config(INT_DMA1, 4, 0);
INT_Interrupt_Enable(INT_DMA1, TRUE);
INT_Clear_Interrupt_Flag(INT_DMA1);
}
/**
* @brief: I2S DMA init
* @param[in] None
* @param[out] None
* @retval : None
*/
void i2s_dma_init(void)
{
/*fuwei dingshiqi*/
RST_SFR->CTL2 |= (uint32_t)1 << 14;
/*clean*/
RST_SFR->CTL2 &= ~((uint32_t)1 << 14);
/*shineng shizhong */
PCLK_SFR->CTL2 |= (uint32_t)1 << 14;
DMA1_SFR->REMAP |= (1 << 7);
/*rx*/
// DMA1_SFR->PADDR6 = (uint32_t)&SPI1_SFR->BUFR;
// DMA0_SFR->MADDR6 = (uint32_t)rx;
// DMA1_SFR->CTLR6 &= ~((uint32_t)0x0FFFF << 16 /*clean tranfer */
// | 1 << 15 /*disenable memory to memory*/
// | 0x03 << 13 /*clean priority*/
// | 1 << 12 /*clean ONESHOT*/
// | 3 << 10 /*clean peripheral Data Width*/
// | 3 << 8 /*clean memory Data Width*/
// | 1 << 7 /*clean peripheral adress incrementation*/
// | 1 << 6 /*clean memory adress incrementation*/
// | 1 << 5 /*clean circulation*/
// | 1 << 4 /*clean dir*/
// | 1 << 3 /*clean BLKM*/
// | 1 << 2 /*clean DMAMODES*/
// | 1 << 0 /*enable*/
// );
// DMA1_SFR->CTLR6 |= ((uint32_t)0x200 << 16 /*tranfer num*/
// | 0 << 15 /*disenable memory to memory*/
// | 0x03 << 13 /*max priority*/
// | 0 << 12 /*clean ONESHOT*/
// | 1 << 10 /*peripheral Data Width 00 8;01 16;10 32*/
// | 1 << 8 /*memory Data Width 00 8;01 16;10 32*/
// | 0 << 7 /*clean peripheral adress incrementation*/
// | 1 << 6 /*memory adress incrementation*/
// | 0 << 5 /*clean circulation*/
// | 0 << 4 /*rx dir*/
// | 0 << 3 /*BLKM 0single data 1block transfer*/
// | 1 << 2 /*DMAMODES*/
// | 1 << 0 /*enable*/
// );
/*tx*/
DMA1_SFR->PADDR5 = (uint32_t)&(SPI1_SFR->BUFR); //(uint32_t)i2s_data23 ;
DMA1_SFR->MADDR5 = (uint32_t)(audio_player.buffer[0]) ; //i2s_stereo_buffer;
DMA1_SFR->CTLR5 &= ~((uint32_t)0x0FFFF << 16 /*clean tranfer */
| 1 << 15 /*disenable memory to memory*/
| 0x03 << 13 /*clean priority*/
| 1 << 12 /*clean ONESHOT*/
| 3 << 10 /*clean peripheral Data Width*/
| 3 << 8 /*clean memory Data Width*/
| 1 << 7 /*clean peripheral adress incrementation*/
| 1 << 6 /*clean memory adress incrementation*/
| 1 << 5 /*clean circulation*/
| 1 << 4 /*clean dir*/
| 1 << 3 /*clean BLKM*/
| 1 << 2 /*clean DMAMODES*/
| 1 << 0 /*enable*/
);
DMA1_SFR->CTLR5 |= ((uint32_t) 2048//50460<< 16 /*tranfer num*/ //(AUDIO_BUFFER_SIZE << 1)
| 0 << 15 /*disenable memory to memory*/
| 0x03 << 13 /*max priority*/
| 0 << 12 /*clean ONESHOT*/
| 1 << 10 /*peripheral Data Width 00 8;01 16;10 32*/
| 1 << 8 /*memory Data Width 00 8;01 16;10 32*/
| 0 << 7 /*clean peripheral adress incrementation*/
| 1 << 6 /*memory adress incrementation*/
| 0 << 5 /* circulation*/
| 1 << 4 /*tx dir*/
| 0 << 3 /*BLKM 0single data 1block transfer*/
| 1 << 2 /*DMAMODES*/
| 0 << 0 /*enable*/
);
// DMA1_SFR->LIFR &= ~(0x07 << 15); /*clean irq flag*/
// DMA1_SFR->LIER &= ~(1 << 15); /*clean finish*/
// DMA1_SFR->LIER |= (1 << 15);
DMA1_SFR->LIFR &= ~(0x07 << 12); /*clean irq flag*/
DMA1_SFR->LIER &= ~(1 << 12); /*clean finish*/
DMA1_SFR->LIER |= (1 << 12);
SPI1_SFR->STR |= (uint32_t)0x01 << 21;
}
/**
* @brief: I2S DMA stop
* @param[in] None
* @param[out] None
* @retval : None
*/
void i2s_stop(void)
{
SPI1_SFR->STR &= ~((uint32_t)0x01 << 21);
SPI1_SFR->CTLR &= ~(1 << 0);
}
软件方面尝试使用dma双缓冲区搬运在flash的音频数组,
但是并没有使用dma半中断,是标志位在主循环进行的采样,确实有问题,dma搬运不连续,目前想改成在中断中发送
下面是主循环的用法
app.h
#ifndef __APP_I2S_H
#define __APP_I2S_H
#include "stdint.h"
#define BUFFER_SIZE 2048 // 每次传输字节数
#define NUM_BUFFERS 2 // 双缓冲区
#define SILENCE_VALUE 0x00 // 8位PCM静音值
typedef struct {
const uint8_t *audio_data; // 原始音频数据
uint32_t total_size; // 总数据大小(50460)
uint32_t remaining_size; // 剩余数据大小
uint8_t buffer[NUM_BUFFERS][BUFFER_SIZE]; // 双缓冲区
volatile uint8_t active_buffer; // 当前DMA正在传输的缓冲区索引
volatile uint8_t buffer_ready[NUM_BUFFERS]; // 缓冲区准备就绪标志
volatile uint32_t bytes_transferred; // 已传输字节数
volatile uint8_t is_playing; // 播放状态标志
volatile uint8_t last_transfer; // 标记是否为最后一次传输
} AudioPlayer_t;
extern AudioPlayer_t audio_player;
extern const uint8_t play_audio[50460];
extern const uint8_t audio_data_49[53480];
extern const uint8_t audio_data_16[42528];
void Audio_on(const uint8_t *data,uint16_t lenth);
void audio_manager_task(void);
void I2S_DmaCpltCallback(void);
void audio_player_init(const uint8_t *data, uint32_t size);
void audio_start_playback(void);
#endif
app.c
#include "Rte.h"
#include "app_i2s.h"
#include "i2s_demo.h"
typedef uint32_t data_t;
AudioPlayer_t audio_player;
// 初始化音频播放器
void audio_player_init(const uint8_t *data, uint32_t size)
{
// memset(&audio_player, 0, sizeof(AudioPlayer_t));
audio_player.audio_data = data;
audio_player.total_size = size;
audio_player.remaining_size = size;
audio_player.is_playing = 0;
audio_player.last_transfer = 0;
audio_player.bytes_transferred = 0;
audio_player.active_buffer = 0;
// 初始时两个缓冲区都未准备
audio_player.buffer_ready[0] = 0;
audio_player.buffer_ready[1] = 0;
}
// 准备指定缓冲区
static void prepare_buffer(uint8_t buffer_index)
{
if (audio_player.remaining_size == 0)
{
// 没有数据了,填充静音
memset(audio_player.buffer[buffer_index], SILENCE_VALUE, BUFFER_SIZE);
audio_player.last_transfer = 1; // 标记这是最后一次数据传输
}
else
{
// 计算本次要拷贝的数据量
uint32_t copy_size = (audio_player.remaining_size > BUFFER_SIZE) ? BUFFER_SIZE : audio_player.remaining_size;
// 计算源数据偏移
uint32_t data_offset = audio_player.total_size - audio_player.remaining_size;
// 拷贝音频数据
memcpy(audio_player.buffer[buffer_index],
&audio_player.audio_data[data_offset],
copy_size);
// 如果拷贝的数据不足一个缓冲区,用静音填充剩余部分
if (copy_size < BUFFER_SIZE)
{
memset(&audio_player.buffer[buffer_index][copy_size],
SILENCE_VALUE,
BUFFER_SIZE - copy_size);
}
// 更新剩余数据量
audio_player.remaining_size -= copy_size;
}
// 标记缓冲区准备就绪
audio_player.buffer_ready[buffer_index] = 1;
}
// 启动音频播放
void audio_start_playback(void)
{
if (audio_player.is_playing)
{
return; // 已经在播放了
}
// 重置状态
audio_player.remaining_size = audio_player.total_size;
audio_player.bytes_transferred = 0;
audio_player.last_transfer = 0;
audio_player.is_playing = 1;
// 准备缓冲区0和缓冲区1
prepare_buffer(0);
prepare_buffer(1);
// 启动DMA传输缓冲区0
audio_player.active_buffer = 0;
audio_player.buffer_ready[0] = 0; // 标记为正在使用
DMA1_SFR->CTLR5 &= ~((uint32_t)0xFFFF << 16);
DMA1_SFR->CTLR5 |= ((uint32_t)BUFFER_SIZE << 16);
DMA1_SFR->MADDR5 = (data_t)(audio_player.buffer[0]);
DMA1_SFR->CTLR5 |= 1;
}
// DMA传输完成中断回调函数
void I2S_DmaCpltCallback(void)
{
// 检查是否是我们使用的I2S实例
if (!audio_player.is_playing)
{
return;
}
// 更新已传输字节数
audio_player.bytes_transferred += BUFFER_SIZE;
// 检查是否所有数据已传输完成
if (audio_player.last_transfer && audio_player.remaining_size == 0)
{
// 最后一次传输完成,停止播放
audio_player.is_playing = 0;
// 这里可以发送一个额外的静音缓冲区来清空管道
static uint8_t final_silence[BUFFER_SIZE];
memset(final_silence, SILENCE_VALUE, BUFFER_SIZE);
// 使用阻塞方式发送最后一次静音(避免再次进入中断)
// HAL_I2S_Transmit((uint16_t *)final_silence, BUFFER_SIZE / 2, 100);
for (int i = 0; i < BUFFER_SIZE; i++)
{
SPI_I2S_SendData8(SPI1_SFR, (final_silence[i]));
}
// 停止DMA和I2S
SPI1_SFR->STR &= ~((uint32_t)0x01 << 21);
SPI1_SFR->CTLR &= ~(1 << 0);
// printf("播放完成,总传输字节\n");
return;
}
// 切换活跃缓冲区索引
uint8_t current_buffer = audio_player.active_buffer;
uint8_t next_buffer = (current_buffer + 1) % NUM_BUFFERS;
// 如果下一个缓冲区已经准备好了
if (audio_player.buffer_ready[next_buffer])
{
// 标记当前缓冲区可重新使用
audio_player.buffer_ready[current_buffer] = 0;
// 切换活跃缓冲区
audio_player.active_buffer = next_buffer;
// 启动下一次传输
DMA1_SFR->CTLR5 &= ~((uint32_t)0xFFFF << 16);
DMA1_SFR->CTLR5 |= ((uint32_t)BUFFER_SIZE << 16);
DMA1_SFR->MADDR5 = (data_t)(audio_player.buffer[next_buffer]);
DMA1_SFR->CTLR5 |= 1;
// SPI_Cmd(SPI1_SFR, TRUE);
// 标记该缓冲区正在使用
audio_player.buffer_ready[next_buffer] = 0;
// 在主循环中准备刚刚释放的缓冲区
// 这里我们不直接在中断中准备,而是设置标志位
}
else
{
// 下一个缓冲区没准备好!发送静音应急
static uint8_t emergency_silence[BUFFER_SIZE];
memset(emergency_silence, SILENCE_VALUE, BUFFER_SIZE);
// 启动下一次传输
DMA1_SFR->CTLR5 &= ~((uint32_t)0xFFFF << 16);
DMA1_SFR->CTLR5 |= ((uint32_t)BUFFER_SIZE << 16);
DMA1_SFR->MADDR5 = (data_t)(emergency_silence);
DMA1_SFR->CTLR5 |= 1;
// SPI_Cmd(SPI1_SFR, TRUE);
}
}
/**
* @brief: 在主循环中调用,管理缓冲区准备
* @param[in] None
* @param[out] None
* @retval : None
*/
void audio_manager_task(void)
{
if (!audio_player.is_playing)
{
return;
}
// 检查哪些缓冲区需要准备
for (int i = 0; i < NUM_BUFFERS; i++)
{
// 如果缓冲区不是当前活跃的,且未就绪,且还有数据要播放
if (i != audio_player.active_buffer &&
!audio_player.buffer_ready[i] &&
(audio_player.remaining_size > 0 || !audio_player.last_transfer))
{
// 准备这个缓冲区
prepare_buffer(i);
// 如果这是最后一个缓冲区且数据已用完,标记为最后一次传输
if (audio_player.remaining_size == 0)
{
audio_player.last_transfer = 1;
}
break; // 一次只准备一个缓冲区
}
}
}
/**
* @brief: 主程序中使用,
* @param[in] None
* @param[out] None
* @retval : None
*/
void Audio_on(const uint8_t *data,uint16_t lenth)
{
// 初始化音频播放器
audio_player_init(data, lenth);
Audio_Start();
// 启动播放
audio_start_playback();
}
效果并不好,因为这款芯片,dma无法访问内部flash,所以并不能使用直接访问其地址的办法,除非写进ram,但是音频往往较大,所以我截取了一段小的音频,直接通过dma播放出来,但是效果十分不理想,声音听起来像蒙了一层东北雨姐他奶袜子一样
十分的闷不清翠
使用其他的芯片,播放同样的音频发现效果好了不少,硬件相同,这就不对劲了,为什么呢
使用py脚本对音频数据改成int16类型数据,并且可控制音量
需要现在命令行使用下面命令下载模块
pip install numpy librosa
wav输出int16数组
import numpy as np
import librosa
import os
import scipy.io.wavfile # 用于生成测试文件
def generate_c_array(input_wav_path, output_c_path, target_sr=18000, volume_factor=1.0):
"""
读取Wav文件,调整音量,转换为单声道,重采样,并生成符合I2S格式的C数组。
参数:
volume_factor: 音量系数 (float)。
1.0 = 原音量
0.5 = 50% 音量 (-6dB)
2.0 = 200% 音量 (+6dB,注意可能会削顶)
"""
# 1. 加载音频
print(f"正在读取并重采样音频到 {target_sr}Hz ...")
try:
# librosa 加载的数据是 float32 (-1.0 到 1.0)
y, sr = librosa.load(input_wav_path, sr=target_sr, mono=True)
except FileNotFoundError:
print(f"错误: 找不到文件 {input_wav_path}")
return
# --- [新增] 2. 音量调整 ---
print(f"正在应用音量缩放: {volume_factor * 100}%")
y = y * volume_factor
# --- [修改] 3. 转换为 int16 (带防削顶保护) ---
# 乘以 32767 转换为整数刻度
y_scaled = y * 32767
# !!! 关键步骤 !!!
# 如果 volume_factor > 1.0 或者原音频本身就在峰值,
# 乘法后的数值可能超过 32767 或低于 -32768。
# 直接转 int16 会导致数据溢出(Wrap around),产生严重的爆破噪音。
# 必须使用 clip 强制限制在 int16 范围内。
y_clamped = np.clip(y_scaled, -32768, 32767)
# 安全转换为 int16
y_int16 = y_clamped.astype(np.int16)
# 4. 构建 I2S 立体声数据 (Interleaved)
# 左声道有数据,右声道补 0
total_samples = len(y_int16)
i2s_buffer = np.zeros(total_samples * 2, dtype=np.int16)
# 偶数索引填入左声道数据
i2s_buffer[0::2] = y_int16
# 奇数索引保持为 0 (右声道)
# 5. 生成 C 语言文件内容
array_name = "audio_data_18k"
c_content = []
c_content.append(f"// Generated by Python Script")
c_content.append(f"// Source: {input_wav_path}")
c_content.append(f"// Sample Rate: {target_sr} Hz")
c_content.append(f"// Volume Factor: {volume_factor} ({(volume_factor*100):.0f}%)")
c_content.append(f"// Format: I2S Standard (Left=Data, Right=0), 16-bit")
c_content.append(f"// Total Samples (L+R): {len(i2s_buffer)}")
c_content.append(f"#include <stdint.h>\n")
c_content.append(f"const int16_t {array_name}[{len(i2s_buffer)}] = {{")
# 将数据格式化
data_str_list = []
for i, sample in enumerate(i2s_buffer):
data_str_list.append(f"{sample}")
if (i + 1) % 16 == 0:
c_content.append(" " + ", ".join(data_str_list) + ",")
data_str_list = []
if data_str_list:
c_content.append(" " + ", ".join(data_str_list))
c_content.append("};")
c_content.append(f"\nconst uint32_t {array_name}_len = sizeof({array_name}) / sizeof({array_name}[0]);")
# 6. 写入文件
print(f"正在写入 C 文件: {output_c_path} ...")
with open(output_c_path, 'w', encoding='utf-8') as f:
f.write("\n".join(c_content))
print("完成!")
# --- 配置与执行 ---
if __name__ == "__main__":
INPUT_WAV = "C:\\Users\\Administrator\\Desktop\\new_mav\\deng_1.wav"
OUTPUT_C = "audio_data.c"
TARGET_RATE = 18000
# --- 设置音量 ---
# 1.0 = 原始音量
# 0.8 = 80% 音量 (推荐用于测试,留一点余量)
# 2.0 = 200% 音量 (会被自动限制最大幅值,防止爆音)
VOLUME = 0.8
# 生成测试文件 (如果不存在)
if not os.path.exists(INPUT_WAV):
print(f"当前目录下没有 {INPUT_WAV},正在生成一个测试音频...")
fs = 44100
t = np.linspace(0, 1, fs)
data = np.sin(2 * np.pi * 440 * t) * 0.5
scipy.io.wavfile.write(INPUT_WAV, fs, (data * 32767).astype(np.int16))
generate_c_array(INPUT_WAV, OUTPUT_C, TARGET_RATE, volume_factor=VOLUME)



怀疑是赋值太大,削顶了
60%的音量听起来好像是好些
前面dma搬运分段的问题也是解决了,半完成中断加双缓冲区
dma双缓冲区 app_i2s.c
#include "Rte.h"
#include "app_i2s.h"
#include "i2s_demo.h"
#include <string.h>
AudioPlayer_Continuous_t player;
/**
* @brief: 8位无符号数据转 16位有符号 I2S 数据
* @param[in] None
* @param[out] None
* @retval : None
*/
static int16_t Convert_8bit_to_16bit_I2S(uint8_t sample8)
{
int16_t signed_sample = (int16_t)sample8 - 128;
return (int16_t)(signed_sample << 8);
}
/**
* @brief 填充 DMA 缓冲区的一半
* @param buffer_ptr: 指向 DMA 缓冲区中要填充的起始位置 (uint16_t*)
* @param len: 要填充的长度 (16位字数)
*/
static void Fill_DMA_Buffer(int16_t *buffer_ptr, uint32_t len)
{
uint32_t bytes_remaining = player.total_length - player.read_offset;
uint32_t i;
// 1. 如果还有数据,进行填充
if (bytes_remaining > 0)
{
uint32_t copy_count = (bytes_remaining >= len) ? len : bytes_remaining;
for (i = 0; i < copy_count; i++)
{
// uint8_t sample8 = player.source_data[player.read_offset + i];
// buffer_ptr[i] = Convert_8bit_to_16bit_I2S(sample8);
buffer_ptr[i] = player.source_data[player.read_offset + i];
}
// 更新全局读取偏移量
player.read_offset += copy_count;
// 如果数据不够填满这一半,剩下的补静音
if (copy_count < len)
{
for (; i < len; i++)
{
buffer_ptr[i] = 0x0000; // 16位静音值
}
}
}
// 2. 如果数据已经读完了,全部填静音
else
{
// 填充静音
for (i = 0; i < len; i++)
{
buffer_ptr[i] = 0x0000;
}
if (player.is_playing && player.stop_countdown == 0)
{
player.stop_countdown = 2; // 确保播放完两个静音块
}
}
}
/**
* @brief: Audio_Play_Start
* @param[in] None
* @param[out] None
* @retval : None
*/
void Audio_Play_Start(const data_t *data, uint32_t size)
{
if (player.is_playing)
return;
// 1. 初始化结构体
player.source_data = data;
player.total_length = size;
player.read_offset = 0;
player.is_playing = 1;
player.stop_countdown = 0; // 计数器初始化为 0
// 2. 预填充缓冲区
Fill_DMA_Buffer(&player.dma_buffer[0], AUDIO_CHUNK_SIZE);
Fill_DMA_Buffer(&player.dma_buffer[AUDIO_CHUNK_SIZE], AUDIO_CHUNK_SIZE);
// 3. 启动 DMA 循环传输
DMA1_SFR->CTLR5 &= ~((uint32_t)0xFFFF << 16);
DMA1_SFR->CTLR5 |= ((uint32_t)DMA_BUFFER_SIZE << 16);
DMA1_SFR->MADDR5 = (uint32_t)(player.dma_buffer);
DMA1_SFR->CTLR5 |= 1;
}
static void Handle_Stop_Countdown(void)
{
if (player.stop_countdown > 0)
{
player.stop_countdown--;
if (player.stop_countdown == 0)
{
Audio_Stop(); // 计数到 0 时,执行停止
}
}
}
/**
* @brief: 半传输完成中断
* @param[in] None
* @param[out] None
* @retval : None
*/
void _I2S_TxHalfCallback(void)
{
// 如果 is_playing 已经为 0,且计数器为 0,则直接返回
if (!player.is_playing && player.stop_countdown == 0)
return;
Handle_Stop_Countdown();
if (player.is_playing)
{
Fill_DMA_Buffer(&player.dma_buffer[0], AUDIO_CHUNK_SIZE);
}
}
/**
* @brief: 传输完成中断
* @param[in] None
* @param[out] None
* @retval : None
*/
void _I2S_TxCallback(void)
{
if (!player.is_playing && player.stop_countdown == 0)
return;
Handle_Stop_Countdown();
if (player.is_playing)
{
Fill_DMA_Buffer(&player.dma_buffer[AUDIO_CHUNK_SIZE], AUDIO_CHUNK_SIZE);
}
}
/**
* @brief: Audio_Stop
* @param[in] None
* @param[out] None
* @retval : None
*/
void Audio_Stop(void)
{
DMA1_SFR->CTLR5 &= ~(1);
player.is_playing = 0;
player.read_offset = 0;
player.stop_countdown = 0;
}
/**
* @brief: Audio_on
* @param[in] None
* @param[out] None
* @retval : None
*/
void Audio_on(void)
{
i2s_init();
Audio_Play_Start(audio_data_18k, 25230);
SPI_I2S_SendData32(SPI1_SFR, 0x0000); // 锟斤拷装锟斤拷1锟斤拷锟斤拷锟捷o拷锟斤拷锟斤拷锟斤拷锟斤拷I2S锟斤拷锟斤拷
SPI_Cmd(SPI1_SFR, TRUE);
}
dma双缓冲区 app_i2s.h
#ifndef __APP_I2S_H
#define __APP_I2S_H
#include "stdint.h"
#define SILENCE_VALUE 0x0000 // 8位PCM静音值
// I2S 传输是 16bit,所以缓冲区定义为 int16_t
// 这里的 2048 是总大小,分为前半(1024)和后半(1024)
#define DMA_BUFFER_SIZE 2048 // 这是一个 16位 数组的长度
#define AUDIO_CHUNK_SIZE (DMA_BUFFER_SIZE / 2) // 每次中断填充的一半大小 (1024)
typedef int16_t data_t;
typedef struct
{
const data_t *source_data; // 指向原始音频大数组 (uint8类型)
uint32_t total_length; // 总数据长度
volatile uint32_t read_offset; // 当前读取到的位置
int16_t dma_buffer[DMA_BUFFER_SIZE]; // DMA 循环使用的环形缓冲区
volatile uint8_t is_playing; // 播放状态
uint8_t stop_countdown; // 静音计数
} AudioPlayer_Continuous_t;
extern AudioPlayer_Continuous_t player;
extern const uint8_t play_audio[50460];
extern const uint8_t audio_data_49[53480];
extern const uint8_t audio_data_16[42528];
extern const int16_t audio_data_18k[25230];
/**
* @brief: Audio_Play_Start
* @param[in] None
* @param[out] None
* @retval : None
*/
void Audio_Play_Start(const data_t *data, uint32_t size);
/**
* @brief: 半传输完成中断
* @param[in] None
* @param[out] None
* @retval : None
*/
void _I2S_TxHalfCallback(void);
/**
* @brief: 传输完成中断
* @param[in] None
* @param[out] None
* @retval : None
*/
void _I2S_TxCallback(void);
/**
* @brief: Audio_Stop
* @param[in] None
* @param[out] None
* @retval : None
*/
void Audio_Stop(void);
/**
* @brief: Audio_on
* @param[in] None
* @param[out] None
* @retval : None
*/
void Audio_on(void);
#endif
浙公网安备 33010602011771号