Make Aircopy transfers reliable with ACK/RESEND recovery and leaner FSK handling

This commit is contained in:
Armel FAUVEAU committed 2026-08-25 13:05:20 +02:00
1 parent ce6bf08b23
commit 285f543d46
13 files changed
+299 -366

No files matched your search

+220 -198
View File
@@ -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 <stddef.h>
#include <string.h>
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
#include "k5viewer.h"
@@ -43,141 +45,74 @@ 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;
}
return AIRCOPY_AvailableMaps[gAircopyCurrentMapIndex];
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;
}
// 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);
#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;
}
}
// Check if transfer is complete
if (CurrentSegmentIndex >= map->num_segments) {
gAircopyState = AIRCOPY_COMPLETE;
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
K5VIEWER_Update(false);
#endif
if (--AircopyCountdown != 0)
return 1;
if (!AIRCOPY_Retry())
return 0;
}
// Send data from current offset
g_FSK_Buffer[1] = CurrentOffset;
EEPROM_ReadBuffer(CurrentOffset, &g_FSK_Buffer[2], 64);
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;
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;
}
+3 -48
View File
@@ -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.
+27 -19
View File
@@ -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 &&
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
if (fskTarget != 0)
{
const unsigned int wordsToRead = interrupts.fskRxFinied ? (36 - gFSKWriteIndex) : 4;
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;
}
gBeamRxWordCount = gFSKWriteIndex;
gUpdateDisplay = true;
#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();
}
+2 -11
View File
@@ -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();
}
-1
View File
@@ -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);
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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);
}
}
+14 -9
View File
@@ -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, 8);
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);
}
}
+1 -2
View File
@@ -20,7 +20,6 @@
#include <stdint.h>
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
+1 -4
View File
@@ -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;
-3
View File
@@ -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
-3
View File
@@ -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
+29 -66
View File
@@ -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,65 +78,39 @@ 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][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++)
gFrameBuffer[4][col + 4] = col < filled ? 0xBD : 0x81;
gFrameBuffer[4][124] = 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;
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++;
}
}
}
ST7565_BlitFullScreen();
}