STM32 串口DMA接收 Openmv / K210 整数、小数字符串数据 (基于HAL库)

平台: STM32 Cube IDE


前言

许多科创比赛中经常会有其他设备与STM32串口通讯的需求,比如可能需要Openmv / K210向STM32串口发送坐标的情况。下面我将介绍一种基于HAL库的串口DMA不定长数据收发和数据解读的方案。


一、工程配置

1.选择好芯片、配置好时钟和debug模式后,使能要用到的串口。
在这里插入图片描述
2.使能该串口的收发收发DMA:
在这里插入图片描述
3.使能串口全局中断,并生成工程文件。在这里插入图片描述

二、串口DMA部分代码

本部分代码修改自xia0816大佬写的《真正实现了STM32 HAL串口不定长数据的接收发送功能(DMA方式,不用限定单次接收长度和添加结束标志)》

1.源文件UART_DMA.c

#include "UART_DMA.h"
#include <string.h>
#include <stdarg.h>
#include <stdio.h>

uint8_t RxBuffer[UART_RX_BUF_SIZE] = {0};
uint8_t TxBuffer[UART_RX_BUF_SIZE] = {0};
uint8_t sendCompleteSign = 1;
uint8_t TxLen = 0;

void DataProcess(void)
{
	//在这里加入数据处理的函数

}

//到USARTx_IRQHandler中添加,如:
//void USART1_IRQHandler(void)
//{
//  /* USER CODE BEGIN USART1_IRQn 0 */
//  if(__HAL_UART_GET_FLAG(&USB_Huart,UART_FLAG_IDLE))
//  {
//	  HAL_UART_IdleCallback(&USB_Huart);
//  }
//
//  /* USER CODE END USART1_IRQn 0 */
//  HAL_UART_IRQHandler(&huartx);
//}
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart)
{
	__HAL_UART_CLEAR_IDLEFLAG(huart);
	{
		HAL_UART_DMAStop(huart);

        ProcessData();

        StartUartRxDMA();
	}
}

void ProcessData()
{
    uint32_t len = 0;

    //得到已经接收了多少个字节 = 总共要接收的字节数 - ?NDTR F1为CNDTR F4为NDTR
    #ifdef __STM32F1xx_HAL_H
    	len = UART_RX_BUF_SIZE - USB_Huart.hdmarx->Instance->CNDTR;
    	#define ProcessDataOK
    #endif

    #ifdef  __STM32F4xx_HAL_H
        len = UART_RX_BUF_SIZE - USB_Huart.hdmarx->Instance->NDTR;
    	#define ProcessDataOK
    #endif

    #ifndef ProcessDataOK
    	增加所用芯片的版本
    #endif

    if(len > 0)
    {
        if(sendCompleteSign == 1)
        {
#if UART_RXTX_Switch
            memset((void *)TxBuffer, 0, sizeof(TxBuffer));
            memcpy(TxBuffer, RxBuffer, len);
            TxLen = len;
            StartUartTxDMA();	//串口回显
#endif
            {
            	//在这里面加入数据处理的函数
            	DataProcess();
            }
        }
    }
}

void USB_DMA_printf(const char *format,...)
{
	uint32_t length;
	va_list args;

	va_start(args, format);
	length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer)+1, (char*)format, args);
	va_end(args);

	HAL_UART_Transmit_DMA(&USB_Huart,TxBuffer,length);
}

void USB_printf(const char *format,...)
{
	uint32_t length;
	va_list args;

	va_start(args, format);
	length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer)+1, (char*)format, args);
	va_end(args);

	HAL_UART_Transmit(&USB_Huart,TxBuffer,length,0xFFFF);
}

/**
  * @brief  Tx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
//  UNUSED(huart);
    if(huart == &USB_Huart)
    {
        sendCompleteSign = 1;
    }
  /* NOTE: This function should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
}

/**
  * @brief  Rx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
//  UNUSED(huart);
    if(huart == &USB_Huart)
    {
        ProcessData();

        StartUartRxDMA();
    }
  /* NOTE: This function should not be modified, when the callback is needed,
           the HAL_UART_RxCpltCallback could be implemented in the user file
   */
}

uint8_t UartTxData(UART_HandleTypeDef *huart, uint8_t *buf, const uint32_t len)
{
	HAL_StatusTypeDef status;
	uint8_t ret = 1;

	if(sendCompleteSign == 0 || len == 0)
	{
		return 0;
	}

    sendCompleteSign = 0;

	status = HAL_UART_Transmit_DMA(huart, (uint8_t*)buf, len);

	if(HAL_OK != status)
	{
		ret = 0;
	}

	return ret;
}

