树莓派pico rp2040 I2C的使用

本文详细介绍了如何在树莓派picorp2040上使用I2C,包括初始化、常用函数i2c_init、i2c_write_blocking和i2c_read_blocking的使用。通过AT24C02的读写操作和OLED显示的示例,帮助读者理解I2C通信过程。
摘要由CSDN通过智能技术生成

前言

本文旨在用两个简单的示例讲述树莓派pico rp2040I2C使用。

系列其他文章:

树莓派pico与RP2040学习(第一篇)

Ubuntu搭建树莓派pico RP2040开发环境

树莓派pico rp2040 ADC的使用

第一章 生成项目

请参照我之前的文章生成一个I2C项目,注意,可以在生成项目时勾选I2C interface,这样就不用手动添加头文件了。

在这里插入图片描述

第二章 常用函数简析

一般情况下,树莓派pico rp2040常用的I2C函数就三个:

  1. i2c_init(i2c接口,速率)
    参数:
    速率I2C的传输速率

    i2c接口树莓派pico rp2040有两个i2c接口,分别是i2c0i2c1,可以在芯片数据手册中查到引脚对应的接口。如果使用的是树莓派pico ,可以参考下图
    在这里插入图片描述

  2. i2c_write_blocking(i2c接口,地址,数据,数据长度,停止信号)
    参数:
    i2c接口:同上
    地址:7位设备地址
    数据:需要发送的数据,注意!这个参数是一个指针
    数据长度:需要发送的数据的长度

    停止信号:如果为0,则此次数据传输完后,下次会重新启动i2c
    如果为1,则此次传输完数据后保持。该参数为1时一般是这样用的:先发送要写入的地址,然后再发送 要写入的数据。
    该参数为0时,一般是这样用的:将需要写入的地址和数据放到一个数组里,则函数中的参数数据为这个 数组。具体使用可以看第三章的示例

  3. i2c_read_blocking(i2c接口,地址,数据,数据长度,停止信号)
    该函数的参数与i2c_write_blocking(i2c接口,地址,数据,数据长度,停止信号)的是一样的,这里不再赘述。

第三章 I2C的使用

3.0 重要提示

第二章介绍的两个函数i2c_write_blocking()``i2c_read_blocking(),其中的地址参数是7位的。通常I2C设备的地址最后一位是读写位,通过置0或1可以设置写或读,我们在使用这两个函数时,直接将7位地址作为参数就行,不需要额外去考虑读或写模式的地址,因为函数已经帮我们做好了。

例如,对AT24C02,在数据手册可以看到地址说明:

[外在这里插入图片描述

我们在使用树莓派pico rp2040对该芯片进行操作时,I2C的地址就是0x50(0101 0000)

3.1 AT24C02的读写操作

我在优信电子购买了模块。电路图如下:

在这里插入图片描述

A0``A1``A2三个引脚接地,则地址为0x50

以下程序实现在寄存器地址0x12写入数据66,并读取出来:

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "pico/binary_info.h"

// I2C defines
// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.
// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments
#define I2C_PORT i2c0
#define I2C_SDA 8
#define I2C_SCL 9

#define ADDR 0x50



int main()
{
    stdio_init_all();

    sleep_ms(3000);

    // I2C Initialisation. Using it at 400Khz.
    i2c_init(I2C_PORT, 400*1000);
    
    gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);
    gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);
    gpio_pull_up(I2C_SDA);
    gpio_pull_up(I2C_SCL);

    bi_decl(bi_2pins_with_func(I2C_SDA,I2C_SCL,GPIO_FUNC_I2C));

    uint8_t buf[2]={0x12,66};

    uint8_t reg_addr=0x12;




    uint8_t buffer[1];


    i2c_write_blocking(I2C_PORT,ADDR,buf, sizeof(buf),false);

    printf("completed");
    //puts("Hello, world!");
    while (1)
    {


        i2c_write_blocking(I2C_PORT,ADDR,&reg_addr,1,true);//写入要读取的地址
        i2c_read_blocking(I2C_PORT,ADDR,buffer,1,false);
        printf("now output results\n");
        printf("%d\n",buffer[0]);
        printf("completed.\n");

        sleep_ms(1000);
    }

    return 0;
}


看了前面的讲解,这部分代码应该不是很难。

输出结果如下:
在这里插入图片描述

3.2 OLED显示

这部分是pico自带的一个示例,在文件夹pico-examples,具体的下载方式可以看我之前的文章。

这部分会有点难,建议先将3.1 AT24C02的读写操作这一部分实践操作一下,熟悉I2C常用的三个函数。

