mikecatのアイコン画像
mikecat 2022年03月03日作成 © CC BY 4+
製作品 製作品 閲覧数 1581
mikecat 2022年03月03日作成 © CC BY 4+ 製作品 製作品 閲覧数 1581

IchigoJamによる1Hz~数MHzのクロック信号生成器

IchigoJamによる1Hz~数MHzのクロック信号生成器

きっかけ

以下の記事を読んだところ、周波数の調整に苦労している様子がみられました。

そこで、より簡単に周波数を設定でき、1~4MHz程度の信号を出せる方法を考えました。

IchigoJam

こどもパソコン IchigoJam - はじめてのプログラミングパソコン(1500円)

IchigoJamは、電源(USB)・キーボード・ディスプレイ(コンポジット信号)を接続してBASICによるプログラミングができる製品です。
また、パソコンからシリアル通信(UART)でのプログラミングもできます。

※IchigoJamはjig.jpの登録商標です。

PWMの操作

今回は、PWM機能を利用してクロック信号を生成します。
これは、周期とHIGHの期間を指定して信号を生成できる機能です。

標準機能

IchigoJam BASIC リファレンス 1.0

IchigoJam BASIC では、以下のPWMコマンドによりPWM機能を使うことができます。

PWM 出力端子, オン時間, 周期

オン時間と周期は、0.01ミリ秒を1とする数値で指定します。
したがって、最大で100kHzの周期が指定できそうですが、これでは数MHzの信号の生成には足りません。
そこで、標準機能でPWMの出力を有効にした後、
使われているマイコンのレジスタをマシン語で直接制御して周期を設定することにしました。
今回は出力端子2 (OUT2) を使用します。

IchigoJam BASICの変数は16ビット幅しか無いのに対し、マシン語を用いると32ビットのレジスタを扱えるので、
より高い精度での周期の設定がしやすくなる、という利点もあります。

最近のバージョンでは周期に負の数を指定することで時間の単位を480分の1にできるので、
100kHzおよび1~4MHz (整数MHz)であればマシン語を使わなくてもこの機能で設定できます。
また、10Hz~10kHzの10のべき乗Hzは、0.01ミリ秒単位の数値で設定できます。
ただし、1Hzは周期が長すぎて設定値が16ビットに収まりません。

マシン語の呼び出し

USR関数を用いることで、IchigoJam BASICからマシン語の関数を呼び出すことができます。

USR(呼び出す仮想アドレス, 引数)

例えばキャラクタコード領域の#700~#7FF にマシン語の関数のデータを置き、これを呼び出すことができます。
これにより、高速な計算やマイコンの機能の直接制御が可能です。
引数はIchigoJam BASICで扱える数値を1個指定することができ、呼び出すマシン語の関数の第1引数として渡されます。

今回は、この引数を用いて設定する周波数の情報を渡すことにしました。
正の値を渡した場合、その値を設定する周波数 [Hz] とみなします。
負の値を渡した場合、その値を設定する周波数 [kHz] の-1倍とみなします。

さらに、マシン語の関数の戻り値がUSR関数の戻り値となります。
そこで、この戻り値を用いて、実際に設定した周波数の情報を返すことにしました。
引数が正の場合はHz単位、負の場合はkHz単位で返します。

IchigoJam のタイマー

IchigoJamの基板を見ることで、出力端子2はマイコンの10番ピンに相当することがわかります。
IchigoJamで使われているマイコンは、LPC1114FN28/102 または LPC1114FDH28/102 です。
データシート UM10398 を参照し、このピンの仕様を調べました。

すると、10番ピンには CT32B1_MAT0 という機能が割り当てられていることがわかります。
これは、32ビットタイマー1のマッチ0の出力、ということになります。
32ビットタイマー1のレジスタはベースアドレス 0x40018000 に配置されており、
今回主に関係するのは以下のレジスタです。