//启动DMA发送
uint8_t StartUartTxDMA()
{
    return UartTxData(&USB_Huart, TxBuffer, TxLen);
}

uint8_t UartRxData(UART_HandleTypeDef *huart, uint8_t *buf, const uint32_t len)
{
	HAL_StatusTypeDef status;
	uint8_t ret = 1;

	status = HAL_UART_Receive_DMA(huart, (uint8_t*)buf, len);
	if(HAL_OK != status)
	{
		ret = 0;
	}
	else
	{
		/* 开启空闲接收中断 */
	    __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);
	}

	return ret;
}

//启动DMA接收
uint8_t StartUartRxDMA()
{
    return UartRxData(&USB_Huart, RxBuffer, UART_RX_BUF_SIZE);
}

void ProcessData()中可能需要视所用芯片情况作部分修改,目前只测试过STM32F103VET6和STM32F411CEU6

    //得到已经接收了多少个字节 = 总共要接收的字节数 - ?NDTR F1为CNDTR F4为NDTR
    #ifdef __STM32F1xx_HAL_H
    	len = UART_RX_BUF_SIZE - USB_Huart.hdmarx->Instance->CNDTR;
    	#define ProcessDataOK
    #endif

    #ifdef  __STM32F4xx_HAL_H
        len = UART_RX_BUF_SIZE - USB_Huart.hdmarx->Instance->NDTR;
    	#define ProcessDataOK
    #endif

    #ifndef ProcessDataOK
    	增加所用芯片的版本
    #endif

2.头文件UART_DMA.h

#ifndef UART_DMA_UART_DMA_H_
#define UART_DMA_UART_DMA_H_

#include "main.h"

extern UART_HandleTypeDef huart1;	//修改为所用串口
#define USB_Huart huart1			//修改为所用串口

#define UART_RX_BUF_SIZE 128

#define UART_RXTX_Switch 1			//串口回显开关

/*
要在Cube中开串口全局中断和收发DMA
 */

extern uint8_t RxBuffer[UART_RX_BUF_SIZE];
extern uint8_t TxBuffer[UART_RX_BUF_SIZE];
extern uint8_t TxLen;

void USB_DMA_printf(const char *format,...);			//printf DMA方式
void USB_printf(const char *format,...);				//printf 普通方式
uint8_t UartTxData(UART_HandleTypeDef *huart, uint8_t *buf, const uint32_t len);
uint8_t StartUartRxDMA();								//接收DMA初始化
uint8_t StartUartTxDMA();								//不需要自己调用
void ProcessData();										//在里面添加数据处理函数
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart);	//到USARTx_IRQHandler中添加

#endif /* UART_DMA_UART_DMA_H_ */

3.stm32f1xx_it.c的修改

需要到stm32f1xx_it.c中的USARTx_IRQHandler添加几句话

//...
/* USER CODE BEGIN Includes */
#include "../UART_DMA/UART_DMA.h"
/* USER CODE END Includes */
//...
//...
/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  if(__HAL_UART_GET_FLAG(&USB_Huart,UART_FLAG_IDLE))
  {
	  HAL_UART_IdleCallback(&USB_Huart);
  }
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}
//...

4.串口收发DMA测试

(2022年1月25日补充)新版Cube MX有BUG,生成的初始化代码顺序有问题,见STM32 HAL串口DMA发送一直失败 —— 攻城狮_鲨鱼,故建议在生成的初始化代码前手动

  MX_DMA_Init();
  MX_USART1_UART_Init();


在这里插入图片描述

启动串口DMA接收

//...
  /* USER CODE BEGIN 2 */
  StartUartRxDMA();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */
//...

进入debug跑起来,将接收区缓存RxBuffer加入 现场表达式

//...
	uint8_t RxBuffer[UART_RX_BUF_SIZE] = {0};
	uint8_t TxBuffer[UART_RX_BUF_SIZE] = {0};
	uint8_t sendCompleteSign = 1;
	uint8_t TxLen = 0;
//...

在这里插入图片描述
在ProcessData()中的该处打上断点。
在这里插入图片描述
打开串口调试助手,选择好参数后发送一段测试字符串,可以发现该字符串已成功存入缓冲区。
在这里插入图片描述
随后又成功将数据通过DMA回显
在这里插入图片描述
至此串口DMA收发已成功实现。

而源文件中附有的USB_DMA_printf()和USB_printf()分别为DMA方式的printf和普通的printf

