[OTTO GO] 喇叭測試-老虎張嘴出聲
Arduino:
#include
#include
#include
// ===== TFT pins (OTTO GO) =====
#define TFT_MOSI 19
#define TFT_SCLK 18
#define TFT_CS 5
#define TFT_DC 16
#define TFT_RST 23
// ===== Button (OTTO GO) =====
#define KEY1_PIN 35 // 使用者按鈕 P35
// ===== I2S AMP pins (OTTO GO) =====
#include "driver/i2s.h"
#define I2S_BCLK 2 // I2S BCLK
#define I2S_LRCK 17 // I2S LRCK/WS
#define I2S_DOUT 32 // 圖上寫 DIN:這裡是送到功放的資料輸出
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
static inline uint16_t RGB565(uint8_t r, uint8_t g, uint8_t b) {
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
// ================== 虎嘴繪圖 ==================
void drawMuzzleBase(int cx, int cy) {
uint16_t furLight = RGB565(235, 228, 210);
uint16_t shadow = RGB565(160, 150, 135);
uint16_t dotDark = RGB565(70, 70, 70);
tft.fillScreen(ST77XX_BLACK);
tft.fillCircle(cx - 55, cy + 10, 55, furLight);
tft.fillCircle(cx + 55, cy + 10, 55, furLight);
tft.fillRoundRect(cx - 90, cy - 25, 180, 110, 28, furLight);
tft.fillRoundRect(cx - 85, cy + 60, 170, 22, 12, shadow);
tft.fillRoundRect(cx - 40, cy + 55, 80, 10, 5, RGB565(120,110,100));
int dotY1 = cy + 6;
int dotY2 = cy + 26;
for (int k = 0; k < 3; k++) {
tft.fillCircle(cx - 55 - k * 12, dotY1 + k * 2, 2, dotDark);
tft.fillCircle(cx - 55 - k * 12, dotY2 + k * 2, 2, dotDark);
tft.fillCircle(cx + 55 + k * 12, dotY1 + k * 2, 2, dotDark);
tft.fillCircle(cx + 55 + k * 12, dotY2 + k * 2, 2, dotDark);
}
}
void drawMouthClosed(int cx, int cy) {
uint16_t lineDark = RGB565(40, 40, 40);
tft.drawLine(cx, cy - 5, cx, cy + 20, lineDark);
tft.drawLine(cx - 1, cy - 5, cx - 1, cy + 20, RGB565(80,80,80));
int mouthY = cy + 18;
for (int i = 0; i < 34; i++) {
int y = mouthY + (i * i) / 95;
tft.drawPixel(cx - i, y, lineDark);
tft.drawPixel(cx - i, y + 1, lineDark);
tft.drawPixel(cx + i, y, lineDark);
tft.drawPixel(cx + i, y + 1, lineDark);
}
tft.fillCircle(cx - 36, mouthY + 12, 3, lineDark);
tft.fillCircle(cx + 36, mouthY + 12, 3, lineDark);
tft.fillRoundRect(cx - 10, mouthY + 20, 20, 4, 2, RGB565(90,80,70));
}
void drawMouthOpen(int cx, int cy) {
uint16_t lineDark = RGB565(40, 40, 40);
uint16_t mouthIn = RGB565(15, 15, 15);
uint16_t tongue = RGB565(210, 120, 120);
tft.drawLine(cx, cy - 5, cx, cy + 10, lineDark);
tft.drawLine(cx - 1, cy - 5, cx - 1, cy + 10, RGB565(80,80,80));
int mouthY = cy + 16;
for (int i = 0; i < 36; i++) {
int y = mouthY + (i * i) / 85;
tft.drawPixel(cx - i, y, lineDark);
tft.drawPixel(cx - i, y + 1, lineDark);
tft.drawPixel(cx + i, y, lineDark);
tft.drawPixel(cx + i, y + 1, lineDark);
}
tft.fillCircle(cx - 38, mouthY + 14, 3, lineDark);
tft.fillCircle(cx + 38, mouthY + 14, 3, lineDark);
int w = 90, h = 45;
int x = cx - w / 2;
int y0 = cy + 28;
tft.fillRoundRect(x, y0, w, h, 18, mouthIn);
tft.fillCircle(cx, y0 + h, 22, mouthIn);
tft.fillRoundRect(cx - 22, y0 + 18, 44, 20, 10, tongue);
tft.fillCircle(cx, y0 + 36, 14, tongue);
}
void drawTigerMouth(bool open) {
int cx = tft.width() / 2;
int cy = tft.height() / 2;
drawMuzzleBase(cx, cy);
if (open) drawMouthOpen(cx, cy);
else drawMouthClosed(cx, cy);
}
// ================== I2S 老虎吼叫(合成音) ==================
// 簡單偽隨機(噪聲用)
static uint32_t rng = 1;
static inline int16_t noise16() {
rng = rng * 1664525UL + 1013904223UL;
return (int16_t)(rng >> 16); // -32768..32767
}
void i2sInit() {
const i2s_port_t I2S_PORT = I2S_NUM_0;
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = 22050,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 單聲道
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 6,
.dma_buf_len = 256,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0
};
i2s_pin_config_t pin_cfg = {
.bck_io_num = I2S_BCLK,
.ws_io_num = I2S_LRCK,
.data_out_num = I2S_DOUT,
.data_in_num = I2S_PIN_NO_CHANGE
};
i2s_driver_install(I2S_PORT, &cfg, 0, NULL);
i2s_set_pin(I2S_PORT, &pin_cfg);
i2s_zero_dma_buffer(I2S_PORT);
}
void playTigerRoarOnce() {
const i2s_port_t I2S_PORT = I2S_NUM_0;
// 0.7 秒左右的咆哮
const int sampleRate = 22050;
const float durSec = 0.7f;
const int total = (int)(sampleRate * durSec);
// 每次寫一小段
const int N = 256;
int16_t buf[N];
// 低頻咆哮(80~120Hz 變化) + 噪聲(牙擦感) + 包絡(先強後弱)
float phase = 0.0f;
for (int i = 0; i < total; ) {
int n = (total - i > N) ? N : (total - i);
for (int k = 0; k < n; k++) {
float t = (float)(i + k) / (float)total; // 0..1
// 頻率滑動:更像吼叫
float f = 120.0f - 50.0f * t; // 120 -> 70 Hz
phase += 2.0f * 3.1415926f * f / (float)sampleRate;
if (phase > 2.0f * 3.1415926f) phase -= 2.0f * 3.1415926f;
// 包絡:快速起音 + 慢慢衰減
float env = (t < 0.08f) ? (t / 0.08f) : (1.0f - (t - 0.08f) / 0.92f);
if (env < 0) env = 0;
// 主音 + 低頻加粗 + 噪聲
float s = sinf(phase) * 0.65f + sinf(phase * 0.5f) * 0.35f;
float nz = (noise16() / 32768.0f) * 0.35f;
float out = (s + nz) * env;
// 音量(可調):0.0~1.0
float gain = 0.9f;
int32_t v = (int32_t)(out * gain * 30000.0f);
if (v > 32767) v = 32767;
if (v < -32768) v = -32768;
buf[k] = (int16_t)v;
}
size_t written = 0;
i2s_write(I2S_PORT, (const char*)buf, n * sizeof(int16_t), &written, portMAX_DELAY);
i += n;
}
}
// ================== 按鍵去彈跳 + 事件觸發 ==================
bool mouthOpen = false;
// 若你按鍵按下讀到 LOW,維持 true;若相反就改成 false
const bool ACTIVE_LOW = true;
int lastStable = HIGH;
int lastRead = HIGH;
unsigned long lastDebounceMs = 0;
const unsigned long DEBOUNCE_MS = 30;
static inline bool isPressedLevel(int level) {
return ACTIVE_LOW ? (level == LOW) : (level == HIGH);
}
void setup() {
// TFT
SPI.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS);
tft.init(240, 320); // 若你確定是 170x320,改成 tft.init(170, 320)
tft.setRotation(1);
// Button
pinMode(KEY1_PIN, INPUT); // P35 無內建上拉下拉
// I2S
i2sInit();
// 初始畫面:嘴巴閉合
drawTigerMouth(mouthOpen);
}
void loop() {
int reading = digitalRead(KEY1_PIN);
if (reading != lastRead) {
lastDebounceMs = millis();
lastRead = reading;
}
if (millis() - lastDebounceMs > DEBOUNCE_MS) {
if (reading != lastStable) {
lastStable = reading;
if (isPressedLevel(lastStable)) {
bool prev = mouthOpen;
mouthOpen = !mouthOpen;
drawTigerMouth(mouthOpen);
// 只有「切到張開」時才吼叫一次
if (!prev && mouthOpen) {
playTigerRoarOnce();
}
}
}
}
}
沒有留言:
張貼留言