Raspberry Pi でI2C: キャラクタLCD
秋月のACM1602NI-FLW-FBW-M01を使ったキャラクタLCDモジュールを購入しました。
Raspberry Piに接続するも、当初思ったように動かなくて困っていたのですが こちらのページ に答えがあり、
要するにモジュールのPICマイコンの処理が間に合っていないという残念な状態のようです。
I2Cクロックを下げてとりあえず動いたので、メモっときます。
接続
LCDモジュールのコネクタは7ピンありますが、Raspberry Pi 側からは VDD(3.3V), GND, SDL, SDA を引っ張ってくればOKです。
コントラスト(V0)は調整可能にしておいた方がいいですけど、0.3Vくらいを入れておけばとりあえず丁度いいです。
注意点
上記ページに書かれている通り、このLCDを使うにはいくつかの注意点があります。
- リードアクセスしない(i2c-toolsのi2cdetectとかもダメ)
- I2Cクロックを50kHzくらいに落とす
以上を踏まえ、進めます。
ドライバのロード
i2c-bcm2708ドライバのオプションに転送レートを指定してロードします。
i2c関係のドライバがロードされている場合は、一旦アンロード
% sudo rmmod i2c-dev i2c-bcm2708
I2Cクロックを50kHzに指定してモジュールをロード
% sudo modprobe i2c-bcm2708 baudrate=50000 % sudo modprobe i2c-dev
これを恒久的な設定とする場合は、/etc/modprobe.d/i2c.conf として以下の内容で作成します。
options i2c_bcm2708 baudrate=50000
設定とデータ送信
i2c-toolsを使用して表示テストをしてみます。
% sudo apt-get i2c-tools
I2Cアドレス
このモジュールでI2CをしゃべっているのはPICのコントローラなのですが
(この人があまりきちんとしていないためにいろいろが制限ある)、以下のアドレスにマップされています。
スレーブアドレス: 0x50
レジスタアドレス
コマンド: 0x00
データ: 0x80
コントロールコマンド
コマンド内容については、製品添付の仕様書は印刷が読みづらい上に必要な説明があんまりないので、
LCDコントローラの仕様書を見たほうがいいです。
以下はi2csetを使った設定例です。
% sudo /usr/sbin/i2cset -y 1 0x50 0x00 0x01 # clear display % sudo /usr/sbin/i2cset -y 1 0x50 0x00 0x38 # function set: 8bit, 2lines, 5x8 % sudo /usr/sbin/i2cset -y 1 0x50 0x00 0x0c # display on, no cursor % sudo /usr/sbin/i2cset -y 1 0x50 0x00 0x06 # direction=INC, no shift
データ書き込み
同じくi2csetを使ってデータを書き込みます。
% sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x41 # 'A' % sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x42 # 'B' % sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x43 # 'C' % sudo /usr/sbin/i2cset -y 1 0x50 0x00 0xc0 # move to second line % sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x61 # 'a' % sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x62 # 'b' % sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x63 # 'c'
以上で基本的な動作が確認できました。
サンプルアプリ
簡単にCでコード書いてみました。コマンドのビット定義はこんなに気合入れるつもり無かったんですが...
busyloop() でタイミング調整してるところは時間は適当なのであしからず。
/* * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE * Version 2, December 2004 * * Copyright (C) 2013 penkoba * * Everyone is permitted to copy and distribute verbatim or modified * copies of this license document, and changing it is allowed as long * as the name is changed. * * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION * * 0. You just DO WHAT THE FUCK YOU WANT TO. */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/i2c-dev.h> #define RPI_I2C_DEV "/dev/i2c-1" /* Rev.2 */ #define ACM1602N1_SLAVE 0x50 #define ACM1602N1_ADR_CMD 0x0 #define ACM1602N1_ADR_DAT 0x80 #define ACM1602N1_CMD_CLEAR_DISP 0x01 #define ACM1602N1_CMD_RETURN_HOME 0x02 #define ACM1602N1_CMD_ENTRY_MODE_ID_INC 0x02 #define ACM1602N1_CMD_ENTRY_MODE_ID_DEC 0x00 #define ACM1602N1_CMD_ENTRY_MODE_S_SHIFT 0x01 #define ACM1602N1_CMD_ENTRY_MODE_S_NOSHIFT 0x00 #define ACM1602N1_CMD_ENTRY_MODE(id, s) \ (0x04 | \ ACM1602N1_CMD_ENTRY_MODE_ID_##id | \ ACM1602N1_CMD_ENTRY_MODE_S_##s) #define ACM1602N1_CMD_DISP_ON_OFF_DISP_ON 0x04 #define ACM1602N1_CMD_DISP_ON_OFF_DISP_OFF 0x00 #define ACM1602N1_CMD_DISP_ON_OFF_CUR_ON 0x02 #define ACM1602N1_CMD_DISP_ON_OFF_CUR_OFF 0x00 #define ACM1602N1_CMD_DISP_ON_OFF_BLINK_ON 0x01 #define ACM1602N1_CMD_DISP_ON_OFF_BLINK_OFF 0x00 #define ACM1602N1_CMD_DISP_ON_OFF(disp, cursor, blink) \ (0x08 | \ ACM1602N1_CMD_DISP_ON_OFF_DISP_##disp | \ ACM1602N1_CMD_DISP_ON_OFF_CUR_##cursor | \ ACM1602N1_CMD_DISP_ON_OFF_BLINK_##blink) #define ACM1602N1_CMD_CUR_DISP_SHIFT_SC_DISP 0x08 #define ACM1602N1_CMD_CUR_DISP_SHIFT_SC_CUR 0x00 #define ACM1602N1_CMD_CUR_DISP_SHIFT_DIR_RIGHT 0x04 #define ACM1602N1_CMD_CUR_DISP_SHIFT_DIR_LEFT 0x00 #define ACM1602N1_CMD_CUR_DISP_SHIFT(sc, dir) \ (0x10 | \ ACM1602N1_CMD_CUR_DISP_SHIFT_SC_##sc | \ ACM1602N1_CMD_CUR_DISP_SHIFT_DIR_##dir) #define ACM1602N1_CMD_FUNCTION_DATA_LEN_8BIT 0x10 #define ACM1602N1_CMD_FUNCTION_DATA_LEN_4BIT 0x00 #define ACM1602N1_CMD_FUNCTION_LINE_NUMBER_2LINES 0x08 #define ACM1602N1_CMD_FUNCTION_LINE_NUMBER_1LINE 0x00 #define ACM1602N1_CMD_FUNCTION_FONT_5x11 0x04 #define ACM1602N1_CMD_FUNCTION_FONT_5x8 0x00 #define ACM1602N1_CMD_FUNCTION_SET(data_len, line_number, font) \ (0x20 | \ ACM1602N1_CMD_FUNCTION_DATA_LEN_##data_len | \ ACM1602N1_CMD_FUNCTION_LINE_NUMBER_##line_number | \ ACM1602N1_CMD_FUNCTION_FONT_##font) #define ACM1602N1_CMD_SET_CGRAM_ADR(adr) (0x40 | adr) #define ACM1602N1_CMD_SET_DDRAM_ADR(adr) (0x80 | adr) static void busyloop(unsigned long n) { volatile unsigned long i; /* volatile not to be optimized */ for (i = 0; i < n; i++); } static int acm_cmd_write(int fd, unsigned char cmd) { const unsigned char buf[2] = {ACM1602N1_ADR_CMD, cmd}; if (write(fd, buf, 2) != 2) { fprintf(stderr, "ACM1602N1 command write error: 0x%02x\n", cmd); return -1; } return 0; } static int acm_dat_write(int fd, const unsigned char *dat, int n) { unsigned char buf[n * 2]; int i; for (i = 0; i < n; i++) { buf[i * 2] = ACM1602N1_ADR_DAT; buf[i * 2 + 1] = dat[i]; } if (write(fd, buf, n * 2) != n * 2) { fprintf(stderr, "ACM1602N1 data write error: data=[0x%02x, ...]\n", dat[0]); return -1; } return 0; } static int init_acm(int fd) { if (acm_cmd_write(fd, ACM1602N1_CMD_CLEAR_DISP) < 0) return -1; busyloop(0x10000); /* clear display takes some millisecs */ if (acm_cmd_write(fd, ACM1602N1_CMD_FUNCTION_SET(8BIT, 2LINES, 5x8)) < 0) return -1; if (acm_cmd_write(fd, ACM1602N1_CMD_DISP_ON_OFF(ON, OFF, OFF)) < 0) return -1; if (acm_cmd_write(fd, ACM1602N1_CMD_ENTRY_MODE(INC, NOSHIFT)) < 0) return -1; return 0; } int main(void) { int fd; const unsigned char test_dat1[] = "good morning!"; const unsigned char test_dat2[] = { 0xb5, 0xca, 0xd6, 0xb3, 0xba, 0xde, 0xbb, 0xde, 0xb2, 0xcf, 0xbd, 0x00 }; if ((fd = open(RPI_I2C_DEV, O_RDWR)) < 0) { printf("Faild to open i2c port\n"); return 1; } if (ioctl(fd, I2C_SLAVE, ACM1602N1_SLAVE) < 0) { printf("Unable to get bus access to talk to slave\n"); return 1; } if (init_acm(fd) < 0) return 1; if (acm_dat_write(fd, test_dat1, sizeof(test_dat1) - 1) < 0) return 1; if (acm_cmd_write(fd, ACM1602N1_CMD_SET_DDRAM_ADR(0x40)) < 0) return 1; if (acm_dat_write(fd, test_dat2, sizeof(test_dat2) - 1) < 0) return 1; return 0; }