parent
367497cced
commit
d691bb6633
@ -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)); |
||||||
|
} |
@ -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
|
@ -1,83 +1,31 @@ |
|||||||
#include "stm8s.h" |
#include "stm8s.h" |
||||||
#include <stdio.h> |
|
||||||
#include "bootstrap.h" |
#include "bootstrap.h" |
||||||
|
#include "fncgen.h" |
||||||
|
|
||||||
#define LEVEL_TOP 400 |
FG_Instance fgi; |
||||||
#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(); |
|
||||||
} |
|
||||||
|
|
||||||
void main(void) |
void main(void) |
||||||
{ |
{ |
||||||
uint16_t cnt = 0; |
u16 t; |
||||||
uint16_t conv; |
bool x = FALSE; |
||||||
|
|
||||||
SimpleInit(); |
SimpleInit(); |
||||||
PWM_Setup(); |
|
||||||
AIN_Setup(); |
|
||||||
|
|
||||||
// Go
|
FG_SPI_Init(); |
||||||
PWM_Cmd(ENABLE); |
|
||||||
|
|
||||||
|
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) { |
while (1) { |
||||||
if (cnt++ == 65535) { |
if (ms_loop_elapsed(&t, 100)) { |
||||||
cnt = 0; |
|
||||||
LED_Toggle(); |
LED_Toggle(); |
||||||
} |
|
||||||
|
|
||||||
// adjust level
|
// Switching freq banks - connect a speaker to hear 2-tone beeping!
|
||||||
if (ADC1_GetFlagStatus(ADC1_FLAG_EOC)) { |
x = !x; |
||||||
conv = ADC1_GetConversionValue(); |
FG_FreqSwitch(&fgi, x); |
||||||
level = conv; |
|
||||||
PWM_Write(); |
|
||||||
} |
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue