2016年03月07日 | 公開。 |
2016年04月28日 | Arduino M0用SPIライブラリの不具合がArduino IDE1.7.10で修正された事を追記。 |
AQM1248AはSPI接続のLCDモジュールですので、SPIバスの規格に則った信号をマイコン側で生成して、AQM1248Aに送る必要があります。その信号の生成の際に、マイコンに内蔵されているSPIインターフェースモジュール(SPIバスの規格に則った信号を生成する回路)を使う、ハードウェアSPIと呼ばれる方法と、ソフトウェアによりGPIO端子を操作して、SPIバスの規格に則った信号を生成する、ソフトウェアSPIと呼ばれる方法があります。ハードウェアSPIとソフトウェアSPIには、表2の様な違いがあります。
比較項目 | ハードウェアSPI | ソフトウェアSPI |
---|---|---|
マイコン内蔵SPIインターフェースモジュールの必要性 | × 必要 | ○ 不要 |
データ転送速度 | ○ 速い | × 遅い |
使用するマイコンのピンの自由度 | × SPIインターフェースに割り振られたピンしか使えない | ○ GPIOピンなら、どのピンでも使える。 |
制御プログラムの大きさ | ○ やや小さくなる | × やや大きくなる |
表2では、利点には○、欠点には×を書いておきました。
ハードウェアSPIの場合は、SPIインターフェースモジュールを内蔵したマイコンを使う必要があります。低価格のマイコンチップを使うとSPIインターフェースモジュールが内蔵されていない場合があるのですが、Arduinoに限って言うと、主だったArduinoはSPIインターフェースモジュールが搭載されたマイコンを採用しているため、気にする必要はありません。
データ転送速度に関しては、やや考慮する必要があります。AQM1248Aのように小型の液晶の場合は、短時間にたくさんの情報を表示しようという要求は大きくないかもしれません。しかし、高速な処理を求められるアプリケーションを開発している際は、液晶表示の遅さに引きずられて、全体の処理が遅くなってしまう可能性もあります。
一番重要なのは、使用するマイコンのピンの自由度でしょう。ソフトウェアSPIの場合は、GPIOに割り当てられているピン(Arduinoの場合でいうと、digitalWrite関数やdigitalRead関数が使えるピン)ならば、どのピンでも使えます。一方でハードウェアSPIの場合は、SPIの通信用に、あらかじめ割り当てられたピンしか使う事ができません。
制御プログラムの大きさは、それほど大きな差ではないので、結局、ハードウェアSPIを使うかソフトウェアSPIを使うかは、速度を取るか、ピン割り当ての自由度を取るかで決まることになります。
主だったArduinoについて、ハードウェアSPIにどのピンが割り当てられているかを表3にまとめました。Arduinoのピン配置図(図4と図5)と合わせてご覧ください。
Arduinoの種類 | MOSI(SDO) | MISO(SDI) | SCK(SCLK) |
---|---|---|---|
Arduino Uno | 11およびSPI-4 | 12およびSPI-1 | 13およびSPI-3 |
Arduino Mega2560 | 51およびSPI-4 | 50およびSPI-1 | 52およびSPI-3 |
Arduino Leonardo | SPI-4 | SPI-1 | SPI-3 |
Arduino M0 | SPI-4 | SPI-1 | SPI-3 |
Arduino Due | SPI-4 | SPI-1 | SPI-3 |
表3を見ると/CS(SS)の信号線がありませんが、/CS信号は、一部のArduinoを除いてマイコンに内蔵のSPIインターフェースモジュールでは生成されませんので、通常GPIOを使います。そのため、SPIバスの信号の中では唯一/CSだけが、好きなGPIOピン(digitalWrite関数が使えるピン)に割り当てられるようになっています。
表3にSPI-1とかSPI-3とかSPI-4とか書いてあるのは、SPIヘッダのそれぞれ1番ピン、3番ピン、4番ピンを指します。
SPIヘッダというのは、図4の右端または図5の中央右よりにある2X3ヘッダの事です。32ビットのArduinoの場合、このヘッダにはSPIと書かれています。一方で8ビットのArduinoの場合、このヘッダにはICSPと書かれています。8ビットのArduinoの場合、本来はこのヘッダをICSPヘッダと呼ぶべきかもしれませんが、ここでは8ビットのArduinoの場合も32ビットのArduinoの場合も、まとめてSPIヘッダと呼ぶ事にします。
Arduino UnoやArduino Mega2560の場合、例えば「11およびSPI-4」(Arduino UnoのMOSI信号)のように、ふたつのピンが併記されています。これは、11番ピンとSPI-4ピンの両方に、同じ信号が同時に出てくる(ふたつのピンは、基板内でショートしている)事を意味しています。
ですから、Arduino Unoの場合、SPIヘッダでSPIバスの信号をやり取りすると、11、12、13の各ピンは、他の用途に使えなくなります。
同様にArduino Mega2560の場合、SPIヘッダでSPIバスの信号をやり取りすると、50、51、52の各ピンは、他の用途に使えなくなります。
Arduinoには、I/O電圧が5VのArduino(Arduino Uno、Arduino Mega2560など)と3.3VのArduino(Arduino M0、Arduino Dueなど)があります。
AQM1248Aは2.4Vから3.6Vの範囲で動作するLCDモジュールで、5VのI/O電圧を印加すると壊れてしまいます。5VのArduinoと接続するには、レベル変換回路(I/O電圧を変換する回路)を通してつなぐ必要があります。
一方で、3.3VのArduinoを使う場合は、AQM1248Aの電源に3.3Vを使えば、レベル変換回路を使わずに、ArduinoとAQM1248Aを直結する事ができます。
ここではまず、より簡単な、3.3VのArduinoとAQM1248Aの接続について説明します。
3.3Vで動作するArduino M0やArduino Dueを用いて、ハードウェアSPIでAQM1248Aを動作させるには、図6の様に接続してください。AQM1248Aの3番端子(/RESET)は、どこにも接続しません。
Arduino M0を使って配線した例を写真6に、Arduino Dueを使って配線した例を写真7に示します。
Arduinoに接続したAQM1248Aが正しく動作したかどうかを確認するためのテストスケッチを、リスト1に示します。AQM1248Aの制御スケッチの作り方については後に説明しますので、ここではリストのみを掲載します。
リスト1のスケッチの動作確認は、Arduino IDE 1.7.8で行いましが、Arduino M0の場合はArduino IDE 1.7.X(Xは任意の数字)のどれでも使えると思います。
またArduino Dueの場合は、Arduino IDE 1.7.Xに加えて、Arduino 1.6.Xも使えるはずです。(Arduino IDE 1.7.8と1.6.7で確認済み)
#include <SPI.h>
#define DI_PIN 9
#define CS_PIN 10
#ifndef SPI_CLOCK_DIV32 // for arduino M0(怒)
#define SPI_CLOCK_DIV32 32
#endif
void LcdCommand(uint8_t cmd)
{
digitalWrite(CS_PIN,LOW);
digitalWrite(DI_PIN,LOW);
SPI.transfer(cmd);
digitalWrite(CS_PIN,HIGH);
} // LcdCommand
void LcdData(uint8_t dat)
{
digitalWrite(CS_PIN,LOW);
digitalWrite(DI_PIN,HIGH);
SPI.transfer(dat);
digitalWrite(CS_PIN,HIGH);
} // LcdCommand
void InitializeLcd()
{
LcdCommand(0xae);
LcdCommand(0xa0);
LcdCommand(0xc8);
LcdCommand(0xa3);
LcdCommand(0x2c);
delay(2);
LcdCommand(0x2e);
delay(2);
LcdCommand(0x2f);
LcdCommand(0x23);
LcdCommand(0x81);
LcdCommand(0x1c);
LcdCommand(0xa4);
LcdCommand(0x40);
LcdCommand(0xa6);
LcdCommand(0xaf);
} // InitializeLcd
void setup() {
pinMode(DI_PIN,OUTPUT);
pinMode(CS_PIN,OUTPUT);
digitalWrite(CS_PIN,HIGH);
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV32);
SPI.setDataMode(SPI_MODE3);
SPI.setBitOrder(MSBFIRST);
InitializeLcd();
}
void loop() {
for(int page=0; page<6; page++) {
LcdCommand(0xb0+page);
LcdCommand(0x10);
LcdCommand(0x00);
for(int x=0; x<128; x++) {
LcdData(0);
} // for x
} // for page
delay(1000);
for(int page=0; page<6; page++) {
LcdCommand(0xb0+page);
LcdCommand(0x10);
LcdCommand(0x00);
for(int x=0; x<128; x++) {
LcdData(x);
} // for x
} // for page
delay(1000);
}
リスト1を実行すると、AQM1248Aの表示は写真8の様になるはずです。また写真では分かりませんが、1秒間隔で、写真8の様な模様が表示→消去→表示→消去→・・・を繰り返します。
なお、図6の配線図ではAQM1248Aの4番端子(RS)と2番端子(/CS)は、それぞれArduinoの9番端子と10番端子につないでいますが、これらの信号の制御はGPIOで行っていますから、他の端子に変更できます。他の端子につなぐ場合は、リスト1の最初の方の
#define DI_PIN 9
#define CS_PIN 10
の部分を書き換えてください。DI_PINというのがAQM1248AのRS信号を、CS_PINというのが/CS信号を表わしています。(ちなみに、DI_PINのDIは、Data/Instructionの略です。RSというのはおそらくReceive/Sendの略だと思うので、違和感があります)
例えば、AQM1248AのRS信号(4番端子)をArduinoの5番端子に、AQM1248Aの/CS信号をArduinoの7番端子に、それぞれ接続したい場合は、
#define DI_PIN 5
#define CS_PIN 7
と書き換えればOKです。
次に、AQM1248Aを3.3VのArduinoに接続し、ソフトウェアSPIで動作させる場合について説明します。
ソフトウェアSPIの場合は、4本の制御信号(RS、/CS、SCLK、MOSI)を、Arduinoの任意のGPIO端子に割り当てられますが、ここでは仮にRS信号を9番端子、/CS信号を10番端子、MOSI信号(AQM1248AのSDI信号)を11番端子、SCLK信号を13番端子として説明します。
ソフトウェアSPIでAQM1248Aを制御するには、図7の配線図に従って配線してください。
Arduino M0を使って配線した場合の写真を写真9に、Arduino Dueを使って配線した場合の写真を写真10に示します。
Arduinoに接続したAQM1248Aが正しく動作したかどうかを確認するためのテストスケッチを、リスト2に示します。Arduino M0の場合はArduino IDE 1.7.Xで動作するはずです。(Arduino IDE 1.7.8で動作確認済み) また、Arduino Dueの場合はArduino IDE 1.6.XとArduino IDE 1.7.Xで動作するはずです。(Arduino IDE 1.6.7と1.7.8で動作確認済み)
#define DI_PIN 9
#define CS_PIN 10
#define MOSI_PIN 11
#define SCK_PIN 13
void LcdWrite(uint8_t DI, uint8_t dat)
{
digitalWrite(CS_PIN,LOW);
digitalWrite(DI_PIN,DI);
digitalWrite(MOSI_PIN,(dat & 0x80 ? HIGH : LOW));
digitalWrite(SCK_PIN,LOW) ;
digitalWrite(SCK_PIN,HIGH);
digitalWrite(MOSI_PIN,(dat & 0x40 ? HIGH : LOW));
digitalWrite(SCK_PIN,LOW) ;
digitalWrite(SCK_PIN,HIGH);
digitalWrite(MOSI_PIN,(dat & 0x20 ? HIGH : LOW));
digitalWrite(SCK_PIN,LOW) ;
digitalWrite(SCK_PIN,HIGH);
digitalWrite(MOSI_PIN,(dat & 0x10 ? HIGH : LOW));
digitalWrite(SCK_PIN,LOW) ;
digitalWrite(SCK_PIN,HIGH);
digitalWrite(MOSI_PIN,(dat & 0x08 ? HIGH : LOW));
digitalWrite(SCK_PIN,LOW) ;
digitalWrite(SCK_PIN,HIGH);
digitalWrite(MOSI_PIN,(dat & 0x04 ? HIGH : LOW));
digitalWrite(SCK_PIN,LOW) ;
digitalWrite(SCK_PIN,HIGH);
digitalWrite(MOSI_PIN,(dat & 0x02 ? HIGH : LOW));
digitalWrite(SCK_PIN,LOW) ;
digitalWrite(SCK_PIN,HIGH);
digitalWrite(MOSI_PIN,(dat & 0x01 ? HIGH : LOW));
digitalWrite(SCK_PIN,LOW) ;
digitalWrite(SCK_PIN,HIGH);
digitalWrite(CS_PIN,HIGH);
} // LcdWrite
inline void LcdCommand(uint8_t cmd)
{
LcdWrite(LOW,cmd);
} // LcdCommand
inline void LcdData(uint8_t dat)
{
LcdWrite(HIGH,dat);
} // LcdCommand
void InitializeLcd()
{
LcdCommand(0xae);
LcdCommand(0xa0);
LcdCommand(0xc8);
LcdCommand(0xa3);
LcdCommand(0x2c);
delay(2);
LcdCommand(0x2e);
delay(2);
LcdCommand(0x2f);
LcdCommand(0x23);
LcdCommand(0x81);
LcdCommand(0x1c);
LcdCommand(0xa4);
LcdCommand(0x40);
LcdCommand(0xa6);
LcdCommand(0xaf);
} // InitializeLcd
void setup() {
pinMode(DI_PIN ,OUTPUT);
pinMode(CS_PIN ,OUTPUT);
pinMode(MOSI_PIN,OUTPUT);
pinMode(SCK_PIN ,OUTPUT);
digitalWrite(CS_PIN ,HIGH);
digitalWrite(SCK_PIN,HIGH);
InitializeLcd();
}
void loop() {
for(int page=0; page<6; page++) {
LcdCommand(0xb0+page);
LcdCommand(0x10);
LcdCommand(0x00);
for(int x=0; x<128; x++) {
LcdData(0);
} // for x
} // for page
delay(1000);
for(int page=0; page<6; page++) {
LcdCommand(0xb0+page);
LcdCommand(0x10);
LcdCommand(0x00);
for(int x=0; x<128; x++) {
LcdData(x);
} // for x
} // for page
delay(1000);
}
リスト2を実行すると、リスト1の場合と同様、写真8の様な表示が出たり消えたりを繰り返します。
図7とリスト2は仮に、RS信号を9番端子、/CS信号を10番端子、MOSI信号(AQM1248AのSDI信号)を11番端子、SCLK信号を13番端子に接続するものとして説明していますが、Arduinoのピン割り当ては、任意のGPIO端子に変更する事ができます。
例えばRS信号を1番端子、/CS信号を2番端子、MOSI信号を3番端子、SCLK信号を4番端子に割り当てる場合、リスト2の
#define DI_PIN 9
#define CS_PIN 10
#define MOSI_PIN 11
#define SCK_PIN 13
の部分を
#define DI_PIN 1
#define CS_PIN 2
#define MOSI_PIN 3
#define SCK_PIN 4
に書き換えてください。
今回AQM1248Aを制御してみたのがArduino M0を本格的に使った初めてのケースになります。その際に遭遇したArduino M0のソフトウェア的な不具合について、このコラムでは書くことにします。なお、これらの不具合が発生したのは、この記事を書いている時点で一番新しいArduino IDEの1.7.8においての事で、将来のバージョンアップによって改善していくと思われます。
リスト1のスケッチで、SPIライブラリを使っていますが、SPIのモードの設定を行うのに、setClockDivider関数、setDataMode関数、およびsetBitOrder関数を用いています。これらの関数は古いSPIライブラリとの互換性のために残されており、新規のプロジェクトではこれらの関数を使わずに、SPISettings関数を使って設定を行う事が推奨されています。
そこで、Arduino M0でもSPISettings関数を使おうとしたら、コンパイル時にエラーになりました。Arduino M0用のSPIライブラリのソースコードを調べてみたら、SPISettings関数自体が実装されていない事が分かりました。
仕方がないので、setClockDivider関数を使い、SPI.setClockDivider(SPI_CLOCK_DIV32);
でCPUクロックの32分の1をSPIクロックの周波数にしようとしたら、 SPI_CLOCK_DIV32という定数が定義されておらず、コンパイルできませんでした。仕方なく、またライブラリのソースコードを調べたら、setClockDividerの引数には分周比をそのまま設定すればよい(例えば32分周ならSPI.setClockDivider(32);とする)という事が分かりました。
2016年04月28日追記:SPIライブラリの不具合は、Arduino IDE 1.7.10で修正されました。詳しくはArduino IDE 1.7.10で解決した不具合、発生した不具合の記事をご覧ください。
min関数やmax関数(正確にはminマクロやmaxマクロ)が宣言されておらず、使えない事が分かりました。この問題を回避するには、
#ifndef min
#define min(a,b) ((a)<(b) ? (a) : (b))
#endif
#ifndef max
#define max(a,b) ((a)>(b) ? (a) : (b))
#endif
と、スケッチ内でmin関数とmax関数を定義する必要があります。
それから、これは不具合とはいえないのですが、Arduino IDEのシリアルモニタに何かを表示しようとして
Serial.begin(9600);
Serial.println("Hello world");
などとしても、うまくいかないのを経験をしました。
正しくは
while(!SerialUSB);
SerialUSB.begin(9600);
SerialUSB.println("Hello world");
とする必要があります。この事は、arduino.orgのGETTING STARTED WITH ARDUINO M0というページに書いてありますが、このページはarduino.orgのトップページからややたどり着きにくいところにあり、なかなか気づかないと思います。
さらに、Arduino IDEに付属のCommunication関係のスケッチの例(図8参照)も、全てSerialUSBではなくSerialを出力先としてコーディングされており、軒並みArduino M0では動作しない(しかもコンパイルエラーにもならない)のは問題です。
Arduino M0に関しては、ネットで検索しても日本語の情報が少ないこともあり、現時点では初心者が安心して使えるマイコンボードではないなという印象を持っています。問題が発生しても自力で解決できる上級者には安価で高性能なArduinoだとはいえますが、本来Arduinoが、アーティストやデザイナーなど、電子回路やプログラムの知識があまりない人にでも使いこなせるマイコンボードをめざして開発されたものである事を考えると、Arduino本来の使い方をしているとは思えません。
その点、Arduino Unoは枯れた(問題が出尽くした)マイコンボードであり、主な問題点は修正されているか、その対応策がネット上で簡単に得られるかのどちらかであるため、価格の割には性能が低い事を考慮しても、初心者には魅力的なマイコンボードであり続けているといえます。
次のページでは、抵抗分圧型レベル変換回路を使って5VのArduinoとAQM1248Aを接続する方法の説明をします。
商品名 | 122X32モノクログラフィックLCDシールド | |
税抜き小売価格 | 3333円 | |
販売店 | スイッチサイエンス | |
サポートページ | 122X32モノクログラフィックLCDシールドサポートページ |
商品名 | GLCD学習シールドキット | |
税抜き小売価格 | 1410円 | |
販売店 | スイッチサイエンス | |
サポートページ | GLCD学習シールドキットサポートページ |