라즈베리파이4 Safe Shutdown


개요

뷰릭스에서 라즈베리파이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 #auto

safeShutdown.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

광고

댓글 1개

댓글 남기기