Files
hpsg5-controller_v2-stm32g4/Core/Kline_Libs/kline.c

1282 lines
40 KiB
C
Raw Blame History

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