当前位置: 首页 > article >正文

ESP32项目 --- 智能门锁(WiFi 蓝牙 OTA)

1 项目简介

1.1 项目概述

本项目是实现一款智能门锁中的智能控制部分, 可以应用在家庭, 办公室等任何使用门锁的场所.

本项目实现了以下主要功能:

(1)通过按键配置密码

(2)通过按键输入密码开锁

(3)录入指纹

(4)通过录入的指纹开锁

(5)通过蓝牙配置密码

(6)语音播报模块

(7)通过蓝牙输入密码开锁

(8)通过WIFI实现OTA在线升级

1.2 功能概述

智能门锁使用的主控芯片为ESP32-C3, 其他功能模块包括:

(1)电容触摸按键. 一共有提供12个电容触摸按键, 分别为数字0-9, M和#

(2)单总线全彩LED. 分别为每个电容触摸按键提供了一个单总线全彩LED, 当按键被按下时可以进行灯光提示.

(3)指纹模块. 指纹模块可以采集指纹

(4)蓝牙模块. 由esp32-c3芯片提供. 用来接收用户手机蓝牙传来的密码, 匹配成功之后,执行开锁动作.

(5)语音播报模块. 当用户执行了一些操作之后, 给用户进行相应的语音播报提示.

(6)WIFI模块. 由esp32-c3芯片提供. 用来进行OTA下载最新固件,实现在线固件升级电机. 使用esp32的GPIO来控制电机的转动,达到开锁的目的

开发流程

ESP32开发环境搭建

  1. 下载ESP-IDF离线安装包

下载地址: https://dl.espressif.cn/dl/esp-idf/?idf=4.4

在这里插入图片描述

  1. 安装

注意我们使用的是ESP32-C3,所以安装时要选择对型号

在这里插入图片描述

  1. 在VSCode安装扩展ESP-IDF

在这里插入图片描述

  1. 使用ESP插件配置ESP环境
  • 在vscode中, 点击菜单查看->命令面板

在这里插入图片描述

  • 输入: Configure esp, 然后选择第一项

在这里插入图片描述

  • 点击EXPRESS进入配置界面

在这里插入图片描述

  • 安装

在这里插入图片描述

  • 注意:安装报错

如果在安装的过程中报错"…python.exe -m pip" is not valid. (ERROR_INVALID_PIP), 则去esp-idf的安装目录:Espressif\tools, 把idf-python目录删除, 然后再点Install重新安装即可.

创建项目

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 配置Flash大小

在这里插入图片描述

  • 配置时钟滴答定时器

在这里插入图片描述

ESP32的开发案例

gpio
  • Espressif\frameworks\esp-idf-v5.3.1\examples\peripherals\gpio\generic_gpio

官方给的初始化写法:

	//句柄
    gpio_config_t io_conf = {};
    //中断
    io_conf.intr_type = GPIO_INTR_DISABLE;
    //模式
    io_conf.mode = GPIO_MODE_OUTPUT;
    //引脚屏蔽位
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    //下拉
    io_conf.pull_down_en = 0;
    //上拉
    io_conf.pull_up_en = 0;

    //让配置信息生效
    gpio_config(&io_conf);

gpio相关函数:

esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level);
int gpio_get_level(gpio_num_t gpio_num)
i2c
  • 参考示例:

https://gitee.com/EspressifSystems/esp-idf/blob/release/v4.4/examples/peripherals/i2c/i2c_simple/main/i2c_simple_main.c


#define I2C_MASTER_SCL_IO           CONFIG_I2C_MASTER_SCL      //时钟引脚
#define I2C_MASTER_SDA_IO           CONFIG_I2C_MASTER_SDA      //数据引脚
#define I2C_MASTER_NUM              0                          //IIC端口号,本芯片是有一个
#define I2C_MASTER_FREQ_HZ          400000                     //IIC频率
#define I2C_MASTER_TX_BUF_DISABLE   0                          //master无需提供
#define I2C_MASTER_RX_BUF_DISABLE   0                          //master无需提供
#define I2C_MASTER_TIMEOUT_MS       1000


  • 相关函数

i2c_master_write_read_device()  //根据这个设备读写

static esp_err_t i2c_master_init(void)
{
    int i2c_master_port = I2C_MASTER_NUM;

    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_IO, //可读可写
        .scl_io_num = I2C_MASTER_SCL_IO, //可读可写
        .sda_pullup_en = GPIO_PULLUP_ENABLE, //上拉
        .scl_pullup_en = GPIO_PULLUP_ENABLE, //上拉
        .master.clk_speed = I2C_MASTER_FREQ_HZ, //传输速率
    };

    i2c_param_config(i2c_master_port, &conf);

    return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}

rmt
  • examples\peripherals\rmt\led_strip_simple_encoder
//1. 宏定义
//2. 时序 ( 0时序,1时序,重置时序)
//3. 编码器回调
//4. 初始化(发送通道 简单编码器)
跟着案例走
...
nvs
  • 参考案例: examples\storage\nvs_rw_value

一些常用的函数:

	static nvs_handle_t my_handle; //声明NVS操作句柄
    esp_err_t err = nvs_flash_init();
    err = nvs_open("pwd", NVS_READWRITE, &my_handle); //打开NVS命名空间
    nvs_get_u8(my_handle, char* key, uint8_t* value);
    nvs_set_u8(my_handle, char* key, value);
	nvs_find_key(my_handle,char* key,NULL);
    nvs_erase_key(my_handle,char* key);

例:

  • Dri_NVS.c
#include "Dri_NVS.h"

//声明NVS操作句柄
static nvs_handle_t my_handle;


/**
 * @brief 初始化NVS Flash
 * 
 */
void Dri_NVS_Init(void)
{
    //1. 初始化NVS_Flash
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    //2.打开NVS命名空间
    err = nvs_open("pwd", NVS_READWRITE, &my_handle);
}

/**
 * @brief 读取u8数据
 * 
 * @param key 
 * @param value 
 * @return esp_err_t 
 */
esp_err_t Dri_NVS_ReadU8(char *key, uint8_t *value)
{
    return nvs_get_u8(my_handle, key, value);
}

/**
 * @brief 写入u8数据
 * 
 * @param key 
 * @param value 
 * @return esp_err_t 
 */
