前言

参考文章

STM32-AS608光学指纹模块驱动(中断接收方式)

【STM32学习笔记——WIFI模块】

轻松学会WiFi模块

ESP-MQTT-AT指令连接阿里云物联网平台

连接阿里云

源码

指纹门禁系统源码

准备工作

:链接仅供参考,可自行准备,很多其实没必要买可以画PCB顺便画上去(主要我不会,只能买现成的模块)

硬件 链接 价格
STM32F103C8T6最小系统板 拼夕夕 15.6r
ST003 三位按键模块 拼夕夕 9r
有源蜂鸣器模块(低电平触发) 拼夕夕 3.2r
1.3寸OLED显示屏12864–带中文字库 淘宝 24r
AS608光学指纹模块 淘宝 44.8r
SG90舵机 拼夕夕 6r
/ / 合计102.6r
  • 立创画底板,用于插最小系统板还有其他模块,打板的话是立创每个月有免费次数的10*10以内好话免费(下单才知道我舵机插座忘了画)

  • 连接图

待写

  • 下载程序

下载的话有两种方案:

  1. USB转串口模块
USB转串口 STM32管脚
3V3 3V3
GND GND
TXD PA10(USART1_RX)
RXD PA9(USART1_TX)

然后使用 FlyMcu软件进行下载,需要设置:

下程序时跳线帽需要这样设置:

下载完后需要拔其中一个否则程序不会运行

  1. 使用DAPLink
DAPLINK模块 STM32管脚
3V3 3V3
GND GND
SWD SWD
SWCK SCK

程序框架

流程图制作可参考网站:ioDraw

大致流程如下:

MX配置

程序编写

LED

  • 硬件连接(最小系统板自带的LED)
LED STM32管脚
LD1 PC13
  • 程序编写
Led.h
/*
*@Description: 
*@Author: Yang
*@Date: 2023-04-21 15:16:50
*/
#ifndef __LED_H
#define __LED_H
#include "MyAll.h"

// LED引脚
#define LED_PIN GPIO_PIN_13
// LED灭
#define LED_OFF 0
// LED亮
#define LED_ON 1

typedef struct
{
    void (*vLed_Control)(uint8_t);
    void (*vLed_Flashing)(void);
}Led_TypeDef;

extern Led_TypeDef Led_Data;

void vLed_Control(uint8_t swch);    //控制LED亮灭
void vLed_Flashing(void);   //LED闪烁
#endif
Led.c
/*
 *@Description: LED
 *@Author: Yang
 *@Date: 2023-04-21 15:16:57
 */
#include "Led.h"

/*====================================变量区 BEGIN====================================*/
Led_TypeDef Led_Data =
{
        .vLed_Control = &vLed_Control,
        .vLed_Flashing = &vLed_Flashing
};
/*====================================变量区    END====================================*/


/*
 * @description: 控制LED亮灭
 * @param: LED_OFF--灭 LED_ON--亮
 * @return: 无
 * @Date: 2023-04-21 16:51:41
 */
// 控制LED亮灭
void vLed_Control(uint8_t swch)
{
    if (LED_ON == swch)
    {
        HAL_GPIO_WritePin(GPIOC, LED_PIN, GPIO_PIN_RESET);
    }
    else if (LED_OFF == swch)
    {
        HAL_GPIO_WritePin(GPIOC, LED_PIN, GPIO_PIN_SET);
    }
}

/*
 * @description: LED闪烁
 * @return: 无
 * @Date: 2023-04-22 00:16:47
 */
// LED闪烁
void vLed_Flashing(void)
{
    static uint8_t Led_State = LED_OFF;

    Led_State = !Led_State;
    Led_Data.vLed_Control(Led_State);
}

按键

  • 硬件连接
按键 STM32管脚
KEY1 PB12
KEY2 PB13
KEY3 PB14
  • 程序编写
Key.h
/*
*@Description: 按键
*@Author: Yang
*@Date: 2023-04-21 15:16:44
*/
#ifndef __KEY_H
#define __KEY_H
#include "MyAll.h"

// 按键1引脚PB12
#define KEY1_PIN GPIO_PIN_12
// 按键2引脚PB13
#define KEY2_PIN GPIO_PIN_13
// 按键3引脚PB14
#define KEY3_PIN GPIO_PIN_14

// 读取按键1电平状态
#define KEY1 HAL_GPIO_ReadPin(GPIOB,KEY1_PIN)
// 读取按键2电平状态
#define KEY2 HAL_GPIO_ReadPin(GPIOB,KEY2_PIN)
// 读取按键3电平状态
#define KEY3 HAL_GPIO_ReadPin(GPIOB,KEY3_PIN)
// 无按键
#define KEY_NULL 0
// 按键1
#define KEY1_VALUE 1
// 按键2
#define KEY2_VALUE 2
// 按键3
#define KEY3_VALUE 3




typedef struct
{
    // 标志是否处于长按状态
    bool Key_Is_Long_Press_Flag;
    // 存储按键状态(3短3长)
    uint8_t Key_Down_Buff[6];
    // 按键按下一瞬间
    uint8_t Key_Down;
   // 按键抬起一瞬间
    uint8_t Key_Up;
   // 按键键值
    uint8_t Key_Value;
   // 按键按下时间
    uint16_t Key_Down_Time;
    uint8_t (*ucKey_Get_Value)(void);
    void (*vKey_Scan_Function)(void);
    void (*vKey_Run_Function)(void);
    void (*vKey_Flag_Init)(void);
}Key_TypeDef;

extern Key_TypeDef Key_Data;


uint8_t ucKey_Get_Value(void);  // 获取按键键值函数
void vKey_Scan_Function(void); // 按键扫描函数
void vKey_Run_Function(void);    // 按键功能执行函数
void vKey_Flag_Init(void);
#endif
Key.c
/*
*@Description: 按键
*@Author: Yang
*@Date: 2023-04-21 15:16:38
*/
#include "Key.h"

/*====================================变量区 BEGIN====================================*/
Key_TypeDef Key_Data = 
{
    .Key_Is_Long_Press_Flag = 0,
    .Key_Down_Buff = {0},
    .Key_Down = 0,
    .Key_Up = 0,
    .Key_Value = 0,
    .Key_Down_Time = 0,
    .ucKey_Get_Value = &ucKey_Get_Value,
    .vKey_Scan_Function = &vKey_Scan_Function,
    .vKey_Run_Function = &vKey_Run_Function,
    .vKey_Flag_Init = &vKey_Flag_Init
};
/*====================================变量区    END====================================*/


/*
 * @description: 获取键值
 * @return: 键值--0/1/2/3
 * @Date: 2023-04-21 17:30:19
 */
// 获取键值
uint8_t ucKey_Get_Value(void)
{
    if((!KEY1) || (!KEY2) || (!KEY3))
    {
        if(!KEY1)
        {
            return KEY1_VALUE;
        }
        else if(!KEY2)
        {
            return KEY2_VALUE;
        }
        else if(!KEY3)
        {
            return KEY3_VALUE;
        }
    }
    return KEY_NULL;
}

/*
 * @description: 按键扫描函数
 * @return: 无
 * @Date: 2023-04-21 17:34:56
 */
// 按键扫描函数
void vKey_Scan_Function(void)
{
    static uint8_t Key_Old_Value = 0;

    Key_Data.Key_Value = Key_Data.ucKey_Get_Value();
    Key_Data.Key_Up = ~Key_Data.Key_Value & (Key_Data.Key_Value^Key_Old_Value);
    Key_Data.Key_Down = Key_Data.Key_Value & (Key_Data.Key_Value^Key_Old_Value);
    Key_Old_Value = Key_Data.Key_Value;

    if(Key_Data.Key_Down)
    {
        Key_Data.Key_Down_Time = 0; //按键计数清0开始计数
        Key_Data.Key_Is_Long_Press_Flag = 0;
    }
    // 短按---<1s
    if(Key_Data.Key_Down_Time < 10)
    {
        switch(Key_Data.Key_Up)
        {
            case KEY1_VALUE:
            {
                Key_Data.Key_Down_Buff[0] = 1;
                break;
            }
            case KEY2_VALUE:
            {
                Key_Data.Key_Down_Buff[1] = 1;
                break;
            }
            case KEY3_VALUE:
            {
                Key_Data.Key_Down_Buff[2] = 1;
                break;
            }
            default:break;
        }
        Key_Data.Key_Is_Long_Press_Flag = 0;
    }
    // 长按
    else
    {
        if (0 == Key_Data.Key_Is_Long_Press_Flag)
        {
                switch (Key_Data.Key_Value)
                {
                case KEY1_VALUE:
                {
                    Key_Data.Key_Down_Buff[3] = 1;
                    break;
                }
                case KEY2_VALUE:
                {
                    Key_Data.Key_Down_Buff[4] = 1;
                    break;
                }
                case KEY3_VALUE:
                {
                    Key_Data.Key_Down_Buff[5] = 1;
                    break;
                }
                default:
                    break;
                }
                Key_Data.Key_Is_Long_Press_Flag = 1;
        }
    }
}

/*
 * @description: 按键功能执行函数
 * @return: 无
 * @Date: 2023-04-21 18:14:29
 */
// 按键功能执行函数
void vKey_Run_Function(void)
{
    static float x = 0;
    if(Key_Data.Key_Down_Buff[0])
    {
        Key_Data.Key_Down_Buff[0] = 0;
        SG90_Data.vSG90_Set_Duty(SG90_ANGLE_45);
    }
    if(Key_Data.Key_Down_Buff[1])
    {
        Key_Data.Key_Down_Buff[1] = 0;
        SG90_Data.vSG90_Set_Duty(SG90_ANGLE_90);
    }
    if(Key_Data.Key_Down_Buff[2])
    {
        Key_Data.Key_Down_Buff[2] = 0;
        SG90_Data.vSG90_Set_Duty(SG90_ANGLE_180);
    }
    if(Key_Data.Key_Down_Buff[3])
    {
        Key_Data.Key_Down_Buff[3] = 0;
        SG90_Data.vSG90_Set_Duty(SG90_ANGLE_0);
    }
}

/*
 * @description: 按键按下标志位清0
 * @return {*}
 * @Date: 2023-04-24 23:30:03
 */
// 按键按下标志位清0
void vKey_Flag_Init(void)
{
    for(uint8_t i = 0; i < 6; i++)
    {
        Key_Data.Key_Down_Buff[i] = 0;
    }
}

蜂鸣器

  • 硬件连接
蜂鸣器 STM32管脚
I/O PB15
  • 程序编写
Buzzer.h
/*
*@Description: 蜂鸣器
*@Author: Yang
*@Date: 2023-04-21 15:16:31
*/
#ifndef __BUZZER_H
#define __BUZZER_H
#include "MyAll.h"

// 蜂鸣器引脚PB15
#define BUZZER_PIN GPIO_PIN_15
// 蜂鸣器响
#define BUZZER_ON 1
// 蜂鸣器不响
#define BUZZER_OFF 0
// 蜂鸣器响的时间(ms)
#define BUZZER_TIME 120

typedef struct
{
    // 蜂鸣器开始标志位
    bool Buzzer_Open_Flag;
    uint8_t Buzzer_State;   //蜂鸣器当前状态
    void (*vBuzzer_Control)(uint8_t);
    void (*vBuzzer_Ring)(void);
}Buzzer_TypeDef;


extern Buzzer_TypeDef Buzzer_Data;

void vBuzzer_Control(uint8_t swch);
void vBuzzer_Ring(void);
#endif
Buzzer.c
/*
*@Description: 蜂鸣器
*@Author: Yang
*@Date: 2023-04-21 15:16:25
*/
#include "Buzzer.h"

/*====================================变量区 BEGIN====================================*/
Buzzer_TypeDef Buzzer_Data = 
{
    .Buzzer_Open_Flag = 0,
    .Buzzer_State = BUZZER_OFF,
    .vBuzzer_Control = &vBuzzer_Control,
    .vBuzzer_Ring = &vBuzzer_Ring
};
/*====================================变量区    END====================================*/



/*
 * @description: 蜂鸣器控制
 * @param: BUZZER_ON--响 BUZZER_OFF--不响
 * @return: 无
 * @Date: 2023-04-21 18:28:41
 */
// 蜂鸣器控制
void vBuzzer_Control(uint8_t swch)
{
    if(BUZZER_ON == swch)
    {
        HAL_GPIO_WritePin(GPIOB,BUZZER_PIN,GPIO_PIN_RESET);
        Buzzer_Data.Buzzer_State = BUZZER_ON;
    }
    else if(BUZZER_OFF == swch)
    {
        HAL_GPIO_WritePin(GPIOB,BUZZER_PIN,GPIO_PIN_SET);
        Buzzer_Data.Buzzer_State = BUZZER_OFF;
    }
}

/*
 * @description: 蜂鸣器响一段时间
 * @return: 无
 * @Date: 2023-04-23 20:07:54
 */
// 蜂鸣器响一段时间
void vBuzzer_Ring(void)
{
    Buzzer_Data.Buzzer_Open_Flag = 1;
}

OLED

  • 接线(看模块丝印,正负极不要接反!),因为我想把串口2的发送接收引脚空出来所以PA2,PA3改成PA6,PA7
OLED STM32管脚
3V3 3V3
GND GND
CLK(SCL) PA0
MOS(SDA) PA1
DC PA6
CS1* PA7
FS0 PA4
CS2* PA5
  • 然后把商家的例程烧进去看看是否正常亮,注意引脚跟程序的引脚是否一致

OLED屏幕像素是 128*64(宽128高64)

显示两个字 16*16 的话左下角是 x:0 y:6,右下角是 x:96 y:6(因为96+16+16=128)

16*16的话高是0~6,宽是 0~128,最多一行显示8个中文

还有太空人的图片.c在 Space_Person_Bmp文件夹就不展示了太长了

滚动的话可以这样:

参考:STM32 OLED显示汉字及屏幕滚动-I2C协议

需要在字体后面加点空格不然可能有乱码,它是整个屏幕移动的

Oled_Data.vOled_Clear();
Oled_Data.vOled_Write_Byte(0x2E, OLED_CMD); //关闭滚动
Oled_Data.vOled_Write_Byte(0x29, OLED_CMD); //向右滚动,27则向左
Oled_Data.vOled_Write_Byte(0x00, OLED_CMD); //虚拟字节
Oled_Data.vOled_Write_Byte(0x00, OLED_CMD); //起始页 这里为0
Oled_Data.vOled_Write_Byte(0x07, OLED_CMD); //滚动速度
Oled_Data.vOled_Write_Byte(0x01, OLED_CMD); //终止页
Oled_Data.vOled_Write_Byte(0x00, OLED_CMD); //虚拟字节
Oled_Data.vOled_Write_Byte(0xFF, OLED_CMD); //虚拟字节
Oled_Data.vOled_Display_Gb2312_String(30,0,(uint8_t*)"智能嵌入式技     ");
Oled_Data.vOled_Display_Gb2312_String(30,3,(uint8_t*)"能大师工作室     ");
Oled_Data.vOled_Display_Gb2312_String(55,6,(uint8_t*)"欢迎您           ");
Oled_Data.vOled_Write_Byte(0x2F, OLED_CMD); //开启滚动
  • 程序编写
Clock.h
/*
*@Description:
*@Author: Yang
*@Date: 2023-04-25 14:20:30
*/
#ifndef __CLOCK_H
#define __CLOCK_H
#include "MyAll.h"

// 时间选择索引
// 时
#define CLOCK_HOUR	1
// 分
#define CLOCK_MIN	2
// 秒
#define CLOCK_SEC	3

//时间结构体
typedef struct
{
    uint8_t hour;
    uint8_t min;
    uint8_t sec;
    //公历日月年周
    uint16_t w_year;
    uint8_t  w_month;
    uint8_t  w_date;
    uint8_t  week;
    void (*vClock_Symbol)(uint8_t, uint8_t);
    void (*vClock_Display_Function)(void);
    void (*vClock_Get_Time_Date)(RTC_TimeTypeDef *);
    void (*vClock_Set_Time)(uint8_t, uint8_t, uint8_t);
} Clock_TypeDef;

extern Clock_TypeDef Clock_Data;
extern uint8_t Clock_Arr[10];
void vClock_Symbol(uint8_t x, uint8_t y);
void vClock_Display_Function(void);
void vClock_Get_Time_Date(RTC_TimeTypeDef *sTime);
void vClock_Set_Time(uint8_t h, uint8_t m, uint8_t s);
#endif
Clock.c
#include "Clock.h"
/*====================================变量区 BEGIN====================================*/
// 字库数组
extern const uint8_t F11x16[][22];
extern const uint8_t F19x24[][57];

uint8_t Clock_Arr[10];
// 秒数组
uint16_t num_buf[4][11];
// 分数组
uint32_t num1_buf[4][19];
// 时数组
uint32_t num2_buf[4][19];
extern RTC_TimeTypeDef rtc_Time;
extern RTC_DateTypeDef rtc_Date;


Clock_TypeDef Clock_Data =
{
    .0,
    .0,
    .0,
    .0,
    .0,
    .0,
    .0,
    .vClock_Symbol = &vClock_Symbol,
    .vClock_Display_Function = &vClock_Display_Function,
    .vClock_Get_Time_Date = &vClock_Get_Time_Date,
    .vClock_Set_Time = &vClock_Set_Time
};
/*====================================变量区    END====================================*/


/*
 * @description: 画时钟符号 ":"
 * @param {uint8_t} x 范围0~128
 * @param {uint8_t} y 范围0~7
 * @return {*} 无
 * @Date: 2023-04-25 14:40:12
 */
// 画时钟符号 ":"
void vClock_Symbol(uint8_t x, uint8_t y)
{
    uint8_t i = 0;
    uint8_t Point_Str[] =
    {
        0x00, 0xF8, 0xF8, 0xF8, 0xF8, 0x00,
        0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0x00,
        0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x00
    };

    Oled_Data.vOled_Set_Address(x, y); // 设置坐标
    for (i = 0; i < 6; i++)
    {
        Oled_Data.vOled_Write_Byte(Point_Str[i], 1);
    }
    Oled_Data.vOled_Set_Address(x, y + 1); // 设置坐标
    for (i = 6; i < 12; i++)
    {
        Oled_Data.vOled_Write_Byte(Point_Str[i], 1);
    }
    Oled_Data.vOled_Set_Address(x, y + 2); // 设置坐标
    for (i = 12; i < 18; i++)
    {
        Oled_Data.vOled_Write_Byte(Point_Str[i], 1);
    }
}

/*
 * @description: 时钟显示
 * @return {*}
 * @Date: 2023-04-25 14:56:45
 */
