Scriptone Scriptone

【Rust】Raspberry Pi로 부저 제어하기

개요

트랜지스터, 부저, 택트 스위치를 사용하여 Rust로 부저 울림을 제어하는 방법을 설명합니다.

부품

사용하는 부품은 다음과 같습니다.

  • NPN 트랜지스터 (S8050) * 1
  • 택트 스위치 * 1
  • 1kΩ 저항 * 1
  • 10kΩ 저항 * 2
  • 액티브 부저 * 1
  • 점퍼선 * 9

회로

회로는 Freenove를 확인해 주세요. Raspberry Pi의 GPIO에서 출력할 수 있는 전류가 작기 때문에 큰 전류가 필요한 액티브 부저를 구동시킬 수 없습니다. 따라서 트랜지스터로 전류를 증폭시켜 액티브 부저가 동작할 수 있도록 하는 간단한 회로가 구성되어 있습니다.

코드

cargo로 rppal과 ctrlc를 추가하고 코드를 작성해 주세요.

Doorbell

이것은 버튼을 누르면 잠시 부저가 울리는 코드입니다.

use rppal::gpio::{Gpio, InputPin, OutputPin, Trigger};
use std::error::Error;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;

const BUZZER_PIN: u8 = 17;
const BTN_PIN: u8 = 18;
const POLL_TIMEOUT_MS: u64 = 10;

fn main() -> Result<(), Box<dyn Error>> {
    print_startup_message();
    
    let (mut buzzer_pin, mut btn_pin) = initialize_gpio()?;
    initialize_buzzer(&mut buzzer_pin);
    setup_button_interrupt(&mut btn_pin)?;
    
    let running = setup_signal_handler()?;
    
    println!("버튼 누름을 기다리는 중...");
    
    run_interrupt_loop(&running, &mut buzzer_pin, &mut btn_pin)?;
    
    cleanup(&mut buzzer_pin, &mut btn_pin)?;
    
    Ok(())
}

fn print_startup_message() {
    println!("프로그램을 시작합니다...");
}

fn initialize_gpio() -> Result<(OutputPin, InputPin), Box<dyn Error>> {
    let gpio = Gpio::new()?;
    let buzzer_pin = gpio.get(BUZZER_PIN)?.into_output();
    let btn_pin = gpio.get(BTN_PIN)?.into_input();
    Ok((buzzer_pin, btn_pin))
}

fn initialize_buzzer(buzzer_pin: &mut OutputPin) {
    buzzer_pin.set_low();
}

fn setup_button_interrupt(btn_pin: &mut InputPin) -> Result<(), Box<dyn Error>> {
    btn_pin.set_interrupt(Trigger::Both, None)?;
    Ok(())
}

fn setup_signal_handler() -> Result<Arc<AtomicBool>, Box<dyn Error>> {
    let running = Arc::new(AtomicBool::new(true));
    let r = running.clone();
    ctrlc::set_handler(move || {
        r.store(false, Ordering::SeqCst);
    })?;
    Ok(running)
}

fn run_interrupt_loop(
    running: &Arc<AtomicBool>,
    buzzer_pin: &mut OutputPin,
    btn_pin: &mut InputPin,
) -> Result<(), Box<dyn Error>> {
    while running.load(Ordering::SeqCst) {
        if let Some(_) = btn_pin.poll_interrupt(true, Some(Duration::from_millis(POLL_TIMEOUT_MS)))? {
            handle_button_interrupt(buzzer_pin, btn_pin);
        }
    }
    Ok(())
}

fn handle_button_interrupt(buzzer_pin: &mut OutputPin, btn_pin: &InputPin) {
    if is_button_pressed(btn_pin) {
        turn_on_buzzer(buzzer_pin);
        print_buzzer_on_message();
    } else {
        turn_off_buzzer(buzzer_pin);
        print_buzzer_off_message();
    }
}

fn is_button_pressed(btn_pin: &InputPin) -> bool {
    btn_pin.is_low()
}

fn turn_on_buzzer(buzzer_pin: &mut OutputPin) {
    buzzer_pin.set_high();
}

fn turn_off_buzzer(buzzer_pin: &mut OutputPin) {
    buzzer_pin.set_low();
}

fn print_buzzer_on_message() {
    println!("버튼이 눌려져 부저가 켜졌습니다 >>>");
}

fn print_buzzer_off_message() {
    println!("버튼이 놓여져 부저가 꺼졌습니다 <<<");
}

