本文主要是介绍门锁系统——屏幕显示,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
接上篇1.门锁系统——项目简介_only_print的博客-CSDN博客
本节主要先简单实现屏幕的显示功能,以供后期的显示需要。
设想先实现上部为显示整个信息,下部显示密码的选项。
目录
显示部分
程序设计
触摸部分
CobeMX设置
程序设计
显示部分
本实验采用的是2.8寸TFTLCD,有关LCD的基本操作过程可以参考我之前的博客:使用FSMC连接TFT LCD_only_print的博客-CSDN博客
。
接下来我们就基于这个地方开始进行开发。
CobeMX中的设置已经完成,直接进入程序
程序设计
为了整个项目的整洁,我们将自己实现的LCD相关程序放在一个新的文件中,在文件目录下创建一个新的文件夹,在其中创建一个bsp_LCD.c和bsp_LCD.h文件,将其分别添加到文件目录中。
然后就可以初步实现显示功能了,先在下方显示LCD的数字显示功能,具体功能实现下:
// 画出下方的屏幕中按键void LCD_Partition(void){uint16_t i,x= 0,y = 170;POINT_COLOR=RED;LCD_Fill(x,y,x+240,y+150,WHITE);/* 此处就是画一些条条框框来放置相应的UI值 */LCD_DrawRectangle(x,y,x+240,y+150); //画竖线 LCD_DrawRectangle(x+80,y,x+160,y+150); LCD_DrawRectangle(x,y+30,x+240,y+60);LCD_DrawRectangle(x,y+90,x+240,y+120);POINT_COLOR=BLUE;LCD_ShowString(x+4,y+7,80,16,16,(uint8_t *)"Del FinPt");// 删除指纹按键 LCD_ShowString(x+4+80,y+7,80,16,16,(uint8_t *)"Cge PasWd");// 修改密码LCD_ShowString(x+4+80*2,y+7,80,16,16,(uint8_t *)"Cre FinPt");// 新建指纹按键 for(i=1;i<10;i++)//数字键盘{// 显示1至9LCD_ShowNum(x+36+((i-1)%3)*80,y+7+30*(((i-1)/3)+1),i,1,16);}// 显示剩下的# 0 *LCD_ShowString(36, y+7+30*4, 8,16,16, (uint8_t *)"*"); // 这个按键用来触摸时,删除错误的按键。LCD_ShowNum(80+36, y+7+30*4, 0, 1, 16);LCD_ShowString(160+36, y+7+30*4, 8,16,16,(uint8_t *)"#"); // 这个按键用来触摸时,提交密码。}
再显示上方的按键功能信息,这部分留于以后模块实现时完成这部分二级菜单,具体实现如下:
// 绘制上方的部分,这部分用来展示按键的作用/* 按键的作用key1-密码设置key2-指纹设置key3-卡片设置key4-忘记密码*/void Key_Press(void){LCD_ShowString(10,0, 240,16,16,(uint8_t*)"[1] Password settings");LCD_ShowString(10,20,240,16,16, (uint8_t*)"[2] Fingerprint settings");LCD_ShowString(10,40,240,16,16, (uint8_t*)"[3] IDCard settings");LCD_ShowString(10,60, 240,16,16,(uint8_t*)"[4] Forgot password");}
最后为了方便将之前的两个初始化添加到一个初始化函数中:
// 屏幕显示初始化函数void Bsp_Lcd_Init(void){LCD_Partition();Key_Press();}
将函数在bsp_lcd.h中声明,在主函数中调用即可。
然后将各个按键的作用分别定义为函数:
// 触摸设置void Touch_Set(void){// 清除y方向上从0到80的区域LCD_Fill(0, 0, 280, 80, WHITE);// 最上方显示选择的按键LCD_ShowString(10,0, 240,16,16,(uint8_t*)"[1] Touch settings");// 触摸校正函数,在touch.c中定义TouchCalibrate();// 显示完成后重新显示主界面HAL_Delay(2000);Bsp_Lcd_Init();}// 指纹设置void Finger_Set(void){LCD_Fill(0, 0, 280, 80, WHITE);LCD_ShowString(10,0,240,16,16, (uint8_t*)"[2] Fingerprint settings");// 画出按键对应的选项uint16_t x= 0,y = 20;POINT_COLOR=RED; // 将颜色设置为红色/* 此处就是画一些条条框框来放置相应的UI值 */LCD_DrawRectangle(x,y,x+120,y+60); // 画矩形 LCD_DrawRectangle(x+120,y,x+240,y+60);POINT_COLOR=BLUE; // 将颜色设置为蓝色// 设置方框内显示内容LCD_ShowString(x+20,y+10,80,16,16,(uint8_t *)"View the");LCD_ShowString(x+20,y+30,80,16,16,(uint8_t *)"password"); // 查看密码(将密码发送到手机上)LCD_ShowString(x+20+120,y+10,80,16,16,(uint8_t *)"Change the");LCD_ShowString(x+20+120,y+30,80,16,16,(uint8_t *)"password"); // 修改密码}// 卡片设置void IDCard_Set(void){LCD_Fill(0, 0, 280, 80, WHITE);LCD_ShowString(10,0,240,16,16, (uint8_t*)"[3] IDCard settings");// 画出按键对应的选项uint16_t x= 0,y = 20;POINT_COLOR=RED; // 将颜色设置为红色/* 此处就是画一些条条框框来放置相应的UI值 */LCD_DrawRectangle(x,y,x+120,y+60); // 画矩形 LCD_DrawRectangle(x+120,y,x+240,y+60);POINT_COLOR=BLUE; // 将颜色设置为蓝色// 设置方框内显示内容LCD_ShowString(x+20,y+10,80,16,16,(uint8_t *)"View the");LCD_ShowString(x+20,y+30,80,16,16,(uint8_t *)"password"); // 查看密码(将密码发送到手机上)LCD_ShowString(x+20+120,y+10,80,16,16,(uint8_t *)"Change the");LCD_ShowString(x+20+120,y+30,80,16,16,(uint8_t *)"password"); // 修改密码}// 密码设置void Password_Set(void){LCD_Fill(0, 0, 280, 100, WHITE);LCD_ShowString(10,0, 240,16,16,(uint8_t*)"[4] Password settings");// 画出按键对应的选项uint16_t x= 0,y = 20;POINT_COLOR=RED; // 将颜色设置为红色/* 此处就是画一些条条框框来放置相应的UI值 */LCD_DrawRectangle(x,y,x+239,y+60); // 画矩形 POINT_COLOR=BLUE; // 将颜色设置为蓝色// 设置方框内显示内容LCD_ShowString(x+20,y+10,240,16,16,(uint8_t *)"View the password");LCD_ShowString(x+20,y+30,240,16,16,(uint8_t *)"Please check your phone"); // 查看密码(将密码发送到手机上)}
注意:这里的函数只是初步的设置,在之后需要为各个函数添加自己相应的函数操作.。
触摸部分
显示完成后,就需要可以触摸实现第一种解锁方式了
基本原理:
电阻触摸屏的主要部分是一块与显示器表面非常配合的电阻薄膜屏,这是一种多层的复合薄膜,它以一层玻璃或硬塑料平板作为基层,表面涂有一层透明氧化金属(透明的导电电阻)导电层,上面再盖有一层外表面硬化处理、光滑防擦的塑料层、它的内表面也涂有一层涂层、在他们之间有许多细小的(小于 1/1000 英寸)的透明隔离点把两层导电层隔开绝缘。 当手指触摸屏幕时,两层导电层在触摸点位置就有了接触,电阻发生变化,在 X 和 Y 两个方向上产生信号,然后送触摸屏控制器。控制器侦测到这一接触并计算出(X,Y)的位置,再根据获得的位置模拟鼠标的方式运作。这就是电阻技术触摸屏的最基本的原理,具体情况如下图所示:
24CXX系列芯片属于EEPROM(Electrically Erasable Programmable read only memory)即掉电可擦可编程只读存储器,是一种掉电后数据不丢失(不挥发)存储芯片。
该系列芯片由美国Mcrochip公司出品,1-512K位的支持I2C总线数据传送协议的串行CMOS E2PROM,可用电擦除,可编程自定时写周期(包括自动擦除时间不超过10ms,典型时间为5ms)的。串行E2PROM一般具有两种写入方式,一种是字节写入方式,还有另一种页写入方式。允许在一个写周期内同时对1个字节到一页的若干字节的编程写入,1页的大小取决于芯片内页寄存器的大小。其中,AT24C01具有8字节数据的页面写能力,AT24C02/04/08/16具有16字节数据的页面写能力,AT24C32/64具有32字节数据的页面写能力。
CobeMX设置
这一块需要用到之前的按键和24C02芯片(EEPROM),这个芯片的作用是用来储存屏幕的校准信息和密码的。这个芯片是我这块开发板自带的,这个每个开发板可能不太一样,需要根据自己的板子来使用,但是实现的功能都一样,是为了在掉电后还可以储存信息。
触摸是使用SPI方式进行通讯的,这里选择软件触发方式,同时提供4个按键的GPIO设置:
这里的KEY经过测试,只有这样才能正常使用。
在使用软件模拟SPI时需要使用到微秒级延时,这里需要打开TIM7,并设置为71分频,具体操作如图:
接下来配置EEPROM,这块需要选择I2C方式通信,这里因为我的开发板中设计的接口只能选择I2C2(如果发现自己的屏幕数据在复位后无法保存,大概率是I2C接口选择错误),选择模式为I2C模式。
这些配置完成后就可以生成代码了。
程序设计
这部分我们仍然需要创建文件夹来存放EEPROM,KEY和触摸的程序,分别创建24C02_EEPROM,KEY和touch文件,在其中创建相应的.c和.h文件,添加到主函数中。
在key.c中我们实现按键的识别功能:
// 轮询4个按键,返回按键值KEYS ScanKeys(uint32_t timeout){KEYS key = KEY0;// 按下按键1if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET){HAL_Delay(10);if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET){key = KEY1;}}else if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET){HAL_Delay(10);if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET){key = KEY2;}}else if((HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET)){HAL_Delay(10);if((HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET)){key = KEY3;}}else if((HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_SET)){HAL_Delay(10);if((HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_SET)){key = KEY4;}}return key;}
相应的在key.h中需要设置一些对应的定义:
#include "main.h"// 按键的枚举变量typedef enum{KEY0 = 0, // 没有按键按下KEY1,KEY2,KEY3, KEY4,} KEYS;
#define KEY_WAIT_ALWAYS 0 // 作为ScanKeys的输入参数,表示一直等待按键输入KEYS ScanKeys(uint32_t timeout);
这样对按键的识别就完成了,接下来我们需要对按键做出反应,在主函数中对按键做出反应:
KEYS curKey;/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while(1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */curKey=ScanKeys(KEY_WAIT_ALWAYS);switch(curKey){case KEY0:break;case KEY1:{Touch_Set();break;}case KEY2:{Finger_Set();break;}case KEY3:{IDCard_Set();break;}// 修改密码case KEY4:{Password_Set();break;}}//end switchHAL_Delay(300);}
switch函数中的各个函数组成在上边的显示部分中.
接下来我们对触摸做出反应,触摸函数实际上就是将按下屏幕的位置进行计算,然后得出坐标.这部分函数有很多实现的方法,这里只是其中的一种,这些函数厂商会提供,可以直接复制厂商的,这里我就不进行详细讲解了,剩下的自己识别部分我们再详细说明:
#include "touch.h"#include "tim.h"#include "touch.h"#include "lcd.h"#include "24C02_EEPROM.h"/* 触摸校正因数设置 */#define LCD_ADJX_MIN (10) //读取四个点的最小X值#define LCD_ADJX_MAX (240 - LCD_ADJX_MIN) //读取四个点的最大X值#define LCD_ADJY_MIN (10) //读取四个点的最小Y值#define LCD_ADJY_MAX (320 - LCD_ADJY_MIN) //读取四个点的最大Y值#define LCD_ADJ_X (LCD_ADJX_MAX - LCD_ADJY_MIN)//读取方框的宽度#define LCD_ADJ_Y (LCD_ADJY_MAX - LCD_ADJY_MIN)//读取方框的高度#define TOUCH_READ_TIMES 5 //一次读取触摸值的次数#define TOUCH_X_CMD 0xD0 //读取X轴命令#define TOUCH_Y_CMD 0x90 //读取Y轴命令#define TOUCH_MAX 20 //预期差值#define TOUCH_X_MAX 4000 //X轴最大值#define TOUCH_X_MIN 100 //X轴最小值#define TOUCH_Y_MAX 4000 //Y轴最大值#define TOUCH_Y_MIN 100 //Y轴最小值//电阻屏SPI接口的基本输入输出#define MISO_Read() HAL_GPIO_ReadPin(T_MISO_GPIO_Port,T_MISO_Pin)#define MOSI_Out0() HAL_GPIO_WritePin(T_MOSI_GPIO_Port,T_MOSI_Pin,GPIO_PIN_RESET)#define MOSI_Out1() HAL_GPIO_WritePin(T_MOSI_GPIO_Port,T_MOSI_Pin,GPIO_PIN_SET)#define SCK_Out0() HAL_GPIO_WritePin(T_SCK_GPIO_Port,T_SCK_Pin,GPIO_PIN_RESET)#define SCK_Out1() HAL_GPIO_WritePin(T_SCK_GPIO_Port,T_SCK_Pin,GPIO_PIN_SET)#define TCS_Out0() HAL_GPIO_WritePin(TP_CS_GPIO_Port,TP_CS_Pin,GPIO_PIN_RESET)#define TCS_Out1() HAL_GPIO_WritePin(TP_CS_GPIO_Port,TP_CS_Pin,GPIO_PIN_SET)#define TOUCH_AdjDelay500ms() HAL_Delay(500)TouchPointDef TouchPoint; //触摸点数据TouchParaDef TouchPara; //触摸屏校正参数,全局静态变量//SPI写数据//向触摸屏写入1byte数据//num:要写入的数据void TOUCH_Write_Byte(uint8_t num){ uint8_t count=0;for(count=0;count<8;count++) { if(num & 0x80)MOSI_Out1();elseMOSI_Out0();num<<=1; SCK_Out0();Delay_us(1);SCK_Out1(); //上升沿有效}}//SPI读数据 ,软件模拟SPI//从触摸屏IC读取adc值//CMD:指令//返回值:读到的数据 uint16_t TOUCH_Read_AD(uint8_t CMD){ uint8_t count=0;uint16_t Num=0;SCK_Out0(); //TCLK=0; //先拉低时钟MOSI_Out0(); //TDIN=0; //拉低数据线TCS_Out0(); //TCS=0; //选中触摸屏ICTOUCH_Write_Byte(CMD);//发送命令字Delay_us(6); //delay_us(6); //ADS7846的转换时间最长为6usSCK_Out0(); //TCLK=0;Delay_us(1); //delay_us(1);SCK_Out1(); //TCLK=1; //给1个时钟,清除BUSYDelay_us(1); //delay_us(1);SCK_Out0(); //TCLK=0;for(count=0;count<16;count++)//读出16位数据,只有高12位有效{ Num<<=1; SCK_Out0(); //TCLK=0; //下降沿有效Delay_us(1); //delay_us(1);SCK_Out1(); //TCLK=1;if (MISO_Read()) //if(DOUT)Num++;}Num>>=4; //只有高12位有效.TCS_Out1(); //TCS=1; //释放片选return(Num); }uint16_t TOUCH_ReadData(uint8_t cmd){uint8_t i, j;uint16_t readValue[TOUCH_READ_TIMES], value;uint32_t totalValue;/* 读取TOUCH_READ_TIMES次触摸值 */for(i=0; i<TOUCH_READ_TIMES; i++){ /* 打开片选 */// TCS_Out0(); //TCS=0;/* 在差分模式下,XPT2046转换需要24个时钟,8个时钟输入命令,之后1个时钟去除 *//* 忙信号,接着输出12位转换结果,剩下3个时钟是忽略位 */ readValue[i]=TOUCH_Read_AD(cmd); // 发送命令,选择X轴或者Y轴// TCS_Out1(); //TCS=1;}/* 滤波处理 *//* 首先从大到小排序 */for(i=0; i<(TOUCH_READ_TIMES - 1); i++){for(j=i+1; j<TOUCH_READ_TIMES; j++){/* 采样值从大到小排序排序 */if(readValue[i] < readValue[j]){value = readValue[i];readValue[i] = readValue[j];readValue[j] = value;}}}/* 去掉最大值,去掉最小值,求平均值 */j = TOUCH_READ_TIMES - 1;totalValue = 0;for(i=1; i<j; i++) //求y的全部值{totalValue += readValue[i];}value = totalValue / (TOUCH_READ_TIMES - 2);return value;}//返回值为0表示有触摸操作uint8_t TOUCH_ReadXY(uint16_t *xValue, uint16_t *yValue){ uint16_t xValue1, yValue1, xValue2, yValue2;xValue1 = TOUCH_Read_AD(TOUCH_X_CMD);yValue1 = TOUCH_Read_AD(TOUCH_Y_CMD);xValue2 = TOUCH_Read_AD(TOUCH_X_CMD);yValue2 = TOUCH_Read_AD(TOUCH_Y_CMD);/* 查看两个点之间的采样值差距 */if(xValue1 > xValue2)*xValue = xValue1 - xValue2;else*xValue = xValue2 - xValue1;if(yValue1 > yValue2)*yValue = yValue1 - yValue2;else*yValue = yValue2 - yValue1;/* 判断采样差值是否在可控范围内 */if((*xValue > TOUCH_MAX+0) || (*yValue > TOUCH_MAX+0)) return 0xFF;/* 求平均值 */*xValue = (xValue1 + xValue2) / 2;*yValue = (yValue1 + yValue2) / 2;/* 判断得到的值,是否在取值范围之内 */if((*xValue > TOUCH_X_MAX+0) || (*xValue < TOUCH_X_MIN)|| (*yValue > TOUCH_Y_MAX+0) || (*yValue < TOUCH_Y_MIN))return 0xFF;elsereturn 0; //读取成功,有触控操作}uint8_t TOUCH_ReadAdjust(uint16_t x, uint16_t y, uint16_t *xValue, uint16_t *yValue){uint8_t i;uint32_t timeCont=0;/* 读取校正点的坐标 */LCD_Clear(WHITE);LCD_DrowSign(x, y, RED);i = 0;while(1){if(!TOUCH_ReadXY(xValue, yValue)){i++;if(i > 10) //延时一下,以读取最佳值{LCD_DrowSign(x, y, WHITE);return 0;}}timeCont++;/* 超时退出 */if(timeCont > 0xFFFFFFFE){LCD_DrowSign(x, y, WHITE);return 0xFF;}}}//触摸屏校准,依次在LCD的4个角上显示十字符号,用户点击后获取输出,计算触摸屏参数void TOUCH_Adjust(void){uint16_t px[2], py[2], xPot[4], yPot[4];float xFactor, yFactor;/* 读取第一个点 */if(TOUCH_ReadAdjust(LCD_ADJX_MIN, LCD_ADJY_MIN, &xPot[0], &yPot[0]))return;TOUCH_AdjDelay500ms();/* 读取第二个点 */if(TOUCH_ReadAdjust(LCD_ADJX_MIN, LCD_ADJY_MAX, &xPot[1], &yPot[1]))return;TOUCH_AdjDelay500ms();/* 读取第三个点 */if(TOUCH_ReadAdjust(LCD_ADJX_MAX, LCD_ADJY_MIN, &xPot[2], &yPot[2]))return;TOUCH_AdjDelay500ms();/* 读取第四个点 */if(TOUCH_ReadAdjust(LCD_ADJX_MAX, LCD_ADJY_MAX, &xPot[3], &yPot[3]))return;TOUCH_AdjDelay500ms();/* 处理读取到的四个点的数据,整合成对角的两个点 */px[0] = (xPot[0] + xPot[1]) / 2;py[0] = (yPot[0] + yPot[2]) / 2;px[1] = (xPot[3] + xPot[2]) / 2;py[1] = (yPot[3] + yPot[1]) / 2;/* 求出比例因数 */xFactor = (float)LCD_ADJ_X / (px[1] - px[0]);yFactor = (float)LCD_ADJ_Y / (py[1] - py[0]);/* 求出偏移量 */TouchPara.xOffset = (int16_t)LCD_ADJX_MAX - ((float)px[1] * xFactor);TouchPara.yOffset = (int16_t)LCD_ADJY_MAX - ((float)py[1] * yFactor);/* 将比例因数进行数据处理,然后保存 */TouchPara.xFactor = xFactor ;TouchPara.yFactor = yFactor ;TouchPara.isSaved = TOUCH_PARA_SAVED;}uint8_t TOUCH_Scan(void){if(TOUCH_ReadXY(&TouchPoint.Vx, &TouchPoint.Vy)) //没有触摸return 0xFF;/* 根据物理坐标值,计算出彩屏坐标值 */TouchPoint.Lcdx = TouchPoint.Vx * TouchPara.xFactor + TouchPara.xOffset;TouchPoint.Lcdy = TouchPoint.Vy * TouchPara.yFactor + TouchPara.yOffset;/* 查看彩屏坐标值是否超过彩屏大小 */if(TouchPoint.Lcdx > 240)TouchPoint.Lcdx = 240;if(TouchPoint.Lcdy > 320)TouchPoint.Lcdy = 320;return 0; }void TOUCH_ScanAfterINT(void) // T_PEN中断后读取{TouchPoint.Vx = TOUCH_ReadData(TOUCH_X_CMD);TouchPoint.Vy = TOUCH_ReadData(TOUCH_Y_CMD);/* 根据物理坐标值,计算出彩屏坐标值 */TouchPoint.Lcdx = TouchPoint.Vx * TouchPara.xFactor + TouchPara.xOffset;TouchPoint.Lcdy = TouchPoint.Vy * TouchPara.yFactor + TouchPara.yOffset;/* 查看彩屏坐标值是否超过彩屏大小 */if(TouchPoint.Lcdx > 240)TouchPoint.Lcdx = 240;if(TouchPoint.Lcdy > 320)TouchPoint.Lcdy = 320;}uint16_t LCD_CurX=0; //当前位置Xuint16_t LCD_CurY=0; //当前位置Yvoid ShowTouchPara(void)//显示触摸屏参数{uint16_t IncY=16; //Y间距LCD_ShowString(10,0,240,16,16,(uint8_t*)"**Parameters of touch screen:");LCD_ShowString(0,16,240,16,16,(uint8_t*)"xOffset= ");LCD_ShowChar(80, 16,TouchPara.xOffset,16,0);LCD_ShowString(0,32,240,16,16,(uint8_t*)"yOffset= ");LCD_ShowChar(80, 32,TouchPara.yOffset,16,0);LCD_ShowString(0,32+IncY,240,16,16,(uint8_t*)"xFactor= ");LCD_ShowxNum(80, 32+IncY,TouchPara.xFactor, 4,16,0);LCD_ShowString(0,32+IncY+IncY,240,16,16,(uint8_t*)"yFactor= ");LCD_ShowxNum(80, 32+IncY+IncY,TouchPara.yFactor, 4,16,0);}void TouchCalibrate(void)//进行触摸屏测试,获取参数{LCD_ShowString(10,0,240,16,16,(uint8_t*)"**Touch screen calibration");LCD_ShowString(10,16,240,16,16,(uint8_t*)"A red cross will display on");LCD_ShowString(10,32,240,16,16,(uint8_t*)"the 4 corners of LCD. ");LCD_ShowString(10,48,240,16,16,(uint8_t*)"Touch red cross one by one.");LCD_ShowString(10,64,240,16,16,(uint8_t*)"Press any key to start...");HAL_Delay(3000);TOUCH_Adjust(); //触摸屏校正时会清屏EP24C_WriteLongData(TOUCH_PARA_ADDR, &TouchPara.isSaved, sizeof(TouchPara));LCD_CurY=40;ShowTouchPara();//显示触摸屏参数LCD_ShowString(10,80,240,16,16,(uint8_t*)"Press any key to enter GUI");HAL_Delay(3000);}
在这些函数之后,我们需要对之前的屏幕下方的按键部分进行识别,这些按键分为数字和命令,所以得分成两个单独的函数,这些函数的实现部分,我在注释中有详细说明,但是这个是之前写的,实现的逻辑不太好,但是还能用,也就不太想改了(就是懒):
// a用来储存当前的数字,c用来检测位置uint8_t a,c = 0;extern uint8_t Password[6];// 位置和第几个数字对应uint8_t a_c(uint8_t x){if(c == 0)return 0;if(c == 1)return 20;if(c == 2)return 40;if(c == 3)return 60;if(c == 4)return 80;if(c == 5)return 100;if(c == 6) // 密码过长了LCD_ShowString(10,130,240,16,16,(uint8_t*)"The password is too long!");HAL_Delay(3000);LCD_Fill(10,130, 240, 170, WHITE);return 0;}// 定义一个储存密码的数组uint8_t PasswordIn[6];// 这个函数用来检测触摸的位置以及做出反应uint8_t Touch_Num(void){// 返回值uint8_t i;if(TOUCH_Scan() == 0) // 触摸屏扫描{// 1if(TouchPoint.Lcdx < 80 && TouchPoint.Lcdy > 200&&TouchPoint.Lcdy<230){a = a_c(c);PasswordIn[c] = 1;LCD_ShowNum(a, 150, 1, 1, 16);c+=1;i = 1;return i;}// 2else if(TouchPoint.Lcdx<160&&TouchPoint.Lcdy > 200&&TouchPoint.Lcdy<230&&TouchPoint.Lcdx>80){a = a_c(c);PasswordIn[c] = 2;LCD_ShowNum(a, 150, 2, 1, 16);c+=1;i = 2;return i;}// 3else if(TouchPoint.Lcdx>160&&TouchPoint.Lcdy > 200&&TouchPoint.Lcdy<230){a = a_c(c);PasswordIn[c] = 3;LCD_ShowNum(a, 150, 3, 1, 16);c+=1;i = 3;return i;}// 4else if(TouchPoint.Lcdx< 80&&TouchPoint.Lcdy > 230&&TouchPoint.Lcdy<260){a = a_c(c);PasswordIn[c] = 4;LCD_ShowNum(a, 150, 4, 1, 16);c+=1;i = 4;return i;}// 5else if(TouchPoint.Lcdx<160&&TouchPoint.Lcdy > 230&&TouchPoint.Lcdy<260&&TouchPoint.Lcdx>80){a = a_c(c);PasswordIn[c] = 5;LCD_ShowNum(a, 150, 5, 1, 16);c+=1;i = 5;return i;}// 6else if(TouchPoint.Lcdx>160&&TouchPoint.Lcdy > 230&&TouchPoint.Lcdy<260){a = a_c(c);PasswordIn[c] = 6;LCD_ShowNum(a, 150, 6, 1, 16);c+=1;i = 6;return i;}// 7else if(TouchPoint.Lcdx<80&&TouchPoint.Lcdy>260&&TouchPoint.Lcdy<290){a = a_c(c);PasswordIn[c] = 7;LCD_ShowNum(a, 150, 7, 1, 16);c+=1;i = 7;return i;}// 8else if(TouchPoint.Lcdx<160&&TouchPoint.Lcdx>80&&TouchPoint.Lcdy>260&&TouchPoint.Lcdy<290){a = a_c(c);PasswordIn[c] = 8;LCD_ShowNum(a, 150, 8, 1, 16);c+=1;i = 8;return i;}// 9else if(TouchPoint.Lcdx>160&&TouchPoint.Lcdy>260&&TouchPoint.Lcdy<290){a = a_c(c);PasswordIn[c] = 9;LCD_ShowNum(a, 150, 9, 1, 16);c+=1;i = 9;return i;}// *——表示输入错误,重新输入上一位else if(TouchPoint.Lcdx<80&&TouchPoint.Lcdy>290){c-=1;a = a_c(c);LCD_Fill(a, 150, a+20, 166, WHITE);i = 0;return i;}// 0else if(TouchPoint.Lcdx<160&&TouchPoint.Lcdx>80&&TouchPoint.Lcdy>290){a = a_c(c);PasswordIn[c] = 0;LCD_ShowNum(a, 150, 0, 1, 16);c+=1;i = 0;return i;}// #——表示确定密码输入else if(TouchPoint.Lcdx>160&&TouchPoint.Lcdy>290){c = 0;for(uint8_t i = 0;i<6;i++){// 输出密码错误if(PasswordIn[i] != Password[i]){LCD_ShowString(10,130,240,16,16,(uint8_t*)"The password is incorrect!");HAL_Delay(3000);LCD_Fill(0,130, 239, 170, WHITE);return 0;}}// 密码正确LCD_ShowString(10,130,240,16,16,(uint8_t*)"The password is correct!");HAL_Delay(3000);LCD_Fill(0,130, 239, 170, WHITE);}}return 0;}// 触摸位置是命令的位置void TOUCH_Command(void){// 删除指纹if(TouchPoint.Lcdx < 80 && TouchPoint.Lcdy > 170&&TouchPoint.Lcdy<200){// 请选择想删除的指纹LCD_ShowString(10,100,240,16,16,(uint8_t*)"Please select the fingerprint!");HAL_Delay(3000);LCD_Fill(0,100, 240, 170, WHITE);// 这个函数用来检测触摸的位置以及做出反应uint8_t i = Touch_Num();// 删除想要删除的指纹IDif(i>0){// Del_FR(i);}}// 修改密码else if(TouchPoint.Lcdx<160&&TouchPoint.Lcdy > 170&&TouchPoint.Lcdy<200&&TouchPoint.Lcdx>80){LCD_ShowString(10,100,240,16,16,(uint8_t*)"Please enter previous password!");// c = 0;for(c = 0;c<6;){Touch_Num();HAL_Delay(200);}for(uint8_t i = 0;i<6;i++){// 输入密码错误if(PasswordIn[i] != Password[i]){LCD_ShowString(10,130,240,16,16,(uint8_t*)"The password is incorrect!");HAL_Delay(3000);LCD_Fill(0, 130,240, 165, WHITE); //清除信息显示区return;}}// 密码正确LCD_ShowString(10,130,240,16,16,(uint8_t*)"The password is correct!");HAL_Delay(3000);LCD_Fill(0, 130,240, 165, WHITE); //清除信息显示区// 输出请输入新密码LCD_ShowString(10,130,240,16,16,(uint8_t*)"Please enter a new password!");for(c = 0;c<6;){// c = 0;Touch_Num();HAL_Delay(200);}LCD_Fill(0, 130,240, 146, WHITE); //清除信息显示区// 输出密码修改成功LCD_ShowString(10,130,240,16,16,(uint8_t*)"The password is OK!");HAL_Delay(3000);LCD_Fill(0, 130,240, 165, WHITE); //清除信息显示区EP24C_WriteLongData(PASSWORD_PARA_ADDR, PasswordIn,sizeof(PasswordIn));c = 0;}// 新建指纹else if(TouchPoint.Lcdx>160&&TouchPoint.Lcdy > 170&&TouchPoint.Lcdy<200){// 请选择新建指纹的位置LCD_ShowString(10,100,240,16,16,(uint8_t*)"Select the new fingerprint");HAL_Delay(3000);LCD_Fill(0,100, 240, 170, WHITE);// 这个函数用来检测触摸的位置以及做出反应uint8_t i = Touch_Num();if(i>0){// 录入指纹ID// Add_FR(i);}}}void Touch_Point(void) // 将之前的函数进行合并,主函数中只需要调用这个函数就足够了.{if(TOUCH_Scan() == 0) // 触摸屏扫描{TOUCH_Command();Touch_Num();}}// 微秒级延时(上边会用到)void Delay_us(uint16_t delay){__HAL_TIM_DISABLE(&htim7);__HAL_TIM_SET_COUNTER(&htim7,0); // 设置计数值初值__HAL_TIM_ENABLE(&htim7);uint16_t curCnt = 0;while(1) // 开始计数{curCnt = __HAL_TIM_GET_COUNTER(&htim7);if(curCnt>delay)break;}__HAL_TIM_DISABLE(&htim7);}
Touch.c所对应的touch.h中的程序如下:
#ifndef __TOUCH_H#define __TOUCH_H #include "main.h"#include "lcd.h"// 微秒级延时void Delay_us(uint16_t delay);/* 定义数据类型 */// 触摸点数据结构体定义typedef struct{uint16_t Vx; //XPT2046输出的X轴电压值uint16_t Vy; //XPT2046输出的Y轴电压值uint16_t Lcdx; //计算的LCD坐标Xuint16_t Lcdy; //计算的LCD坐标Y} TouchPointDef;extern TouchPointDef TouchPoint; //触摸点数据全局变量// 电阻触摸屏校正参数, 需要保存到EEPROMtypedef struct{uint8_t isSaved; // 参数是否已保存到EEPROMint16_t xOffset; //偏移量int16_t yOffset;float xFactor; //相乘因子float yFactor;} TouchParaDef;extern TouchParaDef TouchPara; //触摸屏校准参数全局变量#define TOUCH_PARA_SAVED 'S' //表示触摸校准参数准备好了#define TOUCH_PARA_ADDR 80 //校正参数在24C02中的首地址,必须是页的起始地址,也就是8的整数倍//触摸屏校正,会清除屏幕,依次在屏幕四个角上显示红色十字符号,点击进行测试。//计算的校准参数保存在变量TouchPara里,并保存到EEPROMvoid TOUCH_Adjust(void);//触摸屏扫描,返回值为0表示有触摸操作,触摸点保存到变量TouchPoint里uint8_t TOUCH_Scan(void);void TOUCH_ScanAfterINT(void); //T_PEN中断后读取// 新定义的两个函数//显示触摸屏参数,即全局变量TouchPara的数据void ShowTouchPara(void);//进行触摸屏测试,内部会调用TOUCH_Adjust()void TouchCalibrate(void);// 这个函数用来检测触摸的位置以及做出反应void Touch_Point(void);#endif /* __TOUCH_H */
在上边的函数中使用的EEPROM,这些使用的地方实际上就是将屏幕的坐标存入只读存储器中.接下来就进入24C02.c文件中,这个文件主要定义存储的实现过程,具体这些实现方法如下程序,这些也是厂商提供:
#include "24C02_EEPROM.h"#define EP24C_TIMEOUT 200 //超时等待时间,单位:节拍数#define EP24C_MEMADD_SIZE I2C_MEMADD_SIZE_8BIT //存储器地址大小,8位地址//检查设备是否准备好 I2C 通信,返回HAL_OK 表示OKHAL_StatusTypeDef EP24C_IsDeviceReady(void){uint32_t Trials=10; //尝试次数HAL_StatusTypeDef result=HAL_I2C_IsDeviceReady(&I2C_HANDLE,DEV_ADDR_24CXX,Trials,EP24C_TIMEOUT);return result;}//向任意地址写入1字节的数据,memAddr 是存储器内部地址,byteData 是需要写入的1字节数据HAL_StatusTypeDef EP24C_WriteOneByte(uint16_t memAddress, uint8_t byteData){HAL_StatusTypeDef result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX, memAddress, EP24C_MEMADD_SIZE, &byteData, 1,EP24C_TIMEOUT);return result;}//从任意地址读出1字节的数据,memAddr 是存储器内部地址,byteData是读出的1字节数据HAL_StatusTypeDef EP24C_ReadOneByte(uint16_t memAddress, uint8_t byteData){HAL_StatusTypeDef result=HAL_I2C_Mem_Read(&I2C_HANDLE,DEV_ADDR_24CXX, memAddress, EP24C_MEMADD_SIZE, &byteData, 1,EP24C_TIMEOUT);return result;}//连续读取数据,任意地址,任意长度,不受页的限制HAL_StatusTypeDef EP24C_ReadBytes(uint16_t memAddress, uint8_t *pBuffer,uint16_t bufferLen){if (bufferLen>MEM_SIZE_24CXX) //超过总存储容量return HAL_ERROR;HAL_StatusTypeDef result=HAL_I2C_Mem_Read(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,EP24C_MEMADD_SIZE,pBuffer,bufferLen,EP24C_TIMEOUT);return result;}//限定在一个页内写入连续数据,最多 8 字节。从任意起始地址开始,但起始地址+数据长度不能超过页边界HAL_StatusTypeDef EP24C_WriteInOnePage(uint16_t memAddress, uint8_t *pBuffer,uint16_t bufferLen){if (bufferLen>MEM_SIZE_24CXX) //超过总存储容量return HAL_ERROR;HAL_StatusTypeDef result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,EP24C_MEMADD_SIZE,pBuffer,bufferLen,EP24C_TIMEOUT); return result; }//写任意长的数据,可以超过8字节,但数据地址必须从页首开始,即8XN。自动分解为多次写入HAL_StatusTypeDef EP24C_WriteLongData(uint16_t memAddress, uint8_t *pBuffer,uint16_t bufferLen){if (bufferLen>MEM_SIZE_24CXX) //超过总存储容量return HAL_ERROR;HAL_StatusTypeDef result=HAL_ERROR;if (bufferLen<=PAGE_SIZE_24CXX) //不超过1个page,直接写入后退出{result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,EP24C_MEMADD_SIZE,pBuffer,bufferLen,EP24C_TIMEOUT); return result;}uint8_t *pt=pBuffer; //临时指针,不能改变传入的指针uint16_t pageCount=bufferLen/PAGE_SIZE_24CXX; //Page个数for(uint16_t i=0;i<pageCount;i++) //一次写入一个page 的数据{result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,EP24C_MEMADD_SIZE,pt, PAGE_SIZE_24CXX,EP24C_TIMEOUT); pt += PAGE_SIZE_24CXX;memAddress += PAGE_SIZE_24CXX;HAL_Delay(5); //必须有延时,以等待页写完if (result != HAL_OK)return result;}uint16_t leftBytes=bufferLen%PAGE_SIZE_24CXX; //余数if (leftBytes>0) //写入剩余的数据result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,EP24C_MEMADD_SIZE,pt, leftBytes,EP24C_TIMEOUT); return result; }
对应的.h文件程序:
#ifndef __24C02_EEPROM_H#define __24C02_EEPROM_H#include "main.h"#include "i2c.h"#define I2C_HANDLE hi2c2 // I2C接口外设对象变量#define DEV_ADDR_24CXX 0x00A0 // 24c02的写地址#define PASSWORD_PARA_ADDR 16 //密码在24C02中的首地址,必须是页的起始地址,也就是8的整数倍#define PAGE_SIZE_24CXX 0x0008 // 24c02的page大小为8字节#define MEM_SIZE_24CXX (uint16_t)256 // 24c02总容量为256字节//检查设备是否准备好 I2C 通信,返回HAL_OK 表示OKHAL_StatusTypeDef EP24C_IsDeviceReady(void);//向任意地址写入1字节的数据,memAddr 是存储器内部地址,byteData 是需要写入的1字节数据HAL_StatusTypeDef EP24C_WriteOneByte(uint16_t memAddress, uint8_t byteData);//从意地址读出1字节的数据,memAddr 是存储器内部地址,byteData是读出的1字节数据HAL_StatusTypeDef EP24C_ReadOneByte(uint16_t memAddress, uint8_t byteData);//连续读取数据,任意地址,任意长度,不受页的限制HAL_StatusTypeDef EP24C_ReadBytes(uint16_t memAddress, uint8_t *pBuffer, uint16_t bufferLen);//限定在一个页内写入连续数据,最多 8 字节。从任意起始地址开始,但起始地址+数据长度不能超过页边界HAL_StatusTypeDef EP24C_WriteInOnePage(uint16_t memAddress, uint8_t *pBuffer,uint16_t bufferLen);//写任意长的数据,可以超过8字节,但数据地址必须从页首开始,即8XN。自动分解为多次写入HAL_StatusTypeDef EP24C_WriteLongData(uint16_t memAddress, uint8_t *pBuffer,uint16_t bufferLen);#endif /* __24C02_EEPROM_H */
这些完成后,我们就可以将之前的密码存入rom中了(下面的部分在main.c中):
//====1. 读取保存在EEPROM中的电阻触摸屏参数EP24C_ReadBytes(TOUCH_PARA_ADDR, &TouchPara.isSaved, sizeof(TouchPara));if (TouchPara.isSaved ==TOUCH_PARA_SAVED)LCD_ShowString(10,80,240,12,12,(uint8_t*)"Touch-Res has been calibrated");elseLCD_ShowString(10,80,240,12,12,(uint8_t*)"Touch-Res has not been calibrated");// 向EEPROM中写入初始密码// EP24C_WriteLongData(PASSWORD_PARA_ADDR, Password,sizeof(Password));// 从EEPROM中读取密码EP24C_ReadBytes(PASSWORD_PARA_ADDR,Password,sizeof(Password));// 检验读取的密码// for(uint8_t i = 0;i<6;i++)// {// LCD_ShowxNum(10,80,Password[i],1,16,0);// HAL_Delay(500);// }
至此就完成了屏幕和密码部分的所有操作,但是这些操作在主函数中实现的效率不高,所以我们将主函数中的内容转到freertos中.
打开CobeMX,找到FREERTOS,选择模式为:CMSIS_V2.然后先创建两个任务,分别为Key和Touch,这里设置Key的优先级高于Touch,具体设置如图:
设置完成后,这里需要将SYS的时钟更改为TIM6, 因为之前没有使用FreeRTOS时HAL时钟默认是SysTick,但是现在FreeRTOS使用了SysTick,所以我们要为HAL时钟重新配置基础时钟.完成时钟配置后生成代码.
将之前main.c中的while循环的key相关代码放入Key的for循环中,将触摸相关的代码放入Touch的循环中,
现在main函数中的代码如下:
int main(void){/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_FSMC_Init();MX_I2C2_Init();MX_TIM7_Init();/* USER CODE BEGIN 2 */LCD_Init();// 屏幕显示初始化Bsp_Lcd_Init();//====1. 读取保存在EEPROM中的电阻触摸屏参数EP24C_ReadBytes(TOUCH_PARA_ADDR, &TouchPara.isSaved, sizeof(TouchPara));if (TouchPara.isSaved ==TOUCH_PARA_SAVED)LCD_ShowString(10,80,240,12,12,(uint8_t*)"Touch-Res has been calibrated");elseLCD_ShowString(10,80,240,12,12,(uint8_t*)"Touch-Res has not been calibrated");// 向EEPROM中写入初始密码// EP24C_WriteLongData(PASSWORD_PARA_ADDR, Password,sizeof(Password));// 从EEPROM中读取密码EP24C_ReadBytes(PASSWORD_PARA_ADDR,Password,sizeof(Password));// 检验读取的密码// for(uint8_t i = 0;i<6;i++)// {// LCD_ShowxNum(10,80,Password[i],1,16,0);// HAL_Delay(500);// }/* USER CODE END 2 *//* Init scheduler */osKernelInitialize(); /* Call init function for freertos objects (in freertos.c) */MX_FREERTOS_Init();/* Start scheduler */osKernelStart();/* We should never get here as control is now taken by the scheduler *//* Infinite loop *//* USER CODE BEGIN WHILE */while(1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */}
Freertos.c中各个函数如下:
void Key(void *argument){/* USER CODE BEGIN Key */KEYS curKey;/* Infinite loop */for(;;){curKey=ScanKeys(KEY_WAIT_ALWAYS);switch(curKey){case KEY0:break;case KEY1:{Touch_Set();break;}case KEY2:{Finger_Set();break;}case KEY3:{IDCard_Set();break;}// 修改密码case KEY4:{Password_Set();break;}}//end switchvTaskDelay(300);}/* USER CODE END Key */}/* USER CODE BEGIN Header_Touch *//*** @brief Function implementing the Task_Touch thread.* @param argument: Not used* @retval None*//* USER CODE END Header_Touch */void Touch(void *argument){/* USER CODE BEGIN Touch *//* Infinite loop */for(;;){Touch_Point();vTaskDelay(300);}/* USER CODE END Touch */}
当然在这之前需要添加这些函数的头文件:
#include "bsp_lcd.h"#include "24C02_EEPROM.h"#include "bsp_key.h"#include "touch.h"
至此,有关屏幕的部分已经全部完成了.
这篇关于门锁系统——屏幕显示的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!