So-net無料ブログ作成
検索選択
- | 次の8件

ColdFire V1 でTPMモジュールを使う - 何だ、HCS08と同じじゃん。 [ColdFire (ColdeFire) V1]このエントリーを含むはてなブックマーク#

クロック速度を計測するために、本格的にタイマ・モジュールを使うことにしました。 TPM1モジュールのCH0を "software only" の "output capture" で使います。

タイマの初期設定

初期設定は、こんなもんです。

TPM1SC_CLKSx = 1; // タイマにBUSCLK=4MHzを供給する
TPM1SC_PS = 2; // 1/4プリスケーラで 1µ秒周期でカウンタを動作させる
TPM1C0SC_MS0x = 1; // software output compare
TPM1C0SC_ELS0x = 1; // software output compare

バスクロックを4MHz、プリスケーラの出力を1µ秒周期にして、後の計算で楽をします。 タイマは、フリーランニングで使うので、モジュロ設定はしません。

タイマイベントを捕まえる

"output compare" イベントの待ち合わせには、 割り込みを使わずにフラグを監視します。

while (!TPM1C0SC_CH0F) {
  // Do nothing
}

whileの後ろに「セミコロンだけの空文」をつけると警告が出たので、 「括弧つき空文」としました。

イベントが発生したら、次回のイベント時刻を設定し、フラグをクリアします。

TPM1C0V += 10000; // 10msec later
TPM1C0SC_CH0F = 0; // clear flag

16ビットのタイマに1µ秒周期のクロックをあたえると、 10m秒周期でイベントを発生させるのが精一杯です。 ColdFireに16-bitタイマというのは、ちと物足りないな。

イベント50回、0.5秒ごとにポートをトグルすると、1秒周期でLEDが点滅します。

if (++count >= 50) { // 500msec event
  RGPIO_TOG = RGPIO_TOG_TOG8_MASK;
  count = 0;
}

HCS08と同じモジュールが入っているので、使い方もまったく同じです。 Cでプログラムを書いていると、CodFireを使っているのを忘れてしまいそうです。

ポートのトグルに使ったRGPIOモジュールの初期設定は、 ColdFire V1 のバス周波数を目で確認するに書きました。 実際のプログラムでは、COPの処理もお忘れなく。


ColdFire V1 のクロック設定 - オーバークロックへの道 [ColdFire (ColdeFire) V1]このエントリーを含むはてなブックマーク#

デバッガから周波数を変更する

空プロジェクトをDEMOQE128に書き込んで、周波数を確認しました。 周波数を測定する装置は使いません。 Multilinkが測定して、デバッガのCommandウィンドウに報告してくれます。

デフォルト状態では、内部参照クロックが31.25kHzに設定されて、 FLLが512倍のクロックを生成するので、DCOの周波数は16MHzになります。 これをBDIVで2分周した8MHzのクロックがCPUに入ります。 バスクロックは、この半分の4MHzになります。 デバッガ画面には、 "~4005157hz"と表示されました。

この状態で、デバッガからICSC2($FFFF8039)の値を$40から$00に変更すると、 BDIV=00(1/1分周)となって、デバッガ画面に"~8002027hz"と表示されます。 ところが、それっきりBDMの接続が切れてしまいました。

同様にデバッガからICSSC($FFFF803B)の値を$11から$B1に変更すると、 DRS=10, DMX32=1 (1824倍FLL; 理論的には DCO=57MHz)となって、 デバッガ画面に"~14272350hz"と表示されます。 が、やはりBDMが切れてしまいました。

プログラムでクロック設定を切り替える

それではと、プログラムでこれらのレジスタを変更してみました。

// Configure ICS
ICSC2_BDIV = 0; // BDIV 1/1
ICSSC_DRST_DRS = 2; // FLL x1536

このプログラムを書き込んで、デバッガから実行したところ、デバッガ画面には、 "~24056572hz"と表示され、BDMとの通信も切れません。

ところが、この状態からデバッガのResetとStartボタンを押して、 再実行させても周波数が変更されたという表示が出てきません。 "STEP OVER"でソースコードをステップ実行させると"~23906250hz"と表示が出てきて、 周波数が変わったらしいことがわかります。

何が悪いんだか、わかんないな。"CodeWarrior V6.0"でやっているせいもあるのかな? HCS08では、こんなにBDM通信が切れちゃうことはなかったんだけどな。

良い子はマネしてはいけない、オーバークロックの実験

