LSI Jiu-Jitsu

電子工作とブラジリアン柔術

OLED 0.96インチで遊ぶ (5) - PICから表示

今回はPICから表示を行ってみます。
PICは秋月電子で購入したPIC16F18326を使用しました。
プログラム領域が多くて高機能、それでいて低価格(130円)という大変素晴らしいデバイスです。

部品はブレッドボードに実装しました。
RC0とRC1をI2Cバスとして使用してOLEDに接続しています。
I2Cのプルアップ抵抗は1KΩにしました。

回路図はこちらです。

f:id:mohran:20180606232928p:plain


続いてプログラムです。
クロックは内部発振の8MHzにして、I2Cクロックは20分周してSSD1306が動作できる最速の400KHzにしています。

#include <pic.h>
#include <htc.h>
#include <pic16f18326.h>

// クロックサイクル
#define _XTAL_FREQ  8000000

// I2Cデバイスアドレス
#define DEV_OLED_W  0x78  // 0111_1000

// ========================================
//  Config 1 ~ 4
// ========================================
#pragma config  FCMEN    = OFF     // クロックモニタ
#pragma config  CSWEN    = ON      // クロック切り替え(NOSC & NDIV有効)
#pragma config  CLKOUTEN = OFF     // クロック出力
#pragma config  RSTOSC   = HFINT1  // クロック選択(HFINTOSC(1MHz))
#pragma config  FEXTOSC  = OFF     // 外部発振
#pragma config  DEBUG    = OFF     // デバッグモード
#pragma config  STVREN   = OFF     // スタック異常リセット
#pragma config  PPS1WAY  = OFF     // PPSロック
#pragma config  BORV     = HIGH    // Brown-out Reset電圧設定
#pragma config  BOREN    = OFF     // Brown-out Resetイネーブル
#pragma config  LPBOREN  = OFF     // Low-Power Brown-out Resetイネーブル
#pragma config  WDTE     = OFF     // ウオッチドッグタイマ イネーブル
#pragma config  PWRTE    = ON      // Power-up Timer イネーブル(電源ONから64ms後にプログラムを開始)
#pragma config  MCLRE    = OFF     // Master Clear イネーブル
#pragma config  LVP      = OFF     // 低電圧プログラム
#pragma config  WRT      = OFF     // プログラムメモリ プロテクト
#pragma config  CPD      = OFF     // EEPROM プロテクト
#pragma config  CP       = OFF     // コード プロテクト


// ========================================
//  関数プロトタイプ
// ========================================
void i2c_idle(void);
void i2c_start(void);
void i2c_stop(void);
void i2c_send(unsigned char);
unsigned char i2c_recv(void);
void i2c_sack(void);
void i2c_snack(void);

void oled_init(void);
void oled_display(unsigned char);


// ========================================
//  プログラム メイン
// ========================================
void main(void){
  OSCCON1 = 0b01100000;  // NOSC=HFINTOSC(1MHz),NDIV=1
  OSCFRQ  = 0b00000100;  // HFFRQ=8MHz

  TRISA   = 0b00000000;
  TRISC   = 0b00000011;  // RC 0(SCL1),1(SDA1)
  PORTA   = 0b00000000;
  PORTC   = 0b00000000;
  LATA    = 0b00000000;
  LATC    = 0b00000011;
  ANSELA  = 0b00000000;
  ANSELC  = 0b00000000;

  SSP1CLKPPS = 0x10;  // RC0 - 入力先 = SCL1
  SSP1DATPPS = 0x11;  // RC1 - 入力先 = SDA1
  RC0PPS     = 0x18;  // RC0 - 出力源 = SCL1
  RC1PPS     = 0x19;  // RC1 - 出力源 = SDA1

  // I2C設定
  SSP1ADD  = 0b00000100;
  SSP1CON1 = 0b00101000;  // [7] WCOL   = 0    : ライト衝突無し
                          // [6] SSPOV  = 0    : 受信オーバーフロー無し
                          // [5] SSPEN  = 1    : SDA, SCL有効
                          // [4] CKP    = 0    : クロック極性(未使用)
                          // [3:0] SSPM = 1000 : I2Cマスターモード クロック=FOSC/(4*(SSP1ADD+1))
                          //                                                8MHz / (4*(4+1)) = 400KHz

  SSP1STAT = 0x00;
  INTCON   = 0x00;  // 割り込み無効

  __delay_ms(100);

  oled_init();

  while(1){
    oled_display(0x01);
    oled_display(0x02);
    oled_display(0x04);
    oled_display(0x08);
    oled_display(0x10);
    oled_display(0x20);
    oled_display(0x40);
    oled_display(0x80);
  }
}


