4桁7セグメントLEDをATmega328Pでダイナミック点灯するドライバ基板を作る

# はじめに
趣味の電子工作で表示器として重宝している[ダイナミック点灯用の4桁7セグメントLED](http://akizukidenshi.com/catalog/g/gI-03672/)なんですが、実装はそこそこ面倒です。というのも、ダイナミック点灯[^1]のおかげで省配線になっているとはいえ、桁制御用4本＋LED点灯用8本の計12本の配線と、LEDへの電流調整のための抵抗8つに加えて、シフトレジスタやマイコンでのダイナミック点灯制御の仕組みを実装する必要があります。
というわけで、[Arduino](https://www.arduino.cc/)のスケッチを書き込んだ[ATmega328P](https://www.microchip.com/wwwproducts/en/ATmega328p)を使って、4桁7セグメントLEDをダイナミック点灯制御しつつ、外部のマイコンなどからI2Cで表示内容を設定できるようにするドライバ基板を作ってみました。

# 実装
今回は、ATmega328Pを3.3V,8MHzで単体駆動させます[^2]。電源とGNDをつなぐだけで、マイコン単体でもArduinoのスケッチが動いてしまうのはとても便利。これに、4桁7セグメントLEDをつないで、Arduinoのスケッチを書き込みます。

## ハードウェア
ArduinoではATmega328Pの14本のピンをデジタルIOとして使えますが、そのうち12本を4桁7セグメントLEDの制御に使います。また、外部のマイコンなどと表示内容をやり取りするために、I2Cのポート(SDA,SCLの2本)も使います。回路図は以下の通り。
![img_schematic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336603/276c85e3-78a1-2187-37f9-cae89c172755.png)

今回は、この回路を、中国深センに工場を持つプリント基板製造業者[PCBWay](https://www.pcbway.com/)に発注してプリント基板化してみました。[Autodesk EAGLE](https://www.autodesk.co.jp/products/eagle/overview)で出力したGerberデータをWEBでアップロードすると、その場で見積が完了、Paypal決済で即発注できます[^3]。10枚作って5ドル（＋送料）、納期は1週間から2週間程度、ということで、趣味の工作でも手が出しやすいです。
![送った設計データ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336603/9627bc40-94a2-2c6a-c53a-4a6abe8bb645.png)
![納品された基板](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336603/48c28fe0-7e78-30b3-3424-754fd02f38bf.jpeg)
![部品を載せた基板](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336603/397c9540-92fc-72b7-e2e1-7126ace0b43e.jpeg)
※マイコンや抵抗などの部品と、4桁7セグメントLEDを基板の表裏に実装してコンパクトにまとめてみました。

## ソフトウェア
ATmega328Pには、マイコンのタイマーを使って5ミリ秒間隔で1桁ずつ点灯するダイナミック点灯処理を実装します。また、I2Cのスレーブとして、マスターから各桁の表示情報（各桁の表示データを1バイトの数値データにした計4バイトのデータ）を書き込んでもらう構成とします。Arduino IDEでビルド可能なコードは以下。

```c:7segdriver.ino
#include <MsTimer2.h>
#include <Wire.h>

static int pin_leds[] = {
  2,
  3,
  4,
  5,
  6,
  7,
  8,
  9,
};
static int pin_digits[] = {
  10,
  11,
  12,
  13
};

int  idx;
volatile byte data[4];

void setup() {
  for (int i=0; i<8; i++) {
    pinMode(pin_leds[i], OUTPUT);
    digitalWrite(pin_leds[i], HIGH);
  }
  for (int i=0; i<4; i++) {
    pinMode(pin_digits[i], OUTPUT);
    digitalWrite(pin_digits[i], LOW);
    data[i] = 0b00000000;
  }
  idx = 0;

  MsTimer2::set(5, flash);
  MsTimer2::start();

  for (int i=0; i<4; i++){
    data[i] = 0b11111111;
  }

  Wire.begin(8);
  Wire.onReceive(receiveEvent);
}

void loop() {
}

void flash() {
  interrupts();
  digitalWrite(pin_digits[idx], LOW);
  idx++;
  if (idx > 3) {
    idx = 0;
  }
  for (int i=0; i<8; i++) {
    if (((0b10000000 >> i) & data[idx]) > 0) {
      digitalWrite(pin_leds[i], LOW);
    } else {
      digitalWrite(pin_leds[i], HIGH);
    }
  }
  digitalWrite(pin_digits[idx], HIGH);
}

void receiveEvent(int numBytes) {
  if (numBytes == 4 && Wire.available() == 4) {
    for (int i=0; i<4; i++){
      data[i] = Wire.read();
    }
  }
}
```
このドライバ基板を使うマスター側もArduinoで作るのであれば、スケッチは以下のような感じになります。カウントアップする整数を表示するだけのサンプルです。

```c:main.ino
#include <Wire.h>

byte pattern[12] = {
  0b11111100, // 0
  0b01100000, // 1
  0b11011010, // 2
  0b11110010, // 3
  0b01100110, // 4
  0b10110110, // 5
  0b10111110, // 6
  0b11100000, // 7
  0b11111110, // 8
  0b11110110, // 9
  0b00000000, // (none) [10]
  0b00000010  // - [11]
};

int num = 0;

void setup()
{
  Wire.begin();
}

void loop()
{
  dispInt(num++);
  delay(100);
}

void dispInt(int value)
{
  if (value >= 0 && value < 10000)
  {
    byte data[4];
    char buf[4];
    sprintf(buf, "%4d", value);
    for (int i=0; i<4; i++)
    {
      if (buf[i] == ' ')
      {
        data[i] = pattern[10];
      }
      else if (buf[i] == '-')
      {
        data[i] = pattern[11];
      }
      else
      {
        data[i] = pattern[buf[i] - '0'];
      }
    }
    Wire.beginTransmission(8);
    for (int i=0; i<4; i++) {
      Wire.write(data[i]);
    }
    Wire.endTransmission();
  }
}
```

# おわりに
今回は、4桁7セグメントLEDの実装の手間を最小化するためのドライバ基板を作ってみました。基板製造業者をはじめて使ってみましたが、想像以上に簡単で安く早くできてしまったので、これからも気軽に使ってしまいそうです。
![img_sample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336603/1bbd4de3-e99e-bf4d-8b29-d50c9322dc7b.jpeg)



[^1]: すべての桁を同時に点灯せず、1桁ずつ順に点灯させる点灯方法。高速に切り替えることで見た目はすべての桁が点灯しているように見えます。（参考：[プログラミング・メモ－LEDのダイナミック点灯](https://www.elec-hobbyist.com/MicomMemo/Pgm_method_1.html)）
[^2]: Arduino UNOなどに載っているATmega328Pは5V,16MHz駆動です。3.3V,8MHz駆動にすることで、処理速度は遅くなりますが、外部クロックなどの周辺回路が必要なくなり、消費電力も大幅に減らせます。（参考：[arduinoを最小構成で自作する(atmega328/8MHz/3.3V/内部クロック) - 脳汁portal](http://portaltan.hatenablog.com/entry/2018/01/04/132617)）
[^3]: EAGLEの基本的な使い方やPCBWayへの発注ノウハウについては、WEB上に参考になる記事がたくさんあります。（参考：[EAGLEの使い方 | AkiRacing.com](http://akiracing.com/2017/05/27/eagle_tutorial/)、[Eagleのライブラリを自作する - Essence](http://essence.cambrianrobotics.com/series/Eagle%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E8%87%AA%E4%BD%9C%E3%81%99%E3%82%8B/6)、[初めてのプリント基板設計から発注まで – Watako-Lab.](http://watako-lab.com/2018/08/23/1st_board_design/)、[Technical Support - Notes for Gerber files Generated from Eagle 9.20](https://www.pcbway.com/helpcenter/technical_support/Notes_for_Gerber_files_Generated_from_Eagle_9_20.html)、など）