Search
Duplicate

[pyphy]3.센서&엑츄에이터 핀맵_cds실습

목차(클릭하세요)
파이파이키트 자체가 센서, 엑츄에이터가 이미 연결되어 있으므로 핀맵 정보 필요 실시간 정보를 받아 시간대별 센서 정보를 저장하는 작업은 상당한 난이도 필요

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통신 방식 사용
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 센서 (조도 등)
ATTN_0DB (기본값) ├─ 장점: 정확도가 가장 높음 ├─ 단점: 0~1.1V 범위만 측정 가능 └─ 용도: 정확한 저전압 센서 측정 (온도센서, 압력센서 등)
ATTN_2_5DB ├─ 장점: 정확도 유지, 약간 더 넓은 범위 ├─ 단점: 0~1.5V 범위 제한 └─ 용도: 중간 전압 센서
ATTN_6DB ├─ 장점: 더 넓은 범위 (0~2.2V) ├─ 단점: 정확도 약간 감소 └─ 용도: 0~2.2V 범위의 센서
ATTN_11DB (권장) ├─ 장점: ESP32의 최대 범위(3.3V) 전체 사용 가능 ├─ 단점: 정확도가 조금 떨어짐 (±12bit 해상도) └─ 용도: 다양한 센서 (조도센서, 가변저항, 배터리 전압 등)

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형태로 저장하여 실행해야 함!
이경우 main.py파일을 저장한 뒤 ESP32본체의 리셋버튼을 한번 꼭 눌러주어야 실행 됨을 잊지말자!
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. [복잡]구글 시트에 저장하기

상당한 준비과정이 필요하므로 다음 장에서 새로 시작하기!