【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        从最早的8266,到现在的esp32s31,其实本身esp32有很多的型号。这些型号差异不小,使用的时候需要注意下。当然,不仅仅是产品本身性能的差异,有的时候也可能是供货、成本、产品规划的差异,让我们选择不同的esp32芯片。

1、停止生产

        有的时候,替换是因为之前的产品厂家开始不再生产了。这个时候,就要开始慢慢导入新的产品。导入的时候,不光是软件,有的时候硬件、pin脚也要做同步的修改。

2、产品性能提升的需要

        除了停供产生的替换,还有一种情况,就是产品性能的需要。比如之前的产品对cpu要求比较低,但是现在要求相对而言要高一点,这种情况下出于性能的原因,也需要替换一下esp32的类型。

3、新增接口的需要

        接口方面,主要有两种情况。一种是接口的数量不够,这种情况下大概率是原来的接口数量偏少,现在需要接口更多的模块;还有一种情况,就是之前的模块不具备某种接口,比如usb,这种情况也是很普遍的。

4、交叉备份的需要

        芯片厂家生产芯片,一般都是交替生产的,特定类型的芯片不一定每个月都生产。这种情况下,让自己的底板可以支持多种模块,这反而是一种比较稳妥的选择。

5、主要是pin脚的差别

        不同的esp32模块,如果差异不大,一般改起来也不是特别麻烦。比如我们之前写的这篇文章,

https://feixiaoxing.blog.csdn.net/article/details/162178073?spm=1001.2014.3001.5502

        如果换到esp32s3上面,其实替换一下tp的编号就可以了,其他部分都是一样的,

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
 
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_timer.h"
 
#include "lvgl.h"
 
static void lvgl_task(void *arg);
 
// ================= CONFIG =================
#define LCD_W 480
#define LCD_H 320
 
#define PIN_MOSI 13
#define PIN_CLK  14
#define PIN_CS   15
#define PIN_DC   2
#define PIN_RST  4
#define PIN_BL   12
 
#define TP_MOSI  6
#define TP_MISO  7
#define TP_CLK   18
#define TP_CS    5
#define TP_IRQ   8
 
static spi_device_handle_t spi_lcd;
static spi_device_handle_t spi_tp;
 
static const char *TAG = "ILI9488_LVGL";
 
// ======================================================
// GPIO control
// ======================================================
static inline void dc_cmd(void)  { gpio_set_level(PIN_DC, 0); }
static inline void dc_data(void) { gpio_set_level(PIN_DC, 1); }
 
static void lcd_reset(void)
{
    gpio_set_level(PIN_RST, 0);
    vTaskDelay(pdMS_TO_TICKS(100));
    gpio_set_level(PIN_RST, 1);
    vTaskDelay(pdMS_TO_TICKS(150));
}
 
// ======================================================
// SPI CMD / DATA
// ======================================================
static void lcd_cmd(uint8_t cmd)
{
    spi_transaction_t t = {
        .length = 8,
        .tx_buffer = &cmd,
    };
    dc_cmd();
    spi_device_polling_transmit(spi_lcd, &t);
}
 
static void lcd_data(const void *data, int len)
{
    spi_transaction_t t = {
        .length = len * 8,
        .tx_buffer = data,
    };
    dc_data();
    spi_device_polling_transmit(spi_lcd, &t);
}
 
// ======================================================
// ILI9488 INIT (stable version)
// ======================================================
static void ili9488_init(void)
{
    lcd_reset();
 
    lcd_cmd(0x01); // Software reset
    vTaskDelay(pdMS_TO_TICKS(120));
 
    lcd_cmd(0x11); // Sleep out
    vTaskDelay(pdMS_TO_TICKS(120));
 
    // RGB565 mode (important for stability)
    lcd_cmd(0x3A);
    uint8_t pix = 0x66;
    lcd_data(&pix, 1);
 
    // MADCTL (display orientation)
    lcd_cmd(0x36);
    uint8_t mad = 0x28;   // change to 0x28 if upside-down
    lcd_data(&mad, 1);
 
    lcd_cmd(0x29); // Display ON
    vTaskDelay(pdMS_TO_TICKS(50));
 
    ESP_LOGI(TAG, "LCD init OK");
}
 