仕様書に記述のある最大バスクロック周波数は、25.165MHzなのですが、 ICSの設定だけでそれ以上の周波数が出るらしいことがわかります。 そこで、実際に30MHz動作を実感してみました。

先の24MHzを出していたプログラムをそのまま使います。 そして、プログラムを書き込むときに内部参照クロックの周波数を39.06kHzに変更します。 やることは、これだけです。 これで、DCO出力は、39.60kHz*1536=60.0MHzとなります。

デバッガ画面でも "~30118050hz"の表示が出ています。 つまり、DEMOQE128に搭載されたMultilinkも、 しっかり追従してきているということです。 メモリダンプ画面も変更されている様子が見られるので、通信もできているのでしょう。

当のMCF51QE128ですが、多少暖かくなった程度で、 裏側のMC9S12UF32の方がよっぽど暖かくなっています。 まあ、実験レベルでは動くんじゃないかな?

あくまでも、自己責任で。


部品調達リスト (1) - 4.5V版Music Stick [プリント基板]このエントリーを含むはてなブックマーク#

4.5V版Music Stick

MKEY24-11部品表
部品番号 品名 規格 個数 調達先 通販コード
U1MCUMC9RS08KA2CPC1 digi-keyMC9RS08KA2CPC-ND
-IC Socket8P DIP1 秋月電子P-00035
LED1発光ダイオード1 digi-key160-1087-ND
R2金属皮膜抵抗46.4kΩ 1% 1/4W1 digi-key46.4KXBK-ND
R3金属皮膜抵抗10.0kΩ 1% 1/4W1 digi-key10.0KXBK-ND
R4金属皮膜抵抗4.64kΩ 1% 1/4W1 digi-key4.64KXBK-ND
R5カーボン抵抗2.2kΩ 5% 1/6W1 秋月電子R-25222
R6カーボン抵抗470Ω 5% 1/6W1 秋月電子R-25471
RX0-RX5金属皮膜抵抗2.15kΩ 1% 1/4W6 digi-key2.15KXBK-ND
RY1-RY3金属皮膜抵抗464Ω 1% 1/4W3 digi-key464XBK-ND
C1電解コンデンサ47µF 16V1 digi-keyP969-ND
C2,C5積層セラミックコンデンサ0.1µF 50V2 digi-key490-3859-ND
C4フィルムコンデンサ0.1µF 63V 2%1 digi-keyBC2054-ND
PS1圧電スピーカPS12401 digi-key445-2525-1-ND
CN1ボックスピンヘッダ2 × 31 ダイセン電子-
SW0-SW24Tactical SwitchTL1105LF100Q25 digi-keyEG2504-ND
-電池端子6291 digi-key629K-ND
-電池端子6371 digi-key637K-ND
BAT1-BAT3ボタン電池LR443 --

ColdFire V1 のバス周波数を目で確認する [ColdFire (ColdeFire) V1]このエントリーを含むはてなブックマーク#

命令実行時間を測定するために、アセンブラでソフトウェア・ループを組み、 LEDを点滅させてみました。

#include  /* for EnableInterrupts macro */
#include "derivative.h" /* include peripheral declarations */

プログラムは、全部main関数に押し込んであります。

