Browse Source

AIOC release

aioc
Piotr Wilkon 9 months ago
parent
commit
e926b2e9f8
41 changed files with 1264 additions and 1090 deletions
  1. +11
    -2
      .gitignore
  2. +0
    -32
      .project
  3. +11
    -0
      CHANGELOG.md
  4. +1
    -0
      Core/Inc/ax25.h
  5. +2
    -1
      Core/Inc/beacon.h
  6. +23
    -3
      Core/Inc/common.h
  7. +2
    -2
      Core/Inc/digipeater.h
  8. +16
    -1
      Core/Inc/drivers/digipeater_ll.h
  9. +5
    -162
      Core/Inc/drivers/modem_ll.h
  10. +9
    -1
      Core/Inc/drivers/uart_ll.h
  11. +21
    -3
      Core/Inc/drivers/usb.h
  12. +9
    -3
      Core/Inc/drivers/watchdog.h
  13. +46
    -80
      Core/Inc/filter.h
  14. +2
    -0
      Core/Inc/fx25.h
  15. +1
    -20
      Core/Inc/main.h
  16. +15
    -15
      Core/Inc/modem.h
  17. +1
    -1
      Core/Inc/stm32f1xx_it.h
  18. +0
    -44
      Core/Inc/systick.h
  19. +12
    -6
      Core/Inc/uart.h
  20. +9
    -6
      Core/Src/ax25.c
  21. +24
    -22
      Core/Src/beacon.c
  22. +33
    -3
      Core/Src/common.c
  23. +163
    -155
      Core/Src/config.c
  24. +12
    -7
      Core/Src/digipeater.c
  25. +3
    -2
      Core/Src/fx25.c
  26. +155
    -163
      Core/Src/main.c
  27. +120
    -167
      Core/Src/modem.c
  28. +5
    -5
      Core/Src/stm32f1xx_it.c
  29. +232
    -0
      Core/Src/stm32f3xx_it.c
  30. +0
    -46
      Core/Src/systick.c
  31. +181
    -74
      Core/Src/terminal.c
  32. +26
    -11
      Core/Src/uart.c
  33. +0
    -19
      F103C8T6_DIGI_USB.xml
  34. +20
    -4
      README.md
  35. +18
    -3
      README_pl.md
  36. +8
    -6
      STM32F103C8Tx_FLASH.ld
  37. +1
    -2
      STM32F302CBTX_FLASH.ld
  38. +0
    -0
      TODO
  39. +31
    -7
      doc/manual.md
  40. +31
    -7
      doc/manual_pl.md
  41. +5
    -5
      vp-digi.ioc

+ 11
- 2
.gitignore View File

@ -106,6 +106,7 @@ Drivers/
Middlewares/
USB_DEVICE/
!USB_DEVICE/App/usbd_cdc_if.c
Core/Startup/
.mxproject
.stm32env
@ -113,5 +114,13 @@ openocd.cfg
*.yaml
*.make
Makefile
*.ld
startup*.s
startup*.s
stm32f*_hal_msp.c
syscalls.c
sysmem.c
system_stm32f*.c
gpio.c
gpio.h
stm32f*_hal_conf.h
stm32f*_it.h
*.launch

+ 0
- 32
.project View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>vp-digi</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.st.stm32cube.ide.mcu.MCUProjectNature</nature>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>com.st.stm32cube.ide.mcu.MCUCubeIdeServicesRevAev2ProjectNature</nature>
<nature>com.st.stm32cube.ide.mcu.MCUCubeProjectNature</nature>
<nature>com.st.stm32cube.ide.mcu.MCUAdvancedStructureProjectNature</nature>
<nature>com.st.stm32cube.ide.mcu.MCUSingleCpuProjectNature</nature>
<nature>com.st.stm32cube.ide.mcu.MCURootProjectNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
</natures>
</projectDescription>

+ 11
- 0
CHANGELOG.md View File

@ -1,3 +1,14 @@
# 2.2.0 (2025-03-20)
## New features
* [AIOC](https://github.com/skuep/AIOC)-compatibility
* FPU support for modem
* "Interactive" calibration menu
## Bug fixes
* none
## Other
* Got rid of multiline macros in modem this time :)
## Known bugs
* none
# 2.1.0 (2025-03-04)
## New features
* Digipeater disable input at PB0 (pull to GND to disable digipeater)


+ 1
- 0
Core/Inc/ax25.h View File

@ -22,6 +22,7 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include <stdint.h>
#include <stdbool.h>
#include "defines.h"
#define AX25_NOT_FX25 255


+ 2
- 1
Core/Inc/beacon.h View File

@ -24,6 +24,7 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include <stdint.h>
#define BEACON_MAX_PAYLOAD_SIZE 100
#define BEACON_COUNT 8
struct Beacon
{
@ -35,7 +36,7 @@ struct Beacon
uint32_t next; //next beacon timestamp
};
extern struct Beacon beacon[8];
extern struct Beacon BeaconConfig[BEACON_COUNT];
/**
* @brief Send specified beacon


+ 23
- 3
Core/Inc/common.h View File

@ -23,21 +23,41 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include <stdint.h>
#include "uart.h"
#if defined(STM32F103xB) || defined(STM32F103x8)
#include "stm32f1xx_hal.h"
#elif defined(STM32F302xC)
#include "stm32f3xx_hal.h"
#endif
#define VPDIGI_HAL_TICK_FREQUENCY HAL_TICK_FREQ_100HZ /**< Systick frequency for HAL. Use this to change systick frequency */
#if VPDIGI_HAL_TICK_FREQUENCY == HAL_TICK_FREQ_100HZ
#define SYSTICK_INTERVAL (10)
#elif VPDIGI_HAL_TICK_FREQUENCY == HAL_TICK_FREQ_10HZ
#define SYSTICK_INERVAL (100)
#elif VPDIGI_HAL_TICK_FREQUENCY == HAL_TICK_FREQ_1KHZ
#define SYSTICK_INTERVAL (1)
#else
#error Wrong systick frequency!
#endif
#define SYSTICK_FREQUENCY (1000 / SYSTICK_INTERVAL)
#define IS_UPPERCASE_ALPHANUMERIC(x) ((((x) >= '0') && ((x) <= '9')) || (((x) >= 'A') && ((x) <= 'Z')))
#define IS_NUMBER(x) (((x) >= '0') && ((x) <= '9'))
#define ABS(x) (((x) > 0) ? (x) : (-x))
#define CRC32_INIT 0xFFFFFFFF
struct _GeneralConfig
struct GeneralConfig
{
uint8_t call[6]; //device callsign
uint8_t callSsid; //device ssid
uint8_t dest[7]; //destination address for own beacons. Should be APNV01-0 for VP-Digi, but can be changed. SSID MUST remain 0.
uint8_t dest[7]; //destination address for own beacons. Should be APNV01-0 for Blue Pill VP-Digi and APNV02-0 for AIOC VP-Digi, but can be changed. SSID MUST remain 0.
uint8_t kissMonitor;
};
extern struct _GeneralConfig GeneralConfig;
extern struct GeneralConfig GeneralConfig;
extern const char versionString[]; //version string


+ 2
- 2
Core/Inc/digipeater.h View File

@ -23,7 +23,7 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include <stdint.h>
struct _DigiConfig
struct DigiConfig
{
uint8_t alias[8][6]; //digi alias list
uint8_t ssid[4]; //ssid list for simple aliases
@ -40,7 +40,7 @@ struct _DigiConfig
uint8_t filterPolarity : 1; //filter polarity: 0 - blacklist, 1- whitelist
};
extern struct _DigiConfig DigiConfig; //digipeater state
extern struct DigiConfig DigiConfig; //digipeater state
/**


+ 16
- 1
Core/Inc/drivers/digipeater_ll.h View File

@ -24,8 +24,9 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#define DRIVERS_DIGIPEATER_LL_H_
#include <stdint.h>
#include "defines.h"
#if defined(STM32F103xB) || defined(STM32F103x8)
#if defined(BLUE_PILL)
#include "stm32f1xx.h"
@ -55,7 +56,21 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
GPIOB->CRL &= ~GPIO_CRL_CNF1; \
} while(0); \
#elif defined(AIOC)
#define DIGIPEATER_LL_LED_ON()
#define DIGIPEATER_LL_LED_OFF()
/**
* @brief Placeholder on AIOC port
* @return Always 0
*/
#define DIGIPEATER_LL_GET_DISABLE_STATE() (0)
#define DIGIPEATER_LL_INITIALIZE_RCC()
#define DIGIPEATER_LL_INITIALIZE_INPUTS_OUTPUTS()
#endif


+ 5
- 162
Core/Inc/drivers/modem_ll.h View File

@ -24,173 +24,16 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#define DRIVERS_MODEM_LL_H_
#include <stdint.h>
#include "defines.h"
//Oversampling factor
//This is a helper value, not a setting that can be changed without further code modification!
#define MODEM_LL_OVERSAMPLING_FACTOR 4
#if defined(STM32F103xB) || defined(STM32F103x8)
#include "stm32f1xx.h"
/**
* TIM1 is used for pushing samples to DAC (R2R or PWM) (clocked at 18 MHz)
* TIM3 is the baudrate generator for TX (clocked at 18 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
*/
#define MODEM_LL_DMA_INTERRUPT_HANDLER DMA1_Channel2_IRQHandler
#define MODEM_LL_DAC_INTERRUPT_HANDLER TIM1_UP_IRQHandler
#define MODEM_LL_BAUDRATE_TIMER_INTERRUPT_HANDLER TIM3_IRQHandler
#define MODEM_LL_DMA_IRQ DMA1_Channel2_IRQn
#define MODEM_LL_DAC_IRQ TIM1_UP_IRQn
#define MODEM_LL_BAUDRATE_TIMER_IRQ TIM3_IRQn
#define MODEM_LL_DMA_TRANSFER_COMPLETE_FLAG (DMA1->ISR & DMA_ISR_TCIF2)
#define MODEM_LL_DMA_CLEAR_TRANSFER_COMPLETE_FLAG() (DMA1->IFCR |= DMA_IFCR_CTCIF2)
#define MODEM_LL_BAUDRATE_TIMER_CLEAR_INTERRUPT_FLAG() (TIM3->SR &= ~TIM_SR_UIF)
#define MODEM_LL_BAUDRATE_TIMER_ENABLE() (TIM3->CR1 = TIM_CR1_CEN)
#define MODEM_LL_BAUDRATE_TIMER_DISABLE() (TIM3->CR1 &= ~TIM_CR1_CEN)
#define MODEM_LL_BAUDRATE_TIMER_SET_RELOAD_VALUE(val) (TIM3->ARR = (val))
#define MODEM_LL_DAC_TIMER_CLEAR_INTERRUPT_FLAG (TIM1->SR &= ~TIM_SR_UIF)
#define MODEM_LL_DAC_TIMER_SET_RELOAD_VALUE(val) (TIM1->ARR = (val))
#define MODEM_LL_DAC_TIMER_SET_CURRENT_VALUE(val) (TIM1->CNT = (val))
#define MODEM_LL_DAC_TIMER_ENABLE() (TIM1->CR1 |= TIM_CR1_CEN)
#define MODEM_LL_DAC_TIMER_DISABLE() (TIM1->CR1 &= ~TIM_CR1_CEN)
#define MODEM_LL_ADC_TIMER_ENABLE() (TIM2->CR1 |= TIM_CR1_CEN)
#define MODEM_LL_ADC_TIMER_DISABLE() (TIM2->CR1 &= ~TIM_CR1_CEN)
#define MODEM_LL_PWM_PUT_VALUE(value) (TIM4->CCR1 = (value))
#define MODEM_LL_R2R_PUT_VALUE(value) do {GPIOB->ODR &= ~0xF000; \
GPIOB->ODR |= ((uint32_t)(value) << 12); } while(0); \
#define MODEM_LL_DCD_LED_ON() do { \
GPIOC->BSRR = GPIO_BSRR_BR13; \
GPIOB->BSRR = GPIO_BSRR_BS5; \
} while(0); \
#define MODEM_LL_DCD_LED_OFF() do { \
GPIOC->BSRR = GPIO_BSRR_BS13; \
GPIOB->BSRR = GPIO_BSRR_BR5; \
} while(0); \
#define MODEM_LL_PTT_ON() (GPIOB->BSRR = GPIO_BSRR_BS7)
#define MODEM_LL_PTT_OFF() (GPIOB->BSRR = GPIO_BSRR_BR7)
#define MODEM_LL_INITIALIZE_RCC() do { \
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; \
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; \
} while(0); \
#define MODEM_LL_INITIALIZE_OUTPUTS() do { \
/* DCD LEDs: PC13 (cathode driven - built-in LED on Blue Pill) and PB5 (anode driven) */ \
GPIOC->CRH |= GPIO_CRH_MODE13_1; \
GPIOC->CRH &= ~GPIO_CRH_MODE13_0; \
GPIOC->CRH &= ~GPIO_CRH_CNF13; \
GPIOB->CRL |= GPIO_CRL_MODE5_1; \
GPIOB->CRL &= ~GPIO_CRL_MODE5_0; \
GPIOB->CRL &= ~GPIO_CRL_CNF5; \
/* PTT: PB7 */ \
GPIOB->CRL |= GPIO_CRL_MODE7_1; \
GPIOB->CRL &= ~GPIO_CRL_MODE7_0; \
GPIOB->CRL &= ~GPIO_CRL_CNF7; \
/* R2R: 4 bits, PB12-PB15 */ \
GPIOB->CRH &= ~0xFFFF0000; \
GPIOB->CRH |= 0x22220000; \
/* PWM output: PB6 */ \
GPIOB->CRL |= GPIO_CRL_CNF6_1; \
GPIOB->CRL |= GPIO_CRL_MODE6; \
GPIOB->CRL &= ~GPIO_CRL_CNF6_0; \
} while(0); \
#define MODEM_LL_INITIALIZE_ADC() do { \
/* ADC input: PA0 */ \
GPIOA->CRL &= ~GPIO_CRL_CNF0; \
GPIOA->CRL &= ~GPIO_CRL_MODE0; \
/*/6 prescaler */ \
RCC->CFGR |= RCC_CFGR_ADCPRE_1; \
RCC->CFGR &= ~RCC_CFGR_ADCPRE_0; \
ADC1->CR2 |= ADC_CR2_CONT; \
ADC1->CR2 |= ADC_CR2_EXTSEL; \
ADC1->SQR1 &= ~ADC_SQR1_L; \
/* 41.5 cycle sampling */ \
ADC1->SMPR2 |= ADC_SMPR2_SMP0_2; \
ADC1->SQR3 &= ~ADC_SQR3_SQ1; \
ADC1->CR2 |= ADC_CR2_ADON; \
/* calibrate */ \
ADC1->CR2 |= ADC_CR2_RSTCAL; \
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; \
} while(0); \
#define MODEM_LL_INITIALIZE_DMA(buffer) do { \
/* 16 bit memory region */ \
DMA1_Channel2->CCR |= DMA_CCR_MSIZE_0; \
DMA1_Channel2->CCR &= ~DMA_CCR_MSIZE_1; \
DMA1_Channel2->CCR |= DMA_CCR_PSIZE_0; \
DMA1_Channel2->CCR &= ~DMA_CCR_PSIZE_1; \
/* enable memory pointer increment, circular mode and interrupt generation */ \
DMA1_Channel2->CCR |= DMA_CCR_MINC | DMA_CCR_CIRC| DMA_CCR_TCIE; \
DMA1_Channel2->CNDTR = MODEM_LL_OVERSAMPLING_FACTOR; \
DMA1_Channel2->CPAR = (uintptr_t)&(ADC1->DR); \
DMA1_Channel2->CMAR = (uintptr_t)buffer; \
DMA1_Channel2->CCR |= DMA_CCR_EN; \
} while(0); \
#define MODEM_LL_ADC_TIMER_INITIALIZE() do { \
/* 72 / 9 = 8 MHz */ \
TIM2->PSC = 8; \
/* enable DMA call instead of standard interrupt */ \
TIM2->DIER |= TIM_DIER_UDE; \
} while(0); \
#define MODEM_LL_DAC_TIMER_INITIALIZE() do { \
/* 72 / 4 = 18 MHz */ \
TIM1->PSC = 3; \
TIM1->DIER |= TIM_DIER_UIE; \
} while(0); \
#define MODEM_LL_BAUDRATE_TIMER_INITIALIZE() do { \
/* 72 / 4 = 18 MHz */ \
TIM3->PSC = 3; \
TIM3->DIER |= TIM_DIER_UIE; \
} while(0); \
#define MODEM_LL_PWM_INITIALIZE() do { \
/* 72 / 3 = 24 MHz to provide 8 bit resolution at around 100 kHz */ \
TIM4->PSC = 2; \
/* 24 MHz / 258 = 93 kHz */ \
TIM4->ARR = 257; \
TIM4->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; \
TIM4->CCER |= TIM_CCER_CC1E; \
TIM4->CR1 |= TIM_CR1_CEN; \
} while(0); \
#define MODEM_LL_ADC_SET_SAMPLE_RATE(rate) (TIM2->ARR = (8000000 / (rate)) - 1)
#define MODEM_LL_DAC_TIMER_CALCULATE_STEP(frequency) ((18000000 / (frequency)) - 1)
#define MODEM_LL_BAUDRATE_TIMER_CALCULATE_STEP(frequency) ((18000000 / (frequency)) - 1)
#ifdef BLUE_PILL
#include "modem_ll_bluepill.h"
#elif defined(AIOC)
#include "modem_ll_aioc.h"
#endif
#endif /* DRIVERS_MODEM_LL_H_ */

+ 9
- 1
Core/Inc/drivers/uart_ll.h View File

@ -23,10 +23,14 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#ifndef DRIVERS_UART_LL_H_
#define DRIVERS_UART_LL_H_
#if defined(STM32F103xB) || defined(STM32F103x8)
#include "defines.h"
#if defined(BLUE_PILL)
#include "stm32f1xx.h"
#define UART_BUFFER_SIZE 130
#define UART_LL_ENABLE(port) (port->CR1 |= USART_CR1_RXNEIE | USART_CR1_TE | USART_CR1_RE | USART_CR1_UE | USART_CR1_IDLEIE)
#define UART_LL_DISABLE(port) (port->CR1 &= (~USART_CR1_RXNEIE) & (~USART_CR1_TE) & (~USART_CR1_RE) & (~USART_CR1_UE) & (~USART_CR1_IDLEIE))
@ -76,6 +80,10 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
UART_LL_UART2_STRUCTURE->BRR = (SystemCoreClock / (baudrate * 2)); \
} while(0); \
#elif defined(AIOC)
#define UART_BUFFER_SIZE 130
#endif
#endif /* INC_DRIVERS_UART_LL_H_ */

+ 21
- 3
Core/Inc/drivers/usb.h View File

@ -21,11 +21,12 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#ifndef DRIVERS_USB_H_
#define DRIVERS_USB_H_
#include "systick.h"
#include "defines.h"
#if defined(STM32F103xB) || defined(STM32F103x8)
#if defined(BLUE_PILL)
#include "stm32f1xx.h"
#include "stm32f1xx_hal.h"
#define USB_FORCE_REENUMERATION() do { \
/* Pull D+ to ground for a moment to force reenumeration */ \
@ -33,11 +34,28 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
GPIOA->CRH |= GPIO_CRH_MODE12_1; \
GPIOA->CRH &= ~GPIO_CRH_CNF12; \
GPIOA->BSRR = GPIO_BSRR_BR12; \
Delay(100); \
HAL_Delay(100); \
GPIOA->CRH &= ~GPIO_CRH_MODE12; \
GPIOA->CRH |= GPIO_CRH_CNF12_0; \
} while(0); \
#elif defined(AIOC)
#include "stm32f3xx.h"
#include "stm32f3xx_hal.h"
#define USB_FORCE_REENUMERATION() do { \
/* Pull D+ to ground for a moment to force reenumeration */ \
RCC->AHBENR |= RCC_AHBENR_GPIOAEN; \
GPIOA->MODER |= GPIO_MODER_MODER12_0; \
GPIOA->MODER &= ~GPIO_MODER_MODER12_1; \
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_12; \
GPIOA->BSRR = GPIO_BSRR_BR_12; \
HAL_Delay(20); \
GPIOA->MODER &= ~GPIO_MODER_MODER12_0; \
GPIOA->MODER |= GPIO_MODER_MODER12_1; \
} while(0); \
#endif
#endif /* DRIVERS_USB_H_ */

+ 9
- 3
Core/Inc/drivers/watchdog.h View File

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 Piotr Wilkon
Copyright 2020-2025 Piotr Wilkon
This file is part of VP-Digi.
@ -20,7 +20,13 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#ifndef DRIVERS_WATCHDOG_H_
#define DRIVERS_WATCHDOG_H_
#if defined(STM32F103xB) || defined(STM32F103x8) || defined(STM32F302xC)
#if defined(STM32F103xB) || defined(STM32F103x8)
#include "stm32f1xx.h"
#elif defined(STM32F302xC)
#include "stm32f3xx.h"
#endif
/**
* @brief Initialize watchdog
@ -28,8 +34,8 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
void WdogInit(void)
{
IWDG->KR = 0x5555; //configuration mode
IWDG->PR = 0b101; //prescaler
IWDG->RLR = 0xFFF; //timeout register
IWDG->PR = 0b101; //prescaler: 40 kHz/128=312.5 Hz
IWDG->RLR = 0xFFF; //reload register -> timeout every 13.1 s
IWDG->KR = 0xCCCC; //start
}


+ 46
- 80
Core/Inc/filter.h View File

@ -6,109 +6,64 @@
#ifdef USE_FPU
typedef float sample_t;
typedef float coeff_t;
/**
* @brief BPF filter with 2200 Hz tone 6 dB preemphasis (it actually attenuates 1200 Hz tone by 6 dB)
*/
static const int16_t bpf1200[8] =
static const coeff_t bpf1200[8] =
{
728,
-13418,
-554,
19493,
-554,
-13418,
728,
2104
0.022217f, -0.409485f, -0.016907f, 0.594879f, -0.016907f, -0.409485f, 0.022217f, 0.064209f
};
/**
* @brief BPF filter with 2200 Hz tone 6 dB deemphasis
*/
static const int16_t bpf1200Inv[8] =
static const coeff_t bpf1200Inv[8] =
{
-10513,
-10854,
9589,
23884,
9589,
-10854,
-10513,
-879
-0.320831f, -0.331238f, 0.292633f, 0.728882f, 0.292633f, -0.331238f, -0.320831f, -0.026825f
};
//fs=9600, rectangular, fc1=1500, fc2=1900, 0 dB @ 1600 Hz and 1800 Hz, N = 15, gain 65536
static const int16_t bpf300[15] =
//fs=9600, rectangular, fc1=1500, fc2=1900, 0 dB @ 1600 Hz and 1800 Hz, N = 15
static const coeff_t bpf300[15] =
{
186, 8887, 8184, -1662, -10171, -8509, 386, 5394, 386, -8509, -10171, -1662, 8184, 8887, 186,
0.002838f, 0.135605f, 0.124878f, -0.025360f, -0.155197f, -0.129837f, 0.005890f, 0.082306f, 0.005890f, -0.129837f, -0.155197f, -0.025360f, 0.124878f, 0.135605f, 0.002838f
};
#define BPF_MAX_TAPS 15
//fs=9600 Hz, raised cosine, fc=300 Hz (BR=600 Bd), beta=0.8, N=14, gain=65536
static const int16_t lpf300[14] =
//fs=9600 Hz, raised cosine, fc=300 Hz (BR=600 Bd), beta=0.8, N=14
static const coeff_t lpf300[14] =
{
4385, 4515, 4627, 4720, 4793, 4846, 4878, 4878, 4846, 4793, 4720, 4627, 4515, 4385,
0.066910f, 0.068893f, 0.070602f, 0.072021f, 0.073135f, 0.073944f, 0.074432f, 0.074432f, 0.073944f, 0.073135f, 0.072021f, 0.070602f, 0.068893f, 0.066910f
};
//I don't remember what are this filter parameters,
//but it seems to be the best among all I have tested
static const int16_t lpf1200[15] =
static const coeff_t lpf1200[15] =
{
-6128,
-5974,
-2503,
4125,
12679,
21152,
27364,
29643,
27364,
21152,
12679,
4125,
-2503,
-5974,
-6128
-0.187012f, -0.182312f, -0.076385f, 0.125885f, 0.386932f, 0.645508f, 0.835083f, 0.904633f, 0.835083f, 0.645508f, 0.386932f, 0.125885f, -0.076385f, -0.182312f, -0.187012f
};
//fs=38400 Hz, Gaussian, fc=4800 Hz (9600 Bd), N=9, gain=65536
//seems like there is almost no difference between N=9 and any higher order
static const int16_t lpf9600[9] = {497, 2360, 7178, 13992, 17478, 13992, 7178, 2360, 497};
static const coeff_t lpf9600[9] = {0.007584f, 0.036011f, 0.109528f, 0.213501f, 0.266693f, 0.213501f, 0.109528f, 0.036011f, 0.007584f};
#define LPF_MAX_TAPS 15
#define FILTER_MAX_TAPS ((LPF_MAX_TAPS > BPF_MAX_TAPS) ? LPF_MAX_TAPS : BPF_MAX_TAPS)
struct Filter
{
const int16_t *coeffs;
uint8_t taps;
int32_t samples[FILTER_MAX_TAPS];
uint8_t gainShift;
};
static int32_t filter(struct Filter *filter, int32_t input)
{
int32_t out = 0;
for(uint8_t i = filter->taps - 1; i > 0; i--)
filter->samples[i] = filter->samples[i - 1]; //shift old samples
filter->samples[0] = input; //store new sample
for(uint8_t i = 0; i < filter->taps; i++)
{
out += (int32_t)filter->coeffs[i] * filter->samples[i];
}
return out >> filter->gainShift;
}
#else
typedef int32_t sample_t;
typedef int16_t coeff_t;
/**
* @brief BPF filter with 2200 Hz tone 6 dB preemphasis (it actually attenuates 1200 Hz tone by 6 dB)
*/
static const int16_t bpf1200[8] =
static const coeff_t bpf1200[8] =
{
728,
-13418,
@ -123,7 +78,7 @@ static const int16_t bpf1200[8] =
/**
* @brief BPF filter with 2200 Hz tone 6 dB deemphasis
*/
static const int16_t bpf1200Inv[8] =
static const coeff_t bpf1200Inv[8] =
{
-10513,
-10854,
@ -136,7 +91,7 @@ static const int16_t bpf1200Inv[8] =
};
//fs=9600, rectangular, fc1=1500, fc2=1900, 0 dB @ 1600 Hz and 1800 Hz, N = 15, gain 65536
static const int16_t bpf300[15] =
static const coeff_t bpf300[15] =
{
186, 8887, 8184, -1662, -10171, -8509, 386, 5394, 386, -8509, -10171, -1662, 8184, 8887, 186,
};
@ -144,14 +99,14 @@ static const int16_t bpf300[15] =
#define BPF_MAX_TAPS 15
//fs=9600 Hz, raised cosine, fc=300 Hz (BR=600 Bd), beta=0.8, N=14, gain=65536
static const int16_t lpf300[14] =
static const coeff_t lpf300[14] =
{
4385, 4515, 4627, 4720, 4793, 4846, 4878, 4878, 4846, 4793, 4720, 4627, 4515, 4385,
};
//I don't remember what are this filter parameters,
//but it seems to be the best among all I have tested
static const int16_t lpf1200[15] =
static const coeff_t lpf1200[15] =
{
-6128,
-5974,
@ -172,23 +127,32 @@ static const int16_t lpf1200[15] =
//fs=38400 Hz, Gaussian, fc=4800 Hz (9600 Bd), N=9, gain=65536
//seems like there is almost no difference between N=9 and any higher order
static const int16_t lpf9600[9] = {497, 2360, 7178, 13992, 17478, 13992, 7178, 2360, 497};
static const coeff_t lpf9600[9] = {497, 2360, 7178, 13992, 17478, 13992, 7178, 2360, 497};
#define LPF_MAX_TAPS 15
#define FILTER_MAX_TAPS ((LPF_MAX_TAPS > BPF_MAX_TAPS) ? LPF_MAX_TAPS : BPF_MAX_TAPS)
#endif
struct Filter
{
const int16_t *coeffs;
uint8_t taps;
int32_t samples[FILTER_MAX_TAPS];
uint8_t gainShift;
const coeff_t *coeffs; /**< Filer coefficients */
uint8_t taps; /**< Filter length */
sample_t samples[FILTER_MAX_TAPS]; /** Previous input samples */
uint8_t gainShift; /**< Shift to be applied to output samples, unused on FPU */
};
static int32_t filter(struct Filter *filter, int32_t input)
/**
* @brief Perform filtering using FIR filter
* @param *filter FIR filter
* @param Input sample
* @return Filtered sample
*/
static sample_t filter(struct Filter *filter, sample_t input)
{
int32_t out = 0;
sample_t out = 0;
for(uint8_t i = filter->taps - 1; i > 0; i--)
filter->samples[i] = filter->samples[i - 1]; //shift old samples
@ -196,11 +160,13 @@ static int32_t filter(struct Filter *filter, int32_t input)
filter->samples[0] = input; //store new sample
for(uint8_t i = 0; i < filter->taps; i++)
{
out += (int32_t)filter->coeffs[i] * filter->samples[i];
out += (sample_t)filter->coeffs[i] * filter->samples[i];
}
return out >> filter->gainShift;
}
return out
#ifndef USE_FPU
>> filter->gainShift;
#endif
;
}
#endif

+ 2
- 0
Core/Inc/fx25.h View File

@ -20,6 +20,8 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#ifndef FX25_H_
#define FX25_H_
#include "defines.h"
#ifdef ENABLE_FX25
#include <stdint.h>


+ 1
- 20
Core/Inc/main.h View File

@ -7,7 +7,7 @@
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
@ -16,25 +16,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
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 -------------------------------------*/


+ 15
- 15
Core/Inc/modem.h View File

@ -47,8 +47,12 @@ enum ModemTxTestMode
struct ModemDemodConfig
{
enum ModemType modem;
uint8_t usePWM : 1; //0 - use R2R, 1 - use PWM
uint8_t flatAudioIn : 1; //0 - normal (deemphasized) audio input, 1 - flat audio (unfiltered) input
uint32_t usePWM : 1; //PWM or DAC is always used, R2R support is dropped - this bit is unused since v.2.2.0
uint32_t flatAudioIn : 1; //0 - normal (deemphasized) audio input, 1 - flat audio (unfiltered) input
uint32_t pttOutput : 1; //AIOC version: 0 - PTT1 at PA1, 1 - PTT2 at PA0; non-AIOC version: don't care
uint32_t attenuator : 1; //AIOC version: 0 - no output attenuation, 1 - 90% attenuation; non-AIOC version: don't care
uint32_t gain : 3; //AIOC version: input gain = (1 << gain); non-AIOC version: don't care
uint32_t txLevel : 7; //TX audio level in %
};
extern struct ModemDemodConfig ModemConfig;
@ -101,19 +105,6 @@ uint8_t ModemDcdState(void);
*/
uint8_t ModemIsTxTestOngoing(void);
/**
* @brief Clear modem RMS counter
* @param number Modem number
*/
void ModemClearRMS(uint8_t number);
/**
* @brief Get RMS value for modem
* @param number Modem number
* @return RMS value
*/
uint16_t ModemGetRMS(uint8_t number);
/**
* @brief Start or restart TX test mode
* @param type TX test type: TEST_MARK, TEST_SPACE or TEST_ALTERNATING
@ -139,6 +130,15 @@ void ModemTransmitStart(void);
*/
void ModemTransmitStop(void);
/**
* @brief Apply RX gain setting (if applicable)
*/
void ModemApplyRxGain(void);
/**
* @brief Apply TX attenuator (if applicable)
*/
void ModemApplyTxAttenuator(void);
/**
* @brief Initialize modem module


+ 1
- 1
Core/Inc/stm32f1xx_it.h View File

@ -6,7 +6,7 @@
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file


+ 0
- 44
Core/Inc/systick.h View File

@ -1,44 +0,0 @@
/*
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/>.
*/
#ifndef SYSTICK_H_
#define SYSTICK_H_
#include <stdint.h>
#define SYSTICK_FREQUENCY 100 //systick frequency in Hz
#define SYSTICK_INTERVAL (1000 / SYSTICK_FREQUENCY) //systick interval in milliseconds
/**
* @brief Initialize SysTick
*/
void SysTickInit(void);
/**
* @brief Get current SysTick counter value
* @return Current SysTick counter value
*/
uint32_t SysTickGet(void);
/**
* @brief Execute a blocking delay
* @param ms Time in milliseconds
*/
void Delay(uint32_t ms);
#endif /* SYSTICK_H_ */

+ 12
- 6
Core/Inc/uart.h View File

@ -1,5 +1,5 @@
/*
Copyright 2020-2024 Piotr Wilkon
Copyright 2020-2025 Piotr Wilkon
This file is part of VP-Digi.
@ -25,13 +25,12 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include "ax25.h"
#include "drivers/uart_ll.h"
#define UART_BUFFER_SIZE 130
enum UartMode
{
MODE_KISS,
MODE_TERM,
MODE_MONITOR,
MODE_CALIBRATION,
};
enum UartDataType
@ -44,8 +43,12 @@ enum UartDataType
typedef struct
{
struct
{
uint32_t baudrate; //baudrate 1200-115200
enum UartMode defaultMode;
} config;
volatile USART_TypeDef *port; //UART peripheral
uint32_t baudrate; //baudrate 1200-115200
volatile enum UartDataType rxType; //rx status
uint8_t enabled : 1;
uint8_t isUsb : 1;
@ -55,7 +58,6 @@ typedef struct
volatile uint16_t txBufferHead, txBufferTail;
volatile uint8_t txBufferFull : 1;
enum UartMode mode;
enum UartMode defaultMode;
volatile uint16_t lastRxBufferHead; //for special characters handling
volatile uint8_t kissBuffer[AX25_FRAME_MAX_SIZE + 1];
volatile uint16_t kissBufferHead;
@ -64,7 +66,11 @@ typedef struct
volatile uint16_t kissTempBufferHead;
} Uart;
extern Uart Uart1, Uart2, UartUsb;
#ifdef BLUE_PILL
extern Uart Uart1;
extern Uart Uart2;
#endif
extern Uart UartUsb;
/**


+ 9
- 6
Core/Src/ax25.c View File

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 Piotr Wilkon
Copyright 2020-2025 Piotr Wilkon
This file is part of VP-Digi.
@ -24,7 +24,6 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include "common.h"
#include <stdbool.h>
#include <string.h>
#include "systick.h"
#include "digipeater.h"
struct Ax25ProtoConfig Ax25Config;
@ -33,7 +32,11 @@ struct Ax25ProtoConfig Ax25Config;
#include "fx25.h"
#endif
#define FRAME_MAX_COUNT (10) //max count of frames in buffer
#ifdef AIOC
#define FRAME_MAX_COUNT (15)
#else
#define FRAME_MAX_COUNT (9) //max count of frames in buffer
#endif
#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
@ -900,7 +903,7 @@ void Ax25TransmitBuffer(void)
if((txFrameHead != txFrameTail) || txFrameBufferFull)
{
txQuiet = (SysTickGet() + (Ax25Config.quietTime / SYSTICK_INTERVAL) + Random(0, 200 / SYSTICK_INTERVAL)); //calculate required delay
txQuiet = (HAL_GetTick() + (Ax25Config.quietTime / SYSTICK_INTERVAL) + Random(0, 200 / SYSTICK_INTERVAL)); //calculate required delay
txInitStage = TX_INIT_WAITING;
}
}
@ -936,7 +939,7 @@ void Ax25TransmitCheck(void)
if(ModemIsTxTestOngoing()) //TX test is enabled, wait for now
return;
if(txQuiet < SysTickGet()) //quit time has elapsed
if(txQuiet < HAL_GetTick()) //quit time has elapsed
{
if(!ModemDcdState()) //channel is free
{
@ -954,7 +957,7 @@ void Ax25TransmitCheck(void)
}
else //still trying
{
txQuiet = SysTickGet() + Random(100 / SYSTICK_INTERVAL, 500 / SYSTICK_INTERVAL); //try again after some random time
txQuiet = HAL_GetTick() + Random(100 / SYSTICK_INTERVAL, 500 / SYSTICK_INTERVAL); //try again after some random time
txRetries++;
}
}


+ 24
- 22
Core/Src/beacon.c View File

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 Piotr Wilkon
Copyright 2020-2025 Piotr Wilkon
This file is part of VP-Digi.
@ -21,23 +21,25 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include "digipeater.h"
#include "common.h"
#include <string.h>
#include <systick.h>
#include "ax25.h"
#include "terminal.h"
struct Beacon beacon[8];
struct Beacon BeaconConfig[BEACON_COUNT];
static uint32_t beaconDelay[8] = {0};
static uint8_t buf[150]; //frame buffer
/**
* @brief Send specified beacon
* @param[in] no Beacon number (0-7)
* @brief Send specified BeaconConfig
* @param[in] no BeaconConfig number (0-7)
*/
void BeaconSend(uint8_t number)
{
if(beacon[number].enable == 0)
return; //beacon disabled
if(number >= BEACON_COUNT)
return;
if(BeaconConfig[number].enable == 0)
return; //BeaconConfig disabled
uint16_t idx = 0;
@ -49,13 +51,13 @@ void BeaconSend(uint8_t number)
buf[idx++] = ((GeneralConfig.callSsid << 1) + 0b01100000); //add source ssid
if(beacon[number].path[0] > 0) //this beacon has some path set
if(BeaconConfig[number].path[0] > 0) //this BeaconConfig has some path set
{
for(uint8_t i = 0; i < 14; i++) //loop through path
{
if((beacon[number].path[i] > 0) || (i == 6) || (i == 13)) //normal data, not a NULL symbol
if((BeaconConfig[number].path[i] > 0) || (i == 6) || (i == 13)) //normal data, not a NULL symbol
{
buf[idx] = beacon[number].path[i]; //copy path
buf[idx] = BeaconConfig[number].path[i]; //copy path
if((i == 6) || (i == 13)) //it was and ssid
{
buf[idx] = ((buf[idx] << 1) + 0b01100000); //add appropriate bits for ssid
@ -69,9 +71,9 @@ void BeaconSend(uint8_t number)
buf[idx - 1] |= 1; //add c-bit on the last element
buf[idx++] = 0x03; //control
buf[idx++] = 0xF0; //pid
for(uint8_t i = 0; i < strlen((char*)beacon[number].data); i++)
for(uint8_t i = 0; i < strlen((char*)BeaconConfig[number].data); i++)
{
buf[idx++] = beacon[number].data[i]; //copy beacon comment
buf[idx++] = BeaconConfig[number].data[i]; //copy BeaconConfig comment
}
void *handle = NULL;
@ -95,20 +97,20 @@ void BeaconSend(uint8_t number)
}
/**
* @brief Check if any beacon should be transmitted and transmit if necessary
* @brief Check if any BeaconConfig should be transmitted and transmit if necessary
*/
void BeaconCheck(void)
{
for(uint8_t i = 0; i < 8; i++)
for(uint8_t i = 0; i < BEACON_COUNT; i++)
{
if(beacon[i].enable == 0)
if(BeaconConfig[i].enable == 0)
continue;
if((beacon[i].interval > 0) && ((SysTickGet() >= beacon[i].next) || (beacon[i].next == 0)))
if((BeaconConfig[i].interval > 0) && ((HAL_GetTick() >= BeaconConfig[i].next) || (BeaconConfig[i].next == 0)))
{
if(beaconDelay[i] > SysTickGet()) //check for beacon delay (only for the very first transmission)
if(beaconDelay[i] > HAL_GetTick()) //check for BeaconConfig delay (only for the very first transmission)
continue;
beacon[i].next = SysTickGet() + beacon[i].interval; //save next beacon timestamp
BeaconConfig[i].next = HAL_GetTick() + BeaconConfig[i].interval; //save next BeaconConfig timestamp
beaconDelay[i] = 0;
BeaconSend(i);
}
@ -117,14 +119,14 @@ void BeaconCheck(void)
/**
* @brief Initialize beacon module
* @brief Initialize BeaconConfig module
*/
void BeaconInit(void)
{
for(uint8_t i = 0; i < 8; i++)
for(uint8_t i = 0; i < BEACON_COUNT; i++)
{
beaconDelay[i] = (beacon[i].delay * SYSTICK_FREQUENCY) + SysTickGet() + (30000 / SYSTICK_INTERVAL); //set delay for beacons and add constant 30 seconds of delay
beacon[i].next = 0;
beaconDelay[i] = (BeaconConfig[i].delay * SYSTICK_FREQUENCY) + HAL_GetTick() + (30000 / SYSTICK_INTERVAL); //set delay for beacons and add constant 30 seconds of delay
BeaconConfig[i].next = 0;
}
}

+ 33
- 3
Core/Src/common.c View File

@ -1,5 +1,5 @@
/*
Copyright 2020-2024 Piotr Wilkon
Copyright 2020-2025 Piotr Wilkon
This file is part of VP-Digi.
@ -23,18 +23,25 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include "ax25.h"
#include "usbd_cdc_if.h"
struct _GeneralConfig GeneralConfig =
struct GeneralConfig GeneralConfig =
{
.call = {'N' << 1, '0' << 1, 'C' << 1, 'A' << 1, 'L' << 1, 'L' << 1},
.callSsid = 0,
#ifdef BLUE_PILL
.dest = {130, 160, 156, 172, 96, 98, 96}, //destination address: APNV01-0 by default. SSID MUST remain 0.
#elif defined(AIOC)
.dest = {130, 160, 156, 172, 96, 100, 96}, //destination address: APNV02-0 by default. SSID MUST remain 0.
#endif
.kissMonitor = 0,
};
const char versionString[] = "VP-Digi v. 2.1.0\r\nThe open-source standalone APRS digipeater controller and KISS TNC\r\n"
const char versionString[] = "VP-Digi v. 2.2.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
#ifdef AIOC
"Built for AIOC\r\n"
#endif
;
@ -169,10 +176,12 @@ void SendTNC2(uint8_t *from, uint16_t len)
{
if(UartUsb.mode == MODE_MONITOR)
sendTNC2ToUart(&UartUsb, from, len);
#ifdef BLUE_PILL
if(Uart1.mode == MODE_MONITOR)
sendTNC2ToUart(&Uart1, from, len);
if(Uart2.mode == MODE_MONITOR)
sendTNC2ToUart(&Uart2, from, len);
#endif
}
uint32_t Crc32(uint32_t crc0, uint8_t *s, uint64_t n)
@ -256,4 +265,25 @@ bool ParseSsid(const char *in, uint16_t size, uint8_t *out)
return false;
}
extern volatile uint32_t uwTick;
void HAL_IncTick(void)
{
++uwTick;
}
void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay / SYSTICK_INTERVAL;
/* Add freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
++wait;
}
while((HAL_GetTick() - tickstart) < wait)
{
}
}

+ 163
- 155
Core/Src/config.c View File

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 Piotr Wilkon
Copyright 2020-2025 Piotr Wilkon
This file is part of VP-Digi.
@ -25,15 +25,62 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include "digipeater.h"
#include "ax25.h"
#include "beacon.h"
#include "stm32f1xx.h"
#include "drivers/config_ll.h"
#define CONFIG_ADDRESS 0x800F000
#define CONFIG_PAGE_COUNT 2
#define CONFIG_PAGE_SIZE 1024 //1024 words (2048 bytes)
/**
* @brief Read string from configuration space
* @param address Relative address
* @param *data Output string pointer
* @param len Number of bytes
*/
static void readString(uint32_t address, void *data, uint16_t len)
{
uint8_t *d = data;
uint16_t i = 0;
for(; i < len; i += 2)
{
uint16_t t = read(address + i);
d[i] = t & 0xFF;
d[i + 1] = t >> 8;
}
if(len & 1)
{
d[i] = read(address + i);
}
}
#define CONFIG_FLAG_WRITTEN 0x6B
/**
* @brief Write string to configuration space
* @param address Relative address
* @param *data Input string pointer
* @param len Number of bytes
*/
static void writeString(uint32_t address, const void *data, uint16_t len)
{
const uint8_t *d = data;
uint16_t i = 0;
for(; i < len; i += 2)
{
write(address + i, (uint16_t)d[i] | ((uint16_t)d[i + 1] << 8));
}
if(len & 1)
{
write(address + i, d[i]);
}
}
void ConfigErase(void)
{
unlock();
erase();
lock();
}
#ifdef BLUE_PILL
#define CONFIG_FLAG_WRITTEN 0x6B
//these are relative addresses, absolute address is calculated as relative address + MEM_CONFIG
//all fields are 16-bit or n*16-bit long, as data in flash is stored in 16-bit words
#define CONFIG_FLAG 0 //configuration written flag
@ -91,7 +138,7 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#define CONFIG_DIGISSID5 1198
#define CONFIG_DIGISSID6 1200
#define CONFIG_DIGISSID7 1202
#define CONFIG_PWM_FLAT 1204
#define CONFIG_PWM_FLAT_PTT 1204
#define CONFIG_KISSMONITOR 1206
#define CONFIG_DEST 1208
#define CONFIG_ALLOWNONAPRS 1214
@ -100,143 +147,52 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#define CONFIG_MODE_USB 1220
#define CONFIG_MODE_UART1 1222
#define CONFIG_MODE_UART2 1224
#define CONFIG_XXX 1226 //next address (not used)
/**
* @brief Write word to configuration part in flash
* @param[in] address Relative address
* @param[in] data Data to write
* @warning Flash must be unlocked first
*/
static void write(uint32_t address, uint16_t data)
{
FLASH->CR |= FLASH_CR_PG; //programming mode
*((volatile uint16_t*)(address + CONFIG_ADDRESS)) = data; //store data
while (FLASH->SR & FLASH_SR_BSY);; //wait for completion
if(!(FLASH->SR & FLASH_SR_EOP)) //an error occurred
FLASH->CR &= ~FLASH_CR_PG;
else
FLASH->SR |= FLASH_SR_EOP;
}
/**
* @brief Write data array to configuration part in flash
* @param[in] address Relative address
* @param[in] *data Data to write
* @param[in] len Data length
* @warning Flash must be unlocked first
*/
static void writeString(uint32_t address, uint8_t *data, uint16_t len)
{
uint16_t i = 0;
for(; i < (len / 2); i++)
{
write(address + (i << 1), *(data + (i << 1)) | (*(data + 1 + (i << 1)) << 8)); //program memory
}
if((len % 2) > 0)
{
write(address + (i << 1), *(data + (i << 1))); //store last byte if number of bytes is odd
}
}
/**
* @brief Read single word from configuration part in flash
* @param[in] address Relative address
* @return Data (word)
*/
static uint16_t read(uint32_t address)
{
return *(volatile uint16_t*)((address + CONFIG_ADDRESS));
}
#define CONFIG_TX_LEVEL 1226
#define CONFIG_XXX 1228 //next address (not used)
/**
* @brief Read array from configuration part in flash
* @param[in] address Relative address
* @param[out] *data Data
* @param[in] len Byte count
*/
static void readString(uint32_t address, uint8_t *data, uint16_t len)
{
uint16_t i = 0;
for(; i < (len >> 1); i++)
{
*(data + (i << 1)) = (uint8_t)read(address + (i << 1));
*(data + 1 + (i << 1)) = (uint8_t)read(address + 1 + (i << 1));
}
if((len % 2) > 0)
{
*(data + (i << 1)) = (uint8_t)read(address + (i << 1));
}
}
void ConfigErase(void)
{
FLASH->KEYR = 0x45670123; //unlock memory
FLASH->KEYR = 0xCDEF89AB;
while (FLASH->SR & FLASH_SR_BSY)
;
FLASH->CR |= FLASH_CR_PER; //erase mode
for(uint8_t i = 0; i < CONFIG_PAGE_COUNT; i++)
{
FLASH->AR = CONFIG_ADDRESS + (CONFIG_PAGE_SIZE * i);
FLASH->CR |= FLASH_CR_STRT; //start erase
while (FLASH->SR & FLASH_SR_BSY)
;
if(!(FLASH->SR & FLASH_SR_EOP))
{
FLASH->CR &= ~FLASH_CR_PER;
return;
}
else
FLASH->SR |= FLASH_SR_EOP;
}
FLASH->CR &= ~FLASH_CR_PER;
}
/**
* @brief Store configuration from RAM to Flash
*/
void ConfigWrite(void)
{
ConfigErase();
unlock();
writeString(CONFIG_CALL, GeneralConfig.call, 6);
write(CONFIG_SSID, GeneralConfig.callSsid);
writeString(CONFIG_DEST, GeneralConfig.dest, 6);
write(CONFIG_TXDELAY, Ax25Config.txDelayLength);
write(CONFIG_TXTAIL, Ax25Config.txTailLength);
write(CONFIG_TXQUIET, Ax25Config.quietTime);
writeString(CONFIG_RS1BAUD, (uint8_t*)&Uart1.baudrate, 4);
writeString(CONFIG_RS2BAUD, (uint8_t*)&Uart2.baudrate, 4);
write(CONFIG_BEACONS, (beacon[0].enable > 0) | ((beacon[1].enable > 0) << 1) | ((beacon[2].enable > 0) << 2) | ((beacon[3].enable > 0) << 3) | ((beacon[4].enable > 0) << 4) | ((beacon[5].enable > 0) << 5) | ((beacon[6].enable > 0) << 6) | ((beacon[7].enable > 0) << 7));
#ifdef BLUE_PILL
writeString(CONFIG_RS1BAUD, (uint8_t*)&Uart1.config.baudrate, 4);
write(CONFIG_MODE_UART1, Uart1.config.defaultMode);
writeString(CONFIG_RS2BAUD, (uint8_t*)&Uart2.config.baudrate, 4);
write(CONFIG_MODE_UART2, Uart2.config.defaultMode);
#endif
write(CONFIG_BEACONS, (BeaconConfig[0].enable > 0) | ((BeaconConfig[1].enable > 0) << 1) | ((BeaconConfig[2].enable > 0) << 2) | ((BeaconConfig[3].enable > 0) << 3) | ((BeaconConfig[4].enable > 0) << 4) | ((BeaconConfig[5].enable > 0) << 5) | ((BeaconConfig[6].enable > 0) << 6) | ((BeaconConfig[7].enable > 0) << 7));
for(uint8_t s = 0; s < 8; s++)
{
write(CONFIG_BCIV + (2 * s), beacon[s].interval / 6000);
write(CONFIG_BCIV + (2 * s), BeaconConfig[s].interval / 6000);
}
for(uint8_t s = 0; s < 8; s++)
{
write(CONFIG_BCDL + (2 * s), beacon[s].delay / 60);
write(CONFIG_BCDL + (2 * s), BeaconConfig[s].delay / 60);
}
writeString(CONFIG_BC0, beacon[0].data, 100);
writeString(CONFIG_BC1, beacon[1].data, 100);
writeString(CONFIG_BC2, beacon[2].data, 100);
writeString(CONFIG_BC3, beacon[3].data, 100);
writeString(CONFIG_BC4, beacon[4].data, 100);
writeString(CONFIG_BC5, beacon[5].data, 100);
writeString(CONFIG_BC6, beacon[6].data, 100);
writeString(CONFIG_BC7, beacon[7].data, 100);
writeString(CONFIG_BCP0, beacon[0].path, 14);
writeString(CONFIG_BCP1, beacon[1].path, 14);
writeString(CONFIG_BCP2, beacon[2].path, 14);
writeString(CONFIG_BCP3, beacon[3].path, 14);
writeString(CONFIG_BCP4, beacon[4].path, 14);
writeString(CONFIG_BCP5, beacon[5].path, 14);
writeString(CONFIG_BCP6, beacon[6].path, 14);
writeString(CONFIG_BCP7, beacon[7].path, 14);
writeString(CONFIG_BC0, BeaconConfig[0].data, 100);
writeString(CONFIG_BC1, BeaconConfig[1].data, 100);
writeString(CONFIG_BC2, BeaconConfig[2].data, 100);
writeString(CONFIG_BC3, BeaconConfig[3].data, 100);
writeString(CONFIG_BC4, BeaconConfig[4].data, 100);
writeString(CONFIG_BC5, BeaconConfig[5].data, 100);
writeString(CONFIG_BC6, BeaconConfig[6].data, 100);
writeString(CONFIG_BC7, BeaconConfig[7].data, 100);
writeString(CONFIG_BCP0, BeaconConfig[0].path, 14);
writeString(CONFIG_BCP1, BeaconConfig[1].path, 14);
writeString(CONFIG_BCP2, BeaconConfig[2].path, 14);
writeString(CONFIG_BCP3, BeaconConfig[3].path, 14);
writeString(CONFIG_BCP4, BeaconConfig[4].path, 14);
writeString(CONFIG_BCP5, BeaconConfig[5].path, 14);
writeString(CONFIG_BCP6, BeaconConfig[6].path, 14);
writeString(CONFIG_BCP7, BeaconConfig[7].path, 14);
write(CONFIG_DIGION, DigiConfig.enable);
write(CONFIG_DIGIEN, DigiConfig.enableAlias);
write(CONFIG_DIGIVISC, ((uint16_t)DigiConfig.viscous << 8) | (uint16_t)DigiConfig.directOnly);
@ -265,19 +221,17 @@ void ConfigWrite(void)
write(CONFIG_DIGICALLFILEN, DigiConfig.callFilterEnable);
write(CONFIG_DIGIFILTYPE, DigiConfig.filterPolarity);
writeString(CONFIG_DIGIFILLIST, DigiConfig.callFilter[0], sizeof(DigiConfig.callFilter));
write(CONFIG_PWM_FLAT, ModemConfig.usePWM | (ModemConfig.flatAudioIn << 1));
write(CONFIG_PWM_FLAT_PTT, ModemConfig.usePWM | (ModemConfig.flatAudioIn << 1) | (ModemConfig.pttOutput << 2));
write(CONFIG_TX_LEVEL, ModemConfig.txLevel);
write(CONFIG_KISSMONITOR, GeneralConfig.kissMonitor);
write(CONFIG_ALLOWNONAPRS, Ax25Config.allowNonAprs);
write(CONFIG_FX25, Ax25Config.fx25 | (Ax25Config.fx25Tx << 1));
write(CONFIG_MODEM, ModemConfig.modem);
write(CONFIG_MODE_USB, UartUsb.defaultMode);
write(CONFIG_MODE_UART1, Uart1.defaultMode);
write(CONFIG_MODE_UART2, Uart2.defaultMode);
write(CONFIG_MODE_USB, UartUsb.config.defaultMode);
write(CONFIG_FLAG, CONFIG_FLAG_WRITTEN);
FLASH->CR &= ~FLASH_CR_PG;
FLASH->CR |= FLASH_CR_LOCK;
lock();
}
@ -298,33 +252,37 @@ uint8_t ConfigRead(void)
Ax25Config.txDelayLength = read(CONFIG_TXDELAY);
Ax25Config.txTailLength = read(CONFIG_TXTAIL);
Ax25Config.quietTime = read(CONFIG_TXQUIET);
readString(CONFIG_RS1BAUD, (uint8_t*)&Uart1.baudrate, 4);
readString(CONFIG_RS2BAUD, (uint8_t*)&Uart2.baudrate, 4);
#ifdef BLUE_PILL
readString(CONFIG_RS1BAUD, (uint8_t*)&Uart1.config.baudrate, 4);
Uart1.config.defaultMode = read(CONFIG_MODE_UART1);
readString(CONFIG_RS2BAUD, (uint8_t*)&Uart2.config.baudrate, 4);
Uart2.config.defaultMode = read(CONFIG_MODE_UART2);
#endif
uint8_t bce = (uint8_t)read(CONFIG_BEACONS);
beacon[0].enable = (bce & 1) > 0;
beacon[1].enable = (bce & 2) > 0;
beacon[2].enable = (bce & 4) > 0;
beacon[3].enable = (bce & 8) > 0;
beacon[4].enable = (bce & 16) > 0;
beacon[5].enable = (bce & 32) > 0;
beacon[6].enable = (bce & 64) > 0;
beacon[7].enable = (bce & 128) > 0;
for(uint8_t s = 0; s < (sizeof(beacon) / sizeof(*beacon)); s++)
BeaconConfig[0].enable = (bce & 1) > 0;
BeaconConfig[1].enable = (bce & 2) > 0;
BeaconConfig[2].enable = (bce & 4) > 0;
BeaconConfig[3].enable = (bce & 8) > 0;
BeaconConfig[4].enable = (bce & 16) > 0;
BeaconConfig[5].enable = (bce & 32) > 0;
BeaconConfig[6].enable = (bce & 64) > 0;
BeaconConfig[7].enable = (bce & 128) > 0;
for(uint8_t s = 0; s < BEACON_COUNT; s++)
{
beacon[s].interval = read(CONFIG_BCIV + (2 * s)) * 6000;
BeaconConfig[s].interval = read(CONFIG_BCIV + (2 * s)) * 6000;
}
for(uint8_t s = 0; s < (sizeof(beacon) / sizeof(*beacon)); s++)
for(uint8_t s = 0; s < BEACON_COUNT; s++)
{
beacon[s].delay = read(CONFIG_BCDL + (2 * s)) * 60;
BeaconConfig[s].delay = read(CONFIG_BCDL + (2 * s)) * 60;
}
for(uint8_t g = 0; g < (sizeof(beacon) / sizeof(*beacon)); g++)
for(uint8_t g = 0; g < BEACON_COUNT; g++)
{
readString(CONFIG_BC0 + (g * 100), beacon[g].data, 100);
readString(CONFIG_BC0 + (g * 100), BeaconConfig[g].data, 100);
}
for(uint8_t g = 0; g < (sizeof(beacon) / sizeof(*beacon)); g++)
for(uint8_t g = 0; g < BEACON_COUNT; g++)
{
readString(CONFIG_BCP0 + (g * 14), beacon[g].path, 14);
readString(CONFIG_BCP0 + (g * 14), BeaconConfig[g].path, 14);
}
DigiConfig.enable = (uint8_t)read(CONFIG_DIGION);
DigiConfig.enableAlias = (uint8_t)read(CONFIG_DIGIEN);
@ -356,18 +314,68 @@ uint8_t ConfigRead(void)
DigiConfig.callFilterEnable = (uint8_t)read(CONFIG_DIGICALLFILEN);
DigiConfig.filterPolarity = (uint8_t)read(CONFIG_DIGIFILTYPE);
readString(CONFIG_DIGIFILLIST, DigiConfig.callFilter[0], 140);
t = (uint8_t)read(CONFIG_PWM_FLAT);
t = (uint8_t)read(CONFIG_PWM_FLAT_PTT);
ModemConfig.usePWM = t & 1;
ModemConfig.flatAudioIn = (t & 2) > 0;
ModemConfig.pttOutput = (t & 4) > 0;
ModemConfig.txLevel = read(CONFIG_TX_LEVEL);
GeneralConfig.kissMonitor = (read(CONFIG_KISSMONITOR) == 1);
Ax25Config.allowNonAprs = (read(CONFIG_ALLOWNONAPRS) == 1);
t = (uint8_t)read(CONFIG_FX25);
Ax25Config.fx25 = t & 1;
Ax25Config.fx25Tx = (t & 2) > 0;
ModemConfig.modem = read(CONFIG_MODEM);
UartUsb.defaultMode = read(CONFIG_MODE_USB);
Uart1.defaultMode = read(CONFIG_MODE_UART1);
Uart2.defaultMode = read(CONFIG_MODE_UART2);
UartUsb.config.defaultMode = read(CONFIG_MODE_USB);
return 1;
}
#elif defined(AIOC)
#define CONFIG_FLAG_WRITTEN 0xA10C
#define CONFIG_FLAG 0 //configuration written flag
#define CONFIG_GENERAL 8 //GeneralConfig
#define CONFIG_AX25 (CONFIG_GENERAL + 64) //Ax25Config
#define CONFIG_BEACON (CONFIG_AX25 + 64) //BeaconConfig
#define CONFIG_MODEM (CONFIG_BEACON + 144 * 8) //ModemConfig
#define CONFIG_DIGI (CONFIG_MODEM + 256) //DigiConfig
#define CONFIG_USB (CONFIG_DIGI + 64) //UartUsb.config
void ConfigWrite(void)
{
ConfigErase();
unlock();
writeString(CONFIG_GENERAL, &GeneralConfig, sizeof(GeneralConfig));
writeString(CONFIG_AX25, &Ax25Config, sizeof(Ax25Config));
writeString(CONFIG_BEACON, BeaconConfig, sizeof(DigiConfig));
writeString(CONFIG_MODEM, &ModemConfig, sizeof(ModemConfig));
writeString(CONFIG_DIGI, &DigiConfig, sizeof(DigiConfig));
writeString(CONFIG_USB, &(UartUsb.config), sizeof(UartUsb.config));
write(CONFIG_FLAG, CONFIG_FLAG_WRITTEN);
lock();
}
uint8_t ConfigRead(void)
{
if(read(CONFIG_FLAG) != CONFIG_FLAG_WRITTEN) //no configuration stored
{
return 0;
}
readString(CONFIG_GENERAL, &GeneralConfig, sizeof(GeneralConfig));
readString(CONFIG_AX25, &Ax25Config, sizeof(Ax25Config));
readString(CONFIG_BEACON, BeaconConfig, sizeof(DigiConfig));
readString(CONFIG_MODEM, &ModemConfig, sizeof(ModemConfig));
readString(CONFIG_DIGI, &DigiConfig, sizeof(DigiConfig));
readString(CONFIG_USB, &(UartUsb.config), sizeof(UartUsb.config));
return 1;
}
#endif

+ 12
- 7
Core/Src/digipeater.c View File

@ -23,11 +23,10 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include "common.h"
#include "ax25.h"
#include <math.h>
#include <modem.h>
#include <systick.h>
#include "modem.h"
#include "drivers/digipeater_ll.h"
struct _DigiConfig DigiConfig;
struct DigiConfig DigiConfig;
#define VISCOUS_MAX_FRAME_COUNT 10 //max frames in viscous-delay buffer
#define VISCOUS_MAX_FRAME_SIZE 150
@ -87,7 +86,7 @@ static void DigiViscousRefresh(void)
for(uint8_t i = 0; i < VISCOUS_MAX_FRAME_COUNT; i++)
{
if((viscous[i].timeLimit > 0) && (SysTickGet() >= viscous[i].timeLimit)) //it's time to transmit this frame
if((viscous[i].timeLimit > 0) && (HAL_GetTick() >= viscous[i].timeLimit)) //it's time to transmit this frame
{
void *handle = NULL;
if(NULL != (handle = Ax25WriteTxFrame(viscous[i].frame, viscous[i].size)))
@ -285,7 +284,7 @@ static void makeFrame(uint8_t *frame, uint16_t elStart, uint16_t len, uint32_t h
if((alias < 8) && (DigiConfig.viscous & (1 << alias)))
{
viscous[viscousSlot].hash = hash;
viscous[viscousSlot].timeLimit = SysTickGet() + (VISCOUS_HOLD_TIME / SYSTICK_INTERVAL);
viscous[viscousSlot].timeLimit = HAL_GetTick() + (VISCOUS_HOLD_TIME / SYSTICK_INTERVAL);
TermSendToAll(MODE_MONITOR, (uint8_t*)"Saving frame for viscous-delay digipeating\r\n", 0);
}
else
@ -336,7 +335,7 @@ void DigiDigipeat(uint8_t *frame, uint16_t len)
{
if(deDupe[i].hash == hash)
{
if(SysTickGet() < (deDupe[i].timeLimit))
if(HAL_GetTick() < (deDupe[i].timeLimit))
return; //filter out duplicate frame
}
}
@ -465,7 +464,7 @@ void DigiStoreDeDupe(uint8_t *buf, uint16_t size)
deDupeCount %= DEDUPE_SIZE;
deDupe[deDupeCount].hash = hash;
deDupe[deDupeCount].timeLimit = SysTickGet() + (DigiConfig.dupeTime * 1000 / SYSTICK_INTERVAL);
deDupe[deDupeCount].timeLimit = HAL_GetTick() + (DigiConfig.dupeTime * 1000 / SYSTICK_INTERVAL);
deDupeCount++;
}
@ -481,12 +480,18 @@ void DigiUpdateState(void)
if(DigiConfig.enable)
{
if(DIGIPEATER_LL_GET_DISABLE_STATE())
{
DIGIPEATER_LL_LED_OFF();
}
else
{
DIGIPEATER_LL_LED_ON();
}
}
else
{
DIGIPEATER_LL_LED_OFF();
}
DigiViscousRefresh();
}

+ 3
- 2
Core/Src/fx25.c View File

@ -17,11 +17,12 @@ 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 "fx25.h"
#ifdef ENABLE_FX25
#include "fx25.h"
#include <stddef.h>
#include "rs.h"
#include "../../lwfec/rs.h"
#define FX25_RS_FCR 1


+ 155
- 163
Core/Src/main.c View File

@ -1,20 +1,4 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/*
Copyright 2020-2025 Piotr Wilkon
@ -33,15 +17,32 @@ 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/>.
*/
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usb_device.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdint.h>
#include "systick.h"
#include "modem.h"
#include "ax25.h"
#include "digipeater.h"
@ -81,7 +82,6 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
@ -93,7 +93,7 @@ static void MX_GPIO_Init(void);
*/
static void handleFrame(void)
{
uint8_t modemBitmap = Ax25GetReceivedFrameBitmap(); //store states
uint8_t modemBitmap = Ax25GetReceivedFrameBitmap(); // store states
Ax25ClearReceivedFrameBitmap();
uint8_t *buf;
@ -103,68 +103,71 @@ static void handleFrame(void)
uint8_t signalLevel = 0;
uint8_t fixed = 0;
while(Ax25ReadNextRxFrame(&buf, &size, &peak, &valley, &signalLevel, &fixed))
while (Ax25ReadNextRxFrame(&buf, &size, &peak, &valley, &signalLevel, &fixed))
{
TermSendToAll(MODE_KISS, buf, size);
if(((UartUsb.mode == MODE_MONITOR) || (Uart1.mode == MODE_MONITOR) || (Uart2.mode == MODE_MONITOR)))
if ((UartUsb.mode == MODE_MONITOR)
#ifdef BLUE_PILL
|| (Uart1.mode == MODE_MONITOR)
|| (Uart2.mode == MODE_MONITOR)
#endif
)
{
if(signalLevel > 70)
if (signalLevel > 70)
{
TermSendToAll(MODE_MONITOR, (uint8_t*)"\r\nInput level too high! Please reduce so most stations are around 30-50%.\r\n", 0);
TermSendToAll(MODE_MONITOR, (uint8_t *)"\r\nInput level too high! Please reduce so most stations are around 30-50%.\r\n", 0);
}
else if(signalLevel < 5)
else if (signalLevel < 5)
{
TermSendToAll(MODE_MONITOR, (uint8_t*)"\r\nInput level too low! Please increase so most stations are around 30-50%.\r\n", 0);
TermSendToAll(MODE_MONITOR, (uint8_t *)"\r\nInput level too low! Please increase so most stations are around 30-50%.\r\n", 0);
}
TermSendToAll(MODE_MONITOR, (uint8_t*)"(AX.25) Frame received [", 0);
for(uint8_t i = 0; i < ModemGetDemodulatorCount(); i++)
TermSendToAll(MODE_MONITOR, (uint8_t *)"(AX.25) Frame received [", 0);
for (uint8_t i = 0; i < ModemGetDemodulatorCount(); i++)
{
if(modemBitmap & (1 << i))
if (modemBitmap & (1 << i))
{
enum ModemPrefilter m = ModemGetFilterType(i);
switch(m)
switch (m)
{
case PREFILTER_PREEMPHASIS:
TermSendToAll(MODE_MONITOR, (uint8_t*)"P", 1);
break;
case PREFILTER_DEEMPHASIS:
TermSendToAll(MODE_MONITOR, (uint8_t*)"D", 1);
break;
case PREFILTER_FLAT:
TermSendToAll(MODE_MONITOR, (uint8_t*)"F", 1);
break;
case PREFILTER_NONE:
TermSendToAll(MODE_MONITOR, (uint8_t*)"N", 1);
case PREFILTER_PREEMPHASIS:
TermSendToAll(MODE_MONITOR, (uint8_t *)"P", 1);
break;
case PREFILTER_DEEMPHASIS:
TermSendToAll(MODE_MONITOR, (uint8_t *)"D", 1);
break;
case PREFILTER_FLAT:
TermSendToAll(MODE_MONITOR, (uint8_t *)"F", 1);
break;
case PREFILTER_NONE:
TermSendToAll(MODE_MONITOR, (uint8_t *)"N", 1);
}
}
else
TermSendToAll(MODE_MONITOR, (uint8_t*)"_", 1);
TermSendToAll(MODE_MONITOR, (uint8_t *)"_", 1);
}
TermSendToAll(MODE_MONITOR, (uint8_t*)"], ", 0);
if(fixed != AX25_NOT_FX25)
TermSendToAll(MODE_MONITOR, (uint8_t *)"], ", 0);
if (fixed != AX25_NOT_FX25)
{
TermSendNumberToAll(MODE_MONITOR, fixed);
TermSendToAll(MODE_MONITOR, (uint8_t*)" bytes fixed, ", 0);
TermSendToAll(MODE_MONITOR, (uint8_t *)" bytes fixed, ", 0);
}
TermSendToAll(MODE_MONITOR, (uint8_t*)"signal level ", 0);
TermSendToAll(MODE_MONITOR, (uint8_t *)"signal level ", 0);
TermSendNumberToAll(MODE_MONITOR, signalLevel);
TermSendToAll(MODE_MONITOR, (uint8_t*)"% (", 0);
TermSendToAll(MODE_MONITOR, (uint8_t *)"% (", 0);
TermSendNumberToAll(MODE_MONITOR, peak);
TermSendToAll(MODE_MONITOR, (uint8_t*)"%/", 0);
TermSendToAll(MODE_MONITOR, (uint8_t *)"%/", 0);
TermSendNumberToAll(MODE_MONITOR, valley);
TermSendToAll(MODE_MONITOR, (uint8_t*)"%): ", 0);
TermSendToAll(MODE_MONITOR, (uint8_t *)"%): ", 0);
SendTNC2(buf, size);
TermSendToAll(MODE_MONITOR, (uint8_t*)"\r\n", 0);
TermSendToAll(MODE_MONITOR, (uint8_t *)"\r\n", 0);
}
DigiDigipeat(buf, size);
}
}
/* USER CODE END 0 */
/**
@ -173,6 +176,7 @@ static void handleFrame(void)
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
@ -190,32 +194,34 @@ int main(void)
SystemClock_Config();
/* USER CODE BEGIN SysInit */
HAL_SetTickFreq(VPDIGI_HAL_TICK_FREQUENCY);
SysTickInit();
USB_FORCE_REENUMERATION();
USB_FORCE_REENUMERATION();
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USB_DEVICE_Init();
/* USER CODE BEGIN 2 */
WdogInit(); //initialize watchdog
WdogInit(); // initialize watchdog
memset(&beacon, 0, sizeof(beacon));
memset(BeaconConfig, 0, sizeof(BeaconConfig));
memset(&Ax25Config, 0, sizeof(Ax25Config));
memset(&ModemConfig, 0, sizeof(ModemConfig));
memset(&DigiConfig, 0, sizeof(DigiConfig));
//set some initial values in case there is no configuration saved in memory
Uart1.baudrate = 9600;
Uart1.defaultMode = MODE_KISS;
Uart2.baudrate = 9600;
Uart2.defaultMode = MODE_KISS;
UartUsb.defaultMode = MODE_KISS;
ModemConfig.usePWM = 1; //use PWM by default
ModemConfig.flatAudioIn = 0;
// set some initial values in case there is no configuration saved in memory
#ifdef BLUE_PILL
Uart1.config.baudrate = 9600;
Uart1.config.defaultMode = MODE_KISS;
Uart2.config.baudrate = 9600;
Uart2.config.defaultMode = MODE_KISS;
#endif
UartUsb.config.defaultMode = MODE_KISS;
ModemConfig.usePWM = 1; //use PWM in non-AIOC versions
ModemConfig.flatAudioIn = 0; //no flat audio
ModemConfig.pttOutput = 0; //primary PTT in AIOC versions
ModemConfig.txLevel = 100; //100% - only in AIOC versions
Ax25Config.quietTime = 300;
Ax25Config.txDelayLength = 300;
Ax25Config.txTailLength = 30;
@ -232,12 +238,14 @@ int main(void)
DigiInitialize();
UartInit(&Uart1, UART_LL_UART1_STRUCTURE, Uart1.baudrate);
UartInit(&Uart2, UART_LL_UART2_STRUCTURE, Uart2.baudrate);
UartInit(&UartUsb, NULL, 1);
#ifdef BLUE_PILL
UartInit(&Uart1, UART_LL_UART1_STRUCTURE, Uart1.config.baudrate);
UartConfig(&Uart1, 1);
UartInit(&Uart2, UART_LL_UART2_STRUCTURE, Uart2.config.baudrate);
UartConfig(&Uart2, 1);
#endif
UartInit(&UartUsb, NULL, 1);
UartConfig(&UartUsb, 1);
BeaconInit();
@ -245,78 +253,80 @@ int main(void)
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
WdogReset();
if(Ax25GetReceivedFrameBitmap())
handleFrame();
DigiUpdateState(); //update digipeater state
Ax25TransmitBuffer(); //transmit buffer (will return if nothing to be transmitted)
Ax25TransmitCheck(); //check for pending transmission request
if(UartUsb.rxType != DATA_NOTHING)
{
TermHandleSpecial(&UartUsb);
if(UartUsb.rxType == DATA_KISS)
{
KissProcess(&UartUsb);
}
else if(UartUsb.rxType != DATA_USB)
{
TermParse(&UartUsb);
UartClearRx(&UartUsb);
}
//previously there was just UartUsb.rxType = DATA_NOTHING, which could introduce deadlocks
//assume that we were here, because rxType was DATA_USB, but in the meantime a new USB interrupt fired and rxType changed to DATA_KISS,
//and the KISS parsing handler would set kissProcessingOngoing to 1. Then, we change rxType to DATA_NOTHING and are left in an incorrect scenario, when
//rxType is DATA_NOTHING, and kissProcessingOngoing is 1, which is a clear deadlock, because then DATA_KISS can never be reached again.
//That's why it is necessary to disable interrupts and check one more time for rxType before clearing it
else
{
__disable_irq();
if(UartUsb.rxType == DATA_USB)
UartUsb.rxType = DATA_NOTHING;
__enable_irq();
}
}
if(Uart1.rxType != DATA_NOTHING)
{
if(Uart1.rxType == DATA_KISS)
{
KissProcess(&Uart1);
}
else
{
TermParse(&Uart1);
UartClearRx(&Uart1);
}
}
if(Uart2.rxType != DATA_NOTHING)
{
if(Uart2.rxType == DATA_KISS)
{
KissProcess(&Uart2);
}
else
{
TermParse(&Uart2);
UartClearRx(&Uart2);
}
}
BeaconCheck(); //check beacons
if(SysTickGet() > 0xFFFFF000) //going to wrap around soon - hard reset the device
NVIC_SystemReset();
}
WdogReset();
if (Ax25GetReceivedFrameBitmap())
handleFrame();
DigiUpdateState(); // update digipeater state
Ax25TransmitBuffer(); // transmit buffer (will return if nothing to be transmitted)
Ax25TransmitCheck(); // check for pending transmission request
if (UartUsb.rxType != DATA_NOTHING)
{
TermHandleSpecial(&UartUsb);
if (UartUsb.rxType == DATA_KISS)
{
KissProcess(&UartUsb);
}
else if (UartUsb.rxType != DATA_USB)
{
TermParse(&UartUsb);
UartClearRx(&UartUsb);
}
// previously there was just UartUsb.rxType = DATA_NOTHING, which could introduce deadlocks
// assume that we were here, because rxType was DATA_USB, but in the meantime a new USB interrupt fired and rxType changed to DATA_KISS,
// and the KISS parsing handler would set kissProcessingOngoing to 1. Then, we change rxType to DATA_NOTHING and are left in an incorrect scenario, when
// rxType is DATA_NOTHING, and kissProcessingOngoing is 1, which is a clear deadlock, because then DATA_KISS can never be reached again.
// That's why it is necessary to disable interrupts and check one more time for rxType before clearing it
else
{
__disable_irq();
if (UartUsb.rxType == DATA_USB)
UartUsb.rxType = DATA_NOTHING;
__enable_irq();
}
}
#ifdef BLUE_PILL
if (Uart1.rxType != DATA_NOTHING)
{
if (Uart1.rxType == DATA_KISS)
{
KissProcess(&Uart1);
}
else
{
TermParse(&Uart1);
UartClearRx(&Uart1);
}
}
if (Uart2.rxType != DATA_NOTHING)
{
if (Uart2.rxType == DATA_KISS)
{
KissProcess(&Uart2);
}
else
{
TermParse(&Uart2);
UartClearRx(&Uart2);
}
}
#endif
BeaconCheck(); // check beacons
if (HAL_GetTick() > 0xFFFFF000) // going to wrap around soon - hard reset the device
NVIC_SystemReset();
}
/* USER CODE END 3 */
}
@ -366,24 +376,6 @@ void SystemClock_Config(void)
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
@ -395,11 +387,11 @@ static void MX_GPIO_Init(void)
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
@ -414,8 +406,8 @@ void Error_Handler(void)
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

+ 120
- 167
Core/Src/modem.c View File

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 Piotr Wilkon
Copyright 2020-2025 Piotr Wilkon
This file is part of VP-Digi.
VP-Digi is free software: you can redistribute it and/or modify
@ -23,8 +23,8 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include <stdbool.h>
#include "ax25.h"
#include "common.h"
#include "systick.h"
#include "drivers/modem_ll.h"
#include "filter.h"
/*
* Configuration for PLL-based data carrier detection
@ -86,13 +86,12 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#define PLL_TUNE_BITS 8 //number of bits when tuning PLL to avoid floating point operations
struct ModemDemodConfig ModemConfig;
static uint8_t N; //samples per symbol
static enum ModemTxTestMode txTestState; //current TX test mode
static uint8_t demodCount; //actual number of parallel demodulators
static uint16_t dacSine[DAC_SINE_SIZE]; //sine samples for DAC
static sample_t dacSine[DAC_SINE_SIZE]; //sine samples for DAC
static uint8_t dacSineIdx; //current sine sample index
static volatile uint16_t samples[MODEM_LL_OVERSAMPLING_FACTOR]; //very raw received samples, filled directly by DMA
static uint8_t currentSymbol; //current symbol for NRZI encoding
@ -103,90 +102,11 @@ static float baudRate; //baudrate
static uint8_t markStep; //mark timer step
static uint8_t spaceStep; //space timer step
static uint16_t baudRateStep; //baudrate timer step
static int16_t coeffHiI[NMAX], coeffLoI[NMAX], coeffHiQ[NMAX], coeffLoQ[NMAX]; //correlator IQ coefficients
static sample_t coeffHiI[NMAX], coeffLoI[NMAX], coeffHiQ[NMAX], coeffLoQ[NMAX]; //correlator IQ coefficients
static uint8_t dcd = 0; //multiplexed DCD state from both demodulators
static uint32_t lfsr = 0xFFFFF; //LFSR for 9600 Bd
static int32_t adcBias = 4095; //ADC bias after decimation
/**
* @brief BPF filter with 2200 Hz tone 6 dB preemphasis (it actually attenuates 1200 Hz tone by 6 dB)
*/
static const int16_t bpf1200[8] =
{
728,
-13418,
-554,
19493,
-554,
-13418,
728,
2104
};
/**
* @brief BPF filter with 2200 Hz tone 6 dB deemphasis
*/
static const int16_t bpf1200Inv[8] =
{
-10513,
-10854,
9589,
23884,
9589,
-10854,
-10513,
-879
};
//fs=9600, rectangular, fc1=1500, fc2=1900, 0 dB @ 1600 Hz and 1800 Hz, N = 15, gain 65536
static const int16_t bpf300[15] =
{
186, 8887, 8184, -1662, -10171, -8509, 386, 5394, 386, -8509, -10171, -1662, 8184, 8887, 186,
};
#define BPF_MAX_TAPS 15
//fs=9600 Hz, raised cosine, fc=300 Hz (BR=600 Bd), beta=0.8, N=14, gain=65536
static const int16_t lpf300[14] =
{
4385, 4515, 4627, 4720, 4793, 4846, 4878, 4878, 4846, 4793, 4720, 4627, 4515, 4385,
};
//I don't remember what are this filter parameters,
//but it seems to be the best among all I have tested
static const int16_t lpf1200[15] =
{
-6128,
-5974,
-2503,
4125,
12679,
21152,
27364,
29643,
27364,
21152,
12679,
4125,
-2503,
-5974,
-6128
};
//fs=38400 Hz, Gaussian, fc=4800 Hz (9600 Bd), N=9, gain=65536
//seems like there is almost no difference between N=9 and any higher order
static const int16_t lpf9600[9] = {497, 2360, 7178, 13992, 17478, 13992, 7178, 2360, 497};
#define LPF_MAX_TAPS 15
#define FILTER_MAX_TAPS ((LPF_MAX_TAPS > BPF_MAX_TAPS) ? LPF_MAX_TAPS : BPF_MAX_TAPS)
struct Filter
{
int16_t *coeffs;
uint8_t taps;
int32_t samples[FILTER_MAX_TAPS];
uint8_t gainShift;
};
struct DemodState
{
@ -195,7 +115,7 @@ struct DemodState
enum ModemPrefilter prefilter;
struct Filter bpf;
int16_t correlatorSamples[NMAX];
sample_t correlatorSamples[NMAX];
uint8_t correlatorSamplesIdx;
struct Filter lpf;
@ -215,30 +135,16 @@ struct DemodState
uint16_t dcdDec;
int32_t dcdTune;
int16_t peak;
int16_t valley;
sample_t peak;
sample_t valley;
};
static struct DemodState demodState[MODEM_MAX_DEMODULATOR_COUNT];
static void decode(uint8_t symbol, uint8_t demod);
static int32_t demodulate(int16_t sample, struct DemodState *dem);
static sample_t demodulate(sample_t sample, struct DemodState *dem);
static void setPtt(bool state);
static int32_t filter(struct Filter *filter, int32_t input)
{
int32_t out = 0;
for(uint8_t i = filter->taps - 1; i > 0; i--)
filter->samples[i] = filter->samples[i - 1]; //shift old samples
filter->samples[0] = input; //store new sample
for(uint8_t i = 0; i < filter->taps; i++)
{
out += (int32_t)filter->coeffs[i] * filter->samples[i];
}
return out >> filter->gainShift;
}
float ModemGetBaudrate(void)
{
@ -265,9 +171,15 @@ uint8_t ModemIsTxTestOngoing(void)
void ModemGetSignalLevel(uint8_t modem, int8_t *peak, int8_t *valley, uint8_t *level)
{
#ifndef USE_FPU
*peak = (100 * (int32_t)demodState[modem].peak) >> 12;
*valley = (100 * (int32_t)demodState[modem].valley) >> 12;
*level = (100 * (int32_t)(demodState[modem].peak - demodState[modem].valley)) >> 13;
#else
*peak = 100.f * demodState[modem].peak;
*valley = 100.f * demodState[modem].valley;
*level = 50.f * (demodState[modem].peak - demodState[modem].valley);
#endif
}
enum ModemPrefilter ModemGetFilterType(uint8_t modem)
@ -281,14 +193,10 @@ enum ModemPrefilter ModemGetFilterType(uint8_t modem)
*/
static void setDcd(bool state)
{
if(state)
{
MODEM_LL_DCD_LED_ON();
}
else
{
MODEM_LL_DCD_LED_OFF();
}
if(state)
MODEM_LL_DCD_LED_ON();
else
MODEM_LL_DCD_LED_OFF();
}
static inline uint8_t descramble(uint8_t in)
@ -314,15 +222,17 @@ static inline uint8_t scramble(uint8_t in)
/**
* @brief ISR for demodulator
*/
void MODEM_LL_DMA_INTERRUPT_HANDLER(void) __attribute__ ((interrupt));
void MODEM_LL_DMA_INTERRUPT_HANDLER(void)
{
if(MODEM_LL_DMA_TRANSFER_COMPLETE_FLAG)
{
MODEM_LL_DMA_CLEAR_TRANSFER_COMPLETE_FLAG();
//each sample is 12 bits, output sample is 13 bits
int32_t sample = ((samples[0] + samples[1] + samples[2] + samples[3]) >> 1) - 4095; //calculate input sample (decimation)
//each sample is 12 bits, output sample is 13 bits (non-FPU)
sample_t sample = (sample_t)((int32_t)((samples[0] + samples[1] + samples[2] + samples[3]) >> 1) - adcBias); //calculate input sample (decimation)
#ifdef USE_FPU
sample *= (1.f / 4096.f);
#endif
bool partialDcd = false;
@ -350,20 +260,19 @@ void MODEM_LL_DMA_INTERRUPT_HANDLER(void)
/**
* @brief ISR for pushing DAC samples
*/
void MODEM_LL_DAC_INTERRUPT_HANDLER(void) __attribute__ ((interrupt));
void MODEM_LL_DAC_INTERRUPT_HANDLER(void)
{
MODEM_LL_DAC_TIMER_CLEAR_INTERRUPT_FLAG;
int32_t sample = 0;
sample_t sample;
if(ModemConfig.modem == MODEM_9600)
{
if(ModemConfig.usePWM)
sample = scrambledSymbol ? 256 : 1;
else
sample = scrambledSymbol ? 15 : 0;
#ifdef USE_FPU
sample = (sample_t)(scrambledSymbol ? 1.f : -1.f);
#else
sample = (sample_t)(scrambledSymbol ? MODEM_LL_DAC_MAX : 0);
#endif
sample = filter(&demodState[0].lpf, sample);
}
else
@ -373,22 +282,29 @@ void MODEM_LL_DMA_INTERRUPT_HANDLER(void)
dacSineIdx %= DAC_SINE_SIZE;
}
if(ModemConfig.usePWM)
{
MODEM_LL_PWM_PUT_VALUE(sample);
}
else
{
MODEM_LL_R2R_PUT_VALUE(sample);
}
#ifdef AIOC
sample = (sample * (sample_t)ModemConfig.txLevel) / (sample_t)100;
#endif
int32_t dacSample;
#ifdef USE_FPU
dacSample = ((sample + 1.f) * 0.5f) * (sample_t)MODEM_LL_DAC_MAX;
#else
dacSample = sample;
#endif
if(dacSample < 0)
dacSample = 0;
else if(dacSample > MODEM_LL_DAC_MAX)
dacSample = MODEM_LL_DAC_MAX;
MODEM_LL_DAC_PUT_VALUE(dacSample);
}
/**
* @brief ISR for baudrate generator timer. NRZI encoding is done here.
*/
void MODEM_LL_BAUDRATE_TIMER_INTERRUPT_HANDLER(void) __attribute__ ((interrupt));
void MODEM_LL_BAUDRATE_TIMER_INTERRUPT_HANDLER(void)
{
MODEM_LL_BAUDRATE_TIMER_CLEAR_INTERRUPT_FLAG();
@ -432,14 +348,15 @@ void MODEM_LL_DMA_INTERRUPT_HANDLER(void)
/**
* @brief Demodulate received sample (4x oversampling)
* @param[in] sample Received sample, no more than 13 bits
* @param[in] sample Received sample, not more than 13 bits (non-FPU)
* @param[in] *dem Demodulator state
* @return Current tone (0 or 1)
*/
static int32_t demodulate(int16_t sample, struct DemodState *dem)
static sample_t demodulate(sample_t sample, struct DemodState *dem)
{
//input signal amplitude tracking
if(sample >= dem->peak)
#ifndef USE_FPU
if(sample > dem->peak)
{
dem->peak += (((int32_t)(AMP_TRACKING_ATTACK * (float)32768) * (int32_t)(sample - dem->peak)) >> 15);
}
@ -448,7 +365,7 @@ static int32_t demodulate(int16_t sample, struct DemodState *dem)
dem->peak += (((int32_t)(AMP_TRACKING_DECAY * (float)32768) * (int32_t)(sample - dem->peak)) >> 15);
}
if(sample <= dem->valley)
if(sample < dem->valley)
{
dem->valley -= (((int32_t)(AMP_TRACKING_ATTACK * (float)32768) * (int32_t)(dem->valley - sample)) >> 15);
}
@ -456,6 +373,25 @@ static int32_t demodulate(int16_t sample, struct DemodState *dem)
{
dem->valley -= (((int32_t)(AMP_TRACKING_DECAY * (float)32768) * (int32_t)(dem->valley - sample)) >> 15);
}
#else
if(sample > dem->peak)
{
dem->peak += ((sample_t)AMP_TRACKING_ATTACK * (sample - dem->peak));
}
else
{
dem->peak += ((sample_t)AMP_TRACKING_DECAY * (sample - dem->peak));
}
if(sample < dem->valley)
{
dem->valley -= ((sample_t)AMP_TRACKING_ATTACK * (dem->valley - sample));
}
else
{
dem->valley -= ((sample_t)AMP_TRACKING_DECAY * (dem->valley - sample));
}
#endif
if(ModemConfig.modem != MODEM_9600)
@ -471,23 +407,28 @@ static int32_t demodulate(int16_t sample, struct DemodState *dem)
dem->correlatorSamplesIdx %= N;
int32_t outLoI = 0, outLoQ = 0, outHiI = 0, outHiQ = 0; //output values after correlating
sample_t outLoI = 0, outLoQ = 0, outHiI = 0, outHiQ = 0; //output values after correlating
for(uint8_t i = 0; i < N; i++)
{
int16_t t = dem->correlatorSamples[(dem->correlatorSamplesIdx + i) % N]; //read sample
sample_t t = dem->correlatorSamples[(dem->correlatorSamplesIdx + i) % N]; //read sample
outLoI += t * coeffLoI[i]; //correlate sample
outLoQ += t * coeffLoQ[i];
outHiI += t * coeffHiI[i];
outHiQ += t * coeffHiQ[i];
}
#ifndef USE_FPU
outHiI >>= 14;
outHiQ >>= 14;
outLoI >>= 14;
outLoQ >>= 14;
sample = (abs(outLoI) + abs(outLoQ)) - (abs(outHiI) + abs(outHiQ));
#else
sample = (fabs(outLoI) + fabs(outLoQ)) - (fabs(outHiI) + fabs(outHiQ));
#endif
}
//DCD using "PLL"
@ -690,39 +631,52 @@ void ModemTransmitStop(void)
}
/**
* @brief Controls PTT output
* @brief Control PTT output
* @param state False - PTT off, true - PTT on
*/
static void setPtt(bool state)
{
if(state)
{
MODEM_LL_PTT_ON();
MODEM_LL_PTT_ON(ModemConfig.pttOutput);
}
else
{
MODEM_LL_PTT_OFF();
MODEM_LL_PTT_OFF(ModemConfig.pttOutput);
}
}
/**
* @brief Initialize AFSK module
*/
void ModemApplyRxGain(void)
{
#ifdef AIOC
MODEM_LL_SET_GAIN(1 << ModemConfig.gain);
#endif
}
void ModemApplyTxAttenuator(void)
{
#ifdef AIOC
MODEM_LL_SET_TX_ATTENUATOR(ModemConfig.attenuator);
#endif
}
void ModemInit(void)
{
memset(demodState, 0, sizeof(demodState));
MODEM_LL_INITIALIZE_RCC(); //initialize peripheral clocks
MODEM_LL_INITIALIZE_RCC();
MODEM_LL_INITIALIZE_OUTPUTS();
MODEM_LL_INITIALIZE_ADC();
volatile ADC_TypeDef *adc = MODEM_LL_INITIALIZE_ADC(&adcBias);
MODEM_LL_INITIALIZE_DMA(samples);
MODEM_LL_INITIALIZE_DMA(samples, MODEM_LL_OVERSAMPLING_FACTOR, adc);
NVIC_EnableIRQ(MODEM_LL_DMA_IRQ);
MODEM_LL_INITIALIZE_DAC();
MODEM_LL_ADC_TIMER_INITIALIZE();
MODEM_LL_DAC_TIMER_INITIALIZE();
@ -769,12 +723,12 @@ void ModemInit(void)
demodState[1].dcdTune = DCD1200_TUNE * (float)((uint32_t)1 << PLL_TUNE_BITS);
demodState[1].prefilter = PREFILTER_NONE;
demodState[1].lpf.coeffs = (int16_t*)lpf1200;
demodState[1].lpf.coeffs = lpf1200;
demodState[1].lpf.taps = sizeof(lpf1200) / sizeof(*lpf1200);
demodState[1].lpf.gainShift = 15;
demodState[0].lpf.coeffs = (int16_t*)lpf1200;
demodState[0].lpf.coeffs = lpf1200;
demodState[0].lpf.taps = sizeof(lpf1200) / sizeof(*lpf1200);
demodState[0].lpf.gainShift = 15;
demodState[0].prefilter = PREFILTER_NONE;
@ -787,7 +741,7 @@ void ModemInit(void)
else
#endif
demodState[0].prefilter = PREFILTER_DEEMPHASIS;
demodState[0].bpf.coeffs = (int16_t*)bpf1200Inv;
demodState[0].bpf.coeffs = bpf1200Inv;
demodState[0].bpf.taps = sizeof(bpf1200Inv) / sizeof(*bpf1200Inv);
demodState[0].bpf.gainShift = 15;
@ -795,7 +749,7 @@ void ModemInit(void)
else //when used with normal (filtered) audio input, use flat and preemphasis modems
{
demodState[0].prefilter = PREFILTER_PREEMPHASIS;
demodState[0].bpf.coeffs = (int16_t*)bpf1200;
demodState[0].bpf.coeffs = bpf1200;
demodState[0].bpf.taps = sizeof(bpf1200) / sizeof(*bpf1200);
demodState[0].bpf.gainShift = 15;
}
@ -830,10 +784,10 @@ void ModemInit(void)
demodState[0].dcdTune = DCD300_TUNE * (float)((uint32_t)1 << PLL_TUNE_BITS);
demodState[0].prefilter = PREFILTER_FLAT;
demodState[0].bpf.coeffs = (int16_t*)bpf300;
demodState[0].bpf.coeffs = bpf300;
demodState[0].bpf.taps = sizeof(bpf300) / sizeof(*bpf300);
demodState[0].bpf.gainShift = 16;
demodState[0].lpf.coeffs = (int16_t*)lpf300;
demodState[0].lpf.coeffs = lpf300;
demodState[0].lpf.taps = sizeof(lpf300) / sizeof(*lpf300);
demodState[0].lpf.gainShift = 15;
}
@ -855,7 +809,7 @@ void ModemInit(void)
demodState[0].prefilter = PREFILTER_NONE;
//this filter will be used for RX and TX
demodState[0].lpf.coeffs = (int16_t*)lpf9600;
demodState[0].lpf.coeffs = lpf9600;
demodState[0].lpf.taps = sizeof(lpf9600) / sizeof(*lpf9600);
demodState[0].lpf.gainShift = 16;
@ -874,24 +828,23 @@ void ModemInit(void)
for(uint8_t i = 0; i < N; i++) //calculate correlator coefficients
{
coeffLoI[i] = 4095.f * cosf(2.f * 3.1416f * (float)i / (float)N * markFreq / baudRate);
coeffLoQ[i] = 4095.f * sinf(2.f * 3.1416f * (float)i / (float)N * markFreq / baudRate);
coeffHiI[i] = 4095.f * cosf(2.f * 3.1416f * (float)i / (float)N * spaceFreq / baudRate);
coeffHiQ[i] = 4095.f * sinf(2.f * 3.1416f * (float)i / (float)N * spaceFreq / baudRate);
#ifdef USE_FPU
#define COEFF_SCALE 1.f
#else
#define COEFF_SCALE 4095.f
#endif
coeffLoI[i] = cosf(2.f * 3.1416f * (float)i / (float)N * markFreq / baudRate) * COEFF_SCALE;
coeffLoQ[i] = sinf(2.f * 3.1416f * (float)i / (float)N * markFreq / baudRate) * COEFF_SCALE;
coeffHiI[i] = cosf(2.f * 3.1416f * (float)i / (float)N * spaceFreq / baudRate) * COEFF_SCALE;
coeffHiQ[i] = sinf(2.f * 3.1416f * (float)i / (float)N * spaceFreq / baudRate) * COEFF_SCALE;
}
for(uint8_t i = 0; i < DAC_SINE_SIZE; i++) //calculate DAC sine samples
{
if(ModemConfig.usePWM)
//produce values in range 1 to 256
dacSine[i] = ((sinf(2.f * 3.1416f * (float)i / (float)DAC_SINE_SIZE) + 1.f) * 128.f);
else
dacSine[i] = ((7.f * sinf(2.f * 3.1416f * (float)i / (float)DAC_SINE_SIZE)) + 8.f);
}
if(ModemConfig.usePWM)
{
MODEM_LL_PWM_INITIALIZE();
#if defined(USE_FPU)
dacSine[i] = (sinf(2.f * 3.1416f * (float)i / (float)DAC_SINE_SIZE));
#else
dacSine[i] = ((sinf(2.f * 3.1416f * (float)i / (float)DAC_SINE_SIZE) + 1.f) * ((float)MODEM_LL_DAC_MAX / 2.f));
#endif
}
}

+ 5
- 5
Core/Src/stm32f1xx_it.c View File

@ -6,7 +6,7 @@
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
@ -15,6 +15,7 @@
*
******************************************************************************
*/
#if defined(STM32F103xB) || defined(STM32F103x8)
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
@ -72,7 +73,7 @@ void NMI_Handler(void)
NVIC_SystemReset();
/* USER CODE END NonMaskableInt_IRQn 0 */
/* USER CODE BEGIN NonMaskableInt_IRQn 1 */
while (1)
while (1)
{
}
/* USER CODE END NonMaskableInt_IRQn 1 */
@ -187,8 +188,7 @@ void SysTick_Handler(void)
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
extern volatile uint32_t ticks;
ticks++;
/* USER CODE END SysTick_IRQn 1 */
}
@ -214,5 +214,5 @@ void USB_LP_CAN1_RX0_IRQHandler(void)
}
/* USER CODE BEGIN 1 */
#endif
/* USER CODE END 1 */

+ 232
- 0
Core/Src/stm32f3xx_it.c View File

@ -0,0 +1,232 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file stm32f3xx_it.c
* @brief Interrupt Service Routines.
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#if defined(STM32F302xC)
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f3xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */
/* USER CODE END TD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/* External variables --------------------------------------------------------*/
extern PCD_HandleTypeDef hpcd_USB_FS;
/* USER CODE BEGIN EV */
/* USER CODE END EV */
/******************************************************************************/
/* Cortex-M4 Processor Interruption and Exception Handlers */
/******************************************************************************/
/**
* @brief This function handles Non maskable interrupt.
*/
void NMI_Handler(void)
{
/* USER CODE BEGIN NonMaskableInt_IRQn 0 */
NVIC_SystemReset();
/* USER CODE END NonMaskableInt_IRQn 0 */
/* USER CODE BEGIN NonMaskableInt_IRQn 1 */
while (1)
{
}
/* USER CODE END NonMaskableInt_IRQn 1 */
}
/**
* @brief This function handles Hard fault interrupt.
*/
void HardFault_Handler(void)
{
/* USER CODE BEGIN HardFault_IRQn 0 */
NVIC_SystemReset();
/* USER CODE END HardFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_HardFault_IRQn 0 */
/* USER CODE END W1_HardFault_IRQn 0 */
}
}
/**
* @brief This function handles Memory management fault.
*/
void MemManage_Handler(void)
{
/* USER CODE BEGIN MemoryManagement_IRQn 0 */
NVIC_SystemReset();
/* USER CODE END MemoryManagement_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
/* USER CODE END W1_MemoryManagement_IRQn 0 */
}
}
/**
* @brief This function handles Pre-fetch fault, memory access fault.
*/
void BusFault_Handler(void)
{
/* USER CODE BEGIN BusFault_IRQn 0 */
NVIC_SystemReset();
/* USER CODE END BusFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_BusFault_IRQn 0 */
/* USER CODE END W1_BusFault_IRQn 0 */
}
}
/**
* @brief This function handles Undefined instruction or illegal state.
*/
void UsageFault_Handler(void)
{
/* USER CODE BEGIN UsageFault_IRQn 0 */
NVIC_SystemReset();
/* USER CODE END UsageFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_UsageFault_IRQn 0 */
/* USER CODE END W1_UsageFault_IRQn 0 */
}
}
/**
* @brief This function handles System service call via SWI instruction.
*/
void SVC_Handler(void)
{
/* USER CODE BEGIN SVCall_IRQn 0 */
/* USER CODE END SVCall_IRQn 0 */
/* USER CODE BEGIN SVCall_IRQn 1 */
/* USER CODE END SVCall_IRQn 1 */
}
/**
* @brief This function handles Debug monitor.
*/
void DebugMon_Handler(void)
{
/* USER CODE BEGIN DebugMonitor_IRQn 0 */
/* USER CODE END DebugMonitor_IRQn 0 */
/* USER CODE BEGIN DebugMonitor_IRQn 1 */
/* USER CODE END DebugMonitor_IRQn 1 */
}
/**
* @brief This function handles Pendable request for system service.
*/
void PendSV_Handler(void)
{
/* USER CODE BEGIN PendSV_IRQn 0 */
/* USER CODE END PendSV_IRQn 0 */
/* USER CODE BEGIN PendSV_IRQn 1 */
/* USER CODE END PendSV_IRQn 1 */
}
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
/******************************************************************************/
/* STM32F3xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f3xx.s). */
/******************************************************************************/
/**
* @brief This function handles USB high priority or CAN_TX interrupts.
*/
void USB_HP_CAN_TX_IRQHandler(void)
{
/* USER CODE BEGIN USB_HP_CAN_TX_IRQn 0 */
/* USER CODE END USB_HP_CAN_TX_IRQn 0 */
HAL_PCD_IRQHandler(&hpcd_USB_FS);
/* USER CODE BEGIN USB_HP_CAN_TX_IRQn 1 */
/* USER CODE END USB_HP_CAN_TX_IRQn 1 */
}
/**
* @brief This function handles USB low priority or CAN_RX0 interrupts.
*/
void USB_LP_CAN_RX0_IRQHandler(void)
{
/* USER CODE BEGIN USB_LP_CAN_RX0_IRQn 0 */
/* USER CODE END USB_LP_CAN_RX0_IRQn 0 */
HAL_PCD_IRQHandler(&hpcd_USB_FS);
/* USER CODE BEGIN USB_LP_CAN_RX0_IRQn 1 */
/* USER CODE END USB_LP_CAN_RX0_IRQn 1 */
}
/* USER CODE BEGIN 1 */
#endif
/* USER CODE END 1 */

