前回の (5) に続いて今回もPICから表示を行ってみます。
表示する画像データは予め外部のEEPROMに格納しておき、I2Cバスを通してリードを行いOLEDへ送信するようにしました。
EEPROMは24LC256を使用しました。
秋月電子で100円でした。
1枚の画像データのサイズは128x64=8,192bit(1,024Byte)なので、32KByteの24LC256には最大32枚セットしておくことができます。
今回、画像データはフリー素材の馬が走っているアニメーションgifから作りました。
アニメーションgifをこちらのサイトで1枚づつに分解しておきます。
分解した画像を (3) で使用したgimpで128x64にリサイズしてpbmファイルに変換します。
そして、EEPROMへ書き込むにはHEXフォーマットにしておく必要があるので、スクリプトを通してpbmファイルをHEXファイルに変換します。
#!/usr/bin/perl -w open(RFP, "<" . $ARGV[0]) || die("Fatal : Can't open file ... $ARGV[0]\n"); $base = hex($ARGV[1]); @pbm = (); @dat = (); @dat_len = (); $w = 0; $h = 0; $i = 0; while(<RFP>){ $l = $_; chomp($l); $l =~ s/#.*$//; if($l eq ''){ next; } $i++; if($i==1){ if($l ne 'P1'){ die("Error : It isn't P1 format ...\n"); } }elsif($i==2){ ($width, $height) = split(/\s+/, $l); }else{ $l =~ s/\s+//g; foreach $d (split(//, $l)){ $pbm[$h][$w] = $d; $w++; if($w==$width){ $h++; $w = 0; } } } } close(RFP); $i = 0; for($h=0; $h<$height; $h+=8){ for($w=0; $w<$width; $w++){ $d = unpack("C", pack("B8", $pbm[$h+7][$w] . $pbm[$h+6][$w] . $pbm[$h+5][$w] . $pbm[$h+4][$w] . $pbm[$h+3][$w] . $pbm[$h+2][$w] . $pbm[$h+1][$w] . $pbm[$h+0][$w])); $dat[$i] = $d; $dat_len[($i / 16)] = ($i % 16) + 1; $i++; } } $i = 0; foreach $len (@dat_len){ $adr_h = ($base + $i * 0x10) >> 8 & 0xff; $adr_l = ($base + $i * 0x10) & 0xff; $rec = 0x00; $sum = $len; $sum += $adr_h; $sum += $adr_l; $s = sprintf(":%02x%02x%02x%02x", $len, $adr_h, $adr_l, $rec); for($j=0; $j<$len; $j++){ $d = $dat[$i * 16 + $j]; $s .= sprintf("%02x", $d); $sum += $d; } $sum &= 0xff; if($sum != 0x00){ $sum = 0x100 - $sum; } $s .= sprintf("%02x", $sum); print $s . "\n"; $i++; }
引数にpbmファイルとEEPROMへ格納するアドレスを16進数で指定します。
1枚当たり1,024Byteなので、0x400番地単位でピッタリ収まります。
使用したアニメーションgifは8枚で構成されていました。
$ ./pbm2hex.pl horse0.pbm 0000 > eeprom.hex $ ./pbm2hex.pl horse1.pbm 0400 >> eeprom.hex $ ./pbm2hex.pl horse2.pbm 0800 >> eeprom.hex $ ./pbm2hex.pl horse3.pbm 0c00 >> eeprom.hex $ ./pbm2hex.pl horse4.pbm 1000 >> eeprom.hex $ ./pbm2hex.pl horse5.pbm 1400 >> eeprom.hex $ ./pbm2hex.pl horse6.pbm 1800 >> eeprom.hex $ ./pbm2hex.pl horse7.pbm 1c00 >> eeprom.hex
HEXファイルをFTPでWindowsに移してAKI-PICプログラマー Ver.4を使って24LC256へ書き込みました。
本当はPICKit3を使ってMPLAB IPEからオンボードで書き込みたかったのですが、どうやらIPEは24LC256に対応していないようでした。
設定画面で24LC256を選択できるのに転送できない。
赤丸は非対応と言う意味を理解するのに2時間ほどかかりました(笑)
そこで、旧版のPICKit3プログラマーを使おうとしたらPICKit3のファームウェアを書き換えないと使えないようでした。
しかしそのファームウェアはPIC16F18326には非対応なので、IPEを使うにはまたファームウェアを書き換えてと・・。
あまりにも面倒なので諦めてAKI-PICプログラマーを使うことにしたのです(笑)
回路とプログラムはこのようにしました。
#include <pic.h> #include <htc.h> #include <pic16f18326.h> // クロックサイクル #define _XTAL_FREQ 8000000 // I2Cデバイスアドレス #define DEV_OLED_W 0x78 // 0111_1000 #define DEV_EEPROM_W 0xa0 // 1010_0000 #define DEV_EEPROM_R 0xa1 // 1010_0001 #define PAGE_MAX 8 #define PAGE_OFFSET 0x400 // ======================================== // 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){ unsigned char page; page = 0; 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(page); page++; if(page==PAGE_MAX){ page = 0; } } } // ======================================== // 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 page){ unsigned char data00[64], data01[64], data02[64], data03[64], // [128]では何故かエラー(xc8) data04[64], data05[64], data06[64], data07[64], data08[64], data09[64], data10[64], data11[64], data12[64], data13[64], data14[64], data15[64]; unsigned char addr_high; unsigned char addr_low; unsigned short addr; addr = page * PAGE_OFFSET; addr_high = addr >> 8; addr_low = addr & 0xff; // EEPROMリード i2c_start(); i2c_send(DEV_EEPROM_W); // Write i2c_send(addr_high); // High Addr i2c_send(addr_low); // Low Addr i2c_start(); i2c_send(DEV_EEPROM_R); // Read for(int i=0; i<64; i++){ data00[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data01[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data02[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data03[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data04[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data05[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data06[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data07[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data08[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data09[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data10[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data11[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data12[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data13[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<64; i++){ data14[i] = i2c_recv(); i2c_sack(); } for(int i=0; i<63; i++){ data15[i] = i2c_recv(); i2c_sack(); } data15[63] = i2c_recv(); i2c_snack(); i2c_stop(); // OLED表示 i2c_start(); i2c_send(DEV_OLED_W); i2c_send(0x40); // データライト for(int i=0; i<64; i++){ i2c_send(data00[i]); } for(int i=0; i<64; i++){ i2c_send(data01[i]); } for(int i=0; i<64; i++){ i2c_send(data02[i]); } for(int i=0; i<64; i++){ i2c_send(data03[i]); } for(int i=0; i<64; i++){ i2c_send(data04[i]); } for(int i=0; i<64; i++){ i2c_send(data05[i]); } for(int i=0; i<64; i++){ i2c_send(data06[i]); } for(int i=0; i<64; i++){ i2c_send(data07[i]); } for(int i=0; i<64; i++){ i2c_send(data08[i]); } for(int i=0; i<64; i++){ i2c_send(data09[i]); } for(int i=0; i<64; i++){ i2c_send(data10[i]); } for(int i=0; i<64; i++){ i2c_send(data11[i]); } for(int i=0; i<64; i++){ i2c_send(data12[i]); } for(int i=0; i<64; i++){ i2c_send(data13[i]); } for(int i=0; i<64; i++){ i2c_send(data14[i]); } for(int i=0; i<64; i++){ i2c_send(data15[i]); } i2c_stop(); }
1画面分の画像データをEEPROMからリードして内部変数に保持しておきOLEDへ送信します。
送信後は次の画面へと繰り返します。
I2Cバスのスピードは400KHzで毎フレーム全画素を書き換えていますが、モタつくことも無くスムーズに表示されました。
動画はこちらです。
[参考記事]