Skip to content

askn37/UPDIUart

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UPDIUart

megaAVR-0系統コア上に UPDI書込装置を実装するための支援ライブラリ

概略

これは megaAVR-0 tinyAVR-0/1/2 AVR-Dx/Ex 系統に採用されている UPDI通信に必要なホスト機能を、megaAVR-0自身の上に実装することを支援するライブラリである。

主な用途は自律的に(PCを使わずに)動作する UPDI対応の FUSEリセッター、BOOTCODEライターの作成である。PCからファームウェアを転送して送り込む(mEDBGやjtag2updiのような)用途については、ここでは取り上げない。

UPDI通信の実体はオープンドレイン機構を介した単線半二重通信UARTである。これは一般的なRS232Cのような全二重通信方式と異なり、RS485方式が近しい。実際に UPDIを採用した MCUは全品種で単線半二重通信UART能力を有しているため、UPDI通信を実装するには無理がない。

ゆえにこのライブラリを使用するには、megaAVR-0 tinyAVR-0/1/2 AVR-Dx/Ex 系統のいずれかの MCUを必要とする。特にホスト側については必要十分な記憶容量を要求する。

一方でターゲット側 MCUに AVR-Dx/Ex 系統を選ぶのは、0.2版時点では制約がある。これらの品種は NVMCTRL制御手順が異なるため、抽象化メソッド群が満足できていない。低レベル層については使用できる。

  • 低レベル層は全種共通なので、AVR-Dx/Ex 系統専用ライタを書く一助にはなる。

このライブラリは 2023/11 時点ではメンテナンスを保留している。 AVR-Dx/Ex 系統については対応していない。

高電圧書込(HV programming)にはハードウェア支援が必要になる。 MULTIX UPDI4AVR Programmer を見よ。 こちらの Firmware は (AVR_EAを含む) 既知の全ての UPDI 対応 AVR シリーズを支配下に置ける。

Arduino IDE への導入

  1. Arduino IDE の環境設定に https://mcudude.github.io/MegaCoreX/package_MCUdude_MegaCoreX_index.json を追加し、ボードマネージャーで MegaCoreX を導入する。使用方法と詳細は MCUdude/MegaCoreX を参照のこと。

    tinyAVR をホスト側で使うには代わって megaTinyCore を使用するが、ここでは俎上に載せない。

  2. このライブラリの .ZIPアーカイブをダウンロードする。Click here

  3. ライブラリマネージャで読み込む

    スケッチ -> ライブラリをインクルード -> .ZIP形式のライブラリをインストール...

使用方法

付属のサンプルスケッチを実行するには、ホスト/ターゲットを下図のとおりに結線する。 両者がおなじVCC電圧で動作するなら単純にジャンパワイヤで直結してよい。 ターゲット側にホスト側からVCC電力供給する必要がないなら、VCC接続線は省く。 またターゲット側のVCC電圧は、最低でも2.7Vが供給されなければならない。

UPDI線については、ターゲットMCU側の UPDIポート側でのみ内蔵抵抗でプルアップされたオープンドレイン構成となる。 ゆえに異なる電圧で動作させる場合でも、絶対定格範囲内であるなら異電圧調整回路を必要としない。 ただしターゲットが tinyAVRで、12V印加可能構成を取るならそれ相応の外部回路が必要となる。

ホスト側の PIN_PC0 は Arduino APIでいう Serial1 (USART1) のTXポートを指す。 RXポートは使用しないので他の用途に転用しても良い。 選択された MCUのピンマップ設定によってはピン名は異なる場合がある。 PCからのコンソール制御は Serial (USART0) が使われることを想定している。

Target MCU      Host MCU
+--------+     +---------+     PC Serial
|   UPDI +-----+ PC0     |     +-------+
|        |     |     TX0 +-----+ RX    |
|    VCC +-----+ VCC     |     |       |
|        |     |     RX0 +-----+ TX    |
|    GND +-----+ GND     |     +-------+
+--------+     +---------+

サンプルスケッチ

UPDI-TargetView.ino

