[PJT] AR Class

정재훈·2022년 4월 23일
0

간단 PJT

목록 보기
1/6

사용 부품

  1. Raspberry pi zero w
  2. SSD1306 0.96" monochrome oled display
  3. 안경
  4. 렌즈

라즈베리파이 셋팅

  1. SD카드에 라즈비안 설치
  2. 네트워크 설정
    • 방법 1) 라즈베리 파이에 모니터, 키보드, 마우스 연결 후 네트워크 셋팅
    • 방법 2) SD Card에서 /boot 경로에 wpa_supplicant.conf 파일 내에 내용을 다음과 같이 수정 후 모든 파일(.) 형식으로 저장한다.
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=US
network={
  # 패스워드 없는 경우
  ssid=”와이파이 이름1(SSID)”
  scan_ssid=1
	key_mgmt=NONE
}
network={
  # WPA1, WPA2-personal
  ssid=”와이파이 이름2(SSID)”
  psk=”암호2”
  scan_ssid=1
  key_mgmt=WPA-PSK
}
  1. 접속된 IP 확인 사이트를 통해 IP를 라즈베리 파이 IP확인하기!
  2. SSH 혹은 VNC로 접속한다.

OLED 연결

해상도는 128x64 pixel이며, color와 grayscale은 표현되지 않는 OLED를 사용한다.
OLED와 라즈베리 파이 Zero와 SPI 통신으로 연결할것이다.

SPI 통신이란?

하나의 마스터에 여러개의 Slave를 연결할 수 있다.
4개의 핀을 통해 통신한다.
1. SCK : Clock 핀
2. MOSI : Master -> Slave 데이터 전송
3. MISO : Slave -> Master 데이터 전송
4. SS : Slave를 구분하기 위한 선

회로

설정 및 설치

SPI 활성화 : sudo raspi-config에서 Interface Options에서 SPI를 활성화 시켜준다.
OLED 구동 라이브러리 설치 : pip3 install Adafruit-GPIO==1.0.3 & pip3 install Adafruit-SSD1306
Pillow 라이브러리 설치 : pip3 install pillow
예제 설치 : git clone https://github.com/adafruit/Adafruit_Python_SSD1306.git &
라이브러리 수정 : cd Adafruit_Python_SSD1306/examples에서 sudo nano image.py로 파일 열고 다음 코드 처럼 수정한다.

연결한 핀에 맞게 값 바꿔주고, IC2 통신 주석 처리 및 SPI 통신 주석 처리 해제해주기!

RST = 24
DC = 25
SPI_PORT = 0
SPI_DEVICE = 0


# 128x32 display with hardware I2C:
#disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST)

# 128x64 display with hardware I2C:
# disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)

# 128x32 display with hardware SPI:
# disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST, dc=DC, spi=SPI.SpiDev(SPI_PORT, SPI$

# 128x64 display with hardware SPI:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST, dc=DC, spi=SPI.SpiDev(SPI_PORT, SPI_D$

OLED 예제 코드

OLED에 사진 띄우기 및 변형 참고

import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306
from PIL import Image

# OLED 설정
RST = 24
DC = 25
SPI_PORT = 0
SPI_DEVICE = 0
oled = Adafruit_SSD1306.SSD1306_128_64(rst=RST, dc=DC, spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE, max_speed_hz=8000000))
oled.begin()


im = Image.open('happycat_oled_64.ppm').convert('1') # 이미지 만들기
oled.clear()   # OLED 화면 지우기
oled.display() # OLED에 표시

# 이미지 자르기
box = (32,0,96,64) # (시작 x,y ~ 끝 x,y)로 x축으로는 32~96 y축으로는 0~64인 박스 만들기
region = im.crop(box) # 해당 영역 만큼 이미지 자르기

# 이미지 변형
# region = region.transpose(Image.ROTATE_180)  # 90도 회전
region = region.transpose(Image.FLIP_LEFT_RIGHT)  # 좌우 반전
im.paste(region, box) # 이미지 붙이기

oled.image(im) # 메모리에 이미지 올리기
oled.display() # OLED에 표시

OLED에 도형 그리기 참고

import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306
from PIL import Image

# OLED 설정
RST = 24
DC = 25
SPI_PORT = 0
SPI_DEVICE = 0
oled = Adafruit_SSD1306.SSD1306_128_64(rst=RST, dc=DC, spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE, max_speed_hz=8000000))
oled.begin()


# oled에 하얀색 바탕의 빈 화면 띄우기
# 파라미터 1 : '1'모드는 흑백 모드 , 파라미터 2 : 해상도, 파라미터 3 : 1 => 하얀색
im = Image.new('1', (128,64), 1) 

from PIL Import ImageDraw
draw = ImageDraw.Draw(im) # 그림을 그리겠다고 설정
draw.line((0,0,128,64),fill = 1) # 좌상단에서 우하단으로 하얀 선 그리기
draw.line((0,64,128,0), fill = 0) # 우상단에서 좌하단으로 검은 선 그리기

# 네모
box = (32,0,96,64)
draw.rectangle(box,fill = 0)

