24-bit LUT waveform gen, MSB I2S format, 32-bit slots
Rewrote waveform generation from real-time 16-bit sinf() to pre-filled ring buffer with 4096-entry 24-bit sine LUT. Fixed I2S config: 32-bit data/slot width, MSB format (no bit shift), mclk_multiple 512, explicit slot config matching the Daisy Seed SAI2 expectations.
This commit is contained in:
parent
1d89359807
commit
bedff132f0
|
|
@ -5,6 +5,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "driver/i2s_std.h"
|
#include "driver/i2s_std.h"
|
||||||
#include "driver/gpio.h"
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
|
||||||
#define TAG "i2s_out"
|
#define TAG "i2s_out"
|
||||||
|
|
@ -21,12 +20,26 @@ static i2s_chan_handle_t tx_handle;
|
||||||
void i2s_out_init(void)
|
void i2s_out_init(void)
|
||||||
{
|
{
|
||||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||||
chan_cfg.dma_frame_num = BUF_FRAMES;
|
|
||||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
|
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
|
||||||
|
|
||||||
i2s_std_config_t std_cfg = {
|
i2s_std_config_t std_cfg = {
|
||||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
|
.clk_cfg = {
|
||||||
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
|
.sample_rate_hz = SAMPLE_RATE,
|
||||||
|
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||||
|
.mclk_multiple = I2S_MCLK_MULTIPLE_512,
|
||||||
|
},
|
||||||
|
.slot_cfg = {
|
||||||
|
.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||||
|
.slot_bit_width = I2S_SLOT_BIT_WIDTH_32BIT,
|
||||||
|
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||||
|
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||||
|
.ws_width = 32,
|
||||||
|
.ws_pol = false,
|
||||||
|
.bit_shift = false,
|
||||||
|
.left_align = true,
|
||||||
|
.big_endian = false,
|
||||||
|
.bit_order_lsb = false,
|
||||||
|
},
|
||||||
.gpio_cfg = {
|
.gpio_cfg = {
|
||||||
.mclk = I2S_GPIO_UNUSED,
|
.mclk = I2S_GPIO_UNUSED,
|
||||||
.bclk = PIN_BCLK,
|
.bclk = PIN_BCLK,
|
||||||
|
|
@ -43,27 +56,24 @@ void i2s_out_init(void)
|
||||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
|
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
|
||||||
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
|
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
|
||||||
|
|
||||||
ESP_LOGI(TAG, "I2S TX initialized: 48kHz 16-bit stereo, BCLK=%d WS=%d DOUT=%d",
|
ESP_LOGI(TAG, "I2S TX: 48kHz 24-bit stereo, BCLK=%d WS=%d DOUT=%d",
|
||||||
PIN_BCLK, PIN_WS, PIN_DOUT);
|
PIN_BCLK, PIN_WS, PIN_DOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void i2s_out_task(void *arg)
|
static void i2s_out_task(void *arg)
|
||||||
{
|
{
|
||||||
int16_t buf[BUF_FRAMES * 2];
|
int32_t buf[BUF_FRAMES * 2];
|
||||||
size_t bytes_written;
|
size_t bytes_written;
|
||||||
|
|
||||||
DBG("i2s_out_task started");
|
DBG("i2s_out_task started");
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
for (int i = 0; i < BUF_FRAMES; i++) {
|
waveform_fill_buffer(buf, BUF_FRAMES);
|
||||||
buf[i * 2] = waveform_next_sample(0);
|
|
||||||
buf[i * 2 + 1] = waveform_next_sample(1);
|
|
||||||
}
|
|
||||||
i2s_channel_write(tx_handle, buf, sizeof(buf), &bytes_written, portMAX_DELAY);
|
i2s_channel_write(tx_handle, buf, sizeof(buf), &bytes_written, portMAX_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void i2s_out_start_task(void)
|
void i2s_out_start_task(void)
|
||||||
{
|
{
|
||||||
xTaskCreatePinnedToCore(i2s_out_task, "i2s_out", 4096, NULL, 10, NULL, 1);
|
xTaskCreatePinnedToCore(i2s_out_task, "i2s_out", 8192, NULL, 10, NULL, 1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
118
main/waveform.c
118
main/waveform.c
|
|
@ -10,16 +10,87 @@
|
||||||
#define M_PI 3.14159265358979323846f
|
#define M_PI 3.14159265358979323846f
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define LUT_SIZE 4096
|
||||||
|
#define LUT_SHIFT 20 /* 32 - 12 = 20, maps phase to 12-bit index */
|
||||||
|
|
||||||
|
#define RING_FRAMES 4800 /* 100ms at 48kHz */
|
||||||
|
#define RING_SAMPLES (RING_FRAMES * 2)
|
||||||
|
|
||||||
|
/* left-justified 24-bit amplitude constants */
|
||||||
|
#define AMP_LJ ((int32_t)AMPLITUDE_24BIT << 8)
|
||||||
|
#define AMP_LJ_N (-AMP_LJ)
|
||||||
|
|
||||||
|
static int32_t sine_lut[LUT_SIZE];
|
||||||
|
|
||||||
static channel_state_t channels[2];
|
static channel_state_t channels[2];
|
||||||
|
static int32_t ring[RING_SAMPLES];
|
||||||
|
static int ring_pos;
|
||||||
static SemaphoreHandle_t mutex;
|
static SemaphoreHandle_t mutex;
|
||||||
|
|
||||||
|
static int32_t generate_sample(const channel_state_t *ch, uint32_t phase)
|
||||||
|
{
|
||||||
|
switch (ch->type) {
|
||||||
|
case WAVE_SINE:
|
||||||
|
return sine_lut[phase >> LUT_SHIFT];
|
||||||
|
|
||||||
|
case WAVE_SQUARE:
|
||||||
|
return (phase < (uint32_t)(ch->duty * 4294967296.0f)) ? AMP_LJ : AMP_LJ_N;
|
||||||
|
|
||||||
|
case WAVE_SAW: {
|
||||||
|
/* symmetric triangle: -amp at 0, +amp at mid, -amp at max */
|
||||||
|
int32_t p;
|
||||||
|
if (phase < 0x80000000U) {
|
||||||
|
/* first half: -amp to +amp */
|
||||||
|
p = (int32_t)((int64_t)phase * 2 * AMP_LJ / (int64_t)0x80000000U) - AMP_LJ;
|
||||||
|
} else {
|
||||||
|
/* second half: +amp to -amp */
|
||||||
|
uint32_t ph2 = phase - 0x80000000U;
|
||||||
|
p = AMP_LJ - (int32_t)((int64_t)ph2 * 2 * AMP_LJ / (int64_t)0x80000000U);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void regenerate_ring(void)
|
||||||
|
{
|
||||||
|
uint32_t phase[2] = {0, 0};
|
||||||
|
uint32_t inc[2];
|
||||||
|
|
||||||
|
for (int c = 0; c < 2; c++) {
|
||||||
|
if (channels[c].type != WAVE_NONE && channels[c].freq > 0.0f)
|
||||||
|
inc[c] = (uint32_t)((double)channels[c].freq / SAMPLE_RATE * 4294967296.0);
|
||||||
|
else
|
||||||
|
inc[c] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < RING_FRAMES; i++) {
|
||||||
|
ring[i * 2] = generate_sample(&channels[0], phase[0]);
|
||||||
|
ring[i * 2 + 1] = generate_sample(&channels[1], phase[1]);
|
||||||
|
phase[0] += inc[0];
|
||||||
|
phase[1] += inc[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
ring_pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void waveform_init(void)
|
void waveform_init(void)
|
||||||
{
|
{
|
||||||
mutex = xSemaphoreCreateMutex();
|
mutex = xSemaphoreCreateMutex();
|
||||||
memset(channels, 0, sizeof(channels));
|
memset(channels, 0, sizeof(channels));
|
||||||
channels[0].duty = 0.5f;
|
channels[0].duty = 0.5f;
|
||||||
channels[1].duty = 0.5f;
|
channels[1].duty = 0.5f;
|
||||||
DBG("waveform_init complete");
|
|
||||||
|
for (int i = 0; i < LUT_SIZE; i++)
|
||||||
|
sine_lut[i] = (int32_t)(AMPLITUDE_24BIT * sinf(2.0f * M_PI * i / LUT_SIZE)) << 8;
|
||||||
|
|
||||||
|
memset(ring, 0, sizeof(ring));
|
||||||
|
ring_pos = 0;
|
||||||
|
|
||||||
|
DBG("waveform_init complete, LUT %d entries", LUT_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void waveform_set(int ch, wave_type_t type, float freq, float duty)
|
void waveform_set(int ch, wave_type_t type, float freq, float duty)
|
||||||
|
|
@ -29,45 +100,22 @@ void waveform_set(int ch, wave_type_t type, float freq, float duty)
|
||||||
channels[ch].type = type;
|
channels[ch].type = type;
|
||||||
channels[ch].freq = freq;
|
channels[ch].freq = freq;
|
||||||
channels[ch].duty = duty;
|
channels[ch].duty = duty;
|
||||||
channels[ch].phase = 0.0f;
|
regenerate_ring();
|
||||||
xSemaphoreGive(mutex);
|
xSemaphoreGive(mutex);
|
||||||
DBG("waveform_set ch=%d type=%d freq=%.1f duty=%.2f", ch, type, freq, duty);
|
DBG("waveform_set ch=%d type=%d freq=%.1f duty=%.2f", ch, type, freq, duty);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int16_t generate(channel_state_t *s)
|
void waveform_fill_buffer(int32_t *buf, int frames)
|
||||||
{
|
{
|
||||||
float p = s->phase;
|
int samples = frames * 2;
|
||||||
float v;
|
|
||||||
|
|
||||||
switch (s->type) {
|
|
||||||
case WAVE_SINE:
|
|
||||||
v = sinf(2.0f * M_PI * p);
|
|
||||||
break;
|
|
||||||
case WAVE_SQUARE:
|
|
||||||
v = (p < s->duty) ? 1.0f : -1.0f;
|
|
||||||
break;
|
|
||||||
case WAVE_SAW:
|
|
||||||
if (p < 0.5f)
|
|
||||||
v = p * 4.0f - 1.0f;
|
|
||||||
else
|
|
||||||
v = 3.0f - p * 4.0f;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->phase += s->freq / (float)SAMPLE_RATE;
|
|
||||||
if (s->phase >= 1.0f)
|
|
||||||
s->phase -= 1.0f;
|
|
||||||
|
|
||||||
return (int16_t)(AMPLITUDE * v);
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t waveform_next_sample(int ch)
|
|
||||||
{
|
|
||||||
if (ch < 0 || ch > 1) return 0;
|
|
||||||
xSemaphoreTake(mutex, portMAX_DELAY);
|
xSemaphoreTake(mutex, portMAX_DELAY);
|
||||||
int16_t s = generate(&channels[ch]);
|
int pos = ring_pos;
|
||||||
xSemaphoreGive(mutex);
|
for (int i = 0; i < samples; i++) {
|
||||||
return s;
|
buf[i] = ring[pos];
|
||||||
|
if (++pos >= RING_SAMPLES)
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
ring_pos = pos;
|
||||||
|
xSemaphoreGive(mutex);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define SAMPLE_RATE 48000
|
#define SAMPLE_RATE 48000
|
||||||
#define AMPLITUDE 16384
|
#define AMPLITUDE_24BIT 0x3FFFFF
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
WAVE_NONE = 0,
|
WAVE_NONE = 0,
|
||||||
|
|
@ -16,9 +16,8 @@ typedef struct {
|
||||||
wave_type_t type;
|
wave_type_t type;
|
||||||
float freq;
|
float freq;
|
||||||
float duty;
|
float duty;
|
||||||
float phase;
|
|
||||||
} channel_state_t;
|
} channel_state_t;
|
||||||
|
|
||||||
void waveform_init(void);
|
void waveform_init(void);
|
||||||
void waveform_set(int ch, wave_type_t type, float freq, float duty);
|
void waveform_set(int ch, wave_type_t type, float freq, float duty);
|
||||||
int16_t waveform_next_sample(int ch);
|
void waveform_fill_buffer(int32_t *buf, int frames);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue