到達角度 (AoA) 演算法

為嵌入式系統視覺化高效的 `arcsin` 實作。

1. 問題背景

在嵌入式裝置中,雖然可能配備單精度浮點運算單元 (FPU),但標準 C 語言函式庫的 `asin()` 函數通常使用雙精度浮點數來實作。這會導致在硬體上進行成本高昂的軟體模擬計算,進而影響 UWB (超寬頻) 通訊等應用的即時性要求。

因此,我們採用 查表法 (Lookup Table, LUT) 來取代動態的浮點運算,這是一種典型的「以記憶體換取速度」的策略,能顯著提升執行效能。

2. 輸入與輸出條件

輸入範圍

  • `arcsin` 的標準輸入:[-1.0, 1.0]
  • 為避免浮點運算,輸入值會先乘上一個固定的解析度 (Q9.7 格式的小數部分,即 2⁷ = 128)。

輸出範圍

  • `arcsin` 的標準輸出:[-90°, 90°]
  • 使用 Q9.7 格式,角度值存放於 `int16_t`,範圍為 [-11520, 11520] (因為 90 × 128 = 11520)。

3. LUT 的對稱性優化

`arcsin(x)` 函數在 0 點是奇函數,滿足 arcsin(-x) = -arcsin(x) 的對稱特性。利用這一點,我們只需儲存正區間 [0.0, 1.0] 的對應角度,就可以透過取相反數來推導負區間的結果。這項優化能將 LUT 的尺寸減少一半。

儲存於 LUT 計算得出

LUT 總尺寸 = 129 個 `int16_t` 條目 (索引 0 至 128) × 2 位元組 = 258 位元組

4. LUT 建立方法

建立 LUT 的步驟如下:

  1. 決定輸入解析度:Q9.7 格式提供 128 個步階。每個索引對應的輸入值為 `index / 128.0`。
  2. 計算對應角度:遍歷所有索引,計算其對應的角度值,並轉換為 Q9.7 格式儲存。
for (int i = 0; i <= 128; i++) {
    double input = (double)i / 128.0;         // 正規化輸入 [0.0, 1.0]
    double angle = asin(input) * 180.0 / M_PI; // 弧度轉角度
    arcSinToDegLut[i] = (int16_t)(angle * 128); // 轉為 Q9.7 格式並儲存
}

使用時,若輸入值為正,則直接查表;若為負,則先取其絕對值查表,再將結果取相反數。

5. 範例

索引 (Index) 輸入值 (x) arcsin(x) (°) 存放值 (Q9.7)
0 0.0 0.0 0
43 ~0.34 ~19.625 2512
64 0.5 30.0 3840
128 1.0 90.0 11520

6. 優缺點

優點

  • 避免昂貴的雙精度浮點計算。
  • 執行速度極快,適合即時系統。
  • 記憶體佔用極低 (258 位元組)。

缺點

  • 存在運算誤差 (使用就近取整法時最大約 3.58°)。
  • 增加解析度或改用插值法可降低誤差,但會增加資源消耗。

7. 誤差來源、策略與權衡

LUT 的精度與其實作方式密切相關,尤其取決於查表索引策略。誤差主要來自於量化誤差(將連續輸入對應到離散索引)和 `arcsin` 函數本身的非線性特性(在接近 ±1.0 時斜率急遽增加,放大誤差)。

7.1 解析度、索引策略與理論誤差上限

不同的索引策略會產生截然不同的誤差上限。本文件的模擬器採用的是就近取整 (Round) 策略,其理論誤差上限約為兩點之間最大角度差的一半。

條目數 記憶體 索引策略 理論最大誤差 (°)
129 (Q9.7) 258 B 就近取整 (Round) ~3.58°
線性插值 ~1.79°
257 (Q8.8) 514 B 就近取整 (Round) ~2.53°
線性插值 ~1.27°
513 (Q7.9) 1026 B 就近取整 (Round) ~1.79°
線性插值 ~0.90°

7.2 進階優化:線性插值 (Linear Interpolation)

從上表可見,線性插值是 CP 值極高的優化方案。它不是直接選取最接近的點,而是在兩個相鄰的 LUT 點之間,根據輸入值的位置進行加權平均。這能以極小的計算成本(幾次加減乘除)大幅降低誤差,非常適合嵌入式系統。

其他更複雜的優化方法還包括使用非等距節點(在曲線陡峭處增加取樣點密度)或在極端區間使用多項式近似等,以在有限的記憶體下達到更高的精度。

LUT 產生程式 (C 語言)

這段 C 程式可以在個人電腦上執行,以生成 `arcSinToDegLut[]` 陣列的完整內容。您可以將其輸出結果直接複製到嵌入式裝置的韌體中使用。

#include <stdio.h>
#include <math.h>
#include <stdint.h>

#define Q_FORMAT   128   // Q9.7 -> 2^7 = 128
#define TABLE_SIZE 128   // 輸入 [0.0 .. 1.0] 共 128 個步階

int main(void) {
    printf("static const int16_t arcSinToDegLut[%d] = {\n", TABLE_SIZE + 1);

    for (int i = 0; i <= TABLE_SIZE; i++) {
        double input = (double)i / TABLE_SIZE;
        double angle = asin(input) * 180.0 / M_PI;
        int16_t fixedVal = (int16_t)round(angle * Q_FORMAT);

        if (i % 8 == 0) printf("    ");
        printf("%6d,", fixedVal);
        if (i % 8 == 7 || i == TABLE_SIZE) printf("\n");
    }

    printf("};\n");
    return 0;
}

綜合實踐:互動式 LUT 模擬器

在了解上述原理後,您可以使用下方的滑桿來模擬 `arcsin` 函數的查表過程(採用就近取整法),並與標準函式庫的精確結果進行比較。

LUT 結果

-

標準 `asin` 答案

-

誤差比較

-