第08章 密码开锁
约 3791 字大约 13 分钟
2026-02-03
10.1 需求分析
按键开锁业务分析:
- 录入管理员
- 删除管理员
- 录入用户密码
- 删除用户密码
- 校验用户开锁
输入指令:
- "00" 录入管理员
- "01" 删除管理员
- "10" 录入普通用户
- "11" 删除普通用户
- "99" 删除所有密码
注意:指令需出厂前提前设置
10.2 驱动层:NVS 存储
① NVS 概述
ESP32的NVS(Non-Volatile Storage,非易失性存储)是一种存储子系统,用于在设备的闪存中持久化保存数据。它特别适用于需要在断电后仍能保持数据的应用场景,例如保存Wi-Fi凭据、设备配置参数、传感器校准值等。
NVS库提供了一个简单的键值对存储机制,允许用户以ASCII字符串作为键来存储和检索多种类型的数据值,包括整数型(如uint8_t, int8_t, uint16_t, 等, 每个键值对都存储在一个命名空间内,这样可以避免不同组件之间的键名冲突
② 代码
Dri_NVS.h
#ifndef __DRI_NVS_H__
#define __DRI_NVS_H__
#include "nvs_flash.h"
#include "nvs.h"
/**
* @brief 初始化
*/
void Dri_NVS_Init(void);
/**
* @brief 向flash写数据
*/
esp_err_t Dri_NVS_WriteStr(char *key, char *value);
/**
* @brief 向flash读数据
*/
esp_err_t Dri_NVS_ReadStr(char *key, char *value, size_t *len);
/**
* @brief 删除
*/
esp_err_t Dri_NVS_DeleteKey(char *key);
/**
* @brief 寻找KEY是否存在
*/
esp_err_t Dri_NVS_FindKey(char *key);
/**
* @brief 一键删除
*/
esp_err_t Dri_NVS_DeleteAllKey(void);
#endif /* __DRI_NVS_H__ */Dri_NVS.c
#include "Dri_NVS.h"
/**
* @brief 初始化
*/
// 创建命名空间 并获取该命名空间的操作句柄
nvs_handle_t my_handle;
void Dri_NVS_Init(void)
{
// 初始化NVS
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
// NVS partition was truncated and needs to be erased
// Retry nvs_flash_init
nvs_flash_erase();
err = nvs_flash_init();
}
nvs_open("storage", NVS_READWRITE, &my_handle);
}
/**
* @brief 向flash写数据
*/
esp_err_t Dri_NVS_WriteStr(char *key, char *value)
{
return nvs_set_str(my_handle, key, value);
}
/**
* @brief 向flash读数据
*/
esp_err_t Dri_NVS_ReadStr(char *key, char *value, size_t *len)
{
return nvs_get_str(my_handle, key, value, len);
}
/**
* @brief 删除
*/
esp_err_t Dri_NVS_DeleteKey(char *key)
{
return nvs_erase_key(my_handle, key);
}
/**
* @brief 寻找KEY是否存在
*/
esp_err_t Dri_NVS_FindKey(char *key)
{
// 第三个参数表示值的类型
return nvs_find_key(my_handle, key, NULL);
}
/**
* @brief 一键删除
*/
esp_err_t Dri_NVS_DeleteAllKey(void)
{
return nvs_erase_all(my_handle);
}main.c 测试代码
// 初始化NVS
Dri_NVS_Init();
// 删除所有key(因为烧录和监控都会重启项目)
Dri_NVS_DeleteAllKey();
// 查找某个key是否存在
esp_err_t err = Dri_NVS_FindKey("aaa");
if (err == ESP_OK)
{
printf("aaa存在!!!\r\n");
}
else
{
printf("aaa不存在!!!!\r\n");
}
// 写入key-value
Dri_NVS_WriteStr("aaa", "atguigu");
printf("写入aaa-atguigu数据!!!!\r\n");
// 再次查找某个key是否存在
err = Dri_NVS_FindKey("aaa");
if (err == ESP_OK)
{
printf("aaa存在!!!\r\n");
}
else
{
printf("aaa不存在!!!!\r\n");
}
// 读取该key-value 并打印
uint8_t read_data[10] = {0};
size_t len = 10;
err = Dri_NVS_ReadStr("aaa", (char *)read_data, &len);
printf("err=%#x\r\n", err);
printf("read_data:%s,长度是:%d", read_data, len);10.3 应用层
① 根据按键确认操作类型
App_IO.h
#ifndef __APP_IO_H__
#define __APP_IO_H__
#include "Com_Debug.h"
#include "Com_Config.h"
#include "Dri_NVS.h"
#include "Int_SC12B.h"
#include "Int_WS2812.h"
#include "Int_BDR6120S.h"
#include "Int_WTN6170.h"
/**
* @brief 初始化
*
*/
void App_IO_Init(void);
/**
* @brief 读取按下多个按键(多个按键连续按下,以#结束)
*
* @param keys 按下的按键值组成的数组
* @return Com_Status 操作状态
*/
Com_Status App_IO_ReadKeys(uint8_t* keys);
/**
* @brief 根据按键值确定进行何种操作
*
* @param keys 按下的按键值组成的数组
*/
void App_IO_ProcessKeys(uint8_t* keys);
#endif /* __APP_IO_H__ */App_IO.c
#include "App_IO.h"
/**
* @brief 初始化
*
*/
void App_IO_Init(void)
{
// 初始化NVS
Dri_NVS_Init();
// 初始化电机驱动
Int_BDR6120S_Init();
// 初始化语音
Int_WTN6170_Init();
// 初始化LED
Int_WS2812_Init();
// 初始化SC12B
Int_SC12B_Init();
}
/**
* @brief 读取按下多个按键(多个按键连续按下,以#结束)
*
* @param keys 按下的按键值组成的数组
* @return Com_Status 操作状态
*/
Com_Status App_IO_ReadKeys(uint8_t *keys)
{
// 定义变量:计数,控制两次按键操作之间的时间间隔不能太久
uint8_t pressed_count = 0;
// 定义变量:表示keys数组的索引
uint8_t key_index = 0;
// 定义变量,用于保存当前按下的按键值
Touch_Key_Typedef current_key = Touch_Key_NONE;
// 循环
while (1)
{
// 获取当下所按的按键
current_key = Int_SC12B_TouchKey();
// 如果没有按下
if (current_key == Touch_Key_NONE)
{
// 两次按键操作之间的计数超过200次,直接返回超时
pressed_count++;
if (pressed_count > 200)
{
return Com_TIMEOUT;
}
}
// 如果按下
else
{
// 计数清零
pressed_count = 0;
// 灯光
Int_WS2812_Off();
Int_WS2812_On(current_key, white[0], white[1], white[2]);
// 音效
sayWaterDrop();
// 如果按下的是#,指令或密码输入结束
if (current_key == Touch_Key_SHARP)
{
return Com_OK;
}
// 如果按下的是M,错误指令
else if (current_key == Touch_Key_M)
{
return Com_ERROR;
}
// 否则,将当前按键添加到数组中
else
{
keys[key_index++] = current_key + '0';
}
}
// 阻塞延时 50ms
vTaskDelay(50);
}
}
/**
* @brief 根据按键值确定进行何种操作
*
* @param keys 按下的按键值组成的数组
*/
void App_IO_ProcessKeys(uint8_t* keys)
{
// 获取数组长度
uint8_t keys_len = strlen((char *)keys);
// 长度是2,说明是指令
if (keys_len == 2)
{
if (keys[0] == '0' && keys[1] == '0')
{
printf("add admin \r\n");
}
else if (keys[0] == '0' && keys[1] == '1')
{
printf("delete admin \r\n");
}
else if (keys[0] == '1' && keys[1] == '0')
{
printf("add user \r\n");
}
else if (keys[0] == '1' && keys[1] == '1')
{
printf("delete user \r\n");
}
else if (keys[0] == '9' && keys[1] == '9')
{
printf("delete all \r\n");
}
else
{
printf("Error \r\n");
sayIllegalOperation();
}
}
// 长度小于2,错误
else if (keys_len < 2)
{
printf("Error \r\n");
}
// 其他:密码
else
{
printf("Password \r\n");
/* code */
}
}main.c 测试代码
#include <stdio.h>
#include "esp_task.h"
#include "Com_Debug.h"
#include "App_IO.h"
// 扫描按键任务 ---------------------
// 任务函数的原型
void vScanKeyTaskFunc(void *pvParameters);
// 任务名称
#define SCAN_KEY_TASK_NAME "ScanKeyTask"
// 任务栈大小
#define SCAN_KEY_TASK_STACK_SIZE 4096
// 任务优先级
#define SCAN_KEY_TASK_PRIORITY 6
// 任务句柄
TaskHandle_t xScanKeyTaskHandle = NULL;
void app_main(void)
{
MY_LOGI("Smart Lock Project");
// 创建扫描按键任务
xTaskCreate(vScanKeyTaskFunc, SCAN_KEY_TASK_NAME, SCAN_KEY_TASK_STACK_SIZE, NULL, SCAN_KEY_TASK_PRIORITY, &xScanKeyTaskHandle);
MY_LOGI("ScanKeyTask created");
}
// 扫描按键任务函数
void vScanKeyTaskFunc(void *pvParameters)
{
MY_LOGI("Scan Key Task Start ...");
// 定义数组,用于保存按下的按键值
uint8_t keys[32];
// 初始化IO模块
App_IO_Init();
// 循环
while (1)
{
// 清空数组 keys
memset(keys, 0, 32);
// 读取按键
Com_Status status = App_IO_ReadKeys(keys);
// 如果正确输入
if (status == Com_OK)
{
// 处理按键
App_IO_ProcessKeys(keys);
}
// 如果超时
else if (status == Com_TIMEOUT)
{
// 灯全灭
Int_WS2812_Off();
MY_LOGI("Read Keys Timeout");
}
// 如果错误
else if (status == Com_ERROR)
{
sayIllegalOperation();
MY_LOGE("Read Keys Error");
}
// 阻塞延时
vTaskDelay(50);
}
}② 添加管理员
步骤流程
1. 清除输入缓冲区
2. 如果管理员已存在,直接退出
3. 语音播报添加管理员
4. 语音播报输入密码
5. 收集第一次输入的管理员密码
6. 语音播报:再次输入密码
7. 收集第二次输入的管理员密码
8. 判断两次输入的管理员密码是否一致, 如果不一致直接退出
9. 添加到NVS中App_IO.h
添加宏定义
// 宏定义:最大密码长度
#define MAX_PASSWORD_LEN 32App_IO.c
添加静态函数 App_IO_AddAdmin(),并修改 App_IO_ProcessKeys()
// 定义全局变量:作为输入缓冲区
uint8_t gs_input1[MAX_PASSWORD_LEN];
uint8_t gs_input2[MAX_PASSWORD_LEN];
// 静态函数:添加管理员
static void App_IO_AddAdmin(void)
{
// 清除缓冲区
memset(gs_input1, 0, MAX_PASSWORD_LEN);
memset(gs_input2, 0, MAX_PASSWORD_LEN);
// 如果管理员是否已经存在,直接退出
if (Dri_NVS_FindKey("admin") == ESP_OK)
{
// 语音播报:添加失败
sayWithoutInt();
sayAddFail();
return;
}
// 语音播报:添加管理员
sayWithoutInt();
sayAddAdmin();
// 语音播报:请输入管理员密码
sayWithoutInt();
sayInputAdminPassword();
// 收集第一次输入的管理员密码
if (App_IO_ReadKeys(gs_input1) != Com_OK)
{
// 语音播报:添加失败
sayWithoutInt();
sayAddFail();
return;
}
// 语音播报:请再次输入管理员密码
sayWithoutInt();
sayInputAdminPasswordAgain();
// 收集第二次输入的管理员密码
if (App_IO_ReadKeys(gs_input2) != Com_OK)
{
// 语音播报:添加失败
sayWithoutInt();
sayAddFail();
return;
}
// 判断两次输入的管理员密码是否一致, 如果不一致直接退出
if (strcmp((char *)gs_input1, (char *)gs_input2) != 0)
{
// 语音播报:添加失败
sayWithoutInt();
sayAddFail();
return;
}
// 添加到NVS
if (Dri_NVS_WriteStr("admin", (char *)gs_input1) != ESP_OK)
{
// 语音播报:添加失败
sayWithoutInt();
sayAddFail();
return;
}
// 语音播报:添加成功
sayWithoutInt();
sayAddSucc();
}
/**
* @brief 根据按键值确定进行何种操作
*
* @param keys 按下的按键值组成的数组
*/
void App_IO_ProcessKeys(uint8_t *keys)
{
/* 代码省略 ... */
// 长度是2,说明是指令
if (keys_len == 2)
{
if (keys[0] == '0' && keys[1] == '0')
{
// 添加管理员
printf("add admin \r\n");
App_IO_AddAdmin();
}
/* 代码省略 ... */
}
/* 代码省略 ... */
}③ 删除管理员
步骤流程
验证管理员:
1. 清除输入缓冲区
2. 判断管理员是否存在, 不存在直接退出
3. 语音播报:验证管理员
4. 语音播报:请输入管理员密码
5. 收集输入的管理员密码
6. 获取NVS中的管理员密码
7. 比较输入的管理员密码和NVS中的管理员密码
删除管理员
1. 语言播报:删除管理员
2. 验证管理员
3. 从NVS中删除App_IO.c
添加静态函数 App_IO_CheckAdmin() 和 App_IO_DeleteAdmin(),并修改 App_IO_ProcessKeys()
// 静态函数:验证管理员
static bool App_IO_CheckAdmin(void)
{
// 清除输入缓冲区
memset(gs_input1, 0, MAX_PASSWORD_LEN);
memset(gs_input2, 0, MAX_PASSWORD_LEN);
// 判断管理员是否存在,不存在直接退出
if (Dri_NVS_FindKey("admin") != ESP_OK)
{
return false;
}
// 语音播报:验证管理员
sayWithoutInt();
sayCheckAdmin();
// 语音播报:请输入管理员密码
sayWithoutInt();
sayInputAdminPassword();
// 收集输入的管理员密码, 如果输入错误, 直接退出
if (App_IO_ReadKeys(gs_input1) != Com_OK)
{
return false;
}
// 获取NVS中保存的管理员密码, 获取失败直接退出
size_t admin_password_len = MAX_PASSWORD_LEN;
if (Dri_NVS_ReadStr("admin", (char *)gs_input2, &admin_password_len) != ESP_OK)
{
return false;
}
// 判断输入的管理员密码是否正确, 如果错误直接退出
if (strcmp((char *)gs_input1, (char *)gs_input2) != 0)
{
return false;
}
return true;
}
// 静态函数:删除管理员
static void App_IO_DeleteAdmin(void)
{
// 语音播报:删除管理员
sayWithoutInt();
sayDelAdmin();
// 验证管理员,验证失败,退出
if (!App_IO_CheckAdmin())
{
// 语音播报:删除失败
sayWithoutInt();
sayDelFail();
return;
}
// 从NVS中删除管理员
if (Dri_NVS_DeleteKey("admin") != ESP_OK)
{
// 语音播报:删除失败
sayWithoutInt();
sayDelFail();
return;
}
// 语音播报:删除成功
sayWithoutInt();
sayDelSucc();
}
/**
* @brief 根据按键值确定进行何种操作
*
* @param keys 按下的按键值组成的数组
*/
void App_IO_ProcessKeys(uint8_t *keys)
{
/* 代码省略 ... */
// 长度是2,说明是指令
if (keys_len == 2)
{
/* 代码省略 ... */
else if (keys[0] == '0' && keys[1] == '1')
{
// 删除管理员
printf("delete admin \r\n");
App_IO_DeleteAdmin();
}
/* 代码省略 ... */
}
/* 代码省略 ... */
}④ 添加普通用户
流程步骤
1. 语言播报:添加用户
2. 验证管理员
3. 清除输入缓冲区
4. 语音播报:请输入用户密码
5. 收集第一次输入的用户密码
6. 语言播报:再次输入用户密码
7. 收集第二次输入用户密码
8. 判断两次输入的密码是否一致,不一致退出
9. 添加到NVS中App_IO.c
添加静态函数 App_IO_AddUser() 和 ,并修改 App_IO_ProcessKeys()
// 静态函数:添加普通用户(添加密码)
static void App_IO_AddUser(void)
{
// 语音播报:添加用户
sayWithoutInt();
sayAddUser();
// 验证管理员,验证失败直接退出
if (!App_IO_CheckAdmin())
{
// 语音播报:添加失败
sayWithoutInt();
sayAddFail();
return;
}
// 清除输入缓冲区
memset(gs_input1, 0, MAX_PASSWORD_LEN);
memset(gs_input2, 0, MAX_PASSWORD_LEN);
// 语音播报:请输入用户密码
sayWithoutInt();
sayInputUserPassword();
// 收集第一次输入的用户密码
if (App_IO_ReadKeys(gs_input1) != Com_OK)
{
// 语音播报:添加失败
sayWithoutInt();
sayAddFail();
return;
}
// 语音播报:请再次输入用户密码
sayWithoutInt();
sayInputUserPasswordAgain();
// 收集第二次输入的用户密码
if (App_IO_ReadKeys(gs_input2) != Com_OK)
{
// 语音播报:添加失败
sayWithoutInt();
sayAddFail();
return;
}
// 判断两次输入的用户密码是否一致, 如果不一致直接退出
if (strcmp((char *)gs_input1, (char *)gs_input2) != 0)
{
// 语音播报:添加失败
sayWithoutInt();
sayAddFail();
return;
}
// 添加到NVS,以输入的用户密码作为Key
if (Dri_NVS_WriteStr((char *)gs_input1, "0") != ESP_OK)
{
// 语音播报:添加失败
sayWithoutInt();
sayAddFail();
return;
}
// 语音播报:添加成功
sayWithoutInt();
sayPasswordAddSucc();
}
/**
* @brief 根据按键值确定进行何种操作
*
* @param keys 按下的按键值组成的数组
*/
void App_IO_ProcessKeys(uint8_t *keys)
{
/* 代码省略 ... */
// 长度是2,说明是指令
if (keys_len == 2)
{
/* 代码省略 ... */
else if (keys[0] == '1' && keys[1] == '0')
{
// 添加普通用户
printf("add user \r\n");
App_IO_AddUser();
}
/* 代码省略 ... */
}
/* 代码省略 ... */
}⑤ 删除普通用户
流程步骤
1.语言播报:删除用户
2.验证管理员
3.清除输入缓冲区
4.语言播报:请输入用户密码
5.收集用户输入的密码
6.如果NVS中没有,退出并提示删除失败
7.从NVS中删除用户App_IO.c
添加静态函数 App_IO_DeleteUser(),并修改 App_IO_ProcessKeys()
// 静态函数:删除普通用户(删除密码)
static void App_IO_DeleteUser(void)
{
// 语音播报:删除用户
sayWithoutInt();
sayDelUser();
// 验证管理员,验证失败直接退出
if (!App_IO_CheckAdmin())
{
// 语音播报:删除失败
sayWithoutInt();
sayDelFail();
return;
}
// 清除输入缓冲区
memset(gs_input1, 0, MAX_PASSWORD_LEN);
// 语音播报:请输入用户密码
sayWithoutInt();
sayInputUserPassword();
// 收集输入的用户密码
if (App_IO_ReadKeys(gs_input1) != Com_OK)
{
// 语音播报:删除失败
sayWithoutInt();
sayDelFail();
return;
}
// 判断NVS中是否存在该用户密码
if (Dri_NVS_FindKey((char *)gs_input1) != ESP_OK)
{
// 语音播报:删除失败
sayWithoutInt();
sayDelFail();
return;
}
// 从NVS中删除
if (Dri_NVS_DeleteKey((char *)gs_input1) != ESP_OK)
{
// 语音播报:删除失败
sayWithoutInt();
sayDelFail();
return;
}
// 语音播报:删除成功
sayWithoutInt();
sayDelSucc();
}
/**
* @brief 根据按键值确定进行何种操作
*
* @param keys 按下的按键值组成的数组
*/
void App_IO_ProcessKeys(uint8_t *keys)
{
/* 代码省略 ... */
// 长度是2,说明是指令
if (keys_len == 2)
{
/* 代码省略 ... */
else if (keys[0] == '1' && keys[1] == '1')
{
// 删除普通用户
printf("delete user \r\n");
App_IO_DeleteUser();
}
/* 代码省略 ... */
}
/* 代码省略 ... */
}⑥ 删除所有用户
流程步骤
1. 语音播报:全部删除
2. 验证管理员
3. 从NVS中删除所有App_IO.c
添加静态函数 App_IO_DeleteAll(),并修改 App_IO_ProcessKeys()
// 静态函数:删除所有,包括管理员和用户
static void App_IO_DeleteAll(void)
{
// 语言播报:全部删除
sayWithoutInt();
sayDelAll();
// 验证管理员
if (!App_IO_CheckAdmin())
{
// 语言播报:删除失败
sayWithoutInt();
sayDelFail();
return;
}
// 从NVS中删除所有
if (Dri_NVS_DeleteKey("admin") == ESP_OK)
{
// 语音播报:删除成功
sayWithoutInt();
sayDelSucc();
}
else
{
// 语音播报:删除失败
sayWithoutInt();
sayDelFail();
}
}
/**
* @brief 根据按键值确定进行何种操作
*
* @param keys 按下的按键值组成的数组
*/
void App_IO_ProcessKeys(uint8_t *keys)
{
/* 代码省略 ... */
// 长度是2,说明是指令
if (keys_len == 2)
{
/* 代码省略 ... */
else if (keys[0] == '9' && keys[1] == '9')
{
// 删除所有
printf("delete all \r\n");
App_IO_DeleteAll();
}
/* 代码省略 ... */
}
/* 代码省略 ... */
}⑦ 检查用户密码
流程步骤
1. 判断是否存在输入的密码
2. 存在
2.1 语音播报:验证成功
2.2 语音播报:开门
2.3 执行开锁
3. 不存在
3.1 语音播报:验证失败
3.2 语音播报:请重试App_IO.c
添加静态函数 App_IO_CheckPWDOpenLock(),并修改 App_IO_ProcessKeys()
// 静态函数:验证密码并开锁
static void App_IO_CheckPWDOpenLock(uint8_t *pwd)
{
// 判断NVS中是否存在该用户密码
if (Dri_NVS_FindKey((char *)pwd) == ESP_OK)
{
// 语音播报:验证成功
sayWithoutInt();
sayVerifySucc();
// 语音播报:开门
sayWithoutInt();
sayDoorOpen();
// 执行开锁
Int_BDR6120S_OpenLock();
}
else
{
// 语音播报:验证失败
sayWithoutInt();
sayVerifyFail();
// 语言播报:请重新尝试
sayWithoutInt();
sayRetry();
}
}
/**
* @brief 根据按键值确定进行何种操作
*
* @param keys 按下的按键值组成的数组
*/
void App_IO_ProcessKeys(uint8_t *keys)
{
/* 代码省略 ... */
// 其他:密码
else
{
printf("Check Password \r\n");
App_IO_CheckPWDOpenLock(keys);
}
/* 代码省略 ... */
}