라즈베리파이 피코보드에서 2.8인치 컬러 LCD ( ILI9341 ) 사용하기

라즈베리파이 피코보드에서 2.8인치 컬러 LCD ( ILI9341 ) 사용하기

1. 피코 보드에 적용해 본 여러 디스플레이

이전의 글들을 통해, 라즈베리 파이 피코 보드에서 다양한 디스플레이의 사용방법을 소개했던 바 있었다.

먼저, 정보를 표시하는 매체의 가장 기초적인 형태인 8×8 KED Matrix와 사용법을 다음 2개의 글로 소개했었다.

https://blog.naver.com/mythee1/223191771952

https://blog.naver.com/mythee1/223192592946

LED Matrix는 본격적인 디스플레이라기보다는 LED의 배열을 제어하여 정보를 표시하는 제품으로, 광고판 등에 제한적으로 사용되며, 본격적인 디스플레이라고 말하기에는 무리가 있다.

그다음으로, 본격적인 디스플레이 중 가장 기본적이라고 할 수 있는 1602 LCD 사용법을 소개했었다.

https://blog.naver.com/mythee1/223188515992

1602LCD는 16글자씩 2줄로 표시해 주는 단색 LCD로, 가격이 불과 3-4천 원 정도로 저렴하면서도, SPI 통신방식과 I2C 통신 방식 두 가지 제품 모두가 시판되고 있어서 개인의 선호에 따라 선택할 수 있는 장점이 있다. 시판되는 1602LCD는 외형은 동일하더라도 사용하는 드라이버 칩이 여러 가지인데, 국내에서는 HD44780 드라이버 칩을 사용하는 제품이 가장 보편적이고 가격도 저렴하다. (해외에서는 ATmega328P을 사용하는 제품도 있다고 하는데 국내에서는 구하기도 어렵고 가격도 몇 배 비싸다.)

그다음으로, 0.96인치 OLED 디스플레이를 사용하는 방법을 소개했었다.

이 제품은 128×64의 해상도를 갖는 제품으로, SSD1306 OLED 칩셋을 사용한다.

https://blog.naver.com/mythee1/223193377274

128×64 해상도

이 과정에서 동일한 SSD1306 칩셋을 사용하되 절반 크기인 128×32 해상도의 제품도 함께 사용법을 소개한 바 있었다.

128×32 해상도

그 후, 이러한 OLED이면서 조금 더 큰 사이즈인 1.3인치 OLED의 사용법을 소개했는데, 이 제품은 위에서 소개한 OLED에 비해 약 50% 정도 크고, 칩셋으로 SSH1106을 사용하는 제품이었다. 때문에, 기존의 SSD1306드라이버 용 라이브러리 대신 별도의 라이브러리를 채택하여 코딩하여야만 했다. 그 외 기능들은 0.9인치 OLED와 유사했다.

https://blog.naver.com/mythee1/223242329760

2. 더 크고 색 표현이 가능한 2.8인치 TFT LCD 구입

위에서처럼 5가지 크기의 디스플레이들을 라즈베리 파이 피코 보드에 적용해 보았으나, 2가지 아쉬운 점이 있었다. 그중 하나는 하나의 LCD 화면에 표시되는 정보의 양이 좀 더 많았으면 하는 바람이었다. 위의 디스플레이들은 8×8 형태의 글자를 사용하기에 디스플레이 종류에 따라 달라지나, 가로축으로는 16자를 표시할 뿐이었고, 세로로는 종류에 따라 2줄(LCD), 4줄(128×32 경우) OLED, 8줄(128×64 경우)로 128×64라도 최대 표시 가능한 글자의 수가 128자에 불과했다. 또한 OLED들은 화면의 크기가 0.9~1.3인치로 작아서 가까이 보아야만 하는 문제가 있었다. 이 때문에 좀 더 많은 수의 글자를 동시에 표현할 수 있도록, 또는 좀 더 큰 폰트의 글자 표기가 가능하도록 화면의 크기가 좀 더 컸으면 하는 바람이 있었다.

또 하나의 바램은 단색이 아니라, 여러 가지 색상을 표현하고 싶은 바람이었다. 단색도 물론 화면이 크면 좋겠지만, 이왕이면 여러 색상을 동시에 표시할 수 있으면 다양한 정보 표시에 더 유리할 듯하였다.

그래서 혹시 이처럼 화면도 더 크고, 컬러 지원도 되면서, 피코 보드에서 쉽게 적용할 수 있는 비싸지 않은 새로운 디스플레이가 없는지 찾아보게 되었고, 그 결과, 다음과 같은 2.8인치의 TFT LCD를 구하게 되었다. (사실 조금 더 큰 4인치 정도의 제품도 있었으나, 단가를 고려하여 2.8:인치에서 타협하게 되었다.)

찾게 된 디스플레이는 ILI9341이라는 드라이버 집을 사용하는 TFT LCD로, 240×320의 해상도를 가지는 제품이었다. 색상은 65K Color를 구현한다고 되어 있었다. 모델에 따라 터치센서 기능을 하는 별도 제품이 있기도 했다.

특이한 것은 기존에 보아오던 디스플레이와 다르게 세로 형태로 길쭉한 형태의 제품으로, 가로축의 해상도가 240이며, 세로축으로의 해상도가 320인 제품이었다. 내장된 기본 8×8 폰트를 이용한다면 가로축으로 30자, 세로축으로 40줄을 표시할 수 있어 총 1200자를 동시에 표시할 수 있는 생이었다.

한편, 가격 역시 직구 시 개당 배송비까지 약 1만 원 정도에 구입이 가능해서 기존 LCD나 OLED보다 다소 비싸기는 했으나, 부담스럽지 않은 편이었다. 배송은 주문 후 약 10여 일 정도 걸린듯하다.

해당 제품을 배송받아 살펴본 결과, 전면부 모양은 다음과 같이 생겼으며,

후면부에는 메모리 카드 사용을 위한 소켓과, 배선을 위한 핀들이 연결되어 있었다.

3. 핀 배열

이 제품의 핀들은 뒷면에 총 14개의 핀들이 있는데, 다음 그림의 윗부분 5개가 터치센서 관련 핀이고, 나머지 9개 핀들이 SPI 연결을 위한 핀이다.

SPI 접속 시 사용되는 핀의 기능은 다음과 같으며, 다음과 같은 방식으로 피코 보드에 연결하여 회로를 구성했다.

VCC – 전원용 핀으로 3.3V에 연결

GND – GND에 연결

LED – Backlight 전원용으로 3.3V에 연결

CS – Chip Select 핀으로, 피코 보드의 GP13에 연결

RESET – Reset 핀으로, 피코 보드의 GP14에 연결

DC – Data 핀으로 A0라고 불리기도 하는데, 피코 보드의 GP15에 연결

MOSI – Data 입력용 핀으로, 피코 보드의 GP7에 연결

SCK – SPI Clock 용 핀으로, 피코 보드의 GP6에 연결

MISO – LCD는 Data를 피코 보드로 출력하지는 않으므로, 연결은 필요하지 않음

4. 파이썬 Library

구입한 TFT LCD는 ILI9341 칩을 사용하고 있었기에, 마이크로 파이썬에서 사용하려면 이에 대한 적절한 라이브러리를 찾아야 했다.

Github에서 9341로 검색하고, 사용 언어를 Python으로 제한하면 다음과 같은 결과를 얻을 수 있는데, 이 중에서 나는 가장 평점이 좋았던 rdagger의 라이브러리를 이용하기로 했다.

해당 부분을 클릭해 들어가면 여러 개의 파일이 나오는데, 이중 다음 ili9341.py가 필요한 라이브러리이다.

이를 다운로드하거나 복사한 후, 피코 보드에 업로드해 주면 된다.

첨부파일

ili9341.py

파일 다운로드

한편 이 라이브러리를 이용해 에제를 만들면서 발견한 특징 중 하나는 좌표 지정이 수평축으로 0~239, 수직으로 0~319 범위에서만 지정해야 한다는 점이었다. 그 이상의 값에 해당되는 좌표를 지정하면 중단되지는 않으나, 에러 메시지가 발생했다. (참고: OLED에서는 좌표가 지정된 해상도를 벗어나도 무방하되 단지 보여주지 못한다면, 사용한 TFT LCD 라이브러리에서는 에러 메시지를 발생시킨다.)

