Skip to content

Pairing #213

@mayurkumarthummar1997

Description

@mayurkumarthummar1997

Both Directions Pairing Mechanism

Microcontroller: 2 × STM32F103C8T6
RF Module: 2 × HC-12

A. Role define
RX= Master, TX = Slave
User Manually define master=1 and slave=2

B. HC-12 module setting:
void setupHC12() {
pinMode(HC12_SET_PIN, OUTPUT);
digitalWrite(HC12_SET_PIN, HIGH); // normal mode initially
// 1. Enter AT mode at factory/default speed
HC12_UART.begin(9600);
delay(100);
digitalWrite(HC12_SET_PIN, LOW);
delay(100); // >40 ms, 100 ms is safe
// Test communication and check response
HC12_UART.print("AT\r\n");
delay(100);
String response = readHC12Response(100); // Implement as buffer read below
if (response.indexOf("OK") == -1) {
Serial.println("HC-12 AT mode failed");
// Add error handling, e.g., LED blink
}
// 2. Set FU4 → module usually switches to 1200 bps now
HC12_UART.print("AT+FU4\r\n");
delay(150); // give time to process + switch baud
response = readHC12Response(150);
if (response.indexOf("OK") == -1) {
Serial.println("HC-12 FU4 failed");
}
// 3. Immediately switch our UART to 1200
HC12_UART.end();
delay(20);
HC12_UART.begin(1200);
// Small delay helps some modules stabilize after baud change
delay(100);
// 4. Now send remaining commands safely at 1200 bps
HC12_UART.print("AT+C001\r\n");
delay(150);
response = readHC12Response(150);
if (response.indexOf("OK") == -1) {
Serial.println("HC-12 C001 failed");
}
HC12_UART.print("AT+P8\r\n"); // +20 dBm
delay(150);
response = readHC12Response(150);
if (response.indexOf("OK") == -1) {
Serial.println("HC-12 P8 failed");
}
// Optional – very useful for debugging!
HC12_UART.print("AT+RX\r\n");
delay(300);
response = readHC12Response(300);
Serial.print("HC-12 RX: "); Serial.println(response);
// 5. Exit AT mode
digitalWrite(HC12_SET_PIN, HIGH);
delay(100);
// Now HC-12 is in transparent mode @ 1200 bps
Serial.println("HC-12 configured: FU4, Ch 001, P8, 1200 baud");
}

// Helper for reading responses with buffer (robust to chunks)
String readHC12Response(uint32_t timeoutMs) {
String resp = "";
uint32_t start = millis();
while (millis() - start < timeoutMs) {
while (HC12_UART.available()) {
char c = HC12_UART.read();
if (c == '\n') return resp;
resp += c;
if (resp.length() > 64) { // Overflow protection
return "ERROR: Overflow";
}
}
delay(10);
}
return resp; // Partial or timeout
}

C. Pin connection map:

PA8 = SET pin
PA10 = Tx
PA9 = Rx
PA1 = LED 1: Detection indicator LED
PA2 = LED 2: Pairing status indicator LED
PA3 = LED 3: Heartbeat system
PA4 = Pairing mechanism start button (active-low)
PA5 = Block button(only for master/rx)
PA6 = Buzzer

D. Power On

  1. On every startup, both devices first check they have stored partner id and pairing status =1 in flash memory
    • During every startup, LED(PA2) blinks every 500ms (for 3 cycles during flash check to indicate initialization)

Case A- if both have each other id and pairing status 1 stored in flash memory
• LED(PA2) becomes solid

  1. If device is master they check they have slave id and pairing status =1
  2. If device is slave they check they have master id and pairing status =1
    • They skip pairing process and start heartbeat mechanism

Case B- if both have not each other id and pairing status 1 stored in flash memory
• LED(PA2) blink every 500ms is turned off (device enters idle state)
• They wait for double-press of pairing button(PA4) to start pairing process
• After double press, LED(PA2) blinks every 500ms during pairing process
• After pairing process complete, LED(PA2) becomes solid
• Then heartbeat mechanism
Error Handling: If flash read fails (e.g., CRC mismatch), LED(PA2) blinks rapidly (100ms x 5) before entering idle/OFF state

