diff --git a/.cproject b/.cproject
index e4b7d6f..00c1f43 100644
--- a/.cproject
+++ b/.cproject
@@ -35,6 +35,7 @@
@@ -54,6 +55,7 @@
+
@@ -118,6 +120,7 @@
@@ -136,6 +139,7 @@
+
diff --git a/Core/Inc/stm32g4xx_it.h b/Core/Inc/stm32g4xx_it.h
index a1f0aea..b8a1a2f 100644
--- a/Core/Inc/stm32g4xx_it.h
+++ b/Core/Inc/stm32g4xx_it.h
@@ -60,9 +60,11 @@ void DMA1_Channel1_IRQHandler(void);
void DMA1_Channel2_IRQHandler(void);
void FDCAN1_IT0_IRQHandler(void);
void TIM1_BRK_TIM15_IRQHandler(void);
+void TIM1_TRG_COM_TIM17_IRQHandler(void);
void TIM1_CC_IRQHandler(void);
void TIM2_IRQHandler(void);
void TIM3_IRQHandler(void);
+void USART1_IRQHandler(void);
void TIM6_DAC_IRQHandler(void);
/* USER CODE BEGIN EFP */
diff --git a/Core/Kline_Libs/IKW1281Connection.c b/Core/Kline_Libs/IKW1281Connection.c
new file mode 100644
index 0000000..c880848
--- /dev/null
+++ b/Core/Kline_Libs/IKW1281Connection.c
@@ -0,0 +1,274 @@
+/* IKW1281Connection.c — byte pump, RX FIFO, no HAL_Delay() in hot path */
+
+#include
+#include
+#include
+#include "IKW1281Connection.h"
+#include "main.h"
+
+extern UART_HandleTypeDef huart1;
+
+uint8_t K_TxData[KLINE_BUFFER_SIZE];
+volatile uint8_t rx_done_flag = 0;
+
+// ---------- RX FIFO ----------
+static uint8_t rx_fifo[KLINE_RX_FIFO_SZ];
+static volatile uint16_t rx_head = 0, rx_tail = 0;
+
+static inline int rx_fifo_push(uint8_t b){
+ uint16_t next = (uint16_t)((rx_head + 1U) % KLINE_RX_FIFO_SZ);
+ if (next == rx_tail) rx_tail = (uint16_t)((rx_tail + 1U) % KLINE_RX_FIFO_SZ); // drop oldest
+ rx_fifo[rx_head] = b;
+ rx_head = next;
+ rx_done_flag = 1;
+ return 1;
+}
+int KLine_RxFifo_Pop(uint8_t *out){
+ if (rx_head == rx_tail) return 0;
+ uint8_t b = rx_fifo[rx_tail];
+ rx_tail = (uint16_t)((rx_tail + 1U) % KLINE_RX_FIFO_SZ);
+ if (out) *out = b;
+ return 1;
+}
+
+// ---------- Non-blocking TX "byte pump" ----------
+volatile KTxEngine ktx = {0};
+static uint8_t tx_byte_1;
+
+static uint8_t _packetCounter = 0;
+static uint8_t _packetCounterInitialized = 0;
+
+
+void KLine_BytePump_Init(void){ memset((void*)&ktx, 0, sizeof(ktx)); }
+
+int KLine_TxStart(const uint8_t *data, uint16_t len, uint8_t append_end, uint8_t require_ack){
+ if (ktx.active==1) return 0;
+ if (len > KLINE_TXBUF_MAX) {
+ // defensive: refuse too-long sends (KWP1281 blocks are small, so this is fine)
+ return 0;
+ }
+ memcpy(ktx.ibuf, data, len);
+ ktx.buf = ktx.ibuf;
+ ktx.using_ibuf = 1;
+ ktx.active = 1; ktx.len = len; ktx.idx = 0;
+ ktx.append_end = append_end; ktx.last_tx = 0; ktx.awaiting_echo = 0;
+ ktx.require_ack = require_ack; ktx.tx_inflight = 0; ktx.next_time_ms = HAL_GetTick();
+ return 1;
+}
+int KLine_TxBusy(void){ return ktx.active || ktx.tx_inflight || ktx.awaiting_echo; }
+
+void KLine_BytePump_Service(void){
+ if (!ktx.active) return;
+ uint32_t now = HAL_GetTick();
+ if (ktx.awaiting_echo) return;
+ //if (ktx.tx_inflight) return;
+ if ((int32_t)(now - ktx.next_time_ms) < 0) return;
+
+ uint8_t b;
+ if (ktx.idx < ktx.len) b = ktx.buf[ktx.idx++];
+ else if (ktx.append_end) {
+ b = PACKET_END_EXPECTED;
+ ktx.append_end = 0;
+ ktx.active = 2; //ENDING
+ }
+ else { // === FINISHED: make sure state is fully reset ===
+ ktx.active = 0;
+ //ktx.awaiting_echo = 0; // <— add //TODO
+ //ktx.tx_inflight = 0; // <— add
+ // (optional) ktx.next_time_ms = now;
+ return;
+ }
+
+ tx_byte_1 = b;
+ if (HAL_UART_Transmit_IT(&huart1, &tx_byte_1, 1) == HAL_OK){
+ //ktx.tx_inflight = 1;
+ ktx.last_tx = b;
+ if (ktx.require_ack && ktx.active == 1){
+ ktx.awaiting_echo = 1; // expect tester's ~b
+ } else {
+ ktx.awaiting_echo = 0;
+ }
+ ktx.next_time_ms = now + KWP_P1_GAP_MS;
+
+ }
+}
+//int pepe = 0;
+// Called from HAL_UART_RxCpltCallback (kline.c)
+void KLine_OnByteReceived(uint8_t byte)
+{
+
+ uint32_t now = HAL_GetTick();
+
+ // ---- Self-echo suppression: only within a tiny time window after our TX ----
+ if(ktx.active){
+ if ((uint32_t)(now - ktx.last_tx_done_ms) <= KWP_ECHO_SUPPRESS_MS && byte == ktx.last_tx) {
+ if(ktx.active == 2){
+ ktx.active = 0;
+ ktx.awaiting_echo = 0;
+ }
+ return; // echo of our own last TX byte
+ //no se bien si me quita el echo o el counter, habra que ver despues cuando los mensajes no sean de 03
+ }
+ }
+ /*if(pepe==2){
+ pepe++;
+ }*/
+
+ /*if(ktx.awaiting_ack && byte == ktx.last_tx){
+ return;
+ }*/
+
+ // ---- Tester complement for our TX byte? consume only if it matches ~last_tx ----
+ if (ktx.active == 1) {
+ if(ktx.awaiting_echo){
+ if (byte == (uint8_t)~ktx.last_tx) {
+ ktx.awaiting_echo = 0;
+ //return; // consumed tester's complement
+ } else {
+ // Not a complement: treat as inbound payload, drop awaiting flag to resync
+ ktx.awaiting_echo = 0;
+ //ktx.active = 0; //we are done, communication failed drop message.
+ //return;
+ // fall through to inbound handling
+ }
+ }
+ }
+ else{
+ //WriteComplement(byte); //then its inboud payload, echo
+ rx_fifo_push(byte);
+ }
+
+ // ---- Inbound byte from tester: ACK it immediately so we never race timing ----
+ // (Tiny 1-byte blocking write; safe and deterministic at 9600 bps.)
+ //WriteComplement(byte);
+
+ // Push for higher-level parsing
+}
+void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
+ if (huart->Instance == USART1) {
+ //ktx.tx_inflight = 0;
+ ktx.last_tx_done_ms = HAL_GetTick(); // <-- NEW
+ }
+}
+
+// ---------- Legacy/compat API (kept) ----------
+uint8_t ReadByte(){
+ uint32_t timeout = HAL_GetTick() + 1000;
+ uint8_t b;
+ while (!KLine_RxFifo_Pop(&b)){
+ if ((int32_t)(HAL_GetTick() - timeout) >= 0) return 0xFF;
+ }
+ return b;
+}
+uint8_t ReadAndAckByte(void){ uint8_t b = ReadByte(); WriteComplement(b); return b; }
+void WriteComplement(uint8_t b){
+ uint8_t c = (uint8_t)~b;
+ WriteByteRaw(c); }
+
+void WriteByteRaw(uint8_t b){
+ ktx.active = 0;
+ KLine_TxStart(&b, 1, 0, 0);
+ while (KLine_TxBusy()) KLine_BytePump_Service();
+
+ /*ktx.last_tx = b;
+ ktx.active = 2;
+
+ pepe++;
+ if(b == 0x01){
+ pepe++;
+ }
+
+ extern UART_HandleTypeDef huart1;
+ (void)HAL_UART_Transmit(&huart1, &b, 1, 5);*/
+}
+
+#define MAX_PACKETS 16
+static ParsedPacket packets_buffer[MAX_PACKETS];
+
+ParsedPacket* ReceivePackets(int *out_count){
+ int count = 0;
+ while (1){
+ if (count >= MAX_PACKETS) break;
+ ParsedPacket p = ReceivePacket();
+ packets_buffer[count++] = p;
+ if (p.isAckNak) break;
+ SendAckPacket();
+ }
+ if (out_count) *out_count = count;
+ return packets_buffer;
+}
+
+
+
+ParsedPacket ReceivePacket(){
+ ParsedPacket packet = (ParsedPacket){0};
+ uint8_t idx = 0;
+
+ uint8_t packetLength = ReadAndAckByte(); packet.raw[idx++] = packetLength;
+ uint8_t packetCounter = ReadPacketCounter(); packet.raw[idx++] = packetCounter;
+ uint8_t packetCommand = ReadAndAckByte(); packet.raw[idx++] = packetCommand;
+
+ for (int i=0;i= 0) return 0;
+ }
+ if (out) *out = b; return 1;
+}
+int ReadPacketCounter_Tmo(uint32_t ms, uint8_t *out){
+ uint8_t v; if (!ReadByte_Tmo(ms, &v)) return 0;
+ if (!_packetCounterInitialized){ _packetCounter = v; _packetCounterInitialized = 1; }
+ else if (v != _packetCounter){ _packetCounter = v+1; return 0; }
+ _packetCounter++; WriteComplement(v);
+ if (out) *out = v; return 1;
+}
+int ReadAndAckByte_Tmo(uint32_t ms, uint8_t *out){
+ uint8_t b; if (!ReadByte_Tmo(ms, &b)) return 0; WriteComplement(b); if (out) *out = b; return 1;
+}
+
+void SendAckPacket(){ uint8_t a = (uint8_t)PACKET_CMD_ACK; SendPacket(&a, 1); }
+void SendPacket(uint8_t* payload, uint8_t length){
+ uint8_t packetLength = (uint8_t)(length + 2);
+ static uint8_t packet[1 + 1 + MAX_PACKET_SIZE + 1];
+ int idx = 0;
+ packet[idx++] = packetLength;
+ packet[idx++] = _packetCounter++;
+ for (int i=0;i
+#include "stdint.h"
+
+extern uint8_t K_RxData[];
+extern volatile uint8_t rx_done_flag;
+
+#define KLINE_BUFFER_SIZE 20
+#define PACKET_END_EXPECTED 0x03
+
+// === Protocol sizing ===
+#define MAX_PACKET_SIZE 16 // KWP1281 block size
+
+// === Timings (per ISO 9141-2 / KWP1281) ===
+#define KWP_P4_MIN_MS 5U // tester inter-byte (P4) 5..20 ms
+#define KWP_P4_MAX_MS 20U
+#define KWP_P1_GAP_MS 5U // ECU inter-byte (P1) target
+
+#define KWP_ECHO_SUPPRESS_MS 3U // ~3 ms window after TX to drop self-echo
+
+// 5-baud init post-switch delays
+#define KWP_W1_MS 300U // ECU internal time before 0x55 (20..300 ms allowed)
+#define KWP_W2_MS 10U // 0x55 -> first keyword (5..20 ms)
+#define KWP_W3_MS 10U // KW LSB -> KW MSB (0..20 ms)
+
+// RX FIFO
+#define KLINE_RX_FIFO_SZ 64
+
+typedef struct {
+ char client_ident[13];
+ char unk_ident1[11];
+ char soft_info[11];
+ char unk_ident2[11];
+ char unk_ident3[11];
+ char unk_ident4[7];
+} ControllerInfo;
+
+typedef enum {
+ PACKET_CMD_ReadIdent = 0x00,
+ PACKET_CMD_ReadIdentAdress = 0x01,
+ PACKET_CMD_ReadRomEeprom = 0x03,
+ PACKET_CMD_ActuatorTest = 0x04,
+ PACKET_CMD_FaultCodesDelete = 0x05,
+ PACKET_CMD_End = 0x06,
+ PACKET_CMD_FaultCodesRead = 0x07,
+ PACKET_CMD_ACK = 0x09,
+ PACKET_CMD_NAK = 0x0A,
+ PACKET_CMD_SoftwareCoding = 0x10,
+ PACKET_CMD_LoginEeprom = 0x18,
+ PACKET_CMD_ReadEeprom = 0x19,
+ PACKET_CMD_WriteEeprom = 0x1A,
+ PACKET_CMD_Custom = 0x1B,
+ PACKET_CMD_GroupReading = 0x29,
+ PACKET_CMD_Login = 0x2B,
+ PACKET_CMD_GroupReadingResponse = 0xE7,
+ PACKET_CMD_ReadEepromResponse = 0xEF,
+ PACKET_CMD_ActuatorTestResponse = 0xF5,
+ PACKET_CMD_AsciiData = 0xF6,
+ PACKET_CMD_WriteEepromResponse = 0xF9,
+ PACKET_CMD_FaultCodesResponse = 0xFC,
+ PACKET_CMD_ReadRomEepromResponse = 0xFD
+} PacketCommand;
+
+typedef enum {
+ PACKET_TYPE_UNKNOWN = 0,
+ PACKET_TYPE_ACK,
+ PACKET_TYPE_NAK,
+ PACKET_TYPE_ASCII_DATA,
+ PACKET_TYPE_CODING_WSC,
+ PACKET_TYPE_READ_EEPROM_RESPONSE,
+ PACKET_TYPE_READ_ROM_EEPROM_RESPONSE
+} PacketType;
+
+typedef struct {
+ PacketType type;
+ uint8_t title;
+ uint8_t length;
+ uint8_t raw[MAX_PACKET_SIZE];
+ uint8_t isAckNak;
+} ParsedPacket;
+
+#define KLINE_TXBUF_MAX 32 // enough for KWP1281: 2 header + 16 data + 0x03 + margins
+
+// === Non-blocking TX "byte pump" ===
+typedef struct {
+ uint8_t active;
+ const uint8_t *buf;
+ uint16_t len;
+ uint16_t idx;
+ uint8_t append_end; // append 0x03
+ uint8_t last_tx;
+ uint8_t awaiting_echo; // waiting for complement
+ uint8_t require_ack; // expect complement per byte?
+ uint8_t tx_inflight; // HAL_UART_Transmit_IT in progress
+ uint32_t next_time_ms; // next earliest send time
+ uint32_t last_tx_done_ms; // last send time
+
+ uint8_t ibuf[KLINE_TXBUF_MAX];
+ uint8_t using_ibuf;
+} KTxEngine;
+
+extern volatile KTxEngine ktx;
+
+void KLine_BytePump_Init(void);
+void KLine_BytePump_Service(void);
+int KLine_TxStart(const uint8_t *data, uint16_t len, uint8_t append_end, uint8_t require_ack);
+int KLine_TxBusy(void);
+
+// RX FIFO helpers
+int KLine_RxFifo_Pop(uint8_t *out);
+void KLine_OnByteReceived(uint8_t byte);
+
+// Legacy/blocking API (still available)
+void SendPacket(uint8_t* payload, uint8_t length);
+void SendAckPacket(void);
+void WriteByteAndReadAck(uint8_t b);
+void WriteByteRaw(uint8_t b);
+void WriteComplement(uint8_t b);
+
+extern ParsedPacket ReceivePacket(void);
+extern uint8_t ReadByte(void);
+extern uint8_t ReadAndAckByte(void);
+extern void ResetPacketCounter(void);
+extern uint8_t ReadPacketCounter(void);
+extern inline int32_t tick_diff(uint32_t a, uint32_t b);
+
+// Timed reads used by higher layers
+int ReadByte_Tmo(uint32_t ms, uint8_t *out);
+int ReadPacketCounter_Tmo(uint32_t ms, uint8_t *out);
+int ReadAndAckByte_Tmo(uint32_t ms, uint8_t *out);
+
+#endif /* INC_IKW1281CONNECTION_H_ */
diff --git a/Core/Kline_Libs/kline.c b/Core/Kline_Libs/kline.c
new file mode 100644
index 0000000..e8b04d9
--- /dev/null
+++ b/Core/Kline_Libs/kline.c
@@ -0,0 +1,1281 @@
+/*
+ * 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();
+}
+
diff --git a/Core/Kline_Libs/kline.h b/Core/Kline_Libs/kline.h
new file mode 100644
index 0000000..b5b3cf9
--- /dev/null
+++ b/Core/Kline_Libs/kline.h
@@ -0,0 +1,38 @@
+/* kline.h — cleaned up, matches new connection layer */
+
+#ifndef INC_KLINE_H_
+#define INC_KLINE_H_
+
+#include "IKW1281Connection.h"
+
+#define ECU_INIT_ADDRESS 0xF1
+
+#define KLINE_RX_PIN GPIO_PIN_10
+#define KLINE_TX_PIN GPIO_PIN_9
+#define KLINE_GPIO_PORT GPIOA
+
+#define SLAVE_ADDR 0xF1
+#define SYNC_BYTE 0x55
+#define POST_INIT_BAUD 9600
+#define KEYWORD_LSB 0x8C
+#define KEYWORD_MSB 0x51
+#define REQUIRE_KEY_ACK 0 // no complements expected during 0x55/keywords
+
+extern volatile uint8_t kline_connection_status;
+
+typedef struct {
+ uint8_t dtc;
+ uint8_t status;
+ uint8_t extra;
+} FaultCode;
+
+size_t BuildCombinedFromInfo(const ControllerInfo *ci, uint8_t *out, size_t maxlen);
+
+void KLine_Slave_Init(void);
+void KLine_Rearm5Baud(void);
+void KLine_Service(void);
+void KLine_ServiceCommands(void);
+void KLine_Slave_Poll(void);
+void KLine_KeepAlivePoll(void);
+
+#endif /* INC_KLINE_H_ */
diff --git a/Core/Kline_Libs/psg_prop.h b/Core/Kline_Libs/psg_prop.h
new file mode 100644
index 0000000..c091be3
--- /dev/null
+++ b/Core/Kline_Libs/psg_prop.h
@@ -0,0 +1,25 @@
+/*
+ * psg_prop.h
+ *
+ * Created on: Aug 19, 2025
+ * Author: herli
+ */
+
+#ifndef INC_PSG_PROP_H_
+#define INC_PSG_PROP_H_
+
+typedef struct {
+ char client_ident[13]; // 12 + null
+ char unk_ident1[11];
+ char soft_info[11];
+ char unk_ident2[11];
+ char unk_ident3[11];
+} PSGControllerInfo;
+
+#define i_Ident "0470504005"
+#define i_SerialN "225832"
+#define i_modIndex "000004"
+
+
+
+#endif /* INC_PSG_PROP_H_ */
diff --git a/Core/Src/main.c b/Core/Src/main.c
index 3ab19f6..0f2c336 100644
--- a/Core/Src/main.c
+++ b/Core/Src/main.c
@@ -31,7 +31,8 @@
#include "ee_manager.h"
#include "toothed_wheel.h"
#include "can_read_pump_data.h"
-
+#include "kline.h"
+#include "IKW1281Connection.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
@@ -75,6 +76,7 @@ TIM_HandleTypeDef htim7;
TIM_HandleTypeDef htim8;
TIM_HandleTypeDef htim15;
TIM_HandleTypeDef htim16;
+TIM_HandleTypeDef htim17;
UART_HandleTypeDef huart1;
@@ -108,6 +110,7 @@ static void MX_TIM16_Init(void);
static void MX_RTC_Init(void);
static void MX_TIM8_Init(void);
static void MX_OPAMP2_Init(void);
+static void MX_TIM17_Init(void);
/* USER CODE BEGIN PFP */
void DAC_Voltaje(uint8_t isPeak);
void EvaluateInjection(void);
@@ -292,6 +295,7 @@ int main(void)
MX_RTC_Init();
MX_TIM8_Init();
MX_OPAMP2_Init();
+ MX_TIM17_Init();
/* USER CODE BEGIN 2 */
CAN_AppInit(); //ford might be too fast
@@ -343,6 +347,9 @@ int main(void)
}/*else{
SYNC_Pulse_EnableGPIO();
}*/
+
+ KLine_Slave_Init();
+
/* USER CODE END 2 */
/* Infinite loop */
@@ -352,6 +359,8 @@ int main(void)
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
+ KLine_Service();
+ KLine_ServiceCommands(); // <-- NEW: react to ISR kick, process full packet
TW_Service();
CAN_Service(); // drain queued frames whenever HW has room
@@ -1304,6 +1313,42 @@ static void MX_TIM16_Init(void)
}
+/**
+ * @brief TIM17 Initialization Function
+ * @param None
+ * @retval None
+ */
+static void MX_TIM17_Init(void)
+{
+
+ /* USER CODE BEGIN TIM17_Init 0 */
+
+ /* USER CODE END TIM17_Init 0 */
+
+ /* USER CODE BEGIN TIM17_Init 1 */
+
+ /* USER CODE END TIM17_Init 1 */
+ htim17.Instance = TIM17;
+ htim17.Init.Prescaler = 160-1;
+ htim17.Init.CounterMode = TIM_COUNTERMODE_UP;
+ htim17.Init.Period = 65535;
+ htim17.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
+ htim17.Init.RepetitionCounter = 0;
+ htim17.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
+ if (HAL_TIM_Base_Init(&htim17) != HAL_OK)
+ {
+ Error_Handler();
+ }
+ if (HAL_TIM_OnePulse_Init(&htim17, TIM_OPMODE_SINGLE) != HAL_OK)
+ {
+ Error_Handler();
+ }
+ /* USER CODE BEGIN TIM17_Init 2 */
+
+ /* USER CODE END TIM17_Init 2 */
+
+}
+
/**
* @brief USART1 Initialization Function
* @param None
diff --git a/Core/Src/stm32g4xx_hal_msp.c b/Core/Src/stm32g4xx_hal_msp.c
index cd2b123..4dc40ee 100644
--- a/Core/Src/stm32g4xx_hal_msp.c
+++ b/Core/Src/stm32g4xx_hal_msp.c
@@ -639,6 +639,8 @@ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
/* TIM1 interrupt Init */
HAL_NVIC_SetPriority(TIM1_BRK_TIM15_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(TIM1_BRK_TIM15_IRQn);
+ HAL_NVIC_SetPriority(TIM1_TRG_COM_TIM17_IRQn, 0, 0);
+ HAL_NVIC_EnableIRQ(TIM1_TRG_COM_TIM17_IRQn);
HAL_NVIC_SetPriority(TIM1_CC_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);
/* USER CODE BEGIN TIM1_MspInit 1 */
@@ -746,6 +748,20 @@ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
/* USER CODE END TIM16_MspInit 1 */
}
+ else if(htim_base->Instance==TIM17)
+ {
+ /* USER CODE BEGIN TIM17_MspInit 0 */
+
+ /* USER CODE END TIM17_MspInit 0 */
+ /* Peripheral clock enable */
+ __HAL_RCC_TIM17_CLK_ENABLE();
+ /* TIM17 interrupt Init */
+ HAL_NVIC_SetPriority(TIM1_TRG_COM_TIM17_IRQn, 0, 0);
+ HAL_NVIC_EnableIRQ(TIM1_TRG_COM_TIM17_IRQn);
+ /* USER CODE BEGIN TIM17_MspInit 1 */
+
+ /* USER CODE END TIM17_MspInit 1 */
+ }
}
@@ -823,6 +839,14 @@ void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base)
/* HAL_NVIC_DisableIRQ(TIM1_BRK_TIM15_IRQn); */
/* USER CODE END TIM1:TIM1_BRK_TIM15_IRQn disable */
+ /* USER CODE BEGIN TIM1:TIM1_TRG_COM_TIM17_IRQn disable */
+ /**
+ * Uncomment the line below to disable the "TIM1_TRG_COM_TIM17_IRQn" interrupt
+ * Be aware, disabling shared interrupt may affect other IPs
+ */
+ /* HAL_NVIC_DisableIRQ(TIM1_TRG_COM_TIM17_IRQn); */
+ /* USER CODE END TIM1:TIM1_TRG_COM_TIM17_IRQn disable */
+
HAL_NVIC_DisableIRQ(TIM1_CC_IRQn);
/* USER CODE BEGIN TIM1_MspDeInit 1 */
@@ -936,6 +960,27 @@ void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base)
/* USER CODE END TIM16_MspDeInit 1 */
}
+ else if(htim_base->Instance==TIM17)
+ {
+ /* USER CODE BEGIN TIM17_MspDeInit 0 */
+
+ /* USER CODE END TIM17_MspDeInit 0 */
+ /* Peripheral clock disable */
+ __HAL_RCC_TIM17_CLK_DISABLE();
+
+ /* TIM17 interrupt DeInit */
+ /* USER CODE BEGIN TIM17:TIM1_TRG_COM_TIM17_IRQn disable */
+ /**
+ * Uncomment the line below to disable the "TIM1_TRG_COM_TIM17_IRQn" interrupt
+ * Be aware, disabling shared interrupt may affect other IPs
+ */
+ /* HAL_NVIC_DisableIRQ(TIM1_TRG_COM_TIM17_IRQn); */
+ /* USER CODE END TIM17:TIM1_TRG_COM_TIM17_IRQn disable */
+
+ /* USER CODE BEGIN TIM17_MspDeInit 1 */
+
+ /* USER CODE END TIM17_MspDeInit 1 */
+ }
}
@@ -1001,6 +1046,9 @@ void HAL_UART_MspInit(UART_HandleTypeDef* huart)
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+ /* USART1 interrupt Init */
+ HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
+ HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
@@ -1031,6 +1079,8 @@ void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
+ /* USART1 interrupt DeInit */
+ HAL_NVIC_DisableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspDeInit 1 */
/* USER CODE END USART1_MspDeInit 1 */
diff --git a/Core/Src/stm32g4xx_it.c b/Core/Src/stm32g4xx_it.c
index a972b2a..72632e2 100644
--- a/Core/Src/stm32g4xx_it.c
+++ b/Core/Src/stm32g4xx_it.c
@@ -69,6 +69,8 @@ extern TIM_HandleTypeDef htim2;
extern TIM_HandleTypeDef htim3;
extern TIM_HandleTypeDef htim6;
extern TIM_HandleTypeDef htim15;
+extern TIM_HandleTypeDef htim17;
+extern UART_HandleTypeDef huart1;
/* USER CODE BEGIN EV */
/* USER CODE END EV */
@@ -282,6 +284,21 @@ void TIM1_BRK_TIM15_IRQHandler(void)
/* USER CODE END TIM1_BRK_TIM15_IRQn 1 */
}
+/**
+ * @brief This function handles TIM1 trigger and commutation interrupts and TIM17 global interrupt.
+ */
+void TIM1_TRG_COM_TIM17_IRQHandler(void)
+{
+ /* USER CODE BEGIN TIM1_TRG_COM_TIM17_IRQn 0 */
+
+ /* USER CODE END TIM1_TRG_COM_TIM17_IRQn 0 */
+ HAL_TIM_IRQHandler(&htim1);
+ HAL_TIM_IRQHandler(&htim17);
+ /* USER CODE BEGIN TIM1_TRG_COM_TIM17_IRQn 1 */
+
+ /* USER CODE END TIM1_TRG_COM_TIM17_IRQn 1 */
+}
+
/**
* @brief This function handles TIM1 capture compare interrupt.
*/
@@ -357,6 +374,20 @@ void TIM3_IRQHandler(void)
/* USER CODE END TIM3_IRQn 1 */
}
+/**
+ * @brief This function handles USART1 global interrupt / USART1 wake-up interrupt through EXTI line 25.
+ */
+void USART1_IRQHandler(void)
+{
+ /* USER CODE BEGIN USART1_IRQn 0 */
+
+ /* USER CODE END USART1_IRQn 0 */
+ HAL_UART_IRQHandler(&huart1);
+ /* USER CODE BEGIN USART1_IRQn 1 */
+
+ /* USER CODE END USART1_IRQn 1 */
+}
+
/**
* @brief This function handles TIM6 global interrupt, DAC1 and DAC3 channel underrun error interrupts.
*/
diff --git a/README.md b/README.md
index 831157c..bf3a69c 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,16 @@
-Powered by HERLICSON 19/11/2025
+Powered by Herlic Components 30/03/2026
-forked from 1.8.7 t06301.
-trying to make better general project without touching working one.
+Forked from 1.8.7.T06216
-this is being made for:
+This is main (general) project with git version control.
+It is called v2 because of K-Line implementation.
-soft v1 C062_2.V50
-soft v2 C062_0.P64
-Mod Index 000018
-0470504003
-mejore lo del syncout, la implementacion y poder cambiarlo por can con el watchdog.
-El arreglo esta comprobado en el T06301 y porteado aqui, asi que habra que ver si funciona.
-Last updated 15/01/2025
\ No newline at end of file
+
+
+
+
+
+
+Last updated 30/03/2026
\ No newline at end of file
diff --git a/hpsg5-controller_v2-stm32g4 Debug.launch b/hpsg5-controller_v2-stm32g4 Debug.launch
new file mode 100644
index 0000000..20cd27d
--- /dev/null
+++ b/hpsg5-controller_v2-stm32g4 Debug.launch
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hpsg5-controller_v2-stm32g4.ioc b/hpsg5-controller_v2-stm32g4.ioc
index 018b0e2..9e20296 100644
--- a/hpsg5-controller_v2-stm32g4.ioc
+++ b/hpsg5-controller_v2-stm32g4.ioc
@@ -99,7 +99,8 @@ Mcu.IP18=TIM8
Mcu.IP19=TIM15
Mcu.IP2=COMP4
Mcu.IP20=TIM16
-Mcu.IP21=USART1
+Mcu.IP21=TIM17
+Mcu.IP22=USART1
Mcu.IP3=DAC1
Mcu.IP4=DMA
Mcu.IP5=FDCAN1
@@ -107,7 +108,7 @@ Mcu.IP6=NVIC
Mcu.IP7=OPAMP1
Mcu.IP8=OPAMP2
Mcu.IP9=RCC
-Mcu.IPNb=22
+Mcu.IPNb=23
Mcu.Name=STM32G431K(6-8-B)Ux
Mcu.Package=UFQFPN32
Mcu.Pin0=PF0-OSC_IN
@@ -147,12 +148,14 @@ Mcu.Pin39=VP_TIM8_VS_no_output1
Mcu.Pin4=PA3
Mcu.Pin40=VP_TIM15_VS_ClockSourceINT
Mcu.Pin41=VP_TIM16_VS_ClockSourceINT
+Mcu.Pin42=VP_TIM17_VS_ClockSourceINT
+Mcu.Pin43=VP_TIM17_VS_OPM
Mcu.Pin5=PA4
Mcu.Pin6=PA5
Mcu.Pin7=PA6
Mcu.Pin8=PA7
Mcu.Pin9=PB0
-Mcu.PinsNb=42
+Mcu.PinsNb=44
Mcu.ThirdPartyNb=0
Mcu.UserConstants=
Mcu.UserName=STM32G431KBUx
@@ -174,9 +177,11 @@ NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false
NVIC.TIM1_BRK_TIM15_IRQn=true\:5\:0\:true\:false\:true\:true\:true\:true
NVIC.TIM1_CC_IRQn=true\:0\:0\:true\:false\:true\:true\:true\:true
+NVIC.TIM1_TRG_COM_TIM17_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true
NVIC.TIM2_IRQn=true\:1\:0\:true\:false\:true\:true\:true\:true
NVIC.TIM3_IRQn=true\:2\:0\:true\:false\:true\:true\:true\:true
NVIC.TIM6_DAC_IRQn=true\:4\:0\:true\:false\:true\:true\:true\:true
+NVIC.USART1_IRQn=true\:5\:0\:true\:false\:true\:true\:true\:true
NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
PA0.Signal=S_TIM2_CH1
PA10.Mode=Asynchronous
@@ -265,8 +270,8 @@ ProjectManager.MainLocation=Core/Src
ProjectManager.NoMain=false
ProjectManager.PreviousToolchain=STM32CubeIDE
ProjectManager.ProjectBuild=false
-ProjectManager.ProjectFileName=KBU6_1.8.7.T06216.ioc
-ProjectManager.ProjectName=KBU6_1.8.7.T06216
+ProjectManager.ProjectFileName=hpsg5-controller_v2-stm32g4.ioc
+ProjectManager.ProjectName=hpsg5-controller_v2-stm32g4
ProjectManager.ProjectStructure=
ProjectManager.RegisterCallBack=
ProjectManager.StackSize=0x400
@@ -275,7 +280,7 @@ ProjectManager.ToolChainLocation=
ProjectManager.UAScriptAfterPath=
ProjectManager.UAScriptBeforePath=
ProjectManager.UnderRoot=true
-ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_ADC1_Init-ADC1-false-HAL-true,5-MX_ADC2_Init-ADC2-false-HAL-true,6-MX_COMP4_Init-COMP4-false-HAL-true,7-MX_DAC1_Init-DAC1-false-HAL-true,8-MX_FDCAN1_Init-FDCAN1-false-HAL-true,9-MX_OPAMP1_Init-OPAMP1-false-HAL-true,10-MX_TIM2_Init-TIM2-false-HAL-true,11-MX_TIM4_Init-TIM4-false-HAL-true,12-MX_USART1_UART_Init-USART1-false-HAL-true,13-MX_TIM1_Init-TIM1-false-HAL-true,14-MX_TIM3_Init-TIM3-false-HAL-true,15-MX_TIM6_Init-TIM6-false-HAL-true,16-MX_TIM7_Init-TIM7-false-HAL-true,17-MX_TIM15_Init-TIM15-false-HAL-true,18-MX_TIM16_Init-TIM16-false-HAL-true,19-MX_RTC_Init-RTC-false-HAL-true,20-MX_TIM8_Init-TIM8-false-HAL-true,21-MX_COMP1_Init-COMP1-false-HAL-true,22-MX_OPAMP2_Init-OPAMP2-false-HAL-true
+ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_ADC1_Init-ADC1-false-HAL-true,5-MX_ADC2_Init-ADC2-false-HAL-true,6-MX_COMP4_Init-COMP4-false-HAL-true,7-MX_DAC1_Init-DAC1-false-HAL-true,8-MX_FDCAN1_Init-FDCAN1-false-HAL-true,9-MX_OPAMP1_Init-OPAMP1-false-HAL-true,10-MX_TIM2_Init-TIM2-false-HAL-true,11-MX_TIM4_Init-TIM4-false-HAL-true,12-MX_USART1_UART_Init-USART1-false-HAL-true,13-MX_TIM1_Init-TIM1-false-HAL-true,14-MX_TIM3_Init-TIM3-false-HAL-true,15-MX_TIM6_Init-TIM6-false-HAL-true,16-MX_TIM7_Init-TIM7-false-HAL-true,17-MX_TIM15_Init-TIM15-false-HAL-true,18-MX_TIM16_Init-TIM16-false-HAL-true,19-MX_RTC_Init-RTC-false-HAL-true,20-MX_TIM8_Init-TIM8-false-HAL-true,21-MX_OPAMP2_Init-OPAMP2-false-HAL-true,22-MX_TIM17_Init-TIM17-false-HAL-true
RCC.ADC12Freq_Value=160000000
RCC.AHBFreq_Value=160000000
RCC.APB1Freq_Value=160000000
@@ -339,6 +344,9 @@ TIM15.Prescaler=160-1
TIM15.TIM_MasterOutputTrigger=TIM_TRGO_UPDATE
TIM16.IPParameters=Prescaler
TIM16.Prescaler=16000-1
+TIM17.IPParameters=Prescaler,PeriodNoDither
+TIM17.PeriodNoDither=65535
+TIM17.Prescaler=160-1
TIM2.Channel-Input_Capture1_from_TI1=TIM_CHANNEL_1
TIM2.Channel-Input_Capture3_from_TI3_Remap_TIM2=TIM_CHANNEL_3
TIM2.ICFilter_CH1=15
@@ -386,6 +394,10 @@ VP_TIM15_VS_ClockSourceINT.Mode=Internal
VP_TIM15_VS_ClockSourceINT.Signal=TIM15_VS_ClockSourceINT
VP_TIM16_VS_ClockSourceINT.Mode=Enable_Timer
VP_TIM16_VS_ClockSourceINT.Signal=TIM16_VS_ClockSourceINT
+VP_TIM17_VS_ClockSourceINT.Mode=Enable_Timer
+VP_TIM17_VS_ClockSourceINT.Signal=TIM17_VS_ClockSourceINT
+VP_TIM17_VS_OPM.Mode=OPM_bit
+VP_TIM17_VS_OPM.Signal=TIM17_VS_OPM
VP_TIM1_VS_ClockSourceINT.Mode=Internal
VP_TIM1_VS_ClockSourceINT.Signal=TIM1_VS_ClockSourceINT
VP_TIM1_VS_OPM.Mode=OPM_bit