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

        前面我们讲了很多次普通屏,但是没有聊过触摸屏。对于带屏幕的这类应用,一般是这样的,有屏幕的产品通常会比没有屏幕的产品看上去高档一点。而有触摸屏的产品,又会比纯屏幕的产品高档一点。所以,有的时候为题提高体验,增加触摸屏也是很有必要的。

1、驱动芯片

        编写代码之前,有必要了解下当前的驱动ic是什么。比如,现在准备用的这个触摸屏,它的驱动ic就是xpt2046。

2、外部接线

        一般的驱动ic接线是以i2c为主。不过这个xpt2046比较特殊,它是spi总线。好在esp32芯片有两个spi可以使用。另外,它还带一个irq,可以用作中断。当然,客户也可以不用。

3、接线时的注意事项

        在连接touch ic的时候,clk、cs、irq这些都不会出错。比较容易出错的地方是mosi和miso。这里需要注意,触摸屏的t_do需要连接miso,而t_din则连接mosi。一开始做触摸屏的同学,这些地方很容易出错。

4、用ai生成代码

        生成代码的时候,需要细心一点。最好有一个基础代码,比如屏幕跑通的code,然后告诉ai,当前的mcu是esp32,准备用spi3来连接xpt2046模块,另外再写一个demo,即按键按一下,label自增1,让ai准备一个code。不出意外,就可以看到这样的代码,

#include <stdio.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#include "esp_timer.h"
#include "esp_task_wdt.h"

#include "driver/spi_master.h"
#include "driver/gpio.h"

#include "lvgl.h"

//================================================
// 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      12

#define ILI9341_WIDTH       320
#define ILI9341_HEIGHT      240

//================================================
// XPT2046 TOUCH CONFIG
//================================================
#define XPT2046_SPI_HOST    SPI3_HOST
#define XPT2046_SCLK_PIN    18
#define XPT2046_MOSI_PIN    19
#define XPT2046_MISO_PIN    21
#define XPT2046_CS_PIN      5
#define XPT2046_IRQ_PIN     22

#define XPT2046_CMD_X       0xD0
#define XPT2046_CMD_Y       0x90

#define TOUCH_THRESHOLD     50

static spi_device_handle_t ili9341_spi;
static spi_device_handle_t xpt2046_spi;

static SemaphoreHandle_t lvgl_mutex;

static lv_obj_t *label_count;
static lv_obj_t *btn_increment;
static int counter = 0;

//================================================
// LCD 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);
}

static void ili9341_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
    ili9341_send_cmd(0x2A);
    uint8_t col[] = {x0 >> 8, x0 & 0xFF, x1 >> 8, x1 & 0xFF};
    ili9341_send_data(col, 4);

    ili9341_send_cmd(0x2B);
    uint8_t row[] = {y0 >> 8, y0 & 0xFF, y1 >> 8, y1 & 0xFF};
    ili9341_send_data(row, 4);

    ili9341_send_cmd(0x2C);
}

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;

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

    gpio_set_level(ILI9341_DC_PIN, 1);

    spi_transaction_t t = {
        .length = w * h * 16,
        .tx_buffer = color_map
    };

    spi_device_transmit(ili9341_spi, &t);
    lv_disp_flush_ready(drv);
}

//================================================
// 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(0x01);
    vTaskDelay(pdMS_TO_TICKS(120));

    ili9341_send_cmd(0x11);
    vTaskDelay(pdMS_TO_TICKS(120));

    ili9341_send_cmd(0x3A);
    ili9341_send_data((uint8_t[]){0x55}, 1);

    ili9341_send_cmd(0x36);
    ili9341_send_data((uint8_t[]){0x28}, 1);

    ili9341_send_cmd(0x29);

    gpio_set_level(ILI9341_BL_PIN, 1);
}

//================================================
// SPI INIT LCD
//================================================
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);
}

//================================================
// SPI INIT TOUCH
//================================================
static void xpt2046_spi_init(void)
{
    gpio_config_t io = {
        .pin_bit_mask = (1ULL << XPT2046_IRQ_PIN),
        .mode = GPIO_MODE_INPUT
    };
    gpio_config(&io);

    spi_bus_config_t buscfg = {
        .mosi_io_num = XPT2046_MOSI_PIN,
        .miso_io_num = XPT2046_MISO_PIN,
        .sclk_io_num = XPT2046_SCLK_PIN,
        .max_transfer_sz = 8
    };

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 2 * 1000 * 1000,
        .mode = 0,
        .spics_io_num = XPT2046_CS_PIN,
        .queue_size = 1
    };

    spi_bus_initialize(XPT2046_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
    spi_bus_add_device(XPT2046_SPI_HOST, &devcfg, &xpt2046_spi);
}