ターゲットMCUが UPDI対応品種かを調べ、型式、FUSE設定、シリアル番号情報を Serialコンソールに表示するデモンストレーション。最初にこのスケッチを試すのが良い。

UPDI-BootWriter.ino

メニュー形式でターゲットMCUの チップ消去、FUSE設定、EEPROM書換、ブートローダー書込、メモリダンプ等の各ステップを選択実行できるデモンストレーション。このライブラリが持つ APIを一通り網羅する。

ターゲット対応品種は ヘッダファイルを差し替えることで対応する。 使用したいブートローダーのコードはこのヘッダファイル内に Intel HEX フォーマット形式で貼り付ける。

デバイス施錠も可能だが、ひとたび施錠されたデバイスは チップ消去、USERROW空間書換以外のUPDIアクセスが拒否されることに注意されたい。

UPDI-BootCopy.ino

ホストとターゲットの製品が同じであると仮定して、ホスト自身のブートローダーコードとFUSE設定を、ターゲットにチップ消去した上でコピーするデモンストレーション。 EEPROM(消去前のFUSE内容による)とUSERROW空間は維持される。

コピー実行はスケッチ起動後、SerialコンソールでENTERを打つか、PIN_PC2に繋いだスイッチを押すまで保留される。

系統の異なる同一でない異品種間でのコピー実行結果は不明であり、コードの修正を要する。 ブートローダーコードを持たない(mEDBGが使われる)Arduino UNO Wifi Rev.2 / Every 系統の品種をホストとした場合は、チップ消去と FUSE複写のみが行われる。

このスケッチは Serialコンソールへの実行表示等を除去すれば十分小さなコード量になるだろう。

API説明

全体動作

#include <UPDIUart.h>

このライブラリの使用を宣言する。

主なマクロ定数

個々のメソッドに特有のマクロ定数はそれぞれの説明にある。

#define UPDI_RX_BUFFER_SIZE 32      /* 内部バッファサイズ */
#define UPDI_BREAK_TIME     24600   /* 推奨 BREAK 時間(us) */
#define UPDI_SYNCH          0x55    /* SYNCH シンボル値 */
#define UPDI_ACK            0x40    /* ACK シンボル値 */

UPDIUart_Class(volatile USART_t *hwserial_module, uint8_t hwserial_txrx_pin, bool enable_pullup)

このライブラリのクラスコンストラクタ。 使用するハードウェアUSART資源と TX/RX 単線動作ポートを明示的に指定する。

第3の引数は 単線動作ポートの プルアップ可否を定める。これは特別な理由がない限り false とする。 (ターゲットMCU側で UPDI通信ポートがプルアップされるため、直結するならば必要ない)

単線動作ポートはハードウェアUSART資源によって一意に定まり、自由に変更できるわけではない。 スケッチにおいては通常 PIN_HWSERIALn_TX 書式のマクロ定数が用意されているため、これを用いると良い。 全二重設定でペアとするRXポートは、単線半二重通信形態では使用しない。 他の用途で自由に使うことができる。

#include <UPDIUart.h>

/* megaAVR で USART1 を選択し UPDI という名称の静的インスタンスを宣言する */
UPDIUart_Class UPDI(&USART1, PIN_HWSERIAL1_TX, false);
  • これ以下の記述ではクラスインスタンス名に UPDI を使用する。

void UPDI.begin()

void UPDI.begin(long BAUDRATE)

クラスコンストラクタで指定したハードウェアUSART資源を、指定のボーレートで動作するように初期化する。 ボーレートの省略時既定値は 225000L である。 これは通常設定の UPDIで許される最高速度値だが、±4.9% の速度変動は許容されているため、230400L(1.024倍。14400bpsの16倍速)を指定しても公差の範囲内に納まるからまず問題はない。

ただし実働可能な最高速度はホストMCUのシステムクロックによっても制限される。 上限は F_CPU / 16 であるため、この値に自動調整される。(例えばホスト側 1Mhz駆動時は 62500L)

UPDI.begin(225000L);