void main(void) {
犬のおまわりさん(COP)を黙らせるためには、HCS08同様にSOPT1レジスタを設定します。 このあたりの勝手は、HCS08と同じなので、今までの経験が役立ちます。
  SOPT1
  = 0b01010111;
//    |||| ||+--- RSTPE=1  Enable RST port
//    |||| |+---- BKGDPE=1 Enable BKGD port  
//    |||| +----- RSTOPE=1 Enable RSTO port
//    |||+------- WAITE=1  Enable WAIT
//    ||+-------- STOPE=0  Disable STOP
//    |+--------- COPT=1   Disable COP
//    +---------- COPE=0   Disable COP
バスクロック周波数を設定する方法もHCS08同様です。 QE128は、DRSというレジスタが追加されて、Digitally Controlled Oscillator (DCO)の 出力周波数倍率が変更できるようになっています。 ここでは、 内部参照クロックを31.25kHzに設定し、 DCO出力を内部参照クロックの1536倍、すなわち48MHzに設定します。 バス・クロック周波数は、その半分の24MHzになるはずです。
  // Configure ICS
  ICSC2_BDIV      = 0;  // BDIV 1/1
  ICSSC_DRST_DRS  = 2;  // FLL x1536

LEDを点灯させるために、RGPIOという新モジュールを使います。 ここで使用するポートは、RGPIO8(PTC0)のみです。 RGPIOを出力としてイネーブルします。

  // Configure RGPIO8=PTC0 as output
  RGPIO_DIR_DIR8 = 1;
  RGPIO_ENB_ENB8 = 1;
処理本体では、時間稼ぎループをアセンブラで記述しています。 ループ回数は、4,000,000回です。 MCF51QE128のリファレンス・マニュアルによれば、ループ一回で3バス・クロック要するので、 時間稼ぎ部分で12,000,000バスサイクルを消費するはずです。 バス周波数を24MHzに設定すると、0.5秒に相当します。 nopについては、のちほど。
  
  for(;;) {
    __asm {
      move.l  #4000000,d0
//      nop
      L1:
      subq    #1,d0        //  1(0/0)
      bne     L1           //  2(0/0) backward taken
    }
時間稼ぎが終わったら、RGPIOの出力を反転させます。 0.5秒毎に反転されるので、1秒周期でLEDが点滅します。
    // Toggle RGPIO10
    RGPIO_TOG = RGPIO_TOG_TOG8_MASK;
  } /* loop forever */
  /* please make sure that you never leave main */
}

これで、1秒周期でLEDが点滅するはずです。 ところが、実際の点滅周期は、2/3秒になってしまいました。 なぜなんだろう。

ループの回数を間違えることはないと思われるので、 ループ当たりのバス・サイクル数が2サイクルになったと推測しました。

色々とやり直した結果、ラベル"L1"のアドレスが、"0-modulo-4"だった場合は、 ループ当たり2バス・サイクルを消費し、 "2-modulo-4"の場合は、3サイクルを消費するらしいことがわかりました。 nopをコメントからはずすと、結果が異なってきます。 えっ?リファレンス・マニュアルが違うの?それとも、シリコン?

まさに、パイプラインのなせる業。 インライン・アセンブラは、".align 4"を入れることが出来ないらしいので、 こういった使い方をする際には、要注意です。

RGPIOを使った理由

RGPIOというのは、簡単に16本のポートの操作を行うありがたい仕掛けなのですが、 これを使ったのには理由があります。 この解に至るまでにHCS08で良く使われる以下のような構文を試してみました。

  • PTCD_PTCD0 = !PTCD_PTCD0;
  • PTCD_PTCD0 = ~PTCD_PTCD0;
  • PTCD ^= PTCD_PTCD0_MASK;

ところが、こいつらが見るに耐えない冗長なコードを出力してしまったので、 RGPIOの登場となったしだいです。 どんな、冗長なコードができたかは、ぜひ、ご自身でお試しください。


画像を貼り付けるテスト [電子工作]このエントリーを含むはてなブックマーク#

画像を貼り付けるときには、

そうか、ボタンをポチッで、できちゃうんだ。
これは便利。

ちょっと、デカかったかな?


ColdFire V1でコンテキストスイッチ [ColdFire (ColdeFire) V1]このエントリーを含むはてなブックマーク#

ColdFire V1を使って、コンテキストスイッチを行うプログラムを作成してみました。モデルは、OS-9/68000です。

//**************************************************************
// $Id: main.c,v 1.3 2008/01/10 15:04:18 noritan Exp $
//
// Multi tasking Practice
//**************************************************************
#include <hidef.h> /* for EnableInterrupts macro */
#include "derivative.h" /* include peripheral declarations */

定数の定義です。タスクの数、スタックのサイズ、ステートマシンの状態コードを定義しています。スタックサイズは、4Byte(Long Word)単位の数です。

#define N_TASK (10)
#define N_SSTACK (30)
#define N_USTACK (30)

#define ST_NULL (0)
#define ST_SLEEP (1)
#define ST_ACTIVE (2)
#define ST_RUN (3)

TaskDescriptor は、OS-9では、Process Descriptor と呼ばれていました。
他のオペレーティング・システムでは、 Task Control Block と呼ばれることもあります。
OS-9/68000 の Process Descriptor は、2KByteもありましたが、ここでは必要最小限としてあります。
count は、状況によって使い分ける万能カウンタです。

struct TaskDescriptor {
  const struct ModuleDescriptor *module;
  dword *workarea;
  dword *ssp;
  dword *usp;
  dword status;
  dword count;
  dword sStack[N_SSTACK];
};

