gf_ai_box/docs/technical_report.md

61 KiB
Raw Blame History

KL630 Host Stream 技術報告

目錄

  1. 系統概覽
  2. 硬體架構
  3. 軟體管線架構
  4. IMX662 DOL-HDR 雙曝光 ISP
  5. 程式碼結構詳解
  6. 從零開始:新裝置完整部署流程
  7. 常見問題排查
  8. 關鍵參數速查表
  9. STDC 語義分割邏輯詳解
  10. 下一階段:高爾夫球車警示與煞車控制
  11. 藍牙通訊DX-BT24 BLE UART 模組
  12. CAN Bus 通訊MCP2515 SPI 控制器
  13. 事件錄製器Event Recorder
  14. GPIO 直接存取gpio_devmem
  15. STDC 模型重訓練與後處理 Python→C 移植
  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.34LD_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] (通常 = 25616-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)

[sensor]
sensor_cfg  = "./Resource/VIC/0/imx662_1920x1080_ch0.cfg"   ; 主通道(長曝光)
fusion_cfg  = "./Resource/VIC/1/imx662_1920x1080_ch1.cfg"   ; Fusion 通道(短曝光)

fusion_cfg 這一行不可被注解或刪除。程式碼邏輯:

// 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=FUSIONAWB 仍然只看單通道統計,無法正確收斂。

層面三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)

// 核心迴圈
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 影格後疊加邊界框:

// 正確的 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 分派

switch (job_id) {
    case STDC_JOB_ID:   // = 200
        stdc_inference(job_id, ...);
        break;
    // 其他 job ID...
}

5.5 host_stream.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=關閉 HDMI1=啟用
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

# 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

# 在 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 binaryrpath 指向 $ORIGIN/lib
  3. patchelf 將 dynamic linker 從 glibc 改為 uClibc
  4. patchelflibc.so.6libc.so.0uClibc soname

步驟 2架設 HTTP 伺服器Host PC

# 在 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 IP192.168.3.1(透過 USB 網路介面)
  • Port8080
  • 提供檔案:kp_firmware_host_streamhost_stream.ini

步驟 3連線裝置並執行一次性 ISP 設定

# SSH 連入裝置USB 網路或序列埠)
ssh root@192.168.3.10

# 在裝置上,首次只需執行一次:
sh /tmp/deploy.sh --setup

或手動執行以下指令(效果相同):

BIN_DIR=/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin

# 修正 1AWB 雙曝光統計模式
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每次重新編譯後執行

# 在裝置上
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驗證執行

# 在裝置上確認程式執行
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 = NORMALAES 無法處理 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] 1RTSP/ 0HDMI/RTSP+HDMI 推論用 stream 索引
DrawBoxEnable [nnm] 1 必須,否則語義分割疊加不會執行
DrawOnResize [nnm] 1InferenceStream=0 時) 在 resize stream 上畫框
StreamCount [streamer] 2RTSP/ 1HDMI 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.6libc.so.0 glibc soname → uClibc soname

9. STDC 語義分割邏輯詳解

本節說明目前 stdc_post_process.capp_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定義

碰撞 ROICollision ROI

以影像分割圖比例定義,判斷是否有危險物體進入正前方區域:

左邊界:  25% 寬度   右邊界:  75% 寬度
上邊界:  25% 高度   下邊界:  70% 高度

對應 1920×1080 畫面x=480 y=270 w=960 h=486DrawBox 視覺輸出位置)

前進 ROIForward 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_warninggreenery_warningtree_dense 在球場環境中幾乎常時觸發,不納入煞車邏輯,僅保留為 HUD 顯示資訊。

9.5 視覺疊加輸出DrawBox1920×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 GPIOSPI 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 2collision_risk == 1person_warning == 1
  • Level 1car_warning == 1pond_warning == 1bunker_warning == 1tree_approaching == 1

10.5 新增 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 fpscollision_risk 觸發到 GPIO 拉高延遲 < 80ms是否符合需求
安全冗餘 視覺 AI 單點失效時的 failsafe 策略(例如:推論超時 → 強制觸發警告)