esp_err_t Dri_NVS_WriteU8(char *key, uint8_t value)
{   
    //判断 key 是否存在
    if (nvs_find_key(my_handle, key,NULL) == ESP_OK)
    {
        //如果存在就不用存储 直接返回
        return ESP_FAIL;
    }
    else
    {
        //存储数据
        return nvs_set_u8(my_handle, key, value);
    }
}

/**
 * @brief 删除数据
 * 
 * @param key 
 * @return esp_err_t 
 */
esp_err_t Dri_NVS_DeletePwd(char *key)
{
    //判断 key 是否存在
    if (nvs_find_key(my_handle, key,NULL) == ESP_OK)
    {
        //存在再执行删除操作
        return nvs_erase_key(my_handle, key);
    }
    else
    {
        //不存在则不用删除 直接返回
        return ESP_FAIL;
    }
}

/**
 * @brief 判断验证密码是否存在
 * 
 * @param key 
 * @return esp_err_t 
 */
esp_err_t Dri_NVS_IsPwdExists(char *key)
{
    return nvs_find_key(my_handle, key,NULL);
}

uart

Espressif\frameworks\esp-idf-v5.3.1\examples\peripherals\uart\uart_async_rxtxtasks

void init(void)
{
    const uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };
    // We won't use a buffer for sending data.
    uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
    uart_param_config(UART_NUM_1, &uart_config);
    uart_set_pin(UART_NUM_1, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}
...
uart_write_bytes(UART_NUM_1, data, len);
...
uart_read_bytes(UART_NUM_1, data, RX_BUF_SIZE, 1000 / portTICK_PERIOD_MS);
...

2 硬件选型

2.1 主控芯片

  • ESP32-C3

(1)ESP-RISC-V CPU 是基于 RISC-V ISA 的 32 位内核

(2)ESP32-C3芯片有22个物理通用输入输出管脚(GPIOPin)

(3)支持 2.4 GHz Wi-Fi 和 Bluetooth 5 (LE)

(4)通信模块: ESP32 有2组串口,1个IIC,3个SPI控制器:SPI0, SPI1和GP-SPI2

(5)存储器:

  • 4MB 的内部Flash(ESP32-C3FN4), 用于存储较大的固件和应用程序代码
  • 384KB内部ROM, 用于存储引导程序和固件的一小部分
  • 400KB内部SRAM, 用于运行时的高速数据存储和缓存

(6) 时钟频率高达160MHz

(7) ADC,两个12位逐次逼近型模拟数字转换器(SARADC):SARADC1和SARADC2,共支持六个通道的模拟信号检测

(8) RMT(红外收发器)是一个红外发送和接收控制器,支持多种红外协议。RMT模块可以实现将模块内置RAM中的脉冲编码转换为信号输出,或将模块的输入信号转换为脉冲编码存入RAM中。

  • CPU 内核架构包含中断控制器 (INTC)、调试模块 (DM) 和用于访问存储器和外设的系统总线 (SYS BUS) 接口。

在这里插入图片描述

2.2 按键芯片

  • SC12B 2036

一根总线 通过IIC与CPU连接,内部寄存器是八位,使用两个寄存器中的12位才能表示12个按键

IIC参考示例:

https://gitee.com/EspressifSystems/esp-idf/blob/release/v4.4/examples/peripherals/i2c/i2c_simple/main/i2c_simple_main.c

2.3 单总线全彩LED

  • WS2812

24位全彩灯 一个Pin一个Pout

2.4 语音模块

  • WTN6170

串口时序 先把数据线拉低 4~20ms 后,推荐 10ms,发送 8 位数据,先发送低位,再发送高位,使用高电平和低电
平比例来表示每个数据位的值。
保持高低电平3:1 表示 1
保持高低电平1:3 表示 0

在这里插入图片描述

2.5 指纹采集

  • HLK-FPM383F

2.6 直流电机驱动

  • BDR6120S

直流有刷

3 功能实现

  • 软件架构

在这里插入图片描述

按键输入密码开锁

通用层:

  • common_config.h
#include "esp_task.h"
#include "sys/unistd.h"

typedef enum
{
    Com_OK,
    Com_ERROR,
    Com_TIMEOUT,
    Com_OTHER,
} Com_Status;

#define delay_us(x) usleep(x)
#define delay_ms(x) vTaskDelay(x / portTICK_PERIOD_MS)
语音模块

根据数据手册的时序图以及定制的指令语音对应表进行操作

在这里插入图片描述

  • Inf_WTN6170.c
...
/**
 * @brief 发送语音指令
 * 
 * @param cmd 
 */
void Inf_WTN6170_SendCmd(uint8_t cmd)
{
    //1. 拉低并延时10ms
    WTN6170_SDA_L;
    delay_ms(10);
    //2. 发送数据位
    for (uint8_t i = 0; i < 8; i++)
    {
        if (cmd & 0x01)
        {
            WTN6170_SDA_H;
            delay_us(600);
            WTN6170_SDA_L;
            delay_us(200);
        }
        else
        {
            WTN6170_SDA_H;
            delay_us(200);
            WTN6170_SDA_L;
            delay_us(600);
        }
        cmd >>= 1;
        
    }
    
    //3. 最后拉高
    WTN6170_SDA_H;
    delay_ms(5);
}
触控键盘

与主控通过IIC通信.

  • 读流程

在这里插入图片描述

  • 寄存器地址

按键信息寄存器 Output0 (地址 08H) Output1 (地址 09H)
CH[11:0] 分别对应 CIN[11:0]的按键情况。 无按键时为0, 有按键时为1。

在这里插入图片描述

  • 设备地址

在这里插入图片描述

数据手册给的案例是软件模拟IIC,太过麻烦.但最新版本的工具包把简单案例删除了,所以找到了之前版本的简单案例如下.

  • 参考示例:

https://gitee.com/EspressifSystems/esp-idf/blob/release/v4.4/examples/peripherals/i2c/i2c_simple/main/i2c_simple_main.c

  • IIC接线图

在这里插入图片描述

  • Inf_SC12B.h
...
typedef enum
{
    KEY_0,
    KEY_1,
    KEY_2,
    KEY_3,
    KEY_4,
    KEY_5,
    KEY_6,
    KEY_7,
    KEY_8,
    KEY_9,
    KEY_SHARP,
    KEY_M,
    KEY_NO
} Touch_Key;

//数据引脚
#define I2C_MASTER_SDA_IO GPIO_NUM_2
#define I2C_MASTER_SCL_IO GPIO_NUM_1
#define I2C_MASTER_INTR GPIO_NUM_0