レジスタ オフセット 説明
TMR32B1PR 0x0C 何クロックでカウントを1進めるかを決める (プリスケーラ)
TMR32B1MR0 0x18 (今回は)マッチ出力0がHIGHになるタイミングを決める
TMR32B1MR3 0x24 (慣例上)タイマーのカウントの周期(リセットのタイミング)を決める

タイマーのカウントが TMR32B1MR0 以上のとき、マッチ出力0がHIGHになります。
タイマーのカウントが TMR32B1MR3 のとき、次にカウントを進めるタイミングでカウントが0になります。
(他のレジスタでこの動作をするように設定されています)

マシン語でレジスタの値を読み取って調査をした結果、IchigoJamのPWMコマンドは以下の動作をするようでした。

  • TMR32B1PR を「479」に設定する
  • TMR32B1MR0 を「周期 - オン時間」に設定する
  • TMR32B1MR3 を「周期 - 1」に設定する

IchigoJamは48MHzで動作するので、プリスケーラを479に設定することでカウントが100kHzで進むようになり、
周期とオン時間の設定がしやすくなるようです。

今回は、PWMコマンドで周期を2、オン時間を1に設定し、
マシン語でプリスケーラの設定を書き換えることで生成する信号の周期を設定します。
このとき、2回カウントが進むと生成する信号の1周期となるので、
プリスケーラには生成する信号の周期 (48MHzのクロック何回分か) の半分の値を設定します。
すなわち、24000000 を生成する信号の周波数 [Hz] で割った値を設定します。
プリスケーラには32ビットの値を設定できるので、生成する信号の周波数が1Hz以上のとき、
この値はそのままプリスケーラに設定することができます。
最後に、24000000をプリスケーラに設定した値で割ることで、設定した周波数を出します。
最初の引数が負の値だったときは、この周波数を1000で割ってkHz単位にします。

IchigoJamのマシン語には割り算を行う命令が無いので、
以下のアルゴリズムによる符号なし整数の割り算を実装しました。

  1. 割られる数と割る数を受け取る。
  2. 商を0、商の差分を1、割られる数の差分を割る数、に初期化する。
  3. 割られる数の差分のトップビット(MSB)が0である間、以下を繰り返す。
    1. 商の差分を1ビット左シフトする。
    2. 割られる数の差分を1ビット左シフトする。
  4. 商の差分が0でない間、以下を繰り返す。
    1. 割られる数が割られる数の差分以上の場合、以下を行う。
      1. 割られる数から割られる数の差分を引く。
      2. 商に商の差分を足す。
    2. 割られる数の差分を1ビット論理右シフトする。
    3. 商の差分を1ビット論理右シフトする。

IchigoJam R のタイマー

IchigoJam R で使われているマイコンは、GD32VF103CBT6 です。
IchigoJam Rβ x 無線USBキーボード、ピン配置とセーブデータの読み書き方法公開
を参照すると、出力端子2は TIMER2_CH1 という機能が割り当てられていることがわかります。
これは、タイマー2のチャンネル1ということになります。
User Manual を参照すると、タイマー2のレジスタはベースアドレス 0x40000400 に割り当てられており、
今回主に関係する以下のレジスタがあることがわかります。

レジスタ オフセット 説明
TIMERx_PSC 0x28 何クロックでカウントを1進めるかを決める (プリスケーラ)
TIMERx_CH1CV 0x38 (今回は)チャンネル1の出力がLOWになるタイミングを決める
TIMERx_CAR 0x2C タイマーのカウントの周期(リセットのタイミング)を決める

マシン語でレジスタの値を読み取って調査をした結果、IchigoJam R のPWMコマンドは以下の動作をするようでした。

  • TIMERx_PSC を「959」に設定する
  • TIMERx_CH1CV を「オン時間」に設定する
  • TIMERx_CAR を「周期 - 1」に設定する

IchigoJamの時と同様に、96MHzで動作するIchigoJam R に合わせてプリスケーラを設定し、
オン時間と周期を設定しやすくしているようです。

そこで、こちらもIchigoJamの時と同様に、PWMコマンドでオン時間を1、周期を2に設定し、
プリスケーラに 48000000 を生成する信号の周波数 [Hz] で割った値を設定することにしたいです。
しかし、今回はプリスケーラが16ビットしか設定できず、周波数が低いと設定できません。
そこで、オン時間と周期を伸ばし、プリスケーラの値を設定できる範囲に収める処理を入れることにしました。
1~0x8000のオン時間を大きい方から全探索し、プリスケーラの値が設定できる範囲を超えたら探索を打ち切ります。
周期はオン時間の2倍に設定します。
オン時間をnnにすると、オン時間が1の時と比べて生成する信号の周期はnn倍になり、
プリスケーラに設定する値を1/n1/nにできます。
この割り算では切り捨てを行うため誤差が発生することがありますが、
なるべく誤差が少なくなるオン時間を全探索することにしました。

IchigoJam R のマシン語には割り算を行う命令があるので、
割り算を自前で実装せずにすみます。

実装したプログラム

マシン語

上記の方針で実装しました。
改造版のasm15でアセンブルできます。

IF M0 GOTO @LEGACY
MODE RV32C
R30 = 0
IF R10 >= R0 GOTO @RV32C_NOSCALE
R10 = R0 - R10
R30 = R0 + 1000
R10 = R10 * R30
@RV32C_NOSCALE

' R15 = 48000000
R15 = #02DC7000
R15 = R15 + #FFFFFC00

R10 = R15 / R10
IF !R10 GOTO @RV32C_RET

' bruteforce multiplier
' R11 = minimum error, R12 = prescaler, R13 = multiplier
R11 = -1
R28 = #10000
R14 = #8000
@RV32C_SEARCH
R16 = R10 / R14
IF LTU(R28, R16) GOTO @RV32C_SEARCH_END
R17 = R14 * R16
R29 = R10 - R17
IF LTU(R11, R29) GOTO @RV32C_SEARCH_NO_UPDATE
R11 = R29
R12 = R16
R13 = R14
@RV32C_SEARCH_NO_UPDATE
R14 += -1
IF R14 GOTO @RV32C_SEARCH
@RV32C_SEARCH_END

