diff --git a/Core/Inc/drivers/config_ll.h b/Core/Inc/drivers/config_ll.h new file mode 100644 index 0000000..5053e70 --- /dev/null +++ b/Core/Inc/drivers/config_ll.h @@ -0,0 +1,168 @@ +/* +Copyright 2020-2025 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 . +*/ + +#ifndef DRIVERS_CONFIG_LL_H_ +#define DRIVERS_CONFIG_LL_H_ + +#include + +#if defined(STM32F103xB) || defined(STM32F103x8) + +#include "stm32f1xx.h" + +#define CONFIG_ADDRESS (uintptr_t)0x800F000 //64 KiB starting at 0x8000000 minus 4 kiB (two 1024-word pages) +#define CONFIG_PAGE_COUNT 2 //two 1024-word pages +#define CONFIG_PAGE_SIZE 1024 //1024 words per page (2048 bytes) + +/** + * @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 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)); +} + +static void erase(void) +{ + 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; +} + +static void unlock(void) +{ + FLASH->KEYR = 0x45670123; + FLASH->KEYR = 0xCDEF89AB; +} + +static void lock(void) +{ + FLASH->CR &= ~FLASH_CR_PG; + FLASH->CR |= FLASH_CR_LOCK; +} + +#elif defined(STM32F302xC) + +#include "stm32f3xx.h" + +#define CONFIG_ADDRESS (uintptr_t)0x801F000 //128 KiB starting at 0x8000000 minus 4 kiB (two 1024-word pages) +#define CONFIG_PAGE_COUNT 2 //two 1024-word pages +#define CONFIG_PAGE_SIZE 1024 //1024 words per page (2048 bytes) + +/** + * @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 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)); +} + +static void erase(void) +{ + 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; +} + +static void unlock(void) +{ + FLASH->KEYR = 0x45670123; + FLASH->KEYR = 0xCDEF89AB; +} + +static void lock(void) +{ + FLASH->CR &= ~FLASH_CR_PG; + FLASH->CR |= FLASH_CR_LOCK; +} + +#endif + +#endif diff --git a/Core/Inc/drivers/modem_ll_aioc.h b/Core/Inc/drivers/modem_ll_aioc.h new file mode 100644 index 0000000..41fdb37 --- /dev/null +++ b/Core/Inc/drivers/modem_ll_aioc.h @@ -0,0 +1,400 @@ +/* +Copyright 2020-2025 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 . +*/ + +#ifndef DRIVERS_MODEM_LL_STM32F302_H_ +#define DRIVERS_MODEM_LL_STM32F302_H_ + +#if defined(STM32F302xC) + +#define USE_FPU 1 /**< Use FPU - F302 has one */ +#define MODEM_LL_DAC_MAX 4095 /**< Maximum value for DAC - 4095 for 12-bit DAC */ + +#include +#include "stm32f3xx.h" + +/** + * TIM1 is used for pushing samples to DAC (clocked at 18 MHz) + * TIM3 is the baudrate generator for TX (clocked at 18 MHz) + * 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_TIM16_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_TIM16_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_DAC_PUT_VALUE(value) (DAC1->DHR12R1 = (value)) + +static inline void MODEM_LL_DCD_LED_ON(void) +{ + GPIOB->BSRR = GPIO_BSRR_BR_8; + GPIOB->BSRR = GPIO_BSRR_BS_9; +} + +static inline void MODEM_LL_DCD_LED_OFF(void) +{ + GPIOB->BSRR = GPIO_BSRR_BR_8; + GPIOB->BSRR = GPIO_BSRR_BR_9; +} + +#define MODEM_LL_SET_TX_ATTENUATOR(state) (GPIOA->BSRR = (state ? GPIO_BSRR_BR_3 : GPIO_BSRR_BS_3)) + +/** + * @brief Enable PTT + * @param output Output number (AIOC only, meaningless on other platforms) + */ +static inline void MODEM_LL_PTT_ON(uint8_t output) +{ + GPIOB->BSRR = GPIO_BSRR_BR_9; + GPIOB->BSRR = GPIO_BSRR_BS_8; + switch (output) + { + case 0: + GPIOA->BSRR = GPIO_BSRR_BS_0; + break; + case 1: + GPIOA->BSRR = GPIO_BSRR_BS_1; + break; + } +} + +/** + * @brief Enable PTT + * @param output Output number (AIOC only, meaningless on other platforms) + */ +static inline void MODEM_LL_PTT_OFF(uint8_t output) +{ + GPIOB->BSRR = GPIO_BSRR_BR_8; + GPIOB->BSRR = GPIO_BSRR_BR_9; + switch (output) + { + case 0: + GPIOA->BSRR = GPIO_BSRR_BR_0; + break; + case 1: + GPIOA->BSRR = GPIO_BSRR_BR_1; + break; + } +} + +/** + * @brief Initialize clocks + */ +static void MODEM_LL_INITIALIZE_RCC(void) +{ + RCC->AHBENR |= RCC_AHBENR_GPIOBEN; + RCC->AHBENR |= RCC_AHBENR_GPIOAEN; + RCC->AHBENR |= RCC_AHBENR_ADC12EN; + RCC->APB1ENR |= RCC_APB1ENR_DAC1EN; + RCC->AHBENR |= RCC_AHBENR_DMA1EN; + RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; + RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; + RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; +} + +/** + * @brief Initialize PTT outputs and LEDs + */ +static void MODEM_LL_INITIALIZE_OUTPUTS(void) +{ + /* DCD and PTT LEDs: between PB8 and PB9 */ + GPIOB->MODER &= ~GPIO_MODER_MODER8; + GPIOB->MODER |= GPIO_MODER_MODER8_0; + GPIOB->OTYPER &= ~GPIO_OTYPER_OT_8; + GPIOB->BSRR = GPIO_BSRR_BR_8; + GPIOB->MODER &= ~GPIO_MODER_MODER9; + GPIOB->MODER |= GPIO_MODER_MODER9_0; + GPIOB->OTYPER &= ~GPIO_OTYPER_OT_9; + GPIOB->BSRR = GPIO_BSRR_BR_9; + /* PTT: PA0, PA1 */ + GPIOA->MODER &= ~GPIO_MODER_MODER0; + GPIOA->MODER |= GPIO_MODER_MODER0_0; + GPIOA->OTYPER &= ~GPIO_OTYPER_OT_0; + GPIOA->BSRR = GPIO_BSRR_BR_0; + GPIOA->MODER &= ~GPIO_MODER_MODER1; + GPIOA->MODER |= GPIO_MODER_MODER1_0; + GPIOA->OTYPER &= ~GPIO_OTYPER_OT_1; + GPIOA->BSRR = GPIO_BSRR_BR_1; + /* PA3: open-drain TX attenuator */ + GPIOA->MODER &= ~GPIO_MODER_MODER3; + GPIOA->MODER |= GPIO_MODER_MODER3_0; + GPIOA->OTYPER |= GPIO_OTYPER_OT_3; + GPIOA->BSRR = GPIO_BSRR_BS_3; //open drain = no attenuation by default +} + +/** + * @brief Set RX gain + * @param gain New RX gain: 1, 2, 4, 8 or 16 + * @note Invalid values have no effect + * @note Only used for AIOC rev. >= 1.2, has no effect on other platforms + */ +static void MODEM_LL_SET_GAIN(uint8_t gain) +{ + switch(gain) + { + case 1: + //adc gain=1, switch to follower mode later + OPAMP2->CSR &= ~OPAMP_CSR_PGGAIN; //bias gain=16 + OPAMP2->CSR |= OPAMP_CSR_PGGAIN_0 | OPAMP_CSR_PGGAIN_1; + break; + case 2: + OPAMP2->CSR &= ~OPAMP_CSR_PGGAIN; //bias gain=8 + OPAMP2->CSR |= OPAMP_CSR_PGGAIN_1; + OPAMP1->CSR &= ~OPAMP_CSR_PGGAIN; //adc gain=2 + //3.3%*2*8=52.8% + break; + case 4: + OPAMP2->CSR &= ~OPAMP_CSR_PGGAIN; //bias gain=4 + OPAMP2->CSR |= OPAMP_CSR_PGGAIN_0; + OPAMP1->CSR &= ~OPAMP_CSR_PGGAIN; //adc gain=4 + OPAMP1->CSR |= OPAMP_CSR_PGGAIN_0; + //3.3%*4*4=52.8% + break; + case 8: + OPAMP2->CSR &= ~OPAMP_CSR_PGGAIN; //bias gain=2 + OPAMP1->CSR &= ~OPAMP_CSR_PGGAIN; //adc gain=8 + OPAMP1->CSR |= OPAMP_CSR_PGGAIN_1; + //3.3%*8*2=52.8% + break; + case 16: + //bias gain=1, switch to follower mode later + OPAMP1->CSR &= ~OPAMP_CSR_PGGAIN; //adc gain=16 + OPAMP1->CSR |= OPAMP_CSR_PGGAIN_0 | OPAMP_CSR_PGGAIN_1; + //3.3%*16*1=52.8% + break; + default: + return; + } + + if(1 == gain) + { + //adc gain at 1 (follower), bias gain at 16 (just enable PGA here) + OPAMP1->CSR |= OPAMP_CSR_VMSEL; + OPAMP2->CSR &= ~OPAMP_CSR_VMSEL; + OPAMP2->CSR |= OPAMP_CSR_VMSEL_1; + } + else if(16 == gain) + { + //adc gain at 16 (just enable PGA here), bias gain at 1 (follower) + OPAMP1->CSR &= ~OPAMP_CSR_VMSEL; + OPAMP1->CSR |= OPAMP_CSR_VMSEL_1; + OPAMP2->CSR |= OPAMP_CSR_VMSEL; + } + else + { + //in any other case, enable PGA on both op amps + OPAMP1->CSR &= ~OPAMP_CSR_VMSEL; + OPAMP1->CSR |= OPAMP_CSR_VMSEL_1; + OPAMP2->CSR &= ~OPAMP_CSR_VMSEL; + OPAMP2->CSR |= OPAMP_CSR_VMSEL_1; + } +} + +/** + * @brief Initialize ADC + * @return Utilized ADC pointer + */ +static volatile ADC_TypeDef* MODEM_LL_INITIALIZE_ADC(int32_t *bias) +{ + //OPAMP2 as ADC bias generator + OPAMP2->CSR |= OPAMP_CSR_FORCEVP; //reference voltage as non-inverting input + OPAMP2->CSR &= ~OPAMP_CSR_VMSEL; //programmable gain amplifier mode + OPAMP2->CSR |= OPAMP_CSR_VMSEL_1; + OPAMP2->CSR &= ~OPAMP_CSR_CALSEL; //reference = 3.3% VDDA + OPAMP2->CSR &= ~OPAMP_CSR_PGGAIN; //x16 gain to get 52.8% VDDA bias + OPAMP2->CSR |= OPAMP_CSR_PGGAIN_0 | OPAMP_CSR_PGGAIN_1; + OPAMP2->CSR &= ~OPAMP_CSR_TSTREF; //make sure reference voltage is enabled + OPAMP2->CSR |= OPAMP_CSR_OPAMPxEN; //enable op amp + + //OPAMP1 as ADC preamp + OPAMP1->CSR &= ~OPAMP_CSR_FORCEVP; //make sure reference voltage is not used + OPAMP1->CSR &= ~OPAMP_CSR_VPSEL_1; //PA5 (signal input) as non-inverting inptu + OPAMP1->CSR |= OPAMP_CSR_VPSEL_0; + OPAMP1->CSR |= OPAMP_CSR_VMSEL; //input follower mode for now + OPAMP1->CSR |= OPAMP_CSR_OPAMPxEN; //enable op amp + + MODEM_LL_SET_GAIN(1); + + /* ADC input: PB2 (ADC2 channel 12) or PA5 via OPAMP1 (ADC1 channel 3) */ + GPIOB->MODER |= GPIO_MODER_MODER2; + GPIOA->MODER |= GPIO_MODER_MODER5; + /*/4 prescaler */ + RCC->CFGR2 &= ~RCC_CFGR2_ADCPRE12; + RCC->CFGR2 |= RCC_CFGR2_ADCPRE12_DIV4; + + //configure ADC1 channel 3 first and check if there is bias (AIOC rev. >= 1.2) + ADC1->CFGR |= ADC_CFGR_CONT; + ADC1->CFGR &= ~ADC_CFGR_EXTEN; + /* 61.5 cycle sampling = 292 kHz */ + ADC1->SMPR1 &= ~ADC_SMPR1_SMP3; + ADC1->SMPR1 |= ADC_SMPR1_SMP3_2 | ADC_SMPR1_SMP3_0; + ADC1->SQR1 &= ~ADC_SQR1_SQ1; + ADC1->SQR1 |= (3 << ADC_SQR1_SQ1_Pos); //channel 3 + ADC1->SQR1 &= ~ADC_SQR1_L; //single conversion + ADC1->DIFSEL &= ~ADC_DIFSEL_DIFSEL_3; + /* Enable voltage regulator and perform calibration */ + ADC1->CR &= ~ADC_CR_ADVREGEN; + ADC1->CR |= ADC_CR_ADVREGEN_0; + HAL_Delay(20); + ADC1->CR &= ~ADC_CR_ADCALDIF; + ADC1->CR |= ADC_CR_ADCAL; + while(ADC1->CR & ADC_CR_ADCAL) + ; + HAL_Delay(20); + /* Enable ADC */ + ADC1->CR |= ADC_CR_ADEN; + while(!(ADC1->ISR & ADC_ISR_ADRDY)) + ; + ADC1->CR |= ADC_CR_ADSTART; + + *bias = 4325; //ADC bias is around 4325, because the opamp generates the bias at 3.3%*16=52.8% of VDDA + + //collect some samples and average them + ADC1->DR; + uint32_t sum = 0; + for(uint8_t i = 0; i < 16; i++) + { + while(!(ADC1->ISR & ADC_ISR_EOC)) + ; + sum += ADC1->DR; + } + sum /= 16; + + //check whether the average is between 40% and 60% of the ADC range, that is, whether there is a proper bias present + if((sum < (uint32_t)(0.4f * 4096.f)) || ((sum > (uint32_t)(0.6f * 4096.f)))) + { + //if not, disable ADC1 and set up ADC2 (old AIOC revision) + ADC1->CR |= ADC_CR_ADSTP; + while(ADC1->CR & ADC_CR_ADSTP) + ; + ADC1->CR |= ADC_CR_ADDIS; + + ADC2->CFGR |= ADC_CFGR_CONT; + ADC2->CFGR &= ~ADC_CFGR_EXTEN; + /* 61.5 cycle sampling = 292 kHz */ + ADC2->SMPR2 &= ~ADC_SMPR2_SMP12; + ADC2->SMPR2 |= ADC_SMPR2_SMP12_2 | ADC_SMPR2_SMP12_0; + ADC2->SQR1 &= ~ADC_SQR1_SQ1; + ADC2->SQR1 |= (12 << ADC_SQR1_SQ1_Pos); //channel 12 + ADC2->SQR1 &= ~ADC_SQR1_L; //single conversion + ADC2->DIFSEL &= ~ADC_DIFSEL_DIFSEL_12; + /* Enable voltage regulator and perform calibration */ + ADC2->CR &= ~ADC_CR_ADVREGEN; + ADC2->CR |= ADC_CR_ADVREGEN_0; + HAL_Delay(20); + ADC2->CR &= ~ADC_CR_ADCALDIF; + ADC2->CR |= ADC_CR_ADCAL; + while(ADC2->CR & ADC_CR_ADCAL) + ; + /* Enable ADC */ + ADC2->CR |= ADC_CR_ADEN; + while(!(ADC2->ISR & ADC_ISR_ADRDY)) + ; + ADC2->CR |= ADC_CR_ADSTART; + + *bias = 4095; //assume bias to be in the middle in old AIOC revision + + return ADC2; + } + else + return ADC1; +} + +/** + * @brief Initialize DMA + * @param *buffer Target memory buffer + * @param count Number of words to be copied + * @param *adc Source ADC + */ +static void MODEM_LL_INITIALIZE_DMA(volatile void *buffer, uint16_t count, volatile ADC_TypeDef *adc) +{ + /* 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 = count; + DMA1_Channel2->CPAR = (uintptr_t)&(adc->DR); + DMA1_Channel2->CMAR = (uintptr_t)(buffer); + DMA1_Channel2->CCR |= DMA_CCR_EN; +} + +/** + * @brief Initialize primary DAC + */ +static void MODEM_LL_INITIALIZE_DAC(void) +{ + DAC1->CR &= DAC_CR_WAVE1; + DAC1->CR &= ~DAC_CR_TEN1; + DAC1->CR |= DAC_CR_BOFF1; + DAC1->CR |= DAC_CR_EN1; +} + +static void MODEM_LL_ADC_TIMER_INITIALIZE(void) +{ + /* 72 / 9 = 8 MHz */ + TIM2->PSC = 8; + /* enable DMA call instead of standard interrupt */ + TIM2->DIER |= TIM_DIER_UDE; +} + +static void MODEM_LL_DAC_TIMER_INITIALIZE(void) +{ + /* 72 / 4 = 18 MHz */ + TIM1->PSC = 3; + TIM1->DIER |= TIM_DIER_UIE; +} + +static void MODEM_LL_BAUDRATE_TIMER_INITIALIZE(void) +{ + /* 72 / 4 = 18 MHz */ + TIM3->PSC = 3; + TIM3->DIER |= TIM_DIER_UIE; +} + +#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) + +#endif + +#endif /* DRIVERS_MODEM_LL_H_ */ diff --git a/Core/Inc/drivers/modem_ll_bluepill.h b/Core/Inc/drivers/modem_ll_bluepill.h new file mode 100644 index 0000000..533a346 --- /dev/null +++ b/Core/Inc/drivers/modem_ll_bluepill.h @@ -0,0 +1,209 @@ +/* +Copyright 2020-2025 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 . +*/ + +#ifndef DRIVERS_MODEM_LL_STM32F103_H_ +#define DRIVERS_MODEM_LL_STM32F103_H_ + +#if defined(STM32F103xB) || defined(STM32F103x8) + +#include +#include "stm32f1xx.h" + +#define MODEM_LL_DAC_MAX 255 /**< PWM max value */ + +/** + * 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_DAC_PUT_VALUE(value) (TIM4->CCR1 = (value)) + +static inline void MODEM_LL_DCD_LED_ON(void) +{ + GPIOC->BSRR = GPIO_BSRR_BR13; + GPIOB->BSRR = GPIO_BSRR_BS5; +} + +static inline void MODEM_LL_DCD_LED_OFF(void) +{ + GPIOC->BSRR = GPIO_BSRR_BS13; + GPIOB->BSRR = GPIO_BSRR_BR5; +} + +#define MODEM_LL_PTT_ON(output) (GPIOB->BSRR = GPIO_BSRR_BS7) +#define MODEM_LL_PTT_OFF(output) (GPIOB->BSRR = GPIO_BSRR_BR7) + +static void MODEM_LL_INITIALIZE_RCC(void) +{ + 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; +} + +static void MODEM_LL_INITIALIZE_OUTPUTS(void) +{ + /* 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; +} + +/** + * @brief Initialize ADC + * @return Utilized ADC pointer + */ +static volatile ADC_TypeDef* MODEM_LL_INITIALIZE_ADC(int32_t *bias) +{ + /* 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; + + *bias = 4095; + return ADC1; +} + +/** + * @brief Initialize DMA + * @param *buffer Target memory buffer + * @param count Number of words to be copied + * @param *adc Source ADC + */ +static void MODEM_LL_INITIALIZE_DMA(volatile void *buffer, uint16_t count, volatile ADC_TypeDef *adc) +{ + /* 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 = count; + DMA1_Channel2->CPAR = (uintptr_t)&(adc->DR); + DMA1_Channel2->CMAR = (uintptr_t)buffer; + DMA1_Channel2->CCR |= DMA_CCR_EN; +} + +/** + * @brief Initialize DAC + */ +static void MODEM_LL_INITIALIZE_DAC(void) +{ + /* PWM output: PB6 */ + GPIOB->CRL |= GPIO_CRL_CNF6_1; + GPIOB->CRL |= GPIO_CRL_MODE6; + GPIOB->CRL &= ~GPIO_CRL_CNF6_0; + + /* 72 / 3 = 24 MHz to provide 8 bit resolution at around 100 kHz */ + TIM4->PSC = 2; + /* 24 MHz / 256 = 94 kHz */ + TIM4->ARR = 255; + TIM4->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; + TIM4->CCER |= TIM_CCER_CC1E; + TIM4->CR1 |= TIM_CR1_CEN; +} + +static void MODEM_LL_ADC_TIMER_INITIALIZE(void) +{ + /* 72 / 9 = 8 MHz */ + TIM2->PSC = 8; + /* enable DMA call instead of standard interrupt */ + TIM2->DIER |= TIM_DIER_UDE; +} + +static void MODEM_LL_DAC_TIMER_INITIALIZE(void) +{ + /* 72 / 4 = 18 MHz */ + TIM1->PSC = 3; + TIM1->DIER |= TIM_DIER_UIE; +} + +static void MODEM_LL_BAUDRATE_TIMER_INITIALIZE(void) +{ + /* 72 / 4 = 18 MHz */ + TIM3->PSC = 3; + TIM3->DIER |= TIM_DIER_UIE; +} + +#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) + +#endif + +#endif /* DRIVERS_MODEM_LL_H_ */