采用I2C驱动触摸屏。
I2C多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。
它有两条线,一条是SCL(串行时钟总线),另外一条是SDA(串行数据线),这两条数据需要接上拉电阻,总线空闲的时候SCL和SDA处于高电平。
图来自正点原子linux驱动开发教程
I2C主要有起始位、停止位、数据传输、应答信号等。
I2C写时序
I2C的写时序相较于读时序是比较简单的,大概分以下几个步骤。
- 开始信号
- 发送 I2C设备地址,其中高七位是设备地址,最后一位是读写地址。
- 从机发送应答信号
- 重新发送开始信号
- 发送要写入数据的寄存器地址
- 从机发送应答信号
- 发送要写入寄存器的数据
- 从机发送应答信号
- 停止信号
图来自正点原子linux驱动开发教程
I2C读时序
读时序相对于写时序来说复杂了一点,结束时多了一个非应答信号,以及写入寄存器地址之后要重新发从机的地址。
总体的分为四步,跟写时序差不多。
- 发送设备地址
- 发送要读取的寄存器地址
- 重新发送设备地址
- 读取数据
图来自正点原子linux驱动开发教程
OK,了解了基本的之后,回到驱动触摸屏上来。
我们驱动一个触摸屏需要使用到TP,这里使用的是FT6236。
查询芯片手册,可以发现FT6236有四根线。
两根是I2C需要的线,还有两根分别是INT(输入),RSTN(输出)。
到这里,就很简洁明了了。
我们需要用到GPIO,I2C,以及TP的驱动。
首先,将RST和INT利用GPIO进行初始化,并进行相应的一些设置。、
GPIO
GPIO的使用已经很熟悉了,这里就不多说了。
简单的来说就是定义硬件图所使用的IO口,然后利用寄存器进行相应的初始化,实现我们的功能。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "ds_gpio.h"
#include "ds_system_data_da.h"
//定义IO口,查硬件图
#define GPIO_OUTPUT_IO_0 5
#define GPIO_OUTPUT_PIN_SEL ((1ULL<<GPIO_OUTPUT_IO_0))
#define GPIO_INTPUT_IO_0 4
#define GPIO_INTPUT_PIN_SEL ((1ULL<<GPIO_INTPUT_IO_0))
#define ESP_INTR_FLAG_DEFAULT 0
static xQueueHandle gpio_evt_queue = NULL;
static void IRAM_ATTR gpio_isr_handler(void *arg){
uint32_t gpio_num = (uint32_t)arg;//强转
xQueueSendFromISR(gpio_evt_queue,&gpio_num,NULL);
}
//触摸屏GPIO口初始化
void ds_touch_gpio_init(void){
gpio_config_t io_conf;
//rst不需要中断,关闭中断
io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
//设置模式为输出
io_conf.mode = GPIO_MODE_OUTPUT;
//设置引脚5
io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
//设置上拉和下拉
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
//配置GPIO
gpio_config(&io_conf);
//无论上升沿还是下降沿都触发
io_conf.intr_type = GPIO_INTR_ANYEDGE;
//模式为输入,根据手册判定的
io_conf.mode = GPIO_MODE_INPUT;
//设置为引脚4
io_conf.pin_bit_mask = GPIO_INTPUT_PIN_SEL;
//设置为上拉,默认低电平有效
io_conf.pull_up_en = 1;
//配置GPIO
gpio_config(&io_conf);
//创建处理gpio事件的消息队列
gpio_evt_queue = xQueueCreate(10,sizeof(uint32_t));
//安装gpio中断服务
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
gpio_isr_handler_add(GPIO_INTPUT_IO_0,gpio_isr_handler,(void*)GPIO_INTPUT_IO_0);
}
//复位
void ds_gpio_set_touch_rst(uint32_t level){
gpio_set_level(GPIO_OUTPUT_IO_0,level);
}
GPIO比较简单。
I2C程序编写
I2C的程序编写尽量要参考一下ESP32官方给出的手册,包括读写时序的编写,避免出错。
相关API可以在官方文档里看。
这是官方例程里带寄存器读的例子,可以看到是先设置寄存器的地址,然后再重新写入从机地址的,接下来就可以模仿这个开始写。
首先设置读取地址。
//设置读取地址
static esp_err_t i2c_master_set_addr(uint8_t u8Cmd){
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
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);//设置读取寄存器地址
i2c_master_stop(cmd);
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_set_addr error\n");
}
return ret;
}
然后进行读时序。
//读取数据
esp_err_t i2c_master_read_slave(uint8_t u8Cmd, uint8_t *data_rd, size_t size){
if(size == 0){
return ESP_OK;
}
i2c_master_set_addr(u8Cmd);//设置要读取的寄存器地址
vTaskDelay(30 / portTICK_RATE_MS);//延时24ms
//然后再次写入从机地址,并加上独标志位
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd,(ESP_SLAVE_ADDR<<1)|READ_BIT,ACK_CHECK_EN);
for(int index = 0;index<(size-1);index++){
i2c_master_read_byte(cmd,data_rd+index,ACK_VAL);
}
i2c_master_read_byte(cmd,data_rd+size-1,NACK_VAL);//发送非应答信号
i2c_master_stop(cmd);
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;
}
写时序就比较简单了。
//写入数据,不需要发非应答信号
esp_err_t i2c_master_write_slave(uint8_t u8Cmd, uint8_t *data_wr, size_t size){
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
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);
i2c_master_write(cmd, data_wr, size, ACK_CHECK_EN);
i2c_master_stop(cmd);
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;
}
在进行这些之前,还需要初始化一下我们的I2C。
这就涉及到一些宏定义,可以采用KConfig进行定义,可以直接写值,但是这里还是建议用Kconfig进行定义。
初始化的代码直接参考例程,几乎不怎么需要修改,只需要把SCL和SDA的引脚重新定义一下,适合我们的开发板就行。
//初始化
esp_err_t i2c_master_init(void)
{
int i2c_master_port = I2C_MASTER_NUM;
i2c_config_t conf;
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = I2C_MASTER_SDA_IO;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_io_num = I2C_MASTER_SCL_IO;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
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就已经可以了。
然后开始写最重要的一步,TP的驱动,这个是需要我们自己查芯片手册,然后一步步编写的。
TP驱动
观察芯片手册。
圈出来的这里,就是我们用I2C写入或者读取的寄存器了。
在头文件里进行定义。
//由FT6236芯片手册查询得到各部分寄存器地址
#define FI_DEVIDE_MODE 0x00 //FT6236模式控制寄存器
#define FI_REG_NUM_FINGER 0x02 //触摸状态寄存器
#define FI_TP1_REG 0x03 //第一个触摸点数据地址
#define FI_TP2_REG 0x09 //第一个触摸点数据地址
#define FI_TP3_REG 0x0F //第一个触摸点数据地址
#define FI_TP4_REG 0x15 //第一个触摸点数据地址
#define FI_TP5_REG 0x1B //第一个触摸点数据地址
#define FI_ID_G_LIB_VERSION 0xA1 //版本
#define FI_ID_G_MODE 0xA4 //FT6236中断模式控制寄存器
#define FI_ID_G_THGROUP 0x80 //触摸有效值设置寄存器
#define FI_ID_G_PERIODACTIVE 0x88 //激活状态周期设置寄存器
#define Chip_Vendor_ID 0xA3 //芯片ID(0x36)
#define ID_G_FT6236ID 0xA8 //0x11
然后定义触摸点的结构体。
//触摸点相关数据结构定义
typedef struct
{
//bit7:按下1/松开0
//bit6: 没有按键按下0/有按键按下1
//bit5:保留
//bit4-bit0:触摸点按下有效标志,有效为1,对应五个触摸点
uint8_t touch_sta; //触摸点的情况
uint8_t touch_count; //触摸点数
uint16_t x[5];
uint16_t y[5];
bool updata;
}TouchPoint_T;
定义一下触摸屏被按下或松开的标记。
#define TP_PRESS_DOWN 0x80 //触摸屏被按下,0x10000000,第七位为1
#define TP_COORD_UD 0x40 //触摸屏坐标更新,第六位为1
其他的就看自己的用途定义了,基本就是这些了。
然后就可以开始编写驱动了,主要参考ESP里的相关例程,以及FT6236的一些源码。
#include <string.h>
#include <stdio.h>
#include "ds_tp.h"
#include "ds_i2c.h"
#include "ds_gpio.h"
#include "ds_system_data_da.h"
//触摸芯片最大5组触摸点,FT6236最大支持双触
const uint16_t FT6236_TPX_TBL[5]=
{
FI_TP1_REG,
FI_TP2_REG,
FI_TP3_REG,
FI_TP4_REG,
FI_TP5_REG
};
TouchPoint_T gTPS;
//扫描触摸屏寄存器状态、数据
static void scan_ft6236(void)
{
uint8_t i=0;
uint8_t sta = 0;//触摸点状态
uint8_t buf[4] = {0};//这里是获取四个字节,分别是xH、xL、yH、yL
uint8_t gestid = 0;//手势
i2c_master_read_slave(0x02,&sta,1);//读取寄存器状态,读取的是个数!
gTPS.touch_count = sta;
i2c_master_read_slave(0x01,&gestid,1);//读取触摸点的状态
if(sta&0x0f)//判断有无触摸点按下
{
gTPS.touch_sta = ~(0xFF << (sta & 0x0F));//将有效触摸点的个数转换为对应的标记
for (i = 0; i < 2; i++)//最多同时两个触摸点
{
if (gTPS.touch_sta & (1 << i))
{
i2c_master_read_slave(FT6236_TPX_TBL[i], buf, 4); // 读取触摸点坐标
gTPS.x[i] = (uint16_t)(((buf[0]&0x0F)<<8)+buf[1]);//清空XH的高四位,并左移8位与XL组成坐标
gTPS.y[i] = (uint16_t)(((buf[2]&0x0F)<<8)+buf[3]);
}
}
gTPS.touch_sta |= TP_PRESS_DOWN; //按下标记,置1
}
else //如果判断无触摸点按下,那么检查一下之前的标记
{
if(gTPS.touch_sta & TP_PRESS_DOWN)//如果之前被按下了
{
gTPS.touch_sta &= ~0x80; //清楚按下标记
}
else//
{
gTPS.x[0]=0;
gTPS.y[0]=0;
gTPS.touch_sta &= 0xe0;//将后五位清0,这一块还是有点疑虑
}
}
}
//转换为实际位置
static void count_position_ft6236(TP_POSITION_T *position){
switch (gTPS.touch_count)
{
case 1:
if ((gTPS.x[0] != 0) && (gTPS.y[0] != 0)
&& (gTPS.x[0] < 200) && (gTPS.y[0] < 200))
{
position->x = gTPS.x[0];
position->y = gTPS.y[0];
printf("触摸点的个数=%d\r\n", gTPS.touch_count);
printf("x0:%d,y0:%d\r\n", gTPS.x[0], gTPS.y[0]);
return;
}
break;
case 2:
if ((gTPS.x[0] != 0) && (gTPS.y[0] != 0)
&& (gTPS.x[0] < 200) && (gTPS.y[0] < 200)
&& (gTPS.x[0] < 200) && (gTPS.y[0] < 200)
&& (gTPS.x[1] < 200) && (gTPS.y[1] < 200))
{
printf("触摸点个数::%d\r\n", gTPS.touch_count); // FT6336U最多支持两点触控
printf("x0:%d,y0:%d\r\n", gTPS.x[0], gTPS.y[0]);
printf("x1:%d,y1:%d\r\n", gTPS.x[1], gTPS.y[1]);
}
break;
default:
break;
}
for (int i = 0; i < 2; i++)
{
gTPS.x[i] = 0;
gTPS.y[i] = 0;
}
position->status = 0;
position->x = gTPS.x[0];
position->y = gTPS.y[0];
}
void get_ft6236_touch_sta(TP_POSITION_T *position){
scan_ft6236();
count_position_ft6236(position);
}
void init_ft6236(void){
uint8_t w_data,r_data = 0;
memset(&gTPS,0,sizeof(TouchPoint_T));//清0
//GPIO初始化,INT中断和复位引脚
ds_touch_gpio_init();
//复位初始化,拉低
ds_gpio_set_touch_rst(GPIO_RST_LOW);
vTaskDelay(50 / portTICK_PERIOD_MS);
ds_gpio_set_touch_rst(GPIO_RST_HIGH);
vTaskDelay(100 / portTICK_PERIOD_MS);
//I2C初始化
i2c_master_init();
vTaskDelay(100 / portTICK_PERIOD_MS);
w_data = 0;
//设置正常操作模式
i2c_master_write_slave(FI_DEVIDE_MODE,&w_data,1);
w_data = 22;
//设置触摸有效值22,越小越灵敏
i2c_master_write_slave(FI_ID_G_THGROUP,&w_data,1);
i2c_master_read_slave(FI_ID_G_THGROUP,&r_data,1);
printf("init THGROUP = %d \n",r_data);
//设置激活周期 不能小于12 最大14
i2c_master_write_slave(FI_ID_G_PERIODACTIVE,&w_data,1);
i2c_master_read_slave(FI_ID_G_PERIODACTIVE,&r_data,1);
printf("init PERIODACTIVE = %d \n",r_data);
w_data = 0;
//中断产生方式 持续电平
i2c_master_write_slave(FI_ID_G_MODE,&w_data,1);
i2c_master_read_slave(FI_ID_G_MODE,&r_data,1);
printf("init G_MODE = %d \n",r_data);
}
结合芯片手册去看,更能够理解,包括细节性的东西我都在代码里注释了,也是给自己留一个记录。
实验
OK,这些都写好了之后,在主函数里面进行调用。
编译一下,成功之后上开发板进行测试!
可以看到,实验是成功了!
触摸屏初级的使用就已经到这里完成了。
虽然写的不多,但是花的时间是真的多啊。文章来源:https://www.toymoban.com/news/detail-474760.html
以上参考正点原子linux驱动开发教程、FT6x36芯片手册,野火STM32库文件开发教程等...文章来源地址https://www.toymoban.com/news/detail-474760.html
到了这里,关于ESP32开发---驱动触摸屏的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!