+ 0
- 46
Core/Src/systick.c View File

@ -1,46 +0,0 @@
/*
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 "stm32f1xx.h"
#include "systick.h"
volatile uint32_t ticks = 0; //SysTick counter
//with HAL enabled, the handler is in stm32f1xx_it.c
//void SysTick_Handler(void)
//{
//ticks++;
//}
void SysTickInit(void)
{
SysTick_Config(SystemCoreClock / SYSTICK_FREQUENCY); //SysTick every 10 ms
}
uint32_t SysTickGet(void)
{
return ticks;
}
void Delay(uint32_t ms)
{
uint32_t target = SysTickGet() + ms / SYSTICK_INTERVAL;
while(target > SysTickGet())
;
}

+ 181
- 74
Core/Src/terminal.c View File

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 Piotr Wilkon
Copyright 2020-2025 Piotr Wilkon
This file is part of VP-Digi.
@ -24,11 +24,62 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include "digipeater.h"
#include "config.h"
#include "ax25.h"
#include "systick.h"
#include "kiss.h"
#include "uart.h"
void TermHandleSpecial(Uart *u)
{
if(MODE_CALIBRATION == u->mode)
{
char c = u->rxBuffer[u->rxBufferHead - 1];
switch(c)
{
case 'l':
ModemTxTestStart(TEST_MARK);
UartSendString(u, "Transmitting mark/low tone\r\n", 0);
break;
case 'h':
ModemTxTestStart(TEST_SPACE);
UartSendString(u, "Transmitting space/high tone\r\n", 0);
break;
case 'a':
ModemTxTestStart(TEST_ALTERNATING);
UartSendString(u, "Transmitting alternating tones\r\n", 0);
break;
case 's':
ModemTxTestStop();
UartSendString(u, "Transmission stopped\r\n", 0);
break;
case 'q':
ModemTxTestStop();
u->mode = MODE_MONITOR;
UartSendString(u, "Left calibration mode\r\n", 0);
break;
#ifdef AIOC
case 'x':
ModemConfig.attenuator ^= 1;
ModemApplyTxAttenuator();
ModemConfig.attenuator ? UartSendString(u, "Attenuator enabled\r\n", 0) : UartSendString(u, "Attenuator disabled\r\n", 0);
break;
case 'n':
case 'm':
if(('n' == c) && (ModemConfig.txLevel > 0))
--ModemConfig.txLevel;
else if(('m' == c) && (ModemConfig.txLevel < 100))
++ModemConfig.txLevel;
UartSendString(u, "TX level set to ", 0);
UartSendNumber(u, ModemConfig.txLevel);
UartSendString(u, "%\r\n", 0);
break;
#endif
default:
UartSendString(u, "Unknown key, use s to stop, q to exit\r\n", 0);
break;
}
UartClearRx(u);
return;
}
if(u->mode == MODE_KISS) //don't do anything in KISS mode
{
u->lastRxBufferHead = 0;
@ -65,18 +116,22 @@ void TermSendToAll(enum UartMode mode, uint8_t *data, uint16_t size)
{
if(MODE_KISS == mode)
{
#ifdef BLUE_PILL
KissSend(&Uart1, data, size);
KissSend(&Uart2, data, size);
#endif
KissSend(&UartUsb, data, size);
}
else if(MODE_MONITOR == mode)
{
if(UartUsb.mode == MODE_MONITOR)
UartSendString(&UartUsb, data, size);
#ifdef BLUE_PILL
if(Uart1.mode == MODE_MONITOR)
UartSendString(&Uart1, data, size);
if(Uart2.mode == MODE_MONITOR)
UartSendString(&Uart2, data, size);
#endif
}
}
@ -86,18 +141,19 @@ void TermSendNumberToAll(enum UartMode mode, int32_t n)
{
if(UartUsb.mode == MODE_MONITOR)
UartSendNumber(&UartUsb, n);
#ifdef BLUE_PILL
if(Uart1.mode == MODE_MONITOR)
UartSendNumber(&Uart1, n);
if(Uart2.mode == MODE_MONITOR)
UartSendNumber(&Uart2, n);
#endif
}
}
static const char monitorHelp[] = "\r\nCommands available in monitor mode:\r\n"
"help - show this help page\r\n"
"cal {low|high|alt|stop} - transmit/stop transmitter calibration pattern\r\n"
"\tlow - transmit MARK tone, high - transmit SPACE tone, alt - transmit alternating tones (null bytes)\r\n"
"cal - enter TX level calibration mode\r\n"
"beacon <beacon_number> - immediately transmit selected beacon (number from 0 to 7)\r\n"
"kiss - switch to KISS mode\r\n"
"config - switch to config mode\r\n"
@ -120,10 +176,18 @@ static const char configHelp[] = "\r\nCommands available in config mode:\r\n"
"txdelay <50-2550> - set TXDelay time (ms)\r\n"
"txtail <10-2550> - set TXTail time (ms)\r\n"
"quiet <100-2550> - set quiet time (ms)\r\n"
"uart <1/2> baud <1200-115200> - set UART baud rate\r\n"
"uart <0/1/2> mode [kiss/monitor/config] - set UART default mode (0 for USB)\r\n"
"pwm [on/off] - enable/disable PWM. If PWM is off, R2R will be used instead\r\n"
"flat [on/off] - set to \"on\" if flat audio input is used\r\n"
#ifdef BLUE_PILL
"uart <0/1/2> mode [kiss/monitor/config] - set UART default mode (0 for USB)\r\n"
"uart <1/2> baud <1200-115200> - set UART baud rate\r\n"
#endif
#ifdef AIOC
"ptt [pri/sec] - select primary or secondary PTT output\r\n"
"att [on/off] - enable/disable TX signal attenuator\r\n"
"level <value> - set TX signal level (%)\r\n"
"gain [1/2/4/8/16] - set RX gain\r\n"
"uart 0 mode [kiss/monitor/config] - set USB default mode\r\n"
#endif
"beacon <0-7> [on/off] - enable/disable specified beacon\r\n"
"beacon <0-7> [iv/dl] <0-720> - set interval/delay for the specified beacon (min)\r\n"
"beacon <0-7> path <el1,[el2]>/none - set path for the specified beacon\r\n"
@ -149,11 +213,11 @@ static void sendUartParams(Uart *output, Uart *uart)
{
if(!uart->isUsb)
{
UartSendNumber(output, uart->baudrate);
UartSendNumber(output, uart->config.baudrate);
UartSendString(output, " baud, ", 0);
}
UartSendString(output, "default mode: ", 0);
switch(uart->defaultMode)
switch(uart->config.defaultMode)
{
case MODE_KISS:
UartSendString(output, "KISS", 0);
@ -164,6 +228,8 @@ static void sendUartParams(Uart *output, Uart *uart)
case MODE_TERM:
UartSendString(output, "configuration", 0);
break;
case MODE_CALIBRATION: //calibration is a submode
break;
}
}
@ -209,60 +275,73 @@ static void printConfig(Uart *src)
UartSendNumber(src, Ax25Config.quietTime);
UartSendString(src, "\r\nUSB: ", 0);
sendUartParams(src, &UartUsb);
#ifdef BLUE_PILL
UartSendString(src, "\r\nUART1: ", 0);
sendUartParams(src, &Uart1);
UartSendString(src, "\r\nUART2: ", 0);
sendUartParams(src, &Uart2);
UartSendString(src, "\r\nDAC type: ", 0);
if(ModemConfig.usePWM)
UartSendString(src, "PWM", 0);
else
UartSendString(src, "R2R", 0);
#endif
UartSendString(src, "\r\nFlat audio input: ", 0);
if(ModemConfig.flatAudioIn)
UartSendString(src, "yes", 0);
else
UartSendString(src, "no", 0);
for(uint8_t i = 0; i < (sizeof(beacon) / sizeof(*beacon)); i++)
#ifdef AIOC
UartSendString(src, "\r\nTX level: ", 0);
UartSendNumber(src, ModemConfig.txLevel);
UartSendString(src, "%, attenuator ", 0);
if(ModemConfig.attenuator)
UartSendString(src, "enabled", 0);
else
UartSendString(src, "disabled", 0);
UartSendString(src, "\r\nRX gain: ", 0);
UartSendNumber(src, 1 << ModemConfig.gain);
UartSendString(src, "\r\nPTT output: ", 0);
if(!ModemConfig.pttOutput)
UartSendString(src, "primary", 0);
else
UartSendString(src, "secondary", 0);
#endif
for(uint8_t i = 0; i < BEACON_COUNT; i++)
{
UartSendString(src, "\r\nBeacon ", 0);
UartSendByte(src, i + '0');
UartSendString(src, ": ", 0);
if(beacon[i].enable)
if(BeaconConfig[i].enable)
UartSendString(src, "On, Iv: ", 0);
else
UartSendString(src, "Off, Iv: ", 0);
UartSendNumber(src, beacon[i].interval / 6000);
UartSendNumber(src, BeaconConfig[i].interval / 6000);
UartSendString(src, ", Dl: ", 0);
UartSendNumber(src, beacon[i].delay / 60);
UartSendNumber(src, BeaconConfig[i].delay / 60);
UartSendByte(src, ',');
UartSendByte(src, ' ');
if(beacon[i].path[0] != 0)
if(BeaconConfig[i].path[0] != 0)
{
for(uint8_t j = 0; j < 6; j++)
{
if(beacon[i].path[j] != (' ' << 1))
UartSendByte(src, beacon[i].path[j] >> 1);
if(BeaconConfig[i].path[j] != (' ' << 1))
UartSendByte(src, BeaconConfig[i].path[j] >> 1);
}
UartSendByte(src, '-');
UartSendNumber(src, beacon[i].path[6]);
if(beacon[i].path[7] != 0)
UartSendNumber(src, BeaconConfig[i].path[6]);
if(BeaconConfig[i].path[7] != 0)
{
UartSendByte(src, ',');
for(uint8_t j = 7; j < 13; j++)
{
if(beacon[i].path[j] != (' ' << 1))
UartSendByte(src, beacon[i].path[j] >> 1);
if(BeaconConfig[i].path[j] != (' ' << 1))
UartSendByte(src, BeaconConfig[i].path[j] >> 1);
}
UartSendByte(src, '-');
UartSendNumber(src, beacon[i].path[13]);
UartSendNumber(src, BeaconConfig[i].path[13]);
}
}
else
UartSendString(src, "no path", 0);
UartSendByte(src, ',');
UartSendByte(src, ' ');
UartSendString(src, beacon[i].data, 0);
UartSendString(src, BeaconConfig[i].data, 0);
}
UartSendString(src, "\r\nDigipeater: ", 0);
@ -361,6 +440,7 @@ static void printConfig(Uart *src)
UartSendString(src, "On\r\n", 0);
else
UartSendString(src, "Off\r\n", 0);
#ifdef ENABLE_FX25
UartSendString(src, "FX.25 protocol: ", 0);
if(Ax25Config.fx25 == 1)
UartSendString(src, "On\r\n", 0);
@ -371,12 +451,13 @@ static void printConfig(Uart *src)
UartSendString(src, "On\r\n", 0);
else
UartSendString(src, "Off\r\n", 0);
#endif
}
static void sendTime(Uart *src)
{
UartSendString(src, "Time since boot: ", 0);
UartSendNumber(src, SysTickGet() * SYSTICK_INTERVAL / 60000); //convert from ms to minutes
UartSendNumber(src, HAL_GetTick() * SYSTICK_INTERVAL / 60000); //convert from ms to minutes
UartSendString(src, " minutes\r\n", 0);
}
@ -446,7 +527,7 @@ void TermParse(Uart *src)
if((cmd[7] >= '0') && (cmd[7] <= '7'))
{
uint8_t number = cmd[7] - '0';
if((beacon[number].interval != 0) && (beacon[number].enable != 0))
if((BeaconConfig[number].interval != 0) && (BeaconConfig[number].enable != 0))
{
BeaconSend(number);
}
@ -464,28 +545,13 @@ void TermParse(Uart *src)
}
else if(!strncmp(cmd, "cal", 3))
{
if(!strncmp(&cmd[4], "low", 3))
{
UartSendString(src, "Starting low tone calibration transmission\r\n", 0);
ModemTxTestStart(TEST_MARK);
}
else if(!strncmp(&cmd[4], "high", 4))
{
UartSendString(src, "Starting high tone calibration transmission\r\n", 0);
ModemTxTestStart(TEST_SPACE);
}
else if(!strncmp(&cmd[4], "alt", 3))
{
UartSendString(src, "Starting alternating tones calibration transmission\r\n", 0);
ModemTxTestStart(TEST_ALTERNATING);
}
else if(!strncmp(&cmd[4], "stop", 4))
{
UartSendString(src, "Stopping calibration transmission\r\n", 0);
ModemTxTestStop();
}
else
UartSendString(src, "Usage: cal {low|high|alt|stop}\r\n", 0);
UartSendString(src, "Entered calibration mode\r\n", 0);
UartSendString(src, "l - low tone, h - high tone, a - alternating tones, s - stop, q - exit\r\n", 0);
#ifdef AIOC
UartSendString(src, "n - decrease TX level, m - increase TX level, x - enable/disable attenuator\r\n", 0);
#endif
UartSendString(src, "Values must be saved by switching to the configuration mode and using \"save\"!\r\n", 0);
src->mode = MODE_CALIBRATION;
}
else
UartSendString(src, "Unknown command. For command list type \"help\"\r\n", 0);
@ -680,12 +746,14 @@ void TermParse(Uart *src)
else if(!strncmp(cmd, "uart", 4))
{
Uart *u = NULL;
if((cmd[5] - '0') == 1)
if((cmd[5] - '0') == 0)
u = &UartUsb;
#ifdef BLUE_PILL
else if((cmd[5] - '0') == 1)
u = &Uart1;
else if((cmd[5] - '0') == 2)
u = &Uart2;
else if((cmd[5] - '0') == 0)
u = &UartUsb;
#endif
else
{
UartSendString(src, "Incorrect UART number!\r\n", 0);
@ -699,21 +767,21 @@ void TermParse(Uart *src)
UartSendString(src, "Incorrect baud rate!\r\n", 0);
return;
}
u->baudrate = (uint32_t)t;
u->config.baudrate = (uint32_t)t;
}
else if(!strncmp(&cmd[7], "mode", 4))
{
if(!strncmp(&cmd[12], "kiss", 4))
{
u->defaultMode = MODE_KISS;
u->config.defaultMode = MODE_KISS;
}
else if(!strncmp(&cmd[12], "monitor", 7))
{
u->defaultMode = MODE_MONITOR;
u->config.defaultMode = MODE_MONITOR;
}
else if(!strncmp(&cmd[12], "config", 6))
{
u->defaultMode = MODE_TERM;
u->config.defaultMode = MODE_TERM;
}
else
{
@ -737,9 +805,9 @@ void TermParse(Uart *src)
return;
}
if(!strncmp(&cmd[9], "on", 2))
beacon[bcno].enable = 1;
BeaconConfig[bcno].enable = 1;
else if(!strncmp(&cmd[9], "off", 3))
beacon[bcno].enable = 0;
BeaconConfig[bcno].enable = 0;
else if(!strncmp(&cmd[9], "iv", 2) || !strncmp(&cmd[9], "dl", 2)) //interval or delay
{
int64_t t = StrToInt(&cmd[12], len - 12);
@ -749,9 +817,9 @@ void TermParse(Uart *src)
return;
}
if(!strncmp(&cmd[9], "iv", 2))
beacon[bcno].interval = t * 6000;
BeaconConfig[bcno].interval = t * 6000;
else
beacon[bcno].delay = t * 60;
BeaconConfig[bcno].delay = t * 60;
}
else if(!strncmp(&cmd[9], "data", 4))
@ -763,8 +831,8 @@ void TermParse(Uart *src)
}
uint16_t i = 0;
for(; i < (len - 14); i++)
beacon[bcno].data[i] = cmd[14 + i];
beacon[bcno].data[i] = 0;
BeaconConfig[bcno].data[i] = cmd[14 + i];
BeaconConfig[bcno].data[i] = 0;
}
else if(!strncmp(&cmd[9], "path", 4))
{
@ -776,7 +844,7 @@ void TermParse(Uart *src)
}
if(((len - 14) == 4) && !strncmp(&cmd[14], "none", 4)) //"none" path
{
memset(beacon[bcno].path, 0, sizeof(beacon[bcno].path));
memset(BeaconConfig[bcno].path, 0, sizeof(BeaconConfig[bcno].path));
UartSendString(src, "OK\r\n", 0);
return;
@ -815,7 +883,7 @@ void TermParse(Uart *src)
return;
}
memcpy(beacon[bcno].path, tmp, 14);
memcpy(BeaconConfig[bcno].path, tmp, 14);
}
else
{
@ -1045,24 +1113,63 @@ void TermParse(Uart *src)
else
err = true;
}
else if(!strncmp(cmd, "pwm ", 4))
else if(!strncmp(cmd, "flat ", 5))
{
if(!strncmp(&cmd[5], "on", 2))
ModemConfig.flatAudioIn = 1;
else if(!strncmp(&cmd[5], "off", 3))
ModemConfig.flatAudioIn = 0;
else
err = true;
}
#ifdef AIOC
else if(!strncmp(cmd, "att ", 4))
{
if(!strncmp(&cmd[4], "on", 2))
ModemConfig.usePWM = 1;
ModemConfig.attenuator = 1;
else if(!strncmp(&cmd[4], "off", 3))
ModemConfig.usePWM = 0;
ModemConfig.attenuator = 0;
else
err = true;
ModemApplyTxAttenuator();
}
else if(!strncmp(cmd, "flat ", 5))
else if(!strncmp(cmd, "ptt ", 4))
{
if(!strncmp(&cmd[5], "on", 2))
ModemConfig.flatAudioIn = 1;
else if(!strncmp(&cmd[5], "off", 3))
ModemConfig.flatAudioIn = 0;
if(!strncmp(&cmd[4], "pri", 3))
ModemConfig.pttOutput = 0;
else if(!strncmp(&cmd[4], "sec", 3))
ModemConfig.pttOutput = 1;
else
err = true;
}
else if(!strncmp(cmd, "gain ", 5))
{
int64_t t = StrToInt(&cmd[5], len - 5);
if((t >= 1) && (t <= 16) && (1 == __builtin_popcount((uint32_t)t)))
{
ModemConfig.gain = __builtin_ctz((uint32_t)t);
ModemApplyRxGain();
}
else
{
UartSendString(src, "Incorrect value!\r\n", 0);
return;
}
}
else if(!strncmp(cmd, "level ", 6))
{
int64_t t = StrToInt(&cmd[6], len - 6);
if((t >= 0) && (t <= 100))
{
ModemConfig.txLevel = t;
}
else
{
UartSendString(src, "Incorrect value!\r\n", 0);
return;
}
}
#endif
else if(!strncmp(cmd, "monkiss ", 8))
{
if(!strncmp(&cmd[8], "on", 2))


+ 26
- 11
Core/Src/uart.c View File

@ -1,5 +1,5 @@
/*
Copyright 2020-2023 Piotr Wilkon
Copyright 2020-2025 Piotr Wilkon
This file is part of VP-Digi.
VP-Digi is free software: you can redistribute it and/or modify
@ -20,13 +20,17 @@ along with VP-Digi. If not, see <http://www.gnu.org/licenses/>.
#include "ax25.h"
#include "common.h"
#include <string.h>
#include <systick.h>
#include <uart.h>
#include "digipeater.h"
#include "kiss.h"
Uart Uart1 = {.defaultMode = MODE_KISS}, Uart2 = {.defaultMode = MODE_KISS}, UartUsb= {.defaultMode = MODE_KISS};
#ifdef BLUE_PILL
Uart Uart1 = {.config.defaultMode = MODE_KISS};
Uart Uart2 = {.config.defaultMode = MODE_KISS};
#endif
Uart UartUsb= {.config.defaultMode = MODE_KISS};
#ifdef BLUE_PILL
static void handleInterrupt(Uart *port)
{
if(UART_LL_CHECK_RX_NOT_EMPTY(port->port)) //byte received
@ -65,7 +69,9 @@ static void handleInterrupt(Uart *port)
}
}
}
#endif
#ifdef BLUE_PILL
void UART_LL_UART1_INTERUPT_HANDLER(void) __attribute__ ((interrupt));
void UART_LL_UART1_INTERUPT_HANDLER(void)
{
@ -77,6 +83,7 @@ void UART_LL_UART2_INTERUPT_HANDLER(void)
{
handleInterrupt(&Uart2);
}
#endif
void UartSendByte(Uart *port, uint8_t data)
@ -88,6 +95,7 @@ void UartSendByte(Uart *port, uint8_t data)
{
CDC_Transmit_FS(&data, 1);
}
#ifdef BLUE_PILL
else
{
while(port->txBufferFull)
@ -101,6 +109,7 @@ void UartSendByte(Uart *port, uint8_t data)
UART_LL_ENABLE_TX_EMPTY_INTERRUPT(port->port);
__enable_irq();
}
#endif
}
@ -143,15 +152,15 @@ void UartSendNumber(Uart *port, int32_t n)
void UartInit(Uart *port, USART_TypeDef *uart, uint32_t baud)
{
port->port = uart;
port->baudrate = baud;
port->config.baudrate = baud;
port->rxType = DATA_NOTHING;
port->rxBufferHead = 0;
port->txBufferHead = 0;
port->txBufferTail = 0;
port->txBufferFull = 0;
if(port->defaultMode > MODE_MONITOR)
port->defaultMode = MODE_KISS;
port->mode = port->defaultMode;
if(port->config.defaultMode > MODE_MONITOR)
port->config.defaultMode = MODE_KISS;
port->mode = port->config.defaultMode;
port->enabled = 0;
port->kissBufferHead = 0;
port->lastRxBufferHead = 0;
@ -163,9 +172,10 @@ void UartInit(Uart *port, USART_TypeDef *uart, uint32_t baud)
void UartConfig(Uart *port, uint8_t state)
{
#ifdef BLUE_PILL
if(port->port == UART_LL_UART1_STRUCTURE)
{
UART_LL_UART1_INITIALIZE_PERIPHERAL(port->baudrate);
UART_LL_UART1_INITIALIZE_PERIPHERAL(port->config.baudrate);
if(state)
{
@ -184,10 +194,12 @@ void UartConfig(Uart *port, uint8_t state)
port->enabled = state > 0;
port->isUsb = 0;
return;
}
else if(port->port == UART_LL_UART2_STRUCTURE)
if(port->port == UART_LL_UART2_STRUCTURE)
{
UART_LL_UART2_INITIALIZE_PERIPHERAL(port->baudrate);
UART_LL_UART2_INITIALIZE_PERIPHERAL(port->config.baudrate);
if(state)
{
@ -206,11 +218,14 @@ void UartConfig(Uart *port, uint8_t state)
port->enabled = state > 0;
port->isUsb = 0;
return;
}
else
#endif
if(port == &UartUsb)
{
port->isUsb = 1;
port->enabled = state > 0;
return;
}
}


+ 0
- 19
F103C8T6_DIGI_USB.xml View File

@ -1,19 +0,0 @@
<?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>

+ 20
- 4
README.md View File

@ -1,14 +1,14 @@
# VP-Digi
Polska wersja tego pliku dostępna jest [tutaj](README_pl.md).
**VP-Digi** is a functional, affordable, easy-to-assemble, and configure STM32-based APRS digipeater controller with a built-in KISS modem.
**VP-Digi** is a functional, affordable, easy-to-assemble, and configure STM32-based APRS digipeater controller with a built-in KISS modem. VP-Digi can also run on [AIOC](https://github.com/skuep/AIOC)!
* Multiple modems:
* 1200 Bd AFSK Bell 202 (VHF standard)
* 300 Bd AFSK Bell 103 (HF standard)
* 9600 Bd GFSK G3RUH (UHF standard)
* 1200 Bd AFSK V.23
* PWM (or deprecated R2R) signal generation
* DAC/PWM signal generation
* Analog-digital busy channel detection (data carrier detection)
* AX.25 coder/decoder
* FX.25 (AX.25 with error correction) coder/decoder, fully compatible with [Direwolf](https://github.com/wb2osz/direwolf) and [UZ7HO Soundmodem](http://uz7.ho.ua/packetradio.htm)
@ -31,7 +31,7 @@ The user manual and technical description are available [here](doc/manual.md).
## Source code
The firmware was written using STM32CubeIDE, and you should be able to import this repository directly into the IDE. You can get the source code using:
You can get the source code using:
```bash
git clone https://github.com/sq8vps/vp-digi.git
@ -42,7 +42,23 @@ Since version 2.0.0, you will also need to get the appropriate submodule ([LwFEC
git submodule init
git submodule update
```
Since version 2.0.0, there is also a possibility to build the firmware with or without FX.25 protocol support. The `ENABLE_FX25` symbol must be defined to enable FX.25 support. On STM32CubeIDE, this can be done under *Project->Properties->C/C++ Build->Settings->Preprocessor->Defined symbols*.
Since version 2.2.0, VP-Digi can also run on [AIOC](https://github.com/skuep/AIOC). The source code base is the same for the "Blue Pill" board and AIOC.
However, there are two STM32CubeMX configuration files: `vp-digi.ioc` and `vp-digi_aioc.ioc`. In order to be able to compile and run the project, you need to:
1. Open `vp-digi.ioc` or `vp-digi_aioc.ioc` in STM32CubeMX, depending on your target platform.
2. Generate files for the IDE/toolchain of your choice. Make sure *Generate Under Root* is checked. Under the *Code Generator* tab make sure *Generate peripheral initialization as a pair of '.c/.h' files per peripheral* is checked.
3. Import the generated project to your IDE.
4. If you want to use FX.25, you need to include the `lwfec` directory in your build. In STM32CubeIDE, this can be done under *Project->Properties->C/C++ General->Paths and Symbols* in *Includes* and *Source Locations* tabs.
5. If you are targetting the "Blue Pill" board with STM32F103Cx, then you need to adjust the optimization level to `Optimize For Size (-Os)` for a release build or `Optimize for Debug (-Og)` for a debug build.
On STM32CubeIDE, this can be done under *Project->Properties->C/C++ Build->Settings->MCU GCC Compiler->Optimization*. Otherwise, the program won't fit in the flash memory.
Since version 2.0.0, there is also a possibility to build the firmware with or without FX.25 protocol support. The `ENABLE_FX25` symbol must be defined to enable FX.25 support.
The easiest way is to define this symbol in `defines.h`. Alternatively, on STM32CubeIDE, this can be done under *Project->Properties->C/C++ Build->Settings->MCU GCC Compiler->Preprocessor->Defined symbols*.
When rebulding the project for different platform the code must be regenerated, as explained in the instructions above.
However, since some files remain and some are not regenerated, you need to manually remove them beforehand. This includes removing the `Drivers`, `Middlewares`, `USB_DEVICE` except `USB_DEVICE/App/usbd_cdc_if.c`,
`Core/Startup` directories, the `stm32f*_hal_msp.c`, `syscalls.c`, `sysmem.c`, `gpio.c`, `system_stm32f*.c` files from the `Core/Src` directory, `stm32f*_it.h`, `stm32f*_hal_conf.h`, and `gpio.h` files from the `Core/Inc` directory,
and `.project`, `.cproject`, and `.mxproject` from the main directory.
## Contributing
All contributions are appreciated.


+ 18
- 3
README_pl.md View File

@ -1,13 +1,13 @@
# VP-Digi
**VP-Digi** jest funkcjonalnym, tanim, łatwym w budowie i konfiguracji kontrolerem digipeatera APRS opartym na procesorze STM32 z wbudowanym TNC KISS.
**VP-Digi** jest funkcjonalnym, tanim, łatwym w budowie i konfiguracji kontrolerem digipeatera APRS opartym na procesorze STM32 z wbudowanym TNC KISS. VP-Digi może również działać na [AIOC](https://github.com/skuep/AIOC)!
* Wiele modemów:
* 1200 Bd AFSK Bell 202 (standard VHF)
* 300 Bd AFSK Bell 103 (standard HF)
* 9600 Bd GFSK G3RUH (standard UHF)
* 1200 Bd AFSK V.23
* Generowanie sygnału z użyciem PWM (lub R2R - niezalecane)
* Generowanie sygnału z użyciem DAC/PWM
* Analogowo-cyfrowe wykrywanie zajętości kanału (DCD)
* Obsługa AX.25
* Obsługa FX.25 (AX.25 z korekcją błędów), w pełni kompatybilna z [Direwolf](https://github.com/wb2osz/direwolf) i [UZ7HO Soundmodem](http://uz7.ho.ua/packetradio.htm)
@ -40,7 +40,22 @@ Począwszy od wersji 2.0.0 konieczne jest także pobranie odpowiedniego modułu
git submodule init
git submodule update
```
Począwszy od wersji 2.0.0 istnieje również możliwość kompilowania oprogramowania z obsługą lub bez obsługi protokołu FX.25. Symbol `ENABLE_FX25` musi zostać zdefiniowany, aby włączyć obsługę FX.25. W STM32CubeIDE można to zrobić w menu *Project->Properties->C/C++ Build->Settings->Preprocessor->Defined symbols*.
Począwszy od wersji 2.2.0, VP-Digi może także działać na AIOC [AIOC](https://github.com/skuep/AIOC). Kod źródłowy jest wspólny dla płytek "Blue Pill" i AIOC.
Do wyboru są dwa pliki konfiguracyjne do STM32CubeMX: `vp-digi.ioc` i `vp-digi_aioc.ioc`. W celu poprawnej kompilacji i uruchomienia projektu należy:
1. Otworzyć `vp-digi.ioc` albo `vp-digi_aioc.ioc` w STM32CubeMX, w zależności od docelowej platformy.
2. Wygenerować pliki dla wybranego IDE/toolchainu. Należy zaznaczyć *Generate Under Root*. W zakładce *Code Generator* należy zaznaczyć *Generate peripheral initialization as a pair of '.c/.h' files per peripheral*.
3. Zaimportować projekt do wybranego IDE.
4. W celu obsługi FX.25 należy dołączyć katalog `lwfec` do kompilacji. W STM32CubeIDE można to zrobić w zakładkach *Project->Properties->C/C++ General->Paths and Symbols* in *Includes* i *Source Locations*.
5. Jeśli projekt kompilowany jest na płytkę "Blue Pill" z mikrokontrolerem STM32F103Cx, to konieczne jest ustawienie poziomu optymalizacji na `Optimize For Size (-Os)` dla kompilacji release lub `Optimize for Debug (-Og)` dla kompilacji debug.
W STM32CubeIDE można to zrobić w zakładce *Project->Properties->C/C++ Build->Settings->MCU GCC Compiler->Optimization*. W przeciwnym wypadku program nie zmieści się w pamięci.
Począwszy od wersji 2.0.0 istnieje również możliwość kompilowania oprogramowania z obsługą lub bez obsługi protokołu FX.25.
Symbol `ENABLE_FX25` musi zostać zdefiniowany, aby włączyć obsługę FX.25. W STM32CubeIDE można to zrobić w menu *Project->Properties->C/C++ Build->Settings->MCU GCC Compiler->Preprocessor->Defined symbols*.
W przypadku ponownego budowania projektu dla innej platformy, należy przejść przez kroki opisane powyżej. Niestety pewne pliki mogą pozostać lub nie być ponownie wygenerowane, co poskutkuje błędem kompilacji.
Należy usunąć katalogi `Drivers`, `Middlewares`, `USB_DEVICE` z wyłączeniem pliku `USB_DEVICE/App/usbd_cdc_if.c`,
`Core/Startup`, pliki `stm32f*_hal_msp.c`, `syscalls.c`, `sysmem.c`, `gpio.c`, `system_stm32f*.c` z katalogu `Core/Src`, `stm32f*_it.h`, `stm32f*_hal_conf.h`, `gpio.h` z katalogu `Core/Inc` oraz `.project`, `.cproject` i `.mxproject` z głównego katalogu.
## Wkład
Każdy wkład jest mile widziany.


+ 8
- 6
STM32F103C8Tx_FLASH.ld View File

@ -22,7 +22,7 @@
******************************************************************************
** @attention
**
** Copyright (c) 2023 STMicroelectronics.
** Copyright (c) 2025 STMicroelectronics.
** All rights reserved.
**
** This software is licensed under terms that can be found in the LICENSE file
@ -85,13 +85,15 @@ SECTIONS
. = ALIGN(4);
} >FLASH
.ARM.extab : {
.ARM.extab (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
*(.ARM.extab* .gnu.linkonce.armextab.*)
. = ALIGN(4);
} >FLASH
.ARM : {
.ARM (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
__exidx_start = .;
*(.ARM.exidx*)
@ -99,7 +101,7 @@ SECTIONS
. = ALIGN(4);
} >FLASH
.preinit_array :
.preinit_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
PROVIDE_HIDDEN (__preinit_array_start = .);
@ -108,7 +110,7 @@ SECTIONS
. = ALIGN(4);
} >FLASH
.init_array :
.init_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
PROVIDE_HIDDEN (__init_array_start = .);
@ -118,7 +120,7 @@ SECTIONS
. = ALIGN(4);
} >FLASH
.fini_array :
.fini_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
PROVIDE_HIDDEN (__fini_array_start = .);


+ 1
- 2
STM32F302CBTX_FLASH.ld View File

@ -45,8 +45,7 @@ _Min_Stack_Size = 0x400; /* required amount of stack */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 124K
CONFIG (rw) : ORIGIN = 0x801F000, LENGTH = 4K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
}
/* Sections */


