- tmp235_q1.c: SAADC 완료 플래그(tmp235_saadc_done) 추가 - parser.c: all_sensors()에서 고정 1ms 대기 -> 콜백 완료 플래그 대기로 변경
1318 lines
50 KiB
C
1318 lines
50 KiB
C
/*
|
|
* 2025-12-08 power loop bug fix
|
|
* 2025-12-07 msn, mta, mqq
|
|
* 2025-12-04 by Charles KWON
|
|
* parser.c : Common parser + command table + handlers
|
|
* - Firmware/PC shared
|
|
* - Hardware-dependent parts left as TODO
|
|
* - Added CRC16 validation support
|
|
*
|
|
* [파일 개요]
|
|
* BLE(Bluetooth Low Energy)를 통해 수신된 바이너리 명령 패킷을 파싱하고, 명령 테이블에서 해당 핸들러를 찾아 실행하는 공통 파서 모듈
|
|
*
|
|
* 패킷 구조: [TAG 4바이트] [데이터 N바이트] [CRC16 2바이트(선택)]
|
|
* - TAG: 4글자 ASCII 명령 식별자 (예: "mta?", "mec?")
|
|
* - 데이터: Little-Endian uint16 또는 ASCII 문자열
|
|
* - CRC16: g_plat.crc_check가 true일 때 패킷 끝 2바이트로 무결성 검증
|
|
*
|
|
* 동작 흐름:
|
|
* 1) dr_cmd_parser() → 외부에서 호출하는 진입점
|
|
* 2) dr_parse_cmd() → CRC 검증 + TAG/데이터 분리
|
|
* 3) dr_cmd_dispatch() → 명령 테이블(g_cmd_table) 순회하여 핸들러 호출
|
|
* 4) Cmd_xxx() → 각 명령별 하드웨어 제어 및 BLE 응답 전송
|
|
*/
|
|
|
|
#include "parser.h"
|
|
#include <string.h>
|
|
|
|
#include "nrf_gpio.h"
|
|
#include "nrf_delay.h"
|
|
#include "dr_piezo.h"
|
|
#include "dr_util.h"
|
|
#include "dr_adc121s051.h"
|
|
|
|
|
|
/*==============================================================================
|
|
* 외부 함수 선언부
|
|
* - 다른 모듈(센서, BLE, 디바이스 제어 등)에서 정의된 함수들의 프로토타입
|
|
*============================================================================*/
|
|
|
|
/* 센서 측정 함수 */
|
|
extern void battery_level_meas(void); /* 배터리 잔량 ADC 측정 */
|
|
extern void pressure_all_level_meas(void); /* 압력 센서 1,2 동시 측정 */
|
|
extern void tmp235_voltage_level_meas(void); /* TMP235 온도 센서 전압 측정 */
|
|
|
|
/* 디바이스 전원/상태 제어 함수 */
|
|
extern int device_activated(void); /* 디바이스 활성화 (센서 전원 ON) → 0:성공 */
|
|
extern int device_sleep_mode(void); /* 슬립 모드 진입 (저전력) → 0:성공 */
|
|
|
|
/* 에러 처리 */
|
|
extern void param_error(const char *cmd); /* 파라미터 오류 시 BLE로 에러 응답 전송 */
|
|
|
|
/* BLE 바이너리 전송 함수 */
|
|
extern void single_format_data(uint8_t *buffer, const char *tag, uint16_t value); /* TAG(4바이트) + uint16 값(2바이트)을 buffer에 포맷팅 */
|
|
extern void ascii_format_data(uint8_t *buffer, const char *tag, const char *ascii, uint8_t len); /* TAG(4바이트) + ASCII 문자열을 buffer에 포맷팅 */
|
|
extern void dr_binary_tx_safe(const uint8_t *buffer, uint16_t length); /* BLE NUS를 통해 바이너리 데이터 전송 (length는 워드 단위) */
|
|
extern volatile bool data_tx_in_progress; /* BLE TX 진행 중 플래그 */
|
|
extern void dr_sd_delay_ms(uint32_t ms); /* SoftDevice 호환 딜레이 (BLE 스택 이벤트 처리 허용) */
|
|
|
|
/* FDS(Flash Data Storage) 설정 관련 */
|
|
#include "fstorage.h"
|
|
extern char SERIAL_NO[12]; /* 시리얼 넘버 (12자 ASCII, FDS에 저장) */
|
|
extern char HW_NO[12]; /* 하드웨어 넘버 (12자 ASCII, FDS에 저장) */
|
|
extern char m_static_passkey[6]; /* BLE 정적 패스키 (6자리 숫자 문자열) */
|
|
extern uint32_t m_life_cycle; /* 디바이스 수명 카운터 (FDS에 영구 저장) */
|
|
extern void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length); /* TAG + uint16 배열을 buffer에 포맷팅 (가변 길이 응답용) */
|
|
|
|
#define DR_DEVICE_VERSION "FW25LIT2B102" /* 펌웨어 버전 문자열 - cmd_parse.c의 DEVICE_VERSION과 반드시 일치해야 함 */
|
|
|
|
|
|
/*==============================================================================
|
|
* 외부 변수 선언부
|
|
* - main.c 등 다른 모듈에서 정의된 전역 변수 참조
|
|
*============================================================================*/
|
|
|
|
extern volatile bool processing; /* true: 측정 진행 중 (중복 명령 방지 플래그) */
|
|
extern bool device_status; /* true: 디바이스 활성화 상태 (센서 전원 ON) */
|
|
extern uint8_t resetCount; /* 통신 타임아웃 카운터 (리셋 감지용) */
|
|
extern uint8_t ble_bin_buffer[]; /* BLE 바이너리 전송 버퍼 (공용) */
|
|
|
|
extern bool con_single; /* 단일 연결 모드 플래그 */
|
|
extern bool lock_check; /* 보안 잠금 상태 플래그 */
|
|
|
|
|
|
extern bool info4; /* true: 센서값을 전역 변수에 저장 (BLE 전송 안 함) */
|
|
extern bool ble_got_new_data; /* true: BLE로 새 데이터 수신됨 (처리 대기) */
|
|
extern bool go_batt; /* true: 배터리 측정 요청 플래그 */
|
|
extern bool motion_data_once; /* true: IMU 1회 측정, false: 연속 측정 */
|
|
extern bool motion_raw_data_enabled;/* true: IMU 원시 데이터 전송 활성화 */
|
|
extern int imu_read_direct(void); /* IMU 레지스터 직접 읽기 + BLE 전송 */
|
|
|
|
/* info4 모드 센서값 저장 변수 (mbb? rbb: 패킷 조립용) */
|
|
extern volatile uint16_t info_batt; /* 배터리 전압 (mV) */
|
|
extern volatile uint16_t info_temp; /* 온도 (°C x 100) */
|
|
extern volatile uint16_t info_imu[6]; /* IMU 6축 (accel XYZ + gyro XYZ) */
|
|
|
|
extern void pressure_all_level_meas(void); /* 압력 센서 전체 측정 */
|
|
extern void battery_timer_stop(void); /* 배터리 타이머 중지 */
|
|
extern void main_timer_start(void); /* 메인 타이머 시작 (주기적 측정 트리거) */
|
|
extern void hw_i2c_init_once(void); /* IMU용 I2C 버스 초기화 (최초 1회) */
|
|
|
|
/* 전원/리셋/본딩 제어 플래그 */
|
|
extern bool go_device_power_off; /* true → 메인 루프에서 전원 OFF 실행 */
|
|
extern bool go_NVIC_SystemReset; /* true → 메인 루프에서 시스템 리셋 실행 */
|
|
extern bool bond_data_delete; /* true → 리셋 시 BLE 본딩 정보 삭제 */
|
|
extern uint8_t m_reset_status; /* 리셋 상태 코드 (FDS에 저장, 부팅 시 참조) */
|
|
extern void config_save(void); /* 설정값을 FDS(Flash)에 저장 */
|
|
extern config_data_t m_config; /* 전체 설정 구조체 (FDS 저장 대상) */
|
|
|
|
/* AGC(자동 이득 제어) 게인 스위치 매크로
|
|
* - P0.20 핀으로 아날로그 수신 회로의 게인을 HIGH/LOW 전환
|
|
* - measurements.h의 매크로를 여기서도 사용하기 위해 재정의 */
|
|
#include "nrf_gpio.h"
|
|
#define GAIN_SW_PIN NRF_GPIO_PIN_MAP(0, 20)
|
|
#define AGC_GAIN_SW(x) do { if(x) nrf_gpio_pin_set(GAIN_SW_PIN); else nrf_gpio_pin_clear(GAIN_SW_PIN); } while(0)
|
|
|
|
|
|
/* ---- 피에조 관련 제어 함수 ---- */
|
|
extern void dr_piezo_power_on( void ); /* 피에조 회로 전원 ON (TX/RX 보드) */
|
|
extern void dr_piezo_power_off( void ); /* 피에조 회로 전원 OFF */
|
|
extern bool dr_piezo_is_power_on( void ); /* 피에조 전원 상태 확인 */
|
|
extern void dr_piezo_burst_sw(uint8_t cycles); /* 2.1MHz 버스트 펄스 발생 (기본 주파수) */
|
|
extern void dr_piezo_burst_sw_18mhz(uint8_t cycles); /* 1.8MHz 버스트 펄스 발생 */
|
|
extern void dr_piezo_burst_sw_20mhz(uint8_t cycles); /* 2.0MHz 버스트 펄스 발생 */
|
|
extern void dr_piezo_burst_sw_17mhz(uint8_t cycles); /* 1.7MHz 버스트 펄스 발생 */
|
|
|
|
|
|
/* ---- 전역 변수 정의 (헤더에서 extern 선언) ---- */
|
|
dr_platform_if_t g_plat = { 0, 0, 0 };
|
|
/* 플랫폼 인터페이스 구조체:
|
|
* .tx_bin = BLE 바이너리 전송 함수 포인터
|
|
* .log = 디버그 로그 출력 함수 포인터
|
|
* .crc_check = CRC16 검증 활성화 여부 */
|
|
bool g_log_enable = false;
|
|
/* 디버그 로그 전역 활성화 플래그 (g_plat.log와 함께 사용) */
|
|
|
|
|
|
/* ---- 내부 상수/구조체 정의 ---- */
|
|
#define DR_MAX_DATA 128 /* TAG 이후 데이터의 최대 바이트 수 */
|
|
|
|
|
|
/* 파싱된 명령 구조체 - 수신 패킷을 TAG와 데이터로 분리한 결과 */
|
|
typedef struct {
|
|
char tag[5]; /* 명령 TAG 문자열 (예: "sta?") 4글자 + '\0' */
|
|
uint8_t data[DR_MAX_DATA]; /* TAG 뒤의 원시 데이터 바이트 배열 */
|
|
uint8_t data_len; /* data[]의 유효 길이 (바이트) */
|
|
} ParsedCmd;
|
|
|
|
/* ---- 내부 유틸리티 함수 ---- */
|
|
|
|
/* 수신 버퍼의 처음 4바이트를 TAG 문자열로 복사 (널 종료 포함) */
|
|
static void dr_copy_tag(const uint8_t *buf, char *tag_out)
|
|
{
|
|
tag_out[0] = (char)buf[0];
|
|
tag_out[1] = (char)buf[1];
|
|
tag_out[2] = (char)buf[2];
|
|
tag_out[3] = (char)buf[3];
|
|
tag_out[4] = '\0';
|
|
}
|
|
|
|
/* TAG 4글자 비교 (memcmp 대신 바이트 단위 비교로 최적화) */
|
|
static bool dr_tag_eq(const char *tag, const char *key4)
|
|
{
|
|
return (tag[0] == key4[0] &&
|
|
tag[1] == key4[1] &&
|
|
tag[2] == key4[2] &&
|
|
tag[3] == key4[3]);
|
|
}
|
|
|
|
/* 파싱된 명령 데이터에서 uint16 값 추출 (Little-Endian)
|
|
*
|
|
* word_index: 추출할 워드 인덱스 (0번째 = data[0..1], 1번째 = data[2..3], ...)
|
|
* out: 추출된 uint16 값 저장 포인터
|
|
* 반환값: true=성공, false=데이터 부족
|
|
*
|
|
* PC와 nRF52 모두 Little-Endian이므로 data[pos]=하위바이트, data[pos+1]=상위바이트 */
|
|
static bool dr_get_u16(const ParsedCmd *cmd, uint8_t word_index, uint16_t *out)
|
|
{
|
|
uint8_t pos = (uint8_t)(word_index * 2);
|
|
if (cmd->data_len < (uint8_t)(pos + 2)) {
|
|
return false; /* 데이터 길이 부족 → 기본값 유지 */
|
|
}
|
|
|
|
/* Little Endian 조합: data[pos]=하위, data[pos+1]=상위 */
|
|
*out = (uint16_t)cmd->data[pos]
|
|
| (uint16_t)((uint16_t)cmd->data[pos + 1] << 8);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* ASCII 문자열 추출: data[offset] ~ data[offset+max_len-1] → 널 종료 문자열
|
|
*
|
|
* 시리얼번호, HW번호, 패스키 등 FDS에 저장할 텍스트 데이터를 추출할 때 사용
|
|
* offset: data[] 내 시작 위치
|
|
* out: 추출된 문자열 저장 버퍼 (max_len+1 이상 크기 필요)
|
|
* max_len: 최대 추출 길이 (바이트)
|
|
*/
|
|
static void dr_get_ascii(const ParsedCmd *cmd, uint8_t offset, char *out, uint8_t max_len)
|
|
{
|
|
uint8_t i;
|
|
uint8_t remain;
|
|
|
|
/* 오프셋이 데이터 범위를 초과하면 빈 문자열 반환 */
|
|
if (offset >= cmd->data_len)
|
|
{
|
|
out[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
/* 실제 복사 가능한 길이 계산 (남은 데이터 vs 최대 길이 중 작은 값) */
|
|
remain = (uint8_t)(cmd->data_len - offset);
|
|
if (remain > max_len)
|
|
{
|
|
remain = max_len;
|
|
}
|
|
|
|
for (i = 0; i < remain; i++)
|
|
{
|
|
out[i] = (char)cmd->data[offset + i];
|
|
}
|
|
out[remain] = '\0'; /* 널 종료 보장 */
|
|
}
|
|
|
|
/* ---- CRC16 무결성 검증 함수 ---- */
|
|
|
|
/* CRC16 계산 - Nordic SDK의 crc16_compute와 동일한 알고리즘 (CRC-CCITT 변형)
|
|
*
|
|
* p_data: 계산 대상 데이터 포인터
|
|
* size: 데이터 길이 (바이트)
|
|
* p_crc: 초기 CRC 값 (NULL이면 0xFFFF로 시작)
|
|
* 반환값: 계산된 CRC16 값
|
|
*/
|
|
static uint16_t dr_crc16_compute(const uint8_t *p_data, uint32_t size, const uint16_t *p_crc)
|
|
{
|
|
uint32_t i;
|
|
uint16_t crc = (p_crc == NULL) ? 0xFFFF : *p_crc;
|
|
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
crc = (uint8_t)(crc >> 8) | (crc << 8); /* 상위/하위 바이트 교환 */
|
|
crc ^= p_data[i]; /* 데이터 바이트 XOR */
|
|
crc ^= (uint8_t)(crc & 0xFF) >> 4; /* 다항식 연산 1 */
|
|
crc ^= (crc << 8) << 4; /* 다항식 연산 2 */
|
|
crc ^= ((crc & 0xFF) << 4) << 1; /* 다항식 연산 3 */
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
/* CRC16 검증: 데이터에 대해 계산한 CRC와 기대값 비교 */
|
|
static bool dr_crc16_check(const uint8_t *p_data, uint32_t data_len, uint16_t expected_crc)
|
|
{
|
|
uint16_t computed_crc = dr_crc16_compute(p_data, data_len, NULL);
|
|
return (computed_crc == expected_crc);
|
|
}
|
|
|
|
/* 패킷 단위 CRC16 검증
|
|
* 패킷 구조: [데이터 N바이트] + [CRC16 2바이트(Little-Endian)]
|
|
* 마지막 2바이트를 CRC 기대값으로 추출하여, 나머지 데이터의 CRC와 비교 */
|
|
static bool dr_crc16_check_packet(const uint8_t *packet, uint32_t packet_len)
|
|
{
|
|
uint16_t expected_crc;
|
|
uint32_t data_len;
|
|
|
|
if (packet_len < 2)
|
|
{
|
|
return false; /* 최소 CRC 2바이트도 없으면 실패 */
|
|
}
|
|
|
|
data_len = packet_len - 2; /* CRC를 제외한 실제 데이터 길이 */
|
|
|
|
/* 패킷 끝 2바이트에서 CRC 추출 (Little-Endian: 하위바이트 먼저) */
|
|
expected_crc = (uint16_t)packet[packet_len - 2]
|
|
| ((uint16_t)packet[packet_len - 1] << 8);
|
|
|
|
return dr_crc16_check(packet, data_len, expected_crc);
|
|
}
|
|
|
|
/* ---- 수신 버퍼 → ParsedCmd 구조체 변환 ---- */
|
|
|
|
/* 수신된 원시 바이트 버퍼를 파싱하여 TAG와 데이터를 분리
|
|
*
|
|
* buffer: 수신 패킷 [TAG(4)] [DATA(N)] [CRC16(2, 선택)]
|
|
* length: 전체 패킷 길이
|
|
* out: 파싱 결과 저장 구조체
|
|
* 반환값: true=파싱 성공, false=CRC 실패 또는 패킷 길이 부족
|
|
*/
|
|
static bool dr_parse_cmd(const uint8_t *buffer, uint8_t length, ParsedCmd *out)
|
|
{
|
|
uint8_t i;
|
|
|
|
if (length < 4) {
|
|
return false; /* TAG 4바이트조차 수신되지 않음 */
|
|
}
|
|
|
|
/* CRC 검증이 활성화된 경우 패킷 무결성 확인 */
|
|
if (g_plat.crc_check)
|
|
{
|
|
if (!dr_crc16_check_packet(buffer, length))
|
|
{
|
|
if (g_plat.log && g_log_enable) {
|
|
g_plat.log("CRC check FAILED!\n");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* CRC 검증 성공 → CRC 2바이트를 제외한 실제 데이터 길이로 조정 */
|
|
length = (uint8_t)(length - 2);
|
|
}
|
|
|
|
/* TAG 4바이트 복사 */
|
|
dr_copy_tag(buffer, out->tag);
|
|
|
|
/* TAG 이후 데이터 길이 계산 (최대 DR_MAX_DATA까지) */
|
|
out->data_len = (length > 4) ? (uint8_t)(length - 4) : 0;
|
|
if (out->data_len > DR_MAX_DATA)
|
|
{
|
|
out->data_len = DR_MAX_DATA;
|
|
}
|
|
|
|
/* TAG 뒤의 데이터 바이트를 ParsedCmd.data[]에 복사 */
|
|
for (i = 0; i < out->data_len; i++)
|
|
{
|
|
out->data[i] = buffer[4 + i];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*==============================================================================
|
|
* BLE 명령어
|
|
*
|
|
* A. 디바이스 상태 제어
|
|
* - 전원 OFF (msq? / rsq:)
|
|
* - 재부팅 (mss? / rss:)
|
|
* - 본딩 정보 삭제 및 재부팅 (msr? / rsr:) : 확인 필요
|
|
* - GPIO 제어(핀 테스트용, cmd? / rmd:)
|
|
*
|
|
* B. 디바이스 정보 읽기, 쓰기
|
|
* - 하드웨어 버전 읽기 (mrh? / rrh:)
|
|
* - 하드웨어 버전 쓰기 (mwh? / rwh:)
|
|
* - 시리얼 넘버 읽기 (mrs? / rrs:)
|
|
* - 시리얼 넘버 쓰기 (mws? / rws:)
|
|
* - 펌웨어 버전 읽기 (mfv? / rfv:)
|
|
* - 패스키 읽기 (mqz? / rqz:) : 필요 유무 검토(암호화 혹은 삭제)
|
|
* - 패스키 쓰기 (mpz? / rpz:)
|
|
*
|
|
* C. 각종 센서 측정
|
|
* - 배터리 전압 측정 (msn? / rsn:) - test용
|
|
* - IMU 단발 측정 (msp? / rsp:) : 동기 - test용
|
|
* - IMU 연속 스트리밍 (msi? / rsi:) : 비동기(타이머) 1초 주기, 확인 필요 - test용
|
|
* - 온도 측정 (mso? / rso:) - test용
|
|
*
|
|
* D. Piezo 초음파 측정
|
|
* - TX/RX 전원 활성화 (mpa? / rpa:) - test용
|
|
* - TX/RX 전원 비활성화 (mpb? / rpb:) - test용
|
|
* - 단일 채널 Burst (mpc? / rpc:) - test용
|
|
* - 단일 채널 Burst + ADC -> echo capture (mec? / reb: -> red: -> ree:) : TX/RX Active -> 응답 -> TX/RX Sleep
|
|
* - 모든 채널 Burst + ADC -> echo capture (maa? / reb: -> red: -> raa:) : TX/RX Active -> 응답 -> TX/RX Sleep
|
|
* - 모든 채널 Burst + ADC -> echo capture (mbb? / reb: -> red: -> raa:) + 각종 센서 측정(배터리, IMU, 온도) : TX/RX Active -> 응답 -> TX/RX Sleep (NEW!)
|
|
*
|
|
* 삭제 명령어
|
|
* - 디바이스 활성화/슬립 : TX 전원 활성화/비활성화와 동일 기능
|
|
* - 디바이스 상태 조회
|
|
* - 디바이스 준비 확인
|
|
* - 수명 카운터(life cycle) 읽기 및 쓰기
|
|
* - 압력 센서 측정 : 센서 미탑재
|
|
* - 단일 채널 ADC
|
|
*============================================================================*/
|
|
|
|
/*==============================================================================
|
|
* 핸들러 함수 프로토타입 선언
|
|
* - 반환값: 1=성공, 0=실패/비활성
|
|
* - 각 핸들러는 BLE 응답 전송까지 담당
|
|
*============================================================================*/
|
|
|
|
/* A. 디바이스 상태 제어 */
|
|
static int Cmd_msq(const ParsedCmd *cmd); /* msq? 디바이스 전원 OFF */
|
|
static int Cmd_mss(const ParsedCmd *cmd); /* mss? 디바이스 재부팅 */
|
|
#if FEATURE_SECURE_CONNECTION
|
|
static int Cmd_msr(const ParsedCmd *cmd); /* msr? 본딩 삭제 + 디바이스 재부팅 */
|
|
#endif
|
|
static int Cmd_cmd(const ParsedCmd *cmd); /* cmd? GPIO 핀 테스트 (HIGH/LOW 제어) */
|
|
|
|
/* B. 디바이스 정보 읽기, 쓰기 */
|
|
static int Cmd_mwh(const ParsedCmd *cmd); /* mwh? HW 넘버 FDS에 쓰기 */
|
|
static int Cmd_mws(const ParsedCmd *cmd); /* mws? 시리얼 넘버 FDS에 쓰기 */
|
|
static int Cmd_mrh(const ParsedCmd *cmd); /* mrh? HW 넘버 FDS에서 읽기 */
|
|
static int Cmd_mrs(const ParsedCmd *cmd); /* mrs? 시리얼 넘버 FDS에서 읽기 */
|
|
static int Cmd_mfv(const ParsedCmd *cmd); /* mfv? 펌웨어 버전 읽기 */
|
|
static int Cmd_mpz(const ParsedCmd *cmd); /* mpz? BLE 패스키 FDS에 쓰기 */
|
|
static int Cmd_mqz(const ParsedCmd *cmd); /* mqz? BLE 패스키 FDS에서 읽기 */
|
|
|
|
/* C. 각종 센서 측정 */
|
|
static int Cmd_msn(const ParsedCmd *cmd); /* msn? 배터리 잔량 측정 */
|
|
static int Cmd_mso(const ParsedCmd *cmd); /* mso? TMP235 온도 센서 측정 */
|
|
static int Cmd_msp(const ParsedCmd *cmd); /* msp? IMU 6축 원시 데이터 (단발) */
|
|
static int Cmd_msi(const ParsedCmd *cmd); /* msi? IMU 모션 센서 스트리밍 (타이머 기반) */
|
|
|
|
/* D. Piezo 초음파 측정 */
|
|
static int Cmd_mpa(const ParsedCmd *cmd); /* mpa? 피에조 TX/RX 회로 활성화 (전원 ON) */
|
|
static int Cmd_mpb(const ParsedCmd *cmd); /* mpb? 피에조 TX/RX 회로 비활성화 (전원 OFF) */
|
|
static int Cmd_mpc(const ParsedCmd *cmd); /* mpc? 피에조 버스트 발생 (주파수/사이클 제어) */
|
|
static int Cmd_mec(const ParsedCmd *cmd); /* mec? 피에조 버스트 + 에코 캡처 (16비트 원시) */
|
|
static int Cmd_maa(const ParsedCmd *cmd); /* maa? 6채널 전체 캡처 (비동기 전송) */
|
|
static int Cmd_mbb(const ParsedCmd *cmd); /* mbb? 6채널 캡처 + 센서 측정 (배터리 + 온도 + IMU) */
|
|
static int Cmd_mcf(const ParsedCmd *cmd); /* mcf? 피에조 파라미터 읽기 (FDS) */
|
|
static int Cmd_mcs(const ParsedCmd *cmd); /* mcs? 피에조 파라미터 쓰기 (FDS) */
|
|
|
|
|
|
/* ---- 명령 테이블 ---- */
|
|
|
|
/* 명령 엔트리 구조체: TAG 문자열 + 활성화 여부 + 핸들러 함수 포인터 */
|
|
typedef struct {
|
|
char tag[5]; /* 명령 TAG (예: "sta?") - 4글자 + NULL */
|
|
bool enabled; /* false면 핸들러가 호출되지 않음 (비활성 명령) */
|
|
int (*handler)(const ParsedCmd *cmd); /* 핸들러 함수 포인터 (1=성공, 0=실패) */
|
|
} CmdEntry;
|
|
|
|
/* 전체 명령 테이블 - 수신된 TAG와 순차 비교하여 일치하는 핸들러 호출 */
|
|
static CmdEntry g_cmd_table[] = {
|
|
/* A. 디바이스 상태 제어 */
|
|
{ "msq?", true, Cmd_msq },
|
|
{ "mss?", true, Cmd_mss },
|
|
#if FEATURE_SECURE_CONNECTION
|
|
{ "msr?", true, Cmd_msr },
|
|
#endif
|
|
{ "cmd?", true, Cmd_cmd },
|
|
|
|
/* C. 디바이스 정보 읽기, 쓰기 */
|
|
{ "mwh?", true, Cmd_mwh },
|
|
{ "mws?", true, Cmd_mws },
|
|
{ "mrh?", true, Cmd_mrh },
|
|
{ "mrs?", true, Cmd_mrs },
|
|
{ "mpz?", true, Cmd_mpz },
|
|
{ "mqz?", true, Cmd_mqz },
|
|
{ "mfv?", true, Cmd_mfv },
|
|
|
|
/* D. 각종 센서 측정 */
|
|
{ "msn?", true, Cmd_msn },
|
|
{ "mso?", true, Cmd_mso },
|
|
{ "msp?", true, Cmd_msp },
|
|
{ "msi?", true, Cmd_msi },
|
|
|
|
/* E. Piezo 초음파 측정 */
|
|
{ "mpa?", true, Cmd_mpa },
|
|
{ "mpb?", true, Cmd_mpb },
|
|
{ "mpc?", true, Cmd_mpc },
|
|
{ "mec?", true, Cmd_mec },
|
|
{ "maa?", true, Cmd_maa },
|
|
{ "mbb?", true, Cmd_mbb },
|
|
{ "mcf?", true, Cmd_mcf },
|
|
{ "mcs?", true, Cmd_mcs },
|
|
};
|
|
|
|
/* 명령 테이블 엔트리 수 (컴파일 타임 계산) */
|
|
static const uint16_t g_cmd_count =
|
|
(uint16_t)(sizeof(g_cmd_table) / sizeof(g_cmd_table[0]));
|
|
|
|
/* ---- 명령 디스패처 ----
|
|
* 파싱된 TAG를 소문자로 변환 후, 명령 테이블을 순회하여 일치하는 핸들러 호출
|
|
*
|
|
* 반환값: 핸들러의 반환값 (1=성공), 매칭 실패 또는 비활성 명령이면 0
|
|
*/
|
|
static int dr_cmd_dispatch(const ParsedCmd *cmd)
|
|
{
|
|
uint16_t i;
|
|
char tag_lower[5];
|
|
|
|
/* 수신된 TAG를 소문자로 변환 (대소문자 구분 없이 명령 매칭) */
|
|
for (i = 0; i < 4 && cmd->tag[i]; i++) {
|
|
tag_lower[i] = (cmd->tag[i] >= 'A' && cmd->tag[i] <= 'Z')
|
|
? (cmd->tag[i] + 32) : cmd->tag[i];
|
|
}
|
|
tag_lower[i] = '\0';
|
|
|
|
/* 명령 테이블 순차 검색 */
|
|
for (i = 0; i < g_cmd_count; i++) {
|
|
if (dr_tag_eq(tag_lower, g_cmd_table[i].tag)) {
|
|
|
|
/* 비활성화된 명령이면 실행하지 않음 */
|
|
if (!g_cmd_table[i].enabled) {
|
|
if (g_plat.log && g_log_enable) {
|
|
g_plat.log("Command '%s' disabled\n", cmd->tag);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 매칭된 핸들러 호출 */
|
|
return g_cmd_table[i].handler(cmd);
|
|
}
|
|
}
|
|
|
|
/* 테이블에 없는 TAG 수신 시 */
|
|
if (g_plat.log && g_log_enable) {
|
|
g_plat.log("Unknown TAG '%s'\n", cmd->tag);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*==============================================================================
|
|
* 메인 파서 진입점 - 외부 모듈(BLE 수신 콜백 등)에서 호출
|
|
*
|
|
* buf: 수신된 BLE 패킷 원시 바이트
|
|
* len: 패킷 길이
|
|
* 반환값:
|
|
* 1 = 명령 처리 성공
|
|
* 0 = 알 수 없는 TAG 또는 비활성 명령
|
|
* -1 = CRC 실패 또는 파싱 실패 → 호출자가 레거시 파서로 위임 가능
|
|
*============================================================================*/
|
|
|
|
int dr_cmd_parser(const uint8_t *buf, uint8_t len)
|
|
{
|
|
ParsedCmd cmd;
|
|
|
|
/* 패킷 파싱 (CRC 검증 포함) */
|
|
if (!dr_parse_cmd(buf, len, &cmd)) {
|
|
if (g_plat.log) g_plat.log("[PARSER] PARSE FAIL\r\n");
|
|
|
|
/* CRC 실패 시 "crc!" 에러 응답을 BLE로 전송 (65530 = 에러 코드) */
|
|
if (g_plat.crc_check && g_plat.tx_bin) {
|
|
single_format_data(ble_bin_buffer, "crc!", 65530);
|
|
dr_binary_tx_safe(ble_bin_buffer, 3);
|
|
}
|
|
return -1; /* CRC/파싱 실패 → 음수 반환으로 레거시 파서에 위임 */
|
|
}
|
|
|
|
/* 수신 명령어 종류 확인용 로그 */
|
|
//if (g_plat.log) g_plat.log("[PARSER] tag=%s\r\n", cmd.tag);
|
|
|
|
/* 파싱 성공 → 명령 테이블에서 핸들러를 찾아 실행 */
|
|
return dr_cmd_dispatch(&cmd);
|
|
}
|
|
|
|
/*==============================================================================
|
|
* 각 명령 핸들러 구현부
|
|
*============================================================================*/
|
|
|
|
/* msn? - 배터리 잔량 ADC 측정
|
|
* 응답: battery_level_meas() 내부에서 BLE 전송 처리 */
|
|
static int Cmd_msn(const ParsedCmd *cmd)
|
|
{
|
|
(void)cmd;
|
|
battery_level_meas(); /* ADC로 배터리 전압 측정 → BLE 응답 전송 */
|
|
return 1;
|
|
}
|
|
|
|
/* mso? - TMP235-Q1 온도 센서 전압 측정
|
|
* SAADC로 TMP235 출력 전압을 측정하여 BLE로 응답
|
|
* 응답은 tmp235_q1.c의 콜백에서 자동 전송 */
|
|
static int Cmd_mso(const ParsedCmd *cmd)
|
|
{
|
|
(void)cmd;
|
|
tmp235_voltage_level_meas();
|
|
return 1;
|
|
}
|
|
|
|
/* msi? - IMU 모션 센서 원시 데이터 읽기
|
|
*
|
|
* 파라미터: data[0]='c'이면 연속 측정, 그 외 단발 측정
|
|
* 단발 모드: 1회 측정 후 자동 중지
|
|
* 연속 모드: 타이머 주기마다 반복 측정 (별도 중지 명령 필요)
|
|
*
|
|
* 동작: I2C 초기화 → 플래그 설정 → 메인 타이머 시작
|
|
*/
|
|
static int Cmd_msi(const ParsedCmd *cmd)
|
|
{
|
|
/* IMU용 I2C 버스 초기화 (최초 호출 시 1회만 실행) */
|
|
hw_i2c_init_once();
|
|
|
|
motion_raw_data_enabled = true; /* IMU 원시 데이터 전송 활성화 */
|
|
ble_got_new_data = false;
|
|
|
|
/* 'c' = 연속(continuous) 모드, 그 외 = 단발(single shot) 모드 */
|
|
if (cmd->data_len > 0 && (char)cmd->data[0] == 'c') {
|
|
motion_data_once = false; /* 연속 측정 */
|
|
} else {
|
|
motion_data_once = true; /* 1회 측정 후 자동 중지 */
|
|
}
|
|
|
|
main_timer_start(); /* 타이머 콜백에서 IMU 데이터 읽기 + BLE 전송 */
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*==============================================================================
|
|
* J. 전원/리셋/버전/보안 핸들러
|
|
*============================================================================*/
|
|
|
|
/* msq? - 디바이스 전원 OFF
|
|
*
|
|
* BLE 응답("rsq:") 전송 후 → 메인 루프에서 실제 전원 OFF 수행
|
|
* 즉시 전원을 끄면 BLE 응답이 전달되지 않으므로, 플래그+타이머 방식 사용
|
|
*/
|
|
static int Cmd_msq(const ParsedCmd *cmd)
|
|
{
|
|
uint16_t val = 0;
|
|
dr_get_u16(cmd, 0, &val);
|
|
if (g_plat.log) g_plat.log("[Cmd_msq] Power off val=%u\r\n", val);
|
|
single_format_data(ble_bin_buffer, "rsq:", val);
|
|
dr_binary_tx_safe(ble_bin_buffer, 2);
|
|
go_device_power_off = true; /* 메인 루프에서 전원 OFF 실행 예약 */
|
|
main_timer_start(); /* 타이머 콜백에서 전원 OFF 처리 */
|
|
return 1;
|
|
}
|
|
|
|
/* msr? - BLE 본딩 정보 삭제 + 시스템 리셋
|
|
*
|
|
* 동작 순서:
|
|
* 1. BLE 응답("rsr:") 전송
|
|
* 2. 본딩 삭제 플래그 + 리셋 상태를 FDS에 저장
|
|
* 3. 5ms 딜레이 (FDS 쓰기 완료 대기)
|
|
* 4. NVIC 시스템 리셋 플래그 설정 → 타이머에서 리셋 실행
|
|
*/
|
|
#if FEATURE_SECURE_CONNECTION
|
|
static int Cmd_msr(const ParsedCmd *cmd)
|
|
{
|
|
uint16_t val = 0;
|
|
dr_get_u16(cmd, 0, &val);
|
|
//if (g_plat.log) g_plat.log("[Cmd_msr] Bond delete + reset val=%u\r\n", val);
|
|
single_format_data(ble_bin_buffer, "rsr:", val);
|
|
dr_binary_tx_safe(ble_bin_buffer, 2);
|
|
|
|
/* 본딩 삭제 + 리셋 상태를 설정에 저장 (재부팅 후에도 유지) */
|
|
bond_data_delete = true;
|
|
m_config.bond_data_delete = (uint8_t)bond_data_delete;
|
|
m_reset_status = 2; /* 리셋 상태 코드 2 = 본딩 삭제 리셋 */
|
|
m_config.reset_status = m_reset_status;
|
|
config_save(); /* FDS에 설정 영구 저장 */
|
|
nrf_delay_ms(5); /* Flash 쓰기 완료 대기 */
|
|
go_NVIC_SystemReset = true; /* 시스템 리셋 예약 */
|
|
main_timer_start();
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
/* mss? - 디바이스 소프트 리셋 (리부팅)
|
|
*
|
|
* msr?과 유사하지만 본딩 정보는 삭제하지 않음
|
|
* 리셋 상태 코드를 FDS에 저장하여 부팅 시 리셋 원인 확인 가능 =======> val 파라미터 확인
|
|
*/
|
|
static int Cmd_mss(const ParsedCmd *cmd)
|
|
{
|
|
uint16_t val = 0;
|
|
dr_get_u16(cmd, 0, &val);
|
|
//if (g_plat.log) g_plat.log("[Cmd_mss] Device reset val=%u\r\n", val);
|
|
single_format_data(ble_bin_buffer, "rss:", val);
|
|
dr_binary_tx_safe(ble_bin_buffer, 2);
|
|
|
|
m_reset_status = 2;
|
|
m_config.reset_status = m_reset_status;
|
|
config_save();
|
|
nrf_delay_ms(5);
|
|
go_NVIC_SystemReset = true;
|
|
main_timer_start();
|
|
return 1;
|
|
}
|
|
|
|
/* mfv? - 펌웨어 버전 읽기
|
|
*
|
|
* 응답: "rfv:" + 12자 ASCII 버전 문자열 (DR_DEVICE_VERSION)
|
|
* 구 명령 ssv?에서 mfv?로 변경 (26.03.13)
|
|
*/
|
|
static int Cmd_mfv(const ParsedCmd *cmd)
|
|
{
|
|
(void)cmd;
|
|
ascii_format_data(ble_bin_buffer, "rfv:", DR_DEVICE_VERSION, 12);
|
|
dr_binary_tx_safe(ble_bin_buffer, 8); /* 4(TAG) + 12(버전) = 16바이트 = 8워드 */
|
|
return 1;
|
|
}
|
|
|
|
/* mpa? - 피에조 초음파 TX/RX 회로 활성화
|
|
*
|
|
* 동작: 피에조 전원 ON → 시스템 초기화 (DAC, MUX, ADC 설정)
|
|
* 응답: "rpa:" + 1 (성공)
|
|
* 주의: mec?/mdc?/maa? 실행 전에 반드시 먼저 호출해야 함
|
|
*/
|
|
static int Cmd_mpa(const ParsedCmd *cmd)
|
|
{
|
|
(void)cmd;
|
|
|
|
dr_piezo_power_on(); /* 부팅 시 init 완료, 전원만 ON */
|
|
|
|
if (g_plat.tx_bin)
|
|
{
|
|
single_format_data(ble_bin_buffer, "rpa:", 1);
|
|
dr_binary_tx_safe(ble_bin_buffer, 3);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* mpb? - 피에조 초음파 TX/RX 회로 비활성화 (전원 OFF)
|
|
*
|
|
* 측정 완료 후 전력 소모 절감을 위해 호출
|
|
* 응답: "rpb:" + 1 (성공)
|
|
*/
|
|
static int Cmd_mpb(const ParsedCmd *cmd)
|
|
{
|
|
(void)cmd;
|
|
|
|
dr_piezo_power_off(); /* 피에조 보드 전원 OFF */
|
|
|
|
if (g_plat.tx_bin) {
|
|
single_format_data(ble_bin_buffer, "rpb:", 1);
|
|
dr_binary_tx_safe(ble_bin_buffer, 3);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief mpc? - 피에조 버스트 발생 명령 (주파수/사이클/채널 선택)
|
|
*
|
|
* 초음파 트랜스듀서에 지정된 주파수와 사이클 수로 버스트 펄스를 발생시킴
|
|
* 에코 캡처 없이 버스트만 발생 (테스트/디버그용)
|
|
*
|
|
* 파라미터 (Little-Endian uint16 x 3):
|
|
* word 0: cycles (3~7, 기본값=5) - 버스트 펄스 사이클 수
|
|
* word 1: freq_option (기본값=1) - 주파수 선택
|
|
* 0=1.8MHz, 1=2.1MHz(기본), 2=2.0MHz, 3=1.7MHz, 4=2.2MHz
|
|
* word 2: piezo_ch (0~7, 기본값=0) - 피에조 채널 선택
|
|
*
|
|
* 응답: "rpc:" + cycles (성공), "rpc:" + 2 (범위 초과 에러)
|
|
*
|
|
* 사용 예시 (Harbour에서):
|
|
* mpc?0x05,0x00,0x01,0x00 → 5사이클 @ 2.1MHz
|
|
* mpc?0x05,0x00,0x00,0x00 → 5사이클 @ 1.8MHz
|
|
* mpc?0x07,0x00,0x02,0x00 → 7사이클 @ 2.0MHz
|
|
*/
|
|
static int Cmd_mpc(const ParsedCmd *cmd)
|
|
{
|
|
uint16_t cycles = 5; /* 기본 5사이클 */
|
|
uint16_t freq_option = 1; /* 기본 2.1MHz (0=1.8, 1=2.1, 2=2.0, 3=1.7, 4=2.2) */
|
|
uint16_t piezo_ch = 0; /* 기본 채널 0 (0~7) */
|
|
|
|
/* 파라미터 추출 (데이터 부족 시 기본값 유지) */
|
|
(void)dr_get_u16(cmd, 0, &cycles); /* word 0: 사이클 수 */
|
|
(void)dr_get_u16(cmd, 1, &freq_option); /* word 1: 주파수 옵션 */
|
|
(void)dr_get_u16(cmd, 2, &piezo_ch); /* word 2: 피에조 채널 */
|
|
|
|
/* 채널 범위 검증: 초과 시 0으로 리셋 */
|
|
if (piezo_ch >= MAA_NUM_CHANNELS) piezo_ch = 0;
|
|
|
|
/* 사이클 범위 검증: 3~7 유효 */
|
|
if (cycles < 3 || cycles > 7)
|
|
{
|
|
dr_ble_return_1("rpc:", 2); /* 에러 응답: 범위 초과 */
|
|
return 1;
|
|
}
|
|
|
|
/* MUX로 피에조 채널 선택 (0~7번 트랜스듀서 중 하나) */
|
|
dr_piezo_select_channel((uint8_t)piezo_ch);
|
|
|
|
/* 선택된 주파수로 버스트 펄스 발생 */
|
|
switch (freq_option)
|
|
{
|
|
case 0: dr_piezo_burst_sw_18mhz((uint8_t)cycles); break; /* 1.8MHz */
|
|
case 2: dr_piezo_burst_sw_20mhz((uint8_t)cycles); break; /* 2.0MHz */
|
|
case 3: dr_piezo_burst_sw_17mhz((uint8_t)cycles); break; /* 1.7MHz */
|
|
case 4: dr_piezo_burst_sw_22mhz((uint8_t)cycles); break; /* 2.2MHz */
|
|
/*case 9: dr_piezo_burst_sw_19mhz((uint8_t)cycles); break;*/ /* 1.9MHz (미사용) */
|
|
case 1:
|
|
default: dr_piezo_burst_sw((uint8_t)cycles); break; /* 2.1MHz (기본) */
|
|
}
|
|
|
|
/* 성공 응답: "rpc:" + 실행된 사이클 수 */
|
|
dr_ble_return_1("rpc:", (uint8_t)cycles);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief mec? - 피에조 버스트 + 에코 캡처 (16비트 원시 데이터, 무압축)
|
|
*
|
|
* 12비트 압축(mdc?)과 달리 ADC 원시 16비트 값을 그대로 전송
|
|
* 샘플 수가 적은 근거리 측정(~25cm)에 적합
|
|
*
|
|
* 파라미터 (Little-Endian uint16 x 6):
|
|
* word 0: freq_option (기본=0) - 주파수 선택 (0=1.8MHz, 1=2.1MHz, ...)
|
|
* word 1: delay_us (기본=20) - 버스트 후 ADC 시작 지연 (us)
|
|
* word 2: num_samples (기본=140) - ADC 샘플 수 (140샘플 ≒ 20cm 거리)
|
|
* word 3: cycles (기본=5) - 버스트 사이클 수 (3~7)
|
|
* word 4: averaging (기본=1) - 평균화 횟수 (1~1000, 노이즈 저감용)
|
|
* word 5: piezo_ch (기본=0) - 피에조 채널 (0~7)
|
|
*
|
|
* 응답 멀티패킷 형식 (16비트 원시):
|
|
* 헤더 패킷 ("reb:"): total_pkts, peak, idx, baseline, samples
|
|
* 데이터 패킷 ("red:"): pkt_idx + 16비트 ADC 원시 데이터
|
|
* 종료 패킷 ("ree:"): total_bytes_sent
|
|
*
|
|
* 16비트 포맷: 샘플당 2바이트 (Little-Endian)
|
|
* 예: 140샘플(20cm) = 280바이트 = 약 2패킷
|
|
*/
|
|
static int Cmd_mec(const ParsedCmd *cmd)
|
|
{
|
|
uint16_t freq_option = 0; /* 기본 1.8MHz */
|
|
uint16_t delay_us = 20; /* 기본 20us 딜레이 */
|
|
uint16_t num_samples = 140; /* 기본 100샘플 */
|
|
uint16_t cycles = 5; /* 기본 5사이클 (유효: 3~7) */
|
|
uint16_t averaging = 1; /* 기본 1 (평균화 없음), 최대 1000 */
|
|
uint16_t piezo_ch = 0; /* 기본 채널 0 (유효: 0~7) */
|
|
|
|
/* 피에조 전원이 꺼져 있으면 켜기 */
|
|
if (!dr_piezo_is_power_on())
|
|
{
|
|
//if (g_plat.log) g_plat.log("[Cmd_mec] TX/RX Sleep -> Active\r\n");
|
|
dr_piezo_power_on();
|
|
}
|
|
|
|
/* 6개 파라미터 순서대로 추출 (데이터 부족 시 기본값 유지) */
|
|
(void)dr_get_u16(cmd, 0, &freq_option); /* word 0: 주파수 옵션 */
|
|
(void)dr_get_u16(cmd, 1, &delay_us); /* word 1: 버스트→ADC 딜레이 */
|
|
(void)dr_get_u16(cmd, 2, &num_samples); /* word 2: ADC 샘플 수 */
|
|
(void)dr_get_u16(cmd, 3, &cycles); /* word 3: 버스트 사이클 */
|
|
(void)dr_get_u16(cmd, 4, &averaging); /* word 4: 평균화 횟수 */
|
|
(void)dr_get_u16(cmd, 5, &piezo_ch); /* word 5: 피에조 채널 */
|
|
|
|
/* 평균화 횟수 범위 검증: 1~1000 */
|
|
if (averaging == 0) averaging = 1;
|
|
if (averaging > 1000) averaging = 1000;
|
|
|
|
/* 피에조 채널 범위 검증 */
|
|
if (piezo_ch >= MAA_NUM_CHANNELS) piezo_ch = 0;
|
|
|
|
/*
|
|
if (g_plat.log && g_log_enable) {
|
|
const char *freq_str = (freq_option == 0) ? "1.8MHz" :
|
|
(freq_option == 1) ? "2.1MHz" :
|
|
(freq_option == 2) ? "2.0MHz" :
|
|
(freq_option == 3) ? "1.7MHz" :
|
|
(freq_option == 4) ? "2.2MHz" :
|
|
(freq_option == 9) ? "1.9MHz" : "unknown";
|
|
g_plat.log("[Parameter] freq=%u (%s), delay=%uus, samples=%u, cycles=%u, avg=%u, piezo=%u\r\n",
|
|
freq_option, freq_str, delay_us, num_samples, cycles, averaging, piezo_ch);
|
|
}
|
|
*/
|
|
|
|
/* 통합 버스트+캡처+전송 함수 호출 */
|
|
dr_adc_err_t err = dr_adc_burst_capture_transmit(
|
|
(uint8_t)freq_option, delay_us, num_samples, (uint8_t)cycles,
|
|
(uint16_t)averaging, (uint8_t)piezo_ch, ble_bin_buffer, 0);
|
|
|
|
/* 에러 발생 시 에러 코드 + 요청 샘플 수 응답 */
|
|
if (err != DR_ADC_OK) {
|
|
dr_ble_return_2("rer:", 0xEE00 | (uint16_t)err, num_samples);
|
|
}
|
|
|
|
/* raw 데이터 덤프
|
|
if (g_plat.log && g_log_enable) {
|
|
g_plat.log("[Cmd_mec] result=%d\r\n", err);
|
|
|
|
const uint16_t *buf = dr_adc_get_echo_buffer();
|
|
g_plat.log("[mec] CH%u raw (%u samples):\r\n", piezo_ch, num_samples);
|
|
for (uint16_t i = 0; i < num_samples; i++) {
|
|
g_plat.log("%u ", buf[i]);
|
|
if ((i + 1) % 16 == 0) g_plat.log("\r\n");
|
|
}
|
|
if (num_samples % 16 != 0) g_plat.log("\r\n");
|
|
}*/
|
|
|
|
/* 측정 완료 후 Piezo TX/RX Sleep */
|
|
dr_piezo_power_off();
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* cmd? - GPIO 핀 직접 제어 (디버그/테스트용)
|
|
*
|
|
* 파라미터 (Little-Endian uint16 x 3, 총 6바이트):
|
|
* v1: GPIO 포트 번호 (0 또는 1, nRF52는 2개 포트)
|
|
* v2: GPIO 핀 번호 (0~31)
|
|
* v3: 출력 상태 (1=HIGH, 0=LOW)
|
|
*
|
|
* 동작: 지정된 핀을 출력 모드로 설정 후 HIGH/LOW 제어
|
|
* 응답: "rmd:" + v1(포트) + v2(핀) + v3(상태) - 에코 확인
|
|
*
|
|
* 주의: 잘못된 핀 제어 시 하드웨어 손상 가능 - 디버그용으로만 사용
|
|
*/
|
|
static int Cmd_cmd(const ParsedCmd *cmd)
|
|
{
|
|
uint16_t v1, v2, v3;
|
|
uint32_t pin_number;
|
|
|
|
/* 최소 6바이트(3 워드) 필요 */
|
|
if (cmd->data_len < 6) {
|
|
dr_ble_return_1("rmd:", 0); /* 데이터 부족 에러 */
|
|
return 1;
|
|
}
|
|
|
|
/* Little Endian 파라미터 추출 */
|
|
v1 = (uint16_t)cmd->data[0] | ((uint16_t)cmd->data[1] << 8); /* 포트 (0~1) */
|
|
v2 = (uint16_t)cmd->data[2] | ((uint16_t)cmd->data[3] << 8); /* 핀 (0~31) */
|
|
v3 = (uint16_t)cmd->data[4] | ((uint16_t)cmd->data[5] << 8); /* 상태 (1=HIGH, 0=LOW) */
|
|
|
|
/* nRF52 GPIO 핀 번호 계산: port * 32 + pin */
|
|
pin_number = NRF_GPIO_PIN_MAP(v1, v2);
|
|
|
|
/* 핀을 출력 모드로 설정 */
|
|
nrf_gpio_cfg_output(pin_number);
|
|
|
|
/* HIGH 또는 LOW 출력 */
|
|
if (v3 == 1) {
|
|
nrf_gpio_pin_set(pin_number); /* HIGH 출력 */
|
|
} else {
|
|
nrf_gpio_pin_clear(pin_number); /* LOW 출력 */
|
|
}
|
|
|
|
/* 응답: 포트, 핀, 상태 에코 */
|
|
dr_ble_return_3("rmd:", v1, v2, v3);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief maa? - 4채널 전체 캡처 명령 (비동기 버전)
|
|
*
|
|
* 4개 피에조 채널(CH0~CH3)을 순차적으로 버스트+캡처하고 BLE로 전송
|
|
* 블로킹 방식 대신 비동기 상태 머신으로 구현하여 BLE 스택 안정성 확보
|
|
*
|
|
* 파라미터: mode (uint16, word 0)
|
|
* mode=0: 16비트 원시 데이터 비동기 전송 (유일하게 지원)
|
|
*
|
|
* 비동기 아키텍처:
|
|
* - maa_async_start()가 CH0 캡처 시작 및 헤더 전송
|
|
* - BLE_NUS_EVT_TX_RDY 콜백이 나머지 데이터/채널 전송 구동
|
|
* - 블로킹 없음 → SoftDevice 이벤트 정상 처리
|
|
* - BLE TX 버퍼 오버플로우로 인한 펌웨어 브릭 방지
|
|
*
|
|
* 고정 파라미터 (하드코딩):
|
|
* 주파수 = 1.8MHz, 딜레이 = 10us, 샘플 수 = 140
|
|
* 사이클 = 7, 평균화 = 5회
|
|
*
|
|
* 응답 형식:
|
|
* 각 채널(CH0~CH3)마다:
|
|
* reb: [총패킷수(2)] [피크(2)] [인덱스(2)] [기준선(2)] [샘플수(2)]
|
|
* red: [패킷순번(2)] [데이터...]
|
|
* 전체 완료:
|
|
* raa: [상태(2)]
|
|
*
|
|
* 버전 마커: 0xA000 (vA) = 비동기 4채널
|
|
*/
|
|
/* 피에조 캡처 파라미터: FDS(m_config)에서 로드, 앱에서 변경 가능 */
|
|
|
|
static int Cmd_maa(const ParsedCmd *cmd)
|
|
{
|
|
dr_adc_err_t err;
|
|
|
|
/* 이전 캡처가 진행 중인지 확인 (비동기이므로 중복 실행 방지) */
|
|
if (maa_async_is_busy())
|
|
{
|
|
dr_ble_return_1("raa:", 0xFFFE); /* 처리 중(Busy) 에러 */
|
|
return 1;
|
|
}
|
|
|
|
/* Piezo TX/RX 전원: OFF일 경우 ON → maa_async_start()에서 캡처 완료 후 OFF */
|
|
if (!dr_piezo_is_power_on())
|
|
{
|
|
//if (g_plat.log) g_plat.log("[Cmd_maa] TX/RX Sleep -> Active\r\n");
|
|
dr_piezo_power_on();
|
|
}
|
|
|
|
/*=======================================================================
|
|
* 비동기 6채널 캡처 시작
|
|
* - maa_async_start(): CH0 캡처 실행 + 첫 헤더 패킷 전송
|
|
* - 이후 패킷은 BLE_NUS_EVT_TX_RDY 콜백에서 자동 전송
|
|
*=======================================================================*/
|
|
err = maa_async_start(
|
|
m_config.piezo_freq_option,
|
|
m_config.piezo_delay_us,
|
|
m_config.piezo_num_samples,
|
|
m_config.piezo_cycles,
|
|
m_config.piezo_averaging,
|
|
ble_bin_buffer
|
|
);
|
|
|
|
if (err != DR_ADC_OK)
|
|
{
|
|
/* 시작 실패 → 에러 응답 전송 (앱 타임아웃 방지) */
|
|
if (g_plat.log) g_plat.log("[Cmd_maa] start failed err=%d\r\n", err);
|
|
single_format_data(ble_bin_buffer, "raa:", (uint16_t)(0xFF00 | err));
|
|
dr_binary_tx_safe(ble_bin_buffer, 3);
|
|
dr_piezo_power_off();
|
|
return 1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief mbb? 비동기 캡처 완료 후 콜백 - 센서 측정 체인 실행
|
|
*
|
|
* raa: 전송 + 피에조 전원 OFF 이후 호출
|
|
* info4 모드로 센서값을 전역 변수에 저장한 뒤, rbb: 패킷으로 일괄 전송
|
|
* SAADC 측정은 비동기(콜백)이므로 dr_sd_delay_ms()로 완료 대기
|
|
*
|
|
* 순서: 배터리 → IMU → (Piezo TX/RX ON) → 온도
|
|
* 응답: rbb: [배터리mV(2)] [IMU 6축(12)] [온도°Cx100(2)] = 20바이트
|
|
*/
|
|
static void all_sensors(void)
|
|
{
|
|
info4 = true; /* 센서값을 전역 변수에 저장 (BLE 전송 안 함) */
|
|
|
|
/* 1. 배터리 전압 측정 → info_batt에 저장 */
|
|
battery_level_meas();
|
|
dr_sd_delay_ms(1); /* SAADC 콜백 완료 대기 */
|
|
|
|
/* 2. IMU 6축 단발 읽기 → info_imu[6]에 저장 */
|
|
hw_i2c_init_once();
|
|
imu_read_direct();
|
|
|
|
/* 3. 온도 측정 → info_temp에 저장 (TMP235는 Piezo TX/RX 전원 필요) */
|
|
if (!dr_piezo_is_power_on())
|
|
{
|
|
dr_piezo_power_on();
|
|
}
|
|
|
|
extern volatile bool tmp235_saadc_done;
|
|
tmp235_saadc_done = false;
|
|
tmp235_voltage_level_meas();
|
|
while (!tmp235_saadc_done) { dr_sd_delay_ms(1); } /* SAADC 콜백 완료 대기 */
|
|
|
|
info4 = false;
|
|
|
|
/* rbb: 패킷 조립 및 전송 : [TAG 4B] [배터리 2B] [IMU 12B] [온도 2B] = 20바이트 = 10워드 */
|
|
uint8_t *buf = ble_bin_buffer;
|
|
|
|
buf[0] = 'r'; buf[1] = 'b'; buf[2] = 'b'; buf[3] = ':';
|
|
buf[4] = (uint8_t)(info_batt & 0xFF);
|
|
buf[5] = (uint8_t)(info_batt >> 8);
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
buf[6 + i * 2] = (uint8_t)(info_imu[i] & 0xFF);
|
|
buf[6 + i * 2 + 1] = (uint8_t)(info_imu[i] >> 8);
|
|
}
|
|
|
|
buf[18] = (uint8_t)(info_temp & 0xFF);
|
|
buf[19] = (uint8_t)(info_temp >> 8);
|
|
|
|
dr_binary_tx_safe(buf, 10); /* 20바이트 = 10워드 */
|
|
|
|
/* 배터리, IMU, 온도 확인용 로그 */
|
|
//if (g_plat.log) g_plat.log("-------------------------------------------------------------------------------------\r\n[Battery] %u\r\n[IMU] %d,%d,%d,%d,%d,%d\r\n[Temperature] %u\r\n\r\n", info_batt, (int16_t)info_imu[0], (int16_t)info_imu[1], (int16_t)info_imu[2],
|
|
//(int16_t)info_imu[3], (int16_t)info_imu[4], (int16_t)info_imu[5], info_temp);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief mbb? - 6채널 전체 캡처 + 센서 측정 (배터리/온도/IMU)
|
|
*
|
|
* 센서 측정(rbb:) → 6채널 비동기 캡처(reb:/red:/raa:) → TX/RX OFF
|
|
*
|
|
* 응답 흐름:
|
|
* 1) 센서 측정: rbb: [배터리(2) + IMU 6축(12) + 온도(2)]
|
|
* 2) 각 채널(CH0~CH5): reb: [헤더] → red: [데이터...]
|
|
* 3) 캡처 완료: raa: [상태]
|
|
*/
|
|
|
|
static int Cmd_mbb(const ParsedCmd *cmd)
|
|
{
|
|
dr_adc_err_t err;
|
|
|
|
all_sensors(); /* 배터리, IMU, 온도 센서 먼저 측정 */
|
|
|
|
if (maa_async_is_busy())
|
|
{
|
|
dr_ble_return_1("raa:", 0xFFFE);
|
|
return 1;
|
|
}
|
|
|
|
/* 측정 파라미터 확인용 로그
|
|
if (g_plat.log) g_plat.log("[Parameter] freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n\r\n",
|
|
m_config.piezo_freq_option, m_config.piezo_cycles,
|
|
m_config.piezo_averaging, m_config.piezo_delay_us,
|
|
m_config.piezo_num_samples);*/
|
|
|
|
/* 전채널 선캡처 모드: 모든 채널 캡처 완료 후 BLE 전송하도록 */
|
|
maa_async_set_pre_capture_all(true);
|
|
|
|
/* 비동기 6채널 캡처 시작 (FDS m_config 파라미터 사용) */
|
|
err = maa_async_start(
|
|
m_config.piezo_freq_option,
|
|
m_config.piezo_delay_us,
|
|
m_config.piezo_num_samples,
|
|
m_config.piezo_cycles,
|
|
m_config.piezo_averaging,
|
|
ble_bin_buffer
|
|
);
|
|
|
|
if (err != DR_ADC_OK)
|
|
{
|
|
if (g_plat.log) g_plat.log("[Cmd_mbb] start failed err=%d\r\n", err);
|
|
single_format_data(ble_bin_buffer, "raa:", (uint16_t)(0xFF00 | err));
|
|
dr_binary_tx_safe(ble_bin_buffer, 3);
|
|
dr_piezo_power_off();
|
|
return 1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief mcf? - 피에조 파라미터 읽기 (FDS)
|
|
*
|
|
* 응답: rcf: [freq(2)] [cycles(2)] [avg(2)] [delay_us(2)] [num_samples(2)] = 14바이트 = 7워드
|
|
*/
|
|
static int Cmd_mcf(const ParsedCmd *cmd)
|
|
{
|
|
(void)cmd;
|
|
|
|
uint8_t *buf = ble_bin_buffer;
|
|
buf[0] = 'r'; buf[1] = 'c'; buf[2] = 'f'; buf[3] = ':';
|
|
buf[4] = m_config.piezo_freq_option; buf[5] = 0;
|
|
buf[6] = m_config.piezo_cycles; buf[7] = 0;
|
|
buf[8] = (uint8_t)(m_config.piezo_averaging & 0xFF); buf[9] = (uint8_t)(m_config.piezo_averaging >> 8);
|
|
buf[10] = (uint8_t)(m_config.piezo_delay_us & 0xFF); buf[11] = (uint8_t)(m_config.piezo_delay_us >> 8);
|
|
buf[12] = (uint8_t)(m_config.piezo_num_samples & 0xFF); buf[13] = (uint8_t)(m_config.piezo_num_samples >> 8);
|
|
dr_binary_tx_safe(buf, 7); /* 14바이트 = 7워드 */
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief mcs? - 피에조 파라미터 쓰기 (FDS)
|
|
*
|
|
* 파라미터: [freq(2)] [cycles(2)] [avg(2)] [delay_us(2)] [num_samples(2)] = 10바이트
|
|
* 응답: rcs: [저장된 5개 값] = 14바이트 = 7워드
|
|
*/
|
|
static int Cmd_mcs(const ParsedCmd *cmd)
|
|
{
|
|
if (cmd->data_len < 10)
|
|
{
|
|
if (g_plat.log) g_plat.log("[Cmd_mcs] missing params (data_len=%u)\r\n", cmd->data_len);
|
|
dr_ble_return_1("rcs:", 0xFFFF);
|
|
return 1;
|
|
}
|
|
|
|
uint16_t freq, cycles, averaging, delay_us, num_samples;
|
|
dr_get_u16(cmd, 0, &freq);
|
|
dr_get_u16(cmd, 1, &cycles);
|
|
dr_get_u16(cmd, 2, &averaging);
|
|
dr_get_u16(cmd, 3, &delay_us);
|
|
dr_get_u16(cmd, 4, &num_samples);
|
|
|
|
m_config.piezo_freq_option = (uint8_t)freq;
|
|
m_config.piezo_cycles = (uint8_t)cycles;
|
|
m_config.piezo_averaging = averaging;
|
|
m_config.piezo_delay_us = delay_us;
|
|
m_config.piezo_num_samples = num_samples;
|
|
config_save();
|
|
|
|
/*
|
|
if (g_plat.log) g_plat.log("[Cmd_mcs] saved: freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n",
|
|
m_config.piezo_freq_option, m_config.piezo_cycles,
|
|
m_config.piezo_averaging, m_config.piezo_delay_us,
|
|
m_config.piezo_num_samples);
|
|
*/
|
|
|
|
uint8_t *buf = ble_bin_buffer;
|
|
buf[0] = 'r'; buf[1] = 'c'; buf[2] = 's'; buf[3] = ':';
|
|
buf[4] = m_config.piezo_freq_option; buf[5] = 0;
|
|
buf[6] = m_config.piezo_cycles; buf[7] = 0;
|
|
buf[8] = (uint8_t)(m_config.piezo_averaging & 0xFF); buf[9] = (uint8_t)(m_config.piezo_averaging >> 8);
|
|
buf[10] = (uint8_t)(m_config.piezo_delay_us & 0xFF); buf[11] = (uint8_t)(m_config.piezo_delay_us >> 8);
|
|
buf[12] = (uint8_t)(m_config.piezo_num_samples & 0xFF); buf[13] = (uint8_t)(m_config.piezo_num_samples >> 8);
|
|
dr_binary_tx_safe(buf, 7); /* 14바이트 = 7워드 */
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*==============================================================================
|
|
* 설정: HW/시리얼 넘버 FDS 읽기/쓰기
|
|
*
|
|
* FDS(Flash Data Storage)에 디바이스 식별 정보를 영구 저장
|
|
* 제조 시 공장에서 한 번 기록하고, 이후 읽기로 확인
|
|
*============================================================================*/
|
|
|
|
/* mwh? - HW 넘버 FDS에 쓰기
|
|
*
|
|
* 데이터: 12바이트 ASCII HW 넘버 (예: "VB2025A00001")
|
|
* 응답: "rwh:" + 저장된 HW 넘버 에코
|
|
*/
|
|
static int Cmd_mwh(const ParsedCmd *cmd)
|
|
{
|
|
char buf[13];
|
|
|
|
/* 최소 12바이트 데이터 필요 */
|
|
if (cmd->data_len < 12)
|
|
{
|
|
dr_ble_return_1("rwh:", 0xFFFF); /* 데이터 부족 에러 */
|
|
return 1;
|
|
}
|
|
|
|
/* ASCII 추출 → 전역 변수 + 설정 구조체에 복사 → FDS 저장 */
|
|
dr_get_ascii(cmd, 0, buf, 12);
|
|
memcpy(HW_NO, buf, 12);
|
|
memcpy(m_config.hw_no, buf, 12);
|
|
config_save(); /* FDS(Flash Memory)에 저장 */
|
|
|
|
ascii_format_data(ble_bin_buffer, "rwh:", buf, 12);
|
|
dr_binary_tx_safe(ble_bin_buffer, 8);
|
|
return 1;
|
|
}
|
|
|
|
/* mws? - 시리얼 넘버 FDS에 쓰기
|
|
*
|
|
* 데이터: 12바이트 ASCII 시리얼 넘버
|
|
* 응답: "rws:" + 저장된 시리얼 넘버 에코
|
|
*/
|
|
static int Cmd_mws(const ParsedCmd *cmd)
|
|
{
|
|
char buf[13];
|
|
|
|
if (cmd->data_len < 12) {
|
|
dr_ble_return_1("rws:", 0xFFFF); /* 데이터 부족 에러 */
|
|
return 1;
|
|
}
|
|
|
|
dr_get_ascii(cmd, 0, buf, 12);
|
|
memcpy(SERIAL_NO, buf, 12);
|
|
memcpy(m_config.serial_no, buf, 12);
|
|
config_save(); /* FDS(Flash Memory)에 저장 */
|
|
|
|
ascii_format_data(ble_bin_buffer, "rws:", buf, 12);
|
|
dr_binary_tx_safe(ble_bin_buffer, 8);
|
|
return 1;
|
|
}
|
|
|
|
/* mrh? - HW 넘버 FDS에서 읽기
|
|
* FDS의 설정 구조체에서 HW 넘버를 읽어 BLE로 응답 */
|
|
static int Cmd_mrh(const ParsedCmd *cmd)
|
|
{
|
|
(void)cmd;
|
|
memcpy(HW_NO, m_config.hw_no, 12); /* FDS 설정 → 전역 변수 동기화 */
|
|
ascii_format_data(ble_bin_buffer, "rrh:", HW_NO, 12);
|
|
dr_binary_tx_safe(ble_bin_buffer, 8); /* 4(TAG) + 12(HW넘버) = 16바이트 = 8워드 */
|
|
return 1;
|
|
}
|
|
|
|
/* mrs? - 시리얼 넘버 FDS에서 읽기 */
|
|
static int Cmd_mrs(const ParsedCmd *cmd)
|
|
{
|
|
(void)cmd;
|
|
memcpy(SERIAL_NO, m_config.serial_no, 12); /* FDS 설정 → 전역 변수 동기화 */
|
|
ascii_format_data(ble_bin_buffer, "rrs:", SERIAL_NO, 12);
|
|
dr_binary_tx_safe(ble_bin_buffer, 8);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*==============================================================================
|
|
* 패스키: BLE 페어링 시 사용하는 6자리 숫자 (예: "123456")
|
|
*============================================================================*/
|
|
|
|
/* mpz? - BLE 패스키 FDS에 쓰기
|
|
* 패킷: mpz? + ASCII 6자리 패스키 (예: "654321")
|
|
* 응답: rpz: + 저장된 패스키 에코 (6바이트)
|
|
*/
|
|
static int Cmd_mpz(const ParsedCmd *cmd)
|
|
{
|
|
char passkey[7] = {0};
|
|
dr_get_ascii(cmd, 0, passkey, 6);
|
|
|
|
memcpy(m_static_passkey, passkey, 6);
|
|
memcpy(m_config.static_passkey, m_static_passkey, 6);
|
|
config_save(); /* FDS(Flash Memory)에 저장 */
|
|
|
|
ascii_format_data(ble_bin_buffer, "rpz:", passkey, 6);
|
|
dr_binary_tx_safe(ble_bin_buffer, 5);
|
|
return 1;
|
|
}
|
|
|
|
/* mqz? - BLE 패스키 FDS에서 읽기
|
|
* 패킷: mqz? (파라미터 없음)
|
|
* 응답: rqz: + 현재 저장된 패스키 (ASCII 6바이트)
|
|
*/
|
|
static int Cmd_mqz(const ParsedCmd *cmd)
|
|
{
|
|
(void)cmd;
|
|
memcpy(m_static_passkey, m_config.static_passkey, 6);
|
|
|
|
ascii_format_data(ble_bin_buffer, "rqz:", m_static_passkey, 6);
|
|
dr_binary_tx_safe(ble_bin_buffer, 5);
|
|
return 1;
|
|
}
|
|
|
|
/*==============================================================================
|
|
* IMU: 6축 원시 데이터 (단발 읽기)
|
|
*============================================================================*/
|
|
|
|
/* msp? - IMU 가속도(xyz) + 자이로(xyz) 원시 데이터 단발 읽기
|
|
*
|
|
* I2C로 IMU 레지스터를 직접 읽어 BLE로 전송
|
|
* 타이머/인터럽트 없이 즉시 실행 (동기 방식)
|
|
*
|
|
* 응답: rsp: + 6 x uint16_t
|
|
* [accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z]
|
|
*/
|
|
static int Cmd_msp(const ParsedCmd *cmd)
|
|
{
|
|
(void)cmd;
|
|
|
|
hw_i2c_init_once(); /* I2C 버스 초기화 (최초 1회) */
|
|
|
|
/* IMU 레지스터 직접 읽기 - 타이머, DRDY 인터럽트, 콜백 없이 동기 실행 */
|
|
int rc = imu_read_direct();
|
|
|
|
return 1;
|
|
}
|