5. 동작 예제

다음과 같은 예제를 만들어 동작을 시험해 보았다.

주요 기능으로는 다음처럼 문자나 Data 표시, 선 긋기 등과,

화면을 위로 또는 아래로 Scroll 하기

화면의 색상을 반전시키기

원, 사각형, 타원, 다각형 등의 도형 그리기와

내부가 채워진 형태로 도형 그리기

등이다.

예제에 사용한 Code는 다음과 같으며,

## Pico display on ili9341 SPI Display ##

from machine import Pin, SPI
import utime
import ili9341

CLK_PIN = const(6)
MOSI_PIN = const(7)
CS_PIN = const(13)
RST_PIN = const(14)
DC_PIN = const(15)

spiTFT = SPI(0, baudrate=51200000, sck=Pin(CLK_PIN), mosi=Pin(MOSI_PIN))
display = ili9341.Display(spiTFT, dc=Pin(DC_PIN), cs=Pin(CS_PIN), rst=Pin(RST_PIN))

display.draw_line(0, 0, 100, 100, ili9341.color565(255, 128, 0))
display.draw_text8x8(100, 100, "100,100", ili9341.color565(255, 128, 0))
display.draw_line(0, 0, 100, 200, ili9341.color565(255, 128, 0))
display.draw_text8x8(100, 200, "100,200", ili9341.color565(255, 128, 0))
display.draw_line(0, 0, 100, 300, ili9341.color565(255, 128, 0))
display.draw_text8x8(100, 300, "100,300", ili9341.color565(255, 128, 0))
utime.sleep(0.01)

display.clear()
for i in range(255):
    display.draw_text8x8(10, 20, "Color : "+str(i)+", "+str(i)+", "+str(i)+"  ", ili9341.color565(i, i, i))
    display.draw_text8x8(10, 60, "Color : "+str(255-i)+", "+str(i)+", "+str(i)+"  ", ili9341.color565(255-i, i, i))
    display.draw_text8x8(10, 100, "Color : "+str(i)+", "+str(255-i)+", "+str(i)+"  ", ili9341.color565(i, 255-i, i))
    display.draw_text8x8(10, 140, "Color : "+str(i)+", "+str(i)+", "+str(255-i)+"  ", ili9341.color565(i, i, 255-i))
    display.draw_text8x8(10, 180, "Color : "+str(255-i)+", "+str(255-i)+", "+str(i)+"  ", ili9341.color565(255-i, 255-i, i))
    display.draw_text8x8(10, 220, "Color : "+str(i)+", "+str(255-i)+", "+str(255-i)+"  ", ili9341.color565(i, 255-i, 255-i))
    display.draw_text8x8(10, 260, "Color : "+str(255-i)+", "+str(i)+", "+str(255-i)+"  ", ili9341.color565(255-i, i, 255-i))
    display.draw_text8x8(10, 310, "Color : "+str(255-i)+", "+str(255-i)+", "+str(255-i)+"  ", ili9341.color565(255-i, i, 255-i))
    utime.sleep(0.001)

display.clear()
display.draw_text8x8(0, 0, "ABCGHIJKLMNOPQRS", ili9341.color565(255, 128, 0))
display.draw_text8x8(0, 20, "TUVWXYZ",  ili9341.color565(255, 128, 0))
display.draw_text8x8(0, 40, "abcghijklmnopqrs", ili9341.color565(255, 128, 0))
display.draw_text8x8(0, 60, "tuvwxyz", ili9341.color565(255, 128, 0))
display.draw_text8x8(0, 80, "01234567890", ili9341.color565(255, 128, 0))
display.draw_text8x8(0, 100, "~!@#$%^&*()_+`-={}[]", ili9341.color565(255, 128, 0))
utime.sleep(0.2)
for i in range(320):
    display.scroll(i)
    utime.sleep(0.01)
for i in range(320, 0, -1):
    display.scroll(i)
    utime.sleep(0.01)


display.display_off( )
utime.sleep(1)
display.display_on( )
utime.sleep(1)

display.write_cmd(display.INVON)			# Display inversion on
utime.sleep(2)
display.write_cmd(display.INVOFF)			# Display inversion off
utime.sleep(2)

display.clear()
for i in range(7):
   display.draw_hline(i*20, i*20, 50, ili9341.color565(255, 128, 0))
   utime.sleep(0.1)
   display.draw_vline(i*20, i*20, 50, ili9341.color565(255, 128, 0))
   utime.sleep(0.1)

display.clear()
for i in range(6):
   display.draw_circle(120, 160, i*20, ili9341.color565(255, 128, 0))
   utime.sleep(0.1)

display.clear()
for i in range(6):
   display.draw_ellipse(120, 160, i*20, i*20-10, ili9341.color565(255, 128, 0))
   utime.sleep(0.1)


display.clear()
for i in range(6):
   display.draw_rectangle(120-i*10, 160-i*10, 20*i, 20*i, ili9341.color565(255, 128, 0))
   utime.sleep(0.1)

display.clear()
for i in range(6):
   display.draw_polygon(i+3, 120, 160, 20*i, 20*i, ili9341.color565(255, 128, 0))
   utime.sleep(0.1)


display.clear()
for i in range(6):
   display.fill_circle(120, 160, i*20, ili9341.color565(255, 128, 0))
   utime.sleep(0.1)
           
display.clear()
for i in range(4):
    display.fill_rectangle(80, 100, i*20+20, i*20+20, ili9341.color565(255, 128, 0))
    utime.sleep(0.1)
    

display.clear()
for i in range(239):
    display.draw_pixel(i,i,ili9341.color565(255, 128, 0))
    display.draw_pixel(i,i+5,ili9341.color565(255, 128, 0))
    display.draw_pixel(i,i+10,ili9341.color565(255, 128, 0))
    display.draw_pixel(239-i,i,ili9341.color565(255, 128, 0))
    display.draw_pixel(239-i,i+5,ili9341.color565(255, 128, 0))
    display.draw_pixel(239-i,i+10,ili9341.color565(255, 128, 0))
    utime.sleep(0.01)

이 에제 코드의 동작 모습은 다음과 같다.

6. 주요 동작 함수

참고로 이 라이브러리에서 사용할 수 있는 중요한 함수들은 다음과 같다.

대개의 경우 함수의 이름으로부터 기능을 유추할 수 있다.

clear()

color565(r, g, b)

draw_line(x1, y1, x2, y2, color)

draw_text(x, y, text, font, color, background=0, landscape=False, rotate_180=False, spacing=1)

–> 별도의 지정한 폰트를 이용하여 글자나 Data 표시

draw_text8x8(x, y, text, color, background=0, rotate=0)

–> 내장 폰트 이용하여 글자나 Data 표시

draw_letter(x, y, letter, font, color, background=0, landscape=False, rotate_180=False):

scroll(y)

display_off( )

display_on( )

write_cmd(display.INVON)

–> 화면 보색 반전

write_cmd(display.INVOFF)

–> 화면 보색 반전 되돌리기

draw_pixel(x, y, color)

draw_lines(coords, color)

draw_h line(x, y, w, color)

draw_vline(x, y, h, color)

draw_circle(x0, y0, r, color)

fill_circle(x0, y0, r, color)

draw_rectangle(x, y, w, h, color)

fill_rectangle(x, y, w, h, color)

draw_ellipse(x0, y0, a, b, color)

fill_ellipse(x0, y0, a, b, color)

draw_polygon(sides, x0, y0, r, color, rotate=0)

–> sides 개수만큼의 모서리를 가지는 정다각형 그리기

fill_polygon(sides, x0, y0, r, color, rotate=0)

–> sides 개수만큼의 모서리를 가지는 정다각형 그리되 내부를 채우기

fill_hrect(x, y, w, h, color)

fill_vrect(x, y, w, h, color)

draw_image(path, x=0, y=0, w=320, h=240)