// 时钟显示
void vClock_Display_Function(void)
{
    SearchResult search;
    // 保存的秒数
    static uint8_t Save_Time = 0;
    uint8_t i = 0, x = 0, y = 0;
    // 清屏
    Oled_Data.vOled_Clear();
    // 显示字体
    Oled_Data.vOled_Display_Gb2312_String(0, 6, (uint8_t *)"解锁");
    Oled_Data.vOled_Display_Gb2312_String(96, 6, (uint8_t *)"功能");
    sprintf(Clock_Arr, "%02d", Clock_Data.hour);
    Oled_Data.vOled_Asc_19x24(2, 2, Clock_Arr);
    Clock_Data.vClock_Symbol(2 + 2 * 22, 2);
    sprintf(Clock_Arr, "%02d", Clock_Data.min);
    Oled_Data.vOled_Asc_19x24(2 + 2 * 22 + 2 + 7, 2, Clock_Arr);
    if(Admin_Data.Administrator_Flag)
    {
        Oled_Data.vOled_Display_Gb2312_String(54, 6, (uint8_t *)"★"); // 表示当前为管理员模式
    }
    else
    {
        Oled_Data.vOled_Display_Gb2312_String(54, 6, (uint8_t *)"  ");
    }
    while(1)
    {
        MyUSART1_Data.vUsart1_Rx_Data_Analytic();   // 上位机调试数据解析
        MyUSART2_Data.vUsart2_Rx_Data_Analytic();   // WiFi数据数据解析
        if(MyUSART2_Data.APP_Rx_Flag)
        {
            MyUSART2_Data.APP_Rx_Flag = 0;
            return;
        }
        if(INTERFACE_6 == Menu_Data.Menu_State)
        {
            return;
        }
        // 进入管理员解锁页面(k1)
        if (Key_Data.Key_Down_Buff[0])
        {
            Key_Data.Key_Down_Buff[0] = 0;
            Buzzer_Data.vBuzzer_Ring();        // 蜂鸣器滴一下
            MyAll_Data.vTime_Out_Init();       // 超时等待清0
            if (Admin_Data.Administrator_Flag) // 已解锁,回到主页面
            {
                Key_Data.vKey_Flag_Init();
                Menu_Data.vMenu_Admin_Pass_Interface();
            }
            else // 需要解锁
            {
                Menu_Data.Menu_State = INTERFACE_2;
                Menu_Data.Menu_Flag_Buff[1] = 1;
            }
            return;
        }
        // 进入功能页面(k3),有些功能需要有管理员身份 Admin_Data.Administrator_Flag
        if (Key_Data.Key_Down_Buff[2])
        {
            Key_Data.Key_Down_Buff[2] = 0;
            Buzzer_Data.vBuzzer_Ring();  // 蜂鸣器滴一下
            MyAll_Data.vTime_Out_Init(); // 超时等待清0
            Menu_Data.Menu_State = INTERFACE_3;
            Menu_Data.Menu_Flag_Buff[2] = 1;
            return;
        }
        if(AS608_Data.AS608_Wak_Flag)
        {
            AS608_Data.AS608_Wak_Flag = 0;
            Menu_Data.Menu_Flag_Buff[6] = 1;
            Menu_Data.Menu_State = INTERFACE_7;
            return;
        }
        if (Save_Time != Clock_Data.sec)
        {
            Save_Time = Clock_Data.sec; // 更新保存的秒数

            // printf("Old:%d New:%d\r\n",Save_Time,Clock_Data.sec);
            y = 0;  // 置 y 为 0,用于后面的位移操作
            // 将当前秒和下一秒的秒数分别转换成两个字符,并将它们转换成对应的位图矩阵存储到 num_buf 数组中
            sprintf(Clock_Arr, "%02d", Clock_Data.sec);
            // printf("%d\r\n",Clock_Data.sec);
            for (x = 0; x < 11; x++)
            {
                num_buf[0][x] = F11x16[Clock_Arr[0] - 0x30][x] | (uint16_t)F11x16[Clock_Arr[0] - 0x30][11 + x] << 8;    // 将第一个数字的位图矩阵存储到 num_buf[0] 中
                num_buf[1][x] = F11x16[Clock_Arr[1] - 0x30][x] | (uint16_t)F11x16[Clock_Arr[1] - 0x30][11 + x] << 8;    // 将第二个数字的位图矩阵存储到 num_buf[1] 中
            }
            sprintf(Clock_Arr, "%02d", (Clock_Data.sec + 1) % 60);
            for (x = 0; x < 11; x++)
            {
                num_buf[2][x] = F11x16[Clock_Arr[0] - 0x30][x] | (uint16_t)F11x16[Clock_Arr[0] - 0x30][11 + x] << 8;    // 将下一个秒数的第一个数字的位图矩阵存储到 num_buf[2] 中
                num_buf[3][x] = F11x16[Clock_Arr[1] - 0x30][x] | (uint16_t)F11x16[Clock_Arr[1] - 0x30][11 + x] << 8;    // 将下一个秒数的第二个数字的位图矩阵存储到 num_buf[3] 中
            }

            // 将当前分钟数和下一分钟数分别转换成两个字符,并将它们转换成对应的位图矩阵存储到 num1_buf 数组中
            sprintf(Clock_Arr, "%02d", Clock_Data.min);
            for (x = 0; x < 19; x++)
            {
                num1_buf[0][x] = F19x24[Clock_Arr[0] - 0x30][x] | (uint32_t)F19x24[Clock_Arr[0] - 0x30][1 * 19 + x] << 8 * 1 | (uint32_t)F19x24[Clock_Arr[0] - 0x30][2 * 19 + x] << 8 * 2;  // 将第一个数字的位图矩阵存储到 num1_buf[0] 中
                num1_buf[1][x] = F19x24[Clock_Arr[1] - 0x30][x] | (uint32_t)F19x24[Clock_Arr[1] - 0x30][1 * 19 + x] << 8 * 1 | (uint32_t)F19x24[Clock_Arr[1] - 0x30][2 * 19 + x] << 8 * 2;  // 将第二个数字的位图矩阵存储到 num1_buf[1] 中
            }
            sprintf(Clock_Arr, "%02d", (Clock_Data.min + 1) % 60);
            for (x = 0; x < 19; x++)
            {
                num1_buf[2][x] = F19x24[Clock_Arr[0] - 0x30][x] | (uint32_t)F19x24[Clock_Arr[0] - 0x30][1 * 19 + x] << 8 * 1 | (uint32_t)F19x24[Clock_Arr[0] - 0x30][2 * 19 + x] << 8 * 2;  // 将下一个分钟数的第一个数字的位图矩阵存储到 num1_buf[2] 中
                num1_buf[3][x] = F19x24[Clock_Arr[1] - 0x30][x] | (uint32_t)F19x24[Clock_Arr[1] - 0x30][1 * 19 + x] << 8 * 1 | (uint32_t)F19x24[Clock_Arr[1] - 0x30][2 * 19 + x] << 8 * 2;  // 将下一个分钟数的第二个数字的位图矩阵存储到 num1_buf[3] 中
            }

            // 将当前小时数和下一个小时数分别转换成两个字符,并将它们转换成对应的位图矩阵存储到 num2_buf 数组中
            sprintf(Clock_Arr, "%02d", Clock_Data.hour);
            for (x = 0; x < 19; x++)
            {
                num2_buf[0][x] = F19x24[Clock_Arr[0] - 0x30][x] | (uint32_t)F19x24[Clock_Arr[0] - 0x30][1 * 19 + x] << 8 * 1 | (uint32_t)F19x24[Clock_Arr[0] - 0x30][2 * 19 + x] << 8 * 2;  // 将第一个数字的位图矩阵存储到 num2_buf[0] 中
                num2_buf[1][x] = F19x24[Clock_Arr[1] - 0x30][x] | (uint32_t)F19x24[Clock_Arr[1] - 0x30][1 * 19 + x] << 8 * 1 | (uint32_t)F19x24[Clock_Arr[1] - 0x30][2 * 19 + x] << 8 * 2;  // 将第二个数字的位图矩阵存储到 num2_buf[1] 中
            }

            sprintf(Clock_Arr, "%02d", (Clock_Data.hour + 1) % 24);
            for (x = 0; x < 19; x++)
            {
                num2_buf[2][x] = F19x24[Clock_Arr[0] - 0x30][x] | (uint32_t)F19x24[Clock_Arr[0] - 0x30][1 * 19 + x] << 8 * 1 | (uint32_t)F19x24[Clock_Arr[0] - 0x30][2 * 19 + x] << 8 * 2;  // 将下一个小时数的第一个数字的位图矩阵存储到 num2_buf[2] 中
                num2_buf[3][x] = F19x24[Clock_Arr[1] - 0x30][x] | (uint32_t)F19x24[Clock_Arr[1] - 0x30][1 * 19 + x] << 8 * 1 | (uint32_t)F19x24[Clock_Arr[1] - 0x30][2 * 19 + x] << 8 * 2;  // 将下一个小时数的第二个数字的位图矩阵存储到 num2_buf[3] 中
            }
        }
        // 如果 y 小于 19,表示数字时钟的显示还没有到达顶部
        if (y < 19)
        {
            for (x = 0; x < 11; x++)
            {
                if (y > 2)  // 如果当前显示的行数大于 2,表示数字时钟的显示位于中心区域
                {
                    // 分两种情况更新位图矩阵:
                    // 当前秒数的个位是 9 时,需要将个位的数字从上方更新到下方,将下一秒的个位数字从下方更新到上方
                    // 当前秒数的个位不是 9 时,只需要将个位的数字从上方更新到下方
                    if (9 == Clock_Data.sec % 10)
                    {
                        num_buf[0][x] = (num_buf[0][x] >> 1) | ((num_buf[2][x] & 0x01) << 15);  // 将当前秒数的个位数字从上方更新到下方
                        num_buf[1][x] = (num_buf[1][x] >> 1) | ((num_buf[3][x] & 0x01) << 15);  // 将当前秒数的十位数字从上方更新到下方
                        num_buf[2][x] = num_buf[2][x] >> 1; // 将下一秒的个位数字从下方更新到上方
                        num_buf[3][x] = num_buf[3][x] >> 1; // 将下一秒的十位数字从下方更新到上方
                    }
                    else
                    {
                        num_buf[1][x] = (num_buf[1][x] >> 1) | ((num_buf[3][x] & 0x01) << 15);  // 将下一秒的十位数字从下方更新到上方
                        num_buf[3][x] = num_buf[3][x] >> 1; // 将下一秒的十位数字从下方更新到上方
                    }
                }
                else    // 将下一秒的十位数字从下方更新到上方
                {
                    if (9 == Clock_Data.sec % 10)   // 如果当前秒数的个位是 9,需要将个位的数字从上方更新到下方
                    {
                        num_buf[0][x] = (num_buf[0][x] >> 1);   // 将当前秒数的个位数字从上方更新到下方
                    }
                    num_buf[1][x] = (num_buf[1][x] >> 1);   // 将当前秒数的个位数字从上方更新到下方
                }
            }
            // 更新数字时钟的显示
            // 将数字时钟矩阵的数据写入 OLED 显示屏的相应位置,以更新数字时钟的显示
            Oled_Data.vOled_Set_Address(2 + 4 * 22 + 2 + 7, 3); //设置坐标
            for (x = 0; x < 11; x++)
            {
                Oled_Data.vOled_Write_Byte(num_buf[0][x], 1);
            }
            Oled_Data.vOled_Set_Address(2 + 4 * 22 + 2 + 7, 4);
            for (x = 0; x < 11; x++)
            {
                Oled_Data.vOled_Write_Byte(num_buf[0][x] >> 8, 1);
            }
            Oled_Data.vOled_Set_Address(2 + 4 * 22 + 2 + 7 + 12, 3); //设置坐标
            for (x = 0; x < 11; x++)
            {
                Oled_Data.vOled_Write_Byte(num_buf[1][x], 1);
            }
            Oled_Data.vOled_Set_Address(2 + 4 * 22 + 2 + 7 + 12, 4);
            for (x = 0; x < 11; x++)
            {
                Oled_Data.vOled_Write_Byte(num_buf[1][x] >> 8, 1);
            }
        }
        // 如果当前显示未到达底部并且秒数为 59,则进入更新数字分钟的代码块
        if ((y < 27) && (59 == Clock_Data.sec))
        {
            for (x = 0; x < 19; x++)
            {
                if (y > 2)// 如果当前显示的行数大于 2,表示数字分钟的显示位于中心区域
                {
                    // 分两种情况更新位图矩阵:
                    // 当前分钟数的个位是 9 时,需要将个位的数字从上方更新到下方,将下一分钟的个位数字从下方更新到上方
                    // 当前分钟数的个位不是 9 时,只需要将个位的数字从上方更新到下方
                    if (9 == (Clock_Data.min % 10))
                    {
                        num1_buf[0][x] = (num1_buf[0][x] >> 1) | ((num1_buf[2][x] & 0x01) << 23);// 将当前分钟数的个位数字从上方更新到下方
                        num1_buf[1][x] = (num1_buf[1][x] >> 1) | ((num1_buf[3][x] & 0x01) << 23);// 将当前分钟数的十位数字从上方更新到下方
                        num1_buf[2][x] = num1_buf[2][x] >> 1;// 将下一分钟的个位数字从下方更新到上方
                        num1_buf[3][x] = num1_buf[3][x] >> 1;// 将下一分钟的十位数字从下方更新到上方
                    }
                    else
                    {
                        num1_buf[1][x] = (num1_buf[1][x] >> 1) | ((num1_buf[3][x] & 0x01) << 23);// 将当前分钟数的十位数字从上方更新到下方
                        num1_buf[3][x] = num1_buf[3][x] >> 1;// 将下一分钟的十位数字从下方更新到上方
                    }
                }
                else// 否则,表示数字分钟的显示位于顶部或底部
                {
                    if (9 == (Clock_Data.min % 10)) // 如果当前分钟数的个位是 9,需要将个位的数字从上方更新到下方
                    {
                        num1_buf[0][x] = (num1_buf[0][x] >> 1);// 将当前分钟数的个位数字从上方更新到下方
                    }
                    num1_buf[1][x] = (num1_buf[1][x] >> 1);// 将当前分钟数的十位数字从上方更新到下方
                }
            }
            //	Asc19_24(2+2*22+2+7,2,s);
            // 更新数字分钟的显示
            // 将数字分钟矩阵的数据写入 OLED 显示屏的相应位置,以更新数字分钟的显示
            Oled_Data.vOled_Set_Address(2 + 2 * 22 + 2 + 7, 2);    //设置坐标
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num1_buf[0][x], 1);
            }
            Oled_Data.vOled_Set_Address(2 + 2 * 22 + 2 + 7, 3);
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num1_buf[0][x] >> 8, 1);
            }
            Oled_Data.vOled_Set_Address(2 + 2 * 22 + 2 + 7, 4);
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num1_buf[0][x] >> 16, 1);
            }
            Oled_Data.vOled_Set_Address(2 + 3 * 22 + 2 + 7, 2); // 设置坐标
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num1_buf[1][x], 1);
            }
            Oled_Data.vOled_Set_Address(2 + 3 * 22 + 2 + 7, 3); // 设置坐标
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num1_buf[1][x] >> 8, 1);
            }
            Oled_Data.vOled_Set_Address(2 + 3 * 22 + 2 + 7, 4); // 设置坐标
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num1_buf[1][x] >> 16, 1);
            }
        }
        // 如果当前显示未到达底部,并且分钟数和秒数都为 59,则进行数字小时的更新
        if ((y < 27) && (Clock_Data.min == 59) && (Clock_Data.sec == 59))
        {
            for (x = 0; x < 19; x++)
            {

                if (y > 2)// 如果当前显示的行数大于 2,表示数字小时的显示位于中心区域
                {
                    // 根据数字小时的个位数是否为 9 或当前时间是否为 23 时判断具体更新方式:
                    // 若数字小时的个位数为 9 或当前时间为 23,则需将个位数字从上方更新到下方,将下一小时的个位数字从下方更新到上方;
                    // 否则,只需将个位数字从上方更新到下方。
                    if ((Clock_Data.hour % 10 == 9) || (Clock_Data.hour == 23))
                    {
                        num2_buf[0][x] = (num2_buf[0][x] >> 1) | ((num2_buf[2][x] & 0x01) << 23);// 将当前小时数的个位数字从上方更新到下方
                        num2_buf[1][x] = (num2_buf[1][x] >> 1) | ((num2_buf[3][x] & 0x01) << 23);// 将当前小时数的十位数字从上方更新到下方
                        num2_buf[2][x] = num2_buf[2][x] >> 1;// 将下一小时的个位数字从下方更新到上方
                        num2_buf[3][x] = num2_buf[3][x] >> 1;// 将下一小时的十位数字从下方更新到上方
                    }
                    else
                    {
                        num2_buf[1][x] = (num2_buf[1][x] >> 1) | ((num2_buf[3][x] & 0x01) << 23);// 将当前小时数的十位数字从上方更新到下方
                        num2_buf[3][x] = num2_buf[3][x] >> 1;// 将下一小时的十位数字从下方更新到上方
                    }
                }
                else// 否则,表示数字小时的显示位于顶部或底部
                {
                    if (9 == Clock_Data.hour % 10)
                    {
                        num2_buf[0][x] = (num2_buf[0][x] >> 1); // 将当前小时数的个位数字从上方更新到下方
                    }
                    num2_buf[1][x] = (num2_buf[1][x] >> 1);// 将当前小时数的十位数字从上方更新到下方
                }
            }
            //	Asc19_24(2+2*22+2+7,2,s);
            Oled_Data.vOled_Set_Address(2, 2); // 设置坐标
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num2_buf[0][x], 1);
            }
            Oled_Data.vOled_Set_Address(2, 3);
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num2_buf[0][x] >> 8, 1);
            }
            Oled_Data.vOled_Set_Address(2, 4);
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num2_buf[0][x] >> 16, 1);
            }
            Oled_Data.vOled_Set_Address(2 + 1 * 22, 2); // 设置坐标
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num2_buf[1][x], 1);
            }
            Oled_Data.vOled_Set_Address(2 + 1 * 22, 3);
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num2_buf[1][x] >> 8, 1);
            }
            Oled_Data.vOled_Set_Address(2 + 1 * 22, 4);
            for (x = 0; x < 19; x++)
            {
                Oled_Data.vOled_Write_Byte(num2_buf[1][x] >> 16, 1);
            }
        }
        y++;    // 行数加 1
        HAL_Delay(17);  // 延时,使 OLED 显示屏能够刷新完整屏幕
    }
}

/*
 * @description: 获取当前时间日期
 * @param {RTC_TimeTypeDef} *sTime 要存到哪
 * @return {*}
 * @Date: 2023-04-25 16:42:55
 */
// 获取当前时间日期
void vClock_Get_Time_Date(RTC_TimeTypeDef *sTime)
{
    HAL_RTC_GetTime(&hrtc, &rtc_Time, RTC_FORMAT_BIN);  // 获取时间
    HAL_RTC_GetDate(&hrtc, &rtc_Date, RTC_FORMAT_BIN);  // 获取日期

    Clock_Data.sec = sTime->Seconds;
    Clock_Data.min = sTime->Minutes;
    Clock_Data.hour = sTime->Hours;
}

/*
 * @description: RTC设置时间
 * @return {*}
 * @Date: 2023-04-25 17:55:53
 */
// RTC设置时间
void vClock_Set_Time(uint8_t h, uint8_t m, uint8_t s)
{
    rtc_Time.Hours = h;
    rtc_Time.Minutes = m;
    rtc_Time.Seconds = s;
    rtc_Date.Year = 23;
    rtc_Date.Month = 4;
    rtc_Date.Date = 25;
    HAL_RTC_SetDate(&hrtc, &rtc_Date, RTC_FORMAT_BIN);
    HAL_RTC_SetTime(&hrtc, &rtc_Time, RTC_FORMAT_BIN);
}
Oled.h
/*
*@Description: OLED屏幕
*@Author: Yang
*@Date: 2023-04-21 15:17:10
*/
#ifndef __OLED_H
#define __OLED_H
#include "MyAll.h"

/******引脚定义*******/
// OLED SCL引脚PA0
#define OLED_SCL    GPIO_PIN_0
// OLED SDA引脚PA1
#define OLED_SDA    GPIO_PIN_1
// OLED DC引脚PA2
#define OLED_DC GPIO_PIN_2
// OLED CS1引脚PA3
#define OLED_CS1    GPIO_PIN_3
// OLED FS0引脚PA4
#define OLED_FS0    GPIO_PIN_4
// OLED CS2引脚PA5
#define OLED_CS2    GPIO_PIN_5

/******引脚置位/读取*******/
// SCL 置0/1
#define SCL_OUT_0  HAL_GPIO_WritePin(GPIOA,OLED_SCL,GPIO_PIN_RESET)
#define SCL_OUT_1  HAL_GPIO_WritePin(GPIOA,OLED_SCL,GPIO_PIN_SET)
// SDA 置0/1
#define SDA_OUT_0  HAL_GPIO_WritePin(GPIOA,OLED_SDA,GPIO_PIN_RESET)
#define SDA_OUT_1  HAL_GPIO_WritePin(GPIOA,OLED_SDA,GPIO_PIN_SET)
// DC 置0/1
#define DC_OUT_0  HAL_GPIO_WritePin(GPIOA,OLED_DC,GPIO_PIN_RESET)
#define DC_OUT_1  HAL_GPIO_WritePin(GPIOA,OLED_DC,GPIO_PIN_SET)
// CS1 置0/1
#define CS1_OUT_0  HAL_GPIO_WritePin(GPIOA,OLED_CS1,GPIO_PIN_RESET)
#define CS1_OUT_1  HAL_GPIO_WritePin(GPIOA,OLED_CS1,GPIO_PIN_SET)
// FS0 读取电平状态
#define FS0_READ    HAL_GPIO_ReadPin(GPIOA,OLED_FS0)
// CS2 置0/1
#define CS2_OUT_0  HAL_GPIO_WritePin(GPIOA,OLED_CS2,GPIO_PIN_RESET)
#define CS2_OUT_1  HAL_GPIO_WritePin(GPIOA,OLED_CS2,GPIO_PIN_SET)


/******其他*******/
// 写命令
#define OLED_CMD    0
// 写数据
#define OLED_DATA   1
// 正常
#define OLED_SET    1
// 反常
#define OLED_RESET  0

/******命令大全*******/
// 正常显示
#define CMD_NORMAL  0xA6
// 反转显示
#define CMD_INVERSE  0xA7
// 设置对比度控制(选择1~256,复位值0x7F)
#define CMD_SET_CONTRACT    0x7F
// 电压等级设置(0x00-0.65xVCC 0x20-0.77xVCC(复位值) 0x30-0.83xVCC)
#define CMD_SET_V_LEVEL 0x20
// 显示开启
#define CMD_DISPLAY_ON  0xAF
// 显示关闭(睡眠模式)
#define CMD_DISPLAY_OFF 0xAE
// 控制列地址0被映射到SEG0
#define CMD_RE_MAP_0    0xA0
// 控制列地址127被映射到SEG0
#define CMD_RE_MAP_127    0xA1
// 控制扫描方向COM0-->COM[N-1]
#define CMD_DIRECTION_L_TO_R    0xC0
// 控制扫描方向COM[N-1]-->COM0
#define CMD_DIRECTION_R_TO_L    0xC8



typedef struct
{
    uint32_t Font_Addr;
    void (*vOled_Write_Byte)(uint8_t,uint8_t);
    void (*vOled_Toggle_Display)(uint8_t);
    void (*vOled_Rotate_Display)(uint8_t);
    void (*vOled_Clear)(void);
    void (*vOled_Set_Address)(uint8_t,uint8_t);
    void (*vOled_Display_128x64_Bmp)(uint8_t*);
    void (*vOled_Display_Gb2312_String)(uint8_t,uint8_t,uint8_t*);
    void (*vOled_Display_String_5x7)(uint8_t,uint8_t,uint8_t*);
    void (*vOled_Init)(void);
    void (*vOled_Draw_Bmp)(unsigned char,unsigned char,unsigned char,unsigned char,unsigned char []);
    void (*vOled_Light_Row)(uint8_t,uint8_t);
    void (*vOled_Asc_19x24)(uint8_t,uint8_t,uint8_t []);
    void (*vOled_Painting_Pixel)(uint8_t,uint8_t,uint8_t);
}Oled_TypeDef;

extern Oled_TypeDef Oled_Data;

void vOled_Write_Byte(uint8_t data, uint8_t format);
void vOled_Toggle_Display(uint8_t swch);
void vOled_Rotate_Display(uint8_t swch);
void vOled_Set_Address(uint8_t x,uint8_t y);
void vOled_Display_128x64_Bmp(uint8_t *dp);
void vOled_Clear(void);
void vOled_Display_Gb2312_String(uint8_t x, uint8_t y, uint8_t *text);
void vOled_Display_5x7(uint8_t x, uint8_t y, uint8_t *dp);
void vOled_Init(void);
void vOled_Asc_19x24(uint8_t x, uint8_t y, uint8_t ch[]);
void vOled_Painting_Pixel(uint8_t x,uint8_t y,uint8_t num);
void vOled_Light_Row(uint8_t row,uint8_t swch);
void vOled_Draw_Bmp(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char BMP[]);
#endif
Oled.c
/*
*@Description: OLED屏幕 接线:CLK--PA0 MOS--PA1 DC--PA2 CS1--PA3 FS0--PA4 CS2--PA5
*@Author: Yang
*@Date: 2023-04-21 15:17:04
*/
#include "Oled.h"

/*====================================静态内部函数声明区 BEGIN====================================*/
static void svOled_DisPlay_Control(uint8_t swch);
static void svOled_Display_16x16(uint8_t x, uint8_t y, uint8_t *dp);
static void svOled_Display_8x16(uint8_t x, uint8_t y, uint8_t *dp);
static void svOled_Send_Command_To_Rom(uint8_t dat);
static uint8_t sucOled_Get_Data_From_Rom(void);
static void svOled_Get_Data(uint8_t addrHigh, uint8_t addrMid, uint8_t addrLow, uint8_t *pbuff, uint8_t DataLen);
static void vOled_Display_String_5x7(uint8_t x, uint8_t y, uint8_t *text);
static void svOled_Show_Decimal(uint8_t x, uint8_t y, float num1, uint8_t len);