fn cleanup(buzzer_pin: &mut OutputPin, btn_pin: &mut InputPin) -> Result<(), Box<dyn Error>> {
    println!("프로그램을 종료합니다");
    let _ = btn_pin.clear_interrupt();
    turn_off_buzzer(buzzer_pin);
    Ok(())
}

Alertor

이것은 버튼을 누르고 있는 동안 부저가 계속 울리는 코드입니다.

use rppal::gpio::{Gpio, InputPin, OutputPin};
use std::error::Error;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

const BUZZER_PIN: u8 = 17;
const BTN_PIN: u8 = 18;
const ALERTOR_FREQUENCY: f64 = 220.0; // 220Hz
const ALERTOR_DUTY_CYCLE: f64 = 0.5;  // 50% 듀티 사이클
const LOOP_DELAY_MS: u64 = 10;
const ALERTOR_DURATION_MS: u64 = 100;

fn main() -> Result<(), Box<dyn Error>> {
    print_startup_message();
    
    let (mut buzzer_pin, btn_pin) = initialize_gpio()?;
    initialize_buzzer(&mut buzzer_pin);
    
    let running = setup_signal_handler()?;
    
    println!("버튼 누름을 기다리는 중...");
    
    run_main_loop(&running, &mut buzzer_pin, &btn_pin)?;
    
    cleanup(&mut buzzer_pin)?;
    
    Ok(())
}

fn print_startup_message() {
    println!("프로그램을 시작합니다...");
}

fn initialize_gpio() -> Result<(OutputPin, InputPin), Box<dyn Error>> {
    let gpio = Gpio::new()?;
    let buzzer_pin = gpio.get(BUZZER_PIN)?.into_output();
    let btn_pin = gpio.get(BTN_PIN)?.into_input();
    Ok((buzzer_pin, btn_pin))
}

fn initialize_buzzer(buzzer_pin: &mut OutputPin) {
    buzzer_pin.set_low();
}

fn setup_signal_handler() -> Result<Arc<AtomicBool>, Box<dyn Error>> {
    let running = Arc::new(AtomicBool::new(true));
    let r = running.clone();
    ctrlc::set_handler(move || {
        r.store(false, Ordering::SeqCst);
    })?;
    Ok(running)
}

fn run_main_loop(
    running: &Arc<AtomicBool>,
    buzzer_pin: &mut OutputPin,
    btn_pin: &InputPin,
) -> Result<(), Box<dyn Error>> {
    while running.load(Ordering::SeqCst) {
        if is_button_pressed(btn_pin) {
            play_alertor_sound(buzzer_pin)?;
            print_alertor_on_message();
        } else {
            stop_alertor_sound(buzzer_pin)?;
            print_alertor_off_message();
        }
        
        thread::sleep(Duration::from_millis(LOOP_DELAY_MS));
    }
    Ok(())
}

fn is_button_pressed(btn_pin: &InputPin) -> bool {
    btn_pin.is_low()
}

fn play_alertor_sound(buzzer_pin: &mut OutputPin) -> Result<(), Box<dyn Error>> {
    buzzer_pin.set_pwm_frequency(ALERTOR_FREQUENCY, ALERTOR_DUTY_CYCLE)?;
    thread::sleep(Duration::from_millis(ALERTOR_DURATION_MS));
    Ok(())
}

fn stop_alertor_sound(buzzer_pin: &mut OutputPin) -> Result<(), Box<dyn Error>> {
    buzzer_pin.clear_pwm()?;
    buzzer_pin.set_low();
    Ok(())
}

fn print_alertor_on_message() {
    println!("경보가 켜졌습니다 >>> ");
}

fn print_alertor_off_message() {
    println!("경보가 꺼졌습니다 <<<");
}

fn cleanup(buzzer_pin: &mut OutputPin) -> Result<(), Box<dyn Error>> {
    println!("프로그램을 종료합니다");
    stop_alertor_sound(buzzer_pin)?;
    Ok(())
}

동작 모습

요약

트랜지스터의 사용법을 확인하면서 Rust로 부저의 울림 방식을 제어하는 방법을 배울 수 있었습니다. 회로 자체가 간단하므로 트랜지스터로 전류를 증폭시켜 부저가 동작하는 것을 확인할 수 있었습니다. 전원이 2계통 필요하지만 Raspberry Pi라면 간단히 공급할 수 있고 트랜지스터 학습을 간단하게 할 수 있습니다. 앞으로도 임베디드와 회로를 함께 정리해서 계속 배워나가고 싶습니다.

관련 글