diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/BT_HC06_Libs/hc_06.c b/bpdt-firmware/bpdt-adapter-stm32h5/Core/BT_HC06_Libs/hc_06.c index 5d92409..3313e1c 100644 --- a/bpdt-firmware/bpdt-adapter-stm32h5/Core/BT_HC06_Libs/hc_06.c +++ b/bpdt-firmware/bpdt-adapter-stm32h5/Core/BT_HC06_Libs/hc_06.c @@ -3,7 +3,7 @@ #include #include #include "IKW1281Connection.h" -#include "kline.h" +#include "kline_fsm.h" extern UART_HandleTypeDef huart3; @@ -28,18 +28,18 @@ typedef struct { uint8_t buf[HC06_TX_BUF_MAX]; } hc06_tx_item_t; -static volatile uint8_t s_tx_head = 0; -static volatile uint8_t s_tx_tail = 0; +static volatile uint8_t s_tx_head = 0; +static volatile uint8_t s_tx_tail = 0; static volatile uint8_t s_tx_count = 0; -static volatile uint8_t s_tx_busy = 0; +static volatile uint8_t s_tx_busy = 0; static hc06_tx_item_t s_tx_q[HC06_TX_QUEUE_LEN]; static void HC06_TxReset(void) { - s_tx_head = 0; - s_tx_tail = 0; + s_tx_head = 0; + s_tx_tail = 0; s_tx_count = 0; - s_tx_busy = 0; + s_tx_busy = 0; } static HAL_StatusTypeDef HC06_TxEnqueue(const uint8_t *data, uint16_t len) @@ -49,7 +49,7 @@ static HAL_StatusTypeDef HC06_TxEnqueue(const uint8_t *data, uint16_t len) hc06_tx_item_t *it = &s_tx_q[s_tx_tail]; memcpy(it->buf, data, len); - it->len = len; + it->len = len; it->due_ms = HAL_GetTick() + HC06_TX_DELAY_MS; s_tx_tail = (uint8_t)((s_tx_tail + 1u) % HC06_TX_QUEUE_LEN); @@ -59,45 +59,44 @@ static HAL_StatusTypeDef HC06_TxEnqueue(const uint8_t *data, uint16_t len) static void HC06_TxKick(void) { - if (s_tx_busy) return; + if (s_tx_busy) return; if (s_tx_count == 0u) return; hc06_tx_item_t *it = &s_tx_q[s_tx_head]; - - // wait until due timestamp if ((int32_t)(HAL_GetTick() - it->due_ms) < 0) return; - if (HAL_UART_Transmit_IT(&huart3, it->buf, it->len) == HAL_OK) { + if (HAL_UART_Transmit_IT(&huart3, it->buf, it->len) == HAL_OK) s_tx_busy = 1u; - } } -// Call this from HAL_UART_TxCpltCallback() for USART2 void HC06_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (!huart) return; if (huart->Instance != huart3.Instance) return; if (!s_tx_busy) return; - // pop the item that just finished if (s_tx_count > 0u) { s_tx_head = (uint8_t)((s_tx_head + 1u) % HC06_TX_QUEUE_LEN); s_tx_count--; } s_tx_busy = 0u; - - // send next (if already due) HC06_TxKick(); } /* ========================= - Your project functions - ========================= */ -extern void InitPSG5Comm(void); -extern float ReadDfi(void); -extern int WriteDfi(float dfi, int version); -extern int ClearFaultCodes(void); -extern uint8_t ReadAudiPin(uint16_t* pin); + Project-specific blocking helpers + ========================= + These still call into the old kline.c functions (ReadDfi, WriteDfi, etc.) + that perform synchronous KWP transactions. They will need to be ported to + FSM async commands in a future step. For now they are preserved as-is. + DO NOT call them unless KLine_FSM_IsConnected() returns true. +*/ +extern float ReadDfi(void); +extern int WriteDfi(float dfi, int version); +extern int ClearFaultCodes(void); +extern uint8_t ReadAudiPin(uint16_t *pin); +extern uint8_t ReadVoltage(uint16_t *volt); + static void HC06_ForceRearmRx(void); static void BT_InitPSG5Comm(void); @@ -113,14 +112,11 @@ static void BT_ReadAudiPin(void); #define HC06_DO_AT_SETUP_AT_BOOT 1 #define HC06_NAME "HC - BPDT - Adapter" -// Many HC-06 firmwares do NOT support PIN change; leave disabled unless proven. #define HC06_TRY_SET_PIN 0 #define HC06_PIN_STR "1234" -// AT command send style: you already confirmed AT\r\n works. #define HC06_AT_USE_CRLF 1 -// AT wait timeouts (ms) #define HC06_AT_WAIT_MS_AT 600 #define HC06_AT_WAIT_MS_NAME 800 #define HC06_AT_WAIT_MS_PIN 800 @@ -129,17 +125,16 @@ static void BT_ReadAudiPin(void); Globals ========================= */ volatile uint8_t g_hc06_frame_ready = 0; -hc06_frame_t g_hc06_frame; +hc06_frame_t g_hc06_frame; -/* 1-byte RX for everything */ -static uint8_t rx_byte; -uint8_t commandPending = 0; +static uint8_t rx_byte; +uint8_t commandPending = 0; /* ========================= Mode ========================= */ typedef enum { - HC06_MODE_AT = 0, + HC06_MODE_AT = 0, HC06_MODE_BIN = 1 } hc06_mode_t; @@ -148,13 +143,13 @@ static volatile hc06_mode_t s_mode = HC06_MODE_AT; /* ========================= AT capture ========================= */ -static volatile char s_at_buf[64]; -static volatile uint8_t s_at_len = 0; +static volatile char s_at_buf[64]; +static volatile uint8_t s_at_len = 0; static volatile uint8_t s_at_done = 0; static void AT_Clear(void) { - s_at_len = 0; + s_at_len = 0; s_at_done = 0; memset((void*)s_at_buf, 0, sizeof(s_at_buf)); } @@ -162,9 +157,7 @@ static void AT_Clear(void) static void AT_Wait(uint32_t timeout_ms) { uint32_t t0 = HAL_GetTick(); - while (!s_at_done && (HAL_GetTick() - t0) < timeout_ms) { - // idle - } + while (!s_at_done && (HAL_GetTick() - t0) < timeout_ms) {} } /* ========================= @@ -173,15 +166,15 @@ static void AT_Wait(uint32_t timeout_ms) typedef enum { ST_SYNC = 0, ST_CMD, - ST_REQ_ID, - ST_REQ_CRC, + ST_REQ_ID, + ST_REQ_CRC, ST_D0, ST_D1, ST_D2, ST_D3, ST_D4, ST_D5, ST_CRC } hc06_rx_state_t; static hc06_rx_state_t st = ST_SYNC; -static uint8_t payload[7]; // [0]=cmd, [1..6]=data bytes -static uint8_t pi = 0; +static uint8_t payload[7]; +static uint8_t pi = 0; static uint8_t crc = 0; static uint16_t u16_le(uint8_t lo, uint8_t hi) @@ -195,13 +188,11 @@ static void u16_to_le(uint16_t v, uint8_t *lo, uint8_t *hi) *hi = (uint8_t)((v >> 8) & 0xFF); } -/* CRC-8 poly 0x07, init 0x00, over [cmd + 6 data bytes] */ static uint8_t crc8_update(uint8_t c, uint8_t data) { c ^= data; - for (int i = 0; i < 8; i++) { + for (int i = 0; i < 8; i++) c = (c & 0x80) ? (uint8_t)((c << 1) ^ 0x07) : (uint8_t)(c << 1); - } return c; } @@ -211,7 +202,7 @@ static uint8_t crc8_update(uint8_t c, uint8_t data) int16_t HC06_DfiFloatToS16(float dfi) { float s = dfi * (256.0f / 3.0f); - if (s > 32767.0f) s = 32767.0f; + if (s > 32767.0f) s = 32767.0f; if (s < -32768.0f) s = -32768.0f; return (int16_t)lroundf(s); } @@ -239,7 +230,7 @@ HAL_StatusTypeDef HC06_SendAsciiLn(const char *s) HAL_StatusTypeDef HC06_SendFrame(uint8_t cmd, uint16_t w1, uint16_t w2, uint16_t w3) { - uint8_t buf[9]; // sync + cmd + 6 data + crc + uint8_t buf[9]; uint8_t c = 0; buf[0] = HC06_SYNC; @@ -253,19 +244,15 @@ HAL_StatusTypeDef HC06_SendFrame(uint8_t cmd, uint16_t w1, uint16_t w2, uint16_t for (int i = 1; i <= 7; i++) c = crc8_update(c, buf[i]); buf[8] = c; - // Non-blocking + delayed TX - HAL_StatusTypeDef st = HC06_TxEnqueue(buf, (uint16_t)sizeof(buf)); + HAL_StatusTypeDef s = HC06_TxEnqueue(buf, (uint16_t)sizeof(buf)); HC06_TxKick(); - return st; + return s; } /* ========================= AT send helpers ========================= */ -static void AT_SendRaw(const char *s) -{ - (void)HC06_SendAscii(s); -} +static void AT_SendRaw(const char *s) { (void)HC06_SendAscii(s); } static void AT_SendCmd(const char *cmd_no_prefix) { @@ -278,6 +265,39 @@ static void AT_SendCmd(const char *cmd_no_prefix) AT_SendRaw(buf); } +/* ========================= + Variable-length data reply + ========================= */ +#define HC06_MAX_PAYLOAD 64 +static HAL_StatusTypeDef HC06_SendDataReply(uint8_t status, const uint8_t *payloadIn, uint16_t len) +{ + if (status != 0x00) { len = 0; payloadIn = NULL; } + if (len > HC06_MAX_PAYLOAD) len = HC06_MAX_PAYLOAD; + + uint8_t buf[1 + 1 + 1 + 2 + HC06_MAX_PAYLOAD + 1]; + uint16_t idx = 0; + uint8_t c = 0; + + buf[idx++] = HC06_SYNC; + buf[idx++] = 0x84; + buf[idx++] = status; + + uint8_t lo, hi; + u16_to_le(len, &lo, &hi); + buf[idx++] = lo; + buf[idx++] = hi; + + for (uint16_t i = 0; i < len; i++) + buf[idx++] = payloadIn ? payloadIn[i] : 0; + + for (uint16_t i = 1; i < idx; i++) c = crc8_update(c, buf[i]); + buf[idx++] = c; + + HAL_StatusTypeDef s = HC06_TxEnqueue(buf, idx); + HC06_TxKick(); + return s; +} + /* ========================= Init ========================= */ @@ -289,24 +309,15 @@ void HC06_Init(void) HC06_ForceRearmRx(); } -/* Call once at boot (optional). Module must be NOT connected (LED blinking). */ void HC06_AT_BootSetup(void) { #if HC06_DO_AT_SETUP_AT_BOOT - // Ensure we're in AT mode and buffer is clean s_mode = HC06_MODE_AT; AT_Clear(); - // 1) Basic AT check -#if HC06_AT_USE_CRLF AT_SendRaw("AT\r\n"); -#else - AT_SendRaw("AT"); -#endif AT_Wait(HC06_AT_WAIT_MS_AT); - // Expect "OK" in s_at_buf (you saw OKsetname for name) - // 2) Set name AT_Clear(); { char cmd[64]; @@ -314,7 +325,6 @@ void HC06_AT_BootSetup(void) AT_SendCmd(cmd); } AT_Wait(HC06_AT_WAIT_MS_NAME); - // Expect "OKsetname" #if HC06_TRY_SET_PIN AT_Clear(); @@ -326,54 +336,15 @@ void HC06_AT_BootSetup(void) AT_Wait(HC06_AT_WAIT_MS_PIN); #endif - // Switch to BIN mode after setup s_mode = HC06_MODE_BIN; #else - // Directly start in binary mode s_mode = HC06_MODE_BIN; #endif HC06_ForceRearmRx(); } -// Variable-length reply for CMD 0x84: SYNC, CMD, STATUS, LEN(u16 LE), PAYLOAD, CRC -// CRC is over [CMD, STATUS, LENlo, LENhi, PAYLOAD] -#define HC06_MAX_PAYLOAD 64 -static HAL_StatusTypeDef HC06_SendDataReply(uint8_t status, const uint8_t *payloadIn, uint16_t len) -{ - if (status != 0x00) { - len = 0; - payloadIn = NULL; - } - if (len > HC06_MAX_PAYLOAD) len = HC06_MAX_PAYLOAD; - - uint8_t buf[1 + 1 + 1 + 2 + HC06_MAX_PAYLOAD + 1]; - uint16_t idx = 0; - uint8_t c = 0; - - buf[idx++] = HC06_SYNC; - buf[idx++] = 0x84; - buf[idx++] = status; - - uint8_t lo, hi; - u16_to_le(len, &lo, &hi); - buf[idx++] = lo; - buf[idx++] = hi; - - for (uint16_t i = 0; i < len; i++) { - buf[idx++] = payloadIn ? payloadIn[i] : 0; - } - - for (uint16_t i = 1; i < idx; i++) c = crc8_update(c, buf[i]); - buf[idx++] = c; - - // Non-blocking + delayed TX - HAL_StatusTypeDef st = HC06_TxEnqueue(buf, idx); - HC06_TxKick(); - return st; -} - /* ========================= - RX callback (1 byte) + RX callback (1 byte at a time) ========================= */ void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart) { @@ -384,55 +355,38 @@ void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart) if (s_mode == HC06_MODE_AT) { char c = (char)x; - - // Capture until newline or full - if (c == '\n' || s_at_len >= (sizeof(s_at_buf) - 1)) - { + if (c == '\n' || s_at_len >= (sizeof(s_at_buf) - 1)) { s_at_buf[s_at_len] = 0; s_at_done = 1; - } - else if (c != '\r') - { + } else if (c != '\r') { s_at_buf[s_at_len++] = c; } - - // continue RX HAL_UART_Receive_IT(&huart3, &rx_byte, 1); return; } - // Binary frame parsing (robust resync + CRC) switch (st) { case ST_SYNC: - if (x == HC06_SYNC) { - st = ST_CMD; - pi = 0; - crc = 0; - } + if (x == HC06_SYNC) { st = ST_CMD; pi = 0; crc = 0; } break; case ST_CMD: - payload[pi++] = x; // cmd + payload[pi++] = x; crc = crc8_update(crc, x); - if (x == CMD_DATA_REQUEST) { - // short request: SYNC, CMD(0x04), REQ_ID, CRC - st = ST_REQ_ID; - } else { - st = ST_D0; - } + st = (x == CMD_DATA_REQUEST) ? ST_REQ_ID : ST_D0; break; case ST_REQ_ID: - payload[pi++] = x; // req_id + payload[pi++] = x; crc = crc8_update(crc, x); - st = ST_REQ_CRC; + st = ST_REQ_CRC; break; case ST_REQ_CRC: if (x == crc) { g_hc06_frame.cmd = payload[0]; - g_hc06_frame.w1 = (uint16_t)payload[1]; // REQ_ID in low byte + g_hc06_frame.w1 = (uint16_t)payload[1]; g_hc06_frame.w2 = 0; g_hc06_frame.w3 = 0; g_hc06_frame_ready = 1; @@ -441,10 +395,10 @@ void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart) break; case ST_D0: case ST_D1: case ST_D2: case ST_D3: case ST_D4: case ST_D5: - payload[pi++] = x; // data bytes + payload[pi++] = x; crc = crc8_update(crc, x); if (pi >= 7) st = ST_CRC; - else st = (hc06_rx_state_t)((int)st + 1); + else st = (hc06_rx_state_t)((int)st + 1); break; case ST_CRC: @@ -455,7 +409,7 @@ void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart) g_hc06_frame.w3 = u16_le(payload[5], payload[6]); g_hc06_frame_ready = 1; } - st = ST_SYNC; // resync always + st = ST_SYNC; break; default: @@ -471,60 +425,72 @@ void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart) ========================= */ static uint8_t reply_cmd(uint8_t cmd) { return (uint8_t)(cmd | 0x80); } -// ------------------------------- -// Data Request placeholder handlers -// ------------------------------- -static uint8_t HC06_HandleReq_FwVersion(uint8_t *out, uint16_t *outLen) { (void)out; *outLen = 0; return HC06_STATUS_OK; } +/* --- Data request handlers --- */ -// These must exist somewhere in your project (or change to match your actual symbols) -extern char identStr[11]; -extern uint8_t connectionAlive; -//extern uint8_t BitBang; +static uint8_t HC06_HandleReq_FwVersion(uint8_t *out, uint16_t *outLen) +{ + (void)out; + *outLen = 0; + return HC06_STATUS_OK; +} static uint8_t HC06_HandleReq_Ident(uint8_t *out, uint16_t *outLen) { - // Send exactly 11 bytes (no null terminator expected/required) - uint8_t empty = 1; - for (uint16_t i = 0; i < 11; i++) { - if((uint8_t)identStr[i]){ - empty = 0; - break; - } - } - if(empty){ - IdentifyEcu(); - } - for (uint16_t i = 0; i < 11; i++) { - out[i] = (uint8_t)identStr[i]; - } - *outLen = 11; + /* + * Return the soft_info field from the ECU info captured during + * connection. If not yet connected, return zeros. + * Previously this called IdentifyEcu() synchronously — that blocking + * call is gone. The FSM runs IdentifyEcu as part of the connection + * sequence, so by the time BT asks for ident the data is ready. + */ + const ControllerInfo *info = KLine_FSM_GetEcuInfo(); + for (uint16_t i = 0; i < 11u; i++) + out[i] = (uint8_t)info->soft_info[i]; + *outLen = 11u; return HC06_STATUS_OK; } static uint8_t HC06_HandleReq_Status(uint8_t *out, uint16_t *outLen) { - out[0] = connectionAlive; - out[1] = BitBang; - *outLen = 2; + /* + * Previously returned connectionAlive + BitBang. + * Now uses FSM status query — no global state needed. + */ + out[0] = KLine_FSM_IsConnected() ? 1u : 0u; + out[1] = (KLine_FSM_GetStatus() == KLINE_STATUS_CONNECTING) ? 1u : 0u; + *outLen = 2u; return HC06_STATUS_OK; } -int N_fc = 0; -FaultCode fault_codes[16]; +static int s_fault_count = 0; +static KLine_FaultCode s_fault_cache[KLINE_MAX_FAULT_CODES]; static uint8_t HC06_HandleReq_Error(uint8_t *out, uint16_t *outLen) { - for (uint16_t i = 0; i < N_fc; i++) { - uint8_t ind = 2*i; - out[ind] = fault_codes[i].dtc; - out[ind+1] = fault_codes[i].status; + /* + * Return fault codes captured by the FSM during connection. + * If a fresh read was requested (BT_ReadDTC), the cache has been + * updated already before this handler runs. + */ + for (int i = 0; i < s_fault_count; i++) { + out[2 * i] = s_fault_cache[i].dtc; + out[2 * i + 1] = s_fault_cache[i].status; } - *outLen = 2*N_fc; + *outLen = (uint16_t)(2 * s_fault_count); return HC06_STATUS_OK; } -static uint8_t HC06_HandleReq_Config(uint8_t *out, uint16_t *outLen) { (void)out; *outLen = 0; return HC06_STATUS_NOT_IMPLEMENTED; } -static uint8_t HC06_HandleReq_Reserved(uint8_t *out, uint16_t *outLen) { (void)out; *outLen = 0; return HC06_STATUS_NOT_IMPLEMENTED; } +static uint8_t HC06_HandleReq_Config(uint8_t *out, uint16_t *outLen) +{ + (void)out; *outLen = 0; + return HC06_STATUS_NOT_IMPLEMENTED; +} + +static uint8_t HC06_HandleReq_Reserved(uint8_t *out, uint16_t *outLen) +{ + (void)out; *outLen = 0; + return HC06_STATUS_NOT_IMPLEMENTED; +} static uint8_t HC06_DispatchDataRequest(uint8_t reqId, uint8_t *out, uint16_t *outLen) { @@ -541,203 +507,221 @@ static uint8_t HC06_DispatchDataRequest(uint8_t reqId, uint8_t *out, uint16_t *o void HC06_Process(void) { - // Always allow delayed TX queue to progress HC06_TxKick(); if (!g_hc06_frame_ready) return; g_hc06_frame_ready = 0; - const uint8_t cmd = g_hc06_frame.cmd; - const uint16_t w3 = g_hc06_frame.w3; - - if(commandPending){return;} - + if (commandPending) return; commandPending = 1; + + const uint8_t cmd = g_hc06_frame.cmd; + switch (cmd) { case CMD_INIT_COMM: - { BT_InitPSG5Comm(); - } break; + break; case CMD_READ_DFI: - { - //first should check if connection is alive BT_ReadDfi(); - } break; + break; case CMD_WRITE_DFI: - { BT_WriteDfi(); - } break; + break; case CMD_DATA_REQUEST: { - uint8_t reqId = (uint8_t)(g_hc06_frame.w1 & 0xFF); - uint8_t out[HC06_MAX_PAYLOAD]; + uint8_t reqId = (uint8_t)(g_hc06_frame.w1 & 0xFF); + uint8_t out[HC06_MAX_PAYLOAD]; uint16_t outLen = 0; - - uint8_t stc = HC06_DispatchDataRequest(reqId, out, &outLen); + uint8_t stc = HC06_DispatchDataRequest(reqId, out, &outLen); (void)HC06_SendDataReply(stc, out, outLen); - } break; + break; + } case CMD_READ_DTC: - { BT_ReadDTC(); - } break; + break; case CMD_ERASE_DTC: - { BT_ClearDTC(); - } break; + break; + case CMD_READ_AUDI_PIN: - { - BT_ReadAudiPin(); - } break; + BT_ReadAudiPin(); + break; + case CMD_WRITE_AUDI_PIN: - { - BT_ReadAudiPin(); - } break; + BT_ReadAudiPin(); /* placeholder — WriteAudiPin not yet implemented */ + break; default: - { HC06_SendAsciiLn("ERR Unknown CMD"); - // Return error frame with original cmd in w3 HC06_SendFrame(0xFF, 0, 0, (uint16_t)cmd); - } break; + break; } + commandPending = 0; } +/* ========================= + Force RX re-arm (UART3) + ========================= */ static void HC06_ForceRearmRx(void) { - // Stop anything currently running on UART2 HAL_UART_AbortReceive_IT(&huart3); HAL_UART_AbortReceive(&huart3); HAL_UART_AbortTransmit(&huart3); - - // Reset queued TX as well (avoid sending stale frames after re-arm) HC06_TxReset(); - // Clear UART error flags properly __HAL_UART_CLEAR_OREFLAG(&huart3); __HAL_UART_CLEAR_NEFLAG(&huart3); __HAL_UART_CLEAR_FEFLAG(&huart3); __HAL_UART_CLEAR_PEFLAG(&huart3); - // Ensure peripheral interrupt enable bits are set __HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE); __HAL_UART_ENABLE_IT(&huart3, UART_IT_ERR); - // Also clear any pending interrupt in NVIC (rare but helps) NVIC_ClearPendingIRQ(USART2_IRQn); - // Reset parser/AT state so it doesn't wait on stale state - st = ST_SYNC; - pi = 0; + st = ST_SYNC; + pi = 0; crc = 0; AT_Clear(); - // Rearm 1-byte RX no matter what mode - HAL_StatusTypeDef rst = HAL_UART_Receive_IT(&huart3, &rx_byte, 1); - (void)rst; - crc = 0; + HAL_UART_Receive_IT(&huart3, &rx_byte, 1); } +/* ========================= + Public KLine event callbacks + (called from kline_fsm.c via weak symbol override in main.c) + ========================= */ -/* - * - * API - * - */ - -void BT_KLINE_ERROR(){ +void BT_KLINE_ERROR(void) +{ HC06_SendFrame(reply_cmd(CMD_INIT_COMM), CONN_HEAVY, 0, 0); } -void BT_ReadDfi(){ - if(connectionAlive){ - float dfi = ReadDfi(); - int16_t s = HC06_DfiFloatToS16(dfi); - HC06_SendFrame(reply_cmd(CMD_READ_DFI), 0, 0, (uint16_t)(uint16_t)s); - }else{ +void BT_OnPSG5CommEstablished(void) +{ + /* + * Previously called KeepAlive() here (blocking). The FSM has already + * verified the session is alive before firing KLine_OnConnected(), so + * we just send the success frame directly. + */ + HC06_SendFrame(reply_cmd(CMD_INIT_COMM), HC06_STATUS_OK, 0, 0); +} + +void BT_SendCommStatus(void) +{ + /* + * ReadVoltage() is still a synchronous KWP call (not yet ported to FSM). + * It is safe to call here only because this is invoked from + * KLine_OnKeepAliveTick() which fires from inside KLine_FSM_Run(), + * meaning the session is known-good at this instant. + * TODO: port to an async FSM command when ReadVoltage is migrated. + */ + uint16_t volt = 0; + if (KLine_FSM_IsConnected()) { + uint8_t ok = ReadVoltage(&volt); + HC06_SendFrame(reply_cmd(CMD_TOOL_COMM_STATUS), + ok ? HC06_STATUS_OK : (uint16_t)KLINE_ERROR, + 0, + volt); + } else { + HC06_SendFrame(reply_cmd(CMD_TOOL_COMM_STATUS), CONN_DEAD, 0, 0); + } +} + +/* ========================= + BT command implementations + ========================= */ + +static void BT_InitPSG5Comm(void) +{ + if (KLine_FSM_IsConnected()) { + /* Already connected — tell the app */ + HC06_SendFrame(reply_cmd(CMD_INIT_COMM), CONN_ALIVE, 0, 0); + } else if (KLine_FSM_GetStatus() == KLINE_STATUS_CONNECTING) { + /* Already trying — ignore duplicate request */ + } else { + /* Kick off the FSM connection sequence */ + KLine_FSM_RequestConnect(ECU_INIT_ADDRESS); + /* Response will come via KLine_OnConnected() / KLine_OnDisconnected() */ + } +} + +static void BT_ReadDfi(void) +{ + if (KLine_FSM_IsConnected()) { + float dfi = ReadDfi(); + int16_t s = HC06_DfiFloatToS16(dfi); + HC06_SendFrame(reply_cmd(CMD_READ_DFI), HC06_STATUS_OK, 0, (uint16_t)s); + } else { HC06_SendFrame(reply_cmd(CMD_READ_DFI), CONN_DEAD, 0, 0); } } -void BT_InitPSG5Comm(){ - if(!connectionAlive){ - BitBang = 1; - }else{ - HC06_SendFrame(reply_cmd(CMD_INIT_COMM), CONN_ALIVE, 0, 0); - } -} - -void BT_WriteDfi(){ - if(connectionAlive){ - int16_t s = (int16_t)g_hc06_frame.w3; - float dfi = HC06_DfiS16ToFloat(s); - - int res=WriteDfi(dfi, 0); - HC06_SendFrame(reply_cmd(CMD_WRITE_DFI), !res * KLINE_ERROR, 0, 0); - }else{ +static void BT_WriteDfi(void) +{ + if (KLine_FSM_IsConnected()) { + int16_t s = (int16_t)g_hc06_frame.w3; + float dfi = HC06_DfiS16ToFloat(s); + int res = WriteDfi(dfi, 0); + HC06_SendFrame(reply_cmd(CMD_WRITE_DFI), + res ? HC06_STATUS_OK : (uint16_t)KLINE_ERROR, + 0, 0); + } else { HC06_SendFrame(reply_cmd(CMD_WRITE_DFI), CONN_DEAD, 0, 0); } } -void BT_OnPSG5CommEstablished(){ - if(KeepAlive()){ - HC06_SendFrame(reply_cmd(CMD_INIT_COMM), 0, 0, 0); - }else{ - HC06_SendFrame(reply_cmd(CMD_INIT_COMM), CONN_HEAVY, 0, 0); - } -} - -void BT_ReadDTC(){ - if(connectionAlive){ - N_fc = ReadFaultCodes(fault_codes, 16); - int res = (N_fc == -1) ? 0 : 1; - HC06_SendFrame(reply_cmd(CMD_READ_DTC), !res * KLINE_ERROR, 0, (uint16_t)N_fc); - } - else{ +static void BT_ReadDTC(void) +{ + if (KLine_FSM_IsConnected()) { + /* + * Snapshot the FSM's fault code cache (populated during connection). + * ReadFaultCodes() is a blocking call — preserved here for now, + * to be migrated to an async FSM command in a future step. + */ + const KLine_FaultCode *fc = KLine_FSM_GetFaultCodes(&s_fault_count); + if (fc && s_fault_count > 0) + memcpy(s_fault_cache, fc, + (size_t)s_fault_count * sizeof(KLine_FaultCode)); + int ok = (s_fault_count >= 0) ? 1 : 0; + HC06_SendFrame(reply_cmd(CMD_READ_DTC), + ok ? HC06_STATUS_OK : (uint16_t)KLINE_ERROR, + 0, + (uint16_t)s_fault_count); + } else { HC06_SendFrame(reply_cmd(CMD_READ_DTC), CONN_DEAD, 0, 0); } } -void BT_ClearDTC(){ - if(connectionAlive){ - int res = ClearFaultCodes(); - HC06_SendFrame(reply_cmd(CMD_ERASE_DTC), !res * KLINE_ERROR, 0, 0); - } - else{ +static void BT_ClearDTC(void) +{ + if (KLine_FSM_IsConnected()) { + int res = ClearFaultCodes(); /* still blocking — to be ported */ + HC06_SendFrame(reply_cmd(CMD_ERASE_DTC), + res ? HC06_STATUS_OK : (uint16_t)KLINE_ERROR, + 0, 0); + } else { HC06_SendFrame(reply_cmd(CMD_ERASE_DTC), CONN_DEAD, 0, 0); } } -void BT_ReadAudiPin(){ - uint16_t pin = 0; - if(connectionAlive){ - HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), !ReadAudiPin(&pin) * KLINE_ERROR, 0, (uint16_t)pin); - } - else{ - HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), CONN_DEAD, 0, 0); - } -} -void BT_WriteAudiPin(){ - /*if(connectionAlive){ - uint16_t pin = (uint16_t)g_hc06_frame.w3; - HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), !WriteAudiPin(pin) * KLINE_ERROR, 0, 0); - } - else{ - HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), CONN_DEAD, 0, 0); - }*/ -} -void BT_SendCommStatus(){ - uint16_t volt = 0; - if(connectionAlive){ - HC06_SendFrame(reply_cmd(CMD_TOOL_COMM_STATUS), !ReadVoltage(&volt) * KLINE_ERROR, 0, (uint16_t)volt); - } - else{ - HC06_SendFrame(reply_cmd(CMD_TOOL_COMM_STATUS), CONN_DEAD, 0, 0); +static void BT_ReadAudiPin(void) +{ + uint16_t pin = 0; + if (KLine_FSM_IsConnected()) { + uint8_t ok = ReadAudiPin(&pin); + HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), + ok ? HC06_STATUS_OK : (uint16_t)KLINE_ERROR, + 0, + pin); + } else { + HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), CONN_DEAD, 0, 0); } } diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/IKW1281Connection.c b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/IKW1281Connection.c deleted file mode 100644 index 5b45a26..0000000 --- a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/IKW1281Connection.c +++ /dev/null @@ -1,296 +0,0 @@ -/* - * IKW1281Connection.c - * - * Created on: Apr 9, 2025 - * Author: herli - */ - -#include -#include -#include -#include - -#include "IKW1281Connection.h" -#include "main.h" - - -static void SendAckPacket(void); - -static void WriteComplement(uint8_t b); -static void WriteByteAndDiscardEcho(uint8_t b); -static void WriteByteAndReadAck(uint8_t b); -static void WriteByteRaw(uint8_t b); - -uint8_t timeoutdata[] = "TimeoutNigga\r\n"; -//uint8_t RxData[1]; -uint8_t TxData[KLINE_BUFFER_SIZE]; -uint8_t KlineData[KLINE_BUFFER_SIZE]; -extern volatile uint8_t rx_done_flag; -uint8_t connectionAlive = 0; - - -uint8_t ReadByte(void) -{ - if (!connectionAlive) return 0; - - rx_done_flag = 0; - - // Re-arm UART RX interrupt for next byte - HAL_UART_Receive_IT(&huart1, (uint8_t*)&k_rx_byte, 1); - - uint32_t timeout = HAL_GetTick() + 1000; - while (!rx_done_flag) - { - if ((int32_t)(HAL_GetTick() - timeout) >= 0) - { - HAL_UART_AbortReceive(&huart1); // cancel the pending IT - connectionAlive = 0; - return 0xFF; - } - } - - // Safely capture the byte (atomic copy) - uint8_t b = k_rx_byte; - return b; -} - -uint8_t ReadAndAckByte(void){ - uint8_t b = ReadByte(); - WriteComplement(b); - return b; -} -void ReadComplement(uint8_t b){ - uint8_t expectedComplement = (uint8_t)~b; - uint8_t actualComplement = ReadByte(); - //if(actualComplement != expectedComplement){return;} -} -void WriteComplement(uint8_t b){ - uint8_t complement = (uint8_t)~b; - WriteByteAndDiscardEcho(complement); -} -void WriteByteAndDiscardEcho(uint8_t b){ - WriteByteRaw(b); - uint8_t echo = ReadByte(); - //if(echo != b){return;} -} - -void WriteByteRaw(uint8_t b){ - TxData[0] = b; - HAL_UART_Transmit(&huart1, TxData, 1, 100); -} - -#define MAX_PACKETS 16 // adjust to your needs -static ParsedPacket packets_buffer[MAX_PACKETS]; - - -ParsedPacket* ReceivePackets(int *out_count) -{ - int count = 0; - - while (1) { - if (count >= MAX_PACKETS) { - // reached max, stop receiving more - break; - } - - ParsedPacket packet = ReceivePacket(); - packets_buffer[count++] = packet; - - if (packet.isAckNak || !connectionAlive) { - break; - } - - SendAckPacket(); - - } - - if (out_count) { - *out_count = count; - } - return packets_buffer; // returns pointer to static buffer -} -/* -ParsedPacket* ReceivePackets(int *out_count) { - int capacity = 16; // initial capacity - int count = 0; - ParsedPacket *packets = malloc(capacity * sizeof(ParsedPacket)); - if (!packets) return NULL; - - while (1) { - ParsedPacket packet = ReceivePacket(); - - // Resize array if needed - if (count >= capacity) { - capacity *= 2; - ParsedPacket *new_packets = realloc(packets, capacity * sizeof(ParsedPacket)); - if (!new_packets) { - free(packets); - return NULL; - } - packets = new_packets; - } - - packets[count++] = packet; - - if (packet.isAckNak) break; - - SendAckPacket(); - } - - if (out_count) *out_count = count; - return packets; -}*/ - -uint8_t _packetCounter = 0; -uint8_t _packetCounterInitialized = 0; - -ParsedPacket ReceivePacket() -{ - ParsedPacket packet = {0}; - if (!connectionAlive) return packet; // add this - - uint8_t index = 0; - - uint8_t packetLength = ReadAndAckByte(); - //packet.raw = (uint8_t*)malloc(packetLength*sizeof(uint8_t)); - - packet.raw[index++] = packetLength; - - uint8_t packetCounter = ReadPacketCounter(); - packet.raw[index++] = packetCounter; - - uint8_t packetCommand = ReadAndAckByte(); - packet.raw[index++] = packetCommand; - - for (int i = 0; i < packetLength - 3; i++) { - uint8_t b = ReadAndAckByte(); - packet.raw[index++] = b; - } - - uint8_t packetEnd = ReadByte(); // no ACK - packet.raw[index++] = packetEnd; - packet.length = index; //tiene que ser index - - if (packetEnd != 0x03) { - // Protocol error handling here - return packet; - } - - // Now classify the packet type - packet.title = packetCommand; - - switch (packetCommand) { - case PACKET_CMD_ACK: - packet.type = PACKET_TYPE_ACK; - packet.isAckNak = 1; - break; - - case PACKET_CMD_AsciiData: - if (packet.raw[3] == 0x00) - packet.type = PACKET_TYPE_CODING_WSC; - else - packet.type = PACKET_TYPE_ASCII_DATA; - break; - - case PACKET_CMD_ReadEepromResponse: - packet.type = PACKET_TYPE_READ_EEPROM_RESPONSE; - break; - - case PACKET_CMD_ReadRomEepromResponse: - packet.type = PACKET_TYPE_READ_ROM_EEPROM_RESPONSE; - break; - - case PACKET_CMD_NAK: - packet.type = PACKET_TYPE_NAK; - packet.isAckNak = 1; - default: - packet.type = PACKET_TYPE_UNKNOWN; - break; - } - return packet; -} -void ResetPacketCounter(){ - _packetCounter = 0; -} -uint8_t ReadPacketCounter() -{ - uint8_t packetCounter = ReadAndAckByte(); - - if (!_packetCounterInitialized) - { - _packetCounter = packetCounter; - _packetCounterInitialized = 1; - } - else if (packetCounter != _packetCounter) - { - // Handle mismatch (drop packet, reset, etc.) - } - - _packetCounter++; - return packetCounter; -} -void SendAckPacket() -{ - uint8_t ackByte = (uint8_t)PACKET_CMD_ACK; - SendPacket(&ackByte, 1); -} -void SendPacket(uint8_t* payload, uint8_t length) -{ - uint8_t packetLength = length + 2; // +2 for length and counter - uint8_t packet[packetLength]; - - int index = 0; - - packet[index++] = packetLength; //0 - packet[index++] = _packetCounter++; //1 - - for (int i = 0; i < length; i++) - { - packet[index++] = payload[i]; - } - for (int i = 0; i < index; i++) - { - WriteByteAndReadAck(packet[i]); - HAL_Delay(5); // Small delay between bytes - } - WriteByteRaw(PACKET_END_EXPECTED); // End byte, no ACK expected -} - -void WriteByteAndReadAck(uint8_t b) -{ - WriteByteRaw(b); - uint8_t ack = ReadByte(); - - HAL_UART_AbortReceive(&huart1); - - uint8_t expectedAck = (uint8_t)~b; - if (ack != expectedAck) - { - // Handle NAK or retry - } -} - -// Placeholder for platform-specific read functions - -uint8_t KeepAlive() { - SendAckPacket(); - ParsedPacket packet = ReceivePacket(); - if(packet.type != PACKET_TYPE_ACK){ - return 0; - } - return 1; -} -void EndCommunication(){ - uint8_t endPacket = (uint8_t)PACKET_CMD_End; - SendPacket((uint8_t*)endPacket, 1); - ResetPacketCounter(); -} - -ParsedPacket* SendCustom(const uint8_t* data, int len, int *out_count) { - SendPacket((uint8_t*)data, len); //antes & - int count = 0; - ParsedPacket* packets = ReceivePackets(&count); - *out_count = count; - return packets; -} - diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/IKW1281Connection.h b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/IKW1281Connection.h index 5e69461..ccded7e 100644 --- a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/IKW1281Connection.h +++ b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/IKW1281Connection.h @@ -11,64 +11,63 @@ #include #include "stdint.h" -extern volatile uint8_t k_rx_byte; -extern volatile uint8_t rx_done_flag; -extern uint8_t connectionAlive; - -#define KLINE_BUFFER_SIZE 20 - -#define PACKET_END_EXPECTED 0x03 +#define ECU_INIT_ADDRESS 0xF1 +#define KLINE_GPIO_PORT GPIOB +#define KLINE_PIN GPIO_PIN_14 +/* ------------------------------------------------------------------ */ +/* Packet framing */ +/* ------------------------------------------------------------------ */ +#define PACKET_END_EXPECTED 0x03 +#define MAX_PACKET_SIZE 16 +#define EEPROM_RESPONSE_BODY_MAX 64 -//typedef uint8_t PacketCommand; -#define MAX_PACKET_SIZE 17 // if too big it will crash 128length - 1; i++) { - char c = packet->raw[i] & 0x7F; - if (len < sizeof(data) - 2) { - data[len++] = c; - } - } - - // Null-terminate and add newline - data[len++] = '\r\n'; - data[len] = '\0'; - - HAL_UART_Transmit(huart, (uint8_t*)data, len, 1000); - - // Optional: report more data - if (packet->raw[3] > 0x7F) { - const char more[] = "More data available via ReadIdent\r\n"; - HAL_UART_Transmit(huart, (uint8_t*)more, sizeof(more) - 1, 1000); - } -}*/ - -int FilterNonAckNak(ParsedPacket* all, int total, ParsedPacket* filtered, int max) -{ - int j = 0; - for (int i = 0; i < total && j < max; i++) { - if (!all[i].isAckNak) { - filtered[j++] = all[i]; - } - } - return j; -} -static ControllerInfo info = {0}; // Zero init all strings - -ControllerInfo ReadEcuInfo() { - int packet_count = 0; - - ParsedPacket *packets = ReceivePackets(&packet_count); - - char combined[128] = {0}; // Temporary buffer to build full ASCII text - - if (packets != NULL) { - for (int i = 0; i < packet_count; i++) { - ParsedPacket *p = &packets[i]; - if (p->type == PACKET_TYPE_ASCII_DATA && p->length > 4) { - size_t len = p->length - 4; // Exclude first 3 and last byte - strncat(combined, (char*)(p->raw + 3), len); - } - } - //free(packets); // clean up when done - } - - memset(&info, 0, sizeof(ControllerInfo)); - - // Fill ControllerInfo fields from combined text - strncpy(info.client_ident, combined, 12); - strncpy(info.unk_ident1, combined + 12, 10); - strncpy(info.soft_info, combined + 22, 10); - - if (strlen(combined) > 40) - strncpy(info.unk_ident2, combined + 32, 10); - if (strlen(combined) > 50) - strncpy(info.unk_ident3, combined + 42, 10); - - //free(combined); - return info; -} - -/*void PrintEcuInfo(ControllerInfo* info) { - char data[64]; - - sprintf(data, "client_ident: %s\t\r\n", info->client_ident); - //HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000); - CDC_Transmit_FS((uint8_t*)data, strlen(data)); - HAL_Delay(10); // allow USB stack to flush - - sprintf(data, "unk_ident1: %s\t\r\n", info->unk_ident1); - //HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000); - CDC_Transmit_FS((uint8_t*)data, strlen(data)); - HAL_Delay(10); // allow USB stack to flush - - sprintf(data, "soft_info: %s\t\r\n", info->soft_info); - //HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000); - CDC_Transmit_FS((uint8_t*)data, strlen(data)); - HAL_Delay(10); // allow USB stack to flush - - if (strlen(info->unk_ident2) > 0) { - sprintf(data, "unk_ident2: %s\t\r\n", info->unk_ident2); - //HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000); - CDC_Transmit_FS((uint8_t*)data, strlen(data)); - HAL_Delay(10); // allow USB stack to flush - - } - - if (strlen(info->unk_ident3) > 0) { - sprintf(data, "unk_ident3: %s\t\r\n", info->unk_ident3); - CDC_Transmit_FS((uint8_t*)data, strlen(data)); - HAL_Delay(10); // allow USB stack to flush - -// HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000); - } -}*/ -#define MAX_EEPROM_DATA 256 // adjust to your ECU’s max response -#define MAX_PACKETS 16 - -static ParsedPacket packet_buffer[MAX_PACKETS]; -static uint8_t eeprom_result[MAX_EEPROM_DATA]; - -uint8_t* ReadRomEeprom(uint16_t address, uint8_t count, int* outLength) -{ - uint8_t request[4] = { - PACKET_CMD_ReadRomEeprom, // 0x08 usually - count, - (uint8_t)(address >> 8), - (uint8_t)(address & 0xFF) - }; - - int packetCount = 0; - ParsedPacket* packets = SendCustom(request, sizeof(request), &packetCount); - if (!packets || packetCount == 0) { - *outLength = 0; - return NULL; - } - - // Check for NAK - if (packetCount == 1 && packets[0].type == PACKET_TYPE_NAK) { - *outLength = 0; - return NULL; - } - - // Collect useful packets (non-Ack/Nak) - int usefulCount = 0; - for (int i = 0; i < packetCount && usefulCount < MAX_PACKETS; i++) { - if (!packets[i].isAckNak) { - packet_buffer[usefulCount++] = packets[i]; - } - } - - if (usefulCount != 1) { - *outLength = 0; - return NULL; // Expect exactly one useful packet - } - - int dataLen = packet_buffer[0].length - 4; // skip 3-byte header + 1-byte end - if (dataLen > MAX_EEPROM_DATA) { - dataLen = MAX_EEPROM_DATA; // clamp to buffer size - } - - memcpy(eeprom_result, packet_buffer[0].raw + 3, dataLen); - *outLength = dataLen; - - return eeprom_result; // static buffer pointer -} -/* -uint8_t* ReadRomEeprom(uint16_t address, uint8_t count, int* outLength) { - uint8_t request[4] = { - PACKET_CMD_ReadRomEeprom, // 0x08 usually - count, - (uint8_t)(address >> 8), - (uint8_t)(address & 0xFF) - }; - - int packetCount = 0; - ParsedPacket* packets = SendCustom(request, sizeof(request), &packetCount, 0); - if (!packets || packetCount == 0) { - *outLength = 0; - return NULL; - } - - // Check for NAK - if (packetCount == 1 && packets[0].type == PACKET_TYPE_NAK) { - freeParsedPackets(packets, packetCount); - *outLength = 0; - return NULL; - } - - // Filter to exclude Ack/Nak - ParsedPacket* useful = malloc(packetCount * sizeof(ParsedPacket)); - int usefulCount = 0; - for (int i = 0; i < packetCount; i++) { - if (!packets[i].isAckNak) { - useful[usefulCount++] = packets[i]; - } else { - // If dynamically allocated, free individual packet.raw - free(packets[i].raw); - } - } - free(packets); // Free the original array pointer (but not the reused raw data) - - if (usefulCount != 1) { - freeParsedPackets(useful, usefulCount); - *outLength = 0; - return NULL; // Or handle as error - } - - uint8_t* result = malloc(useful[0].length - 4); // Remove 3-byte header + end (or however yours works) - if (!result) { - freeParsedPackets(useful, usefulCount); - *outLength = 0; - return NULL; - } - - memcpy(result, useful[0].raw + 3, useful[0].length - 4); // Adjust if header is different - *outLength = useful[0].length - 4; - - freeParsedPackets(useful, usefulCount); - return result; -}*/ - -void ReadSerialNumber(char* serialOut, int maxLen) { - uint8_t request[] = { 0x19, 0x06, 0x00, 0x80 }; - - int packetCount = 0; - ParsedPacket* packets = SendCustom(request, sizeof(request), &packetCount); - - if (!packets || packetCount == 0) { - serialOut[0] = '\0'; - return; - } - - for (int i = 0; i < packetCount; i++) { - ParsedPacket* p = &packets[i]; - if (!p->isAckNak && p->type == PACKET_TYPE_READ_EEPROM_RESPONSE) { - int dataLen = p->length - 4; // Skip length, counter, command, end byte - if (dataLen > maxLen - 1) { - dataLen = maxLen - 1; - } - - memcpy(serialOut, p->raw + 3, dataLen); // Adjust if needed - serialOut[dataLen] = '\0'; - - //freeParsedPackets(packets, packetCount); - return; - } - } - - serialOut[0] = '\0'; - //freeParsedPackets(packets, packetCount); -} - -/*void freeParsedPackets(ParsedPacket* packets, int count) { - if (!packets) return; - - for (int i = 0; i < count; i++) { - if (packets[i].raw != NULL) { - //free(packets[i].raw); - //packets[i].raw = NULL; // Avoid double free - } - } - - //free(packets); // Free the packet array itself -}*/ - -uint16_t ExtractEepromAddress(ParsedPacket *packets, int packet_count) { - for (int i = 0; i < packet_count; i++) { - ParsedPacket *p = &packets[i]; - if (!p->isAckNak && p->length > 4) { - uint32_t address = (p->raw[3+1] << 8) | p->raw[3]; - return address - 10; - } - } - return 0; -} -float ReadDfi(){ - float Dfi = 0.0; - if(!KeepAlive()){ - KLINE_THROW_NONALIVE_EXCEPTION(0); - return 0.0; - } - //uint8_t request[] = { 0x18, 0x00, 0x03, 0xFF, 0xFF }; //TODO ERROR ENVIA AC 7E 00 20 00 - int packet_count = 0; - //ParsedPacket* packets = SendCustom(request, 5, &packet_count, 1); //this changes with pump version but for now no exceptions - ParsedPacket* packets = SendCustom((uint8_t[]){ 0x18, 0x00, 0x03, 0xFF, 0xFF }, 5, &packet_count); - - //Dfi = 1.0; - if(!KeepAlive()){ - KLINE_THROW_NONALIVE_EXCEPTION(0); - return 0.0; - } - - uint8_t request2[] = { 0x19, 0x02, 0x00, 0x44}; - packets = SendCustom(request2, 4, &packet_count); //this changes with pump version but for now no exceptions - - - // Filter to exclude Ack/Nak - for (int i = 0; i < packet_count; i++) { - if (!packets[i].isAckNak) { - Dfi = ((int8_t)packets[i].raw[3] * 3.0) / 256.0; - } - } - //free(packets); // Free the original array pointer (but not the reused raw data) - /*if(Dfi != 0.0){ - - }*/ - return Dfi; -} - -/** - * Write DFI parameter (EEPROM 0x44) after password unlock. - * - * version mapping (as in your C#): - * default: {18 00 03 2F FF ...} - * version==1: {18 00 03 2F F2 ...} - * version==2 or 3: {18 00 03 FF F2 ...} - * - * Returns 1 on success, 0 on failure (non-alive). - */ -int WriteDfi(float dfi, int version) -{ - if (!KeepAlive()) { - KLINE_THROW_NONALIVE_EXCEPTION(0); - return 0; - } - - // Password packets (14 bytes) - static const uint8_t pass_default[14] = { - 0x18, 0x00, 0x03, 0x2F, 0xFF, 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45 - }; // V1 KHTCA8G0E (per your comment) - static const uint8_t pass_v2[14] = { - 0x18, 0x00, 0x03, 0x2F, 0xF2, 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45 - }; // V2 KHTCA8G0E - static const uint8_t pass_v3v4[14] = { - 0x18, 0x00, 0x03, 0xFF, 0xF2, 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45 - }; // V3, V4 - - const uint8_t *password_packet = pass_default; - if (version == 1) { - password_packet = pass_v2; - } else if (version == 2 || version == 3) { - password_packet = pass_v3v4; - } - - int packet_count = 0; - (void)SendCustom(password_packet, 14, &packet_count); // throwOnNak=1 (strict) - - if (!KeepAlive()) { - KLINE_THROW_NONALIVE_EXCEPTION(0); - return 0; - } - - // C#: - // sbyte value = (sbyte)((dfi * 256.0) / 3); - // if (value == 0) value = 1; - // - // NOTE: with dfi up to +1.5, math gives 128.0. That does not fit in int8_t. - // We clamp to [-128..127] to avoid wrap to -128. - int vi = (int)((dfi * 256.0f) / 3.0f); // trunc toward 0 (matches typical C# unchecked cast behavior for in-range) - vi = vi > 127 ? 127:vi; - vi = vi < -128 ? -128:vi; - //vi = clamp_int(vi, -128, 127); - int8_t value = (int8_t)vi; - if (value == 0) value = 1; - - // byte value_csum = (byte)(0 - (byte)value); - uint8_t value_u = (uint8_t)value; - uint8_t value_csum = (uint8_t)(0u - value_u); - - // Write command - uint8_t req[7] = { 0x1A, 0x02, 0x00, 0x44, value_u, value_csum, 0x03 }; - (void)SendCustom(req, 7, &packet_count); // throwOnNak=1 (strict) - - if (!KeepAlive()) { - KLINE_THROW_NONALIVE_EXCEPTION(0); - return 0; - } - - return 1; -} -void ReadCustomerChangeIndex(){ - if(!KeepAlive()){ - KLINE_THROW_NONALIVE_EXCEPTION(0); - return; - } - uint8_t request[] = { 0x18, 0x00, 0x00, 0x82, 0x33 }; - - int packet_count = 0; - ParsedPacket* packets = SendCustom(request, 5, &packet_count); //this changes with pump version but for now no exceptions - - //char message[40]; - //sprintf(message, "Reading customer change index\r\n"); - //HAL_UART_Transmit(&huart2, (uint8_t*)message, strlen(message), 1000); - - if(!KeepAlive()){ - KLINE_THROW_NONALIVE_EXCEPTION(12); - return; - } - - uint16_t cust_change_address = 0; - - int data_length = 0; - - uint8_t* test_data = ReadRomEeprom(0x9FFE, 2, &data_length); - if(data_length > 1){ - cust_change_address = (uint16_t)((test_data[1] << 8) | test_data[0]); - cust_change_address += 3; - } - - test_data = ReadRomEeprom(cust_change_address, 6, &data_length); - - char cust_change_index[12]; - - if (test_data && data_length > 0) { - for (int i = 0; i < data_length; i++) { - cust_change_index[i] = (char)test_data[i]; // Convert byte to char - } - } - //sprintf(message, "Mod. Index : %s\r\n", cust_change_index); - //CDC_Transmit_FS((uint8_t*)message, strlen(message)); - /*char cust_change_index[12]; - - if (test_data && data_length > 0) { - int copy_len = (data_length < (sizeof(cust_change_index) - 1)) - ? data_length - : (sizeof(cust_change_index) - 1); - - for (int i = 0; i < copy_len; i++) { - cust_change_index[i] = (char)test_data[i]; - } - cust_change_index[copy_len] = '\0'; // Null-terminate - } - - sprintf(message, "Mod. Index : %s\r\n", cust_change_index); - CDC_Transmit_FS((uint8_t*)message, strlen(message));*/ - //HAL_UART_Transmit(&huart2, (uint8_t*)cust_change_index, strlen(cust_change_index), 1000); -} -char identStr[11]; //11+1 - -void IdentifyEcu() { - //char outputStr[35]; - //float dFi = ReadDfi(); - //sprintf(outputStr, "dFi: %d.%02d\t\t\r\n", (int)dFi, (int)((dFi - (int)dFi) * 100)); - - //CDC_Transmit_FS((uint8_t*)outputStr, strlen(outputStr)); - - ReadCustomerChangeIndex();//Necessary - - if(!KeepAlive()){ - KLINE_THROW_NONALIVE_EXCEPTION(0); - return; - } - int packet_count = 0; - ParsedPacket* packets = SendCustom((uint8_t[]){ 0x01, 0x02, 0x00, 0xC6 }, 4, &packet_count); - - //uint16_t address = ExtractEepromAddress(packets, count); - - uint16_t address = ExtractEepromAddress(packets, packet_count); - - if(!KeepAlive()){ - KLINE_THROW_NONALIVE_EXCEPTION(1); - return; - } - - if (address != 0) { - int data_length = 0; - uint8_t* test_data = ReadRomEeprom(address, 10, &data_length); - - for (int i = 0; i < data_length && i < sizeof(identStr) - 1; i++) { - identStr[i] = (char)test_data[i]; - } //identStr[10] = '\0'; - //HAL_UART_Transmit(&huart2, (uint8_t*)identStr, strlen(identStr), 1000); - //sprintf(outputStr, "Ident. : %s\t\r\n", identStr); - - //CDC_Transmit_FS((uint8_t*)outputStr, strlen(outputStr)); - - } - - if(!KeepAlive()){ - KLINE_THROW_NONALIVE_EXCEPTION(2); - return; - } - - char serial[20]; - ReadSerialNumber(serial, sizeof(serial)); - //sprintf(serial, "serial numb: %s\r\n", serial); - //HAL_UART_Transmit(&huart2, (uint8_t*)serial, strlen(serial), 1000); - /*sprintf(outputStr, "Serial N. : %s\t\r\n", serial); - - CDC_Transmit_FS((uint8_t*)outputStr, strlen(outputStr));*/ - - //sprintf(serial, "serial numb: %s\r\n", serial); - //HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000); -} - - -/* FAULT CODE READING */ - - -#define FAULTCODE_NONE_DTC 0x00 - -/* --- Tuning limits (adapt to your max) --- */ -#define MAX_FAULTCODES 20 -#define MAX_FC_AGGREGATE_LEN 512 // total concatenated body bytes - -int ReadFaultCodes(FaultCode* outCodes, int maxCodes) -{ - //char logmsg[64]; - - if (!outCodes || maxCodes <= 0) return -1; - - // Keep the link alive as you do elsewhere TODO - if (!KeepAlive()) { - KLINE_THROW_NONALIVE_EXCEPTION(0); - return -1; - } - - // "Sending ReadFaultCodes packet" - /*sprintf(logmsg, "Sending ReadFaultCodes packet\r\n"); - CDC_Transmit_FS((uint8_t*)logmsg, (uint16_t)strlen(logmsg));*/ - - // Send single-byte payload with command 0x07 - uint8_t cmd = (uint8_t)PACKET_CMD_FaultCodesRead; // already defined in your enum - SendPacket(&cmd, 1); - - // Receive all response packets - int packet_count = 0; - ParsedPacket* packets = ReceivePackets(&packet_count); - if (!packets || packet_count <= 0) { - return 0; // no codes - } - - // Concatenate all bodies from non-ACK/NAK packets - // Body = raw[3 .. (3 + body_len - 1)], where body_len = (length - 3). - // Full raw frame is: [len][counter][command][body...][end] - // Your receiver code writes the end byte separately (0x03). - // NOTE: guard against malformed lengths. - uint8_t fc_buf[64]; - int fc_len = 0; - - for (int i = 0; i < packet_count; i++) { - if (packets[i].isAckNak) continue; - - // If you classify types and want to be strict, enforce here: - if (packets[i].title != PACKET_CMD_FaultCodesResponse) { - /*sprintf(logmsg, "Expected FaultCodesPacket but got type=0x%02X\r\n", packets[i].type); - CDC_Transmit_FS((uint8_t*)logmsg, (uint16_t)strlen(logmsg));*/ - //free(packets); - return -1; - } - - if (!packets[i].raw || packets[i].length < 3) continue; - - int body_len = (int)packets[i].length - 4; // subtract [len, counter, command] - if (body_len <= 0) continue; - - // Defensive: make sure the raw buffer actually has those bytes (+1 end is read separately) - // We assume your parser filled .raw accordingly. - - if ((fc_len + body_len) > (int)sizeof(fc_buf)) { - /*sprintf(logmsg, "Fault code buffer overflow\r\n"); - CDC_Transmit_FS((uint8_t*)logmsg, (uint16_t)strlen(logmsg));*/ - //free(packets); - return -1; - } - - memcpy(&fc_buf[fc_len], &packets[i].raw[3], (size_t)body_len); - fc_len += body_len; - } - - // Done with the packet list (your comment: free only the array, not reused raw) - //free(packets); - - // Parse aggregated data: - // C# does: take first 3 bytes (dtc,status,extra), then Skip(8) per record. - // That means record stride = 8 bytes, but only first 3 matter here. - // Stop when fewer than 3 bytes remain. - int produced = 0; - int off = 0; - - while (off + 3 <= fc_len) { - uint8_t dtc = fc_buf[off + 0]; - uint8_t status = fc_buf[off + 1]; - uint8_t extra = fc_buf[off + 2]; - - if (dtc != FAULTCODE_NONE_DTC) { - if (produced < maxCodes) { - outCodes[produced].dtc = dtc; - outCodes[produced].status = status; - outCodes[produced].extra = extra; - produced++; - } else { - /*sprintf(logmsg, "Fault code output full (%d)\r\n", maxCodes); - CDC_Transmit_FS((uint8_t*)logmsg, (uint16_t)strlen(logmsg));*/ - break; - } - } - - // Advance to next 8-byte record - off += 8; - } - - return produced; // number of valid fault codes in outCodes[] -} -int ClearFaultCodes(void) -{ - int packet_count = 0; - uint8_t req[1] = { PACKET_CMD_FaultCodesDelete }; - - ParsedPacket *packets = SendCustom(req, 1, &packet_count); - - // C# expects exactly 1 packet - if (packet_count != 1) { - // in embedded: return error code (or assert/log) - return 0; - } - - ParsedPacket *p = &packets[0]; - - // Must be ACK or NAK - if (!p->isAckNak) { - return 0; - } - - if (p->type == PACKET_TYPE_NAK) return 0; - if (p->type == PACKET_TYPE_ACK) return 1; - - return 0; -} - -/*void PrintFaultCodes(const FaultCode* list, int n) -{ - if (!list || n <= 0) { - const char* msg = "No fault codes\r\n"; - CDC_Transmit_FS((uint8_t*)msg, (uint16_t)strlen(msg)); - return; - } - for (int i = 0; i < n; i++) { - PrintFaultCode(&list[i]); - HAL_Delay(10); - } -}*/ - -typedef struct { uint8_t code; const char* text; } DtcMapEntry; - -static const DtcMapEntry DTC_MAP[] = { - {0x50, "Fuel quantity solenoid valve Output stage error"}, - {0x51, "Fuel quantity solenoid valve."}, - {0x52, "Angle sensor/ IWZ system."}, - {0x53, "Angle sensor/ IWZ system"}, - {0x54, "Control unit temperature sensor, temperature too high"}, - {0x55, "Control unit temperature sensor"}, - {0x56, "Battery voltage out of range"}, - {0x57, "Timing device control. Permanent control deviation"}, - {0x58, "Fuel quantity / timing solenoid valve"}, - {0x59, "BIP Fault (Begin of Injection Point)"}, - {0x5A, "Engine speed signal"}, - {0x5B, "Engine speed signal"}, - {0x5C, "CAN-Bus (sporadic)"}, - {0x5D, "CAN bus error"}, - {0x5E, "Self-test error"}, -}; - -static const char* GetDtcText(uint8_t code) -{ - for (unsigned i = 0; i < (sizeof(DTC_MAP)/sizeof(DTC_MAP[0])); i++) { - if (DTC_MAP[i].code == code) return DTC_MAP[i].text; - } - return "Unknown Error Code"; -} -/*int FaultCode_ToString(const FaultCode* fc, char* dst, int dst_len) -{ - if (!fc || !dst || dst_len <= 0) return 0; - - uint8_t status1 = (uint8_t)(fc->status & 0x7F); - const char* txt = GetDtcText(fc->dtc); - - // Format like: "5A Engine speed signal (03)\r\n" - // (use %02u to mimic ":d2" from your C# example) - int n = snprintf(dst, (size_t)dst_len, "%02X %s (%02u)\r\n", - fc->dtc, txt, status1); - if (n < 0) n = 0; - if (n >= dst_len) n = dst_len - 1; - return n; -}*/ -/*void PrintFaultCode(const FaultCode* fc) -{ - char line[128]; - int n = FaultCode_ToString(fc, line, sizeof(line)); - if (n > 0) { - CDC_Transmit_FS((uint8_t*)line, (uint16_t)n); - } -}*/ - -void KLINE_THROW_NONALIVE_EXCEPTION(uint8_t id){ - //char erroralive[20]; - //sprintf(erroralive, "isnt alive %u\r\r\n", id); - BT_KLINE_ERROR(); - //HAL_UART_Transmit(&huart2, (uint8_t*)erroralive, strlen(erroralive), 1000); - //CDC_Transmit_FS((uint8_t*)erroralive, strlen(erroralive)); - -} - -uint8_t ReadAudiPin(uint16_t* pin) { - int data_length = 0; - uint8_t* data = ReadRomEeprom(0x04FA, 2, &data_length); - if(data_length < 1){return 0;} - *pin = (uint16_t)((data[1] << 8) | data[0]); - return 1; -} -uint8_t WriteAudiPin(uint16_t pin) { - /*uint8_t* data = ReadRomEeprom(0x04FA, 2, &data_length); - if(data_length < 1){return 0;} - *pin = (uint16_t)((data[1] << 8) | data[0]);*/ - return 1; -} - -uint8_t ReadVoltage(uint16_t* volt) { - int data_length = 0; - uint8_t* data = ReadRomEeprom(0x0142, 2, &data_length); - if(data_length < 1){return 0;} - *volt = (uint16_t)((data[1] << 8) | data[0]); - return 1; -} diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline.h b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline.h deleted file mode 100644 index 4196d71..0000000 --- a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * kline.h - * - * Created on: Aug 18, 2025 - * Author: herli - */ - -#ifndef INC_KLINE_H_ -#define INC_KLINE_H_ - -#include "IKW1281Connection.h" - -#define ECU_INIT_ADDRESS 0xF1 - -#define KLINE_GPIO_PORT GPIOB -#define KLINE_PIN GPIO_PIN_14 - -extern uint8_t BitBang; - -typedef struct { - uint8_t dtc; // Primary DTC byte - uint8_t status; // Status byte - uint8_t extra; // Third byte in the 3-byte header (kept for parity with C#) -} FaultCode; - -extern ControllerInfo ReadEcuInfo(void); -int WakeUp(uint8_t controllerAddress, uint8_t evenParity); -void IdentifyEcu(void); -int ReadFaultCodes(FaultCode* outCodes, int maxCodes); -uint8_t ReadVoltage(uint16_t* volt); -extern void KLINE_THROW_NONALIVE_EXCEPTION(uint8_t id); -int FaultCode_ToString(const FaultCode* fc, char* dst, int dst_len); - -#endif /* INC_KLINE_H_ */ diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_fsm.c b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_fsm.c new file mode 100644 index 0000000..81cdf0b --- /dev/null +++ b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_fsm.c @@ -0,0 +1,791 @@ +/* + * kline_fsm.c + * + * Full non-blocking KWP1281 state machine. + * + * Timing model + * ------------ + * Every state that needs a delay stores a deadline: + * fsm.deadline = HAL_GetTick() + N_ms; + * and on the next ST_xxx entry just checks: + * if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return; + * + * This means KLine_FSM_Run() returns almost immediately on every call + * and the main loop stays responsive for HC-06 processing. + * + * Zero HAL_Delay calls anywhere in this file. + */ + +#include "kline_fsm.h" +#include "kline_ring.h" +#include "main.h" /* huart1, KLINE_GPIO_PORT, KLINE_PIN */ +#include "hc_06.h" /* BT_OnPSG5CommEstablished, BT_SendCommStatus */ + +#include +#include + +/* ------------------------------------------------------------------ */ +/* Internal timing constants (ms) */ +/* ------------------------------------------------------------------ */ + +#define BITBANG_BIT_MS 200u /* 5 baud = 200 ms/bit */ +#define KWP_INTERBYTE_TX_MS 5u /* between bytes we send */ +#define KWP_BYTE_TIMEOUT_MS 1000u /* max wait for a byte from ECU */ +#define KWP_KW2_COMP_DELAY_MS 5u /* delay before sending ~KW2 */ +#define KWP_KEEPALIVE_PERIOD_MS 500u /* how often we must send keep-alive */ + +/* ------------------------------------------------------------------ */ +/* Low-level TX helpers (synchronous – tiny at 9600 baud, safe) */ +/* ------------------------------------------------------------------ */ + +static void tx_byte(uint8_t b) +{ + HAL_UART_Transmit(&huart1, &b, 1, 10); +} + +void KLine_FSM_TxByte(uint8_t b) +{ + tx_byte(b); +} + +/* ------------------------------------------------------------------ */ +/* Packet parsing helpers */ +/* ------------------------------------------------------------------ */ + +#define MAX_RECV_PACKETS 16 +#define MAX_PACKET_RAW 20 + +/* Scratch packet being assembled right now */ +static ParsedPacket cur_pkt; +static uint8_t cur_pkt_body_idx; /* how many body bytes received so far */ + +/* Completed packet list for the current ReceivePackets burst */ +static ParsedPacket pkt_list[MAX_RECV_PACKETS]; +static int pkt_count; + +static uint8_t pkt_counter_val; +static uint8_t pkt_counter_init; + +static void classify_packet(ParsedPacket *p) +{ + p->title = p->raw[2]; + p->isAckNak = 0; + + switch (p->raw[2]) { + case PACKET_CMD_ACK: + p->type = PACKET_TYPE_ACK; + p->isAckNak = 1; + break; + case PACKET_CMD_NAK: + p->type = PACKET_TYPE_NAK; + p->isAckNak = 1; + break; + case PACKET_CMD_AsciiData: + p->type = (p->raw[3] == 0x00) ? PACKET_TYPE_CODING_WSC + : PACKET_TYPE_ASCII_DATA; + break; + case PACKET_CMD_ReadEepromResponse: + p->type = PACKET_TYPE_READ_EEPROM_RESPONSE; + break; + case PACKET_CMD_ReadRomEepromResponse: + p->type = PACKET_TYPE_READ_ROM_EEPROM_RESPONSE; + break; + default: + p->type = PACKET_TYPE_UNKNOWN; + break; + } +} + +/* ------------------------------------------------------------------ */ +/* Outgoing packet scratch buffer */ +/* ------------------------------------------------------------------ */ + +#define MAX_SEND_PACKET 17 + +static uint8_t send_buf[MAX_SEND_PACKET]; +static uint8_t send_len; /* total bytes in send_buf (excl. end byte) */ +static uint8_t send_idx; /* index of next byte to transmit */ + +//static uint8_t tx_pkt_counter = 0; + +static void build_packet(const uint8_t *payload, uint8_t payload_len) +{ + send_len = payload_len + 2; /* length byte + counter byte + payload */ + send_buf[0] = send_len; + send_buf[1] = pkt_counter_val++; + for (uint8_t i = 0; i < payload_len; i++) + send_buf[2 + i] = payload[i]; + //send_buf[2 + payload_len] = PACKET_END_EXPECTED; + send_idx = 0; +} + +/* ------------------------------------------------------------------ */ +/* FSM context */ +/* ------------------------------------------------------------------ */ + +typedef enum { + /* What to do after the generic send-packet sub-FSM finishes */ + AFTER_SEND_IDLE, + AFTER_SEND_RECV_ECUINFO, + AFTER_SEND_RECV_IDENT, + AFTER_SEND_RECV_KA, + AFTER_SEND_RECV_FAULTCODES, +} AfterSend; + +typedef enum { + /* What to do after the generic recv-packets sub-FSM finishes */ + AFTER_RECV_ECUINFO, + AFTER_RECV_IDENT, + AFTER_RECV_KA, + AFTER_RECV_FAULTCODES, +} AfterRecv; + +typedef struct { + KLine_FSMState state; + KLine_Status status; + uint32_t deadline; + + /* 5-baud bit-bang */ + uint8_t bb_addr; + uint8_t bb_bit; /* current bit index 0-7 */ + + /* WakeUp */ + uint8_t kw1; + + /* Generic TX sub-machine */ + AfterSend after_send; + uint8_t last_sent_byte; /* for ACK check */ + + /* Generic RX sub-machine */ + AfterRecv after_recv; + + /* ECU info */ + ControllerInfo ecu_info; + + /* Fault codes */ + KLine_FaultCode fault_codes[KLINE_MAX_FAULT_CODES]; + int fault_count; + + /* Keep-alive */ + uint32_t ka_due; + uint8_t ka_suspended; /* non-zero → KA paused while an op runs */ + + /* IdentifyEcu phase counter (how many ident packets received so far) */ + uint8_t ident_phase; + +} KLine_FSM; + +static KLine_FSM fsm; + +/* ------------------------------------------------------------------ */ +/* Forward declarations for sub-machine entry points */ +/* ------------------------------------------------------------------ */ + +static void enter_send_packet(const uint8_t *payload, uint8_t len, AfterSend after); +static void enter_recv_packets(AfterRecv after); + +/* ------------------------------------------------------------------ */ +/* Byte-level receive helpers */ +/* ------------------------------------------------------------------ */ + +/* + * Try to read a byte with complement-ACK from the ECU. + * Returns 1 and stores byte in *out if available, 0 if still waiting. + * Handles timeout: transitions to ST_ERROR on timeout. + */ +static uint8_t try_read_and_ack(uint8_t *out) +{ + if (!KLine_Ring_Available()) { + if ((int32_t)(HAL_GetTick() - fsm.deadline) >= 0) { + fsm.state = ST_ERROR; + fsm.status = KLINE_STATUS_ERROR; + } + return 0; + } + *out = KLine_Ring_Pop(); + tx_byte((uint8_t)~(*out)); /* send complement ACK */ + + + uint32_t echo_deadline = HAL_GetTick() + 5u; /* ~1ms at 9600, 5ms generous */ + while (!KLine_Ring_Available()) { + if ((int32_t)(HAL_GetTick() - echo_deadline) >= 0) break; + } + if (KLine_Ring_Available()) KLine_Ring_Pop(); + + fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS; + return 1; +} + +static uint8_t try_read_raw(uint8_t *out) +{ + if (!KLine_Ring_Available()) { + if ((int32_t)(HAL_GetTick() - fsm.deadline) >= 0) { + fsm.state = ST_ERROR; + fsm.status = KLINE_STATUS_ERROR; + } + return 0; + } + *out = KLine_Ring_Pop(); + fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS; + return 1; +} + +/* ------------------------------------------------------------------ */ +/* ECU info parser */ +/* ------------------------------------------------------------------ */ + +static void parse_ecu_info(void) +{ + char combined[128] = {0}; + for (int i = 0; i < pkt_count; i++) { + ParsedPacket *p = &pkt_list[i]; + if (p->type == PACKET_TYPE_ASCII_DATA && p->length > 4) { + size_t len = p->length - 4; + strncat(combined, (char*)(p->raw + 3), len); + } + } + memset(&fsm.ecu_info, 0, sizeof(ControllerInfo)); + strncpy(fsm.ecu_info.client_ident, combined, 12); + strncpy(fsm.ecu_info.unk_ident1, combined + 12, 10); + strncpy(fsm.ecu_info.soft_info, combined + 22, 10); + if (strlen(combined) > 40) + strncpy(fsm.ecu_info.unk_ident2, combined + 32, 10); + if (strlen(combined) > 50) + strncpy(fsm.ecu_info.unk_ident3, combined + 42, 10); +} + +/* ------------------------------------------------------------------ */ +/* Fault code parser */ +/* ------------------------------------------------------------------ */ + +static void parse_fault_codes(void) +{ + uint8_t fc_buf[64]; + int fc_len = 0; + + for (int i = 0; i < pkt_count; i++) { + if (pkt_list[i].isAckNak) continue; + if (pkt_list[i].title != PACKET_CMD_FaultCodesResponse) continue; + int body = (int)pkt_list[i].length - 4; + if (body <= 0) continue; + if (fc_len + body > (int)sizeof(fc_buf)) break; + memcpy(&fc_buf[fc_len], &pkt_list[i].raw[3], (size_t)body); + fc_len += body; + } + + fsm.fault_count = 0; + int off = 0; + while (off + 3 <= fc_len && fsm.fault_count < KLINE_MAX_FAULT_CODES) { + if (fc_buf[off] != 0x00) { + fsm.fault_codes[fsm.fault_count].dtc = fc_buf[off + 0]; + fsm.fault_codes[fsm.fault_count].status = fc_buf[off + 1]; + fsm.fault_codes[fsm.fault_count].extra = fc_buf[off + 2]; + fsm.fault_count++; + } + off += 8; + } +} + +/* ------------------------------------------------------------------ */ +/* Sub-machine entry helpers */ +/* ------------------------------------------------------------------ */ + +static void enter_send_packet(const uint8_t *payload, uint8_t len, AfterSend after) +{ + build_packet(payload, len); + fsm.after_send = after; + fsm.state = ST_SENDPKT_BYTE; + fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS; +} + +static void enter_recv_packets(AfterRecv after) +{ + pkt_count = 0; + pkt_counter_init = 0; + fsm.after_recv = after; + /* Start assembling the first packet */ + memset(&cur_pkt, 0, sizeof(cur_pkt)); + cur_pkt_body_idx = 0; + fsm.state = ST_RECVPKT_LENGTH; + fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS; +} + +/* ------------------------------------------------------------------ */ +/* Main FSM tick */ +/* ------------------------------------------------------------------ */ + +void KLine_FSM_Run(void) +{ + switch (fsm.state) + { + /* ====================================================== */ + case ST_IDLE: + /* Nothing to do — wait for KLine_FSM_RequestConnect() */ + break; + + /* ====================================================== */ + /* 5-baud bit-bang init */ + /* ====================================================== */ + + case ST_BITBANG_START: + { + /* De-init UART, take over TX pin as GPIO */ + HAL_UART_DeInit(&huart1); + + GPIO_InitTypeDef g = {0}; + g.Pin = KLINE_PIN; + g.Mode = GPIO_MODE_OUTPUT_PP; + g.Pull = GPIO_NOPULL; + g.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(KLINE_GPIO_PORT, &g); + + /* Drive start bit (low) */ + HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_PIN, GPIO_PIN_RESET); + fsm.bb_bit = 0; + fsm.deadline = HAL_GetTick() + BITBANG_BIT_MS; + fsm.state = ST_BITBANG_STARTBIT; + break; + } + + case ST_BITBANG_STARTBIT: + if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return; + /* Fall into first data bit */ + fsm.state = ST_BITBANG_DATABIT; + /* intentional fall-through */ + /* fall through */ + + case ST_BITBANG_DATABIT: + { + if (fsm.bb_bit > 0) { + /* We get here after a bit-delay has elapsed */ + if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return; + } + if (fsm.bb_bit == 8) { + /* All data bits sent — send stop bit */ + HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_PIN, GPIO_PIN_SET); + fsm.deadline = HAL_GetTick() + BITBANG_BIT_MS; + fsm.state = ST_BITBANG_STOPBIT; + break; + } + GPIO_PinState level = (fsm.bb_addr & (1u << fsm.bb_bit)) + ? GPIO_PIN_SET : GPIO_PIN_RESET; + HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_PIN, level); + fsm.bb_bit++; + fsm.deadline = HAL_GetTick() + BITBANG_BIT_MS; + break; + } + + case ST_BITBANG_STOPBIT: + if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return; + fsm.state = ST_BITBANG_DONE; + break; + + case ST_BITBANG_DONE: + { + /* Restore UART */ + extern void MX_USART1_UART_Init(void); /* generated by CubeMX */ + MX_USART1_UART_Init(); + KLine_Ring_Init(); /* flushes ring and arms RX IT */ + + fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS; + fsm.state = ST_WAIT_SYNC; + break; + } + + /* ====================================================== */ + /* WakeUp handshake */ + /* ====================================================== */ + + case ST_WAIT_SYNC: + { + uint8_t b; + if (!try_read_raw(&b)) return; + if (b != 0x55) { + fsm.state = ST_ERROR; + fsm.status = KLINE_STATUS_ERROR; + return; + } + fsm.state = ST_READ_KW1; + fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS; + break; + } + + case ST_READ_KW1: + { + uint8_t b; + if (!try_read_raw(&b)) return; + //tx_byte((uint8_t)~b); /* NO ACK KW1 */ + fsm.kw1 = b; + fsm.state = ST_READ_KW2; + fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS; + break; + } + + case ST_READ_KW2: + { + uint8_t b; + if (!try_read_raw(&b)) return; + /* Store KW2, schedule complement send after inter-byte delay */ + send_buf[0] = (uint8_t)~b; /* reuse send_buf[0] as a scratch */ + fsm.deadline = HAL_GetTick() + KWP_KW2_COMP_DELAY_MS; + fsm.state = ST_SEND_KW2_COMPLEMENT; + break; + } + + case ST_SEND_KW2_COMPLEMENT: + if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return; + tx_byte(send_buf[0]); /* send ~KW2 */ + + //uint8_t b; + //if (!try_read_raw(&b)) return; //should return answer to kw2 + while (!KLine_Ring_Available()) {} + KLine_Ring_Pop(); /* discard loopback echo */ + + /* Now receive the ECU info packet burst */ + enter_recv_packets(AFTER_RECV_ECUINFO); + break; + + /* ====================================================== */ + /* Generic send-packet sub-machine */ + /* ====================================================== */ + + case ST_SENDPKT_BYTE: + { + if (send_idx >= send_len) { + /* All bytes sent — send end byte (no ACK) */ + tx_byte(PACKET_END_EXPECTED); + while (!KLine_Ring_Available()) {} + KLine_Ring_Pop(); /* discard loopback echo */ + /* Transition based on what comes after this send */ + switch (fsm.after_send) { + case AFTER_SEND_RECV_ECUINFO: + enter_recv_packets(AFTER_RECV_ECUINFO); break; + case AFTER_SEND_RECV_IDENT: + enter_recv_packets(AFTER_RECV_IDENT); break; + case AFTER_SEND_RECV_KA: + enter_recv_packets(AFTER_RECV_KA); break; + case AFTER_SEND_RECV_FAULTCODES: + enter_recv_packets(AFTER_RECV_FAULTCODES);break; + default: + fsm.state = ST_KA_WAIT; break; + } + break; + } + /* Send next byte */ + fsm.last_sent_byte = send_buf[send_idx++]; + tx_byte(fsm.last_sent_byte); + while (!KLine_Ring_Available()) {} + KLine_Ring_Pop(); /* discard loopback echo */ + + fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS; + fsm.state = ST_SENDPKT_WAIT_ACK; + break; + } + + case ST_SENDPKT_WAIT_ACK: + { + /* Wait for ECU to echo ~byte as ACK */ + if (!KLine_Ring_Available()) { + if ((int32_t)(HAL_GetTick() - fsm.deadline) >= 0) { + fsm.state = ST_ERROR; + fsm.status = KLINE_STATUS_ERROR; + } + return; + } + uint8_t ack = KLine_Ring_Pop(); + (void)ack; /* Could check ack == ~last_sent_byte here */ + /* Inter-byte delay before next byte */ + fsm.deadline = HAL_GetTick() + KWP_INTERBYTE_TX_MS; + fsm.state = ST_SENDPKT_NEXT_BYTE; + break; + } + + case ST_SENDPKT_NEXT_BYTE: + if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return; + fsm.state = ST_SENDPKT_BYTE; + break; + + /* ====================================================== */ + /* Generic receive-packets sub-machine */ + /* ====================================================== */ + + case ST_RECVPKT_LENGTH: + { + //if ((int32_t)(HAL_GetTick() - (fsm.deadline - KWP_BYTE_TIMEOUT_MS + 25)) < 0) return; + + uint8_t b; + if (!try_read_and_ack(&b)) return; + + if(send_len == 3){ + send_len++; + } + memset(&cur_pkt, 0, sizeof(cur_pkt)); + cur_pkt_body_idx = 0; + cur_pkt.raw[0] = b; /* raw[0] = length byte */ + fsm.state = ST_RECVPKT_COUNTER; + break; + } + + case ST_RECVPKT_COUNTER: + { + uint8_t b; + if (!try_read_and_ack(&b)) return; + if (!pkt_counter_init) { + pkt_counter_val = b; + pkt_counter_init = 1; + } + pkt_counter_val++; + cur_pkt.raw[1] = b; + fsm.state = ST_RECVPKT_COMMAND; + break; + } + + case ST_RECVPKT_COMMAND: + { + uint8_t b; + if (!try_read_and_ack(&b)) return; + cur_pkt.raw[2] = b; + cur_pkt_body_idx = 3; + /* body length = packetLength - 3 (len, counter, command already read) */ + /* The end byte (0x03) is read separately in ST_RECVPKT_END */ + if (cur_pkt.raw[0] <= 3) { + /* No body bytes — go straight to end */ + fsm.state = ST_RECVPKT_END; + } else { + fsm.state = ST_RECVPKT_BODY; + } + if(b == PACKET_CMD_ACK || b == PACKET_CMD_NAK){ + cur_pkt.isAckNak = 1; + }else{ + cur_pkt.isAckNak = 0; + } + break; + } + + case ST_RECVPKT_BODY: + { + uint8_t b; + if (!try_read_and_ack(&b)) return; + if (cur_pkt_body_idx < MAX_PACKET_RAW) + cur_pkt.raw[cur_pkt_body_idx] = b; + cur_pkt_body_idx++; + /* body bytes = raw[0] - 3, so we've finished when we have raw[0] bytes total */ + if (cur_pkt_body_idx >= cur_pkt.raw[0]) { + fsm.state = ST_RECVPKT_END; + } + break; + } + + case ST_RECVPKT_END: + { + uint8_t b; + if (!try_read_raw(&b)) return; /* end byte – no ACK */ + cur_pkt.raw[cur_pkt_body_idx] = b; + cur_pkt.length = cur_pkt_body_idx + 1; + classify_packet(&cur_pkt); + + if (pkt_count < MAX_RECV_PACKETS) + pkt_list[pkt_count++] = cur_pkt; + + if (cur_pkt.isAckNak) { + /* This burst is complete — dispatch */ + switch (fsm.after_recv) { + + case AFTER_RECV_ECUINFO: + parse_ecu_info(); + /* Now do IdentifyEcu: send ReadIdent, receive ident packets */ + fsm.ident_phase = 0; + fsm.state = ST_IDENT_KEEPALIVE_SEND; + break; + + case AFTER_RECV_IDENT: + /* More ident packets may follow — check if we need another round */ + fsm.ident_phase++; + if (fsm.ident_phase < 3) { + /* Send another ReadIdent ACK and receive next burst */ + fsm.state = ST_IDENT_KEEPALIVE_SEND; + } else { + /* Done identifying — enter keep-alive + fault code read */ + uint8_t cmd = (uint8_t)PACKET_CMD_FaultCodesRead; + enter_send_packet(&cmd, 1, AFTER_SEND_RECV_FAULTCODES); + } + break; + + case AFTER_RECV_KA: + /* Keep-alive round-trip complete */ + if (pkt_list[pkt_count-1].type != PACKET_TYPE_ACK) { + fsm.state = ST_ERROR; + fsm.status = KLINE_STATUS_ERROR; + break; + } + KLine_OnKeepAliveTick(); + fsm.ka_due = HAL_GetTick() + KWP_KEEPALIVE_PERIOD_MS; + fsm.state = ST_KA_WAIT; + break; + + case AFTER_RECV_FAULTCODES: + parse_fault_codes(); + /* Session fully established */ + fsm.status = KLINE_STATUS_CONNECTED; + KLine_OnConnected(); + fsm.ka_due = HAL_GetTick() + KWP_KEEPALIVE_PERIOD_MS; + fsm.state = ST_KA_WAIT; + break; + } + } else { + /* More packets in this burst — send ACK then read next */ + fsm.state = ST_RECVPKT_SEND_ACK; + } + break; + } + + case ST_RECVPKT_SEND_ACK: + { + /* Send ACK packet (len=3, counter, 0x09) then read next packet */ + uint8_t ack_payload = (uint8_t)PACKET_CMD_ACK; + enter_send_packet(&ack_payload, 1, AFTER_SEND_IDLE); + + /* After sending the ACK packet we continue receiving in the same burst */ + fsm.after_send = AFTER_SEND_IDLE; /* send sub-machine won't auto-recv */ + + /* Prepare for next packet in burst */ + memset(&cur_pkt, 0, sizeof(cur_pkt)); + cur_pkt_body_idx = 0; + + /* Override the send sub-machine's post-send state to loop back to recv */ + /* We do this by saving after_recv and re-entering after send completes */ + /* Simplest: finish the send inline here before entering recv again. */ + /* Actually: enter_send_packet already set state = ST_SENDPKT_BYTE, */ + /* we need it to go to ST_RECVPKT_LENGTH after. We tag after_send. */ + switch (fsm.after_recv) { + case AFTER_RECV_ECUINFO: + fsm.after_send = AFTER_SEND_RECV_ECUINFO; break; + case AFTER_RECV_IDENT: + fsm.after_send = AFTER_SEND_RECV_IDENT; break; + case AFTER_RECV_KA: + fsm.after_send = AFTER_SEND_RECV_KA; break; + case AFTER_RECV_FAULTCODES: + fsm.after_send = AFTER_SEND_RECV_FAULTCODES; break; + } + break; + } + + /* ====================================================== */ + /* IdentifyEcu sub-sequence */ + /* ====================================================== */ + + case ST_IDENT_KEEPALIVE_SEND: + { + /* Send ACK packet as keep-alive / handshake before ReadIdent */ + uint8_t ack = (uint8_t)PACKET_CMD_ACK; + enter_send_packet(&ack, 1, AFTER_SEND_RECV_IDENT); + /* After sending ACK we expect a packet burst back */ + /* The after_recv is set inside enter_recv_packets in AFTER_SEND_RECV_IDENT path */ + /* Override: we actually want AFTER_RECV_IDENT for the receive burst */ + fsm.after_send = AFTER_SEND_RECV_IDENT; + break; + } + + /* ====================================================== */ + /* Keep-alive steady state */ + /* ====================================================== */ + + case ST_KA_WAIT: + if (fsm.ka_suspended) return; /* operations running — don't send KA */ + if ((int32_t)(HAL_GetTick() - fsm.ka_due) < 0) return; + /* Time to send a keep-alive */ + { + uint8_t ack = (uint8_t)PACKET_CMD_ACK; + enter_send_packet(&ack, 1, AFTER_SEND_RECV_KA); + } + break; + + /* ====================================================== */ + /* Error */ + /* ====================================================== */ + + case ST_ERROR: + fsm.status = KLINE_STATUS_ERROR; + KLine_OnDisconnected(); + /* Disarm UART and flush ring */ + HAL_UART_AbortReceive(&huart1); + KLine_Ring_Flush(); + fsm.state = ST_IDLE; + break; + + default: + break; + } +} + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +void KLine_FSM_Init(void) +{ + memset(&fsm, 0, sizeof(fsm)); + fsm.state = ST_IDLE; + fsm.status = KLINE_STATUS_IDLE; +} + +void KLine_FSM_RequestConnect(uint8_t ecuAddress) +{ + if (fsm.state != ST_IDLE) return; + memset(&fsm, 0, sizeof(fsm)); + fsm.bb_addr = ecuAddress; + fsm.status = KLINE_STATUS_CONNECTING; + fsm.state = ST_BITBANG_START; +} + +KLine_Status KLine_FSM_GetStatus(void) +{ + return fsm.status; +} + +uint8_t KLine_FSM_IsConnected(void) +{ + return fsm.status == KLINE_STATUS_CONNECTED; +} + +const ControllerInfo* KLine_FSM_GetEcuInfo(void) +{ + return &fsm.ecu_info; +} + +const KLine_FaultCode* KLine_FSM_GetFaultCodes(int *out_count) +{ + if (out_count) *out_count = fsm.fault_count; + return fsm.fault_codes; +} + +/* ------------------------------------------------------------------ */ +/* Operations support API */ +/* ------------------------------------------------------------------ */ + +uint8_t KLine_FSM_GetNextCounter(void) +{ + return pkt_counter_val++; +} + +void KLine_FSM_SuspendKA(uint8_t suspend) +{ + fsm.ka_suspended = suspend; + if (!suspend) { + /* Resume: push deadline forward so we don't immediately timeout */ + fsm.ka_due = HAL_GetTick() + KWP_KEEPALIVE_PERIOD_MS; + } +} + +void KLine_FSM_ResetKATimer(void) +{ + fsm.ka_due = HAL_GetTick() + KWP_KEEPALIVE_PERIOD_MS; +} + +/* ------------------------------------------------------------------ */ +/* Weak default callbacks — override in main.c */ +/* ------------------------------------------------------------------ */ + +__attribute__((weak)) void KLine_OnConnected(void) {} +__attribute__((weak)) void KLine_OnDisconnected(void) {} +__attribute__((weak)) void KLine_OnKeepAliveTick(void){} diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_fsm.h b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_fsm.h new file mode 100644 index 0000000..bf48a64 --- /dev/null +++ b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_fsm.h @@ -0,0 +1,162 @@ +/* + * kline_fsm.h + * Non-blocking KWP1281 protocol state machine for STM32H533. + * + * Design rules: + * - KLine_FSM_Run() is called every main-loop iteration. It never blocks. + * - Timing is always "store a deadline, check it next tick". No HAL_Delay. + * - UART1 RX bytes are consumed from the ring buffer (kline_ring.h). + * - UART1 TX is synchronous (HAL_UART_Transmit with short timeout) because + * TX completes in microseconds at 9600 baud and does not race with anything. + */ + +#ifndef INC_KLINE_FSM_H_ +#define INC_KLINE_FSM_H_ + +#include +#include "IKW1281Connection.h" /* ParsedPacket, PacketCommand, ControllerInfo */ + +/* ------------------------------------------------------------------ */ +/* Public result / status types */ +/* ------------------------------------------------------------------ */ + +typedef enum { + KLINE_STATUS_IDLE = 0, + KLINE_STATUS_CONNECTING, + KLINE_STATUS_CONNECTED, + KLINE_STATUS_ERROR, +} KLine_Status; + +/* ------------------------------------------------------------------ */ +/* Internal FSM states (exposed in header for debuggability) */ +/* ------------------------------------------------------------------ */ + +typedef enum { + + /* --- Idle --- */ + ST_IDLE, + + /* --- 5-baud init sequence --- */ + ST_BITBANG_START, /* De-init UART, configure TX pin as GPIO */ + ST_BITBANG_STARTBIT, /* Drive line low (start bit) */ + ST_BITBANG_DATABIT, /* Clock out 8 data bits one by one */ + ST_BITBANG_STOPBIT, /* Drive line high (stop bit) */ + ST_BITBANG_DONE, /* Re-init UART, arm RX ring */ + + /* --- Sync byte from ECU (0x55) --- */ + ST_WAIT_SYNC, + + /* --- Keywords --- */ + ST_READ_KW1, + ST_READ_KW2, + ST_SEND_KW2_COMPLEMENT, /* Write ~KW2 after inter-byte delay */ + + /* --- ECU info packets (initial ReceivePackets burst) --- */ + ST_RECV_ECUINFO_PACKET, + ST_SEND_ECUINFO_ACK, + + /* --- IdentifyEcu sub-sequence --- */ + ST_IDENT_KEEPALIVE_SEND, + ST_IDENT_KEEPALIVE_RECV, + ST_IDENT_RECV_PACKET, + ST_IDENT_SEND_ACK, + + /* --- Steady-state keep-alive --- */ + ST_KA_SEND_ACK_PACKET, /* Send ACK packet to ECU */ + ST_KA_RECV_ACK, /* Expect ACK packet back */ + ST_KA_WAIT, /* Idle until next KA is due */ + + /* --- Generic packet TX (SendPacket) --- */ + ST_SENDPKT_BYTE, /* Send one byte of the outgoing packet */ + ST_SENDPKT_WAIT_ACK, /* Wait for complement-ACK from ECU */ + ST_SENDPKT_NEXT_BYTE, /* Inter-byte delay then next byte */ + ST_SENDPKT_END, /* Send 0x03 end byte, no ACK */ + + /* --- Generic packet RX (ReceivePackets) --- */ + ST_RECVPKT_LENGTH, + ST_RECVPKT_COUNTER, + ST_RECVPKT_COMMAND, + ST_RECVPKT_BODY, + ST_RECVPKT_END, + ST_RECVPKT_SEND_ACK, /* Send ACK packet after non-ACK/NAK packet */ + + /* --- Error / disconnected --- */ + ST_ERROR, + +} KLine_FSMState; + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +/* Call after all HAL inits, before the while(1) loop */ +void KLine_FSM_Init(void); + +/* Call every main-loop iteration – returns immediately */ +void KLine_FSM_Run(void); + +/* Trigger a connection attempt (replaces BitBang = 1 / TryConnection()) */ +void KLine_FSM_RequestConnect(uint8_t ecuAddress); + +/* Query current status */ +KLine_Status KLine_FSM_GetStatus(void); + +/* True while a keep-alive session is active */ +uint8_t KLine_FSM_IsConnected(void); + +/* Access last-received ECU info */ +const ControllerInfo* KLine_FSM_GetEcuInfo(void); + +/* Access last-received fault codes */ +typedef struct { + uint8_t dtc; + uint8_t status; + uint8_t extra; +} KLine_FaultCode; + +#define KLINE_MAX_FAULT_CODES 20 +const KLine_FaultCode* KLine_FSM_GetFaultCodes(int *out_count); + +/* ------------------------------------------------------------------ */ +/* Operations support API */ +/* ------------------------------------------------------------------ */ + +/* + * Return the current packet counter and increment it. + * Used by kline_operations.c so that operation packets share the same + * session counter the FSM uses. Must only be called from main-loop context. + */ +uint8_t KLine_FSM_GetNextCounter(void); + +/* + * Suspend / resume keep-alive ticks. + * When suspend != 0, the FSM will NOT send keep-alive packets while in + * ST_KA_WAIT. The caller must resume (suspend=0) when the operation + * finishes so that keep-alive resumes normally. + * + * This also resets the KA timer so the next keep-alive fires a full + * period after the operation ends, preventing an immediate timeout. + */ +void KLine_FSM_SuspendKA(uint8_t suspend); + +/* + * Manually reset the keep-alive deadline to "now + period". + * Called by operation code after finishing so the ECU doesn't time out. + */ +void KLine_FSM_ResetKATimer(void); + +/* ------------------------------------------------------------------ */ +/* Low-level TX helper (shared with kline_operations.c) */ +/* ------------------------------------------------------------------ */ + +void KLine_FSM_TxByte(uint8_t b); + +/* ------------------------------------------------------------------ */ +/* Callbacks – implement in main.c / hc_06 glue */ +/* ------------------------------------------------------------------ */ + +void KLine_OnConnected(void); /* called when session established */ +void KLine_OnDisconnected(void); /* called on timeout / error */ +void KLine_OnKeepAliveTick(void); /* called each successful keep-alive */ + +#endif /* INC_KLINE_FSM_H_ */ diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_operations.c b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_operations.c new file mode 100644 index 0000000..1e61b90 --- /dev/null +++ b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_operations.c @@ -0,0 +1,925 @@ +/* + * kline_operations.c + * + * Non-blocking KWP1281 operations for the FSM architecture. + * + * Each operation (ReadDfi, WriteDfi, ReadVoltage, ReadAudiPin, + * ClearFaultCodes) is implemented as a multi-step state machine driven + * by KLineOp_Poll(), which is called every main-loop iteration. + * + * Design invariants: + * - No HAL_Delay anywhere. + * - All timing is deadline-based: deadline = HAL_GetTick() + N. + * - RX via KLine_Ring_Available() / KLine_Ring_Pop(). + * - TX via KLine_FSM_TxByte() (synchronous, safe at 9600 baud). + * - Packet counter via KLine_FSM_GetNextCounter() (shared session). + * - Keep-alive suspended while an op runs (KLine_FSM_SuspendKA). + */ + +#include "kline_operations.h" +#include "kline_fsm.h" +#include "kline_ring.h" +#include "IKW1281Connection.h" +#include "main.h" + +#include +#include + +/* ------------------------------------------------------------------ */ +/* Timing constants */ +/* ------------------------------------------------------------------ */ + +#define OP_BYTE_TIMEOUT_MS 1000u +#define OP_INTERBYTE_TX_MS 5u + +/* ------------------------------------------------------------------ */ +/* Internal operation types */ +/* ------------------------------------------------------------------ */ + +typedef enum { + OPTYPE_NONE = 0, + OPTYPE_READ_DFI, + OPTYPE_WRITE_DFI, + OPTYPE_READ_VOLTAGE, + OPTYPE_READ_AUDI_PIN, + OPTYPE_CLEAR_FAULT_CODES, +} OpType; + +/* ------------------------------------------------------------------ */ +/* Sub-step states for the generic send/receive packet engine */ +/* ------------------------------------------------------------------ */ + +typedef enum { + /* Idle / done */ + OPS_IDLE, + + /* Send packet bytes one at a time, wait for complement ACK */ + OPS_SEND_BYTE, + OPS_SEND_WAIT_ACK, + OPS_SEND_INTERBYTE_DELAY, + OPS_SEND_END_BYTE, + + /* Receive a packet byte by byte */ + OPS_RECV_LENGTH, + OPS_RECV_COUNTER, + OPS_RECV_COMMAND, + OPS_RECV_BODY, + OPS_RECV_END, + + /* After receiving a non-ACK/NAK packet, send ACK then receive more */ + OPS_RECV_SEND_ACK, + + /* High-level sequencer decides what comes next */ + OPS_STEP_DONE, + +} OpSubState; + +/* ------------------------------------------------------------------ */ +/* Operation context */ +/* ------------------------------------------------------------------ */ + +#define OP_MAX_SEND 24 +#define OP_MAX_RECV_RAW 20 +#define OP_MAX_RECV_PKTS 8 + +typedef struct { + /* What operation */ + OpType type; + KLineOp_Status result; + + /* Sub-state */ + OpSubState sub; + uint32_t deadline; + + /* Send buffer */ + uint8_t send_buf[OP_MAX_SEND]; + uint8_t send_len; + uint8_t send_idx; + uint8_t last_sent; + + /* Receive scratch */ + ParsedPacket cur_pkt; + uint8_t cur_body_idx; + + /* Completed receive packets for current burst */ + ParsedPacket rx_pkts[OP_MAX_RECV_PKTS]; + int rx_pkt_count; + + /* High-level step index (each op defines its own sequence) */ + int step; + + /* Operation-specific params/results */ + float dfi_value; + int dfi_version; + uint16_t u16_result; /* voltage or audi pin */ + int int_result; /* WriteDfi / ClearFC success */ + +} OpCtx; + +static OpCtx op; + +/* ------------------------------------------------------------------ */ +/* Packet classification (same logic as kline_fsm.c) */ +/* ------------------------------------------------------------------ */ + +static void op_classify_packet(ParsedPacket *p) +{ + p->title = p->raw[2]; + switch (p->raw[2]) { + case PACKET_CMD_ACK: + p->type = PACKET_TYPE_ACK; + p->isAckNak = 1; + break; + case PACKET_CMD_NAK: + p->type = PACKET_TYPE_NAK; + p->isAckNak = 1; + break; + case PACKET_CMD_AsciiData: + p->type = (p->raw[3] == 0x00) ? PACKET_TYPE_CODING_WSC + : PACKET_TYPE_ASCII_DATA; + break; + case PACKET_CMD_ReadEepromResponse: + p->type = PACKET_TYPE_READ_EEPROM_RESPONSE; + break; + case PACKET_CMD_ReadRomEepromResponse: + p->type = PACKET_TYPE_READ_ROM_EEPROM_RESPONSE; + break; + default: + p->type = PACKET_TYPE_UNKNOWN; + break; + } +} + +/* ------------------------------------------------------------------ */ +/* Build a KWP1281 packet into op.send_buf */ +/* ------------------------------------------------------------------ */ + +static void op_build_packet(const uint8_t *payload, uint8_t payload_len) +{ + op.send_len = payload_len + 2; /* length + counter + payload */ + op.send_buf[0] = op.send_len; + op.send_buf[1] = KLine_FSM_GetNextCounter(); + for (uint8_t i = 0; i < payload_len; i++) + op.send_buf[2 + i] = payload[i]; + op.send_idx = 0; +} + +/* ------------------------------------------------------------------ */ +/* Enter send / receive sub-states */ +/* ------------------------------------------------------------------ */ + +static void op_enter_send(const uint8_t *payload, uint8_t len) +{ + op_build_packet(payload, len); + op.sub = OPS_SEND_BYTE; + op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS; +} + +static void op_enter_recv(void) +{ + op.rx_pkt_count = 0; + memset(&op.cur_pkt, 0, sizeof(op.cur_pkt)); + op.cur_body_idx = 0; + op.sub = OPS_RECV_LENGTH; + op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS; +} + +/* ------------------------------------------------------------------ */ +/* Byte-level RX helpers */ +/* ------------------------------------------------------------------ */ + +/* Read a byte and send complement ACK. Returns 1 if got byte. */ +static uint8_t op_try_read_ack(uint8_t *out) +{ + if (!KLine_Ring_Available()) { + if ((int32_t)(HAL_GetTick() - op.deadline) >= 0) { + op.result = KLOP_FAIL; + op.sub = OPS_IDLE; + } + return 0; + } + *out = KLine_Ring_Pop(); + KLine_FSM_TxByte((uint8_t)~(*out)); + op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS; + return 1; +} + +/* Read a byte with no ACK (for end byte). */ +static uint8_t op_try_read_raw(uint8_t *out) +{ + if (!KLine_Ring_Available()) { + if ((int32_t)(HAL_GetTick() - op.deadline) >= 0) { + op.result = KLOP_FAIL; + op.sub = OPS_IDLE; + } + return 0; + } + *out = KLine_Ring_Pop(); + op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS; + return 1; +} + +/* ------------------------------------------------------------------ */ +/* Send sub-machine */ +/* ------------------------------------------------------------------ */ + +static void op_run_send(void) +{ + switch (op.sub) + { + case OPS_SEND_BYTE: + if (op.send_idx >= op.send_len) { + /* All data bytes sent — send end byte (no ACK expected) */ + KLine_FSM_TxByte(PACKET_END_EXPECTED); + /* Packet fully transmitted */ + op.sub = OPS_STEP_DONE; + return; + } + op.last_sent = op.send_buf[op.send_idx++]; + KLine_FSM_TxByte(op.last_sent); + op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS; + op.sub = OPS_SEND_WAIT_ACK; + break; + + case OPS_SEND_WAIT_ACK: + if (!KLine_Ring_Available()) { + if ((int32_t)(HAL_GetTick() - op.deadline) >= 0) { + op.result = KLOP_FAIL; + op.sub = OPS_IDLE; + } + return; + } + (void)KLine_Ring_Pop(); /* consume complement ACK */ + op.deadline = HAL_GetTick() + OP_INTERBYTE_TX_MS; + op.sub = OPS_SEND_INTERBYTE_DELAY; + break; + + case OPS_SEND_INTERBYTE_DELAY: + if ((int32_t)(HAL_GetTick() - op.deadline) < 0) return; + op.sub = OPS_SEND_BYTE; + break; + + default: + break; + } +} + +/* ------------------------------------------------------------------ */ +/* Receive sub-machine */ +/* ------------------------------------------------------------------ */ + +static void op_run_recv(void) +{ + switch (op.sub) + { + case OPS_RECV_LENGTH: + { + uint8_t b; + if (!op_try_read_ack(&b)) return; + memset(&op.cur_pkt, 0, sizeof(op.cur_pkt)); + op.cur_body_idx = 0; + op.cur_pkt.raw[0] = b; + op.sub = OPS_RECV_COUNTER; + break; + } + + case OPS_RECV_COUNTER: + { + uint8_t b; + if (!op_try_read_ack(&b)) return; + op.cur_pkt.raw[1] = b; + op.sub = OPS_RECV_COMMAND; + break; + } + + case OPS_RECV_COMMAND: + { + uint8_t b; + if (!op_try_read_ack(&b)) return; + op.cur_pkt.raw[2] = b; + op.cur_body_idx = 3; + if (op.cur_pkt.raw[0] <= 3) { + op.sub = OPS_RECV_END; + } else { + op.sub = OPS_RECV_BODY; + } + break; + } + + case OPS_RECV_BODY: + { + uint8_t b; + if (!op_try_read_ack(&b)) return; + if (op.cur_body_idx < OP_MAX_RECV_RAW) + op.cur_pkt.raw[op.cur_body_idx] = b; + op.cur_body_idx++; + if (op.cur_body_idx >= op.cur_pkt.raw[0]) { + op.sub = OPS_RECV_END; + } + break; + } + + case OPS_RECV_END: + { + uint8_t b; + if (!op_try_read_raw(&b)) return; + op.cur_pkt.raw[op.cur_body_idx] = b; + op.cur_pkt.length = op.cur_body_idx + 1; + op_classify_packet(&op.cur_pkt); + + if (op.rx_pkt_count < OP_MAX_RECV_PKTS) + op.rx_pkts[op.rx_pkt_count++] = op.cur_pkt; + + if (op.cur_pkt.isAckNak) { + /* Burst complete */ + op.sub = OPS_STEP_DONE; + } else { + /* More packets — need to send ACK then continue receiving */ + op.sub = OPS_RECV_SEND_ACK; + } + break; + } + + case OPS_RECV_SEND_ACK: + { + /* Build and send an ACK packet, then loop back to recv */ + uint8_t ack_payload = (uint8_t)PACKET_CMD_ACK; + op_build_packet(&ack_payload, 1); + /* We'll send this packet then re-enter recv */ + op.sub = OPS_SEND_BYTE; + /* Mark that after this send completes, we go back to receiving. + * We use a special trick: after send completes (OPS_STEP_DONE), + * the sequencer checks if we were mid-burst and loops back. */ + break; + } + + default: + break; + } +} + +/* ------------------------------------------------------------------ */ +/* Helper: send a packet then receive the response burst */ +/* Returns 1 when the full send+receive is done, 0 while busy. */ +/* The sequencer uses op.step to track phases: */ +/* step N → sending packet */ +/* step N+1 → receiving response */ +/* ------------------------------------------------------------------ */ + +/* Send-then-receive sequencer states per step: + * phase 0 = start send + * phase 1 = finish send → start recv + * phase 2 = finish recv → done + */ +typedef enum { + PHASE_SEND_START, + PHASE_SENDING, + PHASE_RECV_START, + PHASE_RECEIVING, + PHASE_COMPLETE, +} SendRecvPhase; + +static SendRecvPhase sr_phase; +static uint8_t sr_was_mid_burst; /* flag: we were sending an ACK mid-burst */ + +static void sr_begin_send(const uint8_t *payload, uint8_t len) +{ + op_enter_send(payload, len); + sr_phase = PHASE_SENDING; + sr_was_mid_burst = 0; +} + +static void sr_begin_send_then_recv(const uint8_t *payload, uint8_t len) +{ + op_enter_send(payload, len); + sr_phase = PHASE_SENDING; + sr_was_mid_burst = 0; +} + +/* Returns 1 when the send-then-receive cycle is complete */ +static uint8_t sr_poll(void) +{ + if (op.result == KLOP_FAIL) return 1; /* error abort */ + + switch (sr_phase) + { + case PHASE_SENDING: + if (op.sub == OPS_STEP_DONE) { + if (sr_was_mid_burst) { + /* We just finished sending an intra-burst ACK — go back to recv */ + sr_was_mid_burst = 0; + op_enter_recv(); + sr_phase = PHASE_RECEIVING; + /* But we keep the existing rx_pkt list intact — don't reset count */ + /* Actually op_enter_recv resets count. We need a variant. */ + /* Fix: manually set sub to recv length without resetting pkt list */ + /* We'll inline it: */ + memset(&op.cur_pkt, 0, sizeof(op.cur_pkt)); + op.cur_body_idx = 0; + op.sub = OPS_RECV_LENGTH; + op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS; + return 0; + } + /* Normal send complete — start receiving */ + op_enter_recv(); + sr_phase = PHASE_RECEIVING; + return 0; + } + /* Still sending */ + if (op.sub >= OPS_SEND_BYTE && op.sub <= OPS_SEND_INTERBYTE_DELAY) { + op_run_send(); + } + return 0; + + case PHASE_RECEIVING: + if (op.sub == OPS_STEP_DONE) { //AQUI ACABA DE ENVIAR EL MENSAJE + /* Burst complete */ + sr_phase = PHASE_COMPLETE; + return 1; + } + if (op.sub == OPS_RECV_SEND_ACK) { + /* Need to send ACK mid-burst, then continue recv */ + uint8_t ack_payload = (uint8_t)PACKET_CMD_ACK; + op_build_packet(&ack_payload, 1); + op.sub = OPS_SEND_BYTE; + op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS; + sr_was_mid_burst = 1; + sr_phase = PHASE_SENDING; + return 0; + } + if (op.sub >= OPS_RECV_LENGTH && op.sub <= OPS_RECV_END) { + op_run_recv(); + } + return 0; + + case PHASE_COMPLETE: + return 1; + + default: + return 1; + } +} + +/* ------------------------------------------------------------------ */ +/* Send-only sequencer (no receive expected, or we handle it inline) */ +/* ------------------------------------------------------------------ */ + +static void send_only_begin(const uint8_t *payload, uint8_t len) +{ + op_enter_send(payload, len); + sr_phase = PHASE_SENDING; + sr_was_mid_burst = 0; +} + +static uint8_t send_only_poll(void) +{ + if (op.result == KLOP_FAIL) return 1; + if (op.sub == OPS_STEP_DONE) return 1; + if (op.sub >= OPS_SEND_BYTE && op.sub <= OPS_SEND_INTERBYTE_DELAY) { + op_run_send(); + } + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Helper: find first non-ACK/NAK packet in rx list */ +/* ------------------------------------------------------------------ */ + +static ParsedPacket* op_find_data_packet(void) +{ + for (int i = 0; i < op.rx_pkt_count; i++) { + if (!op.rx_pkts[i].isAckNak) + return &op.rx_pkts[i]; + } + return (ParsedPacket*)0; +} + +/* ------------------------------------------------------------------ */ +/* Common init/finish for all operations */ +/* ------------------------------------------------------------------ */ + +static int op_begin(OpType t) +{ + if (op.type != OPTYPE_NONE) return -1; /* already busy */ + memset(&op, 0, sizeof(op)); + op.type = t; + op.result = KLOP_BUSY; + op.sub = OPS_IDLE; + op.step = 0; + sr_phase = PHASE_SEND_START; + KLine_FSM_SuspendKA(1); + return 0; +} + +static void op_finish(KLineOp_Status s) +{ + op.result = s; + op.type = OPTYPE_NONE; + op.sub = OPS_IDLE; + KLine_FSM_SuspendKA(0); + KLine_FSM_ResetKATimer(); +} + +/* ================================================================== */ +/* ReadRomEeprom: send {0x03, count, addr_hi, addr_lo} → recv */ +/* Extracts body bytes raw[3 .. length-2] */ +/* ================================================================== */ + +/* Shared result buffer for ROM EEPROM reads */ +static uint8_t rom_eeprom_data[16]; +static int rom_eeprom_len; + +/* ================================================================== */ +/* Operation: ReadVoltage */ +/* ReadRomEeprom(0x0142, 2) → (data[1]<<8)|data[0] */ +/* ================================================================== */ + +static void poll_read_voltage(void) +{ + switch (op.step) + { + case 0: + { + uint8_t req[4] = { + (uint8_t)PACKET_CMD_ReadRomEeprom, + 2, /* count */ + (uint8_t)(0x0142 >> 8), /* addr hi */ + (uint8_t)(0x0142 & 0xFF) /* addr lo */ + }; + sr_begin_send_then_recv(req, 4); + op.step = 1; + break; + } + case 1: + if (!sr_poll()) return; + if (op.result == KLOP_FAIL) {op_finish(KLOP_FAIL); return; } + { + /* Check for NAK */ + if (op.rx_pkt_count == 1 && op.rx_pkts[0].type == PACKET_TYPE_NAK) { + op_finish(KLOP_FAIL); + return; + } + ParsedPacket *p = op_find_data_packet(); + if (!p || p->length < 5) { + op_finish(KLOP_FAIL); + return; + } + int data_len = p->length - 4; + if (data_len >= 2) { + op.u16_result = (uint16_t)((p->raw[4] << 8) | p->raw[3]); + } else { + op_finish(KLOP_FAIL); + return; + } + } + op_finish(KLOP_OK); + break; + } +} + +/* ================================================================== */ +/* Operation: ReadAudiPin */ +/* ReadRomEeprom(0x04FA, 2) → (data[1]<<8)|data[0] */ +/* ================================================================== */ + +static void poll_read_audi_pin(void) +{ + switch (op.step) + { + case 0: + { + uint8_t req[4] = { + (uint8_t)PACKET_CMD_ReadRomEeprom, + 2, + (uint8_t)(0x04FA >> 8), + (uint8_t)(0x04FA & 0xFF) + }; + sr_begin_send_then_recv(req, 4); + op.step = 1; + break; + } + case 1: + if (!sr_poll()) return; + if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; } + { + if (op.rx_pkt_count == 1 && op.rx_pkts[0].type == PACKET_TYPE_NAK) { + op_finish(KLOP_FAIL); + return; + } + ParsedPacket *p = op_find_data_packet(); + if (!p || p->length < 5) { + op_finish(KLOP_FAIL); + return; + } + int data_len = p->length - 4; + if (data_len >= 2) { + op.u16_result = (uint16_t)((p->raw[4] << 8) | p->raw[3]); + } else { + op_finish(KLOP_FAIL); + return; + } + } + op_finish(KLOP_OK); + break; + } +} + +/* ================================================================== */ +/* Operation: ClearFaultCodes */ +/* Send {0x05}, expect ACK or NAK back */ +/* ================================================================== */ + +static void poll_clear_fault_codes(void) +{ + switch (op.step) + { + case 0: + { + uint8_t req[1] = { (uint8_t)PACKET_CMD_FaultCodesDelete }; + sr_begin_send_then_recv(req, 1); + op.step = 1; + break; + } + case 1: + if (!sr_poll()) return; + if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; } + { + if (op.rx_pkt_count >= 1 && op.rx_pkts[0].type == PACKET_TYPE_ACK) { + op.int_result = 1; + op_finish(KLOP_OK); + } else { + op.int_result = 0; + op_finish(KLOP_FAIL); + } + } + break; + } +} + +/* ================================================================== */ +/* Operation: ReadDfi */ +/* Step 0: send group reading unlock {0x18,0x00,0x03,0xFF,0xFF} */ +/* Step 1: recv response */ +/* Step 2: send ReadEeprom {0x19,0x02,0x00,0x44} */ +/* Step 3: recv response → extract DFI */ +/* ================================================================== */ + +static void poll_read_dfi(void) +{ + switch (op.step) + { + case 0: + { + /* Group reading unlock */ + uint8_t req[5] = { 0x18, 0x00, 0x03, 0xFF, 0xFF }; + sr_begin_send_then_recv(req, 5); + op.step = 1; + break; + } + case 1: + if (!sr_poll()) return; + if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; } + /* Don't care about the unlock response content — move on */ + op.step = 2; + break; + + case 2: + { + /* ReadEeprom at 0x0044 */ + uint8_t req[4] = { + (uint8_t)PACKET_CMD_ReadEeprom, + 0x02, 0x00, 0x44 + }; + sr_begin_send_then_recv(req, 4); + op.step = 3; + break; + } + case 3: + if (!sr_poll()) return; + if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; } + { + ParsedPacket *p = op_find_data_packet(); + if (!p || p->length < 5) { + op.dfi_value = 0.0f; + op_finish(KLOP_FAIL); + return; + } + /* DFI = (int8_t)raw[3] * 3.0 / 256.0 */ + int8_t raw_val = (int8_t)p->raw[3]; + op.dfi_value = ((float)raw_val * 3.0f) / 256.0f; + } + op_finish(KLOP_OK); + break; + } +} + +/* ================================================================== */ +/* Operation: WriteDfi */ +/* Step 0: send version-dependent password (14 bytes) */ +/* Step 1: recv response */ +/* Step 2: send WriteEeprom {0x1A,0x02,0x00,0x44,val,~val,0x03} */ +/* Step 3: recv response */ +/* ================================================================== */ + +static const uint8_t pass_default[14] = { + 0x18, 0x00, 0x03, 0x2F, 0xFF, + 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45 +}; +static const uint8_t pass_v2[14] = { + 0x18, 0x00, 0x03, 0x2F, 0xF2, + 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45 +}; +static const uint8_t pass_v3v4[14] = { + 0x18, 0x00, 0x03, 0xFF, 0xF2, + 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45 +}; + +static void poll_write_dfi(void) +{ + switch (op.step) + { + case 0: + { + /* Select password based on version */ + const uint8_t *password = pass_default; + if (op.dfi_version == 1) { + password = pass_v2; + } else if (op.dfi_version == 2 || op.dfi_version == 3) { + password = pass_v3v4; + } + sr_begin_send_then_recv(password, 14); + op.step = 1; + break; + } + case 1: + if (!sr_poll()) return; + if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; } + /* Password response received — move on to write */ + op.step = 2; + break; + + case 2: + { + /* Compute value byte */ + int vi = (int)((op.dfi_value * 256.0f) / 3.0f); + if (vi > 127) vi = 127; + if (vi < -128) vi = -128; + int8_t value = (int8_t)vi; + if (value == 0) value = 1; + + uint8_t value_u = (uint8_t)value; + uint8_t value_csum = (uint8_t)(0u - value_u); + + uint8_t req[7] = { + (uint8_t)PACKET_CMD_WriteEeprom, + 0x02, 0x00, 0x44, + value_u, value_csum, 0x03 + }; + sr_begin_send_then_recv(req, 7); + op.step = 3; + break; + } + case 3: + if (!sr_poll()) return; + if (op.result == KLOP_FAIL) { + op.int_result = 0; + op_finish(KLOP_FAIL); + return; + } + /* Check for NAK */ + if (op.rx_pkt_count >= 1 && op.rx_pkts[0].type == PACKET_TYPE_NAK) { + op.int_result = 0; + op_finish(KLOP_FAIL); + return; + } + op.int_result = 1; + op_finish(KLOP_OK); + break; + } +} + +/* ================================================================== */ +/* Public API: Start functions */ +/* ================================================================== */ + +int KLineOp_Start_ReadDfi(void) +{ + return op_begin(OPTYPE_READ_DFI); +} + +int KLineOp_Start_WriteDfi(float dfi, int version) +{ + if (op_begin(OPTYPE_WRITE_DFI) != 0) return -1; + op.dfi_value = dfi; + op.dfi_version = version; + return 0; +} + +int KLineOp_Start_ReadVoltage(void) +{ + return op_begin(OPTYPE_READ_VOLTAGE); +} + +int KLineOp_Start_ReadAudiPin(void) +{ + return op_begin(OPTYPE_READ_AUDI_PIN); +} + +int KLineOp_Start_ClearFaultCodes(void) +{ + return op_begin(OPTYPE_CLEAR_FAULT_CODES); +} + +/* ================================================================== */ +/* Public API: Poll */ +/* ================================================================== */ + +KLineOp_Status KLineOp_Poll(void) +{ + if (op.result != KLOP_BUSY) return op.result; + + switch (op.type) + { + case OPTYPE_READ_DFI: poll_read_dfi(); break; + case OPTYPE_WRITE_DFI: poll_write_dfi(); break; + case OPTYPE_READ_VOLTAGE: poll_read_voltage(); break; + case OPTYPE_READ_AUDI_PIN: poll_read_audi_pin(); break; + case OPTYPE_CLEAR_FAULT_CODES: poll_clear_fault_codes(); break; + default: + op.result = KLOP_IDLE; + break; + } + + return op.result; +} + +/* ================================================================== */ +/* Public API: Result accessors */ +/* ================================================================== */ + +float KLineOp_Result_Dfi(void) { return op.dfi_value; } +uint16_t KLineOp_Result_Voltage(void) { return op.u16_result; } +uint16_t KLineOp_Result_AudiPin(void) { return op.u16_result; } +int KLineOp_Result_WriteDfi(void) { return op.int_result; } +int KLineOp_Result_ClearFaultCodes(void) { return op.int_result; } + +/* ================================================================== */ +/* Legacy shim functions (called from hc_06.c) */ +/* */ +/* These are the OLD blocking API signatures. They now kick off the */ +/* async operation and BLOCK by polling in a tight loop. This is a */ +/* transitional shim so hc_06.c keeps working without changes. */ +/* */ +/* For a fully non-blocking architecture, hc_06.c should be updated */ +/* to use KLineOp_Start_XXX() + KLineOp_Poll() directly. */ +/* ================================================================== */ + +float ReadDfi(void) +{ + if (KLineOp_Start_ReadDfi() != 0) return 0.0f; + KLineOp_Status s; + do { s = KLineOp_Poll(); } while (s == KLOP_BUSY); + return (s == KLOP_OK) ? KLineOp_Result_Dfi() : 0.0f; +} + +int WriteDfi(float dfi, int version) +{ + if (KLineOp_Start_WriteDfi(dfi, version) != 0) return 0; + KLineOp_Status s; + do { s = KLineOp_Poll(); } while (s == KLOP_BUSY); + return (s == KLOP_OK) ? KLineOp_Result_WriteDfi() : 0; +} + +uint8_t ReadVoltage(uint16_t *volt) +{ + if (volt) *volt = 0; + if (KLineOp_Start_ReadVoltage() != 0) return 0; + KLineOp_Status s; + do { s = KLineOp_Poll(); } while (s == KLOP_BUSY); + if (s == KLOP_OK) { + if (volt) *volt = KLineOp_Result_Voltage(); + return 1; + } + return 0; +} + +uint8_t ReadAudiPin(uint16_t *pin) +{ + if (pin) *pin = 0; + if (KLineOp_Start_ReadAudiPin() != 0) return 0; + KLineOp_Status s; + do { s = KLineOp_Poll(); } while (s == KLOP_BUSY); + if (s == KLOP_OK) { + if (pin) *pin = KLineOp_Result_AudiPin(); + return 1; + } + return 0; +} + +int ClearFaultCodes(void) +{ + if (KLineOp_Start_ClearFaultCodes() != 0) return 0; + KLineOp_Status s; + do { s = KLineOp_Poll(); } while (s == KLOP_BUSY); + return (s == KLOP_OK) ? KLineOp_Result_ClearFaultCodes() : 0; +} diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_operations.h b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_operations.h new file mode 100644 index 0000000..2e0fd4a --- /dev/null +++ b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_operations.h @@ -0,0 +1,51 @@ +/* + * kline_operations.h + * + * Created on: 1 abr 2026 + * Author: herli + */ + +#ifndef KLINE_MASTER_LIBS_KLINE_OPERATIONS_H_ +#define KLINE_MASTER_LIBS_KLINE_OPERATIONS_H_ + +#include + +/* ------------------------------------------------------------------ */ +/* Operation result status */ +/* ------------------------------------------------------------------ */ + +typedef enum { + KLOP_IDLE = 0, /* no operation pending */ + KLOP_BUSY = 1, /* operation in progress — keep polling */ + KLOP_OK = 2, /* completed successfully */ + KLOP_FAIL = 3, /* completed with failure (NAK, timeout, etc.) */ +} KLineOp_Status; + +/* ------------------------------------------------------------------ */ +/* Start functions — return 0 on success, -1 if already busy */ +/* ------------------------------------------------------------------ */ + +int KLineOp_Start_ReadDfi(void); +int KLineOp_Start_WriteDfi(float dfi, int version); +int KLineOp_Start_ReadVoltage(void); +int KLineOp_Start_ReadAudiPin(void); +int KLineOp_Start_ClearFaultCodes(void); + +/* ------------------------------------------------------------------ */ +/* Poll — call every main-loop iteration */ +/* ------------------------------------------------------------------ */ + +KLineOp_Status KLineOp_Poll(void); + +/* ------------------------------------------------------------------ */ +/* Result accessors (valid only after KLineOp_Poll() returns KLOP_OK) */ +/* ------------------------------------------------------------------ */ + +float KLineOp_Result_Dfi(void); +uint16_t KLineOp_Result_Voltage(void); +uint16_t KLineOp_Result_AudiPin(void); +int KLineOp_Result_WriteDfi(void); /* 1=success, 0=fail */ +int KLineOp_Result_ClearFaultCodes(void); /* 1=success, 0=fail */ + + +#endif /* KLINE_MASTER_LIBS_KLINE_OPERATIONS_H_ */ diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_ring.c b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_ring.c new file mode 100644 index 0000000..60f9bca --- /dev/null +++ b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_ring.c @@ -0,0 +1,56 @@ +/* + * kline_ring.c + * ISR-safe ring buffer for K-Line UART1 RX. + * + * Producer: HAL_UART_RxCpltCallback (ISR context) via KLine_Ring_ISR_PushByte() + * Consumer: KLine_FSM_Run() (main-loop context) + * + * No locking needed: head written only by ISR, tail written only by main. + */ + +#include "kline_ring.h" +#include "main.h" /* for huart1 */ + +volatile uint8_t kline_rx_staging = 0; + +static volatile uint8_t ring_buf[KLINE_RING_SIZE]; +static volatile uint32_t ring_head = 0; /* written by ISR */ +static volatile uint32_t ring_tail = 0; /* written by main */ + +void KLine_Ring_Init(void) +{ + ring_head = 0; + ring_tail = 0; + HAL_UART_Receive_IT(&huart1, (uint8_t*)&kline_rx_staging, 1); +} + +/* ---- ISR context ---- */ +void KLine_Ring_ISR_PushByte(uint8_t b) +{ + uint32_t next = (ring_head + 1u) & KLINE_RING_MASK; + if (next != ring_tail) /* drop byte on overflow rather than corrupt */ + { + ring_buf[ring_head] = b; + ring_head = next; + } + /* Re-arm immediately – this is the ONLY place we call Receive_IT for UART1 */ + HAL_UART_Receive_IT(&huart1, (uint8_t*)&kline_rx_staging, 1); +} + +/* ---- Main-loop context ---- */ +uint8_t KLine_Ring_Available(void) +{ + return ring_head != ring_tail; +} + +uint8_t KLine_Ring_Pop(void) +{ + uint8_t b = ring_buf[ring_tail]; + ring_tail = (ring_tail + 1u) & KLINE_RING_MASK; + return b; +} + +void KLine_Ring_Flush(void) +{ + ring_tail = ring_head; +} diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_ring.h b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_ring.h new file mode 100644 index 0000000..605a261 --- /dev/null +++ b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Kline_Master_Libs/kline_ring.h @@ -0,0 +1,29 @@ +/* + * kline_ring.h + * Lock-free single-producer (ISR) / single-consumer (FSM) ring buffer + * for UART1 K-Line RX bytes. + */ + +#ifndef INC_KLINE_RING_H_ +#define INC_KLINE_RING_H_ + +#include + +#define KLINE_RING_SIZE 64u /* Must be power of 2 */ +#define KLINE_RING_MASK (KLINE_RING_SIZE - 1u) + +/* Call once after HAL_UART_Init to start receiving */ +void KLine_Ring_Init(void); + +/* Called ONLY from UART1 RxCplt ISR callback */ +void KLine_Ring_ISR_PushByte(uint8_t b); + +/* Called from FSM / main context */ +uint8_t KLine_Ring_Available(void); /* non-zero if at least one byte ready */ +uint8_t KLine_Ring_Pop(void); /* pop oldest byte; undefined if empty */ +void KLine_Ring_Flush(void); /* discard all pending bytes */ + +/* Raw RX staging byte – huart1 DMA/IT target, touched only by HAL + ISR */ +extern volatile uint8_t kline_rx_staging; + +#endif /* INC_KLINE_RING_H_ */ diff --git a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Src/main.c b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Src/main.c index 266a30f..b63ded7 100644 --- a/bpdt-firmware/bpdt-adapter-stm32h5/Core/Src/main.c +++ b/bpdt-firmware/bpdt-adapter-stm32h5/Core/Src/main.c @@ -21,8 +21,8 @@ /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ -#include "kline.h" -#include "IKW1281Connection.h" +#include "kline_ring.h" +#include "kline_fsm.h" #include "hc_06.h" /* USER CODE END Includes */ @@ -49,7 +49,12 @@ UART_HandleTypeDef huart1; UART_HandleTypeDef huart3; /* USER CODE BEGIN PV */ - +/* + * kline_rx_staging is the 1-byte HAL IT target for UART1. + * It lives here so the linker puts it in normal SRAM (not any cached/MPU region). + * It is only written by HAL inside the ISR and read by KLine_Ring_ISR_PushByte. + */ +/* defined in kline_ring.c: volatile uint8_t kline_rx_staging; */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ @@ -57,72 +62,67 @@ void SystemClock_Config(void); static void MPU_Config(void); static void MX_GPIO_Init(void); static void MX_FDCAN1_Init(void); -static void MX_USART1_UART_Init(void); +void MX_USART1_UART_Init(void); static void MX_USART3_UART_Init(void); /* USER CODE BEGIN PFP */ - +//void MX_USART1_UART_Init(void); +//SIEMPRE QUITAR EL STATIC /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ -volatile uint8_t k_rx_byte; -volatile uint8_t rx_done_flag = 0; -ControllerInfo ecuinfo = {0}; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { - if (huart->Instance == huart3.Instance) + if (huart->Instance == USART3) { - HC06_UART_RxByteCallback(huart); + HC06_UART_RxByteCallback(huart); + return; } if (huart->Instance == USART1) { - rx_done_flag = 1; - // Do NOT re-arm here. ReadByte() does it. + /* + * Push received byte into the ring buffer. + * KLine_Ring_ISR_PushByte() re-arms Receive_IT internally. + * DO NOT call HAL_UART_Receive_IT here directly. + */ + KLine_Ring_ISR_PushByte(kline_rx_staging); } - } + void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { HC06_UART_TxCpltCallback(huart); } -uint8_t pc_connected = 0; -uint8_t sent_init_message = 0; -uint8_t psg_connected = 0; +/* ---- KLine FSM callbacks (implement BT integration here) ---- */ -void OnEnterReceived(void) { - //char msg[] = "Enter pressed!\r\n"; - //CDC_Transmit_FS((uint8_t*)msg, strlen(msg)); - BitBang = 1; +void KLine_OnConnected(void) +{ + BT_OnPSG5CommEstablished(); } -uint32_t alive_due_ms =0; - -void TryConnection(void){ - BitBang = 0; - - int protocolVersion = WakeUp(ECU_INIT_ADDRESS, 0); - - if (!protocolVersion || !connectionAlive) return; // add connectionAlive check - - ecuinfo = ReadEcuInfo(); - - if(!KeepAlive()){ - KLINE_THROW_NONALIVE_EXCEPTION(4); - return; - } - IdentifyEcu(); - - /*FaultCode fault_codes[20]; // buffer for up to 32 codes (tune size as needed) - int N_fc = ReadFaultCodes(fault_codes, 20); - PrintFaultCodes(fault_codes, N_fc);*/ - //free(fault_code) - psg_connected = 1; - alive_due_ms = HAL_GetTick() + 500; - - BT_OnPSG5CommEstablished(); +void KLine_OnDisconnected(void) +{ + BT_KLINE_ERROR(); } + +void KLine_OnKeepAliveTick(void) +{ + BT_SendCommStatus(); +} + +/* ---- Button / trigger ---- */ + +void OnEnterReceived(void) +{ + /* + * User pressed button — request a KWP1281 connection. + * The FSM will handle everything from here, non-blocking. + */ + KLine_FSM_RequestConnect(ECU_INIT_ADDRESS); +} +int BitBang = 0; /* USER CODE END 0 */ /** @@ -161,12 +161,17 @@ int main(void) MX_USART1_UART_Init(); MX_USART3_UART_Init(); /* USER CODE BEGIN 2 */ - //HAL_UART_Receive_IT(&huart1, &k_rx_byte, 1); - HAL_Delay(25); // Wait before starting UART (standard KWP wait time) - //int protocolVersion = WakeUp(ECU_INIT_ADDRESS, 0); + + KLine_FSM_Init(); + /* Note: KLine_Ring_Init() is called inside the FSM after the 5-baud + bitbang sequence restores UART1, so we do NOT call it here. */ + + HAL_Delay(25); /* Standard KWP power-on wait — only HAL_Delay in the project */ HC06_Init(); - HC06_AT_BootSetup(); // sets NAME, then switches to BIN mode + HC06_AT_BootSetup(); + + /* USER CODE END 2 */ /* Infinite loop */ @@ -176,26 +181,12 @@ int main(void) /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ - - HC06_Process(); - if(BitBang){ - //HAL_GPIO_WritePin(KLINE_GPIO_PORT, GPIO_PIN_8, GPIO_PIN_RESET); - TryConnection(); - //AQUI HARIA FALTA IMPLEMENTAR UN TIMER QUE CADA x ms haga un keep alive - //EndCommunication(); - } - if(psg_connected){ - if ((int32_t)(HAL_GetTick() - alive_due_ms) >= 0){ - if(!KeepAlive()){ - KLINE_THROW_NONALIVE_EXCEPTION(4); - psg_connected = 0; - //return; - } - BT_SendCommStatus(); - alive_due_ms = HAL_GetTick() + 500; - } - - } +if(BitBang){ + KLine_FSM_RequestConnect(ECU_INIT_ADDRESS); + BitBang = 0; +} + KLine_FSM_Run(); + HC06_Process(); } /* USER CODE END 3 */ } @@ -304,11 +295,11 @@ static void MX_FDCAN1_Init(void) * @param None * @retval None */ -static void MX_USART1_UART_Init(void) +void MX_USART1_UART_Init(void) { /* USER CODE BEGIN USART1_Init 0 */ - +//siempre quitar el static /* USER CODE END USART1_Init 0 */ /* USER CODE BEGIN USART1_Init 1 */