SSブログ
English Version

GreenPAKで遊ぶ(その2)書き込み環境完成 [GreenPAK]

 前回の記事の最後に書いたように PIC を使った自作の汎用制御基板の Pic24ジェネラルボックス(以降、GenBox と記す)を使った SLG46826 の書き込み環境が完成したので開発する上で調査した内容等をまとめてメモしておきます。
 久しぶりに自作のセルフコンパイラである独自言語の picle を使いました。全てを自分のコードで構築していく作業はある意味世界を一から創造していくようでもあり、独特な趣きがあります。
 尚、今回開発した書き込み環境を Grenwriter と命名しました。



■書き込み処理での確認事項
 主に下記の2つの資料を参考にしましたが今回のソフトを開発する上での重要事項について確認結果も含めて書いてみます。
  • 資料1:In-System Programming Guide SLG46824/6/7-A
  • 資料2:SLG46826 Datasheet


①NVMメモリの消去方法
 設計情報を保存している NVM は Pic24 等のようなフラッシュメモリを実装している一般的なマイコンの場合と同様にメモリ内容を消去してから書き込む必要があります(消去で'0'の状態になり、書き込みで'1'に変更できる)。消去と書き込みの最小単位(ページ
)は 16 バイトです。
 上記の資料1には書き込み方法として下図のフローチャートが記載されています。

書き込みフローチャート


 無料の資料なので言い辛いのですが、'Data'の初期値が未記載ですし'Yes'が変な位置にあります。メモリマップは下図のようになっていて、先頭がコンフィグ情報等が設定されるレジスタで 200H からがコンフィグ情報を保持する不揮発メモリ、300H からが EEPROM 用の不揮発メモリの領域です。

SLG46826 メモリマップ


 消去用のレジスタの構造は下図のようになっていて、NVM は 16 バイト(=1ページ)x16 の構成で最後のページはチップ情報なので書き込み禁止であることから上記のフローチャートでの'Data'の初期値は 80H であることが判りました。

消去レジスタの構造


 消去レジスタの説明には I2C でレジスタ内容を受け取った後に I2C 規格に準拠した ACK を返さず、詳細は SLG46824/6/7-A errata document (revision XC)を参照するように書かれています。

 下図は実機で確認した消去時の動作で、消去開始時のロジアナ波形です。ACK が無いだけでなく、微妙なタイミングでセッション終了(最初の橙色の四角)が発生しています。消去中はバスマスター等の動作は停止すると書いてあるので消去動作の完了は ACK 応答がある(I2C 通信では無反応の場合は NAK と見なされる)ことで確認する処理にしました。消去は最大 20ms と書いてあるのでタイマウェイトでもいいのですが ACK 方式の方が速いと思います。消去試験をしていたため、下図では I2C アドレスが 00H になっています。
 尚、前回記事では I2C のクロックが 100kHz でしたが今回から 400kHz に変更しています。

消去開始時のロジアナ波形例


 下図は ACK 応答を受け取り、消去処理の完了を確認しているタイミングのロジアナ波形です。ACK 応答に続くリードデータに対してはセッションを終了するために NAK 応答するようにしています。最終的な消去処理の実装はライトコマンドでポーリングし、ACK 応答が来た時は即座にセッションを終了するようにしました。この ACK ポーリング方式は NVM の書き込み時の書き込み処理終了の判断でも使いました。

消去完了時のロジアナ波形例


 消去処理全体の波形が下図になります。この時は 9ms 程度で消去処理が完了しています。

消去時全体のロジアナ波形例


②ソフトリセット時の待ち時間
 コンフィグデータを NVM に書き込んだ後に動作に反映させるためには電源を再投入するかソフトウェアでのリセット処理を行うことで NVM のコンフィグデータをコンフィグ用のレジスタにコピーする必要があります。今回は I2C でレジスタを設定することで実行されるソフトリセット方式を採用します。

 ソフトリセットは C8H レジスタに 02H を書き込むことで実行されます(下図参照)。

ソフトリセット関連レジスタ


 リセットコマンドを受信すると I2C のセッション終了をトリガにしてPOR(パワーオンリセット)と同じ処理が実行されるとのことです(下図参照)。

リセットコマンドタイミング


 リセット処理の時間が資料に見当たらないので上記の ACK ポーリング方式でリセット処理の完了を判断することにしました。
 下図はリセットコマンド発行時の波形です。書き込みデータに対してもきちんと ACK が返ってきてますね。セッション終了(橙色の四角)でリセット処理が開始されるので直後の ACK ポーリングに対しては NAK 状態になっています。

ソフトリセット開始時のロジアナ波形例


 下図は ACK を受信でき、 ACK ポーリング処理を終了した部分の波形です。

ソフトリセット終了時のロジアナ波形例


 下図はソフトリセット実行時の全体の波形です。リセット処理には 1.2ms 程度の時間がかかっているようです。この時間は電源電圧や設計した回路内容等にも依存するかもしれません。

ソフトリセット実行中のロジアナ波形例


③ I2C アドレスの設定方法
 SLG46826 は設定により I2C のアドレスを変更できますし、IO ピンで設定するようにもできます。
 下図がアドレス設定用レジスタの構成です。上位4ビットでレジスタ設定か外部ピン設定かを指定します。レジスタ設定の場合は下位4ビットの値になります。従って CAH レジスタに1が設定(これはディフォルト値)されるとアドレスの上位4ビット(GrrenPAK の資料では Control Code と記載)が 0001B になります。

I2C アドレス設定レジスタ


 このように I2C のアドレス設定は柔軟性があるので今回開発した書き込みソフト Grenwriter では後述するように I2C スキャン機能やアドレス設定機能を付けました。



■Grenwriter に実装した機能
 下図のメニュー画面で表示されている機能を実装しました。I2C によるレジスタの書き込み機能も実装するか迷いましたが、NVM から レジスタへの転送時はほぼすべての機能を停止して実行しているようなのでレジスタの詳細を理解せずに下手に動かしながら変更した場合、問題が発生する可能性があること、及び NVM の書き込みは 1000 回程度は可能なようなのでレジスタ書き込みは実装しないことにしました。

メニュー画面

 個々の機能に関して以降に記載します。
  1. I2C スキャン
     I2C のアドレス空間をスキャンする機能です。現在のアドレス設定値に加え、見つかったアドレス候補の値も表示するようにしました。複数の SLG46826 が接続されているケースも考慮して、現在のアドレス設定値は自動変更されません。

    スキャン画面

  2. I2C アドレス設定
     SLG46826 と I2C 通信するためのアドレスを設定します。

  3. コンフィグ用 I2C アドレス設定
     本設定をした場合、NVM 書き込み時にコンフィグ内の I2C アドレスが設定値に置き換えられます。I2C 通信用のアドレスも自動で変更されるので NVM 書き込み後も継続して通信できます。

  4. ヘキサファイルロード
     GreenPAK Designer で作成したヘキサファイルを TeraTerm の画面にドラッグ&ドロップすることで読み込み、読み込んだデータの内容を画面に表示します。Grenwriter には内部に256バイトのバッファがあり、ヘキサファイルの内容はこのバッファに読み込まれます。以降に記載しているダンプ系コマンドを実行した際もバッファ内データがダンプ内容に置き換えられます。

    ヘキサファイルロード画面

  5. NVM 書き込み
     バッファ内容を NVM へ書き込みます。コンフィグ用 I2C アドレスが設定済みの場合はバッファ内のアドレス値を設定値に変更してから NVM へ書き込みます。書き込みにより I2C アドレスが変更される場合でもその後の操作が継続できるようにアドレスの設定値が自動的に追従します。

    NVM 書き込み画面

  6. EEP 書き込み
     バッファ内容を EEP に書き込みます。後述の EEP 読み込み後にバッファにパッチを当て EEP 書き込みを行うことでチップ内のデータへのパッチが可能となります。

    EEP 書き込み画面

  7. NVM ダンプ
     NVM の内容を SLG46826 から読み込みます。内部バッファの内容も読み込んだものに置き換えられます。

    NVM ダンプ画面

  8. EEP ダンプ
     EEP の内容を SLG46826 から読み込みます。内部バッファの内容も読み込んだものに置き換えられます。画面構成は NVM ダンプと同様です。

    EEP ダンプ画面

  9. レジスタダンプ
     レジスタの内容を SLG46826 から読み込みます。内部バッファの内容も読み込んだものに置き換えられます。

    REG ダンプ画面

  10. バッファデータパッチ
     バッファ内の内容を変更します。

    バッファパッチ画面





