基于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是半双工,而不是全双工 ,同一时间只可以单向通信

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

下图是I2C的时序图
img
开始信号:SCL为高电平,SDA拉低
结束信号:SCL为高电平,SDA拉高

开始和结束信号之间是地址、读/写标志位、应答符号ACK

主机收到应答信号之后才会继续发信号,如果收不到就超时停止。

实际波形图
img

2. 源码

项目位置:.../DesktopScreenV4.0.3/main/src/hal/ds_i2c.c
切换到dev7

git checkout dev7

img

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_BIT
    • ESP_SLAVE_ADDR << 1
      • ESP_SLAVE_ADDR 是 I2C 从设备的 7 位地址。在 I2C 协议中,地址的高 7 位用于表示从设备地址,而第 8 位(最低位)表示操作的方向(读/写)。
      • << 1 操作是将 ESP_SLAVE_ADDR 左移 1 位。这是为了为最低位(第 8 位)预留出空间,以表示数据传输方向。
      • 例如,如果 ESP_SLAVE_ADDR 的值为 0x3C(二进制 0111100),则左移一位后变为 0x78(二进制 1111000)。
    • | WRITE_BIT
      • WRITE_BIT 是一个常量,通常定义为 01,用于指示写或读操作。
      • 在 I2C 协议中,第 8 位(最低位)为 0 表示写操作,为 1 表示读操作。
      • WRITE_BIT(ESP_SLAVE_ADDR << 1) 通过按位或(|)运算符组合,得到完整的 8 位地址字节。
      • 如果 WRITE_BIT0,表示写操作;如果 WRITE_BIT1,表示读操作。
  • 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_DISABLEI2C_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 不符,需要修改
img

这个文件无法直接修改,因为这个文件是由编译过程自动生成的,会在每次编译时被覆盖。因此,手动修改这些文件后,下一次编译可能会重写更改。所以要在终端中使用命令来配置选项

idf.py menuconfig

找到DesktopScreen Comfiguration ---> I2C Master 修改SCL和SDA的GPIO编号。原来是 16 和 17,应按照原理图改为 32 和 33

img

img

下载运行

idf.py -p /dev/ttyUSB0 flash monitor

运行成功后终端中会显示:
img

前三行打印的是初始化触摸屏时,某些配置寄存器的初始值:

  • init THGROUP = 22THGROUP 是一个阈值配置,通常表示触摸屏的“群组阈值”(group threshold),该值影响屏幕对触摸压力或强度的敏感度。值为 22 表示当前寄存器的值,数值越高,触摸屏可能需要更强的触摸信号才能识别触摸事件。

  • init PERIODACTIVE = 14PERIODACTIVE 通常表示在屏幕检测到触摸输入时,触摸控制器对触摸信号采样的频率。值为 14 表示一个具体的采样间隔或频率值;数值较低时采样频率可能更高,屏幕响应也更灵敏。

  • init G_MODE = 0G_MODE 通常用来配置触摸控制器的中断模式或工作模式。值为 0 表示当前配置为“中断模式”,即触摸屏在检测到触摸事件时会触发中断,允许系统及时响应。

当点击触摸屏时,会打印出触摸点数和触摸坐标。

推荐链接

posted @ 2025-11-19 09:45  茴香豆的茴  阅读(140)  评论(0)    收藏  举报