模块我是在telesky旗舰店买的0.91寸4针OLED显示屏 IIC接口

3.2.1 全部代码

代码如下:

/**
 * Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
//#include "raspberry26x32.h"

/* Example code to talk to an SSD1306-based OLED display

   NOTE: Ensure the device is capable of being driven at 3.3v NOT 5v. The Pico
   GPIO (and therefore I2C) cannot be used at 5v.

   You will need to use a level shifter on the I2C lines if you want to run the
   board at 5v.

   Connections on Raspberry Pi Pico board, other boards may vary.

   GPIO PICO_DEFAULT_I2C_SDA_PIN (on Pico this is GP4 (pin 6)) -> SDA on display
   board
   GPIO PICO_DEFAULT_I2C_SCK_PIN (on Pico this is GP5 (pin 7)) -> SCL on
   display board
   3.3v (pin 36) -> VCC on display board
   GND (pin 38)  -> GND on display board
*/
#define IMG_WIDTH 26
#define IMG_HEIGHT 32

static uint8_t raspberry26x32[] = { 0x0, 0x0, 0xe, 0x7e, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfc, 0xf8, 0xfc, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7e, 0x1e, 0x0, 0x0, 0x0, 0x80, 0xe0, 0xf8, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf8, 0xe0, 0x80, 0x0, 0x0, 0x1e, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x1e, 0x0, 0x0, 0x0, 0x3, 0x7, 0xf, 0x1f, 0x1f, 0x3f, 0x3f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x7f, 0x3f, 0x3f, 0x1f, 0x1f, 0xf, 0x7, 0x3, 0x0, 0x0};


// commands (see datasheet)
#define OLED_SET_CONTRAST _u(0x81)
#define OLED_SET_ENTIRE_ON _u(0xA4)
#define OLED_SET_NORM_INV _u(0xA6)
#define OLED_SET_DISP _u(0xAE)
#define OLED_SET_MEM_ADDR _u(0x20)
#define OLED_SET_COL_ADDR _u(0x21)
#define OLED_SET_PAGE_ADDR _u(0x22)
#define OLED_SET_DISP_START_LINE _u(0x40)
#define OLED_SET_SEG_REMAP _u(0xA0)
#define OLED_SET_MUX_RATIO _u(0xA8)
#define OLED_SET_COM_OUT_DIR _u(0xC0)
#define OLED_SET_DISP_OFFSET _u(0xD3)
#define OLED_SET_COM_PIN_CFG _u(0xDA)
#define OLED_SET_DISP_CLK_DIV _u(0xD5)
#define OLED_SET_PRECHARGE _u(0xD9)
#define OLED_SET_VCOM_DESEL _u(0xDB)
#define OLED_SET_CHARGE_PUMP _u(0x8D)
#define OLED_SET_HORIZ_SCROLL _u(0x26)
#define OLED_SET_SCROLL _u(0x2E)

#define OLED_ADDR _u(0x3C)
#define OLED_HEIGHT _u(32)
#define OLED_WIDTH _u(128)
#define OLED_PAGE_HEIGHT _u(8)
#define OLED_NUM_PAGES OLED_HEIGHT / OLED_PAGE_HEIGHT
#define OLED_BUF_LEN (OLED_NUM_PAGES * OLED_WIDTH)

#define OLED_WRITE_MODE _u(0xFE)
#define OLED_READ_MODE _u(0xFF)

struct render_area {
    uint8_t start_col;
    uint8_t end_col;
    uint8_t start_page;
    uint8_t end_page;

    int buflen;
};

void fill(uint8_t buf[], uint8_t fill) {
    // fill entire buffer with the same byte
    for (int i = 0; i < OLED_BUF_LEN; i++) {
        buf[i] = fill;
    }
};

void fill_page(uint8_t *buf, uint8_t fill, uint8_t page) {
    // fill entire page with the same byte
    memset(buf + (page * OLED_WIDTH), fill, OLED_WIDTH);
};

// convenience methods for printing out a buffer to be rendered
// mostly useful for debugging images, patterns, etc

void print_buf_page(uint8_t buf[], uint8_t page) {
    // prints one page of a full length (128x4) buffer
    for (int j = 0; j < OLED_PAGE_HEIGHT; j++) {
        for (int k = 0; k < OLED_WIDTH; k++) {
            printf("%u", (buf[page * OLED_WIDTH + k] >> j) & 0x01);
        }
        printf("\n");
    }
}

