AS5600编码器的使用

AS5600为绝对编码器,即根据安装的磁铁角度不同,初始位置的读数不同,读出来的数是当前初始位置在0~360°的一个值。(复现记录自用)
流程图:
初始化:
校准标定磁块【checkMagnetPresence()函数,通过返回值确定磁块位置是否准确距离】→读取初始角度值【ReadRawAngle()读取当前的绝对位置角度值信息】→
进入循环(每次循环都是有耗时,故转速不能超过某特定的值):
读取更新当前的角度值【ReadRawAngle()函数】→计算转过的角度数值【correctAngle()函数,可以计算角度值】→计算、累计转过的圈数【checkQuadrant()函数,通每次读取的象限,来确定是否转过一周】→更新显示屏的数值信息【refreshDisplay()函数】
1、该工程使用的是IIC总线通信,显示屏和编码器连接同一接口。
2、代码一

// 编码器测量出旋转角度值,并在显示屏中显示出来
// AS5600编码器
#include <Wire.h> //This is for i2C
#include <SSD1306Ascii.h> //i2C OLED库
#include <SSD1306AsciiWire.h> //i2C OLED库,GitHub开源可下载

// i2C OLED
#define I2C_ADDRESS 0x3C
#define RST_PIN -1
SSD1306AsciiWire oled;
float OLEDTimer = 0; //屏幕刷新时间
//I2C pins:
//STM32: SDA: PB7 SCL: PB6
//Arduino: SDA: A4 SCL: A5

//---------------------------------------------------------------------------
//Magnetic sensor things
int magnetStatus = 0; //磁块3种状态 (MD, ML, MH)

int lowbyte; //raw angle 7:0
word highbyte; //raw angle 7:0 and 11:8
int rawAngle; //final raw angle 
float degAngle; //raw angle in degrees (360/4096 * [value between 0-4095])

int quadrantNumber, previousquadrantNumber; //quadrant IDs
float numberofTurns = 0; //number of turns
float correctedAngle = 0; //tared angle - based on the startup value
float startAngle = 0; //starting angle
float totalAngle = 0; //total absolute angular displacement
float previoustotalAngle = 0; //for the display printing

void setup()
{
  Serial.begin(115200); //start serial - tip: don't use serial if you don't need it (speed considerations)
  Wire.begin(); //start i2C  
	Wire.setClock(800000L); //fast clock

  checkMagnetPresence(); //check the magnet (blocks until magnet is found)校准磁极

  ReadRawAngle(); //make a reading so the degAngle gets updated
  startAngle = degAngle; //update startAngle with degAngle - for taring

  //------------------------------------------------------------------------------
  //OLED 部分
  #if RST_PIN >= 0
  	oled.begin(&Adafruit128x32, I2C_ADDRESS, RST_PIN);
  #else // RST_PIN >= 0
  	oled.begin(&Adafruit128x32, I2C_ADDRESS);
  #endif // RST_PIN >= 0

	oled.setFont(Adafruit5x7);
	oled.clear(); //clear display
	oled.set2X(); //double-line font size - better to read it
  oled.println("Welcome!"); //print a welcome message  
  oled.println("AS5600"); //print a welcome message
  delay(3000);
	OLEDTimer = millis(); //start the timer
  
}

void loop()
{    
    ReadRawAngle(); //ask the value from the sensor
    correctAngle(); //tare the value
    checkQuadrant(); //check quadrant, check rotations, check absolute angular position
    refreshDisplay();
    //delay(100); //wait a little - adjust it for "better resolution"

}

