ラズパイとLCDディスプレイ(AQM0802)で遊んでみた

目次

LCDディスプレイ(AQM0802)をラズパイに繋げて遊んでみた

秋月通商でLCDディスプレイを購入したので、ラズパイに繋いでみた

https://akizukidenshi.com/catalog/g/gK-11354/

参考書籍をもとにつなげてスレーブアドレスを確認すると3eだった。

$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 3e -- 
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --  

https://akizukidenshi.com/catalog/g/gK-11354/

秋月通商のサイトからデータシートを確認。初期設定例の通りにいくかテスト

# 初期設定
$ i2cset -y 1 0x3e 0x00 0x38
$ i2cset -y 1 0x3e 0x00 0x39
$ i2cset -y 1 0x3e 0x00 0x14
$ i2cset -y 1 0x3e 0x00 0x70
$ i2cset -y 1 0x3e 0x00 0x56
$ i2cset -y 1 0x3e 0x00 0x6c
$ i2cset -y 1 0x3e 0x00 0x38
$ i2cset -y 1 0x3e 0x00 0x0c
$ i2cset -y 1 0x3e 0x00 0x01

# `a`という文字を表示
$ i2cset -y 1 0x3e 0x40 0x61

うまくいったので、ts-nodeで書いてみた

import * as i2c from "i2c-bus";
const ADDRESS_LCD = 0x3e;

const writeCommand = (i2c1, command: number) => {
  i2c1.writeByte(ADDRESS_LCD, 0x00, command);
};

const writeData = (i2c1, data: number) => {
  i2c1.writeByte(ADDRESS_LCD, 0x40, data);
};

const wait = (ms: number) => {
  return new Promise((resolve, reject) => setTimeout(resolve, ms));
};

const init = async (i2c1) => {
  writeCommand(i2c1, 0x38);
  await wait(10);
  writeCommand(i2c1, 0x39);
  await wait(10);
  writeCommand(i2c1, 0x14);
  await wait(10);
  writeCommand(i2c1, 0x70);
  await wait(10);
  writeCommand(i2c1, 0x56);
  await wait(10);
  writeCommand(i2c1, 0x6c);
  await wait(10);
  writeCommand(i2c1, 0x38);
  await wait(10);
  writeCommand(i2c1, 0x0c);
  await wait(10);
  writeCommand(i2c1, 0x01);
  await wait(10);

  writeData(i2c1, 0x61);
};

これもうまくいった。

LCDの操作がわかったので最寄駅の電車が次にいつ出発するかLCDに表示することにした。家から駅が遠く、駅には普通電車しか止まらないので、今まで駅で長く足止めを喰らうことが多かったのでそれを防ぐためにやってみた。

次の電車といっても、平日ダイヤと休日ダイヤおよび大阪方面と神戸方面それぞれでダイヤがあるため、合計4種類のダイヤを表示する必要がある。2行あるLCDディスプレイのうち、1行に1種類のダイヤを表示して、2秒ごとに表示を切り替えれるようにした。

本当は現在時刻によって休日ダイヤと平日ダイヤを切り替えたかっけど祝日対応が面倒だったので諦めた。

import * as i2c from "i2c-bus";

const ADDRESS_LCD = 0x3e;

const NEW_LINE = 0xc0;
const CLEAR = 0x01;
const SPACE = 0x20;

/**
 * 猶予時間(分)
 * 出発時刻までの時間がこの時間以下になると次の電車に切り替える
 */
const BUFFER_MINUTE = 4;

const wait = (ms: number) => {
  return new Promise((resolve, reject) => setTimeout(resolve, ms));
};

const writeCommand = (i2c1, command: number) => {
  i2c1.writeByte(ADDRESS_LCD, 0x00, command);
};
const writeData = (i2c1, data: number) => {
  i2c1.writeByte(ADDRESS_LCD, 0x40, data);
};

const readFile = (path: string): Promise<string> => {
  return new Promise((resolve, reject) => {
    const fs = require("fs");

    fs.readFile(path, "utf8", (err, data) => {
      if (err) {
        console.error(err);
        reject(err);
      }
      resolve(data);
    });
  });
};

const init = async (i2c1) => {
  writeCommand(i2c1, 0x38);
  await wait(10);
  writeCommand(i2c1, 0x39);
  await wait(10);
  writeCommand(i2c1, 0x14);
  await wait(10);
  writeCommand(i2c1, 0x70);
  await wait(10);
  writeCommand(i2c1, 0x56);
  await wait(10);
  writeCommand(i2c1, 0x6c);
  await wait(10);
  writeCommand(i2c1, 0x38);
  await wait(10);
  writeCommand(i2c1, 0x0c);
  await wait(10);
  writeCommand(i2c1, 0x01);
  await wait(10);
};

const getTimeTableList = async (path: string) => {
  const timeTable = await readFile(path);
  const timeTableList = timeTable.split("\n");
  timeTableList.sort();
  return timeTableList;
};

const addMinutes = (date, minutes): Date => {
  return new Date(date.getTime() + minutes * 60000);
};

