C 解析串口数据

最近面试,收到一份面试题,特此研究下C的串口解析

题目要求如下:

  • 用C语言写一个程序,此程序持续从串口读取数据(串口速率是115200,偶校验),串口数据有可能包含DL-T 645协议格式或者DL-T 698协议格式的数据帧(协议见word文档)。
    要求把收到的符合以上两种格式的数据帧检出来。
    附加要求:注意程序的可扩展性(如果以后此程序再支持其他协议数据解析,要容易扩展)。*

由于文档比较内容繁琐,特贴出来,协议格式

DL-T 645协议格式
(以下简称协议A)
在这里插入图片描述
DL-T 698协议格式
(以下简称协议B)
在这里插入图片描述
串口解析思路

 - 一、串口数据接收: 	
 		定义一个1024字节的buf,将串口接收到的数据依次追加到此buf中; 
 - 二、解析串口数据流程:
 		1、从buf中检索起始字符0x68的位置,->2
 		2、去匹配是否符合协议A,会有三种解析结果
 			a.解析到完整的一帧数据,->5
 			b.数据未接收完 ->3
 			c.解析不满足规则 ->3
 		3、去匹配是否符合协议B,会有三种解析结果
 			a.解析到完整的一帧数据, ->5
 			b.数据未接收完 ->6
 			c.解析不满足规则
 				协议A也不满足规则 ->7
 				协议A未接收完 ->6
 		5、解析到完整的一帧数据,->10
 		6、协议匹配未接收完 ->9
 		7、两个协议解析都不满足,->8
 		8、从1中的位置继续寻找下一个0x68的位置
 			a.找到0x68    ->1
 			b.未找到0x68 ->6
 		9、继续循环,等待串口数据过来
 		10、解析完成,将buf中剩余数据前移到位置0,

代码如下:
先定义两种协议对应的结构体

typedef struct dl_t_698
{
    uint8_t st_byte;//起始字符
    uint16_t data_length;//长度域--此处注意大小端的问题
    uint8_t control_byte;//控制域
    uint8_t address_bytes[100];//地址域
    uint16_t frame_head_hcs;//帧头校验
    uint8_t user_data[100];//用户数据
    uint16_t frame_crc_fcs;//帧校验
    uint8_t end_byte;//结束字符
}dlt_698_frame_body;

typedef struct dl_t_645
{
    uint8_t st_byte;//帧起始符
    uint8_t address_bytes[5];//地址欲
    uint8_t mid_st_byte;//帧起始符
    uint8_t control_byte;//控制码
    uint16_t data_length;//长度域--此处注意大小端的问题-- 前面字符长度为10
    uint8_t user_data[100];//数据data
    uint16_t frame_crc_cs;//校验
    uint8_t end_byte;//结束字符
}dlt_645_frame_body;

//解析到一帧数据可能出现的情况
typedef enum frame_result
{
    UNKNOWN,
    OK,               //成功找到一帧
    UNFINISHED,//未接收完成
    ERROR,        //不满足此协议
} frame_result_t;

//定义协议类型
typedef enum protocol_type {
    PROTOCOL_UNKNOWN,
    PROTOCOL_DL_T_698,
    PROTOCOL_DL_T_645,
    PROTOCOL_OTHER,
}protocol_type_t;

char uart_rcvd_buf[UART_BUFFER_LEN];//接收串口发送过来的数据
char frame_buf[FRAME_BUFFER_LEN];//用来存取一帧数据
uint16_t uart_rcvd_pos = 0;//当前buf接收到的数据长度

dlt_698_frame_body s_dlt_698_frame_body;
dlt_645_frame_body s_dlt_645_frame_body;

/*
 * 功能:接收串口过来的数据
 **/
/*void uart_rev_data(uint8_t data)
{
    uart_rcvd_buf[uart_rcvd_len] = data;
    uart_rcvd_len++; 

    if (uart_rcvd_len >= UART_BUFFER_LEN) {
        //清空所有命令
        uart_rcvd_len = 0;
        //my_memset(uart_rcvd_buf,0,sizeof(uart_rcvd_buf));
    } 
}*/

/*
 * 该函数由库函数调用
 **/
