LPC1114FN28/102 (20) -- ICSP 接続で PIC32MX のフラッシュに書き込む (5)

JTAG インターフェースには (プローブ側から見れば) TDI と TMS の 2 本の出力があり、TDO の 1 本の入力があります。
TDI と TMS は独立で、ハードウェア的に言えば、それぞれがパラレル・データから「シリアライズ」されてシリアル・データとして出力されます。
ソフトウェアあるいは文書では、それぞれ、あるビット幅を持つ「数値」として表現されることになります。
Microchips 社のドキュメント「PIC32MX Flash Programming Specification」では、その表現に Verilog-HDL と同様の
3'b001
6'h1F
のような表記方法をとっています。
シングル・クオート「'」の前の 10 進数値が「ビット幅」を表し、シングル・クオートの後の「b」は 2 進表記であることを示し、「h」は 16 進表記であることを示します。
JTAG インターフェースの「シリアライズ」は LSB ファーストに行われるので、TDI / TMS では数値の LSB から順に送りだされ、TDO から入力されるビット列は LSB 側から組み立てられて「パラレル値」の TDO として読み取られます。
JTAG インターフェースにビット幅が len の 2 つのパラレル値 tdi、tms を出力し、TDO 端子からのシリアル入力をパラレルに組み立てたものを戻り値とする関数のプロトタイプは、たとえば次のようになります。

uint32_t JTAG_xmit(uint32_t len, uint32_t tdi, 
                                 uint32_t tms);

これに相当する機能を ICSP で実現したプログラムのリストを下に示します。

uint32_t ICSP_last_tdo;

uint32_t ICSP_2w_xmit(uint32_t len, uint32_t tdi, 
                                    uint32_t tms)
{
  int i;
  uint32_t bit_mask, d, tdo;

  tdo = ICSP_last_tdo;
  bit_mask = 0x01; // from LSB
  for (i = 0; i < len; i++) { // scan from LSB
//
//  d = {TDI:TMS:0:0}
//
    d  = ((0 != (tdi & bit_mask)) << 3); // TDI in b3
    d |= ((0 != (tms & bit_mask)) << 2); // TMS in b2
    d  = LPC11xx_SSP0_xmit(d); // skewed TDO returned
    d &= 0x01;                 // extract TDO (b0)
    if ((i+1) < len) {     // not MSB timing
      tdo |= (d << (i+1)); // assemble TDO bits
    } else {               // MSB timing
      ICSP_last_tdo = d;   // remember TDO for next xfer
    }
    bit_mask <<= 1;
  } // for (i = 0; ...
  return(tdo);
} // uint32_t ICSP_2w_xmit()

32 ビット整数を使っているので、扱えるビット幅は 32 までとなります。
uint64_t 型などを使って、33 ビット以上に対応させるようなプログラムを書くこともできますが、頻度が多くなければ、32 ビット・バージョンを複数回使って処理したほうが効率的です。
ICSP と JTAG とでは、TDO 入力が JTAG クロック 1 個分ズレるのを補正するため、「ICSP_last_tdo」というグローバル変数を用意しています。
ICSP の今回の転送での tdo の LSB には前回に設定された ICSP_last_tdo の値を使用し、今回の転送の MSB のタイミングで読み取った TDO 入力の値を ICSP_last_tdo に保存しておいて、次回の転送時に役立てます。
ICSP_2w_xmit() 関数の for ループの部分が tdi / tms を LSB からビット・シリアルに 1 ビットずつ抽出して処理している部分で、抽出した 1 ビットの TDI / TMS を

b3 b2 b1 b0
TDI TMS 0 0