■Grenwriterソフトウェア
 ソースを以下に貼っておきます。picle言語はC言語に近いのでC言語が使えるなら処理内容を確認するのは容易だと思います。

メイン処理(picle言語)
# GreenPAK writer Ver 0.01 2024/05/26 by skyriver # Grenwriter written with picle language use LibGreen; # serial reset proc SeriReset() { var dmy; if (I2CAdr(Padr,I2C_WR) = 0) { if (I2CSnd($c8) = 0) { dmy = I2CSnd( 2 ); # reset } I2CStop(); } } # erase NVM proc EraNvm() { PrnStr_( "Erase NVM :" ); EraRom(0,14); } # erase EEPROM proc EraEep() { PrnStr_( "Erase EEP :" ); EraRom(16,31); } # write NVM func WriteNvm() { PrnStr_( "¥nWrite NVM :" ); return = WritePages( 2, 14 ); PrnStr_( "¥n" ); } # write EEPROM func WriteEep() { PrnStr_( "¥nWrite EEP :" ); return = WritePages( 3, 15 ); PrnStr_( "¥n" ); } # input I2C adr func InpAdr( now ) { PrnStr_( "¥ninput I2C adr(0..F) " ); if ( now >= 0 ) { PrnHexB_( now / 8 ); } else { PrnStr_( "xx" ); } PrnStr_( " -> " ); return = GetHex(); } # patch buffer data proc Patch() { var adr,dat,c; PrnStr_( "¥n input adr:" ); adr = GetHexByte(); if ( (adr>=0) & (adr<=$FF) ) { PrnHexB_( adr ); PrnStr_( " data " ); PrnHexB_( _Buf[ adr ] ); PrnStr_( " -> " ); dat = GetHexByte(); PrnHexB_( dat ); PrnStr_( " sure?(Y/N) : " ); c = InpChar_(); PrnChar_( c ); if ( ToUpper(c) = 'Y' ) { _Buf[ adr ] = dat; } PrnStr_( "¥n" ); } } proc PrnErr() { PrnStr_( " .. Error !!¥n" ); } proc Help() { PrnStr_("¥n<<<< Grenwriter for Green PAK SLG46826 >>>>¥n"); PrnStr_(" S:scan I2C A:set I2C current adr¥n"); PrnStr_(" L:load HexFile B:set I2C adr(NVM)¥n"); PrnStr_(" W:write NVM E:write EEP¥n"); PrnStr_(" D:dump NVM F:dump EEP¥n"); PrnStr_(" R:dump REG P:patch buffer data¥n"); PrnStr_(" Q:quit¥n"); PrnStr_(" V0.01 2024/05/26 by skyriver¥n¥n"); } proc main() { var c,adr,nadr,flg; Init(); Padr = $08; # I2C addr nadr = -1; while (1) { Help(); PrnChar_( ']' ); c = ToUpper(InpChar_()); PrnChar_( c ); if ( c = 'S' ) { PrnStr_( "¥nscan I2C adr" ); adr = Scan(); PrnStr_( "¥n now adr:" ); PrnHexB_( Padr / 8 ); if ( adr >= 0 ) { PrnStr_( " estimate adr:" ); PrnHexB_( adr / 8 ); } PrnStr_( "¥n" ); } else if ( c = 'A' ) { adr = InpAdr( Padr ); if ( (adr>=0)&(adr<16) ) { PrnHexB_( adr ); Padr = adr * 8; } PrnStr_( "¥n" ); } else if ( c = 'L' ) { PrnStr_( "¥nDrag HexFile" ); if ( LoadHex() = 0 ) { DumpBuf(); } } else if ( c = 'B' ) { adr = InpAdr( nadr ); if ( (adr >=0)&(adr<16) ) { PrnHexB_( adr ); nadr = adr * 8; } PrnStr_( "¥n" ); } else if ( c = 'W' ) { if ( nadr >= 0 ) { _Buf[ $CA ] = nadr / 8; } PrnStr_( "¥nErase NVM :" ); flg = 1; EraRom( 0, 14 ); if ( WriteNvm() = 0 ) { DumpBuf(); flg = 0; } SeriReset(); Padr = _Buf[ $CA ] * 8; WaitAck(); if ( flg ) { PrnErr(); } } else if ( c = 'E' ) { PrnStr_( "¥nErase EEP :" ); flg = 1; EraRom( 16, 31 ); if ( WriteEep() = ) { DumpBuf(); flg = 0; } if ( flg ) { PrnErr(); } } else if ( c = 'D' ) { PrnStr_( "¥n dump NVM" ); if ( ReadMem( 2 ) = 0 ) { DumpBuf(); } } else if ( c = 'F' ) { PrnStr_( "¥n dump EEP" ); if ( ReadMem( 3 ) = 0 ) { DumpBuf(); } } else if ( c = 'R' ) { PrnStr_( "¥n dump REG" ); if ( ReadMem( 0 ) = 0 ) { DumpBuf(); } } else if ( c = 'P' ) { Patch(); } else if ( c = 'Q' ) { break; } } PrnStr_( "¥nSee you again!" ); }



