Compare commits

...

2 Commits

  1. 16
      User/bootstrap.c
  2. 59
      User/bootstrap.h
  3. 294
      User/fncgen.c
  4. 82
      User/fncgen.h
  5. 84
      User/main.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;
}
/**

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

@ -0,0 +1,294 @@
#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)
/**
* Send 16 bits
* @param word
*/
static void FG_SPI_Send16(uint16_t word)
{
while(!SPI_GetFlagStatus(SPI_FLAG_TXE));
SPI_SendData((u8)(word >> 8));
while(!SPI_GetFlagStatus(SPI_FLAG_TXE));
SPI_SendData((u8)(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)
{
// NSS down
inst->NSS_GPIO->ODR &= ~inst->NSS_PIN;
// Data
FG_SPI_Send16(word);
// Wait for write to complete
while(SPI_GetFlagStatus(SPI_FLAG_BSY));
// NSS up
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)
{
// NSS down
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)
{
// Wait for write to complete
while(SPI_GetFlagStatus(SPI_FLAG_BSY));
// NSS up
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);
}
/**
* @brief Configure SPI for talking to FG devices
* Sets up GPIOs and inits the SPI peripheral
*/
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);
}

@ -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 <stdio.h>
#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);
}
}
}

Loading…
Cancel
Save