STM32-DMA数据传输(USART-ADC-数组)

一、DMA简介

DMA:直接存储器访问

  • 主要功能:

    把数据从一个位置搬移到另外一个位置,而且不占用CPU,即在传输数据的时候, CPU 可以干其他的事情。
    数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是 SRAM 或者是 FLASH。

    存储器到外设典型应用是:USART
    外设到存储器典型应用是:ADC

    在这里插入图片描述
    DMA 控制器包含了 DMA1 和 DMA2,其中 DMA1 有 7 个通道, DMA2 有 5 个通道,这里的通道可以理解为传输数据的一种管道。

  • 使用USART传输数据时

    数组的内容首先发送给CPU的CR1,CR2,CR3然后再发到串口

  • 使用DMA传输数据

    只需要CPU给DMA下达一个指令,数组内容即可通过串口发送出去

二、DMA 功能框图

在这里插入图片描述

(一)DMA请求

如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求, DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕

(二)DMA通道

  • DM1请求映射
    在这里插入图片描述
    如果使用串口,则对应通道2,3,4,5,6,7
    USART1的TX和RX分别对应DMA1的通道4和通道5(并不是随便使用)

  • DMA2请求映射
    在这里插入图片描述
    当多个DMA请求同时来的时候,则需要用到仲裁器来决定谁先执行

(三)DMA仲裁

仲裁器根据通道请求的优先级来启动外设/存储器的访问。

优先权管理分2个阶段:
● 软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:
─ 最高优先级
─ 高优先级
─ 中等优先级
─ 低优先级
● 硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4。

三、数据配置

  • DMA初始化结构体
    在这里插入图片描述

  • DMA数据配置

    使用 DMA,最核心就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是循环传输等等。

(一)数据从哪里来到哪里去?

在这里插入图片描述

  • DMA_PeripheralBaseAddr

    外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。

  • DMA_Memory0BaseAddr

    存储器地址,设定DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址。—如果定义了一个数组,数组名即为首地址

  • DMA_DIR

    传输方向选择,可选外设到存储器、存储器到外设。它设定 DMA_CCR 寄存器的DIR[1:0] 位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。

    DMA_DIR:规定外设是作为数据传输的目的地还是来源(数据传输方向)

     DMA_DIR_PeripheralDST	外设作为数据传输的目的地
     DMA_DIR_PeripheralSRC	外设作为数据传输的来源
    

(二)数据要传多少,传输的单位是什么?

在这里插入图片描述

  • DMA_BufferSize

    要传输的数据个数

  • DMA_PeripheralInc

    如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,(比如说外设地址定义了一个数组,地址需要递增),一般外设都是只有一个数据寄存器,所以一般不会使能该位。

  • DMA_MemoryInc

    如果配置为 DMA_MemoryInc_Enable,使能存储器地址自动递增功能(同样定义了一个数组,地址需要递增),我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。

  • DMA_PeripheralDataSize

    外设的数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位)—这是根据定义的外设变量位数来决定

  • DMA_MemoryDataSize

    存储器数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位)–根据定义的存储器变量位数决定

    当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小

(三)什么时候传输结束?

在这里插入图片描述

  • DMA_Mode

    DMA 传输模式选择,可选一次传输或者循环传输

    例如串口发送一个数组内容的数据就可以设置位单次传输
    例如我们的 ADC 采集是持续循环进行的,所以使用循环传输模式。

四、DMA三种应用

(一)从存储器到存储器(M-to-M)

当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。

DMA 外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址, DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。

实验目的:
我们先定义一个静态的源数据,存放在内部 FLASH,然后使用 DMA 传输把源数据拷贝到目标地址上(内部 SRAM),最后对比源数据和目标地址的数据,看看是否传输准确

  • 首先定义两个数组:一个作为外设,一个作为存储器

    内部FLASH是存放代码code
    内部SRAM是定义的变量

     /* 定义aSRC_Const_Buffer数组作为DMA传输数据源
      * const关键字将aSRC_Const_Buffer数组变量定义为常量类型
      * 表示数据存储在内部的FLASH中
      */
     const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {
                                         0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
                                         0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
                                         0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
                                         0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
                                         0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
                                         0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
                                         0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
                                         0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};
     /* 定义DMA传输目标存储器
      * 存储在内部的SRAM中																		
      */
     uint32_t aDST_Buffer[BUFFER_SIZE];
    

    aSRC_Const_Buffer[BUFFER_SIZE] 定义用来存放源数据,并且使用了 const 关键字修饰,即常量类型,使得变量是存储在内部 flash 空间上

  • DMA初始化部分

    源地址和目标地址使用之前定义的数组首地址,传输的数据量为宏 BUFFER_SIZE 决定,源和目标地址指针地址递增,使用一次传输模式不能循环传输,因为只有一个 DMA 通道,优先级随便设置,最后调用 DMA_Init 函数完成 DMA 的初始化配置

     //存储器到存储器						
     void DMA_mtm_config(void)
     {
     	//1-要初始化结构体肯定要定义一个结构体变量
     	DMA_InitTypeDef DMA_InitStruct;
     	//2、配置DMA时钟
     	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA1时钟 
     	
     	//数据从哪里来到哪里去(配置3个)
     	//源数据地址(把此数组看做外设)
     	DMA_InitStruct.DMA_PeripheralBaseAddr=(uint32_t)aSRC_Const_Buffer;//外设地址---是一个数组,数组名称就是首地址
     	//目标地址
     	DMA_InitStruct.DMA_MemoryBaseAddr=(uint32_t)aDST_Buffer;//存储器地址 --是一个空数组
     	//方向:外设作为源地址
     	DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方式
     	
     	//传多少,单位是多少
     	DMA_InitStruct.DMA_BufferSize= BUFFER_SIZE;//传输数目(要传输多少个)
     	
     	DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Enable ;//外设地址递增
     	DMA_InitStruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Word  ;//数据宽度,word是一个字32位
     	
     	//配置memory
     	DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable ;//内存地址递增
     	DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Word ;
     	
     	
     	//配置模式和优先级
     	DMA_InitStruct.DMA_Mode=DMA_Mode_Normal ;//正常模式或者是循环模式
     	DMA_InitStruct.DMA_Priority=DMA_Priority_High ;//共有四种优先级
     	DMA_InitStruct.DMA_M2M=DMA_M2M_Enable;//刚好是M-M模式
     	DMA_Init(DMA1_Channel6, &DMA_InitStruct);
     	//M-T-M模式可以是任意的通道
     	
     	DMA_ClearFlag(DMA1_FLAG_TC6);//先将这个标志位清楚
     	//3、使能DMA
     	DMA_Cmd(DMA1_Channel6, ENABLE);
     	
     }
    
  • 数据是否传输完成且无误,两个数组中的内容要进行比较

     /**
       * 判断指定长度的两个数据源是否完全相等,
       * 如果完全相等返回1,只要其中一对数据不相等返回0
     	*参数1是常量指针,是不变的
     	*参数2也是一个指针
     	*参数3是要比较多长
       */
     uint8_t Buffercmp(const uint32_t* pBuffer, 
                       uint32_t* pBuffer1, uint16_t BufferLength)
     {
       /* 数据长度递减 */
       while(BufferLength--)
       {
         /* 判断两个数据源是否对应相等 */
         if(*pBuffer != *pBuffer1)
         {
           /* 对应数据源不相等马上退出函数,并返回0 */
           return 0;
         }
         /* 递增两个数据源的地址指针 */
         pBuffer++;
         pBuffer1++;
       }
       /* 完成判断并且对应数据相对 */
       return 1;  
     }
    
  • 主函数部分的内容:

     int main(void)
      {	
     	uint8_t status=0;
     	delay_init();	    	 //延时函数初始化	  
     	LED_Init();		  	//初始化与LED连接的硬件接口
     	
     	DMA_mtm_config();//DMA初始化
     	 //在比较之前最后先检测数据是否传送完毕:使用 DMA_GetFlagStatus(uint32_t DMAy_FLAG);函数
     	 //使用的DMA1通道6
     	 while(DMA_GetFlagStatus(DMA1_FLAG_TC6)==RESET);//如果返回值位reset表示还没哟传输成功
     	 
     	status=Buffercmp(aSRC_Const_Buffer,aDST_Buffer,BUFFER_SIZE);//返回值是一个8位的
     	if(status==0)//表示失败
     	{
     		LED0=0;
     	}
     	else//表示成功
     	{
     		LED1=0;
     	}
     	while(1)
     	{
     	}
      }
    

如果两个数组中的内容一样、则点亮LED1灯作为信号,反之传输失败则点亮LED0灯作为信号。
STM32-DMA(存储器到存储器传输数据)–代码链接

(二)从存储器到外设(M-to-P)

当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。 DMA 外设寄存器的地址对应的就是串口数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址

实验目的:
我们先定义一个数据变量,存于 SRAM 中,然后通过 DMA 的方式传输到串口的数据寄存器,然后通过串口把这些数据发送到电脑的上位机显示出来。