/*====================================静态内部函数声明区    END====================================*/



/*====================================变量区 BEGIN====================================*/
extern const uint8_t F11x16[][22];
extern const uint8_t F19x24[][57];

Oled_TypeDef Oled_Data =
{
    .Font_Addr = 0,
    .vOled_Write_Byte = &vOled_Write_Byte,
    .vOled_Toggle_Display = &vOled_Toggle_Display,
    .vOled_Rotate_Display = &vOled_Rotate_Display,
    .vOled_Clear = &vOled_Clear,
    .vOled_Set_Address = &vOled_Set_Address,
    .vOled_Display_128x64_Bmp = &vOled_Display_128x64_Bmp,
    .vOled_Display_Gb2312_String = &vOled_Display_Gb2312_String,
    .vOled_Display_String_5x7 = &vOled_Display_String_5x7,
    .vOled_Init = &vOled_Init,
    .vOled_Draw_Bmp = &vOled_Draw_Bmp,
    .vOled_Light_Row = &vOled_Light_Row,
    .vOled_Asc_19x24 = &vOled_Asc_19x24,
    .vOled_Painting_Pixel = &vOled_Painting_Pixel
};
/*====================================变量区    END====================================*/


/*
 * @description: 向SSD1306写入一个字节
 * @param: 待写入数据
 * @param: 格式-- OLED_CMD(命令)  OLED_DATA(数据)
 * @return: 无
 * @Date: 2023-04-22 17:46:53
 */
// 向SSD1306写入一个字节
void vOled_Write_Byte(uint8_t data, uint8_t format)
{
    if(OLED_DATA == format)
    {
        DC_OUT_1;
    }
    else
    {
        DC_OUT_0;
    }
    CS1_OUT_0;
    for (uint8_t i = 0; i < 8; i++)
    {
        SCL_OUT_0;
        if(data & 0x80)
        {
            SDA_OUT_1;
        }
        else
        {
            SDA_OUT_0;
        }
        SCL_OUT_1;
        data <<= 1;
    }
    CS1_OUT_1;
    DC_OUT_1;
}

/*
 * @description: 反显函数
 * @param : OLED_SET--正常显示 OLED_RESET--反色显示
 * @return: 无
 * @Date: 2023-04-22 17:40:54
 */
// 反显函数
void vOled_Toggle_Display(uint8_t swch)
{
    if(OLED_SET == swch)
    {
        Oled_Data.vOled_Write_Byte(CMD_NORMAL, OLED_CMD); //正常显示
    }
    else if(OLED_RESET == swch)
    {
        Oled_Data.vOled_Write_Byte(CMD_INVERSE, OLED_CMD);  //反转显示
    }
}

/*
 * @description: 屏幕旋转180度
 * @param: OLED_SET--正常显示 OLED_RESET--旋转显示
 * @return: 无
 * @Date: 2023-04-22 18:32:16
 */
// 屏幕旋转180度
void vOled_Rotate_Display(uint8_t swch)
{
    if(OLED_SET == swch)
    {
        Oled_Data.vOled_Write_Byte(CMD_DIRECTION_R_TO_L, OLED_CMD); //正常显示
        Oled_Data.vOled_Write_Byte(CMD_RE_MAP_127, OLED_CMD);
    }
    else if(OLED_RESET == swch)
    {
        Oled_Data.vOled_Write_Byte(CMD_DIRECTION_L_TO_R, OLED_CMD); //旋转显示
        Oled_Data.vOled_Write_Byte(CMD_RE_MAP_0, OLED_CMD);
    }
}

/*
 * @description: OLED显示控制
 * @param: OLED_SET--显示 OLED_RESET--不显示
 * @return: 无
 * @Date: 2023-04-22 18:36:36
 */
// OLED显示控制
static void svOled_DisPlay_Control(uint8_t swch)
{
    if (OLED_SET == swch)
    {
        Oled_Data.vOled_Write_Byte(0x8D, OLED_CMD); // 电荷泵使能
        Oled_Data.vOled_Write_Byte(0x14, OLED_CMD); // 开启电荷泵
        Oled_Data.vOled_Write_Byte(CMD_DISPLAY_ON, OLED_CMD); // 点亮屏幕
    }
    else if (OLED_RESET == swch)
    {
        Oled_Data.vOled_Write_Byte(0x8D, OLED_CMD); // 电荷泵使能
        Oled_Data.vOled_Write_Byte(0x10, OLED_CMD); // 关闭电荷泵
        Oled_Data.vOled_Write_Byte(CMD_DISPLAY_OFF, OLED_CMD); // 关闭屏幕
    }
}

/*
 * @description: 清屏函数
 * @return: 无
 * @Date: 2023-04-22 18:41:02
 */
// 清屏函数
void vOled_Clear(void)
{
    uint8_t i, n;
    for (i = 0; i < 8; i++)
    {
        Oled_Data.vOled_Write_Byte(0xb0 + i, OLED_CMD); // 设置页地址
        Oled_Data.vOled_Write_Byte(0x10, OLED_CMD);     // 设置列地址的高4位
        Oled_Data.vOled_Write_Byte(0x02, OLED_CMD);     // 设置列地址的低4位
        for (n = 0; n < 128; n++)
        {
            Oled_Data.vOled_Write_Byte(0x00, OLED_DATA); // 清除所有数据
        }
    }
}

/*
 * @description: 设置光标位置
 * @param {uint8_t} x
 * @param {uint8_t} y
 * @return: 无
 * @Date: 2023-04-22 18:45:13
 */
// 设置光标位置
void vOled_Set_Address(uint8_t x, uint8_t y)
{
    x += 2;
    Oled_Data.vOled_Write_Byte(0xb0 + y, OLED_CMD);             //设置页地址
    Oled_Data.vOled_Write_Byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD); //设置列地址的高4位
    Oled_Data.vOled_Write_Byte((x & 0x0f), OLED_CMD);           //设置列地址的低4位
}

/*
 * @description: 显示128x64点阵图像
 * @param {uint8_t} *dp
 * @return: 无
 * @Date: 2023-04-22 18:52:20
 */
// 显示128x64点阵图像
void vOled_Display_128x64_Bmp(uint8_t *dp)
{
    uint8_t i, j;
    for(i = 0; i < 8; i++)
    {
        Oled_Data.vOled_Set_Address(0, i);
        for(j = 0; j < 128; j++)
        {
            Oled_Data.vOled_Write_Byte(*dp, OLED_DATA); //写数据到OLED,每写完一个8位的数据后列地址自动加1
            dp++;
        }
    }
}

/*
 * @description: 显示16x16点阵图像、汉字、生僻字或16x16点阵的其他图标
 * @param {uint8_t} x
 * @param {uint8_t} y
 * @param {uint8_t} *dp
 * @return: 无
 * @Date: 2023-04-22 18:54:23
 */
// 显示16x16点阵图像、汉字、生僻字或16x16点阵的其他图标
static void svOled_Display_16x16(uint8_t x, uint8_t y, uint8_t *dp)
{
    uint8_t i, j;
    for (j = 0; j < 2; j++)
    {
        Oled_Data.vOled_Set_Address(x, y);
        for (i = 0; i < 16; i++)
        {
            Oled_Data.vOled_Write_Byte(*dp, OLED_DATA); // 写数据到OLED,每写完一个8位的数据后列地址自动加1
            dp++;
        }
        y++;
    }
}

/*
 * @description: 显示8x16点阵图像、ASCII, 或8x16点阵的自造字符、其他图标
 * @param {uint8_t} x
 * @param {uint8_t} y
 * @param {uint8_t} *dp
 * @return: 无
 * @Date: 2023-04-22 18:55:58
 */
// 显示8x16点阵图像、ASCII, 或8x16点阵的自造字符、其他图标
static void svOled_Display_8x16(uint8_t x, uint8_t y, uint8_t *dp)
{
    uint8_t i, j;
    for (j = 0; j < 2; j++)
    {
        Oled_Data.vOled_Set_Address(x, y);
        for (i = 0; i < 8; i++)
        {
            Oled_Data.vOled_Write_Byte(*dp, OLED_DATA); // 写数据到LCD,每写完一个8位的数据后列地址自动加1
            dp++;
        }
        y++;
    }
}

/*
 * @description: 显示5*7点阵图像、ASCII, 或5x7点阵的自造字符、其他图标
 * @param {uint8_t} x
 * @param {uint8_t} y
 * @param {uint8_t} *dp
 * @return: 无
 * @Date: 2023-04-22 18:57:55
 */
// 显示5*7点阵图像、ASCII, 或5x7点阵的自造字符、其他图标
void vOled_Display_5x7(uint8_t x, uint8_t y, uint8_t *dp)
{
    uint8_t i;
    Oled_Data.vOled_Set_Address(x, y);
    for (i = 0; i < 6; i++)
    {
        Oled_Data.vOled_Write_Byte(*dp, OLED_DATA);
        dp++;
    }
}


/*
 * @description: 送指令到晶联讯字库IC
 * @param {uint8_t} dat
 * @return: 无
 * @Date: 2023-04-22 19:05:28
 */
// 送指令到晶联讯字库IC
static void svOled_Send_Command_To_Rom(uint8_t dat)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        SCL_OUT_0;
        if (dat & 0x80)
        {
            SDA_OUT_1;
        }
        else
        {
            SDA_OUT_0;
        }
        dat <<= 1;
        SCL_OUT_1;
    }
}

/*
 * @description: 从晶联讯字库IC中取汉字或字符数据(1个字节)
 * @return {*}
 * @Date: 2023-04-22 19:13:07
 */
// 从晶联讯字库IC中取汉字或字符数据(1个字节)
static uint8_t sucOled_Get_Data_From_Rom(void)
{
    uint8_t i, read = 0;

    for (i = 0; i < 8; i++)
    {
        SCL_OUT_0;
        read <<= 1;
        if (FS0_READ)
        {
            read++;
        }
        SCL_OUT_1;
    }
    return read;
}


/*
 * @description: 连续读取
 * @param {uint8_t} addrHigh
 * @param {uint8_t} addrMid
 * @param {uint8_t} addrLow
 * @param {uint8_t} *pbuff
 * @param {uint8_t} DataLen
 * @return: 无
 * @Date: 2023-04-22 19:20:35
 */
// 连续读取(从相关地址(addrHigh:地址高字节,addrMid:地址中字节,addrLow:地址低字节)中连续读出DataLen个字节的数据到 pbuff的地址)
static void svOled_Get_Data(uint8_t addrHigh, uint8_t addrMid, uint8_t addrLow, uint8_t *pbuff, uint8_t DataLen)
{
    uint8_t i;
    CS2_OUT_0;
    svOled_Send_Command_To_Rom(0x03);
    svOled_Send_Command_To_Rom(addrHigh);
    svOled_Send_Command_To_Rom(addrMid);
    svOled_Send_Command_To_Rom(addrLow);
    for (i = 0; i < DataLen; i++)
    {
        *(pbuff + i) = sucOled_Get_Data_From_Rom();
    }
    CS2_OUT_1;
}

/*
 * @description: 指定坐标显示GB2312字符串(16*16)
 * @param {uint8_t} x: 范围0~128
 * @param {uint8_t} y: 范围0~6
 * @param {uint8_t} *text : 要显示的字符串
 * @return: 无
 * @Date: 2023-04-22 19:36:42
 */
// 指定坐标显示GB2312字符串(16*16)
void vOled_Display_Gb2312_String(uint8_t x, uint8_t y, uint8_t *text)
{
    uint8_t i = 0;
    uint8_t addrHigh, addrMid, addrLow;
    uint8_t fontbuf[32];

    while (text[i] > 0x00)
    {
        if ((text[i] >= 0xb0) && (text[i] <= 0xf7) && (text[i + 1] >= 0xa1))
        {
            // 国标简体(GB2312)汉字在晶联讯字库IC中的地址由以下公式来计算:
            // Address = ((MSB - 0xB0) * 94 + (LSB - 0xA1)+ 846)*32+ BaseAdd;BaseAdd=0
            // 由于担心8位单片机有乘法溢出问题,所以分三部取地址
            Oled_Data.Font_Addr = (text[i] - 0xb0) * 94;
            Oled_Data.Font_Addr += (text[i + 1] - 0xa1) + 846;
            Oled_Data.Font_Addr = (Oled_Data.Font_Addr) * 32;

            addrHigh = (Oled_Data.Font_Addr & 0xff0000) >> 16; // 地址的高8位,共24位
            addrMid = (Oled_Data.Font_Addr & 0xff00) >> 8;     // 地址的中8位,共24位
            addrLow = (Oled_Data.Font_Addr & 0xff);            // 地址的低8位,共24位

            svOled_Get_Data(addrHigh, addrMid, addrLow, fontbuf, 32);
            // 取32个字节的数据,存到"fontbuf[32]"
            svOled_Display_16x16(x, y, fontbuf);
            // 显示汉字到LCD上,y为页地址,x为列地址,fontbuf[]为数据
            x += 16;
            i += 2;
        }
        else if ((text[i] >= 0xa1) && (text[i] <= 0xa3) && (text[i + 1] >= 0xa1))
        {

            Oled_Data.Font_Addr = (text[i] - 0xa1) * 94;
            Oled_Data.Font_Addr += (text[i + 1] - 0xa1);
            Oled_Data.Font_Addr = (Oled_Data.Font_Addr)  * 32;

            addrHigh = (Oled_Data.Font_Addr & 0xff0000) >> 16;
            addrMid = (Oled_Data.Font_Addr & 0xff00) >> 8;
            addrLow = (Oled_Data.Font_Addr & 0xff);

            svOled_Get_Data(addrHigh, addrMid, addrLow, fontbuf, 32);
            svOled_Display_16x16(x, y, fontbuf);
            x += 16;
            i += 2;
        }
        else if ((text[i] >= 0x20) && (text[i] <= 0x7e))
        {
            unsigned char fontbuf[16];
            Oled_Data.Font_Addr = (text[i] - 0x20);
            Oled_Data.Font_Addr = (unsigned long)(Oled_Data.Font_Addr * 16);
            Oled_Data.Font_Addr = (unsigned long)(Oled_Data.Font_Addr + 0x3cf80);

            addrHigh = (Oled_Data.Font_Addr & 0xff0000) >> 16;
            addrMid = (Oled_Data.Font_Addr & 0xff00) >> 8;
            addrLow = Oled_Data.Font_Addr & 0xff;

            svOled_Get_Data(addrHigh, addrMid, addrLow, fontbuf, 16);
            svOled_Display_8x16(x, y, fontbuf);
            x += 8;
            i += 1;
        }
        else
            i++;
    }
}

/*
 * @description: 指定坐标显示字符串(5*7)
 * @param {uint8_t} x
 * @param {uint8_t} y
 * @param {uint8_t} *text
 * @return: 无
 * @Date: 2023-04-22 19:41:51
 */
// 指定坐标显示字符串(5*7)
static void vOled_Display_String_5x7(uint8_t x, uint8_t y, uint8_t *text)
{
    uint8_t i = 0;
    uint8_t addrHigh, addrMid, addrLow;

    while (text[i] > 0x00)
    {
        if ((text[i] >= 0x20) && (text[i] <= 0x7e))
        {
            uint8_t fontbuf[8];
            Oled_Data.Font_Addr = (text[i] - 0x20);
            Oled_Data.Font_Addr = (unsigned long)(Oled_Data.Font_Addr * 8);
            Oled_Data.Font_Addr = (unsigned long)(Oled_Data.Font_Addr + 0x3bfc0);

            addrHigh = (Oled_Data.Font_Addr & 0xff0000) >> 16;
            addrMid = (Oled_Data.Font_Addr & 0xff00) >> 8;
            addrLow = Oled_Data.Font_Addr & 0xff;

            svOled_Get_Data(addrHigh, addrMid, addrLow, fontbuf, 8);
            vOled_Display_5x7(x, y, fontbuf);
            x += 6;
            i += 1;
        }
        else
            i++;
    }
}

/*
 * @description: 显示2个数字(小数)
 * @param: 起点坐标x
 * @param: 起点坐标y
 * @param: 要显示的小数
 * @param: 数字的位数
 * @return: 无
 * @Date: 2023-04-22 19:49:24
 */
// 显示2个数字(小数)
static void svOled_Show_Decimal(uint8_t x, uint8_t y, float num1, uint8_t len)
{
    uint8_t i;
    uint32_t t, num;
    x = x + len * 8 + 8;                                    // 要显示的小数最低位的横坐标
    num = num1 * 100;                                       // 将小数左移两位并转化为整数
    Oled_Data.vOled_Display_Gb2312_String(x - 24, y, (uint8_t *)"."); // 显示小数点
    for (i = 0; i < len; i++)
    {
        t = num % 10;   // 取个位数的数值
        num = num / 10; // 将整数右移一位
        x -= 8;
        if (2 == i)
        {
            x -= 8;
        }
        // 当显示出来两个小数之后,空出小数点的位置
        switch (t)
        {
        case 0:
            Oled_Data.vOled_Display_Gb2312_String(x, y, (uint8_t *)"0");
            break;
        case 1:
            Oled_Data.vOled_Display_Gb2312_String(x, y, (uint8_t *)"1");
            break;
        case 2:
            Oled_Data.vOled_Display_Gb2312_String(x, y, (uint8_t *)"2");
            break;
        case 3:
            Oled_Data.vOled_Display_Gb2312_String(x, y, (uint8_t *)"3");
            break;
        case 4:
            Oled_Data.vOled_Display_Gb2312_String(x, y, (uint8_t *)"4");
            break;
        case 5:
            Oled_Data.vOled_Display_Gb2312_String(x, y, (uint8_t *)"5");
            break;
        case 6:
            Oled_Data.vOled_Display_Gb2312_String(x, y, (uint8_t *)"6");
            break;
        case 7:
            Oled_Data.vOled_Display_Gb2312_String(x, y, (uint8_t *)"7");
            break;
        case 8:
            Oled_Data.vOled_Display_Gb2312_String(x, y, (uint8_t *)"8");
            break;
        case 9:
            Oled_Data.vOled_Display_Gb2312_String(x, y, (uint8_t *)"9");
            break;
        }
    }
}


/*
 * @description: OLED的初始化
 * @return: 无
 * @Date: 2023-04-22 19:50:58
 */
// OLED的初始化
void vOled_Init(void)
{
    HAL_Delay(200);

    Oled_Data.vOled_Write_Byte(CMD_DISPLAY_OFF, OLED_CMD); /*display off*/
    Oled_Data.vOled_Write_Byte(0x02, OLED_CMD); /*set lower column address*/
    Oled_Data.vOled_Write_Byte(0x10, OLED_CMD); /*set higher column address*/
    Oled_Data.vOled_Write_Byte(0x40, OLED_CMD); /*set display start line*/
    Oled_Data.vOled_Write_Byte(0xB0, OLED_CMD); /*set page address*/
    Oled_Data.vOled_Write_Byte(0x81, OLED_CMD); /*contract control*/
    Oled_Data.vOled_Write_Byte(0xcf, OLED_CMD); /*128*/
    Oled_Data.vOled_Write_Byte(CMD_RE_MAP_127, OLED_CMD); /*set segment remap*/
    Oled_Data.vOled_Write_Byte(CMD_NORMAL, OLED_CMD); /*normal / reverse*/
    Oled_Data.vOled_Write_Byte(0xA8, OLED_CMD); /*multiplex ratio*/
    Oled_Data.vOled_Write_Byte(0x3F, OLED_CMD); /*duty = 1/64*/
    Oled_Data.vOled_Write_Byte(0xad, OLED_CMD); /*set charge pump enable*/
    Oled_Data.vOled_Write_Byte(0x8b, OLED_CMD); /* 0x8B 内供 VCC */
    Oled_Data.vOled_Write_Byte(0x33, OLED_CMD); /*0X30---0X33 set VPP 9V */
    Oled_Data.vOled_Write_Byte(CMD_DIRECTION_R_TO_L, OLED_CMD); /*Com scan direction*/
    Oled_Data.vOled_Write_Byte(0xD3, OLED_CMD); /*set display offset*/
    Oled_Data.vOled_Write_Byte(0x00, OLED_CMD); /* 0x20 */
    Oled_Data.vOled_Write_Byte(0xD5, OLED_CMD); /*set osc division*/
    Oled_Data.vOled_Write_Byte(0x80, OLED_CMD);
    Oled_Data.vOled_Write_Byte(0xD9, OLED_CMD); /*set pre-charge period*/
    Oled_Data.vOled_Write_Byte(0x1f, OLED_CMD); /*0x22*/
    Oled_Data.vOled_Write_Byte(0xDA, OLED_CMD); /*set COM pins*/
    Oled_Data.vOled_Write_Byte(0x12, OLED_CMD);
    Oled_Data.vOled_Write_Byte(0xdb, OLED_CMD); /*set vcomh*/
    Oled_Data.vOled_Write_Byte(0x40, OLED_CMD);
    Oled_Data.vOled_Clear();
    Oled_Data.vOled_Write_Byte(CMD_DISPLAY_ON, OLED_CMD); /*display ON*/
}

/*
 * @description: 指定大小显示BMP
 * @param {unsigned char} x0
 * @param {unsigned char} y0
 * @param {unsigned char} x1
 * @param {unsigned char} y1
 * @param {unsigned char} BMP
 * @return: 无
 * @Date: 2023-04-23 11:02:48
 */
// 指定大小显示BMP
void vOled_Draw_Bmp(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char BMP[])
{
    unsigned int j = 0;
    unsigned char x, y;
    if (y1 % 8 == 0)
    {
        y = y1 / 8;
    }
    else
    {
        y = y1 / 8 + 1;
    }
    for (y = y0; y < y1; y++)
    {
        Oled_Data.vOled_Set_Address(x0, y);
        for (x = x0; x < x1; x++)
        {
            Oled_Data.vOled_Write_Byte(BMP[j++], OLED_DATA);
        }
    }
}


/*
 * @description: 点亮/熄灭OLED屏幕某一行像素
 * @param {uint8_t} row
 * @return {*}
 * @Date: 2023-04-23 18:48:18
 */
// 点亮/熄灭OLED屏幕某一行像素
void vOled_Light_Row(uint8_t row, uint8_t swch)
{
    // 设置OLED显示范围为第row行(row从0开始计算)
    Oled_Data.vOled_Set_Address(0, row);
    // 向OLED写入每个像素点的数据,可以根据需要修改该部分代码
    for (uint8_t col = 0; col < 128; col++)
    {
        if(SET == swch)
        {
            Oled_Data.vOled_Write_Byte(0x01, OLED_DATA); // 写入0x01表示该像素点为白色(0xFF则表示8行像素即1列大小)
        }
        else
        {
            Oled_Data.vOled_Write_Byte(0x00, OLED_DATA); // 灭
        }
    }
}

/*
 * @description: 写入一组标准ASCII字符串	 19x24  数字字符
 * @param {uint8_t} x 范围0~128
 * @param {uint8_t} y 范围0~7
 * @param {uint8_t} ch 要显示的字符串
 * @return {*} 无
 * @Date: 2023-04-25 14:53:14
 */
// 写入一组标准ASCII字符串	 19x24  数字字符
void vOled_Asc_19x24(uint8_t x, uint8_t y, uint8_t ch[])
{
    uint8_t c = 0, i = 0, j = 0;
    while (ch[j] != '\0')
    {
        c = ch[j] - 0x30;   //查字符在库中位置
        Oled_Data.vOled_Set_Address(x, y); //设置坐标
        for (i = 0; i < 19; i++)
        {
            Oled_Data.vOled_Write_Byte(F19x24[c][i], 1);
        }
        Oled_Data.vOled_Set_Address(x, y + 1);
        for (i = 19; i < 38; i++)
        {
            Oled_Data.vOled_Write_Byte(F19x24[c][i], 1);
        }
        Oled_Data.vOled_Set_Address(x, y + 2);
        for (i = 38; i < 57; i++)
        {
            Oled_Data.vOled_Write_Byte(F19x24[c][i], 1);
        }
        x += 22; //下个字符的x坐标
        j++;     //下个字符
    }
}

/*
 * @description: 画横向的短线
 * @param {uint8_t} x x坐标
 * @param {uint8_t} y y坐标
 * @param {uint8_t} num 需要的像素长度(左到右)
 * @return {*} 无
 * @Date: 2023-04-26 11:14:28
 */
// 画横向的短线
void vOled_Painting_Pixel(uint8_t x, uint8_t y, uint8_t num)
{
    for(uint8_t i = 0; i <= num; i++)
    {
        Oled_Data.vOled_Set_Address(x + i, y);
        Oled_Data.vOled_Write_Byte(0x01, OLED_DATA); // 写入0x01表示该像素点为白色(0xFF则表示8行像素即1列大小)
    }
}
Bmp.c
// 略

AS608指纹

  • 接线(看模块丝印,正负极不要接反!WAK引脚接不接无所谓看需要)
AS608 STM32管脚
3V3(红色) 3V3
GND(黑色) GND
RXD(白色) PB10(USART3_TX)
TXD(黄色) PB11(USART3_RX)
WAK(蓝色) PA15,有感应输出高电平,默认设置下拉输入即可
Vt(绿色) 触摸感应电源输入端,接3.3V(这个不接的话WAK引脚没用!)
  • 需要用到一个定时器 TIM3进行串口接收时间计数
  • 可以先把模块连接USB转串口模块然后打开上位机 SYDemo 进行测试录入等功能看看模块是否是好的
  • 一般是通过判断WAK引脚来看用户是否按下,还有就是那个模块的氛围灯好像关不了找手册也没看到,看着好不舒服,我想的是按下才亮,不按就灭…
  • 程序编写
AS608.h
/*
*@Description: 指纹模块
*@Author: Yang
*@Date: 2023-04-21 15:16:12
*/
#ifndef __AS608_H
#define __AS608_H
#include "MyAll.h"

// AS608模块WAK引脚
#define AS608_WAK_PIN GPIO_PIN_15
// 特征存储位置1
#define AS608_SAVE_1    0x01
// 特征存储位置2
#define AS608_SAVE_2    0x02
// 指纹ID最大数(看手册0~299)
#define AS608_MAX_ID    299

// 指令-正确(0x00)
#define AS608_RETURN_TRUE    0x00
// 指令-未搜到指纹(0x09)
#define AS608_RETURN_NOT_FOUND  0x09
// 指令-处于一直把指纹放在上面状态
#define AS608_RETURN_ALWAY_LEAVE    0x17

// result值~处于一直把指纹放在上面状态
#define RESULT_ALWAY_LEAVE    3
// result值~默认值
#define RESULT_DEFAULT_VALUE    2

// 管理员指纹ID
#define ADMIN_ID    1

// LOG测试
#define AS608_LOG_1 0

// 开锁后倒计时时间(s)
#define AS608_OPEN_LOCK_TIME    6

typedef struct
{
	uint16_t pageID;//指纹ID
	uint16_t mathscore;//匹配得分
}SearchResult;

typedef struct
{
	uint16_t PS_max;//指纹最大容量
	uint8_t  PS_level;//安全等级
	uint32_t PS_addr;
	uint8_t  PS_size;//通讯数据包大小
	uint8_t  PS_N;//波特率基数N
}SysPara;

typedef struct
{
    // 有效指纹个数
    uint16_t AS608_Fingerprint_Number;
    // 是否有指纹按下标志位(wak引脚)
    bool AS608_Wak_Flag;
    // 默认地址
    uint32_t AS608_Addr;
    uint8_t (*ucAS608_Get_Templete_Number)(uint16_t*);
    uint8_t (*ucAS608_Admin_Brush_Fingerprint)(void);
    void (*vAS608_Add_Fingerprint)(void);
    void (*vAS608_Delete_Fingerprint)(void);
    void (*vAS608_Search_Fingerprint_Number)(void);
    void (*vAS608_Verify_Fingerprint_1)(void);
    void (*vAS608_Fingerprint_Control_Function)(void);
    void (*vAS608_Empty_Fingerprint_All)(void);
}AS608_TypeDef;

extern AS608_TypeDef AS608_Data;

uint8_t ucAS608_Admin_Brush_Fingerprint(void);
void vAS608_Add_Fingerprint(void);
void vAS608_Delete_Fingerprint(void);
void vAS608_Search_Fingerprint_Number(void);
void vAS608_Verify_Fingerprint_1(void);
void vAS608_Fingerprint_Control_Function(void);
void vAS608_Empty_Fingerprint_All(void);
uint8_t ucAS608_Get_Templete_Number(uint16_t *ValidN);
#endif
AS608.c
/*
 *@Description: 指纹模块 接线:红色-->3.3 黑色-->GND 黄色-->USART3_RX 白色-->USART3_TX  蓝色-->PA15 绿色-->3.3V
 *@Author: Yang
 *@Date: 2023-04-21 15:07:12
 */
#include "As608.h"



/*====================================静态内部函数声明区 BEGIN====================================*/
static void svUsart3_Send_Byte(uint8_t byte_data);
static void svAS608_Send_Head(void);
static void svAS608_Send_Addr(void);
static void svAS608_Send_Flag(uint8_t flag);
static void svAS608_Send_Length(int length);
static void svAS608_Send_Cmd(uint8_t cmd);
static void svAS608_Send_Word(uint32_t word);
static void svAS608_Send_Check(uint16_t check);
static uint8_t sucAS608_Shake_Hand(uint32_t *PS_Addr);
static uint8_t *spucAS608_Wait_Str(uint16_t waittime);
static uint8_t sucAS608_Get_Image(void);
static uint8_t sucAS608_Create_Feature(uint8_t save_id);
static uint8_t sucAS608_Match(void);
static uint8_t sucAS608_Search(uint8_t save_id, uint16_t StartPage, uint16_t PageNum, SearchResult *p);
static uint8_t sucAS608_Compound_Feature(void);
static uint8_t sucAS608_Save_Template(uint8_t save_id, uint16_t PageID);
static uint8_t sucAS608_Delete_Template(uint16_t PageID, uint16_t N);
static uint8_t sucAS608_Empty_Template(void);
static uint8_t sucAS608_Write_Reg(uint8_t RegNum, uint8_t DATA);
static uint8_t sucAS608_Read_System_Parameters(SysPara *p);
static uint8_t sucAS608_Set_Addr(uint32_t PS_addr);
static uint8_t sucAS608_Write_Note(uint8_t NotePageNum, uint8_t *Byte32);
static uint8_t sucAS608_Read_Note(uint8_t NotePageNum, uint8_t *Byte32);
static uint8_t sucAS608_High_Speed_Search(uint8_t save_id, uint16_t StartPage, uint16_t PageNum, SearchResult *p);
static const char *cpcAS608_Error_Message(uint8_t ensure);
static uint8_t sucAS608_Verify_Fingerprint(SearchResult *p);
/*====================================静态内部函数声明区    END====================================*/



/*====================================变量区 BEGIN====================================*/
// 串口3接收数组
uint8_t Uart3_Rx_Buff[16];

AS608_TypeDef AS608_Data =
{
    .AS608_Fingerprint_Number = 0,
    .AS608_Wak_Flag = 0,
    .AS608_Addr = 0xFFFFFFFF,
    .ucAS608_Get_Templete_Number = &ucAS608_Get_Templete_Number,
    .ucAS608_Admin_Brush_Fingerprint = &ucAS608_Admin_Brush_Fingerprint,
    .vAS608_Add_Fingerprint = &vAS608_Add_Fingerprint,
    .vAS608_Delete_Fingerprint = &vAS608_Delete_Fingerprint,
    .vAS608_Search_Fingerprint_Number = &vAS608_Search_Fingerprint_Number,
    .vAS608_Verify_Fingerprint_1 = &vAS608_Verify_Fingerprint_1,
    .vAS608_Fingerprint_Control_Function = &vAS608_Fingerprint_Control_Function,
    .vAS608_Empty_Fingerprint_All = &vAS608_Empty_Fingerprint_All
};
/*====================================变量区    END====================================*/










/*
 * @description: 向串口3发送一个字节数据
 * @param: 要发送的数据
 * @return: 无
 * @Date: 2023-04-22 09:00:58
 */
// 向串口3发送一个字节数据
static void svUsart3_Send_Byte(uint8_t byte_data)
{
    // 循环等待USART3发送寄存器(SR)的第6位标志位(TXE)变为1,表示发送缓冲区为空,可以继续发送数据
    while (0 == (USART3->SR & 0X40))
        ;
    // 赋值给USART3数据寄存器(DR),从而实现向USART3发送数据
    USART3->DR = byte_data;
}

/*
 * @description: 向AS608发送包头
 * @return: 无
 * @Date: 2023-04-22 09:14:27
 */
// 向AS608发送包头
static void svAS608_Send_Head(void)
{
    svUsart3_Send_Byte(0xEF);
    svUsart3_Send_Byte(0x01);
}

/*
 * @description: 向AS608发送芯片地址
 * @return: 无
 * @Date: 2023-04-22 09:20:40
 */
// 向AS608发送芯片地址
static void svAS608_Send_Addr(void)
{
    svUsart3_Send_Byte((AS608_Data.AS608_Addr) >> 24);
    svUsart3_Send_Byte((AS608_Data.AS608_Addr) >> 16);
    svUsart3_Send_Byte((AS608_Data.AS608_Addr) >> 8);
    svUsart3_Send_Byte(AS608_Data.AS608_Addr);
}

/*
 * @description: 向AS608发送标识符
 * @param: 标识符
 * @return: 无
 * @Date: 2023-04-22 09:22:24
 */
// 向AS608发送标识符
static void svAS608_Send_Flag(uint8_t flag)
{
    svUsart3_Send_Byte(flag);
}

/*
 * @description: 向AS608发送包长度
 * @param: 包长度
 * @return: 无
 * @Date: 2023-04-22 09:24:40
 */
// 向AS608发送包长度
static void svAS608_Send_Length(int length)
{
    svUsart3_Send_Byte(length >> 8);
    svUsart3_Send_Byte(length);
}

/*
 * @description: 向AS608发送命令码
 * @param: 命令码
 * @return: 无
 * @Date: 2023-04-22 09:26:00
 */
// 向AS608发送命令码
static void svAS608_Send_Cmd(uint8_t cmd)
{
    svUsart3_Send_Byte(cmd);
}

/*
 * @description: 向AS608发送口令
 * @param: 口令
 * @return: 无
 * @Date: 2023-04-22 09:28:19
 */
// 向AS608发送口令
static void svAS608_Send_Word(uint32_t word)
{
    svUsart3_Send_Byte(word >> 24);
    svUsart3_Send_Byte(word >> 16);
    svUsart3_Send_Byte(word >> 8);
    svUsart3_Send_Byte(word);
}

/*
 * @description: 向AS608发送校验和
 * @param: 校验和
 * @return: 无
 * @Date: 2023-04-22 09:30:06
 */
// 向AS608发送校验和
static void svAS608_Send_Check(uint16_t check)
{
    svUsart3_Send_Byte(check >> 8);
    svUsart3_Send_Byte(check);
}

/*
 * @description: 与AS608握手
 * @param: PS_Addr地址指针
 * @return: 新的地址(正确地址)
 * @Date: 2023-04-22 10:40:10
 */
// 与AS608握手
static uint8_t sucAS608_Shake_Hand(uint32_t *PS_Addr)
{
    svAS608_Send_Head();                        // 包头EF01
    svAS608_Send_Addr();                        // 地址FFFFFFFF
    svAS608_Send_Flag(0X01);                    // 包标识:命令
    svAS608_Send_Length(0x0007);                // 包长度:1(命令码1byte)+4(口令4byte)+2(校验码2byte)
    svAS608_Send_Cmd(0x13);                     // 命令码
    svAS608_Send_Word(0x00000000);              // 口令:默认为00000000
    svAS608_Send_Check(0x1b);                   // 校验码为01+07+13=1b
    HAL_UART_Receive(&huart3, Uart3_Rx_Buff, 16, 100); // 等待回应

    if ((0XEF == Uart3_Rx_Buff[0]) && (0X01 == Uart3_Rx_Buff[1]) && (0X07 == Uart3_Rx_Buff[6])) // 判断应答包
    {
        *PS_Addr = (Uart3_Rx_Buff[2] << 24) + (Uart3_Rx_Buff[3] << 16) + (Uart3_Rx_Buff[4] << 8) + (Uart3_Rx_Buff[5]); // 更新address
        return 0;
    }
    return 1;
}

/*
 * @description: 判断中断接收的数组有没有应答包
 * @param: 等待中断接收数据的时间(单位1ms)
 * @return: 数据包首地址
 * @Date: 2023-04-22 10:37:32
 */
// 判断中断接收的数组有没有应答包
static uint8_t *spucAS608_Wait_Str(uint16_t waittime)
{
    char *data;
    uint8_t str[8];
    str[0] = 0xef;
    str[1] = 0x01;
    str[2] = (AS608_Data.AS608_Addr) >> 24;
    str[3] = (AS608_Data.AS608_Addr) >> 16;
    str[4] = (AS608_Data.AS608_Addr) >> 8;
    str[5] = AS608_Data.AS608_Addr;
    str[6] = 0x07;
    str[7] = '\0';

    MyUSART3_Data.Usart3_Rx_Sta = 0;
    while (--waittime)
    {
        HAL_Delay(1);
        if (MyUSART3_Data.Usart3_Rx_Sta & 0x8000) // 接收到一次数据
        {
            data = strstr((const char *)MyUSART3_Data.Usart3_Rx_Buff, (const char *)str); // 比较接收到的包
            if (data)
            {
                return (uint8_t *)data;
            }
        }
    }
    return 0;
}

/*
 * @description: 录入图像(探测手指,探测到后录入指纹图像存于ImageBuffer)
 * @return: 返回确认字
 * @Date: 2023-04-22 10:51:47
 */
// 录入图像(探测手指,探测到后录入指纹图像存于ImageBuffer)
static uint8_t sucAS608_Get_Image(void)
{
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x03);
    svAS608_Send_Cmd(0x01);
    svAS608_Send_Check(0x0005);
    data = spucAS608_Wait_Str(500); // 500
    if (data)
    {
        ensure = data[9];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 生成特征(将ImageBuffer中的原始图像生成指纹特征文件存于AS608_SAVE_1或AS608_SAVE_2)
 * @param: AS608_SAVE_1:0x01	AS608_SAVE_2:0x02
 * @return: 返回确认字
 * @Date: 2023-04-22 11:07:42
 */
// 生成特征
static uint8_t sucAS608_Create_Feature(uint8_t save_id)
{
    uint16_t temp;
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x04);
    svAS608_Send_Cmd(0x02);
    svUsart3_Send_Byte(save_id);
    temp = 0x01 + 0x04 + 0x02 + save_id;
    svAS608_Send_Check(temp);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 精确比对两枚指纹特征(精确比对AS608_SAVE_1 与AS608_SAVE_2 中的特征文件)
 * @return: 返回确认字
 * @Date: 2023-04-22 11:15:05
 */
// 精确比对两枚指纹特征
static uint8_t sucAS608_Match(void)
{
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x03);
    svAS608_Send_Cmd(0x03);
    svAS608_Send_Check(0x0007);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 搜索指纹(以AS608_SAVE_1或AS608_SAVE_2 中的特征文件搜索整个或部分指纹库.若搜索到,则返回页码)
 * @param: 特征存储位置1/2
 * @param: 某一页开始
 * @param: 到若干页
 * @param: 搜索结果存储到结构体变量p中
 * @return: 确认字,页码(相配指纹模板)
 * @Date: 2023-04-22 11:19:46
 */
// 搜索指纹
static uint8_t sucAS608_Search(uint8_t save_id, uint16_t StartPage, uint16_t PageNum, SearchResult *p)
{
    uint16_t temp;
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x08);
    svAS608_Send_Cmd(0x04);
    svUsart3_Send_Byte(save_id);
    svUsart3_Send_Byte(StartPage >> 8); // 因为为2byte数据
    svUsart3_Send_Byte(StartPage);
    svUsart3_Send_Byte(PageNum >> 8);
    svUsart3_Send_Byte(PageNum);
    temp = 0x01 + 0x08 + 0x04 + save_id + (StartPage >> 8) + (uint8_t)StartPage + (PageNum >> 8) + (uint8_t)PageNum;
    svAS608_Send_Check(temp);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
        p->pageID = (data[10] << 8) + data[11];
        p->mathscore = (data[12] << 8) + data[13];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 合并特征(生成模板)(将AS608_SAVE_1与AS608_SAVE_2中的特征文件合并生成 模板,结果存于AS608_SAVE_1与AS608_SAVE_2)
 * @return: 返回确认字
 * @Date: 2023-04-22 11:31:24
 */
// 生成模板
static uint8_t sucAS608_Compound_Feature(void)
{
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x03);
    svAS608_Send_Cmd(0x05);
    svAS608_Send_Check(0x0009);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 储存模板(将 AS608_SAVE_1 或 AS608_SAVE_2 中的模板文件存到 PageID 号flash数据库位置)
 * @param: AS608_SAVE_1/AS608_SAVE_2
 * @param: 指纹库位置号
 * @return: 返回确认字
 * @Date: 2023-04-22 11:36:52
 */
// 储存模板
static uint8_t sucAS608_Save_Template(uint8_t save_id, uint16_t PageID)
{
    uint16_t temp;
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x06);
    svAS608_Send_Cmd(0x06);
    svUsart3_Send_Byte(save_id);
    svUsart3_Send_Byte(PageID >> 8);
    svUsart3_Send_Byte(PageID);
    temp = 0x01 + 0x06 + 0x06 + save_id + (PageID >> 8) + (uint8_t)PageID;
    svAS608_Send_Check(temp);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 删除模板(删除flash数据库中指定ID号开始的连续N个指纹模板)
 * @param: 指纹库模板号
 * @param: 删除的模板个数
 * @return: 返回确认字
 * @Date: 2023-04-22 11:41:24
 */
// 删除单个模板
static uint8_t sucAS608_Delete_Template(uint16_t PageID, uint16_t N)
{
    uint16_t temp;
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x07);
    svAS608_Send_Cmd(0x0C);
    svUsart3_Send_Byte(PageID >> 8);
    svUsart3_Send_Byte(PageID);
    svUsart3_Send_Byte(N >> 8);
    svUsart3_Send_Byte(N);
    temp = 0x01 + 0x07 + 0x0C + (PageID >> 8) + (uint8_t)PageID + (N >> 8) + (uint8_t)N;
    svAS608_Send_Check(temp);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 清空指纹库(删除flash数据库中所有指纹模板)
 * @return: 返回确认字
 * @Date: 2023-04-22 11:45:51
 */
// 清空指纹库(删除flash数据库中所有指纹模板)
static uint8_t sucAS608_Empty_Template(void)
{
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x03);
    svAS608_Send_Cmd(0x0D);
    svAS608_Send_Check(0x0011);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 写系统寄存器(写模块寄存器)
 * @param: 寄存器序号RegNum:4\5\6
 * @param: 数据
 * @return: 返回确认字
 * @Date: 2023-04-22 11:54:03
 */
// 写系统寄存器(写模块寄存器)
static uint8_t sucAS608_Write_Reg(uint8_t RegNum, uint8_t DATA)
{
    uint16_t temp;
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x05);
    svAS608_Send_Cmd(0x0E);
    svUsart3_Send_Byte(RegNum);
    svUsart3_Send_Byte(DATA);
    temp = RegNum + DATA + 0x01 + 0x05 + 0x0E;
    svAS608_Send_Check(temp);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 读系统基本参数(读取模块的基本参数(波特率,包大小等))
 * @param: 结构体指针
 * @return: 返回确认字 + 基本参数(16bytes)
 * @Date: 2023-04-22 12:01:40
 */
// 读系统基本参数(读取模块的基本参数(波特率,包大小等))
static uint8_t sucAS608_Read_System_Parameters(SysPara *p)
{
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x03);
    svAS608_Send_Cmd(0x0F);
    svAS608_Send_Check(0x0013);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
        p->PS_max = (data[14] << 8) + data[15];                                        // 模块最大指纹容量
        p->PS_level = data[17];                                                        // 对比等级
        p->PS_addr = (data[18] << 24) + (data[19] << 16) + (data[20] << 8) + data[21]; // 地址
        p->PS_size = data[23];
        p->PS_N = data[25];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 设置模块地址
 * @param: 地址
 * @return: 返回确认字
 * @Date: 2023-04-22 12:05:59
 */
// 设置模块地址
static uint8_t sucAS608_Set_Addr(uint32_t PS_addr)
{
    uint16_t temp;
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x07);
    svAS608_Send_Cmd(0x15);
    svUsart3_Send_Byte(PS_addr >> 24);
    svUsart3_Send_Byte(PS_addr >> 16);
    svUsart3_Send_Byte(PS_addr >> 8);
    svUsart3_Send_Byte(PS_addr);
    temp = 0x01 + 0x07 + 0x15 + (uint8_t)(PS_addr >> 24) + (uint8_t)(PS_addr >> 16) + (uint8_t)(PS_addr >> 8) + (uint8_t)PS_addr;
    svAS608_Send_Check(temp);
    AS608_Data.AS608_Addr = PS_addr; // 发送完指令,更换地址
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
    }
    else
    {
        ensure = 0xFF;
    }
    AS608_Data.AS608_Addr = PS_addr;
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 写入记事本(模块内部为用户开辟了256bytes的FLASH空间用于存用户记事本该记事本逻辑上被分成 16 个页)
 * @param: 0~15
 * @param: 要写入的内容
 * @return:返回确认字
 * @Date: 2023-04-22 12:10:12
 */
