基于ESP32的桌面小屏幕实战[13]:I2C驱动触摸屏
1. 基本概念
I2C总线是由Philips公司在80年代开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
场景:硬件看门狗芯片、加密芯片、触摸屏驱动芯片、系列传感器等等。
IIC一共有只有两个总线:
- SDA (Serial data) 是串行数据线,D代表Data也就是数据,Send Data 也就是用来传输数据的
- SCL (Serial clock line) 是串行时钟线,C代表Clock 也就是时钟 也就是控制数据发送的时序的
IIC的一个优点是它支持多主控(multimastering),其中任何一个能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
支持不同速率的通讯速度,标准速度(最高速度100kHZ),快速(最高400kHZ),高速(3.4Mbit/s)
SCL和SDA都需要接上拉电阻 (大小由速度和容性负载决定一般在3.3K-10K之间) 保证数据的稳定性,减少干扰。
IIC是半双工,而不是全双工 ,同一时间只可以单向通信

上图中 EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。
下图是I2C的时序图

开始信号:SCL为高电平,SDA拉低
结束信号:SCL为高电平,SDA拉高
开始和结束信号之间是地址、读/写标志位、应答符号ACK
主机收到应答信号之后才会继续发信号,如果收不到就超时停止。
实际波形图

2. 源码
项目位置:.../DesktopScreenV4.0.3/main/src/hal/ds_i2c.c
切换到dev7
git checkout dev7

