C8051F120的串口升级程序应用

本人实现该功能的原因

记得是在2018年的8月份,公司放高温假比前两年要晚上一两个星期,媳妇在放高温假的前一个星期就请了产假回老家了。凑巧的是在高温假之后马上就是预产期,所以在一星期的高温假之后,我紧接着又请了一周的陪产假,而过了预产期一周之后还没生,我又请了一周的假,连休了近20天(不上班爽歪歪)。这两个星期就是在老家陪着媳妇散步,等待孩子的到来。那时候最搞笑的是每天小区的人看到总会问“生了没?”,“还没生了?”。
而我请了陪产假而别人正式上班,有个同事(做重要项目的一个部门领导)说甲方要求增加串口升级功能,不能改个程序老得拆开。所以就有了这个事儿,虽然是在将近1年后才做完这个功能。(儿子都1周岁了,时间过得好快,这周末就回老家看你)。

串口升级的原理

串口升级程序的功能的实现,是通过异步通信串口(UART)将新的程序代码下载到单片机中,将旧程序替换掉,从而实现程序更新。而为了实现该功能,就需要一个引导程序来实现代码的更新。
先写一个引导程序BootLoader,下载到单片机中。而正在运行的程序则通过引导程序下载保存到单片机的Flash中。引导程序在Flash中位置如下:
0x1FFFF Flash_MAX Addr
… … … … 引导程序及一些控制参数保存位置
0x0C000 引导程序的起始位置
0x0003~0xBFFF 要升级运行的程序代码
0x00000 程序的起始,头三个字节为跳转命令,跳转到引导程序
其中,位置的设置只是个人应用设置,可根据自己的需要修改。
因为程序的一些中断等在Flash中的位置是固定的,所以引导程序中关于异步串口UART的操作都不是通过中断而是通过查询的方式来实现的。
程序在开始运行时,先进入BootLoader程序中,在开始约两秒时间内检测串口数据,看是否下载程序,如果收到下载程序命令,则进入下载程序流程。如果在这两秒内未检测到下载命令,则程序继续运行下一步骤。
程序中会读取一个固定位置的值,若该值为0x01表示当前程序存储区存储着正确的程序,则读取存储程序起始位置的数据,并跳转到正确程序的起始位置开始运行程序;则表示无效程序,会一直运行BootLoader程序等待下载程序。
整个流程看现象如同89C52等芯片的通过串口下载更新程序。

具体实现

BootLoader的程序代码起始位置设定

要实现BootLoader的程序起始位置为0xC000,通过在Keil工程文件中的设置选项中设置BL51 Locate 如下图:
BL51 Locate
按此设置编译下载后,Flash中的0x0000~0x0002为跳转命令和地址,跳转到BootLoader程序的运行位置,而0x0003 - 0xBFFF 的值为0xFF,即无程序,后续串口将真正要运行的程序保存到其中。

BootLoader的通信

要通过串口进行程序下载,就需要通过一定的通信协议来进行下载。
通信协议要能够实现进入下载流程、发送程序、对下载的数据校验等功能。

通信协议

在我的应用中,使用的通信协议如下:

帧头命令数据帧尾校验
0xA5 0x5A0xF0开始下载12字节0xAACRC校验,同DS18B20的校验相同
0xF1~0xFA下载 12字节,前2字节表示数据id(索引地址),后10个字节表示数据,0xF1 – 数据为1字节,0xF2 – 数据为2字节…,0xFA – 数据为10字节
0xFB校验 12字节前两字节表示数据长度,第1字节表示代码CRC校验值(最后1字节回报0失败,1校验成功)
0xFC ClearFlash 12字节

通信代码

/*
 * @Author: xiaoyong yang 
 * @Date: 2019-04-14 15:10:13 
 * @Last Modified by: xiaoyong yang
 * @Last Modified time: 2019-08-15 21:33:18
 */
#include "Uart.h"
#include "Main.h"
#include "DownLoadCtrl.h"
#include "Communicate.h"

#define FRAME_LENGTH 17

#define FRAME_HEAD1  0xA5
#define FRAME_HEAD2  0x5A

#define FRAME_END    0xAA

#define CMD_DOWNLOAD_START  0xF0    //开始下载

#define CMD_DOWNLOAD_CHECK  0xFB    //校验
#define CMD_CLEAR_FLASH 0xFC        //清除FLash


static unsigned char frameBuf[FRAME_LENGTH] = {0};

/**
 * 名称: PutInBuf
 * 功能: 将数据放入到缓存区中
 * 参数: buf - 要放入数据的缓冲区
 *       inData - 要放入缓冲区中的数据
 *       bufSize - 缓冲数组的大小
 * 返回值: void 
 * */
void PutInBuf(unsigned char *buf,unsigned char inData, unsigned char bufSize)
{
    unsigned char i;
    for(i=0;i<bufSize-1;i++)
    {
        buf[i] = buf[i+1];
        Idle();
    }
    buf[bufSize-1] = inData;
}

/**
 * 名称: Sum
 * 功能: 求和
 * 参数: buf - 要进行和运算的缓冲区
 *       offset - 起始位移
 *       length - 要进行计算的长度
 * 返回值: 计算的和
 * */
unsigned char Sum(unsigned char *buf,unsigned char offset,unsigned char length)
{
    unsigned char sum = 0,i;
    unsigned char sumLength = offset+length;
    for(i=offset;i<sumLength;i++)
    {
        sum += buf[i];
        Idle();
    }
    return sum;
}

/**
 * 名称: ScanUart
 * 功能: 扫描串口数据
 * 参数: void
 * 返回值: void
 * */
void ScanUart(void)
{
    int temp = -1;
    unsigned int idx = 0,length = 0;
    if(COM0_RxBufLength()>0)
    {
        temp = COM0_GetByte();
        if(temp != -1)
        {
            PutInBuf(frameBuf,(unsigned char)(temp&0x00FF),FRAME_LENGTH);
            if(FRAME_HEAD1 == frameBuf[0] && FRAME_HEAD2 == frameBuf[1] && FRAME_END == frameBuf[15])
            {
                if(frameBuf[16] == Sum(frameBuf,0,FRAME_LENGTH-1))
                {
                    switch(frameBuf[2])
                    {
                        case CMD_DOWNLOAD_START:
                            flag_dowoLoad = 1;
                            StartDownload();
                            UpLoadInfo(CMD_DOWNLOAD_START,&frameBuf[3]);
                            break;
                        case 0xF1:
                        case 0xF2:
                        case 0xF4:
                        case 0xF5:
                        case 0xF6:
                        case 0xF7:
                        case 0xF8:
                        case 0xF9:
                        case 0xFA:
                            idx = frameBuf[3]*256+frameBuf[4];
                            WriteDownLoadBytes(idx,&frameBuf[5],frameBuf[2]&0x0F);
                            UpLoadInfo(frameBuf[2],&frameBuf[3]);
                            break;                                                   
                        case 0xF3:
                            idx = frameBuf[3]*256+frameBuf[4];
                            if(idx == 0)
                            {
                                WriteStartJumpData(&frameBuf[5]);
                            }
                            else
                            {
                                WriteDownLoadBytes(idx,&frameBuf[5],3);
                            }
                            UpLoadInfo(frameBuf[2],&frameBuf[3]);
                            break;                        
                        case CMD_DOWNLOAD_CHECK:
                            length = frameBuf[3]*256+frameBuf[4];  
                            frameBuf[5] = CheckCode(length,frameBuf[5]);
                            UpLoadInfo(CMD_DOWNLOAD_CHECK,&frameBuf[3]);
                            break;
                        case CMD_CLEAR_FLASH:                            
                            ClearFlash();   
                            UpLoadInfo(CMD_CLEAR_FLASH,&frameBuf[3]);
                            break;
                    }
                }
            }
        }
    }
}

/**
 * 名称: UpLoadInfo
 * 功能: 上报信息
 * 参数: cmd - 命令
 *       datas - 要上报的数据
 * 返回值: void
 * */
void UpLoadInfo(unsigned char cmd, unsigned char *datas)
{
    unsigned char sendBuf[FRAME_LENGTH] = {0},i;
    sendBuf[0] = 0xA5;
    sendBuf[1] = 0x5A;
    sendBuf[2] = cmd;
    for(i=0;i<12;i++)
    {
        sendBuf[i+3] = datas[i];
    }
    sendBuf[15] = 0xAA;
    for(i=0;i<FRAME_LENGTH-1;i++)
    {    
        sendBuf[16] += sendBuf[i];
        COM0_SendByte(sendBuf[i]);
    }
    COM0_SendByte(sendBuf[16]);    
}