// ======================================================
// Set drawing window
// ======================================================
static void set_window(int x1,int y1,int x2,int y2)
{
    uint8_t d[4];
 
    lcd_cmd(0x2A);
    d[0]=x1>>8; d[1]=x1;
    d[2]=x2>>8; d[3]=x2;
    lcd_data(d,4);
 
    lcd_cmd(0x2B);
    d[0]=y1>>8; d[1]=y1;
    d[2]=y2>>8; d[3]=y2;
    lcd_data(d,4);
 
    lcd_cmd(0x2C);
}
 
// ======================================================
// Touch read (XPT2046 style)
// ======================================================
static uint16_t tp_read(uint8_t cmd)
{
    uint8_t tx[3] = {cmd,0,0};
    uint8_t rx[3] = {0};
 
    spi_transaction_t t = {
        .length = 24,
        .tx_buffer = tx,
        .rx_buffer = rx,
    };
 
    spi_device_polling_transmit(spi_tp, &t);
 
    return ((rx[1] << 8) | rx[2]) >> 3;
}
 
static void touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
    static bool touched = false;
 
    if (gpio_get_level(TP_IRQ) == 0)
    {
        data->state = LV_INDEV_STATE_PRESSED;
        
        // Read raw touch coordinates
        uint16_t x_raw = tp_read(0xD0);
        uint16_t y_raw = tp_read(0x90);
        
        // Calibrate and convert to screen coordinates
        // Swap X and Y, and invert Y axis for correct orientation
        // Adjust these values based on your touch panel calibration
        int x = (LCD_W * (3900 - y_raw)) / 3900; // very critical, by feixiaoxing
        int y = LCD_H - 1 - (LCD_H * (x_raw - 100)) / 3900;
        
        // Clamp values
        if (x < 0) x = 0;
        if (x >= LCD_W) x = LCD_W - 1;
        if (y < 0) y = 0;
        if (y >= LCD_H) y = LCD_H - 1;
        
        data->point.x = x;
        data->point.y = y;
        
        if (!touched) {
            ESP_LOGI(TAG, "Touch: x=%d, y=%d (raw: x=%d, y=%d)", 
                     data->point.x, data->point.y, x_raw, y_raw);
            touched = true;
        }
    }
    else
    {
        data->state = LV_INDEV_STATE_RELEASED;
        touched = false;
    }
}
 
// ======================================================
// LVGL flush (stable version)
// ======================================================
static uint8_t line_buf[480 * 3];
 
static void lcd_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
    int w = area->x2 - area->x1 + 1;
    int h = area->y2 - area->y1 + 1;
 
    set_window(area->x1, area->y1, area->x2, area->y2);
 
    dc_data();
 
    spi_transaction_t t = {
        .length = w * 3 * 8,
        .tx_buffer = line_buf,
    };
 
    for (int y = 0; y < h; y++)
    {
        for (int x = 0; x < w; x++)
        {
            uint16_t c = color_p[y * w + x].full;
 
            line_buf[x*3+0] = ((c >> 11) & 0x1F) << 3;
            line_buf[x*3+1] = ((c >> 5)  & 0x3F) << 2;
            line_buf[x*3+2] = (c & 0x1F) << 3;
        }
 
        spi_device_polling_transmit(spi_lcd, &t);
    }
 
    lv_disp_flush_ready(disp);
}
 
// Lottery Program
// ======================================================
static lv_obj_t *label_number;  // Display the random number
static lv_obj_t *btn_start;     // Start button
static lv_obj_t *btn_stop;      // Stop button
static bool is_running = false; // Lottery running flag
static int current_number = 0;  // Current random number
 
// Lottery update task
static void lottery_task(void *arg)
{
    char buf[16];
    while (1) {
        if (is_running) {
            // Generate random number between 1-999
            current_number = (rand() % 1000);
            sprintf(buf, "%03d", current_number);
            lv_label_set_text(label_number, buf);
        }
        vTaskDelay(pdMS_TO_TICKS(50)); // Update every 50ms
    }
}
 
// Start button callback
static void btn_start_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED) {
        if (!is_running) {
            is_running = true;
            ESP_LOGI(TAG, "Lottery started");
        }
    }
}
 