// 读取编码器的绝对位置的数值
void ReadRawAngle()
{ 
  //7:0 - bits
  Wire.beginTransmission(0x36); //connect to the sensor
  Wire.write(0x0D); //figure 21 - register map: Raw angle (7:0)
  Wire.endTransmission(); //end transmission
  Wire.requestFrom(0x36, 1); //request from the sensor
  
  while(Wire.available() == 0); //wait until it becomes available 
  lowbyte = Wire.read(); //Reading the data after the request
 
  //11:8 - 4 bits
  Wire.beginTransmission(0x36);
  Wire.write(0x0C); //figure 21 - register map: Raw angle (11:8)
  Wire.endTransmission();
  Wire.requestFrom(0x36, 1);
  
  while(Wire.available() == 0);  
  highbyte = Wire.read();
  
  //4 bits have to be shifted to its proper place as we want to build a 12-bit number
  highbyte = highbyte << 8; //shifting to left
  //What is happening here is the following: The variable is being shifted by 8 bits to the left:
  //Initial value: 00000000|00001111 (word = 16 bits or 2 bytes)
  //Left shifting by eight bits: 00001111|00000000 so, the high byte is filled in
  
  //Finally, we combine (bitwise OR) the two numbers:
  //High: 00001111|00000000
  //Low:  00000000|00001111
  //      -----------------
  //H|L:  00001111|00001111
  rawAngle = highbyte | lowbyte; //int is 16 bits (as well as the word)

  //We need to calculate the angle:
  //12 bit -> 4096 different levels: 360° is divided into 4096 equal parts:
  //360/4096 = 0.087890625
  //Multiply the output of the encoder with 0.087890625
  degAngle = rawAngle * 0.087890625; 
  Serial.println(degAngle);
  //Serial.print("Deg angle: ");
  //Serial.println(degAngle, 2); //absolute position of the encoder within the 0-360 circle
  
}

// 校对编码器的数值,因为编码器是0~360°的位置绝对值,需要计算其的圈数进行累加
void correctAngle()
{
  //recalculate angle
  correctedAngle = degAngle - startAngle; //this tares the position 没变动则为0;变动的数值

  if(correctedAngle < 0) //if the calculated angle is negative, we need to "normalize" it
  {
  correctedAngle = correctedAngle + 360; //correction for negative numbers (i.e. -15 becomes +345)
  }
  else
  {
    //do nothing
  }
  //Serial.print("Corrected angle: ");
  //Serial.println(correctedAngle, 2); //print the corrected/tared angle  
}

// 用相数的变化来计算旋转的圈数,用于校对
void checkQuadrant()
{
  /*
  //Quadrants:
  4  |  1
  ---|---
  3  |  2
  */

  //Quadrant 1
  if(correctedAngle >= 0 && correctedAngle <=90)
  {
    quadrantNumber = 1;
  }

  //Quadrant 2
  if(correctedAngle > 90 && correctedAngle <=180)
  {
    quadrantNumber = 2;
  }

  //Quadrant 3
  if(correctedAngle > 180 && correctedAngle <=270)
  {
    quadrantNumber = 3;
  }

  //Quadrant 4
  if(correctedAngle > 270 && correctedAngle <360)
  {
    quadrantNumber = 4;
  }
  //Serial.print("Quadrant: ");
  //Serial.println(quadrantNumber); //print our position "quadrant-wise"

  if(quadrantNumber != previousquadrantNumber) //if we changed quadrant
  {
    if(quadrantNumber == 1 && previousquadrantNumber == 4)
    {
      numberofTurns++; // 4 --> 1 transition: CW rotation
    }

    if(quadrantNumber == 4 && previousquadrantNumber == 1)
    {
      numberofTurns--; // 1 --> 4 transition: CCW rotation
    }
    //this could be done between every quadrants so one can count every 1/4th of transition

    previousquadrantNumber = quadrantNumber;  //update to the current quadrant
  
  }  
  //Serial.print("Turns: ");
  //Serial.println(numberofTurns,0); //number of turns in absolute terms (can be negative which indicates CCW turns)  

  //after we have the corrected angle and the turns, we can calculate the total absolute position
  totalAngle = (numberofTurns*360) + correctedAngle; //number of turns (+/-) plus the actual angle within the 0-360 range
  //Serial.print("Total angle: ");
  //Serial.println(totalAngle, 2); //absolute position of the motor expressed in degree angles, 2 digits
}

