TinyUSB USB传输性能调优:端点大小与缓冲区配置

TinyUSB USB传输性能调优:端点大小与缓冲区配置

【免费下载链接】tinyusb An open source cross-platform USB stack for embedded system 【免费下载链接】tinyusb 项目地址: https://gitcode.com/gh_mirrors/ti/tinyusb

引言:USB性能的隐形瓶颈

你是否曾遇到嵌入式设备USB传输卡顿、数据丢失或无法达到预期速率的问题?在资源受限的嵌入式系统中,USB(Universal Serial Bus,通用串行总线)传输性能往往成为整个系统的瓶颈。TinyUSB作为一款轻量级开源USB协议栈,其性能表现很大程度上取决于端点(Endpoint)大小与缓冲区(Buffer)的配置策略。本文将深入剖析TinyUSB的端点与缓冲区工作机制,提供一套系统化的性能调优方案,帮助开发者在不同应用场景下实现USB传输效率的最大化。

读完本文,你将掌握:

  • USB端点与缓冲区的底层工作原理
  • TinyUSB关键配置参数的优化方法
  • 不同传输类型(控制/批量/中断/等时)的调优策略
  • 实战案例分析与性能测试方法
  • 常见问题诊断与解决方案

一、USB端点与缓冲区基础

1.1 USB端点工作原理

USB端点是设备与主机之间进行数据传输的逻辑通道,每个端点都有固定的传输方向和类型。在TinyUSB中,端点被分为控制端点(Endpoint 0)和若干个非控制端点,后者根据传输特性又可分为批量(Bulk)、中断(Interrupt)、等时(Isochronous)三种类型。

mermaid

1.2 TinyUSB缓冲区架构

TinyUSB采用双缓冲区(Double Buffer)架构,在设备控制器(DCD)层实现数据的并行处理。这种设计允许CPU在准备下一包数据的同时,硬件可以传输当前包,从而显著提升吞吐量。

mermaid

TinyUSB的缓冲区管理通过以下关键组件实现:

  • FIFO队列:用于临时存储待传输数据
  • 缓冲区描述符:跟踪每个缓冲区的状态和位置
  • 中断服务程序(ISR):处理缓冲区切换和传输完成事件

二、TinyUSB配置参数解析

2.1 核心配置参数

TinyUSB的性能调优主要通过修改配置头文件中的参数实现。以下是影响传输性能的关键配置项:

参数名描述典型值影响范围
CFG_TUD_ENDPOINT_MAX设备模式下的最大端点数量8-16整体资源占用
CFG_TUD_(EP/RX/TX)_SIZE端点/接收/发送缓冲区大小64-512字节传输吞吐量
CFG_TUD_BULK_EP_SIZE批量传输端点大小512字节批量传输性能
CFG_TUD_INTERRUPT_EP_SIZE中断传输端点大小64字节中断响应速度
CFG_TUD_AUDIO_EP_SIZE音频类端点大小192-512字节音频流质量
CFG_TUD_VIDEO_EP_SIZE视频类端点大小1024字节视频帧率
CFG_TUSB_MEM_ALIGN内存对齐要求4-32字节内存访问效率
CFG_TUD_CDC_EP_BUFSIZECDC类端点缓冲区大小512-1024字节串口数据吞吐量

2.2 参数配置策略

参数配置需要在性能、内存占用和兼容性之间寻找平衡:

  1. 端点大小选择

    • 高速USB(USB 2.0)设备最大端点大小为512字节
    • 全速USB(USB 1.1)设备最大端点大小为64字节
    • 应根据实际传输需求选择接近最大值的端点大小,以减少传输次数
  2. 缓冲区大小设置

    • 缓冲区大小通常应设置为端点大小的整数倍
    • 对于连续数据流(如音频/视频),建议使用2-4倍端点大小的缓冲区
    • 内存受限系统可适当减小缓冲区,但需避免频繁的缓冲区切换开销
  3. 端点数量规划

    • 根据设备功能需求合理规划端点数量
    • 每个接口通常需要至少一个输入端点和一个输出端点
    • 预留1-2个备用端点以应对未来功能扩展

三、不同传输类型的调优策略

3.1 批量传输(Bulk Transfer)调优