// Stop button callback
static void btn_stop_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED) {
        if (is_running) {
            is_running = false;
            ESP_LOGI(TAG, "Lottery stopped: %03d", current_number);
        }
    }
}
 
static void ui_create(void)
{
    // Initialize random seed
    srand(esp_timer_get_time());
 
    // Create title label
    lv_obj_t *label_title = lv_label_create(lv_scr_act());
    lv_label_set_text(label_title, "LOTTERY");
    lv_obj_align(label_title, LV_ALIGN_CENTER, 0, -80);
    lv_obj_set_style_text_font(label_title, &lv_font_montserrat_14, LV_PART_MAIN);
 
    // Create number display label
    label_number = lv_label_create(lv_scr_act());
    lv_label_set_text(label_number, "0");
    lv_obj_align(label_number, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_style_text_font(label_number, &lv_font_montserrat_32, LV_PART_MAIN);
    lv_obj_set_style_text_color(label_number, lv_color_hex(0xFF0000), LV_PART_MAIN);
 
    // Create start button
    btn_start = lv_btn_create(lv_scr_act());
    lv_obj_set_size(btn_start, 120, 50);
    lv_obj_align(btn_start, LV_ALIGN_CENTER, -80, 70);
    lv_obj_add_event_cb(btn_start, btn_start_event_cb, LV_EVENT_ALL, NULL);
 
    lv_obj_t *label_start = lv_label_create(btn_start);
    lv_label_set_text(label_start, "Start");
    lv_obj_center(label_start);
 
    // Create stop button
    btn_stop = lv_btn_create(lv_scr_act());
    lv_obj_set_size(btn_stop, 120, 50);
    lv_obj_align(btn_stop, LV_ALIGN_CENTER, 80, 70);
    lv_obj_add_event_cb(btn_stop, btn_stop_event_cb, LV_EVENT_ALL, NULL);
 
    lv_obj_t *label_stop = lv_label_create(btn_stop);
    lv_label_set_text(label_stop, "Stop");
    lv_obj_center(label_stop);
 
    // Create lottery task
    xTaskCreate(lottery_task, "lottery", 2048, NULL, 5, NULL);
}
 
// ================= LVGL Tick Timer =================
static void lv_tick_cb(void *arg)
{
    lv_tick_inc(1); // LVGL 1ms tick
}
 
// ======================================================
// Backlight control
// ======================================================
static void backlight_init(void)
{
    gpio_config_t io = {
        .pin_bit_mask = 1ULL << PIN_BL,
        .mode = GPIO_MODE_OUTPUT
    };
    gpio_config(&io);
    gpio_set_level(PIN_BL, 1);
}
 
// ======================================================
// MAIN ENTRY
// ======================================================
void app_main(void)
{
    gpio_set_direction(PIN_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_RST, GPIO_MODE_OUTPUT);
 
    // ================= LCD SPI =================
    spi_bus_config_t bus_lcd = {
        .mosi_io_num = PIN_MOSI,
        .miso_io_num = -1,
        .sclk_io_num = PIN_CLK,
        .max_transfer_sz = 1024 * 10
    };
 
    spi_bus_initialize(SPI2_HOST, &bus_lcd, SPI_DMA_CH_AUTO);
 
    spi_device_interface_config_t dev_lcd = {
        .clock_speed_hz = 20 * 1000 * 1000, // 20MHz
        .mode = 0,
        .spics_io_num = PIN_CS,
        .queue_size = 7,
    };
 
    spi_bus_add_device(SPI2_HOST, &dev_lcd, &spi_lcd);
 
    // ================= TOUCH SPI =================
    spi_bus_config_t bus_tp = {
        .mosi_io_num = TP_MOSI,
        .miso_io_num = TP_MISO,
        .sclk_io_num = TP_CLK,
        .max_transfer_sz = 32
    };
 
    spi_bus_initialize(SPI3_HOST, &bus_tp, SPI_DMA_CH_AUTO);
 
    spi_device_interface_config_t dev_tp = {
        .clock_speed_hz = 2 * 1000 * 1000,
        .mode = 0,
        .spics_io_num = TP_CS,
        .queue_size = 3,
    };
 
    spi_bus_add_device(SPI3_HOST, &dev_tp, &spi_tp);
 
    gpio_set_direction(TP_IRQ, GPIO_MODE_INPUT);
 
    // ================= LCD INIT =================
    ili9488_init();
 
    // ================= LVGL INIT =================
    lv_init();
 
    static lv_color_t *buf1;
    buf1 = heap_caps_malloc(LCD_W * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA);
 
    static lv_disp_draw_buf_t draw_buf;
    lv_disp_draw_buf_init(&draw_buf, buf1, NULL, LCD_W * 20);
 
    // LVGL tick timer (1ms)
    esp_timer_handle_t timer;
    const esp_timer_create_args_t tick_args = {
        .callback = &lv_tick_cb,
        .name = "lv_tick"
    };
    esp_timer_create(&tick_args, &timer);
    esp_timer_start_periodic(timer, 1000); // 1ms
 
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = LCD_W;
    disp_drv.ver_res = LCD_H;
    disp_drv.flush_cb = lcd_flush;
    disp_drv.draw_buf = &draw_buf;
    disp_drv.full_refresh = 1;
 
    lv_disp_drv_register(&disp_drv);
 
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touch_read;
    lv_indev_drv_register(&indev_drv);
 
    ui_create(); // Create LVGL UI
 
    // Create LVGL task handler in separate task
    xTaskCreate(lvgl_task, "lvgl_task", 4096, NULL, 5, NULL);
 
    // no flush operation here again
    backlight_init(); // Initialize backlight here, by feixiaoxing
 
    // Keep main task alive
    while (1)
    {
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}
 
// ================= LVGL Task =================
static void lvgl_task(void *arg)
{
    while (1)
    {
        lv_timer_handler(); // LVGL main loop
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

注:

        同样之前2.8寸的电容触摸屏,可以这样修改为支持esp32s3,也是线序的调节。这样避免重新写代码了。

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_random.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "lvgl.h"

//================================================
// FT6336G CONFIG
//================================================
#define FT6336G_I2C_ADDR     0x38
#define FT6336G_I2C_PORT     I2C_NUM_0
#define FT6336G_SDA_PIN      8
#define FT6336G_SCL_PIN      9
#define FT6336G_INT_PIN      10
#define FT6336G_RST_PIN      18

#define FT6336G_DEV_MODE     0x00
#define FT6336G_NUM_TOUCH    0x02
#define FT6336G_TOUCH1_XH    0x03
#define FT6336G_TOUCH1_YH    0x05
#define FT6336G_TOUCH1_XL    0x04
#define FT6336G_TOUCH1_YL    0x06

//================================================
// ILI9341 CONFIG
//================================================
#define ILI9341_SPI_HOST    SPI2_HOST
#define ILI9341_SCLK_PIN    14
#define ILI9341_MOSI_PIN    13
#define ILI9341_CS_PIN      15
#define ILI9341_DC_PIN      2
#define ILI9341_RST_PIN     4
#define ILI9341_BL_PIN      48

#define ILI9341_WIDTH       320
#define ILI9341_HEIGHT      240

#define ILI9341_SWRESET     0x01
#define ILI9341_SLPOUT      0x11
#define ILI9341_NORON       0x13
#define ILI9341_INVOFF      0x20
#define ILI9341_INVON       0x21
#define ILI9341_DISPON      0x29
#define ILI9341_CASET       0x2A
#define ILI9341_RASET       0x2B
#define ILI9341_RAMWR       0x2C
#define ILI9341_COLMOD      0x3A
#define ILI9341_MADCTL      0x36

static spi_device_handle_t ili9341_spi;
static SemaphoreHandle_t lvgl_mutex;

// UI objects
static lv_obj_t *label_time;

// Timer state
static volatile bool timer_running = false;
static volatile uint32_t timer_seconds = 0;
static volatile uint32_t timer_minutes = 0;
static volatile uint32_t timer_hours = 0;
static volatile bool timer_need_update = false;  // 标记需要更新显示

// Touch state
static volatile bool touch_pressed = false;
static volatile int16_t touch_x = -1;
static volatile int16_t touch_y = -1;
static SemaphoreHandle_t touch_mutex;

//================================================
// FT6336G I2C Functions
//================================================
static esp_err_t ft6336g_i2c_write(uint8_t reg, uint8_t data)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, FT6336G_I2C_ADDR << 1, I2C_MASTER_ACK);
    i2c_master_write_byte(cmd, reg, I2C_MASTER_ACK);
    i2c_master_write_byte(cmd, data, I2C_MASTER_ACK);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(FT6336G_I2C_PORT, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);
    return ret;
}

static esp_err_t ft6336g_i2c_read(uint8_t reg, uint8_t *data, size_t len)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, FT6336G_I2C_ADDR << 1, I2C_MASTER_ACK);
    i2c_master_write_byte(cmd, reg, I2C_MASTER_ACK);
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (FT6336G_I2C_ADDR << 1) | 0x01, I2C_MASTER_ACK);

    if (len > 1) {
        i2c_master_read(cmd, data, len - 1, I2C_MASTER_ACK);
    }
    i2c_master_read_byte(cmd, data + len - 1, I2C_MASTER_NACK);

    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(FT6336G_I2C_PORT, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);
    return ret;
}

//================================================
// FT6336G INIT
//================================================
static void ft6336g_init(void)
{
    printf("Initializing FT6336G...\n");

    gpio_config_t rst_io = {
        .pin_bit_mask = 1ULL << FT6336G_RST_PIN,
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
    };
    gpio_config(&rst_io);

    gpio_set_level(FT6336G_RST_PIN, 0);
    vTaskDelay(pdMS_TO_TICKS(10));
    gpio_set_level(FT6336G_RST_PIN, 1);
    vTaskDelay(pdMS_TO_TICKS(50));

    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = FT6336G_SDA_PIN,
        .scl_io_num = FT6336G_SCL_PIN,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = 100000,
    };

    esp_err_t ret = i2c_param_config(FT6336G_I2C_PORT, &conf);
    if (ret != ESP_OK) {
        printf("I2C param config failed: %d\n", ret);
        return;
    }

    ret = i2c_driver_install(FT6336G_I2C_PORT, conf.mode, 0, 0, 0);
    if (ret != ESP_OK) {
        printf("I2C driver install failed: %d\n", ret);
        return;
    }

    uint8_t data;
    ret = ft6336g_i2c_read(FT6336G_NUM_TOUCH, &data, 1);
    if (ret == ESP_OK) {
        printf("FT6336G detected successfully!\n");
        ft6336g_i2c_write(FT6336G_DEV_MODE, 0x00);
        printf("FT6336G set to normal mode\n");
    } else {
        printf("FT6336G not detected, error: %d\n", ret);
    }
}

//================================================
// Touch Task
//================================================
static void touch_task(void *arg)
{
    printf("Touch task started\n");
    uint8_t num_touch;
    uint8_t touch_data[4];
    uint16_t x, y;
    int consecutive_fail = 0;

    while (1) {
        esp_err_t ret = ft6336g_i2c_read(FT6336G_NUM_TOUCH, &num_touch, 1);

        if (ret == ESP_OK) {
            consecutive_fail = 0;
            
            if (num_touch > 0 && num_touch <= 5) {
                ret = ft6336g_i2c_read(FT6336G_TOUCH1_XH, touch_data, 4);

                if (ret == ESP_OK) {
                    x = ((touch_data[0] & 0x0F) << 8) | touch_data[1];
                    y = ((touch_data[2] & 0x0F) << 8) | touch_data[3];
                    
                    uint16_t t = x;
                    x = y;
                    y = 240 - t;

                    if (x < ILI9341_WIDTH && y < ILI9341_HEIGHT) {
                        if (xSemaphoreTake(touch_mutex, pdMS_TO_TICKS(10))) {
                            touch_x = (int16_t)x;
                            touch_y = (int16_t)y;
                            touch_pressed = true;
                            xSemaphoreGive(touch_mutex);
                        }
                    } else {
                        if (xSemaphoreTake(touch_mutex, pdMS_TO_TICKS(10))) {
                            touch_pressed = false;
                            xSemaphoreGive(touch_mutex);
                        }
                    }
                }
            } else {
                if (xSemaphoreTake(touch_mutex, pdMS_TO_TICKS(10))) {
                    touch_pressed = false;
                    xSemaphoreGive(touch_mutex);
                }
            }
        } else {
            consecutive_fail++;
            if (consecutive_fail > 10) {
                printf("Touch I2C error: %d\n", ret);
                consecutive_fail = 0;
            }
        }

        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

//================================================
// LVGL touchpad read callback
//================================================
static void touchpad_read_cb(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
    static int16_t last_x = -1;
    static int16_t last_y = -1;
    static bool last_pressed = false;
    
    if (xSemaphoreTake(touch_mutex, pdMS_TO_TICKS(10))) {
        if (touch_pressed) {
            if (touch_x >= 0 && touch_y >= 0) {
                data->point.x = touch_x;
                data->point.y = touch_y;
                last_x = touch_x;
                last_y = touch_y;
            } else {
                data->point.x = last_x;
                data->point.y = last_y;
            }
            data->state = LV_INDEV_STATE_PRESSED;
            last_pressed = true;
        } else {
            data->point.x = last_x;
            data->point.y = last_y;
            data->state = LV_INDEV_STATE_RELEASED;
            last_pressed = false;
        }
        xSemaphoreGive(touch_mutex);
    } else {
        data->point.x = last_x;
        data->point.y = last_y;
        data->state = last_pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
    }
}

//================================================
// SPI LOW LEVEL
//================================================
static void ili9341_send_cmd(uint8_t cmd)
{
    gpio_set_level(ILI9341_DC_PIN, 0);
    spi_transaction_t t = {
        .length = 8,
        .tx_buffer = &cmd
    };
    spi_device_transmit(ili9341_spi, &t);
}

static void ili9341_send_data(const uint8_t *data, size_t len)
{
    gpio_set_level(ILI9341_DC_PIN, 1);
    spi_transaction_t t = {
        .length = len * 8,
        .tx_buffer = data
    };
    spi_device_transmit(ili9341_spi, &t);
}

//================================================
// WINDOW
//================================================
static void ili9341_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
    ili9341_send_cmd(ILI9341_CASET);
    uint8_t ca[] = { x0 >> 8, x0 & 0xFF, x1 >> 8, x1 & 0xFF };
    ili9341_send_data(ca, 4);
    ili9341_send_cmd(ILI9341_RASET);
    uint8_t ra[] = { y0 >> 8, y0 & 0xFF, y1 >> 8, y1 & 0xFF };
    ili9341_send_data(ra, 4);
    ili9341_send_cmd(ILI9341_RAMWR);
}

//================================================
// LCD INIT
//================================================
static void ili9341_init(void)
{
    gpio_set_level(ILI9341_RST_PIN, 0);
    vTaskDelay(pdMS_TO_TICKS(100));
    gpio_set_level(ILI9341_RST_PIN, 1);
    vTaskDelay(pdMS_TO_TICKS(120));
    ili9341_send_cmd(ILI9341_SWRESET);
    vTaskDelay(pdMS_TO_TICKS(120));
    ili9341_send_cmd(ILI9341_SLPOUT);
    vTaskDelay(pdMS_TO_TICKS(120));
    ili9341_send_cmd(ILI9341_COLMOD);
    ili9341_send_data((uint8_t[]){0x55}, 1);
    ili9341_send_cmd(ILI9341_MADCTL);
    ili9341_send_data((uint8_t[]){0x28}, 1);

    ili9341_send_cmd(ILI9341_INVON);

    ili9341_send_cmd(ILI9341_DISPON);
    gpio_set_level(ILI9341_BL_PIN, 1);
}

//================================================
// SPI INIT
//================================================
static void ili9341_spi_init(void)
{
    gpio_config_t io = {
        .pin_bit_mask =
            (1ULL << ILI9341_DC_PIN) |
            (1ULL << ILI9341_RST_PIN) |
            (1ULL << ILI9341_BL_PIN),
        .mode = GPIO_MODE_OUTPUT
    };
    gpio_config(&io);

    spi_bus_config_t buscfg = {
        .mosi_io_num = ILI9341_MOSI_PIN,
        .miso_io_num = -1,
        .sclk_io_num = ILI9341_SCLK_PIN,
        .max_transfer_sz = ILI9341_WIDTH * ILI9341_HEIGHT * 2
    };

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 20 * 1000 * 1000,
        .mode = 0,
        .spics_io_num = ILI9341_CS_PIN,
        .queue_size = 7
    };

    spi_bus_initialize(ILI9341_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
    spi_bus_add_device(ILI9341_SPI_HOST, &devcfg, &ili9341_spi);
}

//================================================
// LVGL FLUSH
//================================================
static void ili9341_flush_cb(lv_disp_drv_t *drv,
                             const lv_area_t *area,
                             lv_color_t *color_map)
{
    uint32_t w = area->x2 - area->x1 + 1;
    uint32_t h = area->y2 - area->y1 + 1;
    uint32_t pixels = w * h;

    ili9341_set_window(area->x1, area->y1, area->x2, area->y2);

    for (uint32_t i = 0; i < pixels; i++) {
        uint16_t c = color_map[i].full;
        color_map[i].full = (c << 8) | (c >> 8);
    }

    gpio_set_level(ILI9341_DC_PIN, 1);
    spi_transaction_t t = {
        .length = pixels * 16,
        .tx_buffer = color_map
    };
    spi_device_transmit(ili9341_spi, &t);

    lv_disp_flush_ready(drv);
}

//================================================
// BUTTON EVENT CALLBACKS - 不直接操作UI,只修改状态
//================================================
static void start_btn_event_cb(lv_event_t *e)
{
    printf("START button clicked!\n");
    if (!timer_running) {
        timer_hours = 0;
        timer_minutes = 0;
        timer_seconds = 0;
        timer_running = true;
        timer_need_update = true;  // 标记需要更新显示
        printf("Timer started from 0\n");
    }
}

static void stop_btn_event_cb(lv_event_t *e)
{
    printf("STOP button clicked!\n");
    timer_running = false;
    timer_need_update = true;  // 标记需要更新显示
    printf("Timer stopped\n");
}

static void reset_btn_event_cb(lv_event_t *e)
{
    printf("RESET button clicked!\n");
    // 只修改状态,不直接操作UI
    timer_running = false;
    timer_hours = 0;
    timer_minutes = 0;
    timer_seconds = 0;
    timer_need_update = true;  // 标记需要更新显示
    printf("Timer reset to 00:00:00\n");
}

//================================================
// UI
//================================================
static void create_ui(void)
{
    lv_obj_set_style_bg_color(lv_scr_act(), lv_color_white(), 0);
    lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0);

    label_time = lv_label_create(lv_scr_act());
    lv_label_set_text(label_time, "00:00:00");
    lv_obj_set_style_text_color(label_time, lv_color_hex(0xff0000), 0);
    lv_obj_set_style_text_font(label_time, &lv_font_montserrat_32, 0);
    lv_obj_align(label_time, LV_ALIGN_CENTER, 0, -40);

    lv_obj_t *btn_start = lv_btn_create(lv_scr_act());
    lv_obj_set_size(btn_start, 90, 55);
    lv_obj_align(btn_start, LV_ALIGN_CENTER, -100, 60);
    lv_obj_add_event_cb(btn_start, start_btn_event_cb, LV_EVENT_CLICKED, NULL);
    lv_obj_set_style_bg_color(btn_start, lv_color_hex(0x00aa00), 0);
    lv_obj_t *label_start = lv_label_create(btn_start);
    lv_label_set_text(label_start, "START");
    lv_obj_set_style_text_color(label_start, lv_color_white(), 0);
    lv_obj_center(label_start);

    lv_obj_t *btn_stop = lv_btn_create(lv_scr_act());
    lv_obj_set_size(btn_stop, 90, 55);
    lv_obj_align(btn_stop, LV_ALIGN_CENTER, 0, 60);
    lv_obj_add_event_cb(btn_stop, stop_btn_event_cb, LV_EVENT_CLICKED, NULL);
    lv_obj_set_style_bg_color(btn_stop, lv_color_hex(0xcc0000), 0);
    lv_obj_t *label_stop = lv_label_create(btn_stop);
    lv_label_set_text(label_stop, "STOP");
    lv_obj_set_style_text_color(label_stop, lv_color_white(), 0);
    lv_obj_center(label_stop);

    lv_obj_t *btn_reset = lv_btn_create(lv_scr_act());
    lv_obj_set_size(btn_reset, 90, 55);
    lv_obj_align(btn_reset, LV_ALIGN_CENTER, 100, 60);
    lv_obj_add_event_cb(btn_reset, reset_btn_event_cb, LV_EVENT_CLICKED, NULL);
    lv_obj_set_style_bg_color(btn_reset, lv_color_hex(0x0066cc), 0);
    lv_obj_t *label_reset = lv_label_create(btn_reset);
    lv_label_set_text(label_reset, "RESET");
    lv_obj_set_style_text_color(label_reset, lv_color_white(), 0);
    lv_obj_center(label_reset);
}

