文档类型:ESP-IDF 通用 API 教程,不绑定单一开发板。
板级例程见 热门例程详解。
目录
简介
本教程将详细介绍如何使用 ESP32-S3 的 I2C 接口驱动 CST816 电容触摸屏控制器,实现触摸坐标的获取和手势识别功能。CST816 是一款常见的电容触摸屏控制器,广泛应用于各种小尺寸触摸屏产品中。
CST816 触摸芯片介绍
CST816 是一款单点电容触摸屏控制器,具有以下特点:
- 支持 I2C 接口通信
- 工作电压:2.8V-3.3V
- 支持单点触控
- 支持多种手势识别(上滑、下滑、左滑、右滑、单击、长按等)
- 低功耗设计
- 最高分辨率支持 240x240
硬件连接
引脚连接
CST816 触摸屏与 ESP32-S3 的连接方式如下:
| CST816 引脚 | ESP32-S3 引脚 | 说明 |
|---|---|---|
| VDD | 3.3V | 电源 |
| GND | GND | 地 |
| SCL | GPIO18 | I2C 时钟线 |
| SDA | GPIO17 | I2C 数据线 |
| INT | GPIO16 (可选) | 中断引脚,触摸时产生中断 |
| RST | GPIO15 (可选) | 复位引脚 |
上拉电阻
I2C 总线需要上拉电阻,通常为 4.7kΩ 或 10kΩ。ESP32-S3 内部有可配置的上拉电阻,可以通过软件启用,但在某些情况下,外部上拉电阻可能提供更好的性能。
I2C 驱动初始化
首先,我们需要初始化 ESP32-S3 的 I2C 驱动:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c.h"
#include "esp_log.h"
#include "esp_err.h"
static const char *TAG = "cst816_example";
// I2C 配置
#define I2C_MASTER_SCL_IO 18 // SCL 引脚
#define I2C_MASTER_SDA_IO 17 // SDA 引脚
#define I2C_MASTER_FREQ_HZ 400000 // I2C 频率 400kHz
#define I2C_MASTER_TIMEOUT_MS 1000 // 超时时间
#define I2C_MASTER_PORT I2C_NUM_0 // I2C 端口号
// CST816 寄存器地址
#define CST816_ADDR 0x15 // CST816 I2C 地址
#define CST816_GESTURE_REG 0x01 // 手势寄存器
#define CST816_FINGER_NUM_REG 0x02 // 手指数量寄存器
#define CST816_XPOS_H_REG 0x03 // X 坐标高字节
#define CST816_XPOS_L_REG 0x04 // X 坐标低字节
#define CST816_YPOS_H_REG 0x05 // Y 坐标高字节
#define CST816_YPOS_L_REG 0x06 // Y 坐标低字节
#define CST816_CHIP_ID_REG 0xA7 // 芯片 ID 寄存器
#define CST816_FIRMWARE_VERSION_REG 0xA9 // 固件版本寄存器
// 手势定义
#define GESTURE_NONE 0x00
#define GESTURE_SLIDE_DOWN 0x01
#define GESTURE_SLIDE_UP 0x02
#define GESTURE_SLIDE_LEFT 0x03
#define GESTURE_SLIDE_RIGHT 0x04
#define GESTURE_SINGLE_CLICK 0x05
#define GESTURE_DOUBLE_CLICK 0x0B
#define GESTURE_LONG_PRESS 0x0C
// 初始化 I2C 总线
static esp_err_t i2c_master_init(void)
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE, // 启用内部上拉
.scl_pullup_en = GPIO_PULLUP_ENABLE, // 启用内部上拉
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
esp_err_t ret = i2c_param_config(I2C_MASTER_PORT, &conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "I2C 参数配置失败");
return ret;
}
ret = i2c_driver_install(I2C_MASTER_PORT, conf.mode, 0, 0, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "I2C 驱动安装失败");
return ret;
}
ESP_LOGI(TAG, "I2C 初始化成功");
return ESP_OK;
}
CST816 驱动实现
接下来,我们实现 CST816 触摸屏的基本操作函数:
// 从 CST816 读取数据
static esp_err_t cst816_read_reg(uint8_t reg_addr, uint8_t *data, size_t len)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (CST816_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (CST816_ADDR << 1) | I2C_MASTER_READ, true);
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(I2C_MASTER_PORT, cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return ret;
}
// 向 CST816 写入数据
static esp_err_t cst816_write_reg(uint8_t reg_addr, uint8_t *data, size_t len)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (CST816_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_write(cmd, data, len, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return ret;
}
// 读取 CST816 芯片 ID
static esp_err_t cst816_read_chip_id(uint8_t *chip_id)
{
return cst816_read_reg(CST816_CHIP_ID_REG, chip_id, 1);
}
// 读取 CST816 固件版本
static esp_err_t cst816_read_firmware_version(uint8_t *version)
{
return cst816_read_reg(CST816_FIRMWARE_VERSION_REG, version, 1);
}
// 读取触摸数据
typedef struct {
uint8_t gesture; // 手势类型
uint8_t finger_num; // 手指数量
uint16_t x; // X 坐标
uint16_t y; // Y 坐标
} cst816_touch_data_t;
static esp_err_t cst816_read_touch_data(cst816_touch_data_t *touch_data)
{
uint8_t data[6];
esp_err_t ret = cst816_read_reg(CST816_GESTURE_REG, data, 6);
if (ret != ESP_OK) {
return ret;
}
touch_data->gesture = data[0];
touch_data->finger_num = data[1];
touch_data->x = ((uint16_t)data[2] << 8) | data[3];
touch_data->y = ((uint16_t)data[4] << 8) | data[5];
return ESP_OK;
}
// 初始化 CST816
static esp_err_t cst816_init(void)
{
// 初始化 I2C
esp_err_t ret = i2c_master_init();
if (ret != ESP_OK) {
return ret;
}
// 读取芯片 ID 验证通信
uint8_t chip_id;
ret = cst816_read_chip_id(&chip_id);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "无法读取 CST816 芯片 ID");
return ret;
}
ESP_LOGI(TAG, "CST816 芯片 ID: 0x%02X", chip_id);
// 读取固件版本
uint8_t firmware_version;
ret = cst816_read_firmware_version(&firmware_version);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "CST816 固件版本: 0x%02X", firmware_version);
}
ESP_LOGI(TAG, "CST816 初始化成功");
return ESP_OK;
}
完整示例代码
下面是一个完整的示例,演示如何初始化 CST816 触摸屏并持续读取触摸坐标:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_err.h"
static const char *TAG = "cst816_example";
// I2C 配置
#define I2C_MASTER_SCL_IO 18 // SCL 引脚
#define I2C_MASTER_SDA_IO 17 // SDA 引脚
#define I2C_MASTER_FREQ_HZ 400000 // I2C 频率 400kHz
#define I2C_MASTER_TIMEOUT_MS 1000 // 超时时间
#define I2C_MASTER_PORT I2C_NUM_0 // I2C 端口号
// CST816 寄存器地址
#define CST816_ADDR 0x15 // CST816 I2C 地址
#define CST816_GESTURE_REG 0x01 // 手势寄存器
#define CST816_FINGER_NUM_REG 0x02 // 手指数量寄存器
#define CST816_XPOS_H_REG 0x03 // X 坐标高字节
#define CST816_XPOS_L_REG 0x04 // X 坐标低字节
#define CST816_YPOS_H_REG 0x05 // Y 坐标高字节
#define CST816_YPOS_L_REG 0x06 // Y 坐标低字节
#define CST816_CHIP_ID_REG 0xA7 // 芯片 ID 寄存器
#define CST816_FIRMWARE_VERSION_REG 0xA9 // 固件版本寄存器
// 手势定义
#define GESTURE_NONE 0x00
#define GESTURE_SLIDE_DOWN 0x01
#define GESTURE_SLIDE_UP 0x02
#define GESTURE_SLIDE_LEFT 0x03
#define GESTURE_SLIDE_RIGHT 0x04
#define GESTURE_SINGLE_CLICK 0x05
#define GESTURE_DOUBLE_CLICK 0x0B
#define GESTURE_LONG_PRESS 0x0C
// 触摸数据结构
typedef struct {
uint8_t gesture; // 手势类型
uint8_t finger_num; // 手指数量
uint16_t x; // X 坐标
uint16_t y; // Y 坐标
} cst816_touch_data_t;
// 初始化 I2C 总线
static esp_err_t i2c_master_init(void)
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE, // 启用内部上拉
.scl_pullup_en = GPIO_PULLUP_ENABLE, // 启用内部上拉
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
esp_err_t ret = i2c_param_config(I2C_MASTER_PORT, &conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "I2C 参数配置失败");
return ret;
}
ret = i2c_driver_install(I2C_MASTER_PORT, conf.mode, 0, 0, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "I2C 驱动安装失败");
return ret;
}
ESP_LOGI(TAG, "I2C 初始化成功");
return ESP_OK;
}
// 从 CST816 读取数据
static esp_err_t cst816_read_reg(uint8_t reg_addr, uint8_t *data, size_t len)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (CST816_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (CST816_ADDR << 1) | I2C_MASTER_READ, true);
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(I2C_MASTER_PORT, cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return ret;
}
// 向 CST816 写入数据
static esp_err_t cst816_write_reg(uint8_t reg_addr, uint8_t *data, size_t len)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (CST816_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_write(cmd, data, len, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return ret;
}
// 读取 CST816 芯片 ID
static esp_err_t cst816_read_chip_id(uint8_t *chip_id)
{
return cst816_read_reg(CST816_CHIP_ID_REG, chip_id, 1);
}
// 读取 CST816 固件版本
static esp_err_t cst816_read_firmware_version(uint8_t *version)
{
return cst816_read_reg(CST816_FIRMWARE_VERSION_REG, version, 1);
}
// 读取触摸数据
static esp_err_t cst816_read_touch_data(cst816_touch_data_t *touch_data)
{
uint8_t data[6];
esp_err_t ret = cst816_read_reg(CST816_GESTURE_REG, data, 6);
if (ret != ESP_OK) {
return ret;
}
touch_data->gesture = data[0];
touch_data->finger_num = data[1];
touch_data->x = ((uint16_t)data[2] << 8) | data[3];
touch_data->y = ((uint16_t)data[4] << 8) | data[5];
return ESP_OK;
}
// 获取手势名称
static const char *get_gesture_name(uint8_t gesture)
{
switch (gesture) {
case GESTURE_NONE: return "无手势";
case GESTURE_SLIDE_DOWN: return "下滑";
case GESTURE_SLIDE_UP: return "上滑";
case GESTURE_SLIDE_LEFT: return "左滑";
case GESTURE_SLIDE_RIGHT: return "右滑";
case GESTURE_SINGLE_CLICK: return "单击";
case GESTURE_DOUBLE_CLICK: return "双击";
case GESTURE_LONG_PRESS: return "长按";
default: return "未知手势";
}
}
// 触摸屏任务
static void cst816_task(void *pvParameters)
{
cst816_touch_data_t touch_data;
while (1) {
esp_err_t ret = cst816_read_touch_data(&touch_data);
if (ret == ESP_OK) {
if (touch_data.finger_num > 0) {
ESP_LOGI(TAG, "触摸检测: 手指数=%d, X=%d, Y=%d, 手势=%s",
touch_data.finger_num, touch_data.x, touch_data.y,
get_gesture_name(touch_data.gesture));
}
} else {
ESP_LOGW(TAG, "读取触摸数据失败: %s", esp_err_to_name(ret));
}
// 延时 50ms
vTaskDelay(pdMS_TO_TICKS(50));
}
}
// 初始化 CST816
static esp_err_t cst816_init(void)
{
// 初始化 I2C
esp_err_t ret = i2c_master_init();
if (ret != ESP_OK) {
return ret;
}
// 读取芯片 ID 验证通信
uint8_t chip_id;
ret = cst816_read_chip_id(&chip_id);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "无法读取 CST816 芯片 ID");
return ret;
}
ESP_LOGI(TAG, "CST816 芯片 ID: 0x%02X", chip_id);
// 读取固件版本
uint8_t firmware_version;
ret = cst816_read_firmware_version(&firmware_version);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "CST816 固件版本: 0x%02X", firmware_version);
}
ESP_LOGI(TAG, "CST816 初始化成功");
return ESP_OK;
}
void app_main(void)
{
ESP_LOGI(TAG, "CST816 触摸屏示例开始");
// 初始化 CST816
esp_err_t ret = cst816_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "CST816 初始化失败");
return;
}
// 创建触摸屏任务
xTaskCreate(cst816_task, "cst816_task", 4096, NULL, 10, NULL);
}
常见问题与解决方案
1. 无法检测到 CST816 设备
如果无法检测到 CST816 设备,可能的原因和解决方法:
-
I2C 地址不正确:
- 确认 CST816 的 I2C 地址(通常为 0x15 或 0x38)
- 使用 I2C 扫描工具检测总线上的设备
-
硬件连接问题:
- 检查 SCL 和 SDA 引脚连接是否正确
- 确保 VDD 和 GND 连接正确
- 检查上拉电阻是否正确连接
-
I2C 时钟频率过高:
- 尝试降低 I2C 时钟频率(如从 400kHz 降至 100kHz)
2. 触摸坐标不准确
如果触摸坐标不准确,可能的原因和解决方法:
-
坐标系统不匹配:
- 确认屏幕和触摸控制器的坐标系统是否一致
- 可能需要进行坐标转换(如 X/Y 交换、坐标反转等)
-
触摸屏校准问题:
- 实现触摸屏校准功能
- 存储校准参数并在读取坐标时应用
-
干扰问题:
- 检查是否有电磁干扰源
- 改善接地和屏蔽
3. 手势识别不工作
如果手势识别不工作,可能的原因和解决方法:
-
固件版本问题:
- 确认 CST816 固件是否支持手势识别
- 可能需要更新固件
-
配置问题:
- 检查是否需要启用手势识别功能
- 可能需要配置特定寄存器
高级应用
1. 中断驱动方式
为了降低 CPU 负载,可以使用中断驱动方式检测触摸事件:
#define CST816_INT_GPIO 16 // 中断引脚
// 中断服务函数
static void IRAM_ATTR cst816_isr_handler(void *arg)
{
// 通知主任务处理触摸事件
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(touch_semaphore, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// 初始化中断
static void cst816_init_interrupt(void)
{
// 创建信号量
touch_semaphore = xSemaphoreCreateBinary();
// 配置 GPIO
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << CST816_INT_GPIO),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_NEGEDGE, // 下降沿触发
};
gpio_config(&io_conf);
// 安装 GPIO 中断服务
gpio_install_isr_service(0);
// 添加中断处理函数
gpio_isr_handler_add(CST816_INT_GPIO, cst816_isr_handler, NULL);
ESP_LOGI(TAG, "CST816 中断初始化完成");
}
// 中断驱动的触摸任务
static void cst816_int_task(void *pvParameters)
{
cst816_touch_data_t touch_data;
while (1) {
// 等待中断信号
if (xSemaphoreTake(touch_semaphore, portMAX_DELAY) == pdTRUE) {
// 读取触摸数据
esp_err_t ret = cst816_read_touch_data(&touch_data);
if (ret == ESP_OK && touch_data.finger_num > 0) {
ESP_LOGI(TAG, "触摸检测: 手指数=%d, X=%d, Y=%d, 手势=%s",
touch_data.finger_num, touch_data.x, touch_data.y,
get_gesture_name(touch_data.gesture));
}
}
}
}
2. 触摸屏校准
实现触摸屏校准功能,提高触摸精度:
typedef struct {
float a, b, c, d, e, f; // 校准参数
bool calibrated; // 是否已校准
} touch_calibration_t;
static touch_calibration_t calibration = {
.a = 1.0f, .b = 0.0f, .c = 0.0f,
.d = 0.0f, .e = 1.0f, .f = 0.0f,
.calibrated = false
};
// 应用校准参数
static void apply_calibration(uint16_t raw_x, uint16_t raw_y, uint16_t *cal_x, uint16_t *cal_y)
{
if (calibration.calibrated) {
float x = calibration.a * raw_x + calibration.b * raw_y + calibration.c;
float y = calibration.d * raw_x + calibration.e * raw_y + calibration.f;
*cal_x = (uint16_t)x;
*cal_y = (uint16_t)y;
} else {
*cal_x = raw_x;
*cal_y = raw_y;
}
}
3. 多点触控支持
对于支持多点触控的 CST816 变种,可以扩展代码以支持多点触控:
#define MAX_TOUCH_POINTS 5 // 最大触摸点数
typedef struct {
uint8_t id; // 触摸点 ID
uint16_t x; // X 坐标
uint16_t y; // Y 坐标
uint8_t pressure; // 压力值
} touch_point_t;
typedef struct {
uint8_t gesture; // 手势类型
uint8_t touch_num; // 触摸点数量
touch_point_t points[MAX_TOUCH_POINTS]; // 触摸点数据
} multi_touch_data_t;
总结
本教程详细介绍了如何使用 ESP32-S3 的 I2C 接口驱动 CST816 触摸屏控制器。通过以上示例代码,我们实现了:
-
基本功能:
- I2C 驱动初始化
- CST816 触摸芯片初始化
- 触摸坐标读取
- 手势识别
-
高级功能:
- 中断驱动方式
- 触摸屏校准
- 多点触控支持
-
性能优化:
- 使用中断代替轮询
- 适当的采样率设置
- 合理的任务优先级
参考资料
注意事项
-
硬件连接:
- 确保 I2C 引脚连接正确(SCL=GPIO18, SDA=GPIO17)
- 添加适当的上拉电阻(4.7kΩ 或 10kΩ)
- 注意电源和地的连接
-
软件配置:
- 选择合适的 I2C 时钟频率
- 根据实际需求配置中断和采样率
- 注意错误处理和异常情况
-
调试建议:
- 使用逻辑分析仪检查 I2C 信号
- 通过串口监视触摸数据
- 实现简单的测试程序验证功能
希望本教程能帮助您在 ESP32-S3 项目中成功集成 CST816 触摸屏。如有任何问题,欢迎在 Espressif 论坛或 GitHub 上提问。