# 원
draw.ellipse(box,fill = 1)

oled.image(im) # 메모리에 이미지 올리기
oled.display() # OLED에 표시

OLED에 텍스트 출력

우선 wget -O malgun.ttf https://bit.ly/3BzjZfq를 통해 폰트 다운

import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306
from PIL import Image

# OLED 설정
RST = 24
DC = 25
SPI_PORT = 0
SPI_DEVICE = 0
oled = Adafruit_SSD1306.SSD1306_128_64(rst=RST, dc=DC, spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE, max_speed_hz=8000000))
oled.begin()


# oled에 검은색 바탕의 빈 화면 띄우기
# 파라미터 1 : '1'모드는 흑백 모드 , 파라미터 2 : 해상도, 파라미터 3 : 0 => 검은색
im = Image.new('1', (128,64), 0) 

from PIL Import ImageDraw
draw = ImageDraw.Draw(im) # 그림을 그리겠다고 설정

font = ImageFont.truetype('malgun.ttf', 15) # 다운 받은 15 사이즈 맑음 폰트
draw.text((10,10), "안녕하세요\n반갑습니다", font = font, fill = 1) # 하얀색 폰트인 글씨를 (10,10) 위치에 그리기

oled.image(im) # 메모리에 이미지 올리기
oled.display() # OLED에 표시

날짜 출력 코드

참고 문서
우선, 날짜 및 시간을 올바르게 출력하기 위해서는 라즈베리 파이를 내가 사는 지역 시간에 맞춰주어야 한다.
sudo raspi-config에서 5. Locallisation Options > L2. Timezone > Asia, Seoul을 차례로 션택. > Finish로 설정해줍니다.

 # 특정 날짜 표시
from datetime import date

my_birthday = date(1998,1,16)
print(my_birthday)  # 1998-01-16
print(f'나는 {my_birthday.year}{my_birthday.month}{my_birthday.day}일에 태어났습니다.')  
# => 나는 1978년 8월 8일에 태어났습니다.

# 요일 표시
# print(my_birthday.weekday())  숫자로 출력됨 => 0 : mon ~~ 6 : sun 
# day = '월화수목금토일'[my_birthday.weekday()]  # 요일로 전환
# print(day)

# 오늘
# print(date.today())   # 2022-04-22

시간 출력 코드

from datetime import time

# 특정 시간 표시
at = time(14,2,0)
print(at) # 14:02:00

print(f'기차는 {at.hour}{at.minute}분에 떠납니다') # 기차는 14시 2분에 떠납니다.

# 특정 날짜 & 시간 동시 표시
from datetime import datetime
time = datetime(2022,4,22,14,4,30) 
print(time) # 2022-04-22 14:04:30

# 지금 날짜 & 시간 출력
from time import sleep
while True:
    now = datetime.now()
    print(now)  # 2022-04-22 14:06:07.053065

    # 오늘에서 원하는 것만 추출 
    print(f'지금은 {now.month}{now.day}{now.hour}{now.minute}분입니다.') 
    print(f'{"월화수목금토일" [now.weekday()]}요일입니다.')
    
    # 시간 표현 프레임 
    go = now.strftime('%Y %h.%d %p%I:%M %S')
    print(go)
    sleep(1)

AR 제작

OLED에서 보여지는 상을 홀로그램에서 많이 사용되는 perper's ghost 기술을 사용하여 반사가 많이 되는 유리를 통해 OLED의 허상과 실제 보는 상을 동시에 보게됩니다. 야외버스에 창밖을 보면 버스 내부가 보이는 현상과 같이, 유리에 상을 띄우게 되어도 하나의 문제가 남아있습니다. 그건 바로 초점이 맞지 않는다입니다. 따라서, 저는 초점을 맞추기 위해 Fresnel lens라는 얇은 볼록렌즈를 사용하여 초점을 맞추었습니다.

완성된 모습

버튼 연결

라즈베리파이 내부 풀다운 저항을 이용하여 버튼을 연결합니다.

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(20, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)

def myFunction(channel):
	clear

# interrupt 함수로, RISING/FALLING/BOTH
GPIO.add_event_detect(20, GPIO.RISING, callback = myFunction) 

마이크 연결

I2S Microphone : MEMS(반도체 공정)을 활용한 조그만 마이크
핀 4개
1. CLK : Clock 선, 동기화를 위한 것
2. SD : 데이터 선, 데이터 송수신 선
3. WS : 2-스테레오를 구현을 위해 왼쪽인지 오른쪽인지 구분하기 위한 선
4. SEL : 0V => 왼쪽 마이크, 3.3V => 오른쪽 마이크

장치를 테스트하기 위해 커널을 항상 빌드하는것이 매우 많은 시간이 소요되기 때문에, 커널 모듈을 통해서 장치를 테스트합니다.

Device Tree : .dts 텍스트 파일을 dtc로 컴파일하여 dtb 바이너리로 만들어 어떤 장치를 테스트할지 결정한다.

마이크 동작 설치

  1. 리눅스 커널 헤더를 다운받습니다.
    sudo apt-get install raspberrypi-kernel-headerssudo reboot

  2. 마이크를 동작 시키기 위해 디바이스 드라이버가 커널 모듈 형태로 제작된 코드를 다운받습니다. 우선, mkdir ics43432 파일 생성 후 cd ics43432 내부로 이동한다.
    uname -a로을 확인하여 커널 버전을 확인하고, 버전에 맞게 명령어를 입력해준다.

wget https://raw.githubusercontent.com/raspberrypi/linux/rpi-5.10.y/sound/soc/codecs/ics43432.c
  1. Makefile 제작 : sudo nano Makefile
obj-m := ics43432.o
all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
install:
	sudo cp ics43432.ko /lib/modules/$(shell uname -r)
	sudo depmod -a
  1. make all install로 빌드하기
    에러 발생시 2번을 다시 다운받고 4번 실행

  2. 디바이스 트리 소스파일 다운
    wget https://raw.githubusercontent.com/lhdangerous/i2s-mems-mic/main/i2s-soundcard-overlay.dts

  3. dts로 컴파일 후 오버레이를 /boot/overlays에 설치하기
    dtc -@ -I dts -O dtb -o i2s-soundcard.dtbo i2s-soundcard-overlay.dts
    sudo cp i2s-soundcard.dtbo /boot/overlays

  4. 부팅시 디바이스 오버레이 설정
    설정 파일 열기 : sudo nano /boot/config.txt

# 아래 내용 uncomment한다.
dtparam=i2s=on
dtparam=audio=on
# 아래 내용 추가한다.
dtoverlay=i2s-soundcard,alsaname=i2sPiSound
  1. 재부팅 후 테스트
    재부팅 : sudo reboot
    테스트 : arecord -c1 -d10 -vv test.wave

  2. 파이썬에서 오디오 장치를 다루는 툴 설치
    sudo apt-get install portaudio19-dev
    pip3 install pyaudio

오디오 예제

Context Manager

# with 안에서 파일 사용 : 명시적으로 close를 적어주지 않아도 with 구문을 빠져나올떄는 close가 실행된다.
# with 구문 내에서는 중간에 error 발생시 파일이 닫히는 것이 보장된다.
with open('test.txt', 'r') as file:
	print(file.read())
    
# with 없이 파일 사용
file = open('test.txt', 'r')
print(file.read())
# 하지만 with 없이 사용할 때 중간에 error 발생시 파일이 닫히지 않는다.
file.cloes()

# Python Context Manager
class ContextManager() :
	def __init__(self):
    	print("__init__")
    def __enter__(self):
    	print("__enter__")
    def __exit__(self, exc_type, exc_value, exc_traceback):
    	print("__exit__")

# with 구문과 함께 context Manager을 사용하면 with 구문 실행시 __init__ & __enter__ 실행
# 종료시 __exit__ 실행 보장
with ContextManager() as manager:
	print("with in")
    
print("with after")

Python Generator

끝나지 않는 함수, 보통 함수를 시작하면 메모리 적재후 return으로 값을 리턴 후 메모리에서 내려오는데, yield를 통해 값을 리턴하는데, 이때 메모리에서 내려가는게 아니고 잠시 대기한 후, 추가 명령어를 통해 진행된다.

# 일반 함수
def func():
    n = 1
    print('first')
    return n

    n += 1
    print('second')
    return n

a=func() # first만 출력되고 second는 출력되지 않는다. 

# generator function
def my_gen():
    n = 2
    print('first')
    yield n
    
    n += 2
    print('second')
    yield n
    
    n += 2
    print('third')
    yield n

a = my_gen() # 시작하자마자 대기
next(a) # first 출력
next(a) # second 출력
next(a) # third 출력
next(a) # 에러 발생

# for루프에서 사용
b = my_gen()
for i in b:
    print(i)
from time import sleep
import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306


RST = 24
DC = 25
SPI_PORT = 0
SPI_DEVICE = 0

oled = Adafruit_SSD1306.SSD1306_128_64(rst=RST, dc=DC, spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE, max_speed_hz=8000000))

oled.begin()

oled.clear()
oled.display()

# text

from PIL import Image, ImageDraw, ImageFont
im = Image.new('1',(128,64),0)
draw = ImageDraw.Draw(im)

font1 = ImageFont.truetype('malgun.ttf', 15)
font2 = ImageFont.truetype('malgun.ttf', 25)

from datetime import datetime


now = datetime.now()
pm = now.strftime('%p')
time = now.strftime('%I:%M')
sec = now.strftime('%S')
draw.text((0,0), pm, font = font1, fill = 1)
draw.text((0,15), time, font = font2, fill = 1)
draw.text((40,40), sec, font = font1, fill = 1)
im = im.transpose(Image.FLIP_LEFT_RIGHT)

im = im.transpose(Image.ROTATE_90)
box = (0,0,128,64)
go = im.crop(box)
oled.image(go)

oled.display()

화면

한계점
제가 예상한 결과로는 1초마다 시간이 갱신되어 유리에 시간을 띄우게 하려고 하였지만, 코드의 미흡으로 구현하지 못하였습니다.

profile
여러 방향으로 접근하는 개발자

0개의 댓글