久久ER99热精品一区二区-久久精品99国产精品日本-久久精品免费一区二区三区-久久综合九色综合欧美狠狠

新聞中心

EEPW首頁 > 嵌入式系統 > 設計應用 > 一種低成本、高靈活度的電子滾輪測距方案

一種低成本、高靈活度的電子滾輪測距方案

作者: 時間:2025-12-16 來源: 收藏


項目難度:初學者

所需時間:約 1 小時

提供完整制作說明


項目簡介

c641519f-456e-4d0e-807c-18b1590e0085.png

本文介紹了一種可測量任意形狀表面的電子測量設備
該設備通過滾輪與旋轉編碼器的組合,實現對曲線、不規則邊緣、多邊形等復雜路徑的距離測量,突破了傳統直尺或卷尺在實際應用中的限制。

無論是圓形、三角形、正方形,還是不規則輪廓,只需將設備沿著目標表面滾動,即可實時獲得測量結果。


設計背景與靈感來源

項目作者 Piyush 在電商平臺上看到一種被稱為 Electronic Digital Tape Measure 的電子測距產品。這類產品通過滾輪記錄行進距離,使用體驗直觀、效率高,能夠輕松測量各種復雜表面。

然而,該類成品設備價格較高(在 eBay 上約 60 美元),性價比并不理想。
在看到 Instructables 舉辦的 Build A Tool Contest 后,作者決定自行設計并制作一款功能完整、成本更低的替代方案


本設備的主要優勢

  • 操作簡單,上手快

  • 測量速度快,實時顯示

  • 支持單位切換

  • 體積小巧,便于攜帶

  • 成本遠低于市售成品

  • 可測量任意形狀路徑

  • 界面直觀,用戶友好

  • 實測精度可達約 99%


系統組成

1b692080-2444-4b49-910e-45f1ee90cd1d.png

硬件組件

  • 自鎖按鍵開關 ×1

  • 滾輪 ×1

  • 旋轉編碼器 ×1

  • 40 針單排公頭(0.1")×1

  • 3.7V 300mAh 鋰電池 ×1

  • Adafruit 1.3" 128×64 單色 OLED 顯示屏 ×1

  • Arduino Pro Micro(HID)×1

  • 跳線若干

  • 輕觸按鍵(SPST-NO)×1

  • 木棍 ×1


軟件環境

  • Arduino IDE

  • Adafruit GFX Library

  • Adafruit SSD1306 Library


工具

  • 剪刀

  • 電烙鐵

  • 無鉛焊錫絲

  • 熱熔膠槍

  • 剝線鉗

  • 砂紙


結構與機械部分制作

步驟一:加工木棍支撐結構

根據滾輪和旋轉編碼器的尺寸,對木棍進行切割和打磨:

  1. 使用鉛筆和直尺在木棍上標記尺寸

  2. 用剪刀裁剪出大致形狀

  3. 使用砂紙打磨木棍中部,使中間形成約 1 mm 的間隙

該結構用于將滾輪與旋轉編碼器剛性連接,確保滾輪轉動時編碼器同步旋轉。


步驟二:將旋轉編碼器與滾輪連接

  1. 將木棍中部插入旋轉編碼器的軸槽

  2. 將整個組件橫向固定在滾輪直徑方向

  3. 確保滾輪轉動順暢且無明顯偏擺


電子系統組裝

步驟三:整機裝配

  1. 使用熱熔膠將 3.7V 鋰電池固定在旋轉編碼器外殼上

  2. 按照示意圖在編碼器上焊接信號線

  3. 將 4 根母頭跳線焊接至 OLED 顯示屏

  4. 將 OLED 顯示屏固定在電池頂部

  5. 將自鎖電源開關粘貼在 OLED 顯示屏下方,使按壓顯示屏即可通電

  6. 將輕觸按鍵安裝在旋轉編碼器引腳附近

  7. 最后,將 Arduino Pro Micro 安裝在跳線頂部,完成整體裝配


電氣連接說明

506af3e1-8001-4022-9b34-6b905ce56ccd.png

步驟四:電路連接

  • 電池正極 → Arduino Pro Micro VCC

  • 電池負極 → 自鎖開關 → 系統 GND

  • OLED VCC → Arduino VCC

  • 所有 GND 共地

  • OLED I2C 通信:

    • SDA → Arduino 引腳 2

    • SCL → Arduino 引腳 3

  • 旋轉編碼器:

    • S1 → Arduino 引腳 5

    • S2 → Arduino 引腳 6

    • Key → Arduino 引腳 7

  • 輕觸按鍵 → Arduino 引腳 4

?? 注意:電源 GND 必須通過自鎖開關,否則會導致設備無法正確斷電。


工作原理深度解析(編碼器 + 中斷 + 精度)

1)測距的核心思路:把“滾動距離”變成“脈沖計數”

這個裝置的本質是一個“電子測距輪”:

  • 滾輪貼著被測表面滾動

  • 滾輪帶動**旋轉編碼器(或旋轉電位器式的編碼器結構)**轉動

  • 編碼器輸出一串脈沖信號

  • 單片機(Arduino Pro Micro)對脈沖計數

  • 根據滾輪周長與單圈脈沖數,把脈沖數換算為距離

代碼中定義了兩個關鍵參數:

  • pulsePerRound = 21:滾輪轉一圈產生 21 個脈沖

  • circumference = 15:滾輪周長設定為 15 cm(等效于滾輪直徑約 4.77 cm)

因此,每一個脈沖對應的距離為:

[
Delta d = frac{circumference}{pulsePerRound} = frac{15}{21}approx 0.714285text{ cm}
]

最終距離(cm)在代碼中是這樣算的:

cm = abs(pulseCounter) * (circumference / pulsePerRound) + (cornercount * corner);

其中 abs(pulseCounter) * (circumference / pulsePerRound) 就是滾輪在“直線/連續滾動”情況下的累計距離。


2)方向判斷:兩路信號(A/B 相)實現正轉/反轉計數

旋轉編碼器通常提供兩路相位錯開的信號(常叫 A/B 相)。當你只對 A 相做中斷觸發,再讀取 B 相的電平,就能判斷方向:

  • A 相上升沿觸發中斷

  • 在中斷里讀取 B 相:

    • B 為 HIGH:認為正向,計數 pulseCounter++

    • B 為 LOW:認為反向,計數 pulseCounter--

代碼中對應邏輯:

void rotaryPot() {
  if (digitalRead(PotPin2) == HIGH) {
    pulseCounter++;
    delay(10);
  }
  if (digitalRead(PotPin2) == LOW) {
    pulseCounter--;
    delay(10);
  }
}

這就是典型的“單邊沿中斷 + 讀取另一相”實現方向判斷的方式,優點是硬件和程序都更簡單。


3)為什么用中斷:避免主循環漏計數

如果你在 loop() 里用 digitalRead()不斷輪詢,滾輪轉得快時就可能漏掉脈沖,導致距離偏小。

這里采用:

attachInterrupt(digitalPinToInterrupt(PotPin1), rotaryPot, RISING);

含義是:
PotPin1(A 相)每出現一次上升沿,就立刻打斷主程序去執行 rotaryPot(),把這一脈沖記下來。

因此:

  • 主循環可以負責顯示、按鍵邏輯、單位換算

  • 脈沖統計交給中斷去做,計數更可靠


4)“拐角補償 cornercount”機制:解決“提輪/轉彎不滾動”的缺口

在測量復雜形狀(比如多邊形、尖角)時,用戶可能會:

  • 在拐角處停一下、抬一下輪子調整方向

  • 或輪子在拐角處打滑、短暫停轉

這樣會造成編碼器脈沖增長不足,從而“測量缺口”。

代碼里引入了一個很有意思的“拐角補償”:

const float corner = (circumference / 3.1415);

這里 corner = 周長 / π,數值上約等于 滾輪直徑(因為 C = πD → D = C/π)。

然后用 cornercount 來累加補償量:

cm = abs(pulseCounter) * (circumference / pulsePerRound) + (cornercount * corner);

也就是說,每次認為發生了一次“角點動作”,就額外加上一個 約等于滾輪直徑的距離補償

如何觸發“角點補償”?

同一個按鍵有兩種操作:

  • 短按:清零(pulseCounter=0, cornercount=0)

  • 長按(按住超過 cornerTimeGoal=1500ms):cornercount++

對應代碼片段:

if (millis() - premilli > cornerTimeGoal) {
  cornercount++;
}

并在長按期間給 OLED 做了 “三點加載”動畫,提示用戶正在累計角點邏輯。

這個機制很適合 DIY 場景:不用復雜的姿態檢測、也不需要額外傳感器,用“人為確認角點”的方式讓測量更接近真實輪廓。


5)精度來源與誤差分析(非常關鍵)

這類滾輪測距的誤差主要來自 4 類:

A. 周長參數誤差(系統性誤差)

代碼寫死 circumference=15
如果你的真實滾輪周長不是 15 cm,會產生線性比例誤差

  • 真實周長比 15 大 1%,測距也會整體大 1%

  • 真實周長比 15 小 1%,測距也會整體小 1%

? 建議:做一次標定。比如在 100cm 標準尺上滾動一次:

  • 顯示為 98cm → 周長應放大到 15*(100/98)

  • 顯示為 102cm → 周長應縮小到 15*(100/102)

B. 脈沖分辨率限制(量化誤差)

每脈沖約 0.714 cm,因此即使一切完美,也存在量化臺階:

  • 單次最小變化 ≈ 0.714 cm

  • 距離越短,量化誤差占比越高

? 改進方向:提高每圈脈沖數(更高分辨率編碼器),或用雙邊沿計數/四倍頻算法提升有效分辨率。

C. 打滑、接觸壓力、表面材質(隨機誤差)

輪子與表面摩擦不足、表面太光滑、或者壓力不穩定都會導致實際滾動距離與輪子轉動不一致。

? 建議:輪子用更高摩擦材質;測量時保持穩定壓力與速度。

D. 軟件層面:中斷里 delay(10) 的影響

你的中斷函數里有:

delay(10);

在中斷中延時會顯著限制最大可計數頻率(滾得快就會漏脈沖),這是精度在高速度下下降的一個重要原因。

? 建議(如果允許改代碼):去掉中斷里的 delay,用硬件消抖或軟件更輕量的消抖方式(比如記錄 micros 時間間隔)。


完整代碼(原樣保留)

// importing the Libraries: Download "Adafruit SD1306" and the "Adafruit GFX Library"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

#define SCREEN_ADDRESS 0x3C  // Oled Display's Address

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Change The Setting Acording To your Setup. If you Followed the Instructions, No need to Change Anything
#define unitChangePin 5
#define PotPin1 7
#define PotPin2 6
#define corner_resetPin 4

const int pulsePerRound = 21;
const float circumference = 15;
const float corner = (circumference / 3.1415);
int cornerTimeGoal = 1500;

// Some variables use in Program
int pulseCounter;
float cm;
int unit = 0;
int cornercount;
unsigned long premilli;
bool buttonPresedBefore = false;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);  // Start Serial Com

  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;
  }

  display.clearDisplay();  // Clear Display
  display.display();


  display.setTextColor(WHITE);
  display.setRotation(3);  // Rotate the screen: 0 = 0° ,1 = 90° , 2 = 180° ,3 = 270° 

  attachInterrupt(digitalPinToInterrupt(PotPin1), rotaryPot, RISING);//Attaching Interupt 
  pinMode(corner_resetPin, INPUT_PULLUP);
}

void loop() {
  // put your main code here, to run repeatedly:
  cm = abs(pulseCounter) * (circumference / pulsePerRound) + (cornercount * corner);  // Convert Pulses from Rotary Pot and convert to Cm

  if (!digitalRead(unitChangePin)) { //if Unit Changing Button Clicked
    unit++;
    if (unit == 2) {
      unit = 0;
    }
    Serial.print("Unit Changed! ");
    Serial.println(unit);
    delay(300); //De-bounce delay
  }

  if (!digitalRead(corner_resetPin)) {//We are Checking if the Button is Being Held or Clicked

    if (!buttonPresedBefore) {
      buttonPresedBefore = true;
      delay(100);
      premilli = millis();
    }

  } else {
    if (buttonPresedBefore) {
      if (millis() - premilli > cornerTimeGoal) {
        Serial.println(millis() - premilli);
        buttonPresedBefore = false;

        cornercount++;
        Serial.print("Corner Counter is Set to:");// How many Times are we Going to Multiply the corner Variable
        Serial.println(cornercount);

      } else {
        buttonPresedBefore = false;

        pulseCounter = 0;
        cornercount = 0;
        Serial.println("Reseting Data...");
      }
    }
  }


  // This is the Part where We are Displaying Stuff

  display.clearDisplay();// Clearing Display For New Data
  display.drawRect(0, 0, 64, 128, 1);// Make the UI look better

  if (unit == 0) {// Making the Correct Measurements and Setting and Display them
    display.setTextSize(3);
    display.setCursor(18, 20);
    if (cm < 10) {
      display.print("0" + String(cm));
    } else if (cm < 100) {
      display.print(String(cm, 2));
    } else {
      display.print(String(99.99, 2));
    }
    display.setCursor(15, 80);
    display.print("CM");

  } else if (unit == 1) {

    display.setTextSize(3);
    display.setCursor(15, 15);

    display.print(String(int(cm / 100)) + ".");

    display.setCursor(15, 40);
    char buffer[10];
    if ((int(cm) - (int(cm / 100) * 100)) < 10) {
      sprintf(buffer, "0%i", (int(cm) - (int(cm / 100) * 100)));
      display.print(buffer);

    } else if ((int(cm) - (int(cm / 100) * 100)) >= 10) {
      display.print(int(cm) - (int(cm / 100) * 100));
    }
    display.setCursor(22, 75);
    display.setTextSize(4);
    display.print("M");
  }


  if (buttonPresedBefore) {// 3 Dots Animation

    if (cornerTimeGoal / 3 < millis() - premilli) {
      display.drawPixel(22, 110, 1);
    }
    if (cornerTimeGoal * 2 / 3 < millis() - premilli) {
      display.drawPixel(32, 110, 1);
    }
    if (cornerTimeGoal < millis() - premilli) {
      display.drawPixel(42, 110, 1);
    }
  }

  display.display(); // Display EVERYTHING!
}


void rotaryPot() {// Function to Get the Pulses From the Rotary Pot by Interrupt

  if (digitalRead(PotPin2) == HIGH) {
    pulseCounter++;
    delay(10);
  }
  if (digitalRead(PotPin2) == LOW) {
    pulseCounter--;
    delay(10);
  }
}

總結

該項目以極低的硬件成本,實現了商業電子測距輪的核心功能,適合:

  • 電子與嵌入式初學者

  • Arduino 實踐教學

  • DIY 工具設計

  • 工程測量輔助工具原型

同時,該方案也為后續升級(如藍牙、數據記錄、更高分辨率編碼器)提供了良好的基礎。



關鍵詞:

評論


相關推薦

技術專區

關閉