中断及串口通信进阶

学习stm32中断、DMA通信原理和编程方法。使用stm32tubemx和HAL库分别完成以下编程练习:

一. 用stm32F103核心板的GPIOA端一管脚接一个LED,GPIOB端口一引脚接一个开关(用杜邦线模拟代替)。采用中断模式编程,当开关接高电平时,LED亮灯;接低电平时,LED灭灯。

HAL库实现

1.打开stm32tubeMX创建工程,选择stm32F103c8
在这里插入图片描述
2.设置PB6为中断模式
在这里插入图片描述

3.设置PA6为输出
在这里插入图片描述
4.设置引脚
设置引脚PA6,点击 GPIO---->PA6—>GPIO output level,GPIO output level设置为high。如图。
在这里插入图片描述
设置引脚PB6,点击 GPIO---->PB6—>GPIO mode,如图。
在这里插入图片描述
5.开启中断。
点击NVIC—>EXIT line[9:5] interrupts.
如图:
在这里插入图片描述
6.设置RCC:设置时钟源为外部时钟源。
点击RCC---->High speed clock.
在这里插入图片描述
7.设置SYS。
点击SYS—>Debug.
在这里插入图片描述
8.设置时钟树
点击Clock Configuration。
设置结果:
在这里插入图片描述
9.配置项目
点击Project Manager
设置项目名称及Toolchain/IDE及Min Version。设置结果如图
在这里插入图片描述
点击Code Generator。设置结果如图
在这里插入图片描述
生成。

代码

在这里插入图片描述
中断服务函数。根据前面的设置,上升沿就会触发这个函数,在这个函数中调用了另外一个名为HAL_GPIO_EXTI_Callback()函数,这个函数是回调函数,前面__weak表示此函数为虚函数,需重写。如下
在这里插入图片描述
在main.c中添加如下代码

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
	GPIO_PinState b6_pin = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6);  // 读取b6的状态
	switch (GPIO_Pin){
		case GPIO_PIN_6:
			HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, b6_pin);  // 将a6写入与b6相同的电位
			break;
	}	
}

编译
在这里插入图片描述
编译成功。
连线:
LED长脚——3V3
LED短脚——PA6
PB6——3V3——亮灯
PB6——GND——熄灯

烧录。
结果:
在这里插入图片描述
在这里插入图片描述

二. 采用串口中断方式重做上周的串口通信作业,分别实现:1)当stm32接收到字符“s”时,停止持续发送“hello windows!”; 当接收到字符“t”时,持续发送“hello windows!”;
1.打开stm32cubeMX,新建一个工程。
2.设置RCC
在这里插入图片描述
3.设置SYS在这里插入图片描述
4.设置USART1
在这里插入图片描述
5.设置NVIC
在这里插入图片描述
设置
在这里插入图片描述
创建成功。
4.main函数中的while循环里面添加传输代码

if(flag==1){
			//发送信息
			HAL_UART_Transmit(&huart1, (uint8_t *)&message, strlen(message),0xFFFF); 
			
			//延时
			HAL_Delay(1000);
		}

5.在main函数下面重写中断处理函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	
	//当输入的指令为0时,发送提示并改变flag
	if(c=='0'){
		flag=0;
		HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF); 
	}
	
	//当输入的指令为1时,发送提示并改变flag
	else if(c=='1'){
		flag=1;
		HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF); 
	}
	
	//当输入不存在指令时,发送提示并改变flag
	else {
		flag=0;
		HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF); 
	}

	//重新设置中断
		HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);  
}

完整的main.c代码

#include "main.h"
#include "usart.h"
#include "gpio.h"
#include <string.h>

void SystemClock_Config(void);

char c;//?? 0:??  1:??
char message[]="hello Windows\n";//????
char tips[]="CommandError\n";//??1
char tips1[]="Start.....\n";//??2
char tips2[]="Stop......\n";//??3
int flag='s';//?? 0:???? 1.????


