홍창민정보·인공지능 교사

빛·소리·불꽃 · 난이도 보통

Tessel Ambient(Attx4)의 빛·소리 트리거를 피코에서 KY-018과 LM393으로 직접 만들기

Attx4는 ATtiny44 펌웨어와 포토다이오드·마이크로 빛과 소리를 0.0~1.0 값과 트리거로 알려 주는 Tessel Ambient 모듈이에요. 원본 드라이버를 옮긴 피코용 실험 드라이버와, KY-018 조도·LM393 소리 모듈로 같은 일을 하는 배선과 트리거 코드, 문턱 탐구를 정리했어요.

글 홍창민 · 갱신 2026-10-31 · 읽는 데 약 10분 · 가상 실행 확인 (실물 미확인)

이 센서로 무엇을 잴 수 있나

"Attx4"는 Tessel이라는 Node.js 보드용으로 나온 Ambient 모듈의 드라이버 이름(ambient-attx4)에서 온 말이에요. Tessel 하드웨어 문서에 따르면 이 모듈(TM-08, 최신판 TM-08-03)의 핵심 부품은 빛을 받는 PT15-21C/TR8과 SPU0410HR5H-PB 마이크이고, 전류는 10mA예요. 드라이버 코드를 보면 두 신호를 읽어 주는 칩은 ATtiny44예요.

드라이버 README를 보면 이 모듈이 하는 일은 두 가지예요. 빛과 소리의 크기를 0.0~1.0 사이 값으로 계속 알려 주는 것, 그리고 정해 둔 값(트리거)을 넘으면 light-trigger, sound-trigger 사건을 알려 주는 것이에요.

문제는 피코예요. 이 모듈을 MicroPython으로 쓰는 공개 드라이버는 찾아본 범위에서 없었어요. 그래서 시나브로 PICO는 원본 드라이버(index.js)와 펌웨어 소스의 SPI 통신 순서를 MicroPython으로 옮긴 실험용 드라이버 ambient_attx4.py를 /lib에 넣어 두었어요(SPI 모드 0, 1kHz SoftSPI, 응답 첫 바이트 0x55). 다만 실제 모듈로는 확인하지 못했고, 모듈의 G1·G2·G3 핀 배정도 원본 드라이버를 따른 것이에요. 그래서 이 글은 같은 일을 흔한 KY-018 조도 센서와 LM393 소리 센서로 피코에서 직접 만드는 방법을 중심으로 써요. Tessel 모듈이 있다면 시나브로 PICO 센서 가이드의 'Tessel Ambient Attx4' 항목과 가상 실행으로 먼저 연습해 보세요.

원리

Tessel 모듈이 하던 방식. 하드웨어 문서에 따르면 빛 신호는 차단 주파수 1.6Hz의 저역 통과 필터를, 소리는 반파 정류를 거쳐 크기로 바뀌어요. 드라이버(index.js)는 ATtiny44에서 받은 숫자를 1024로 나눠 0.0~1.0으로 만들고, 트리거 값은 1024를 곱해 보내요. 문턱을 넘으면 ATtiny44가 IRQ 핀을 올려 알려요. 공통 코드(attiny-common)를 보면 G1은 칩 선택, G2는 리셋, G3는 IRQ이고 SPI는 1kHz예요. 원리상 피코의 SPI와 GP 핀 세 개로 이을 수는 있겠지만, 이 통신 순서를 MicroPython으로 옮겨 검증한 자료는 찾지 못했어요.

피코에서 직접 만드는 방식. 같은 일을 이렇게 나눠요.

Tessel Ambient 이 글의 피코 코드
getLightLevel() (0.0~1.0) light_level(): KY-018 전압을 뒤집어 0.0~1.0
getSoundLevel() (0.0~1.0) sound_level(): LM393 AO의 50ms 진폭 ÷ 65535
setLightTrigger(값) LIGHT_ON 문턱과 GAP 여유
light-trigger 사건 문턱을 넘는 순간 한 번 출력

KY-018은 밝아질수록 S 전압이 내려가는 모듈이라(KY-018 조도 센서), 1 − 전압 비율로 뒤집어 밝을수록 1에 가깝게 만들어요. 소리는 한 번 읽은 값이 파형의 한 점일 뿐이라(LM393 소리 센서), 50ms 동안의 최대−최소(진폭)를 써요.

