ESP32 IDF开发 应用篇⑫Wifi STA模式和AP模式的使用


别迷路-导航栏
快速导航找到你想要的(文章目录)

此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。

1、博主写这篇技术文章的目的:

(1)、了解 WIFI的3中工作模式;
(2)、掌握 esp32wifi的基本使用;

2、 概述

在前面章节已经为大家介绍了ESP32的基本应用,idf框架已经有了基本认识。从本章开始将带领大家一起探索esp32的核心功能wifi。
(1) Wifi的3中工作模式
•Station模式,也叫站点模式;
•AP模式,也叫Soft-AP模式,可以理解为WiFi热点模式;
•以上两种的集合模式,Station 兼Soft-AP,也是Mesh NetWork的实现基础;
任何基于ESP32的WiFi功能开发,都是基于上面其中一种工作模式来进行开发。所以,它们是我们WiFi基础学习的重点。
(2) 什么是STA模式
Station, 类似于无线终端,sta本身并不接受无线的接入,它可以连接到AP,一般无线网卡即工作在该模式。
(3) 什么是AP模式
Access Point,提供无线接入服务,允许其它无线设备接入,提供数据访问,一般的无线路由/网桥工作在该模式下。AP和AP之间允许相互连接。
简单理解AP就是路由器,STA就是手机,STA连接AP。

3、 Wifi库的介绍

有关wifi详细函数请参esp-idf\components\esp_wifi\include\esp_wifi.h
在这里我只做几个重要经常使用的API函数讲解
(1)wifi初始化,主要初始化wifi_init_config_t结构体

/**
  * @brief  初始化WiFi
  *         WiFi驱动程序的分配内存,例如WiFi控制结构,RX / TX缓冲区,WiFi NVS结构等,此WiFi也将启动WiFi任务
  * @注意1.必须先调用此API,然后才能调用所有其他WiFi API
  * @注意2.始终使用WIFI_INIT_CONFIG_DEFAULT宏将配置初始化为默认值
  *	当将更多字段添加到wifi_init_config_t中时,确保所有字段都具有正确的值
  *	在将来的版本中。如果要设置所有者的初始值,请覆盖默认值
  *	由WIFI_INIT_CONFIG_DEFAULT设置,请注意
  * 	wifi_init_config_t应该始终为WIFI_INIT_CONFIG_MAGIC!
  * @param 配置指向WiFi初始化配置结构的指针
  * @return
  *    - ESP_OK: succeed
  *    - ESP_ERR_NO_MEM: 内存不足
  */
esp_err_t esp_wifi_init(const wifi_init_config_t *config);

(2)设置wifi模式

/**
   * @brief 设置WiFi操作模式
   *将WiFi操作模式设置为station,soft-AP或station + soft-AP,
   *默认模式是软AP模式。
   * @param 模式WiFi操作模式
   * @return
   *-ESP_OK:成功
   *-ESP_ERR_WIFI_NOT_INIT:WiFi未由esp_wifi_init初始化
   *-ESP_ERR_INVALID_ARG:无效的参数
   *-其他:请参阅esp_err.h中的错误代码
   */
esp_err_t esp_wifi_set_mode(wifi_mode_t mode);

(3)设置wifi wifi_config_t结构体

/**
  * @brief 设置ESP32 STA或AP的配置
  * @注意1.仅当启用了指定的接口时,才能调用此API,否则,API将失败
  * @注意2。对于工作站配置,bssid_set需要为0;否则为0。仅当用户需要检查AP的MAC地址时才需要设置为1。
  * @注意3. ESP32仅限于一个通道,因此在soft-AP + station模式下,soft-AP会自动将其通道调整为与ESP32站的通道。
  * @param 接口
  * @param conf 工作站或soft-AP配置
  *
  * @return
  *-ESP_OK:成功
  *-ESP_ERR_WIFI_NOT_INIT:WiFi未由esp_wifi_init初始化
  *-ESP_ERR_INVALID_ARG:无效的参数
  *-ESP_ERR_WIFI_IF:无效的界面
  *-ESP_ERR_WIFI_MODE:无效模式
  *-ESP_ERR_WIFI_PASSWORD:无效的密码
  *-ESP_ERR_WIFI_NVS:WiFi内部NVS错误
  *-其他:请参阅esp_err.h中的错误代码
  */