#define SC12B_I2C_ADDR 0x40

#define I2C_MASTER_FREQ_HZ 100000
  • Inf_SC12B.c
#include "Inf/Inf_SC12B.h"

/**
 * @brief 读取寄存器函数
 * 
 * @param reg 
 * @return uint8_t 
 */
uint8_t Inf_SC12B_ReadReg(uint8_t reg)
{
    uint8_t data = 0;
    i2c_master_write_read_device(I2C_NUM_0,
                                 SC12B_I2C_ADDR,
                                 &reg,
                                 1,
                                 &data,
                                 1,
                                 2000
                                
    );
    return data;
}

uint8_t isTouch = 0;
/**
 * @brief 按键中断回调函数
 * 
 * @param arg 
 */
void SC12B_Handler(void *arg)
{
    isTouch = 1;
}

/**
 * @brief 初始化ESP32-C3 的I2C模块
 * 
 */
void Inf_SC12B_Init(void)
{
    //1. 设置i2c参数
    i2c_config_t config = {
        .mode = I2C_MODE_MASTER,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .scl_pullup_en = GPIO_PULLUP_ENABLE, //IIC必须配置上拉
        .sda_pullup_en = GPIO_PULLUP_ENABLE, //IIC必须配置上拉
        .master.clk_speed = I2C_MASTER_FREQ_HZ,
    };

//2. 使配置生效
    i2c_param_config(I2C_NUM_0, &config);
//3. 开启iic模块
    i2c_driver_install(I2C_NUM_0, 
                            config.mode,
                            0,0,//主模式下不用分配接收和发送缓冲区
                            0 //中断优先级
                            );
	/*4. 中断引脚配置*/
    //4.1 引脚工作配置信息
    gpio_config_t io_config = {
    .intr_type = GPIO_INTR_POSEDGE, //上升沿触发
    .mode = GPIO_MODE_INPUT, //输入
    .pull_down_en = GPIO_PULLDOWN_ENABLE, //下拉
    .pull_up_en = GPIO_PULLUP_DISABLE,
    .pin_bit_mask = 1 << I2C_MASTER_INTR,
    };  

    //4.2 让配置项生效
    gpio_config(&io_config);

    //4.3 安装ISR服务
    gpio_install_isr_service(0);

    //4.4 将引脚与回调函数绑定
    gpio_isr_handler_add(I2C_MASTER_INTR,SC12B_Handler,(void *)I2C_MASTER_INTR);
    
}



/**
 * @description: 获取按下的按键值
 * 1.读取08和09寄存器中的数据
 * 2.拼接读取到的两部分数据
 * 3.判断按下的是哪一个按键
 * 4.输出
 * @return {*}
 */
Touch_Key Inf_SC12B_ReadKey(void)
{
    // 1.读取08和09寄存器中的数据
    uint8_t data1 = Inf_SC12B_ReadReg(0x08);
    uint8_t data2 = Inf_SC12B_ReadReg(0x09);

    // 2.拼接读取到的两部分数据
    uint16_t key = (data1 << 8) | data2;

    Touch_Key touchKey = KEY_NO;

    // 3.判断按下的是哪一个按键
    switch (key)
    {
    case 0x8000:
        touchKey = KEY_0;
        break;
    case 0x4000:
        touchKey = KEY_1;
        break;
    case 0x2000:
        touchKey = KEY_2;
        break;
    case 0x1000:
        touchKey = KEY_3;
        break;
    case 0x0100:
        touchKey = KEY_4;
        break;
    case 0x0400:
        touchKey = KEY_5;
        break;
    case 0x0200:
        touchKey = KEY_6;
        break;
    case 0x0800:
        touchKey = KEY_7;
        break;
    case 0x0040:
        touchKey = KEY_8;
        break;
    case 0x0020:
        touchKey = KEY_9;
        break;
    case 0x0010:
        touchKey = KEY_SHARP;
        break;
    case 0x0080:
        touchKey = KEY_M;
        break;
    default:
        break;
    }

    // 4.输出
    return touchKey;
}

Touch_Key Inf_SC2B_KeyClick(void)
{
    Touch_Key key = KEY_NO;
    if(isTouch) /* 如果有按键按下 */
    {
        key = Inf_SC12B_ReadKey(); /* 读取按下的按键 */
        isTouch = 0;
    }
    return key;
}

全色LED灯

全色LED需要的时序信号可用使用红外线外设精确生成.代码可用从官方案例直接移植修改: examples\peripherals\rmt\led_strip_simple_encoder

  • Inf_WS2812.h
#include "Inf_WS2812.h"

static uint8_t led_strip_pixels[EXAMPLE_LED_NUMBERS * 3];
    
static rmt_channel_handle_t led_chan = NULL;
    
static rmt_encoder_handle_t simple_encoder = NULL;

/* 定义几种常见颜色 */
uint8_t black[3]  = {0, 0, 0};
uint8_t white[3]  = {255, 255, 255};
uint8_t red[3]    = {0, 255, 0};
uint8_t green[3]  = {255, 0, 0};
uint8_t blue[3]   = {0, 0, 255};
uint8_t cyan[3]   = {255, 0, 255}; /* 青色 */
uint8_t purple[3] = {0, 255, 255}; /* 紫色 */

//0时序
static const rmt_symbol_word_t ws2812_zero = {
    .level0 = 1,
    .duration0 = 0.3 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T0H=0.3us
    .level1 = 0,
    .duration1 = 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T0L=0.9us
};

//1时序
static const rmt_symbol_word_t ws2812_one = {
    .level0 = 1,
    .duration0 = 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T1H=0.9us
    .level1 = 0,
    .duration1 = 0.3 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T1L=0.3us
};

//reset defaults to 50uS
static const rmt_symbol_word_t ws2812_reset = {
    .level0 = 1,
    .duration0 = RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2,
    .level1 = 0,
    .duration1 = RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2,
};

