使用i2s遇到的问题

在使用i2s的时候原本以为用dma把数据搬运过去就万事大吉,但是搬运过去后喇叭播放听起来十分的难听。

i2s主要由
SCK(串行时钟):也叫位时钟(BCLK),每个时钟脉冲对应数据线的一位数据。

WS(字选择):也叫左右声道时钟(LRCK),用于选择左右声道。标准飞利浦模式下,WS=0表示左声道,WS=1表示右声道。

SD(串行数据):用于传输实际的音频数据。

有时还有MCLK(主时钟),用于为编解码器等提供参考时钟,但并非必需。
有四种工作模式,标准飞利浦模式,LSB左声道对齐,MSB右声道对齐,PCM模式

标准飞利浦模式

L-low
image

LSB

L-high
image

MSB

L-high
image
image

PCM

image

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

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

这里可以看到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)

将同样的音频改了分别是100%、60%、40%

image

image

image

怀疑是赋值太大,削顶了
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

posted on 2025-12-11 13:36  li5920o  阅读(4)  评论(0)    收藏  举报

导航