前記事は[電子工作]aitendoのDDSモジュールを動かす その2でした。
しばらく触ってなかったのですが、元々無いやる気を必死に絞り出して、ちょっとだけ進めました。現状だとLCDを付けたのが新しい点です。
基板の写真です。
さて、今回も幾つか詰まった点があったので、メモしておきます。
aitendoのLPC1114ボードには、裏面にシルクでポート番号が書かれています。それを信じて接続してたところ、最初はLCDが動きませんでした。LCDコネクタとピンヘッダ間はテスターで導通チェックしていたので、配線に間違いは無いはずだと思い、ソフト側が問題かと思っていました。しかし、ロジアナでソフトの動作を確認していたところ、P2.4、P2.5に接続していたRS、Eが全く動いてないことに気づきました。
で、ふと回路図を見ていたところなんか変だったので、実際にテスターで追っていったところ、正しい結線は以下のようになってるらしいです。
ややこしい…というか、これはおいそれとは気付かないと思うんだ。
プログラム側で簡単に修正したら動くようになりました。
しかし、DM/DPは多分USBの端子の名前で、LPC1114にはUSB機能はないのですが…LPC1300とかと間違えたんでしょうか?
移植と言っても、移植手順に書かれているように、
1)hd44780.hの_LCD_ROWSで行数を、_LCD_COLSで桁数を設定する
2)hd44780.cの、環境依存な関数(ポートのON/OFFとか)を記述する
だけです。
実際に、hd44780.cの環境依存部のコードを下に示します。上に書いたように、ポートが違ってたなどのミスもありましたが、割と直ぐに動かすことが出来ました。
参考 : Starting with the LPC1114FN28 ARM Cortex-M0 chip in DIP package
ここのarm_lpc111x/lib/delay.hをほぼ丸パクリ参考にしました。
次に各命令についてです(といっても3つだけですが)。
ちなみに、上のコードは、[Optimization]が[-O0]で、以下のようなコードになりました。volatileなのに中身が変わっている…
warning: #1267-D: Implicit physical register R1 should be defined as a variable
と出るので、KeilのFAQにあるように、ローカル変数か何かを使った方がいいみたいです(上の例では、アセンブラを見たところ、reg1がr3として使われてました)。
また、subsではなくsub命令を使おうとしたところ、
hd44780.c(68): error: #2829: Cannot perform desired action on condition flags
SUB reg1, reg1, 1; //1clock
と出てしまい使えませんでした(理由が分からない、condition flagsというのはなんだ)。
Compiler User Guide: Inline assembler rules for compiler keywords __asm and asm
Compiler User Guide: Inline assembly language syntax with the __asm keyword in C and C++
また、Cortex-M0の命令については以下を参考にしました。
ARM Information Center Cortex-M0 テクニカルリファレンス マニュアル 3.3. 命令セットの概要 #<imm>は即値。サイクル数などの参考に。
ARM Information Center RealView Compilation Tools アセンブラガイド 2.4.2. 条件実行 bne(b + ne)などの条件実行命令について。
Assembly Programming on ARM Linux (2) 条件実行について。これめっちゃ便利ですね。
・EZ-LCDライブラリは移植が楽だが、delay()系関数が無いと少し大変。
・インラインアセンブリは難しい(CPUの理解、命令の理解、インラインアセンブリの書き方の理解)。
今回は以上です。
Read more →
しばらく触ってなかったのですが、元々無いやる気を必死に絞り出して、ちょっとだけ進めました。現状だとLCDを付けたのが新しい点です。
基板の写真です。
基板表面 + LCD + LPC-Link2 |
基板裏面。UEWによる配線(電源も)だけれど、電流容量とクロストークノイズが気になるところ。 |
aitendoのLPC1114ボードの誤表記
一番詰まったことは、シルクに書いてあるポートと、実際に接続されてるポートが違うことでした。aitendoのLPC1114ボードには、裏面にシルクでポート番号が書かれています。それを信じて接続してたところ、最初はLCDが動きませんでした。LCDコネクタとピンヘッダ間はテスターで導通チェックしていたので、配線に間違いは無いはずだと思い、ソフト側が問題かと思っていました。しかし、ロジアナでソフトの動作を確認していたところ、P2.4、P2.5に接続していたRS、Eが全く動いてないことに気づきました。
で、ふと回路図を見ていたところなんか変だったので、実際にテスターで追っていったところ、正しい結線は以下のようになってるらしいです。
LPC1114のポート | 基板に書かれてるシルク(J2のピン番号) |
---|---|
PIO2_4 | DM |
PIO2_5 | DP |
PIO3_4 | P2.4 |
PIO3_5 | P2.5 |
ややこしい回路図。 |
しかし、DM/DPは多分USBの端子の名前で、LPC1114にはUSB機能はないのですが…LPC1300とかと間違えたんでしょうか?
EZ-LCDライブラリの移植
EZ-LCDとは
EZ-LCDとは、ChaN氏という方が制作された、HD44780互換LCDを動かすためのC言語のライブラリです。今回LCDを使いたかったので、これを利用することにしました。移植と言っても、移植手順に書かれているように、
1)hd44780.hの_LCD_ROWSで行数を、_LCD_COLSで桁数を設定する
2)hd44780.cの、環境依存な関数(ポートのON/OFFとか)を記述する
だけです。
実際に、hd44780.cの環境依存部のコードを下に示します。上に書いたように、ポートが違ってたなどのミスもありましたが、割と直ぐに動かすことが出来ました。
#else #include "LPC11xx.h" #include "gpio.h" #define IF_BUS 4 #define DELAY_US(n) delay_us(n) #define IF_INIT() {} #define IF_DLY60() {} #define IF_DLY450() delay_us(1) #define E1_HIGH() (GPIOSetValue(3, 4, 1)) #define E1_LOW() (GPIOSetValue(3, 4, 0)) //#define E2_HIGH() bclr(P1) /* Set E2 high (dual controller only) */ //#define E2_LOW() bclr(P1) /* Set E2 low (dual controller only) */ #define RS_HIGH() (GPIOSetValue(3, 5, 1)) #define RS_LOW() (GPIOSetValue(3, 5, 0)) #define OUT_DATA(d) dataOut(d) static void delay_us (int ustime){ //LPC1114 is runnning at SystemCoreClock const int count = SystemCoreClock / 1000000 / 3 * ustime; //Nusあたりのループ数 int reg1; __asm volatile{ MOV reg1, count; //1clock loop: SUBS reg1, reg1, 1; //1clock BNE loop; //1clock if not branch, 3clock if branch } return; } #endif void dataOut(const uint8_t data){ GPIOSetValue(2, 0, data >> 7); //DB7 GPIOSetValue(2, 1, data >> 6); //DB6 GPIOSetValue(2, 2, data >> 5); //DB5 GPIOSetValue(2, 3, data >> 4); //DB4 }注意としては、予めポートの方向をGPIOSetDir()などで設定すること、R/WはサポートされないのでGNDに落とすなどする(常時Write)、です。
delay_us()について
上のコードで、delay_us()だけはAVRと違い公式ライブラリには存在しないので、アセンブラで書きました。参考 : Starting with the LPC1114FN28 ARM Cortex-M0 chip in DIP package
ここのarm_lpc111x/lib/delay.hを
次に各命令についてです(といっても3つだけですが)。
命令 | 意味 |
---|---|
MOV Rd, Rm | レジスタからレジスタに移動。1cycle。 |
SUBS Rd, Rn, #<imm> | SUBは減算(Rd = Rn - #<imm>)。サフィックスが-Sなのでステータスフラグ更新。1cycle。 |
BNE | ジャンプ(branch if (Z flag is) not Equal(Zフラグが0、つまり前の計算結果が0じゃないなら実行))。分岐時は3cycle、しないときは1cycle |
61: static void delay_us (int ustime){
62: //LPC1114 is runnning at SystemCoreClock
0x00000300 B5F8 PUSH {r3-r7,lr}
0x00000302 4604 MOV r4,r0
63: const int count = SystemCoreClock / 1000000 / 3 * ustime; //Nusあたりのループ数
64: int reg1;
65: __asm volatile{
0x00000304 498A LDR r1,[pc,#552] ; @0x00000530
0x00000306 488B LDR r0,[pc,#556] ; @0x00000534
0x00000308 6800 LDR r0,[r0,#0x00]
0x0000030A F000FFBF BL.W __aeabi_uidiv (0x0000128C)
0x0000030E 4607 MOV r7,r0
0x00000310 2103 MOVS r1,#0x03
0x00000312 F000FFBB BL.W __aeabi_uidiv (0x0000128C)
0x00000316 4360 MULS r0,r4,r0
0x00000318 4605 MOV r5,r0
66: MOV reg1, count; //1clock
0x0000031A 462E MOV r6,r5
67: loop:
0x0000031C BF00 NOP
68: SUBS reg1, reg1, 1; //1clock
0x0000031E 1E76 SUBS r6,r6,#1
0x00000320 D000 BEQ 0x00000324
69: BNE loop; //1clock if not branch, 3clock if branch
70: }
71: return;
0x00000322 E7FC B 0x0000031E
72: }
62: //LPC1114 is runnning at SystemCoreClock
0x00000300 B5F8 PUSH {r3-r7,lr}
0x00000302 4604 MOV r4,r0
63: const int count = SystemCoreClock / 1000000 / 3 * ustime; //Nusあたりのループ数
64: int reg1;
65: __asm volatile{
0x00000304 498A LDR r1,[pc,#552] ; @0x00000530
0x00000306 488B LDR r0,[pc,#556] ; @0x00000534
0x00000308 6800 LDR r0,[r0,#0x00]
0x0000030A F000FFBF BL.W __aeabi_uidiv (0x0000128C)
0x0000030E 4607 MOV r7,r0
0x00000310 2103 MOVS r1,#0x03
0x00000312 F000FFBB BL.W __aeabi_uidiv (0x0000128C)
0x00000316 4360 MULS r0,r4,r0
0x00000318 4605 MOV r5,r0
66: MOV reg1, count; //1clock
0x0000031A 462E MOV r6,r5
67: loop:
0x0000031C BF00 NOP
68: SUBS reg1, reg1, 1; //1clock
0x0000031E 1E76 SUBS r6,r6,#1
0x00000320 D000 BEQ 0x00000324
69: BNE loop; //1clock if not branch, 3clock if branch
70: }
71: return;
0x00000322 E7FC B 0x0000031E
72: }
エラーとか
さて、最初はinline assemblerにレジスタを直に書いていました(r1など)が、warning: #1267-D: Implicit physical register R1 should be defined as a variable
と出るので、KeilのFAQにあるように、ローカル変数か何かを使った方がいいみたいです(上の例では、アセンブラを見たところ、reg1がr3として使われてました)。
また、subsではなくsub命令を使おうとしたところ、
hd44780.c(68): error: #2829: Cannot perform desired action on condition flags
SUB reg1, reg1, 1; //1clock
と出てしまい使えませんでした(理由が分からない、condition flagsというのはなんだ)。
参考サイト
KeilのARMCCで__asmを使うときのガイドはこちら。Compiler User Guide: Inline assembler rules for compiler keywords __asm and asm
Compiler User Guide: Inline assembly language syntax with the __asm keyword in C and C++
また、Cortex-M0の命令については以下を参考にしました。
ARM Information Center Cortex-M0 テクニカルリファレンス マニュアル 3.3. 命令セットの概要 #<imm>は即値。サイクル数などの参考に。
ARM Information Center RealView Compilation Tools アセンブラガイド 2.4.2. 条件実行 bne(b + ne)などの条件実行命令について。
Assembly Programming on ARM Linux (2) 条件実行について。これめっちゃ便利ですね。
多分違うであろうdelay_us
ちなみに、絶対間違ってるとは思いますが、以下の様なナメた感じのコードでも動作はします。また、IF_DLY60()、IF_DLY450()は何も書かなくても動作するのを確認しました。12MHzなら大体1clock=83nsなので、ポートon/off以外のコードが間に入ることを考えれば、妥当?static void delay_us (int ustime){ //LPC1114 is running at SystemCoreClock const int count = SystemCoreClock / 1000000 * ustime; //Nusあたりのループ数 while(--count > 0){ __asm volatile("nop"); } return ; }
まとめ
・aitendoのLPC1114ボードは、少し結線の表記がおかしいところがある。・EZ-LCDライブラリは移植が楽だが、delay()系関数が無いと少し大変。
・インラインアセンブリは難しい(CPUの理解、命令の理解、インラインアセンブリの書き方の理解)。
今回は以上です。