ModuleDescriptor は、プログラム単位を定義しています。
OS-9/68000 では、ModuleDescriptor に指定された大きさの作業領域などを動的に割り当てることが出来るのですが、今回は、初期化と実行部のエントリだけ定義しています。

struct ModuleDescriptor {
  void (*init)(
    struct TaskDescriptor *task,
     int argc, char **argv
  );
  void (*proc)(void);
};

typedef struct TaskDescriptor TaskDescriptor;
typedef struct ModuleDescriptor ModuleDescriptor;

TaskDescriptor は、同時実行可能なタスクの数だけ必要です。
thisTaskは、現在実行中のタスクを指します。

TaskDescriptor taskTable[N_TASK];

TaskDescriptor *thisTask = 0;

各タスクに必要な作業領域は、初期化ルーチンで確保します。
ユーザモードでのスタックは、ここに確保します。
作業領域は、割り当てるけど開放しないいいかげん方式です。

typedef struct WorkArea {
  dword toggle;
  dword period;
  dword uStack[N_USTACK];
} WorkArea;

dword workareaUsed = 0;
WorkArea workareaTable[N_TASK];

ModuleDescriptor を元に新しいタスクを作成し、実行中タスクリストに登録します。
もっとも、現在の実装では、リストを作成しているわけではなく、
タスクの状態を "ACTIVE" にするだけで、
あとはスケジューラが解決してくれています。

//----------------------------------------------
//  Create a TASK with a MODULE and PARAMETER
//  and add to ACTIVE list.
//----------------------------------------------
TaskDescriptor *tsk_start(
    const ModuleDescriptor * module,
     int argc,
     char * *  argv
) {
  int i;
  struct TaskDescriptor *task;
  
  for (i = 0, task = &taskTable[0]; i < N_TASK; i++, task++) {
    if (task->status == ST_NULL) {
      task->module = module;
      task->ssp = &(task->sStack[N_SSTACK]);
      task->status = ST_ACTIVE;
      task->count = 0;
      (task->module->init)(task, argc, argv);
      return task;
    }
  }
  return 0;
}
「何もしないプログラム」の初期化部分です。 作業領域を確保し、スタックフレームを構築しています。 スタックフレームは、例外が発生した時のイメージを再現しています。
void idle_task_init(
    TaskDescriptor *  task,
    int argc,
    char **argv
) {
#pragma unused (argc, argv)
  WorkArea *workarea = &workareaTable[workareaUsed++];
  register dword *usp = &(workarea->uStack[N_USTACK]);
  register dword *ssp;
  dword *_A5;
  dword i;
  void idle_task_proc();
  void tsk_exit(void);

#pragma warn_any_ptr_int_conv off  
  // Establish DUMMY user stack
  *(--usp) = (word)tsk_exit;

  // Establish a stack frame
  ssp = task->ssp;
  *(--ssp) = (dword)idle_task_proc;    // PC
  *(--ssp) = 0x40000000;              // Vector & SR
  *(--ssp) = (dword)usp;  // A6
  __asm {
    move a5,_A5
  }
  *(--ssp) = (dword)_A5;  // copy of current A5
  for (i = 0; i < 13; i++) {
    *(--ssp) = 0;  // D0-D7/A0-A4
  }
#pragma warn_any_ptr_int_conv reset  

  task->workarea = (dword *)workarea;
  task->usp = usp;
  task->ssp = ssp;
}
「何もしないプログラム」の実行部です。 犬にえさを与え続けます。
void idle_task_proc(void) {
  for (;;) {
    // Do nothing
    __RESET_WATCHDOG(); /* feeds the dog */
  }
}
「何もしないプログラム」モジュールを定義します。 モジュールは、ROMに配置されます。
const ModuleDescriptor idle_module = {
  idle_task_init,
  idle_task_proc
};
メインルーチンです。 マイコンの初期化を行い、 「何もしないプログラム」モジュールを使って二つの「何もしないタスク」を作成し、 "trap #0" でコンテキストを切り替えます。
void main(void) {
  TaskDescriptor *idle_task;

  // Configure one-time-write SOPT1
  SOPT1 = 0
    | SOPT1_WAITE_MASK    // Enable WAIT instruction
    | SOPT1_RSTOPE_MASK   // Enable RSTO* port
    | SOPT1_BKGDPE_MASK   // Enable BKGD port
    | SOPT1_RSTPE_MASK ;  // Enable RESET* port

  // Configure RTC module
  RTCSC_RTCLKS = 0;       // 1kHz OSC
  RTCSC_RTCPS  = 11;      // 10msec
  RTCMOD       = 50-1;    // 50counts (500msec)
  RTCSC_RTIE   = 1;       // Enable interrupts.

  // Invoke two tasks
  idle_task = tsk_start(&idle_module, 0, 0);
  idle_task = tsk_start(&idle_module, 0, 0);
  __asm {
    // yeild to context switch
    trap #0
  }

  EnableInterrupts; /* enable interrupts */
  for(;;) {
    __RESET_WATCHDOG(); /* feeds the dog */
  } /* loop forever */
  /* please make sure that you never leave main */
}
ユーザモードの実行部プログラムが終了したら、"tsk_exit" にやってきます。 今回は、使われません。 あれ? "trap #0" が必要なはずだぞ。
void tsk_exit(void) {
  thisTask->status = ST_NULL;
}
次に実行すべきタスクを選択します。 OS-9/68000 がそうであるように、 タスクリストの中から、もっとも待ち時間が長い実行状態タスクを選びます。 待ち時間は、 "count" で数えていて、 "schedule" で選択されないと インクリメントされます。
void schedule(void) {
  int i;
  struct TaskDescriptor *task;
  dword maxCount = 0;
  struct TaskDescriptor *taskSelected = 0;
  
  for (i = 0, task = &taskTable[0]; i < N_TASK; i++, task++) {
    if (task->status == ST_ACTIVE) {
      task->count++;
      if (task->count > maxCount) {
        taskSelected = task;
        maxCount = task->count;
      }
    }
  }
  thisTask = taskSelected;
  if (thisTask) {
    thisTask->count = 0;
  }
}
"trap #0" 例外は、現在実行中のタスクを中断して、 別のタスクに切り替える機能があります。 この部分は、 タスク選択の部分だけは"schedule"関数を呼び出していますが、 スタックの深さが重要になってくるので、 残りはフルにアセンブラで記述しています。
asm interrupt VectorNumber_Vtrap0 trap0_isr(void) {
  // Save the stack frame == task context
    lea -60(a7),a7
    movem d0-d7/a0-a6,(a7)
  // Save SSP/USP to the current TaskDescriptor
    move.l thisTask,a0
    move.l a7,struct(TaskDescriptor.ssp)(a0)
    move.l usp,a1
    move.l a1,struct(TaskDescriptor.usp)(a0)
  jsr schedule  
  // Restore SSP/USP from the selected TaskDescriptor
    move.l thisTask,a0
    move.l struct(TaskDescriptor.usp)(a0),a1
    move.l a1,usp
    move.l struct(TaskDescriptor.ssp)(a0),a7
  // Restore the stack frame
    movem (a7),d0-d7/a0-a6
    lea 60(a7),a7
    rte
}
タイマ割り込みのISRです。 基本的に"trap #0"と同じですが、 タイマフラグをクリアするシーケンス"rtc_isr_content"を 呼び出しています。
void rtc_isr_content(void) {
    RTCSC_RTIF = 1;         // Clear the flag.
}

