kopia lustrzana https://github.com/sq8vps/vp-digi
conflict resolution
commit
f4ad4a8022
|
@ -14,7 +14,7 @@
|
|||
</extensions>
|
||||
</storageModule>
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<configuration artifactExtension="elf" artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.debug" cleanCommand="rm -rf" description="" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.config.exe.debug.1612780670" name="Debug" parent="com.st.stm32cube.ide.mcu.gnu.managedbuild.config.exe.debug">
|
||||
<configuration artifactExtension="elf" artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.debug" cleanCommand="rm -rf" description="" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.config.exe.debug.1612780670" name="Debug" parent="com.st.stm32cube.ide.mcu.gnu.managedbuild.config.exe.debug" postbuildStep="">
|
||||
<folderInfo id="com.st.stm32cube.ide.mcu.gnu.managedbuild.config.exe.debug.1612780670." name="/" resourcePath="">
|
||||
<toolChain id="com.st.stm32cube.ide.mcu.gnu.managedbuild.toolchain.exe.debug.1045242817" name="MCU ARM GCC" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.toolchain.exe.debug">
|
||||
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_mcu.975930624" name="MCU" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_mcu" useByScannerDiscovery="true" value="STM32F103C8Tx" valueType="string"/>
|
||||
|
@ -23,6 +23,7 @@
|
|||
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_board.264529151" name="Board" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_board" useByScannerDiscovery="false" value="genericBoard" valueType="string"/>
|
||||
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.defaults.477742252" name="Defaults" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.defaults" useByScannerDiscovery="false" value="com.st.stm32cube.ide.common.services.build.inputs.revA.1.0.6 || Debug || true || Executable || com.st.stm32cube.ide.mcu.gnu.managedbuild.option.toolchain.value.workspace || STM32F103C8Tx || 0 || 0 || arm-none-eabi- || ${gnu_tools_for_stm32_compiler_path} || ../Core/Inc | ../Drivers/STM32F1xx_HAL_Driver/Inc | ../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy | ../Drivers/CMSIS/Device/ST/STM32F1xx/Include | ../Drivers/CMSIS/Include | ../USB_DEVICE/App | ../USB_DEVICE/Target | ../Middlewares/ST/STM32_USB_Device_Library/Core/Inc | ../Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc || || || USE_HAL_DRIVER | STM32F103xB || || Drivers | Core/Startup | Middlewares | Core | USB_DEVICE || || || ${workspace_loc:/${ProjName}/STM32F103C8TX_FLASH.ld} || true || NonSecure || || secure_nsclib.o || || None || || || " valueType="string"/>
|
||||
<option id="com.st.stm32cube.ide.mcu.debug.option.cpuclock.1223138390" name="Cpu clock frequence" superClass="com.st.stm32cube.ide.mcu.debug.option.cpuclock" useByScannerDiscovery="false" value="72" valueType="string"/>
|
||||
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.converthex.1551522557" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.converthex" value="true" valueType="boolean"/>
|
||||
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.targetplatform.471687747" isAbstract="false" osList="all" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.targetplatform"/>
|
||||
<builder buildPath="${workspace_loc:/vp-digi}/Debug" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.builder.44519105" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.builder"/>
|
||||
<tool id="com.st.stm32cube.ide.mcu.gnu.managedbuild.tool.assembler.741718030" name="MCU GCC Assembler" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.tool.assembler">
|
||||
|
@ -101,7 +102,7 @@
|
|||
</extensions>
|
||||
</storageModule>
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<configuration artifactExtension="elf" artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release" cleanCommand="rm -rf" description="" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.config.exe.release.215580651" name="Release" parent="com.st.stm32cube.ide.mcu.gnu.managedbuild.config.exe.release">
|
||||
<configuration artifactExtension="elf" artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release" cleanCommand="rm -rf" description="" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.config.exe.release.215580651" name="Release" parent="com.st.stm32cube.ide.mcu.gnu.managedbuild.config.exe.release" postbuildStep="">
|
||||
<folderInfo id="com.st.stm32cube.ide.mcu.gnu.managedbuild.config.exe.release.215580651." name="/" resourcePath="">
|
||||
<toolChain id="com.st.stm32cube.ide.mcu.gnu.managedbuild.toolchain.exe.release.1538168622" name="MCU ARM GCC" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.toolchain.exe.release">
|
||||
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_mcu.816715468" name="MCU" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_mcu" useByScannerDiscovery="true" value="STM32F103C8Tx" valueType="string"/>
|
||||
|
@ -110,6 +111,7 @@
|
|||
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_board.1578375475" name="Board" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_board" useByScannerDiscovery="false" value="genericBoard" valueType="string"/>
|
||||
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.defaults.484573632" name="Defaults" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.defaults" useByScannerDiscovery="false" value="com.st.stm32cube.ide.common.services.build.inputs.revA.1.0.6 || Release || false || Executable || com.st.stm32cube.ide.mcu.gnu.managedbuild.option.toolchain.value.workspace || STM32F103C8Tx || 0 || 0 || arm-none-eabi- || ${gnu_tools_for_stm32_compiler_path} || ../Core/Inc | ../Drivers/STM32F1xx_HAL_Driver/Inc | ../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy | ../Drivers/CMSIS/Device/ST/STM32F1xx/Include | ../Drivers/CMSIS/Include | ../USB_DEVICE/App | ../USB_DEVICE/Target | ../Middlewares/ST/STM32_USB_Device_Library/Core/Inc | ../Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc || || || USE_HAL_DRIVER | STM32F103xB || || Drivers | Core/Startup | Middlewares | Core | USB_DEVICE || || || ${workspace_loc:/${ProjName}/STM32F103C8TX_FLASH.ld} || true || NonSecure || || secure_nsclib.o || || None || || || " valueType="string"/>
|
||||
<option id="com.st.stm32cube.ide.mcu.debug.option.cpuclock.190595004" name="Cpu clock frequence" superClass="com.st.stm32cube.ide.mcu.debug.option.cpuclock" useByScannerDiscovery="false" value="72" valueType="string"/>
|
||||
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.converthex.1082212326" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.converthex" value="true" valueType="boolean"/>
|
||||
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.targetplatform.1389447086" isAbstract="false" osList="all" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.targetplatform"/>
|
||||
<builder buildPath="${workspace_loc:/vp-digi}/Release" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.builder.1065880262" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.builder"/>
|
||||
<tool id="com.st.stm32cube.ide.mcu.gnu.managedbuild.tool.assembler.1248680947" name="MCU GCC Assembler" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.tool.assembler">
|
||||
|
|
|
@ -25,7 +25,6 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
#define AX25_NOT_FX25 255
|
||||
|
||||
|
||||
//for AX.25 329 bytes is the theoretical max size assuming 2-byte Control, 1-byte PID, 256-byte info field and 8 digi address fields
|
||||
#define AX25_FRAME_MAX_SIZE (329) //single frame max length
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
@ -15,6 +16,7 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COMMON_H_
|
||||
#define COMMON_H_
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -39,5 +39,4 @@ void KissSend(Uart *port, uint8_t *buf, uint16_t size);
|
|||
void KissParse(Uart *port, uint8_t data);
|
||||
|
||||
void KissProcess(Uart *port);
|
||||
|
||||
#endif /* KISS_H_ */
|
||||
|
|
|
@ -16,6 +16,25 @@
|
|||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
VP-Digi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/* USER CODE END Header */
|
||||
|
||||
/* Define to prevent recursive inclusion -------------------------------------*/
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
@ -15,7 +16,6 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SYSTICK_H_
|
||||
#define SYSTICK_H_
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
@ -30,12 +31,14 @@ struct _GeneralConfig GeneralConfig =
|
|||
.kissMonitor = 0,
|
||||
};
|
||||
|
||||
|
||||
const char versionString[] = "VP-Digi v. 2.0.0\r\nThe open-source standalone APRS digipeater controller and KISS TNC\r\n"
|
||||
#ifdef ENABLE_FX25
|
||||
"With FX.25 support compiled-in\r\n"
|
||||
#endif
|
||||
;
|
||||
|
||||
|
||||
static uint64_t pow10i(uint16_t exp)
|
||||
{
|
||||
if(exp == 0)
|
||||
|
@ -158,6 +161,8 @@ static void sendTNC2ToUart(Uart *uart, uint8_t *from, uint16_t len)
|
|||
}
|
||||
else
|
||||
UartSendString(uart, "<not UI packet>", 0);
|
||||
|
||||
UartSendByte(uart, 0); //terminate with NULL
|
||||
}
|
||||
|
||||
void SendTNC2(uint8_t *from, uint16_t len)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
@ -13,7 +14,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with VP-DigiConfig. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "modem.h"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -49,6 +49,7 @@ void KissSend(Uart *port, uint8_t *buf, uint16_t size)
|
|||
|
||||
void KissParse(Uart *port, uint8_t data)
|
||||
{
|
||||
|
||||
volatile uint8_t *buf = NULL;
|
||||
volatile uint16_t *index = NULL;
|
||||
if(!port->kissProcessingOngoing)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
<<<<<<< HEAD:Core/Src/terminal.c
|
||||
This file is part of VP-DigiConfig.
|
||||
=======
|
||||
|
||||
This file is part of VP-Digi.
|
||||
>>>>>>> e9a5ebd2c6fc83adbfacb614029274ed01491d16:Src/terminal.c
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -13,7 +18,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with VP-DigiConfig. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "modem.h"
|
||||
|
@ -24,6 +29,10 @@ along with VP-DigiConfig. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include "digipeater.h"
|
||||
#include "config.h"
|
||||
#include "ax25.h"
|
||||
<<<<<<< HEAD:Core/Src/terminal.c
|
||||
=======
|
||||
#include "drivers/systick.h"
|
||||
>>>>>>> e9a5ebd2c6fc83adbfacb614029274ed01491d16:Src/terminal.c
|
||||
#include "kiss.h"
|
||||
|
||||
void TermHandleSpecial(Uart *u)
|
||||
|
@ -59,6 +68,10 @@ void TermHandleSpecial(Uart *u)
|
|||
|
||||
}
|
||||
|
||||
<<<<<<< HEAD:Core/Src/terminal.c
|
||||
=======
|
||||
|
||||
>>>>>>> e9a5ebd2c6fc83adbfacb614029274ed01491d16:Src/terminal.c
|
||||
void TermSendToAll(enum UartMode mode, uint8_t *data, uint16_t size)
|
||||
{
|
||||
if(MODE_KISS == mode)
|
||||
|
@ -92,6 +105,7 @@ void TermSendNumberToAll(enum UartMode mode, int32_t n)
|
|||
|
||||
}
|
||||
|
||||
<<<<<<< HEAD:Core/Src/terminal.c
|
||||
|
||||
static const char monitorHelp[] = "\r\nCommands available in monitor mode:\r\n"
|
||||
"help - show this help page\r\n"
|
||||
|
@ -101,6 +115,16 @@ static const char monitorHelp[] = "\r\nCommands available in monitor mode:\r\n"
|
|||
"kiss - switch to KISS mode\r\n"
|
||||
"config - switch to config mode\r\n"
|
||||
"reboot - reboot the device\r\n"
|
||||
=======
|
||||
static const char monitorHelp[] = "\r\nCommans available in monitor mode:\r\n"
|
||||
"help - shows this help page\r\n"
|
||||
"cal {low|high|alt|stop} - transmits/stops transmitter calibration pattern\r\n"
|
||||
"\tlow - transmits MARK tone, high - transmits SPACE tone, alt - transmits alternating tones (null bytes)\r\n"
|
||||
"beacon <beacon_number> - immediately transmits selected beacon (number from 0 to 7)\r\n"
|
||||
"kiss - switches to KISS mode\r\n"
|
||||
"config - switches to config mode\r\n"
|
||||
"reboot - reboots the device\r\n"
|
||||
>>>>>>> e9a5ebd2c6fc83adbfacb614029274ed01491d16:Src/terminal.c
|
||||
"time - show time since boot\r\n"
|
||||
"version - show full firmware version info\r\n\r\n\r\n";
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE targetDefinitions [
|
||||
<!ELEMENT targetDefinitions (board)>
|
||||
<!ELEMENT board (name, dbgIF+, dbgDEV, mcuId)>
|
||||
<!ELEMENT name (#PCDATA)>
|
||||
<!ELEMENT dbgIF (#PCDATA)>
|
||||
<!ELEMENT dbgDEV (#PCDATA)>
|
||||
<!ELEMENT mcuId (#PCDATA)>
|
||||
<!ATTLIST board id CDATA #REQUIRED>
|
||||
]>
|
||||
|
||||
<targetDefinitions>
|
||||
<board id="f103c8t6_digi_usb">
|
||||
<name>F103C8T6_DIGI_USB</name>
|
||||
<dbgIF>SWD</dbgIF>
|
||||
<dbgDEV>ST-Link</dbgDEV>
|
||||
<mcuId>stm32f103c8tx</mcuId>
|
||||
</board>
|
||||
</targetDefinitions>
|
|
@ -27,7 +27,12 @@ Since version 2.0.0, the component values have changed to provide support for fa
|
|||
|
||||
## Description, schematic, instructions
|
||||
|
||||
<<<<<<< HEAD
|
||||
The user manual and technical description are available [here](doc/manual.md).
|
||||
=======
|
||||
## Source code usage
|
||||
The firmware was written using System Workbench for STM32 (SW4STM32) and you should be able to import this repository directly to the IDE. The source code is publicly available since version 1.2.0.
|
||||
>>>>>>> e9a5ebd2c6fc83adbfacb614029274ed01491d16
|
||||
|
||||
## Source code
|
||||
|
||||
|
|
|
@ -0,0 +1,578 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
VP-Digi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ax25.h"
|
||||
#include <stdlib.h>
|
||||
#include "drivers/modem.h"
|
||||
#include "common.h"
|
||||
#include "drivers/systick.h"
|
||||
#include <stdbool.h>
|
||||
#include "digipeater.h"
|
||||
|
||||
struct Ax25ProtoConfig Ax25Config;
|
||||
|
||||
|
||||
#define FRAME_MAX_COUNT (10) //max count of frames in buffer
|
||||
#define FRAME_BUFFER_SIZE (FRAME_MAX_COUNT * AX25_FRAME_MAX_SIZE) //circular frame buffer length
|
||||
|
||||
#define STATIC_HEADER_FLAG_COUNT 4 //number of flags sent before each frame
|
||||
#define STATIC_FOOTER_FLAG_COUNT 8 //number of flags sent after each frame
|
||||
|
||||
#define MAX_TRANSMIT_RETRY_COUNT 8 //max number of retries if channel is busy
|
||||
|
||||
struct FrameHandle
|
||||
{
|
||||
uint16_t start;
|
||||
uint16_t size;
|
||||
uint16_t signalLevel;
|
||||
};
|
||||
|
||||
static uint8_t rxBuffer[FRAME_BUFFER_SIZE]; //circular buffer for received frames
|
||||
static uint16_t rxBufferHead = 0; //circular RX buffer write index
|
||||
static struct FrameHandle rxFrame[FRAME_MAX_COUNT];
|
||||
static uint8_t rxFrameHead = 0;
|
||||
static uint8_t rxFrameTail = 0;
|
||||
static bool rxFrameBufferFull = false;
|
||||
|
||||
static uint8_t txBuffer[FRAME_BUFFER_SIZE]; //circular TX frame buffer
|
||||
static uint16_t txBufferHead = 0; //circular TX buffer write index
|
||||
static uint16_t txBufferTail = 0;
|
||||
static struct FrameHandle txFrame[FRAME_MAX_COUNT];
|
||||
static uint8_t txFrameHead = 0;
|
||||
static uint8_t txFrameTail = 0;
|
||||
static bool txFrameBufferFull = false;
|
||||
|
||||
static uint8_t frameReceived; //a bitmap of receivers that received the frame
|
||||
|
||||
|
||||
enum TxStage
|
||||
{
|
||||
TX_STAGE_IDLE,
|
||||
TX_STAGE_PREAMBLE,
|
||||
TX_STAGE_HEADER_FLAGS,
|
||||
TX_STAGE_DATA,
|
||||
TX_STAGE_CRC,
|
||||
TX_STAGE_FOOTER_FLAGS,
|
||||
TX_STAGE_TAIL,
|
||||
};
|
||||
|
||||
enum TxInitStage
|
||||
{
|
||||
TX_INIT_OFF,
|
||||
TX_INIT_WAITING,
|
||||
TX_INIT_TRANSMITTING
|
||||
};
|
||||
|
||||
static uint8_t txByte = 0; //current TX byte
|
||||
static uint16_t txByteIdx = 0; //current TX byte index
|
||||
static int8_t txBitIdx = 0; //current bit index in txByte
|
||||
static uint16_t txDelayElapsed = 0; //counter of TXDelay bytes already sent
|
||||
static uint8_t txFlagsElapsed = 0; //counter of flag bytes already sent
|
||||
static uint8_t txCrcByteIdx = 0; //currently transmitted byte of CRC
|
||||
static uint8_t txBitstuff = 0; //bit-stuffing counter
|
||||
static uint16_t txTailElapsed; //counter of TXTail bytes already sent
|
||||
static uint16_t txCrc = 0xFFFF; //current CRC
|
||||
static uint32_t txQuiet = 0; //quit time + current tick value
|
||||
static uint8_t txRetries = 0; //number of TX retries
|
||||
static enum TxInitStage txInitStage; //current TX initialization stage
|
||||
static enum TxStage txStage; //current TX stage
|
||||
|
||||
struct RxState
|
||||
{
|
||||
uint16_t crc; //current CRC
|
||||
uint8_t frame[AX25_FRAME_MAX_SIZE]; //raw frame buffer
|
||||
uint16_t frameIdx; //index for raw frame buffer
|
||||
uint8_t receivedByte; //byte being currently received
|
||||
uint8_t receivedBitIdx; //bit index for recByte
|
||||
uint8_t rawData; //raw data being currently received
|
||||
enum Ax25RxStage rx; //current RX stage
|
||||
uint8_t frameReceived; //frame received flag
|
||||
};
|
||||
|
||||
static volatile struct RxState rxState[MODEM_DEMODULATOR_COUNT];
|
||||
|
||||
static uint16_t lastCrc = 0; //CRC of the last received frame. If not 0, a frame was successfully received
|
||||
static uint16_t rxMultiplexDelay = 0; //simple delay for decoder multiplexer to avoid receiving the same frame twice
|
||||
|
||||
static uint16_t txDelay; //number of TXDelay bytes to send
|
||||
static uint16_t txTail; //number of TXTail bytes to send
|
||||
|
||||
static uint8_t outputFrameBuffer[AX25_FRAME_MAX_SIZE];
|
||||
|
||||
#define GET_FREE_SIZE(max, head, tail) (((head) < (tail)) ? ((tail) - (head)) : ((max) - (head) + (tail)))
|
||||
#define GET_USED_SIZE(max, head, tail) (max - GET_FREE_SIZE(max, head, tail))
|
||||
|
||||
/**
|
||||
* @brief Recalculate CRC for one bit
|
||||
* @param bit Input bit
|
||||
* @param *crc CRC pointer
|
||||
*/
|
||||
static void calculateCRC(uint8_t bit, uint16_t *crc)
|
||||
{
|
||||
uint16_t xor_result;
|
||||
xor_result = *crc ^ bit;
|
||||
*crc >>= 1;
|
||||
if (xor_result & 0x0001)
|
||||
{
|
||||
*crc ^= 0x8408;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Ax25GetReceivedFrameBitmap(void)
|
||||
{
|
||||
return frameReceived;
|
||||
}
|
||||
|
||||
void Ax25ClearReceivedFrameBitmap(void)
|
||||
{
|
||||
frameReceived = 0;
|
||||
}
|
||||
|
||||
|
||||
void *Ax25WriteTxFrame(uint8_t *data, uint16_t size)
|
||||
{
|
||||
if((GET_FREE_SIZE(FRAME_BUFFER_SIZE, txBufferHead, txBufferTail) < size) || txFrameBufferFull)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
txFrame[txFrameHead].size = size;
|
||||
txFrame[txFrameHead].start = txBufferHead;
|
||||
for(uint16_t i = 0; i < size; i++)
|
||||
{
|
||||
txBuffer[txBufferHead++] = data[i];
|
||||
txBufferHead %= FRAME_BUFFER_SIZE;
|
||||
}
|
||||
void *ret = &txFrame[txFrameHead];
|
||||
__disable_irq();
|
||||
txFrameHead++;
|
||||
txFrameHead %= FRAME_MAX_COUNT;
|
||||
if(txFrameHead == txFrameTail)
|
||||
txFrameBufferFull = true;
|
||||
__enable_irq();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool Ax25ReadNextRxFrame(uint8_t **dst, uint16_t *size, uint16_t *signalLevel)
|
||||
{
|
||||
if((rxFrameHead == rxFrameTail) && !rxFrameBufferFull)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*dst = outputFrameBuffer;
|
||||
|
||||
for(uint16_t i = 0; i < rxFrame[rxFrameTail].size; i++)
|
||||
{
|
||||
(*dst)[i] = rxBuffer[(rxFrame[rxFrameTail].start + i) % FRAME_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
*signalLevel = rxFrame[rxFrameTail].signalLevel;
|
||||
*size = rxFrame[rxFrameTail].size;
|
||||
|
||||
__disable_irq();
|
||||
rxFrameBufferFull = false;
|
||||
rxFrameTail++;
|
||||
rxFrameTail %= FRAME_MAX_COUNT;
|
||||
__enable_irq();
|
||||
return true;
|
||||
}
|
||||
|
||||
enum Ax25RxStage Ax25GetRxStage(uint8_t modem)
|
||||
{
|
||||
return rxState[modem].rx;
|
||||
}
|
||||
|
||||
|
||||
void Ax25BitParse(uint8_t bit, uint8_t modem)
|
||||
{
|
||||
if(lastCrc != 0) //there was a frame received
|
||||
{
|
||||
rxMultiplexDelay++;
|
||||
if(rxMultiplexDelay > (4 * MODEM_DEMODULATOR_COUNT)) //hold it for a while and wait for other decoders to receive the frame
|
||||
{
|
||||
lastCrc = 0;
|
||||
rxMultiplexDelay = 0;
|
||||
for(uint8_t i = 0; i < MODEM_DEMODULATOR_COUNT; i++)
|
||||
{
|
||||
frameReceived |= ((rxState[i].frameReceived > 0) << i);
|
||||
rxState[i].frameReceived = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct RxState *rx = (struct RxState*)&(rxState[modem]);
|
||||
|
||||
rx->rawData <<= 1; //store incoming bit
|
||||
rx->rawData |= (bit > 0);
|
||||
|
||||
|
||||
if(rx->rawData == 0x7E) //HDLC flag received
|
||||
{
|
||||
if(rx->rx == RX_STAGE_FRAME) //if we are in frame, this is the end of the frame
|
||||
{
|
||||
if((rx->frameIdx > 15)) //correct frame must be at least 16 bytes long
|
||||
{
|
||||
uint16_t i = 0;
|
||||
for(; i < rx->frameIdx - 2; i++) //look for path end bit
|
||||
{
|
||||
if(rx->frame[i] & 1)
|
||||
break;
|
||||
}
|
||||
|
||||
//if non-APRS frames are not allowed, check if this frame has control=0x03 and PID=0xF0
|
||||
|
||||
if(Ax25Config.allowNonAprs || (((rx->frame[i + 1] == 0x03) && (rx->frame[i + 2] == 0xf0))))
|
||||
{
|
||||
if((rx->frame[rx->frameIdx - 2] == ((rx->crc & 0xFF) ^ 0xFF)) && (rx->frame[rx->frameIdx - 1] == (((rx->crc >> 8) & 0xFF) ^ 0xFF))) //check CRC
|
||||
{
|
||||
rx->frameReceived = 1;
|
||||
rx->frameIdx -= 2; //remove CRC
|
||||
if(rx->crc != lastCrc) //the other decoder has not received this frame yet, so store it in main frame buffer
|
||||
{
|
||||
lastCrc = rx->crc; //store CRC of this frame
|
||||
|
||||
if(!rxFrameBufferFull) //if enough space, store the frame
|
||||
{
|
||||
rxFrame[rxFrameHead].start = rxBufferHead;
|
||||
rxFrame[rxFrameHead].signalLevel = ModemGetRMS(modem);
|
||||
__disable_irq();
|
||||
rxFrame[rxFrameHead++].size = rx->frameIdx;
|
||||
rxFrameHead %= FRAME_MAX_COUNT;
|
||||
if(rxFrameHead == rxFrameTail)
|
||||
rxFrameBufferFull = true;
|
||||
__enable_irq();
|
||||
|
||||
for(uint16_t i = 0; i < rx->frameIdx; i++)
|
||||
{
|
||||
rxBuffer[rxBufferHead++] = rx->frame[i];
|
||||
rxBufferHead %= FRAME_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
rx->rx = RX_STAGE_FLAG;
|
||||
ModemClearRMS(modem);
|
||||
rx->receivedByte = 0;
|
||||
rx->receivedBitIdx = 0;
|
||||
rx->frameIdx = 0;
|
||||
rx->crc = 0xFFFF;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if((rx->rawData & 0x7F) == 0x7F) //received 7 consecutive ones, this is an error
|
||||
{
|
||||
rx->rx = RX_STAGE_FLAG;
|
||||
ModemClearRMS(modem);
|
||||
rx->receivedByte = 0;
|
||||
rx->receivedBitIdx = 0;
|
||||
rx->frameIdx = 0;
|
||||
rx->crc = 0xFFFF;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(rx->rx == RX_STAGE_IDLE) //not in a frame, don't go further
|
||||
return;
|
||||
|
||||
|
||||
if((rx->rawData & 0x3F) == 0x3E) //dismiss bit 0 added by bit stuffing
|
||||
return;
|
||||
|
||||
if(rx->rawData & 0x01) //received bit 1
|
||||
rx->receivedByte |= 0x80; //store it
|
||||
|
||||
if(++rx->receivedBitIdx >= 8) //received full byte
|
||||
{
|
||||
if(rx->frameIdx > AX25_FRAME_MAX_SIZE) //frame is too long
|
||||
{
|
||||
rx->rx = RX_STAGE_IDLE;
|
||||
ModemClearRMS(modem);
|
||||
rx->receivedByte = 0;
|
||||
rx->receivedBitIdx = 0;
|
||||
rx->frameIdx = 0;
|
||||
rx->crc = 0xFFFF;
|
||||
return;
|
||||
}
|
||||
if(rx->frameIdx >= 2) //more than 2 bytes received, calculate CRC
|
||||
{
|
||||
for(uint8_t i = 0; i < 8; i++)
|
||||
{
|
||||
calculateCRC((rx->frame[rx->frameIdx - 2] >> i) & 1, &(rx->crc));
|
||||
}
|
||||
}
|
||||
rx->rx = RX_STAGE_FRAME;
|
||||
rx->frame[rx->frameIdx++] = rx->receivedByte; //store received byte
|
||||
rx->receivedByte = 0;
|
||||
rx->receivedBitIdx = 0;
|
||||
}
|
||||
else
|
||||
rx->receivedByte >>= 1;
|
||||
}
|
||||
|
||||
|
||||
uint8_t Ax25GetTxBit(void)
|
||||
{
|
||||
if(txBitIdx == 8)
|
||||
{
|
||||
txBitIdx = 0;
|
||||
if(txStage == TX_STAGE_PREAMBLE) //transmitting preamble (TXDelay)
|
||||
{
|
||||
if(txDelayElapsed < txDelay) //still transmitting
|
||||
{
|
||||
txByte = 0x7E;
|
||||
txDelayElapsed++;
|
||||
}
|
||||
else //now transmit initial flags
|
||||
{
|
||||
txDelayElapsed = 0;
|
||||
txStage = TX_STAGE_HEADER_FLAGS;
|
||||
}
|
||||
|
||||
}
|
||||
if(txStage == TX_STAGE_HEADER_FLAGS) //transmitting initial flags
|
||||
{
|
||||
if(txFlagsElapsed < STATIC_HEADER_FLAG_COUNT)
|
||||
{
|
||||
txByte = 0x7E;
|
||||
txFlagsElapsed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
txFlagsElapsed = 0;
|
||||
txStage = TX_STAGE_DATA; //transmit data
|
||||
}
|
||||
}
|
||||
if(txStage == TX_STAGE_DATA) //transmitting normal data
|
||||
{
|
||||
transmitNormalData:
|
||||
__disable_irq();
|
||||
if((txFrameHead != txFrameTail) || txFrameBufferFull)
|
||||
{
|
||||
__enable_irq();
|
||||
if(txByteIdx < txFrame[txFrameTail].size) //send buffer
|
||||
{
|
||||
txByte = txBuffer[(txFrame[txFrameTail].start + txByteIdx) % FRAME_BUFFER_SIZE];
|
||||
txByteIdx++;
|
||||
}
|
||||
else //end of buffer, send CRC
|
||||
{
|
||||
txStage = TX_STAGE_CRC; //transmit CRC
|
||||
txCrcByteIdx = 0;
|
||||
}
|
||||
}
|
||||
else //no more frames
|
||||
{
|
||||
__enable_irq();
|
||||
txByteIdx = 0;
|
||||
txBitIdx = 0;
|
||||
txStage = TX_STAGE_TAIL;
|
||||
}
|
||||
}
|
||||
if(txStage == TX_STAGE_CRC) //transmitting CRC
|
||||
{
|
||||
if(txCrcByteIdx <= 1)
|
||||
{
|
||||
txByte = (txCrc & 0xFF) ^ 0xFF;
|
||||
txCrc >>= 8;
|
||||
txCrcByteIdx++;
|
||||
}
|
||||
else
|
||||
{
|
||||
txCrc = 0xFFFF;
|
||||
txStage = TX_STAGE_FOOTER_FLAGS; //now transmit flags
|
||||
txFlagsElapsed = 0;
|
||||
}
|
||||
|
||||
}
|
||||
if(txStage == TX_STAGE_FOOTER_FLAGS)
|
||||
{
|
||||
if(txFlagsElapsed < STATIC_FOOTER_FLAG_COUNT)
|
||||
{
|
||||
txByte = 0x7E;
|
||||
txFlagsElapsed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
txFlagsElapsed = 0;
|
||||
txByteIdx = 0;
|
||||
txStage = TX_STAGE_DATA; //return to normal data transmission stage. There might be a next frame to transmit
|
||||
__disable_irq();
|
||||
txFrameBufferFull = false;
|
||||
txFrameTail++;
|
||||
txFrameTail %= FRAME_MAX_COUNT;
|
||||
__enable_irq();
|
||||
goto transmitNormalData;
|
||||
}
|
||||
}
|
||||
if(txStage == TX_STAGE_TAIL) //transmitting tail
|
||||
{
|
||||
if(txTailElapsed < txTail)
|
||||
{
|
||||
txByte = 0x7E;
|
||||
txTailElapsed++;
|
||||
}
|
||||
else //tail transmitted, stop transmission
|
||||
{
|
||||
txTailElapsed = 0;
|
||||
txStage = TX_STAGE_IDLE;
|
||||
txCrc = 0xFFFF;
|
||||
txBitstuff = 0;
|
||||
txByte = 0;
|
||||
txInitStage = TX_INIT_OFF;
|
||||
txBufferTail = txBufferHead;
|
||||
ModemTransmitStop();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uint8_t txBit = 0;
|
||||
|
||||
if((txStage == TX_STAGE_DATA) || (txStage == TX_STAGE_CRC)) //transmitting normal data or CRC
|
||||
{
|
||||
if(txBitstuff == 5) //5 consecutive ones transmitted
|
||||
{
|
||||
txBit = 0; //transmit bit-stuffed 0
|
||||
txBitstuff = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(txByte & 1) //1 being transmitted
|
||||
{
|
||||
txBitstuff++; //increment bit stuffing counter
|
||||
txBit = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
txBit = 0;
|
||||
txBitstuff = 0; //0 being transmitted, reset bit stuffing counter
|
||||
}
|
||||
if(txStage == TX_STAGE_DATA) //calculate CRC only for normal data
|
||||
calculateCRC(txByte & 1, &txCrc);
|
||||
|
||||
txByte >>= 1;
|
||||
txBitIdx++;
|
||||
}
|
||||
}
|
||||
else //transmitting preamble or flags, don't calculate CRC, don't use bit stuffing
|
||||
{
|
||||
txBit = txByte & 1;
|
||||
txByte >>= 1;
|
||||
txBitIdx++;
|
||||
}
|
||||
return txBit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize transmission and start when possible
|
||||
*/
|
||||
void Ax25TransmitBuffer(void)
|
||||
{
|
||||
if(txInitStage == TX_INIT_WAITING)
|
||||
return;
|
||||
if(txInitStage == TX_INIT_TRANSMITTING)
|
||||
return;
|
||||
|
||||
if((txFrameHead != txFrameTail) || txFrameBufferFull)
|
||||
{
|
||||
txQuiet = (SysTickGet() + (Ax25Config.quietTime / SYSTICK_INTERVAL) + Random(0, 200 / SYSTICK_INTERVAL)); //calculate required delay
|
||||
txInitStage = TX_INIT_WAITING;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Start transmission immediately
|
||||
* @warning Transmission should be initialized using Ax25_transmitBuffer
|
||||
*/
|
||||
static void transmitStart(void)
|
||||
{
|
||||
txCrc = 0xFFFF; //initial CRC value
|
||||
txStage = TX_STAGE_PREAMBLE;
|
||||
txByte = 0;
|
||||
txBitIdx = 0;
|
||||
txFlagsElapsed = 0;
|
||||
ModemTransmitStart();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Start transmitting when possible
|
||||
* @attention Must be continuously polled in main loop
|
||||
*/
|
||||
void Ax25TransmitCheck(void)
|
||||
{
|
||||
if(txInitStage == TX_INIT_OFF) //TX not initialized at all, nothing to transmit
|
||||
return;
|
||||
if(txInitStage == TX_INIT_TRANSMITTING) //already transmitting
|
||||
return;
|
||||
|
||||
if(ModemIsTxTestOngoing()) //TX test is enabled, wait for now
|
||||
return;
|
||||
|
||||
if(txQuiet < SysTickGet()) //quit time has elapsed
|
||||
{
|
||||
if(!ModemDcdState()) //channel is free
|
||||
{
|
||||
txInitStage = TX_INIT_TRANSMITTING; //transmit right now
|
||||
txRetries = 0;
|
||||
transmitStart();
|
||||
}
|
||||
else //channel is busy
|
||||
{
|
||||
if(txRetries == MAX_TRANSMIT_RETRY_COUNT) //timeout
|
||||
{
|
||||
txInitStage = TX_INIT_TRANSMITTING; //transmit right now
|
||||
txRetries = 0;
|
||||
transmitStart();
|
||||
}
|
||||
else //still trying
|
||||
{
|
||||
txQuiet = SysTickGet() + Random(100 / SYSTICK_INTERVAL, 500 / SYSTICK_INTERVAL); //try again after some random time
|
||||
txRetries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ax25Init(void)
|
||||
{
|
||||
txCrc = 0xFFFF;
|
||||
|
||||
memset((void*)rxState, 0, sizeof(rxState));
|
||||
for(uint8_t i = 0; i < (sizeof(rxState) / sizeof(rxState[0])); i++)
|
||||
rxState[i].crc = 0xFFFF;
|
||||
|
||||
txDelay = ((float)Ax25Config.txDelayLength / (8.f * 1000.f / (float)MODEM_BAUDRATE)); //change milliseconds to byte count
|
||||
txTail = ((float)Ax25Config.txTailLength / (8.f * 1000.f / (float)MODEM_BAUDRATE));
|
||||
}
|
|
@ -0,0 +1,711 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
VP-Digi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "drivers/modem.h"
|
||||
#include "drivers/systick.h"
|
||||
#include "ax25.h"
|
||||
#include "stm32f1xx.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include "common.h"
|
||||
|
||||
|
||||
/*
|
||||
* Configuration for PLL-based data carrier detection
|
||||
* DCD_MAXPULSE is the maximum value of the DCD pulse counter
|
||||
* DCD_THRES is the threshold value of the DCD pulse counter. When reached the input signal is assumed to be valid
|
||||
* DCD_MAXPULSE and DCD_THRES difference sets the DCD "inertia" so that the DCD state won't change rapidly when a valid signal is present
|
||||
* DCD_DEC is the DCD pulse counter decrementation value when symbol changes too far from PLL counter zero
|
||||
* DCD_INC is the DCD pulse counter incrementation value when symbol changes near the PLL counter zero
|
||||
* DCD_PLLTUNE is the DCD timing coefficient when symbol changes, pll_counter = pll_counter * DCD_PLLTUNE
|
||||
* The DCD mechanism is described in afsk_demod().
|
||||
* All values were selected by trial and error
|
||||
*/
|
||||
#define DCD_MAXPULSE 100
|
||||
#define DCD_THRES 30
|
||||
#define DCD_DEC 1
|
||||
#define DCD_INC 7
|
||||
#define DCD_PLLTUNE 0
|
||||
|
||||
#define N 8 //samples per symbol
|
||||
#define DAC_SINE_SIZE 32 //DAC sine table size
|
||||
#define PLLINC 536870912 //PLL tick increment value
|
||||
#define PLLLOCKED 0.74 //PLL adjustment value when locked
|
||||
#define PLLNOTLOCKED 0.50 //PLL adjustment value when not locked
|
||||
|
||||
#define PTT_ON GPIOB->BSRR = GPIO_BSRR_BS7
|
||||
#define PTT_OFF GPIOB->BSRR = GPIO_BSRR_BR7
|
||||
#define DCD_ON (GPIOC->BSRR = GPIO_BSRR_BR13)
|
||||
#define DCD_OFF (GPIOC->BSRR = GPIO_BSRR_BS13)
|
||||
|
||||
struct ModemDemodConfig ModemConfig;
|
||||
|
||||
static enum ModemTxTestMode txTestState; //current TX test mode
|
||||
static uint16_t dacSine[DAC_SINE_SIZE]; //sine samples for DAC
|
||||
static uint8_t dacSineIdx; //current sine sample index
|
||||
static uint16_t samples[4]; //very raw received samples, filled directly by DMA
|
||||
static uint8_t currentSymbol; //current symbol for NRZI encoding
|
||||
static uint8_t markFreq; //mark frequency (inter-sample interval)
|
||||
static uint8_t spaceFreq; //space frequency (inter-sample interval)
|
||||
static uint16_t baudRate; //baudrate
|
||||
static int32_t coeffHiI[N], coeffLoI[N], coeffHiQ[N], coeffLoQ[N]; //correlator IQ coefficients
|
||||
static uint8_t dcd = 0; //multiplexed DCD state from both demodulators
|
||||
|
||||
|
||||
/**
|
||||
* @brief BPF filter with 2200 Hz tone 6 dB preemphasis (it actually attenuates 1200 Hz tone by 6 dB)
|
||||
*/
|
||||
static const int16_t bpfCoeffs[8] =
|
||||
{
|
||||
728,
|
||||
-13418,
|
||||
-554,
|
||||
19493,
|
||||
-554,
|
||||
-13418,
|
||||
728,
|
||||
2104
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief BPF filter with 2200 Hz tone 6 dB deemphasis
|
||||
*/
|
||||
static const int16_t invBpfCoeffs[8] =
|
||||
{
|
||||
-10513,
|
||||
-10854,
|
||||
9589,
|
||||
23884,
|
||||
9589,
|
||||
-10854,
|
||||
-10513,
|
||||
-879
|
||||
};
|
||||
|
||||
#define BPF_TAPS (sizeof(bpfCoeffs) / sizeof(*bpfCoeffs) > sizeof(invBpfCoeffs) / sizeof(*invBpfCoeffs) ? \
|
||||
sizeof(bpfCoeffs) / sizeof(*bpfCoeffs) : sizeof(invBpfCoeffs) / sizeof(*invBpfCoeffs))
|
||||
|
||||
/**
|
||||
* @brief Output LPF filter to remove data faster than 1200 baud
|
||||
* It actually is a 600 Hz filter: symbols can change at 1200 Hz, but it takes 2 "ticks" to return to the same symbol - that's why it's 600 Hz
|
||||
*/
|
||||
static const int16_t lpfCoeffs[15] =
|
||||
{
|
||||
-6128,
|
||||
-5974,
|
||||
-2503,
|
||||
4125,
|
||||
12679,
|
||||
21152,
|
||||
27364,
|
||||
29643,
|
||||
27364,
|
||||
21152,
|
||||
12679,
|
||||
4125,
|
||||
-2503,
|
||||
-5974,
|
||||
-6128
|
||||
};
|
||||
|
||||
#define LPF_TAPS (sizeof(lpfCoeffs) / sizeof(*lpfCoeffs))
|
||||
|
||||
|
||||
struct DemodState
|
||||
{
|
||||
enum ModemEmphasis emphasis; //preemphasis/deemphasis
|
||||
uint8_t rawSymbols; //raw, unsynchronized symbols
|
||||
uint8_t syncSymbols; //synchronized symbols
|
||||
int16_t rawSample[BPF_TAPS]; //input (raw) samples
|
||||
int32_t rxSample[BPF_TAPS]; //rx samples after pre/deemphasis filter
|
||||
uint8_t rxSampleIdx; //index for the array above
|
||||
int64_t lpfSample[LPF_TAPS]; //rx samples after final filtering
|
||||
uint8_t dcd : 1; //DCD state
|
||||
uint64_t RMSenergy; //frame energy counter (sum of samples squared)
|
||||
uint32_t RMSsampleCount; //number of samples for RMS
|
||||
int32_t pll; //bit recovery PLL counter
|
||||
int32_t lastPll; //last bit recovery PLL counter value
|
||||
int32_t dcdPll; //DCD PLL main counter
|
||||
uint8_t dcdLastSymbol; //last symbol for DCD
|
||||
uint8_t dcdCounter; //DCD "pulse" counter (incremented when RX signal is correct)
|
||||
};
|
||||
|
||||
static volatile struct DemodState demodState[MODEM_DEMODULATOR_COUNT];
|
||||
|
||||
static void decode(uint8_t symbol, uint8_t demod);
|
||||
static int32_t demodulate(int16_t sample, struct DemodState *dem);
|
||||
static void setPtt(uint8_t state);
|
||||
|
||||
uint8_t ModemDcdState(void)
|
||||
{
|
||||
return dcd;
|
||||
}
|
||||
|
||||
uint8_t ModemIsTxTestOngoing(void)
|
||||
{
|
||||
if(txTestState != TEST_DISABLED)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ModemClearRMS(uint8_t modem)
|
||||
{
|
||||
|
||||
demodState[modem].RMSenergy = 0;
|
||||
demodState[modem].RMSsampleCount = 0;
|
||||
|
||||
}
|
||||
|
||||
uint16_t ModemGetRMS(uint8_t modem)
|
||||
{
|
||||
return sqrtf((float)demodState[modem].RMSenergy / (float)demodState[modem].RMSsampleCount);
|
||||
}
|
||||
|
||||
enum ModemEmphasis ModemGetFilterType(uint8_t modem)
|
||||
{
|
||||
return demodState[modem].emphasis;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set DCD LED
|
||||
* @param[in] state 0 - OFF, 1 - ON
|
||||
*/
|
||||
static void setDcd(uint8_t state)
|
||||
{
|
||||
if(state)
|
||||
{
|
||||
GPIOC->BSRR = GPIO_BSRR_BR13;
|
||||
GPIOB->BSRR = GPIO_BSRR_BS5;
|
||||
}
|
||||
else
|
||||
{
|
||||
GPIOC->BSRR = GPIO_BSRR_BS13;
|
||||
GPIOB->BSRR = GPIO_BSRR_BR5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief ISR for demodulator
|
||||
* Called at 9600 Hz by DMA
|
||||
*/
|
||||
void DMA1_Channel2_IRQHandler(void) __attribute__ ((interrupt));
|
||||
void DMA1_Channel2_IRQHandler(void)
|
||||
{
|
||||
if(DMA1->ISR & DMA_ISR_TCIF2)
|
||||
{
|
||||
DMA1->IFCR |= DMA_IFCR_CTCIF2;
|
||||
|
||||
int32_t sample = ((samples[0] + samples[1] + samples[2] + samples[3]) >> 1) - 4095; //calculate input sample (decimation)
|
||||
|
||||
uint8_t partialDcd = 0;
|
||||
|
||||
for(uint8_t i = 0; i < MODEM_DEMODULATOR_COUNT; i++)
|
||||
{
|
||||
uint8_t symbol = (demodulate(sample, (struct DemodState*)&demodState[i]) > 0); //demodulate sample
|
||||
decode(symbol, i); //recover bits, decode NRZI and call higher level function
|
||||
if(demodState[i].dcd)
|
||||
partialDcd |= 1;
|
||||
}
|
||||
|
||||
if(partialDcd) //DCD on any of the demodulators
|
||||
{
|
||||
dcd = 1;
|
||||
setDcd(1);
|
||||
}
|
||||
else //no DCD on both demodulators
|
||||
{
|
||||
dcd = 0;
|
||||
setDcd(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief ISR for pushing DAC samples
|
||||
*/
|
||||
void TIM1_UP_IRQHandler(void) __attribute__ ((interrupt));
|
||||
void TIM1_UP_IRQHandler(void)
|
||||
{
|
||||
TIM1->SR &= ~TIM_SR_UIF;
|
||||
|
||||
if(ModemConfig.usePWM)
|
||||
{
|
||||
TIM4->CCR1 = dacSine[dacSineIdx];
|
||||
}
|
||||
else
|
||||
{
|
||||
GPIOB->ODR &= ~0xF000; //zero 4 oldest bits
|
||||
GPIOB->ODR |= (dacSine[dacSineIdx] << 12); //write sample to 4 oldest bits
|
||||
}
|
||||
|
||||
dacSineIdx++;
|
||||
dacSineIdx &= (DAC_SINE_SIZE - 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief ISR for baudrate generator timer. NRZI encoding is done here.
|
||||
*/
|
||||
void TIM3_IRQHandler(void) __attribute__ ((interrupt));
|
||||
void TIM3_IRQHandler(void)
|
||||
{
|
||||
TIM3->SR &= ~TIM_SR_UIF;
|
||||
|
||||
if(txTestState == TEST_DISABLED) //transmitting normal data
|
||||
{
|
||||
if(Ax25GetTxBit() == 0) //get next bit and check if it's 0
|
||||
{
|
||||
currentSymbol ^= 1; //change symbol - NRZI encoding
|
||||
}
|
||||
//if 1, no symbol change
|
||||
}
|
||||
else //transmit test mode
|
||||
{
|
||||
currentSymbol ^= 1; //change symbol
|
||||
}
|
||||
|
||||
TIM1->CNT = 0;
|
||||
|
||||
if(currentSymbol) //current symbol is space
|
||||
TIM1->ARR = spaceFreq;
|
||||
else //mark
|
||||
TIM1->ARR = markFreq;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Demodulate received sample (4x oversampling)
|
||||
* @param[in] sample Received sample
|
||||
* @param[in] *dem Demodulator state
|
||||
* @return Current tone (0 or 1)
|
||||
*/
|
||||
static int32_t demodulate(int16_t sample, struct DemodState *dem)
|
||||
{
|
||||
|
||||
dem->RMSenergy += ((sample >> 1) * (sample >> 1)); //square the sample and add it to the sum
|
||||
dem->RMSsampleCount++; //increment number of samples
|
||||
|
||||
if(dem->emphasis != EMPHASIS_NONE) //preemphasis/deemphasis is used
|
||||
{
|
||||
int32_t out = 0; //filtered output
|
||||
|
||||
for(uint8_t i = BPF_TAPS - 1; i > 0; i--)
|
||||
dem->rawSample[i] = dem->rawSample[i - 1]; //shift old samples
|
||||
|
||||
dem->rawSample[0] = sample; //store new sample
|
||||
for(uint8_t i = 0; i < BPF_TAPS; i++)
|
||||
{
|
||||
if(dem->emphasis == PREEMPHASIS)
|
||||
out += bpfCoeffs[i] * dem->rawSample[i]; //use preemphasis
|
||||
else
|
||||
out += invBpfCoeffs[i] * dem->rawSample[i]; //use deemphasis
|
||||
}
|
||||
dem->rxSample[dem->rxSampleIdx] = (out >> 15); //store filtered sample
|
||||
}
|
||||
else //no pre/deemphasis
|
||||
{
|
||||
dem->rxSample[dem->rxSampleIdx] = sample; //store incoming sample
|
||||
}
|
||||
|
||||
dem->rxSampleIdx = (dem->rxSampleIdx + 1) % BPF_TAPS; //increment sample pointer and wrap around if needed
|
||||
|
||||
int64_t outLoI = 0, outLoQ = 0, outHiI = 0, outHiQ = 0; //output values after correlating
|
||||
|
||||
for(uint8_t i = 0; i < N; i++) {
|
||||
int32_t t = dem->rxSample[(dem->rxSampleIdx + i) % BPF_TAPS]; //read sample
|
||||
outLoI += t * coeffLoI[i]; //correlate sample
|
||||
outLoQ += t * coeffLoQ[i];
|
||||
outHiI += t * coeffHiI[i];
|
||||
outHiQ += t * coeffHiQ[i];
|
||||
}
|
||||
|
||||
uint64_t hi = 0, lo = 0;
|
||||
|
||||
hi = ((outHiI >> 12) * (outHiI >> 12)) + ((outHiQ >> 12) * (outHiQ >> 12)); //calculate output tone levels
|
||||
lo = ((outLoI >> 12) * (outLoI >> 12)) + ((outLoQ >> 12) * (outLoQ >> 12));
|
||||
|
||||
//DCD using PLL
|
||||
//PLL is running nominally at 1200 Hz (= baudrate)
|
||||
//PLL timer is counting up and eventually overflows to a minimal negative value
|
||||
//so it crosses zero in the middle
|
||||
//tone change should happen somewhere near this zero-crossing (in ideal case of exactly same TX and RX baudrates)
|
||||
//nothing is ideal, so we need to have some region around zero where tone change is expected
|
||||
//if tone changed inside this region, then we add something to the DCD pulse counter (and adjust counter phase for the counter to be closer to 0)
|
||||
//if tone changes outside this region, then we subtract something from the DCD pulse counter
|
||||
//if some DCD pulse threshold is reached, then we claim that the incoming signal is correct and set DCD flag
|
||||
//when configured properly, it's generally immune to noise, as the detected tone changes much faster than 1200 baud
|
||||
//it's also important to set some maximum value for DCD counter, otherwise the DCD is "sticky"
|
||||
|
||||
dem->dcdPll = (signed)((unsigned)(dem->dcdPll) + ((unsigned)PLLINC)); //keep PLL ticking at the frequency equal to baudrate
|
||||
|
||||
uint8_t dcdSymbol = (hi > lo); //get current symbol
|
||||
|
||||
if(dcdSymbol != dem->dcdLastSymbol) //tone changed
|
||||
{
|
||||
if(abs(dem->dcdPll) < PLLINC) //tone change occurred near zero
|
||||
dem->dcdCounter += DCD_INC; //increase DCD counter
|
||||
else //tone change occurred far from zero
|
||||
{
|
||||
if(dem->dcdCounter >= DCD_DEC) //avoid overflow
|
||||
dem->dcdCounter -= DCD_DEC; //decrease DCD counter
|
||||
}
|
||||
dem->dcdPll = (int)(dem->dcdPll * DCD_PLLTUNE); //adjust PLL
|
||||
}
|
||||
|
||||
dem->dcdLastSymbol = dcdSymbol; //store last symbol for symbol change detection
|
||||
|
||||
if(dem->dcdCounter > DCD_MAXPULSE) //maximum DCD counter value reached
|
||||
dem->dcdCounter = DCD_MAXPULSE; //avoid "sticky" DCD and counter overflow
|
||||
|
||||
if(dem->dcdCounter > DCD_THRES) //DCD threshold reached
|
||||
dem->dcd = 1; //DCD!
|
||||
else //below DCD threshold
|
||||
dem->dcd = 0; //no DCD
|
||||
|
||||
//filter out signal faster than 1200 baud
|
||||
int64_t out = 0;
|
||||
|
||||
for(uint8_t i = LPF_TAPS - 1; i > 0; i--)
|
||||
dem->lpfSample[i] = dem->lpfSample[i - 1];
|
||||
|
||||
dem->lpfSample[0] = (int64_t)hi - (int64_t)lo;
|
||||
for(uint8_t i = 0; i < LPF_TAPS; i++)
|
||||
{
|
||||
out += lpfCoeffs[i] * dem->lpfSample[i];
|
||||
}
|
||||
|
||||
return out > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Decode received symbol: bit recovery, NRZI decoding and pass the decoded bit to higher level protocol
|
||||
* @param[in] symbol Received symbol
|
||||
* @param demod Demodulator index
|
||||
*/
|
||||
static void decode(uint8_t symbol, uint8_t demod)
|
||||
{
|
||||
struct DemodState *dem = (struct DemodState*)&demodState[demod];
|
||||
|
||||
//This function provides bit/clock recovery and NRZI decoding
|
||||
//Bit recovery is based on PLL which is described in the function above (DCD PLL)
|
||||
//Current symbol is sampled at PLL counter overflow, so symbol transition should occur at PLL counter zero
|
||||
dem->lastPll = dem->pll; //store last clock state
|
||||
|
||||
dem->pll = (signed)((unsigned)(dem->pll) + (unsigned)PLLINC); //keep PLL running
|
||||
|
||||
dem->rawSymbols <<= 1; //store received unsynchronized symbol
|
||||
dem->rawSymbols |= (symbol & 1);
|
||||
|
||||
|
||||
if ((dem->pll < 0) && (dem->lastPll > 0)) //PLL counter overflow, sample symbol, decode NRZI and process in higher layer
|
||||
{
|
||||
dem->syncSymbols <<= 1; //shift recovered (received, synchronized) bit register
|
||||
|
||||
uint8_t t = dem->rawSymbols & 0x07; //take last three symbols for sampling. Seems that 1 symbol is not enough, but 3 symbols work well
|
||||
if(t == 0b111 || t == 0b110 || t == 0b101 || t == 0b011) //if there are 2 or 3 ones, then the received symbol is 1
|
||||
{
|
||||
dem->syncSymbols |= 1; //push to recovered symbols register
|
||||
}
|
||||
//if there 2 or 3 zeros, no need to add anything to the register
|
||||
|
||||
//NRZI decoding
|
||||
if (((dem->syncSymbols & 0x03) == 0b11) || ((dem->syncSymbols & 0x03) == 0b00)) //two last symbols are the same - no symbol transition - decoded bit 1
|
||||
{
|
||||
Ax25BitParse(1, demod);
|
||||
}
|
||||
else //symbol transition - decoded bit 0
|
||||
{
|
||||
Ax25BitParse(0, demod);
|
||||
}
|
||||
}
|
||||
|
||||
if(((dem->rawSymbols & 0x03) == 0b10) || ((dem->rawSymbols & 0x03) == 0b01)) //if there was a symbol transition, adjust PLL
|
||||
{
|
||||
|
||||
if(Ax25GetRxStage(demod) != RX_STAGE_FRAME) //not in a frame
|
||||
{
|
||||
dem->pll = (int)(dem->pll * PLLNOTLOCKED); //adjust PLL faster
|
||||
}
|
||||
else //in a frame
|
||||
{
|
||||
dem->pll = (int)(dem->pll * PLLLOCKED); //adjust PLL slower
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ModemTxTestStart(enum ModemTxTestMode type)
|
||||
{
|
||||
if(txTestState != TEST_DISABLED) //TX test is already running
|
||||
ModemTxTestStop(); //stop this test
|
||||
|
||||
setPtt(1); //PTT on
|
||||
txTestState = type;
|
||||
|
||||
//DAC timer
|
||||
TIM1->PSC = 17; //72/18=4 MHz
|
||||
TIM1->DIER = TIM_DIER_UIE; //enable interrupt
|
||||
TIM1->CR1 |= TIM_CR1_CEN; //enable timer
|
||||
|
||||
TIM2->CR1 &= ~TIM_CR1_CEN; //disable RX timer
|
||||
|
||||
NVIC_DisableIRQ(DMA1_Channel2_IRQn); //disable RX DMA interrupt
|
||||
NVIC_EnableIRQ(TIM1_UP_IRQn); //enable timer 1 for PWM
|
||||
|
||||
if(type == TEST_MARK)
|
||||
{
|
||||
TIM1->ARR = markFreq;
|
||||
} else if(type == TEST_SPACE)
|
||||
{
|
||||
TIM1->ARR = spaceFreq;
|
||||
}
|
||||
else //alternating tones
|
||||
{
|
||||
//enable baudrate generator
|
||||
TIM3->PSC = 71; //72/72=1 MHz
|
||||
TIM3->DIER = TIM_DIER_UIE; //enable interrupt
|
||||
TIM3->ARR = baudRate; //set timer interval
|
||||
TIM3->CR1 = TIM_CR1_CEN; //enable timer
|
||||
NVIC_EnableIRQ(TIM3_IRQn); //enable interrupt in NVIC
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ModemTxTestStop(void)
|
||||
{
|
||||
txTestState = TEST_DISABLED;
|
||||
|
||||
TIM3->CR1 &= ~TIM_CR1_CEN; //turn off timers
|
||||
TIM1->CR1 &= ~TIM_CR1_CEN;
|
||||
TIM2->CR1 |= TIM_CR1_CEN; //enable RX timer
|
||||
|
||||
NVIC_DisableIRQ(TIM3_IRQn);
|
||||
NVIC_DisableIRQ(TIM1_UP_IRQn);
|
||||
NVIC_EnableIRQ(DMA1_Channel2_IRQn);
|
||||
|
||||
setPtt(0); //PTT off
|
||||
}
|
||||
|
||||
|
||||
void ModemTransmitStart(void)
|
||||
{
|
||||
setPtt(1); //PTT on
|
||||
|
||||
TIM1->PSC = 17;
|
||||
TIM1->DIER |= TIM_DIER_UIE;
|
||||
|
||||
|
||||
TIM3->PSC = 71;
|
||||
TIM3->DIER |= TIM_DIER_UIE;
|
||||
TIM3->ARR = baudRate;
|
||||
|
||||
TIM3->CR1 = TIM_CR1_CEN;
|
||||
TIM1->CR1 = TIM_CR1_CEN;
|
||||
TIM2->CR1 &= ~TIM_CR1_CEN;
|
||||
|
||||
NVIC_DisableIRQ(DMA1_Channel2_IRQn);
|
||||
NVIC_EnableIRQ(TIM1_UP_IRQn);
|
||||
NVIC_EnableIRQ(TIM3_IRQn);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Stop TX and go back to RX
|
||||
*/
|
||||
void ModemTransmitStop(void)
|
||||
{
|
||||
|
||||
TIM2->CR1 |= TIM_CR1_CEN;
|
||||
TIM3->CR1 &= ~TIM_CR1_CEN;
|
||||
TIM1->CR1 &= ~TIM_CR1_CEN;
|
||||
|
||||
NVIC_DisableIRQ(TIM1_UP_IRQn);
|
||||
NVIC_DisableIRQ(TIM3_IRQn);
|
||||
NVIC_EnableIRQ(DMA1_Channel2_IRQn);
|
||||
|
||||
setPtt(0);
|
||||
|
||||
TIM4->CCR1 = 44; //set around 50% duty cycle
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Controls PTT output
|
||||
* @param[in] state 0 - PTT off, 1 - PTT on
|
||||
*/
|
||||
static void setPtt(uint8_t state)
|
||||
{
|
||||
if(state)
|
||||
PTT_ON;
|
||||
else
|
||||
PTT_OFF;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize AFSK module
|
||||
*/
|
||||
void ModemInit(void)
|
||||
{
|
||||
/**
|
||||
* TIM1 is used for pushing samples to DAC (R2R or PWM) at 4 MHz
|
||||
* TIM3 is the baudrate generator for TX running at 1 MHz
|
||||
* TIM4 is the PWM generator with no software interrupt
|
||||
* TIM2 is the RX sampling timer with no software interrupt, but it directly calls DMA
|
||||
*/
|
||||
|
||||
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
|
||||
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
|
||||
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
|
||||
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
|
||||
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
|
||||
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
|
||||
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
|
||||
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
|
||||
|
||||
|
||||
GPIOC->CRH |= GPIO_CRH_MODE13_1; //DCD LED on PC13
|
||||
GPIOC->CRH &= ~GPIO_CRH_MODE13_0;
|
||||
GPIOC->CRH &= ~GPIO_CRH_CNF13;
|
||||
|
||||
GPIOB->CRH &= ~0xFFFF0000; //R2R output on PB12-PB15
|
||||
GPIOB->CRH |= 0x22220000;
|
||||
|
||||
GPIOA->CRL &= ~GPIO_CRL_CNF0; //ADC input on PA0
|
||||
GPIOA->CRL &= ~GPIO_CRL_MODE0;
|
||||
|
||||
|
||||
GPIOB->CRL |= GPIO_CRL_MODE7_1; //PTT output on PB7
|
||||
GPIOB->CRL &= ~GPIO_CRL_MODE7_0;
|
||||
GPIOB->CRL &= ~GPIO_CRL_CNF7;
|
||||
|
||||
GPIOB->CRL |= GPIO_CRL_MODE5_1; //2nd DCD LED on PB5
|
||||
GPIOB->CRL &= ~GPIO_CRL_MODE5_0;
|
||||
GPIOB->CRL &= ~GPIO_CRL_CNF5;
|
||||
|
||||
|
||||
RCC->CFGR |= RCC_CFGR_ADCPRE_1; //ADC prescaler /6
|
||||
RCC->CFGR &= ~RCC_CFGR_ADCPRE_0;
|
||||
|
||||
ADC1->CR2 |= ADC_CR2_CONT; //continuous conversion
|
||||
ADC1->CR2 |= ADC_CR2_EXTSEL;
|
||||
ADC1->SQR1 &= ~ADC_SQR1_L; //1 conversion
|
||||
ADC1->SMPR2 |= ADC_SMPR2_SMP0_2; //41.5 cycle sampling
|
||||
ADC1->SQR3 &= ~ADC_SQR3_SQ1; //channel 0 is first in the sequence
|
||||
ADC1->CR2 |= ADC_CR2_ADON; //ADC on
|
||||
|
||||
ADC1->CR2 |= ADC_CR2_RSTCAL; //calibrate ADC
|
||||
while(ADC1->CR2 & ADC_CR2_RSTCAL)
|
||||
;
|
||||
ADC1->CR2 |= ADC_CR2_CAL;
|
||||
while(ADC1->CR2 & ADC_CR2_CAL)
|
||||
;
|
||||
|
||||
ADC1->CR2 |= ADC_CR2_EXTTRIG;
|
||||
ADC1->CR2 |= ADC_CR2_SWSTART; //start ADC conversion
|
||||
|
||||
//prepare DMA
|
||||
DMA1_Channel2->CCR |= DMA_CCR_MSIZE_0; //16 bit memory region
|
||||
DMA1_Channel2->CCR &= ~DMA_CCR_MSIZE_1;
|
||||
DMA1_Channel2->CCR |= DMA_CCR_PSIZE_0;
|
||||
DMA1_Channel2->CCR &= ~DMA_CCR_PSIZE_1;
|
||||
|
||||
DMA1_Channel2->CCR |= DMA_CCR_MINC | DMA_CCR_CIRC| DMA_CCR_TCIE; //circular mode, memory increment and interrupt
|
||||
DMA1_Channel2->CNDTR = 4; //4 samples
|
||||
DMA1_Channel2->CPAR = (uint32_t)&(ADC1->DR); //ADC data register address
|
||||
DMA1_Channel2->CMAR = (uint32_t)samples; //sample buffer address
|
||||
DMA1_Channel2->CCR |= DMA_CCR_EN; //enable DMA
|
||||
|
||||
NVIC_EnableIRQ(DMA1_Channel2_IRQn);
|
||||
|
||||
TIM2->PSC = 17; //72/18=4 MHz
|
||||
TIM2->DIER |= TIM_DIER_UDE; //enable calling DMA on timer tick
|
||||
TIM2->ARR = 103; //4MHz / 104 =~38400 Hz (4*9600 Hz for 4x oversampling)
|
||||
TIM2->CR1 |= TIM_CR1_CEN; //enable timer
|
||||
|
||||
markFreq = 4000000 / (DAC_SINE_SIZE * (uint32_t)MODEM_MARK_FREQUENCY) - 1; //set mark frequency
|
||||
spaceFreq = 4000000 / (DAC_SINE_SIZE * (uint32_t)MODEM_SPACE_FREQUENCY) - 1; //set space frequency
|
||||
baudRate = 1000000 / (uint32_t)MODEM_BAUDRATE - 1; //set baudrate
|
||||
|
||||
for(uint8_t i = 0; i < N; i++) //calculate correlator coefficients
|
||||
{
|
||||
coeffLoI[i] = 4095.f * cosf(2.f * 3.1416f * (float)i / (float)N * MODEM_MARK_FREQUENCY / MODEM_BAUDRATE);
|
||||
coeffLoQ[i] = 4095.f * sinf(2.f * 3.1416f * (float)i / (float)N * MODEM_MARK_FREQUENCY / MODEM_BAUDRATE);
|
||||
coeffHiI[i] = 4095.f * cosf(2.f * 3.1416f * (float)i / (float)N * MODEM_SPACE_FREQUENCY / MODEM_BAUDRATE);
|
||||
coeffHiQ[i] = 4095.f * sinf(2.f * 3.1416f * (float)i / (float)N * MODEM_SPACE_FREQUENCY / MODEM_BAUDRATE);
|
||||
}
|
||||
|
||||
|
||||
for(uint8_t i = 0; i < DAC_SINE_SIZE; i++) //calculate DAC sine samples
|
||||
{
|
||||
if(ModemConfig.usePWM)
|
||||
dacSine[i] = ((sinf(2.f * 3.1416f * (float)i / (float)DAC_SINE_SIZE) + 1.f) * 45.f);
|
||||
else
|
||||
dacSine[i] = ((7.f * sinf(2.f * 3.1416f * (float)i / (float)DAC_SINE_SIZE)) + 8.f);
|
||||
}
|
||||
|
||||
if(ModemConfig.flatAudioIn) //when used with flat audio input, use deemphasis and flat modems
|
||||
{
|
||||
demodState[0].emphasis = EMPHASIS_NONE;
|
||||
demodState[1].emphasis = DEEMPHASIS;
|
||||
}
|
||||
else //when used with normal (filtered) audio input, use flat and preemphasis modems
|
||||
{
|
||||
demodState[0].emphasis = EMPHASIS_NONE;
|
||||
demodState[1].emphasis = PREEMPHASIS;
|
||||
}
|
||||
|
||||
if(ModemConfig.usePWM)
|
||||
{
|
||||
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; //configure timer
|
||||
|
||||
GPIOB->CRL |= GPIO_CRL_CNF6_1; //configure pin for PWM
|
||||
GPIOB->CRL |= GPIO_CRL_MODE6;
|
||||
GPIOB->CRL &= ~GPIO_CRL_CNF6_0;
|
||||
|
||||
//set up PWM generation
|
||||
TIM4->PSC = 7; //72MHz/8=9MHz
|
||||
TIM4->ARR = 90; //9MHz/90=100kHz
|
||||
|
||||
TIM4->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2;
|
||||
TIM4->CCER |= TIM_CCER_CC1E;
|
||||
TIM4->CCR1 = 44; //initial duty cycle
|
||||
|
||||
TIM4->CR1 |= TIM_CR1_CEN;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
Copyright 2020-2023 Piotr Wilkon
|
||||
|
||||
This file is part of VP-Digi.
|
||||
|
||||
VP-Digi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
VP-Digi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "drivers/uart.h"
|
||||
#include "drivers/systick.h"
|
||||
#include "terminal.h"
|
||||
#include "ax25.h"
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include "digipeater.h"
|
||||
#include "kiss.h"
|
||||
|
||||
Uart Uart1, Uart2, UartUsb;
|
||||
|
||||
static void handleInterrupt(Uart *port)
|
||||
{
|
||||
if(port->port->SR & USART_SR_RXNE) //byte received
|
||||
{
|
||||
port->port->SR &= ~USART_SR_RXNE;
|
||||
uint8_t data = port->port->DR;
|
||||
port->rxBuffer[port->rxBufferHead++] = data; //store it
|
||||
port->rxBufferHead %= UART_BUFFER_SIZE;
|
||||
|
||||
KissParse(port, data);
|
||||
TermHandleSpecial(port);
|
||||
}
|
||||
if(port->port->SR & USART_SR_IDLE) //line is idle, end of data reception
|
||||
{
|
||||
port->port->DR; //reset idle flag by dummy read
|
||||
if(port->rxBufferHead != 0)
|
||||
{
|
||||
if(((port->rxBuffer[port->rxBufferHead - 1] == '\r') || (port->rxBuffer[port->rxBufferHead - 1] == '\n'))) //data ends with \r or \n, process as data
|
||||
{
|
||||
port->rxType = DATA_TERM;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(port->port->SR & USART_SR_TXE) //TX buffer empty
|
||||
{
|
||||
if((port->txBufferHead != port->txBufferTail) || port->txBufferFull) //if there is anything to transmit
|
||||
{
|
||||
port->port->DR = port->txBuffer[port->txBufferTail++]; //push it to the register
|
||||
port->txBufferTail %= UART_BUFFER_SIZE;
|
||||
port->txBufferFull = 0;
|
||||
}
|
||||
else //nothing more to be transmitted
|
||||
{
|
||||
port->port->CR1 &= ~USART_CR1_TXEIE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USART1_IRQHandler(void) __attribute__ ((interrupt));
|
||||
void USART1_IRQHandler(void)
|
||||
{
|
||||
handleInterrupt(&Uart1);
|
||||
}
|
||||
|
||||
void USART2_IRQHandler(void) __attribute__ ((interrupt));
|
||||
void USART2_IRQHandler(void)
|
||||
{
|
||||
handleInterrupt(&Uart2);
|
||||
}
|
||||
|
||||
|
||||
void UartSendByte(Uart *port, uint8_t data)
|
||||
{
|
||||
if(!port->enabled)
|
||||
return;
|
||||
|
||||
if(port->isUsb)
|
||||
{
|
||||
CDC_Transmit_FS(&data, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
while(port->txBufferFull)
|
||||
;
|
||||
port->txBuffer[port->txBufferHead++] = data;
|
||||
port->txBufferHead %= UART_BUFFER_SIZE;
|
||||
if(port->txBufferHead == port->txBufferTail)
|
||||
port->txBufferFull = 1;
|
||||
if(0 == (port->port->CR1 & USART_CR1_TXEIE))
|
||||
port->port->CR1 |= USART_CR1_TXEIE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UartSendString(Uart *port, void *data, uint16_t len)
|
||||
{
|
||||
if(0 == len)
|
||||
len = strlen((char*)data);
|
||||
|
||||
for(uint16_t i = 0; i < len; i++)
|
||||
{
|
||||
UartSendByte(port, ((uint8_t*)data)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static unsigned int findHighestPosition(unsigned int n)
|
||||
{
|
||||
unsigned int i = 1;
|
||||
while((i * 10) <= n)
|
||||
i *= 10;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
void UartSendNumber(Uart *port, int32_t n)
|
||||
{
|
||||
if(n < 0)
|
||||
UartSendByte(port, '-');
|
||||
n = abs(n);
|
||||
unsigned int position = findHighestPosition(n);
|
||||
while(position)
|
||||
{
|
||||
unsigned int number = n / position;
|
||||
UartSendByte(port, (number + 48));
|
||||
n -= (number * position);
|
||||
position /= 10;
|
||||
}
|
||||
}
|
||||
|
||||
void UartInit(Uart *port, USART_TypeDef *uart, uint32_t baud)
|
||||
{
|
||||
port->port = uart;
|
||||
port->baudrate = baud;
|
||||
port->rxType = DATA_NOTHING;
|
||||
port->rxBufferHead = 0;
|
||||
port->txBufferHead = 0;
|
||||
port->txBufferTail = 0;
|
||||
port->txBufferFull = 0;
|
||||
port->kissBufferHead = 0;
|
||||
port->mode = MODE_KISS;
|
||||
port->enabled = 0;
|
||||
port->lastRxBufferHead = 0;
|
||||
memset(port->rxBuffer, 0, sizeof(port->rxBuffer));
|
||||
memset(port->txBuffer, 0, sizeof(port->txBuffer));
|
||||
memset(port->kissBuffer, 0, sizeof(port->kissBuffer));
|
||||
}
|
||||
|
||||
|
||||
void UartConfig(Uart *port, uint8_t state)
|
||||
{
|
||||
if(port->port == USART1)
|
||||
{
|
||||
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
|
||||
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
|
||||
GPIOA->CRH |= GPIO_CRH_MODE9_1;
|
||||
GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
|
||||
GPIOA->CRH |= GPIO_CRH_CNF9_1;
|
||||
GPIOA->CRH |= GPIO_CRH_CNF10_0;
|
||||
GPIOA->CRH &= ~GPIO_CRH_CNF10_1;
|
||||
|
||||
USART1->BRR = (SystemCoreClock / (port->baudrate));
|
||||
|
||||
if(state)
|
||||
USART1->CR1 |= USART_CR1_RXNEIE | USART_CR1_TE | USART_CR1_RE | USART_CR1_UE | USART_CR1_IDLEIE;
|
||||
else
|
||||
USART1->CR1 &= (~USART_CR1_RXNEIE) & (~USART_CR1_TE) & (~USART_CR1_RE) & (~USART_CR1_UE) & (~USART_CR1_IDLEIE);
|
||||
|
||||
NVIC_SetPriority(USART1_IRQn, 2);
|
||||
if(state)
|
||||
NVIC_EnableIRQ(USART1_IRQn);
|
||||
else
|
||||
NVIC_DisableIRQ(USART1_IRQn);
|
||||
|
||||
port->enabled = state > 0;
|
||||
port->isUsb = 0;
|
||||
}
|
||||
else if(port->port == USART2)
|
||||
{
|
||||
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
|
||||
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
|
||||
GPIOA->CRL |= GPIO_CRL_MODE2_1;
|
||||
GPIOA->CRL &= ~GPIO_CRL_CNF2_0;
|
||||
GPIOA->CRL |= GPIO_CRL_CNF2_1;
|
||||
GPIOA->CRL |= GPIO_CRL_CNF3_0;
|
||||
GPIOA->CRL &= ~GPIO_CRL_CNF3_1;
|
||||
|
||||
USART2->BRR = (SystemCoreClock / (port->baudrate * 2));
|
||||
if(state)
|
||||
USART2->CR1 |= USART_CR1_RXNEIE | USART_CR1_TE | USART_CR1_RE | USART_CR1_UE | USART_CR1_IDLEIE;
|
||||
else
|
||||
USART2->CR1 &= (~USART_CR1_RXNEIE) & (~USART_CR1_TE) & (~USART_CR1_RE) & (~USART_CR1_UE) & (~USART_CR1_IDLEIE);
|
||||
|
||||
NVIC_SetPriority(USART2_IRQn, 2);
|
||||
if(state)
|
||||
NVIC_EnableIRQ(USART2_IRQn);
|
||||
else
|
||||
NVIC_DisableIRQ(USART2_IRQn);
|
||||
|
||||
port->enabled = state > 0;
|
||||
port->isUsb = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
port->isUsb = 1;
|
||||
port->enabled = state > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void UartClearRx(Uart *port)
|
||||
{
|
||||
port->rxBufferHead = 0;
|
||||
port->rxType = DATA_NOTHING;
|
||||
}
|
Ładowanie…
Reference in New Issue