通信フレームの形式は 8ビット幅:偶数パリティ:ストップビット2 の 1フレーム12ビット編成 である。

void UPDI.end()

UPDI動作を終了し、ハードウェアUSART資源を開放する。 開放に先立ち UPDI.RESET()UPDI.STOP() も実行される。 使われていたポートは Hi-Z にされる。 開放された資源は改めて、通常の UART/USART として初期化できる。

UPDI.end();

uint8_t UPDI.rxError()

最後に受信動作を行った際の USART結果コードを返す。 これは以下のマクロ定数で表されるビットフラグを保持する。

name comment
UPDI_STAT_RXCIF 受信無効/タイムアウト
UPDI_STAT_BUFOVF フレームバッファ溢れ
UPDI_STAT_FERR フレームエラー
UPDI_STAT_PERR パリティエラー
if (UPDI.rxError()) { /* Recv Error */ }

uint8_t UPDI.rxLast()

最後に受信したキャラクタを返す。これは自身が送信した(loopbackした)文字であることもある。

if (UPDI_ACK == UPDI.rxLast()) { /* STAB */ }

uint8_t* UPDI.buffer()

インスタンス内部に保持する受信バッファの先頭ポインタを返す。通常は最大32バイト。 単線動作USARTでは、ホストが送信したキャラクタもまた loopback されて受信バッファに送り込まれるが、これは自動的に読み捨てられてバッファには最終的にターゲットから送信されたキャラクタだけが格納される。 受信データ量がこの最大値以内であれば、応用コードが自前のバッファを持つことを省略できる。 最大値を超えて受信したキャラクタは捨てられる。 バッファ開始位置は後述の UPDI.BeginTransaction() でリセットされる。

uint8_t *buffer;
buffer = UPDI.buffer();

UPDI制御

UPDIコントローラの制御を行う。

int16_t UPDI.START()

UPDI通信を開始する。通信に失敗すると -1 が返る。 成功すれば UPDIプロトコル版番号を返し、これは最低でも 0x10 以上の値となる。 これが成功しなければ、この記述以後のメソッドは正しく動作しない。 下位4bitは常にゼロで、上位4bitが有効。 2021年時点で既知の UPDI対応全品種では必ず 0x10 0x20 0x30 0x40 のいずれかである。 版番号の違いは主に OCD制御手順の違いに由来するため NVM操作には関係がない。

UPDI.START();

void UPDI.STOP()

UPDI通信を終了して、UPDIステートマシンを初期状態に戻す。 ターゲットMCUは UPDI通信状態から脱して、通常の待機状態に遷移する。 これを発行したあと再び UPDI通信を再開するには UPDI.START() から始める。

UPDI.END();

void UPDI.BREAK()

void UPDI.BREAK(uint16_t us)

BREAKシンボルを送信する。現在進行中のUPDI送受信シーケンスは中断される。 引数で BREAKシンボルの持続時間を指定できるが、省略した場合は現在の通信ボーレートの 24bitシグナルに相当する時間となる。

より安全に完全な BREAK を実施するには 24600us の持続時間を必要とする。 これはターゲットMCU側 UPDI機構が許容する実行可能最低速度での BREAK 操作を満足する。

UPDI.BREAK(UPDI_BREAK_TIME);  /* 24600us */

void UPDI.RESET()

ターゲットMCUを、UPDI制御を介してリセットする。 これは PORや ハードリセットとは異なり、ソフトウェア的なものであることに注意。 現在の UPDI制御状態から他の制御状態へ遷移する際に発行する。

UPDI.RESET();

size_t UPDI.LDSIB()

ターゲットMCUから SIB (System Infomation Block) を取得する。 返値は読み出されたデータ量で、成功すれば 16を返す。 読み出された SIB は UPDI.buffer() ポインタに格納される。

if (16 == UPDI.LDSIB()) {
  uint8_t *buffer = UPDI.buffer();
  /* STAB */
}

int16_t UPDI.LDCS(uint8_t cs)

LDCS指令を引数の制御レジスタ番号で発行する。以下のマクロ定数が使用できる。 返値は失敗すれば -1、成功すれば指定レジスタから取得した内容値。

