2024年7月18日木曜日

出入り検知器をPCB化する(その10)メロディ生成4

 次はテンポの処理である。

単音の場合は適当に遅延を挟むことでメロディを演奏することが出来たが和音の場合は各音符の長さが同一ではないため単純な遅延では処理できない。 そこで32分音符の長さを基準とした割り込みとソフトのカウンタで処理する事とした。

実際にはスタッカートやテヌートも処理したいので音符の長さを12分割した間隔で割り込みをかけ発音を制御する。最高のレートだとBPM=200の場合は約3ms毎に割り込む事になる。

割込みにはTimer6(TMR2)を使った。250KHzのクロックをプリスケーラで16分周するとポストスケーラを使わなくても丁度良い具合になる。 BPM=40で244分周、BPM=200で49分周と、8ビットの分周器に丁度収まるスケールだ。

ここまで出来たら、あとは譜面の記法を決めればよい。TeraTermで操作できるよう譜面は全て文字で表記するようにした。前に書いたように音の高さはMIDIの音階と同じコードを使うがメモリーを節約するため16進数で表記する。音の長さは次の様にした。

音符は音の高さ2文字と長さ1文字の3文字で表す(休符は音階=0とする)。更にチャネル指定を「:c:」で、BPMを「%hh」、音階のオフセットを「$±h」で表す。1音を3文字で表すよう数値は16進標記とした。3文字の間にはスペースや幾つかの区切り記号を挿入することが出来る。

譜面は改行で終わる1行の文字列である。譜面を最後まで読み込んだら内部でチャネル(音源)ごとの楽譜に変換し、32分音符の1/12の時間間隔の割込みに従って同時進行で演奏(音をON/OFF)する。

