ARM LPC810 と、MLPS331 で高度計を作成( トランジスタ技術2014年2月号付録 )
トランジスタ技術2014年2月号付録基板と、8ピンのDIPパッケージのARMに、気圧センサーとLCDなどの部品を追加して高度計を作成するページです。
この写真はトランジスタ技術2014年2月号付録のデモプログラム、LPC810_DEMO.hex を動作させたもの。後で使うため、押しボタンを追加しています。写真の場所は、標高約80mで、撮ったときの付近の気圧は、気象庁のデータから、1021hPaだったので、約10hPa低く、標高80mとすると、かなり正確に測定されているのがわかります。
まず、デモソフトのソースを再コンパイルして、同じものができるか、開発環境のテストを行います。
気温の表示のところに、高度を表示するテスト。 高度への換算は、近似計算とします。
現在の場所を、押しボタンを押すと、0メートルとする改造。
EEPROMに、現在の場所の高度を記憶させ、電源を切ってもその補正値で0メートルとする改造
押しボタンを2個にして、現在の場所の高度を+と−操作で設定でき、且つ記憶する
たったこれだけの処理に、4kバイト必要なのか!!
LPC810にモニタを移植してみる
ソフトウエアでポートをHigh、Low出力してI2Cを動かす
追加部品は、マルツパーツからセットを購入しています。
mbed LPC1768 など、他のARM基板の使用例PIC32MXと速度比較(1MHz換算)
付属CDには onodera フォルダに、onodera.zip という名の、温度と気圧を液晶に表示するプロジェクトファイル( zip Archive )があります。この中にコンパイル済みの実行ファイル、LPC810_DEMO.hex があるので、これを、FlashMagic で LPC810 に書くと、温度と気圧を表示してくれますが、高度計に改造するには、ソースを変更して再コンパイルする必要があるので、まず、LPCXpresso の使い方を習得するため、ソースをそのまま再コンパイルしてみます。
付属CDに収納されている温度と気圧を液晶に表示するデモソフトのソースを再コンパイルするため、LPCXpresso で、新しいプロジェクトフォルダを指定します。ここでは、E:\nxp\kiatsu
OKを押すと、ブランクプロジェクトのウインドウになります。
プロジェクトフォルダ、E:\nxp\kiatsu に、付属CDに収納されている、onodera.zip をコピーしておき、
左下の、Start here にある、Inport project(s) を左クリックすると、以下のようなダイアログが開くので、Archive 欄に、onodera.zip を指定して、Next を押すと、
以下のように、LPC810_DEMO にチェックが入ったプロジェクトの選択がでるので、そのまま、Finish を押します。
以下のような、LPC810_DEMO のプロジェクト画面になります。
ここで、以下の図のように、LPC810_DEMO を選択しておいて、メインメニューの、project のプルダウンメニューの、Build All を選択すると、プロジェクトに設定されているソース群のコンパイル、リンクを行いますが、
以下のように、エラーになってしまいます。
IDEの右下の、Build Console [LPC810_DEMO]には、以下のようなエラーメッセージが出て、どうやら、ヘッダーファイルの、lpc8xx_i2c.h が見つからなくて、コンパイルすらできないということみたいです。
LPCXpresso の使い方をよく理解していないので、おそらくインクルードファイルに、lpc8xx_i2c.h が無いか、パスをしていしていないと推定し、とりあえず lpc8xx_i2c.h がどこにあるか検索すると、onodera.zip には無く、LPCXpresso 開発環境をインストールした、C:\nxp\LPCXpresso_6.1.0_164\lpcxpresso\Examples\NXP\LPC800\ フォルダに、LPC8xx_Libraries.zip があって、圧縮されたファイルの中に見つかりました。検索結果からでは場所がわからないので、
lpc800_driver_lib を検索すると、以下のように所在がわかりました。
つまり、必要なヘッダーファイルは、圧縮されたままなので、LPCXpresso でインクルードパスを追加しても読めないということみたいです。
では LPC8xx_Libraries.zip を解凍して LPC810_DEMO プロジェクトに追加すればいいのではないか、と思って解凍すると、たくさんのファイルがあって、どれも必要なように見え、なにをどこにコピーするのかさえ判りません。解凍したファイルを、適当に LPC810_DEMO プロジェクトに追加して何度か Build All を試しましたが、「何々が無い」ばかり出てきて、先に進めません。
2時間ほど試しても、らちが明かないので、LPC8xx_Libraries.zip を解凍したフォルダを調べていると、3個のフォルダがあって、各フォルダには、.poject なるものがあって、どうやら、プロジェクトファイル一式だと判るようになりました。 .project ファイルは、onodera.zip にもあったので、zip ファイルがそもそも、プロジェクトの設定のすべてを含んでいるらしく、適当にファイルを追加しただけではだめだということが判りました。手順にのっとって LPCXpresso で、Build しないとだめだということみたいです。
そこで、LPCXpresso_6.1.0_164\lpcxpresso\Examples\NXP\LPC800\LPC8xx_Libraries.zip を、onodera.zip を置いたフォルダ、E:\nxp\kiatsu にコピーして、Import project(s) で追加してみると、
LPC8xx_Libraries.zip を、onodera.zip を置いた同じフォルダにコピーして、LPCXpresso で開くと、
以下のようになるので、チェックが入った状態で、Finish を押します。
以下のように、4個のプロジェクトが対象となっているIDE画面になります。
これで、Build All すると、4個のプロジェクトに対してコンパイルが進み、最後に CMSIS_CORE_LPC8xx のコンパイルで終わるみたいですが、IDE画面を見ると、以下のように、LPC810_DEMO の部分には、赤色のX印が付いたままです。
LPC810_DEMO\Release フォルダを見ると、LPC810_DEMO.hex の日付が、付録のCDに収納されている元のままで、今回のコンパイル操作で作成されていないのが判ります。
LPC810_DEMO.map ファイルは更新されていましたが、中身が途中で切れていて、なんらかの理由で中断したみたいです。Console にはその結果は残っていなかったので、( どうも、4個のプロジェクト単位に、Console のアウトプットはリセットされて、先にコンパイルされたものは残らないようです )
今度は、個別に Build してみると、
CMSIS_DSPLIB_CM0 は、build できないようなので、次のlpc800_driver_lib に移ります
どうやらコンパイルできたようで、LPC810_DEMO.hex の日付が更新されています。中身も、付属CDの onodera.zip にあるものとまったく同じになっていました。
これで、やっと、LPC810のソフトの変更ができる環境が整いました。
後で、トランジスタ技術の記事を詳しく読んで見ると、LPC8xx_Libraries.zip をコンパイルし、後で、onodera.zip をコンパイルすると書いてあった。( P.97 -- P.98 )_| ̄|○
温度センサと、EEPROMを外し、簡易に高度を表示したもの。単位メートルが2個出てますが、これはmmではなく、バグ。気圧センサは光が当たると、数値が増加し、直射日光では、10%ほど増加して、1100hPaにもなるので、黒く塗った厚紙で覆っています。1013.2hPaを0メートルとし、−8.8hPaで10m高くなるような計算をしています。この例では、1017.7hPaなので、4.5*(−8.8)==> −39.6mで、計算は合ってます。
以下は、デモソフトに簡易高度表示を入れたもの。
// #define TEMP は外して有ります。
// PutI2C(ADT7410_CONFIG, 0xC0, ADT7410_ADDR);// これもコメントアウト。
#ifdef BAR
// Barometer
// get press_high
GetI2C(LPS331_PRESS_OUT_H, LPS331_ADDR);
press=I2CMasterRXBuffer[0]*0x10000;
// get tpress_low
GetI2C(LPS331_PRESS_OUT_L, LPS331_ADDR);
press+=I2CMasterRXBuffer[0]*0x100;
// get press_xl
GetI2C(LPS331_PRESS_POUT_XL_REH, LPS331_ADDR);
press+=I2CMasterRXBuffer[0];
// press/=4096; // for hPa
// press/=41; // ←これでは、誤差が−0.1%ほど出る。
press*=100;// 100倍して、4096で割ると、40.96 で割ったことになる。
press/=4096;
itoa(press,msg,10);
LocateLCD(0,1);
PutsLCD(msg);
PutsLCD("Pa ");
press /= 10;// 0.1hPa 単位 ここから高度への変換計算
j = 10132 - press;// 10132:海抜0メートルの標準気圧(0.1hPa 単位)
j *= 88;// この比率は、海抜0m〜1000mぐらいまで
j /= 100;
k = 0;
if(j < 0)//
{
k = 1;
j = 0 - j;
}
if(k)
msg[0] = '-';
else
msg[0] = ' ';
itoa(j,&msg[1],10);
LocateLCD(0,0);// 液晶の上段、温度表示をしていたところ。
PutsLCD(msg);
PutsLCD(" m");
WaitN(1000);
現在の場所を、押しボタンを押すと、高度0メートルとする改造。
高度の計算ができたところで、標準気圧1013.2hPa(高度0メートル、15℃)のみを基準とすると、気圧は刻々変化するので、今いる場所以外の高度を知るには、表示された高度の差を計算しないと実際の高度が出ません。また、高度の判っている場所での表示を覚えていないと計算すらできません。
そこで、LPC810 の余っているピンに、タクトスイッチを付けて、GND側に落とすように配線し、このスイッチを押すと、現在表示されている高度をレジスタに記憶しておき、次に高度を表示するときは、測定した高度から、レジスタの値を引いて表示するようにします。こうすると、現在の場所が0メートルとなり、以降の別の場所の計測では、ボタンを押した場所からの、相対高度を表示できるようになります。
なお、タクトスイッチはもう一個つけて、機能アップできるようにしておきます。(付録基板のLPC810では、スタンドアローン動作する場合では、2本余る。)以下が付録基板のLPC810付近にタクトスイッチ、SW2,SW3を追加した回路図
押しボタンスイッチを押したとき、現在の場所を0メートルとするには、補正値を記憶するレジスタに、読み取った高度を書き、次に新しく読んだ高度を計算した後、その補正値を引いてから表示すればよいのですが、押しボタンの読み取りとその後の判定をやりやすくするため、内部にカウンタをいくつか設けます。
気圧の読み取りは、1秒間隔で行っていますが、そのための時間待ち、WaitN(1000); をもっと短くして、ループ回数が20になったら気圧を読んで表示するという時間制御にします。このため、loopc というカウンタを設けます。カウンタが20になったら気圧を読んで計算して表示します。
WaitN(1000); を、WaitN(50); として、50msに一回、押しボタンの状態を読んで、Lowならカウンタを+1し、2になったら、補正値を変更するようにします。このために、押しボタンがLowの状態が続くとカウントする、setcount と、2回以上になったら1にするフラグ、writef を設けます。
押しボタンの状態を、50msの時間間隔で読んで、( 約50ms〜100msで writef フラグがセットされる)フラグが1なら、気圧を読んで表示する前に補正値を書き換え、直ちに高度計算に反映させます。これらの処理のソースが以下です。main.c
//**********************
// Barometer and Temperature for LP810-DEMO board
//
// (C)Copyright 2013 All rights reserved by Y.Onodera
// http://einstlab.web.fc2.com
// modified 2014.3.1 by bitcraft
//**********************
#include
#include
#include "LPC8xx.h"
#include "mrt.h"
#include
#include "type.h"
#include "lpc8xx_clkconfig.h"
#include "lpc8xx_i2c.h"
#include "I2C.h"
#include "LCD.h"
#include "ADT7410.h"
#include "LPS331.h"
#include "LED.h"
#if defined(__CODE_RED)
// #include
// #include
// __CRP const unsigned int CRP_WORD = CRP_NO_CRP ;
#endif
void SwitchMatrix_Init();
//void IOCON_Init();
#define PCF8591_ADDR 0x92
#define M24LC64_ADDR 0xA0
int main(void)
{
char msg[10];
int temp;
int l,m;
long press;
long altitude,bias;
int loopc,setcount,writef;
// IRC 12MHz
/* Configure the core clock/PLL via CMSIS */
SystemCoreClockUpdate();
/* Initialize the GPIO block */
// gpioInit();
/* Initialize the UART0 block for printf output */
// uart0Init(115200);
/* Configure the switch matrix (setup pins for UART0 and GPIO) */
SwitchMatrix_Init();
// IOCON_Init();
InitLED();
InitI2C();
mrtInit(SystemCoreClock/1000);
InitLCD();
// set 16bit resolution
// PutI2C(ADT7410_CONFIG, 0xC0, ADT7410_ADDR);
// Power ON Cycle=1Hz
PutI2C(LPS331_CTRL_REG1, 0x90, LPS331_ADDR);
//
bias = 0;
//#define EEPROM
//#define ADC
//#define TEMP
loopc = 0; setcount = 0; writef = 0;
#define BAR
while(1)
{
#ifdef EEPROM
// EEPROM 0x0002=0x5A
PutEEPROM(0x00, 0x02, 0x5A, M24LC64_ADDR);
WaitN(5);
for(l=0;l<0x1FFF;l++){
// EEPROM for 24LC64
GetEEPROM(l>>8, l&0xFF, M24LC64_ADDR);
temp=I2CMasterRXBuffer[0];
LocateLCD(0,0);
PutsLCD("ADR=");
itoa(l,msg,16);
PutsLCD(msg);
LocateLCD(0,1);
PutsLCD("DAT=");
itoa(temp,msg,16);
PutsLCD(msg);
PutsLCD(" ");
onLED();
WaitN(500);
offLED();
WaitN(500);
}
#endif
#ifdef ADC
// A/D for PCF8591
GetI2C(0x40, PCF8591_ADDR); // ch0 with D/A enable
// GetI2C(0x01, PCF8591_ADDR); // ch1
// GetI2C(0x02, PCF8591_ADDR); // ch2
// GetI2C(0x03, PCF8591_ADDR); // ch3
temp=I2CMasterRXBuffer[0];
itoa(temp,msg,10);
LocateLCD(0,0);
PutsLCD("A/D=");
PutsLCD(msg);
PutsLCD(" ");
onLED();
// D/A for PCF8591
PutI2C(0x40, temp, PCF8591_ADDR);
WaitN(500);
offLED();
WaitN(500);
#endif
#ifdef TEMP
// Temperature
// RDY?
do{
GetI2C(ADT7410_STATUS, ADT7410_ADDR);
temp=I2CMasterRXBuffer[0];
}while(temp & 0x80);
// get temp_high
GetI2C(ADT7410_TEMP_H, ADT7410_ADDR);
temp=I2CMasterRXBuffer[0]*0x100;
// get temp_low
GetI2C(ADT7410_TEMP_L, ADT7410_ADDR);
temp+=I2CMasterRXBuffer[0];
// temp/=128; // for C
// temp/=13;// これは荒い計算
temp *= 10;
temp >>=7;
itoa(temp,msg,10);
l=strlen(msg);
msg[l]=msg[l-1];
msg[l-1]='.';
msg[l+1]=0;
LocateLCD(0,0);
PutsLCD(msg);
PutsLCD("C ");
onLED();
WaitN(500);
#endif
#ifdef BAR
loopc++;
if (( LPC_GPIO_PORT->PIN0 & (0x1<<2) ) == 0)// 緑プッシュボタン( SW2 )が押された
{
setcount++;
if(setcount >= 2)// 0.1sec
{
writef = 1;
// ボタンを押し続けると、約10秒で再度設定する。
setcount = -200;// 10 sec
}
}
else
{
setcount = 0;
}
if(loopc >= 20)// 50ms X 20 ---> 1 sec
{
loopc = 0;
// Barometer
// get press_high
GetI2C(LPS331_PRESS_OUT_H, LPS331_ADDR);
press=I2CMasterRXBuffer[0]*0x10000;
// get tpress_low
GetI2C(LPS331_PRESS_OUT_L, LPS331_ADDR);
press+=I2CMasterRXBuffer[0]*0x100;
// get press_xl
GetI2C(LPS331_PRESS_POUT_XL_REH, LPS331_ADDR);
press+=I2CMasterRXBuffer[0];
// press/=4096; // for hPa
// press/=41;// これは、荒い計算。ほぼ −0.1% の誤差になる。
press *= 100;
press/=4096;// 40.96 で割ったのと同じ。press >>= 12; でもよい。
itoa(press,msg,10);
LocateLCD(0,1);
PutsLCD(msg);
PutsLCD("Pa ");
altitude = 10132 - press/10;
altitude *= 88;// 1013 hPa 付近の気圧と海抜の比。10hPa で、約 88m に相当する。
altitude /= 100;// この比率は、海抜1000mぐらいまで、ほぼ有効
offLED();
if(writef)
{
bias = altitude;
onLED();// 補正値を変更した。1秒間点灯する。
writef = 0;
}
altitude -= bias;
itoa(altitude,msg,10);
LocateLCD(0,0);
PutsLCD(msg);
PutsLCD(" m ");
}
WaitN(50);// 50ms
#endif
}
}
この機能を入れると、高度が判っている場所を0メートとして記憶しておくと、電源を切っても再現できるので、測りたい場所まで電源を切っておくことができます。幸い、トランジスタ技術のデモソフトには、基板につけるEEPROMのリード、ライトのルーチンがコメントアウトされて残っているので、このEEPROM制御ソフトを復活させて、上記の機能を入れます。電源を入れたとき、補正値をレジスタに読みますが、特定のアドレスに補正値が書かれているとして、まずそれを読み出します。番地は、0x1F00としています。ここにデータが有効である意味のフラグ、1が書いてあると、続く2バイトが補正値として、変数、bias に設定します。
GetEEPROM(0x1F, 0, M24LC64_ADDR);// 気圧の補正値フラグのアドレス
bias = 0;
if(I2CMasterRXBuffer[0] == 1)// 補正値フラグが1なら、16ビットの値を読む
{
GetEEPROM(0x1F, 1, M24LC64_ADDR);
bias = I2CMasterRXBuffer[0];
bias &= 0xFF;
GetEEPROM(0x1F, 2, M24LC64_ADDR);
m = I2CMasterRXBuffer[0];
m <<= 8;
if(m & 0x8000)// I2CMasterRXBuffer が、uint8_t なので、符号拡張
m |= 0xFFFF0000;
bias += m;
}
これで、電源を切る前に設定した、bias 値が復旧するので、基準となる場所を0として、気圧から、高度を計算できます。無論、この基準値は、時間経過で付近の気圧が変化しないという条件の高度になります。
次に、押しボタンをおしたとき、現在の補正値を、EEPROMに書く処理を追加します。ここで、long 変数、l が追加されてますが、これは後で機能追加する準備です。
if(writef)
{
l = altitude;
PutEEPROM(0x1F, 0x0, 0x1, M24LC64_ADDR);// フラグを1にする。
WaitN(5);
PutEEPROM(0x1F, 0x1, l & 0xFF, M24LC64_ADDR);// 現在の高度データを書く。
WaitN(5);
PutEEPROM(0x1F, 0x2, (l>>8) & 0xFF, M24LC64_ADDR);//
WaitN(5);
bias = l;
onLED();// 補正値を書いた。
writef = 0;
}
altitude -= bias;
itoa(altitude,msg,10);
LocateLCD(0,0);
PutsLCD(msg);
PutsLCD(" m ");
これらを追加した、main.c はこちら → main.txt テキストファイルですが、main.c に変換して使ってください。
現在の場所の高度を+と−操作で設定できるようにし、記憶する
少ない押しボタンで、いろいろな設定をできるようにするには、押し方(押す順番、押してる時間、放す順番、押していた時間)を区別して行いますが、2個のボタンでは、それほどバリエーションを持たすことはできません。そこで、以下のような機能を追加する方法を考えます。
現在の高度を0mとし、それをSROMに記憶する。電源ONで、高度が、0mになる。
現在の高度を設定できるようにする。+1m、−1m単位。
現在の高度になるような、補正値をSROMに書いて、電源ONで、現在の高度が出るようにする。
ここで、2番は、2個の押しボタンに対応すればすぐにでも実現しますが、1番と3番は、2個のボタンの押し方で区別するしかありません。そこで以下のような押し方の区別で判定します。2個のボタンを、それぞれ、SW2,SW3とします。
SW2が短い時間押され、放された。放されたとき、SW3は押されていない。高度−1m −−>高度
SW3が短い時間押され、放された。放されたとき、SW2は押されていない。高度+1m −−>高度
SW2を、2秒以上押してる状態から、SW3を押す。SW3は100ms押されているとする。この場合、SW3が押されなければ、この機能は無効にする。−−> 現在の高度を0mとして、SROMに書く。
SW3を、2秒以上押してる状態から、SW2を押す。SW2は100ms押されているとする。この場合、SW3が押されなければ、この機能は無効にする。−−> 現在表示されている高度を記憶してSROMに書く。
上記の改造をした main.c です。テキストファイルなので、main.c と名前を変更して、プロジェクトのソースに上書きして、再コンパイルすると、 LPC810_DEMO.hex になります。
さて、できあがった実行ファイルのサイズは、ほとんど4kバイトになっています。
たったこれだけの処理に、4kバイト必要なのか!! と驚いてしまいます。
4kバイトあれば、もっといろいろなことができるはずなのでは?・・・・・と言うのが率直な感想です。
そこで、ソースを追跡すると、気圧を表示するだけなのに、標準的な32ビットのARMチップを動作させるBIOS処理などが、そのままになっており、不必要なベクタテーブルや、使わない割り込み処理関数もそのままなので、これらを削っていこうと思います。
とはいうものの、汎用ARM向けのソース構成で作成されているので、LPCXpresso の開発環境を崩さないと小さくできそうもありません。コンパイラはGCCなので、GCCを使って必要な部分のみを使うようにすれば、もっと小さくなるはず・・・・・・。これだけの簡単な処理しかできないのであれば、わざわざ8ピンのARMを使う意味がありません。PICや、AVRのほうがずっと存在価値があります。
そこで、GCCでLPC810を使う例を検索してみると、 ELM ChaN のNXPの小ピンARMの試食 に、トランジスタ技術のサンプルソースを使わない、Lチカと、UARTサンプルが見つかりました。
特に、Lチカは、非常に短く、公開されているソースをコンパイルしてみると、373バイト( 0x175 )しかありません。 無論、PICならもっと短くなりますが。
以降は、 ELM ChaN 氏 の、NXPの小ピンARMの試食 で公開されているソースを参考に、サイズの短いコードで、気圧計として機能するものを作成していこうと思います。とは言うものの、多機能なLPC810の初期設定やBIOS設定は、LPC810の膨大なマニュアルを確認しながらなので、紆余曲折があったとしても、そのまま記録したいと思います。
上記のELM ChaN 氏 の、NXPの小ピンARMの試食 で公開されているソースを見ると、簡単なモニタが走り、任意番地からのメモリの、ダンプや、変更などができるようになっていますが、当サイトの他のCPUで使ってきたモニタを移植します。ELM ChaN 氏 のソースを見ると、UARTの受信が割り込み処理になっているので、これをポーリング受信に変えます。当サイトでしばしば使っているモニタは、受信をポーリングで行うというのを一貫してきているので、LPC810でもそうします。UARTの受信は、割り込み処理にすると、いつシリアルで送られてきても、受信を取りこぼすことがないというメリットがある反面、モニタの移植段階で、割り込み関連も移植する必要があり、手間がかかるからです。またRAMも使ってしまいます。
「組み込みCPUの道具箱」の主旨としては、UARTの送信、受信が出来た段階でモニタを移植し、CPU内臓周辺デバイスのレジスタを直接設定して、「プログラムのソースの変更、コンパイル、実行で結果を見る」 という手順を省きます。幸い、ARM ( Cortex ) の周辺回路のレジスタ群は、比較的よく整理されており、マニュアルの説明も判りやすいので、UARTが動作した段階で、I2Cとかの設定レジスタをモニタで操作して、動作を確認していきます。ある程度動作の確認ができると、モニタのソースに追加し、自動設定して、モニタを起動し、次のI/Oの動作チェックに移ります。
さて、モニタを移植すると言っても、機能を欲張るとどんどん膨れ上がってしまうので、最低限の機能にしぼり、メモリ(周辺回路設定レジスタ)の表示と変更(コピーを含む)が出来れはよしとします。
メモリの表示、変更、コピーができる、LPC810用モニタ。 解凍して、uart2t フォルダに移動し、path1.bat を実行すると、LPCExpresso をインストールしている、ツールチェーンにパスを通します。( LPCExpresso のバージョンによって、名称が異なることがあり、その場合は、path1.bat を編集してください)makefile は、ELM ChaN 氏 のオリジナルのものと、ほとんど同じです。
モニタを移植して動作テストするため、小さなユニバーサル基板を作成しました。写真の左の基板。基板の左は、USB−UART変換ケーブルで、3.3VのUARTのTX,RX、そして電源5Vを接続しています。3.3VはLDOで作っています。右は、気圧表示に使ったトラ技の付属基板で LPC810 を外してあります。I2C 信号、3.3Vをユニバーサル基板と接続して、ユニバーサル基板から、圧力センサのデータを読めるようにしています。トラ技の付属基板には、外部から3.5V〜5Vを供給すると、LDOで3.3Vを作って、3.7VのLipo電池などで動作させることができるコネクタを、引き出しています。
ユニバーサル基板の回路です。右下のジャンパーピンは、左に入れて、リセットボタンを押すと、Flash Magic でプログラムを書き変えることができます。
簡単なモニタが動作する上記のソースを元に、ARM LPC810 が、リセット解除後、どのような処理をして、モニタが動作するようになるのか、順を追って見てみます。
リセット解除後はどこから実行するのか? → 内部のブートローダーが、0番地から始まる8個のベクター(32ビット値を8個)を加算して結果の32ビット(桁上がりは無視)が0x00000000になると、内臓フラッシュメモリの命令コードは正しいとして、0x00000000番地の32ビット値をスタックポインタ、0x00000004番地の32ビット値をプログラムカウンタとして、フラッシュメモリの命令を実行します。
もし、加算値が正しくなければ、USART0に、ホストPCから送られるプログラムを、フラッシュメモリに書くローダーが走ります。ここでは、1番の正しかったとして、3に移ります
startup81x.c が、Cで書いた、ベクターテーブルですが、134行の、Reset_Handler が最初に走るプログラムです。
BODCTRL というレジスタに0x15を設定していますが、これはブラウンアウトの設定です。
次に、クロックは何かを設定しますが、外部に何も付けていないので、内部のオシレータを使う設定となっています。約12MHzのオシレータとなっています。#define 文で、CLK_SEL は、3としています。
145行から、165行で、CPUのクロックを設定が終わります。
169行から、170行で、初期化付きの変数を、フラッシュメモリからコピーしています。Cで書いたプログラムを、ROM化するときの、常套手段です。
これで、基本的な設定が終わったので、ユーザープログラム、main() に実行を移します。プログラムカウンタとスタックポインタ、ブラウンアウトと、クロック、そして初期化付き変数領域の設定以外の、マイコンの周辺モジュールの設定は、main() の先頭などで行います。
main.c の、52行、__disable_pin_function(SWCLK_EN | SWDIO_EN); は、LPC81x.h で宣言されたレジスタ設定で、関数みたいな記述ですが、レジスタに値を書いているだけです。( ここで余談ですが、関数でもないのに、関数みたいな記述はあまり好きではありません。素直に、PINENABLE0 |= (SWCLK_EN | SWDIO_EN); と書けばいいのではないでしょうか? )
同じく、56行の、__enable_ahbclk(PCGPIO | PCIOCON); は、SYSAHBCLKCTRL |= (PCGPIO | PCIOCON); です。
次に、uartclk_init(); は名前のごとく、UARTのボーレートの設定で、uart81x.c の、17行の関数を呼び出して行っています。
次に、UART0を、どのピンに割り振るかの設定ですが、__attach_function(U0_TXD_O, 4); は、PINASSIGN[U0_TXD_O/4] = (PINASSIGN[U0_TXD_O/4] & ~(0xFF<<((U0_TXD_O%4)*8))) | (4<<((U0_TXD_O%4)*8)); となります。なにをやっているのか、これではすぐにはわかりません。マニュアル( UM10601 )を追って、ビット構成をたどっていくしかありません。
同じく、__attach_function(U0_RXD_I, 0); は、__attach_function(U0_RXD_I, 4); は、PINASSIGN[U0_RXD_I/4] = (PINASSIGN[U0_RXD_I/4] & ~(0xFF<<((U0_RXD_I%4)*8))) | (0<<((U0_RXD_I%4)*8)); となります。
次に、UART0の、ストップビットや、パリティーの設定関数、uart0_init(); を行って、モニタで使う周辺機器の初期化は終了です。
以下は、モニタを動作させるのに必要なレジスタ設定です。レジスタは、モニタから確認しています。
レジスタの設定 すべて32ビットレジスタ
レジスタ名
アドレス
ビット位置
解説
設定値
意味
BODCTRL
4004 8150
D1-D0
リセットレベル
01
2.1V でリセット、2.2V で解除
BODCTRL
4004 8150
D3-D2
割り込みレベル
01
2.3V で割り込み、2.4V で解除
BODCTRL
4004 8150
D4
リセット有効
1
1でリセット有効
PDRUNCFG
4004 8238
D7 を0に設定
水晶発振、WDT、PLLの電源
0x0000ED70
System PLL を使う
SYSPLLCLKSEL
4004 8040
D1-D0
クロック選択
00
内臓CR発振を使う
MAINCLKSEL
4004 8070
D1-D0
クロックソース選択
11
PLLの出力を使う
SYSPLLCTRL
4004 8008
D4-D0
PLL逓倍数ー1
00100
5倍で、12x5=60MHz
SYSPLLCTRL
4004 8008
D6-D5
PLLポスト分周
10
4分の1
SYSOSCCTRL
4004 8020
D1-D0
バイパス無し
00
設定していない
MAINCLKUEN
4004 8074
D0
クロック更新
0x0 , 0x01
0を書いて、1を書く
SYSAHBCLKDIV
4004 8078
D7-D0
クロック分周
0x02
SYSAHBCLKCTRL
4004 8080
D14を1に設定
UART0用クロック
0x000440DF
クロックを供給
UARTCLKDIV
4004 8094
D15-D0
ボーレートクロック分周
0x20
ボーレート設定
BRG
4006 4020
D15-D0
ボーレート分周
0x0
0で分周なし
CFG
4006 4000
D15-D0
ビット数など
0x00000005
8bit,1stop,none,no-flow,async
ソフトウエアでポートをHigh、Low出力してI2Cを動かす
I2Cで他のスレーブICと接続する場合、たいてい内臓しているI2Cコントローラを使いますが、ソフトウエアのみでポートを制御してI2Cの信号を出し、ICと通信してみます。いまどきのマイコンのポート(GPIO)は、オープンドレイン出力とか、プルアップ付き入力が可能なので、I2Cコントローラのポート割付けができない場合でも、I2C制御を行うことが出来ます。UARTと違ってクロック同期なので、処理中に割り込みが入って、タイミングがずれても、マスターなら、問題なく制御することが可能です。同じ理由で、完全ソフトウエアポート制御によるSPIも可能です。(無論マスター側での話ですが)
I2Cは、ノイズに弱いなど、いろいろ問題のある通信方法ですが、2線式、簡便さがものを言って、衰える気配がありません。
ここで、I2Cの信号の基本的な仕様をまとめてみます
クロックとデータラインで構成され、双方向通信にするため、オープンドレイン(オープンコレクタ)でドライブされ、外部にプルアップ抵抗を接続します。
オープンドレインであるため、Lowがアクティブ (データが1という意味ではない) 信号となります。
リード側は、クロックの立ち上がりで、データを読み、ライト側はクロックがたち下がった少し後、次のデータに変化させます。
データはMSBから先に出力されます。
通信が行われていない状態(アイドル状態)は、クロック、データともにHigh。つまり、どちらも非アクティブで、Lowにドライブされていない状態。
通信開始は、クロックがHighの状態でデータが先にLowになり、その後(時間規定はあるが、通常1μ秒あれば十分)クロックがLowになることで開始します。旧フィリップスの仕様書を置いてるところ。→ http://ekousaku.web.fc2.com/doc/I2C.pdf
通信終了は、データがLowの状態で、クロックが先にHighになり、その後、データがHighになることで完了します。
上記2項目の重要な点は、クロックがHighのときに、データが変化することで特別な意味を持たせてあります。データの送受信を行うときは、クロックがLowのときのみデータを変化させます。
通信は、アイドル状態のとき、どれかのマスターが、通信相手のスレーブアドレス(7ビット)+リードライトビットの1バイトを送信し、1ビットの返信を読んで開始します。返信の1ビットがLowだと、その通信相手が存在し、続けてデータ転送を行えると判断します。
上記のように、通信開始は、必ずスレーブアドレス+リードライトビットから始まります。
通信データは1バイト単位ですが、その1バイトに続いて返信の1ビットが付加されています。返信の1ビットは、データ8ビットの通信方向とは必ず逆になっています。このため、8ビットの送受信の後、必ずデータラインのLowドライブ権が入れ替わります。
リードライトビットが1のとき、続くデータ転送は、マスターがスレーブからのデータを読む通信になります。マスターは1バイト読むと、1ビットのLowの返信をドライブしますが、複数のデータを読む場合、最後のデータの後は、返信の1ビットをHighとします。
リードライトビットが0のとき、続くデータ転送は、マスターがスレーブにデータを書く通信になります。1バイト送信後、スレーブからの1ビットの返信がLowなら書けたことになり、Highなら、データが書けたか否か不明で、通常転送エラーと判断して、通信を終了します。
スレーブ側は、データの用意が間に合ってなかったり、受信したデータを処理中は、クロックをLowにドライブすることができ、その間マスターに待機させることができます。このためマスター側は、クロックをHighにするべくLowドライブをやめても、クロックがHighにならなければ先に進んではいけません。
以下は、スレーブの内部レジスタに1バイトのデータを書く例、赤:マスターがドライブ 、青:スレーブがドライブ
ここでは、データアドレスは1バイトですが、メモリICなどでは、2〜3バイトになることがあります。この場合、バイト列の順番は、ビッグエンディアンになっています。
具体的には、圧力センサ、LPS3331 の、圧力、温度の変換インターバルを、両方とも1秒に設定するコマンドです。
スタート
スレーブアドレス(7bit)
R/Wビット(1bit)
ACK(1bit)
データアドレス(8bit)
ACK(1bit)
ライトデータ(8bit)
ACK(1bit)
ストップ
1011100(7bit)
0(1bit)
0(1bit)
00100000(8bit)
0(1bit)
10010100(8bit)
0(1bit)
以上のことを念頭におきながら、ポートを制御してI2C信号を生成し、圧力センサ(LPS331)から、圧力と温度を読んでみます。
LPC810のGPIOは、ビット単位の制御がやりやすい
ポート出力で特定のビットを書き換えるには、単純なGPIOでは、
ポートを読む(もしくは、プログラム内で最終出力データを記憶しておく)
前の状態から特定のビットを変更する
そのデータをポートに書く
の手順になり、ポート操作のライブラリ関数などでは、ポート番号、ビット番号などを指定して呼び出すと、インライン展開されるか、サブルーチンを呼び出すかになります。
この点、LPC81xでは、(他のメーカーのARMでも同じなのだろうか?)上記の機能はもちろん使えますが、
指定のピットを1にするアドレスが割り付けられている。ビットは複数同時指定が可能
指定のピットを0にするアドレスが割り付けられている。ビットは複数同時指定が可能
指定のピットを反転するアドレスが割り付けられている。ビットは複数同時指定が可能
指定のピンだけリード、ライトできる。1ピンあたり1バイト用意されていて、0か1を書くとポートピンに反映され、また読むと、ピンの状態を、1バイトのLSBに反映される。PIO0_2 ピンなら、0xA0000002が、そのバイトアドレスである。
指定のピンだけリード、ライトできる。1ピンあたり1ロングワード用意されていて、0を書けばLowになり、0以外の値を書くとHighになる。ピンがLowなら、0を読み出せ、Highなら、0xFFFFFFFFが読み出せる。PIO0_2 ピンなら、0xA0001008が、そのロングワードアドレスである。
があって、変更しない他のビットデータを用意しておく必要がありません。(逆を言えば、今何が出力されているか、プログラム内で管理しておく必要がでてくることがある)このため、高速なポート出力制御が可能になります。
ポートの選択と、初期化
8ピンのICなので、UARTを使ってしまうと、電源(2本)、リセット(1本)、UART(2本)で、残りは3本しかありません。残りのPIO0_3をクロック(3pin),PIO0_2をデータ(4pin)として使います。あと1本ありますが、発信器から正確なクロックを入力できるように残してあります。
GPIOとして使う場合は、GPIOモジュールにクロックを供給する必要がありますが、モニタを移植するのに使ったもとのソースに、
__enable_ahbclk(PCGPIO | PCIOCON);
とあるので、追加は不必要ですが、ポートをどのように使うかを決めるには、IOCONで、オープンドレインとか内部プルアップなどの設定し(0x40044000から、1ピン毎に32ビットのレジスタ、PIO0_0〜PIO0_17がある)、DIR0、PIN0、SET0、CLR0、NOT0、などで、Low、High、ポートの入出力を決めます。これらは、NXP の、UM10601.pdf に解説されています。
ソフトのみでポートを制御して、I2C信号を作成し、気圧センサから、気圧と温度を読み出す機能を入れた、モニタ。
uart6t.lzh
上のプログラムを書いて、teraterm で操作した例。
1003.2 hp 28.3 ℃ と表示されていますが、その左のヘキサデータは、I2Cで読んだ生データです。
ここで、ii は、I2C の初期設定、ip は、気圧と温度を読むコマンドです。
準備中
ホームに戻る
SEO