动画animition方法

本文介绍了一种在Android应用中实现帧动画的方法。通过设置ImageView的背景为AnimationDrawable资源,并控制其开始和停止,实现了简单的动画效果。

 protected void animate() {          // TODO Auto-generated method stub 
       
ImageView img = (ImageView) findViewById(R.id.image_view); 
        img
.setBackgroundResource(R.drawable.frame_animation); 
        img
.setVisibility(ImageView.VISIBLE); 
 
       
AnimationDrawable anim = (AnimationDrawable) img.getBackground(); 
 
       
if(anim.isRunning()){ 
            anim
.stop(); 
       
} 
       
else{  
            anim
.stop(); 
            anim
.start(); 
       
} 
   
} 

#include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <stdbool.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_system.h" #include "esp_random.h" #include "esp_rom_sys.h" #include "led_strip.h" #define TAG "TTP229_EXT_FIXED" #define WS2812B_PIN 2 #define NUM_LEDS 12 #define TTP229_SCL 27 #define TTP229_SDO 26 #define BUZZER_PIN 25 #define OLED_SPI_HOST HSPI_HOST #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) #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* ---------- 全局与声明 ---------- */ led_strip_handle_t led_strip; static uint8_t led_brightness[NUM_LEDS]; static uint8_t led_active[NUM_LEDS]; static SemaphoreHandle_t led_mutex = NULL; typedef enum { MODE_NORMAL = 0, MODE_LED_KEY_INTERACT, MODE_LED_GRADIENT_CYCLE, MODE_BLESSING_PLAY, MODE_GAME } app_mode_t; static volatile app_mode_t g_mode = MODE_NORMAL; /* gradient */ static volatile bool gradient_run = false; static int gradient_pos = 0; /* blessing task control */ 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]; static TickType_t game_next_spawn; /* OLED */ static uint8_t oled_buf[OLED_BUF_SIZE]; static spi_device_handle_t spi_oled; /* 菜单状态 */ typedef enum { MENU_NONE, MENU_MUSIC_SELECT, MENU_FUNC_SELECT } menu_state_t; static volatile menu_state_t current_menu = MENU_NONE; static int selected_item = 0; static int brightness_level = 4; // 亮度等级:0~4(4=最大) static int pitch_offset_semitones = 0; // 音高偏移(半音) /* 映射:TP -> LED 索引 */ static const int8_t tp_to_led_idx[16] = { /* TP0 */ 8, /* TP1 */ 9, /* TP2 */ 10, /* TP3 */ 11, /* TP4 */ -1, /* TP5 */ -1, /* TP6 */ -1, /* TP7 */ -1, /* TP8 */ 3, /* TP9 */ 2, /* TP10 */ 1, /* TP11 */ 0, /* TP12 */ 4, /* TP13 */ 5, /* TP14 */ 6, /* TP15 */ 7 }; /* 游戏键映射 */ static const int8_t tp_to_game_region[16] = { /* TP0 */ -1, /* TP1 */ 1, /* TP2 */ -1, /* TP3 */ 0, /* TP4 */ -1, /* TP5 */ -1, /* TP6 */ -1, /* TP7 */ -1, /* TP8 */ -1, /* TP9 */ 5, /* TP10 */ -1, /* TP11 */ 6, /* TP12 */ 4, /* TP13 */ -1, /* TP14 */ 3, /* TP15 */ 2 }; /* 彩虹颜色 */ 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 键基础频率 */ static const int note_freqs[16] = { 262,294,330,349,370,392,415,440,466,494,523,554,587,622,659,698 }; /* TTP 按键重映射 */ static const uint8_t key_map[16] = {3,2,1,0,15,14,13,12,8,9,10,11,4,5,6,7}; /* 小音符位图 (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 }; /* 祝福曲调 */ typedef struct { int freq; int dur; } tone_t; static const tone_t blessing[] = { {494, 600}, {440, 600}, {523, 150}, {440, 150}, {392, 150}, {492, 150}, {523, 150}, {494, 150}, {440, 300}, {440, 150}, {392, 150}, {440, 150}, {587, 150}, {494, 150}, {494, 150}, {523, 150}, {587, 150}, {587, 150}, {659, 150}, {392, 150}, {659, 150}, {587, 150}, {523, 150}, {494, 150}, {392, 300}, {494, 150}, {523, 150}, {587, 150}, {523, 600}, {0, 600}, {0, 600}, {0, 600}, {0, 600}, {0, 600}, {0, 600}, {494, 150}, {523, 150}, {587, 150}, {494, 150}, {587, 150}, {494, 150}, {587, 150}, {659, 150}, {587, 150}, {587, 150}, {587, 150}, {523, 150}, {494, 150}, {440, 150}, {440, 150}, {392, 150}, {494, 150}, {494, 150}, {0, 150}, {392, 150}, {440, 150}, {494, 150}, {523, 150}, {494, 150}, {494, 150}, {392, 150}, {440, 150}, {494, 150}, {494, 150}, {523, 150}, {494, 150}, {494, 150}, {440, 150}, {440, 150}, {494, 150}, {494, 150}, {440, 150}, {392, 150}, {494, 150}, {494, 150}, {0, 150}, {494, 150}, {523, 150}, {587, 150}, {494, 150}, {587, 150}, {494, 150}, {587, 150}, {392, 150}, {440, 150}, {392, 150}, {440, 150}, {392, 150}, {440, 150}, {440, 150}, {494, 150}, {494, 150}, {440, 150}, {392, 150}, {494, 150}, {494, 150}, {0, 150}, {494, 150}, {523, 150}, {587, 150}, {494, 150}, {587, 150}, {494, 150}, {587, 150}, {659, 150}, {587, 150}, {659, 150}, {587, 150}, {659, 150}, {587, 150}, {523, 150}, {523, 150}, {587, 150}, {587, 150}, {523, 150}, {494, 150}, {587, 150}, {587, 150}, {0, 150}, {494, 150}, {523, 150}, {440, 150}, {440, 150}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {392, 150}, {392, 150}, {659, 150}, {740, 150}, {440, 150}, {494, 300}, {392, 150}, {440, 150}, {440, 150}, {440, 150}, {494, 150}, {440, 150}, {440, 150}, {440, 150}, {523, 150}, {440, 150}, {659, 150}, {659, 150}, {740, 150}, {440, 150}, {494, 300}, {392, 150}, {440, 150}, {440, 150}, {494, 150}, {440, 150}, {440, 150}, {440, 150}, {523, 150}, {440, 150}, {740, 150}, {659, 150}, {587, 300}, {392, 150}, {659, 150}, {740, 150}, {392, 150}, {392, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {392, 150}, {392, 150}, {659, 150}, {392, 150}, {659, 150}, {659, 150}, {523, 300}, {494, 300}, {440, 300}, {392, 300}, {440, 300}, {494, 300}, {523, 300}, {494, 300}, {0, 300}, {626, 150}, {626, 150}, {392, 300}, {392, 300}, {392, 300}, {392, 300}, {523, 300}, {392, 300}, {659, 300}, {440, 300}, {494, 300}, {440, 300}, {494, 300}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {0, 300}, {494, 300}, {494, 300}, {392, 150}, {494, 150}, {494, 150}, {659, 150}, {523, 150}, {494, 300}, {659, 300}, {392, 150}, {659, 150}, {587, 150}, {587, 150}, {494, 300}, {494, 150}, {392, 150}, {440, 150}, {494, 150}, {523, 150}, {494, 150}, {440, 150}, {392, 150}, {494, 300}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {0, 300}, {494, 300}, {494, 300}, {392, 150}, {494, 150}, {494, 150}, {659, 150}, {523, 150}, {494, 300}, {659, 300}, {392, 150}, {659, 150}, {587, 150}, {587, 150}, {494, 300}, {523, 300}, {392, 150}, {659, 150}, {587, 150}, {523, 150}, {494, 150}, {440, 150}, {440, 150}, {392, 150}, {440, 150}, {392, 150}, {0, 300}, {0, 300}, {0, 600}, {0, 600}, {0, 600}, {0, 450}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {523, 150}, {494, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {392, 150}, {494, 150} }; static const int blessing_len = sizeof(blessing) / sizeof(blessing[0]); // 音乐列表 static const char* music_names[3] = {"Blessing", "Song 2", "Song 3"}; static const tone_t* music_tracks[3] = {blessing, NULL, NULL}; static const int music_lengths[3] = {blessing_len, 1, 1}; /* 字体数据:6x8 ASCII */ const uint8_t font6x8[95 * 6] = { 0x00,0x00,0x00,0x00,0x00,0x00, // ' ' 0x00,0x00,0x5F,0x00,0x00,0x00, // ! 0x00,0x03,0x00,0x03,0x00,0x00, // " 0x14,0x3E,0x14,0x3E,0x14,0x00, // # 0x24,0x2A,0x7F,0x2A,0x12,0x00, // $ 0x43,0x33,0x08,0x66,0x61,0x00, // % 0x36,0x49,0x55,0x22,0x50,0x00, // & 0x00,0x05,0x03,0x00,0x00,0x00, // ' 0x00,0x1C,0x22,0x41,0x00,0x00, // ( 0x00,0x41,0x22,0x1C,0x00,0x00, // ) 0x14,0x08,0x3E,0x08,0x14,0x00, // * 0x08,0x08,0x3E,0x08,0x08,0x00, // + 0x00,0x50,0x30,0x00,0x00,0x00, // , 0x08,0x08,0x08,0x08,0x08,0x00, // - 0x00,0x60,0x60,0x00,0x00,0x00, // . 0x20,0x10,0x08,0x04,0x02,0x00, // / 0x3E,0x51,0x49,0x45,0x3E,0x00, // 0 0x00,0x42,0x7F,0x40,0x00,0x00, // 1 0x42,0x61,0x51,0x49,0x46,0x00, // 2 0x21,0x41,0x45,0x4B,0x31,0x00, // 3 0x18,0x14,0x12,0x7F,0x10,0x00, // 4 0x27,0x45,0x45,0x45,0x39,0x00, // 5 0x3C,0x4A,0x49,0x49,0x30,0x00, // 6 0x01,0x71,0x09,0x05,0x03,0x00, // 7 0x36,0x49,0x49,0x49,0x36,0x00, // 8 0x06,0x49,0x49,0x29,0x1E,0x00, // 9 0x00,0x36,0x36,0x00,0x00,0x00, // : 0x00,0x56,0x36,0x00,0x00,0x00, // ; 0x08,0x14,0x22,0x41,0x00,0x00, // < 0x14,0x14,0x14,0x14,0x14,0x00, // = 0x00,0x41,0x22,0x14,0x08,0x00, // > 0x02,0x01,0x51,0x09,0x06,0x00, // ? 0x3E,0x41,0x5D,0x55,0x1E,0x00, // @ 0x7C,0x12,0x11,0x12,0x7C,0x00, // A 0x7F,0x49,0x49,0x49,0x36,0x00, // B 0x3E,0x41,0x41,0x41,0x22,0x00, // C 0x7F,0x41,0x41,0x22,0x1C,0x00, // D 0x7F,0x49,0x49,0x49,0x41,0x00, // E 0x7F,0x09,0x09,0x09,0x01,0x00, // F 0x3E,0x41,0x49,0x49,0x7A,0x00, // G 0x7F,0x08,0x08,0x08,0x7F,0x00, // H 0x00,0x41,0x7F,0x41,0x00,0x00, // I 0x20,0x40,0x41,0x3F,0x01,0x00, // J 0x7F,0x08,0x14,0x22,0x41,0x00, // K 0x7F,0x40,0x40,0x40,0x40,0x00, // L 0x7F,0x02,0x0C,0x02,0x7F,0x00, // M 0x7F,0x04,0x08,0x10,0x7F,0x00, // N 0x3E,0x41,0x41,0x41,0x3E,0x00, // O 0x7F,0x09,0x09,0x09,0x06,0x00, // P 0x3E,0x41,0x51,0x21,0x5E,0x00, // Q 0x7F,0x09,0x19,0x29,0x46,0x00, // R 0x46,0x49,0x49,0x49,0x31,0x00, // S 0x01,0x01,0x7F,0x01,0x01,0x00, // T 0x3F,0x40,0x40,0x40,0x3F,0x00, // U 0x1F,0x20,0x40,0x20,0x1F,0x00, // V 0x3F,0x40,0x38,0x40,0x3F,0x00, // W 0x63,0x14,0x08,0x14,0x63,0x00, // X 0x07,0x08,0x70,0x08,0x07,0x00, // Y 0x61,0x51,0x49,0x45,0x43,0x00, // Z 0x00,0x7F,0x41,0x41,0x00,0x00, // [ 0x02,0x04,0x08,0x10,0x20,0x00, // '\\' 0x00,0x41,0x41,0x7F,0x00,0x00, // ] 0x04,0x02,0x01,0x02,0x04,0x00, // ^ 0x40,0x40,0x40,0x40,0x40,0x00, // _ 0x00,0x01,0x02,0x04,0x00,0x00, // ` 0x20,0x54,0x54,0x54,0x78,0x00, // a 0x7F,0x44,0x44,0x44,0x38,0x00, // b 0x38,0x44,0x44,0x44,0x20,0x00, // c 0x38,0x44,0x44,0x44,0x7F,0x00, // d 0x38,0x54,0x54,0x54,0x18,0x00, // e 0x08,0x7E,0x09,0x01,0x02,0x00, // f 0x18,0xA4,0xA4,0xA4,0x7C,0x00, // g 0x7F,0x08,0x04,0x04,0x78,0x00, // h 0x00,0x44,0x7D,0x40,0x00,0x00, // i 0x40,0x80,0x84,0x7D,0x00,0x00, // j 0x7F,0x10,0x28,0x44,0x00,0x00, // k 0x00,0x41,0x7F,0x40,0x00,0x00, // l 0x7C,0x04,0x18,0x04,0x78,0x00, // m 0x7C,0x08,0x04,0x04,0x78,0x00, // n 0x38,0x44,0x44,0x44,0x38,0x00, // o 0xFC,0x24,0x24,0x24,0x18,0x00, // p 0x18,0x24,0x24,0x18,0xFC,0x00, // q 0x7C,0x08,0x04,0x04,0x08,0x00, // r 0x48,0x54,0x54,0x54,0x20,0x00, // s 0x04,0x04,0x3F,0x44,0x40,0x00, // t 0x3C,0x40,0x40,0x20,0x7C,0x00, // u 0x1C,0x20,0x40,0x20,0x1C,0x00, // v 0x3C,0x40,0x30,0x40,0x3C,0x00, // w 0x44,0x28,0x10,0x28,0x44,0x00, // x 0x1C,0xA0,0xA0,0x7C,0x00,0x00, // y 0x44,0x64,0x54,0x4C,0x44,0x00, // z 0x00,0x08,0x36,0x41,0x00,0x00, // { 0x00,0x00,0x77,0x00,0x00,0x00, // | 0x00,0x41,0x36,0x08,0x00,0x00, // } 0x02,0x01,0x02,0x04,0x02,0x00 // ~ }; /* ---------- 前向声明 ---------- */ 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_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_face_happy(void); static void oled_draw_music_symbol(void); static void led_update_from_state(void); static void led_fade_task(void *arg); static void blessing_task(void *arg); static void start_blessing_task(void); static void stop_blessing_task(void); static void game_spawn_block(void); static void game_draw(void); void run_animation(void); static void oled_draw_menu(const char* title, const char* items[], int item_count); /* ---------- 实现 ---------- */ 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)); if (!led_mutex) led_mutex = xSemaphoreCreateMutex(); for (int i=0;i<NUM_LEDS;i++){ led_brightness[i]=255; led_active[i]=0; led_strip_set_pixel(led_strip, i, rainbow_colors[i][0], rainbow_colors[i][1], rainbow_colors[i][2]); } led_strip_refresh(led_strip); } 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); } static void oled_send_cmd(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, 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_refresh(void) { 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(); } static void oled_draw_bitmap(int x, int y, int w, int h, const uint8_t *bm) { 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)); } } } } static void oled_draw_face_happy(void) { memset(oled_buf, 0, OLED_BUF_SIZE); for (int yy = 20; yy <= 22; yy++) { for (int xx = 30; xx <= 40; xx++) { if (yy == 21 || yy == 22) { int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); } } } for (int yy = 20; yy <= 22; yy++) { for (int xx = 85; xx <= 95; xx++) { if (yy == 21 || yy == 22) { int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); } } } for (int xx = 46; xx < 82; xx++) { int yy = 42 + (int)(4.0f * sinf(((xx - 46) / 36.0f) * M_PI)); if (yy < 0 || yy >= OLED_HEIGHT) continue; int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); if (yy + 1 < OLED_HEIGHT) { int page2 = (yy + 1) / 8; oled_buf[page2 * OLED_WIDTH + xx] |= (1 << ((yy + 1) & 7)); } } oled_refresh(); } static void oled_draw_music_symbol(void) { memset(oled_buf,0,OLED_BUF_SIZE); oled_draw_bitmap(56, 20, 12, 12, bm_music); oled_refresh(); } static void oled_draw_menu(const char* title, const char* items[], int item_count) { memset(oled_buf, 0, OLED_BUF_SIZE); int len = strlen(title) * 6; int x = (OLED_WIDTH - len) / 2; int y = 0; for (int i = 0; title[i]; i++) { uint8_t ch = title[i] - ' '; const uint8_t* font = &font6x8[ch * 6]; for (int col = 0; col < 6; col++) { if (col >= 6) break; uint8_t b = font[col]; for (int bit = 0; bit < 8; bit++) { if (b & (1 << bit)) { int py = y + bit, px = x + i * 6 + col; if (px < OLED_WIDTH && py < OLED_HEIGHT) { oled_buf[(py / 8) * OLED_WIDTH + px] |= (1 << (py & 7)); } } } } } y = 16; for (int i = 0; i < item_count; i++) { if (i == selected_item) { oled_buf[(y / 8) * OLED_WIDTH + 2] |= (1 << (y & 7)); } len = strlen(items[i]); for (int j = 0; j < len; j++) { int px = 10 + j * 6, py = y; uint8_t ch = items[i][j] - ' '; const uint8_t* font = &font6x8[ch * 6]; for (int col = 0; col < 6; col++) { uint8_t b = font[col]; for (int bit = 0; bit < 8; bit++) { if (b & (1 << bit)) { int ppy = py + bit; if (px + col < OLED_WIDTH && ppy < OLED_HEIGHT) { oled_buf[(ppy / 8) * OLED_WIDTH + px + col] |= (1 << (ppy & 7)); } } } } } y += 10; } oled_refresh(); } static void set_led_rgb(int idx, uint8_t r, uint8_t g, uint8_t b, uint8_t brightness) { uint8_t r2 = (r * brightness)/255; uint8_t g2 = (g * brightness)/255; uint8_t b2 = (b * brightness)/255; led_strip_set_pixel(led_strip, idx, r2, g2, b2); } static void led_update_from_state(void) { if (!led_mutex) return; 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); } 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) { int nb = (int)led_brightness[i] - 8; if (nb < 0) nb = 0; led_brightness[i] = (uint8_t)nb; changed = true; } } } else if (g_mode == MODE_LED_GRADIENT_CYCLE && gradient_run) { gradient_pos++; for (int i=0;i<NUM_LEDS;i++){ int pos = (i + gradient_pos) % NUM_LEDS; float v = (1.0f + sinf((pos*2.0f*M_PI)/NUM_LEDS)) * 0.5f; int b = 80 + (int)(175.0f * v); if (b < 0) b = 0; if (b>255) b = 255; led_brightness[i] = (uint8_t)b; } changed = true; } else { 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)); } } static void blessing_task(void *arg) { ESP_LOGI(TAG, "Blessing task started"); const tone_t* track = music_tracks[selected_item]; int len = music_lengths[selected_item]; while (blessing_running) { for (int i=0;i<len && blessing_running;i++){ int f = track[i].freq; int d = track[i].dur; if (f > 0) buzzer_play_tone(f); else buzzer_play_tone(0); int led_idx = i % NUM_LEDS; xSemaphoreTake(led_mutex, portMAX_DELAY); led_brightness[led_idx] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); int chunks = d / 20; if (chunks < 1) chunks = 1; for (int c=0;c<chunks && blessing_running;c++){ vTaskDelay(pdMS_TO_TICKS(d / chunks)); } buzzer_play_tone(0); vTaskDelay(pdMS_TO_TICKS(40)); } } buzzer_play_tone(0); ESP_LOGI(TAG, "Blessing task exit"); blessing_task_handle = NULL; vTaskDelete(NULL); } static void start_blessing_task(void) { if (blessing_running) return; blessing_running = true; BaseType_t r = xTaskCreate(blessing_task, "blessing", 4096, NULL, 5, &blessing_task_handle); if (r != pdPASS) { ESP_LOGW(TAG,"Create blessing task failed"); blessing_running = false; blessing_task_handle = NULL; } } static void stop_blessing_task(void) { if (!blessing_running) return; blessing_running = false; int t = 0; while (blessing_task_handle != NULL && t < 50) { vTaskDelay(pdMS_TO_TICKS(50)); t++; } buzzer_play_tone(0); } static void game_spawn_block(void) { 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) { memset(oled_buf,0,OLED_BUF_SIZE); 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]) { 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 { 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(); } void run_animation(void) { float phase = 0.0f; for (int frame = 0; frame < 100; frame++) { xSemaphoreTake(led_mutex, portMAX_DELAY); for (int i = 0; i < NUM_LEDS; i++) { float angle = phase + i * (2 * M_PI / NUM_LEDS); float v = (1.0f + sinf(angle)) * 0.5f; int b = (int)(v * 255); set_led_rgb(i, rainbow_colors[i][0], rainbow_colors[i][1], rainbow_colors[i][2], b); } xSemaphoreGive(led_mutex); led_strip_refresh(led_strip); phase += 0.1f; vTaskDelay(pdMS_TO_TICKS(50)); uint16_t k = ttp229_read_keys(); if (k & ((1<<4)|(1<<5)|(1<<6)|(1<<7))) break; } oled_clear(); oled_draw_face_happy(); } void app_main(void) { ESP_LOGI(TAG,"App start"); ws2812b_init(); ttp229_init(); buzzer_init(); gpio_config_t io_conf = {0}; 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_config_t buscfg = { .miso_io_num = -1, .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)); gpio_set_level(OLED_RST_PIN, 0); vTaskDelay(pdMS_TO_TICKS(10)); gpio_set_level(OLED_RST_PIN, 1); vTaskDelay(pdMS_TO_TICKS(10)); const uint8_t init_cmds[] = { 0xAE,0xD5,0x80,0xA8,0x3F,0xD3,0x00,0x40,0x8D,0x14,0x20,0x00,0xA1,0xC8,0xDA,0x12,0x81,0xCF,0xD9,0xF1,0xDB,0x40,0xA4,0xA6,0xAF }; oled_send_cmd(init_cmds, sizeof(init_cmds)); oled_clear(); xTaskCreate(led_fade_task, "led_fade", 4096, NULL, 5, NULL); uint16_t prev_keys = 0; ESP_LOGI(TAG,"Main loop start"); while (1) { uint16_t keys = ttp229_read_keys(); uint16_t newly = (keys & ~prev_keys); uint16_t releases = (~keys) & prev_keys; bool tp4_pressed = keys & (1 << 4); bool tp5_pressed = keys & (1 << 5); // === 功能键单独处理 === if (newly & (1 << 4)) { if (current_menu == MENU_NONE && !(tp5_pressed)) { if (g_mode == MODE_LED_KEY_INTERACT) { g_mode = MODE_NORMAL; oled_clear(); oled_draw_face_happy(); } else { g_mode = MODE_LED_KEY_INTERACT; oled_clear(); oled_draw_face_happy(); } vTaskDelay(pdMS_TO_TICKS(200)); } } if (newly & (1 << 5)) { if (current_menu == MENU_NONE && !(tp4_pressed)) { if (g_mode == MODE_LED_GRADIENT_CYCLE) { g_mode = MODE_NORMAL; gradient_run = false; oled_clear(); oled_draw_face_happy(); } else { g_mode = MODE_LED_GRADIENT_CYCLE; gradient_run = true; oled_clear(); oled_draw_face_happy(); } vTaskDelay(pdMS_TO_TICKS(200)); } } if (newly & (1 << 6)) { if (current_menu == MENU_NONE) { current_menu = MENU_MUSIC_SELECT; selected_item = 0; g_mode = MODE_NORMAL; stop_blessing_task(); oled_draw_menu("Music", music_names, 3); } else if (current_menu == MENU_MUSIC_SELECT) { current_menu = MENU_NONE; oled_clear(); oled_draw_face_happy(); } vTaskDelay(pdMS_TO_TICKS(200)); } if (newly & (1 << 7)) { if (current_menu == MENU_NONE) { current_menu = MENU_FUNC_SELECT; selected_item = 0; static const char* func_items[] = {"Game", "Animation"}; oled_draw_menu("Func", func_items, 2); } else if (current_menu == MENU_FUNC_SELECT) { current_menu = MENU_NONE; oled_clear(); oled_draw_face_happy(); } vTaskDelay(pdMS_TO_TICKS(200)); } // === 菜单内导航 === if (current_menu == MENU_MUSIC_SELECT) { if (newly & (1 << 5)) { selected_item = (selected_item - 1 + 3) % 3; oled_draw_menu("Music", music_names, 3); } else if (newly & (1 << 7)) { selected_item = (selected_item + 1) % 3; oled_draw_menu("Music", music_names, 3); } else if (newly & (1 << 4)) { if (music_tracks[selected_item]) { stop_blessing_task(); start_blessing_task(); g_mode = MODE_BLESSING_PLAY; oled_clear(); oled_draw_music_symbol(); } current_menu = MENU_NONE; } vTaskDelay(pdMS_TO_TICKS(150)); prev_keys = keys; continue; } if (current_menu == MENU_FUNC_SELECT) { if (newly & (1 << 4)) { selected_item = (selected_item - 1 + 2) % 2; static const char* items[] = {"Game", "Animation"}; oled_draw_menu("Func", items, 2); } else if (newly & (1 << 6)) { selected_item = (selected_item + 1) % 2; static const char* items[] = {"Game", "Animation"}; oled_draw_menu("Func", items, 2); } else if (newly & (1 << 5)) { if (selected_item == 0) { g_mode = MODE_GAME; game_running = true; memset(game_regions, 0, sizeof(game_regions)); game_next_spawn = xTaskGetTickCount() + pdMS_TO_TICKS(800); oled_clear(); game_draw(); } else { g_mode = MODE_NORMAL; run_animation(); } current_menu = MENU_NONE; } vTaskDelay(pdMS_TO_TICKS(150)); prev_keys = keys; continue; } // === 游戏模式:可随时按 TP7 退出 === 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(); } // 新增:TP7 可退出游戏 if (newly & (1 << 7)) { g_mode = MODE_NORMAL; game_running = false; oled_clear(); oled_draw_face_happy(); } } // === 处理琴键输入 === if (keys) { bool played_any = false; for (int i=0;i<16;i++){ uint8_t tp = key_map[i]; if (keys & (1 << tp)) { if (tp>=4 && tp<=7) continue; if (g_mode == MODE_GAME) { int region = tp_to_game_region[tp]; if (region >= 0 && region < 7) { if (game_regions[region]) { game_regions[region] = 0; buzzer_play_tone(880); vTaskDelay(pdMS_TO_TICKS(80)); buzzer_play_tone(0); game_draw(); } else { buzzer_play_tone(220); vTaskDelay(pdMS_TO_TICKS(60)); buzzer_play_tone(0); } } } else { float ratio = powf(2.0f, pitch_offset_semitones / 12.0f); int adjusted_freq = (int)(note_freqs[i] * ratio); if (g_mode != MODE_BLESSING_PLAY) { buzzer_play_tone(adjusted_freq); played_any = true; } int led_idx = tp_to_led_idx[tp]; if (g_mode == MODE_LED_KEY_INTERACT && led_idx >= 0 && led_idx < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_active[led_idx] = 1; led_brightness[led_idx] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); } if (g_mode == MODE_LED_GRADIENT_CYCLE && led_idx >= 0 && led_idx < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_brightness[led_idx] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); } if (g_mode == MODE_NORMAL) { memset(oled_buf,0,OLED_BUF_SIZE); int bars = NUM_LEDS; int w = OLED_WIDTH / bars; for (int bi=0;bi<bars;bi++){ int x = bi*w + 1; int base_y = OLED_HEIGHT - 2; int bar_h = (bi == i) ? (OLED_HEIGHT/2) : (OLED_HEIGHT/6); 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(); } } } } vTaskDelay(pdMS_TO_TICKS(40)); } else { if (g_mode != MODE_BLESSING_PLAY) buzzer_play_tone(0); } if (releases) { for (int i=0;i<16;i++){ uint8_t tp = key_map[i]; if (releases & (1<<tp)) { int led_idx = tp_to_led_idx[tp]; if (g_mode == MODE_LED_KEY_INTERACT && led_idx >=0 && led_idx < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_active[led_idx] = 0; xSemaphoreGive(led_mutex); } } } } if (newly & (1 << 5)) { if (current_menu == MENU_NONE && g_mode == MODE_NORMAL) { oled_clear(); oled_draw_face_happy(); } } prev_keys = keys; vTaskDelay(pdMS_TO_TICKS(20)); } 问题在这里:这段代码的问题是,tp6菜单中有三首歌,但是第二三首空占位,导致按下tp4后直接开始播放音乐(本来tp4应该是上下选择用的),为了避免这个问题,给第二三首音乐随便加两个音调,模拟三首歌都能播放的功能,另外,tp7的animition我不希望是灯光表演,(删去tp7的灯光表演)而是oled上的动画放映,播放动画如需要ssd1306.h请提醒我, 提醒我,不改变其他原有功能,给出我修改后的完整代码程序main.c,阐明修改思路,保留了什么,修改了什么
最新发布
10-23
```markdown ### 代码概述 该代码实现了一个基于 ESP32 的多功能交互设备,集成了触摸按键(TTP229)、WS2812B 彩灯、蜂鸣器、OLED 显示屏等功能。支持以下模式: - **琴键演奏**:通过触摸输入触发音调和灯光反馈。 - **灯光交互模式**:按键点亮对应 LED 并渐暗。 - **渐变循环灯光**:自动流动的彩虹亮度效果。 - **祝福曲播放**:选择音乐并通过蜂鸣器播放,同时控制灯光同步。 - **游戏模式**:模拟下落方块,用户点击消除。 - **动画模式**:原为灯光动画,现需改为 OLED 动画。 当前存在的问题: 1. 音乐列表中后两首为空占位,导致 `TP4` 错误触发播放。 2. `TP7` 触发的 “Animation” 当前是灯光动画,要求改为 OLED 动画,并保留原有功能结构。 --- ### 修改思路 #### 1. **修复空音乐占位问题** - 原因:`music_tracks[1]` 和 `music_tracks[2]` 为 `NULL`,长度为 1,但代码未做判断直接启动任务,导致播放非法数据或默认第一首。 - 解决方案:为第二、第三首歌曲添加简单的模拟旋律(如短音阶),避免空指针且可正常播放。 #### 2. **替换 `run_animation()` 为 OLED 动画** - 原功能:调用 `run_animation()` 播放灯光波浪动画。 - 新需求:使用 OLED 屏幕播放简单动画(例如移动笑脸或滚动条)。 - 实现方式:编写新的 `oled_animation()` 函数,在 OLED 上绘制动态内容,不涉及 WS2812B 灯光。 #### 3. **提醒依赖库** - 若使用复杂 OLED 图形操作,推荐引入 `ssd1306.h` 或图形库提升开发效率。 - 本修改仍使用现有底层绘图函数,无需额外头文件。 #### 4. **保留功能** - 所有菜单逻辑、触摸映射、LED 控制、蜂鸣器音调、状态机均保持不变。 - 只修改音乐数据和动画行为。 --- ### 修改后的完整代码(main.c) ```c #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <stdbool.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_system.h" #include "esp_random.h" #include "esp_rom_sys.h" #include "led_strip.h" #define TAG "TTP229_EXT_FIXED" #define WS2812B_PIN 2 #define NUM_LEDS 12 #define TTP229_SCL 27 #define TTP229_SDO 26 #define BUZZER_PIN 25 #define OLED_SPI_HOST HSPI_HOST #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) #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* ---------- 全局与声明 ---------- */ led_strip_handle_t led_strip; static uint8_t led_brightness[NUM_LEDS]; static uint8_t led_active[NUM_LEDS]; static SemaphoreHandle_t led_mutex = NULL; typedef enum { MODE_NORMAL = 0, MODE_LED_KEY_INTERACT, MODE_LED_GRADIENT_CYCLE, MODE_BLESSING_PLAY, MODE_GAME } app_mode_t; static volatile app_mode_t g_mode = MODE_NORMAL; /* gradient */ static volatile bool gradient_run = false; static int gradient_pos = 0; /* blessing task control */ 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]; static TickType_t game_next_spawn; /* OLED */ static uint8_t oled_buf[OLED_BUF_SIZE]; static spi_device_handle_t spi_oled; /* 菜单状态 */ typedef enum { MENU_NONE, MENU_MUSIC_SELECT, MENU_FUNC_SELECT } menu_state_t; static volatile menu_state_t current_menu = MENU_NONE; static int selected_item = 0; static int brightness_level = 4; // 亮度等级:0~4(4=最大) static int pitch_offset_semitones = 0; // 音高偏移(半音) /* 映射:TP -> LED 索引 */ static const int8_t tp_to_led_idx[16] = { /* TP0 */ 8, /* TP1 */ 9, /* TP2 */ 10, /* TP3 */ 11, /* TP4 */ -1, /* TP5 */ -1, /* TP6 */ -1, /* TP7 */ -1, /* TP8 */ 3, /* TP9 */ 2, /* TP10 */ 1, /* TP11 */ 0, /* TP12 */ 4, /* TP13 */ 5, /* TP14 */ 6, /* TP15 */ 7 }; /* 游戏键映射 */ static const int8_t tp_to_game_region[16] = { /* TP0 */ -1, /* TP1 */ 1, /* TP2 */ -1, /* TP3 */ 0, /* TP4 */ -1, /* TP5 */ -1, /* TP6 */ -1, /* TP7 */ -1, /* TP8 */ -1, /* TP9 */ 5, /* TP10 */ -1, /* TP11 */ 6, /* TP12 */ 4, /* TP13 */ -1, /* TP14 */ 3, /* TP15 */ 2 }; /* 彩虹颜色 */ 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 键基础频率 */ static const int note_freqs[16] = { 262,294,330,349,370,392,415,440,466,494,523,554,587,622,659,698 }; /* TTP 按键重映射 */ static const uint8_t key_map[16] = {3,2,1,0,15,14,13,12,8,9,10,11,4,5,6,7}; /* 小音符位图 (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 }; /* 祝福曲调 */ typedef struct { int freq; int dur; } tone_t; static const tone_t blessing[] = { {494, 600}, {440, 600}, {523, 150}, {440, 150}, {392, 150}, {492, 150}, {523, 150}, {494, 150}, {440, 300}, {440, 150}, {392, 150}, {440, 150}, {587, 150}, {494, 150}, {494, 150}, {523, 150}, {587, 150}, {587, 150}, {659, 150}, {392, 150}, {659, 150}, {587, 150}, {523, 150}, {494, 150}, {392, 300}, {494, 150}, {523, 150}, {587, 150}, {523, 600}, {0, 600}, {0, 600}, {0, 600}, {0, 600}, {0, 600}, {0, 600}, {494, 150}, {523, 150}, {587, 150}, {494, 150}, {587, 150}, {494, 150}, {587, 150}, {659, 150}, {587, 150}, {587, 150}, {587, 150}, {523, 150}, {494, 150}, {440, 150}, {440, 150}, {392, 150}, {494, 150}, {494, 150}, {0, 150}, {392, 150}, {440, 150}, {494, 150}, {523, 150}, {494, 150}, {494, 150}, {392, 150}, {440, 150}, {494, 150}, {494, 150}, {523, 150}, {494, 150}, {494, 150}, {440, 150}, {440, 150}, {494, 150}, {494, 150}, {440, 150}, {392, 150}, {494, 150}, {494, 150}, {0, 150}, {494, 150}, {523, 150}, {587, 150}, {494, 150}, {587, 150}, {494, 150}, {587, 150}, {392, 150}, {440, 150}, {392, 150}, {440, 150}, {392, 150}, {440, 150}, {440, 150}, {494, 150}, {494, 150}, {440, 150}, {392, 150}, {494, 150}, {494, 150}, {0, 150}, {494, 150}, {523, 150}, {587, 150}, {494, 150}, {587, 150}, {494, 150}, {587, 150}, {659, 150}, {587, 150}, {659, 150}, {587, 150}, {659, 150}, {587, 150}, {523, 150}, {523, 150}, {587, 150}, {587, 150}, {523, 150}, {494, 150}, {587, 150}, {587, 150}, {0, 150}, {494, 150}, {523, 150}, {440, 150}, {440, 150}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {392, 150}, {392, 150}, {659, 150}, {740, 150}, {440, 150}, {494, 300}, {392, 150}, {440, 150}, {440, 150}, {440, 150}, {494, 150}, {440, 150}, {440, 150}, {440, 150}, {523, 150}, {440, 150}, {659, 150}, {659, 150}, {740, 150}, {440, 150}, {494, 300}, {392, 150}, {440, 150}, {440, 150}, {494, 150}, {440, 150}, {440, 150}, {440, 150}, {523, 150}, {440, 150}, {740, 150}, {659, 150}, {587, 300}, {392, 150}, {659, 150}, {740, 150}, {392, 150}, {392, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {392, 150}, {392, 150}, {659, 150}, {392, 150}, {659, 150}, {659, 150}, {523, 300}, {494, 300}, {440, 300}, {392, 300}, {440, 300}, {494, 300}, {523, 300}, {494, 300}, {0, 300}, {626, 150}, {626, 150}, {392, 300}, {392, 300}, {392, 300}, {392, 300}, {523, 300}, {392, 300}, {659, 300}, {440, 300}, {494, 300}, {440, 300}, {494, 300}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {0, 300}, {494, 300}, {494, 300}, {392, 150}, {494, 150}, {494, 150}, {659, 150}, {523, 150}, {494, 300}, {659, 300}, {392, 150}, {659, 150}, {587, 150}, {587, 150}, {494, 300}, {494, 150}, {392, 150}, {440, 150}, {494, 150}, {523, 150}, {494, 150}, {440, 150}, {392, 150}, {494, 300}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {0, 300}, {494, 300}, {494, 300}, {392, 150}, {494, 150}, {494, 150}, {659, 150}, {523, 150}, {494, 300}, {659, 300}, {392, 150}, {659, 150}, {587, 150}, {587, 150}, {494, 300}, {523, 300}, {392, 150}, {659, 150}, {587, 150}, {523, 150}, {494, 150}, {440, 150}, {440, 150}, {392, 150}, {440, 150}, {392, 150}, {0, 300}, {0, 300}, {0, 600}, {0, 600}, {0, 600}, {0, 450}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {523, 150}, {494, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {392, 150}, {494, 150} }; static const int blessing_len = sizeof(blessing) / sizeof(blessing[0]); // === 新增:第二、第三首歌曲旋律 === static const tone_t song2[] = { {523, 200}, {587, 200}, {659, 200}, {784, 400}, {659, 200}, {587, 200}, {523, 400}, {0, 200} }; static const int song2_len = sizeof(song2) / sizeof(song2[0]); static const tone_t song3[] = { {392, 150}, {440, 150}, {494, 150}, {523, 150}, {587, 150}, {659, 150}, {740, 150}, {784, 300}, {0, 300} }; static const int song3_len = sizeof(song3) / sizeof(song3[0]); /* 音乐列表 - 已填充真实数据 */ static const char* music_names[3] = {"Blessing", "Song 2", "Song 3"}; static const tone_t* music_tracks[3] = {blessing, song2, song3}; // 修改:不再为 NULL static const int music_lengths[3] = {blessing_len, song2_len, song3_len}; /* 字体数据:6x8 ASCII */ const uint8_t font6x8[95 * 6] = { 0x00,0x00,0x00,0x00,0x00,0x00, // ' ' 0x00,0x03,0x00,0x03,0x00,0x00, // " 0x14,0x3E,0x14,0x3E,0x14,0x00, // # 0x24,0x2A,0x7F,0x2A,0x12,0x00, // $ 0x43,0x33,0x08,0x66,0x61,0x00, // % 0x36,0x49,0x55,0x22,0x50,0x00, // & 0x00,0x05,0x03,0x00,0x00,0x00, // ' 0x00,0x1C,0x22,0x41,0x00,0x00, // ( 0x00,0x41,0x22,0x1C,0x00,0x00, // ) 0x14,0x08,0x3E,0x08,0x14,0x00, // * 0x08,0x08,0x3E,0x08,0x08,0x00, // + 0x00,0x50,0x30,0x00,0x00,0x00, // , 0x08,0x08,0x08,0x08,0x08,0x00, // - 0x00,0x60,0x60,0x00,0x00,0x00, // . 0x20,0x10,0x08,0x04,0x02,0x00, // / 0x3E,0x51,0x49,0x45,0x3E,0x00, // 0 0x00,0x42,0x7F,0x40,0x00,0x00, // 1 0x42,0x61,0x51,0x49,0x46,0x00, // 2 0x21,0x41,0x45,0x4B,0x31,0x00, // 3 0x18,0x14,0x12,0x7F,0x10,0x00, // 4 0x27,0x45,0x45,0x45,0x39,0x00, // 5 0x3C,0x4A,0x49,0x49,0x30,0x00, // 6 0x01,0x71,0x09,0x05,0x03,0x00, // 7 0x36,0x49,0x49,0x49,0x36,0x00, // 8 0x06,0x49,0x49,0x29,0x1E,0x00, // 9 0x00,0x36,0x36,0x00,0x00,0x00, // : 0x00,0x56,0x36,0x00,0x00,0x00, // ; 0x08,0x14,0x22,0x41,0x00,0x00, // < 0x14,0x14,0x14,0x14,0x14,0x00, // = 0x00,0x41,0x22,0x14,0x08,0x00, // > 0x02,0x01,0x51,0x09,0x06,0x00, // ? 0x3E,0x41,0x5D,0x55,0x1E,0x00, // @ 0x7C,0x12,0x11,0x12,0x7C,0x00, // A 0x7F,0x49,0x49,0x49,0x36,0x00, // B 0x3E,0x41,0x41,0x41,0x22,0x00, // C 0x7F,0x41,0x41,0x22,0x1C,0x00, // D 0x7F,0x49,0x49,0x49,0x41,0x00, // E 0x7F,0x09,0x09,0x09,0x01,0x00, // F 0x3E,0x41,0x49,0x49,0x7A,0x00, // G 0x7F,0x08,0x08,0x08,0x7F,0x00, // H 0x00,0x41,0x7F,0x41,0x00,0x00, // I 0x20,0x40,0x41,0x3F,0x01,0x00, // J 0x7F,0x08,0x14,0x22,0x41,0x00, // K 0x7F,0x40,0x40,0x40,0x40,0x00, // L 0x7F,0x02,0x0C,0x02,0x7F,0x00, // M 0x7F,0x04,0x08,0x10,0x7F,0x00, // N 0x3E,0x41,0x41,0x41,0x3E,0x00, // O 0x7F,0x09,0x09,0x09,0x06,0x00, // P 0x3E,0x41,0x51,0x21,0x5E,0x00, // Q 0x7F,0x09,0x19,0x29,0x46,0x00, // R 0x46,0x49,0x49,0x49,0x31,0x00, // S 0x01,0x01,0x7F,0x01,0x01,0x00, // T 0x3F,0x40,0x40,0x40,0x3F,0x00, // U 0x1F,0x20,0x40,0x20,0x1F,0x00, // V 0x3F,0x40,0x38,0x40,0x3F,0x00, // W 0x63,0x14,0x08,0x14,0x63,0x00, // X 0x07,0x08,0x70,0x08,0x07,0x00, // Y 0x61,0x51,0x49,0x45,0x43,0x00, // Z 0x00,0x7F,0x41,0x41,0x00,0x00, // [ 0x02,0x04,0x08,0x10,0x20,0x00, // '\\' 0x00,0x41,0x41,0x7F,0x00,0x00, // ] 0x04,0x02,0x01,0x02,0x04,0x00, // ^ 0x40,0x40,0x40,0x40,0x40,0x00, // _ 0x00,0x01,0x02,0x04,0x00,0x00, // ` 0x20,0x54,0x54,0x54,0x78,0x00, // a 0x7F,0x44,0x44,0x44,0x38,0x00, // b 0x38,0x44,0x44,0x44,0x20,0x00, // c 0x38,0x44,0x44,0x44,0x7F,0x00, // d 0x38,0x54,0x54,0x54,0x18,0x00, // e 0x08,0x7E,0x09,0x01,0x02,0x00, // f 0x18,0xA4,0xA4,0xA4,0x7C,0x00, // g 0x7F,0x08,0x04,0x04,0x78,0x00, // h 0x00,0x44,0x7D,0x40,0x00,0x00, // i 0x40,0x80,0x84,0x7D,0x00,0x00, // j 0x7F,0x10,0x28,0x44,0x00,0x00, // k 0x00,0x41,0x7F,0x40,0x00,0x00, // l 0x7C,0x04,0x18,0x04,0x78,0x00, // m 0x7C,0x08,0x04,0x04,0x78,0x00, // n 0x38,0x44,0x44,0x44,0x38,0x00, // o 0xFC,0x24,0x24,0x24,0x18,0x00, // p 0x18,0x24,0x24,0x18,0xFC,0x00, // q 0x7C,0x08,0x04,0x04,0x08,0x00, // r 0x48,0x54,0x54,0x54,0x20,0x00, // s 0x04,0x04,0x3F,0x44,0x40,0x00, // t 0x3C,0x40,0x40,0x20,0x7C,0x00, // u 0x1C,0x20,0x40,0x20,0x1C,0x00, // v 0x3C,0x40,0x30,0x40,0x3C,0x00, // w 0x44,0x28,0x10,0x28,0x44,0x00, // x 0x1C,0xA0,0xA0,0x7C,0x00,0x00, // y 0x44,0x64,0x54,0x4C,0x44,0x00, // z 0x00,0x08,0x36,0x41,0x00,0x00, // { 0x00,0x00,0x77,0x00,0x00,0x00, // | 0x00,0x41,0x36,0x08,0x00,0x00, // } 0x02,0x01,0x02,0x04,0x02,0x00 // ~ }; /* ---------- 前向声明 ---------- */ 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_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_face_happy(void); static void oled_draw_music_symbol(void); static void led_update_from_state(void); static void led_fade_task(void *arg); static void blessing_task(void *arg); static void start_blessing_task(void); static void stop_blessing_task(void); static void game_spawn_block(void); static void game_draw(void); void oled_animation(void); // 替换 run_animation static void oled_draw_menu(const char* title, const char* items[], int item_count); /* --- 新增:OLED 动画函数 --- */ void oled_animation(void) { int x = 0; for (int frame = 0; frame < 100 && xTaskGetTickCount() > 0; frame++) { memset(oled_buf, 0, OLED_BUF_SIZE); // 绘制移动的笑脸眼睛 int lx = x, rx = x + 20; if (lx >= OLED_WIDTH - 10) break; for (int yy = 20; yy <= 22; yy++) { for (int xx = lx + 30; xx <= lx + 40; xx++) { if (yy == 21 || yy == 22) { int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); } } } for (int yy = 20; yy <= 22; yy++) { for (int xx = rx + 85; xx <= rx + 95; xx++) { if (yy == 21 || yy == 22) { int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); } } } // 微笑弧线 for (int xx = 46; xx < 82; xx++) { int yy = 42 + (int)(4.0f * sinf(((xx - 46) / 36.0f) * M_PI)); if (yy < 0 || yy >= OLED_HEIGHT) continue; int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); if (yy + 1 < OLED_HEIGHT) { int page2 = (yy + 1) / 8; oled_buf[page2 * OLED_WIDTH + xx] |= (1 << ((yy + 1) & 7)); } } oled_refresh(); x += 2; vTaskDelay(pdMS_TO_TICKS(80)); uint16_t k = ttp229_read_keys(); if (k & ((1<<4)|(1<<5)|(1<<6)|(1<<7))) break; } oled_clear(); oled_draw_face_happy(); } /* ========== 原有函数保持不变 ========== */ 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)); if (!led_mutex) led_mutex = xSemaphoreCreateMutex(); for (int i=0;i<NUM_LEDS;i++){ led_brightness[i]=255; led_active[i]=0; led_strip_set_pixel(led_strip, i, rainbow_colors[i][0], rainbow_colors[i][1], rainbow_colors[i][2]); } led_strip_refresh(led_strip); } 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); } static void oled_send_cmd(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, 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_refresh(void) { 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(); } static void oled_draw_bitmap(int x, int y, int w, int h, const uint8_t *bm) { 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)); } } } } static void oled_draw_face_happy(void) { memset(oled_buf, 0, OLED_BUF_SIZE); for (int yy = 20; yy <= 22; yy++) { for (int xx = 30; xx <= 40; xx++) { if (yy == 21 || yy == 22) { int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); } } } for (int yy = 20; yy <= 22; yy++) { for (int xx = 85; xx <= 95; xx++) { if (yy == 21 || yy == 22) { int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); } } } for (int xx = 46; xx < 82; xx++) { int yy = 42 + (int)(4.0f * sinf(((xx - 46) / 36.0f) * M_PI)); if (yy < 0 || yy >= OLED_HEIGHT) continue; int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); if (yy + 1 < OLED_HEIGHT) { int page2 = (yy + 1) / 8; oled_buf[page2 * OLED_WIDTH + xx] |= (1 << ((yy + 1) & 7)); } } oled_refresh(); } static void oled_draw_music_symbol(void) { memset(oled_buf,0,OLED_BUF_SIZE); oled_draw_bitmap(56, 20, 12, 12, bm_music); oled_refresh(); } static void oled_draw_menu(const char* title, const char* items[], int item_count) { memset(oled_buf, 0, OLED_BUF_SIZE); int len = strlen(title) * 6; int x = (OLED_WIDTH - len) / 2; int y = 0; for (int i = 0; title[i]; i++) { uint8_t ch = title[i] - ' '; const uint8_t* font = &font6x8[ch * 6]; for (int col = 0; col < 6; col++) { if (col >= 6) break; uint8_t b = font[col]; for (int bit = 0; bit < 8; bit++) { if (b & (1 << bit)) { int py = y + bit, px = x + i * 6 + col; if (px < OLED_WIDTH && py < OLED_HEIGHT) { oled_buf[(py / 8) * OLED_WIDTH + px] |= (1 << (py & 7)); } } } } } y = 16; for (int i = 0; i < item_count; i++) { if (i == selected_item) { oled_buf[(y / 8) * OLED_WIDTH + 2] |= (1 << (y & 7)); } len = strlen(items[i]); for (int j = 0; j < len; j++) { int px = 10 + j * 6, py = y; uint8_t ch = items[i][j] - ' '; const uint8_t* font = &font6x8[ch * 6]; for (int col = 0; col < 6; col++) { uint8_t b = font[col]; for (int bit = 0; bit < 8; bit++) { if (b & (1 << bit)) { int ppy = py + bit; if (px + col < OLED_WIDTH && ppy < OLED_HEIGHT) { oled_buf[(ppy / 8) * OLED_WIDTH + px + col] |= (1 << (ppy & 7)); } } } } } y += 10; } oled_refresh(); } static void set_led_rgb(int idx, uint8_t r, uint8_t g, uint8_t b, uint8_t brightness) { uint8_t r2 = (r * brightness)/255; uint8_t g2 = (g * brightness)/255; uint8_t b2 = (b * brightness)/255; led_strip_set_pixel(led_strip, idx, r2, g2, b2); } static void led_update_from_state(void) { if (!led_mutex) return; 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); } 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) { int nb = (int)led_brightness[i] - 8; if (nb < 0) nb = 0; led_brightness[i] = (uint8_t)nb; changed = true; } } } else if (g_mode == MODE_LED_GRADIENT_CYCLE && gradient_run) { gradient_pos++; for (int i=0;i<NUM_LEDS;i++){ int pos = (i + gradient_pos) % NUM_LEDS; float v = (1.0f + sinf((pos*2.0f*M_PI)/NUM_LEDS)) * 0.5f; int b = 80 + (int)(175.0f * v); if (b < 0) b = 0; if (b>255) b = 255; led_brightness[i] = (uint8_t)b; } changed = true; } else { 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)); } } static void blessing_task(void *arg) { ESP_LOGI(TAG, "Blessing task started"); const tone_t* track = music_tracks[selected_item]; int len = music_lengths[selected_item]; while (blessing_running) { for (int i=0;i<len && blessing_running;i++){ int f = track[i].freq; int d = track[i].dur; if (f > 0) buzzer_play_tone(f); else buzzer_play_tone(0); int led_idx = i % NUM_LEDS; xSemaphoreTake(led_mutex, portMAX_DELAY); led_brightness[led_idx] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); int chunks = d / 20; if (chunks < 1) chunks = 1; for (int c=0;c<chunks && blessing_running;c++){ vTaskDelay(pdMS_TO_TICKS(d / chunks)); } buzzer_play_tone(0); vTaskDelay(pdMS_TO_TICKS(40)); } } buzzer_play_tone(0); ESP_LOGI(TAG, "Blessing task exit"); blessing_task_handle = NULL; vTaskDelete(NULL); } static void start_blessing_task(void) { if (blessing_running) return; blessing_running = true; BaseType_t r = xTaskCreate(blessing_task, "blessing", 4096, NULL, 5, &blessing_task_handle); if (r != pdPASS) { ESP_LOGW(TAG,"Create blessing task failed"); blessing_running = false; blessing_task_handle = NULL; } } static void stop_blessing_task(void) { if (!blessing_running) return; blessing_running = false; int t = 0; while (blessing_task_handle != NULL && t < 50) { vTaskDelay(pdMS_TO_TICKS(50)); t++; } buzzer_play_tone(0); } static void game_spawn_block(void) { 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) { memset(oled_buf,0,OLED_BUF_SIZE); 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]) { 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 { 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(); } void app_main(void) { ESP_LOGI(TAG,"App start"); ws2812b_init(); ttp229_init(); buzzer_init(); gpio_config_t io_conf = {0}; 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_config_t buscfg = { .miso_io_num = -1, .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)); gpio_set_level(OLED_RST_PIN, 0); vTaskDelay(pdMS_TO_TICKS(10)); gpio_set_level(OLED_RST_PIN, 1); vTaskDelay(pdMS_TO_TICKS(10)); const uint8_t init_cmds[] = { 0xAE,0xD5,0x80,0xA8,0x3F,0xD3,0x00,0x40,0x8D,0x14,0x20,0x00,0xA1,0xC8,0xDA,0x12,0x81,0xCF,0xD9,0xF1,0xDB,0x40,0xA4,0xA6,0xAF }; oled_send_cmd(init_cmds, sizeof(init_cmds)); oled_clear(); xTaskCreate(led_fade_task, "led_fade", 4096, NULL, 5, NULL); uint16_t prev_keys = 0; ESP_LOGI(TAG,"Main loop start"); while (1) { uint16_t keys = ttp229_read_keys(); uint16_t newly = (keys & ~prev_keys); uint16_t releases = (~keys) & prev_keys; bool tp4_pressed = keys & (1 << 4); bool tp5_pressed = keys & (1 << 5); // === 功能键单独处理 === if (newly & (1 << 4)) { if (current_menu == MENU_NONE && !(tp5_pressed)) { if (g_mode == MODE_LED_KEY_INTERACT) { g_mode = MODE_NORMAL; oled_clear(); oled_draw_face_happy(); } else { g_mode = MODE_LED_KEY_INTERACT; oled_clear(); oled_draw_face_happy(); } vTaskDelay(pdMS_TO_TICKS(200)); } } if (newly & (1 << 5)) { if (current_menu == MENU_NONE && !(tp4_pressed)) { if (g_mode == MODE_LED_GRADIENT_CYCLE) { g_mode = MODE_NORMAL; gradient_run = false; oled_clear(); oled_draw_face_happy(); } else { g_mode = MODE_LED_GRADIENT_CYCLE; gradient_run = true; oled_clear(); oled_draw_face_happy(); } vTaskDelay(pdMS_TO_TICKS(200)); } } if (newly & (1 << 6)) { if (current_menu == MENU_NONE) { current_menu = MENU_MUSIC_SELECT; selected_item = 0; g_mode = MODE_NORMAL; stop_blessing_task(); oled_draw_menu("Music", music_names, 3); } else if (current_menu == MENU_MUSIC_SELECT) { current_menu = MENU_NONE; oled_clear(); oled_draw_face_happy(); } vTaskDelay(pdMS_TO_TICKS(200)); } if (newly & (1 << 7)) { if (current_menu == MENU_NONE) { current_menu = MENU_FUNC_SELECT; selected_item = 0; static const char* func_items[] = {"Game", "Animation"}; oled_draw_menu("Func", func_items, 2); } else if (current_menu == MENU_FUNC_SELECT) { current_menu = MENU_NONE; oled_clear(); oled_draw_face_happy(); } vTaskDelay(pdMS_TO_TICKS(200)); } // === 菜单内导航 === if (current_menu == MENU_MUSIC_SELECT) { if (newly & (1 << 5)) { selected_item = (selected_item - 1 + 3) % 3; oled_draw_menu("Music", music_names, 3); } else if (newly & (1 << 7)) { selected_item = (selected_item + 1) % 3; oled_draw_menu("Music", music_names, 3); } else if (newly & (1 << 4)) { if (music_tracks[selected_item]) { stop_blessing_task(); start_blessing_task(); g_mode = MODE_BLESSING_PLAY; oled_clear(); oled_draw_music_symbol(); } current_menu = MENU_NONE; } vTaskDelay(pdMS_TO_TICKS(150)); prev_keys = keys; continue; } if (current_menu == MENU_FUNC_SELECT) { if (newly & (1 << 4)) { selected_item = (selected_item - 1 + 2) % 2; static const char* items[] = {"Game", "Animation"}; oled_draw_menu("Func", items, 2); } else if (newly & (1 << 6)) { selected_item = (selected_item + 1) % 2; static const char* items[] = {"Game", "Animation"}; oled_draw_menu("Func", items, 2); } else if (newly & (1 << 5)) { if (selected_item == 0) { g_mode = MODE_GAME; game_running = true; memset(game_regions, 0, sizeof(game_regions)); game_next_spawn = xTaskGetTickCount() + pdMS_TO_TICKS(800); oled_clear(); game_draw(); } else { g_mode = MODE_NORMAL; oled_animation(); // 改为 OLED 动画 } current_menu = MENU_NONE; } vTaskDelay(pdMS_TO_TICKS(150)); prev_keys = keys; continue; } // === 游戏模式:可随时按 TP7 退出 === 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(); } if (newly & (1 << 7)) { g_mode = MODE_NORMAL; game_running = false; oled_clear(); oled_draw_face_happy(); } } // === 处理琴键输入 === if (keys) { bool played_any = false; for (int i=0;i<16;i++){ uint8_t tp = key_map[i]; if (keys & (1 << tp)) { if (tp>=4 && tp<=7) continue; if (g_mode == MODE_GAME) { int region = tp_to_game_region[tp]; if (region >= 0 && region < 7) { if (game_regions[region]) { game_regions[region] = 0; buzzer_play_tone(880); vTaskDelay(pdMS_TO_TICKS(80)); buzzer_play_tone(0); game_draw(); } else { buzzer_play_tone(220); vTaskDelay(pdMS_TO_TICKS(60)); buzzer_play_tone(0); } } } else { float ratio = powf(2.0f, pitch_offset_semitones / 12.0f); int adjusted_freq = (int)(note_freqs[i] * ratio); if (g_mode != MODE_BLESSING_PLAY) { buzzer_play_tone(adjusted_freq); played_any = true; } int led_idx = tp_to_led_idx[tp]; if (g_mode == MODE_LED_KEY_INTERACT && led_idx >= 0 && led_idx < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_active[led_idx] = 1; led_brightness[led_idx] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); } if (g_mode == MODE_LED_GRADIENT_CYCLE && led_idx >= 0 && led_idx < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_brightness[led_idx] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); } if (g_mode == MODE_NORMAL) { memset(oled_buf,0,OLED_BUF_SIZE); int bars = NUM_LEDS; int w = OLED_WIDTH / bars; for (int bi=0;bi<bars;bi++){ int x = bi*w + 1; int base_y = OLED_HEIGHT - 2; int bar_h = (bi == i) ? (OLED_HEIGHT/2) : (OLED_HEIGHT/6); 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(); } } } } vTaskDelay(pdMS_TO_TICKS(40)); } else { if (g_mode != MODE_BLESSING_PLAY) buzzer_play_tone(0); } if (releases) { for (int i=0;i<16;i++){ uint8_t tp = key_map[i]; if (releases & (1<<tp)) { int led_idx = tp_to_led_idx[tp]; if (g_mode == MODE_LED_KEY_INTERACT && led_idx >=0 && led_idx < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_active[led_idx] = 0; xSemaphoreGive(led_mutex); } } } } if (newly & (1 << 5)) { if (current_menu == MENU_NONE && g_mode == MODE_NORMAL) { oled_clear(); oled_draw_face_happy(); } } prev_keys = keys; vTaskDelay(pdMS_TO_TICKS(20)); } } ``` --- ### 知识点 1. **SPI 驱动 OLED 显示原理** 通过 SPI 发送命令与数据,控制 SSD1306 屏幕刷新帧缓冲区。 2. **蜂鸣器 PWM 音调生成** 使用 LEDC 模块调节频率与占空比输出不同音符。 3. **有限状态机设计模式** 利用枚举与全局变量管理多模式切换,确保逻辑清晰无冲突。 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值