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