/*void UART_INT_Func(void)
{
    uint8_t u8TempData=0;

    if(1 == M0P_UART1->ISR_f.RI)
    {    
        u8TempData = M0P_UART1->SBUF_f.SBUF; 
        uart_rev_data(u8TempData);          
        
        M0P_UART1->ICR_f.RICLR = 0;
    }    
}*/

/*
 * 功能:检索一帧数据将值赋给结构体       
 * 校验OK return 1;
 **/
uint8_t parse_dlt645_frame(char *p_frame, uint16_t frame_len, dlt_645_frame_body* sframe_body) {
    uint16_t temp16_t = 0;//一帧数据的总长度
    uint16_t i = 0;

    //计算校验码
    for (i = 0; i < frame_len - 3; i ++) {
        temp16_t += p_frame[i];
    }
    if (temp16_t == p_frame[frame_len - 3] | p_frame[frame_len - 2]) {
        sframe_body->st_byte = p_frame[0];
        for (i = 0; i < 6; i ++) {
            sframe_body->address_bytes[i] = p_frame[1+i];

        }
        sframe_body->mid_st_byte = p_frame[7];
        sframe_body->control_byte = p_frame[8];
        temp16_t = (p_frame[9]<<8) |p_frame[10];
        sframe_body->data_length = temp16_t;
        for (i = 0; i < temp16_t; i ++) {
            sframe_body->user_data[i] = p_frame[11 + i];
        }
        sframe_body->frame_crc_cs = p_frame[frame_len - 3] | p_frame[frame_len - 2];
        sframe_body->end_byte = p_frame[frame_len - 1];
        
        return 1;
    }
    return 0;
} 

/*
 * 功能:检索一帧数据将值赋给结构体               
 **/
uint8_t parse_dlt698_frame(char *p_frame, uint16_t frame_len, dlt_698_frame_body* sframe_body) {
    uint16_t temp16_t = 0;//一帧数据的总长度
    uint16_t adr_temp16_t = 0;//地址域的地址长度
    uint16_t i = 0;

    //校验
    for (i = 0; i < frame_len - 3; i ++) {
        temp16_t += p_frame[i];
    }
    if (temp16_t == p_frame[frame_len - 3] | p_frame[frame_len - 2]) {
        
        sframe_body->st_byte = p_frame[0];
        temp16_t = ((p_frame[1]<<8) |p_frame[2]) & 0x3FFF;
        sframe_body->data_length = temp16_t;
        sframe_body->control_byte = p_frame[3];
        sframe_body->address_bytes[0] = p_frame[4];//地址域第一个字节
        adr_temp16_t = p_frame[4] & 0x0F;
        for (i = 0; i < adr_temp16_t; i ++) {
            sframe_body->address_bytes[i] = p_frame[5 + i];
        }
        sframe_body->frame_head_hcs = (p_frame[6 + adr_temp16_t - 1] >> 8) | p_frame[6 + adr_temp16_t];
        for (i = 0; i < temp16_t; i ++) {
            sframe_body->user_data[i] = p_frame[adr_temp16_t + 7];
        }
        sframe_body->frame_crc_fcs = p_frame[frame_len - 3] | p_frame[frame_len - 2];
        sframe_body->end_byte = p_frame[frame_len - 1];
        
        
        return 1;
    }
    return 0;
} 

/*
 * 功能:从缓存区buf中检索dlt645帧数据
 * 将一帧数据读取到frame_buf中     
 * line:缓存区0x68开头的数据
 * out:将捡出来的帧复制到该数组中
 * frame_len:捡出来的帧的长度,
 * line_len:缓存区buf中0x68开头的数据长度
 **/