2.1 ds_i2c.c文件
2.1.1 包含头文件
#include <stdio.h>
#include "driver/i2c.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "ds_i2c.h"
2.1.2 设置读取地址
I2C 协议的命令发送
static esp_err_t i2c_master_set_addr(uint8_t u8Cmd){
i2c_cmd_handle_t cmd = i2c_cmd_link_create();//创建 I2C 命令链接
i2c_master_start(cmd);//发送起始信号
i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN);//发送从设备地址和写操作标志
i2c_master_write_byte(cmd, u8Cmd, ACK_CHECK_EN);//向从设备发送命令字节 u8Cmd ,并启用应答检查
i2c_master_stop(cmd);//发送停止信号
/* i2c_master_cmd_begin 在 I2C_MASTER_NUM 上开始执行构建的 I2C 命令,并在 1000 毫秒内完成。
* ret 存储执行结果,成功则返回 ESP_OK。*/
esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);//删除命令链接。删除 cmd,释放资源。
if (ret != ESP_OK) {//如果 ret 不为 ESP_OK
printf("i2c_master_set_addr error\n");//输出错误信息
}
return ret;//返回 I2C 命令执行的结果
}
i2c_cmd_handle_t是 ESP-IDF 中 I2C 驱动库定义的一种数据类型,代表一个 I2C 命令句柄(command handle)。它的类型通常是指向内部数据结构的指针,用于存储 I2C 主设备(master)操作的命令链,包含了 I2C 操作的顺序和参数。- 在 I2C 通信中,每个事务(transaction)由一系列的操作(如起始信号、写入字节、读取字节和停止信号等)组成。这些操作需要按照一定的顺序执行,而
i2c_cmd_handle_t用来将这些操作链接起来形成一条完整的命令序列。 i2c_cmd_link_create创建一个 I2C 命令句柄cmd,用于构建 I2C 传输序列。i2c_master_start生成一个 I2C 起始信号,表示 I2C 通信的开始。(ESP_SLAVE_ADDR << 1) | WRITE_BIT表示向从设备的地址ESP_SLAVE_ADDR发送写操作(WRITE_BIT)。ACK_CHECK_EN启用应答检查,以确保从设备收到地址和操作标志。- 分解
(ESP_SLAVE_ADDR << 1) | WRITE_BITESP_SLAVE_ADDR << 1ESP_SLAVE_ADDR是 I2C 从设备的 7 位地址。在 I2C 协议中,地址的高 7 位用于表示从设备地址,而第 8 位(最低位)表示操作的方向(读/写)。- << 1 操作是将
ESP_SLAVE_ADDR左移 1 位。这是为了为最低位(第 8 位)预留出空间,以表示数据传输方向。 - 例如,如果
ESP_SLAVE_ADDR的值为0x3C(二进制0111100),则左移一位后变为0x78(二进制1111000)。
| WRITE_BITWRITE_BIT是一个常量,通常定义为0或1,用于指示写或读操作。- 在 I2C 协议中,第 8 位(最低位)为
0表示写操作,为1表示读操作。 - 将
WRITE_BIT与(ESP_SLAVE_ADDR << 1)通过按位或(|)运算符组合,得到完整的 8 位地址字节。 - 如果
WRITE_BIT是0,表示写操作;如果WRITE_BIT是1,表示读操作。
i2c_master_stop生成 I2C 停止信号,表示传输的结束。
2.1.3 读取数据
从 I2C 从设备(slave)读取数据
/*
u8Cmd: 要发送给从设备的命令字节。
data_rd: 一个指向存储接收数据的缓冲区的指针。
size: 要读取的字节数。
*/
esp_err_t i2c_master_read_slave(uint8_t u8Cmd, uint8_t *data_rd, size_t size)
{
if (size == 0) {//size为0表示没有数据需要读取
return ESP_OK;
}
/* 把 u8Cmd 写入从设备 */
i2c_master_set_addr(u8Cmd);
/* 创建 I2C 命令链(cmd),用来构建读取操作的I2C指令 */
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
/* 启动 I2C 通信,为通信过程生成起始信号 */
i2c_master_start(cmd);
/* 发送从设备地址并指示读取操作。ACK_CHECK_EN 表示会检查从设备的应答信号。*/
i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | READ_BIT, ACK_CHECK_EN);
/* 逐字节读取数据 */
for(int index=0;index<(size-1);index++)
{ /* 逐字节读取数据并存储到 data_rd 缓冲区中
* 每读取一个字节,向从设备发送 ACK 确认,表示还会继续读取 */
i2c_master_read_byte(cmd, data_rd+index, ACK_VAL);
}
/* 最后一个字节读取后,向从设备发送 NACK 确认,表示不会继续读取数据 */
i2c_master_read_byte(cmd, data_rd+size-1, NACK_VAL);
/* 生成停止信号,结束本次 I2C 通信 */
i2c_master_stop(cmd);
/* 执行 I2C 命令链中的所有操作,等待时间为 1000 毫秒 */
esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
/* 删除命令链,释放相关资源 */
i2c_cmd_link_delete(cmd);
/* 错误检查 */
if (ret != ESP_OK) {
printf("i2c_master_read_slave error\n");
}
return ret;
}
- 先向从设备写入指定寄存器地址,然后进行读取。
- 读取
size个字节的数据,最后一个字节发送 NACK 表示读取结束。
2.1.4 写入数据
通过 I2C 向从设备(slave)发送数据
/*
u8Cmd: 发送的命令字节,通常用于指定从设备中的寄存器地址或操作命令。
data_wr: 要发送的实际数据的指针。
size: 要发送的数据的字节数。
*/
esp_err_t i2c_master_write_slave(uint8_t u8Cmd, uint8_t *data_wr, size_t size)
{
/* 创建一个新的 I2C 命令链 cmd,用于存储本次写操作的 I2C 指令 */
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);//启动 I2C 通信,生成起始信号
i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN);//发送从设备地址并指示写操作,启用从设备应答信号检查
i2c_master_write_byte(cmd, u8Cmd, ACK_CHECK_EN);//将 u8Cmd(命令字节)发送到从设备,启用 ACK 检查
i2c_master_write(cmd, data_wr, size, ACK_CHECK_EN);//data_wr 指向的数据(长度为 size)发送给从设备,启用 ACK 检查
i2c_master_stop(cmd);//生成停止信号,传输结束
/* 执行命令链中所有命令,并在 1000 毫秒内等待操作完成 */
esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);//删除命令链,释放与之相关的资源
/* 错误检查 */
if (ret != ESP_OK) {
printf("i2c_master_write_slave error\n");
}
return ret;
}
2.1.5 初始化 I2C 主设备(Master)
配置 I2C 的参数,包括数据线和时钟线的 GPIO 引脚、上拉电阻以及 I2C 的通信频率。初始化完成后,将 I2C 驱动程序安装到指定的 I2C 主设备端口。
/**
* @brief i2c master initialization
*/
esp_err_t i2c_master_init(void)
{
int i2c_master_port = I2C_MASTER_NUM;// I2C 主设备的端口号
i2c_config_t conf;//定义并初始化 i2c_config_t 类型的结构体变量 conf
conf.mode = I2C_MODE_MASTER;//该设备作为 I2C 主设备工作
conf.sda_io_num = I2C_MASTER_SDA_IO;//SDA(数据线)的 GPIO 引脚编号
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;//启用 SDA 引脚的内部上拉电阻,以确保信号稳定
conf.scl_io_num = I2C_MASTER_SCL_IO;//设置 SCL(时钟线)的 GPIO 引脚编号
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;//启用 SCL 引脚的内部上拉电阻
conf.master.clk_speed = I2C_MASTER_FREQ_HZ;//设置 I2C 的时钟频率
/* 将配置参数 conf 应用到指定的 I2C 主设备端口 i2c_master_port */
i2c_param_config(i2c_master_port, &conf);
return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);//安装 I2C 驱动程序
}
i2c_driver_install函数用于将 I2C 驱动程序安装到指定的端口i2c_master_port: 要安装驱动的 I2C 端口。conf.mode: 工作模式(主设备模式)。I2C_MASTER_RX_BUF_DISABLE和I2C_MASTER_TX_BUF_DISABLE: 接收和发送缓冲区的大小设为 0,表示禁用缓冲区。0: 中断优先级设置为默认值。
2.2 app_main.c文件
2.2.1 包含头文件及宏定义
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "esp_log.h"
#include "ds_timer.h"
#include "ds_spiffs.h"
#include "ds_system_data.h"
#include "ds_nvs.h"
#include "ds_ft6336.h"
#define CHIP_NAME "ESP32"
static const char *TAG = "MAIN APP";
2.2.2 定义一个任务函数
static void test_task_example(void* arg)
{
for(;;) {
vTaskDelay(1000 / portTICK_PERIOD_MS);//延时 1 秒
printf("task run \n");
}
}
2.2.3 主函数
app_main 函数负责初始化系统和一些设备组件,并启动一个 FreeRTOS 任务。
void app_main(void)
{
printf("Hello world!\n");
/* 打印芯片信息 */
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
printf("This is %s chip with %d CPU core(s), WiFi%s%s, ",
CHIP_NAME,
chip_info.cores,
(chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
(chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
printf("silicon revision %d, ", chip_info.revision);
printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024),
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());
ESP_LOGI(TAG, "system init V1.1");
ds_timer_init();//定时器初始化
/* SPIFFS 文件系统初始化 */
init_spiffs();//初始化 SPIFFS,它允许在外部闪存中存储文件数据
ds_spiffs_test();//执行 SPIFFS 测试操作,检查文件系统是否可用
ds_spiffs_deinit();//释放 SPIFFS 资源并关闭文件系统。
/* WiFi 配置与存储 */
char *ssid="leo";
char *psw="123456789";
set_system_data_wifi_info(ssid,strlen(ssid),psw,strlen(psw));//设置 WiFi 的 SSID 和密码
ds_nvs_init();//初始化 NVS,用于存储 WiFi 配置信息。
ds_nvs_save_wifi_info();//保存 WiFi 信息到 NVS 中
ds_nvs_read_wifi_info();//读取并打印已保存的 WiFi 信息
/* 触摸屏控制器初始化 */
TP_POSITION_T position;//定义一个结构体 position 来保存触摸屏位置数据
init_ft6336();//初始化触摸屏控制器(FT6336),使触摸屏可以检测触摸事件。
/* 创建任务 */
xTaskCreate(test_task_example, "test_task_example", 2048, NULL, 10, NULL);
while(1){
// printf("system run ...\n");
get_ft6336_touch_sta(&position);//检查触摸屏的状态,并将触摸点位置存储在 position 中
vTaskDelay(10 / portTICK_PERIOD_MS);//延迟 10 毫秒,以降低 CPU 使用率,防止循环过于频繁
}
}
2.3 ds_ft6336.h文件
FT6236 是一款由 FocalTech 公司生产的电容式触摸屏控制芯片,广泛应用于智能手机、平板电脑、智能穿戴设备等设备的触摸屏模块中。它能够实现多点触控,并支持手势识别。
#ifndef _DS_FT6336_H_
#define _DS_FT6336_H_
#include <stdio.h>
#include <stdbool.h>
#define GPIO_RST_LOW 0 //复位引脚低电平
#define GPIO_RST_HIGHT 1 //复位引脚高电平
#define TP_PRES_DOWN 0x80 //触屏被按下
#define TP_COORD_UD 0x40 //触摸坐标更新标记
//FT6236 部分寄存器定义
#define FT_DEVIDE_MODE 0x00 //FT6236模式控制寄存器
#define FT_REG_NUM_FINGER 0x02 //触摸状态寄存器
#define FT_TP1_REG 0X03 //第一个触摸点数据地址
#define FT_TP2_REG 0X09 //第二个触摸点数据地址
#define FT_TP3_REG 0X0F //第三个触摸点数据地址
#define FT_TP4_REG 0X15 //第四个触摸点数据地址
#define FT_TP5_REG 0X1B //第五个触摸点数据地址
#define FT_ID_G_LIB_VERSION 0xA1 //版本
#define FT_ID_G_MODE 0xA4 //FT6236中断模式控制寄存器
#define FT_ID_G_THGROUP 0x80 //触摸有效值设置寄存器
#define FT_ID_G_PERIODACTIVE 0x88 //激活状态周期设置寄存器
#define Chip_Vendor_ID 0xA3 //芯片ID(0x36)
#define ID_G_FT6236ID 0xA8 //0x11
typedef struct
{
uint8_t status;
uint16_t x;
uint16_t y;
}TP_POSITION_T;
//触摸点相关数据结构体定义
typedef struct
{
//bit7:按下1/松开0;
//bit6:0没有按键按下/1有按键按下;
//bit5:保留;
//bit4-bit0触摸点按下有效标志,有效为1,分别对应触摸点5-1;
uint8_t touch_sta; //触摸情况,
uint8_t touch_count; //触摸点数
uint16_t x[5]; //最多支持5点触摸,需要使用5组坐标存储触摸点数据
uint16_t y[5];
bool update;
}TouchPoint_T;
void get_ft6336_touch_sta(TP_POSITION_T *position);
void init_ft6336(void);
#endif
3. 编译工程
在终端中输入
idf.py fullclean
idf.py clean
idf.py build
如果出现idf.py: command not found需要重新安装编译链,然后设置环境
cd ~/esp/esp-idf
export IDF_GITHUB_ASSETS="dl.espressif.com/github_assets"
./install.sh
. /home/xzh/esp/esp-idf/export.sh
回到项目路径
cd ~/esp/DesktopScreenV4.0.3
再次在终端中输入
idf.py fullclean
idf.py clean
idf.py build
4. 下载运行
分支dev7文件 ~/esp/DesktopScreenV4.0.3/build/config/sdkconfig.h 中显示 SCL 和 SDA 对应的 IO 口是 16 和 17
#define CONFIG_I2C_MASTER_SCL 16
#define CONFIG_I2C_MASTER_SDA 17
与原理图中的 32 和 33 不符,需要修改

这个文件无法直接修改,因为这个文件是由编译过程自动生成的,会在每次编译时被覆盖。因此,手动修改这些文件后,下一次编译可能会重写更改。所以要在终端中使用命令来配置选项
idf.py menuconfig
找到DesktopScreen Comfiguration ---> I2C Master 修改SCL和SDA的GPIO编号。原来是 16 和 17,应按照原理图改为 32 和 33


下载运行
idf.py -p /dev/ttyUSB0 flash monitor
运行成功后终端中会显示:

前三行打印的是初始化触摸屏时,某些配置寄存器的初始值:
-
init THGROUP = 22:THGROUP是一个阈值配置,通常表示触摸屏的“群组阈值”(group threshold),该值影响屏幕对触摸压力或强度的敏感度。值为22表示当前寄存器的值,数值越高,触摸屏可能需要更强的触摸信号才能识别触摸事件。 -
init PERIODACTIVE = 14:PERIODACTIVE通常表示在屏幕检测到触摸输入时,触摸控制器对触摸信号采样的频率。值为14表示一个具体的采样间隔或频率值;数值较低时采样频率可能更高,屏幕响应也更灵敏。 -
init G_MODE = 0:G_MODE通常用来配置触摸控制器的中断模式或工作模式。值为0表示当前配置为“中断模式”,即触摸屏在检测到触摸事件时会触发中断,允许系统及时响应。
当点击触摸屏时,会打印出触摸点数和触摸坐标。

浙公网安备 33010602011771号