개요
뷰릭스에서 라즈베리파이4를 구동하고 있는데요.
사용하면서 불편한점을 하나 꼽으라면 전원 관리가 불편하다는 점입니다.
기존에는 전원은 인가된 체로 라즈베리파이 OS만 on/off해서 사용했는데요.
이번에 푸시스위치 하나로 safe shutdown을 포함한 전원관리 기능을 추가해 봤습니다.
제작
일단은 푸시스위치를 통해 릴레이 스위치 제어가 잘되는지 확인해 봤습니다.

좀 더 효과적인 전원관리를 위해 라즈베리파이와 아두이노간의 시리얼 통신을 시도해봤습니다.
아두이노의 TX,RX를 라즈베리파이의 RX,TX에 연결하였습니다.

기존에 사용하지 않는 라즈베리파이 케이스가 있어서 그 안에 부품들을 넣었습니다.

최종 완성된 형태는 다음과 같습니다.


동작 방식
- 라즈베리파이 전원 Off시 푸시스위치 누름 : 라즈베리파이로 바로 전원을 연결합니다.
- 라즈베리파이 전원 On시 푸시스위치 누름 : 라즈베리파이를 shutdown 시킨후 전원을 해제합니다.
- 라즈베리파이에서 시스템 종료시 : 시스템이 종료된 후 전원을 해제합니다.
- 전원 ON / Safe Shutdown 시작 / 전원 OFF 를 수행할때 멜로디로 전원 스위치의 동작을 확인할 수 있습니다.
부품별 동작 흐름
- 푸시스위치 --> 아두이노 : 전원 ON/OFF에 따른 로직을 처리합니다.
- 아두이노 --> 릴레이 스위치 : 라즈베리파이로의 전원 ON/OFF를 수행합니다.
- 아두이노 --> 라즈베리파이 : Shutdown 신호를 전송하여, 시스템을 종료합니다.
- 라즈베리파이 --> 아두이노 : 시스템 종료 신호를 전송하여, 안전하게 전원을 해제합니다.
전원 제어별 멜로디
전원을 넣어주면 경쾌한 마리오 음악이 나옵니다.
전원 종료시 마리오의 언더월드 테마곡이 나옵니다.
Safe Shutdown
푸쉬 스위치를 통해 shutdown 및 전원차단을 수행합니다.
라즈베리파이에서 시스템 종료시 20초 후에 전원을 차단합니다.
아두이노
아두이노에서는 푸시스위치 인식, 라즈베리파이와 통신, 릴레이스위치 제어를 담당합니다.
#include "pitches.h"
// 시리얼 통신용
const String POWER_ON = "Power On";
const String POWER_OFF = "Power Off";
const String SAFE_SHUTDOWN = "Safe Shutdown";
const String SAFE_SHUTDOWN_SIGNAL = "S";
// 핀/포트 정의
int pushSwitchPin = 9;
int relayPin = 2;
int speakerPin = 3;
int serialBaudRate = 9600;
// 상태값
boolean prePushStatus = false;
boolean nowPushStatus = false;
boolean isPowered = false;
// 셧다운 후 파워OFF 시간지연
int shutdownDelay = 15000;
// 악보
int marioMelody[] = {NOTE_E7, NOTE_E7, 0, NOTE_E7, 0, NOTE_C7, NOTE_E7, 0, NOTE_G7, 0, 0, 0, NOTE_G6, 0, 0, 0};
int marioTempo[] = { 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 };
int underworldMelody1[] = { NOTE_C4, NOTE_C5, NOTE_A3, NOTE_A4, NOTE_AS3, NOTE_AS4, 0, 0, NOTE_F3, NOTE_F4, NOTE_D3, NOTE_D4, NOTE_DS3, NOTE_DS4, 0 };
int underworldTempo1[] = { 12, 12, 12, 12, 12, 12, 6, 3, 12, 12, 12, 12, 12, 12, 6 };
int underworldMelody2[] = { NOTE_DS4, NOTE_CS4, NOTE_D4, NOTE_CS4, NOTE_DS4, NOTE_DS4, NOTE_GS3, NOTE_G3, NOTE_CS4, NOTE_C4, NOTE_FS4, NOTE_F4, NOTE_E3, NOTE_AS4, NOTE_A4, NOTE_GS4, NOTE_DS4, NOTE_B3, NOTE_AS3, NOTE_A3, NOTE_GS3, 0, 0, 0 };
int underworldTempo2[] = { 18, 18, 18, 6, 6, 6, 6, 6, 6, 18, 18, 18, 18, 18, 18, 10, 10, 10, 10, 10, 10, 3, 3, 3 };
enum Song { MARIO, UNDERWORLD_1, UNDERWORLD_2 };
// 기타
String inputString;
void setup()
{
//Serial.begin(serialBaudRate); // 시리얼 모니터 입출력
Serial1.begin(serialBaudRate); // 시리얼 HW 입출력
pinMode(pushSwitchPin, INPUT_PULLUP); // 푸시스위치를 입력으로 설정
pinMode(relayPin, OUTPUT); // 릴레이로 출력
pinMode(speakerPin, OUTPUT); // 스피커 출력
powerOn(); // 전원 인입시 자동으로 전원켬
}
void loop()
{
// 푸시스위치 눌리면 true
nowPushStatus = digitalRead(pushSwitchPin)==LOW;
// 푸시스위치 상태 변경시
if (prePushStatus != nowPushStatus) {
// false -> [true -> false] : 푸시스위치를 눌러다가 떼면 전원 제어
if (prePushStatus && !nowPushStatus) {
prePushStatus = false;
nowPushStatus = false;
managePower();
}
else {
prePushStatus = nowPushStatus;
}
}
// 라즈베리파이 입력값 처리
excuteRpiCommand();
// 지연
delay(100);
}
void excuteRpiCommand() {
String msg = readFromRpi();
if (msg == POWER_ON) {
powerOn();
}
else if (msg == POWER_OFF) {
powerOff();
}
else if (msg == SAFE_SHUTDOWN || msg == SAFE_SHUTDOWN_SIGNAL) {
safeShutdown(false);
}
}
void managePower() {
if(isPowered) {
safeShutdown(true);
}
else {
powerOn();
}
}
void powerOn() {
writeToRpi(POWER_ON);
digitalWrite(relayPin, HIGH);
isPowered = true;
play(MARIO);
}
void safeShutdown(boolean isPushSwitch) {
// 푸시스위치로 종료시, 라즈베리파이로 시스템 종료신호 전송
if(isPushSwitch) {
writeToRpi(SAFE_SHUTDOWN);
}
play(UNDERWORLD_1);
delay(shutdownDelay);
powerOff();
}
void powerOff() {
writeToRpi(POWER_OFF);
digitalWrite(relayPin, LOW);
isPowered = false;
play(UNDERWORLD_2);
}
String readFromRpi() {
if (Serial1.available()) {
inputString = Serial1.readString();
inputString.trim();
//Serial.print("[READ]");
//Serial.println(inputString);
return inputString;
}
return "";
}
void writeToRpi(String msg) {
//Serial.println(msg);
Serial1.println(msg);
}
void play(Song song) {
switch (song) {
case MARIO :
for (int thisNote = 0; thisNote < (sizeof(marioMelody) / sizeof(int)); thisNote++) {
play(marioMelody[thisNote], 1000 / marioTempo[thisNote]);
}
break;
case UNDERWORLD_1 :
for (int thisNote = 0; thisNote < (sizeof(underworldMelody1) / sizeof(int)); thisNote++) {
play(underworldMelody1[thisNote], 1000 / underworldTempo1[thisNote]);
}
break;
case UNDERWORLD_2 :
for (int thisNote = 0; thisNote < (sizeof(underworldMelody2) / sizeof(int)); thisNote++) {
play(underworldMelody2[thisNote], 1000 / underworldTempo2[thisNote]);
}
break;
}
}
void play(int note, int noteDuration) {
buzz(speakerPin, note, noteDuration);
delay(noteDuration * 1.30);
buzz(speakerPin, 0, noteDuration);
}
void buzz(int targetPin, long frequency, long length) {
digitalWrite(13, HIGH);
long delayValue = 1000000 / frequency / 2;
long numCycles = frequency * length / 1000;
for (long i = 0; i < numCycles; i++) {
digitalWrite(targetPin, HIGH);
delayMicroseconds(delayValue);
digitalWrite(targetPin, LOW);
delayMicroseconds(delayValue);
}
digitalWrite(13, LOW);
}라즈베리파이 시리얼 통신 설정
라즈베리파이에서는 시리얼 통신을 아두이노로 Shutdown 정보를 전달하거나,
아두이노로부터 Shutdown 시작을 수신하여 시스템 종료를 수행합니다.
// 시리얼 포트 활성화
$ sudo raspi-config --> 5 Interfacing Options --> P6 Seral --> login Shell : No --> serial port enabled : Yes
// 콘솔정보 제거
$ vi /boot/cmdline.txt
console=serial0,115200 --> 제거
// 블루투스 비활성화, UART 활성화
$ vi /boot/config.txt
dtoverlay=pi3-disable-bt --> 추가
enable_uart=1 --> 추가
// 재부팅
$ sudo reboot
// serial0 -> ttyAMA0 링크 확인
$ ls -l /dev/serial*
lrwxrwxrwx 1 root root 7 Jun 13 20:51 /dev/serial0 -> ttyAMA0
lrwxrwxrwx 1 root root 5 Jun 13 20:51 /dev/serial1 -> ttyS0
// 사용가능한 시리얼 확인
$ dmesg | grep tty
[ 0.435731] fe201000.serial: ttyAMA0 at MMIO 0xfe201000 (irq = 34, base_baud = 0) is a PL011 rev2
// 파이썬용 시리얼 라이브러리 설치
$ sudo python -m pip install pyserial
# 라즈베리파이용 SafeShutdown 코드
$ vi /home/pi/kimstar/safeShutdown.py
$ sudo vi /opt/retropie/configs/all/autostart.sh
python /home/pi/kimstar/safeShutdown.py &
emulationstation #auto라즈베리파이용 SafeShutdown 코드
라즈베리파이에서 아두이노와 시리얼 통신을 통해 safe shutdown하는 코드는 파이썬으로 작성했습니다.
시스템 부팅시 safeShutdown.py를 백드라운드로 구동하기 위해 autostart.sh 파엘에 실행명령을 추가하였습니다.
$ vi /home/pi/kimstar/safeShutdown.py
$ sudo vi /opt/retropie/configs/all/autostart.sh
python /home/pi/kimstar/safeShutdown.py &
emulationstation #autosafeShutdown.py 는 다음과 같습니다.
라즈베리파이 시스템 종료를 인식하면 아두이노로 "S" 라는 문자열을 전송하고, 15초 뒤에 아두이노에서 전원을 Off합니다.
#!/usr/bin/python
import serial
import signal
import os
import sys
ser = serial.Serial("/dev/ttyAMA0", 9600)
def safeShutdown(Code, Frame):
ser.write("S\n")
sys.exit(0)
uncatchable = ['SIG_DFL','SIGSTOP','SIGKILL']
for i in [x for x in dir(signal) if x.startswith("SIG")]:
if not i in uncatchable:
signum = getattr(signal,i)
signal.signal(signum,safeShutdown)
while True:
received_data = ser.readline().strip()
if received_data == "Safe Shutdown":
print("Safe Shutdown")
os.system("sudo shutdown now -h")
break참고사항
Mario Melody
Detect Shutdown
UART Communication
- https://wikidocs.net/7974
- https://kdaa.tistory.com/31
- https://www.electronicwings.com/raspberry-pi/raspberry-pi-uart-communication-using-python-and-c






[…] 참고 : http://kimstar.kr/8953/ […]