SSブログ

例外ベクタを調べた (1) [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

MCF52233の内蔵モジュールをめぐる旅を続けてきましたが、例外(割り込み)については、ほぼ無視してきました。 SilentCで使うには無理があるだろうという理由からです。 本当のところ、例外処理関連はどうなっているのか調べてみました。

例外ベクタ・テーブルは、どこだ?

例外ベクタ・テーブルの先頭アドレスは、CPU内の Vector Base Register (VBR) に格納されています。 ところが、このレジスタには、 BDM を使って裏口からのぞきに行く以外には、書き込みを行うインストラクションしか見当たりません。 そのため、例外ベクタ・テーブルの正確な場所を調べることはできません。 ただし、VBR レジスタの値は1MByte単位でしか移動できないため、MCF52233で動作しているSilentCの場合、 FLASH (0x00000000) または、 RAM (0x20000000) しか可能性がありません。 きっと、 RAM には置かないだろうという推測の下、 0x00000000 からの内容を調べてみました。

CPUに直結したシステム例外

MCF52233の例外ベクタは、三つの部分に分かれています。

  1. CPUに直結したシステム例外
  2. 割り込みコントローラ0が発行する例外
  3. 割り込みコントローラ1が発行する例外

最初は、システム例外ベクタを調べます。 MCF52233リファレンス・マニュアルに用途が明記されていたベクタ及び例外ベクタ・テーブルに値が書き込まれていた(0xFFFFFFFFではない)ベクタを抜き出してみました。

例外ベクタ0-63
ベクタ番号ベクタ・アドレスベクタ用途
00x0000x20000800初期スーパバイザ・スタック・ポインタ
10x0040x00000418リセット・ベクタ
20x0080x000115F0アクセス・エラー
30x00C0x000115F0アドレス・エラー
40x0100x000115F0不正命令
50x0140xFFFFFFFF0割り
80x0200x000115F0特権違反
90x0240x000115F0トレース
100x0280x000115F0未実装A行命令
110x02C0x000115F0未実装F行命令
120x0300x000115F0デバッグ
140x0380x000115F0フォーマット
150x03C0x000115F0Reserved
240x0600xFFFFFFFFスプリアス割り込み
25-310x064 - 0x07C0x000115E0Reserved
32-470x080 - 0x0BC0x000115F0TRAP命令
620x0F80x001FE8**Reserved
630x0FC0x****0000Reserved

ベクタ番号0には、初期スーパバイザ・スタック・ポインタの値が入っています。 0x20008000 というと RAM の末尾を指しているように見えます。 しかし、リセット直後、 MCF52233 の RAM は使用不可の状態になっているため、そのままでは使用することが出来ません。 RAM を使用可能にするには、 MOVEC インストラクションで隠しレジスタ RAMBAR に値を設定しなくてはなりません。 リセット直後のプログラムであわただしく、この初期化作業をしているのだろうと思われます。 初期化される前にバス・エラーなんかが起こったら、どうなるんだろうね。

ベクタ番号5の「0割り (divide by zero)」のベクタが実装されていません。 もし、「0割り」が発生したら、0xFFFFFFFFにインストラクション・フェッチが発生します。 ところが、奇数番地でのインストラクション実行は禁止されているので、別のエラー(どれかな?)が発生して、0x000115F0に飛ぶことになるでしょう。

ベクタ番号15の未使用 (Reserved) ベクタに値が書かれています。 ここは、ColdFire のプログラマーズ・マニュアルでは、「未初期化割り込み (Uninitialized Interrupt)」があるとされていますが、MCF52233では、「未使用 (Reserved)」になっています。

ベクタ番号24には、「スプリアス割り込み」が割り当てられていますが、ベクタが実装されていません。 スプリアス割り込みって、何でしょうね? 説明が見当たりません。

ベクタ番号25から31は、MCF52233では未使用 (Reserved) なのですが、ベクタが入っています。 ColdFire のプログラマーズ・マニュアルによると、この部分にはオート・ベクタと呼ばれる外部割込みのためのベクタが入ることになっています。 ところが、MCF52233の場合には、すべての割り込みが割り込みコントローラを介して発行されるので、オード・ベクタは、入っていないはずです。

ベクタ番号62,63は、未使用 (Reserved) ですが、ベクタらしからぬ値が入っています。 これは、MACアドレスだ。 こういう所に置かれると、そうそう簡単には書き換えられないですね。

一覧にしてわかったのは、システム例外は、「リセット」と「それ以外」の二つの選択肢しかないということです。 リセット以外のシステム例外が発生したら、どういう処理がなされるのかな? スタック・フレームを調べて、改めて分岐するようなプログラムになっているのかな?

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentCが使っていないモジュールを探せ(9) [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

モジュール探訪の旅。 今日は、Universal Asynchronous Receiver / Transmitter (UART) を走ります。

最初は、レジスタのダンプから

UARTは、いわゆる非同期シリアル・インターフェースの事で、8ビットマイコンでは、 Serial Communication Interface (SCI) という名前で呼ばれたりします。 いずれも、信号レベルを変換して RS-232C という規格に使用されます。

MCF52233の三つのUARTのうち、UART0はSilentCのコンソールとして使用されていることが分かっていますので、適切にレジスタが設定されているはずです。 さっそく、レジスタを調べてみましょう。

40000200  07 00 00 00 0c 00 00 00  ........
40000208  00 00 00 00 00 00 00 00  ........
40000210  1e 00 00 00 05 00 00 00  ........
40000218  00 00 00 00 08 00 00 00  ........
40000220  00 00 00 00 00 00 00 00  ........
40000228  00 00 00 00 00 00 00 00  ........
40000230  0f 00 00 00 fe 00 00 00  ........
40000238  00 00 00 00 00 00 00 00  ........

これは、UART0のレジスタです。 UART1/UART2のレジスタは、初期設定のままでしたので、今回は割愛します。

UARTのレジスタは、32ビット境界に8ビットずつ配置されています。 16ビットのボーレートレジスタでさえ、8ビットずつ配置されているので、簡単に16ビットの値を設定することはできません。 加えて、UMR10/UMR20レジスタのように同じアドレスに別のレジスタが配置されて、モードによって書き込み先レジスタが切り替わるような仕組みも入っています。 これは、またDMAのお世話にならなきゃいけませんね。 ダンプリストをとっただけでは、隠れたレジスタの値は確認できないので、DMAの力を借りて引っ張り出してみました。

char *uart0 =0x40000200;
main(){
  char *pacr2 =0x40000026;
  char *umr=MemoryAlloc(4);
  char usr,uipcr,uisr,uip;
  usr=uart0[0x04];uipcr=uart0[0x10];
  uisr=uart0[0x14];uip=uart0[0x34];
  *pacr2 = 0x40 | (*pacr2 & 0x0F); // Enable DMA for UART
  readUMR(uart0,umr);
  PrStr("\r\nUMR10 =");PrHexByte(umr[0]);
  PrStr("\r\nUMR20 =");PrHexByte(umr[1]);
  PrStr("\r\nUSR0  =");PrHexByte(usr);
  PrStr("\r\nUIPCR0=");PrHexByte(uipcr);
  PrStr("\r\nUISR0 =");PrHexByte(uisr);
  PrStr("\r\nUIP0  =");PrHexByte(uip);
  PrStr("\r\n");
  MemoryFree(umr);
}
readUMR(char *uart, char *buf){
  long *sar1  =0x40000110;
  long *dar1  =0x40000114;
  long *bcr1  =0x40000118;
  long *dcr1  =0x4000011c;
  *bcr1 = 0x01000000; // clear DONE
  uart[8] = 0x10; // UCR:Reset UMR pointer
  *sar1 = uart+0; // source=UMRx
  *dar1 = buf; // destination
  *bcr1 = 2; // byte count
  *dcr1 = 0x001B0000; // Simple byte transfer
}

こうなりゃ、意地でもSilentCを使ってやる。 実行結果は、こんな風になりました。

UMR10 =13
UMR20 =07
USR0  =0c
UIPCR0=0e
UISR0 =05
UIP0  =fe

今まで見えてこなかった、UMR10が出てきました。 ステータス・レジスタ以外の設定レジスタを表にしてみました。

UART0のレジスタ設定
レジスタ備考
UMR10 [RXRTS]0 (no effect) 受信機能では、URTS0出力(request to send)をネゲートすることで通信相手に受信できないことを知らせ、バッファあふれを防止することができます。 ここでは、使用していません。
UMR10 [RXIRQ/FFULL]0 (RXIRQ) 1キャラクタが受信機に到着するたびに割り込みまたはDMAを発生します。 ここで、1 (FFULL)を指定すると、3キャラクタ分のFIFOがいっぱいになるまで割り込みまたはDMAを発生させません。
UMR10 [ERR]0 (character mode) FIFOの先頭のキャラクタを受信したときのエラー情報をステータスレジスタUSRのRB/FE/PEビットに示します。 ここで、1 (block mode)を指定すると、エラー・リセット・コマンドを送るまでエラー情報を蓄積し続けます。
UMR10 [PM]10 (no parity) パリティ無しプロトコルを指定します。
UMR10 [PT]0 (N/A) パリティの極性を示しますが、PMビットが、10 (no parity)の場合には、無効です。
UMR10 [B/C]11 (8bits) キャラクタあたりのビット長を指定します。
UMR20 [CM]00 (normal) ループ・バックや自動エコー・バックの機能を指定します。 ここでは、ループ・バックもエコー・バックも使用しません。
UMR20 [TXRTS]0 (no effect) 送信機能では、URTS0出力(ready to send)をアサートして送信するキャラクタがあることを通信相手に知らせることができます。 ここでは、使用していません。
UMR20 [TXCTS]0 (no effect) 送信機能では、通信相手から送られてきたUCTS0入力(clear to send)信号がアサートされるまで通信を止めることができます。 この機能によって、通信相手の受信バッファあふれを防ぐことができます。 ここでは、使用していません。
UMR20 [SB]0111 (1 bit) 1キャラクタの通信の最後にあるストップ・ビットの幅を指定します。 1ビット幅のストップ・ビットが使用されます。

と、各レジスタビットを並べてみましたが、UARTモジュールの設定レジスタは、多くが書き込み専用(write-only)になっているので、レジスタを読み取るだけでは本当の設定がわかりません。 例えば、ボーレートレジスタUBG10/UBG20も書き込み専用なので、ボーレートの設定も確認できない状態です。 これは、セキュリティなのか?

Interface誌の「マイコン基板とRS-232Cボードの接続図」では、UCTS0端子がGNDに接続されて「いつでも受信できます」状態になっています。 ところが、レジスタ設定だけを見ると、UCTS0入力で送信を抑止する機能(TXCTS)は使用されていないことがわかります。 ソフトウェアで処理しているのかな?

SilentCからUART1を使う

UART0は、SilentCによって使用されているので、ここではUART1を使用します。

最初にボーレートの計算を行っておきます。 今回は、SilentCと同じく 57600baud;8bit;no-parity;1-stopbit プロトコルを使用します。

divider=(60MHz)÷(32×57600Hz)=32.55

ということで、UBG11=0x00;UBG21=0x21;としてみます。 誤差が1.7%と大き目ですが、まあ大丈夫でしょう。

処理する内容ですが、「HELLO WORLD!」を延々と送信し続けるプログラムを作成してみました。

main(){
  char *pubpar=0x40100072;
  char *pacr2 =0x40000026;
  char *uart1 =0x40000240;
  char *ucr1  = uart1+0x08;
  char *msg   ="HELLO WORLD!\r\n";
  char *s;
  *pacr2 = 0x04 | (*pacr2 & 0xF0); // Enable DMA for UART1
  *pubpar= 0x05; // select URXD1/UTXD1
  *ucr1  = 0x0A; // disable RX/TX
  setUMR(uart1, 0x13, 0x07); // Set UMR
  setUBG(uart1, 0x0021); // Set UBG
  *ucr1  = 0x04; // enable TX
  s = msg;
  #stop 0;
  for (;;){
    if (Getc(0)=='q') break;
    while (!isTXRDY(uart1)) SystemSleep();
    writeUTB(uart1, *s++);
    if (*s == 0) s = msg;
  }
}
readUMR(char *uart, char *buf){
  long *sar1  =0x40000110;
  long *dar1  =0x40000114;
  long *bcr1  =0x40000118;
  long *dcr1  =0x4000011c;
  *bcr1 = 0x01000000; // clear DONE
  uart[8] = 0x10; // UCR:Reset UMR pointer
  *sar1 = uart+0; // source=UMRx
  *dar1 = buf; // destination
  *bcr1 = 2; // byte count
  *dcr1 = 0x001B0000; // Simple byte transfer
}
setUMR(char *uart, char umr1, char umr2){
  long *sar1  =0x40000110;
  long *dar1  =0x40000114;
  long *bcr1  =0x40000118;
  long *dcr1  =0x4000011c;
  char *buf = MemoryAlloc(4);
  buf[0] = umr1;
  buf[1] = umr2;
  *bcr1 = 0x01000000; // clear DONE
  uart[8] = 0x10; // UCR:Reset UMR pointer
  *sar1 = buf; // source
  *dar1 = uart+0; // destination=UMR
  *bcr1 = 2; // byte count
  *dcr1 = 0x00530000; // Simple byte transfer
  MemoryFree(buf);
}
setUBG(char *uart, int baud){
  writeByte(uart+0x18, (baud>>8)&0xff);
  writeByte(uart+0x1c, (baud   )&0xff);
}
isTXRDY(char *uart){
  return uart[0x04]&0x04;
}
isRXRDY(char *uart){
  return uart[0x04]&0x01;
}
writeUTB(char *uart, char c){
  writeByte(uart+0x0c, c);
}
readURB(char *uart){
  return uart[0x0c];
}
writeByte(char *addr, char data){
  long *sar1  =0x40000110;
  long *dar1  =0x40000114;
  long *bcr1  =0x40000118;
  long *dcr1  =0x4000011c;
  char *buf = MemoryAlloc(4);
  buf[0] = data;
  *bcr1 = 0x01000000; // clear DONE
  *sar1 = buf; // source
  *dar1 = addr; // destination=UTB
  *bcr1 = 1; // byte count
  *dcr1 = 0x00530000; // Simple byte transfer
  MemoryFree(buf);
}

実行速度は、毎秒3行ほどでした。 換算すると、500bpsほどにしかなりません。 やはり、このあたりがインタプリタの限界でしょうか。

UARTモジュールには、読み出し・書き込みを厳密に制御しなくてはならないところがたくさんあります。 リファレンス・マニュアルには、「UBGレジスタから値を読み出すと、良く分からないことが起きて、送受信がうまくいかなくなるよ。」などと脅し文句が書いてあります。 また、URB/UTBレジスタは、同じアドレスに配置されているので、送信データを書き込む時に読み出しアクセスが発生すると、せっかく受信したデータが消えてしまいます。 こういった、クリティカルな箇所は、すべてDMAによる転送処理を使用しました。 え~と、これは、何のプログラムだっけ。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentC のメモリ管理をのぞく [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

SilentCには、メモリ割り当て関数 MemoryAlloc が用意されています。 どんなメモリ管理が行われているのか、ちょっとのぞいてみましょう。

割り当て限界を見る

MCF52233には、32KByteのRAMが搭載されています。 この32KByteのメモリを MemoryAlloc でどのぐらい割り当ててくれるのか、調べます。 調査方法は、 MemoryAlloc 関数を何度も呼び出す方法をとりました。

SilentC (May 13 2008)
OK
PrHex(MemoryAlloc(256))
20003934
OK
PrHex(MemoryAlloc(256))
20003a38
OK
PrHex(MemoryAlloc(256))
20003b3c
OK
  :
PrHex(MemoryAlloc(256))
20007c3c
OK
PrHex(MemoryAlloc(256))
20007d40
OK
PrHex(MemoryAlloc(256))
20007e44
OK
PrHex(MemoryAlloc(256))

256バイトのメモリを割り当てていくと、0x20007e44から0x20007f43までの256バイトを割り当てたところで、SilentCは、どこかに飛んでいってしまいました。 どうやら、ヒープ領域の上限は、このあたりにありそうです。

管理領域には何があるのか

割り当てられているアドレスを見てみるとわかるように、割り当てた領域よりも4バイト多くアドレスが進んでいます。 おそらく、この4バイトに何らかの管理情報が含まれているのだと考えられます。 何があるのか、ダンプしてみます。

PrHex(MemoryAlloc(256))
20003934
OK
PrHex(MemoryAlloc(256))
20003a38
OK
m::d(0x20003a00)
20003a00  00 00 00 00 00 00 00 00  ........
20003a08  00 00 00 00 00 00 00 00  ........
20003a10  00 00 00 00 00 00 00 00  ........
20003a18  00 00 00 00 00 00 00 00  ........
20003a20  00 00 00 00 00 00 00 00  ........
20003a28  00 00 00 00 00 00 00 00  ........
20003a30  00 00 00 00 00 41 00 40  .....A.@
20003a38  00 00 00 00 00 00 00 00  ........
20003a40  00 00 00 00 00 00 00 00  ........
20003a48  00 00 00 00 00 00 00 00  ........
20003a50  00 00 00 00 00 00 00 00  ........
20003a58  00 00 00 00 00 00 00 00  ........
20003a60  00 00 00 00 00 00 00 00  ........
20003a68  00 00 00 00 00 00 00 00  ........
20003a70  00 00 00 00 00 00 00 00  ........
20003a78  00 00 00 00 00 00 00 00  ........

0x20003a38からの256バイトのブロックの直前には、00410040という4バイトの値が入っていました。 これは、何かな? 割り当てるブロックのサイズを変えながら、値を調べてみました。

割り当てブロックサイズと直前の管理領域の値
サイズブロック先頭管理領域アドレス管理領域内容
1620003934200039300005 0004
3220003948200039440009 0008
482000396c20003968000d 000c
64200039a02000399c0011 0010
80200039e4200039e00015 0014
9620003a3820003a340019 0018
11220003a9c20003a98001d 001c
12820003b1020003b0c0021 0020

管理領域の2ワード目は、割り当てたブロックサイズの4分の1の値になっていました。 そのため、ここには、ロングワード(4バイト)単位のブロックサイズが入っているのだろうと推測します。

メモリを開放すると管理領域はどうなるか

管理領域の1ワード目の数値の意味は、これだけでは不明です。 そこで、1024バイトの領域を確保・開放して管理領域の変化を調べました。

PrHex(MemoryAlloc(1020))
20003934
OK
PrHex(MemoryAlloc(1024))
20003d34
OK
MemoryFree(0x20003934)
OK
PrHex(MemoryAlloc(1024))
20004138
OK
PrHex(MemoryAlloc(1024))
2000453c
OK
MemClear(0x20003d34,1024)
OK
MemClear(0x20004138,1024)
OK
MemClear(0x2000453c,1024)
OK
MemoryFree(0x20004138)
開放したメモリは、すぐにインタプリタの作業領域にされてしまうようなので、少々、トリッキーな事をしています。 管理領域の前後はこのようになりました。
20004120  00 00 00 00 00 00 00 00  ........
20004128  00 00 00 00 00 00 00 00  ........
20004130  00 00 00 00 01 01 01 00  ........
20004138  00 00 00 00 00 00 00 00  ........
20004140  00 00 00 00 00 00 00 00  ........

20004528  00 00 00 00 00 00 00 00  ........
20004530  00 00 00 00 00 00 00 00  ........
20004538  01 01 01 00 00 00 00 00  ........
20004540  00 00 00 00 00 00 00 00  ........
20004548  00 00 00 00 00 00 00 00  ........

確保された状態でも、開放された状態でも管理領域の値には変化がありません。 ということは、1ワード目は、単に管理情報を含んだメモリブロックの長さということも考えられます。 空エリア情報が管理領域に書いてあるわけではなかったのか。

4バイトの管理領域があることを発見したときには、MS-DOSのような管理方法かと思っていたのですが、どうもそうではなさそうです。 ここから先は、リバース・エンジニアリングかな?

関連文献

私に記憶にあるMS-DOSのメモリ管理方法は、この本で読んだ知識だったはずです。

応用MS‐DOS (アスキー・ラーニングシステム)

応用MS‐DOS (アスキー・ラーニングシステム)

  • 作者: 村瀬 康治
  • 出版社/メーカー: アスキー
  • 発売日: 1986/07
  • メディア: 単行本

(続) SilentC で QSPI を使う [ColdFire V2]このエントリーを含むはてなブックマーク#

2090522

「QSPI」再び。 QSPIを使うためには、同一番地に連続書き込みをする必要があります。 ところが、SilentCを使うと書き込みだけを行ってくれないようで限界があるらしいことがわかりました。 でも、SilentCの手軽さは、捨てがたい。 と考えあぐねていたある日、メモリ空間に書き込みを行うのは、何もプログラム、つまりCPUに限らないことに気がつきました。

QSPIのRAMをDMAで初期化する

MCF52233には、DMAが備わっています。 そして、DMAには、あるメモリ空間の内容を特定のアドレスに連続して書き込む機能が備わっています。 そうだ、プログラム(CPU)で書き込めないなら、DMAで書き込んじゃえ。

int  *pqspar =0x4010006c;
int  *qmr    =0x40000340;
int  *qdlyr  =0x40000344;
int  *qwr    =0x40000348;
int  *qir    =0x4000034c;
int  *qar    =0x40000350;
int  *qdr    =0x40000354;
main(){
  int v;
  init();
  #stop 0
  v=0
  for(;;){
    if(Getc(0)=='q')break;
    SystemSleep();
    send((v<<1)&0x1FFE);
    v+=80;
    if (v>2480) v=0;
  }
}
init(){
  int *command = MemoryAlloc(4);
  *pqspar=0x0051; // PQSPAR[3,2,0]=01 (QSPI)
  *qmr   =0x801E; // See table
  *qdlyr =0x0404; // See table
  *qir   =0x000F; // clear all flags
  command[0] = 0x4E00; // Send 16-bit
  writeQDR(0x0020,command,1); // Put a command
  *qwr   =0x1000; // See table
  MemoryFree(command);
}
send(int value){
  int *data = MemoryAlloc(4);
  data[0] = value;
  writeQDR(0x0000,data,1); // Put a TX data
  *qdlyr|=0x8000; // SPE=1
  while (!(*qir&0x0001));// wait SPI completed
  *qir   =0x000F; // clear all flags
  MemoryFree(data);
}
writeQDR(int addr, char *buf, int len){
  char *pacr4 =0x40000028;
  long *sar1  =0x40000110;
  long *dar1  =0x40000114;
  long *bcr1  =0x40000118;
  long *dcr1  =0x4000011c;
  *pacr4= 0x04; // Enable QSPI access
  *bcr1 = 0x01000000; // clear DONE
  *qar  = addr; // RAM address
  *sar1 = buf; // source
  *dar1 = qdr; // destination
  *bcr1 = len*2; // byte count
  *dcr1 = 0x00650000; // Simple word transfer
}

QSPIのRAMにデータを書き込むための関数 writeQDR を作りました。 書き込み先アドレスと書き込むデータのアドレスと長さを指定します。 これで、コマンド部も送信データ部も書き込みが可能です。

GPIOのレジスタにDMAで書き込みを行ったときにわかったように、レジスタへのユーザ・モードでの書き込みアクセスを許可しなくてはなりません。 QSPIのレジスタへの許可を与えるのは、PACR4レジスタのACCESS_CTRL0というフィールドです。 GPIOのときと同じようにユーザ・モードでQSPIのレジスタに読み書きが出来るように設定します。

QSPIが受け取ったデータを取り出す関数は作成していませんので、各自、お考えください。

QSPIから周期的にデータを送り出す

QSPIは、キューの内容を繰り返し送り出す機能を持っています。 もし、キューの送出データに正弦波データを入れてあれば、D/Aコンバータの出力からは正弦波が出力されるはずです。 この場合のデータ更新の周期は、データ転送間隔で決まります。 そのため、データ転送間隔、たとえばボーレートを制御すると、音程も変えられるはずです。 しかも、音を出す動作にソフトウェアの関与は不要です。

int  *pqspar =0x4010006c;
int  *qmr    =0x40000340;
int  *qdlyr  =0x40000344;
int  *qwr    =0x40000348;
int  *qir    =0x4000034c;
int  *qar    =0x40000350;
int  *qdr    =0x40000354;
main(){
  int v;
  init();
  #stop 0
  v=255;
  for(;;){
    if(Getc(0)=='q')break;
    Sleep(3);
    *qmr=0x8000+v;
    v--;
    if (v<60) v=255;
  }
}
init(){
  int *command = MemoryAlloc(34);
  int *data = MemoryAlloc(34);
  *pqspar=0x0051; // PQSPAR[3,2,0]=01 (QSPI)
  *qmr   =0x80FF; // See table
  *qdlyr =0x0404; // See table
  *qir   =0x000F; // clear all flags
  for (int i=0;i<16;i++){
    command[i] = 0x4E00; // Send 16-bit
  }
  writeQDR(0x0020,command,16); // Put a command
  *qwr   =0x5F00; // 16-word transfer
  data[ 0]=data[ 8]=1000*2; // sin0
  data[ 1]=data[ 7]=1383*2; // sin22.5
  data[ 2]=data[ 6]=1707*2; // sin45
  data[ 3]=data[ 5]=1924*2; // sin67.5
  data[ 4]         =2000*2; // sin90
  data[ 9]=data[15]= 617*2; // sin112.5
  data[10]=data[14]= 293*2; // sin135
  data[11]=data[13]=  76*2; // sin157.5
  data[12]         =   0*2; // sin180
  writeQDR(0x0000,data,16); // Put a TX data
  *qdlyr|=0x8000; // SPE=1
  MemoryFree(data);
  MemoryFree(command);
}
send(int value){
}
writeQDR(int addr, char *buf, int len){
  char *pacr4 =0x40000028;
  long *sar1  =0x40000110;
  long *dar1  =0x40000114;
  long *bcr1  =0x40000118;
  long *dcr1  =0x4000011c;
  *pacr4= 0x04; // Enable QSPI access
  *bcr1 = 0x01000000; // clear DONE
  *qar  = addr; // RAM address
  *sar1 = buf; // source
  *dar1 = qdr; // destination
  *bcr1 = len*2; // byte count
  *dcr1 = 0x00650000; // Simple word transfer
}

ソフトウェアが全く関与しないのも面白くないので、メインプログラムでQSPIのボーレートを変更して音階を変化させるようにしてみました。 回路は、LEDピカピカと同じものを使っています。 トランジスタのエミッタにクリスタル・イヤホンを当てて出力をとりだします。 D/Aコンバータで曲がりなりにも正弦波を作っているので、矩形波にくらべると柔らかな音が出てきました。 音階の精度は、QSPIの転送間隔時間によっているので、少しビブラートがかかったように聞こえます。 正式には、割り込みを使って、QSPIにトリガをかけた方がよいのでしょうね。

参考物件

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

楽天市場連動記念、無関係(でもないか)広告


SilentCが使っていないモジュールを探せ(8) [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

モジュール探訪の旅。 今日は、Queued Serial Peripheral Interface (QSPI) を走ります。

QSPIは、いわゆるSPIなのですが、ちょっと違います。 キュー・バッファを持っていて、自動的にデータを送受信してくれます。 まずは、レジスタ・マップから

40000340  01 04 00 00 04 04 00 00  ........
40000348  00 00 00 00 00 00 00 00  ........
40000350  00 00 00 00 00 00 00 00  ........

32ビット境界に16ビットのレジスタが埋め込まれています。 何のために? いずれのレジスタも初期設定のままです。 ただし、EzPORTで他のMCF52233のFlashROMをプログラムするためにこのモジュールが使用されることが分かっています。

QSPIは、どこがSPIと違うのか

手持ちの1990年版MC68332ユーザーズ・マニュアルにQSPIというモジュールが登場しています。 今回、QSPIの仕様を詳しく読みましたが、MC68332の仕様とも違っているようです。 MC68332のQSPIの方が、使いやすそうなんだけどな。

まず、第一の特徴は、QSPIは、マスタ・モードでのみ使用できます。 仕様書の最初のほうに書いてありますが、マスタ・スレーブ切り替えレジスタ・ビット MSTR が存在しているにも関わらず、スレーブ・モードでは、使用できません。 これは、衝撃的な事実でした。 ちなみに、MC68332のQSPIは、スレーブ・モードでも使用できるようです。

次の特徴です。 通信の内容を「QSPI RAM」と呼ばれるキュー・バッファに入れておいて一括で処理することが出来ます。 80バイトの「QSPI RAM」は、送信すべきデータ、受信したデータ、チップセレクトの出し方やビット数などの通信方式を指定するコマンド、の三つの領域に分けられています。 ここまで見るとすごいのですが、メモリマップには、80バイトの領域が見当たりません。 はて?どうやってアクセスするんだろう。

ここで、第二の衝撃です。 ColdFireのアドレス空間は、32ビット・アドレス・バスを採用しているので、当然、4Gバイトの広大な空間になっているはずです。 ところが、「QSPI RAM」へのアクセスは、いわゆるバンク方式になっていて、アドレスを指定するQARレジスタと、「QSPI RAM」の中の特定の16ビットのデータが読み書きできるQDRレジスタという構成になっています。 HC08やRS08だったら、限られたアドレス空間を有効に使うために、こういう方式もあるでしょうが、いやしくも、32ビットMCUでこんな事をしているとは、思いもよりませんでした。 ちなみに、MC68332の場合、「QSPI RAM」へは直接アクセスが可能です。 いや、そうあるべきでしょう。

「QSPI RAM」がColdFireのアドレス空間に配置されていないことから、DMAでのアクセスも事実上不可能です。 可能だったとしても、DMAを3チャネル使用することになるので、もったいなくて。 そもそも、QSPIからDMAに転送要求を出す機能も無いので、大量のデータを自動的に送り出すことも出来ないでしょう。 割り込みなどを使用するにしても、必ず、ソフトウェアの介在が必要なので、SilentCとは、最悪の相性だと思います。 このモジュール、苦情は出なかったのかなあ。

QSPIで、LEDピカピカのハードウェア

max5353.png 2090522

QSPIを使って、毎度おなじみの「LEDピカピカ」をさせます。 このモジュールもI2Cと同様に通信相手になるデバイスが必要です。 昔、サンプルでもらったMAXIM社MAX5353というD/Aコンバータをつないでみました。 このD/Aコンバータは、DIP8に収まった12bit分解能のD/Aコンバータです。 出力には、FB端子付きのOPアンプが使われているので、色々と実験が出来ます。 惜しむらくは、DO端子が無いので、数珠つなぎにはできないところでしょうか。 このD/Aコンバータと2SC1815を使って、データシートに回路図がある可変電流源を構成して、LEDを光らせます。

エミッタには、100Ωの電流検出抵抗を接続します。 これで、FB端子が0.0Vから1.0Vの範囲で出力の電流が0mAから10mAまで可変するはずです。 D/AコンバータのREF端子には、二本の4.7kΩ抵抗でVDD=3.3Vの半分の電圧(1.65V)を与えました。 D/Aコンバータの設定値としては、0から2480までの値を与えればよい計算になります。

この回路図には、パスコンが描かれていませんので、適宜入れてください。

QSPIが、動かない

レジスタの設定は、以下のようにしました。

QSPIモジュールのレジスタ設定
レジスタ備考
QMR [MSTR]1 こんな設定ビットがあるのですが、このモジュールは、マスタ専用です。
QMR [DOHIE]0

SPIを使用しないときにデータ出力を高インピーダンスにするかどうかを指定します。

複数のスレーブ・デバイスが同一のバスに接続される場合、チップ・セレクトで指定されないデバイスは、データ出力を高インピーダンス状態にして、明け渡すのが礼儀です。 ところが、このモジュールはマスタ専用なので、データ出力を高インピーダンスにする理由がありません。 よって、ここでは、常にデータ出力を行う設定にします。

QMR [BITS]0000 (16bit) MAX5353が受け付けるデータ形式は、16ビットです。
QMR [CPOL]0 (Low if inactive) SPIが動作していないときのクロックの位相を指定します。 MAX5353が期待しているのは、非動作時にLowになるクロックです。
QMR [CPHA]0 (capture-change) データを受け取るクロックエッジとデータを変化させるクロックエッジを指定します。 MAX5353では、クロックの前縁でデータを受け取り、後縁でデータを変化させます。
QMR [BAUD]0x1E (30) クロックの周波数を指定します。 MAX5353の最小クロック周期は、100n秒となっています。 ここでは、無理に高速なクロックを使う必要も無いので、1MHzのクロックを使用します。
QDLYR [SPE]0 (disable) → 1 (enable) 通信を開始するときには、このビットをセットします。 レジスタを初期設定する際には、クリアしておき、通信を開始するタイミングでセットします。
QDLYR [QCD]0x04 (67µsec) チップセレクトがアサートされてから最初のクロックエッジが発生するまでの時間をバス・クロック周期単位で設定します。 MAX5353の仕様書では、最小40n秒となっていますので、3以上の値であればよいはずです。 ここでは、初期状態のままの値を使用します。
QDLYR [DTL]0x04 (67µsec) 複数のSPI通信を連続して行うようにコマンドを組んだときに、二つのSPI通信の間の遅延時間をバス・クロック周期単位で設定します。 今回は、複数のコマンドを駆使するような使い方はしないので、初期状態のままの値を使用します。
QWR [HALT]0 (disable) このビットをセットすると、実行中の通信が中断されます。
QWR [WREN]0 (disable) コマンドの実行が終了したときに、再度、コマンドを最初から実行するかどうかを指定します。 今回は、単発での通信を考えているので、コマンド実行終了後は停止させます。
QWR [WRTO]0 Wrap機能は使わないので、0のままにしておきます。
QWR [CSIV]1 (active low) MAX5353のCS入力は、 active low 信号です。
QWR [ENDQP]0000 通信に使用する最後のワードのポインタを示します。 このアプリケーションでは、1ワードだけ送信したいので、終点は始点と同じ"0"とします。
QWR [NEWQP]0000 通信に使用する最初のワードのポインタを示します。 キュー・バッファの"0"番目から通信に使用します。
QIR [WCEFB, ABRTB, ABRTL]0 (disable) これらは、エラー処理をイネーブルするために使用されます。 とりあえずは、エラー処理無しで進めます。
QIR [WCEFE, ABRTE, SPIFE]0 (disable) エラーおよび通信終了時に割り込みをかけるかどうかを指定します。 このアプリケーションでは、割り込みは使いません。
QCR0 [CONT]0 (single) 1ワードごとにCS出力をネゲートするかどうかを指定します。 今回は、1ワードだけの通信を行いますので、どちらでもかまわないはずです。
QCR0 [BITSE]1 (specify) MAX5353との通信は、16ビット単位で行います。 そのため、このビットをセットして、データ長をQMR [BITS]レジスタで示す必要があります。
QCR0 [DT]0 (default) 複数のSPI通信を連続して行うようにコマンドを組んだときに、二つのSPI通信の間の遅延時間をバス・クロック周期単位で設定します。 デフォルトのままの値を使います。
QCR0 [DSCK]0 (default) チップセレクトがアサートされてから最初のクロックエッジが発生するまでの時間を指定します。 デフォルトのままの値を使います。
QCR0 [QSPI_CS]1110 チップ・セレクト出力のパターンを示します。 80ピンのMCF52233には、複数のチップ・セレクト出力端子があるわけではないので、CS0だけアサートします。
  int  *pqspar =0x4010006c;
  int  *qmr    =0x40000340;
  int  *qdlyr  =0x40000344;
  int  *qwr    =0x40000348;
  int  *qir    =0x4000034c;
  int  *qar    =0x40000350;
  int  *qdr    =0x40000354;
main(){
  int v;
  init();
  #stop 0
  v=0
  for(;;){
    if(Getc(0)=='q')break;
    SystemSleep();
    send((v<<1)&0x1FFE);
    v+=80;
    if (v>2480) v=0;
  }
}
init(){
  *pqspar=0x0051; // PQSPAR[3,2,0]=01 (QSPI)
  *qmr   =0x801E; // See table
  *qdlyr =0x0404; // See table
  *qir   =0x000F; // clear all flags
  *qar   =0x0020; // select command RAM
  *qdr   =0x4E00; // COMMAND code
  *qwr   =0x1000; // See table
}
send(int value){
  *qar   =0x0000; // select transmit RAM
  *qdr   =value; // DATA code
  *qdlyr|=0x8000; // SPE=1
  while (!(*qir&0x0001));// wait SPI completed
  *qir   =0x000F; // clear all flags
}

と、綿密にプログラムを書いたつもりなのに、まったく動く気配がありません。 設定を色々と変えてみましたが、進展しません。

ソフトウェアSPIを試してみる

全く、動かないので、ハードウェアに問題があるのかソフトウェアに問題があるのか判断がつきません。 そこで、ハードウェアはそのままで、GPIOをソフトウェアで制御するプログラムを作成しました。

  char *portqs =0x4010000c;
  char *ddrqs  =0x40100024;
  int  *pqspar =0x4010006c;
main(){
  int v;
  init();
  #stop 0
  v=0
  for(;;){
    if(Getc(0)=='q')break;
    SystemSleep();
    send((v<<1)&0x1FFE);
    v+=80;
    if (v>2480) v=0;
  }
}
init(){
  *pqspar =0x0000; // PQSPAR[6:0]=00 (GPIO)
  *portqs =0x08; // CS=1;SCK=0;DO=0
  *ddrqs  =0x0D; // PQS[3,2,0] as output
}
send(int value){
  int i;
  for (i=16;--i>=0;) {
    *portqs = (value&0x8000)?0x01:0x00; // CS=0;SCK=0;D(i)
    value=value*2;
    *portqs|= 0x04; // SCK=1
  }
  *portqs = 0x00; // CS=0;SCK=0;DO=0
  *portqs = 0x08; // CS=1;SCK=0;DO=0
}

すると、このプログラムなら、動作することがわかりました。 問題は、QSPIの使い方にありそうです。

キュー・バッファにアクセスできない?

様々なステータスを表示させてデバッグをしました。 その結果、ある事に気がつきました。 キュー・バッファのうち、データ部分は、QDRレジスタを通じて読み書きが出来ることになっているのですが、どうやっても前に書き込んだはずのデータが出てきません。

ここから先は想像でしかありません。 おそらく、QDRレジスタへの書き込みアクセスの過程で、SilentCの内部処理によりQDRレジスタへの読み出しアクセスが発生したものと思われます。 QDRレジスタに読み出し・書き込みアクセスが発生すると、QARレジスタがインクリメントされる仕掛けになっています。 そのため、目的とするキュー・バッファにコマンドと送信データが書き込まれなかったものと思われます。 現に*qdrへの書き込みの後、QARレジスタを読み出すと、+2インクリメントされています。

逆転の発想

もし、SilentCが余計な読み出しをしているのなら、QCR0に書き込みたかったコマンドは、QCR1に書き込まれているはずです。 また、送信すべきデータも0x0001に書き込まれるはずです。 ならば、QWRレジスタに、「1番地のバッファを使え」と指示したらどうなるでしょうか。

  int  *pqspar =0x4010006c;
  int  *qmr    =0x40000340;
  int  *qdlyr  =0x40000344;
  int  *qwr    =0x40000348;
  int  *qir    =0x4000034c;
  int  *qar    =0x40000350;
  int  *qdr    =0x40000354;
main(){
  int v;
  init();
  #stop 0
  v=0
  for(;;){
    if(Getc(0)=='q')break;
    SystemSleep();
    send((v<<1)&0x1FFE);
    v+=80;
    if (v>2480) v=0;
  }
}
init(){
  *pqspar=0x0051; // PQSPAR[3,2,0]=01 (QSPI)
  *qmr   =0x801E; // See table
  *qdlyr =0x0404; // See table
  *qir   =0x000F; // clear all flags
  *qar   =0x0020; // select command RAM
  *qdr   =0x4E00; // COMMAND code
  *qwr   =0x1101; // See table
}
send(int value){
  *qar   =0x0000; // select transmit RAM
  *qdr   =value; // DATA code
  *qdlyr|=0x8000; // SPE=1
  while (!(*qir&0x0001));// wait SPI completed
  *qir   =0x000F; // clear all flags
}

変更したのは、QWRレジスタの設定部分だけです。 なんと、LEDが点滅を始めてしまいました。 しかし、この方法は、再現性も無いし、必ず正しく動く保証も無いので、決してお勧めはできません。

本日の結論

というわけで、本日の結論は、

SilentCQSPIの組み合わせは、最悪であって、まともに使えるものではない。

でした。 深追いした割りに得るものは少ないな。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌


I2CでLEDピカピカ完全版 [ColdFire V2]このエントリーを含むはてなブックマーク#

2073158

前回作成した、「I2Cを使ってLEDピカピカ」は、フラグの扱いに不備があることが判明したので、完全版を再度作成しました。

I2Cには、フラグがたくさん

I2Cモジュールには、I2SRというレジスタがあって、フラグビットがぎっしりと詰まっています。 通信の終了を知るには、どのビットを参照したら良いのか、はっきり把握していなかったので、まとめてみました。

I2Cモジュールのフラグレジスタ
ビット名備考
7 ICF 通信中は、クリアされます。 9番目のクロックの立下りでセットされます。
6 IAAS I2ADRレジスタに設定したものと同じアドレスがバス上で検出されたことを示します。 このフラグがセットされたら、マイコンはスレーブ・デバイスとして振舞わなくてはなりません。
5 IBB I2Cバスが使用されている場合にセットされます。 礼儀として、バスが空いていることを確認して通信を始めたほうがよいでしょう。
4 IAL アービトレーション・ロストが発生したことを示します。 アービトレーション・ロストについては、後述します。
2 SRW マイコンがスレーブ・デバイスとして振舞うときに、マスタ・デバイスが指定してきたデータの送受信方向を示します。 スレーブ・デバイスは、マスター・デバイスに完全に従って動作します。
1 IIF I2Cモジュールが割り込みを発生させるためのフラグです。 I2CRレジスタのIIEビットがセットされていたら、割り込みが発生します。 割り込みの発生は、I2Cモジュールが割り込み・サービス・ルーチンに対して何らかの処理を要求している事を示します。
0 RXAK あるデバイスがデータを送信すると、それを受信したデバイスから9ビット目のデータが返却されます。 これをアクノリッジと呼んでいます。 一般に、アクノリッジ・ビットがACK(0)だった場合には、続くデータを送り出しますが、NACK(1)だった場合には、通信が中断されます。 このビットは、ACKが返却された場合にはクリアされ、NACKが返却された場合にはセットされます。 RXAKというレジスタ名から受ける印象と動作が異なっていますので、はまらないように注意が必要です。

で、どのフラグを見て通信の終わりを判断すべきかですが、リファレンス・マニュアルを良く読むと書いてありました。

27.4.3 Post-Transfer Software Response

Software can service the I2C I/O in the main program by monitoring the IIF bit if the interrupt function is disabled. Polling should monitor IIF rather than ICF, because that operation is different when arbitration is lost.

割り込み機能をディセーブルしたうえで、メイン・プログラムでIIFビットを監視することによって、ソフトウェアでI2C入出力を使うことが出来ます。 フラグの監視(ポーリング)には、ICFビットよりもIIFフラグを使用すべきです。 なぜなら、アービトレーション・ロストが発生したときの動作が異なるからです。

アービトレーション・ロストは、ちょっと難しい概念です。

I2Cバスというのは、マルチ・マスタを原則としているプロトコルなので、複数のマスタ・デバイスがバスをドライブしようとします。 そのため、複数のマスタデバイスが同時にバスをドライブする事態も十分に考えられます。 複数のマスタデバイスが同時に通信を始めた場合、マスタ・デバイスはそれを検出して「優先順位のより高いマスタ・デバイス」にバスを譲らなくてはなりません。 このような状態をアービトレーション・ロストと呼びます。

他にも、ノイズなどによって、マスタ・デバイスが出したつもりの無いデータがバス上に出たりした場合にもアービトレーション・ロストが発生します。 このような場合、I2Cモジュールは、即座に通信を中断し、バスを開放し、IALフラグで事態を示します。 同時に、IIFフラグがセットされ、割り込みサービスルーチンに処理を促すのですが、ICFフラグがセットされるとは記述されていません。

つまり、ソフトウェアで対処が必要な場合には、IIFフラグがセットされるので、状況に応じて対処せよということのようです。 今回作り直したプログラムでは、IIFフラグを検出し、RXAKフラグを確認して通信が完了したかどうかを確認しています。

レジスタの設定(再掲)

プログラムで使用したI2Cモジュールの主な設定は、以下の通りです。

I2Cのレジスタ設定
レジスタ備考
I2FDR0x38 (640) I2C通信で使用されるクロックを指定します。 バスクロック(60MHz)を640分周した94kHzの転送レートが使用されます。
I2CR [IEN]1 (enable) I2Cモジュールをイネーブルします。
I2CR [IIEN]0 (disable) I2C割り込みを使用するときにはセットします。 もちろん、割り込みは使いません。
I2CR [MSTA]0 → 1 → 0 マスタ・モードを意味するビットとされているのですが、実際には、マスタとして通信の開始・終了時のSTART状態とSTOP状態を作り出すために使用されます。
I2CR [MTX]1 (TX) I2C通信の方向を示します。 今回は、MCF52233からデータを送り込む方向の通信だけを行っているので、セットしたまま使います。
I2CR [TXAK]0 (ACK) データの受信確認のための符号としてACK/NAKのどちらを返すかを指定します。 前述のように送信方向のみに使用しているので、どちらでもかまいません。
I2CR [RSTA]0 (no Sr) I2C通信の二つのパケットをつなぐリピーテッド・スタートという特殊なSTART状態を作り出すかどうかを指定します。 今回は、リピーテッド・スタートは使用しないので、クリアしておきます。

プログラム

プログラムは、光が流れるように作り直しました。

  int  *pqspar =0x4010006c;
  char *i2adr  =0x40000300;
  char *i2fdr  =0x40000304;
  char *i2cr   =0x40000308;
  char *i2sr   =0x4000030c;
  char *i2dr   =0x40000310;
main(){
  int  i;
  *pqspar =0x00A0; // PQSPAR[3:2]=10 (i2c)
  *i2fdr = 0x38; // (x640)
  *i2cr  = 0x90; // See table
  write(0x03,0x00); // config as OUT
  #stop 0
  i=0;
  for(;;){
    if(Getc(0)=='q')break;
    Sleep(25);
    write(0x01,0xEF<<i);
    i=(i+1)&3;
  }
}
write(char com, char data){
  while ((*i2sr)&0x20) SystemSleep(); // Wait for BUS idle
  *i2sr  = 0x12; // Clear flags
  *i2cr |= 0x20; // START
  do {
    *i2dr  = 0x40; // WRITE for 0100000
    while (!(*i2sr&0x02)); // Wait for IIF
    if (*i2sr&0x01) break; // Quit if no ACK
    *i2sr  = 0x12; // Clear flags
    *i2dr  = com; // command
    while (!(*i2sr&0x02)); // Wait for IIF
    if (*i2sr&0x01) break; // Quit if no ACK
    *i2sr  = 0x12; // Clear flags
    *i2dr  = data; // data
    while (!(*i2sr&0x02)); // Wait for IIF
  } while (0);
  *i2cr &= 0xDF; // STOP
}
このプログラムは、まだバグがあります。 詳細は追記を参照の事。

前回のプログラムでは、全てのフラグのポーリングでSystemSleep()関数を使用していましたが、今回は通信開始前のバスの監視部分だけに使いました。 これは、3バイト分の通信が計算上0.4m秒程度という短時間で完了してしまうと判断したためです。 でも、この時間はインタプリタのオーバ・ヘッドがどのくらいになるか、考慮していません。 この部分だけ、機械語で作り直すかな。

try-finallyは、通信を中断した時に確実にSTOP状態を送り出すためには、ぜひとも欲しい構文なのですが、Cには、try-finally構文はありません。 関数を入れ子にして、returnで脱出する方法もありますが、SilentCでは、むやみにネストを深くしたくはありません。 そこで、do-whileを使って、try-finally構文を実現してみました。 こういう使い方も、アリだよね。

2008-09-18 追記

完全版だったはずなのに、「機械語に移植したら動かない」との知らせを受けました。 調べたところ、IIFビットをクリアする方法がHCS08のI2Cモジュールとは異なっているらしいことがわかりました。

  • HCS08のI2Cモジュールは、1を書いてクリア。
  • MCF52233のI2Cモジュールは、0を書いてクリア。

すると、Interface誌に掲載されているプログラムもフラグをクリアすることなく動作しているという事ですか。 ステータス・レジスタの値を表示させて確認してみなくては。

2008-09-21 追記

フラグの扱い方に問題があることを記事「MCF52233付録基板 - I2CでLEDピカピカ(真)完全版」で確認しました。 また、修正版のプログラムも作成しました。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

モジュールの使い方は、HCS08もColdFireも変わりないんですよね。 と、思っていたら大間違いでした。

HCS08 Unleashed: Designer's Guide to the Hcs08 Microcontrollers

HCS08 Unleashed: Designer's Guide to the Hcs08 Microcontrollers

  • 作者: Fabio Pereira
  • 出版社/メーカー: Booksurge Llc
  • 発売日: 2007/11/13
  • メディア: ペーパーバック

SilentCが使っていないモジュールを探せ(7) [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

今日は、I2Cを走ります。

やっぱり、レジスタ・ダンプ・リスト

最初は、レジスタ領域のダンプ・リストをとります。

40000300  00 00 00 00 00 00 00 00  ........
40000308  00 00 00 00 81 00 00 00  ........
40000310  00 00 00 00 00 00 00 00  ........
40000318  00 00 00 00 00 00 00 00  ........

デフォルトのままです。 まあ、外部にI2Cデバイスをつないでいないのだから、当然ではあります。

I/O拡張チップでLEDピカピカ

2073160

I2Cインターフェースだけでは、遊べませんので、NXP社のI/O拡張チップPCA9554を接続して、LEDをピカピカさせました。


2073158

SDA/SCL端子は、PQS[3:2]を使用しています。 これは、単にブレッドボード上の配線の制約によるものです。


プログラムで使用したI2Cモジュールの主な設定は、以下の通りです。

I2Cのレジスタ設定
レジスタ備考
I2FDR0x38 (640) I2C通信で使用されるクロックを指定します。 バスクロック(60MHz)を640分周した94kHzの転送レートが使用されます。
I2CR [IEN]1 (enable) I2Cモジュールをイネーブルします。
I2CR [IIEN]0 (disable) I2C割り込みを使用するときにはセットします。 もちろん、割り込みは使いません。
I2CR [MSTA]0 → 1 → 0 マスタ・モードを意味するビットとされているのですが、実際には、マスタとして通信の開始・終了時のSTART状態とSTOP状態を作り出すために使用されます。
I2CR [MTX]1 (TX) I2C通信の方向を示します。 今回は、MCF52233からデータを送り込む方向の通信だけを行っているので、セットしたまま使います。
I2CR [TXAK]0 (ACK) データの受信確認のための符号としてACK/NAKのどちらを返すかを指定します。 前述のように送信方向のみに使用しているので、どちらでもかまいません。
I2CR [RSTA]0 (no Sr) I2C通信の二つのパケットをつなぐリピーテッド・スタートという特殊なSTART状態を作り出すかどうかを指定します。 今回は、リピーテッド・スタートは使用しないので、クリアしておきます。

単純な点滅をするプログラムは、こんなふうになりました。 ステータスレジスタのフラグをしつこくチェックしていますが、どこまで必要なのかは検証していません。

  int  *pqspar =0x4010006c;
  char *i2adr  =0x40000300;
  char *i2fdr  =0x40000304;
  char *i2cr   =0x40000308;
  char *i2sr   =0x4000030c;
  char *i2dr   =0x40000310;
main(){
  *pqspar =0x00A0; // PQSPAR[3:2]=10 (i2c)
  *i2fdr = 0x38; // (x640)
  *i2cr  = 0x90; // See table
  write(0x03,0x00); // config as OUT
  #stop 0
  for(;;){
    if(Getc(0)=='q')break;
    Sleep(25);
    write(0x01,0x00); // Turn ON
    Sleep(25);
    write(0x01,0xFF); // Turn OFF
  }
}
write(char com, char data){
  while ((*i2sr)&0x20) SystemSleep();
  *i2cr |= 0x20; // START
  *i2dr  = 0x40; // WRITE for 0100000
  while (!(*i2sr)&0x20) SystemSleep();
  *i2dr  = com; // command
  while (!(*i2sr)&0x20) SystemSleep();
  *i2dr  = data; // data
  while (!(*i2sr)&0x20) SystemSleep();
  *i2cr &= 0xDF; // STOP
}
このプログラムは、フラグの操作に問題があります。 それでも動作しているのは、SilentCの処理速度が遅いためだと思われます。 フラグ操作を修正した記事を MCF52233付録基板 - I2CでLEDピカピカ完全版に追加しました。

I2Cの書き込みを行う関数をくくりだしたことから、レジスタへのポインタ変数は大域変数になっています。 メイン・ループでは、Sleep(25)で250m秒ごとにI2C通信を行い、LEDを点滅させています。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

モジュールの使い方は、HCS08もColdFireも変わりないんですよね。

HCS08 Unleashed: Designer's Guide to the Hcs08 Microcontrollers

HCS08 Unleashed: Designer's Guide to the Hcs08 Microcontrollers

  • 作者: Fabio Pereira
  • 出版社/メーカー: Booksurge Llc
  • 発売日: 2007/11/13
  • メディア: ペーパーバック

ネットワーク電飾 (3) [ColdFire V2]このエントリーを含むはてなブックマーク#

2068980

今回は、さらにDMAタイマを組み合わせて、自動的にLEDを点灯させます。

DTIM0の設定

DTIM0には、0.2秒ごとにDMA1に転送要求を発行させます。 設定項目は、SilentCが使っていないモジュールを探せ(5)の設定から少しだけ変更されています。

DTIM0のレジスタ設定
レジスタ設定値備考
DTMR0 [PS]0x3B (60-1) プリスケーラの分周比を60に設定します。 カウンタを動作させるクロック周期は、1µ秒になります。
DTMR0 [CE]00 (disable) 入力キャプチャは使いません。
DTMR0 [OM]1 (toggle) 今回は、DTOUT0端子は使わないのですが、トグルに設定しておきます。 何かあったら、デバッグに使えるでしょう。
DTMR0 [ORRI]1 (enable) DMA転送要求を発行します。 このビットは、DMA要求と割り込み要求の双方の許可ビットになっています。 どちらの要求を発行するかは、DTXMR0[DMAEN]ビットで設定します。
DTMR0 [FRR]1 (enable) リファレンス・レジスタの周期でイベントを発生させます。
DTMR0 [CLK]01 (BUSCLK×1) プリスケーラをバスクロック (60MHz) で駆動します。
DTMR0 [RST]0 (reset) → 1 (run) リセットした状態でタイマを設定し、最後にこのビットをセットしてタイマを起動します。
DTXMR0 [DMAEN]1 (DMA) イベントが発生したら、DMA転送要求を発行します。
DMXMR0 [HALTED]0 (run when HALT) STOP状態の時にタイマを停止するかどうかを決定します。 このアプリケーションでは、STOPは使いませんが、STOP状態でも止まらないように設定しておきます。
DMXMR0 [MODE16]0 (32-bit mode) タイマカウンタを16-bitレジスタとして使いたい場合にセットします。 このアプリケーションでは、32-bitカウンタとして使います。
DTRR0199999 (200000-1) プリスケーラ出力の200000周期ごとにイベントを発生させます。 プリスケーラ出力は、1µ秒としたので、0.2秒ごとにDMA転送が発生します。

設定方針が決まったので、固定点灯パターンを使ったプログラムを作成します。

main() {
  char *pubpar  = 0x40100072;
  char *portub  = 0x40100012;
  char *ddrub   = 0x4010002a;
  long *dmareqc = 0x40000014;
  long *sar1    = 0x40000110;
  long *dar1    = 0x40000114;
  long *bcr1    = 0x40000118;
  long *dcr1    = 0x4000011c;
  char *gpacr0  = 0x40000030;
  int  *dtmr0   = 0x40000400;
  char *dtxmr0  = 0x40000402;
  long *dtrr0   = 0x40000404;
  long *dtcn0   = 0x4000040c;
  
  char *alloc;
  char *pat;

  alloc = MemoryAlloc(32);
  pat = (alloc+15)&0xFFFFFFF0;
  pat[0]=1;pat[1]=3;pat[2]=11;pat[3]=15;
  pat[4]=14;pat[5]=12;pat[6]=4;pat[7]=0;
  pat[8]=pat[10]=pat[12]=15;
  pat[9]=pat[11]=pat[13]=pat[14]=pat[15]=0;

  *gpacr0  = 0x04; // ACCESS_CTRL=0100
  *pubpar  = 0x00; // PUBPAR[3:0]=0000
  *portub  = 0x00; // PORTUB[3:0]=0000
  *ddrub   = 0x0F; // DDRUB[3:0]=1111
  *bcr1    = 0x01000000; // clear DONE
  *sar1    = pat;    // source
  *dar1    = portub; // destination
  *bcr1    = 0x00FFFFF0; // byte count
  *dcr1    = 0x62521080; // See table
  *dmareqc = 0x00000040; // DMAC1=DTIM0
  *dtmr0   = 0x0000; // software reset
  *dtmr0   = 0x3B3A; // See table
  *dtxmr0  = 0x80;   // enable DMA
  *dtrr0   = 199999; // period 200000
  *dtmr0  |= 0x0001; // RST=1

  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    SystemSleep();
  }
  *dtmr0 &= 0xFFFE; // RST=0
  *bcr1   = 0x00000000; // reset byte count
  MemoryFree(alloc);
}

プログラムを実行させると、固定パターンでの点滅が始まります。 プログラムを見るとわかるように、レジスタ設定が終わったら、CPUは休眠状態に入っていて、あとはDMAタイマとDMAが自動的にLEDを点滅させています。

ネットワーク電飾

それでは、最後に点灯パターンをネットワークから送り込めるようにUDP受信機能を取り入れます。 今までのプログラムの組み合わせなので、簡単ですね。 PC側のプログラムは、ネットワーク電飾 (1)で作成したJavaのプログラムがそのまま使えます。

main() {
  char *pubpar  = 0x40100072;
  char *portub  = 0x40100012;
  char *ddrub   = 0x4010002a;
  long *dmareqc = 0x40000014;
  long *sar1    = 0x40000110;
  long *dar1    = 0x40000114;
  long *bcr1    = 0x40000118;
  long *dcr1    = 0x4000011c;
  char *gpacr0  = 0x40000030;
  int  *dtmr0   = 0x40000400;
  char *dtxmr0  = 0x40000402;
  long *dtrr0   = 0x40000404;
  long *dtcn0   = 0x4000040c;
  
  char *alloc;
  char *pat;
  char socket;
  int  errcode;
  char *buf;

  alloc = MemoryAlloc(32);
  pat = (alloc+15)&0xFFFFFFF0;

  *gpacr0  = 0x04; // ACCESS_CTRL=0100
  *pubpar  = 0x00; // PUBPAR[3:0]=0000
  *portub  = 0x00; // PORTUB[3:0]=0000
  *ddrub   = 0x0F; // DDRUB[3:0]=1111
  *bcr1    = 0x01000000; // clear DONE
  *sar1    = pat;    // source
  *dar1    = portub; // destination
  *bcr1    = 0x00FFFFF0; // byte count
  *dcr1    = 0x62521080; // See table
  *dmareqc = 0x00000040; // DMAC1=DTIM0
  *dtmr0   = 0x0000; // software reset
  *dtmr0   = 0x3B3A; // See table
  *dtxmr0  = 0x80;   // enable DMA
  *dtrr0   = 199999; // period 200000
  *dtmr0  |= 0x0001; // RST=1

  socket = CreateSocket(0); // UDP socket
  Bind(socket, 30049, 1); // Bind to port
  MemClear(pat,16);

  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    errcode = RecvFrom(socket, 100); // 1sec
    if (errcode >= 16){
      buf=GetReceiveBuffer(socket,1);
      BufCopy(pat,buf,16);
      MemoryFree(buf);
    }
    SystemSleep();
  }
  CloseSocket(socket);
  *dtmr0 &= 0xFFFE; // RST=0
  *bcr1   = 0x00000000; // reset byte count
  MemoryFree(alloc);
}

メインループでは、UDPの受信作業しか行っていません。

また、パケット待ちタイムアウト時間は、1秒と長めにしました。 このタイムアウト時間は、プログラムを停止させるための'q'キーの検出時間になるので、1秒にしましたが、キー入力で停止させる必要が無いのであれば、もっともっと長いタイムアウト時間にしてもかまいません。 プログラムがパケット待ちをしている間、DMAタイマとDMAが、せっせとLEDを点滅させてくれます。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

ネットワーク電飾 (2) [ColdFire V2]このエントリーを含むはてなブックマーク#

2068980

ネットワーク電飾 (1)では、ソフトウェアのポーリングでLEDの点灯タイミングを作っていました。 今回は、ハードウェアの力をちょっと借りて、LEDを点灯させます。

DMAでLEDを点灯させる

先のプログラムでは、ループの中に通信を受け持つ部分とLEDを点灯させる部分が混在していました。 どうも、すっきりしないので、これら二つの部分を分けたいと思います。

とはいえ、SilentCでマルチタスクなプログラムが書けるのかどうか、疑問です。 そこで、LEDを点灯させる部分をハードウェアに任せます。 具体的には、DMAを使うことが出来ます。

DMAモジュール自体は、時間稼ぎをしてくれるモジュールでは無いので、そのままではLEDをピカピカさせることは出来ません。 しかし、DMAには、「DMA Timer」という強い味方がいます。 そこで、「DMA Timer」でタイミングを作って、DMAを叩き、LEDを点灯させます。

DMA1の設定

DMA1は、DTIM0によって周期的に要求を受け、要求のたびに1バイトのデータをPORTUBに書き込みます。 転送元アドレスは、modulo-16で動作させて、繰り返しパターンが出てくるようにします。 また、膨大な総転送バイト数を入れることによって、半永久的にDMA転送要求が続きます。

DMA1のレジスタ設定
レジスタ設定値備考
DMAREQC [DMAC1]0100 (DTIM0) DMAタイマ#0によって、転送要求が発生します。
SAR1pat データの転送元になるSilentCが確保したRAMのアドレスを指定します。 この領域は、SilentCが動作しているときに限り使用可能な領域なので、プログラムの実行が停止するときには、DMAの動作を止めなくてはなりません。 まあ、読み出し側なので、副作用は無いとは思いますが。
DAR10x4010_0012 (PORTUB) 転送先にPORTUBを指定します。
BCR1 [DSR]0xFF DONEフラグをクリアします。 DONEフラグは、割り込みの確認をするのでなければ、特にクリアする必要も無いのですが、隣のBCR1[BCR]と同時に書き込む必要があることから、ついでにクリアしてしまいます。
BCR1 [BCR]0xFFFFF0 半永久的に転送を続けるために、総転送バイト数には、大きな値を指定します。
DCR1 [INT]0 (disable) 割り込みは、使いません。
DCR1 [EEXT]1 (enable) DMAタイマによって、DMAの転送要求が発行されるので、外部要求は許可します。
DCR1 [CS]1 (cycle-steal) このアプリケーションでは、DMA要求1回当たり1バイトの転送を行います。 このような使い方をサイクルスチールモードと呼び、CSビットをセットして設定します。 転送一回あたりのデータ長は、DCR1[SSIZE]とDCR1[DSIZE]で指定されるデータ長の大きいほうです。
DCR1 [AA]0 (disable) MCF52233は、32ビット幅のバスに8,16,32ビットのデータを流すため、必要に応じてデータを分断したり結合したりして転送を行います。 この作業を自動的に行うのが、auto-alignという機能です。 このアプリケーションでは、1バイト単位の転送しか行わないので、どちらでもかまいませんが、ディセーブルにしておきます。
DCR1 [BWC]001 (16KB) DMAがデータを転送するときに使用する最大ブロック長を指定します。 これによって、DMAがバスを占有しすぎないようにすることができます。 このアプリケーションでは、設定が正常であれば1バイトしか転送が行われないのですが、保険として最小の16Kバイトを指定しておきます。
DCR1 [SINC]1 (increment) 転送元アドレスレジスタ(SAR1)を自動的にインクリメントするかどうかを決定します。 このアプリケーションでは、転送元にあるパターンを次々に変更しながらPORTUBに送り込むので、インクリメントの機能が必要です。
DCR1 [SSIZE]01 (Byte) 転送元から読み出されるデータ長は、1バイトです。
DCR1 [DINC]0 (not-increment) 転送先アドレスレジスタ(DAR1)は、常にPORTUBを指していてほしいので、インクリメントはさせません。
DCR1 [DSIZE]01 (Byte) 転送先に書き込まれるデータ長は、1バイトです。
DCR1 [START]0 (inactive) このビットは、プログラムから転送の開始を要求するときにセットします。 このアプリケーションでは、DMAタイマによって要求を発行するので、プログラムではセットしません。
DCR1 [SMOD]0001 (16Bytes)

このアプリケーションでは、転送元にある16バイトのデータを順にPORTUBに送り出します。 そのため、転送元のアドレスは、16バイトインクリメントするごとに元に戻さなくてはなりません。

このレジスタビットを0000以外の値にすると、アドレスレジスタの下位ビット(たとえば、16バイト単位であれば、下位4ビット)だけをインクリメントしてくれるので、事実上16バイトごとに元に戻すという機能が実現できます。 ただし、下位ビットだけをインクリメントしていることから、転送元のバッファのアドレスは、下位ビットが0になるアドレスから始まる必要があります。

SilentCでは、MemoryAllocで確保された領域が何処から始まるか指定できないので、大き目の領域を確保し、その中から下位ビットが0になるアドレスから始まる16バイトの領域を使用します。

DCR1 [DMOD]0000 (disable) 転送先アドレスは、インクリメントしないので、この設定には意味がありません。
DCR1 [D_REQ]1 (enable) 総転送バイトカウンタ(BCR1)が0になったらDCR1[EEXT]ビットをクリアします。 まあ、バイトカウンタが0になるのは1000時間後のことです。
DCR1 [LNKCC]00 (no link) 複数のDMAチャネルを連続して動作させるための設定です。 このアプリケーションでは、DMA1しか使用しません。 LCH1とLCH2も00にしておきます。

DMA1の設定を考えたので、ここでいったん、ソフトウェアでDMAに要求を出すプログラムを作成して、DMAの動作を確認しておきます。

main() {
  char *pubpar  = 0x40100072;
  char *portub  = 0x40100012;
  char *ddrub   = 0x4010002a;
  char *dmareqc = 0x40000014;
  long *sar1    = 0x40000110;
  long *dar1    = 0x40000114;
  long *bcr1    = 0x40000118;
  long *dcr1    = 0x4000011c;
  
  char *alloc;
  char *pat;

  alloc = MemoryAlloc(32);
  pat = (alloc+15)&0xFFFFFFF0;
  pat[0]=1;pat[1]=3;pat[2]=11;pat[3]=15;
  pat[4]=14;pat[5]=12;pat[6]=4;pat[7]=0;
  pat[8]=pat[10]=pat[12]=15;
  pat[9]=pat[11]=pat[13]=pat[14]=pat[15]=0;

  *pubpar = 0x00; // PUBPAR[3:0]=0000
  *portub = 0x00; // PORTUB[3:0]=0000
  *ddrub  = 0x0F; // DDRUB[3:0]=1111
  *bcr1   = 0x01000000; // clear DONE
  *sar1   = pat;    // source
  *dar1   = portub; // destination
  *bcr1   = 0x00FFFFF0; // byte count
  *dcr1   = 0x62521080; // See table
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    PrHex(*dar1);PrStr(" ");
    PrHex(*sar1);PrStr(" ");
    PrHex(*bcr1);PrStr("\r\n");
    *dcr1  |= 0x00010000; // START=1
    Sleep(20);
  }
  *bcr1   = 0x00000000; // reset byte count
  MemoryFree(alloc);
}

BCR1レジスタは、DSRとBCRを一度に設定しようと考えていたのですが、先にフラグをクリアしないとBCRが設定できない仕様になってるようで、うまく動作しませんでした。

OK
run
40100012 20003770 00fffff0
40100012 20003771 11ffffef
40100012 20003772 11ffffee
40100012 20003773 11ffffed
40100012 20003774 11ffffec
40100012 20003775 11ffffeb
40100012 20003776 11ffffea
40100012 20003777 11ffffe9
40100012 20003778 11ffffe8

表示されているのは、DAR1, SAR1, BCR1の各レジスタの値です。 この表示で、DMA転送の様子が分かるのですが、PORTUBには反応がありません。 BCR1[DSR]のBED (Bus Error on Destination) がセットされている、つまりエラーが発生しているようです。

freescale.comFAQを探したところ、DMAでのアクセスを制限するレジスタの存在を教えてもらいました。 一連のレジスタは、0x4000_0020から配置されています。

40000020  03 00 00 08 00 00 00 00  ........
40000028  00 00 00 00 00 00 00 00  ........
40000030  00 00 00 00 00 00 00 00  ........

いずれもリセット後のデフォルト値です。 設定は、以下のとおりです。

  1. DMAのアクセスは、Userモードで行われます。
  2. GPIOへのアクセスは、Supervisorモードのみ許可されます。

そのため、DMAによるGPIOモジュールへのアクセスは、禁止されます。 これを解決するためには、UserモードでもGPIOへのRead/Writeアクセスを許可します。 GPACR0[ACCESS_CTRL]を0100 (U/S両モードでRead/Write可能) に変更します。

main() {
  char *pubpar  = 0x40100072;
  char *portub  = 0x40100012;
  char *ddrub   = 0x4010002a;
  char *dmareqc = 0x40000014;
  long *sar1    = 0x40000110;
  long *dar1    = 0x40000114;
  long *bcr1    = 0x40000118;
  long *dcr1    = 0x4000011c;
  char *gpacr0  = 0x40000030;
  
  char *alloc;
  char *pat;

  alloc = MemoryAlloc(32);
  pat = (alloc+15)&0xFFFFFFF0;
  pat[0]=1;pat[1]=3;pat[2]=11;pat[3]=15;
  pat[4]=14;pat[5]=12;pat[6]=4;pat[7]=0;
  pat[8]=pat[10]=pat[12]=15;
  pat[9]=pat[11]=pat[13]=pat[14]=pat[15]=0;

  *gpacr0 = 0x04; // ACCESS_CTRL=0100
  *pubpar = 0x00; // PUBPAR[3:0]=0000
  *portub = 0x00; // PORTUB[3:0]=0000
  *ddrub  = 0x0F; // DDRUB[3:0]=1111
  *bcr1   = 0x01000000; // clear DONE
  *sar1   = pat;    // source
  *dar1   = portub; // destination
  *bcr1   = 0x00FFFFF0; // byte count
  *dcr1   = 0x62521080; // See table
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    PrHex(*dar1);PrStr(" ");
    PrHex(*sar1);PrStr(" ");
    PrHex(*bcr1);PrStr("\r\n");
    *dcr1  |= 0x00010000; // START=1
    Sleep(20);
  }
  *bcr1   = 0x00000000; // reset byte count
  MemoryFree(alloc);
}

実行すると、DSR1[BSY]ビットだけがセットされ、LEDの点滅も始まり、正常に転送が行われていることが確認できます。

OK
run
40100012 20003ad0 00fffff0
40100012 20003ad1 02ffffef
40100012 20003ad2 02ffffee
40100012 20003ad3 02ffffed
40100012 20003ad4 02ffffec
40100012 20003ad5 02ffffeb
40100012 20003ad6 02ffffea
40100012 20003ad7 02ffffe9
40100012 20003ad8 02ffffe8
40100012 20003ad9 02ffffe7
40100012 20003ada 02ffffe6

またまた、つづく

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

ネットワーク電飾 (1) [ColdFire V2]このエントリーを含むはてなブックマーク#

2068980

ネットワークから点灯パターンを送り込んでプログラムする「ネットワーク電飾」を作りました。

固定パターンによる電飾

LEDは、PUB[3:0]に配置しました。 LEDは、電流制限抵抗付きに改造してありますので、誤解の無いように。 最初のモデルは、固定パターンに従って、LEDを点灯させるプログラムです。

main() {
  char *pubpar = 0x40100072;
  char *portub = 0x40100012;
  char *ddrub  = 0x4010002a;
  char *pat;
  int  index;
  *pubpar = 0x00; // PUBPAR[3:0]=0000
  *portub = 0x00; // PORTUB[3:0]=0000
  *ddrub  = 0x0F; // DDRUB[3:0]=1111
  pat = MemoryAlloc(16);
  pat[0]=1;pat[1]=3;pat[2]=11;pat[3]=15;
  pat[4]=14;pat[5]=12;pat[6]=4;pat[7]=0;
  pat[8]=pat[10]=pat[12]=15;
  pat[9]=pat[11]=pat[13]=pat[14]=pat[15]=0;
  index=0;
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    Sleep(20);
    *portub = pat[index];
    index = (index+1)&0x0F;
  }
  MemoryFree(pat);
}

