chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

お越しいただきありがとうございます。

このブログでは主に以下の内容を記載しています(自分の記録用)。

  • 何か作った話(自分は何かを作るために生きている..できたらクスっと笑えるものや、アートと絡みたい)(長続きせず、飽きるのが問題)
  • 音楽(AerophoneやDTM(FL-Studio))を勉強しようと思ったが、音楽はレッスンが続けられないので、、絵に転向しようとしたが、DTMに戻った(が、悶々とするだけで何も作っていない)。テクノ音楽を作れるよう、オンライン教材やソフト(AbletonLive等)に投資中。でもLLMに追い越されて、プロンプト入力したら勝手に音楽作ってくれる時代らしい。
  • 趣味のロードバイク(坂道をひーこら走ると結構大変な人生(笑)と重なる。。その後、定年・再雇用でメインタスクからはずれて仕事のストレスがほぼゼロで最近は全く走っていない。。いかん)

記事が渾然一体となってカオス状態ですが、マイコンのプログラムも、ちゃりで走るのも、料理するのも、自分の中ではそれぞれが繋がっているので、カオスな日常をそのまま反映させています。リンクや引用はご自由にどうぞ。質問等ありましたらコメントに書いてください。微力ながら自分の分かる範囲で回答します。
とにかく、、人に「あほやなぁ」と言われる事に精進するのが我が人生。へこんでも創造の力で進みたい

■2025年目標
今年は音楽(EDM)とAI(ミニマムなLLM、エッジAI(tiny ML))に全振りする(時間、お金、そして情熱?)。

■スターを付けてくださる方へのお礼
古い記事を見つけて時々スターを付けてくださる方がおられます。わざわざお越しくださりありがとうございます。不明な点があればコメント欄に書いておいていただけると、回答できるかもしれません。

RP2350/HSTX/TMDSでDVI信号を出す、モノクロ版

背景:RP2350に搭載されているHSTX(w/ TMDSエンコーダ)を使ってDVI信号を生成してTVでグラフィック描画表示したい
取り組み:VGAサイズでもカラーにするとRP2350のSRAMに乗らないが、モノクロにすると外付けメモリ無くてもSRAM上でフレームバッファを確保することができる。640x480によるフレームバッファ確保に先立ち、HSTX/TMDSでモノクロ出力する設定方法を確認
結果:REPEATを使ってるので、縦じましか出せないが、モノクロ表示を確認
詳細:

ソースコード要約

HSTXのTMDS Expanderの設定。エンコーダへの取り出しは、1bit幅で32回シフトする。(モノクロなので1bitずつ取り出す)

#
# EXPAND SHIFT
#
# EXPAND_SHIFT
# RAW_SHIFT: 32
# RAW_N_SHIFTS: 1

RAW_SHIFT = 0       # 0 means 32
RAW_N_SHIFTS = 1

# encoded expand
# each 1bit (monoral) and 32 times(register setting 0)
#
ENC_SHIFT=1
ENC_N_SHIFTS=0  # N_SHIFT 0 means 32
#
#
val = ENC_N_SHIFTS << 24 | ENC_SHIFT << 16 |RAW_N_SHIFTS << 8 | RAW_SHIFT
write_HSTX_CTRL_EXPAND_SHIFT(val)

3つのTMDSエンコーダにどう渡すのかを指定するHSTX_CTRL:EXPAND_TMDS Registerの設定は以下
25回右ローテートすることでMSBの位置のbit0をbit7まで移動させて、bit幅1(設定値は0)を3レーンのエンコーダに渡す。同じ値を渡しているので、白色か黒色のモノクロになると。。

#
# EXPAND TMDS  MONO COLOR

l0_nbits = 0
l0_rot = 25
l1_nbits = l0_nbits
l1_rot = l0_rot
l2_nbits = l0_nbits
l2_rot = l0_rot

val = l2_nbits << 21 | l2_rot << 16 | l1_nbits << 13 |  l1_rot << 8 |  l0_nbits << 5 |  l0_rot 
write_HSTX_CTRL_EXPAND_TMDS(val)

描画用データは以下で640回リピート。描画パターンは、1111_1111で白い太線で始まり、010101と細かいストライプになるはず。

# mono color
pixel_pattern = 0b1111_0000_1100_1100_1010_1010_1111_1111
                  *略*
        # DISPLAY DATA
        buffer[index] = 0x00_00_30_00 + VGA_WIDTH
        index += 1
        buffer[index] = pixel_pattern
        index += 1