void USB_DMA_printf(const char *format,...)
{
	uint32_t length;
	va_list args;

	va_start(args, format);
	length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer)+1, (char*)format, args);
	va_end(args);

	HAL_UART_Transmit_DMA(&USB_Huart,TxBuffer,length);
}

void USB_printf(const char *format,...)
{
	uint32_t length;
	va_list args;

	va_start(args, format);
	length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer)+1, (char*)format, args);
	va_end(args);

	HAL_UART_Transmit(&USB_Huart,TxBuffer,length,0xFFFF);
}

效果如下:
在这里插入图片描述
为了进一步处理数据,下面介绍字符串数字提取的方案。

三、字符串数字提取代码

改进型代码见C语言字符串数字提取函数,支持负数、浮点数、科学记数法
实测double数据直接传参数据会出错,故采取了指针的方式。

1.源文件NumAndStr.c:

/*
 * NumAndStr.c
 *
 *  Created on: Mar 15, 2021
 *      Author: 乙酸氧铍
 */
#include "../NumAndStr/NumAndStr.h"
#include  <stdlib.h>

int32_t str2int(uint8_t * str, uint8_t flag, uint8_t no)
{
	uint8_t No = 1;
	uint8_t * Str = str;
	uint8_t NumTemp[TempIntLen];
	while(No!=no)
	{
		if(*Str == flag)
			No++;
		Str++;
	}
	No = 0;
	while(*Str != flag && *Str != '\r' && *Str != '\n' && *Str != '\0' && No < (TempIntLen - 1))
	{
		NumTemp[No] = *Str;
		Str++;
		No++;
	}
	NumTemp[No] = '\0';
	return atoi(NumTemp);
}

void str2double(uint8_t * str, uint8_t flag, uint8_t no, double * Output)
{
	uint8_t No = 1;
	uint8_t * Str = str;
	uint8_t NumTemp[TempDoubleLen];
	uint8_t NumTemp_int[TempDoubleLen];
	double OutputNum;
	while(No!=no)
	{
		if(*Str == flag)
			No++;
		Str++;
	}
	No = 0;
	while(*Str != flag && *Str != '\r' && *Str != '\n' && *Str != '\0' && No < (TempDoubleLen - 1))
	{
		NumTemp[No] = *Str;
		Str++;
		No++;
	}
	NumTemp[No] = '\0';
	NumTemp[(TempDoubleLen - 1)] = 0;
	No = 0;
	while(NumTemp[NumTemp[(TempDoubleLen - 1)]] != '\0' && NumTemp[(TempDoubleLen - 1)] < (TempDoubleLen - 1))
	{
		if(NumTemp[NumTemp[(TempDoubleLen - 1)]] == '.')
		{
			NumTemp[(TempDoubleLen - 1)]++;
			NumTemp_int[(TempDoubleLen - 1)] = NumTemp[(TempDoubleLen - 1)];
		}
		NumTemp_int[No] = NumTemp[NumTemp[(TempDoubleLen - 1)]];
		No++;
		NumTemp[(TempDoubleLen - 1)]++;
	}
	NumTemp_int[No]='\0';
	NumTemp[(TempDoubleLen - 1)] = NumTemp_int[(TempDoubleLen - 1)]++;
	OutputNum = (double)atoi(NumTemp_int);
	while(NumTemp[NumTemp[(TempDoubleLen - 1)]] != '\0')
	{
		OutputNum /= 10;
		NumTemp[(TempDoubleLen - 1)] ++;
	}
	*Output = OutputNum;
}

2.头文件NumAndStr.h:

/*
 * NumAndStr.h
 *
 *  Created on: Mar 15, 2021
 *      Author: 乙酸氧铍
 */

#ifndef NUMANDSTR_NUMANDSTR_H_
#define NUMANDSTR_NUMANDSTR_H_

#include "main.h"

#define TempDoubleLen 18
#define TempIntLen 11

/*
str:数字字符串首地址
flag:分隔符
no:第no个数字 从1开始计
Output: 小数存放地址
 */
extern int32_t str2int(uint8_t * str, uint8_t flag, uint8_t no);
extern void str2double(uint8_t * str, uint8_t flag, uint8_t no, double * Output);

#endif /* NUMANDSTR_NUMANDSTR_H_ */

str:数字字符串首地址
flag:分隔符
no:第no个数字 从1开始计
Output: 小数存放地址

3.测试:

修改UART_DMA.c中的DataProcess()函数

#include "../NumAndStr/NumAndStr.h"		//包含头文件

