これらが、次の図のように接続されています。
まずはここでは、この全体構成図の詳細までみる必要はありません。
載っているLEDやスイッチなどが、CPUが実行する
プログラムによって制御できるんだ、ということだけ、
頭に入れておいてください。
「CPUがプログラムを実行する」とは、メモリに入っている プログラム(作業手順書)を順に「読み出し」、解釈して 実行し、必要に応じて結果をメモリに「書き込む」こと、 と言うことができます。
そしてメモリから命令やデータを読み書きするときには、 メモリの中の場所を「アドレス(address)」という数値によって 指定をするのでした。
このボードでは、LEDやスイッチなどの、入出力装置(Input/Output; I/Oと略記します)も、この「メモリに対する読み書き(アクセス)」と 同様に行うことができるようになっています。 このような仕組みを メモリマップドI/O (Memory-mapped I/O)と呼びます。
具体的には、それぞれのI/O装置が、次のように 割り当て(memory-mapped)されています。
I/O装置 | アドレス(16進数表記) | アクセス方法 |
7セグメントLED(0桁目) | 0x4000番地 | 書き込み |
7セグメントLED(1桁目) | 0x4001番地 | 書き込み |
7セグメントLED(2桁目) | 0x4002番地 | 書き込み |
7セグメントLED(3桁目) | 0x4003番地 | 書き込み |
スイッチ(8個) | 0x4004番地 | 読み出し |
LED(8個) | 0x4005番地 | 書き込み |
例えば、0x4004番地を「読み出す」と、スイッチの状態がわかり、 0x4000番地に数値を「書き込む」と、7セグメントLEDの0桁目(右端)の 表示を設定できる、というわけです。
なお、書き込み用のものを読み出そうとしても、 正しい値を読み出すことはできません。 つまり右端の7セグメントLEDに表示されている値を調べようと思って、 次のような記述をしても、正しい値は得られません。 (正しくない値が代入される可能性が高い。 偶然表示されている値が得られることもあるが、 得られないことも多い。)
i = num0; /* NG (read) */
volatile BYTE xdata num0 _at_ 0x4000; // 0 digit of 7seg.LED volatile BYTE xdata num1 _at_ 0x4001; // 1 digit of 7seg.LED volatile BYTE xdata num2 _at_ 0x4002; // 2 digit of 7seg.LED volatile BYTE xdata num3 _at_ 0x4003; // 3 digit of 7seg.LED volatile BYTE xdata sw _at_ 0x4004; // SWs volatile BYTE xdata led _at_ 0x4005; // LEDs例えば、「led」というBYTE型(1バイト=8ビット)の変数が 0x4005番地に割り当てられています。 この0x4005番地の内容は、先のとおり「LED(8個)の点灯・消灯を決める メモリマップドI/O」でした。 そのため、変数ledに値を書き込むと、実際には0x4005番地に 書き込まれ、結果としてその値に応じて各LEDが点灯・消灯することに なります。 なおここで、変数ledの値は1バイト=8ビット、つまり 8組の「0または1の値」ですが、 その各桁が、8個のLEDそれぞれの点灯・消灯に対応していることに 注意しましょう。
例えばこの変数ledに、次のように値0x81を代入するとします。
led = 0x81;ここで代入している値0x81は、2進数で書くと"10000001"ですから、 値が1になっている、一番左端のLED(LED7)と一番右端のLED(LED0)のみが 点灯し、それ以外は消灯することになります。
同様に例えば、 1バイトの変数swの値を読み出すと 8個のスイッチそれぞれの状態がわかることになります。 つまり8ビットのそれぞれが8個のスイッチに対応し、 最上位が左端のSW7、最下位が右端のSW0の値をあらわし、 各スイッチが押されていれば、その桁(ビット)の値が1に、 押されていなければ0になります。 そこで、例えばSW3の値をif文で調べたければ、 次のように書けばよいでしょう。
if ((sw & 0x08) != 0){ ..
まず実験用のフォルダを作成します。 マイコンピュータ→Zドライブの中、または、「マイ ドキュメント」の中に、 実験第1用のフォルダを作成します。 今後の作業はすべてこの中で行うことにします。 なお、「Zドライブ」または「マイ ドキュメント」に置いたファイルは どのパソコンでも共有されます。
まず、先ほどのフォルダ内に、適当な名称で、これからの作業用の フォルダを作成します。 そこにサンプルプログラムとして、 sample.c と sample.Uv2 をダウンロードします。 今後、別のプログラムを作成するときは、適宜別のフォルダを作成し、 その中にこの2つをダウンロードして、それを編集するようにします。 また、作成したプログラムを残しながら、かつ、それを変更していきたい場合には、 フォルダをまるごとコピーします。 単にsample.cをコピーして、 コピー先の新しいファイルを編集しようとしてもうまくいきません。
その中にあるsample.Uv2の次のアイコンをダブルクリックすると、
コンパイラなどが統合された開発環境uVision2が起動します。
この左側のウインドウ内の、Target1→SourceGroup1と順に
+マークをクリックすると、sample.cが見つかるはずですので、
それをダブルクリックすると、右側のウインドウに、
プログラムsample.cの内容が表示されます。
#include "Fx2.h" #include "Fx2regs.h" // definition of memory-mapped I/O devices and external memories volatile BYTE xdata num0 _at_ 0x4000; // 0 digit of 7seg.LED volatile BYTE xdata num1 _at_ 0x4001; // 1 digit of 7seg.LED volatile BYTE xdata num2 _at_ 0x4002; // 2 digit of 7seg.LED volatile BYTE xdata num3 _at_ 0x4003; // 3 digit of 7seg.LED volatile BYTE xdata sw _at_ 0x4004; // SWs volatile BYTE xdata led _at_ 0x4005; // LEDs main() { // initialization CKCON |= 0x07; OEE = 0xef; led = 0; num0 = 10; num1 = 10; num2 = 10; num3 = 10; while(1){ // write your code here } }最後のほうに、while文がありますが、while(1)ですので、 {}内が無限ループになっています。 そこでこの{}内に、実行するべきプログラムを書くことにします。 今回は、例として次のように書いてみましょう。
while(1){ // write your code here led = sw; }プログラムを記述したら、保存した後、 メニューからProject→Build Target(F7)を 選んで、プログラムのコンパイルを行います。 (ファンクションキーのF7を押してもかまいません) コンパイルの過程が下のウインドウに表示されますので、 エラーなどがないか確認します。
ボードとパソコンのUSBポートをケーブルで接続します。 (このとき、最初だけドライバのインストールが行われることがあります) このとき、8個のLEDのすべてまたは一部が点灯することがありますが、 ここでは気にしなくてかまいません。
その後、
デスクトップ上にある"EZ-USB Control Panel"をダブルクリックして
EZ-USB Control Panel(以下、ControlPanelと略記)を起動します。
起動すると、次のような画面になっているはずです。
なおControlPanelの中央上の「Target」のところが"FX2"となっていることを
確認しておいてください。
もしこのようになっていないときや、"No EZ-USB Device Found"と
表示されるときは、
ボードの接続を確認し、[Open All]ボタンを押してみましょう。
続いて、コンパイルしたプログラムを、ボードに転送します。 [Download]ボタンを押すと、転送するプログラムファイルを選ぶ 画面が現れますので、さきほど作成したフォルダ内の sample.hexを選択します。
すると転送が行われ、直ちにプログラムの実行が開始されます。
なお2回目以降プログラムを転送するとき、 同じプログラムファイルを修正しただけであれば、 uVision2でコンパイル後に、ControlPanelで[Re-Load]ボタンを 押すだけで転送・実行が行われます。 (つまり毎回転送するプログラムファイルを選択する必要がない) ちなみに[HOLD]ボタンでCPUをリセットさせることができます。 また[RUN]ボタンで、プログラムの実行を停止・再開することができます。
そしてプログラム側から見えるのは、 それぞれの桁に表示させたい値(0〜9)を、 それぞれのメモリマップドI/Oのアドレスに書き込むだけで その値が表示されます。 例えば0桁目に「3」と表示させたければ、 次のように記述すればよいことになります。
num0 = 3;なお書き込む値が0〜9の範囲外のときは、数値が表示されません。 また前述のように、値を読み出すこともできません。
void ShowInt(int d) { // write your code here }のように作成しておくと、後で再利用できるので便利です。
ヒント:
ただし、 表示させる0〜9999の数値は 内部ではint型の変数一個に保持させることとし、 その変数の値から千の位〜一の位をそれぞれ求め、 それらを7セグメントLEDに表示させることとします。
ヒント:
一般には、ある型が何ビット(何バイト)であるかは、一定ではなく、 CPUの種類や用いるコンパイラに依存します。 変数の型が何バイトであるかはsizeofという演算子で得ることができます。 「sizeof (データ型)」という式は、 「データ型」が必要とするバイト数を与えます。 「sizeof 変数名」は、「変数名」のバイト数を与えます。
このバイト数は、その変数に代入できる数値の限界に関係してきます。 例えば符号なし8ビット(unsigned char型)では0〜255の数値を表現できます。 代入できる数値の限界には、符号の有無も関係しています。 同じ8ビット(1バイト)であっても、 符号つき8ビット(char型)では-128〜127になります。 このように変数がプログラム中でとりうる値に 注意する必要があります。
num3 = sizeof(long); num2 = sizeof(int); num1 = sizeof(short); num0 = sizeof(char);
unsigned char x; char y;である時に、
x = 255; x = x + 1; y = 128; y = 16 * 17; y = 16 * 16; y = 16 * 15; y = 16 * 8;などを計算してみましょう。
I/Oポートを使用するときには、プログラムの最初に、 各ピンを入力あるいは出力のどちらとして使用するかを 設定する必要があります。 この設定はOEE(Output Enable E)という特殊な変数(実際には メモリマップドされた1バイト変数)に値を書き込むことで行います。 具体的には、出力として使用したいピンの桁を1、 入力として使用したいピンの桁を0とした8ビットの2進数(1バイト)の 値を設定します。
例えばPE0が出力として設定されているとして、 IOEの0ビット目を1にすると、PE0の値が1(つまり高い電圧3V)となります。
逆にPE3が入力として設定されているとすると、
IOEの3ビット目を読み取ると、PE3に加えられている電圧が
高い(3V=1)か低い(0V=0)かを知ることができます。
ビット演算・シフト
このように特にI/Oポートを操作するときには、特定のビットのみを
1または0にしたり、特定のビットの値のみを知る必要が生じます。
このようなときには、ビット演算と呼ばれる方法が便利です。
ビット操作は、AND演算子(&)やOR演算子(|)やNOT演算子(~)を用いて 変数の中の特定のビットのみを操作する技法で、 例えば次のような記述によって行うことができます。
変数aの0ビット目(最下位ビット)のみを1にする | a = a | 0x01; |
変数aの2ビット目のみを1にする | a = a | 0x04; |
変数aの4ビット目のみを0にする | a = a & ~0x10; |
変数aの6ビット目が0であるかを調べる | (a & 0x40)==0 |
このD/A変換器の扱い方を知るためには、 D/A変換器TLV5626のデータシートを 読むことになります。 要点は、次のようなことになります。
つまりD/A変換器TLV5626へのデータの転送は16ビット(2バイト)を 単位として行うことになります。
そして送るべきデータは、次の2組の16ビット(2バイト)となることが 記述されています。
出力値 | 出力電圧[V] |
0 | 0.00 |
.. | .. |
128 | 2.048 |
.. | .. |
255 | 4.096 |
この「出力値」を、上位4ビットと下位4ビットにわけ、 D/A変換器に送ることになるわけですが、 例えば値0x12を送る場合には、2回目の転送で 次のような2バイトを送ることになります。
(ヒント:出力値が、どのように2バイトのデータに埋め込まれているか、 を考えるのがポイント。 1バイト目の上位4ビット (16進数の上位桁) 「0xc」は、 おまじないと考えてかまいません。 何故「0xc」になるのかを考える必要はありません。)
void DACwrite(unsigned char d) { /* 内部参照電圧を設定するおまじない */ CS_DAC(1); CK(1); /* 初期状態 */ CS_DAC(0); DACwrite_byte(0xd0); DACwrite_byte(0x02); CS_DAC(1); /* 出力値dに対応する電圧を発生させる */ CS_DAC(0); DACwrite_byte(0xc0 | (d >> 4)); DACwrite_byte(d << 4); CS_DAC(1); }なお「>>」は、指定したビット分だけ右にシフトする演算子で、 例えば"0x53 >> 4"は0x05となります。 同様に「<<」は、指定したビット分だけ左にシフトする演算子です。
(第1日目はこのあたりまでを目安とするとよいでしょう。 もっと先まで進んでおくと第2日目が楽になります。)
/* CK(d): D/A変換器のSCLKをd (d=0 or 1)に設定する関数 */ void CK(char d) {if (d == 1) IOE |= 0x01; else IOE &= ~0x01;} /* CS_DAC(d): D/A変換器の/CSをd (d=0 or 1)に設定する関数 */ void CS_DAC(char d){if (d == 1) IOE |= 0x04; else IOE &= ~0x04;} /* DO_DAC(d): D/A変換器のDINをd (d=0 or 1)に設定する関数 */ void DO_DAC(char d){if (d == 1) IOE |= 0x08; else IOE &= ~0x08;}
与える電圧[V] | 変換値 |
0.0 | 0x00 |
.. | .. |
2.5 | 0x80 |
.. | .. |
5.0 | 0xff |
なお/CS=0の期間にCLOCKを8回与える箇所が2回ありますが、 この1回目でアナログ電圧をディジタル値に変換だけ行われ、その結果を2回目に読み出しているためです。 そのため必ず2回必要です。
/* CK(d): D/A変換器のSCLKをd (d=0 or 1)に設定する関数 */ void CK(char d) {if (d == 1) IOE |= 0x01; else IOE &= ~0x01;} /* CS_ADC(d): A/D変換器の/CSをd (d=0 or 1)に設定する関数 */ void CS_ADC(char d){if (d == 1) IOE |= 0x02; else IOE &= ~0x02;} /* CS_DAC(d): D/A変換器の/CSをd (d=0 or 1)に設定する関数 */ void CS_DAC(char d){if (d == 1) IOE |= 0x04; else IOE &= ~0x04;} /* DO_DAC(d): D/A変換器のDINをd (d=0 or 1)に設定する関数 */ void DO_DAC(char d){if (d == 1) IOE |= 0x08; else IOE &= ~0x08;} /* DI_ADC(): A/D変換器のDATAOUTを読み取る関数 */ char DI_ADC(){if ((IOE & 0x10) == 0) return(0); else return(1);}
SN7404はインバータ(否定ゲート)が6個入ったICです。
このボードのVDDを電源電圧(+5V)、GNDをグランド(0V)に接続し、
INに与える電圧を変えたときにOUTに現れる電圧の
関係を調べてみることにします。
インバータでは、入力が0(低い電圧)のときには出力が1(高い電圧)、
入力が1(高い電圧)のときには出力が0(低い電圧)となるはずですので、
おおまかには次のようなグラフとなると考えられます
(図の横軸はかなりいい加減です)。
そこでD/A変換器の出力である「Aout」をインバータのINに、
またインバータの出力をA/D変換器の入力である「Ain」に、
ケーブルを用いて接続します。
注意とヒント: