2026年4月28日火曜日

C言語ソースコードをブログに埋め込む


ソースコードをブログにきれいに埋め込みたい

もうかなり長いことブログを書いているのに最適な方法が見つからない。Google BloggerもJimdoも同じコードで同じように表示されるようにっていう課題もある。
今まで試したもの
  • Jimdoの「文章」コンテンツ ==> Google Bloggerで使えない
  • srctohtml ==> 概ね満足してたけどCopyボタンがない
  • CarbonでiFrameを取得 ==> コードが長いと欠落
  • Geminiにコードそのものを食わせてhtmlで出力させる ==> Geminiがコードそのものを勝手に咀嚼したりする
  • で、ひねり出したアイデアが、carbonのiFrameをJavascriptで取得してhtml形式で出力するってんだけど、このJavascriptをGeminiに生成させるって超手抜き。

    で、できたコードがこちら


    これをブラウザで開いて、テクストボックスにコードを入力してボタンを押すとhtmlになる。このhtmlをコピーしてブログ内にペーストする。
    しばらくはこれでいってみようと思う。


    nRF52833で測距(3)


    ちょっと本気出して測距を考えてみたい。が、その前にnRF52833からIQデータを取り出せるようにしたい。

    前回(nRF52833で測距(2))、なんでIQを取り出したいのかってのを書いたつもり。で、忘れないうちにnRF52833モジュールでのやり方を記録しておく。何しろ認知症に片足突っ込んでるので。
    まずは、nRF52833で測距(1)をなぞって、測距できるようにしておく。ただし、SDKのバージョンによってはelfファイルの出力場所が変わってしまうみたいなので、launch.jsonの"executable"はそれに合わせて変更しよう。
    で、nRF52833で測距(1)のmain.cに対して、nrf_dm_populate_report関数を使ってdistance measurementのデータを取得して、それを表示するってのを追加するだけ。、、、なんだけど、動くようになったら大したことないんだけど、謎のエラーに悩まされて四苦八苦した;で、以下にmain.cのすべてを掲載;今回SDKをv3.2.4に更新しました(だいぶ間をあけてしまったから)。なので、distance measurementにuartを追加した部分はコメントの2026.4.11の部分を、IQを出力するために追加した部分は2026.4.28のコメントの部分を見てください。実は大部分はGeminiに協力してもらった。動くものを作るとなるとGeminiだけでは完全なものってのは無理だけど(そりゃーGeminiはハードウェア持ってないからしょうがない)、全部自分で調べるよりは全然ラク。

    全部載せちゃうのは手抜き感あるけど(変化点だけにしろよって説)、まあ、後後のことを考えるとわるくないよね。、、、ソースコード領域にコピーボタンもつけたし。


    ところで、
    まじで戦争やめてほしいのです。

    nRF52833で測距(2)


    ちょっと本気出して測距を考えてみたい。が、その前にnRF52833からIQデータを取り出せるようにしたい。が、どうして測距できるのか調べてみたい。

    Bluetooth6.0からBluetooth Channel Sounding (CS)ってのが導入されていて、RTT(Round Trip Time)やPBR(Phase-Based Ranging)というのがあるらしい。ちなみに以前のnRF52833で測距(1)の結果画面を見ると、
  • high_precision
  • ifft
  • phase_slope
  • rssi_openspace
  • って4種類の測距結果が表示されていて、PBRとphase_slopeってのが対応しているってのはわかるんだけど、それ以外はよくわからない。以前の結果はnRF52833が勝手に計算しているので、これを自力で紐解いてみたい。っていう挑戦。

    2つの通信モジュールの、先に信号を発する方をイニシエータ、もう一方をリフレクタということにしよう。
    イニシエータが無変調連続波を発信するとしよう。
    ベースバンド信号は、
    \[ x_{IB,TX}(t)=e^{j \phi_{IB}(t) } \tag{1} \] ただし、
    \[ \phi_{IB}(t)=\theta_{IB0} \tag{2} \] であり、$ \theta_{IB0} $は、イニシエータのIQデータの初期位相とする。
    イニシエータの局発は
    \[ x_{IL}(t)=e^{j \phi_{IL}(t) } \tag{3} \] ただし、
    \[ \phi_{IL}(t)=\omega_I \cdot t + \theta_{IL0} \tag{4} \] であり、$ \theta_{IL0} $ は局発の初期位相とする。
    直行変調すると、変調波形は
    \[ x_{IR,TX}(t)=x_{IB,TX}(t) \cdot x_{IL}(t) \tag{5} \] \[ x_{IR,TX}(t)=e^{ j \left\{ \phi_{IB}(t) + \phi_{IL}(t) \right\} } \tag{6} \] \[ x_{IR,TX}(t)=e^{ j \left\{ \theta_{IB0} + \omega_I \cdot t + \theta_{IL0} \right\} } \tag{7} \] ほんとうは、これの実部が電波として飛び出していくんだけど、めんどくさいのでここまでにして、これを距離D離れた場所にあるリフレクタが受信したとする。
    受信波形は
    \[ x_{RR,RX}(t)=e^{ j \left\{ \theta_{IB0} + \omega_I \cdot (t-\frac{D}{c}) + \theta_{IL0} \right\} } \tag{8} \] 本当は実部を受信して、直交復調してLPFするんだけど、めんどくさいので、、、そうだなー、、、ヒルベルト変換して直交復調するって体で
    リフレクタの局発を
    \[ x_{RL}(t)=e^{j \phi_{RL} } \tag{9} \] ただし、
    \[ \phi_{RL}(t)=\omega_R \cdot t + \theta_{RL0} \tag{10} \] であり、$ \theta_{RL0} $は局発の初期位相とすると、復調波形は
    \[ x_{IB,TX}(t)=x_{RR,RX}(t) \cdot \overline{ x_{RL}(t) } \tag{11} \] \[ x_{RB,RX}(t) = e^{ j \{ \theta_{IB0} + \omega_I (t-\frac{D}{c}) + \theta_{IL0} \} } \cdot e^{ -j \{ \omega_R t + \theta_{RL0} \} } \tag{12} \] \[ x_{RB,RX}(t) = e^{ j \{ (\omega_I - \omega_R)t + \theta_{IB0} + \theta_{IL0} - \theta_{RL0} - \omega_I \frac{D}{c} \} } \tag{13} \] さらに、
    局発の位相状態を維持しながらこれとまったく逆の経路で無線送受信すると、イニシエータの受信波形の復調波形は
    \[ x_{IB,RX}(t) = e^{ j \{ (\omega_R - \omega_I)t + \theta_{RB0} + \theta_{RL0} - \theta_{IL0} - \omega_R \frac{D}{c} \} } \tag{14} \] ここで、
    \[ \theta_{IB0}=\theta_{RB0}=0 \tag{15} \] に制御したとすると、(13),(14)は \[ x_{RB,RX}(t) = e^{ j \{ (\omega_I - \omega_R)t + \theta_{IL0} - \theta_{RL0} - \omega_I \frac{D}{c} \} } \tag{16} \] \[ x_{IB,RX}(t) = e^{ j \{ (\omega_R - \omega_I)t + \theta_{RL0} - \theta_{IL0} - \omega_R \frac{D}{c} \} } \tag{17} \] (16),(17)より \[ x_{RB,RX}(t) \cdot x_{IB,RX}(t)=e^{-j ( \omega_I + \omega_R ) \frac{D}{c} } \tag{18} \] \[ arg(x_{RB,RX}(t) \cdot x_{IB,RX}(t))=- ( \omega_I + \omega_R ) \frac{D}{c} \tag{19} \] \[ D=-\frac{arg(x_{RB,RX}(t) \cdot x_{IB,RX}(t)) }{ \omega_I + \omega_R }c \tag{20} \] なので、$ D $が求められるんだけど、実際の$ \omega_I $,$ \omega_R $なんてランタイムで求めることはできないし、クリスタルをソースとしていると(クリスタルがおそらく最も精度と価格のバランスが優れていると思うが)、温度や経年で変わってくる。ここで、Geminiに聞いてみるとBluetoothSIGはBLEで使われるモジュールに対して周波数+/-20ppmを求めているらしい。
    なので、(20)によると距離計算結果に対しても$ \pm 20 ppm $(←もっとちゃんと計算しろ)の影響なので、無視してもいいのだろう。
    ところで(20)だけ見ると、これだけで$D$が求まりそうだけど、距離によって位相が何周回ったかまではわからないので、複数の周波数での位相差を使って距離を算出する。
    まあ、ここで、 \[ \omega_I=\omega_R=\omega_c \tag{21} \] として \[ \phi=arg(x_{RB,RX}(t) \cdot x_{IB,RX}(t)) \tag{22} \] とすると、 \[ \phi=-\frac{2 \omega_c D}{c} \tag{23} \] 複数の周波数で測定するとして \[ \Delta \phi=-\frac{2 \Delta \omega_c D}{c} \tag{24} \] ってことで、複数の周波数でイニシエータとリフレクタでIQを受信することで周波数vs位相の傾きから距離を推定できそうってなるわけだ。
    まあ、AIに聞くとこれってMCPDって名称だぜって、スパッと式を出してくれるんだけどね(T_T)
    ところで、ここまできたら、nrf52833で受信したIQを取得するのってどうするの?ってのはちょっと後回しにして、実はすでにできているので、実際のデータで検証してみる。

    実際のデータはこちら(ちなみにイニシエータ側とリフレクタ側にデータとしては同じものが出力されます、、、当然だけど)
    Measurement Result

    Addr: E3:C2:11:AD:97:82 (random)

    Quality: ok

    Distance: mcpd: high_precision=1.58 ifft=0.88 phase_slope=2.53 rssi_openspace=2.51 best=0.88

    --- IQ Data Export (MCPD) ---
    Step | Freq_Idx | Real_Freq | Local(I, Q)           | Remote(I, Q)
    [04] | 2404 MHz | (  23.000,  -49.000) | (  66.000,    3.000)
    [05] | 2405 MHz | ( -52.000,   -7.000) | ( -13.000,   62.000)
    [06] | 2406 MHz | (   0.000,  -50.000) | (  60.000,    9.000)
    [07] | 2407 MHz | (  38.000,   27.000) | ( -32.000,  -48.000)
    [08] | 2408 MHz | (  -1.000,   44.000) | ( -54.000,   11.000)
    [09] | 2409 MHz | (  14.000,  -38.000) | (  37.000,  -34.000)
    [10] | 2410 MHz | ( -38.000,    8.000) | (  13.000,   45.000)
    [11] | 2411 MHz | (  33.000,    9.000) | ( -37.000,  -22.000)
    [12] | 2412 MHz | (  32.000,   -4.000) | ( -28.000,  -28.000)
    [13] | 2413 MHz | ( -24.000,  -18.000) | (  35.000,   -4.000)
    [14] | 2414 MHz | (   0.000,  -27.000) | (   8.000,  -32.000)
    [15] | 2415 MHz | ( -24.000,   -6.000) | (  29.000,   -6.000)
    [16] | 2416 MHz | (   0.000,  -21.000) | (  -5.000,  -26.000)
    [17] | 2417 MHz | (  10.000,   15.000) | (  -4.000,   22.000)
    [18] | 2418 MHz | (  -7.000,   16.000) | (  18.000,    8.000)
    [19] | 2419 MHz | (  -6.000,   13.000) | (  17.000,   -2.000)
    [20] | 2420 MHz | (   1.000,  -15.000) | ( -17.000,    2.000)
    [21] | 2421 MHz | (  13.000,    4.000) | (  14.000,    6.000)
    [22] | 2422 MHz | (  14.000,   -2.000) | (  14.000,    5.000)
    [23] | 2423 MHz | (  16.000,    3.000) | (  17.000,   -8.000)
    [24] | 2424 MHz | (  19.000,   -5.000) | (  19.000,  -10.000)
    [25] | 2425 MHz | (  10.000,  -20.000) | (  25.000,    1.000)
    [26] | 2426 MHz | (   4.000,   25.000) | ( -29.000,   -9.000)
    [27] | 2427 MHz | ( -31.000,   -3.000) | (   6.000,   34.000)
    [28] | 2428 MHz | ( -32.000,   15.000) | (   1.000,   40.000)
    [29] | 2429 MHz | (  22.000,   33.000) | ( -46.000,    5.000)
    [30] | 2430 MHz | ( -12.000,   44.000) | ( -21.000,   48.000)
    [31] | 2431 MHz | (  51.000,  -11.000) | ( -46.000,  -37.000)
    [32] | 2432 MHz | (  15.000,   55.000) | ( -28.000,   58.000)
    [33] | 2433 MHz | ( -27.000,   57.000) | (  26.000,   66.000)
    [34] | 2434 MHz | ( -50.000,   48.000) | (  63.000,   45.000)
    [35] | 2435 MHz | ( -41.000,  -65.000) | (  19.000,  -83.000)
    [36] | 2436 MHz | ( -13.000,   80.000) | (  59.000,   69.000)
    [37] | 2437 MHz | ( -64.000,   59.000) | (  97.000,    1.000)
    [38] | 2438 MHz | ( -37.000,   86.000) | ( 100.000,   26.000)
    [39] | 2439 MHz | (  58.000,   80.000) | (  50.000,   96.000)
    [40] | 2440 MHz | ( -26.000,  101.000) | ( 115.000,    1.000)
    [41] | 2441 MHz | (-105.000,   27.000) | (  39.000, -112.000)
    [42] | 2442 MHz | (-101.000,   50.000) | (  49.000, -114.000)
    [43] | 2443 MHz | (-114.000,   26.000) | (  -1.000, -129.000)
    [44] | 2444 MHz | (  78.000,   91.000) | ( 124.000,   42.000)
    [45] | 2445 MHz | ( -39.000,  118.000) | (  91.000, -101.000)
    [46] | 2446 MHz | (-124.000,   30.000) | ( -52.000, -128.000)
    [47] | 2447 MHz | ( 110.000,   70.000) | ( 136.000,   34.000)
    [48] | 2448 MHz | ( 126.000,  -46.000) | (  75.000,  122.000)
    [49] | 2449 MHz | ( -51.000, -127.000) | (-118.000,   86.000)
    [50] | 2450 MHz | (-129.000,  -52.000) | (-149.000,   -9.000)
    [51] | 2451 MHz | ( -52.000,  130.000) | (  -9.000, -150.000)
    [52] | 2452 MHz | (  40.000,  136.000) | (  71.000, -134.000)
    [53] | 2453 MHz | ( 139.000,  -30.000) | ( 143.000,   50.000)
    [54] | 2454 MHz | (  81.000, -119.000) | (  90.000,  122.000)
    [55] | 2455 MHz | (  74.000,  124.000) | (  57.000, -142.000)
    [56] | 2456 MHz | (-136.000,  -52.000) | (-129.000,   83.000)
    [57] | 2457 MHz | ( -19.000,  145.000) | ( -69.000, -140.000)
    [58] | 2458 MHz | ( -99.000, -111.000) | ( -46.000,  148.000)
    [59] | 2459 MHz | ( -39.000, -145.000) | (  36.000,  152.000)
    [60] | 2460 MHz | (-101.000, -112.000) | ( -28.000,  155.000)
    [61] | 2461 MHz | (  35.000,  148.000) | ( -66.000, -147.000)
    [62] | 2462 MHz | ( -38.000, -151.000) | (  76.000,  142.000)
    [63] | 2463 MHz | ( -53.000,  149.000) | (-152.000,  -68.000)
    [64] | 2464 MHz | (  31.000,  158.000) | (-117.000, -122.000)
    [65] | 2465 MHz | (  71.000,  148.000) | ( -98.000, -142.000)
    [66] | 2466 MHz | ( -97.000,  136.000) | (-171.000,   40.000)
    [67] | 2467 MHz | ( -88.000,  146.000) | (-175.000,   36.000)
    [68] | 2468 MHz | (  64.000,  162.000) | (-148.000, -110.000)
    [69] | 2469 MHz | (-138.000, -113.000) | (  96.000,  161.000)
    [70] | 2470 MHz | (-169.000,   62.000) | ( -69.000,  176.000)
    [71] | 2471 MHz | (  -9.000, -183.000) | ( 193.000,    6.000)
    [72] | 2472 MHz | (-167.000,  -77.000) | ( 105.000,  162.000)
    [73] | 2473 MHz | (-184.000,  -16.000) | (  64.000,  185.000)
    [74] | 2474 MHz | (  59.000,  176.000) | (-196.000,   -8.000)
            
    --- End of Report ---
    で、これを処理すると、
    \[ D=-\frac{1}{4 \pi}\frac{\Delta \phi}{\Delta f_c}c \tag{25} \] \[ D \fallingdotseq 4.8m \tag{26} \] うむう、、、SDKが計算した結果はphase_slopeで2.53mなので、かなり差がある。なぜなのかをgeminiに聞いたら、色んな遅延を定数として持っていてそれでもって補正しているはずだって言う。
    じゃぁ、自前でCalibrationするか、その定数をSDKから引っぱり出してくるか、、、


    最近、わけわからんことが多すぎる。全員が善良な社員なら物理的に守れないルールを定めるって一体どういうこと?って。一定数の不真面目な社員がいることを期待しているってことよね。それって信頼関係構築を放棄している?
    とか