conflict resolution

pull/32/head
Piotr Wilkon 2023-10-03 16:13:36 +02:00
commit f4ad4a8022
27 zmienionych plików z 1610 dodań i 7 usunięć

Wyświetl plik

@ -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">

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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_

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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_ */

Wyświetl plik

@ -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 -------------------------------------*/

Wyświetl plik

@ -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

Wyświetl plik

@ -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_

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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"

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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";

Wyświetl plik

@ -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>

Wyświetl plik

@ -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

578
Src/ax25.c 100644
Wyświetl plik

@ -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));
}

711
Src/drivers/modem.c 100644
Wyświetl plik

@ -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;
}
}
/**
* @}
*/

226
Src/drivers/uart.c 100644
Wyświetl plik

@ -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;
}