int main(void)
{
	HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	
	
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);

	
  while (1)
  {
		if(flag=='t'){
			//????
			HAL_UART_Transmit(&huart1, (uint8_t *)&message, strlen(message),0xFFFF); 
		
			HAL_Delay(1000);
		}
  }
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	

	if(c=='s'){
		flag='s';
		HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF); 
	}
	
	
	else if(c=='t'){
		flag='t';
		HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF); 
	}
	
	
	else {
		flag='s';
		HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF); 
	}

	
		HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);  
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */


#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

编译无误
在这里插入图片描述
烧录及运行结果:
发送s时停止发送“hello world”
发送t时开始发送“hello world”
在这里插入图片描述

2)当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”
1.打开stm32cubeMX,新建一个工程。
2.设置RCC
在这里插入图片描述
3.设置SYS在这里插入图片描述
4.设置USART1
在这里插入图片描述
5.设置NVIC
在这里插入图片描述
设置
在这里插入图片描述
创建成功。
要将接收到的连续字符保存到一个字符数组里,进行判别匹配。写一个接收字符串的函数。

完整的main.c文件

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "Serial.h"

#include "string.h"
int flag=1;
int main(void)
{

	Serial_Init();
	
	
	
	while (1)
	{
		
		if (Serial_RxFlag == 1)
		{
			
			
			if (strcmp(Serial_RxPacket, "stop stm32!") == 0)
			{
				flag=0;
				printf("stop!");
			}
			else if (strcmp(Serial_RxPacket, "go stm32!") == 0)
			{
				flag=1;
				printf("go");
			}
			
			printf("over");
			Serial_RxFlag = 0;
		}

         if(flag==1)
					 
				 {printf("hello window!");
					 Delay_ms(1000);
				 }


	}
}

编译在这里插入图片描述
编译无误。
烧录,运行。
运行结果:发送“stop stm32!”,停止发送“hello world”

在这里插入图片描述
发送“go stm32!”继续发送“hello world”
在这里插入图片描述

三. STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据。

什么是DMA

DMA就是基于以上设想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作–计算、控制等。

直接存储器访问 (DMA)
DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。
DMA传输过程的初始化和启动由CPU完成,传输过程由DMA控制器来执行,无需CPU参与,从而节省CPU资源,提高利用率。

2.DMA传输方式
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:

外设到内存
内存到外设
内存到内存
外设到外设
普通模式
传输结束后(即要传输数据的数量达到零),将不再产生DMA操作。若
开始新的DMA传输,需在关闭DMA通道情况下,重新启动DMA传输。
循环模式
可用于处理环形缓冲区和连续数据流(例如ADC扫描模式)。当激活循
环模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值
进行加载, 并继续响应DMA请求。

DMA传输参数
数据传输需要:
1 数据的源地址
2 数据传输位置的目标地址
3 传递数据多少的数据传输量
4 进行多少次传输的传输模式

当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时 达到传输终点,结束DMA传输 ,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输。 
也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。

DMA数据传输的四个要素
① 传输源 :DMA数据传输的来源
② 传输目标:DMA数据传输的目的
③ 传输数量:DMA传输数据的数量
④ 触发信号:启动一次DMA数据传输的动作

DMA控制器特点
STM32F411微控制器具备两个DMA控制器:DMA1和DMA2,每个
控制器有8个数据流,每个数据流可以映射到8个通道(或请求);
每一个DMA控制器用于管理一个或多个外设的存储器访问请求,并通
过总线仲裁器来协调各个DMA请求的优先级;
数据流(stream)是用于连接传输源和传输目标的数据通路,每个数
据流可以配置为不同的传输源和传输目标,这些传输源和传输目标称为
通道(Channel);
具备16字节的FIFO。使能FIFO功能后,源数据先送入FIFO,达到FIFO
的触发阈值后,再传送到目标地址。

STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据。

