編集履歴一覧に戻る
mikecatのアイコン画像

mikecat が 2022年03月03日07時09分06秒 に編集

初版

タイトルの変更

+

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

タグの変更

+

IchigoJam

+

信号生成

+

クロック生成

+

タイマー

+

マシン語

+

ロジックIC

+

マイコン

メイン画像の変更

メイン画像が設定されました

記事種類の変更

+

製作品

ライセンスの変更

+

(CC BY 4+) Creative Commons Attribution CC BY version 4.0 or later

本文の変更

+

# きっかけ 以下の記事を読んだところ、周波数の調整に苦労している様子がみられました。 * [LTC1799モジュールと周波数カウンタを組み合わせて30MHzまで出せるオシレータの作成 by shigobu | elchika](https://elchika.com/article/00673492-2375-4ce9-b8c5-5df0d80838e1/) * [TD4オーバークロック MHz級 by shigobu | elchika](https://elchika.com/article/34d9f265-3758-4d1b-9ec3-2be2bdef5e7c/) そこで、より簡単に周波数を設定でき、1~4MHz程度の信号を出せる方法を考えました。 # IchigoJam [こどもパソコン IchigoJam - はじめてのプログラミングパソコン(1500円)](https://ichigojam.net/) IchigoJamは、電源(USB)・キーボード・ディスプレイ(コンポジット信号)を接続してBASICによるプログラミングができる製品です。 また、パソコンからシリアル通信(UART)でのプログラミングもできます。 ※IchigoJamはjig.jpの登録商標です。 # PWMの操作 今回は、PWM機能を利用してクロック信号を生成します。 これは、周期とHIGHの期間を指定して信号を生成できる機能です。 ## 標準機能 [IchigoJam BASIC リファレンス 1.0](https://ichigojam.net/IchigoJam-1.0.html) 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キーボード、ピン配置とセーブデータの読み書き方法公開](https://fukuno.jig.jp/3104) を参照すると、出力端子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倍に設定します。 オン時間を$n$にすると、オン時間が1の時と比べて生成する信号の周期は$n$倍になり、 プリスケーラに設定する値を$1/n$にできます。 この割り算では切り捨てを行うため誤差が発生することがありますが、 なるべく誤差が少なくなるオン時間を全探索することにしました。 IchigoJam R のマシン語には割り算を行う命令があるので、 割り算を自前で実装せずにすみます。 # 実装したプログラム ## マシン語 上記の方針で実装しました。 [改造版のasm15](https://mikecat.github.io/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 \times 5 / 4.5 = 3.5$ [V] となり、3.3Vだと若干足りなそうです。 …が、動いたのでとりあえずヨシ! ついでに、パスコンも使わないユニットの入力も省略していますが、とりあえず動いたのでヨシ! ![回路図](https://camo.elchika.com/1e8237e46b5c8d3c91054d2272d53ec75f23e129/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f34323336376365652d363839362d346364612d396134612d3433343133383638326635342f32333536303064342d653937332d346461322d386331362d366266626632643161306338/) ![実際に接続した様子](https://camo.elchika.com/840fc6cec1d34b16290bf8653d7eddae637cf6fe/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f34323336376365652d363839362d346364612d396134612d3433343133383638326635342f64623064636463662d373431392d343161312d623263642d636133393834626661663037/) # 実行結果 IchigoJam BASIC 1.0.0 では、PWMコマンドがSyntax Errorとなってしまい、動きませんでした。 FNIRSI-1013D というオシロスコープで出力を確認しました。 まずは低い周波数を試してみました。 ![IchigoJamの画面 (Hz単位)](https://camo.elchika.com/8464689843b4366ebc23a9bda165181114f394c6/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f34323336376365652d363839362d346364612d396134612d3433343133383638326635342f66303865373266372d326263372d343364652d393439312d653637396664373533633863/) ![オシロスコープによる出力信号の観測 (501Hz)](https://camo.elchika.com/1316a07dc511fec3934134b215ea83e9fe5717a5/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f34323336376365652d363839362d346364612d396134612d3433343133383638326635342f31666633653038652d343162392d343865392d386139392d643135666331633761373865/) ![オシロスコープによる出力信号の観測 (502Hz)](https://camo.elchika.com/7f34eb5634b58b723a731f1024e668e729057f94/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f34323336376365652d363839362d346364612d396134612d3433343133383638326635342f62326436306238392d366164372d343063322d386665662d376264616538623532656435/) 501Hzと502Hzを出し分けることができています。 次に、3MHzの信号を出してみました。 ![IchigoJamの画面 (kHz単位)](https://camo.elchika.com/25e300e8d375927298cc8b756204be4e72910f78/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f34323336376365652d363839362d346364612d396134612d3433343133383638326635342f63623564366338612d383634612d343239312d393466362d373936613439663663386639/) ![オシロスコープによる出力信号の観測 (3MHz)](https://camo.elchika.com/577bfce71431c766b8e8b1e902e1bc5ef51a5994/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f34323336376365652d363839362d346364612d396134612d3433343133383638326635342f36656334306233322d366563642d343339392d623534632d303064623230663133636336/) 24の約数である1MHz、2MHz、3MHz、4MHz、6MHz、12MHzは出せますが、5MHzは出せません。 (4.8MHzなら出せます) ちなみに、TC74HC00APの入力から出力まで約10ns遅れることが観測できました。 (立ち上がりと立ち下がりが1画面に入るよう、12MHzに設定しました) ![オシロスコープによる出力信号の観測 (12MHz)](https://camo.elchika.com/ea5929f596a40410d39723e9d4f1b60163d0a9c0/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f34323336376365652d363839362d346364612d396134612d3433343133383638326635342f64326531663237382d643263352d346132342d393334662d363563393534313732323164/)