트리거에서 가장 중요한 것은 한 번만 울리게 하는 일이에요. 값이 문턱 근처에서 오르내리면 문턱 하나로는 알림이 연달아 울려요. 그래서 넘을 때의 문턱(LIGHT_ON)과 다시 준비하는 문턱(LIGHT_ON − GAP)을 다르게 두는 히스테리시스를 써요. 손뼉처럼 짧게 튀는 소리는 울린 뒤 일정 시간(HOLD_MS) 동안 다시 울리지 않게 해요.

준비물

  • 라즈베리파이 피코 2 W와 USB 케이블(보드 LED를 트리거 표시로 써요)
  • KY-018 조도 모듈, AO가 있는 4핀 LM393 소리 모듈, 암-수 점퍼선 6개, 브레드보드
  • LED 스탠드나 손전등, 빛을 가릴 두꺼운 종이, 줄자

배선

Tessel Ambient(Attx4)의 빛·소리 트리거를 피코에서 KY-018과 LM393으로 직접 만들기: 브레드보드에 피코와 센서를 꽂은 배선 그림
시나브로 PICO 브레드보드 작업대와 같은 계산으로 그린 배선이에요. 위 + 줄은 3.3V, 아래 + 줄은 5V예요.
브레드보드 따라 꽂기 순서 (13단계)
  1. 피코를 브레드보드 가운데 홈에 걸쳐 꽂아요. USB 단자가 왼쪽을 보게 하고, 1번 핀이 f2, 40번 핀이 e2에 들어가요. 다리가 홈 양쪽에 모두 들어갔는지 옆에서 봐요.
  2. 주황 선: 피코 36번 핀(3V3(OUT)) 옆 칸 a6 → 위쪽 + 줄(3.3V). 이제 이 줄 전체가 3.3V예요.
  3. 검은 선: 피코 38번 핀(GND) 옆 칸 a4 → 위쪽 − 줄(GND). 이제 이 줄 전체가 GND예요.
  4. 조도·소리 트리거 모듈 Tessel Ambient Attx4 꽂기: 다리 GND (1번) · 3V3 (2번) · SCK (5번) · MISO (6번) · MOSI (7번) · CS (8번 G1) · RST (9번 G2) · IRQ (10번 G3)를 위쪽 a24 · a25 · a26 · a27 · a28 · a29 · a30 · a31에 차례로 꽂아요. 다리가 브레드보드에 바로 안 들어가면 암-수 점퍼선으로 같은 칸에 이어요.
  5. 검은 선(GND): 조도·소리 트리거 모듈 GND (1번) 줄 b24 → 위쪽 − 줄(GND)
  6. 주황 선(3.3V 전원): 조도·소리 트리거 모듈 3V3 (2번) 줄 b25 → 위쪽 + 줄(3.3V)
  7. 분홍 선(신호): 조도·소리 트리거 모듈 SCK (5번) 줄 b26 → 피코 24번 핀(GP18) 옆 칸 a18
  8. 분홍 선(신호): 조도·소리 트리거 모듈 MISO (6번) 줄 b27 → 피코 21번 핀(GP16) 옆 칸 a21
  9. 분홍 선(신호): 조도·소리 트리거 모듈 MOSI (7번) 줄 b28 → 피코 25번 핀(GP19) 옆 칸 a17
  10. 분홍 선(신호): 조도·소리 트리거 모듈 CS (8번 G1) 줄 b29 → 피코 22번 핀(GP17) 옆 칸 a20
  11. 초록 선(신호): 조도·소리 트리거 모듈 RST (9번 G2) 줄 b30 → 피코 26번 핀(GP20) 옆 칸 a16
  12. 초록 선(신호): 조도·소리 트리거 모듈 IRQ (10번 G3) 줄 b31 → 피코 27번 핀(GP21) 옆 칸 a15
  13. 마지막 확인: ① 위쪽 + 줄(3.3V)과 아래쪽 + 줄(5V)이 서로 이어지지 않았나요? ② 모든 부품의 GND가 − 줄에 이어졌나요? ③ 5V 신호 다리는 저항을 거쳐 피코에 이어졌나요? ④ 긴 브레드보드는 + − 줄이 가운데에서 끊긴 것도 있어요(빨간·파란 선이 끊겨 있으면 짧은 선으로 이어요). ⑤ 그림과 한 칸씩 비교한 뒤 USB를 꽂아요.