/* end of file */

下载控制

程序升级下载的流程控制的代码如下:

/*
* @Author: xiaoyong yang 
* @Date: 2019-04-15 08:21:38 
* @Last Modified by: xiaoyong yang
* @Last Modified time: 2019-08-15 21:36:40
*/
#include "F120_FlashUtils.h"
#include "C8051F120.h"
#include "CRC_8.h"

// This routine writes <byte> to the linear FLASH address <addr>.   
// Linear map is decoded as follows:   
// Linear Address       Bank     Bank Address   
// ------------------------------------------------   
// 0x00000 - 0x07FFF    0        0x0000 - 0x7FFF   
// 0x08000 - 0x0FFFF    1        0x8000 - 0xFFFF   
// 0x10000 - 0x17FFF    2        0x8000 - 0xFFFF   
// 0x18000 - 0x1FFFF    3        0x8000 - 0xFFFF   
#define CODE_SELECT_ADDR    0x14000 //当前的工作flash的模式选择的地址
#define CODE_START_ADDR     0x14001


/**
* 名称:StartDownLoad
* 功能:开始下载
* 参数: void
* 返回值: void
* */
void StartDownload(void)
{	
   FLASH_PageErase(CODE_SELECT_ADDR,0);
}

/**
* 名称: WriteDownLoadBytes
* 功能: 写下载的数据
* 参数: idx - 要写的数据的索引起始
*       writeDatas - 要写入flash中的数据
*       length - 要写的数据长度
* 返回值: void
* */
void WriteDownLoadBytes(unsigned int idx,unsigned char *writeDatas,unsigned char length)
{  
   unsigned char temp[10],i;
   FLASH_Read(temp,idx,length,0);
   for(i=0;i<length;i++)
   {
       if(temp[i] != writeDatas[i])
       {
           FLASH_Write(idx+i,&writeDatas[i],length-i,0);
           return;
       }
   }       
}

/**
* 名称: WriteStartJumpData
* 功能: 写开始跳转的地址数据
* 参数: writeDatas - 要写的数据
* 返回值: void
* */
void WriteStartJumpData(unsigned char *writeDatas)
{
   unsigned char temp[3],i;
   FLASH_Read(temp,CODE_START_ADDR,3,0);
   for(i=0;i<3;i++)
   {
       if(temp[i] != writeDatas[i])
       {
           FLASH_Write(CODE_START_ADDR,writeDatas,3,0);
           return;
       }
   }
}

/**
* 名称: ClearFlash
* 功能: 清除Flash
* 参数: void
* 返回值: void
* */
void ClearFlash(void)
{
   unsigned char i;
   FLASH_Clear(3,1024-3,0);
   for(i=1;i<48;i++)
   {
   	FLASH_PageErase(0x400*i,0);
   }
}

/**
* 名称: CheckCode
* 功能: 校验代码
* 参数: length - 要进行校验的代码长度
*       checkWork - 校验的值   
* 返回值: 校验的结果,0:校验失败 1:校验成功
* */
unsigned char CheckCode(unsigned int length,unsigned char checkWork)
{
   unsigned char crc = 0;
   unsigned int i;    
   for(i=0;i<3;i++)
   {        
       crc =DoCrc8(crc,FLASH_ByteRead(CODE_START_ADDR+i,0));
   }
   for(i=3;i<length;i++)
   {
       crc = DoCrc8(crc,FLASH_ByteRead(i,0));
   }
   if(checkWork == crc)
   {    
       FLASH_ByteWrite(CODE_SELECT_ADDR,1,0);        
       return 1;
   }
   return 0;
}

/**
* 名称: JumpWorkSpace
* 功能: 跳转到工作区
* 参数: void
* 返回值: void
* */
void JumpWorkSpace(void)
{
   unsigned char state = 0;
   unsigned int startAddr = 0;
   state = FLASH_ByteRead(CODE_SELECT_ADDR,0);
   if(state == 1)
   {
       startAddr = FLASH_ByteRead(CODE_START_ADDR+1,0);
       startAddr *= 256;
       startAddr += FLASH_ByteRead(CODE_START_ADDR+2,0);
       (*((void(*)(void))startAddr) )();
   }    
}

/* end of file */

程序的流程(主函数)

/*
 * @Author: xiaoyong yang 
 * @Date: 2019-04-14 14:58:36 
 * @Last Modified by: xiaoyong yang
 * @Last Modified time: 2019-08-15 18:00:24
 */
#include "Main.h"
#include "C8051F120.h"
#include "Uart.h"
#include "DownLoadCtrl.h"
#include "Communicate.h"


#define MAX_WAIT_DOWN 200000

extern void Init_Device(void);

static unsigned long count = 0;

unsigned char flag_dowoLoad = 0;

/**
 * 名称: Idle
 * 功能: 空闲函数,喂狗
 * 参数: void
 * 返回值: void
 * */
void Idle(void)
{
    //WDTCN = 0xA5;//喂狗
//	WDTCN     = 0xDE;
//    WDTCN     = 0xAD;
}

/**
 * 名称: InitModule
 * 功能: 初始化模块
 * 参数: void
 * 返回值: void
 * */
void InitModule(void)
{
    ResetAllPin();
}

/**
 * 名称: DownLoadCode
 * 功能: 下载代码
 * 参数: void
 * 返回值: void
 * */
void DownLoadCode(void)
{
    EA = 0;  
    COM0_Initialize();
    while(1)
    {
        Idle();
        COM0_TR();
        ScanUart();
    }
}

/**
 * 名称: WaitDownLoad
 * 功能: 等待程序下载,程序开始运行后约2S时间等待下载程序
 * 参数: void
 * 返回值: void
 * */
void WaitDownLoad(void)
{
    unsigned long unloadCnt = 0;
    EA = 0;  
    COM0_Initialize();
    while(unloadCnt < MAX_WAIT_DOWN || flag_dowoLoad )
    {
        Idle();
        COM0_TR();
        ScanUart();
        unloadCnt++;
    }
}

/**
 * 名称: Main
 * 功能: 主函数,程序入口
 * 参数: void
 * 返回值: void
 * */
void Main(void)
{
    EA = 0;    
    Init_Device();
    EA = 1;	
    WaitDownLoad();
    JumpWorkSpace();
    while(1)
    {
        DownLoadCode();
    }	
}

/* end of file */

目标代码的下载

目标代码的文件格式

要向MCU中下载更新的程序代码的文件格式为 “.Bin”文件。我们要得到Bin文件,可以通过Keil生成 “.Hex”文件,然后将Hex文件转换成Bin文件。(关于".Bin"文件和".Hex"文件,可自行去了解,本文不进行介绍)。
文件转换的程序可以用“51Hex_Bin.exe”,也可自行搜索相应的软件。

目标代码下载的方式