// 初始时对MCU进行锁定,确定其位置进行标定
void checkMagnetPresence()
{  
  //This function runs in the setup() and it locks the MCU until the magnet is not positioned properly
  // 他的函数在setup()中运行,它锁定MCU,直到磁铁没有正确定位  
  while((magnetStatus & 32) != 32) //while the magnet is not adjusted to the proper distance - 32: MD = 1
                                   // 磁铁位置是否调整到合适的位置。
  {  
    magnetStatus = 0; //reset reading

    Wire.beginTransmission(0x36); //connect to the sensor
    Wire.write(0x0B); //figure 21 - register map: Status: MD ML MH
    Wire.endTransmission(); //end transmission
    Wire.requestFrom(0x36, 1); //request from the sensor

    while(Wire.available() == 0); //wait until it becomes available 
    magnetStatus = Wire.read(); //Reading the data after the request

    //Serial.print("Magnet status: ");
    //Serial.println(magnetStatus, BIN); //print it in binary so you can compare it to the table (fig 21)      
  }      
  
  //Status register output: 0 0 MD ML MH 0 0 0  
  //MH: Too strong magnet - 100111 - DEC: 39 
  //ML: Too weak magnet - 10111 - DEC: 23     
  //MD: OK magnet - 110111 - DEC: 55

  //Serial.println("Magnet found!");
  //delay(1000);  
}

void refreshDisplay()
{
  if (millis() - OLEDTimer > 100) //chech if we will update at every 100 ms
	{ 
    if(totalAngle != previoustotalAngle) //if there's a change in the position*
    {
        oled.clear(); //delete the content of the display
        oled.println(totalAngle); //print the new absolute position
        OLEDTimer = millis(); //reset timer 	
        previoustotalAngle = totalAngle; //update the previous value
    }
	}
	else
	{
		//skip
	}
  //*idea: you can define a certain tolerance for the angle so the screen will not flicker
  //when there is a 0.08 change in the angle (sometimes the sensor reads uncertain values)
}

二、AS5600利用数码轮同步转动

1、解释
旋转脉冲编码器,步进电机同步旋转到对应的位置,而编码器起到测量反馈的作用。

2、代码二

#include <Wire.h> //This is for i2C
#include <SSD1306Ascii.h> //i2C OLED
#include <SSD1306AsciiWire.h> //i2C OLED

// i2C OLED
#define I2C_ADDRESS 0x3C
#define RST_PIN -1
SSD1306AsciiWire oled;
float OLEDTimer = 0; //Timer for the screen refresh
//I2C pins:
//STM32: SDA: PB7 SCL: PB6
//Arduino: SDA: A4 SCL: A5

//---------------------------------------------------------------------------
//Magnetic sensor things
int magnetStatus = 0; //value of the status register (MD, ML, MH)

int lowbyte; //raw angle 7:0
word highbyte; //raw angle 7:0 and 11:8
int rawAngle; //final raw angle 
float degAngle; //raw angle in degrees (360/4096 * [value between 0-4095])

int quadrantNumber, previousquadrantNumber; //quadrant IDs
float numberofTurns = 0; //number of turns
float correctedAngle = 0; //tared angle - based on the startup value
float startAngle = 0; //starting angle
float totalAngle = 0; //total absolute angular displacement
float previoustotalAngle = 0; //for the display printing
float encoderTimer = 0;
//---------------------------------------------------------------------------
int pinA = PB10; // Pin A of the encoder
int pinB = PB11; // Pin B of the encoder

//CNC Decoder behavior
// CNC编译器特性
//CW rotation: output of B is half square wave delayed from output of A
// CW旋转模式下:B输出比A的输出延迟半个方波
//CCW rotation: output of A is half square wave delayed from output of B
// CCW旋转模式下:A输出比B的输出延迟半个方波

//The pulse generator's output can be DIRECTLY wired to the step and dir pins.
//脉冲发生器的输出可以直接连接到step和dir引脚。 
//This means that the microcontroller can be omitted!!! - of course there won't be any feedback then
//这意味着可以省略微控制器!! 当然那时不会有任何反馈  

volatile int numberofclicks = 0; //Stores the number of click done by the encoder. 1 turn = 100 clicks
                                 // 存储编码器完成的点击次数。 1回合= 100次点击  
int previous_numberofclicks = 0; //Stores the "previous" number of clicks. Helps us to see if the encoder was moved
                                 // 存储“以前”的点击次数。 帮助我们确定编码器是否被移动过  


//--Stepper motor related 步进电机相关----------------------------------------------------------
#include <AccelStepper.h>
AccelStepper stepper(1, PA9, PA8);// pulses/steps 9; Direction 8 
const int stepperEnablePin = PB12;  //enable/disable pin for the stepper motor driver
//remember that for Arduino, you don't need the "PA" and "PB" prefixes. Just use 1,2,3...etc.

