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