frame_result_t find_dlt645_frame(char* line, char* out, uint16_t* frame_len, uint16_t line_len) {
    uint16_t frame_length = 0;//一帧数据的总长度
    uint16_t temp_len = 0;

    if (line_len < DLT_645_LEAST_LEN) {
        return UNFINISHED;
    }

    //判断第七位
    if (line[7] != 0x68) {
        return ERROR;
    }

    frame_length = 9;/*帧起始符+地址域+帧起始符+控制域*/
    temp_len = (line[9]<<8) |line[10];//数据data的长度
    printf("645 data len = %d\n", temp_len);
    frame_length = frame_length + 2 + temp_len;/*2-长度域占的字节*/
    frame_length += 3;/*校验码和结束符*/
    if (frame_length > FRAME_BUFFER_LEN) {
        //超过单包缓存区的最大长度
        return ERROR;
    } else {
        if (frame_length <= line_len) {
            if (line[frame_length - 1] == 0x16) {
                //检到一帧数据                
                for (temp_len = 0; temp_len < frame_length; temp_len ++) {
                    out[temp_len] = *line;
                    line++;
                }
                *frame_len = frame_length;
                return OK;
            } else {
                //不满足此协议的0x16结束符
                return ERROR;
            }
        } else {
            //数据还没接收完整
            return UNFINISHED;
        }
    }
    
    return UNKNOWN;
}

/*
 * 功能:从缓存区buf中检索dlt698帧数据
 * 将一帧数据读取到frame_buf中     
 * line:缓存区0x68开头的数据
 * out:将捡出来的帧复制到该数组中
 * frame_len:捡出来的帧的长度,
 * line_len:缓存区buf中0x68开头的数据长度
 **/
frame_result_t find_dlt698_frame(char* line, char* out, uint16_t* frame_len, uint16_t line_len) {
    uint16_t frame_length = 0;//一帧数据的总长度
    uint16_t temp_len = 0;

    if (line_len < DLT_698_LEAST_LEN) {
        return UNFINISHED;
    }
    
    frame_length = 4;/*起始符+长度域+控制域*/
    //地址域
    temp_len = line[4] & 0x0F;
    //printf("698 address len = %d\n", temp_len);    
    frame_length = frame_length + 1 + temp_len;
    //帧头校验
    frame_length += 2;
    //用户数据长度
    temp_len = ((line[1]<<8) |line[2]) & 0x3FFF;
    //printf("698 data len = %d\n", temp_len);
    frame_length += temp_len;//data长度
    //
    frame_length += 3;//帧校验+结束符
    if (frame_length > FRAME_BUFFER_LEN) {
        //超过单包缓存区的最大长度
        return ERROR;
    } else {
        if (frame_length <= line_len) {
            if (line[frame_length - 1] == 0x16) {
                //检到一帧数据                
                for (temp_len = 0; temp_len < frame_length; temp_len ++) {
                    out[temp_len] = *line;
                    line++;
                }
                *frame_len = frame_length;
                return OK;
            } else {
                //不满足此协议的0x16结束符
                return ERROR;
            }
        } else {
            //数据还没接收完整        
            return UNFINISHED;
        }
    }

    return UNKNOWN;
}


/*
 * 功能:协议数据解析
 **/
