LCD表示器を取り付ける

これまで、GPIO、タイマ、割り込みという重要な要素の使い方を学んできました.
これらがわかったので、LCDを取り付けてお手軽に表示させることができるようになります.

LCDは、秋月電子で売っているものです.
14pinのピンヘッダでマザーボードと接続するようになっています.
したがって、この14pinを制御するのがSTM8S-DISCOVERYのお仕事になります.

LCDキャラクタディスプレイモジュール(16×2行バックライト無)  SC1602BS
SUNLIKE社液晶モジュール、超ハイコントラスト視野角70°
■5V単一電源動作
◆16文字×2行表示
◆インターフェースIC:HD44780コンパチブル
◆LEDバックライト非搭載




















どんなことをやらせるか?

LCD moduleは16文字2行の表示機能がありますので、とりあえず日時分秒を表示させるようにしましょう.
出来上がり写真は下図です.
STM8S-DISCOVERYの下右に14ピンのピンヘッダを取り付けて、そこへ配線して、LCDのピンヘッダをグサッと挿して表示させます.



システム仕様

LCDの仕様とSTM8Sのポートを見て考えて、次のような配線でSTM8SとLCDを結線することにしました.
STM8Sのポートは、なるべく他の機能へピンアサインされてないピンをLCD用途に使います.


注意点:
●LCD moduleのDB[3:0]が未接続である理由は、配線を少なくするためにLCDの4bit interface modeを使うからです.
●LCD moduleの3ピンには、C 0.1uF と R 4.7kΩ がついています.
このピンはLCDのコントラスト調節をするためのアナログ入力ピンです.電圧が低いとコントラストが濃くなります.
ボリウムを一つ追加すれば済むのですが、それだと芸がないので、TIM3のPWM出力をCRでLPFしてDC電圧にしてLCDに入力することにしました.

システムダイアグラムはこうなります.



ハードウエアの準備

ピンヘッダに配線しなくちゃはじまりません.
●STM8S-DISCOVERYのユニバーサル基板部に、14pinピンヘッダを取り付けて、下図のように配線します.
●STM8S-DISCOVERYのJP1は、下図のようにshortします.これでVDD=5Vが供給されます.

TOP VIEW


BOTTOM VIEW




ソースコードの解説

サンプルプログラムはこちらです.
以下のソースコードは、project名test32の main.c と usrlib-lcd.c です.

main.c

●#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stm8s.h"
Cの標準関数を使うためによくあるファイルをインクルードしておきます.

●#include "usrlib-lcd.h"
これは私の自作のファイルです.LCDのドライバを使うためにインクルードします.

●CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
日時分秒を表示するので周波数精度が高いほうがよいので、外付け水晶(HSE)を源発振にします.
HSEではプリスケーラがデフォルトで外されますので、16MHzが、タイマーやCPUに供給されることになります.

●LCD_Init();
LCDを初期化する自作の関数です.あとで内部を説明します.

●TIM2_DeInit();
TIM2_TimeBaseInit( TIM2_PRESCALER_128, 12499 ); // 16MHz/128/12500=10Hz
TIM2_ITConfig(TIM2_IT_UPDATE, ENABLE);
TIM2_Cmd(ENABLE);
enableInterrupts();
日時分秒をカウントするための0.1秒毎の割り込みです.

●int day=0,hour=0,min=0,sec=0,sec10=0;
void TIM2_interrupt(void) interrupt 13   0.1秒ごとに呼び出される割り込みルーチン
{
    char str[20];
    GPIO_WriteReverse(GPIOD, GPIO_PIN_0);   割り込みインジケータとしてLEDを点滅させる
    sec10++;
    if(sec10>=10)
    {
        sec10=0;
        sec++;
        if(sec>=60)
        {
            sec=0;
            min++;
            if(min>=60)
            {
                min=0;
                hour++;
                if(hour>=24)
                {
                    hour=0;
                    day++;
                }   }    }    }    ここまでは、日時分秒をカウントしています
  sprintf(str,"%02ud%02uh%02um%02u.%1us",day,hour,min,sec,sec10);   日時分秒をstrという変数に格納
  LCD_Disp_Str(str,LCD_DISP_UPPERLINE);    LCDに表示させる自作関数
  LCD_Disp_Str(str,LCD_DISP_LOWERLINE);   同上
  TIM2_ClearFlag(TIM2_FLAG_UPDATE);   次回の割り込みのためフラグをクリアします
}

