用ESP32S3驱动CST328并在lvgl_esp32_drivers新增芯片 | 过程记录
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以上编译过不了。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)