E. Pairing process
• Pairing process is start when Pairing Button(PA4) Double Press.
• The pairing process starts on each side when each side pairing button(PA4) is pressed.

  1. When the button is double-pressed, the device enters Pairing Mode
  2. Take 3 seconds before message send (used for message collapse and internal scanning)

Pairing Protocol (Flash + ID + Checksum)

Pairing Protocol (recommended messages):

  1. Master (initiator) → Slave
    PAIR_REQ:8-digit uppercase hex UID[0]*2-digit uppercase hex CRC8\r\n
    (sent every 5 seconds for up to 120 seconds)

  2. Slave → Master
    PAIR_ACK:8-digit uppercase hex UID[0]*2-digit uppercase hex CRC8\r\n
    (sent every 5 seconds for up to 120 seconds after receiving valid PAIR_REQ)

  3. Master → Slave
    PAIR_CONFIRM:8-digit uppercase hex UID[0]*2-digit uppercase hex CRC8\r\n // Added UID/CRC for security
    (sent every 5 seconds for up to 120 seconds after receiving valid PAIR_ACK)

  4. Slave → Master
    PAIR_OK:8-digit uppercase hex UID[0]*2-digit uppercase hex CRC8\r\n // Added UID/CRC for security
    (sent every 5 seconds for up to 120 seconds after receiving PAIR_CONFIRM)

• UID[0] = first 32 bits of STM32 Unique ID (0x1FFFF7E8)
• CRC8 using polynomial 0x8C (init=0x00, no reflect, no final XOR)
• All messages end with \r\n

  1. Master Sends - PAIR_REQ to Tx/Slave
    • Send PAIR_REQ:8-digit uppercase hex UID[0]*2-digit uppercase hex CRC8\r\n for every 5 second interval upto 120 seconds
    • After 120 second timeout buzzer beep(PA6) 3× short + LED(PA2) blinks 3× short → return to idle

    •STM32 UID: STM32F103C8T6 inbuilt Unique ID, UID[0] 0x1FFFF7E8
    • uint32_t uid = (volatile uint32_t)0x1FFFF7E8;

char idStr[9];
snprintf(idStr, 9, "%08lX", uid);

uint8_t crc = crc8_0x8C((uint8_t*)idStr, 8);

char crcStr[3];
snprintf(crcStr, 3, "%02X", crc);

char pair_req[32];
snprintf(pair_req, sizeof(pair_req),
"PAIR_REQ:%s*%s\r\n",
idStr, crcStr);

• Checksum: CRC8 (polynomial 0x8C)

  1. Slave Receives PAIR_REQ from master
    • slave Wait 120 sec for PAIR_REQ → timeout: buzzer beep(PA6) + LED(PA2) blinks 3× short → return to idle

If Slave receive PAIR_REQ message in time slave store partner uid flash memory last page (last page at 0x0800FC00 – 0x0800FFFF):

UID storage sequence in slave flash memory
• HAL_FLASH_Unlock();
• HAL_FLASHEx_Erase(...);
• HAL_FLASH_Program(...);
• HAL_FLASH_Lock();

  • After successful write → slave send PAIR_ACK:8-digit uppercase hex UID[0]*2-digit uppercase hex CRC8\r\n   every 5 second interval upto 120 seconds  

•STM32 UID: STM32F103C8T6 inbuilt Unique ID, UID[0] 0x1FFFF7E8
• uint32_t uid = (volatile uint32_t)0x1FFFF7E8;

char idStr[9];
snprintf(idStr, 9, "%08lX", uid);

uint8_t crc = crc8_0x8C((uint8_t*)idStr, 8);

char crcStr[3];
snprintf(crcStr, 3, "%02X", crc);