usrlib-lcd.c

●void LCD_Init(void)
LCDを初期化するルーチンです.

●volatile int i;
LCDのレジスタwriteのたびに50uSのwaitを挿入したいので、単純なfor-next-loopでタイマ代わりにしています.
そのfor-nextのための変数です.
volatileにしているのはコンパイラが高速化のためにシカトしないようにするためです.

●GPIO_Init(GPIOG, GPIO_PIN_0, GPIO_MODE_OUT_PP_HIGH_FAST); // LCD DB0
GPIO_Init(GPIOG, GPIO_PIN_1, GPIO_MODE_OUT_PP_HIGH_FAST); // LCD DB1
GPIO_Init(GPIOE, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_FAST); // LCD E
GPIO_Init(GPIOC, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_FAST); // LCD RS
GPIO_Init(GPIOC, GPIO_PIN_6, GPIO_MODE_OUT_PP_HIGH_FAST); // LCD DB2
GPIO_Init(GPIOC, GPIO_PIN_7, GPIO_MODE_OUT_PP_HIGH_FAST); // LCD DB3
GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_PP_HIGH_FAST); // LED
これらをLCDへの出力ポートとして使います.(末尾はLEDインジケータです)

●TIM3_DeInit();
TIM3_TimeBaseInit( TIM3_PRESCALER_1, 159 ); // 16MHz/1/160=100kHz
TIM3_OC1Init( TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, 20, TIM3_OCPOLARITY_HIGH );
TIM3_Cmd(ENABLE);
LCD moduleの3ピンは、LCDのコントラスト調節をするためのアナログ入力ピンです.電圧が低いとコントラストが濃くなります.
本来はボリウムを一つ追加すれば済むのですが、それだと芸がないので、TIM3のPWM出力をCRでLPFしてDC電圧にしてLCDに入力することにしました.
そのために、TIM3を占有します.
TIM3_CH1 [43pin]に出力が出ます.
引数に20という数字が見えますが、これがコントラスト調節値です.大きくすると薄くなり、小さくすると濃くなるはずです.
(20でしか試してないんですけど....)

●for(i=0;i<=20000;i++){;} // wait 20mS
LCD_Write_Nibble(LCD_REG_FUNC,0b0011);
for(i=0;i<=5000;i++){;} // wait 5mS
LCD_Write_Nibble(LCD_REG_FUNC,0b0011);
for(i=0;i<=20;i++){;} // wait 100uS
LCD_Write_Nibble(LCD_REG_FUNC,0b0011);
LCD_Write_Nibble(LCD_REG_FUNC,0b0010); // 4bit
LCD_Write_Nibble(LCD_REG_FUNC,0b0010); // 4bit
LCD_Write_Nibble(LCD_REG_FUNC,0b1000); // 2lines 5x7dots
LCD_Write_Nibble(LCD_REG_FUNC,0b0000);
LCD_Write_Nibble(LCD_REG_FUNC,0b1000); // display off
LCD_Write_Nibble(LCD_REG_FUNC,0b0000);
LCD_Write_Nibble(LCD_REG_FUNC,0b0001); // clear display
for(i=0;i<=2000;i++){;} // 2mS wait
LCD_Write_Nibble(LCD_REG_FUNC,0b0000);
LCD_Write_Nibble(LCD_REG_FUNC,0b1100); // increment not-shift
LCDを4bit通信モードで初期化しています.
やっていることは、LCD moduleの仕様書に書かれているとおりです.
いろいろなwait時間を挟んでいますが、これも仕様書のとおりです.

