一、评测目标
验证用 Grove Rotary Angle Sensor(旋转角度传感器 / 电位器) 作为模拟输入,将 A2 引脚采集到的电压实时映射为 Grove Green LED 的 PWM 占空比,验证整套"模拟采集 → 占空比输出"链路在 Nano RP2040 Connect 上的可行性、线性度和稳定性。具体回答三个问题:
ADC 实际分辨率是不是 10 bit?满量程是 1023 还是 4095?
旋钮角度(0°–300°)和 PWM 占空比(0%–100%)的对应关系是否线性?
在没有外部分压的情况下,A2 引脚上能否稳定达到接近 3.3 V 的满量程电压?
二、硬件清单
Grove 接口定义:
三、接线图
Rotary Angle Sensor 接 Nano RP2040 Connect 的 A2 母座(模拟输入)。
Green LED 接 D4 母座(PWM 输出)。
两块模块的 VCC 都接到板载 3.3V,不接 5V,因为 Nano RP2040 Connect 的 ADC 输入上限就是 3.3 V;超过会烧 IO。
四、软件设计
4.1 关键引脚
const uint8_t ADC_PIN = 16; // 丝印 A2 → Arduino D16 (p28 / GPIO28) const uint8_t PWM_PIN = 4; // 丝印 D4 → Arduino D4 (p16 / GPIO16)
踩坑记录:ArduinoCore‑mbed 的 variant.cpp 把丝印 A2 的"数字引脚号"定为 16(对应 GPIO28),直接写 A2 也可以,但写常量时建议统一用 D16 / D4 这种和丝印对齐的编号,避免把 16 误读成 GPIO16(D4)。
4.2 ADC 分辨率
const float VREF_NOMINAL = 3.3f; const uint16_t ADC_NOMINAL_MAX = 1023; // 实测:这块板 analogRead() 是 10 bit
实测确认:旋钮拧到最大时,万用表测得 A2 = 3.30 V,串口 adc=1020。
1020 ≈ 1023(差 4 个码值是分压损耗 + ADC 噪声),说明这块板的 analogRead() 实际返回 10 bit,不是 12 bit。
4.4 测试代码
#include <Arduino.h>
#include "pinDefinitions.h" // p16 / p18 等 PinName 常量(可选)
const uint8_t ADC_PIN = 16; // 丝印 A2 -> Arduino D16 (p28)
const uint8_t PWM_PIN = 4; // 丝印 D4 -> Arduino D4 (p16)
const float VREF_NOMINAL = 3.3f;
const uint16_t ADC_NOMINAL_MAX = 1023;
#define ENABLE_CALIBRATION 1
const uint32_t CALIB_MS = 5000; // 5 秒校准窗口
const uint16_t ADC_FLOOR = 8; // adc < 8 视为接近 0 V(防止信号源没接时被锁成 0)
const uint8_t PWM_MAX = 255;
const uint32_t LOG_INTERVAL_MS = 100;
const uint8_t DUTY_DELTA_PCT = 1;
void setup() {
// PWM 输出
pinMode(PWM_PIN, OUTPUT);
analogWrite(PWM_PIN, 0); // 初始占空比 = 0%
// 模拟输入(analogRead 内部已配置为输入,无需显式 pinMode)
// 调试串口
Serial.begin(115200);
// USB CDC 必须等到主机端"打开串口监视器"才被视为就绪。
// 为了不让程序在没有打开监视器时永久阻塞,这里加 2 s 超时。
uint32_t t0 = millis();
while (!Serial && (millis() - t0) < 2000) { /* 等枚举或超时 */ }
Serial.println();
Serial.println("=== A2 (analog) -> D4 (PWM) ready ===");
Serial.println(" format: V_in duty% raw_adc pwm_raw adc_full");
#if ENABLE_CALIBRATION
Serial.println();
Serial.print("[CALIB] 接下来 ");
Serial.print(CALIB_MS / 1000);
Serial.println(" 秒:请把 A2 信号源调到你能给的 **最大值**,保持住。");
Serial.println(" 程序会自动把它记作本机 ADC 满量程,再换算电压 / 占空比。");
Serial.println(" (不需要这个功能就把源码里 ENABLE_CALIBRATION 改成 0)");
// 在校准窗口内不输出 PWM,避免"低电压小灯"误读
analogWrite(PWM_PIN, 0);
#endif
// 用 D4 闪一下作为 setup() 心跳 —— 不开串口监视器也能肉眼确认固件在跑
analogWrite(PWM_PIN, 64); // ~25%
delay(100);
analogWrite(PWM_PIN, 0);
}
uint32_t lastLogMs = 0;
uint8_t lastDutyPct = 0xFF; // 强制首次一定会打
uint16_t adcFull = ADC_NOMINAL_MAX; // 校准中持续刷新
uint32_t calibStartMs = 0;
bool calibrated = false;
void loop() {
// 1) 读 A2(12 bit)
uint16_t adcRaw = analogRead(ADC_PIN);
// 2) 校准窗口:持续跟踪 adc 极大值作为本机满量程
if (!calibrated) {
if (calibStartMs == 0) {
calibStartMs = millis();
}
if (adcRaw > adcFull) {
adcFull = adcRaw;
}
if ((millis() - calibStartMs) >= CALIB_MS) {
// 校准结束。给一个合理下限,避免信号源根本没接 / 一直 0 时
// adcFull 也被锁在 0,下面所有映射都会爆。
if (adcFull < ADC_FLOOR) {
adcFull = ADC_FLOOR;
}
calibrated = true;
Serial.println();
Serial.print("[CALIB] done. adcFull=");
Serial.print(adcFull);
Serial.print(" (此即信号源最大值对应的 ADC 码值;打印的 V_in 由此换算)");
Serial.println();
}
}
uint8_t pwmRaw = (uint8_t)((uint32_t)adcRaw * PWM_MAX / adcFull);
analogWrite(PWM_PIN, pwmRaw);
uint8_t dutyPct = (uint8_t)((uint32_t)pwmRaw * 100 / PWM_MAX);
float vIn = (adcRaw * VREF_NOMINAL) / adcFull;
// 3) 节流打印
uint32_t now = millis();
if (now - lastLogMs >= LOG_INTERVAL_MS) {
lastLogMs = now;
if (dutyPct == lastDutyPct) {
Serial.print("V_in=");
Serial.print(vIn, 3);
Serial.print(" V adc=");
Serial.print(adcRaw);
Serial.print(" adcFull=");
Serial.println(adcFull);
} else {
Serial.print("V_in=");
Serial.print(vIn, 3);
Serial.print(" V duty=");
Serial.print(dutyPct);
Serial.print("% adc=");
Serial.print(adcRaw);
Serial.print(" pwm_raw=");
Serial.print(pwmRaw);
Serial.print(" adcFull=");
Serial.println(adcFull);
lastDutyPct = dutyPct;
}
}
// 简单周期 ≈ 10 ms(采样 + PWM + 串口)
delay(10);
}五、测试效果

我要赚赏金
