单片机---HLK-W801驱动触摸屏
背景介绍
最近在学习lvgl,这是个开源的嵌入式图像显示框架,足够支撑一些资源匮乏的单片机,来显示一些看起来比较专业的界面,例如下面这种
还有这种
是不是很酷
放下Lvgl姑且不表。因为是用户操作的界面,现在的大部分屏幕都可以配备触摸操作,不再需要额外的键盘鼠标,所以,今天就来学习一下配置我手中这块支持触摸的屏幕。
电阻屏
不像我们的手机,嵌入式设备大部分配备的是电阻屏,因为精准度高,并且廉价。
最常见的就是我这种4线电阻屏。就是4根线,X+,X-,Y+,Y-。
原理如下:
四线主要是由镀有ITO镀层的两层薄膜所组成。其中的一层在屏幕的左右边缘,各有一条垂直的总线,另外一层是在屏幕的顶部和底部都各有一条水平的总线。如果你在一层的薄膜两条总线上施加以电压,那么在ITO的镀层之上就会形成一个均匀的电场。当其使用者触击到触摸屏时,触击点的两层薄膜就会发生接触,在另外一层的薄膜之上就可以测量到接触点的电压值了。
那么4根线如何测量呢
首先第一步:在X-上施加于0V电压,X+上施加于VCC,然后测量Y-(或者Y+)电极上的电压值VPX,然后计算出接触点P的X座标。
第二步:在Y-上施加于0V的电压,在Y+上施加VCC,测量出X-(或X+)电极上的电压值VPY。然后计算出接触点P的Y座标。
以上的两个步骤可以组成一个测量周期,可以得到一组(X,Y)的座标。
以上内容出自《四线式电阻屏工作时的基本工作原理介绍 》
ADC测量法(免费法)
原理上的方法,我暂且称之为ADC测量法,因为是通过ADC测量电压得到的。既然原理清晰了,那么就可以开始解决了,利用4个GPIO,连接屏幕的4线,按照测量方法,进行编程。这叫需求转化代码。
关键代码
//获取x y 的值 x,y 为指针,指向存储的变量void get_map_x_y(int* x,int* y){GPIO_InitTypeDef GPIO_InitStruct;//读 X 值//配置 X+为高 推挽 X-为低 推挽 GPIO_InitStruct.Pin = P_TOUCH_X_JIA;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);HAL_GPIO_WritePin(P_TOUCH_PORT, P_TOUCH_X_JIA, GPIO_PIN_SET);GPIO_InitStruct.Pin = P_TOUCH_X_JIAN;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);HAL_GPIO_WritePin(P_TOUCH_PORT, P_TOUCH_X_JIAN, GPIO_PIN_RESET);//配置 Y+为adc模式 Y-为浮空输入hadcy.Instance = ADC;hadcy.Init.channel = P_TOUCH_Y_CH;hadcy.Init.freq = 1000;if (HAL_ADC_Init(&hadcy) != HAL_OK){Error_Handler();}GPIO_InitStruct.Pin = P_TOUCH_Y_JIAN;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);*x=HAL_ADC_GET_INPUT_VOLTAGE(&hadcy);HAL_GPIO_WritePin(P_TOUCH_PORT, P_TOUCH_X_JIA, GPIO_PIN_RESET);HAL_GPIO_WritePin(P_TOUCH_PORT, P_TOUCH_X_JIAN, GPIO_PIN_RESET);//读 Y 值//配置 y+为高 推挽 y-为低 推挽 GPIO_InitStruct.Pin = P_TOUCH_Y_JIA;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);HAL_GPIO_WritePin(P_TOUCH_PORT, P_TOUCH_Y_JIA, GPIO_PIN_SET);GPIO_InitStruct.Pin = P_TOUCH_Y_JIAN;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);HAL_GPIO_WritePin(P_TOUCH_PORT, P_TOUCH_Y_JIAN, GPIO_PIN_RESET);//配置 x+为adc模式 x-为浮空输入hadcx.Instance = ADC;hadcx.Init.channel = P_TOUCH_X_CH;hadcx.Init.freq = 1000;if (HAL_ADC_Init(&hadcx) != HAL_OK){Error_Handler();}GPIO_InitStruct.Pin = P_TOUCH_X_JIAN;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);*y=HAL_ADC_GET_INPUT_VOLTAGE(&hadcx);HAL_GPIO_WritePin(P_TOUCH_PORT, P_TOUCH_Y_JIA, GPIO_PIN_RESET);HAL_GPIO_WritePin(P_TOUCH_PORT, P_TOUCH_Y_JIAN, GPIO_PIN_RESET);}
完全就是按照原理图,翻译出来的操作,这里我们需要注意一下啊,测量点X+与Y+要在ADC模式和输出模式下转换,所以,我们要选择既支持GPIO,又支持ADC转化的引脚。否则就无法测量出来值。
不过这种方式我研究了5个小时,然后就发现,X坐标测量出来的电压,全屏幕都在变化,然后我就细心的发现了,板子上自带了一个ADC芯片,并且已经接好了线。所以这种电压的变化,我怀疑和外部接了芯片有关系,遂放弃了这种做法。
不过这种做法完全是可行的。并且还不需要额外的芯片,换句话说,这种做法,免费。
ADS7846
板子上自带的芯片,就是ads7846,这是一款非常主流的电阻屏驱动芯片,它将4线电阻的测量,转化为SPI总线的数据读取,并且提供了中断,对接单片机,那叫一个6。
提供了6根线
引脚 | 说明 |
---|---|
CLK | SPI时钟 |
CS | 片选 |
MOSI | 主机输出从机输入 |
MISO | 主机输入从机输出 |
BUSY | 忙信号 |
PEN | 中断信号 |
这里使用了一下GPIO模拟的方式。
GPIO初始化
static void TOUCH_GPIO_Init(void){GPIO_InitTypeDef GPIO_InitStruct;__HAL_RCC_GPIO_CLK_ENABLE();GPIO_InitStruct.Pin = TDIN;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);HAL_GPIO_WritePin(P_TOUCH_PORT, TDIN, GPIO_PIN_RESET);GPIO_InitStruct.Pin = TCLK;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);HAL_GPIO_WritePin(P_TOUCH_PORT, TCLK, GPIO_PIN_RESET);GPIO_InitStruct.Pin = TCS;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);HAL_GPIO_WritePin(P_TOUCH_PORT, TCS, GPIO_PIN_RESET);GPIO_InitStruct.Pin = DOUT;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_PULLUP;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);GPIO_InitStruct.Pin = PEN;GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;GPIO_InitStruct.Pull = GPIO_PULLUP;HAL_GPIO_Init(P_TOUCH_PORT, &GPIO_InitStruct);HAL_NVIC_SetPriority(GPIOA_IRQn, 0);HAL_NVIC_EnableIRQ(GPIOA_IRQn);}
这里注意一下,要打开中断,这个PEN引脚,在有触摸的时候,是低电平,当手拿开的时候,就是高电平了。所以如果要持续读取,要不断的判断PEN引脚的电瓶。
BUSY信号也可以用上,初始化为读入方式就可以。
核心函数,读取坐标
#define CMD_RDX 0X90 //0B10010000即用差分方式读X坐标#define CMD_RDY0XD0 //0B11010000即用差分方式读Y坐标 //读取一次X,Y值//读到的X,Y坐标值必须都大于100//成功返回1,不成功返回0//读数限制在100~3800之间. const float y_sin=0.1348;const float x_sin=0.1739;int get_map_x_y(int* X,int* Y){int px=0,py=0;start_spi();//启动SPI WriteByteADS(CMD_RDX); //ADS7846的转换时间最长为6usTCLK_SET(1);delay_us(3);TCLK_SET(0);delay_us(3);px=ReadWordADS(); WriteByteADS(CMD_RDY); //ADS7846的转换时间最长为6usTCLK_SET(1);delay_us(3);TCLK_SET(0);delay_us(3); py=ReadWordADS();//读Y轴坐标 TCS_SET(1); if((px>100)&&(py>100)&&(px<3800)&&(py<3800)){int abs_px,abs_py;abs_px=px-100;abs_py=py-100;*X=x_sin*abs_px;*Y=y_sin*abs_py;return 1;//读数成功(范围限制)}else{return 0; //读数失败} }
我先大概试了一下4个角的坐标,然后估算出了x轴和y轴的单位,即读取到的坐标1个单位表示多少个像素。得到了下面两个值。
const float y_sin=0.1348;
const float x_sin=0.1739;
然后就可以按照读取到的值去掉起始值,再乘以刚才的系数,就能够得到我这个320*240屏幕的具体坐标了。
abs_px=px-100;
abs_py=py-100;
X=x_sinabs_px;
Y=y_sinabs_py;
绘图板
在前面一篇文章中,显示屏16线的驱动已经完成,《单片机—HLK-W801并口驱动ST7789》
那么结合起来今天的触摸屏,就可以做一个绘图板了。
中断判断,这里只是置一个标志位,有触摸操作会触发。
void HAL_GPIO_EXTI_Callback(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin){if ((GPIOx == P_TOUCH_PORT) && (GPIO_Pin == PEN)){key_flag = 1;}}
然后主函数中,进行持续读点画点就可以了。
while (1){if (key_flag == 1){HAL_Delay(20);while(HAL_GPIO_ReadPin(P_TOUCH_PORT, PEN) == GPIO_PIN_RESET){int pos_x=0, pos_y=0;get_map_x_y(&pos_x,&pos_y);LCD_DrawPoint(320-pos_x,pos_y, 0xf000);HAL_Delay(5);}key_flag = 0;}}
在HAL_Delay不同的情况下,有不同的绘制效果。
HAL_Delay(20)
HAL_Delay(5)
去掉延迟
可以看出,有延迟的时候,坐标比较干净,没有延迟的时候,在落笔和起笔的时候,会有很多漂移的点,这个漂移的效果,倒像是沙画的效果,撒出来的。
结束语
这篇文章只是为了学习lvgl做了一个技术储备,用来作为lvgl的输入方式。顺带也了解了一下电阻屏的驱动方式,并且掌握了两种测量方法。
昨天是妇女节,姑娘都嫌弃这个名字,显得土,后来就叫女神节。其实当初这个节日是女工为了争取平等的福利而创立的,全称是“联合国妇女权益和国际和平日”,同事说,其实也应该给已婚妇女的老公也放半天,这样能更好的给女性放个假,我觉得说的有道理啊。