数码之家

 找回密码
 立即注册
搜索
查看: 939|回复: 14

[Arduino] 想把撸来的esp32c2用在小智AI上,但要用SPI和PWM模拟I2S才行,有没有会的大...

[复制链接]
发表于 2025-3-20 10:29:25 | 显示全部楼层 |阅读模式
本帖最后由 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,完全没有声音,抛砖引玉,大家看看怎么改。
  1. #include <SPI.h>
  2. #include <math.h>
  3. #include "driver/ledc.h"
  4. #include "driver/timer.h"

  5. // 引脚定义
  6. const int SPI_CLK_PIN = 20;  // I2S SCK
  7. const int PWM_WS_PIN = 10;   // I2S WS/LRCLK
  8. const int SPI_DATA_PIN = 19; // I2S SD

  9. // 音频参数(修正采样率匹配)
  10. const float frequency = 500.0;        // 500Hz正弦波
  11. const float samplingRate = 8000.0;    // 8kHz采样率 [[4]][[10]]
  12. const int amplitude = 32767;          // 16位PCM范围
  13. const int numSamples = samplingRate / frequency;

  14. // 生成正弦波表
  15. uint16_t sineWave[numSamples];
  16. volatile int dataIndex = 0;

  17. // WS信号配置(修正为8kHz)
  18. const int ledcChannel = 0;
  19. const int ledcFreq = 8000;            // 与采样率一致 [[7]][[9]]
  20. const int ledcResolution = 8;         // 8位分辨率

  21. // SPI时钟计算(修正为256kHz)
  22. const uint32_t spiClock = samplingRate * 16 * 2; // 8000*16*2=256kHz [[4]][[10]]

  23. // 定时器配置(同步采样率)
  24. const int timerDivider = 80000000 / samplingRate; // APB_CLK=80MHz

  25. void IRAM_ATTR onTimer(void* arg);

  26. void setup() {


  27.   Serial.begin(74880);
  28. Serial.printf("SPI Clock: %u Hz\n", spiClock);
  29. Serial.printf("WS Frequency: %u Hz\n", ledcFreq);
  30. delay(1000);
  31.   // 生成正弦波数据
  32.   for (int i = 0; i < numSamples; i++) {
  33.     float sample = sin(2.0 * PI * frequency * i / samplingRate);
  34.     sineWave[i] = (uint16_t)(amplitude * sample + amplitude);
  35.   }

  36.   // 初始化SPI(修正时钟频率)
  37.   SPI.begin(SPI_CLK_PIN, -1, SPI_DATA_PIN, -1);
  38.   SPI.beginTransaction(SPISettings(spiClock, MSBFIRST, SPI_MODE0)); // [[6]]

  39.   // 配置WS信号(LRCLK)
  40.   ledc_timer_config_t ledc_timer = {
  41.     .speed_mode = LEDC_LOW_SPEED_MODE,
  42.     .duty_resolution = LEDC_TIMER_8_BIT,
  43.     .timer_num = LEDC_TIMER_0,
  44.     .freq_hz = ledcFreq,  // 8kHz关键修正
  45.     .clk_cfg = LEDC_AUTO_CLK
  46.   };
  47.   ledc_timer_config(&ledc_timer);

  48.   ledc_channel_config_t ledc_channel = {
  49.     .gpio_num = PWM_WS_PIN,
  50.     .speed_mode = LEDC_LOW_SPEED_MODE,
  51.     .channel = LEDC_CHANNEL_0,
  52.     .timer_sel = LEDC_TIMER_0,
  53.     .duty = 128,         // 50%占空比(8位分辨率)
  54.     .hpoint = 0
  55.   };
  56.   ledc_channel_config(&ledc_channel);

  57.   // 配置定时器(与采样率同步)
  58.   timer_config_t config = {
  59.     .alarm_en = TIMER_ALARM_EN,
  60.     .counter_en = TIMER_START,
  61.     .intr_type = TIMER_INTR_LEVEL,
  62.     .counter_dir = TIMER_COUNT_UP,
  63.     .auto_reload = TIMER_AUTORELOAD_EN,
  64.     .divider = timerDivider  // 80MHz/8000=10000
  65.   };
  66.   timer_init(TIMER_GROUP_0, TIMER_0, &config);
  67.   timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
  68.   timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 1); // 每周期触发
  69.   timer_enable_intr(TIMER_GROUP_0, TIMER_0);
  70.   timer_isr_register(TIMER_GROUP_0, TIMER_0, onTimer, NULL, ESP_INTR_FLAG_IRAM, NULL);
  71.   timer_start(TIMER_GROUP_0, TIMER_0);
  72. }

  73. void IRAM_ATTR onTimer(void* arg) {
  74.   static bool wsState = false;
  75.   
  76.   // 在WS边沿切换时传输数据(符合I2S时序)
  77.   if(!wsState) {
  78.     SPI.transfer16(sineWave[dataIndex]); // 左声道传输
  79.     dataIndex = (dataIndex + 1) % numSamples;
  80.   }
  81.   wsState = !wsState; // 8kHz翻转(50%占空比)

  82.   // 清除中断标志
  83.   timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
  84. }

  85. void loop() {}
