無線機の調整用に特化したオーディオシグナルジェネレータの自作をみて私も作ろうとChatGPTにコードを作ってもらいました。ESP32+ILI9488でタッチパネル操作で頼んでみました。何度かやり取りをして最終的にこんな感じになりました。

操作は問題なく動いてくれますが一部表示周波数と出力周波数が一致しない事があり最終的に20Hz~20KHzで完全に動作はしていません。ChatGPTよりESP32 GPIO25の内蔵DACをそのまま使用し、2段RCローパスフィルターで波形を滑らかにする方法の提案がありためしてみようと思います。
/*
ASG-10 ESP32 AUDIO SIGNAL GENERATOR
STABLE AUDIO TASK VERSION
Board : ESP32 DevKit V1
Display : ILI9488 480x320
Touch : XPT2046
DAC : Internal DAC GPIO25
Output : I2S DMA
Arduino ESP32 Core 2.0.17
*/
#include <Arduino.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>
#include <driver/i2s.h>
#include <math.h>
// =================================================
// PIN SETTINGS
// =================================================
#define TOUCH_CS 5
#define TOUCH_IRQ 34
#define DAC_PIN 25
#define SCREEN_W 480
#define SCREEN_H 320
TFT_eSPI tft = TFT_eSPI();
XPT2046_Touchscreen touch(
TOUCH_CS,
TOUCH_IRQ
);
// =================================================
// I2S SETTINGS
// =================================================
// 従来のサンプリング設定を維持
#define SAMPLE_RATE 192000
#define BUFFER_SIZE 512
#define I2S_PORT I2S_NUM_0
#define DMA_COUNT 8
uint16_t audioBuffer[BUFFER_SIZE];
// =================================================
// DDS SETTINGS
// =================================================
#define SINE_TABLE_SIZE 2048
int16_t sineTable[SINE_TABLE_SIZE];
uint32_t phase1 = 0;
uint32_t phase2 = 0;
uint32_t envelopePhase = 0;
uint32_t phaseStep1 = 0;
uint32_t phaseStep2 = 0;
// 音声タスクとの共有保護
portMUX_TYPE audioMux = portMUX_INITIALIZER_UNLOCKED;
TaskHandle_t audioTaskHandle = NULL;
bool dacReady = false;
// =================================================
// GENERATOR SETTINGS
// =================================================
enum WaveType {
SINE,
SQUARE,
TRIANGLE,
TWO_TONE,
TRAPEZOID
};
WaveType waveMode = SINE;
bool outputEnabled = false;
float frequency = 1000.0f;
float frequency2 = 1500.0f;
int amplitude = 100;
int frequencyStep = 100;
const float MIN_FREQ = 20.0f;
const float MAX_FREQ = 20000.0f;
const float MAX_TWO_TONE = 5000.0f;
// =================================================
// TOUCH CALIBRATION
// =================================================
#define TOUCH_X_MIN 200
#define TOUCH_X_MAX 3900
#define TOUCH_Y_MIN 200
#define TOUCH_Y_MAX 3900
// =================================================
// COLORS
// =================================================
#define BG TFT_BLACK
#define PANEL 0x10A2
#define WHITE_C TFT_WHITE
#define CYAN_C TFT_CYAN
#define YELLOW_C TFT_YELLOW
#define GREEN_C TFT_GREEN
#define RED_C TFT_RED
#define GRAY_C 0x8410
#define BLUE_C TFT_BLUE
// =================================================
// SINE TABLE
// =================================================
void makeSineTable() {
for (int i = 0; i < SINE_TABLE_SIZE; i++) {
float a =
2.0f * PI * (float)i /
(float)SINE_TABLE_SIZE;
sineTable[i] =
(int16_t)(32767.0f * sinf(a));
}
}
// =================================================
// FREQUENCY SETTING
// =================================================
// 以前の1/2補正を維持
// 1kHzで実測999.994~1000.005Hzを確認した設定
uint32_t frequencyToStep(float f) {
return (uint32_t)(
((double)f * 4294967296.0) /
((double)SAMPLE_RATE * 2.0)
);
}
void updateFrequency() {
uint32_t newStep1;
uint32_t newStep2;
if (waveMode == TWO_TONE) {
newStep1 = frequencyToStep(700.0f);
newStep2 = frequencyToStep(frequency);
}
else {
newStep1 = frequencyToStep(frequency);
newStep2 = frequencyToStep(frequency2);
}
portENTER_CRITICAL(&audioMux);
phaseStep1 = newStep1;
phaseStep2 = newStep2;
portEXIT_CRITICAL(&audioMux);
}
// =================================================
// I2S DAC INITIALIZATION
// =================================================
bool setupDAC() {
i2s_config_t config = {};
config.mode =
(i2s_mode_t)(
I2S_MODE_MASTER |
I2S_MODE_TX |
I2S_MODE_DAC_BUILT_IN
);
config.sample_rate = SAMPLE_RATE;
config.bits_per_sample =
I2S_BITS_PER_SAMPLE_16BIT;
config.channel_format =
I2S_CHANNEL_FMT_ONLY_RIGHT;
config.communication_format =
I2S_COMM_FORMAT_STAND_I2S;
config.intr_alloc_flags = 0;
config.dma_buf_count = DMA_COUNT;
config.dma_buf_len = BUFFER_SIZE;
config.use_apll = false;
config.tx_desc_auto_clear = true;
esp_err_t result = i2s_driver_install(
I2S_PORT,
&config,
0,
NULL
);
if (result != ESP_OK) {
Serial.printf(
"I2S INSTALL ERROR: %d\n",
result
);
return false;
}
result = i2s_set_dac_mode(
I2S_DAC_CHANNEL_RIGHT_EN
);
if (result != ESP_OK) {
Serial.printf(
"DAC MODE ERROR: %d\n",
result
);
i2s_driver_uninstall(I2S_PORT);
return false;
}
i2s_zero_dma_buffer(I2S_PORT);
updateFrequency();
Serial.println("INTERNAL DAC READY");
return true;
}
// =================================================
// DDS WAVE GENERATION
// =================================================
int32_t getSine(uint32_t phase) {
uint16_t index =
phase >> 21;
return sineTable[index];
}
// =================================================
// TRIANGLE
// =================================================
int32_t getTriangle(uint32_t phase) {
uint32_t p = phase >> 16;
if (p < 32768) {
return (int32_t)(p * 2) - 32768;
}
return 98303 - (int32_t)(p * 2);
}
// =================================================
// WAVE SAMPLE GENERATION
// =================================================
int32_t getWaveSample(
WaveType mode,
uint32_t step1,
uint32_t step2
) {
int32_t value = 0;
switch (mode) {
// -----------------------------
// SINE
// -----------------------------
case SINE:
value = getSine(phase1);
break;
// -----------------------------
// SQUARE
// -----------------------------
case SQUARE:
value =
(phase1 & 0x80000000UL)
? 32767 : -32767;
break;
// -----------------------------
// TRIANGLE
// -----------------------------
case TRIANGLE:
value = getTriangle(phase1);
break;
// -----------------------------
// TWO TONE
// -----------------------------
case TWO_TONE:
value = (
getSine(phase1) +
getSine(phase2)
) / 2;
break;
// -----------------------------
// TRAPEZOID
// -----------------------------
case TRAPEZOID: {
uint32_t envelopeStep = 44739UL;
envelopePhase += envelopeStep;
uint32_t envelope =
envelopePhase >> 16;
value =
(int32_t)(
((int64_t)getSine(phase1) *
envelope) / 65535
);
break;
}
}
// DDS phase update
phase1 += step1;
phase2 += step2;
return value;
}
// =================================================
// AUDIO TASK
// =================================================
// 音声処理を専用タスクで実行
// TFT描画・タッチ処理から独立
void audioTask(void *parameter) {
Serial.println("AUDIO TASK START");
while (true) {
// -----------------------------
// 設定値を安全に取得
// -----------------------------
uint32_t localStep1;
uint32_t localStep2;
int localAmplitude;
bool localOutput;
WaveType localMode;
portENTER_CRITICAL(&audioMux);
localStep1 = phaseStep1;
localStep2 = phaseStep2;
localAmplitude = amplitude;
localOutput = outputEnabled;
localMode = waveMode;
portEXIT_CRITICAL(&audioMux);
// -----------------------------
// Generate audio buffer
// -----------------------------
for (int i = 0; i < BUFFER_SIZE; i++) {
int32_t sample = 0;
if (localOutput) {
sample = getWaveSample(
localMode,
localStep1,
localStep2
);
sample =
(int32_t)(
(int64_t)sample *
localAmplitude / 127
);
}
else {
// 出力停止中も位相を進める
phase1 += localStep1;
phase2 += localStep2;
}
// -----------------------------
// Signed 16bit to 8bit DAC
// -----------------------------
int32_t dacValue =
128 + sample / 256;
dacValue = constrain(
dacValue,
0,
255
);
audioBuffer[i] =
(uint16_t)(dacValue << 8);
}
// -----------------------------
// I2S DMA output
// -----------------------------
size_t bytesWritten = 0;
esp_err_t result = i2s_write(
I2S_PORT,
audioBuffer,
sizeof(audioBuffer),
&bytesWritten,
portMAX_DELAY
);
if (result != ESP_OK) {
Serial.println("I2S WRITE ERROR");
vTaskDelay(1);
}
}
}
// =================================================
// WAVEFORM PREVIEW
// =================================================
void drawWaveformPreview() {
const int x0 = 248;
const int y0 = 70;
const int w = 208;
const int h = 48;
const int centerY = y0 + h / 2;
tft.fillRect(
x0, y0, w, h, TFT_BLACK
);
tft.drawFastHLine(
x0, centerY, w, TFT_DARKGREY
);
tft.drawRect(
x0 - 2,
y0 - 2,
w + 4,
h + 4,
TFT_WHITE
);
int previousX = x0;
int previousY = centerY;
for (int i = 0; i < w; i++) {
float t = (float)i / (float)w;
float value = 0.0f;
switch (waveMode) {
case SINE:
value =
sinf(2.0f * PI * 2.0f * t);
break;
case SQUARE:
value =
(fmodf(t * 2.0f, 1.0f) < 0.5f)
? 1.0f : -1.0f;
break;
case TRIANGLE: {
float p = fmodf(t * 2.0f, 1.0f);
if (p < 0.5f) {
value = -1.0f + p * 4.0f;
}
else {
value = 3.0f - p * 4.0f;
}
break;
}
case TWO_TONE: {
float time = t * 0.004f;
value = (
sinf(2.0f * PI * frequency * time) +
sinf(2.0f * PI * 700.0f * time)
) / 2.0f;
break;
}
case TRAPEZOID: {
float p = fmodf(t * 2.0f, 1.0f);
if (p < 0.2f) {
value = -1.0f + p * 5.0f;
}
else if (p < 0.7f) {
value = 0.0f;
}
else {
value = (p - 0.7f) * 3.333f - 1.0f;
}
break;
}
}
int currentX = x0 + i;
int currentY =
centerY - (int)(value * (h / 2 - 3));
currentY = constrain(
currentY,
y0 + 2,
y0 + h - 2
);
if (i > 0) {
tft.drawLine(
previousX,
previousY,
currentX,
currentY,
TFT_CYAN
);
}
previousX = currentX;
previousY = currentY;
}
}
// =================================================
// DISPLAY HEADER
// =================================================
void drawHeader() {
tft.fillRect(
0, 0, SCREEN_W, 42, PANEL
);
tft.setTextColor(
WHITE_C, PANEL
);
tft.drawCentreString(
"ASG-10 ESP32 AUDIO GENERATOR",
240, 12, 2
);
}
// =================================================
// FREQUENCY DISPLAY
// =================================================
void drawFrequency() {
tft.fillRoundRect(
15, 52, 215, 75, 8, BG
);
tft.drawRoundRect(
15, 52, 215, 75, 8, CYAN_C
);
char buf[32];
snprintf(
buf,
sizeof(buf),
"%.1f",
frequency
);
tft.setTextColor(
YELLOW_C, BG
);
tft.drawCentreString(
buf, 122, 57, 7
);
int cursorIndex = 0;
if (frequencyStep == 10) {
cursorIndex = 2;
}
else if (frequencyStep == 100) {
cursorIndex = 1;
}
else {
cursorIndex = 0;
}
int totalWidth = tft.textWidth(buf, 7);
int startX = 122 - totalWidth / 2;
int cursorX = startX +
tft.textWidth(
String(buf).substring(0, cursorIndex), 7
);
tft.fillRect(
cursorX + 5,
107,
20,
4,
TFT_CYAN
);
tft.fillRoundRect(
240, 52, 225, 75, 8, BG
);
tft.drawRoundRect(
240, 52, 225, 75, 8, CYAN_C
);
tft.setTextColor(
WHITE_C, BG
);
tft.drawString(
"WAVEFORM",
248, 55, 1
);
drawWaveformPreview();
}
// =================================================
// BUTTON DRAWING
// =================================================
void drawButton(
int x,
int y,
int w,
int h,
const char *label,
uint16_t color
) {
tft.fillRoundRect(
x, y, w, h, 7, color
);
tft.drawRoundRect(
x, y, w, h, 7, WHITE_C
);
tft.setTextColor(
WHITE_C, color
);
tft.drawCentreString(
label,
x + w / 2,
y + h / 2 - 8,
2
);
}
// =================================================
// WAVEFORM BUTTONS
// =================================================
void drawWaveButtons() {
const char *names[] = {
"SINE",
"SQUARE",
"TRIANGLE",
"2-TONE",
"TRAPEZOID"
};
for (int i = 0; i < 5; i++) {
uint16_t color =
(waveMode == i)
? 0x03EF : PANEL;
drawButton(
10 + i * 94,
145,
88,
42,
names[i],
color
);
}
}
// =================================================
// FREQUENCY CONTROL BUTTONS
// =================================================
void drawFrequencyButtons() {
drawButton(
15, 202, 90, 42,
"- STEP", RED_C
);
drawButton(
115, 202, 90, 42,
"+ STEP", BLUE_C
);
drawButton(
220, 202, 110, 42,
"STEP", PANEL
);
drawButton(
345, 202, 120, 42,
"AMP +", PANEL
);
drawButton(
15, 253, 90, 42,
"AMP -", PANEL
);
drawButton(
115, 253, 150, 42,
"START / STOP",
outputEnabled ? GREEN_C : RED_C
);
drawButton(
280, 253, 185, 42,
"RESET 1kHz", PANEL
);
}
// =================================================
// STATUS DISPLAY
// =================================================
void drawStatus() {
tft.fillRect(
0, 300, 480, 20, BG
);
tft.setTextColor(
outputEnabled ? GREEN_C : RED_C,
BG
);
tft.drawString(
outputEnabled ? "OUTPUT ON" : "OUTPUT OFF",
10, 301, 2
);
tft.setTextColor(
WHITE_C, BG
);
char buf[32];
snprintf(
buf,
sizeof(buf),
"LEVEL: %d/127",
amplitude
);
tft.drawRightString(
buf, 470, 301, 2
);
}
// =================================================
// FULL SCREEN
// =================================================
void drawScreen() {
tft.fillScreen(BG);
drawHeader();
drawFrequency();
drawWaveButtons();
drawFrequencyButtons();
drawStatus();
}
// =================================================
// TOUCH READING
// =================================================
bool getTouchXY(int &x, int &y) {
if (!touch.touched()) {
return false;
}
TS_Point p = touch.getPoint();
int rawX = constrain(
p.x,
TOUCH_X_MIN,
TOUCH_X_MAX
);
int rawY = constrain(
p.y,
TOUCH_Y_MIN,
TOUCH_Y_MAX
);
x = map(
rawX,
TOUCH_X_MIN,
TOUCH_X_MAX,
0,
SCREEN_W - 1
);
y = map(
rawY,
TOUCH_Y_MIN,
TOUCH_Y_MAX,
0,
SCREEN_H - 1
);
return true;
}
// =================================================
// TOUCH CONTROL
// =================================================
void processTouch() {
static uint32_t lastTouch = 0;
if (millis() - lastTouch < 180) {
return;
}
int x, y;
if (!getTouchXY(x, y)) {
return;
}
lastTouch = millis();
Serial.printf(
"TOUCH X=%d Y=%d\n",
x, y
);
// -----------------------------
// Waveform selection
// -----------------------------
if (y >= 140 && y <= 195) {
int index = x / 94;
if (index >= 0 && index < 5) {
portENTER_CRITICAL(&audioMux);
waveMode = (WaveType)index;
portEXIT_CRITICAL(&audioMux);
updateFrequency();
drawScreen();
}
return;
}
// -----------------------------
// Frequency minus
// -----------------------------
if (y >= 200 && y <= 247 &&
x >= 10 && x <= 110) {
frequency -= frequencyStep;
frequency = constrain(
frequency,
MIN_FREQ,
MAX_FREQ
);
updateFrequency();
drawScreen();
return;
}
// -----------------------------
// Frequency plus
// -----------------------------
if (y >= 200 && y <= 247 &&
x >= 110 && x <= 210) {
frequency += frequencyStep;
frequency = constrain(
frequency,
MIN_FREQ,
MAX_FREQ
);
updateFrequency();
drawScreen();
return;
}
// -----------------------------
// Step selection
// -----------------------------
if (y >= 200 && y <= 247 &&
x >= 215 && x <= 335) {
if (frequencyStep == 10) {
frequencyStep = 100;
}
else if (frequencyStep == 100) {
frequencyStep = 1000;
}
else if (frequencyStep == 1000) {
frequencyStep = 10000;
}
else {
frequencyStep = 10;
}
drawScreen();
return;
}
// -----------------------------
// Amplitude plus
// -----------------------------
if (y >= 200 && y <= 247 &&
x >= 340) {
portENTER_CRITICAL(&audioMux);
amplitude = min(
amplitude + 5,
127
);
portEXIT_CRITICAL(&audioMux);
drawScreen();
return;
}
// -----------------------------
// Amplitude minus
// -----------------------------
if (y >= 250 && y <= 299 &&
x <= 110) {
portENTER_CRITICAL(&audioMux);
amplitude = max(
amplitude - 5,
0
);
portEXIT_CRITICAL(&audioMux);
drawScreen();
return;
}
// -----------------------------
// START / STOP
// -----------------------------
if (y >= 250 && y <= 299 &&
x >= 110 && x <= 270) {
portENTER_CRITICAL(&audioMux);
outputEnabled = !outputEnabled;
portEXIT_CRITICAL(&audioMux);
drawScreen();
return;
}
// -----------------------------
// RESET
// -----------------------------
if (y >= 250 && y <= 299 &&
x >= 275) {
frequency = 1000.0f;
updateFrequency();
drawScreen();
return;
}
}
// =================================================
// SETUP
// =================================================
void setup() {
Serial.begin(115200);
delay(500);
Serial.println();
Serial.println("ASG-10 ESP32 START");
// -----------------------------
// Display
// -----------------------------
tft.init();
tft.setRotation(3);
tft.fillScreen(BG);
// -----------------------------
// Touch
// -----------------------------
SPI.begin(
18, 19, 23, TOUCH_CS
);
touch.begin();
touch.setRotation(1);
// -----------------------------
// Sine table
// -----------------------------
makeSineTable();
// -----------------------------
// DAC
// -----------------------------
dacReady = setupDAC();
if (!dacReady) {
Serial.println("DAC INITIALIZATION FAILED");
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_RED);
tft.drawCentreString(
"DAC ERROR",
240, 150, 4
);
return;
}
// -----------------------------
// Audio task
// -----------------------------
BaseType_t taskResult =
xTaskCreatePinnedToCore(
audioTask,
"ASG_AUDIO",
4096,
NULL,
3,
&audioTaskHandle,
0
);
if (taskResult != pdPASS) {
Serial.println("AUDIO TASK CREATE ERROR");
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_RED);
tft.drawCentreString(
"AUDIO TASK ERROR",
240, 150, 4
);
return;
}
// -----------------------------
// UI
// -----------------------------
drawScreen();
Serial.println("SYSTEM READY");
}
// =================================================
// MAIN LOOP
// =================================================
void loop() {
if (!dacReady || audioTaskHandle == NULL) {
delay(100);
return;
}
// Touch control only
processTouch();
delay(1);
}

コメント