char pair_ack[32];
snprintf(pair_ack, sizeof(pair_ack),
"PAIR_ACK:%s*%s\r\n",
idStr, crcStr);

• Checksum: CRC8 (polynomial 0x8C)

  1. Master Receives PAIR_ACK from slave
    • master Wait 120 sec for PAIR_ACK → timeout: buzzer beep(PA6) + LED(PA2) blinks 3× short → idle

If Master receive PAIR_ACK message in time Master store partner uid flash memory last page (last page at 0x0800FC00 – 0x0800FFFF):

UID storage sequence in master flash memory
• HAL_FLASH_Unlock();
• HAL_FLASHEx_Erase(...);
• HAL_FLASH_Program(...);
• HAL_FLASH_Lock();

  • After successful write →master  send PAIR_CONFIRM:8-digit uppercase hex UID[0]*2-digit uppercase hex CRC8\r\n  every 5 second interval upto 120 seconds  

char pair_confirm[32];
snprintf(pair_confirm, sizeof(pair_confirm),
"PAIR_CONFIRM:%s*%s\r\n",
idStr, crcStr);

  1. Slave Receives PAIR_CONFIRM from master
    • slave Wait 120 sec for PAIR_CONFIRM → timeout: buzzer beep(PA6) + LED(PA2) blinks 3× short → idle
    •after receiving PAIR_CONFIRM in time slave change their pairingStatus 0 to = 1 and store in flash last page after master uid store
    • LEDs(PA2) become Solid ON (pairing complete)
    • slave Send PAIR_OK:8-digit uppercase hex UID[0]*2-digit uppercase hex CRC8\r\n to Master every 5 second interval upto 120 seconds

char pair_ok[32];
snprintf(pair_ok, sizeof(pair_ok),
"PAIR_OK:%s*%s\r\n",
idStr, crcStr);

  1. Master Receives PAIR_OK from slave
    • Wait 120 sec for PAIR_OK → timeout: buzzer beep (PA6) + LED(PA2) blinks 3× short → idle
    • after receiving PAIR_OK in time Master change their pairingStatus 0 to = 1 and store in flash last page after slave uid store
    • LEDs(PA2) become Solid ON (pairing complete)

// Improved message reception: Use buffer-based parser in main loop or IRQ
#define RX_BUFFER_SIZE 64
char rxBuffer[RX_BUFFER_SIZE];
uint8_t rxIndex = 0;

void handleUARTReceive() {
while (HC12_UART.available()) {
char c = HC12_UART.read();
if (rxIndex < RX_BUFFER_SIZE - 1) {
rxBuffer[rxIndex++] = c;
} else {
// Overflow: Reset buffer
rxIndex = 0;
// Optional: Error LED blink
}
if (c == '\n') {
rxBuffer[rxIndex] = '\0'; // Null-terminate
processMessage(String(rxBuffer)); // Parse and handle
rxIndex = 0; // Reset for next
}
}
}

// Call handleUARTReceive() in main loop

J. LED Behavior
Mode LED Action
1 Idle OFF
2 During Pairing process Blink 500 ms
3 Erase Mode Blink 1000 ms for 5 times
4 Pairing Complete Solid ON
5 Timeout / Fail 3× short blinks → return to idle/OFF

F. Flash Memory Handler

