学习方向:如何获取键值?
一、BS8116介绍
BS81x 系列芯片具有 2~16 个触摸按键,可用来检测外部触摸按键上人手的触摸动作。 该系列的芯片具有较高的集成度, 仅需极少的外部组件便可实现触摸按键的检测
工作原理:
在STM32与BS8116的通信过程中,STM32作为主设备,通过I2C接口向BS8116发送配置命令和数据。这些命令和数据用于设置BS8116的工作模式、灵敏度、按键布局等参数。BS8116在接收到这些配置后,会根据这些参数进行初始化,并准备好接收触摸输入。一旦有触摸动作发生在BS8116的触摸按键上,BS8116会检测到这个动作,并通过I2C接口将触摸状态数据通过IRQ引脚发送回STM32。STM32在接收到这些数据后,可以解析出触摸按键的状态,并根据应用程序的需求进行相应的处理,如点亮LED灯、播放声音或执行其他操作。
二、BS8116引脚说明
原理图:
BS8116_IIC_SCK--------PB6 //通用开漏输出
BS8116_IIC_SDA-------PB7 //通用开漏输出
BS8116_IRQ-----------PA1 //通用输入
三、BS8116通信方式
BS8116驱动芯片使用的通信方式是IIC通信
器件地址
7位从机地址 0x50 101 0000
8位器件地址 从机地址+读/写 1010 000x
读:0xa1 或者 0x50<<1 | 0x01
写:0xa0 或者 0x50<<1
通信问题
①IO口初始化时钟IO配置开漏输出
说明:从机也能拉低时钟线,不再是只能主机控制时钟线
②在IIC检测应答的最后一步要去掉安全作用(拉低时钟线)
错误现象:①烧录成功后,读取寄存器的值全为0x00 ②复位不了
说明:一笔数据 (8bit +ACK) 完成后,从机开始处理数据(从机忙碌), 无法接收 下一笔数据,此时从机将 SCL 拉低, 主机需等待 SCL 变为高电平时才可 以继续进行数据传送。 -->故IIC检测应答的最后一步无需拉低时钟线,因为在 发送完一笔数据后,从机会主动拉低时钟线
③在IIC起始信号的拉低时钟线后延时5us左右,再拉高数据线
错误现象:烧录成功后,读配置寄存器的值全为0x00
说明:在BS8116的IIC起始信号的拉低时钟线后延时约5微秒再拉高数据线,是为了确保信号的稳定传输、满足设备内部处理时间以及符合I2C通信协议的要求。
四、程序设计
宏定义
#define BS8116_IIC_SCL_H (GPIOB->ODR |= (1 << 6))
#define BS8116_IIC_SCL_L (GPIOB->ODR &= ~(1 << 6))
#define BS8116_IIC_SDA_OUT_H (GPIOB->ODR |= (1 << 7))
#define BS8116_IIC_SDA_OUT_L (GPIOB->ODR &= ~(1 << 7))
#define BS8116_IIC_SDA_INT (GPIOB->IDR & (1 << 7))
#define BS8116_IIC_SCL_INT (GPIOB->IDR & (1 << 6))
#define BS8116ADDR_W (0x50<<1)
#define BS8116ADDR_R (0x50<<1 | 0x01)
#define BS8116_IRQ (GPIOA->IDR & (1<<1))
IIC通信的相关程序
IIC的IO初始化配置
/***********************************************
*函数名 :BS8116_iic_IO_init
*函数功能 :IIC所用IO口初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 : PB6----SCL 通用开漏输出
PB7----SDA 通用开漏输出
************************************************/
void BS8116_iic_IO_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//端口时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //通用输出
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; //开漏
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 ; // 6 7
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; //速度
GPIO_Init(GPIOB,&GPIO_InitStruct);
//空闲状态
GPIO_SetBits(GPIOB, GPIO_Pin_6);//SCL
GPIO_SetBits(GPIOB, GPIO_Pin_7);//SDA
}
IIC起始信号
/***********************************************
*函数名 :iic_star
*函数功能 :IIC起始信号函数
*函数参数 :无
*函数返回值:无
*函数描述 :时钟线为高电平,数据线出现下降沿
************************************************/
void BS8116_iic_star(void)
{
//时钟线拉低,才可以动数据线
BS8116_IIC_SCL_L;
tim5_delay_us(5);--->标准IIC的不同之处
//数据线拉高,目的是为出现下降沿做准备
BS8116_IIC_SDA_OUT_H;
//时钟线拉高 产生起始信号的条件
BS8116_IIC_SCL_H;
tim5_delay_us(5);
//数据线拉低 可以产生起始信号
BS8116_IIC_SDA_OUT_L;
tim5_delay_us(5);
//安全
BS8116_IIC_SCL_L;
}
IIC停止信号
/***********************************************
*函数名 :iic_stop
*函数功能 :IIC停止信号函数
*函数参数 :无
*函数返回值:无
*函数描述 :时钟线为高电平,数据线出现上升沿
************************************************/
void BS8116_iic_stop(void)
{
//准备工作
BS8116_IIC_SCL_L;
BS8116_IIC_SDA_OUT_L;
//产生停止信号
BS8116_IIC_SCL_H;
tim5_delay_us(5);
BS8116_IIC_SDA_OUT_H;
tim5_delay_us(5);
}
IIC发送应答/不应答信号函数
/***********************************************
*函数名 :iic_send_ack
*函数功能 :发送应答/不应答信号
*函数参数 :u8 ack
*函数返回值:无
*函数描述 :应答: 时钟线为高电平,数据线已经保持好低电平了
不应答 时钟线为高电平,数据线已经保持好高电平了
参数:0 发送应答信号
参数:1 发送不应答信号
************************************************/
void BS8116_iic_send_ack(u8 ack)
{
BS8116_IIC_SCL_L; //时钟线拉低,才可以动数据线
tim5_delay_us(3);
if(ack == 0) //发送应答
{
BS8116_IIC_SDA_OUT_L; //让数据线先保持低电平
}
else if(ack == 1)
{
BS8116_IIC_SDA_OUT_H; //让数据线先保持高电平
}
tim5_delay_us(2);
BS8116_IIC_SCL_H; //产生了应答信号
tim5_delay_us(5);
//安全
BS8116_IIC_SCL_L;
}
IIC检测应答/不应答信号函数
/***********************************************
*函数名 :BS8116_iic_rec_ack
*函数功能 :检测应答/不应答信号
*函数参数 :无
*函数返回值:u8
*函数描述 :
返回0 表示应答信号
返回1 表示不应答信号
************************************************/
u8 BS8116_iic_rec_ack(void)
{
u8 ack = 0;
//数据线要为输入
BS8116_IIC_SCL_L;
//数据线切换输入状态
BS8116_IIC_SDA_OUT_H;
//检测应答/不应答信号
BS8116_IIC_SCL_L; //主机帮助从机拉低时钟线,从机才可以动数据线,来表现应答/不应答信号
tim5_delay_us(5);
BS8116_IIC_SCL_H; //主机才可以读数据线
tim5_delay_us(5);
if(BS8116_IIC_SDA_INT == 0)
{
//低电平——应答信号
ack = 0;
}
else
{
//高电平----不应答信号
ack = 1;
}
//安全
//BS8116_IIC_SCL_L;--->标准IIC不同之处
return ack;
}
IIC发送一个字节函数
/***********************************************
*函数名 :iic_send_byte
*函数功能 :IIC发送一个字节函数
*函数参数 :u8 data
*函数返回值:无
*函数描述 :
************************************************/
void BS8116_iic_send_byte(u8 data)
{
u8 i;
for(i=0;i<8;i++)
{
//时钟线拉低才可以改变数据线
BS8116_IIC_SCL_L;
tim5_delay_us(3);
//根据参数的对应位是0还是1决定数据线的高低
if(data & 0x80)
{
BS8116_IIC_SDA_OUT_H;
}
else
{
BS8116_IIC_SDA_OUT_L;
}
tim5_delay_us(2);
//时钟下拉高,帮助从机,这样从机才可以读这一位数据
BS8116_IIC_SCL_H;
tim5_delay_us(5);
//下一位数据,次高位变成最高位
data = data << 1;
}
//安全
BS8116_IIC_SCL_L;
}
IIC接收一个字节函数
/*******************************************
*函数名 :iic_read_byte
*函数功能 :主机通过IIC接收一个字节函数
*函数参数 :无
*函数返回值:u8
*函数描述 :
*********************************************/
u8 BS8116_iic_rec_byte(void)
{
u8 data;
u8 i;
//将IO口切换为输入
BS8116_IIC_SCL_L;
BS8116_IIC_SDA_OUT_H;
//读数据
for(i=0;i<8;i++)
{
BS8116_IIC_SCL_L; //主机帮助从机拉低时钟线
tim5_delay_us(5);
BS8116_IIC_SCL_H; //主机可以读数据
tim5_delay_us(5);
data = data << 1;
if(BS8116_IIC_SDA_INT)
data= data | 0x01;
}
//安全
BS8116_IIC_SCL_L;
return data;
}
BS8116的相关程序
BS8116初始化函数
(IIC通信初始化配置--->IRQ管脚初始化配置-->设置寄存器)
/**************************************************
*函数名 :BS8116_IO_init
*函数功能 :BS8116的管脚初始函数
*函数参数 :无
*函数返回值:无
*函数描述 : IO口模拟
BS8116_IRQ----------- PA1 //通用输入
****************************************************/
void BS8116_init(void)
{
u8 write_buff[21] = { 0x00,0x00,0x83,0xf3,0x98,
0x3f,0x3f,0x3f,0x3f,0x3f,
0x3f,0x3f,0x3f,0x3f,0x3f,
0x3f,0x3f,0x3f,0x3f,0x3f,0x7f};
//IIC通信初始化
BS8116_iic_IO_init();
//PA1
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;//输入模式
GPIO_InitStruct.GPIO_Pin = (GPIO_Pin_1) ;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
GPIO_Init(GPIOA, &GPIO_InitStruct);
//写入参数到寄存器
BS8116_WriteRegister(write_buff);
}
BS8116写寄存器值函数
校验和:把前面21个字节累加后取低8位
所以:IIC连续写22个字节的函数
作用:将下面的数据写入设置寄存器
什么是触发门坎值?
触发门坎值决定了触控芯片对触摸动作的感知灵敏度
触发门坎值是指触控芯片在检测触摸动作时所设定的一个阈值。当触摸信号达到或超过这个阈值时,触控芯片会认为发生了有效的触摸动作,并作出相应的响应。
以BS8116A-3触摸芯片为例,其触发门坎值可以通过I2C接口进行调整。根据相关资料,BS8116A-3的触发门坎值调整范围通常为8~63,值越大感度越低。
什么是校验和?
①校验和的作用
校验和是用于检测数据传输或存储中是否出现错误的一种方法。在BS8116A-3芯片中,校验和通常用于确保配置寄存器的数据在传输过程中没有发生变化。
②校验和的计算方式
在BS8116A-3中,校验和通常是通过对寄存器数据的每个字节进行累加得到的。具体来说,就是将配置寄存器中的所有字节值相加,得到一个累加和,然后将这个累加和作为校验和存储在某个寄存器中或用于后续的数据验证。
③校验和的应用
数据验证:在数据传输过程中,接收方可以重新计算接收到的数据的校验和,并与发送方提供的校验和进行比较。如果两者相等,则说明数据在传输过程中没有发生变化,是有效的;如果不相等,则说明数据在传输过程中可能出现了错误,需要进行错误处理。
错误检测:通过校验和,可以检测到数据传输或存储中的错误,从而提高系统的可靠性。
④注意事项
校验和的局限性:校验和虽然可以检测到数据传输中的错误,但并不能确定错误的位置和类型。因此,在出现错误时,通常需要采取其他措施来定位和纠正错误。
校验和的计算时机:在BS8116A-3中,校验和通常在配置寄存器数据写入后进行计算。此外,在读取配置寄存器数据时,也可以重新计算校验和以验证数据的完整性。
从机忙碌如何处理?
故接收应答后,不是主机帮从机拉低时钟线,而是从机自己拉低时钟线,主机则在等待时钟线被拉高
I2C通信的应答检测通常包括以下几个步骤:
主机发送数据或命令后,等待从机(BS8116)的应答信号。
从机接收到数据或命令后,会根据内部状态或配置决定是否应答。
如果从机决定应答,从机自己拉低时钟线,它会在应答时钟周期内将SDA线拉低(表示应答信号)。
主机检测到应答信号后,会继续进行后续的数据传输或命令执行。
BS8116写寄存器值函数:
/**************************************************
*函数名 :BS8116_WriteRegister
*函数功能 :BS8116写寄存器值函数
*函数参数 :u8*cmd
*函数返回值:u8
*函数描述 :
****************************************************/
u8 BS8116_WriteRegister(u8*cmd)
{
u32 check_sum = 0;
BS8116_iic_star();
BS8116_iic_send_byte(BS8116ADDR_W);
if(BS8116_iic_rec_ack())
{
BS8116_iic_stop( );
return 1;
}
//等待从机不忙
while(!BS8116_IIC_SCL_INT);
BS8116_iic_send_byte(0xB0); //寄存器的起始地址
if(BS8116_iic_rec_ack())
{
BS8116_iic_stop( );
return 2;
}
//等待从机不忙
while(!BS8116_IIC_SCL_INT);
/*发送数据*/
//发送21个寄存器数据和一个累加和数据
for(u8 i=1;i<=22;i++)
{
//第22次发送累加和
if(i==22)
{
BS8116_iic_send_byte(check_sum & 0xff); //发送校验和 低八位
}
//21之前要发送寄存器数据并且累加
else
{
BS8116_iic_send_byte(*cmd);
check_sum = check_sum + *cmd; //累加
cmd++;
}
//主机检测应答
if(BS8116_iic_rec_ack())
{
BS8116_iic_stop( );
return 3;
}
//等待从机不忙
while(!BS8116_IIC_SCL_INT);
}
BS8116_iic_stop( );
return 0;
}
BS8116读配置寄存器值函数
所以:IIC连续读21字节函数
注:读取数据后,需要选择继续应答还是不应答--->发送应答/不应答
/***********************************************
*函数名 :BS8116_Readregister
*函数功能 :BS8116读寄存器的值
*函数参数 :u8 *buff
*函数返回值:u8
*函数描述 :读到的21个寄存器的值
************************************************/
u8 BS8116_ReadRegister(u8*data)
{
BS8116_iic_star();
BS8116_iic_send_byte(BS8116ADDR_W);
if(BS8116_iic_rec_ack())
{
BS8116_iic_stop( );
return 1;
}
//从机把时钟拉低表示正在处理数据
//主机需要等待从机把时钟线拉高
while(!BS8116_IIC_SCL_INT);
BS8116_iic_send_byte(0xB0); //从起始寄存器地址开始
if(BS8116_iic_rec_ack())
{
BS8116_iic_stop( );
return 2;
}
//等待从机不忙
while(!BS8116_IIC_SCL_INT);
BS8116_iic_star();
BS8116_iic_send_byte(BS8116ADDR_R);
if(BS8116_iic_rec_ack())
{
BS8116_iic_stop( );
return 3;
}
//等待从机不忙
while(!BS8116_IIC_SCL_INT);
//读21个配置寄存器数据
for(u8 i=0;i<21;i++)
{
*data = BS8116_iic_rec_byte();
if(i==20) BS8116_iic_send_ack(1);
else BS8116_iic_send_ack(0);
data++;
}
BS8116_iic_stop( );
return 0;
}
BS8116读键值函数
思路:
根据手册的时序获取两个键值寄存器的值并且存储到一个u16类型的变量中,然后测试每一个按键的对应数据值,再根据所测的键值的对应数据值返回键值字符,如果无效键值则返回0xff
先高位后低位
所以:IIC连续两个字节获取键值函数
注:读取数据后,需要选择继续应答还是不应答--->发送应答/不应答
通过按下按键获取到的寄存器的值
按键按下,对应寄存器的位是1;按键抬起,对应寄存器的位是0
寄存器中按键标识 寄存器的值 键盘按键
KEY1--------------------------0x0001 KEY_1
KEY2--------------------------0x0002 KEY_4
KEY3--------------------------0x0004 KEY_*
KEY4--------------------------0x0008 KEY_7
KEY5--------------------------0x0010 KEY_#
KEY6--------------------------0x0020 KEY_9
KEY7--------------------------0x0040 KEY_6
KEY8--------------------------0x0080 KEY_3
KEY9--------------------------0x0100 KEY_8
KEY10------------------------0x0200 KEY_0
KEY11------------------------0x0400 KEY_2
KEY12------------------------0x0800 KEY_5
KEY13------------------------0x1000
KEY14------------------------0x2000
KEY15------------------------0x4000
KEY16------------------------0x8000
寄存器中按键标识与键盘按键值不匹配的原因:由于硬件上需要绕线导致的问题
/***********************************************
*函数名 :BS8116_ReadKey
*函数功能 :BS8116读键值
*函数参数 :无
*函数返回值:u8
*函数描述 :按键按下,对应寄存器的位是1;按键抬起,对应寄存器的位是0
************************************************/
u8 BS8116_ReadKey(void)
{
u16 key=0xff;
BS8116_iic_star();
BS8116_iic_send_byte(BS8116ADDR_W);
if(BS8116_iic_rec_ack())
{
BS8116_iic_stop( );
return 1;
}
//从机把时钟拉低表示正在处理数据
//主机需要等待从机把时钟线拉高
while(!BS8116_IIC_SCL_INT);
BS8116_iic_send_byte(0x08); //从起始寄存器地址开始
if(BS8116_iic_rec_ack())
{
BS8116_iic_stop( );
return 2;
}
//等待从机不忙
while(!BS8116_IIC_SCL_INT);
BS8116_iic_star();
BS8116_iic_send_byte(BS8116ADDR_R);
if(BS8116_iic_rec_ack())
{
BS8116_iic_stop( );
return 3;
}
//从机把时钟拉低表示正在处理数据
//主机需要等待从机把时钟线拉高
while(!BS8116_IIC_SCL_INT);
/*读取键值*/
key = BS8116_iic_rec_byte();//08
BS8116_iic_send_ack(0);
key = (BS8116_iic_rec_byte()<<8)| key;
BS8116_iic_send_ack(1);
BS8116_iic_stop();
// printf("key:0x%x\r\n",key);
switch(key)
{
case 0X0001:return '1';
case 0X0400:return '2';
case 0X0080:return '3';
case 0X0002:return '4';
case 0x0800:return '5';
case 0X0040:return '6';
case 0X0008:return '7';
case 0X0100:return '8';
case 0X0020:return '9';
case 0X0004:return '*';
case 0X0200:return '0';
case 0X0010:return '#';
}
return 0xff;
}
BS8116按键扫描函数
如何实现按一次按键,返回一次键值?
在获取键值函数的基础下,封装按键扫描函数
思路:
检测按键按下
读取键值,滤波--->如果是无效键值则重新获取键值,有效键值就锁定标志位
检测按键抬起
解锁标志位
返回键值
关键点:在获取到无效键值时,不锁标志位
/*
函数名: BS8116_scan
函数功能:按键扫描函数
返回值:u8
形参:void
函数说明:
*/
u8 BS8116_scan(void)
{
u8 keynum = 0xff;
static u8 keyflag = 1;
if(!BS8116_IRQ && keyflag)
{
keynum = BS8116_ReadKey();
if(keynum != 0xff)//滤波,如果是无效键值则重新获取键值,有效键值就锁标志位
{
keyflag = 0;
voice_work(0x1d);
}
}
else if(BS8116_IRQ)
{
keyflag = 1;
}
return keynum;
}
五、基于BS8116电容触摸按键的密码开锁
时间显示功能
数字不采取字库中的,而是使用更美观的艺术体(iconfont官网下载,然后取模)
显示艺术字符串
思路:
把所要显示的艺术体数字存储到一维数组中
把所要显示的艺术体数字取模48*48大小的数据存储到一维字模数组中
通过传入想要打印的艺术体数字在一维数组中查找,计算出所要显示的数字与一维数组中的第一个数字的偏移量
需要避免所要找的数字不是数字数组里的,并且如果不是数字数组里的,则需要退出程序
如果是数字数组里的,则:
遍历行-->取出每一行数据
遍历每行的每一个像素点-->根据所取出每一行数据,判断打点
/******************************************************************************
*函数名 :LCD_show_number_val
*函数功能 :显示艺术字符串
*函数参数 :u16 x,u16 y,u8 *numstr,u16 color,u8 mode,u16 b_color
*函数返回值:无
*函数描述 :
size 字号大小 16 32
mode 传入1 就带背景颜色
mode 传入0 就不带背景颜色
********************************************************************************/
void LCD_show_number_val(u16 x,u16 y,u8*numstr,u16 color,u8 mode,u16 b_color)
{
u8 i,j;
long long temp;
while(*numstr != '\0')
{
u16 n = 0;
/*计算要显示的数字与第一个数字的偏移量*/
while(numb_table[n] != '\0')//避免所要找的数字不是数字数组的
{
if(*numstr == numb_table[n])
{
break;
}
n++;
}
//n值就是数字个数偏移量
if(numb_table[n] == '\0')
{
return ;
}
for(i=0;i<48;i++)
{
//拿一行的数据
temp = (long long)number48[n*288+6*i]<<40 | (long long)number48[n*288+6*i+1]<<32 | (long long)number48[n*288+6*i+2]<<24 | (long long)number48[n*288+6*i+3]<<16 | (long long)number48[n*288+6*i+4]<<8 | (long long)number48[n*288+6*i+5];
//每行打点
for(j=0;j<48;j++)
{
if(temp & ((long long)1 << (47-j)))
{
LCD_point(x+j,y+i,color);
}
else
{
if(mode == 1)
{
LCD_point(x+j,y+i,b_color);
}
}
}
}
numstr++;
x+=48;
}
}
在图片上显示艺术字符串
在显示艺术字符串函数的基础上,图片的打点需要跳过头数据和照片内不需要打点的地方
/******************************************************************************
*函数名 :LCD_show_number_pic
*函数功能 :在图片上显示艺术字符串
*函数参数 :u16 x,u16 y,u8 *numstr,u16 color,u8 mode,const u8 *pic
*函数返回值:无
*函数描述 :
size 字号大小 16 32
mode 传入1 就带背景颜色
mode 传入0 就不带背景颜色
********************************************************************************/
void LCD_show_number_pic(u16 x,u16 y,u8*numstr,u16 color,u8 mode,const u8 *pic)
{
u16 n,i,j;
long long temp;
u16 *b_pic;
while(*numstr!='\0')
{
n = 0;
/*计算出要显示的艺术字与第一个艺术字符偏移量*/
while(numb_table[n] != '\0')
{
if(*numstr == numb_table[n])
{
break;
}
n++;
}
if(numb_table[n] == '\0')
{
return;
}
//一共的行数循环
for(i=0;i<48;i++)
{
temp = (long long)number48[n*288+6*i]<<40 | (long long)number48[n*288+6*i+1]<<32 | (long long)number48[n*288+6*i+2]<<24 | (long long)number48[n*288+6*i+3]<<16 | (long long)number48[n*288+6*i+4]<<8 | (long long)number48[n*288+6*i+5];
//每行的像素点个数循环
for(j=0;j<48;j++)
{
//判断要打点的像素点
if(temp & ((long long)1<<(47-j)))
{
LCD_point(x+j,y+i,color);
}
else
{
if(mode == 1)
{
b_pic = (u16 *)&pic[sizeof(HEADCOLOR)+((y+i)*LCD_W+(x+j))*2];
LCD_point(x+j,y+i,*b_pic);
}
}
}
}
numstr++;
x+=48;
}
}
说明:
b_pic = (u16 *)&pic[sizeof(HEADCOLOR)+((y+i)*LCD_W+(x+j))*2];
在图像数据pic中,跳过头部HEADCOLOR的大小,然后根据给定的坐标偏移量(y+i, x+j)和LCD宽度LCD_W,计算出目标像素的字节偏移量,并将这个偏移量转换为指向16位无符号整数的指针,最后将这个指针赋值给b_pic。这样,b_pic就指向了图像中特定位置的像素数据
利用RTC获取编译时间
思路:调用RTC初始化函数,利用sprintf将获取到的时间数据转换字符串,再将字符串显示到屏幕上(利用定时中断一分钟显示一次小时和分钟)
sprintf 的核心功能是将格式化的数据输出到字符数组中
/*状态扫描1min*/
if(TIM9_cnt[2] >= 60000)
{
TIM9_cnt[2] = 0;
//获取时间和日期
RTC_GetTime(RTC_Format_BIN,&RTC_TimeStruct);
RTC_GetDate(RTC_Format_BIN,&RTC_DateStruct);
//时间数据转换字符串
sprintf((char *)timer_buff,"%02d:%02d",RTC_TimeStruct.RTC_Hours,RTC_TimeStruct.RTC_Minutes);
//显示时间
LCD_show_number_pic(0,40,timer_buff,LGRAY,1,gImage_text_pic);
}
解锁显示功能
密码未输入显示空心圆,而密码输入显示用实心圆
密码成功或者错误则需要清除该区域--->清除屏幕某个区域函数
/**************************************************
*函数名 :LCD_xy_clear
*函数功能 :屏幕某个区域为某种颜色
*函数参数 :u16 x,u16 y,u16 w,u16 h,u16 color,u8 mode,const u8 *pic
*函数返回值:无
*函数描述 :
****************************************************/
void LCD_xy_clear(u16 x,u16 y,u16 w,u16 h,u16 color,u8 mode,const u8 *pic)
{
u16 a=0;
u16 *p = (u16*)&pic[sizeof(HEADCOLOR)+(y*240+x)*2];
/*确定区域*/
//确定x方向
st7789vm_write_command(0x2A); //横坐标命令
//起始横坐标
st7789vm_write_data(x);
//结束横坐标
st7789vm_write_data(x+w-1);
//确定Y方向
st7789vm_write_command(0x2B); //纵坐标命令
//起始纵坐标
st7789vm_write_data(y);
//结束纵坐标
st7789vm_write_data(y+h-1);
/*确定颜色*/
st7789vm_write_command(0x2C); //颜色命令
for(u32 i=0;i<w*h;i++)
{
if(mode == 0)
{
st7789vm_write_data(color);
}
else if(mode == 1)
{
st7789vm_write_data(*p);
p++;
a++;
if(a==w)
{
p+=(LCD_W-w);
a=0;
}
}
}
}
思路:
程序框架
{
判断是否为第一次开机
如果是第一次开机 //设置密码并且保存密码
{
第一次输入密码
第二次输入密码
两次密码对比如果一致
{
保存密码设置密码成功
}
两次密码对比如果不一致
{
提示密码不一致并且回到第一次输入密码
}
}
如果不是第一次开机 //对比密码
{
输入密码
把系统密码从AT24C02中读出来
对比密码如果密码正确
{
开门功能
}
对比密码如果密码错误
{
提示并且重新输入
}
}
}
触摸按键密码开锁函数:
/***********************************************
*函数名 :open_passward
*函数功能 :密码开锁
*函数参数 :u8 bs8116_key
*函数返回值:u8
*函数描述 :
************************************************/
void open_passward(u8 bs8116_key)
{
static u8 open_val;
static u8 pass_word_1[10] = {0};
static u8 pass_word_2[10] = {0};
static u8 pass_i = 0;
static u8 pass_flag = 1;
static u8 x = 45, y = 200;
static u8 pass_page_flag = 1;
//登录主界面
if(pass_page_flag)
{
LCD_show_pic(0,0,gImage_text_pic);//背景图
TIM9_cnt[2] = 60000;//一开机就能显示时间,并且按键按下流畅
x = 45;
for(u8 i=0;i<6;i++)
{
LCD_Ring(x,y,8,BLACK);
x += 30;
}
x = 45;
pass_page_flag = 0;
//读取开机标志位,以便于判断是否为第一次开机
at24c0x_read_byte(10,&open_val);
}
/*第一次开机*/
if(open_val != OPEN_FLAG)
{
if(pass_flag)
{
LCD_show(70,130,(u8*)"请设置新密码",RED,16,Backgroundcolor,LGRAYBLUE);
voice_work(0x0d);//请输入新密码
pass_flag = 0;
}
//第一次输入密码
if(bs8116_key != '*' && bs8116_key != '#' && bs8116_key != 0xff)
{
//存储第一次输入的密码
if(pass_i<6)
{
//存储密码
pass_word_1[pass_i] = bs8116_key;
pass_i++;
LCD_Ring_shi(x,y,8,WHITE);
x += 30;
//清空密码显示区域和更新汉字显示区域
if(pass_i==6)
{
//更新汉字显示区域
LCD_show(70,130,(u8*)"请再次输入新密码",RED,16,Backgroundcolor,LGRAYBLUE);
voice_work(0x0e);//请再次输入新密码
//清空密码显示区域
LCD_xy_clear(45-8,200-8,166,16,LGRAY,Backgroundcolor,gImage_text_pic);//w:30*5+2*8
x = 45;
for(u8 i=0;i<6;i++)
{
LCD_Ring(x,y,8,BLACK);
x += 30;
}
x = 45;
}
}
//存储第二次输入的密码
else if(pass_i>=6)
{
//存储密码
pass_word_2[pass_i-6] = bs8116_key;
pass_i++;
LCD_Ring_shi(x,y,8,WHITE);
x += 30;
//密码设置成功 对比密码
if(pass_i==12)
{
//密码一致
if(strcmp((char*)pass_word_1,(char*)pass_word_2)==0)
{
//将密码存储到AT24中
at24c0x_write_bytes(11,sizeof(pass_word_2),pass_word_2,AT24C04);
//存储开机标志位
at24c0x_write_byte(10,OPEN_FLAG);
voice_work(0x11);//操作成功
//回到登录主界面
pass_page_flag=1;
//恢复出厂
pass_i=0;
pass_flag=1;
}
//密码不一致
else
{
voice_work(0x0f);//验证失败
//回到登录主界面
pass_page_flag=1;
//恢复出厂
pass_i=0;
pass_flag=1;
}
}
}
}
//删除密码
if(bs8116_key == '#')
{
//第一次输入密码的删除
if(pass_i<6)
{
//清空密码显示区域
LCD_xy_clear(45-8,200-8,166,16,LGRAY,Backgroundcolor,gImage_text_pic);//w:30*5+2*8
x = 45;
for(u8 i=0;i<6;i++)
{
LCD_Ring(x,y,8,BLACK);
x += 30;
}
x = 45;
//恢复出厂
pass_i=0;
}
//第二次输入密码的删除
else if(pass_i >= 6)
{
//清空密码显示区域
LCD_xy_clear(45-8,200-8,166,16,LGRAY,Backgroundcolor,gImage_text_pic);//w:30*5+2*8
x = 45;
for(u8 i=0;i<6;i++)
{
LCD_Ring(x,y,8,BLACK);
x += 30;
}
x = 45;
//恢复出厂
pass_i=6;
}
}
}
/*不是第一次开机*/
else
{
if(pass_flag)
{
LCD_show(70,130,(u8*)"请输入开门密码 ",RED,16,Backgroundcolor,LGRAYBLUE);
voice_work(0x0c);//请输入管理员密码
pass_flag = 0;
}
//输入密码
if(bs8116_key != '*' && bs8116_key != '#' && bs8116_key != 0xff)
{
//存储输入的密码
pass_word_1[pass_i] = bs8116_key;
pass_i++;
LCD_Ring_shi(x,y,8,WHITE);
x += 30;
//对比密码
if(pass_i==6)
{
//读出系统密码
at24c0x_read_bytes(11,sizeof(pass_word_2),pass_word_2);
//密码一致
if(strcmp((char*)pass_word_1,(char*)pass_word_2)==0)
{
voice_work(0x18);//欢迎回家
//开门
open_door();
//回到登录主界面
pass_page_flag=1;
//恢复出厂
pass_i=0;
pass_flag=1;
}
//密码不一致
else
{
voice_work(0x19);//开门失败
//回到登录主界面
pass_page_flag=1;
//恢复出厂
pass_i=0;
pass_flag=1;
}
}
}
//删除密码
if(bs8116_key == '#')
{
//清空密码显示区域
LCD_xy_clear(45-8,200-8,166,16,LGRAY,Backgroundcolor,gImage_text_pic);//w:30*5+2*8
x = 45;
for(u8 i=0;i<6;i++)
{
LCD_Ring(x,y,8,BLACK);
x += 30;
}
x = 45;
//恢复出厂
pass_i=0;
}
}
}
主函数:
int main(void)
{
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2);//优先级分组
u8 keynum,key;
/*显示时间*/
u8 timer_buff[10];
LCD_init();
led_init();
key_init();
voice_init();
rtc_init();
at24c0x_init();
w25qxx_init();
usart1_init(115200);
BS8116_init();
voice_work(0x26);
mg200_init();
printf("硬件初始化成功\r\n");
/*更新字库*/
zk_updata();
Tim9_IT_ms(1);
while(1)
{
keynum = BS8116_scan();
open_passward(keynum);
/*状态扫描1s*/
if(TIM9_cnt[2] >= 60000)
{
TIM9_cnt[2] = 0;
//获取时间和日期
RTC_GetTime(RTC_Format_BIN,&RTC_TimeStruct);
RTC_GetDate(RTC_Format_BIN,&RTC_DateStruct);
//时间数据转换字符串
sprintf((char *)timer_buff,"%02d:%02d",RTC_TimeStruct.RTC_Hours,RTC_TimeStruct.RTC_Minutes);
//显示时间
LCD_show_number_pic(0,40,timer_buff,LGRAY,1,gImage_text_pic);
}
}
}
现象: