/* test3_main.c - 修正版
修复内容:
- TP->LED 映射按用户要求调整
- 修复 TP6 祝福播放阻塞/持续发声问题
- OLED 表情改为 (* ̄︶ ̄),音乐符号为 ♪
- 小游戏键映射调整为 TP3, TP1, TP15, TP14, TP12, TP9, TP11
- 清理未使用的数据,避免编译警告
*/
#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"
/* ---------- Hardware pins (如需修改请在这里改) ---------- */
#define WS2812B_PIN 2
#define NUM_LEDS 12
#define TTP229_SCL 27
#define TTP229_SDO 26
#define BUZZER_PIN 25
/* OLED SPI pins - 请按你实际接线调整 */
#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;
/* 用于映射:TTP 的 TP 编号 -> LED 索引(0..11) */
/* 你要求:tp 3 2 1 0 15 14 13 12 8 9 10 11 分别对应 第12 11 10 9 8 7 6 5 4 3 2 1 */
/* 也就是:TP3 -> LED idx 11, TP2 -> idx10, ..., TP11 -> idx0 */
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
};
/* 小游戏键 -> 七个区域映射(按顺序 region 0..6) */
/* 用户希望:tp3, tp1, tp15, tp14, tp12, tp9, tp11 */
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 原始映射(你的原始 key_map,用于遍历逻辑键) */
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}, // 小节1
{523, 150}, {440, 150}, {392, 150}, {492, 150}, {523, 150}, {494, 150}, {440, 300}, // 小节2: \dot{7}≈升G(492), \widehat{1}=C(523)
{440, 150}, {392, 150}, {440, 150}, {587, 150}, {494, 150}, {494, 150}, {523, 150}, {587, 150}, // 小节3
{587, 150}, {659, 150}, {392, 150}, {659, 150}, {587, 150}, {523, 150}, {494, 150}, {392, 300}, // 小节4
{494, 150}, {523, 150}, {587, 150}, {523, 600}, // 小节5
{0, 600}, {0, 600}, // 小节6
{0, 600}, {0, 600}, // 小节7
{0, 600}, {0, 600}, {494, 150}, {523, 150}, // 小节8
{587, 150}, {494, 150}, {587, 150}, {494, 150}, {587, 150}, {659, 150}, {587, 150}, {587, 150}, {587, 150}, // 小节9
{523, 150}, {494, 150}, {440, 150}, {440, 150}, {392, 150}, {494, 150}, {494, 150}, {0, 150}, {392, 150}, {440, 150}, // 小节10
{494, 150}, {523, 150}, {494, 150}, {494, 150}, {392, 150}, {440, 150}, {494, 150}, {494, 150}, {523, 150}, {494, 150}, {494, 150}, // 小节11
{440, 150}, {440, 150}, {494, 150}, {494, 150}, {440, 150}, {392, 150}, {494, 150}, {494, 150}, {0, 150}, {494, 150}, {523, 150}, // 小节12
{587, 150}, {494, 150}, {587, 150}, {494, 150}, {587, 150}, {392, 150}, {440, 150}, {392, 150}, {440, 150}, {392, 150}, // 小节13
{440, 150}, {440, 150}, {494, 150}, {494, 150}, {440, 150}, {392, 150}, {494, 150}, {494, 150}, {0, 150}, {494, 150}, {523, 150}, // 小节14
{587, 150}, {494, 150}, {587, 150}, {494, 150}, {587, 150}, {659, 150}, {587, 150}, {659, 150}, {587, 150}, {659, 150}, {587, 150}, // 小节15
{523, 150}, {523, 150}, {587, 150}, {587, 150}, {523, 150}, {494, 150}, {587, 150}, {587, 150}, {0, 150}, {494, 150}, {523, 150}, // 小节16
{440, 150}, {440, 150}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {392, 150}, {392, 150}, {659, 150}, {740, 150}, // 小节17: $^{b}7$≈A(440)? 此处按上下文调整;^♯7→740(F#)
{440, 150}, {494, 300}, {392, 150}, {440, 150}, {440, 150}, {440, 150}, {494, 150}, // 小节18
{440, 150}, {440, 150}, {440, 150}, {523, 150}, {440, 150}, {659, 150}, {659, 150}, {740, 150}, // 小节19
{440, 150}, {494, 300}, {392, 150}, {440, 150}, {440, 150}, {494, 150}, // 小节20
{440, 150}, {440, 150}, {440, 150}, {523, 150}, {440, 150}, {740, 150}, {659, 150}, // 小节21
{587, 300}, {392, 150}, {659, 150}, {740, 150}, {392, 150}, {392, 150}, {494, 150}, // 小节22
{392, 150}, {392, 150}, {392, 150}, {392, 150}, {392, 150}, {659, 150}, {392, 150}, {659, 150}, {659, 150}, // 小节23
{523, 300}, {494, 300}, {440, 300}, {392, 300}, {440, 300}, {494, 300}, {523, 300}, // 小节24
{494, 300}, {0, 300}, {626, 150}, {626, 150}, {392, 300}, // 小节25: ^♯2 ≈ B#≈626Hz, 近似C+(523*1.15)
{392, 300}, {392, 300}, {392, 300}, {523, 300}, {392, 300}, {659, 300}, // 小节26
{440, 300}, {494, 300}, {440, 300}, // 小节27
{494, 300}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {0, 300}, {494, 300}, // 小节28
{494, 300}, {392, 150}, {494, 150}, {494, 150}, {659, 150}, {523, 150}, {494, 300}, // 小节29
{659, 300}, {392, 150}, {659, 150}, {587, 150}, {587, 150}, {494, 300}, // 小节30
{494, 150}, {392, 150}, {440, 150}, {494, 150}, {523, 150}, {494, 150}, {440, 150}, {392, 150}, // 小节31
{494, 300}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {0, 300}, {494, 300}, // 小节32
{494, 300}, {392, 150}, {494, 150}, {494, 150}, {659, 150}, {523, 150}, {494, 300}, // 小节33
{659, 300}, {392, 150}, {659, 150}, {587, 150}, {587, 150}, {494, 300}, // 小节34
{523, 300}, {392, 150}, {659, 150}, {587, 150}, {523, 150}, {494, 150}, {440, 150}, // 小节35
{440, 150}, {392, 150}, {440, 150}, {392, 150}, {0, 300}, {0, 300}, // 小节36
{0, 600}, {0, 600}, {0, 600}, {0, 450}, {494, 150}, // 小节37-40前
{494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, // 小节40
{494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {523, 150}, {494, 150}, {494, 150}, // 小节41
{494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, {494, 150}, {392, 150}, {392, 150}, {392, 150}, {494, 150}, // 小节42
{494, 150}, {392, 150}, {392, 150}, {392, 150}, {440, 150}, {392, 150}, {392, 150}, {494, 150} // 小节43
};
static const int blessing_len = sizeof(blessing) / sizeof(blessing[0]);
/* ---------- 前向声明 ---------- */
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);
/* ---------- 实现 ---------- */
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);
}
/* ---------- OLED 简易驱动 ---------- */
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);
// ==== 左眼 ====
// 左眼中心大约 (x:35, y:20)
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();
}
/* ---------- LED 控制 ---------- */
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);
}
/* 背景 LED 线程:处理渐变与淡出 */
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));
}
}
/* ---------- Blessing 播放任务(独立) ---------- */
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);
} else {
buzzer_play_tone(0);
}
// LED 反馈:点亮一个位置(仅 temporary bump)
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;
// 等待任务自己退出(任务会在循环检查到 blessing_running==false 后 vTaskDelete)
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 {
// 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();
}
/* ---------- 主程序 ---------- */
void app_main(void)
{
ESP_LOGI(TAG,"App start");
ws2812b_init();
ttp229_init();
buzzer_init();
// OLED init (SPI bus)
// configure DC/CS/RST gpio
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 init
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));
// Reset 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));
// init seq (typical)
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();
// start led fade task
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(); // active-low -> bit=1 when pressed
uint16_t newly = (keys & ~prev_keys);
uint16_t releases = (~keys) & prev_keys;
// TP4..TP7 功能键 - 注意 TP 编号是芯片输出编号(0..15)
// TP4: 切换 LED-key-interact
if (newly & (1<<4)) {
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(160));
}
// TP5: 切换 gradient
if (newly & (1<<5)) {
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(160));
}
// TP6: 切换 blessing 播放(修正在独立任务中)
if (newly & (1<<6)) {
if (g_mode == MODE_BLESSING_PLAY) {
// 停止
stop_blessing_task();
g_mode = MODE_NORMAL;
oled_clear();
oled_draw_face_happy();
} else {
// 启动
g_mode = MODE_BLESSING_PLAY;
start_blessing_task();
oled_clear();
oled_draw_music_symbol();
}
vTaskDelay(pdMS_TO_TICKS(160));
}
// TP7: 游戏开关
if (newly & (1<<7)) {
if (g_mode == MODE_GAME) {
g_mode = MODE_NORMAL;
game_running = false;
oled_clear();
oled_draw_face_happy();
} else {
g_mode = MODE_GAME;
game_running = true;
memset(game_regions,0,sizeof(game_regions));
game_next_spawn = xTaskGetTickCount() + pdMS_TO_TICKS(800);
oled_clear();
oled_draw_face_happy();
}
vTaskDelay(pdMS_TO_TICKS(160));
}
/* 游戏自动生成 */
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 (keys) {
bool played_any = false;
for (int i=0;i<16;i++){
uint8_t tp = key_map[i];
if (keys & (1 << tp)) {
// skip function keys (4..7) as they are handled above
if (tp>=4 && tp<=7) continue;
// 游戏模式下:按键触发对应区域(使用 tp_to_game_region)
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 {
// 非游戏模式下的常规琴键
// 若处于祝福播放模式,不用主循环来播放音(避免冲突)
if (g_mode != MODE_BLESSING_PLAY) {
int freq = note_freqs[i];
buzzer_play_tone(freq);
played_any = true;
}
// LED-key-interact 模式:按下即 latch 对应 LED(使用 tp_to_led_idx)
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();
}
// gradient 模式下按键会暂时点亮对应 LED
if (g_mode == MODE_LED_GRADIENT_CYCLE) {
if (led_idx >= 0 && led_idx < NUM_LEDS) {
xSemaphoreTake(led_mutex, portMAX_DELAY);
led_brightness[led_idx] = 255;
xSemaphoreGive(led_mutex);
led_update_from_state();
}
}
// 常态下做 oled 可视化(简单条形)
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 && (keys & (1<<tp))) ? (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();
}
}
}
} // end for keys loop
if (!played_any && g_mode != MODE_BLESSING_PLAY) {
// nothing
}
vTaskDelay(pdMS_TO_TICKS(40));
} else {
if (g_mode != MODE_BLESSING_PLAY) buzzer_play_tone(0);
}
/* 处理释放:LED-key-interact 模式下 release 要取消 led_active 来触发淡出 */
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; // 由 led_fade_task 处理淡出
xSemaphoreGive(led_mutex);
}
}
}
}
prev_keys = keys;
vTaskDelay(pdMS_TO_TICKS(20));
}
}
阐释这段代码中是如何使用spi驱动oled显示图案的
最新发布