void setup()
{
  pinMode(pinA, INPUT_PULLUP); //A terminal of the CNC wheel
  pinMode(pinB, INPUT_PULLUP); //B terminal of the CNC wheel

  attachInterrupt(digitalPinToInterrupt(pinA), pinAInterrupt, RISING); //pin A is an interrupt 低电平变高电平触发,中断函数被触发pinAInterrupt()。

  Serial.begin(115200); //start serial - tip: don't use serial if you don't need it (speed considerations)
  Wire.begin(); //start i2C  
  Wire.setClock(800000L); //fast clock
  //General remark on i2C: it seems that the i2C interferes with the attachInterrupt() in some way causing
  //strange readings if the i2C-related hardware is read too often (in every loop iteration). 
  // 注意的bug: 似乎i2C以某种方式干扰了attachInterrupt()导致;
  // 如果i2c相关的硬件被读得太频繁(在每个循环迭代中),则会出现奇怪的读数。

  checkMagnetPresence(); //check the magnet (blocks until magnet is found)

  ReadRawAngle(); //make a reading so the degAngle gets updated
  startAngle = degAngle; //update startAngle with degAngle - for taring

  //------------------------------------------------------------------------------
  //OLED part
  #if RST_PIN >= 0
  	oled.begin(&Adafruit128x32, I2C_ADDRESS, RST_PIN);
  #else // RST_PIN >= 0
  	oled.begin(&Adafruit128x32, I2C_ADDRESS);
  #endif // RST_PIN >= 0

	oled.setFont(Adafruit5x7);
	oled.clear(); //clear display
	oled.set2X(); //double-line font size - better to read it 建立两行
    oled.println("Welcome!"); //print a welcome message  
    oled.println("AS5600"); //print a welcome message

  //Stepper setup---------------------------------------------------------
	stepper.setSpeed(1000); //SPEED = Steps / second
	stepper.setMaxSpeed(1000); //SPEED = Steps / second
	stepper.setAcceleration(5000); //ACCELERATION = Steps /(second)^2  
	pinMode(stepperEnablePin, OUTPUT); //enable/disable pin is defined as an output
	digitalWrite(stepperEnablePin, LOW); //enable motor current
	//disabling the current can prevent the driver and the motor running hot
	//on the other hand, it can lead to inaccuracies because the motor is not held at place when it is not under power  
    delay(2000);
	OLEDTimer = millis(); //start the timer
    encoderTimer = millis(); //start encoder timer
  
} 

void loop()
{    
  if(millis()- encoderTimer > 125) //125 ms will be able to make 8 readings in a sec which is enough for 60 RPM
                                   //60转每分钟
  {    
    ReadRawAngle(); //ask the value from the sensor
    correctAngle(); //tare the value
    checkQuadrant(); //check quadrant, check rotations, check absolute angular position        
    
    encoderTimer = millis();  

    /*A little brainstorm on determining the required delay
     * The above 3 functions require about 300-310 us to finish
     * They mess up the interrupt of the CNC encoder due to the i2C communication
     * Therefore it is not good if they are called very often
     * We want to detect at least every rotations of the shaft
     * I say (arbitrarily), that we need to detect at least 2 angles in each quadrants, so in 1 turn of the shaft, there are 8 readings
     * 我说(随意地),我们需要在每个象限中检测至少两个角度,所以在轴的一圈中,有8个读数  
     * 8 readings per turn can be converted into readings per second based on the expected highest speed
     * 每轮8个读数可转换为基于预期的最高速度的每秒读数  
     * Example:
     * 60 RPM = 60/60 RPS (rounds per seconds) = 1 RPS
     * 1 round per second -> 8 reading per second -> 1 second/8 readings = 0.125 s = 125 ms is the frequency of readings
     * 
     * Example 2:
     * 
     * 100 RPM = 100/60 = 1.667 RPS
     * 1 round = 0.599 s -> 0.599 s/ 8 readings = 74.98 ~ 75 ms. 
     * Check: 60/100 = 0.6 -> 75/125 = 0.6.    
     */
    
  }
  
  refreshDisplay(); //refresh the display - won't refresh until certain conditions are not fulfilled  
                    // 刷新显示-在某些条件未满足之前不会刷新  
	while (stepper.distanceToGo() != 0) //This blocks the rest of the code!
	{
		stepper.runSpeedToPosition(); //Runs to the target position defined by the moveTo() function	
    //does not use accelerations	//运行到moveTo()函数定义的目标位置 ,不使用加速  
	}
 
}



