From 285f543d465ae563b220e006fab02248f0a31f11 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Tue, 25 Aug 2026 13:05:20 +0200 Subject: [PATCH] Make Aircopy transfers reliable with ACK/RESEND recovery and leaner FSK handling --- App/app/aircopy.c | 422 +++++++++++++++++++------------------ App/app/aircopy.h | 51 +---- App/app/app.c | 64 +++--- App/app/beam.c | 13 +- App/app/beam.h | 1 - App/app/menu.c | 2 +- App/app/uart.c | 2 +- App/driver/eeprom.c | 37 ++-- App/driver/eeprom.h | 3 +- App/driver/eeprom_compat.c | 5 +- App/misc.c | 3 - App/misc.h | 3 - App/ui/aircopy.c | 105 +++------ 13 files changed, 322 insertions(+), 389 deletions(-) diff --git a/App/app/aircopy.c b/App/app/aircopy.c index e0a4e356..6d6aa7bd 100644 --- a/App/app/aircopy.c +++ b/App/app/aircopy.c @@ -21,6 +21,7 @@ #include "driver/bk4819.h" #include "driver/crc.h" #include "driver/eeprom.h" +#include "driver/system.h" #include "frequencies.h" #include "misc.h" #include "radio.h" @@ -29,6 +30,7 @@ #include "ui/ui.h" #include "settings.h" #include +#include #ifdef ENABLE_FEAT_F4HWN_K5VIEWER #include "k5viewer.h" @@ -43,140 +45,73 @@ bool gAirCopyIsSendMode; uint16_t g_FSK_Buffer[36]; -// ============================================================================ -// Transfer Maps Definition -// ============================================================================ +// Stop-and-wait protocol. Every frame keeps the original 64-byte payload: +// DATA is acknowledged only after storage, ACK confirms that offset, and +// RESEND requests the same offset immediately instead of waiting for timeout. +#define AIRCOPY_PACKET_DATA 0xABCDu +#define AIRCOPY_PACKET_ACK 0xABCEu +#define AIRCOPY_PACKET_RESEND 0xABCFu +#define AIRCOPY_PACKET_END 0xDCBAu +#define AIRCOPY_ACK_TIMEOUT_10MS 400u +#define AIRCOPY_RX_TIMEOUT_10MS 2000u +#define AIRCOPY_RX_LINGER_10MS 500u +#define AIRCOPY_MAX_RETRIES 3u -#define AIRCOPY_BANK_SEGMENTS(bank) \ -{ \ - { 0x0000 + (bank)*0x0800, 0x0000 + (bank)*0x0800 + 0x0800, AIRCOPY_WRITE_STRUCT }, \ - { 0x4000 + (bank)*0x0800, 0x4000 + (bank)*0x0800 + 0x0800, AIRCOPY_WRITE_STRUCT }, \ - { 0x8000 + (bank)*0x0100, 0x8000 + (bank)*0x0100 + 0x0100, AIRCOPY_WRITE_BYTES }, \ -} +static uint16_t AircopyCountdown; +static uint8_t AircopyRetries; -#define AIRCOPY_STD_MAP(seg_array) \ -{ \ - .segments = seg_array, \ - .num_segments = 3, \ - .total_blocks = 68 \ -} - -// total_blocks = 68 (blocs of 64 bytes) because (16 bytes + 16 bytes + 2 bytes) * 128 = 4352 / 64 = 68 - -#define DECLARE_AIRCOPY_BANK(n) \ - static const AIRCOPY_Segment_t AIRCOPY_Segments_Bank##n[] = \ - AIRCOPY_BANK_SEGMENTS(n); \ - \ - static const AIRCOPY_TransferMap_t AIRCOPY_Map_Bank##n = \ - AIRCOPY_STD_MAP(AIRCOPY_Segments_Bank##n); - -DECLARE_AIRCOPY_BANK(0) -DECLARE_AIRCOPY_BANK(1) -#if AIRCOPY_NUM_BANKS >= 4 // if 512 MR CHANNEL - DECLARE_AIRCOPY_BANK(2) - DECLARE_AIRCOPY_BANK(3) -#endif -#if AIRCOPY_NUM_BANKS >= 6 // if 758 MR CHANNEL - DECLARE_AIRCOPY_BANK(4) - DECLARE_AIRCOPY_BANK(5) -#endif -#if AIRCOPY_NUM_BANKS >= 8 // if 1024 MR CHANNEL - DECLARE_AIRCOPY_BANK(6) - DECLARE_AIRCOPY_BANK(7) -#endif - -// For settings only - -static const AIRCOPY_Segment_t AIRCOPY_Segments_Settings[] = { - // Ends are rounded to full Aircopy blocks. The extra bytes are unmapped - // EEPROM-compat holes (0x90E0..0x90E6 is the complete Fox Hunt tail). - { 0xA000, 0xA180, AIRCOPY_WRITE_BYTES }, - { 0x880E, 0x888E, AIRCOPY_WRITE_BYTES }, - { 0x9000, 0x9100, AIRCOPY_WRITE_BYTES }, -}; - -// total_blocks = 0x180/64 + 0x80/64 + 0x100/64 = 6 + 2 + 4 = 12 -static const AIRCOPY_TransferMap_t AIRCOPY_Map_Settings = { - .segments = AIRCOPY_Segments_Settings, - .num_segments = 3, - .total_blocks = 12 -}; - -// Finally - -static const AIRCOPY_TransferMap_t *AIRCOPY_AvailableMaps[] = { - &AIRCOPY_Map_Bank0, - &AIRCOPY_Map_Bank1, - #if AIRCOPY_NUM_BANKS >= 4 // if 512 MR CHANNEL - &AIRCOPY_Map_Bank2, - &AIRCOPY_Map_Bank3, - #endif - #if AIRCOPY_NUM_BANKS >= 6 // if 758 MR CHANNEL - &AIRCOPY_Map_Bank4, - &AIRCOPY_Map_Bank5, - #endif - #if AIRCOPY_NUM_BANKS >= 8 // if 1024 MR CHANNEL - &AIRCOPY_Map_Bank6, - &AIRCOPY_Map_Bank7, - #endif - &AIRCOPY_Map_Settings, -}; - -#define AIRCOPY_NUM_MAPS (sizeof(AIRCOPY_AvailableMaps) / sizeof(AIRCOPY_AvailableMaps[0])) +#define AIRCOPY_NUM_MAPS (AIRCOPY_NUM_BANKS + 1u) +#define AIRCOPY_BANK_BLOCKS 68u +#define AIRCOPY_SETTINGS_BLOCKS 12u // ============================================================================ // Helper Functions // ============================================================================ -const AIRCOPY_TransferMap_t* AIRCOPY_GetCurrentMap(void) +uint8_t AIRCOPY_GetTotalBlocks(void) { - if (gAircopyCurrentMapIndex >= AIRCOPY_NUM_MAPS) { - gAircopyCurrentMapIndex = 0; + return gAircopyCurrentMapIndex == AIRCOPY_NUM_BANKS + ? AIRCOPY_SETTINGS_BLOCKS + : AIRCOPY_BANK_BLOCKS; +} + +static uint16_t AIRCOPY_GetBlockOffset(uint16_t block) +{ + if (gAircopyCurrentMapIndex == AIRCOPY_NUM_BANKS) + { + // Settings: 6 blocks at 0xA000, 2 at 0x880E and 4 at 0x9000. + if (block < 6u) + return 0xA000u + block * AIRCOPY_BLOCK_SIZE; + if (block < 8u) + return 0x880Eu + (block - 6u) * AIRCOPY_BLOCK_SIZE; + return 0x9000u + (block - 8u) * AIRCOPY_BLOCK_SIZE; } - return AIRCOPY_AvailableMaps[gAircopyCurrentMapIndex]; + + // A bank contains 32 frequency, 32 name and 4 attribute blocks. + const uint16_t channelOffset = gAircopyCurrentMapIndex * 0x0800u; + if (block < 32u) + return channelOffset + block * AIRCOPY_BLOCK_SIZE; + if (block < 64u) + return 0x4000u + channelOffset + (block - 32u) * AIRCOPY_BLOCK_SIZE; + return 0x8000u + gAircopyCurrentMapIndex * 0x0100u + + (block - 64u) * AIRCOPY_BLOCK_SIZE; } static void AIRCOPY_clear() { - for (uint8_t i = 0; i < 15; i++) - { - crc[i] = 0; - } #ifdef ENABLE_FEAT_F4HWN_K5VIEWER K5VIEWER_Update(true); #endif } -static inline const AIRCOPY_Segment_t *AIRCOPY_FindSegmentForOffset(uint16_t off) +static void AIRCOPY_Finish(AIRCOPY_State_t state) { - const AIRCOPY_TransferMap_t *map = AIRCOPY_GetCurrentMap(); - - for (uint16_t i = 0; i < map->num_segments; i++) - { - const AIRCOPY_Segment_t *seg = &map->segments[i]; - - if (off >= seg->start_offset && off < seg->end_offset && - ((off - seg->start_offset) & (AIRCOPY_BLOCK_SIZE - 1u)) == 0u) - return seg; - } - - return NULL; -} - -static inline void AIRCOPY_CheckComplete(uint16_t *num) -{ - *num = *num + 1; - - const AIRCOPY_TransferMap_t *map = AIRCOPY_GetCurrentMap(); - uint16_t done = gAirCopyBlockNumber + gErrorsDuringAirCopy; - - if (done >= map->total_blocks) - { - gAircopyState = AIRCOPY_COMPLETE; + AircopyCountdown = 0; + gAircopyState = state; + gUpdateDisplay = true; #ifdef ENABLE_FEAT_F4HWN_K5VIEWER - K5VIEWER_Update(false); + K5VIEWER_Update(false); #endif - } } void AIRCOPY_Obfuscate(unsigned int count) @@ -186,70 +121,97 @@ void AIRCOPY_Obfuscate(unsigned int count) } } +static void AIRCOPY_TransmitBuffer(void) +{ + // Both sides need time to leave TX and re-arm FSK RX before the reply. + SYSTEM_DelayMs(50); + RADIO_SetTxParameters(); + BK4819_SendFSKData(g_FSK_Buffer); + BK4819_SetupPowerAmplifier(0, 0); + BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false); +} + +static void AIRCOPY_FinalizeAndSend(void) +{ + g_FSK_Buffer[34] = CRC_Calculate(&g_FSK_Buffer[0], + 4 + AIRCOPY_BLOCK_SIZE); + g_FSK_Buffer[35] = AIRCOPY_PACKET_END; + AIRCOPY_Obfuscate(34); + AIRCOPY_TransmitBuffer(); + gFSKWriteIndex = 0; + BK4819_PrepareFSKReceive(); +} + +static void AIRCOPY_SendControl(uint16_t type, uint16_t offset) +{ + g_FSK_Buffer[0] = type; + g_FSK_Buffer[1] = offset; + memset(&g_FSK_Buffer[2], 0, AIRCOPY_BLOCK_SIZE); + AIRCOPY_FinalizeAndSend(); +} + +static void AIRCOPY_RequestResend(void) +{ + gErrorsDuringAirCopy++; + gUpdateDisplay = true; + AircopyCountdown = AIRCOPY_RX_TIMEOUT_10MS; + AIRCOPY_SendControl(AIRCOPY_PACKET_RESEND, + AIRCOPY_GetBlockOffset(gAirCopyBlockNumber)); +} + +static bool AIRCOPY_Retry(void) +{ + if (AircopyRetries >= AIRCOPY_MAX_RETRIES) + { + AIRCOPY_Finish(AIRCOPY_FAILED); + return false; + } + + AircopyRetries++; + gErrorsDuringAirCopy++; + gUpdateDisplay = true; + AircopyCountdown = 0; + return true; +} + // ============================================================================ // Send/Receive Functions // ============================================================================ bool AIRCOPY_SendMessage(void) { - static uint8_t gAircopySendCountdown = 1; - static uint16_t CurrentOffset = 0; - static uint16_t CurrentSegmentIndex = 0; - if (gAircopyState != AIRCOPY_TRANSFER) { return 1; } - if (--gAircopySendCountdown) { + if (!gAirCopyIsSendMode) + { + if (AircopyCountdown != 0 && --AircopyCountdown == 0) + { + AIRCOPY_Finish(gAirCopyBlockNumber >= AIRCOPY_GetTotalBlocks() + ? AIRCOPY_COMPLETE + : AIRCOPY_FAILED); + return 0; + } return 1; } - const AIRCOPY_TransferMap_t *map = AIRCOPY_GetCurrentMap(); - - // Initialize on first call - if (gAirCopyBlockNumber == 0) { - CurrentSegmentIndex = 0; - CurrentOffset = map->segments[0].start_offset; - } - - // Advance to next segment if current is done - while (CurrentSegmentIndex < map->num_segments && - CurrentOffset >= map->segments[CurrentSegmentIndex].end_offset) + if (AircopyCountdown != 0) { - CurrentSegmentIndex++; - if (CurrentSegmentIndex < map->num_segments) { - CurrentOffset = map->segments[CurrentSegmentIndex].start_offset; - } + if (--AircopyCountdown != 0) + return 1; + if (!AIRCOPY_Retry()) + return 0; } - // Check if transfer is complete - if (CurrentSegmentIndex >= map->num_segments) { - gAircopyState = AIRCOPY_COMPLETE; - #ifdef ENABLE_FEAT_F4HWN_K5VIEWER - K5VIEWER_Update(false); - #endif - return 0; - } + const uint16_t currentOffset = AIRCOPY_GetBlockOffset(gAirCopyBlockNumber); + g_FSK_Buffer[0] = AIRCOPY_PACKET_DATA; + g_FSK_Buffer[1] = currentOffset; + EEPROM_ReadBuffer(currentOffset, &g_FSK_Buffer[2], AIRCOPY_BLOCK_SIZE); + AIRCOPY_FinalizeAndSend(); + AircopyCountdown = AIRCOPY_ACK_TIMEOUT_10MS; - // Send data from current offset - g_FSK_Buffer[1] = CurrentOffset; - EEPROM_ReadBuffer(CurrentOffset, &g_FSK_Buffer[2], 64); - - g_FSK_Buffer[34] = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64); - - AIRCOPY_Obfuscate(34); - - RADIO_SetTxParameters(); - - BK4819_SendFSKData(g_FSK_Buffer); - BK4819_SetupPowerAmplifier(0, 0); - BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false); - - CurrentOffset += 64; - gAirCopyBlockNumber++; - gAircopySendCountdown = 30; - - return 0; + return 1; } void AIRCOPY_StorePacket(void) @@ -259,53 +221,111 @@ void AIRCOPY_StorePacket(void) } gFSKWriteIndex = 0; - gUpdateDisplay = true; - uint16_t Status = BK4819_ReadRegister(BK4819_REG_0B); - BK4819_PrepareFSKReceive(); + const uint16_t status = BK4819_ReadRegister(BK4819_REG_0B); + const uint16_t type = g_FSK_Buffer[0]; + bool valid = (status & 0x0010u) == 0 && + (type == AIRCOPY_PACKET_DATA || + type == AIRCOPY_PACKET_ACK || + type == AIRCOPY_PACKET_RESEND) && + g_FSK_Buffer[35] == AIRCOPY_PACKET_END; - if ((Status & 0x0010U) != 0 || g_FSK_Buffer[0] != 0xABCD || g_FSK_Buffer[35] != 0xDCBA) { - BK4819_ResetFSK(); // <- important - BK4819_PrepareFSKReceive(); // <- re-arm proprement - - AIRCOPY_CheckComplete(&gErrorsDuringAirCopy); - return; - } - - AIRCOPY_Obfuscate(34); - - uint16_t Crc = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64); - if (g_FSK_Buffer[34] != Crc) { - AIRCOPY_CheckComplete(&gErrorsDuringAirCopy); - return; - } - - uint16_t Offset = g_FSK_Buffer[1]; - - const AIRCOPY_Segment_t *seg = AIRCOPY_FindSegmentForOffset(Offset); - - if (seg == NULL) { - AIRCOPY_CheckComplete(&gErrorsDuringAirCopy); - return; - } - - const uint8_t *pData = (const uint8_t *)&g_FSK_Buffer[2]; - - for (unsigned int i = 0; i < 8; i++) + if (valid) { - EEPROM_WriteBuffer(Offset + (i * 8), pData + (i * 8)); + AIRCOPY_Obfuscate(34); + valid = g_FSK_Buffer[34] == + CRC_Calculate(&g_FSK_Buffer[0], 4 + AIRCOPY_BLOCK_SIZE); } - AIRCOPY_CheckComplete(&gAirCopyBlockNumber); + if (gAirCopyIsSendMode) + { + if (!valid) + { + BK4819_PrepareFSKReceive(); + return; + } + + const uint16_t offset = g_FSK_Buffer[1]; + const uint16_t currentOffset = AIRCOPY_GetBlockOffset(gAirCopyBlockNumber); + if (type == AIRCOPY_PACKET_ACK && offset == currentOffset) + { + AircopyCountdown = 0; + AircopyRetries = 0; + gAirCopyBlockNumber++; + gUpdateDisplay = true; + if (gAirCopyBlockNumber >= AIRCOPY_GetTotalBlocks()) + AIRCOPY_Finish(AIRCOPY_COMPLETE); + return; + } + + if (type == AIRCOPY_PACKET_RESEND && offset == currentOffset) + { + (void)AIRCOPY_Retry(); + return; + } + + BK4819_PrepareFSKReceive(); + return; + } + + if (!valid) + { + if (type == AIRCOPY_PACKET_DATA) + AIRCOPY_RequestResend(); + else + BK4819_PrepareFSKReceive(); + return; + } + + if (type != AIRCOPY_PACKET_DATA) + { + BK4819_PrepareFSKReceive(); + return; + } + + const uint16_t offset = g_FSK_Buffer[1]; + if (gAirCopyBlockNumber != 0u && + offset == AIRCOPY_GetBlockOffset(gAirCopyBlockNumber - 1u)) + { + AircopyCountdown = gAirCopyBlockNumber >= AIRCOPY_GetTotalBlocks() + ? AIRCOPY_RX_LINGER_10MS + : AIRCOPY_RX_TIMEOUT_10MS; + AIRCOPY_SendControl(AIRCOPY_PACKET_ACK, offset); + return; + } + + if (offset != AIRCOPY_GetBlockOffset(gAirCopyBlockNumber)) + { + AIRCOPY_RequestResend(); + return; + } + + EEPROM_WriteBuffer(offset, &g_FSK_Buffer[2], AIRCOPY_BLOCK_SIZE); + // All pending RX errors concerned this stop-and-wait block. + gErrorsDuringAirCopy = 0; + gAirCopyBlockNumber++; + gUpdateDisplay = true; + + AircopyCountdown = gAirCopyBlockNumber < AIRCOPY_GetTotalBlocks() + ? AIRCOPY_RX_TIMEOUT_10MS + : AIRCOPY_RX_LINGER_10MS; + + AIRCOPY_SendControl(AIRCOPY_PACKET_ACK, offset); } static void AIRCOPY_InitTransfer(bool isSendMode) { - gAircopyStep = 1; + if (gAircopyCurrentMapIndex >= AIRCOPY_NUM_MAPS) + gAircopyCurrentMapIndex = 0; + gFSKWriteIndex = 0; gAirCopyBlockNumber = 0; + gErrorsDuringAirCopy = 0; gInputBoxIndex = 0; gAirCopyIsSendMode = isSendMode; + AircopyCountdown = isSendMode ? 0 : AIRCOPY_RX_TIMEOUT_10MS; + AircopyRetries = 0; + AIRCOPY_clear(); gAircopyState = AIRCOPY_TRANSFER; @@ -359,8 +379,6 @@ static void AIRCOPY_Key_EXIT() { if (gInputBoxIndex == 0) { AIRCOPY_InitTransfer(0); // Mode: Receive - gErrorsDuringAirCopy = lErrorsDuringAirCopy = 0; - BK4819_PrepareFSKReceive(); } else { @@ -371,10 +389,6 @@ static void AIRCOPY_Key_EXIT() static void AIRCOPY_Key_MENU() { AIRCOPY_InitTransfer(1); // Mode: Send - - g_FSK_Buffer[0] = 0xABCD; - g_FSK_Buffer[1] = 0; - g_FSK_Buffer[35] = 0xDCBA; } static void AIRCOPY_Key_UP_DOWN(int8_t Direction) @@ -400,6 +414,14 @@ void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) return; } + if (gAircopyState == AIRCOPY_COMPLETE || gAircopyState == AIRCOPY_FAILED) + { + gAircopyState = AIRCOPY_READY; + gUpdateDisplay = true; + gRequestDisplayScreen = DISPLAY_AIRCOPY; + return; + } + if (Key != KEY_PTT) { gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; } diff --git a/App/app/aircopy.h b/App/app/aircopy.h index f84f22d0..2dd594d6 100644 --- a/App/app/aircopy.h +++ b/App/app/aircopy.h @@ -28,53 +28,8 @@ #define AIRCOPY_BLOCK_SIZE 0x0040u // 64 bytes per AirCopy block #define AIRCOPY_CHANNELS_PER_BANK 128 #define AIRCOPY_NUM_BANKS MR_CHANNELS_MAX / AIRCOPY_CHANNELS_PER_BANK -#define AIRCOPY_CHANNEL_SIZE 16 // bytes per channel (freq/name) -#define AIRCOPY_BANK_SIZE_BYTES 0x1080u // 0x800 (Freq) + 0x800 (Name) + 0x80 (Attr) #define AIRCOPY_BAR_WIDTH 120 // Visible width of the progress gauge -// ============================================================================ -// Segment write mode -// ============================================================================ - -/* - * Defines how a segment must be written to EEPROM. - * - * - STRUCT: structured data (frequencies, names) - * - BYTES : raw byte stream (attributes, settings, etc.) - */ -typedef enum { - AIRCOPY_WRITE_STRUCT = 0, - AIRCOPY_WRITE_BYTES = 1, -} AIRCOPY_WriteMode_t; - -// ============================================================================ -// Transfer segment structure -// ============================================================================ - -/* - * Describes a contiguous EEPROM region involved in AirCopy. - * The write_mode defines how the RX side must write the data. - */ -typedef struct { - uint16_t start_offset; - uint16_t end_offset; - AIRCOPY_WriteMode_t write_mode; -} AIRCOPY_Segment_t; - -// ============================================================================ -// Transfer map structure -// ============================================================================ - -/* - * A transfer map is a collection of segments describing - * one complete AirCopy operation (bank, settings, etc.). - */ -typedef struct { - const AIRCOPY_Segment_t *segments; - uint16_t num_segments; - uint16_t total_blocks; -} AIRCOPY_TransferMap_t; - // ============================================================================ // AirCopy state // ============================================================================ @@ -82,7 +37,8 @@ typedef struct { typedef enum { AIRCOPY_READY = 0, AIRCOPY_TRANSFER, - AIRCOPY_COMPLETE + AIRCOPY_COMPLETE, + AIRCOPY_FAILED } AIRCOPY_State_t; // ============================================================================ @@ -103,8 +59,7 @@ extern uint16_t g_FSK_Buffer[36]; bool AIRCOPY_SendMessage(void); void AIRCOPY_StorePacket(void); void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld); - -const AIRCOPY_TransferMap_t* AIRCOPY_GetCurrentMap(void); +uint8_t AIRCOPY_GetTotalBlocks(void); // XOR-obfuscate `count` words of g_FSK_Buffer starting at index 1. // Self-inverse: applying twice restores the original buffer. diff --git a/App/app/app.c b/App/app/app.c index 618680e9..653be3ff 100644 --- a/App/app/app.c +++ b/App/app/app.c @@ -1007,36 +1007,44 @@ static void CheckRadioInterrupts(void) BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false); } -#ifdef ENABLE_AIRCOPY - if (interrupts.fskFifoAlmostFull && - gScreenToDisplay == DISPLAY_AIRCOPY && - gAircopyState == AIRCOPY_TRANSFER && - gAirCopyIsSendMode == 0) +#if defined(ENABLE_AIRCOPY) || defined(ENABLE_FEAT_F4HWN_BEAM) + if (interrupts.fskFifoAlmostFull || interrupts.fskRxFinied) { - for (unsigned int i = 0; i < 4; i++) { - g_FSK_Buffer[gFSKWriteIndex++] = BK4819_ReadRegister(BK4819_REG_5F); - } - - AIRCOPY_StorePacket(); - } -#endif + uint8_t fskTarget = 0; #ifdef ENABLE_FEAT_F4HWN_BEAM - if ((interrupts.fskFifoAlmostFull || interrupts.fskRxFinied) && - gBeamActive && - gBeamMode == BEAM_MODE_RX && - (gBeamStatus == BEAM_STATUS_RX_WAIT || gBeamStatus == BEAM_STATUS_ERROR)) - { - const unsigned int wordsToRead = interrupts.fskRxFinied ? (36 - gFSKWriteIndex) : 4; - for (unsigned int i = 0; i < wordsToRead; i++) { - const uint16_t word = BK4819_ReadRegister(BK4819_REG_5F); - if (gFSKWriteIndex < 36) - g_FSK_Buffer[gFSKWriteIndex++] = word; - } + if (gBeamActive && + gBeamMode == BEAM_MODE_RX && + (gBeamStatus == BEAM_STATUS_RX_WAIT || gBeamStatus == BEAM_STATUS_ERROR)) + fskTarget = 2; +#endif +#ifdef ENABLE_AIRCOPY + // Aircopy wins if stale state ever makes both receivers eligible. + if (gScreenToDisplay == DISPLAY_AIRCOPY && + gAircopyState == AIRCOPY_TRANSFER) + fskTarget = 1; +#endif - gBeamRxWordCount = gFSKWriteIndex; - gUpdateDisplay = true; - BEAM_StorePacket(); + if (fskTarget != 0) + { + const unsigned int wordsToRead = interrupts.fskRxFinied + ? (gFSKWriteIndex < 36 ? 36u - gFSKWriteIndex : 0u) + : 4u; + for (unsigned int i = 0; i < wordsToRead; i++) { + const uint16_t word = BK4819_ReadRegister(BK4819_REG_5F); + if (gFSKWriteIndex < 36) + g_FSK_Buffer[gFSKWriteIndex++] = word; + } + +#ifdef ENABLE_AIRCOPY + if (fskTarget == 1) + AIRCOPY_StorePacket(); +#endif +#ifdef ENABLE_FEAT_F4HWN_BEAM + if (fskTarget == 2) + BEAM_StorePacket(); +#endif + } } #endif } @@ -1446,7 +1454,7 @@ void CheckKeys(void) #endif #ifdef ENABLE_AIRCOPY - if (gScreenToDisplay == DISPLAY_AIRCOPY && gAircopyState != AIRCOPY_READY){ + if (gScreenToDisplay == DISPLAY_AIRCOPY && gAircopyState == AIRCOPY_TRANSFER){ return; } #endif @@ -1750,7 +1758,7 @@ void APP_TimeSlice10ms(void) #endif #ifdef ENABLE_AIRCOPY - if (gScreenToDisplay == DISPLAY_AIRCOPY && gAircopyState == AIRCOPY_TRANSFER && gAirCopyIsSendMode == 1) { + if (gScreenToDisplay == DISPLAY_AIRCOPY && gAircopyState == AIRCOPY_TRANSFER) { if (!AIRCOPY_SendMessage()) { GUI_DisplayScreen(); } diff --git a/App/app/beam.c b/App/app/beam.c index fab2923d..ae4b3190 100644 --- a/App/app/beam.c +++ b/App/app/beam.c @@ -69,7 +69,6 @@ static_assert(sizeof(BEAM_Payload_t) <= 64); BEAM_Mode_t gBeamMode = BEAM_MODE_TX; BEAM_Status_t gBeamStatus = BEAM_STATUS_READY; uint16_t gBeamCopiedChannel = 0xFFFFu; -uint8_t gBeamRxWordCount; bool gBeamActive; static VFO_Info_t gBeamRadioVfo; @@ -120,7 +119,6 @@ static void BEAM_SendPacket(void) payload->dtmf_decoding_enable = vfo->DTMF_DECODING_ENABLE; #endif payload->step_setting = vfo->STEP_SETTING; - payload->scrambling_type = vfo->SCRAMBLING_TYPE; payload->band = vfo->Band; payload->scanlist = vfo->SCANLIST_PARTICIPATION; payload->compander = vfo->Compander; @@ -192,18 +190,16 @@ static void BEAM_SavePayloadToFirstFreeChannel(const BEAM_Payload_t *payload) #endif vfo.STEP_SETTING = payload->step_setting < STEP_N_ELEM ? payload->step_setting : STEP_12_5kHz; vfo.StepFrequency = gStepFrequencyTable[vfo.STEP_SETTING]; - vfo.SCRAMBLING_TYPE = payload->scrambling_type; vfo.SCANLIST_PARTICIPATION = payload->scanlist; vfo.Compander = payload->compander; memcpy(vfo.Name, payload->name, sizeof(vfo.Name)); vfo.Name[sizeof(vfo.Name) - 1] = '\0'; - RADIO_ApplyOffset(&vfo); - RADIO_ConfigureSquelchAndOutputPower(&vfo); - SETTINGS_SaveChannel(channel, gEeprom.TX_VFO, &vfo, 3); +#ifndef ENABLE_KEEP_MEM_NAME SETTINGS_SaveChannelName(channel, vfo.Name); +#endif gBeamCopiedChannel = channel; gBeamStatus = BEAM_STATUS_RX_SAVED; @@ -221,7 +217,6 @@ static void BEAM_KeyMenu(void) } else { gBeamStatus = BEAM_STATUS_RX_WAIT; gBeamCopiedChannel = 0xFFFFu; - gBeamRxWordCount = 0; gFSKWriteIndex = 0; BK4819_PrepareFSKReceive(); } @@ -249,7 +244,6 @@ void ACTION_Beam(void) gBeamMode = BEAM_MODE_TX; gBeamStatus = BEAM_STATUS_READY; gBeamCopiedChannel = 0xFFFFu; - gBeamRxWordCount = 0; gBeamActive = true; GUI_SelectNextDisplay(DISPLAY_MAIN); } @@ -267,7 +261,6 @@ void BEAM_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) case KEY_DOWN: gBeamMode ^= 1; // (gBeamMode == BEAM_MODE_TX) ? BEAM_MODE_RX : BEAM_MODE_TX gBeamStatus = BEAM_STATUS_READY; - gBeamRxWordCount = 0; break; case KEY_MENU: BEAM_KeyMenu(); @@ -290,7 +283,6 @@ void BEAM_StorePacket(void) if (gFSKWriteIndex < 36) return; - gBeamRxWordCount = gFSKWriteIndex; gFSKWriteIndex = 0; const uint16_t Status = BK4819_ReadRegister(BK4819_REG_0B); @@ -315,7 +307,6 @@ void BEAM_StorePacket(void) error: gBeamStatus = BEAM_STATUS_ERROR; - gBeamRxWordCount = 0; gUpdateDisplay = true; BACKLIGHT_TurnOn(); } diff --git a/App/app/beam.h b/App/app/beam.h index 6571119e..ca9aafa4 100644 --- a/App/app/beam.h +++ b/App/app/beam.h @@ -42,7 +42,6 @@ typedef enum { extern BEAM_Mode_t gBeamMode; extern BEAM_Status_t gBeamStatus; extern uint16_t gBeamCopiedChannel; -extern uint8_t gBeamRxWordCount; extern bool gBeamActive; void ACTION_Beam(void); diff --git a/App/app/menu.c b/App/app/menu.c index d5f905c8..0b80664b 100644 --- a/App/app/menu.c +++ b/App/app/menu.c @@ -72,7 +72,7 @@ uint8_t gUnlockAllTxConfCnt; // EEPROM_ReadBuffer(0x1F88, &misc, 8); misc.BK4819_XtalFreqLow = value; - EEPROM_WriteBuffer(0x1F88, &misc); + EEPROM_WriteBuffer(0x1F88, &misc, 8); } } #endif diff --git a/App/app/uart.c b/App/app/uart.c index a298cefb..87be1f5b 100644 --- a/App/app/uart.c +++ b/App/app/uart.c @@ -486,7 +486,7 @@ static void CMD_051D(uint32_t Port, const uint8_t *pBuffer) if ((Offset < 0x0E98 || Offset >= 0x0EA0) || !bIsInLockScreen || pCmd->bAllowPassword) { - EEPROM_WriteBuffer(Offset, &pCmd->Data[i * 8U]); + EEPROM_WriteBuffer(Offset, &pCmd->Data[i * 8U], 8); } } diff --git a/App/driver/eeprom.c b/App/driver/eeprom.c index 4357f658..b00a7e1b 100644 --- a/App/driver/eeprom.c +++ b/App/driver/eeprom.c @@ -39,25 +39,30 @@ void EEPROM_ReadBuffer(uint16_t Address, void *pBuffer, uint8_t Size) I2C_Stop(); } -void EEPROM_WriteBuffer(uint16_t Address, const void *pBuffer) +void EEPROM_WriteBuffer(uint16_t Address, const void *pBuffer, uint8_t Size) { - if (pBuffer == NULL || Address >= 0x2000) + if (pBuffer == NULL) return; - uint8_t buffer[8]; - EEPROM_ReadBuffer(Address, buffer, 8); - if (memcmp(pBuffer, buffer, 8) == 0) { - return; + while (Size >= sizeof(buffer) && Address < 0x2000) + { + EEPROM_ReadBuffer(Address, buffer, sizeof(buffer)); + if (memcmp(pBuffer, buffer, sizeof(buffer)) != 0) + { + I2C_Start(); + I2C_Write(0xA0); + I2C_Write((Address >> 8) & 0xFF); + I2C_Write((Address >> 0) & 0xFF); + I2C_WriteBuffer(pBuffer, sizeof(buffer)); + I2C_Stop(); + + // give the EEPROM time to burn the data in (apparently takes 5ms) + SYSTEM_DelayMs(8); + } + + Address += sizeof(buffer); + pBuffer += sizeof(buffer); + Size -= sizeof(buffer); } - - I2C_Start(); - I2C_Write(0xA0); - I2C_Write((Address >> 8) & 0xFF); - I2C_Write((Address >> 0) & 0xFF); - I2C_WriteBuffer(pBuffer, 8); - I2C_Stop(); - - // give the EEPROM time to burn the data in (apparently takes 5ms) - SYSTEM_DelayMs(8); } diff --git a/App/driver/eeprom.h b/App/driver/eeprom.h index 545c48c9..e0213595 100644 --- a/App/driver/eeprom.h +++ b/App/driver/eeprom.h @@ -20,7 +20,6 @@ #include void EEPROM_ReadBuffer(uint16_t Address, void *pBuffer, uint8_t Size); -void EEPROM_WriteBuffer(uint16_t Address, const void *pBuffer); +void EEPROM_WriteBuffer(uint16_t Address, const void *pBuffer, uint8_t Size); #endif - diff --git a/App/driver/eeprom_compat.c b/App/driver/eeprom_compat.c index ed9d9c7c..a2a2215b 100644 --- a/App/driver/eeprom_compat.c +++ b/App/driver/eeprom_compat.c @@ -110,11 +110,8 @@ void EEPROM_ReadBuffer(uint16_t Address, void *pBuffer, uint8_t Size) } } -void EEPROM_WriteBuffer(uint16_t Address, const void *pBuffer) +void EEPROM_WriteBuffer(uint16_t Address, const void *pBuffer, uint8_t Size) { - // Write 8 bytes!! - - uint16_t Size = 8; while (Size) { uint32_t PY_Addr; diff --git a/App/misc.c b/App/misc.c index f49ba7d9..8c5292ad 100644 --- a/App/misc.c +++ b/App/misc.c @@ -144,9 +144,6 @@ enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx; uint8_t gDW = 0; uint8_t gCB = 0; bool gSaveRxMode = false; - uint8_t crc[15] = { 0 }; - uint8_t lErrorsDuringAirCopy = 0; - uint8_t gAircopyStep = 0; uint8_t gAircopyCurrentMapIndex = 0; bool gAirCopyBootMode = 0; #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS diff --git a/App/misc.h b/App/misc.h index 461f636d..8457a621 100644 --- a/App/misc.h +++ b/App/misc.h @@ -219,9 +219,6 @@ extern enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx; extern uint8_t gDW; extern uint8_t gCB; extern bool gSaveRxMode; - extern uint8_t crc[15]; - extern uint8_t lErrorsDuringAirCopy; - extern uint8_t gAircopyStep; extern uint8_t gAircopyCurrentMapIndex; extern bool gAirCopyBootMode; #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS diff --git a/App/ui/aircopy.c b/App/ui/aircopy.c index 135d95c5..333820b5 100644 --- a/App/ui/aircopy.c +++ b/App/ui/aircopy.c @@ -27,14 +27,6 @@ #include "ui/helper.h" #include "ui/inputbox.h" -static void set_bit(uint8_t* array, int bit_index) { - array[bit_index / 8] |= (1 << (bit_index % 8)); -} - -static int get_bit(uint8_t* array, int bit_index) { - return (array[bit_index / 8] >> (bit_index % 8)) & 1; -} - void UI_DisplayAircopy(void) { char String[16]; @@ -46,9 +38,10 @@ void UI_DisplayAircopy(void) pPrintStr = "AIR COPY(RDY)"; } else if (gAircopyState == AIRCOPY_TRANSFER) { pPrintStr = "AIR COPY"; + } else if (gAircopyState == AIRCOPY_COMPLETE) { + pPrintStr = "AIR COPY OK"; } else { - pPrintStr = "AIR COPY(CMP)"; - gAircopyState = AIRCOPY_READY; + pPrintStr = "AIR COPY FAIL"; } UI_PrintString(pPrintStr, 2, 127, 0, 8); @@ -67,19 +60,15 @@ void UI_DisplayAircopy(void) // show the main large frequency digits UI_DisplayFrequency(String, 16, 2, false); - // Get the current map and calculate percentage based on its total blocks - const AIRCOPY_TransferMap_t *currentMap = AIRCOPY_GetCurrentMap(); + const uint8_t totalBlocks = AIRCOPY_GetTotalBlocks(); + uint16_t doneBlocks = gAirCopyBlockNumber; - uint16_t doneBlocks = gAirCopyBlockNumber + gErrorsDuringAirCopy; - - if (doneBlocks > currentMap->total_blocks) - doneBlocks = currentMap->total_blocks; + if (doneBlocks > totalBlocks) + doneBlocks = totalBlocks; // Draw memory selection if (gAircopyState == AIRCOPY_READY) { - doneBlocks = 0; - if(gAircopyCurrentMapIndex < AIRCOPY_NUM_BANKS) { sprintf(String, "MEM %03u - %03u", (gAircopyCurrentMapIndex * 128) + 1, (gAircopyCurrentMapIndex + 1) * 128); } else { @@ -89,67 +78,41 @@ void UI_DisplayAircopy(void) } else { - uint16_t percent = (doneBlocks * 10000) / currentMap->total_blocks; + uint16_t percent = (doneBlocks * 10000) / totalBlocks; + const unsigned displayedErrors = gErrorsDuringAirCopy > 99u + ? 99u + : gErrorsDuringAirCopy; - if (gAirCopyIsSendMode == 0) { - sprintf(String, "RCV:%02u.%02u%% E:%d", percent / 100, percent % 100, gErrorsDuringAirCopy); + if (gAircopyState == AIRCOPY_COMPLETE || gAircopyState == AIRCOPY_FAILED) { + sprintf(String, "%s %u/%u %s:%u", + gAircopyState == AIRCOPY_COMPLETE ? "OK" : "KO", + doneBlocks, totalBlocks, + gAirCopyIsSendMode ? "RT" : "ER", + displayedErrors); + } else if (gAirCopyIsSendMode == 0) { + sprintf(String, "RX:%02u.%02u ER:%u", percent / 100, percent % 100, + displayedErrors); } else { - sprintf(String, "SND:%02u.%02u%%", percent / 100, percent % 100); + sprintf(String, "TX:%02u.%02u RT:%u", percent / 100, percent % 100, + displayedErrors); } - // Draw gauge - if(gAircopyStep != 0) - { - UI_PrintString(String, 2, 127, 5, 8); - - gFrameBuffer[4][1] = 0x3c; - gFrameBuffer[4][2] = 0x42; - - for(uint8_t i = 1; i <= AIRCOPY_BAR_WIDTH + 2; i++) - { - gFrameBuffer[4][2 + i] = 0x81; - } - - gFrameBuffer[4][125] = 0x42; - gFrameBuffer[4][126] = 0x3c; - } - } - - if (doneBlocks > 0) - { - // Track CRC errors per real block index - if (gErrorsDuringAirCopy != lErrorsDuringAirCopy) - { - // Mark the last processed block as faulty - set_bit(crc, doneBlocks - 1); - lErrorsDuringAirCopy = gErrorsDuringAirCopy; - } - - uint16_t b = 0; - uint16_t fraction_accumulator = 0; + UI_PrintString(String, 2, 127, 5, 8); + gFrameBuffer[4][1] = 0x3c; + gFrameBuffer[4][2] = 0x42; + gFrameBuffer[4][3] = 0x81; + // Match the former DDA gauge exactly, including its partial first pixel. + const uint8_t filled = (doneBlocks * AIRCOPY_BAR_WIDTH + totalBlocks - 1u) + / totalBlocks; for (uint8_t col = 0; col < AIRCOPY_BAR_WIDTH; col++) - { - bool processed = (b < doneBlocks); - bool error = processed && get_bit(crc, b); - - if (!processed) - gFrameBuffer[4][col + 4] = 0x81; // not yet processed - else if (error) - gFrameBuffer[4][col + 4] = 0x81; // error gap (intentional hole) - else - gFrameBuffer[4][col + 4] = 0xBD; // ok filled - - // DDA/Bresenham algorythm - fraction_accumulator += currentMap->total_blocks; - while (fraction_accumulator >= AIRCOPY_BAR_WIDTH) { - fraction_accumulator -= AIRCOPY_BAR_WIDTH; - b++; - } - } + gFrameBuffer[4][col + 4] = col < filled ? 0xBD : 0x81; + gFrameBuffer[4][124] = 0x81; + gFrameBuffer[4][125] = 0x42; + gFrameBuffer[4][126] = 0x3c; } ST7565_BlitFullScreen(); } -#endif \ No newline at end of file +#endif