一、前言
实物图片:

二、项目简介
矩阵按键定义
7 8 9 注册IC卡
4 5 6 删除IC卡
1 2 3 注册指纹
返回 0 确认 修改密码
功能说明
1.STM32F103C8T6单片机进行数据处理;
2.AS608指纹模块录入指纹,删除指纹,验证指纹;
3.RC522模块激活IC卡,注销IC卡,刷卡开锁;
4.矩阵键盘设置密码,密码开锁(6位密码),默认密码000000,管理员密码111111,输入管理员密码后,将密码强制初始化为000000
5.开锁失败三次蜂鸣器报警,警报期间无法进行任何操作,语音播报:键盘已锁定;密码输错时会提示密码错误
6.SG90舵机转动模拟开/关锁;
7.通过OLED模块进行状态显示;
8.通过语音模块进行状态播报;
9.通过WiFi模块ESP-01S将数据传输到手机APP上,手机APP显示门的开关状态、当前开锁方式,开锁时间,是否开锁成功和历史记录;
10.手机app可以修改密码以及远程开锁;
11.输入正确密码后,舵机动作,开锁成功,屏幕显示:开门,语音播报:门已打开;
12.修改密码时,必须在开门状态下,再次输入当前的开门密码,屏幕显示:请输入新密码,语音播报:请输入新密码;
13.完成输入后,按下确认按键,屏幕显示:请再次输入新密码,语音播报:请再次输入新密码;
14.再次输入新密码后按下确认,若两次密码一致,则屏幕显示:新密码设置成功,语音播报:新密码设置成功;
15.若第二次输入新密码与第一次密码不一致,则屏幕显示:设置失败,跳出密码设置程序,语音播报:新密码设置失败。
16.可通过注册IC卡按键,添加IC卡,按下后,屏幕显示:注册卡模式,语音播报:请刷卡;
17.刷卡后,屏幕显示:录入成功,语音播报:录入成功;
18.刷卡时,识别成功,屏幕显示:当前卡号,语音播报:门已打开,舵机动作;刷卡时若识别失败,则屏幕显示:无效卡号,语音播报:无效卡号;
19.通过删除IC卡按键进行IC卡删除操作,当按下该按键后,屏幕显示:请刷卡,语音播报:请刷卡,刷卡后,屏幕显示:删除成功,语音播报:删除成功,
20.可通过注册指纹按键,进行指纹注册,按下按键时,需完成两次指纹录入,即可成功录入指纹,然后屏幕显示指纹的ID,语音播报:指纹录入成功;
21.在主界面时,若指纹识别成功,则屏幕显示:开门,舵机转动,语音播报:门已打开;若识别失败,则屏幕显示:无效指纹,语音播报:无效指纹
22.开门状态下,输入112233,再按下确认,即可一键删除所有指纹
三、原理图设计

