diff --git a/.cproject b/.cproject
index 00c1f43..b7ce1d0 100644
--- a/.cproject
+++ b/.cproject
@@ -25,6 +25,7 @@
+
diff --git a/Core/Inc/fuel_map.h b/Core/Inc/fuel_map.h
index 7124064..99c8c2a 100644
--- a/Core/Inc/fuel_map.h
+++ b/Core/Inc/fuel_map.h
@@ -24,20 +24,20 @@ struct AlphaStruct {
float ME_RPM_Beta_array[FM_N_RPM][FM_N_ME]; //para la transpuesta
};
extern struct AlphaStruct fuelmap_m12;
-//extern struct AlphaStruct fuelmap_m5;
+extern struct AlphaStruct fuelmap_m5;
extern struct AlphaStruct fuelmap_10;
extern struct AlphaStruct fuelmap_25;
extern struct AlphaStruct fuelmap_60;
-extern struct AlphaStruct fuelmap_80;
+//extern struct AlphaStruct fuelmap_80;
extern struct fuelMapIndexes fuelMapI;
static const struct AlphaStruct* g_FuelMaps[] = {
&fuelmap_m12,
- //&fuelmap_m5,
+ &fuelmap_m5,
&fuelmap_10,
&fuelmap_25,
&fuelmap_60,
- &fuelmap_80
+ //&fuelmap_80
// add/remove as needed; order must match fuelMapI.T_Index_array
};
diff --git a/Core/Inc/id.h b/Core/Inc/id.h
index 56e1e4e..4b8108d 100644
--- a/Core/Inc/id.h
+++ b/Core/Inc/id.h
@@ -23,25 +23,26 @@
#define CYLINDERS 4
/* TIMING COMPENSATIONS */
-#define PHI1 25.102
+#define PHI1 25.4 //25.4??
#define TEIN_NOMINAL 0
#define TEIN_FAULT 950.5
/* ALL FBKW */
#define FBKW_DEM_M 0.5
-#define FBKW_DEM_TEMP_M -0.0146
-#define FBKW_DEM_TEMP_N 2.25
-#define FBKW_DEM_A1 0.576
-#define FBKW_DEM_A2 2.2
-#define FBKW_DEM_A3 -0.636
+#define FBKW_DEM_TEMP_M -0.0138271344
+#define FBKW_DEM_TEMP_N 1.619546633
+
+#define FBKW_DEM_A1 -0.649
+#define FBKW_DEM_A2 3.86
+#define FBKW_DEM_A3 -0.795
#define FBKW_DEM_MIN 0
-#define FBKW_FEEDBACK_ZERO 7.75
-#define FBKW_FEEDBACK_MIN -3.16
-#define FBKW_FEEDBACK_MAX 17.84
+#define FBKW_FEEDBACK_ZERO 8.345
+#define FBKW_FEEDBACK_MIN -3.77
+#define FBKW_FEEDBACK_MAX 18
#define FBKW_FEEDBACK_IC_DT 27
#define FBKW_PID_KP 90 //16
@@ -68,7 +69,6 @@
#define FM_N_ME 12
#define FM_N_T 5
-
/* PEAK AND HOLD */
#define PH_PEAK_DEF 600
diff --git a/Core/Inc/id_504009.h b/Core/Inc/id_504009.h
new file mode 100644
index 0000000..5c9ab92
--- /dev/null
+++ b/Core/Inc/id_504009.h
@@ -0,0 +1,108 @@
+/*
+ * id.h
+ *
+ * Created on: Jul 28, 2025
+ * Author: herli
+ */
+
+#ifndef INC_ID_H_
+#define INC_ID_H_
+
+/* DEBUG PARAMETERS*/
+//#define T06301 //ford 004 -> 002 004 006 || 504 -> 010 018
+//#define T06215 //bmw rover 004 -> 005 014 015 016 017 || 504 -> 005 007 017 || 006 -> 001 002 003 004 007 008
+//#define T15021 //audi 506 -> 030 033
+//#define T31804 //audi 506 -> 037 038
+#define T06209 //eq: T06216
+
+/* FORD */
+#define FORD_SYNC_PULSE_OUT 0
+
+#define ENABLE_AUDI_IMMO 0
+#define HAS_PREINJECTION 0
+
+#define CYLINDERS 4
+
+/* TIMING COMPENSATIONS */
+#define PHI1 41.016
+
+#define TEIN_NOMINAL 1550 //faltaria confirmar esto
+#define TEIN_FAULT 1027
+
+/* ALL FBKW */
+#define FBKW_DEM_M 0.5
+#define FBKW_DEM_TEMP_M 0
+#define FBKW_DEM_TEMP_N -7.6
+
+//rpm comp
+#define FBKW_DEM_A1 0
+#define FBKW_DEM_A2 0
+#define FBKW_DEM_A3 0
+
+#define FBKW_DEM_MIN -6
+
+#define FBKW_FEEDBACK_ZERO 55.12
+#define FBKW_FEEDBACK_MIN -4.24
+#define FBKW_FEEDBACK_MAX 19.75
+#define FBKW_FEEDBACK_IC_DT 27
+
+#define FBKW_PID_KP 90 //16
+#define FBKW_PID_KI 0 //18
+#define FBKW_PID_KD 0
+#define FBKW_PID_KAW 0 //16
+#define FBKW_PID_BIAS 60
+#define FBKW_PID_INTEGRAL 0
+#define FBKW_PID_MAXRATE 10000
+
+#define FBKW_PWM_MAX 95
+#define FBKW_PWM_MIN 5
+
+#define FBKW_MAX 90 //en 504 parece que era 506
+#define FBKW_MAX_REAL_DEM 165 //wtf is this
+
+/* CAN DEFINITIONS */
+#define CAN_BAUDRATE 500
+#define CAN_RPM_SEND_ASYNC 250
+#define CAN_EMPF2_INSTANT 0
+
+/* ALL FUELMAP */
+#define FM_N_RPM 6
+#define FM_N_ME 10
+#define FM_N_T 5
+
+
+/* PEAK AND HOLD */
+#define PH_PEAK_DEF 600
+
+/* ANALOG CALIBRATION PARAMETERS */
+// MOSFET
+#define V_PEAK 2.94
+#define V_HOLD 1.94
+
+#define INJ_CLOSING_MARGIN 0 //o cero, o 20
+
+// TOOTHED WHEEL
+#define TW_MT_THRESHOLD 2.4
+#define TW_THEETHS 120
+#define TW_TOOTH_ALPHA 3
+#define TW_STARTED_RPM 400
+
+#define MIN_RPM 20
+
+#define USTODEG 0.000006
+// TIMINGS
+#define TEIN_READING_OFFSET 8
+
+/* ifdef things */
+
+#if CYLINDERS == 4
+ #define TW_PERCYL_TEETH 26
+#elif CYLINDERS == 6
+ #define TW_PERCYL_TEETH 16
+#else
+ #define TW_PERCYL_TEETH 26
+ #error "Unsupported number of cylinders"
+#endif
+
+
+#endif /* INC_ID_H_ */
diff --git a/Core/Kline_Libs/IKW1281Connection.c b/Core/Kline_Libs/IKW1281Connection.c
deleted file mode 100644
index 663e9e9..0000000
--- a/Core/Kline_Libs/IKW1281Connection.c
+++ /dev/null
@@ -1,276 +0,0 @@
-/* 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 SendNakPacket(){ uint8_t a = (uint8_t)PACKET_CMD_NAK; 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_ReadRam = 0x01,
- PACKET_CMD_WriteRam = 0x02,
- PACKET_CMD_ReadRomEeprom = 0x03,
- PACKET_CMD_ActuatorTest = 0x04,
- PACKET_CMD_FaultCodesDelete = 0x05,
- PACKET_CMD_End = 0x06,
- PACKET_CMD_FaultCodesRead = 0x07,
- PACKET_CMD_SingleRead = 0x08,
- 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_SingleReadResponse = 0xFB,
- 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 24 // 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 SendNakPacket(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/PacketCommandEnum.txt b/Core/Kline_Libs/PacketCommandEnum.txt
new file mode 100644
index 0000000..0f507ed
--- /dev/null
+++ b/Core/Kline_Libs/PacketCommandEnum.txt
@@ -0,0 +1,29 @@
+typedef enum {
+ PACKET_CMD_ReadIdent = 0x00,
+ PACKET_CMD_ReadRam = 0x01,
+ PACKET_CMD_WriteRam = 0x02,
+ PACKET_CMD_ReadRomEeprom = 0x03,
+ PACKET_CMD_ActuatorTest = 0x04,
+ PACKET_CMD_FaultCodesDelete = 0x05,
+ PACKET_CMD_End = 0x06,
+ PACKET_CMD_FaultCodesRead = 0x07,
+ PACKET_CMD_SingleRead = 0x08,
+ 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_EepromLoginResponse = 0xF0,
+ PACKET_CMD_ActuatorTestResponse = 0xF5,
+ PACKET_CMD_AsciiData = 0xF6,
+ PACKET_CMD_WriteEepromResponse = 0xF9,
+ PACKET_CMD_SingleReadResponse = 0xFB,
+ PACKET_CMD_FaultCodesResponse = 0xFC,
+ PACKET_CMD_ReadRomResponse = 0xFD
+} PacketCommand;
\ No newline at end of file
diff --git a/Core/Kline_Libs/ProtocolDescription.txt b/Core/Kline_Libs/ProtocolDescription.txt
new file mode 100644
index 0000000..fdfb6ba
--- /dev/null
+++ b/Core/Kline_Libs/ProtocolDescription.txt
@@ -0,0 +1,53 @@
+Communication flow description:
+
+
+1.
+Slave communication state is completely idle and rx gpio pin has exti awaiting for bitbang.
+
+2.
+When the Rx pin goes low (gpio exti), it shall start sampling at 5 bauds.
+When the byte is received, it is verified that it equals 0xf1. If verification is passed, the huart should be initialized and the 9600 baud communication halted for a delay. If the verification fails, it returns to idle/awaiting bitbang.
+
+3.
+When the 9600 baud is enabled a handshake sequence will take place. The slave sends a sync byte, waits 10.5ms, then the keyword lsb, waits 10.5ms, and the keyword msb. After the keyword msb has been sent, it should schedule another handshake sequence to start after 43 ms. The handshake should be repeated for a maximum of 5 times before failing and the communication returning to idle.
+After each time the three bytes of the handshake have been sent (sync lsb and msb), the master will have an opportunity to send an acknowledgement byte to end the handshake sequence. For advancing to the next state, the acked byte’s value should be equal to ~keyword msb.
+
+4.
+After the valid handshake has been completed, the slave should identify itself.
+The identification will be a combined ascii string of data with lengths up to 64 bytes, sent in chunks in n packets.
+These packets will respect the general packet structure and characteristics, described in the packets section.
+The packets’ title will be 0xF6 which corresponds to CMD_AsciiData.
+After each packet containing a chunk is sent, a CMD_Ack packet is expected, and when it is received, the next packet is sent. This repeats until all the data is sent. When all the data is sent and the last ACK packet is received, a final ack packet is sent and the next state is achieved.
+
+5.
+After all the initiation sequence is correctly done, the slave will be in a ready state, and the session will be alive until it reaches its deadline. The session deadline is kicked for 1000ms every time a valid packet is received.
+
+In this state the master will be sending different types of packets that the slave should receive and answer accordingly.
+
+If there are non valid packets received (unknown title or denied action), a NAK packet should be returned.
+
+
+Packet based Protocol description:
+Each packet is transmitted in the form:
+[LENGTH] [COUNTER] [COMMAND] [DATA_0 ... DATA_N] [0x03]
+
+The LENGTH byte defines the size of the logical packet excluding the end byte 0x03, and is calculated as the number of payload bytes plus two additional bytes for COUNTER and COMMAND. The COUNTER is an incrementing sequence byte used to keep synchronization between slave and master. The COMMAND defines the requested service or response type, and the DATA field is variable length depending on the service. The byte 0x03 is always used as packet terminator.
+
+It may not need clarification, but a packet can be sent from either master or slave.
+
+A key aspect of the protocol is the byte-by-byte acknowledge mechanism. Every byte transmitted by one side must be immediately acknowledged by the other side with its bitwise complement. In other words, for any received byte B, the acknowledge byte is ~B. For example, if the slave sends 0x10, the master responds with 0xEF. This acknowledge is required for LENGTH, COUNTER, COMMAND bytes and every data byte, except the important packet terminator byte.
+
+Once a complete packet has been received and all bytes (except the packet terminator one) have been individually acknowledged, the receiving side processes the command and sends a packet-level response. This response is distinct from the per-byte complements and represents the application-level protocol behavior. The response packet itself follows exactly the same framing and byte acknowledge rules as the original request.
+
+
+Implemented packets are:
+CMD_ReadIdent -> Triggers a repeat of the ascii packets identification sequence.
+CMD_WritesRam -> Returns variable values that are mapped to valid address requests. If valid returns ACK, else fails with NAK.
+CMD_FaultCodesRead -> Triggers a chunk sending sequence of a dtc built stream awaiting ack packets between each packet containing a chunk and sends a final ack packet. It’s similar to the identification chunk sending.
+CMD_LoginEeprom -> Compares login payload and if valid, unlocks implemented zones with a flag. If valid returns PACKET_CMD_EepromLoginResponse and awaits ACK, else fails with NAK.
+CMD_WriteEeprom -> Checks requested write address and length is valid and unlocked. If valid returns CMD_WriteEepromResponse and awaits ACK, else fails with NAK.
+CMD_ReadRom -> Checks requested read address and length is valid. If valid returns CMD_ReadRomResponse with the corresponding data as payload and awaits ack.
+CMD_End -> Sends ack packet and terminates the session, returning to idle.
+
+Special consideration:
+As the byte by byte protocol is implemented in an embedded solution with a kline transceiver with the simpler huart setup, each transmitted byte is echoed through the rx and received instantly. This echo is the same value as the last sent byte and is received instantly upon transmission, and should not be confused with the acknowledged byte sent by the master, and should be discarded correctly. A simple comparison between the last transmitted byte and the currently received one is not enough as there are packets that start with length 0x03, and the echo detection would suppress this byte when comparing it when the last transmitted one (0x03 packet terminator). A better driver implementation strategy is expected.
diff --git a/Core/Kline_Libs/kl_api.c b/Core/Kline_Libs/kl_api.c
new file mode 100644
index 0000000..138f18f
--- /dev/null
+++ b/Core/Kline_Libs/kl_api.c
@@ -0,0 +1,30 @@
+/*
+ * kl_api.c
+ * Top-level orchestration: init and service all K-Line layers.
+ */
+
+#include "kl_api.h"
+#include "kl_phy.h"
+#include "kl_transport.h"
+#include "kl_session.h"
+#include "kl_app.h"
+
+void KLine_Init(void)
+{
+ KL_App_Init();
+ KL_Transport_Init();
+ KL_Session_Init(); /* Calls KL_Phy_Init internally, arms EXTI */
+}
+
+void KLine_Service(void)
+{
+ KL_Phy_Service();
+ KL_Transport_Service();
+ KL_App_Service();
+ KL_Session_Service();
+}
+
+uint8_t KLine_GetStatus(void)
+{
+ return KL_Session_GetStatus();
+}
diff --git a/Core/Kline_Libs/kl_api.h b/Core/Kline_Libs/kl_api.h
new file mode 100644
index 0000000..5fc32da
--- /dev/null
+++ b/Core/Kline_Libs/kl_api.h
@@ -0,0 +1,38 @@
+/*
+ * kl_api.h
+ * Public entry points for the K-Line protocol stack.
+ *
+ * Integration:
+ * - Call KLine_Init() once after HAL peripheral init
+ * - Call KLine_Service() from the main loop (frequently, <3ms)
+ * - Route ISR callbacks as described below
+ *
+ * ISR wiring (add to your existing HAL callbacks):
+ *
+ * void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
+ * if (huart->Instance == USART1) KL_Phy_TxCpltCB();
+ * }
+ *
+ * void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
+ * if (huart->Instance == USART1) KL_Phy_RxCpltCB();
+ * }
+ *
+ * void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
+ * if (GPIO_Pin == KL_RX_PIN) KL_Session_OnExtiRxFalling();
+ * }
+ *
+ * void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
+ * if (htim->Instance == TIM17) KL_Session_OnTim17Elapsed();
+ * }
+ */
+
+#ifndef KL_API_H
+#define KL_API_H
+
+#include
+
+void KLine_Init(void);
+void KLine_Service(void);
+uint8_t KLine_GetStatus(void); /* 0=idle, 1=connecting, 2=active */
+
+#endif /* KL_API_H */
diff --git a/Core/Kline_Libs/kl_app.c b/Core/Kline_Libs/kl_app.c
new file mode 100644
index 0000000..a18e8a5
--- /dev/null
+++ b/Core/Kline_Libs/kl_app.c
@@ -0,0 +1,537 @@
+/*
+ * kl_app.c
+ * Application layer: command dispatch, handlers, generic stream sender.
+ *
+ * All handlers are non-blocking. They prepare response payloads and
+ * start TX via the transport layer. KL_App_Service() drives the
+ * multi-step response state machines.
+ */
+
+#include "kl_app.h"
+#include "kl_transport.h"
+#include "kl_session.h"
+#include "psg_prop.h"
+#include "ee_manager.h"
+#include
+
+/* s_dfi_code: shared with ee_manager via extern in ee_manager.h */
+int8_t s_dfi_code = 0;
+
+/* ================================================================
+ * Application context
+ * ================================================================ */
+static struct {
+ KL_AppState state;
+
+ /* Single-response payload */
+ uint8_t resp_payload[KL_MAX_PACKET_PAYLOAD];
+ uint8_t resp_len;
+
+ /* Stream sender */
+ uint8_t stream_buf[KL_STREAM_BUF_SIZE];
+ uint8_t stream_len;
+ uint8_t stream_offset;
+ uint8_t stream_cmd; /* 0xF6 for ASCII, 0xFC for DTC */
+ uint8_t stream_chunk_size; /* 13 for ASCII, 15 for DTC */
+ uint8_t stream_cur_chunk; /* Current chunk size being sent */
+
+ /* EEPROM/ROM unlock flags */
+ uint8_t eeprom_unlocked_dfi;
+ uint8_t eeprom_unlocked_dfi_write;
+ uint8_t rom_unlocked_cust;
+} app;
+
+/* ================================================================
+ * DTC storage
+ * ================================================================ */
+static KL_FaultCode s_dtc_list[KL_DTC_TOTAL_RECORDS];
+static size_t s_dtc_count = 0;
+
+/* Default test DTCs */
+static const KL_FaultCode s_default_dtcs[] = {
+ { 0x5B, 0x20, 0x55 },
+ { 0x50, 0x20, 0x03 },
+ { 0x56, 0x20, 0x56 },
+ { 0x5E, 0x30, 0x60 },
+};
+
+/* ================================================================
+ * Password / login bodies (from old kline.c)
+ * ================================================================ */
+static const uint8_t kPwdBody_DFI[] = { 0x00, 0x03, 0xFF, 0xFF };
+static const uint8_t kLoginBody_DFI[] = { 0x00, 0x00, 0x00, 0xBF, 0x30, 0x35, 0x30, 0x30, 0x30, 0x31, 0x1C, 0x09, 0x04 };
+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, 0x30, 0x35, 0x30, 0x30, 0x30, 0x31, 0x1C, 0x09, 0x04 };
+static const uint8_t kPwdBody_OEM_1[] = { 0x00, 0x02, 0x2E, 0x10 };
+static const uint8_t kLoginBody_OEM_1[] = { 0x9F, 0xD0, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+
+static const uint8_t kReadIdentAddrBody[] = { 0x02, 0x00, 0xC6 };
+static const uint8_t kReadFahrSoftwareAddress[] = { 0x02, 0x00, 0xA4 };
+static const uint8_t kReadStart55Address[] = { 0x01, 0x00, 0xAE };
+
+static const uint8_t kActivateSyncOut[] = { 0x88, 0x01, 0x04, 0x06, 0x01 };
+
+/* ================================================================
+ * Init / Reset
+ * ================================================================ */
+void KL_App_Init(void)
+{
+ memset(&app, 0, sizeof(app));
+ app.state = KL_APP_IDLE;
+
+ KL_App_SetDTCList(s_default_dtcs, sizeof(s_default_dtcs) / sizeof(s_default_dtcs[0]));
+}
+
+void KL_App_ResetLocks(void)
+{
+ app.eeprom_unlocked_dfi = 0;
+ app.eeprom_unlocked_dfi_write = 0;
+ app.rom_unlocked_cust = 0;
+}
+
+int KL_App_Busy(void)
+{
+ return (app.state != KL_APP_IDLE && app.state != KL_APP_DONE) ? 1 : 0;
+}
+
+int KL_App_Done(void)
+{
+ return (app.state == KL_APP_DONE || app.state == KL_APP_IDLE) ? 1 : 0;
+}
+
+/* ================================================================
+ * DTC management
+ * ================================================================ */
+void KL_App_SetDTCList(const KL_FaultCode *list, size_t count)
+{
+ if (!list) { s_dtc_count = 0; return; }
+ if (count > KL_DTC_TOTAL_RECORDS) count = KL_DTC_TOTAL_RECORDS;
+ for (size_t i = 0; i < count; ++i) s_dtc_list[i] = list[i];
+ s_dtc_count = count;
+}
+
+/* ================================================================
+ * DTC stream builder
+ * ================================================================ */
+static size_t build_dtc_stream(uint8_t *out)
+{
+ size_t w = 0;
+ for (size_t i = 0; i < KL_DTC_TOTAL_RECORDS; ++i) {
+ if (i < s_dtc_count) {
+ out[w++] = s_dtc_list[i].dtc;
+ out[w++] = s_dtc_list[i].status;
+ out[w++] = s_dtc_list[i].extra;
+ } else {
+ out[w++] = 0x00;
+ out[w++] = 0xFF;
+ out[w++] = 0xFF;
+ }
+ /* Pad to 8-byte record */
+ for (int j = 0; j < 5; j++) out[w++] = 0xFF;
+ }
+ return w; /* 64 */
+}
+
+/* ================================================================
+ * Ident stream builder (for ReadIdent command)
+ * ================================================================ */
+static size_t app_build_ident(uint8_t *out, size_t maxlen)
+{
+ size_t w = 0;
+ const char *strs[] = {
+ PSG_KUNDENNUMMER_STR, PSG_DATENSATZ_STR,
+ PSG_SOFTWARE_VER_STR, PSG_SOFTWARE_VER2_STR,
+ PSG_STEUERGERAET_STR, PSG_UNKNOWN_STR
+ };
+ const size_t lens[] = { 12, 10, 10, 10, 10, 6 };
+ for (int s = 0; s < 6; s++) {
+ for (size_t i = 0; i < lens[s] && strs[s][i] != '\0' && w < maxlen; i++) {
+ out[w++] = (uint8_t)strs[s][i];
+ }
+ }
+ return w;
+}
+
+/* ================================================================
+ * Helper: start a single-response send
+ * ================================================================ */
+static void start_response(const uint8_t *payload, uint8_t len)
+{
+ memcpy(app.resp_payload, payload, len);
+ app.resp_len = len;
+ KL_Transport_SendPacket(app.resp_payload, app.resp_len);
+ app.state = KL_APP_RESP_WAIT_TX;
+}
+
+/* ================================================================
+ * Helper: start a stream send
+ * ================================================================ */
+static void start_stream(uint8_t cmd, uint8_t chunk_size, const uint8_t *data, uint8_t len)
+{
+ memcpy(app.stream_buf, data, len);
+ app.stream_len = len;
+ app.stream_offset = 0;
+ app.stream_cmd = cmd;
+ app.stream_chunk_size = chunk_size;
+ app.state = KL_APP_STREAM_SEND;
+}
+
+/* ================================================================
+ * Command handlers
+ * ================================================================ */
+static void handle_ack(void)
+{
+ KL_Transport_SendAck();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+}
+
+static void handle_read_ident(void)
+{
+ uint8_t buf[KL_STREAM_BUF_SIZE];
+ size_t n = app_build_ident(buf, sizeof(buf));
+ start_stream((uint8_t)KL_CMD_AsciiData, KL_ASCII_CHUNK_SIZE, buf, (uint8_t)n);
+}
+
+static void handle_fault_codes_read(void)
+{
+ uint8_t buf[KL_DTC_TOTAL_RECORDS * KL_DTC_RECORD_SIZE];
+ size_t n = build_dtc_stream(buf);
+ start_stream((uint8_t)KL_CMD_FaultCodesResponse, KL_DTC_CHUNK_SIZE, buf, (uint8_t)n);
+}
+
+static void handle_read_eeprom(const KL_Packet *cmd)
+{
+ if (cmd->data_len < 3U) { KL_Transport_SendNak(); app.state = KL_APP_SIMPLE_WAIT_TX; return; }
+
+ uint8_t len_req = cmd->data[0];
+ uint16_t addr = ((uint16_t)cmd->data[1] << 8) | cmd->data[2];
+
+ if (addr == 0x0044 && !app.eeprom_unlocked_dfi) {
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ return;
+ }
+
+ uint8_t data[16] = {0};
+ if (!PSG_EEPROM_GetData(addr, len_req, data)) {
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ return;
+ }
+
+ uint8_t payload[1 + 16];
+ payload[0] = (uint8_t)KL_CMD_ReadEepromResponse;
+ memcpy(&payload[1], data, len_req);
+ start_response(payload, (uint8_t)(1U + len_req));
+}
+
+static void handle_read_rom_eeprom(const KL_Packet *cmd)
+{
+ if (cmd->data_len < 3U) { KL_Transport_SendNak(); app.state = KL_APP_SIMPLE_WAIT_TX; return; }
+
+ uint8_t len_req = cmd->data[0];
+ uint16_t addr = ((uint16_t)cmd->data[1] << 8) | cmd->data[2];
+
+ if ((addr == 0x9FFE || addr == (uint16_t)(PSG_CUST_CHANGE_ADDR + 3U)) && !app.rom_unlocked_cust) {
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ return;
+ }
+
+ uint8_t data[10];
+ if (!PSG_ROM_GetData(addr, len_req, data)) {
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ return;
+ }
+
+ uint8_t payload[1 + 10];
+ payload[0] = (uint8_t)KL_CMD_ReadRomResponse;
+ memcpy(&payload[1], data, len_req);
+ start_response(payload, (uint8_t)(1U + len_req));
+}
+
+static void handle_login_eeprom(const KL_Packet *cmd)
+{
+ const uint8_t *body = cmd->data;
+ uint8_t body_len = cmd->data_len;
+
+ const uint8_t *login_body = NULL;
+ size_t login_len = 0;
+
+ if (body_len == sizeof(kPwdBody_DFI) && memcmp(body, kPwdBody_DFI, sizeof(kPwdBody_DFI)) == 0) {
+ app.eeprom_unlocked_dfi = 1;
+ login_body = kLoginBody_DFI;
+ login_len = sizeof(kLoginBody_DFI);
+ } else if (body_len == sizeof(kPwdBody_CUST) && memcmp(body, kPwdBody_CUST, sizeof(kPwdBody_CUST)) == 0) {
+ app.rom_unlocked_cust = 1;
+ login_body = kLoginBody_CUST;
+ login_len = sizeof(kLoginBody_CUST);
+ } else if (body_len == sizeof(kPwdBody_OEM_1) && memcmp(body, kPwdBody_OEM_1, sizeof(kPwdBody_OEM_1)) == 0) {
+ login_body = kLoginBody_OEM_1;
+ login_len = sizeof(kLoginBody_OEM_1);
+ } else if (body_len == sizeof(kPwdBody_DFI_WRITE) && memcmp(body, kPwdBody_DFI_WRITE, sizeof(kPwdBody_DFI_WRITE)) == 0) {
+ app.eeprom_unlocked_dfi_write = 1;
+ login_body = kLoginBody_DFI;
+ login_len = sizeof(kLoginBody_DFI);
+ } else {
+ /* Wrong password -- clear all locks */
+ app.eeprom_unlocked_dfi = 0;
+ app.eeprom_unlocked_dfi_write = 0;
+ app.rom_unlocked_cust = 0;
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ return;
+ }
+
+ uint8_t payload[1 + 16];
+ payload[0] = (uint8_t)KL_CMD_EepromLoginResponse;
+ memcpy(&payload[1], login_body, login_len);
+ start_response(payload, (uint8_t)(1U + login_len));
+}
+
+static void handle_write_eeprom(const KL_Packet *cmd)
+{
+ if (cmd->data_len < 5U) { KL_Transport_SendNak(); app.state = KL_APP_SIMPLE_WAIT_TX; return; }
+
+ uint8_t len_req = cmd->data[0];
+ uint16_t addr = ((uint16_t)cmd->data[1] << 8) | cmd->data[2];
+ uint8_t value = cmd->data[3];
+ uint8_t csum = cmd->data[4];
+
+ if (!app.eeprom_unlocked_dfi_write) {
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ return;
+ }
+
+ if (addr != 0x0044 || len_req != 0x02) {
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ return;
+ }
+
+ if ((uint8_t)(value + csum) != 0) {
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ return;
+ }
+
+ s_dfi_code = (int8_t)value;
+ memWrite = 1;
+
+ uint8_t payload[1] = { (uint8_t)KL_CMD_WriteEepromResponse };
+ start_response(payload, 1);
+}
+
+uint8_t SYNC_PULSE_OUT;
+
+static void handle_read_ram(const KL_Packet *cmd)
+{
+ if (cmd->data_len >= sizeof(kReadIdentAddrBody) &&
+ memcmp(cmd->data, kReadIdentAddrBody, sizeof(kReadIdentAddrBody)) == 0) {
+ uint8_t payload[3];
+ payload[0] = 0xFE;
+ payload[1] = (uint8_t)(PSG_IDENT_ADDR & 0xFF);
+ payload[2] = (uint8_t)((PSG_IDENT_ADDR >> 8) & 0xFF);
+ start_response(payload, 3);
+ return;
+ }
+ if (cmd->data_len >= sizeof(kReadFahrSoftwareAddress) &&
+ memcmp(cmd->data, kReadFahrSoftwareAddress, sizeof(kReadFahrSoftwareAddress)) == 0) {
+ uint8_t payload[3];
+ payload[0] = 0xFE;
+ payload[1] = (uint8_t)(PSG_FAHRSOFTWARE_ADDR & 0xFF);
+ payload[2] = (uint8_t)((PSG_FAHRSOFTWARE_ADDR >> 8) & 0xFF);
+ start_response(payload, 3);
+ return;
+ }
+ if (cmd->data_len >= sizeof(kReadStart55Address) &&
+ memcmp(cmd->data, kReadStart55Address, sizeof(kReadStart55Address)) == 0) {
+ uint8_t payload[2];
+ payload[0] = 0xFE;
+ payload[1] = (uint8_t)(PSG_START55_ADDR);
+ start_response(payload, 2);
+ return;
+ }
+
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+}
+
+static void handle_write_ram(const KL_Packet *cmd)
+{
+ if (cmd->data_len >= sizeof(kActivateSyncOut) &&
+ memcmp(cmd->data, kActivateSyncOut, sizeof(kActivateSyncOut)) == 0) {
+ //SYNC_PULSE_OUT = 1;
+ SYNC_Pulse_RequestSyncout(1);
+ KL_Transport_SendAck();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ return;
+ }
+
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+}
+
+static void handle_end(void)
+{
+ KL_Transport_SendAck();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ /* Session layer will check end_requested after we're done */
+ KL_Session_RequestEnd();
+}
+
+/* ================================================================
+ * Command dispatch
+ * ================================================================ */
+void KL_App_Dispatch(const KL_Packet *cmd)
+{
+ app.state = KL_APP_IDLE;
+ KL_Transport_TxReset();
+
+ switch (cmd->command) {
+ case KL_CMD_ACK:
+ handle_ack();
+ break;
+ case KL_CMD_ReadIdent:
+ handle_read_ident();
+ break;
+ case KL_CMD_FaultCodesRead:
+ handle_fault_codes_read();
+ break;
+ case KL_CMD_ReadEeprom:
+ handle_read_eeprom(cmd);
+ break;
+ case KL_CMD_ReadRomEeprom:
+ handle_read_rom_eeprom(cmd);
+ break;
+ case KL_CMD_LoginEeprom:
+ handle_login_eeprom(cmd);
+ break;
+ case KL_CMD_WriteEeprom:
+ handle_write_eeprom(cmd);
+ break;
+ case KL_CMD_ReadRam:
+ handle_read_ram(cmd);
+ break;
+ case KL_CMD_WriteRam:
+ handle_write_ram(cmd);
+ break;
+ case KL_CMD_End:
+ handle_end();
+ break;
+ default:
+ KL_Transport_SendNak();
+ app.state = KL_APP_SIMPLE_WAIT_TX;
+ break;
+ }
+}
+
+/* ================================================================
+ * Service: drive response state machines
+ * ================================================================ */
+void KL_App_Service(void)
+{
+ switch (app.state) {
+
+ case KL_APP_IDLE:
+ case KL_APP_DONE:
+ break;
+
+ /* ---- Simple send (ACK/NAK, no follow-up ACK exchange) ---- */
+ case KL_APP_SIMPLE_WAIT_TX:
+ if (KL_Transport_TxDone()) {
+ KL_Transport_TxReset();
+ app.state = KL_APP_DONE;
+ } else if (KL_Transport_TxError()) {
+ KL_Transport_TxReset();
+ app.state = KL_APP_DONE;
+ }
+ break;
+
+ /* ---- Single response: send and return to ready ---- */
+ case KL_APP_RESP_WAIT_TX:
+ if (KL_Transport_TxDone()) {
+ KL_Transport_TxReset();
+ KL_Session_Kick();
+ app.state = KL_APP_DONE;
+ } else if (KL_Transport_TxError()) {
+ KL_Transport_TxReset();
+ app.state = KL_APP_DONE;
+ }
+ break;
+
+ /* ---- Stream: send chunks -> wait ACK each -> final ACK ---- */
+ case KL_APP_STREAM_SEND:
+ {
+ uint8_t remaining = app.stream_len - app.stream_offset;
+ app.stream_cur_chunk = (remaining > app.stream_chunk_size)
+ ? app.stream_chunk_size : remaining;
+
+ uint8_t payload[1 + KL_DTC_CHUNK_SIZE]; /* max chunk size */
+ payload[0] = app.stream_cmd;
+ memcpy(&payload[1], &app.stream_buf[app.stream_offset], app.stream_cur_chunk);
+
+ if (KL_Transport_SendPacket(payload, (uint8_t)(1 + app.stream_cur_chunk))) {
+ app.state = KL_APP_STREAM_WAIT_TX;
+ }
+ break;
+ }
+
+ case KL_APP_STREAM_WAIT_TX:
+ if (KL_Transport_TxDone()) {
+ KL_Transport_TxReset();
+ app.stream_offset += app.stream_cur_chunk;
+ if (app.stream_offset >= app.stream_len) {
+ /* Last chunk sent -- return to ready state.
+ * No ACK is expected after the final chunk.
+ * The master's next packet (keepalive ACK or command)
+ * will be handled normally by KL_App_Dispatch. */
+ KL_Session_Kick();
+ app.state = KL_APP_DONE;
+ } else {
+ /* Intermediate chunk: wait for ACK before sending next */
+ KL_Transport_StartReceive(400);
+ app.state = KL_APP_STREAM_WAIT_ACK;
+ }
+ } else if (KL_Transport_TxError()) {
+ KL_Transport_TxReset();
+ app.state = KL_APP_DONE;
+ }
+ break;
+
+ case KL_APP_STREAM_WAIT_ACK:
+ if (KL_Transport_RxDone()) {
+ const KL_Packet *ack = KL_Transport_GetRxPacket();
+ KL_Transport_RxReset();
+ if (ack->command == (uint8_t)KL_CMD_ACK) {
+ KL_Session_Kick();
+ app.state = KL_APP_STREAM_SEND;
+ } else {
+ app.state = KL_APP_DONE;
+ }
+ } else if (KL_Transport_RxError()) {
+ KL_Transport_RxReset();
+ app.state = KL_APP_DONE;
+ }
+ break;
+
+ case KL_APP_STREAM_FINAL_WAIT:
+ /* No longer used, but kept for safety */
+ if (KL_Transport_TxDone()) {
+ KL_Transport_TxReset();
+ KL_Session_Kick();
+ app.state = KL_APP_DONE;
+ } else if (KL_Transport_TxError()) {
+ KL_Transport_TxReset();
+ app.state = KL_APP_DONE;
+ }
+ break;
+
+ default:
+ app.state = KL_APP_DONE;
+ break;
+ }
+}
diff --git a/Core/Kline_Libs/kl_app.h b/Core/Kline_Libs/kl_app.h
new file mode 100644
index 0000000..463741e
--- /dev/null
+++ b/Core/Kline_Libs/kl_app.h
@@ -0,0 +1,59 @@
+/*
+ * kl_app.h
+ * Application layer: command dispatch, handlers, stream sender.
+ */
+
+#ifndef KL_APP_H
+#define KL_APP_H
+
+#include "kl_protocol.h"
+
+/* ================================================================
+ * Application states
+ * ================================================================ */
+typedef enum {
+ KL_APP_IDLE,
+
+ /* Single response: send response -> wait ACK -> send final ACK */
+ KL_APP_RESP_TX,
+ KL_APP_RESP_WAIT_TX,
+ KL_APP_RESP_WAIT_ACK,
+ KL_APP_RESP_FINAL_ACK,
+ KL_APP_RESP_FINAL_WAIT,
+
+ /* Stream response: send chunks -> wait ACK each -> final ACK */
+ KL_APP_STREAM_SEND,
+ KL_APP_STREAM_WAIT_TX,
+ KL_APP_STREAM_WAIT_ACK,
+ KL_APP_STREAM_FINAL_ACK,
+ KL_APP_STREAM_FINAL_WAIT,
+
+ /* Simple send (ACK/NAK with no follow-up) */
+ KL_APP_SIMPLE_WAIT_TX,
+
+ KL_APP_DONE
+} KL_AppState;
+
+/* ================================================================
+ * Fault code structure
+ * ================================================================ */
+typedef struct {
+ uint8_t dtc;
+ uint8_t status;
+ uint8_t extra;
+} KL_FaultCode;
+
+/* ================================================================
+ * API
+ * ================================================================ */
+void KL_App_Init(void);
+void KL_App_Service(void);
+void KL_App_Dispatch(const KL_Packet *cmd);
+int KL_App_Busy(void);
+int KL_App_Done(void);
+void KL_App_ResetLocks(void);
+
+/* DTC list management */
+void KL_App_SetDTCList(const KL_FaultCode *list, size_t count);
+
+#endif /* KL_APP_H */
diff --git a/Core/Kline_Libs/kl_phy.c b/Core/Kline_Libs/kl_phy.c
new file mode 100644
index 0000000..8f41151
--- /dev/null
+++ b/Core/Kline_Libs/kl_phy.c
@@ -0,0 +1,162 @@
+/*
+ * kl_phy.c
+ * Physical layer: UART driver, state-based echo suppression, RX FIFO.
+ */
+
+#include "kl_phy.h"
+#include "main.h"
+#include
+
+extern UART_HandleTypeDef huart1;
+
+static KL_Phy phy;
+
+/* ================================================================
+ * RX FIFO (single-producer ISR, single-consumer main loop)
+ * ================================================================ */
+static void rx_fifo_push(uint8_t b)
+{
+ uint16_t next = (uint16_t)((phy.rx_head + 1U) % KL_RX_FIFO_SIZE);
+ if (next == phy.rx_tail) {
+ /* Overflow: drop oldest */
+ phy.rx_tail = (uint16_t)((phy.rx_tail + 1U) % KL_RX_FIFO_SIZE);
+ }
+ phy.rx_fifo[phy.rx_head] = b;
+ phy.rx_head = next;
+}
+
+int KL_Phy_RxPop(uint8_t *out)
+{
+ if (phy.rx_head == phy.rx_tail) return 0;
+ if (out) *out = phy.rx_fifo[phy.rx_tail];
+ phy.rx_tail = (uint16_t)((phy.rx_tail + 1U) % KL_RX_FIFO_SIZE);
+ return 1;
+}
+
+int KL_Phy_RxAvailable(void)
+{
+ return (phy.rx_head != phy.rx_tail) ? 1 : 0;
+}
+
+void KL_Phy_RxFlush(void)
+{
+ phy.rx_tail = phy.rx_head;
+}
+
+/* ================================================================
+ * GPIO pin configuration helpers
+ * ================================================================ */
+static void phy_set_pins_uart_af(void)
+{
+ GPIO_InitTypeDef gi = {0};
+ __HAL_RCC_GPIOA_CLK_ENABLE();
+
+ gi.Pin = KL_TX_PIN | KL_RX_PIN;
+ gi.Mode = GPIO_MODE_AF_PP;
+ gi.Pull = GPIO_PULLUP;
+ gi.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
+ gi.Alternate = GPIO_AF7_USART1;
+ HAL_GPIO_Init(KL_GPIO_PORT, &gi);
+}
+
+/* ================================================================
+ * Init / Mode switching
+ * ================================================================ */
+void KL_Phy_Init(void)
+{
+ memset(&phy, 0, sizeof(phy));
+ phy.tx_state = KL_PHY_TX_IDLE;
+ HAL_UART_DeInit(&huart1);
+}
+
+void KL_Phy_EnableUart9600(void)
+{
+ HAL_UART_DeInit(&huart1);
+ phy_set_pins_uart_af();
+
+ 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();
+ }
+
+ KL_Phy_RxFlush();
+ phy.tx_state = KL_PHY_TX_IDLE;
+ phy.echo_pending = 0;
+
+ HAL_UART_Receive_IT(&huart1, phy.rx_it_buf, 1);
+}
+
+void KL_Phy_DisableUart(void)
+{
+ HAL_UART_DeInit(&huart1);
+ phy.tx_state = KL_PHY_TX_IDLE;
+ phy.echo_pending = 0;
+}
+
+/* ================================================================
+ * Non-blocking single-byte TX
+ * ================================================================ */
+int KL_Phy_TxByte(uint8_t byte)
+{
+ if (phy.tx_state != KL_PHY_TX_IDLE) return 0;
+
+ phy.tx_byte = byte;
+ phy.last_tx_byte = byte;
+ phy.echo_pending = 1;
+ phy.tx_state = KL_PHY_TX_SENDING;
+
+ if (HAL_UART_Transmit_IT(&huart1, &phy.tx_byte, 1) != HAL_OK) {
+ phy.tx_state = KL_PHY_TX_IDLE;
+ phy.echo_pending = 0;
+ return 0;
+ }
+ return 1;
+}
+
+int KL_Phy_TxBusy(void)
+{
+ return (phy.tx_state != KL_PHY_TX_IDLE) ? 1 : 0;
+}
+
+/* ================================================================
+ * Service: echo-timeout recovery
+ * ================================================================ */
+void KL_Phy_Service(void)
+{
+ if (phy.tx_state == KL_PHY_TX_ECHO_WAIT) {
+ if (kl_tick_diff(HAL_GetTick(), phy.tx_done_tick) >= (int32_t)KL_ECHO_TMO_MS) {
+ phy.tx_state = KL_PHY_TX_IDLE;
+ phy.echo_pending = 0;
+ }
+ }
+}
+
+/* ================================================================
+ * ISR callbacks
+ * ================================================================ */
+void KL_Phy_TxCpltCB(void)
+{
+ phy.tx_done_tick = HAL_GetTick();
+ phy.tx_state = KL_PHY_TX_ECHO_WAIT;
+}
+
+void KL_Phy_RxCpltCB(void)
+{
+ uint8_t byte = phy.rx_it_buf[0];
+
+ if (phy.echo_pending && byte == phy.last_tx_byte) {
+ phy.echo_pending = 0;
+ phy.tx_state = KL_PHY_TX_IDLE;
+ } else {
+ rx_fifo_push(byte);
+ }
+
+ HAL_UART_Receive_IT(&huart1, phy.rx_it_buf, 1);
+}
diff --git a/Core/Kline_Libs/kl_phy.h b/Core/Kline_Libs/kl_phy.h
new file mode 100644
index 0000000..88b7c3f
--- /dev/null
+++ b/Core/Kline_Libs/kl_phy.h
@@ -0,0 +1,56 @@
+/*
+ * kl_phy.h
+ * Physical layer: UART driver, state-based echo suppression, RX FIFO.
+ */
+
+#ifndef KL_PHY_H
+#define KL_PHY_H
+
+#include "kl_protocol.h"
+
+/* ================================================================
+ * TX state
+ * ================================================================ */
+typedef enum {
+ KL_PHY_TX_IDLE, /* No TX in progress */
+ KL_PHY_TX_SENDING, /* HAL_UART_Transmit_IT issued */
+ KL_PHY_TX_ECHO_WAIT /* TxCplt fired, awaiting self-echo */
+} KL_PhyTxState;
+
+/* ================================================================
+ * Physical layer context
+ * ================================================================ */
+typedef struct {
+ volatile KL_PhyTxState tx_state;
+ uint8_t tx_byte; /* Byte buffer for Transmit_IT */
+ uint8_t last_tx_byte; /* For echo matching */
+ volatile uint32_t tx_done_tick; /* Tick when TxCplt fired */
+ volatile uint8_t echo_pending; /* Expecting self-echo on RX */
+
+ uint8_t rx_fifo[KL_RX_FIFO_SIZE];
+ volatile uint16_t rx_head; /* Written by ISR */
+ volatile uint16_t rx_tail; /* Read by main loop */
+
+ uint8_t rx_it_buf[1]; /* HAL_UART_Receive_IT buffer */
+} KL_Phy;
+
+/* ================================================================
+ * API
+ * ================================================================ */
+void KL_Phy_Init(void);
+void KL_Phy_Service(void); /* Echo-timeout recovery */
+void KL_Phy_EnableUart9600(void);
+void KL_Phy_DisableUart(void);
+
+int KL_Phy_TxByte(uint8_t byte); /* Returns 1 accepted, 0 busy */
+int KL_Phy_TxBusy(void); /* Includes echo wait */
+
+int KL_Phy_RxPop(uint8_t *out); /* Returns 1 if byte available */
+int KL_Phy_RxAvailable(void);
+void KL_Phy_RxFlush(void);
+
+/* ISR callbacks -- call from HAL callbacks */
+void KL_Phy_TxCpltCB(void);
+void KL_Phy_RxCpltCB(void);
+
+#endif /* KL_PHY_H */
diff --git a/Core/Kline_Libs/kl_protocol.h b/Core/Kline_Libs/kl_protocol.h
new file mode 100644
index 0000000..acb2b92
--- /dev/null
+++ b/Core/Kline_Libs/kl_protocol.h
@@ -0,0 +1,118 @@
+/*
+ * kl_protocol.h
+ * Shared constants, enums, and types for the K-Line protocol stack.
+ */
+
+#ifndef KL_PROTOCOL_H
+#define KL_PROTOCOL_H
+
+#include
+#include
+
+/* ================================================================
+ * Hardware pin mapping (USART1 on PA9/PA10)
+ * ================================================================ */
+#define KL_RX_PIN GPIO_PIN_10
+#define KL_TX_PIN GPIO_PIN_9
+#define KL_GPIO_PORT GPIOA
+
+/* ================================================================
+ * Protocol constants
+ * ================================================================ */
+#define KL_SLAVE_ADDR 0xF1
+#define KL_SYNC_BYTE 0x55
+#define KL_KEYWORD_LSB 0x8C
+#define KL_KEYWORD_MSB 0x51
+#define KL_PACKET_END 0x03
+
+/* ================================================================
+ * Timing (milliseconds)
+ * ================================================================ */
+#define KL_5BAUD_BIT_MS 200U /* 5-baud bit period */
+#define KL_5BAUD_FIRST_MS 300U /* 1.5 bit times for first sample */
+#define KL_W1_MS 200U /* Post-5baud delay before sync */
+#define KL_W2_MS 11U /* Sync to keyword LSB */
+#define KL_W3_MS 11U /* Keyword LSB to MSB */
+#define KL_HS_RETRY_MS 43U /* Between handshake retries */
+#define KL_P1_GAP_MS 5U /* ECU inter-byte gap */
+#define KL_SESSION_TMO_MS 1000U /* Session keepalive timeout */
+#define KL_BYTE_TMO_MS 50U /* Per-byte RX timeout */
+#define KL_PKT_TMO_MS 800U /* Overall packet RX timeout */
+#define KL_COMPL_TMO_MS 25U /* Complement wait timeout */
+#define KL_ECHO_TMO_MS 3U /* Max time to wait for echo */
+
+/* ================================================================
+ * Buffer sizing
+ * ================================================================ */
+#define KL_MAX_PACKET_PAYLOAD 16 /* KWP1281 max block data */
+#define KL_MAX_PACKET_RAW 20 /* LEN+CNT+CMD+16+0x03+margin */
+#define KL_RX_FIFO_SIZE 64
+#define KL_IDENT_BUF_SIZE 64
+#define KL_STREAM_BUF_SIZE 64
+
+/* ================================================================
+ * Handshake
+ * ================================================================ */
+#define KL_HANDSHAKE_MAX_RETRIES 5
+
+/* ================================================================
+ * ASCII / DTC chunk sizes
+ * ================================================================ */
+#define KL_ASCII_CHUNK_SIZE 13 /* Data bytes per ASCII packet */
+#define KL_DTC_CHUNK_SIZE 15 /* Data bytes per DTC packet */
+#define KL_DTC_RECORD_SIZE 8
+#define KL_DTC_TOTAL_RECORDS 8
+
+/* ================================================================
+ * Packet command enum
+ * ================================================================ */
+typedef enum {
+ KL_CMD_ReadIdent = 0x00,
+ KL_CMD_ReadRam = 0x01,
+ KL_CMD_WriteRam = 0x02,
+ KL_CMD_ReadRomEeprom = 0x03,
+ KL_CMD_ActuatorTest = 0x04,
+ KL_CMD_FaultCodesDelete = 0x05,
+ KL_CMD_End = 0x06,
+ KL_CMD_FaultCodesRead = 0x07,
+ KL_CMD_SingleRead = 0x08,
+ KL_CMD_ACK = 0x09,
+ KL_CMD_NAK = 0x0A,
+ KL_CMD_SoftwareCoding = 0x10,
+ KL_CMD_LoginEeprom = 0x18,
+ KL_CMD_ReadEeprom = 0x19,
+ KL_CMD_WriteEeprom = 0x1A,
+ KL_CMD_Custom = 0x1B,
+ KL_CMD_GroupReading = 0x29,
+ KL_CMD_Login = 0x2B,
+ KL_CMD_GroupReadingResponse = 0xE7,
+ KL_CMD_ReadEepromResponse = 0xEF,
+ KL_CMD_EepromLoginResponse = 0xF0,
+ KL_CMD_ActuatorTestResponse = 0xF5,
+ KL_CMD_AsciiData = 0xF6,
+ KL_CMD_WriteEepromResponse = 0xF9,
+ KL_CMD_SingleReadResponse = 0xFB,
+ KL_CMD_FaultCodesResponse = 0xFC,
+ KL_CMD_ReadRomResponse = 0xFD
+} KL_PacketCmd;
+
+/* ================================================================
+ * Packet structure (parsed)
+ * ================================================================ */
+typedef struct {
+ uint8_t length; /* LENGTH field from wire */
+ uint8_t counter; /* COUNTER field */
+ uint8_t command; /* COMMAND field */
+ uint8_t data[KL_MAX_PACKET_PAYLOAD]; /* Data bytes after COMMAND */
+ uint8_t data_len; /* Number of data bytes */
+} KL_Packet;
+
+/* ================================================================
+ * Inline helper
+ * ================================================================ */
+static inline int32_t kl_tick_diff(uint32_t now, uint32_t ref)
+{
+ return (int32_t)(now - ref);
+}
+
+#endif /* KL_PROTOCOL_H */
diff --git a/Core/Kline_Libs/kl_session.c b/Core/Kline_Libs/kl_session.c
new file mode 100644
index 0000000..a1fdbd2
--- /dev/null
+++ b/Core/Kline_Libs/kl_session.c
@@ -0,0 +1,501 @@
+/*
+ * kl_session.c
+ * Session layer: 5-baud init, handshake, ident stream, session lifecycle.
+ *
+ * All operations are non-blocking state machines.
+ * The 5-baud sampling is ISR-driven via TIM17.
+ */
+
+#include "kl_session.h"
+#include "kl_phy.h"
+#include "kl_transport.h"
+#include "kl_app.h"
+#include "psg_prop.h"
+#include "main.h"
+#include
+
+extern TIM_HandleTypeDef htim17;
+
+static KL_Session sess;
+
+/* Forward declarations for BuildCombined (ident data) */
+static size_t sess_build_ident(uint8_t *out, size_t maxlen);
+
+/* ================================================================
+ * TIM17 configuration (0.1ms tick for 5-baud sampling)
+ * ================================================================ */
+static void tim17_config(void)
+{
+ __HAL_TIM_DISABLE(&htim17);
+ __HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
+ __HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
+
+ 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;
+
+ const uint32_t target_hz = 10000U;
+ uint32_t psc = (timclk + target_hz - 1U) / target_hz - 1U;
+ if (psc > 0xFFFFU) psc = 0xFFFFU;
+
+ __HAL_TIM_SET_PRESCALER(&htim17, psc);
+ __HAL_TIM_SET_AUTORELOAD(&htim17, 9U);
+ __HAL_TIM_SET_COUNTER(&htim17, 0U);
+
+ htim17.Instance->CR1 |= (TIM_CR1_OPM | TIM_CR1_URS);
+ htim17.Instance->EGR = TIM_EGR_UG;
+ __HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
+
+ HAL_NVIC_SetPriority(TIM1_TRG_COM_TIM17_IRQn, 6, 0);
+ HAL_NVIC_EnableIRQ(TIM1_TRG_COM_TIM17_IRQn);
+}
+
+static void tim17_schedule(uint32_t delay_ms)
+{
+ uint32_t ticks = delay_ms * 10U;
+ if (ticks < 1U) ticks = 1U;
+ if (ticks > 0x10000U) ticks = 0x10000U;
+
+ __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));
+ __HAL_TIM_SET_COUNTER(&htim17, 0U);
+
+ htim17.Instance->EGR = TIM_EGR_UG;
+ __HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
+
+ htim17.Instance->CR1 |= (TIM_CR1_OPM | TIM_CR1_URS);
+
+ __HAL_TIM_ENABLE_IT(&htim17, TIM_IT_UPDATE);
+ __HAL_TIM_ENABLE(&htim17);
+}
+
+/* ================================================================
+ * EXTI / 5-baud arming
+ * ================================================================ */
+static void sess_rearm_5baud(void)
+{
+ KL_Phy_DisableUart();
+
+ __HAL_RCC_SYSCFG_CLK_ENABLE();
+
+ /* TX pin idle high (input with pull-up) */
+ GPIO_InitTypeDef giTx = {0};
+ giTx.Pin = KL_TX_PIN;
+ giTx.Mode = GPIO_MODE_INPUT;
+ giTx.Pull = GPIO_PULLUP;
+ giTx.Speed = GPIO_SPEED_FREQ_LOW;
+ HAL_GPIO_Init(KL_GPIO_PORT, &giTx);
+ HAL_GPIO_WritePin(KL_GPIO_PORT, KL_TX_PIN, GPIO_PIN_SET);
+
+ /* RX pin: EXTI falling edge */
+ GPIO_InitTypeDef giRx = {0};
+ giRx.Pin = KL_RX_PIN;
+ giRx.Mode = GPIO_MODE_IT_FALLING;
+ giRx.Pull = GPIO_PULLUP;
+ HAL_GPIO_Init(KL_GPIO_PORT, &giRx);
+
+ __HAL_GPIO_EXTI_CLEAR_IT(KL_RX_PIN);
+ HAL_NVIC_SetPriority(EXTI15_10_IRQn, 5, 0);
+ HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
+ HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
+}
+
+static void sess_reset_to_idle(void)
+{
+ sess.state = KL_SESS_IDLE;
+ sess.five_bit_index = 0;
+ sess.five_byte = 0;
+ sess.five_active = 0;
+ sess.connection_status = 0;
+ sess.end_requested = 0;
+
+ KL_Transport_ResetCounter();
+
+ HAL_TIM_Base_Stop_IT(&htim17);
+ __HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
+
+ __HAL_GPIO_EXTI_CLEAR_IT(KL_RX_PIN);
+ HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
+ HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
+}
+
+/* ================================================================
+ * Init
+ * ================================================================ */
+void KL_Session_Init(void)
+{
+ memset(&sess, 0, sizeof(sess));
+ KL_Phy_Init();
+ tim17_config();
+ sess_rearm_5baud();
+}
+
+/* ================================================================
+ * ISR: GPIO EXTI falling edge on RX pin (start of 5-baud)
+ * ================================================================ */
+void KL_Session_OnExtiRxFalling(void)
+{
+ if (sess.state == KL_SESS_IDLE) {
+ HAL_NVIC_DisableIRQ(EXTI15_10_IRQn);
+ sess.five_active = 1;
+ sess.five_bit_index = 0;
+ sess.five_byte = 0;
+ sess.state = KL_SESS_5BAUD_SAMPLING;
+ tim17_schedule(KL_5BAUD_FIRST_MS);
+ }
+}
+
+/* ================================================================
+ * ISR: TIM17 period elapsed (5-baud bit sampling)
+ * ================================================================ */
+void KL_Session_OnTim17Elapsed(void)
+{
+ if (!sess.five_active) {
+ HAL_TIM_Base_Stop_IT(&htim17);
+ return;
+ }
+
+ if (sess.state == KL_SESS_5BAUD_SAMPLING) {
+ GPIO_PinState pin = HAL_GPIO_ReadPin(KL_GPIO_PORT, KL_RX_PIN);
+ uint8_t bit = (pin == GPIO_PIN_SET) ? 1U : 0U;
+
+ sess.five_byte |= (bit << sess.five_bit_index);
+ sess.five_bit_index++;
+
+ if (sess.five_bit_index >= 8) {
+ sess.five_active = 0;
+ HAL_TIM_Base_Stop_IT(&htim17);
+
+ if (sess.five_byte != KL_SLAVE_ADDR) {
+ sess_reset_to_idle();
+ sess_rearm_5baud();
+ return;
+ }
+
+ sess.connection_status = 1;
+ sess.hs_next_tick = HAL_GetTick() + KL_W1_MS;
+ sess.state = KL_SESS_5BAUD_DONE;
+ } else {
+ tim17_schedule(KL_5BAUD_BIT_MS);
+ }
+ }
+}
+
+/* ================================================================
+ * Session kick (extend deadline)
+ * ================================================================ */
+void KL_Session_Kick(void)
+{
+ sess.session_deadline = HAL_GetTick() + KL_SESSION_TMO_MS;
+}
+
+void KL_Session_RequestEnd(void)
+{
+ sess.end_requested = 1;
+}
+
+uint8_t KL_Session_GetStatus(void)
+{
+ return sess.connection_status;
+}
+
+/* ================================================================
+ * Build identification data
+ * ================================================================ */
+static size_t append_str(const char *s, size_t n, uint8_t *out, size_t w, size_t maxlen)
+{
+ for (size_t i = 0; i < n && s[i] != '\0' && w < maxlen; i++) {
+ out[w++] = (uint8_t)s[i];
+ }
+ return w;
+}
+
+static size_t sess_build_ident(uint8_t *out, size_t maxlen)
+{
+ size_t w = 0;
+ w = append_str(PSG_KUNDENNUMMER_STR, 12, out, w, maxlen);
+ w = append_str(PSG_DATENSATZ_STR, 10, out, w, maxlen);
+ w = append_str(PSG_SOFTWARE_VER_STR, 10, out, w, maxlen);
+ w = append_str(PSG_SOFTWARE_VER2_STR, 10, out, w, maxlen);
+ w = append_str(PSG_STEUERGERAET_STR, 10, out, w, maxlen);
+ w = append_str(PSG_UNKNOWN_STR, 6, out, w, maxlen);
+ return w;
+}
+
+/* ================================================================
+ * Terminate session and rearm 5-baud
+ * ================================================================ */
+static void sess_terminate(void)
+{
+ sess.connection_status = 0;
+ sess.session_deadline = 0;
+ sess.end_requested = 0;
+
+ KL_App_ResetLocks();
+ KL_Transport_ResetCounter();
+ KL_Transport_TxReset();
+ KL_Transport_RxReset();
+ KL_Phy_DisableUart();
+ sess_rearm_5baud();
+
+ sess.state = KL_SESS_IDLE;
+}
+
+/* ================================================================
+ * Main session service (called from KLine_Service)
+ * ================================================================ */
+void KL_Session_Service(void)
+{
+ uint32_t now = HAL_GetTick();
+
+ switch (sess.state) {
+
+ case KL_SESS_IDLE:
+ case KL_SESS_5BAUD_SAMPLING:
+ /* Driven by ISR */
+ break;
+
+ /* ---- 5-baud done: wait W1 then enter UART mode ---- */
+ case KL_SESS_5BAUD_DONE:
+ if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
+ KL_Phy_EnableUart9600();
+ KL_Transport_Init();
+ KL_Phy_RxFlush();
+
+ sess.hs_retry_count = 0;
+ sess.hs_next_tick = now;
+ sess.connection_status = 1;
+ sess.state = KL_SESS_HS_SYNC;
+ }
+ break;
+
+ /* ---- Handshake: send sync byte ---- */
+ case KL_SESS_HS_SYNC:
+ if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
+ if (KL_Transport_SendRawByte(KL_SYNC_BYTE)) {
+ sess.state = KL_SESS_HS_SYNC_WAIT;
+ }
+ }
+ break;
+
+ case KL_SESS_HS_SYNC_WAIT:
+ if (KL_Transport_RawTxDone()) {
+ sess.hs_next_tick = HAL_GetTick() + KL_W2_MS;
+ KL_Transport_RawTxReset();
+ sess.state = KL_SESS_HS_KW_LSB;
+ }
+ break;
+
+ /* ---- Handshake: send keyword LSB ---- */
+ case KL_SESS_HS_KW_LSB:
+ if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
+ if (KL_Transport_SendRawByte(KL_KEYWORD_LSB)) {
+ sess.state = KL_SESS_HS_KW_LSB_WAIT;
+ }
+ }
+ break;
+
+ case KL_SESS_HS_KW_LSB_WAIT:
+ if (KL_Transport_RawTxDone()) {
+ sess.hs_next_tick = HAL_GetTick() + KL_W3_MS;
+ KL_Transport_RawTxReset();
+ sess.state = KL_SESS_HS_KW_MSB;
+ }
+ break;
+
+ /* ---- Handshake: send keyword MSB ---- */
+ case KL_SESS_HS_KW_MSB:
+ if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
+ if (KL_Transport_SendRawByte(KL_KEYWORD_MSB)) {
+ sess.state = KL_SESS_HS_KW_MSB_WAIT;
+ }
+ }
+ break;
+
+ case KL_SESS_HS_KW_MSB_WAIT:
+ if (KL_Transport_RawTxDone()) {
+ KL_Transport_RawTxReset();
+ /* Set deadline for master ACK */
+ sess.hs_next_tick = HAL_GetTick() + KL_HS_RETRY_MS;
+ sess.state = KL_SESS_HS_WAIT_ACK;
+ }
+ break;
+
+ /* ---- Handshake: wait for master ~KW_MSB ---- */
+ case KL_SESS_HS_WAIT_ACK:
+ {
+ uint8_t rx_byte;
+ if (KL_Phy_RxPop(&rx_byte)) {
+ if (rx_byte == (uint8_t)~KL_KEYWORD_MSB) {
+ /* Handshake succeeded -- prepare identification */
+ sess.ident_len = (uint8_t)sess_build_ident(sess.ident_buf,
+ sizeof(sess.ident_buf));
+ sess.ident_offset = 0;
+ KL_Phy_RxFlush();
+ sess.state = KL_SESS_IDENT_SEND;
+ break;
+ }
+ /* Unexpected byte, fall through to retry */
+ }
+ if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
+ sess.hs_retry_count++;
+ if (sess.hs_retry_count >= KL_HANDSHAKE_MAX_RETRIES) {
+ sess_terminate();
+ } else {
+ sess.hs_next_tick = now;
+ sess.state = KL_SESS_HS_SYNC;
+ }
+ }
+ break;
+ }
+
+ /* ---- Identification: send ASCII chunks ---- */
+ case KL_SESS_IDENT_SEND:
+ {
+ uint8_t remaining = sess.ident_len - sess.ident_offset;
+ sess.ident_chunk_len = (remaining > KL_ASCII_CHUNK_SIZE)
+ ? KL_ASCII_CHUNK_SIZE : remaining;
+
+ uint8_t payload[1 + KL_ASCII_CHUNK_SIZE];
+ payload[0] = (uint8_t)KL_CMD_AsciiData;
+ memcpy(&payload[1], &sess.ident_buf[sess.ident_offset], sess.ident_chunk_len);
+
+ if (KL_Transport_SendPacket(payload, (uint8_t)(1 + sess.ident_chunk_len))) {
+ sess.state = KL_SESS_IDENT_WAIT_TX;
+ }
+ break;
+ }
+
+ case KL_SESS_IDENT_WAIT_TX:
+ if (KL_Transport_TxDone()) {
+ KL_Transport_TxReset();
+ sess.ident_offset += sess.ident_chunk_len;
+ if (sess.ident_offset >= sess.ident_len) {
+ /* Last chunk sent -- go straight to ready.
+ * No ACK expected after the final chunk. */
+ KL_Session_Kick();
+ sess.connection_status = 2;
+ sess.state = KL_SESS_READY;
+ } else {
+ /* Intermediate chunk: wait for ACK before next */
+ KL_Transport_StartReceive(400);
+ sess.state = KL_SESS_IDENT_WAIT_ACK;
+ }
+ } else if (KL_Transport_TxError()) {
+ sess_terminate();
+ }
+ break;
+
+ case KL_SESS_IDENT_WAIT_ACK:
+ if (KL_Transport_RxDone()) {
+ const KL_Packet *pkt = KL_Transport_GetRxPacket();
+ KL_Transport_RxReset();
+ if (pkt->command == (uint8_t)KL_CMD_ACK) {
+ sess.state = KL_SESS_IDENT_SEND;
+ } else {
+ sess_terminate();
+ }
+ } else if (KL_Transport_RxError()) {
+ sess_terminate();
+ }
+ break;
+
+ case KL_SESS_IDENT_FINAL_WAIT:
+ /* No longer used, kept for enum completeness */
+ if (KL_Transport_TxDone()) {
+ KL_Transport_TxReset();
+ KL_Session_Kick();
+ sess.connection_status = 2;
+ sess.state = KL_SESS_READY;
+ } else if (KL_Transport_TxError()) {
+ sess_terminate();
+ }
+ break;
+
+ /* ---- Active session: ready to receive commands ---- */
+ case KL_SESS_READY:
+ if (kl_tick_diff(now, sess.session_deadline) >= 0) {
+ sess_terminate();
+ break;
+ }
+ /* Use the session deadline as the RX timeout so we don't
+ * artificially cut short a receive that started late.
+ * Compute remaining time until session deadline. */
+ {
+ int32_t remaining = (int32_t)(sess.session_deadline - now);
+ if (remaining <= 0) { sess_terminate(); break; }
+ if (KL_Transport_StartReceive((uint32_t)remaining)) {
+ sess.state = KL_SESS_CMD_RX;
+ }
+ /* If StartReceive fails (rx not idle), stay in READY
+ * and retry next cycle rather than advancing to CMD_RX
+ * with no active receive. */
+ }
+ break;
+
+ case KL_SESS_CMD_RX:
+ if (kl_tick_diff(now, sess.session_deadline) >= 0) {
+ KL_Transport_RxReset();
+ sess_terminate();
+ break;
+ }
+ if (KL_Transport_RxDone()) {
+ KL_Session_Kick();
+ sess.state = KL_SESS_CMD_DISPATCH;
+ } else if (KL_Transport_RxError()) {
+ KL_Transport_RxReset();
+ /* Only send NAK if we actually received partial data
+ * (i.e., at least the length byte was consumed).
+ * A plain timeout with no data is just "no packet yet"
+ * and should silently retry. */
+ if (KL_Transport_RxHadData()) {
+ KL_Transport_SendNak();
+ sess.state = KL_SESS_CMD_NAK_WAIT;
+ } else {
+ sess.state = KL_SESS_READY;
+ }
+ }
+ break;
+
+ case KL_SESS_CMD_NAK_WAIT:
+ if (kl_tick_diff(now, sess.session_deadline) >= 0) {
+ KL_Transport_TxReset();
+ sess_terminate();
+ break;
+ }
+ if (KL_Transport_TxDone() || KL_Transport_TxError()) {
+ KL_Transport_TxReset();
+ sess.state = KL_SESS_READY;
+ }
+ break;
+
+ case KL_SESS_CMD_DISPATCH:
+ {
+ const KL_Packet *pkt = KL_Transport_GetRxPacket();
+ KL_Transport_RxReset();
+ KL_App_Dispatch(pkt);
+ sess.state = KL_SESS_CMD_RESPONSE;
+ break;
+ }
+
+ case KL_SESS_CMD_RESPONSE:
+ if (sess.end_requested) {
+ sess_terminate();
+ break;
+ }
+ if (KL_App_Done()) {
+ KL_Session_Kick();
+ sess.state = KL_SESS_READY;
+ }
+ break;
+
+ case KL_SESS_TERMINATE:
+ sess_terminate();
+ break;
+ }
+}
diff --git a/Core/Kline_Libs/kl_session.h b/Core/Kline_Libs/kl_session.h
new file mode 100644
index 0000000..bd0a54d
--- /dev/null
+++ b/Core/Kline_Libs/kl_session.h
@@ -0,0 +1,92 @@
+/*
+ * kl_session.h
+ * Session layer: 5-baud init, handshake, ident stream, session lifecycle.
+ */
+
+#ifndef KL_SESSION_H
+#define KL_SESSION_H
+
+#include "kl_protocol.h"
+
+/* ================================================================
+ * Session states
+ * ================================================================ */
+typedef enum {
+ KL_SESS_IDLE,
+
+ /* 5-baud address reception */
+ KL_SESS_5BAUD_SAMPLING,
+ KL_SESS_5BAUD_DONE,
+
+ /* Handshake: sync + keywords */
+ KL_SESS_HS_SYNC,
+ KL_SESS_HS_SYNC_WAIT,
+ KL_SESS_HS_KW_LSB,
+ KL_SESS_HS_KW_LSB_WAIT,
+ KL_SESS_HS_KW_MSB,
+ KL_SESS_HS_KW_MSB_WAIT,
+ KL_SESS_HS_WAIT_ACK,
+
+ /* Identification stream */
+ KL_SESS_IDENT_SEND,
+ KL_SESS_IDENT_WAIT_TX,
+ KL_SESS_IDENT_WAIT_ACK,
+ KL_SESS_IDENT_FINAL_ACK,
+ KL_SESS_IDENT_FINAL_WAIT,
+
+ /* Active session */
+ KL_SESS_READY,
+ KL_SESS_CMD_RX,
+ KL_SESS_CMD_DISPATCH,
+ KL_SESS_CMD_RESPONSE,
+ KL_SESS_CMD_NAK_WAIT, /* Sending NAK after invalid packet */
+
+ /* Teardown */
+ KL_SESS_TERMINATE
+} KL_SessionState;
+
+/* ================================================================
+ * Session context
+ * ================================================================ */
+typedef struct {
+ KL_SessionState state;
+
+ /* 5-baud sampling */
+ uint8_t five_bit_index;
+ uint8_t five_byte;
+ uint8_t five_active;
+
+ /* Handshake */
+ uint8_t hs_retry_count;
+ uint32_t hs_next_tick;
+
+ /* Identification stream */
+ uint8_t ident_buf[KL_IDENT_BUF_SIZE];
+ uint8_t ident_len;
+ uint8_t ident_offset;
+ uint8_t ident_chunk_len; /* Current chunk size being sent */
+
+ /* Session keepalive */
+ uint32_t session_deadline;
+
+ /* Connection status: 0=idle, 1=connecting, 2=active */
+ volatile uint8_t connection_status;
+
+ /* Signal from app layer that End command was handled */
+ uint8_t end_requested;
+} KL_Session;
+
+/* ================================================================
+ * API
+ * ================================================================ */
+void KL_Session_Init(void);
+void KL_Session_Service(void);
+uint8_t KL_Session_GetStatus(void);
+void KL_Session_RequestEnd(void); /* Called by app on CMD_End */
+void KL_Session_Kick(void); /* Extend session deadline */
+
+/* ISR callbacks */
+void KL_Session_OnExtiRxFalling(void);
+void KL_Session_OnTim17Elapsed(void);
+
+#endif /* KL_SESSION_H */
diff --git a/Core/Kline_Libs/kl_transport.c b/Core/Kline_Libs/kl_transport.c
new file mode 100644
index 0000000..9e0f43f
--- /dev/null
+++ b/Core/Kline_Libs/kl_transport.c
@@ -0,0 +1,418 @@
+/*
+ * kl_transport.c
+ * Transport layer: packet framing, byte-by-byte complement ack, counter.
+ *
+ * All operations are non-blocking state machines driven by
+ * KL_Transport_Service() called from the main loop.
+ */
+
+#include "kl_transport.h"
+#include "kl_phy.h"
+#include "main.h"
+#include
+
+static KL_Transport tl;
+
+/* ================================================================
+ * Init / Reset
+ * ================================================================ */
+void KL_Transport_Init(void)
+{
+ memset(&tl, 0, sizeof(tl));
+ tl.tx_state = KL_TL_TX_IDLE;
+ tl.rx_state = KL_TL_RX_IDLE;
+ tl.raw_state = KL_TL_RAW_IDLE;
+}
+
+void KL_Transport_ResetCounter(void)
+{
+ tl.pkt_counter = 0;
+ tl.counter_initialized = 0;
+}
+
+/* ================================================================
+ * Packet TX
+ * ================================================================ */
+int KL_Transport_SendPacket(const uint8_t *payload, uint8_t payload_len)
+{
+ if (tl.tx_state != KL_TL_TX_IDLE) return 0;
+ if (payload_len == 0 || payload_len > KL_MAX_PACKET_PAYLOAD) return 0;
+
+ /* Serialize: [LENGTH][COUNTER][payload...] */
+ uint8_t length_field = payload_len + 2; /* +2 for COUNTER and LENGTH convention */
+ uint8_t idx = 0;
+ tl.tx_buf[idx++] = length_field;
+ tl.tx_buf[idx++] = tl.pkt_counter++;
+ memcpy(&tl.tx_buf[idx], payload, payload_len);
+ idx += payload_len;
+
+ tl.tx_len = idx;
+ tl.tx_idx = 0;
+ tl.tx_deadline = HAL_GetTick() + KL_PKT_TMO_MS;
+ tl.tx_next_tick = HAL_GetTick();
+ tl.tx_state = KL_TL_TX_SEND_BYTE;
+ return 1;
+}
+
+void KL_Transport_SendAck(void)
+{
+ uint8_t payload = (uint8_t)KL_CMD_ACK;
+ KL_Transport_SendPacket(&payload, 1);
+}
+
+void KL_Transport_SendNak(void)
+{
+ uint8_t payload = (uint8_t)KL_CMD_NAK;
+ KL_Transport_SendPacket(&payload, 1);
+}
+
+int KL_Transport_TxBusy(void)
+{
+ return (tl.tx_state != KL_TL_TX_IDLE &&
+ tl.tx_state != KL_TL_TX_DONE &&
+ tl.tx_state != KL_TL_TX_ERROR) ? 1 : 0;
+}
+
+int KL_Transport_TxDone(void) { return (tl.tx_state == KL_TL_TX_DONE) ? 1 : 0; }
+int KL_Transport_TxError(void) { return (tl.tx_state == KL_TL_TX_ERROR) ? 1 : 0; }
+
+void KL_Transport_TxReset(void)
+{
+ tl.tx_state = KL_TL_TX_IDLE;
+}
+
+/* ================================================================
+ * TX state machine
+ * ================================================================ */
+static void tl_tx_service(void)
+{
+ uint32_t now = HAL_GetTick();
+ uint8_t byte_val;
+
+ switch (tl.tx_state) {
+
+ case KL_TL_TX_SEND_BYTE:
+ if (kl_tick_diff(now, tl.tx_next_tick) < 0) break;
+ if (tl.tx_idx >= tl.tx_len) {
+ /* All data bytes sent, send terminator */
+ tl.tx_state = KL_TL_TX_SEND_TERM;
+ break;
+ }
+ byte_val = tl.tx_buf[tl.tx_idx];
+ if (KL_Phy_TxByte(byte_val)) {
+ tl.tx_last_byte = byte_val;
+ tl.tx_idx++;
+ tl.tx_state = KL_TL_TX_WAIT_PHY;
+ }
+ break;
+
+ case KL_TL_TX_WAIT_PHY:
+ if (kl_tick_diff(now, tl.tx_deadline) >= 0) {
+ tl.tx_state = KL_TL_TX_ERROR;
+ break;
+ }
+ if (!KL_Phy_TxBusy()) {
+ /* Echo consumed by PHY, now wait for master's complement */
+ tl.tx_state = KL_TL_TX_WAIT_COMPLEMENT;
+ tl.tx_next_tick = now; /* Record when we started waiting */
+ }
+ break;
+
+ case KL_TL_TX_WAIT_COMPLEMENT:
+ if (kl_tick_diff(now, tl.tx_deadline) >= 0) {
+ tl.tx_state = KL_TL_TX_ERROR;
+ break;
+ }
+ {
+ uint8_t rx_byte;
+ if (KL_Phy_RxPop(&rx_byte)) {
+ if (rx_byte == (uint8_t)~tl.tx_last_byte) {
+ /* Valid complement received, advance to next byte */
+ tl.tx_next_tick = HAL_GetTick() + KL_P1_GAP_MS;
+ tl.tx_state = KL_TL_TX_SEND_BYTE;
+ } else {
+ /* Unexpected byte -- protocol error */
+ tl.tx_state = KL_TL_TX_ERROR;
+ }
+ }
+ }
+ break;
+
+ case KL_TL_TX_SEND_TERM:
+ if (KL_Phy_TxByte(KL_PACKET_END)) {
+ tl.tx_state = KL_TL_TX_WAIT_TERM_PHY;
+ }
+ break;
+
+ case KL_TL_TX_WAIT_TERM_PHY:
+ if (kl_tick_diff(now, tl.tx_deadline) >= 0) {
+ tl.tx_state = KL_TL_TX_ERROR;
+ break;
+ }
+ if (!KL_Phy_TxBusy()) {
+ tl.tx_state = KL_TL_TX_DONE;
+ }
+ break;
+
+ case KL_TL_TX_IDLE:
+ case KL_TL_TX_DONE:
+ case KL_TL_TX_ERROR:
+ break;
+ }
+}
+
+/* ================================================================
+ * Packet RX
+ * ================================================================ */
+int KL_Transport_StartReceive(uint32_t timeout_ms)
+{
+ if (tl.rx_state != KL_TL_RX_IDLE) return 0;
+
+ memset(&tl.rx_packet, 0, sizeof(tl.rx_packet));
+ tl.rx_idx = 0;
+ tl.rx_pkt_len = 0;
+ tl.rx_expected_total = 0;
+ tl.rx_deadline = HAL_GetTick() + timeout_ms;
+ tl.rx_byte_deadline = HAL_GetTick() + timeout_ms;
+ tl.rx_state = KL_TL_RX_WAIT_LEN;
+ return 1;
+}
+
+int KL_Transport_RxBusy(void)
+{
+ return (tl.rx_state != KL_TL_RX_IDLE &&
+ tl.rx_state != KL_TL_RX_DONE &&
+ tl.rx_state != KL_TL_RX_ERROR) ? 1 : 0;
+}
+
+int KL_Transport_RxDone(void) { return (tl.rx_state == KL_TL_RX_DONE) ? 1 : 0; }
+int KL_Transport_RxError(void) { return (tl.rx_state == KL_TL_RX_ERROR) ? 1 : 0; }
+
+const KL_Packet* KL_Transport_GetRxPacket(void) { return &tl.rx_packet; }
+
+void KL_Transport_RxReset(void)
+{
+ tl.rx_state = KL_TL_RX_IDLE;
+}
+
+int KL_Transport_RxHadData(void)
+{
+ return (tl.rx_idx > 0) ? 1 : 0;
+}
+
+/* ================================================================
+ * RX helper: send complement and advance to wait-for-PHY state
+ * ================================================================ */
+static int rx_send_complement(uint8_t byte, KL_TlRxState next_after_phy)
+{
+ uint8_t compl = (uint8_t)~byte;
+ if (KL_Phy_TxByte(compl)) {
+ tl.rx_last_byte = byte;
+ tl.rx_state = KL_TL_RX_WAIT_COMPL_PHY;
+ /* Store the state to transition to after PHY completes.
+ * We encode the "next state after complement" in rx_expected_total
+ * as a side channel -- but cleaner to use a dedicated field.
+ * Instead, we use a convention: after complement PHY done,
+ * we look at rx_idx to determine what to do next. */
+ (void)next_after_phy;
+ return 1;
+ }
+ return 0;
+}
+
+/* ================================================================
+ * RX state machine
+ * ================================================================ */
+static void tl_rx_service(void)
+{
+ uint32_t now = HAL_GetTick();
+ uint8_t byte_val;
+
+ switch (tl.rx_state) {
+
+ case KL_TL_RX_WAIT_LEN:
+ if (kl_tick_diff(now, tl.rx_deadline) >= 0) {
+ tl.rx_state = KL_TL_RX_ERROR;
+ break;
+ }
+ if (KL_Phy_RxPop(&byte_val)) {
+ tl.rx_buf[tl.rx_idx++] = byte_val;
+ tl.rx_pkt_len = byte_val;
+ tl.rx_packet.length = byte_val;
+ tl.rx_byte_deadline = now + KL_BYTE_TMO_MS;
+ /* Send complement for LENGTH */
+ tl.rx_state = KL_TL_RX_COMPL_LEN;
+ }
+ break;
+
+ case KL_TL_RX_COMPL_LEN:
+ if (kl_tick_diff(now, tl.rx_deadline) >= 0) {
+ tl.rx_state = KL_TL_RX_ERROR;
+ break;
+ }
+ {
+ uint8_t compl = (uint8_t)~tl.rx_buf[0];
+ if (KL_Phy_TxByte(compl)) {
+ tl.rx_state = KL_TL_RX_WAIT_COMPL_PHY;
+ }
+ }
+ break;
+
+ case KL_TL_RX_WAIT_CNT:
+ if (kl_tick_diff(now, tl.rx_deadline) >= 0) {
+ tl.rx_state = KL_TL_RX_ERROR;
+ break;
+ }
+ if (KL_Phy_RxPop(&byte_val)) {
+ tl.rx_buf[tl.rx_idx++] = byte_val;
+ tl.rx_byte_deadline = now + KL_BYTE_TMO_MS;
+
+ /* Validate/sync packet counter */
+ if (!tl.counter_initialized) {
+ tl.pkt_counter = byte_val;
+ tl.counter_initialized = 1;
+ }
+ tl.rx_packet.counter = byte_val;
+ tl.pkt_counter = byte_val + 1;
+
+ /* Send complement for COUNTER */
+ tl.rx_state = KL_TL_RX_COMPL_CNT;
+ }
+ break;
+
+ case KL_TL_RX_COMPL_CNT:
+ if (kl_tick_diff(now, tl.rx_deadline) >= 0) {
+ tl.rx_state = KL_TL_RX_ERROR;
+ break;
+ }
+ {
+ uint8_t compl = (uint8_t)~tl.rx_buf[1];
+ if (KL_Phy_TxByte(compl)) {
+ tl.rx_state = KL_TL_RX_WAIT_COMPL_PHY;
+ }
+ }
+ break;
+
+ case KL_TL_RX_WAIT_BYTE:
+ if (kl_tick_diff(now, tl.rx_deadline) >= 0) {
+ tl.rx_state = KL_TL_RX_ERROR;
+ break;
+ }
+ if (KL_Phy_RxPop(&byte_val)) {
+ tl.rx_buf[tl.rx_idx++] = byte_val;
+ tl.rx_byte_deadline = now + KL_BYTE_TMO_MS;
+ /* Always send complement for every non-terminator byte.
+ * WAIT_COMPL_PHY will decide whether to go to WAIT_TERM
+ * or back to WAIT_BYTE based on rx_idx vs rx_pkt_len. */
+ tl.rx_state = KL_TL_RX_COMPL_BYTE;
+ }
+ break;
+
+ case KL_TL_RX_COMPL_BYTE:
+ if (kl_tick_diff(now, tl.rx_deadline) >= 0) {
+ tl.rx_state = KL_TL_RX_ERROR;
+ break;
+ }
+ {
+ uint8_t compl = (uint8_t)~tl.rx_buf[tl.rx_idx - 1];
+ if (KL_Phy_TxByte(compl)) {
+ tl.rx_state = KL_TL_RX_WAIT_COMPL_PHY;
+ }
+ }
+ break;
+
+ case KL_TL_RX_WAIT_COMPL_PHY:
+ if (kl_tick_diff(now, tl.rx_deadline) >= 0) {
+ tl.rx_state = KL_TL_RX_ERROR;
+ break;
+ }
+ if (!KL_Phy_TxBusy()) {
+ /* Complement echo done. Determine next state based on progress.
+ * rx_idx tells us how many bytes we've stored so far:
+ * 1 = just finished LEN complement -> wait for CNT
+ * 2 = just finished CNT complement -> wait for CMD/data
+ * >=3 = CMD or data complement done -> wait for next byte or terminator
+ */
+ if (tl.rx_idx == 1) {
+ tl.rx_state = KL_TL_RX_WAIT_CNT;
+ } else if (tl.rx_idx == 2) {
+ tl.rx_state = KL_TL_RX_WAIT_BYTE;
+ } else {
+ /* Check if all non-terminator bytes are collected */
+ if (tl.rx_idx >= tl.rx_pkt_len) {
+ tl.rx_state = KL_TL_RX_WAIT_TERM;
+ } else {
+ tl.rx_state = KL_TL_RX_WAIT_BYTE;
+ }
+ }
+ }
+ break;
+
+ case KL_TL_RX_WAIT_TERM:
+
+ if (kl_tick_diff(now, tl.rx_deadline) >= 0) {
+ tl.rx_state = KL_TL_RX_ERROR;
+ break;
+ }
+ if (KL_Phy_RxPop(&byte_val)) {
+ if (byte_val != KL_PACKET_END) {
+ tl.rx_state = KL_TL_RX_ERROR;
+ break;
+ }
+ /* Parse into KL_Packet structure */
+ tl.rx_packet.command = tl.rx_buf[2]; /* CMD at index 2 */
+ tl.rx_packet.data_len = tl.rx_pkt_len - 3; /* pkt_len - 3 = data count */
+ if (tl.rx_packet.data_len > KL_MAX_PACKET_PAYLOAD) {
+ tl.rx_packet.data_len = KL_MAX_PACKET_PAYLOAD;
+ }
+ memcpy(tl.rx_packet.data, &tl.rx_buf[3], tl.rx_packet.data_len);
+ tl.rx_state = KL_TL_RX_DONE;
+ }
+ break;
+
+ case KL_TL_RX_IDLE:
+ case KL_TL_RX_DONE:
+ case KL_TL_RX_ERROR:
+ break;
+ }
+}
+
+/* ================================================================
+ * Raw byte TX (for handshake -- no framing, no complement)
+ * ================================================================ */
+int KL_Transport_SendRawByte(uint8_t byte)
+{
+ if (tl.raw_state != KL_TL_RAW_IDLE) return 0;
+ if (!KL_Phy_TxByte(byte)) return 0;
+ tl.raw_state = KL_TL_RAW_WAIT_PHY;
+ return 1;
+}
+
+int KL_Transport_RawTxDone(void)
+{
+ return (tl.raw_state == KL_TL_RAW_DONE) ? 1 : 0;
+}
+
+void KL_Transport_RawTxReset(void)
+{
+ tl.raw_state = KL_TL_RAW_IDLE;
+}
+
+static void tl_raw_service(void)
+{
+ if (tl.raw_state == KL_TL_RAW_WAIT_PHY) {
+ if (!KL_Phy_TxBusy()) {
+ tl.raw_state = KL_TL_RAW_DONE;
+ }
+ }
+}
+
+/* ================================================================
+ * Combined service
+ * ================================================================ */
+void KL_Transport_Service(void)
+{
+ tl_tx_service();
+ tl_rx_service();
+ tl_raw_service();
+}
diff --git a/Core/Kline_Libs/kl_transport.h b/Core/Kline_Libs/kl_transport.h
new file mode 100644
index 0000000..98eb450
--- /dev/null
+++ b/Core/Kline_Libs/kl_transport.h
@@ -0,0 +1,116 @@
+/*
+ * kl_transport.h
+ * Transport layer: packet framing, byte-by-byte complement ack, counter.
+ */
+
+#ifndef KL_TRANSPORT_H
+#define KL_TRANSPORT_H
+
+#include "kl_protocol.h"
+
+/* ================================================================
+ * TX state machine
+ * ================================================================ */
+typedef enum {
+ KL_TL_TX_IDLE,
+ KL_TL_TX_SEND_BYTE, /* Submitting data byte to PHY */
+ KL_TL_TX_WAIT_PHY, /* Waiting for PHY TX+echo done */
+ KL_TL_TX_WAIT_COMPLEMENT, /* Waiting for master's ~byte */
+ KL_TL_TX_SEND_TERM, /* Sending 0x03 terminator */
+ KL_TL_TX_WAIT_TERM_PHY, /* Waiting for terminator echo */
+ KL_TL_TX_DONE,
+ KL_TL_TX_ERROR
+} KL_TlTxState;
+
+/* ================================================================
+ * RX state machine
+ * ================================================================ */
+typedef enum {
+ KL_TL_RX_IDLE,
+ KL_TL_RX_WAIT_LEN, /* Waiting for LENGTH byte */
+ KL_TL_RX_COMPL_LEN, /* Sending complement for LENGTH */
+ KL_TL_RX_WAIT_CNT, /* Waiting for COUNTER byte */
+ KL_TL_RX_COMPL_CNT, /* Sending complement for COUNTER */
+ KL_TL_RX_WAIT_BYTE, /* Waiting for CMD/DATA byte */
+ KL_TL_RX_COMPL_BYTE, /* Sending complement for CMD/DATA */
+ KL_TL_RX_WAIT_COMPL_PHY, /* Waiting for complement echo */
+ KL_TL_RX_WAIT_TERM, /* Waiting for 0x03 terminator */
+ KL_TL_RX_DONE,
+ KL_TL_RX_ERROR
+} KL_TlRxState;
+
+/* ================================================================
+ * Raw-byte TX state (for handshake bytes, no framing)
+ * ================================================================ */
+typedef enum {
+ KL_TL_RAW_IDLE,
+ KL_TL_RAW_WAIT_PHY,
+ KL_TL_RAW_DONE
+} KL_TlRawState;
+
+/* ================================================================
+ * Transport context
+ * ================================================================ */
+typedef struct {
+ uint8_t pkt_counter;
+ uint8_t counter_initialized;
+
+ /* TX engine */
+ KL_TlTxState tx_state;
+ uint8_t tx_buf[KL_MAX_PACKET_RAW];
+ uint8_t tx_len; /* Bytes in tx_buf (excl. 0x03) */
+ uint8_t tx_idx;
+ uint8_t tx_last_byte;
+ uint32_t tx_deadline;
+ uint32_t tx_next_tick; /* Inter-byte gap timing */
+
+ /* RX engine */
+ KL_TlRxState rx_state;
+ uint8_t rx_buf[KL_MAX_PACKET_RAW];
+ uint8_t rx_idx;
+ uint8_t rx_expected_total; /* LENGTH field value + 1 (for 0x03) */
+ uint8_t rx_pkt_len; /* LENGTH field value */
+ uint8_t rx_last_byte; /* Last received byte (for complement send) */
+ uint32_t rx_deadline;
+ uint32_t rx_byte_deadline;
+ KL_Packet rx_packet;
+
+ /* Raw byte TX */
+ KL_TlRawState raw_state;
+} KL_Transport;
+
+/* ================================================================
+ * API
+ * ================================================================ */
+void KL_Transport_Init(void);
+void KL_Transport_Service(void);
+void KL_Transport_ResetCounter(void);
+
+/* Packet TX (non-blocking) */
+int KL_Transport_SendPacket(const uint8_t *payload, uint8_t payload_len);
+int KL_Transport_TxBusy(void);
+int KL_Transport_TxDone(void);
+int KL_Transport_TxError(void);
+void KL_Transport_TxReset(void);
+
+/* Packet RX (non-blocking) */
+int KL_Transport_StartReceive(uint32_t timeout_ms);
+int KL_Transport_RxBusy(void);
+int KL_Transport_RxDone(void);
+int KL_Transport_RxError(void);
+const KL_Packet* KL_Transport_GetRxPacket(void);
+void KL_Transport_RxReset(void);
+
+/* Diagnostics */
+int KL_Transport_RxHadData(void); /* True if RX consumed any bytes before error */
+
+/* Convenience */
+void KL_Transport_SendAck(void);
+void KL_Transport_SendNak(void);
+
+/* Raw byte TX for handshake (no framing, no complement) */
+int KL_Transport_SendRawByte(uint8_t byte);
+int KL_Transport_RawTxDone(void);
+void KL_Transport_RawTxReset(void);
+
+#endif /* KL_TRANSPORT_H */
diff --git a/Core/Kline_Libs/kline.c b/Core/Kline_Libs/kline.c
deleted file mode 100644
index 49cefe2..0000000
--- a/Core/Kline_Libs/kline.c
+++ /dev/null
@@ -1,1294 +0,0 @@
-/*
- * 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_HandleWriteRam(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();
-
-}
-
-// ===== 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_ReadRam: // readIdentAddressCMD
- KLine_HandleReadIdentAddress(&packet.raw[3], (uint8_t)(packet.length - 4));
- packet.type = PACKET_TYPE_UNKNOWN;
- break;
- case PACKET_CMD_WriteRam: // readIdentAddressCMD
- KLine_HandleWriteRam(&packet.raw[2], (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:
- SendNakPacket();
- //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) {
- SendNakPacket();
- return;
- }
- //KLine_SetDFI_Value(dFi);
- int8_t s_dfi_code = GetDfiValue();
-
- 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_HandleWriteRam(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();
- return;
- }
- }
-
- SendNakPacket();
-/*
- 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
deleted file mode 100644
index b5b3cf9..0000000
--- a/Core/Kline_Libs/kline.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* 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.c b/Core/Kline_Libs/psg_prop.c
new file mode 100644
index 0000000..e618dcf
--- /dev/null
+++ b/Core/Kline_Libs/psg_prop.c
@@ -0,0 +1,222 @@
+/*
+ * psg_prop.c
+ * PSG K-Line data dictionary implementation.
+ *
+ * Provides lookup functions for all ROM and EEPROM read responses.
+ * Static constant tables are derived from protocol captures.
+ * Dynamic values (DFI, calibration) use runtime storage.
+ *
+ * Created on: 2025
+ * Author: herli
+ */
+
+#include "psg_prop.h"
+#include "ee_manager.h"
+#include
+
+// ================================================================
+// ROM lookup tables
+// ================================================================
+
+/* Binary ROM entries: fixed-length raw data blobs */
+typedef struct {
+ uint16_t addr;
+ uint8_t len;
+ uint8_t data[10];
+} RomEntry_t;
+
+static const RomEntry_t s_rom_bin_table[] = {
+ /* addr len data bytes (from protocol capture) description */
+ { 0x9FCC, 10, { 0xA2,0x93,0x61,0x9F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF } }, /* fixed block */
+ { 0x9FD6, 10, { 0x4B,0x9F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x10,0x9C } }, /* fixed block */
+ { 0x9FE0, 10, { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF } }, /* fixed block */
+ { 0x9FEA, 10, { 0xFF,0xFF,0xFF,0xFF,0xDD,0x95,0xFF,0xFF,0xFF,0xFF } }, /* fixed block */
+ { 0x9FF4, 10, { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF } }, /* fixed block */
+ { 0x9FB6, 2, { 0x44,0x00 } }, /* ptr -> DFI EEPROM addr */
+ { 0x9FBE, 2, { 0xC6,0x00 } }, /* unknown ptr */
+ { 0x9FBC, 2, { 0xA4,0x00 } }, /* unknown ptr */
+ { 0x9FBA, 2, { 0xED,0x00 } }, /* unknown ptr */
+ { 0x9FA6, 2, { 0x06,0x04 } }, /* unknown */
+ { 0x9FA8, 2, { 0x42,0x00 } }, /* dT-Hybrid ref */
+ { 0x9F74, 2, { 0x70,0x00 } }, /* DeltaBlockierwinkel */
+ { 0x9F72, 2, { 0x74,0x00 } }, /* DeltaPhi-Offset */
+ { 0x9F70, 2, { 0x48,0x00 } }, /* DynFDKorrekturt */
+ { 0x93AD, 3, { 0x30,0x30,0x30 } }, /* Änderungsindex PV "000" */
+ { 0x9F9E, 3, { 0xFF,0xFF,0xFF } }, /* PSG Fertigungsdatum (unset) */
+ { 0x9FA1, 3, { 0xFF,0xFF,0xFF } }, /* PSG Chargen-Nr. (unset) */
+ { 0x9C10, 10, PSG_KUNDENNUMMER1_STR },/* Kundennummer 1 "1234567891" */
+ { 0x9C1A, 2, PSG_KUNDENNUMMER2_STR }, /* Kundennummer 2 "23" */
+ { PSG_CUST_CHANGE_ADDR, 3, PSG_FERTIGUNGSWERK_STR }, /* Fertigungswerk */
+ { PSG_CUST_CHANGE_ADDR + 3, 6, PSG_MOD_INDEX_STR }, /* Mod Index */
+};
+#define ROM_BIN_COUNT (sizeof(s_rom_bin_table) / sizeof(s_rom_bin_table[0]))
+
+/* String ROM entries: 10-byte ASCII identifiers */
+typedef struct {
+ uint16_t addr;
+ const char *str; /* exactly 10 ASCII bytes, no NUL */
+} RomStrEntry_t;
+
+static const RomStrEntry_t s_rom_str_table[] = {
+ { 0x93BA, PSG_IDENT_STR }, /* ident (at PSG_IDENT_ADDR - 10) */
+ { 0x9F80, PSG_SOFTWARE_VER_STR }, /* software version */
+ { 0x9F94, PSG_STEUERGERAET_STR }, /* Steuergeraet */
+ { 0x9F76, PSG_FAHRSOFTWARE_STR }, /* Fahrsoftware */
+ { 0x9C1C, PSG_DATENSATZ_STR }, /* Datensatz */
+ { 0x93B0, PSG_REFERENZ_STR }, /* Referenz-Kennfeld */
+
+};
+#define ROM_STR_COUNT (sizeof(s_rom_str_table) / sizeof(s_rom_str_table[0]))
+
+// ================================================================
+// EEPROM static data blocks
+// ================================================================
+
+/*
+ * DTC storage region (0x0000-0x003F, 64 bytes).
+ * Stored as a flat array so any-length chunk reads reconstruct correctly.
+ * Byte values derived from protocol capture.
+ */
+static const uint8_t s_dtc_eeprom_block[64] = {
+ /* 0x0000-0x000C (13 bytes, DTC MSG 1) */
+ 0x50, 0x20, 0xAB, 0x0A, 0x0E, 0x09, 0x01, 0xED, 0x5A, 0x20, 0xDF, 0x2D, 0x05,
+ /* 0x000D-0x0019 (13 bytes, DTC MSG 2) */
+ 0x00, 0x02, 0x33, 0x5B, 0x20, 0xDF, 0x2D, 0x05, 0x00, 0x03, 0x34, 0x52, 0x20,
+ /* 0x001A-0x0026 (13 bytes, DTC MSG 3) */
+ 0xDF, 0x2D, 0x00, 0x00, 0x04, 0x30, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ /* 0x0027-0x0033 (13 bytes, DTC MSG 4) */
+ 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ /* 0x0034-0x003F (12 bytes, DTC MSG 5) */
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+};
+
+/*
+ * DeltaPhi calibration array (0x009A-0x00BD, 36 bytes = 18 x int16 LE).
+ * 0x8000 entries are "not calibrated" placeholders.
+ */
+static const uint8_t s_deltaphi_block[36] = {
+ /* 0x009A-0x00A6 (13 bytes, AD1) */
+ 0xDE, 0xFF, 0xE8, 0xFF, 0x0C, 0x00, 0x08, 0x00, 0xFE, 0xFF, 0x00, 0x80, 0x00,
+ /* 0x00A7-0x00B3 (13 bytes, AD2) */
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80,
+ /* 0x00B4-0x00BD (10 bytes, AD3) */
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80
+};
+
+/* ================================================================
+ * Runtime-writable EEPROM values.
+ * Initialized to sniffed defaults; updated by WriteEeprom handlers.
+ * ================================================================ */
+static uint8_t s_dt_hybrid[2] = { 0xDE, 0x22 }; /* 0x0042, dT-Hybrid, default 1958.5 K */
+static uint8_t s_dynfd_korr[2] = { 0x00, 0x00 }; /* 0x0048, DynFDKorrekturt */
+static uint8_t s_delta_blockierwinkel[4] = { 0x00, 0x00, 0x00, 0x00 }; /* 0x0070 */
+static uint8_t s_deltaphi_offset[4] = { 0x00, 0x00, 0x00, 0x00 }; /* 0x0074 */
+
+// ================================================================
+// PSG_ROM_GetData
+// ================================================================
+uint8_t PSG_ROM_GetData(uint16_t addr, uint8_t len, uint8_t *out)
+{
+ /* customerChangeAddress (0x9FFE) — 2 bytes little-endian */
+ if (addr == 0x9FFE && len == 2) {
+ out[0] = (uint8_t)(PSG_CUST_CHANGE_ADDR & 0xFF);
+ out[1] = (uint8_t)((PSG_CUST_CHANGE_ADDR >> 8) & 0xFF);
+ return 1;
+ }
+
+ /* Binary data blocks */
+ for (int i = 0; i < (int)ROM_BIN_COUNT; i++) {
+ if (s_rom_bin_table[i].addr == addr && s_rom_bin_table[i].len == len) {
+ memcpy(out, s_rom_bin_table[i].data, len);
+ return 1;
+ }
+ }
+
+ /* 10-byte ASCII string entries */
+ for (int i = 0; i < (int)ROM_STR_COUNT; i++) {
+ if (s_rom_str_table[i].addr == addr && len == 10) {
+ memcpy(out, s_rom_str_table[i].str, 10);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+// ================================================================
+// PSG_EEPROM_GetData
+// ================================================================
+uint8_t PSG_EEPROM_GetData(uint16_t addr, uint8_t len, uint8_t *out)
+{
+ /* DFI angle correction (0x0044, 2 bytes) — dynamic, caller must check lock */
+ if (addr == 0x0044 && len == 2) {
+ int8_t code = GetDfiValue();
+ out[0] = (uint8_t)code;
+ out[1] = (uint8_t)(-(int8_t)code); /* two's-complement checksum byte */
+ return 1;
+ }
+
+ /* Serial number (0x0080, 6 bytes) */
+ if (addr == 0x0080 && len == 6) {
+ memcpy(out, PSG_SERIAL_STR, 6);
+ return 1;
+ }
+
+ /* Fertigungsdatum / manufacturing date (0x0086, 3 bytes) */
+ if (addr == 0x0086 && len == 3) {
+ memcpy(out, FERTIGUNGSDATUM_STR, 3);
+ //memcpy(out + 3, FERTIGUNGSDATUM_STR, 3); si length = 6
+
+ return 1;
+ }
+
+ /* Unknown/reserved (0x0094, 2 bytes) */
+ if (addr == 0x0094 && len == 2) {
+ out[0] = 0x00; out[1] = 0x00;
+ return 1;
+ }
+
+ /* dT-Hybrid (0x0042, 2 bytes) */
+ if (addr == 0x0042 && len == 2) {
+ memcpy(out, s_dt_hybrid, 2);
+ return 1;
+ }
+
+ /* DynFDKorrekturt (0x0048, 2 bytes) */
+ if (addr == 0x0048 && len == 2) {
+ memcpy(out, s_dynfd_korr, 2);
+ return 1;
+ }
+
+ /* DeltaBlockierwinkel (0x0070, 4 bytes) */
+ if (addr == 0x0070 && len == 4) {
+ memcpy(out, s_delta_blockierwinkel, 4);
+ return 1;
+ }
+
+ /* DeltaPhi-Offset (0x0074, 4 bytes) */
+ if (addr == 0x0074 && len == 4) {
+ memcpy(out, s_deltaphi_offset, 4);
+ return 1;
+ }
+
+ /* DTC EEPROM block (0x0000-0x003F, 64 bytes total) */
+ if (addr < 0x0040U) {
+ if ((uint16_t)(addr + len) <= 0x0040U) {
+ memcpy(out, &s_dtc_eeprom_block[addr], len);
+ return 1;
+ }
+ }
+
+ /* DeltaPhi calibration array (0x009A-0x00BD, 36 bytes) */
+ if (addr >= 0x009AU && addr < (0x009AU + 36U)) {
+ uint16_t off = addr - 0x009AU;
+ uint16_t avail = (uint16_t)(36U - off);
+ uint8_t n = (len <= (uint8_t)avail) ? len : (uint8_t)avail;
+ memcpy(out, &s_deltaphi_block[off], n);
+ if (n < len) memset(out + n, 0xFF, len - n);
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/Core/Kline_Libs/psg_prop.h b/Core/Kline_Libs/psg_prop.h
index c091be3..d3b8969 100644
--- a/Core/Kline_Libs/psg_prop.h
+++ b/Core/Kline_Libs/psg_prop.h
@@ -1,5 +1,9 @@
/*
* psg_prop.h
+ * PSG device properties dictionary.
+ *
+ * All K-Line ROM and EEPROM read responses are defined here.
+ * Edit the values in this file to configure what the master sees.
*
* Created on: Aug 19, 2025
* Author: herli
@@ -8,18 +12,95 @@
#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;
+#include
+#include
-#define i_Ident "0470504005"
-#define i_SerialN "225832"
-#define i_modIndex "000004"
+// ================================================================
+// Device identity strings (used in ASCII init stream and ROM reads)
+// ================================================================
+
+/* Part number / ECU identifier — 10 ASCII chars, ROM 0x93BA */
+#define PSG_IDENT_STR "0470004004"
+//#define PSG_IDENT_STR "0470504009"
+
+/* Serial number — 6 ASCII chars, EEPROM 0x0080 */
+#define PSG_SERIAL_STR "297523"
+
+/* Modification index — 6 ASCII chars, ROM 0x93A7 */
+#define PSG_MOD_INDEX_STR "000000"
+
+/* Software version — 10 ASCII chars, ROM 0x9F80 */
+//#define PSG_SOFTWARE_VER_STR "C063_2.V61"
+#define PSG_SOFTWARE_VER_STR "HCPS_G2.04"
+#define PSG_SOFTWARE_VER2_STR "C062_0.P64" //no la lee directamente, solo al principio
+
+/* Steuergeraet (controller unit number) — 10 ASCII chars, ROM 0x9F94 */
+#define PSG_STEUERGERAET_STR "0281010888"
+
+/* Fahrsoftware (driving software code) — 10 ASCII chars, ROM 0x9F76 */
+#define PSG_FAHRSOFTWARE_STR "1469946013"
+
+/* Datensatz (dataset code) — 10 ASCII chars, ROM 0x9C1C */
+#define PSG_DATENSATZ_STR "2469947023"
+
+/* Referenz-Kennfeld (reference map code) — 10 ASCII chars, ROM 0x93B0 */
+#define PSG_REFERENZ_STR "2469948008"
+
+/* Fertigungsdatum (manufacturing date) — 3 ASCII chars, EEPROM 0x0086 */
+//#define FERTIGUNGSDATUM_STR "489"
+#define FERTIGUNGSDATUM_STR "420"
+
+/* Fertigungswerk (manufacturing plant) — 3 ASCII chars, ROM 0x93A4 */
+//#define PSG_FERTIGUNGSWERK_STR "015"
+#define PSG_FERTIGUNGSWERK_STR "069"
+
+#define PSG_ANDERUNGSINDEX_STR "000"
+
+#define PSG_KUNDENNUMMER_STR "YC1Q9A543EF "
+#define PSG_KUNDENNUMMER1_STR "YC1Q9A543E"
+#define PSG_KUNDENNUMMER2_STR "F "
+
+#define PSG_UNKNOWN_STR "\xFF\xFF\xFF\xFF\xFF\xFF"
+
+
+// ================================================================
+// Key address constants
+// ================================================================
+
+/*
+ * Address returned by the ReadIdentAdress (0x01) command.
+ * The ROM ident string lives at (PSG_IDENT_ADDR - 10) = 0x93BA.
+ */
+#define PSG_IDENT_ADDR 0x93C4U
+
+#define PSG_FAHRSOFTWARE_ADDR 0x9C28U
+
+#define PSG_START55_ADDR 0x60U
+
+/*
+ * Customer change address — returned at ROM 0x9FFE (little-endian).
+ * The mod-index string lives at (PSG_CUST_CHANGE_ADDR + 3) = 0x93A7.
+ */
+#define PSG_CUST_CHANGE_ADDR 0x93A4U
+
+// ================================================================
+// ROM lookup interface (implemented in psg_prop.c)
+// ================================================================
+
+/*
+ * PSG_ROM_GetData — fill 'out' with 'len' bytes for the given ROM address.
+ * Returns 1 if the address is known, 0 if unhandled.
+ * Access-control (lock checks) is the caller's responsibility.
+ */
+uint8_t PSG_ROM_GetData(uint16_t addr, uint8_t len, uint8_t *out);
+
+/*
+ * PSG_EEPROM_GetData — fill 'out' with 'len' bytes for the given EEPROM address.
+ * Returns 1 if the address is known, 0 if unhandled.
+ * Access-control (lock checks) is the caller's responsibility.
+ */
+uint8_t PSG_EEPROM_GetData(uint16_t addr, uint8_t len, uint8_t *out);
#endif /* INC_PSG_PROP_H_ */
diff --git a/Core/Src/fuel_map.c b/Core/Src/fuel_map.c
index ea3fb37..a44dfd5 100644
--- a/Core/Src/fuel_map.c
+++ b/Core/Src/fuel_map.c
@@ -375,9 +375,11 @@ float BoostMultiplier(uint8_t mode, float RPM, float ME){
return m;
}
-#define _004006
-//#define _004004
+//#define _004006
+#define _004004
//#define _004002
+//#define _504003
+//#define _504009
#if defined(_004006)
struct AlphaStruct fuelmap_m12 = { //probar este mapa
@@ -471,7 +473,7 @@ struct AlphaStruct fuelmap_m12 = { //probar este mapa
{ -2.273, 1.383, 2.438, 2.848, 4.008, 5.145, 12.363, 15.117, 16.523, 17.941, 24.598, 24.598}, //RPM = 2199
}
};
-struct AlphaStruct fuelmap_10 = { //probar este mapa
+struct AlphaStruct fuelmap_m5 = { //probar este mapa
{
{ 5.109, 7.020, 7.137, 7.230, 7.500, 8.156, 10.711, 11.520, 11.742, 11.965, 12.961, 13.781}, //RPM = 99
{ 5.367, 7.559, 7.688, 7.793, 8.121, 8.859, 11.379, 12.141, 12.316, 12.504, 13.465, 14.227}, //RPM = 199
@@ -486,7 +488,7 @@ struct AlphaStruct fuelmap_10 = { //probar este mapa
}
};
-struct AlphaStruct fuelmap_25 = { //probar este mapa
+struct AlphaStruct fuelmap_10 = { //probar este mapa
{
{ 5.320, 7.289, 7.406, 7.500, 7.781, 8.355, 10.898, 11.566, 11.871, 12.188, 13.184, 14.004}, //RPM = 99
{ 5.484, 7.711, 7.852, 7.957, 8.285, 9.094, 11.531, 12.176, 12.445, 12.738, 13.699, 14.461}, //RPM = 199
@@ -500,7 +502,7 @@ struct AlphaStruct fuelmap_25 = { //probar este mapa
{ -2.027, 1.711, 2.836, 3.199, 4.254, 5.391, 13.254, 15.938, 17.801, 19.676, 24.598, 24.598}, //RPM = 2199
}
};
-struct AlphaStruct fuelmap_60 = { //probar este mapa
+struct AlphaStruct fuelmap_25 = { //probar este mapa
{
{ 5.590, 7.652, 7.781, 7.863, 8.145, 8.613, 11.133, 11.625, 12.047, 12.457, 13.477, 14.297}, //RPM = 99
{ 5.637, 7.922, 8.074, 8.168, 8.496, 9.375, 11.707, 12.223, 12.609, 13.020, 13.969, 14.730}, //RPM = 199
@@ -514,7 +516,7 @@ struct AlphaStruct fuelmap_60 = { //probar este mapa
{ -1.875, 1.945, 3.070, 3.445, 4.430, 5.578, 13.793, 16.465, 18.668, 20.824, 24.598, 24.598}, //RPM = 2199664, 2.180, 3.398, 3.715, 4.711, 5.777, 14.461, 17.086, 19.605, 22.125, 24.598, 24.598}, //RPM = 2199
}
};
-struct AlphaStruct fuelmap_80 = { //probar este mapa
+struct AlphaStruct fuelmap_60 = { //probar este mapa
{
{ 5.918, 8.109, 8.227, 8.320, 8.590, 8.930, 11.426, 11.695, 12.246, 12.820, 13.816, 14.625}, //RPM = 99
{ 5.824, 8.180, 8.309, 8.426, 8.742, 9.727, 11.953, 12.281, 12.809, 13.336, 14.320, 15.070}, //RPM = 199
@@ -626,6 +628,135 @@ struct fuelMapIndexes fuelMapI = { //probar este mapa
{ 0.375, 0.500, 1.625, 2.500, 5.000, 10.000, 30.000, 35.000, 40.000, 45.000, 60.000, 72.000},
{ -15.5, -5.5 ,6.3, 27.5, 47.8, 75}
};
+#elif defined(_504003)
+struct AlphaStruct fuelmap_m12 = { //probar este mapa
+ {
+ { 0.563, 10.008, 10.113, 10.172, 10.676, 11.895, 12.691, 13.406, 14.273, 21.059}, //RPM = 99.75
+ { 0.609, 10.324, 10.559, 10.688, 11.531, 12.668, 13.535, 13.910, 14.730, 20.730}, //RPM = 199.75
+ { 0.527, 11.098, 11.438, 11.672, 13.078, 14.602, 15.680, 16.289, 17.742, 18.680}, //RPM = 399.75
+ { 1.031, 14.977, 15.410, 15.727, 17.215, 20.238, 22.348, 23.801, 26.484, 28.441}, //RPM = 999.75
+ { 0.984, 19.594, 20.016, 20.262, 22.066, 27.398, 30.633, 33.188, 33.188, 33.188}, //RPM = 1749.75
+ { 1.066, 22.266, 22.863, 23.074, 25.090, 30.961, 34.816, 38.145, 38.109, 38.121}, //RPM = 2149.50
+ }
+};
+struct AlphaStruct fuelmap_m5 = { //probar este mapa
+ {
+ { 0.656, 10.102, 10.207, 10.266, 10.758, 12.023, 12.867, 13.570, 14.438, 21.223}, //RPM = 99.75
+ { 0.691, 10.406, 10.641, 10.758, 11.613, 12.949, 13.781, 14.168, 14.965, 20.977}, //RPM = 199.75
+ { 0.656, 11.227, 11.578, 11.801, 13.219, 14.801, 15.926, 16.629, 18.082, 19.031}, //RPM = 399.75
+ { 1.078, 15.023, 15.457, 15.773, 17.262, 20.402, 22.711, 24.246, 26.930, 28.887}, //RPM = 999.75
+ { 1.172, 19.781, 20.215, 20.461, 22.266, 27.703, 31.125, 33.926, 33.961, 33.949}, //RPM = 1749.75
+ { 1.313, 22.500, 23.098, 23.309, 25.336, 31.313, 35.449, 39.105, 39.070, 39.094}, //RPM = 2149.75
+ }
+};
+struct AlphaStruct fuelmap_10 = { //probar este mapa
+ {
+ { 0.762, 10.207, 10.313, 10.371, 10.863, 12.164, 13.078, 13.770, 14.637, 21.410}, //RPM = 99.75
+ { 0.773, 10.500, 10.723, 10.863, 11.695, 13.301, 14.086, 14.461, 15.270, 21.281}, //RPM = 199.75
+ { 0.832, 11.402, 11.754, 11.977, 13.395, 15.047, 16.242, 17.039, 18.492, 19.441}, //RPM = 399.75
+ { 1.137, 15.082, 15.516, 15.832, 17.320, 20.602, 23.133, 24.785, 27.492, 29.461}, //RPM = 999.75
+ { 1.430, 20.039, 20.438, 20.707, 22.488, 28.078, 31.734, 34.898, 34.922, 34.793}, //RPM = 1749.75
+ { 1.605, 22.793, 23.391, 23.602, 25.629, 31.781, 36.211, 40.277, 40.289, 40.289}, //RPM = 2149.75
+ }
+};
+struct AlphaStruct fuelmap_25 = { //probar este mapa
+ {
+ { 0.844, 10.289, 10.395, 10.453, 10.945, 12.293, 13.242, 13.922, 14.789, 21.574}, //RPM = 99.75
+ { 0.855, 10.570, 10.816, 10.934, 11.777, 13.559, 14.309, 14.695, 15.504, 21.516}, //RPM = 199.75
+ { 0.961, 11.531, 11.883, 12.105, 13.512, 15.234, 16.500, 17.367, 18.820, 19.770}, //RPM = 399.75
+ { 1.172, 15.117, 15.563, 15.867, 17.355, 20.754, 23.496, 25.219, 27.902, 29.859}, //RPM = 999.75
+ { 1.617, 20.215, 20.648, 20.883, 22.688, 28.348, 32.203, 35.555, 35.555, 35.566}, //RPM = 1749.75
+ { 1.828, 23.004, 23.613, 23.824, 25.852, 32.133, 36.773, 41.203, 41.227, 41.215}, //RPM = 2149.75
+ }
+};
+struct AlphaStruct fuelmap_60 = { //probar este mapa
+ {
+ { 0.938, 10.383, 10.488, 10.547, 11.039, 12.410, 13.418, 14.086, 14.953, 21.738}, //RPM = 99.75
+ { 0.949, 10.664, 10.898, 11.027, 11.871, 13.863, 14.578, 14.965, 15.773, 21.773}, //RPM = 199.75
+ { 1.113, 11.684, 12.035, 12.258, 13.664, 15.457, 16.770, 17.730, 19.195, 20.145}, //RPM = 399.75
+ { 1.230, 15.176, 15.609, 15.926, 17.414, 20.930, 23.895, 25.711, 28.395, 30.352}, //RPM = 999.75
+ { 1.828, 20.426, 20.848, 21.094, 22.910, 28.699, 32.754, 36.363, 36.387, 36.398}, //RPM = 1749.75
+ { 2.098, 23.273, 23.883, 24.094, 26.121, 32.543, 37.512, 42.293, 42.305, 42.316}, //RPM = 2149.75
+ }
+};
+/*struct AlphaStruct fuelmap_80 = { //probar este mapa
+ {
+ { 6.234, 8.531, 8.602, 8.613, 8.672, 9.234, 11.320, 11.508, 12.176, 12.844, 14.285, 15.445}, //RPM = 99
+ { 5.625, 7.910, 8.098, 8.238, 8.684, 9.996, 11.801, 11.918, 12.516, 13.102, 14.332, 15.316}, //RPM = 199
+ { 4.582, 6.949, 7.266, 7.664, 8.859, 9.891, 12.188, 12.844, 13.348, 13.840, 15.387, 16.594}, //RPM = 424
+ { 4.160, 6.527, 6.973, 7.441, 8.801, 9.668, 12.234, 12.879, 13.523, 14.203, 15.973, 17.367}, //RPM = 499
+ { 2.496, 5.145, 5.988, 6.434, 7.676, 8.520, 12.832, 13.840, 14.859, 15.891, 18.855, 21.188}, //RPM = 895
+ { 2.109, 4.805, 5.742, 6.141, 7.313, 8.238, 12.984, 14.109, 15.223, 16.336, 19.582, 22.172}, //RPM = 999
+ { 0.914, 4.031, 4.945, 5.320, 6.387, 7.594, 13.488, 15.117, 16.723, 18.328, 22.465, 24.598}, //RPM = 1399
+ { 0.551, 3.727, 4.652, 5.074, 6.281, 7.488, 13.816, 15.598, 17.203, 18.820, 23.145, 24.598}, //RPM = 1499
+ { -1.383, 2.156, 3.023, 3.621, 5.320, 6.773, 14.754, 16.922, 19.066, 21.223, 24.598, 24.598}, //RPM = 1999
+ { -2.191, 1.477, 2.262, 2.906, 4.746, 6.363, 14.766, 16.898, 19.523, 22.160, 24.598, 24.598}, //RPM = 2199
+ }
+};*/
+
+struct fuelMapIndexes fuelMapI = { //probar este mapa
+ { 100, 200, 400, 1000, 1750, 2150}, //N_RPM = 17
+ { 0.031, 0.406, 1.656, 2.406, 8.313, 24.906, 37.313, 45.688, 60.000, 72.000},
+ { -15.0, 5.1 ,30, 50, 74.5}
+};
+#elif defined(_504009)
+struct AlphaStruct fuelmap_m12 = { //probar este mapa
+ {
+ { 0.563, 9.387, 9.680, 9.785, 10.406, 11.742, 12.434, 12.844, 13.898, 20.602}, //RPM = 99.75
+ { 0.598, 9.785, 9.820, 9.996, 11.063, 12.762, 13.020, 13.242, 14.180, 20.145}, //RPM = 199.75
+ { 0.680, 10.910, 11.227, 11.426, 12.867, 14.355, 14.988, 15.516, 16.758, 17.613}, //RPM = 399.75
+ { 0.949, 14.777, 15.164, 15.410, 16.605, 19.207, 21.059, 22.219, 24.609, 26.367}, //RPM = 999.50
+ { 1.137, 19.277, 19.629, 19.875, 21.234, 25.582, 28.324, 30.281, 34.922, 37.922}, //RPM = 1749.75
+ { 1.336, 22.617, 23.496, 23.801, 25.523, 29.719, 32.742, 34.676, 40.770, 42.176}, //RPM = 2149.75
+ }
+};
+struct AlphaStruct fuelmap_m5 = { //probar este mapa
+ {
+ { 0.633, 9.457, 9.750, 9.855, 10.477, 11.848, 12.645, 13.148, 14.203, 20.906}, //RPM = 99.75
+ { 0.668, 9.855, 9.891, 10.066, 11.133, 12.855, 13.230, 13.547, 14.484, 20.438}, //RPM = 199.75
+ { 0.738, 10.969, 11.285, 11.484, 12.926, 14.461, 15.199, 15.809, 17.051, 17.906}, //RPM = 399.75
+ { 1.020, 14.848, 15.234, 15.480, 16.676, 19.406, 21.352, 22.641, 25.031, 26.801}, //RPM = 999.75
+ { 1.266, 19.406, 19.758, 20.004, 21.363, 25.840, 28.734, 30.902, 35.543, 38.543}, //RPM = 1749.75
+ { 1.488, 22.770, 23.672, 23.953, 25.652, 29.988, 33.117, 35.473, 41.555, 42.984}, //RPM = 2149.75
+ }
+};
+struct AlphaStruct fuelmap_10 = { //probar este mapa
+ {
+ { 0.715, 9.539, 9.832, 9.938, 10.559, 11.965, 12.902, 13.512, 14.566, 21.270}, //RPM = 99.75
+ { 0.750, 9.938, 9.961, 10.148, 11.203, 12.984, 13.488, 13.898, 14.836, 20.801}, //RPM = 199.75
+ { 0.820, 11.051, 11.367, 11.566, 13.008, 14.578, 15.457, 16.172, 17.414, 18.270}, //RPM = 399.75
+ { 1.113, 14.941, 15.328, 15.574, 16.770, 19.641, 21.703, 23.156, 25.547, 27.316}, //RPM = 999.75
+ { 1.430, 19.570, 19.922, 20.168, 21.527, 26.168, 29.238, 31.688, 36.316, 39.316}, //RPM = 1749.75
+ { 1.676, 22.934, 23.859, 24.117, 25.840, 30.328, 33.609, 36.457, 42.563, 43.969}, //RPM = 2149.75
+ }
+};
+struct AlphaStruct fuelmap_25 = { //probar este mapa
+ {
+ { 0.773, 9.598, 9.891, 9.996, 10.617, 12.059, 13.102, 13.793, 14.836, 21.539}, //RPM = 99.75
+ { 0.809, 9.996, 10.031, 10.207, 11.273, 13.078, 13.676, 14.180, 15.129, 21.082}, //RPM = 199.75
+ { 0.879, 11.109, 11.426, 11.625, 13.066, 14.672, 15.656, 16.453, 17.695, 18.551}, //RPM = 399.75
+ { 1.184, 15.012, 15.398, 15.645, 16.840, 19.828, 21.984, 23.555, 25.945, 27.715}, //RPM = 999.75
+ { 1.547, 19.688, 20.039, 20.285, 21.645, 26.414, 29.613, 32.262, 36.914, 39.902}, //RPM = 1749.75
+ { 1.805, 23.086, 23.988, 24.328, 25.980, 30.598, 33.973, 37.207, 43.301, 44.707}, //RPM = 2149.75
+ }
+};
+struct AlphaStruct fuelmap_60 = { //probar este mapa
+ {
+ { 0.855, 9.680, 9.973, 10.078, 10.699, 12.176, 13.348, 14.145, 15.188, 21.891}, //RPM = 99.75
+ { 0.879, 10.066, 10.102, 10.277, 11.344, 13.195, 13.934, 14.531, 15.469, 21.434}, //RPM = 199.75
+ { 0.949, 11.191, 11.508, 11.707, 13.148, 14.789, 15.902, 16.781, 18.047, 18.902}, //RPM = 399.50
+ { 1.277, 15.105, 15.492, 15.738, 16.934, 20.063, 22.324, 24.047, 26.438, 28.207}, //RPM = 999.75
+ { 1.699, 19.840, 20.191, 20.438, 21.797, 26.730, 30.094, 33.000, 37.664, 40.652}, //RPM = 1749.75
+ { 1.992, 23.262, 24.164, 24.457, 26.145, 30.914, 34.418, 38.227, 44.238, 45.680}, //RPM = 2150.00
+ }
+};
+
+
+struct fuelMapIndexes fuelMapI = { //probar este mapa
+ { 100, 200, 400, 1000, 1750, 2150}, //N_RPM = 17
+ { 0.031, 0.406, 1.656, 2.406, 8.313, 24.906, 37.313, 45.688, 60.000, 72.000},
+ { -15.0, 5.1 ,30, 50, 74.5}
+};
#endif
diff --git a/Core/Src/main.c b/Core/Src/main.c
index 0f2c336..baf9efd 100644
--- a/Core/Src/main.c
+++ b/Core/Src/main.c
@@ -31,8 +31,12 @@
#include "ee_manager.h"
#include "toothed_wheel.h"
#include "can_read_pump_data.h"
-#include "kline.h"
-#include "IKW1281Connection.h"
+/*#include "kline.h"
+#include "IKW1281Connection.h"*/
+#include "kl_api.h"
+#include "kl_protocol.h"
+#include "kl_session.h"
+
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
@@ -245,6 +249,27 @@ void CAN_AppInit(void)
can_read_pump_data_register(); // wraps 0x502 and sends EMPF2 for immobiliser pattern
}
uint32_t canfails = 0;
+
+void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
+ if (huart->Instance == USART1) KL_Phy_TxCpltCB();
+}
+
+void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
+ if (huart->Instance == USART1) KL_Phy_RxCpltCB();
+}
+void EXTI15_10_IRQHandler(void)
+{
+ HAL_GPIO_EXTI_IRQHandler(KL_RX_PIN);
+}
+void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
+ if (GPIO_Pin == KL_RX_PIN) {
+ KL_Session_OnExtiRxFalling();
+ }
+}
+
+void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
+ if (htim->Instance == TIM17) KL_Session_OnTim17Elapsed();
+}
/* USER CODE END 0 */
/**
@@ -348,7 +373,7 @@ int main(void)
SYNC_Pulse_EnableGPIO();
}*/
- KLine_Slave_Init();
+ KLine_Init();
/* USER CODE END 2 */
@@ -360,7 +385,7 @@ int main(void)
/* USER CODE BEGIN 3 */
KLine_Service();
- KLine_ServiceCommands(); // <-- NEW: react to ISR kick, process full packet
+ //KLine_ServiceCommands(); // <-- NEW: react to ISR kick, process full packet
TW_Service();
CAN_Service(); // drain queued frames whenever HW has room
diff --git a/Core/Src/stm32g4xx_hal_msp.c b/Core/Src/stm32g4xx_hal_msp.c
index 4dc40ee..13bb63e 100644
--- a/Core/Src/stm32g4xx_hal_msp.c
+++ b/Core/Src/stm32g4xx_hal_msp.c
@@ -1039,7 +1039,14 @@ void HAL_UART_MspInit(UART_HandleTypeDef* huart)
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
- GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
+ GPIO_InitStruct.Pin = GPIO_PIN_9;
+ GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
+ GPIO_InitStruct.Pull = GPIO_NOPULL;
+ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+ GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
+ HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+ GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
diff --git a/hpsg5-controller_v2-stm32g4.ioc b/hpsg5-controller_v2-stm32g4.ioc
index 9e20296..a987440 100644
--- a/hpsg5-controller_v2-stm32g4.ioc
+++ b/hpsg5-controller_v2-stm32g4.ioc
@@ -184,6 +184,10 @@ 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.GPIOParameters=GPIO_ModeDefaultPP,GPIO_Speed,GPIO_PuPd
+PA10.GPIO_ModeDefaultPP=GPIO_MODE_AF_PP
+PA10.GPIO_PuPd=GPIO_NOPULL
+PA10.GPIO_Speed=GPIO_SPEED_FREQ_LOW
PA10.Mode=Asynchronous
PA10.Signal=USART1_RX
PA11.Mode=FDCAN_Activate
@@ -216,6 +220,10 @@ PA6.Signal=OPAMP2_VOUT
PA7.Locked=true
PA7.Mode=Follower
PA7.Signal=OPAMP2_VINP
+PA9.GPIOParameters=GPIO_ModeDefaultPP,GPIO_Speed,GPIO_PuPd
+PA9.GPIO_ModeDefaultPP=GPIO_MODE_AF_PP
+PA9.GPIO_PuPd=GPIO_NOPULL
+PA9.GPIO_Speed=GPIO_SPEED_FREQ_HIGH
PA9.Mode=Asynchronous
PA9.Signal=USART1_TX
PB0.GPIOParameters=GPIO_Label