批量传输适用于大量数据的可靠传输,如存储设备和打印机。TinyUSB中批量传输的性能调优要点:

  1. 最大化端点大小

    // 在 tusb_config.h 中
    #define CFG_TUD_BULK_EP_SIZE 512  // 高速USB最大端点大小
    
  2. 使用双缓冲区模式

    // 在批量传输初始化代码中
    tud_descriptor_configuration_t const desc_configuration = {
      .bLength             = sizeof(tud_descriptor_configuration_t),
      .bDescriptorType     = TUSB_DESC_CONFIGURATION,
      .wTotalLength        = 0x0020,
      .bNumInterfaces      = 1,
      .bConfigurationValue = 1,
      .iConfiguration      = 0x00,
      .bmAttributes        = 0x80,  // 总线供电
      .bMaxPower           = 0x32,  // 100mA
    
      .interface = {
        // 批量传输接口
        {
          .bLength             = sizeof(tud_descriptor_interface_t),
          .bDescriptorType     = TUSB_DESC_INTERFACE,
          .bInterfaceNumber    = 0,
          .bAlternateSetting   = 0,
          .bNumEndpoints       = 2,  // 一个IN端点,一个OUT端点
          .bInterfaceClass     = TUSB_CLASS_VENDOR_SPECIFIC,
          .bInterfaceSubClass  = 0x00,
          .bInterfaceProtocol  = 0x00,
          .iInterface          = 0x00,
    
          // 端点描述符
          .endpoint = {
            {
              .bLength          = sizeof(tud_descriptor_endpoint_t),
              .bDescriptorType  = TUSB_DESC_ENDPOINT,
              .bEndpointAddress = 0x01,  // IN端点
              .bmAttributes     = TUSB_XFER_BULK,
              .wMaxPacketSize   = CFG_TUD_BULK_EP_SIZE,
              .bInterval        = 0x00
            },
            {
              .bLength          = sizeof(tud_descriptor_endpoint_t),
              .bDescriptorType  = TUSB_DESC_ENDPOINT,
              .bEndpointAddress = 0x81,  // OUT端点
              .bmAttributes     = TUSB_XFER_BULK,
              .wMaxPacketSize   = CFG_TUD_BULK_EP_SIZE,
              .bInterval        = 0x00
            }
          }
        }
      }
    };
    
  3. 优化数据填充函数

    // 高效的批量数据发送函数
    bool bulk_send_data(uint8_t const *data, uint16_t len) {
      uint16_t sent = 0;
      while (sent < len) {
        uint16_t to_send = MIN(len - sent, CFG_TUD_BULK_EP_SIZE);
        // 等待上一次传输完成
        while (!tud_ready()) {
          tud_task();
        }
        // 发送数据块
        tud_ep_write(0x01, data + sent, to_send);
        tud_ep_write_done(0x01, to_send);
        sent += to_send;
      }
      return true;
    }
    

3.2 中断传输(Interrupt Transfer)调优

中断传输适用于小量数据的周期性传输,如键盘、鼠标和游戏控制器。调优重点在于减少延迟和提高响应速度:

  1. 合理设置轮询间隔

    // 在中断端点描述符中
    .endpoint = {
      {
        .bLength          = sizeof(tud_descriptor_endpoint_t),
        .bDescriptorType  = TUSB_DESC_ENDPOINT,
        .bEndpointAddress = 0x81,  // IN端点
        .bmAttributes     = TUSB_XFER_INTERRUPT,
        .wMaxPacketSize   = 64,    // 中断传输通常使用较小的端点
        .bInterval        = 4      // 轮询间隔,单位为ms(全速)或125us(高速)
      }
    }
    
  2. 优化中断服务程序

    // 中断传输完成回调
    void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len) {
      (void) instance;
      (void) len;
    
      // 立即准备下一次报告,减少延迟
      prepare_next_hid_report(report);
    }
    
  3. 减少数据处理时间

    // 高效的HID报告处理
    void prepare_next_hid_report(uint8_t* report) {
      // 仅更新变化的数据,减少处理时间
      static uint8_t last_report[6] = {0};
    
      // 读取传感器数据
      uint8_t new_report[6] = {
        0x01,  // 报告ID
        get_x_axis(),
        get_y_axis(),
        get_z_axis(),
        get_buttons(),
        get_hat_switch()
      };
    
      // 仅当数据变化时才更新报告
      if (memcmp(new_report, last_report, 6) != 0) {
        memcpy(report, new_report, 6);
        memcpy(last_report, new_report, 6);
      }
    }
    

3.3 等时传输(Isochronous Transfer)调优

等时传输适用于实时数据流,如音频和视频,注重传输速率而非可靠性:

  1. 设置合适的端点大小和缓冲区

    // 在 tusb_config.h 中
    #define CFG_TUD_AUDIO_EP_SIZE 192  // 音频端点大小
    
    // 音频缓冲区应足够大以应对突发延迟
    #define AUDIO_BUFFER_SIZE (CFG_TUD_AUDIO_EP_SIZE * 4)
    static uint8_t audio_tx_buffer[AUDIO_BUFFER_SIZE] __attribute__((aligned(4)));
    
  2. 使用DMA进行数据传输

    // 初始化DMA传输
    void audio_dma_init(void) {
      // 配置DMA通道
      dma_channel_config_t config = dma_channel_get_default_config(DMA_CHANNEL_0);
      channel_config_set_transfer_data_size(&config, DMA_SIZE_8);
      channel_config_set_direction(&config, DMA_MEMORY_TO_PERIPHERAL);
      channel_config_set_enable_irq_on_transfer(&config, true);
    
      dma_channel_configure(
        DMA_CHANNEL_0, &config,
        &USB1->DATAX[1],  // USB IN端点1数据寄存器
        audio_tx_buffer,  // 源缓冲区
        AUDIO_BUFFER_SIZE,
        false  // 不立即启动
      );
    
      // 启用DMA中断
      irq_set_enabled(DMA_IRQ_0, true);
    }
    
  3. 实现平滑的数据流控制

    // 音频数据填充函数
    void fill_audio_buffer(void) {
      static uint32_t buffer_ptr = 0;
    
      // 检查缓冲区可用空间
      uint32_t available = AUDIO_BUFFER_SIZE - dma_channel_get_trans_count(DMA_CHANNEL_0);
    
      // 填充足够的数据以避免欠载
      if (available > CFG_TUD_AUDIO_EP_SIZE * 2) {
        // 从I2S麦克风读取数据到缓冲区
        uint32_t bytes_read = i2s_read_blocking(I2S_INSTANCE, 
                                                &audio_tx_buffer[buffer_ptr], 
                                                CFG_TUD_AUDIO_EP_SIZE);
    
        buffer_ptr = (buffer_ptr + bytes_read) % AUDIO_BUFFER_SIZE;
    
        // 如果DMA未运行,则启动它
        if (!dma_channel_is_enabled(DMA_CHANNEL_0)) {
          dma_channel_start(DMA_CHANNEL_0);
        }
      }
    }
    

四、实战案例分析

4.1 案例一:USB CDC串口性能优化

问题描述:使用TinyUSB的CDC类实现串口功能时,传输大量数据时出现严重延迟和数据丢失。

优化步骤

  1. 增大CDC端点和缓冲区大小

    --- a/src/class/cdc/cdc_device.h
    +++ b/src/class/cdc/cdc_device.h
    @@ -37,7 +37,7 @@
    #define CFG_TUD_CDC_RX_BUFSIZE     (TUD_OPT_HIGH_SPEED ? 512 : 64)
    #define CFG_TUD_CDC_TX_BUFSIZE     (TUD_OPT_HIGH_SPEED ? 512 : 64)
    #else
    -#define CFG_TUD_CDC_RX_BUFSIZE     64
    -#define CFG_TUD_CDC_TX_BUFSIZE     64
    +#define CFG_TUD_CDC_RX_BUFSIZE     512
    +#define CFG_TUD_CDC_TX_BUFSIZE     512
    #endif
    
  2. 实现非阻塞的数据发送

    // 改进的CDC数据发送函数
    int cdc_send_data(const void *data, size_t len) {
      const uint8_t *data_ptr = (const uint8_t *)data;
      size_t bytes_sent = 0;
    
      while (bytes_sent < len) {
        // 计算剩余字节数
        size_t remaining = len - bytes_sent;
        // 一次最多发送缓冲区大小的数据
        size_t to_send = MIN(remaining, CFG_TUD_CDC_TX_BUFSIZE);
    
        // 尝试发送数据(非阻塞)
        size_t sent = tud_cdc_write(data_ptr + bytes_sent, to_send);
    
        if (sent == 0) {
          // 缓冲区已满,等待一小段时间
          tud_task();
          tight_loop_contents();
        } else {
          bytes_sent += sent;
        }
      }
    
      // 触发实际USB传输
      tud_cdc_write_flush();
    
      return bytes_sent;
    }
    
  3. 优化USB任务调度

    // 在主循环中更频繁地调用tud_task()
    int main(void) {
      board_init();
      tusb_init();
    
      while (1) {
        // 更频繁地处理USB事件
        for (int i = 0; i < 10; i++) {
          tud_task();
          sleep_us(10);  // 短暂延迟,降低CPU占用
        }
    
        // 处理其他任务
        process_sensors();
        update_display();
      }
    }
    

4.2 案例二:USB MSC存储设备性能调优

问题描述:基于TinyUSB MSC类实现的U盘功能传输速度慢,写入大文件时速度仅为1MB/s左右。

优化方案

  1. 调整MSC端点大小和缓冲区

    // 在 tusb_config.h 中
    #define CFG_TUD_MSC_EP_SIZE 512  // MSC端点大小设为最大值
    
    // MSC数据缓冲区
    #define MSC_BUF_SIZE (CFG_TUD_MSC_EP_SIZE * 2)
    uint8_t msc_buf[MSC_BUF_SIZE] __attribute__((aligned(4)));
    
  2. 优化块设备访问

    // 实现块缓存以减少Flash访问次数
    static uint32_t cached_block = 0xFFFFFFFF;
    static uint8_t block_cache[512];
    
    // 读取块数据,使用缓存减少Flash访问
    int32_t disk_read(uint8_t pdrv, uint8_t *buff, uint32_t sector, uint8_t count) {
      (void)pdrv;
    
      for (uint8_t i = 0; i < count; i++) {
        // 如果请求的块在缓存中,直接使用缓存数据
        if (sector == cached_block) {
          memcpy(buff + i*512, block_cache, 512);
        } else {
          // 从Flash读取块并更新缓存
          flash_read_block(sector, buff + i*512, 512);
          memcpy(block_cache, buff + i*512, 512);
          cached_block = sector;
        }
        sector++;
      }
    
      return RES_OK;
    }
    
  3. 使用多块传输

    // 在MSC回调中支持多块传输
    int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {
      (void) lun;
      (void) offset;
    
      // 计算块数量
      uint32_t block_count = bufsize / 512;
    
      // 支持多块写入以提高性能
      if (block_count > 1) {
        return flash_write_blocks(lba, buffer, block_count) ? bufsize : -1;
      } else {
        return flash_write_block(lba, buffer) ? bufsize : -1;
      }
    }
    
  4. 调整USB中断优先级

    // 在初始化代码中提高USB中断优先级
    void usb_irq_priority_init(void) {
      // 设置USB中断优先级高于Flash操作中断
      NVIC_SetPriority(USB_IRQn, 2);      // USB中断优先级设为2
      NVIC_SetPriority(FLASH_IRQn, 3);    // Flash中断优先级设为3
      NVIC_EnableIRQ(USB_IRQn);
    }
    

五、性能测试与评估方法

5.1 吞吐量测试