四、程序设计
cpp
#include "project.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "AS608.h"
#include "BEEP.H"
#include "RC522.H"
#include "SG_90.H"
#include "JQ6500.H"
#include "KEY_4_4.H"
#include "bsp_flash.h"
#include "communication.h"
#include "delay.h"
#include "show.h"
#define LOOP_MS 10u
#define BOOT_MS 1000u
#define OPEN_HOLD_MS 5000u
#define SERVO_SETTLE_MS 400u
#define MODE_TIMEOUT_MS 30000u
#define ALARM_HOLD_MS 60000u
#define NOTICE_MS 2200u
#define CARD_POLL_MS 200u
#define OLED_PAGE_MS 2500u
#define CARD_CAPACITY 20u
#define FLASH_A 0x0800F800u
#define FLASH_B 0x0800FC00u
#define FLASH_MAGIC 0x42534350u
typedef struct {
uint8_t length;
uint8_t uid[RC522_UID_MAX_SIZE];
} Card;
typedef struct {
uint32_t magic;
uint32_t generation;
char password[7];
uint8_t card_count;
uint16_t next_finger_id;
Card cards[CARD_CAPACITY];
uint32_t checksum;
} Credentials;
typedef enum {
MODE_NORMAL, MODE_OLD, MODE_NEW, MODE_REPEAT,
MODE_CARD_ADD, MODE_CARD_DELETE, MODE_FINGER_FIRST,
MODE_FINGER_RELEASE, MODE_FINGER_SECOND
} Mode;
static Credentials credentials;
static uint32_t current_page = FLASH_A;
static uint32_t now_ms, open_ms, alarm_ms, notice_ms, card_ms, page_ms, display_ms, mode_ms;
static uint32_t event_sequence;
static uint32_t last_request_id;
static uint8_t request_seen;
static uint32_t last_cycles, remaining_cycles;
#define CYCLE_COUNTER_CTRL (*(volatile uint32_t *)0xE0001000u)
#define CYCLE_COUNTER (*(volatile uint32_t *)0xE0001004u)
static uint8_t opened, alarm, failures, last_key = KEY_4_4_NO_KEY;
static uint8_t card_present, finger_present, display_page, notice_active;
static uint8_t voice_queue[8], voice_head, voice_tail;
static uint8_t voice_started;
static uint32_t voice_last_ms, detail_ms;
static uint8_t finger_counter_dirty;
static uint32_t finger_retry_ms;
static Mode mode;
static char input[7], first_new[7];
static char detail[28];
static uint8_t input_length;
static const char *notice;
static void EndMode(void);
static uint32_t Checksum(const Credentials *c)
{
const uint8_t *p = (const uint8_t *)c;
uint32_t hash = 2166136261u;
uint16_t i;
for (i = 0u; i < (uint16_t)(sizeof(*c) - sizeof(c->checksum)); ++i)
hash = (hash ^ p[i]) * 16777619u;
return hash;
}
static uint8_t Valid(const Credentials *c)
{
uint8_t i, j;
if (c->magic != FLASH_MAGIC || c->card_count > CARD_CAPACITY ||
c->next_finger_id > AS608_GetCapacity() ||
c->password[6] != 0 || c->checksum != Checksum(c)) return 0u;
for (i = 0u; i < 6u; ++i)
if (c->password[i] < '0' || c->password[i] > '9') return 0u;
for (i = 0u; i < c->card_count; ++i) {
if (c->cards[i].length == 0u || c->cards[i].length > RC522_UID_MAX_SIZE) return 0u;
for (j = c->cards[i].length; j < RC522_UID_MAX_SIZE; ++j)
if (c->cards[i].uid[j] != 0u) return 0u;
}
return 1u;
}
static void Load(void)
{
Credentials a, b;
uint8_t av, bv;
BSP_Flash_Read(FLASH_A, &a, sizeof(a));
BSP_Flash_Read(FLASH_B, &b, sizeof(b));
av = Valid(&a); bv = Valid(&b);
if (av && (!bv || (int32_t)(a.generation - b.generation) > 0)) {
credentials = a; current_page = FLASH_A;
} else if (bv) {
credentials = b; current_page = FLASH_B;
} else {
memset(&credentials, 0, sizeof(credentials));
credentials.magic = FLASH_MAGIC;
strcpy(credentials.password, "000000");
credentials.checksum = Checksum(&credentials);
current_page = FLASH_B;
}
}
static uint8_t Save(const Credentials *next)
{
Credentials candidate, verify;
uint32_t target = current_page == FLASH_A ? FLASH_B : FLASH_A;
candidate = *next;
candidate.magic = FLASH_MAGIC;
candidate.generation = credentials.generation + 1u;
candidate.checksum = Checksum(&candidate);
if (BSP_Flash_ReplacePage(target, &candidate, sizeof(candidate)) == 0u) return 0u;
BSP_Flash_Read(target, &verify, sizeof(verify));
if (!Valid(&verify) || verify.generation != candidate.generation) return 0u;
credentials = verify;
current_page = target;
finger_counter_dirty = 0u;
return 1u;
}
static void Say(uint8_t track)
{
uint8_t next = (uint8_t)((voice_tail + 1u) % 8u);
if (next != voice_head) {
voice_queue[voice_tail] = track;
voice_tail = next;
}
}
static void VoiceProcess(void)
{
if (voice_head == voice_tail) return;
if (voice_started && JQ6500_IsBusy()) return;
if (JQ6500_IsBusy()) return;
if (voice_started && (uint32_t)(now_ms - voice_last_ms) <
(JQ6500_HasBusySignal() ? 300u : 2000u)) return;
if (JQ6500_PlayTrack(voice_queue[voice_head]) == JQ6500_OK) {
voice_head = (uint8_t)((voice_head + 1u) % 8u);
voice_started = 1u;
voice_last_ms = now_ms;
}
}
static void Notice(const char *text)
{
notice = text;
notice_active = 1u;
notice_ms = now_ms;
}
static void State(void)
{
Communication_SendState(opened, alarm, failures);
}
static void Attempt(const char *method, uint8_t success, const char *reason)
{
if (alarm) {
Communication_SendEvent(++event_sequence, method, "BLOCKED", opened, "LOCKED");
return;
}
if (success) {
failures = 0u;
opened = 1u;
open_ms = now_ms;
SG_90_SetAngle(90u);
delay_ms(SERVO_SETTLE_MS);
Notice("\xE5\xBC\x80\xE9\x97\xA8");
Say(1u);
Communication_SendEvent(++event_sequence, method, "SUCCESS", opened, 0);
} else {
if (failures < 3u) ++failures;
if (strcmp(method, "CARD") == 0) Notice("\xE6\x97\xA0\xE6\x95\x88\xE5\x8D\xA1\xE5\x8F\xB7");
else if (strcmp(method, "FINGER") == 0) Notice("\xE6\x97\xA0\xE6\x95\x88\xE6\x8C\x87\xE7\xBA\xB9");
else if (reason && strcmp(reason, "BAD_PASSWORD") == 0) {
Notice("\xE5\xAF\x86\xE7\xA0\x81\xE9\x94\x99\xE8\xAF\xAF");
Say(13u);
} else Notice("\xE9\xAA\x8C\xE8\xAF\x81\xE5\xA4\xB1\xE8\xB4\xA5");
if (failures == 3u) {
alarm = 1u;
alarm_ms = now_ms;
opened = 0u;
SG_90_SetAngle(0u);
BEEP_Set(1u);
Notice("\xE9\x94\xAE\xE7\x9B\x98\xE5\xB7\xB2\xE9\x94\x81\xE5\xAE\x9A");
Say(2u);
EndMode();
}
Communication_SendEvent(++event_sequence, method, "FAIL", opened, reason);
}
State();
}
static uint8_t MatchPassword(const char *password)
{
return (strcmp(password, "111111") != 0 &&
strcmp(password, "112233") != 0 &&
strcmp(password, credentials.password) == 0) ? 1u : 0u;
}
static uint8_t CardIndex(const uint8_t *uid, uint8_t length)
{
uint8_t i;
for (i = 0u; i < credentials.card_count; ++i)
if (credentials.cards[i].length == length &&
memcmp(credentials.cards[i].uid, uid, length) == 0) return i;
return 0xFFu;
}
static void ResetInput(void)
{
memset(input, 0, sizeof(input));
input_length = 0u;
}
static void EndMode(void)
{
mode = MODE_NORMAL;
if (opened) open_ms = now_ms;
ResetInput();
memset(first_new, 0, sizeof(first_new));
}
static void HandleConfirm(void)
{
Credentials next;
if (input_length != 6u) { Notice("\xE9\x9C\x80\xE8\xA6\x81\xE5\x85\xAD\xE4\xBD\x8D\xE5\xAF\x86\xE7\xA0\x81"); ResetInput(); return; }
if (mode == MODE_NORMAL) {
if (strcmp(input, "112233") == 0) {
if (!opened) Notice("\xE8\xAF\xB7\xE5\x85\x88\xE5\xBC\x80\xE9\x97\xA8");
else if (AS608_EmptyDatabase() == AS608_OK) {
next = credentials;
next.next_finger_id = 0u;
if (!Save(&next)) {
credentials.next_finger_id = 0u;
finger_counter_dirty = 1u;
finger_retry_ms = now_ms;
}
Notice("\xE6\x8C\x87\xE7\xBA\xB9\xE5\xB7\xB2\xE6\xB8\x85\xE7\xA9\xBA");
} else Notice("\xE6\xB8\x85\xE7\xA9\xBA\xE5\xA4\xB1\xE8\xB4\xA5");
} else if (strcmp(input, "111111") == 0) {
next = credentials;
strcpy(next.password, "000000");
if (Save(&next)) Notice("\xE5\xAF\x86\xE7\xA0\x81\xE5\xB7\xB2\xE9\x87\x8D\xE7\xBD\xAE");
else Notice("\xE4\xBF\x9D\xE5\xAD\x98\xE5\xA4\xB1\xE8\xB4\xA5");
} else Attempt("PWD", MatchPassword(input), "BAD_PASSWORD");
ResetInput();
} else if (mode == MODE_OLD) {
if (strcmp(input, credentials.password) != 0) {
Notice("\xE6\x97\xA7\xE5\xAF\x86\xE7\xA0\x81\xE9\x94\x99\xE8\xAF\xAF"); EndMode();
} else { mode = MODE_NEW; mode_ms = now_ms; ResetInput(); Notice("\xE8\xAF\xB7\xE8\xBE\x93\xE5\x85\xA5\xE6\x96\xB0\xE5\xAF\x86\xE7\xA0\x81"); Say(3u); }
} else if (mode == MODE_NEW) {
if (strcmp(input, "112233") == 0 || strcmp(input, "111111") == 0) {
Notice("\xE5\xAF\x86\xE7\xA0\x81\xE4\xB8\x8D\xE5\x8F\xAF\xE7\x94\xA8"); EndMode();
}
else { strcpy(first_new, input); mode = MODE_REPEAT; mode_ms = now_ms; ResetInput();
Notice("\xE8\xAF\xB7\xE5\x86\x8D\xE6\xAC\xA1\xE8\xBE\x93\xE5\x85\xA5\xE6\x96\xB0\xE5\xAF\x86\xE7\xA0\x81"); Say(4u); }
} else if (mode == MODE_REPEAT) {
if (strcmp(input, first_new) != 0) {
Notice("\xE8\xAE\xBE\xE7\xBD\xAE\xE5\xA4\xB1\xE8\xB4\xA5"); Say(6u);
} else {
next = credentials;
strcpy(next.password, input);
if (Save(&next)) { Notice("\xE6\x96\xB0\xE5\xAF\x86\xE7\xA0\x81\xE8\xAE\xBE\xE7\xBD\xAE\xE6\x88\x90\xE5\x8A\x9F"); Say(5u); }
else Notice("\xE4\xBF\x9D\xE5\xAD\x98\xE5\xA4\xB1\xE8\xB4\xA5");
}
EndMode();
}
}
static void KeyProcess(void)
{
static const char digit[16] = {'7','8','9',0,'4','5','6',0,
'1','2','3',0,0,'0',0,0};
uint8_t key = KEY_4_4_Scan();
if (key == last_key) return;
last_key = key;
if (key == KEY_4_4_NO_KEY) return;
if (alarm) return;
if (key == 12u) { EndMode(); Notice("\xE5\xB7\xB2\xE8\xBF\x94\xE5\x9B\x9E"); return; }
if (mode >= MODE_CARD_ADD) return;
if (key == 14u) { HandleConfirm(); return; }
if (mode == MODE_NORMAL && key == 15u) {
if (!opened) { Notice("\xE8\xAF\xB7\xE5\x85\x88\xE5\xBC\x80\xE9\x97\xA8"); return; }
mode = MODE_OLD; mode_ms = now_ms; ResetInput(); Notice("\xE8\xAF\xB7\xE8\xBE\x93\xE5\x85\xA5\xE6\x97\xA7\xE5\xAF\x86\xE7\xA0\x81"); return;
}
if (mode == MODE_NORMAL && (key == 3u || key == 7u)) {
if (!opened) { Notice("\xE8\xAF\xB7\xE5\x85\x88\xE5\xBC\x80\xE9\x97\xA8"); return; }
mode = key == 3u ? MODE_CARD_ADD : MODE_CARD_DELETE;
mode_ms = now_ms;
Notice(key == 3u ? "\xE6\xB3\xA8\xE5\x86\x8C\xE5\x8D\xA1\xE6\xA8\xA1\xE5\xBC\x8F" : "\xE5\x88\xA0\xE9\x99\xA4\xE5\x8D\xA1\xE6\xA8\xA1\xE5\xBC\x8F"); Say(7u); return;
}
if (mode == MODE_NORMAL && key == 11u) {
if (!opened) { Notice("\xE8\xAF\xB7\xE5\x85\x88\xE5\xBC\x80\xE9\x97\xA8"); return; }
mode = MODE_FINGER_FIRST; mode_ms = now_ms; Notice("\xE8\xAF\xB7\xE6\x94\xBE\xE7\xBD\xAE\xE6\x89\x8B\xE6\x8C\x87"); return;
}
if (digit[key] && input_length < 6u) {
mode_ms = now_ms;
if (input_length == 0u && mode == MODE_NORMAL) {
display_page = 0u;
page_ms = now_ms;
}
input[input_length++] = digit[key];
input[input_length] = 0;
}
}
static void CardProcess(void)
{
uint8_t uid[RC522_UID_MAX_SIZE], length, index, result;
Credentials next;
if ((uint32_t)(now_ms - card_ms) < CARD_POLL_MS) return;
card_ms = now_ms;
result = RC522_ReadCardSerial(uid, &length);
if (result != RC522_OK) { card_present = 0u; return; }
if (card_present) return;
card_present = 1u;
if (alarm) return;
if (mode != MODE_NORMAL && mode != MODE_CARD_ADD && mode != MODE_CARD_DELETE) return;
index = CardIndex(uid, length);
strcpy(detail, "C:");
RC522_FormatUid(uid, length, detail + 2, (uint8_t)(sizeof(detail) - 2u));
detail_ms = now_ms;
display_page = 0u;
page_ms = now_ms;
if (mode == MODE_CARD_ADD) {
if (index != 0xFFu || credentials.card_count >= CARD_CAPACITY) Notice("\xE5\xBD\x95\xE5\x85\xA5\xE5\xA4\xB1\xE8\xB4\xA5");
else {
next = credentials;
next.cards[next.card_count].length = length;
memset(next.cards[next.card_count].uid, 0, RC522_UID_MAX_SIZE);
memcpy(next.cards[next.card_count].uid, uid, length);
++next.card_count;
if (Save(&next)) { Notice("\xE5\xBD\x95\xE5\x85\xA5\xE6\x88\x90\xE5\x8A\x9F"); Say(8u); }
else Notice("\xE4\xBF\x9D\xE5\xAD\x98\xE5\xA4\xB1\xE8\xB4\xA5");
}
EndMode();
} else if (mode == MODE_CARD_DELETE) {
if (index == 0xFFu) Notice("\xE5\x88\xA0\xE9\x99\xA4\xE5\xA4\xB1\xE8\xB4\xA5");
else {
uint8_t i;
next = credentials;
for (i = index; i + 1u < next.card_count; ++i) next.cards[i] = next.cards[i + 1u];
--next.card_count;
memset(&next.cards[next.card_count], 0, sizeof(Card));
if (Save(&next)) { Notice("\xE5\x88\xA0\xE9\x99\xA4\xE6\x88\x90\xE5\x8A\x9F"); Say(10u); }
else Notice("\xE4\xBF\x9D\xE5\xAD\x98\xE5\xA4\xB1\xE8\xB4\xA5");
}
EndMode();
} else if (mode == MODE_NORMAL) {
if (index == 0xFFu) { Notice("\xE6\x97\xA0\xE6\x95\x88\xE5\x8D\xA1\xE5\x8F\xB7"); Say(9u); }
Attempt("CARD", index != 0xFFu, "INVALID_CARD");
}
}
static void FingerProcess(void)
{
uint8_t pressed = AS608_IsFingerPressed();
AS608_Status result;
uint16_t id, score;
Credentials next;
if (!pressed) { finger_present = 0u; if (mode == MODE_FINGER_RELEASE) {
mode = MODE_FINGER_SECOND; mode_ms = now_ms; Notice("\xE5\x86\x8D\xE6\xAC\xA1\xE6\x94\xBE\xE7\xBD\xAE\xE6\x89\x8B\xE6\x8C\x87"); } return; }
if (finger_present || alarm) return;
finger_present = 1u;
if (mode == MODE_FINGER_FIRST || mode == MODE_FINGER_SECOND) {
result = AS608_GetImage();
if (result == AS608_OK)
result = AS608_GenerateChar(mode == MODE_FINGER_FIRST ? AS608_BUFFER_1 : AS608_BUFFER_2);
if (result != AS608_OK) { Notice("\xE5\xBD\x95\xE5\x85\xA5\xE5\xA4\xB1\xE8\xB4\xA5"); EndMode(); return; }
if (mode == MODE_FINGER_FIRST) { mode = MODE_FINGER_RELEASE; mode_ms = now_ms; Notice("\xE8\xAF\xB7\xE7\xA7\xBB\xE5\xBC\x80\xE6\x89\x8B\xE6\x8C\x87"); return; }
result = AS608_CreateModel();
id = credentials.next_finger_id;
if (result == AS608_OK && id < AS608_GetCapacity() &&
AS608_StoreModel(AS608_BUFFER_1, id) == AS608_OK) {
next = credentials;
next.next_finger_id = (uint16_t)(id + 1u);
if (Save(&next)) {
sprintf(detail, "F:%u", (unsigned int)id);
detail_ms = now_ms;
Notice("\xE6\x8C\x87\xE7\xBA\xB9\xE5\xBD\x95\xE5\x85\xA5\xE6\x88\x90\xE5\x8A\x9F"); Say(11u);
} else {
(void)AS608_DeleteModel(id, 1u);
Notice("\xE4\xBF\x9D\xE5\xAD\x98\xE5\xA4\xB1\xE8\xB4\xA5");
}
} else Notice("\xE5\xBD\x95\xE5\x85\xA5\xE5\xA4\xB1\xE8\xB4\xA5");
EndMode();
} else if (mode == MODE_NORMAL) {
result = AS608_Identify(&id, &score);
if (result != AS608_OK) { Notice("\xE6\x97\xA0\xE6\x95\x88\xE6\x8C\x87\xE7\xBA\xB9"); Say(12u); }
Attempt("FINGER", result == AS608_OK, "INVALID_FINGER");
}
}
static void CommandProcess(Communication_Command *command)
{
const char *op = command->op == 2u ? "SET_PASSWORD" :
command->op == 1u ? "OPEN" : "UNKNOWN";
Credentials next;
if (request_seen && command->id <= last_request_id) {
Communication_SendAck(command->id, op, "ERROR", "FORMAT_ERROR"); return;
}
if (command->valid) {
request_seen = 1u;
last_request_id = command->id;
}
if (!command->valid) {
Communication_SendAck(command->id, op, "DENIED", "FORMAT_ERROR"); return;
}
if (alarm) {
if (command->op == 1u) Attempt("APP", 0u, "LOCKED");
Communication_SendAck(command->id, op, "LOCKED", "LOCKED"); return;
}
if (command->op == 1u) {
uint8_t valid = MatchPassword(command->password);
Attempt("APP", valid, "BAD_PASSWORD");
Communication_SendAck(command->id, op, valid ? "SUCCESS" : "DENIED",
valid ? 0 : "BAD_PASSWORD");
} else {
if (!opened) Communication_SendAck(command->id, op, "DENIED", "DOOR_CLOSED");
else if (strcmp(command->password, credentials.password) != 0)
Communication_SendAck(command->id, op, "DENIED", "BAD_PASSWORD");
else if (strcmp(command->next_password, "112233") == 0 ||
strcmp(command->next_password, "111111") == 0)
Communication_SendAck(command->id, op, "DENIED", "FORMAT_ERROR");
else {
next = credentials;
strcpy(next.password, command->next_password);
if (Save(&next)) Communication_SendAck(command->id, op, "SUCCESS", 0);
else Communication_SendAck(command->id, op, "ERROR", "SAVE_FAILED");
}
}
memset(command->password, 0, sizeof(command->password));
memset(command->next_password, 0, sizeof(command->next_password));
}
void Project_Init(void)
{
delay_init();
Show_Init();
Show_ShowBoot();
delay_ms(BOOT_MS); /* 1000 uF supply reservoir settling; boot text stays visible. */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
CYCLE_COUNTER_CTRL |= 1u;
CYCLE_COUNTER = 0u;
last_cycles = 0u;
Load();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
BEEP_Init(); BEEP_Set(0u);
SG_90_Init(); SG_90_SetAngle(0u);
KEY_4_4_Init(); RC522_Init(); AS608_Init();
(void)JQ6500_Init();
Communication_Init();
Notice("\xE7\xB3\xBB\xE7\xBB\x9F\xE5\xB0\xB1\xE7\xBB\xAA");
}
void Project_Process(void)
{
Communication_Command command;
uint8_t event;
uint32_t sample = CYCLE_COUNTER;
uint32_t cycles_per_ms = SystemCoreClock / 1000u;
remaining_cycles += (uint32_t)(sample - last_cycles);
last_cycles = sample;
now_ms += remaining_cycles / cycles_per_ms;
remaining_cycles %= cycles_per_ms;
if (finger_counter_dirty && (uint32_t)(now_ms - finger_retry_ms) >= 5000u) {
Credentials next = credentials;
next.next_finger_id = 0u;
if (Save(&next)) finger_counter_dirty = 0u;
finger_retry_ms = now_ms;
}
if (mode != MODE_NORMAL && (uint32_t)(now_ms - mode_ms) >= MODE_TIMEOUT_MS) {
EndMode();
Notice("\xE6\x93\x8D\xE4\xBD\x9C\xE8\xB6\x85\xE6\x97\xB6");
}
if (opened && mode == MODE_NORMAL &&
(uint32_t)(now_ms - open_ms) >= OPEN_HOLD_MS) {
opened = 0u; SG_90_SetAngle(0u); State();
}
if (alarm && (uint32_t)(now_ms - alarm_ms) >= ALARM_HOLD_MS) {
alarm = 0u; failures = 0u; BEEP_Set(0u); Notice("\xE9\x94\x81\xE5\xAE\x9A\xE5\xB7\xB2\xE8\xA7\xA3\xE9\x99\xA4"); State();
}
if (notice_active && (uint32_t)(now_ms - notice_ms) >= NOTICE_MS) notice_active = 0u;
KeyProcess(); CardProcess(); FingerProcess();
event = Communication_Poll(&command, now_ms);
if (event == 2u) { request_seen = 0u; State(); }
else if (event == 1u) CommandProcess(&command);
VoiceProcess();
if (mode == MODE_NORMAL && input_length == 0u && !notice_active &&
(uint32_t)(now_ms - detail_ms) < 5000u && detail[0] == 'C' &&
strlen(detail + 2u) > 15u &&
(uint32_t)(now_ms - page_ms) >= OLED_PAGE_MS) {
page_ms = now_ms; display_page ^= 1u;
}
if ((uint32_t)(now_ms - display_ms) >= 200u) {
display_ms = now_ms;
Show_Render(notice_active ? notice : 0, opened, alarm, failures,
(uint8_t)mode, input_length, display_page,
(uint32_t)(now_ms - detail_ms) < 5000u ? detail : 0);
}
delay_ms(LOOP_MS);
}