name
UPDI_CS_STATUSA
UPDI_CS_STATUSB
UPDI_CS_CTRLA
UPDI_CS_CTRLB
UPDI_CS_ASI_KEY_STATUS
UPDI_CS_ASI_RESET_REQ
UPDI_CS_ASI_CTRLA
UPDI_CS_ASI_SYS_CTRLA
UPDI_CS_ASI_SYS_STATUS
UPDI_CS_ASI_CRC_STATUS

void UPDI.STCS(uint8_t cs, uint8_t val)

STCS指令を発行し、引数で指示した制御レジスタに値を書き込む。

bool UPDI.ENABLE_NVMPROG()

施錠されていないターゲットMCUに対し、NVMPROG状態への遷移を要求する。 成功すれば真を返し、施錠されているなら失敗して偽を返す。 ターゲットMCUの通常動作を停止し、FUSE、EEPROM、FLASH空間を正しく読み書き可能にするにはこの状態に遷移しなければならない。 NVMPROG状態は UPDI.RESET() の発行で終了する。

施錠されていないターゲットMCUでは、NVMPROGモードでなくてもシステムクロックが活性化しているあいだ、すなわち深い休止状態でなければレジスタ空間・IO空間・データ空間を読むことはできる。 デバッグアクセスは通常その状態で行われる。

bool UPDI.ERASE_CHIP()

ターゲットMCUに対し、チップ消去を要求する。成功すれば真を返す。 成功したなら、続けて直ちに NVMPROG状態遷移と FUSE書換を実行すべきである。

チップ消去は施錠されたターゲットMCUを解錠できる唯一の手段である。 USERROW空間以外の内容は保持されない。 よって本メソッドに続けてチップ施錠解除FUSE値を書き込むことなく UPDI.RESET() でチップ消去状態を解消すると、ただちに再び施錠されてしまうことに注意されたい。

bool UPDI.START_USERROW()

ターゲットMCUに対し、USERROW書換状態遷移を要求する。成功すれば真を返す。

USERROW空間は EEPROM空間とは別の、EEPROMと同様に扱える不揮発メモリ空間である。 通常の状態では EEPROMアクセスと同様の手順で読み書きができる。 唯一の違いは、EEPROM空間が施錠されるとUPDIから全く読み書きできなくなるのに対し、USERROW空間だけは施錠されていても 盲目的に書き込むことだけ ができる点にある。 (書き込むことはできるが UPDIで読み出すことは出来ないことに注意されたい) 一般には最終製品出荷前段階で固有の個体情報を記録するために使われる。

このメソッドはデバイスが施錠状態であっても USERROW空間を書き換え可能にする準備を行う。 成功した後に USERROW_BASE が示すアドレス に32バイト(一部のMCUでは64バイトまで)のデータを書き込み UPDI.FINAL_USERROW() を発行することでUSERROW空間が上書きされる。書き込んだ値を読み返す手段はない。

内部的には USERPROGモードになると、USERROW空間に SRAM領域が内部バッファとして一時的にオーバーラップされる。そして USERPROGを抜けるとそこに書かれたデータが実際の USERROW空間に転送され、書き込まれる。この動作を施錠されていないデバイスで観測すると、USERROWアドレスに書いたデータが SRAM空間先頭にコピーされている様子として見ることができる。

bool UPDI.FINAL_USERROW()

USERROW書換状態の完了を宣言して、USERROW空間の上書き処理を実施する。上書きできるのは32あるいは64バイトの情報だけである。

低レベルAPI群

このグループはすべての UPDI対応品種をフォローできる。

void UPDI.BeginTransaction()

一連のUPDI送受信 低レベル シーケンスの開始を宣言する。 直接的にはインスタンス内部バッファをクリーンにする。 この記述以後の一連の送受信処理に先立って必ず呼ばなければならない。 またこの記述に続いて送信する最初のキャラクタは UPDI_SYNCH シンボルとすべきである。

void UPDI.EndTransaction(size_t len)

