基于GT911触控IC的电容屏在MSP430上的驱动

news2024/11/20 13:41:07

背景

最近参加公司一个电池测试仪的项目,负责电容屏驱动开发,电容屏的触控IC是汇顶科技的GT911,电容屏的总线接口是I2C
因为项目沟通方面的失误,本应接到主控芯片的电容屏,被连到了MSP430这款负责供电管理的MCU,领导说等PCB改版太慢了,让我就在MSP430上开发驱动,于是电容屏接入系统的方式就变成了这样:
电容屏连接到系统的方式
本来写一份驱动就行,现在得MSP430和主机各写一部分,而且两边还要加一些SPI总线处理。

MSP430的I2C驱动调试

首先要打通MSP430跟电容屏之间的I2C通信。

I2C控制器的初始化

分配I2C控制器对应的GPIO管脚

需要配置PxSEL0和PxSEL1寄存器,二者的相同位置的一对bit可以确定一个GPIO管脚是复用成4种功能的哪一种,我们根据datasheet选择合适的值,就能将其配置成I2C的SCL,同理SDA要用另外一对bit
根据PxSEL配置管脚功能

配置I2C工作模式和波特率

这需要I2C控制器在复位状态下才能进行,因此需要先设置UCBxCTLW0的UCSWRST位,配置完毕后再清零相应bit位。

工作模式需要先设置UCBxCTLW0的UCMODE_3位,表示该USCI_B模块工作在I2C模式下,再设置UCMST位,表示I2C扮演Master角色。

波特率需要先设置UCBxCTLW0的UCSSEL__SMCLK位,表示时钟源选择SMCLK,再将UCB1BRW寄存器的值配成20,表示将SMCLK(我的是8M)分频20倍,就得到了400K,是GT911能支持的最高时钟频率。

配置I2C接收完成、发送完成、收发NACK中断

因为承载I2C功能的是USCI_B模块,因此中断向量要这样写

#pragma vector = USCI_B1_VECTOR
__interrupt void USCI_B1_ISR(void)
{
	switch(__even_in_range(UCB1IV, USCI_I2C_UCBIT9IFG))
	{
			case USCI_NONE:          		break;         // Vector 0: No interrupts
		case USCI_I2C_UCALIFG:   	break;         // Vector 2: ALIFG
		case USCI_I2C_UCNACKIFG:                	   // Vector 4: NACKIFG
			// NACK中断的处理
			UCB1CTLW0 |= UCTXSTP;           // pull up SCL
			break;
		case USCI_I2C_UCSTTIFG:  	break;         // Vector 6: STTIFG
		case USCI_I2C_UCSTPIFG:  	break;         // Vector 8: STPIFG
		case USCI_I2C_UCRXIFG3:  	break;         // Vector 10: RXIFG3
		case USCI_I2C_UCTXIFG3:  	break;         // Vector 12: TXIFG3
		case USCI_I2C_UCRXIFG2:  	break;         // Vector 14: RXIFG2
		case USCI_I2C_UCTXIFG2:  	break;         // Vector 16: TXIFG2
		case USCI_I2C_UCRXIFG1:  	break;         // Vector 18: RXIFG1
		case USCI_I2C_UCTXIFG1:  	break;         // Vector 20: TXIFG1
		case USCI_I2C_UCRXIFG0:                 	// Vector 22: RXIFG0
				// 接收完成中断的处理
				break;
		
		case USCI_I2C_UCTXIFG0:  				   // Vector 24: TXIFG0
				// 发送完成中断的处理
				break;
		case USCI_I2C_UCBCNTIFG:    break;    		// Vector 26: BCNTIFG
		case USCI_I2C_UCCLTOIFG: 	break;         // Vector 28: clock low timeout
		case USCI_I2C_UCBIT9IFG: 	break;         // Vector 30: 9th bit
		default: 					break;
	}
}

注意,NACK中断一定要使能,且在中断里发送stop condition,这会让SCL信号重新拉高,不这样做的话,一旦I2C地址输错,就会出现很诡异的现象:SCL一直为低。