//编码器回调
static size_t encoder_callback(const void *data, size_t data_size,
                               size_t symbols_written, size_t symbols_free,
                               rmt_symbol_word_t *symbols, bool *done, void *arg)
{
    // We need a minimum of 8 symbol spaces to encode a byte. We only
    // need one to encode a reset, but it's simpler to simply demand that
    // there are 8 symbol spaces free to write anything.
    if (symbols_free < 8) {
        return 0;
    }

    // We can calculate where in the data we are from the symbol pos.
    // Alternatively, we could use some counter referenced by the arg
    // parameter to keep track of this.
    size_t data_pos = symbols_written / 8;
    uint8_t *data_bytes = (uint8_t*)data;
    if (data_pos < data_size) {
        // Encode a byte
        size_t symbol_pos = 0;
        for (int bitmask = 0x80; bitmask != 0; bitmask >>= 1) {
            if (data_bytes[data_pos]&bitmask) {
                symbols[symbol_pos++] = ws2812_one;
            } else {
                symbols[symbol_pos++] = ws2812_zero;
            }
        }
        // We're done; we should have written 8 symbols.
        return symbol_pos;
    } else {
        //All bytes already are encoded.
        //Encode the reset, and we're done.
        symbols[0] = ws2812_reset;
        *done = 1; //Indicate end of the transaction.
        return 1; //we only wrote one symbol
    }
}

void Inf_WS2812_Init(void)
{
    //1. 构建发送通道函数
    rmt_tx_channel_config_t tx_chan_config = {
        .clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
        .gpio_num = RMT_LED_STRIP_GPIO_NUM,
        .mem_block_symbols = 64, // increase the block size can make the LED less flickering
        .resolution_hz = RMT_LED_STRIP_RESOLUTION_HZ,
        .trans_queue_depth = 4, // set the number of transactions that can be pending in the background
    };
    //2. 创建发送通道
    ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &led_chan));

    //3. 简单编码器配置信息
    const rmt_simple_encoder_config_t simple_encoder_cfg = {
        .callback = encoder_callback
        //Note we don't set min_chunk_size here as the default of 64 is good enough.
    };

    //4. 创建简单编码器
    ESP_ERROR_CHECK(rmt_new_simple_encoder(&simple_encoder_cfg, &simple_encoder));
    //5. 启动发送通道
    ESP_ERROR_CHECK(rmt_enable(led_chan));
}

void Inf_WS2812_LightLed(void)
{
    rmt_transmit_config_t tx_config = {
        .loop_count = 0, // no transfer loop
    };
        // Flush RGB values to LEDs
        rmt_transmit(led_chan, simple_encoder, led_strip_pixels, sizeof(led_strip_pixels), &tx_config);
        rmt_tx_wait_all_done(led_chan, 0xffff);
}

/**
 * @brief 所有灯亮同一个颜色
 * 
 * @param color 
 */
void Inf_WS2812_LightAllLeds(uint8_t color[])
{
    for (uint8_t i = 0; i < EXAMPLE_LED_NUMBERS; i++)
    {
        memcpy(&led_strip_pixels[i * 3], color, 3);
    }

    //亮灯
    Inf_WS2812_LightLed();
    
}

/**
 * @brief 指定灯亮一个颜色
 * 
 * @param index 指定灯索引
 * @param color 指定灯颜色
 */
void Inf_WS2812_LightKeyLed(uint8_t index,uint8_t color[])
{
    //1. 先关闭所有灯
    Inf_WS2812_LightAllLeds(black);

    //2. 修改指定灯位置的颜色
    memcpy(&led_strip_pixels[index * 3], color, 3);

    //3. 刷新灯
    Inf_WS2812_LightLed();
}

App层,按键逻辑

在这里插入图片描述

  • App_IO.h
...

#define PWD_CT "pwd_ct"

extern TaskHandle_t FingerScanHandler;
extern TaskHandle_t otaHandler;
extern uint8_t isHasFinger;

/* 输入状态 */
typedef enum
{
    FREE = 0, /* 空闲状态 */
    INPUT,    /* 输入阶段 */
    DONE      /* 输入完成 */
} Input_Status;

/* 密码操作状态 */
typedef enum
{
    ADD = 0,    /* 添加密码 */
    DELETE,    /* 删除密码 */
    CHECK      /* 校验密码 */
} Pwd_Op_Status;
...
  • App_IO.c

没有按键逻辑处理:

/**
 * @description: 按键扫描
 *
 *    密码输入和设定 状态机:  共分为3个状态
 *      0:自由状态:       默认状态. 在此状态下, 如果检测到有任何按键, 则进入 1:密码输入阶段
 *
 *      1:密码输入阶段
 *                      保存密码
 *      2:输入完成阶段
 *                      对输入密码根据协议进行各种处理
 *
 * @return {*}
 */
Input_Status inputStatus = FREE;
Pwd_Op_Status pwdOpStatus = CHECK;
uint8_t password[100] = {0};
uint8_t pwdLen = 0;
void App_IO_KeyScan(void)
{
    // 定义没有按键时间
    static uint16_t noKeyTime = 0;

    // 读取按键
    Touch_Key key = Inf_SC12B_ReadKey();

    if (key == KEY_NO)
    {
        noKeyTime++;
        if (noKeyTime >= 100) // 如果超过5s种没有按键按下则进入空闲状态
        {
            inputStatus = FREE;
            Inf_WS2812_LightAllLeds(black);
            noKeyTime = 100; // 防止溢出
            // 清理前置所有输入
            pwdLen = 0;
            memset(password, 0, sizeof(password));
        }
        return;
    }
    else
    {
        // 一旦按下按键,重新开始计时
        noKeyTime = 0;
        printf("Key = %d\r\n", key);

        switch (inputStatus)
        {
        case FREE:
            Inf_WS2812_LightAllLeds(white);
            inputStatus = INPUT;
            break;

        case INPUT:
            // 无论是那种按键,统一逻辑处理:亮按键灯,响水滴声
            Inf_WS2812_LightAllLeds(black);
            delay_ms(10);
            Inf_WS2812_LightKeyLed((uint8_t)key, purple);
            sayWaterDrop();
            delay_ms(500);

            // 根据具体按键,做不同逻辑业务处理
            if (key == KEY_M)
            {
                printf("按下M键,非法输入,清除前置所有数据\r\n");
                inputStatus = FREE;
                Inf_WS2812_LightAllLeds(black);
                delay_ms(50);
                sayIllegalOperation();
                // 清理前置所有输入
                pwdLen = 0;
                memset(password, 0, sizeof(password));
            }
            else if (key == KEY_SHARP)
            {
                // #按下,根据前置输入进行逻辑处理
                inputStatus = DONE;

                // 调用逻辑处理函数
                App_IO_InputHandler();

                // 恢复空闲状态
                inputStatus = INPUT;
                // 清理前置所有输入
                pwdLen = 0;
                memset(password, 0, sizeof(password));
            }
            else
            {
                // 数值键被按下,保持到临时存储
                password[pwdLen++] = key + 48; // 将数字转为字符 1 ==> '1'
            }

            break;
        default:
            break;
        }
    }
}

