Intel社CPLDの立ち上げ2
FPGA、CPLDに関する一連の記事ではVHDLについての詳細な記述を展開するつもりはなく、現在のIntel、Xilinxそれぞれの環境における操作フローを主に解説する予定である。
前回の記事 http://d.hatena.ne.jp/kazima/20180419 では、Intel MAX-2 CPLDに対してプッシュボタンによるLED点灯というとても簡単な実例を示した。
FPGAのSDKは進歩が速く、web上の解説が古くなりやすい。前回の記事の流れからもわかるように、現在のIntel SDKは操作性が劣悪であり、初見ではどこを触ればよいのかわからないGUIは使用法の習得効率の悪化を招いていることが理由である。
今回は取り上げるのは以下の3点とする。
- 前回のプログラム実装で宿題となった空きピン処理
- 手を付けなかったタイミング制約
- process文による簡単な同期処理
- 空きピン処理
本来、FPGAデバイスの空きピン処理とは配線を接続しないハードウェアピンの処理を意味する。(グラウンドにつなぐのかプルアップするのかなど)その設定は
Assignments > Device GUIから Device and Pin Options ボタンを押し、Unused PinsのReserval all unused pinsで調整可能である。
本記事でいう空きピン処理は、EVMを使用する上で不要な7セグLEDやブザーをどのように処理するかを意味している。これは、既成のEVMはすでに様々な部品が接続されているので、出力ピンがグラウンドに落ちるとアクティブ状態になるときがあることが理由である。なお、本来の「空きピン処理」によりプルアップすることにより意図しないアクティブ状態は防げるが、基板上に配線したピンはVHDファイル内に記述するべきという思想からいって、VHDファイル内にすべて記述することにする。
ピンヘッダ以外の出力ピンは、LED、7セグLED、ブザーのみなので、サンプルファイルのようにport、出力状態を記述する。
- タイミング制約
Intel環境におけるタイミング制約は、.sdcファイルに記述する。タイミング制約は内部でクロックとして使用するピンや出力ピンに要求されるスピードを規定し、内部処理が間に合わないことをコンパイル段階で察知することができる。(出力ピンに対して記述するものであり、入力ピンに対しては記述しない)
sdcファイルはテキストエディタで編集して拡張子を変えて作ることもできるが、プロジェクト内でsdcファイルを生成するには 新規作成 > Other Files > Synopsys Design Constraints File を選択する。
sdcファイルの中には、外部端子に接続されている各ピンのクロックの周期をns単位に変換して記述する。のC-M240基板には50MHzの水晶が実装されているので、クロックは20ns周期で制約することになる。
sdcファイル内で右クリック > Insert Constraint > Create Clock を選択すると、以下のようなGUIが表示され、ある程度GUIで何を記述すればよいかがわかるようになっている。
GUIで指定した結果、あるいはテキスト編集で
create_clock -name LED0 -period 200.000 [get_ports {LED0}]
と記述したとすると、LED0という名前で(この部分は自由につけてよい)200ns周期での駆動が要求されるという制約をつけ、LED0という名前の出力ポートに紐づくという意味になる。
このとき、get_portsのtargetとして定義されている変数(今回はLED0)がVHDLファイル内とPin Plannerで定義されていないと、以下のような警告メッセージが出て正しく制約できていないことが示される。
Warning (332174): Ignored filter at t1.sdc(1): LED0 could not be matched with a port
Warning (332049): Ignored create_clock at t1.sdc(1): Argument
ここで、周期にデバイススペックが対応していないほど短い値(2ns)などを記述すると、タイミングが間に合わないことを示す以下のような警告メッセージが現れる。
Critical Warning (332148): Timing requirements not met
また、タイミング制約を記述していない出力ピンがあると、コンパイル後のタイミング解析部分で警告が表示される。Lチカ程度の処理であればタイミング制約は記述しなくても問題ないが、実際にMHzオーダーの高速信号を取り扱う上では、きわめて重要となる。
今回は、各出力ポートと操作用クロックとして使用するCLK、PB1に対して以下のように記述する。C-M240には50MHzの水晶が実装されているので、水晶に接続されているCLK端子には20nsを指定し、それ以外のLED駆動クロックには1us(1MHz)を指定した。(ファイル名はt1.sdcとした。混乱防止のためにsdcの名前はプロジェクト名と合わせるとよい。)
create_clock -name CLK -period 20.000 [get_ports {CLK}]
create_clock -name PB1 -period 1000.000 [get_ports {PB1}]create_clock -name LED1 -period 1000.000 [get_ports {LED1}]
create_clock -name LED2 -period 1000.000 [get_ports {LED2}]
create_clock -name LED3 -period 1000.000 [get_ports {LED3}]
create_clock -name LED4 -period 1000.000 [get_ports {LED4}]
create_clock -name LED5 -period 1000.000 [get_ports {LED5}]
create_clock -name LED6 -period 1000.000 [get_ports {LED6}]
create_clock -name LED7 -period 1000.000 [get_ports {LED7}]
create_clock -name LED8 -period 1000.000 [get_ports {LED8}]create_clock -name LED4_1 -period 1000.000 [get_ports {LED4_1}]
create_clock -name LED4_2 -period 1000.000 [get_ports {LED4_2}]
create_clock -name LED4_3 -period 1000.000 [get_ports {LED4_3}]
create_clock -name LED4_4 -period 1000.000 [get_ports {LED4_4}]
create_clock -name LED4_5 -period 1000.000 [get_ports {LED4_5}]
create_clock -name LED4_6 -period 1000.000 [get_ports {LED4_6}]
create_clock -name LED4_7 -period 1000.000 [get_ports {LED4_7}]
create_clock -name LED4_8 -period 1000.000 [get_ports {LED4_8}]create_clock -name LED4_A -period 1000.000 [get_ports {LED4_A}]
create_clock -name LED4_B -period 1000.000 [get_ports {LED4_B}]
create_clock -name LED4_C -period 1000.000 [get_ports {LED4_C}]
create_clock -name LED4_D -period 1000.000 [get_ports {LED4_D}]
create_clock -name LED4_E -period 1000.000 [get_ports {LED4_E}]
create_clock -name LED4_F -period 1000.000 [get_ports {LED4_F}]
create_clock -name LED4_G -period 1000.000 [get_ports {LED4_G}]
create_clock -name LED4_DP -period 1000.000 [get_ports {LED4_DP}]
用意したsdcファイルは、Assignments > Settings >TimeQuest Timing Analyzer の中で以下のように指定あるいは読み込みが可能である。
- 同期回路
今回のサンプルファイルには、まず論理演算子による2つのボタンが同時に押されたときにLEDが光る回路を加えた。
architecture文の中の信号線の定義
signal tmp : std_logic;
およびAND計算と代入
tmp <= NOT(NOT(PB0) AND NOT(PB1));--2つのpinのAND処理をsignalに割り当てる
により、実装している。この部分は、コードを読めば直感的に読み取れるであろう。
さらに、入力クロックに応じて駆動される論理回路を同期回路という。VHDLではprocess文で表現し、クロック信号の立ち上がり、立ち下りのタイミングで従属的に同期している論理回路を駆動する。今回は、プッシュボタンPB1のオンオフ動作を駆動クロックの代わりとし、ボタンを押した回数を数えるカウンタを実装する。クロックの立ち上がりのタイミングで駆動するには
if(PB1'event and PB1 = '1') then
とif-then文で記述し(記述法にはrising_edgeという書き方もある)、その中にボタンを押しただけバッファに値を加算する処理を入れれば、2進カウンタを構築できる。バッファとなる信号は、architecture文内にsignal文で定義する。今回は8bitカウンタを
signal counter : std_logic_vector(7 downto 0);--8bitカウンタ
と定義する。
さらに、このカウンタに対してPB1に対する同期カウンタをprocess文で記述すると、プッシュボタンがactive lowであることを考慮してNOT演算による反転を加え、以下のように記述する。
process(PB1) begin
if(PB1'event and PB1 = '1') then
counter <= counter + NOT(PB1);
end if;
end process;
この記述は直感的に理解しやすいが、ひとつ大きな問題がある。論理回路はロジックゲートの組み合わせで実現するので基本的に信号入力部には0か1の状態しか取れず、C言語プログラムのように気軽に加算や減算をすることはできない。そこで
use ieee.std_logic_unsigned.all;
という記述をVHDLファイルの先頭に加えると、あたかもC言語プログラムのような四則演算が扱えるようになり、"+"演算子をvhdファイル内で使用できる。
以上の操作で今回の課題3つを適用したt2.vhdとt1.sdcが生成できる。t2.vhdの全貌は以下のようになる。
(ボタンのAND操作とカウンタはどちらもLED1への出力に設計しており、これらのアルゴリズムの違いは今回の本質ではないので、コンパイル時に切り替えて対応する)
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;entity t2 is
port (
PB0 : in std_logic;--プッシュボタン入力0 DEV_CLR
PB1 : in std_logic;--プッシュボタン入力1 K1
PB2 : in std_logic;--プッシュボタン入力2 K2
PB3 : in std_logic;--プッシュボタン入力3 K3
PB4 : in std_logic;--プッシュボタン入力4 K4LED1 : out std_logic;--LED出力(active low)
LED2 : out std_logic;--LED出力(active low)
LED3 : out std_logic;--LED出力(active low)
LED4 : out std_logic;--LED出力(active low)
LED5 : out std_logic;--LED出力(active low)
LED6 : out std_logic;--LED出力(active low)
LED7 : out std_logic;--LED出力(active low)
LED8 : out std_logic;--LED出力(active low)
LED4_1 : out std_logic;--7seg LED出力(active low)
LED4_2 : out std_logic;--7seg LED出力(active low)
LED4_3 : out std_logic;--7seg LED出力(active low)
LED4_4 : out std_logic;--7seg LED出力(active low)
LED4_5 : out std_logic;--7seg LED出力(active low)
LED4_6 : out std_logic;--7seg LED出力(active low)
LED4_7 : out std_logic;--7seg LED出力(active low)
LED4_8 : out std_logic;--7seg LED出力(active low)
LED4_A : out std_logic;--7seg LED出力(active low)
LED4_B : out std_logic;--7seg LED出力(active low)
LED4_C : out std_logic;--7seg LED出力(active low)
LED4_D : out std_logic;--7seg LED出力(active low)
LED4_E : out std_logic;--7seg LED出力(active low)
LED4_F : out std_logic;--7seg LED出力(active low)
LED4_G : out std_logic;--7seg LED出力(active low)
LED4_DP : out std_logic;--7seg LED出力(active low)
CLK : in std_logic; --XTAL入力 50MHz
BUZZER : out std_logic --BUZZER出力(active low));
end t2;architecture rtl of t2 is
- 内部信号線の定義
signal tmp : std_logic;
signal counter : std_logic_vector(7 downto 0);--8bitカウンタbegin
--プッシュボタンカウンタ
process(PB1) begin
if(PB1'event and PB1 = '1') then
- counter <= counter(6 downto 0) & CLK;
counter <= counter + NOT(PB1);
end if;
end process;
tmp <= NOT(NOT(PB0) AND NOT(PB1));--2つのpinのAND処理をsignalに割り当てる
- LED1 <= tmp;
LED1 <= counter(7);--カウンタの最上位ビットを出力する
LED2 <= counter(5);
LED3 <= counter(4);
LED4 <= counter(3);
LED5 <= counter(2);
LED6 <= counter(1);
LED7 <= counter(0);
LED8 <= counter(0);
LED4_1 <= '1';
LED4_2 <= '1';
LED4_3 <= '1';
LED4_4 <= '1';
LED4_5 <= '1';
LED4_6 <= '1';
LED4_7 <= '1';
LED4_8 <= '1';LED4_A <= '1';
LED4_B <= '1';
LED4_C <= '1';
LED4_D <= '1';
LED4_E <= '1';
LED4_F <= '1';
LED4_G <= '1';
LED4_DP <= '1';
BUZZER <= '1';
end rtl;
以上を実装すると、意図せず7セグLEDが光ったりブザーが鳴ったりするのを防ぎ、さらにボタンを押した回数によるLED発光が可能になる。
上に示したt2.vhdには、次回の伏線として以下のコメント文を残しておいた。
- counter <= counter(6 downto 0) & CLK;
次回はシフトレジスタの記述法(シリアル-パラレル変換)について述べる。
空きピン処理、クロック同期処理、カウンタ作成、シフトレジスタ作成は論理回路設計の最も基本的な部分なので、仮に既存IPを組み合わせて使う設計者も読み書きできるようにしてくとよい。
Intel(Altera)社CPLDの立ち上げ
前回記事のように、FPGAは(Vivadoに対応している世代の)Xilinx社のチップが入手しやすいのに対し、CPLDはIntel(旧Altera)社製のチップの方が入手性がよく、またEVMも安価に入手可能である。
現在のIntel社の無償開発環境はQuartus Primeであるのに対し、web上の情報は旧Quartus IIの入門記事が多くGUIの変更などに追従できていない。
現在のHDL開発環境は、既存IPの充実によりVHDLの文法をマスターするよりも開発環境の設定を覚える方が実際は時間がかかる状況になっているので、現時点での最新のGUI設定方法を紹介することにした。今回は、Intel社の環境の入門となる押しボタンによるLED発光(いわゆるLチカ)の方法を記すことにする。
- 開発環境
OSはwindows10、SDKにはQuartus Prime Light Edition v17.1を使用する。
EVMは、MAX-2デバイスが搭載されているC-M240を使用している。他のEVMを使用しても基本的な使用法は同じであり、本記事を元にピン番号などを調整すれば実行可能である。
RTL言語はVHDLを使用する。
ダウンロードケーブルは、ヤフオクで入手したUSB Blaster互換品(\1000程度)を使用した。
本記事は、アルティマ社(現マクニカ・アルティマカンパニー)の先行記事
https://service.macnica.co.jp/library/113049
を元にしており内容はほぼ同一ではあるが、こちらの記事ではGUIのどのボタンを押すのかなどが不明なので、操作面を重視して解説する。
今回はひとまず自分の書いたVHDLでEVM上のLEDを光らせることを目的とするので、不要ピンの処理やタイミング制約には触れず、一連の実装プロセスをレビューする。
- プロジェクトの新規作成
GUIメニューの新規作成から、PJ名を指定してEmpty projectを選択する。
Project templateを選択しても、デバイスファミリーからMAX-2を選択できないので今回はEmpty projectを選択する。今回はAdd Filesはスキップし、次にデバイスを選択する。以下のGUIのようにファミリーパッケージを選択し、本EVMに対してはMAX-2の候補のうちEPM240T100C5を選択する。
今回はこれ以上の設定は不要であり、finishボタンを押す。
- VHDLファイルの作成
論理回路を記述するVHDLファイルを記述する。
新規作成>VHDL fileを選択すると。Quartus PrimeのGUI上に新しいエディタが生成されるので、今回は以下のように記述する。
デバイスのportとしてプッシュボタン、LED配線をひとつずつ配置し、ボタンを押すとLEDが光るようにする。
library ieee;
use ieee.std_logic_1164.all;entity LED1 is
port (
PB0 : in std_logic;--プッシュボタン
LED0 : out std_logic--LED
);
end LED1;architecture rtl of LED1 is
begin
LED0 <= PB0;
end rtl;
VHDLファイルを用意したら、続いてコンパイルボタンでプログラムをコンパイルする。
コンパイルを終えたら、Pin Plannerでハードウェアピンを各ポートにアサインする。
ピンをアサインしてからコンパイルするのではなく、コンパイルをしてからピンをアサインする。この部分がとても間違えやすく、プログラムを書いた段階でPin Plannerを開いてポートが見つからないと焦ることが多い。コンパイルによって初めてポートがPin Plannerに表示される。
C-M240には各ボタンのピン番号がシルク印刷されているので、回路図を読まなくてもピン番号をアサインできる。
以下の図では、先行してEVMに実装されている様々なピンまで定義しているが、今回のプログラムでは2つのピンのみが表示されることになる。
- タイミング制約の記述
本来はここで高度な回路設計では重要なタイミング制約を記述するが、今回の回路は制約を要しないほど単純なので、今回はスキップする。
- 二回目のコンパイル
各種制約を設定したあと、二回目のコンパイルを実行する。これにより、ピン情報を含めた論理回路が合成される。
- 論理シミュレーション
今回はシミュレーションも不要につき、スキップする。
- 回路プログラム
Programmerで回路をプログラムする。
その際は、あらかじめJTAGプログラマのダウンロードケーブルをUSB端子に接続しておく。
ダウンロードケーブルのドライバは勝手にはインストールされないので、デバイスマネージャからQuartus Primeのインストールフォルダ内のdriverフォルダを指定してインストールしておく。また、EVMの電源はオンにしておく。
Programmer内でHardware Setupの部分にUSB Blasterが認識されていることを確認し、pofファイルがリストされていることを確認したら、Program/Configureのチェックを入れてStartボタンを押す。
成功すると、Progressが100%になり色が緑色になる。
- 実装確認
指定したプッシュボタンを押してLED(下図の緑丸部分)が光ることを確認する…が、今回の回路ではボタンを押すとLEDが消えることになる。また、プログラムに定義していない他のLEDが光っており、常時ブザーが鳴っている。これらは各ピンがプルアップされており、active lowで動作していることを示している。
次の記事では、これらのピン状態を調整し、もう少し複雑な論理回路を設計する。
Xilinx社FPGA Artix-7の立ち上げ
現在、Digilent社のcmod A7というFPGAボードを入手して、動作テストをしている。
本ボードはDigilent社のサイトに立ち上げの手順Cmod A7 Programming Guide [Reference.Digilentinc]
が示されているが、2018年現在ではすでにXilinx VivadoのGUIが変化しており、この通りに実行
しようとしても項目が見当たらない部分が多いので、本ページに手順を示す。
本ページの内容は、Vivado 2017.4の仕様に基づいている。Xilinx社はGUIの更新が激しく、本ページの内容もいつまで追従できるかわからない(特に、hardware managerあたりは改善の余地が大きく、現在のままで放置されるとは考えにくい)
ことには注意を要する。
- プロジェクトの作成(今回は、Digilent社のページに掲載されているdemo.zipを使用する)
Vivadoを立ち上げ、Quick Startからcreate Projectを選択>RTL projectを選択する。
Add Sources のGUIから、btn_led.vを追加する。
Add Constarins のGUIから、CmodA7_B.xdc を追加する。
パーツリストで Artix-7 cpg236-1を選択する。デバイスの区分で絞り込めるので、以下の図に示した内容を指定することで適切なパーツが指定できる。
以上の操作でプロジェクトの作成が完了する。
- プロジェクト内での設定
※マスターブートに使用するSPI FLASH書き込みの設定(ここは元サイトに画像があるので、画像掲載は割愛する)
tool > edit device propertiesを開き、
- general >Enable Bitstream Compression をTRUEにする(要求容量が減り、書き込み時間が少なくなる
- Configuration Rate MHzを33にする
- Cofiguration ModesでMaster SPIx4をチェックする
- プロジェクト内の各ステップの実行
すでにサンプルプログラムのソースは用意されているので、まずRun Synthesis を実行し、次にRun Implementationを実行する。
Runが終わったらopen designを選択する。(これは必須操作ではないが、今回は段階的に実行する)
- Generate Bitstreamを実行する
Runが終わったら、Generate Memory Configuration File を選択してOKを押す。この部分の操作により、マスターブートに必要なSPI FLASHに書き込むコンフィギュレーションファイルが生成される。
- Write Memory Configuration File内の設定
Generate Memory Configuration Fileの実行により、コンフィギュレーションファイルの生成GUIが立ち上がる。
※この部分の流れがわかりにくいので、将来GUIが変更される可能性が高い。
(必須ではないが)FORMATは BINを選択する。
- メモリ設定ははCustomではなく、Memory Partを選択して n25q32-3.3v-spi-x1_x2_x4 を選ぶ。
...をクリックするとパーツ選択GUIが立ち上がるので、以下のように設定して対象を絞り込み、3.3V品のマイクロン社製FLASHを選択する。
- Filename はこれから保存するファイルの名前を指定するので、自由につけてよいが、元のbitファイルと類似した名前が望ましい。
Options内の設定を追加する。
- SPIx4 を選択する(x4で高速化)
- Load bitstream filesをチェックする
この操作でファイル指定ウインドウが選択できるようになるので、Bitfileの部分にgenerate Bitstreamで作った.bitファイルを指定する。生成されたファイルは、プロジェクトフォルダ内のPJ名.runs>impl_1にある。
- ターゲットデバイスの接続
- この段階まで来たら、USBケーブルでターゲットデバイスをつなぐ。念のため、デバイスマネージャで新しいCOMを認識していることを確認する。
- プロジェクトGUIのProgram and Debug内から、Open Target>認識しているデバイスを選択する。ローカルホストサーバーを立ち上げ、現在接続中のcmod A7を選択する。
※手順が間違っていた、あるいはUSB端子との接触が悪かったからか、この作業でブルースクリーンが頻発した。デバイスをつなげる前にctrl+sでプロジェクトを保存しておくと安全である。
この段階でプログラムをJTAGで書き込むことも可能なので、hardware manager内でデバイス名を右クリック>Program Deviceを選択し、プログラムの動作をテストしておくとよい。
- コンフィギュレーションメモリの登録
- Open Hardware Manager > Add Confgiuration Memory Device を選択し、Generate Memory Configuration Fileで設定したのと同じSPI FLASHをHardware Managerに登録する。
- Hardware Managerに登録されたSPI Flashを右クリックし、Program Configuration fileを選択する
先ほど作ったBINファイルを選択して、Apply > OK で書き込まれる
- マスターブートのチェック
Hardware Manager内のxc7a35t_0を選択して右クリック >Boot from Configuration memory deviceを選ぶと、これまでの手順を正しく実行していれば、SPI FLASHからFPGAがブートする。demoのプログラムでは、ボード上の2つのボタンを押すことで2つのLEDがそれぞれ点灯する。
以上がデモプログラムのプロジェクト作成、各種設定、デバイスへの書き込み、動作チェックの一通りの手順である。
一度プロジェクトを作って動作確認できれば、プロジェクトを流用してSPI FLASHなどの設定を毎回する必要がなくなるので、今後の実装の手間が省ける。
本プロジェクトをスターと地点とし、次は内蔵クロックを使用する例を実装する予定である。
オルゴール時報つき時計の製作
Arduinoの学習の一環で何か実用的なものを作れないかと考えたところ、自動演奏楽器による時報つき時計を考案した。時報つき時計はいわゆる目覚まし時計であり、スマートフォンのアラームなどでも対応可能である。今回は、オルゴールをステッピングモーターで回し、一周ごとを時報として使用することをコンセプトにした。オルゴールは連続回転を前提としたゼンマイ駆動の楽器であり、本来は一周ごとに止める方法は想定されておらず、一曲のはじめと終わりを正確に毎回鳴らすには、角度指定による回転が必要となる。(PWM音再生による電子オルゴールならば実現は容易であるが、モーターを使って実物のオルゴールを回す自動演奏楽器を作るところがポイントである。)
部品構成は以下の通りである。それぞれ注意点があるので、各項目で記述する。
- Arduino Nano(互換品)
eBayで購入した。互換品につきUSBシリアル変換チップがCH341SERという廉価品に変更されており、ドライバインストールが必要である。開発環境はwindows10であったが、旧来のドライバは問題なく動作した。
- Tiny RTC DS1307基板
eBayで購入した。I2C接続でリアルタイムクロックを取得できる基板であるが、本基板は電子回路設計が間違っており、そのままではバッテリーバックアップがまともに機能しない。
"Tiny RTC I2C Module" issue
で議論されているように、回路構成が間違っていてVBATから十分な電位が供給されないので、VCCを外した途端にRTCの電源が落ちてしまい、バックアップが切れて時刻がリセットされる。本基板については、web上に様々な使用記事が掲載されており、例えばLIR2032用Tiny RTC I2C モジュールをCR2032用へのカスタマイズ方法 | 気分はメイカーズなど、充電可能なLIR2032ではなくCR2032を使用するという記事が散見される。どういうわけか、web上の本基板についての日本語記事を探してもバッテリーバックアップが動作しないといった指摘がなかった。世間的には、基板を接続して動いた動いた!でそれ以上テストするのをやめてしまうのかもしれない。本質的には、バッテリーで動作するようにすること自体が大切であり、どの電池を使うかは二の次である。クォーツ駆動のリアルタイムクロックは電気で時刻を保持するものなので、AC通電していないときに時刻を失ってしまっては、時計として実用に耐えない。故に、本回路で充電が不可能であれば、VCCで駆動するのではなく、常にバッテリー駆動するような設計にするのが適切な使用法である。
まずVBATとバッテリーを絶縁し、さらにI2C通信端子のプルアップ電位がチップの供給電位と合うように、VCCとバッテリーの+端子側を直結する。このようにしないと、電源電位は3.3Vなのにプルアップ電位は5Vなどと、バイアス電流が流れすぎる意図しない構成になる。具体的には、D1、R4、R6の3つの部品を取り外し、R5の基板内側とD1の基板内側のランドをショートする。
- 双方向ロジックレベルコンバータ
千石電商で購入した。RTC基板がCR2032の3.3V駆動でArduinoのディジタルピンは5V駆動なので、正規動作させるには、レベルコンバータを挟む必要がある。例えば5V Arduino+3.3V I2Cデバイス+双方向ロジックレベルコンバータを試す - Qiitaに使用例がある。実験ではレベルコンバータなしでも特に問題なく動作したが、意図しない高圧がRTCチップのポートにかかることで壊れるリスクがあるので、レベルコンバータ基板を挿入した。
- ステッピングモーター28BYJ-48基板
Amazon JPで購入した。ステッピングモーターの入門品として有名な製品で、Arduinoのディジタルピンに直接ドライバ基板(という名前のトランジスタアレイ基板)を接続して使用する。例えば"ステッピングモーター (28BYJ-48)"に作例がある。本モーターは電源の配線とディジタルピンを4つ消費する必要があり、トルクも弱いので、本格的なモーター制御には向かないが、小型オルゴールを回す程度であれば十分に実用になる。
専用のステッピングモータードライバが用意されていないので、自分で制御ルーチンを作る必要がある。そういった状況を踏まえ、制御にはCheapStepper.hのライブラリを使用した。このライブラリにはモーター動作を止めたときの終了処理が入っておらず、ディジタルピンのどれかに電圧がかかりっぱなしになるので、setStop関数として全停止状態を定義し、モーター動作が不要なときは全制御ピンにlowを出力するようにした。
- 8桁7セグLED
Amazonで購入した。7セグLEDは定番の時計表示デバイスである。RTCから得られる時刻情報に対して、西暦年、月、日、曜日、時、分、秒をすべて表示するのに、8桁のLED基板2つで必要十分な表示領域が確保できた。購入品は月表示の2桁目に不良があり、右下のバーが点灯しないようであったが、さして重要な表示部ではないのでそのまま使用している。7セグLEDを使用する作例はArduino+MAX7219で8桁7セグLEDを簡単に扱う - Qiitaなどにある。
LEDドライバMAX7219を使用すると、基板を8枚までカスケード接続できるようになる。カスケード接続の番号は、ドライバのインスタンスを定義するところでデバイスIDを指定する。ID番号が大きくなるほど、遠い方のデバイスが指定される。(なぜかこの情報も、なかなか見つからなった)
- 「ウェストミンスターの鐘」オルゴール
【MM801-FD】ウエストミンスターの鐘 ♪試聴無料 18弁ムーブメント オルゴールギャラリーから購入した。まずゼンマイを取り外したところ、モーターをオルゴールの回転軸に直結することは難しかったので、ゼンマイのシャフトにモーターを接続し、ピニオンギヤを介して回す方法を選択した。シャフトとの接続ははじめ強力接着剤を使用したが保持力が足りなかったので、圧着端子を挿入してかしめることで、機械的に接続した。オルゴールの動作原理上、ドラムの突起に響板が引っ掛かるときに強力な制動がかかり回転速度が遅くなるので、響板が引っ掛かる深さがなるべく浅くなるように調整が必要であった。調整を終えても今回使用したモーターではトルクが不足気味ではあったので、もう少し強いモーターを使用する方が滑らかな演奏が可能となりそうである。
- 時刻リセット用トグルスイッチ
ArduinoからRTCに時刻を書き込むときは、windowsの時計情報をビルド時にハードコードし、ブート時にその値を書き込むことになる。ブート時に毎回時刻をリセットしては意味がないので、トグルスイッチのオンオフをディジタルピンで検出し、オフ時のみ時刻の書き込みが動作するようにした。
以上の構成によりハードウェアを構築し、以下のプログラムをArduinoIDEで作成した。本プログラムではroundconditionのプリプロセッサで1分に1回オルゴールが鳴るようにしてあるが、鳴らす時刻を指定することで特定時刻のみでの動作が可能となる。また、Arduinoはマルチスレッド動作ができないので、ifを分けることで割込み動作させ、時刻表示とモーター動作を交互に動作させている。このような使用法は、本来は推奨されない。モーター動作中に割込みが入ることになり、意図通りの角度の回転が実現できなくなるので、回転時間、スピード、角度指定の3つのパラメータからちょうどオルゴール一周分になるように調整した。以下の例ではシリアルモニタにも時刻を出力しているが、シリアル出力をやめるだけでもバランスが変わり、再調整が必要となる。Arduinoのディジタルピンを直接モータードライバとして使用していることが原因なので、別の専用モータードライバ基板があるモーターを用意するか、Arduinoを2つ使用して片方をモーター専用にするかのいずれかが理想的な実装方法である。今回はコストとの兼ね合いもあり、割込み動作で実装した。完成品の動作は
Arduinoによるオルゴール時報つきディジタル時計 - YouTube
で視聴可能である。
#include
#include "RTClib.h"
#include "CheapStepper.h"
#include "LedControl.h"RTC_DS1307 RTC;
//RTCデータ初期値
#define rtcresetPin 5
unsigned int yearvalue = 2000;
unsigned int monthvalue = 0;
unsigned int dayofweek = 100;
unsigned int dayvalue = 100;
unsigned int hourvalue = 100;
unsigned int minvalue = 100;
unsigned int secondvalue = 100;
#define time_adj 15//秒の調整// モーター設定
#define motorPin1 9 // Blue - 28BYJ48 pin 1
#define motorPin2 10 // Pink - 28BYJ48 pin 2
#define motorPin3 11 // Yellow - 28BYJ48 pin 3
#define motorPin4 12 // Orange - 28BYJ48 pin 4
// 回転設定
#define roundspeed 22
#define rounddegree 13
#define roundtime 13
// 回転条件設定
#define effectivedayofweek 8// < 6で平日のみ
#define roundcondition (timenow.second() % 60) < roundtime
CheapStepper stepper(motorPin1, motorPin2, motorPin3, motorPin4);// LED設定
#define LedDataPin 6
#define LedClockPin 8
#define LedLoadPin 7
#define cascades 2 //1 to 8
#define intensity 8 //0 to 8
#define disp1 1
#define disp2 0
LedControl lc = LedControl(LedDataPin, LedClockPin, LedLoadPin, cascades);void setup () {
//declare the motor pins as outputs
pinMode(rtcresetPin, INPUT_PULLUP);
pinMode(motorPin1, OUTPUT);
pinMode(motorPin2, OUTPUT);
pinMode(motorPin3, OUTPUT);
pinMode(motorPin4, OUTPUT);
stepper.setRpm(roundspeed);Serial.begin(9600);
Wire.begin();
Serial.println("Arduino Start!");delay(300);
RTC.begin();
delay(300);
Serial.println("RTC begin");
if (! RTC.isrunning()) {//RTCが起動していないとき
Serial.println("RTC is NOT running!");
if (digitalRead(rtcresetPin)) {
Serial.println(" RTC is adjusted");
// following line sets the RTC to the date & time this sketch was compiled
RTC.adjust(DateTime(__DATE__, __TIME__));
DateTime nowtmp = RTC.now();
RTC.adjust(DateTime(nowtmp.unixtime() + time_adj));//ブート時間分の時刻調整
}
}
else {
Serial.println("RTC is running!");
if (digitalRead(rtcresetPin)) {
Serial.print("pin ");
Serial.print(rtcresetPin);
Serial.println(" is low. RTC is adjusted");RTC.adjust(DateTime(__DATE__, __TIME__));
DateTime nowtmp = RTC.now();
RTC.adjust(DateTime(nowtmp.unixtime() + time_adj));//ブート時間分の時刻調整
}
else {
Serial.println("RTC is NOT adjusted");
}
}
Serial.println("RTC setup end");
/*---------------- LED setup --------------------*/
/*
The MAX72XX is in power-saving mode on startup,
we have to do a wakeup call
*/
// LED初期化
for (int index = 0; index < lc.getDeviceCount(); index++) {
lc.shutdown(index, false);
}
/* Set the brightness to a medium values */
lc.setIntensity(0, intensity);
lc.setIntensity(1, intensity);
/* and clear the display */
lc.clearDisplay(disp1);
lc.clearDisplay(disp2);
lc.setChar(disp1, 6, '-', false);//常に-を表示する
}
void loop () {
DateTime timenow = RTC.now();
LEDshowtime(timenow);
serialshowtime(timenow);
stepper.run();
if (timenow.dayOfTheWeek() < effectivedayofweek) { //曜日出力が平日のとき=6(土曜)未満
if (roundcondition) {//時刻0秒をトリガにオルゴール回転
stepper.newMoveDegreesCCW(rounddegree);
}
else {
setStop();//全停止
}
}
}
//////////////////////////////////////////////////////////////////////////////////
void setStop()//モータードライブピン全停止
{
digitalWrite(motorPin1, 0);
digitalWrite(motorPin2, 0);
digitalWrite(motorPin3, 0);
digitalWrite(motorPin4, 0);
}
void LEDshowtime(DateTime timenow)
{
if (secondvalue != timenow.second() ) { //秒が変わったときだけ描画更新する
unsigned int seconds[2];
secondvalue = timenow.second();
seconds[0] = secondvalue % 10; secondvalue /= 10;//1桁目
seconds[1] = secondvalue % 10; secondvalue /= 10;//2桁目
lc.setDigit(disp1, 1, seconds[1], false);
lc.setDigit(disp1, 0, seconds[0], false);
}
if (minvalue != timenow.minute() ) { //分が変わったときだけ描画更新する
unsigned int mins[2];
minvalue = timenow.minute();
mins[0] = minvalue % 10; minvalue /= 10;//1桁目
mins[1] = minvalue % 10; minvalue /= 10;//2桁目
lc.setDigit(disp1, 3, mins[1], false);
lc.setDigit(disp1, 2, mins[0], true);
}
if (minvalue != timenow.hour() ) { //時間が変わったときだけ描画更新する
unsigned int hours[2];
hourvalue = timenow.hour();
hours[0] = hourvalue % 10; hourvalue /= 10;//1桁目
hours[1] = hourvalue % 10; hourvalue /= 10;//2桁目
lc.setDigit(disp1, 5, hours[1], false);
lc.setDigit(disp1, 4, hours[0], true);
}
if (dayvalue != timenow.day() ) { //日が変わったときだけ描画更新する
unsigned int days[2];
dayvalue = timenow.day();
days[0] = dayvalue % 10; dayvalue /= 10;//1桁目
days[1] = dayvalue % 10; dayvalue /= 10;//2桁目
lc.setDigit(disp2, 1, days[1], false);
lc.setDigit(disp2, 0, days[0], false);
}
if (dayofweek != timenow.dayOfTheWeek() ) { //曜日が変わったときだけ描画更新する
dayofweek = timenow.dayOfTheWeek();
lc.setDigit(disp1, 7, timenow.dayOfTheWeek(), false);
}
if (monthvalue != timenow.month() ) { //月が変わったときだけ描画更新する
unsigned int months[2];
monthvalue = timenow.month();
months[0] = monthvalue % 10; monthvalue /= 10;//1桁目
months[1] = monthvalue % 10; monthvalue /= 10;//2桁目
lc.setDigit(disp2, 3, months[1], false);
lc.setDigit(disp2, 2, months[0], true);
}if (monthvalue != timenow.month() ) { //年が変わったときだけ描画更新する
yearvalue = timenow.year();
unsigned int years[4];
years[0] = yearvalue % 10; yearvalue /= 10;//1桁目
years[1] = yearvalue % 10; yearvalue /= 10;//2桁目
years[2] = yearvalue % 10; yearvalue /= 10;//3桁目
years[3] = yearvalue % 10;//4桁目
lc.setDigit(disp2, 7, years[3], false);
lc.setDigit(disp2, 6, years[2], false);
lc.setDigit(disp2, 5, years[1], false);
lc.setDigit(disp2, 4, years[0], true);
}
}
void serialshowtime(DateTime timenow) {
if (secondvalue != timenow.second() ) { //秒が変わったときだけ描画更新する
secondvalue = timenow.second();
Serial.print(timenow.year(), DEC);
Serial.print('/');
Serial.print(timenow.month(), DEC);
Serial.print('/');
Serial.print(timenow.day(), DEC);
Serial.print(' ');
Serial.print(timenow.hour(), DEC);
Serial.print(':');
Serial.print(timenow.minute(), DEC);
Serial.print(':');
Serial.print(timenow.second(), DEC);
// Serial.println(); //空白
Serial.println(""); // 改行
// Serial.println("-----"); //文字列
// delay(1000);//wait 1s
}
}
サーボモーターの修理
フィジカルコンピューティングというと旬は過ぎ去ったようにも感じるこの頃、arduinoによるモーター制御を試している。
入門者向けとして有名な、マイクロサーボ SG90を使用して2軸カメラマウントの動作をテストしていたところ、当初正常であったモーターが突然ブルブルと震えるようになった。
同様の症状はamazonの購入レビューにもいくつか寄せられており、まともに動かなくなったので交換したという声を散見した。
サーボモーターがブルブルと震える現象には、主に2種類の原因がある。ひとつは、負荷質量が大きいことで制御が不安定になり、ハンチングを起こしている状況であり、もうひとつは回路の不良により正しい信号がモータに送られていない状況である。
ついさっきまで動いていたモーターが動作不良を起こすとすれば、必ず何らかの短期的原因があり、後者である可能性が高い。
ケーシングネジを外して封止シールを捲ったところ、写真に示すような配線の断線が見られた。この部分をはんだ付けし直したところ、正常に動作するようになった。
今回の購入品は、ラベルにあるようなTowerPro社製品ではない安価なコピー品であったようで、配線の品質などは正規品と比べると劣っているのかもしれない。
今回の入手品の配線材をケーブルストリッパで剥いてみると、銅の多芯線であったので、断線しやすい硬い単身線ではなかったので、修理は容易であった。
安価に入手したマイクロサーボモータが動作しないときは、簡単に捨ててしまわずにケースを開けてみることをお勧めする。
修理に必要な道具は、精密ドライバー、半田ごて、ケーブルストリッパ(AGW30に対応しているような、一番穴が小さいもの)である。