2014/03/23

[電子工作]aitendoのDDSモジュールを動かす その3

,
 前記事は[電子工作]aitendoのDDSモジュールを動かす その2でした。
しばらく触ってなかったのですが、元々無いやる気を必死に絞り出して、ちょっとだけ進めました。現状だと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
ちなみに、上のコードは、[Optimization]が[-O0]で、以下のようなコードになりました。volatileなのに中身が変わっている…
    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: }

エラーとか

さて、最初は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の理解、命令の理解、インラインアセンブリの書き方の理解)。

今回は以上です。

0 コメント to “[電子工作]aitendoのDDSモジュールを動かす その3”

コメントを投稿