74HC595×2個で駆動する8桁7セグメントLEDモジュールのドライバ作成

74HC595(シフトレジスタ)×2個でダイナミック駆動する8桁の7セグメントLED用のドライバを作成しました。
開発環境：VSCode + PlatformIO, ESP32-WROOM-32D
対象モジュール：下記画像のものです。
![8digit_led.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/919305/e5ebfde0-531a-4856-8b9c-7df352ac75b3.jpeg)

・数字、アルファベット、一部の記号の表示に対応しています。
・数字の直後のドットは数字のdpになります。
・今後、文字単位でのブリンク機能、表示全体のスクロール機能を追加するつもりです。

```C++:hc595led.h
//////////////////////////////////////////////////////////////////////////////
// a driver for 8-digit 7-segment LED driven dynamically by two 74HC595's.
// optimized for arduino ESP32-WROOM-32D (PlatformIO).
//////////////////////////////////////////////////////////////////////////////

#ifndef __H_HC595LED_H__
#define __H_HC595LED_H__

#include <Arduino.h>
#include <cstdio>

static volatile char _cRam[17];
static int _pinClk;     // SCK
static int _pinDat;     // DIO
static int _pinLatch;   // RCK
                        // GND
                        // 3.3V - 5V

// first byte (LSB first, inverted)
const unsigned int _getChar[] = {
  //         abcdefgp
  /*  0 */ 0b00000011, // 0
  /*  1 */ 0b10011111, // 1
  /*  2 */ 0b00100101, // 2
  /*  3 */ 0b00001101, // 3
  /*  4 */ 0b10011001, // 4
  /*  5 */ 0b01001001, // 5
  /*  6 */ 0b01000001, // 6
  /*  7 */ 0b00011011, // 7
  /*  8 */ 0b00000001, // 8
  /*  9 */ 0b00001001, // 9
  /* 10 */ 0b11111111, // blank
  /* 11 */ 0b11101101, // colon
  /* 12 */ 0b11111110, // .
  /* 13 */ 0b00010001, // A
  /* 14 */ 0b11000001, // B
  /* 15 */ 0b01100011, // C
  /* 16 */ 0b10000101, // D
  /* 17 */ 0b01100001, // E
  /* 18 */ 0b01110001, // F
  /* 19 */ 0b01000011, // G
  /* 20 */ 0b10010001, // H
  /* 21 */ 0b11011111, // I
  /* 22 */ 0b10000111, // J
  /* 23 */ 0b01010001, // K
  /* 24 */ 0b11100011, // L
  /* 25 */ 0b00010011, // M
  /* 26 */ 0b11010101, // N
  /* 27 */ 0b11000101, // O
  /* 28 */ 0b00110001, // P
  /* 29 */ 0b00100001, // Q
  /* 30 */ 0b11110101, // R
  /* 31 */ 0b11001001, // S
  /* 32 */ 0b11100001, // T
  /* 33 */ 0b11000111, // U
  /* 34 */ 0b10000011, // V
  /* 35 */ 0b10000001, // W
  /* 36 */ 0b10010001, // X
  /* 37 */ 0b10001001, // Y
  /* 38 */ 0b00100111, // Z
  /* 39 */ 0b11111101, // -
  /* 40 */ 0b00000010, // 0.
  /* 41 */ 0b10011110, // 1.
  /* 42 */ 0b00100100, // 2.
  /* 43 */ 0b00001100, // 3.
  /* 44 */ 0b10011000, // 4.
  /* 45 */ 0b01001000, // 5.
  /* 46 */ 0b01000000, // 6.
  /* 47 */ 0b00011010, // 7.
  /* 48 */ 0b00000000, // 8.
  /* 49 */ 0b00001000, // 9.
  // you can define character patterns here as you like.

};

// character map
const unsigned char _charMap[256] = {
//       0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
/* 0 */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
/* 1 */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
/* 2 */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 39, 12, 10, 
/* 3 */  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 11, 10, 10, 10, 10, 10,
/* 4 */ 10, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 
/* 5 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 10, 10, 10, 10, 10, 
/* 6 */ 10, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 
/* 7 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 10, 10, 10, 10, 10, 
/* 8 */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
/* 9 */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
/* a */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
/* b */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
/* c */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
/* d */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
/* e */ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
/* f */ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 10, 10, 10, 10, 10, 10, 
}; 

// second byte (LSB first)
const unsigned int _getDigit[8] = {
  0b00010000,
  0b00100000,
  0b01000000,
  0b10000000,
  0b00000001,
  0b00000010,
  0b00000100,
  0b00001000,
};

class HC595LED {
  private:
  static void task7segDrv(void *param) {
    for(;;) {
      for(int i = 0; i < 8; i++) {
        shiftOut(_pinDat, _pinClk, LSBFIRST, _cRam[i]);
        digitalWrite(_pinLatch, LOW);
        shiftOut(_pinDat, _pinClk, LSBFIRST, _getDigit[i]);
        digitalWrite(_pinLatch, HIGH);
        delay(1);
      }
    }
  }

  public:
  HC595LED(int pinClk, int pinDat, int pinLatch) {
    memset((void *)_cRam, 0, sizeof(_cRam));

    _pinClk = pinClk;
    _pinDat = pinDat;
    _pinLatch = pinLatch;
    pinMode(_pinClk, OUTPUT);
    pinMode(_pinDat, OUTPUT);
    pinMode(_pinLatch, OUTPUT);

    for(int i = 0; i < 16; i++) {
      digitalWrite(_pinLatch, LOW);
      shiftOut(_pinDat, _pinClk, LSBFIRST, 0b11111111);
      digitalWrite(_pinLatch, HIGH);
    }
    xTaskCreateUniversal(task7segDrv, "task7segDrv", 1024, NULL, 1, NULL, 1);
  }

  int setToLED(char *str) {
    int len;
    int i;
    char buf[20] = {0};

    strcpy(buf, str);
    len = strlen(buf);

    // join dot to a previous number,
    // and shorten the length of string by the number of dots.
    for(i = 1; i < len; i++) {
        if(buf[i] == '.' && buf[i - 1] >= '0' && buf[i - 1] <= '9') {
            buf[i - 1] += 0xc0;
            for(int j = 0; j < len - i; j++) {
                buf[i + j] = buf[i + j + 1];
            }
            len--;
        }
    }

    // set character patterns to C-RAM.
    for(i = 0; i < len; i++) {
      if(buf[i] == 0) {
        break;
      }
      _cRam[len - 1 - i] = _getChar[_charMap[buf[i]]];
    }
    _cRam[i] = 0;

    return 0;
  }
};

#endif /* __H_HC595LED_H__ */

```

