Dump prerelease code, needs cleaned up

main
alexis 2023-01-02 11:55:41 -07:00
commit d1e7ce0776
9 changed files with 1136 additions and 0 deletions

26
.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
# Ignore list for: c
*.d
*.o
*.obj
*.elf
*.map
*.gch
*.pch
*.lib
*.a
*.so
*.out
*.su
*.bin
*.eep
*.hex
*.lss
*.dSYM
# Ignore list for: kicad
*.bck
*.kicad_pcb-bak
*.sch-bak
*-cache
*-backups
*.kicad_prl

29
meson.build Normal file
View File

@ -0,0 +1,29 @@
project('timdac', 'c', default_options: ['default_library=static'])
o_hw = get_option('timdac_hw')
sources = ['src/timdac.c', 'src/timdac_ll.c']
dependencies = []
defs = []
if o_hw == 'ch32v103'
ch32v103_proj = subproject('ch32v103-meson')
ch32v103_dep = ch32v103_proj.get_variable('ch32v103_dep')
dependencies += ch32v103_dep
sources += ['src/timdac_hw_ch32v103.c']
endif
add_project_arguments(defs, language: ['c', 'cpp'])
timdac_lib = library(
'timdac',
sources,
dependencies: dependencies,
include_directories: ['src'],
)
libtimdac_dep = declare_dependency(
compile_args: defs,
include_directories: include_directories('inc'),
link_with: timdac_lib,
)

132
src/timdac.c Normal file
View File

@ -0,0 +1,132 @@
// intellectual property is bullshit bgdc
#include "timdac.h"
#include "timdac_config.h"
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdatomic.h>
// Reserved channel numbers to indicate a pending queue-jumper. First, a queue
// jumper is written into ->pend. The ISR swaps that with PEND_SLOT. PEND_SLOT
// is like NO_PEND, but timdac_set will not pend another channel while it's set
// - ensuring we do not flood the sequence with queue jumpers. Once PEND_SLOT
// comes back around to the ISR, it is replaced with NO_PEND.
#define NO_PEND UINT8_MAX
#define PEND_SLOT (NO_PEND - 1)
// Channels are stored as 32-bit integers, packing a 16-bit signed int into the
// low half and flags into the high half.
#define CHAN_FLAG_MASK 0xFFFF0000u
#define CHAN_VALUE_MASK 0x0000FFFFu
#define ENCODE_CHAN(value, flags) \
((uint32_t)(uint16_t)(value) | ((flags) & CHAN_FLAG_MASK))
#define CHAN_VALUE(chan) ((int16_t)(uint16_t)((chan) & CHAN_VALUE_MASK))
#define CHAN_FLAG_SLOW 0x00010000u
void timdac_init(timdac_t * tds)
{
tds->chan = 0;
tds->pend = NO_PEND;
tds->count = 0;
for (size_t i = 0; i < tds->n_channels; i++)
tds->channels[i] = CHAN_FLAG_SLOW;
timdac_ll_init(tds->td);
}
void timdac_start(timdac_t * tds)
{
timdac_ll_idle(tds->td);
}
bool timdac_set(timdac_t * tds, uint8_t chan, int32_t value)
{
if (chan >= tds->n_channels)
return false;
int32_t prev = CHAN_VALUE(atomic_load(&tds->channels[chan]));
bool slow = prev & CHAN_FLAG_SLOW;
if ((value < 0 && prev > 0) || (value > 0 && prev < 0))
slow = true;
else
slow |= abs(value - prev) >= TIMDAC_SLOW_DELTA;
bool stable = atomic_compare_exchange_strong(
&tds->channels[chan],
&prev,
ENCODE_CHAN(value, slow ? CHAN_FLAG_SLOW : 0)
);
if (!stable)
atomic_store(&tds->channels[chan],
ENCODE_CHAN(value, CHAN_FLAG_SLOW));
unsigned no_pend = NO_PEND;
atomic_compare_exchange_strong(
&tds->pend,
&no_pend,
chan
);
return true;
}
bool timdac_pending(timdac_t * tds)
{
return atomic_load(&tds->pend) < PEND_SLOT;
}
void timdac_poll(timdac_t * tds)
{
if (timdac_ll_poll(tds->td))
return;
_Static_assert(
TIMDAC_TUNE_INTERVAL <= UINT8_MAX,
"Calibration interval too high!");
if (tds->count >= TIMDAC_TUNE_INTERVAL || !tds->td->tune)
{
// Reached the end, time to calibrate
tds->count = 0;
if (timdac_ll_tune(tds->td))
return;
}
uint8_t chan;
unsigned pend = atomic_exchange(&tds->pend, PEND_SLOT);
if (pend < PEND_SLOT)
{
chan = tds->pend;
}
else
{
chan = tds->chan;
uint8_t next_chan = chan + 1;
if (next_chan >= tds->n_channels)
next_chan = 0;
tds->chan = next_chan;
atomic_compare_exchange_strong(&tds->pend, &pend, NO_PEND);
}
uint32_t chancode = atomic_fetch_and(
&tds->channels[chan],
~CHAN_FLAG_SLOW
);
tds->count++;
timdac_ll_emit(
tds->td,
chan,
CHAN_VALUE(chancode),
chancode & CHAN_FLAG_SLOW
);
}