void ReadRawAngle()
{ 
  //7:0 - bits
  Wire.beginTransmission(0x36); //connect to the sensor
  Wire.write(0x0D); //figure 21 - register map: Raw angle (7:0)
  Wire.endTransmission(); //end transmission
  Wire.requestFrom(0x36, 1); //request from the sensor
  
  while(Wire.available() == 0); //wait until it becomes available 
  lowbyte = Wire.read(); //Reading the data after the request
 
  //11:8 - 4 bits
  Wire.beginTransmission(0x36);
  Wire.write(0x0C); //figure 21 - register map: Raw angle (11:8)
  Wire.endTransmission();
  Wire.requestFrom(0x36, 1);
  
  while(Wire.available() == 0);  
  highbyte = Wire.read();
  
  //4 bits have to be shifted to its proper place as we want to build a 12-bit number
  highbyte = highbyte << 8; //shifting to left
  //What is happening here is the following: The variable is being shifted by 8 bits to the left:
  //Initial value: 00000000|00001111 (word = 16 bits or 2 bytes)
  //Left shifting by eight bits: 00001111|00000000 so, the high byte is filled in
  
  //Finally, we combine (bitwise OR) the two numbers:
  //High: 00001111|00000000
  //Low:  00000000|00001111
  //      -----------------
  //H|L:  00001111|00001111
  rawAngle = highbyte | lowbyte; //int is 16 bits (as well as the word)

  //We need to calculate the angle:
  //12 bit -> 4096 different levels: 360° is divided into 4096 equal parts:
  //360/4096 = 0.087890625
  //Multiply the output of the encoder with 0.087890625
  degAngle = rawAngle * 0.087890625; 
  
  //Serial.print("Deg angle: ");
  //Serial.println(degAngle, 2); //absolute position of the encoder within the 0-360 circle
  
}

void correctAngle()
{
  //recalculate angle
  correctedAngle = degAngle - startAngle; //this tares the position

  if(correctedAngle < 0) //if the calculated angle is negative, we need to "normalize" it
  {
  correctedAngle = correctedAngle + 360; //correction for negative numbers (i.e. -15 becomes +345)
  }
  else
  {
    //do nothing
  }
  //Serial.print("Corrected angle: ");
  //Serial.println(correctedAngle, 2); //print the corrected/tared angle  
}

void checkQuadrant()
{
  /*
  //Quadrants:
  4  |  1
  ---|---
  3  |  2
  */

  //Quadrant 1
  if(correctedAngle >= 0 && correctedAngle <=90)
  {
    quadrantNumber = 1;
  }

  //Quadrant 2
  if(correctedAngle > 90 && correctedAngle <=180)
  {
    quadrantNumber = 2;
  }

  //Quadrant 3
  if(correctedAngle > 180 && correctedAngle <=270)
  {
    quadrantNumber = 3;
  }

  //Quadrant 4
  if(correctedAngle > 270 && correctedAngle <360)
  {
    quadrantNumber = 4;
  }
  //Serial.print("Quadrant: ");
  //Serial.println(quadrantNumber); //print our position "quadrant-wise"

  if(quadrantNumber != previousquadrantNumber) //if we changed quadrant
  {
    if(quadrantNumber == 1 && previousquadrantNumber == 4)
    {
      numberofTurns++; // 4 --> 1 transition: CW rotation
    }

    if(quadrantNumber == 4 && previousquadrantNumber == 1)
    {
      numberofTurns--; // 1 --> 4 transition: CCW rotation
    }
    //this could be done between every quadrants so one can count every 1/4th of transition

    previousquadrantNumber = quadrantNumber;  //update to the current quadrant
  
  }  
  //Serial.print("Turns: ");
  //Serial.println(numberofTurns,0); //number of turns in absolute terms (can be negative which indicates CCW turns)  

  //after we have the corrected angle and the turns, we can calculate the total absolute position
  totalAngle = (numberofTurns*360) + correctedAngle; //number of turns (+/-) plus the actual angle within the 0-360 range
  //Serial.print("Total angle: ");
  //Serial.println(totalAngle, 2); //absolute position of the motor expressed in degree angles, 2 digits
}

