515 lines
16 KiB
C
515 lines
16 KiB
C
/*
|
|
* kl_session.c
|
|
* Session layer: 5-baud init, handshake, ident stream, session lifecycle.
|
|
*
|
|
* All operations are non-blocking state machines.
|
|
* The 5-baud sampling is ISR-driven via TIM17.
|
|
*/
|
|
|
|
#include "kl_session.h"
|
|
#include "kl_phy.h"
|
|
#include "kl_transport.h"
|
|
#include "kl_app.h"
|
|
#include "psg_prop.h"
|
|
#include "main.h"
|
|
#include <string.h>
|
|
|
|
extern TIM_HandleTypeDef htim17;
|
|
|
|
static KL_Session sess;
|
|
|
|
/* Forward declarations for BuildCombined (ident data) */
|
|
static size_t sess_build_ident(uint8_t *out, size_t maxlen);
|
|
|
|
/* ================================================================
|
|
* TIM17 configuration (0.1ms tick for 5-baud sampling)
|
|
* ================================================================ */
|
|
static void tim17_config(void)
|
|
{
|
|
__HAL_TIM_DISABLE(&htim17);
|
|
__HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
|
|
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
|
|
|
|
uint32_t timclk = HAL_RCC_GetPCLK2Freq();
|
|
RCC_ClkInitTypeDef clk;
|
|
uint32_t fl;
|
|
HAL_RCC_GetClockConfig(&clk, &fl);
|
|
if (clk.APB2CLKDivider != RCC_HCLK_DIV1) timclk *= 2U;
|
|
|
|
const uint32_t target_hz = 10000U;
|
|
uint32_t psc = (timclk + target_hz - 1U) / target_hz - 1U;
|
|
if (psc > 0xFFFFU) psc = 0xFFFFU;
|
|
|
|
__HAL_TIM_SET_PRESCALER(&htim17, psc);
|
|
__HAL_TIM_SET_AUTORELOAD(&htim17, 9U);
|
|
__HAL_TIM_SET_COUNTER(&htim17, 0U);
|
|
|
|
htim17.Instance->CR1 |= (TIM_CR1_OPM | TIM_CR1_URS);
|
|
htim17.Instance->EGR = TIM_EGR_UG;
|
|
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
|
|
|
|
HAL_NVIC_SetPriority(TIM1_TRG_COM_TIM17_IRQn, 6, 0);
|
|
HAL_NVIC_EnableIRQ(TIM1_TRG_COM_TIM17_IRQn);
|
|
}
|
|
|
|
static void tim17_schedule(uint32_t delay_ms)
|
|
{
|
|
uint32_t ticks = delay_ms * 10U;
|
|
if (ticks < 1U) ticks = 1U;
|
|
if (ticks > 0x10000U) ticks = 0x10000U;
|
|
|
|
__HAL_TIM_DISABLE(&htim17);
|
|
__HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
|
|
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
|
|
|
|
__HAL_TIM_SET_AUTORELOAD(&htim17, (uint16_t)(ticks - 1U));
|
|
__HAL_TIM_SET_COUNTER(&htim17, 0U);
|
|
|
|
htim17.Instance->EGR = TIM_EGR_UG;
|
|
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
|
|
|
|
htim17.Instance->CR1 |= (TIM_CR1_OPM | TIM_CR1_URS);
|
|
|
|
__HAL_TIM_ENABLE_IT(&htim17, TIM_IT_UPDATE);
|
|
__HAL_TIM_ENABLE(&htim17);
|
|
}
|
|
|
|
/* ================================================================
|
|
* EXTI / 5-baud arming
|
|
* ================================================================ */
|
|
static void sess_rearm_5baud(void)
|
|
{
|
|
KL_Phy_DisableUart();
|
|
|
|
__HAL_RCC_SYSCFG_CLK_ENABLE();
|
|
|
|
/* TX pin idle high (input with pull-up) */
|
|
GPIO_InitTypeDef giTx = {0};
|
|
giTx.Pin = KL_TX_PIN;
|
|
giTx.Mode = GPIO_MODE_INPUT;
|
|
giTx.Pull = GPIO_PULLUP;
|
|
giTx.Speed = GPIO_SPEED_FREQ_LOW;
|
|
HAL_GPIO_Init(KL_GPIO_PORT, &giTx);
|
|
HAL_GPIO_WritePin(KL_GPIO_PORT, KL_TX_PIN, GPIO_PIN_SET);
|
|
|
|
/* RX pin: EXTI falling edge */
|
|
GPIO_InitTypeDef giRx = {0};
|
|
giRx.Pin = KL_RX_PIN;
|
|
giRx.Mode = GPIO_MODE_IT_FALLING;
|
|
giRx.Pull = GPIO_PULLUP;
|
|
HAL_GPIO_Init(KL_GPIO_PORT, &giRx);
|
|
|
|
__HAL_GPIO_EXTI_CLEAR_IT(KL_RX_PIN);
|
|
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 5, 0);
|
|
HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
|
|
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
|
|
}
|
|
|
|
static void sess_reset_to_idle(void)
|
|
{
|
|
sess.state = KL_SESS_IDLE;
|
|
sess.five_bit_index = 0;
|
|
sess.five_byte = 0;
|
|
sess.five_active = 0;
|
|
sess.connection_status = 0;
|
|
sess.end_requested = 0;
|
|
|
|
KL_Transport_ResetCounter();
|
|
|
|
HAL_TIM_Base_Stop_IT(&htim17);
|
|
__HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
|
|
|
|
__HAL_GPIO_EXTI_CLEAR_IT(KL_RX_PIN);
|
|
HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
|
|
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
|
|
}
|
|
|
|
/* ================================================================
|
|
* Init
|
|
* ================================================================ */
|
|
void KL_Session_Init(void)
|
|
{
|
|
memset(&sess, 0, sizeof(sess));
|
|
KL_Phy_Init();
|
|
tim17_config();
|
|
sess_rearm_5baud();
|
|
}
|
|
|
|
/* ================================================================
|
|
* ISR: GPIO EXTI falling edge on RX pin (start of 5-baud)
|
|
* ================================================================ */
|
|
void KL_Session_OnExtiRxFalling(void)
|
|
{
|
|
if (sess.state == KL_SESS_IDLE) {
|
|
HAL_NVIC_DisableIRQ(EXTI15_10_IRQn);
|
|
sess.five_active = 1;
|
|
sess.five_bit_index = 0;
|
|
sess.five_byte = 0;
|
|
sess.state = KL_SESS_5BAUD_SAMPLING;
|
|
tim17_schedule(KL_5BAUD_FIRST_MS);
|
|
}
|
|
}
|
|
|
|
/* ================================================================
|
|
* ISR: TIM17 period elapsed (5-baud bit sampling)
|
|
* ================================================================ */
|
|
void KL_Session_OnTim17Elapsed(void)
|
|
{
|
|
if (!sess.five_active) {
|
|
HAL_TIM_Base_Stop_IT(&htim17);
|
|
return;
|
|
}
|
|
|
|
if (sess.state == KL_SESS_5BAUD_SAMPLING) {
|
|
GPIO_PinState pin = HAL_GPIO_ReadPin(KL_GPIO_PORT, KL_RX_PIN);
|
|
uint8_t bit = (pin == GPIO_PIN_SET) ? 1U : 0U;
|
|
|
|
sess.five_byte |= (bit << sess.five_bit_index);
|
|
sess.five_bit_index++;
|
|
|
|
if (sess.five_bit_index >= 8) {
|
|
sess.five_active = 0;
|
|
HAL_TIM_Base_Stop_IT(&htim17);
|
|
|
|
if (sess.five_byte != KL_SLAVE_ADDR) {
|
|
sess_reset_to_idle();
|
|
sess_rearm_5baud();
|
|
return;
|
|
}
|
|
|
|
sess.connection_status = 1;
|
|
sess.hs_next_tick = HAL_GetTick() + KL_W1_MS;
|
|
sess.state = KL_SESS_5BAUD_DONE;
|
|
} else {
|
|
tim17_schedule(KL_5BAUD_BIT_MS);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ================================================================
|
|
* Session kick (extend deadline)
|
|
* ================================================================ */
|
|
void KL_Session_Kick(void)
|
|
{
|
|
sess.session_deadline = HAL_GetTick() + KL_SESSION_TMO_MS;
|
|
}
|
|
|
|
void KL_Session_RequestEnd(void)
|
|
{
|
|
sess.end_requested = 1;
|
|
}
|
|
|
|
uint8_t KL_Session_GetStatus(void)
|
|
{
|
|
return sess.connection_status;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Build identification data
|
|
* ================================================================ */
|
|
static size_t append_str(const char *s, size_t n, uint8_t *out, size_t w, size_t maxlen)
|
|
{
|
|
for (size_t i = 0; i < n && s[i] != '\0' && w < maxlen; i++) {
|
|
out[w++] = (uint8_t)s[i];
|
|
}
|
|
return w;
|
|
}
|
|
|
|
static size_t sess_build_ident(uint8_t *out, size_t maxlen)
|
|
{
|
|
size_t w = 0;
|
|
w = append_str(PSG_KUNDENNUMMER_STR, 12, out, w, maxlen);
|
|
w = append_str(PSG_DATENSATZ_STR, 10, out, w, maxlen);
|
|
w = append_str(PSG_SOFTWARE_VER_STR, 10, out, w, maxlen);
|
|
w = append_str(PSG_SOFTWARE_VER2_STR, 10, out, w, maxlen);
|
|
w = append_str(PSG_STEUERGERAET_STR, 10, out, w, maxlen);
|
|
w = append_str(PSG_UNKNOWN_STR, 6, out, w, maxlen);
|
|
return w;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Terminate session and rearm 5-baud
|
|
* ================================================================ */
|
|
static void sess_terminate(void)
|
|
{
|
|
sess.connection_status = 0;
|
|
sess.session_deadline = 0;
|
|
sess.end_requested = 0;
|
|
|
|
KL_App_Reset();
|
|
KL_Transport_ResetCounter();
|
|
KL_Transport_TxReset();
|
|
KL_Transport_RxReset();
|
|
KL_Phy_DisableUart();
|
|
sess_rearm_5baud();
|
|
|
|
sess.state = KL_SESS_IDLE;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Main session service (called from KLine_Service)
|
|
* ================================================================ */
|
|
void KL_Session_Service(void)
|
|
{
|
|
uint32_t now = HAL_GetTick();
|
|
|
|
switch (sess.state) {
|
|
|
|
case KL_SESS_IDLE:
|
|
case KL_SESS_5BAUD_SAMPLING:
|
|
/* Driven by ISR */
|
|
break;
|
|
|
|
/* ---- 5-baud done: wait W1 then enter UART mode ---- */
|
|
case KL_SESS_5BAUD_DONE:
|
|
if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
|
|
KL_Phy_EnableUart9600();
|
|
KL_Transport_Init();
|
|
KL_Phy_RxFlush();
|
|
|
|
sess.hs_retry_count = 0;
|
|
sess.hs_next_tick = now;
|
|
sess.connection_status = 1;
|
|
sess.state = KL_SESS_HS_SYNC;
|
|
}
|
|
break;
|
|
|
|
/* ---- Handshake: send sync byte ---- */
|
|
case KL_SESS_HS_SYNC:
|
|
if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
|
|
if (KL_Transport_SendRawByte(KL_SYNC_BYTE)) {
|
|
sess.state = KL_SESS_HS_SYNC_WAIT;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KL_SESS_HS_SYNC_WAIT:
|
|
if (KL_Transport_RawTxDone()) {
|
|
sess.hs_next_tick = HAL_GetTick() + KL_W2_MS;
|
|
KL_Transport_RawTxReset();
|
|
sess.state = KL_SESS_HS_KW_LSB;
|
|
}
|
|
break;
|
|
|
|
/* ---- Handshake: send keyword LSB ---- */
|
|
case KL_SESS_HS_KW_LSB:
|
|
if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
|
|
if (KL_Transport_SendRawByte(KL_KEYWORD_LSB)) {
|
|
sess.state = KL_SESS_HS_KW_LSB_WAIT;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KL_SESS_HS_KW_LSB_WAIT:
|
|
if (KL_Transport_RawTxDone()) {
|
|
sess.hs_next_tick = HAL_GetTick() + KL_W3_MS;
|
|
KL_Transport_RawTxReset();
|
|
sess.state = KL_SESS_HS_KW_MSB;
|
|
}
|
|
break;
|
|
|
|
/* ---- Handshake: send keyword MSB ---- */
|
|
case KL_SESS_HS_KW_MSB:
|
|
if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
|
|
if (KL_Transport_SendRawByte(KL_KEYWORD_MSB)) {
|
|
sess.state = KL_SESS_HS_KW_MSB_WAIT;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KL_SESS_HS_KW_MSB_WAIT:
|
|
if (KL_Transport_RawTxDone()) {
|
|
KL_Transport_RawTxReset();
|
|
/* Set deadline for master ACK */
|
|
sess.hs_next_tick = HAL_GetTick() + KL_HS_RETRY_MS;
|
|
sess.state = KL_SESS_HS_WAIT_ACK;
|
|
}
|
|
break;
|
|
|
|
/* ---- Handshake: wait for master ~KW_MSB ---- */
|
|
case KL_SESS_HS_WAIT_ACK:
|
|
{
|
|
uint8_t rx_byte;
|
|
if (KL_Phy_RxPop(&rx_byte)) {
|
|
if (rx_byte == (uint8_t)~KL_KEYWORD_MSB) {
|
|
/* Handshake succeeded -- prepare identification */
|
|
sess.ident_len = (uint8_t)sess_build_ident(sess.ident_buf,
|
|
sizeof(sess.ident_buf));
|
|
sess.ident_offset = 0;
|
|
KL_Phy_RxFlush();
|
|
sess.session_deadline = HAL_GetTick() + KL_SESSION_TMO_MS;
|
|
sess.state = KL_SESS_IDENT_SEND;
|
|
break;
|
|
}
|
|
/* Unexpected byte, fall through to retry */
|
|
}
|
|
if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
|
|
sess.hs_retry_count++;
|
|
if (sess.hs_retry_count >= KL_HANDSHAKE_MAX_RETRIES) {
|
|
sess_terminate();
|
|
} else {
|
|
sess.hs_next_tick = now;
|
|
sess.state = KL_SESS_HS_SYNC;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* ---- Identification: send ASCII chunks ---- */
|
|
case KL_SESS_IDENT_SEND:
|
|
{
|
|
uint8_t remaining = sess.ident_len - sess.ident_offset;
|
|
sess.ident_chunk_len = (remaining > KL_ASCII_CHUNK_SIZE)
|
|
? KL_ASCII_CHUNK_SIZE : remaining;
|
|
|
|
uint8_t payload[1 + KL_ASCII_CHUNK_SIZE];
|
|
payload[0] = (uint8_t)KL_CMD_AsciiData;
|
|
memcpy(&payload[1], &sess.ident_buf[sess.ident_offset], sess.ident_chunk_len);
|
|
|
|
if (KL_Transport_SendPacket(payload, (uint8_t)(1 + sess.ident_chunk_len))) {
|
|
sess.state = KL_SESS_IDENT_WAIT_TX;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case KL_SESS_IDENT_WAIT_TX:
|
|
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
|
|
sess_terminate();
|
|
break;
|
|
}
|
|
if (KL_Transport_TxDone()) {
|
|
KL_Transport_TxReset();
|
|
sess.ident_offset += sess.ident_chunk_len;
|
|
if (sess.ident_offset >= sess.ident_len) {
|
|
/* Last chunk sent -- go straight to ready.
|
|
* No ACK expected after the final chunk. */
|
|
KL_Session_Kick();
|
|
sess.connection_status = 2;
|
|
sess.state = KL_SESS_READY;
|
|
} else {
|
|
/* Intermediate chunk: wait for ACK before next */
|
|
KL_Transport_StartReceive(400);
|
|
sess.state = KL_SESS_IDENT_WAIT_ACK;
|
|
}
|
|
} else if (KL_Transport_TxError()) {
|
|
sess_terminate();
|
|
}
|
|
break;
|
|
|
|
case KL_SESS_IDENT_WAIT_ACK:
|
|
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
|
|
sess_terminate();
|
|
break;
|
|
}
|
|
if (KL_Transport_RxDone()) {
|
|
const KL_Packet *pkt = KL_Transport_GetRxPacket();
|
|
KL_Transport_RxReset();
|
|
if (pkt->command == (uint8_t)KL_CMD_ACK) {
|
|
sess.state = KL_SESS_IDENT_SEND;
|
|
} else {
|
|
sess_terminate();
|
|
}
|
|
} else if (KL_Transport_RxError()) {
|
|
sess_terminate();
|
|
}
|
|
break;
|
|
|
|
case KL_SESS_IDENT_FINAL_WAIT:
|
|
/* No longer used, kept for enum completeness */
|
|
if (KL_Transport_TxDone()) {
|
|
KL_Transport_TxReset();
|
|
KL_Session_Kick();
|
|
sess.connection_status = 2;
|
|
sess.state = KL_SESS_READY;
|
|
} else if (KL_Transport_TxError()) {
|
|
sess_terminate();
|
|
}
|
|
break;
|
|
|
|
/* ---- Active session: ready to receive commands ---- */
|
|
case KL_SESS_READY:
|
|
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
|
|
sess_terminate();
|
|
break;
|
|
}
|
|
/* Use the session deadline as the RX timeout so we don't
|
|
* artificially cut short a receive that started late.
|
|
* Compute remaining time until session deadline. */
|
|
{
|
|
int32_t remaining = (int32_t)(sess.session_deadline - now);
|
|
if (remaining <= 0) { sess_terminate(); break; }
|
|
if (KL_Transport_StartReceive((uint32_t)remaining)) {
|
|
sess.state = KL_SESS_CMD_RX;
|
|
}
|
|
/* If StartReceive fails (rx not idle), stay in READY
|
|
* and retry next cycle rather than advancing to CMD_RX
|
|
* with no active receive. */
|
|
}
|
|
break;
|
|
|
|
case KL_SESS_CMD_RX:
|
|
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
|
|
KL_Transport_RxReset();
|
|
sess_terminate();
|
|
break;
|
|
}
|
|
if (KL_Transport_RxDone()) {
|
|
KL_Session_Kick();
|
|
sess.state = KL_SESS_CMD_DISPATCH;
|
|
} else if (KL_Transport_RxError()) {
|
|
KL_Transport_RxReset();
|
|
/* Only send NAK if we actually received partial data
|
|
* (i.e., at least the length byte was consumed).
|
|
* A plain timeout with no data is just "no packet yet"
|
|
* and should silently retry. */
|
|
if (KL_Transport_RxHadData()) {
|
|
KL_Transport_SendNak();
|
|
sess.state = KL_SESS_CMD_NAK_WAIT;
|
|
} else {
|
|
sess.state = KL_SESS_READY;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KL_SESS_CMD_NAK_WAIT:
|
|
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
|
|
KL_Transport_TxReset();
|
|
sess_terminate();
|
|
break;
|
|
}
|
|
if (KL_Transport_TxDone() || KL_Transport_TxError()) {
|
|
KL_Transport_TxReset();
|
|
sess.state = KL_SESS_READY;
|
|
}
|
|
break;
|
|
|
|
case KL_SESS_CMD_DISPATCH:
|
|
{
|
|
const KL_Packet *pkt = KL_Transport_GetRxPacket();
|
|
KL_Transport_RxReset();
|
|
KL_App_Dispatch(pkt);
|
|
sess.state = KL_SESS_CMD_RESPONSE;
|
|
break;
|
|
}
|
|
|
|
case KL_SESS_CMD_RESPONSE:
|
|
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
|
|
sess_terminate();
|
|
break;
|
|
}
|
|
if (KL_App_Done()) {
|
|
if (sess.end_requested) {
|
|
sess_terminate();
|
|
} else {
|
|
KL_Session_Kick();
|
|
sess.state = KL_SESS_READY;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KL_SESS_TERMINATE:
|
|
sess_terminate();
|
|
break;
|
|
}
|
|
}
|