// ========================================
//  I2C IDLE
// ========================================
void i2c_idle(void){
  while(SSP1CON2bits.SEN   |
        SSP1CON2bits.PEN   |
        SSP1CON2bits.RCEN  |
        SSP1CON2bits.ACKEN |
        SSP1STATbits.R_nW){}
}

// ========================================
//  I2C スタート送信
// ========================================
void i2c_start(void){
  i2c_idle();
  SSP1CON2bits.SEN = 1;      // スタート出力
  while(SSP1CON2bits.SEN){}  // スタート終了待ち
}

// ========================================
//  I2C ストップ送信
// ========================================
void i2c_stop(void){
  SSP1CON2bits.PEN = 1;      // ストップ出力
  while(SSP1CON2bits.PEN){}  // ストップ終了待ち
  i2c_idle();
}

// ========================================
//  I2C データ送信
// ========================================
void i2c_send(unsigned char data){
  SSP1BUF = data;                // データセット送信開始
  while(SSP1STATbits.BF){}       // 送信終了待ち
  while(SSP1CON2bits.ACKSTAT){}  // ACK返信待ち
  i2c_idle();                    // アイドル待ち
}

// ========================================
//  I2C データ受信
// ========================================
unsigned char i2c_recv(void){
  SSP1CON2bits.RCEN = 1;
  while(SSP1CON2bits.RCEN){}
  return(SSP1BUF);
}

// ========================================
//  I2C Ack送信
// ========================================
void i2c_sack(void){
  SSP1CON2bits.ACKDT = 0;
  SSP1CON2bits.ACKEN = 1;
  while(SSP1CON2bits.ACKEN){}
}

// ========================================
//  I2C NAck送信
// ========================================
void i2c_snack(void){
  SSP1CON2bits.ACKDT = 1;
  SSP1CON2bits.ACKEN = 1;
  while(SSP1CON2bits.ACKEN){}
}

// ========================================
//  OLED初期化
// ========================================
void oled_init(void){
  // 表示モード,アドレスモード指定
  i2c_start();
  i2c_send(DEV_OLED_W);
  i2c_send(0x00);  // コマンドライト
  i2c_send(0xa0);  // 表示モード設定 左右 - 通常:0xa0 / 逆転:0xa1
  i2c_send(0xc0);  // 表示モード設定 上下 - 通常:0xc0 / 逆転:0xc8
  i2c_send(0x20);  // アドレッシングモード指定
  i2c_send(0x00);  //   → Horizontalモード
  i2c_send(0x21);  // カラム指定
  i2c_send(0x00);  //   → 開始位置(0)
  i2c_send(0x7f);  //   → 終了位置(127)
  i2c_send(0x22);  // ページ指定
  i2c_send(0x00);  //   → 開始ページ(0)
  i2c_send(0x07);  //   → 終了ページ(7)
  i2c_send(0x8d);  // チャージポンプ設定
  i2c_send(0x14);  //   → On
  i2c_send(0xaf);  // 画面表示 On
  i2c_stop();
}

// ========================================
//  OLED表示
// ========================================
void oled_display(unsigned char data){
  i2c_start();
  i2c_send(DEV_OLED_W);
  i2c_send(0x40);  // データライト
  for(int j=0; j<8; j++){
    for(int i=0; i<128; i++){
      i2c_send(data);
    }
  }
  i2c_stop();
}

XC8でコンパイル後、PICKit3を通して書き込みます。

8本のラインが上から下へスクロールするというシンプルなプログラムです。

PICのMSSPモジュールを使用したことによりI2C通信のプログラムは簡単に実装できました。

[参考記事]