LPC-Link2用ブレイクアウトボードを改版してみました
LPC-Link2用ブレイクアウトボードを改版しましたので頒布再開します。前回のものはJTAGコネクタが干渉していたので、切り欠き追加の改良をしてみました。
seen from Netherlands
seen from China

seen from Malaysia

seen from United States
seen from Türkiye
seen from Singapore
seen from United States

seen from Singapore
seen from Kenya

seen from Germany
seen from China

seen from United States
seen from United States

seen from Germany
seen from United States
seen from United States
seen from Germany
seen from United States
seen from Russia

seen from Russia
LPC-Link2用ブレイクアウトボードを改版してみました
LPC-Link2用ブレイクアウトボードを改版しましたので頒布再開します。前回のものはJTAGコネクタが干渉していたので、切り欠き追加の改良をしてみました。
ソフトウェアによるFMラジオ実装についてインターフェース誌に寄稿しました
ARMマイコンによるSDR実装について、CQ出版社のインターフェース誌2015年7月号に寄稿しました。昨年夏にこちらで紹介したソフトウェアだけで実装したFMラジオの仕組みを紹介しています。Cortex-M4をぎりぎりまで使ったRF信号処理の実践について解説しました。
Interface(インターフェース) 2015年 07 月号
LPC810(13) 〜 ライブラリ使用上の注意点
LPCXpressoで添付のライブラリ(CMSIS_CORE_LPC8xx、lpc800_driver_lib)を使って色々とやっているが、その時に気づいた点について今回はまとめておこうと思う。 ただし、どちらかというと自分自身の個人的見解(単なる文句)の羅列になっていると思うので、今回は余り技術的な情報は無いかもしれない。
なお、自分が使用しているライブラリはLPCXpresso Ver.6.1.0付属のライブラリであるため、新しい版では改善されているかもしれない(だったら良いが)。その点はご了承願いたい。
1.ライブラリlpc800_driver_libについて
一応“ライブラリ”という位置付けであるし、使っていて便利な関数がいくつか用意されているが、本当にライブラリなのだろうか。 どちらかというと“サンプルコード”集の意味合いが強く、使う場合に注意が必要である。
GPIO関連の設定を行うGPIOInitやGPIOSetBitValueを使った時に、使用ROM容量が想像以上に大きくなった。 これらの関数自体は非常に小さいし、何がそんなに肥大したのか生成されたmapファイルを調べてみたところ、勝手にGPIO関連の割り込みハンドラが登録されてしまっていることに気づいた(以下、mapファイルから一部抜粋)。
.bss 0x00000000 0x0 C:\...\liblpc800_driver_lib.a(lpc8xx_gpio.o) .bss.gint0_counter 0x00000000 0x4 C:\...\liblpc800_driver_lib.a(lpc8xx_gpio.o) 〜略 .text.PININT_Handler 0x0000049c 0x1e4 C:\...\liblpc800_driver_lib.a(lpc8xx_gpio.o) .text.PININT0_IRQHandler 0x00000680 0x10 C:\...\liblpc800_driver_lib.a(lpc8xx_gpio.o) .text.PININT1_IRQHandler 0x00000690 0x10 C:\...\liblpc800_driver_lib.a(lpc8xx_gpio.o) 〜略
勝手にbssセクションに変な変数も取られているし、そもそも自分はGPIOの割り込みを使った覚えはない。 これはlpc800_driver_lib内のファイルlpc8xx_gpio.cで、これら変数や割り込みハンドラが勝手に定義されていることに原因がある。
よくよく調べてみると、定義されていた割り込みハンドラはサンプルコードNXP_LPC8xx_SampleCodeBundleに含まれるサンプルプログラムと対応するものであり、要するに割り込みハンドラの使い方を示した単なるサンプルコードなのだ。
ただでさえROM/RAM容量の少ないLPC810に、このような贅肉が勝手についてしまうのは非常に困る。
lpc800_driver_libのソースコードは、該当のフォルダ中のsrcフォルダに入っており、これらのファイルをあらためて調べてみたところ“勝手ハンドラ”は以下のようになった。
ファイル名 勝手ハンドラ 説明 lpc8xx_bod.c BOD_IRQHandler BOD(Brown-Out Detect)ユニット、すなわち電源喪失監視ユニットで、電源が落ちそうになった時に発生する割り込みハンドラ。明らかにサンプルコード。 lpc8xx_clkconfig.c (なし) ウォッチドッグタイマ(WDT)用のクロック設定と、CLKOUT端子用のクロック設定関数が含まれる。WDTの方は使わない方が良い(WDTについては別項で説明する)。 lpc8xx_comp.c CMP_IRQHandler アナログコンパレータ用の割り込みハンドラが勝手に入っている。 lpc8xx_crc.c (なし) LPC810組み込みのCRCユニットを使うための関数群。 lpc8xx_gpio.c PININT0_IRQHandler 〜PININT7_IRQHandler GPIOピン割り込みのハンドラが勝手に入っている。上で述べた通り、いらない。 lpc8xx_i2c.c I2C_IRQHandler I2C関連の関数が定義されている。このハンドラもサンプルコードだ。 lpc8xx_mrt.c MRT_IRQHandler MRT(Multi-rate timer)ユニット。このハンドラも単なるサンプルコード。 lpc8xx_nmi.c NMI_Handler NMI_Initを使うと、勝手ハンドラがもれなく付いてくる。 lpc8xx_pmu.c (なし) PMU(Power Management Unit)、すなわち電源管理ユニットの関数群が含まれる。 lpc8xx_spi.c SPI0_IRQHandler 〜SPI1_IRQHandler ピン数の少ないLPC810ではSPIは使わないと思うが、I2C同様に、こちらも勝手ハンドラがもれなく付いてくる。 lpc8xx_uart.c UART0_IRQHandler 〜UART2_IRQHandler UART関連の関数群が含まれる。勝手ハンドラは完全なサンプルコードであり、いらない。 lpc8xx_wdt.c WDT_IRQHandler ウィンドウウォッチドッグタイマ(WWDT)の関数群なのだが、はっきり言ってこのファイル内の関数は全て使えない。WDTに関しては項を分けて詳しく述べるつもり。 lpc8xx_wkt.c WKT_IRQHandler WKT(Wake Timer)ユニット、すなわちスリープ中の定期起床タイマの関数群が含まれる。このまま使えそうな唯一のハンドラなのだが、自分独自の実装に置き換えた方が良いと思う。
これらのファイルに含まれるライブラリ関数を使う場合は、これら“勝手ハンドラ”をコメントアウトなど完全に削除してから使わないと、ROM容量を逼迫したり、いらぬ混乱を招く結果になるだろう。
なお、各ファイル内に定義されている(ムダな)変数は、使用している関数を削除さえしておけばリンカに無視されるので、面倒ならばわざわざ消す必要はない。
あらためて見てみると、各ファイルの先頭にあるヘッダコメントでもexampleと書いてあるから、これはライブラリでなく本当にサンプルコードであり、必要な関数のみコピペして使ってね、ということなのだろうか。何か釈然としないが。
2.ライブラリCMSIS_CORE_LPC8xxについて
システムクロックの項でも詳しく述べたが、このライブラリにはシステムクロック関係の以下の二つの関数が含まれている。
名称 説明 SystemInit システムクロックの設定を行う。 SystemCoreClockUpdate 現在のシステムクロックの周波数を取得し、外部変数SystemCoreClockに設定する。
ウォッチドッグタイマ用オシレータの周波数がユーザーズマニュアルと異なっている点については、既にシステムクロックの項で説明した。 ここでは、それ以外の点について言っておきたいことを以下にまとめておく。
(1) SystemInitはロックアップする恐れを孕んでいる。
いきなり酷い言い回しだが、このリスクは十分承知しておいた方が良い。
SystemInitはmain前のスタートアップルーチン(ResetISR)から呼び出される関数であり、内部でシステムクロックの設定を行っている。 システムクロックの設定なので、ところどころに非常にセンシティブな設定があるのは致し方ないと思うが、その中で例えば以下のようなコードがある。 目立たないが、一番右のカッコ脇にセミコロンがある点に注意。
while (!(LPC_SYSCON->MAINCLKUEN & 0x01)); /* Wait Until Updated */
これは、メインクロックの入力がマイコン内で安定するまで待機しているループである。メインクロックとはシステムクロックのソースクロックであり、各種設定後、LPC_SYSCON->MAINCLKUENレジスタのb0が1になることで、安定したことが示される。
SystemInit内には、このようないわゆるビジーウェイトで状態変化を待っている箇所が他にも二カ所ある(条件コンパイルの内容により変わる場合もあるが。)
このようなハードウェアに依存する終了条件のみのループは、全てロックアップする恐れを孕んでいる。必ず終わることが論理的に証明できないからだ。 上記例では、LPC_SYSCON->MAINCLKUENがいつまでたっても0のままであれば、永久にここで待ち続けることになり、それはすなわちロックアップ状態そのものである。
このようなことを言うと、メインクロックの状態が変なのだから、他にどうしようもないのであり、手段として待ち続ける以外なく、結果としてロックアップしてしまってもしようがないのでは、という意見が出てくると思うが、そういった人に私は問いたい。
例えば、あなたが使用しているPCなりスマートフォンなり、何でも良いが、そのような普段使っている機器が無反応になった場合、あなたはボケーと待ち続けますか? 恐らく10人中10人が、リセットボタンがあればそれを押すか、無ければ電源を入れ直すかするだろう。 当然、それで動き出すかどうかは分からないが、まずは試してみて、何回か電源を入れ直してもダメだったら、あらためて修理に出すなりするはずだ。
今回のケースもそうであり、ある程度待ってみて、ダメそうだったらリセットして最初からやり直してみてらどうですか、と言いたいのだ。 それでダメなら、その時こそマイコンの故障を疑うべきであり、ただ単に待ち続けるのは余りに能がない。
先に示したような状態待ちループは、本来ならばウォッチドッグタイマ(WDT)の監視下で行うべきであり、そもそも、このような時のためにWDTがあるのではないだろうか。 まぁ、シンプルに作っておきたかったから、SystemInitはこのようなコードになったのかもしれない。それでも、組み込みプログラミングにWDTは必須の機能だし、サンプルコードであれば、なおのことその点を示しても良かったと思うのだが...
この改善については、WDTの項で説明したいと思う。
(2) SystemCoreClockは分周された値を示す。
現在のシステムクロックは、SystemCoreClockUpdateを呼び出すことにより、外部変数SystemCoreClockに設定される。 例えば、SysTickタイマなどでは、この外部変数SystemCoreClockをもとに初期化すれば、システムクロックが変更された時も連動して設定し直されるので、可搬性の高いプログラムを作ることができる。
しかし、UARTのボーレート設定やIOCON(I/O Configuration)ユニットのデジタルグリッチ機能でクロック設定する時には注意が必要である。(デジタルグリッチ機能についてはGPIOについて説明した時に触れたいと思う。)
LPC8xxシリーズマイコンではクロックとして“メインクロック”と“システムクロック”の二種類のクロックがあり、マイコン自体の動作クロック数に相当する“システムクロック”は“メインクロック”を分周したものが使われている。この分周値はSYSAHBCLKDIVレジスタの値が使用される。
SystemCoreClockは“システムクロック”を示し、この分周後の値が格納されているのだ。しかし、UARTのボーレートジェネレータやIOCONユニットは分周前の“メインクロック”をソースクロックとしている。
SYSAHBCLKDIVレジスタはSystemInitで初期化されており、何も操作しなければ1で初期化される。そのため、通常はメインクロックとシステムクロックは同じ値となるが、この分周値を1より大きくした場合(〜255まで可能)は、SystemCoreClockはメインクロックの値と異なってくる。
だから、例えばUARTのボーレート設定を以下のようにSystemCoreClockから直接求めるのは正しくない(UARTCLKDIVはUART側の分周値)。
UARTSysClk = SystemCoreClock / LPC_SYSCON->UARTCLKDIV; /* ? */
本来ならばメインクロックの周波数から求めるべきだ。 この点について、先のSystemInitのロックアップと合わせて改善したものを、WDTの項で触れるつもりだ。
(3) SystemInit内の条件コンパイルについて
これは、はっきり言って自分のいちゃもんに近いが、とりあえず触れておこう。
SystemInit内では条件コンパイルでプログラムが分かれている。 このぐらいのサイズならば、まだ追跡して内容を把握することは可能だが、ちょっと気を許すと、いつのまにか条件コンパイルと通常の制御文が入り乱れた、追跡するのも理解するのも困難で面倒なプログラムになってしまう恐れがある。
何を言いたいのかと言うと、条件コンパイルなど使わなくとも、通常の制御文だけで条件コンパイルと同様の作用を及ぼすことが可能だ、ということが言いたいのだ。
ご存知の方は今更感が大きいと思うが、一応念のために触れておく。 例えば、条件コンパイルで以下のようなプログラムの“場合分け”をしたとする。
int main(void) { int i; #if SELECTOR == 0 for (i = 0; i < 200; i++) { ; } sample1(); #elif SELECTOR == 1 sample2(); #endif return 0 ; }
SELECTORを0、1、それ以外とし、生成されるコードを選択するつもりだ。 このままだと、オリジナルのSystemInit同様にSELECTORが0以外の時にウォーニング(iが使われてない)が出る。
実は、このプログラムでは条件コンパイルを使う必要がない。以下のようにコーディング可能である。
int main(void) { int i; if (SELECTOR == 0) { for (i = 0; i < 200; i++) { ; } sample1(); } else if (SELECTOR == 1) { sample2(); } else { ; /* 何もしない */ } return 0 ; }
これだとSELECTORの値に関わらず余分なコードが作られてしまい、ROM容量がもったいないのでは、と思われる人がいるかもしれないが、今どきのコンパイラは、そこまで愚かではない。 SELECTORを2にしてビルド(LPCXpresso Ver.6.1.0)した場合、上記プログラムで以下のようなアセンブラコードが生成された。
main: push {r7, lr} add r7, sp, #0 mov r3, #0 mov r0, r3 mov sp, r7 pop {r7, pc}
sample1もsample2も出てこないし、forループも出てこない。単に戻り値として0を返しているだけだ。 念のために言っておくが、これはオプティマイズなし(-O0)でコンパイルしている。 今どきのコンパイラは、この程度の操作は頼まれなくてもやってくれるのだ。 ちなみにiが使われてないというウォーニングも出ない。
SELECTORを1にすると以下のようになる。
main: push {r7, lr} add r7, sp, #0 bl sample2 ; call sample2 mov r3, #0 mov r0, r3 mov sp, r7 pop {r7, pc}
この通り、無駄なコードは一切生成されない。
このプログラムの優れている点は、条件コンパイルだった部分が、ちゃんと制御の一部としてプログラムに組み込まれているため、条件コンパイルとCプログラムという、それぞれ異なる制御を追う必要がなくなる点にある。
条件コンパイルの話題について触れたが、ついでと言っては何だがdefineシンボルについても、自分の見解を述べておこうと思う。
このブログで自分のオリジナルプログラムを載せるときはdefineシンボルを使わずにenumを使っている。これには理由がある。 defineシンボルは単なる文字列置換に過ぎず、文脈によって解釈が変わってしまう恐れがあるからだ。
既にご存知の方も多いと思うが説明しておく。 以下のようにdefineシンボルとenumで値を定義したとする。
#define define_BIT2 1<<2 enum { enum_BIT2 = 1<<2 };
この二つの値は全く異なる。enum_BIT2は間違いなく4と解釈されるが、define_BIT2はそうならない。 以下に例を示す。
param1 = 1 + define_BIT2; /* (1) */ param2 = 1 + enum_BIT2; /* (2) */
上記のようなプログラムをコンパイルした場合、恐らく(1)に対して次のようなウォーニングが出ると思う。
warning: suggest parentheses around '+' inside '<<'
先のプログラムをプリプロセッサだけを通すと、以下のように変換される。
param1 = 1 + 1<<2; param2 = 1 + enum_BIT2;
ウォーニングの指摘通り、<<よりも+の優先順位が高いため、(1)は(1 + 1) << 2と解釈されparam1には8が入る。 param2には望んだ通り5が入る。
このような事態を避けるため、defineシンボルでの定義では必ずカッコでくくって定義するよう言われているが、それでも最終的にどのように解釈されるかは、そのdefineシンボルの使用されている文脈に依存してしまう。 しかし最初からenumを使っていれば、このようないらぬ混乱とは無縁だ。
LPCXpressoのタスクバーでのアイコン
自分はWindows 8.1でLPCXpressoを使っているが、LPCXpressoをタスクバーにピン留めするとEclipseのアイコンになってしまうのが地味に嫌だった。
Eclipseの方もタスクバーにピン留めしているので、LPCXpressoと区別がつかなくなる。 しかし、どうやらLPCXpressoを起動してワークスペースのダイアログが出ている時にタスクバーにピン留めすれば、そのままLPCXpressoのアイコンのままになることが分かった。
ただし、ワークスペース選択後、通常のWelcomeページが表示されるとタスクバーにLPCXpressoのアイコンが二つ並ぶことになってしまうようだ。
どうもうまく行かないが、先にピン留めしておいたEclipseと並んでも混乱することはなくなったので、これでやっていこうと思う。
LPC810(7) 〜 LPCXpressoでデバッガを使う
LPC810のデバッグに今回はトランジスタ技術2014年3月号に付いていたデバッガ基板を使用した。
ドライバのインストールや環境構築は、トラ技の該当の記事を読めば特に問題なくできた。すこぶる快調に動いてくれている。
この号だけ特別定価として税込み1,100円だったが、同じような基板としてNXP社のLPC-Link2の方は2,000円〜3,000円する。ちょっと試す分にはトラ技の付録の方が手軽だ。 ただし、雑誌の付属基板以外にUSB mini-Bのレセプタクルとプッシュスイッチ2つ、それとL型のピンソケットが最低限必要で、プッシュスイッチは通常のタクトスイッチでは大き過ぎたので、自分は手持ちのアルプス電気の表面実装タイプのもの(SKRPACE010)を使用した。
デバッグはこのデバッガ基板とLPCXpressoで行ったが、その途上LPC810の問題点や、LPCXpressoのデバッグ機能など、いくつか気づいた点があったので、ここでまとめおく。
なお、何度も何度もくどいようだが、これらの情報はLPCXpresso Ver.6.1.0(2013-10-21リリース)を元にしており、これ以外の版では違ってしまっているかもしれない。その点はご了承頂きたい。
デバッガインターフェースSWD(Serial Wire Debug)について LPC810は非常にコンパクトでROM/RAM容量も少ないしピンの数も少ない。 絶対に必要な電源(VDD)とGND(VSS)を除けば6つのピンしか使用できない。
こんなにピン数が少ないのに開発のためのデバッガが接続できるのか、という疑問もあるが、その点はちゃんと考慮されており、二つのピンしか使用しないシリアル・ワイヤ・デバッグ(Serial Wire Debug)インターフェースというものを備えている。 これはARMの持つ標準インターフェースでARM社のこのページ「シリアル ワイヤ デバッグ」で詳しく説明されている。
ユーザーズマニュアル(UM10601.pdf)を見てみると、一応LPC810でもJTAGインターフェースをサポートしているようで、その場合は当然のことながら5つのピン(TRST, TCK, TMS, TDO, TDI)を使ってしまい、自由に使えるのが1ピンだけになってしまう。 恐らくこれは、JTAG本来のバウンダリスキャンテストでの使用を見越しているのだろう。
取り合えず、普通は2ピンのみを使用するSWDインターフェースでデバッグするのが妥当だろう。 ちなみに、これらインターフェース用のピンはLPC810の3番ピンと4番ピン固定となっており、他のピンに割り当てることはできない。
ピンNo. 名称 説明 3 SWCLK シリアル・ワイヤ・クロック(Serial Wire Clock) SWD用クロック 4 SWDIO シリアル・ワイヤ・デバッグデータ(Serial Wire Debug data Input/Output) デバッガとの通信用
LPCXpressoを使い、SWDインターフェースによりデバッガを接続すると、LPC810のフラッシュROM内にSWD経由でプログラムを書き込んでくれるので、前項で説明したISPモードによる書き込みを行う必要はない。この点は便利だ。
ただし、3番と4番ピンをSWD以外の用途で使ってしまうとデバッガを接続できなくなる。
これは考えてみれば当たり前なのだが、最初はこれに気づかずにGPIOに割り当ててしまい、デバッガ接続できなくてハマってしまった。 LPCXpressoの表示するエラーメッセージも単に「接続に失敗」的な物言いしかしてくれなかったため、なかなかこの点に気づかなかった。
ただでさえピン数の少ない所に、さらにデバッグ用にピンを二つ献上するのはきついかもしれない。要するにデバッガ接続を考えると、実質自由に使えるピンは4つしかないことになる。 この点は、LPC810で何かを作ろうと考えたときに、最初に十分検討する課題となる。 すなわち、デバッガ抜きで6ピン使うか、用心のためにデバッガ接続を考慮し4ピンで妥協するか、である。
ここから自分の個人的見解を述べさせてもらうが、LPC810で何らかの製品化を考えた場合、少なくとも、このデバッグ用インターフェースは残しておくべきだと私は思う。 ピン数が少ないからと言って別の用途に割り当てて使ってしまったら、いざ問題が起きた場合にどうやって不具合解析するのだろう。 まぁ、LPC810自体プログラム領域が少なく、プログラムも高々4kBぐらいにしかならないから、問題が起きても机上デバッグで十分、デバッガ接続なんか必要ない、と言う考え方もあるにはあるだろう。 しかし、組み込みプログラムではソフトではなくハード側に起因する問題もあるわけで、例えばハード側の問題なのか、ソフト側の問題なのかを切り分けるのに、デバッガほど適した機材はないと思うのだが(デバッガを使ってGPIOに1を出力したにも関わらず、問題個所の信号がLowになっていたとか)。
なお、条件コンパイルを使用して、製品版と開発版とで3番ピンと4番ピンの割り当てを切り替える手段は取れるかもしれない。 ただし、この場合は製品版の3番ピンと4番ピンには、余り重要な機能は割り当てられない(最悪の場合、動かなくとも良いもの)。
ところで、LPCXpressoのデバッグメニューにリスタート(Restart)がある。
これは、プログラムをもう一度最初から動かしたい場合に使用するボタンだが、これは特にLPC810のピンにRESET(1番ピン固定)を割り当てなくても使えるようだ。
スタートアップルーチンをデバッガで追うには デバッグを開始すると、通常はmainルーチンの先頭でプログラムが停止し、ユーザーの操作待ち状態になると思う。 しかし、実際はリセットしたら、いきなりmainルーチンに飛んで来るわけではなく、メモリのクリアなどの初期化処理(スタートアップルーチン)を通ってからmainまで来るはずだ。
このスタートアップルーチンは、プロジェクトを新規作成した時に自動生成されるcr_startup_lpc8xx.c内で定義されている。 ResetISRという関数がスタートアップルーチンで、その処理の最後でmainを呼び出している。
LPCXpressoでプロジェクトを新規作成した場合、デフォルトではmainまで実行させてから停止する設定となっているので、設定を変えることによりスタートアップルーチン(ResetISR)の処理をデバッガで追うことも可能である。
以下、プロジェクト名を“sample”とした場合の設定例を示す。 変更は“Quickstart Panel”の“Edit 'sample' project settings”で行う。 クリックで表示された“Properties for sample”ウィンドウの左下にある“Run/Debug Settings”をクリック、真ん中にある設定選択画面で“sample Debug”を選択、そして“Edit...”ボタンをクリックする。
表示された“Edit Configuration”ウィンドウで“Debugger”タブを選択する。すると“Stop on startup at:”と記述されている欄があり、デフォルトではチェックが入っていて“main”が入力されていると思う。
チェックはそのままで、mainをスタートアップルーチン名のResetISRに変更し“OK”で終了する。
これで、デバッガを接続した時に、まさにリセットベクタから飛んできた直後でプログラムが停止してくれる。
この後はステップ実行などでスタートアップルーチン内を探索できる。 ちなみに先のチェックマークを外した場合は、デバッガ接続時に停止せず、接続と共にプログラムは実行状態になる。
消してしまったパネルの再表示方法と、お勧めのパネル LPCXpressoで新規ワークスペースを作った直後は、ワークスペースウィンドウ内に色々と便利なパネルがタブ形式で表示されていると思う。
これらは、ついうっかりと消してしまう場合もある。 そのような場合は“Window”メニューの“Show View”から再表示可能である。 メニューには4つの項目しかないが“Other...”を選択することにより、全ての項目を選ぶことができる。
デバッグ時に便利なパネルは“Debug”内にある。グレー表示されているものは既に表示されているものである。
自分が便利だと感じたパネルを三つだけ挙げておく。
“Expressions”は指定した変数の内容や計算式の結果を表示してくれる(以下はLPC_WWDTレジスタの内容)。
変数の場合は、ちゃんと型に沿った表示の仕方をしてくれる。また、プログラム実行時に変化があった箇所を黄色で強調表示してくれる。 “Value”欄を修正することにより、変数の内容を変更することも可能だ。
続いて“Breakpoints”を紹介する。 デバッグ時には思いつくままにブレークポイントを付けてしまい、どこに付けたか訳が分からなくなってしまう場合があるが、その場合はこのパネルが便利だ。
現在設定しているブレークポイントの一覧を知ることができる。チェックマークを付けたり外したりすることでブレークポイントの有効/無効を選べるし、また、一括で削除することも可能だ。
デフォルトでは表示されてないが“Disassembly”も地味に便利だ。
指定した関数のアセンブルコードを表示してくれる。 ここではアセンブルコードのみを表示しているが、Cプログラムと一緒に表示することもできる。 例えば、オプティマイズを高レベルで設定したコードが、どれだけ研ぎ澄まされているかを、この逆アセンブル機能で見てみるのも良い。
ここまででLPC810の開発環境については一通り説明し終わったので、これ以降は、LPC810の個々の機能ついて気づいた点や興味深い点について触れていきたいと思う。
LPC810(5) 〜 LPCXpressoを使う(4)
ここまでの説明で、ビルド環境に関しては一通り説明し終わったので、ここではLPCXpressoでプログラムを作っていくに当たって、その他の気付いた点について挙げておく。 なお、くどいようだが、これらの情報はLPCXpresso Ver.6.1.0(2013-10-21リリース)を元にしており、これ以外の版では違ってしまっているかもしれない。その点はご了承頂きたい。
プログラムの文字コードにはUTF-8を使う。 やはりプログラムのコメントには日本語を使いたいが、この場合は文字コードとしてUTF-8を選択する必要がある。 最初はこの点に気付かずに、ついうっかりSHIFT-JISを使ってしまい、LPCXpressoの画面で文字化け表示を起こしてしまった。
この状態でLPCXpressoから「保存」してしまうとファイルのコメントが滅茶苦茶になってしまう。 プログラムファイルやヘッダファイルの文字コードはすべてUTF-8を使うこと。
スイッチマトリクスツールではswm.cのみを使えば良い。 スイッチマトリクスツール(NXP Switch Matrix Tool for LPC800)とは、LPC810の各ピンを自分好みの割り当てにするためのコード(C言語)を自動生成してくれるツールだが、ピン割り当て後はウィンドウ内のswm.cタブを選択し、そこに記述されているSwitchMatrix_Init()をコピペして使う。その他は必要ない。
選択して右クリックしてもメニューは出てこないが、コントロールキーとCキーとの同時押しで選択範囲をコピーできる。 その後、main.cあたりに貼付けて使えば良い。本関数はmain先頭で呼び出す。(なお、貼付けたあとに関数定義をSwitchMatrix_Init(void)と直しておこう。)
基本型(uint?_tなど)や基本シンボル(TRUEなど)の取り込み。 C99からサポートされるようになったuint8_tやint32_tなどの標準型はプログラム先頭でstdint.hをインクルードすれば使えるようになる。
ブール型_Boolはstdbool.hをインクルードすれば良い。stdbool.h内では_Boolの別名としてboolも定義されているので、自分の慣れている名称を使えば良い。また、本ヘッダファイル内でtrueやfalseも定義されている。
これらは、コンパイラの規格でc90やgnu90を選択してもインクルードしさえすれば使えるようだ。
なお、慣習的に使われているTRUE、FALSE、NULLといったシンボルの定義は、これら標準ヘッダ内でなくLPC810側のライブラリヘッダであるtype.hで定義されていた。
だから、これら標準的な機能の取り込みは、以下のようなインクルード文を羅列した共通ヘッダファイルを作り、プログラムではその共通ヘッダを取り込むようにしておけば面倒が無い。
#include <stdint.h> /* C99型定義 */ #include <stdbool.h> /* ブール型定義(C99) */ #include "type.h" /* NULL, TRUE, FALSE */ #include "LPC8xx.h" /* LPC800関連の定義 */
最後のLPC8xx.hは、LPC810のレジスタ定義の含まれるヘッダファイルだ。
標準ライブラリも使えるが注意が必要。 isdigit()やstrcpy()などの便利なC言語の標準ライブラリは、単にプログラム先頭で該当のヘッダファイル(ctype.hやstring.h)をインクルードすれば使えるようになる。特に面倒な手続きは必要ない。
この点は非常に便利なのだが、一つLPC810ならではの大きな問題がある。
例えば、LPC810では残念ながらvsprintf()などのsprintf()一族の文字列フォーマッタ関数は使用できない。 これらが使えると非常に便利なのだが、これらを使うために追加されるライブラリだけでも、プログラム領域10kB近く、メモリ領域100バイト余り消費されてしまう。 すなわち、プログラムを格納するフラッシュROMが4kBしかないLPC810では問題外である(整数onlyのサブセット版も選べるが、それでもプログラム領域はライブラリだけで5kB近くとなる)。
なお、isdigit()などの文字タイプ判別関数(ctype.h)や、memcpyやstrcpyなどの文字列・メモリ操作関数(string.h)は状況次第だ。
例えばctype.hはプログラム領域で380バイト近く増えた(オプティマイズ無し)。メモリ領域は増えなかった。 isdigit()だけを使いたい場合は、直接if (('0' <= c) && (c <= '9'))などと書いてしまった方が、プログラム領域の消費を抑えることができるだろう。ただし、可搬性は著しく損なわれるが。
string.hの方は実際にプログラムで使用した関数の分(例えばmemcpyを使えば、そのmemcpyの分)増えるだけなので、余り神経質になる必要はないと思う。 しかし、意外にプログラム領域を消費する関数もあるのでマップファイル(*.map)での使用量確認は必須だ。
搭載するプログラムは機能を絞る必要がある 今回LPC810を使って、非常にシンプルなプログラムを作ってみた。 汎用システムタイマ(SysTick)を使ってLEDを点滅し、UARTを使って簡単なターミナル的な処理を行う至ってシンプルなテストプログラムである。また、ファームウェアには必須のウォッチドッグタイマも搭載してみた。
これぐらいの非常にシンプルなプログラムでも、オプティマイズ無しではプログラム領域が3kBを超えてしまい、残り数百バイトというようなことになってしまった。
プログラム領域を絞る方法はある。今回はCMSIS_CORE_LPC8xxなどのライブラリを使用したが、これを止めて本当に必要なもののみ自分のプログラムにコピーして持ってきたり、オプティマイズオプションのレベルを上げたり、などだ。
自分としてはvsprintf()などのsprintf()系が、プログラム容量的に全く使えないのが一番痛かったが、例えばプログラム容量を抑えたサブセット版のsprintf()を自分で作る、又はネットから漁ってくるなど方法はある。
しかし、このようなことをしてまでLPC810を選択する理由ってなんだろうと、ふと考えてみると、そもそも今回はトラ技のオマケについていたから使っているわけであり、LPC810に特別なこだわりがあるわけではない。
プログラム容量を削る作業というのも、ある意味面白いものであるが、なぜLPC810で?と考えると、残念ながらそこまでする魅力をLPC810に感じてないのも事実だ。 これは、あくまで自分の個人的見解であるが。 それこそもっとROM/RAM容量の潤沢な、上位のARM、又はその他のマイコンの載った安い評価ボードが星の数ほど出ているから、どうせだったらそっちで遊びたい。
半ば愚痴のようになってしまったが、すなわちフラッシュROM4kBと言うのは少しばかり手狭であり、プログラムとしても本当に単機能(UARTならUARTのみ、I2CならI2Cのみ)しか載せられない、と言うことである。 LPC810には標準ライブラリもまともに載せられないので、気軽にちょっとプログラミングを、という用途には適してない。
LPC810(4) 〜 LPCXpressoを使う(3)
ライブラリを追加したら、いよいよ新規プロジェクトを作る。 なお、この情報はLPCXpresso Ver.6.1.0(2013-10-21リリース)を元にしており、これ以外の版では違ってしまっているかもしれない。その点はご了承頂きたい。
新規プロジェクトの作成
“Quickstart Panel”の“New project...”をクリック。
“LPC8xx”を展開して“C Project”を選択し“Next >”をクリックする。
なお、ここでLPCOpenとあるが、これはNXP社が提供するかなり高機能なプラットフォームなのだ。このページ(http://www.lpcware.com/content/project/lpcopen-platform-nxp-lpc-microcontrollers)で詳しく説明されている。 さすがにLPC810には載らないが、もう少しROM/RAMに余裕のあるNXP社のマイコンで一度試してみたいと思っている。
プロジェクト名の入力を要求されるので入力する。ここではsampleとしている。 入力したら“Next >”をクリック。
ターゲットの選択画面になるので“LPC8xx”を展開して“LPC810”を選択し“Next >”をクリック。
ここでライブラリの選択画面になる。 LPC810に搭載するプログラムの開発を行うなら“CMSIS Core library”で“CMSIS_CORE_LPC8xx”を選択する。 また、“Peripheral Driver Library”で周辺機器アクセスのためのライブラリを選択できるので“lpc800_driver_lib”を選択する。 先の手順で示した通り、これらライブラリは、あらかじめワークスペース内に存在してる必要がある。
なお、LPC810のレジスタ定義や、その他諸々のものを全部自分で用意すれば、これらライブラリを使う必要はない。 しかし面倒だし、CMSISに沿ったコーディングをしておけば、後々別のARMチップを使った開発でも役立つので使っておくに越したことはない。 なお、これらライブラリについては「LPCXpressoを使う(1)〜」を参照して頂きたい。
MTBの設定画面だが、取り合えずチェックを外して良いだろう。 MTB(Micro Trace Buffer)についてはユーザーズマニュアル(UM10601.pdf)のDebuggingの章で触れられている。要するにデバッグ用のトレースバッファを管理するためのものらしいが、取り合えず今はノータッチ。
続いて以下の三つの機能選択画面が表示される。
Code Read Protect(CRP)機能の選択
要するにフラッシュROMに書き込まれているプログラムの読み出しをガードする機能だ。 ユーザーズマニュアル(UM10601.pdf)のISPモードの章で詳しく触れられている。 面白そうだが、取り合えずチェックを外してノータッチ。
Project Structure
ソースファイルとインクルードファイルを格納するフォルダを指定する。 ソースファイルはデフォルトのsrcのままで良いだろう。 “Create 'inc' directory and add to path”にはチェックを入れておくことをお勧めする。 ここにチェックを入れておくと、ヘッダファイル格納用のフォルダincを自動的に作り、さらにコンパイル時のインクルードパスも通しておいてくれる。
Compiler language dialect
要するにCコンパイラの規格として何を使うか指定する。 Default、GNU90、C90、GNU99、C99の中から選択できる。
Defaultだと何の規格が適用されるかが分からないので、好みにあった規格を選択すれば良いだろう。ただし、どうせ選択するのであればGNU99かC99を強くお勧めする。
90版は恐らくANSI-C(C89)準拠の事だと思うが、それの改善版である99版では配列や構造体の初期化方法が劇的に便利になっている。 メンテナンス目的として、古いコンパイラを使わざるを得ない状況以外は99版の方を使うべきだ。 ここでは、gcc以外の環境でのソースコードの使い回しも考慮しC99を選んだ。
なお、stdint.h(ビットの幅の分かるuint8_tなどの型)やstdbool.h(いわゆるブール型_Bool)もC99からのサポートだが、これらは選択した規格に関わらず、ヘッダファイルをインクルードしておけば使えるようだ。
これで“Finish”をクリックすれば新しいプロジェクトの完成だ。
“Project Explorer”を見てみるとmain.cを含むソースファイルが自動生成されているはずだ。 今回はsampleという名称でワークスペース内に作ったが、同名のフォルダがワークスペースで指定したフォルダ内にできていると思う。 以下に自動生成されたファイルの簡単な説明をしておく。
フォルダ 名称 説明 inc (ファイル無し) 今はまだ空だが、自分で作ったヘッダファイルを格納しておくフォルダ。 src cr_startup_lpc8xx.c ベクタテーブルを含むスタートアップルーチン。 本ファイル内のResetISRがリセット時に起動される関数で最後にmainを呼び出している。 ベクタテーブルも含め、こういう処理をアセンブラ抜きで記述できるのだからC言語は凄い。 src crp.c コード読み出しプロテクト(CRP)機能の定義ファイル。 CRP無しで初期化されている。 src main.c いわゆるmainルーチンの含まれるファイル。 中では無限ループしているだけ。 src mtb.c デバッグ用トレースバッファ(MTB)定義ファイル。 条件コンパイルで囲まれているが、MTB無しとしているので、実質本ファイル内のプログラムは全て無効である。
最後に一つ仕事が残っている。 このままビルドしてもデバッガ用のARM ELF形式のファイル(*.axf)しかできない。 FlashMagicで書き込むにはインテルHEX形式のファイルが必要だ。 そのためには“Quickstart Panel”で(プロジェクト名がsampleの場合は)“Edit 'sample' project settings”をクリックする。
表示された“Properties”ウィンドウで“C/C++ Build”内にある“Settings”をクリック、続いて、その中の“Build Steps”タブをクリックする。
そこに“Post-build steps”の欄があるが、これはビルド完了した後に自動的に起動されるコマンドである。 ここが、恐らく以下のようになっていると思う(見易いようにコマンド毎に改行している)。
arm-none-eabi-size "${BuildArtifactFileName}"; # arm-none-eabi-objcopy -O binary "${BuildArtifactFileName}" "${BuildArtifactFileBaseName}.bin" ; checksum -p ${TargetChip} -d "${BuildArtifactFileBaseName}.bin";
;がコマンドセパレータであり、全部で三つのコマンドが実行される。ただし、二番目以降は#でコメントアウトされているので動作しない。
コマンド名 説明 arm-none-eabi-size ビルドしたコードのサイズを表示する。 arm-none-eabi-objcopy axfファイルを別フォーマットのファイルに変換する。 ここではコメントアウトされている。 checksum 変換したファイルのチェックサム値を表示する。 #以降は行末までコメントアウトされるのでこれも無効化される。
表示はウィンドウ右下の“Console”にされる(ビルドした時にパラパラと表示が変わるところ)。 上記コマンドを以下のように変更する。なお、見易いように途中で改行しているが、実際は改行せずに入力する必要がある。
arm-none-eabi-size "${BuildArtifactFileName}"; arm-none-eabi-objcopy -O ihex "${BuildArtifactFileName}" "${BuildArtifactFileBaseName}.hex"
arm-none-eabi-objcopyの頭に付いていた#文字を削除し、フォーマットをインテルHEXに変換すべく“-O ihex”オプションを指定した。また、出力ファイルの拡張子を“.hex”としている。 checksum以降は無意味なので削除している。もともとは“-O binary”で単純バイナリに変換しており、この場合はチェックサムを計算する意味もあったが、インテルHEXは所詮テキストファイルなので、チェックサムを計算しても余り意味がないからである。
修正後、OKボタンをクリックして終了。
“Quickstart Panel”で(プロジェクト名がsampleの場合は)“Build 'sample'”をクリックしビルドする。 エラー無くビルドできると思うが、もしエラーが発生したら、もう一度ここまでの手順を見直して欲しい。
プロジェクト名がsampleの場合は“sample¥Debug”内に“sample.axf”と“sample.hex”ができていると思う。 “.hex”ファイルができていない場合は、先の“Post-build steps”コマンドの入力にミスっている可能性が考えられるので再確認のこと。 FlashMagicでは、この“sample.hex”を使う。
補足事項
main.c以外の自分独自のソースファイルを追加したい場合は、srcフォルダ内に単に追加しておけば良い。 “Quickstart Panel”で(プロジェクト名がsampleの場合は)“Clean 'sample'”をクリックすると“Project Explorer”画面に自動的に追加される。
これだけの何もしないプログラムでもコードサイズとして440バイト余りとなった。
プログラムにもよるが、オプティマイズをかければコードサイズを小さくすることができる。 先に“Post-build steps”を変更する手順で“C/C++ Build”内にある“Settings”を選んだが、その中の“Tool Settings”タブを選択する。 そこの“MCU C Compiler”内の“Optimization”がそれだ。 デフォルトではオプティマイズなし(None)となっているので、これを変更できる。 変更後はOKで終わること。
レベルの高いオプティマイズをかけた場合、生成されるコードと実際のプログラムソースとの間で対応が取れなくなり、デバッガを使ってステップ実行した時に意外な動きをしたり、ブレークポイントをかけたら変な所でブレークしたりする場合があるので注意が必要である。 何を言っているか分からない場合は、オプティマイズレベルをレベル2ぐらいにしてビルドしたコードで適当な関数をステップ実行してみると良い。
LPC810(3) 〜 LPCXpressoを使う(2)
LPCXpressoをインストールしたら、後はプロジェクトを作り、自分の作りたいコードを書いていくことになる。 実際にLPCXpressoでLPC810用の新規プロジェクトを作ってみたが、色々とひっかかりそうな所があったので、後々のことを考えてここにまとめておく。
なお、この情報はLPCXpresso Ver.6.1.0(2013-10-21リリース)を元にしており、これ以外の版では違ってしまっているかもしれない。その点はご了承頂きたい。
ここでは、LPCXpressoを使ってLPC810マイコンに組み込むプログラムを作るための手順についてまとめておく。 このため、LPCXpresso付属のライブラリなどを使うことにしている。
慣れてくれば付属のライブラリなど使用せずに、自分でLPC810のレジスタ群を直接操作してプログラムを作ることも可能であるが、まだそこまで行かない場合や、ちょっとした参考として見てみたいなど、付属のライブラリはワークスペース内に追加しておいても良いと思う。 LPCXpressoのリンカは非常に有能なので、ワークスペースにライブラリを追加しただけでは、すぐにプログラム領域を浪費することはない。実際にプログラム中で該当のライブラリを使わない限り組み込まれることはない。
手順はおおまかに言うと、
ワークスペースへのライブラリの追加
新規プロジェクトの作成
となる。 同じワークスペースを使う限りは、ライブラリは一度登録しておけば、あとはそれぞれのプロジェクトから参照可能である。 まずは、ワークスペースへライブラリを追加する手順について説明する。
ワークスペースへのライブラリの追加
“Quickstart Panel”の“Import project(s)”をクリック。
“Import project(s)”ウィンドウが表示されるので“Browse...”ボタンをクリックして、ライブラリの含まれているzipファイルを指定する。 ライブラリは「LPCXpressoを使う(1)」でも触れた以下のフォルダにあるzipファイルである。
C:¥nxp¥LPCXpresso_6.1.0_164¥lpcxpresso¥Examples¥NXP¥LPC800
選択するzipファイルはどちらでも良いが、取り合えずNXP_LPC8xx_SampleCodeBundle.zipを選択し“Next >”をクリック。
すると、そのzipファイルに含まれる全てのプロジェクトが表示されるので、このうち以下の二つのプロジェクトのみチェックを入れて他は外す。
CMSIS_CORE_LPC8xx
lpc800_driver_lib
最初に“Deselect All”をクリックしてから必要なもののみチェックを入れるのが早い。
その後“Finish”ボタンをクリック。 なお、その他のLPC810が内蔵する機能名の付いているプロジェクトは、該当の機能を使うためのサンプルプロジェクトなので、参考としてチェックをいれてワークスペース中に取り込んでも良い。
これでワークスペース内に二つのプロジェクト(ライブラリ)が取り込まれているので一応ビルドしておこう。 ビルドは“Quickstart Panel”の“Build all projects”をクリックすることでできる。
どうだろうか?問題無くビルドできただろうか? ビルド時にエラーが発生した場合は、左上の“Project Explorer”で該当のプロジェクトに赤いバッテン(×)印が付く。 この状況でエラーが出たら、恐らくLPCXpressoのインストールに失敗していると思うので、再インストールした方が良い。
ウォーニングが発生した場合は、該当のプロジェクトに対して黄色い警告標識が付く。 実は、自分の使っているVer.6.1.0付属のライブラリではビルド時にウォーニングが発生する。 これは、CMSIS_CORE_LPC8xxで発生しているようで、展開するとsystem_LPC8xx.cファイル内でウォーニングが発生しているようである。
このウォーニングを調べてみることにする。 なお、新しいバージョンでは、このウォーニングは対策されているかもしれないので、その場合はこの記事を無視して頂いて構わない。
“Project Explorer”ウィンドウで、ウォーニング表示されているsystem_LPC8xx.cファイルをダブルクリックすると、該当のファイルの内容が右のウィンドウに展開される。 そのウィンドウのスクロールバーを見て頂きたい。黄色い帯の区間があると思うが、この行でウォーニングが発生しているのだ。
その黄色い部分をクリックすると、該当のウォーニングの発生している行が表示される。 恐らく以下の行で発生していると思う。
volatile uint32_t i;
調べてみると、変数iは条件コンパイル文の中でしか使用されてない。 シンボルSYSPLLCLKSEL_Val、及びMAINCLKSEL_Valがある特定の値の場合にしかiは使われず、現在は該当のシンボルが0で定義されているため使われない。そのため、未使用ローカル変数としてウォーニングが発生しているのだ。
そのため、このウォーニングは無視しても悪影響は無いと思うが、気になる人は以下の様にiが使われる時だけ有効となる様に条件コンパイル文を追加すれば良い。
#if ((SYSPLLCLKSEL_Val & 0x03) == 1) \ || ((SYSPLLCLKSEL_Val & 0x03) == 3) \ || ((MAINCLKSEL_Val & 0x03) == 2) volatile uint32_t i; #endif
これでビルドし直せばウォーニングは消えるだろう。
ちなみにLPCXpressoでは、上のように条件コンパイルで無効となっているブロックがグレーアウトされて表示される。 また、SYSPLLCLKSEL_Valにポインタを合わされば、その数値0x00000000が表示される。この点は便利だ。
ライブラリのウォーニングぐらい取っておいても良さそうに思うのだが、これがNXP社の社風なのかもしれない。
長くなってしまったので、新規プロジェクトの作成については次の項で説明する。