// 写入记事本
static uint8_t sucAS608_Write_Note(uint8_t NotePageNum, uint8_t *Byte32)
{
    uint16_t temp;
    uint8_t ensure, i;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(36);
    svAS608_Send_Cmd(0x18);
    svUsart3_Send_Byte(NotePageNum);
    for (i = 0; i < 32; i++)
    {
        svUsart3_Send_Byte(Byte32[i]);
        temp += Byte32[i];
    }
    temp = 0x01 + 36 + 0x18 + NotePageNum + temp;
    svAS608_Send_Check(temp);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 读记事(读取FLASH用户区的128bytes数据)
 * @param: 0~15
 * @param: 存储读到的数据
 * @return: 返回确认字+用户信息
 * @Date: 2023-04-22 12:14:35
 */
// 读记事
static uint8_t sucAS608_Read_Note(uint8_t NotePageNum, uint8_t *Byte32)
{
    uint16_t temp;
    uint8_t ensure, i;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x04);
    svAS608_Send_Cmd(0x19);
    svUsart3_Send_Byte(NotePageNum);
    temp = 0x01 + 0x04 + 0x19 + NotePageNum;
    svAS608_Send_Check(temp);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
        for (i = 0; i < 32; i++)
        {
            Byte32[i] = data[10 + i];
        }
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 高速搜索(以 AS608_SAVE_1 或 AS608_SAVE_2 中的特征文件高速搜索整个或部分指纹库。若搜索到,则返回页码,该指令对于的确存在于指纹库中 ,且登录时质量很好的指纹,会很快给出搜索结果。)
 * @param: AS608_SAVE_1/AS608_SAVE_2
 * @param: 起始页
 * @param: 页数
 * @param: 存储指针
 * @return: 返回确认字+页码(相配指纹模板)
 * @Date: 2023-04-22 12:18:37
 */
// 高速搜索指纹库
static uint8_t sucAS608_High_Speed_Search(uint8_t save_id, uint16_t StartPage, uint16_t PageNum, SearchResult *p)
{
    uint16_t temp;
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x08);
    svAS608_Send_Cmd(0x1b);
    svUsart3_Send_Byte(save_id);
    svUsart3_Send_Byte(StartPage >> 8);
    svUsart3_Send_Byte(StartPage);
    svUsart3_Send_Byte(PageNum >> 8);
    svUsart3_Send_Byte(PageNum);
    temp = 0x01 + 0x08 + 0x1b + save_id + (StartPage >> 8) + (uint8_t)StartPage + (PageNum >> 8) + (uint8_t)PageNum;
    svAS608_Send_Check(temp);
    data = spucAS608_Wait_Str(600);
    if (data)
    {
        ensure = data[9];
        p->pageID = (data[10] << 8) + data[11];
        p->mathscore = (data[12] << 8) + data[13];
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 读有效模板个数
 * @param: 存储读取结果
 * @return: 返回确认字+有效模板个数ValidN
 * @Date: 2023-04-22 12:24:55
 */
// 读有效模板个数(需要初始化时读取一次否则后面第一次读取是错误然后才正常)
uint8_t ucAS608_Get_Templete_Number(uint16_t *ValidN)
{
    uint8_t ensure;
    uint8_t *data;

    svAS608_Send_Head();     // 包头EF01
    svAS608_Send_Addr();     // 地址
    svAS608_Send_Flag(0X01); // 包标识:命令
    svAS608_Send_Length(0x03);
    svAS608_Send_Cmd(0x1d);
    svAS608_Send_Check(0x0021);
    data = spucAS608_Wait_Str(500);
    if (data)
    {
        ensure = data[9];
        *ValidN = (data[10] << 8) + data[11]; // 有效指纹数
    }
    else
    {
        ensure = 0xFF;
    }
    memset(MyUSART3_Data.Usart3_Rx_Buff, 0, MyUSART3_Data.Usart3_Rx_Sta & 0x7FFF);
    MyUSART3_Data.Usart3_Rx_Sta = 0;
    return ensure;
}

/*
 * @description: 各种错误信息输出
 * @param: 错误编号
 * @return: 字符串
 * @Date: 2023-04-22 12:28:56
 */
// 各种错误信息输出
static const char *cpcAS608_Error_Message(uint8_t ensure)
{
    const char *p;
    switch (ensure)
    {
    case AS608_RETURN_TRUE:
    {
        p = "OK";
        break;
    }
    case 0x01:
    {
        p = "数据包接收错误";
        break;
    }
    case 0x02:
        p = "传感器上没有手指";
        break;
    case 0x03:
    {
        p = "录入指纹图像失败";
        break;
    }
    case 0x04:
    {
        p = "指纹图像太干、太淡而生不成特征";
        break;
    }
    case 0x05:
    {
        p = "指纹图像太湿、太糊而生不成特征";
        break;
    }
    case 0x06:
    {
        p = "指纹图像太乱而生不成特征";
        break;
    }
    case 0x07:
    {
        p = "指纹图像正常,但特征点太少(或面积太小)而生不成特征";
        break;
    }
    case 0x08:
    {
        p = "指纹不匹配";
        break;
    }
    case AS608_RETURN_NOT_FOUND:
    {
        p = "没搜索到指纹";
        break;
    }
    case 0x0a:
    {
        p = "特征合并失败";
        break;
    }
    case 0x0b:
    {
        p = "访问指纹库时地址序号超出指纹库范围";
        break;
    }
    case 0x10:
    {
        p = "删除模板失败";
        break;
    }
    case 0x11:
    {
        p = "清空指纹库失败";
        break;
    }
    case 0x15:
    {
        p = "缓冲区内没有有效原始图而生不成图像";
        break;
    }
    case 0x18:
    {
        p = "读写 FLASH 出错";
        break;
    }
    case 0x19:
    {
        p = "未定义错误";
        break;
    }
    case 0x1a:
    {
        p = "无效寄存器号";
        break;
    }
    case 0x1b:
    {
        p = "寄存器设定内容错误";
        break;
    }
    case 0x1c:
    {
        p = "记事本页码指定错误";
        break;
    }
    case 0x1f:
    {
        p = "指纹库满";
        break;
    }
    case 0x20:
    {
        p = "地址错误";
        break;
    }
    case AS608_RETURN_ALWAY_LEAVE:
    {
        p = "处于一直把指纹放在上面状态";
        break;
    }
    default:
    {
        p = "模块返回确认码有误";
        break;
    }
    }
    return p;
}

/*******************************************应用层**************************************/
/*
 * @description: 验证指纹
 * @return {*} 验证成功-FINGERPRINT_TRUE 验证失败-FINGERPRINT_FALSE
 * @Date: 2023-04-24 12:01:10
 */
// 验证指纹
static uint8_t sucAS608_Verify_Fingerprint(SearchResult *p)
{
    uint8_t ensure;
    uint8_t result;
    while (1)
    {
        ensure = sucAS608_Get_Image();
        if (AS608_RETURN_TRUE == ensure) // 获取图像成功
        {
            ensure = sucAS608_Create_Feature(AS608_SAVE_2);
            if (AS608_RETURN_TRUE == ensure) // 生成特征成功
            {
                ensure = sucAS608_High_Speed_Search(AS608_SAVE_2, 0, AS608_MAX_ID, (SearchResult *)p);
#if AS608_LOG_1
                printf("%d\r\n", ensure);
                printf("%s\r\n", cpcAS608_Error_Message(ensure));
                printf("R:%d---%d\r\n", p->pageID, p->mathscore);
#endif
                if (AS608_RETURN_TRUE == ensure) // 搜索成功
                {
                    result = FINGERPRINT_TRUE;
                    break;
                }
                else if (AS608_RETURN_NOT_FOUND == ensure) // 搜索不到
                {
                    result = FINGERPRINT_FALSE;
                    break;
                }
                else if (AS608_RETURN_ALWAY_LEAVE == ensure)
                {
                    result = RESULT_ALWAY_LEAVE;
                    break;
                }
            }
            else
            {
                result = RESULT_DEFAULT_VALUE;
                break;
            }
        }
        else
        {
            result = RESULT_DEFAULT_VALUE;
            break;
        }
    }
    return result;
}

/*
 * @description: 管理员刷指纹验证
 * @return {*} 验证成功-FINGERPRINT_TRUE 验证失败-FINGERPRINT_FALSE
 * @Date: 2023-04-24 10:58:37
 */
// 管理员刷指纹验证
uint8_t ucAS608_Admin_Brush_Fingerprint(void)
{
    uint8_t result = RESULT_DEFAULT_VALUE;
    SearchResult search;

    result = sucAS608_Verify_Fingerprint(&search);
    if (FINGERPRINT_TRUE == result) // 验证成功
    {
        if (Admin_Data.Admin_Fingerprint_ID == search.pageID) // 对比ID号
        {
            Buzzer_Data.vBuzzer_Ring();
            // 回到主页面
            printf("%d\r\n", search.pageID);
            Menu_Data.Menu_State = INTERFACE_1;
            Menu_Data.Menu_Flag_Buff[0] = 0;
            Admin_Data.Administrator_Flag = 1;
        }
        // printf("L:%d---%d\r\n",search.pageID,search.mathscore);
    }
    return result;
}

/*
 * @description: 添加指纹,存储到指定ID
 * @return {*}
 * @Date: 2023-04-24 15:02:54
 */
// 添加指纹,存储到指定ID
void vAS608_Add_Fingerprint(void)
{
    uint8_t ensure;
    // 执行步骤索引
    uint8_t Add_Index = 1;
    // 可选择ID范围0~299(但是一般不要用0,1~299即可)
    uint16_t id = 1;
    uint8_t arr[20];

    Oled_Data.vOled_Clear();
    Oled_Data.vOled_Light_Row(5, SET); // 显示一行像素
    Oled_Data.vOled_Display_Gb2312_String(2, 6, (uint8_t *)"返回");
    // 当录指纹成功或者按返回才会回到上一级菜单
    while (1)
    {
        switch (Add_Index)
        {
        case 1:
        {
            // 显示文字
            Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"请按指纹");
            ensure = sucAS608_Get_Image();
            if (AS608_RETURN_TRUE == ensure) // 等待模块发回指令
            {
                Buzzer_Data.vBuzzer_Ring();
                HAL_Delay(100); // 这里需要延时一下,模块内部处理图像需要时间
                ensure = sucAS608_Create_Feature(AS608_SAVE_1);
                if (AS608_RETURN_TRUE == ensure)
                {
                    Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"指纹正常");
                    HAL_Delay(MESSAGE_TIME);
                    Add_Index++; // 跳到下一步
                }
                else
                {
                    printf("%s\r\n", cpcAS608_Error_Message(ensure));
                }
            }
            else
            {
                printf("%s\r\n", cpcAS608_Error_Message(ensure));
            }
            break;
        }
        case 2:
        {
            Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"再按一次");
            ensure = sucAS608_Get_Image();
            if (AS608_RETURN_TRUE == ensure) // 等待模块发回指令
            {
                Buzzer_Data.vBuzzer_Ring();
                HAL_Delay(100); // 这里需要延时一下,模块内部处理图像需要时间
                ensure = sucAS608_Create_Feature(AS608_SAVE_2);
                if (AS608_RETURN_TRUE == ensure)
                {
                    Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"指纹正常");
                    HAL_Delay(MESSAGE_TIME);
                    Add_Index++; // 跳到下一步
                }
                else
                {
                    printf("%s\r\n", cpcAS608_Error_Message(ensure));
                    Add_Index = 1;
                }
            }
            else
            {
                printf("%s\r\n", cpcAS608_Error_Message(ensure));
            }
            break;
        }
        case 3:
        {
            Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"对比指纹");
            HAL_Delay(MESSAGE_TIME);
            ensure = sucAS608_Match();
            if (AS608_RETURN_TRUE == ensure) // 对比成功
            {
                Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"对比成功");
                HAL_Delay(MESSAGE_TIME);
                Add_Index++; // 跳到下一步
            }
            else // 对比失败
            {
                Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"对比失败");
                HAL_Delay(MESSAGE_TIME);
                Add_Index = 1; // 回到第一步
            }
            break;
        }
        case 4:
        {
            Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"生成模板");
            HAL_Delay(MESSAGE_TIME);
            ensure = sucAS608_Compound_Feature();
            if (AS608_RETURN_TRUE == ensure) // 生成模板成功
            {
                Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"生成成功");
                HAL_Delay(MESSAGE_TIME);
                Add_Index++; // 跳到下一步
            }
            else
            {
                Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"生成失败");
                HAL_Delay(MESSAGE_TIME);
                Add_Index = 1; // 回到第一步
            }
            break;
        }
        case 5: // 选择存储ID
        {
            Oled_Data.vOled_Display_Gb2312_String(2, 6, (uint8_t *)"返回");
            Oled_Data.vOled_Display_Gb2312_String(47, 6, (uint8_t *)"加减");
            Oled_Data.vOled_Display_Gb2312_String(96, 6, (uint8_t *)"确认");
            if (Key_Data.Key_Down_Buff[1]) // K2短按++
            {
                Key_Data.Key_Down_Buff[1] = 0;
                Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
                id++;
                if (id > 299)
                {
                    id = 1;
                }
                Oled_Data.vOled_Clear();
            }
            else if (Key_Data.Key_Down_Buff[4]) // K2长按
            {
                Key_Data.Key_Down_Buff[4] = 0;
                Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
                if (id > 1)
                {
                    id--;
                }
                Oled_Data.vOled_Clear();
            }
            else if (Key_Data.Key_Down_Buff[2]) // K3短按确认
            {
                Key_Data.Key_Down_Buff[2] = 0;
                Buzzer_Data.vBuzzer_Ring();                        // 蜂鸣器滴一下
                ensure = sucAS608_Save_Template(AS608_SAVE_2, id); // 把第二次按下的存储
                if (AS608_RETURN_TRUE == ensure)
                {
                    Oled_Data.vOled_Clear();
                    snprintf(arr, sizeof(arr), "添加成功,ID为%d", id);
                    Oled_Data.vOled_Display_Gb2312_String(10, 2, (uint8_t *)arr);
                    HAL_Delay(MESSAGE_TIME);
                    Menu_Data.Menu_State = INTERFACE_3;
                    Menu_Data.Menu_Flag_Buff[2] = 1;
                    return;
                }
                else
                {
                    Oled_Data.vOled_Clear();
                    Oled_Data.vOled_Display_Gb2312_String(27, 2, (uint8_t *)"添加失败");
                    HAL_Delay(MESSAGE_TIME);
                    Add_Index = 1; // 回到第一步
                }
            }
            snprintf(arr, sizeof(arr), "选择存储ID为:%d", id);
            Oled_Data.vOled_Display_Gb2312_String(0, 2, (uint8_t *)arr);
            break;
        }
        }
        // 回到功能页面
        if (Key_Data.Key_Down_Buff[0])
        {
            Key_Data.Key_Down_Buff[0] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            Menu_Data.Menu_State = INTERFACE_3;
            Menu_Data.Menu_Flag_Buff[2] = 1;
            return;
        }
        MyUSART1_Data.vUsart1_Rx_Data_Analytic(); // 不要忘记串口数据解析(因为这是死循环所以不放这接收不到)
    }
}

/*
 * @description: 指定ID删除单个指纹
 * @return {*}
 * @Date: 2023-04-24 18:05:44
 */
// 指定ID删除单个指纹
void vAS608_Delete_Fingerprint(void)
{
    uint8_t ensure;
    uint8_t Add_Index = 1;
    // 可选择ID范围0~299
    uint16_t id = 0;
    uint8_t arr[30];

    Oled_Data.vOled_Clear();
    while (1)
    {
        switch (Add_Index)
        {
        case 1:
        {
            Oled_Data.vOled_Light_Row(5, SET); // 显示一行像素
            snprintf(arr, sizeof(arr), "要删除的ID为:%d", id);
            Oled_Data.vOled_Display_Gb2312_String(0, 2, (uint8_t *)arr);
            Oled_Data.vOled_Display_Gb2312_String(2, 6, (uint8_t *)"返回");
            Oled_Data.vOled_Display_Gb2312_String(47, 6, (uint8_t *)"加减");
            Oled_Data.vOled_Display_Gb2312_String(96, 6, (uint8_t *)"确认");
            if (Key_Data.Key_Down_Buff[1])
            {
                Key_Data.Key_Down_Buff[1] = 0;
                Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
                id++;
                if (id >= 299)
                {
                    id = 299;
                }
                Oled_Data.vOled_Clear();
            }
            if (Key_Data.Key_Down_Buff[4])
            {
                Key_Data.Key_Down_Buff[4] = 0;
                Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
                if (id != 0)
                {
                    id--;
                }
                Oled_Data.vOled_Clear();
            }
            if (Key_Data.Key_Down_Buff[2]) // 确认
            {
                Key_Data.Key_Down_Buff[2] = 0;
                Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
                snprintf(arr, sizeof(arr), "确认要删除id%d吗?", id);
                Oled_Data.vOled_Clear();
                Oled_Data.vOled_Display_Gb2312_String(0, 2, (uint8_t *)arr);
                Oled_Data.vOled_Display_Gb2312_String(2, 6, (uint8_t *)"取消");
                Oled_Data.vOled_Display_Gb2312_String(96, 6, (uint8_t *)"确认");
                Add_Index++;
            }
            break;
        }
        case 2:
        {
            if (Key_Data.Key_Down_Buff[0]) // 取消
            {
                Key_Data.Key_Down_Buff[0] = 0;
                Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
                id = 0;
                Oled_Data.vOled_Clear();
                Add_Index = 1;
            }
            if (Key_Data.Key_Down_Buff[2]) // 确认
            {
                Key_Data.Key_Down_Buff[2] = 0;
                Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
                ensure = sucAS608_Delete_Template(id, 1);
                if (AS608_RETURN_TRUE == ensure)
                {
                    Oled_Data.vOled_Clear();
                    Oled_Data.vOled_Light_Row(5, SET); // 显示一行像素
                    snprintf(arr, sizeof(arr), "删除ID%d成功", id);
                    Oled_Data.vOled_Display_Gb2312_String(0, 2, (uint8_t *)arr);
                    HAL_Delay(MESSAGE_TIME);
                    Menu_Data.Menu_State = INTERFACE_3;
                    Menu_Data.Menu_Flag_Buff[2] = 1;
                    return;
                }
                else
                {
                    Oled_Data.vOled_Clear();
                    Oled_Data.vOled_Light_Row(5, SET); // 显示一行像素
                    memset(arr, 0, sizeof(arr));
                    snprintf(arr, sizeof(arr), "删除ID%d失败", id);
                    Oled_Data.vOled_Display_Gb2312_String(0, 2, (uint8_t *)arr);
                    HAL_Delay(MESSAGE_TIME);
                    Oled_Data.vOled_Clear();
                    Add_Index = 1;
                }
            }
            break;
        }
        }
        // 回到功能页面
        if (Key_Data.Key_Down_Buff[0])
        {
            Key_Data.Key_Down_Buff[0] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            Menu_Data.Menu_State = INTERFACE_3;
            Menu_Data.Menu_Flag_Buff[2] = 1;
            return;
        }
        MyUSART1_Data.vUsart1_Rx_Data_Analytic(); // 不要忘记串口数据解析(因为这是死循环所以不放这接收不到)
    }
}

/*
 * @description: 指纹数量
 * @return {*}
 * @Date: 2023-04-24 19:24:54
 */
// 指纹数量
void vAS608_Search_Fingerprint_Number(void)
{
    uint8_t arr[20];
    uint8_t ensure;
    Oled_Data.vOled_Clear();
    Oled_Data.vOled_Light_Row(5, SET); // 显示一行像素
    Oled_Data.vOled_Display_Gb2312_String(2, 6, (uint8_t *)"返回");
    ensure = AS608_Data.ucAS608_Get_Templete_Number(&AS608_Data.AS608_Fingerprint_Number);
    while (1)
    {
        if (AS608_RETURN_TRUE == ensure)
        {
            snprintf(arr, sizeof(arr), "已用ID:%d 个", AS608_Data.AS608_Fingerprint_Number);
            Oled_Data.vOled_Display_Gb2312_String(10, 0, (uint8_t *)arr);
            snprintf(arr, sizeof(arr), "剩余ID:%d 个", AS608_MAX_ID - AS608_Data.AS608_Fingerprint_Number);
            Oled_Data.vOled_Display_Gb2312_String(10, 2, (uint8_t *)arr);
        }
        else
        {
            Oled_Data.vOled_Display_Gb2312_String(10, 1, (uint8_t *)"查询失败!!!");
            HAL_Delay(MESSAGE_TIME);
            Menu_Data.Menu_State = INTERFACE_3;
            Menu_Data.Menu_Flag_Buff[2] = 1;
        }
        // 回到功能页面
        if (Key_Data.Key_Down_Buff[0])
        {
            Key_Data.Key_Down_Buff[0] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            Menu_Data.Menu_State = INTERFACE_3;
            Menu_Data.Menu_Flag_Buff[2] = 1;
            return;
        }
        if (INTERFACE_6 == Menu_Data.Menu_State)
        {
            return;
        }
        MyUSART1_Data.vUsart1_Rx_Data_Analytic(); // 串口通信
    }
}

/*
 * @description: 验证指纹---功能区
 * @return {*} 无
 * @Date: 2023-04-26 13:00:23
 */
