RISC-V MCU 基于CH32V307的智能门锁设计

RISC-V MCU 基于CH32V307的智能门锁设计

一、设计概述

1.1 设计目的

​ 随着物联网、大数据等技术的快速发展,智能门锁应用广泛。但是传统智能门锁还存在着许多的问题,例如,能被特斯拉线圈开锁,机械强度不够容易被暴力强制开锁,忘记带钥匙,等等。这些问题给用户带来了巨大的隐患,同时也限制了智能门锁的市场发展,功能的不足也会影响门锁的便捷性。在此背景之下,打造一把安全的多功能密码锁,具有重大的现实意义,我们想到了一种多方位控制的智能锁系统的设计,极大减少了传统锁具中存在的不便性且保密性更强,更结合了蓝牙、服务器等方式实现了多方式、多途径控制。

1.2 设计主要功能

  1. 采用按键密码锁的方式进行解锁,它可以设计任意六位数作为解锁密码,极大的增加了安全性、可靠性;

  2. 增加了指纹解锁的方式,优化了密码锁结构,也减轻了用户开锁的困难;

  3. 通过蓝牙、服务器的方式远程监控其状态,并可以实现远程控制,给人们生活带来极大的便捷

二、系统设计组成

2.1 总体框图

在这里插入图片描述

2.2 各模块介绍

  1. 门锁控制方式模块

在这里插入图片描述
​ 分为矩阵键盘模块、指纹模块与显示Led。

​ 矩阵键盘原理图如下:
在这里插入图片描述
​ 矩阵键盘实物图如下:
在这里插入图片描述
​ 本设计采用外部中断方式实现矩阵键盘扫描。
​ AS608指纹模块实物图如下:
在这里插入图片描述
AS608指纹模块相关接线如下:
在这里插入图片描述
​ AS608采用串口通信,本设计采用UART4作为通信串口。

  1. 远程控制模块

    ​ 蓝牙模块:

    ​ 实物图
    在这里插入图片描述
    ​ 引脚及指令集
    在这里插入图片描述在这里插入图片描述
    ​ 蓝牙为串口通信,本设计采用UART5作为通信串口。

    ​ esp8266 WiFi模块:

    ​ 接线示意:
    在这里插入图片描述
    ​ 相关指令集参考:

    ​ https://blog.csdn.net/qq_45104817/article/details/105834987

    ​ WiFi模块采用串口通信,本设计采用UART6作为通信串口。

三、部分重要相关代码

3.1 矩阵键盘按键扫描

//key.c
#include "key.h"
#include <rtthread.h>
#include <rthw.h>
#include "debug.h"
#include "drivers/pin.h"
#include <rtdef.h>
#include "mg90s.h"
#include "esp8266.h"
#include "led.h"

char key_data[7] = {'1', '2', '3', '4', '5', '6', '\0'}; //密码设置
char key_input[7] = {0};
int count_t = 0;

extern rt_thread_t key_scancheck;
extern rt_sem_t sem_keych;
extern rt_sem_t sem_keyscan;

/*************************************************************
 * name:        thread_keychange_func
 * function:    修改密码线程
 * input:       parameter - 线程创建时传递的参数
 * return:      无
 *************************************************************/
void thread_keychange_func(void *parameter)
{
    while (1)
    {
        rt_kprintf("keychangesem taking...\n");
        rt_sem_take(sem_keych, RT_WAITING_FOREVER);
        rt_thread_suspend(key_scancheck);
        count_t = 0;
        char ch[7] = {0};
        char send_data[64] = {0};
        rt_kprintf("keychange start...\n");
        rt_strncpy(send_data, "password:", rt_strlen("password:"));
        while (count_t != 6)
        {
            rt_strncpy(ch, key_input, 6);
        }
        rt_strncpy(key_data, key_input, 6);
        for (int i = 0; i < 6; i++)
        {
            send_data[9 + i] = key_input[i];
        }
        esp8266_send(send_data, rt_strlen(send_data));
        rt_kprintf("password has been changed!\n");
        count_t = 0;
        rt_thread_resume(key_scancheck);
    }
}

/*************************************************************
 * name:        key1_init
 * function:    按键扫描gpio初始化,使用中断方式
 * input:       无
 * return:      无
 *************************************************************/
void key1_init()
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    EXTI_InitTypeDef EXTI_InitStructure = {0};
    NVIC_InitTypeDef NVIC_InitStructure = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOE, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOE, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOE, &GPIO_InitStructure);

    GPIO_ResetBits(GPIOE, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);

    /* GPIOA ----> EXTI_Line0 */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource0);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource1);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource2);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3);
    EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1 | EXTI_Line2 | EXTI_Line3;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
    NVIC_Init(&NVIC_InitStructure);
    NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
    NVIC_Init(&NVIC_InitStructure);
    NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;
    NVIC_Init(&NVIC_InitStructure);
}

/*************************************************************
 * name:        exti_disable
 * function:    关按键扫描中断
 * input:       无
 * return:      无
 *************************************************************/
void exti_disable()
{
    EXTI_InitTypeDef EXTI_InitStructure = {0};
    EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1 | EXTI_Line2 | EXTI_Line3;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = DISABLE;
    EXTI_Init(&EXTI_InitStructure);
}

/*************************************************************
 * name:        exti_enable
 * function:    开按键扫描中断
 * input:       无
 * return:      无
 *************************************************************/
void exti_enable()
{
    EXTI_InitTypeDef EXTI_InitStructure = {0};
    EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1 | EXTI_Line2 | EXTI_Line3;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}