按 # 逻辑处理 根据数字个数处理:

/*键盘输入协议:
1. 所有输入都是以 # 结束
2. 输入M位非法输入, 以前所有输入作废
3. 协议规则
01# 新增密码
02# 删除密码

            10#  新增指纹
            
            11#  删除指纹
            
            21#  OTA更新
            
            ...
  1. 数字超过2位的认为是在输入密码开门

*/

void App_IO_InputHandler(void)
{
    // 如果输入的数字 < 2 ,则为非法操作
    if (pwdLen < 2)
    {
        printf("输入长度小于2位,非法操作\r\n");
        sayIllegalOperation();
    }
    else if (pwdLen == 2)
    {
        // 输入的为操作指令
        if (password[0] == '0' && password[1] == '1') // 添加密码指令
        {
            delay_ms(1000); // 首尾加点延迟让第一次闪烁消失
            // isAdd = 1;
            pwdOpStatus = ADD;
            sayAddUser();
            delay_ms(2000);
            sayPassword();
            delay_ms(50);
            delay_ms(50); // 首尾加点延迟让第一次闪烁消失
        }
        else if (password[0] == '0' && password[1] == '2') // 删除密码指令
        {
            // isDel = 1;
            pwdOpStatus = DELETE;
            sayDelUser();
            delay_ms(2000);
            sayPassword();
            delay_ms(50);
        }

        else if (password[0] == '1' && password[1] == '1') // 添加指纹指令
        {
            // 通知指纹扫描业务,录入指纹
            xTaskNotify(FingerScanHandler,(uint32_t)'1',eSetValueWithOverwrite);
        }
        else if (password[0] == '1' && password[1] == '2') // 删除指纹指令
        {
            // 通知指纹扫描业务,删除指纹
            xTaskNotify(FingerScanHandler,(uint32_t)'2',eSetValueWithOverwrite);
        }
        else if (password[0] == '1' && password[1] == '3') // 删除指纹库指令
        {
            // 通知指纹扫描业务,删除所有指纹
            xTaskNotify(FingerScanHandler,(uint32_t)'3',eSetValueWithOverwrite);
        }
        else if (password[0] == '2' && password[1] == '1') // OTA升级指令
        {
            // 通知OTA升级业务
            xTaskNotify(otaHandler,(uint32_t)'4',eSetValueWithOverwrite);
        }
        else
        {
            printf("输入指令不存在\r\n");
            sayIllegalOperation();
        }
    }
    else
    {
        if (pwdLen < 5 || pwdLen > 10)
        {
            printf("密码长度不规范\r\n");
            sayIllegalOperation();
            delay_ms(50);
        }
        else
        {

            switch (pwdOpStatus)
            {
            case ADD:
                App_IO_AddPwd(password);
                pwdOpStatus = CHECK;
                break;
            case DELETE:
                App_IO_DelPwd(password);
                pwdOpStatus = CHECK;
                break;
            case CHECK:
                App_IO_CheckPwd(password);
                break;

            default:
                break;
            }
        }
    }
}

/**
 * @brief 添加密码
 *
 */
void App_IO_AddPwd(uint8_t pwd[])
{
    // 限定密码存储上限为100
    uint8_t pwdCount = 0;
    // 读取Flash中存储的密码个数
    Dri_NVS_ReadU8(PWD_CT, &pwdCount);

    if (pwdCount >= 100)
    {
        printf("密码数量已达上限\r\n");
        sayPasswordAddFail();
    }
    else
    {
        if (Dri_NVS_IsPwdExists((char *)pwd) == ESP_OK)
        {
            sayPasswordAddFail();
            return;
        }

        // 存储密码
        esp_err_t err = Dri_NVS_WriteU8((char *)pwd, 0);
        if (err == ESP_OK)
        {
            sayPasswordAddSucc();
            delay_ms(2000);
            // 将个数+1 并将密码存储进Flash
            pwdCount++;
            Dri_NVS_WriteU8(PWD_CT, pwdCount);

            printf("密码个数:%d\r\n", pwdCount);
        }
        else
        {
            sayPasswordAddFail();
            delay_ms(2000);
        }
    }
}
/**
 * @brief 删除密码
 *
 */
void App_IO_DelPwd(uint8_t pwd[])
{
    esp_err_t err = Dri_NVS_DeletePwd((char *)pwd);
    if (err == ESP_OK)
    {
        sayDelSucc();
        // 将密码个数-1
        uint8_t pwdCt = 0;
        Dri_NVS_ReadU8(PWD_CT, &pwdCt);
        Dri_NVS_WriteU8(PWD_CT, pwdCt - 1);
    }
    else
    {
        sayDelFail();
    }
}
/**
 * @brief 校验开锁
 *
 */
void App_IO_CheckPwd(uint8_t pwd[])
{
    esp_err_t err = Dri_NVS_IsPwdExists((char *)pwd);
    if (err == ESP_OK)
    {
        // 验证成功,执行开锁
        sayPasswordVerifySucc();
        delay_ms(2000);
        Inf_BDR6120_OpenLock();
        sayDoorOpen();
        delay_ms(50);
    }
    else
    {
        // 验证失败,重试
        sayPasswordVerifyFail();
        delay_ms(2000);
        sayRetry();
    }
}
指纹模块

HLK-FPM583F接口支持UART,UART默认波特率为57600。

本次使用的芯片特征:

a) UART 缺省波特率为 57.6Kbps,数据格式:8 位数据位,1 位停止位(用户手册写的两位,但实测两位不行),无校验位;
b) UART 波特率可以通过指令进行设置,范围从 9600 至 115200;
c) 如果主控是 MCU(3.3V),则直接与 UART_TD 和 UART_RD 连接;如果主控是 PC,则需要挂接
RS232 电平转换设备。

  • Inf_FPM383.c
/**
 * @brief 初始化,用到了uart
 *
 */
