開発環境

基盤

  • SPPBoard(dsPIC30F3014使用)

コンパイラ

  • C30

はじめに

組み込みプログラムの入門として

1 ~ 9までの数字を0.1秒毎にカウントアップしてLCDの1マス目に表示するプログラムを作成しました。

LCDはSD1602HUOBを使用します。

それでは、仕様書の読み方から解説していきたいと思います。


LCDの仕様書から必要な情報を読み取ろう

基礎知識

R/S端子 
 - Low : コマンドと認識
 - High: 文字データと認識

R/W端子
 - Low : Writeモード
 - High: Readモード

E端子
 - Low -> Highに変化で命令を回収

今回はR/WはGNDに落としているため常にWriteモードです。

E端子は意図的な命令であることと信号を送るタイミングを知らせるために使います。

用意されたコマンド

Clear Display       0000 0001
Cursor At Home      0000 0010
Entry Mode Set      0000 0100
Display ON/OFF      0000 1000
Cursor/DisplayShift 0001 0000
Function Set        0010 0000
CGRAW AddressSet    0100 0000
DDRAW AddressSet    1000 0000

どの位置に1を立てるかでどのコマンドかが判別されます。

LCDの初期化手順

電源ON
15ms以上待つ
8ビットモードに設定
4.1ms以上待つ
8ビットモードに設定
100us以上待つ
8ビットモードに設定
4ビットモードに設定
ファンクション設定
ディスプレイOFF
ディスプレイON
エントリーモード

LCDの制御関数を作ろう

まず、ピンの名前をわかりやすく定義しましょう。

#define LCD_RS LATBbits.LATB5
#define LCD_E  LATBbits.LATB4
#define LCD_D4 LATBbits.LATB9
#define LCD_D5 LATBbits.LATB10
#define LCD_D6 LATBbits.LATB11
#define LCD_D7 LATBbits.LATB12

8ビットモード用指令関数

void lcd_out8(unsigned char dat){
    LCD_RS = 0;
    LCD_E  = 1;
    LCD_D4 = (dat & 0x01);
    dat = dat >> 1;
    LCD_D5 = (dat & 0x01);
    dat = dat >> 1;
    LCD_D6 = (dat & 0x01);
    dat = dat >> 1;
    LCD_D7 = (dat & 0x01);

    wait_usec(50);
    LCD_E = 0;
    wait_usec(50);
}
  • LCD_RS = 0
    LCDにコマンドを送信
  • LCD_E = 1
    コマンドを回収(450nsecの時間が必要)

  • LCD_D[4-7] = (dat & 0x01)
    LCDの上位4本に指令を送信

  • wait_usec(50)
    上記の命令を回収

  • LCD_E = 0
    E端子をLowへ

上位ピンに一つずつ送る理由

SPPBoard

LCDの上位4ピンは9, 10, 11, 12に接続されています。

char型は8ビットなのでまとめてLATBで出力に変更できないからです。

4ビットモード用指令関数

void lcd_out4(int rs, unsigned char dat){
    unsigned char bk = dat;

    LCD_RS = rs;
    LCD_E  = 1;

    bk = bk >> 4;
    LCD_D4 = (bk & 0x01);
    bk = bk >> 1;
    LCD_D5 = (bk & 0x01);
    bk = bk >> 1;
    LCD_D6 = (bk & 0x01);
    bk = bk >> 1;
    LCD_D7 = (bk & 0x01);

    wait_usec(50);
    LCD_E = 0;
    wait_usec(50);

    LCD_E = 1;

    LCD_D4 = (dat & 0x01);
    dat = dat >> 1;
    LCD_D5 = (dat & 0x01);
    dat = dat >> 1;
    LCD_D6 = (dat & 0x01);
    dat = dat >> 1;
    LCD_D7 = (dat & 0x01);

    wait_usec(50);
    LCD_E = 0;
    wait_usec(50);
    LCD_RS = 0;

}

引数rsが0の時コマンド、1の時文字データとして扱います。

LCDへは上位4ビット、下位4ビットの順に送信します。

例として引数datに0010 1000が渡されたとします。

順番だけ見ればLCDは0010 0001と認識しそうですがLCD_D7が12ビット目なのを思い出すと

それぞれ反転して0010 1000となることがわかるでしょう。

LCD初期化関数

void lcd_format(void){

    wait_msec(20);
    lcd_out8(0x23);
    wait_msec(10);
    lcd_out8(0x23);
    lcd_out8(0x22);
    lcd_out4(0, 0x28);
    lcd_out4(0, 0x0E);
    lcd_out4(0, 0x06);
    lcd_out4(0, 0x02);

}

仕様書よりも長めにwaitを入れています。

  • lcd_out8(0x23)
    Function Set コマンド
    0010 0011 -> (認識)0011 0000

  • lcd_out8(0x22)
    Function Set コマンド
    0010 0010 -> (認識)0010 0000

  • lcd_out4(0, 0x28)
    Function Set コマンド
    0010 1000

  • lcd_out4(0, 0x0E)
    Display ON/OFF コマンド
    0000 1110

  • lcd_out4(0, 0x06)
    Entry ModeSet コマンド
    0000 0110

  • lcd_out4(0, 0x02)
    Cursor At Home コマンド
    0000 0010

1文字表示関数

void lcd_data(unsigned char asci){
    lcd_out4(1, asci);
    wait_usec(50);
}

文字列表示関数

void lcd_puts(unsigned char *str){
    while(*str != 0x00){
        lcd_out4(1, *str);
        str++;
    }
}

数字表示関数

void lcd_convert(unsigned int number){
    lcd_data('0' + number);
}

画面消去関数

void lcd_clear(void){
    lcd_out4(0, 0x01);
    wait_usec(1650);
}

LCD制御関数を使ってみよう

まずはコンフィギュレーション設定をしましょう。

_FOSC(CSW_FSCM_OFF & XT_PLL8);
_FWDT(WDT_OFF);
_FBORPOR(PBOR_ON & BORV_20 & PWRT_64 & MCLR_EN);
_FGS(CODE_PROT_OFF);
  • _FOSC(CSW_FSCM_OFF & XT_PLL8)
    クロック停止の際の処理を監視せず、内部の別クロック源への切り替えもしないので
    CSW(クロック切り替え)とFSCM(クロックのエラー検出)をOFFに設定
    8x10MHz(外部発振器) = 80MHz(クロック周波数)

  • _FWDT(WDT_OFF)
    特にシステムの監視を行わないためウォッチドックタイマを無効

  • _FBORPOR(PBOR_ON & BORV_20 & PWRT_64 & MCLR_EN)
    電源ON直後に動作をリセット
    電源OFF直後に動作停止
    電源OFFを検出する電圧 -> 2.0V
    電源ON直後のリセットパルス幅 -> 64msec
    MCLRピンを有効

  • _FGS(CODE_PROT_OFF)
    コードプロテクト -> 読み出し、書き込みともにOFF

main文の中

unsigned char msgA[] = "start";
LATB = 0x0;
TRISB = 0x00;

lcd_format();

lcd_puts(msgA);
wait_msec(1000);

ConfigIntTimer1(T1_INT_PRIOR_3 & T1_INT_ON);
OpenTimer1(T1_ON & T1_GATE_OFF & T1_PS_1_64 & 
           T1_SYNC_EXT_OFF & T1_SOURCE_INT, 31250-1);

while(1);
  • ConfigIntTimer1(T1_INT_PRIOR_3 & T1_INT_ON)
    優先度3 & 割り込み許可

  • OpenTimer1(...)
    タイマ周期=(4/クロック周波数)x(プリスケーラ分周比)x(PR1設定-1) 100msec = 4/80MHz x 64 x 31250

T1_ON : タイマ1を有効
T1_GATE_OFF : ゲートタイマモードOFF(外部信号の時間幅の計測をしない)
T1_PS_1_64 : プリスケーラ分周比を64に設定
T1_SYNC_EXT_OFF : タイマ非同期
T1_SOURCE_INT : 内部クロックを使用
31250-1 : PR1設定

タイマ1の定義

void _ISR _T1Interrupt(void){
    static unsigned int TimeCount1 = 0;
    IFS0bits.T1IF = 0;

    TimeCount1++;

    if(TimeCount1 <= 9){
        lcd_clear();
        lcd_convert(TimeCount1);
    }else{
        TimeCount1 = 0;
    }
}
  • IFS0bits.T1IF = 0
    割り込みサブルーチンの構文では割り込みフラグをクリアできないため

以上で100msec毎に1~9までの範囲でカウントアップするプログラムは完成です。



blog comments powered by Disqus

Published

18 August 2013

Category

SPPBoard

Tags

Profile

千葉工大産のロボットナビゲーションエンジニア

ros-jpの勉強会の主催やロボカップ世界大会優勝チームのリーダをやってました。


 
badge_description about badge's

 

Follow me on Github

Follow me on Qiita

総訪問者数

アクセスカウンター