LibGreen ライブラリ(picle言語)
#LibGreen GreenPAK writer libraly Ver 0.01 2024/05/26 by skyriver # written with picle language use LibPic; use LibI2C; use LibGreen1; # load Hex into buffer # return -> 0:ok func LoadHex() { var i,c,cnt,adr,type,dmy; for ( i = 0; i < 256; i=i+1 ) { _Buf[ i ] = 0; } do { do { c = InpChar_(); if ( c = $1B ) { # ESC return = -1; break; # $__$ } } while ( c <> ':' ); Sum = 0; cnt = GetHexByte(); if ( (cnt>$20) | (cnt<0) ) { return = 1; } else { adr = GetHexByte() * 256 + GetHexByte(); if ( adr >$F0 ) { return = -2; } else { type = GetHexByte(); for ( i = 0; i < cnt; i=i+1 ) { _Buf[ adr + i ] = GetHexByte(); } dmy = GetHexByte(); if ( Sum & $FF ) { return = 3; # SUM err } } } } while ( (return=0) & (type=0) ); } # write ROM 1 page # badr <- block adr 00:RAM,02:NVM,03:EEP # page <- write page num # return -> 0:ok func WrRom( badr, page ) { var i,cbyte,tmp; cbyte = Padr | badr; page = page * 16; return = I2CAdr( cbyte, I2C_WR ); if ( return = 0 ) { return = I2CSnd( page ); if ( return = 0 ) { for ( i = 0; (i < 16) & (return = 0); i=i+1 ) { return = I2CSnd( _Buf[ page + i ] ); } I2CStop(); WaitAck(); if ( return = 0 ) { return = 1; if ( I2CAdr( cbyte, I2C_WR ) = 0 ) { if ( I2CSnd( page ) = 0 ) { if ( I2CAdr( cbyte, I2C_RD ) = 0 ) { for ( i = 0; i < 16; i=i+1 ) { return = I2CRcv(1); if ( return = 0 ) { if ( I2CRcv <> _Buf[ page + i ] ) { PrnStr_( " missmatch at " ); PrnHex_( page + i ); PrnChar_( ' ' ); PrnHexB_( _Buf[ page + i ] ); PrnStr_( "->" ); PrnHexB_( I2CRcv ); return = 2; # miss match break; # $__$ } } } tmp = I2CRcv(0); } } } I2CStop(); } } } } # write pages in ROM # basr <- block adr 00:RAM,02:NVM,03:EEP # until <- last page num func WritePages( badr, until ) { var i; for ( i = 0; (i<=until) & (return=0); i=i+1 ) { PrnChar_( ' ' ); PrnHexB_( i ); return = WrRom( badr, i ); } } proc Init() { var i; InitReg(); InitI2C( $0210 ); # I2C 2ch:$0210 LATA[0] = 0; LATB[0] = 0; LATA[-2] = $0000; LATB[-2] = $0b0c; I2C_RD = 1; I2C_WR = 0; _Buf = Alloc( 256 ); for ( i = 0; i < 256; i=i+1 ) { _Buf[ i ] = 0; } } proc main() { var est; Init(); Padr = $08; # I2C addr PrnStr_( "¥nread RAM" ); if ( ReadMem( 0 ) = 0 ) { DumpBuf(); } PrnStr_( "¥nread NVM" ); if ( ReadMem( 2 ) = 0 ) { DumpBuf(); } PrnStr_( "¥nread EEP" ); if ( ReadMem( 3 ) = 0 ) { DumpBuf(); } est = Scan(); if ( est >= 0 ) { PrnStr_( "¥n estimate:" ); PrnHexB_( est ); } PrnStr_( "¥nDrag Hex file" ); if ( LoadHex() = 0 ) { DumpBuf(); } }



