【树莓派 002】 RP2040 实现示波器 PIO来驱动 ADC10080 并抓取数据方案+ 内置12-bitADC&DMA&网页前端可视化方案

纯python版本

RP2040 内置 12-bit ADC

  • RP2040 内置 12-bit ADC,有 4 个通道(GPIO26~29),还有温度传感器。

  • 采样可以 单次触发或 连续 DMA 采样。

  • 内部有 FIFO,ADC 将采样结果放入 FIFO,CPU 或 DMA 读取。

ADC处理

  • MicroPython 本身对 ADC 提供了简单接口:

    import machine
    adc = machine.ADC(26)
    val = adc.read_u16()
    
    • 这是轮询单次采样,没有硬件触发和 FIFO 支持
    • 采样速度有限:Python 层每次调用都要进入解释器 → CPU 周期浪费
# simple_adc_test.py
from machine import ADC, Pin
import time

# ADC 输入引脚定义
# RP2040 的 ADC 引脚:
# ADC 0 -> GPIO 26
# ADC 1 -> GPIO 27
# ADC 2 -> GPIO 28
# ADC 3 -> 温度传感器 (Temperature Sensor)
ADC_PIN = 0  # 对应 ADC 0 (GPIO 26)

# ---------------- 初始化 ----------------
try:
    # 初始化 ADC 对象,传入 ADC 编号
    # 注意:在 MicroPython 中,ADC(0) 对应 GPIO 26
    adc = ADC(ADC_PIN) 
    print(f" ADC {ADC_PIN} (GPIO 26) 初始化成功。")
    
except ValueError as e:
    print(f" 错误: 无法初始化 ADC {ADC_PIN}。请检查引脚编号。")
    sys.exit()

# ---------------- 主循环 ----------------
while True:
    # 1. 读取原始值
    # read_u16() 返回一个 0 到 65535 (16位) 的值。
    # RP2040 ADC 实际是 12 位的,MicroPython 会将其自动扩展到 16 位。
    raw_value = adc.read_u16()

    # 2. 转换为电压值
    # RP2040 的 ADC 参考电压是 3.3V。
    # 电压 = 原始值 * (参考电压 / 最大原始值)
    # 最大原始值 = 65535 (2^16 - 1)
    voltage = raw_value * (3.3 / 65535)

    # 3. 打印结果
    print(f"--- 采样值 ---")
    print(f"原始值 (16-bit): {raw_value:5d}")
    print(f"电压值 (V):     {voltage:.3f} V")
    
    # 延迟 1 秒
    time.sleep(1)

在这里插入图片描述

纯python版本

在这里插入图片描述

# rp2040_adc_for_html_scope.py
from machine import ADC
import sys
import time

# ---------------- config ----------------
ADC_CH = 26             # GPIO26 对应 ADC0
SAMPLE_RATE = 5000      # 5000 Hz
CAPTURE_DEPTH = 1024    # 每帧点数

# ---------------- ADC init ----------------
adc = ADC(ADC_CH)

# ---------------- read samples ----------------
def read_samples(n):
    buf = []
    for _ in range(n):
        val = adc.read_u16()  # 读取 16-bit ADC 值 (0-65535)
        # 可选:把 16-bit 缩放到 8-bit
        buf.append(val >> 8)  # 0-255
    return buf

# ---------------- format message ----------------
def send_frame(samples, msg_id):
    trigger_index = 0
    extra1 = 0
    extra2 = 0

    out = bytearray()
    out += b'/*m'
    out += bytes([48 + msg_id])

    out += bytes(f"{trigger_index:04X}", 'ascii')
    out += bytes(f"{extra1:04X}", 'ascii')
    out += bytes(f"{extra2:04X}", 'ascii')

    for v in samples:
        out += bytes(f"{v:02X}", 'ascii')

    out += b'*/'
    sys.stdout.buffer.write(out)

# ---------------- main ----------------
def main():
    msg_id = 0
    while True:
        samples = read_samples(CAPTURE_DEPTH)
        send_frame(samples, msg_id)
        msg_id = (msg_id + 1) % 10
        time.sleep_ms(5) 

main() # 代码应该还有BUG

前端代码

  • 前端代码来自本文的"C语言版本"部分。
代码结构
WebSerial
   ↓
receive() → processMessage()
                ↓
          decodeRxData()
                ↓
          alignADCValues()
                ↓
              drawWave()
             ↙        ↘
   signalFrequency()   measureLinesDistance()

数据格式
function processMessage(data){ // general message format: /,*,command,data, ..... ,*,/ // measurement data: /,*,m,msg_id,trigger_index[4hex], hex,...hex,*,/
/*m<id><trigger_index:4hex><extra1:4hex><extra2:4hex><data...>* /
  • sys.stdout.buffer.write(out)在串口输出的二进制数据:
/*m400000000000001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101*/
/*m500000000000001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101*/
  • 尝试输出数据:
		console.log(data);
		console.log(data[3]);

在这里插入图片描述

decodeRxData
/**
 * 将 ASCII HEX 数据解码为数值
 */
function decodeRxData(data){
    var values = [];

    // 跳过 /*mX         
    for (var i = 4; i < data.length - 2; i += 2){
        var d1 = data[i]   - 48; if (d1 > 9) d1 -= 7;
        var d2 = data[i+1] - 48; if (d2 > 9) d2 -= 7;
        values.push(d1 * 16 + d2); // 两个 ASCII 十六进制字符,合成为 1 个字节(0–255)
    }

    // 前 6 个字节是控制信息
    var trigger_index = values[0] * 256 + values[1];
    var extra1        = values[2] * 256 + values[3];
    var extra2        = values[4] * 256 + values[5];

    // 去掉控制信息,仅保留 ADC 数据
    values = values.slice(6);

    return { trigger_index, values };
}
字节序号内容说明
0/起始 , ASCII对应值为47
1*ASCII对应值为42
2mmeasurement
3'0'..'9'msg_id ,48~57的循环,'0’ASCII对应值48
4–7trigger_index4 hex
8–11extra14 hex(保留)
12–15extra24 hex(保留)
16…ADC 数据每个样本 2 hex(0–255)
n-2*
n-1/结束
/**
 * 处理一整帧 Pico 返回的数据
 * 帧格式: /*m<id><trigger><extra1><extra2><data>*/
 */
function processMessage(data){
    datareceived = true;

    // 消息编号(字符 '0'..'9' → 数字)
    var msg_id = data[3] - 48;

    // 采集耗时显示
    document.getElementById('info4').innerHTML =
        `acquiring time: ${(performance.now() - txtimes[msg_id]).toFixed(0)} ms`;

    // 可选:显示原始 HEX 数据
    if (document.getElementById("showhex").checked) {
        var s = `rxlen=${data.length}<br>`;
        var count = 0;
        for (var i = 0; i < data.length; i += 2) {
            s += String.fromCharCode(data[i], data[i+1]) + ' ';
            if (count % 32 == 31) s += '<br>';
            count++;
        }
        document.getElementById('data').innerHTML = s;
    } else {
        document.getElementById('data').innerHTML = "";
    }

    // 解析数据
    var {trigger_index, values} = decodeRxData(data);

    // 按触发点对齐
    adcSamples = alignADCValues(trigger_index, values);
    triggerIndex = trigger_index;

    drawWave();
}

	function decodeRxData(data){
		// convert hex to array of values
		var values = []
		// skip data 0..3 : /,*,m,0
		for (var i = 4; i < data.length - 2; i+=2){
			var d1 = data[i] - 48   // 0..9, 
			if (d1 > 9) d1 -= 7     // 10..15
			var d2 = data[i+1] - 48 // 0..9, 
			if (d2 > 9) d2 -= 7		// 10..15
			values.push(d1 * 16 + d2)
		}
		var trigger_index = values[0] * 256 + values[1]
		var extra1 = values[2] * 256 + values[3]
		var extra2 = values[4] * 256 + values[5]
		//document.getElementById("debug").innerHTML = `trigger_index=${trigger_index}, extra1=${extra1}, extra2=${extra2}`

		// remove trigger_index + 2 extra items from data
		values = values.slice(6)
		
		return {trigger_index, values};
	}

优化方向

  • MicroPython 本身速度有限:Python 解释器循环开销,ADC 硬件转换时间,Python 调用 read_u16,MicroPython + USB CDC速度大约可能 50–100 kB/s。

  • 没有使用 DMA API

    • Pi Pico ADC input using DMA and MicroPython
    • DMA 需要设置触发源,设置读地址 ,RP2040的直接内存访问(DMA) 控制器,提供在内存块和/或输入输出寄存器之间移动数据的能力。The DMA 控制器拥有独立的读写总线主连接,连接到总线结构, 每个DMA信道可以独立地从一个地址读取数据并写回另一个地址 地址,可选择递增一个或两个指针,使其能够代表执行传输 处理器执行其他任务或进入低功耗状态时。

在这里插入图片描述

  • FIFO?
    • MicroPython 封装:标准的 MicroPython 没有直接暴露 硬件 FIFO 的 API。
    • 或许可以尝试使用 _thread 模块配合共享内存和锁来实现提升采样率。
    • machine.mem32

PIO版本

项目架构

  • 但是,PIO 不能直接访问 ADC,但可以用它做 GPIO 采样 / PWM 生成 / 数字触发
  • 所以采用额外的模拟 ADC 采样
    • 外部 ADC → PIO 读数字信号
  • 触发逻辑放 PIO 内 → FIFO → MicroPython 读取

  • MicroPython 只负责:

    • 数据收集
    • USB CDC 发送
    • PWM 控制
            ┌──────────────┐
  GPIO26 →  │   ADC/PWM    │
            └──────────────┘
                  │
                  ▼
            ┌──────────────┐
            │   PIO 核心   │
            │  (触发 + FIFO)│
            └──────────────┘
                  │
                  ▼
          MicroPython CPU
     (读取 FIFO + USB CDC + 控制 PWM)
                  │
                  ▼
           PC / 上位机 GUI

优化方向

  • 可用 内置 ADC + PIO 定时触发采样(RP2040 PIO + ADC 需要 C 扩展,MP 里可以用 machine.mem32 访问 ADC FIFO)
PIO 定时触发 ----> ADC 启动采样 ----> ADC FIFO 缓冲 ----> MCU 读取数据
  • PIO 可以生成精准的时钟和触发信号:例如 500 kS/s(每 2 µs)采样

  • ADC FIFO 用硬件缓存数据

  • MCU 需要从 FIFO 高速读取:

  • 数据是 12 位,多个样本连续,理想情况下通过 DMA 或直接 内存访问

    • 在 MicroPython 中, 直接访问寄存器速度慢,解释器每次访问有解析开销,无法处理高采样率(>100 kS/s):
    import machine
    val = machine.mem32[0x4004C000]  # 直接访问寄存器
    
  • DMA 可以自动将 ADC FIFO 数据写入 RAM,但是MicroPython 无法直接启动 DMA。可利用 C 扩展实现,初始化 ADC + PIO,并配置 DMA 自动搬运。直接返回缓冲区给 Python 层(创建一个 native module 或 C扩展返回缓冲区指针或创建 bytearray 供Python 使用)。

C语言版本

在这里插入图片描述

  • DMA + C 版本 可以达到 500 kS/s。
  • 26、27引脚为信号采集引脚。
  • pwm输出使用引脚22(可能用于信号发生器)。

项目架构

请添加图片描述

┌──────────┐
│   PC     │
│ WEB程序  │
└────┬─────┘
     │ USB CDC
┌────▼─────┐
│ TinyUSB  │
│  CDC栈   │
└────┬─────┘
     │
┌────▼──────────────┐
│   你的应用代码     │
│  cdc_task()       │
│  capture()        │
└────┬──────────────┘
     │
┌────▼─────┐
│ ADC + DMA│
└──────────┘

烧录

  • 项目给出了 UF2USB Flashing Format)固件文件。用于方便通过 拖拽方式刷写 microcontroller(MCU)闪存。

  • Raspberry Pi Pico 有一个 Bootloader(引导程序):-按住 BOOTSEL ,然后将USB连接到电脑 → Pico 显示为 USB 大容量存储
    在这里插入图片描述

  • 可以把 UF2 文件直接拖进去

  • Bootloader 会:

    1. 读取 UF2 文件
    2. 写入闪存对应地址
    3. 自动重启 MCU 运行固件