74
src/timdac.h Normal file
View File

@ -0,0 +1,74 @@
// intellectual property is bullshit bgdc
#ifndef TIMDAC_H
#define TIMDAC_H
// ============================================================================
// TIMDAC
// ----------------------------------------------------------------------------
//
// NOTE - estimated CPU utilization - 0.9% at 72 MHz, 12.25us total
// (5.48us discharge, 2.29us pulse, 2.14us stabilize, 2.29us transfer) across a
// 1.29ms cycle
// ============================================================================
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <stdatomic.h>
#include <signal.h>
#include "timdac_ll.h"
#define TIMDAC_SCANNER_MAX_CHANNELS 32
// timdac_scanner provides a higher level interface to timdac, allowing the
// user to simply write output values as they need changing. When in use, it
// takes full ownership of the timdac; you should never call any timdac_
// functions including timdac_init().
typedef struct timdac_s {
// #### BEGIN - USER MUST INITIALIZE ####
// Pointer to the timdac low-level instance in use
timdac_ll_t * td;
// Array of output channel values
atomic_uint * channels;
// Number of channels, must be no more than TIMDAC_SCANNER_MAX_CHANNELS
uint8_t n_channels;
// #### END - USER MUST INITIALIZE ####
sig_atomic_t volatile chan; // Current channel number
sig_atomic_t volatile count; // How many channels have been emitted since tuning
atomic_uint pend; // A pending immediate-update channel
} timdac_t;
// Initialize the DAC and DAC scanner. You must have filled in the "USER MUST
// INITIALIZE" fields in the struct.
void timdac_init(timdac_t * tds);
// Start the scanner, if using interrupts. If you are not using interrupts,
// simply poll in a loop when you want to run - do not call this.
void timdac_start(timdac_t * tds);
// Set a new value for an output channel. If no other channel has a pending
// immediate update, this channel will also be added to the pend slot so that
// it can be updated as soon as the DAC is ready without waiting for a full
// cycle.
//
// Returns true on success. Only fails on an invalid channel number.
bool timdac_set(timdac_t * tds, uint8_t chan, int32_t value);
// Return whether an update is currently pending. Only one channel can hold the
// pending slot at a time, so if you post an update while another channel is
// pending, the newer update will not be written to the output until it is
// next hit in the normal scanning cycle. To ensure the fastest update, you
// can poll for this flag to clear before calling timdac_set().
bool timdac_pending(timdac_t * tds);
// Poll the scanner. Should be called periodically.
void timdac_poll(timdac_t * tds);
#endif // !defined(TIMDAC_H)

66
src/timdac_config.h Normal file
View File

@ -0,0 +1,66 @@
// intellectual property is bullshit bgdc
#ifndef TIMDAC_CONFIG_H
#define TIMDAC_CONFIG_H
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#define TIMDAC_N_CHANNELS 4
// ----------------------------------------------------------------------------
// HARDWARE DRIVER CONFIGURATION - These may differ between hardware drivers,
// check the documentation in yours.
// CH32V103 timer, must be a general-purpose timer
#define TIMDAC_HW_TIMER TIM3
// CH32V103 output compare channel
#define TIMDAC_HW_TIMER_CHANNEL 1
// CH32V103 timer interrupt. Optional, if you don't define this you must poll.
#define TIMDAC_HW_TIMER_IRQn TIM3_IRQn
// CH32V103 timer interrupt preemption priority
#define TIMDAC_HW_TIMER_IRQPRIO 3
// CH32V103 timer interrupt subpriority
#define TIMDAC_HW_TIMER_IRQSUBPRIO 3
// CH32V103 GPIO and pin: timer output compare
// You are responsible for making sure this and TIMDAC_HW_TIMER_CHANNEL match
// up, and also for applying any GPIO_PinRemapConfig() and clock startup as
// necessary prior to initializing timdac.
#define TIMDAC_HW_GPIO_TIMER GPIOC, GPIO_Pin_6
// CH32V103 GPIO and pin: mux inhibit
#define TIMDAC_HW_GPIO_MUXINHIBIT GPIOC, GPIO_Pin_3
// CH32V103 GPIO and pin: integrator discharge
#define TIMDAC_HW_GPIO_DISCHARGE GPIOC, GPIO_Pin_5
// CH32V103 GPIO and pin: polarity
// OPTIONAL, do not define if polarity switch is not implemented
#define TIMDAC_HW_GPIO_POLARITY GPIOC, GPIO_Pin_4
// CH32V103 GPIO and pin: tuning comparator
#define TIMDAC_HW_GPIO_TUNE GPIOC, GPIO_Pin_7
// CH32V103 tuning comparator polarity, true = noninverting
#define TIMDAC_HW_GPIO_TUNE_POL false
// CH32V103 GPIO: channel select. All channel select pins must be on the same
// port
#define TIMDAC_HW_GPIO_CHAN_PORT GPIOC
// CH32V103 pins: channel select. Start from the LSB, omit or zero unused
// bits. Up to 8 bits.
#define TIMDAC_HW_GPIO_CHAN_PINS GPIO_Pin_0, GPIO_Pin_1, GPIO_Pin_2
#define TIMDAC_TIMERFREQ_HZ 72000000
// ----------------------------------------------------------------------------
// TIMDAC CONFIGURATION
#include "timdac_config_ref1p0.h"
#endif // !defined(TIMDAC_CONFIG_H)