串口初始化部分不再赘述
stm32串口自定义协议接收一串十六进制数据

DMA代码部分:

  • 首先需要定义一个发送数据的数组

     #define SendBuff_Size 5000
     u8 SendBuffe[SendBuff_Size];//定义一个发送数组
    
  • DMA初始化工作

    因为数据是从存储器到串口,所以设置存储器为源地址,串口的数据寄存器为目标地址,要发送的数据有很多且都先存储在存储器中,则存储器地址指针递增,串口数据寄存器只有一个,则外设地址地址不变,两边数据单位设置成一致,传输模式可选一次或者循环传输,只有一个 DMA 请求,优先级随便设

     //Memory->p(USART->DR)
     //通过手册查询串口的DR寄存器偏移地址是:0x04则外设地址是USART1_BASE+0x04
     void USART1_DMA_mtp_config(void)
     {
     	//1-要初始化结构体肯定要定义一个结构体变量
     	DMA_InitTypeDef DMA_InitStruct;
     	//2、配置DMA时钟(通过查看手册可以知道USART1_TX使用DMA1的通道4)
     	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA1通道4的时钟
     	
     	//3、数据从哪里来到哪里去(配置3个)
     	//外设地址(串口数据寄存器)--目标地址
     	DMA_InitStruct.DMA_PeripheralBaseAddr=(u32)&USART1->DR;//外设地址(或者USART1_BASE+0x04)
     	//存储器地址---源地址
     	DMA_InitStruct.DMA_MemoryBaseAddr=(uint32_t)SendBuffe;//存储器地址(指向了数组首地址)
     	//方向:存储器到外设(外设串口作为目的地)
     	DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralDST;//传输方式(MTP)
     	//规定外设是作为数据传输的目的地还是来源(数据传输方向)
     	//DMA_DIR_PeripheralDST	外设作为数据传输的目的地
     	//DMA_DIR_PeripheralSRC	外设作为数据传输的来源
     	
     	
     	//4、传多少,单位是多少
     	DMA_InitStruct.DMA_BufferSize= SendBuff_Size;//传输数目(数组的长度)
     	
     	//只有一个串口数据寄存器不需要递增,数组是U8类型,所以一次传输一个字节
     	DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable ;//外设地址b不需要递增
     	DMA_InitStruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte  ;//数据宽度,u8类型,一个字节
     	
     	//配置memory(定义了一个数组,发送一个会继续下一个,所以地址是递增)
     	DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable ;//内存地址递增
     	DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte  ;//一个字节
     	
     	
     	//5、配置模式和优先级
     	DMA_InitStruct.DMA_Mode=DMA_Mode_Normal ;//正常模式或者是循环模式
     	DMA_InitStruct.DMA_Priority=DMA_Priority_High ;//共有四种优先级
     	DMA_InitStruct.DMA_M2M=DMA_M2M_Disable  ;//不使用M-M模式
     	DMA_Init(DMA1_Channel4, &DMA_InitStruct);//串口1TX是DMA通道4
     	//
     	
     	DMA_ClearFlag(DMA1_FLAG_TC4);//先将这个标志位清除
     	//6、使能DMA
     	DMA_Cmd(DMA1_Channel4, ENABLE);
     	
     }
    
  • 主函数部分内容:

      int main(void)
      {	
    
     	 u16 i;
     	for(i=0;i<SendBuff_Size;i++)//对数组填充数据
     	{
     		SendBuffe[i]='b';
     	}
     	delay_init();	    	 //延时函数初始化	  
     	LED_Init();		  	//初始化与LED连接的硬件接口
     	uart_init(9600);
     	USART1_DMA_mtp_config();
     	 //等待串口发送数据(去usart头文件中找有关DMA的函数)
     	USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
     	 //把定义的变量发送到串口,串口通过TX引脚发送出去
     	
     	while(1)
     	{
     			//为了证明dma不占用CPU此处让led闪烁
     			LED0=0;
     			delay_ms(100);
     			LED0=1;
     			delay_ms(100);
     	}
     }
    

实验现象:串口助手可以接收到5000个数据–数据均为字符b,与此同时LED0在不停的闪烁

STM32+DMA+串口发送数据(存储器到外设数据传输)-代码链接

(三)外设到存储器(P-to-M)

当我们使用从外设到存储器传输时,以 ADC 采集为例。 DMA 外设寄存器的地址对应的就是 ADC数据寄存器的地址ADC_DR, DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址,方向我们设置外设为源地址。