复制代码







本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

打赏

参与人数 1家元 +12 收起 理由
南宁谢工 + 12 優秀文章

查看全部打赏

发表于 2025-3-20 13:07:59 | 显示全部楼层
问问Deepseek看它会不会?
回复 支持 反对

使用道具 举报

发表于 2025-3-20 15:26:25 | 显示全部楼层
不要屏幕就行了。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-3-20 21:20:05 来自手机浏览器 | 显示全部楼层
猪小呆 发表于 2025-3-20 13:07
问问Deepseek看它会不会?

claude编程是最强的,DS问到后面出错就不会改了。
回复 支持 反对

使用道具 举报

发表于 2025-3-21 20:48:48 | 显示全部楼层
kindzhon 发表于 2025-3-20 21:20
claude编程是最强的,DS问到后面出错就不会改了。

请教,这个模块的I2C是那两个引脚,我看ESP8684介绍有I2C但引脚描述中却没有。
回复 支持 反对

使用道具 举报

发表于 2025-3-21 21:22:19 来自手机浏览器 | 显示全部楼层
我也撸了,竟然不支持i2c
回复 支持 反对

使用道具 举报

发表于 2025-3-22 05:29:34 来自手机浏览器 | 显示全部楼层
玛德陛下 发表于 2025-3-21 21:22
我也撸了,竟然不支持i2c

支持的,我测试没问题,就是目前双串口一直没测试好。我用来点亮屏幕了。ssd1306
回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-3-22 10:30:56 | 显示全部楼层
慕名而来 发表于 2025-3-21 20:48
请教,这个模块的I2C是那两个引脚,我看ESP8684介绍有I2C但引脚描述中却没有。 ...

我说的是I2S不是I2C
回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-3-22 10:36:15 | 显示全部楼层
玛德陛下 发表于 2025-3-21 21:22
我也撸了,竟然不支持i2c

我说的是I2S不是I2C
回复 支持 反对

使用道具 举报

发表于 2025-3-22 11:10:34 | 显示全部楼层
直接PWM输出接三极管接喇叭呗?
回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-4-9 23:33:37 | 显示全部楼层
珜羽 发表于 2025-3-22 11:10
直接PWM输出接三极管接喇叭呗?

那个难听的要命。
回复 支持 反对

使用道具 举报

发表于 2025-4-10 00:05:10 | 显示全部楼层



SmartArduino/DOIT_AI
https://github.com/SmartArduino/DOIT_AI

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-4-11 13:22:21 | 显示全部楼层
vip2128 发表于 2025-4-10 00:05
SmartArduino/DOIT_AI
https://github.com/SmartArduino/DOIT_AI

这个用了离线主意模块。
回复 支持 反对

使用道具 举报

发表于 2025-4-13 16:21:59 | 显示全部楼层
我觉得离线语音唤醒,这个比较好,可以改任意唤醒词,好!!!
回复 支持 反对

使用道具 举报

发表于 2025-4-16 22:39:27 | 显示全部楼层
kindzhon 发表于 2025-4-9 23:33
那个难听的要命。

又不是不能用.jpg
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-5-1 15:09 , Processed in 0.124800 second(s), 12 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

快速回复 返回顶部 返回列表