其他两个中断在下面讲。

I2C控制器的发送、接收流程

I2C发送流程

主循环的处理流程
  1. 记录tx_buf、tx_buf_len、tx_buf_offset到全局变量
  2. 将slave的I2C地址写入UCBxI2CSA寄存器
  3. 设置UCBxCTLW0的UCTR位,表示自己扮演Transmitter,同时设置UCTXSTT位,在线路上生成start condition
  4. 等待tx_buf_offset == tx_buf_len
发送完成中断的处理流程

第一次I2C发送完成中断貌似是由I2C控制器发送完slave地址后触发的,后面的中断都是buf内第tx_buf_offset个字节发送完毕触发,不过中断里面的流程是一样的:

  1. tx_buf_offset是否等于tx_buf_len
  2. 是,说明buf发送完毕,设置UCBxCTLW0的UCTXSTP,发送stop condition,清零UCBxIFG寄存器的UCTXIFG位,主循环稍后会退出
  3. 否,说明buf还未发完,将第tx_buf_offset个字节送入UCBxTXBUF寄存器,该寄存器的内容会被发送到线路,然后tx_buf_offset++

I2C接收流程

  1. 记录rx_buf、rx_buf_len、rx_buf_offset到全局变量
  2. 将slave的I2C地址写入UCBxI2CSA寄存器
  3. 清零UCBxCTLW0的UCTR位,表示自己扮演Receiver,同时设置UCTXSTT位,在线路上生成start condition
  4. 等待rx_buf_offset == rx_buf_len
接收完成中断的处理流程

I2C控制器扮演receiver角色时,发送完slave地址貌似不会进入发送完成中断,当然也不会进入接收完成中断,所以就简单一些:

  1. rx_buf_offset是否等于rx_buf_len
  2. 是,说明buf接收完毕,设置UCBxCTLW0的UCTXSTP,发送stop condition,将UCBxRXBUF的内容存入rx_buf[rx_buf_offset],主循环稍后会退出
  3. 否,说明buf还未收完,将将UCBxRXBUF的内容存入rx_buf[rx_buf_offset],然后rx_buf_offset++

电容屏的MSP430驱动调试

电容屏的初始化

分配I2C之外的RST和INT管脚

I2C只是数据传输通道,但电容屏还需要RST(复位)管脚来获悉什么时候复位其内部状态,还需要INT(中断)管脚来主动告知MSP430触摸事件的发生,快来通过I2C读取触摸坐标吧

RST是输出管脚,且GT911要求默认为高电平,因此要先设置PxDIR的特定bit表示输出,再设置PxOUT的相应bit表示输出高电平。

INT按理说是输入管脚,但GT911还用它来配置slave地址,因此要先配置成输出,在复位一段约定的时间后,输出一个高/低的电平,让GT911知道自己的I2C地址是0x14还是0x5D,地址配好之后,再将该管脚配置成输入。

电容屏的复位流程

  1. 执行上面的管脚分配和初始化流程
  2. 延迟5ms后,拉低RST管脚,复位GT911
  3. 延迟20ms后,让INT管脚输出低电平,GT911从而知道自己的I2C地址是0x5D
  4. 延迟1ms后,拉高RST管脚
  5. 延迟10ms后,将RST管脚配置成输入,确保其不会无意中复位GT911
  6. 让INT管脚输出低电平
  7. 延迟50ms后,将INT管脚配置成输入

电容屏的初始化流程

电容屏的固件初始化
  1. 延迟5ms后,读取0x8140(CMD)寄存器获取GT911的chip PID
  2. 向CMD寄存器写入0x02复位GT911的固件
  3. 从0x8047(CFG_DATA)寄存器读取GT911的固件版本号
  4. 计算GT911的配置参数的校验和
  5. 将配置参数写到CFG_DATA起始的寄存器空间
  6. 将校验和写入0x80FF(CFG_CHECKSUM)寄存器
  7. 延迟1ms后,向CMD寄存器写入0x0,使得固件退出复位状态,进入坐标读取状态
  8. 向0x814E(COOR_ADDR)寄存器写0,清除上报的坐标信息
