2017年6月1日木曜日

【rapsberry pi】 外部データストリームをDMAでメモリへ展開したいんだが

よろずハードウエア処理を好むわたしとしてはDMAって好きなんです.DMAした経験はあるけれど、Linux上でDMAしてthreadを起動したりした経験は無いんです.どうしたらrapsberry piでDMAできるんだろう? 楽チンなライブラリってあるのだろうか?

そんな目論見はあれど、よく判らないのがひら的な実情です.

ラズパイでロボット制御するために、DMAでPWMを発生させようとする試行例はネットに多々紹介されているようです.でも、外部から到来するデータストリームをDMAで取得してメモリに整列させる判りやすい例は見当たらない気がしています.

まずはメインLSIの仕様を読んでみようかと思います.以下、散文的に感想を記します.間違いがあっても許してくれ.


【ラズパイのメインLSI】
wikiによると、1,2,3,zeroとあるラズパイの世代のメインLSIは皆少しづつ異なります.
 ラズパイ1: Broadcom BCM2835
 ラズパイ2: Broadcom BCM2836 と BCM2837 が混ざってる
 ラズパイ3: Broadcom BCM2837
 ラズパイ0: Broadcom BCM2835

これらのペリフェラルの読み物としては、BCM2835版だけがネットに存在し、36と37についてはロクな資料が見つかりません.ペリフェラルについては基本的に35=36=37と思えばOKという考え方のようです.

だがしかし、ペリフェラルのベースアドレスが違うんです.
 BCM2835  0x20000000
 BCM2836  0x3F000000
 BCM2837  0x3F000000
ラズパイ0と1のグループと、ラズパイ2と3のグループに分かれている状況です.

Rasbian OSのデバドラを利用している分にはこのペリフェラルアドレス違いで悩まされる場面は無いと思われますが、GPIO制御などでペリフェラルを直接叩いているようなprogramでは動かないなんていう場面がありそうですから注意が必要と思います.
実際に、ラズパイ1ベースで書かれた古い解説記事を参考にして、それをラズパイ3で動かそうとしたら、このアドレス問題で最初は動きませんでした.

今わたしが使っているのはラズパイ3なので、以下ではラズパイ3を念頭に書くつもりです.


【ラズパイのアドレスマップ】
LSI内部アドレスは3つあるそうです.SDRAMやペリフェラルなどが3つの異なるアドレスに配置されているわけですが、それはMMUで配置された結果であって本体は3つとも同じであるのは言うまでもありません.
  virtual address           ペリフェラル=0xF2000000
  bus address               ペリフェラル=0x7E000000
  physical address         ペリフェラル=0x3F000000

ペリフェラルを直接叩くなら、physical addressを使うので 0x3F000000 がベースアドレスということになります.


【GPIOピン】
DMAにとっての外部窓口はGPIOにしたいです.まずはGPIOがどうなっているのかを調べました.

メインLSIのGPIOは54本あるらしいです.各GPIOはSPIやI2CやUARTを機能重複しているので、例えばGPIO機能を生かすかUART機能を生かすかのセレクトになります.(機能詳細図)

ラズパイ1の初期モデルを除き、GPIOの外部コネクタ配置は右図になっているらしいです.GPIO2~27までの26本しかコネクタに配線されていません.
残りの26本が何をしているのかは知りませんが、たぶんカメラコネクタに配線されたりしているのではないでしょうか.


【GPIOレジスタ】
例として、GPIO0の設定に関係するレジスタを記します.

GPFSEL0[2:0]        pin機能をセレクトする
  000 = GPIO Pin 9 is an input
  001 = GPIO Pin 9 is an output
  100 = GPIO Pin 9 takes alternate function 0
                            中略
  010 = GPIO Pin 9 takes alternate function 5
alternate functionの具体例はさておきます.6種類もあるのかいな?

GPSET0[0]        ここに1を書くとGPIO0=1が出力される
GPCLR0[0]        ここに1を書くとGPIO0=0が出力される
SET/CLRが独立していると、read modify writeをしなくて済むそうだ.