次のような4和音の譜面を1オクターブ上げてBPM=100で演奏してみた(この譜面はプログラム中にデータとして記述して分かり易いよう改行を入れている部分であるが、実際は「"」や改行を外し1行にする必要がある)。

       "%64$+c"
    ":1:43E 41E 46E 48G 4dG 4aE_48G 4dG 4aE 46E 48E 43E 413"
    ":3:3EE 3cE 3eE 45E     45E_45E     46E 43E 43E 3cE 3c3"
    ":2:3aE 39E 3aE 3cE     41E_3cE     41E 3eE 40E 37E 393"
    ":4:37E 35E 37E 35E     32E_35E     3aE 37E 30E 34E 353"


 これを解釈し内部で使う音源ごとの楽譜に変換したものは次のようなものである(各音源の最初の16バイトの16進ダンプ)。

  --1-- 54 43 54 41 54 46 74 48  74 4d 54 4a 74 48 74 4d
  --2-- 54 3a 54 39 54 3a 54 3c  54 41 54 3c 54 41 54 3e
  --3-- 54 3e 54 3c 54 3e 54 45  54 45 54 45 54 46 54 43
  --4-- 54 37 54 35 54 37 54 35  54 32 54 35 54 3a 54 37

これを演奏するとこのような音になる。

(その後)

3連符を表現したくて割込み頻度を3倍にして、32分音符の間に3×12回割り込むようにした。これで音符の長さを表すパラメータが3倍となり3で割り切れるようになる。表記も改訂した。

この機能を使って3連符のあるこんな演奏ができる。我ながら上出来である。 

(さらにその後)

音源3,4はパルス幅変調ができるので、試してみた。パルス幅を50%、25%、12.5%、…と半分ずつにしていくと、50%~6.25%までは音質が変わり、それを超えると音量が低下する。これは主旋律を目立たせる技として使えそうである。

(さらにその後)

オシロスコープを使って割り込み処理にどの程度時間を取られているか観測してみたところ約2msかかっていた。一方200BPMの時32分音符に費やされる時間は約37ms。実際にはそれを12分割して管理していたが、さらに3連符処理のためその1/3倍としている。つまり割込み処理を1ms以下で終える必要がある。一方実際には処理にその倍の時間がかかっており100BPM以上の演奏には追従できない事が分かる。 これを解決するためCPUクロック(Fosc)を4倍に引き上げて4MHzとした。これで1回の割込み処理が500μs程度で終わり時間に余裕が生まれた(実際には36回(或いはその倍数)に1回起こる音符の切り替え処理時にもっと多くの時間がかかるが聞いていても分からない)。またクロックの変更に伴って影響を受けるUARTの通信速度や音の高さに関係するパラメータを変更した。幸いにも音符の高さを表すパラメータはクロックを細工する事で再計算せずに当初の音域をカバーする事が出来た。詳しくは後述する。

(つづく)



2024年7月16日火曜日

出入り検知器をPCB化する(その9)メロディ生成3

 4チャネルのミキサーを作る。

4chの発振器の出力をPIC外で抵抗ネットワークでも組んで合成すれば済むわけだが、ここではデジタル的に行えないか。4つの信号のXORを取れば済むと考えCLCを3つ組み合わせてミキサーを構成してみた。しかしサッパリ動かない。かなり長期間悩んだが問題はPIC16F18326のデータシートの次の図であった。

図を見てAND-XOR回路とみてプログラムを組んだ。即ち1番目と3番目を入力としてXORとして出力させるために2番目と4番目の空き入力をHとした。しかし図は間違いで、実際には図上の記述の通りOR-XOR回路で2番目と4番目の空き入力をLとしなければ動かなかった。細かい字を読みたくない老眼の弊害が現れた訳だ。

これを使ってCやCmの和音を発生させたが歪が酷くて聞くに堪えない。方形波は奇数倍の高調波を多く含むのでこうなるのか。偶数倍の高調波の多いのこぎり波だったらまだマシだったかもしれないが・・・。

それだったらと、4つの波形をデジタル・サンプリングして合成する事に挑戦した。実際には各波形は1か0かの2値波形だし、16F18326のD/A変換器は5ビット(32値)なので、この間をどうマッピングするかが課題になる。単純に各波形に 8の重みを付けて足し合わせたらよいのか。或いはエネルギーは振幅の2乗に比例するから32の2乗を4等分した平方根の重み付けで良いのか。サンプリング周波数(割り込み)を4KHzで試してみた。インストラクションサイクルが250k/sなのでサンプリング当たり62命令しか実行できない。実際に動かしてみると割込みは1.8K回/秒程度が限界でとても追いついていない。サンプリング周波数を1KHzに落とすと500Hzまで再現できるはずであるが、やってみても音にならない。Fosc/4を少なくとも4倍の1MHzくらいまで上げる必要がありそうで、そうするとNCO1の精度が悪くなるし、全体的なパラメータの見直し・変更が必要になる。

そこでパラメータの大幅変更を伴うデジタル・サンプリングは後回しとし、とりあえず14ピンソケットを8ピンソケットに重ね、4つの発振器の信号をピンに取り出して抵抗ネットワークを空中に組んで音を出してみたところ、素人の耳で聞いた限りではまぁ使えそうである。


先ずはこれで進める事とした。

(その10へ続く)

 

2024年7月12日金曜日

出入り検知器をPCB化する(その8)メロディ生成2

メモリー容量の関係でPIC16F18313では単音しか発生できなかった。これをPIC16F18326に換えることで機能拡大ができそうである。とりあえず基板を改造してPIC16F18326とオーディオアンプが使えるテスト環境を作った。

14ピンのPIC16F18326はPIC16F18313用に用意した8ピンソケットに一部だけ挿し、残りは宙ぶらりんの状態にしている。

和音を発生させるためには複数の発振器が必要であり、そのためにNCO1以外に使えそうなのがTMR0、TMR1、及びTMR2であるが、TMR1は任意の分周比の連続波形が発生できないので除外する。 

TMR0は次のような構成である。

クロックは幾つか選択できるが、後の計算の結果HFINTOSC(16MHz)を選択した。プリスケーラは1/2^n(n=0..15)が選択できる。中央のカウンタ部には8ビットモードと16ビットモードがあるが、8ビットモードでないと任意の分周比が設定できない。8ビットの分周器を経た後はポストスケーラで1/1~1/16の分周比を選択できる。最後にD-FFで1/2に分周し方形波として出力する。クロック周波数、プリスケーラ、分周器をどう組み合わせれば目的とする周波数を発生できるかExcelで最も誤差の少ない組み合わせを求めた。その結果全音階に渡って誤差を数Hz以内に収めることが出来た。

TMR2(Timer2、Timer4、Timer6)は次のような構成である。

この回路をそのまま音階発生に使うには次の欠点がある。

  1. 入力のクロックがFosc/4固定である事。
  2. 分周比の設定が8ビットしかできない事。
  3. 音階発生にはポストスケーラが使えない(信号が取り出せない)。
  4. そのままではPIPで出力を取り出せない。
  5. 発生周波数(分周比)によってduty比が変化する。

1,のFoscは1MHzとしており、これは他のデバイスのパラメータとの関係で安易に変更できない。従ってクロックは250KHz固定になる。4,5,を解決するため次の様にCCPと組み合わせてPWM(パルス幅変調器)として使う。duty比は他に合わせて50%とするが、これを変化させて音量・音質に変化を持たせることもできるだろう。

プリスケーラと8ビットの分周器を組み合わせると最低音の2音階を除いて予定した全ての音を発生させることが出来る事が分かった(ただし最高音部は周波数誤差が50Hz以上に大きくなる)。何処まで使えるかは実際に耳で聞いて決めるが最高音部は周波数が高すぎて現実的には使わないだろう。

音階のパラメータと誤差を纏めると次表のようになる(一部省略)。NCO1はこういう目的には優れものだという事が分かる。

TMR2はTimer2、Timer4、Timer6の3個搭載されているので、後述のミキシングを考えてTimer2、Timer4の2個を使い、NCO1とTMR0と併せて全体で4音を発生できるようにした。

Timer6は テンポの作成に使う。

(その9に続く)

2024年7月5日金曜日

出入り検知器をPCB化する(その7)メロディ生成

 メインの基板が働く目途が立ったのでメロディ回路作成に取り組んだ。前回のメイン基板の写真で空いている8ピンICソケットにPIC16F18313を挿してURATインターフェイスを通じてメロディを発生させるようにする。

3声のシンセサイザを作りたかったが試したらPIC16F18313の少ないメモリー(プログラム=3.5KB、データRAM=256B)では1声の単音色が限界だった。 音階はクロックの1MHzを使ってNCO1(数値制御発信器、次図)で発生させた。

一般的には特定の周波数を発生させるには高い周波数のクロックを分周器で分周するのが普通であるが、分周器は整数分の1の分周しかできない(PLLを使った高級な分周器では分数の分周が出来るものもあるらしい)。一方音楽に使うためには12音階を数オクターブを発生させる必要がある。12音階は整数比ではない(1音階の周波数比は2の12乗根=約1.060)ため分周で正確に発生させることはできない。特に分周器を使って高い周波数を出す場合は分周比が少なくなり周波数精度が悪くなる。音楽では単に周波数精度だけでなく正しい音階からの絶対的な周波数のずれが唸りとなって聞こえるのでこれはいただけない。分周で精度を上げるにはクロック周波数を上げ分周比も大きくする必要があるが、Excelで計算してみるとA8まで周波数誤差を0.5Hz以内に収めるためにはPICのクロックが32MHzでも足りない。

一方NCO1は動作がやや複雑である。中心となる加算器で加算値を足し合わせ、加算器のオーバーフローを出力とする。NCO1では周波数が高くなるほど周波数精度が高くなる。それもクロックが低いほど加算数が大きくなり周波数精度も高くなる。クロックが1MHz、20ビット加算器を使うとA1の55Hzを出すための加算値は115、A8の7040Hzを出すための加算値は14764である。実際には出力を方形波とするためにオーバーフローを2分周して出力しており、その時の加算値が上の値である。周波数誤差で見るとNCO1では音階毎の周波数誤差の凸凹が少なく、全音階で0.4Hz以下に収まっており、高音になるほど周波数誤差が大きくなりがちな分周方式と違ってNCO1方式は音楽向きである。但しオーバーフロー毎に加算の余りが残るはずで、その分ビブラートがかかる筈であるが、その計算は後に回す。

NCO1の弱点は求める周波数を出すための加算数の計算がやや面倒な事であるが、これは予めExcel等で計算しておくことで対処できる。

結局、PIC18F27Q43からUARTで音階と音の長さを纏めた1行を送ることでメロディを奏でさせることとした。音階はMIDIの音階コードを流用して16進数で表す。音の長さは簡略化して1文字で表す。例えばA4(440Hz)の四分音符は「455」と表現する。1音を3バイトで表すのでRAMのうち141バイトを使って47音(休符を含む)まで演奏できる。

音階はNCO1で簡単に実現できた。音の長さはメモリー容量の関係でタイマー割込みは使わず1msの遅延を必要回数繰り返すことで実現した。この繰り返し回数は拍数(BPM)から簡単に算出できる。ON/OFFの割合を変えることでスタッカートやレガートも出来る。一応次の様に文字を割り当てた。i や j や l を使っていないのは視覚的に判別しにくいからである。

一方、使い慣れたはずのUARTインターフェイスが全く使い物にならず苦労した。送信は出来るが受信が全くできない。ソフトやハードをあれやこれやチェックしても全く動かない。さんざん悩んだ挙句に最終的に2番ピン(RA5)が死んでいることが分かり、これをPPSの機能で空いている6番ピン(RA1)に振り替える事で解決した。この事は、PICを交換しても駄目だったのでロット不良か或いはまがい物を買った可能性がある(このPICは秋月で買ったんだけど・・・※1)。

UARTインターフェイスは AquesTalk pico と似たようなものにした。

次のバージョンでは和音を出したいのでPIC16F18313の上位互換のPIC16F18326(14ピン、プログラム=28KB,データRAM=2KB)、或いは別のPICを使ってできないかあれやこれや検討している。しかしPICに搭載されているNCOはせいぜい1個であり和音には程遠い。(複数個ある)タイマー1は使えないか?またPWMを使ってエンベロープ機能も搭載できないか?PWNはタイマー2と組み合わせる必要があり、タイマー2のクロックにはシステムクロック/4しか使えないが要求を満たせるか、また音の合成はどうするか?など検討している。 

※1:後日別のPIC(16F18326)に変更したが、相変わらずRA5が使えないのでPIC16F18xxxのタイプのPICの欠陥かもしれない。

(その8へ続く)

2024年6月21日金曜日

出入り検知器をPCB化する(その6)

発注後1週間と1日で JLCPCB から3作目の基板が届いた。指定した最も安い配送方法 OCS NEP のうちOCS は ANA グループの会社名らしい。一方 NEP というサービスが何なのかはよく分からなかったが国内はネコポスでは配達された。毎度使って何も問題は無いので良しとしよう。

早速組み立ててデバッグ中のソフトを書き込んだ。

新しい部品を基板に押し込む面積を稼ぐためPIC(18F27Q43)のピンの割り当てを変更したが、それに伴うバグの侵入が幾つかあり、虫取りが進行中である。

 今回は音声合成ICにこれまでのATP3011ではなくATP3012を使った(半田ジャンパーを切り替えることで双方に対応するよう設計している)。しかしATP3012は上手く動かない。仕方がなくATP3011に戻すと問題なく動く。そこでテスト環境を作ってATP3012を単体でテストしたが特に問題ないようだ。怪しいのは16MHzのセラロックか?オシロスコープでセラロックを観測するとSLEEP状態では発振停止しておりSLEEPが解除されて発振が安定するまで1ms程度かかる事が分かった。即ちSLEEP解除後に数msの遅延を入れる事で問題は解決した。

 ここまで来て、一応前バージョンと同じ機能まで確認できたので、これから新しく追加したEEPROMを使ったソフトを開発する。

余談だが、半田ジャンパーが上手く繋がらなかったので半田ごての温度を270℃から260℃に変更したら簡単に繋がった。


10℃の差がハンダの粘性に大きく影響しているようだ。この半田ごて(PX-280)はなかなか優れものである。値段は高いが思い切って買ってよかった。

(その7へ続く)


2024年6月12日水曜日

出入り検知器をPCB化する(その5)

 ここで改めて何を作っているか要求項目を列挙すると、

  1. 人の出入りを検知して声でメッセージ(いらっしゃいませ、ありがとうございました、等)を出す。
  2. 電池で稼働できること(低消費電流)
  3. 専門知識が無くても使える事
  4. 気圧の変化を感じて何かのメッセージを喋る。
  5. 4.を処理するため時計が必要。
  6. 5. の機能を使って時刻に対応したメッセージを喋る。 
  7. 外部制御端子(発声時にONするなど)
  8. 電源電圧の監視と警告機能

といった ところであるが、これに付随して次のような機能を追加した。

  •  時刻、気圧(補正値)の設定
  •  喋る内容を設置環境に応じて変える(家庭用、店舗用など)。
  •  時刻、曜日を指定して喋る機能(毎日、週末、週末以外)
  • 定期的に時刻と気圧、気圧変化傾向を喋る機能
  •  夜間モード(夜間は発声抑止、LEDを暗くする、など)
  •  LEDの明るさを設定する機能
  •  

また次のような機能も実装を予定している

  •  時には声ではなくメロディを流す。

 これらを実現するため、コア部分には低消費電力のPICを使うが、PICに足りない機能は別途実装する。精密時計、気圧計、EEPROMなどである。

 これらのうち大部分はこれまで説明した内容であるが、LEDの明るさを制御するにはPWM(パルス幅変調)を使いデューティ比を変化させる。CLC(プログラム可能なロジックセル)を使ってこれとLEDのON信号とのANDをとったものをLEDに与える。この理屈は簡単であるが実際にこれをPICの機能を組み合わせて実現するには面倒臭いプログラミングが必要である。PIC18F27Q43には8つのCLCを内蔵するがそのうち4つをLED制御に使った。また3つ内蔵するPWM(パルス幅変調器)のうち2つを使って、1つを明るさの調整に、他をLEDのブリンク(点滅)に使った。

CLCを使う上での第一の関門はInput Data Selection on Gates である。ここではPIC内部の数十ある信号の中から4つ、及びその反転信号を選択して取り出すことが出来(つまり8つの入力信号ができる)、4つの出力はそれぞれ8つの入力信号の中から任意の信号を選んでORをとりその正論理又は負論理を取り出すことが出来る。つまりNAND、OR、それにロジック1の何れか及びその反転出力(AND,NOR,0)を実現できる。この出力が上図のkxg1~4である。

次にLogic Functionではあらかじめ設定された4入力の8つの回路(機能)のうちの1つを選択できる。そのうち4-AND、AND-OR、OR-XORを使った。それぞれ負論理で使うと4-OR、OR-AND、AND-XORとして機能させることもできる。次図は8つの機能の内の最初の4つである。

これらを使って赤LEDは次の様に点灯させている(図は分かり易いよう簡略化している)。

このなかのXOR(OR-XORを除く)の2つの入力は等価ではなく上が何らかの信号入力、下がソフトウェアでセットするビットである。即ち輝度はCLC2でBrightnessのPWMとANDを取っているが、警告信号は赤の連続点灯、電圧低下は赤のブリンクで示し、警告と電圧低下が重なった場合にはブリンクの明と暗の時間比を逆転させている。ここで信号名の前の「/」は負論理を表す。

(その6へ続く)

2024年6月10日月曜日

出入り検知器をPCB化する(その4)

 その3に引き続き次のバージョンに向けた開発を進めている。SPIインターフェイスをUARTやI2Cインターフェイスに変更するため、基板を少し手改造した。具体的にはボイスシンセサイザはUART接続に、気圧計、時計、それに新たに追加するEEPROMはI2Cインターフェイス接続に変更した。

 また音階を出したいという要望があるので送信部と同じ8ピンのPIC16F18313を使いUARTで通信する事を前提に回路を設計した。音階はICを追加しないでPIC18F27Q43自体でも対応できると思うが割り込みを使っている関係上不規則に音にブツブツとノイズが入りそうなのでとりあえず安全策をとった。I2Cインターフェイスのデバッグでは安価(\1.5K程度)な中華製ロジックアナライザが活躍した。これはPulseViewというフリーソフトを使うと単にデジタル信号を見るだけでなくプロトコルの解析もできるので非常に便利で有効であった。次図は気圧センサー(LPS-25HB)の0x0F番目のレジスタにあるwho am i 値(0xBD)を読み出しているところ。

 I2Cインターフェイスで一番の問題だったのは時々クロックがローレベル(スレーブからの応答待ち)になったままハングアップする事である。一度ハングアップするとソフトの再起動では解決せず(I2Cデバイスにはリセット端子も無いため)一度電源を落とすしかない。 この点はスレーブからの応答待ちのないSPIが優れており、これに戻す事も検討した。そんな中でEEPROMの説明の中にソフト的にI2Cをリセットする方法が書かれていたのでこれを実装した(下図)。EEPROMはきちっとリセットしておかないと(内部状態が中途半端な時にコマンドを送ると)不正な書き込みが起こる事があるらしい。これでハングアップの問題が解決したのか分からないが必要な機能だろう。

 I2Cインターフェイスのデバッグが済んだ所でKiCADで3世代目のPCBの設計を始めたが、色気を出して最新の KiCAD 8.0 をインストールしたのが運の尽き。バージョン7.0で使っていたフットプリントの一部が使えなくなってしまった。が、いろいろ弄っているうちに何とか使えるようにできた。理由は不明。

 これを使って新しい基板を設計した。基板のサイズは前と同じである。

 前バージョンよりICが2個増えているのでこれらを詰め込めるか心配し、PICのピンの割り当てを配線がしやすいように変更したが実際にパターンを作ってみたらまだ面積に余裕があるようである。いざとなったら表面実装部品を裏面に配置する裏技もある。

 ボイスシンセサイザICにはこれまでATP3011を使っていたが、知らずに(ATP3011だと思って)ATP3012を買い、音が出なくて色々調べているうちにATP3012である事を知った。同時に両者が一部ピンコンパチブルではない事、またATP3012はサンプリング周波数が高く音質が良い事、外付けの発振子(セラミック発振子)が必要な事を知り、新しい基板は両者に対応できるよう設計しなおした(切り替え用のはんだジャンパーは基板裏面に配置した)。

 (その5へ続く)