11. 藍牙通訊DX-BT24 BLE UART 模組

11.1 模組規格

項目 規格
模組型號 DX-BT24
通訊介面 透明 UART → BLEBLE 4.2
BLE 服務 UUID 0xFFE0Notify FFE1裝置→手機Write FFE2(手機→裝置)
UART 預設鮑率 9600 bps出廠一次性升級至 115200 bps
KL630 UART 腳位 /dev/ttyS1J15 擴充連接器)

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\nBLE 以封包為單位):

{"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.inibt_at_probe = 1
  2. 啟動韌體 — bt_uart_init() 偵測模組當前鮑率並送出 AT+BAUD7→115200AT+RESET
  3. 確認 /tmp/fw.log 出現 [BT] upgrade complete
  4. bt_at_probe 改回 0(避免 AT 指令被 BLE 客戶端看到)

11.5 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 公開 APIbt_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 驗證方法

裝置端(韌體啟動後):

# 手動送出 JSONstty 先設定鮑率)
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 0x10011-bit Standard Frame可 INI 設定)

J16 接線(依板上標示順序):

J16 腳位 MCP2515 模組
CS CS
DO SI (MOSI)
CLK SCK
RESERVED SO (MISO)

註:若板上 RESERVED 版本實際未接到 SPI_1_DI_EMCP2515 將無法回應。

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 草地 L3T+10s 嚴重違規,強制停車
10 SPEED_LEVEL_2 草地 L2T+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 初始化流程

/* 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 暫存器
  │               印出 hintTX 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 參數

[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_BITBANGMCP2515 透過 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 公開 APIcan_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 hysteresisGRASS_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 輸出)

{"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 參數

[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 公開 APIevent_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 Directionbit=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

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-4out: 1=輸出
int  gpio_devmem_set(int gpio, int value);          // set high/low via PSOR/PCOR
int  gpio_devmem_get(int gpio);                     // 讀 PSR bit0 或 1

14.5 使用方式SPI bit-bang

compile.sh 加入 -DSPI_BITBANG 後,can_bus.c 改用 gpio_devmem 模擬 SPI 時序:

# 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_RAWIOfirmware 本身已以 root 執行)
  • mmap 以 page4096 bytes為單位base 已自動對齊,偏移在程式碼中修正
  • 若日後 pinmux 問題解決,可切回 /dev/spidev1.0 硬體 SPI移除 -DSPI_BITBANG


15. STDC 模型重訓練與後處理 Python→C 移植

15.1 重訓練背景

原始 STDC 模型以公開道路資料集訓練,對高爾夫球場環境的辨識率不足:

  • 球道fairway被誤判為草地grass或道路road
  • 沙坑bunker辨識率偏低
  • 球場邊緣樹木與一般道路樹木特徵差異大

因此收集球場現場影像,重新標注並 fine-tune 模型,提升球場環境下的分類準確度。

<ED><A0><BD><ED><B3><B7> (訓練資料範例圖)

15.2 重訓練流程

現場採集影像
    │
    ▼
人工標注8 類:道路、草地、車輛、行人、沙坑、水塘、植被、樹木)
    │
    ▼
Fine-tune STDC 模型(基於原始權重)
    │
    ▼
Python 推論驗證stdc630inference.py
    │
    ▼
Kneron Model Zoo 轉換 → .nef 格式KL630 NPU 使用)
    │
    ▼
STDC_0520.nef  →  部署到裝置

<ED><A0><BD><ED><B3><B7> (訓練前後準確度對比圖)

15.3 後處理 Python → C 移植

原先推論結果的後處理ROI 計算、類別佔比、警告旗標)全部在 Pythonstdc630inference.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 訓練時的前處理一致,確保推論結果數值相同。

<ED><A0><BD><ED><B3><B7> (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_SNAPVideo 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

<ED><A0><BD><ED><B3><B7> (快照存檔後的 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 進入碰撞 ROI0→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 立即

<ED><A0><BD><ED><B3><B7> (各事件觸發快照的對比圖)


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 卡

<ED><A0><BD><ED><B3><B7> (GF cloud 後台收到事件的截圖)