|
|
|
@ -44,6 +44,10 @@ Includes |
|
|
|
#include <stddef.h> |
|
|
|
#include <stdio.h> |
|
|
|
#include <r_cg_port.h> |
|
|
|
#include "app_types.h" |
|
|
|
#include "app_cmd_parser.h" |
|
|
|
#include "app_scheduler.h" |
|
|
|
#include "app_owi_service.h" |
|
|
|
/* End user code. Do not edit comment generated here */ |
|
|
|
#include "r_cg_userdefine.h" |
|
|
|
/* Start user code for adding. Do not edit comment generated here */ |
|
|
|
@ -54,14 +58,14 @@ Includes |
|
|
|
#define UART_RX_BUF_SIZE 1024 |
|
|
|
#define RS485_BRIDGE_FIFO_SZ 2048 |
|
|
|
|
|
|
|
#define BRIDGE_IDLE_DONE_US 5000U // 마지막 바이트 이후 3ms 조용하면 종료로 판단
|
|
|
|
#define BRIDGE_TOTAL_WAIT_US 600000U |
|
|
|
#define BRIDGE_IDLE_DONE_US 5000U // ? 3ms ? ?
|
|
|
|
#define BRIDGE_TOTAL_WAIT_US 1290000U |
|
|
|
|
|
|
|
/* =========================
|
|
|
|
* UART RX Buffers |
|
|
|
* ========================= */ |
|
|
|
volatile uint8_t uart_rx_done = 0; |
|
|
|
volatile uint16_t uart_rx_index = 0; // ★ uint8_t -> uint16_t
|
|
|
|
volatile uint16_t uart_rx_index = 0; // uint8_t -> uint16_t
|
|
|
|
volatile uint8_t uart_rx_buffer[UART_RX_BUF_SIZE] = {0}; |
|
|
|
volatile uint16_t uart_rx_length = 0; |
|
|
|
|
|
|
|
@ -88,8 +92,8 @@ uint8_t g_fixed_addr = 0; |
|
|
|
/* =========================
|
|
|
|
* Driver globals (Renesas) |
|
|
|
* ========================= */ |
|
|
|
extern volatile uint16_t g_uart1_tx_count; /* UART1 TX 진행 카운트 */ |
|
|
|
extern volatile uint16_t g_uart0_tx_count; /* UART0 TX 진행 카운트 */ |
|
|
|
extern volatile uint16_t g_uart1_tx_count; /* UART1 TX ?? */ |
|
|
|
extern volatile uint16_t g_uart0_tx_count; /* UART0 TX ?? */ |
|
|
|
|
|
|
|
extern volatile uint8_t g_uart0_tx_done; |
|
|
|
|
|
|
|
@ -126,7 +130,7 @@ static int RS485_Bridge_ReadLine(char *out, int out_sz, uint32_t timeout_us) |
|
|
|
int n = 0; |
|
|
|
|
|
|
|
while (timeout_us--) { |
|
|
|
// FIFO에 데이터가 있으면 1바이트씩 꺼내서 line 구성
|
|
|
|
// FIFO
|
|
|
|
while (s_rb_tail != s_rb_head) { |
|
|
|
char c = (char)s_rb_fifo[s_rb_tail]; |
|
|
|
s_rb_tail++; |
|
|
|
@ -138,7 +142,7 @@ static int RS485_Bridge_ReadLine(char *out, int out_sz, uint32_t timeout_us) |
|
|
|
|
|
|
|
if (c == '\n') { |
|
|
|
out[n] = '\0'; |
|
|
|
return 1; // line 완성
|
|
|
|
return 1; // line
|
|
|
|
} |
|
|
|
} |
|
|
|
delay_us(1); |
|
|
|
@ -152,7 +156,6 @@ static int Is_V_Response_For(char *line, uint8_t addr) |
|
|
|
{ |
|
|
|
uint8_t a; |
|
|
|
|
|
|
|
// line 예: "V02\n" 또는 "V02\r\n" (CR은 위에서 제거됨)
|
|
|
|
if (line[0] != 'V' && line[0] != 'v') return 0; |
|
|
|
if (!isdigit((unsigned char)line[1]) || !isdigit((unsigned char)line[2])) return 0; |
|
|
|
|
|
|
|
@ -162,9 +165,8 @@ static int Is_V_Response_For(char *line, uint8_t addr) |
|
|
|
|
|
|
|
|
|
|
|
/* =========================
|
|
|
|
* ? PC(UART1) Safe TX (중요) |
|
|
|
* - 로컬 버퍼 금지 |
|
|
|
* - 항상 전역 버퍼로 복사 후 R_UART1_Send |
|
|
|
* PC(UART1) Safe TX |
|
|
|
* R_UART1_Send |
|
|
|
* ========================= */ |
|
|
|
static uint8_t g_uart1_txbuf[1024]; |
|
|
|
|
|
|
|
@ -172,7 +174,7 @@ static void UART1_WaitTxIdle(void) |
|
|
|
{ |
|
|
|
unsigned long guard = 0; |
|
|
|
while (g_uart1_tx_count != 0U) { |
|
|
|
if (guard++ > 3000000UL) break; /* 무한루프 방지 */ |
|
|
|
if (guard++ > 3000000UL) break; /* ? */ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -200,7 +202,7 @@ static void UART1_SendString_Safe(const char *s) |
|
|
|
|
|
|
|
if (s == 0) return; |
|
|
|
|
|
|
|
UART1_WaitTxIdle(); // ? 보내기 전 대기
|
|
|
|
UART1_WaitTxIdle(); // ?
|
|
|
|
|
|
|
|
len = (uint16_t)strlen(s); |
|
|
|
if (len >= (uint16_t)sizeof(g_uart1_txbuf)) |
|
|
|
@ -211,22 +213,21 @@ static void UART1_SendString_Safe(const char *s) |
|
|
|
|
|
|
|
R_UART1_Send(g_uart1_txbuf, len); |
|
|
|
|
|
|
|
UART1_WaitTxIdle(); // ? ★ 추가: 보내고 난 뒤에도 대기 (버퍼 덮어쓰기 방지)
|
|
|
|
UART1_WaitTxIdle(); // ? ?: ? ( ? )
|
|
|
|
} |
|
|
|
|
|
|
|
/* =========================
|
|
|
|
* RS485 Bridge Drain -> PC |
|
|
|
* - uart1_send_string(out) 같은 1바이트 문자열 송신 금지 |
|
|
|
* - UART1_SendBytes_Safe로 한 바이트씩이라도 "안전하게" 전송 |
|
|
|
* - uart1_send_string(out) |
|
|
|
* - UART1_SendBytes_Safe |
|
|
|
* ========================= */ |
|
|
|
static void RS485_Bridge_DrainToPC(void) |
|
|
|
{ |
|
|
|
uint16_t n = 0; |
|
|
|
|
|
|
|
/* UART1이 TX중이면 다음 루프에서 */ |
|
|
|
/* UART1 TX */ |
|
|
|
if (g_uart1_tx_count != 0U) return; |
|
|
|
|
|
|
|
/* FIFO -> 임시 버퍼에 최대 64바이트까지 모아서 한 번에 전송 */ |
|
|
|
while (s_rb_tail != s_rb_head && n < 1024U) { |
|
|
|
g_uart1_txbuf[n++] = s_rb_fifo[s_rb_tail]; |
|
|
|
s_rb_tail++; |
|
|
|
@ -262,6 +263,15 @@ typedef enum { |
|
|
|
|
|
|
|
static PrefixMode s_prefix_mode = PREFIX_NONE; |
|
|
|
|
|
|
|
typedef struct { |
|
|
|
uint8_t active; |
|
|
|
uint8_t scan_started; |
|
|
|
uint8_t current_scan_addr; |
|
|
|
app_job_t job; |
|
|
|
} app_runtime_job_t; |
|
|
|
|
|
|
|
static app_runtime_job_t g_app_runtime_job; |
|
|
|
|
|
|
|
/* =========================
|
|
|
|
* parse x-prefix: xNNc_001011:... |
|
|
|
* ========================= */ |
|
|
|
@ -321,15 +331,11 @@ static int bridge_wait_until_end(uint32_t total_us) |
|
|
|
uint8_t st = 0; |
|
|
|
|
|
|
|
while (total_us--) { |
|
|
|
// FIFO -> PC 전송
|
|
|
|
// FIFO -> PC
|
|
|
|
while (s_rb_tail != s_rb_head) { |
|
|
|
char c = (char)s_rb_fifo[s_rb_tail]; |
|
|
|
s_rb_tail = (s_rb_tail + 1) % RS485_BRIDGE_FIFO_SZ; |
|
|
|
|
|
|
|
// PC로 바로 출력(버퍼링은 기존처럼 모아서 보내도 OK)
|
|
|
|
// 여기서는 단순화를 위해 1바이트라 가정하지 말고 기존 256모아서 보내는 함수 유지 권장
|
|
|
|
|
|
|
|
// 패턴 매칭
|
|
|
|
if (c == pat[st]) { |
|
|
|
st++; |
|
|
|
if (pat[st] == 0) return 1; // found
|
|
|
|
@ -351,21 +357,20 @@ typedef enum { |
|
|
|
CMD_SRC_RS485 = 1 |
|
|
|
} CmdSource; |
|
|
|
|
|
|
|
/* ? PC 출력은 무조건 Safe로 */ |
|
|
|
static void OUT_PRINT(CmdSource src, const char *s) |
|
|
|
{ |
|
|
|
if (src == CMD_SRC_PC) { |
|
|
|
UART1_SendString_Safe(s); |
|
|
|
} else { |
|
|
|
/* RS485_PRINT는 네 프로젝트 기존 함수(보통 UART0 TX) */ |
|
|
|
/* RS485_PRINT ? ?( UART0 TX) */ |
|
|
|
RS485_PRINT(s); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void send_end_response(void) |
|
|
|
static void send_end_response(CmdSource src) |
|
|
|
{ |
|
|
|
OUT_PRINT(CMD_SRC_PC, "<end>\r\n"); |
|
|
|
OUT_PRINT(src, "<end>\r\n"); |
|
|
|
} |
|
|
|
/* =========================
|
|
|
|
* xNNv command |
|
|
|
@ -404,7 +409,7 @@ static int parse_x_o_cmd(const char *s, int len, uint8_t *addr) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* ? 여기서도 OUT_PRINT가 Safe를 타므로 깨짐 없음 */ |
|
|
|
/* OUT_PRINT Safe */ |
|
|
|
static void send_v_response(CmdSource src, uint8_t addr) |
|
|
|
{ |
|
|
|
char resp[8]; |
|
|
|
@ -417,7 +422,7 @@ static void send_v_response(CmdSource src, uint8_t addr) |
|
|
|
resp[5] = '\0'; |
|
|
|
|
|
|
|
if (src == CMD_SRC_RS485) { |
|
|
|
delay_us(600); // 200~500us 권장 (환경 따라 조정)
|
|
|
|
delay_us(1200); // 200~500us
|
|
|
|
} |
|
|
|
|
|
|
|
OUT_PRINT(src, resp); |
|
|
|
@ -432,7 +437,6 @@ static void UART0_WaitTxDone_Flag(uint32_t guard_us) |
|
|
|
|
|
|
|
static void RS485_FlushJunk(uint16_t loops, uint16_t step_us) |
|
|
|
{ |
|
|
|
// 스캔 시작 전에, 들어와 있던 CR/LF/노이즈를 PC로 빼고 FIFO도 비움
|
|
|
|
while (loops--) { |
|
|
|
RS485_Bridge_DrainToPC(); |
|
|
|
delay_us(step_us); |
|
|
|
@ -445,7 +449,7 @@ static void PC_PrintLine_CRLF(const char *line) |
|
|
|
size_t n = strlen(line); |
|
|
|
size_t i; |
|
|
|
|
|
|
|
// line이 '\n'로 끝나면, "\r\n"로 보정
|
|
|
|
// line '\n' , "\r\n"
|
|
|
|
if (n > 0 && line[n-1] == '\n') { |
|
|
|
size_t m = 0; |
|
|
|
for (i=0; i<n && m < sizeof(tmp)-3; i++) { |
|
|
|
@ -464,9 +468,6 @@ static void PC_PrintLine_CRLF(const char *line) |
|
|
|
/* =========================
|
|
|
|
* RS485 scan: xNNv\r\n |
|
|
|
* ========================= */ |
|
|
|
/* =========================
|
|
|
|
* RS485 scan: xNNv\r\n (pattern-based, no line needed) |
|
|
|
* ========================= */ |
|
|
|
static void scan_one_addr_rs485(uint8_t addr) |
|
|
|
{ |
|
|
|
char cmdline[8]; |
|
|
|
@ -482,65 +483,52 @@ static void scan_one_addr_rs485(uint8_t addr) |
|
|
|
cmdline[5] = '\n'; |
|
|
|
cmdline[6] = '\0'; |
|
|
|
|
|
|
|
/* ? try 횟수 줄이기: 속도 핵심 */ |
|
|
|
for (try = 0; try < 3; try++) |
|
|
|
{ |
|
|
|
RS485_FlushJunk(2, 100); |
|
|
|
|
|
|
|
for (try = 0; try < 2; try++) { |
|
|
|
if (g_rs485_need_recover) { |
|
|
|
rs485_recover(); |
|
|
|
} |
|
|
|
|
|
|
|
{ |
|
|
|
/* --- bridge 준비 --- */ |
|
|
|
g_rs485_bridge_active = 1; |
|
|
|
g_rs485_bridge_done = 0; |
|
|
|
RS485_Bridge_ResetFifo(); |
|
|
|
|
|
|
|
/* RX arm (브릿지 모드는 항상 buffer[0]로 1바이트 받는 구조여야 함) */ |
|
|
|
rs485_rx_done = 0; |
|
|
|
rs485_rx_index = 0; |
|
|
|
rs485_rx_length = 0; |
|
|
|
R_UART0_Receive((uint8_t*)&rs485_rx_buffer[0], 1); |
|
|
|
|
|
|
|
/* ---- 송신 ---- */ |
|
|
|
g_uart0_tx_done = 0; |
|
|
|
RS485_PRINT(cmdline); |
|
|
|
|
|
|
|
/* TX 완료 대기 (너무 길게 잡지 말기) */ |
|
|
|
UART0_WaitTxDone_Flag(2500); // 2.5ms 가드
|
|
|
|
rs485_set_tx(0); // 혹시 몰라 강제 RX
|
|
|
|
delay_us(80); // turnaround (환경 따라 50~150us)
|
|
|
|
UART0_WaitTxDone_Flag(5000); |
|
|
|
rs485_set_tx(0); |
|
|
|
delay_us(250); |
|
|
|
|
|
|
|
/* ---- 응답 대기 (짧게) ---- */ |
|
|
|
{ |
|
|
|
uint32_t total_wait_us = 2500; // ? 2.5ms면 대부분 충분
|
|
|
|
uint32_t total_wait_us = 8000; |
|
|
|
char linebuf[32]; |
|
|
|
|
|
|
|
while (total_wait_us > 0) { |
|
|
|
/* 0.5ms씩 끊어 보기 */ |
|
|
|
int got = RS485_Bridge_ReadLine(linebuf, sizeof(linebuf), 500); |
|
|
|
if (got) { |
|
|
|
if (Is_V_Response_For(linebuf, addr)) { |
|
|
|
int got = RS485_Bridge_ReadLine(linebuf, sizeof(linebuf), 1000); |
|
|
|
if (got && Is_V_Response_For(linebuf, addr)) { |
|
|
|
PC_PrintLine_CRLF(linebuf); |
|
|
|
g_rs485_bridge_active = 0; |
|
|
|
return; |
|
|
|
} |
|
|
|
/* 다른 라인(쓰레기/에코)면 계속 */ |
|
|
|
} |
|
|
|
total_wait_us -= 500; |
|
|
|
|
|
|
|
if (total_wait_us >= 1000) total_wait_us -= 1000; |
|
|
|
else total_wait_us = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
g_rs485_bridge_active = 0; |
|
|
|
} |
|
|
|
|
|
|
|
// 실패 원인이 에러/꼬임이면 복구 후 재시도
|
|
|
|
if (g_rs485_need_recover) { |
|
|
|
rs485_recover(); |
|
|
|
} |
|
|
|
delay_us(500); |
|
|
|
} |
|
|
|
|
|
|
|
/* 실패 시 Nxx */ |
|
|
|
send_n_response(addr); |
|
|
|
|
|
|
|
/* 다음을 위해 RX 재arm */ |
|
|
|
rs485_rx_done = 0; |
|
|
|
rs485_rx_index = 0; |
|
|
|
rs485_rx_length = 0; |
|
|
|
@ -548,7 +536,6 @@ static void scan_one_addr_rs485(uint8_t addr) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* =========================
|
|
|
|
* Build line from rx buffer |
|
|
|
* ========================= */ |
|
|
|
@ -599,7 +586,199 @@ static void cmd_unknown(const unsigned char *d, unsigned int len) |
|
|
|
delay(100000); |
|
|
|
} |
|
|
|
|
|
|
|
/* 여기는 네 프로젝트 기존 함수들 호출 */ |
|
|
|
static void print_owi_write_result(CmdSource src, const app_owi_result_t *r) |
|
|
|
{ |
|
|
|
(void)r; |
|
|
|
OUT_PRINT(src, "51\r\n"); |
|
|
|
} |
|
|
|
|
|
|
|
static void print_owi_read_result(CmdSource src, const app_owi_result_t *r) |
|
|
|
{ |
|
|
|
char out[(2 * OWI_IO_MAX_BYTES) + 40]; |
|
|
|
uint16_t p = 0; |
|
|
|
uint16_t i; |
|
|
|
|
|
|
|
if (!r) { |
|
|
|
OUT_PRINT(src, "Err:owi_null\r\n"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (r->read_len == 0) { |
|
|
|
OUT_PRINT(src, "Err:read_len_nonzero\r\n"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
for (i = 0; i < r->read_len && i < OWI_IO_MAX_BYTES; i++) { |
|
|
|
uint8_t b = r->data[i]; |
|
|
|
out[p++] = "0123456789ABCDEF"[b >> 4]; |
|
|
|
out[p++] = "0123456789ABCDEF"[b & 0x0F]; |
|
|
|
} |
|
|
|
|
|
|
|
if (r->timeout) { |
|
|
|
p += (uint16_t)sprintf(&out[p], " !TO(B%u b%u)", |
|
|
|
(unsigned)r->timeout_byte_index, |
|
|
|
(unsigned)r->timeout_bit_index); |
|
|
|
} |
|
|
|
|
|
|
|
out[p++] = '\r'; |
|
|
|
out[p++] = '\n'; |
|
|
|
out[p] = '\0'; |
|
|
|
|
|
|
|
OUT_PRINT(src, out); |
|
|
|
} |
|
|
|
|
|
|
|
static int execute_owi_service_from_line(CmdSource src, const char *line) |
|
|
|
{ |
|
|
|
uint8_t addr = 0, ch = 0; |
|
|
|
char mode = 0; |
|
|
|
uint8_t hash_on = 0, anaout_on = 0, check_on = 1; |
|
|
|
int payload_pos = 0; |
|
|
|
int len; |
|
|
|
|
|
|
|
ProtocolType proto; |
|
|
|
uint8_t id = 0; |
|
|
|
unsigned int byte_len = 0; |
|
|
|
int pos; |
|
|
|
uint8_t cmd[CMD_MAX]; |
|
|
|
unsigned int k; |
|
|
|
app_owi_result_t r; |
|
|
|
|
|
|
|
if (!line) return 0; |
|
|
|
|
|
|
|
len = (int)strlen(line); |
|
|
|
|
|
|
|
/* x-prefix 파싱 */ |
|
|
|
if (parse_x_prefix(line, len, |
|
|
|
&addr, &ch, &mode, |
|
|
|
&hash_on, &anaout_on, &check_on, |
|
|
|
&payload_pos) != 1) { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
/* 자기 주소가 아니면 기존 경로로 넘김 */ |
|
|
|
if (addr != g_fixed_addr) { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
if (ch < 1 || ch > 20) { |
|
|
|
OUT_PRINT(src, "Err:ch_range\r\n"); |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
/* prefix 동작 유지: 기존 process_one_line_now()와 동일하게 선행 */ |
|
|
|
if (mode == 'C') { |
|
|
|
s_prefix_mode = PREFIX_CAL; |
|
|
|
Cal_Init(); |
|
|
|
Gate_SetByNum(ch, hash_on, anaout_on, check_on); |
|
|
|
GateCtrl_SelectChannel(ch); |
|
|
|
} else { |
|
|
|
s_prefix_mode = PREFIX_EOL; |
|
|
|
Eol_Init(); |
|
|
|
Gate_SetByNum(ch, hash_on, anaout_on, check_on); |
|
|
|
GateCtrl_SelectChannel(ch); |
|
|
|
} |
|
|
|
|
|
|
|
if (payload_pos >= len) { |
|
|
|
if (mode == 'E') { |
|
|
|
OUT_PRINT(src, "<ACK>XE51\r\n"); |
|
|
|
return 1; |
|
|
|
} else { |
|
|
|
OUT_PRINT(src, "Err:CAL_need_payload\r\n"); |
|
|
|
return 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
pos = payload_pos; |
|
|
|
|
|
|
|
{ |
|
|
|
char h0, h1; |
|
|
|
|
|
|
|
h0 = (char)toupper((unsigned char)line[pos]); |
|
|
|
h1 = (char)toupper((unsigned char)line[pos + 1]); |
|
|
|
|
|
|
|
proto = detect_protocol(h0, h1); |
|
|
|
if (proto != PROTOCOL_OWIW && proto != PROTOCOL_OWIR) { |
|
|
|
return 0; /* I2C 등은 기존 경로 fallback */ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
pos += 2; |
|
|
|
|
|
|
|
if ((line[pos] == 't' || line[pos] == 'T') && proto == PROTOCOL_OWIW) { |
|
|
|
proto = PROTOCOL_OWIT; |
|
|
|
pos++; |
|
|
|
} |
|
|
|
|
|
|
|
if (line[pos] == '_' || line[pos] == ':') { |
|
|
|
pos++; |
|
|
|
} |
|
|
|
|
|
|
|
if (pos + 1 >= len) { |
|
|
|
OUT_PRINT(src, "Err:id_short\r\n"); |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
id = hex2byte(line[pos], line[pos + 1]); |
|
|
|
pos += 2; |
|
|
|
|
|
|
|
if (pos + 2 >= len || |
|
|
|
!(line[pos] >= '0' && line[pos] <= '9') || |
|
|
|
!(line[pos+1] >= '0' && line[pos+1] <= '9') || |
|
|
|
!(line[pos+2] >= '0' && line[pos+2] <= '9')) { |
|
|
|
OUT_PRINT(src, "Err:len_dec\r\n"); |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
byte_len = (unsigned int)(100 * (line[pos] - '0') + |
|
|
|
10 * (line[pos+1] - '0') + |
|
|
|
(line[pos+2] - '0')); |
|
|
|
pos += 3; |
|
|
|
|
|
|
|
if (byte_len > CMD_MAX) { |
|
|
|
OUT_PRINT(src, "Err:len_range\r\n"); |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
if (proto == PROTOCOL_OWIT || proto == PROTOCOL_OWIW) { |
|
|
|
if (byte_len == 0) { |
|
|
|
OUT_PRINT(src, "Err:payload0\r\n"); |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
if ((int)(pos + (int)byte_len * 2) > len) { |
|
|
|
OUT_PRINT(src, "Err:len_mismatch\r\n"); |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
for (k = 0; k < byte_len; k++) { |
|
|
|
cmd[k] = hex2byte(line[pos + (int)(2 * k)], |
|
|
|
line[pos + (int)(2 * k + 1)]); |
|
|
|
} |
|
|
|
|
|
|
|
if (proto == PROTOCOL_OWIT) { |
|
|
|
r = app_owi_write_t_basic(id, cmd, (uint8_t)byte_len); |
|
|
|
} else { |
|
|
|
r = app_owi_write_basic(id, cmd, (uint8_t)byte_len); |
|
|
|
} |
|
|
|
|
|
|
|
print_owi_write_result(src, &r); |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
if (proto == PROTOCOL_OWIR) { |
|
|
|
if (byte_len == 0) { |
|
|
|
OUT_PRINT(src, "Err:read_len_nonzero\r\n"); |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
r = app_owi_read_basic(id, (int)byte_len); |
|
|
|
print_owi_read_result(src, &r); |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static void process_cmd(ProtocolType protocol, uint8_t id, |
|
|
|
const unsigned char *data, unsigned int len) |
|
|
|
{ |
|
|
|
@ -625,7 +804,7 @@ static void process_cmd_by_prefix(PrefixMode pm, |
|
|
|
/* =========================
|
|
|
|
* Main line processor |
|
|
|
* ========================= */ |
|
|
|
static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint16_t rx_len) |
|
|
|
static void process_one_line_now(CmdSource src, const char *input_line) |
|
|
|
{ |
|
|
|
char line[UART_RX_BUF_SIZE]; |
|
|
|
|
|
|
|
@ -650,35 +829,39 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
|
|
|
|
s_prefix_mode = PREFIX_NONE; |
|
|
|
|
|
|
|
idx = build_line_from_rx(rx_buf, (int)rx_len, line, (int)sizeof(line)); |
|
|
|
if (!input_line) return; |
|
|
|
|
|
|
|
strncpy(line, input_line, sizeof(line) - 1); |
|
|
|
line[sizeof(line) - 1] = '\0'; |
|
|
|
idx = (int)strlen(line); |
|
|
|
if (idx <= 0) return; |
|
|
|
|
|
|
|
if (src == CMD_SRC_RS485) { |
|
|
|
if (!(line[0] == 'x' || line[0] == 'X')) return; // V/N/Err 응답 등 무시
|
|
|
|
if (!(line[0] == 'x' || line[0] == 'X')) return; // V/N/Err
|
|
|
|
} |
|
|
|
|
|
|
|
/* =========================
|
|
|
|
* 1) xNNv 처리 (x-prefix보다 먼저) |
|
|
|
* 1) xNNv (x-prefix ) |
|
|
|
* ========================= */ |
|
|
|
is_v = parse_x_v_cmd(line, idx, &v_addr); |
|
|
|
if (is_v) { |
|
|
|
|
|
|
|
if (v_addr > 31) { OUT_PRINT(src, "Err:addr_range\r\n"); return; } |
|
|
|
|
|
|
|
/* 보드0(addr=0) + PC에서 x00v => 00~31 스캔 */ |
|
|
|
/* 0(addr=0) + PC x00v => 00~31 */ |
|
|
|
if (g_fixed_addr == 0 && src == CMD_SRC_PC && v_addr == 0) { |
|
|
|
|
|
|
|
/* 자기 자신(00) 응답 */ |
|
|
|
|
|
|
|
send_v_response(CMD_SRC_PC, 0); |
|
|
|
|
|
|
|
/* 01~31 순차 조회 */ |
|
|
|
/* 01~31 */ |
|
|
|
for (a = 1; a <= 31; a++) { |
|
|
|
scan_one_addr_rs485(a); |
|
|
|
} |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
/* 그 외: 내 주소와 일치할 때만 응답 */ |
|
|
|
|
|
|
|
if (v_addr == g_fixed_addr) { |
|
|
|
send_v_response(src, g_fixed_addr); |
|
|
|
} |
|
|
|
@ -686,7 +869,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
} |
|
|
|
|
|
|
|
/* =========================
|
|
|
|
* 1.5) xNNo 처리 (OFF) ? 수정: 보드0이 다른 주소면 RS485 중계 |
|
|
|
* 1.5) xNNo(OFF) |
|
|
|
* ========================= */ |
|
|
|
{ |
|
|
|
uint8_t off_addr = 0; |
|
|
|
@ -694,15 +877,15 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
|
|
|
|
if (off_addr > 31) { OUT_PRINT(src, "Err:addr_range\r\n"); return; } |
|
|
|
|
|
|
|
/* 1) 내 주소면 로컬 OFF */ |
|
|
|
/* 1) OFF */ |
|
|
|
if (off_addr == g_fixed_addr) { |
|
|
|
Cal_Init(); // “전체 OFF(기본 상태)”
|
|
|
|
Cal_Init(); |
|
|
|
OUT_PRINT(src, "<ACK>OFF\r\n"); |
|
|
|
send_end_response(); // GUI가 멀티라인이면 <end> 편함
|
|
|
|
send_end_response(src); //? <end>
|
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
/* 2) 보드0(addr=0) + PC에서 들어온 x01o~ 는 RS485로 브릿지 */ |
|
|
|
/* 2) 0(addr=0) + PC x01o RS485 */ |
|
|
|
if (g_fixed_addr == 0 && src == CMD_SRC_PC) { |
|
|
|
|
|
|
|
g_rs485_bridge_active = 1; |
|
|
|
@ -714,10 +897,10 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
rs485_rx_length = 0; |
|
|
|
R_UART0_Receive((uint8_t*)&rs485_rx_buffer[0], 1); |
|
|
|
|
|
|
|
/* ---- RS485로 명령 송신 ---- */ |
|
|
|
/* ---- RS485 ---- */ |
|
|
|
{ |
|
|
|
static char txbuf[UART_RX_BUF_SIZE + 4]; |
|
|
|
int n = idx; // line 길이
|
|
|
|
int n = idx; // line
|
|
|
|
|
|
|
|
if (n > (int)sizeof(txbuf) - 3) n = (int)sizeof(txbuf) - 3; |
|
|
|
|
|
|
|
@ -726,7 +909,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
txbuf[n++] = '\n'; |
|
|
|
txbuf[n] = '\0'; |
|
|
|
|
|
|
|
/* UART0 TX busy면 잠깐 대기 후 송신 */ |
|
|
|
/* UART0 TX busy */ |
|
|
|
{ |
|
|
|
unsigned long g = 0; |
|
|
|
while (g_uart0_tx_count != 0U) { |
|
|
|
@ -737,7 +920,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
RS485_PRINT(txbuf); |
|
|
|
} |
|
|
|
|
|
|
|
/* ---- 응답 드레인: 데이터 1번이라도 오고 3ms idle이면 종료 ---- */ |
|
|
|
|
|
|
|
{ |
|
|
|
uint32_t total_us = BRIDGE_TOTAL_WAIT_US; |
|
|
|
uint32_t idle_us = 0; |
|
|
|
@ -749,7 +932,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
delay_us(50); |
|
|
|
total_us -= 50U; |
|
|
|
|
|
|
|
if (g_rs485_bridge_done) break; // <end>면 종료
|
|
|
|
if (g_rs485_bridge_done) break; // <end>
|
|
|
|
|
|
|
|
if (g_rs485_bridge_seq != last_seq) { |
|
|
|
last_seq = g_rs485_bridge_seq; |
|
|
|
@ -764,7 +947,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
RS485_Bridge_DrainToPC(); |
|
|
|
g_rs485_bridge_active = 0; |
|
|
|
|
|
|
|
if (total_us < 50U) { |
|
|
|
if (total_us < 300U) { |
|
|
|
rs485_recover(); |
|
|
|
OUT_PRINT(CMD_SRC_PC, "Err:rs485_timeout\r\n"); |
|
|
|
sprintf(msg, "Err:rs485_timeout fef=%lu ovf=%lu pef=%lu\r\n", |
|
|
|
@ -774,17 +957,17 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return; // 슬레이브는 mismatch면 그냥 무시
|
|
|
|
return; // mismatch
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* RS485 중계용 원본 저장 */ |
|
|
|
/* RS485 */ |
|
|
|
orig_len = idx; |
|
|
|
memcpy(orig_line, line, (size_t)orig_len); |
|
|
|
orig_line[orig_len] = '\0'; |
|
|
|
|
|
|
|
/* =========================
|
|
|
|
* 2) x-prefix 처리 |
|
|
|
* 2) x-prefix |
|
|
|
* ========================= */ |
|
|
|
{ |
|
|
|
uint8_t addr = 0; |
|
|
|
@ -815,7 +998,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
/* addr mismatch */ |
|
|
|
if (addr != g_fixed_addr) |
|
|
|
{ |
|
|
|
/* 보드0(addr=0) + PC에서만 RS485 중계 */ |
|
|
|
/* 0(addr=0) + PC RS485 */ |
|
|
|
if (g_fixed_addr == 0 && src == CMD_SRC_PC) |
|
|
|
{ |
|
|
|
uint32_t total_us; |
|
|
|
@ -832,7 +1015,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
rs485_rx_length = 0; |
|
|
|
R_UART0_Receive((uint8_t*)&rs485_rx_buffer[0], 1); |
|
|
|
|
|
|
|
/* ---- RS485로 명령 송신 ---- */ |
|
|
|
/* ---- RS485 ---- */ |
|
|
|
{ |
|
|
|
static char txbuf[UART_RX_BUF_SIZE + 4]; |
|
|
|
int n = orig_len; |
|
|
|
@ -844,7 +1027,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
txbuf[n++] = '\n'; |
|
|
|
txbuf[n] = '\0'; |
|
|
|
|
|
|
|
/* UART0 TX busy면 잠깐 대기 후 송신 */ |
|
|
|
/* UART0 TX busy ? */ |
|
|
|
{ |
|
|
|
unsigned long g = 0; |
|
|
|
while (g_uart0_tx_count != 0U) { |
|
|
|
@ -855,7 +1038,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
RS485_PRINT(txbuf); |
|
|
|
} |
|
|
|
|
|
|
|
/* ---- 응답 드레인 ---- */ |
|
|
|
|
|
|
|
total_us = BRIDGE_TOTAL_WAIT_US; |
|
|
|
idle_us = 0; |
|
|
|
last_seq = g_rs485_bridge_seq; |
|
|
|
@ -881,7 +1064,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
RS485_Bridge_DrainToPC(); |
|
|
|
g_rs485_bridge_active = 0; |
|
|
|
|
|
|
|
if (total_us < 50U) { |
|
|
|
if (total_us < 300U) { |
|
|
|
rs485_recover(); |
|
|
|
OUT_PRINT(CMD_SRC_PC, "Err:rs485_timeout\r\n"); |
|
|
|
sprintf(msg, "Err:rs485_timeout fef=%lu ovf=%lu pef=%lu\r\n", |
|
|
|
@ -890,10 +1073,10 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return; /* 슬레이브는 addr mismatch 무시 */ |
|
|
|
return; /* addr mismatch */ |
|
|
|
} |
|
|
|
|
|
|
|
/* addr == g_fixed_addr: 로컬 처리(채널 선택 등) */ |
|
|
|
/* addr == g_fixed_addr */ |
|
|
|
if (mode == 'C') { |
|
|
|
s_prefix_mode = PREFIX_CAL; |
|
|
|
Cal_Init(); |
|
|
|
@ -906,7 +1089,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
GateCtrl_SelectChannel(ch); |
|
|
|
} |
|
|
|
|
|
|
|
/* prefix만 온 경우 / payload 처리 */ |
|
|
|
/* prefix */ |
|
|
|
{ |
|
|
|
int rem = idx - payload_pos; |
|
|
|
|
|
|
|
@ -933,9 +1116,7 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* =========================
|
|
|
|
* 3) 일반 커맨드 파싱 |
|
|
|
* ========================= */ |
|
|
|
|
|
|
|
if (idx < 7) { OUT_PRINT(src, "Err:short\r\n"); return; } |
|
|
|
|
|
|
|
{ |
|
|
|
@ -995,6 +1176,111 @@ static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void process_one_line(CmdSource src, const volatile uint8_t *rx_buf, uint16_t rx_len) |
|
|
|
{ |
|
|
|
char line[UART_RX_BUF_SIZE]; |
|
|
|
int idx; |
|
|
|
app_job_t job; |
|
|
|
app_cmd_src_t app_src; |
|
|
|
|
|
|
|
idx = build_line_from_rx(rx_buf, (int)rx_len, line, (int)sizeof(line)); |
|
|
|
if (idx <= 0) return; |
|
|
|
|
|
|
|
if (src == CMD_SRC_RS485) { |
|
|
|
if (!(line[0] == 'x' || line[0] == 'X')) return; |
|
|
|
} |
|
|
|
|
|
|
|
app_src = (src == CMD_SRC_PC) ? APP_CMD_SRC_PC : APP_CMD_SRC_RS485; |
|
|
|
|
|
|
|
if (!app_cmd_parse_line(app_src, line, &job)) { |
|
|
|
OUT_PRINT(src, "Err:parse\r\n"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (!app_scheduler_push(&job)) { |
|
|
|
OUT_PRINT(src, "Err:queue_full\r\n"); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void app_runtime_reset(void) |
|
|
|
{ |
|
|
|
memset(&g_app_runtime_job, 0, sizeof(g_app_runtime_job)); |
|
|
|
} |
|
|
|
|
|
|
|
static void app_runtime_try_start(void) |
|
|
|
{ |
|
|
|
if (g_app_runtime_job.active) return; |
|
|
|
|
|
|
|
if (app_scheduler_pop(&g_app_runtime_job.job)) { |
|
|
|
g_app_runtime_job.active = 1; |
|
|
|
g_app_runtime_job.scan_started = 0; |
|
|
|
g_app_runtime_job.current_scan_addr = 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void app_job_tick(void) |
|
|
|
{ |
|
|
|
CmdSource src; |
|
|
|
uint8_t scan_addr; |
|
|
|
|
|
|
|
app_runtime_try_start(); |
|
|
|
if (!g_app_runtime_job.active) return; |
|
|
|
|
|
|
|
src = (g_app_runtime_job.job.src == APP_CMD_SRC_PC) ? CMD_SRC_PC : CMD_SRC_RS485; |
|
|
|
|
|
|
|
switch (g_app_runtime_job.job.type) { |
|
|
|
case APP_JOB_SCAN_ADDR: |
|
|
|
scan_addr = g_app_runtime_job.job.addr; |
|
|
|
|
|
|
|
if (scan_addr > 31) { |
|
|
|
OUT_PRINT(src, "Err:addr_range\r\n"); |
|
|
|
app_runtime_reset(); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
/* 기존 x00v on master(0) from PC 만 분할 실행 */ |
|
|
|
if (g_fixed_addr == 0 && src == CMD_SRC_PC && scan_addr == 0) { |
|
|
|
if (!g_app_runtime_job.scan_started) { |
|
|
|
send_v_response(CMD_SRC_PC, 0); |
|
|
|
g_app_runtime_job.scan_started = 1; |
|
|
|
g_app_runtime_job.current_scan_addr = 1; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (g_app_runtime_job.current_scan_addr <= 31) { |
|
|
|
scan_one_addr_rs485(g_app_runtime_job.current_scan_addr); |
|
|
|
g_app_runtime_job.current_scan_addr++; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
app_runtime_reset(); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
/* 나머지는 기존 실행 경로 그대로 */ |
|
|
|
process_one_line_now(src, g_app_runtime_job.job.line); |
|
|
|
app_runtime_reset(); |
|
|
|
return; |
|
|
|
|
|
|
|
case APP_JOB_PROTO_OW: |
|
|
|
case APP_JOB_PROTO_OR: |
|
|
|
if (!execute_owi_service_from_line(src, g_app_runtime_job.job.line)) { |
|
|
|
/* service로 못 처리하면 기존 경로로 fallback */ |
|
|
|
process_one_line_now(src, g_app_runtime_job.job.line); |
|
|
|
} |
|
|
|
app_runtime_reset(); |
|
|
|
return; |
|
|
|
|
|
|
|
case APP_JOB_FORWARD_LINE: |
|
|
|
case APP_JOB_LOCAL_EXEC: |
|
|
|
default: |
|
|
|
process_one_line_now(src, g_app_runtime_job.job.line); |
|
|
|
app_runtime_reset(); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* =========================
|
|
|
|
* Main loop handler |
|
|
|
* ========================= */ |
|
|
|
@ -1004,22 +1290,22 @@ void handle_uart_command_line(void) |
|
|
|
{ |
|
|
|
if (g_rs485_need_recover) { |
|
|
|
|
|
|
|
/* 1) 브릿지 강제 종료 */ |
|
|
|
|
|
|
|
g_rs485_bridge_active = 0; |
|
|
|
g_rs485_bridge_done = 0; |
|
|
|
RS485_Bridge_ResetFifo(); // 같은 파일 내 static이라 호출 가능
|
|
|
|
RS485_Bridge_ResetFifo(); // static? ?
|
|
|
|
|
|
|
|
/* 2) RS485 RX 상태 정리 */ |
|
|
|
/* RS485 RX */ |
|
|
|
rs485_rx_done = 0; |
|
|
|
rs485_rx_index = 0; |
|
|
|
rs485_rx_length = 0; |
|
|
|
|
|
|
|
/* 3) UART0 stop/start + RX 재arm */ |
|
|
|
/* UART0 stop/start + RX arm */ |
|
|
|
rs485_recover(); |
|
|
|
g_rs485_need_recover = 0; |
|
|
|
} |
|
|
|
|
|
|
|
/* 브릿지 중이면 RS485->PC 계속 드레인 */ |
|
|
|
|
|
|
|
if (g_rs485_bridge_active) { |
|
|
|
RS485_Bridge_DrainToPC(); |
|
|
|
} |
|
|
|
@ -1053,6 +1339,7 @@ void handle_uart_command_line(void) |
|
|
|
{ |
|
|
|
|
|
|
|
} |
|
|
|
app_job_tick(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -1075,9 +1362,12 @@ void main(void) |
|
|
|
R_UART0_Start(); |
|
|
|
R_UART1_Start(); |
|
|
|
|
|
|
|
app_scheduler_init(); |
|
|
|
app_runtime_reset(); |
|
|
|
|
|
|
|
sprintf(b, "BOOT addr=%u\r\n", g_fixed_addr); |
|
|
|
|
|
|
|
/* ? BOOT 출력도 안전 출력 사용 */ |
|
|
|
/* ? BOOT μ */ |
|
|
|
UART1_SendString_Safe(b); |
|
|
|
RS485_PRINT(b); |
|
|
|
|
|
|
|
|