라즈베리파이나 아두이노를 다루면서 숫자 출력할 일은 꼭 있습니다.
그럴 때 엘리베이터에서 쉽게 볼 수 있는 'seven-segment display' 또는 'flexible numeric display (FND)'를 이용합니다.
이렇게 무식하게 생겼고, 10개 핀이 달렸습니다.
그리고 이 모듈을 여러 개 달아서 대량의 정보를 표시하자니, 배선이 지옥입니다.
그래서 multiplexer를 이용하거나 Multiplexed 4 digit display 모듈을 판매하고 있습니다.
하지만, 4자리도 보여주고 싶은 정보를 보여주기에 부족하다면요?
MAX7219 칩이 달린 제품을 이용하시면 됩니다.
두 제품을 소개합니다.
1) MAX7219 dot matrix module
2) MAX7219 8-digit 7-segment LED display
앞에 MAX7219 이름이 붙는 이유는 이 이름의 칩을 사용한 모듈이라는 것입니다.
LED 형태에 따라 쉽게 제어를 할 수 있도록 만들어놨죠.
중국산(?)이라 그런지 자유롭게 사용가능한 제품 이미지도 구하기 힘들지만, 국내에서도 쉽게 구할 수 있는 좋은 제품입니다.
통신 방식은 DATA(MOSI), CS(chip select), CLK(clock) 3핀을 사용하는 SPI입니다.
그리고 MAX7219 제품끼리 여러 개를 물려서 사용할 수 있는 큰 장점이 있습니다.
저는 소개한 두 제품을 가지고 Project Cars 2 전용 SLI 제작하려고 합니다.
이번 포스팅에서는 라즈베리 파이에서 라이브러리 없이 C++로 MAX7219 모듈을 제어하는 방법을 소개합니다.
SLI가 무엇인지 궁금하다면?
2018/03/20 - [DIY/Project Cars 2] - Raspberry Pi에서 Project Cars 2 UDP 데이터 받기
MAX7219와 라즈베리 파이를 연결
라즈베리 파이에서 SPI 통신하는 핀은 정해져있기 때문에 핀맵은 달라질 수가 없겠네요.
WiringPI 기준으로 12 14 10번. 물리적인 핀 번호는 19 23 24번 입니다.
나머지 2개인 5v와 GND만 빠지지 않게 잘 연결해주면 되겠습니다.
MAX7219.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 |
// define pins for Raspberry PI
#define DATA 12
#define CLOCK 14
#define LOAD 10
// MAX7219 Registers
#define DECODE_MODE 0x09
#define INTENSITY 0x0a
#define SCAN_LIMIT 0x0b
#define SHUTDOWN 0x0c
#define DISPLAY_TEST 0x0f
void send_SPI_16bits(unsigned short data)
{
for (int i = 16; i > 0; i--)
{
/* bitmask */
unsigned short mask = 1 << (i - 1);
/* send data */
digitalWrite(CLOCK, 0);
digitalWrite(DATA, (data & mask) ? 1 : 0);
digitalWrite(CLOCK, 1);
/* no receive data */
//digitalWrite(CLOCK, 0);
}
}
void send_MAX7219(unsigned short reg_number, unsigned short data1, unsigned short data2)
{
digitalWrite(LOAD, 1);
send_SPI_16bits((reg_number << 8) + data2);
send_SPI_16bits((reg_number << 8) + data1);
digitalWrite(LOAD, 0); // to latch
digitalWrite(LOAD, 1);
}
|
cs |
MAX7219를 제어하기 위한 함수 2개가 구현된 헤더 파일입니다.
네, 알고 있어요... 억지로 2개를 제어하겠다는 막코드죠 ㅋㅋㅋㅋ
MAX7219를 제어하기 위한 함수가 있구요, 모듈이 2개이니 data1과 data2를 같이 입력받습니다.
언젠가..는 초기화 과정에서 모듈 개수를 입력받아 유연하게 데이터를 입력받는 클래스로 구현해야겠죠?
다른 함수는 SPI 프로토콜에 맞게 16비트 데이터를 보내는 함수입니다.
MAX7219에 맞게 구현했으니 당연히 데이터를 받지 않고 보내기만 하고 있습니다. 16번 반복하면서요.
그럼 이 함수를 사용하는 예제를 살펴볼까요?
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 |
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <wiringPi.h>
#include "max7219.h"
void intHandler(int dummy)
{
send_MAX7219(SHUTDOWN, 0, 0);
exit(0);
}
int main(int argc, char** argv)
{
signal(SIGINT, intHandler);
if( wiringPiSetup() < 0 )
{
return 1;
}
pinMode(DATA, OUTPUT);
pinMode(CLOCK, OUTPUT);
pinMode(LOAD, OUTPUT);
send_MAX7219(SCAN_LIMIT, 7, 7);
send_MAX7219(DECODE_MODE, 0, 0);
send_MAX7219(INTENSITY, 8, 8);
send_MAX7219(SHUTDOWN, 1, 1);
int digit_table[11] = { 126, 48, 109, 121, 51, 91, 95, 114, 127, 123, 0 };
int dot_table[9][9] =
{ // 128 64 32 16 8 4 2 1
{ 0, 0, 0, 0, 15, 0, 0, 0, 0 }, // N
{ 0, 1, 1, 1, 1, 1, 1, 1, 0 }, // 1
{ 0, 15, 1, 1, 15, 8, 8, 15, 0 }, // 2
{ 0, 15, 1, 1, 15, 1, 1, 15, 0 }, // 3
{ 0, 9, 9, 9, 15, 1, 1, 1, 0 }, // 4
{ 0, 15, 8, 8, 15, 1, 1, 15, 0 }, // 5
{ 0, 15, 8, 8, 15, 9, 9, 15, 0 }, // 6
{ 0, 15, 9, 9, 9, 1, 1, 1, 0 }, // 7
{ 0, 0, 0, 0, 15, 8, 8, 8, 0 }, // R
};
send_MAX7219(1, digit_table[0], dot_table[0][1]);
send_MAX7219(2, digit_table[0], dot_table[0][2]);
send_MAX7219(3, digit_table[0], dot_table[0][3]);
send_MAX7219(4, digit_table[0], dot_table[0][4]);
send_MAX7219(5, digit_table[0], dot_table[0][5]);
send_MAX7219(6, digit_table[0], dot_table[0][6]);
send_MAX7219(7, digit_table[0], dot_table[0][7]);
send_MAX7219(8, digit_table[0], dot_table[0][8]);
//intHandler(0);
return 0;
} |
cs |
25-27 줄에서 SPI 통신에 사용할 3핀을 WiringPI로 초기화해줍니다.
그 다음으로, MAX7219 모듈을 초기화하는 코드가 나옵니다.
1) SCAN_LIMIT 명령어로 초기화를 해주는데요, 값을 바꿀 이유는 없을 것 같습니다.
2) DECODE_MODE는 제가 작성한 예제에서는 사용하지 않습니다.
디코드 모드를 사용하면 FND 모듈에서 출력 가능한 숫자를 포함한 문자를 그대로 출력할 수 있습니다.
0부터 15까지 데이터를 보내서 7 segment display 답게 0-9 숫자 또는 A B C D E F 문자를 출력할 수 있습니다
라고 알고 있는데, 그럼 .은 어떻게 출력하지?
디코드 모드는 자세히 살펴보지 않아 정확한 내용은 datasheet를 봐야할 것 같네요.
디코드 모드를 사용하지 않는다면, 8비트 마스크를 OR 비트 연산한 값인 (비트가 겹치지 않으니 더하면 됩니다.) 0-255 사이의 데이터를 보내야 합니다.
필요에 따라 출력할 숫자의 비트 마스크를 미리 계산하고 테이블로 만들어두고 배열로 출력하면 됩니다. 저처럼 말이죠, 이게 더 편해요.
3) INTENSITY는 말그대로 밝기입니다.
15까지 올릴 수 있는데, 너무 밝으니 눈이 아프더라구요.
4) SHUTDOWN은 ON OFF 입니다.
define 이름이 이상하게 보이는데, 1을 보내서 키고 0을 보내서 쉽게 끌 수 있습니다.
34 라인은 FND 모듈의 숫자 테이블입니다. 0-9 숫자에다가 부분적으로 끄기 위해 맨 마지막에 0을 추가했습니다.
. 을 찍고 싶으면 테이블 값에 128을 더하면 됩니다.
35 라인은 도트 매트릭스에서 자동차 기어를 표시하기 위해 ---- (Neutral), 1-7, ┌ (Reverse) 를 추가했습니다.
8x8 도트이니 당연히 2차원 배열이죠.
숫자를 예쁘게 출력하기 위해 오른쪽에 치우쳐 출력했고, 왼쪽 빈 공간에는 다른 정보를 출력할 수 있겠군요.
48-55 라인은 초기화가 완료되었음을 표시하기 위해
위에서 만들어놓은 테이블을 참조하여 FND 모듈과 도트 매트릭스 모듈에 각각 00000000 과 ---- 를 표시하는 코드입니다.
그리고 LED는 켜진 채 종료됩니다.
종료를 시키지 않는 경우에는 57 라인의 주석을 해제하여 메인함수가 종료되기 전 LED를 끌 수 있습니다.
물론, 핸들러 덕분에 Ctrl+C로 종료할 때 역시 LED가 꺼집니다.
지금까지 두 개의 MAX7219 모듈에 제가 원하는 정보를 출력할 수 있었습니다.
그리고, 아주 간단했습니다. 제 주관적인 생각으로는 라이브러리를 사용하여 이 모듈을 다루면 너무 무거워지는 것 같네요.
물론 테이블을 만들 때 약간의 노가다가 필요하죠.
여러분은 어떤 정보를 출력하시나요? 유용한 테이블은 공유해요~
참고자료:
[1] https://en.wikipedia.org/wiki/Seven-segment_display
[2] https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
'코딩 > Raspberry PI' 카테고리의 다른 글
Project Cars 2 SLI 완성! (0) | 2018.05.16 |
---|---|
Raspberry Pi에서 Project Cars 2 UDP 데이터 받기 (2) | 2018.03.20 |