●void LCD_Write_Nibble(u8 RS,char C)
4bitをLCD moduleにwriteするルーチンです.
第1引数は、つぎのどちらかです.
     LCD_REG_FUNC: function register is written
     LCD_REG_CHAR: character register is written
レジスタ設定のときは.....FUNCにします.
キャラクタ表示のときは......CHARにします.
第2引数は、下4bit[3:0]だけがLCDにwriteされます.[7:4]は無視します.

●volatile int i;
LCDのレジスタwriteのたびに50uSのwaitを挿入したいので、単純なfor-next-loopでタイマ代わりにしています.
そのfor-nextのための変数です.
volatileにしているのはコンパイラが高速化のためにシカトしないようにするためです.

●if(RS==0)        GPIO_WriteLow ( GPIOC, GPIO_PIN_5 ); // RS
if(RS==1)        GPIO_WriteHigh( GPIOC, GPIO_PIN_5 );
if((C&1)==0)    GPIO_WriteLow ( GPIOG, GPIO_PIN_0 ); // DB4=data[0]
if((C&1)==1)    GPIO_WriteHigh( GPIOG, GPIO_PIN_0 );
if((C&2)==0)    GPIO_WriteLow ( GPIOG, GPIO_PIN_1 ); // DB5=data[1]
if((C&2)==2)    GPIO_WriteHigh( GPIOG, GPIO_PIN_1 );
if((C&4)==0)    GPIO_WriteLow ( GPIOC, GPIO_PIN_6 ); // DB6=data[2]
if((C&4)==4)    GPIO_WriteHigh( GPIOC, GPIO_PIN_6 );
if((C&8)==0)    GPIO_WriteLow ( GPIOC, GPIO_PIN_7 ); // DB7=data[3]
if((C&8)==8)    GPIO_WriteHigh( GPIOC, GPIO_PIN_7 );
RSとDB[7:4]に出力します.

●GPIO_WriteHigh( GPIOE, GPIO_PIN_5 ); // E
GPIO_WriteLow ( GPIOE, GPIO_PIN_5 );
EにHIGHのパルスを出力します.これでLCDにwriteされます.

●for(i=0;i<=20;i++){;}
約50uSec waitします.
タイマを占有したくなかったので、こんな原始的な手法にしました.
これでfCPU=16MHzのときに実測で50uSecになります.
もしもfCPUを低くしたときは50uSecよりも長くなるのがイマイチですが、長くなる分には不具合は生じません.

●void LCD_Write_Char(u8 RS,char C){
LCD_Write_Nibble(RS,C>>4);
LCD_Write_Nibble(RS,C);   }
LCD に1キャラクタをwriteします.
第1引数は、つぎのどちらかです.
     LCD_REG_FUNC: function register is written
     LCD_REG_CHAR: character register is written
レジスタ設定のときは.....FUNCにします.
キャラクタ表示のときは......CHARにします.

第2引数は、ASCIIのキャラクタを1文字です.表示可能なキャラクタの一覧表はこちらです.

●void LCD_Disp_Str(char *str, u8 line)  {
    int i;
    for(i=0;i<=strlen(str)-1;i++)
    {
        if(i==0)
        {
            if(line==LCD_DISP_UPPERLINE) LCD_Write_Char(LCD_REG_FUNC,0b10000000); // DDRAM-adrs=0
            if(line==LCD_DISP_LOWERLINE) LCD_Write_Char(LCD_REG_FUNC,0b11000000); // DDRAM-adrs=40H
        }
        LCD_Write_Char(LCD_REG_CHAR,str[i]);
    }  }
LCDに文字列をwriteする関数です.
LCD moduleは、16文字を2行表示できます.
なのでこの関数は、1行目あるいは2行目に最大16文字を表示できます.
第1引数は、表示したい16文字までの文字列です.
第2引数は、1行目か2行目の選択です.
次のどちらかです.
   LCD_DISP_UPPERLINE    1行目に表示
   LCD_DISP_LOWERLINE   2行目に表示

inserted by FC2 system