吞吐量是衡量USB传输性能的关键指标,可以通过以下方法进行测试:

  1. 使用dd命令(Linux/macOS)

    # 测试写入速度
    dd if=/dev/zero of=/media/user/TINYUSB/test bs=1M count=100 oflag=direct
    
    # 测试读取速度
    dd if=/media/user/TINYUSB/test of=/dev/null bs=1M count=100 iflag=direct
    
  2. 使用HDD/SSD测试工具

    • CrystalDiskMark(Windows)
    • AJA System Test(跨平台)
    • Disk Speed Test(macOS)
  3. 自定义嵌入式测试程序

    // 嵌入式设备端吞吐量测试
    void throughput_test(void) {
      uint8_t test_buf[CFG_TUD_BULK_EP_SIZE];
      uint32_t start_time, end_time;
      uint32_t total_bytes = 0;
    
      // 填充测试数据
      for (int i = 0; i < CFG_TUD_BULK_EP_SIZE; i++) {
        test_buf[i] = i % 256;
      }
    
      // 开始计时
      start_time = time_us_32();
    
      // 持续传输10秒
      while ((time_us_32() - start_time) < 10000000) {
        // 发送数据
        tud_ep_write(EP_BULK_IN, test_buf, CFG_TUD_BULK_EP_SIZE);
        tud_ep_write_done(EP_BULK_IN, CFG_TUD_BULK_EP_SIZE);
    
        total_bytes += CFG_TUD_BULK_EP_SIZE;
    
        // 等待传输完成
        while (!tud_ep_is_tx_done(EP_BULK_IN)) {
          tud_task();
        }
      }
    
      // 计算吞吐量
      end_time = time_us_32();
      float duration = (end_time - start_time) / 1000000.0f;
      float throughput = total_bytes / (1024.0f * 1024.0f) / duration;
    
      printf("Throughput: %.2f MB/s\n", throughput);
    }
    

5.2 延迟测试

对于实时性要求高的应用,延迟测试同样重要:

  1. 往返延迟测试

    // 主机发送数据后等待设备回应的延迟测试
    void latency_test(void) {
      uint8_t tx_buf[64] = "TEST";
      uint8_t rx_buf[64];
      uint32_t start_time, end_time;
      uint32_t total_latency = 0;
      uint32_t test_count = 100;
    
      for (int i = 0; i < test_count; i++) {
        // 发送测试数据
        tud_ep_write(EP_BULK_IN, tx_buf, sizeof(tx_buf));
        tud_ep_write_done(EP_BULK_IN, sizeof(tx_buf));
    
        // 等待回应
        start_time = time_us_32();
        while (tud_ep_available(EP_BULK_OUT) < sizeof(rx_buf)) {
          tud_task();
        }
        end_time = time_us_32();
    
        // 读取回应
        tud_ep_read(EP_BULK_OUT, rx_buf, sizeof(rx_buf));
    
        // 累加延迟
        total_latency += (end_time - start_time);
      }
    
      // 计算平均延迟
      float avg_latency = (float)total_latency / test_count;
      printf("Average latency: %.2f us\n", avg_latency);
    }
    
  2. 使用逻辑分析仪

    • 连接USB D+/D-线和设备GPIO
    • 触发发送/接收事件并测量时间差
    • 可直观观察信号质量和传输时序

5.3 稳定性测试

长时间稳定性测试对于USB设备至关重要:

  1. 连续传输测试

    # Linux shell脚本:连续传输测试
    while true; do
      dd if=/dev/zero of=/media/user/TINYUSB/test bs=1M count=10 oflag=direct
      sync
      dd if=/media/user/TINYUSB/test of=/dev/null bs=1M count=10 iflag=direct
      sleep 1
    done
    
  2. 压力测试

    // 嵌入式设备压力测试
    void stress_test(void) {
      uint8_t buf[CFG_TUD_BULK_EP_SIZE];
      uint32_t error_count = 0;
      uint32_t transfer_count = 0;
    
      // 填充测试数据
      for (int i = 0; i < CFG_TUD_BULK_EP_SIZE; i++) {
        buf[i] = rand();
      }
    
      printf("Starting stress test...\n");
    
      while (1) {
        // 发送测试数据
        tud_ep_write(EP_BULK_IN, buf, CFG_TUD_BULK_EP_SIZE);
        tud_ep_write_done(EP_BULK_IN, CFG_TUD_BULK_EP_SIZE);
    
        // 等待数据回传
        while (!tud_ep_available(EP_BULK_OUT)) {
          tud_task();
        }
    
        // 读取回传数据
        uint8_t rx_buf[CFG_TUD_BULK_EP_SIZE];
        tud_ep_read(EP_BULK_OUT, rx_buf, CFG_TUD_BULK_EP_SIZE);
    
        // 验证数据完整性
        if (memcmp(buf, rx_buf, CFG_TUD_BULK_EP_SIZE) != 0) {
          error_count++;
          printf("Error detected! Total errors: %lu\n", error_count);
        }
    
        transfer_count++;
    
        // 每1000次传输报告一次状态
        if (transfer_count % 1000 == 0) {
          printf("Transfers: %lu, Errors: %lu\n", transfer_count, error_count);
        }
      }
    }
    