#include "stm32f1xx_hal.h"
#include <string.h> // for memcpy
// Flash storage location
#define FLASH_PAIR_PAGE_ADDR 0x0800FC00UL
// Persistent pairing data — 8 bytes total (2 flash words)
typedef struct {
uint32_t partner_id; // UID[0] of the paired device
uint8_t pairing_status; // 0 = not paired, 1 = paired
uint8_t crc; // CRC8 of partner_id + pairing_status
uint8_t reserved[2]; // padding — keep total size 8 bytes
} PairData_t;
// Forward declarations
static void Flash_ClearFlags(void);
// LED helpers (adapt to PA pins; example for PA2)
void LED_Init(uint16_t pin) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE(); // Assuming PA pins
GPIO_InitStruct.Pin = pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, pin, GPIO_PIN_SET); // LED off (assuming active high)
}
static void LED_Blink(uint16_t pin, uint8_t count, uint32_t delay_ms) {
for (uint8_t i = 0; i < count; i++) {
HAL_GPIO_WritePin(GPIOA, pin, GPIO_PIN_RESET); // ON (adapt if active low)
HAL_Delay(delay_ms / 2);
HAL_GPIO_WritePin(GPIOA, pin, GPIO_PIN_SET); // OFF
HAL_Delay(delay_ms / 2);
}
HAL_Delay(500); // pause between sequences
}
// Flash low-level helpers
static void Flash_ClearFlags(void) {
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR);
}
HAL_StatusTypeDef Flash_ReadPairData(PairData_t *data) {
if (data == NULL) return HAL_ERROR;
memcpy(data, (const void )FLASH_PAIR_PAGE_ADDR, sizeof(PairData_t));
// Verify CRC
uint8_t computed_crc = crc8_0x8C((const uint8_t
)data, sizeof(uint32_t) + sizeof(uint8_t));
if (computed_crc != data->crc) {
memset(data, 0, sizeof(PairData_t)); // Treat as invalid/unpaired
return HAL_ERROR;
}
return HAL_OK;
}
HAL_StatusTypeDef Flash_ErasePairPage(void) {
HAL_StatusTypeDef status;
uint8_t retries = 3;
while (retries--) {
Flash_ClearFlags();
status = HAL_FLASH_Unlock();
if (status != HAL_OK) {
LED_Blink(GPIO_PIN_2, 5, 150); // Use PA2 for error feedback
continue;
}
FLASH_EraseInitTypeDef eraseInit = {
.TypeErase = FLASH_TYPEERASE_PAGES,
.Banks = FLASH_BANK_1,
.PageAddress = FLASH_PAIR_PAGE_ADDR,
.NbPages = 1
};
uint32_t pageError = 0xFFFFFFFF;
status = HAL_FLASHEx_Erase(&eraseInit, &pageError);
HAL_FLASH_Lock();
if (status == HAL_OK && pageError == 0xFFFFFFFF) {
LED_Blink(GPIO_PIN_2, 3, 150); // success feedback
return HAL_OK;
}
LED_Blink(GPIO_PIN_2, 5, 150);
}
return HAL_ERROR;
}
HAL_StatusTypeDef Flash_WritePairData(const PairData_t *data) {
if (data == NULL) {
LED_Blink(GPIO_PIN_2, 5, 150);
return HAL_ERROR;
}
PairData_t temp = data; // Copy to compute CRC
memset(temp.reserved, 0, sizeof(temp.reserved)); // Zero padding
temp.crc = crc8_0x8C((const uint8_t
)&temp, sizeof(uint32_t) + sizeof(uint8_t));
HAL_StatusTypeDef status;
uint8_t retries = 3;
// Check if erase is needed (compare existing data)
PairData_t existing;
if (Flash_ReadPairData(&existing) == HAL_OK && memcmp(&existing, &temp, sizeof(PairData_t)) == 0) {
return HAL_OK; // No change needed
}
while (retries--) {
Flash_ClearFlags();
status = HAL_FLASH_Unlock();
if (status != HAL_OK) {
LED_Blink(GPIO_PIN_2, 5, 150);
continue;
}
// Erase page if needed
FLASH_EraseInitTypeDef eraseInit = {
.TypeErase = FLASH_TYPEERASE_PAGES,
.Banks = FLASH_BANK_1,
.PageAddress = FLASH_PAIR_PAGE_ADDR,
.NbPages = 1
};
uint32_t pageError = 0xFFFFFFFF;
status = HAL_FLASHEx_Erase(&eraseInit, &pageError);
if (status != HAL_OK || pageError != 0xFFFFFFFF) {
HAL_FLASH_Lock();
LED_Blink(GPIO_PIN_2, 5, 150);
continue;
}
// Program 2 × 32-bit words
uint32_t *src = (uint32_t *)&temp;
uint32_t addr = FLASH_PAIR_PAGE_ADDR;
for (size_t i = 0; i < (sizeof(PairData_t) / sizeof(uint32_t)); i++) {
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, src[i]);
if (status != HAL_OK) {
HAL_FLASH_Lock();
LED_Blink(GPIO_PIN_2, 5, 150);
break;
}
addr += sizeof(uint32_t);
}
if (status != HAL_OK) continue;
HAL_FLASH_Lock();
// Verify written data
PairData_t verify = {0};
if (Flash_ReadPairData(&verify) == HAL_OK &&
memcmp(&verify, &temp, sizeof(PairData_t)) == 0) {
LED_Blink(GPIO_PIN_2, 3, 150); // success
return HAL_OK;
}
LED_Blink(GPIO_PIN_2, 5, 150); // verification failed
}
return HAL_ERROR;
}

