/* * kline.c * * Created on: Aug 18, 2025 * Author: herli */ #include "kline.h" #include "IKW1281Connection.h" #include "ee_manager.h" #include "stdint.h" #include "main.h" #include #include // for roundf static int ReceivePacket_Tmo(ParsedPacket *out, uint32_t overall_ms); static void KLine_HandleWriteEeprom(const uint8_t *body, uint8_t body_len); static void KLine_HandleReadRomEeprom(const uint8_t *body, uint8_t body_len); static void KLine_HandleReadIdentAddress(const uint8_t *body, uint8_t body_len); static void KLine_HandleReadEeprom(const uint8_t *body, uint8_t body_len); static void KLine_HandlePassword(const uint8_t *body, uint8_t body_len); static void KLine_HandleEnd(void); static void KLine_HandleCustomPacket(const uint8_t *body, uint8_t body_len); static void KLine_TerminateAndRearm(void); extern float dFi; int BitBang = 0; uint8_t K_RxData[1]; volatile uint8_t kline_connection_status = 0; // 0 = idle, 1 = busy, 2 = hold // --- config you can change --- extern UART_HandleTypeDef huart1; extern TIM_HandleTypeDef htim17; typedef enum { FIVE_IDLE = 0, FIVE_WAIT_FIRST_SAMPLE, FIVE_SAMPLING } five_state_t; volatile five_state_t five_state = FIVE_IDLE; volatile uint8_t five_bit_index = 0; // 0..7 volatile uint8_t five_byte = 0; // assembled address byte volatile uint32_t next_interval_ms = 0; // for dynamic ARR updates volatile uint8_t five_active = 0; #define K5_BIT_MS 200U #define K5_FIRST_SAMPLE_MS 300U // 1.5 * 200ms ControllerInfo info = { "0002246 826E", "1469947023", "C062_2.V60", "C062_0.P64", "0281001827", "971057" }; static void KLine5_ResetToIdle(void); //void KLine_Rearm5Baud(void); static void KLine5_StartSampling(void); static void KLine5_ScheduleTimer(uint32_t delay_ms); static void KLine_EnterUartMode_9600(void); static void KLine_SendSyncAndKeywords(void); static void TIM17_ConfigTick100us(void) { // Stop & clean __HAL_TIM_DISABLE(&htim17); __HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE); __HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE); // Compute timer clock (APB2 x2 if prescaler != 1) uint32_t timclk = HAL_RCC_GetPCLK2Freq(); RCC_ClkInitTypeDef clk; uint32_t fl; HAL_RCC_GetClockConfig(&clk, &fl); if (clk.APB2CLKDivider != RCC_HCLK_DIV1) timclk *= 2U; // 10 kHz tick => 0.1 ms per tick const uint32_t target_hz = 10000U; uint32_t psc = (timclk + target_hz - 1U) / target_hz - 1U; // round up if (psc > 0xFFFFU) psc = 0xFFFFU; __HAL_TIM_SET_PRESCALER(&htim17, psc); __HAL_TIM_SET_AUTORELOAD(&htim17, 9U); // dummy non-zero ARR __HAL_TIM_SET_COUNTER(&htim17, 0U); // One-pulse; URS so only overflow triggers update IRQ (UG won't) htim17.Instance->CR1 |= (TIM_CR1_OPM | TIM_CR1_URS); // Load PSC/ARR now without causing an IRQ htim17.Instance->EGR = TIM_EGR_UG; __HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE); // NVIC HAL_NVIC_SetPriority(TIM1_TRG_COM_TIM17_IRQn, 6, 0); HAL_NVIC_EnableIRQ(TIM1_TRG_COM_TIM17_IRQn); } // Schedule an interrupt after delay_ms using TIM17 periodic update static void KLine5_ScheduleTimer(uint32_t delay_ms) { // 0.1 ms per tick uint32_t ticks = delay_ms * 10U; if (ticks < 1U) ticks = 1U; if (ticks > 0x10000U) ticks = 0x10000U; // clamp (ARR is 16-bit) // Stop, clear, program ARR/CNT __HAL_TIM_DISABLE(&htim17); __HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE); __HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE); __HAL_TIM_SET_AUTORELOAD(&htim17, (uint16_t)(ticks - 1U)); // ARR = ticks-1 __HAL_TIM_SET_COUNTER(&htim17, 0U); // Apply registers, then clear UIF so we don't fire instantly htim17.Instance->EGR = TIM_EGR_UG; __HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE); // Ensure one-pulse remains set htim17.Instance->CR1 |= (TIM_CR1_OPM | TIM_CR1_URS); // Arm and start __HAL_TIM_ENABLE_IT(&htim17, TIM_IT_UPDATE); __HAL_TIM_ENABLE(&htim17); // CEN } // ===== Initialization entry (call once in main after MX_* inits) ===== void KLine_Slave_Init(void) { // We want to START in GPIO/EXTI mode on PA10 (falling edge). // Ensure UART is not owning PA10: HAL_UART_DeInit(&huart1); // PA10 should already be configured by CubeMX as GPIO Input + EXTI (falling) with pull-up. // If not, configure here manually. TIM17_ConfigTick100us(); KLine_Rearm5Baud(); // <— call it here //KLine5_ResetToIdle(); KLine_SetDFI_Value(dFi); } // ===== Return to IDLE (re-arm EXTI) ===== static void KLine5_ResetToIdle(void) { five_state = FIVE_IDLE; five_bit_index = 0; five_byte = 0; five_active = 0; kline_connection_status = 0; // <-- Disconnected ResetPacketCounter(); // Stop timer and disable its IRQ HAL_TIM_Base_Stop_IT(&htim17); __HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE); // Clear EXTI and re-arm falling edge on PA10 __HAL_GPIO_EXTI_CLEAR_IT(KLINE_RX_PIN); HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); } // ===== Start 5-baud sampling sequence ===== static void KLine5_StartSampling(void) { five_active = 1; five_state = FIVE_WAIT_FIRST_SAMPLE; five_bit_index = 0; five_byte = 0; // First sample at 1.5 bit times (300 ms) KLine5_ScheduleTimer(K5_FIRST_SAMPLE_MS); } // ===== GPIO EXTI ISR hook ===== void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == KLINE_RX_PIN) // PA10 (K-Line RX) { // Falling edge = start bit candidate if (five_state == FIVE_IDLE) { // Debounce / sanity: ensure line is still low ~a bit later if you want (optional) // Disable EXTI so we don't retrigger during the 5-baud window HAL_NVIC_DisableIRQ(EXTI15_10_IRQn); KLine5_StartSampling(); } } } void EXTI15_10_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(KLINE_RX_PIN); } volatile uint8_t k5_done = 0; volatile uint32_t k5_ready_at = 0; // tick when we can switch to UART volatile uint8_t kline_cmd_pending = 0; // 0 = none, 1 = a command start is pending volatile uint8_t kline_first_len = 0; // first byte (length) captured by ISR #define KEEPALIVE_TIMEOUT_MS 1000U static volatile uint32_t kl_session_deadline = 0; // === W-time handshake state === static uint8_t hs_state = 0; static uint32_t hs_next = 0; static void KLine_SessionKick(void); // forward static void KLine_SessionKick(void) { kl_session_deadline = HAL_GetTick() + KEEPALIVE_TIMEOUT_MS; } void KLine_Service(void) { // Always progress the TX byte pump KLine_BytePump_Service(); // After 5-baud address done: switch to UART and schedule handshake if (k5_done && (int32_t)(HAL_GetTick() - k5_ready_at) >= 0) { k5_done = 0; KLine_EnterUartMode_9600(); KLine_BytePump_Init(); // Arm RX HAL_UART_Receive_IT(&huart1, K_RxData, 1); // Schedule W1 -> 0x55 -> W2 -> KW LSB -> W3 -> KW MSB hs_state = 1; hs_next = HAL_GetTick() + KWP_W1_MS; kline_connection_status = 1; return; } // Handshake sequencer if (hs_state != 0) { if ((int32_t)(HAL_GetTick() - hs_next) >= 0) { if (hs_state == 1) { uint8_t b = SYNC_BYTE; // 0x55 KLine_TxStart(&b, 1, 0, 0); // no complement hs_state = 2; hs_next = HAL_GetTick() + KWP_W2_MS; } else if (hs_state == 2) { uint8_t b = KEYWORD_LSB; KLine_TxStart(&b, 1, 0, 0); hs_state = 3; hs_next = HAL_GetTick() + KWP_W3_MS; } else if (hs_state == 3) { uint8_t b = KEYWORD_MSB; KLine_TxStart(&b, 1, 0, 0); hs_state = 4; // done } } if (hs_state != 4) return; // After keywords, send your initial ASCII ident stream if (!KLine_TxBusy()) { uint8_t combined[64] = {0}; size_t n = BuildCombinedFromInfo(&info, combined, sizeof(combined)); (void)Slave_SendAsciiStream_WithAcks(combined, n); KLine_SessionKick(); kline_connection_status = 2; hs_state = 0; } return; } if(kline_connection_status == 2){ ParsedPacket packet = {0}; if (ReceivePacket_Tmo(&packet, 800)) { //waiting for an ack packet of stay alive // any packet seen → extend session KLine_SessionKick(); // keep-alive: ACK → ACK switch (packet.title) { case PACKET_CMD_ReadIdent: uint8_t combined[64] = {0}; size_t n = BuildCombinedFromInfo(&info, combined, sizeof(combined)); HAL_Delay(20); Slave_SendAsciiStream_WithAcks(combined, n); KLine_SessionKick(); break; case PACKET_CMD_ReadIdentAdress: // readIdentAddressCMD KLine_HandleReadIdentAddress(&packet.raw[3], (uint8_t)(packet.length - 4)); packet.type = PACKET_TYPE_UNKNOWN; break; case PACKET_CMD_FaultCodesRead: KLine_DTCResponse(); // <-- respond break; case PACKET_CMD_ACK: SendAckPacket(); KLine_SessionKick(); break; case PACKET_CMD_LoginEeprom: // password/login for EEPROM KLine_HandlePassword(&packet.raw[3], (uint8_t)(packet.length - 4)); packet.type = PACKET_TYPE_UNKNOWN; break; case PACKET_CMD_WriteEeprom: // 0x1A KLine_HandleWriteEeprom(&packet.raw[3], (uint8_t)(packet.length - 4)); packet.type = PACKET_TYPE_UNKNOWN; // or PACKET_TYPE_WRITE_EEPROM_RESPONSE if you add it break; case PACKET_CMD_ReadEeprom: // 0x19 KLine_HandleReadEeprom(&packet.raw[3], (uint8_t)(packet.length - 4)); packet.type = PACKET_TYPE_READ_EEPROM_RESPONSE; // we just sent EF break; case PACKET_CMD_ReadRomEeprom: // 0x03 KLine_HandleReadRomEeprom(&packet.raw[3], (uint8_t)(packet.length - 4)); packet.type = PACKET_TYPE_READ_ROM_EEPROM_RESPONSE; break; case PACKET_CMD_End: KLine_HandleEnd(); packet.type = PACKET_TYPE_UNKNOWN; // we’re done; session is torn down break; // early return is fine default: KLine_HandleCustomPacket(&packet.raw[2], (uint8_t)(packet.length - 4)); break; } } if (tick_diff(HAL_GetTick(), kl_session_deadline) >= 0) { //KLine_HandleEnd(); KLine_TerminateAndRearm(); return; } } } // ===== TIM17 ISR hook: sampling scheduler ===== void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance != TIM17) return; if (!five_active) { HAL_TIM_Base_Stop_IT(&htim17); return; } if (five_state == FIVE_WAIT_FIRST_SAMPLE || five_state == FIVE_SAMPLING) { // Sample the data bit in the middle of its window GPIO_PinState pin = HAL_GPIO_ReadPin(KLINE_GPIO_PORT, KLINE_RX_PIN); uint8_t bit = (pin == GPIO_PIN_SET) ? 1U : 0U; // LSB first five_byte |= (bit << five_bit_index); five_bit_index++; if (five_bit_index >= 8) { // Done. We can ignore stop bit timing and switch to UART mode. five_active = 0; HAL_TIM_Base_Stop_IT(&htim17); // Optional: check address value if you only respond to specific ones if (five_byte != 0xF1) { KLine5_ResetToIdle(); return; } kline_connection_status = 1; // <-- Connected //HAL_Delay(25); kline_connection_status = 1; // connected k5_ready_at = HAL_GetTick() + 200; // W1 ~20..300ms; choose ~200ms margin k5_done = 1; // signal main loop // From now on you're in USART mode; if you ever want to // go back to "listen for 5-baud", DeInit UART and call KLine5_ResetToIdle(). } else { // Schedule next bit sample in 200 ms five_state = FIVE_SAMPLING; KLine5_ScheduleTimer(K5_BIT_MS); } } } static void KLine_SetPinsToUartAF(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); // PA9 = TX, PA10 = RX for USART1, AF7 GPIO_InitStruct.Pin = KLINE_TX_PIN | KLINE_RX_PIN; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; // K-line transceiver RX usually idle-high GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(KLINE_GPIO_PORT, &GPIO_InitStruct); } // ===== Switch PA9/PA10 into USART1 9600-8N1 mode ===== static void KLine_EnterUartMode_9600(void) { // Make sure peripheral is clean HAL_UART_DeInit(&huart1); // Restore pins to AF7 KLine_SetPinsToUartAF(); // Re-init only with HAL (no MX_ call needed) huart1.Instance = USART1; huart1.Init.BaudRate = 9600; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } static uint8_t rx; HAL_UART_Receive_IT(&huart1, &rx, 1); } // ===== Send sync + keywords ===== static void KLine_SendSyncAndKeywords(void) { uint8_t buf[3] = {0x55, KEYWORD_LSB, KEYWORD_MSB}; //uint8_t buf[2] = {KEYWORD_LSB, KEYWORD_MSB}; //WriteByteRaw(SYNC_BYTE); //WriteByteRaw(KEYWORD_LSB); //WriteByteAndReadAck(KEYWORD_MSB); HAL_UART_Transmit(&huart1, buf, sizeof(buf), 200); } void KLine_Rearm5Baud(void) { // 1. DeInit UART1 so PA9/PA10 return to GPIO state HAL_UART_DeInit(&huart1); // Make sure SYSCFG is clocked (EXTI routing) __HAL_RCC_SYSCFG_CLK_ENABLE(); GPIO_InitTypeDef giTX = {0}; giTX.Pin = KLINE_TX_PIN; // PA9 //giTX.Mode = GPIO_MODE_OUTPUT_PP; // deterministic high //giTX.Pull = GPIO_NOPULL; giTX.Mode = GPIO_MODE_INPUT; // deterministic high giTX.Pull = GPIO_PULLUP; giTX.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(KLINE_GPIO_PORT, &giTX); HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_TX_PIN, GPIO_PIN_SET); // idle high // 2. Reconfigure PA10 back as GPIO input with EXTI (falling edge) GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = KLINE_RX_PIN; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(KLINE_GPIO_PORT, &GPIO_InitStruct); // 3. Clear pending EXTI interrupt for PA10 //__HAL_GPIO_EXTI_CLEAR_FLAG(KLINE_RX_PIN); //TODO PROBAR SI FUNCIONA PORQUE ANTES ESTABA CON FLAG Y IBA __HAL_GPIO_EXTI_CLEAR_IT(KLINE_RX_PIN); // not _CLEAR_FLAG //HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn); HAL_NVIC_SetPriority(EXTI15_10_IRQn, 5, 0); // pick a prio that fits your app HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); // 4. Reset state machine variables (your existing helper) KLine5_ResetToIdle(); } // ===== Optional: USART1 IRQ callback ===== void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance != USART1) return; uint8_t byte = K_RxData[0]; KLine_OnByteReceived(byte); // <-- NEW: central RX handler HAL_UART_Receive_IT(&huart1, K_RxData, 1); // re-arm RX } void KLine_ServiceCommands(void) { if (!kline_cmd_pending) return; // nothing queued // Clear the event first to avoid reentrancy/races kline_cmd_pending = 0; // Process exactly one full packet now (uses your timeout reads for remaining bytes) ParsedPacket pkt; if (KLine_ReceiveCMD(&pkt)) { // Optionally: extend session on any well-formed packet KLine_SessionKick(); // If you prefer handling keep-alive here: if (pkt.title == PACKET_CMD_ACK) { SendAckPacket(); // ACK → ACK KLine_SessionKick(); } // For FaultCodesRead you already call KLine_DTCResponse() inside KLine_ReceiveCMD() // so nothing more to do here. } } int KLine_ReceiveCMD(ParsedPacket *out){ ParsedPacket packet = (ParsedPacket){0}; uint8_t index = 0, v; uint8_t packetLength = K_RxData[0]; packet.raw[index++] = packetLength; if (!ReadPacketCounter_Tmo(100, &v)) return 0; // or call ReadPacketCounter() if you need lock-in uint8_t packetCounter = v; packet.raw[index++] = packetCounter; if (!ReadAndAckByte_Tmo(20, &v)) return 0; uint8_t packetCommand = v; packet.raw[index++] = packetCommand; for (int i = 0; i < packetLength - 3; i++) { if (!ReadAndAckByte_Tmo(20, &v)) return 0; packet.raw[index++] = v; } if (!ReadByte_Tmo(20, &v)) return 0; if (v != PACKET_END_EXPECTED) return 0; packet.raw[index++] = v; packet.length = index; packet.title = packetCommand; switch (packetCommand) { case PACKET_CMD_FaultCodesRead: KLine_DTCResponse(); // <-- respond packet.type = PACKET_TYPE_UNKNOWN; // (we don’t really use the parsed packet after responding) break; case PACKET_CMD_ACK: packet.type = PACKET_TYPE_ACK; packet.isAckNak = 1; break; case PACKET_CMD_NAK: packet.type = PACKET_TYPE_NAK; packet.isAckNak = 1; break; case PACKET_CMD_AsciiData: packet.type = (packet.raw[3] == 0x00) ? PACKET_TYPE_CODING_WSC : 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; default: packet.type = PACKET_TYPE_UNKNOWN; break; } *out = packet; return 1; } #define DTC_CHUNK_MAX 0x10U // payload bytes including 0xFC #define DTC_CMD_SIZE 3U // 0xFC #define DTC_BODY_MAX_PER_PKT (DTC_CHUNK_MAX - DTC_CMD_SIZE) // 0x0F = 15 #define DTC_RECORD_SIZE 8U #define DTC_TOTAL_RECORDS 8U // 8*8 = 64 body bytes (your master's buffer) // ---- storage set by app ---- static FaultCode s_dtc_list[DTC_TOTAL_RECORDS]; static size_t s_dtc_count = 0; FaultCode dtcs[] = { { 0x5B, 0x20, 0x55 }, { 0x50, 0x20, 0x03 }, { 0x56, 0x20, 0x56 }, { 0x5E, 0x30, 0x60 }, }; void KLine_SetDTCList(const FaultCode *list, size_t count) { if (!list) { s_dtc_count = 0; return; } if (count > DTC_TOTAL_RECORDS) count = DTC_TOTAL_RECORDS; for (size_t i = 0; i < count; ++i) s_dtc_list[i] = list[i]; s_dtc_count = count; } // one 8-byte record: first 3 meaningful, rest 0xFF static void KLine_FillRecord(uint8_t *dst, const FaultCode *fc) { dst[0] = fc->dtc; dst[1] = fc->status; dst[2] = fc->extra; dst[3] = 0xFF; dst[4] = 0xFF; dst[5] = 0xFF; dst[6] = 0xFF; dst[7] = 0xFF; } // Build the full 64-byte stream (8 records). Pads with "no-fault" records. static size_t KLine_BuildDTCStream(uint8_t *out /*>=64*/) { KLine_SetDTCList(dtcs, 4); //custom test errors KLine_SetDFI_Value(dFi); // whatever current DFI value is; this encodes s_dfi_code size_t w = 0; for (size_t i = 0; i < DTC_TOTAL_RECORDS; ++i) { FaultCode fc; if (i < s_dtc_count) { fc = s_dtc_list[i]; } else { fc.dtc = 0x00; fc.status = 0xFF; fc.extra = 0xFF; // no-fault filler } KLine_FillRecord(&out[w], &fc); w += DTC_RECORD_SIZE; } return w; // 64 } // Send one FC packet (0xFC + <=15 body bytes), wait for tester ACK static int KLine_SendFCChunk_AndWaitAck(const uint8_t *body, uint8_t body_len) { uint8_t payload[DTC_CMD_SIZE + DTC_BODY_MAX_PER_PKT]; // 1 + 15 = 16 payload[0] = (uint8_t)PACKET_CMD_FaultCodesResponse; // 0xFC memcpy(&payload[1], body, body_len); SendPacket(payload, (uint8_t)(1 + body_len)); ParsedPacket ack = (ParsedPacket){0}; if (!ReceivePacket_Tmo(&ack, 200)) return 0; if (ack.title != PACKET_CMD_ACK) return 0; return 1; } // Respond to PACKET_CMD_FaultCodesRead using <=0x10 chunk rule (includes 0xFC) void KLine_DTCResponse(void) { uint8_t stream[DTC_TOTAL_RECORDS * DTC_RECORD_SIZE]; // 64 size_t total = KLine_BuildDTCStream(stream); size_t off = 0; while (off < total) { uint8_t body_len = (uint8_t)((total - off) > DTC_BODY_MAX_PER_PKT ? DTC_BODY_MAX_PER_PKT : (total - off)); if (!KLine_SendFCChunk_AndWaitAck(&stream[off], body_len)) { // Optional: KLine_EndSession(); return; } off += body_len; } // Tell the master the list is complete SendAckPacket(); // Keep the session alive for immediate keep-alive exchange KLine_SessionKick(); } #define ASCII_CHUNK 13 // 13 info bytes + 1 f6 ascii title static size_t append_n(const char *s, size_t n, uint8_t *out, size_t w, size_t maxlen) { size_t i = 0; while (i < n && s[i] != '\0' && w < maxlen) { out[w++] = (uint8_t)s[i++]; } return w; } size_t BuildCombinedFromInfo(const ControllerInfo *ci, uint8_t *out, size_t maxlen) { size_t w = 0; w = append_n(ci->client_ident, 12, out, w, maxlen); w = append_n(ci->unk_ident1, 10, out, w, maxlen); w = append_n(ci->soft_info, 10, out, w, maxlen); w = append_n(ci->unk_ident2, 10, out, w, maxlen); w = append_n(ci->unk_ident3, 10, out, w, maxlen); w = append_n(ci->unk_ident4, 6, out, w, maxlen); return w; } int Slave_SendAsciiStream_WithAcks(const uint8_t *data, size_t len) { uint8_t payload[1 + ASCII_CHUNK]; // [CMD][chunk...] size_t off = 0; while (off < len) { uint8_t chunk = (uint8_t)((len - off) > ASCII_CHUNK ? ASCII_CHUNK : (len - off)); payload[0] = (uint8_t)PACKET_CMD_AsciiData; for (uint8_t i = 0; i < chunk; ++i) payload[1 + i] = data[off + i]; // Send AsciiData packet using your SendPacket() SendPacket(payload, (uint8_t)(chunk + 1)); // Expect a packet-level ACK from tester ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 400)) { if (ack.title == PACKET_CMD_ACK) { //SendAckPacket(); // mirror your style: ACK←→ACK KLine_SessionKick(); } else { return 0; } } else { return 0; } // Optional strict check: // if (ack.title != PACKET_CMD_ACK) { /* handle retry/error if you want */ } off += chunk; } // Terminal ACK so master breaks out SendAckPacket(); KLine_SessionKick(); return 1; /*ParsedPacket rx = {0}; if (ReceivePacket_Tmo(&rx, 200)) { //waiting for an ack packet of stay alive // any packet seen → extend session // keep-alive: ACK → ACK KLine_SessionKick(); if (rx.title == PACKET_CMD_ACK) { SendAckPacket(); KLine_SessionKick(); return; } }*/ } // Handle ReadIdent request by sending ControllerInfo as AsciiData stream /*static void KLine_Slave_HandleReadIdent(const ControllerInfo *ci) { uint8_t combined[128] = {0}; size_t n = BuildCombinedFromInfo(ci, combined, sizeof(combined)); if (n > 0) { Slave_SendAsciiStream_WithAcks(combined, n); } else { uint8_t nak_payload[1] = { (uint8_t)PACKET_CMD_NAK }; SendPacket(nak_payload, 1); } }*/ // Receive ONE packet with overall timeout (ms). Returns 1=ok, 0=timeout/error. static int ReceivePacket_Tmo(ParsedPacket *out, uint32_t overall_ms) { uint32_t deadline = HAL_GetTick() + overall_ms; // helper: remaining time #define REMAIN_MS() ({ \ int32_t d__ = (int32_t)(deadline - HAL_GetTick()); \ (d__ > 0) ? (uint32_t)d__ : 0U; \ }) ParsedPacket packet = (ParsedPacket){0}; uint8_t index = 0, v; uint32_t ms; ms = REMAIN_MS(); if (!ms) return 0; if (!ReadAndAckByte_Tmo(ms, &v)) return 0; uint8_t packetLength = v; packet.raw[index++] = packetLength; ms = REMAIN_MS(); if (!ms) return 0; if (!ReadPacketCounter_Tmo(ms, &v)) return 0; // or call ReadPacketCounter() if you need lock-in uint8_t packetCounter = v; packet.raw[index++] = packetCounter; ms = REMAIN_MS(); if (!ms) return 0; if (!ReadAndAckByte_Tmo(ms, &v)) return 0; uint8_t packetCommand = v; packet.raw[index++] = packetCommand; for (int i = 0; i < packetLength - 3; i++) { ms = REMAIN_MS(); if (!ms) return 0; if (!ReadAndAckByte_Tmo(ms, &v)) return 0; packet.raw[index++] = v; } ms = REMAIN_MS(); if (!ms) return 0; if (!ReadByte_Tmo(ms, &v)) return 0; if (v != PACKET_END_EXPECTED) return 0; packet.raw[index++] = v; packet.length = index; packet.title = packetCommand; switch (packetCommand) { case PACKET_CMD_ACK: packet.type = PACKET_TYPE_ACK; packet.isAckNak = 1; break; case PACKET_CMD_NAK: packet.type = PACKET_TYPE_NAK; packet.isAckNak = 1; break; case PACKET_CMD_AsciiData: packet.type = (packet.raw[3] == 0x00) ? PACKET_TYPE_CODING_WSC : 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; default: packet.type = PACKET_TYPE_UNKNOWN; break; } *out = packet; return 1; } void KLine_KeepAlivePoll(void) { if (!kline_connection_status) return; if (kl_session_deadline == 0) return; // timeout? if (tick_diff(HAL_GetTick(), kl_session_deadline) >= 0) { KLine_EndSession(); return; } // try to receive one packet quickly ParsedPacket rx = {0}; if (!ReceivePacket_Tmo(&rx, 20)) { return; // nothing arrived this slice } // any packet seen → extend session KLine_SessionKick(); // keep-alive: ACK → ACK if (rx.title == PACKET_CMD_ACK) { SendAckPacket(); KLine_SessionKick(); return; } // (optional) handle other tester commands here if needed } void KLine_Slave_Poll(void) { // Optional: if you later want to respond to ReadIdent on demand, you can: // ParsedPacket req; // if (ReceivePacket_Tmo(&req, 20)) { if (req.title == PACKET_CMD_ReadIdent) KLine_Slave_HandleReadIdent(&info); } } static volatile uint8_t s_eeprom_unlocked_dfi = 0; int8_t s_dfi_code = 0; // encoded byte sent as first data of 0xEF static volatile uint8_t s_eeprom_unlocked_dfi_write = 0; // default value to return for address 0x9FFE static volatile uint8_t s_rom_unlocked_cust; // from your earlier code static uint16_t s_customerChangeAddress = 0x7E36; // example default -> FD body "36 7E" static uint16_t s_identAddress = 0x7E56; // will be returned at 0x01 → 0xFE static const char s_identAscii[10] = "0470504005"; // 10 bytes, no NUL static const char s_serialNumberAscii[6] = "225832";// 6 ASCII bytes, no NUL static const char s_modIndexAscii[6] = "000004"; // Passwords & login-result bodies static const uint8_t kPwdBody_DFI[] = { 0x00, 0x03, 0xFF, 0xFF }; static const uint8_t kLoginBody_DFI[] = { 0x00, 0x00, 0x00, 0xBF, 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45 }; static const uint8_t kPwdBody_DFI_WRITE[] = { 0x00, 0x03, 0x2F, 0xFF, 'K','H','T','C','A','8','G','0','E'}; static const uint8_t kPwdBody_CUST[] = { 0x00, 0x00, 0x82, 0x33 }; static const uint8_t kLoginBody_CUST[] = { 0x00, 0x00, 0x9F, 0xFF, 0x41, 0x31, 0x32, 0x50, 0x35, 0x34, 0x11, 0x02, 0x00 }; static const uint8_t kReadIdentAddrBody[] = { 0x02, 0x00, 0xC6 }; static const uint8_t kActivateSyncOut[] = { 0x02, 0x88, 0x01, 0x04, 0x06, 0x01 }; void KLine_SetDFI_Value(float dfi) { // encode: master decodes as ((int8_t)raw[3]) * 3.0 / 256.0 float scaled = dfi * 256.0f / 3.0f; int val = (int)lroundf(scaled); if (val < -128) val = -128; if (val > 127) val = 127; s_dfi_code = (int8_t)val; } float KLine_GetDFI(void) { return ((float)s_dfi_code * 3.0f) / 256.0f; } void KLine_SetCustomerChangeAddress(uint16_t addr) { s_customerChangeAddress = addr; } /*void KLine_SetModIndexAscii(const char *six_digits) { if (!six_digits) return; for (int i = 0; i < 6; ++i) { char c = six_digits[i]; s_modIndexAscii[i] = (c ? c : '0'); } }*/ static int KLine_SendLoginResultBody_AndAck(const uint8_t *body, size_t len) { uint8_t payload[1 + 16]; // 0xF0 + up to 16 bytes (we send 13) uint8_t idx = 0; payload[idx++] = 0xF0; // login result command for (size_t i = 0; i < len; ++i) payload[idx++] = body[i]; SendPacket(payload, idx); ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 200)) { if (ack.title == PACKET_CMD_ACK) { SendAckPacket(); // mirror your style: ACK←→ACK KLine_SessionKick(); } else { return 0; } } else { return 0; } KLine_SessionKick(); return 1; } static void KLine_HandlePassword(const uint8_t *body, uint8_t body_len) { if (body_len == sizeof(kPwdBody_DFI) && memcmp(body, kPwdBody_DFI, sizeof(kPwdBody_DFI)) == 0) { s_eeprom_unlocked_dfi = 1; (void)KLine_SendLoginResultBody_AndAck(kLoginBody_DFI, sizeof(kLoginBody_DFI)); return; } if (body_len == sizeof(kPwdBody_CUST) && memcmp(body, kPwdBody_CUST, sizeof(kPwdBody_CUST)) == 0) { s_rom_unlocked_cust = 1; (void)KLine_SendLoginResultBody_AndAck(kLoginBody_CUST, sizeof(kLoginBody_CUST)); return; } if (body_len == sizeof(kPwdBody_DFI_WRITE) && memcmp(body, kPwdBody_DFI_WRITE, sizeof(kPwdBody_DFI_WRITE)) == 0) { s_eeprom_unlocked_dfi_write = 1; (void)KLine_SendLoginResultBody_AndAck(kLoginBody_DFI, sizeof(kLoginBody_DFI)); return; } // wrong password: clear both s_eeprom_unlocked_dfi = 0; s_eeprom_unlocked_dfi_write = 0; s_rom_unlocked_cust = 0; // (optional) Send NAK // uint8_t nak = PACKET_CMD_NAK; SendPacket(&nak,1); } static void KLine_HandleWriteEeprom(const uint8_t *body, uint8_t body_len) { // Body: [len][addr_hi][addr_lo][value][csum] if (body_len < 5U) return; const uint8_t len_req = body[0]; const uint16_t addr = ((uint16_t)body[1] << 8) | body[2]; const uint8_t value = body[3]; const uint8_t csum = body[4]; // Only allow if write unlock is active if (!s_eeprom_unlocked_dfi_write) return; // Only handle DFI @ 0x0044, len 0x02 if (addr != 0x0044 || len_req != 0x02) return; // Simple checksum rule: complement must match if ((uint8_t)(value + csum) != 0) { // Optional NAK on bad checksum: // uint8_t nak = PACKET_CMD_NAK; SendPacket(&nak, 1); return; } // Update DFI code in RAM (your GetDFI uses s_dfi_code -> float) s_dfi_code = (int8_t)value; memWrite = 1; // Send WriteEepromResponse (0xF9) — often with empty body besides the title uint8_t payload[1] = { (uint8_t)PACKET_CMD_WriteEepromResponse }; // 0xF9 SendPacket(payload, (uint8_t)sizeof(payload)); ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) { SendAckPacket(); KLine_SessionKick(); } } static void KLine_HandleReadEeprom(const uint8_t *body, uint8_t body_len) { // Body: [len_req][addr_hi][addr_lo] if (body_len < 3U) return; const uint8_t len_req = body[0]; const uint16_t addr = ((uint16_t)body[1] << 8) | body[2]; // ---- DFI @ 0x0044, len=2 (requires DFI unlock) ---- if (addr == 0x0044 && len_req == 0x02) { if (!s_eeprom_unlocked_dfi) return; uint8_t payload[3]; payload[0] = (uint8_t)PACKET_CMD_ReadEepromResponse; // 0xEF payload[1] = (uint8_t)s_dfi_code; // dfi1 payload[2] = 0x00; // dfi2 (placeholder) SendPacket(payload, (uint8_t)sizeof(payload)); ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) { SendAckPacket(); KLine_SessionKick(); } return; } // ---- Serial number @ 0x0080, len=6 (no unlock) ---- if (addr == 0x0080 && len_req == 0x06) { uint8_t payload[1 + 6]; payload[0] = (uint8_t)PACKET_CMD_ReadEepromResponse; // 0xEF for (int i = 0; i < 6; ++i) payload[1 + i] = (uint8_t)s_serialNumberAscii[i]; SendPacket(payload, (uint8_t)sizeof(payload)); ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) { SendAckPacket(); KLine_SessionKick(); } return; } // Unknown/unsupported EEPROM address → ignore or NAK (uncomment to NAK) // { uint8_t nak = PACKET_CMD_NAK; SendPacket(&nak, 1); } } static void KLine_HandleReadRomEeprom(const uint8_t *body, uint8_t body_len) { // Body: [len_req][addr_hi][addr_lo] if (body_len < 3U) return; const uint8_t len_req = body[0]; const uint16_t addr = ((uint16_t)body[1] << 8) | body[2]; // --- Case 1: 0x9FFE -> return customerChangeAddress (lo, hi) // Requires ROM unlock (password 0x00 00 82 33). if (addr == 0x9FFE) { if (!s_rom_unlocked_cust) return; if (len_req != 0x02) return; // enforce length uint8_t payload[1 + 2]; payload[0] = (uint8_t)PACKET_CMD_ReadRomEepromResponse; // 0xFD payload[1] = (uint8_t)(s_customerChangeAddress & 0xFF); // lo payload[2] = (uint8_t)((s_customerChangeAddress >> 8) & 0xFF); // hi SendPacket(payload, (uint8_t)sizeof(payload)); ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) { SendAckPacket(); // mirror tester's ACK with our ACK KLine_SessionKick(); } return; } // --- Case 2: Ident string at (s_identAddress - 10), len = 10 // DOES NOT require ROM unlock. if (addr == (uint16_t)(s_identAddress - 10)) { if (len_req != 0x0A) return; uint8_t payload[1 + 10]; payload[0] = (uint8_t)PACKET_CMD_ReadRomEepromResponse; // 0xFD for (int i = 0; i < 10; ++i) payload[1 + i] = (uint8_t)s_identAscii[i]; SendPacket(payload, (uint8_t)sizeof(payload)); ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) { SendAckPacket(); KLine_SessionKick(); } return; } // --- Case 3: customerChangeAddress + 3 -> 6 ASCII bytes (mod index), len = 6 // Requires ROM unlock. if (addr == (uint16_t)(s_customerChangeAddress + 3)) { if (!s_rom_unlocked_cust) return; if (len_req != 0x06) return; uint8_t payload[1 + 6]; payload[0] = (uint8_t)PACKET_CMD_ReadRomEepromResponse; // 0xFD for (int i = 0; i < 6; ++i) payload[1 + i] = (uint8_t)s_modIndexAscii[i]; SendPacket(payload, (uint8_t)sizeof(payload)); ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) { SendAckPacket(); KLine_SessionKick(); } return; } // Unknown ROM address: ignore (or NAK if you prefer) // uint8_t nak = PACKET_CMD_NAK; SendPacket(&nak, 1); } void Kline_CCIResponse(uint8_t len_req){ // customerChangeIndex (2 bytes) uint8_t payload[1 + 2]; // [FD][hi][lo] payload[0] = (uint8_t)PACKET_CMD_ReadRomEepromResponse; // 0xFD payload[1] = (uint8_t)((s_customerChangeAddress >> 8) & 0xFF); payload[2] = (uint8_t)( s_customerChangeAddress & 0xFF); // If tester asked for exactly 2 bytes; otherwise you could clamp/extend (void)len_req; // if needed, enforce: if (len_req != 0x02) return; SendPacket(payload, sizeof(payload)); // Expect tester ACK; respond ACK; keepalive ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) { SendAckPacket(); KLine_SessionKick(); } } static void KLine_HandleReadIdentAddress(const uint8_t *body, uint8_t body_len) { if (body_len < sizeof(kReadIdentAddrBody)) return; if (memcmp(body, kReadIdentAddrBody, sizeof(kReadIdentAddrBody)) != 0) return; // Respond: 0xFE [lo][hi] uint8_t payload[1 + 2]; payload[0] = 0xFE; // custom "ReadIdentAddressResponse" payload[1] = (uint8_t)(s_identAddress & 0xFF); // lo payload[2] = (uint8_t)((s_identAddress >> 8) & 0xFF); // hi SendPacket(payload, sizeof(payload)); ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) { SendAckPacket(); // mirror your ACK↔ACK style KLine_SessionKick(); } } uint8_t SYNC_PULSE_OUT; static void KLine_HandleCustomPacket(const uint8_t *body, uint8_t body_len) { if (body_len < sizeof(kActivateSyncOut)){ if (memcmp(body, kActivateSyncOut, sizeof(kActivateSyncOut)) == 0){ SYNC_PULSE_OUT = 1; SendAckPacket(); KLine_SessionKick(); } } /* ParsedPacket ack = (ParsedPacket){0}; if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) { SendAckPacket(); // mirror your ACK↔ACK style KLine_SessionKick(); }*/ } /*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; }*/ #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, 0); 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 } static void KLine_TerminateAndRearm(void) { // Drop session state kline_connection_status = 0; kl_session_deadline = 0; // Clear feature unlocks (DFI/ROM) s_eeprom_unlocked_dfi = 0; s_rom_unlocked_cust = 0; // Stop any pending command processing from ISR kline_cmd_pending = 0; // Reset packet counters (TX/RX sequencing) ResetPacketCounter(); // Make sure RX IT isn’t mid-flight; then drop UART and rearm 5-baud // (AbortReceive_IT only if you enabled it; otherwise DeInit is fine) // HAL_UART_AbortReceive_IT(&huart1); HAL_UART_DeInit(&huart1); // Go back to EXTI/bit-bang listen KLine_Rearm5Baud(); } // Handle tester's End command: ACK then terminate static void KLine_HandleEnd(void) { // ACK the End command as a packet-level ACK SendAckPacket(); // Immediately end session and rearm KLine_TerminateAndRearm(); }