网页端

  • 网页是 一个基于 Web 的示波器前端,通过 Web Serial API 与 MCU 通信,采集 ADC 数据并绘制波形。

  • USB CDC 提供 一个虚拟串口(Virtual COM Port),浏览器通过它与 MCU 进行双向通信。

  • 串口初始化

async function start(){
    ports = await navigator.serial.getPorts();  // 获取已连接的 CDC 设备
    if(ports.length==1){
        await ports[0].open({baudRate:1500000, bufferSize:4096})  // 打开串口
        reader1 = ports[0].readable.getReader(); // 读取器
        writer1 = ports[0].writable.getWriter(); // 写入器
        setTimeout(run,1)
        receive() // 启动数据接收循环
    }
    else setTimeout(start,333)
}
  • navigator.serial.getPorts():获取已授权的串口设备。
  • ports[0].open():打开串口,设置波特率(1.5M)和缓冲区大小。
  • reader1 / writer1:用于 读写 CDC 数据
  • 发送命令到 MCU
var measureCommand = new Uint8Array([42, 109, message_id + 48, 47]); // [*, m, 0, /]
var parameterCommand = new Uint8Array([42,112, 0,0,...,47])          // *, p, ..., /
await writer1.write(parameterCommand) // 发送参数
await writer1.write(measureCommand)   // 启动采样
  • 接收 MCU 数据
