1617 lines
61 KiB
Markdown
1617 lines
61 KiB
Markdown
# KL630 Host Stream 技術報告
|
||
|
||
## 目錄
|
||
|
||
1. [系統概覽](#1-系統概覽)
|
||
2. [硬體架構](#2-硬體架構)
|
||
3. [軟體管線架構](#3-軟體管線架構)
|
||
4. [IMX662 DOL-HDR 雙曝光 ISP](#4-imx662-dol-hdr-雙曝光-isp)
|
||
5. [程式碼結構詳解](#5-程式碼結構詳解)
|
||
6. [從零開始:新裝置完整部署流程](#6-從零開始新裝置完整部署流程)
|
||
7. [常見問題排查](#7-常見問題排查)
|
||
8. [關鍵參數速查表](#8-關鍵參數速查表)
|
||
9. [STDC 語義分割邏輯詳解](#9-stdc-語義分割邏輯詳解)
|
||
10. [下一階段:高爾夫球車警示與煞車控制](#10-下一階段高爾夫球車警示與煞車控制)
|
||
11. [藍牙通訊:DX-BT24 BLE UART 模組](#11-藍牙通訊dx-bt24-ble-uart-模組)
|
||
12. [CAN Bus 通訊:MCP2515 SPI 控制器](#12-can-bus-通訊mcp2515-spi-控制器)
|
||
13. [事件錄製器(Event Recorder)](#13-事件錄製器event-recorder)
|
||
14. [GPIO 直接存取:gpio_devmem](#14-gpio-直接存取gpio_devmem)
|
||
15. [STDC 模型重訓練與後處理 Python→C 移植](#15-stdc-模型重訓練與後處理-pythonc-移植)
|
||
16. [快照功能:實作 / 事件觸發 / 打包上傳](#16-快照功能實作--事件觸發--打包上傳)
|
||
17. [本週進度與下週計畫(2026-04-16)](#17-本週進度與下週計畫2026-04-16)
|
||
|
||
---
|
||
|
||
## 1. 系統概覽
|
||
|
||
本專案在 Kneron KL630 AI SoC 上實現一個完整的即時視覺 AI 推論管線:
|
||
|
||
```
|
||
IMX662 魚眼攝影機 (DOL-HDR 雙曝光)
|
||
│
|
||
▼
|
||
KL630 ISP / IFP
|
||
(去馬賽克、AWB、AE、色調映射)
|
||
│
|
||
▼
|
||
VMF 影像管線 (YM12 planar YUV420)
|
||
├── Stream 0: 1920×1080 → H.264 → RTSP
|
||
└── Stream 1: 724×362 → NPU 推論 (STDC 語義分割)
|
||
│
|
||
▼
|
||
KL630 NPU (STDC_0520.nef)
|
||
語義分割:道路 / 車輛 / 行人
|
||
│
|
||
▼
|
||
結果疊加 → HDMI (VOC) 或 RTSP 輸出
|
||
```
|
||
|
||
**主要元件版本:**
|
||
|
||
| 元件 | 版本 / 規格 |
|
||
|------|------------|
|
||
| SoC | Kneron KL630 (ARMv7-A Cortex-A7) |
|
||
| SDK | VMF Vienna SDK 2.5.6 |
|
||
| 感測器 | Sony IMX662 1920×1080 DOL-HDR |
|
||
| AI 模型 | STDC (Short-Term Dense Concatenate) 語義分割,ModelId=32769 |
|
||
| 執行環境 | uClibc 1.0.34,LD_LIBRARY_PATH=/mnt/flash/vienna/lib |
|
||
|
||
---
|
||
|
||
## 2. 硬體架構
|
||
|
||
### KL630 SoC 功能方塊
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────┐
|
||
│ KL630 │
|
||
│ │
|
||
│ MIPI CSI-2 ──► IFP (Image Front-end Processor) │
|
||
│ │ Bayer RAW 處理 │
|
||
│ │ DOL-HDR 合成 (長短曝光 Bayer → HDR) │
|
||
│ ▼ │
|
||
│ ISP (Image Signal Processor) │
|
||
│ │ AWB / AE / GTR (Global Tone Remap) │
|
||
│ │ 輸出:YM12 (planar YUV420) │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────────────┐ │
|
||
│ │ VMF (Vatics Media Framework) │ │
|
||
│ │ SSM 共享記憶體環形緩衝區 │ │
|
||
│ │ Video Encoder (H.264/H.265/MJPEG) │ │
|
||
│ │ VOC (Video Output Component/HDMI) │ │
|
||
│ └─────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ NPU (Neural Processing Unit) │
|
||
│ │ 執行 .nef 模型 │
|
||
│ │ FifoQ 非同步推論佇列 │
|
||
│ ▼ │
|
||
│ 推論結果回傳 → 主程式後處理 │
|
||
│ │
|
||
│ /mnt/flash/ ── eMMC flash,重開機資料保留 │
|
||
│ /dev/shm/ ── 揮發性 RAM shared memory │
|
||
└──────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 儲存空間配置
|
||
|
||
```
|
||
/mnt/flash/vienna/
|
||
├── kp_firmware_host_stream ← 主程式 binary
|
||
├── lib/ ← VMF 動態函式庫
|
||
├── demo_rtsp.sh ← RTSP 模式啟動腳本
|
||
├── demo_hdmi.sh ← HDMI 模式啟動腳本
|
||
├── demo_rtsp_hdmi.sh ← RTSP+HDMI 同時輸出啟動腳本
|
||
└── drivers/ ← 硬體 kernel module
|
||
├── driver.sh ← 一鍵載入所有驅動腳本
|
||
├── vpl_edmc_v6.3.0.2.ko ← EDMC DMA 控制器(必須第一個載入)
|
||
├── vpl_dmac_v2.0.0.3.ko
|
||
├── vma_ifpe_v1.1.0.0.ko ← IFP 前端處理
|
||
├── vma_ispe_v2.0.0.5.ko ← ISP 影像處理
|
||
├── vpl_voc_v2.2.0.1.ko ← VOC HDMI 輸出
|
||
├── it66121enc_v1.0.0.3.ko ← HDMI encoder
|
||
├── IMX662_v1.0.0.1.ko ← 感測器驅動
|
||
└── ...(其他 VMA/VPL 模組)
|
||
|
||
/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin/
|
||
├── ini/
|
||
│ ├── host_stream.ini ← 主設定檔
|
||
│ ├── demo_rtsp.sh ← 腳本副本(從 vienna/ 複製)
|
||
│ ├── demo_hdmi.sh
|
||
│ ├── demo_rtsp_hdmi.sh
|
||
│ └── fec_calibrate.ini / fec_conf.ini
|
||
├── nef/
|
||
│ └── STDC_0520.nef ← NPU 模型
|
||
└── Resource/
|
||
├── VIC/0/imx662_1920x1080_ch0.cfg ← 主感測器設定
|
||
├── VIC/1/imx662_1920x1080_ch1.cfg ← Fusion (短曝光通道)
|
||
├── AWB/AutoWhiteBalance.ini
|
||
└── ISP/0/ 及 ISP/1/
|
||
└── pqtable_ispe_Config.cfg
|
||
```
|
||
|
||
> **重要:** `/mnt/flash/etc/` 雖然是 UBIFS 分區的一部分,但寫入的變更在斷電後不會持久(UBIFS journal 未 flush)。寫入後必須執行 `sync` 才能確保資料寫入 flash。`/mnt/flash/vienna/` 和 `/mnt/flash/plus/` 不受此限制。
|
||
|
||
---
|
||
|
||
## 3. 軟體管線架構
|
||
|
||
### 執行緒模型
|
||
|
||
```
|
||
主執行緒 (kp_firmware.c: main)
|
||
│
|
||
├── loadConfig() 讀取 host_stream.ini
|
||
├── init_video_source() 初始化 VMF_VSRC (IFP + ISP)
|
||
├── 建立 FifoQ NPU 推論佇列
|
||
│
|
||
├── Thread A: kdp2_host_stream_image_thread
|
||
│ │ SSM_Reader → 取得 YUV 影格
|
||
│ │ app_hdr_send_inf_cb → 放入 FifoQ
|
||
│ └► 餵圖給 NPU
|
||
│
|
||
├── Thread B: kdp2_host_update_result_thread
|
||
│ └► NPU 結果回傳 → 解析 STDC 輸出 → g_atDrawInfo[]
|
||
│
|
||
├── Thread C: kdp2_host_stream_draw_box (DrawBoxEnable=1 時)
|
||
│ │ SSM_Reader → DMA 複製 YUV → 在 YUV 上畫框
|
||
│ └► SSM_Writer → 送給 H.264 Encoder
|
||
│
|
||
├── Thread D: kdp2_host_video_thread
|
||
│ └► H.264 Encoder → SRB → rtsps (RTSP server)
|
||
│
|
||
└── Thread E: kdp2_host_voc_thread (VOC=1 時)
|
||
└► SSM_Reader → VOC (HDMI 輸出)
|
||
```
|
||
|
||
### SSM (Sync Shared Memory) 緩衝區格式
|
||
|
||
ISP 輸出的每個影格存放在 SSM 緩衝區中,格式為 **YM12 planar YUV420**:
|
||
|
||
```
|
||
SSM 緩衝區佈局 (1920×1080 為例)
|
||
┌─────────────────────────────────┐ ← buffer 起始
|
||
│ VMF_FRAME_INFO_T header │ 偏移 0,大小 = VMF_MAX_SSM_HEADER_SIZE (256 bytes)
|
||
│ 包含:dwOffset[0/1/2] │
|
||
│ dwYStride, dwYSize │
|
||
│ dwUVSize, dwType... │
|
||
├─────────────────────────────────┤ ← dwOffset[0] (通常 = 256,16-byte aligned)
|
||
│ Y 平面 │ dwYSize = dwYStride × H = 1920 × 1080
|
||
│ 1920 × 1080 = 2,073,600 bytes │
|
||
│ stride = 1920 (無 padding) │
|
||
├─────────────────────────────────┤ ← dwOffset[1] (≠ 256+YSize!需 stride 對齊)
|
||
│ Cb 平面 │ dwUVSize = dwYStride/2 × H/2 = 960 × 540
|
||
│ 960 × 540 = 518,400 bytes │
|
||
├─────────────────────────────────┤ ← dwOffset[2]
|
||
│ Cr 平面 │ 同 Cb 大小
|
||
│ 960 × 540 = 518,400 bytes │
|
||
└─────────────────────────────────┘
|
||
|
||
重要:dwOffset[1] 由 ISP 計算含對齊 padding,
|
||
不等於 VMF_MAX_SSM_HEADER_SIZE + dwYSize。
|
||
所有 DMA 操作必須使用 dwOffset[0/1/2],不可自行計算。
|
||
```
|
||
|
||
---
|
||
|
||
## 4. IMX662 DOL-HDR 雙曝光 ISP
|
||
|
||
### 4.1 為什麼 IMX662 需要特殊設定
|
||
|
||
IMX662 是 Sony 的 StarVis 2 感測器,支援 **DOL-HDR (Digital Overlap High Dynamic Range)** 模式。在此模式下,感測器在同一條 MIPI 資料流中交錯輸出兩次曝光的 Bayer 資料:
|
||
|
||
```
|
||
MIPI 資料流內容 (DOL-HDR)
|
||
┌──────────────────────────────────────────┐
|
||
│ 列 0 (短曝光 Bayer) ← 亮部細節 │
|
||
│ 列 1 (長曝光 Bayer) ← 暗部細節 │
|
||
│ 列 2 (短曝光 Bayer) │
|
||
│ 列 3 (長曝光 Bayer) │
|
||
│ ... │
|
||
└──────────────────────────────────────────┘
|
||
|
||
IFP 接收後格式標識:VMF_FRAME_FORMAT_FUSION_BAY = 31
|
||
```
|
||
|
||
這個格式讓 ISP 能夠合成高動態範圍影像——長曝光捕捉暗部,短曝光捕捉亮部,合成後得到超過單次曝光能表現的動態範圍。
|
||
|
||
### 4.2 雙曝光模式下 AWB 的問題
|
||
|
||
AWB (Auto White Balance) 需要分析影像的色彩統計數據才能決定色溫增益。對於 DOL-HDR 感測器,ISP 的 AES (Auto Exposure Statistics) 模組必須同時處理兩個曝光通道的統計數據。
|
||
|
||
**錯誤模式(使用 NORMAL 模式):**
|
||
|
||
```
|
||
程式碼:vsrc_initopt.eAppMode = VMF_VSRC_APP_MODE_NORMAL
|
||
|
||
→ IFP 嘗試用單通道統計模式處理雙通道 Bayer 資料
|
||
→ VSRC_AES_GetViBuffer() 持續失敗,錯誤碼 0x801C000B
|
||
→ AWB 無法取得有效統計數據
|
||
→ AWB 卡在冷啟動初始增益 (Cr 增益偏高)
|
||
→ 輸出影像持續偏粉紅色
|
||
```
|
||
|
||
**正確模式(使用 FUSION 模式):**
|
||
|
||
```
|
||
程式碼:vsrc_initopt.eAppMode = VMF_VSRC_APP_MODE_FUSION
|
||
|
||
→ IFP 使用雙曝光統計模式 (dwStatisticsSrcType = 2)
|
||
→ AES 成功取得兩個通道的統計數據
|
||
→ AWB 正常收斂,色溫 Cr/Cb 趨近 128 (中性)
|
||
→ 輸出色彩正常
|
||
```
|
||
|
||
### 4.3 啟用 Fusion 模式的完整條件
|
||
|
||
Fusion 模式由三個層面共同決定,**三個必須同時正確**:
|
||
|
||
#### 層面一:INI 設定 (`host_stream.ini`)
|
||
|
||
```ini
|
||
[sensor]
|
||
sensor_cfg = "./Resource/VIC/0/imx662_1920x1080_ch0.cfg" ; 主通道(長曝光)
|
||
fusion_cfg = "./Resource/VIC/1/imx662_1920x1080_ch1.cfg" ; Fusion 通道(短曝光)
|
||
```
|
||
|
||
`fusion_cfg` 這一行**不可被注解或刪除**。程式碼邏輯:
|
||
|
||
```c
|
||
// kp_firmware.c → loadConfig()
|
||
tmp = iniparser_getstring(ini, "sensor:fusion_cfg", NULL);
|
||
if (tmp)
|
||
pHostStreamInit->pszFusionConfigPath = strdup(tmp);
|
||
|
||
// kdp2_host_stream.c → init_video_source()
|
||
if (pHostStreamInit->pszFusionConfigPath) {
|
||
tFrontConfig.dwSensorConfigCount = 2;
|
||
tFrontConfig.apszSensorConfig[0] = pHostStreamInit->pszSensorConfigPath;
|
||
tFrontConfig.apszSensorConfig[1] = pHostStreamInit->pszFusionConfigPath;
|
||
vsrc_initopt.eAppMode = VMF_VSRC_APP_MODE_FUSION; // ← 關鍵
|
||
}
|
||
```
|
||
|
||
#### 層面二:ISP AWB 統計設定 (裝置端 flash)
|
||
|
||
```
|
||
檔案:$BIN_DIR/Resource/AWB/AutoWhiteBalance.ini
|
||
|
||
必須設定:dwStatisticsSrcType = 2
|
||
(0 = 單曝光,2 = 雙曝光 DOL-HDR)
|
||
```
|
||
|
||
此設定告訴 AWB 演算法從哪個統計來源取色彩數據。設為 0 時,即使 eAppMode=FUSION,AWB 仍然只看單通道統計,無法正確收斂。
|
||
|
||
#### 層面三:ISP 色調映射設定 (裝置端 flash)
|
||
|
||
```
|
||
檔案:$BIN_DIR/Resource/ISP/0/pqtable_ispe_Config.cfg
|
||
$BIN_DIR/Resource/ISP/1/pqtable_ispe_Config.cfg
|
||
|
||
必須設定:bGTREnable = 1
|
||
(GTR = Global Tone Remapping,全域色調重映射)
|
||
```
|
||
|
||
GTR 是 HDR 到 SDR 的色調壓縮演算法。DOL-HDR 合成的高動態範圍影像若不經過 GTR 壓縮,在標準顯示器上亮部會過飽和,色彩失真。
|
||
|
||
### 4.4 三個層面的關係圖
|
||
|
||
```
|
||
┌──────────────────────────────┐
|
||
INI: fusion_cfg ───►│ eAppMode = FUSION │
|
||
│ dwSensorConfigCount = 2 │
|
||
│ 提供長短曝光兩組 .cfg 給 IFP │
|
||
└──────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────┐
|
||
Resource: AWB ─────►│ dwStatisticsSrcType = 2 │
|
||
│ AES 模組使用雙通道統計 │
|
||
│ AWB 收斂 → 色溫正確 │
|
||
└──────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────┐
|
||
Resource: ISP ─────►│ bGTREnable = 1 │
|
||
│ GTR 壓縮 HDR→SDR │
|
||
│ 亮度/色彩還原正常 │
|
||
└──────────────┬───────────────┘
|
||
│
|
||
▼
|
||
✅ 色彩正確的 YM12 影像
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 程式碼結構詳解
|
||
|
||
### 5.1 原始碼目錄
|
||
|
||
```
|
||
kl630_build/
|
||
├── compile.sh ← 交叉編譯腳本 (ARM armv7-a)
|
||
├── ini/
|
||
│ └── host_stream.ini ← 主設定檔(部署時下載至裝置)
|
||
├── src/
|
||
│ ├── host_stream/
|
||
│ │ ├── kp_firmware.c ← main(),loadConfig(),執行緒管理
|
||
│ │ ├── kdp2_host_stream.c ← 核心管線:影像執行緒、畫框、VOC
|
||
│ │ ├── application_init.c ← NPU job dispatch (STDC_JOB_ID=200)
|
||
│ │ └── app_header_init.c ← FifoQ 封包封裝
|
||
│ ├── app_flow/
|
||
│ │ └── demo_customize_inf_single_model.c ← 推論回呼
|
||
│ ├── pre_post/
|
||
│ │ └── stdc_post_process.c ← STDC 後處理(分割圖解析)
|
||
│ └── stdc/
|
||
│ ├── fec_api.c ← FEC 魚眼校正 API
|
||
│ ├── stat_shim.c ← stat64 shim(交叉編譯相容)
|
||
│ └── glibc_shim.c ← glibc ABI 相容層
|
||
├── lib/ ← 裝置端 .so 函式庫(從裝置複製)
|
||
├── tools/device/
|
||
│ ├── deploy.sh ← 一鍵部署腳本
|
||
│ ├── demo_rtsp.sh ← 裝置端 RTSP 啟動腳本
|
||
│ └── demo_hdmi.sh ← 裝置端 HDMI 啟動腳本
|
||
└── SDK/ ← Kneron VMF SDK 原始碼(參考用)
|
||
```
|
||
|
||
### 5.2 kp_firmware.c — 入口點
|
||
|
||
```
|
||
main()
|
||
├── loadConfig(HOST_STREAM_CONFIG_PATH)
|
||
│ ├── 讀取 [sensor] sensor_cfg, fusion_cfg, fec_mode ...
|
||
│ ├── 讀取 [nnm] ModelPath, ModelId, JobId, InferenceStream ...
|
||
│ ├── 讀取 [streamer] StreamCount ...
|
||
│ └── 讀取 [voc] → g_dwVocPixFmt = YM12 (hardcoded)
|
||
│
|
||
├── VMF_NNM_Fifoq_Manager_Init() ← NPU 推論佇列初始化
|
||
├── VMF_NNM_Inference_App_Init() ← 載入 .nef 模型
|
||
│
|
||
├── kdp2_host_stream_init(pHostStreamInit)
|
||
│ ├── init_video_source()
|
||
│ │ ├── loadFECConfig() ← 魚眼校正參數
|
||
│ │ ├── tFrontConfig.apszSensorConfig[0/1]
|
||
│ │ ├── eAppMode = FUSION / NORMAL
|
||
│ │ └── VMF_VSRC_Init() + VMF_VSRC_Start()
|
||
│ │
|
||
│ └── 啟動各執行緒
|
||
│
|
||
└── 等待 SIGINT/SIGTERM 清理退出
|
||
```
|
||
|
||
### 5.3 kdp2_host_stream.c — 核心執行緒
|
||
|
||
#### 影像採集執行緒 (`kdp2_host_stream_image_thread`)
|
||
|
||
```c
|
||
// 核心迴圈
|
||
SSM_Reader_ReturnReceiveNewestBuff(ptSsmHandle, &ssm_buf, eImageBufMode);
|
||
VMF_VSRC_SSM_GetInfo(ssm_buf.buffer, &vsrc_ssm_info);
|
||
// 取得 YUV 影格,交給 NPU FifoQ
|
||
app_hdr_send_inf_cb(buf_addr, ..., ssm_buf.buffer, &vsrc_ssm_info);
|
||
```
|
||
|
||
#### 畫框執行緒 (`kdp2_host_stream_draw_box`)
|
||
|
||
此執行緒在 DrawBoxEnable=1 時啟動,用 DMA 複製 YUV 影格後疊加邊界框:
|
||
|
||
```c
|
||
// 正確的 DMA 目標位址(使用 SSM header 中的對齊偏移)
|
||
dma_addr.pbyDstYPhysAddr = buffer_phys + vsrc_ssm_reader_info.dwOffset[0];
|
||
dma_addr.pbyDstCbPhysAddr = buffer_phys + vsrc_ssm_reader_info.dwOffset[1];
|
||
dma_addr.pbyDstCrPhysAddr = buffer_phys + vsrc_ssm_reader_info.dwOffset[2];
|
||
// ↑ 關鍵:不可用 VMF_MAX_SSM_HEADER_SIZE + dwYSize,
|
||
// 因為 dwOffset[1] 含有 stride 對齊的 padding,數值不同。
|
||
```
|
||
|
||
### 5.4 application_init.c — NPU Job 分派
|
||
|
||
```c
|
||
switch (job_id) {
|
||
case STDC_JOB_ID: // = 200
|
||
stdc_inference(job_id, ...);
|
||
break;
|
||
// 其他 job ID...
|
||
}
|
||
```
|
||
|
||
### 5.5 host_stream.ini — 關鍵設定說明
|
||
|
||
```ini
|
||
[sensor]
|
||
sensor_cfg = "./Resource/VIC/0/imx662_1920x1080_ch0.cfg"
|
||
fusion_cfg = "./Resource/VIC/1/imx662_1920x1080_ch1.cfg" ; ← 必須!啟用 DOL-HDR
|
||
fec_mode = 0 ; 0=直通(不做魚眼校正),其他值啟用 FEC
|
||
|
||
[nnm]
|
||
ModelPath = "nef/SDTC_0520.nef"
|
||
ModelId = 32800 ; STDC segmentation model id
|
||
JobId = 212 ; STDC_JOB_ID,對應 application_init.c
|
||
InferenceStream = 1 ; 使用 stream1 (724×362) 做推論
|
||
; RTSP+HDMI 同時模式必須設為 0(否則 SIGSEGV)
|
||
Fps = 25
|
||
DrawBoxEnable = 1 ; 必須為 1,才會啟動畫框執行緒與語義分割疊加
|
||
DrawOnResize = 0 ; InferenceStream=0 時必須設為 1(在 resize stream 畫框)
|
||
|
||
[streamer]
|
||
StreamCount = 2 ; 開啟 stream0 (1920×1080) 和 stream1 (724×362)
|
||
; HDMI 單獨模式設為 1
|
||
|
||
[stream0]
|
||
Width = 1920 Height = 1080 FPS = 25 Bitrate = 2000000
|
||
|
||
[stream1]
|
||
Width = 724 Height = 362 FPS = 25 Bitrate = 2000000
|
||
|
||
[voc]
|
||
voc_enable = 1 ; 0=關閉 HDMI,1=啟用
|
||
VocWidth = 1920 VocHeight = 1080
|
||
PixFmt = NV12 ; 注意:此值被程式碼忽略,實際固定為 YM12
|
||
```
|
||
|
||
### 5.6 三種輸出模式的 INI 差異
|
||
|
||
| 模式 | voc_enable | StreamCount | InferenceStream | DrawOnResize |
|
||
|------|-----------|-------------|----------------|--------------|
|
||
| RTSP only | 0 | 2 | 1 | 0 |
|
||
| HDMI only | 1 | 1 | 0 | 1 |
|
||
| RTSP + HDMI | 1 | 2 | **0** (必須!) | 1 |
|
||
|
||
> `voc_enable=1 + StreamCount=2 + InferenceStream=1` 會造成 **SIGSEGV** crash,因此 RTSP+HDMI 模式必須將 InferenceStream 設為 0,並用 DrawOnResize=1 讓畫框作用在 resize stream 上。
|
||
|
||
---
|
||
|
||
## 6. 從零開始:新裝置完整部署流程
|
||
|
||
### 步驟 0:前置準備 — 準備開發環境
|
||
|
||
**在 Host PC 上(Windows + Docker):**
|
||
|
||
```bash
|
||
# 1. 確認 Docker 安裝並有 ARM 交叉編譯環境
|
||
docker run --rm arm-compiler:latest arm-linux-gnueabihf-gcc --version
|
||
|
||
# 2. 確認 lib/ 目錄有裝置端函式庫(從裝置複製或 SDK 提供)
|
||
ls kl630_build/lib/
|
||
# 應包含:libvmf.so, libmembroker.so, libmsgbroker.so,
|
||
# libsyncringbuffer.so, libiniparser.so,
|
||
# libvmf_nnm.so, libkutils.so, libapp_yolo.so,
|
||
# libuClibc-1.0.34.so 等
|
||
|
||
# 3. 確認 SDK headers 路徑
|
||
ls kl630_build/SDK/sdk/vtcs_root_vienna/include/vmf/
|
||
# 應包含:video_source.h, video_encoder.h, ssm_info.h 等
|
||
```
|
||
|
||
### 步驟 1:編譯 Binary
|
||
|
||
```bash
|
||
# 在 Host PC 上,於 Docker 容器內執行
|
||
docker run --rm \
|
||
-v "C:/Users/hipe5/Desktop/kl630_build:/workspace/kl630_build" \
|
||
kl630-dev \
|
||
bash /workspace/kl630_build/compile.sh
|
||
|
||
# 編譯成功後輸出:
|
||
# === SUCCESS: /workspace/kl630_build/build/kp_firmware_host_stream ===
|
||
# 並顯示 patchelf 修補後的 NEEDED 清單(應無 ld-linux-armhf.so.3)
|
||
```
|
||
|
||
**compile.sh 做了什麼:**
|
||
|
||
1. 用 `arm-linux-gnueabihf-gcc` 編譯所有 `.c` → `.o`
|
||
2. Link 成 ELF binary,rpath 指向 `$ORIGIN/lib`
|
||
3. 用 `patchelf` 將 dynamic linker 從 glibc 改為 uClibc
|
||
4. 用 `patchelf` 將 `libc.so.6` → `libc.so.0`(uClibc soname)
|
||
|
||
### 步驟 2:架設 HTTP 伺服器(Host PC)
|
||
|
||
```bash
|
||
# 在 kl630_build/build/ 目錄下架設 HTTP server
|
||
cd kl630_build/build/
|
||
cp ../ini/host_stream.ini . # 複製最新 INI
|
||
python -m http.server 8080
|
||
|
||
# 或 Windows 版
|
||
python -m http.server 8080
|
||
```
|
||
|
||
確認裝置能連到 Host PC:
|
||
- Host PC IP:`192.168.3.1`(透過 USB 網路介面)
|
||
- Port:`8080`
|
||
- 提供檔案:`kp_firmware_host_stream`、`host_stream.ini`
|
||
|
||
### 步驟 3:連線裝置並執行一次性 ISP 設定
|
||
|
||
```bash
|
||
# SSH 連入裝置(USB 網路或序列埠)
|
||
ssh root@192.168.3.10
|
||
|
||
# 在裝置上,首次只需執行一次:
|
||
sh /tmp/deploy.sh --setup
|
||
```
|
||
|
||
或手動執行以下指令(效果相同):
|
||
|
||
```sh
|
||
BIN_DIR=/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin
|
||
|
||
# 修正 1:AWB 雙曝光統計模式
|
||
sed -i 's/dwStatisticsSrcType = 0/dwStatisticsSrcType = 2/' \
|
||
$BIN_DIR/Resource/AWB/AutoWhiteBalance.ini
|
||
|
||
# 修正 2:啟用 GTR 色調映射(兩個 ISP 通道)
|
||
sed -i 's/bGTREnable = 0/bGTREnable = 1/' \
|
||
$BIN_DIR/Resource/ISP/0/pqtable_ispe_Config.cfg
|
||
sed -i 's/bGTREnable = 0/bGTREnable = 1/' \
|
||
$BIN_DIR/Resource/ISP/1/pqtable_ispe_Config.cfg
|
||
|
||
# 確認
|
||
grep dwStatisticsSrcType $BIN_DIR/Resource/AWB/AutoWhiteBalance.ini
|
||
grep bGTREnable $BIN_DIR/Resource/ISP/0/pqtable_ispe_Config.cfg
|
||
```
|
||
|
||
> **這個步驟只需要在新裝置上執行一次。**
|
||
> 所有變更寫入 `/mnt/flash/`(eMMC),重開機後永久保留。
|
||
|
||
### 步驟 4:部署新 Binary(每次重新編譯後執行)
|
||
|
||
```bash
|
||
# 在裝置上
|
||
wget -q http://192.168.3.1:8080/deploy.sh -O /tmp/deploy.sh
|
||
sh /tmp/deploy.sh
|
||
```
|
||
|
||
`deploy.sh` 自動執行:
|
||
|
||
```
|
||
1. killall -9 kp_firmware_host_stream
|
||
2. killall -9 rtsps
|
||
3. rm -f /dev/shm/*
|
||
4. wget kp_firmware_host_stream → /mnt/flash/vienna/kp_firmware_host_stream
|
||
5. wget host_stream.ini → $BIN_DIR/ini/host_stream.ini
|
||
6. cd $BIN_DIR && sh ./ini/demo_rtsp.sh
|
||
```
|
||
|
||
### 步驟 5:驗證執行
|
||
|
||
```bash
|
||
# 在裝置上確認程式執行
|
||
ps | grep kp_firmware
|
||
ps | grep rtsps
|
||
|
||
# 查看啟動 log
|
||
# 應看到:
|
||
# [NNM] fusion_cfg: ./Resource/VIC/1/imx662_1920x1080_ch1.cfg
|
||
# [init_video_source] Fusion Mode
|
||
# [VOC] PixFmt: YM12 (0x324D5930)
|
||
|
||
# 用 VLC 或 ffplay 播放 RTSP
|
||
# rtsp://192.168.3.10/live1.sdp
|
||
```
|
||
|
||
### 步驟 6:確認色彩正常
|
||
|
||
| 現象 | 可能原因 | 解法 |
|
||
|------|---------|------|
|
||
| 粉紅色影像 | fusion_cfg 沒設定 | 檢查 host_stream.ini 第二行有無 `fusion_cfg` |
|
||
| 粉紅色影像 | dwStatisticsSrcType = 0 | 重新執行 `deploy.sh --setup` |
|
||
| 粉紅色影像 | bGTREnable = 0 | 重新執行 `deploy.sh --setup` |
|
||
| 色彩正常但有條紋 | DMA 畫框位址錯誤 | 已修正,重新編譯部署即可 |
|
||
|
||
---
|
||
|
||
## 7. 常見問題排查
|
||
|
||
### 7.1 `VSRC_AES_GetViBuffer() failed: 801C000B`
|
||
|
||
```
|
||
問題:log 出現此錯誤且持續出現(每幀)
|
||
原因:eAppMode = NORMAL,AES 無法處理 FUSION_BAY (fmt=31) 格式
|
||
解法:確認 host_stream.ini 有 fusion_cfg 這一行且未被注解
|
||
```
|
||
|
||
### 7.2 Stack Smashing 崩潰
|
||
|
||
```
|
||
問題:*** stack smashing detected *** : Aborted
|
||
原因:stat64/fstat64 符號在 uClibc 上與 glibc 不相容
|
||
解法:已在 src/stdc/stat_shim.c 加入 shim,確認編譯時包含此檔案
|
||
```
|
||
|
||
### 7.3 程式啟動後立即退出
|
||
|
||
```
|
||
問題:VMF_VSRC_Init() 回傳 NULL
|
||
檢查清單:
|
||
1. LD_LIBRARY_PATH=/mnt/flash/vienna/lib 是否設定
|
||
2. /dev/shm/ 是否清空(執行 rm -f /dev/shm/*)
|
||
3. 舊的 kp_firmware_host_stream 是否已 kill
|
||
4. Resource/ 目錄下的設定檔是否存在
|
||
```
|
||
|
||
### 7.4 RTSP 連不上
|
||
|
||
```
|
||
問題:VLC 打開 rtsp://192.168.3.10/live1.sdp 失敗
|
||
檢查:
|
||
1. demo_rtsp.sh 中 sleep 4 後 rtsps 是否已啟動
|
||
→ ps | grep rtsps
|
||
2. stream_server_config.ini 是否存在於 $BIN_DIR
|
||
3. venc_srb_* 共享記憶體是否存在
|
||
→ ls /dev/shm/
|
||
```
|
||
|
||
### 7.5 HDMI 無輸出
|
||
|
||
```
|
||
問題:voc_enable = 1 但 HDMI 無畫面
|
||
檢查:
|
||
1. VocWidth/VocHeight 是否正確設定
|
||
2. ISP 輸出格式:固定為 YM12,程式碼已 hardcode
|
||
3. HDMI 顯示器是否支援 1920×1080
|
||
```
|
||
|
||
### 7.6 `[MemMgr][Error]: Open EDMC device fail !!`
|
||
|
||
```
|
||
問題:firmware 啟動後立即出現此錯誤並退出
|
||
原因:硬體 kernel module 尚未載入,/dev/vpl_edmc 不存在
|
||
(EDMC = Enhanced DMA Controller,由 vpl_edmc_v6.3.0.2.ko 提供)
|
||
|
||
排查:
|
||
strace 確認:
|
||
open("/dev/vpl_edmc", O_RDWR) = -1 ENOENT (No such file or directory)
|
||
|
||
解法:
|
||
cd /mnt/flash/vienna/drivers && sh driver.sh 2>/dev/null
|
||
然後重新執行 firmware
|
||
|
||
注意:
|
||
- /lib/modules/ 中有兩個版本:Godshand.ko 和 Godshand_v2.1.0.1.ko
|
||
系統預設載入的是 Godshand.ko,兩者皆不建立 /dev/vpl_edmc
|
||
vpl_edmc 必須由 driver.sh 明確載入 vpl_edmc_v6.3.0.2.ko 才會建立
|
||
- driver.sh 使用相對路徑 insmod,必須從 drivers/ 目錄執行
|
||
- demo_rtsp.sh / demo_hdmi.sh / demo_rtsp_hdmi.sh 已內建自動檢查:
|
||
[ -e /dev/vpl_edmc ] || (cd /mnt/flash/vienna/drivers && sh driver.sh 2>/dev/null && sleep 1)
|
||
```
|
||
|
||
### 7.7 Write Autostart 後重開機無效
|
||
|
||
```
|
||
問題:透過 web UI Write Autostart 後,重開機 firmware 沒有自動啟動
|
||
原因:/mnt/flash/etc/ 的寫入在斷電重開後會消失
|
||
UBIFS journal 未 flush 到 flash
|
||
|
||
解法:
|
||
web_serve.py 的 api_autostart_write 在寫完 rc.local 後自動執行 sync
|
||
手動寫入時必須在斷電前執行:sync
|
||
|
||
開機自動啟動原理:
|
||
/etc/init.d/rcS → S95done → insmod /lib/modules/Godshand.ko
|
||
→ sh /mnt/flash/etc/rc.local(若存在)
|
||
rc.local 內容:
|
||
cd /mnt/flash/vienna/drivers && sh driver.sh 2>/dev/null ← 載入硬體驅動
|
||
sleep 2
|
||
cd /mnt/flash/vienna
|
||
sleep 3
|
||
nohup sh /mnt/flash/vienna/demo_*.sh > /tmp/fw.log 2>&1 &
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 關鍵參數速查表
|
||
|
||
### 每次部署需確認的 INI 設定
|
||
|
||
| 參數 | 位置 | 必要值 | 說明 |
|
||
|------|------|--------|------|
|
||
| `fusion_cfg` | `[sensor]` | `./Resource/VIC/1/...` | DOL-HDR 必須,不可注解 |
|
||
| `ModelPath` | `[nnm]` | `nef/SDTC_0520.nef` | STDC 模型路徑 |
|
||
| `ModelId` | `[nnm]` | `32800` | STDC segmentation model id |
|
||
| `JobId` | `[nnm]` | `212` | 對應 `STDC_JOB_ID` |
|
||
| `InferenceStream` | `[nnm]` | `1`(RTSP)/ `0`(HDMI/RTSP+HDMI) | 推論用 stream 索引 |
|
||
| `DrawBoxEnable` | `[nnm]` | `1` | 必須,否則語義分割疊加不會執行 |
|
||
| `DrawOnResize` | `[nnm]` | `1`(InferenceStream=0 時) | 在 resize stream 上畫框 |
|
||
| `StreamCount` | `[streamer]` | `2`(RTSP)/ `1`(HDMI only) | stream 數量 |
|
||
|
||
### 裝置端 flash 一次性設定
|
||
|
||
| 參數 | 檔案 | 必要值 | 說明 |
|
||
|------|------|--------|------|
|
||
| `dwStatisticsSrcType` | `AWB/AutoWhiteBalance.ini` | `2` | 雙曝光統計模式 |
|
||
| `bGTREnable` | `ISP/0/pqtable_ispe_Config.cfg` | `1` | 色調映射開啟 |
|
||
| `bGTREnable` | `ISP/1/pqtable_ispe_Config.cfg` | `1` | 色調映射開啟 |
|
||
|
||
### 編譯關鍵 Flags
|
||
|
||
| Flag | 說明 |
|
||
|------|------|
|
||
| `-DVATICS_PLATFORM` | VMF SDK 平台識別 |
|
||
| `-DKL630` | KL630 特定程式碼路徑 |
|
||
| `-march=armv7-a -mfpu=neon -mfloat-abi=hard` | Cortex-A7 + NEON |
|
||
| `-Wl,--dynamic-linker,/lib/ld-uClibc.so.1` | 指定 uClibc dynamic linker |
|
||
| patchelf: `libc.so.6` → `libc.so.0` | glibc soname → uClibc soname |
|
||
|
||
---
|
||
|
||
## 9. STDC 語義分割邏輯詳解
|
||
|
||
本節說明目前 `stdc_post_process.c` 和 `app_header_init.c` 實作的完整判斷邏輯,作為下一階段警示/煞車控制的輸入基礎。
|
||
|
||
### 9.1 分割類別定義
|
||
|
||
STDC 模型輸出 128×64 的像素分類圖,每個像素屬於以下 8 個類別之一:
|
||
|
||
| 索引 | 類別 | 說明 |
|
||
|------|------|------|
|
||
| 0 | `BUNKER` | 沙坑 |
|
||
| 1 | `CAR` | 車輛 |
|
||
| 2 | `GRASS` | 草地 |
|
||
| 3 | `GREENERY` | 植被(灌木、雜草等) |
|
||
| 4 | `PERSON` | 行人 |
|
||
| 5 | `POND` | 水塘 |
|
||
| 6 | `ROAD` | 道路/路徑 |
|
||
| 7 | `TREE` | 樹木 |
|
||
|
||
每個類別都計算全域佔比 `class_ratio[ci]`(該類像素數 ÷ 總像素數)。
|
||
|
||
### 9.2 感興趣區域(ROI)定義
|
||
|
||
#### 碰撞 ROI(Collision ROI)
|
||
|
||
以影像分割圖比例定義,判斷是否有危險物體進入正前方區域:
|
||
|
||
```
|
||
左邊界: 25% 寬度 右邊界: 75% 寬度
|
||
上邊界: 25% 高度 下邊界: 70% 高度
|
||
|
||
對應 1920×1080 畫面:x=480 y=270 w=960 h=486(DrawBox 視覺輸出位置)
|
||
```
|
||
|
||
#### 前進 ROI(Forward ROI)— 梯形
|
||
|
||
模擬球車前方路面的透視梯形,用於草地/樹木判斷:
|
||
|
||
```
|
||
頂邊:左 45% ─────────── 右 55% (影像 55% 高度處)
|
||
底邊:左 30% ─────────────────── 右 70% (影像 95% 高度處)
|
||
```
|
||
|
||
### 9.3 警告旗標與閥值
|
||
|
||
`stdc_analysis_t` 中的 boolean 旗標由以下邏輯設定:
|
||
|
||
| 旗標 | 條件 | 閥值來源 |
|
||
|------|------|---------|
|
||
| `on_grass` | 前進 ROI 內草地佔比 > 30% | `THR_GRASS_ROI = 0.30` |
|
||
| `grass_warning` | `on_grass` 持續時間 > 2 秒 | `STDC_WARNING_SECONDS = 2.0` |
|
||
| `person_warning` | 全域行人佔比 > 1% | `THR_PERSON_GLOBAL = 0.01` |
|
||
| `car_warning` | 全域車輛佔比 > 5% | `THR_CAR_GLOBAL = 0.05` |
|
||
| `greenery_warning` | 全域植被佔比 > 20% | `THR_GREENERY_GLOBAL = 0.20` |
|
||
| `bunker_warning` | 全域沙坑佔比 > 1% | `THR_BUNKER_GLOBAL = 0.01` |
|
||
| `pond_warning` | 全域水塘佔比 > 1% | `THR_POND_GLOBAL = 0.01` |
|
||
| `tree_dense` | 全域樹木佔比 > 30% | `THR_TREE_DENSE = 0.30` |
|
||
| `tree_approaching` | 前進 ROI 內樹木佔比相較前幀成長 > 5% | `STDC_TREE_GROWTH_THR = 0.05` |
|
||
| `collision_risk` | 碰撞 ROI 內任一危險類別超過各自閥值(見下表) | — |
|
||
| `is_moving` | 前進 ROI 內 luma 平均差 > 3.0 | `STDC_MOTION_THRESHOLD = 3.0` |
|
||
|
||
#### `collision_risk` 詳細條件(碰撞 ROI 內)
|
||
|
||
| 類別 | 閥值 |
|
||
|------|------|
|
||
| 行人 (`col_person_ratio`) | > 1% |
|
||
| 車輛 (`col_car_ratio`) | > 5% |
|
||
| 樹木 (`col_tree_ratio`) | > 20% |
|
||
| 沙坑 (`col_bunker_ratio`) | > 1% |
|
||
| 水塘 (`col_pond_ratio`) | > 1% |
|
||
|
||
任一條件成立即觸發 `collision_risk = 1`。
|
||
|
||
### 9.4 警告優先層級
|
||
|
||
目前的邏輯是平坦的 bitmask(無優先順序),下一階段需要整合成層級:
|
||
|
||
```
|
||
Level 2(緊急) ← collision_risk = 1
|
||
OR person_warning = 1
|
||
|
||
Level 1(警告) ← car_warning = 1
|
||
OR pond_warning = 1
|
||
OR bunker_warning = 1
|
||
OR tree_approaching = 1
|
||
|
||
Level 0(正常) ← 其餘情況
|
||
```
|
||
|
||
`grass_warning`、`greenery_warning`、`tree_dense` 在球場環境中幾乎常時觸發,**不納入煞車邏輯**,僅保留為 HUD 顯示資訊。
|
||
|
||
### 9.5 視覺疊加輸出(DrawBox,1920×1080)
|
||
|
||
```
|
||
畫面區域 內容
|
||
─────────────────────────────────────────────────────
|
||
中央大框 碰撞 ROI 輪廓(480,270 960×486)
|
||
collision_risk 時加畫第二層內框(492,282 936×462)
|
||
中央下框 前進 ROI 輪廓(576,594 768×432)
|
||
左側警告欄 (200×55) y=10 collision_risk
|
||
y=73 person_warning
|
||
y=136 car_warning
|
||
y=199 grass_warning
|
||
y=262 tree_dense
|
||
y=325 tree_approaching
|
||
右側類別欄 (40×55) 每個 class 當比例超過各自閥值時亮起
|
||
bunker/car/grass/greenery/person/pond/road/tree(由上到下)
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 下一階段:高爾夫球車警示與煞車控制
|
||
|
||
### 10.1 目標
|
||
|
||
在現有 KL630 AI 視覺管線上疊加一套安全警示與動力介入系統,適用於高爾夫球場電動球車:
|
||
|
||
- **行人/障礙物偵測** → 即時警告
|
||
- **緊急煞車** → GPIO 訊號觸發 relay,截斷馬達動力或拉起電磁煞車
|
||
- **未來擴充** → 油門 PWM 控制(依障礙物距離梯度減速)
|
||
|
||
### 10.2 系統架構變化
|
||
|
||
```
|
||
現有管線(第一階段)
|
||
STDC 分割 → stdc_analysis_t flags → printf 警告 + DrawBox 視覺疊加
|
||
|
||
新增(第二階段)
|
||
stdc_analysis_t flags
|
||
│
|
||
▼
|
||
warn_level 整合(0/1/2) ← app_header_init.c 新增
|
||
│
|
||
┌────┴─────────┐
|
||
▼ ▼
|
||
GPIO 煞車訊號 GPIO 警告燈 ← gpio_ctrl.c 新增
|
||
/sys/class/gpio/ relay/buzzer
|
||
│
|
||
▼
|
||
馬達控制器 / 電磁煞車
|
||
```
|
||
|
||
### 10.3 已完成 / 待完成項目
|
||
|
||
| 檔案 | 狀態 | 說明 |
|
||
|------|------|------|
|
||
| `src/host_stream/event_recorder.c` | ✅ **已實作** | 草地狀態機 + 單次碰撞事件 + JPEG 快照 + tar.gz 上傳(見第 13 節) |
|
||
| `src/host_stream/can_bus.c` | ✅ **已實作** | CAN 控制指令 `can_bus_send_control_cmd(level)`(見第 12 節) |
|
||
| `src/host_stream/gpio_devmem.c` | ✅ **已實作** | /dev/mem GPIO(SPI bit-bang 用,見第 14 節) |
|
||
| `src/host_stream/bt_uart.c` | ✅ **已實作** | BLE JSON 事件推送(見第 11 節) |
|
||
| 警告燈 / 煞車 relay GPIO | ❌ **待實作** | 需確認板上 GPIO pin 與繼電器接線後實作 `gpio_set_warning(level)` |
|
||
| `ini/host_stream.ini [golf_cart]` | ❌ **待實作** | 警告/煞車 GPIO pin 號設定 |
|
||
|
||
### 10.4 警告等級 → GPIO 輸出對應
|
||
|
||
```
|
||
warn_level 0(正常) → GPIO_WARN=0 GPIO_BRAKE=0
|
||
warn_level 1(警告) → GPIO_WARN=1 GPIO_BRAKE=0 (警示燈亮)
|
||
warn_level 2(緊急) → GPIO_WARN=1 GPIO_BRAKE=1 (警示燈 + 煞車 relay 觸發)
|
||
```
|
||
|
||
觸發條件:
|
||
- Level 2:`collision_risk == 1` 或 `person_warning == 1`
|
||
- Level 1:`car_warning == 1` 或 `pond_warning == 1` 或 `bunker_warning == 1` 或 `tree_approaching == 1`
|
||
|
||
### 10.5 新增 INI 參數規劃
|
||
|
||
```ini
|
||
[golf_cart]
|
||
BrakeOnCollision = 1 ; collision_risk 觸發煞車(0=停用)
|
||
BrakeOnPerson = 1 ; person_warning 觸發煞車(0=停用)
|
||
WarnOnCar = 1 ; car_warning 觸發警告燈
|
||
WarnOnPond = 1 ; pond_warning 觸發警告燈
|
||
WarnOnBunker = 1 ; bunker_warning 觸發警告燈
|
||
GpioPinWarn = 5 ; 警告燈/蜂鳴器 GPIO pin 編號
|
||
GpioPinBrake = 6 ; 煞車 relay GPIO pin 編號
|
||
BrakePulseMs = 0 ; 0=持續拉高;>0=觸發後維持 N ms 再釋放(pulse 模式)
|
||
```
|
||
|
||
### 10.6 待確認事項
|
||
|
||
| 項目 | 說明 |
|
||
|------|------|
|
||
| KL630 GPIO pin mapping | 確認板上哪些 pin 拉出,對應 `/sys/class/gpio/gpioX` 編號(需查 BSP / schematic) |
|
||
| 煞車介面 | relay DO(直接 GPIO)或需要 PWM 訊號給馬達控制器 |
|
||
| 草地閥值調整 | 球場全是草,`THR_GRASS_ROI=0.30` 會讓 `on_grass` 幾乎常時觸發;考慮提高閥值或直接排除 grass 警告出煞車邏輯 |
|
||
| 反應延遲 | 目前 STDC 推論速率約 25 fps,collision_risk 觸發到 GPIO 拉高延遲 < 80ms,是否符合需求 |
|
||
| 安全冗餘 | 視覺 AI 單點失效時的 failsafe 策略(例如:推論超時 → 強制觸發警告) |
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 11. 藍牙通訊:DX-BT24 BLE UART 模組
|
||
|
||
### 11.1 模組規格
|
||
|
||
| 項目 | 規格 |
|
||
|------|------|
|
||
| 模組型號 | DX-BT24 |
|
||
| 通訊介面 | 透明 UART → BLE(BLE 4.2) |
|
||
| BLE 服務 | UUID `0xFFE0`;Notify `FFE1`(裝置→手機);Write `FFE2`(手機→裝置) |
|
||
| UART 預設鮑率 | 9600 bps(出廠);一次性升級至 115200 bps |
|
||
| KL630 UART 腳位 | `/dev/ttyS1`(J15 擴充連接器) |
|
||
|
||
### 11.2 功能架構
|
||
|
||
```
|
||
KL630 firmware
|
||
event_recorder_update()
|
||
│
|
||
▼
|
||
bt_uart_send_json() ← 非阻塞:放入 FIFO 佇列即返回
|
||
│
|
||
▼
|
||
[bt_writer_thread] ← 專用執行緒,實際寫入 UART
|
||
│
|
||
▼
|
||
/dev/ttyS1 → DX-BT24 → BLE → iPhone nRF Connect / 球場 iPad App
|
||
```
|
||
|
||
**關鍵設計**:`bt_uart_send_json()` 只做 `malloc + enqueue + cond_signal`,永不阻塞推論主迴圈。寫入執行緒在有訊息時才喚醒並寫入 UART。
|
||
|
||
### 11.3 JSON 事件格式
|
||
|
||
每次偵測到違規即送出一個 JSON 字串(無 `\r\n`,BLE 以封包為單位):
|
||
|
||
```json
|
||
{"class":"car","level":1}
|
||
{"class":"person","level":2}
|
||
{"class":"grass","level":1}
|
||
{"class":"boot","level":0}
|
||
```
|
||
|
||
| 欄位 | 說明 |
|
||
|------|------|
|
||
| `class` | 違規類型:`boot` / `road` / `grass` / `car` / `person` / `pond` / `bunker` / `tree` / `hazard` |
|
||
| `level` | 嚴重等級:0=正常/開機,1=警告,2=緊急 |
|
||
|
||
> **注意**:不加 `\r\n`。加了 DX-BT24 會在換行後額外送出一個空的 BLE Notification,手機端看到非 UTF-8 內容。
|
||
|
||
### 11.4 一次性鮑率升級
|
||
|
||
出廠模組預設 9600 bps。升級步驟(僅需做一次):
|
||
|
||
1. 在 `ini/host_stream.ini` 設 `bt_at_probe = 1`
|
||
2. 啟動韌體 — `bt_uart_init()` 偵測模組當前鮑率並送出 `AT+BAUD7`(→115200)後 `AT+RESET`
|
||
3. 確認 `/tmp/fw.log` 出現 `[BT] upgrade complete`
|
||
4. 將 `bt_at_probe` 改回 `0`(避免 AT 指令被 BLE 客戶端看到)
|
||
|
||
### 11.5 INI 參數
|
||
|
||
```ini
|
||
[event]
|
||
enable = 1
|
||
bt_uart_dev = /dev/ttyS1 # UART 裝置節點
|
||
bt_at_probe = 0 # 0: 直接以 115200 開啟(正常使用)
|
||
# 1: 執行一次性 AT 鮑率升級(出廠/重置後)
|
||
```
|
||
|
||
### 11.6 相關原始碼
|
||
|
||
| 檔案 | 說明 |
|
||
|------|------|
|
||
| `src/host_stream/bt_uart.c` | UART 驅動:FIFO 佇列 + 專用寫入執行緒 + AT 探測/升級邏輯 |
|
||
| `include/host_stream/bt_uart.h` | 公開 API:`bt_uart_init()` / `bt_uart_send_json()` / `bt_uart_close()` |
|
||
| `src/host_stream/event_recorder.c` | `fire_json_async()` 呼叫 `bt_uart_send_json()` |
|
||
| `src/host_stream/kp_firmware.c` | `loadConfig()` 中讀取 INI 並呼叫 `bt_uart_init()` |
|
||
|
||
### 11.7 驗證方法
|
||
|
||
裝置端(韌體啟動後):
|
||
|
||
```sh
|
||
# 手動送出 JSON(stty 先設定鮑率)
|
||
stty -F /dev/ttyS1 115200 raw cs8 -parenb -cstopb -echo
|
||
printf '{"class":"test","level":0}' > /dev/ttyS1
|
||
```
|
||
|
||
iPhone 端:開啟 nRF Connect → 掃描 DX-BT24 → 連線 → 訂閱 FFE1 Notify → 應看到 `{"class":"test","level":0}`(正確 UTF-8 字串)。
|
||
|
||
---
|
||
|
||
## 12. CAN Bus 通訊:MCP2515 SPI 控制器
|
||
|
||
### 12.1 硬體配置
|
||
|
||
| 項目 | 規格 |
|
||
|------|------|
|
||
| CAN 控制器 | MCP2515 |
|
||
| 介面 | SPI via J16 擴充連接器 |
|
||
| SPI 裝置節點 | `/dev/spidev1.0` |
|
||
| SPI 時脈 | 1 MHz |
|
||
| 晶振 | 8 MHz |
|
||
| 預設 CAN 速率 | 250 kbps |
|
||
| 輸出 CAN Frame ID | 0x100(11-bit Standard Frame,可 INI 設定) |
|
||
|
||
J16 接線(依板上標示順序):
|
||
|
||
| J16 腳位 | MCP2515 模組 |
|
||
|---|---|
|
||
| CS | CS |
|
||
| DO | SI (MOSI) |
|
||
| CLK | SCK |
|
||
| RESERVED | SO (MISO) |
|
||
|
||
註:若板上 `RESERVED` 版本實際未接到 `SPI_1_DI_E`,MCP2515 將無法回應。
|
||
|
||
### 12.2 功能架構
|
||
|
||
本週重構為**控制訊號模式(control-frame mode)**,移除了 MsgBroker FIFO 依賴,直接使用 CAN 控制幀進行速度控制:
|
||
|
||
```
|
||
KL630 firmware
|
||
event_recorder.c: grass_enter_level(level) / collision detection
|
||
│
|
||
▼
|
||
msg_send() → can_bus_send_control_cmd(level) ← 直接呼叫(同步,有 retry)
|
||
│
|
||
▼
|
||
send_one_frame_with_retry() ← 最多 20 次重試 + TX stuck 恢復
|
||
│
|
||
▼
|
||
mcp2515_sendMessage() ← SPI ioctl 到 /dev/spidev1.0
|
||
│ (或 SPI_BITBANG 模式:gpio_devmem)
|
||
▼
|
||
MCP2515 (J16) → CAN bus → 車體馬達控制器
|
||
```
|
||
|
||
> **重要改動**:`msg_send("setSpeed")` 現在直接呼叫 `can_bus_send_control_cmd()`,不再依賴 `/tmp/canbus/c0/command.fifo` MsgBroker FIFO。JSON 事件僅透過藍牙(Channel A)傳送給 iPad。
|
||
|
||
CAN 控制器包含 keep-alive 機制,每 200ms 自動重發最後的速度指令,確保馬達控制器不會因通訊中斷而失去控制。
|
||
|
||
### 12.3 CAN Frame 格式
|
||
|
||
#### 控制 Frame(主要輸出)
|
||
|
||
```
|
||
CAN ID : 0x75 (11-bit, hardcoded in s_ctl_can_id)
|
||
DLC : 8
|
||
Data[0] : 速度控制指令(見下表)
|
||
Data[1-7]: 0x00 (reserved)
|
||
```
|
||
|
||
| Data[0] | 定義常數 | 觸發條件 | 說明 |
|
||
|---------|---------|---------|------|
|
||
| `10` | `SPEED_LEVEL_0` | 碰撞事件 | 緊急停車(單次觸發) |
|
||
| `10` | `SPEED_LEVEL_1` | 草地 L3(T+10s) | 嚴重違規,強制停車 |
|
||
| `10` | `SPEED_LEVEL_2` | 草地 L2(T+6s) | 持續違規,中度減速 |
|
||
| `10` | `SPEED_LEVEL_3` | 草地 L1 持續 | 持續違規,輕度減速 |
|
||
| `10` | `SPEED_LEVEL_4` | 草地 L1 進入 | 初次草地違規,警告 |
|
||
| `240` | `SPEED_LEVEL_5` | 正常狀態 / 事件結束 | 恢復正常(全速) |
|
||
|
||
> **注意**:目前測試階段所有 SPEED_LEVEL 均設為 10(低速),正常速度為 240。生產環境可依需求調整各等級的速度值。
|
||
|
||
控制指令由 `event_recorder.c` 的草地狀態機和碰撞檢測觸發(詳見第 13 節)。CAN 控制器會每 200ms 自動重發最後的控制指令作為 keep-alive。
|
||
|
||
#### JSON 多幀(保留,目前停用)
|
||
|
||
```
|
||
CAN ID : 0x100 (INI can_id,可設定)
|
||
DLC : 最多 8 bytes / frame
|
||
格式 : JSON 字串分段,每幀 8 bytes,最後一幀以 0x00 補齊
|
||
接收端 : 逐幀拼接直到遇到 null byte
|
||
```
|
||
|
||
此模式已保留程式碼但 `can_bus_send_json()` 目前不發送。若日後需要同時傳送 JSON 到 CAN,移除 `can_bus_send_json()` 中的 early return 即可。
|
||
|
||
### 12.4 初始化流程
|
||
|
||
```c
|
||
/* can_bus_init 內部步驟 */
|
||
s_dev = new_mcp2515_dev(spidev); // 配置 SPI: 1MHz, 8-bit
|
||
// 或 "gpio-bitbang"(SPI_BITBANG 模式)
|
||
mcp2515_initial(s_dev); // SPI reset → 進入 CONFIG mode
|
||
mcp2515_can_speed(s_dev, CAN_250KBPS, MCP_8MHZ); // 8MHz 晶振 @ 250kbps
|
||
mcp2515_setMode(s_dev, CANCTRL_REQOP_NORMAL); // 進入 NORMAL mode
|
||
|
||
/* 啟動迴環自測 (loopback self-test) */
|
||
mcp2515_setMode(LOOPBACK) → sendMessage(0x321, 0xA5) → readMessage()
|
||
// 成功:log "[CAN] startup loopback self-test OK"
|
||
// 失敗:log "[CAN] startup loopback self-test failed (-N)"
|
||
|
||
// 啟動 can_writer_thread(佇列消費用,目前主要由 can_bus_send_control_cmd 直接送)
|
||
// 啟動 can_keepalive_thread:每 200ms 重發最後控制指令 (預設 SPEED_LEVEL_5 = 240)
|
||
// 初始化完成後送出 boot 控制 frame: can_bus_send_control_cmd(SPEED_LEVEL_5)
|
||
```
|
||
|
||
### 12.5 TX 錯誤恢復機制
|
||
|
||
當 `mcp2515_sendMessage()` 失敗時觸發分層恢復:
|
||
|
||
```
|
||
sendMessage() 回傳 ERROR_ALLTXBUSY 或 ERROR_FAILTX
|
||
│
|
||
▼
|
||
重試最多 20 次(每次 backoff 2ms)
|
||
│
|
||
├── 仍失敗 → 讀取 EFLG / TEC / REC 暫存器
|
||
│ 印出 hint(TX bus-off / TX error-passive / 警告)
|
||
│ can_recover_tx_stuck():
|
||
│ CANCTRL_ABAT → 中止掛起的重傳
|
||
│ 清除 TXB0/1/2 TXREQ bit
|
||
│ 恢復 NORMAL mode
|
||
│
|
||
└── 若 EFLG_TXBO 或 TEC ≥ 128 (bus-off 狀態)
|
||
can_reinit_controller_locked(): reset + 重設速率 + NORMAL
|
||
can_loopback_selftest_locked(): 確認 SPI 路徑健康
|
||
```
|
||
|
||
|
||
### 12.6 INI 參數
|
||
|
||
```ini
|
||
[can]
|
||
enable = 1 # 1: 啟用 CAN 輸出;0: 停用
|
||
spidev = /dev/spidev1.0 # SPI 裝置路徑(SPI_BITBANG 模式下忽略)
|
||
speed_kbps = 250 # CAN 速率:125 / 250 / 500 / 1000 kbps
|
||
can_id = 0x100 # JSON 多幀的 11-bit CAN ID(目前停用)
|
||
# 控制 Frame 固定使用 ID 0x75
|
||
```
|
||
|
||
> **本週變更**:`enable` 已從 `0` 改為 `1`(預設開啟)。
|
||
|
||
### 12.7 SPI Bit-bang 模式
|
||
|
||
當 compile.sh 加上 `-DSPI_BITBANG` 時,MCP2515 透過 gpio_devmem 以軟體模擬 SPI:
|
||
|
||
```
|
||
/dev/spidev1.0 不可用(被其他 kernel module 佔用)時使用此模式
|
||
|
||
J15 腳位 (GPIOC_0):
|
||
GPIO1 = CS (SPI_GPIO_CS, compile.sh 定義)
|
||
GPIO2 = MOSI (SPI_GPIO_MOSI)
|
||
GPIO3 = SCK (SPI_GPIO_SCK)
|
||
GPIO4 = MISO (SPI_GPIO_MISO)
|
||
|
||
底層實作:gpio_devmem_set(pin, 0/1) → /dev/mem → KL630 GPIO_C 0x402E0000
|
||
```
|
||
|
||
詳細暫存器配置見第 14 節。
|
||
|
||
### 12.8 相關原始碼
|
||
|
||
| 檔案 | 說明 |
|
||
|------|------|
|
||
| `src/host_stream/mcp2515.c` | MCP2515 低層 SPI 驅動 |
|
||
| `include/host_stream/mcp2515.h` | MCP2515 暫存器定義、API 宣告 |
|
||
| `src/host_stream/can_bus.c` | 高層封裝:`can_bus_send_control_cmd()` + 錯誤恢復 |
|
||
| `include/host_stream/can_bus.h` | 公開 API:`can_bus_init()` / `can_bus_send_control_cmd()` / `can_bus_close()` |
|
||
| `src/host_stream/gpio_devmem.c` | SPI bit-bang 用的 GPIO /dev/mem 驅動(第 14 節) |
|
||
| `src/host_stream/kp_firmware.c` | `loadConfig()` 讀取 `[can]` INI 並呼叫 `can_bus_init()` |
|
||
|
||
### 12.9 待確認事項
|
||
|
||
| 項目 | 說明 |
|
||
|------|------|
|
||
| J16 MISO 接腳 | `RESERVED` 需確認為 `SPI_1_DI_E`,否則 MCP2515 回應讀不回來 |
|
||
| 控制 Frame ID 0x75 | 與車體 ECU 協調確認不與其他 node 衝突 |
|
||
| 控制指令語意 | cmd=1/2/3 的具體油門百分比或減速邏輯由馬達控制器端定義 |
|
||
| 接收端需求 | 目前只實作發送;若需接收回授,在 can_writer_thread 加 mcp2515_readMessage() 輪詢 |
|
||
| CAN 速率匹配 | 車體 CAN bus 速率需與 `speed_kbps` 一致,否則全部 Frame 被丟棄 |
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 13. 事件錄製器(Event Recorder)
|
||
|
||
`event_recorder.c` 是本週新增的核心模組,負責整合 STDC 分析結果、觸發雙通道事件輸出,並將現場 JPEG 快照打包上傳。
|
||
|
||
### 13.1 雙通道架構
|
||
|
||
```
|
||
STDC 分析結果 (stdc_analysis_t)
|
||
│
|
||
▼
|
||
event_recorder_update() ← 在 recv callback(約 25fps)呼叫
|
||
│
|
||
┌────┴──────────────────────────────┐
|
||
▼ ▼
|
||
Channel A (BLE/iPad) Channel B (tar.gz 上傳)
|
||
bt_uart_send_json(json) VMF_SNAP → JPEG → event.json → tar.gz
|
||
{"class":"lane","level":1} → kCurl POST → /api/golf.cgi
|
||
│ │
|
||
DX-BT24 BLE → iPad SD 卡存檔 (最多 7GB)
|
||
```
|
||
|
||
另外,草地事件同時觸發 CAN 控制指令(Channel C,第 12 節):
|
||
|
||
```
|
||
grass_enter_level(level)
|
||
├── fire_json_async() → BLE JSON
|
||
└── can_bus_send_control_cmd(level) → CAN 馬達控制
|
||
```
|
||
|
||
### 13.2 草地違規狀態機
|
||
|
||
觸發條件:`on_grass == 1 AND is_moving == 1`(靜止停車在草地不觸發)
|
||
|
||
```
|
||
GRASS_IDLE ──── on_grass & is_moving ────► GRASS_L1 (T+0s)
|
||
│ fire BT level=1, CAN cmd=1, snap level1.jpg
|
||
│
|
||
!on_grass 持續 2s ◄─────────────┤─── on_grass & 經過 6s ──► GRASS_L2 (T+6s)
|
||
(exit hysteresis) │ │ fire BT level=2
|
||
│ │ │ CAN cmd=2, snap level2.jpg
|
||
▼ │ !on_grass 持續 2s ◄─────────┤─── 經過 10s ──► GRASS_L3 (T+10s)
|
||
GRASS_DONE │ │ │ │ fire BT level=3
|
||
fire BT level=0 │ ▼ │ │ CAN cmd=3
|
||
CAN cmd=0 │ GRASS_DONE │ !on_grass 持續 2s
|
||
launch_upload() └─────────────────────────────── │ ──────────────► GRASS_DONE
|
||
│
|
||
▼
|
||
upload_thread (detached)
|
||
delay → event.json → tar.gz → kCurl POST → SD 清理
|
||
│
|
||
▼
|
||
GRASS_IDLE
|
||
```
|
||
|
||
**退出遲滯(exit hysteresis)**:`GRASS_EXIT_HYSTERESIS_MS = 2000ms`。
|
||
球場上 STDC 偶爾在路邊草地閃爍,2 秒遲滯避免誤以為事件結束。
|
||
|
||
### 13.3 單次碰撞事件(Single-shot)
|
||
|
||
以下事件僅在碰撞 ROI 內偵測到,且只觸發一次(Rising edge 0→1):
|
||
|
||
| 類型 | 閾值 | 觸發行為 |
|
||
|------|------|---------|
|
||
| `person` | `col_person_ratio ≥ THR_PERSON_COLLISION` | BT JSON level=1 + snap + 立即上傳 |
|
||
| `bunker` | `col_bunker_ratio ≥ THR_BUNKER_COLLISION` | BT JSON level=1 + snap + 立即上傳 |
|
||
| `pond` | `col_pond_ratio ≥ THR_POND_COLLISION` | BT JSON level=1 + snap + 立即上傳 |
|
||
| `tree` | `col_tree_ratio ≥ THR_TREE_COLLISION` | BT JSON level=1 + snap + 立即上傳 |
|
||
|
||
碰撞事件不觸發 CAN 控制指令(目前設計;日後可加)。
|
||
|
||
### 13.4 JSON 事件格式(BLE 輸出)
|
||
|
||
```json
|
||
{"class":"lane","level":1} ← 草地 L1
|
||
{"class":"lane","level":2} ← 草地 L2
|
||
{"class":"lane","level":3} ← 草地 L3
|
||
{"class":"lane","level":0} ← 草地結束(恢復正常)
|
||
{"class":"person","level":1} ← 行人碰撞 ROI
|
||
{"class":"bunker","level":1} ← 沙坑碰撞 ROI
|
||
```
|
||
|
||
> **type "lane"**:草地事件統一以 `lane`(超出車道邊界)回報,方便前端 app 分類。
|
||
|
||
### 13.5 JPEG 快照與 tar.gz 打包
|
||
|
||
快照由 `event_recorder_provide_frame()` 在 VMF 影格發送 callback 中執行:
|
||
|
||
```
|
||
[主執行緒: send callback]
|
||
g_snap_req.active == 1
|
||
→ VMF_SNAP_ProcessOneFrame(1920×1080, QP=75)
|
||
→ save_jpeg("/tmp/ev_<id>/level1.jpg")
|
||
→ 草地事件:保留,等事件結束後統一打包
|
||
→ 碰撞事件:立即 launch_upload()
|
||
```
|
||
|
||
tar.gz 結構:
|
||
|
||
```
|
||
event_<id>_<timestamp>.tar.gz
|
||
├── event.json ← id, date(UTC+8), type, max_level, duration_sec, images[]
|
||
├── level1.jpg ← 草地 L1 快照(若存在)
|
||
├── level2.jpg ← 草地 L2 快照(若存在)
|
||
├── level3.jpg ← 草地 L3 快照(若存在)
|
||
└── snapshot.jpg ← 碰撞事件快照
|
||
```
|
||
|
||
SD 卡清理:總大小超過 `sd_max_mb`(預設 7168 MB = 7GB)時,按 mtime 由舊到新刪除。
|
||
|
||
### 13.6 INI 參數
|
||
|
||
```ini
|
||
[event]
|
||
enable = 1
|
||
bt_uart_dev = /dev/ttyS1 # Channel A: DX-BT24 UART 節點
|
||
bt_at_probe = 0 # 0: 直接 115200; 1: 一次性 AT 升級
|
||
upload_url = http://192.168.0.114:8081/api/golf.cgi # Channel B: tar.gz 上傳端點
|
||
# Production: http://192.168.0.99/api/golf.cgi
|
||
sd_path = /tmp/sdcard/events # SD 卡存檔路徑
|
||
sd_max_mb = 7168 # SD 卡容量上限(MB)
|
||
upload_delay_ms = 0 # 0: 事件結束後立即打包上傳
|
||
```
|
||
|
||
### 13.7 相關原始碼
|
||
|
||
| 檔案 | 說明 |
|
||
|------|------|
|
||
| `src/host_stream/event_recorder.c` | 狀態機、快照、打包、上傳全部邏輯 |
|
||
| `include/host_stream/event_recorder.h` | 公開 API:`event_recorder_init()` / `event_recorder_update()` / `event_recorder_provide_frame()` |
|
||
| `src/host_stream/kp_firmware.c` | `loadConfig()` 讀取 `[event]` INI 並呼叫 `event_recorder_init()` |
|
||
| `src/host_stream/kp_firmware.c` | 在 recv callback 呼叫 `event_recorder_update(ana)` |
|
||
|
||
---
|
||
|
||
## 14. GPIO 直接存取:gpio_devmem
|
||
|
||
### 14.1 背景
|
||
|
||
KL630 J15 擴充連接器的 GPIO 腳位在部分韌體版本中已被 `dh2228fv` display driver 透過 pinmux 佔用,導致 `/sys/class/gpio` 介面無法使用。解法是透過 `/dev/mem` 直接存取實體暫存器,繞過 pinmux 衝突。
|
||
|
||
### 14.2 硬體對應
|
||
|
||
```
|
||
KL630 GPIO_C 基底位址:0x402E0000
|
||
|
||
J15 連接器腳位對應(GPIOC_0 group):
|
||
J15 Pin 1 (CS) → GPIO1 = GPIOC_0_IO_DATA_1
|
||
J15 Pin 2 (MOSI) → GPIO2 = GPIOC_0_IO_DATA_2
|
||
J15 Pin 3 (SCK) → GPIO3 = GPIOC_0_IO_DATA_3
|
||
J15 Pin 4 (MISO) → GPIO4 = GPIOC_0_IO_DATA_4
|
||
```
|
||
|
||
### 14.3 暫存器對應
|
||
|
||
| 偏移 | 暫存器 | 功能 |
|
||
|------|--------|------|
|
||
| `+0x0000` | `GPIOD_PSR` | Port Status(唯讀,反映實際 pin 狀態) |
|
||
| `+0x0004` | `GPIOD_PDDR` | Port Data Direction(bit=1 輸出,bit=0 輸入) |
|
||
| `+0x0008` | `GPIOD_PSOR` | Port Set(寫 1 → 該腳拉高,其他 pin 不影響) |
|
||
| `+0x000C` | `GPIOD_PCOR` | Port Clear(寫 1 → 該腳拉低,其他 pin 不影響) |
|
||
| `+0x0010` | `GPIOD_PTOR` | Port Toggle |
|
||
| `+0x0014` | `GPIOD_PIDR` | Port Input Disable |
|
||
|
||
### 14.4 API
|
||
|
||
```c
|
||
int gpio_devmem_init(void); // 開啟 /dev/mem + mmap GPIO_C
|
||
void gpio_devmem_cleanup(void); // munmap + close
|
||
int gpio_devmem_set_direction(int gpio, int out); // gpio: 1-4,out: 1=輸出
|
||
int gpio_devmem_set(int gpio, int value); // set high/low via PSOR/PCOR
|
||
int gpio_devmem_get(int gpio); // 讀 PSR bit(0 或 1)
|
||
```
|
||
|
||
### 14.5 使用方式(SPI bit-bang)
|
||
|
||
在 `compile.sh` 加入 `-DSPI_BITBANG` 後,`can_bus.c` 改用 gpio_devmem 模擬 SPI 時序:
|
||
|
||
```bash
|
||
# compile.sh 加入的 flags:
|
||
-DSPI_BITBANG
|
||
-DSPI_GPIO_CS=1 # J15 Pin1
|
||
-DSPI_GPIO_MOSI=2 # J15 Pin2
|
||
-DSPI_GPIO_SCK=3 # J15 Pin3
|
||
-DSPI_GPIO_MISO=4 # J15 Pin4
|
||
```
|
||
|
||
### 14.6 注意事項
|
||
|
||
- 需要 root 權限(`CAP_SYS_RAWIO`,firmware 本身已以 root 執行)
|
||
- mmap 以 page(4096 bytes)為單位,base 已自動對齊,偏移在程式碼中修正
|
||
- 若日後 pinmux 問題解決,可切回 `/dev/spidev1.0` 硬體 SPI(移除 `-DSPI_BITBANG`)
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 15. STDC 模型重訓練與後處理 Python→C 移植
|
||
|
||
### 15.1 重訓練背景
|
||
|
||
原始 STDC 模型以公開道路資料集訓練,對高爾夫球場環境的辨識率不足:
|
||
|
||
- 球道(fairway)被誤判為草地(grass)或道路(road)
|
||
- 沙坑(bunker)辨識率偏低
|
||
- 球場邊緣樹木與一般道路樹木特徵差異大
|
||
|
||
因此收集球場現場影像,重新標注並 fine-tune 模型,提升球場環境下的分類準確度。
|
||
|
||
> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> *(訓練資料範例圖)*
|
||
|
||
### 15.2 重訓練流程
|
||
|
||
```
|
||
現場採集影像
|
||
│
|
||
▼
|
||
人工標注(8 類:道路、草地、車輛、行人、沙坑、水塘、植被、樹木)
|
||
│
|
||
▼
|
||
Fine-tune STDC 模型(基於原始權重)
|
||
│
|
||
▼
|
||
Python 推論驗證(stdc630inference.py)
|
||
│
|
||
▼
|
||
Kneron Model Zoo 轉換 → .nef 格式(KL630 NPU 使用)
|
||
│
|
||
▼
|
||
STDC_0520.nef → 部署到裝置
|
||
```
|
||
|
||
> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> *(訓練前後準確度對比圖)*
|
||
|
||
### 15.3 後處理 Python → C 移植
|
||
|
||
原先推論結果的後處理(ROI 計算、類別佔比、警告旗標)全部在 Python(`stdc630inference.py`)中完成,無法在嵌入式端即時執行。
|
||
|
||
**移植目標**:完全在 KL630 上執行,零 Python 依賴。
|
||
|
||
| 項目 | Python 原版 | C 移植版(`stdc_post_process.c`) |
|
||
|------|------------|----------------------------------|
|
||
| 分割圖解析 | numpy 陣列切片 | 直接 pointer 操作 NPU output buffer |
|
||
| 前進 ROI(梯形) | 向量化條件判斷 | `stdc_is_in_forward_roi()` 逐像素計算 |
|
||
| 碰撞 ROI | slice 計算 | 固定比例邊界,整數乘法 |
|
||
| 動態偵測(is_moving) | frame diff | `stdc_compute_motion_diff()` + `prev_roi_luma` 快取 |
|
||
| 草地持續時間計時 | time.time() | `gettimeofday()` + `consecutive_grass_frames` |
|
||
| 輸出 | JSON / 終端機 | `stdc_analysis_t` struct → event_recorder |
|
||
|
||
**正規化對齊**:C 版輸入正規化設為 `KP_NORMALIZE_KNERON`(= `/256 − 0.5`),與 Python 訓練時的前處理一致,確保推論結果數值相同。
|
||
|
||
> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> *(Python vs C 輸出比對畫面)*
|
||
|
||
### 15.4 相關原始碼
|
||
|
||
| 檔案 | 說明 |
|
||
|------|------|
|
||
| `src/pre_post_proc/stdc_post_process.c` | 後處理主體:ROI 計算、類別佔比、警告旗標、動態偵測 |
|
||
| `src/app_flow/stdc_inf_single_model.c` | NPU 推論配置:正規化、resize、post_proc callback 綁定 |
|
||
| `include/stdc_post_process.h` | `stdc_analysis_t` 結構定義、閾值常數 |
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 16. 快照功能:實作 / 事件觸發 / 打包上傳
|
||
|
||
### 16.1 Snapshot function implement
|
||
|
||
#### 架構選擇
|
||
|
||
KL630 VMF 提供 `VMF_SNAP`(Video Snapshot Mechanism)模組,可從現有 SSM 影像管線直接擷取 JPEG,無需額外複製 YUV 緩衝區。
|
||
|
||
```
|
||
SSM Ring Buffer (vsrc_ssm_0, 1920×1080)
|
||
│
|
||
▼
|
||
VMF_SNAP_Init() ← Lazy init,首次觸發時才建立(避免閒置佔資源)
|
||
opt.dwStreamIdx = 0
|
||
opt.dwQp = 75 ← JPEG 品質
|
||
│
|
||
▼
|
||
VMF_SNAP_ProcessOneFrame() ← 在 VMF 影格 send callback 呼叫
|
||
│ (必須在 VMF 執行緒內,不可跨執行緒)
|
||
▼
|
||
JPEG buffer (MemBroker, 最大 2MB)
|
||
│
|
||
▼
|
||
save_jpeg("/tmp/ev_<id>/level1.jpg")
|
||
```
|
||
|
||
#### 跨執行緒協調(SnapReq 機制)
|
||
|
||
VMF_SNAP 只能在 VMF 影格 callback 執行緒中呼叫。事件判斷在 recv callback(不同執行緒)。解法:
|
||
|
||
```
|
||
[recv callback 執行緒] [VMF send callback 執行緒]
|
||
event 觸發
|
||
→ request_snap() → event_recorder_provide_frame()
|
||
寫入 g_snap_req 讀取 g_snap_req.active
|
||
g_snap_req.active = 1 若 active → 執行 VMF_SNAP
|
||
(mutex 保護) 清除 active
|
||
存檔
|
||
```
|
||
|
||
若上一次快照尚未完成,新請求會被丟棄並 log(避免 race condition)。
|
||
|
||
> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> *(快照存檔後的 JPEG 範例)*
|
||
|
||
---
|
||
|
||
### 16.2 Snapshot trigger by specific event
|
||
|
||
快照由兩類事件觸發,時機與邏輯不同:
|
||
|
||
#### 草地違規(連續事件)
|
||
|
||
每升一個 level 拍一張,事件結束後統一打包:
|
||
|
||
```
|
||
GRASS_L1 進入 → snap "level1.jpg"(immediate_upload = 0)
|
||
GRASS_L2 進入 → snap "level2.jpg"(immediate_upload = 0)
|
||
GRASS_L3 進入 → snap "level3.jpg"(immediate_upload = 0)
|
||
事件結束 → launch_upload(images=["level1.jpg", "level2.jpg", ...])
|
||
```
|
||
|
||
#### 碰撞 ROI 單次事件(Rising edge)
|
||
|
||
偵測到的瞬間拍照並立即上傳:
|
||
|
||
```
|
||
偵測到 person / bunker / pond / tree 進入碰撞 ROI(0→1 上升緣)
|
||
→ snap "snapshot.jpg"(immediate_upload = 1)
|
||
→ 快照完成後立即 launch_upload(),delay_ms = 0
|
||
```
|
||
|
||
| 事件類型 | 觸發條件 | 快照檔名 | 上傳時機 |
|
||
|---------|---------|---------|---------|
|
||
| 草地 L1 | on_grass & is_moving | level1.jpg | 事件結束後 |
|
||
| 草地 L2 | 草地持續 ≥ 6s | level2.jpg | 事件結束後 |
|
||
| 草地 L3 | 草地持續 ≥ 10s | level3.jpg | 事件結束後 |
|
||
| 行人碰撞 | col_person_ratio 0→1 | snapshot.jpg | 立即 |
|
||
| 沙坑碰撞 | col_bunker_ratio 0→1 | snapshot.jpg | 立即 |
|
||
| 水塘碰撞 | col_pond_ratio 0→1 | snapshot.jpg | 立即 |
|
||
| 樹木碰撞 | col_tree_ratio 0→1 | snapshot.jpg | 立即 |
|
||
|
||
> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> *(各事件觸發快照的對比圖)*
|
||
|
||
---
|
||
|
||
### 16.3 Zip event snapshots and send to GF cloud
|
||
|
||
#### 打包流程
|
||
|
||
事件結束後由獨立 detached thread 執行,不阻塞推論主迴圈:
|
||
|
||
```
|
||
upload_thread (detached)
|
||
│
|
||
├─ [可選] sleep upload_delay_ms(目前設 0,立即執行)
|
||
│
|
||
├─ write_event_json("/tmp/ev_<id>/event.json")
|
||
│ {
|
||
│ "id": "<unix timestamp>",
|
||
│ "date": "2026-04-16T10:30:00+08:00", ← 台灣時間 UTC+8
|
||
│ "type": "grass",
|
||
│ "max_level": 2,
|
||
│ "duration_sec": 8.3,
|
||
│ "images": ["level1.jpg", "level2.jpg"]
|
||
│ }
|
||
│
|
||
├─ build_targz()
|
||
│ (cd /tmp/ev_<id> && tar cf - .) | gzip -c > /tmp/sdcard/events/event_<id>_<ts>.tar.gz
|
||
│ sync() ← SD 卡寫入確保
|
||
│
|
||
├─ http_post_file()
|
||
│ kCurl --data-binary @event_<id>_<ts>.tar.gz
|
||
│ 'http://192.168.0.99/api/golf.cgi?filename=event_<id>_<ts>.tar.gz'
|
||
│
|
||
├─ sd_cleanup() ← 總大小超過 7GB 時刪除最舊的 .tar.gz
|
||
│
|
||
└─ rm -rf /tmp/ev_<id> ← 清除暫存工作目錄
|
||
```
|
||
|
||
#### tar.gz 內容結構
|
||
|
||
```
|
||
event_<id>_20260416_103000.tar.gz
|
||
├── event.json ← 事件 metadata
|
||
├── level1.jpg ← 草地 L1 現場快照
|
||
├── level2.jpg ← 草地 L2 現場快照(若存在)
|
||
└── level3.jpg ← 草地 L3 現場快照(若存在)
|
||
```
|
||
|
||
#### 上傳端點
|
||
|
||
| 環境 | URL |
|
||
|------|-----|
|
||
| 開發測試 | `http://192.168.0.114:8081/api/golf.cgi` |
|
||
| 生產(GF cloud) | `http://192.168.0.99/api/golf.cgi` |
|
||
|
||
透過 INI `upload_url` 切換,無需重新編譯。
|
||
|
||
#### SD 卡管理
|
||
|
||
- 每次上傳後執行 `sd_cleanup()`
|
||
- 掃描 `/tmp/sdcard/events/` 下所有 `.tar.gz`,超過 `sd_max_mb`(預設 7168 MB)時按 mtime 由舊到新刪除
|
||
- 即使網路不通,tar.gz 仍保留在 SD 卡
|
||
|
||
> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> *(GF cloud 後台收到事件的截圖)*
|
||
|
||
---
|
||
|