Petalinux2018.3でキャラクタLCD制御(ST7032i) のバックアップ(No.8)

更新


電気回路/zynq

I2C 経由で繋いだキャラクタ型の液晶ディスプレイ(LCD)を制御する

繋いだのはこれ digikey:NHD-C0216CIZ-FSW-FBW-3V3-ND

16 文字 x 2 行 で白色バックライト付きの LCD

付属の PDF だけだといろいろ情報が足りなくて困るのだけれど、 どうやらこの LCD には Strawberry Linux の ST7032i という有名なコントローラが載っているらしいので、このキーワードで検索すればいろいろと情報が得られる。

ST7032i のマニュアル:
https://strawberry-linux.com/pub/ST7032i.pdf

環境

作業履歴 のように Petalinux 2018.3 を基本として作業してます。

LCD は i2c-1 に繋いだ。Petalinux は i2c-1 を認識して dtb に反映してくれていた。

Zynq 機器で動作する Ubuntu 18.04LTC のコンソールで、

LANG:console
$ less /boot/system.dts
 ...

 i2c1: i2c@e0005000 {
  compatible = "cdns,i2c-r1p10";
  status = "disabled";
  clocks = <&clkc 39>;
  interrupt-parent = <&intc>;
  interrupts = <0 48 4>;
  reg = <0xe0005000 0x1000>;
  #address-cells = <1>;
  #size-cells = <0>;
 };
 ...

$ sudo grep i2c /var/log/syslog
 Jan 28 15:58:20 zturn2018_3 kernel: i2c /dev entries driver
 Jan 28 15:58:20 zturn2018_3 kernel: cdns-i2c e0004000.i2c: 400 kHz mmio e0004000 irq 24
 Jan 28 15:58:20 zturn2018_3 kernel: cdns-i2c e0005000.i2c: 400 kHz mmio e0005000 irq 25
$ ls -l /dev | grep i2c
 crw------- 1 root     root     89,   0  1月 28 15:58 i2c-0
 crw------- 1 root     root     89,   1  1月 28 15:58 i2c-1
$ cat /proc/device-tree/amba/i2c@e0005000/status
 okay

ちゃんと 400kHz クロックになっており、status = "okay" なのでそのまま使える。

リセットピンを制御可能にする

電気回路/zynq/Petalinux2018.3でaxi_gpio により、 /sys/class/gpio/gpio905/value 経由でリセットピンの上げ下げができるようになった。

リセットは Active Low なので、使用時には上げておく必要がある。

LANG:console
$ echo 905 > /sys/class/gpio/export
$ echo out > /sys/class/gpio/gpio905/direction
$ echo 0 > /sys/class/gpio/gpio905/value # reset
$ echo 1 > /sys/class/gpio/gpio905/value # release reset

LCD との通信

LCD のアドレスは 0x7c だそうだ。

  • int i2c = open("/dev/i2c-1", O_RDWR);
  • ioctl(i2c, I2C_SLAVE, 0x7c);
  • write(i2c, &bytes, length_of_bytes);
  • read(i2c, &buffer, length_to_receive);

の組み合わせでやりとりすればいいのかと思ったのだけれど、

https://www.kernel.org/doc/Documentation/i2c/dev-interface

を見ると、

Note that only a subset of the I2C and SMBus protocols can be achieved by the means of read() and write() calls. In particular, so-called combined transactions (mixing read and write messages in the same transaction) aren't supported. For this reason, this interface is almost never used by user-space programs.

などと書かれてた orz

LCD の制御だけが目的の場合にはデータの読み出しを必要としないので、 まあそこそこ行ける・・・かな?

試してみる。

ちょっとうまく行かないので調べ中

まずはこれだけ。

LANG:c
 int i2c = open("/dev/i2c-1",O_RDWR);
 ioctl(i2c, I2C_SLAVE, 0x7c);
 write(i2c, "\x00\x38", 2);
 close(i2c);

オシロスコープで見た信号は、

&tchart( @w_hold 5 @w_transient 1 SCL ~~~_|~~|~~|~~|~~|~~|~~|~~|~~|~~|~~~~
SDA ~~|~~~~~~~~~~~~~~~~~~~~______~~~~_|~
DATA -=S=--=1=--=1=--=1=--=1=--=1=--=0=--=0=--=0=--=1=--=0=-=P=- HEX -----==========7=X==============C=XW===XNA===------- );

のようになっていた。

例えば http://www.picfun.com/c15.html を参考に波形を読み解くと、

  • SCL = 1 における SDA negedge がスタートコンディション (S)
  • 以降、SCL = 0 でのみ SDA の遷移が許される
  • SCL posedge にてデータの読み取り
  • 1バイト目は 7bit のスレーブアドレスに R/W フラグを付けたもの
  • 今の場合は (0x7C << 1) + (write ? 0 : 1) の write モードになる
  • スレーブが自分のアドレスを受け取ると、上のチャートで NA となっているところで SDA を 0 にする(Acknowledge)のだが、実測ではここが1になっているのでスレーブが反応していない状況だ
  • SCL = 1 における SDA posedge がストップコンディション (P)
  • ストップを送るためには SDA を一旦 0 にしなければならないので、直前にマスターが 0 を出している

思った通りの信号が出ているにもかかわらずスレーブが応答しない理由は・・・まさかアドレスが間違ってる?!

もう一度 LCD のマニュアルを見直すと、

around

でかでかと Slave Address = 0x7C と書いておきながら、 サンプルコード等で実際に送っているのは 0b0111110 = 0x3e だった orz

ioctl(i2c, I2C_SLAVE, 0x3e); にしたところ、うまく行き始めた。

操作プログラム

lcd-control.c

LANG:c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/i2c-dev.h> 

#define DEV_I2C "/dev/i2c-1"
#define LCD_ADDR 0x3e

int lcd_write(const unsigned char bytes[], int length, int is_cmd)
{
  int i2c = open(DEV_I2C,O_RDWR);
  if(i2c < 0) {
    fprintf(stderr, "ERROR: unable to open I2C device \"%s\".\n", DEV_I2C);
    return i2c;
  }

  if (ioctl(i2c, I2C_SLAVE, LCD_ADDR)<0) {
    perror("ioctl(I2C_SLAVE)");
    exit(1);
  }
  
  unsigned char *buffer = (unsigned char *)malloc(length+2);
  buffer[0] = is_cmd ? 0x00 : 0x40; /* control byte */
  strcpy(buffer+1, bytes);          /* data bytes */
  write(i2c, buffer, length+1);
  free(buffer);
  
  close(i2c);
  
  return 0;
}

unsigned int my_str2num(const char str[])
{
  unsigned int value;
  char *p;
  if (strlen(str)>2 && str[0]=='0' && ((str[1]=='x')||str[1]=='X')) {
    value = strtol(str+2, &p, 16); /* hex */
  } else {
    value = strtol(str, &p, 10);   /* dec */
  }
  if (*p!=0) {
    fprintf(stderr, "ERROR: Irregal number \"%s\" was given.\n", str);
    exit(1);
  }
  return value;
}

void show_usage()
{
  fprintf(stderr, "USAGE:\n");
  fprintf(stderr, "lcd-control \"Hello!\" \n");
  fprintf(stderr, "lcd-control -c 0x39 0x14 0x78 ...\n");
  exit(1);
}

int main(int argc, const char *argv[])
{
  int i;
  unsigned char *buffer;
  if (argc < 2) show_usage();
  if (strcmp(argv[1], "-c")==0) {
    buffer = (unsigned char*)malloc(argc-2);
    for(i = 2; i < argc; i++) {
      buffer[i-2] = my_str2num(argv[i]);
    }
    lcd_write(buffer, argc-2, 1);
    free(buffer);
  } else {
    for(i = 1; i < argc; i++) {
      if (i != 1) lcd_write(" ", 1, 0);
      lcd_write(argv[i], strlen(argv[i]), 0);
    }
  }
  return 0;
}

マニュアルによれば、これを使って、

LANG:console
$ sudo lcd-control -c 0x38
$ sudo lcd-control -c 0x39
$ sudo lcd-control -c 0x14 0x78 0x5e 0x6d 0x0c 0x06 0x01
$ sudo lcd-control "Hello, world!"

で良いはずなのだけれど、実際には

  • コントラスト、というか、黒が強すぎる
  • 1行目の上半分が表示されない

の不具合があった。

コントラストについては、0x5e となっていたところを 0x5d にすれば良くなった。

1行目の上半分が表示されないのは、たまたま当たってしまった LCD の初期不良な可能性が高い。
→ LCD を指で押すと表示されない領域が増えたり減ったりする orz

ということで、以下のようにして使えるようになった。

LANG:console
$ # 初期化
$ sudo lcd-control -c 0x38
$ sudo lcd-control -c 0x39
$ sudo lcd-control -c 0x14 0x78 0x5d 0x6d 0x0c 0x06

$ # 通常の使い方
$ sudo lcd-control -c 0x01 # 画面消去&ホームポジション
$ sudo lcd-control -c 0xc0 # 2行目先頭へ移動
$ sudo lcd-control "Hello, world!"

$ # IP アドレスを表示
$ sudo lcd-control -c 0x01 # 画面消去&ホームポジション
$ sudo lcd-control -c 0xc0 # 2行目先頭へ移動
$ sudo lcd-control `ip a s eth0 | grep "inet " | sed -e "s/.*inet //" | sed -e "s/\/.*//"`

元々この LCD は Ethernet 経由でデバイスを制御する目的で IP アドレスを表示するためのものだったので、さしあたりは 1行しか表示できなくてもまあなんとかなりそう。

LAN ケーブルの抜き差しに応じて表示を変更する

/usr/local/bin/lcd-show-ip

LANG:sh
#!/bin/sh

test -e /sys/class/gpio/gpio905 || exit 1

# Deassert active-low reset
echo out > /sys/class/gpio/gpio905/direction
echo 0 > /sys/class/gpio/gpio905/value # reset
echo 1 > /sys/class/gpio/gpio905/value # release reset


lcd-control -c 0x38
lcd-control -c 0x39
lcd-control -c 0x14 0x7c 0x5d 0x6d 0x0c 0x06 0x01

lcd-control -c 0x01 # clear screen
lcd-control -c 0xc0 # move to second line

# show ip address
ADDR=`ip a s eth0 | grep "inet " | sed -e "s/.*inet //" | sed -e "s/\/.*//"`
if [ -z $ADDR ]; then
 lcd-control "NO ADDRESS"
else
   lcd-control $ADDR
fi

を用意して、

LANG:console
$ sudo chown root:root /usr/local/bin/lcd-show-ip
$ sudo ln -s /usr/local/bin/lcd-show-ip /usr/lib/networkd-dispatcher/routable.d/lcd-show-ip
$ sudo ln -s /usr/local/bin/lcd-show-ip /usr/lib/networkd-dispatcher/no-carrier.d/lcd-show-ip

としたところ、

  • LAN ケーブルを抜くと NO ADDRESS を表示
  • LAN ケーブルを挿すと dhcp で自動取得した IP アドレスを表示

を実現できた。

コメント・質問





Counter: 3272 (from 2010/06/03), today: 1, yesterday: 3