목차(클릭하세요)
파이파이키트 자체가 센서, 엑츄에이터가 이미 연결되어 있으므로 핀맵 정보 필요
실시간 정보를 받아 시간대별 센서 정보를 저장하는 작업은 상당한 난이도 필요
1. 센서(Sensor) 핀 연결 정보(4개)
1-1. 터치 센서, 컬러인식 센서, 온습도 센서, 조도센서
센서명 | 센서 종류 | 통신 방식 | GPIO 핀 | 기능 설명 |
터치 센서 1번 | 디지털 입력 | GPIO | D33 (S핀) | 터치 버튼 감지 |
터치 센서 2번 | 디지털 입력 | GPIO | D32 (S핀) | 터치 버튼 감지 |
터치 센서 3번 | 디지털 입력 | GPIO | D35 (S핀) | 터치 버튼 감지 |
터치 센서 4번 | 디지털 입력 | GPIO | D34 (S핀) | 터치 버튼 감지 |
컬러 인식 센서 | TCS34725 | I2C | SDA: D21, SCL: D22 | RGB 색상 측정 |
온습도 센서 | DHT11 | 디지털 | D27 (S핀) | 온도/습도 측정 |
조도센서 | Cds | 아날로그 입력 (ADC) | D4 (S핀) | 빛 밝기 측정
0~4095 (연속값) |
1-2.특이점
•
컬러인식 센서는 I2C통신 방식 사용
•
2개 핀(SDA, SCL)으로 여러 센서 제어 가능
•
SDA (Serial Data): D21
•
SCL (Serial Clock): D22
•
여러 센서를 체인 형태로 연결 가능 (주소 다름)
2. 액추에이터(Actuator) 핀 연결 정보(3개)
액추에이터명 | 액추에이터 종류 | 통신 방식 | GPIO 핀 | 기능 설명 |
네오픽셀 LED 링 | WS2812B | 1-Wire (단일선) | D14 (S핀) | RGB LED 제어 |
OLED 디스플레이 | SSD1306 | I2C | SDA: D21, SCL: D22 | 화면 출력 |
내장ED | 디지털 출력 | GPIO | D2: 2번핀 | 보드 상태 표시 (ON/OFF 제어)로 주로 활용 |
3. 센서&엑추에이터 기본 연결 코드
3-1. 목적
•
터치 센서, 컬러인식 센서, 온습도 센서, 조도센서와 액추에이터(Actuator) 핀이 모두 잘 연결되어 작동하는지 체크하는 일종의 체크코드
"""
파이파이 IoT 보드 - 하드웨어 핀 연결 순차 체크 코드
각 센서와 액추에이터를 단계별로 확인
"""
from machine import Pin, ADC, SoftI2C
from neopixel import NeoPixel
import time
def print_step(step_num, title):
"""단계 제목 출력"""
print("\n" + "=" * 50)
print(f"STEP {step_num}: {title}")
print("=" * 50)
def print_result(name, status, message=""):
"""결과 출력"""
emoji = "✅" if status else "❌"
print(f"{emoji} {name}: {message}")
# ============================================
# STEP 1️⃣: 터치센서 확인 (사용자 입력 대기)
# ============================================
print_step(1, "터치센서 확인")
print("터치센서(1번~4번) 중 하나를 터치하면 다음 단계로 이동합니다.")
print("(최대 10초 대기)")
touch_pins = {
"터치1": Pin(33, Pin.IN),
"터치2": Pin(32, Pin.IN),
"터치3": Pin(35, Pin.IN),
"터치4": Pin(34, Pin.IN),
}
# 터치센서 상태 확인
touch_detected = False
start_time = time.time()
timeout = 10 # 10초 타임아웃
while not touch_detected and (time.time() - start_time) < timeout:
for name, pin in touch_pins.items():
if pin.value() == 1:
print_result(name, True, "터치 감지됨!")
touch_detected = True
break
time.sleep(0.1)
if not touch_detected:
print_result("터치센서", False, "10초 이상 반응 없음 (타임아웃)")
print("⚠️ 터치센서 연결을 확인하고 다시 시도하세요.")
else:
print_result("터치센서", True, "정상 작동")
time.sleep(1)
# ============================================
# STEP 2️⃣: 조도센서 확인
# ============================================
print_step(2, "조도센서 확인")
try:
light_sensor = ADC(Pin(4))
light_sensor.atten(ADC.ATTN_11DB)
# 5번 측정하여 평균값 출력
light_values = []
print("조도값 측정 중... (5회)")
for i in range(5):
value = light_sensor.read()
light_values.append(value)
print(f" 측정 {i+1}: {value}/4095", end="")
# 밝기 상태 표시
if value > 3500:
print(" (매우 밝음 ☀️)")
elif value > 2000:
print(" (밝음 💡)")
elif value > 1000:
print(" (어두움 🌙)")
else:
print(" (매우 어두움 🌑)")
time.sleep(0.3)
avg_light = sum(light_values) // len(light_values)
print_result("조도센서", True, f"평균값: {avg_light}/4095")
time.sleep(1)
except Exception as e:
print_result("조도센서", False, f"오류 - {e}")
# ============================================
# STEP 3️⃣: 컬러인식센서 확인
# ============================================
print_step(3, "컬러인식센서 확인")
try:
# I2C 통신 확인
i2c = SoftI2C(sda=Pin(21), scl=Pin(22))
devices = i2c.scan()
if devices:
print_result("I2C 통신", True, "정상")
# 발견된 디바이스 주소 출력
for device in devices:
device_name = f"TCS34725 (컬러센서)" if device == 0x29 else "기타 I2C 디바이스"
print(f" └─ 발견: 0x{device:02x} ({device_name})")
# TCS34725 라이브러리가 있으면 색상값 출력 시도
try:
from tcs34725 import TCS34725
tcs = TCS34725(i2c)
print("\n컬러값 측정 중... (3회)")
for i in range(3):
rgb = tcs.read('rgb')
r, g, b = rgb[0], rgb[1], rgb[2]
print(f" 측정 {i+1}: R={r}, G={g}, B={b}")
time.sleep(0.5)
print_result("컬러센서", True, "정상 작동 (RGB값 측정 완료)")
except ImportError:
print("⚠️ tcs34725 라이브러리가 없어 상세 측정 불가")
print(" (하지만 I2C 통신은 정상입니다)")
print_result("컬러센서", True, "I2C 연결됨 (라이브러리 필요)")
else:
print_result("컬러센서", False, "I2C에서 디바이스 미발견")
print("⚠️ 케이블 연결을 확인하세요.")
time.sleep(1)
except Exception as e:
print_result("컬러센서", False, f"오류 - {e}")
# ============================================
# STEP 4️⃣: 온습도센서 확인
# ============================================
print_step(4, "온습도센서 I2C 확인")
try:
dht_pin = Pin(27, Pin.IN)
dht_value = dht_pin.value()
if dht_value in [0, 1]:
print_result("온습도센서", True, f"GPIO 응답 정상 (현재값: {dht_value})")
print(" └─ 정확한 온습도 값은 DHT11 라이브러리로 측정 가능")
else:
print_result("온습도센서", False, "GPIO 응답 이상")
time.sleep(1)
except Exception as e:
print_result("온습도센서", False, f"오류 - {e}")
# ============================================
# STEP 5️⃣: 네오픽셀 LED 확인
# ============================================
print_step(5, "네오픽셀 LED 확인")
try:
np = NeoPixel(Pin(14), 12)
# 빨강
print("네오픽셀 색상 변화 중...")
for i in range(12):
np[i] = (255, 0, 0) # 빨강
np.write()
print(" 1️⃣ 빨강 점등 (1초)")
time.sleep(1)
# 초록
for i in range(12):
np[i] = (0, 255, 0) # 초록
np.write()
print(" 2️⃣ 초록 점등 (1초)")
time.sleep(1)
# 파랑
for i in range(12):
np[i] = (0, 0, 255) # 파랑
np.write()
print(" 3️⃣ 파랑 점등 (1초)")
time.sleep(1)
# OFF
for i in range(12):
np[i] = (0, 0, 0) # 검정(OFF)
np.write()
print(" 4️⃣ OFF 상태 (소등)")
print_result("네오픽셀 LED", True, "정상 작동 (RGB 모두 정상)")
time.sleep(1)
except Exception as e:
print_result("네오픽셀 LED", False, f"오류 - {e}")
# ============================================
# STEP 6️⃣: OLED 디스플레이 I2C 확인
# ============================================
print_step(6, "OLED 디스플레이 I2C 확인")
try:
# I2C 재스캔
i2c = SoftI2C(sda=Pin(21), scl=Pin(22))
devices = i2c.scan()
oled_found = False
for device in devices:
# OLED는 일반적으로 0x3c 또는 0x3d 주소
if device in [0x3c, 0x3d]:
print_result("OLED", True, f"I2C 주소 0x{device:02x}에서 발견됨")
oled_found = True
break
if not oled_found:
if devices:
print("⚠️ OLED 미발견 (예상 주소: 0x3c 또는 0x3d)")
print(f" 발견된 I2C 디바이스: {[hex(d) for d in devices]}")
print_result("OLED", False, "예상 주소에서 미발견")
else:
print_result("OLED", False, "I2C 통신 불가")
except Exception as e:
print_result("OLED", False, f"오류 - {e}")
# ============================================
# 🎯 최종 체크 완료
# ============================================
print("\n" + "=" * 50)
print("🎯 모든 단계 완료!")
print("=" * 50)
print("""
✅ 라이브러리 설치가 필요한 센서, 엑츄에이터는 라이브러리를 설치
""")
Python
복사
3-2.조도센서 실습
•
현재 조도센서 값 측정하기
[주의] 파이파이 키트의 조도 센서는 4번 핀에 연결되어 있으며, 아날로그 신호를 읽어오기 위해 ADC 클래스를 사용
ESP32는 기본적으로 1.0V~1.1V까지만 측정할 수 있으므로, 3.3V 전압을 사용하는 센서 값을 제대로 읽으려면 감쇠(Attenuation) 설정을 반드시 해야 함
from machine import Pin, ADC
import time
# 1. 조도 센서 객체 생성 (4번 핀)
# 파이파이 키트의 조도 센서는 GPIO 4번에 연결됨
cds = ADC(Pin(4))
# 2. 감쇠(Attenuation) 설정: 11dB
# ESP32는 기본적으로 0~1.1V까지만 측정 가능
# 3.3V 범위까지 읽기 위해 ATTN_11DB 설정을 사용
cds.atten(ADC.ATTN_11DB)
print(">>> 조도 센서 측정 시작 (Ctrl+C로 종료)")
while True:
# 3. 아날로그 값 읽기 (0 ~ 4095)
# 0에 가까울수록 어둡고, 4095에 가까울수록 밝은 상태(회로 구성에 따라 반대일 수 있음).
val = cds.read()
print(f"현재 밝기 값: {val}")
time.sleep(0.5) # 0.5초 대기
Python
복사
감쇠란?
전압을 깎아서(감쇠시켜서) ADC가 읽을 수 있는 범위로 맞추는 설정
감쇠가 클수록 더 높은 전압을 측정할 수 있지만, 정밀도는 조금 떨어질 수 있음
•
ESP32의 ADC 감쇠 기능은 입력 전압의 측정 범위를 결정
•
왜 필요한가? ESP32의 ADC는 기본적으로 약 1.1V가 넘어가면 최대값(4095)으로 포화(Saturated)되어 측정이 불가능
•
결국 3.3V 센서를 쓰려면 범위를 늘려야 함!
설정값 (상수) | 감쇠율 | 측정 가능 전압 범위 (약) | 용도 |
ADC.ATTN_0DB | 0dB | 0V ~ 1.1V | 매우 낮은 전압의 정밀 측정 |
ADC.ATTN_2_5DB | 2.5dB | 0V ~ 1.5V | 1.5V 이하 센서 |
ADC.ATTN_6DB | 6dB | 0V ~ 2.0V | 2.0V 이하 센서 |
ADC.ATTN_11DB | 11dB | 0V ~ 3.3V (3.6V Max) | 대부분의 3.3V 센서 (조도 등) |
3-3.조도센서 실습 with 감쇠
•
같은 밝기(전압)라도 감쇠 설정에 따라 읽히는 숫자가 달라지거나 최대값(4095)에 막히는 현상을 확인할 수 있음
from machine import Pin, ADC
import time
# 조도 센서 핀 설정
sensor_pin = Pin(4)
adc = ADC(sensor_pin)
# 테스트할 감쇠 설정 리스트
atten_modes = [
(ADC.ATTN_0DB, "0dB (0~1.1V)"),
(ADC.ATTN_2_5DB, "2.5dB (0~1.5V)"),
(ADC.ATTN_6DB, "6dB (0~2.0V)"),
(ADC.ATTN_11DB, "11dB (0~3.3V)")
]
print(">>> ADC 감쇠 설정별 값 비교 테스트 <<<")
while True:
print("-" * 40)
for mode, desc in atten_modes:
# 감쇠 설정 변경
adc.atten(mode)
time.sleep(0.1) # 설정 적용 대기
# 값 읽기
val = adc.read()
# 결과 출력 (4095가 나오면 측정 범위를 초과한 것임)
status = " (최대값 포화!)" if val >= 4095 else ""
print(f"설정: {desc:<15} | 측정값: {val}{status}")
print("-" * 40)
print("센서 밝기를 변화시켜 보기, 탈출은 ctrl+c")
time.sleep(2)
Python
복사
3-4.조도센서 측정값을 시각화 하기
•
Thonny 개발 환경에서 ESP32 자체적으로 matplotlib 라이브러리를 직접 import하여 사용하는 것은 불가능
◦
matplotlib은 컴퓨터(PC)의 운영체제 위에서 돌아가는 무거운 라이브러리이며, 마이크로컨트롤러인 ESP32의 메모리와 연산 능력으로는 구동할 수 없기 때문
[해결책] Thonny의 내장 'Plotter' 기능
•
이를 위해 코드 일부를 수정
[핵심]
•
출력하고 싶은 값들을 튜플 (값1, 값2) 형태로 print() 하면 자동으로 그래프가 그려짐
•
감쇠(Attenuation) 설정에 따른 차이를 한 눈에 보기 위해 4가지 감쇠 설정 값을 한방에 출력
from machine import Pin, ADC
import time
# 조도 센서 (4번 핀)
cds = ADC(Pin(4))
# 감쇠 설정 상수 리스트
attens = [ADC.ATTN_0DB, ADC.ATTN_2_5DB, ADC.ATTN_6DB, ADC.ATTN_11DB]
print("Plotter 시작: 4가지 감쇠 모드 값을 동시에 출력합니다.")
while True:
# values라는 빈 리스트 설정
values = []
# 4가지 감쇠 설정을 순서대로 빠르게 변경하며 측정
for atten_mode in attens:
cds.atten(atten_mode)
time.sleep_ms(20) # 설정 변경 후 안정화 대기
# 측정된 값을 리스트에 추가
values.append(cds.read())
# 튜플 형태로 묶어서 출력 -> Thonny 플로터가 이를 인식해 4개의 선으로 그림
# 출력 형식 예: (150, 400, 1200, 3500)
print(tuple(values))
time.sleep(0.1)
Python
복사
•
시각화 확인하기: 상단 메뉴에서 [보기(View)] -> **[플로터(Plotter)]**를 선택
4.측정된 값 저장하기
4-1. [간편]엑셀 시트에 저장하기
•
가장 간편한 방법
1.
장점: 초간편, 이보다 더 간편한 코드는 없을 듯
from machine import Pin, ADC
import time
# 1. 조도 센서 설정 (4번 핀) [4]
cds = ADC(Pin(4))
cds.atten(ADC.ATTN_11DB) # 3.3V 범위까지 측정
# 2. CSV 파일 저장 함수 정의 [2], [3]
def save_to_csv(filename, data):
print("파일 저장 중...")
# 'w' 모드는 쓰기 전용으로 파일을 엽니다. [5]
with open(filename, 'w') as csvfile:
# 엑셀 헤더(제목) 작성
csvfile.write("Time(s),Light_Value\n")
# 데이터 한 줄씩 작성
for row in data:
# 콤마(,)로 값을 구분하여 저장합니다. [6]
csvfile.write("{},{}\n".format(row, row))
print(f"{filename} 저장 완료!")
# 3. 데이터 수집 시작
data_list = []
print(">>> 측정 시작 (10회 측정)")
for i in range(1, 11): # 10번만 측정 예시
val = cds.read()
print(f"측정 {i}: {val}")
# (시간, 조도값) 형태로 리스트에 추가
data_list.append((i, val))
time.sleep(1)
# 4. 파일로 내보내기
save_to_csv("light_data.csv", data_list)
Python
복사
2.
파일 확인 방법
•
코드가 실행되고 "저장 완료" 메시지가 뜨면,
•
Thonny IDE 왼쪽 파일 탐색기에서 MicroPython 장치 를 새로 고침한 다음 **light_data.csv**를 찾아 다운로드
[csv 결과물 확인]
3.
단점
•
메모리 부족 (Memory Error) 위험: 현재 코드는 측정된 모든 데이터를 data_list라는 리스트 변수에 계속 쌓아두었다가, 마지막에 한 번에 파일로 저장
•
ESP32와 같은 마이크로컨트롤러는 RAM(메모리) 용량이 매우 제한
•
만약 측정 횟수가 10회가 아니라 1,000회, 10,000회로 늘어나면 data_list의 크기가 메모리 한계를 초과하여 MemoryError가 발생하고 보드가 멈출 수 있음
•
문제점: 실제 루프의 실행 시간은 **센서 읽는 시간 + 출력하는 시간 + 리스트에 추가하는 시간 + 1초 대기**가 됨
◦
따라서 실제로는 1.01초, 1.05초 처럼 조금씩 밀리게 되며, 시간이 지날수록 오차(Drift)가 누적되어 정확한 시계열 데이터를 얻기 불가능해짐
◦
해결: time.ticks_ms() 등을 활용하여 경과 시간을 계산해 보정하거나 타이머 인터럽트를 사용
•
실제 시간(Real Time) 기록 불가: 현재 코드는 단순히 i (횟수)를 시간으로 기록
◦
문제점: 데이터가 "언제" 측정되었는지(예: 2023-10-27 14:30:00) 알 수 없음
◦
해결: ESP32는 전원이 꺼지면 시간이 초기화되므로, 정확한 기록을 위해서는 Wi-Fi로 NTP 서버에서 시간을 받아오거나 RTC(Real Time Clock) 모듈을 설정해야 함!
4.
개선된 코드
[주의] 즉시실행방식이 아닌 Main.py형태로 저장하여 실행해야 함!
from machine import Pin, ADC, RTC
import network
import ntptime
import time
# ===== [설정 영역] =====
FILENAME = "smart_log.csv"
SSID = "???"
PASSWORD = " ??? "
MEASUREMENT_TIME = 30 # 측정 시간 (초)
INTERVAL = 1000 # 측정 간격 (밀리초)
FLUSH_RATE = 10 # 10회마다 저장소에 완전 기록
# ===== [WiFi 연결 + NTP 시간 동기화] =====
def setup_time():
"""WiFi 연결 및 NTP 시간 동기화 (UTC+9)"""
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print("[WiFi] 연결 중...")
wlan.connect(SSID, PASSWORD)
timeout = 10
while not wlan.isconnected() and timeout > 0:
time.sleep(0.5)
timeout -= 1
if wlan.isconnected():
print(f"[WiFi] 연결 성공")
try:
# NTP로 UTC 시간 동기화
ntptime.settime()
print("[NTP] UTC 시간 동기화 완료")
# UTC → 한국시간 변환 (UTC+9)
rtc = RTC()
t = rtc.datetime()
# t: (년, 월, 일, 요일, 시, 분, 초, 마이크로초)
year, month, day, weekday, hour, minute, second = t[0], t[1], t[2], t[3], t[4], t[5], t[6]
# 시간에 9시간 추가
hour = (hour + 9) % 24
# 시간이 넘어갔으면 다음날로
if hour < t[4]: # 24시간 초과해서 넘어간 경우
day += 1
# 간단히: 월마다 최대 31일로 가정 (대부분의 경우 충분)
if day > 31:
day = 1
month += 1
if month > 12:
month = 1
year += 1
# RTC에 한국시간 설정
rtc.datetime((year, month, day, weekday, hour, minute, second, 0))
# 확인용 출력
t_korea = rtc.datetime()
print(f"[RTC] 한국시간 설정: {t_korea[0]:04d}-{t_korea[1]:02d}-{t_korea[2]:02d} {t_korea[4]:02d}:{t_korea[5]:02d}:{t_korea[6]:02d}")
return True
except Exception as e:
print(f"[NTP] 동기화 실패: {e}")
return False
else:
print("[WiFi] 연결 실패")
return False
# ===== [CDS 센서 초기화] =====
def init_cds():
"""CDS 센서 초기화"""
print("[센서] CDS 초기화 중...")
# WiFi 연결 해제 (간섭 방지)
wlan = network.WLAN(network.STA_IF)
wlan.active(False)
time.sleep(0.5) # 안정화 대기
# GPIO4를 ADC로 설정
cds = ADC(Pin(4))
cds.atten(ADC.ATTN_11DB) # 3.3V 범위
# 첫 몇 개 읽음 (워밍업)
for _ in range(5):
val = cds.read()
time.sleep(0.1)
print(f"[센서] CDS 초기화 완료 (현재값: {cds.read()})")
return cds
# ===== [메인 측정 루프] =====
def measure_and_log(cds):
"""30초 동안 센서 측정 및 기록"""
rtc = RTC()
# CSV 파일 헤더 확인
try:
with open(FILENAME, 'r') as f:
pass
except OSError:
with open(FILENAME, 'w') as f:
f.write("Date,Time,Light_Value\n")
print(f"\n[측정 시작] {MEASUREMENT_TIME}초간 {INTERVAL}ms 간격으로 센서 측정\n")
# 파일을 append 모드로 열어둠
f = open(FILENAME, 'a')
next_tick = time.ticks_ms()
measurement_count = 0
end_time = time.ticks_add(time.ticks_ms(), MEASUREMENT_TIME * 1000)
try:
while time.ticks_diff(end_time, time.ticks_ms()) > 0:
# 설정 시간이 되었는지 확인 (논블로킹)
if time.ticks_diff(time.ticks_ms(), next_tick) >= 0:
next_tick = time.ticks_add(next_tick, INTERVAL)
# 센서값 읽음
val = cds.read()
# RTC에서 현재 시간 가져옴
t = rtc.datetime()
# t[0]:년, t[1]:월, t[2]:일, t[3]:요일, t[4]:시, t[5]:분, t[6]:초
# 날짜: YYYY-MM-DD
date_str = "{:04d}-{:02d}-{:02d}".format(t[0], t[1], t[2])
# 시간: HHMMSS (숫자 형식, 6자리)
time_num = int("{:02d}{:02d}{:02d}".format(t[4], t[5], t[6]))
# 파일에 기록
f.write(f"{date_str},{time_num},{val}\n")
measurement_count += 1
print(f" [{measurement_count:3d}] {date_str} {time_num:06d} | CDS값: {val}")
# FLUSH_RATE마다 저장
if measurement_count % FLUSH_RATE == 0:
f.flush()
print(f" ↳ {measurement_count}개 데이터 저장 완료")
# 마지막 저장
f.flush()
finally:
f.close()
print(f"\n[완료] 총 {measurement_count}개 데이터 기록\n")
# ===== [메인 실행] =====
if __name__ == "__main__":
if setup_time():
cds = init_cds()
measure_and_log(cds)
print("[프로그램 종료]")
else:
print("[오류] WiFi 연결 실패")
Python
복사
•
코드 포인트3가지
1. time.sleep()을 쓰지 않음: "잠들지 않고 시계만 보면서 기다리기 때문에, 기다리는 동안 버튼을 누르는 것도 다 알 수 있음."
2. f.flush() 사용: "글을 쓰다가 저장을 안 하고 끄면 날아가지만, 이 코드는 10번 쓸 때마다 '저장 버튼'을 눌러주는 것과 같음."
3. f.write() 바로 사용: "머릿속(리스트)에 다 외우고 있다가 한 번에 쓰려면 머리가 아프지만(메모리 부족), 이 코드는 한 줄씩 바로 공책에 적는 방식임."
[값 확인방법] 위와 동일하게 Thonny IDE 왼쪽 파일 탐색기에서 MicroPython 장치 를 새로 고침한 다음 **smart_log.csv**를 찾아 다운로드
이 코드까지 오는데 반나절이상 걸림.. AI가 코드를 아무리 잘 짜도 결국은 디버깅 포인트를 사람이 제대로 설명해주어야 주도적 프로그래밍 가능
4-2. [복잡]구글 시트에 저장하기
•
상당한 준비과정이 필요하므로 다음 장에서 새로 시작하기!