106
src/timdac_config_ref1p0.h Normal file
View File

@ -0,0 +1,106 @@
// intellectual property is bullshit bgdc
// ============================================================================
// TIMDAC REFERENCE DESIGN CONFIGURATION - REFERENCE DESIGN 1.0
//
// This file is designed to be #included from your main timdac_config.h, and
// provides all the configuration specified for the timdac hardware reference
// design. These parameters should not be changed without good reason, as they
// can affect the DAC performance in surprising ways. If you do need to change
// them, you should #undef and re-#define them after including this file.
//
// The macros in this file depend on a definition in timdac_config.h:
//
// #define TIMDAC_TIMERFREQ_HZ 72000000
// This definition should be set to the base operating frequency of your
// system's timer in Hz. It is strongly recommended not to deviate far
// from the design frequency of 72 MHz.
//
// For the curious, there is a good amount of documentation in this file
// explaining how the parameters affect the output, but for a basic
// implementation you are not required to understand this.
// ============================================================================
#ifndef TIMDAC_CONFIG_REF1P0_H
#define TIMDAC_CONFIG_REF1P0_H
// ----------------------------------------------------------------------------
// Helper macros
// Convert n*R*C into a number of timer counts
#ifdef __GNUC__
#define TIMDAC_NRC(n, r, c) ({ \
_Static_assert((n) * (r) * (c) * TIMDAC_TIMERFREQ_HZ <= 65535, \
"Time constant too high for 16-bit timer"); \
(unsigned)(((n) * (r) * (c) * TIMDAC_TIMERFREQ_HZ)) })
#else
#define TIMDAC_NRC(n, r, c) ((unsigned)((n) * (r) * (c) * TIMDAC_TIMERFREQ_HZ))
#endif
// ----------------------------------------------------------------------------
// Circuit properties
#define TIMDAC_INT_CAP 10e-9
#define TIMDAC_OUT_CAP 100e-9
#define TIMDAC_DISCH_RES 1e+3
#define TIMDAC_OUT_RES 1e+3
// ----------------------------------------------------------------------------
// TIMDAC control parameters
// Numerator of the tuning weight. This weight determines how quickly the DAC
// tuning is pulled towards the result of each tuning operation; adjustments
// are made by a factor of (TIMDAC_TUNE_WEIGHT / 65536). This parameter will
// determine how much tuning noise bleeds through into the output; the RMS
// noise can be measured using the TIMDAC_DIAGNOSTIC option, but should also
// be validated using a precise multimeter.
#define TIMDAC_TUNE_WEIGHT 64
// Tuning noise threshold. Above this amount of error in timer counts, the
// tuning is assumed to have drifted suddenly, and is immediately clobbered.
// Because the tuning algorithm can be noisy, this must be high enough to avoid
// spurious clobbers which can result in sudden shifts in output value. The
// clobber count can be measured using the TIMDAC_DIAGNOSTIC option.
#define TIMDAC_TUNE_NTHRESH 2048
// How many channels may be scanned before tuning. Beware that changing this
// will have ramifications for TIMDAC_TUNE_WEIGHT and TIMDAC_TUNE_NTHRESH.
#define TIMDAC_TUNE_INTERVAL 64
// Integrator discharge time. It is essential that all charge on the integrator
// be fully discharged before the next channel is emitted. This parameter can
// be validated by setting all channels to zero and measuring the output, then
// setting all channels except one to full-scale and ensuring that the channel
// that remains set to zero has not shifted.
//
// REV 1.1 TODO: Rev 1.1 is changing the recommended integrating cap type;
// recheck this afterward. It can probably go down.
#define TIMDAC_DISCHARGE_TIME TIMDAC_NRC(12, TIMDAC_DISCH_RES, TIMDAC_INT_CAP)
// Stabilization time. This is the time after the pulse finishes before we
// transfer the final ramp value to the holding capacitor. This time allows the
// integrator to settle. If too short, transient edge behavior could create
// offsets or ripple in the output. If too long, the integrator will integrate
// leakage current and create an offset error.
#define TIMDAC_STABILIZATION_TIME TIMDAC_NRC(50e-6, 1, 1)
// Fast transfer time in timer counts. This is the time for which the
// integrated ramp is sent to the output when doing fast scanning (no output
// has been changed significantly). It only needs to be fast enough to "top up"
// the output capacitor to account for any leakage that may have occurred
// during a cycle.
#define TIMDAC_FAST_TRANSFER_TIME TIMDAC_NRC(2, TIMDAC_OUT_RES, TIMDAC_OUT_CAP)
// Slow transfer time in timer counts. This is the time for which the
// integrated ramp is sent to the output when performing an output value
// change.
//
// REV 1.1 TODO: This is currently set to the maximum because dielectric
// absorption in the rev 1.0 design is extending the time necessary to set the
// output voltage. After changing to lower absorption capacitors, recheck.
#define TIMDAC_SLOW_TRANSFER_TIME 65535
// How much can an output change without triggering a slow transfer cycle.
#define TIMDAC_SLOW_DELTA 128
#endif // !defined(TIMDAC_CONFIG_REF1P0_H)