电容屏的中断初始化

GT911默认是下降沿触发,且是浮动输入

  1. 设置PxIES的相应bit,表示下降沿触发
  2. 清零PxREN的相应bit,表示浮动输入,不使能上拉or下拉电阻
  3. 设置PxIE相应bit,使能INT管脚的中断

电容屏的坐标读取流程

中断响应

为了最小化中断耗时,只在中断里更新一个触摸事件计数器,并清零中断,其他都在主循环里完成

#pragma vector = PORT2_VECTOR
__interrupt void GPIO_PORT2_ISR(void)
{
	switch(__even_in_range(P2IV, P2IV_P2IFG7))
	{
		case P2IV_NONE:     break;
		case P2IV_P2IFG0:   break;
		case P2IV_P2IFG1:	break;
		case P2IV_P2IFG2:  	break;
		case P2IV_P2IFG3:
			g_touch_event_cnt++;
			P2IFG &= ~TOUCH_INT;
			break;
		case P2IV_P2IFG4:  	break;
		case P2IV_P2IFG5:  	break;
		case P2IV_P2IFG6:  	break;
		case P2IV_P2IFG7:  	break;
		default:            break;
	}
}

坐标读取流程

  1. 检测当前触摸事件技术,为0则立即退出
  2. 读取COOR_ADDR寄存器,获得当前的触屏是否有坐标数据,有几个手指的坐标数据
  3. 如果COOR_ADDR寄存器的bit7为0,说明没数据,立即向COOR_ADDR写0,使得GT911恢复工作
  4. 再检查COOR_ADDR的bit[3:0],为0说明所有手指离开,如果不为0,说明有手指触摸
  5. 读取从0x814F(COOR_DATA)寄存器开始的8*N个手指的坐标数据
  6. 向COOR_ADDR写0,使得GT911恢复工作

8字节坐标数据的格式:

/* 读取触摸点坐标数据,从0x814F寄存器开始读取
 * 其中每一个触摸点使用8个寄存器来描述
 * 以第一个触摸点为例,各寄存器描述信息如下:
 * 0x814F: 触摸点id
 * 0x8150: 触摸点X轴坐标低位字节
 * 0x8151: 触摸点X轴坐标高位字节
 * 0x8152: 触摸点Y轴坐标低位字节
 * 0x8153: 触摸点Y轴坐标高位字节
 * 0x8154~0x8155: 触摸点的大小信息,我们不需要
 * 0x8156: 保留
 */

主机侧的Linux接收流程

这块交给外包做了,我只做了些指导,略。

电容屏的数据上报到主机侧

使用SPI上报,因为之前的SPI口仅用于供电管理,因为可扩展性非常差,为了复用SPI通道及其通信代码,对相关数据结构和收发函数做了优化:

SPI数据结构的扩展

typedef struct
{
	uint8_t	cmd;			/*命令码控制字*/
	union {
		struct {
			uint8_t	dc:1;			/*适配器在位状态*/
			uint8_t	bat:	1;			/*电池在位状态*/
			uint8_t 	full: 1;			/*电池电量满状态*/
			uint8_t 	ad: 1;			/*电池电量数据模式*/
			uint8_t 	reserve_4: 1;		/*保留位4*/
			uint8_t 	reserve_5: 1;		/*保留位5*/
			uint8_t 	reserve_6: 1;		/*保留位6*/
			uint8_t 	pd: 1;     			/*操作电源*/
			uint8_t	battery_data;		/*电池电量数据*/
		};
		struct {
			uint8_t	state;
			uint8_t	x_l;
			uint8_t	x_h;
			uint8_t y_l;
			uint8_t y_h;
			uint8_t	rsv1;
			uint8_t	rsv2;
		};
		uint8_t data[7];
	};
} spi_pack;

为了最小化扩展带来的代码改动,用到了匿名联合体匿名结构体,这个在我之前的文章中有描述。

供电管理的SPI上报流程