void parse_buf (void)
{
    uint16_t frame_length = 0;//一帧数据的总长度
    uint16_t i = 0, temp_len = 0;
    uint8_t has_content = 0;//buf中是否有数据
    uint8_t frame_error = 0;//缓存区当前的数据对所有协议都不满足
    char* p_buf;
    protocol_type_t protl_type = PROTOCOL_UNKNOWN;
    frame_result_t find_frame_re = UNKNOWN;

    //用来保存每个协议解析后的结果 
    //frame_results[0] 保存PROTOCOL_DL_T_645协议解析结果
    //frame_results[1] 保存PROTOCOL_DL_T_698协议解析结果
    frame_result_t frame_results[2] = {UNKNOWN, UNKNOWN}; 

    has_content = uart_rcvd_pos > 2;
    while (has_content) {
        p_buf = uart_rcvd_buf;
        printf("p_buf = %#x\n", *p_buf);
        //检索0x68开头的数据
        while (*p_buf != 0x68 && p_buf < uart_rcvd_buf + uart_rcvd_pos) {
            p_buf ++;
        }
        
        if (p_buf == uart_rcvd_buf + uart_rcvd_pos) {
            //检索当前包数据,都不包含,清空
            uart_rcvd_pos = 0;
            break;
        }

        //uart_rcvd_buf中剩余的数据长度
        temp_len = uart_rcvd_pos - (p_buf - uart_rcvd_buf);

        printf("while start has_content uart_rcvd_pos - (p_buf - uart_rcvd_buf) = %d\n", temp_len);
        
        //以下处理不包含校验
        switch(protl_type) {
            case PROTOCOL_UNKNOWN:
                memset(frame_buf,0,sizeof(frame_buf));
                find_frame_re = UNKNOWN;
                frame_error = 0;
                frame_length = 0;
                for (i = 0; i < 3; i ++) {
                    frame_results[i] = UNKNOWN;
                }
                
            case PROTOCOL_DL_T_645:
                find_frame_re = find_dlt645_frame(p_buf, frame_buf, &frame_length, temp_len);
                frame_results[0] = find_frame_re;
                if (find_frame_re == OK) {
                    printf("\nfind dlt_645 OK frame_buf = %s, frame_length = %d\n", frame_buf, frame_length);
                    printf("\n");                    
                    memset(&s_dlt_645_frame_body, 0, sizeof(dlt_645_frame_body));                
                    if (parse_dlt645_frame(frame_buf, frame_length, &s_dlt_645_frame_body)) {
                        //解析到一包有效数据
                    }
                    break;
                }
                
            case PROTOCOL_DL_T_698:
                find_frame_re = find_dlt698_frame(p_buf, frame_buf, &frame_length, temp_len);
                frame_results[1] = find_frame_re;
                if (find_frame_re == OK) {
                    printf("\nfind dlt_698 OK frame_buf = %s, frame_length = %d\n", frame_buf, frame_length);
                    printf("\n");
                    memset(&s_dlt_698_frame_body, 0, sizeof(dlt_698_frame_body));

                    break;
                }

            case PROTOCOL_OTHER:
                //此处添加其他协议解析
                //break;
                
            default :
                if (frame_results[0] == ERROR && frame_results[1] == ERROR) {
                    //缓存区的数据不满足现有协议的解析
                    //继续找下一个0x68起始符
                    p_buf ++;//跳过当前的0x68
                    //检索0x68开头的数据
                    while (*p_buf != 0x68 && p_buf < uart_rcvd_buf + uart_rcvd_pos) {
                        p_buf ++;
                    }
                    
                    if (p_buf == uart_rcvd_buf + uart_rcvd_pos) {
                        //检索当前包数据,都不包含,清空
                        uart_rcvd_pos = 0;
                        break;
                    }

                    //找到下一条0x68开头的数据帧
                    frame_error = 1;
                    
                }
                break;
        }


        //当成功检索到一帧数据或缓存区的数据不满足现有协议的解析
        //buf中剩余的有效数据前移
        if (find_frame_re == OK || frame_error) {
            //uart_rcvd_buf剩余的数据长度
            temp_len = uart_rcvd_pos - (p_buf - uart_rcvd_buf) - frame_length;
            if (temp_len > 0) {
                //当前uart_rcvd_buf中剩余的数据前移
                for (i = 0; i < temp_len; i ++) {
                    uart_rcvd_buf[i] = *(p_buf + frame_length + i);
                    *(p_buf + frame_length + i) = 0x00;
                }
                has_content = 1;//继续循环解析
            } else {
                //解析过的位清空
                for (i = 0; i < (p_buf - uart_rcvd_buf) + frame_length; i ++) {
                    uart_rcvd_buf[i] = 0x00;
                }
                has_content = 0;
            }
            uart_rcvd_pos = temp_len;
        } else {
            has_content = 0;
        }
        printf("while end has_content = %d, uart_rcvd_pos = %d\n", has_content, uart_rcvd_pos);
        
    }

}


int main(void)
{

    uint16_t timer;

    //RCH 24MHz 使用内部时钟
    /*Clk_SwitchTo(ClkRCL);
    Clk_SetRCHFreq(ClkFreq24Mhz);
    Clk_SwitchTo(ClkRCH);

    //enable module clk
    M0P_CLOCK->PERI_CLKEN_f.GPIO = 1;  //打开GPIO的clk
    M0P_CLOCK->PERI_CLKEN_f.BASETIM = 1;
    M0P_CLOCK->PERI_CLKEN_f.UART1   = 1;
    M0P_CLOCK->PERI_CLKEN_f.I2C   = 1;

    //UART init    
    Gpio_SetFunc_UART1TX_P23();
    Gpio_SetFunc_UART1RX_P24();    

    M0P_UART1->SCON_f.DBAUD = 1;    //双倍波特率
    timer = 0x10000-((24000000*2)/(115200*32));  //单倍波特率,定时器配置
    //使用basetimer1作为串口的波特率产生器   
    M0P_BT1->CR_f.GATE_P = 0u;
    M0P_BT1->CR_f.GATE   = 0u;
    M0P_BT1->CR_f.PRS    = 0u;
    M0P_BT1->CR_f.TOG_EN = 0u;
    M0P_BT1->CR_f.CT     = 0u;         //定时器模式
    M0P_BT1->CR_f.MD     = 1u;         //重载模式
    M0P_BT1->ARR_f.ARR = timer;
    M0P_BT1->CNT_f.CNT = timer;
    M0P_BT1->CR_f.TR = TRUE;

    M0P_UART1->SCON_f.SM01 = 0x1;     //模式1
    M0P_UART1->SCON_f.SM2  = 0;       //多主机通信disable

    EnableNvic(UART1_IRQn, 3u, TRUE);
    M0P_UART1->SCON_f.TIEN = 0;
    M0P_UART1->SCON_f.RIEN = 1;    
    M0P_UART1->ICR_f.RICLR = 0;
    M0P_UART1->ICR_f.TICLR = 0;    
    M0P_UART1->SCON_f.REN = 1;*/

    **

“测试代码”;

**

char* p_temp;
    uint8_t i = 0;
    char dlt_645_frame_msg[38] = {0x06, 0x06,0x68, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x68,0x03, 
                                                                    0x00,0x04, 0x0D, 0x0D, 0x0D, 0x0D, 0x02, 0x0A, 0x16, 
                                                                    0x68, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x68,0x03, 
                                                                    0x00,0x04, 0x0D, 0x0D, 0x0D, 0x0D, 0x02, 0x0A, 0x16};

    char dlt_698_frame_msg[34] = {0x06, 0x06,0x68, 0x00, 0x05, 0x0C, 0x01, 0x0A, 0x00, 0xCC,
                                                                    0x0A, 0x0D, 0x0D, 0x0D, 0x0E, 0x0F, 0xCC, 0x16,
                                                                    0x68, 0x00, 0x05, 0x0C, 0x01, 0x0A, 0x00, 0xCC,
                                                                    0x0A, 0x0D, 0x0D, 0x0D, 0x0E, 0x0F, 0xCC, 0x16 };
    
    char error_frame_msg[20] = {0x06, 0x06,0x68, 0x00, 0x05, 0x0C, 0x01, 0x0A, 0x00, 0xCC, 
                                                0x16, 0x06,0x68, 0x60, 0x05, 0x01, 0x01, 0x0A, 0x00, 0xCC};


    char dlt_645_frame_msg_half_st[29] = {0x06, 0x06,0x68, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x68,0x03, 
                                                                    0x00,0x04, 0x0D, 0x0D, 0x0D, 0x0D, 0x02, 0x0A, 0x16, 
                                                                    0x68, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x68,0x03};

    char dlt_645_frame_msg_half_end[9] = {0x00,0x04, 0x0D, 0x0D, 0x0D, 0x0D, 0x02, 0x0A, 0x16};
    
    
    p_temp = uart_rcvd_buf;


//DLT_645 TEST
#if 0

    printf("\n ****** dlt 645 start ******\n");

    //stpcpy(uart_rcvd_buf, dlt_645_frame_msg);
    //strcat(uart_rcvd_buf, end_byte);
    memcpy(uart_rcvd_buf, dlt_645_frame_msg, sizeof(dlt_645_frame_msg));
    uart_rcvd_pos = sizeof(dlt_645_frame_msg);
    printf("main start 645 msg uart_rcvd_buf = %s, uart_rcvd_pos = %d\n", uart_rcvd_buf, uart_rcvd_pos);
    parse_buf();
    printf("\n ****** dlt 645 end ******\n");

#endif


//DLT_698 TEST
#if 0
    printf("\n ****** dlt 698 start ******\n ");
    memcpy(uart_rcvd_buf, dlt_698_frame_msg, sizeof(dlt_698_frame_msg));
    uart_rcvd_pos = sizeof(dlt_698_frame_msg);
    printf("main start 698 msg uart_rcvd_buf = %s, uart_rcvd_pos = %d\n", uart_rcvd_buf, uart_rcvd_pos);
    parse_buf();
    printf("\n ****** dlt 698 end ******\n ");

#endif


//ALL TEST
#if 0

    printf("\n ****** dlt 698 and 645 start ******\n ");
    memcpy(uart_rcvd_buf + uart_rcvd_pos, dlt_698_frame_msg, sizeof(dlt_698_frame_msg));
    uart_rcvd_pos += sizeof(dlt_698_frame_msg);
    memcpy(uart_rcvd_buf + uart_rcvd_pos, dlt_645_frame_msg, sizeof(dlt_645_frame_msg));
    uart_rcvd_pos += sizeof(dlt_645_frame_msg);
    
    printf("main start 698 and 645 msg uart_rcvd_buf = %s, uart_rcvd_pos = %d\n", uart_rcvd_buf, uart_rcvd_pos);
    //parse_buf();
    printf("\n ****** dlt 698 and 645 end ******\n ");

#endif

//Error msg TEST
#if 1
    printf("\n ****** dlt error msg start ******\n ");
    memcpy(uart_rcvd_buf + uart_rcvd_pos, error_frame_msg, sizeof(error_frame_msg));
    uart_rcvd_pos += sizeof(error_frame_msg);
    printf("main start error msg uart_rcvd_buf = %s, uart_rcvd_pos = %d\n", uart_rcvd_buf, uart_rcvd_pos);
    //parse_buf();
    printf("\n ****** dlt error msg end ******\n ");

#endif


//Half test
#if 1

    printf("\n ****** dlt 645 half start ******\n ");
    memcpy(uart_rcvd_buf + uart_rcvd_pos, dlt_645_frame_msg_half_st, sizeof(dlt_645_frame_msg_half_st));
    uart_rcvd_pos += sizeof(dlt_645_frame_msg_half_st);
    parse_buf();

    printf("\n ****** dlt 645 half middle +++ ******\n ");
    
    memcpy(uart_rcvd_buf + uart_rcvd_pos, dlt_645_frame_msg_half_end, sizeof(dlt_645_frame_msg_half_end));
    uart_rcvd_pos += sizeof(dlt_645_frame_msg_half_end);

    printf("main start 645 half msg uart_rcvd_buf = %s, uart_rcvd_pos = %d\n", uart_rcvd_buf, uart_rcvd_pos);
    printf("\n ****** dlt 645 half end ******\n ");

#endif

//ALL TEST
#if 1

    printf("\n ****** dlt 698 and 645 start ******\n ");
    memcpy(uart_rcvd_buf + uart_rcvd_pos, dlt_645_frame_msg, sizeof(dlt_645_frame_msg));
    uart_rcvd_pos += sizeof(dlt_645_frame_msg);
    memcpy(uart_rcvd_buf + uart_rcvd_pos, dlt_698_frame_msg, sizeof(dlt_698_frame_msg));
    uart_rcvd_pos += sizeof(dlt_698_frame_msg);
    memcpy(uart_rcvd_buf + uart_rcvd_pos, dlt_645_frame_msg, sizeof(dlt_645_frame_msg));
    uart_rcvd_pos += sizeof(dlt_645_frame_msg);
    
    printf("main start 698 and 645 msg uart_rcvd_buf = %s, uart_rcvd_pos = %d\n", uart_rcvd_buf, uart_rcvd_pos);
    printf("\n ****** dlt 698 and 645 end ******\n ");

#endif

//Error msg TEST
#if 1
    printf("\n ****** dlt error msg start ******\n ");
    memcpy(uart_rcvd_buf + uart_rcvd_pos, error_frame_msg, sizeof(error_frame_msg));
    uart_rcvd_pos += sizeof(error_frame_msg);
    printf("main start error msg uart_rcvd_buf = %s, uart_rcvd_pos = %d\n", uart_rcvd_buf, uart_rcvd_pos);
    //parse_buf();
    printf("\n ****** dlt error msg end ******\n ");

#endif

    parse_buf();

    printf("main end msg uart_rcvd_buf = %s, uart_rcvd_pos = %d\n", uart_rcvd_buf, uart_rcvd_pos);
    
}
  • 15
    点赞
  • 119
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值