のように並べ、4 ビット転送モードの SPI (MSB ファースト) で出力しています。
b1,b0 の部分は、ICSP の本来のハードウェアではスリーステートになって、ターゲット側から TDO の値が出力されてくる部分です。
ここでは SSP の MOSI と MISO を抵抗で合成しているだけなので、ターゲットの PGEDx 端子で観測すると、本来ハイ・インピーダンスとなる部分もこの「0」でドライブされた「0」が見えます。
TDO 部分は、どうせ「オーバーライド」されるので、「0」でも「1」で構いません。
「0」にしているのは、オシロで観測する場合の見やすさを考えた上のことです。
TDI/TMS と TDO との「合成」をオープン・コレクタ (オープン・ドレイン) で行う場合には、TDO 出力部分の値は「1」にしないと TDO = 1 の状態を読み込めなくなります。
ICSP で本来は TDO 入力は 3 番目と 4 番目の PGECx クロックの境界位置の立ち上がりエッジで取り込むものですが、SPI モード 1 での動作なので、常に立ち下りエッジで読みこんでいます。
4 番目の PGECx クロックの立ち下りエッジ以降でターゲットの PGEDx 端子はスリーステートになります。
それでも、スペック上は、MISO 入力データのクロックに対するホールド・タイムの最小値は 0 ns なので、TDO の値はちゃんと取り込めます。
SSP のシフトレジスタにシフトインされて来たデータの LSB (b0) がこの TDO をサンプルしたものになります。
ICSP_2w_xmit() 関数より上位のプログラムでは、実際のハードウェア出力が JTAG であるか ICSP であるかに関係がなくなります。
ドキュメントでの「JTAG では何々」というのをそのままインプリメントすればよいことになります。
「PIC32MX Flash Programming Specification」では、JTAG/ICSP 操作で常用される操作のいくつかを「Psuedo Operation」として定義しています。

  • SetMode (mode)
  • SendCommand (command)
  • oData = XferData (iData)
  • oData = XferFastData (iData)
  • oData = XferInstruction (instruction)

ここでは、これらを C の「関数」として作成しました。
SetMode() 関数は、TMS を操作して TAP コントローラを希望のステートに遷移させるものです。
通常は TDI は「0」を維持したままなので、TDI をコントロールする引数はありません。
そのリストを下に示します。

uint32_t ICSP_SetMode(uint32_t len, uint32_t mode)
{
  uint32_t tdo;
  tdo = ICSP_2w_xmit(len, 0, mode);
  return(tdo);
} // uint32_t ICSP_SetMode()

ドキュメントの Psuedo Operation では引数の「mode」にビット幅の情報も入っているものとしていますが、実際の C での関数としては、簡単のため、ビット幅の情報は引数 len として与えることにしました。
uint32_t 型の関数としていますが、通常は TDO の値を参照することはなく、void 型でも構いません。
この関数の主な処理としては、引数 tdi の部分を「0」として、ICSP_2w_xmit() 関数を呼んでいるだけです。
JTAG TAP コントローラでは、TMS 入力に 5 連続の「1」をシフトインすることで、どの状態からでも「Test-Logic-Reset」ステートに遷移することが保証されており、さらに「0」をシフトインすると「Run-Test/idle」ステートに遷移します。
通常は「Run-Test/idle」ステートから出発し、目的の操作を行い、「Run-Test/idle」ステートに戻るという流れになります。
具体的には、TMS 1-1-1-1-1-0 というシーケンスとなり、TMS の数値としては、6'h1F = 6'b011111 を送出します。
Pseudo Operation では

SetMode(6'h1F)

で表現され、ここでの C のプログラムでは、

    ICSP_SetMode(6, 0x1F);

になります。
「Test-Logic-Reset」ステートでは、「IDCODE」インストラクションを実行したと同様の状態にセットされるので、前回示したプログラムで 2-wire ICSP モードに入った後に、下に示す ICSP_read_IDCODE() 関数を実行すれば、その戻り値として IDCODE (Dev ID) が読み取れます。

uint32_t ICSP_read_IDCODE( void )
{
  uint32_t oData;

  ICSP_SetMode(6, 0x1F); // reset TAP state
  ICSP_SetMode(3, 0x01); // TMS header = 3'b001
  oData = ICSP_2w_xmit(32, 0, (1UL << (32-1)) );
  ICSP_SetMode(2, 0x01); // TMS footer = 2'b01
  return(oData);
} // uint32_t ICSP_read_IDCODE()

秋月で販売している PIC32MX については、下の表のようになっているので、対応する値が読み取れれば ICSP 接続が成功したことになります。
(2014/10/07 追記: PIC32MX250F128B を追加しました)

バイス DEVID
PIC32MX250F128B 0x04D00053
PIC32MX220F032B 0x04A00053
PIC32MX210F016B 0x04A01053
PIC32MX120F032B 0x04A06053
PIC32MX110F016B 0x04A07053