int32_t a,b,c;
double  d,e,f;
void DataProcess(void)
{
	//在这里加入数据处理的函数
	a = str2int(RxBuffer, ' ', 1);
	b = str2int(RxBuffer, ' ', 2);
	c = str2int(RxBuffer, ' ', 3);
	str2double(RxBuffer, ' ', 4, &d);
	str2double(RxBuffer, ' ', 5, &e);
	str2double(RxBuffer, ' ', 6, &f);
}

进入debug模式,监视变量a、b、c、d、e、f,使用串口调试助手再次发送一段测试字符串
效果如图所示:
在这里插入图片描述
可以看到六个数据都已成功存入对应的变量中,并成功回显。
在这里插入图片描述
且多次测试都能成功解读
在这里插入图片描述

四、Openmv / K210 发送、STM32接收测试

(示例) 平台: MaixPy IDE、K210 Maix Bit

K210 串口测试程序
延时500ms时

import utime
from board import board_info
from Maix import freq
from fpioa_manager import fm
from machine import UART

import random

fm.register(9,fm.fpioa.UART1_TX)
fm.register(10,fm.fpioa.UART1_RX)
UART_USB = UART(UART.UART1, 115200, 8, None, 1, timeout = 1000, read_buf_len = 128)

while(True):
    Tube_X = random.randint(-200,200)
    Tube_Y = random.randint(-200,200)
    Tube_Angle = random.random()
    print('%d %d %f'%(Tube_X, Tube_Y, Tube_Angle))
    UART_USB.write('%d %d %f\r\n'%(Tube_X, Tube_Y, Tube_Angle))
    utime.sleep_ms(500)

修改UART_DMA.c中的DataProcess()函数

int32_t Tube_X = 0, Tube_Y = 0;
double  Tube_Angle = 0;
void DataProcess(void)
{
	//在这里加入数据处理的函数
	Tube_X = str2int(RxBuffer, ' ', 1);
	Tube_Y = str2int(RxBuffer, ' ', 2);
	str2double(RxBuffer, ' ', 3, &Tube_Angle);
}

进入debug,如图所示,数据提取成功
在这里插入图片描述
延时15ms时

#...
while(True):
    Tube_X = random.randint(-200,200)
    Tube_Y = random.randint(-200,200)
    Tube_Angle = random.random()
    print('%d %d %f'%(Tube_X, Tube_Y, Tube_Angle))
    UART_USB.write('%d %d %f\r\n'%(Tube_X, Tube_Y, Tube_Angle))
    utime.sleep_ms(15)
    #...

在这里插入图片描述

总结

本文介绍了一种STM32 串口DMA收发并解读的方案,对CPU要求较小,只需自己选择分隔符号,不需要设计复杂的通信协议就能得到对应位置的数据,应该可以应用到使用STM32的多种科创比赛项目中去。

修订版本

UART_DMA.c

/*
 * UART_DMA.c
 *
 *  Created on: Mar 14, 2021
 *      Author: Royic
 */
#include "UART_DMA.h"
#include <string.h>
#include <stdarg.h>
#include <stdio.h>

uint8_t RxBuffer[UART_RX_BUF_SIZE] = {0};
uint8_t TxBuffer[UART_RX_BUF_SIZE] = {0};
uint8_t sendCompleteSign = 1;
uint8_t TxLen = 0;
uint8_t USE_UART_DMA = 0;

void DataProcess(UART_HandleTypeDef *huart, uint32_t Len)
{
	//在这里加入数据处理的函数
#ifdef USB_Huart_1
	if(huart == &USB_Huart_1)
    {
        ;
    }
#endif
#ifdef USB_Huart_2
	if(huart == &USB_Huart_2)
	{
		;
	}
#endif
}

//到USARTx_IRQHandler中添加,如:
//void USART1_IRQHandler(void)
//{
//  /* USER CODE BEGIN USART1_IRQn 0 */
//  if(__HAL_UART_GET_FLAG(&USB_Huart_1,UART_FLAG_IDLE))
//  {
//	  HAL_UART_IdleCallback(&USB_Huart_1);
//  }
//
//  /* USER CODE END USART1_IRQn 0 */
//  HAL_UART_IRQHandler(&huartx);
//}
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart)
{
	__HAL_UART_CLEAR_IDLEFLAG(huart);
	{
		HAL_UART_DMAStop(huart);

        ProcessData(huart);

        StartUartRxDMA(huart);
	}
}