SilentCは、定数配列が使えないので、ヒープに領域を確保して、1バイトずつ値を代入していきます。 ダサいけど、確実な方法です。 パターンは、16ステップで一周するようにしてあります。

ネットワークから点灯パターンを送り込む

このプログラムのループでは、200m秒間の時間待ちがあります。 この部分にタイムアウト時間200m秒でUDP通信の到着を待つ構文を入れて、到着したデータでパターンを更新するようにすると、ネットワーク対応電飾になります。

main() {
  char *pubpar = 0x40100072;
  char *portub = 0x40100012;
  char *ddrub  = 0x4010002a;
  char *pat;
  char socket;
  int  errcode;
  char *buf;
  int  index;
  *pubpar = 0x00; // PUBPAR[3:0]=0000
  *portub = 0x00; // PORTUB[3:0]=0000
  *ddrub  = 0x0F; // DDRUB[3:0]=1111
  socket = CreateSocket(0); // UDP socket
  Bind(socket, 30049, 1); // Bind to port
  pat=MemoryAlloc(16);
  MemClear(pat,16);
  index=0;
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    errcode = RecvFrom(socket, 20);
    if (errcode >= 16){
      buf=GetReceiveBuffer(socket,1);
      BufCopy(pat,buf,16);
      MemoryFree(buf);
    }
    *portub=pat[index];
    index=(index+1)&0x0F;
  }
  MemoryFree(pat);
  CloseSocket(socket);
}