STM32-ADC单通道采集数据(中断形式和DMA形式)–代码
代码参考ADC学习笔记整理部分

STM32-ADC(独立模式、双重模式)+DMA读取数据+部分基础知识-链接

  • 19
    点赞
  • 122
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个使用DMA方式传输ADC值的示例代码,用于在STM32微控制器上使用RS485通信协议传输ADC值。 ```c #include "stm32f10x.h" #include "stdio.h" #define RS485_GPIO GPIOA #define RS485_USART USART1 #define RS485_TX_PIN GPIO_Pin_9 #define RS485_RX_PIN GPIO_Pin_10 #define RS485_TX_ENABLE_PIN GPIO_Pin_8 #define ADC_GPIO GPIOA #define ADC_PIN GPIO_Pin_0 #define ADC_CHANNEL ADC_Channel_0 ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; USART_InitTypeDef USART_InitStructure; u16 ADC_Value; u8 RS485_TxBuffer[2]; void RCC_Configuration(void) { /* ADC and GPIO Clock Enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); /* USART and DMA Clock Enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); } void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; /* RS485 GPIO Configuration */ GPIO_InitStructure.GPIO_Pin = RS485_TX_PIN | RS485_RX_PIN | RS485_TX_ENABLE_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(RS485_GPIO, &GPIO_InitStructure); /* ADC GPIO Configuration */ GPIO_InitStructure.GPIO_Pin = ADC_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(ADC_GPIO, &GPIO_InitStructure); } void ADC_Configuration(void) { /* ADC Configuration */ ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); /* Enable ADC DMA */ ADC_DMACmd(ADC1, ENABLE); /* Enable ADC */ ADC_Cmd(ADC1, ENABLE); /* ADC Calibration */ ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); } void DMA_Configuration(void) { /* DMA Configuration */ DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(USART1->DR)); DMA_InitStructure.DMA_MemoryBaseAddr = (u32)RS485_TxBuffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = 2; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); } void USART_Configuration(void) { /* USART Configuration */ USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(RS485_USART, &USART_InitStructure); /* Enable USART */ USART_Cmd(RS485_USART, ENABLE); /* Enable USART Tx Enable Pin */ GPIO_WriteBit(RS485_GPIO, RS485_TX_ENABLE_PIN, Bit_SET); /* Enable USART DMA Tx Request */ USART_DMACmd(RS485_USART, USART_DMAReq_Tx, ENABLE); } void RS485_Send(u8 *data, u16 length) { /* Enable RS485 Tx Enable Pin */ GPIO_WriteBit(RS485_GPIO, RS485_TX_ENABLE_PIN, Bit_RESET); /* Configure DMA */ DMA_Configuration(); /* Load Data into DMA Buffer */ RS485_TxBuffer[0] = data[0]; RS485_TxBuffer[1] = data[1]; /* Enable DMA Channel */ DMA_Cmd(DMA1_Channel1, ENABLE); /* Wait for DMA Transmission to Complete */ while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); /* Disable DMA Channel */ DMA_Cmd(DMA1_Channel1, DISABLE); /* Disable RS485 Tx Enable Pin */ GPIO_WriteBit(RS485_GPIO, RS485_TX_ENABLE_PIN, Bit_SET); } int main(void) { /* RCC Configuration */ RCC_Configuration(); /* GPIO Configuration */ GPIO_Configuration(); /* ADC Configuration */ ADC_Configuration(); /* USART Configuration */ USART_Configuration(); while(1) { /* Wait for ADC Conversion to Complete */ while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); /* Read ADC Value */ ADC_Value = ADC_GetConversionValue(ADC1); /* Convert ADC Value to Data */ RS485_TxBuffer[0] = ADC_Value >> 8; RS485_TxBuffer[1] = ADC_Value & 0xFF; /* Send Data */ RS485_Send(RS485_TxBuffer, 2); /* Delay */ for(u32 i = 0; i < 100000; i++); } } ``` 在上面的代码中,我们使用STM32USARTADCDMA模块。使用DMA方式传输数据可以减少CPU的负载,从而提高系统性能。在主循环中,我们等待ADC转换完成并读取ADC值。然后,我们将ADC值转换为两个字节的数据,并使用RS485协议将数据发送出去。在发送数据之前,我们需要将RS485的Tx Enable引脚设置为低电平,以启用发送模式。发送完成后,我们将Tx Enable引脚设置为高电平,以禁用发送模式。 请注意,此示例代码仅用于演示目的。在实际应用中,您需要根据您的需求进行修改。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

永栀哇

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

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

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

打赏作者

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

抵扣说明:

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

余额充值