织女星开发板RISC-V核通过SPI协议驱动ARDUINO LCD模块(触屏)
前言
之前写了一个通过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 个 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 && 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模块(触屏)相关推荐
- 织女星开发板RISC-V核通过SPI协议驱动ARDUINO LCD模块(显示)
前言 第一次写这个博客,算是新手吧,刚好有这个机会,手边有VEGA的开发板和Arduino的LCD模块,做了点小东西,想和大家分享一下. 一开始只是想着通过SPI协议初始化LCD屏幕,然后发个字符串就 ...
- 织女星开发板使用RISC-V核驱动GPIO
文章目录 前言 准备工作 寄存器简介 GPIO配置PCR寄存器 GPIO控制寄存器 库函数简介 PORT_SetPinConfig PORT_SetPinMux GPIO_PinInit GPIO_W ...
- 织女星开发板能移植linux吗,织女星开发板启动模式修改——从ARM M4核启动
前言 刚开始玩织女星开发板的时候,想先从熟悉的ARM核入手,连上Jlink,打开MDK版本的Demo程序,编译OK,却检测不到芯片,仔细看了一下文档,原来RV32M1芯片默认从RISC-V核启动,如果 ...
- mssql 无法启动调试器 数据为空_织女星开发板启动模式修改——从ARM M4核启动
织女星开发板启动模式修改--从ARM M4核启动 前言 刚开始玩织女星开发板的时候,想先从熟悉的ARM核入手,连上Jlink,打开MDK版本的Demo程序,编译OK,却检测不到芯片,仔细看了一下文档, ...
- 真正的RISC-V开发板——VEGA织女星开发板开箱评测
文章目录 前言 关于RISC-V架构 关于VEGA织女星开发板 基于FPGA实现的RISC-V开发板 基于RISC-V芯片实现的开发板 开箱爆照 板载资源简介 主控芯片RV32M1简介 支持的开发工具 ...
- NXP恩智浦VEGA织女星开发板免费申请!
前言 大概两周前申请了一块NXP恩智浦的开发板,今天终于收到了!在这里推荐给大家,官方网站刚上线一个月左右,目前申请的人还不算多,感兴趣的朋友可以申请一个,体验一下这个四核性能怪兽.大厂就是大气,包装 ...
- 如何将RT-Thread移植到织女星开发板?
上个学期天津大学的吕卫老师,在他<微处理器系统>课程的实验环节上,让研究生们使用织女星开发板做了一些项目. 本文是其中一份工作,由国际工程师学院,电子与通信工程的徐扬扬和张千依同学共同完成 ...
- 织女星开发板RISC-V内核实现微秒级精确延时
文章目录 前言 关于LPIT0 ZERO核的SysTick定时器 delay.c文件 delay.h文件 实际验证 驱动IIC接口OLED 总结 参考资料 历史精选 前言 收到VEGA织女星开发板也有 ...
- 织女星开发板调试器升级为Jlink固件
文章目录 前言 准备工作 升级操作 升级Jlink驱动 板载接口的说明 历史精选 前言 为了能使用板载的FreeLink调试器来调试RISC-V内核,我们需要把默认的CMSIC-DAP固件,升级为JL ...
最新文章
- 2019最后一期—宏基因组分析技术研讨会
- 初学Struts遇到的坑爹问题
- Floyd算法及其应用
- 判断一个点是否在矩形内部_矩形、圆形泄水管规格型号优势
- 微软 CEO 萨提亚·纳德拉:不要重复造轮子,提升技术强密度
- 数据挖掘:模型选择——树模型
- 网页飘窗效果,jsp页面飘窗浮窗,html飘窗浮窗,点叉号关闭飘窗
- 用java编写矩阵运算_基本矩阵运算的Java实现
- JAVA快递单号查询接口对接教程【快递鸟DEMO】
- 小米android通知栏图标不显示,MIUI开发版更新:已修复原生样式下部分通知图标不显示的问题...
- 详解OpenCV的椭圆曲线点坐标近似计算函数ellipse2Poly()
- 7-95 深入虎穴 (树的深搜)
- 利用定时/计数器T1产生定时时钟,由P1口控制8个发光二极管,使8个提示灯依次一个一个闪动,闪动频率为10次每秒(8个灯亮一遍为一个周期),循环
- 判断一个时间段是否经过了另一个时间段
- GPU 资源消耗原因和解决方案:
- 电磁学讲义5:高斯定理
- 百度股市通日k数据接口
- 黑马程序员 面向对象总结2
- 基于vue大数据可视化(大屏展示)案例
- 手把手教你快速搭建个人博客 Hexo + Github
热门文章
- 婚恋交友app源码,实现一个不一样的轮播指示器
- 中方强调国际刑事法院应尊重有关国家司法传统-国际刑事法院-司法传统
- noteexpress 笔记导出_礼拜笔记下载V1.0.6-礼拜笔记安卓版下载
- 软件工程硕士是否值得读?
- Kinect for Windows Guideline
- 求一维数组中的最大数c语言,C语言 求一维数组中最大值的位置
- 苹果iOS13.2闷杀后台程序,你游戏APP或游戏有中招?PerfDog狗可以帮提前检测
- 五百年龙池涅槃之长岛人的长江大旱谭
- 推进产学研协同 | 昇思受邀出席“科创中国”开源创新与信创产业发展高峰论坛
- Android获取电池电量信息的几种方式