接上篇1.门锁系统——项目简介_only_print的博客-CSDN博客
本节主要先简单实现屏幕的显示功能,以供后期的显示需要。
设想先实现上部为显示整个信息,下部显示密码的选项。
目录
显示部分
本实验采用的是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至9
LCD_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;
// 按下按键1
if(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 switch
HAL_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();
else
MOSI_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; //选中触摸屏IC
TOUCH_Write_Byte(CMD);//发送命令字
Delay_us(6); //delay_us(6); //ADS7846的转换时间最长为6us
SCK_Out0(); //TCLK=0;
Delay_us(1); //delay_us(1);
SCK_Out1(); //TCLK=1; //给1个时钟,清除BUSY
Delay_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;
else
return 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; //当前位置X
uint16_t LCD_CurY=0; //当前位置Y
void 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) // 触摸屏扫描
{
// 1
if(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;
}
// 2
else 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;
}
// 3
else 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;
}
// 4
else 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;
}
// 5
else 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;
}
// 6
else 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;
}
// 7
else 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;
}
// 8
else 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;
}
// 9
else 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;
}
// 0
else 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();
// 删除想要删除的指纹ID
if(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坐标X
uint16_t Lcdy; //计算的LCD坐标Y
} TouchPointDef;
extern TouchPointDef TouchPoint; //触摸点数据全局变量
// 电阻触摸屏校正参数, 需要保存到EEPROM
typedef struct{
uint8_t isSaved; // 参数是否已保存到EEPROM
int16_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里,并保存到EEPROM
void 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 表示OK
HAL_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 表示OK
HAL_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");
else
LCD_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");
else
LCD_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 switch
vTaskDelay(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"
至此,有关屏幕的部分已经全部完成了.