LibGreen1ライブラリ(picle言語)
#LibGreen1 GreenPAK writer libraly Ver 0.01 2024/05/26 by skyriver # written with picle language var I2C_RD, I2C_WR, Padr, _Buf, Sum; func ToUpper( c ) { if ( (c >= 'a') & (c <='z' ) ) { c = c - ('a' - 'A'); } return = c; } # scan I2C # return -> estimate adr func Scan() { var i,j,adr,cnt,est; cnt = 0; return = -1; est = -1; adr = 0; for ( i = 0; i < 8; i=i+1 ) { PrnStr_( "¥n " ); PrnHexB_( adr ); PrnStr_( " :" ); for ( j = 0; j < 16; j=j+1 ) { PrnChar_( ' ' ); if ( I2CAdr(adr,I2C_WR) ) { PrnStr_( "--" ); if ( cnt = 4 ) { return = est; } cnt = 0; } else { PrnHexB_( adr ); cnt = cnt + 1; if ( cnt = 1 ) { est = adr; } } I2CStop(); adr = adr + 1; } } PrnStr_( "¥n" ); } # wait until ack proc WaitAck() { var dat; do { dat = I2CAdr(Padr,I2C_WR); I2CStop(); } while (dat); } # erase ROM # st <- start block # en <- end block proc EraRom( st, en ) { var i,dmy; if ( st <= en ) { for ( i=st; i<=en; i=i+1 ) { PrnChar_(' '); if (I2CAdr(Padr,I2C_WR) = 0) { if (I2CSnd($E3) = 0) { # ERSR dmy = I2CSnd(i | $80); } } I2CStop(); WaitAck(); PrnHexB_(i); } } } # dump buffer proc DumpBuf() { var adr,i,j; adr = 0; for ( j=0; j<16; j=j+1 ) { PrnStr_( "¥n " ); PrnHexB_( adr ); PrnStr_( " :" ); for ( i=0; i< 16; i=i+1 ) { PrnChar_( ' ' ); if ( i = 8 ) { PrnStr_( "- " ); } PrnHexB_( _Buf[adr] ); adr = adr +1 ; } } PrnStr_( "¥n" ); } # read memory # badr <- block adr 00:RAM,02:NVM,03:EEP # return -> 0:no error func ReadMem( badr ){ var i, cbyte; cbyte = Padr | badr; return = I2CAdr( cbyte, I2C_WR ); if ( return = 0 ) { return = I2CSnd( 0 ); if ( return = 0 ) { return = I2CAdr( cbyte, I2C_RD ); if ( return = 0 ) { for ( i = 0; i < 256; i=i+1 ) { return = I2CRcv( 1 ); if ( return ) { break; # $__$ } _Buf[ i ] = I2CRcv; } cbyte = I2CRcv( 0 ); # dummy read(nack) I2CStop(); } } } } # get hex data from input # GetHex() { func GetHex() { var c; c = InpChar_(); c = ToUpper( c ); if ( (c >= '0') & (c <= '9') ) { return = c - '0'; } else if ( (c >= 'A') & (c <= 'F') ) { return = c + ( 10 - 'A' ); } else { return = -1; } } # get hex byte data func GetHexByte() { return = GetHex(); if ( return >= 0 ) { return = return * 16 + GetHex(); Sum = Sum + return; } }



LibI2C ライブラリ(picle言語)
#LibI2C I2C libraly v0.02 2024/05/25 # written with picle language by skyriver var RegRcv,RegTrn,RegBrg; var RegCon,RegStat; var I2CRcv; # send data to slave # data <- send data # return -> 0:no error func I2CSnd( data ) { while ( RegCon[0] & $1f ) {} RegTrn[0] = data; while ( RegStat[0] & $4000 ) {} return = RegStat[0] & $8000; # check nack } # receive data form slave # ack <- 0:nak, else:ack # I2CRcv -> receive data # return -> 0:no error func I2CRcv( ack ) { while ( RegCon[0] & $1f ) {} RegCon[0] = RegCon[0] | $0008; # enable receive while ( RegCon[0] & $0008 ) {} if ( ack ) { RegCon[0] = RegCon[0] & ~$0020; # set ack } else { RegCon[0] = RegCon[0] | $0020; # set nack } RegCon[0] = RegCon[0] | $0010; # send ack/nack while ( RegCon[0] & $0010 ) {} I2CRcv = RegRcv[0]; return = RegStat[0] & $0040; # check overflow } # start seqence and send salave adrs # adr <- slave_adrs # rd <- 1:rd,0:wr # return -> 0:no error func I2CAdr( adr, rd ) { RegCon[0] = RegCon[0] | 3; # repeated start return = I2CSnd( (adr+adr)|rd ); } # stop I2C sequence proc I2CStop() { RegCon[0] = RegCon[0] | $0004; # set PEN(stop) while ( RegCon[0] & $0004 ) {} } proc InitI2C( base ) { RegRcv = base; RegTrn = base + 2; RegBrg = base + 4; RegCon = base + 6; RegStat = base + 8; RegBrg[0] = 37; # Fcy:16MHz -> 157:100kHz 37:400kHz RegCon[0] = $8000; # enable I2C } proc main() { var adr,err,i,j; InitI2C( $0210 ); # I2C 2ch:$0210 adr = 0; for ( j = 0; j < 8; j=j+1 ) { PrnHexB_( adr ); PrnStr_( " :" ); for ( i = 0; i < 16; i=i+1 ) { PrnChar_( ' ' ); if ( I2CAdr( adr, 0 ) ) { PrnStr_( "--" ); } else { PrnHexB_( adr ); } I2CStop(); adr = adr + 1; } PrnStr_( "¥n" ); } }



LibPic ライブラリ(picle言語)
#LibPic major registance v0.01 2018/08/20 var _REG,LATA,LATB,LATC; var ArIdx,Word,_Byte; func Alloc( size ) { return = Array_(ArIdx); ArIdx=ArIdx+size; } proc InitReg() { var Ad1pcfg; Ad1pcfg = $032c; Ad1pcfg[0] = $ffff; # set digital mode LATA=$02c4; LATB=$02cc; LATC=$02d4; Word = Alloc(1); _Byte = Word; } func GetHigh( dat ) { Word[0] = dat; return = _Byte[1]; } func GetLow( dat ) { return = dat & $ff; }



★追記 2024/05/28
 X(旧Twitter)に投稿したメッセージに添付した操作例の動画を貼っておきます。




[TOP] [ 前へ ] 連載記事一覧 [ 次へ ]

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。