// 验证指纹---功能区
void vAS608_Verify_Fingerprint_1(void)
{
    uint8_t v_Flag = 0;
    SearchResult search;
    uint8_t result = RESULT_DEFAULT_VALUE;
    uint8_t arr[20];

    while (1)
    {
        MyUSART1_Data.vUsart1_Rx_Data_Analytic(); // 串口通信
        // 回到功能页面
        if (Key_Data.Key_Down_Buff[0])
        {
            Key_Data.Key_Down_Buff[0] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            Menu_Data.Menu_State = INTERFACE_3;
            Menu_Data.Menu_Flag_Buff[2] = 1;
            return;
        }
        if (0 == v_Flag)
        {
            v_Flag = 1;
            Menu_Data.vMenu_Admin_Verify_Interface();
        }
        result = sucAS608_Verify_Fingerprint(&search);
        MyAll_Data.vTime_Out_Init();    // 超时等待清0
        if (FINGERPRINT_TRUE == result) // 验证成功
        {
            Buzzer_Data.vBuzzer_Ring();
            Oled_Data.vOled_Clear();
            snprintf(arr, sizeof(arr), "验证成功,ID为%d", search.pageID);
            Oled_Data.vOled_Display_Gb2312_String(0, 2, (uint8_t *)arr);
            HAL_Delay(MESSAGE_TIME);
            v_Flag = 0;
            result = RESULT_DEFAULT_VALUE;
        }
        else if (FINGERPRINT_FALSE == result)
        {
            Buzzer_Data.vBuzzer_Ring();
            Oled_Data.vOled_Clear();
            snprintf(arr, sizeof(arr), "验证失败!!!");
            Oled_Data.vOled_Display_Gb2312_String(10, 2, (uint8_t *)arr);
            HAL_Delay(MESSAGE_TIME);
            // 回到主页面
            v_Flag = 0;
            result = RESULT_DEFAULT_VALUE;
        }
        else if (RESULT_ALWAY_LEAVE == result)
        {
            Buzzer_Data.vBuzzer_Ring();
            Oled_Data.vOled_Clear();
            snprintf(arr, sizeof(arr), "勿一直放传感器上");
            Oled_Data.vOled_Display_Gb2312_String(0, 2, (uint8_t *)arr);
            HAL_Delay(MESSAGE_TIME);
            v_Flag = 0;
            result = RESULT_DEFAULT_VALUE;
        }
    }
}

/*
 * @description: 指纹控制函数
 * @return {*}
 * @Date: 2023-04-26 18:30:45
 */
// 指纹控制函数
void vAS608_Fingerprint_Control_Function(void)
{
    uint8_t arr[20];
    SearchResult search;
    uint8_t result = RESULT_DEFAULT_VALUE;

    result = sucAS608_Verify_Fingerprint(&search);
    MyAll_Data.vTime_Out_Init();    // 超时等待清0
    if (FINGERPRINT_TRUE == result) // 验证成功
    {
        SG90_Data.vSG90_Mode_switch();
        result = RESULT_DEFAULT_VALUE;
    }
    else if (FINGERPRINT_FALSE == result)
    {
        Buzzer_Data.vBuzzer_Ring();
        Oled_Data.vOled_Clear();
        snprintf(arr, sizeof(arr), "验证失败!!!");
        Oled_Data.vOled_Display_Gb2312_String(20, 2, (uint8_t *)arr);
        HAL_Delay(MESSAGE_TIME);
        result = RESULT_DEFAULT_VALUE;
    }
}

/*
 * @description: 清空指纹库界面 【功能】
 * @return {*}
 * @Date: 2023-04-27 14:46:13
 */
// 清空指纹库界面 【功能】
void vAS608_Empty_Fingerprint_All(void)
{
    uint8_t arr[20];
    uint8_t ensure;
    Oled_Data.vOled_Clear();
    Oled_Data.vOled_Light_Row(5, SET); // 显示一行像素
    Oled_Data.vOled_Display_Gb2312_String(0, 0, (uint8_t *)"是否真的要清空?");
    Oled_Data.vOled_Display_Gb2312_String(2, 6, (uint8_t *)"取消");
    Oled_Data.vOled_Display_Gb2312_String(96, 6, (uint8_t *)"确认");

    while (1)
    {
        // 取消清空 回到功能页面
        if (Key_Data.Key_Down_Buff[0])
        {
            Key_Data.Key_Down_Buff[0] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            Menu_Data.Menu_State = INTERFACE_3;
            Menu_Data.Menu_Flag_Buff[2] = 1;
            return;
        }
        // 删除!!!
        if (Key_Data.Key_Down_Buff[2])
        {
            Key_Data.Key_Down_Buff[2] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            ensure = sucAS608_Empty_Template();
            if (AS608_RETURN_TRUE == ensure)
            {
                Oled_Data.vOled_Display_Gb2312_String(30, 0, (uint8_t *)"已清空");
                HAL_Delay(MESSAGE_TIME);
                Menu_Data.Menu_State = INTERFACE_3;
                Menu_Data.Menu_Flag_Buff[2] = 1;
                return;
            }
            else
            {
                Oled_Data.vOled_Display_Gb2312_String(10, 1, (uint8_t *)"清空失败!!!");
                HAL_Delay(MESSAGE_TIME);
                Menu_Data.Menu_State = INTERFACE_3;
                Menu_Data.Menu_Flag_Buff[2] = 1;
                return;
            }
        }
        if (INTERFACE_6 == Menu_Data.Menu_State)
        {
            return;
        }
        MyUSART1_Data.vUsart1_Rx_Data_Analytic(); // 串口通信
    }
}

SG90

  • 接线
SG90 STM32管脚
VCC(红色) 5V
GND(棕色) GND
PWM线(橙色) PB8(TIM4_CH3)

需要在MX设置PWM频率为50HZ

  • 程序编写
SG90.h
#ifndef __SG90_H
#define __SG90_H
#include "MyAll.h"

// SG90定时器的arr计数值
#define SG90_ARR_VALUE  200
// SG90--0度所需占空比
#define SG90_ANGLE_0 2.5
// SG90--45度所需占空比
#define SG90_ANGLE_45 5
// SG90--90度所需占空比
#define SG90_ANGLE_90 7.5
// SG90--180度所需占空比
#define SG90_ANGLE_180 12.5

typedef struct
{
    void (*vSG90_Set_Duty)(float);
    void (*vSG90_Control_Angle)(uint8_t);
    void (*vSG90_Mode_switch)(void);
} SG90_TypeDef;

extern SG90_TypeDef SG90_Data;

void vSG90_Set_Duty(float duty);
void vSG90_Control_Angle(uint8_t angle);
void vSG90_Mode_switch(void);
#endif
SG90.c
/*
*@Description: SG90舵机
*@Author: Yang
*@Date: 2023-04-26 14:46:43
*/
#include "SG90.h"

/*====================================变量区 BEGIN====================================*/
SG90_TypeDef SG90_Data =
{
    .vSG90_Set_Duty = &vSG90_Set_Duty,
    .vSG90_Control_Angle = &vSG90_Control_Angle,
    .vSG90_Mode_switch = &vSG90_Mode_switch
};
/*====================================变量区    END====================================*/


/*
 * @description: 设置SG90占空比
 * @param {float} duty 占空比范围(0~100)
 * @return {*}
 * @Date: 2023-04-26 15:18:03
 */
// 设置SG90占空比
void vSG90_Set_Duty(float duty)
{
    TIM4->CCR3 = SG90_ARR_VALUE * (duty / 100.0f); // 设置占空比
}

/*
 * @description: 控制舵机转动0~180度(通过斜率公式计算)
 * @param {uint8_t} angle 范围0~180
 * @return {*} 无
 * @Date: 2023-04-26 17:38:07
 */
// 控制舵机转动0~180度
void vSG90_Control_Angle(uint8_t angle)
{
    float duty_temp = 0;

    duty_temp = (0.055f * angle) + 2.55;
    SG90_Data.vSG90_Set_Duty(duty_temp);
}

/*
 * @description: 舵机开关门动作及显示函数
 * @return {*}
 * @Date: 2023-05-02 19:08:32
 */
// 舵机开关门动作及显示函数
void vSG90_Mode_switch(void)
{
    uint8_t arr[20];

    Oled_Data.vOled_Clear();
    Buzzer_Data.vBuzzer_Ring();
    SG90_Data.vSG90_Control_Angle(180); // 开锁
    Oled_Data.vOled_Light_Row(1, SET);  // 显示一行像素
    Oled_Data.vOled_Light_Row(4, SET);  // 显示一行像素
    Oled_Data.vOled_Display_Gb2312_String(35, 2, (uint8_t *)"已开锁");
    MyUSART2_Data.WIFI_printf("Open\r\n");
    for (uint8_t i = AS608_OPEN_LOCK_TIME; i > 0; i--)
    {
        MyAll_Data.vTime_Out_Init(); // 超时等待清0
        snprintf(arr, sizeof(arr), "将在%02d秒后上锁", i);
        Oled_Data.vOled_Display_Gb2312_String(0, 6, (uint8_t *)arr);
        HAL_Delay(1000);
    }
    Oled_Data.vOled_Clear();
    SG90_Data.vSG90_Control_Angle(0);  // 上锁
    Oled_Data.vOled_Light_Row(1, SET); // 显示一行像素
    Oled_Data.vOled_Light_Row(4, SET); // 显示一行像素
    Oled_Data.vOled_Display_Gb2312_String(35, 2, (uint8_t *)"已上锁");
    HAL_Delay(MESSAGE_TIME);
}

通用

  • 程序编写
MyAll.h
/*
*@Description: 总头文件
*@Author: Yang
*@Date: 2023-04-21 16:19:37
*/
#ifndef __MYALL_H
#define __MYALL_H
#include "main.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "rtc.h"
#include "iwdg.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>
#include <stdarg.h>

#include "As608.h"
#include "Led.h"
#include "Key.h"
#include "Oled.h"
#include "Buzzer.h"
#include "MyUSART1.h"
#include "MyUSART2.h"
#include "MyUSART3.h"
#include "Menu.h"
#include "Clock.h"
#include "SG90.h"
#include "ESP8266.h"


// 按键扫描(单位:ms)
#define KEY_TASK_TIME 10
// LED间隔闪烁(单位:ms)
#define LED_TASK_TIME 300
// 喂狗间隔(单位: ms)MX设置是6s
#define IWDG_TIME   5800
// 打印测试
#define MY_ALL_LOG_1    0
// 打印测试
#define MENU_LOG_1    0

typedef struct
{
    void (*vHardware_Init)(void);
    uint8_t (*vPassword_Contrast)(uint8_t *, uint8_t *);
    void (*vTime_Out_Init)(void);
} MyAll_TypeDef;

extern MyAll_TypeDef MyAll_Data;

void vHardware_Init(void);
uint8_t vPassword_Contrast(uint8_t *Old_str, uint8_t *New_str);
void vTime_Out_Init(void);
#endif
MyAll.c
/*
 *@Description: 通用
 *@Author: Yang
 *@Date: 2023-04-21 16:22:56
 */
#include "MyAll.h"
/*====================================变量区 BEGIN====================================*/
MyAll_TypeDef MyAll_Data =
{
    .vHardware_Init = &vHardware_Init,
    .vPassword_Contrast = &vPassword_Contrast,
    .vTime_Out_Init = &vTime_Out_Init
};

// RTC时间结构体
RTC_TimeTypeDef rtc_Time = {0};
// RTC日期结构体
RTC_DateTypeDef rtc_Date = {0};
/*====================================变量区    END====================================*/


/*
 * @description: 外设初始化
 * @return: 无
 * @Date: 2023-04-21 17:03:30
 */
// 外设初始化
void vHardware_Init(void)
{
    printf("KEY1:模拟按键1\r\n");
    printf("KEY2:模拟按键2\r\n");
    printf("KEY3:模拟按键3\r\n");
    printf("KEY2_L:模拟按键2长按\r\n");
    printf("ROOT:管理员身份\r\n");
    TIM3->CNT = 0;
    HAL_TIM_Base_Stop_IT(&htim3);
    HAL_UART_Receive_IT(&huart3, (uint8_t *)&MyUSART3_Data.Usart3_New_Data, 1);
    Oled_Data.vOled_Init();
    Oled_Data.vOled_Toggle_Display(OLED_SET);
    Oled_Data.vOled_Rotate_Display(OLED_SET);
    AS608_Data.ucAS608_Get_Templete_Number(&AS608_Data.AS608_Fingerprint_Number);  // 获取一次指纹数量
    Menu_Data.vMenu_Start_Interface();
    __HAL_RCC_RTC_ENABLE(); // 使能RTC
    HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_3);   // 使能PWM通道
    SG90_Data.vSG90_Control_Angle(0); //初始化舵机0度
    Clock_Data.vClock_Get_Time_Date(&rtc_Time);    //获取时间日期
    ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_RESTORE, ESP8266_RETURN_OK, ESP8266_WAIT_TIME);
}

/*
 * @description: 中断服务函数(需要去删除stm32f1xx_it.c里的)
 * @return: 无
 * @Date: 2023-04-22 00:04:33
 */
void SysTick_Handler(void)
{
    // LED计数
    static uint16_t Led_Timer_Cnt = 0;
    // 按键长按计数
    static uint8_t Key_Timer_Cnt = 0;
    // 按键按下计数
    static uint8_t Key_Down_Cnt = 0;
    // 蜂鸣器响计数
    static uint8_t Buzzer_Set_Timer_Cnt = 0;
    // 太空人刷新计数
    static uint8_t Bmp_Space_Person_Cnt = 0;
    // 时钟计数
    static uint16_t Standby_Cnt = 0;
    // 看门狗计数
    static uint16_t IWDG_Cnt = 0;

    HAL_IncTick();
    Key_Down_Cnt++;
    Led_Timer_Cnt++;
    Key_Timer_Cnt++;
    Bmp_Space_Person_Cnt++;
    Standby_Cnt++;
    IWDG_Cnt++;

    if(IWDG_TIME == IWDG_Cnt)
    {
        IWDG_Cnt = 0;
        HAL_IWDG_Refresh(&hiwdg);   // 喂狗
    }
    if (1000 == Standby_Cnt)
    {
        Standby_Cnt = 0;
        Clock_Data.vClock_Get_Time_Date(&rtc_Time); // 时间赋值
        Menu_Data.Time_Out_Standby_Count++;
        if ((TIME_OUT_MAX_TIME == Menu_Data.Time_Out_Standby_Count) && (1 == Menu_Data.Standby_Choose_Flag))
        {
            Menu_Data.Menu_Flag_Buff[5] = 1;
            Menu_Data.Menu_State = INTERFACE_6;
        }
    }
    if (130 == Bmp_Space_Person_Cnt)
    {
        Bmp_Space_Person_Cnt = 0;
        Menu_Data.Bmp_Space_Person_Flag = 1;
    }
    if (Buzzer_Data.Buzzer_Open_Flag)
    {
        Buzzer_Data.vBuzzer_Control(BUZZER_ON);
        Buzzer_Set_Timer_Cnt++;
        if (BUZZER_TIME == Buzzer_Set_Timer_Cnt)
        {
            Buzzer_Set_Timer_Cnt = 0;
            Buzzer_Data.vBuzzer_Control(BUZZER_OFF);
            Buzzer_Data.Buzzer_Open_Flag = 0;
        }
    }
    if (100 == Key_Down_Cnt)
    {
        Key_Down_Cnt = 0;
        Key_Data.Key_Down_Time++;
    }
    if (LED_TASK_TIME == Led_Timer_Cnt)
    {
        Led_Timer_Cnt = 0;
        Led_Data.vLed_Flashing();
    }
    if (KEY_TASK_TIME == Key_Timer_Cnt)
    {
        Key_Timer_Cnt = 0;
        Key_Data.vKey_Scan_Function();
        if (SET == HAL_GPIO_ReadPin(GPIOA, AS608_WAK_PIN))
        {
            if (SET == HAL_GPIO_ReadPin(GPIOA, AS608_WAK_PIN))
            {
                AS608_Data.AS608_Wak_Flag = 1;
            }
        }
    }
}

/*
 * @description: 定时器中断回调函数
 * @param {TIM_HandleTypeDef} *htim
 * @return {*}
 * @Date: 2023-04-24 02:01:50
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(&htim3 == htim)
    {
        MyUSART3_Data.Usart3_Rx_Sta |= 1 << 15;
        HAL_TIM_Base_Stop_IT(&htim3);
    }
}

/*
 * @description: 管理员密码对比
 * @param {uint8_t} Old_str 旧
 * @param {uint8_t} New_str 输入的
 * @return: 正确---PASSWORD_TRUE  错误---PASSWORD_FALSE
 * @Date: 2023-04-24 02:06:19
 */
// 管理员密码对比
uint8_t vPassword_Contrast(uint8_t *Old_str, uint8_t *New_str)
{
    uint8_t result = PASSWORD_TRUE;
    for (uint8_t i = 0; i < 3; i++)
    {
        if (Old_str[i] != New_str[i])
        {
            result = PASSWORD_FALSE;
        }
    }
#if MY_ALL_LOG_1
    printf("Password:%d\r\n", result);
#endif
    return result;
}

/*
 * @description: 超时等待时间清0
 * @return {*}
 * @Date: 2023-04-25 05:46:55
 */
// 超时等待时间清0
void vTime_Out_Init(void)
{
    Menu_Data.Time_Out_Standby_Count = 0;
}

串口

  • 程序编写
MyUSART1.h
/*
*@Description: 串口
*@Author: Yang
*@Date: 2023-04-21 19:02:50
*/
#ifndef __MYUSART1_H
#define __MYUSART1_H
#include "MyAll.h"

// USART1接收的最大长度数据
#define USART1_MAX_LEN 50

typedef struct
{
    // USART1接收数组
    uint8_t Usart1_Rx_Buff[USART1_MAX_LEN];
    // USART1接收长度
    uint8_t Usart1_Rx_Len;
    // USART1接收完成标志位
    bool Usart1_Rx_Over_Flag;
    void (*vUsart1_Rx_Data_Analytic)(void);
} MyUSART1_TypeDef;

extern MyUSART1_TypeDef MyUSART1_Data;

void vUsart1_Rx_Data_Analytic(void);
#endif
MyUSART1.c
/*
 *@Description: 串口1--9600bound  串口3--57600bound
 *@Author: Yang
 *@Date: 2023-04-21 19:02:39
 */
#include "MyUSART1.h"

/*====================================变量区 BEGIN====================================*/
MyUSART1_TypeDef MyUSART1_Data =
{
    .Usart1_Rx_Buff = {0},
    .Usart1_Rx_Len = 0,
    .Usart1_Rx_Over_Flag = 0,
    .vUsart1_Rx_Data_Analytic = &vUsart1_Rx_Data_Analytic
};

extern DMA_HandleTypeDef hdma_usart1_rx;
/*====================================变量区    END====================================*/


/*
 * @description: USART1数据解析
 * @return: 无
 * @Date: 2023-04-22 00:45:56
 */
// USART1数据解析
void vUsart1_Rx_Data_Analytic(void)
{
    if (MyUSART1_Data.Usart1_Rx_Over_Flag)
    {
        MyUSART1_Data.Usart1_Rx_Over_Flag = 0;
        if ((MyUSART1_Data.Usart1_Rx_Len > 1) && Menu_Data.Menu_Usart1_State)   // 需要打开通信
        {
            // 比较
            if(0 == strncmp(MyUSART1_Data.Usart1_Rx_Buff, "KEY1", 4))
            {
                Key_Data.Key_Down_Buff[0] = 1;
            }
            // 比较
            else if(0 == strncmp(MyUSART1_Data.Usart1_Rx_Buff, "KEY2", 4))
            {
                if(4 == MyUSART1_Data.Usart1_Rx_Len)
                {
                    Key_Data.Key_Down_Buff[1] = 1;
                }
            }
            // 比较
            else if(0 == strncmp(MyUSART1_Data.Usart1_Rx_Buff, "KEY3", 4))
            {
                Key_Data.Key_Down_Buff[2] = 1;
            }
            // 比较
            else if(0 == strncmp(MyUSART1_Data.Usart1_Rx_Buff, "KEY2_L", 6))
            {
                if(6 == MyUSART1_Data.Usart1_Rx_Len)
                {
                    Key_Data.Key_Down_Buff[4] = 1;
                }
            }
            // 比较
            else if(0 == strncmp(MyUSART1_Data.Usart1_Rx_Buff, "ROOT", 4))
            {
                if(4 == MyUSART1_Data.Usart1_Rx_Len)
                {
                    Admin_Data.Administrator_Flag = 1;
                }
            }
            MyAll_Data.vTime_Out_Init();    //超时等待清0
        }
        HAL_UART_Transmit(&huart1, (uint8_t *)MyUSART1_Data.Usart1_Rx_Buff, strlen(MyUSART1_Data.Usart1_Rx_Buff), 0xffff);
        memset(MyUSART1_Data.Usart1_Rx_Buff, 0, sizeof(MyUSART1_Data.Usart1_Rx_Buff));
        // 重新打开DMA接收
        HAL_UART_Receive_DMA(&huart1, (uint8_t *)MyUSART1_Data.Usart1_Rx_Buff, USART1_MAX_LEN);
    }
}

/*
 * @description: USART1中断函数(需要在stm32f1xx_it.c删除)
 * @return: 无
 * @Date: 2023-04-22 00:33:28
 */
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart1);
    // 触发空闲中断
    if(SET == __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
    {
        // 清除空闲中断标志位
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);
        // 停止DMA接收
        HAL_UART_DMAStop(&huart1);
        // 计算长度
        MyUSART1_Data.Usart1_Rx_Len = USART1_MAX_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
        // 接收标志位置1
        MyUSART1_Data.Usart1_Rx_Over_Flag = 1;
    }
}

/*
 * @description: 重定向printf
 * @param {int} ch
 * @param {FILE} *f
 * @return: int
 * @Date: 2023-04-22 03:01:47
 */
int fputc(int ch, FILE *f)
{
    uint32_t temp = ch;
    HAL_UART_Transmit(&huart1, (uint8_t *)&temp, 1, 1000);
    return ch;
}
MyUSART3.h
#ifndef __MYUSART3_H
#define __MYUSART3_H
#include "MyAll.h"

// USART3接收的最大长度数据
#define USART3_MAX_LEN 50

typedef struct
{
    uint16_t Usart3_Rx_Sta;
    // USART3接收数组
    uint8_t Usart3_Rx_Buff[USART3_MAX_LEN];
    // USART3接收长度
    uint8_t Usart3_Rx_Len;
    // USART3接收完成标志位
    bool Usart3_Rx_Over_Flag;
    // 接收的最新数据
    uint8_t Usart3_New_Data;
} MyUSART3_TypeDef;