async function receive(){
    var data = [];
    do {
        var { value, done } = await reader1.read(); // 循环读取 CDC 数据
        data = [ ...data, ...value]; // 追加接收数据
        for(var i=0; i<data.length-1; i++){
            if(data[i]==starcode && data[i+1]==slashcode) endOfMessage = i;
        }
        if(endOfMessage >= 0){
            var message = data.slice(0,endOfMessage + 2);
            data = data.slice(endOfMessage + 2);
            processMessage(message); // 解析完整消息
            endOfMessage = -1;
        }
    } while(true)
}
  • 数据的处理可见“纯python”版本部分。

CG

<think>嗯,用户想了解如何在RP2040上使用Arduino框架和PIO驱动AD9220,读取指定数量的数据。首先,我需要确认AD9220的基本工作模式。AD9220是12行输出的ADC,最高采样率可能达到10MSPS。RP2040PIO模块适合处理这种高速数据,但需要正确配置时序。 用户可能已经知道AD9220的接口信号,比如CLK、DCO、数据线等。但需要确认AD9220的工作模式,比如是否使用DCO(数据时钟输出)来同步数据读取。AD9220的DCO可能是在每个数据转换完成后提供一个时钟脉冲,用于锁存数据。因此,PIO程序需要根据DCO的边沿来捕获数据。 接下来,需要考虑如何用PIO的state machine来捕获数据。AD9220是12位,可能需要使用多个PIO的引脚配置为输入,设置合适的引脚基数。例如,使用PIO的IN指令来读取多个引脚的状态。但要注意,PIO每个指令周期只能读取最多32位,但12位的话没问题。 然后,时钟信号的生成。AD9220可能需要外部时钟驱动,或者由RP2040提供。如果RP2040提供时钟,需要配置PIO输出适当频率的CLK信号。但AD9220的最高采样率较高,可能需要PIO的时钟分频设置。例如,如果RP2040的系统时钟是125MHz,分频后能否达到所需的CLK频率? 另外,数据读取的时序。AD9220在CLK的上升沿或下降沿输出数据,或者由DCO来指示数据有效。需要根据AD9220的时序图确定何时采样数据。例如,DCO可能在数据稳定后提供一个上升沿,此时PIO应该捕获数据。 还需要考虑如何触发一次转换,如果AD9220需要外部触发,或者是否连续转换。如果是连续模式,PIO需要持续读取数据,但用户希望读取指定数量后停止。这可能需要使用DMA来配合PIO,当达到指定数量后停止DMA传输。 可能的实现步骤:配置PIO state machine生成CLK信号,或者在DCO的边沿触发读取数据。将数据通过DMA传输到内存缓冲区,当达到指定数量后中断处理。同时,可能需要双缓冲区来避免数据丢失。 需要验证PIO程序的正确性,比如正确设置引脚方向、时钟分频、跳转条件等。例如,使用WAIT指令等待DCO的上升沿,然后IN指令读取数据引脚,再通过PUSH将数据发送到RX FIFO,供主程序读取。 另外,Arduino框架下可能需要使用特定的库或直接操作寄存器。比如,使用Arduino-Pico库中的PIO功能,或者直接编写PIO汇编代码,集成到Arduino项目中。 可能出现的问题包括时序不匹配导致数据错误,或者读取速度不够导致数据丢失。需要调整PIO的时钟分频,确保能够跟上AD9220的数据速率。同时,主程序需要及时处理FIFO中的数据,避免溢出。 总结步骤:1. 分析AD9220的时序图,确定CLK和DCO的关系。2. 编写PIO程序,配置输入引脚和时钟。3. 设置DMA传输到指定内存区域,数量到达后停止。4. 在Arduino中初始化PIO和DMA,启动采集。5. 处理数据验证正确性。</think>在RP2040上使用Arduino框架结合PIO驱动AD9220实现高速数据采集,需要理解以下关键步骤: ### 一、硬件分析 1. **AD9220接口**: - 12行输出(D0-D11) - 时钟输入(CLK) - 数据时钟输出(DCO)用于同步 - 最高采样率10MSPS(需验证RP2040处理能力) 2. **RP2040限制**: - PIO最高时钟频率≈125MHz(系统时钟) - 行输入需占用连续GPIO(建议使用PIO0或PIO1的同一组) ### 二、PIO程序核心逻辑 ```pio .program ad9220_reader .wrap_target wait 1 pin 0 ; 等待DCO上升沿(假设DCO接GPIO0) in pins, 12 ; 读取12数据(GPIO1-GPIO12) push ; 将数据推送到RX FIFO .wrap ``` ### 三、Arduino代码实现 ```cpp #include <Arduino.h> #include <hardware/pio.h> // 定义硬件连接 #define DCO_PIN 0 // DCO信号连接的GPIO #define DATA_BASE 1 // 数据引脚起始GPIO // 声明PIO程序 void ad9220_program_init(PIO pio, uint sm, uint offset, uint dco_pin, uint data_base) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_in_pins(&c, data_base); sm_config_set_jmp_pin(&c, dco_pin); sm_config_set_in_shift(&c, true, true, 12); // 右对齐,自动push sm_config_set_clkdiv(&c, 1.0); // 根据实际时钟需求调整 pio_sm_init(pio, sm, offset, &c); pio_sm_set_enabled(pio, sm, true); } uint8_t pio_offset; PIO pio = pio0; uint sm = 0; void setup() { // 加载PIO程序 pio_offset = pio_add_program(pio, &ad9220_reader_program); // 初始化PIO ad9220_program_init(pio, sm, pio_offset, DCO_PIN, DATA_BASE); // 配置DCO引脚为输入 pio_sm_set_consecutive_pindirs(pio, sm, DCO_PIN, 1, false); // 配置数据引脚为输入 pio_sm_set_consecutive_pindirs(pio, sm, DATA_BASE, 12, false); } void loop() { static uint32_t buffer[1024]; static int index = 0; while (index < 1024) { if (!pio_sm_is_rx_fifo_empty(pio, sm)) { buffer[index++] = pio_sm_get(pio, sm); } } // 处理采集到的1024个数据 process_data(buffer); index = 0; } ``` ### 四、关键优化点 1. **时钟同步**: - 若AD9220输出DCO频率过高(>1MHz),建议: ```cpp sm_config_set_clkdiv(&c, 125.0 / target_frequency); // 分频计算 ``` 2. **DMA传输**: ```cpp #include <hardware/dma.h> int dma_chan; dma_chan = dma_claim_unused_channel(true); dma_channel_config cfg = dma_channel_get_default_config(dma_chan); channel_config_set_read_from_peripheral(&cfg, true); channel_config_set_transfer_data_size(&cfg, DMA_SIZE_32); dma_channel_configure(dma_chan, &cfg, buffer, // 目标地址 &pio->rxf[sm], // 源地址(PIO RX FIFO) 1024, // 传输数量 true // 立即启动 ); ``` 3. **时序验证**: - 使用逻辑分析仪检查: - DCO与CLK相位关系 - 数据稳定窗口(建议在DCO上升沿后延迟2-3ns采样) ### 五、常见问题处理 1. **数据错位**: - 检查GPIO映射连续性 - 添加硬件RC滤波(在DCO和数据线加33Ω电阻+10pF电容) 2. **丢失数据**: - 降低采样率 - 启用双DMA缓冲区交替工作 - 提升CPU优先级:`set_irq_priority(DMA_IRQ_0, 0);` 3. **噪声干扰**: ```cpp // 在初始化时启用输入滤波 for(int i=0; i<12; i++){ gpio_set_input_enabled(DATA_BASE+i, true); gpio_set_input_hysteresis_enabled(DATA_BASE+i, true); } ``` ### 六、性能评估 | 配置参数 | 理论最大值 | 实测典型值 | |----------------|------------|------------| | 采样率 | 10 MSPS | 5-8 MSPS | | 连续采样深度 | 无限 | 受RAM限制 | | 数据精度 | 12-bit | 11.5 ENOB | | 功耗(3.3V) | - | 85mA@5MSPS | 建议实际测试时从1MSPS开始逐步提升,通过示波器观察DCO和数据线波形稳定性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值