六、常见问题诊断与解决方案

6.1 传输速度低于预期

可能原因

  • 端点大小未设置为最大值
  • 缓冲区大小不足或对齐不当
  • CPU处理速度慢,无法及时填充/读取数据
  • USB中断优先级过低
  • 数据处理耗时过长,阻塞了USB传输

解决方案

  1. 确保端点大小设置为USB规范允许的最大值
  2. 增加缓冲区大小并确保正确对齐
  3. 使用DMA或双缓冲区减少CPU干预
  4. 提高USB中断优先级
  5. 优化数据处理算法,减少处理时间

6.2 传输过程中出现数据丢失

可能原因

  • 缓冲区溢出
  • 数据处理不及时
  • USB总线不稳定
  • 电源电压波动
  • 接地不良导致干扰

解决方案

  1. 增加缓冲区大小,实现缓冲区溢出检测
  2. 优化数据处理流程,确保及时读取接收缓冲区
  3. 缩短USB线缆长度,使用屏蔽USB线
  4. 确保稳定的电源供应,必要时添加电容滤波
  5. 改善PCB布局,减少电磁干扰

6.3 系统稳定性问题

可能原因

  • 内存不足导致堆溢出
  • 中断处理不当导致死锁
  • USB控制器配置错误
  • 缓冲区访问冲突
  • 低功耗模式与USB传输冲突

解决方案

  1. 增加堆大小或优化内存使用
  2. 避免在中断服务程序中执行耗时操作
  3. 检查USB控制器配置参数
  4. 使用互斥锁保护共享缓冲区访问
  5. 在USB传输期间禁用深度睡眠模式

七、总结与展望

USB传输性能调优是一个系统性工程,需要开发者在硬件设计、软件配置和算法实现等多个层面进行优化。本文详细介绍了TinyUSB端点与缓冲区的工作原理,提供了针对不同传输类型的调优策略,并通过实际案例展示了性能优化的具体方法。

通过合理配置端点大小、优化缓冲区管理、使用DMA传输和优化中断处理等措施,可以显著提升TinyUSB的传输性能。性能测试结果表明,经过优化的TinyUSB实现可以达到接近硬件极限的传输速度,满足大多数嵌入式应用的需求。

未来,随着USB4等新标准的普及,嵌入式USB传输将面临更高带宽和更低延迟的挑战。TinyUSB作为一款活跃发展的开源项目,也将不断引入新的优化技术,如更智能的缓冲区管理算法、动态端点配置和更高效的电源管理等。开发者应持续关注TinyUSB项目的更新,及时应用新的性能优化技术。

最后,USB性能调优是一个迭代过程,建议开发者结合具体应用场景,通过系统性测试和分析,找到最适合的优化方案。希望本文提供的知识和方法能够帮助开发者充分发挥TinyUSB的潜力,构建高性能的嵌入式USB应用。

附录:TinyUSB性能调优检查清单

端点配置检查清单

  •  端点大小设置为USB规范允许的最大值
  •  端点数量满足应用需求并有适当预留
  •  端点描述符正确配置了传输类型和方向
  •  中断端点的轮询间隔设置合理

缓冲区配置检查清单

  •  缓冲区大小为端点大小的整数倍
  •  缓冲区正确对齐以提高访问效率
  •  实现了缓冲区溢出检测机制
  •  双缓冲区或环形缓冲区用于连续数据流

代码优化检查清单

  •  使用DMA进行大数据传输
  •  中断服务程序简洁高效
  •  避免在中断中执行耗时操作
  •  实现非阻塞的数据处理流程
  •  USB任务调度频率合理

系统集成检查清单

  •  USB中断优先级设置适当
  •  电源供应稳定可靠
  •  避免USB传输与其他外设的资源冲突
  •  实现了必要的错误处理和恢复机制
  •  系统在USB传输期间保持足够的处理能力

【免费下载链接】tinyusb An open source cross-platform USB stack for embedded system 【免费下载链接】tinyusb 项目地址: https://gitcode.com/gh_mirrors/ti/tinyusb

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

抵扣说明:

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

余额充值