timdac/src/timdac_ll.c

333 lines
6.6 KiB
C

// intellectual property is bullshit bgdc
#include "timdac_ll.h"
#include "timdac_config.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <inttypes.h>
#include <stdatomic.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
static uint32_t _tune;
static uint16_t _tune_sar_top = UINT16_MAX;
static uint16_t _tune_sar_bot;
static uint8_t _chan;
static uint8_t _state;
static bool _slow;
#if TIMDAC_DIAGNOSTIC
static atomic_uint _diag_count;
static timdac_lldiag_t _diag;
#endif
// Compute the tuned compare value for a given DAC code
static uint16_t _compare_for_value(uint16_t value);
// Start the external hardware cycle for the current state. Note the timer
// behavior: in STATE_IDLE, _select() will never start the timer; in other
// states, it will always start the timer.
static void _select(void);
// Perform one step of the tuning SAR binary search by moving _tune_sar_top
// and _tune_sar_bot as appropriate. This function does not perform the
// computation to adjust _tune afterward; that is handled by timdac_ll_tune()
// to keep the more long-winded arithmetic out of ISR context.
static void _sar_search(void);
void timdac_ll_init(void)
{
_chan = 0;
_state = STATE_IDLE;
_tune = 0;
_slow = false;
_tune_sar_top = UINT16_MAX;
_tune_sar_bot = 0;
#if TIMDAC_DIAGNOSTIC
_diag.cycles = 0;
_diag.clobbers = 0;
_diag.noise = 0;
_diag.tune = 0;
#endif
timdac_hw_init();
}
bool timdac_ll_tune(void)
{
if (_tune_sar_top < _tune_sar_bot)
{
// Start fresh.
_tune_sar_top = UINT16_MAX;
_tune_sar_bot = 0;
return false;
}
else if (_tune_sar_top != _tune_sar_bot)
{
// A SAR tuning is starting or in progress.
uint16_t guess =
((uint32_t) _tune_sar_top
+ (uint32_t) _tune_sar_bot) / 2;
timdac_hw_timer_set_compare(guess);
_chan = CHAN_TUNE;
_state = STATE_DISCHARGE;
_select();
return true;
}
else
{
// Done tuning, enter the new value
uint16_t tunevalue = _tune_sar_top;
int32_t err = tunevalue * 65536L - _tune;
bool clobber =
err > (TIMDAC_TUNE_NTHRESH * 65536L)
|| err < (TIMDAC_TUNE_NTHRESH * -65536L);
if (clobber)
{
_tune = tunevalue * 65536UL;
}
else
{
err *= TIMDAC_TUNE_WEIGHT;
err /= UINT16_MAX;
_tune += err;
}
#if TIMDAC_DIAGNOSTIC
_diag.tune = _tune >> 16;
_diag.cycles++;
if (_diag.cycles == 0)
_diag.noise = 0;
uint64_t noise = _diag.noise + err * err;
if (noise < _diag.noise && !clobber)
{
// Overflow
_diag.cycles = 1;
_diag.noise = err * err;
}
else if (!clobber)
_diag.noise = noise;
else
_diag.clobbers++;
atomic_fetch_add(&_diag_count, 1);
#endif
_tune_sar_top = UINT16_MAX;
_tune_sar_bot = 0;
return false;
}
}
void timdac_ll_emit(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(value_pos);
timdac_hw_gpio_polarity(value >= 0);
timdac_hw_timer_set_compare(comp);
_chan = chan;
_state = STATE_DISCHARGE;
_slow = slow;
_select();
}
bool timdac_ll_poll(void)
{
if (timdac_hw_timer_running())
return true;
switch (_state)
{
default:
// fall through
case STATE_IDLE:
_state = STATE_IDLE;
break;
case STATE_DISCHARGE:
// We just finished discharging the cap. Now, output the pulse.
_state = STATE_PULSE;
break;
case STATE_PULSE:
// The ramp is complete; give the integrator some time to settle
_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 (_chan == CHAN_TUNE)
{
_state = STATE_IDLE;
_sar_search();
}
else
{
_state = STATE_TRANSFER;
}
break;
case STATE_TRANSFER:
// Done!
_state = STATE_IDLE;
break;
}
_select();
return _state != STATE_IDLE;
}
void timdac_ll_idle(void)
{
if (_state != STATE_IDLE)
return;
_select();
timdac_hw_timer_set_period(100);
timdac_hw_timer_start();
}
bool timdac_ll_tuned(void)
{
return _tune != 0;
}
void timdac_ll_diagnostic(timdac_lldiag_t * diag)
{
#if TIMDAC_DIAGNOSTIC
// The diagnostic struct is only updated at about 760 Hz, so most of
// the time we'll get it just fine. Don't bother locking, just check a
// counter that increments on each update to ensure it was not changed.
unsigned count;
do {
count = atomic_load(&_diag_count);
memcpy(diag, &_diag, sizeof(timdac_lldiag_t));
} while (count != atomic_load(&_diag_count));
#else
memset(diag, 0, sizeof(timdac_lldiag_t));
#endif
}
static uint16_t _compare_for_value(uint16_t value)
{
// Scale value according to tuning, with rounding.
//
// WARNING: This can affect DNL! The rounding method used can shift
// the DNL plot up or down slightly around zero, which changes the
// maximum value. This method centers it the best for lowest DNL. Do
// not change without testing!
return (value * (_tune / UINT16_MAX) + 16384) / 32768;
}
static void _select(void)
{
bool inhibit = true;
bool discharge = false;
uint16_t period = UINT16_MAX;
uint8_t state = _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 = _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(_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(void)
{
bool tune_high = timdac_hw_gpio_tune_is_high();
uint16_t guess =
((uint32_t) _tune_sar_top
+ (uint32_t) _tune_sar_bot) / 2;
if (tune_high)
{
if (_tune_sar_top == guess)
_tune_sar_bot = guess;
else
_tune_sar_top = guess;
}
else
{
if (_tune_sar_bot == guess)
_tune_sar_top = guess;
else
_tune_sar_bot = guess;
}
if (_tune_sar_bot > _tune_sar_top)
{
uint16_t temp = _tune_sar_bot;
_tune_sar_bot = _tune_sar_top;
_tune_sar_top = temp;
}
}