+ 0
- 0
TODO View File


+ 31
- 7
doc/manual.md View File

@ -12,7 +12,8 @@ The changelog is placed at the bottom of this document.
- [2.1.2. Example configuration](#212-example-configuration)
- [2.2. Monitor mode](#22-monitor-mode)
- [2.2.1. Commands](#221-commands)
- [2.2.2. Received packet view](#222-received-packet-view)
- [2.2.2. Calibration mode](#222-calibration-mode)
- [2.2.3. Received packet view](#223-received-packet-view)
- [2.3. KISS mode](#23-kiss-mode)
- [2.4. Signal level setting](#24-signal-level-setting)
- [2.5. Programming](#25-programming)
@ -96,7 +97,6 @@ The following commands are available in configuration mode:
- `quiet TIME` – sets the time that must elapse between channel release and transmission start. Value in milliseconds, ranging from 100 to 2550.
- `uart NUMBER baud RATE` - sets the baud rate (1200-115200 Bd) for the selected serial port.
- `uart NUMBER mode <kiss/monitor/config>` - sets the default operating mode for the selected serial port (0 for USB).
- `pwm <on/off>` – sets the DAC type. *on* when a PWM filter is installed, *off* when an R2R ladder is installed. Starting from version 2.0.0, it is recommended to use only PWM.
- `flat <on/off>` – configures the modem for use with a radio with *flat audio* output. *on* when the signal is fed from the *flat audio* connector, *off* when the signal is fed from the headphone jack. This option only affects 1200 Bd modems.
- `beacon NUMBER <on/off>`*on* activates, *off* deactivates the beacon with the specified number, ranging from 0 to 7.
- `beacon NUMBER iv TIME` – sets the beacon transmission interval (in minutes) for the beacon with the specified number, ranging from 0 to 7.
@ -122,6 +122,12 @@ The following commands are available in configuration mode:
- `fx25 <on/off>` - *on* enables, *off* disables FX.25 protocol support. When enabled, both AX.25 and FX.25 packets will be received simultaneously.
- `fx25tx <on/off>` - *on* enables, *off* disables transmission using the FX.25 protocol. If FX.25 support is completely disabled (command *fx25 off*), packets will always be transmitted using AX.25.
On AIOC, the following commands are additionally available:
- `ptt <pri/sec>` - selects primary (*pri*) or secondary (*sec*) PTT output.
- `att <on/off>` - *on* enables, *off* disables TX signal attenuator.
- `level VALUE` - sets TX signal level given in %.
- `gain <1/2/4/8/16>` - sets RX signal gain to 1, 2, 4, 8, or 16 (AIOC rev1.2+ only).
Additionally, there are control commands available:
- `print` – displays the current settings.
- `list` – displays the contents of the filtering list.
@ -192,7 +198,7 @@ In monitor mode, received and transmitted packets are displayed, and it is also
The following commands are available:
- `beacon NUMBER` - transmits a beacon from 0 to 7 if that beacon is enabled.
- `cal <low/high/alt/stop>` - starts or stops calibration mode: *low* transmits a low tone, *high* transmits a high tone, *alt* transmits zero bytes/alternating tones, and *stop* stops transmission. For the 9600 Bd modem, zero bytes are always transmitted.
- `cal` - enters interactive calibration mode.
Common commands are also available:
@ -201,8 +207,24 @@ Common commands are also available:
- `version` – displays software version information.
- `config` – switches the port to configuration mode.
- `kiss` – switches the port to KISS mode.
#### 2.2.2. Received packet view
#### 2.2.2. Calibration mode
The calibration mode is entered using `cal` command. The following keys are available:
- `l` - transmits low/mark tone.
- `h` - transmits high/space tone.
- `a` - transmits alternating symbols (logical zeros).
- `s` - stops transmission.
- `q` - quit calibration mode and stop transmission (if necessary).
On AIOC, the following keys are additionally available:
- `x` - switches TX attenuator on/off.
- `n` - decreases TX level by 1%.
- `m` - increases TX level by 1%.
The calibration mode does not require pressing the enter key. Remember to save the TX level set with `n` and `m` keys by switching to the configuration mode and using `save`.
#### 2.2.3. Received packet view
For each received AX.25 packet, the header is displayed in the following format:
> Frame received [...], signal level XX% (HH%/LL%)
@ -234,8 +256,8 @@ KISS mode is used for working as a standard TNC KISS, compatible with many Packe
After device startup, you should enter monitor mode (using the `monitor` command) and wait for packets to appear. You should adjust the signal level so that most packets have a signal level of around 50% (as described in [section 2.2.2](#222-received-packet-view)). The received signal level should be maintained within the range of 10-90%.\
The correct setting of the audio output type from the transceiver using the `flat <on/off>` command is crucial for the performance of the 1200 Bd modem. If you are using the headphone/speaker output (filtered), this option should be set to *off*. If you are using the *flat audio* output (unfiltered), this option should be set to *on*. This setting does not affect modems other than 1200 Bd.\
To ensure the best network performance, the transmitted signal level should also be properly set. This is especially important for the 1200 Bd modem, where the signal is transmitted through the standard microphone connector of an FM radio. Excessive signal levels can lead to distortion and significant tone amplitude imbalances.
Signal calibration requires an FM receiver tuned to the same frequency as the transmitter. You should enter monitor mode (using the `monitor` command) and enable high tone transmission (using the `cal high` command). You should set the potentiometer to the minimum amplitude level position and then slowly increase the level while carefully monitoring the signal strength in the receiver, which should increase. At some point, the signal level will stop increasing. At that point, gently retract the potentiometer and turn off the calibration mode (using the `cal stop` command). After this operation, the transmitter should be correctly driven.
> Note! If you fail to reach the point where the signal level stops increasing, the resistor value in the TX path is probably too high. If the signal level is clearly too low, reduce the value of this resistor. Otherwise, no action is necessary.
Signal calibration requires an FM receiver tuned to the same frequency as the transmitter. You should enter monitor mode (using the `monitor` command), switch to calibration mode (using the `cal` command) and enable high tone transmission (using `h` key). You should set the potentiometer to the minimum amplitude level position (or reduce TX level to 0% using `n` key on AIOC) and then slowly increase the level (using `m` key on AIOC) while carefully monitoring the signal strength in the receiver, which should increase. At some point, the signal level will stop increasing. At that point, gently retract the potentiometer (or use `n` key on AIOC) and turn off the calibration mode (using `q` key). After this operation, the transmitter should be correctly driven.
> Note! If you fail to reach the point where the signal level stops increasing, the resistor value in the TX path is probably too high. If the signal level is clearly too low, reduce the value of this resistor. On AIOC, consult AIOC documentation. Otherwise, no action is necessary.
### 2.5. Programming
Programming (flashing firmware) can be done in two ways: using an ST-Link programmer or through the UART1 serial port (e.g., using a USB-UART adapter).
@ -349,6 +371,8 @@ If *viscous delay* functionality is enabled for the matched alias, the completed
In addition, the *viscous delay* buffer is regularly refreshed. If the specified time has passed, and the packet has not been removed from the buffer (see *the beginning of this section*), its hash is saved to the duplicate filter buffer, the packet is transmitted, and removed from the *viscous delay* buffer.
## 4. Documentation changelog
### 2025/03/20
- Calibration instructions updated, AIOC instructions added - Piotr Wilkoń
### 2025/03/04
- New schematic - Piotr Wilkoń
### 2024/05/16


+ 31
- 7
doc/manual_pl.md View File

@ -14,7 +14,8 @@ Rejestr zmian dostępny jest na końcu tego dokumentu.
- [2.1.2. Przykładowe ustawienia](#212-przykładowe-ustawienia)
- [2.2. Tryb monitora](#22-tryb-monitora)
- [2.2.1. Polecenia](#221-polecenia)
- [2.2.2. Widok pakietów odbieranych](#222-widok-pakietów-odbieranych)
- [2.2.2. Tryb kalibracji](#222-tryb-kalibracji)
- [2.2.3. Widok pakietów odbieranych](#223-widok-pakietów-odbieranych)
- [2.3. Tryb KISS](#23-tryb-kiss)
- [2.4. Kalibracja poziomów sygnału](#24-kalibracja-poziomów-sygnału)
- [2.5. Programowanie](#25-programowanie)
@ -98,14 +99,13 @@ W trybie konfiguracji dostępne są następujące polecenia:
- `quiet CZAS` – ustawia czas, który musi upłynąć pomiędzy zwolnieniem się kanału a włączeniem nadawania. Wartość w milisekundach z zakresu od 100 do 2550.
- `uart NUMER baud PREDKOSC` - ustawia prędkość (1200-115200 Bd) pracy wybranego portu szeregowego.
- `uart NUMBER mode <kiss/monitor/config` - ustawia domyślny tryb pracy wybranego portu szeregowego (0 dla USB).
- `pwm <on/off>` – ustawia typ DAC. *on*, gdy zainstalowany jest filtr PWM, *off* gdy zainstalowana jest drabinka R2R. Od wersji 2.0.0 zalecane jest użycie wyłącznie PWM.
- `flat <on/off>` – konfiguruje modem do użycia z radiem z wyjściem *flat audio*. *on* gdy sygnał podawany jest ze złącza *flat audio*, *off* gdy sygnał podawany jest ze złącza słuchawkowego. Opcja ma wpływ jedynie na modemy 1200 Bd.
- `beacon NUMER <on/off>`*on* włącza, *off* wyłącza beacon o podanym numerze z zakresu od 0 do 7.
- `beacon NUMER iv CZAS` – ustawia interwał nadawania (w minutach) beaconu o numerze z zakresu od 0 do 7.
- `beacon NUMER dl CZAS` – ustawia opóźnienie/przesunięcie nadawania (w minutach) beaconu o numerze z zakresu od 0 do 7.
- `beacon NUMER path SCIEZKAn-N[,SCIEZKAn-N]/none` – ustawia ścieżkę beaconu o numerze z zakresu od 0 do 7. Polecenie przyjmuje jeden (np. *WIDE2-2*) lub dwa (np. *WIDE2-2,SP3-3*) elementy ścieżki albo opcję *none* dla braku ścieżki.
- `beacon NUMER data TRESC` – ustawia treść beaconu o numerze z zakresu od 0 do 7.
- `digi <on/off>`*on* włącza, *off* wyłącza digipeater
- `digi <on/off>`*on* włącza, *off* wyłącza digipeater.
- `digi NUMER <on/off>`*on* włącza, *off* wyłącza obsługę aliasu o numerze z zakresu od 0 do 7.
- `digi NUMER alias ALIAS` – ustawia alias o numerze z zakresu od 0 do 7. W przypadku slotów 0-3 (typ *n-N*) przyjmuje do 5 znaków bez SSID, w przypadku slotów 4-7 (aliasy proste) przyjmuje aliasy w formie jak znak wywoławczy wraz z SSID lub bez.
- `digi NUMER max N` – ustawia maksymalne *n* (z zakresu od 1 do 7) dla normalnego powtarzania aliasów typu *n-N* (zakres od 0 do 3).
@ -124,6 +124,12 @@ W trybie konfiguracji dostępne są następujące polecenia:
- `fx25 <on/off>` - *on* włącza, *off* wyłącza obsługę protokołu FX.25. Po włączeniu jednocześnie będą odbierane pakiety AX.25 i FX.25.
- `fx25tx <on/off>` - *on* włącza, *off* wyłącza nadawanie z użyciem protokołu FX.25. Jeśli obsługa FX.25 jest wyłączona całkowicie (polecenie *fx25 off*), to pakiety zawsze będą nadawane z użyciem AX.25.
Dla platformy AIOC dostępne są dodatkowo następujące polecenia:
- `ptt <pri/sec>` - wybiera podstawowe (*pri*) lub dodatkowe (*sec*) wyjście PTT.
- `att <on/off>` - *on* włącza, *off* wyłącza tłumik sygnału nadawanego.
- `level WARTOSC` - ustawia poziom sygnału nadawanego podany w %.
- `gain <1/2/4/8/16>` - ustawia wzmocnienie sygnału odbieranego na 1, 2, 4, 8 lub 16 (tylko dla AIOC rev1.2+).
Ponadto dostępne są polecenia kontrolne:
- `print` – pokazuje aktualne ustawienia.
- `list` – pokazuje zawartość listy filtrującej.
@ -189,7 +195,7 @@ W trybie monitora wyświetlane są pakiety odbierane i nadawane, a ponadto możl
#### 2.2.1. Polecenia
Dostępne są następujące polecenia:
- `beacon NUMER` - nadaje beacon z zakresu 0 do 7, o ile ten beacon jest włączony.
- `cal <low/high/alt/stop>` - rozpoczyna lub kończy tryb kalibracji: *low* nadaje niski ton, *high* nadaje wysoki ton, *alt* nadaje bajty zerowe/zmieniające się tony, a *stop* zatrzymuje transmisję. Dla modemu 9600 Bd zawsze nadawane są bajty zerowe.
- `cal` - przechodzi do interaktywnego trybu kalibracji.
Dostępne są także polecenia wspólne:
- `help` – pokazuje stronę pomocy
@ -197,8 +203,24 @@ Dostępne są także polecenia wspólne:
- `version` – pokazuje informacje o wersji oprogramowania
- `config` – przełącza port do trybu konfiguracji
- `kiss` – przełącza port do trybu KISS
#### 2.2.2. Widok pakietów odbieranych
#### 2.2.2. Tryb kalibracji
Tryb kalibracji uruchamiany jest poprzez polecenie `cal`. W tym trybie dostępne są następujące klawisze:
- `l` - nadaje niski ton.
- `h` - nadaje wysoki ton.
- `a` - nadaje zmieniające się tony (logiczne zera).
- `s` - zatrzymuje transmisję.
- `q` - wychodzi z trybu kalibracji i zatrzymuje transmisję (jeśli to konieczne).
Na platformie AIOC dostępne są dodatkowo klawisze:
- `x` - włącza/wyłącza tłumik sygnału nadawanego.
- `n` - zmniejsza poziom sygnału nadawanego o 1%.
- `m` - zwiększa poziom sygnału nadawanego o 1%.
W trybie kalibracji nie jest konieczne zatwierdzanie enterem. Należy pamiętać, aby zapisać poziom sygnału nadawanego ustawiony klawiszami `n` i `m` poprzez przejście do trybu konfiguracji i użycie polecenia `save`.
#### 2.2.3. Widok pakietów odbieranych
Dla każdego odebranego pakietu AX.25 wyświetlany jest nagłówek w następującym formacie:
> Frame received [...], signal level XX% (HH%/LL%)
@ -229,7 +251,7 @@ Tryb KISS służy do pracy jako standardowe TNC KISS, współpracujące z wielom
Po uruchomieniu urządzenia należy przejść do trybu monitora (polecenie `monitor`) i czekać na pojawienie się pakietów. Należy wyregulować poziom sygnału tak, aby większość pakietów miała poziom sygnału ok. 50% (jak opisano w [sekcji 2.2.2](#222-widok-pakietów-odbieranych)) Poziom sygnału odbieranego należy utrzymywać w zakresie 10-90%.\
Istotne dla wydajności modemu 1200 Bd jest odpowiednie ustawienie typu wyjścia audio z radiotelefonu przy pomocy polecenia `flat <on/off>`. Jeśli używane jest wyjście słuchawkowe/głośnikowe (filtrowane), to opcja ta powinna być ustawiona na *off*. Jeśli używane jest wyjście zwane *flat audio* (niefiltrowane), to opcja ta powinna być ustawiona na *on*. To ustawienie nie ma wpływu na modemy inne niż 1200 Bd.\
Aby zapewnić najlepszą wydajność sieci, poziom sygnału nadawanego powinien być prawidłowo ustawiony. Jest to szczególnie istotne w przypadku modemu 1200 Bd, gdzie sygnał nadawany jest przez standardowe złącze mikrofonowe radiotelefonu FM. Zbyt duży poziom sygnału prowadzi do występowania zniekształceń i dużych dysproporcji amplitud tonów.\
Do kalibracji potrzebny jest odbiornik FM zestrojony na tę samą częstotliwość, co nadajnik. Należy przejść do trybu monitora (polecenie `monitor`) i właczyć nadawanie tonu wysokiego (polecenie `cal high`). Należy ustawić potencjometr do pozycji minimalnego poziomu wysterowania, a następnie powoli zwiększać poziom, jednocześnie uważnie nasłuchując siły sygnału w odbiorniku, który powinien rosnąć. W pewnym momencie poziom sygnału przestanie się zwiększać. Wówczas należy delikatnie cofnąć potencjometr i wyłączyć tryb kalibracji (polecenie `cal stop`). Po tej operacji nadajnik powinien być poprawnie wysterowany.
Do kalibracji potrzebny jest odbiornik FM zestrojony na tę samą częstotliwość, co nadajnik. Należy przejść do trybu monitora (polecenie `monitor`), uruchomić tryb kalibracji (polecenie `cal`) i właczyć nadawanie tonu wysokiego (klawisz `h`). Należy ustawić potencjometr do pozycji minimalnego poziomu wysterowania (lub zmniejszyć poziom sygnału do 0% używając klawisza `n` na AIOC), a następnie powoli zwiększać poziom (używając klawisza `m` na AIOC), jednocześnie uważnie nasłuchując siły sygnału w odbiorniku, który powinien rosnąć. W pewnym momencie poziom sygnału przestanie się zwiększać. Wówczas należy delikatnie cofnąć potencjometr (używając klawisza `n` na AIOC) i wyłączyć tryb kalibracji (klawisz `q`). Po tej operacji nadajnik powinien być poprawnie wysterowany.
> Uwaga! Jeśli nie uda się osiągnąć punktu, w którym poziom sygnału przestanie się zwiększać, to prawdopodobnie wartość rezystancji w torze nadawczym jest zbyt duża. Jeśli poziom sygnału jest wyraźnie zbyt niski, należy zmniejszyć wartość tej rezystancji. W przeciwnym wypadku nie jest konieczne podejmowanie kroków.
### 2.5. Programowanie
@ -333,6 +355,8 @@ Jeśli dla dopasowanego aliasu włączona jest funkcja *viscous delay*, to gotow
Ponadto regularnie odświeżany jest bufor funkcji *viscous delay*. Jeśli minął odpowiedni czas i pakiet nie został usunięty z bufora (patrz *początek tej sekcji*), to jego hasz jest zapisywany do bufora filtra duplikatów, pakiet jest wysyłany i usuwany z bufora *viscous delay*.
## 4. Rejestr zmian dokumentacji
### 2025/03/20
- Zaktualizowano instrukcję kalibracji, dodano instrukcje do AIOC - Piotr Wilkoń
### 2025/03/04
- Nowy schemat - Piotr Wilkoń
### 2024/05/16


+ 5
- 5
vp-digi.ioc View File

@ -27,8 +27,8 @@ Mcu.PinsNb=8
Mcu.ThirdPartyNb=0
Mcu.UserConstants=
Mcu.UserName=STM32F103C8Tx
MxCube.Version=6.9.1
MxDb.Version=DB.6.0.91
MxCube.Version=6.11.1
MxDb.Version=DB.6.0.111
NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.ForceEnableDMAVector=true
@ -58,15 +58,15 @@ ProjectManager.AskForMigrate=true
ProjectManager.BackupPrevious=false
ProjectManager.CompilerOptimize=6
ProjectManager.ComputerToolchain=false
ProjectManager.CoupleFile=false
ProjectManager.CoupleFile=true
ProjectManager.CustomerFirmwarePackage=
ProjectManager.DefaultFWLocation=true
ProjectManager.DeletePrevious=true
ProjectManager.DeviceId=STM32F103C8Tx
ProjectManager.FirmwarePackage=STM32Cube FW_F1 V1.8.5
ProjectManager.FirmwarePackage=STM32Cube FW_F1 V1.8.6
ProjectManager.FreePins=false
ProjectManager.HalAssertFull=false
ProjectManager.HeapSize=0x100
ProjectManager.HeapSize=0x200
ProjectManager.KeepUserCode=true
ProjectManager.LastFirmware=true
ProjectManager.LibraryCopy=1


Loading…
Cancel
Save