robot-notes /核心/硬體/編碼器
編碼器(Encoder)
編碼器是機器人「知道輪子轉了多少」的眼睛,odometry(里程定位)與閉迴路速度控制都靠它。本篇講霍爾編碼器與增量式 A/B 相的原理,以及 STM32F4 如何在硬體層自動數脈衝、軟體怎麼讀。
章節編號沿用原始《送餐機器人基礎原理補充》,方便與舊文件對照(故本檔編號不連續,如 §1→§5→§10,非缺漏)。 延伸閱讀:馬達與 FOC、下位機運動控制、定位
11. 霍爾編碼器:原理與 STM32F4 連接
11.1 原理:三顆霍爾開關 + 轉子磁鐵
先把兩個之後一路要用的詞釘住,不然後面的數字會看不懂。
極對數:轉子上那圈永久磁鐵是 N/S 交替排列的,成對算。輪轂馬達常見 15 對。
電氣角:磁場的圖樣每經過一對磁鐵就重複一次。所以輪子在物理上轉一圈(機械角 360°),馬達在電上經歷的是 15 圈——這個放大過的角度叫電氣角,電氣角 = 機械角 × 極對數。
為什麼要分這兩種角度:馬達的控制邏輯(何時該換相、Park 變換要轉多少)看的全是磁場的相對位置,也就是電氣角;而 odometry 要的是輪子實際轉了多少,是機械角。兩者差 15 倍,搞混就是 15 倍的誤差。 下面除非特別說明,講的都是電氣角。
BLDC 定子上嵌三顆霍爾開關(HA、HB、HC),彼此相隔 120° 電氣角。轉子永磁體掃過時,每顆輸出 0 或 1(N 極靠近 = 1)。三個位元組合出 8 種狀態,其中 000、111 物理上不會出現,有效狀態 6 個,轉子每轉過 60° 電氣角切換一次:
正轉狀態序列(典型):
HA HB HC 狀態值
1 0 1 → 5
1 0 0 → 4 每個箭頭 = 60° 電氣角
1 1 0 → 6 反轉就是同一序列倒著走
0 1 0 → 2 → 看「下一個狀態」就知道方向
0 1 1 → 3
0 0 1 → 1
(回到 5,循環)
解析度換算(關鍵):6 個狀態是「每電氣圈」;機械一圈 = 電氣圈數 × 極對數。輪轂馬達極對數多(常見 15 對),所以:
每機械圈狀態數 = 6 × 15 = 90 → 角解析度 = 360°/90 = 4°
輪徑 15cm → 一格 ≈ 5.2mm 地面距離
對比 §1.5:這就是「霍爾 = 低解析度編碼器」的由來——做速度閉迴路夠用(中高速時),做精細 odometry 偏粗,所以講究的方案會在輪轂馬達內加裝磁編碼器。
它的雙重身分:同一組訊號,既給驅動器做換相(知道該給哪兩相通電),又可當編碼器(數狀態變化次數 = 角度;量狀態間隔時間 = 速度)。
11.2 接線:先弄清楚訊號從哪裡出來
輪轂馬達引出線通常是「3 粗 + 5 細」:
3 條粗線:U / V / W 三相動力線 → 接 FOC 驅動器功率端
5 條細線:VCC(常見 5V)、GND、HA、HB、HC → 訊號線
架構決定接到哪:
| 方案 | 霍爾線接到 | STM32 怎麼拿到速度 |
|---|---|---|
| 買現成 FOC 驅動器(本專案建議) | 接驅動器,它自己用來換相+測速 | 走 CAN 讀驅動器回報的轉速/位置——STM32 根本不直接接霍爾線 |
| 自研驅動 / 額外獨立測速 | 接 STM32 | 下述兩種讀法 |
以下 §11.3 適用於第二種情境,或單純理解原理。
11.3 STM32F4 硬體連接
接線要點:
- 上拉電阻必要:霍爾開關輸出幾乎都是 open-collector / open-drain(只能拉低,不能推高),不上拉就永遠讀到不定值。上拉到 3.3V(即使霍爾供電 5V,open-drain 輸出上拉到 3.3V 就安全;若該霍爾板是推挽 5V 輸出,則要確認所用腳位為 5V-tolerant)。
- 訊號線跟三相動力線分開走線/隔開,動力線的 PWM 開關噪聲很容易耦合進來;必要時霍爾線用遮蔽線。
- 三個訊號接到同一顆 Timer 的 CH1/CH2/CH3(如 TIM4 的 PB6/PB7/PB8),才能用下面的硬體霍爾介面模式。
11.4 軟體讀法(兩種)
方法 A:Timer 霍爾感測器介面模式(硬體支援,建議)
STM32 Timer 有專門的 Hall sensor interface:把 CH1/CH2/CH3 三路訊號硬體 XOR 成一路接到內部 TI1,任何一顆霍爾翻轉 → 產生一次 capture 事件 + 計數器自動歸零:
- HAL 初始化:
HAL_TIMEx_HallSensor_Init()(內部就是設TIM_TI1SELECTION_XORCOMBINATION+ reset 觸發模式)。 -
速度 = 60° 電氣角 ÷ capture 到的時間間隔(再除以極對數換成機械角速度)。量「狀態間隔時間」這種 T 法測速在低速時特別準(狀態少但每段時間長,正合適霍爾這種低解析度訊號)。
為什麼低速用 T 法、高速用 M 法?第一性原理是「哪邊量到的數多就用哪邊」:低速時固定時間窗內脈衝太少(M 法數 1~2 格,少一格就差很多),但每段間隔時間很長,改量時間(T 法)反而準;高速則相反,脈衝多、時間段短,改數脈衝(M 法)。
- 方向:capture 中斷裡讀三支腳的當下狀態,跟上一個狀態比對序列順序。
- 雜訊處理:設定輸入濾波器
IC1Filter(數位去抖),對馬達旁的環境很重要。
方法 B:三支 GPIO + EXTI 中斷(簡單直觀)
三支腳都設「雙緣觸發 EXTI」,中斷裡讀出 3-bit 狀態,查表:
// 正轉序列 5→4→6→2→3→1,做成查找表
static const int8_t hall_dir[8][8] = { /* [prev][curr] = +1 / -1 / 0(非法跳變) */ };
void EXTI_IRQHandler(void) {
uint8_t curr = (HA << 2) | (HB << 1) | HC;
position += hall_dir[prev][curr]; // 累積 count → odometry
prev = curr; // 非法跳變(跳 2 格)→ 記異常計數
}
取捨:方法 B 好懂、好移植,但高轉速時中斷頻率 = 6 × 極對數 × 轉速(15 對極、300 RPM → 每輪每秒 2700 次中斷),兩顆輪子加起來 CPU 負擔可觀;方法 A 把計時歸零全交給硬體,CPU 只在需要時讀結果。正式韌體用方法 A,快速驗證接線用方法 B。
11.5 與本專案架構的對應
再強調一次邊界:採用「現成 FOC 驅動器 + CAN」的建議架構下,霍爾線接驅動器,STM32 不碰這五條線——轉速與累積位置由驅動器經 CAN 回報,STM32 拿來做運動學正解與 odometry 即可。本節的價值在於:(1) 看懂驅動器規格書裡的霍爾參數(極對數、霍爾角度 120°/60°);(2) 現場除錯時能用三用電表/邏輯分析儀直接驗證霍爾訊號;(3) 若日後自研驅動或加裝獨立編碼器,知道從哪裡下手。
19. Encoder 圖解:增量式 A/B 相如何運作
19.1 光學增量式編碼器的構造
磁編碼器原理相同,只是把「光+碼盤」換成「磁鐵+磁感應晶片」,抗油污粉塵(輪轂馬達內建的就是這種);霍爾(§11)則可視為極粗的磁編碼器。
19.2 為什麼要兩個訊號(A/B 相)?——方向
A、B 兩個接收器在空間上錯開 1/4 個縫距,所以輸出方波相位差 90°:
只有一相只能數「轉了多少」,不知道往哪轉;90° 相位差就是方向資訊。
19.3 ×4 解碼:解析度免費變四倍
一個縫距內,A/B 共有 4 個邊緣(A↑、B↑、A↓、B↓),每個邊緣都計一次數:
規格書寫「1024 PPR (pulses per revolution)」,經 ×4 解碼後實際可用 4096 CPR (counts per revolution)——§10.3 粗算用的 4096 就是這麼來的。
19.4 增量式 vs 絕對式(知道差別即可)
| 增量式(A/B/Z) | 絕對式(§10 伺服那種 17-bit) | |
|---|---|---|
| 開機時知道角度嗎 | ✗ 只知道「之後轉了多少」 | ✓ 每個角度有唯一編碼 |
| 訊號 | 兩路方波 | 數位介面(SSI/BiSS) |
| 價格 | 低 | 高 |
| 底盤適用 | ✓ 速度環+odometry 都只需要「增量」 | 不必要 |
底盤只關心轉速與位移增量,開機絕對角度無所謂——所以增量式就是正解。
20. Encoder 回授與下位機:STM32 程式實例
20.1 回授迴路全貌(資料在下位機裡怎麼流)
encoder 在這個迴路裡身兼兩職:對內當 PID 的回授(快迴路,留在下位機),對外供 odometry(慢資料,上報上位機)。這就是「encoder 回授跟 STM32 的關聯」的全貌。
20.2 第一步:讓硬體自己數,CPU 不介入
STM32 Timer 的 encoder mode 把 §19.3 的 ×4 解碼做在硬體裡:A/B 接同一顆 Timer 的 CH1/CH2,計數器 CNT 自動加減(正轉加、反轉減),CPU 完全不用處理每個脈衝:
// 左輪 encoder:TIM3,A 相 = PA6 (CH1),B 相 = PA7 (CH2)
void Encoder_Init(void)
{
TIM_Encoder_InitTypeDef enc = {0};
htim3.Instance = TIM3;
htim3.Init.Period = 0xFFFF; // 16-bit 計數器讓它自由迴繞
enc.EncoderMode = TIM_ENCODERMODE_TI12; // A/B 雙緣 ×4 解碼
enc.IC1Filter = 6; // 數位濾波去抖(馬達旁必加)
enc.IC2Filter = 6;
enc.IC1Polarity = TIM_ICPOLARITY_RISING;
enc.IC2Polarity = TIM_ICPOLARITY_RISING;
HAL_TIM_Encoder_Init(&htim3, &enc);
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
}
20.3 第二步:100Hz 控制任務(回授真正發生的地方)
#define DT 0.01f // 100Hz 控制週期
#define CPR 4096.0f // counts/圈(§19.3)
#define RAD_PER_CNT (2.0f * PI / CPR)
static uint16_t last_cnt;
static float integ; // PID 積分項
void ControlTask_100Hz(void) // 由 FreeRTOS 週期任務或定時中斷呼叫
{
/* ① 讀增量:int16_t 強制轉型自動處理 0xFFFF→0 的迴繞 */
uint16_t now = __HAL_TIM_GET_COUNTER(&htim3);
int16_t delta = (int16_t)(now - last_cnt); // 兩次讀數之差 = 這 10ms 轉的 counts
last_cnt = now;
/* ② counts → 實測輪速 (rad/s):這就是「回授」本身 */
float w_meas = delta * RAD_PER_CNT / DT;
/* ③ PID:讓實測追上目標(目標來自上位機指令的運動學逆解) */
float err = w_target - w_meas;
integ += err * DT;
integ = CLAMP(integ, -INTEG_MAX, INTEG_MAX); // 抗 windup(§7.3)
float u = KP * err + KI * integ; // 底盤 PI 就夠
/* ④ 輸出:經 CAN 下給 FOC 驅動器(或自驅時換算 PWM)*/
Driver_SetTorque(LEFT, u);
/* ⑤ 同一份 delta 順便餵 odometry(§4.1 公式)*/
odom_accumulate(delta /*left*/, delta_right);
}
逐行對應前面的概念:① 是 §19 的硬體計數收割;② 把「格數」換成物理量(§7.2 的單位換算鏈);③ 是 §7 說的「PID 追目標」;⑤ 是同一顆 encoder 的第二重身分。
20.4 兩個容易踩的坑
- 迴繞處理:
(int16_t)(now - last_cnt)這個慣用法,靠 16-bit 無號減法的模運算特性,即使 CNT 從 65535 跳回 0 也算出正確增量——前提是兩次讀數間轉動不超過 ±32767 counts(100Hz 下絕對夠)。寫成if (now < last) ...的手工判斷反而容易錯。 - 週期一致性:
DT必須真的是 10ms。若控制任務被高優先級中斷拖延,實測速度會抖、PID 跟著抖——這就是 §4.2 說「控制環必須固定週期」的具體原因。用硬體定時器觸發、或 FreeRTOSvTaskDelayUntil()(不是vTaskDelay())保證節拍。
注意邊界:本專案建議架構(現成 FOC 驅動器)下,「速度環在驅動器裡、encoder 接驅動器」,STM32 收到的是 CAN 回報的轉速/位置——上面 ②③ 發生在驅動器內部,STM32 只做 ①⑤ 的 odometry 部分與更外層的邏輯。若自驅(§11.2 第二種情境),這段程式就完整跑在 STM32 上。理解這段程式,兩種架構都看得懂。