PIC18F4550でI2C通信を行う
2017/03/08
次はちょっとだけ高度な処理としてI2C通信を使って16×2行のLCD表示器を動作させてみる。
LCDは、秋月電子の出している「I2C接続キャラクタLCDモジュール 16x2行 白色バックライト付」というもので、1280円である。
電源も3.3VでPICKit-3から供給出来るので便利である。
※バックライトの電源は、単三電池2本の3Vを別個に供給している。
回路は前回とほぼ同じで、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');
信号が若干長いので、二つの画面を合成してある。
送られている信号は、以下のようになっている。
1バイト目、10100000:これがLCDのスレーブアドレス
2バイト目、00000000:コマンドを送る。
3バイト目、11000001:カーソル設定で番地が2行目2列目
4バイト目、10100000:LCDのスレーブアドレス
5バイト目、10000000:データを送る。
6バイト目、01010001:大文字のX
HOME