lots of updates

main
alexis 2023-01-02 21:44:51 -07:00
parent e3217ffa69
commit e38da22af5
6 changed files with 605 additions and 546 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -6,10 +6,98 @@
// ============================================================================
// TIMDAC
// ----------------------------------------------------------------------------
// TIMDAC is a firmware-defined precision 15-bit DAC using external hardware.
// For the full hardware design, see the documentation in this repository.
// In summary, TIMDAC requires:
//
// 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
// - A 32-bit (ideally) platform running at somewhere near 72 MHz
//
// (If you can run the timers at that speed, that's fine - 144 MHz with a
// divide-by-two prescale works. Also, necessary frequency is only about
// plus or minus 20% as long as you scale the slope resistor R1 in the
// reference design accordingly.)
//
// - A 16-bit timer that can emit single pulses on demand and interrupt when
// it's done.
//
// - 5 GPIOs, plus log2(number of channels)
//
// ----------------------------------------------------------------------------
// Configuring TIMDAC
// ----------------------------------------------------------------------------
// TIMDAC requires a configuration file called timdac_config.h to be accessible
// on the include path. The core driver only has a few options, but the
// individual hardware drivers will have many more -- see the documentation in
// the driver files for those options. For the TIMDAC core:
//
// #define TIMDAC_TIMERFREQ_HZ 72000000
// Defines the counting frequency of the timer in Hz. Remember that if you
// are running at a frequency other than 72 MHz, one of the resistors in the
// reference design needs changed -- see the notes on the schematic.
//
// #define TIMDAC_N_CHANNELS 4
// Number of output channels. May be up to 254.
//
// #define TIMDAC_DIAGNOSTIC 1
// Define to nonzero to make the diagnostic functions (currently just
// timdac_ll_diagnostic) work. If zero or undefined, the diagnostic
// functionality is not built in. These diagnostics are mostly used to dial
// in parameters for a new reference design, so you shouldn't need them.
//
// ----------------------------------------------------------------------------
// Configuring TIMDAC (advanced)
// ----------------------------------------------------------------------------
// The following parameters pertain to an individual hardware design. Unless
// you're hacking on TIMDAC itself, you should just use a provided set of
// parameters.
//
// #define TIMDAC_TUNE_WEIGHT
// 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_NTHRESH
// 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_INTERVAL
// 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_DISCHARGE_TIME
// 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.
//
// #define TIMDAC_STABILIZATION_TIME
// 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_FAST_TRANSFER_TIME
// 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_SLOW_TRANSFER_TIME
// 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.
//
// #define TIMDAC_SLOW_DELTA
// How much can an output change without triggering a slow transfer cycle.
// ============================================================================
#include <stdbool.h>
@ -18,12 +106,12 @@
#include <stdatomic.h>
#include "timdac_ll.h"
// Initialize the DAC and DAC scanner. You must have filled in the "USER MUST
// INITIALIZE" fields in the struct.
// Initialize TIMDAC but do not start scanning yet.
void timdac_init(void);
// 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.
// Start TIMDAC. It will immediately start with a tuning cycle, then begin
// scanning channels. If you would like the channels to start with an initial
// value other than zero, you may call timdac_set() prior to timdac_start().
void timdac_start(void);
// Set a new value for an output channel. If no other channel has a pending
@ -41,7 +129,4 @@ bool timdac_set(uint8_t chan, int32_t value);
// can poll for this flag to clear before calling timdac_set().
bool timdac_pending(void);
// Poll the scanner. Should be called periodically.
void timdac_poll(void);
#endif // !defined(TIMDAC_H)

View File

@ -8,17 +8,6 @@
// 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
@ -48,59 +37,25 @@
// ----------------------------------------------------------------------------
// 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)

View File

@ -19,8 +19,8 @@
#include <ch32v10x_tim.h>
#include <ch32v10x_gpio.h>
#define TIMDAC_MAX_SELCHAN 5
// Diagnostic info struct. If TIMDAC_DIAGNOSTIC is defined nonzero, this is the
// struct that timdac_ll_diagnostic() will fill.
typedef struct timdac_lldiag_s {
// Total number of calibration cycles run (will overflow)
uint16_t cycles;
@ -38,41 +38,35 @@ typedef struct timdac_lldiag_s {
uint64_t noise;
} timdac_lldiag_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.
// Initialize the DAC.
void timdac_ll_init(void);
// Start to tune the DAC. After calling, run timdac_poll() until it
// reports idle.
// Initiate a tuning cycle. Once the tuning cycle has been started, it will
// proceed through a sequence as stepped through by timdac_ll_poll(). Note that
// a full tuning requires multiple cycles -- the initial tuning is not complete
// until timdac_ll_tuned() returns true.
//
// 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.
// Undefined (analog) behavior will be produced if this is called when
// timdac_ll_poll() is not yet returning false. Don't do that.
//
// Returns whether the timer is now busy.
// Returns whether the timer is now busy. If not, this "cycle" was
// purely computational and has completed immediately.
bool timdac_ll_tune(void);
// Start to emit a voltage on a channel. The integrating ramp will be started.
// After calling, run timdac_poll() until it reports idle.
// Start to emit a voltage on a channel. Once the cycle has been started, it
// will proceed through a sequence as stepped through by timdac_ll_poll().
//
// If timdac_poll() was not already reporting idle, the previous cycle will be
// aborted.
// Undefined (analog) behavior will be produced if this is called when
// timdac_ll_poll() is not yet returning false. Don't do that.
//
// 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
// chan: channel number to select; will be passed to the hardware driver
// value: DAC code to emit
// slow: whether to perform a slow transfer at the end of the cycle
void timdac_ll_emit(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.
//
// return: true if busy, false if idle
// Run one step of the low-level DAC process. Should be called from
// timdac_poll() every time the timer stops. Returns whether the timer is now
// busy - if not, the last cycle finished and a new one may be started.
bool timdac_ll_poll(void);
// Run a single idle cycle. This can be used to kickstart an interrupt driven
@ -86,6 +80,10 @@ bool timdac_ll_tuned(void);
// Get low-level diagnostics. TIMDAC_DIAGNOSTIC must be defined in
// timdac_config.h for this to work; otherwise the struct will be filled with
// zeros.
//
// This is the only low-level function that may be called by normal users
// rather than the high-level driver, but it is probably not of much interest
// to them.
void timdac_ll_diagnostic(timdac_lldiag_t * diag);
// ----------------------------------------------------------------------------
@ -103,5 +101,8 @@ 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);
// This is a high-level function, but declared here as it is never meant to be
// called by the user. Instead, the hardware driver will call it from an ISR.
void timdac_poll(void);
#endif // !defined(TIMDAC_LL_H)

View File

@ -8,7 +8,8 @@
#include <inttypes.h>
#include <stdlib.h>
#include <stdatomic.h>
#include <signal.h> // TODO use atomics instead
_Static_assert(TIMDAC_N_CHANNELS <= 254, "TIMDAC_N_CHANNELS: too many channels");
// 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
@ -30,8 +31,8 @@
static atomic_uint _channels[TIMDAC_N_CHANNELS];
static atomic_uint _pend;
static sig_atomic_t volatile _chan;
static sig_atomic_t volatile _count;
static atomic_uint _chan;
static atomic_uint _count;
void timdac_init(void)
{