extern MyUSART3_TypeDef MyUSART3_Data;

#endif
MyUSART3.c
/*
*@Description: 串口3
*@Author: Yang
*@Date: 2023-04-22 13:17:59
*/
#include "MyUSART3.h"

/*====================================变量区 BEGIN====================================*/
MyUSART3_TypeDef MyUSART3_Data =
{
    .Usart3_Rx_Sta = 0,
    .Usart3_Rx_Buff = {0},
    .Usart3_Rx_Len = 0,
    .Usart3_Rx_Over_Flag = 0,
    .Usart3_New_Data = 0
};
/*====================================变量区    END====================================*/


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(&huart3 == huart)
    {
        // 接收完的一批数据,还没有被处理,则不再接收其他数据
        if(0 == (MyUSART3_Data.Usart3_Rx_Sta & (1 << 15)))
        {
            if(MyUSART3_Data.Usart3_Rx_Sta < USART3_MAX_LEN)	//还可以接收数据
            {
                TIM3->CNT = 0;//计数器清空
                if(0 == MyUSART3_Data.Usart3_Rx_Sta) 				//使能定时器7的中断
                {
                    HAL_TIM_Base_Start_IT(&htim3);
                }
                MyUSART3_Data.Usart3_Rx_Buff[MyUSART3_Data.Usart3_Rx_Sta++] = MyUSART3_Data.Usart3_New_Data;	//记录接收到的值
            }
            else
            {
                MyUSART3_Data.Usart3_New_Data |= 1 << 15;				//强制标记接收完成
            }
        }
        HAL_UART_Receive_IT(&huart3, (uint8_t *)&MyUSART3_Data.Usart3_New_Data, 1); // 串口3接收中断
    }
}
MyUSART2.h
/*
*@Description: 串口2
*@Author: Yang
*@Date: 2023-04-29 07:33:42
*/
#ifndef __MYUSART2_H
#define __MYUSART2_H
#include "MyAll.h"

// USART2接收的最大长度数据
#define USART2_MAX_LEN 100

typedef struct
{
    // 接收app发送过来的有效命令标志位
    bool APP_Rx_Flag;
    // USART2接收数组
    uint8_t Usart2_Rx_Buff[USART2_MAX_LEN];
    // USART2接收长度
    uint8_t Usart2_Rx_Len;
    // USART2接收完成标志位
    bool Usart2_Rx_Over_Flag;
    void (*vUsart2_Rx_Data_Analytic)(void);
    void (*WIFI_printf)(char *, ...);
    void (*WIFI_TCP_Send)(char *, ...);
} MyUSART2_TypeDef;

extern MyUSART2_TypeDef MyUSART2_Data;
extern uint16_t Client_Id;

void vUsart2_Rx_Data_Analytic(void);
void WIFI_printf (char *fmt, ...);
void WIFI_TCP_Send(char *fmt, ...);
#endif
MyUSART2.c
/*
*@Description: 串口2
*@Author: Yang
*@Date: 2023-04-29 07:33:32
*/
#include "MyUSART2.h"


/*====================================变量区 BEGIN====================================*/
MyUSART2_TypeDef MyUSART2_Data =
{
    .APP_Rx_Flag = 0,
    .Usart2_Rx_Buff = {0},
    .Usart2_Rx_Len = 0,
    .Usart2_Rx_Over_Flag = 0,
    .vUsart2_Rx_Data_Analytic = &vUsart2_Rx_Data_Analytic,
    .WIFI_printf = &WIFI_printf,
    .WIFI_TCP_Send = &WIFI_TCP_Send
};
extern DMA_HandleTypeDef hdma_usart2_rx;
/*====================================变量区    END====================================*/

uint16_t Client_Id = 0;


/*
 * @description: USART2数据解析 【接收格式:0x0D0x0A+IPD,<link ID>,<len>:后面是数据内容】数据格式:0x55 0xXX 0xXX 0xXX 0xBB
 * @return: 无
 * @Date: 2023-04-22 00:45:56
 */
/*
帧头--0x55 帧尾--0xBB
-----------------------------------------------------------
| 主指令 | 副指令[1] | 副指令[2] |   说明        |
|-----------|--------------|--------------|-----------------|
| 0x01    |	 0x01	  |   0x00    |打开舵机
|------------|-------------|--------------|-----------------|
|            |	       	   |              |
|------------|-------------|--------------|-----------------|
*/
// USART2数据解析(只在待机和主页面才执行这个函数)
void vUsart2_Rx_Data_Analytic(void)
{
    if (MyUSART2_Data.Usart2_Rx_Over_Flag)
    {
        MyUSART2_Data.Usart2_Rx_Over_Flag = 0;
        if((0x0D == MyUSART2_Data.Usart2_Rx_Buff[0]) &&
                (0x0A == MyUSART2_Data.Usart2_Rx_Buff[1]) &&
                ('+' == MyUSART2_Data.Usart2_Rx_Buff[2]) &&
                ('I' == MyUSART2_Data.Usart2_Rx_Buff[3]) &&
                ('P' == MyUSART2_Data.Usart2_Rx_Buff[4]) &&
                ('D' == MyUSART2_Data.Usart2_Rx_Buff[5]))
        {
            if((0x55 == MyUSART2_Data.Usart2_Rx_Buff[11]) && (0xBB == MyUSART2_Data.Usart2_Rx_Buff[15])) // 判断帧头
            {
                Client_Id = MyUSART2_Data.Usart2_Rx_Buff[7] - '0';
                switch(MyUSART2_Data.Usart2_Rx_Buff[12])    // 判断主指令
                {
                case 0x01:
                {
                    // 控制舵机开
                    if((0x01 == MyUSART2_Data.Usart2_Rx_Buff[13]) && 0x00 == MyUSART2_Data.Usart2_Rx_Buff[14])    // 判断副1,2
                    {
                        SG90_Data.vSG90_Mode_switch();
                        Menu_Data.Menu_State = INTERFACE_1;
                        Menu_Data.Menu_Flag_Buff[0] = 0;
                        WIFI_TCP_Send("OK\r\n");
                    }
                    break;
                }
                default:
                    break;
                }
                MyUSART2_Data.APP_Rx_Flag = 1;
            }
        }
        MyAll_Data.vTime_Out_Init();    //超时等待清0
        memset(MyUSART2_Data.Usart2_Rx_Buff, 0, sizeof(MyUSART2_Data.Usart2_Rx_Buff));
    }
}

void USART2_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart2);
    // 触发空闲中断
    if (SET == __HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE))
    {
        // 清除空闲中断标志位
        __HAL_UART_CLEAR_IDLEFLAG(&huart2);
        // 停止DMA接收
        HAL_UART_DMAStop(&huart2);
        // 计算长度
        MyUSART2_Data.Usart2_Rx_Len = USART2_MAX_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
        // 接收标志位置1
        MyUSART2_Data.Usart2_Rx_Over_Flag = 1;
    }
    // 重新打开DMA接收
    HAL_UART_Receive_DMA(&huart2, (uint8_t *)MyUSART2_Data.Usart2_Rx_Buff, USART2_MAX_LEN);
}

/*
 * @description: 发送到串口2函数
 * @param {char} *fmt
 * @return {*}
 * @Date: 2023-04-29 09:02:55
 */
// 发送到串口2函数
void WIFI_printf (char *fmt, ...)
{
    char buff[USART2_MAX_LEN + 1]; //用于存放转换后的数据 [长度]
    uint16_t i = 0;
    __va_list arg_ptr;
    va_start(arg_ptr, fmt);
    vsnprintf(buff, USART2_MAX_LEN + 1, fmt, arg_ptr); //数据转换
    i = strlen(buff); //得出数据长度
    if(strlen(buff) > USART2_MAX_LEN)
    {
        i = USART2_MAX_LEN; //如果长度大于最大值,则长度等于最大值(多出部分忽略)
    }
    HAL_UART_Transmit(&huart2, (uint8_t *)buff, i, 0xffff); //串口发送函数(串口号,内容,数量,溢出时间)
    va_end(arg_ptr);
}

/*
 * @description: TCP模式下的数据发送
 * @param {char} *fmt
 * @return {*}
 * @Date: 2023-05-03 15:06:14
 */
// TCP模式下的数据发送
void WIFI_TCP_Send(char *fmt, ...)
{
    char buff[USART2_MAX_LEN + 1]; //用于存放转换后的数据 [长度]
    uint16_t i = 0;
    va_list arg_ptr;
    va_start(arg_ptr, fmt);
    vsnprintf(buff, USART2_MAX_LEN + 1, fmt, arg_ptr); //数据转换
    i = strlen(buff); //得出数据长度
    if(strlen(buff) > USART2_MAX_LEN)
    {
        i = USART2_MAX_LEN; //如果长度大于最大值,则长度等于最大值(多出部分忽略)
    }
    MyUSART2_Data.WIFI_printf("AT+CIPSEND=%d,%d\r\n", Client_Id, i); //先发送AT指令和数据数量
    HAL_Delay(150);//毫秒延时等待WIFI模块返回">",此处没做返回是不是">"的判断。稳定性要求高的项目要另加判断。
    HAL_UART_Transmit(&huart2, (uint8_t *)buff, i, 0xffff); //发送数据内容(串口号,内容,数量,溢出时间)
    va_end(arg_ptr);
}

看门狗

定时6s,然后5.8s在滴答定时器中断里进行喂狗一次

WiFi

  • 接线(看模块丝印,正负极不要接反!),这里只需要接5个脚就够了

编号 名称 STM32管脚
1 GND GND
4 RX USART2_TX
5 TX USART2_RX
6 EN 3.3V
8 VCC 3.3V
2 GPIO2 看情况
3 GPIO0 烧固件需要接地
7 RST 悬空,烧固件需要快速接地然后悬空,否则一直显示等待上电
  • 三种模式

STA(MODE1):类似一个接收模式,就是开启该模式后,你只能通过串口给他发送信息,ESP8266只负责接收,而不会产生一个热点(手机或其他设备是找不到热点的)

这种模式更加适用于需要联网,ESP8266作为一种物联网设备,需要通过Wi-Fi接入互联网,以便与其他设备进行通信、远程监测和控制等操作,可以使用ESP8266的STA模式通过互联网连接到远程服务器,以便实现设备的远程监控和控制

//tcp服务器
AT+CWMODE=1    	                	设置成sta模式	
AT+RST				                重启生效	
AT+CWMODE?			                查询WiFi模块的模式
AT+CWJAP="111","12345678"		    连接wifi名字以及密码
AT+CIPMUX=1				            设置多连接
AT+CIPSERVER=1,8899		            设置端口号
AT+CIFSR	                        查询路由器分配的ip地址
// 服务器发送给客户端数据格式是
AT+CIPSEND=ID,LEN	// ID是客户端的ID因为连接到ESP8266有很多个它不能全部都发送只能发送给指定ID,LEN是发送的数据长度    
//tcp客户端
AT+CWMODE=2      	                	设置成sta模式	
AT+RST				                	重启生效	
AT+CWMODE?			                	查询WiFi模块的模式
AT+CWJAP="111","12345678" 				连接wifi名字以及密码
AT+CIPMUX=0				                设置单连接
AT+CIPSTART="TCP","10.128.19.xxx",1121  这个需要根据手机端打开的tcp服务器的ip地址和端口号来修改
AT+CIPMODE=1                          	开启透传模式(仅单连接 客服端时支持)
AT+CIPSEND                              开始传送数据

AP(MODE2):发出去一个热点,供手机或其他设备连接,如果要实现手机连热点来控制单片机,那么该模式就要开启了

下面两种主要区别是esp8266当做客户端时可以自动发送采集的数据给手机端服务器端,适用于一直采集接收的场景;esp8266当做服务器端时不会主动发送采集的数据给手机客户端,需要手机客户端发送请求命令才能,适用于比如控制模块、设备状态查询等功能

// tcp服务器
AT+CWMODE=2      	                设置成ap模式	
AT+RST				                重启生效	
AT+CWMODE?			                查询WiFi模块的模式
AT+CWSAP="ESP8266","12345678",11,0  设置要产生的wifi名字以及密码
AT+CIPMUX=1				            设置多接入点模式
AT+CIPSERVER=1,8899		            设置端口号
AT+CIFSR	                        查询路由器分配的ip地址

设置完上面后,手机下载WiFi调试助手,选择 TCP客户端,写上WiFI模块的IP地址还有端口号即可连接,然后可以进行发送数据,在程序里串口接收需要判断发送过来的数据,格式为: 0x0D0x0A+IPD,<link ID>,<len>:后面是数据内容,其中0x0D0x0A是换行, 表示连接标识符,标识着网络连接的唯一性,一个 ESP8266 模块可以同时与多个客户端建立 TCP 或 UDP 连接,每个连接都有一个唯一的标识符,CP 和 UDP 连接的 link ID 编号从 0 开始递增,根据连接建立的顺序,越早建立的连接 link ID 越小;表示数据的长度大小

定好通讯协议,我的通讯协议是:

0x55 0xXX 0xXX 0xXX 0xBB,其中0x55表示帧头,第一个0xXX是主指令,第二个0xXX是副1指令,第三个0xXX是副2指令,0xBB是帧尾,这里我没加校验和因为手机发送的话需要计算校验和比较麻烦

// tcp客户端
AT+CWMODE=2      	                	设置成ap模式	
AT+RST				                	重启生效	
AT+CWMODE?			                	查询WiFi模块的模式
AT+CWSAP="ESP8266","12345678",11,0  	设置要产生的wifi名字以及密码
AT+CIPMUX=0				                设置单连接
AT+CIPSTART="TCP","10.128.19.xxx",1121  这个需要根据手机端打开的tcp服务器的ip地址和端口号来修改
AT+CIPMODE=1                          	开启透传模式(仅单连接 客服端时支持)
AT+CIPSEND                              开始传送数据

STA+AP(MODE3):以上两种同时实现

  • 传输方式一般选择透传

如果不采用透传模式,那么每发送一次数据都要发送一次 AT+CIPSEND=<param>的指令就显得尤为麻烦,因此模式一般设置为透传模式,退出透传模式就给指令 +++ 即可

  • 一般下ESP8266会生成一个固定的IP地址,根据不同模块地址也不一样(我的是192.168.4.1),然而如果有其他设备比如手机连接到这个ESP8266则该手机会被分配一个固定IP地址(192.168.4.x),用于跟ESP8266进行通信的

程序编写

ESP8266.h
/*
*@Description:
*@Author: Yang
*@Date: 2023-04-29 07:35:05
*/
#ifndef __ESP8266_H
#define __ESP8266_H
#include "MyAll.h"

// ESP8266返回值--成功
#define ESP8266_PASS    1
// ESP8266返回值--失败
#define ESP8266_FAIL    0
// 响应值--OK
#define ESP8266_RETURN_OK   "OK"
// 发一条AT普通指令所等待时间,需要*10才是最终所等待的时间ms
#define ESP8266_WAIT_TIME   100
// 宏参数转字符串常量
#define TO_STRING(x)    #x

/***********用户数据*************/
// 热点/路由器名称(看实际改)
#define SSID    "yang520"
// 热点/路由器密码(看实际改)
#define SSID_PASSWORD   "00000000"
// 云服务器IP地址【必须按您的实际情况修改】
# define TCP_IP "iot-06z00b2xuy7fxl9.mqtt.iothub.aliyuncs.com"
// 云服务器端口号
# define TCP_PORT   1883

/********常用指令(以下指令都是掉电不保存)********/
// 查看模块是否在线/正常
#define ESP8266_CMD_ONLINE  "AT"
// 设置工作模式为STA
#define ESP8266_CMD_MODE_STA "AT+CWMODE=1"
// 设置工作模式为AP
#define ESP8266_CMD_MODE_AP "AT+CWMODE=2"
// 设置工作模式为STA+AP
#define ESP8266_CMD_MODE_STAAP "AT+CWMODE=3"
// 恢复出厂设置(擦除所有Flash参数恢复为默认值,会导致模块重启)
#define ESP8266_CMD_RESTORE "AT+RESTORE"
// 模块重启
#define ESP8266_CMD_REBOOT  "AT+RST"
// 设置ESP8266的名称,密码,通道号,加密方式(AP模式下此命令才有效)
#define ESP8266_CMD_SETTING "AT+CWSAP=\"ESP8266_401\",\"66666666\",1,4"
// 上电是否自动连接AP(0--不自动 1--自动)
#define ESP8266_CMD_CONNECT_AP(num) "AT+CWAUTOCONN="TO_STRING(num)
// 是否开启回显(0--关闭 1--开启)
#define ESP8266_CMD_SWCH_ECHO(num)   "ATE"TO_STRING(num)
// 设置DHCP(参1:模式1/2/3 参2:0-关闭 1-开启)
#define ESP8266_CMD_DHCP    "AT+CWDHCP=1,1"
// 进入单路/多路连接模式(0--单  1--多)
#define ESP8266_CMD_ENTER_MUX(num)  "AT+CIPMUX="TO_STRING(num)
// 进入透传模式
#define ESP8266_CMD_MUX_MODE    "AT+CIPMODE=1"
// 开始传输数据(如果需要发命令需退出透传)
#define ESP8266_CMD_MUX_START   "AT+CIPSEND"
// 设置端口号(参1:1-建立服务器 0-关闭服务器 参2:端口号默认333范围是1~65535)
#define ESP8266_CMD_SET_PORT    "AT+CIPSERVER=1,8899"
// 查询路由分配的IP地址
#define ESP8266_CMD_FIND_IP "AT+CIFSR"


typedef struct
{
    // 当前ESP8266属于哪种状态(1--设为服务器端 2--设为客户端 0--无)
    uint8_t ESP8266_State;
    // 透传标志位(0--非透传 1--透传)
    bool MUX_Flag;
    // ESP连接服务器时IP的最后一位
    uint16_t Server_IP_Number;
    // ESP连接服务器时的端口
    uint16_t Server_Port_Number;
    // AP模式下端口号
    uint16_t ESP8266_AP_Port;
    uint8_t (*ucESP8266_Send_Cmd)(uint8_t *, uint8_t *, uint16_t);
    uint8_t (*ucESP8266_Check_Cmd)(uint8_t *);
    char *(*cpcESP8266_Success_Message)(uint8_t);
    uint8_t (*ucESP8266_Quit_Mux)(void);
    uint8_t (*ucESP8266_Connect_AP)(void);
    uint8_t (*ucESP8266_Connect_Server)(char *, uint16_t);
    uint8_t (*ucESP8266_Set_Server)(void);
    char *(*pcESP8266_Check_IP)(void);
    uint8_t (*ucESP8266_Set_Client)(void);
} ESP8266_TypeDef;

extern ESP8266_TypeDef ESP8266_Data;

uint8_t ucESP8266_Send_Cmd(uint8_t *cmd, uint8_t *ack, uint16_t wait_time);
uint8_t ucESP8266_Check_Cmd(uint8_t *str);
const char *cpcESP8266_Success_Message(uint8_t ensure);
uint8_t ucESP8266_Quit_Mux(void);
uint8_t ucESP8266_Connect_AP(void);
uint8_t ucESP8266_Connect_Server(char *str, uint16_t _port);
uint8_t ucESP8266_Set_Server(void);
char *pcESP8266_Check_IP(void);
uint8_t ucESP8266_Set_Client(void);
#endif
ESP8266.c
#include "ESP8266.h"

/*====================================变量区 BEGIN====================================*/
ESP8266_TypeDef ESP8266_Data =
{
    .ESP8266_State = 0,
    .MUX_Flag = 0,
    .Server_IP_Number = 0,
    .Server_Port_Number = 8000,
    .ESP8266_AP_Port = 0,
    .ucESP8266_Send_Cmd = &ucESP8266_Send_Cmd,
    .ucESP8266_Check_Cmd = &ucESP8266_Check_Cmd,
    .cpcESP8266_Success_Message = &cpcESP8266_Success_Message,
    .ucESP8266_Quit_Mux = &ucESP8266_Quit_Mux,
    .ucESP8266_Connect_AP = &ucESP8266_Connect_AP,
    .ucESP8266_Connect_Server = &ucESP8266_Connect_Server,
    .ucESP8266_Set_Server = &ucESP8266_Set_Server,
    .pcESP8266_Check_IP = &pcESP8266_Check_IP,
    .ucESP8266_Set_Client = &ucESP8266_Set_Client
};


/*====================================变量区    END====================================*/

/*
 * @description: 设置为客户端
 * @return {*} 设置成功返回0 其余返回表示失败
 * @Date: 2023-05-03 16:16:24
 */