void Inf_FPM383_Init(void)
{
    // 1. 参数列表
    const uart_config_t uart_config = {
        .baud_rate = 57600,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };
    // 2.安装uart服务
    uart_driver_install(UART_NUM_1, 1024 * 2, 0, 0, NULL, 0);
    // 3.让配置信息生效
    uart_param_config(UART_NUM_1, &uart_config);
    // 4. 绑定引脚
    uart_set_pin(UART_NUM_1, FPM_TX_PIN, FPM_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

    // 5. 处理中断
    // 5.1 中断引脚相关配置
    gpio_config_t io_config = {
        .intr_type = GPIO_INTR_POSEDGE,
        .mode = GPIO_MODE_INPUT,
        .pull_down_en = GPIO_PULLDOWN_ENABLE,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pin_bit_mask = (1ULL << FPM_INTR_PIN),
    };
    // 5.2 配置信息生效
    gpio_config(&io_config);

    // 5.3 采用默认配置
    gpio_install_isr_service(0);

    // 5.4 添加中断的回调函数
    gpio_isr_handler_add(FPM_INTR_PIN, Inf_FPM383_Intr_Handler, (void *)FPM_INTR_PIN);

    // 5.5 控制中断开启与否
    //  gpio_intr_enable(FPM_INTR_PIN);
    gpio_intr_disable(FPM_INTR_PIN);

    // 5.6 芯片休眠 a. 低功耗 b. 在进入休眠模式后会将Touch引脚拉低(中断引脚拉低)
    Inf_FPM383_Sleep();
}

/**
 * @brief 中断回调函数
 *
 */
static void Inf_FPM383_Intr_Handler(void *)
{
    esp_rom_printf("123...\r\n");
    isHasFinger = 1;
    gpio_intr_disable(FPM_INTR_PIN);
}

/**
 * @brief ESP32发送数据到FPM指纹模块
 *
 * @param data
 * @param len
 * @return Com_Status
 */
Com_Status Inf_FPM383_WriteCmd(uint8_t *data, uint8_t len)
{
    int txBytes = uart_write_bytes(UART_NUM_1, data, len);
    return txBytes == len ? Com_OK : Com_ERROR;
}

/**
 * @brief 计算校验和,同时设置指令
 *
 * @param cmd
 * @param len
 */
void Inf_FPM383_AddCheckSum(uint8_t *cmd, uint8_t len)
{
    // 校验和是从包标识至校验和之间所有字节之和,包含包标识不包含校验和
    uint16_t checkSum = 0;
    for (uint8_t i = 6; i < len - 2; i++)
    {
        checkSum += cmd[i];
    }
    // 将计算完的校验和写入指令集
    cmd[len - 2] = checkSum >> 8;
    cmd[len - 1] = checkSum;
}
...

一站式注册指纹:

在这里插入图片描述

辅助说明:
ID 号:高字节在前,低字节在后。
参数:最低位为 bit0。

  1. bit0:采图背光灯控制位,0-LED 长亮,1-LED 获取图像成功后灭;— 没用
  2. bit1:采图预处理控制位,0-关闭预处理,1-打开预处理;
  3. bit2:注册过程中,是否要求模组在关键步骤,返回当前状态,0-要求返回,1-不
    要求返回;
  4. bit3:是否允许覆盖 ID 号,0-不允许,1-允许;
  5. bit4:允许指纹重复注册控制位,0-允许,1-不允许;
  6. bit5:注册时,多次指纹采集过程中,是否要求手指离开才能进入下一次指纹图
    像采集, 0-要求离开;1-不要求离开;
  7. bit6~bit15:预留。
/**
 * @brief 一站式注册指纹
 *
 * @param id
 * @return Com_Status
 */
Com_Status Inf_FPM383_AutoEnroll(uint16_t id)
{
    // 1. 一站式注册指纹指令
    uint8_t cmd[17] = {
        0xEF, 0x01,             // 包头
        0xFF, 0xFF, 0xFF, 0xFF, // 设备地址
        0x01,                   // 包标识
        0x00, 0x08,             // 包长度
        0x31,                   // 指令码
        '\0', '\0',             // ID号
        0x02,                   // 录入次数 2次
        0x00, 0x3B,             // 参数
        '\0', '\0'              // 校验和
    };

    // 2. 补充ID
    cmd[10] = id >> 8;
    cmd[11] = id;

    // 3. 添加校验和
    Inf_FPM383_AddCheckSum(cmd, 17);

    // 4.bug,需要取消4次自动注册
    Inf_FPM383_CancelAutoAction();
    Inf_FPM383_CancelAutoAction();
    Inf_FPM383_CancelAutoAction();
    Inf_FPM383_CancelAutoAction();

    // 5. 发送指令
    Inf_FPM383_WriteCmd(cmd, 17);

    while (1)
    {
        // 提取关键阶段的返回值结果
        Inf_FPM383_ReadData(14, 2000);
        // 只要中间关键阶段任何一次返回的不是00就直接退出
        if (receData[9] != 0x00)
        {
            return Com_ERROR;
        }
        // 返回的确认码为00,同时返回的参数1的结果为0x06,说明注册成功
        else if (receData[10] == 0x06)
        {
            return Com_OK;
        }
    }
    return Com_TIMEOUT;
}

在删除指纹是需要索引id,但是官方提供的那个获取索引并不好用,所以这里我们可以使用验证时使用的获取id指令

/**
 * @brief 搜索指定的指纹Id号
 *
 * @return uint16_t
 */
int16_t Inf_FPM383_SearchFingerPrint(void)
{
    // 1. 验证指纹指令
    uint8_t cmd[17] = {
        0xEF, 0x01,             // 包头
        0xFF, 0xFF, 0xFF, 0xFF, // 设备地址
        0x01,                   // 包标识
        0x00, 0x08,             // 包长度
        0x32,                   // 指令码
        0x03,                   // 分数等级
        0xFF, 0xFF,             // ID号,如果为FFFF,则表示与所有指纹进行对比,反之只与指定ID号指纹进行对比
        0x00, 0x06,             // 参数
        '\0', '\0'              // 校验和
    };

    // 2. 添加校验和
    Inf_FPM383_AddCheckSum(cmd, 17);

    // 3. 发送指令
    Inf_FPM383_WriteCmd(cmd, 17);

    // 4. 获取最后一次返回值结果
    Inf_FPM383_ReadData(17, 3000);
    if (receData[9] == 0x00)
    {
        // 获取存储在指纹库中的ID号
        uint16_t id = (receData[11] << 8) | receData[12];
        return id;
    }
    else
    {
        return -1;
    }
}

App应用层

  • App_IO.c
/**
 * @brief 指纹扫描任务调用的函数
 * 1.录入指纹(由按键任务通知)
 * 2.删除指纹(由按键任务通知)
 * 3.验证指纹
 */
void App_IO_FingerScan(void)
{
    uint32_t action = 0;
    xTaskNotifyWait(0xFFFFFFFF,0xFFFFFFFF,&action,0);
    if (action != 0)
    {
        //关闭中断
        gpio_intr_disable(FPM_INTR_PIN);
        //注册指纹
        if (action == '1')
        {
            sayAddUserFingerprint();
            delay_ms(2000);
            sayPlaceFinger();
            delay_ms(2000);
            //先获得最小的可用ID
            uint16_t id = Inf_FPM383_GetMindId();
            esp_rom_printf("ADD id = %d\r\n",id);
            //一站式注册指纹
            Com_Status comstatus = Inf_FPM383_AutoEnroll(id);
            if (comstatus == Com_OK)    
            {
                sayFingerprintAddSucc();
            }
            else
            {
                sayFingerprintAddFail();
            }
            //进入休眠
            Inf_FPM383_Sleep();
            //在注册以及删除指纹后芯片会出现问题,所以重启芯片
            esp_restart();
            
        }
        else if (action == '2')
        {
            //删除指纹
            sayDelUserFingerprint();
            delay_ms(2000);
            sayPlaceFinger();
            delay_ms(4000);
            //获取按下手指,存在指纹库中的id
            int16_t id = Inf_FPM383_SearchFingerPrint();
            esp_rom_printf("DEL id = %d\r\n",id);
            if (id == -1)
            {
                sayDelFail();
            }
            else
            {
                //执行删除指纹命令
                Com_Status comstatus = Inf_FPM383_DeleteFingerPrint(id);
                if (comstatus == Com_OK)    
                {
                    sayDelSucc();
                }
                else
                {
                    sayDelFail();
                }
            }
            //进入休眠
            Inf_FPM383_Sleep();
            esp_restart();
        }
        else if (action == '3')
        {
            //删除指纹
            Inf_FPM383_DeleteAllFingerPrint();
            sayDelUserFingerprint();
            delay_ms(4000);
            
            //进入休眠
            Inf_FPM383_Sleep();
            esp_restart();
        }
    }
    else
    {
        // 验证指纹
        if (isHasFinger)
        {
            //清除标志位
            isHasFinger = 0;
            //开始验证
            Com_Status comstatus = Inf_FPM383_CheckFingerPrint();
            if (comstatus == Com_OK)    
            {
               //验证成功
               sayFingerprintVerifySucc();
               delay_ms(2000);
               Inf_BDR6120_OpenLock();
               sayDoorOpen();
            }
            else
            {
                //验证失败
                sayFingerprintVerifyFail();
            }
            //进入休眠
            Inf_FPM383_Sleep();
            
        }
        
    }
}
蓝牙模块

Espressif\frameworks\esp-idf-v5.3.1\examples\bluetooth\bluedroid\ble\gatt_security_server

打开蓝牙:

在这里插入图片描述

打开4.2,关闭5.0

在这里插入图片描述

在这里插入图片描述

移植的时候修改下引用的头文件 蓝牙名称 以及主函数名

然后拿到数据后到App层里再做处理,此处定义弱实现函数,:

  • Dri_BT.c
/* 定义esp32收到手机数据时的回调弱函数函数 */
void __attribute__((weak)) App_Communication_RecvDataCb(uint8_t *data, uint16_t dataLen)
{
}
...
case ESP_GATTS_WRITE_EVT:
            ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_WRITE_EVT, write value:");
            esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len);
            // printf("接收到手机发过来的消息:%s\r\n",param->write.value);     

            App_Communication_RecvDataCb(param->write.value,param->write.len);