GPLEV0[0]         GPIO0のロジックレベル、GPIO入力としての機能

GPPUD[1:0]       GPIO一括のプルアップ/プルダウンの設定
   00 = Off – disable pull-up/down
   01 = Enable Pull Down control
   10 = Enable Pull Up control
   11 = Reserved

GPPUDCLK0[0]     GPIO0のプルアップ/プルダウンの設定
設定シーケンスがチト面倒臭い.GPIO0をプルアップにする例.
GPPUD[1:0]=10 → 150clk待つ
        → GPPUDCLK0[0]=1 → 150clk待つ
              → GPPUD[1:0]=00 → GPPUDCLK0[0]=0

以上はI/Oに関するものでした.思ったよりもシンプルです.

以下はGPIOポート監視に関するもの.

GPEDS0[0]         GPIO0のレベル/エッジ監視結果
GPIO0に生じた変化をキャッチする.1で変化あり.ここに1をwriteするとクリアされる.

GPREN0[0]         1→GPIO0の立ち上がりエッジ検出をenable
GPFEN0[0]         1→GPIO0の立下りエッジ検出をenable
両方とも1にすると両エッジを検出することになる.
エッジ検出はsystem clkの同期サンプルで動作する.

GPAREN0[0]        1→GPIO0の立ち上がりエッジ検出をenable
GPAFEN0[0]        1→GPIO0の立下りエッジ検出をenable
両方とも1にすると両エッジを検出することになる.
こちらだと非同期サンプルで動作する.

GPHEN0[0]        1→GPIO0がHighを検出するのをenable
GPLEN0[0]         1→GPIO0がLowを検出するのをenable

ポート監視は割り込みに関係すると思われますが、GPIOの項には割り込みについての解説はありませんでした.

以下はGPIO CLOCKに関するもの.

ラズパイの40pinコネクタに出てきているGPIO2~27の中に在るGPIO CLKは5箇所あります.GPFSELで機能選択するとGPIO CLKを使えるようになります.
   GPIO4     GPCLK0
   GPIO5     GPCLK1
   GPIO6     GPCLK2
   GPIO20   GPCLK0
   GPIO21   GPCLK1
これを見ると判るように、GPIO CLKは3系統あります.

GPIO CLKの仕様は、
・最高で125MHz          (BCM2835の場合.36/37でも同じかどうかは不明)
・出力周波数=源発振周波数÷(DIVI + DIVF/1024)
・PLL jitterをオーディオ帯域外に飛ばす効果のあるMASHフィルタ回路がある

レジスタのうちGPCLK0に関する主なものだけを記す.
CM_GP0CTL[3:0]           源発振セレクト
   0 = GND
   1 = oscillator
   2 = testdebug0
   3 = testdebug1
   4 = PLLA per
   5 = PLLC per
   6 = PLLD per
   7 = HDMI auxiliary
CM_GP0DIV[23:12]         DIVI
CM_GP0DIV[11:0]           DIVF


【DMA】
次はDMAについて.
・16個のDMAチャンネルを内蔵している.
・バスマスタである.
・ペリフェラルからDMAへ、DREQ信号を発行することによってDMAのデータ出力に待ったをかけることができる.

ここで、「DMAのデータ出力に待った」が気になります.なぜなら、今わたしが知りたいことは、
Q1:「DMAのデータ受信に待ったをかける」
ですので逆向きのDMAフロー制御になります.
データシートを読みましたが、
A1: ユーザーに開放されたこの機能はありません!
さらにGPIOについて言えば、DREQすらありません!
つまり、GPIOのDMAには入りにも出にもフロー制御手段が一切無いらしいんです.

ありゃりゃ、、、GPIO経由のDMAでデータ受信をしたいんですけど、フロー制御が出来ないなら無理だ、詰んでいると悟りました.

目論見が崩壊したところで、
Q2: 「XXX経由のDMAデータ受信でフロー制御」のXXXは何なら可能なのだ?
これが初心に還っての疑問となりました.