// 设置为客户端
uint8_t ucESP8266_Set_Client(void)
{
    uint16_t ip = 0;
    uint16_t port = 8000;
    uint8_t arr[40] = {0};

    Oled_Data.vOled_Clear();
    Oled_Data.vOled_Display_Gb2312_String(0, 0, (uint8_t *)"正在设置...");
    ESP8266_Data.ucESP8266_Quit_Mux();
    if(ESP8266_FAIL == ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_MODE_AP, ESP8266_RETURN_OK, ESP8266_WAIT_TIME))
    {
        return 3;
    }
    if(ESP8266_FAIL == ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_REBOOT, ESP8266_RETURN_OK, ESP8266_WAIT_TIME))
    {
        return 6;
    }
    HAL_Delay(5000);
    if(ESP8266_FAIL == ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_ONLINE, ESP8266_RETURN_OK, ESP8266_WAIT_TIME))
    {
        return 1;
    }
    ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_SETTING, ESP8266_RETURN_OK, ESP8266_WAIT_TIME);
    if(ESP8266_FAIL == ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_ENTER_MUX(0), ESP8266_RETURN_OK, ESP8266_WAIT_TIME))
    {
        return 11;
    }
    Oled_Data.vOled_Clear();
    Oled_Data.vOled_Display_Gb2312_String(0, 0, (uint8_t *)"TCP服务端IP");
    Oled_Data.vOled_Display_Gb2312_String(0, 6, (uint8_t *)"返回");
    Oled_Data.vOled_Display_Gb2312_String(47, 6, (uint8_t *)"增/减");
    Oled_Data.vOled_Display_Gb2312_String(96, 6, (uint8_t *)"确认");
    while(1)
    {
        if (Key_Data.Key_Down_Buff[0])
        {
            Key_Data.Key_Down_Buff[0] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            return 16;
        }
        else if(Key_Data.Key_Down_Buff[1])
        {
            Key_Data.Key_Down_Buff[1] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            ip++;
            if(ip >= 250)
            {
                ip = 250;
            }
        }
        else if(Key_Data.Key_Down_Buff[4])
        {
            Key_Data.Key_Down_Buff[4] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            while(ip != 0)
            {
                ip--;
                break;
            }
        }
        else if(Key_Data.Key_Down_Buff[2])
        {
            Key_Data.Key_Down_Buff[2] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            break;
        }
        snprintf(arr, sizeof(arr), "IP: 192.168.4.%d", ip);
        Oled_Data.vOled_Display_String_5x7(0, 3, (uint8_t *)arr);
    }
    Oled_Data.vOled_Clear();
    Oled_Data.vOled_Display_Gb2312_String(0, 0, (uint8_t *)"TCP端口输入");
    Oled_Data.vOled_Display_Gb2312_String(0, 6, (uint8_t *)"返回");
    Oled_Data.vOled_Display_Gb2312_String(47, 6, (uint8_t *)"增/减");
    Oled_Data.vOled_Display_Gb2312_String(96, 6, (uint8_t *)"确认");
    while(1)
    {
        if (Key_Data.Key_Down_Buff[0])
        {
            Key_Data.Key_Down_Buff[0] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            return 16;
        }
        else if(Key_Data.Key_Down_Buff[1])
        {
            Key_Data.Key_Down_Buff[1] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            port++;
            if(port >= 9000)
            {
                port = 9000;
            }
        }
        else if(Key_Data.Key_Down_Buff[4])
        {
            Key_Data.Key_Down_Buff[4] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            while(port != 8000)
            {
                port--;
                break;
            }
        }
        else if(Key_Data.Key_Down_Buff[2])
        {
            Key_Data.Key_Down_Buff[2] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            break;
        }
        snprintf(arr, sizeof(arr), "端口: %d", port);
        Oled_Data.vOled_Display_Gb2312_String(0, 3, (uint8_t *)arr);
    }
    ESP8266_Data.Server_IP_Number = ip;
    ESP8266_Data.Server_Port_Number = port;
    memset(arr, 0, sizeof(arr));
    snprintf(arr, sizeof(arr), "192.168.4.%d", ESP8266_Data.Server_IP_Number);
    ESP8266_Data.ucESP8266_Connect_Server(arr, ESP8266_Data.Server_Port_Number);
    ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_MUX_MODE, ESP8266_RETURN_OK, ESP8266_WAIT_TIME);
    ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_MUX_START, ESP8266_RETURN_OK, ESP8266_WAIT_TIME);
    ESP8266_Data.MUX_Flag = 1;
    return 0;
}

/*
 * @description: 查找ESP8266模块的IP
 * @return {*} 返回IP地址
 * @Date: 2023-05-03 13:19:06
 */
// 查找ESP8266模块的IP
char *pcESP8266_Check_IP(void)
{
    ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_FIND_IP, "APIP", 1000);
    char *ptr = strstr(MyUSART2_Data.Usart2_Rx_Buff, "APIP"); // 查找 "APIP" 子字符串的位置
    if (ptr != NULL)
    {
        char *qtr = strchr(ptr, '\"'); // 查找第一个双引号位置
        if (qtr != NULL)
        {
            char *rtr = strchr(qtr + 1, '\"'); // 查找第二个双引号位置
            if (rtr != NULL)
            {
                char *ip = (char *) malloc(rtr - qtr); // 使用malloc()函数分配空间
                memset(ip, 0, rtr - qtr);           // 初始化缓冲区(不要用sizeof不然末尾有一个乱码字符R)
                strncpy(ip, qtr + 1, rtr - qtr - 1); // 拷贝 IP 地址到缓冲区
                return ip;
            }
        }
    }
    return NULL;
}

/*
 * @description: 设为服务器端
 * @return {*} 返回0表示设置成功 其余失败
 * @Date: 2023-05-02 16:34:54
 */
// 设为服务器端
uint8_t ucESP8266_Set_Server(void)
{
    uint8_t arr[30];
    uint16_t Port = 8000;   // 端口
    char *p = (char *)malloc(50);   // 分配存储空间的指针

    Oled_Data.vOled_Clear();
    Oled_Data.vOled_Display_Gb2312_String(0, 0, (uint8_t *)"正在设置...");
    ESP8266_Data.ucESP8266_Quit_Mux();
    if(ESP8266_FAIL == ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_MODE_AP, ESP8266_RETURN_OK, ESP8266_WAIT_TIME))
    {
        return 3;
    }
    if(ESP8266_FAIL == ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_REBOOT, ESP8266_RETURN_OK, ESP8266_WAIT_TIME))
    {
        return 6;
    }
    HAL_Delay(5000);
    if(ESP8266_FAIL == ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_ONLINE, ESP8266_RETURN_OK, ESP8266_WAIT_TIME))
    {
        return 1;
    }
    ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_SETTING, ESP8266_RETURN_OK, ESP8266_WAIT_TIME);
    if(ESP8266_FAIL == ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_ENTER_MUX(1), ESP8266_RETURN_OK, ESP8266_WAIT_TIME))
    {
        return 11;
    }
    Oled_Data.vOled_Display_Gb2312_String(0, 6, (uint8_t *)"返回");
    Oled_Data.vOled_Display_Gb2312_String(47, 6, (uint8_t *)"增/减");
    Oled_Data.vOled_Display_Gb2312_String(96, 6, (uint8_t *)"确认");
    while(1)
    {
        if (Key_Data.Key_Down_Buff[0])
        {
            Key_Data.Key_Down_Buff[0] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            return 14;
        }
        else if(Key_Data.Key_Down_Buff[1])
        {
            Key_Data.Key_Down_Buff[1] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            Port += 2;
            if(Port >= 9000)
            {
                Port = 9000;
            }
        }
        else if(Key_Data.Key_Down_Buff[4])
        {
            Key_Data.Key_Down_Buff[4] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            Port -= 2;
            if(Port <= 8000)
            {
                Port = 8000;
            }
        }
        else if(Key_Data.Key_Down_Buff[2])
        {
            Key_Data.Key_Down_Buff[2] = 0;
            Buzzer_Data.vBuzzer_Ring(); // 蜂鸣器滴一下
            ESP8266_Data.ESP8266_AP_Port = Port;
            sprintf((char *)p, "AT+CIPSERVER=1,%d", ESP8266_Data.ESP8266_AP_Port); // 发送连接AT指令
            ESP8266_Data.ucESP8266_Send_Cmd(p, ESP8266_RETURN_OK, ESP8266_WAIT_TIME);
            free(p);    // 释放分配的空间和指针
            Oled_Data.vOled_Clear();
            Oled_Data.vOled_Display_Gb2312_String(0, 0, (uint8_t *)"正在设置...");
            break;
        }
        snprintf(arr, sizeof(arr), "选择端口:%d", Port);
        Oled_Data.vOled_Display_Gb2312_String(0, 2, (uint8_t *)arr);
    }
    return 0;
}

/*
 * @description: ESP8266连接到服务器
 * @return {*}
 * @Date: 2023-04-29 15:11:21
 */
// ESP8266连接到服务器
uint8_t ucESP8266_Connect_Server(char *str, uint16_t _port)
{
    uint8_t i = 10;
    char *p = (char *)malloc(50); //分配存储空间的指针
    sprintf((char *)p, "AT+CIPSTART=\"TCP\",\"%s\",%d", str, _port);
    while(i)
    {
        if(ESP8266_PASS == ESP8266_Data.ucESP8266_Send_Cmd(p, "CONNECT", 1000))
        {
            break;
        }
        i--;
    }
    free(p);    // 释放分配的空间和指针
    if(i)
    {
        return ESP8266_PASS;
    }
    else
    {
        return ESP8266_FAIL;
    }
}

/*
 * @description: ESP8266连接AP设备(无线路由器)
 * @return {*}
 * @Date: 2023-04-29 14:49:55
 */
//ESP8266连接AP设备(无线路由器)
uint8_t ucESP8266_Connect_AP(void)
{
    uint8_t i = 10;
    char *p = (char *)malloc(50);   // 分配存储空间的指针

    sprintf((char *)p, "AT+CWJAP=\"%s\",\"%s\"", SSID, SSID_PASSWORD); // 发送连接AT指令
    while(i)   // 循环判断等待连接AP的结果
    {
        if(ESP8266_PASS == ESP8266_Data.ucESP8266_Send_Cmd(p, "WIFI GOT IP", 1000))
        {
            break;
        }
        i--;
    }
    free(p);    // 释放分配的空间和指针
    if(i)
    {
        return ESP8266_PASS;
    }
    else
    {
        return ESP8266_FAIL;
    }
}

/*
 * @description: 退出透传模式
 * @return {*}
 * @Date: 2023-04-29 14:31:09
 */
// 退出透传模式
uint8_t ucESP8266_Quit_Mux(void)
{
    uint8_t result;

    MyUSART2_Data.WIFI_printf("+++");
    HAL_Delay(1000);
    MyUSART2_Data.WIFI_printf("+++");
    HAL_Delay(1000);
    ESP8266_Data.MUX_Flag = 0;
    result = ESP8266_Data.ucESP8266_Send_Cmd(ESP8266_CMD_ONLINE, ESP8266_RETURN_OK, ESP8266_WAIT_TIME);
    return result;
}

/*
 * @description: 向ESP8266发送指令
 * @param {uint8_t} *cmd 指令字串符
 * @param {uint8_t} *ack 响应字串符
 * @param {uint16_t} wait_time 超时等待
 * @return {*} ESP8266_FAIL--失败  ESP8266_PASS--成功
 * @Date: 2023-04-29 11:42:56
 */
// 向ESP8266发送指令
uint8_t ucESP8266_Send_Cmd(uint8_t *cmd, uint8_t *ack, uint16_t wait_time)
{
    uint8_t res = ESP8266_PASS;

    MyUSART2_Data.Usart2_Rx_Over_Flag = 0;  // 接收标志位清0
    memset(MyUSART2_Data.Usart2_Rx_Buff, 0, sizeof(MyUSART2_Data.Usart2_Rx_Buff));  // 接收数组清0
    MyUSART2_Data.WIFI_printf("%s\r\n", cmd);   // 发送指令
    if (ack && wait_time)   // 响应值不为NULL且超时时间不为0则进入if
    {
        while (--wait_time)
        {
            HAL_Delay(10);  // 延时
            if (MyUSART2_Data.Usart2_Rx_Over_Flag)  // 判断接收标志位是否置1
            {
                if (ESP8266_Data.ucESP8266_Check_Cmd(ack))  // 检查ESP8266回传的响应值是否跟ack一致
                {
                    res = ESP8266_PASS;
                }
                else
                {
                    res = ESP8266_FAIL;
                }
                MyUSART2_Data.Usart2_Rx_Over_Flag = 0;
                return res;
            }
        }
        if (0 == wait_time)
        {
            res = ESP8266_FAIL;
        }
    }
    return res;
}

/*
 * @description: ESP8266检查指令
 * @param {uint8_t} *str    需要查找的字符串
 * @return {*} ESP8266_FAIL--失败  ESP8266_PASS--成功
 * @Date: 2023-04-29 11:55:25
 */
// ESP8266检查指令
uint8_t ucESP8266_Check_Cmd(uint8_t *str)
{
    uint8_t res = ESP8266_FAIL;

    if (MyUSART2_Data.Usart2_Rx_Over_Flag)
    {
        if (strstr((const char *)MyUSART2_Data.Usart2_Rx_Buff, (const char *)str))  // 查找str在串口接收数组中第一次出现的位置,找到返回对应位置指针,找不到返回NULL
        {
            res = ESP8266_PASS;
        }
    }
    return res;
}

/*
 * @description: 各种失败信息
 * @param {uint8_t} ensure 错误信息编号
 * @return {*} 失败信息字符串
 * @Date: 2023-04-29 13:53:04
 */
// 各种失败信息
const char *cpcESP8266_Success_Message(uint8_t ensure)
{
    const char *p;

    Oled_Data.vOled_Clear();
    switch(ensure)
    {
    case 1:
    {
        p = "模块不在线";
        break;
    }
    case 2:
    {
        p = "设置为STA失败";
        break;
    }
    case 3:
    {
        p = "设置为AP失败";
        break;
    }
    case 4:
    {
        p = "设置为STP失败";
        break;
    }
    case 5:
    {
        p = "恢复出厂设置失败";
        break;
    }
    case 6:
    {
        p = "模块重启失败";
        break;
    }
    case 7:
    {
        p = "设置模块参数失败";
        break;
    }
    case 8:
    {
        p = "设置自动连接失败";
        break;
    }
    case 9:
    {
        p = "设置回显失败";
        break;
    }
    case 10:
    {
        p = "设置DHCP失败";
        break;
    }
    case 11:
    {
        p = "进入单多连接失败";
        break;
    }
    case 12:
    {
        p = "进入透传模式失败";
        break;
    }
    case 13:
    {
        p = "开始传输数据失败";
        break;
    }
    case 14:
    {
        p = "设置端口号失败";
        break;
    }
    case 15:
    {
        p = "查询IP地址失败";
        break;
    }
    case 16:
    {
        p = "连接服务器失败";
        break;
    }
    case 17:
    {
        p = "退出透传失败";
        break;
    }
    case 18:
    {
        p = "连接路由失败";
        break;
    }
    case 19:
    {
        p = "开启SNTP失败";
        break;
    }
    case 20:
    {
        p = "配置信息失败";
        break;
    }
    case 21:
    {
        p = "连接URL失败";
        break;
    }
    case 22:
    {
        p = "订阅失败";
        break;
    }
    case 23:
    {
        p = "发布失败";
        break;
    }
    default:
        break;
    }
    return p;
}

固件烧录

以下步骤是在 ESP8266-01S 型号下进行的

WiFi烧固件除了上面所说的引脚要接外还有 GPIO0 引脚需要接地(接地为下载状态;悬空为工作状态); RST引脚需要悬空,当提示等待上电同步时接地并迅速悬空,复位模块

  • 烧录MQTT固件步骤
  1. 使用USB转串口按上面要求接线好
  2. 前往安信可官网找到 固件号:1471 下载
  3. 打开烧录软件 flash_download_tool_v3.8.5.exe ,点击 ESP8266 DownloadTool

  • 这是一般参数

  1. 下载完成测试

将ESP8266-01s的引脚IO0拉高(不接GND/置空)

ESP8266-01s模块重新上电,打开串口助手

在串口助手发送AT+GMR指令,输出版本信息则说明下载成功

串口助手连接MQTT

下面是使用串口来进行MQTT的连接测试

  • 创建产品那些就不写了普通步骤,创建完后点击添加自定义功能设置一下参数,添加一个功能属性,设置完成之后点击发布上线。

  • 开始在串口助手那输入命令进行连接(前提是ESP8266已经烧了MQTT固件)
  1. 复位
AT+RST
  1. 恢复出厂设置(这个不会把你烧的固件也恢复到出厂的放心…)
AT+RESTORE
  1. 配置为STA模式
AT+CWMODE=1
  1. 开启SNTP服务器,8时域,SNTP服务器为阿里云域名(这样ESP8266 就可以通过 ntp1.aliyun.com 获取准确的网络时间,在进行短信发送、数据上传等操作时能够更加精确地记录时间戳)

CIPSNTPCFG:设置 SNTP(简单网络时间协议)配置参数

1:SNTP 服务器数量,这里设置为 1

8:时区偏移量,这里设置为 GMT+8(东八区)

"ntp1.aliyun.com":SNTP 服务器地址,这里设置为阿里云提供的ntp1.aliyun.com,如果使用别的物联网平台需要看它的这个地址是哪个

AT+CIPSNTPCFG=1,8,"ntp1.aliyun.com"
  1. 连接手机热点或者其他路由
AT+CWJAP="yang520","00000000"
  1. 配置 MQTT 用户属性

username和password可以在 设备设备信息MQTT连接参数 那查看

MQTTUSERCFG:设置 MQTT 用户名和密码

0:MQTT 客户端编号

1: MQTT 服务器的 SECUREMODE,这里设置为 1,即使用 SSL 安全连接

"NULL": MQTT 服务器的 CA 证书地址,这里设置为 NULL,表示不需要 CA 证书验证

"username":MQTT 服务器的用户名

"passwd":MQTT 服务器的密码

0:MQTT 连接的会话类型,这里设置为 0,即非持久化会话

0:MQTT 连接的 KEEPALIVE 参数,这里设置为 0,表示不启用 KEEPALIVE

"":MQTT 连接的遗嘱主题,这里设置为 "",表示不需要遗嘱功能

AT+MQTTUSERCFG=0,1,"NULL","username","passwd",0,0,""

# 示例
AT+MQTTUSERCFG=0,1,"NULL","ESP8266&ikjy4ej7BOp","67078699f9d2d3d42be9fe3e141b1cd752c46d226cf4d5c88275c1a260815136",0,0,""
  1. 配置 MQTT 客户端 ID

clientId可以在 设备设备信息MQTT连接参数 那查看

注意:第二个参数中有逗号的需在逗号前添加 \

MQTTCLIENTID:设置 MQTT 客户端 ID

0:MQTT 客户端编号

"clientId":MQTT 客户

AT+MQTTCLIENTID=0,"clientId"

# 示例
AT+MQTTCLIENTID=0,"ikjy4ej7BOp.ESP8266|securemode=2\,signmethod=hmacsha256\,timestamp=1682842914176|"
  1. 连接/查询 MQTT Broker

mqttHostUrl和port可以在 设备设备信息MQTT连接参数 那查看

MQTTCONN:建立 MQTT 连接

0:MQTT 客户端编号

"mqttHostUrl":MQTT 服务器的 URL 地址

port:MQTT 服务器的端口号,通常为 1883

1:MQTT 连接的 CLEAN SESSION 参数,这里设置为 1,即每次连接时清除会话信息

AT+MQTTCONN=0,"mqttHostUrl",port,1

# 示例
AT+MQTTCONN=0,"iot-06z00b2xuy7fxl9.mqtt.iothub.aliyuncs.com",1883,1
  1. 订阅指令

${deviceName} 需要替换你的设备名称

MQTTSUB:订阅 MQTT 主题

0:MQTT 客户端编号

"topic":要订阅的 MQTT 主题

1:MQTT 订阅的 QoS 等级,可以为 0、1 或 2

AT+MQTTSUB=0,"topic",1

# 示例
AT+MQTTSUB=0,"/ikjy4ej7BOp/ESP8266/user/get",1
  1. 发布指令

${deviceName} 需要替换你的设备名称

MQTTPUB:发布 MQTT 消息

0:MQTT 客户端编号

"topic":要发布的 MQTT 主题

"Json格式内容":要发布的 MQTT 消息内容

1:MQTT 消息的 QoS 等级,可以为 0、1 或 2

0:MQTT 消息的 RETAIN 标志,可以为 0 或 1

AT+MQTTPUB=0,"topic","Json格式内容",1,0

# 示例
AT+MQTTPUB=0,"/ikjy4ej7BOp/ESP8266/user/update","Json格式内容",1,0

其他指令

断开MQTT连接

AT+MQTTCLEAN=0

其他知识

在 MQTT 协议中,发布消息(Publish)指的是客户端向服务器发送消息,而订阅消息(Subscribe)则是客户端接收服务器发送给它的消息

对于 ESP8266 设备而言,如果您需要将设备采集的数据上传至 MQTT 服务器,就需要通过发布消息的方式将数据发送给服务器。而当服务器需要向 ESP8266 发送特定的控制命令或其他数据时,则需要通过订阅 ESP8266 订阅的主题(Topic),向其发送消息

注意/遇到的问题

USART1串口接收需要注意卡死问题,__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE) 不需要开启,两条就够了,还有发送字符串含中文会乱码的情况,可以去Keil勾选C库那个选项,但是还是有警告不用管因为是V6编译,虽然速度快但是对中文还是不太支持,使用V5的话就没那个警告,然后回到vscode重新打开窗口编译即可,也可以重定向printf来发送

OLED显示中文需要把中文字符串所在的 .c 文件设置为 GB2312 编码,不然显示乱码

使用RTC时,我丢滴答定时器里一直闪烁的LED忽然不会闪了一直亮,我以为是代码哪里不小心注释了,结果看了半天没看到,然后想想可能是RTC的问题,果然在MX配置里PC13引脚是RTC相关引脚,通常用于检测备份电源的状态或检测外部事件,如果想控制PC13的话需要在MX里选择输出为无,这样LED正常闪烁了

setting.json
{
    "files.associations": {
        "my.h": "c",
        "tim.h": "c",
        "as608.h": "c",
        "myall.h": "c",
        "buzzer.h": "c",
        "key.h": "c",
        "led.h": "c",
        "oled.h": "c",
        "usart.h": "c",
        "string": "c",
        "string_view": "c",
        "array": "c",
        "myusart3.h": "c",
        "sstream": "c",
        "menu.h": "c",
        "bmp.h": "c",
        "functional": "c",
        "algorithm": "c",
        "clock.h": "c"
    },
    "C_Cpp.intelliSenseEngineFallback": "Disabled", //需要添加的
    "C_Cpp.intelliSenseEngine": "Tag Parser",  //  需要添加的
}
launch.json
{
    "name": "Your Debug Configuration",
    "type": "cortex-debug",
    "request": "launch",
    "servertype": "keil",
    "preLaunchTask": "build",
    "cwd": "${workspaceFolder}",
    "program": "${workspaceFolder}/MDK-ARM/Fingerprint_Access_Control/Fingerprint_Access_Control.axf",
    "ignore": ["WarningNumber1", "WarningNumber2","-Winvalid-source-encoding"]
}

喂狗需要注意不要太接近重装载值,因为RC温漂大可能会导致复位

如果WIFI设置模式或者发送AT都返回错误,可以试试把WiFi模块的3.3v重新拔了再插回去(不要只按单片机复位,一定要断电WiFI模块)

供电最好使用USB口供电,如果只使用下载程序那供电会出现电压不足,导致OLED有乱码,舵机不会动等问题

待解决

指纹的背光问题还是找不到解决方法,一直亮着太闪眼了,理想是按下才亮,不按下指纹处于灭状态

参考过的文章:https://www.arduino.cn/forum.php?mod=viewthread&tid=105344&page=1#pid622605

结语

这次搞这个也学到了很多,知道了怎么去烧写wifi固件,只不过遗憾的是搞MQTT连接阿里云那里一直不太行,不知道哪里有问题,这个问题暂时先这样了只能,等后面准备再搞一个物联网的小项目做做