OTA模块
  • 简介:

OTA 升级机制可以让设备在固件正常运行时根据接收数据(如通过 Wi-Fi、蓝牙或以太网)进行自我更新。

要运行 OTA 机制,需配置设备的分区表,该分区表至少包括两个OTA 应用程序分区(即 ota_0 和 ota_1)和一个 OTA 数据分区。

OTA 功能启动后,向当前未用于启动的 OTA 应用分区写入新的应用固件镜像。镜像验证后,OTA 数据分区更新,指定在下一次启动时使用该镜像。

  • 创建分区表

在这里插入图片描述

  • 修改配置

在这里插入图片描述

  • partitions.csv
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap

nvs,      data, nvs,     ,        0x4000,
otadata,  data, ota,     ,        0x2000,
phy_init, data, phy,     ,        0x1000,
# Original App Section
ota_0,    app,  ota_0,   ,        1800K,
# New App Section
ota_1,    app,  ota_1,   ,        1800K,
  • WiFi移植

    我们是通过wifi进行ota升级, 移植官方示例: examples\wifi\getting_started\station

先创建一个配置文件Kconfig.projbuild方便修改wifi账户和密码.

然后移植官方驱动,修改初始化.

  • 启动Http服务

这里本机模拟服务端,在一个空文件夹启动,使用PowerShell在这里打开:

python.exe -m http.server 8080

然后把.bin文件放入其中即可联网访问下载,也就可以使用OTA在线升级

  • OTA移植

移植官方案例: examples\system\ota\simple_ota_example

  • App_Communication.c
static void get_sha256_of_partitions(void)
{
    uint8_t         sha_256[HASH_LEN] = {0};
    esp_partition_t partition;

    // get sha256 digest for bootloader
    partition.address = ESP_BOOTLOADER_OFFSET;
    partition.size    = ESP_PARTITION_TABLE_OFFSET;
    partition.type    = ESP_PARTITION_TYPE_APP;
    esp_partition_get_sha256(&partition, sha_256);

    // get sha256 digest for running partition
    esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256);
}

#define TAG "ota"
/// 处理一系列的HTTP事件
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
    switch(evt->event_id)
    {
        case HTTP_EVENT_ERROR:
            ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
            break;
        case HTTP_EVENT_HEADER_SENT:
            ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
            break;
        case HTTP_EVENT_ON_HEADER:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
            break;
        case HTTP_EVENT_ON_DATA:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
            break;
        case HTTP_EVENT_ON_FINISH:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
            break;
        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
            break;
        case HTTP_EVENT_REDIRECT:
            ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");
            break;
    }
    return ESP_OK;
}

/**
 * @description: 下载ota用的二进制文件
 * @return {*}
 */