//================================================
// LVGL TICK
//================================================
static void lv_tick_cb(void *arg)
{
    lv_tick_inc(1);
}

//================================================
// UPDATE DISPLAY - 统一在timer_task中更新
//================================================
static void update_display(void)
{
    char buf[16];
    snprintf(buf, sizeof(buf), "%02lu:%02lu:%02lu", 
             (unsigned long)timer_hours, 
             (unsigned long)timer_minutes, 
             (unsigned long)timer_seconds);
    lv_label_set_text(label_time, buf);
}

//================================================
// TIMER TASK
//================================================
static void timer_task(void *arg)
{
    char buf[16];
    
    while (1) {
        // 检查是否需要更新显示(由按钮触发)
        if (timer_need_update) {
            if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY)) {
                update_display();
                xSemaphoreGive(lvgl_mutex);
            }
            timer_need_update = false;
        }
        
        // 计时逻辑
        if (timer_running) {
            timer_seconds++;
            
            if (timer_seconds >= 60) {
                timer_seconds = 0;
                timer_minutes++;
                
                if (timer_minutes >= 60) {
                    timer_minutes = 0;
                    timer_hours++;
                    
                    if (timer_hours >= 100) {
                        timer_running = false;
                        timer_hours = 99;
                        timer_minutes = 59;
                        timer_seconds = 59;
                    }
                }
            }
            
            // 每秒更新显示
            if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY)) {
                update_display();
                xSemaphoreGive(lvgl_mutex);
            }
            
            vTaskDelay(pdMS_TO_TICKS(1000));
        } else {
            vTaskDelay(pdMS_TO_TICKS(50));
        }
    }
}