asm interrupt VectorNumber_Vrtc rtc_isr(void) {
  // Save the stack frame == task context
    lea -60(a7),a7
    movem d0-d7/a0-a6,(a7)
  // Save SSP/USP to the current TaskDescriptor
    move.l thisTask,a0
    move.l a7,struct(TaskDescriptor.ssp)(a0)
    move.l usp,a1
    move.l a1,struct(TaskDescriptor.usp)(a0)
  // Do ISR specific task    
    jsr rtc_isr_content
  // Do task scheduling
    jsr schedule  
  // Restore SSP/USP from the selected TaskDescriptor
    move.l thisTask,a0
    move.l struct(TaskDescriptor.usp)(a0),a1
    move.l a1,usp
    move.l struct(TaskDescriptor.ssp)(a0),a7
  // Restore the stack frame
    movem (a7),d0-d7/a0-a6
    lea 60(a7),a7
    rte
}

//**************************************************************
//  $Log: main.c,v $
//  Revision 1.3  2008/01/10 15:04:18  noritan
//  Completed... maybe
//
//**************************************************************

参考文献

OS‐9/68000 (OSシリーズ)

OS‐9/68000 (OSシリーズ)

  • 作者: 高沢 嘉光
  • 出版社/メーカー: 共立出版
  • 発売日: 1989/01
  • メディア: 単行本

- | 次の8件