正点原子MiniFly Firmware V1.5开源四轴代码硬件部分分析1:BMP280模块。

BMP280是大气压传感器,大气压和海拔有关,所以测量的大气压可以转换成高度,minifly用BMP280来给四轴飞控定高。

BMP280寄存器

#define BMP280_I2C_ADDR					(0x76) //I2C通讯地址
#define BMP280_DEFAULT_CHIP_ID			(0x58) //ID值:0X58
#define BMP280_CHIP_ID					(0xD0)  /* Chip ID Register */
#define BMP280_RST_REG					(0xE0)  /* Softreset Register */
#define BMP280_STAT_REG					(0xF3)  /* Status Register */
#define BMP280_CTRL_MEAS_REG			(0xF4)  /* Ctrl Measure Register */
#define BMP280_CONFIG_REG				(0xF5)  /* Configuration Register */
#define BMP280_PRESSURE_MSB_REG			(0xF7)  /* Pressure MSB Register */
#define BMP280_PRESSURE_LSB_REG			(0xF8)  /* Pressure LSB Register */
#define BMP280_PRESSURE_XLSB_REG		(0xF9)  /* Pressure XLSB Register */
#define BMP280_TEMPERATURE_MSB_REG		(0xFA)  /* Temperature MSB Reg */
#define BMP280_TEMPERATURE_LSB_REG		(0xFB)  /* Temperature LSB Reg */
#define BMP280_TEMPERATURE_XLSB_REG		(0xFC)  /* Temperature XLSB Reg */

 Chip ID Register:寄存器内固定值为0x58,读取地址0xD0数据的时候,传感器返回0x58,代表身份辨认完毕。

Softreset Register:写入0xB6时,所有寄存器(除身份编号寄存器)数据全部清零。

status Register:状态寄存器,包含了两个位的数据。

状态寄存器位名描述
Bit 3measuring[0]转换发生时设定为1,结果转存后转换为0
Bit 0im_update[0]NVM数据拷贝时设定为1,拷贝结束后设置为0

Ctrl Measure Register:设备的运行状态寄存器

ctrl_meas 寄存器位名称描述
Bit 7, 6, 5osrs_t[2:0]控制温度的过采样
Bit 4, 3, 2osrs_p[2:0]控制气压的过采样
Bit 1, 0mode[1:0]控制电流的模式

Configuration Register:配置寄存器用于控制采样率,滤波器模式和通讯模式。 

config寄存器位名描述
Bit 7, 6, 5t_sb[2:0]设定normal_mode下的无效时间
Bit 4, 3, 2filter[2:0]设定IIR滤波器
Bit 0spi3w_en[0]spi通讯模式开启标志 1表示开启

 press(_msb, _lsb, _xlsb) :这几个寄存器就是存储ADC读出来的气压数据寄存器了,XLSB应该是小数位的意思,后面用来浮点转化用的。

press寄存器位名描述
0xF7press_msb[7:0]压力的msb部分 19:12
0xF8press_lsb[7:0]压力的lsb部分 11:4
0x F9 (bit 7, 6, 5, 4)press_xlsb[3:0]压力的xlsb部分 3:0

temp(_msb, _lsb, _xlsb) :温度数据寄存器

temp寄存器位名描述
0xFAtemp_msb[7:0]温度的msb部分 19:12
0xFBtemp_lsb[7:0]温度的lsb部分 11:4
0xFC(bit 7, 6, 5, 4)temp_xlsb[3:0]温度的xlsb部分 3:0

BMP280的三种模式

BMP280提供了三种工作模式:

  • 休眠模式(Sleep mode) :传感器进入休眠状态,停止采集数据,但寄存器的值不变;
  • 强制模式(Forced mode): 进行一次数据采集,采集完成后返回休眠模式;
  • 普通模式(Normal mode):循环进行多次数据采集。
#define BMP280_SLEEP_MODE				(0x00) //睡眠模式、强制模式、正常模式
#define BMP280_FORCED_MODE				(0x01)
#define BMP280_NORMAL_MODE				(0x03)

BMP280过采样设置

BMP280提供了温度和气压的过采样设置,其意义是一次测量多个数据,将这些数据进行求和平均得到输出值。例如将气压ADC过采样设置为×16,那么一次测量16个数据,将这16个数据进行求和平均得到最终的输出值。 

/*
过采样设置
 0 禁用
 1 过采样×1
 2 过采样×2
 4 过采样×4
 ....
*/
#define BMP280_OVERSAMP_SKIPPED			(0x00)
#define BMP280_OVERSAMP_1X				(0x01)
#define BMP280_OVERSAMP_2X				(0x02)
#define BMP280_OVERSAMP_4X				(0x03)
#define BMP280_OVERSAMP_8X				(0x04)
#define BMP280_OVERSAMP_16X				(0x05)

微调参数

每一个BMP280设备都存在一定的微调参数,这些微调参数在设备生产过程中,就已经被写入到设备的NVM中,并且不支持客户进行修改,在计算采集的温度数据时,都应该通过微调参数的调整。

//校准数据寄存器,总共是26个寄存器,存储了计算压力温度最终值的厂家校准数据,高低两位
typedef struct 
{
    u16 dig_T1;	/* calibration T1 data *///typedef uint16_t u16;
    s16 dig_T2; /* calibration T2 data */
    s16 dig_T3; /* calibration T3 data */
    u16 dig_P1;	/* calibration P1 data */
    s16 dig_P2; /* calibration P2 data */
    s16 dig_P3; /* calibration P3 data */
    s16 dig_P4; /* calibration P4 data */
    s16 dig_P5; /* calibration P5 data */
    s16 dig_P6; /* calibration P6 data */
    s16 dig_P7; /* calibration P7 data */
    s16 dig_P8; /* calibration P8 data */
    s16 dig_P9; /* calibration P9 data */
    s32 t_fine; /* calibration t_fine data */ 
} bmp280Calib;

BMP280 初始化

输入:I2C端口

输出:0:false 1:true 

细节在代码解析中

static bool isInit = false;

bool bmp280Init(I2C_Dev *i2cPort)
{	
    if (isInit)
        return true;

	I2Cx = i2cPort;
	devAddr = BMP280_I2C_ADDR;

    delay_xms(50);
	
	i2cdevReadByte(I2Cx, devAddr, BMP280_CHIP_ID, &bmp280ID);	/* 读取bmp280 ID*/
	
	if(bmp280ID == BMP280_DEFAULT_CHIP_ID) //若读出的ID等于默认值,则成功且打印显示。
		printf("BMP280 ID IS: 0x%X\n",bmp280ID);
    else 
        return false;

    /* 读取校准数据,一共有24个,地址从BMP280_TEMPERATURE_CALIB_DIG_T1_LSB_REG开始*/
    i2cdevRead(I2Cx, devAddr, BMP280_TEMPERATURE_CALIB_DIG_T1_LSB_REG, 24, (u8 *)&bmp280Cal);	

    /*设置BMP过采样因子 MODE,用到Ctrl Measure Register:设备的运行状态寄存器,上文有介绍*/ 
	i2cdevWriteByte(I2Cx, devAddr, BMP280_CTRL_MEAS_REG, BMP280_MODE);

	/*设置保持时间和滤波器分频因子,BMP280内容自带一个IIR滤波器,关于滤波器的具体笔者不了解太多。*/
	i2cdevWriteByte(I2Cx, devAddr, BMP280_CONFIG_REG, 5<<2);		/*配置IIR滤波*/
	
	
    isInit = true;//初始化完成,置1

    return true;
}

获取气压值

static void bmp280GetPressure(void)
{
    u8 data[BMP280_DATA_FRAME_SIZE]; //data[6]:分别为气压的和温度_msb, _lsb, _xlsb

    // read data from sensor
    i2cdevRead(I2Cx, devAddr, BMP280_PRESSURE_MSB_REG, BMP280_DATA_FRAME_SIZE, data);
    bmp280RawPressure = (s32)((((uint32_t)(data[0])) << 12) | (((uint32_t)(data[1])) << 4) | ((uint32_t)data[2] >> 4));//寄存器的值组合起来
    bmp280RawTemperature = (s32)((((uint32_t)(data[3])) << 12) | (((uint32_t)(data[4])) << 4) | ((uint32_t)data[5] >> 4));
}

 数据拟合

 BMP280读取到的数据是电压经过AD转换后的数值,并非最终的气压。需要根据气压和电压的曲线进行拟合,拟合系数储存在BMP280的寄存器当中。测量步骤:1、读取拟合系数;2、读取原始AD数据;3、进行拟合最后得出气压。具体如下:

u32 bmp280CompensateT(s32 adcT)
{
    s32 var1, var2, T;

    var1 = ((((adcT >> 3) - ((s32)bmp280Cal.dig_T1 << 1))) * ((s32)bmp280Cal.dig_T2)) >> 11;
    var2  = (((((adcT >> 4) - ((s32)bmp280Cal.dig_T1)) * ((adcT >> 4) - ((s32)bmp280Cal.dig_T1))) >> 12) * ((s32)bmp280Cal.dig_T3)) >> 14;
    bmp280Cal.t_fine = var1 + var2;
	
    T = (bmp280Cal.t_fine * 5 + 128) >> 8;

    return T;
}

// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
// Output value of "24674867" represents 24674867/256 = 96386.2 Pa = 963.862 hPa
u32 bmp280CompensateP(s32 adcP)
{
    int64_t var1, var2, p;
    var1 = ((int64_t)bmp280Cal.t_fine) - 128000;
    var2 = var1 * var1 * (int64_t)bmp280Cal.dig_P6;
    var2 = var2 + ((var1*(int64_t)bmp280Cal.dig_P5) << 17);
    var2 = var2 + (((int64_t)bmp280Cal.dig_P4) << 35);
    var1 = ((var1 * var1 * (int64_t)bmp280Cal.dig_P3) >> 8) + ((var1 * (int64_t)bmp280Cal.dig_P2) << 12);
    var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)bmp280Cal.dig_P1) >> 33;
    if (var1 == 0)
        return 0;
    p = 1048576 - adcP;
    p = (((p << 31) - var2) * 3125) / var1;
    var1 = (((int64_t)bmp280Cal.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
    var2 = (((int64_t)bmp280Cal.dig_P8) * p) >> 19;
    p = ((p + var1 + var2) >> 8) + (((int64_t)bmp280Cal.dig_P7) << 4);
    return (uint32_t)p;
}

 将压力转换成高度

一个公式转换具体没深究

(81条消息) 根据温度、气压计算海拔高度_hypsometric公式_大强强小强强的博客-CSDN博客

/**
 * 将压力转换为海平面以上高度(ASL) ,单位为米
 */
float bmp280PressureToAltitude(float* pressure/*, float* groundPressure, float* groundTemp*/)
{	
    if(*pressure > 0)
    {
		return 44330.f * (powf((1015.7f / *pressure), 0.190295f) - 1.0f);
    }
    else
    {
        return 0;
    }
}

获得最终数据

void bmp280GetData(float* pressure, float* temperature, float* asl)
{
    static float t;
    static float p;
	
	bmp280GetPressure();

	t = bmp280CompensateT(bmp280RawTemperature)/100.0;		
	p = bmp280CompensateP(bmp280RawPressure)/25600.0;		

	pressureFilter(&p,pressure);
	*temperature = (float)t;/*单位度*/
//	*pressure = (float)p ;	/*单位hPa*/	
	
	*asl=bmp280PressureToAltitude(pressure);	/*转换成海拔*/	
}

一些参考:(75条消息) STM32驱动BMP280模块_stm32 bmp280_bdjsm_hh的博客-CSDN博客

全部代码:BMP280.c,BMP280.h

#include <math.h>
#include "stdbool.h"
#include "delay.h"
#include "config.h"
#include "bmp280.h"

/********************************************************************************	 
 * 本程序只供学习使用,未经作者许可,不得用于其它任何用途
 * ALIENTEK MiniFly
 * BMP280驱动代码	
 * 正点原子@ALIENTEK
 * 技术论坛:www.openedv.com
 * 创建日期:2017/5/12
 * 版本:V1.3
 * 版权所有,盗版必究。
 * Copyright(C) 广州市星翼电子科技有限公司 2014-2024
 * All rights reserved
********************************************************************************/
/*
传感器测试范围:
温度:-45℃~+85℃
大气压强:0~20000hPa(百帕)
*/

/*
BMP280大气压传感器(我直接叫他高度传感器)看似很冷门,或许大家都觉得,大气压不是一个地区就那么一个值.测量它有什么用?
但是这个模块很神奇,它测量精度很高,大气压和所处海拔关系密切,但是精度高的特点使得它可以测你的所在高度,
你拿着传感器起身,他的ADC值会发生变化.大佬们就用它给飞控定高(也就是很秀的操作,四轴悬浮在那里,一动不动,很神奇吧!)
*/

/*bmp280 气压和温度过采样 工作模式*/
//过采样大概是多次采样,获取一个均值,用于滤波
#define BMP280_PRESSURE_OSR			(BMP280_OVERSAMP_8X)
#define BMP280_TEMPERATURE_OSR		(BMP280_OVERSAMP_8X)
#define BMP280_MODE					(BMP280_PRESSURE_OSR << 2 | BMP280_TEMPERATURE_OSR << 5 | BMP280_NORMAL_MODE)


static uint8_t devAddr;
static I2C_Dev *I2Cx;
static bool isInit; 


//校准数据寄存器,总共是26个寄存器,存储了计算压力温度最终值的厂家校准数据,高低两位
typedef struct 
{
    u16 dig_T1;	/* calibration T1 data *///typedef uint16_t u16;
    s16 dig_T2; /* calibration T2 data */
    s16 dig_T3; /* calibration T3 data */
    u16 dig_P1;	/* calibration P1 data */
    s16 dig_P2; /* calibration P2 data */
    s16 dig_P3; /* calibration P3 data */
    s16 dig_P4; /* calibration P4 data */
    s16 dig_P5; /* calibration P5 data */
    s16 dig_P6; /* calibration P6 data */
    s16 dig_P7; /* calibration P7 data */
    s16 dig_P8; /* calibration P8 data */
    s16 dig_P9; /* calibration P9 data */
    s32 t_fine; /* calibration t_fine data */ 用于计算补偿
} bmp280Calib;

bmp280Calib  bmp280Cal;

static u8 bmp280ID = 0;
static bool isInit = false;
static s32 bmp280RawPressure = 0; //初始气压
static s32 bmp280RawTemperature = 0;//初始温度

static void bmp280GetPressure(void);

bool bmp280Init(I2C_Dev *i2cPort)
{	
    if (isInit)
        return true;

	I2Cx = i2cPort;
	devAddr = BMP280_I2C_ADDR;

    delay_xms(50);
	
	i2cdevReadByte(I2Cx, devAddr, BMP280_CHIP_ID, &bmp280ID);	/* 读取bmp280 ID*///#define BMP280_CHIP_ID(0xD0)  /*  Chip ID Register */
	
	if(bmp280ID == BMP280_DEFAULT_CHIP_ID)
		printf("BMP280 ID IS: 0x%X\n",bmp280ID);
    else 
        return false;

    /* 读取校准数据 */
    i2cdevRead(I2Cx, devAddr, BMP280_TEMPERATURE_CALIB_DIG_T1_LSB_REG, 24, (u8 *)&bmp280Cal);	
		//设置BMP过采样因子 MODE 
	i2cdevWriteByte(I2Cx, devAddr, BMP280_CTRL_MEAS_REG, BMP280_MODE);
		//设置保持时间和滤波器分频因子
	i2cdevWriteByte(I2Cx, devAddr, BMP280_CONFIG_REG, 5<<2);		/*配置IIR滤波*/
	
//	printf("BMP280 Calibrate Registor Are: \r\n");
//	for(i=0;i<24;i++)
//		printf("Registor %2d: 0x%X\n",i,p[i]);
	
    isInit = true;

    return true;
}

static void bmp280GetPressure(void)
{
    u8 data[BMP280_DATA_FRAME_SIZE];

    // read data from sensor
    i2cdevRead(I2Cx, devAddr, BMP280_PRESSURE_MSB_REG, BMP280_DATA_FRAME_SIZE, data);
    bmp280RawPressure = (s32)((((uint32_t)(data[0])) << 12) | (((uint32_t)(data[1])) << 4) | ((uint32_t)data[2] >> 4));//寄存器的值组合起来
    bmp280RawTemperature = (s32)((((uint32_t)(data[3])) << 12) | (((uint32_t)(data[4])) << 4) | ((uint32_t)data[5] >> 4));
}
//数据补偿转化
//浮点补偿
// Returns temperature in DegC, resolution is 0.01 DegC. Output value of "5123" equals 51.23 DegC
// t_fine carries fine temperature as global value
u32 bmp280CompensateT(s32 adcT)
{
    s32 var1, var2, T;

    var1 = ((((adcT >> 3) - ((s32)bmp280Cal.dig_T1 << 1))) * ((s32)bmp280Cal.dig_T2)) >> 11;
    var2  = (((((adcT >> 4) - ((s32)bmp280Cal.dig_T1)) * ((adcT >> 4) - ((s32)bmp280Cal.dig_T1))) >> 12) * ((s32)bmp280Cal.dig_T3)) >> 14;
    bmp280Cal.t_fine = var1 + var2;
	
    T = (bmp280Cal.t_fine * 5 + 128) >> 8;

    return T;
}

// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
// Output value of "24674867" represents 24674867/256 = 96386.2 Pa = 963.862 hPa
u32 bmp280CompensateP(s32 adcP)
{
    int64_t var1, var2, p;
    var1 = ((int64_t)bmp280Cal.t_fine) - 128000;
    var2 = var1 * var1 * (int64_t)bmp280Cal.dig_P6;
    var2 = var2 + ((var1*(int64_t)bmp280Cal.dig_P5) << 17);
    var2 = var2 + (((int64_t)bmp280Cal.dig_P4) << 35);
    var1 = ((var1 * var1 * (int64_t)bmp280Cal.dig_P3) >> 8) + ((var1 * (int64_t)bmp280Cal.dig_P2) << 12);
    var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)bmp280Cal.dig_P1) >> 33;
    if (var1 == 0)
        return 0;
    p = 1048576 - adcP;
    p = (((p << 31) - var2) * 3125) / var1;
    var1 = (((int64_t)bmp280Cal.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
    var2 = (((int64_t)bmp280Cal.dig_P8) * p) >> 19;
    p = ((p + var1 + var2) >> 8) + (((int64_t)bmp280Cal.dig_P7) << 4);
    return (uint32_t)p;
}

#define FILTER_NUM	5
#define FILTER_A	0.1f

/*限幅平均滤波法*/
void pressureFilter(float* in, float* out)
{	
	static u8 i=0;
	static float filter_buf[FILTER_NUM]={0.0};
	double filter_sum=0.0;
	u8 cnt=0;	
	float deta;		
	
	if(filter_buf[i] == 0.0f)
	{
		filter_buf[i]=*in;
		*out=*in;
		if(++i>=FILTER_NUM)	i=0;
	} else 
	{
		if(i) deta=*in-filter_buf[i-1];
		else deta=*in-filter_buf[FILTER_NUM-1];
		
		if(fabs(deta)<FILTER_A)
		{
			filter_buf[i]=*in;
			if(++i>=FILTER_NUM)	i=0;
		}
		for(cnt=0;cnt<FILTER_NUM;cnt++)
		{
			filter_sum+=filter_buf[cnt];
		}
		*out=filter_sum /FILTER_NUM;
	}
}

void bmp280GetData(float* pressure, float* temperature, float* asl)
{
    static float t;
    static float p;
	
	bmp280GetPressure();

	t = bmp280CompensateT(bmp280RawTemperature)/100.0;		
	p = bmp280CompensateP(bmp280RawPressure)/25600.0;		

	pressureFilter(&p,pressure);
	*temperature = (float)t;/*单位度*/
//	*pressure = (float)p ;	/*单位hPa*/	
	
	*asl=bmp280PressureToAltitude(pressure);	/*转换成海拔*/	
}

/**
 * 将压力转换为海平面以上高度(ASL) ,单位为米
 */
float bmp280PressureToAltitude(float* pressure/*, float* groundPressure, float* groundTemp*/)
{	
    if(*pressure > 0)
    {
		return 44330.f * (powf((1015.7f / *pressure), 0.190295f) - 1.0f);
    }
    else
    {
        return 0;
    }
}
#ifndef __BMP280_H
#define __BMP280_H
#include "stm32f4xx.h"
#include "i2cdev.h"

/********************************************************************************	 
 * 本程序只供学习使用,未经作者许可,不得用于其它任何用途
 * ALIENTEK MiniFly
 * BMP280驱动代码	
 * 正点原子@ALIENTEK
 * 技术论坛:www.openedv.com
 * 创建日期:2017/5/12
 * 版本:V1.3
 * 版权所有,盗版必究。
 * Copyright(C) 广州市星翼电子科技有限公司 2014-2024
 * All rights reserved
********************************************************************************/
/*
身份编号寄存器(id)(0xD0):
寄存器内固定值为0x58,读取0xD0数据的时候,传感器返回0x58,代表身份辨认完毕。
*/

/*
BMP280读取到的数据是芯片内部 ADC转换后的原始数值,并非最终的大气压力值。需
要进行转换才能得到气压值,根据 BMP280的寄存器中的系数进行计算转换。
*/

#define BMP280_I2C_ADDR					(0x76)
#define BMP280_DEFAULT_CHIP_ID			(0x58) //ID寄存器,读出来好像是0X58

#define BMP280_CHIP_ID					(0xD0)  /* Chip ID Register */
#define BMP280_RST_REG					(0xE0)  /* Softreset Register */
#define BMP280_STAT_REG					(0xF3)  /* Status Register */
#define BMP280_CTRL_MEAS_REG			(0xF4)  /* Ctrl Measure Register */
#define BMP280_CONFIG_REG				(0xF5)  /* Configuration Register */
#define BMP280_PRESSURE_MSB_REG			(0xF7)  /* Pressure MSB Register */
#define BMP280_PRESSURE_LSB_REG			(0xF8)  /* Pressure LSB Register */
#define BMP280_PRESSURE_XLSB_REG		(0xF9)  /* Pressure XLSB Register */
#define BMP280_TEMPERATURE_MSB_REG		(0xFA)  /* Temperature MSB Reg */
#define BMP280_TEMPERATURE_LSB_REG		(0xFB)  /* Temperature LSB Reg */
#define BMP280_TEMPERATURE_XLSB_REG		(0xFC)  /* Temperature XLSB Reg */
/*
睡眠模式就是低功耗了,关了不测了;正常模式即连续测,内部一直转化,然后置状态位,我们读寄存器就可以读出原始数据了;
触发模式挺有用,你测一次后,传感器进入睡眠模式,下次再测只要重新设置为触发模式就可以再测一次
*/
#define BMP280_SLEEP_MODE				(0x00) //睡眠模式、触发模式、正常模式
#define BMP280_FORCED_MODE				(0x01)
#define BMP280_NORMAL_MODE				(0x03)

#define BMP280_TEMPERATURE_CALIB_DIG_T1_LSB_REG             (0x88) //矫正参数寄存器
#define BMP280_PRESSURE_TEMPERATURE_CALIB_DATA_LENGTH       (24)
#define BMP280_DATA_FRAME_SIZE			(6)

#define BMP280_OVERSAMP_SKIPPED			(0x00)
#define BMP280_OVERSAMP_1X				(0x01)
#define BMP280_OVERSAMP_2X				(0x02)
#define BMP280_OVERSAMP_4X				(0x03)
#define BMP280_OVERSAMP_8X				(0x04)
#define BMP280_OVERSAMP_16X				(0x05)


bool bmp280Init(I2C_Dev *i2cPort);
void bmp280GetData(float* pressure, float* temperature, float* asl);

u32 bmp280CompensateT(s32 adcT);
u32 bmp280CompensateP(s32 adcP);
void pressureFilter(float* in, float* out);/*限幅平均滤波法*/
float bmp280PressureToAltitude(float* pressure/*, float* groundPressure, float* groundTemp*/);

#endif


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值