一連のUPDI送受信 低レベル シーケンスの終了を宣言する。 引数は ターゲットMCUから受信すべきキャラクタ数である。 なにも受信するキャラクタが無いのであれば、ゼロを指定する。 まだ取得していない loopback キャラクタがあるならば処理される。 受信されたキャラクタ列は UPDI.buffer() で示されるバッファに格納される。

一連の低レベルシーケンスは、1キャラクタ以上の送信と、0キャラクタ以上の受信が 1セットである。 (単線半二重通信ネゴシエーションの特徴であるが)ホストから見てこの組が送信なしに受信から開始されることはありえない。 新たな送信セットを開始するたびに区切りとして UPDI.BeginTransaction() が発行されなければならない。

void UPDI.SEND(uint8_t data)

void UPDI.SEND(uint8_t* data, size_t len)

一連のUPDI送受信 低レベル シーケンスで、単一のキャラクタ、あるいは指定数のキャラクタ列を送信する。

int16_t UPDI.RECV()

一連のUPDI送受信 低レベル シーケンスで、単一のキャラクタを受信する。 これは UPDI_ACK の受信確認に使用できる。 失敗すれば -1 が返り、その原因は UPDI.rxError() で得られる。

このメソッドは以下の等価コードに対する利便上のショートカットである。 ゆえに先立って UPDI.EndTransaction() を呼ぶ必要はない。

UPDI.EndTransaction(1);
int c = UPDI.rxError ? -1 : UPDI.rxLast();

UPDI送受信低レベルシーケンス制御の例

/* データ空間を示す目的の addr(16bit幅) から1バイトを読む == LD8に等価 */
UPDI.BeginTransaction();
UPDI.SEND(UPDI_SYNCH);
UPDI.SEND(UPDI_LDS|UPDI_ADDR2|UPDI_DATA1);
UPDI.SEND(addr);
UPDI.SEND(addr >> 8);
// UPDI.EndTransaction(1);
int c = UPDI.RECV();

高レベルAPI群

このグループはデータアクセス空間が最大 64KBまでに対応している。

uint16_t UPDI.LD8(uint16_t addr)

8bit幅の LD指令を指定のデータアドレスに発行する。 返値は失敗すれば -1、成功すれば指定アドレスから取得した内容値(単バイト)。

uint16_t UPDI.LD16(uint16_t addr)

16bit幅の LD指令を指定のデータアドレスに発行する。 返値は成功すれば指定アドレスから取得した内容値(語)。 失敗した場合は UPDI.rxError() がゼロ以外を返す。

size_t UPDI.LDS8(uint16_t addr, size_t len)

連続した8bit幅のLD指令を発行し、引数で指定したデータアドレス以降を、指定長読み込む。 返値は読み込みに成功した長さ。 読まれたデータは UPDI.buffer() に格納される。

size_t UPDI.LDS8(uint16_t addr, uint8_t *data, size_t len)

連続した8bit幅のLD指令を発行し、引数で指定したデータアドレス以降を、 指定のデータ列ポインタ以降に、指定長読み込む。 返値は読み込みに成功した長さ。 バッファオーバーフローは考慮されない。

bool UPDI.ST8(uint16_t addr, uint8_t val)

8bit幅の ST指令を発行し、引数で指定したデータアドレスに値(単バイト)を書き込む。

bool UPDI.ST16(uint16_t addr, uint16_t val)

16bit幅の ST指令を発行し、引数で指定したデータアドレスに値(語)を書き込む。

size_t UPDI.STS8(uint16_t addr, uint8_t *data, size_t len)

連続した8bit幅のST指令を発行し、引数で指定したデータアドレス以降へ、データ列ポインタ以降の指定長を書き込む。 返値は書き込みに成功した長さ。

抽象化API群

以下のメソッドは SIB内のNVM版番号が P:0 である場合に対応できるよう書かれている。 他の形式では NVMCTRL制御手順が異なるため、これらのメソッドで不揮発メモリを書き換えることはできない。

bool UPDI.ST_FUSE(uint16_t index, uint8_t data)