/*************************************************************
 * name:        scan
 * function:    进中断后判断按键
 * input:       pin - 传送需要判断对应的列脚
 * return:      无
 *************************************************************/
int scan(uint16_t pin)
{
    GPIO_SetBits(GPIOE, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
    for (int i = 0; i < 4; i++)
    {
        switch (i)
        {
        case 0:
            GPIO_SetBits(GPIOE, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
            GPIO_ResetBits(GPIOE, GPIO_Pin_4);
            break;
        case 1:
            GPIO_SetBits(GPIOE, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
            GPIO_ResetBits(GPIOE, GPIO_Pin_5);
            break;
        case 2:
            GPIO_SetBits(GPIOE, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
            GPIO_ResetBits(GPIOE, GPIO_Pin_6);
            break;
        case 3:
            GPIO_SetBits(GPIOE, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
            GPIO_ResetBits(GPIOE, GPIO_Pin_7);
            break;
        }
        if (GPIO_ReadInputDataBit(GPIOE, pin) == 0)
        {
            while (GPIO_ReadInputDataBit(GPIOE, pin) == 0)
                ;
            GPIO_ResetBits(GPIOE, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
            return (i + 1);
        }
    }
    GPIO_ResetBits(GPIOE, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
    return 0;
}

//第一列有按键按下中断服务程序
void EXTI0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI0_IRQHandler(void)
{
    rt_kprintf("ex0.");
    exti_disable();
    switch (scan(GPIO_Pin_0))
    {
    case 1:
        key_input[count_t++] = '1';
        break;
    case 2:
        key_input[count_t++] = '4';
        break;
    case 3:
        key_input[count_t++] = '7';
        break;
    case 4:
        key_input[count_t++] = '*';
        break;
    }
    rt_kprintf("%c\n", key_input[count_t - 1]);
    exti_enable();
    EXTI_ClearITPendingBit(EXTI_Line0);
}

//第二列有按键按下中断服务程序
void EXTI1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI1_IRQHandler(void)
{
    rt_kprintf("ex1.");
    exti_disable();
    switch (scan(GPIO_Pin_1))
    {
    case 1:
        key_input[count_t++] = '2';
        break;
    case 2:
        key_input[count_t++] = '5';
        break;
    case 3:
        key_input[count_t++] = '8';
        break;
    case 4:
        key_input[count_t++] = '0';
        break;
    }
    rt_kprintf("%c\n", key_input[count_t - 1]);
    exti_enable();
    EXTI_ClearITPendingBit(EXTI_Line1);
}

//第三列有按键按下中断服务程序
void EXTI2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI2_IRQHandler(void)
{
    rt_kprintf("ex2.");
    exti_disable();
    switch (scan(GPIO_Pin_2))
    {
    case 1:
        key_input[count_t++] = '3';
        break;
    case 2:
        key_input[count_t++] = '6';
        break;
    case 3:
        key_input[count_t++] = '9';
        break;
    case 4:
        key_input[count_t++] = '#';
        break;
    }
    rt_kprintf("%c\n", key_input[count_t - 1]);
    exti_enable();
    EXTI_ClearITPendingBit(EXTI_Line2);
}

//第四列有按键按下中断服务程序
void EXTI3_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI3_IRQHandler(void)
{
    rt_kprintf("ex3.");
    exti_disable();
    switch (scan(GPIO_Pin_3))
    {
    case 1:
        key_input[count_t++] = 'A';
        break;
    case 2:
        key_input[count_t++] = 'B';
        break;
    case 3:
        key_input[count_t++] = 'C';
        break;
    case 4:
        key_input[count_t++] = 'D';
        break;
    }
    rt_kprintf("%c\n", key_input[count_t - 1]);
    exti_enable();
    EXTI_ClearITPendingBit(EXTI_Line3);
}

/*************************************************************
 * name:        thread_keyscancheck_func
 * function:    按键扫描判定,确定按键密码数量足够则发送信号量唤醒
 *              密码判断线程
 * input:       parameter - 线程创建时传递的参数
 * return:      无
 *************************************************************/
void thread_keyscancheck_func(void *parameter)
{
    rt_kprintf("thread_keyscancheck_func entry...\n");
    while (1)
    {
        while (count_t != 6)
        {
            rt_thread_mdelay(1000);
        }
        count_t = 0;
        rt_kprintf("sem_keyscan release...\n");
        rt_sem_release(sem_keyscan);
    }
}

/*************************************************************
 * name:        thread_keyscan_func
 * function:    按键扫描密码处理线程
 * input:       parameter - 线程创建时传递的参数
 * return:      无
 *************************************************************/
void thread_keyscan_func(void *parameter)
{
    int num = 0;
    while (1)
    {
        rt_kprintf("sem_keyscan taking...\n");
        rt_sem_take(sem_keyscan, RT_WAITING_FOREVER);
        rt_kprintf("sem_keyscan toke...\n");
        if (rt_strcmp(key_input, key_data) == 0)
        {
            num = 0;
            rt_kprintf("password correct!\n");
            led_crtl(1, 0);
            rt_kprintf("open the door...\n");
            mg90s_ctrl(OPEN_DOOR);
            esp8266_send("door:1 ", rt_strlen("door:1 "));
            rt_thread_mdelay(1000);
            led_crtl(1, 1);
            rt_thread_mdelay(5000);
            rt_kprintf("close the door...\n");
            mg90s_ctrl(CLOSE_DOOR);
            esp8266_send("door:0 ", rt_strlen("door:0 "));
        }
        else
        {
            num++;
            rt_kprintf("fingerprint is not been stored!\n");
            mg90s_ctrl(CLOSE_DOOR);
            esp8266_send("door:0 ", rt_strlen("door:0 "));
            led_crtl(2, 0);
            rt_thread_mdelay(1000);
            led_crtl(2, 1);
            if (num == 10)
            {
                esp8266_send("warn:1\n", rt_strlen("warn:1\n"));
                num = 0;
            }
            rt_kprintf("password wrong...\n");
        }
    }
}

3.2 AS608指纹识别模块相关代码

3.2.1 指令集

u8 cmd_packagehead[6] = {0xef, 0x01, 0xff, 0xff, 0xff, 0xff};                           //发送数据包头
u8 cmd_handshake[10] = {0x01, 0x00, 0x07, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b};    //握手口令,应答包12字节
u8 cmd_readmodenum[6] = {0x01, 0x00, 0x03, 0x1d, 0x00, 0x21};                           //读当前存储的模板个数,应答包14字节
u8 cmd_getimage[6] = {0x01, 0x00, 0x03, 0x01, 0x00, 0x05};                              //采集原始手指图像,应答包12字节
u8 cmd_genfeature1[7] = {0x01, 0x00, 0x04, 0x02, 0x01, 0x00, 0x08};                     //从图像生成特征并存入CharBuffer1,应答包12字节
u8 cmd_genfeature2[7] = {0x01, 0x00, 0x04, 0x02, 0x02, 0x00, 0x09};                     //从图像生成特征并存入CharBuffer2,应答包12字节
u8 cmd_commode[6] = {0x01, 0x00, 0x03, 0x05, 0x00, 0x09};                               //通过CharBuffer1 2 中的特征生成模板,应答包12字节
u8 cmd_storedata[9] = {0x01, 0x00, 0x06, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00};           //存储模板至指定位置,应答包12字节
u8 cmd_search[11] = {0x01, 0x00, 0x08, 0x04, 0x01, 0x00, 0x00, 0x03, 0xe7, 0x00, 0xf8}; //通过CharBuffer1中的特征搜索匹配指纹,应答包16字节
u8 cmd_readfingerprint[9] = {0x01, 0x00, 0x06, 0x07, 0x01, 0x00, 0x00, 0x00, 0x00};     //从flash中读取指定id号的指纹模板数据存入CharBuffer1,应答包12字节
u8 cmd_deletemode[10] = {0x01, 0x00, 0x07, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00};   //删除指定id号的指纹模板,[4][5]是pageId,[8][9]为校验和,应答包12字节
u8 cmd_deleteall[6] = {0x01, 0x00, 0x03, 0x0d, 0x00, 0x11};                             //删除所有指纹模板,应答包12字节
u8 cmd_readmode[9] = {0x01, 0x00, 0x06, 0x07, 0x01, 0x00, 0x00, 0x00, 0x00};            //读出指定pageId的模板到buf1,最后四位分别为页码与校验和,应答包12字节
u8 rec_buf[32] = {0};                                                                   //接收buf

3.2.2 执行相关指令发包函数

/*************************************************************
 * name:        as608_FingerImageGet
 * function:    录入指纹原始图像
 * input:       无
 * return:      无
 *************************************************************/
void as608_FingerImageGet()
{
    while (1)
    {
        while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_9) != 1) //判断是否有手指按上,有手指按上跳出循环继续执行
            ;
        count = 0;
        num = 12;
        usart_send(UART4, cmd_packagehead, sizeof(cmd_packagehead)); //发送包头
        usart_send(UART4, cmd_getimage, sizeof(cmd_getimage));       //发送命令
        while (count != num)
        {
            rt_thread_mdelay(100);
        }
        if (rec_buf[9] != 0x00) //判断应答包确认码是否成功采集
        {
            continue;
        }
        break;
    }
}

/*************************************************************
 * name:        as608_GetFeature
 * function:    将指纹图像提取特征存于对应buf
 * input:       buf_num - buf号码,1或2
 * return:      成功返回0,失败返回对应错码
 *************************************************************/
int as608_GetFeature(int buf_num)
{
    while (1)
    {
        count = 0;
        num = 12;
        usart_send(UART4, cmd_packagehead, sizeof(cmd_packagehead)); //发送包头
        if (buf_num == 1)
        {
            usart_send(UART4, cmd_genfeature1, sizeof(cmd_genfeature1)); //发送命令
        }
        else if (buf_num == 2)
        {
            usart_send(UART4, cmd_genfeature2, sizeof(cmd_genfeature2)); //发送命令
        }
        else
        {
            return -1;
        }
        while (count != num)
        {
            rt_thread_mdelay(100);
        }
        if (rec_buf[9] == 0x00)
        {
            return 0;
        }
        else
        {
            return (int)rec_buf[9];
        }
    }
}

/*************************************************************
 * name:        as608_FingerprintStore
 * function:    存储指纹特征模板
 * input:       无
 * return:      成功返回0,失败返回对应错码
 *************************************************************/
int as608_FingerprintStore()
{
    while (1)
    {
        u8 tmp[4] = {0};
        int t_num = fingerprint_num;

        //判断存储位置是否有模板,修正位置
        while (1)
        {
            u8 t_tmp[4] = {0};
            t_tmp[0] = t_tmp[0] | (u8)(t_num >> 8);
            t_tmp[1] = t_tmp[1] | (u8)t_num;
            int s = 0;
            s = 0x01 + 0x06 + 0x07 + 0x01 + (int)t_tmp[0] + (int)t_tmp[1];
            t_tmp[2] = 0x00 | (u8)(s >> 8);
            t_tmp[3] = 0x00 | (u8)s;

            for (int i = 0; i < 4; i++)
            {
                cmd_readfingerprint[5 + i] = t_tmp[i];
            }
            count = 0;
            num = 12;
            usart_send(UART4, cmd_packagehead, sizeof(cmd_packagehead));         //发送包头
            usart_send(UART4, cmd_readfingerprint, sizeof(cmd_readfingerprint)); //发送命令
            while (count != num)
            {
                rt_thread_mdelay(100);
            }
            if (rec_buf[9] == 0x00) //成功读出代表里面有模板
            {
                t_num++;
                if (t_num == 1000)
                {
                    t_num = 0;
                }
                if (t_num == fingerprint_num) //扫描完全部存储位置仍未找到空位
                {
                    return -RT_ENOMEM;
                }
            }
            else if (rec_buf[9] == 0x0c) //无法读出代表内部无模板
            {
                fingerprint_num = t_num;
                break;
            }
            else
            {
                return -1;
            }
        }

        //计算位置号
        tmp[0] = tmp[0] | (u8)(fingerprint_num >> 8);
        tmp[1] = tmp[1] | (u8)fingerprint_num;

        //计算校验和
        int sum = 0;
        sum = 0x01 + 0x06 * 2 + 0x01 + (int)tmp[0] + (int)tmp[1];
        tmp[2] = tmp[2] | (u8)(sum >> 8);
        tmp[3] = tmp[3] | (u8)sum;

        //修改发送命令包
        int i = 0;
        for (i = 0; i < 4; i++)
        {
            cmd_storedata[5 + i] &= 0x00;   //清零最后四字节
            cmd_storedata[5 + i] |= tmp[i]; //修改
        }
        count = 0;
        num = 12;
        usart_send(UART4, cmd_packagehead, sizeof(cmd_packagehead)); //发送包头
        usart_send(UART4, cmd_storedata, sizeof(cmd_storedata));     //发送命令
        while (count != num)
        {
            rt_thread_mdelay(100);
        }
        if (rec_buf[9] == 0x00) //存储完成返回0,修正指纹序号
        {
            fingerprint_num++;
            if (fingerprint_num == 1000)
            {
                fingerprint_num = 0;
            }
            return 0;
        }
        else if (rec_buf[9] == 0x01)
        {
            continue;
        }
        else
        {
            return (int)rec_buf[9];
        }
    }
}

/*************************************************************
 * name:        as608_deleteall
 * function:    删除所有as608指纹模板
 * input:       无
 * return:      成功返回0,失败返回-1
 *************************************************************/
int as608_deleteall()
{
    for (int i = 1; i < 10; i++)
    {
        count = 0;
        num = 12;
        usart_send(UART4, cmd_packagehead, sizeof(cmd_packagehead)); //发送包头
        usart_send(UART4, cmd_deleteall, sizeof(cmd_deleteall));     //发送命令
        while (count != num)
        {
            rt_thread_mdelay(100);
        }
        if (rec_buf[9] == 0x00)
        {
            return 0;
        }
    }
    return -1;
}

/*************************************************************
 * name:        as608_delete
 * function:    删除as608内指定的指纹模板
 * input:       num - 指纹模板序号(0-999)
 * return:      成功返回0,失败返回-1
 *************************************************************/
int as608_delete(int num)
{
    int tmp[4] = {0};

    //计算位置号
    tmp[0] = tmp[0] | (u8)(num >> 8);
    tmp[1] = tmp[1] | (u8)num;

    //计算校验和
    int sum = 0;
    sum = 0x01 + 0x07 + 0x0c + 0x01 + (int)tmp[0] + (int)tmp[1];
    tmp[2] = tmp[2] | (u8)(sum >> 8);
    tmp[3] = tmp[3] | (u8)sum;

    //修正命令
    cmd_deletemode[4] = tmp[0];
    cmd_deletemode[5] = tmp[1];
    cmd_deletemode[8] = tmp[2];
    cmd_deletemode[9] = tmp[3];

    count = 0;
    num = 12;
    usart_send(UART4, cmd_packagehead, sizeof(cmd_packagehead)); //发送包头
    usart_send(UART4, cmd_deletemode, sizeof(cmd_deletemode));   //发送命令
    while (count != num)
    {
        rt_thread_mdelay(100);
    }
    if (rec_buf[9] == 0x00)
    {
        return 0;
    }
    else
    {
        return -1;
    }
}

3.2.3 指纹采集流程

1.录入图像:	PS_GetImage
	功能:	探测手指他,探测到后录入指纹图像存于ImageBuffer,返回确认码;
	发送指令包格式;
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x01
		包长度:0x00 0x03
		指令码:0x01
		校验和:0x00 0x05
		{0xef, 0x01, 0xff, 0xff, 0xff, 0xff}
		{0x01, 0x00, 0x03, 0x01, 0x00, 0x05}
	应答包格式:12字节
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x07
		包长度:0x00 0x03
		确认码:0x** (0x00录入成功 0x01收包有错 0x02无手指 0x03录入不成功)
		校验和:0x** 0x**(sum)
		
2.生成特征:	PS_GenChar
	功能:将 ImageBuffer 中的原始图像生成指纹特征文件存于 CharBuffer1 或 CharBuffer2;
	发送指令包格式;
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x01
		包长度:0x00 0x04
		指令码:0x02
		缓冲区号:0x01(CharBuffer1) / 0x02(CharBuffer2)
		校验和:0x00 0x08(CharBuffer1) / 0x09(CharBuffer2)
		{0x01, 0x00, 0x04, 0x02, 0x01, 0x00, 0x08}
		{0x01, 0x00, 0x04, 0x02, 0x02, 0x00, 0x09}
	应答包格式;12字节
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x07
		包长度:0x00 0x03
		确认码:0x** (0x00生成特征成功 0x01收包有错 0x06指纹图像太乱生不成特征 0x07指纹图像正常,特征点太少生不成特征 0x15图像缓冲区内无有效原始图)
		校验和:0x** 0x**(sum)
		
3.合并特征(生成模板):	PS_RegModel
	功能:将CharBuffer1与CharBuffer2中的特征文件合并成模板,结果存于CharBuffer1与CharBuffer2;
	发送指令包格式;
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x01
		包长度:0x00 0x03
		指令码:0x05
		校验和;0x00 0x09
		{0x01, 0x00, 0x03, 0x05, 0x00, 0x09}
	应答包格式:12字节
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x07
		包长度:0x00 0x03
		确认码:0x**(0x00合并成功 0x01收包有误 0x0a合并失败)
		校验和:0x** 0x**(sum)
		
4.存储模板:	PS_StoreChar
	功能:将CharBuffer1与CharBuffer2中的模板文件存到PageID号flash数据库位置;
	指令包格式:
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x01
		包长度:0x00 0x06
		指令码:0x06
		缓冲区号:0x01 / 0x02
		位置号:0x** 0x** (0-999自行转换)
		校验和:(sum 2bytes 自行相加)
		{0x01, 0x00, 0x06, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00}
	应答包格式:12字节
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x07
		包长度:0x00 0x03
		确认码:0x**(0x00存储成功 0x01收包有误 0x0bPageID超出范围 0x18写flash出错)
		校验和:0x** 0x**(sum)

​ 相关代码如下:

/*************************************************************
 * name:        as608_FingerprintEntry
 * function:    录入指纹
 * input:       无
 * return:      成功返回0,失败返回对应错码
 *************************************************************/
int as608_FingerprintEntry()
{
    //第一次采集指纹特征
    while (1)
    {
        //录入图像
        rt_kprintf("please put your finger on it.[1]\n");
        as608_FingerImageGet();

        //生成特征存于CharBuffer1
        int ret = as608_GetFeature(1);
        if (ret == 0)
        {
            break;
        }
        else if (ret == 0x01)
        {
            continue;
        }
        else
        {
            return ret;
        }
    }
    rt_kprintf("please release your finger.\n");
    while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_9) == 1) //判断手指是否松开,松开跳出循环
        ;

    //第二次采集指纹特征
    while (1)
    {
        //录入图像
        rt_kprintf("please put your finger on it.[2]\n");
        as608_FingerImageGet();

        //生成特征存于CharBuffer2
        int ret = as608_GetFeature(2);
        if (ret == 0)
        {
            break;
        }
        else if (ret == 0x01)
        {
            continue;
        }
        else
        {
            return ret;
        }
    }

    //合并特征生成模板
    while (1)
    {
        count = 0;
        num = 12;
        usart_send(UART4, cmd_packagehead, sizeof(cmd_packagehead)); //发送包头
        usart_send(UART4, cmd_commode, sizeof(cmd_commode));         //发送命令
        while (count != num)
        {
            rt_thread_mdelay(100);
        }
        if (rec_buf[9] != 0x00)
        {
            if (rec_buf[9] == 0x0a)
            {
                return 0x0a;
            }
            continue;
        }
        break;
    }

    //存储模板
    while (1)
    {
        int ret = as608_FingerprintStore();
        return ret;
    }
}

3.2.4 检测指纹流程

1.录入图像:	PS_GetImage
	功能:	探测手指他,探测到后录入指纹图像存于ImageBuffer,返回确认码;
	发送指令包格式;
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x01
		包长度:0x00 0x03
		指令码:0x01
		校验和:0x00 0x05
	应答包格式:
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x07
		包长度:0x00 0x03
		确认码:0x** (0x00录入成功 0x01收包有错 0x02无手指 0x03录入不成功)
		校验和:0x** 0x**(sum)
		
2.生成特征:	PS_GenChar
	功能:将 ImageBuffer 中的原始图像生成指纹特征文件存于 CharBuffer1 或 CharBuffer2;
	发送指令包格式;
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x01
		包长度:0x00 0x04
		指令码:0x02
		缓冲区号:0x01(CharBuffer1) 0x02(CharBuffer2)
		校验和:0x08(CharBuffer1) / 0x09(CharBuffer2)
	应答包格式;
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x07
		包长度:0x00 0x03
		确认码:0x** (0x00生成特征成功 0x01收包有错 0x06指纹图像太乱生不成特征 0x07指纹图像正常,特征点太少生不成特征 0x15图像缓冲区内无有效原始图)
		校验和:0x** 0x**(sum)
		
3.搜索指纹:	PS_Search
	功能:以 CharBuffer1 或 CharBuffer2 中的特征文件搜索整个或部分指纹库,搜索到则返回页码;
	指令包:
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x01
		包长度:0x00 0x08
		指令码:0x04
		缓冲区号:0x01 / 0x02
		起始页码:0x00 0x00
		搜索页数:0x03 0xe7(0~999)
		校验和:0x00 0xf8 / 0xf9
		{0x01, 0x00, 0x08, 0x04, 0x01, 0x00, 0x00, 0x03, 0xe7, 0x00, 0xf8}
	应答包:
		包头:0xef 0x01
		芯片地址:0xff 0xff 0xff 0xff
		包标识:0x07
		包长度:0x00 0x07
		确认码:0x**(0x00搜索到 0x01收包有误 0x09没搜索到)
		页码:0x** 0x**
		得分:0x** 0x**
		校验和:0x** 0x**(sum)

​ 相关代码如下:

/*************************************************************
 * name:        as608_FingerprintCheck
 * function:    匹配指纹
 * input:       无
 * return:      成功匹配返回0,失败返回对应错码,0x09表示未搜索到
 *************************************************************/
int as608_FingerprintCheck()
{
    //指纹图像采集
    while (1)
    {
        //录入图像
        as608_FingerImageGet();

        //生成特征存于CharBuffer1
        int ret = as608_GetFeature(1);
        if (ret == 0)
        {
            break;
        }
        else if (ret == 0x01)
        {
            continue;
        }
        else
        {
            return ret;
        }
    }

    //查找buf1中的特征匹配指纹
    while (1)
    {
        int cnt = 0;
        count = 0;
        num = 16;
        usart_send(UART4, cmd_packagehead, sizeof(cmd_packagehead)); //发送包头
        usart_send(UART4, cmd_search, sizeof(cmd_search));           //发送命令
        while (count != num)
        {
            rt_thread_mdelay(100);
        }
        if (rec_buf[9] == 0x00) //查找到返回0
        {
            return 0;
        }
        else if (rec_buf[9] == 0x09) //未查找到返回错码
        {
            return 0x09;
        }
        else
        {
            cnt++;
            if (cnt <= 10)
            {
                continue;
            }
            return 0x01;
        }
    }
}

3.2.5 相关执行线程函数

/*************************************************************
 * name:        thread_fingerscan_func
 * function:    手指扫描线程执行函数
 * input:       parameter - 线程创建时传递的参数
 * return:      无
 *************************************************************/
void thread_fingerscan_func(void *parameter)
{
    int num_touch = 0;
    while (1)
    {
        if (as608_FingerprintCheck() == 0)
        {
            num_touch = 0;
            rt_kprintf("fingerprint has been founded!\n");
            led_crtl(1, 0);
            rt_kprintf("open the door...\n");
            mg90s_ctrl(OPEN_DOOR);
            esp8266_send("door:1 ", rt_strlen("door:1 "));
            rt_thread_mdelay(1000);
            led_crtl(1, 1);
            rt_thread_mdelay(5000);
            rt_kprintf("close the door...\n");
            mg90s_ctrl(CLOSE_DOOR);
            esp8266_send("door:0 ", rt_strlen("door:0 "));
        }
        else
        {
            num_touch++;
            rt_kprintf("fingerprint is not been stored!\n");
            mg90s_ctrl(CLOSE_DOOR);
            esp8266_send("door:0 ", rt_strlen("door:0 "));
            led_crtl(2, 0);
            rt_thread_mdelay(1000);
            led_crtl(2, 1);
            if (num_touch == 10)
            {
                esp8266_send("warn:1\n", rt_strlen("warn:1\n"));
                num_touch = 0;
            }
        }
        rt_kprintf("please release your finger.\n");
        while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_9) == 1) //判断手指是否松开,松开跳出循环
            ;
    }
}

/*************************************************************
 * name:        thread_fingerprintstore_func
 * function:    指纹录入线程执行函数
 * input:       parameter - 线程创建时传递的参数
 * return:      无
 *************************************************************/
void thread_fingerprintstore_func(void *parameter)
{
    while (1)
    {
        rt_kprintf("storethread start...\n");
        rt_sem_take(sem_sc2st, RT_WAITING_FOREVER);
        rt_thread_suspend(finger_scan);
        rt_kprintf("storing your finger...\n");
        if (as608_FingerprintEntry() == 0)
        {
            rt_kprintf("fingerprint has been stored!\n");
            led_crtl(1, 0);
            rt_thread_mdelay(1000);
            led_crtl(1, 1);
        }
        rt_thread_resume(finger_scan);
    }
}

3.3 蓝牙模块相关代码

//串口中断服务程序
void UART5_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void UART5_IRQHandler(void)
{
    // rt_kprintf("uart5irq...\n");
    static int i = 0;
    if (USART_GetITStatus(UART5, USART_IT_RXNE) == SET)
    {
        blue_recbuf[i++] = USART_ReceiveData(UART5);
        if (blue_recbuf[0] != 0xef)
        {
            i = 0;
        }
    }
    if (i == 3)
    {
        i = 0;
        flag = 1;
        rt_kprintf("flag has been set!\n");
    }
}

/*************************************************************
 * name:        thread_bluetoothscan_func
 * function:    蓝牙接收串口数据中断判定线程,若接收数据满3字节则
 *              唤醒处理线程
 * input:       parameter - 线程创建时传递的参数
 * return:      无
 *************************************************************/
void thread_bluetoothscan_func(void *parameter)
{
    while (1)
    {
        if (flag == 1)
        {
            rt_kprintf("%d,%d,%d\n", blue_recbuf[0], blue_recbuf[1], blue_recbuf[2]);
            flag = 0;
            rt_kprintf("sem_bthrec release\n");
            rt_sem_release(sem_bthrec);
        }
        rt_thread_mdelay(1000);
    }
}

/*************************************************************
 * name:        thread_bluetoothrec_func
 * function:    蓝牙接收中断处理线程
 * input:       parameter - 线程创建时传递的参数
 * return:      无
 *************************************************************/
void thread_bluetoothrec_func(void *parameter)
{
    u8 err[3] = {0xef, 0xee, 0x0a};
    while (1)
    {
        rt_kprintf("bluetoothrec_start\n");
        rt_sem_take(sem_bthrec, RT_WAITING_FOREVER);
        rt_kprintf("bluetoothrec take\n");
        if (blue_recbuf[0] != 0xef || blue_recbuf[2] != 0x0a)
        {
            rt_kprintf("NONE\n");
            continue;
        }
        else if (blue_recbuf[0] == 0xef && blue_recbuf[2] == 0x0a)
        {
            rt_kprintf("y\n");
            switch ((int)blue_recbuf[1])
            {
            case 0x01:
                rt_sem_release(sem_sc2st); // 0x01使能指纹录入线程
                break;
            case 0x02: //删除所有指纹模板
                if (as608_deleteall() == 0)
                {
                    rt_kprintf("fingerprints in as608 has been all deleted!\n");
                }
                else
                {
                    rt_kprintf("delete failed...\n");
                    for (int i = 0; i < 3; i++)
                    {
                        while (USART_GetFlagStatus(UART5, USART_FLAG_TXE) == RESET)
                            ;
                        USART_SendData(UART5, err[i]);
                    }
                }
                break;
            case 0x03:
                rt_kprintf("close the door...\n");
                mg90s_ctrl(CLOSE_DOOR);
                esp8266_send("door:0 ", rt_strlen("door:0 "));
                break;
            case 0x04:
                rt_kprintf("open the door...\n");
                mg90s_ctrl(OPEN_DOOR);
                esp8266_send("door:1 ", rt_strlen("door:1 "));
                break;
            case 0x05: //更改密码
                rt_kprintf("release sem_keych...\n");
                rt_sem_release(sem_keych);
                break;
            default:
                rt_kprintf("bluetooth command error...\n");
                for (int i = 0; i < 3; i++)
                {
                    while (USART_GetFlagStatus(UART5, USART_FLAG_TXE) == RESET)
                        ;
                    USART_SendData(UART5, err[i]);
                }
                break;
            }
        }
    }
}

3.4 ESP8266-01 WiFi模块相关代码

3.4.1 发送函数

/*************************************************************
 * name:        esp8266_send
 * function:    esp8266发送函数,通过esp8266发送信息给服务器
 * input:       data - 字符型指针,指向发送的数据数组
 *              data_size - 发送数据长度
 * return:      无
 *************************************************************/
void esp8266_send(char *data, int data_size)
{
    char cmd_send[32] = "AT+CIPSEND=";
    char num[8] = {0};
    int tmp = data_size;
    int i = 0;
    rt_kprintf("sending...\n");
    if (data_size <= 0)
    {
        rt_kprintf("can't send <=0 data...\n");
        return;
    }
    while (tmp != 0)
    {
        num[i] = (tmp % 10) + 48; //解析data_size转换成ascii
        tmp /= 10;
        i++;
    }
    tmp = i - 1; //定位最高位

    //组包
    i = 11;
    for (; tmp >= 0; tmp--)
    {
        cmd_send[i++] = num[tmp];
    }
    cmd_send[i++] = '\r';
    cmd_send[i++] = '\n';
    cmd_send[i] = '\0';
    int sum = rt_strlen(cmd_send);
    for (i = 0; i < sum; i++)
    {
        while (USART_GetFlagStatus(UART6, USART_FLAG_TXE) == RESET)
            ;
        USART_SendData(UART6, cmd_send[i]);
    }
    //rt_kprintf("%s\n", cmd_send);
    rt_thread_mdelay(10);
    //发送数据
    for (i = 0; i < data_size; i++)
    {
        while (USART_GetFlagStatus(UART6, USART_FLAG_TXE) == RESET)
            ;
        USART_SendData(UART6, data[i]);
    }

    rt_kprintf("send finished!\n");
}

3.4.2 相关接收处理线程

/*************************************************************
 * name:        thread_connectrecscan_func
 * function:    esp8266接收扫描线程
 * input:       parameter - 线程创建时传递的参数
 * return:      无
 *************************************************************/
void thread_connectrecscan_func(void *parameter)
{
    while (1)
    {
        if (rec_flag == 1)
        {
            connect_scan_flag = 1;
            rt_thread_mdelay(2000);
            if (connect_scan_flag == 1)
            {
                rt_kprintf("release sem_esprec...\n");
                rt_sem_release(sem_esprec);
            }
        }
        rt_thread_mdelay(1000);
    }
}

/*************************************************************
 * name:        thread_connectdatadeal_func
 * function:    esp8266接收数据处理线程
 * input:       parameter - 线程创建时传递的参数
 * return:      无
 *************************************************************/
void thread_connectdatadeal_func(void *parameter)
{
    while (1)
    {
        rt_kprintf("connectrecdeal start...\n");
        rt_sem_take(sem_esprec, RT_WAITING_FOREVER);
        rt_kprintf("sem_esprec take...\n");
        int i = 0;
        if (recbuf[0] == '\r' && recbuf[1] == '\n' && recbuf[2] == '+' && recbuf[3] == 'I' && recbuf[4] == 'P' && recbuf[5] == 'D' && recbuf[6] == ',')
        {
            //判断为接收了数据
            i = 7;
            char tmp[8] = {0};
            char data[64] = {0};
            int n = 0, j = 0, sum = 0;
            //解析数据长度
            while (recbuf[i] != ':')
            {
                tmp[i - 7] = recbuf[i];
                i++;
                n++;
            }
            for (j = 0; j < n; j++)
            {
                sum = sum * 10 + (int)tmp[j] - 48;
            }

            //接收数据
            i++;
            for (j = 0; j < sum; j++)
            {
                data[j] = recbuf[i];
                i++;
            }
            data[j] = '\0'; //结尾加上\0方便比较
            rt_kprintf("%s\n", data);
            //对比命令
            if (rt_strcmp(data, "open door") == 0)
            {
                mg90s_ctrl(OPEN_DOOR);
                esp8266_send("door:1 ", rt_strlen("door:1 "));
            }
            else if (rt_strcmp(data, "close door") == 0)
            {
                mg90s_ctrl(CLOSE_DOOR);
                esp8266_send("door:0 ", rt_strlen("door:0 "));
            }
            else if (rt_strcmp(data, "connected") == 0)
            {
                rt_kprintf("connected to the server!\n");
            }
            else if (rt_strcmp(data, "clear all fingerprints") == 0)
            {
                if (as608_deleteall() == 0)
                {
                    rt_kprintf("all fingerprints has been deleted!\n");
                }
                else
                {
                    rt_kprintf("can't delete...\n");
                }
            }
            else if (rt_strcmp(data, "record fingerprint") == 0)
            {
                rt_sem_release(sem_sc2st);
            }
            else if (rt_strcmp(data, "door:1 ") == 0)
            {
                mg90s_ctrl(OPEN_DOOR);
            }
            else if (rt_strcmp(data, "door:0 ") == 0)
            {
                mg90s_ctrl(CLOSE_DOOR);
            }
            else if (rt_strcmp(data, "change password") == 0)
            {
                rt_kprintf("release sem_keych...\n");
                rt_sem_release(sem_keych);
            }
            else if (data[0] == 'p' && data[1] == 'a' && data[2] == 's' && data[3] == 's' && data[4] == 'w' && data[5] == 'o' && data[6] == 'r' && data[7] == 'd')
            {
                for (i = 0; i < 6; i++)
                {
                    key_data[i] = data[i + 9];
                }
                key_data[7] = 0;
            }
        }
        else
        {
            //rt_kprintf("%s\n", recbuf);
        }
        rec_flag = 0;
        cnt = 0;
        for (i = 0; i < 128; i++)
        {
            recbuf[i] = 0;
        }
    }
}

四、github仓库

     rt_kprintf("all fingerprints has been deleted!\n");
            }
            else
            {
                rt_kprintf("can't delete...\n");
            }
        }
        else if (rt_strcmp(data, "record fingerprint") == 0)
        {
            rt_sem_release(sem_sc2st);
        }
        else if (rt_strcmp(data, "door:1 ") == 0)
        {
            mg90s_ctrl(OPEN_DOOR);
        }
        else if (rt_strcmp(data, "door:0 ") == 0)
        {
            mg90s_ctrl(CLOSE_DOOR);
        }
        else if (rt_strcmp(data, "change password") == 0)
        {
            rt_kprintf("release sem_keych...\n");
            rt_sem_release(sem_keych);
        }
        else if (data[0] == 'p' && data[1] == 'a' && data[2] == 's' && data[3] == 's' && data[4] == 'w' && data[5] == 'o' && data[6] == 'r' && data[7] == 'd')
        {
            for (i = 0; i < 6; i++)
            {
                key_data[i] = data[i + 9];
            }
            key_data[7] = 0;
        }
    }
    else
    {
        //rt_kprintf("%s\n", recbuf);
    }
    rec_flag = 0;
    cnt = 0;
    for (i = 0; i < 128; i++)
    {
        recbuf[i] = 0;
    }
}

}