void checkMagnetPresence()
{  
  //This function runs in the setup() and it locks the MCU until the magnet is not positioned properly

  while((magnetStatus & 32) != 32) //while the magnet is not adjusted to the proper distance - 32: MD = 1
  {
    magnetStatus = 0; //reset reading

    Wire.beginTransmission(0x36); //connect to the sensor
    Wire.write(0x0B); //figure 21 - register map: Status: MD ML MH
    Wire.endTransmission(); //end transmission
    Wire.requestFrom(0x36, 1); //request from the sensor 

    while(Wire.available() == 0); //wait until it becomes available 
    magnetStatus = Wire.read(); //Reading the data after the request

    //Serial.print("Magnet status: ");
    //Serial.println(magnetStatus, BIN); //print it in binary so you can compare it to the table (fig 21)      
  }      
  
  //Status register output: 0 0 MD ML MH 0 0 0  
  //MH: Too strong magnet - 100111 - DEC: 39 
  //ML: Too weak magnet - 10111 - DEC: 23     
  //MD: OK magnet - 110111 - DEC: 55

  //Serial.println("Magnet found!");
  delay(1000);  
}

void refreshDisplay()
{
  if (millis() - OLEDTimer > 250) //chech if we will update at every 100 ms 检查我们是否每100毫秒更新一次
	{ 
    if(totalAngle != previoustotalAngle || previous_numberofclicks != numberofclicks) //if there's a change in the position*
    //if(previous_numberofclicks != numberofclicks) //if there's a change in the position*
    {
        oled.clear(); //delete the content of the display
        oled.print("M: "); //M: Magnet signal (Degrees)电磁信号(度)
        oled.println(totalAngle); //print the new absolute position
        oled.print("W: "); //W: Wheel signal (Clicks) (轮的信号:点击数
        oled.println(numberofclicks);
        OLEDTimer = millis(); //reset timer 重置时钟	
        previoustotalAngle = totalAngle; //update the previous value
        previous_numberofclicks = numberofclicks; //update current position
    }
	}
	else
	{
		//skip
	}
  //*idea: you can define a certain tolerance for the angle so the screen will not flicker
  //when there is a 0.08 change in the angle (sometimes the sensor reads uncertain values)
  // //*想法:你可以定义一定的角度公差,这样屏幕就不会闪烁  
  //当角度有0.08的变化时(有时传感器读取不确定值)  
}

void pinAInterrupt()
{
	//When pin A's wave is detected...
    //当检测到引脚A的波时…


	if (digitalRead(pinB) == 0) //if B is LOW, it means that pin A's wave occured first -> CW rotation occured (I had to change it because of the stepper motor)
	                            //如果B是低的,这意味着引脚A的波先发生-> CW旋转发生(我不得不改变它,因为步进电机)  
    {
		numberofclicks++; //increase value 
		//Serial.println(numberofclicks); //do not use delays or prints in the final code, use it only for debugging/developing
	}
	else //if B is HIGH, it means that pin B's wave occured first. So, when pin A has a rising edge, pin B is alreadi high -> CCW rotation
	     //如果B为高,则表示B引脚的波先出现。 因此,当引脚A有上升边时,引脚B已经是高的-> CCW旋转  
    {
		numberofclicks--; //decrease value
		//Serial.println(numberofclicks);
	}
  //Serial.println(numberofclicks);
  stepper.moveTo(-1*numberofclicks); //Updates the "go to" position of the stepper motor - absolute value
  //The above moveTo() function means that if the numberofclick variable = 936, then the stepper motor will be
  //936 steps away from the origin. 
  //更新步进电机的“go to”位置-绝对值  
  //上面的moveTo()函数的意思是,如果numberofclick变量= 936,则步进电机将  
  //距离原点936步。  
  • 11
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值