ホストPC側のプログラムは、Javaで書きました。

package org.noritan.networkillumination;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.ArrayList;

/**
 * This UdpSender class is used to send a UDP packet to a specified
 * host.
 * 
 * @author noritan
 */
public class UdpSender {

    /**
     * A String type of hostname constant. This
     * specifies the host where to send a UDP packet.
     */
    private String hostname;

    /**
     * Construct a UdpSender object.
     * 
     * @param hostname
     *            a host name to send a packet.
     */
    public UdpSender(String hostname) {
        this.hostname = hostname;
    }

    /**
     * Send a UDP packet including a data specified by the argument.
     * 
     * @param buf
     *            a UDP payload to be sent.
     */
    public void send(byte[] buf) {
        DatagramPacket packet = new DatagramPacket(buf, buf.length);
        try {
            DatagramSocket socket;
            socket = new DatagramSocket();
            try {
                SocketAddress address = new InetSocketAddress(hostname, 30049);
                socket.connect(address);
                socket.send(packet);
            } catch (IOException e) {
                // Failed to connect/send
                e.printStackTrace();
            } finally {
                socket.close();
            }
        } catch (SocketException e) {
            // Failed to open a socket
            e.printStackTrace();
        }
    }

    /**
     * Send a UDP packet including data specified as String.
     * 
     * @param data
     *            a UDP payload to be sent.
     */
    private void send(String data) {
        // Convert a String into ArrayList
        ArrayList work = new ArrayList();
        for (int i = 0; i < data.length(); i++) {
            try {
                work.add(Byte.parseByte(data.substring(i, i + 1), 16));
            } catch (NumberFormatException e) {
                // Do nothing
            }
        }
        // Make a Byte[]
        Byte[] buf = new Byte[work.size()];
        // Then send it
        send(work.toArray(buf));
    }