G. Heartbeat System
• Active only if pairingStatus=1 (checked from flash on startup or after pairing).
• Before heartbeat starts: Wait 5 seconds after pairing complete or power-on (if already paired). Add random jitter (1-5s) for master on startup to reduce sync issues.
• Master initiates heartbeats; slave responds.
• Interval between heartbeats: 90 seconds (from master's send time).
• LED(PA3) for heartbeat indicators.
• Failure threshold: 2 consecutive missed responses → heartbeat failed (unpair).
• Improvements:

  • Separate timeouts: 15 seconds for response (master waiting ACK, slave waiting REQ) to account for transmission delays/packet loss.
  • Rate-limited failure handling: Increment failureCount only once per expected period (on explicit timeout detection, not every tick).
  • Retry on single failure: Resend/wait once before full failure.
  • State resets: On timeout, revert to send/wait states to avoid stuck loops.
  • UID mismatch: Immediate failure (as original), but with logging/feedback.
  • Initial state: Slave starts with extended first-wait (95s) to sync if master slightly delayed.
  1. Master (initiator) sends its own UID
    • Master initiates and sends HB_REQ every 90 seconds.
    • Message: HB_REQ:8-digit hex master UID*2-digit CRC8 of the 8-digit hex string\r\n
    • On send: LED(PA3) OFF, start response timeout (15s).

  2. Slave Receives HB_REQ from master
    • Slave waits for HB_REQ (timeout: 105s for first cycle to allow sync, then 95s thereafter—slight buffer over 90s).
    • Compare received UID with stored master UID.
    • Case 1: Match → LED(PA3) 2x short blinks (100ms), immediately send HB_ACK:8-digit hex slave UID*2-digit CRC8 of the 8-digit hex string\r\n, reset failureCount=0, restart wait timeout.
    • Case 2: Mismatch → Immediate failure: Buzzer beep (PA6) 5x short, LED(PA3) 5x short blinks, set pairingStatus=0, LED(PA2) OFF, return to idle.

  3. Master Receives HB_ACK from slave
    • Master waits for HB_ACK (timeout: 15s after send).
    • Compare received UID with stored slave UID.
    • Case 1: Match → LED(PA3) 2x short blinks (100ms), reset failureCount=0, complete cycle, schedule next HB_REQ in 90s from now.
    • Case 2: Mismatch → Immediate failure: Buzzer beep (PA6) 5x short, LED(PA3) 5x short blinks, set pairingStatus=0, LED(PA2) OFF, return to idle.

Heartbeat Failure

  1. On 2 consecutive timeouts/misses: Buzzer beep (PA6) 5x short & LED(PA3) 5x short blinks, set pairingStatus=0 (update flash), LED(PA2) OFF → return to idle.
  2. On single timeout: Increment failureCount, retry once (master resends immediately; slave extends wait by 15s).
  3. Update pairingStatus in flash on failure for persistence.

Message Formatting (same for both devices):
uint32_t uid = (volatile uint32_t)0x1FFFF7E8;
char idStr[9];
snprintf(idStr, 9, "%08lX", uid);

uint8_t crc = crc8_0x8C((uint8_t*)idStr, 8);

char crcStr[3];
snprintf(crcStr, 3, "%02X", crc);

char hb_req[32]; // For master
snprintf(hb_req, sizeof(hb_req),
"HB_REQ:%s*%s\r\n",
idStr, crcStr);

char hb_ack[32]; // For slave
snprintf(hb_ack, sizeof(hb_ack),
"HB_ACK:%s*%s\r\n",
idStr, crcStr);

enum HeartbeatState {
HB_IDLE,
HB_MASTER_SEND_REQ,
HB_MASTER_WAIT_ACK,
HB_SLAVE_WAIT_REQ,
HB_SLAVE_SEND_ACK // Brief state for sending ACK
};

HeartbeatState hbState = HB_IDLE;
uint32_t lastHeartbeatTime = 0;
uint32_t responseStartTime = 0;
uint32_t pairingCompleteTime = 0; // Set when pairing completes or on startup if paired
bool isMaster = false; // Set based on user define (master=1/slave=2)
uint8_t failureCount = 0;
bool hasTimedOutThisCycle = false; // Prevent rapid failure increments
bool isFirstCycle = true; // For slave's extended first wait

void handleHeartbeat() {
if (pairingStatus == 0) return;

uint32_t now = HAL_GetTick(); // Use HAL for STM32  

// Start heartbeat 5 seconds after pairing/startup, with jitter for master  
if (hbState == HB_IDLE && now - pairingCompleteTime >= 5000) {  
    hbState = (isMaster) ? HB_MASTER_SEND_REQ : HB_SLAVE_WAIT_REQ;  
    lastHeartbeatTime = now; // Init timer  
    responseStartTime = now; // Init for slave wait  
    if (isMaster) {  
        lastHeartbeatTime += random(1000, 5000); // 1-5s jitter  
    }  
    isFirstCycle = true;  
}  

// Master: Check if time to send  
if (isMaster && hbState == HB_MASTER_SEND_REQ && now - lastHeartbeatTime >= 90000) {  // 90 seconds  
    ledOff(GPIO_PIN_3); // OFF on send (PA3)  
    HC12_UART.print(hb_req);  // Send "HB_REQ:..."  
    hbState = HB_MASTER_WAIT_ACK;  
    responseStartTime = now; // Start 15s response timeout  
    lastHeartbeatTime = now; // Reset cycle timer  
    hasTimedOutThisCycle = false;  
    isFirstCycle = false;  
}  

// Timeout checks  
uint32_t responseTimeoutMs = 15000; // 15s for responses  
uint32_t slaveWaitTimeoutMs = (isFirstCycle) ? 105000 : 95000; // Buffer over 90s  

if (hbState == HB_MASTER_WAIT_ACK && now - responseStartTime > responseTimeoutMs && !hasTimedOutThisCycle) {  
    failureCount++;  
    hasTimedOutThisCycle = true;  
    if (failureCount >= 2) {  
        // Full failure  
        pairingStatus = 0;  
        Flash_WritePairData(...); // Update flash with status=0  
        buzzerBeep(5);  
        LED_Blink(GPIO_PIN_3, 5, 150);  
        ledOff(GPIO_PIN_2);  
        hbState = HB_IDLE;  
        failureCount = 0;  
    } else {  
        // Retry: Resend immediately  
        HC12_UART.print(hb_req);  
        responseStartTime = now; // Restart timeout  
    }  
}  

if (hbState == HB_SLAVE_WAIT_REQ && now - responseStartTime > slaveWaitTimeoutMs && !hasTimedOutThisCycle) {  
    failureCount++;  
    hasTimedOutThisCycle = true;  
    if (failureCount >= 2) {  
        // Full failure  
        pairingStatus = 0;  
        Flash_WritePairData(...); // Update flash with status=0  
        buzzerBeep(5);  
        LED_Blink(GPIO_PIN_3, 5, 150);  
        ledOff(GPIO_PIN_2);  
        hbState = HB_IDLE;  
        failureCount = 0;  
    } else {  
        // Retry: Extend wait  
        responseStartTime = now; // Restart timeout  
    }  
}  

// Receive handling (call this in main loop or UART IRQ, using handleUARTReceive())  
// In processMessage(String msg):  
msg.trim();  

if (hbState == HB_SLAVE_WAIT_REQ && msg.startsWith("HB_REQ:")) {  
    if (validateHeartbeat(msg, true)) {  // true = expecting REQ, checks UID/CRC  
        failureCount = 0;  
        hasTimedOutThisCycle = false;  
        LED_Blink(GPIO_PIN_3, 2, 100);  
        HC12_UART.print(hb_ack);  // Immediate send  
        hbState = HB_SLAVE_WAIT_REQ; // Back to wait  
        responseStartTime = now; // Reset for next REQ wait  
        isFirstCycle = false;  
    } else {  
        // Immediate mismatch fail  
        pairingStatus = 0;  
        Flash_WritePairData(...); // Update flash  
        buzzerBeep(5);  
        LED_Blink(GPIO_PIN_3, 5, 150);  
        ledOff(GPIO_PIN_2);  
        hbState = HB_IDLE;  
        failureCount = 0;  
    }  
} else if (hbState == HB_MASTER_WAIT_ACK && msg.startsWith("HB_ACK:")) {  
    if (validateHeartbeat(msg, false)) {  // false = expecting ACK, checks UID/CRC  
        failureCount = 0;  
        hasTimedOutThisCycle = false;  
        LED_Blink(GPIO_PIN_3, 2, 100);  
        hbState = HB_MASTER_SEND_REQ; // Back to send state  
        lastHeartbeatTime = now; // Reset cycle timer for next 90s  
    } else {  
        // Immediate mismatch fail  
        pairingStatus = 0;  
        Flash_WritePairData(...); // Update flash  
        buzzerBeep(5);  
        LED_Blink(GPIO_PIN_3, 5, 150);  
        ledOff(GPIO_PIN_2);  
        hbState = HB_IDLE;  
        failureCount = 0;  
    }  
}  

}

bool validateHeartbeat(String msg, bool isReq) {
// Parse msg, e.g., "HB_REQ:XXXXXXXXYY\r\n" or similar for ACK
String prefix = isReq ? "HB_REQ:" : "HB_ACK:";
int startIdx = msg.indexOf(prefix) + prefix.length();
if (startIdx < prefix.length()) return false;
int sepIdx = msg.indexOf('
', startIdx);
if (sepIdx == -1) return false;
String uidStr = msg.substring(startIdx, sepIdx);
if (uidStr.length() != 8) return false;
String crcStr = msg.substring(sepIdx + 1, sepIdx + 3);
if (crcStr.length() != 2) return false;
// Compute CRC
uint8_t computedCrc = crc8_0x8C((uint8_t*)uidStr.c_str(), 8);
uint8_t receivedCrc;
sscanf(crcStr.c_str(), "%02X", &receivedCrc);
if (computedCrc != receivedCrc) return false;
// Compare UID to stored partner UID
uint32_t receivedUid;
sscanf(uidStr.c_str(), "%08lX", &receivedUid);
PairData_t data;
Flash_ReadPairData(&data);
uint32_t expectedUid = data.partner_id;
return (receivedUid == expectedUid);
}

// Similar validation for pairing messages (PAIR_CONFIRM/PAIR_OK now have UID/CRC, parse accordingly in processMessage)

LED Action
Event Indicator

  1. Heartbeat request sent OFF (PA3)
  2. Valid response received 2× short blinks (PA3)
    Failure:
  3. Timeout/mismatch 5× short blinks (PA3) & buzzer beep (PA6) 5x
  4. Link lost (after 2 failures) LED(PA2) OFF

H. Erase Memory (Triple Press)

  1. Erases flash memory last page only
  2. LED blinks 1000ms for 5 times→ return to idle
  3. pairingStatus = 0 (not paired)
    I. Pairing Status
    Status Storage Description
    0 SRAM Not paired
    1 SRAM Paired (active)
    If pairingStatus == 0 → device ignores all user data transmissions

K. Button Timing (PAIR_BUTTON_PIN PA4)
• 1× press = nothing happen
• 2× press = pairing start
• 3× press = erase flash last page memory

#define DEBOUNCE_TIME 50 // ms
#define MAX_PRESS_GAP 700 // max time between two consecutive presses
#define MAX_SEQUENCE_TIME 2500 // total allowed time for whole sequence (2.5 sec, increased for usability)

uint32_t lastDebounceTime = 0;
uint32_t firstPressTime = 0;
uint32_t lastPressTime = 0;
uint8_t pressCount = 0;
bool buttonState = false; // current stable state (false = not pressed)
bool lastButtonReading = false;

void updateButton() {
// Read current button (active LOW)
bool reading = (HAL_GPIO_ReadPin(GPIOA, PAIR_BUTTON_PIN) == GPIO_PIN_RESET);

// Debounce: only consider change if stable for DEBOUNCE_TIME  
if (reading != lastButtonReading) {  
    lastDebounceTime = HAL_GetTick();  
}  

if ((HAL_GetTick() - lastDebounceTime) > DEBOUNCE_TIME) {  
    // Stable change detected  
    if (reading != buttonState) {  
        buttonState = reading;  

        // === BUTTON JUST PRESSED (rising edge to true) ===  
        if (buttonState == true) {   // pressed  
            uint32_t now = HAL_GetTick();  

            // If this is the first press in sequence  
            if (pressCount == 0) {  
                firstPressTime = now;  
                pressCount = 1;  
            }  
            // If within allowed gap from previous press  
            else if ((now - lastPressTime) <= MAX_PRESS_GAP) {  
                pressCount++;  
            }  
            // Too long gap → start new sequence  
            else {  
                pressCount = 1;  
                firstPressTime = now;  
            }  

            lastPressTime = now;  

            // Safety: if total sequence time exceeded → reset  
            if ((now - firstPressTime) > MAX_SEQUENCE_TIME) {  
                pressCount = 1;  
                firstPressTime = now;  
            }  
        }  
    }  
}  

lastButtonReading = reading;  

// === Check if sequence is finished (button released and quiet for a while) ===  
if (buttonState == false && pressCount > 0) {  
    // Wait ~400 ms after last press to make sure no more presses come  
    if ((HAL_GetTick() - lastPressTime) > 400) {  
        // Sequence complete → evaluate  
        if (pressCount == 2) {  
            // Double press → Start pairing  
            startPairingProcess();  
            // Example: start slow blinking on PA2  
            // LED_Blink(GPIO_PIN_2, 0, 500);  // infinite slow blink until pairing done  
        }  
        else if (pressCount == 3) {  
            // Triple press → Erase flash & reset pairing  
            Flash_ErasePairPage();  
            pairingStatus = 0;  
            LED_Blink(GPIO_PIN_2, 5, 1000);   // 5 long blinks  
            buzzerBeep(5);            // 5 beeps  
        }  
        // Reset for next sequence  
        pressCount = 0;  
        firstPressTime = 0;  
        lastPressTime = 0;  
    }  
}  

}
L. Checksum
CRC8 algorithm using polynomial 0x8C
Used to validate pairing messages and flash data integrity

Recommended implementation (add this function):
uint8_t crc8_0x8C(const uint8_t *data, uint8_t len) {
uint8_t crc = 0;
for(uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for(uint8_t j = 0; j < 8; j++) {
if (crc & 0x80) crc = (crc << 1) ^ 0x8C;
else crc <<= 1;
}
}
return crc;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions