學習如何用 MCU ADC 讀取類比感測器,及 I²C / SPI 讀取數位感測器。


一、學習目標

  • ADC 取樣與校正
  • I²C / SPI 介面通訊
  • 訊號處理(濾波、線性化)
  • 簡單顯示介面(OLED / Serial)

二、實作專案:環境監測站

規格

  • 溫度:-10°C ~ 50°C,精度 ±1°C
  • 濕度:0–100%,精度 ±5%
  • 光度:0–100k lux
  • 距離:2–80 cm
  • OLED 顯示
  • USB 序列輸出

三、感測器選擇

溫度

感測器 介面 範圍 精度 價格
LM35 類比 (10mV/°C) -55–150°C ±0.5°C
TMP36 類比 -40–125°C ±2°C
DS18B20 1-Wire -55–125°C ±0.5°C
DHT22 單線 -40–80°C, 0–100%RH ±0.5°C
BME280 I²C/SPI -40–85°C, 0–100%, 壓力 ±1°C
SHT4x I²C -40–125°C, 0–100% ±0.2°C

光度

  • LDR(光敏電阻)+ 分壓 → ADC
  • BH1750(I²C,數位 lux 直接輸出)
  • TSL2591(高動態範圍)

距離

  • HC-SR04(超音波,2–400cm)
  • VL53L0X / VL53L1X(雷射 ToF,I²C)
  • Sharp GP2Y0A21(紅外,類比)

四、Arduino 完整程式

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_BME280.h>
#include <BH1750.h>

#define OLED_WIDTH 128
#define OLED_HEIGHT 64
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire);
Adafruit_BME280 bme;
BH1750 lightMeter;

const int trigPin = 9;
const int echoPin = 10;

void setup() {
  Serial.begin(115200);
  Wire.begin();

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("OLED 初始化失敗");
    while (1);
  }

  if (!bme.begin(0x76)) {
    Serial.println("BME280 找不到");
    while (1);
  }

  lightMeter.begin();
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
}

float readDistance() {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  long duration = pulseIn(echoPin, HIGH, 30000);
  return duration * 0.0343 / 2.0;
}

void loop() {
  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0F;
  float lux = lightMeter.readLightLevel();
  float dist = readDistance();

  Serial.print("T="); Serial.print(t);
  Serial.print(" H="); Serial.print(h);
  Serial.print(" P="); Serial.print(p);
  Serial.print(" Lux="); Serial.print(lux);
  Serial.print(" D="); Serial.println(dist);

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print("Temp: "); display.print(t); display.println(" C");
  display.print("Humi: "); display.print(h); display.println(" %");
  display.print("Pres: "); display.print(p); display.println(" hPa");
  display.print("Lux:  "); display.println(lux);
  display.print("Dist: "); display.print(dist); display.println(" cm");
  display.display();

  delay(1000);
}

五、進階主題

1. ADC 校正

實際 ADC 與理想差異: - 偏移誤差 - 增益誤差 - 非線性

校正:

real_value = a * adc_reading + b

用兩點校正法(known low、known high)求 a、b。

2. 類比訊號降雜訊

  • 平均濾波:取多次平均
  • 中值濾波:去除尖峰
  • 指數移動平均(EMA):低通效果
float ema = 0;
const float alpha = 0.1;
ema = alpha * new + (1 - alpha) * ema;

3. I²C 故障排除

  • 拉高電阻:4.7 kΩ(標準)、2.2 kΩ(fast mode)
  • 地址衝突:用 I²C scanner 確認
  • 線太長 → 減少電容、降速
// I²C scanner
for (uint8_t addr = 1; addr < 127; addr++) {
  Wire.beginTransmission(addr);
  if (Wire.endTransmission() == 0) {
    Serial.print("找到 0x");
    Serial.println(addr, HEX);
  }
}

4. 單位轉換 / 線性化

例如 NTC 熱敏電阻:

$$ \frac{1}{T} = \frac{1}{T_0} + \frac{1}{B} \ln!\left(\frac{R}{R_0}\right) $$

需要查表或多項式擬合。


六、PCB 化(進階)

把麵包板電路畫成 PCB:

  1. KiCad 畫原理圖
  2. 元件選 SMD(精緻)
  3. 整合 USB-C、TypeC、CH340 USB 轉串
  4. 加入 ESD 保護
  5. 量產 10 片,分送朋友

七、本專案重點

✅ 您學到:

  • [x] ADC 量測類比感測器
  • [x] I²C 讀取數位感測器
  • [x] OLED 顯示
  • [x] 序列通訊 debug
  • [x] 訊號濾波
  • [x] 多感測器整合

下一專案

03-電源供應器.md