@[toc]
概要

本實驗通過一個“小實驗框架 GPIO Mode Lab”,在同一個 GPIO 引腳上依次配置不同模式,并用 ADC 探頭測量電壓、同時讀取數字電平,系統化地觀察:
- 內部上拉 / 下拉電阻的大致阻值和效果
- 外部電阻(不同阻值、接 VCC / 接 GND / 懸空)與內部電阻的“誰主導”關系
- 推挽輸出(OUTPUT)和開漏輸出(OPEN-DRAIN)的真實行為
- 浮空輸入在模擬量和數字量上的不穩定性
并由此得出一系列可量化的直觀結論,可寫成一篇“GPIO 行為直觀實驗報告”。
整體架構流程
整體分為三層:
- 實驗框架層:GPIO Mode Lab 類
- 串口交互層:MiniShell + 菜單
- 用戶通過串口輸入:
- 外部電阻值
R_ext(單位 Ω,支持 0 表示無) - 電阻連接方式:
node → R → GND / VCC / none
- 外部電阻值
- 支持多輪實驗連續運行,任意輸入階段按
q/Q退出。
- 用戶通過串口輸入:
- 測量與分析層:ADC + 簡單電路模型
技術名詞解釋
- INPUT(浮空輸入)
僅打開數字輸入緩沖,不啟用內部上拉/下拉。引腳呈高阻態,電平完全由外部電路和泄漏、噪聲等決定。 - INPUT_PULLUP / INPUT_PULLDOWN
在 INPUT 的基礎上,內部通過一只幾十 kΩ 量級的“弱上拉 / 弱下拉”電阻,把引腳輕微拉向 VCC 或 GND,常用于按鍵等簡單輸入,避免懸空。 - INPUT_ANALOG
關閉數字輸入緩沖和施密特觸發器,僅保留到 ADC 的模擬路徑,減小噪聲和漏電,專用于電壓采樣。 - OUTPUT(推挽輸出)
上下兩個 MOS 管組成推挽結構,可主動拉高到 VCC 或拉低到 GND,等效輸出電阻較小,能驅動一定電流。 - OUTPUT_OPEN_DRAIN(開漏輸出)
僅有下拉管能導通到 GND,上拉管常關;輸出 LOW 時主動拉低,輸出 HIGH 時為高阻態,需要外部上拉決定高電平,適合 I2C、線與等總線。 - 浮空(Floating)
引腳未通過明顯的上拉/下拉或驅動源確定電平,表現為 ADC 讀數在中間隨機漂移,digitalRead 可能隨機判 0/1。 - 內部上拉/下拉電阻
MCU 內部集成的可選電阻網絡,通常在幾十 kΩ 量級,用于給輸入引腳提供弱上拉/下拉,避免懸空。
技術細節
1. 測量配置與流程
- ADC 分辨率:10 位,
0..1023,Vref ≈ 3.3 V。 - 每個模式下:
2. 外部電阻與接法的實驗組合
通過串口交互設置:
R_ext(示例:100 kΩ、15 kΩ;輸入整數:100000、15000)- 接法:
node -> R -> GND / VCC / none
本次記錄的數據覆蓋六種典型組合:
R_ext = 100 kΩ,接 VCCR_ext = 100 kΩ,接 GNDR_ext = 100 kΩ,邏輯上當作 none(菜單選 3)R_ext = 15 kΩ,接 VCCR_ext = 15 kΩ,接 GNDR_ext = 15 kΩ,邏輯上當作 none(菜單選 3)
3. 實驗數據表格
3.1 R_ext = 100 kΩ,接 VCC
| 序號 | 模式 | ADC | 電壓 (V) | digitalRead | 備注 |
|---|---|---|---|---|---|
| 1 | INPUT (floating) | 715 | 2.306 | 1 | 外部 100k 上拉占主導,輸入腳高阻跟著被拉高 |
| 2 | INPUT_PULLUP | 917 | 2.958 | 1 | 內部上拉與外部 100k 上拉并聯,更接近 VCC |
| 3 | INPUT_PULLDOWN | 268 | 0.865 | 0 | 內部下拉 vs 外部上拉分壓,電平在中間偏低 |
| 4 | INPUT_ANALOG | 713 | 2.300 | 1 | 數字緩沖關掉,模擬只看到外部 100k 上拉 |
| 5 | OUTPUT LOW | 0 | 0.000 | 0 | 推挽強拉低,壓制 100k 上拉 |
| 6 | OUTPUT HIGH | 1022 | 3.297 | 1 | 推挽強拉高,與上拉同向 |
| 7 | OUTPUT_OPEN_DRAIN LOW | 0 | 0.000 | 0 | 開漏下管導通,壓制外部上拉 |
| 8 | OUTPUT_OPEN_DRAIN HIGH | 715 | 2.306 | 1 | 開漏高阻,完全由 100k 上拉決定 |
3.2 R_ext = 100 kΩ,接 GND
| 序號 | 模式 | ADC | 電壓 (V) | digitalRead | 備注 |
|---|---|---|---|---|---|
| 1 | INPUT (floating) | 0 | 0.000 | 0 | 外部 100k 下拉占主導 |
| 2 | INPUT_PULLUP | 653 | 2.106 | 1 | 內部上拉 vs 100k 下拉分壓,估算 Rup≈56.7 kΩ |
| 3 | INPUT_PULLDOWN | 0 | 0.000 | 0 | 內部+外部下拉疊加,牢牢在 0V |
| 4 | INPUT_ANALOG | 0 | 0.000 | 0 | 模擬輸入也只看到外部下拉 |
| 5 | OUTPUT LOW | 0 | 0.000 | 0 | 推挽強拉低 |
| 6 | OUTPUT HIGH | 1022 | 3.297 | 1 | 推挽強拉高,壓制 100k 下拉 |
| 7 | OUTPUT_OPEN_DRAIN LOW | 0 | 0.000 | 0 | 開漏拉低 |
| 8 | OUTPUT_OPEN_DRAIN HIGH | 0 | 0.000 | 0 | 開漏高阻 + 僅有 100k 下拉 → 節點仍為低 |
3.3 R_ext = 100 kΩ,邏輯當作 none(菜單選 3)
注:代碼中
extType=EXT_NONE,因此提示為“no external resistor (node floating)”,物理上是否仍接 100k 視實驗連線而定。這里按“邏輯視為懸空”來理解。
| 序號 | 模式 | ADC | 電壓 (V) | digitalRead | 備注 |
|---|---|---|---|---|---|
| 1 | INPUT (floating) | 128 | 0.413 | 1 | 浮空,受雜散電容/泄漏影響,偏低但數字偶判 1 |
| 2 | INPUT_PULLUP | 861 | 2.777 | 1 | 僅內部上拉,電平接近 VCC |
| 3 | INPUT_PULLDOWN | 0 | 0.000 | 0 | 僅內部下拉 |
| 4 | INPUT_ANALOG | 237 | 0.765 | 0 | 模擬浮空,電壓在低中區間漂移 |
| 5 | OUTPUT LOW | 0 | 0.000 | 0 | 推挽拉低 |
| 6 | OUTPUT HIGH | 1022 | 3.297 | 1 | 推挽拉高 |
| 7 | OUTPUT_OPEN_DRAIN LOW | 0 | 0.000 | 0 | 開漏拉低 |
| 8 | OUTPUT_OPEN_DRAIN HIGH | 238 | 0.768 | 0 | 開漏高阻 + 無上拉,下垂到中間偏低 |
3.4 R_ext = 15 kΩ,接 GND
| 序號 | 模式 | ADC | 電壓 (V) | digitalRead | 備注 |
|---|---|---|---|---|---|
| 1 | INPUT (floating) | 0 | 0.000 | 0 | 外部 15k 下拉很強,直接拉到 0V |
| 2 | INPUT_PULLUP | 276 | 0.890 | 0 | 15k 下拉明顯比內部上拉強,節點偏低;估算 Rup≈40.6 kΩ |
| 3 | INPUT_PULLDOWN | 0 | 0.000 | 0 | 內部+外部下拉,更低 |
| 4 | INPUT_ANALOG | 0 | 0.000 | 0 | 模擬通道也看到 0V |
| 5 | OUTPUT LOW | 0 | 0.000 | 0 | 推挽拉低 |
| 6 | OUTPUT HIGH | 1021 | 3.294 | 1 | 推挽拉高,壓制 15k 下拉 |
| 7 | OUTPUT_OPEN_DRAIN LOW | 0 | 0.000 | 0 | 開漏拉低 |
| 8 | OUTPUT_OPEN_DRAIN HIGH | 0 | 0.000 | 0 | 開漏高阻 + 15k 下拉 → 節點為低 |
3.5 R_ext = 15 kΩ,接 VCC
| 序號 | 模式 | ADC | 電壓 (V) | digitalRead | 備注 |
|---|---|---|---|---|---|
| 1 | INPUT (floating) | 995 | 3.210 | 1 | 外部 15k 上拉很強,幾乎 3.3V |
| 2 | INPUT_PULLUP | 1013 | 3.268 | 1 | 內部+外部上拉并聯,更接近滿刻度 |
| 3 | INPUT_PULLDOWN | 738 | 2.381 | 1 | 內部下拉 vs 15k 上拉分壓,仍被視為 HIGH |
| 4 | INPUT_ANALOG | 996 | 3.213 | 1 | 模擬通道看到 3.2V 左右 |
| 5 | OUTPUT LOW | 1 | 0.003 | 0 | 推挽強拉低,對 15k 上拉也是輕松壓制 |
| 6 | OUTPUT HIGH | 1022 | 3.297 | 1 | 推挽拉高,與上拉同向 |
| 7 | OUTPUT_OPEN_DRAIN LOW | 1 | 0.003 | 0 | 開漏拉低 |
| 8 | OUTPUT_OPEN_DRAIN HIGH | 995 | 3.210 | 1 | 開漏高阻 + 15k 上拉 → 節點接近 3.3V |
3.6 R_ext = 15 kΩ,邏輯當作 none(菜單選 3)
同樣地,代碼邏輯將其視為“無外部電阻”,以下理解為“懸空”場景。
| 序號 | 模式 | ADC | 電壓 (V) | digitalRead | 備注 |
|---|---|---|---|---|---|
| 1 | INPUT (floating) | 174 | 0.561 | 1 | 浮空偏低,但數字采樣到 1(說明門限在中間) |
| 2 | INPUT_PULLUP | 860 | 2.774 | 1 | 僅內部上拉,接近 VCC |
| 3 | INPUT_PULLDOWN | 0 | 0.000 | 0 | 僅內部下拉 |
| 4 | INPUT_ANALOG | 232 | 0.748 | 0 | 浮空模擬,低中間漂移 |
| 5 | OUTPUT LOW | 0 | 0.000 | 0 | 推挽拉低 |
| 6 | OUTPUT HIGH | 1022 | 3.297 | 1 | 推挽拉高 |
| 7 | OUTPUT_OPEN_DRAIN LOW | 0 | 0.000 | 0 | 開漏拉低 |
| 8 | OUTPUT_OPEN_DRAIN HIGH | 180 | 0.581 | 0 | 開漏高阻 + 無上拉,電平漂在中間偏低 |
同樣地,代碼邏輯將其視為“無外部電阻”,以下理解為“懸空”場景。
| 序號 | 模式 | ADC | 電壓 (V) | digitalRead | 備注 |
|---|---|---|---|---|---|
| 1 | INPUT (floating) | 174 | 0.561 | 1 | 浮空偏低,但數字采樣到 1(說明門限在中間) |
| 2 | INPUT_PULLUP | 860 | 2.774 | 1 | 僅內部上拉,接近 VCC |
| 3 | INPUT_PULLDOWN | 0 | 0.000 | 0 | 僅內部下拉 |
| 4 | INPUT_ANALOG | 232 | 0.748 | 0 | 浮空模擬,低中間漂移 |
| 5 | OUTPUT LOW | 0 | 0.000 | 0 | 推挽拉低 |
| 6 | OUTPUT HIGH | 1022 | 3.297 | 1 | 推挽拉高 |
| 7 | OUTPUT_OPEN_DRAIN LOW | 0 | 0.000 | 0 | 開漏拉低 |
| 8 | OUTPUT_OPEN_DRAIN HIGH | 180 | 0.581 | 0 | 開漏高阻 + 無上拉,電平漂在中間偏低 |
4. GPIO 模式與外部場景對比總表
下面用一張總表,橫向對比“同一個 GPIO 模式在不同外部電阻場景下”的典型行為,便于在文章中一眼看出規律。
說明:
- “低 / 高”指數字電平穩定為 0 / 1;
- “中間電壓”指 ADC 在 0.8–2.5 V 區間,屬于分壓或浮空狀態;
- “浮空/不穩定”指 ADC 明顯在中間且 digitalRead 有抖動可能。
| 模式 / 場景 | 100 kΩ → GND | 100 kΩ → VCC | 浮空(ext=none) | 15 kΩ → GND | 15 kΩ → VCC |
|---|---|---|---|---|---|
| INPUT (floating) | 0 V,穩定低,完全由外部下拉決定 | ≈2.3 V,中間偏高,數字判高 | 0.4–0.6 V 浮動,數字判值不穩定 | 0 V,穩定低,下拉很強 | ≈3.2 V,穩定高,上拉很強 |
| INPUT_PULLUP | ≈2.1 V,中間偏高,數字判高,弱上拉與 100k 下拉分壓 | ≈3.0 V,接近 VCC,內部+外部上拉并聯更硬 | ≈2.8 V,穩定高,僅內部上拉 | ≈0.9 V,中間偏低,數字已判低,15k 壓制內部上拉 | ≈3.27 V,穩定高,內部+15k 上拉都朝上 |
| INPUT_PULLDOWN | 0 V,穩定低,內部+外部都往下拉 | ≈0.87 V,中間偏低,數字判低 | 0 V,穩定低,僅內部下拉 | 0 V,穩定低,內部+15k 下拉都往下 | ≈2.38 V,中間偏高,數字已判高,15k 壓制內部下拉 |
| INPUT_ANALOG | 0 V,只看到外部下拉 | ≈2.3 V,只看到外部上拉 | 0.7–0.8 V 左右,中間電壓,浮空漂移 | 0 V,只看到外部 15k 下拉 | ≈3.21 V,只看到外部 15k 上拉 |
| OUTPUT LOW | 0 V,強拉低,壓制 100k 下拉 | 0 V,強拉低,壓制 100k 上拉 | 0 V,強拉低 | 0 V,強拉低,壓制 15k 下拉 | ≈0 V,強拉低,壓制 15k 上拉 |
| OUTPUT HIGH | ≈3.30 V,強拉高,壓制 100k 下拉 | ≈3.30 V,強拉高,與 100k 上拉同向 | ≈3.30 V,強拉高 | ≈3.29 V,強拉高,壓制 15k 下拉 | ≈3.30 V,強拉高,與 15k 上拉同向 |
| OPEN_DRAIN LOW | 0 V,下管導通,壓制外部 | 0 V,下管導通,壓制外部 | 0 V,下管導通 | 0 V,下管導通 | ≈0 V,下管導通 |
| OPEN_DRAIN HIGH(高阻) | 0 V,高阻 + 100k 下拉 → 低 | ≈2.3 V,高阻 + 100k 上拉 → 中間/偏高,高電平 | 0.6–0.8 V,中間電壓,浮空漂移,數字多為低 | 0 V,高阻 + 15k 強下拉 → 低 | ≈3.2 V,高阻 + 15k 強上拉 → 穩定高 |
小結
結合上面的完整表格,可以得出幾條在結論(每條都能用上述數據佐證):
- 內部上拉電阻量級在幾十千歐,可以用“R_ext→GND + ADC 分壓”反推出大致范圍
R_ext=100 kΩ → GND時推算約 56.7 kΩ,R_ext=15 kΩ → GND時推算約 40.6 kΩ,印證了“弱上拉”的工程經驗。
- 外部電阻是“比拼阻值”的游戲:誰阻值小,誰主導節點電平
- 100 kΩ 下拉 vs 內部上拉:得到中間電壓(約 2.1 V);
- 15 kΩ 下拉 vs 內部上拉:節點被明顯拉向 0 V,digitalRead 直接讀 0。
- 當外部電阻和內部上/下拉連在同一側電源時,只能增強高/低電平,不能再用來估 R_up
- 比如 15 kΩ → VCC + INPUT_PULLUP,電壓接近滿刻度,只能說明“上拉更硬”,不適合再反算內部阻值。
- 浮空輸入真的會亂飄,模擬值在 0.x V 區間抖動,數字判 0/1 都有可能
R_ext=none的多組數據表明,INPUT/INPUT_ANALOG 下 ADC 在 0.4–0.8 V 對應的計數間漂移,digitalRead 既有 0 也有 1,說明浮空腳高度不可靠。
- 推挽輸出表現為幾乎理想的電壓源,輕松壓住 15 kΩ、100 kΩ 這類負載
- 不論 100 kΩ 還是 15 kΩ 接到 VCC 或 GND,
OUTPUT HIGH/LOW電壓幾乎仍為理想的 0 / 3.3 V,佐證了推挽輸出的強驅動能力。
- 不論 100 kΩ 還是 15 kΩ 接到 VCC 或 GND,
- 開漏輸出 HIGH 態的“高阻”本質:節點完全等于外部網絡的結果
OD HIGH + R→GND得低電平,OD HIGH + R→VCC得高電平,OD HIGH + none得中間漂移電平,清楚展示了開漏 + 上拉構成“線與總線”的物理基礎。
代碼部分
/**
* @brief 示例主程序
*
* 通過編譯開關選擇運行不同的硬件實驗:
* - LED 自動檢測實驗(使用 AutoDetect 框架)
* - GPIO 模式學習實驗(使用 GpioModeLab)
*/
#include < PinNames.h >
#include "AutoDetect.h"
#include "GpioModeLab.h"
/**
* @brief 實驗選擇開關
*
* 將對應宏改為 1/0 以選擇要編譯運行的實驗。
*/
#define DEMO_LED_AUTODETECT 0 ///< LED 自動檢測演示
#define DEMO_GPIO_MODE_LAB 1 ///< GPIO 模式學習實驗
#if DEMO_LED_AUTODETECT
/***************************************
* LED 自動檢測演示 (AutoDetect)
***************************************/
/**
* @brief 候選引腳列表
* 包含所有可能連接LED的引腳,排除已知用途的引腳
*/
const PinName allPins[] = {
// Port A(去掉 PA_0: probe、PA_2/PA_3: USART2、PA_13/PA_14: SWD)
PA_1, PA_4, PA_5, PA_6, PA_7,
PA_8, PA_9, PA_10, PA_11, PA_12, PA_15,
// Port B
PB_0, PB_1, PB_2, PB_3, PB_4, PB_5, PB_6, PB_7,
PB_8, PB_9, PB_10, PB_11, PB_12, PB_13, PB_14, PB_15,
// Port C
PC_0, PC_1, PC_2, PC_3, PC_4, PC_5, PC_6, PC_7,
PC_8, PC_9, PC_10, PC_11, PC_12, PC_13, PC_14, PC_15,
// Port F
PF_0, PF_1, PF_2, PF_3, PF_4, PF_5, PF_6, PF_7,
PF_8, PF_9, PF_10, PF_11, PF_12, PF_13, PF_14, PF_15
};
/**
* @brief 排除引腳列表
* 包含串口、SWD調試接口等不能用于LED控制的引腳
*/
const PinName excludedPins[] = {
PA_2, PA_3, // USART2 (Serial2)
PA_13, PA_14, // SWD
PC_0 // 已知 LED=PC_0,避免再次測試
};
/// @brief 候選引腳總數
const int ALL_PIN_COUNT = sizeof(allPins) / sizeof(allPins[0]);
/// @brief 排除引腳總數
const int EXCLUDED_PIN_COUNT = sizeof(excludedPins) / sizeof(excludedPins[0]);
/// @brief 探頭引腳,用于檢測LED連接狀態
const PinName PROBE_PIN = PA_0;
/**
* @brief LED專用自動檢測類
* 繼承自AutoDetect框架,專門用于檢測LED引腳并演示閃爍效果
*/
class LedDetect : public AutoDetect {
public:
LedDetect(const PinName* pins,
int pinCount,
const PinName* excluded,
int excludedCount,
PinName probePin,
Stream& log)
: AutoDetect(pins, pinCount, excluded, excludedCount, probePin, log)
{}
protected:
/// @brief 打印 LED 自動檢測的頭信息。
void logHeader() override {
log_.println("LED pin auto-detect (AutoDetect framework)");
log_.println("Please connect PA0 to LED low-side pad.");
}
/// @brief 找到 LED 引腳后,在已找到狀態下循環閃燈(低電平亮)。
void loopOnFound(PinName pin) override {
digitalWrite((int)pin, LOW); // 亮
delay(300);
digitalWrite((int)pin, HIGH); // 滅
delay(300);
}
/// @brief 找到 LED 引腳瞬間的動作:釋放其它引腳,只保留 LED 為輸出。
void onFound(PinName pin) override {
// 1) 把所有候選腳恢復為輸入,避免繼續強推導致發熱
for (int i = 0; i < pinCount_; ++i) {
if (isExcluded(pins_[i])) continue;
pinMode((int)pins_[i], INPUT);
}
// 2) 只保留 LED 引腳為輸出,高電平默認滅(低電平亮)
pinMode((int)pin, OUTPUT);
digitalWrite((int)pin, HIGH);
foundPin_ = pin;
}
};
/// @brief 全局檢測器指針,指向具體的檢測器實例
AutoDetect* g_detector = nullptr;
/**
* @brief setup:初始化串口通信和LED檢測器
*/
void setup() {
Serial2.begin(115200);
delay(100);
static LedDetect ledDetector(allPins,
ALL_PIN_COUNT,
excludedPins,
EXCLUDED_PIN_COUNT,
PROBE_PIN,
Serial2);
g_detector = &ledDetector;
g_detector- >begin();
}
/**
* @brief loop:執行LED引腳檢測和閃爍演示
*/
void loop() {
if (g_detector) {
g_detector- >update();
}
}
#elif DEMO_GPIO_MODE_LAB
/***************************************
* GPIO 模式學習實驗 (GpioModeLab)
***************************************/
#include "MiniShell.h"
const PinName TARGET_PIN = PB_1;
const PinName PROBE_ADC_PIN = PA_0;
GpioModeLab gpioLab(TARGET_PIN, PROBE_ADC_PIN, Serial2);
MiniShell shell(Serial2);
static bool g_running = false; // 當前是否在跑一輪實驗
static bool g_quit = false; // 全局退出標志
/**
* @brief 做一次“配置 + gpioLab.begin()”。
* @return true 正常開始一輪實驗
* false 用戶在任意輸入階段按 q/Q,要求退出所有實驗
*/
static bool configureExperimentOnce() {
Serial2.println("========================================");
Serial2.println(" GPIO Mode Lab - external resistor configuration");
Serial2.println(" (press 'q' at any time to quit)");
Serial2.println("----------------------------------------");
Serial2.println("Connect TARGET_PIN < - > PROBE_ADC_PIN with a wire.");
Serial2.println();
Serial2.println("Step 1: input external resistor value (Ohm).");
Serial2.println(" Enter 0 if no external resistor.");
Serial2.print ("R_ext (Ohm) = ");
long r = 0;
if (shell.readUInt(r) == MiniShell::QUIT) {
Serial2.println("Quit requested.");
return false;
}
Serial2.println(); // 換行
GpioModeLab::ExternalNodeType type = GpioModeLab::EXT_NONE;
if (r <= 0) {
Serial2.println("No external resistor will be used (floating node).");
} else {
Serial2.println();
Serial2.println("Step 2: choose how this resistor is connected:");
Serial2.println(" [1] node - > R - > GND");
Serial2.println(" [2] node - > R - > VCC");
Serial2.println(" [3] ignore resistor (treat as none)");
Serial2.print ("Select 1/2/3 (or 'q' to quit): ");
char sel = 0;
if (shell.readMenuKey("123", sel) == MiniShell::QUIT) {
Serial2.println("Quit requested.");
return false;
}
if (sel == '1') type = GpioModeLab::EXT_TO_GND;
else if (sel == '2') type = GpioModeLab::EXT_TO_VCC;
else type = GpioModeLab::EXT_NONE;
}
gpioLab.setExternal((float)r, type);
gpioLab.begin();
return true;
}
/**
* @brief setup:打印總說明。
*/
void setup() {
Serial2.begin(115200);
delay(100);
Serial2.println("GPIO Mode Lab - multi-run demo");
Serial2.println("Use this firmware to learn GPIO modes.");
Serial2.println("At ANY time, press 'q' or 'Q' to quit all experiments.");
Serial2.println();
}
/**
* @brief loop:
* - 沒有在跑實驗時:彈出菜單配置一輪;可多輪;
* - 正在跑實驗時:調用 gpioLab.update() 推進;
* - 任意階段輸入 q/Q:在 MiniShell 里統一處理,結束循環。
*/
void loop() {
if (g_quit) {
return;
}
if (!g_running) {
bool ok = configureExperimentOnce();
if (!ok) {
g_quit = true;
Serial2.println("nExperiment loop stopped by user.");
return;
}
g_running = true;
return;
}
gpioLab.update();
if (gpioLab.isFinished()) {
g_running = false;
Serial2.println("n=== One experiment round finished. ===");
Serial2.println("You can start a new configuration, or press 'q' at any prompt to quit.");
Serial2.println();
delay(500);
}
}
#else
#warning "No demo enabled. Set DEMO_LED_AUTODETECT or DEMO_GPIO_MODE_LAB to 1."
void setup() {}
void loop() {}
#endif
#include "GpioModeLab.h"
/// ADC 滿量程值(10 位 ADC:0..1023)
static const int ADC_MAX_COUNTS = 1023;
/// 參考電壓(按 3.3V 算)
static const float ADC_VREF = 3.3f;
/**
* @brief 本實驗中要依次測試的 GPIO 模式列表。
*
* - name : 模式名稱(日志打印用)
* - mode : 傳給 pinMode 的模式值
* - isOutput : 是否為輸出模式
* - driveLevel : 輸出模式下的電平(0=LOW,1=HIGH,-1=不驅動)
*/
static const GpioModeLab::ModeTest kModeTests[] = {
{ "INPUT (floating)", INPUT, false, -1 },
{ "INPUT_PULLUP", INPUT_PULLUP, false, -1 },
{ "INPUT_PULLDOWN", INPUT_PULLDOWN, false, -1 },
{ "INPUT_ANALOG", INPUT_ANALOG, false, -1 },
{ "OUTPUT LOW", OUTPUT, true, 0 },
{ "OUTPUT HIGH", OUTPUT, true, 1 },
{ "OUTPUT_OPEN_DRAIN LOW", OUTPUT_OPEN_DRAIN, true, 0 },
{ "OUTPUT_OPEN_DRAIN HIGH", OUTPUT_OPEN_DRAIN, true, 1 },
};
GpioModeLab::GpioModeLab(PinName targetPin,
PinName probeAdcPin,
Stream& log)
: targetPin_(targetPin),
probeAdcPin_(probeAdcPin),
log_(log),
tests_(kModeTests),
testCount_(sizeof(kModeTests) / sizeof(kModeTests[0])),
currentIndex_(0),
finished_(false),
extResOhms_(0.0f),
extType_(EXT_NONE)
{}
void GpioModeLab::setExternal(float resistorOhms, ExternalNodeType type) {
if (resistorOhms <= 0.0f || type == EXT_NONE) {
extResOhms_ = 0.0f;
extType_ = EXT_NONE;
} else {
extResOhms_ = resistorOhms;
extType_ = type;
}
}
void GpioModeLab::begin() {
finished_ = false;
currentIndex_ = 0;
log_.println("========================================");
log_.println(" GPIO Mode Lab - learn GPIO modes");
log_.println(" - targetPin : PB1 (default in sketch)");
log_.println(" - probeAdcPin: PA0 (ADC)");
log_.println("Please connect TARGET_PIN < - > PROBE_ADC_PIN with a wire.");
log_.print("External wiring: ");
if (extType_ == EXT_NONE || extResOhms_ <= 0.0f) {
log_.println("no external resistor (node floating).");
} else if (extType_ == EXT_TO_GND) {
log_.print("node - > ");
log_.print(extResOhms_, 0);
log_.println(" Ohm - > GND.");
} else if (extType_ == EXT_TO_VCC) {
log_.print("node - > ");
log_.print(extResOhms_, 0);
log_.println(" Ohm - > VCC.");
}
log_.println("========================================");
}
/**
* @brief 對探頭 ADC 引腳進行多次采樣并取平均值。
*/
int GpioModeLab::readAdcAverage(uint8_t samples) {
long sum = 0;
pinMode((int)probeAdcPin_, INPUT_ANALOG);
delay(2);
for (uint8_t i = 0; i < samples; ++i) {
sum += analogRead((int)probeAdcPin_);
delay(2);
}
return (int)(sum / samples);
}
/**
* @brief 根據 ADC 原始值粗略判斷電平狀態。
*/
int GpioModeLab::classifyLevel(int rawAdc) const {
const int lowTh = ADC_MAX_COUNTS / 4; // ≈ 256
const int highTh = (ADC_MAX_COUNTS * 3) / 4; // ≈ 768
if (rawAdc < lowTh) return 0; // LOW
if (rawAdc > highTh) return 1; // HIGH
return 2; // MID / unknown
}
/**
* @brief 打印單次模式測試的結果,并給出英文說明。
*/
void GpioModeLab::printResult(const ModeTest& t,
int rawAdc,
int digitalLevel,
int idx)
{
int cls = classifyLevel(rawAdc);
log_.print("n[");
log_.print(idx + 1);
log_.print("/");
log_.print(testCount_);
log_.print("] Mode = ");
log_.println(t.name);
float volts = (float)rawAdc * ADC_VREF / (float)ADC_MAX_COUNTS;
log_.print(" ADC avg = ");
log_.print(rawAdc);
log_.print(" (");
log_.print(volts, 3);
log_.print(" V) - > ");
if (cls == 0) log_.print("LOW");
else if (cls == 1) log_.print("HIGH");
else log_.print("MID/unknown");
log_.print("n digitalRead = ");
log_.print(digitalLevel);
log_.println();
// 簡單英文說明(詳細中文可以看源碼注釋)
log_.print(" info: ");
if (!t.isOutput && t.mode == INPUT && t.driveLevel < 0) {
log_.println("Digital input, floating (no pull). High impedance, level decided by external circuit.");
} else if (!t.isOutput && t.mode == INPUT_PULLUP) {
log_.println("Digital input with internal pull-up (~tens of kOhm) to VCC. Good for buttons, avoids floating.");
} else if (!t.isOutput && t.mode == INPUT_PULLDOWN) {
log_.println("Digital input with internal pull-down to GND. Default level is low.");
} else if (!t.isOutput && t.mode == INPUT_ANALOG) {
log_.println("Analog input: digital buffer off, only ADC path enabled. Used for ADC measurement.");
} else if (t.isOutput && t.mode == OUTPUT && t.driveLevel == 0) {
log_.println("Push-pull output, driving LOW. Strongly sinks current to GND.");
} else if (t.isOutput && t.mode == OUTPUT && t.driveLevel == 1) {
log_.println("Push-pull output, driving HIGH. Strongly sources current to VCC.");
} else if (t.isOutput && t.mode == OUTPUT_OPEN_DRAIN && t.driveLevel == 0) {
log_.println("Open-drain output, pulling LOW (transistor to GND on).");
} else if (t.isOutput && t.mode == OUTPUT_OPEN_DRAIN && t.driveLevel == 1) {
log_.println("Open-drain output, HIGH = high-Z. Level decided by external pull-up/down.");
} else {
log_.println("Uncategorized mode.");
}
// 只有在“節點通過已知電阻接 GND + INPUT_PULLUP”時,用分壓估算內部上拉電阻
if (!t.isOutput &&
t.mode == INPUT_PULLUP &&
extType_ == EXT_TO_GND &&
extResOhms_ > 0.0f &&
rawAdc > 0 &&
rawAdc < ADC_MAX_COUNTS) {
float ratio = (float)rawAdc / (float)ADC_MAX_COUNTS; // Vnode / Vref
// Vnode / Vref = Rdown / (Rup + Rdown) = > Rup = Rdown * (1/ratio - 1)
float rup = extResOhms_ * (1.0f / ratio - 1.0f);
log_.print(" Est. internal pull-up ~= ");
if (rup > 1000.0f) {
log_.print(rup / 1000.0f, 1);
log_.println(" kOhm");
} else {
log_.print(rup, 1);
log_.println(" Ohm");
}
}
}
/**
* @brief 執行下一步模式測試。
*/
void GpioModeLab::update() {
if (finished_) {
delay(200);
return;
}
if (currentIndex_ >= testCount_) {
finished_ = true;
log_.println("nAll GPIO mode tests finished.");
return;
}
const ModeTest& t = tests_[currentIndex_];
// 1. 配置目標引腳模式
pinMode((int)targetPin_, t.mode);
delay(5);
// 2. 若為輸出模式,設置輸出電平
if (t.isOutput && t.driveLevel >= 0) {
digitalWrite((int)targetPin_, t.driveLevel ? HIGH : LOW);
}
delay(10); // 等待電平穩定
// 3. 采樣 ADC 平均值
int rawAdc = readAdcAverage(16);
// 4. 再以數字輸入方式讀取一次
pinMode((int)probeAdcPin_, INPUT);
delay(2);
int dig = digitalRead((int)probeAdcPin_);
// 5. 打印結果
printResult(t, rawAdc, dig, currentIndex_);
currentIndex_++;
delay(300); // 模式之間稍作停頓
}
#pragma once
#include < Arduino.h >
#include < PinNames.h >
/**
* @brief GPIO 模式實驗:在一個目標引腳上依次配置不同模式,
* 并通過一個 ADC 探頭引腳采樣電壓,用于學習各模式的差異。
*/
class GpioModeLab {
public:
/**
* @brief 單個模式測試描述。
*/
struct ModeTest {
const char* name; ///< 模式名稱
uint8_t mode; ///< pinMode 使用的模式值
bool isOutput; ///< 是否為輸出模式
int driveLevel;///< 輸出電平:0=LOW,1=HIGH,-1=不驅動
};
/**
* @brief 外部接線類型
*
* - EXT_NONE : 節點無額外電阻
* - EXT_TO_GND : 節點 - > R - > GND
* - EXT_TO_VCC : 節點 - > R - > VCC
*/
enum ExternalNodeType {
EXT_NONE = 0,
EXT_TO_GND,
EXT_TO_VCC
};
/**
* @brief 構造函數
* @param targetPin 被測試的 GPIO 引腳(會被配置為各種模式)
* @param probeAdcPin 用于 ADC 采樣的探頭引腳(只讀電壓)
* @param log 日志輸出流(例如 Serial2)
*/
GpioModeLab(PinName targetPin, PinName probeAdcPin, Stream& log);
/**
* @brief 配置外部電阻及其連接方式。
*
* @param resistorOhms 電阻值(單位:歐姆)。<=0 表示無電阻。
* @param type 連接方式:EXT_NONE / EXT_TO_GND / EXT_TO_VCC
*/
void setExternal(float resistorOhms, ExternalNodeType type);
/**
* @brief 初始化實驗(在 setup() 中調用)。
*/
void begin();
/**
* @brief 運行實驗的下一步(在 loop() 中反復調用)。
*/
void update();
/**
* @brief 實驗是否已經完成所有模式測試。
*/
bool isFinished() const { return finished_; }
private:
PinName targetPin_;
PinName probeAdcPin_;
Stream& log_;
const ModeTest* tests_;
int testCount_;
int currentIndex_;
bool finished_;
// 外部電阻配置
float extResOhms_; ///< 外接電阻值(歐姆),0 表示無
ExternalNodeType extType_; ///< 外接電阻接到哪:GND / VCC / none
int readAdcAverage(uint8_t samples);
int classifyLevel(int rawAdc) const;
void printResult(const ModeTest& t, int rawAdc, int digitalLevel, int idx);
};
#include "MiniShell.h"
MiniShell::Result MiniShell::readLine(char* buf, size_t len) {
size_t idx = 0;
while (true) {
if (!io_.available()) continue;
char c = io_.read();
// 全局退出命令
if (c == 'q' || c == 'Q') {
io_.println();
return QUIT;
}
// 回車/換行:一行結束
if (c == 'r' || c == 'n') {
if (idx > 0) {
buf[idx] = '?';
return OK;
}
// 空行則繼續讀
continue;
}
// 退格
if (c == 'b' || c == 127) {
if (idx > 0) {
idx--;
io_.print("b b");
}
continue;
}
// 普通字符,回顯并存入緩沖
if (idx < len - 1) {
buf[idx++] = c;
io_.print(c);
}
}
}
MiniShell::Result MiniShell::readUInt(long& value) {
char line[16];
Result r = readLine(line, sizeof(line));
if (r != OK) return r;
value = atol(line);
return OK;
}
MiniShell::Result MiniShell::readMenuKey(const char* valid, char& outKey) {
while (true) {
if (!io_.available()) continue;
char c = io_.read();
if (c == 'q' || c == 'Q') {
io_.println();
return QUIT;
}
// 檢查是否在候選集合中
for (const char* p = valid; *p; ++p) {
if (c == *p) {
io_.println(c);
outKey = c;
return OK;
}
}
}
}
#pragma once
#include < Arduino.h >
/**
* @brief 串口迷你命令行:統一處理行輸入、菜單輸入和 q/Q 退出。
*/
class MiniShell {
public:
/// 輸入結果
enum Result {
OK = 0, ///< 正常返回
QUIT ///< 用戶輸入 q/Q 請求退出
};
explicit MiniShell(Stream& io) : io_(io) {}
/**
* @brief 讀取一整行文本(回車結束),支持退格、回顯。
* 若過程中收到 q/Q,則返回 QUIT。
*
* @param buf 緩沖區
* @param len 緩沖區長度
*/
Result readLine(char* buf, size_t len);
/**
* @brief 讀取一個正整數(十進制),回車結束。
* 若過程中收到 q/Q,則返回 QUIT。
*
* @param value 輸出的整數
*/
Result readUInt(long& value);
/**
* @brief 從給定候選集合中讀取一個按鍵(例如 "123"),
* 若收到 q/Q,則返回 QUIT。
*
* @param valid C 字符串,如 "123"
* @param outKey 輸出選中的字符
*/
Result readMenuKey(const char* valid, char& outKey);
private:
Stream& io_;
};
#pragma once
#include < Arduino.h >
#include < PinNames.h >
/**
* @brief 通用自動檢測框架
*
* 該類提供了一個通用的引腳自動檢測框架,通過掃描候選引腳列表,
* 使用探頭引腳檢測特定條件,自動識別匹配的引腳。
* 支持排除特定引腳、自定義匹配規則和日志輸出。
*/
class AutoDetect {
public:
/**
* @brief 構造函數
*
* @param pins 候選引腳列表
* @param pinCount 候選引腳數量
* @param excluded 排除引腳列表
* @param excludedCount 排除引腳數量
* @param probePin 探頭引腳
* @param log 日志輸出流
*/
AutoDetect(const PinName* pins,
int pinCount,
const PinName* excluded,
int excludedCount,
PinName probePin,
Stream& log);
/// @brief 析構函數
virtual ~AutoDetect() {}
/// @brief 初始化檢測過程,在setup()中調用
void begin();
/// @brief 執行檢測循環,在loop()中反復調用
void update();
/// @brief 檢查檢測是否完成
/// @return true如果掃描完成,false如果仍在進行
bool isFinished() const { return finished_; }
/// @brief 檢查是否找到匹配引腳
/// @return true如果找到匹配引腳,false否則
bool isFound() const { return found_; }
/// @brief 獲取找到的匹配引腳
/// @return 匹配的引腳名,如果未找到返回NC
PinName getFoundPin() const { return foundPin_; }
protected:
// —— 可在子類中按需覆寫的鉤子 —— //
/// @brief 開始檢測時打印頭信息
virtual void logHeader();
/// @brief 測試引腳前打印信息
virtual void logTestingPin(PinName pin);
/// @brief 打印探頭讀取的電平值
virtual void logProbeValues(int vLow, int vHigh);
/// @brief 打印檢測進度條
virtual void logProgress(int index, int count);
/// @brief 找到匹配引腳時打印信息
virtual void logFound(PinName pin);
/// @brief 掃描完成但未找到匹配時打印信息
virtual void logFinishedNoFound();
/// @brief 判斷是否為匹配引腳,默認規則:探頭電平變化
/// @return true如果匹配,false否則
virtual bool isMatch(int vLow, int vHigh, PinName pin);
/**
* @brief 找到匹配引腳時調用的鉤子函數,由子類實現具體行為。
*
* @param pin 被判定為匹配的引腳。
*/
virtual void onFound(PinName pin) = 0;
/// @brief 已找到匹配引腳后的循環行為,默認空實現
virtual void loopOnFound(PinName pin);
// 工具函數
/// @brief 檢查引腳是否在排除列表中
/// @return true如果被排除,false否則
bool isExcluded(PinName p) const;
/// @brief 獲取引腳的端口字符(A, B, C等)
/// @return 端口字符
char portChar(PinName p) const;
/// @brief 獲取引腳的編號(0-15)
/// @return 引腳編號
int pinNumber(PinName p) const;
protected:
const PinName* pins_;
int pinCount_;
const PinName* excluded_;
int excludedCount_;
PinName probePin_;
Stream& log_;
int index_;
bool found_;
bool finished_;
PinName foundPin_;
static const int PROGRESS_BAR_LEN = 20;
};
#include "AutoDetect.h"
AutoDetect::AutoDetect(const PinName* pins,
int pinCount,
const PinName* excluded,
int excludedCount,
PinName probePin,
Stream& log)
: pins_(pins),
pinCount_(pinCount),
excluded_(excluded),
excludedCount_(excludedCount),
probePin_(probePin),
log_(log),
index_(0),
found_(false),
finished_(false),
foundPin_(NC)
{}
/// @brief 檢查引腳是否在排除列表中,包括探頭引腳
bool AutoDetect::isExcluded(PinName p) const {
if (p == probePin_) return true;
for (int i = 0; i < excludedCount_; i++) {
if (excluded_[i] == p) return true;
}
return false;
}
/// @brief 獲取引腳的端口字符,從PinName的高4位提取
char AutoDetect::portChar(PinName p) const {
return 'A' + (p > > 4); // 高 4 位:port 號
}
/// @brief 獲取引腳的編號,從PinName的低4位提取
int AutoDetect::pinNumber(PinName p) const {
return p & 0xF; // 低 4 位:pin 號
}
// —— 默認日志/行為實現,可在子類中覆寫 —— //
/// @brief 默認實現:打印檢測開始信息
void AutoDetect::logHeader() {
log_.println("AutoDetect start");
}
/// @brief 默認實現:打印正在測試的引腳信息
void AutoDetect::logTestingPin(PinName pin) {
log_.print("nTesting pin: ");
log_.print(portChar(pin));
log_.print(pinNumber(pin));
}
/// @brief 默認實現:打印探頭讀取的電平值
void AutoDetect::logProbeValues(int vLow, int vHigh) {
log_.print(" probe LOW/HIGH = ");
log_.print(vLow);
log_.print(" / ");
log_.println(vHigh);
}
/// @brief 默認實現:打印檢測進度條
void AutoDetect::logProgress(int index, int count) {
if (count <= 0) return;
float ratio = (float)index / (float)count;
int filled = (int)(ratio * PROGRESS_BAR_LEN);
log_.print("r[");
for (int i = 0; i < PROGRESS_BAR_LEN; i++) {
if (i < filled) log_.print("#");
else log_.print(".");
}
log_.print("] ");
log_.print(index);
log_.print("/");
log_.print(count);
log_.print(" ");
}
/// @brief 默認實現:打印找到的匹配引腳信息
void AutoDetect::logFound(PinName pin) {
log_.print("n >> > Pin FOUND: ");
log_.print(portChar(pin));
log_.println(pinNumber(pin));
}
/// @brief 默認實現:打印掃描完成但未找到匹配的信息
void AutoDetect::logFinishedNoFound() {
log_.println("nOne full round finished, no match found.");
}
/// @brief 默認匹配規則:探頭電平發生變化即認為匹配
bool AutoDetect::isMatch(int vLow, int vHigh, PinName /*pin*/) {
// 默認規則:探頭在此引腳高低切換時也發生變化
return (vLow != vHigh);
}
/// @brief 默認實現:找到匹配后無特殊行為,由子類覆寫
void AutoDetect::loopOnFound(PinName /*pin*/) {
// 默認什么也不做,由子類決定
}
// —— 生命周期 —— //
/// @brief 初始化檢測過程,設置引腳模式和初始狀態
void AutoDetect::begin() {
// 注意:日志串口應在外部先 begin(),這里只做邏輯打印
index_ = 0;
found_ = false;
finished_ = false;
foundPin_ = NC;
logHeader();
// 探頭腳輸入,下拉
pinMode((int)probePin_, INPUT_PULLDOWN);
// 所有候選引腳設置為輸出高電平(不包括排除項)
for (int i = 0; i < pinCount_; i++) {
PinName p = pins_[i];
if (isExcluded(p)) continue;
pinMode((int)p, OUTPUT);
digitalWrite((int)p, HIGH); // 假設"低電平有效",默認關閉
}
}
/// @brief 執行檢測循環的主要邏輯
void AutoDetect::update() {
if (found_) {
// 已找到:由子類決定在“已找到狀態”下如何循環
loopOnFound(foundPin_);
return;
}
if (finished_) {
// 已掃描完但沒找到:空轉即可
delay(100);
return;
}
// 跳過所有被排除的引腳
while (index_ < pinCount_ && isExcluded(pins_[index_])) {
index_++;
}
if (index_ >= pinCount_) {
finished_ = true;
logFinishedNoFound();
return;
}
PinName testPin = pins_[index_];
// 日志:正在測試哪個腳
logTestingPin(testPin);
// 拉低
digitalWrite((int)testPin, LOW);
delay(20);
int vLow = digitalRead((int)probePin_);
// 拉高
digitalWrite((int)testPin, HIGH);
delay(20);
int vHigh = digitalRead((int)probePin_);
// 日志:探頭電平
logProbeValues(vLow, vHigh);
// 日志:進度條
logProgress(index_ + 1, pinCount_);
// 判斷是否匹配
if (isMatch(vLow, vHigh, testPin)) {
found_ = true;
foundPin_ = testPin;
logFound(foundPin_);
onFound(foundPin_);
return;
}
index_++;
}
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。
舉報投訴
-
單片機
+關注
關注
6076文章
45494瀏覽量
670236 -
mcu
+關注
關注
147文章
18923瀏覽量
397979 -
引腳
+關注
關注
16文章
2111瀏覽量
55680 -
GPIO
+關注
關注
16文章
1328瀏覽量
56215
發布評論請先 登錄
相關推薦
熱點推薦
詳解MCU的運行過程
課程簡介:本課程基于STM32F103RC講解,通過從MCU上電開始啟動開始分析,詳解MCU的運行過程,講師“東方青”多年從事開發經驗而言,學習Cortex-M系列的
發表于 11-03 07:58
MCU芯片中GPIO口的驅動方式可分為哪幾類
嵌入式學習之GPIO接口詳解單個引腳的操作無外乎3種:輸出高低電平、檢測引腳狀態、中斷。對某個引腳的操作一般通過讀、寫特定寄存器 配置寄存器來完成。MCU中
發表于 11-03 06:35
MCU+CPLD/FPGA實現GPIO擴展與控制的資料大合集
:2019-04-26;=====================分割線========================立題詳解:本次介紹“MCU+CPLD/FPGA實現GPIO擴展與控制”,使用此種組合具有一定的優...
發表于 11-04 07:42
介紹ATMEL MCU的GPIO配置
Getting Started with Atmel SMART SAM D MCU Configuring the GPIO
MCU學習筆記_GPIO工作原理
MCU學習筆記STM32時鐘1. STM32 GPIO基礎知識2. STM32 GPIO工作模式3. STM32 GPIO寄存器1. STM
發表于 10-25 11:21
?17次下載
MCU中GPIO口的驅動方式
嵌入式學習之GPIO接口詳解http://www.51hei.com/bbs/dpj-115534-1.html單個引腳的操作無外乎3種:輸出高低電平、檢測引腳狀態、中斷。對某個引腳的操作一般通過讀
發表于 10-28 18:20
?8次下載
ST MCU_GPIO的八種工作模式詳解。
補充:N、P型的區別,就是一個為正電壓啟動(NMOS),一個為負電壓啟動(PMOS)GPIO的八種工作模式詳解浮空輸入_IN_FLOATING帶上拉輸入_IPU帶下拉輸入_IPD模擬輸入_AIN開漏
發表于 10-28 20:51
?13次下載
c語言實現串口通信_MCU+CPLD/FPGA實現對GPIO擴展與控制
:2019-04-26;=====================分割線========================立題詳解:本次介紹“MCU+CPLD/FPGA實現GPIO擴展與控制”,使用此種組合具有一定的優...
發表于 10-29 10:21
?2次下載
STM32學習-GPIO詳解
一、GPIO介紹GPIO:就是一個引腳作為輸入或者輸出。GPIO的八種工作模式:輸入輸出是相對于CPU,四種輸入、四種輸出模式及四種輸出最大速度輸入:外部數據輸入到開發板輸出:開發板的數據輸出
發表于 11-29 16:51
?20次下載
STM學習- GPIO工作原理
STM學習- GPIO工作原理Sat 0203:0006:0009:0012:0003:0006:0009:00Jan 03已完成 時間安排主要內容: GPIO工作方式
發表于 12-28 19:32
?6次下載
STM32學習筆記---GPIO
STM32的學習筆記—GPIO我使用的是STM32F401ZGT6,有7組IO口,每組16個引腳,共112個引腳。因為太菜了,確實容易出錯,還請賜教參考官方文檔:八種IO口模式區別結構原理該單片機在
發表于 01-13 16:31
?6次下載
【MCU學習】GPIO詳解


評論