# 四、github仓库

​		所有代码及核心板PCB设计位于:https://github.com/lcready/zhinengshuo
  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
计算机组成与设计是一门学科,专门研究计算机的硬件组成和设计原理。而基于RISC-V架构的计算机组成与设计则是以RISC-V(Reduced Instruction Set Computer - V)指令集架构为基础的计算机硬件设计课程。 RISC-V是一种开源的指令集架构,它具有精简、清晰的特点,便于教学和研究使用。在计算机组成与设计课程中,基于RISC-V架构进行教学有以下优点: 首先,RISC-V是一个相对简单的指令集架构,具有较小的指令集和规范。学生能够更容易理解和分析RISC-V的指令和硬件设计,从而更好地掌握计算机组成原理。 其次,RISC-V具有开源的特点,学生可以更加方便地获取RISC-V的指令集和参考实现,并可以进行自主的实践和创新。这有利于培养学生的实践能力和创新意识。 再次,基于RISC-V架构进行计算机组成与设计的教学可以培养学生的系统思维和综合能力。学生需要理解和设计RISC-V的处理器结构、指令流水线、存储体系结构等多个硬件模块,从而能够综合运用所学知识进行高效的系统设计。 最后,基于RISC-V架构的计算机组成与设计课程还能更好地引导学生学习并掌握现代计算机体系结构的原理和设计方法。通过学习和实践,学生能够理解计算机的指令执行原理、流水线设计、超标量和乱序执行等先进的计算机体系结构。 综上所述,基于RISC-V架构的计算机组成与设计课程可以提供一个开放、简洁和实践的教学环境,有助于培养学生的硬件设计能力、系统思维和创新意识。这门课程对于计算机专业学生的专业知识学习和综合能力培养具有重要的意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值