PIC18F4550でI2C通信を行う
2017/03/08


次はちょっとだけ高度な処理としてI2C通信を使って16×2行のLCD表示器を動作させてみる。

LCDは、秋月電子の出している「I2C接続キャラクタLCDモジュール 16x2行 白色バックライト付」というもので、1280円である。

test_pgm005
電源も3.3VでPICKit-3から供給出来るので便利である。

※バックライトの電源は、単三電池2本の3Vを別個に供給している。



test_pgm006
回路は前回とほぼ同じで、SCLとSDAの二本の通信線が追加になる。

たったこれだけの追加で、16×2行の表示が出来るのだから、シリアル通信というのはたいしたものである。

現実には信号線2本とアース線の合計3本が必要なのであるが・・・・・


PIC18F4550にはI2C通信用のモジュールが組み込まれているので、以前の「チョコットカメラ奮闘記」の時のように自分でポートを直接制御したりはしないで良いのである。


プログラムは、以下のように3分割して作っておく。

1、I2Cの制御プログラム
2、LCDの制御プログラム
3、メインのプログラム


まずはI2Cを制御するプログラムのヘッダーファイル


void I2C_Master_Init(void);
void I2C_Master_Wait(void);
void I2C_Master_Start(void);
void I2C_Master_Stop(void);
void I2C_Master_Write(unsigned d);

#define _XTAL_FREQ 48000000     // 使用するPIC等により動作周波数値を設定する
                                // 水晶は20MHzでPLLで48MHz動作

次にI2Cを制御するプログラムの本体

初期設定の部分でI2Cを宣言するのだが、けっこう簡単で、以下のようなものである。

1、SSPSTATで、スルーレートは標準で、SMバスを使う(これは実はよく解っていなかったりする、実際SMBusの値は0でも1でも動くようである)
2、SSPCON1で、マスターモードで動作するということと、SDAとSCLのピンを使用するという宣言を行う
3、SSPADDでクロック周波数の設定を行う。

この計算式は、マニュアルを見るとclock=Fosc/(4*(SSPADD+1))となっている。
つまり、SSPADDを逆算するのは、SSPADD=(Fosc/(4*Clock))-1

LCDのマニュアルにはクロックが100KHzと書いてあり、その値でやっていたのだが全く動作しなかった。
当初は原因が解らず、初めてのI2Cだったので、どっか別のミスがあるに違いないとずいぶんと悩んでしまった。

とうとうネットで調べていたら、どっかのHP(どこだか忘れてしまった)で「この表示器は50KHz以下でないと動作が不安定だ」という記述を見つけたのである。
半信半疑で動作速度を落としたら、それだけで表示器が動いたのだった。
私の持っている表示器では80KHzくらいで動作をしている。


#include < xc.h >
#include "I2C.h"

void I2C_Master_Init()
{
    SSPSTAT = 0b11000000;        //
    SSPCON1 = 0b00101000;        //SSP Module as Master
    SSPCON2 = 0;
//    SSPADD = 119;                //Setting Clock Speed 100KHz
//    SSPADD = 239;                //Setting Clock Speed 50KHz
    SSPADD = 149;                //Setting Clock Speed 80KHz
}

void I2C_Master_Wait()
{
  while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F)); //Transmit is in progress
}

void I2C_Master_Start()
{
  I2C_Master_Wait();    
  SSPCON2bits.SEN = 1;             //Initiate start condition
}

void I2C_Master_Stop()
{
  I2C_Master_Wait();
  SSPCON2bits.PEN = 1;           //Initiate stop condition
}

void I2C_Master_Write(unsigned d)
{
  I2C_Master_Wait();
  SSPBUF = d;         //Write data to SSPBUF
}

次に、LCDを制御するプログラムのヘッダーファイル

ここに定義してあるのは、LCDのスレーブアドレスで、マニュアルによれば0xa0がアドレスで、コマンドを送る場合は2バイト目は0x00、データを送る場合は2バイト目は0x80となる。
コマンド本体やデータそのものは3バイト目で送信する。


#include < xc.h >

#define LCD_ADDR          0xa0  // LCDのアドレス
#define LCD_CMD           0x00  // コマンド
#define LCD_DATA          0x80  // データ

#define LCD_CLEAR_DISPLAY 0x01  // LCDクリア
#define LCD_FUNCTION_SET  0x38  // 8ビットデータ、2行
#define LCD_DISPLAY_ON    0x0c  // ディスプレイオン
#define LCD_ENTRY_MODE    0x06  //
#define LCD_LOC           0x80  //

void lcd_init(void);
void lcd_cls(void);
void lcd_loc(unsigned char, unsigned char);
void lcd_putc(unsigned char);
void lcd_str(char *);

#define _XTAL_FREQ 48000000     // 使用するPIC等により動作周波数値を設定する
                                // 水晶は20MHzでPLLで48MHz動作

LCDの制御プログラム

基本的に、初期設定と、画面のクリア、表示場所の設定、そしてその場所へ1文字表示、複数文字表示、の5種類である。


#include "LCD.h"
#include "I2C.h"

