chinese直男口爆体育生外卖, 99久久er热在这里只有精品99, 又色又爽又黄18禁美女裸身无遮挡, gogogo高清免费观看日本电视,私密按摩师高清版在线,人妻视频毛茸茸,91论坛 兴趣闲谈,欧美 亚洲 精品 8区,国产精品久久久久精品免费

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

【MCU學(xué)習(xí)】GPIO詳解

Jankin沐沐 ? 來(lái)源:Jankin沐沐 ? 作者:Jankin沐沐 ? 2026-01-24 11:45 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

@[toc]

概要

在這里插入圖片描述
在這里插入圖片描述
摘自:STM32F10x 參考手冊(cè)

在這里插入圖片描述

本實(shí)驗(yàn)通過(guò)一個(gè)“小實(shí)驗(yàn)框架 GPIO Mode Lab”,在同一個(gè) GPIO 引腳上依次配置不同模式,并用 ADC 探頭測(cè)量電壓、同時(shí)讀取數(shù)字電平,系統(tǒng)化地觀察:

  • 內(nèi)部上拉 / 下拉電阻的大致阻值和效果
  • 外部電阻(不同阻值、接 VCC / 接 GND / 懸空)與內(nèi)部電阻的“誰(shuí)主導(dǎo)”關(guān)系
  • 推挽輸出(OUTPUT)和開(kāi)漏輸出(OPEN-DRAIN)的真實(shí)行為
  • 浮空輸入在模擬量和數(shù)字量上的不穩(wěn)定性

并由此得出一系列可量化的直觀結(jié)論,可寫(xiě)成一篇“GPIO 行為直觀實(shí)驗(yàn)報(bào)告”。


整體架構(gòu)流程

整體分為三層:

  1. 實(shí)驗(yàn)框架層:GPIO Mode Lab 類(lèi)
    • 固定一組 GPIO 模式序列:
      • INPUT、INPUT_PULLUPINPUT_PULLDOWN、INPUT_ANALOG
      • OUTPUT LOW / OUTPUT HIGH
      • OUTPUT_OPEN_DRAIN LOW / OUTPUT_OPEN_DRAIN HIGH
    • 每輪實(shí)驗(yàn):依次切換模式 → 延時(shí)穩(wěn)定 → ADC 采樣多次求平均 → 讀取數(shù)字電平 → 串口打印結(jié)果和說(shuō)明。
  2. 串口交互層:MiniShell + 菜單
    • 用戶(hù)通過(guò)串口輸入:
      • 外部電阻值 R_ext(單位 Ω,支持 0 表示無(wú))
      • 電阻連接方式:node → R → GND / VCC / none
    • 支持多輪實(shí)驗(yàn)連續(xù)運(yùn)行,任意輸入階段按 q/Q 退出。
  3. 測(cè)量與分析層:ADC + 簡(jiǎn)單電路模型
    • ADC:10 位,0–1023,對(duì)應(yīng)約 0–3.3 V。
    • 對(duì)特定場(chǎng)景(如 INPUT_PULLUP + R_ext→GND)使用分壓公式:
      • Vnode / Vref = R_down / (R_up + R_down)
      • 估算內(nèi)部上拉電阻 R_up = R_down * (1 / ratio - 1),其中 ratio = ADC / 1023。
    • 把不同 R_ext / 接法 / 模式的結(jié)果整理成表格,對(duì)比得出工程結(jié)論。

技術(shù)名詞解釋

  • INPUT(浮空輸入)
    僅打開(kāi)數(shù)字輸入緩沖,不啟用內(nèi)部上拉/下拉。引腳呈高阻態(tài),電平完全由外部電路和泄漏、噪聲等決定。
  • INPUT_PULLUP / INPUT_PULLDOWN
    在 INPUT 的基礎(chǔ)上,內(nèi)部通過(guò)一只幾十 kΩ 量級(jí)的“弱上拉 / 弱下拉”電阻,把引腳輕微拉向 VCC 或 GND,常用于按鍵等簡(jiǎn)單輸入,避免懸空。
  • INPUT_ANALOG
    關(guān)閉數(shù)字輸入緩沖和施密特觸發(fā)器,僅保留到 ADC 的模擬路徑,減小噪聲和漏電,專(zhuān)用于電壓采樣。
  • OUTPUT(推挽輸出)
    上下兩個(gè) MOS 管組成推挽結(jié)構(gòu),可主動(dòng)拉高到 VCC 或拉低到 GND,等效輸出電阻較小,能驅(qū)動(dòng)一定電流。
  • OUTPUT_OPEN_DRAIN(開(kāi)漏輸出)
    僅有下拉管能導(dǎo)通到 GND,上拉管常關(guān);輸出 LOW 時(shí)主動(dòng)拉低,輸出 HIGH 時(shí)為高阻態(tài),需要外部上拉決定高電平,適合 I2C、線與等總線。
  • 浮空(Floating)
    引腳未通過(guò)明顯的上拉/下拉或驅(qū)動(dòng)源確定電平,表現(xiàn)為 ADC 讀數(shù)在中間隨機(jī)漂移,digitalRead 可能隨機(jī)判 0/1。
  • 內(nèi)部上拉/下拉電阻
    MCU 內(nèi)部集成的可選電阻網(wǎng)絡(luò),通常在幾十 kΩ 量級(jí),用于給輸入引腳提供弱上拉/下拉,避免懸空。

技術(shù)細(xì)節(jié)

1. 測(cè)量配置與流程

  • ADC 分辨率:10 位,0..1023,Vref ≈ 3.3 V
  • 每個(gè)模式下:
    1. pinMode(target, mode) 配置模式
    2. 若為輸出模式,則 digitalWrite 設(shè)為 HIGH/LOW
    3. 延時(shí)若干 ms 等待電平穩(wěn)定
    4. analogRead(probe) 多次采樣取平均 → 得到 ADC avg 和電壓
    5. 把探針腳臨時(shí)設(shè)為 INPUTdigitalRead 一次 → 觀察數(shù)字門(mén)限行為
    6. 串口打印模式名、ADC、電壓、數(shù)字結(jié)果和文字說(shuō)明

2. 外部電阻與接法的實(shí)驗(yàn)組合

通過(guò)串口交互設(shè)置:

  • R_ext(示例:100 kΩ、15 kΩ;輸入整數(shù):100000、15000)
  • 接法:node -> R -> GND / VCC / none

本次記錄的數(shù)據(jù)覆蓋六種典型組合:

  1. R_ext = 100 kΩ,接 VCC
  2. R_ext = 100 kΩ,接 GND
  3. R_ext = 100 kΩ,邏輯上當(dāng)作 none(菜單選 3)
  4. R_ext = 15 kΩ,接 VCC
  5. R_ext = 15 kΩ,接 GND
  6. R_ext = 15 kΩ,邏輯上當(dāng)作 none(菜單選 3)

3. 實(shí)驗(yàn)數(shù)據(jù)表格

3.1 R_ext = 100 kΩ,接 VCC

序號(hào)模式ADC電壓 (V)digitalRead備注
1INPUT (floating)7152.3061外部 100k 上拉占主導(dǎo),輸入腳高阻跟著被拉高
2INPUT_PULLUP9172.9581內(nèi)部上拉與外部 100k 上拉并聯(lián),更接近 VCC
3INPUT_PULLDOWN2680.8650內(nèi)部下拉 vs 外部上拉分壓,電平在中間偏低
4INPUT_ANALOG7132.3001數(shù)字緩沖關(guān)掉,模擬只看到外部 100k 上拉
5OUTPUT LOW00.0000推挽強(qiáng)拉低,壓制 100k 上拉
6OUTPUT HIGH10223.2971推挽強(qiáng)拉高,與上拉同向
7OUTPUT_OPEN_DRAIN LOW00.0000開(kāi)漏下管導(dǎo)通,壓制外部上拉
8OUTPUT_OPEN_DRAIN HIGH7152.3061開(kāi)漏高阻,完全由 100k 上拉決定

3.2 R_ext = 100 kΩ,接 GND

序號(hào)模式ADC電壓 (V)digitalRead備注
1INPUT (floating)00.0000外部 100k 下拉占主導(dǎo)
2INPUT_PULLUP6532.1061內(nèi)部上拉 vs 100k 下拉分壓,估算 Rup≈56.7 kΩ
3INPUT_PULLDOWN00.0000內(nèi)部+外部下拉疊加,牢牢在 0V
4INPUT_ANALOG00.0000模擬輸入也只看到外部下拉
5OUTPUT LOW00.0000推挽強(qiáng)拉低
6OUTPUT HIGH10223.2971推挽強(qiáng)拉高,壓制 100k 下拉
7OUTPUT_OPEN_DRAIN LOW00.0000開(kāi)漏拉低
8OUTPUT_OPEN_DRAIN HIGH00.0000開(kāi)漏高阻 + 僅有 100k 下拉 → 節(jié)點(diǎn)仍為低

3.3 R_ext = 100 kΩ,邏輯當(dāng)作 none(菜單選 3)

注:代碼中 extType=EXT_NONE,因此提示為“no external resistor (node floating)”,物理上是否仍接 100k 視實(shí)驗(yàn)連線而定。這里按“邏輯視為懸空”來(lái)理解。

序號(hào)模式ADC電壓 (V)digitalRead備注
1INPUT (floating)1280.4131浮空,受雜散電容/泄漏影響,偏低但數(shù)字偶判 1
2INPUT_PULLUP8612.7771僅內(nèi)部上拉,電平接近 VCC
3INPUT_PULLDOWN00.0000僅內(nèi)部下拉
4INPUT_ANALOG2370.7650模擬浮空,電壓在低中區(qū)間漂移
5OUTPUT LOW00.0000推挽拉低
6OUTPUT HIGH10223.2971推挽拉高
7OUTPUT_OPEN_DRAIN LOW00.0000開(kāi)漏拉低
8OUTPUT_OPEN_DRAIN HIGH2380.7680開(kāi)漏高阻 + 無(wú)上拉,下垂到中間偏低

3.4 R_ext = 15 kΩ,接 GND

序號(hào)模式ADC電壓 (V)digitalRead備注
1INPUT (floating)00.0000外部 15k 下拉很強(qiáng),直接拉到 0V
2INPUT_PULLUP2760.890015k 下拉明顯比內(nèi)部上拉強(qiáng),節(jié)點(diǎn)偏低;估算 Rup≈40.6 kΩ
3INPUT_PULLDOWN00.0000內(nèi)部+外部下拉,更低
4INPUT_ANALOG00.0000模擬通道也看到 0V
5OUTPUT LOW00.0000推挽拉低
6OUTPUT HIGH10213.2941推挽拉高,壓制 15k 下拉
7OUTPUT_OPEN_DRAIN LOW00.0000開(kāi)漏拉低
8OUTPUT_OPEN_DRAIN HIGH00.0000開(kāi)漏高阻 + 15k 下拉 → 節(jié)點(diǎn)為低

3.5 R_ext = 15 kΩ,接 VCC

序號(hào)模式ADC電壓 (V)digitalRead備注
1INPUT (floating)9953.2101外部 15k 上拉很強(qiáng),幾乎 3.3V
2INPUT_PULLUP10133.2681內(nèi)部+外部上拉并聯(lián),更接近滿(mǎn)刻度
3INPUT_PULLDOWN7382.3811內(nèi)部下拉 vs 15k 上拉分壓,仍被視為 HIGH
4INPUT_ANALOG9963.2131模擬通道看到 3.2V 左右
5OUTPUT LOW10.0030推挽強(qiáng)拉低,對(duì) 15k 上拉也是輕松壓制
6OUTPUT HIGH10223.2971推挽拉高,與上拉同向
7OUTPUT_OPEN_DRAIN LOW10.0030開(kāi)漏拉低
8OUTPUT_OPEN_DRAIN HIGH9953.2101開(kāi)漏高阻 + 15k 上拉 → 節(jié)點(diǎn)接近 3.3V

3.6 R_ext = 15 kΩ,邏輯當(dāng)作 none(菜單選 3)

同樣地,代碼邏輯將其視為“無(wú)外部電阻”,以下理解為“懸空”場(chǎng)景。

序號(hào)模式ADC電壓 (V)digitalRead備注
1INPUT (floating)1740.5611浮空偏低,但數(shù)字采樣到 1(說(shuō)明門(mén)限在中間)
2INPUT_PULLUP8602.7741僅內(nèi)部上拉,接近 VCC
3INPUT_PULLDOWN00.0000僅內(nèi)部下拉
4INPUT_ANALOG2320.7480浮空模擬,低中間漂移
5OUTPUT LOW00.0000推挽拉低
6OUTPUT HIGH10223.2971推挽拉高
7OUTPUT_OPEN_DRAIN LOW00.0000開(kāi)漏拉低
8OUTPUT_OPEN_DRAIN HIGH1800.5810開(kāi)漏高阻 + 無(wú)上拉,電平漂在中間偏低

同樣地,代碼邏輯將其視為“無(wú)外部電阻”,以下理解為“懸空”場(chǎng)景。

序號(hào)模式ADC電壓 (V)digitalRead備注
1INPUT (floating)1740.5611浮空偏低,但數(shù)字采樣到 1(說(shuō)明門(mén)限在中間)
2INPUT_PULLUP8602.7741僅內(nèi)部上拉,接近 VCC
3INPUT_PULLDOWN00.0000僅內(nèi)部下拉
4INPUT_ANALOG2320.7480浮空模擬,低中間漂移
5OUTPUT LOW00.0000推挽拉低
6OUTPUT HIGH10223.2971推挽拉高
7OUTPUT_OPEN_DRAIN LOW00.0000開(kāi)漏拉低
8OUTPUT_OPEN_DRAIN HIGH1800.5810開(kāi)漏高阻 + 無(wú)上拉,電平漂在中間偏低

4. GPIO 模式與外部場(chǎng)景對(duì)比總表

下面用一張總表,橫向?qū)Ρ取巴粋€(gè) GPIO 模式在不同外部電阻場(chǎng)景下”的典型行為,便于在文章中一眼看出規(guī)律。

說(shuō)明:

  • “低 / 高”指數(shù)字電平穩(wěn)定為 0 / 1;
  • “中間電壓”指 ADC 在 0.8–2.5 V 區(qū)間,屬于分壓或浮空狀態(tài);
  • “浮空/不穩(wěn)定”指 ADC 明顯在中間且 digitalRead 有抖動(dòng)可能。
模式 / 場(chǎng)景100 kΩ → GND100 kΩ → VCC浮空(ext=none)15 kΩ → GND15 kΩ → VCC
INPUT (floating)0 V,穩(wěn)定低,完全由外部下拉決定≈2.3 V,中間偏高,數(shù)字判高0.4–0.6 V 浮動(dòng),數(shù)字判值不穩(wěn)定0 V,穩(wěn)定低,下拉很強(qiáng)≈3.2 V,穩(wěn)定高,上拉很強(qiáng)
INPUT_PULLUP≈2.1 V,中間偏高,數(shù)字判高,弱上拉與 100k 下拉分壓≈3.0 V,接近 VCC,內(nèi)部+外部上拉并聯(lián)更硬≈2.8 V,穩(wěn)定高,僅內(nèi)部上拉≈0.9 V,中間偏低,數(shù)字已判低,15k 壓制內(nèi)部上拉≈3.27 V,穩(wěn)定高,內(nèi)部+15k 上拉都朝上
INPUT_PULLDOWN0 V,穩(wěn)定低,內(nèi)部+外部都往下拉≈0.87 V,中間偏低,數(shù)字判低0 V,穩(wěn)定低,僅內(nèi)部下拉0 V,穩(wěn)定低,內(nèi)部+15k 下拉都往下≈2.38 V,中間偏高,數(shù)字已判高,15k 壓制內(nèi)部下拉
INPUT_ANALOG0 V,只看到外部下拉≈2.3 V,只看到外部上拉0.7–0.8 V 左右,中間電壓,浮空漂移0 V,只看到外部 15k 下拉≈3.21 V,只看到外部 15k 上拉
OUTPUT LOW0 V,強(qiáng)拉低,壓制 100k 下拉0 V,強(qiáng)拉低,壓制 100k 上拉0 V,強(qiáng)拉低0 V,強(qiáng)拉低,壓制 15k 下拉≈0 V,強(qiáng)拉低,壓制 15k 上拉
OUTPUT HIGH≈3.30 V,強(qiáng)拉高,壓制 100k 下拉≈3.30 V,強(qiáng)拉高,與 100k 上拉同向≈3.30 V,強(qiáng)拉高≈3.29 V,強(qiáng)拉高,壓制 15k 下拉≈3.30 V,強(qiáng)拉高,與 15k 上拉同向
OPEN_DRAIN LOW0 V,下管導(dǎo)通,壓制外部0 V,下管導(dǎo)通,壓制外部0 V,下管導(dǎo)通0 V,下管導(dǎo)通≈0 V,下管導(dǎo)通
OPEN_DRAIN HIGH(高阻)0 V,高阻 + 100k 下拉 → 低≈2.3 V,高阻 + 100k 上拉 → 中間/偏高,高電平0.6–0.8 V,中間電壓,浮空漂移,數(shù)字多為低0 V,高阻 + 15k 強(qiáng)下拉 → 低≈3.2 V,高阻 + 15k 強(qiáng)上拉 → 穩(wěn)定高

小結(jié)

結(jié)合上面的完整表格,可以得出幾條在結(jié)論(每條都能用上述數(shù)據(jù)佐證):

  1. 內(nèi)部上拉電阻量級(jí)在幾十千歐,可以用“R_ext→GND + ADC 分壓”反推出大致范圍
    • R_ext=100 kΩ → GND 時(shí)推算約 56.7 kΩ,R_ext=15 kΩ → GND 時(shí)推算約 40.6 kΩ,印證了“弱上拉”的工程經(jīng)驗(yàn)。
  2. 外部電阻是“比拼阻值”的游戲:誰(shuí)阻值小,誰(shuí)主導(dǎo)節(jié)點(diǎn)電平
    • 100 kΩ 下拉 vs 內(nèi)部上拉:得到中間電壓(約 2.1 V);
    • 15 kΩ 下拉 vs 內(nèi)部上拉:節(jié)點(diǎn)被明顯拉向 0 V,digitalRead 直接讀 0。
  3. 當(dāng)外部電阻和內(nèi)部上/下拉連在同一側(cè)電源時(shí),只能增強(qiáng)高/低電平,不能再用來(lái)估 R_up
    • 比如 15 kΩ → VCC + INPUT_PULLUP,電壓接近滿(mǎn)刻度,只能說(shuō)明“上拉更硬”,不適合再反算內(nèi)部阻值。
  4. 浮空輸入真的會(huì)亂飄,模擬值在 0.x V 區(qū)間抖動(dòng),數(shù)字判 0/1 都有可能
    • R_ext=none 的多組數(shù)據(jù)表明,INPUT/INPUT_ANALOG 下 ADC 在 0.4–0.8 V 對(duì)應(yīng)的計(jì)數(shù)間漂移,digitalRead 既有 0 也有 1,說(shuō)明浮空腳高度不可靠。
  5. 推挽輸出表現(xiàn)為幾乎理想的電壓源,輕松壓住 15 kΩ、100 kΩ 這類(lèi)負(fù)載
    • 不論 100 kΩ 還是 15 kΩ 接到 VCC 或 GND,OUTPUT HIGH/LOW 電壓幾乎仍為理想的 0 / 3.3 V,佐證了推挽輸出的強(qiáng)驅(qū)動(dòng)能力。
  6. 開(kāi)漏輸出 HIGH 態(tài)的“高阻”本質(zhì):節(jié)點(diǎn)完全等于外部網(wǎng)絡(luò)的結(jié)果
    • OD HIGH + R→GND 得低電平,OD HIGH + R→VCC 得高電平,OD HIGH + none 得中間漂移電平,清楚展示了開(kāi)漏 + 上拉構(gòu)成“線與總線”的物理基礎(chǔ)。

代碼部分

/**
 * @brief 示例主程序
 *
 * 通過(guò)編譯開(kāi)關(guān)選擇運(yùn)行不同的硬件實(shí)驗(yàn):
 * - LED 自動(dòng)檢測(cè)實(shí)驗(yàn)(使用 AutoDetect 框架)
 * - GPIO 模式學(xué)習(xí)實(shí)驗(yàn)(使用 GpioModeLab)
 */

#include < PinNames.h >
#include "AutoDetect.h"
#include "GpioModeLab.h"

/**
 * @brief 實(shí)驗(yàn)選擇開(kāi)關(guān)
 *
 * 將對(duì)應(yīng)宏改為 1/0 以選擇要編譯運(yùn)行的實(shí)驗(yàn)。
 */
#define DEMO_LED_AUTODETECT   0   ///< LED 自動(dòng)檢測(cè)演示
#define DEMO_GPIO_MODE_LAB    1   ///< GPIO 模式學(xué)習(xí)實(shí)驗(yàn)

#if DEMO_LED_AUTODETECT

/***************************************
 *  LED 自動(dòng)檢測(cè)演示 (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調(diào)試接口等不能用于LED控制的引腳
 */
const PinName excludedPins[] = {
  PA_2, PA_3,    // USART2 (Serial2)
  PA_13, PA_14,  // SWD
  PC_0           // 已知 LED=PC_0,避免再次測(cè)試
};

/// @brief 候選引腳總數(shù)
const int ALL_PIN_COUNT      = sizeof(allPins) / sizeof(allPins[0]);

/// @brief 排除引腳總數(shù)
const int EXCLUDED_PIN_COUNT = sizeof(excludedPins) / sizeof(excludedPins[0]);

/// @brief 探頭引腳,用于檢測(cè)LED連接狀態(tài)
const PinName PROBE_PIN = PA_0;

/**
 * @brief LED專(zhuān)用自動(dòng)檢測(cè)類(lèi)
 * 繼承自AutoDetect框架,專(zhuān)門(mén)用于檢測(cè)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 自動(dòng)檢測(cè)的頭信息。
  void logHeader() override {
    log_.println("LED pin auto-detect (AutoDetect framework)");
    log_.println("Please connect PA0 to LED low-side pad.");
  }

  /// @brief 找到 LED 引腳后,在已找到狀態(tài)下循環(huán)閃燈(低電平亮)。
  void loopOnFound(PinName pin) override {
    digitalWrite((int)pin, LOW);   // 亮
    delay(300);
    digitalWrite((int)pin, HIGH);  // 滅
    delay(300);
  }

  /// @brief 找到 LED 引腳瞬間的動(dòng)作:釋放其它引腳,只保留 LED 為輸出。
  void onFound(PinName pin) override {
    // 1) 把所有候選腳恢復(fù)為輸入,避免繼續(xù)強(qiáng)推導(dǎo)致發(fā)熱
    for (int i = 0; i < pinCount_; ++i) {
      if (isExcluded(pins_[i])) continue;
      pinMode((int)pins_[i], INPUT);
    }
    // 2) 只保留 LED 引腳為輸出,高電平默認(rèn)滅(低電平亮)
    pinMode((int)pin, OUTPUT);
    digitalWrite((int)pin, HIGH);

    foundPin_ = pin;
  }
};

/// @brief 全局檢測(cè)器指針,指向具體的檢測(cè)器實(shí)例
AutoDetect* g_detector = nullptr;

/**
 * @brief setup:初始化串口通信和LED檢測(cè)器
 */
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:執(zhí)行LED引腳檢測(cè)和閃爍演示
 */
void loop() {
  if (g_detector) {
    g_detector- >update();
  }
}

#elif DEMO_GPIO_MODE_LAB

/***************************************
 *  GPIO 模式學(xué)習(xí)實(shí)驗(yàn) (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;  // 當(dāng)前是否在跑一輪實(shí)驗(yàn)
static bool g_quit    = false;  // 全局退出標(biāo)志

/**
 * @brief 做一次“配置 + gpioLab.begin()”。
 * @return true  正常開(kāi)始一輪實(shí)驗(yàn)
 *         false 用戶(hù)在任意輸入階段按 q/Q,要求退出所有實(shí)驗(yàn)
 */
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:打印總說(shuō)明。
 */
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:
 *  - 沒(méi)有在跑實(shí)驗(yàn)時(shí):彈出菜單配置一輪;可多輪;
 *  - 正在跑實(shí)驗(yàn)時(shí):調(diào)用 gpioLab.update() 推進(jìn);
 *  - 任意階段輸入 q/Q:在 MiniShell 里統(tǒng)一處理,結(jié)束循環(huán)。
 */
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 滿(mǎn)量程值(10 位 ADC:0..1023)
static const int   ADC_MAX_COUNTS = 1023;
/// 參考電壓(按 3.3V 算)
static const float ADC_VREF       = 3.3f;

/**
 * @brief 本實(shí)驗(yàn)中要依次測(cè)試的 GPIO 模式列表。
 *
 * - name       : 模式名稱(chēng)(日志打印用)
 * - mode       : 傳給 pinMode 的模式值
 * - isOutput   : 是否為輸出模式
 * - driveLevel : 輸出模式下的電平(0=LOW,1=HIGH,-1=不驅(qū)動(dòng))
 */
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 對(duì)探頭 ADC 引腳進(jìn)行多次采樣并取平均值。
 */
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 根據(jù) ADC 原始值粗略判斷電平狀態(tài)。
 */
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 打印單次模式測(cè)試的結(jié)果,并給出英文說(shuō)明。
 */
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();

  // 簡(jiǎn)單英文說(shuō)明(詳細(xì)中文可以看源碼注釋?zhuān)?/span>
  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.");
  }

  // 只有在“節(jié)點(diǎn)通過(guò)已知電阻接 GND + INPUT_PULLUP”時(shí),用分壓估算內(nèi)部上拉電阻
  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 執(zhí)行下一步模式測(cè)試。
 */
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. 配置目標(biāo)引腳模式
  pinMode((int)targetPin_, t.mode);
  delay(5);

  // 2. 若為輸出模式,設(shè)置輸出電平
  if (t.isOutput && t.driveLevel >= 0) {
    digitalWrite((int)targetPin_, t.driveLevel ? HIGH : LOW);
  }

  delay(10); // 等待電平穩(wěn)定

  // 3. 采樣 ADC 平均值
  int rawAdc = readAdcAverage(16);

  // 4. 再以數(shù)字輸入方式讀取一次
  pinMode((int)probeAdcPin_, INPUT);
  delay(2);
  int dig = digitalRead((int)probeAdcPin_);

  // 5. 打印結(jié)果
  printResult(t, rawAdc, dig, currentIndex_);

  currentIndex_++;
  delay(300); // 模式之間稍作停頓
}
#pragma once

#include < Arduino.h >
#include < PinNames.h >

/**
 * @brief GPIO 模式實(shí)驗(yàn):在一個(gè)目標(biāo)引腳上依次配置不同模式,
 *        并通過(guò)一個(gè) ADC 探頭引腳采樣電壓,用于學(xué)習(xí)各模式的差異。
 */
class GpioModeLab {
public:
  /**
   * @brief 單個(gè)模式測(cè)試描述。
   */
  struct ModeTest {
    const char* name;      ///< 模式名稱(chēng)
    uint8_t     mode;      ///< pinMode 使用的模式值
    bool        isOutput;  ///< 是否為輸出模式
    int         driveLevel;///< 輸出電平:0=LOW,1=HIGH,-1=不驅(qū)動(dòng)
  };

  /**
   * @brief 外部接線類(lèi)型
   *
   * - EXT_NONE   : 節(jié)點(diǎn)無(wú)額外電阻
   * - EXT_TO_GND : 節(jié)點(diǎn) - > R - > GND
   * - EXT_TO_VCC : 節(jié)點(diǎn) - > R - > VCC
   */
  enum ExternalNodeType {
    EXT_NONE = 0,
    EXT_TO_GND,
    EXT_TO_VCC
  };

  /**
   * @brief 構(gòu)造函數(shù)
   * @param targetPin   被測(cè)試的 GPIO 引腳(會(huì)被配置為各種模式)
   * @param probeAdcPin 用于 ADC 采樣的探頭引腳(只讀電壓)
   * @param log         日志輸出流(例如 Serial2)
   */
  GpioModeLab(PinName targetPin, PinName probeAdcPin, Stream& log);

  /**
   * @brief 配置外部電阻及其連接方式。
   *
   * @param resistorOhms 電阻值(單位:歐姆)。<=0 表示無(wú)電阻。
   * @param type         連接方式:EXT_NONE / EXT_TO_GND / EXT_TO_VCC
   */
  void setExternal(float resistorOhms, ExternalNodeType type);

  /**
   * @brief 初始化實(shí)驗(yàn)(在 setup() 中調(diào)用)。
   */
  void begin();

  /**
   * @brief 運(yùn)行實(shí)驗(yàn)的下一步(在 loop() 中反復(fù)調(diào)用)。
   */
  void update();

  /**
   * @brief 實(shí)驗(yàn)是否已經(jīng)完成所有模式測(cè)試。
   */
  bool isFinished() const { return finished_; }

private:
  PinName targetPin_;
  PinName probeAdcPin_;
  Stream& log_;

  const ModeTest* tests_;
  int             testCount_;
  int             currentIndex_;
  bool            finished_;

  // 外部電阻配置
  float            extResOhms_; ///< 外接電阻值(歐姆),0 表示無(wú)
  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;
    }

    // 回車(chē)/換行:一行結(jié)束
    if (c == 'r' || c == 'n') {
      if (idx > 0) {
        buf[idx] = '?';
        return OK;
      }
      // 空行則繼續(xù)讀
      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 串口迷你命令行:統(tǒng)一處理行輸入、菜單輸入和 q/Q 退出。
 */
class MiniShell {
public:
  /// 輸入結(jié)果
  enum Result {
    OK = 0,   ///< 正常返回
    QUIT      ///< 用戶(hù)輸入 q/Q 請(qǐng)求退出
  };

  explicit MiniShell(Stream& io) : io_(io) {}

  /**
   * @brief 讀取一整行文本(回車(chē)結(jié)束),支持退格、回顯。
   *        若過(guò)程中收到 q/Q,則返回 QUIT。
   *
   * @param buf 緩沖區(qū)
   * @param len 緩沖區(qū)長(zhǎng)度
   */
  Result readLine(char* buf, size_t len);

  /**
   * @brief 讀取一個(gè)正整數(shù)(十進(jìn)制),回車(chē)結(jié)束。
   *        若過(guò)程中收到 q/Q,則返回 QUIT。
   *
   * @param value 輸出的整數(shù)
   */
  Result readUInt(long& value);

  /**
   * @brief 從給定候選集合中讀取一個(gè)按鍵(例如 "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 通用自動(dòng)檢測(cè)框架
 *
 * 該類(lèi)提供了一個(gè)通用的引腳自動(dòng)檢測(cè)框架,通過(guò)掃描候選引腳列表,
 * 使用探頭引腳檢測(cè)特定條件,自動(dòng)識(shí)別匹配的引腳。
 * 支持排除特定引腳、自定義匹配規(guī)則和日志輸出。
 */
class AutoDetect {
public:
  /**
   * @brief 構(gòu)造函數(shù)
   *
   * @param pins 候選引腳列表
   * @param pinCount 候選引腳數(shù)量
   * @param excluded 排除引腳列表
   * @param excludedCount 排除引腳數(shù)量
   * @param probePin 探頭引腳
   * @param log 日志輸出流
   */
  AutoDetect(const PinName* pins,
             int pinCount,
             const PinName* excluded,
             int excludedCount,
             PinName probePin,
             Stream& log);

  /// @brief 析構(gòu)函數(shù)
  virtual ~AutoDetect() {}

  /// @brief 初始化檢測(cè)過(guò)程,在setup()中調(diào)用
  void begin();

  /// @brief 執(zhí)行檢測(cè)循環(huán),在loop()中反復(fù)調(diào)用
  void update();

  /// @brief 檢查檢測(cè)是否完成
  /// @return true如果掃描完成,false如果仍在進(jìn)行
  bool isFinished() const { return finished_; }

  /// @brief 檢查是否找到匹配引腳
  /// @return true如果找到匹配引腳,false否則
  bool isFound()    const { return found_; }

  /// @brief 獲取找到的匹配引腳
  /// @return 匹配的引腳名,如果未找到返回NC
  PinName getFoundPin() const { return foundPin_; }

protected:
  // —— 可在子類(lèi)中按需覆寫(xiě)的鉤子 —— //

  /// @brief 開(kāi)始檢測(cè)時(shí)打印頭信息
  virtual void logHeader();

  /// @brief 測(cè)試引腳前打印信息
  virtual void logTestingPin(PinName pin);

  /// @brief 打印探頭讀取的電平值
  virtual void logProbeValues(int vLow, int vHigh);

  /// @brief 打印檢測(cè)進(jìn)度條
  virtual void logProgress(int index, int count);

  /// @brief 找到匹配引腳時(shí)打印信息
  virtual void logFound(PinName pin);

  /// @brief 掃描完成但未找到匹配時(shí)打印信息
  virtual void logFinishedNoFound();

  /// @brief 判斷是否為匹配引腳,默認(rèn)規(guī)則:探頭電平變化
  /// @return true如果匹配,false否則
  virtual bool isMatch(int vLow, int vHigh, PinName pin);

  /**
   * @brief 找到匹配引腳時(shí)調(diào)用的鉤子函數(shù),由子類(lèi)實(shí)現(xiàn)具體行為。
   *
   * @param pin 被判定為匹配的引腳。
   */
  virtual void onFound(PinName pin) = 0;

  /// @brief 已找到匹配引腳后的循環(huán)行為,默認(rèn)空實(shí)現(xiàn)
  virtual void loopOnFound(PinName pin);

  // 工具函數(shù)
  /// @brief 檢查引腳是否在排除列表中
  /// @return true如果被排除,false否則
  bool isExcluded(PinName p) const;

  /// @brief 獲取引腳的端口字符(A, B, C等)
  /// @return 端口字符
  char portChar(PinName p) const;

  /// @brief 獲取引腳的編號(hào)(0-15)
  /// @return 引腳編號(hào)
  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 號(hào)
}

/// @brief 獲取引腳的編號(hào),從PinName的低4位提取
int AutoDetect::pinNumber(PinName p) const {
  return p & 0xF;         // 低 4 位:pin 號(hào)
}

// —— 默認(rèn)日志/行為實(shí)現(xiàn),可在子類(lèi)中覆寫(xiě) —— //

/// @brief 默認(rèn)實(shí)現(xiàn):打印檢測(cè)開(kāi)始信息
void AutoDetect::logHeader() {
  log_.println("AutoDetect start");
}

/// @brief 默認(rèn)實(shí)現(xiàn):打印正在測(cè)試的引腳信息
void AutoDetect::logTestingPin(PinName pin) {
  log_.print("nTesting pin: ");
  log_.print(portChar(pin));
  log_.print(pinNumber(pin));
}

/// @brief 默認(rèn)實(shí)現(xiàn):打印探頭讀取的電平值
void AutoDetect::logProbeValues(int vLow, int vHigh) {
  log_.print("  probe LOW/HIGH = ");
  log_.print(vLow);
  log_.print(" / ");
  log_.println(vHigh);
}

/// @brief 默認(rèn)實(shí)現(xiàn):打印檢測(cè)進(jìn)度條
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 默認(rèn)實(shí)現(xiàn):打印找到的匹配引腳信息
void AutoDetect::logFound(PinName pin) {
  log_.print("n >> > Pin FOUND: ");
  log_.print(portChar(pin));
  log_.println(pinNumber(pin));
}

/// @brief 默認(rèn)實(shí)現(xiàn):打印掃描完成但未找到匹配的信息
void AutoDetect::logFinishedNoFound() {
  log_.println("nOne full round finished, no match found.");
}

/// @brief 默認(rèn)匹配規(guī)則:探頭電平發(fā)生變化即認(rèn)為匹配
bool AutoDetect::isMatch(int vLow, int vHigh, PinName /*pin*/) {
  // 默認(rèn)規(guī)則:探頭在此引腳高低切換時(shí)也發(fā)生變化
  return (vLow != vHigh);
}

/// @brief 默認(rèn)實(shí)現(xiàn):找到匹配后無(wú)特殊行為,由子類(lèi)覆寫(xiě)
void AutoDetect::loopOnFound(PinName /*pin*/) {
  // 默認(rèn)什么也不做,由子類(lèi)決定
}

// —— 生命周期 —— //

/// @brief 初始化檢測(cè)過(guò)程,設(shè)置引腳模式和初始狀態(tài)
void AutoDetect::begin() {
  // 注意:日志串口應(yīng)在外部先 begin(),這里只做邏輯打印
  index_    = 0;
  found_    = false;
  finished_ = false;
  foundPin_ = NC;

  logHeader();

  // 探頭腳輸入,下拉
  pinMode((int)probePin_, INPUT_PULLDOWN);

  // 所有候選引腳設(shè)置為輸出高電平(不包括排除項(xiàng))
  for (int i = 0; i < pinCount_; i++) {
    PinName p = pins_[i];
    if (isExcluded(p)) continue;
    pinMode((int)p, OUTPUT);
    digitalWrite((int)p, HIGH); // 假設(shè)"低電平有效",默認(rèn)關(guān)閉
  }
}

/// @brief 執(zhí)行檢測(cè)循環(huán)的主要邏輯
void AutoDetect::update() {
  if (found_) {
    // 已找到:由子類(lèi)決定在“已找到狀態(tài)”下如何循環(huán)
    loopOnFound(foundPin_);
    return;
  }

  if (finished_) {
    // 已掃描完但沒(méi)找到:空轉(zhuǎn)即可
    delay(100);
    return;
  }

  // 跳過(guò)所有被排除的引腳
  while (index_ < pinCount_ && isExcluded(pins_[index_])) {
    index_++;
  }

  if (index_ >= pinCount_) {
    finished_ = true;
    logFinishedNoFound();
    return;
  }

  PinName testPin = pins_[index_];

  // 日志:正在測(cè)試哪個(gè)腳
  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);

  // 日志:進(jìn)度條
  logProgress(index_ + 1, pinCount_);

  // 判斷是否匹配
  if (isMatch(vLow, vHigh, testPin)) {
    found_    = true;
    foundPin_ = testPin;
    logFound(foundPin_);
    onFound(foundPin_);
    return;
  }

  index_++;
}
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 單片機(jī)
    +關(guān)注

    關(guān)注

    6074

    文章

    45459

    瀏覽量

    667215
  • mcu
    mcu
    +關(guān)注

    關(guān)注

    147

    文章

    18797

    瀏覽量

    393229
  • 引腳
    +關(guān)注

    關(guān)注

    16

    文章

    2105

    瀏覽量

    55436
  • GPIO
    +關(guān)注

    關(guān)注

    16

    文章

    1323

    瀏覽量

    55955
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    詳解MCU的運(yùn)行過(guò)程

    課程簡(jiǎn)介:本課程基于STM32F103RC講解,通過(guò)從MCU上電開(kāi)始啟動(dòng)開(kāi)始分析,詳解MCU的運(yùn)行過(guò)程,講師“東方青”多年從事開(kāi)發(fā)經(jīng)驗(yàn)而言,學(xué)習(xí)Cortex-M系列的
    發(fā)表于 11-03 07:58

    MCU芯片中GPIO口的驅(qū)動(dòng)方式可分為哪幾類(lèi)

    嵌入式學(xué)習(xí)GPIO接口詳解單個(gè)引腳的操作無(wú)外乎3種:輸出高低電平、檢測(cè)引腳狀態(tài)、中斷。對(duì)某個(gè)引腳的操作一般通過(guò)讀、寫(xiě)特定寄存器 配置寄存器來(lái)完成。MCU
    發(fā)表于 11-03 06:35

    MCU+CPLD/FPGA實(shí)現(xiàn)GPIO擴(kuò)展與控制的資料大合集

    :2019-04-26;=====================分割線========================立題詳解:本次介紹“MCU+CPLD/FPGA實(shí)現(xiàn)GPIO擴(kuò)展與控制”,使用此種組合具有一定的優(yōu)...
    發(fā)表于 11-04 07:42

    介紹ATMEL MCUGPIO配置

    Getting Started with Atmel SMART SAM D MCU Configuring the GPIO
    的頭像 發(fā)表于 07-09 00:25 ?5895次閱讀

    Linux內(nèi)核GPIO操作函數(shù)的詳解分析

    本文檔的主要內(nèi)容詳細(xì)介紹的是Linux內(nèi)核GPIO操作函數(shù)的詳解分析免費(fèi)下載。
    發(fā)表于 01-22 16:58 ?28次下載

    MCU學(xué)習(xí)筆記_GPIO工作原理

    MCU學(xué)習(xí)筆記STM32時(shí)鐘1. STM32 GPIO基礎(chǔ)知識(shí)2. STM32 GPIO工作模式3. STM32 GPIO寄存器1. STM
    發(fā)表于 10-25 11:21 ?17次下載
    <b class='flag-5'>MCU</b><b class='flag-5'>學(xué)習(xí)</b>筆記_<b class='flag-5'>GPIO</b>工作原理

    MCUGPIO口的驅(qū)動(dòng)方式

    嵌入式學(xué)習(xí)GPIO接口詳解http://www.51hei.com/bbs/dpj-115534-1.html單個(gè)引腳的操作無(wú)外乎3種:輸出高低電平、檢測(cè)引腳狀態(tài)、中斷。對(duì)某個(gè)引腳的操作一般通過(guò)讀
    發(fā)表于 10-28 18:20 ?8次下載
    <b class='flag-5'>MCU</b>中<b class='flag-5'>GPIO</b>口的驅(qū)動(dòng)方式

    ST MCU_GPIO的八種工作模式詳解

    補(bǔ)充:N、P型的區(qū)別,就是一個(gè)為正電壓?jiǎn)?dòng)(NMOS),一個(gè)為負(fù)電壓?jiǎn)?dòng)(PMOS)GPIO的八種工作模式詳解浮空輸入_IN_FLOATING帶上拉輸入_IPU帶下拉輸入_IPD模擬輸入_AIN開(kāi)漏
    發(fā)表于 10-28 20:51 ?13次下載
    ST <b class='flag-5'>MCU_GPIO</b>的八種工作模式<b class='flag-5'>詳解</b>。

    c語(yǔ)言實(shí)現(xiàn)串口通信_(tái)MCU+CPLD/FPGA實(shí)現(xiàn)對(duì)GPIO擴(kuò)展與控制

    :2019-04-26;=====================分割線========================立題詳解:本次介紹“MCU+CPLD/FPGA實(shí)現(xiàn)GPIO擴(kuò)展與控制”,使用此種組合具有一定的優(yōu)...
    發(fā)表于 10-29 10:21 ?2次下載
    c語(yǔ)言實(shí)現(xiàn)串口通信_(tái)<b class='flag-5'>MCU</b>+CPLD/FPGA實(shí)現(xiàn)對(duì)<b class='flag-5'>GPIO</b>擴(kuò)展與控制

    STM32學(xué)習(xí)-GPIO詳解

    一、GPIO介紹GPIO:就是一個(gè)引腳作為輸入或者輸出。GPIO的八種工作模式:輸入輸出是相對(duì)于CPU,四種輸入、四種輸出模式及四種輸出最大速度輸入:外部數(shù)據(jù)輸入到開(kāi)發(fā)板輸出:開(kāi)發(fā)板的數(shù)據(jù)輸出
    發(fā)表于 11-29 16:51 ?20次下載
    STM32<b class='flag-5'>學(xué)習(xí)</b>-<b class='flag-5'>GPIO</b><b class='flag-5'>詳解</b>

    STM學(xué)習(xí)- GPIO工作原理

    STM學(xué)習(xí)- GPIO工作原理Sat 0203:0006:0009:0012:0003:0006:0009:00Jan 03已完成 時(shí)間安排主要內(nèi)容: GPIO工作方式
    發(fā)表于 12-28 19:32 ?6次下載
    STM<b class='flag-5'>學(xué)習(xí)</b>- <b class='flag-5'>GPIO</b>工作原理

    STM32學(xué)習(xí)筆記---GPIO

    STM32的學(xué)習(xí)筆記—GPIO我使用的是STM32F401ZGT6,有7組IO口,每組16個(gè)引腳,共112個(gè)引腳。因?yàn)樘肆?,確實(shí)容易出錯(cuò),還請(qǐng)賜教參考官方文檔:八種IO口模式區(qū)別結(jié)構(gòu)原理該單片機(jī)在
    發(fā)表于 01-13 16:31 ?6次下載
    STM32<b class='flag-5'>學(xué)習(xí)</b>筆記---<b class='flag-5'>GPIO</b>

    AN092GD32MCU GPIO結(jié)構(gòu)與使用注意事項(xiàng)

    AN092 GD32 MCU GPIO結(jié)構(gòu)與使用注意事項(xiàng)
    發(fā)表于 03-01 18:48 ?0次下載
    AN092GD32<b class='flag-5'>MCU</b> <b class='flag-5'>GPIO</b>結(jié)構(gòu)與使用注意事項(xiàng)

    敏矽微電子Cortex-M0學(xué)習(xí)筆記04——GPIO詳解及應(yīng)用實(shí)例

    敏矽微電子Cortex-M0學(xué)習(xí)筆記04——GPIO詳解及應(yīng)用實(shí)例
    的頭像 發(fā)表于 09-26 17:07 ?2228次閱讀
    敏矽微電子Cortex-M0<b class='flag-5'>學(xué)習(xí)</b>筆記04——<b class='flag-5'>GPIO</b><b class='flag-5'>詳解</b>及應(yīng)用實(shí)例

    MM32F0140 GPIO學(xué)習(xí)筆記

    MM32F0140 GPIO學(xué)習(xí)筆記
    的頭像 發(fā)表于 09-26 16:42 ?1338次閱讀
    MM32F0140 <b class='flag-5'>GPIO</b><b class='flag-5'>學(xué)習(xí)</b>筆記