void print_buf_pages(uint8_t buf[]) {
    // prints all pages of a full length buffer
    for (int i = 0; i < OLED_NUM_PAGES; i++) {
        printf("--page %d--\n", i);
        print_buf_page(buf, i);
    }
}

void print_buf_area(uint8_t *buf, struct render_area *area) {
    // print a render area of generic size
    int area_width = area->end_col - area->start_col + 1;
    int area_height = area->end_page - area->start_page + 1; // in pages, not pixels
    for (int i = 0; i < area_height; i++) {
        for (int j = 0; j < OLED_PAGE_HEIGHT; j++) {
            for (int k = 0; k < area_width; k++) {
                printf("%u", (buf[i * area_width + k] >> j) & 0x01);
            }
            printf("\n");
        }
    }
}

void calc_render_area_buflen(struct render_area *area) {
    // calculate how long the flattened buffer will be for a render area
    area->buflen = (area->end_col - area->start_col + 1) * (area->end_page - area->start_page + 1);
}

#ifdef i2c_default

void oled_send_cmd(uint8_t cmd) {
    // I2C write process expects a control byte followed by data
    // this "data" can be a command or data to follow up a command

    // Co = 1, D/C = 0 => the driver expects a command
    uint8_t buf[2] = {0x80, cmd};
    i2c_write_blocking(i2c_default, (OLED_ADDR & OLED_WRITE_MODE), buf, 2, false);
}

void oled_send_buf(uint8_t buf[], int buflen) {
    // in horizontal addressing mode, the column address pointer auto-increments
    // and then wraps around to the next page, so we can send the entire frame
    // buffer in one gooooooo!

    // copy our frame buffer into a new buffer because we need to add the control byte
    // to the beginning

    // TODO find a more memory-efficient way to do this..
    // maybe break the data transfer into pages?
    uint8_t *temp_buf = malloc(buflen + 1);

    for (int i = 1; i < buflen + 1; i++) {
        temp_buf[i] = buf[i - 1];
    }
    // Co = 0, D/C = 1 => the driver expects data to be written to RAM
    temp_buf[0] = 0x40;
    i2c_write_blocking(i2c_default, (OLED_ADDR & OLED_WRITE_MODE), temp_buf, buflen + 1, false);

    free(temp_buf);
}

void oled_init() {
    // some of these commands are not strictly necessary as the reset
    // process defaults to some of these but they are shown here
    // to demonstrate what the initialization sequence looks like

    // some configuration values are recommended by the board manufacturer

    oled_send_cmd(OLED_SET_DISP | 0x00); // set display off

    /* memory mapping */
    oled_send_cmd(OLED_SET_MEM_ADDR); // set memory address mode
    oled_send_cmd(0x00); // horizontal addressing mode

    /* resolution and layout */
    oled_send_cmd(OLED_SET_DISP_START_LINE); // set display start line to 0

    oled_send_cmd(OLED_SET_SEG_REMAP | 0x01); // set segment re-map
    // column address 127 is mapped to SEG0

    oled_send_cmd(OLED_SET_MUX_RATIO); // set multiplex ratio
    oled_send_cmd(OLED_HEIGHT - 1); // our display is only 32 pixels high

    oled_send_cmd(OLED_SET_COM_OUT_DIR | 0x08); // set COM (common) output scan direction
    // scan from bottom up, COM[N-1] to COM0

    oled_send_cmd(OLED_SET_DISP_OFFSET); // set display offset
    oled_send_cmd(0x00); // no offset

    oled_send_cmd(OLED_SET_COM_PIN_CFG); // set COM (common) pins hardware configuration
    oled_send_cmd(0x02); // manufacturer magic number

    /* timing and driving scheme */
    oled_send_cmd(OLED_SET_DISP_CLK_DIV); // set display clock divide ratio
    oled_send_cmd(0x80); // div ratio of 1, standard freq

    oled_send_cmd(OLED_SET_PRECHARGE); // set pre-charge period
    oled_send_cmd(0xF1); // Vcc internally generated on our board

    oled_send_cmd(OLED_SET_VCOM_DESEL); // set VCOMH deselect level
    oled_send_cmd(0x30); // 0.83xVcc

    /* display */
    oled_send_cmd(OLED_SET_CONTRAST); // set contrast control
    oled_send_cmd(0xFF);

    oled_send_cmd(OLED_SET_ENTIRE_ON); // set entire display on to follow RAM content

    oled_send_cmd(OLED_SET_NORM_INV); // set normal (not inverted) display

    oled_send_cmd(OLED_SET_CHARGE_PUMP); // set charge pump
    oled_send_cmd(0x14); // Vcc internally generated on our board

    oled_send_cmd(OLED_SET_SCROLL | 0x00); // deactivate horizontal scrolling if set
    // this is necessary as memory writes will corrupt if scrolling was enabled

    oled_send_cmd(OLED_SET_DISP | 0x01); // turn display on
}

