WiFi로 제어하는 탁상시계 만들기 #2 7세그먼트 LED 디스플레이

2019. 1. 10. 11:41

Project/Turtle Clock

Turtle Clock BIGFONT에 사용할 디스플레이 장치를 소개합니다.

이번에 사용할 디스플레이 장치는 일반적으로 시간 표현에 많이 쓰이는 7 세그먼트 LED 모듈입니다. Adafruit 사에서 구매하였는데, 글자 크기가 1.2인치나 되어서, 집에서 쓸 시계를 만들 용도로 미리 구매해 둔 것입니다. 아이와 함께 자는 안방에서 사용할 시계이고 자리에 누워서 자주 확인해야 되기에 좀 더 큰 글자라서 좋을 듯 했습니다. 또, 많이 판매하는 LCD 시계들과는 다르게 어둠속에서 따로 백라이트(back light)를 켜지 않아도 되는 장점이 있습니다. 이번 포스트에서는 이 디스플레이에 대해 소개하고 NodeMCU 보드와 연결하여 테스트를 수행하겠습니다.

Adafruit 1.2“ 4-Digit 7-Segment Display with I2C Backpack - Green

제품명은 위와 같습니다. 사진처럼 숫자 4자리를 표현할 수 있고 색상은 Green이며 이 외에 Yellow, Red 색상도 판매되고 있습니다. 각 숫자 당 7개의 LED로 표현하는데 이 때문에 7 세그먼트 LED라고 합니다.

별도의 박스 없이 위와 같이 배송되었습니다. 제가 구입한 제품은 LED 모듈과 이를 구동할 수 있는 백팩(Backpack) 즉 브레이크 아웃(Break out) 보드가 함께 있는 세트 상품이고 Adafruit 홈페이지에서 각각 별도로 구입할 수도 있습니다.

7 세그먼트 LED 모듈입니다. 숫자 4 자리와 5개의 도트 포인트가 있습니다. 전용 보드를 사용할 것이기 때문에 자세한 스펙을 알 필요는 없고 글자 세로 길이가 약 30mm, 폭은 17.6mm 정도입니다. 전체 폭은 120mm이고 더 자세한 정보를 원하시면 아래 링크를 참고하시기 바랍니다.

전용 백팩입니다. 폭은 LED 모듈과 동일하고 위 아래로 약간 더 튀어 나옵니다. LED 모듈을 삽입하여 납땜하거나 핀 소켓 등을 이용해 탈부착이 가능하도록 할 수도 있습니다. 각 모서리의 구멍은 생각보다 좁네요. M3 사이즈 볼트는 들어가지 않습니다. 역시 자세한 정보는 아래 링크를 참고하세요.

드라이버는 HT16K33 칩을 사용하였고 Adafruit사 제품 홈페이지에서 자세한 사용법 및 라이브러리를 제공합니다. 아두이노 같은 개발 보드와 I2C 방식으로 연결하기 때문에, 제어를 위해 두 핀(SCL, SDA)만 사용하면 됩니다. I2C 주소는 0x70 ~ 0x77중에 선택할 수 있고 기본은 0x70이며 보드 상의 A0, A1, A2 점퍼를 납땜하여 구성합니다.

I2C 관련 핀을 제외한 나머지 세 개의 핀은 전원 공급용입니다. 이 모듈은 3.3V와 5V 프로세서와 모두 연결할 수 있습니다. “V_IO” 핀은 프로세서 보드와 동일하게 맞춰 주시면 되고, 모듈의 전력 소모가 좀 있는 편이라 “+5V” 핀은 표시된 대로 5V 전원을 공급해야 합니다. 따라서 5V 개발 보드와 연결할 때는 상관이 없지만, 3.3V 보드와 연결할 때는 5V 전원까지 따로 준비해야 합니다. 이 프로젝트에서 사용할 NodeMCU 보드도 3.3V로 구동되므로 여기에 해당합니다. 백팩에 대한 자세한 사용법은 아래 링크를 참고하시기 바랍니다.

백팩에 LED 모듈과 핀 소켓을 납땜하였습니다. 다른 I2C 장치가 없기 때문에 주소는 그냥 기본 값을 사용하겠습니다.

LED 모듈과 Backpack을 위한 아두이노 라이브러리 설치

Adafruit의 해당 Tutorial에서는 아두이노 IDE에서 펌웨어를 작성하기 위해 두 개의 라이브러리를 설치하라고 설명하고 있습니다.

첫 번째 라이브러리는 백팩을 구동하기 위한 것으로 위 링크에서 다운 받아 설치합니다.

우선 링크를 따라 github의 해당 페이지로 이동한 후, 초록색의 “Clone or download” 버튼(오른쪽 중간 화살표가 가리키는 곳)을 클릭합니다.

“Clone or download” 버튼을 클릭하면 나오는 팝업 윈도우입니다. 여기서 “Download ZIP” 버튼을 클릭하여 전체 파일을 ZIP 압축 파일로 받습니다. 이렇게 하면 라이브러리 파일과 예제 파일까지 같이 설치가 됩니다. 또 ZIP 파일로 받게 되면 아두이노 IDE에서 메뉴를 통해 쉽게 설치할 수 있어 편리합니다.

이제 아두이노 IDE를 실행하고 위와 같이 “Sketch”-“Include Library”-“Add .ZIP Library...”를 실행한 후 나타나는 대화상자에서 받아 놓은 ZIP 파일을 선택해 주면 자동으로 설치를 하고 완료 메시지를 표시합니다.

이 라이브러리는 Adafruit의 HT16K33 드라이버를 사용하는 백팩을 위한 라이브러리로 7 세그먼트뿐만 아니라 도트 매트릭스 등 여러 가지 LED 디스플레이용 백팩을 모두 포함하고 있습니다.

