🟧 micro:bit 範例:神攝手
回到神攝手產品頁 | micro:bit 讀值教學 | 讀值參考(I2C)
警告
v1 尚未上機驗證:數值排法已定案並通過電腦端測試,實機數值(fps、循線門檻、I2C 腳位)上機後可能調整。
這一頁是寫給完全沒寫過程式的人。4 個範例:色塊在哪邊、取樣點看到什麼顏色、循線小車、紅色三角形在哪邊(物體偵測)。全部用 MakeCode 網頁版的 JavaScript 寫:切到 JavaScript、把範例整個貼上就能用,也可以切回積木模式看它變成哪些積木。
重要
- 不用加擴充。每支範例最下面都有一段「神攝手 小幫手」,用 micro:bit 內建的
pins.i2c…指令跟相機講話。擴充 pxt-mbc-sensors 裡的「神攝手」積木是學校版用的,讀不到目前神攝手的值,不要混用。- 請用 通用 I2C 版韌體的相機。SPIKE、EV3、MATRIX 版韌體接 micro:bit 讀不到。
- 範例都在電腦上用 MakeCode CLI 編譯通過(V1+V2 各一次),也用假的 I2C 資料跑過邏輯;還沒接真的相機跑過。
軟體對照表
| 寫法 | micro:bit V1 | micro:bit V2 | 怎麼用 |
|---|---|---|---|
| MakeCode 網頁版(makecode.microbit.org,JavaScript) | ✔ | ✔ | 新專案 → 右上切到 JavaScript → 全選刪掉原本的內容、貼上範例 → 下載,把 .hex 拖進 MICROBIT 磁碟 |
| MakeCode 離線版(Windows/Mac App) | ✔ | ✔ | 同上 |
| Python Editor(MicroPython) | — | — | 還沒有範例 |
- 模擬器:MakeCode 模擬器沒有真的 I2C,讀到的都是 0(範例會顯示 X),要看真的數值一定要下載到 micro:bit。
- 伺服:範例 3 用兩顆連續旋轉伺服(FS90R 之類),左輪 P1、右輪 P2。伺服電源一律外接 4.8~6 V 電池盒,電池負極要和 micro:bit GND 接在一起(共地)。
開始之前:硬體關卡(上機待測)
| 關卡 | 這款產品 | 狀態 |
|---|---|---|
| 電壓 | 相機的 I2C 是 3.3 V 準位,直接接 P19/P20,不用準位轉換 | ✔ |
| 供電 | ESP32 相機+螢幕峰值超過 300 mA,一定要外接 5 V,不能從 micro:bit 3V 腳取電 | 上機待測 |
| I2C 上拉 | micro:bit 板上已有 4.7 kΩ;相機板若也有上拉,並聯後要確認低電位 ≤ 0.3×VDD | 上機待測 |
| 時脈延展 | ESP32 不能時脈延展:小幫手寫完編號會先等 2 ms 再讀,不用自己處理 | 上機待測 |
| I2C 腳位 | 相機端暫定 SDA=IO3、SCL=IO1(跟樂高串口同一對),要等實測才定案 | 上機待測 |
| 就緒時間 | 小幫手 ucReady() 讀 F3 bit0;範例開機會一直等到相機回應才開始 |
上機待測 |
接線(所有範例共用)
| 相機 | micro:bit |
|---|---|
| SDA | P20 |
| SCL | P19 |
| VCC | 外接 5 V(必要) |
| GND | GND(伺服電池負極也接這裡) |
讀不到值(一直顯示 X)時先看:SDA/SCL 有沒有接反 → 外接電源有沒有開 → 相機韌體是不是「通用 I2C 版」。
這頁會用到的數值
- 模式編號 = 相機模式 × 10 + 子選項:13=顏色追蹤 ③ 標準、21=座標取色 ① 色相、41=循線 ① 黑線。範例開機時會用 E0 命令自動切,也可以在相機選單自己切(兩邊要一樣)。
- 座標:像素 X 0~159(左 → 右)、Y 0~119(上 → 下);0=沒找到。
- 色相碼 0~255:0~240 是色相(紅 0/240、黃 40、綠 80、青 120、藍 160、紫 200);241~248 白/灰;249~255 黑。
- 循線偏移 −100~100:站在車子後面往前看,線在左=負、線在右=正。
I2C 值編號(位址 0x4C)
| 編號 | 內容 |
|---|---|
0x01 |
模式編號(例 13;物體偵測 51/52) |
0x02 |
子選項 |
0x03 |
已教幾色 |
0x04 |
這一層給幾個值 |
0x10 起 |
這個模式的值(照 讀值參考 的順序:值 1=0x10、值 2=0x11…) |
0xF0 |
裝置 ID(0x4C+3×256,3=目前的神攝手) |
0xF2 |
韌體版(100=1.0) |
0xF3 |
狀態(bit0=準備好、bit1=目前模式有找到東西) |
寫 0xE0 |
[相機模式 1~5, 子選項]:切模式(存進相機;物體偵測 [5, 1]/[5, 2]) |
寫 0xE1 |
[第幾點 1~20, X 0~159, Y 0~119]:改取樣點位置(存進相機) |
讀法(MBC 通用 I2C 協議 v2):寫 [編號] → 等 2 ms → 讀 2×k byte,就是從那個編號起連續 k 個值(int16,低位元組在前),一次最多 16 個;沒有的編號回 −32768。
小幫手函式
| 函式 | 回傳 | 意思 |
|---|---|---|
ucReadMany(id, k) |
數字陣列 | 從編號 id 起連續讀 k 個值(1~16);讀不到全部是 UC_NONE(−32768) |
ucWrite(id, params) |
— | 寫命令 [id][參數…] |
ucReady() |
true/false | 相機準備好了嗎(F3 bit0) |
ucSetMode(camMode, sub) |
— | E0:切相機模式(照順序給色號的子選項填 0) |
ucSetPoint(n, x, y) |
— | E1:把第 n 個取樣點移到 (x, y) |
ucModeNumber() |
數字 | 相機目前的模式編號 |
ucReadBlob(mode, color) |
[x, y, 寬, 高, 塊數, 面積%] |
顏色追蹤 11~14,色 1~16;x=0 是沒找到;這個模式沒給的是 −1 |
ucColorsFound(mode) |
數字 | 16 個目標色裡找到幾色 |
ucReadSpot(mode, point) |
[色相碼, 飽和度, 明度] |
座標取色 21~23,點 1~16;沒給的是 −1 |
ucReadOrder() |
數字陣列 | 照順序給色號:由左到右每塊的色相碼(最多 16 塊) |
ucOrderTotal() |
數字 | 總塊數 |
ucReadLine() |
14 個數字 | 循線:[近, 中, 遠偏移, 角度, 線寬, 有線旗標, 路口類型, 路口距離, 左緣, 右緣, 遠線寬, 標記色號, 標記條數, 離線方向] |
ucColorCode(h) |
1~9 | 色相碼 → 1 紅 2 橙 3 黃 4 綠 5 青 6 藍 7 紫 8 白/灰 9 黑 |
ucReadObject(mode, i) |
[代碼, 色號, 形狀, x, y, 寬, 高, 面積%, 角度, 頂點數, 追蹤編號] |
物體偵測 51/52,物體 1~8(1=最大);代碼=色號×10+形狀,0=沒有;模式 51 沒給的是 −1 |
ucObjectCount(mode) |
數字 | 畫面裡有幾個物體 0~8 |
ucShapeName(s) |
文字 | 形狀 0~9 → "other" "circle" "ellipse" "triangle" "square" "rectangle" "pentagon" "hexagon" "star" "trapezoid" |
範例 1:色塊在哪邊(箭頭)
這個範例會做什麼:色 1(相機教的第 1 個顏色)在畫面左邊 → LED 往左箭頭;中間 → 向上箭頭;右邊 → 往右箭頭;沒看到 → 問號。按 A 顯示面積 %。
要準備什麼
- micro:bit V1 或 V2+USB 線
- 神攝手(通用 I2C 版韌體)+外接 5 V 電源
- 一塊顏色鮮豔的積木
相機設定:在相機上教好「目標 1」(即時畫面停在目標 1 → 按 OK 取色)。相機模式 1 顏色追蹤 ・子選項 ③ 標準(模式編號 13)——程式開頭會用 E0 自動切。
怎麼接:照上面的接線表。
下載程式:ex01_track_arrow.ts(右鍵另存)→ MakeCode 新專案 → 右上切到 JavaScript → 全選刪掉、貼上 → 下載。
程式
// 神攝手 × micro:bit 範例 1:色塊在哪邊(箭頭)
// ★ 需要把相機選單切到 相機模式 1 顏色追蹤 ・子選項 ③ 標準(模式編號 13)(程式開頭也會用 E0 自動切)
// 做什麼:色 1(相機教的第 1 個顏色)在畫面左邊 → 往左箭頭、中間 → 向上箭頭、右邊 → 往右箭頭;沒找到 → 問號。按 A 顯示面積 %。
// 接線:相機 SDA → P20、SCL → P19、VCC → 外接 5 V(不要從 micro:bit 3V 取電)、GND → GND 共地。
// 準備:在相機機身教好「目標 1」的顏色(停在目標 1 按 OK 取色)。MakeCode 新專案 → 右上切到 JavaScript → 把本檔全部貼上 → 下載。不用加擴充。
// 看到什麼:開機打勾;拿目標色的積木在鏡頭前左右移動,箭頭跟著換方向;拿走 → 問號。
// ⚠ v1 尚未上機驗證:數值排法已定案並通過電腦端測試,實機數值上機後可能調整。
const MODE = 13 // 模式編號 = 相機模式 1 × 10 + 子選項 3
let ready = false
let busy = false
basic.showIcon(IconNames.SmallDiamond) // 等相機
basic.forever(function () {
if (!ready) { // 第一次:等相機準備好,再切到模式 13
if (ucReady()) {
ucSetMode(1, 3) // E0:相機模式 1、子選項 3
ready = true
basic.showIcon(IconNames.Yes)
basic.pause(500)
} else {
basic.showIcon(IconNames.No) // 沒回應:檢查 P19/P20、外接電源
basic.pause(500)
}
return
}
if (busy) {
basic.pause(100)
return
}
const b = ucReadBlob(MODE, 1) // [x, y, 寬, 高, 塊數, 面積%]
const x = b[0] // 0 = 沒找到;1~159 左 → 右
if (x <= 0) {
basic.showString("?", 0)
} else if (x < 60) { // 👉 可以改:左邊的範圍
basic.showArrow(ArrowNames.West, 0)
} else if (x > 100) { // 👉 可以改:右邊的範圍
basic.showArrow(ArrowNames.East, 0)
} else {
basic.showArrow(ArrowNames.North, 0)
}
basic.pause(50)
})
input.onButtonPressed(Button.A, function () {
busy = true
basic.showNumber(ucReadBlob(MODE, 1)[5]) // 面積:佔畫面 %(0 = 沒找到)
busy = false
})
// ==== MBC_UNICAM_MB_HELPERS_BEGIN ====
// ===== 神攝手 小幫手(MBC 通用 I2C 協議 v2):所有範例都一樣,不用改 =====
// ===== UniCam helpers (MBC common I2C protocol v2): same in every example, no need to change =====
const UC_ADDR = 0x4C // 相機的 I2C 位址 / camera I2C address
const UC_NONE = -32768 // 讀不到 / no value
// 從編號 id 起連續讀 k 個 int16(1~16);讀不到全部是 UC_NONE
function ucReadMany(id: number, k: number): number[] {
const cmd = pins.createBuffer(1)
cmd[0] = id
const r = pins.i2cWriteBuffer(UC_ADDR, cmd, false)
basic.pause(2) // 相機(ESP32)要等 2 ms 再讀
const out: number[] = []
if (r) { // 實機:非 0 = 沒有回應
for (let i = 0; i < k; i++) out.push(UC_NONE)
return out
}
const b = pins.i2cReadBuffer(UC_ADDR, 2 * k, false)
for (let i = 0; i < k; i++) out.push(b.getNumber(NumberFormat.Int16LE, 2 * i))
return out
}
// 寫命令 [id][參數…] / write a command
function ucWrite(id: number, params: number[]): void {
const b = pins.createBuffer(1 + params.length)
b[0] = id
for (let i = 0; i < params.length; i++) b[1 + i] = params[i]
pins.i2cWriteBuffer(UC_ADDR, b, false)
}
// 相機準備好了嗎(F3 bit0)/ is the camera ready
function ucReady(): boolean {
const f = ucReadMany(0xF3, 1)[0]
return f != UC_NONE && (f & 1) == 1
}
// 切相機模式(E0):camMode 1~5、sub 子選項(照順序給色號填 0;物體偵測 1 清單、2 詳細)/ switch camera mode
function ucSetMode(camMode: number, sub: number): void {
ucWrite(0xE0, [camMode, sub])
basic.pause(50)
}
// 改取樣點位置(E1):n 1~20、x 0~159、y 0~119 / move a sample spot
function ucSetPoint(n: number, x: number, y: number): void {
ucWrite(0xE1, [n, x, y])
}
// 相機目前的模式編號(例 13)/ current mode number
function ucModeNumber(): number {
return ucReadMany(0x01, 1)[0]
}
// 顏色追蹤每色幾個值 / values per colour (modes 11~14)
function ucStride(mode: number): number {
if (mode == 11) return 2
if (mode == 12) return 3
if (mode == 13) return 4
if (mode == 14) return 6
return 0
}
// 色 color(1~16)的色塊:[x, y, 寬, 高, 塊數, 面積%],x = 0 = 沒找到;這個模式沒給的是 -1
// blob of colour: [x, y, w, h, count, area%]; x = 0 = not found; -1 = not in this mode
function ucReadBlob(mode: number, color: number): number[] {
const k = ucStride(mode)
const d = ucReadMany(0x10 + (color - 1) * k, k)
if (mode == 11) return [d[0], d[1], -1, -1, -1, -1]
if (mode == 12) return [d[0], d[1], -1, -1, d[2], -1]
if (mode == 13) return [d[0], d[1], -1, -1, d[3], d[2]]
return [d[0], d[1], d[2], d[3], d[4], d[5]]
}
// 16 個目標色裡找到幾色 / colours found (modes 11~14)
function ucColorsFound(mode: number): number {
return ucReadMany(0x10 + 16 * ucStride(mode), 1)[0]
}
// 取樣點 point(1~16):[色相碼, 飽和度, 明度];沒給的是 -1 / spot: [hue, sat, val]
function ucReadSpot(mode: number, point: number): number[] {
const k = mode - 20
const d = ucReadMany(0x10 + (point - 1) * k, k)
return [d[0], k >= 2 ? d[1] : -1, k >= 3 ? d[2] : -1]
}
// 照順序給色號:由左到右每塊的色相碼(最多 16 塊)/ hue codes left to right
function ucReadOrder(): number[] {
const total = ucReadMany(0x20, 1)[0]
const d = ucReadMany(0x10, 16)
if (total == UC_NONE) return []
return d.slice(0, Math.min(16, total))
}
// 總塊數 / total blocks (mode 30)
function ucOrderTotal(): number {
return ucReadMany(0x20, 1)[0]
}
// 循線 14 個值:[近, 中, 遠偏移, 角度, 線寬, 有線旗標, 路口類型, 路口距離, 左緣, 右緣, 遠線寬, 標記色號, 標記條數, 離線方向]
// line values (near/mid/far offset: line on the left = negative)
function ucReadLine(): number[] {
return ucReadMany(0x10, 14)
}
// 物體偵測每個物體幾個值:51 → 4(色號 形狀 X Y)、52 → 10 / values per object (modes 51, 52)
function ucObjStride(mode: number): number {
if (mode == 51) return 4
if (mode == 52) return 10
return 0
}
// 物體 i(1~8,1 = 面積最大):[代碼, 色號, 形狀, x, y, 寬, 高, 面積%, 角度, 頂點數, 追蹤編號]
// 代碼 = 色號×10+形狀,0 = 沒有;模式 51 沒給的是 -1
// object i: [code, colour, shape, x, y, w, h, area%, angle, vertices, trackId]; -1 = not in mode 51
function ucReadObject(mode: number, i: number): number[] {
const k = ucObjStride(mode)
const d = ucReadMany(0x10 + (i - 1) * k, k)
const code = d[0] > 0 ? d[0] * 10 + d[1] : 0
if (mode == 51) return [code, d[0], d[1], d[2], d[3], -1, -1, -1, -1, -1, -1]
return [code, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9]]
}
// 畫面裡有幾個物體 0~8 / number of objects in view (modes 51, 52)
function ucObjectCount(mode: number): number {
return ucReadMany(0x10 + 8 * ucObjStride(mode), 1)[0]
}
// 形狀編號 → 英文名稱 / shape number -> name
function ucShapeName(s: number): string {
const n = ["other", "circle", "ellipse", "triangle", "square", "rectangle", "pentagon", "hexagon", "star", "trapezoid"]
return (s >= 0 && s <= 9) ? n[s] : "?"
}
// 色相碼 → 顏色代號:1 紅 2 橙 3 黃 4 綠 5 青 6 藍 7 紫 8 白/灰 9 黑
function ucColorCode(h: number): number {
if (h > 248) return 9
if (h > 240) return 8
if (h < 12 || h >= 220) return 1
if (h < 28) return 2
if (h < 56) return 3
if (h < 100) return 4
if (h < 140) return 5
if (h < 180) return 6
return 7
}
// ==== MBC_UNICAM_MB_HELPERS_END ====一步一步看懂程式
- 開機顯示小菱形,
forever第一次進來先ucReady()等相機回應;回應了就ucSetMode(1, 3)切到模式 13、打勾。 ucReadBlob(MODE, 1)一次讀色 1 的 4 個值(X、Y、面積、數量),整理成[x, y, 寬, 高, 塊數, 面積%]。x是 0 → 沒看到 → 問號;小於 60 → 左;大於 100 → 右;其他 → 中間(畫面寬 160,中間是 80)。- 按 A:
[5]是面積 %,用數字顯示。
跑起來應該看到什麼
- 開機約 3 秒後打勾。
- 積木放在畫面左邊:←;正中間:↑;右邊:→;拿走:?。
- 積木越靠近鏡頭,按 A 的數字越大。
常見問題
- 一直是 X? → 相機沒回應:看接線、外接電源、韌體是不是通用 I2C 版。相機先上電、等畫面出來,再按 micro:bit 的重置鍵。
- 一直是問號? → 還沒教「目標 1」,或燈光太暗。看相機螢幕底部資訊列的 X 有沒有數字。
- 左右相反? → 相機裝反了(鏡頭朝向你):把
x < 60和x > 100的箭頭對調。
範例 2:取樣點看到什麼顏色
這個範例會做什麼:LED 顯示取樣點 S1 看到的顏色字母:R 紅、O 橙、Y 黃、G 綠、C 青、B 藍、P 紫、W 白/灰、K 黑。按 A 用 E1 命令把 S1 移到畫面正中間;按 B 顯示色相碼數字。
要準備什麼:範例 1 的東西+紅、黃、綠、藍、白、黑的積木或色紙。
相機設定:相機模式 2 座標取色 ・子選項 ① 色相(模式編號 21)——程式開頭會自動切。
怎麼接:同範例 1。
下載程式:ex02_spot_color.ts(右鍵另存)→ 貼進 MakeCode JavaScript → 下載。
程式
// 神攝手 × micro:bit 範例 2:取樣點看到什麼顏色
// ★ 需要把相機選單切到 相機模式 2 座標取色 ・子選項 ① 色相(模式編號 21)(程式開頭也會用 E0 自動切)
// 做什麼:LED 顯示取樣點 S1 看到的顏色字母:R 紅、O 橙、Y 黃、G 綠、C 青、B 藍、P 紫、W 白/灰、K 黑。
// 按 A:用 E1 命令把 S1 移到畫面正中間(X 80、Y 60);按 B:顯示色相碼數字(0~255)。
// 接線:相機 SDA → P20、SCL → P19、VCC → 外接 5 V、GND → GND 共地。
// 準備:紅、黃、綠、藍、白、黑的積木或色紙。MakeCode 切到 JavaScript 貼上本檔 → 下載。不用加擴充。
// 看到什麼:先按 A,把積木放在鏡頭正中間:紅 → R、綠 → G、藍 → B、白紙 → W。
// ⚠ v1 尚未上機驗證:顏色的分界(ucColorCode)是依色相碼估的,每個場地光線不同,請按 B 看數字自己調。
const MODE = 21 // 模式編號 = 相機模式 2 × 10 + 子選項 1
const LETTERS = "?ROYGCBPWK" // ucColorCode 1~9 對應的字母
let ready = false
let busy = false
basic.showIcon(IconNames.SmallDiamond) // 等相機
basic.forever(function () {
if (!ready) { // 第一次:等相機準備好,再切到模式 21
if (ucReady()) {
ucSetMode(2, 1) // E0:相機模式 2、子選項 1
ready = true
basic.showIcon(IconNames.Yes)
basic.pause(500)
} else {
basic.showIcon(IconNames.No) // 沒回應:檢查 P19/P20、外接電源
basic.pause(500)
}
return
}
if (busy) {
basic.pause(100)
return
}
const h = ucReadSpot(MODE, 1)[0] // S1 的色相碼 0~255
if (h == UC_NONE) {
basic.showString("-", 0) // 讀不到
} else {
basic.showString(LETTERS.charAt(ucColorCode(h)), 0)
}
basic.pause(100)
})
input.onButtonPressed(Button.A, function () {
busy = true
ucSetPoint(1, 80, 60) // E1:第 1 點移到 X 80、Y 60(畫面中間,會存進相機)
basic.showIcon(IconNames.Target)
basic.pause(500)
busy = false
})
input.onButtonPressed(Button.B, function () {
busy = true
basic.showNumber(ucReadSpot(MODE, 1)[0]) // 色相碼:紅 0/240、黃 40、綠 80、青 120、藍 160、紫 200;241~248 白、249~255 黑
busy = false
})
// ==== MBC_UNICAM_MB_HELPERS_BEGIN ====
// ===== 神攝手 小幫手(MBC 通用 I2C 協議 v2):所有範例都一樣,不用改 =====
// ===== UniCam helpers (MBC common I2C protocol v2): same in every example, no need to change =====
const UC_ADDR = 0x4C // 相機的 I2C 位址 / camera I2C address
const UC_NONE = -32768 // 讀不到 / no value
// 從編號 id 起連續讀 k 個 int16(1~16);讀不到全部是 UC_NONE
function ucReadMany(id: number, k: number): number[] {
const cmd = pins.createBuffer(1)
cmd[0] = id
const r = pins.i2cWriteBuffer(UC_ADDR, cmd, false)
basic.pause(2) // 相機(ESP32)要等 2 ms 再讀
const out: number[] = []
if (r) { // 實機:非 0 = 沒有回應
for (let i = 0; i < k; i++) out.push(UC_NONE)
return out
}
const b = pins.i2cReadBuffer(UC_ADDR, 2 * k, false)
for (let i = 0; i < k; i++) out.push(b.getNumber(NumberFormat.Int16LE, 2 * i))
return out
}
// 寫命令 [id][參數…] / write a command
function ucWrite(id: number, params: number[]): void {
const b = pins.createBuffer(1 + params.length)
b[0] = id
for (let i = 0; i < params.length; i++) b[1 + i] = params[i]
pins.i2cWriteBuffer(UC_ADDR, b, false)
}
// 相機準備好了嗎(F3 bit0)/ is the camera ready
function ucReady(): boolean {
const f = ucReadMany(0xF3, 1)[0]
return f != UC_NONE && (f & 1) == 1
}
// 切相機模式(E0):camMode 1~5、sub 子選項(照順序給色號填 0;物體偵測 1 清單、2 詳細)/ switch camera mode
function ucSetMode(camMode: number, sub: number): void {
ucWrite(0xE0, [camMode, sub])
basic.pause(50)
}
// 改取樣點位置(E1):n 1~20、x 0~159、y 0~119 / move a sample spot
function ucSetPoint(n: number, x: number, y: number): void {
ucWrite(0xE1, [n, x, y])
}
// 相機目前的模式編號(例 13)/ current mode number
function ucModeNumber(): number {
return ucReadMany(0x01, 1)[0]
}
// 顏色追蹤每色幾個值 / values per colour (modes 11~14)
function ucStride(mode: number): number {
if (mode == 11) return 2
if (mode == 12) return 3
if (mode == 13) return 4
if (mode == 14) return 6
return 0
}
// 色 color(1~16)的色塊:[x, y, 寬, 高, 塊數, 面積%],x = 0 = 沒找到;這個模式沒給的是 -1
// blob of colour: [x, y, w, h, count, area%]; x = 0 = not found; -1 = not in this mode
function ucReadBlob(mode: number, color: number): number[] {
const k = ucStride(mode)
const d = ucReadMany(0x10 + (color - 1) * k, k)
if (mode == 11) return [d[0], d[1], -1, -1, -1, -1]
if (mode == 12) return [d[0], d[1], -1, -1, d[2], -1]
if (mode == 13) return [d[0], d[1], -1, -1, d[3], d[2]]
return [d[0], d[1], d[2], d[3], d[4], d[5]]
}
// 16 個目標色裡找到幾色 / colours found (modes 11~14)
function ucColorsFound(mode: number): number {
return ucReadMany(0x10 + 16 * ucStride(mode), 1)[0]
}
// 取樣點 point(1~16):[色相碼, 飽和度, 明度];沒給的是 -1 / spot: [hue, sat, val]
function ucReadSpot(mode: number, point: number): number[] {
const k = mode - 20
const d = ucReadMany(0x10 + (point - 1) * k, k)
return [d[0], k >= 2 ? d[1] : -1, k >= 3 ? d[2] : -1]
}
// 照順序給色號:由左到右每塊的色相碼(最多 16 塊)/ hue codes left to right
function ucReadOrder(): number[] {
const total = ucReadMany(0x20, 1)[0]
const d = ucReadMany(0x10, 16)
if (total == UC_NONE) return []
return d.slice(0, Math.min(16, total))
}
// 總塊數 / total blocks (mode 30)
function ucOrderTotal(): number {
return ucReadMany(0x20, 1)[0]
}
// 循線 14 個值:[近, 中, 遠偏移, 角度, 線寬, 有線旗標, 路口類型, 路口距離, 左緣, 右緣, 遠線寬, 標記色號, 標記條數, 離線方向]
// line values (near/mid/far offset: line on the left = negative)
function ucReadLine(): number[] {
return ucReadMany(0x10, 14)
}
// 物體偵測每個物體幾個值:51 → 4(色號 形狀 X Y)、52 → 10 / values per object (modes 51, 52)
function ucObjStride(mode: number): number {
if (mode == 51) return 4
if (mode == 52) return 10
return 0
}
// 物體 i(1~8,1 = 面積最大):[代碼, 色號, 形狀, x, y, 寬, 高, 面積%, 角度, 頂點數, 追蹤編號]
// 代碼 = 色號×10+形狀,0 = 沒有;模式 51 沒給的是 -1
// object i: [code, colour, shape, x, y, w, h, area%, angle, vertices, trackId]; -1 = not in mode 51
function ucReadObject(mode: number, i: number): number[] {
const k = ucObjStride(mode)
const d = ucReadMany(0x10 + (i - 1) * k, k)
const code = d[0] > 0 ? d[0] * 10 + d[1] : 0
if (mode == 51) return [code, d[0], d[1], d[2], d[3], -1, -1, -1, -1, -1, -1]
return [code, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9]]
}
// 畫面裡有幾個物體 0~8 / number of objects in view (modes 51, 52)
function ucObjectCount(mode: number): number {
return ucReadMany(0x10 + 8 * ucObjStride(mode), 1)[0]
}
// 形狀編號 → 英文名稱 / shape number -> name
function ucShapeName(s: number): string {
const n = ["other", "circle", "ellipse", "triangle", "square", "rectangle", "pentagon", "hexagon", "star", "trapezoid"]
return (s >= 0 && s <= 9) ? n[s] : "?"
}
// 色相碼 → 顏色代號:1 紅 2 橙 3 黃 4 綠 5 青 6 藍 7 紫 8 白/灰 9 黑
function ucColorCode(h: number): number {
if (h > 248) return 9
if (h > 240) return 8
if (h < 12 || h >= 220) return 1
if (h < 28) return 2
if (h < 56) return 3
if (h < 100) return 4
if (h < 140) return 5
if (h < 180) return 6
return 7
}
// ==== MBC_UNICAM_MB_HELPERS_END ====一步一步看懂程式
- 開機等相機、
ucSetMode(2, 1)切到模式 21(每個取樣點給 1 個值:色相碼)。 ucReadSpot(MODE, 1)[0]:S1 的色相碼 0~255。ucColorCode(h)把色相碼分成 1~9 類,LETTERS.charAt(...)取出對應的字母。- 按 A:
ucSetPoint(1, 80, 60)送 E1 命令,S1 移到畫面中間(會存進相機,下次開機還在)。相機螢幕上的十字會跟著移。 - 按 B:顯示色相碼數字,用來自己調顏色的分界。
跑起來應該看到什麼
- 按 A 後相機螢幕上 S1 的十字跑到正中間、micro:bit 顯示靶心。
- 紅積木 → R;黃 → Y;綠 → G;藍 → B;白紙 → W;黑積木 → K。
常見問題
- 紅色顯示 O、藍色顯示 C? → 每個場地的燈光不同。按 B 看數字,再改小幫手外面的判斷(例如自己寫
if (h < 20) …);不要改小幫手本身。 - 白紙顯示成彩色? → 白紙在暖色燈下會偏黃。到相機 SET 選單做「白色校正」。
範例 3:循線小車(PD 循線+路口停車)
這個範例會做什麼:按 A 開始沿黑線走:線偏左就往左修、偏右就往右修(近處和遠處偏移一起看,彎道會比較早轉);遇到十字路口或 T 字路口就停車、顯示方塊;看不到線就往最後看到線的那一邊原地轉。按 B 停。
要準備什麼
- 範例 1 的東西
- 兩輪小車:左右各一顆連續旋轉伺服+ 4.8~6 V 電池盒
- 白底黑線的場地(線寬約 2 cm),中間做一個十字路口
相機設定:相機裝在車頭、斜斜往前下方看地面。相機模式 4 循線 ・子選項 ① 黑線(模式編號 41)——程式開頭會自動切;白線把 ucSetMode(4, 1) 改成 (4, 2)、色線改 (4, 3)。
怎麼接:相機同範例 1;左輪伺服訊號 → P1、右輪 → P2;伺服電源接電池盒,電池負極接 micro:bit GND。
下載程式:ex03_line_follow.ts(右鍵另存)→ 貼進 MakeCode JavaScript → 下載。
程式
// 神攝手 × micro:bit 範例 3:循線小車(近/遠偏移 PD 循線,遇到路口停車)
// ★ 需要把相機選單切到 相機模式 4 循線 ・子選項 ① 黑線(模式編號 41)(程式開頭也會用 E0 自動切;白線改 ②、色線改 ③)
// 做什麼:按 A 開始沿黑線走:線偏左就往左修、偏右就往右修(近處+遠處偏移一起看,轉彎比較早反應);
// 遇到十字路口或 T 字路口就停車、顯示方塊;看不到線就往最後看到線的那一邊原地轉。按 B 停。
// 接線:相機 SDA → P20、SCL → P19、VCC → 外接 5 V、GND → GND。左輪連續旋轉伺服訊號 → P1、右輪 → P2;
// 伺服電源 4.8~6 V 電池盒,電池負極接 micro:bit GND。
// 準備:兩輪小車,相機裝在車頭、斜斜往前下方看地面;白底黑線的場地(線寬約 2 cm)。
// 方向:站在車子後面往前看,線在左 = 負、線在右 = 正。
// ⚠ v1 尚未上機驗證:BASE、KP、KD 是建議值,請依你的車子調。
const BASE = 40 // 👉 可以改:直走速度(-100~100)
const KP = 0.5 // 👉 可以改:偏多少就轉多少(P)
const KD = 0.3 // 👉 可以改:偏移變化快就多修一點(D)
let ready = false
let running = false
let lastErr = 0
// 左輪伺服 P1、右輪伺服 P2(連續旋轉伺服);left/right = −100~100,正 = 前進
function drive(left: number, right: number): void {
left = Math.constrain(left, -100, 100)
right = Math.constrain(right, -100, 100)
pins.servoWritePin(AnalogPin.P1, Math.round(90 + left * 0.9)) // 90 = 停、180 = 全速前進、0 = 全速後退
pins.servoWritePin(AnalogPin.P2, Math.round(90 - right * 0.9)) // 右輪裝的方向相反,所以用減的
}
input.onButtonPressed(Button.A, function () {
running = true
lastErr = 0
basic.showIcon(IconNames.Happy, 0)
})
input.onButtonPressed(Button.B, function () {
running = false
drive(0, 0)
basic.showIcon(IconNames.No, 0)
})
drive(0, 0)
basic.showIcon(IconNames.SmallDiamond) // 等相機
basic.forever(function () {
if (!ready) { // 第一次:等相機準備好,再切到模式 41
if (ucReady()) {
ucSetMode(4, 1) // E0:相機模式 4、子選項 1(黑線)
ready = true
basic.showIcon(IconNames.Yes)
} else {
basic.showIcon(IconNames.No) // 沒回應:檢查 P19/P20、外接電源
basic.pause(500)
}
return
}
if (!running) {
basic.pause(100)
return
}
const v = ucReadLine() // [近, 中, 遠, 角度, 線寬, 有線旗標, 路口類型, 路口距離, …, 離線方向]
if (v[0] == UC_NONE) { // 讀不到相機:安全第一,停車
drive(0, 0)
basic.showIcon(IconNames.No, 0)
return
}
const junction = v[6] // 0 無 1 十字 2 T 3 左 4 右 5 盡頭
if (junction == 1 || junction == 2) { // 十字或 T 字路口 → 停車
drive(0, 0)
running = false
basic.showIcon(IconNames.Square, 0)
return
}
if (v[5] == 0) { // 有線旗標 = 0:近中遠都沒看到線
if (v[13] == 1) {
drive(-30, 30) // 最後在左邊看到線 → 原地左轉找線
} else {
drive(30, -30) // 最後在右邊(或不知道)→ 原地右轉
}
return
}
const err = v[0] * 0.7 + v[2] * 0.3 // 近處偏移占 7 成、遠處占 3 成(線在左 = 負)
const turn = KP * err + KD * (err - lastErr) // PD:偏多少修多少+偏移變多快
lastErr = err
drive(BASE + turn, BASE - turn) // 線在左(負)→ 左輪慢、右輪快 → 往左修
basic.pause(20)
})
// ==== MBC_UNICAM_MB_HELPERS_BEGIN ====
// ===== 神攝手 小幫手(MBC 通用 I2C 協議 v2):所有範例都一樣,不用改 =====
// ===== UniCam helpers (MBC common I2C protocol v2): same in every example, no need to change =====
const UC_ADDR = 0x4C // 相機的 I2C 位址 / camera I2C address
const UC_NONE = -32768 // 讀不到 / no value
// 從編號 id 起連續讀 k 個 int16(1~16);讀不到全部是 UC_NONE
function ucReadMany(id: number, k: number): number[] {
const cmd = pins.createBuffer(1)
cmd[0] = id
const r = pins.i2cWriteBuffer(UC_ADDR, cmd, false)
basic.pause(2) // 相機(ESP32)要等 2 ms 再讀
const out: number[] = []
if (r) { // 實機:非 0 = 沒有回應
for (let i = 0; i < k; i++) out.push(UC_NONE)
return out
}
const b = pins.i2cReadBuffer(UC_ADDR, 2 * k, false)
for (let i = 0; i < k; i++) out.push(b.getNumber(NumberFormat.Int16LE, 2 * i))
return out
}
// 寫命令 [id][參數…] / write a command
function ucWrite(id: number, params: number[]): void {
const b = pins.createBuffer(1 + params.length)
b[0] = id
for (let i = 0; i < params.length; i++) b[1 + i] = params[i]
pins.i2cWriteBuffer(UC_ADDR, b, false)
}
// 相機準備好了嗎(F3 bit0)/ is the camera ready
function ucReady(): boolean {
const f = ucReadMany(0xF3, 1)[0]
return f != UC_NONE && (f & 1) == 1
}
// 切相機模式(E0):camMode 1~5、sub 子選項(照順序給色號填 0;物體偵測 1 清單、2 詳細)/ switch camera mode
function ucSetMode(camMode: number, sub: number): void {
ucWrite(0xE0, [camMode, sub])
basic.pause(50)
}
// 改取樣點位置(E1):n 1~20、x 0~159、y 0~119 / move a sample spot
function ucSetPoint(n: number, x: number, y: number): void {
ucWrite(0xE1, [n, x, y])
}
// 相機目前的模式編號(例 13)/ current mode number
function ucModeNumber(): number {
return ucReadMany(0x01, 1)[0]
}
// 顏色追蹤每色幾個值 / values per colour (modes 11~14)
function ucStride(mode: number): number {
if (mode == 11) return 2
if (mode == 12) return 3
if (mode == 13) return 4
if (mode == 14) return 6
return 0
}
// 色 color(1~16)的色塊:[x, y, 寬, 高, 塊數, 面積%],x = 0 = 沒找到;這個模式沒給的是 -1
// blob of colour: [x, y, w, h, count, area%]; x = 0 = not found; -1 = not in this mode
function ucReadBlob(mode: number, color: number): number[] {
const k = ucStride(mode)
const d = ucReadMany(0x10 + (color - 1) * k, k)
if (mode == 11) return [d[0], d[1], -1, -1, -1, -1]
if (mode == 12) return [d[0], d[1], -1, -1, d[2], -1]
if (mode == 13) return [d[0], d[1], -1, -1, d[3], d[2]]
return [d[0], d[1], d[2], d[3], d[4], d[5]]
}
// 16 個目標色裡找到幾色 / colours found (modes 11~14)
function ucColorsFound(mode: number): number {
return ucReadMany(0x10 + 16 * ucStride(mode), 1)[0]
}
// 取樣點 point(1~16):[色相碼, 飽和度, 明度];沒給的是 -1 / spot: [hue, sat, val]
function ucReadSpot(mode: number, point: number): number[] {
const k = mode - 20
const d = ucReadMany(0x10 + (point - 1) * k, k)
return [d[0], k >= 2 ? d[1] : -1, k >= 3 ? d[2] : -1]
}
// 照順序給色號:由左到右每塊的色相碼(最多 16 塊)/ hue codes left to right
function ucReadOrder(): number[] {
const total = ucReadMany(0x20, 1)[0]
const d = ucReadMany(0x10, 16)
if (total == UC_NONE) return []
return d.slice(0, Math.min(16, total))
}
// 總塊數 / total blocks (mode 30)
function ucOrderTotal(): number {
return ucReadMany(0x20, 1)[0]
}
// 循線 14 個值:[近, 中, 遠偏移, 角度, 線寬, 有線旗標, 路口類型, 路口距離, 左緣, 右緣, 遠線寬, 標記色號, 標記條數, 離線方向]
// line values (near/mid/far offset: line on the left = negative)
function ucReadLine(): number[] {
return ucReadMany(0x10, 14)
}
// 物體偵測每個物體幾個值:51 → 4(色號 形狀 X Y)、52 → 10 / values per object (modes 51, 52)
function ucObjStride(mode: number): number {
if (mode == 51) return 4
if (mode == 52) return 10
return 0
}
// 物體 i(1~8,1 = 面積最大):[代碼, 色號, 形狀, x, y, 寬, 高, 面積%, 角度, 頂點數, 追蹤編號]
// 代碼 = 色號×10+形狀,0 = 沒有;模式 51 沒給的是 -1
// object i: [code, colour, shape, x, y, w, h, area%, angle, vertices, trackId]; -1 = not in mode 51
function ucReadObject(mode: number, i: number): number[] {
const k = ucObjStride(mode)
const d = ucReadMany(0x10 + (i - 1) * k, k)
const code = d[0] > 0 ? d[0] * 10 + d[1] : 0
if (mode == 51) return [code, d[0], d[1], d[2], d[3], -1, -1, -1, -1, -1, -1]
return [code, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9]]
}
// 畫面裡有幾個物體 0~8 / number of objects in view (modes 51, 52)
function ucObjectCount(mode: number): number {
return ucReadMany(0x10 + 8 * ucObjStride(mode), 1)[0]
}
// 形狀編號 → 英文名稱 / shape number -> name
function ucShapeName(s: number): string {
const n = ["other", "circle", "ellipse", "triangle", "square", "rectangle", "pentagon", "hexagon", "star", "trapezoid"]
return (s >= 0 && s <= 9) ? n[s] : "?"
}
// 色相碼 → 顏色代號:1 紅 2 橙 3 黃 4 綠 5 青 6 藍 7 紫 8 白/灰 9 黑
function ucColorCode(h: number): number {
if (h > 248) return 9
if (h > 240) return 8
if (h < 12 || h >= 220) return 1
if (h < 28) return 2
if (h < 56) return 3
if (h < 100) return 4
if (h < 140) return 5
if (h < 180) return 6
return 7
}
// ==== MBC_UNICAM_MB_HELPERS_END ====一步一步看懂程式
drive(left, right):左右輪 −100~100 換成伺服角度(90=停);右輪裝反向,所以用減的。ucReadLine()一次讀 14 個循線值。v[0]是 −32768 → 讀不到相機 → 停車。v[6]路口類型:1 十字、2 T → 停車、running = false。v[5]有線旗標=0 → 近中遠都沒看到線 → 看v[13](最後離線方向,1=左)原地轉回去找。- 偏移
err = 近×0.7 + 遠×0.3;turn = KP×err + KD×(err − 上一次)(P=偏多少修多少、D=偏移變多快就多修一點)。 drive(BASE + turn, BASE - turn):線在左(負)→ 左輪慢、右輪快 → 往左修。
跑起來應該看到什麼:按 A(笑臉)後車子沿著線走,彎道會提早轉;走到十字路口停下來、顯示方塊。把車子拿離開線,它會原地轉回去找線。
常見問題
- 一直往外衝、修不回來? →
KP加大一點(0.5 → 0.8);左右晃很厲害 →KP減小、BASE調慢。 - 方向反了(線在左卻往右轉)? → 伺服接反了:把 P1、P2 的線對調,或把
drive裡兩行的+/-對調。 - 還沒到路口就停? → 相機看得比較遠,路口在畫面上方就會報。可以先看
v[7](路口距離 %),大於某個數字才停。
範例 4:紅色三角形在哪邊(物體偵測)
這個範例會做什麼:相機把畫面裡的物體由大到小排好(物體 1 最大),每個物體給「物體代碼」=色號 × 10 + 形狀。程式從最大的往下找「目標 1 的三角形」(代碼 13):在左邊 → 往左箭頭;中間 → 向上箭頭;右邊 → 往右箭頭;沒找到 → 問號。按 A 顯示畫面裡有幾個物體,按 B 捲動物體 1 的形狀名稱。
要準備什麼
- 範例 1 的東西
- 紅色三角形(紙板或積木);再放一個紅色圓形、別色三角形,看它會不會認錯
相機設定:在相機上把「目標 1」教成紅色(形狀可以不指定,相機會自己判斷;只有目標 1~8 會當成物體)。相機模式 5 物體偵測 ・子選項 ① 物體清單(模式編號 51)——程式開頭會用 E0 自動切。
怎麼接:照上面的接線表。
下載程式:ex04_object.ts(右鍵另存)→ 貼進 MakeCode JavaScript → 下載。
程式
// 神攝手 × micro:bit 範例 4:紅色三角形在哪邊(物體偵測)
// ★ 需要把相機選單切到 相機模式 5 物體偵測 ・子選項 ① 物體清單(模式編號 51)(程式開頭也會用 E0 自動切)
// 做什麼:相機把物體由大到小排好(物體 1 最大),每個物體給「物體代碼」=色號 × 10 + 形狀。從最大的往下找「目標 1 的三角形」(代碼 13):
// 在左邊 → 往左箭頭、中間 → 向上箭頭、右邊 → 往右箭頭;沒找到 → 問號。按 A 顯示畫面裡有幾個物體,按 B 捲動物體 1 的形狀名稱。
// 形狀:0 其他 1 圓 2 橢圓 3 三角 4 正方 5 長方 6 五邊 7 六邊 8 星 9 梯形。
// 接線:相機 SDA → P20、SCL → P19、VCC → 外接 5 V(不要從 micro:bit 3V 取電)、GND → GND 共地。
// 準備:在相機機身把「目標 1」教成紅色(形狀可以不指定)。MakeCode 新專案 → 右上切到 JavaScript → 把本檔全部貼上 → 下載。不用加擴充。
// 看到什麼:開機打勾;拿紅色三角形在鏡頭前左右移動,箭頭跟著換方向;換成圓形或別的顏色 → 問號。
// ⚠ v1 尚未上機驗證:數值排法已定案並通過電腦端測試,實機數值上機後可能調整。
const MODE = 51 // 模式編號 = 相機模式 5 × 10 + 子選項 1
const WANT = 1 * 10 + 3 // 👉 可以改:目標 1 × 10 + 形狀 3(三角);正方形 14、圓 11
let ready = false
let busy = false
basic.showIcon(IconNames.SmallDiamond) // 等相機
// 從最大的物體往下找代碼 = WANT 的,回傳它的 X(0 = 沒找到)
function findX(): number {
const n = ucObjectCount(MODE) // 畫面裡有幾個物體 0~8
for (let i = 1; i <= n && i <= 8; i++) {
const o = ucReadObject(MODE, i) // [代碼, 色號, 形狀, x, y, …]
if (o[0] == WANT) return o[3]
}
return 0
}
basic.forever(function () {
if (!ready) { // 第一次:等相機準備好,再切到模式 51
if (ucReady()) {
ucSetMode(5, 1) // E0:相機模式 5、子選項 1
ready = true
basic.showIcon(IconNames.Yes)
basic.pause(500)
} else {
basic.showIcon(IconNames.No) // 沒回應:檢查 P19/P20、外接電源
basic.pause(500)
}
return
}
if (busy) {
basic.pause(100)
return
}
const x = findX() // 0 = 沒找到;1~159 左 → 右
if (x <= 0) {
basic.showString("?", 0)
} else if (x < 60) { // 👉 可以改:左邊的範圍
basic.showArrow(ArrowNames.West, 0)
} else if (x > 100) { // 👉 可以改:右邊的範圍
basic.showArrow(ArrowNames.East, 0)
} else {
basic.showArrow(ArrowNames.North, 0)
}
basic.pause(50)
})
input.onButtonPressed(Button.A, function () {
busy = true
basic.showNumber(ucObjectCount(MODE)) // 畫面裡有幾個物體 0~8
busy = false
})
input.onButtonPressed(Button.B, function () {
busy = true
const o = ucReadObject(MODE, 1) // 物體 1(最大的)
basic.showString(o[0] > 0 ? ucShapeName(o[2]) : "-") // 形狀名稱(英文);沒有物體顯示 -
busy = false
})
// ==== MBC_UNICAM_MB_HELPERS_BEGIN ====
// ===== 神攝手 小幫手(MBC 通用 I2C 協議 v2):所有範例都一樣,不用改 =====
// ===== UniCam helpers (MBC common I2C protocol v2): same in every example, no need to change =====
const UC_ADDR = 0x4C // 相機的 I2C 位址 / camera I2C address
const UC_NONE = -32768 // 讀不到 / no value
// 從編號 id 起連續讀 k 個 int16(1~16);讀不到全部是 UC_NONE
function ucReadMany(id: number, k: number): number[] {
const cmd = pins.createBuffer(1)
cmd[0] = id
const r = pins.i2cWriteBuffer(UC_ADDR, cmd, false)
basic.pause(2) // 相機(ESP32)要等 2 ms 再讀
const out: number[] = []
if (r) { // 實機:非 0 = 沒有回應
for (let i = 0; i < k; i++) out.push(UC_NONE)
return out
}
const b = pins.i2cReadBuffer(UC_ADDR, 2 * k, false)
for (let i = 0; i < k; i++) out.push(b.getNumber(NumberFormat.Int16LE, 2 * i))
return out
}
// 寫命令 [id][參數…] / write a command
function ucWrite(id: number, params: number[]): void {
const b = pins.createBuffer(1 + params.length)
b[0] = id
for (let i = 0; i < params.length; i++) b[1 + i] = params[i]
pins.i2cWriteBuffer(UC_ADDR, b, false)
}
// 相機準備好了嗎(F3 bit0)/ is the camera ready
function ucReady(): boolean {
const f = ucReadMany(0xF3, 1)[0]
return f != UC_NONE && (f & 1) == 1
}
// 切相機模式(E0):camMode 1~5、sub 子選項(照順序給色號填 0;物體偵測 1 清單、2 詳細)/ switch camera mode
function ucSetMode(camMode: number, sub: number): void {
ucWrite(0xE0, [camMode, sub])
basic.pause(50)
}
// 改取樣點位置(E1):n 1~20、x 0~159、y 0~119 / move a sample spot
function ucSetPoint(n: number, x: number, y: number): void {
ucWrite(0xE1, [n, x, y])
}
// 相機目前的模式編號(例 13)/ current mode number
function ucModeNumber(): number {
return ucReadMany(0x01, 1)[0]
}
// 顏色追蹤每色幾個值 / values per colour (modes 11~14)
function ucStride(mode: number): number {
if (mode == 11) return 2
if (mode == 12) return 3
if (mode == 13) return 4
if (mode == 14) return 6
return 0
}
// 色 color(1~16)的色塊:[x, y, 寬, 高, 塊數, 面積%],x = 0 = 沒找到;這個模式沒給的是 -1
// blob of colour: [x, y, w, h, count, area%]; x = 0 = not found; -1 = not in this mode
function ucReadBlob(mode: number, color: number): number[] {
const k = ucStride(mode)
const d = ucReadMany(0x10 + (color - 1) * k, k)
if (mode == 11) return [d[0], d[1], -1, -1, -1, -1]
if (mode == 12) return [d[0], d[1], -1, -1, d[2], -1]
if (mode == 13) return [d[0], d[1], -1, -1, d[3], d[2]]
return [d[0], d[1], d[2], d[3], d[4], d[5]]
}
// 16 個目標色裡找到幾色 / colours found (modes 11~14)
function ucColorsFound(mode: number): number {
return ucReadMany(0x10 + 16 * ucStride(mode), 1)[0]
}
// 取樣點 point(1~16):[色相碼, 飽和度, 明度];沒給的是 -1 / spot: [hue, sat, val]
function ucReadSpot(mode: number, point: number): number[] {
const k = mode - 20
const d = ucReadMany(0x10 + (point - 1) * k, k)
return [d[0], k >= 2 ? d[1] : -1, k >= 3 ? d[2] : -1]
}
// 照順序給色號:由左到右每塊的色相碼(最多 16 塊)/ hue codes left to right
function ucReadOrder(): number[] {
const total = ucReadMany(0x20, 1)[0]
const d = ucReadMany(0x10, 16)
if (total == UC_NONE) return []
return d.slice(0, Math.min(16, total))
}
// 總塊數 / total blocks (mode 30)
function ucOrderTotal(): number {
return ucReadMany(0x20, 1)[0]
}
// 循線 14 個值:[近, 中, 遠偏移, 角度, 線寬, 有線旗標, 路口類型, 路口距離, 左緣, 右緣, 遠線寬, 標記色號, 標記條數, 離線方向]
// line values (near/mid/far offset: line on the left = negative)
function ucReadLine(): number[] {
return ucReadMany(0x10, 14)
}
// 物體偵測每個物體幾個值:51 → 4(色號 形狀 X Y)、52 → 10 / values per object (modes 51, 52)
function ucObjStride(mode: number): number {
if (mode == 51) return 4
if (mode == 52) return 10
return 0
}
// 物體 i(1~8,1 = 面積最大):[代碼, 色號, 形狀, x, y, 寬, 高, 面積%, 角度, 頂點數, 追蹤編號]
// 代碼 = 色號×10+形狀,0 = 沒有;模式 51 沒給的是 -1
// object i: [code, colour, shape, x, y, w, h, area%, angle, vertices, trackId]; -1 = not in mode 51
function ucReadObject(mode: number, i: number): number[] {
const k = ucObjStride(mode)
const d = ucReadMany(0x10 + (i - 1) * k, k)
const code = d[0] > 0 ? d[0] * 10 + d[1] : 0
if (mode == 51) return [code, d[0], d[1], d[2], d[3], -1, -1, -1, -1, -1, -1]
return [code, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9]]
}
// 畫面裡有幾個物體 0~8 / number of objects in view (modes 51, 52)
function ucObjectCount(mode: number): number {
return ucReadMany(0x10 + 8 * ucObjStride(mode), 1)[0]
}
// 形狀編號 → 英文名稱 / shape number -> name
function ucShapeName(s: number): string {
const n = ["other", "circle", "ellipse", "triangle", "square", "rectangle", "pentagon", "hexagon", "star", "trapezoid"]
return (s >= 0 && s <= 9) ? n[s] : "?"
}
// 色相碼 → 顏色代號:1 紅 2 橙 3 黃 4 綠 5 青 6 藍 7 紫 8 白/灰 9 黑
function ucColorCode(h: number): number {
if (h > 248) return 9
if (h > 240) return 8
if (h < 12 || h >= 220) return 1
if (h < 28) return 2
if (h < 56) return 3
if (h < 100) return 4
if (h < 140) return 5
if (h < 180) return 6
return 7
}
// ==== MBC_UNICAM_MB_HELPERS_END ====一步一步看懂程式
WANT = 1 × 10 + 3:物體代碼=色號 × 10 + 形狀,目標 1 的三角形=13(正方形 14、圓 11)。ucObjectCount(MODE):畫面裡有幾個物體(0~8)。ucReadObject(MODE, i)→[代碼, 色號, 形狀, x, y, …];物體 1 最大,所以從 1 往下找到的第一個就是最大的。模式 51 只給代碼、色號、形狀、X、Y,後面幾格是 −1。ucShapeName(o[2]):形狀編號換成英文名稱(LED 只能捲動英文)。
跑起來應該看到什麼:開機打勾;紅色三角形在左邊 → 往左箭頭、在右邊 → 往右箭頭;換成紅色圓形或藍色三角形 → 問號。按 B 會捲動 triangle 這樣的字。
常見問題
- 一直是問號? → 相機螢幕右上要顯示
5-1;看相機畫面每個物體旁邊標的「色號 形狀」,三角形被判成別的形狀就在相機上給目標 1 指定 Tri,或在 SET 選單把 ShapeTol 調大。 - 想要大小、面積、角度? → 改
ucSetMode(5, 2)、MODE = 52:ucReadObject會多給寬、高、面積 %、角度 0~179、頂點數、追蹤編號。