个人为了实现程序的下载,特写了一个对应的下载程序,界面如下:
在这里插入图片描述
贴出部分C#代码:

		string binFilePath;
        FileStream binFileStream = null;
        BinaryReader binReader = null;
        byte[] datas = null;
        private void button_selectBin_Click(object sender, EventArgs e)
        {            
            OpenFileDialog binFileSelectDialog = new OpenFileDialog();
            binFileSelectDialog.Filter = "bin文件(*.bin)|*.bin";
            DialogResult result = binFileSelectDialog.ShowDialog();
            if(result == DialogResult.OK)
            {
                binFilePath = binFileSelectDialog.FileName;
                textBox_bin.Text = binFilePath;
                fileOk = true;
                binFileStream = new FileStream(binFilePath, FileMode.Open, FileAccess.Read);
                binReader = new BinaryReader(binFileStream);
                datas = new byte[binFileStream.Length];                
                datas = binReader.ReadBytes((int)binFileStream.Length);
            }           
        }
       

        private void button_downLoad_Click(object sender, EventArgs e)
        {
            if(uartOk && fileOk)
            {
                DownLoadCode();
            }
            else
            {
                if (!uartOk)
                    MessageBox.Show("请选择串口!","提示");
                else
                    MessageBox.Show("请选择要下载的Bin文件!","提示" );
            }
        }
        bool startOk = false;
        bool clearOk = false;
        bool downLoadOk = false;
        bool checkOk = false;        
        int downloadOkIdx = -1;
        int downFileLength = 0;
        byte checkWord = 0x00;
        private void DownLoadCode()
        {
            
            startOk = false;
            int count = 0;
            int countSend = 0;
            while (!startOk)
            {
                if(count % 10 == 0)
                {
                    if(!StartDownLoad())
                    {
                        return;
                    }
                    countSend++;
                }
                Thread.Sleep(1);
                count++;
                if(countSend > 200)
                {
                    MessageBox.Show("未能成功下载", "下载失败!");
                    return;
                }
            }                       
            int fileLength = (int)binFileStream.Length;
            downFileLength = fileLength;
            int lastLength = downFileLength;
            downLoadProgress.Maximum = (fileLength + 1);
            downLoadProgress.Value = 0;
            downLoadProgress.Show();
            
            binReader.Read(datas, 0, fileLength);

            clearOk = false;
            count = 0;
            countSend = 0;
            while (!clearOk)
            {
                if (count % 50 == 0)
                {
                    ClearFlash();
                    countSend++;
                }
                Thread.Sleep(1);
                count++;
                if (countSend > 20)
                {
                    MessageBox.Show("未能成功清除Flash", "下载失败!");
                    return;
                }
            }

            byte crc = 0;
            int writeAddr = 0;
            downloadOkIdx = -1;
            byte[] writeDatas = new byte[10];
            for (int i=0; i<3;i++)
            {
                crc = DoCrc8(crc, datas[i]);
                writeDatas[i] = datas[i];
            }            
            downLoadOk = false;
            count = 0;
            countSend = 0;
            while (!downLoadOk || downloadOkIdx != writeAddr)
            {
                if (count % 10 == 0)
                {
                    DownLoadByte(writeAddr, writeDatas, 3);
                    downLoadProgress.Value = 3;
                    countSend++;
                }
                Thread.Sleep(1);
                count++;
                if (countSend > 20)
                {
                    MessageBox.Show("下载失败");
                    return;
                }
            }
            lastLength -= 3;
            int n = lastLength / 10;
            int mod = lastLength % 10;
            for (int i = 0; i < n; i++)
            {
                writeAddr = 3 + i * 10;
                for (int j=0; j <10; j++)
                {
                    crc = DoCrc8(crc, datas[writeAddr+j]);
                    writeDatas[j] = datas[writeAddr + j];
                }
                downLoadOk = false;
                count = 0;
                countSend = 0;
                while (!downLoadOk || downloadOkIdx != writeAddr)
                {
                    if (count % 10 == 0)
                    {
                        DownLoadByte(writeAddr, writeDatas, 10);
                        downLoadProgress.Value = 13 + i * 10;
                        countSend++;
                    }
                    Thread.Sleep(1);
                    count++;
                    if (countSend > 20)
                    {
                        MessageBox.Show("下载失败");
                        return;
                    }
                }
            }

            writeAddr = n * 10 + 3;
            for (int i = 0;i<mod; i++)
            {
                crc = DoCrc8(crc, datas[writeAddr + i]);
                writeDatas[i] = datas[writeAddr + i];
            }
            downLoadOk = false;
            count = 0;
            countSend = 0;
            while (!downLoadOk || downloadOkIdx != writeAddr)
            {
                if (count % 10 == 0)
                {
                    DownLoadByte(writeAddr, writeDatas, (byte)mod);
                    downLoadProgress.Value = 3 + n * 10 + mod;
                    countSend++;
                }
                Thread.Sleep(1);
                count++;                
                if (count > 50)
                {
                    MessageBox.Show("下载失败");
                    return;
                }
            }
                       
            MessageBox.Show("下载成功,开始校验", "提示");
            CheckCode();
        }

        private bool StartDownLoad()
        {
            byte[] datas = new byte[12];            
            return UartSend(CMD_DOWNLOAD_START, datas);
        }

        public bool ClearFlash()
        {
            byte[] datas = new byte[12];
            return UartSend(CMD_DOWNLOAD_CLEAR, datas);
        }

        private bool DownLoadByte(int idx,byte[] datas,byte length)
        {
            byte[] sendDatas = new byte[12];
            sendDatas[0] = (byte)((idx & 0xFF00) >> 8);
            sendDatas[1] = (byte)(idx & 0xFF);
            if (datas.Length <= 10)
            {
                for (int i = 0; i < datas.Length; i++)
                {
                    sendDatas[2 + i] = datas[i];
                }
            }
            return UartSend((byte)(0xF0|length), sendDatas);
        }
        
        private bool DownLoadCheck(int length,byte checkCode)
        {
            byte[] datas = new byte[12];
            datas[0] = (byte)((length & 0xFF00) >> 8);
            datas[1] = (byte)(length & 0xFF);
            datas[2] = checkCode;
            return UartSend(CMD_DOWNLOAD_CHECK, datas );
        }
        private bool UartSend(byte cmd, byte[] datas)
        {
            byte[] sendDatas = new byte[17];
            sendDatas[0] = FRAME_HEAD1;
            sendDatas[1] = FRAME_HEAD2;
            sendDatas[2] = cmd;
            if(datas.Length <= 12)
            {
                for(int i=0;i<datas.Length;i++)
                {
                    sendDatas[3 + i] = datas[i];
                }
            }            
            sendDatas[15] = FRAME_END;
            sendDatas[16] = 0;
            for (int i = 0; i < 16; i++)
            {
                sendDatas[16] += sendDatas[i];
            }
            if (serialPort1 != null && serialPort1.IsOpen)
            {
               
                try
                {
                    serialPort1.Write(sendDatas, 0, sendDatas.Length);
                    return true;
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message, "串口异常");
                    CloseUart();
                }                    
                
            }
            else
            {                
                MessageBox.Show("请选择串口并打开","提示");
            }
            return false;
        }
     
        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            int n = serialPort1.BytesToRead;
            byte[] buf = new byte[n];
            serialPort1.Read(buf, 0, n);
            for (int i = 0; i < n; i++)
            {
                ScanUart(buf[i]);
            }
        }

        byte[] frameBuf = new byte[17];
        private void ScanUart(byte inData)
        {
            for(int i=0;i<16;i++)
            {
                frameBuf[i] = frameBuf[i + 1];
            }
            frameBuf[16] = inData;
            if(FRAME_HEAD1 == frameBuf[0] && FRAME_HEAD2 == frameBuf[1] && FRAME_END == frameBuf[15])
            {
                byte sum = 0;
                for(int i=0;i<16;i++)
                {
                    sum += frameBuf[i];
                }
                if (sum == frameBuf[16])
                {
                    switch(frameBuf[2])
                    {
                        case CMD_DOWNLOAD_START:
                            startOk = true;
                            break;
                        case 0xF1:
                        case 0xF2:
                        case 0xF3:
                        case 0xF4:
                        case 0xF5:
                        case 0xF6:
                        case 0xF7:
                        case 0xF8:
                        case 0xF9:                        
                            downloadOkIdx = frameBuf[3] * 256 + frameBuf[4];
                            downLoadOk = true;
                            break;
                        case 0xFA:
                            downloadOkIdx = frameBuf[3] * 256 + frameBuf[4];
                            downLoadOk = true;
                            break;
                        case CMD_DOWNLOAD_CHECK:
                            int length = frameBuf[3] * 256 + frameBuf[4];
                            if(length == binFileStream.Length && frameBuf[5] == 0x01)
                            {
                                checkOk = true;
                            }                            
                            break;
                        case CMD_DOWNLOAD_CLEAR:                                                        
                            clearOk = true;                       
                            break;
                    }
                }
            }
        }

        private void button_check_Click(object sender, EventArgs e)
        {
            if (uartOk && fileOk)
            {
                CheckCode();
            }
            else
            {
                if (!uartOk)
                    MessageBox.Show("请选择串口!", "提示");
                else
                    MessageBox.Show("请选择要下载的Bin文件!", "提示");
            }
            
        }

        private void CheckCode()
        {
            byte crcValue = 0;
            for(int i=0;i<binFileStream.Length;i++)
            {
                crcValue = DoCrc8(crcValue, datas[i]);
            }
            checkOk = false;
            int count = 0;
            int countSend = 0;
            while (!checkOk)
            {
                if (count % 10 == 0)
                {
                    DownLoadCheck((int)binFileStream.Length, crcValue);
                    countSend++;
                }
                Thread.Sleep(1);
                count++;
                if (countSend > 20)
                {
                    MessageBox.Show("校验失败", "提示");
                    return;
                }
            }
            MessageBox.Show("校验成功", "提示");
        }
  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值