void ProcessData(UART_HandleTypeDef *huart)
{
    uint32_t len = 0;

	len = UART_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx);

    if(len > 0)
    {
        if(sendCompleteSign == 1)
        {
#if UART_RXTX_Switch
            memset((void *)TxBuffer, 0, sizeof(TxBuffer));
            memcpy(TxBuffer, RxBuffer, len);
            TxLen = len;
            StartUartTxDMA(huart);	//串口回显
#endif
        }
        {
        	//在这里面加入数据处理的函数
        	DataProcess(huart, len);
        }
    }
}

void USB_DMA_printf(UART_HandleTypeDef *huart, const char *format,...)
{
	uint32_t length;
	va_list args;

	va_start(args, format);
	length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer)+1, (char*)format, args);
	va_end(args);

	HAL_UART_Transmit_DMA(huart,TxBuffer,length);
}

void USB_printf(UART_HandleTypeDef *huart, const char *format,...)
{
	uint32_t length;
	va_list args;

	va_start(args, format);
	length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer)+1, (char*)format, args);
	va_end(args);

	HAL_UART_Transmit(huart,TxBuffer,length,0xFFFF);
}

/**
  * @brief  Tx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
	sendCompleteSign = 1;
  /* NOTE: This function should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
}

/**
  * @brief  Rx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
	ProcessData(huart);
	StartUartRxDMA(huart);
  /* NOTE: This function should not be modified, when the callback is needed,
           the HAL_UART_RxCpltCallback could be implemented in the user file
   */
}

uint8_t UartTxData(UART_HandleTypeDef *huart, uint8_t *buf, const uint32_t len)
{
	HAL_StatusTypeDef status;
	uint8_t ret = 1;

	if(sendCompleteSign == 0 || len == 0)
	{
		return 0;
	}

    sendCompleteSign = 0;

	status = HAL_UART_Transmit_DMA(huart, (uint8_t*)buf, len);

	if(HAL_OK != status)
	{
		ret = 0;
	}

	return ret;
}

//启动DMA发送
uint8_t StartUartTxDMA(UART_HandleTypeDef *huart)
{
    return UartTxData(huart, TxBuffer, TxLen);
}

uint8_t UartRxData(UART_HandleTypeDef *huart, uint8_t *buf, const uint32_t len)
{
	HAL_StatusTypeDef status;
	uint8_t ret = 1;

	status = HAL_UART_Receive_DMA(huart, (uint8_t*)buf, len);
	if(HAL_OK != status)
	{
		ret = 0;
	}
	else
	{
		/* 开启空闲接收中断 */
	    __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);
	}

	return ret;
}

//启动DMA接收
uint8_t StartUartRxDMA(UART_HandleTypeDef *huart)
{
	USE_UART_DMA = 1;
    return UartRxData(huart, RxBuffer, UART_RX_BUF_SIZE);
}

UART_DMA.h

/*
 * UART_DMA.h
 *
 *  Created on: Mar 14, 2021
 *      Author: Royic
 */

#ifndef UART_DMA_UART_DMA_H_
#define UART_DMA_UART_DMA_H_

#include "main.h"

#define USB_Huart_1 huart1			//修改为所用串口
extern UART_HandleTypeDef USB_Huart_1;

#define USB_Huart_2 huart2			//修改为所用串口
extern UART_HandleTypeDef USB_Huart_2;

#define UART_RX_BUF_SIZE 128

#define UART_RXTX_Switch 0			//串口回显开关
//#define UART_DMA_Switch 0			

/*
要在Cube中开串口全局中断和收发DMA
 */

extern uint8_t RxBuffer[UART_RX_BUF_SIZE];
extern uint8_t TxBuffer[UART_RX_BUF_SIZE];
extern uint8_t TxLen;
extern uint8_t USE_UART_DMA;

void USB_DMA_printf(UART_HandleTypeDef *huart, const char *format,...);			//printf DMA方式
void USB_printf(UART_HandleTypeDef *huart, const char *format,...);				//printf 普通方式
uint8_t UartTxData(UART_HandleTypeDef *huart, uint8_t *buf, const uint32_t len);
uint8_t StartUartRxDMA(UART_HandleTypeDef *huart);								//接收DMA初始化
uint8_t StartUartTxDMA(UART_HandleTypeDef *huart);								//不需要自己调用
void ProcessData(UART_HandleTypeDef *huart);										//在里面添加数据处理函数
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart);	//到USARTx_IRQHandler中添加

#endif /* UART_DMA_UART_DMA_H_ */

  • 19
    点赞
  • 149
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乙酸氧铍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值