(async () => {
  const i2c1 = await i2c.openPromisified(1);

  await init(i2c1);

  await wait(100);
  writeCommand(i2c1, CLEAR);

  while (true) {
    const targetDate = addMinutes(new Date(), BUFFER_MINUTE);
    const targetHour = ("0" + String(targetDate.getHours())).slice(-2);
    const targetMinute = ("0" + String(targetDate.getMinutes())).slice(-2);
    const targetTime = `${targetHour}:${targetMinute}`;

    const osakaWeekendDiffMinute = await getDiffMinute(
      targetTime,
      "./timetable_hankyu_weekend_osaka.txt"
    );
    const kobeWeekendDiffMinute = await getDiffMinute(
      targetTime,
      "./timetable_hankyu_weekend_kobe.txt"
    );
    const osakaWeekdayDiffMinute = await getDiffMinute(
      targetTime,
      "./timetable_hankyu_weekday_osaka.txt"
    );
    const kobeWeekdayDiffMinute = await getDiffMinute(
      targetTime,
      "./timetable_hankyu_weekday_kobe.txt"
    );

    writeTimeTable(i2c1, 0xb5, 0xb7, osakaWeekendDiffMinute);
    writeCommand(i2c1, NEW_LINE);
    writeTimeTable(i2c1, 0xba, 0xb7, kobeWeekendDiffMinute);

    await wait(2000);
    writeCommand(i2c1, CLEAR);
    await wait(100);

    writeTimeTable(i2c1, 0xb5, 0xcd, osakaWeekdayDiffMinute);
    writeCommand(i2c1, NEW_LINE);
    writeTimeTable(i2c1, 0xba, 0xcd, kobeWeekdayDiffMinute);

    await wait(2000);
    writeCommand(i2c1, CLEAR);
    await wait(100);
    
  }
})();

const getDiffMinute = async (targetTime: string, path: string) => {
  const timeTableList = await getTimeTableList(path);
  const nextTimeTable = calcNextTimeTable(targetTime, timeTableList);
  const diffMinute = calcDiffMinute(nextTimeTable);
  return diffMinute;
};

/**
 * 時刻を書き込む
 * 現在の行のみに書き込む
 * @param i2c1 
 * @param headChar1 
 * @param headChar2 
 * @param diffMinute 
 */
const writeTimeTable = (
  i2c1,
  headChar1: number,
  headChar2: number,
  diffMinute: number
) => {
  writeData(i2c1, headChar1);
  writeData(i2c1, headChar2);
  writeData(i2c1, SPACE);

  writeData(i2c1, 0xb1);
  writeData(i2c1, 0xc4);
  writeData(i2c1, diffMinute >= 10 ? 0x30 + Math.floor(diffMinute / 10) : SPACE);
  writeData(i2c1, 0x30 + (diffMinute % 10));
  writeData(i2c1, 0x6d);

};

const calcNextTimeTable = (
  targetTime: string,
  timeTableList: string[]
): string => {
  // 次の時刻を算出
  const filtered = timeTableList.filter((t) => t > targetTime);
  return filtered.length !== 0 ? filtered[0] : timeTableList[0];
};

const calcDiffMinute = (time: string) => {
  const y = new Date().getFullYear();
  const m = new Date().getMonth();
  const d = new Date().getDate();
  const nextTimeTableDate = new Date(
    y,
    m,
    d,
    Number(time.split(":")[0]),
    Number(time.split(":")[1])
  );

  return Math.ceil(
    (nextTimeTableDate.getTime() - new Date().getTime()) / 1000 / 60
  );
};

無事に表示できました👏

ハマったこと

カタカナからhexに変換してかんたんにカタカナを表示できるようにしたかったがうまくいかずに諦めた

表示文字を柔軟に変更できるよう、 writeData('イロハニホヘト')のようなメソッドを用意したく、カタカナからhexへの変換を試みたがうまくいかなかった

具体的には、のUnicode値が12450, が12452なので、 0xb1 + (code - 12450)/2とすれば、LCDのコードに対応すると考えたが、のUnicode値が12501になってて混乱した

// 実装してたコード
const length = characters.length
const codeList = []
for (let index = 0; index < characters.length; index++) {
    const code = characters.charCodeAt(index)
    const distance = code - 12450
    const distanceForCode = distance /2
    codeList.push(0xb1 + distanceForCode)
}

今回は表示する文字が固定なので、諦めて地道にデータシートを見てhexを打ち込んだ 😭

連続してデータを送信すると文字の表示位置がずれる

連続してデータを送信すると、文字の表示位置が一文字分後ろにずれてしまった。

    writeTimeTable(i2c1, 0xb5, 0xb7, osakaWeekendDiffMinute);
    writeCommand(i2c1, NEW_LINE);
    writeTimeTable(i2c1, 0xba, 0xb7, kobeWeekendDiffMinute);

    await wait(2000);
    writeCommand(i2c1, CLEAR);
+    await wait(100);

    writeTimeTable(i2c1, 0xb5, 0xcd, osakaWeekdayDiffMinute);
    writeCommand(i2c1, NEW_LINE);
    writeTimeTable(i2c1, 0xba, 0xcd, kobeWeekdayDiffMinute);

    await wait(2000);
    writeCommand(i2c1, CLEAR);
+    await wait(100);

表示文字を消去した後には適切にwaitする必要があるらしい。多分100m秒もいらないけど短くすると表示が切り替わっている感じがしなかったためこれぐらいがちょうどよかった

所感

  • Macでコード書いてgitにpullして、ラズパイでgitpullしてデバッグしていたので思ったより時間かかってしまった
  • js/tsでhex扱いにくい
  • 文字が小さすぎてディスプレイに近付かないと見えなかった😭
/img/2021-02-11.JPG

ソファから見た場合。マサイ族でも見えん

参考にしたサイト


See also