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_Flashesp_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. 拉低并延时10msWTN6170_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 bytesize_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 LEDsrmt_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[])
{// 限定密码存储上限为100uint8_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 并将密码存储进FlashpwdCount++;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();// 将密码个数-1uint8_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. 补充IDcmd[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);//先获得最小的可用IDuint16_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);//获取按下手指,存在指纹库中的idint16_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 overlapnvs,      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 bootloaderpartition.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 partitionesp_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+666666memcpy(pwd, &data[2], dataLen - 2);printf("密码为:%s\r\n", pwd);App_IO_CheckPwd(pwd);break;case '2': // 2+55555memcpy(pwd, &data[2], dataLen - 2);printf("密码为:%s\r\n", pwd);App_IO_AddPwd(pwd);break;case '3': // 3+55555memcpy(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.mzph.cn/pingmian/62852.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

洛谷题单-入门2-分支结构-python-下

找出出现的最早的最大值 count 0 list_number [] while True:list_number.append(list(map(int, input().split())))count 1if count 7:breaklist2_number_total []for i1,i2 in list_number:list2_number_total.append(i1i2)target max(list2_number_total)index 0 if…

框架模块说明 #05 权限管理_03

背景 权限设计可以分为两个主要方面&#xff1a;操作权限和数据权限。前两篇文章已经详细介绍了操作权限的设计与实现&#xff0c;以及如何将其与菜单关联起来的具体方法。本篇将聚焦于数据权限&#xff0c;为您深入讲解相关的设计与实现方式。 全局开关 Value("${syst…

Linux网络编程之---多线程实现并发服务器

下面我们来使用tcp集合多线程实现并发服务器 一.服务端 #include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <pthread.h>typedef struct sockinfo {char ip[16];unsigne…

Linux C/C++编程之静态库

【图书推荐】《Linux C与C一线开发实践&#xff08;第2版&#xff09;》_linux c与c一线开发实践pdf-CSDN博客《Linux C与C一线开发实践&#xff08;第2版&#xff09;&#xff08;Linux技术丛书&#xff09;》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com…

网际协议(IP)与其三大配套协议(ARP、ICMP、IGMP)

网际协议&#xff08;Internet Protocol&#xff0c;IP&#xff09;&#xff0c;又称互联网协议。是OSI中的网络层通信协议&#xff0c;用于跨网络边界分组交换。它的路由功能实现了互联互通&#xff0c;并从本质上建立了互联网。网际协议IP是 TCP/IP 体系中两个最主要的协议之…

uniapp实现加密Token并在每次请求前动态更新(vue、微信小程序、原生js也通用!)

导语&#xff1a;在Web开发中&#xff0c;Token作为一种身份验证的机制&#xff0c;被广泛应用于前后端交互过程中。本文将为大家介绍如何在每次请求前动态设置加密的Token&#xff0c;并在请求一次后使Token值加1&#xff08;或其他动态改变的逻辑&#xff09;&#xff0c;从而…

IDL学习笔记(二)IDL处理卫星数据

IDL处理卫星数据 HDF文件数据集属性通用属性 常用HDF4操作函数常用的HDF5操作函数读取HDF文件的一般步骤 HDF4文件读取-----数据信息查询HDF4文件读取示例-----目标数据TIFF输出 HDF文件 数据集属性 数据集名称&#xff0c;是“&#xff1a;”前的一部分&#xff0c;不是long_…

论文阅读——量子退火Experimental signature of programmable quantum annealing

摘要&#xff1a;量子退火是一种借助量子绝热演化解决复杂优化问题的通用策略。分析和数值证据均表明&#xff0c;在理想化的封闭系统条件下&#xff0c;量子退火可以胜过基于经典热化的算法&#xff08;例如模拟退火&#xff09;。当前设计的量子退火装置的退相干时间比绝热演…

TCP/IP协议簇自学笔记

摘抄于大学期间记录在QQ空间的一篇自学笔记&#xff0c;当前清理空间&#xff0c;本来想直接删除掉的&#xff0c;但是感觉有些舍不得&#xff0c;因此先搬移过来。 曾经&#xff0c;我只知道socket函数能进行网络间数据的通信&#xff0c;知道tcp/ip协议也是用来进行网络数据…

c++领域展开第一幕——入门基础(命名空间、iostream、缺省参数、函数重载、nullptr、inline(内联函数))超详细!!!!

文章目录 前言一、c的第一个程序二、命名空间2.1 namespace 的价值2.2 namespace 的定义2.3 命名空间的使用 三、c的输入和输出四、缺省参数五、函数重载六、nullptr七、inline总结 前言 今天小编带着大家进入c的大门&#xff0c;虽然c难&#xff0c;但好事多磨&#xff0c;一起…

DM-VIO(ROS)+t265配置运行记录(ubuntu18.04+ros melodic)

在工作中需要对DM-VIO算法进行测试&#xff0c;于是配置并记录了一下&#xff1a; 首先运行ros接口的dm-vio&#xff0c;一定要先配置源码 https://github.com/lukasvst/dm-vio在这个网址把源码下载下来并解压&#xff0c;并安装一下依赖&#xff1a; sudo apt-get install …

基于Java Springboot成人教育APP且微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 微信…

基于Java Springboot个人财务APP且微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 微信…

PotPlayer 最新版本支持使用 Whisper 自动识别语音生成字幕

PotPlayer 最新版本支持使用 Whisper 自动识别语音生成字幕 设置使用下载地址 设置 使用 下载地址 https://www.videohelp.com/software/PotPlayer

【软考网工笔记】网络基础理论——传输层

IPSec协议 Internet协议安全性是一种开放标准的框架结构&#xff0c;通过使用加密的安全服务以确保在Internet协议&#xff08;IP&#xff09;网络上进行保密而安全的通讯。 工作在OSI模型的第三层网络层上&#xff0c;使其在单独使用时适于保护基于TCP或UDP的协议&#xff0…

Fastify装饰器:增强你的路由处理功能加入日志

Fastify以其出色的性能和扩展性脱颖而出。装饰器是Fastify提供的一个强大功能&#xff0c;它允许开发者在不修改核心代码的情况下&#xff0c;向请求&#xff08;Request&#xff09;和响应&#xff08;Response&#xff09;对象添加自定义属性和方法。本文将通过一个简单的示例…

redis命令行常用的操作及数据备份

redis命令行常用的操作及数据备份 1.连接命令行2.常用的命令3.数据备份恢复4.桌面管理工具 在日常工作中&#xff0c;有时候会需要去查看redis中某个缓存key是否存在、是否过期等情况&#xff1b;因此&#xff0c;记录整理了一些常用的命令&#xff1b; 1.连接命令行 连接到re…

【大数据学习 | Spark-SQL】关于RDD、DataFrame、Dataset对象

1. 概念&#xff1a; RDD&#xff1a; 弹性分布式数据集&#xff1b; DataFrame&#xff1a; DataFrame是一种以RDD为基础的分布式数据集&#xff0c;类似于传统数据库中的二维表格。带有schema元信息&#xff0c;即DataFrame所表示的二维表数据集的每一列都带有名称和类型…

分布式集群下如何做到唯一序列号

优质博文&#xff1a;IT-BLOG-CN 分布式架构下&#xff0c;生成唯一序列号是设计系统常常会遇到的一个问题。例如&#xff0c;数据库使用分库分表的时候&#xff0c;当分成若干个sharding表后&#xff0c;如何能够快速拿到一个唯一序列号&#xff0c;是经常遇到的问题。实现思…

【算法刷题指南】优先级队列

&#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系列 &#x1f308;Linux学习专栏&#xff1a; 南桥谈Linux &#x1f308;数据结构学习专栏&#xff1a; 数据结构杂谈 &#x1f308;数据…