0. 废话

貌似CST328这块触摸IC在成品屏中并不常见,最近买的一块微雪的2.8寸电容屏很不巧就是用的这块IC,我打算用ESP-IDF来开发,资料可以说少之又少,最有价值的两份资料是微雪给的Arduino例程和制造商海栎创微电子的一份一言难尽的数据手册。
笔者也是ESP32的新手,本博客默认读者起码有用ESP-IDF或者VSCode新建ESP32工程并引入组件的基础。
笔者的目的是用lvgl_esp32_drivers组件建立lvgl的环境,在点亮屏幕之后发现这个库并没有适配这块触摸芯片。

1. 参考资料

一些常见的步骤这篇博客就不重复了,这里贴出我参考的博客:
ESP32怎么用硬件IIC
用lvgl_esp32_drivers组件点亮屏幕
屏幕IC+触摸IC移植的正常流程
参考资料,下面参考到的地方不完全标注:
cst328数据手册
微雪2.8寸屏幕资料
请务必点亮了屏幕再往下看

2. 过程分析

2.1 新建驱动文件

其实不难看出,lvgl_esp32_drivers库所有适配的触摸芯片都在lvgl_touch文件夹下。因此在该文件夹下新建cst328.c和cst328.h作为驱动程序,从该库的github项目说明得知,驱动主要是要完成初始化函数和读取坐标函数,这两个函数直接用于驱动lvgl。函数原型为

void cst328_init(uint8_t dev_addr);
bool cst328_read(lv_indev_drv_t *drv, lv_indev_data_t *data)

cst328和gt911都是用iic驱动的,所以一些硬件不相干的地方可以照搬过来的,下文如果有些地方不知道为什么要这样写,问就是我抄来的

如果想把自己写的驱动添加到menuconfig,又像我这样完全不懂Kconfig的语法,有一个笨办法,打开lvgl_touch文件夹下的Kconfig,按照gt911的样子复制一份改个CST328的名字即可。

2.2 cst328初始化流程

lvgl_esp32_drivers库中所有触摸芯片的IIC驱动都没有INT和RST引脚的配置,但是貌似很多芯片初始化和复位都是依赖这两个引脚的,不知道算不算一个漏洞

数据手册中有记录芯片上电和复位的流程,但是我不确定上电的时序要不要控制器去控制,实测是不用的。所以初始化函数中只要添加复位部分就好了。

#define CST328_RST_PIN GPIO_NUM_2
#define CST328_INT_PIN GPIO_NUM_4
static void cst328_rst() {
    vTaskDelay(pdMS_TO_TICKS(50));
    gpio_set_direction(CST328_INT_PIN, (GPIO_MODE_INPUT)| (GPIO_MODE_DEF_OD));
    gpio_set_direction(CST328_RST_PIN, GPIO_MODE_OUTPUT);
    gpio_set_level(CST328_INT_PIN, 1);
    gpio_set_level(CST328_RST_PIN, 1);
    vTaskDelay(pdMS_TO_TICKS(50));
    gpio_set_level(CST328_RST_PIN, 0);
    vTaskDelay(pdMS_TO_TICKS(5));
    gpio_set_level(CST328_RST_PIN, 1);
    vTaskDelay(pdMS_TO_TICKS(200));
    gpio_set_level(CST328_INT_PIN, 0);
}

其实有以上的过程,芯片的初始化已经完成了,不过为了保险起见,在初始化函数中再验证一下校验位,顺便测试一下i2c驱动有没有问题。有关i2c的api在下节说明。
整体初始化函数如下:

typedef struct {
    bool inited;
    uint8_t i2c_dev_addr;
} cst328_status_t;

void cst328_init(uint8_t dev_addr) {
    cst328_rst();
    if (!cst328_status.inited) {
    
        cst328_status.i2c_dev_addr = dev_addr;
        cst328_status.max_x_coord = LV_HOR_RES_MAX;
        cst328_status.max_y_coord = LV_VER_RES_MAX;
        
        uint8_t data_buf[4];
        esp_err_t ret;

        ESP_LOGI(TAG, "Checking for CST328 Touch Controller");
        if ((ret = cst328_i2c_write(dev_addr, HYN_REG_MUT_DEBUG_INFO_MODE, data_buf, 0) != ESP_OK)) {
            ESP_LOGE(TAG, "Error reading from device: %s",
                 esp_err_to_name(ret));    // Only show error the first time
            return;
        }
        cst328_i2c_read(dev_addr, HYN_REG_MUT_DEBUG_INFO_BOOT_TIME, data_buf, 4);
        cst328_i2c_write(dev_addr, HYN_REG_MUT_NORMAL_MODE, data_buf, 0);

        uint16_t Verification = (uint16_t)(data_buf[4] << 8) | data_buf[3];

        if(!((Verification==0xCACA)?true:false)){
            ESP_LOGE(TAG, "Touch initialization failed!\r\n");
            cst328_status.inited = false;
        }
        else
        {
            ESP_LOGI(TAG, "Touch initialization success!\r\n");
            cst328_status.inited = true;
        }
    }
}

