前言

之前写了一个通过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_IRQPORT_SetPinMux(PORTB, PIN14_IDX, kPORT_MuxAsGpio);  //TP_CS}

Pin 1, 3, 13, 14设置为GPIO,需要初始化设置,在main函数中的语句如下:

     GPIO_PinInit(GPIOB, 1u, &spi_config);    //lcd-dcGPIO_PinInit(GPIOB, 3u, &spi_config);    //lcd-blGPIO_PinInit(GPIOB, 6u, &spi_config);    //lcd-csspi_config.outputLogic =1 ;GPIO_PinInit(GPIOB, 14u, &spi_config);   //tp-csgpio_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. 发送 1 个 8 字节的控制命令。
  2. 读取 2 个字节的返回数据。
  3. 进行数据处理。也就是丢弃最后读取到的 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 && hwYpos1 < hwYpos2 + ERR_RANGE) || (hwYpos1 <= hwYpos2 && hwYpos2 < hwYpos1 + ERR_RANGE))) {*phwXpos = (hwXpos1 + hwXpos2) >> 1;*phwYpos = (hwYpos1 + hwYpos2) >> 1;return true;}return false;
}

在读取函数的程序中,为了获取数据值的准确性,进行多次读取,然后除去最大最小值,求出平均值:

uint16_t xpt2046_read_average(uint8_t chCmd)
{uint8_t i, j;uint16_t hwbuffer[READ_TIMES], hwSum = 0, hwTemp; for (i = 0; i < READ_TIMES; i ++) {hwbuffer[i] = xpt2046_read_ad_value(chCmd);}for (i = 0; i < READ_TIMES - 1; i ++) {        //sortfor (j = i + 1; j < READ_TIMES; j ++) {if (hwbuffer[i] > hwbuffer[j]) {hwTemp = hwbuffer[i];hwbuffer[i] = hwbuffer[j];hwbuffer[j] = hwTemp;}}}for (i = LOST_NUM; i < READ_TIMES - LOST_NUM; i ++) {hwSum += hwbuffer[i];           }hwTemp = hwSum / (READ_TIMES - 2 * LOST_NUM);return hwTemp;
}

读取AD转换数据:

uint16_t xpt2046_read_ad_value(uint8_t chCmd)
{uint16_t hwData = 0; XPT2046_CS_CLR();xpt2046_write_byte(chCmd);  //发送控制命令hwData = xpt2046_write_byte(0x00); //读取两个字节的返回数据hwData <<= 8;hwData |= xpt2046_write_byte(0x00);;hwData >>= 4;            //只需要12位的转换结果,所以丢弃后4位XPT2046_CS_SET();  return hwData;
}

屏幕校正

选取屏幕四个角上的四个点,将读取的数据存放在一个二维数组中,然后根据计算的factor值判定校准是否成功,如果成功则进入下一步,不成功的话就再来一次。触屏校正代码如下所示:

void tp_adjust(void)
{   uint8_t  cnt = 0;uint16_t hwTimeout = 0, d1, d2, pos_temp[4][2];uint32_t tem1, tem2;float fac;                lcd_clear_screen(LCD_COLOR_WHITE);lcd_display_string(40, 40, (const uint8_t *)"Please use the stylus click the cross on the screen. The cross will always move until the screen adjustment is completed.",16, LCD_COLOR_RED);tp_draw_touch_point(20, 20, LCD_COLOR_RED);s_tTouch.chStatus = 0;s_tTouch.fXfac = 0;while (1) {tp_scan(1);if((s_tTouch.chStatus & 0xC0) == TP_PRESSED) { hwTimeout = 0;s_tTouch.chStatus &= ~(1 << 6);                          pos_temp[cnt][0] = s_tTouch.hwXpos;pos_temp[cnt][1] = s_tTouch.hwYpos;cnt ++;      switch(cnt) {            case 1:                       tp_draw_touch_point(20, 20, LCD_COLOR_WHITE);tp_draw_touch_point(LCD_WIDTH - 20, 20, LCD_COLOR_RED);break;case 2:tp_draw_touch_point(LCD_WIDTH - 20, 20, LCD_COLOR_WHITE);tp_draw_touch_point(20, LCD_HEIGHT - 20, LCD_COLOR_RED);break;case 3:tp_draw_touch_point(20, LCD_HEIGHT - 20, LCD_COLOR_WHITE);tp_draw_touch_point(LCD_WIDTH - 20, LCD_HEIGHT - 20, LCD_COLOR_RED);break;case 4: tem1=abs((int16_t)(pos_temp[0][0]-pos_temp[1][0]));//x1-x2tem2=abs((int16_t)(pos_temp[0][1]-pos_temp[1][1]));//y1-y2tem1*=tem1;tem2*=tem2;tem1+=tem2;d1=sqrt(tem1);tem1=abs((int16_t)(pos_temp[2][0]-pos_temp[3][0]));//x3-x4tem2=abs((int16_t)(pos_temp[2][1]-pos_temp[3][1]));//y3-y4tem1*=tem1;tem2*=tem2;tem1+=tem2;d2=sqrt(tem1);fac=(float)d1/d2;if(fac<0.95||fac>1.05||d1==0||d2==0) {cnt=0;tp_show_info(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);  lcd_delayms(1000);tp_draw_touch_point(LCD_WIDTH - 20, LCD_HEIGHT - 20, LCD_COLOR_WHITE);tp_draw_touch_point(20, 20, LCD_COLOR_RED);continue;}tem1=abs((int16_t)(pos_temp[0][0]-pos_temp[2][0]));//x1-x3tem2=abs((int16_t)(pos_temp[0][1]-pos_temp[2][1]));//y1-y3tem1*=tem1;tem2*=tem2;tem1+=tem2;d1=sqrt(tem1);tem1=abs((int16_t)(pos_temp[1][0]-pos_temp[3][0]));//x2-x4tem2=abs((int16_t)(pos_temp[1][1]-pos_temp[3][1]));//y2-y4tem1*=tem1;tem2*=tem2;tem1+=tem2;d2=sqrt(tem1);fac=(float)d1/d2;if(fac<0.95||fac>1.05) {cnt=0;tp_show_info(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);  lcd_delayms(1000);lcd_fill_rect(96, 240, 24, 16, LCD_COLOR_WHITE);tp_draw_touch_point(LCD_WIDTH - 20, LCD_HEIGHT - 20, LCD_COLOR_WHITE);tp_draw_touch_point(20, 20, LCD_COLOR_RED);continue;}             tem1=abs((int16_t)(pos_temp[1][0]-pos_temp[2][0]));//x2-x3tem2=abs((int16_t)(pos_temp[1][1]-pos_temp[2][1]));//y2-y3tem1*=tem1;tem2*=tem2;tem1+=tem2;d1=sqrt(tem1);tem1=abs((int16_t)(pos_temp[0][0]-pos_temp[3][0]));//x1-x4tem2=abs((int16_t)(pos_temp[0][1]-pos_temp[3][1]));//y1-y4tem1*=tem1;tem2*=tem2;tem1+=tem2;d2=sqrt(tem1);fac=(float)d1/d2;if(fac<0.95||fac>1.05) {cnt=0;                      tp_show_info(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100); lcd_delayms(1000);tp_draw_touch_point(LCD_WIDTH - 20, LCD_HEIGHT - 20, LCD_COLOR_WHITE);tp_draw_touch_point(20, 20, LCD_COLOR_RED);continue;}s_tTouch.fXfac = (float)(LCD_WIDTH - 40) / (int16_t)(pos_temp[1][0] - pos_temp[0][0]); s_tTouch.iXoff = (LCD_WIDTH - s_tTouch.fXfac * (pos_temp[1][0] + pos_temp[0][0])) / 2;s_tTouch.fYfac = (float)(LCD_HEIGHT - 40) / (int16_t)(pos_temp[2][1] - pos_temp[0][1]);      s_tTouch.iYoff = (LCD_HEIGHT - s_tTouch.fYfac * (pos_temp[2][1] + pos_temp[0][1])) / 2;if(abs(s_tTouch.fXfac) > 2 || abs(s_tTouch.fYfac) > 2) {cnt=0;tp_draw_touch_point(LCD_WIDTH - 20, LCD_HEIGHT - 20, LCD_COLOR_WHITE);tp_draw_touch_point(20, 20, LCD_COLOR_RED);                               lcd_display_string(40, 26, (const uint8_t *)"TP Need readjust!", 16, LCD_COLOR_RED);continue;}lcd_clear_screen(LCD_COLOR_WHITE);lcd_display_string(35, 110, (const uint8_t *)"Touch Screen Adjust OK!", 16, LCD_COLOR_BLUE);lcd_delayms(1000); lcd_clear_screen(LCD_COLOR_WHITE);  return;               }}lcd_delayms(600);if (++ hwTimeout >= 6000) {break;}}
}

其中调用到tp_scan()函数,检测是否有触屏并获得物理坐标值,具体实现如下:

uint8_t tp_scan(uint8_t chCoordType)
{if (!(XPT2046_IRQ_READ())) {if (chCoordType) {xpt2046_twice_read_xy(&s_tTouch.hwXpos, &s_tTouch.hwYpos);} else if (xpt2046_twice_read_xy(&s_tTouch.hwXpos, &s_tTouch.hwYpos)) {s_tTouch.hwXpos = s_tTouch.fXfac * s_tTouch.hwXpos + s_tTouch.iXoff;s_tTouch.hwYpos = s_tTouch.fYfac * s_tTouch.hwYpos + s_tTouch.iYoff;}if (0 == (s_tTouch.chStatus & TP_PRESS_DOWN)) {s_tTouch.chStatus = TP_PRESS_DOWN | TP_PRESSED;s_tTouch.hwXpos0 = s_tTouch.hwXpos;s_tTouch.hwYpos0 = s_tTouch.hwYpos;} } else {if (s_tTouch.chStatus & TP_PRESS_DOWN) {s_tTouch.chStatus &= ~(1 << 7);} else {s_tTouch.hwXpos0 = 0;s_tTouch.hwYpos0 = 0;s_tTouch.hwXpos = 0xffff;s_tTouch.hwYpos = 0xffff;}}return (s_tTouch.chStatus & TP_PRESS_DOWN);
}

展示一下成果(一只手拿手机拍摄另一只手拿吸管写字,哈哈):

最后展示一下我的简笔画兔子(嘻嘻):

本文的部分内容参考了XPT2046触摸屏实验过程详解与STM32代码解析和2.8inch TFT Touch Shield用户手册

织女星开发板RISC-V核通过SPI协议驱动ARDUINO LCD模块(触屏)相关推荐

  1. 织女星开发板RISC-V核通过SPI协议驱动ARDUINO LCD模块(显示)

    前言 第一次写这个博客,算是新手吧,刚好有这个机会,手边有VEGA的开发板和Arduino的LCD模块,做了点小东西,想和大家分享一下. 一开始只是想着通过SPI协议初始化LCD屏幕,然后发个字符串就 ...

  2. 织女星开发板使用RISC-V核驱动GPIO

    文章目录 前言 准备工作 寄存器简介 GPIO配置PCR寄存器 GPIO控制寄存器 库函数简介 PORT_SetPinConfig PORT_SetPinMux GPIO_PinInit GPIO_W ...

  3. 织女星开发板能移植linux吗,织女星开发板启动模式修改——从ARM M4核启动

    前言 刚开始玩织女星开发板的时候,想先从熟悉的ARM核入手,连上Jlink,打开MDK版本的Demo程序,编译OK,却检测不到芯片,仔细看了一下文档,原来RV32M1芯片默认从RISC-V核启动,如果 ...

  4. mssql 无法启动调试器 数据为空_织女星开发板启动模式修改——从ARM M4核启动

    织女星开发板启动模式修改--从ARM M4核启动 前言 刚开始玩织女星开发板的时候,想先从熟悉的ARM核入手,连上Jlink,打开MDK版本的Demo程序,编译OK,却检测不到芯片,仔细看了一下文档, ...

  5. 真正的RISC-V开发板——VEGA织女星开发板开箱评测

    文章目录 前言 关于RISC-V架构 关于VEGA织女星开发板 基于FPGA实现的RISC-V开发板 基于RISC-V芯片实现的开发板 开箱爆照 板载资源简介 主控芯片RV32M1简介 支持的开发工具 ...

  6. NXP恩智浦VEGA织女星开发板免费申请!

    前言 大概两周前申请了一块NXP恩智浦的开发板,今天终于收到了!在这里推荐给大家,官方网站刚上线一个月左右,目前申请的人还不算多,感兴趣的朋友可以申请一个,体验一下这个四核性能怪兽.大厂就是大气,包装 ...

  7. 如何将RT-Thread移植到织女星开发板?

    上个学期天津大学的吕卫老师,在他<微处理器系统>课程的实验环节上,让研究生们使用织女星开发板做了一些项目. 本文是其中一份工作,由国际工程师学院,电子与通信工程的徐扬扬和张千依同学共同完成 ...

  8. 织女星开发板RISC-V内核实现微秒级精确延时

    文章目录 前言 关于LPIT0 ZERO核的SysTick定时器 delay.c文件 delay.h文件 实际验证 驱动IIC接口OLED 总结 参考资料 历史精选 前言 收到VEGA织女星开发板也有 ...

  9. 织女星开发板调试器升级为Jlink固件

    文章目录 前言 准备工作 升级操作 升级Jlink驱动 板载接口的说明 历史精选 前言 为了能使用板载的FreeLink调试器来调试RISC-V内核,我们需要把默认的CMSIC-DAP固件,升级为JL ...

最新文章

  1. 2019最后一期—宏基因组分析技术研讨会
  2. 初学Struts遇到的坑爹问题
  3. Floyd算法及其应用
  4. 判断一个点是否在矩形内部_矩形、圆形泄水管规格型号优势
  5. 微软 CEO 萨提亚·纳德拉:不要重复造轮子,提升技术强密度
  6. 数据挖掘:模型选择——树模型
  7. 网页飘窗效果,jsp页面飘窗浮窗,html飘窗浮窗,点叉号关闭飘窗
  8. 用java编写矩阵运算_基本矩阵运算的Java实现
  9. JAVA快递单号查询接口对接教程【快递鸟DEMO】
  10. 小米android通知栏图标不显示,MIUI开发版更新:已修复原生样式下部分通知图标不显示的问题...
  11. 详解OpenCV的椭圆曲线点坐标近似计算函数ellipse2Poly()
  12. 7-95 深入虎穴 (树的深搜)
  13. 利用定时/计数器T1产生定时时钟,由P1口控制8个发光二极管,使8个提示灯依次一个一个闪动,闪动频率为10次每秒(8个灯亮一遍为一个周期),循环
  14. 判断一个时间段是否经过了另一个时间段
  15. GPU 资源消耗原因和解决方案:
  16. 电磁学讲义5:高斯定理
  17. 百度股市通日k数据接口
  18. 黑马程序员 面向对象总结2
  19. 基于vue大数据可视化(大屏展示)案例
  20. 手把手教你快速搭建个人博客 Hexo + Github

热门文章

  1. 婚恋交友app源码,实现一个不一样的轮播指示器
  2. 中方强调国际刑事法院应尊重有关国家司法传统-国际刑事法院-司法传统
  3. noteexpress 笔记导出_礼拜笔记下载V1.0.6-礼拜笔记安卓版下载
  4. 软件工程硕士是否值得读?
  5. Kinect for Windows Guideline
  6. 求一维数组中的最大数c语言,C语言 求一维数组中最大值的位置
  7. 苹果iOS13.2闷杀后台程序,你游戏APP或游戏有中招?PerfDog狗可以帮提前检测
  8. 五百年龙池涅槃之长岛人的长江大旱谭
  9. 推进产学研协同 | 昇思受邀出席“科创中国”开源创新与信创产业发展高峰论坛
  10. Android获取电池电量信息的几种方式