검색하기

OLED 디스플레이를 이용한 움직이는 이미지 만들기

2018-09-21

이미지 만들기 


아두이노를 이용하여 외부입력 데이터나 저장된 데이터를 표현을 하고자 할 때, 소형화가 가능하고 휴대가 가능하도록 저전력으로 이용 가능한 OLED 디스플레이는 고해상도로 점과 선, 도형을 출력할 수 있을 뿐 아니라 영문자, 기호, 숫자, 이미지까지 다양하게 표현 가능하다. 그렇기 때문에 2장 마지막 예제로 나오는 게임 예제에 사용할 디스플레이 장치로 OLED 디스플레이를 사용하도록 한다. 마지막 예제를 제작하여 배터리를 추가하고, 좀 더 작은 크기의 아두이노인 아두이노 나노(nano)를 사용한다면 충분히 휴대가 가능한 게임기를 만드는 것이 가능할 것이다.

 

이브러리에 없는 도형이나 모양을 만들어보고 애니메이션처럼 움직이는 표현을 보도록 한다. 그리고 OLED 디스플레이를 제어하는 라이브러리로 사용되는 U8glib가 아두이노 뿐 아니라 다른 하드웨어 플랫폼에서도 주로 사용하는 이유와 특징 및 장점, 단점에 대해서 자세히 살펴보도록 한다.

 


 

 


 

 

코드설명 


이번 스케치 코드는 OLED 디스플레이를 이용하여 움직이는 이미지를 만들어보는 예제이다. 코드에 들어간 모형이나 그림들은 U8glib에 따로 정의되지 않는 형태의 모양을 그려 processing 프로그램 GridEditor를 이용하여 코드로 변환하였다. OLED 디스플레이에 표현한 그림은 야근을 마치고 가로수 길을 지나 퇴근하는 자동차 풍경을 표현해보았다. 배경으로 지나가는 가로수는 두 종류의 나무로 구성하였고 자동차는 전조등을 비추며 다른 속도로 앞뒤를 오가는 모습을 표현하였다.

 

앞서 시계를 만드는 예제에서도 보았듯이, U8glib를 이용하여 OLED에 디스플레이하는 방식은 그리기 구간(Picture loop)에서 draw( ) 함수 이용하여 원하는 도형이나 문자를 표현한다. 라이브러리에 없는 모양을 직접 GridEditor를 통하여 그리다 보니 코드가 길어지다 보니 그림의 구분이 어려와 나무와 자동차를 그리는 함수를 따로 정의하여 코드를 구분해두었다. 스케치 코드 2-3에서는 표현되는 숫자를 변수로 두었지만 이번 예제에서는 나무와 자동차의 x 좌표를 변수로 하여 움직이는 모습을 연출하였다. 각각의 도형을 그리는 함수는 심화 함수 설명에서 간략하게 설명해두었다.

 

나무와 자동차의 움직임을 표현하는 알고리즘은 매우 간단한다. 나무는 x 좌표를 loop( )함수를 갱신 할 때마다 왼쪽으로 움직이도록 하며 자동차는 x 좌표에 ?2씩 감소시키다 x 좌표가 ?30 이하가 되면 방향을 바꾸어 반대로 +2를 증가시킨다. 또 증가하던 x 좌표가 10 이상이 되면 반대방향으로 진행방향을 바꾸게 된다.

 

마지막으로 OLED 디스플레이가 어떻게 비교적 낮은 처리사양을 갖는 아두이노를 이용해 고해상도의 그림을 표현할 수 있는지에 대한 내용을 살펴보기로 한다.

 

● 디스플레이 메모리에서 데이터를 읽을 수 없다.

● 디스플레이 메모리에 단일 픽셀을 설정할 수 없으며, 8 개의 픽셀을 함께 할당해야한다.

● 내장형 컨트롤러의 RAM 영역의 크기가 제한되어 있다.

 

위의 제한사항으로 생긴 문제를 U8glib를 사용하여 해결할 수 있다.

 