1.创建一个新的工程。
2.设置RCC
点击RCC---->High Speed Clock…,
结果如图:
在这里插入图片描述
3.设置串口
在这里插入图片描述
使能中断
在这里插入图片描述
DMA设置
点击DMA Settings的Add添加通道,传输速率设置为中速Medium。
在这里插入图片描述
模式设置为Normal,右侧选择Memory
在这里插入图片描述
在System view下选择DMA
在这里插入图片描述
设置时钟
在这里插入图片描述
导出keil文件。
代码
在main.c文件添加代码

uint8_t Senbuff[] = "Hello world!";  //定义数据发送数组

在这里插入图片描述

  HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff));
  HAL_Delay(1000);

在这里插入图片描述

完整的main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;

DMA_HandleTypeDef hdma_memtomem_dma1_channel1;
/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
uint8_t Senbuff[] = "Hello world!";  

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
  HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff));
  HAL_Delay(1000);

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 460800;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * Enable DMA controller clock
  * Configure DMA for memory to memory transfers
  *   hdma_memtomem_dma1_channel1
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* Configure DMA request hdma_memtomem_dma1_channel1 on DMA1_Channel1 */
  hdma_memtomem_dma1_channel1.Instance = DMA1_Channel1;
  hdma_memtomem_dma1_channel1.Init.Direction = DMA_MEMORY_TO_MEMORY;
  hdma_memtomem_dma1_channel1.Init.PeriphInc = DMA_PINC_ENABLE;
  hdma_memtomem_dma1_channel1.Init.MemInc = DMA_MINC_ENABLE;
  hdma_memtomem_dma1_channel1.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  hdma_memtomem_dma1_channel1.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  hdma_memtomem_dma1_channel1.Init.Mode = DMA_NORMAL;
  hdma_memtomem_dma1_channel1.Init.Priority = DMA_PRIORITY_LOW;
  if (HAL_DMA_Init(&hdma_memtomem_dma1_channel1) != HAL_OK)
  {
    Error_Handler( );
  }

  /* DMA interrupt init */
  /* DMA1_Channel4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  /* DMA1_Channel5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


编译无误后烧录即可。
在这里插入图片描述
运行结果:
在这里插入图片描述
总结:学习需要耐心,即使一开始看着很不理解,但还是要耐心思考。写代码时头脑要清晰有条理。
参考链接:
https://blog.csdn.net/qq_46467126/article/details/121055475?spm=1001.2014.3001.5501
https://blog.csdn.net/qq_47281915/article/details/121024427
https://blog.csdn.net/qq_43279579/article/details/110138564

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32进阶之串口环形缓冲区实现FIFO。 在STM32中,串口通信是一种常见的通信方式。为了提高串口通信的效率,我们可以使用环形缓冲区来实现FIFO(先进先出)的数据存储与读取。 串口环形缓冲区的实现主要包括一个接收缓冲区和一个发送缓冲区。为了避免数据覆盖和数据丢失,接收缓冲区和发送缓冲区的大小应根据实际需求进行合理设置。 在接收数据时,当有新的数据到达时,将数据存储到接收缓冲区的当前位置,并将接收缓冲区的指针移动到下一个位置。如果接收缓冲区已满,则将指针重新指向缓冲区的起始位置。 在发送数据时,将要发送的数据存储到发送缓冲区的当前位置,并将发送缓冲区的指针移动到下一个位置。如果发送缓冲区已满,则等待缓冲区有空位继续发送数据。 通过使用环形缓冲区实现串口的FIFO机制,我们可以在数据发送过程中实现异步操作,提高了系统的响应速度和数据处理的能力。 需要注意的是,当接收缓冲区或发送缓冲区满时,应该有相应的处理机制,例如中断或轮询方式来检测缓冲区的状态,并对缓冲区进行相应的处理,以免数据丢失或覆盖。 总之,串口环形缓冲区的实现可以提高串口通信的效率,实现FIFO机制。通过合理设置缓冲区的大小和相应的处理机制,我们可以实现稳定可靠的数据传输。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值