動作確認用サンプルアプリケーションも掲載しておきます。

```C++:main.cpp
/////////////////////////////////////////////////////////////
// all you have to do is to include the header file below!
/////////////////////////////////////////////////////////////
#include "hc595led.h"

//#include <SPI.h>
#include <Arduino.h>
#include <FreeRTOS.h>
#include <WiFi.h>
//#include <WebServer.h>

void ntpTest(const char *srv) {
  configTime(9 * 3600, 0, srv);
  struct tm timeInfo;
  getLocalTime(&timeInfo);
  Serial.printf(" %04d/%02d/%02d %02d:%02d:%02d\n",
          timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
          timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
}

// declare the global instance. 
HC595LED led = HC595LED(17,   // SCK
                        21,   // DIO
                        22);  // RCK

void setup(void) {
  Serial.begin(115200);

  WiFi.begin("rs500m-14qaf8-1", "6a6a227fc3f17");
  while(WiFi.status() != WL_CONNECTED) {
    delay(250);
  }
  Serial.print("IP Addr. : ");
  Serial.println(WiFi.localIP());

  ntpTest("time.google.com");

  Serial.println("set-up done.");
}

void loop() {
  static char buf[20] = {0};
  struct tm timeInfo;

  getLocalTime(&timeInfo);
  sprintf(buf, "Jp%02d.%02d.%02d", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
  led.setToLED(buf);

  // once a day at 3:59:59,
  if(timeInfo.tm_hour == 3 && timeInfo.tm_min == 59 && timeInfo.tm_sec == 59) {
      // adjust time
      led.setToLED("-adjust-");
      ntpTest("time.google.com");
      delay(1000);
  } else {
    delay(500);
  }  
}
```