文章目录
纯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 |
| 2 | m | measurement |
| 3 | '0'..'9' | msg_id ,48~57的循环,'0’ASCII对应值48 |
| 4–7 | trigger_index | 4 hex |
| 8–11 | extra1 | 4 hex(保留) |
| 12–15 | extra2 | 4 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版本
-
Github ADC10080: 主要是 用 RP2040 的 PIO 来驱动 ADC10080 并抓取数据,使用 sideset 控制时钟,同时用 FIFO + DMA 保存数据。

-
RP2040 的 PIO + MicroPython 来做高速采样,理论上可以比纯 Python 快很多
-
PIO(Programmable I/O) 是 RP2040 独立硬件,支持:
- 以极高速度采集 GPIO 状态
- 可以用 FIFO 与 CPU 交换数据
- 微秒级循环,不受 Python GIL 限制
项目架构
- 但是,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语言版本
- https://github.com/jklomp/pico-RP2040-oscilloscope,烧录文件后,打开项目中rp2040scope.html,选择CDC通信串口之后就能运行:

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

┌──────────┐
│ PC │
│ WEB程序 │
└────┬─────┘
│ USB CDC
┌────▼─────┐
│ TinyUSB │
│ CDC栈 │
└────┬─────┘
│
┌────▼──────────────┐
│ 你的应用代码 │
│ cdc_task() │
│ capture() │
└────┬──────────────┘
│
┌────▼─────┐
│ ADC + DMA│
└──────────┘
烧录
-
项目给出了 UF2 (USB Flashing Format)固件文件。用于方便通过 拖拽方式刷写 microcontroller(MCU)闪存。
-
Raspberry Pi Pico 有一个 Bootloader(引导程序):-按住 BOOTSEL ,然后将USB连接到电脑 → Pico 显示为 USB 大容量存储

-
可以把 UF2 文件直接拖进去
-
Bootloader 会:
- 读取 UF2 文件
- 写入闪存对应地址
- 自动重启 MCU 运行固件
- 如果想回到之前的状态,可下载官方 MicroPython UF2并烧录。
网页端
-
网页是 一个基于 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
- RP2040 ADC 最大采样速率 500 kS/s: It’s ADC takes a 96 CPU clock cycle to perform one conversion. Therefore, the sampling frequency is (96 x 1 / 48MHz) = 2 μs per sample (500kS/s).
- 用你的Rasperry Pi Pico和安卓手机作为示波器和逻辑分析仪
- 使用树莓派 Pico (RP2040) 和 Sigrok 作为逻辑分析仪和示波器
- TeenyUSB:适用于STM32和其他MCU的轻量级USB设备和主机协议栈。支持USB 3.0设备。
- CherryUSB协议栈的原理与使用
3503

被折叠的 条评论
为什么被折叠?



