单片机成品方案:基于STM32单片机智能门禁系统

一、前言

实物图片:

视频讲解:单片机成品方案:基于STM32单片机智能门禁系统https://www.bilibili.com/video/BV13faq6gEtv/?share_source=copy_web&vd_source=91b967b41a17a5686913521545a8c3fb

二、项目简介

矩阵按键定义

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);
}
相关推荐
jianqiang.xue1 小时前
【CStackGUI 实战】采集记录器:模拟采集 + 双曲线 + CSV 落盘 + 回放
单片机·嵌入式·cstackgui·c语言gui·可视化拖拽
iCxhust2 小时前
51单片机电力载波应用开发(3)载波模块点对点收发
网络·嵌入式硬件·51单片机
硅基手札2 小时前
【risc-v专栏03】CSR 速查与特权架构 M/S/U
单片机·架构·risc-v
单片机设计·······2 小时前
【dz-1162】汽车前玻璃雨刮器设计
c++·单片机·嵌入式硬件
尼喃2 小时前
USB口5V过压过流限流保护芯片:3A,6A的压降与热成像数据
嵌入式硬件
兴通物联科技2 小时前
对俄出口诚实标识 2026 时间节点与产线赋码采集技术方案
大数据·服务器·单片机·嵌入式硬件·深度学习·计算机视觉
en.en..3 小时前
STM 与 ARM
stm32
逐步前行3 小时前
ESP32-S3-TIMG(定时器组)
单片机
天空'之城4 小时前
单片机基础核心知识点汇总(四十五)
单片机·嵌入式硬件