基于STM32智能马蹄锁设计

1. 前言

设计一款基于单片机的家用马蹄锁,主要用在自行车、共享单车上。

主要要求:

1、手机蓝牙直连控制开关:手机直接连接智能马蹄锁可以控制其开锁关锁的功能。

2、即连即开: 手机端与智能马蹄锁第一次连接成功后可以选择即连即开模式,在之后距离智能马蹄锁一定范围内,智能马蹄锁会自动打开。

3、即走即关:手机端与智能马蹄锁第一次连接成功后可以选择即连即走即关,在之后离开智能马蹄锁一定范围,智能马蹄锁会自动关团。

4、实体触控按键控制开锁手动关锁。当用户无法使用蓝牙功能时,可以使用侧面实体触控按键进行密码解锁。

5、GPRS防盗预警当自行车在智能马蹄锁住的的情况下三轴加速度传感器检测到车辆被长时间移动时会向用户手机端发送预警。

技术总结:

(1)单片机采用STM32F103RCT6

(2)设计一款Android手机APP,支持连接马蹄锁进行无线开锁。

(3)采用5V继电器模块模拟马蹄锁的锁开关。

(4)通过马蹄锁上的密码按钮实现密码开锁。–采用矩阵电容按键代替

(5)采用三轴加速度传感器检测ADXL345检测车辆在关锁的情况下,是否被移动,实现报警检测。

(6)如果车辆异常移动,通过GPRS向用户发送报警短信,提醒用户车辆可能被盗

(7)蓝牙断开自动上锁

image-20220505154949874

image-20220505155020088

image-20220506005501912

如果需要源码可以从这里下载:
https://download.csdn.net/download/xiaolong1126626497/85897821

演示视频:
【基于STM32设计的自行车防盗智能锁】 https://www.bilibili.com/video/BV1hT4y1B7aF?share_source=copy_web&vd_source=347136f3e32fe297fc17177194ce0a8b

2. 硬件选型

2.1 继电器

image-20220430220022202

2.2 电容键盘

image-20220430220134160

2.3 加速度计传感器

ADXL345是一款小尺寸、薄型、低功耗、完整的三轴加速度计,提供经过信号调理的电压输出。

说明:CS接高电平则选择IIC通信,反之则SPI通信。SDO(地址引脚)接高电平,根据手册器件的7位I2C地址是0x1D,后面跟上读取/写位(R/W),则写寄存器为0x3A,读寄存器为0x3B;接低电平,则7位I2C地址是0x53,同理,跟上读写标志位后写寄存器为0xA6,读寄存器为0xA7;

image-20220430220306841

2.4 STM32开发板

STM32F103RCT6的芯体规格是32位,速度是72MHz,程序存储器容量是256KB,程序存储器类型是FLASH,RAM容量是48K。

2.5 PCB洞洞板

image-20220411155607391

2.6 BLE低功耗蓝牙模块

image-20220421132832091

2.7 母对母杜邦线

image-20220411155302747

2.8 SIM800C

image-20220430220908634

模块特点:

1、支持极限DC5V-18V宽电压输入

2、有电源使能开关引脚EN

3、支持锂电池供电接口VBAT3.5-4.5V

4、输入支持移动和联通手机卡Micro SIM卡

5、送51/STM32/ARDUINO驱动例程

image-20220416115302900

1、DC 5V-18V电源输入,推荐使用DC 9V

2、电源开始使能引脚默认使能

3、电源地

4、GSM模块的TXD引脚接其它模块的RXD

5、GSM模块的RXD引脚接其它模块的TXD

6、数据终端准备

7、内核音频输出引脚

8、内核音频输出引脚

9、锂电池输入引脚,DC 3.5 - 4.5V

10、电源地

11、启动引脚和GND短路可实现开机自启动

12、电源地

13、RTC外置电池引脚

14、内核振铃提示引脚

15、内合音频输入引脚

16、内核音频输入引脚

加粗的引脚一般都用到。

image-20220416120423490

建议使用V_IN单独供电DC5-18V输入(推荐使用9V),或者VBAT供电锂电池两种供电方式这两种供电方式最稳定。如果只是简单调试,也可使用USB-TTL或者开发板的5V直接给模块供电。不过一般电脑或者开发板的功率有限,可能会不稳定。请根据具体情况自己取舍选择合适电源。

总结:

模块本身支持自适应波特率,可以自动根据发送过去的指令计算对应的波特率,一般使用115200即可。

模块调试总结:

(1)供电电压5V也可以,采用电脑USB供电(直接插电脑USB口)。正常供电之后,模块上有电源指示灯。

(2)SIM800C的TX脚接单片机的RX脚

(3)SIM800C的RX脚接单片机的TX脚

(4)SIM800C的第11个引脚(PWK)和12个引脚(GND)短接接在一起,才可以开机。

image-20220416121804182

电源正常后,右上角有一个黄色的电源灯。

image-20220416121820844

2.9 SIM卡套

image-20220430220947832

2.10 蜂鸣器

image-20220430221037415

3. 手机APP软件设计

3.1 通信说明

上位机与设备之间通过BLE低功耗串口蓝牙进行通信,手机AP下发open_lock和close_lock实现关锁开锁。

open_lock
close_lock

3.2 搭建开发环境

上位机软件采用Qt框架设计,Qt是一个跨平台的C++图形用户界面应用程序框架。Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。简单来说,QT可以很轻松的帮你做带界面的软件,甚至不需要你投入很大精力。

QT官网: https://www.qt.io/

image-20220314143105032

QT学习入门实战专栏文章: https://blog.csdn.net/xiaolong1126626497/category_11400392.html

QT5.12.6的下载地址:
https://download.qt.io/archive/qt/5.12/5.12.6/

打开下载链接后选择下面的版本进行下载:

qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details

软件安装时断网安装,否则会提示输入账户。

安装的时候,第一个复选框里勾选一个mingw 32编译器即可,其他的不管默认就行,直接点击下一步继续安装。

image-20220417145923643

选择MinGW 32-bit 编译器:

image-20220417150002770

选择MinGW 32-bit 编译器:

image-20220417150031913

安装好之后,将源码工程复制到英文路径下,双击工程文件打开。

image-20220505150930661

工程打开之后,点击左下角的绿色三角形按钮即可编译运行。

image-20220505150856647

运行效果如下:

image-20220505151110408

注意:因为QT的BLE蓝牙接口不适用于windows系统。这里在windows上运行只是方便看代码和整体软件UI效果,要实际运行功能演示,需要安装在Android手机上进行测试。

3.3 手机上运行效果

image-20220506005352364

4. STM32程序设计

4.1 硬件接线

硬件连接方式:
1. TFT 1.44 寸彩屏接线
GND   电源地
VCC   接5V或3.3v电源
SCL   接PC8(SCL)
SDA   接PC9(SDA)
RST   接PC10
DC    接PB7
CS    接PB8
BL	  接PB11

2. 蜂鸣器
VCC--->3.3V
DAT--->PA6
GND--->GND

3. 继电器模块
VCC--->3.3V
GND--->GND
DAT--->PA4

4. 板载LED灯接线
LED1---PA8
LED2---PD2

5. 板载按键接线
K0---PA0 
K1---PC5 
K2---PA15

6. SIM800C--GSM模块
GND----GND
VCC--->5V
PA2----SIM800C_RXD
PA3----SIM800C_TXD

7. BLE低功耗蓝牙模块
PB10(TX)--RXD 模块接收脚
PB11(RX)--TXD 模块发送脚
PB12-----输入引脚,检测模块是否连接或者断开
GND---GND 地
VCC---VCC 电源(3.3V)

8. 触摸按键使用TTP229型号的驱动芯片
SCL接----------->PC12
SDA-OUT接------->PC13
电源接---------->VCC-3.3
GND接----------->GND

9. ADXL345三轴加速度计
IIC_SDA IIC 通信数据线     -->PB6
IIC_SCL IIC 通信时钟线	   -->PB7
电源接---------->VCC-3.3
GND接----------->GND

4.2 系统原理图

image-20220506011147350

4.3 keil工程

image-20220506003058220

image-20220506011257413

4.4 汉字取模

image-20220505170512409

4.5 main.c代码

#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include <string.h>
#include "timer.h"
#include "oled.h"
#include <string.h>
#include <stdlib.h>
#include "font.h"
#include "sim800c.h"
#include "app.h"

/*
(1)单片机采用STM32F103RCT6

(2)设计一款Android手机APP,支持连接马蹄锁进行无线开锁。

(3)采用5V继电器模块模拟马蹄锁的锁开关。

(4)通过马蹄锁上的密码按钮实现密码开锁。--采用矩阵电容按键代替

(5)采用三轴加速度传感器检测ADXL345检测车辆在关锁的情况下,是否被移动,实现报警检测。

(6)如果车辆异常移动,通过GPRS向用户发送报警短信,提醒用户车辆可能被盗

(7)蓝牙断开自动上锁

*/


/*
硬件连接方式:
1. TFT 1.44 寸彩屏接线
GND   电源地
VCC   接5V或3.3v电源
SCL   接PC8(SCL)
SDA   接PC9(SDA)
RST   接PC10
DC    接PB7
CS    接PB8
BL	  接PB11

2. 蜂鸣器
VCC--->3.3V
DAT--->PA6
GND--->GND

3. 继电器模块
VCC--->3.3V
GND--->GND
DAT--->PA4

4. 板载LED灯接线
LED1---PA8
LED2---PD2

5. 板载按键接线
K0---PA0 
K1---PC5 
K2---PA15

6. SIM800C--GSM模块
GND----GND
VCC--->5V
PA2----SIM800C_RXD
PA3----SIM800C_TXD

7. BLE低功耗蓝牙模块
PB10(TX)--RXD 模块接收脚
PB11(RX)--TXD 模块发送脚
PB12-----输入引脚,检测模块是否连接或者断开
GND---GND 地
VCC---VCC 电源(3.3V)

8. 触摸按键使用TTP229型号的驱动芯片
SCL接----------->PC12
SDA-OUT接------->PC13
电源接---------->VCC-3.3
GND接----------->GND

9. ADXL345三轴加速度计
IIC_SDA IIC 通信数据线     -->PB6
IIC_SCL IIC 通信时钟线	   -->PB7
电源接---------->VCC-3.3
GND接----------->GND
*/


//JTAG模式设置,用于设置JTAG的模式
//mode:jtag,swd模式设置;00,全使能;01,使能SWD;10,全关闭;	   
#define JTAG_SWD_DISABLE   0X02
#define SWD_ENABLE         0X01
#define JTAG_SWD_ENABLE    0X00		  
void JTAG_Set(u8 mode)
{
    
    
	u32 temp;
	temp=mode;
	temp<<=25;
	RCC->APB2ENR|=1<<0;     //开启辅助时钟	   
	AFIO->MAPR&=0XF8FFFFFF; //清除MAPR的[26:24]
	AFIO->MAPR|=temp;       //设置jtag模式
}


char pass_show_temp_buff[100]={
    
    "\0"}; //显示密码
char pass_show_buff[100]={
    
    "\0"}; //显示 * 号
int pass_show_cnt=0;

//限制密码拼接长度
void my_strcat(char *s)
{
    
    
   // 1表示密码
    if(strlen(pass_show_buff)<10)
     {
    
    
       strcat(pass_show_buff,"*");
       strcat(pass_show_temp_buff,s);
     } 
}

//删除密码最后一位
void delete_ending_char(char *p)
{
    
    
    int len=strlen(p);
    if(len>0)
    {
    
    
        p=p+len-1;
        *p='\0';
    }
}


char data_buff[100];
short ADXL345_x;
short ADXL345_y;
short ADXL345_z;
//车辆被盗提示
u8 sim800c_buff[100]="Warning, vehicle may be stolen";
u8 adxl_cnt=0; //记录运动的次数