두 번째 라이브러리는 Adafruit GFX Library입니다. 역시 아래 github 링크를 통해 같은 방식으로 다운 받아 설치합니다. GFX 라이브러리는 Adafruit사의 여러 디스플레이 장치에서 선, 사각형 등의 그래픽 요소들을 출력하기 위한 함수들의 라이브러리입니다. 세그먼트 백팩은 이 라이브러리가 필요 없을 듯한데, LED 백팩용 라이브러리에서 GFX 라이브러리를 포함(#include)하고 있기 때문에 설치하지 않으면 컴파일 할 때 에러가 납니다.

제 컴퓨터의 경우, 사용자가 설치한 라이브러리는 “내 문서” 폴더에 설치됩니다. 위와 같이 따라가 보면 설치된 두 개의 라이브러리를 확인할 수 있습니다.

NodeMCU 보드에 LED 디스플레이 연결하기

아두이노 Uno 보드의 경우 I2C 포트는 A4(SDA), A5(SCL) 핀으로 정해져 있지만, NodeMCU 보드는 모든 GPIO 핀을 I2C 포트로 사용할 수 있습니다. 여기서는 보드와 LED 장치를 연결하기 위해 D1(SDA), D2(SCL) 핀을 사용하였고, 미리 정해진 값이 아니기 때문에 소스 상에서 반드시 코딩을 해줘야 합니다.

이미 언급했듯이 V_IO 핀은 보드와 동일한 전원을 연결해야 합니다. NodeMCU는 3.3V이므로 여기에 맞게 보드의 3.3V 출력 핀에 V_IO 핀을 연결하였습니다.

NodeMCU는 두 가지 방법으로 전원을 공급할 수 있습니다. 하나는 USB 포트를 통해 전원을 입력 받는 방법인데, 이 때 Vin 핀에도 동일한 전원이 공급됩니다. 다른 하나는 Vin 핀을 통해서 입력 받는 것입니다. 5V ~ 12V 사이가 허용되는데, 보드 뒷면에 보면 +5V를 추천하고 있습니다. 그리고 Vin 포트로 공급된 전원은 USB 포트로 출력되지는 않습니다.

테스트하는 동안은 USB 포트를 통해서 전원을 공급하기 때문에, Vin, GND 핀을 LED 백팩 전원 공급용으로 사용하였습니다. LED 모듈의 전력 소모가 조금 있는 편이라서 실제로 조립할 때는 안정적인 동작을 위해서 별도의 전원을 연결할 예정입니다. 이렇게 LED를 위한 다섯 개의 핀을 모두 연결하였습니다.

첫 번째 예제 : HT16K33 드라이버 테스트

첫 번째 예제는 LED 백팩에서 사용하는 HT16K33 드라이버에 대한 테스트입니다. 아두이노를 실행하여 “File” - “Examples” 메뉴에서 위와 같이 “HT16K33” 예제를 선택합니다.

void setup() {
  Serial.begin(9600);
  Serial.println("HT16K33 test");
  Wire.begin(D1, D2); /* join i2c bus with SDA=D1 and SCL=D2 of NodeMCU */
  matrix.begin(0x70);  // pass in the address
}

만약, 아두이노 UNO 등과 같은 레퍼런스 보드였다면 소스 수정 없이 바로 실행할 수 있지만, NodeMCU 보드를 사용하기 위해선 어떤 핀을 I2C를 위해 사용할 것인지 즉, 어떤 핀에 LED 백팩이 연결되었는지 코딩해야 합니다. 그래서 위와 같이 setup() 함수 내에 코드 한 줄을 추가하였습니다.

업로드하고 실행한 모습입니다. 모듈 상의 모든 LED를 순회하며 On/Off 합니다. Adafruit사의 경우 7 세그먼트 LED 말고도 도트 매트릭스 등 여러 제품에 HT16K33 드라이버를 사용하고 있습니다.

두 번째 예제 : 7 세그먼트 LED 테스트

두 번째 예제는 숫자를 이용한 몇 가지 패턴을 출력하는 예제입니다. 위와 같이 “sevenseg” 예제를 실행합니다.

void setup() {
#ifndef __AVR_ATtiny85__
  Serial.begin(9600);
  Serial.println("7 Segment Backpack Test");
#endif
  Wire.begin(D1, D2); /* join i2c bus with SDA=D1 and SCL=D2 of NodeMCU */
  matrix.begin(0x70);
}

첫 번째 예제와 마찬가지로 다른 수정 없이 I2C 관련 핀 설정을 setup() 함수에 추가한 후 업로드 합니다.

결과 화면입니다. 출력 내용중 0부터 10000까지 카운팅하는 부분에 해당합니다.

라이브러리를 이용하여 예제 작성하기

이제 Adafruit LED Backpack Library에서 제공하는 함수를 소개하겠습니다. 라이브러리에서 제공하는 기능이 워낙 많기 때문에, 여기에서 모든 함수에 대해 설명하진 않겠습니다. 시간 표현에 필요한 부분만 언급할 것이며, 전체적인 부분에 대한 자세한 설명은 다른 포스트에서 따로 다루도록 하겠습니다.

Adafruit LED Backpack Library의 함수들은 LED 디스플레이에 직접 데이터를 출력하지 않고 먼저 드라이버 내의 버퍼 메모리에 데이터를 저장한 다음 처리합니다. 그래서 버퍼에 기록하는 함수들과 버퍼의 내용을 LED에 출력하는 함수들로 나눌 수 있습니다.

위 그림의 가운데가 버퍼 메모리입니다. 그리고 왼쪽은 버퍼에 저장하는 함수들, 오른쪽은 LED에 출력하는 함수들입니다. 이 함수들을 이용하여 소스 코드를 작성할 때, 버퍼 저장 함수들로 출력할 데이터를 준비한 후 버퍼 출력 함수로 LED에 출력하는 순서로 구성해야 합니다.

1. print(인수1, 인수2) 함수와 writeDisplay( ) 함수

우선 두 함수를 이용해 출력해 보겠습니다. print( ) 함수는 버퍼에 데이터를 전송하는 함수인데, 세그먼트 네 자리 값을 한 번에 저장합니다. 두 개의 인수가 필요한데 인수1은 저장할 데이터이고 인수2는 데이터의 종류를 지정합니다. 인수2에 따라 10진 또는 16진 등 여러 가지 데이터를 선택할 수 있지만, 시간은 십진수로만 표현하기 때문에 아래와 같은 구문만 알면 됩니다.

led.print(1234, DEC);

처음 나오는 “led”는 백팩의 object입니다. 두 번째 인수 “DEC”는 전송할 데이터가 십진수임을 알려주고 첫 번째 인수 “1234”는 LED 세그먼트 네 자리에 해당하는 데이터입니다. 7 세그먼트 LED는 그 이름처럼, 일곱 개의 LED로 한 자리를 표현합니다. 특정 LED를 on/off 하는 것으로 숫자나 문자의 모양을 만드는데 print() 함수를 이용하면 주어진 인수에 따라 알아서 모양을 만들어 줍니다. 만약, 숫자나 문자가 아닌 다른 모양을 출력하고자 한다면 다른 함수를 사용해야 합니다.

led.print(1234, DEC);
led.writeDisplay();

이미 언급했듯이 print() 함수는 버퍼에 저장만 하고, writeDisplay() 함수를 이용하여 디스플레이에 실제로 출력합니다. 이제 두 함수를 테스트하는 예제를 작성해 보겠습니다.

#include <Wire.h>
#include <Adafruit_LEDBackpack.h>

우선 백팩용 라이브러리를 이용하기 위해 두 개의 헤더 파일을 포함해야 합니다. Wire.h는 I2C 통신을 이용하기 위해 필요한 헤더 파일입니다. LED 백팩이 I2C 방식으로 연결되기 때문에 포함시켜야 하는데, Adafruit_LEDBackpack.h 헤더 파일에서 이미 포함하고 있기 때문에 생략해도 됩니다.

Adafruit_7segment led = Adafruit_7segment();

“led”라는 이름으로 Adafruit 7segment를 위한 오브젝트를 생성하였습니다. 이 오브젝트를 통하여 라이브러리 함수 및 변수들에 접근하게 됩니다.

void setup() {
  Wire.begin(D1, D2);
  led.begin(0x70);
}

setup() 함수는 위와 같습니다. 두 라인 모두 I2C 통신을 위한 코드인데, 첫 번째 라인은 앞선 두 개의 예제에서 이미 언급했듯이 어떤 핀을 I2C를 위해 사용할 것인지 알려주는 것이고, 두 번째 줄은 이 백팩의 주소를 지정하는 것입니다. 주소를 바꾸지 않았게 때문에 기본 주소인 0x70을 인수로 전달했습니다.

void loop() {
  led.print(1234, DEC);
  led.writeDisplay();
}

loop() 함수도 간단합니다. 위에서 설명한 코드 두 줄을 그대로 기술하였습니다.

#include <Adafruit_LEDBackpack.h>
//
Adafruit_7segment led = Adafruit_7segment();
//
void setup() {
  Wire.begin(D1, D2);
  led.begin(0x70);
}
//
void loop() {
  led.print(1234, DEC);
  led.writeDisplay();
}

전체 소스와 결과 화면입니다.

led.print(123, DEC);
led.writeDisplay();

print() 함수는 첫 번째 인수로 받은 값을 LED의 오른쪽부터 채워서 출력합니다. “1234”의 경우 네 자리 모두 출력되지만 위와 같이 “123”이면 왼쪽 첫 자리는 비워두고 오른쪽에 붙여서 출력합니다.

2. writeDigitNum(위치, 값, 도트 포인트) 함수

writeDigitNum() 함수는 print() 함수와는 다르게, 한 자리씩 버퍼에 저장하는 함수입니다. 첫 번째 인수는 네 자리 중 어느 자리에 출력할 지를 알려주고, 두 번째 인수는 저장할 값을 알려 줍니다. 세 번째 인수는 도트 포인트 출력 여부를 지정하는 것인데, 이 LED 모듈에는 해당하지 않습니다. 일반적인 7 Segment LED는 숫자 오른쪽 아래에 소수점 등을 위한 도트 포인트가 있는데 이 도트 포인트의 출력 여부를 위한 인수입니다. 여기서 사용하는 LED 모듈은 이 도트 포인트가 없으므로 항상 “false” 값을 인수로 제공합니다.

출력할 데이터를 저장하는 내부 버퍼는 위와 같이 다섯 개의 배열로 구성되어 있다고 보면 편합니다. 나머지 공간은 세그먼트 백팩에는 해당 사항 없기 때문입니다. Adafruit LED backpack 라이브러리는 이 버퍼 공간을 displaybuffer 라는 배열로 접근하고 있습니다. writeDigitNum() 함수의 첫 번째 인수는 바로 이 배열을 지정하는 값입니다.

void loop() {
  led.writeDigitNum(0, 1, false);
  led.writeDigitNum(1, 2, false);
  led.writeDigitNum(3, 3, false);
  led.writeDigitNum(4, 4, false);
  led.writeDisplay();
}

바로 위 예제의 loop() 함수를 위와 같이 수정하면 print(1234, DEC)와 출력 결과가 같습니다. 두 번째 인수인 출력할 값은 함수 이름처럼 숫자와 16진수에서 사용하는 6개의 알파벳 문자만 가능하고, 세 번째 인수는 해당 사항 없기 때문에 모두 “true”로 한다고 해도 아무런 변화가 없습니다.

3. drawColon(), writeColon() 함수 및 도트 포인트 출력

이 LED 모듈에는 총 5개의 도트 포인트가 있습니다. 이들은 위에서 언급한 소수점과는 다른 위치이며 버퍼 배열을 통해 직접 제어할 수 있습니다. drawColon() 함수는 이 중에 가운데 콜론을 세팅하는 함수이고, writeColon() 함수는 이 콜론을 디스플레이에 출력하는 함수입니다.

void loop() {
  led.drawColon(true);
  led.writeColon();
  delay(1000);
  led.drawColon(false);
  led.writeColon();
  delay(1000);
}

위와 같이 하면 1초마다 콜론이 깜박이게 됩니다. writeColon() 함수 대신 writeDisplay() 함수를 써도 결과는 같고, 숫자를 제외하고 콜론만 제어하고 싶을 때는 구별하여 사용하면 됩니다.

  led.displaybuffer[2] = 2; // DP3, DP4 On
  led.displaybuffer[2] = 4; // DP1 On
  led.displaybuffer[2] = 8; // DP2 On
  led.displaybuffer[2] = 16; // DP5 On

drawColon(), writeColon() 함수는 가운데 콜론만 제어합니다. 가운데 콜론을 포함하여 다른 도트 포인트를 제어하기 위해서 위와 같이 버퍼에 직접 데이터를 저장하는 방법을 이용할 수 있습니다. displaybuffer[2] 는 LED 모듈의 모든 도트 포인트를 담당합니다. 여기에 숫자를 할당하는 것은 해당하는 비트를 세팅(비트마스크)하기 위해서입니다.

가운데 콜론은 위, 아래 따로 제어할 수 없었습니다. 아마도 회로 자체가 그렇게 배선되어 있는 듯 하고, 여러 개의 도트 포인트들을 동시에 제어하고 싶으면 위 숫자들을 더하여 비트마스크하면 됩니다. 예를 들어, 저는 앞쪽 도트 두 개를 AM, PM 표현을 위해 사용하고, 가운데 콜론은 1초마다 깜박이도록 할 예정입니다. 이를 비슷하게 표현하면 아래와 같습니다.

#include <Adafruit_LEDBackpack.h>
//
Adafruit_7segment led = Adafruit_7segment();
//
void setup() {
  Wire.begin(D1, D2);
  led.begin(0x70);
}
//
void loop() {
  led.print(1234, DEC);
  led.displaybuffer[2] = 6; // 4 + 2
  led.writeDisplay();
  delay(1000);
  led.displaybuffer[2] = 4;
  led.writeDisplay();
  delay(1000);
}

이상으로 프로젝트에 사용할 LED 디스플레이에 대한 기본적인 소개 및 설명을 마치겠습니다. 이 LED 모듈에 대한 나머지 부분은 프로젝트를 진행하며 추가하도록 하겠습니다.

Comments