static void App_Communication_OTADownloadBin(void)
{
    // esp_err_t err = nvs_flash_init();
    // if(err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
    // {
    //     nvs_flash_erase();
    //     err = nvs_flash_init();
    // }

    /* 1. 获取分区信息 */
    get_sha256_of_partitions();

    /* 2. 初始化网络 */
    esp_netif_init();

    /* 3. 创建和初始化默认事件循环 */
    esp_event_loop_create_default();

    esp_http_client_config_t config = {
        .url               = "http://172.20.10.4:8080/esp-hello-world.bin",
        .crt_bundle_attach = esp_crt_bundle_attach,
        .event_handler     = NULL,
        .keep_alive_enable = true,
    };

    esp_https_ota_config_t ota_config = {
        .http_config = &config,
    };

    esp_https_ota(&ota_config);
}

void App_Communication_OTA(void)
{
    /* 1. 连接wifi */
    Dri_Wifi_Init();

    /* 2. ota升级   使用python启动个本地http-server 命令
          C:\esp\tools\idf-python\3.11.2\python -m http.server 8080
    */
    printf("ota开始升级\r\n");
    App_Communication_OTADownloadBin();
    printf("ota完成升级\r\n");

    /* 3. 关闭wifi */
    esp_wifi_stop();

    /* 4. 重启esp32 */
    esp_restart();
}


/**
 * @description: 蓝牙模块初始化
 * @return {*}
 */
void App_Communication_Init(void)
{
    Dri_BT_Init();
}

/**
 * @description: 蓝牙模块中弱函数的回调实现
 * @param {uint8_t} *data
 * @param {uint16_t} dataLen
 * @return {*}
 */
void App_Communication_RecvDataCb(uint8_t *data, uint16_t dataLen)
{
    printf("接收到手机传输过来的数据:%s\r\n", data);

    /*
    蓝牙发送数据格式:     功能
            1:           开锁
            2:密码       设置密码
            3:密码       删除密码
    */
    /* 1. 数据长度 < 2, 直接返回, 没有任何操作 */
    if (dataLen < 2)
    {
        sayIllegalOperation();
        return;
    }
    /*
        客户端连接上蓝牙之后, 会发送锁的 序列号 +open 来开锁
            锁的序列号一般在锁出厂的时候就已经固定了,而且是唯一的
            我们可以使用 esp32的mac地址作为序列号
     */
    uint8_t pwd[100] = {0};
    switch (data[0])
    {
    case '1': // 1+666666
        memcpy(pwd, &data[2], dataLen - 2);
        printf("密码为:%s\r\n", pwd);
        App_IO_CheckPwd(pwd);
        break;
    case '2': // 2+55555
        memcpy(pwd, &data[2], dataLen - 2);
        printf("密码为:%s\r\n", pwd);
        App_IO_AddPwd(pwd);
        break;
    case '3': // 3+55555
        memcpy(pwd, &data[2], dataLen - 2);
        printf("密码为:%s\r\n", pwd);
        App_IO_DelPwd(pwd);
        break;
    default:
        break;
    }
}

main函数

  • main.c
...
int app_main(void)
{
    // 1. 初始化
    App_IO_Init();
    App_Communication_Init();
    Inf_FPM383_ReadId();
    Inf_FPM383_Sleep();

    xTaskCreate(Key_Scan_Task, "Key_Scan", 2048, NULL, 5, &KeyScanHandler);
    xTaskCreate(Finger_Scan_Task, "Finger_Scan", 2048, NULL, 5, &FingerScanHandler);
    xTaskCreate(OTA_Task, "Ota_Scan", 8192, NULL, 5, &otaHandler);

    return 0;
}

/**
 * @brief 按键扫描任务
 *
 */
void Key_Scan_Task(void *)
{
    TickType_t tickType = xTaskGetTickCount();

    while (1)
    {
        App_IO_KeyScan();

        vTaskDelayUntil(&tickType, 50);
    }
}

/**
 * @brief 手指检测任务
 *
 */
void Finger_Scan_Task(void *)
{
    delay_ms(500);

    TickType_t tickType = xTaskGetTickCount();

    while (1)
    {
        App_IO_FingerScan();

        vTaskDelayUntil(&tickType, 50);
    }
}

void OTA_Task(void *)
{
    uint32_t action = 0;
    while (1)
    {
    xTaskNotifyWait(0xFFFFFFFF,0xFFFFFFFF,&action,portMAX_DELAY);
    if (action == '4')
    {
        //执行OTA固件升级
        App_Communication_OTA();
    }
    
    }
}

http://www.kler.cn/a/421409.html

相关文章:

  • 【工具变量】上市公司企业所在地城市等级直辖市、副省级城市、省会城市 计划单列市(2005-2022年)
  • QObject 类是 rviz::_AdditionalTopicSubscriber 的一个不可访问的基类 报错解决
  • 考研英语翻译与大小作文
  • Java基础之GUI:探索图形化界面编程的魅力
  • <数据集>路面坑洼识别数据集<目标检测>
  • 深入探索 HarmonyOS 的 Navigation 组件:灵活的页面管理与动态导航
  • 《Vue零基础入门教程》第十七课:侦听器
  • SQLite:DDL(数据定义语言)的基本用法
  • echarts的双X轴,父级居中的相关配置
  • 如何参加华为欧拉考试?
  • 安装战网的时候提示“缺失libcef.dll”怎么办?libcef.dll文件丢失是什么原因?教你六大解决方法
  • Cad c#二次开发 常见错误
  • 小程序入门学习(四)之全局配置
  • EdDSA (Edwards-curve Digital Signature Algorithm)算法详解及python实现
  • 论文导读 I RAFT:使语言模型适应特定领域的RAG
  • C++小玩具1
  • 学习嵩山版《Java 开发手册》:编程规约 - 命名风格(P15 ~ P16)
  • Python HttpServer 的一个bug问题
  • The First项目报告:以太坊再质押赛道新星Swell Network
  • 2024年12月chrome131版本http自动跳转https解决
  • 【Unity基础】使用InputSystem实现物体跳跃
  • Zotero安装使用在线翻译Ubuntu
  • 数字运动_光影射箭(-->合作)
  • Vue前端页面内嵌套本项目iframe窗口的通信传输方式
  • 【深度学习|特征增强融合模块】MAF (Multi-scale Attention Fusion)一种多尺度的注意力融合模块
  • 原型模式的理解和实践