Arduino を使った ATtiny10 の書き込み (1)

最近話題の「米粒 AVR」こと 6 ピン SOT-23 パッケージの「ATtiny10」を私も手に入れましたが、TPI (Tiny Programming Interface) 方式の ATtiny10 に対応したプログラム書き込み器は持っていません。
新たなデバイス/ハードウェアの購入や、書き込み器の組み立てなしに、手持ちの Arduino (互換ボード) に簡単な配線を施し、ソフトウェアのみで ATtiny10 の書き込みができないかと考え、その第一段階として、ATtiny10 の内部のレジスタ/メモリ内容の 16 進ダンプをするスケッチ (プログラム) を作りました。
そのスケッチ本体は、この記事の一番最後に掲載します。
まず、ATtiny10 への書き込み方法としては、Atmel 純正の

  • AVRISP mk II
  • STK600 + 書き込みアダプタ

を使う方法があります。
また、オープンソース書き込みソフトウェアの「avrdude」の TPI 書き込み対応版と、TPI 対応書き込みハードウェアを併用する方法もあります。
その中で、FTDI の USB-シリアル変換チップの「bitbang」モードを使った方法が最も手軽ですが、残念ながら FTDI チップは持っていません。
もうひとつ別の方法として、Atmel のアプリケーション・ノート

の組み合わせを使うものがあります。
AVR918 では TPI (Tiny Programming Interface) が解説されており、その実際例として ATmega324 上に実現した書き込み器ファームウェアのソースリストのアーカイブ (avr918.zip) が用意されています。
これは、アプリケーション・ノート AVR911 で解説され、ソース/オブジェクトのアーカイブ (avr911.zip) が用意されている、オープンソース書き込みアプリケーション「AVROSP」と組み合わせて使うようになっています。
ホスト PC 上で動作させる AVROSP と書き込み器 (ATmega324) とのインターフェースに USART0 を使い、ATTiny 側との TPI 接続に USART1 の、合計 2 つの USART モジュールを使うようになっています。
Arduino で言えば、「Arduino MEGA」は ATmega1280 を使っているので、USART モジュールは複数あり、比較的簡単に実装できると思います。
MEGA 版でない Arduino では、USART モジュールが 1 個しかない ATmega168/ATmega328 を使っているので、そのままでは AVR918 のファームを実装することはできません。
そこで、TPI 側は SPI モジュールを使って代用することを考えました。
TPI のフレーム・フォーマットは

の 12 ビット構成ですが、SPI では 8 ビット単位でしか扱えないので、送信は前後に 2 ビットずつのアイドルビットを加えた 16 ビット (16 クロック) で、受信は 2 バイト (16 クロック) あるいは 3 バイト (24 クロック) を読み込んでいます。
Arduino と ATtiny10 (SOT-23) との接続を下に示します。

TPI のデータ線 (TPIDATA) は双方向性なので、送信/受信の切り替えが必須ですか、簡単のため、SPI の MOSI 端子は出力オンリー、MISO 端子は入力オンリーとして抵抗で合成し、ATtiny10 側が出力の場合には ATtiny10 側が勝つようにしてあります。
上の図の Arduino ディジタル・ピン 11 (MOSI) と ATtiny10 ピン 1 (TPIDATA/PB0) との間の抵抗だけが重要で、残りの部分は、他に接続がなければ抵抗なしで直結で構いません。
抵抗値も数百Ω から数 kΩ 程度ならば良く、値は重要ではありません。
ATtiny10 のフラッシュ書き込みには、Vcc に +5 V が供給されている必要がありますが、後に示すスケッチでは読み出ししかしていないので、3.3 V 版の Arduino との組み合わせでも動くかも知れませんが、テストはしていません。
また、ATtiny10 がリセット不可の設定にプログラムされている場合には図の接続では対応できず、ピン 6 に+12 V をかける必要があります。
秋月の 150 円 AE-ATmega 基板に部品を実装したもの (アキヅキーノ) に ATtiny10 を接続した写真を下に示します。

