/******************************************************************************* * @file dr_piezo.c * @brief Piezo Transducer Driver (2MHz Signal Generator) * @author Charles KWON * @date 2025-12-09 * * @details Uses Timer2 + GPIOTE + PPI for CPU-free 2MHz waveform generation * * Timing Diagram (???): * * |<----------- PE HIGH ----------->| * PE ___/?????????????????????????????????\___ * P_OUT ___/?\_/?\_/?\_/?\_/?\________________\___ * N_OUT ___\_/?\_/?\_/?\_/?\_/________________\___ * DMP _________________________/?????\_________ * |<-- 3~5 cycles -->| || * * P_OUT? N_OUT? ?? ?? ?? (???) * * 2MHz = 500ns period = 250ns half-period * Timer @ 16MHz: 1 tick = 62.5ns * Half-period = 250ns = 4 ticks * Full-period = 500ns = 8 ticks ******************************************************************************/ /******************************************************************************* * [한국어 설명] 피에조 초음파 트랜스듀서 드라이버 * * === 개요 === * 방광 측정용 2MHz 초음파 송신 신호를 생성하는 드라이버. * nRF52840의 하드웨어 주변장치(Timer2 + GPIOTE + PPI)를 활용하여 * CPU 개입 없이 정밀한 2MHz 파형을 자동으로 생성한다. * * === 초음파 TX 시퀀스 === * 1단계: PE(Pulse Enable) = HIGH -> MOSFET 드라이버 활성화 * 2단계: P_OUT/N_OUT 교번 펄스 생성 (2MHz, 3~7 사이클) * - P_OUT과 N_OUT은 역상(반대 위상)으로 동작 * - 피에조 소자 양단에 +/-20V 교번 전압 인가 * 3단계: DMP(Dump) = HIGH -> 펄스 완료 후 피에조에 남은 잔류 에너지 방전 * 4단계: DMP = LOW -> 방전 완료 * 5단계: PE = LOW -> MOSFET 드라이버 비활성화, 유휴 상태 복귀 * * === 하드웨어 아키텍처 === * [Timer2] --- CC[0](반주기=4틱) ---> [PPI CH8,9] ---> [GPIOTE CH4,5] -> P_OUT/N_OUT 토글 * |-- CC[1](전체주기=8틱) --> [PPI CH10,11] --> [GPIOTE CH4,5] -> P_OUT/N_OUT 토글 * |-- CC[2](전체주기=8틱) --> 타이머 자동 클리어 + 인터럽트(잔여 사이클 카운트) * * - Timer2: 16MHz 클럭, 16비트 모드 * - CC[0] = 4틱(250ns) -> 반주기 시점에서 P_OUT/N_OUT 토글 * - CC[1] = 8틱(500ns) -> 전체주기 시점에서 P_OUT/N_OUT 토글 * - CC[2] = 8틱(500ns) -> 인터럽트 발생 + 타이머 자동 클리어(SHORT) * * - GPIOTE CH4: P_OUT 핀 토글 모드 * - GPIOTE CH5: N_OUT 핀 토글 모드 * * - PPI CH8: CC[0] 이벤트 -> P_OUT 토글 태스크 * - PPI CH9: CC[0] 이벤트 -> N_OUT 토글 태스크 * - PPI CH10: CC[1] 이벤트 -> P_OUT 토글 태스크 * - PPI CH11: CC[1] 이벤트 -> N_OUT 토글 태스크 * * === 전원 === * DR_PIEZO_PWR_EN(P1.9) -> DC/DC 컨버터 활성화 -> +/-20V 고전압 생성 * * === 소프트웨어 버스트 모드 (dr_piezo_burst_sw 계열) === * Timer/PPI 대신 CPU에서 직접 GPIO를 제어하는 방식. * 인터럽트를 비활성화(__disable_irq)하고 NOP 명령어로 정밀 타이밍 생성. * 포트 레지스터(NRF_P1->OUT)에 직접 접근하여 여러 핀을 동시에 제어. * 주파수별(1.7/1.8/1.9/2.0/2.1/2.2 MHz) NOP 개수가 다름. * * NOP 타이밍 계산법: * CPU 클럭 64MHz -> 1 NOP = 15.625ns * 목표 반주기(ns) = 1,000,000 / (목표주파수MHz * 2) * 필요 NOP 수 = (목표 반주기 - 레지스터 쓰기 시간(~30ns)) / 15.625 * 루프 오버헤드(~47ns = 3 NOP)는 두 번째 반주기에서 차감 ******************************************************************************/ /* 헤더 포함 */ #include "dr_piezo.h" #include "nrf_gpio.h" /* GPIO 제어 (핀 설정, 출력) */ #include "nrf_timer.h" /* 타이머 주변장치 제어 */ #include "nrf_gpiote.h" /* GPIOTE (GPIO Tasks and Events) 제어 */ #include "nrf_ppi.h" /* PPI (Programmable Peripheral Interconnect) 제어 */ #include "nrf_delay.h" /* 지연(딜레이) 함수 */ #include "power_control.h" /* 전원 관리 */ #include "app_util_platform.h" /* 인터럽트 우선순위 등 플랫폼 유틸리티 */ /* 조건부 디버그 출력: FEATURE_PRINTF 정의 시 SEGGER RTT로 출력 */ #ifdef FEATURE_PRINTF #include "debug_print.h" #else #define DBG_PRINTF(...) #endif /*============================================================================== * 하드웨어 리소스 할당 * - Timer2: 2MHz 파형 생성의 시간 기준 (16MHz 클럭) * - GPIOTE CH4/5: P_OUT/N_OUT 핀의 하드웨어 토글 * - PPI CH8~11: 타이머 비교 이벤트 -> GPIOTE 토글 태스크 자동 연결 *============================================================================*/ #define PIEZO_TIMER NRF_TIMER2 /* 사용할 타이머 인스턴스 */ #define PIEZO_TIMER_IRQn TIMER2_IRQn /* 타이머2 인터럽트 번호 */ #define PIEZO_TIMER_IRQ_PRIORITY 6 /* 인터럽트 우선순위 (6 = 중간) */ /* GPIOTE 채널 할당 - 핀 토글 제어용 */ #define GPIOTE_CH_P_OUT 4 /* P_OUT(양극 출력) 토글용 GPIOTE 채널 */ #define GPIOTE_CH_N_OUT 5 /* N_OUT(음극 출력) 토글용 GPIOTE 채널 */ /* PPI 채널 할당 - 타이머 이벤트 -> GPIOTE 태스크 연결 */ #define PPI_CH_P_OUT_TOGGLE_0 8 /* CC[0](반주기) -> P_OUT 토글 */ #define PPI_CH_N_OUT_TOGGLE_0 9 /* CC[0](반주기) -> N_OUT 토글 */ #define PPI_CH_P_OUT_TOGGLE_1 10 /* CC[1](전체주기) -> P_OUT 토글 */ #define PPI_CH_N_OUT_TOGGLE_1 11 /* CC[1](전체주기) -> N_OUT 토글 */ /*============================================================================== * 타이밍 상수 * Timer2 클럭: 16MHz -> 1틱 = 62.5ns * 2MHz 신호: 주기 500ns(8틱), 반주기 250ns(4틱) *============================================================================*/ #define TIMER_FREQ_MHZ 16 /* 타이머 클럭 주파수 (MHz) */ #define TICK_NS (1000 / TIMER_FREQ_MHZ) /* 1틱 = 62.5ns */ /* 2MHz 기준 타이밍: 주기 = 500ns, 반주기 = 250ns */ #define PERIOD_TICKS_2MHZ 8 /* 전체 주기: 500ns / 62.5ns = 8틱 */ #define HALF_PERIOD_TICKS 4 /* 반주기: 250ns / 62.5ns = 4틱 */ /*============================================================================== * 피에조 동작 주파수 설정 *============================================================================*/ /* * 목표 피에조 주파수: 2.1 MHz (하드웨어 버스트 모드용) * * 소프트웨어 버스트 모드의 타이밍은 각 주파수별 함수에 NOP 개수로 하드코딩됨. * 안정적인 파형 생성을 위해 컴파일 타임에 고정. * * NOP 기반 타이밍 계산 (CPU 64MHz, 1 NOP = 15.625ns): * 1.7 MHz: 반주기 294ns -> 첫 반주기 18 NOP, 둘째 반주기 10 NOP + 루프 오버헤드 * 1.8 MHz: 반주기 278ns -> 첫 반주기 17 NOP, 둘째 반주기 11 NOP + 루프 오버헤드 * 1.9 MHz: 반주기 263ns -> 첫 반주기 15 NOP, 둘째 반주기 9 NOP + 루프 오버헤드 * 2.0 MHz: 반주기 250ns -> 첫 반주기 15 NOP, 둘째 반주기 10 NOP + 루프 오버헤드 * 2.1 MHz: 반주기 238ns -> 첫 반주기 14 NOP, 둘째 반주기 9 NOP + 루프 오버헤드 * 2.2 MHz: 반주기 227ns -> 첫 반주기 13 NOP, 둘째 반주기 8 NOP + 루프 오버헤드 * * 주파수를 변경하려면 dr_piezo_burst_sw_XXmhz() 함수의 NOP 수를 수정할 것. */ #define PIEZO_FREQ_MHZ 2.1f /* 기본 동작 주파수 (MHz) */ /*============================================================================== * 정적 변수 *============================================================================*/ static volatile bool m_tx_active = false; /* TX 송신 중 플래그 (인터럽트에서 변경) */ static volatile uint8_t m_remaining_cycles = 0; /* 남은 펄스 사이클 수 (인터럽트에서 감소) */ static uint32_t m_period_ticks = PERIOD_TICKS_2MHZ; /* 현재 주기 (타이머 틱 단위) */ static bool m_power_enabled = false; /* DC/DC 컨버터 전원 상태 */ static bool m_initialized = false; /* 드라이버 초기화 완료 여부 */ /*============================================================================== * 타이머2 인터럽트 핸들러 * 매 주기(CC[2])마다 호출되어 잔여 사이클을 감소시킨다. * 잔여 사이클이 0이 되면: * 1) 타이머 정지 및 클리어 * 2) GPIOTE 비활성화 (P_OUT/N_OUT 토글 중단) * 3) GPIO를 출력 모드로 재설정 후 LOW로 초기화 * 4) DMP 펄스 발생 (피에조 잔류 에너지 방전) * 5) PE = LOW (MOSFET 드라이버 비활성화) *============================================================================*/ void TIMER2_IRQHandler(void) { if (nrf_timer_event_check(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE2)) { nrf_timer_event_clear(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE2); if (m_remaining_cycles > 0) { m_remaining_cycles--; } if (m_remaining_cycles == 0) { /* Step 1: Stop timer */ nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_STOP); nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_CLEAR); /* Step 2: Disable GPIOTE (GPIO ??? ??) */ nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); /* Step 3: GPIO? ?? ? idle ?? */ nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT); nrf_gpio_pin_clear(DR_PIEZO_PIN_P_OUT); nrf_gpio_pin_clear(DR_PIEZO_PIN_N_OUT); /* Step 4: DMP pulse */ nrf_gpio_pin_set(DR_PIEZO_PIN_DMP); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); nrf_gpio_pin_clear(DR_PIEZO_PIN_DMP); /* Step 5: PE = LOW */ nrf_gpio_pin_clear(DR_PIEZO_PIN_PE); m_tx_active = false; } } } /*============================================================================== * 전원 제어 함수 * DC/DC 컨버터(±20V)를 ON/OFF하여 피에조 구동 전압을 제어한다. * 전원 안정화에 약 10ms 필요. *============================================================================*/ /* 피에조 전원 ON: DC/DC 컨버터 활성화 → ±20V 생성 */ void dr_piezo_power_on(void) { nrf_delay_ms(20); nrf_gpio_cfg_output(DR_PIEZO_PWR_EN); nrf_gpio_pin_set(DR_PIEZO_PWR_EN); /* Wait for power stabilization */ nrf_delay_ms(10); m_power_enabled = true; DBG_PRINTF("[PIEZO] Power ON: +/-20V ready\r\n"); } /* 피에조 전원 OFF: TX 비활성화 → MUX 비활성화 → DC/DC 컨버터 차단 */ void dr_piezo_power_off(void) { dr_piezo_disable(); /* MUX enable 핀 클리어: select_channel() 이후 HIGH로 남은 핀 해제 */ nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1); nrf_gpio_pin_clear(DR_PIEZO_PWR_EN); m_power_enabled = false; DBG_PRINTF("[PIEZO] Power OFF\r\n"); } /* 피에조 전원 상태 확인 */ bool dr_piezo_is_power_on(void) { return m_power_enabled; } /*============================================================================== * 내부(private) 초기화 함수 *============================================================================*/ /* GPIO 초기화: 모든 신호 핀을 출력 모드로 설정하고 LOW(유휴)로 초기화 */ static void dr_piezo_gpio_init(void) { nrf_gpio_cfg_output(DR_PIEZO_PIN_PE); nrf_gpio_cfg_output(DR_PIEZO_PIN_DMP); nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT); /* All LOW = idle */ nrf_gpio_pin_clear(DR_PIEZO_PIN_PE); nrf_gpio_pin_clear(DR_PIEZO_PIN_DMP); nrf_gpio_pin_clear(DR_PIEZO_PIN_P_OUT); nrf_gpio_pin_clear(DR_PIEZO_PIN_N_OUT); /* Initialize MUX control pins */ dr_piezo_mux_init(); DBG_PRINTF("[PIEZO] GPIO init done\r\n"); } /* GPIOTE 초기화: P_OUT/N_OUT을 토글 모드로 설정 (PPI 연결 대상) */ static void dr_piezo_gpiote_init(void) { /* P_OUT: Toggle mode, initial LOW */ nrf_gpiote_task_configure( GPIOTE_CH_P_OUT, DR_PIEZO_PIN_P_OUT, NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_LOW ); nrf_gpiote_task_enable(GPIOTE_CH_P_OUT); /* N_OUT: Toggle mode, initial LOW */ nrf_gpiote_task_configure( GPIOTE_CH_N_OUT, DR_PIEZO_PIN_N_OUT, NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_LOW ); nrf_gpiote_task_enable(GPIOTE_CH_N_OUT); DBG_PRINTF("[PIEZO] GPIOTE init done\r\n"); } /* * 타이머 초기화: 16MHz 클럭, 16비트 모드 * CC[0]=반주기(4틱): 반주기 시점 토글 이벤트 * CC[1]=전체주기(8틱): 전체주기 시점 토글 이벤트 * CC[2]=전체주기(8틱): 인터럽트 발생 + 타이머 자동 클리어(SHORT) */ static void dr_piezo_timer_init(void) { nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_STOP); nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_CLEAR); nrf_timer_mode_set(PIEZO_TIMER, NRF_TIMER_MODE_TIMER); nrf_timer_bit_width_set(PIEZO_TIMER, NRF_TIMER_BIT_WIDTH_16); nrf_timer_frequency_set(PIEZO_TIMER, NRF_TIMER_FREQ_16MHz); /* * ??? ??? ?? ???: * - CC[0] = 4 (half period): P_OUT ??, N_OUT ?? * - CC[1] = 8 (full period): P_OUT ??, N_OUT ?? * - CC[2] = 8 (full period): ??? ??? + CLEAR * * ?? ??: P=HIGH, N=LOW * t=4: ? ? ?? -> P=LOW, N=HIGH * t=8: ? ? ?? -> P=HIGH, N=LOW (+ CLEAR) * t=12: ? ? ?? -> P=LOW, N=HIGH * ... */ /* CC[0]: ??? ?? */ nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL0, HALF_PERIOD_TICKS); // 4 /* CC[1]: ? ?? ?? */ nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL1, m_period_ticks); // 8 /* CC[2]: ? ?? - ??? ???? */ nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL2, m_period_ticks); // 8 /* CC[2]?? ?? CLEAR */ nrf_timer_shorts_enable(PIEZO_TIMER, NRF_TIMER_SHORT_COMPARE2_CLEAR_MASK); /* CC[2] ???? (??? ???) */ nrf_timer_int_enable(PIEZO_TIMER, NRF_TIMER_INT_COMPARE2_MASK); NVIC_SetPriority(PIEZO_TIMER_IRQn, PIEZO_TIMER_IRQ_PRIORITY); NVIC_EnableIRQ(PIEZO_TIMER_IRQn); DBG_PRINTF("[DR_PIEZO] Timer init done (period=%d ticks)\r\n", m_period_ticks); } /* * PPI 초기화: 타이머 비교 이벤트 → GPIOTE 토글 태스크 연결 (4채널) * CC[0] 이벤트(반주기) → P_OUT 토글 + N_OUT 토글 * CC[1] 이벤트(전체주기) → P_OUT 토글 + N_OUT 토글 * 이로써 P_OUT과 N_OUT은 항상 역상으로 동작함. */ static void dr_piezo_ppi_init(void) { /* * ??? ??: * CC[0] (t=4): P_OUT ??, N_OUT ?? * CC[1] (t=8): P_OUT ??, N_OUT ?? */ /* CC[0] -> P_OUT toggle */ nrf_ppi_channel_endpoint_setup( (nrf_ppi_channel_t)PPI_CH_P_OUT_TOGGLE_0, (uint32_t)nrf_timer_event_address_get(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE0), (uint32_t)&NRF_GPIOTE->TASKS_OUT[GPIOTE_CH_P_OUT] ); nrf_ppi_channel_enable((nrf_ppi_channel_t)PPI_CH_P_OUT_TOGGLE_0); /* CC[0] -> N_OUT toggle */ nrf_ppi_channel_endpoint_setup( (nrf_ppi_channel_t)PPI_CH_N_OUT_TOGGLE_0, (uint32_t)nrf_timer_event_address_get(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE0), (uint32_t)&NRF_GPIOTE->TASKS_OUT[GPIOTE_CH_N_OUT] ); nrf_ppi_channel_enable((nrf_ppi_channel_t)PPI_CH_N_OUT_TOGGLE_0); /* CC[1] -> P_OUT toggle */ nrf_ppi_channel_endpoint_setup( (nrf_ppi_channel_t)PPI_CH_P_OUT_TOGGLE_1, (uint32_t)nrf_timer_event_address_get(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE1), (uint32_t)&NRF_GPIOTE->TASKS_OUT[GPIOTE_CH_P_OUT] ); nrf_ppi_channel_enable((nrf_ppi_channel_t)PPI_CH_P_OUT_TOGGLE_1); /* CC[1] -> N_OUT toggle */ nrf_ppi_channel_endpoint_setup( (nrf_ppi_channel_t)PPI_CH_N_OUT_TOGGLE_1, (uint32_t)nrf_timer_event_address_get(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE1), (uint32_t)&NRF_GPIOTE->TASKS_OUT[GPIOTE_CH_N_OUT] ); nrf_ppi_channel_enable((nrf_ppi_channel_t)PPI_CH_N_OUT_TOGGLE_1); DBG_PRINTF("[DR_PIEZO] PPI init done (4 channels)\r\n"); } /*============================================================================== * TX 드라이버 공개 함수 *============================================================================*/ /* TX 드라이버 초기화: GPIO → GPIOTE → Timer → PPI 순서로 설정 */ void dr_piezo_init(void) { DBG_PRINTF("[DR_PIEZO] Initializing TX driver...\r\n"); dr_piezo_gpio_init(); dr_piezo_gpiote_init(); dr_piezo_timer_init(); dr_piezo_ppi_init(); m_tx_active = false; m_remaining_cycles = 0; m_initialized = true; DBG_PRINTF("[DR_PIEZO] TX driver ready (2MHz)\r\n"); } /* TX 드라이버 해제: 타이머 정지, PPI/GPIOTE 비활성화, 모든 핀 LOW */ void dr_piezo_uninit(void) { nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_STOP); nrf_timer_int_disable(PIEZO_TIMER, NRF_TIMER_INT_COMPARE2_MASK); NVIC_DisableIRQ(PIEZO_TIMER_IRQn); nrf_ppi_channel_disable((nrf_ppi_channel_t)PPI_CH_P_OUT_TOGGLE_0); nrf_ppi_channel_disable((nrf_ppi_channel_t)PPI_CH_N_OUT_TOGGLE_0); nrf_ppi_channel_disable((nrf_ppi_channel_t)PPI_CH_P_OUT_TOGGLE_1); nrf_ppi_channel_disable((nrf_ppi_channel_t)PPI_CH_N_OUT_TOGGLE_1); nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); nrf_gpio_pin_clear(DR_PIEZO_PIN_PE); nrf_gpio_pin_clear(DR_PIEZO_PIN_DMP); nrf_gpio_pin_clear(DR_PIEZO_PIN_P_OUT); nrf_gpio_pin_clear(DR_PIEZO_PIN_N_OUT); m_tx_active = false; m_initialized = false; DBG_PRINTF("[DR_PIEZO] TX driver uninitialized\r\n"); } /* * 하드웨어 기반 버스트 송신 (Timer + PPI + GPIOTE 사용) * 1) GPIOTE를 재설정: P_OUT=HIGH 시작, N_OUT=LOW 시작 (역상) * 2) PE = HIGH → MOSFET 드라이버 활성화 * 3) 타이머 시작 → PPI가 자동으로 P_OUT/N_OUT 토글 * 4) 인터럽트 핸들러에서 잔여 사이클 관리 및 종료 처리 */ void dr_piezo_burst(uint8_t cycles) { if (m_tx_active) { DBG_PRINTF("[DR_PIEZO] TX busy!\r\n"); return; } if (!m_initialized) { DBG_PRINTF("[DR_PIEZO] ERROR: Not initialized!\r\n"); return; } if (cycles < DR_PIEZO_MIN_CYCLES) cycles = DR_PIEZO_MIN_CYCLES; if (cycles > DR_PIEZO_MAX_CYCLES) cycles = DR_PIEZO_MAX_CYCLES; DBG_PRINTF("[DR_PIEZO] Burst: %d cycles\r\n", cycles); m_remaining_cycles = cycles; m_tx_active = true; /* GPIOTE ??? */ nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); nrf_gpiote_task_configure( GPIOTE_CH_P_OUT, DR_PIEZO_PIN_P_OUT, NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_HIGH /* P_OUT starts HIGH */ ); nrf_gpiote_task_enable(GPIOTE_CH_P_OUT); nrf_gpiote_task_configure( GPIOTE_CH_N_OUT, DR_PIEZO_PIN_N_OUT, NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_LOW /* N_OUT starts LOW */ ); nrf_gpiote_task_enable(GPIOTE_CH_N_OUT); /* DMP = LOW */ nrf_gpio_pin_clear(DR_PIEZO_PIN_DMP); /* Timer clear */ nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_CLEAR); /* Clear pending events */ nrf_timer_event_clear(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE0); nrf_timer_event_clear(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE1); nrf_timer_event_clear(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE2); /* PE = HIGH (??? ??) */ nrf_gpio_pin_set(DR_PIEZO_PIN_PE); /* ??? ?? ? ??? ?? */ __NOP(); __NOP(); __NOP(); __NOP(); /* Timer START -> PPI? ???? P_OUT/N_OUT ?? */ nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_START); } /* 기본 사이클 수(5)로 버스트 송신 */ void dr_piezo_pulse(void) { dr_piezo_burst(DR_PIEZO_DEFAULT_CYCLES); } /* TX 출력 활성화: 수동으로 초기 상태 설정 (PE=HIGH, P_OUT=HIGH, N_OUT=LOW) */ void dr_piezo_enable(void) { nrf_gpio_pin_set(DR_PIEZO_PIN_P_OUT); nrf_gpio_pin_clear(DR_PIEZO_PIN_N_OUT); nrf_gpio_pin_clear(DR_PIEZO_PIN_DMP); nrf_gpio_pin_set(DR_PIEZO_PIN_PE); DBG_PRINTF("[DR_PIEZO] TX enabled\r\n"); } /* TX 출력 비활성화: 모든 신호 핀 LOW로 복귀 (유휴 상태) */ void dr_piezo_disable(void) { nrf_gpio_pin_clear(DR_PIEZO_PIN_DMP); nrf_gpio_pin_clear(DR_PIEZO_PIN_PE); nrf_gpio_pin_clear(DR_PIEZO_PIN_P_OUT); nrf_gpio_pin_clear(DR_PIEZO_PIN_N_OUT); DBG_PRINTF("[DR_PIEZO] TX disabled\r\n"); } /* TX 송신 중 여부 확인 (인터럽트 핸들러에서 false로 전환) */ bool dr_piezo_is_busy(void) { return m_tx_active; } /* 동작 주파수 변경 (100kHz~4MHz): 타이머 CC 레지스터 재설정 */ void dr_piezo_set_frequency(uint32_t freq_hz) { if (freq_hz < 100000 || freq_hz > 4000000) { DBG_PRINTF("[DR_PIEZO] Invalid freq: %d Hz\r\n", freq_hz); return; } uint32_t period_ns = 1000000000UL / freq_hz; m_period_ticks = period_ns * TIMER_FREQ_MHZ / 1000; nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL0, m_period_ticks / 2); nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL1, m_period_ticks); nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL2, m_period_ticks); DBG_PRINTF("[DR_PIEZO] Freq: %d Hz (period=%d ticks)\r\n", freq_hz, m_period_ticks); } /*============================================================================== * MUX 제어 함수 * 8채널 아날로그 MUX로 피에조 에코 신호 경로를 선택한다. * MUXA: CH0~CH3 담당, MUXB: CH4~CH7 담당 * SEL0, SEL1: MUX 내부 채널 주소 선택 * High Drive(H0H1) 모드: MUX IC의 빠른 스위칭을 위해 강한 출력 구동력 사용 *============================================================================*/ /* MUX 제어 핀 초기화: 4개 핀 모두 High Drive 출력으로 설정, 기본값 LOW */ void dr_piezo_mux_init(void) { /* Configure MUX control pins as outputs */ /*nrf_gpio_cfg_output(DR_PIEZO_MUX_SEL1); nrf_gpio_cfg_output(DR_PIEZO_MUX_SEL2); nrf_gpio_cfg_output(DR_PIEZO_MUX_SEL3);*/ /*nrf_gpio_cfg_output(DR_PIEZO_EN_MUXA); nrf_gpio_cfg_output(DR_PIEZO_EN_MUXB); nrf_gpio_cfg_output(DR_PIEZO_MUX_SEL0); nrf_gpio_cfg_output(DR_PIEZO_MUX_SEL1);*/ /* Configure pins as output with high drive strength */ nrf_gpio_cfg( DR_PIEZO_MUX_SEL0, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, /* High drive */ NRF_GPIO_PIN_NOSENSE ); nrf_gpio_cfg( DR_PIEZO_MUX_SEL1, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, /* High drive */ NRF_GPIO_PIN_NOSENSE ); /* GPIO PIN Setting jhChun 0129 */ nrf_gpio_cfg( DR_PIEZO_EN_MUXA, // PIN NRF_GPIO_PIN_DIR_OUTPUT, // DIR : OUTPUT NRF_GPIO_PIN_INPUT_DISCONNECT, // INPUT BUFFER X (DIR : OUTPUT) NRF_GPIO_PIN_NOPULL, // PULL UP, PULL DOWN X NRF_GPIO_PIN_H0H1, // HIGH DRIVE(STRONG OUTPUT) !! NRF_GPIO_PIN_NOSENSE // INTERRUPT X ); nrf_gpio_cfg( DR_PIEZO_EN_MUXB, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE ); /* Set MUX selection: P1.13=HIGH, P1.12=LOW, P1.11=LOW */ //nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1); /* P1.13 = HIGH */ //nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL2); /* P1.12 = LOW */ //nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL3); /* P1.11 = LOW */ nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); /* P0.21 = LOW, Select Channel -> HIGH */ nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); /* P0.23 = LOW, Select Channel -> HIGH */ nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); /* P1.10 = LOW */ nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1); /* P0.28 = LOW */ DBG_PRINTF("[DR_PIEZO] MUX init done\r\n"); } /* * 피에조 채널 선택 (0~7) * 채널 매핑 (EN_MUXA, EN_MUXB, SEL0, SEL1): * CH0 = MUXA 입력0 (1,0,0,0) CH4 = MUXB 입력0 (0,1,1,1) * CH1 = MUXA 입력2 (1,0,1,0) CH5 = MUXB 입력1 (0,1,0,1) * CH2 = MUXA 입력1 (1,0,0,1) CH6 = MUXB 입력2 (0,1,1,0) * CH3 = MUXA 입력3 (1,0,1,1) CH7 = MUXB 입력3 (0,1,0,0) * 채널 전환 후 MUX 안정화 대기 시간(1.3ms) 필요. */ void dr_piezo_select_channel(uint8_t channel) { channel = channel & 0x07; /* 0~7 범위로 마스킹 */ switch (channel) { // EN_A EN_B SEL0 SEL1 case 0: // A0: 1 0 0 0 nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1); break; case 1: // A2: 1 0 1 0 nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1); break; case 2: // A1: 1 0 0 1 nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1); break; case 3: // A3: 1 0 1 1 nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1); break; case 4: // B0: 0 1 1 1 nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1); break; case 5: // B1: 0 1 1 0 nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1); break; case 6: // B2: 0 1 0 1 nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1); break; case 7: // B3: 0 1 0 0 nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1); break; } /* Delay for MUX settling (analog path stabilization) */ nrf_delay_us(DR_PIEZO_MUX_SETTLING_US); } /* 핀 테스트: 각 신호 핀을 순서대로 HIGH/LOW 토글 (오실로스코프 확인용) */ void dr_piezo_test_pins(void) { DBG_PRINTF("[DR_PIEZO] Pin test...\r\n"); nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_PE); nrf_gpio_cfg_output(DR_PIEZO_PIN_DMP); nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT); DBG_PRINTF(" PE (P1.05)...\r\n"); nrf_gpio_pin_set(DR_PIEZO_PIN_PE); nrf_delay_ms(100); nrf_gpio_pin_clear(DR_PIEZO_PIN_PE); nrf_delay_ms(100); DBG_PRINTF(" DMP (P1.09)...\r\n"); nrf_gpio_pin_set(DR_PIEZO_PIN_DMP); nrf_delay_ms(100); nrf_gpio_pin_clear(DR_PIEZO_PIN_DMP); nrf_delay_ms(100); DBG_PRINTF(" P_OUT (P1.03)...\r\n"); nrf_gpio_pin_set(DR_PIEZO_PIN_P_OUT); nrf_delay_ms(100); nrf_gpio_pin_clear(DR_PIEZO_PIN_P_OUT); nrf_delay_ms(100); DBG_PRINTF(" N_OUT (P1.02)...\r\n"); nrf_gpio_pin_set(DR_PIEZO_PIN_N_OUT); nrf_delay_ms(100); nrf_gpio_pin_clear(DR_PIEZO_PIN_N_OUT); nrf_delay_ms(100); DBG_PRINTF("[DR_PIEZO] Pin test done\r\n"); } /*============================================================================== * 시스템 함수 (전원 + TX 드라이버 통합 제어) *============================================================================*/ /* 시스템 전체 초기화: 전원 ON → TX 드라이버 초기화 */ void dr_piezo_system_init(void) { DBG_PRINTF("[PIEZO] System init...\r\n"); dr_piezo_power_on(); dr_piezo_init(); DBG_PRINTF("[PIEZO] System ready\r\n"); } /* 시스템 전체 종료: TX 드라이버 해제 → 전원 OFF */ void dr_piezo_system_uninit(void) { dr_piezo_uninit(); dr_piezo_power_off(); DBG_PRINTF("[DR_PIEZO] System shutdown\r\n"); } /* 전원 확인 후 버스트 송신 (블로킹: 송신 완료까지 대기) */ void dr_piezo_transmit(uint8_t cycles) { if (!m_power_enabled) { DBG_PRINTF("[DR_PIEZO] ERROR: Power not enabled!\r\n"); return; } dr_piezo_burst(cycles); while (dr_piezo_is_busy()) { } } /*============================================================================== * 소프트웨어 기반 버스트 모드 * 2025-12-11 Charles KWON *============================================================================== * * Timer/PPI/GPIOTE 하드웨어 대신 CPU에서 직접 GPIO 레지스터를 조작하여 * 초음파 펄스를 생성하는 방식. 인터럽트를 비활성화하여 정확한 타이밍 보장. * * === 타이밍 다이어그램 === * * |<-마진->|<----- 펄스들 ----->|<-- DMP -->|<-마진->| * * PE ___/--------------------------------------------------\___ * P_OUT ___________/-\_/-\_/-\_/-\_/-\____________________\_______ * N_OUT ___________\_/-\_/-\_/-\_/-\_/____________________\_______ * DMP __________________________________/----------\____________ * * === 신호 설명 === * - PE (Pulse Enable): 전체 시퀀스를 감싸는 활성화 신호 (전후 마진 포함) * - P_OUT: 양극 출력, N_OUT과 역상으로 2MHz 토글 * - N_OUT: 음극 출력, P_OUT과 역상으로 2MHz 토글 * - DMP (Dump): 펄스 완료 후 피에조 잔류 에너지 방전 * * === 동작 원리 === * 1) __disable_irq()로 인터럽트 차단 → 타이밍 흔들림 방지 * 2) PE ON (P0.25 OUTSET 레지스터 사용 → 다른 P0 핀 영향 없음) * 3) NOP 마진 후 for 루프로 P_OUT/N_OUT 교번 출력 * 4) DMP 펄스 (32 NOP ≒ 500ns) * 5) PE OFF 후 __enable_irq()로 인터럽트 복원 * * === 포트 레지스터 직접 접근 === * NRF_P1->OUT 레지스터에 미리 계산된 비트 마스크를 직접 기록. * 이 방식으로 P_OUT, N_OUT, DMP를 동시에 제어하면서도 * 채널 선택 핀(MUX SEL)은 보존한다 (P1_CTRL_MASK로 제어 핀만 변경). * PE는 P0 포트에 있으므로 OUTSET/OUTCLR 레지스터로 별도 제어. *============================================================================*/ /* 핀 번호에서 포트 내 비트 위치 추출 (하위 5비트 = 0~31) */ #define PIN_NUM(pin) ((pin) & 0x1F) /* P1 포트 핀의 비트 마스크 - dr_piezo.h의 핀 정의에서 자동 생성 * * 경고: 핀 번호를 절대 하드코딩하지 말 것! * 하드코딩은 개발자의 시간을 일시적으로 절약해줄 수 있지만, * 동시에 개발자의 수명을 단축시킬 것이다. * - Charles KWON * * 각 마스크는 해당 핀의 포트 레지스터 내 비트 위치를 나타낸다. * NRF_P1->OUT에 직접 쓸 때 사용되며, 여러 핀을 동시에 제어 가능. */ #define P_OUT_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_P_OUT)) /* P1.07 양극 출력 */ #define N_OUT_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_N_OUT)) /* P1.06 음극 출력 */ #define PE_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_PE)) /* P0.25 펄스 활성화 */ #define DMP_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_DMP)) /* P1.00 방전 제어 */ /* P1 포트에서 피에조 제어에 사용하는 핀들의 결합 마스크 (채널 선택 핀 제외) */ #define P1_CTRL_MASK (P_OUT_MASK | N_OUT_MASK | DMP_MASK) /* * 소프트웨어 버스트 - 기본 주파수 2.1MHz * * NOP 타이밍 계산: * 2.1MHz → 주기 476ns, 반주기 238ns * CPU 64MHz → 1 NOP = 15.625ns * 첫 반주기: 14 NOP(≒219ns) + 레지스터 쓰기(≒30ns) = ≒249ns * 둘째 반주기: 9 NOP(≒141ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒218ns * 합계: ≒436~476ns (≒2.1MHz) */ void dr_piezo_burst_sw(uint8_t cycles) { /* Clamp cycles to valid range (1-20) */ if (cycles < 1) cycles = 1; if (cycles > 20) cycles = 20; /* RTT: snapshot then single print */ uint32_t _d0 = NRF_P1->OUT; /* Disable GPIOTE hardware control to prevent conflicts */ nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); uint32_t _d1 = NRF_P1->OUT; /* Save ENTIRE P1 port state BEFORE any GPIO config */ uint32_t saved_p1_out = NRF_P1->OUT; uint32_t saved_p0_out = NRF_P0->OUT; /* Configure all signal pins as outputs */ nrf_gpio_cfg_output(DR_PIEZO_PIN_PE); nrf_gpio_cfg_output(DR_PIEZO_PIN_DMP); nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT); uint32_t _d2 = NRF_P1->OUT; //DBG_PRINTF("[B]S0:%u%u%u\r\n", (_d0>>10)&1, (_d1>>10)&1, (_d2>>10)&1); /* Restore P1 port state (nrf_gpio_cfg_output may clear bits) */ NRF_P1->OUT = saved_p1_out; NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK); /*-------------------------------------------------------------------------- * Pre-calculate all output states for fast switching * Each state represents a specific combination of pin levels * IMPORTANT: Channel select pins (P1.11, P1.12) are preserved in all states *------------------------------------------------------------------------*/ // PE는 OUTSET/OUTCLR 사용 (다른 P0 핀 영향 방지) uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK; uint32_t p1_DMP_high = p1_all_low | DMP_MASK; /*-------------------------------------------------------------------------- * Critical timing section - interrupts disabled *------------------------------------------------------------------------*/ __disable_irq(); /* Initialize: Set all signals to LOW */ NRF_P0->OUTCLR = PE_MASK; // PE OFF (OUTCLR로 다른 핀 영향 없음) NRF_P1->OUT = p1_all_low; /* PE rises first with margin before pulses start */ NRF_P0->OUTSET = PE_MASK; // PE ON (OUTSET로 다른 핀 영향 없음) __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */ /*-------------------------------------------------------------------------- * Generate 2.1MHz pulse burst (hardcoded NOP timing for stability) * * 2.1 MHz: 476ns period, 238ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 13 NOPs (~203ns) + register write (~30ns) = ~233ns * Second half: 10 NOPs (~156ns) + loop overhead (~47ns) = ~203ns * Total: ~436-476ns per cycle *------------------------------------------------------------------------*/ for (uint8_t i = 0; i < cycles; i++) { /* First half-period: P_OUT=HIGH, N_OUT=LOW */ NRF_P1->OUT = p1_P_high_N_low; __NOP(); __NOP(); __NOP(); __NOP(); __NOP();__NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* Second half-period: P_OUT=LOW, N_OUT=HIGH */ NRF_P1->OUT = p1_P_low_N_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); } /*-------------------------------------------------------------------------- * DMP (Dump) pulse * - Starts simultaneously with N_OUT falling edge * - Duration: ~500ns (32 NOPs) *------------------------------------------------------------------------*/ NRF_P1->OUT = p1_DMP_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* DMP falls, PE remains HIGH for margin period */ NRF_P1->OUT = p1_all_low; __NOP(); __NOP(); __NOP(); /* ~47ns margin */ NRF_P0->OUTCLR = PE_MASK; // PE OFF __enable_irq(); } /** * @brief Software-based burst at 1.8 MHz * @param cycles Number of cycles (1~20) * * Timing: * 1.8 MHz: 556ns period, 278ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 15 NOPs (~234ns) * Second half: 12 NOPs (~188ns) + loop overhead */ /* * 소프트웨어 버스트 - 1.8MHz * NOP 타이밍: 반주기 278ns * 첫 반주기: 17 NOP(≒266ns) + 레지스터 쓰기(≒30ns) = ≒296ns * 둘째 반주기: 11 NOP(≒172ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒249ns * 합계: ≒499~556ns (≒1.8MHz) */ void dr_piezo_burst_sw_18mhz(uint8_t cycles) { /* Clamp cycles to valid range (1-20) */ if (cycles < 1) cycles = 1; if (cycles > 20) cycles = 20; /* Disable GPIOTE hardware control to prevent conflicts */ nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); /* Save port states BEFORE any GPIO config */ uint32_t saved_p1_out = NRF_P1->OUT; uint32_t saved_p0_out = NRF_P0->OUT; /* Configure all signal pins as outputs */ nrf_gpio_cfg_output(DR_PIEZO_PIN_PE); nrf_gpio_cfg_output(DR_PIEZO_PIN_DMP); nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT); /* Restore port states (nrf_gpio_cfg_output may clear bits) */ NRF_P1->OUT = saved_p1_out; NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK); //NRF_P0->OUT = saved_p0_out; /* Pre-calculate P1 output states (PE는 P0.25 - OUTSET/OUTCLR로 제어) */ uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK; uint32_t p1_DMP_high = p1_all_low | DMP_MASK; __disable_irq(); /* Initialize: Set all signals to LOW */ NRF_P0->OUTCLR = PE_MASK; NRF_P1->OUT = p1_all_low; /* PE rises first with margin before pulses start */ NRF_P0->OUTSET = PE_MASK; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /*-------------------------------------------------------------------------- * Generate 1.8MHz pulse burst * * 1.8 MHz: 556ns period, 278ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 15 NOPs (~234ns) + register write (~30ns) = ~264ns * Second half: 12 NOPs (~188ns) + loop overhead (~47ns) = ~235ns * Total: ~499-556ns per cycle *------------------------------------------------------------------------*/ for (uint8_t i = 0; i < cycles; i++) { /* First half-period: P_OUT=HIGH, N_OUT=LOW */ NRF_P1->OUT = p1_P_high_N_low; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* Second half-period: P_OUT=LOW, N_OUT=HIGH */ NRF_P1->OUT = p1_P_low_N_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); } /* DMP (Dump) pulse */ NRF_P1->OUT = p1_DMP_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* DMP falls, PE remains HIGH for margin period */ NRF_P1->OUT = p1_all_low; __NOP(); __NOP(); __NOP(); /* End of sequence: PE OFF */ NRF_P0->OUTCLR = PE_MASK; __enable_irq(); } /** * @brief Software-based burst at 2.0 MHz * @param cycles Number of cycles (1~20) * * Timing: * 2.0 MHz: 500ns period, 250ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 14 NOPs (~219ns) + register write (~30ns) = ~249ns * Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns * Total: ~468-500ns per cycle */ /* * 소프트웨어 버스트 - 2.0MHz * NOP 타이밍: 반주기 250ns * 첫 반주기: 15 NOP(≒234ns) + 레지스터 쓰기(≒30ns) = ≒264ns * 둘째 반주기: 10 NOP(≒156ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒233ns * 합계: ≒468~500ns (≒2.0MHz) */ void dr_piezo_burst_sw_20mhz(uint8_t cycles) { /* Clamp cycles to valid range (1-20) */ if (cycles < 1) cycles = 1; if (cycles > 20) cycles = 20; /* Disable GPIOTE hardware control to prevent conflicts */ nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); /* Save port states BEFORE any GPIO config */ uint32_t saved_p1_out = NRF_P1->OUT; uint32_t saved_p0_out = NRF_P0->OUT; /* Configure all signal pins as outputs */ nrf_gpio_cfg_output(DR_PIEZO_PIN_PE); nrf_gpio_cfg_output(DR_PIEZO_PIN_DMP); nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT); /* Restore port states (nrf_gpio_cfg_output may clear bits) */ NRF_P1->OUT = saved_p1_out; //NRF_P0->OUT = saved_p0_out; NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK); /* Pre-calculate P1 output states (PE는 P0.25 - OUTSET/OUTCLR로 제어) */ uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK; uint32_t p1_DMP_high = p1_all_low | DMP_MASK; __disable_irq(); /* Initialize: Set all signals to LOW */ NRF_P0->OUTCLR = PE_MASK; NRF_P1->OUT = p1_all_low; /* PE rises first with margin before pulses start */ NRF_P0->OUTSET = PE_MASK; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /*-------------------------------------------------------------------------- * Generate 2.0MHz pulse burst * * 2.0 MHz: 500ns period, 250ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 14 NOPs (~219ns) + register write (~30ns) = ~249ns * Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns * Total: ~468-500ns per cycle *------------------------------------------------------------------------*/ for (uint8_t i = 0; i < cycles; i++) { /* First half-period: P_OUT=HIGH, N_OUT=LOW */ NRF_P1->OUT = p1_P_high_N_low; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* Second half-period: P_OUT=LOW, N_OUT=HIGH */ NRF_P1->OUT = p1_P_low_N_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); } /* DMP (Dump) pulse */ NRF_P1->OUT = p1_DMP_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* DMP falls, PE remains HIGH for margin period */ NRF_P1->OUT = p1_all_low; __NOP(); __NOP(); __NOP(); /* End of sequence: PE OFF */ NRF_P0->OUTCLR = PE_MASK; __enable_irq(); } /** * @brief Software-based burst at 2.0 MHz * @param cycles Number of cycles (1~20) * * Timing: * 1.9 MHz: 500ns period, 250ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 14 NOPs (~219ns) + register write (~30ns) = ~249ns * Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns * Total: ~468-500ns per cycle */ /* * 소프트웨어 버스트 - 1.9MHz * NOP 타이밍: 반주기 263ns * 첫 반주기: 15 NOP(≒234ns) + 레지스터 쓰기(≒30ns) = ≒264ns * 둘째 반주기: 9 NOP(≒141ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒218ns * 합계: ≒468~500ns (≒1.9MHz) */ void dr_piezo_burst_sw_19mhz(uint8_t cycles) { /* Clamp cycles to valid range (1-20) */ if (cycles < 1) cycles = 1; if (cycles > 20) cycles = 20; /* Disable GPIOTE hardware control to prevent conflicts */ nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); /* Save port states BEFORE any GPIO config */ uint32_t saved_p1_out = NRF_P1->OUT; uint32_t saved_p0_out = NRF_P0->OUT; /* Configure all signal pins as outputs */ nrf_gpio_cfg_output(DR_PIEZO_PIN_PE); nrf_gpio_cfg_output(DR_PIEZO_PIN_DMP); nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT); /* Restore port states (nrf_gpio_cfg_output may clear bits) */ NRF_P1->OUT = saved_p1_out; //NRF_P0->OUT = saved_p0_out; NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK); /* Pre-calculate P1 output states (PE는 P0.25 - OUTSET/OUTCLR로 제어) */ uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK; uint32_t p1_DMP_high = p1_all_low | DMP_MASK; __disable_irq(); /* Initialize: Set all signals to LOW */ NRF_P0->OUTCLR = PE_MASK; NRF_P1->OUT = p1_all_low; /* PE rises first with margin before pulses start */ NRF_P0->OUTSET = PE_MASK; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /*-------------------------------------------------------------------------- * Generate 2.0MHz pulse burst * * 2.0 MHz: 500ns period, 250ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 14 NOPs (~219ns) + register write (~30ns) = ~249ns * Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns * Total: ~468-500ns per cycle *------------------------------------------------------------------------*/ for (uint8_t i = 0; i < cycles; i++) { /* First half-period: P_OUT=HIGH, N_OUT=LOW */ NRF_P1->OUT = p1_P_high_N_low; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* Second half-period: P_OUT=LOW, N_OUT=HIGH */ NRF_P1->OUT = p1_P_low_N_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); } /* DMP (Dump) pulse */ NRF_P1->OUT = p1_DMP_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* DMP falls, PE remains HIGH for margin period */ NRF_P1->OUT = p1_all_low; __NOP(); __NOP(); __NOP(); /* End of sequence: PE OFF */ NRF_P0->OUTCLR = PE_MASK; __enable_irq(); } /** * @brief Software-based burst at 2.2 MHz * @param cycles Number of cycles (1~20) * * Timing: * 2.2 MHz: 454ns period, 227ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 13 NOPs (~203ns) + register write (~30ns) = ~233ns * Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns * Total: ~452ns per cycle (~2.21 MHz) */ /* * 소프트웨어 버스트 - 2.2MHz * NOP 타이밍: 반주기 227ns * 첫 반주기: 13 NOP(≒203ns) + 레지스터 쓰기(≒30ns) = ≒233ns * 둘째 반주기: 8 NOP(≒125ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒202ns * 합계: ≒435~454ns (≒2.2MHz) */ void dr_piezo_burst_sw_22mhz(uint8_t cycles) { /* Clamp cycles to valid range (1-20) */ if (cycles < 1) cycles = 1; if (cycles > 20) cycles = 20; /* Disable GPIOTE hardware control to prevent conflicts */ nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); /* Save port states BEFORE any GPIO config */ uint32_t saved_p1_out = NRF_P1->OUT; uint32_t saved_p0_out = NRF_P0->OUT; /* Configure all signal pins as outputs */ nrf_gpio_cfg_output(DR_PIEZO_PIN_PE); nrf_gpio_cfg_output(DR_PIEZO_PIN_DMP); nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT); /* Restore port states (nrf_gpio_cfg_output may clear bits) */ NRF_P1->OUT = saved_p1_out; NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK); /* Pre-calculate P1 output states */ uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK; uint32_t p1_DMP_high = p1_all_low | DMP_MASK; __disable_irq(); /* Initialize: Set all signals to LOW */ NRF_P0->OUTCLR = PE_MASK; NRF_P1->OUT = p1_all_low; /* PE rises first with margin before pulses start */ NRF_P0->OUTSET = PE_MASK; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /*-------------------------------------------------------------------------- * Generate 2.2MHz pulse burst * * 2.2 MHz: 454ns period, 227ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 11 NOPs - positive pulse width * Second half: 10 NOPs - negative pulse width * Total: 21 NOPs per cycle (~2.2 MHz) *------------------------------------------------------------------------*/ for (uint8_t i = 0; i < cycles; i++) { /* First half-period: P_OUT=HIGH, N_OUT=LOW (positive pulse width) */ NRF_P1->OUT = p1_P_high_N_low; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* Second half-period: P_OUT=LOW, N_OUT=HIGH (negative pulse width) */ NRF_P1->OUT = p1_P_low_N_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); } /* DMP (Dump) pulse */ NRF_P1->OUT = p1_DMP_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* DMP falls, PE remains HIGH for margin period */ NRF_P1->OUT = p1_all_low; __NOP(); __NOP(); __NOP(); /* End of sequence: PE OFF */ NRF_P0->OUTCLR = PE_MASK; __enable_irq(); } /** * @brief Software-based burst at 1.7 MHz * @param cycles Number of cycles (1~20) * * Timing: * 1.7 MHz: 588ns period, 294ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 17 NOPs (~266ns) + register write (~30ns) = ~296ns * Second half: 14 NOPs (~219ns) + loop overhead (~47ns) = ~266ns * Total: ~532-588ns per cycle */ /* * 소프트웨어 버스트 - 1.7MHz * NOP 타이밍: 반주기 294ns * 첫 반주기: 18 NOP(≒281ns) + 레지스터 쓰기(≒30ns) = ≒311ns * 둘째 반주기: 10 NOP(≒156ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒233ns * 합계: ≒532~588ns (≒1.7MHz) */ void dr_piezo_burst_sw_17mhz(uint8_t cycles) { /* Clamp cycles to valid range (1-20) */ if (cycles < 1) cycles = 1; if (cycles > 20) cycles = 20; /* Disable GPIOTE hardware control to prevent conflicts */ nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); /* Save port states BEFORE any GPIO config */ uint32_t saved_p1_out = NRF_P1->OUT; uint32_t saved_p0_out = NRF_P0->OUT; /* Configure all signal pins as outputs */ nrf_gpio_cfg_output(DR_PIEZO_PIN_PE); nrf_gpio_cfg_output(DR_PIEZO_PIN_DMP); nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT); /* Restore port states (nrf_gpio_cfg_output may clear bits) */ NRF_P1->OUT = saved_p1_out; NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK); /* Pre-calculate P1 output states (PE는 P0에서 OUTSET/OUTCLR로 제어) */ uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK; uint32_t p1_DMP_high = p1_all_low | DMP_MASK; __disable_irq(); /* Initialize: Set all signals to LOW */ NRF_P0->OUTCLR = PE_MASK; // PE OFF NRF_P1->OUT = p1_all_low; /* PE rises first with margin before pulses start */ NRF_P0->OUTSET = PE_MASK; // PE ON __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /*-------------------------------------------------------------------------- * Generate 1.7MHz pulse burst * * 1.7 MHz: 588ns period, 294ns half-period * At 64MHz CPU (1 NOP = 15.625ns): * First half: 17 NOPs (~266ns) + register write (~30ns) = ~296ns * Second half: 14 NOPs (~219ns) + loop overhead (~47ns) = ~266ns * Total: ~532-588ns per cycle *------------------------------------------------------------------------*/ for (uint8_t i = 0; i < cycles; i++) { /* First half-period: P_OUT=HIGH, N_OUT=LOW */ NRF_P1->OUT = p1_P_high_N_low; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* Second half-period: P_OUT=LOW, N_OUT=HIGH */ NRF_P1->OUT = p1_P_low_N_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); //__NOP(); __NOP(); __NOP(); __NOP(); } /* DMP (Dump) pulse */ NRF_P1->OUT = p1_DMP_high; __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); /* DMP falls, PE remains HIGH for margin period */ NRF_P1->OUT = p1_all_low; __NOP(); __NOP(); __NOP(); /* End of sequence: PE OFF */ NRF_P0->OUTCLR = PE_MASK; // PE OFF __enable_irq(); }