/******************************************************************************* * @file pulse_gen.c * @brief PWM Pulse Generator (Build-safe version) * @author CandyPops Co. / ??? ???? * @version V1.0.1 * @date 2025-10-13 ******************************************************************************/ /******************************************************************************* * [한국어 설명] PWM 펄스 생성기 * * === 현재 상태 === * SKIP_PWM 매크로가 정의되어 있어 PWM 기능이 비활성화됨. * init/start/stop 함수는 모두 Stub(빈 껍데기) 구현으로, * 로그만 출력하고 실제 PWM 동작은 하지 않는다. * 빌드 호환성을 위해 함수 인터페이스만 유지. * * === 실제 구현 (SKIP_PWM 미정의 시) === * nrfx_pwm 드라이버 + app_timer를 사용하여 3핀(P28/P29/P30) PWM 제어. * PWM 인스턴스 0, 16MHz 클럭, TOP=8, 개별 채널 로드 모드. * 6단계 시퀀스로 채널별 듀티 사이클 조절. * app_timer(10ms)로 주기적 반복 재생. * * === 비활성화 사유 === * 현재 프로젝트에서는 피에조 구동을 dr_piezo 드라이버가 담당하므로 * 이 PWM 펄스 생성기는 사용하지 않음. ******************************************************************************/ #define SKIP_PWM // ? ?? ???? PWM ?? ??? #include "pulse_gen.h" #include "debug_print.h" #include "app_timer.h" #include "nrf_log.h" #ifdef SKIP_PWM /* ========================================================================== */ /* PWM 비활성화 모드: Stub 구현 (로그만 출력, 실제 동작 없음) */ /* ========================================================================== */ /* Stub 초기화: 아무 동작 없이 성공 반환 */ ret_code_t pulse_gen_init(void) { DBG_PRINTF("[PWM] skipped init\r\n"); return NRF_SUCCESS; } /* Stub 시작: 아무 동작 없음 */ void pulse_gen_start(void) { DBG_PRINTF("[PWM] skipped start\r\n"); } /* Stub 정지: 아무 동작 없음 */ void pulse_gen_stop(void) { DBG_PRINTF("[PWM] skipped stop\r\n"); } #else /* ==================================================================== */ /* 실제 PWM 구현 코드 (SKIP_PWM 미정의 시에만 컴파일됨) */ /* ========================================================================== */ #include "nrfx_pwm.h" /* --- 설정값 --- */ #define PULSE_PIN_1 28 /* PWM 출력 핀 1 (P0.28) */ #define PULSE_PIN_2 29 /* PWM 출력 핀 2 (P0.29) */ #define PULSE_PIN_3 30 /* PWM 출력 핀 3 (P0.30) */ #define PULSE_DELAY_MS 10 /* 반복 재생 간격 (ms) */ #define PWM_INSTANCE_ID 0 /* PWM 하드웨어 인스턴스 번호 */ #define PWM_TOP_VALUE 8 /* PWM 카운터 최대값 (듀티 사이클 분해능) */ #define SEQUENCE_LENGTH 6 /* PWM 시퀀스 단계 수 */ static nrfx_pwm_t m_pwm = NRFX_PWM_INSTANCE(PWM_INSTANCE_ID); /* PWM 인스턴스 */ APP_TIMER_DEF(m_pulse_delay_timer_id); /* 반복 재생용 앱 타이머 */ static volatile bool m_pulses_running = false; /* 펄스 생성 동작 중 플래그 */ /* PWM 시퀀스 데이터: 6단계에 걸쳐 3채널의 듀티 사이클을 개별 설정 */ static nrf_pwm_values_individual_t m_pulse_seq_values[SEQUENCE_LENGTH]; static nrf_pwm_sequence_t const m_pulse_sequence = { .values.p_individual = m_pulse_seq_values, .length = NRF_PWM_VALUES_LENGTH(m_pulse_seq_values), .repeats = 0, /* 각 단계 반복 없음 */ .end_delay = 0 /* 시퀀스 종료 후 추가 지연 없음 */ }; /* -------------------------------------------------------------------------- */ /* 내부 핸들러 함수 */ /* -------------------------------------------------------------------------- */ /* * PWM 시퀀스 값 초기화 * 6단계에 걸쳐 CH0/CH1/CH2의 듀티 사이클을 교대 설정: * 단계0: CH0=100%, CH1=0, CH2=0 * 단계1: CH0=0, CH1=100%, CH2=0 * 단계2: CH0=100%, CH1=0, CH2=0 * 단계3: CH0=0, CH1=100%, CH2=0 * 단계4: CH0=0, CH1=0, CH2=0 (모두 OFF) * 단계5: CH0=0, CH1=0, CH2=100% */ static void prepare_pulse_sequence(void) { m_pulse_seq_values[0].channel_0 = PWM_TOP_VALUE; m_pulse_seq_values[0].channel_1 = 0; m_pulse_seq_values[0].channel_2 = 0; m_pulse_seq_values[1].channel_0 = 0; m_pulse_seq_values[1].channel_1 = PWM_TOP_VALUE; m_pulse_seq_values[1].channel_2 = 0; m_pulse_seq_values[2].channel_0 = PWM_TOP_VALUE; m_pulse_seq_values[2].channel_1 = 0; m_pulse_seq_values[2].channel_2 = 0; m_pulse_seq_values[3].channel_0 = 0; m_pulse_seq_values[3].channel_1 = PWM_TOP_VALUE; m_pulse_seq_values[3].channel_2 = 0; m_pulse_seq_values[4].channel_0 = 0; m_pulse_seq_values[4].channel_1 = 0; m_pulse_seq_values[4].channel_2 = 0; m_pulse_seq_values[5].channel_0 = 0; m_pulse_seq_values[5].channel_1 = 0; m_pulse_seq_values[5].channel_2 = PWM_TOP_VALUE; } /* 타이머 타임아웃 핸들러: m_pulses_running이면 PWM 시퀀스 1회 재생 후 타이머 재시작 */ static void pulse_delay_timeout_handler(void * p_context) { if (m_pulses_running) { (void)nrfx_pwm_simple_playback(&m_pwm, &m_pulse_sequence, 1, NRFX_PWM_FLAG_STOP); (void)app_timer_start(m_pulse_delay_timer_id, APP_TIMER_TICKS(PULSE_DELAY_MS), NULL); } } /* -------------------------------------------------------------------------- */ /* 공개 함수 */ /* -------------------------------------------------------------------------- */ /* * PWM 초기화: PWM 인스턴스 설정 + 앱 타이머 생성 * PWM: 16MHz 클럭, UP 카운트 모드, TOP=8, 개별 채널 로드 * 타이머: 단발(SINGLE_SHOT) 모드, 10ms 후 시퀀스 재생 */ ret_code_t pulse_gen_init(void) { ret_code_t err_code; prepare_pulse_sequence(); nrfx_pwm_config_t const config = { .output_pins = { PULSE_PIN_1, PULSE_PIN_2, PULSE_PIN_3, NRFX_PWM_PIN_NOT_USED }, .irq_priority = NRFX_PWM_DEFAULT_CONFIG_IRQ_PRIORITY, .base_clock = NRF_PWM_CLK_16MHz, .count_mode = NRF_PWM_MODE_UP, .top_value = PWM_TOP_VALUE, .load_mode = NRF_PWM_LOAD_INDIVIDUAL, .step_mode = NRF_PWM_STEP_AUTO }; err_code = nrfx_pwm_init(&m_pwm, &config, NULL); if (err_code != NRF_SUCCESS) return err_code; err_code = app_timer_create(&m_pulse_delay_timer_id, APP_TIMER_MODE_SINGLE_SHOT, pulse_delay_timeout_handler); return err_code; } /* 펄스 생성 시작: 플래그 설정 후 타임아웃 핸들러를 직접 호출하여 즉시 첫 재생 */ void pulse_gen_start(void) { if (m_pulses_running) return; /* 이미 동작 중이면 무시 */ m_pulses_running = true; pulse_delay_timeout_handler(NULL); /* 첫 번째 재생 즉시 시작 */ } /* 펄스 생성 정지: 플래그 해제 + 타이머 정지 + PWM 즉시 정지 */ void pulse_gen_stop(void) { m_pulses_running = false; app_timer_stop(m_pulse_delay_timer_id); nrfx_pwm_stop(&m_pwm, true); /* true = 즉시 정지 (현재 시퀀스 중단) */ } #endif /* SKIP_PWM */