모듈 핀 피코 핀 비고
KY-018 S GP26 (31번) 밝을수록 전압이 내려가요
KY-018 가운데(+) 3V3 (36번) 3.3V
KY-018 − GND (38번) 공통 접지
LM393 VCC 3V3 (36번) 3.3V로 켜야 AO가 3.3V를 넘지 않아요
LM393 GND GND (38번) 공통 접지
LM393 AO GP27 (32번) 마이크 파형
LM393 DO 연결 안 함 문턱은 코드로 정해요

이 자료실의 KY-018 글과 LM393 글은 둘 다 GP27을 써요. 두 모듈을 함께 쓰면 아날로그 핀이 겹치니 이 글은 KY-018을 GP26으로 옮겼어요. 두 모듈 모두 3V3에 이어요. 5V로 켜면 S와 AO가 3.3V를 넘을 수 있어요. 시나브로 PICO 작업대에는 아직 Tessel Ambient 부품이 없어서 이 글에는 배선 그림이 없어요.

코드

from machine import ADC, Pin
import time

light_adc = ADC(Pin(26))         # KY-018 S → GP26
mic = ADC(Pin(27))               # LM393 AO → GP27
led = Pin("LED", Pin.OUT)        # 보드 LED로 소리 트리거를 보여 줘요

LIGHT_ON = 0.60                  # 빛 트리거 문턱(0.0~1.0)
SOUND_ON = 0.30                  # 소리 트리거 문턱(0.0~1.0)
GAP = 0.05                       # 다시 준비할 때의 여유(히스테리시스)
HOLD_MS = 300                    # 소리 트리거 뒤 쉬는 시간(ms)

def light_level():
    s = 0
    for i in range(16):
        s += light_adc.read_u16()
    return 1 - s / 16 / 65535    # KY-018은 밝을수록 전압이 내려가서 뒤집어요

def sound_level(ms=50):
    v = mic.read_u16()
    hi = v
    lo = v
    t0 = time.ticks_ms()
    while time.ticks_diff(time.ticks_ms(), t0) < ms:
        v = mic.read_u16()
        hi = max(hi, v)
        lo = min(lo, v)
    return (hi - lo) / 65535     # 50ms 동안의 진폭을 0.0~1.0으로

light = light_level()
light_armed = True
sound_armed = True
last_sound = 0
claps = 0

while True:
    light = light * 0.8 + light_level() * 0.2    # 천천히 따라가게(저역 통과)
    snd = sound_level()
    now = time.ticks_ms()
    if light_armed and light > LIGHT_ON:
        print("light-trigger: 밝아졌어요")
        light_armed = False
    elif not light_armed and light < LIGHT_ON - GAP:
        light_armed = True                     # 충분히 어두워져야 다시 준비
    if sound_armed and snd > SOUND_ON and time.ticks_diff(now, last_sound) > HOLD_MS:
        claps += 1
        last_sound = now
        sound_armed = False
        led.on()
        print(f"sound-trigger: {claps}번째")
    elif not sound_armed and snd < SOUND_ON - GAP:
        sound_armed = True
        led.off()
    print(f"DATA: light={light:.3f}, sound={snd:.3f}, claps={claps}")
    time.sleep(0.1)
  • light는 새 값을 20%씩만 섞어 천천히 따라가요. 형광등 깜빡임이나 손 그림자처럼 짧은 변화에 덜 흔들리게 하려는 거예요.
  • light_armed, sound_armed는 "다시 울릴 준비가 됐나"를 기억하는 깃발이에요. 문턱을 넘어 한 번 울리면 꺼지고, 값이 문턱 − GAP 아래로 내려와야 다시 켜져요.
  • 소리 트리거는 한 번 울린 뒤 0.3초 동안 다시 울리지 않아요. 손뼉 한 번이 두 번으로 세어지는 일을 막아요.

값 해석과 보정

0.0~1.0의 뜻이 Tessel과 달라요. 같은 이름의 숫자라도 부품과 회로가 달라서, 이 글의 light=0.6과 Tessel 모듈의 0.6은 같은 밝기가 아니에요. 다른 장치의 문턱 값을 그대로 옮겨 오지 말고 내 장치에서 다시 정해요. Tessel README도 빛 값이 로그처럼 늘어난다고 적듯, 빛 값은 밝기에 직선으로 늘지 않아요. 밝기 차이가 큰 장소마다 문턱을 따로 정해요.

문턱은 기록으로 정해요. 트리거가 없어야 할 상태(불 끈 방, 조용한 교실)와 있어야 할 상태(불 켬, 2m 앞 손뼉)를 1분씩 기록하고, 문턱을 두 값 사이에 두되 조용할 때의 최댓값에서 충분히 떨어뜨려요. 두 상태의 값이 겹치면 문턱으로는 가를 수 없으니 센서 위치나 평균 시간을 바꿔요.

GAP과 HOLD_MS는 맞바꾸는 값이에요. 크게 하면 잘못 울리는 일은 줄지만 빠르게 이어지는 사건을 놓쳐요. 손뼉을 1초에 두 번 치는 작품이라면 HOLD_MS는 0.5초보다 짧아야 해요.

자주 하는 실수

  1. 두 모듈의 출력을 같은 GP27에 꽂기. 두 신호가 섞여 둘 다 엉뚱한 값이 나와요. 빛은 GP26, 소리는 GP27이에요.
  2. KY-018의 방향을 잊기. 밝을수록 전압이 내려가요. 뒤집지 않으면 "어두워질 때" 트리거가 울려요.
  3. 문턱 하나로만 판단하기. 문턱 근처에서 알림이 연달아 울려요. GAP을 둬요.
  4. Tessel 모듈을 확인 없이 피코에 꽂기. SPI 통신 순서와 ATtiny44 펌웨어까지 맞아야 해요. 시나브로 PICO의 실험용 드라이버는 실물로 확인하지 않았으니, 처음에는 대안 회로(KY-018 + LM393)로 시작해요.
  5. 모듈을 5V로 켜기. S와 AO가 3.3V를 넘을 수 있어요.

이 센서로 잴 수 있는 탐구

1. 손뼉 문턱을 어디에 둘까 - 질문: SOUND_ON을 0.1, 0.2, 0.3, 0.4로 바꾸면 2m 거리 손뼉 10번 중 몇 번을 잡고, 조용한 1분 동안 몇 번 잘못 울릴까? - 바꾸는 것: SOUND_ON(0.1, 0.2, 0.3, 0.4) - 재는 것: 잡은 손뼉 수(10번 중), 1분 동안의 잘못 울림 수(번) - 방법: 같은 사람이 같은 자리에서 3초 간격으로 손뼉을 쳐요. 문턱마다 3번 반복해 잡은 비율과 잘못 울린 수를 함께 그래프로 그려요.

2. 여유(GAP)를 두면 연달아 울리는 일이 줄까 - 질문: GAP을 0, 0.02, 0.05, 0.1로 바꾸면 종이로 센서를 천천히 가렸다 치울 때 light-trigger가 몇 번 울릴까? - 바꾸는 것: GAP - 재는 것: 한 번 가렸다 치울 때 울린 횟수(번) - 방법: 스탠드를 30cm 위에 두고 종이를 5초에 걸쳐 천천히 밀어 넣었다 빼요. 조건마다 10번 반복해요.

3. 사물함 문이 열린 횟수를 빛 트리거로 셀 수 있을까 - 질문: 사물함 안에 둔 장치가 센 light-trigger 수는 실제로 문을 연 횟수와 얼마나 같을까? - 바꾸는 것: 문을 여는 방법(크게 활짝, 반쯤, 아주 조금) - 재는 것: 트리거 수, 직접 센 횟수, 놓친 비율(%) - 방법: 방법마다 20번 열고 닫아요. 사물함 안 밝기를 미리 기록해 문턱을 정하고, 교실 조명을 켠 때와 끈 때를 나눠 재요.

안전

  • 큰 소리를 귀 가까이에서 내지 않고, 밝은 조명을 눈으로 직접 들여다보지 않아요.
  • 이 코드는 소리를 녹음하지 않고 크기 숫자만 남겨요. 교실에서 잴 때는 무엇을 왜 기록하는지 미리 알려요.
  • 전원은 3V3에만 잇고, 배선은 USB를 뽑은 채로 바꿔요.

더 알아보기

글쓴이

홍창민 · 울산 학성고등학교 정보·인공지능 교사. 수업에 쓰는 도구(시나브로 PICO 웹 IDE, 학교 AI 서버 등)를 직접 만들어 운영해요. 소개 보기 · 고칠 곳 알려 주기