/* Full ESP32 program - extended from user's base.
Features:
- WS2812B LED strip (12 leds)
- TTP229 16-key reading
- Buzzer (LEDC) for tone output
- SPI OLED (128x64) driver (no ssd1306)
- Mode keys: physical TP4..TP7 as function keys
- Mode4: LED-key interactive (latched while pressed, fade on release)
- Mode5: LED gradient rotating
- Mode6: Play blessing tune loop with LED feedback
- Mode7: mini-game (7 regions) on OLED
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "driver/spi_master.h"
#include "esp_log.h"
#include "esp_rom_sys.h"
#include "led_strip.h"
#include <math.h>
#include "esp_random.h"
#define TAG "TTP229_16KEY_EXT"
/// Hardware pins (adjust to your wiring)
#define WS2812B_PIN 2
#define NUM_LEDS 12
#define TTP229_SCL 27
#define TTP229_SDO 26
#define BUZZER_PIN 25
// OLED SPI pins (example, change if different)
#define OLED_SPI_HOST HSPI_HOST
#define OLED_MISO_PIN -1
#define OLED_MOSI_PIN 17
#define OLED_SCLK_PIN 4
#define OLED_DC_PIN 18
#define OLED_CS_PIN 19
#define OLED_RST_PIN 5
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_BUF_SIZE ((OLED_WIDTH * OLED_HEIGHT) / 8)
#define M_PI 3.14159265358979323846
#pragma GCC diagnostic ignored "-Wunused-const-variable"
static const char *TAG2 = "OLED";
led_strip_handle_t led_strip;
/// Rainbow preset for LED colors (RGB)
const uint8_t rainbow_colors[NUM_LEDS][3] = {
{255,0,48}, {255,89,0}, {255,183,0}, {255,233,0},
{136,255,0}, {0,255,136}, {0,204,255}, {0,64,255},
{89,0,255}, {183,0,255}, {255,0,191}, {255,0,98}
};
// 16-key note frequencies (C4~F5)
static const int note_freqs[16] = {
262,294,330,349,370,392,415,440,466,494,523,554,587,622,659,698
};
// mapping logical index i -> TP number (as in your original)
static const uint8_t key_map[16] = {3,2,1,0,15,14,13,12,8,9,10,11,4,5,6,7};
// forward declarations
static void ws2812b_init(void);
static void ttp229_init(void);
static uint16_t ttp229_read_keys(void);
static void buzzer_init(void);
static void buzzer_play_tone(int freq);
static void oled_init(void);
static void oled_refresh(void);
static void oled_clear(void);
static void oled_draw_bitmap(int x, int y, int w, int h, const uint8_t *bm);
static void oled_draw_rect(int x,int y,int w,int h);
static void oled_fill_rect(int x,int y,int w,int h);
static void oled_draw_text_centered(const char *txt);
static void oled_draw_music_bars(uint16_t keys);
static void start_blessing_task(void);
static void stop_blessing_task(void);
static void blessing_led_feedback(int step);
// Global state
typedef enum {
MODE_NORMAL = 0,
MODE_LED_KEY_INTERACT, // TP4
MODE_LED_GRADIENT_CYCLE, // TP5
MODE_BLESSING_PLAY, // TP6
MODE_GAME // TP7
} app_mode_t;
static volatile app_mode_t g_mode = MODE_NORMAL;
// Variables for LED-key interactivity
static uint8_t led_brightness[NUM_LEDS]; // 0-255 brightness
static uint8_t led_active[NUM_LEDS]; // 1=latched on, 0=idle
static SemaphoreHandle_t led_mutex = NULL;
// Gradient cycle control
static volatile bool gradient_run = false;
static int gradient_pos = 0;
// Blessing task handle
static TaskHandle_t blessing_task_handle = NULL;
static volatile bool blessing_running = false;
// Game state
static bool game_running = false;
static uint8_t game_regions[7]; // 1=block present, 0=empty
static TickType_t game_next_spawn;
// OLED framebuffer
static uint8_t oled_buf[OLED_BUF_SIZE];
static spi_device_handle_t spi_oled;
// Simple bitmaps (8x8 or larger) for emoticons/music glyphs
// Emoticon 1: (^_^) - 16x8 bitmap (example)
static const uint8_t bm_face1[] = {
// 16x8 (each byte is 8 vertical pixels column-major if we render simply)
0x00,0x08,0x04,0x02,0x04,0x08,0x40,0x40,
0x40,0x40,0x08,0x04,0x02,0x04,0x08,0x00
};
// Emoticon 2: (o_o)
static const uint8_t bm_face2[] = {
0x00,0x0E,0x0A,0x0A,0x0E,0x40,0x20,0x10,
0x20,0x40,0x0E,0x0A,0x0A,0x0E,0x00,0x00
};
// Music symbol (simple 12x12)
static const uint8_t bm_music[] = {
0x10,0x10,0x10,0x18,0x18,0x1C,0x1C,0x0E,0x06,0x06,0x00,0x00,
0x1F,0x1F,0x0F,0x0F,0x07,0x07,0x03,0x03,0x01,0x01,0x00,0x00
};
// Small filled block 12x12 for game tile
static const uint8_t bm_block12[12*2] = {
// we'll draw block by filling rectangle, not use bitmap
};
// Blessing tune array (freq, duration_ms) - shortened example uses your full data
typedef struct { int freq; int dur; } tone_t;
static const tone_t blessing[] = {
// I will include the start of your provided data; you can extend as necessary.
{494,600},{600,600},{440,600},{440,600},{392,600},{392,600},{370,600},{392,600},{392,600},{494,600},{440,600},
// (NOTE: in your provided sheet you had many entries — paste the entire array here in your final code)
// For brevity in this example, repeat a few to demonstrate:
{440,600},{392,600},{392,600},{370,600},{392,600},{392,600},
{0,600},{0,600},{0,600} // zeros mean rest
};
static const int blessing_len = sizeof(blessing)/sizeof(blessing[0]);
/*** --- Implementation --- ***/
static void ws2812b_init(void){
led_strip_config_t strip_config = {
.strip_gpio_num = WS2812B_PIN,
.max_leds = NUM_LEDS,
};
led_strip_rmt_config_t rmt_config = {
.resolution_hz = 10 * 1000 * 1000,
};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config,&rmt_config,&led_strip));
// Initialize to rainbow
for (int i = 0 ; i < NUM_LEDS ; i++){
led_strip_set_pixel(led_strip, i, rainbow_colors[i][0], rainbow_colors[i][1], rainbow_colors[i][2]);
}
led_strip_refresh(led_strip);
if (!led_mutex) led_mutex = xSemaphoreCreateMutex();
// initialize brightness/active arrays
for (int i=0;i<NUM_LEDS;i++){ led_brightness[i]=255; led_active[i]=0; }
}
static void ttp229_init(void) {
gpio_config_t io_conf = {0};
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = 1ULL << TTP229_SCL;
gpio_config(&io_conf);
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = 1ULL << TTP229_SDO;
gpio_config(&io_conf);
gpio_set_level(TTP229_SCL, 1);
vTaskDelay(pdMS_TO_TICKS(10));
}
static uint16_t ttp229_read_keys(void) {
uint16_t data = 0;
gpio_set_level(TTP229_SCL, 1);
esp_rom_delay_us(2);
for (int i = 0; i < 16; i++) {
gpio_set_level(TTP229_SCL, 0);
esp_rom_delay_us(2);
int bit = gpio_get_level(TTP229_SDO);
if (bit == 0) data |= (1 << i);
gpio_set_level(TTP229_SCL, 1);
esp_rom_delay_us(2);
}
return data;
}
static void buzzer_init(void) {
ledc_timer_config_t timer_conf = {
.duty_resolution = LEDC_TIMER_10_BIT,
.freq_hz = 1000,
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0
};
ledc_timer_config(&timer_conf);
ledc_channel_config_t channel_conf = {
.channel = LEDC_CHANNEL_0,
.duty = 0,
.gpio_num = BUZZER_PIN,
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_sel = LEDC_TIMER_0
};
ledc_channel_config(&channel_conf);
}
static void buzzer_play_tone(int freq) {
if (freq <= 0) {
ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
return;
}
ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, freq);
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 512);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
}
/*** Simple SPI OLED (128x64) - basic frame buffer + commands ***/
static void oled_send_cmd(const uint8_t *data, size_t len) {
spi_transaction_t t = {
.length = len * 8,
.tx_buffer = data,
.flags = 0
};
// DC low for command
gpio_set_level(OLED_DC_PIN, 0);
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_oled, &t));
}
static void oled_send_data(const uint8_t *data, size_t len) {
spi_transaction_t t = {
.length = len * 8,
.tx_buffer = data,
.flags = 0
};
gpio_set_level(OLED_DC_PIN, 1);
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_oled, &t));
}
static void oled_init(void) {
gpio_config_t io_conf = {0};
// DC, CS, RST as outputs
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL<<OLED_DC_PIN) | (1ULL<<OLED_CS_PIN) | (1ULL<<OLED_RST_PIN);
gpio_config(&io_conf);
// SPI bus init
spi_bus_config_t buscfg = {
.miso_io_num = OLED_MISO_PIN,
.mosi_io_num = OLED_MOSI_PIN,
.sclk_io_num = OLED_SCLK_PIN,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = OLED_BUF_SIZE + 8
};
ESP_ERROR_CHECK(spi_bus_initialize(OLED_SPI_HOST, &buscfg, 1));
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 8*1000*1000,
.mode = 0,
.spics_io_num = OLED_CS_PIN,
.queue_size = 1,
};
ESP_ERROR_CHECK(spi_bus_add_device(OLED_SPI_HOST, &devcfg, &spi_oled));
// Reset sequence
gpio_set_level(OLED_RST_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(OLED_RST_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(10));
// Basic init sequence (compatible with many 128x64 controllers)
const uint8_t init_cmds[] = {
0xAE, // display off
0xD5, 0x80, // set display clock
0xA8, 0x3F, // multiplex 1/64
0xD3, 0x00, // display offset
0x40, // start line
0x8D, 0x14, // charge pump on
0x20, 0x00, // memory addressing mode horizontal
0xA1, // segment remap
0xC8, // COM scan dec
0xDA, 0x12, // COM pins
0x81, 0xCF, // contrast
0xD9, 0xF1, // precharge
0xDB, 0x40, // vcom detect
0xA4, // resume RAM
0xA6, // normal display
0xAF // display on
};
oled_send_cmd(init_cmds, sizeof(init_cmds));
memset(oled_buf, 0, OLED_BUF_SIZE);
oled_refresh();
ESP_LOGI(TAG2,"OLED initialized");
}
static void oled_refresh(void) {
// set column/page addresses
const uint8_t setcol[] = {0x21, 0, OLED_WIDTH-1};
const uint8_t setpage[] = {0x22, 0, (OLED_HEIGHT/8)-1};
oled_send_cmd(setcol, sizeof(setcol));
oled_send_cmd(setpage, sizeof(setpage));
oled_send_data(oled_buf, OLED_BUF_SIZE);
}
static void oled_clear(void) {
memset(oled_buf, 0, OLED_BUF_SIZE);
oled_refresh();
}
// draw rectangle border (byte addressing vertical pages)
static void oled_draw_rect(int x,int y,int w,int h) {
// naive draw pixel onto buffer
for (int yy=y; yy<y+h; yy++) {
for (int xx=x; xx<x+w; xx++) {
if (xx<0 || xx>=OLED_WIDTH || yy<0 || yy>=OLED_HEIGHT) continue;
int page = yy / 8;
int idx = page * OLED_WIDTH + xx;
oled_buf[idx] |= (1 << (yy & 7));
}
}
}
// fill rect
static void oled_fill_rect(int x,int y,int w,int h) {
oled_draw_rect(x,y,w,h);
}
static void oled_draw_bitmap(int x, int y, int w, int h, const uint8_t *bm) {
// simple bitmap: each byte is a column of 8 vertical pixels (LSB top)
int bytes_per_col = (h + 7)/8;
for (int col=0; col<w; col++) {
for (int b=0;b<bytes_per_col;b++) {
int src_idx = col*bytes_per_col + b;
uint8_t v = bm[src_idx];
for (int bit=0;bit<8;bit++) {
int yy = y + b*8 + bit;
int xx = x + col;
if (yy>=y+h) break;
if (xx<0 || xx>=OLED_WIDTH || yy<0 || yy>=OLED_HEIGHT) continue;
int page = yy/8;
int dst_idx = page*OLED_WIDTH + xx;
if (v & (1<<bit)) oled_buf[dst_idx] |= (1<<(yy&7));
}
}
}
}
// center simple string using very crude 6x8 font hard-coded for limited characters
static const uint8_t font6x8_A[] = {
// only include bytes for a few characters used in emoticons and small labels:
// We'll support: space, '(', ')', '^', '_', 'o', '-', '<', '3', '>', '!', 'M', 'u', 's', 'i', 'c'
// For brevity we won't include a complete font; we'll implement text using small bitmaps manually.
};
static void oled_draw_text_centered(const char *txt) {
// crude: show at middle as ascii using available characters; we'll just draw as pixel blocks for each char
int len = strlen(txt);
int char_w = 6;
int total_w = len * (char_w+1);
int start_x = (OLED_WIDTH - total_w)/2;
int y = OLED_HEIGHT/2 - 4;
// Simple: draw each char as filled rectangle representing presence (not real glyphs) for demo
for (int i=0;i<len;i++) {
int x = start_x + i*(char_w+1);
if (txt[i] == ' ') continue;
// draw small shape to represent char
oled_fill_rect(x,y,4,6);
}
}
/*** Mode helpers ***/
static void set_led_rgb(int idx, uint8_t r, uint8_t g, uint8_t b, uint8_t brightness) {
// apply brightness scaling 0-255
uint32_t scale = brightness;
uint8_t r2 = (r * scale)/255;
uint8_t g2 = (g * scale)/255;
uint8_t b2 = (b * scale)/255;
led_strip_set_pixel(led_strip, idx, r2, g2, b2);
}
static void led_update_from_state(void) {
xSemaphoreTake(led_mutex, portMAX_DELAY);
for (int i=0;i<NUM_LEDS;i++) {
uint8_t b = led_brightness[i];
if (b==0) {
led_strip_set_pixel(led_strip,i,0,0,0);
} else {
set_led_rgb(i, rainbow_colors[i][0], rainbow_colors[i][1], rainbow_colors[i][2], b);
}
}
led_strip_refresh(led_strip);
xSemaphoreGive(led_mutex);
}
// background LED fade task
static void led_fade_task(void *arg) {
while (1) {
bool changed = false;
xSemaphoreTake(led_mutex, portMAX_DELAY);
if (g_mode == MODE_LED_KEY_INTERACT) {
for (int i=0;i<NUM_LEDS;i++) {
if (!led_active[i] && led_brightness[i] > 0) {
// fade down slowly
uint8_t old = led_brightness[i];
int nb = old - 5;
if (nb < 0) nb = 0;
led_brightness[i] = nb;
changed = true;
}
}
} else if (g_mode == MODE_LED_GRADIENT_CYCLE && gradient_run) {
// gradient animation: rotate brightness pattern
gradient_pos++;
for (int i=0;i<NUM_LEDS;i++) {
int pos = (i + gradient_pos) % NUM_LEDS;
// wave brightness
int b = 80 + (int)(175.0 * (1.0 + sinf((pos*2.0*M_PI)/NUM_LEDS))/2.0);
led_brightness[i] = (uint8_t)b;
}
changed = true;
} else {
// In normal mode ensure all leds full
for (int i=0;i<NUM_LEDS;i++) {
if (led_brightness[i] != 255) { led_brightness[i]=255; changed = true; }
}
}
xSemaphoreGive(led_mutex);
if (changed) led_update_from_state();
vTaskDelay(pdMS_TO_TICKS(40));
}
}
/*** Blessing player task (plays in loop while blessing_running) ***/
static void blessing_task(void *arg) {
ESP_LOGI(TAG,"Blessing task started");
while (blessing_running) {
for (int i=0;i<blessing_len && blessing_running;i++){
int f = blessing[i].freq;
int d = blessing[i].dur;
if (f>0) {
buzzer_play_tone(f);
// LED feedback: light one LED corresponding to index (wrap)
blessing_led_feedback(i);
} else {
buzzer_play_tone(0);
}
int step = d/10;
for (int t=0;t<d && blessing_running;t+=10) vTaskDelay(pdMS_TO_TICKS(10));
// small gap
buzzer_play_tone(0);
vTaskDelay(pdMS_TO_TICKS(30));
}
// loop
}
buzzer_play_tone(0);
ESP_LOGI(TAG,"Blessing task ending");
vTaskDelete(NULL);
}
static void start_blessing_task(void) {
if (blessing_running) return;
blessing_running = true;
xTaskCreatePinnedToCore(blessing_task, "blessing", 4*1024, NULL, 5, &blessing_task_handle, tskNO_AFFINITY);
}
static void stop_blessing_task(void) {
if (!blessing_running) return;
blessing_running = false;
// task will stop itself
blessing_task_handle = NULL;
}
static void blessing_led_feedback(int step) {
xSemaphoreTake(led_mutex, portMAX_DELAY);
// simple: light LED at step % NUM_LEDS to full for short time
int idx = step % NUM_LEDS;
led_brightness[idx] = 255;
led_update_from_state();
xSemaphoreGive(led_mutex);
}
/*** Game logic ***/
static void game_spawn_block(void) {
// spawn random region among 0..6 that's empty
int tries = 0;
while (tries<10) {
int r = esp_random() % 7;
if (!game_regions[r]) { game_regions[r]=1; break; }
tries++;
}
}
static void game_draw(void) {
// draw seven regions as columns on OLED
oled_clear();
int region_w = OLED_WIDTH / 7;
for (int i=0;i<7;i++){
int x = i*region_w + 4;
int y = 12;
int w = region_w - 8;
int h = 40;
if (game_regions[i]) {
// filled
for (int yy=y; yy<y+h; yy++) for (int xx=x;xx<x+w;xx++) {
int page = yy/8;
int idx = page*OLED_WIDTH + xx;
oled_buf[idx] |= (1 << (yy&7));
}
} else {
// outline
for (int xx=x;xx<x+w;xx++){
int yy=y; int page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7));
yy=y+h-1; page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7));
}
for (int yy=y;yy<y+h;yy++){
int xx=x; int page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7));
xx=x+w-1; page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7));
}
}
}
oled_refresh();
}
/*** Main app ***/
void app_main(void)
{
// init components
ESP_LOGI(TAG,"Starting extended piano app");
ws2812b_init();
ttp229_init();
buzzer_init();
oled_init();
// start led fade/animation task
xTaskCreate(led_fade_task, "led_fade", 4096, NULL, 5, NULL);
ESP_LOGI(TAG,"TTP229 16-key piano extended start!");
// previous key state for edge detection
uint16_t prev_keys = 0;
while (1) {
uint16_t keys = ttp229_read_keys(); // active-low pressed -> bit=1 for pressed
// FUNCTION KEYS - these are physical TP4..TP7 (bits 4..7)
// detect rising edge (pressed now and not previously pressed)
uint16_t newly = (keys & ~prev_keys);
// TP4 (bit 4) -> toggle LED-key interaction mode
if (newly & (1<<4)) {
if (g_mode == MODE_LED_KEY_INTERACT) {
g_mode = MODE_NORMAL;
ESP_LOGI(TAG,"TP4: back to NORMAL");
oled_clear();
oled_draw_text_centered("^_^ (normal)");
oled_refresh();
} else {
g_mode = MODE_LED_KEY_INTERACT;
ESP_LOGI(TAG,"TP4: LED-KEY-INTERACT");
oled_clear();
oled_draw_bitmap(56, 28, 16, 8, bm_face1);
oled_refresh();
}
// small debounce
vTaskDelay(pdMS_TO_TICKS(200));
}
// TP5 (bit 5) -> toggle gradient cycle
if (newly & (1<<5)) {
if (g_mode == MODE_LED_GRADIENT_CYCLE) {
g_mode = MODE_NORMAL;
gradient_run = false;
ESP_LOGI(TAG,"TP5: back to NORMAL");
oled_clear();
oled_draw_bitmap(56,28,16,8,bm_face1);
oled_refresh();
} else {
g_mode = MODE_LED_GRADIENT_CYCLE;
gradient_run = true;
ESP_LOGI(TAG,"TP5: LED GRADIENT CYCLE");
oled_clear();
oled_draw_bitmap(56,28,16,8,bm_face2);
oled_refresh();
}
vTaskDelay(pdMS_TO_TICKS(200));
}
// TP6 (bit 6) -> toggle blessing play
if (newly & (1<<6)) {
if (g_mode == MODE_BLESSING_PLAY) {
g_mode = MODE_NORMAL;
stop_blessing_task();
ESP_LOGI(TAG,"TP6: STOP blessing");
oled_clear();
oled_draw_text_centered("Stopped");
oled_refresh();
} else {
g_mode = MODE_BLESSING_PLAY;
start_blessing_task();
ESP_LOGI(TAG,"TP6: START blessing");
oled_clear();
oled_draw_bitmap(56,20,12,12,bm_music);
oled_refresh();
}
vTaskDelay(pdMS_TO_TICKS(200));
}
// TP7 (bit 7) -> toggle game
if (newly & (1<<7)) {
if (g_mode == MODE_GAME) {
g_mode = MODE_NORMAL;
game_running = false;
ESP_LOGI(TAG,"TP7: STOP game");
oled_clear();
oled_draw_text_centered("Bye!");
oled_refresh();
} else {
g_mode = MODE_GAME;
game_running = true;
memset(game_regions,0,sizeof(game_regions));
game_next_spawn = xTaskGetTickCount() + pdMS_TO_TICKS(800);
ESP_LOGI(TAG,"TP7: START game");
oled_clear();
oled_draw_text_centered("Go!");
oled_refresh();
}
vTaskDelay(pdMS_TO_TICKS(200));
}
// If in game mode, manage spawns & draws
if (g_mode == MODE_GAME && game_running) {
if (xTaskGetTickCount() > game_next_spawn) {
game_spawn_block();
game_next_spawn = xTaskGetTickCount() + pdMS_TO_TICKS(1000 + (esp_random()%2000));
game_draw();
}
}
// handle normal piano key behavior and modes requiring per-key handling
if (keys) {
// iterate logical keys (i = 0..15)
bool played_any = false;
for (int i=0;i<16;i++) {
uint8_t tp = key_map[i];
if (keys & (1 << tp)) {
// pressed
// If function key pressed, we already handled on edge; just ignore as piano note
if (tp>=4 && tp<=7) continue;
int freq = note_freqs[i];
buzzer_play_tone(freq);
played_any = true;
// Mode specific behavior:
if (g_mode == MODE_LED_KEY_INTERACT) {
// map key i to LED idx (we'll map first 12 keys to leds)
if (i < NUM_LEDS) {
xSemaphoreTake(led_mutex, portMAX_DELAY);
led_active[i] = 1; // latch on
led_brightness[i] = 255;
xSemaphoreGive(led_mutex);
led_update_from_state();
}
} else if (g_mode == MODE_NORMAL) {
// music visualization on OLED: draw bars based on which keys are pressed
oled_draw_music_bars(keys);
// show a friendly face small
// do not block
} else if (g_mode == MODE_BLESSING_PLAY) {
// pressing keys can also trigger small sound/LED feedback
// light a LED proportional
int idx = i % NUM_LEDS;
xSemaphoreTake(led_mutex, portMAX_DELAY);
led_brightness[idx] = 255;
xSemaphoreGive(led_mutex);
led_update_from_state();
} else if (g_mode == MODE_GAME) {
// Map certain keys to game regions.
// Game expects 7 regions -> map 7 left-most piano keys (i 0..6)
if (i < 7) {
if (game_regions[i]) {
game_regions[i] = 0; // clear block
// small success beep
buzzer_play_tone(880);
vTaskDelay(pdMS_TO_TICKS(80));
buzzer_play_tone(0);
game_draw();
} else {
// miss sound
buzzer_play_tone(220);
vTaskDelay(pdMS_TO_TICKS(60));
buzzer_play_tone(0);
}
}
} // end mode cases
// For LED gradient cycle, pressing keys can temporarily bump brightness
if (g_mode == MODE_LED_GRADIENT_CYCLE) {
if (i < NUM_LEDS) {
xSemaphoreTake(led_mutex, portMAX_DELAY);
led_brightness[i] = 255;
xSemaphoreGive(led_mutex);
led_update_from_state();
}
}
// simple small debounce for this key handling
// (we don't use goto as before)
} else {
// key not pressed -> if in LED-key-interact mode, we should mark release for that key_map index
// find i -> led index
if (g_mode == MODE_LED_KEY_INTERACT) {
if (i < NUM_LEDS) {
// if it was active and now released, start fade by clearing active
if (led_active[i]) {
// check previous state had it pressed? simplest: clear active if not pressed
// We can't know per-key previous here unless we track prev_keys; we'll use prev_keys below
}
}
}
}
} // end for keys loop
if (!played_any) { /* nothing else */ }
// hold tone for small time to avoid chopping
vTaskDelay(pdMS_TO_TICKS(40));
// stop tone unless key still pressed
// We'll keep continuing while key status remains; main loop will handle
} else {
// no keys pressed -> silence buzzer
buzzer_play_tone(0);
}
// handle key releases specifically to clear latched led_active when key released
uint16_t releases = (~keys) & prev_keys;
if (releases) {
for (int i=0;i<16;i++) {
uint8_t tp = key_map[i];
if (releases & (1<<tp)) {
// if it was latched active for LED-key-interact, clear active -> fade begins
if (g_mode == MODE_LED_KEY_INTERACT && i < NUM_LEDS) {
xSemaphoreTake(led_mutex, portMAX_DELAY);
led_active[i] = 0; // fade will reduce brightness in led_fade_task
xSemaphoreGive(led_mutex);
}
}
}
}
prev_keys = keys;
vTaskDelay(pdMS_TO_TICKS(20));
}
}
/*** OLED small helper to show music bars visualization ***/
static void oled_draw_music_bars(uint16_t keys) {
memset(oled_buf,0,OLED_BUF_SIZE);
// We'll show up to 12 bars across horizontally mapped to 12 leds/keys
int bars = NUM_LEDS;
int w = OLED_WIDTH / bars;
for (int i=0;i<bars;i++) {
int x = i*w + 1;
int base_y = OLED_HEIGHT - 2;
// height depends on whether corresponding key is pressed
// find corresponding logical key: reverse map -> find i's logical index if any
int bar_h = 6;
// simple rule: if any pressed among first 12 keys matching i -> tall
bool pressed = false;
for (int li=0; li<16; li++) {
if (key_map[li] < 16 && li == i) {
if (keys & (1 << key_map[li])) pressed = true;
}
}
if (pressed) bar_h = OLED_HEIGHT/2;
else bar_h = OLED_HEIGHT/6;
// draw rectangle
for (int yy = base_y - bar_h; yy <= base_y; yy++) {
for (int xx = x; xx < x + w - 2; xx++) {
if (xx<0 || xx>=OLED_WIDTH || yy<0 || yy>=OLED_HEIGHT) continue;
int page = yy/8;
int idx = page*OLED_WIDTH + xx;
oled_buf[idx] |= (1 << (yy&7));
}
}
}
oled_refresh();
}
这段代码出现了一些问题,出现了无用的参数,删去这些部分,并且tp6键播放blessing出现异常,蜂鸣器持续发生,找出问题所在,修改并重新整合代码
最新发布