|
本帖最后由 kindzhon 于 2025-3-20 10:35 编辑
不少人都撸到了这个https://www.mydigit.cn/thread-501366-1-2.html
以为是esp32什么都有了,结果这货没有i2s,没有ulp,没有rtcram,主频最高只有120MHz,
但有蓝牙,有两路ADC,还是有点用。
可是没有I2S,在语音方面就非常麻烦了。
esp相关的audio库基本都不能用,
然后我看到了这个一文搞懂如何通过SPI+PWM模拟I2S
不知道哪个大神研究过软件模拟I2S没?如果有,做成arduino库,小智AI的成本又能降个档次了。
我们就只需要在MCU的外部产生两个PWM,一个作为I2S的CLK信号且连接到SPI的SPI_CLK上,另一个作为I2S的WS信号,然后SPI的SPI_DATA作为I2S_DATA信号进行输出。且让两个PWM同时输出,这样就实现了CLK、WS、DATA同时产生的目的。如下图:
我用claude3.5做了点研究,生成了500Hz正弦波代码,但连接了NS4168,完全没有声音,抛砖引玉,大家看看怎么改。
- #include <SPI.h>
- #include <math.h>
- #include "driver/ledc.h"
- #include "driver/timer.h"
- // 引脚定义
- const int SPI_CLK_PIN = 20; // I2S SCK
- const int PWM_WS_PIN = 10; // I2S WS/LRCLK
- const int SPI_DATA_PIN = 19; // I2S SD
- // 音频参数(修正采样率匹配)
- const float frequency = 500.0; // 500Hz正弦波
- const float samplingRate = 8000.0; // 8kHz采样率 [[4]][[10]]
- const int amplitude = 32767; // 16位PCM范围
- const int numSamples = samplingRate / frequency;
- // 生成正弦波表
- uint16_t sineWave[numSamples];
- volatile int dataIndex = 0;
- // WS信号配置(修正为8kHz)
- const int ledcChannel = 0;
- const int ledcFreq = 8000; // 与采样率一致 [[7]][[9]]
- const int ledcResolution = 8; // 8位分辨率
- // SPI时钟计算(修正为256kHz)
- const uint32_t spiClock = samplingRate * 16 * 2; // 8000*16*2=256kHz [[4]][[10]]
- // 定时器配置(同步采样率)
- const int timerDivider = 80000000 / samplingRate; // APB_CLK=80MHz
- void IRAM_ATTR onTimer(void* arg);
- void setup() {
- Serial.begin(74880);
- Serial.printf("SPI Clock: %u Hz\n", spiClock);
- Serial.printf("WS Frequency: %u Hz\n", ledcFreq);
- delay(1000);
- // 生成正弦波数据
- for (int i = 0; i < numSamples; i++) {
- float sample = sin(2.0 * PI * frequency * i / samplingRate);
- sineWave[i] = (uint16_t)(amplitude * sample + amplitude);
- }
- // 初始化SPI(修正时钟频率)
- SPI.begin(SPI_CLK_PIN, -1, SPI_DATA_PIN, -1);
- SPI.beginTransaction(SPISettings(spiClock, MSBFIRST, SPI_MODE0)); // [[6]]
- // 配置WS信号(LRCLK)
- ledc_timer_config_t ledc_timer = {
- .speed_mode = LEDC_LOW_SPEED_MODE,
- .duty_resolution = LEDC_TIMER_8_BIT,
- .timer_num = LEDC_TIMER_0,
- .freq_hz = ledcFreq, // 8kHz关键修正
- .clk_cfg = LEDC_AUTO_CLK
- };
- ledc_timer_config(&ledc_timer);
- ledc_channel_config_t ledc_channel = {
- .gpio_num = PWM_WS_PIN,
- .speed_mode = LEDC_LOW_SPEED_MODE,
- .channel = LEDC_CHANNEL_0,
- .timer_sel = LEDC_TIMER_0,
- .duty = 128, // 50%占空比(8位分辨率)
- .hpoint = 0
- };
- ledc_channel_config(&ledc_channel);
- // 配置定时器(与采样率同步)
- timer_config_t config = {
- .alarm_en = TIMER_ALARM_EN,
- .counter_en = TIMER_START,
- .intr_type = TIMER_INTR_LEVEL,
- .counter_dir = TIMER_COUNT_UP,
- .auto_reload = TIMER_AUTORELOAD_EN,
- .divider = timerDivider // 80MHz/8000=10000
- };
- timer_init(TIMER_GROUP_0, TIMER_0, &config);
- timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
- timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 1); // 每周期触发
- timer_enable_intr(TIMER_GROUP_0, TIMER_0);
- timer_isr_register(TIMER_GROUP_0, TIMER_0, onTimer, NULL, ESP_INTR_FLAG_IRAM, NULL);
- timer_start(TIMER_GROUP_0, TIMER_0);
- }
- void IRAM_ATTR onTimer(void* arg) {
- static bool wsState = false;
-
- // 在WS边沿切换时传输数据(符合I2S时序)
- if(!wsState) {
- SPI.transfer16(sineWave[dataIndex]); // 左声道传输
- dataIndex = (dataIndex + 1) % numSamples;
- }
- wsState = !wsState; // 8kHz翻转(50%占空比)
- // 清除中断标志
- timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
- }
- void loop() {}
复制代码
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
打赏
-
查看全部打赏
|