282
src/timdac_hw_ch32v103.c Normal file
View File

@ -0,0 +1,282 @@
// intellectual property is bullshit bgdc
// ============================================================================
// TIMDAC HARDWARE DRIVER -- WCH CH32V103
// ----------------------------------------------------------------------------
// This driver implements TIMDAC for the WCH CH32V103 microcontroller. It
// requires the following:
//
// - Core clock = 72 MHz
// - One general-purpose timer module
// - The standard TIMDAC GPIOs:
// - 1 output compare pin
// - 1 mux inhibit pin
// - 1 discharge pin
// - 0 or 1 polarity pins
// - From 0 to 8 channel select pins
//
// To use it, include this source file in your build, and define the following
// in timdac_config.h:
//
// #define TIMDAC_HW_TIMER
// Name of the timer module (e.g. TIM3)
//
// #define TIMDAC_HW_TIMER_CHANNEL
// Number of the output compare channel, from 1 to 4
//
// #define TIMDAC_HW_GPIO_TIMER
// GPIO and pin corresponding to the output compare channel
// (e.g. GPIOC, GPIO_Pin_6)
//
// #define TIMDAC_HW_GPIO_MUXINHIBIT
// GPIO and pin corresponding to the mux inhibit signal
//
// #define TIMDAC_HW_GPIO_DISCHARGE
// GPIO and pin corresponding to the discharge signal
//
// #define TIMDAC_HW_GPIO_POLARITY
// OPTIONAL, GPIO and pin corresponding to the polarity signal, where High
// means Negative. If not using polarity, do not define this.
//
// #define TIMDAC_HW_GPIO_TUNE
// GPIO and pin corresponding to the tuning comparator output signal.
//
// #define TIMDAC_HW_GPIO_TUNE_POL
// Polarity of the tuning comparator; false = inverting with respect to the
// positive-going ramp. Note that the reference design uses inverting
// polarity, so this should generally be false.
//
// #define TIMDAC_HW_GPIO_CHAN_PORT
// GPIO port where the channel select pins live. If there is only one
// channel, this may be left undefined. If this is undefined,
// TIMDAC_HW_GPIO_CHAN_PINS must also be undefined.
// (e.g. GPIOC)
//
// #define TIMDAC_HW_GPIO_CHAN_PINS
// List of channel select pins, least significant bit first, up to eight.
// If there is only one channel, this may be left undefined. If this is
// undefined, TIMDAC_HW_GPIO_CHAN must also be undefined. Note that if
// a channel number is requested that exceeds pow2(CHAN_PINS)-1, the
// channel number will be truncated.
// (e.g. GPIO_Pin_0, GPIO_Pin_1, GPIO_Pin_2)
// ============================================================================
#include "timdac_ll.h"
#include "timdac_config.h"
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <ch32v10x_tim.h>
#include <ch32v10x_gpio.h>
#if defined(TIMDAC_HW_GPIO_CHAN_PORT) && !defined(TIMDAC_HW_GPIO_CHAN_PINS)
# error "timdac_hw: channel select port defined, but no pins"
#elif defined(TIMDAC_HW_GPIO_CHAN_PINS) && !defined(TIMDAC_HW_GPIO_CHAN_PORT)
# error "timdac_hw: channel select pins defined, but no port"
#elif defined(TIMDAC_HW_GPIO_CHAN_PORT)
static const uint16_t _chansel[8] = {TIMDAC_HW_GPIO_CHAN_PINS};
#define N_CHANSEL (sizeof(_chansel)/sizeof(_chansel[0]))
#endif
static uint16_t _ntim_to_channel(uint16_t ntim);
static void _init_gpio(
GPIO_TypeDef * gpio,
uint16_t pin,
GPIOMode_TypeDef mode,
bool fast,
bool value
);
void timdac_hw_init(void)
{
_init_gpio(TIMDAC_HW_GPIO_MUXINHIBIT, GPIO_Mode_Out_PP, false, true);
_init_gpio(TIMDAC_HW_GPIO_DISCHARGE, GPIO_Mode_Out_PP, false, true);
#ifdef TIMDAC_HW_GPIO_POLARITY
_init_gpio(TIMDAC_HW_GPIO_POLARITY, GPIO_Mode_Out_PP, false, false);
#endif
#ifdef TIMDAC_HW_GPIO_CHAN_PORT
for (size_t i = 0; i < N_CHANSEL; i++)
{
_init_gpio(
TIMDAC_HW_GPIO_CHAN_PORT,
_chansel[i],
GPIO_Mode_Out_PP,
false,
false
);
}
#endif
_init_gpio(TIMDAC_HW_GPIO_TUNE, GPIO_Mode_IPU, false, false);
_init_gpio(TIMDAC_HW_GPIO_TIMER, GPIO_Mode_AF_PP, true, false);
TIM_TimeBaseInitTypeDef tbi = {
.TIM_Prescaler = 0,
.TIM_CounterMode = TIM_CounterMode_Up,
.TIM_Period = TIMDAC_DISCHARGE_TIME,
.TIM_ClockDivision = TIM_CKD_DIV1,
.TIM_RepetitionCounter = 0x0000,
};
TIM_TimeBaseInit(TIMDAC_HW_TIMER, &tbi);
TIM_OCInitTypeDef oci = {
.TIM_OCMode = TIM_OCMode_PWM2,
.TIM_OutputState = TIM_OutputState_Enable,
.TIM_OutputNState = TIM_OutputNState_Disable,
.TIM_Pulse = 0,
.TIM_OCPolarity = TIM_OCPolarity_High,
.TIM_OCNPolarity = TIM_OCPolarity_High,
.TIM_OCIdleState = TIM_OCIdleState_Reset, // TIM1/8 only
.TIM_OCNIdleState = TIM_OCIdleState_Reset, // TIM1/8 only
};
switch (TIMDAC_HW_TIMER_CHANNEL) {
case 1:
TIM_OC1Init(TIMDAC_HW_TIMER, &oci);
TIM_OC1PreloadConfig(TIMDAC_HW_TIMER, TIM_OCPreload_Disable);
break;
case 2:
TIM_OC2Init(TIMDAC_HW_TIMER, &oci);
TIM_OC2PreloadConfig(TIMDAC_HW_TIMER, TIM_OCPreload_Disable);
break;
case 3:
TIM_OC3Init(TIMDAC_HW_TIMER, &oci);
TIM_OC3PreloadConfig(TIMDAC_HW_TIMER, TIM_OCPreload_Disable);
break;
case 4:
TIM_OC4Init(TIMDAC_HW_TIMER, &oci);
TIM_OC4PreloadConfig(TIMDAC_HW_TIMER, TIM_OCPreload_Disable);
break;
}
TIM_SelectOnePulseMode(TIMDAC_HW_TIMER, TIM_OPMode_Single);
TIM_CtrlPWMOutputs(TIMDAC_HW_TIMER, ENABLE);
#ifdef TIMDAC_HW_TIMER_IRQn
NVIC_InitTypeDef tim_it_init = {
.NVIC_IRQChannel = TIMDAC_HW_TIMER_IRQn,
.NVIC_IRQChannelPreemptionPriority = TIMDAC_HW_TIMER_IRQPRIO,
.NVIC_IRQChannelSubPriority = TIMDAC_HW_TIMER_IRQSUBPRIO,
.NVIC_IRQChannelCmd = ENABLE,
};
NVIC_Init(&tim_it_init);
TIM_ITConfig(TIMDAC_HW_TIMER, TIM_IT_Update, ENABLE);
#endif
}
void timdac_hw_timer_set_period(uint16_t counts)
{
TIM_SetAutoreload(TIMDAC_HW_TIMER, counts);
}
void timdac_hw_timer_set_compare(uint16_t counts)
{
// Our timer mode results in a pulse that is LOW for `counts`, so
// invert the duration:
uint16_t value = UINT16_MAX - counts;
switch (TIMDAC_HW_TIMER_CHANNEL) {
case 1:
TIM_SetCompare1(TIMDAC_HW_TIMER, value);
break;
case 2:
TIM_SetCompare2(TIMDAC_HW_TIMER, value);
break;
case 3:
TIM_SetCompare3(TIMDAC_HW_TIMER, value);
break;
case 4:
TIM_SetCompare4(TIMDAC_HW_TIMER, value);
break;
}
}
void timdac_hw_timer_set_oc_enabled(bool en)
{
TIM_CCxCmd(TIMDAC_HW_TIMER, _ntim_to_channel(TIMDAC_HW_TIMER_CHANNEL),
en ? TIM_CCx_Enable : TIM_CCx_Disable);
}
void timdac_hw_timer_start(void)
{
TIM_Cmd(TIMDAC_HW_TIMER, ENABLE);
}
bool timdac_hw_timer_running(void)
{
return TIMDAC_HW_TIMER->CTLR1 & TIM_CEN;
}
void timdac_hw_gpio_muxinhibit(bool en)
{
GPIO_WriteBit(TIMDAC_HW_GPIO_MUXINHIBIT, en ? Bit_SET : Bit_RESET);
}
void timdac_hw_gpio_discharge(bool en)
{
GPIO_WriteBit(TIMDAC_HW_GPIO_DISCHARGE, en ? Bit_SET : Bit_RESET);
}
void timdac_hw_gpio_polarity(bool pos)
{
#ifdef TIMDAC_HW_GPIO_POLARITY
GPIO_WriteBit(TIMDAC_HW_GPIO_POLARITY, !pos ? Bit_SET : Bit_RESET);
#endif
}
void timdac_hw_gpio_select_channel(uint8_t chan)
{
#ifdef TIMDAC_HW_GPIO_CHAN_PORT
uint16_t chan_mask = 0, chan_bits = 0;
for (uint8_t i = 0; i < N_CHANSEL; i++, chan >>= 1)
{
if (!_chansel[i]) break;
uint16_t bit = _chansel[i];
chan_mask |= bit;
if (chan & 1) chan_bits |= bit;
}
GPIO_ResetBits(TIMDAC_HW_GPIO_CHAN_PORT, chan_mask);
GPIO_SetBits(TIMDAC_HW_GPIO_CHAN_PORT, chan_bits);
#else
(void) chan;
#endif
}
bool timdac_hw_gpio_tune_is_high(void)
{
return GPIO_ReadInputDataBit(TIMDAC_HW_GPIO_TUNE)
^ !TIMDAC_HW_GPIO_TUNE_POL;
}
static uint16_t _ntim_to_channel(uint16_t ntim)
{
return (ntim - 1) << 2;
}
static void _init_gpio(
GPIO_TypeDef * gpio,
uint16_t pin,
GPIOMode_TypeDef mode,
bool fast,
bool value
)
{
if (mode == GPIO_Mode_Out_OD || mode == GPIO_Mode_Out_PP)
GPIO_WriteBit(gpio, pin, value);
GPIO_InitTypeDef init = {
.GPIO_Pin = pin,
.GPIO_Speed = fast ? GPIO_Speed_50MHz : GPIO_Speed_2MHz,
.GPIO_Mode = mode,
};
GPIO_Init(gpio, &init);
}