FUSEデータ空間内の指定したアドレスに新しい FUSE値を1バイト単位で書き込む。 成功すれば真を返す。 これはNVMPROGモード状態中でのみ実行できる。

FUSE空間は EEPROM空間の一部なので通常の LD指令で読むことはできるが、正しい手順を踏まなければ書き換えることは出来ない。このメソッドはその簡便なショートカットを提供する。

FUSE空間の中でもデバイス施錠を施す LOCKBIT バイトは UPDIアクセスへの影響が著しい。 これはFUSE空間の(megaAVR/tinyAVRでは)11バイト目に有り、0xC5以外の値が書かれると施錠が有効となる。 これを 0xC5に戻して解錠するには、チップ消去直後に NVMPROG状態に遷移して FUSE書換を行う以外の手段がない。 すなわちいったん施錠されたMCUからは、SIBを除く情報を外部から得ることが出来なくなる。

if (UPDI.ENABLE_NVMPROG()) {
  for (uint8_t i = 0; i < 10; i++) {
    UPDI.ST_FUSE((uint16_t)&FUSE + i, new_fuse[i]);
  }
  UPDI.RESET();
}

なお FUSEバイトの未使用予約ビットについては、品種によって 0/1 いずれとするかを指定されているので各個の公開データシートを確認されたい。

bool UPDI.NVM_CMD(uint8_t cmd)

NVMPROGモード中で、ターゲットMCUの NVMCTRL 制御器に指定の指令を発行する。

EEPROM空間の書換は、簡便には、希望のEEPROM空間に直接 ST指令でデータ列を書き、UPDI.NVM_CMD(NVM_CMD_ERWP) 指令を発行するだけで良い。 ただしこのときEEPROMページ粒度(ターゲットMCUにより異なるが32あるいは64バイト)境界を超えて一度に書き換えることは出来ない。 通常は EEPROMページ範囲ごとに、ページ境界を跨がないよう、かつ範囲内で先頭から末尾まで連続しているデータ列を過不足なく充填したのちに、本メソッドで NVM_CMD_ERWP 指令を発行する。 続けて次の領域を書き換え始めるには UPDI.NVM_WAIT()で直前の内部処理が終わるのを待つ。

  • EEPROM空間へのST指令は、NVMCTRL制御部の持つ内部緩衝バッファRAMに蓄えられる。 緩衝バッファは明示的にクリア指令(緩衝バッファを 0xFF で充填する:クリア処理中 MCUは動作停止を伴う)を発行しなければ以前の ST書込値を保持していると見られる。 なのでアドレスが飛び飛びの連続しないデータを書こうとすると、意図せず好ましくないデータが、意図しない EEPROM空間に複写されてしまう場合がある。

FLASH空間(コード領域)の書換も手順は EEPROM書換と全く同じで、異なるのは対応するデータ空間アドレス(コード領域アドレスではない)と、ページ粒度を 64あるいは 128バイト境界とすることである。

なお未使用領域の不足分を埋める書込バイト値には 0xFF が推奨される。 これはページ消去成功時のデフォルト値だ。 データ書込行為はこれを 0xFF 以外の値にする(各ビット毎に論理ANDを取る)ことなので、不要バイトは 0xFF のままにしておくのが効率的である。

uint8_t UPDI.NVM_WAIT()

UPDI.NVM_CMD(cmd) メソッドの実行完了を待つ。 成功すればゼロを返す。書込エラーがあれば 0x04 ビットが立つ。 実行完了前にターゲットMCUがリセットされた場合、その結果は不定である。 また実行完了前の新たな LD/ST指令は正しく受け入れられる保障がない。

EEPROM/FLASH書換失敗の主原因は、VCC電圧の安定性不足である。 書換中はターゲットMCUの BODリセット機構が強要される。 よって許容限度を超える電圧降下は、自ずと失敗に帰結する。

uint16_t addr = FLASH_BASE;
uint8_t *code = {...};
for (uint8_t page = 0; page < page_length; page++) {
  UPDI.STS8(addr, code, FLASH_PART);
  UPDI.NVM_CMD(NVM_CMD_ERWP);
  UPDI.NVM_WAIT();
  addr += FLASH_PART;
  code += FLASH_PART;
}

付録:いくつかの考察

SIB (System Information Block) フォーマット

LDCS指令を使って読み出せる 16バイトの SIBは、ターゲットMCUの素性を知る重要なヒントとなる。 これは可読可能なASCIIキャラクタ列になっていて、例をあげれば次のようなものだ。

index 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
char 6D 65 67 61 41 56 52 20 50 3A 30 44 3A 31 2D 33
ASCII m e g a A V R P : 0 D : 1 - 3

実際の SIB は 32バイトあるのだが、ここではデータシートに明言された16文字分についてのみ言及する。

最初の7文字は所属品種系統を表す。megaAVRtinyAVR 等の代表系統名がここに現れる。 8文字目は予約値で、ここでは 0x20 でパディングされている。

非常に古い最初期の AVR_DA の一部出荷製品のみ、先頭4文字が0x20でパディングされた"____AVR_"として読める。それを除く AVR*系統は全て"AVR_____"("AVR"と空白5文字)として読まれ、シリーズの区別がつかない。

9〜11文字目は NVM版番号を表す。P:0でプログラミング方式0の意味。

12〜14文字目は OCD(オンサーキットデバッガ)版番号を表す。 D:1でデバッガ方式1の意味。(具体的にはハードウェアブレイクポイント未対応等)

15文字目は予約値で、ここでは 0x2D でパディングされている。

16文字目はデバッガ発振周波数を表す。ここでは 0x33 が書かれている。

NVM版番号は2023年時点で、P:0P:2P:3P:5 の4種類が知られている。 これらは NVM制御方式が異なり互いに制御コードの互換性がない。(3と5の使い方は同じだが、各NVM領域の配置アドレスが異なる)

このライブラリでは 0.1版時点で P:0 系列の NVMCTRL制御手順についてのみ対応している。

SIBを読んで判明することは、チップ消去と FUSE書換に必要な最低限の情報だけと捉えて良い。 これらは系統名称が解れば一意に決定できる。 これ以上の情報は SIGROW空間を読んで判別しなければならない。

UPDIの通信速度

UPDI実行部は本体のメインMCUとは独立したシステムクロックを持ち、並列稼働するサブのマイクロコントローラで、専用の独立した半二重単線動作 USART回路を持つ。 その UPDIシステムクロック既定値は 4Mhzであり、225kbps〜450bps の単線半二重通信速度に対応する。 通信速度は SYNCH シンボル受信毎に自動的に調整される。 ボーレートの公差は±4.9%が許容されるため、より一般的なボーレート値である 230.4kbps も実用上は問題ない。

さらに STCS 指令で SYS_ASI_CTRLA レジスタを操作すれば UPDIシステムクロックの倍率を操作でき、8Mhz、16Mhz(AVR DA,DBでは更に32Mhz)に増速することも可能だ。 この場合の速度上限は 450kbps、900kbps(さらに1.8Mbps)まで引き上げることができる。

ただしこの通信速度を実現するにはターゲットMCUのBOD設定を事前に無効化あるいは最大許容設定に変更しておかねばならない。 また NVMCTRL 制御部のチップ消去・書換に要する物理速度にはクロック変更の影響が及ばないため(チップ消去で20~30msあるいはそれ以上、ページ単位消去で4ms) 単純に倍速・4倍速とはならない。つまりUPDI増速はほぼ読み出し専用のデバッグアクセス用途にしか寄与しない。

このライブラリでは以上の観点から、倍速・4倍速変更制御のショートカットは提供していない。

BREAKの実装

BREAKは通常のキャラクタではなく、現在の通信速度に比して 12bitシグナル以上の連続したLOW信号であると定義されている。 これを実現するにはふたつのアプローチが考えられる。

  1. 一時的にボーレートを33%以上落として、キャラクタ0x00が必要な時間 LOW信号を維持できるようにする。
  2. 一時的にUSART機構を停止して pinMode を変更し、必要な時間 LOW信号を維持する。