//================================================
// MAIN
//================================================
void app_main(void)
{
    printf("TIMER START\n");
    
    lvgl_mutex = xSemaphoreCreateMutex();
    touch_mutex = xSemaphoreCreateMutex();
    
    ili9341_spi_init();
    ili9341_init();

    ft6336g_init();
    xTaskCreate(touch_task, "touch_task", 4096, NULL, 5, NULL);

    lv_init();
    
    esp_timer_handle_t timer;
    const esp_timer_create_args_t tick_args = {
        .callback = lv_tick_cb,
        .name = "lv_tick"
    };
    esp_timer_create(&tick_args, &timer);
    esp_timer_start_periodic(timer, 1000);

    static lv_disp_draw_buf_t draw_buf;
    static lv_color_t buf1[ILI9341_WIDTH * 20];
    static lv_color_t buf2[ILI9341_WIDTH * 20];
    lv_disp_draw_buf_init(&draw_buf,
                          buf1,
                          buf2,
                          ILI9341_WIDTH * 20);

    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = ILI9341_WIDTH;
    disp_drv.ver_res = ILI9341_HEIGHT;
    disp_drv.flush_cb = ili9341_flush_cb;
    disp_drv.draw_buf = &draw_buf;
    lv_disp_drv_register(&disp_drv);

    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read_cb;
    lv_indev_drv_register(&indev_drv);

    create_ui();
    xTaskCreate(timer_task, "timer_task", 4096, NULL, 5, NULL);

    while (1) {
        if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY)) {
            lv_timer_handler();
            xSemaphoreGive(lvgl_mutex);
        }
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}

更多推荐