#include #include #include #include #include #include #include #include // --- ピン定義 --- #define TFT_DC 9 #define TFT_CS 10 #define TFT_RST 8 #define TOUCH_CS 7 // --- グローバルリソース --- ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST); XPT2046_Touchscreen ts(TOUCH_CS); SemaphoreHandle_t xSPIMutex; QueueHandle_t xTouchQueue; // キャンバス設定 (320x240) #define CANVAS_WIDTH 320 #define CANVAS_HEIGHT 240 // DMAMEMを使用してTeensy 4.1の外部RAMに配置 #define CANVAS_BUF_SIZE (CANVAS_WIDTH * CANVAS_HEIGHT * 2) DMAMEM static uint8_t canvas_buf[CANVAS_BUF_SIZE]; lv_obj_t * canvas; lv_obj_t * btn; lv_obj_t * label; int current_hue_start = 0; // LVGL描画用中間バッファ #define DRAW_BUF_SIZE (320 * 20) static uint16_t draw_buf[DRAW_BUF_SIZE]; // --- グラデーション描画関数 --- void draw_background_gradient(int hue_offset) { for (int y = 0; y < CANVAS_HEIGHT; y++) { // USB保護のための休憩 if (y % 10 == 0) vTaskDelay(pdMS_TO_TICKS(1)); for (int x = 0; x < CANVAS_WIDTH; x++) { int h = (hue_offset + (x * 360 / CANVAS_WIDTH)) % 360; int v = 100 - (y * 100 / CANVAS_HEIGHT); // 安全なピクセル書き込み関数を使用 lv_color_t color = lv_color_hsv_to_rgb(h, 100, v); lv_canvas_set_px(canvas, x, y, color, LV_OPA_COVER); } } lv_obj_invalidate(canvas); } void my_disp_flush(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map) { if (xSemaphoreTake(xSPIMutex, portMAX_DELAY) == pdTRUE) { tft.writeRect(area->x1, area->y1, lv_area_get_width(area), lv_area_get_height(area), (uint16_t *)px_map); xSemaphoreGive(xSPIMutex); } lv_display_flush_ready(disp); } void my_touch_read(lv_indev_t * indev, lv_indev_data_t * data) { if (ts.touched()) { TS_Point p = ts.getPoint(); data->point.x = map(p.x, 200, 3800, 0, 320); data->point.y = map(p.y, 200, 3800, 0, 240); data->state = LV_INDEV_STATE_PRESSED; } else { data->state = LV_INDEV_STATE_RELEASED; } } static void btn_event_cb(lv_event_t * e) { if(lv_event_get_code(e) == LV_EVENT_CLICKED) { uint8_t dummy = 1; xQueueSend(xTouchQueue, &dummy, 0); } } void TaskGUI(void *pvParameters) { tft.begin(); tft.setRotation(3); ts.begin(); ts.setRotation(1); lv_init(); lv_tick_set_cb((uint32_t (*)(void))millis); lv_display_t * disp = lv_display_create(320, 240); lv_display_set_flush_cb(disp, my_disp_flush); lv_display_set_buffers(disp, (void*)draw_buf, NULL, sizeof(draw_buf), LV_DISPLAY_RENDER_MODE_PARTIAL); lv_indev_t * indev = lv_indev_create(); lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(indev, my_touch_read); lv_obj_t * scr = lv_screen_active(); // キャンバス作成とバッファ紐付け canvas = lv_canvas_create(scr); // 第5引数はフォーマットを明示的に指定 lv_canvas_set_buffer(canvas, canvas_buf, CANVAS_WIDTH, CANVAS_HEIGHT, LV_COLOR_FORMAT_RGB565); // 初回描画 draw_background_gradient(0); lv_obj_align(canvas, LV_ALIGN_CENTER, 0, 0); // ボタン設定 btn = lv_button_create(scr); lv_obj_set_size(btn, 240, 60); lv_obj_center(btn); lv_obj_set_style_bg_color(btn, lv_color_black(), 0); lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0); lv_obj_set_style_border_color(btn, lv_color_white(), 0); lv_obj_set_style_border_width(btn, 2, 0); lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); label = lv_label_create(btn); lv_label_set_text(label, "Hello Teensy4.1!!"); lv_obj_set_style_text_color(label, lv_color_white(), 0); lv_obj_center(label); uint8_t received_event; uint32_t last_sync_time = millis(); for (;;) { lv_timer_handler(); uint32_t now = millis(); if (now - last_sync_time >= 1000) { if (xQueueReceive(xTouchQueue, &received_event, 0) == pdTRUE) { current_hue_start = (current_hue_start + 36) % 360; draw_background_gradient(current_hue_start); } last_sync_time = now; } vTaskDelay(pdMS_TO_TICKS(5)); } } void setup() { Serial.begin(115200); xTouchQueue = xQueueCreate(10, sizeof(uint8_t)); xSPIMutex = xSemaphoreCreateMutex(); if (xTouchQueue != NULL) { xTaskCreate(TaskGUI, "GUI_TASK", 12288, NULL, 3, NULL); } vTaskStartScheduler(); } void loop() {}