I04 I2S speaker audio for S3 #15
@@ -4,6 +4,9 @@ set(priv_req driver)
|
|||||||
if(IDF_TARGET STREQUAL "esp32")
|
if(IDF_TARGET STREQUAL "esp32")
|
||||||
list(APPEND srcs wroom/hal_audio.c)
|
list(APPEND srcs wroom/hal_audio.c)
|
||||||
list(APPEND priv_req board_wroom)
|
list(APPEND priv_req board_wroom)
|
||||||
|
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||||
|
list(APPEND srcs s3/hal_audio.c)
|
||||||
|
list(APPEND priv_req board_jc4827w543)
|
||||||
else()
|
else()
|
||||||
list(APPEND srcs stub/hal_audio.c)
|
list(APPEND srcs stub/hal_audio.c)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "driver/i2s_std.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
|
#include "board.h"
|
||||||
|
|
||||||
|
#define SPK_SAMPLE_RATE 48000
|
||||||
|
#define TONE_HZ 2000
|
||||||
|
#define TONE_PERIOD_SMP (SPK_SAMPLE_RATE / TONE_HZ) /* 24 samples */
|
||||||
|
#define CHUNK_MS 10
|
||||||
|
#define CHUNK_FRAMES (SPK_SAMPLE_RATE / 1000 * CHUNK_MS) /* 480, multiple of period */
|
||||||
|
#define TONE_AMPLITUDE 12000
|
||||||
|
#define DMA_DESC_NUM 4
|
||||||
|
#define DMA_FRAME_NUM 240
|
||||||
|
|
||||||
|
static const char *TAG = "audio";
|
||||||
|
|
||||||
|
enum {
|
||||||
|
AUDIO_SHORT = 1,
|
||||||
|
AUDIO_ALARM,
|
||||||
|
AUDIO_CANCEL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static QueueHandle_t s_q;
|
||||||
|
static TaskHandle_t s_task;
|
||||||
|
static i2s_chan_handle_t s_tx;
|
||||||
|
static bool s_chan_on;
|
||||||
|
static bool s_inited;
|
||||||
|
|
||||||
|
static int16_t s_tone[CHUNK_FRAMES * 2];
|
||||||
|
static int16_t s_silence[CHUNK_FRAMES * 2];
|
||||||
|
|
||||||
|
static void chan_on(void)
|
||||||
|
{
|
||||||
|
if (!s_chan_on && s_tx != NULL) {
|
||||||
|
i2s_channel_enable(s_tx);
|
||||||
|
s_chan_on = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chan_off(void)
|
||||||
|
{
|
||||||
|
if (s_chan_on && s_tx != NULL) {
|
||||||
|
size_t w = 0;
|
||||||
|
i2s_channel_write(s_tx, s_silence, sizeof(s_silence), &w, 50);
|
||||||
|
i2s_channel_disable(s_tx);
|
||||||
|
s_chan_on = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool drain_cancel(void)
|
||||||
|
{
|
||||||
|
int msg;
|
||||||
|
while (xQueueReceive(s_q, &msg, 0) == pdTRUE) {
|
||||||
|
if (msg == AUDIO_CANCEL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write buf for ~ms wall time. DMA pacing makes each 10 ms write block
|
||||||
|
* in this task only; the UI path never touches I2S. */
|
||||||
|
static bool play_buf_ms(const int16_t *buf, size_t len, uint32_t ms)
|
||||||
|
{
|
||||||
|
TickType_t t0 = xTaskGetTickCount();
|
||||||
|
while ((xTaskGetTickCount() - t0) < pdMS_TO_TICKS(ms)) {
|
||||||
|
if (drain_cancel()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t w = 0;
|
||||||
|
if (i2s_channel_write(s_tx, buf, len, &w, portMAX_DELAY) != ESP_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void play_short(void)
|
||||||
|
{
|
||||||
|
chan_on();
|
||||||
|
play_buf_ms(s_tone, sizeof(s_tone), 100);
|
||||||
|
chan_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void play_alarm(void)
|
||||||
|
{
|
||||||
|
chan_on();
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
if (!play_buf_ms(s_tone, sizeof(s_tone), 500)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!play_buf_ms(s_silence, sizeof(s_silence), 250)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void audio_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
int msg;
|
||||||
|
for (;;) {
|
||||||
|
if (xQueueReceive(s_q, &msg, portMAX_DELAY) != pdTRUE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (msg == AUDIO_SHORT) {
|
||||||
|
play_short();
|
||||||
|
} else if (msg == AUDIO_ALARM) {
|
||||||
|
play_alarm();
|
||||||
|
} else if (msg == AUDIO_CANCEL) {
|
||||||
|
chan_off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void post(int msg)
|
||||||
|
{
|
||||||
|
if (s_q == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xQueueSend(s_q, &msg, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void autofilm_audio_task(void *arg)
|
||||||
|
{
|
||||||
|
audio_task(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_init(void)
|
||||||
|
{
|
||||||
|
if (s_inited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gpio_num_t bclk = board_pin_spk_bclk();
|
||||||
|
gpio_num_t lrclk = board_pin_spk_lrclk();
|
||||||
|
gpio_num_t din = board_pin_spk_din();
|
||||||
|
if (bclk == GPIO_NUM_NC || lrclk == GPIO_NUM_NC || din == GPIO_NUM_NC) {
|
||||||
|
ESP_LOGW(TAG, "no speaker pins on this board");
|
||||||
|
s_inited = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < CHUNK_FRAMES; i++) {
|
||||||
|
int16_t v = ((i % TONE_PERIOD_SMP) < (TONE_PERIOD_SMP / 2))
|
||||||
|
? TONE_AMPLITUDE : -TONE_AMPLITUDE;
|
||||||
|
s_tone[i * 2] = v;
|
||||||
|
s_tone[i * 2 + 1] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
|
||||||
|
chan_cfg.dma_desc_num = DMA_DESC_NUM;
|
||||||
|
chan_cfg.dma_frame_num = DMA_FRAME_NUM;
|
||||||
|
if (i2s_new_channel(&chan_cfg, &s_tx, NULL) != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "i2s_new_channel failed");
|
||||||
|
s_inited = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i2s_std_config_t std_cfg = {
|
||||||
|
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SPK_SAMPLE_RATE),
|
||||||
|
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
|
||||||
|
I2S_SLOT_MODE_STEREO),
|
||||||
|
.gpio_cfg = {
|
||||||
|
.mclk = I2S_GPIO_UNUSED,
|
||||||
|
.bclk = bclk,
|
||||||
|
.ws = lrclk,
|
||||||
|
.dout = din,
|
||||||
|
.din = I2S_GPIO_UNUSED,
|
||||||
|
.invert_flags = {
|
||||||
|
.mclk_inv = false,
|
||||||
|
.bclk_inv = false,
|
||||||
|
.ws_inv = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (i2s_channel_init_std_mode(s_tx, &std_cfg) != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "i2s_channel_init_std_mode failed");
|
||||||
|
i2s_del_channel(s_tx);
|
||||||
|
s_tx = NULL;
|
||||||
|
s_inited = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_q = xQueueCreate(8, sizeof(int));
|
||||||
|
if (s_task == NULL) {
|
||||||
|
xTaskCreate(audio_task, "audio", 3072, NULL, 2, &s_task);
|
||||||
|
}
|
||||||
|
s_inited = true;
|
||||||
|
ESP_LOGI(TAG, "init i2s bclk=%d ws=%d dout=%d", bclk, lrclk, din);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_beep_short(void)
|
||||||
|
{
|
||||||
|
post(AUDIO_SHORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_alarm_complete(void)
|
||||||
|
{
|
||||||
|
post(AUDIO_ALARM);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_alarm_cancel(void)
|
||||||
|
{
|
||||||
|
post(AUDIO_CANCEL);
|
||||||
|
}
|
||||||
@@ -38,4 +38,4 @@ If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase fi
|
|||||||
| I01 | `integration/I01-board-configs.md` | Board pin allocations & accessors | DONE |
|
| I01 | `integration/I01-board-configs.md` | Board pin allocations & accessors | DONE |
|
||||||
| I02 | `integration/I02-temp-fix.md` | Temp HAL shared & starvation fix | DONE |
|
| I02 | `integration/I02-temp-fix.md` | Temp HAL shared & starvation fix | DONE |
|
||||||
| I03 | `integration/I03-motor-hal.md` | Motor HAL shared across boards | DONE |
|
| I03 | `integration/I03-motor-hal.md` | Motor HAL shared across boards | DONE |
|
||||||
| I04 | `integration/I04-audio-i2s.md` | I2S audio implementation for S3 | TODO |
|
| I04 | `integration/I04-audio-i2s.md` | I2S audio implementation for S3 | DONE |
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
STATUS: TODO
|
STATUS: DONE
|
||||||
DEPENDS: I01
|
DEPENDS: I01
|
||||||
|
Notes: `idf.py build` for both targets verified via CI run 38684 (no local IDF).
|
||||||
|
|
||||||
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `components/hal_audio/*`
|
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `components/hal_audio/*`
|
||||||
|
|
||||||
@@ -17,6 +18,6 @@ DEPENDS: I01
|
|||||||
1. `Add I2S speaker audio for S3`
|
1. `Add I2S speaker audio for S3`
|
||||||
|
|
||||||
**DoD checkboxes:**
|
**DoD checkboxes:**
|
||||||
- [ ] `hal_audio` I2S implementation added for S3.
|
- [x] `hal_audio` I2S implementation added for S3.
|
||||||
- [ ] Queue protocol and non-blocking semantics maintained.
|
- [x] Queue protocol and non-blocking semantics maintained.
|
||||||
- [ ] Build succeeds.
|
- [x] Build succeeds.
|
||||||
|
|||||||
Reference in New Issue
Block a user