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

背景

最近参加公司一个电池测试仪的项目,负责电容屏驱动开发,电容屏的触控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. 不要因为可以马上开始就选择步行,下地库取车能更快抵达目的地。
  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用内容,msp430f5529驱动外接TFT液晶幕的步骤如下: 1. 首先,需要初始化SPI2和LCD。在初始化SPI2时,需要注意SPI的波特率设置。对于SPI2,最大波特率为APB1的2分频,即18M。如果使用SPI1,则最大波特率为36M。根据手册的要求,最好不要超过18M的波特率。 2. 设置显示区域和写颜色。可以使用函数`ST7735_setAddrWindow`来设置显示区域,函数参数为x0、y0、x1、y1,分别表示矩形区域的左上角和右下角的坐标。然后,可以使用函数`LCD_pushColor`来填充颜色,函数参数为颜色值和填充的次数。 3. 在主函数中调用上述函数来驱动TFT液晶幕。首先进行SPI2和LCD的初始化,然后使用`LCD_fillScreen`函数来填充整个幕的颜色,函数参数为颜色值。接下来,可以使用`LCD_ShowCharStr`函数来显示字符,函数参数为字符的位置、字符内容、前景色、背景色和字体大小。 综上所述,通过上述步骤,可以实现msp430f5529驱动外接TFT液晶幕的功能。 #### 引用[.reference_title] - *1* *2* [TFT驱动ST7735S使用实例](https://blog.csdn.net/weixin_38345163/article/details/105264721)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [MSP430F5529硬件I2C驱动SH1106芯片显示](https://blog.csdn.net/qq_19654489/article/details/124080837)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值