博客 / 詳情

返回

使用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

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.