uint8_t  power_report(uint8_t opt)
{
	spi_pack power_pack = {0};

	power_pack.cmd = SPI_CMD_WRITE;
	if(opt == POWER_DOWN)
	{
		power_pack.pd = POWER_DOWN;
	} else {
        power_pack.pd = POWER_NORMAL;
    }
    DEBUG("spi-0:0x%02x, 1:0x%02x, 2:0x%02x\r\n",power_pack.cmd,power_pack.data[0],power_pack.data[1]);
    spi_sent(&power_pack, sizeof(power_pack));
}

触摸坐标的SPI上报流程

static int touch_report(uint8_t state, uint16_t x, uint16_t y)
{
	spi_pack touch_pack = {0};

	touch_pack.cmd = SPI_CMD_TOUCH;
	touch_pack.state = state;
	touch_pack.x_l = x & 0xff;
	touch_pack.x_h = (x >> 8) & 0xff;
	touch_pack.y_l = y & 0xff;
	touch_pack.y_h = (y >> 8) & 0xff;
	DEBUG("spi-touch: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\r\n",
          touch_pack.cmd, touch_pack.data[0], touch_pack.data[1],
          touch_pack.data[2], touch_pack.data[3], touch_pack.data[4]);
	spi_sent(&touch_pack, sizeof(touch_pack));
    return 0;
}

最终效果

在调通流程后,发现默认配置参数存在坐标x轴和y轴弄反、触摸灵敏度差等问题,反馈给FAE后,更新了一版配置参数,效果好多了。

F581触屏

总结

  1. 裸机开发在功能较多时非常不便,最好先移植个RTOS,磨刀不误砍柴工。
  2. 不要因为可以马上开始就选择步行,下地库取车能更快抵达目的地。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1520910.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day05-SpringBootWeb请求响应

请求响应: 请求(HttpServletRequest):获取请求数据响应(HttpServletResponse):设置响应数据 BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器&#xf…

Linux使用Docker部署Registry结合内网穿透实现公网远程拉取推送镜像

文章目录 1. 部署Docker Registry2. 本地测试推送镜像3. Linux 安装cpolar4. 配置Docker Registry公网访问地址5. 公网远程推送Docker Registry6. 固定Docker Registry公网地址 Docker Registry 本地镜像仓库,简单几步结合cpolar内网穿透工具实现远程pull or push (拉取和推送)…

科普文之五分钟轻松入门Generative AI

1. 引言 最近,生成式人工智能(Generative AI)在行业内带来了巨大的变动。还记得 2022 年 11 月推出的 ChatGPT 吗?在短时间内,它就成为了有史以来用户数量最快突破 1 亿的产品。 人工智能已经存在了很长一段时间&…

MySQL锁整理

MySQL锁信息来源 MySQL锁太多,内容太杂。写篇文章记录一下

3D地图在BI大屏中的应用实践

前言 随着商业智能的不断发展,数据可视化已成为一项重要工具,有助于用户更好地理解数据和分析结果。其中,3D地图作为一种可视化工具,已经在BI大屏中得到了广泛地应用。 3D地图通过将地理信息与数据相结合,以更加直观…

python--函数的基本用法

python--函数的基本用法 定义函数无参无返回值有参无返回值无参有返回值有参有返回值 空函数多个返回值函数的参数必传参数(不传会报错)关键字参数不定长参数*args**kwargs区别 拆包(了解)*用法**用法 定义函数 无参无返回值 def foo():语句有参无返回…

网络安全框架和云安全参考架构介绍

目录 一、网络安全框架 1.1 概述 1.2 IATF框架 1.2.1 框架来源 1.2.2 框架结构图 1.2.3 框架内容 1.2.3.1 人(People) 1.2.3.2 技术(Technology) 1.2.3.3 操作(Operation) 1.3 NIST网络安全框架 …

任何图≌自己这一几何最最起码常识推翻直线公理和平面公理

