前言
之前写了一个通过SPI协议初始化LCD屏幕并显示字符串和图片的文章,这次再尝试一下触屏的功能。
准备工作
1,焊接织女星开发板J1,J2,J3,J4的双排母座,以便与LCD屏通信。前段时间免费申请的织女星开发板出厂是没有焊接这些模块的,所以要自己焊一下。
2,网上查看LCD的相关资料,网址:这里是触摸屏的用户手册
3,已经能够实现通过SPI协议点亮LCD屏幕并成功显示字符串(参考我另一篇LCD显示的文章)。
代码分析和修改
Arduino LCD 模块使用的触屏驱动芯片是XPT2046,这是一款 4 导线制触摸屏控制器,内含 12 位分辨率125KHz转换速率逐步逼近型A/D 转换器。XPT2046 支持从 1.5V 到 5.25V 的低电压 I/O 接口,能通过执行两次 A/D 转换查出被按的屏幕位置。
四线电阻式触摸屏主要由两层镀有ITO镀层的薄膜组成。其中一层在屏幕的左右边缘各有一条垂直总线,另一层在屏幕的底部和顶部各有一条水平总线,如果在一层薄膜的两条总线上施加电压,在ITO 镀层上就会形成均匀电场。 当使用者触击触摸屏时,触击点处两层薄膜就会接触,在另一层薄膜上就可以测量到接触点的电压值。
Arduino LCD 模块关于触屏功能的引脚有TP-CS 和TP-IRQ ,查看LCD 模块和织女星开发板的原理图可以得到如表1所示的pin 脚对应关系。
表1 PIN脚和端子对应表
信号名称 | 端子号 | 信号值 |
---|---|---|
时钟CKL | 12(J2) | PTB4 |
LCD片选CS | 6(J2) | PTB6 |
SPI数据输出 | 8(J2) | PTB5 |
SPI数据输入 | 10(J2) | PTB7 |
背光BL | 4(J2) | PTB3 |
数据命令DC | 16(J1) | PTB1 |
TP片选 | 10(J1) | PTB14 |
TP中断 | 8(J1) | PTB13 |
在BOARD_InitPins()函数中根据以上分析,初始化相应的pin脚,具体实现如下:
void BOARD_InitPins(void) {
/* Clock Gate Control: 0x01u */
CLOCK_EnableClock(kCLOCK_PortB);
/* PORTB4 is configured as LPSPI0_SCK */
PORT_SetPinMux(PORTB, PIN4_IDX, kPORT_MuxAlt2);
/* PORTB5 is configured as LPSPI0_SOUT */
PORT_SetPinMux(PORTB, PIN5_IDX, kPORT_MuxAlt2);
/* PORTB6 is configured as GPIO */
PORT_SetPinMux(PORTB, PIN6_IDX, kPORT_MuxAsGpio); //注意此处与之前只实现LCD显示的代码设置不同
/* PORTB7 is configured as LPSPI0_SIN */
PORT_SetPinMux(PORTB, PIN7_IDX, kPORT_MuxAlt2);
/* PORTB1 is configured as GPIO */
PORT_SetPinMux(PORTB, PIN1_IDX, kPORT_MuxAsGpio);
/* PORTB3 is configured as GPIO */
PORT_SetPinMux(PORTB, PIN3_IDX, kPORT_MuxAsGpio);
PORT_SetPinMux(PORTB, PIN13_IDX, kPORT_MuxAsGpio); //TP_IRQ
PORT_SetPinMux(PORTB, PIN14_IDX, kPORT_MuxAsGpio); //TP_CS
}
Pin 1, 3, 13, 14设置为GPIO,需要初始化设置,在main函数中的语句如下:
GPIO_PinInit(GPIOB, 1u, &spi_config); //lcd-dc
GPIO_PinInit(GPIOB, 3u, &spi_config); //lcd-bl
GPIO_PinInit(GPIOB, 6u, &spi_config); //lcd-cs
spi_config.outputLogic =1 ;
GPIO_PinInit(GPIOB, 14u, &spi_config); //tp-cs
gpio_pin_config_t spi1_config = {
kGPIO_DigitalInput, 1,
};
GPIO_PinInit(GPIOB, 13u, &spi1_config); //tp-irq
以上,关于PIN脚的配置就完成了,接下来是关于LCD和SPI 的设置。直接调用SDK中的CLOCK_SetIpSrc函数为SPI设置时钟源和获取主时钟源。
/*Set clock source for LPSPI and get master clock source*/
CLOCK_SetIpSrc(kCLOCK_Lpspi0, kCLOCK_IpSrcFircAsync);
LCD初始化函数详见《织女星开发板通过SPI协议驱动ARDUINO LCD模块(显示)》。
通过之前的分析得知可以将XPT2046看作是一个AD转换器,所以也不需要什么初始化设置的,而具体的初始化其实也就是IO的初始化和SPI的初始化。
通过SPI读写一个字节的代码如下所示:
static uint8_t spi_RxTx_byte(uint8_t data){
lpspi_transfer_t masterXfer;
masterXfer.txData = &data;
masterXfer.rxData = &data;
masterXfer.dataSize = 1;
masterXfer.configFlags =0;
LPSPI_MasterTransferBlocking(LCD_SPI, &masterXfer);
while (LPSPI_GetStatusFlags(LCD_SPI) & kLPSPI_ModuleBusyFlag) {
}
return data;
}
后面的功能可以直接调用上面的函数,为了方便理解,我用xpt2046名称的函数封装了一下:
uint8_t xpt2046_write_byte(uint8_t chData)
{
return spi_RxTx_byte(chData);
}
读取XPT2046的x和y值
触摸屏根据方向分为 X 轴和 Y 轴两个部分,通过读取 X 轴和 Y 轴的数据,我们就可以知道触摸屏触摸的位置了。
图1 显示了xpt2046 8位总线接口,无DCLK时钟延迟,24时钟周期转换时序。XPT2046 完成一个完整的转换需要 24 个串行时钟,也就是需要 3 个字节的 SPI 时钟。对照图1,XPT2046 前 8 个串行时钟,是接收 1 个字节的转换命令,接收到转换命令之后,使用 1 个串行时钟的时间来完成数据转换,然后返回 12 个串行时钟长度的转换结果。最后 3 个串行时钟返回三个无效数据。
所以读取一个完整转换过程为:
- 发送 1 个 8 字节的控制命令。
- 读取 2 个字节的返回数据。
- 进行数据处理。也就是丢弃最后读取到的 3 位数据。 我们需要读取两个数据,一个 X 轴数据和一个 Y 轴数据,所以我们这里需要两个控制命令。
表1表2显示了一个完整的控制命令的结构。
表1 控制字各位描述
表2 差分模式输入配置
从表2可以知道操作数0xD0表示测量x轴位置,操作数0x90表示测量y轴位置。读取触摸到屏幕上某一点坐标的函数实现如下:
void xpt2046_read_xy(uint16_t *phwXpos, uint16_t *phwYpos)
{
*phwXpos = xpt2046_read_average(0xD0);
*phwYpos = xpt2046_read_average(0x90);
}
一般读取两次提高准确度:
#define ERR_RANGE 50
bool xpt2046_twice_read_xy(uint16_t *phwXpos, uint16_t *phwYpos)
{
uint16_t hwXpos1, hwYpos1, hwXpos2, hwYpos2;
xpt2046_read_xy(&hwXpos1, &hwYpos1);
xpt2046_read_xy(&hwXpos2, &hwYpos2);
if (((hwXpos2 <= hwXpos1 && hwXpos1 < hwXpos2 + ERR_RANGE) || (hwXpos1 <= hwXpos2 && hwXpos2 < hwXpos1 + ERR_RANGE))
&& ((hwYpos2 <= hwYpos1 &&