↓答はこのブロックダイヤグラムにあります.I2C/PCM-IFの図です.
右の4本の信号が外部I2C信号線です.
DREQは左下にあります.たぶんですがDREQはTX-FIFO FULLから内部で生成されているのではないかと想像します.=送信フロー制御
わたしが知りたいのは受信フロー制御ですが、データシートには「受信データが無ければDMAは一時停止する」とさらりと書かれています.
けれど上図にはどこにもそれっぽい信号が見えません.たぶんですが、APBがRX-FIFO EMPTYを監視しているのではないかと想像します.=受信フロー制御

というわけで、
A2: DMAフロー制御をしたくば、所定のペリフェラルを使うしかない
というのがここまでの結論となりました.GPIO経由のDMAは断念しました.

次の疑問は、
Q3: DMAにペリフェラルを結合させるにはどうするんだろ?
Q4: 所定のペリフェラルってどれ?
DMA0のレジスタですが、
0_TI[20:16]      PERMAP[4:0]
A3: これがペリフェラル番号を設定するレジスタであるらしい
そしてPERMAPに入る番号は、抜粋でこのようなのがあります.各種ペリフェラルが見られます.なおGPIOはリストに在りません
   0 DREQ=1
   2 PCM TX
   3 PCM RX
   6 SPI TX
   7 SPI RX
   8  BSC/SPI Slave TX
   9  BSC/SPI Slave RX
   12 UART TX
   14 UART RX
   17 HDMI
0はどのペリフェラルにも繋がず、DREQが常に1とみなされます.これはmemory同士のDMAで使われるのではないでしょうか?

汎用的なDMAに使えそうなペリフェラルをデータシートから読むと、
A4: SPIまたはI2Cしか使えそうなのがありません
SPIの方が高速動作と考えますので、I2Cは捨てます.

ところが、まだ問題があります.
DMAとSPI-IFの間でフロー制御しているのは想像に難くありません.しかし、わたしが欲しいのは、SPI-IFと外部FIFOの間でのフロー制御です.SPIにフロー制御なんか規格自体に在りませんから、無理ってことで終了です.
それでもしぶとく粘るとしたら、SPI slaveならSPI clockをGO/STOPさせることでフロー制御が出来るかもしれません.
Q5: BCM2835にはSPI slave機能があるのか?
すぐ上に答えがあります.
A5: BSC/SPI Slave RX という文字があります
でもたぶん期待とは意味が違うと思います.BCM2835のSPI CLKは出力onlyであり、外部SPI CLKによるGO/STOPは不可能とデータシートから読めます.

どうやら結論としては、こうなったようです.
 ・外部データストリームをDMAでメモリへ展開したい.フロー制御は必須
 ・だが、フロー制御の点でBCM2835には使えそうなIFがない


【DMAレジスタ】
DMA0のためのレジスタのうちめぼしいものを抜粋します.

0_TI[20:16]      PERMAP[4:0]    上で述べました

0_TI[10]   SRC_DREQ        1:DREQが有効で、ソースリードが抑制される

0_TI[8]       SRC_INC           0:ソースアドレスが固定される
ペリフェラルのようにアドレスが固定されたポートで使われるのでしょう.

0_TI[6]   DEST_DREQ      1:DREQが有効で、デスチネーションライトが抑制される

0_TI[4]       DEST_INC           0:デスチネーションアドレスが固定される
ペリフェラルのようにアドレスが固定されたポートで使われるのでしょう.

0_SOURCE_AD[31:0]        ソースアドレス

0_DEST_AD[31:0]         デスチネーションアドレス

0_TXFR_LEN[15:0]      XLENGTH[15:0]       転送BYTE数

INT_STATUS[0]          INT0          DMA0割り込みフラグ

ENABLE[0]           EN0      DMA0イネーブル


ふ~ん、ラズパイのDMAはこうなってるのか、と判ったような気がしたところで、今宵はこれまでにしとうございます.

詰みました

0 件のコメント:

コメントを投稿