迷你小板子尺寸适配的esp32c3 super mini 开发板。(但这个板子射频电路不好,也是要慎用。)

音频小板子,用的NS4168音频i2s音频解码功放IC(资料:http://www.nsiway.com.cn/product/18.html)和MSM261S4030H0R数字麦克风(资料:https://item.szlcsc.com/datasheet/MSM261S4030H0R/3020642.html)
原理图非常简单,单纯就是根据IC资料拉线就完事了。

所以板子背面可以直接焊接esp32c3super mini,也可以通过杜邦线连接其他开发板。

根据如图与其他开发连接即可。
NS4168使用时需要注意一个问题,这个IC有个问题,在停止发送i2s音频数据后,芯片不会自动将喇叭输出的电压归零,有时会导致喇叭发热到烫手,所以在初始化后和音频播放结束后,一定要播放一段空白音频,让喇叭发出最后一个一个音时空白音频。
下面时一个基本录音和放音的范例,按住boot(IO9)开始录音,松开boot播放录音。esp32c3只有一路i2S音频,所以在录放两个设备切换前要重新初始化i2s。
#include "FS.h"
#include "SPIFFS.h"
#define FORMAT_SPIFFS_IF_FAILED true
#include <driver/i2s.h>
#define REC_I2S_WS 7
#define REC_I2S_SD 5
#define REC_I2S_SCK 6
//#define I2S_LR_RX 3
#define I2S_PORT_0 I2S_NUM_0
#define SAMPLE_RATE 16000
#define BUTTON_PIN 9
#define PLAY_I2S_LRC 3
#define PLAY_I2S_BCLK 2
#define PLAY_I2S_DIN 1
#define BUFSIZE 128
int16_t audioData[BUFSIZE]; //i2s缓存
int16_t zeroData[128]; //空白音频
uint recordingSize = 0;
bool wste = 0;
void setup() {
pinMode(BUTTON_PIN, INPUT);
for(int i = 0; i < 128; i ++){
zeroData[i] = 0x00;
}
Serial.begin(115200);
if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) { //初始化SPIFFS,存储音频数据
Serial.println("SPIFFS Mount Failed");
return;
}
//SPIFFS.format();
listDir(SPIFFS, "/", 0);
}
void loop() {
if (digitalRead(BUTTON_PIN) == LOW) {
delay(20);
if (digitalRead(BUTTON_PIN) == LOW) {
i2s_rx_init(); //初始化录音I2S模式
Serial.println("Recording...");
size_t bytes_read = 0;
recordingSize = 0;
File file = SPIFFS.open("/pcm", FILE_WRITE);
while (digitalRead(BUTTON_PIN) == LOW) {
esp_err_t result = i2s_read(I2S_PORT_0, audioData, sizeof(audioData), &bytes_read, portMAX_DELAY);
for (int i = 0; i < BUFSIZE; i++) {
int16_t data = audioData[i];
byte* bytes = (byte*)&data;
file.write(bytes, sizeof(int16_t));
}
recordingSize += bytes_read / 2;
}
file.close();
Serial.print(recordingSize);
Serial.println(" Recording complete.");
i2s_driver_uninstall(I2S_PORT_0);
wste = 1;
}
}
if (wste) {
i2s_tx_init(); //初始化播放I2S模式
Serial.println("Playing...");
size_t bytes_written = 0;
File file = SPIFFS.open("/pcm", FILE_READ);
if (!file) {
Serial.println("无法打开文件");
return;
}
while (file.available()) {
int bytesRead = file.readBytes((char*)audioData, sizeof(audioData));
if (bytesRead > 0) {
amplifyAudio(audioData,BUFSIZE,5); //放大音量
esp_err_t result = i2s_write(I2S_PORT_0, audioData, bytesRead, &bytes_written, portMAX_DELAY);
} else {
// End of file reached, close the file
file.close();
Serial.println("File closed");
}
}
esp_err_t result = i2s_write(I2S_PORT_0, zeroData, 16, &bytes_written, portMAX_DELAY);
Serial.println("Playing complete.");
i2s_zero_dma_buffer(I2S_PORT_0); // 清空I2S DMA缓冲区
i2s_driver_uninstall(I2S_PORT_0);
wste = 0;
}
}
void amplifyAudio(int16_t* aData, int dataSize, float gain) {
for (int i = 0; i < dataSize; i++) {
aData[i] = aData[i] * gain;
}
}
void i2s_rx_init() {
const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = i2s_bits_per_sample_t(16),
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
.intr_alloc_flags = 0, // default interrupt priority
.dma_buf_count = 8,
.dma_buf_len = 512,
.use_apll = false
};
esp_err_t err = i2s_driver_install(I2S_PORT_0, &i2s_config, 0, NULL);
if (err != ESP_OK) {
Serial.printf("I2S driver install failed (I2S_PORT_0): %d\n", err);
while (true)
;
}
const i2s_pin_config_t pin_config = {
.bck_io_num = REC_I2S_SCK,
.ws_io_num = REC_I2S_WS,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = REC_I2S_SD
};
err = i2s_set_pin(I2S_PORT_0, &pin_config);
if (err != ESP_OK) {
Serial.printf("I2S set pin failed (I2S_PORT_0): %d\n", err);
while (true)
;
}
}
void i2s_tx_init() {
const i2s_config_t i2sOut_config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = i2s_bits_per_sample_t(16),
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = BUFSIZE
};
esp_err_t err = i2s_driver_install(I2S_PORT_0, &i2sOut_config, 0, NULL);
if (err != ESP_OK) {
Serial.printf("I2S driver install failed (I2S_PORT_0): %d\n", err);
while (true)
;
}
const i2s_pin_config_t i2sOut_pin_config = {
.bck_io_num = PLAY_I2S_BCLK,
.ws_io_num = PLAY_I2S_LRC,
.data_out_num = PLAY_I2S_DIN,
.data_in_num = -1
};
err = i2s_set_pin(I2S_PORT_0, &i2sOut_pin_config);
if (err != ESP_OK) {
Serial.printf("I2S set pin failed (I2S_PORT_1): %d\n", err);
while (true)
;
}
}
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
Serial.printf("Listing directory: %s\r\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("- failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println(" - not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.path(), levels -1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print("\tSIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}