)
第一次拿到 AMG8833 這個 8×8 紅外陣列傳感器的時候我多少有點懷疑一片不到指甲蓋大的板子居然只靠 64 個測溫點就能做出“熱成像”效果真正把它接到 STM32F103 上讀到第一幀溫度數據之后結論立刻反轉。這個方案雖然分辨率低但隔空看見熱源、判斷“有沒有人”“哪里溫度異?!边@類需求它完全能勝任而且成本壓得很低實際玩起來很有意思。這篇文章我會把整個實戰(zhàn)過程完整講清楚為什么選 AMG8833、怎么跟 STM32F103 接線、固件里怎么初始化傳感器和讀取 64 點溫度、怎么做簡單的人體檢測和高溫報警最后再聊聊我在調試中踩過的坑。適合剛學 I2C 通信、想上手紅外測溫項目的同學也適合需要低成本非接觸測溫方案、想做快速原型驗證的工程師參考。1. 項目概述與方案選型1.1 為什么用 AMG8833 做人體感應很多人提到“人體感應”第一反應就是 PIR 熱釋電傳感器。PIR 確實便宜又成熟但它的缺點是只能檢測“移動”人一旦站著不動或者以很慢的速度走動輸出就會消失。更麻煩的是它分不清“人”和“被曬熱的沙發(fā)”只要溫差變化明顯什么都可能觸發(fā)。AMG8833 則是一塊 8×8 的熱電堆陣列每個像素都能測到對應的紅外輻射溫度。它不像 PIR 那樣只能給一個“有人/沒人”的信號而是能告訴我們“熱源在哪、大概有多熱”哪怕人站著不動只要身體溫度與環(huán)境溫度有差異就能持續(xù)檢測到。這個能力在 AV 聯動、養(yǎng)殖場觀察、會議簽到、門禁待機喚醒等場合非常實用。當然8×8 分辨率很低只有 64 個溫度點每個點覆蓋的角度大概是 7.5°整體視場角 60°。這決定了它沒辦法做臉部識別但在“這個方向 30 厘米外有個 36°C 左右的移動熱源”這種粒度上已經夠了。相比 MLX90614 那種單點紅外傳感器AMG8833 最大的優(yōu)勢是多了一個空間分布信息。比如房間里來了兩個人MLX90614 面對的方向不湊巧就測不到而 AMG8833 可以通過不同像素的溫度差判斷出“這里有一個熱點那邊也有一個熱點”。相比更高級的 MLX90640 32×24 陣列AMG8833 雖然像素少但是價格便宜一個數量級數據量小F103 的 I2C 接口和內存都完全帶得動。1.2 為什么選 STM32F103 而不是其他芯片STM32F103 這個系列到今天已經被大家玩成“國民單片機”了開發(fā)資料多到數不過來。拿最小的 F103C8T6 來說主頻 72MHz、Flash 64KB、RAM 20KBI2C、USART、GPIO 全都有跑一個 AMG8833 讀取 蜂鳴器報警 OLED 顯示的固件資源綽綽有余。從性價比看AMG8833 模塊幾十塊錢STM32F103C8T6 最小系統(tǒng)板幾塊錢到十幾塊錢整套硬件成本壓得很低。相比 ESP32、樹莓派這類帶著無線或系統(tǒng)級資源的平臺F103 更“純粹”也更接近嵌入式開發(fā)的底層路徑沒有 SDK 幫你把 I2C 寫到飛起寄存器、DMA、錯誤處理都得自己想清楚。做完這個項目你對 I2C 協議、傳感器寄存器、嵌入式外設配置的掌握會比刷十遍教程都扎實。如果你手頭已經有其他 Cortex-M 內核的板子比如 APM32、AT32移植思路也是一模一樣的。AMG8833 只依賴 I2C 和少量 GPIOHAL 庫的 API 基本通用把引腳配置和時鐘樹改一下就行沒必要重新學一套東西。1.3 整體方案框架整套系統(tǒng)的數據流大概是這樣的AMG8833 傳感器上電之后持續(xù)采集 8×8 陣列的紅外溫度單片機通過 I2C 從寄存器 0x80 開始一次性讀取 64 個字節(jié)的原始數據轉換成攝氏度之后在內存里形成一個 8×8 的二維溫度數組。然后程序對這個數組做兩件事一是找出最高溫和平均背景溫度估算是否有“高溫目標”二是判斷這個目標是否超過報警閾值如果超過就點亮 LED、讓蜂鳴器響。為了方便調試我另外用串口把幾個關鍵溫度值打印出來再用一塊 OLED 把 64 點溫度畫成簡易熱圖。這里有個關鍵點報警不只是一個“溫度大于多少度”的簡單比較而是要結合背景溫差做判斷否則夏天環(huán)境溫度高的時候整個畫面都是 38°C 以上會瘋狂誤報。這個邏輯后面單獨講。2. 硬件準備與接線2.1 核心硬件清單做這個項目下面幾樣東西就夠了STM32F103C8T6 最小系統(tǒng)板一塊也就是大家常說的藍色藥丸板。AMG8833 紅外熱像模塊一個建議買自帶 LDO 穩(wěn)壓和 PCB 天線的常見模塊具體看商家標注。0.96 寸 SSD1306 OLEDI2C 版本方便可視化調試。有源蜂鳴器模塊或普通蜂鳴器 S8050 三極管 1k 電阻。一個 LED 和 220Ω 限流電阻。杜邦線若干、4.7kΩ 上拉電阻兩個備用。AMG8833 模塊的接口一般是 VIN、GND、SCL、SDA有的板子還會把 AD0 引腳引出來甚至標成 SDO。模塊工作電壓典型值是 3.3V部分模塊上面有穩(wěn)壓芯片接 5V 也能扛住但為了和 F103 邏輯電平統(tǒng)一我建議直接接 3.3V省去電平轉換的麻煩。2.2 AMG8833 引腳定義與 I2C 接線F103 的 I2C1 默認映射在 PB6SCL和 PB7SDA這個項目就用這一組。接線表如下AMG8833 引腳STM32F103 引腳說明VIN3.3V傳感器供電GNDGND共地SCLPB6I2C 時鐘SDAPB7I2C 數據AD0GND 或 3.3V從機地址選擇AD0 引腳的用法很關鍵。AMG8833 有固定 I2C 地址選擇當 AD0 接 GND 時從機地址是 0x68接 3.3V 時地址是 0x69。如果你的模塊沒有引出 AD0一般默認地址就是 0x68直接在代碼里寫 0x68 就行。如果讀不到數據優(yōu)先懷疑地址設置是否跟硬件一致。另外I2C 總線需要上拉電阻。很多 AMG8833 模塊板上已經焊了上拉直接接就能工作但如果你的模塊沒有或者你想把 OLED、AMG8833 放在同一條 I2C 總線上最好在 SCL 和 SDA 上各接一個 4.7kΩ 電阻到 3.3V。F103 的 I2C 外設本身對總線時序比較敏感上拉電阻缺了或者阻值太大很容易出現“能識別地址但讀不到數據”的奇怪現象。2.3 顯示與報警模塊接法OLED 和 AMG8833 可以共用同一條 I2C 總線OLED 的地址一般是 0x3C 或 0x3D。這里需要注意如果兩個設備共享總線代碼里發(fā)送地址時要分清設備地址最高位要左移一位。HAL 庫的HAL_I2C_Mem_Read和HAL_I2C_Mem_Write會自動幫你做地址左移但裸機讀寫時很多人會在這里翻車少移一位就找不到設備。報警部分我用了兩個輸出一個 LED一個有源蜂鳴器。LED 串一個 220Ω 電阻接到 PA1 即可。蜂鳴器如果是有源模塊直接把信號引腳接到 PA2高電平觸發(fā)也能工作但蜂鳴器工作時電流經常沖到 20mA 以上用單片機的 GPIO 直接驅動雖然偶爾能起來長期跑還是不放心。更穩(wěn)的做法是 GPIO 接 S8050 三極管的基極基極串 1kΩ 電阻蜂鳴器接在集電極和電源之間發(fā)射極接地。這樣 GPIO 只負責給基極灌電流大電流由外部電源承擔整個系統(tǒng)可靠很多。3. 固件設計與核心代碼3.1 CubeMX 初始化配置我開發(fā)時用的 STM32CubeIDE 加 HAL 庫第一步先在 CubeMX 里把硬件工程搭好。配置清單如下SYS 里選擇 Serial Wire方便用 SWD 調試。RCC 選擇外部晶振HSEF103 藍色藥丸板上通常有 8MHz 晶振和復位電路如果你用的是別的核心板注意確認外部晶振是否焊好沒有外部晶振就用內部 HSI也能跑只是時鐘精度差一些。I2C1 打開模式選 I2C速度選 100kHz。AMG8833 手冊里支持 100kHz 和 400kHz但第一次調我強烈建議用 100kHz穩(wěn)定第一。400kHz 可以后面再試很多“偶發(fā)讀不到數據”的問題其實就是速率拉滿導致的。USART1 打開波特率 115200用于打印報警狀態(tài)和溫度最大值。PA1 配置成 GPIO_Output控制 LEDPA2 配置成 GPIO_Output控制蜂鳴器。時鐘樹不用太復雜用默認的 72MHz 主頻就行。AMG8833 的數據量不大I2C 時鐘掛在外設時鐘上就算分頻后是 100kHz讀取一幀 64 字節(jié)加地址幀也就幾毫秒的事。3.2 AMG8833 寄存器初始化AMG8833 的寄存器地址不多初始化只需要關注三個0x00 電源控制寄存器寫 0x00 進入正常模式。0x01 復位寄存器寫 0x3F 觸發(fā)一次軟件復位讓傳感器回到已知狀態(tài)。0x02 幀率寄存器寫 0x00 表示 10fps寫 0x01 表示 1fps。為什么要把幀率調到 10fps因為人體感應場景里我們希望報警延遲不要太長。1fps 雖然功耗低但一個人快速走過傳感器視野時可能剛好錯過兩幀之間的空檔導致漏報。10fps 的響應速度更適合實時檢測。初始化代碼這樣寫#include main.h #define AMG8833_ADDR 0x68 // AD0 接 GND #define AMG8833_PCTL 0x00 #define AMG8833_RST 0x01 #define AMG8833_FPSC 0x02 #define AMG8833_T01L 0x80 extern I2C_HandleTypeDef hi2c1; uint8_t amg8833_init(void) { uint8_t cmd; // 正常模式 cmd 0x00; if (HAL_I2C_Mem_Write(hi2c1, AMG8833_ADDR 1, AMG8833_PCTL, I2C_MEMADD_SIZE_8BIT, cmd, 1, 100) ! HAL_OK) return 0; // 軟件復位 cmd 0x3F; if (HAL_I2C_Mem_Write(hi2c1, AMG8833_ADDR 1, AMG8833_RST, I2C_MEMADD_SIZE_8BIT, cmd, 1, 100) ! HAL_OK) return 0; HAL_Delay(50); // 10fps cmd 0x00; if (HAL_I2C_Mem_Write(hi2c1, AMG8833_ADDR 1, AMG8833_FPSC, I2C_MEMADD_SIZE_8BIT, cmd, 1, 100) ! HAL_OK) return 0; return 1; }這里有個細節(jié)AMG8833_ADDR 是 7 位地址但是 I2C 通信時實際發(fā)送的是 8 位其中最低位是 R/W 位。HAL 庫的HAL_I2C_Mem_Write在內部會把地址左移一位再拼上讀寫位所以傳參時要把 0x68 左移一位AMG8833_ADDR 1別直接填 0x68。這個坑不少新手踩過一旦寫錯函數會返回 HAL_ERROR總線毫無響應。3.3 64 點溫度讀取與換算初始化完成之后讀取溫度就是一次普通的內存讀操作。AMG8833 從寄存器 0x80 開始連續(xù)存放 64 個像素的測溫結果每個像素占 1 個字節(jié)共 64 字節(jié)。代碼非常直接void amg8833_read_pixels(float *dst) { uint8_t raw[64]; uint8_t reg AMG8833_T01L; // 0x80 if (HAL_I2C_Mem_Read(hi2c1, AMG8833_ADDR 1, reg, I2C_MEMADD_SIZE_8BIT, raw, 64, 100) ! HAL_OK) { // 讀取失敗可以順手置一個錯誤標志 return; } for (int i 0; i 64; i) { dst[i] (int8_t)raw[i] * 0.25f; } }為什么乘以 0.25因為 AMG8833 內部把溫度做成了帶符號整數單位是 0.25°C/LSB。把這個整數強轉成 int8_t再乘 0.25就能得到攝氏度。這里要特別注意如果直接把 uint8_t 拿來乘溫度在零下時就會出錯。比如原始字節(jié) 0xFC按 uint8_t 算是 252乘以 0.25 變成 63°C實際上它是 -4°C。必須先轉成有符號數再計算。溫度數據拿到之后我習慣同時讀一下傳感器內部熱敏電阻的數值作為環(huán)境參考溫度。熱敏電阻寄存器在 0x0E 和 0x0F各存 1 個字節(jié)組合成一個帶符號數分辨率 0.0625°C。讀取方法類似但這里的寄存器地址是 16 位數據需要分開讀兩個字節(jié)再拼起來。環(huán)境溫度可以用來輔助判斷“當前室溫多高”報警算法里會更穩(wěn)。3.4 人體檢測與溫度報警邏輯這部分是整個項目的核心也是我認為比“能讀數”更有意思的地方。先說最簡單的思路如果畫面里有某個像素溫度超過 35°C就認為有人。這個邏輯在春秋天的室內環(huán)境確實能用但到了夏天墻面被曬到 38°C傳感器掃過去畫面全是 36°C 以上的熱斑誤報就成了噩夢。更合理的方法是“背景差檢測”。先把這 64 個點排序找出最大值maxTemp。然后去掉最大值附近幾個點計算剩下像素的平均值作為背景溫度bgTemp。人體檢測條件有兩層maxTemp 33.0f保證目標有一定絕對熱度。maxTemp - bgTemp 3.0f保證目標和周圍環(huán)境有明顯溫差。溫度報警的條件則單獨設一個閾值ALARM_TEMP#define ALARM_TEMP 37.5f #define PRESENCE_MIN 33.0f #define DELTA_MIN 3.0f static int stable_count 0; void process_temperature(float *pixels, uint8_t *presence, uint8_t *alarm) { float max_temp -100.0f; float sum 0.0f; float bg_temp; int max_idx -1; // 找到最大值和最大值索引 for (int i 0; i 64; i) { if (pixels[i] max_temp) { max_temp pixels[i]; max_idx i; } } // 計算背景溫度時去掉最大值本身 for (int i 0; i 64; i) { if (i ! max_idx) { sum pixels[i]; } } bg_temp sum / 63.0f; // 人體存在判斷 if (max_temp PRESENCE_MIN (max_temp - bg_temp) DELTA_MIN) { if (stable_count 5) stable_count; } else { stable_count 0; } *presence (stable_count 3) ? 1 : 0; *alarm 0; if (*presence max_temp ALARM_TEMP) { *alarm 1; } }這里之所以引入stable_count是為了防抖。AMG8833 單幀的讀數噪聲有 ±0.5°C 左右人站在檢測區(qū)域邊緣時max_temp可能一會 36.2°C、一會 33.8°C。如果幀幀直接判斷LED 就會瘋狂閃爍。讓“有人”狀態(tài)連續(xù)保持至少 3 幀再激活會讓輸出穩(wěn)定很多。報警閾值為什么選 37.5°C這里必須說明AMG8833 測出來的是物體表面的紅外輻射溫度不是人體的醫(yī)學體溫。實際皮膚發(fā)射率、距離、鏡片反射都會造成偏差直接拿這個數字當發(fā)燒判定完全不可靠。我這個項目只是演示“溫度超限報警”真正做醫(yī)療或工業(yè)件需要做黑體標定和距離校準。3.5 串口和 OLED 顯示串口打印是最快的調試手段。我在主循環(huán)里打印最高溫度、背景溫度、檢測狀態(tài)和報警狀態(tài)printf(Max:%.2f BG:%.2f Presence:%d Alarm:%d\r\n, max_temp, bg_temp, presence, alarm);需要注意STM32 標準庫和 HAL 庫的 printf 重定向方式不同我一般用fputc重定向到 USART1否則串口助手只能看到亂碼或者什么都沒有。OLED 部分我用的是 SSD1306 的 I2C 版本畫一個 8×8 的偽熱圖。每一格矩形對應一個像素顏色深度可以用灰度表示也可以簡單分成幾個等級低于 28°C 顯示空白28~32°C 顯示淺色高于 32°C 顯示最深色。這樣調試時眼睛掃一眼就能看出熱源在視野的哪個方向。OLED 的存在不是必須的但強烈推薦因為你調試人體檢測算法時單看一串串口數字不如看熱圖直觀位置和大小一目了然。4. 實際調試與問題排查4.1 I2C 讀取失敗怎么查我調試這個項目時第一輪最常見的現象是程序跑起來串口一點輸出都沒有HAL_I2C_Mem_Read返回 HAL_ERROR。排查順序基本固定首先確認接線。SCL 和 SDA 不要接反F103 的 PB6、PB7 要核對清楚有些核心板的絲印標得上拉/復用引腳容易看錯。然后用示波器或者邏輯分析儀看 SCL 和 SDA 波形如果根本沒有波形檢查 CubeMX 里 I2C1 是否初始化成功。其次確認地址。AMG8833 的地址 0x68 對應 AD0 接 GND0x69 對應 AD0 接 3.3V。有的模塊默認已經把 AD0 拉到高電平那你代碼里就必須用 0x69。不確定時寫一個 10 行以內的 I2C 掃描程序把總線上所有能 ACK 的地址都打印出來比自己瞎猜快得多。然后是上拉電阻。AMG8833 模塊如果沒有板上拉SCL 和 SDA 會一直處于低電平HAL_I2C 初始化的時候總線繁忙任何通信都發(fā)起不了。這時候加上 4.7k 上拉問題立刻消失。如果你在一條總線上并聯了 OLED上拉電阻算一份就夠不需要每個設備都加。4.2 溫度偏差大或者漂移怎么辦AMG8833 的測量原理是熱電堆它測的是目標與傳感器之間的紅外輻射差。這個值受距離影響極大。同樣一個人站在 10cm 處最高溫度可能 36.5°C站到 1m 處因為 60° 視場角太大熱點被攤到多個像素里單點峰值會掉到 30°C 以下。這是傳感器硬件性質決定的不是固件 bug。另外物體發(fā)射率也是個大問題。人體皮膚發(fā)射率接近 0.98但衣服、金屬、玻璃差別巨大。鋁罐、鏡面這類低發(fā)射率物體即使實際溫度 60°CAMG8833 也可能讀出 30°C。所以這個項目做人體檢測沒問題做高溫物體檢測時需要非常小心“看起來溫度不高”的反射面。如果你發(fā)現固定距離下讀數一直偏高或偏低 1~2°C可以在代碼里做一階線性校準float calibrated (raw_temp - offset) * scale;offset 和 scale 需要用已知溫度源標定比如恒溫水浴或者黑體輻射源。沒有標定條件的至少也要拿工業(yè)溫度計做幾次對比把大致偏差記下來。4.3 誤報和漏報怎么優(yōu)化誤報來源最多的是陽光直射、熱地板、暖氣片。這些物體的絕對溫度經常超過 35°C而且與周圍背景溫差也可能超過 3°C。應對辦法有兩個方向一是調整檢測區(qū)域只用畫面中間一部分像素做判斷避開邊緣的干擾源二是對時間序列做濾波比如取最近 5 幀的最高溫度做移動平均單幀的異常尖峰就不會直接觸發(fā)報警。漏報主要發(fā)生在目標太遠、衣物太厚或者環(huán)境溫度接近體表溫度的時候。我在項目里加了一個“溫差閾值”可配置參數默認 3°C但如果你在夏天用可以適當調低到 1.5°C。需要說明的是環(huán)境溫度 36°C 時人體表面溫度可能只有 34°C單純靠熱成像做人體檢測的可靠性會明顯下降這種情況最好配合 PIR 或毫米波雷達做多傳感器融合。4.4 常見問題速查表現象可能原因處理方法串口無輸出printf 未重定向檢查 fputc 重定向代碼I2C 讀回 HAL_ERROR地址錯誤/接線錯誤/總線上拉缺失I2C 掃描、檢查 AD0、補上拉溫度整體偏高目標距離近、發(fā)射率高做線性校準或標定溫度整體偏低目標距離遠、視野攤開縮小檢測范圍或用最高溫附近 3×3 平均報警頻繁誤觸發(fā)環(huán)境高溫/陽光干擾調整動態(tài)溫差閾值、加濾波蜂鳴器一響就死機蜂鳴器電流拉低電壓改用三極管驅動避免 GPIO 直連大電流5. 幾個可以繼續(xù)擴展的方向5.1 升級更高分辨率的傳感器AMG8833 只有 8×8做原型驗證夠了但如果想判斷更精確的位置或者識別熱源的輪廓可以考慮換到MLX90640。這是一個 32×24 的紅外陣列像素數是 AMG8833 的 12 倍數據量也相應變大。F103 的 I2C 吞吐量可能會比較緊張建議用 SPI 接口版本或者換更高主頻的芯片。代碼結構上只需要改傳感器讀取部分人體檢測和報警邏輯基本可以復用。5.2 加無線模塊做遠程報警這個報警器如果固定在某一個地方意義有限。把它接上 ESP8266 或 ESP32通過 WiFi 把溫度數據傳到手機就能變成一個帶遠程通知的“熱源看護”設備。做遠程方案時注意電源隔離和地線共地否則無線模塊發(fā)射電流的瞬間會把 AMG8833 的電源拉偏導致溫度讀數抖動。5.3 與上位機聯動生成熱力圖我現在調試時主要的可視化工具還是 OLED 和串口數據量大了之后明顯不夠用??梢园?64 個浮點溫度通過串口或者 USB 轉發(fā)到 PC用 Python 的 matplotlib 畫成偽彩色熱力圖甚至加上實時動態(tài)刷新。這樣不但調試更舒服發(fā)布演示的時候效果也更好。上位機的核心就是解析串口協議把收到的 64 字節(jié)還原成 8×8 矩陣再用imshow繪制即可。5.4 從“會響”到“有用”的工程化改造報警器能響只是第一步。實際落地到養(yǎng)殖場、機房、倉庫這類環(huán)境還需要考慮傳感器安裝高度、檢測距離、環(huán)境溫濕度補償、邊緣計算節(jié)點聯網等問題。AMG8833 的低成本優(yōu)勢在批量場景里很明顯但它的精度確實有限做最終產品前一定要對這個硬性條件有清晰認知別拿它做醫(yī)療級體溫檢測也別指望 8×8 分辨率能識別出人臉。我個人在實際調試中發(fā)現這個項目最值錢的部分不是硬件本身而是你開始認真處理“一坨溫度數據”的過程排序、濾波、閾值判斷、時序穩(wěn)定、多設備協調。這些思路放到任何傳感器套件里都通用。最后分享一個小技巧報警閾值不要寫死在代碼里做成一個全局變量再用串口協議遠程調整。調試的時候你會感謝自己這個決定不用每次改一個數字就重新編譯燒錄一次。