USB-シリアル変換モジュールを使用せず、シリアルは外部から 5V ロジック・レベルで供給しているので、変換モジュールを実装するスペースの 24 ピン・パターンが空いています。
しばらくは ATtiny10 を接続したままにしておくので、シールドを作成するのではなく、空きスペースに直接 IC ソケットを実装し、配線して、変換基板に実装した ATtiny10 を挿しています。
この変換基板は以前のバージョンのもので、裏面に部品実装パターンがない代わりに、ピン間隔が正しく 2.54 mm グリッドに乗っています。
後に示すスケッチでは、SPI による TPI 接続での読み書きの検証を主眼としており、フラッシュ・メモリ書き込みの機能はないのですが、ATtiny10 内部のレジスタ/メモリの読み込みは出来るので、メモリ内容をホスト PC 側にシリアルを介して 16 進ダンプするようになっています。
その結果を下に示します。

      +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
0000: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0030: 00 00 00 00 00 00 03 00 00 98 00 03 00 00 00 00
0040: 51 F6 4D B7 73 FA 65 77 B5 EF FB D7 FC 68 ED A4
0050: CE FB CC ED E1 61 AF 35 87 B7 7B D3 E1 FB FC FD
0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

      +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
3F00: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
3F40: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
3F80: 98 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
3FC0: 1E 90 03 FF FF FF 39 4A 36 30 35 39 0F FF 3F 4B

未プログラムのデバイスなので、フラッシュ・メモリ部分は消去ずみの 0xFF の連続なので表示を省略して、表示して意味のある部分だけを表示しています。
メモリの読み込み自体は、TPI を介して、64 K バイトの全空間に対して行っています。
アドレス 0x0000 〜 0x003F までは ATtiny10 のレジスタです。 ほとんどがゼロですが、OSCCAL (0x0039) など一部だけがゼロでない値になっています。
アドレス 0x0040 〜 0x005F までは SRAM です。 オール 0 でも、オール 1 でも、全くランダムでもない値が入っています。
現在のスケッチでは、TPI 接続を終了させるコードは入っていないので、単に Arduino 側をリセットしても、ATtiny10 側は TPI モードのままで、状態は変わらず「前回の続き」のままです。
いったん、電源を落として再投入すると、新たに TPI モードに入ります。
0x3F00 からは configuration / calibration / signature のセクションです。
コンフィグ・ビットはすべて未プログラムを意味する '1' で、0x3F80 のキャリブレーション・バイトの値は 0x98 であり、また、0x3FC0 からの 3 バイトはシグネチャの 0x1E 0x90 0x03 になっており、ATtiny10 を表すコードになっています。
最後にスケッチを示します。
Arduino IDEバージョンは 0022 で作成・テストしており、それ以外ではテストしてありません。
以下のリストをコピー・アンド・ペーストすれば実行できます。
PC 側とは 38400 bps のシリアル接続になっており、ボードにアップロード後、38400 bps に設定したシリアルモニタを起動すれば、上に示したようなダンプ出力が見られます
ダンプ出力は途中で止めない限り、無限に繰り返されます。
TPI のクロック周波数は 16 [MHz] / 32 = 500 [kHz] になっています。
Arduino 基板上の「L」と表示されている LED はディジタル 13 ピン (SCK) に接続されていますから、スケッチを実行すると「L」LED が点灯しますが、これは意図して点灯させているわけではありません。
SPI のモード設定はモード 0 でも、モード 3 でも動作しますが、下のスケッチで指定しているモード 0 では、PC 側へ出力している部分で LED は消灯します。

/**************************************************
 * tpitest.pde : Dump memory content of ATtiny10  *
 *               via TPI connection to Arduino    *
 *                                                *
 * Arduino                 ATtiny10               *
 * ----------+          +----------------         *
 * (SS#)  10 |--[R]-----| 6 (RESET#/PB3)          *
 *           |          |                         *     
 * (MOSI) 11 |--[R]--+--| 1 (TPIDATA/PB0)         *
 *           |       |  |                         *
 * (MISO) 12 |--[R]--+  |                         *
 *           |          |                         *     
 * (SCK)  13 |--[R]-----| 3 (TPICLK/PB1)          *
 * ----------+          +----------------         *
 *                                                *
 *  -[R]-  =  a few killo-Ohm resistor            *
 *                                                *
 * 2011/12/08 by pcm1723                          *
 **************************************************/
 
#include <SPI.h>
#include "pins_arduino.h"

#define SKEY 		0b11100000
#define NVM_PROGRAM_ENABLE 0x1289AB45CDD888FFULL

unsigned short adrs = 0;

// send a byte in one TPI frame (12 bits)
// (1 start + 8 data + 1 parity + 2 stop)
// using 2 SPI data bytes (2 x 8 = 16 clocks)
// (with 4 extra idle bits)

void tpi_send_byte( uint8_t data )
{
  uint8_t par = data;
  par ^= (par >> 4); // b[7:4] (+) b[3:0]
  par ^= (par >> 2); // b[3:2] (+) b[1:0]
  par ^= (par >> 1); // b[1] (+) b[0]
// (2 idle) + (1 start bit) + (data[4:0])
  SPI.transfer(0x03 | (data << 3));
// (data[7:5]) + (1 parity) + (2 stop bits) + (2 idle)
  SPI.transfer(0xf0 | (par << 3) | (data >> 5));
} // void tpi_send_byte()

// receive TPI 12-bit format byte data
// via SPI 2 bytes (16 clocks) or 3 bytes (24 clocks)

uint8_t tpi_receive_byte( void )
{
  uint8_t b1, b2, b3;
  do { // find start bit
    b1 = SPI.transfer(0xff);
  } while (0xff == b1); 
  b2 = SPI.transfer(0xff); // get (partial) data bits
  if (0x0f == (0x0f & b1)) { // need to get 3rd byte
// receive (1 parity) / (2 stop) / (1 or more idle)
    b3 = SPI.transfer(0xff); 
  } // if
  while (0x7f != b1) { // data not aligned
    b2 <<= 1; // shift left data bits
    if (0x80 & b1) { // carry from 1st byte
      b2 |= 1; // set bit
    } // if
    b1 <<= 1; // shift left byte1
    b1 |= 0x01; // fill with idle bit (1)
  } // while
    return( b2 ); // aligned data bits
} // uint8_t tpi_receive_byte()

void send_skey(uint64_t nvm_key)
{
  tpi_send_byte(SKEY);
    while(nvm_key)
      {
        tpi_send_byte(nvm_key & 0xFF);
        nvm_key >>= 8;
      } // while
} // void send_skey()

void outChar( char c )
{
  Serial.print(c);  
} // void outChar()

void outNewline( void )
{
  Serial.println();  
} // void outNewline()

 void outHex1(uint8_t n)
{
  Serial.print(0x0f & n, HEX);  
} // void outHex1()

void outHex2(uint8_t n)
{
  outHex1(n >> 4); outHex1(n);
} // void outHex2()

void outHex4(uint16_t n)
{
  outHex2(n >> 8); outHex2(n);
} // void outHex4()

void setup()
{
  Serial.begin(38400);
  SPI.begin();
  SPI.setBitOrder(LSBFIRST);
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV32);
  digitalWrite(SS, LOW); // assert RESET
  delay(1); // t_RST min = 400 ns @ Vcc = 5 V
  SPI.transfer(0xff); // activate TPI by emitting
  SPI.transfer(0xff); // 16 or more pulses on TPICLK
  SPI.transfer(0xff); // while holding TPIDATA to "1"
  tpi_send_byte(0xc2); // SSTCS  TPIPCR
  tpi_send_byte(0x04); // guard time = 8 clk
  send_skey(NVM_PROGRAM_ENABLE); // enable NVM interface
// initialize memory pointer register  
  tpi_send_byte(0x68); // SSTPR 0
  tpi_send_byte(0x00); //
  tpi_send_byte(0x69); // SSTPR 1
  tpi_send_byte(0x00); //
} // void setup()

void loop()
{
  uint8_t b, i;
  tpi_send_byte(0x24); // SLD with post inc
  b = tpi_receive_byte(); // get data byte
  if ((0x0000 == (0xff80 & adrs)) // register/SRAM
     |(0x3f00 == (0xff30 & adrs)) ) { // config/signature
    if (0 == (0x00ff & adrs)) { // time to print legend?
      outNewline(); outNewline();
      for (i = 0; i < 5; i++) { outChar(' '); }
      for (i = 0; i < 16; i++) {
        outChar(' '); outChar('+');
        outHex1(i);
      } // for (i ...
    } // if 
    if (0 == (0x000f & adrs)) { // time to print address?
      outNewline();
      outHex4(adrs); // print address in hex 4 digits
      outChar(':'); // delimiter
    } // if 
    outChar(' '); // delimiter
    outHex2(b); // print data in hex 2 digits 
  } // if 
  adrs++; // increment memory address
} // void loop()