diff --git a/User/bootstrap.c b/User/bootstrap.c index fd630e3..5d4bf16 100644 --- a/User/bootstrap.c +++ b/User/bootstrap.c @@ -38,7 +38,7 @@ void SimpleInit(void) // Timebase generation counter TIM4_UpdateRequestConfig(TIM4_UPDATESOURCE_REGULAR); - TIM4_PrescalerConfig(TIM4_PRESCALER_128, TIM4_PSCRELOADMODE_IMMEDIATE); + TIM4_PrescalerConfig(TIM4_PRESCALER_64, TIM4_PSCRELOADMODE_IMMEDIATE); TIM4_SetAutoreload(0xFF); TIM4_ARRPreloadConfig(ENABLE); TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE); @@ -60,7 +60,7 @@ INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23) } /** Delay ms */ -void Delay(uint16_t ms) +void delay_ms(uint16_t ms) { uint16_t start = time_ms; uint16_t t2; @@ -72,13 +72,15 @@ void Delay(uint16_t ms) } } -/** Delay N seconds */ -void Delay_s(uint16_t s) +/** Helper for looping with periodic branches */ +bool ms_loop_elapsed(uint16_t *start, uint16_t duration) { - while (s != 0) { - Delay(1000); - s--; + if (time_ms - *start >= duration) { + *start = time_ms; + return TRUE; } + + return FALSE; } /** diff --git a/User/bootstrap.h b/User/bootstrap.h index 2475abd..418841f 100644 --- a/User/bootstrap.h +++ b/User/bootstrap.h @@ -5,29 +5,63 @@ #ifndef STM8S_STDINIT_H #define STM8S_STDINIT_H +/** + * Simple init (UART, LED, timebase) + */ +void SimpleInit(void); + +//region Timing + /** Global timebase */ extern volatile uint16_t time_ms; -/** Uart IRQ handler */ -void UART1_RX_IRQHandler(void) INTERRUPT(18); - /** SysTick handler */ void TIM4_UPD_OVF_IRQHandler(void) INTERRUPT(23); -/** putchar, used by the SDCC stdlib */ -void putchar(char c); - /** - * Simple init (UART, LED, timebase) + * Millisecond delay + * + * @param ms - nr of milliseconds */ -void SimpleInit(void); +void delay_ms(uint16_t ms); /** - * Millisecond delay + * Seconds delay * * @param ms - nr of milliseconds */ -void Delay(uint16_t ms); +inline void delay_s(uint16_t s) +{ + while (s != 0) { + delay_ms(1000); + s--; + } +} + +/** Get milliseconds elapsed since start timestamp */ +inline uint16_t ms_elapsed(uint16_t start) +{ + return time_ms - start; +} + +/** Get current timestamp. */ +inline uint16_t ms_now(void) +{ + return time_ms; +} + +/** Helper for looping with periodic branches */ +bool ms_loop_elapsed(uint16_t *start, uint16_t duration); + +//endregion + +//region UART + +/** Uart IRQ handler */ +void UART1_RX_IRQHandler(void) INTERRUPT(18); + +/** putchar, used by the SDCC stdlib */ +void putchar(char c); /** * User UART rx handler @@ -38,6 +72,10 @@ void Delay(uint16_t ms); */ extern void UART_HandleRx(char c); +//endregion + +//region LED + /** Toggle indicator LED */ inline void LED_Toggle(void) { @@ -54,5 +92,6 @@ inline void LED_Set(bool state) } } +//endregion #endif //STM8S_DEBUG_H diff --git a/User/fncgen.c b/User/fncgen.c new file mode 100644 index 0000000..0637ea7 --- /dev/null +++ b/User/fncgen.c @@ -0,0 +1,279 @@ +#include "stm8s.h" +#include "fncgen.h" + +/** Get 14-bit LSB from a 28-bit value */ +#define FREQ_LSB(reg) (u16)(0x3FFF & (reg)) + +/** Get 14-bit MSB from a 28-bit value */ +#define FREQ_MSB(reg) (u16)(0x3FFF & ((reg)>>14)) + +/** Mask applied to the FG CREG to clear any previous waveform preset */ +#define FG_WFM_MASK (u16)(FG_OPBITEN | FG_DIV2 | FG_MODE) + +static void FG_SPI_Send16(uint16_t word); + +/** + * @brief Write a single word over SPI to a FG + * @param inst - FG instance + * @param word - word to write (16 bits) + */ +static void FG_WriteWord(FG_Instance *inst, uint16_t word) +{ + inst->NSS_GPIO->ODR &= ~inst->NSS_PIN; + FG_SPI_Send16(word); + inst->NSS_GPIO->ODR |= inst->NSS_PIN; +} + +/** + * @brief Manually start a FG data frame + * This function may be used for broadcasting register + * changes to multiple devices (ie. FG_RESET release + * for phase synchronisation) + * + * @param inst - FG instance + */ +void FG_JoinBroadcast(FG_Instance *inst) +{ + inst->NSS_GPIO->ODR &= ~inst->NSS_PIN; +} + +/** + * @brief Manually end a FG data frame + * This function is used to terminate a broadcast session. + * + * @param inst - FG instance + */ +void FG_LeaveBroadcast(FG_Instance *inst) +{ + inst->NSS_GPIO->ODR |= inst->NSS_PIN; +} + +/** + * @brief Flush the CREG instance variable to the device + * @param inst - FG instance + */ +static inline void FG_WriteCREG(FG_Instance *inst) +{ + FG_WriteWord(inst, inst->CREG); +} + +/** + * @brief Set frequency in a bank register + * Change is immediately applied if the bank is active. + * + * @param inst - FG instance + * @param bank - bank number + * @param regval - new frequency register value (28 bits) + */ +void FG_SetFreq(FG_Instance *inst, FG_FreqBank bank, u32 regval) +{ + u16 word; + + // Ensure B28 is set + if (!(inst->CREG & FG_B28)) { + inst->CREG |= FG_B28; + FG_WriteCREG(inst); + } + + word = (bank == FREQ0 ? FG_ADDR_FREQ0 : FG_ADDR_FREQ1); + FG_WriteWord(inst, word | FREQ_LSB(regval)); // 14 bits LSB + FG_WriteWord(inst, word | FREQ_MSB(regval)); // 14 bits MSB +} + +/** + * @brief Set frequency MSB in a bank register + * Change is immediately applied if the bank is active. + * + * @param inst - FG instance + * @param bank - frequency bank to change + * @param lsb14 - upper 14 bits of the frequency register + */ +void FG_SetFreqMSB(FG_Instance *inst, FG_FreqBank bank, u16 msb14) +{ + u16 word; + + // Ensure B28 is cleared + if ((inst->CREG & (FG_B28 | FG_HLB)) != FG_HLB) { + inst->CREG &= ~FG_B28; + inst->CREG |= FG_HLB; + FG_WriteCREG(inst); + } + + word = (bank == FREQ0 ? FG_ADDR_FREQ0 : FG_ADDR_FREQ1); + FG_WriteWord(inst, word | (u16)(msb14 & 0x3FFF)); // 14 bits LSB +} + +/** + * @brief Set frequency LSB in a bank register + * Change is immediately applied if the bank is active. + * + * @param inst - FG instance + * @param bank - frequency bank to change + * @param lsb14 - lower 14 bits of the frequency register + */ +void FG_SetFreqLSB(FG_Instance *inst, FG_FreqBank bank, u16 lsb14) +{ + u16 word; + + // Ensure B28 is cleared + if ((inst->CREG & (FG_B28 | FG_HLB)) != 0) { + inst->CREG &= ~FG_B28; + inst->CREG &= ~FG_HLB; + FG_WriteCREG(inst); + } + + word = (bank == FREQ0 ? FG_ADDR_FREQ0 : FG_ADDR_FREQ1); + FG_WriteWord(inst, word | (u16)(lsb14 & 0x3FFF)); // 14 bits LSB +} + +/** + * @brief Select active frequency bank (for FSK) + * @param inst - FG instance + * @param bank - bank number + */ +void FG_FreqSwitch(FG_Instance *inst, FG_FreqBank bank) +{ + if (bank == FREQ0) { + inst->CREG &= ~FG_FSELECT; + } else { + inst->CREG |= FG_FSELECT; + } + FG_WriteCREG(inst); +} + +/** + * @brief Set phase in a bank register + * Change is immediately applied if the bank is active. + * + * @param inst - FG instance + * @param bank - bank number + * @param regval - new phase register value (12 bits) + */ +void FG_SetPhase(FG_Instance *inst, FG_PhaseBank bank, u16 regval) +{ + u16 word = (bank == PHASE0 ? FG_ADDR_PHASE0 : FG_ADDR_PHASE1); + FG_WriteWord(inst, word | (u16) (regval & 0xFFF)); // 12 bits LSB +} + +/** + * @brief Select active phase register (for PSK) + * @param inst - FG instance + * @param bank - bank number + */ +void FG_PhaseSwitch(FG_Instance *inst, FG_PhaseBank bank) +{ + if (bank == PHASE0) { + inst->CREG &= ~FG_PSELECT; + } else { + inst->CREG |= FG_PSELECT; + } + FG_WriteCREG(inst); +} + +/** + * @brief Select a function generator waveform preset + * @param inst - FG instance + * @param wfm - waveform preset (enum) + */ +void FG_SetWaveform(FG_Instance *inst, FG_Waveform wfm) +{ + inst->CREG &= ~FG_WFM_MASK; + inst->CREG |= wfm; + FG_WriteCREG(inst); +} + +/** + * @brief Suspend a function generator + * Stops the counter, keeping the output constant + * + * @param inst - FG instance + */ +void FG_Suspend(FG_Instance *inst) +{ + inst->CREG |= FG_SLEEP1; + FG_WriteCREG(inst); +} + +/** + * @brief Resume a function generator from suspend state * + * @param inst - FG instance + */ +void FG_Resume(FG_Instance *inst) +{ + inst->CREG &= ~FG_SLEEP1; + FG_WriteCREG(inst); +} + +/** + * @brief Enable or disable a function generator + * When the FG is disabled, the output goes to a midpoint and + * counting registers are reset. + * + * @param inst - FG instance + * @param enable - enable status + */ +void FG_Cmd(FG_Instance *inst, FunctionalState enable) +{ + if (enable == ENABLE) { + inst->CREG &= ~FG_RESET; + } else { + inst->CREG |= FG_RESET; + } + FG_WriteCREG(inst); +} + +void FG_Reset(FG_Instance *inst) +{ + // Stop, enable fullsize freq writes + inst->CREG = FG_B28 | FG_RESET; + FG_WriteCREG(inst); + + FG_SetFreq(inst, FREQ0, HZ_REG(500)); + FG_SetFreq(inst, FREQ1, 0); + FG_SetPhase(inst, PHASE0, 0); + FG_SetPhase(inst, PHASE1, 0); +} + +/** + * @brief Configure a function generator * + * Sets up the GPIO pin and resets the target. + * SPI must already be configured! + * + * @param inst - FG instance + * @param NSS_GPIO - port with the slave select pin + * @param NSS_PIN - slave slect pin position + */ +void FG_Init(FG_Instance *inst, GPIO_TypeDef *NSS_GPIO, GPIO_Pin_TypeDef NSS_PIN) +{ + GPIO_Init(NSS_GPIO, NSS_PIN, GPIO_MODE_OUT_PP_HIGH_FAST); + + inst->NSS_GPIO = NSS_GPIO; + inst->NSS_PIN = NSS_PIN; + + FG_Reset(inst); +} + +void FG_SPI_Init(void) +{ + // MOSI, SCK + GPIO_Init(GPIOC, GPIO_PIN_5 | GPIO_PIN_6, GPIO_MODE_OUT_PP_HIGH_FAST); + + SPI_Init(SPI_FIRSTBIT_MSB, + SPI_BAUDRATEPRESCALER_2, + SPI_MODE_MASTER, + SPI_CLOCKPOLARITY_HIGH, + SPI_CLOCKPHASE_1EDGE, + SPI_DATADIRECTION_1LINE_TX, + SPI_NSS_SOFT, + 0); + + SPI_Cmd(ENABLE); +} + +static void FG_SPI_Send16(uint16_t word) +{ + SPI_SendData((u8)(word >> 8)); + while(!SPI_GetFlagStatus(SPI_FLAG_TXE)); + SPI_SendData((u8)(word)); + while(SPI_GetFlagStatus(SPI_FLAG_BSY)); +} diff --git a/User/fncgen.h b/User/fncgen.h new file mode 100644 index 0000000..295ef29 --- /dev/null +++ b/User/fncgen.h @@ -0,0 +1,82 @@ +// +// Created by MightyPork on 2017/02/17. +// + +#ifndef STM8S_FNCGEN_H +#define STM8S_FNCGEN_H + +// See the C file for doxygen + +#include "stm8s.h" + +// Freq write - addr + 14 bits +// B28 and HLB allow sequential write, or repeated update of half-registers +// In sequential write, the half-registers are written in the order LSB, MSB +#define FG_ADDR_CR (u16)0x0000 +#define FG_ADDR_FREQ0 (u16)0x4000 +#define FG_ADDR_FREQ1 (u16)0x8000 + +// Phase write - addr + 12 bits +#define FG_ADDR_PHASE0 (u16)0xC000 +#define FG_ADDR_PHASE1 (u16)0xE000 + +#define FG_B28 (u16)0x2000 // Enable sequential write of the freq registers +#define FG_HLB (u16)0x1000 // Nonsequential write, register select (0-LSB, 1-MSB) +#define FG_FSELECT (u16)0x0800 // Select active frequency reg +#define FG_PSELECT (u16)0x0400 // Select active phase reg +#define FG_RESET (u16)0x0100 // Reset counter and keep output at midpoint +#define FG_SLEEP1 (u16)0x0080 // Suspend +#define FG_SLEEP12 (u16)0x0040 // Shut down ADC (when using square output) +#define FG_OPBITEN (u16)0x0020 // Output counter MSb (0=SIN/TRI,1=SQUARE) +#define FG_DIV2 (u16)0x0008 // Divide counter MSb by 2 +#define FG_MODE (u16)0x0002 // If ADC enabled, 0 = SIN, 1 = TRI + +/** Convert frequency in Hz to a register value (for 25 MHz MCLK) */ +#define HZ_REG(hz) (u32)((float)hz * 10.73741824f) + +/** Function generator instance object */ +typedef struct { + uint16_t CREG; + GPIO_TypeDef *NSS_GPIO; + GPIO_Pin_TypeDef NSS_PIN; +} FG_Instance; + +/** FG frequency bank number */ +typedef enum { + FREQ0 = 0, + FREQ1 = 1 +} FG_FreqBank; + +/** FG phase bank number */ +typedef enum { + PHASE0 = 0, + PHASE1 = 1 +} FG_PhaseBank; + +/** + * Function generator waveform presets + */ +typedef enum { + WFM_SINE = (u16) 0, + WFM_TRIANGLE = (u16) FG_MODE, + WFM_SQUARE = (u16) (FG_OPBITEN | FG_DIV2), + WFM_SQUARE_DIV2 = (u16) FG_OPBITEN +} FG_Waveform; + +void FG_JoinBroadcast(FG_Instance *inst); +void FG_LeaveBroadcast(FG_Instance *inst); +void FG_SetFreq(FG_Instance *inst, FG_FreqBank bank, u32 regval); +void FG_SetFreqMSB(FG_Instance *inst, FG_FreqBank bank, u16 msb14); +void FG_SetFreqLSB(FG_Instance *inst, FG_FreqBank bank, u16 lsb14); +void FG_FreqSwitch(FG_Instance *inst, FG_FreqBank bank); +void FG_SetPhase(FG_Instance *inst, FG_PhaseBank bank, u16 regval); +void FG_PhaseSwitch(FG_Instance *inst, FG_PhaseBank bank); +void FG_SetWaveform(FG_Instance *inst, FG_Waveform wfm); +void FG_Suspend(FG_Instance *inst); +void FG_Resume(FG_Instance *inst); +void FG_Cmd(FG_Instance *inst, FunctionalState enable); +void FG_Reset(FG_Instance *inst); +void FG_Init(FG_Instance *inst, GPIO_TypeDef *NSS_GPIO, GPIO_Pin_TypeDef NSS_PIN); +void FG_SPI_Init(void); + +#endif //STM8S_FNCGEN_H diff --git a/User/main.c b/User/main.c index c9231fb..ab170ba 100644 --- a/User/main.c +++ b/User/main.c @@ -1,83 +1,31 @@ #include "stm8s.h" -#include #include "bootstrap.h" +#include "fncgen.h" -#define LEVEL_TOP 400 -#define LEVEL_MAX 370 - -volatile uint16_t level = 200; - -/** - * Set PWM level - */ -void PWM_Write(void) -{ - uint16_t tmp = level; - if (tmp > LEVEL_MAX) tmp = LEVEL_MAX; - TIM1_SetCompare4(tmp); -} - -void PWM_Cmd(FunctionalState fs) -{ - TIM1_CtrlPWMOutputs(fs); -} - -/** - * Set up the PWM generation - */ -void PWM_Setup() -{ - // open drain, fast - GPIOC->DDR |= GPIO_PIN_4; // out - //GPIOC->CR1 &= ~GPIO_PIN_4; // open drain - GPIOC->CR2 |= GPIO_PIN_4; // fast - - TIM1_TimeBaseInit(0, TIM1_COUNTERMODE_UP, LEVEL_TOP, 0); - - TIM1_OC4Init(TIM1_OCMODE_PWM1, - TIM1_OUTPUTSTATE_ENABLE, - level, - TIM1_OCPOLARITY_HIGH, - TIM1_OCIDLESTATE_SET); - - TIM1_Cmd(ENABLE); -} - -/** - * Set up the analog input - */ -void AIN_Setup() -{ - ADC1_ConversionConfig(ADC1_CONVERSIONMODE_CONTINUOUS, - ADC1_CHANNEL_3, - ADC1_ALIGN_RIGHT); - ADC1_Cmd(ENABLE); - ADC1_StartConversion(); -} +FG_Instance fgi; void main(void) { - uint16_t cnt = 0; - uint16_t conv; - + u16 t; + bool x = FALSE; SimpleInit(); - PWM_Setup(); - AIN_Setup(); - // Go - PWM_Cmd(ENABLE); + FG_SPI_Init(); + FG_Init(&fgi, GPIOC, GPIO_PIN_4); + FG_SetFreq(&fgi, FREQ0, HZ_REG(4000)); + FG_SetFreq(&fgi, FREQ1, HZ_REG(2500)); + FG_SetWaveform(&fgi, WFM_SINE); + FG_Cmd(&fgi, ENABLE); + + t = ms_now(); while (1) { - if (cnt++ == 65535) { - cnt = 0; + if (ms_loop_elapsed(&t, 100)) { LED_Toggle(); - } - // adjust level - if (ADC1_GetFlagStatus(ADC1_FLAG_EOC)) { - conv = ADC1_GetConversionValue(); - level = conv; - PWM_Write(); + // Switching freq banks - connect a speaker to hear 2-tone beeping! + x = !x; + FG_FreqSwitch(&fgi, x); } } }