290
src/timdac_ll.c Normal file
View File

@ -0,0 +1,290 @@
// intellectual property is bullshit bgdc
#include "timdac_ll.h"
#include "timdac_config.h"
#include "light_output.h"
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
// State definitions
enum state {
STATE_IDLE,
STATE_DISCHARGE,
STATE_PULSE,
STATE_STABILIZE,
STATE_TRANSFER,
};
#define CHAN_TUNE UINT8_MAX
#define VALUE_FLAG_NEG 0x10000000u
#define VALUE_MASK 0x0000FFFFu
// Compute the tuned compare value for a given DAC code
static uint16_t _compare_for_value(timdac_ll_t * td, uint16_t value);
// Start the external hardware cycle for the current state
static void _select(timdac_ll_t * td);
// Perform one step of the tuning SAR binary search
static void _sar_search(timdac_ll_t * td);
#define NTIM_VALID(ntim) ((ntim) >= 1 && (ntim) <= 4)
void timdac_ll_init(timdac_ll_t * td)
{
td->chan = 0;
td->state = STATE_IDLE;
td->tune = 0;
td->slow = false;
td->tune_sar_top = UINT16_MAX;
td->tune_sar_bot = 0;
if (td->tunediag)
{
td->tunediag->cycles = 0;
td->tunediag->clobbers = 0;
td->tunediag->noise = 0;
td->tunediag->tune = 0;
}
timdac_hw_init();
}
bool timdac_ll_tune(timdac_ll_t * td)
{
if (td->tune_sar_top == 0 || td->tune_sar_bot == UINT16_MAX)
{
// Start fresh.
td->tune_sar_top = UINT16_MAX;
td->tune_sar_bot = 0;
return false;
}
else if (td->tune_sar_top != td->tune_sar_bot)
{
// A SAR tuning is starting or in progress.
uint16_t guess =
((uint32_t) td->tune_sar_top
+ (uint32_t) td->tune_sar_bot) / 2;
timdac_hw_timer_set_compare(guess);
td->chan = CHAN_TUNE;
td->state = STATE_DISCHARGE;
_select(td);
return true;
}
else
{
// Done tuning, enter the new value
uint16_t tunevalue = td->tune_sar_top;
int32_t err = tunevalue * 65536L - td->tune;
bool clobber =
err > (TIMDAC_TUNE_NTHRESH * 65536L)
|| err < (TIMDAC_TUNE_NTHRESH * -65536L);
if (clobber)
{
td->tune = tunevalue * 65536UL;
}
else
{
err *= TIMDAC_TUNE_WEIGHT;
err /= UINT16_MAX;
td->tune += err;
}
if (td->tunediag)
{
td->tunediag->tune = td->tune >> 16;
td->tunediag->cycles++;
if (td->tunediag->cycles == 0)
td->tunediag->noise = 0;
uint64_t noise = td->tunediag->noise + err * err;
if (noise < td->tunediag->noise && !clobber)
{
// Overflow
td->tunediag->cycles = 1;
td->tunediag->noise = err * err;
}
else if (!clobber)
td->tunediag->noise = noise;
else
td->tunediag->clobbers++;
}
td->tune_sar_top = UINT16_MAX;
td->tune_sar_bot = 0;
return false;
}
}
void timdac_ll_emit(timdac_ll_t * td, uint8_t chan, int16_t value, bool slow)
{
uint16_t const value_pos =
value >= 0 ? value :
value == INT16_MIN ? INT16_MAX : -value;
uint16_t const comp = _compare_for_value(td, value_pos);
timdac_hw_gpio_polarity(value >= 0);
timdac_hw_timer_set_compare(comp);
td->chan = chan;
td->state = STATE_DISCHARGE;
td->slow = slow;
_select(td);
}
bool timdac_ll_poll(timdac_ll_t * td)
{
if (timdac_hw_timer_running())
return true;
switch (td->state)
{
default:
// fall through
case STATE_IDLE:
td->state = STATE_IDLE;
break;
case STATE_DISCHARGE:
// We just finished discharging the cap. Now, output the pulse.
td->state = STATE_PULSE;
break;
case STATE_PULSE:
// The ramp is complete; give the integrator some time to settle
td->state = STATE_STABILIZE;
break;
case STATE_STABILIZE:
// The ramp has now reached its final value. Transfer it to
// the holding capacitor or save a tuning.
if (td->chan == CHAN_TUNE)
{
td->state = STATE_IDLE;
_sar_search(td);
}
else
{
td->state = STATE_TRANSFER;
}
break;
case STATE_TRANSFER:
// Done!
td->state = STATE_IDLE;
break;
}
_select(td);
return td->state != STATE_IDLE;
}
void timdac_ll_idle(timdac_ll_t * td)
{
if (td->state != STATE_IDLE)
return;
_select(td);
timdac_hw_timer_set_period(100);
timdac_hw_timer_start();
}
static uint16_t _compare_for_value(timdac_ll_t * td, uint16_t value)
{
// Scale value according to tuning, with rounding.
return (value * (td->tune / UINT16_MAX) + 16384) / 32768;
}
static void _select(timdac_ll_t * td)
{
bool inhibit = false;
bool discharge = false;
uint16_t period = UINT16_MAX;
uint8_t state = td->state;
switch (state)
{
case STATE_DISCHARGE:
period = TIMDAC_DISCHARGE_TIME;
// fall through
case STATE_IDLE:
inhibit = true;
discharge = true;
break;
case STATE_STABILIZE:
period = TIMDAC_STABILIZATION_TIME;
// fall through
case STATE_PULSE:
inhibit = true;
discharge = false;
break;
case STATE_TRANSFER:
period = td->slow
? TIMDAC_SLOW_TRANSFER_TIME
: TIMDAC_FAST_TRANSFER_TIME;
inhibit = false;
discharge = false;
break;
}
// Perform channel switching
if (inhibit) timdac_hw_gpio_muxinhibit(true);
if (state == STATE_DISCHARGE) timdac_hw_gpio_select_channel(td->chan);
if (!inhibit) timdac_hw_gpio_muxinhibit(false);
timdac_hw_gpio_discharge(discharge);
// Timer is not going to run now if we're in idle.
if (state == STATE_IDLE)
return;
// Set up the timer and go!
timdac_hw_timer_set_oc_enabled(state == STATE_PULSE);
timdac_hw_timer_set_period(period);
timdac_hw_timer_start();
}
static void _sar_search(timdac_ll_t * td)
{
bool tune_high = timdac_hw_gpio_tune_is_high();
uint16_t guess =
((uint32_t) td->tune_sar_top
+ (uint32_t) td->tune_sar_bot) / 2;
if (tune_high)
{
if (td->tune_sar_top == guess)
td->tune_sar_bot = guess;
else
td->tune_sar_top = guess;
}
else
{
if (td->tune_sar_bot == guess)
td->tune_sar_top = guess;
else
td->tune_sar_bot = guess;
}
if (td->tune_sar_bot > td->tune_sar_top)
{
uint16_t temp = td->tune_sar_bot;
td->tune_sar_bot = td->tune_sar_top;
td->tune_sar_top = temp;
}
}

131
src/timdac_ll.h Normal file
View File

@ -0,0 +1,131 @@
// intellectual property is bullshit bgdc
#ifndef TIMDAC_LL_H
#define TIMDAC_LL_H
// ============================================================================
// TIMDAC LOW LEVEL DRIVER
// ----------------------------------------------------------------------------
// This module provides the low-level (but still hardware-abstracted) part of
// TIMDAC. It implements the state machine that provides each individual cycle
// of operation, but must be sequenced through cycles correctly. You are not
// generally meant to use it directly; see the high-level interface in timdac.h
// which handles this sequencing.
// ============================================================================
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <ch32v10x_tim.h>
#include <ch32v10x_gpio.h>
#define TIMDAC_MAX_SELCHAN 5
typedef struct timdac_tunediag_s {
// Total number of calibration cycles run (will overflow)
uint16_t cycles;
// Tuning clobber counter.
uint16_t clobbers;
// Current tune value.
uint16_t tune;
// Tuning noise accumulator. This will hold the sum of the squares of
// the tuning errors, and will be reset to zero each time cycles wraps
// around. To get the RMS tuning noise, divide this by cycles, take
// the square root, then divide by 65536.
uint64_t noise;
} timdac_tunediag_t;
typedef struct timdac_ll_s {
// #### BEGIN - USER MUST INITIALIZE ####
// ---- Diagnostics ----
// Optional calibration diagnostics struct
timdac_tunediag_t * tunediag;
// #### END - USER MUST INITIALIZE ####
// Tuning value. This is 65536 times the timer setting required to
// produce Vref. May be read but should not be written; if zero, the
// first tuning has not completed yet.
uint32_t tune;
// Tuning successive approximation upper bound
uint16_t tune_sar_top;
// Tuning successive approximation lower bound
uint16_t tune_sar_bot;
// Current channel number
uint8_t chan;
// Current state
uint8_t state;
// Whether performing a slow update
bool slow;
} timdac_ll_t;
// Initialize the DAC. The mux GPIOs will be initialized as well, but you are
// responsible for configuring the timer channel IOs as well as enabling the
// clock to the GPIO and timer.
//
// td: timdac instance
void timdac_ll_init(timdac_ll_t * td);
// Start to tune the DAC. After calling, run timdac_poll() until it
// reports idle.
//
// Tuning requires external hardware support. A comparator must sense
// when the ramp crosses Vref and signal back to us on the input capture line.
// Note that the CH32V10x has no internal comparator; an external one will be
// needed. Do not use hysteresis as this will make the level less exact.
//
// Returns whether the timer is now busy.
bool timdac_ll_tune(timdac_ll_t * td);
// Start to emit a voltage on a channel. The integrating ramp will be started.
// After calling, run timdac_poll() until it reports idle.
//
// If timdac_poll() was not already reporting idle, the previous cycle will be
// aborted.
//
// td: timdac instance
// chan: channel number, sent to the channel select GPIOs. Remember that 0
// is the discharge channel - you should never use it directly. Behavior
// is undefined if you do.
// value: DAC code
// slow: if true, dwell longer during transfer for large updates
void timdac_ll_emit(timdac_ll_t * td, uint8_t chan, int16_t value, bool slow);
// Poll the timer. This must be called repeatedly while a tuning or output
// cycle is in progress - it does not need to be called with perfect regularity,
// but waiting too long may result in some signal droop and loss of accuracy.
//
// td: timdac instance
// return: true if busy, false if idle
bool timdac_ll_poll(timdac_ll_t * td);
// Run a single idle cycle. This can be used to kickstart an interrupt driven
// timdac. If the timdac is not already at the idle state, does nothing.
void timdac_ll_idle(timdac_ll_t * td);
// ----------------------------------------------------------------------------
// HARDWARE DRIVER INTERFACE
// These functions are implemented by each hardware driver.
void timdac_hw_init(void);
void timdac_hw_timer_set_period(uint16_t counts);
void timdac_hw_timer_set_compare(uint16_t counts);
void timdac_hw_timer_set_oc_enabled(bool en);
void timdac_hw_timer_start(void);
bool timdac_hw_timer_running(void);
void timdac_hw_gpio_muxinhibit(bool en);
void timdac_hw_gpio_discharge(bool en);
void timdac_hw_gpio_polarity(bool pos);
void timdac_hw_gpio_select_channel(uint8_t chan);
bool timdac_hw_gpio_tune_is_high(void);
#endif // !defined(TIMDAC_LL_H)