このライブラリでは2のアプローチを取っている。 これは実際には2つの連続した(24bitシグナル以上の)BREAKがターゲット側 UPDIステートマシンの中断と停止(ひとつめのBREAKで進行中の通信を中断し、ふたつめのBREAKで UPDIステートマシンが初期状態に戻るのを促す)に必要なためと、さらにそれが理論上の実行可能な最低速度 450bps では 24600us のLOW信号に相当するため、1のアプローチでは調整が難しいことによる。

実用上は BREAK の用途がほとんどない為、1のアプローチの方が現実的な実装になる。

自分自身に UPDIアクセスを行ってできること

前述のようにメインMCU本体と UPDI層は独立したハードウェア資源なので、自分自身の UPDIポートに接続して通信を行うと、自分のデータ空間を間接的に UPDI権限で読み書きすることができる。しかしながら NVMPROG・ERASE・USERROW 各モードへの遷移はデバイスリセット状態を経由せねばならないので実用性は乏しい。 せいぜい自分自身の SIB内容(これは UPDIアクセス可能空間内にしか無い)を確認する程度の用途しか無いだろう。

FUSEリセット・ブートコード書込の自動化

FUSEリセットについては SIBが読み出せれば OSC・RST・LOCKBITの必要な位置について決定できるため実用上問題なく実現できる。 tinyAVRに実装する場合でも8ピン4KB品種で十分実現可能な範囲だ。

それ以上の動作、ブートコードの選択、EEPROMやUSERROW空間の書き換えについてはSIGROWを読み、データベース照合を介して各種アドレス位置を決定する必要があるのでコード規模が増加する。 tinyAVRをホスト側に使う場合は容量に余力のある上位品種が欲しいだろう。 ホストPCから間接的に操作するならその制限はないが、JTAG-ICE通信レイヤの実装が正道だろう。 これは異なる層の実装となるので、このライブラリの及ぶところではない。

UPDI線の付加受動部品

これはいくつかの理由から本来必要がない。 単線オープンドレイン構成は(I2Cでもそうだが)双方の絶対定格を超えない限り異電圧接続に通常は支障がなく、プルアップ抵抗も回路単位内のただ一点でだけ必要とされる。 これはターゲット側UPDIポート内部で常時提供されており(平均的には35kΩ)ゆえに無用な外部プルアップ抵抗の付加はかえって回路の電気的特性を損なう。

  • オープンドレイン構成時に問題を生じるのはTTLトレラントになっていない、余分な保護ダイオードが内蔵されている場合である。

  • 未使用のGPIOポートは、ソフトウェアからは INPUT_PULLUP あるいは INPUT_DISABLE 設定にすることが公開データシートにて要請されている。これを遵守するならばそもそも MCU外部にプルアップ抵抗を施す意味はない。

やむなく外部回路を付加せざるを得ない状況には、tinyAVR に対して12Vパルスを必要とする場合がある。 これには複数の MOSFETを用いた複電圧ドメイン分離とフォトカプラによる印加回路が適するだろう。

まれに開放端点となっている UPDI端子へのサージ入力で、望まずして12Vシーケンスの発動を促す場合がある。 しかし正しくないシーケンスの開始はタイムアウトを経て拒否されるから実害には至らない。

改版履歴

  • 0.2 (2023/12/03)

    • 同梱ブートローダーHEXを独自作成に変更。
    • 他プロジェクト由来のコードと引用を除去したため、ライセンス許諾を LGPL から MIT に変更。
  • 0.1 初版

    • P:0 形式のみ対応。AVR DA,DB系統以降の制御は非対応。

Copyright and Contact

Twitter(X): @askn37
BlueSky Social: @multix.jp
GitHub: https://github.com/askn37/
Product: https://askn37.github.io/

Copyright (c) 2023 askn (K.Sato) multix.jp
Released under the MIT license
https://opensource.org/licenses/mit-license.php
https://www.oshwa.org/

About

UPDI UART host support library for megaAVR

Topics

Resources

License

Stars

Watchers

Forks

Languages