첫 번째와 두 번째 제한사항에 대한 해결방법은 로컬 프레임 버퍼(Local Frame Buffer)를 사용하는 것이다. 디스플레이 메모리 내에서 단일 픽셀을 지울 수 없다. 단일 픽셀을 설정할 수도 없으며 해당 픽셀의 주변 영역을 읽을 수도 없다. 이에 대한 간단한 해결책은 디스플레이 메모리를 컨트롤러의 RAM에 미러링(mirroring)하는 것이다. 디스플레이 장치를128x64 모노크롬(monochrome, 단색표시장치) 디스플레이로 가정하면 알고리즘은 다음과 같다.

 

(1) 128x64 디스플레이의 컨트롤러 RAM에 로컬 프레임 버퍼를 할당

(2) 로컬 프레임 버퍼를 비우기(clear). (128x64 픽셀)

(3) draw( )를 실행하여 그림을 로컬 프레임 버퍼에 렌더링(rendering)

(4) 메모리를 표시하기 위해 로컬 프레임 버퍼 전송

 

세 번째 제한사항에 대한 해결방법은 하프 프레임 버퍼(Half Frame Buffer)를 사용하는 것이다. RAM은 일반적으로 임베디드 시스템에서 매우 제한적이다. 그렇기 때문에 컨트롤러에 필요한 RAM의 절반만을 사용하여 동일한 그림을 두 부분으로 나누어 그린다.

 

(1) 마이크로 컨트롤러 RAM(128x32 픽셀)에서 절반의 로컬 프레임 버퍼('Page'라고 함)를 할당

(2) 페이지(128x32 픽셀)를 비운다, 0 ~ 31 행으로 클리핑(clipping) 설정

(3) draw( )를 수행

(4) 페이지를 디스플레이 프레임 버퍼의 상단으로 전송

(5) 페이지(128x32 픽셀)를 비운다, 32 ~ 63 행으로 클리핑 설정

(6) draw( )를 수행

(7) 페이지를 디스플레이 프레임 버퍼의 하단으로 전송

 

 

 

  

 a. (1) ~ (4) 단계 그림

  b. (5) ~ (7) 단계 그림 

 c. 전체 그림  

 

주변의 장치의 수와 경우에 따라 draw( ) 는 다양하게 구성할 수 있다. 따라서 함수를 호출하는 대신 그리는 작업을 명시적으로 그리기 구간(Picture loop)을 통하여 수행한다. 

 


하지만 제한사항을 해결하는 동시에 U8glib를 사용함으로서 문제점이 발생한다. 먼저는 시각적으로 매우 복잡한 장면은 천천히 렌더링(rendering) 될 수 있으며, 사용자는 디스플레이의 상단 부분이 하위 부분보다 먼저 업데이트됨을 관측할 수 있다. 두 번째 문제점은 프로그래밍 제한 사항으로서 디스플레이 메모리를 업데이트 할 수 없다. , 디스플레이에 ‘AB’를 표현하고자 할 때, ‘A’를 쓰고, ‘A’가 다 써지를 기다렸다가, 'B'만 쓰면 'A'가 지워진다. 따라서 세 번째 단계에서 'A''B'를 그려야한다. 이 의미는 현재 페이지의 픽셀에 대한 정보만 사용할 수 있다. 따라서 디스플레이 메모리 내의 영역을 복사하거나 스크롤하는 것은 불가능하다는 것과 같다. 또한 동일한 그래픽 명령은 그리기 구간(picture loop)이 종료 될 때까지 반복해야한다.

 

결론적으로 U8glib을 사용하면 읽기 전용 디스플레이 장치를 거의 추가 램 없이 사용할 수 있다. 또한 화면은 동일한 내용으로 여러 번 렌더링 되지만 새로 고침 빈도를 최소화 할 수 있다. 느리게 렌더링 되는 것을 걱정할지 모르지만 ATMEGA 시스템에서는 초당 20 개 이상의 프레임 속도가 가능하기 때문에 예제에서 사용되는 소형 디스플레이의 지연은 걱정하지 않아도 된다.