void lcd_init(void)
{
    I2C_Master_Init();
    __delay_ms(5) ;        // 5ms

    I2C_Master_Start();
    I2C_Master_Write(LCD_ADDR);
    I2C_Master_Write(LCD_CMD);
    I2C_Master_Write(LCD_CLEAR_DISPLAY);
    I2C_Master_Stop();
    __delay_ms(5) ;        // 5ms

    I2C_Master_Start();
    I2C_Master_Write(LCD_ADDR);
    I2C_Master_Write(LCD_CMD);
    I2C_Master_Write(LCD_FUNCTION_SET);
    I2C_Master_Stop();
    __delay_ms(5) ;        // 5ms

    I2C_Master_Start();
    I2C_Master_Write(LCD_ADDR);
    I2C_Master_Write(LCD_CMD);
    I2C_Master_Write(LCD_DISPLAY_ON);
    I2C_Master_Stop();
    __delay_ms(5) ;        // 5ms

    I2C_Master_Start();
    I2C_Master_Write(LCD_ADDR);
    I2C_Master_Write(LCD_CMD);
    I2C_Master_Write(LCD_ENTRY_MODE);
    I2C_Master_Stop();
    __delay_ms(5);        // 5ms
}

void lcd_cls(void)
{
    I2C_Master_Start();
    I2C_Master_Write(LCD_ADDR);
    I2C_Master_Write(LCD_CMD);
    I2C_Master_Write(LCD_CLEAR_DISPLAY);
    I2C_Master_Stop();
    __delay_ms(5);        // 5ms
}

void lcd_loc(unsigned char x, unsigned char y)
{
    unsigned char addr;

    addr = y * 0x40 + x;
    addr = addr | LCD_LOC;
    I2C_Master_Start();
    I2C_Master_Write(LCD_ADDR);
    I2C_Master_Write(LCD_CMD);
    I2C_Master_Write(addr);
    I2C_Master_Stop();
}

void lcd_putc(unsigned char c)
{
    I2C_Master_Start();
    I2C_Master_Write(LCD_ADDR);
    I2C_Master_Write(LCD_DATA);
    I2C_Master_Write(c);
    I2C_Master_Stop();
}

void lcd_str(char *s)
{
    unsigned char i;

    for (i = 0; i < 16; i ++) {
	if (*s == 0) {
	    break;
	}
    I2C_Master_Start();
	I2C_Master_Write(LCD_ADDR);
	I2C_Master_Write(LCD_DATA);
	I2C_Master_Write(*s);
    I2C_Master_Stop();
	s ++;
    }
}

最後にメインのプログラム

機能としては、画面イッパイにコード順の文字が流れていくもので、ただのデモンストレーションでしかない。



#include < stdio.h >
#include < stdlib.h >
#include < xc.h >

#include "I2C.h"
#include "LCD.h"

#define _XTAL_FREQ 48000000     // 使用するPIC等により動作周波数値を設定する
                                // 水晶は20MHzでPLLで48MHz動作
/*
 * 
 */
// コンフィギュレーション pic18f4550  c18
#pragma config PLLDIV = 5, CPUDIV = OSC1_PLL2, USBDIV = 2
#pragma config FOSC = HSPLL_HS, FCMEN = OFF, IESO = OFF
#pragma config PWRT = ON, BOR = OFF, BORV = 0
#pragma config VREGEN = ON, WDT = OFF, WDTPS = 1
#pragma config CCP2MX = OFF, PBADEN = OFF, LPT1OSC = OFF
#pragma config MCLRE = OFF, STVREN = OFF, LVP = OFF
#pragma config ICPRT = OFF, XINST = OFF, DEBUG = OFF
#pragma config CP0 = OFF, CP1 = OFF, CP2 = OFF, CP3 = OFF
#pragma config CPB = OFF, CPD = OFF
#pragma config WRT0 = OFF, WRT1 = OFF, WRT2 = OFF, WRT3 = OFF
#pragma config WRTC = OFF, WRTB = OFF, WRTD = OFF
#pragma config EBTR0 = OFF, EBTR1 = OFF, EBTR2 = OFF, EBTR3 = OFF, EBTRB = OFF

int main(int argc, char** argv) {
    
    unsigned char x;
    unsigned char y;
    unsigned char chr;

    TRISA = 0b00000000;			//PORTA=0-7 output   
  TRISB = 0b11111111;			//PORTB=0-7 input   
    LATA = 0;
    
    lcd_init();

    x = 0;
    y = 0;
    chr = 32;
    
    while(1){
        LATAbits.LA0 = 1;

        lcd_loc(x,y);
        lcd_putc(chr);
//        lcd_loc(1,1);      // I2C信号のテスト
//        lcd_putc('X');
        
        x = x + 1;
        if ( x > 15 ) {
            x = 0;
            y = y + 1;
            if ( y > 1 ) y = 0;
        }
	chr = chr + 1;
        if ( chr > 127 ) chr = 32;
        
        LATAbits.LA0 = 0;
        __delay_ms(5) ;        // ms
   }
    
    return (EXIT_SUCCESS);
}

これがI2Cで通信している信号である。
上段がSDA、下段がSCL

メインのプログラムの一部を修正して以下のようにする。
実際には、最初にこのコードで動作させてXが表示されることを確認し、その後色々な文字を表示させるように変更した。

//        lcd_loc(x,y);
//        lcd_putc(chr);
        lcd_loc(1,1);      // I2C信号のテスト
        lcd_putc('X');
信号が若干長いので、二つの画面を合成してある。
test_pgm007
送られている信号は、以下のようになっている。

1バイト目、10100000:これがLCDのスレーブアドレス
2バイト目、00000000:コマンドを送る。
3バイト目、11000001:カーソル設定で番地が2行目2列目
4バイト目、10100000:LCDのスレーブアドレス
5バイト目、10000000:データを送る。
6バイト目、01010001:大文字のX



HOME