/* Copyright 2023 Dual Tachyon * https://github.com/DualTachyon * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include "app/aircopy.h" #include "audio.h" #include "bsp/dp32g030/gpio.h" #include "driver/backlight.h" #include "driver/bk4819.h" #include "driver/crc.h" #include "driver/eeprom.h" #include "driver/gpio.h" #include "driver/system.h" #include "driver/uart.h" #include "frequencies.h" #include "misc.h" #include "radio.h" #include "settings.h" #include "ui/helper.h" #include "ui/inputbox.h" #include "ui/ui.h" // ********************** #define AIRCOPY_MAGIC_START_REQ 0xBCDA // used to request a block resend #define AIRCOPY_MAGIC_END_REQ 0xCDBA // used to request a block resend #define AIRCOPY_MAGIC_START 0xABCD // normal start value #define AIRCOPY_MAGIC_END 0xDCBA // normal end value #define AIRCOPY_LAST_EEPROM_ADDR 0x1E00 // size of eeprom transferred // FSK Data Length .. 0xABCD + 2 byte eeprom address + 64 byte payload + 2 byte CRC + 0xDCBA #define AIRCOPY_DATA_PACKET_SIZE (2 + 2 + 64 + 2 + 2) // FSK Data Length .. 0xBCDA + 2 byte eeprom address + 2 byte CRC + 0xCDBA #define AIRCOPY_REQ_PACKET_SIZE (2 + 2 + 64 + 2 + 2) // ********************** const unsigned int g_aircopy_block_max = 120; unsigned int g_aircopy_block_number; uint8_t g_aircopy_rx_errors_fsk_crc; uint8_t g_aircopy_rx_errors_magic; uint8_t g_aircopy_rx_errors_crc; aircopy_state_t g_aircopy_state; uint16_t g_fsk_buffer[AIRCOPY_DATA_PACKET_SIZE / 2]; unsigned int g_fsk_write_index; uint16_t g_fsk_tx_timeout_10ms; uint8_t aircopy_send_count_down_10ms; void AIRCOPY_init(void) { // turn the backlight ON GPIO_SetBit(&GPIOB->DATA, GPIOB_PIN_BACKLIGHT); RADIO_setup_registers(true); BK4819_SetupAircopy(AIRCOPY_DATA_PACKET_SIZE); BK4819_reset_fsk(); g_aircopy_state = AIRCOPY_READY; g_fsk_write_index = 0; BK4819_set_GPIO_pin(BK4819_GPIO6_PIN2_GREEN, false); // LED off BK4819_start_fsk_rx(AIRCOPY_DATA_PACKET_SIZE); GUI_SelectNextDisplay(DISPLAY_AIRCOPY); } void AIRCOPY_start_fsk_tx(const int request_block_num) { const unsigned int eeprom_addr = (request_block_num < 0) ? g_aircopy_block_number * 64 : (unsigned int)request_block_num * 64; uint16_t fsk_reg59; unsigned int k; unsigned int tx_size = 0; // ********* // packet start g_fsk_buffer[tx_size++] = (request_block_num < 0) ? AIRCOPY_MAGIC_START : AIRCOPY_MAGIC_START_REQ; // eeprom address g_fsk_buffer[tx_size++] = eeprom_addr; // data if (request_block_num < 0) { EEPROM_ReadBuffer(eeprom_addr, &g_fsk_buffer[tx_size], 64); tx_size += 64 / 2; } // data CRC g_fsk_buffer[tx_size++] = CRC_Calculate(&g_fsk_buffer[1], (request_block_num < 0) ? 2 + 64 : 2); // packet end g_fsk_buffer[tx_size++] = (request_block_num < 0) ? AIRCOPY_MAGIC_END : AIRCOPY_MAGIC_END_REQ; // ********* { // scramble the packet uint8_t *p = (uint8_t *)&g_fsk_buffer[1]; for (k = 0; k < ((tx_size - 2) * 2); k++) *p++ ^= obfuscate_array[k % ARRAY_SIZE(obfuscate_array)]; } g_fsk_tx_timeout_10ms = 1000 / 10; // 1 second timeout // turn the TX on RADIO_enableTX(true); // REG_59 // // <15> 0 TX FIFO // 1 = clear // // <14> 0 RX FIFO // 1 = clear // // <13> 0 FSK Scramble // 1 = Enable // // <12> 0 FSK RX // 1 = Enable // // <11> 0 FSK TX // 1 = Enable // // <10> 0 FSK data when RX // 1 = Invert // // <9> 0 FSK data when TX // 1 = Invert // // <8> 0 ??? // // <7:4> 0 FSK preamble length selection // 0 = 1 byte // 1 = 2 bytes // 2 = 3 bytes // 15 = 16 bytes // // <3> 0 FSK sync length selection // 0 = 2 bytes (FSK Sync Byte 0, 1) // 1 = 4 bytes (FSK Sync Byte 0, 1, 2, 3) // // <2:0> 0 ??? // // 0x0068 0000 0000 0110 1000 // fsk_reg59 = (0u << 15) | // 0 or 1 1 = clear TX FIFO (0u << 14) | // 0 or 1 1 = clear RX FIFO (0u << 13) | // 0 or 1 1 = scramble (0u << 12) | // 0 or 1 1 = enable RX (0u << 11) | // 0 or 1 1 = enable TX (0u << 10) | // 0 or 1 1 = invert data when RX (0u << 9) | // 0 or 1 1 = invert data when TX (0u << 8) | // 0 or 1 ??? (6u << 4) | // 0 ~ 15 preamble Length Selection (1u << 3) | // 0 or 1 sync length selection (0u << 0); // 0 ~ 7 ??? // set the packet size BK4819_WriteRegister(BK4819_REG_5D, (((tx_size * 2) - 1) << 8)); // clear TX fifo BK4819_WriteRegister(BK4819_REG_59, (1u << 15) | fsk_reg59); BK4819_WriteRegister(BK4819_REG_59, fsk_reg59); // load the packet for (k = 0; k < tx_size; k++) BK4819_WriteRegister(BK4819_REG_5F, g_fsk_buffer[k]); // enable tx interrupt(s) BK4819_WriteRegister(BK4819_REG_3F, BK4819_REG_3F_FSK_TX_FINISHED); // enable scramble, enable TX BK4819_WriteRegister(BK4819_REG_59, (1u << 13) | (1u << 11) | fsk_reg59); } void AIRCOPY_stop_fsk_tx(void) { if (g_aircopy_state != AIRCOPY_TX && g_fsk_tx_timeout_10ms == 0) return; g_fsk_tx_timeout_10ms = 0; // disable the TX BK4819_SetupPowerAmplifier(0, 0); // BK4819_set_GPIO_pin(BK4819_GPIO1_PIN29_PA_ENABLE, false); // PA off BK4819_set_GPIO_pin(BK4819_GPIO5_PIN1_RED, false); // LED off BK4819_reset_fsk(); if (g_aircopy_state == AIRCOPY_TX) { g_aircopy_block_number++; // TX pause/gap time till we start the next packet aircopy_send_count_down_10ms = 250 / 10; // 250ms g_update_display = true; GUI_DisplayScreen(); } } void AIRCOPY_process_fsk_tx_10ms(void) { uint16_t interrupt_bits = 0; if (g_aircopy_state != AIRCOPY_TX && g_aircopy_state != AIRCOPY_RX) return; if (g_fsk_tx_timeout_10ms == 0) { // not currently TX'ing if (g_aircopy_state == AIRCOPY_TX) { // we're still TX transferring if (g_fsk_write_index > 0) return; // currently RX'ing a packet if (aircopy_send_count_down_10ms > 0) if (--aircopy_send_count_down_10ms > 0) return; // not yet time to TX next packet if (g_aircopy_block_number >= g_aircopy_block_max) { // transfer is complete g_aircopy_state = AIRCOPY_TX_COMPLETE; AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP); } else { // start next TX packet AIRCOPY_start_fsk_tx(-1); } g_update_display = true; GUI_DisplayScreen(); } return; } if (--g_fsk_tx_timeout_10ms > 0) { // still TX'ing if ((BK4819_ReadRegister(BK4819_REG_0C) & (1u << 0)) == 0) return; BK4819_WriteRegister(BK4819_REG_02, 0); interrupt_bits = BK4819_ReadRegister(BK4819_REG_02); if ((interrupt_bits & BK4819_REG_02_FSK_TX_FINISHED) == 0) return; // TX not yet finished } AIRCOPY_stop_fsk_tx(); if (g_aircopy_state == AIRCOPY_RX) { g_fsk_write_index = 0; BK4819_start_fsk_rx(AIRCOPY_DATA_PACKET_SIZE); } else if (g_aircopy_state == AIRCOPY_TX) { g_fsk_write_index = 0; BK4819_start_fsk_rx(AIRCOPY_REQ_PACKET_SIZE); } } void AIRCOPY_process_fsk_rx_10ms(void) { const unsigned int block_size = 64; const unsigned int write_size = 8; const unsigned int req_ack_size = 4; uint16_t interrupt_bits; uint16_t status; uint16_t crc1; uint16_t crc2; uint16_t eeprom_addr; uint16_t *data; unsigned int block_num; bool req_ack_packet = false; unsigned int i; // REG_59 // // <15> 0 TX FIFO // 1 = clear // // <14> 0 RX FIFO // 1 = clear // // <13> 0 FSK Scramble // 1 = Enable // // <12> 0 FSK RX // 1 = Enable // // <11> 0 FSK TX // 1 = Enable // // <10> 0 FSK data when RX // 1 = Invert // // <9> 0 FSK data when TX // 1 = Invert // // <8> 0 ??? // // <7:4> 0 FSK preamble length selection // 0 = 1 byte // 1 = 2 bytes // 2 = 3 bytes // 15 = 16 bytes // // <3> 0 FSK sync length selection // 0 = 2 bytes (FSK Sync Byte 0, 1) // 1 = 4 bytes (FSK Sync Byte 0, 1, 2, 3) // // <2:0> 0 ??? // status = BK4819_ReadRegister(BK4819_REG_59); if (status & (1u << 11) || g_fsk_tx_timeout_10ms > 0) return; // FSK TX is busy if ((status & (1u << 12)) == 0) { // FSK RX is disabled, enable it g_fsk_write_index = 0; BK4819_set_GPIO_pin(BK4819_GPIO6_PIN2_GREEN, false); // LED off BK4819_start_fsk_rx((g_aircopy_state == AIRCOPY_TX) ? AIRCOPY_REQ_PACKET_SIZE : AIRCOPY_DATA_PACKET_SIZE); } status = BK4819_ReadRegister(BK4819_REG_0C); if ((status & (1u << 0)) == 0) return; // no flagged interrupts // read the interrupt flags BK4819_WriteRegister(BK4819_REG_02, 0); // clear them interrupt_bits = BK4819_ReadRegister(BK4819_REG_02); if (interrupt_bits & BK4819_REG_02_FSK_RX_SYNC) BK4819_set_GPIO_pin(BK4819_GPIO6_PIN2_GREEN, true); // LED on if (interrupt_bits & BK4819_REG_02_FSK_RX_FINISHED) BK4819_set_GPIO_pin(BK4819_GPIO6_PIN2_GREEN, false); // LED off if ((interrupt_bits & BK4819_REG_02_FSK_FIFO_ALMOST_FULL) == 0) return; BK4819_set_GPIO_pin(BK4819_GPIO6_PIN2_GREEN, true); // LED on // fetch RX'ed data for (i = 0; i < 4; i++) { const uint16_t word = BK4819_ReadRegister(BK4819_REG_5F); if (g_fsk_write_index < ARRAY_SIZE(g_fsk_buffer)) g_fsk_buffer[g_fsk_write_index++] = word; } // REG_0B read only // // <15:12> ??? // // <11:8> DTMF/5-tone code received // // <7> FSK RX sync negative has been found // // <6> FSK RX sync positive has been found // // <5> ??? // // <4> FSK RX CRC indicator // 1 = CRC pass // 0 = CRC fail // // <3:0> ??? // status = BK4819_ReadRegister(BK4819_REG_0B); // check to see if it's a REQ/ACK packet if (g_fsk_write_index == req_ack_size) req_ack_packet = (g_fsk_buffer[0] == AIRCOPY_MAGIC_START_REQ && g_fsk_buffer[g_fsk_write_index - 1] == AIRCOPY_MAGIC_END_REQ); #if defined(ENABLE_UART) && defined(ENABLE_UART_DEBUG) // UART_printf("aircopy rx %04X %u\r\n", interrupt_bits, g_fsk_write_index); #endif if (g_fsk_write_index < ARRAY_SIZE(g_fsk_buffer) && !req_ack_packet) return; // not yet a complete packet // restart the RX BK4819_set_GPIO_pin(BK4819_GPIO6_PIN2_GREEN, false); // LED off BK4819_start_fsk_rx((g_aircopy_state == AIRCOPY_TX) ? AIRCOPY_REQ_PACKET_SIZE : AIRCOPY_DATA_PACKET_SIZE); g_update_display = true; // doc says bit 4 should be 1 = CRC OK, 0 = CRC FAIL, but original firmware checks for FAIL if ((status & (1u << 4)) != 0) { g_aircopy_rx_errors_fsk_crc++; #if defined(ENABLE_UART) && defined(ENABLE_UART_DEBUG) UART_printf("aircopy status %04X\r\n", status); #endif g_fsk_write_index = 0; return; } { // unscramble the packet uint8_t *p = (uint8_t *)&g_fsk_buffer[1]; for (i = 0; i < ((g_fsk_write_index - 2) * 2); i++) *p++ ^= obfuscate_array[i % ARRAY_SIZE(obfuscate_array)]; } // compute the CRC crc1 = CRC_Calculate(&g_fsk_buffer[1], (g_fsk_write_index - 3) * 2); // fetch the CRC crc2 = g_fsk_buffer[g_fsk_write_index - 2]; #if defined(ENABLE_UART) && defined(ENABLE_UART_DEBUG) // show the entire packet UART_SendText("aircopy"); for (i = 0; i < g_fsk_write_index; i++) UART_printf(" %04X", g_fsk_buffer[i]); UART_printf(" - %04X\r\n", status); #endif // check the CRC if (crc2 != crc1) { // invalid CRC g_aircopy_rx_errors_crc++; #if defined(ENABLE_UART) && defined(ENABLE_UART_DEBUG) UART_printf("aircopy invalid CRC %04X %04X\r\n", crc2, crc1); #endif if (g_aircopy_state == AIRCOPY_RX) goto send_req; g_fsk_write_index = 0; return; } eeprom_addr = g_fsk_buffer[1]; data = &g_fsk_buffer[2]; block_num = eeprom_addr / block_size; if (req_ack_packet) { // it's a req/ack packet #if defined(ENABLE_UART) && defined(ENABLE_UART_DEBUG) UART_printf("aircopy RX req %04X %04X\r\n", block_num * 64, g_aircopy_block_number * 64); #endif if (g_aircopy_state == AIRCOPY_TX) { // we are the TX'ing radio if (block_num >= g_aircopy_block_max) { // they have all the blocks .. transfer is complete g_aircopy_block_number = g_aircopy_block_max; g_aircopy_state = AIRCOPY_TX_COMPLETE; AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP); } else { // send them the block they want g_aircopy_block_number = block_num; // go to the block number they want aircopy_send_count_down_10ms = 0; // TX asap } } g_fsk_write_index = 0; return; } if (g_aircopy_state != AIRCOPY_RX) { // not in RX mode .. ignore it g_fsk_write_index = 0; return; } if (g_fsk_buffer[0] != AIRCOPY_MAGIC_START || g_fsk_buffer[g_fsk_write_index - 1] != AIRCOPY_MAGIC_END) { // invalid magics g_aircopy_rx_errors_magic++; goto send_req; } if (eeprom_addr != (block_num * block_size)) { // eeprom address not block aligned .. ignore it goto send_req; } if (block_num != g_aircopy_block_number) { // not the block number we're expecting .. request the correct one goto send_req; } if ((eeprom_addr + block_size) > AIRCOPY_LAST_EEPROM_ADDR) { // ignore it goto send_req; } // clear the error counts g_aircopy_rx_errors_fsk_crc = 0; g_aircopy_rx_errors_magic = 0; g_aircopy_rx_errors_crc = 0; // eeprom block appears valid .. write it directly to eeprom for (i = 0; i < (block_size / write_size); i++) { if (eeprom_addr == 0x0E98) { // power-on password .. wipe it //#ifndef ENABLE_PWRON_PASSWORD data[0] = 0xffff; data[1] = 0xffff; //#endif } else if (eeprom_addr == 0x0F30) { // AES key .. wipe it //#ifdef ENABLE_RESET_AES_KEY data[0] = 0xffff; data[1] = 0xffff; data[2] = 0xffff; data[3] = 0xffff; //#endif } else if (eeprom_addr == 0x0F40) { // killed flag is here data[2] = false; // remove it } EEPROM_WriteBuffer8(eeprom_addr, data); // 8 bytes at a time data += write_size / sizeof(data[0]); eeprom_addr += write_size; } g_aircopy_block_number = block_num + 1; g_fsk_write_index = 0; if (eeprom_addr >= AIRCOPY_LAST_EEPROM_ADDR) { // transfer is complete g_aircopy_state = AIRCOPY_RX_COMPLETE; AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP); #ifdef ENABLE_AIRCOPY_RX_REBOOT #if defined(ENABLE_OVERLAY) overlay_FLASH_RebootToBootloader(); #else NVIC_SystemReset(); #endif #endif } return; send_req: g_fsk_write_index = 0; #if defined(ENABLE_UART) && defined(ENABLE_UART_DEBUG) UART_printf("aircopy TX req %04X %04X\r\n", g_aircopy_block_number * 64, block_num * 64); #endif // this packet takes 150ms start to finish AIRCOPY_start_fsk_tx(g_aircopy_block_number); g_fsk_tx_timeout_10ms = 200 / 5; // allow up to 200ms for the TX to complete while (g_fsk_tx_timeout_10ms-- > 0) { SYSTEM_DelayMs(5); if (BK4819_ReadRegister(BK4819_REG_0C) & (1u << 0)) { // we have interrupt flags BK4819_WriteRegister(BK4819_REG_02, 0); const uint16_t interrupt_bits = BK4819_ReadRegister(BK4819_REG_02); if (interrupt_bits & BK4819_REG_02_FSK_TX_FINISHED) g_fsk_tx_timeout_10ms = 0; // TX is complete } } AIRCOPY_stop_fsk_tx(); BK4819_start_fsk_rx(AIRCOPY_DATA_PACKET_SIZE); } static void AIRCOPY_Key_DIGITS(key_code_t Key, bool key_pressed, bool key_held) { if (g_aircopy_state == AIRCOPY_RX || g_aircopy_state == AIRCOPY_TX) return; if (g_aircopy_state != AIRCOPY_READY) { g_aircopy_state = AIRCOPY_READY; AIRCOPY_stop_fsk_tx(); g_update_display = true; GUI_DisplayScreen(); } if (!key_held && key_pressed) { uint32_t Frequency; unsigned int i; INPUTBOX_Append(Key); g_request_display_screen = DISPLAY_AIRCOPY; if (g_input_box_index < 6) { #ifdef ENABLE_VOICE g_another_voice_id = (voice_id_t)Key; #endif return; } g_input_box_index = 0; NUMBER_Get(g_input_box, &Frequency); for (i = 0; i < ARRAY_SIZE(FREQ_BAND_TABLE); i++) { if (Frequency >= FREQ_BAND_TABLE[i].lower && Frequency < FREQ_BAND_TABLE[i].upper) { #ifdef ENABLE_VOICE g_another_voice_id = (voice_id_t)Key; #endif g_rx_vfo->band = i; // round the frequency to nearest step size Frequency = ((Frequency + (g_rx_vfo->step_freq / 2)) / g_rx_vfo->step_freq) * g_rx_vfo->step_freq; g_aircopy_freq = Frequency; #ifdef ENABLE_AIRCOPY_REMEMBER_FREQ SETTINGS_save(); // remeber the frequency for the next time #endif g_rx_vfo->freq_config_rx.frequency = Frequency; g_rx_vfo->freq_config_tx.frequency = Frequency; RADIO_ConfigureSquelchAndOutputPower(g_rx_vfo); g_current_vfo = g_rx_vfo; AIRCOPY_init(); return; } } g_request_display_screen = DISPLAY_AIRCOPY; } } static void AIRCOPY_Key_EXIT(bool key_pressed, bool key_held) { if (!key_pressed) return; if (g_aircopy_state != AIRCOPY_READY) { if (!key_held) { // turn the green LED off BK4819_set_GPIO_pin(BK4819_GPIO6_PIN2_GREEN, false); g_input_box_index = 0; g_aircopy_state = AIRCOPY_READY; AIRCOPY_stop_fsk_tx(); AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP); AIRCOPY_init(); g_update_display = true; GUI_DisplayScreen(); } } else if (key_held) { if (g_input_box_index > 0) { // cancel the frequency input g_input_box_index = 0; g_update_display = true; GUI_DisplayScreen(); } } else if (g_input_box_index > 0) { // entering a new frequency to use g_input_box[--g_input_box_index] = 10; GUI_DisplayScreen(); } else { // enter RX mode // turn the green LED off BK4819_set_GPIO_pin(BK4819_GPIO6_PIN2_GREEN, false); g_input_box_index = 0; AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP); AIRCOPY_init(); g_fsk_write_index = 0; g_aircopy_block_number = 0; g_aircopy_rx_errors_fsk_crc = 0; g_aircopy_rx_errors_magic = 0; g_aircopy_rx_errors_crc = 0; g_aircopy_state = AIRCOPY_RX; BK4819_start_fsk_rx(AIRCOPY_DATA_PACKET_SIZE); g_update_display = true; GUI_DisplayScreen(); } } static void AIRCOPY_Key_MENU(bool key_pressed, bool key_held) { (void)key_held; if (g_aircopy_state == AIRCOPY_RX || g_aircopy_state == AIRCOPY_TX) return; // busy if (key_pressed && !key_held) { // key released // enter TX mode g_input_box_index = 0; AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP); AIRCOPY_init(); g_fsk_write_index = 0; g_aircopy_block_number = 0; g_aircopy_rx_errors_fsk_crc = 0; g_aircopy_rx_errors_magic = 0; g_aircopy_rx_errors_crc = 0; g_fsk_tx_timeout_10ms = 0; aircopy_send_count_down_10ms = 0; g_aircopy_state = AIRCOPY_TX; g_update_display = true; GUI_DisplayScreen(); } } void AIRCOPY_process_key(key_code_t Key, bool key_pressed, bool key_held) { switch (Key) { case KEY_0: case KEY_1: case KEY_2: case KEY_3: case KEY_4: case KEY_5: case KEY_6: case KEY_7: case KEY_8: case KEY_9: AIRCOPY_Key_DIGITS(Key, key_pressed, key_held); break; case KEY_MENU: AIRCOPY_Key_MENU(key_pressed, key_held); break; case KEY_EXIT: AIRCOPY_Key_EXIT(key_pressed, key_held); break; default: break; } }