表示確認

改めて見てみると、、出力パターンのLSBから描画されるので、最初は8本分の太い白線から始まる。次に、黒白黒白・・・

ソースコードは以下
github.com

RP2350のDMA転送速度を確認

背景:DMAでいろいろややこしい使い方をしようとしているので、、沼に落ちるのを避けるため簡単なサンプルコード作成と、転送速度を測る
取り組み:まずは1chだけのDMAを使ってGPIOのPinをLHした時の周期を測定
結果:システムクロックの速度(150MHz)でPinをLHできるのかと単純に思っていました。実際計測すると37MHzの速度に留まっていました。これはデータ取り込み等のオーバーヘッドがあるためらしいです*1
詳細:
この方法がベストかどうか分かりませんが、、GPIOのレジスタをDMAでL/H設定する。
テストコード

#
# DMA speed test simple
# dma0 
# dma0  L->H->L->H...    27ns 37MHz
# 
#
from rp2 import DMA
from uctypes import addressof
from array import array

from machine import Pin
pin0 = Pin(0,Pin.OUT)
pin0.low()

BASE_IO_BANK0 = 0x40028000
OFFSET_GPIO0_CTRL = 0x4
GPIO0_CTRL_REG_ADDR = BASE_IO_BANK0 + OFFSET_GPIO0_CTRL

buf_HL = array("I", [0] * 50)
for i in range(0, len(buf_HL), 2):
   buf_HL[i] = (3 << 12) + 5
   buf_HL[i+1] = (2 << 12) + 5

dma0 = DMA()

def dma0_handler(arg):
   print('dmq0 fin')

SIZE_WORD=2
dma0_ctrl = dma0.pack_ctrl(enable = True, size=SIZE_WORD, inc_write=0, inc_read=True, irq_quiet=False)
transfer_count = 10
dma0.irq(dma0_handler)

while True:
    dma0.config(write=GPIO0_CTRL_REG_ADDR, read=buf_HL, count=transfer_count, ctrl=dma0_ctrl, trigger=False)
    print('start address')
    hex(addressof(buf_HL))
    machine.mem32[GPIO0_CTRL_REG_ADDR] = (2 << 12) + 5
    dma0.active(1)
    time.sleep(0.1)
    print('read:', hex(dma0.read))
    print('count:', dma0.count)
    print(dma0.unpack_ctrl(dma0.ctrl))
    
    # dma0  L->H->L->H...    27ns 37MHz

山->谷にかかった時間をオシロで測定

山ー>谷にかかった時間は、27ns (37.04MHz)であった。これが正しい結果なのか?をAIに質問、回答は以下


Claudeに相談すると、データの取り込みやDMAでの送り出しを考慮すると、4サイクル(27ns (37.04MHz))で1データ(32bit)送り出すのは理にかなっているとの回答
■ご参考
別途、DMAを2つ使ってピンポンで送信した場合の速度も調べました。DMA0による送信、DMA0->DMA1の切り替え、DMA1の送信までの時間を測ると、47ns(21.28MHz)でした。ですので単純に引き算で計算すると、2つのDMA間での切り替えにかかるオーバーヘッドは、20nsで、約3サイクルかかっている計算になります。

2つのDMAをピンポンで動かすサンプルコード

#
# DMA speed test
# dma0 <--> dma1  pin pon  archi tecture
#
# dma0 -> dma1   pin pon  21.28MHz  47ns  (time for H->L)
#

from rp2 import DMA
from uctypes import addressof
from array import array

BASE_IO_BANK0 = 0x40028000
OFFSET_GPIO0_CTRL = 0x4
GPIO0_CTRL_REG_ADDR = BASE_IO_BANK0 + OFFSET_GPIO0_CTRL

from machine import Pin
pin0 = Pin(0,Pin.OUT)
pin0.low()

# data set for GPIO to H and GPIO to L
buf_H = array("I", ((3 << 12) + 5,))
buf_L = array("I", ((2 << 12) + 5,))

dma0 = DMA()
dma1 = DMA()

# data size (WORD)
TRANSFER_SIZE_WORD = 2
transfer_size = TRANSFER_SIZE_WORD 

# transfer count
transfer_count = 1

dma0_ctrl = dma0.pack_ctrl(enable = True, size=transfer_size, inc_write=0, inc_read=0, irq_quiet=True, chain_to=dma1.channel)
dma0.config(write=GPIO0_CTRL_REG_ADDR, read=buf_H, count=transfer_count, ctrl=dma0_ctrl, trigger=False)

dma1_ctrl = dma1.pack_ctrl(enable = True, size=transfer_size, inc_write=0, inc_read=0, irq_quiet=True, chain_to=dma0.channel)
dma1.config(write=GPIO0_CTRL_REG_ADDR, read=buf_L, count=transfer_count, ctrl=dma1_ctrl, trigger=False)

dma0.active(1)

*1:自分はDMAって一度走り出すと、パイプラインにように詰め詰めで転送してくれるものと単純に想像していました

RP2350/MicroPythonの割込みハンドラのオーバーヘッドを調べる->おおよそ32us

背景:DVI信号を生成する。DMA/HSTX/PIOを使うが、MicroPythonによる制御も必要
課題:MicroPythonで行単位の制御、画面単位の制御を任せられるか不明。どれぐらいの速度で処理できるのか明らかにする
取り組み:簡易的にPinのIRQを発行して、割り込みハンドラが起動されるまでの時間をticks_usで計測
結果:自分でPin操作して、割り込みハンドラとして戻ってくるまでおおよそ32usかかった*1。DVI信号で460x480 @60Hzの場合、ブランク領域含めて一行あたりの周期が31.7usになるので、一行ごとに割込み発生させてMicroPythonで処理させるのは不可能。行単位のDMA転送起動はchain等による自動起動でないとだめ。
詳細:
以下のような割込み発生プログラムを試作、実行

from machine import Pin
import time
pin0 = Pin(0, Pin.OUT)
pin0.high()

print(machine.freq())

end_us = 0
@micropython.native
def intr_handler(arg):
    global end_us
    end_us = time.ticks_us()    

#
#
pin0.irq(trigger=Pin.IRQ_FALLING, handler=intr_handler,hard=True)

@micropython.native
def measure():
    global end_us
    measure = []
    for _ in range(10):
        end_us = 0
        pin0.high()
        start_us = time.ticks_us()    
        pin0.low()
        time.sleep_ms(10)
        delta = time.ticks_diff(end_us, start_us) 
        print(f'delta us: {delta} us')
        measure.append(delta)
    print(f'average: {sum(measure)/len(measure)} us')
       
measure()

実行結果は以下

150000000
<irq>
delta us: 36 us
delta us: 36 us
delta us: 36 us
delta us: 31 us
delta us: 32 us
delta us: 30 us
delta us: 29 us
delta us: 30 us
delta us: 31 us
delta us: 29 us
average: 32.0 us

分かった事:変動しているが、割り込みハンドラのオーバーヘッドは平均32usであった。DVI信号の画面描画中、一行ごとにMicroPythonを呼ぶアーキだと間に合わない(一行表示の周期が31.7usのため*2)。水平ブランク->描画->水平ブランク->描画のループはchainでお任せにするか、さらにDMAを追加して、追加したDMAから転送用DMAを自動制御するアーキテクチャにするか、いずれかで実装する必要あり

*1:pinをHにすると処理も含まれるのでこれよりも短いのですが、別途調べると、Hに操作する処理はだいたい4us程度

*2:この計算は概算です

PIO/DMA/HSTXを組み合わせたDVI信号生成案

背景:手軽にDVI信号を出したい、MicroPythonのframebufモジュールを使って描画させたい
アプローチ:TMDSエンコードさせる際課題となる、TMDS制御コード(32bitサイズ)を画素データの間にはめこまれたデータ形式を作る必要がある(通称Zipper形式、制御コード-> 画素->制御コード->画素の並び)。画像の一行分だけバッファを作ってそこでZipper形式を作ってからDMAでHSTX FIFOに転送する方法が多いと思うが、Zipper形式の変換をPIOにまかせてみる*1
結論:案は考えたけど動くかどうか不明


この案のメリット
1. 一行バッファが不要
2. DMAの挙動をもう一つのDMAで制御させるデザインパターンもあるけど、その場合、テーブルが必要になる。テーブルが不要

課題
1. 一行単位でMicroPythonにIRQで割込みが入る。この周期でDMAの再設定が必要。時間的に間に合うのか未確認。間に合わなかったらこの案は不成立*2 *3
2. ハードブロック間のタイミングが間に合うのか不明、
3. アーキが複雑、デバッグが大変かも

課題(最終版)

  • DMA転送終了でIRQによりMicroPythonに割込みを起こし、DMAのchain先を変更する方式は、MicroPythonの割込みオーバーヘッドが大きすぎて処理が間に合わないことが判明。1画面分の転送が終わったかどうかは割込みによるソフト実装は不可。だから、、DMAによるconfig転送か、PIOを使ったカウンタ実装か? ハードによる実装が必要。config転送にすると、バッファがまた大量に必要になるし、実装があまりきれいとは思えないので、PIOを使ったカウンタ実装をやってみる。(1ライン転送終了するごとにPIO FIFOに1を書く。PIOはFIFOに書き込まれたらカウントして、640に到達したら出力FIFOに書き込んで、設定専用DMAを起動する)使うハードや組み合わせが段々と複雑になるが、無駄なバッファ要らないので、PIOで1画面終了判断をさせたい。

*1:PIOをZipper変換装置として使う

*2:そもそも1行データ転送開始にMicroPythonが介在する時点でジッタが発生して画像が乱れるのではなかろうか。HSTXのFIFO分で吸収できればいいけど。。

*3:画像転送DMAの終了時、Chainを使って水平同期のDMAを起動する。これにより遅延なく信号送信が可能

RP2350のHSTXを使ってDVI信号を出す->塗りつぶしはできた

背景:HSTXを使ってDVI信号を出したい
取り組み:RP2350のHSTXとTMDSエンコーダを使ってDVI信号を出す。Cは使わずMicroPythonで実装する
結果:いろいろあったが、最終的に適当な色で640x480で塗りつぶすところまではできた*1
詳細:

まずはRPi Pico2とDVI基板をハンダづけする

5VとGNDのジャンパは急づくりで接続しています(当初5Vの供給が必要と知らなかった。GNDは18番ピン、23番ピンが基板同士で接合されているのでGNDのジャンパは不要と思います)

640x480の塗りつぶしDVIテストプログラムを作成。プログラムの仕様は以下

  • 画素数は640x480*2
  • 省メモリで画面を出すには、画面数行分だけ画像データを作ってC言語等でDMAを起動して高速に処理させるらしい。が、MicroPythonだと描画速度が間に合わないので1画面分のフレームバッファを構成
  • データ圧縮しないとRPiPicoのメモリではフレームバッファが確保できない。TMDSエンコーダの圧縮機能を活用して省サイズ化(PSRAMが搭載されているボードならそんな苦労は不要)
  • DVI信号生成はHSTX(TMDSエンコーダ) + DMAに任せる
  • DMAを2つ使い、チェーンで交互に起動
  • 片方のDMA転送が終わったら割込みを発生させ、DMA転送元アドレスをバッファの先頭に戻す(ここだけMicroPythonが関与)
  • 表示は640x480を同じ色で塗りつぶし*3

MicroPythonはHSTXをサポートしていないので、machine.mem32を使ってHSTXのレジスタを直接操作しています。
以下はレジスタをRead/Writeする関数

def write_reg(base, offset, value):
    machine.mem32[base + offset] = value
def read_reg(base, offset):
    return machine.mem32[base + offset]

塗りつぶしDVIテストプログラムをRPi Pico2に焼いて実行
TV(Sharpの液晶TV)は640x480と認識して、塗りつぶしの四角が表示された。色は適当です。

■参考URL
ソース一式は以下にアップしています。興味のある人はどうぞ(できることは塗りつぶしだけ・・・)
github.com
DVI基板はSwitchScience製
Raspberry Pi Pico用DVIアダプタ基板 — スイッチサイエンス

*1:Claudeにかなり手伝ってもらいました。間違ったこともいろいろ言われましたが、助かった局面の方が多かったです

*2:DVI信号の仕様としては640x480という意味。 framebufferのサイズ制限から、640x480の画素でカラーグラフィック表示できるわけではありません。とはいえ、DMAを駆使したらモノクロなら640x480の表示が可能と思われ(バッファサイズ:37KB)、RGBの8色なら可能かも

*3:今後は塗りつぶしの色を変えたり、点やラインを描画してみる。最終的にはMicroPythonのframe bufferモジュールと連結させたいのですが・・

HSTXの理解(1)Command Expanderを使わず素で出力

背景:HSTXを使ってDVI信号を出したい
取り組み:HSTX制御を理解するため、Command Expanderは使わず、FIFOから入れたデータをシフトしながら出力、波形や周期を確認する
結果:オシロで観察される波形はきれいではないが、意図通りには生成されたと理解*1 !! DDRで動いていない(意図通りではない)!!
詳細:
方針

  • 今回生成する信号はそのままではDVI同期できないが、できるだけDVI信号に近づける。信号はRGB3レーン+画像クロックで構成する。また差動信号として生成する。1画素10bit、1画素(10bit)に同期して画像クロックが出るようにする。クロックの微調整は必要だが、1bitを251MHz付近で出力させる
  • 最終的にはDMA+HSTXでDVI信号出すので(高速処理は不要と期待して)、Cは使わずMicroPythonで試作する

仕様書を見ながら、MicroPythonのmachine.mem32を使って32bit幅でアドレス指定でレジスタに読み書きする。

def write_reg(base, offset, value):
    machine.mem32[base + offset] = value
def read_reg(base, offset):
    return machine.mem32[base + offset]
CLOCKのブロックにおいて、HSTX用のCLKを生成
  • CLOCKS: CLK_HSTX_CTRL Register
    • クロックのソースは、0x1:CLKSRC_PLL_SYSを選択(PLL 150MHzで動いているはず)
  • CLOCKS: CLK_HSTX_DIV Register :
    • 分周: 1 (150MHzを供給)
GPIO
  • FUNCTION:0 (HSTX)を選択
HSTXのブロックにおいて素通しモードを指定
  • HSTX_CTRL: CSR Register
    • CLKDIV:10 (8->10expandをイメージして)
    • N_SHIFTS: 10 (3bitデータを 10 回シフトさせる)
    • SHIFT: 3 (R/G/Bそれぞれ1bitずつで3bit)
    • EXPAND_EN: 0 (Command Expander 使わない)
    • EN: 1 (HSTX有効)
  • HSTX_CTRL: BIT0, BIT1, …, BIT6, BIT7
    • SEL_P/SEL_N: OutShiftRegisterのどの位置から取ってくるかを指定(基本、0,1,2,CLKのどれか)
    • INV: 差動信号にするため、ペアとなる対抗のGPIO出力用にINVビットで反転させる(本人は反転なし)
  • HSTX_CTRL: EXPAND_SHIFT Register (Expandしないので設定不要)
  • HSTX_CTRL: EXPAND_TMDS Register (Expandしないので設定不要)

PythonからFIFOに連続書き込み(32bit)

while True:
    write_HSTX_FIFO(0xC7_1C_71_C7)

C71C71C7とは、RGBの並びが000111000....000111となるパターン、Bit Crossbarを経て3レーンに分割されると、R、G、Bそれぞれが、0101010101... となる。

100MHzサンプリングのオシロで波形見ると以下

不合格になりそうな波形ですが*2、、上の2つは画像クロックを想定していて、差動信号なので、正負反転している。下のうねうねは画像信号(1色分)。テストデータは0101を出しているので、一山で2bit分となる。観測された周期としては、1パルスが83MHzで、ここには0/1が含まれるはずなので、166MHzで動いている(はず)。画像クロック1周期に含まれる画像信号の周期が5山(画素としては10bit分)で、1クロック10bit(1画素)なので、ここもDVIと合っている(はず)(と思ったけど、DDRで出力しているから、HSTXに供給した周波数(150MHz)の2倍の300MHzで動くはずだったが、2倍になっていない。DDRとして動いていない??)

波形のひどさは計測装置によるものか?確認するため、HSTXに供給するクロックを下げて再確認。クロックを下げると以下のように整う。整ったからと言って、150MHz動作時に崩れていない保証はないが。。まぁ拡張基板に直結するので、波形は整うと期待。

Clockのソースとして、0x2 : CLKSRC_PLL_USB(48MHz)を指定、CLOCKS: CLK_HSTX_DIV Register で3分周を指定、これにより、HSTXには16MHzが供給される。オシロで画像信号の一山を計測すると、8MHzになっているので、2画素分のはずなので、計算は合う。(と最初は思ったけど、よく考えるとDDRとして動いていない。供給クロックを間違っているか、DDRの設定がでてきないか)

差動で動く同期信号の波形で、片方にノイズ?が乗っているのはGPIOのPull/PullDown等の設定の差異かもしれない。さらに確認が必要

■追記
Bit Crossbarの設定が間違っている。以下は間違っているソース。SEL_NはSEL_Pの隣ではなく、+3オフセットとすべし (RGBRGB...と並ぶから)

SET_BIT_0 = 0 | (1 << SEL_N_OFFSET)
SET_BIT_1 = SET_BIT_0 | (INV_FLAG << INV_FLAG_OFFSET)

SET_BIT_2 = 2 |  (3 << SEL_N_OFFSET)
SET_BIT_3 = SET_BIT_2 | (INV_FLAG << INV_FLAG_OFFSET)

SET_BIT_4 = 4 | (5 << SEL_N_OFFSET)
SET_BIT_5 = SET_BIT_4 | (INV_FLAG << INV_FLAG_OFFSET)

SET_BIT_6 = CLK_FLAG << CLK_FLAG_OFFSET
SET_BIT_7 = SET_BIT_6 | (INV_FLAG << INV_FLAG_OFFSET)

nth_bit = 0
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_0)

nth_bit = 1
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_1)

nth_bit = 2
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_2)

nth_bit = 3
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_3)

nth_bit = 4
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_4)

nth_bit = 5
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_5)

nth_bit = 6
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_6)

nth_bit = 7
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_7)

1回のフェッチ?というのかで、DDRで6bit取り込むから、以下も間違っている

  • N_SHIFTS: 10 (3bitデータを 10 回シフトさせる) -> 一度に6bit処理できるので、シフトは5回
  • SHIFT: 3 (R/G/Bそれぞれ1bitずつで3bit) -> DDRだから N=6

そもそも、上記、SHIFT:6 / N_SHIFTS:5 というのはデフォルト値なのであった。さすがデフォルト値、よく考えられている(そら設計した人だから)

■まとめ
DDRの理解が不十分で間違っていた。CrossBarでは隣のビットから取ってこず、3つ離れた桁(bit)から取得。SHIFT:3/N_SHIFTS:10は間違いで、DDRのお陰で一度にRGB(=3)*2処理するので、SNIFT:6 / N_SHIFTS:5に修正する。
CLKDIVももう一度考える必要あり。CLKDIV:1とは1シフト(DDRで2bit)(=1 clk_hstx)ごとなのか、1bitごとなのか。。。マニュアルではシフト回数と書いているように読めるが。だったら5か?

■修正版
HSTX_CTRL_CSR Registerは以下を設定

write_HSTX_CTRL_CSR(0x50_05_06_01)  # <<< CLKDIV:5 N_SHIFTS:5 SHIFT:6 EN:1

Cross_Barを修正(DDRの2bit分は隣ではなく、+3離れた位置のbit)

SET_BIT_0 = 0 | (3 << SEL_N_OFFSET)
SET_BIT_1 = SET_BIT_0 | (INV_FLAG << INV_FLAG_OFFSET)
SET_BIT_2 = 1 | (4 << SEL_N_OFFSET)
SET_BIT_3 = SET_BIT_2 | (INV_FLAG << INV_FLAG_OFFSET)
SET_BIT_4 = 2 | (5 << SEL_N_OFFSET)
SET_BIT_5 = SET_BIT_4 | (INV_FLAG << INV_FLAG_OFFSET)
SET_BIT_6 = CLK_FLAG << CLK_FLAG_OFFSET
SET_BIT_7 = SET_BIT_6 | (INV_FLAG << INV_FLAG_OFFSET)

nth_bit = 0
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_0)
nth_bit = 1
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_1)
nth_bit = 2
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_2)
nth_bit = 3
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_3)
nth_bit = 4
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_4)
nth_bit = 5
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_5)
nth_bit = 6
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_6)
nth_bit = 7
write_HSTX_CTRL_BIT(nth_bit, SET_BIT_7)

再度確認、Clockのソースは、0x2 : CLKSRC_PLL_USB(48MHz)を指定、CLOCKS: CLK_HSTX_DIV Register で3分周を指定、これにより16MHzがHSTXに供給されている。画像信号の一山は、15,6MHzで、データとしては01の2bit分なので、1ビットの転送速度は、31.2MHzよって、DDRで動いている。また、CLKDIV:5と設定することで、5シフト分で1サイクルとなっている。名前通り、単純にHSTXに供給しているクロックの5分周と考えたらいいのではと思った。(15.6の5分周だから、3.1MHz) (31.2 * 1/10 = 3.12なので計算通り)

CLOCKブロックで、HSTXへの供給元をPLL_USBに変更する例

CLKSRC_PLL_USB = 2   # 48MHz
val = (CLK_ENABLE << 11) + (CLKSRC_PLL_USB << 5)

write_CLK_HSTX_CTRL(0)  # disable clock
div = 3 << 16
write_CLK_HSTX_DIV(div)   # 1/3
write_CLK_HSTX_CTRL(val)  # enable and PLL source

*1:RGBの3レーンは細かく見ていませんが

*2:プローブのGND接続を手抜きしたせいと思われる