esp_err_t esp_wifi_set_config(wifi_interface_t interface, wifi_config_t *conf);

(4)启动wifi

/**
   * @brief 根据当前配置启动WiFi
   *如果模式为WIFI_MODE_STA,则创建站点控制块并启动站点
   *如果模式为WIFI_MODE_AP,则会创建soft-AP控制块并启动soft-AP
   *如果模式为WIFI_MODE_APSTA,则创建soft-AP和站点控制块并启动soft-AP和站点
   *
   * @return
   *-ESP_OK:成功
   *-ESP_ERR_WIFI_NOT_INIT:WiFi未由esp_wifi_init初始化
   *-ESP_ERR_INVALID_ARG:无效的参数
   *-ESP_ERR_NO_MEM:内存不足
   *-ESP_ERR_WIFI_CONN:WiFi内部错误,站或soft-AP控制块错误
   *-ESP_FAIL:其他WiFi内部错误
   */
esp_err_t esp_wifi_start(void);

(4) wifi回调函数,具体分析参考:
【ESP32 IDF开发 应用篇⑬ 连接Wifi回调函数esp_event_handler_register专题】

/**
 * @brief 将事件处理程序注册到系统事件循环。
 *
 *此函数可用于注册以下任一处理程序:(1)特定事件,
 *(2)某个事件基础的所有事件,或(3)系统事件循环已知的所有事件。
 *
 *-特定事件:指定确切的event_base和event_id
 *-具有特定基准的所有事件:指定确切的event_base并将ESP_EVENT_ANY_ID用作event_id
 *-循环已知的所有事件:将ESP_EVENT_ANY_BASE用作event_base,将ESP_EVENT_ANY_ID用作event_id
 *
 *可以将多个处理程序注册到事件。将单个处理程序注册到多个事件是
 *也可以。但是,将同一处理程序多次注册到同一事件将导致
 *以前的注册将被覆盖。
 *
 * @param [in] event_base事件的基本ID,用于为其注册处理程序
 * @param [in] event_id要为其注册处理程序的事件的ID
 * @param [in] event_handler处理程序函数,在调度事件时将调用该函数
 * @param [in] event_handler_arg数据,除事件数据外,在调用时传递给处理程序
 *
 * @note 事件循环库不维护event_handler_arg的副本,因此用户应
 *确保在调用处理程序时event_handler_arg仍指向有效位置
 *
 * @返回
 *-ESP_OK:成功
 *-ESP_ERR_NO_MEM:无法为处理程序分配内存
 *-ESP_ERR_INVALID_ARG:事件库和事件ID的无效组合
 *-其他:失败
 */
esp_err_t esp_event_handler_register(esp_event_base_t event_base,
                                        int32_t event_id,
                                        esp_event_handler_t event_handler,
                                        void* event_handler_arg);

4、 Wifi sta模式和ap模式的软件设计

•sta模式
(1)、创建wifi连接事件组;
(2)、初始化wifi,注册回调函数
主要对2个结构体初始化
wifi_init_config_t结构体一般使用系统默认的参数即可,使用函数WIFI_INIT_CONFIG_DEFAULT()赋值

wifi_init_config_t cfg =WIFI_INIT_CONFIG_DEFAULT();//给wifi_init_config_t结构体初始化系统默认的数据

结构体内容:

/**
 * @brief WiFi stack configuration parameters passed to esp_wifi_init call.
 */
typedef struct {
    system_event_handler_t event_handler;          /**< WiFi event handler */
    wifi_osi_funcs_t*      osi_funcs;              /**< WiFi OS functions */
    wpa_crypto_funcs_t     wpa_crypto_funcs;       /**< WiFi station crypto functions when connect */
    int                    static_rx_buf_num;      /**< WiFi static RX buffer number */
    int                    dynamic_rx_buf_num;     /**< WiFi dynamic RX buffer number */
    int                    tx_buf_type;            /**< WiFi TX buffer type */
    int                    static_tx_buf_num;      /**< WiFi static TX buffer number */
    int                    dynamic_tx_buf_num;     /**< WiFi dynamic TX buffer number */
    int                    csi_enable;             /**< WiFi channel state information enable flag */
    int                    ampdu_rx_enable;        /**< WiFi AMPDU RX feature enable flag */
    int                    ampdu_tx_enable;        /**< WiFi AMPDU TX feature enable flag */
    int                    nvs_enable;             /**< WiFi NVS flash enable flag */
    int                    nano_enable;            /**< Nano option for printf/scan family enable flag */
    int                    tx_ba_win;              /**< WiFi Block Ack TX window size */
    int                    rx_ba_win;              /**< WiFi Block Ack RX window size */
    int                    wifi_task_core_id;      /**< WiFi Task Core ID */
    int                    beacon_max_len;         /**< WiFi softAP maximum length of the beacon */
    int                    mgmt_sbuf_num;          /**< WiFi management short buffer number, the minimum value is 6, the maximum value is 32 */
    uint64_t               feature_caps;           /**< Enables additional WiFi features and capabilities */
    int                    magic;                  /**< WiFi init magic number, it should be the last field */
} wifi_init_config_t;

wifi_config_t结构体只要是对sta和ap模式的SSID和PASSWORD进行设置

typedef union {
    wifi_ap_config_t  ap;  /**< configuration of AP */
    wifi_sta_config_t sta; /**< configuration of STA */
} wifi_config_t;

wifi_ap_config_t

/** @brief ESP32的Soft-AP配置设置 */
typedef struct {
    uint8_t ssid[32];  /**< SSID 如果ssid_len字段为0,则该字符串必须为Null终止的字符串。 否则,根据ssid_len设置长度. */
    uint8_t password[64];       /**< 密码.*/
    uint8_t ssid_len;           /**< SSID字段的可选长度. */
    uint8_t channel;            /**< ESP32 soft-AP通道 */
    wifi_auth_mode_t authmode;  /**< ESP32 soft-AP的认证方式。 在软AP模式下不支持AUTH_WEP */
    uint8_t ssid_hidden;        /**< 是否广播SSID,默认为0,广播SSID */
    uint8_t max_connection;     /**< 允许连接的最大站数,默认为4,最大为4 */
    uint16_t beacon_interval;   /**< 信标间隔100〜60000 ms,默认100 ms */
} wifi_ap_config_t;

wifi_sta_config_t

/** @brief ESP32的STA配置设置 */
typedef struct {
    uint8_t ssid[32];      /**< 目标AP的SSID。 空终止的字符串。 */
    uint8_t password[64];  /**< 目标AP的密码。 空终止的字符串。*/
    wifi_scan_method_t scan_method;    /**< 执行所有通道扫描或快速扫描 */
    bool bssid_set;        /**< 是否设置目标AP的MAC地址。 通常,station_config.bssid_set需要为0;否则为0。 仅当用户需要检查AP的MAC地址时才需要设置为1。*/
    uint8_t bssid[6];     /**< 目标AP的MAC地址*/
    uint8_t channel;       /**< 目标AP的信道。 设置为1〜13可以在连接到AP之前从指定的通道开始扫描。 如果AP的信道未知,则将其设置为0。*/
    uint16_t listen_interval;   /**< 设置WIFI_PS_MAX_MODEM时ESP32站接收信标的监听间隔。 单位:AP信标间隔。 如果设置为0,则默认为3。 */
    wifi_sort_method_t sort_method;    /**< 按rssi或安全模式对列表中的连接AP进行排序 */
    wifi_scan_threshold_t  threshold;     /**< 设置sort_method时,将仅使用具有比所选身份验证模式更安全的身份验证模式和比最小RSSI强的信号的AP。 */
    wifi_pmf_config_t pmf_cfg;    /**< Configuration for Protected Management Frame. Will be advertized in RSN Capabilities in RSN IE. */
} wifi_sta_config_t;

(3)、等待wifi连接打印连接消息

5、 Wifi sta模式实例

方法1
将esp-idf\examples\wifi\getting_started\station目录下的工程复制到自己的目录下面改名字为 idf_wifi_sta即可 文件名字改为 i idf_wifi_sta.C makefile文件也改成 PROJECT_NAME := idf_wifi_sta即可,然后复制一下代码测试。

在make menuconfig 中
Example Configuration —>
WiFi SSID:设置账号
WiFi Password :设置密码
Maximum retry :重复连接次数
然后保存退出

方法2:如何在menuconfig中添加编译菜单
复制原来做的工程,改好名字
在工程的main目录下添加Kconfig.projbuild

然后在Kconfig.projbuild中添加
menu “Example Configuration”

config ESP_WIFI_SSID
    string "WiFi SSID"
    default "myssid"
    help
        SSID (network name) for the example to connect to.

config ESP_WIFI_PASSWORD
    string "WiFi Password"
    default "mypassword"
    help
        WiFi password (WPA or WPA2) for the example to use.

config ESP_MAXIMUM_RETRY
    int "Maximum retry"
    default 5
    help
        Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.

endmenu
这样make menuconfig时就会有Example Configuration选项
config ESP_WIFI_PASSWORD、config ESP_WIFI_PASSWORD、config ESP_MAXIMUM_RETRY就是程序中使用的宏

/**********************************************************************
*               文件名:            idf_wifi_sta.c
*               创建人:            
*               创建日期:           
*               修改人:
*               修改日期:           
*               版本号:            V1.1
*               备注:
*               公司:
********************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "nvs.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "lwip/err.h"
#include "lwip/sys.h"
static const char *TAG = "WIFI_STA";

//事件标志组
static EventGroupHandle_t xCreatedEventGroup_WifiConnect = NULL;
#define WIFI_CONNECTED_BIT  BIT0
#define WIFI_FAIL_BIT       BIT1
static int retry_num = 0;
/***********************************************************************
* 函数:  
* 描述:   wifi 回调函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{
    ESP_LOGI(TAG, "event_base: %s  ; event_id : %d",event_base,event_id);
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) 
    {
        esp_wifi_connect();//开始连接
    } 
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) //wifi断开时
    {
        if (retry_num < CONFIG_ESP_MAXIMUM_RETRY) //重新连接次数
        {
            esp_wifi_connect();
            retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } 
        else 
        {
            xEventGroupSetBits(xCreatedEventGroup_WifiConnect, WIFI_FAIL_BIT);
            xEventGroupClearBits(xCreatedEventGroup_WifiConnect, WIFI_CONNECTED_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } 
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)//获得IP
    {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        retry_num = 0;
        xEventGroupSetBits(xCreatedEventGroup_WifiConnect, WIFI_CONNECTED_BIT);
        xEventGroupClearBits(xCreatedEventGroup_WifiConnect, WIFI_FAIL_BIT);
    }
}
/***********************************************************************
* 函数:  
* 描述:   wifi sta初始化代码相对比较固定,一般不需要做修改
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void wifi_init_sta(void)
{
    ESP_ERROR_CHECK(esp_netif_init());//初始化TCP / IP堆栈和esp-netif
    ESP_ERROR_CHECK(esp_event_loop_create_default()); //创建默认event loop
    esp_netif_create_default_wifi_sta();//创建默认的WIFI STA。

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//给wifi_init_config_t结构体初始化系统默认的数据
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));//使用默认的参数初始化wifi

    //注册wifi 连接过程中回调函数
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
    //设置账户和密码,在menuconfig中设置
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = CONFIG_ESP_WIFI_SSID,
            .password = CONFIG_ESP_WIFI_PASSWORD
            //.ssid = "ssid",
            //.password = "password"
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );//设置为sta模式
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );//启动

    ESP_LOGI(TAG, "wifi_init_sta finished.");
}
/***********************************************************************
* 函数:  
* 描述:   主函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void app_main()
{    
    //初始化NVS
   esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
    //创建事件标志组,用于等待wifi连接
    xCreatedEventGroup_WifiConnect = xEventGroupCreate();
    //WIFI sta初始化
    wifi_init_sta();
    //等待连接成功,或已经连接有断开连接,此函数会一直阻塞,只有有连接
   EventBits_t bits =  xEventGroupWaitBits(xCreatedEventGroup_WifiConnect,// 事件标志组句柄 
    WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, // 等待bit0和bit1被设置 
    pdFALSE,                            //TRUE退出时bit0和bit1被清除,pdFALSE退出时bit0和bit1不清除
    pdFALSE,                            //设置为pdTRUE表示等待bit1和bit0都被设置,pdFALSE表示等待bit1或bit0其中一个被设置
    portMAX_DELAY);                     //等待延迟时间,一直等待 

    if (bits & WIFI_CONNECTED_BIT) 
    {
        ESP_LOGI(TAG, "connected to ap" );
    } 
    else if (bits & WIFI_FAIL_BIT) 
    {
        ESP_LOGI(TAG, "Failed to connect");
    } 
    //注销删除事件
    ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler));
    ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler));
    vEventGroupDelete(xCreatedEventGroup_WifiConnect);
}

在这里插入图片描述

6、 Wifi ap模式实例

/**********************************************************************
*               文件名:            idf_wifi_sta.c
*               创建人:            
*               创建日期:           
*               修改人:
*               修改日期:           
*               版本号:            V1.1
*               备注:
*               公司:
********************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "nvs.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "lwip/err.h"
#include "lwip/sys.h"
static const char *TAG = "WIFI_AP";

/***********************************************************************
* 函数:  
* 描述:   wifi 回调函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{
    ESP_LOGI(TAG, "event_base: %s  ; event_id : %d",event_base,event_id);
    if (event_id == WIFI_EVENT_AP_STACONNECTED) //有STA连接
    {
        wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
        ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
                 MAC2STR(event->mac), event->aid);
    } 
    else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) //STA断开连接
    {
        wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
        ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
                 MAC2STR(event->mac), event->aid);
    }
}
/***********************************************************************
* 函数:  
* 描述:   wifi sta初始化代码相对比较固定,一般不需要做修改
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void wifi_init_ap(void)
{
    ESP_ERROR_CHECK(esp_netif_init());//初始化TCP / IP堆栈和esp-netif
    ESP_ERROR_CHECK(esp_event_loop_create_default()); //创建默认event loop
    esp_netif_create_default_wifi_ap();//创建默认的WIFI ap。

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//给wifi_init_config_t结构体初始化系统默认的数据
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));//使用默认的参数初始化wifi

    //注册wifi 连接过程中回调函数
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    //设置账户和密码,在menuconfig中设置
    wifi_config_t wifi_config = {
        .ap = {
            .ssid = "idf_wifi_ap",
            .password = "123456789",//此处的密码长度不能小于8位
            .max_connection = 1,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP) );//设置为sta模式
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );//启动

    ESP_LOGI(TAG, "wifi_init_AP finished.");
}
/***********************************************************************
* 函数:  
* 描述:   主函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void app_main()
{    
    //初始化NVS
   esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_AP");

    //WIFI sta初始化
    wifi_init_ap();
}

在启动过程中,需要注意一点密码不能小于8位,小于8位系统会一直重启。

在这里插入图片描述
在这里插入图片描述
所有文章源代码:https://download.csdn.net/download/lu330274924/88518092

  • 25
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

物联网程序猿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值