//================================================
// TOUCH READ (FIXED)
//================================================
static uint16_t xpt2046_read_adc(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_transmit(xpt2046_spi, &t);

    return ((rx[1] << 8) | rx[2]) >> 3 & 0x0FFF;
}

static bool xpt2046_read_touch(uint16_t *x, uint16_t *y)
{
    if (gpio_get_level(XPT2046_IRQ_PIN) == 1) {
        return false;
    }

    uint16_t raw_x = xpt2046_read_adc(XPT2046_CMD_X);
    uint16_t raw_y = xpt2046_read_adc(XPT2046_CMD_Y);
    uint16_t z1    = xpt2046_read_adc(0xB0);

    if (z1 < TOUCH_THRESHOLD) {
        return false;
    }

    *x = (raw_x * ILI9341_WIDTH) / 4095;
    *y = (raw_y * ILI9341_HEIGHT) / 4095;

    if (*x >= ILI9341_WIDTH) *x = ILI9341_WIDTH - 1;
    if (*y >= ILI9341_HEIGHT) *y = ILI9341_HEIGHT - 1;

    return true;
}

//================================================
// TOUCH LVGL CALLBACK
//================================================
static void touch_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
    uint16_t x, y;

    if (xpt2046_read_touch(&x, &y)) {
        data->state = LV_INDEV_STATE_PRESSED;
        data->point.x = x;
        data->point.y = y;
    } else {
        data->state = LV_INDEV_STATE_RELEASED;
    }
}

//================================================
// UI
//================================================
static void btn_event_cb(lv_event_t *e)
{
    if (lv_event_get_code(e) == LV_EVENT_CLICKED) {

        if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY)) {
            counter++;

            char buf[32];
            sprintf(buf, "Count: %d", counter);

            lv_label_set_text(label_count, buf);

            xSemaphoreGive(lvgl_mutex);
        }
    }
}

static void ui_create(void)
{
    lv_obj_set_style_bg_color(lv_scr_act(), lv_color_white(), 0);

    label_count = lv_label_create(lv_scr_act());
    lv_label_set_text(label_count, "Count: 0");
    lv_obj_align(label_count, LV_ALIGN_CENTER, 0, -40);

    btn_increment = lv_btn_create(lv_scr_act());
    lv_obj_set_size(btn_increment, 120, 50);
    lv_obj_align(btn_increment, LV_ALIGN_CENTER, 0, 40);

    lv_obj_t *lbl = lv_label_create(btn_increment);
    lv_label_set_text(lbl, "Press");
    lv_obj_center(lbl);

    lv_obj_add_event_cb(btn_increment, btn_event_cb, LV_EVENT_CLICKED, NULL);
}

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

//================================================
// MAIN
//================================================
void app_main(void)
{
    printf("START\n");

    lvgl_mutex = xSemaphoreCreateMutex();

    ili9341_spi_init();
    ili9341_init();

    xpt2046_spi_init();

    lv_init();

    esp_timer_handle_t timer;
    esp_timer_create_args_t args = {
        .callback = lv_tick_cb,
        .name = "lv_tick"
    };
    esp_timer_create(&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 = touch_cb;
    lv_indev_drv_register(&indev_drv);

    ui_create();

    esp_task_wdt_add(NULL);

    while (1) {
        esp_task_wdt_reset();
        lv_timer_handler();
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

5、编译、测试和验证

        流程和之前的测试流程差不多。就是有一个地方需要注意下。正常,触摸屏都是需要标定一下的。不过这里为了方便,没有标定,直接编译完,就用触摸笔测试了。所以测试的时候,会发现有些地方即使不在按钮上单击,label也能自增1,这一点需要注意下。即测试的时候,需要各个地方都点击几次,这样才不会造成漏测,以为模块没起作用。

6、没有使用irq

        虽然我们连接了irq,但是本身lvgl使用触摸屏,还是走的touch_cb这个回调函数。这一点和ili9341_flush_cb非常相似。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