void render(uint8_t *buf, struct render_area *area) {
    // update a portion of the display with a render area
    oled_send_cmd(OLED_SET_COL_ADDR);
    oled_send_cmd(area->start_col);
    oled_send_cmd(area->end_col);

    oled_send_cmd(OLED_SET_PAGE_ADDR);
    oled_send_cmd(area->start_page);
    oled_send_cmd(area->end_page);

    oled_send_buf(buf, area->buflen);
}

#endif

int main() {
    stdio_init_all();

#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
    #warning i2c / oled_i2d example requires a board with I2C pins
    puts("Default I2C pins were not defined");
#else
    // useful information for picotool
    bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
    bi_decl(bi_program_description("OLED I2C example for the Raspberry Pi Pico"));

    printf("Hello, OLED display! Look at my raspberries..\n");

    // I2C is "open drain", pull ups to keep signal high when no data is being
    // sent
    i2c_init(i2c_default, 400 * 1000);
    gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
    gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);

    // run through the complete initialization process
    oled_init();

    // initialize render area for entire frame (128 pixels by 4 pages)
    struct render_area frame_area = {start_col: 0, end_col : OLED_WIDTH - 1, start_page : 0, end_page : OLED_NUM_PAGES -
                                                                                                        1};
    calc_render_area_buflen(&frame_area);

    // zero the entire display
    uint8_t buf[OLED_BUF_LEN];
    fill(buf, 0x00);
    render(buf, &frame_area);

    // intro sequence: flash the screen 3 times
    for (int i = 0; i < 3; i++) {
        oled_send_cmd(0xA5); // ignore RAM, all pixels on
        sleep_ms(500);
        oled_send_cmd(0xA4); // go back to following RAM
        sleep_ms(500);
    }

    // render 3 cute little raspberries
    struct render_area area = {start_col: 0, end_col : IMG_WIDTH - 1, start_page : 0, end_page : OLED_NUM_PAGES - 1};
    calc_render_area_buflen(&area);
    render(raspberry26x32, &area);
    for (int i = 1; i < 3; i++) {
        uint8_t offset = 5 + IMG_WIDTH; // 5px padding
        area.start_col += offset;
        area.end_col += offset;
        render(raspberry26x32, &area);
    }

    // configure horizontal scrolling
    oled_send_cmd(OLED_SET_HORIZ_SCROLL | 0x00);
    oled_send_cmd(0x00); // dummy byte
    oled_send_cmd(0x00); // start page 0
    oled_send_cmd(0x00); // time interval
    oled_send_cmd(0x03); // end page 3
    oled_send_cmd(0x00); // dummy byte
    oled_send_cmd(0xFF); // dummy byte

    // let's goooo!
    oled_send_cmd(OLED_SET_SCROLL | 0x01);

#endif
    return 0;
}

3.2.2 oled_send_cmd

代码很长,但是别紧张,我们将代码拆解,先了解核心部分,其他部分会容易很多。

首先,先看到代码的132到139行的oled_send_cmd(uint8_t cmd)

void oled_send_cmd(uint8_t cmd) {
    // I2C write process expects a control byte followed by data
    // this "data" can be a command or data to follow up a command

    // Co = 1, D/C = 0 => the driver expects a command
    uint8_t buf[2] = {0x80, cmd};
    i2c_write_blocking(i2c_default, (OLED_ADDR & OLED_WRITE_MODE), buf, 2, false);
}

还记得我在第二章讲解i2c_write_blocking()时提到的可以将地址和数据放到一个数组里发送吗?在上面的代码中,buf[2]就是这样一个数组。

数组中的第一个是寄存器地址,第二个是要发送的命令。Co为1表示连续写入,D/C为0表示是写入命令(Data/Command),所以是0x80(1000 0000)。

在这里插入图片描述

看到(OLED_ADDR & OLED_WRITE_MODE),你可能想起了我在第三章提到的,不需要考虑读或写模式下的地址,直接将7位地址作为参数即可,而这里却似乎对地址进行了更改,其实不是的,模块的地址依然为0x3C,地址后面是0,所以进行与操作是不会更改最后一位的。

总的来看,oled_send_cmd(uint8_t cmd)实现了命令的写入。

3.2.3 oled_send_buf

再看141行的void oled_send_buf(uint8_t buf[], int buflen),看到uint8_t *temp_buf = malloc(buflen + 1),以及i2c_write_blocking(i2c_default, (OLED_ADDR & OLED_WRITE_MODE), temp_buf, buflen + 1, false),这里其实就是生成一个数组,之所以要+1,是要有一个位置用于存放地址。

3.2.4 render

看到221行的void render(uint8_t *buf, struct render_area *area),要理解这个函数的意义,需要先看到SSD1306数据手册。OLED_SET_COL_ADDR0x21,在数据手册找到相关说明。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FgQHEfoJ-1673610623619)(/0x21.png)]

可以看到,oled_send_cmd(OLED_SET_COL_ADDR)发送了0x21之后,需要发送开始与结束的列,范围是0到127,所以就有以下两行代码:

oled_send_cmd(area->start_col);
oled_send_cmd(area->end_col);

那么,所谓的开始与结束的列是什么呢?我们再看看数据手册:
在这里插入图片描述

看到下面的SEG0-----------SEG127,这个就是了,一共128列。如果还是不太懂,我们再继续看数据手册:

在这里插入图片描述

看到SEG0,这个就是所谓的列了。一列有八个格子,稍后你可以尝试给一列设置为0xFF,查看显示效果。

现在,再继续往下看,这三行代码应该就比较好理解了。OLED_SET_PAGE_ADDR0x22,就在0x21的附近,自己在数据手册中找找吧。

oled_send_cmd(OLED_SET_PAGE_ADDR);
oled_send_cmd(area->start_page);
oled_send_cmd(area->end_page);

如果你仔细看上面的图或者数据手册,你对page这个单词一定不会陌生。

在这里插入图片描述

一共有8个page,每个page有8行,总共就是64,128列与64行,这个就是我们OLED模块常见的配置128×64了。

然后就是屏幕的初始化,我们再回到SSD1306数据手册,里面有Command Table,这些命令就是用来初始化的,你对照着代码看一下就应该懂了。

其他几个函数并不是必须的,有了上面的讲解,应该不会太难。

首先,需要将麦克风接入树莓派pico的GPIO引脚上,通过pico的ADC模块读取声音信号,然后将读取到的数据传入LM324运算放大器进行放大处理。接下来,将处理后的信号传入频谱分析算法中,计算出频谱数据并通过I2C总线传输给OLED显示屏进行实时显示。 具体实现步骤如下: 1. 准备材料 - 树莓派pico - OLED I2C显示屏 - LM324运算放大器 - 麦克风 - 杜邦线 2. 连接电路 将麦克风的VCC引脚连接到pico的3.3V引脚上,将GND引脚连接到pico的GND引脚上,将OUT引脚连接到pico的ADC引脚上。然后将LM324运算放大器的电源引脚连接到pico的3.3V引脚上,将GND引脚连接到pico的GND引脚上,将IN+引脚连接到pico的ADC引脚上,将IN-引脚连接到pico的GND引脚上,将OUT引脚连接到pico的GPIO引脚上。 3. 编写代码 使用Micropython语言编写代码,读取声音信号并通过LM324运算放大器进行放大处理,然后使用频谱分析算法计算出频谱数据,最后将数据通过I2C总线传输给OLED显示屏进行实时显示。具体代码如下: ```python from machine import Pin, ADC, I2C import ssd1306 import utime # 定义OLED显示屏相关参数 WIDTH = 128 HEIGHT = 64 i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=200000) oled = ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c) # 定义LM324运算放大器相关参数 gain = 100 # 定义频谱分析算法相关参数 N = 64 fs = 20000 f = [i * fs / N for i in range(N // 2)] y = [0 for i in range(N // 2)] # 初始化ADC模块 adc = ADC(0) while True: # 读取声音信号并进行放大处理 v = adc.read_u16() / 65535 * 3.3 v = v * gain # 更新频谱数据 y.pop(0) y.append(v) # 计算频谱并显示在OLED屏幕上 oled.fill(0) for i in range(N // 2): oled.line(i * 2, HEIGHT, i * 2, HEIGHT - int(y[i] / 10)) oled.show() # 等待一段时间后再次更新频谱数据 utime.sleep_ms(10) ``` 4. 运行程序 将代码上传到树莓派pico上,然后将OLED显示屏连接到pico的I2C引脚上,启动程序后,当麦克风接收到声音时,程序会实时计算出频谱数据并在OLED显示屏上显示出来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

存江

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

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

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

打赏作者

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

抵扣说明:

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

余额充值