R10 = R12 * R13
R14 = #40000000
R14 = R14 + #400
R12 += -1
[R14 + #28]L = R12
[R14 + #38]L = R13
R13 += R13
R13 += -1
[R14 + #2C]L = R13

R10 = R15 / R10
IF R30 = R0 GOTO @RV32C_RET
R12 = R0 + 1000
R10 = R10 / R12
@RV32C_RET
RET

MODE M0
@LEGACY
PUSH {R4,R5,R6,LR}
R4 = 10
R3 = 100
R3 *= R4
R0 - 0
IF GE GOTO @M0_NOSCALE
R0 = -R0
R0 *= R3
R4 = 0
@M0_NOSCALE
R6 = R0
' R5 = 24000000
R5 = 24
R5 *= R3
R5 *= R3
R0 = R5
R1 = R6
GOSUB @DIVIDE
R0 & R0
IF EQ GOTO @M0_RET
' R1 = #4001800C
R1 = 4
R1 = R1 << 28
R2 = #18
R2 = R2 << 12
R1 = R1 + R2
R1 += #C
R2 = R0 - 1
[R1 + 0]L = R2

R1 = R0
R0 = R5
GOSUB @DIVIDE
R4 & R4
IF NE GOTO @M0_RET
R1 = 10
R2 = 100
R1 *= R2
GOSUB @DIVIDE

@M0_RET
POP {R4,R5,R6,PC}

' R0 = R0 / R1 (unsigned)
@DIVIDE
R2 = R0
R0 = 0
R3 = 1
R1 & R1
GOTO @DIVIDE_LOOP1_COND
@DIVIDE_LOOP1
R3 = R3 << 1
R1 = R1 << 1
@DIVIDE_LOOP1_COND
IF PL GOTO @DIVIDE_LOOP1
@DIVIDE_LOOP2
R2 - R1
IF CC GOTO @DIVIDE_LOOP2_ZERO
R2 = R2 - R1
R0 = R0 + R3
@DIVIDE_LOOP2_ZERO
R1 = R1 >> 1
R3 = R3 >> 1
IF NE GOTO @DIVIDE_LOOP2
RET

IchigoJam BASIC

上記のマシン語を組み込んだプログラムです。
IchigoJam・IchigoJam R 両対応です。
条件によっては設定した周波数がオーバーフローするので、表示を工夫しています。

10 ' クロック セイセイ
20 POKE#700,183,47,54,224,1,79,99,88,5,0,51,5,160,64,19,15,128,62,51,5,229,3,183,119,220,2,147,135,7,192,51,197,167,2,57,197,253,85,65,110,33,103,51,72,229,2,99,109,14,1,179,8,7,3,179,14,21,65,99,229
30 POKE#73C,213,1,246,133,66,134,186,134,125,23,117,243,51,5,214,2,55,7,0,64,19,7,7,64,125,22,16,215,20,223,182,150,253,22,84,215,51,197,167,2,99,6,15,0,19,6,128,62,51,69,197,2,130,128,112,181,10,36
40 POKE#776,100,35,99,67,0,40,2,218,64,66,88,67,0,36,6,70,24,37,93,67,93,67,40,70,49,70,0,240,22,248,0,66,18,208,4,33,9,7,24,34,18,3,137,24,12,49,66,30,10,96,1,70,40,70,0,240,8,248,36,66,4,209,10,33
50 POKE#7B6,100,34,81,67,0,240,1,248,112,189,2,70,0,32,1,35,9,66,1,224,91,0,73,0,252,213,138,66,1,211,82,26,192,24,73,8,91,8,248,209,112,71
60 INPUT"タンイ? 1:Hz 2:kHz > ",U:IFU<1OR2<UGOTO60
70 INPUT"シュウハスウ? > ",F:IFF<0GOTO70
80 IFF=0OUT2,0:?"テイシ シマシタ":END
90 PWM2,1,2:R=USR(#700,F*(3-U*2)):IFR=0?"エラー デス":END
100 IFR<-25536?3;:R=R-30000
110 IFR<0?4;:R=R-20000-20000
120 ?R;:IFU=2?"k";
130 ?"Hz ニ セッテイ シマシタ"

74HC00を用いた5V化

IchigoJam・IchigoJam R の出力信号は3.3Vです。
そこで、(手元にあった)ロジックICのTC74HC00APを用いて5Vに変換してみました。
データシートによればTC74HC00APの"H"レベルの入力電圧の最小はVCC=4.5Vのとき3.15V、
単純計算でVCC=5Vに換算すると 3.15×5/4.5=3.53.15 \times 5 / 4.5 = 3.5 [V] となり、3.3Vだと若干足りなそうです。
…が、動いたのでとりあえずヨシ!
ついでに、パスコンも使わないユニットの入力も省略していますが、とりあえず動いたのでヨシ!

回路図

実際に接続した様子

実行結果

IchigoJam BASIC 1.0.0 では、PWMコマンドがSyntax Errorとなってしまい、動きませんでした。
FNIRSI-1013D というオシロスコープで出力を確認しました。

まずは低い周波数を試してみました。

IchigoJamの画面 (Hz単位)
オシロスコープによる出力信号の観測 (501Hz)
オシロスコープによる出力信号の観測 (502Hz)

501Hzと502Hzを出し分けることができています。

次に、3MHzの信号を出してみました。

IchigoJamの画面 (kHz単位)
オシロスコープによる出力信号の観測 (3MHz)

24の約数である1MHz、2MHz、3MHz、4MHz、6MHz、12MHzは出せますが、5MHzは出せません。
(4.8MHzなら出せます)

ちなみに、TC74HC00APの入力から出力まで約10ns遅れることが観測できました。
(立ち上がりと立ち下がりが1画面に入るよう、12MHzに設定しました)

オシロスコープによる出力信号の観測 (12MHz)

1
  • mikecat さんが 2022/03/03 に 編集 をしました。 (メッセージ: 初版)
ログインしてコメントを投稿する