    /**
     * Send a UDP packet including data specified as Byte.
     * 
     * @param data
     *            a UDP payload to be sent.
     */
    private void send(Byte[] data) {
        // Convert a Byte[] into byte[]
        byte[] buf = new byte[data.length];
        for (int i = 0; i < data.length; i++) {
            buf[i] = data[i];
        }
        // Then send it
        send(buf);
    }

    /**
     * Main method as an stand alone application.
     * 
     * @param args
     *            HEX data to be sent as an UDP packet.
     */
    public static void main(String[] args) {
        UdpSender sender = new UdpSender("192.168.1.10");
        try {
            for (int i = 0; i < args.length; i++) {
                sender.send(args[i]);
                Thread.sleep(10000); // 10sec
            }
        } catch (InterruptedException e) {
            // Exit Program
        }
    }
}

引数で与えられたパターンを10秒ごとに送り込みます。

java org.noritan.networkillumination.UdpSender 1284821284821000 13BFEC40F0F0F000 1010202080804040

続く

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentCが使っていないモジュールを探せ(6) [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

モジュール探訪の旅。 今日は、Direct Memory Access (DMA) を走ります。

最初はレジスタ・ダンプ

DMAの仕事は、アドレス空間のA地点からB地点までデータをコピーすることです。 もちろん、A地点とB地点の場所とデータの大きさを指定する必要があります。 それだけなら、十分にソフトウェアで記述できますが、DMAの凄いところは、プログラムの実行とは無関係に勝手にコピーを行ってくれるところです。 また、他のモジュールからの刺激によって自動的にコピーを開始する機能もあります。

さあ、さっそくレジスタ領域のダンプ・リストから始めます。

40000010  80 01 00 00 00 00 00 00  ........

40000100  20 00 31 d0 20 00 14 60   .1. ..`
40000108  01 00 00 00 00 08 00 00  ........
40000110  00 00 00 00 00 00 00 00  ........
40000118  00 00 00 00 00 00 00 00  ........
40000120  00 00 00 00 00 00 00 00  ........
40000128  00 00 00 00 00 00 00 00  ........
40000130  00 00 00 00 00 00 00 00  ........
40000138  00 00 00 00 00 00 00 00  ........

リファレンスマニュアルによると、DMAに関連するレジスタは、二箇所に分かれています。 DMAREQC (0x4000_0014) には、データのコピーを始めるきっかけを指定します。 その他のレジスタがDMAの四つのチャネルを制御するために使用されます。

おや、どうもDMA0を使った形跡がありますね。 SAR0 (0x4000_0100) と DAR0 (0x4000_0104) は、転送元・転送先アドレスを指定します。 DSR0[DONE] (0x4000_0108) がセットされているので、使用後の状態だと思われます。 そのため、SAR0 と DAR0 は「転送に使われた領域の次のアドレス」を示しているものと思われます。 いずれもRAMの中を示しています。 また、 DCR0[DINC] だけがセットされていることから、「0x200031d0番地の32bitの内容を0x20001460付近にばら撒いた」跡だろうと想像します。

まだ、これらのアドレスに何が入っている(いた)のかは、未解明です。 今のところ、判明しているのは、SAR0の方には、毎度同じ値が入っているのに、DAR0には、起動するたびに違う値が入っていることです。 さらに、このアドレス近辺のダンプリストをとると、16進部分とキャラクタ表示部分が異なっていることから、インタプリタの実行中にも使用されているのだろうと推測します。 何だろうね?

使用例は、延期

SilentCで実現可能な、DMAモジュールを使った例を考えましたが、良いアイディアが出てきません。 配列間のメモリ転送なら、できますが、ワクワクしないな。 そんな訳で、使用例は、延期とします。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

端子一覧表 [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

リファレンスマニュアルを見ると、80ピンパッケージから出てこない端子がたくさんあることがわかります。 くやしいうるさいので、出ている端子だけを一覧表にしました。


MCF52233付録基板 - 端子一覧表
Pin GroupGPIOPrimarySecondaryTertiaryDrive StrengthWired ORPull-up/ -downPin#
ADCPAN[7]AN7--Low--51
ADCPAN[6]AN6--Low--50
ADCPAN[5]AN5--Low--46
ADCPAN[4]AN4--Low--45
ADCPAN[3]AN3--Low--52
ADCPAN[2]AN2--Low--53
ADCPAN[1]AN1--Low--54
ADCPAN[0]AN0--Low--55
ADCPAS[3]SYNCA-FEC_MDIOPDSR[39]--18
ADCPAS[2]SYNCB-FEC_MDCPDSR[39]--15
I2CPAS[1]SDA-URXD2PDSR[0]-Pull-up62
I2CPAS[0]SCL-UTXD2PDSR[0]-Pull-up61
DEBUG-ALLPST--High--5
LEDPLD[4]COLLED--PDSR[36]PWOR[12]-31
LEDPLD[3]DUPLED--PDSR[35]PWOR[11]-36
LEDPLD[2]SPDLED--PDSR[34]PWOR[10]-42
LEDPLD[1]LNKLED--PDSR[33]PWOR[9]-43
LEDPLD[0]ACTLED--PDSR[32]PWOR[8]-44
PHY-PHY_RXN-----38
PHY-PHY_RXP-----39
PHY-PHY_TXN-----40
PHY-PHY_TXP-----41
InterruptPGP[3]IRQ11--PDSR[43]-Pull-up35
InterruptPNQ[7]IRQ7--Low-Pull-up34
InterruptPNQ[4]IRQ4--Low-Pull-up27
InterruptPNQ[1]IRQ1SYNCAPWM1Low-Pull-up30
JTAG-JTAG_EN----Pull-down8
JTAG-TCLK/ PSTCLKCLKOUT-High-Pull-up63
JTAG-TDI/ DSI----Pull-up2
JTAG-TDO/ DSO--High--3
JTAG-TMS/ BKPT----Pull-up64
JTAG-TRST/ DSCLK----Pull-up4
MODE-RCON/ EZPCS----Pull-up1
QSPIPQS[3]QSPI_CS0SDAUCTS1PDSR[4]PWOR[7]Pull-up26
QSPIPQS[2]QSPI_CLKSCLURTS1PDSR[3]PWOR[6]Pull-up25
QSPIPQS[1]QSPI_DIN-URXD1PDSR[2]PWOR[4]-23
QSPIPQS[0]QSPI_DOUT-UTXD1PDSR[1]PWOR[5]-24
Reset-RSTI----Pull-up28
Reset-RSTO--High--29
GPTPTA[3]GPT3FEC_TXD[3]PWM7PDSR[23]-Pull-up59
GPTPTA[2]GPT2FEC_TXD[2]PWM5PDSR[22]-Pull-up58
GPTPTA[1]GPT1FEC_TXD[1]PWM3PDSR[21]-Pull-up59
GPTPTA[0]GPT0FEC_TXERPWM1PDSR[20]-Pull-up60
DTIMPTC[3]DTIN3DTOUT3PWM6PDSR[19]--10
DTIMPTC[2]DTIN2DTOUT2PWM4PDSR[18]--9
DTIMPTC[1]DTIN1DTOUT1PWM2PDSR[17]--7
DTIMPTC[0]DTIN0DTOUT0PWM0PDSR[16]--6
UART0PUA[3]UCTS0-FEC_RXCLKPDSR[11]--14
UART0PUA[2]URTS0-FEC_RXDVPDSR[10]--13
UART0PUA[1]URXD0-FEC_RXD[0]PDSR[9]PWOR[0]-19
UART0PUA[0]UTXD0-FEC_CRSPDSR[8]PWOR[1]-20
UART1PUB[3]UCTS1SYNCAURXD2PDSR[15]--12
UART1PUB[2]URTS1SYNCBUTXD2PDSR[14]--11
UART1PUB[1]URXD1-FEC_TXD[0]PDSR[13]PWOR[2]-21
UART1PUB[0]UTXD1-FEC_COLPDSR[12]PWOR[3]-22

間違いなどありましたら、お知らせください。 EXCEL版を公開する予定でしたが、google版を作成してもらったので、こちらをお使いください。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentCが使っていないモジュールを探せ(5) [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

モジュール探訪の旅。 今日は、DMA Timer (DTIM) を走ります。

最初は、レジスタ・ダンプ

DTIMは、一般のタイマとしても使用することが出来るのですが、名前に「DMA」と入っている通り、「DMA」と密接につながってもいます。 まずは、レジスタ領域のダンプ・リストをとります。

40000400  00 00 00 00 ff ff ff ff  ........
40000408  00 00 00 00 00 00 00 00  ........

40000440  00 00 00 00 ff ff ff ff  ........
40000448  00 00 00 00 00 00 00 00  ........

40000480  00 00 00 00 ff ff ff ff  ........
40000488  00 00 00 00 00 00 00 00  ........

400004c0  00 00 00 00 ff ff ff ff  ........
400004c8  00 00 00 00 00 00 00 00  ........

このようにいずれもデフォルトのままで、使われた形跡がありません。 まるごと、使えそうですね。

しつこいけれど、LEDピカピカ

これもタイマに違いは無いので、LEDピカピカに使ってみます。 このタイマのすごいところは、8-bit高級プリスケーラに32-bitタイマ・カウンタを装備しているため、超長時間(81時間以上)のタイマに使えるというところです。 32-bitだと使いにくい用途もあるのか、16-bitモードの設定も装備されています。 これだけの機能があれば、苦も無く正確に250m秒の時間が作り出せます。

DTMR0レジスタ設定の詳細
フィールド備考
PS0xF9 (250-1) プリスケーラの分周比を250に設定します。
CE00 (disable) DTINn端子でのエッジの捕まえ方を指定します。 ここでは、入力キャプチャの機能は使いません。
OM1 (toggle) DTOUTnの出力の仕方を指定します。 パルス状に出されてもLEDで確認できないので、トグルさせることにします。
ORRI0 (disable) 今は、割り込みもDMAも使いません。
FRR1 (enable) このフィールドをセットすると、カウンタがリファレンスに一致したらゼロにリセットされます。 この機能を使うと、「リファレンスの値」+1の周期でイベントが発生します。
CLK01 (BUSCLKx1) プリスケーラをバスクロックで駆動します。
RST0 (reset) → 1 (run) リセットした状態でタイマを設定し、最後にこのフィールドをセットしてタイマを起動します。

出力は、DTOUT0(PTC[0])をトグルして出します。 トグルの周期は、

1 × 250 × 60000 ÷ 60MHz = 250msec

となります。 32-bitのカウンタは、必要ありませんでしたね。 さて、コーディングだ。

main() {
  char *ptcpar = 0x4010006f;
  int  *dtmr0  = 0x40000400;
  long *dtrr0  = 0x40000404;
  long *dtcn0  = 0x4000040c;
  *ptcpar  = 0x02;   // PTCPAR0=10
  *dtmr0   = 0x0000; // software reset
  *dtmr0   = 0xF92A; // See table
  *dtrr0   = 59999;  // period 60000
  *dtmr0  |= 0x0001; // RST=1
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    PrNum(*dtcn0);PrStr(" ");Sleep(10);
  }
}

レジスタの設定が終われば、他にすることも無いのですが、タイマカウンタの値を表示しています。 このプログラムの場合も、'q'キーで実行を終了してもLEDはピカピカを続けます。

7行目にDTMR0レジスタに"0x0000"を書き込んでいます。 これは、DTMR0[RST]ビットをクリアすることが他の設定ビットを変更するための条件になっているため、確実にDTMR0[RST]をクリアするための仕掛けです。 この行を入れないと、二度目のプログラムの実行でタイマの設定ができませんでした。 この動きは、ちょっと、バグっぽい気もします。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

ネットワークD/Aコンバータ [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

PWM出力の応用として、D/Aコンバータを作ります。

UDPで受信したデータに従って、PWMを制御すると、ネットワーク越しにアナログ出力の電圧を可変することが出来るはずです。

UDPで受信したデータを表示する

まずは、UDPで受信したデータを表示するプログラムを作成します。

main(){
  char socket;
  int  errcode;
  char *buf;
  char value;
  socket = CreateSocket(0); // UDP socket
  Bind(socket, 30049, 1); // Bind to port
  #stop 0
  for(;;){
    if(Getc(0)=='q')break;
    errcode = RecvFrom(socket, 2);
    if(errcode >= 1){
      buf=GetReceiveBuffer(socket,1);
      value=buf[0];
      MemoryFree(buf);
      PrHexByte(value);PrStr(" ");
    }
  }
  CloseSocket(socket);
}

UDPで受信したデータを16進数の数値として延々と表示し続けます。 ポート番号には、おなじみの30049番を使っていますが、UDP電圧計とは異なり、今回はColdFireがこのポート番号でパケットを待ちます。

データを送り込むためのPC側のプログラムは、Javaで書きました。 サイン波を作っているつもりです。

package org.noritan.networkdac;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;

public class UdpSine implements Runnable {
	byte theta = 0;

	public UdpSine() {
		try {
			for (;;) {
				Thread.sleep(10);
				new Thread(this).run();
			}
		} catch (InterruptedException e) {
			System.exit(0);
		}
	}

	@Override
	public void run() {
		byte value = (byte)(Math.sin(theta * Math.PI / 6) * 100 + 128);
		byte[] buf = new byte[2];
		DatagramPacket packet = new DatagramPacket(buf, buf.length);
		try {
			DatagramSocket socket;
			socket = new DatagramSocket();
			try {
				SocketAddress address = new InetSocketAddress("192.168.1.10", 30049);
				socket.connect(address);
				buf[0] = value;
				socket.send(packet);
			} catch (IOException e) {
				// Failed to connect/send
				e.printStackTrace();
			} finally {
				socket.close();
			}
		} catch (SocketException e) {
			// Failed to open a socket
			e.printStackTrace();
		}
		if (++theta >= 12) theta = 0;
	}

	public static void main(String[] args) {
		new UdpSine();
	}
}

ほとんどコメントがありませんが、ご勘弁を。 10m秒ごとにサイン波に相当するデータを送信しています。 一周期が12データで構成されているので、期待される出力の周波数は、8.3Hzです。 双方のプログラムを実行すると、このような表示が現れます。

run
80 b2 d6 e4 d6 b2 80 4e 29 1c 29 4d 80 b2 d6 e4 
d6 b2 80 4e 29 1c 29 4d 80 b2 d6 e4 d6 b2 80 4e 
29 1c 29 4d 80 b2 d6 e4 d6 b2 80 4e 29 1c 29 4d 
80 b2 d6 e4 d6 b2 80 4e 29 1c 29 4d 80 b2 d6 e4 
d6 b2 80 4e 29 1c 29 4d 80 b2 d6 e4 d6 b2 80 4e 
29 1c 29 4d 80 b2 d6 e4 d6 b2 80 4e 29 1c 29 4d 

データが到着していることが確認できたので、こんどは、これをPWMのデューティーに設定してやります。

ネットワークD/Aコンバータ

データを表示していた部分をデューティー・レジスタへの代入に変更しました。 SilentCのプログラムは、送られてきた値を延々とPWMのデューティレジスタに入れるだけです。

main(){
  int  *pnqpar   = 0x40100068;
  char *pwme     = 0x401b0000;
  char *pwmclk   = 0x401b0002;
  char *pwmprclk = 0x401b0003;
  char *pwmcnt1  = 0x401b000d;
  char *pwmper1  = 0x401b0015;
  char *pwmdty1  = 0x401b001d;
  char socket;
  int  errcode;
  char *buf;
  char value;
  *pnqpar   |= 0x000C; // PNQPAR1=11 (tertiary:PWM)
  *pwmclk    = 0x00; // PCLK1=0 (clock A)
  *pwmprclk  = 0x00; // PCKA=000 (x1)
  *pwmper1   = 0xFF; // PWMPER1=255
  *pwmdty1   = 0x7F; // PWMDTY1=127 (50%)
  *pwme     |= 0x02; // PWME1=1 (enable)
  socket = CreateSocket(0); // UDP socket
  Bind(socket, 30049, 1); // Bind to port
  #stop 0
  for(;;){
    if(Getc(0)=='q')break;
    errcode = RecvFrom(socket, 2);
    if(errcode >= 1){
      buf=GetReceiveBuffer(socket,1);
      value=buf[0];
      MemoryFree(buf);
      *pwmdty1 = value;
    }
  }
  CloseSocket(socket);
}

クリスタルイヤホンで聞くと美しくない音が聞こえてきました。 さすがに、これでPCMが出来るなんて、夢にも思いませんが。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentCが使っていないモジュールを探せ(4) [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

モジュール探訪の旅。 今日は、Pulse-Width Modulation (PWM) を走ります。

最初はレジスタ・ダンプ

PWMは、レジスタに設定した値に従って、パルス波形を作ります。 こういったモジュールなら、SilentCからでも簡単に使えそうです。 まず、レジスタ領域のダンプをとりました。

401b0000  00 00 00 00 00 00 00 00  ........
401b0008  00 00 00 00 00 00 00 00  ........
401b0010  00 00 00 00 ff ff ff ff  ........
401b0018  ff ff ff ff ff ff ff ff  ........
401b0020  ff ff ff ff 04 00 00 00  ........

全く使われていないようで、デフォルトのままです。 ただ一箇所だけ、PWM7INビットがセットされているのがデフォルトから変わっています。 このビットはPWM7端子の状態をモニタするためのRead-onlyビットなので、このような値が返ってきたのでしょう。 でも、PWM7端子は、どこにも割り当てていないんだけどな?

おなじみ、LEDピカピカ

SilentCでは、使っていないらしいので、PWMモジュールを使って恒例のLEDピカピカをプログラムしてみます。 PWM出力は、いくつかの端子から出すことが出来るのですが、先人に倣ってPNQ[1]端子から出力してみます。

PWMを8-bitで使った場合の最大周期を計算すると

128 × 256 × 2 × 255 ÷ 60MHz = 279msec

だいたい3.6Hzの点滅になります。 ちょっと早いかもしれないけれど、この設定でやってみます。

main(){
  int  *pnqpar   = 0x40100068;
  char *pwme     = 0x401b0000;
  char *pwmclk   = 0x401b0002;
  char *pwmprclk = 0x401b0003;
  char *pwmscla  = 0x401b0008;
  char *pwmcnt1  = 0x401b000d;
  char *pwmper1  = 0x401b0015;
  char *pwmdty1  = 0x401b001d;
  *pnqpar   |= 0x000C; // PNQPAR1=11 (tertiary:PWM)
  *pwmclk    = 0x02; // PCLK1=1 (clock SA)
  *pwmprclk  = 0x07; // PCKA=111 (x128)
  *pwmscla   = 0x00; // SCALEA=0 (x256)
  *pwmper1   = 0xFF; // PWMPER1=255
  *pwmdty1   = 0x7F; // PWMDTY1=127 (50%)
  *pwme     |= 0x02; // PWME1=1 (enable)
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    PrNum(*pwmcnt1);PrStr(" ");Sleep(10);
  }
}

コンソールには、PWMカウンタの値を表示しています。 コレぐらいなら、簡単だね。 このPWMモジュールは、ソフトウェアと無関係に動作するので、'q'キーでプログラムの実行が終了した後もLEDの点滅を続けます。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentCが使っていないモジュールを探せ(3) [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

モジュール探訪の旅。 今日は、Programmable Interrupt Timer (PIT) を走ります。

まずは、レジスタ領域ダンプ

PITモジュールの役目は、周期的に割り込みをかけることです。 割り込みをかける以外に特別な機能は持っていません。 また、MCF52233には、同じ構成のモジュールが2セット入っています。 タスク切り替え割り込みの本命ですね。

さっそく、レジスタ領域のダンプ・リストを取りましたが、いずれのPITも途中で止まってしまいます。

40150000  05 1b 24 9f 04 0f
40160000  00 00 ff ff ff ff

どうも、仕様上の正規のレジスタ以外の部分では、バス・エラーか何かが発生するようです。 ダンプ・プログラムに終了アドレスが指定できるように修正を加えてもいいのですが、6バイト分のデータは確認できたので、このまま調べます。

レジスタの考察

アドレス 0x40150000 からのPIT0モジュールが怪しいですね。 レジスタのビット・フィールドごとに解析しました。

フィールド考察
PRE0101 プリスケーラの分周比を指定します。 32分周が行われているので、基準クロックの周期は

2 × 32 ÷ 60MHz = 1.0667usec

と計算されます。
DOZE0 マイコンがdoze(うたた寝)モードに入ったときにタイマを止めるかどうかを決めます。 この設定では、dozeモードでもタイマは止まりません。
DBG0 マイコンがdebugモードに入ったときにタイマを止めるかどうかを決めます。 この設定では、debugモードでもタイマは止まりません。
OVW1 モジュラス・レジスタに値を書き込んだときに同時にカウンタも書き換えるかどうかを決めます。 デフォルトでは、次のアンダーフローまで待って書き換えるようになっているのですが、ここでは同時にカウンタを書き換える設定になっています。
PIE1 PIFフラグがセットされたときに割り込みを発生するかを決めます。 この設定では、割り込みが発生します。
PIF0 タイマのカウンタが0に達したときにセットされるフラグです。
RLD1 タイマのカウンタが0に達したときにモジュロ・レジスタの値をカウンタに書き込むかどうかを決めます。 この設定では、カウント0でモジュロ・レジスタの値が書き込まれるので、「モジュロ・レジスタの値+1」の周期で割り込みが発生します。
EN1 タイマ・モジュールをイネーブル・ディセーブルします。 この設定では、タイマはイネーブルされています。
PMR00x249F 割り込みの周期を決めます。 0x249Fは、10進数では、9375です。
PCNTR00x040F カウンタの値です。 読み出すたびに値が変わっているはずなので、値に意味はありません。

以上の調査から、割り込みの周期は、以下のように計算されます。

2 × 32 × (9375 + 1) ÷ 60MHz = 10.001msec

これが、SilentCで使用されている10msecの基準時間になっていると見て間違いないでしょう。

また、PIT1は、全く使われていないらしいことがわかります。 でも、割り込みしか能がないんじゃ、SilentCからは使いにくいな。

割り込みハンドラなどについては、後の課題とします。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentCのCreateTimer関数の使い方 [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

SilentCには、システムコールと呼ばれる関数が定義されています。 その中にタイマ機能というものがあり、CreateTimer()関数で起動するとされています。 今日は、このタイマ機能の使い方を探ります。

Interface誌の記述に従う

インターフェース誌には、CreateTimerの定義が一覧表にちょっとだけ書いてあります。 funcには、文字列で関数を指定するのですね。

main(){
  char th;
  th=CreateTimer(0,100,"Main::pr");
  PrNum(th);PrStr("\r\n");
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    SystemSleep();
  }
}
pr(){PrStr("Hello World\r\n");}

タイマの作用で、1秒ごとに"Hello World"が表示されると思っていたのですが、ハングしてしまいました。 このプログラムは、CreateTimerさえ入れなければ動作します。 明らかに、CreateTimerの使い方に問題があるものと思われます。

OS-1プログラミングマニュアルに従う

サイレントシステム社のダウンロードページには、別のマイコンに実装されたSilentCOS-1プログラミングマニュアルがあります。 このマニュアルには、システムコールの紹介とそのプログラム例が記述されています。 あれ? funcには、一般的なステートメントが書けることになっていますね。 さっそく、MCF52233付録基板用に変更して走らせて見ました。

main(){
  char th;
  th=CreateTimer(0,100,"pr();");
  PrNum(th);PrStr("\r\n");
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    SystemSleep();
  }
  CreateTimer(th,0,0);
}
pr(){PrStr("Hello World\r\n");}

やっぱり、これもハングしたまま、帰って来ませんでした。

OS-1ユーザーズマニュアルに従う

サイレントシステム社のダウンロードページには、もう一つ、OS-1ユーザーズマニュアルという文書があります。 この文書には、SilentMoonのシステムコールと題してCreateTimer関数の定義が書いてあります。 ありゃりゃ? 引数が多いな。 スタックサイズを指定せよ? この書式に従って、パラメータを整えて、実行してみました。

main(){
  char th;
  th=CreateTimer(3,Main::pr,100,0,256);
  PrNum(th);PrStr("\r\n");
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    SystemSleep();
  }
  CreateTimer(0,0,0,th,0);
}
pr(){PrStr("Hello World\r\n");}

実行すると、"HelloWorld"と表示されました。 しかし、これは、CreateTimerの引数として"Main::pr"を与えようとしたときに"Main::pr"のアドレスを渡す代わりに"Main::pr"を実行してしまったためでした。 その後、タイマ・ハンドルとして"3"を返してきましたが、これもCreateTimer関数の第一引数が戻ってきているに過ぎません。 従って、第一引数はタイマ・ハンドルが入るべきであり、ユーザーズマニュアルに書いてある定義ではないのだろうと思われます。

もう一度プログラミングマニュアルに戻る

再度、プログラミングマニュアルに従った記法を試します。 ただし、今度は関数を与えずにカウンタの値を監視してみます。

main(){
  char th;
  th=CreateTimer(0,100,0);
  PrNum(th);PrStr("\r\n");
  while(GetTimerCount(th)>0){
    PrChar('*');
    Sleep(1);
  }
  CreateTimer(th,0,0);
}

このプログラムを実行すると、一秒の間、100個のアスタリスクを表示してくれました。 どうやら、こういう使い方なら出来るようです。 でも、これではタイマを使う意味が無いから、コールバック関数を呼び出すようにしたいよな。

CreateTimerの正しい使い方、教えてください

こんなわけで、CreateTimerをうまく使う方法が見つかりませんでした。 出版社やサイレント・システムに問い合わせるのは違反らしいから、ここで吠えてみます。

参考文献

まだ、在庫あるそうです。

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentCが使っていないモジュールを探せ(2) [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

モジュール探訪の旅。 今日は、General Purpose Timer (GPT) を走ります。

GPTのレジスタ

GPT関連レジスタのダンプリストをとりました。 16バイトずつだと広くなりすぎるので、8バイト単位にプログラムを変更してあります。

401a0000  00 00 00 00 00 00 00 00  ........
401a0008  00 00 00 00 00 00 00 00  ........
401a0010  00 00 00 00 00 00 00 00  ........
401a0018  00 00 00 00 00 0f 00 00  ........

レジスタの値の考察

GPTPORT(0x1A_001D)以外のレジスタは、すべて"0"になっています。 GPTの動作を決めるGPTSCR1(0x1A_0006)レジスタのGPTENもクリアされているので、GPTモジュールは使用されていないものと推測します。

唯一、"0"になっていないGPTPORTレジスタは、GPTが関連付けられる端子の値を返しています。 GPT[3:0]は、プルアップ素子つきの端子なので、4本とも"1"を返してきているものと思われます。

GPTのカウンタを動かしてみる

使っていないと分かれば、しめたもの。 動かしてみましょう。 とは、言っても SilentC でむやみに割り込みをかけるわけにもいかないし、GPT[3:0]は、加速度センサの制御に割り振られているので、カウンタの値を表示させることにしました。

GPTのタイマは、システムクロックを2分周したクロックをさらにプリスケーラで分周して16ビットのカウンタに導いています。 プリスケーラの最大分周比は、128なので、16ビットのカウンタがオーバフローするまでの時間は、

2 × 128 × 65536 ÷ 60MHz = 279msec

ということで、SilentCSleep(int)関数を使って、100msecに相当する時間を作り、その間に進んだカウント数を表示させるプログラムを作成しました。

type Main
main(){
  char *gptscr1 = 0x401a0006;
  char *gptscr2 = 0x401a000d;
  int  *gptcnt  = 0x401a0004;
  long now=0,last=0,diff;
  *gptscr2 |= 0x07; // PR=111 (x128)
  *gptscr1 |= 0x80; // GPTEN=1
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    SystemSleep();last=*gptcnt;
    Sleep(10);now=*gptcnt;
    diff=now-last;
    if (diff<0) diff+=65536;
    PrNum(diff);PrStr(" - ");
  }
}

GPTの設定は、プリスケーラの分周比を決め、GPTをイネーブルしただけです。 Sleep(10);実行前後の16ビットのカウンタの値を比較し、延々と表示させます。 実行結果は、このようになりました。

OK
run
23120 - 22520 - 22552 - 22473 - 22553 - 22557 - 22470 - 22557 - 
22469 - 22558 - 22552 - 22473 - 22553 - 22557 - 22469 - 22557 - 
22554 - 22473 - 22554 - 22557 - 22469 - 22556 - 22469 - 22557 - 
22554 - 22473 - 22553 - 22557 - 22469 - 22558 - 22552 - 22473 - 
22553 - 22557 - 22470 - 22557 - 22469 - 22557 - 22553 - 22473 - 
22553 - 22557 - 22469 - 22556 - 22553 - 22473 - 22554 - 22557 - 

大体、22500前後の値が出てきました。 100msecというと、23400程度の値が出てきて欲しかったのですが、少し小さめです。 まあ、許容できる誤差ということにしておきましょう。

やっぱり、LEDピカピカ

GPT3端子は、GPIO出力に設定されてはいるのですが、使用されている形跡がありません。 これをGPTで操作させて、LEDをピカピカさせます。 先ほど計算したタイマのオーバフロー周期が279msecとちょうどいい値になっているので、GPT3のアウトプット・コンペア機能を有効にして出力を出します。

main(){
  char *ptapar  = 0x4010006e;
  char *gptios  = 0x401a0000;
  char *gptctl1 = 0x401a0009;
  char *gptscr1 = 0x401a0006;
  char *gptscr2 = 0x401a000d;
  int  *gptcnt  = 0x401a0004;
  long now=0,last=0,diff;
  *ptapar  |= 0x40; // PTAPAR3=01 (primary)
  *gptscr2 |= 0x07; // PR=111 (x128)
  *gptios  |= 0x08; // IOS3=1 (O.C.)
  *gptctl1 |= 0x40; // OM3/OL3=01 (toggle)
  *gptscr1 |= 0x80; // GPTEN=1
  #stop 0
  for(;;){
    if (Getc(0)=='q')break;
    SystemSleep();last=*gptcnt;
    Sleep(10);now=*gptcnt;
    diff=now-last;
    if (diff<0) diff+=65536;
    PrNum(diff);PrStr(" - ");
  }
}

このプログラムでは、GPTが設定された後は、プログラムの実行に関わり無くGPTがLEDを点滅させます。 そのため、'q'キーでプログラムを終了させたあとでもLEDはピカピカを続けます。 次は、音だな。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

七色仮面 [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

または、「レインボーマン」

問題

クロス・ケーブルは、どれでしょう?


Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentCが使っていないモジュールを探せ(1) [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

MCF52233には、PIT、GPT、DMA、DTIM、UART、QSPI、PWM、など様々なモジュールが入っています。 GPIOを操作してLEDの点滅が出来たように、SilentCからレジスタを操作することでこれらのモジュールを使うことが出来るようになると思います。 ところが、どのモジュールをSilentCで使用しているのか、という情報は出ていませんし、情報を請求することも出来ません。 仕方が無いから、自分で調査しますか。

調査方針

もちろん、SilentCが使っているであろうモジュールは見当がつきます。 EPHYとFECは、まず間違いなく使っているでしょう。 UART0もコンソールとして使っているはずです。

ところが、タイマのように外から見ただけでは、どれを使っていて、どれを使っていないかという事が判断できないモジュールもあります。 例えば、「10msecごとにタスクを切り替える際に使用されるのは、どのタイマなのか」ということは、倫理的な問題が残るかも知れませんが、SilentCのプログラムを読むしか知る方法ありません。

そこで、ここではもっとお手軽に、「レジスタ領域がデフォルトから変更されていたら、そのモジュールは使用されている。」という判断をすることとします。 つまり、レジスタ領域を逐一調査するということです。 前回作成した、メモリ・ダンプ・プログラムが使えます。 ちょっと(かなり)、面倒かもしれないな。

ポート機能

手始めにポートレジスタを調べました。

40100060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
40100070  00 55 00 00 0f 02 00 00 00 00 00 00 00 00 00 00  .U..............

このアドレス(IPSBAR+0x10_0060)には、"Port Pin Assignment Register"と呼ばれるレジスタが並んでいて、それぞれの端子がどのような用途で使用されているのかがわかります。

レジスタ端子名設定
PNQPAR (0x10_0068)PNG[7:1]0x00_00 PNQ[7:1] 端子 (/IRQ7 ~ /IRQ1)は、すべてGPIO(00)として設定されています。 ただし、MCF52233付録基板のLQFP80パッケージでは、PNQ[7] PNQ[4] PNQ[1] の三つの端子しか出ていません。 また、 PNQ[4] (/IRQ4) 端子は、リカバリ用のEzPORT制御で、EZPCS出力に使われるようになっています。
PANPAR (0x10_006A)PAN[7:0]0x00 デフォルトでは、PAN[7:0] 端子は、すべてGPIO(0)として設定されています。 ただし、InitAd(char)関数でADC入力としてイニシャライズすると、値が変更されます。
PASPAR (0x10_006B)PAS[3:0]0x00 デフォルトでは、PAS[7:0] 端子は、すべてGPIO(00)として設定されています。 PAS[3:2]の基本機能は、ADCの変換タイミングを決定するSYNCxという信号だそうです。 また、PAS[1:0]の基本機能は、I2Cバスです。
PQSPAR (0x10_006C)PQS[6:0]0x00_00 デフォルトでは、PQS[6:0] 端子は、すべてGPIO(00)として設定されています。 ただし、MCF52233付録基板のLQFP80パッケージでは、PQS[3:0] の四つの端子しか出ていません。 PQS[6:0]の基本機能は、QSPIです。 また、リカバリ用のEzPORT制御でも使われます。
PTAPAR (0x10_006E)PTA[3:0]0x00 デフォルトでは、PTA[3:0] 端子は、すべてGPIO(00)として設定されています。 PTA[3:0]の基本機能は、GPTです。 別のレジスタにあるDDRTAの値(0x08 AdInit() 実行後は0x0E)を見ると、PTA[3:1]が出力として使用されていることがわかります。 回路図を見ると、これらの端子は、加速度センサの制御信号につながっているように見えるのですが、加速度センサの制御に使用されているのはPTA[2:1]で、PTA[3]は使われていません。 はて、どうしたわけだろう。
PTCPAR (0x10_006F)PTC[3:0]0x00 デフォルトでは、PTC[3:0] 端子は、すべてGPIO(00)として設定されています。 PTC[3:0]の基本機能は、DMA Timerです。
PTDPAR (0x10_0070)PTD[3:0]0x00 デフォルトでは、PTD[3:0] 端子は、すべてGPIO(00)として設定されています。 ただし、MCF52233付録基板のLQFP80パッケージでは、どの端子も出ていないので、使えません。
PUAPAR (0x10_0071)PUA[3:0]0x55 デフォルトでは、PUA[3:0] 端子は、すべて基本機能(01)として設定されています。 これらの端子の基本機能は、UART0です。 SilentCでは、このUARTを使ったモニタを装備しているので当然の結果でしょう。
PUBPAR (0x10_0072)PUB[3:0]0x00 デフォルトでは、PUB[3:0] 端子は、すべてGPIO(00)として設定されています。 これらの端子の基本機能は、UART1です。
PUCPAR (0x10_0073)PUC[3:0]0x00 デフォルトでは、PUC[3:0] 端子は、すべてGPIO(0)として設定されています。 ただし、MCF52233付録基板のLQFP80パッケージでは、どの端子も出ていないので、使えません。
PDDPAR (0x10_0074)PDD[7:0]0x0F デフォルトでは、PDD[7:4] 端子がGPIO(0)として、PDD[3:0]端子が基本機能(1)として設定されています。 これらの端子の基本機能は、DEBUGです。 そのため、PDD[3:0]からPST[3:0]、すなわちプロセッサ・ステータスが出てきそうなのですが、MCF52233付録基板のLQFP80パッケージでは、どの端子も出ていないので、使えません。 開発を行ったときの時のなごりでしょうか。
PLDPAR (0x10_0075)PLD[6:0]0x02 デフォルトでは、PLD[1]端子が基本機能(1)として、そのほかの端子は、GPIO(0)として設定されています。 これらの端子については、PLD[1]は、LNKLED出力として使用されていることがわかっています。 また、PLD[2]は、DDRLDレジスタの値(0x01)から、出力として使用されていて、ACTLEDを制御していることがわかります。 MCF52233付録基板のLQFP80パッケージでは、PLD[6:5]は出ていないので、使えません。
PGPPAR (0x10_0076)PGP[7:0]0x00 デフォルトでは、PGP[3:0] (/IRQ15 ~ /IRQ8)端子は、すべてGPIO(0)として設定されています。 ただし、MCF52233付録基板のLQFP80パッケージでは、PGP[3] (/IRQ11) しか出ていないので、使えません。

やっぱり、かなり面倒だわい。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。