黄小宁 与x∈R相异(等)的实数均可表为yxδ(增量δ可0也可≠0)。因各实数的绝对值都可是表示长度的数故各实数都可是一维空间“管道”g内点的坐标。于是x∈R变换为实数yxδ的几何意义可是:“管道”g内R轴上的质点x∈R(x…

由浅到深认识C语言(9):动态内存分配

该文章Github地址:https://github.com/AntonyCheng/c-notes 在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址:https://blog.csdn…

前端基础——HTML傻瓜式入门(2)

该文章Github地址:https://github.com/AntonyCheng/html-notes 在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址:https://blog.c…

夏天快到了,车辆改色膜备案流程是什么?

夏天快到了 越来越多的车主想给自己的爱车贴上变色膜。 但也有不少车主对车辆贴膜登记流程并不清楚。 本期电影一号小编就给大家科普一下。 一起来看看吧~ 您可以先更改车身颜色,然后再进行: 现在相关政策发生了变化,允许车主先改变车辆颜…

复杂微纳结构制造需求旺盛 微纳3D打印市场发展前景广阔

复杂微纳结构制造需求旺盛 微纳3D打印市场发展前景广阔 微纳3D打印是一种基于增材原理制造微纳结构的新型微纳加工技术。   3D打印又称为增材制造,是以数字模型为基础,将材料逐层堆积制造出实体物品的制造技术。我国3D打印市场发展动力强劲&#xff0c…

加速渲染:Blender与在线渲染农场的结合

​在数字艺术和三维设计的世界里,Blender软件因其强大的功能和灵活性而广受欢迎。然而,随着项目复杂性的增加,渲染时间也随之增长,成为艺术家和设计师面临的一大挑战。在线渲染农场的出现,为这一问题提供了革命性的解决…

Python Learn day05

Python Learn day05 本文主要讲解 继承、多态、定制类 继承和多态 什么是继承 当新类想要拥有现有类的功能结构,可以使用继承。继承的前提是新类 is a 现有类,即: 子类 is 父类 总是从某个类继承: class Myclass(object):pass…

从单一收入到自由职业:宝哥多元化收入探索之旅

你好,我是宝哥,一个热爱前端开发的自媒体人。 目前是: 前端社群达人 (管理10个前端交流群,连接数千开发者)微博前端大V (拥有20万粉丝,分享最新前端技术和资讯)前端公众号主编 (运营头部前端公众号,影响着数…

算法打卡day18|二叉树篇07|Leetcode 530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先

算法题 Leetcode 530.二叉搜索树的最小绝对差 题目链接:530.二叉搜索树的最小绝对差 大佬视频讲解:二叉搜索树的最小绝对差视频讲解 个人思路 因为是在二叉搜索树求绝对差,而二叉搜索树是有序的,那就把它想成在一个有序数组上求最值&…

P6技巧:导出XER设置老版本/新版本

前言 在一个大型的项目中,虽然业主方已要求承包商必须使用P6格式来提交计划,但实际情况是承包商会给到你多种不同版本的XER文件,使得得在Oracle Primavera P6 之间导入或导出。 如果收到的 XER 文件不适合你使用的 Primavera P6 版本&#x…

使用IDEA2023创建传统的JavaWeb项目并运行与调试

日期:2024-0312 作者:dusuanyun 文档环境说明: OS:Deepin 20.9(Linux) JDK: OpenJDK21 Tomcat:10.1.19 IDEA: 2023.3.4 (Ultimate Edition) 本文档默认已经安装JDK及环境变量的配置。 关键词…

广度优先算法(一篇文章讲透)

目录 引言 一、算法概述 二、算法步骤 1 初始化 2 循环处理 三、算法应用 1 图的最短路径问题 2 网络爬虫 3 社交网络分析 4 游戏路径搜索 事例 四、算法特点与性能 五、性能优化 1 剪枝策略: 2 使用高效的数据结构: 3 并行化处理&#…

VS2022实现简单控件的缩放

private float X;//当前窗体的宽度private float Y;//当前窗体的高度public Form1(){InitializeComponent();}// 将控件的宽,高,左边距,顶边距和字体大小暂存到tag属性中private void setTag(Control cons){foreach (Control con in cons.Con…