2.2 i2c驱动

提一个我被坑的地方,我使用menuconfig配置i2c的,默认居然没使能内置上拉电阻……手头的板应该是没上拉电阻的,导致i2c握手失败。
直接上代码,调用的lvgl_i2c_write、cst328_i2c_read的api是lvgl_esp32_drivers在ESP库函数基础上封装一层的,保存在lvgl_i2c下,这个api用宏的方式兼容了大多数i2c的标准命令格式。

esp_err_t cst328_i2c_write(uint8_t slave_addr, uint16_t register_addr, uint8_t *data_buf, uint8_t len) {
    if (len == 0)
    {
        uint8_t register_buf[2] = {(uint8_t)(register_addr >> 8), (uint8_t)(register_addr)};
        return lvgl_i2c_write(CONFIG_LV_I2C_TOUCH_PORT, slave_addr, register_addr | I2C_NO_REG, register_buf, 2);
    }
    else
        return lvgl_i2c_write(CONFIG_LV_I2C_TOUCH_PORT, slave_addr, register_addr | I2C_REG_16, data_buf, len);
}

esp_err_t cst328_i2c_read(uint8_t slave_addr, uint16_t register_addr, uint8_t *data_buf, uint8_t len) {
    return lvgl_i2c_read(CONFIG_LV_I2C_TOUCH_PORT, slave_addr, register_addr | I2C_REG_16, data_buf, len);
}

2.3 读坐标函数

lvgl不需要多点触控,因此读坐标只需要读一个通道就行了,此外提一下,从别的博客得知,成品屏能读出来的坐标只会在分辨率之内,还有默认的坐标轴起点可能与所需的方向不一致,需要翻转。
直接上代码,对照一下手册,应该很容易看懂的

bool cst328_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
    uint8_t touch_pnt_cnt = 0;        // Number of detected touch points
    const uint8_t clear = 0;
    static int16_t last_x = 0;  // 12bit pixel value
    static int16_t last_y = 0;  // 12bit pixel value
    uint8_t data_buf[3];

    cst328_i2c_read(cst328_status.i2c_dev_addr, ESP_LCD_TOUCH_CST328_READ_Number_REG, data_buf, 1); // 读取触控数量
    touch_pnt_cnt = data_buf[0] & 0x0F;
    ESP_LOGI(TAG, "touchnum:%d", touch_pnt_cnt);
    if (touch_pnt_cnt != 1){ // ignore no touch & multi touch
        data->point.x = last_x;
        data->point.y = last_y;
        data->state = LV_INDEV_STATE_REL;
        cst328_i2c_write(cst328_status.i2c_dev_addr, ESP_LCD_TOUCH_CST328_READ_Number_REG, (uint8_t *)&clear, 1);
        return false;
    }

    cst328_i2c_read(cst328_status.i2c_dev_addr, ESP_LCD_TOUCH_CST328_READ_XY_REG + 1, &data_buf[0], 3); // 只取第一个通道的坐标
    cst328_i2c_write(cst328_status.i2c_dev_addr, ESP_LCD_TOUCH_CST328_READ_Number_REG, (uint8_t *)&clear, 1);
    
    last_x = (uint16_t)(((uint16_t)data_buf[0] << 4) + ((data_buf[2] & 0xF0)>> 4));
    last_y = (uint16_t)(((uint16_t)data_buf[1] << 4) + (data_buf[2] & 0x0F));

#ifdef CONFIG_LV_CST328_SWAPXY
    int16_t swap_buf = last_x;
    last_x = last_y;
    last_y = swap_buf;
#endif
#ifdef CONFIG_LV_CST328_INVERT_X
    last_x = cst328_status.max_x_coord - last_x;
#endif
#ifdef CONFIG_LV_CST328_INVERT_Y
    last_y = cst328_status.max_y_coord - last_y;
#endif

    data->point.x = last_x;
    data->point.y = last_y;
    data->state = LV_INDEV_STATE_PR;
    ESP_LOGI(TAG, "X=%u Y=%u", data->point.x, data->point.y);
    ESP_LOGV(TAG, "X=%u Y=%u", data->point.x, data->point.y);
    return false;
}

3. 整体工程

可能有部分我漏讲或者不懂的,可参考完整的工程,esp-idf的版本为4.4.8,貌似用5.0以上编译过不了。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