int main()
{
    
    
    u8 devid;
    u8 state=0;
    u8 key=0;
    u32 time_cnt=0;
    u32 time_cnt2=0;
    u32 time_cnt3=0; //输入密码计时
    u8 open_lock_cnt=0; //开锁失败次数
    u32 i=0;
    JTAG_Set(SWD_ENABLE); //释放PA15
    LED_Init();  //LED灯初始化
    BEEP_Init(); //蜂鸣器初始化
    KEY_Init();  //按键初始化
    Hardware_Init(); //继电器初始化--模拟锁的开关
    USART1_Init(115200); //串口1初始化-打印调试信息
    Lcd_Init();    //LCD初始化
    Lcd_Clear(0);  //清屏为黑色
    Touch_Configuration(); //触摸屏初始化
    LCD_LED_SET;   //通过IO控制背光亮		
    TIMER2_Init(72,20000);  //辅助串口2接收,超时时间为20ms
    USART2_Init(115200);    //SIM800C-可能的波特率(测试):  57600 、9600、115200 
    USART3_Init(9600);      //串口-BLE低功耗蓝牙模块
    TIMER3_Init(36,20000);  //超时时间20ms
    
    //提示
    LCD_ShowChineseFont(0+10,16*0,16,HZ_FONT_16[0],RED,0);
    LCD_ShowChineseFont(16+10,16*0,16,HZ_FONT_16[1],RED,0);
    LCD_ShowChineseFont(16*2+10,16*0,16,HZ_FONT_16[2],RED,0);
    LCD_ShowChineseFont(16*3+10,16*0,16,HZ_FONT_16[3],RED,0);
    LCD_ShowChineseFont(16*4+10,16*0,16,HZ_FONT_16[4],RED,0);
    LCD_ShowChineseFont(16*5+10,16*0,16,HZ_FONT_16[5],RED,0);
    LCD_ShowChineseFont(16*6+10,16*0,16,HZ_FONT_16[6],RED,0);
    
    //锁状态
    LCD_ShowChineseFont(16*0,16*2,16,HZ_FONT_16[7],WHITE,0);
    LCD_ShowChineseFont(16*1,16*2,16,HZ_FONT_16[8],WHITE,0);
    LCD_ShowChineseFont(16*2,16*2,16,HZ_FONT_16[9],WHITE,0);
    //开启或者关闭
    LCD_ShowChineseFont(16*5,16*2,16,HZ_FONT_16[12],WHITE,0);
    LCD_ShowChineseFont(16*6,16*2,16,HZ_FONT_16[13],WHITE,0);
    
    //蓝牙状态
    LCD_ShowChineseFont(16*0,16*3,16,HZ_FONT_16[14],WHITE,0);
    LCD_ShowChineseFont(16*1,16*3,16,HZ_FONT_16[15],WHITE,0);
    LCD_ShowChineseFont(16*2,16*3,16,HZ_FONT_16[8],WHITE,0);
    LCD_ShowChineseFont(16*3,16*3,16,HZ_FONT_16[9],WHITE,0);
    //连接或者断开
    LCD_ShowChineseFont(16*5,16*3,16,HZ_FONT_16[18],WHITE,0);
    LCD_ShowChineseFont(16*6,16*3,16,HZ_FONT_16[19],WHITE,0);
    
    //初始化SIM800C
    state=SIM800C_InitCheck();
    printf("SIM800C初始化状态:%d\r\n",state);
    DelayMs(1000);
    
    //设置文本模式
    state=SIM800C_SetNoteTextMode();
    printf("设置文本模式状态:%d\r\n",state);
    DelayMs(1000);
    
    key=ADXL345_Init();
    printf("ADXL345_Init:%d\r\n",key);
    
   while(1)
   {
    
    
        //触摸按键检测
        key=Touch_KeyScan();
        if(key)
        {
    
    
            printf("触摸按键=%d\r\n",key);
            time_cnt3=0;
            
            //密码输入
            if(key==1)my_strcat("1");
            if(key==2)my_strcat("2");
            if(key==3)my_strcat("3");
            if(key==5)my_strcat("4");
            if(key==6)my_strcat("5");
            if(key==7)my_strcat("6");
            if(key==9)my_strcat("7"); 
            if(key==10)my_strcat("8"); 
            if(key==11)my_strcat("9"); 
            if(key==14)my_strcat("0");             
            
            //按下*号键,清除所有密码
            if(key==13)
            {
    
    
                pass_show_buff[0]='\0';
                pass_show_temp_buff[0]='\0';
                pass_show_cnt=0;
                Gui_DrawFont_GBK16(0,16*5,WHITE,0,(u8*)"          ");
            }
            
            //按下D号按键,清除密码最后一位
            if(key==16)
            {
    
    
                delete_ending_char(pass_show_buff);
                delete_ending_char(pass_show_temp_buff);
                Gui_DrawFont_GBK16(0,16*5,WHITE,0,(u8*)"          ");
            }
            

            
            //按下#号键
            if(key==15)
            {
    
    
                printf("密码:%s\r\n",pass_show_temp_buff);
                //比较密码是否输入正确
                if(strcmp(pass_show_temp_buff,"123456")==0)
                {
    
    
                    open_lock_cnt=0; //清理失败次数
                    LOCK_CTL=1;
                    printf("输入密码->开锁成功.\r\n");
                }
                else
                {
    
    
                    open_lock_cnt++; //记录失败次数
                    printf("输入密码错误.开锁失败.\r\n");
                    
                    if(open_lock_cnt>=3)
                    {
    
    
                         BEEP=1; //蜂鸣器报警
                        
                        //表示车子恶意开锁,可能被盗,发送短信提示
                        //发送短信
                        if(SIM800C_SendNote((u8*)"18171571217",sim800c_buff,strlen((char*)sim800c_buff))==0)
                        printf("短信发送成功\r\n");
                        else
                        printf("短信发送失败\r\n");
                    
                    }
                }
            }
            
            //显示密码*号
            Gui_DrawFont_GBK16(0,16*5,WHITE,0,(u8*)pass_show_buff);
        }
        
        //按键检测 
        key=KEY_Scan();
        if(key)
        {
    
    
            printf("板载按键=%d\r\n",key);
            
            //0表示连接上(板子上蓝色LED灯灭)  1表示断开连接(板子上蓝色LED灯亮)
            printf("蓝牙的连接状态:%d\r\n",BLE_STATE);
            
            //0表示关闭 1表示打开
            printf("继电器的状态:%d\r\n",LOCK_CTL);
        }
       
        //蓝牙断开连接
        if(BLE_STATE)
        {
    
    
            LCD_ShowChineseFont(16*5,16*3,16,HZ_FONT_16[18],WHITE,0);
            LCD_ShowChineseFont(16*6,16*3,16,HZ_FONT_16[19],WHITE,0);
        }
        //蓝牙已连接
        else 
        {
    
    
            //消除蜂鸣器报警
            BEEP=0;
            //LOCK_CTL=1;
            //printf("蓝牙已连接,开锁成功.\r\n");
            LCD_ShowChineseFont(16*5,16*3,16,HZ_FONT_16[16],WHITE,0);
            LCD_ShowChineseFont(16*6,16*3,16,HZ_FONT_16[17],WHITE,0);
        }
        
        //锁开启状态
        if(LOCK_CTL)
        {
    
    
            LCD_ShowChineseFont(16*5,16*2,16,HZ_FONT_16[10],WHITE,0);
            LCD_ShowChineseFont(16*6,16*2,16,HZ_FONT_16[11],WHITE,0);
        }
        //锁关闭状态
        else
        {
    
    
            LCD_ShowChineseFont(16*5,16*2,16,HZ_FONT_16[12],WHITE,0);
            LCD_ShowChineseFont(16*6,16*2,16,HZ_FONT_16[13],WHITE,0); 
        }            
        
        
      
        //控制LED灯
        if(key==2)
        {
    
    
            LED2=!LED2;
            BEEP=!BEEP;
        }    
        
        //手动控制蜂鸣器测试
        //手动发送短信测试
        if(key==1)
        {
    
    
            BEEP=1;
            //LOCK_CTL=1;
            delay_ms(200);
            BEEP=0;
            //LOCK_CTL=0;

            //发送短信
            if(SIM800C_SendNote((u8*)"18171571217",sim800c_buff,strlen((char*)sim800c_buff))==0)
            printf("短信发送成功\r\n");
            else
            printf("短信发送失败\r\n");
        }
        
        
         // 接收蓝牙返回的数据
        if(USART3_RX_FLAG)
        {
    
    
            USART3_RX_BUFFER[USART3_RX_CNT]='\0';
            
            printf("蓝牙收到数据:\r\n");
            //向串口打印服务器返回的数据
            for(i=0;i<USART3_RX_CNT;i++)
            {
    
    
                printf("%c",USART3_RX_BUFFER[i]);
            }
            
            //开锁
            if(strstr((char*)USART3_RX_BUFFER,"open_lock"))
            {
    
    
                LOCK_CTL=1;
                printf("开锁成功.\r\n");
            }
            //关锁
            else if(strstr((char*)USART3_RX_BUFFER,"close_lock"))
            {
    
    
                LOCK_CTL=0;
                printf("关锁成功.\r\n");
            }
            
            memset(USART3_RX_BUFFER,0,sizeof(USART3_RX_BUFFER));
            USART3_RX_CNT=0;
            USART3_RX_FLAG=0;
        }
        
        /*
        静止状态:
x=7,y=-9,z=255
x=5,y=-8,z=255
x=6,y=-9,z=255
x=6,y=-9,z=254
x=6,y=-9,z=258
x=7,y=-9,z=255
x=6,y=-9,z=255
x=6,y=-9,z=256
x=4,y=-8,z=256
x=6,y=-9,z=254
x=5,y=-9,z=256
x=1,y=-7,z=255
x=1,y=-7,z=255
x=1,y=-7,z=255
x=2,y=-7,z=255
x=1,y=-7,z=256
x=1,y=-7,z=255
x=1,y=-7,z=256
x=2,y=-8,z=255
x=1,y=-7,z=255
x=1,y=-8,z=256
x=1,y=-7,z=256
x=0,y=-7,z=255
x=1,y=-7,z=256
x=1,y=-8,z=255
x=1,y=-8,z=255

        运动状态:        
x=148,y=-46,z=234
x=-34,y=-29,z=258
x=138,y=-74,z=224
x=-57,y=-10,z=277
x=127,y=-44,z=247
x=17,y=17,z=272
x=38,y=-39,z=260
x=148,y=-53,z=243
x=-19,y=17,z=264
x=64,y=-6,z=253
x=58,y=26,z=262
x=33,y=-3,z=261
x=166,y=-41,z=231
x=-18,y=-25,z=271
x=182,y=-9,z=251
x=5,y=8,z=257
x=116,y=31,z=234
x=84,y=29,z=259
x=58,y=20,z=261
x=27,y=-43,z=245

        */
        //轮询时间到达
        if(time_cnt>10)
        {
    
    
            //读取三轴加速度
            ADXL345_RD_Avval(&ADXL345_x,&ADXL345_y,&ADXL345_z);
            printf("x=%d,y=%d,z=%d\r\n",ADXL345_x,ADXL345_y,ADXL345_z);
            
            //横向移动判断x轴
            if(ADXL345_x>=10 || ADXL345_x<0)
            {
    
    
                adxl_cnt++;
            }
            else
            {
    
    
                adxl_cnt=0;
            }
            
            //检测到当前处于运动状态
            if(adxl_cnt>=3)
            {
    
    
                //锁开启状态下
                if(LOCK_CTL)
                {
    
    
                    //表示正常行驶中
                     time_cnt2=0;
                }
                //锁关闭状态下
                else
                {
    
    
                    //表示车子可能被盗,发送短信提示
                    //发送短信
                    if(SIM800C_SendNote((u8*)"18171571217",sim800c_buff,strlen((char*)sim800c_buff))==0)
                    printf("车子可能被盗,短信发送成功\r\n");
                    else
                    printf("车子可能被盗,短信发送失败\r\n");
                    
                    //蜂鸣器报警
                    BEEP=1;
                }
            }
            
            time_cnt=0;
            LED1=!LED1;
        }
        
        //1分钟后,如果车辆处于停止状态并且蓝牙处于关闭状态就上锁
        if(time_cnt2>100*60)
        {
    
    
            time_cnt2=0;
            
            if(BLE_STATE && adxl_cnt==0)
            {
    
    
                LOCK_CTL=0;
                printf("1分钟静止时间到达,自动关锁成功.\r\n");
            }
        }
        
        //输入密码超时清除密码输入框
        if(time_cnt3>=300)
        {
    
    
            time_cnt3=0;
            delete_ending_char(pass_show_buff);
            delete_ending_char(pass_show_temp_buff);
            Gui_DrawFont_GBK16(0,16*5,WHITE,0,(u8*)"          ");
        }
        
        
        delay_ms(10);
        time_cnt++;
        time_cnt2++;
        time_cnt3++;
	 }
}

猜你喜欢

转载自blog.csdn.net/xiaolong1126626497/article/details/126567469