135 lines
2.9 KiB
C
135 lines
2.9 KiB
C
// 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>
|
|
|
|
_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
|
|
// 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
|
|
|
|
static atomic_uint _channels[TIMDAC_N_CHANNELS];
|
|
static atomic_uint _pend;
|
|
static atomic_uint _chan;
|
|
static atomic_uint _count;
|
|
|
|
void timdac_init(void)
|
|
{
|
|
_chan = 0;
|
|
_pend = NO_PEND;
|
|
_count = 0;
|
|
|
|
for (size_t i = 0; i < TIMDAC_N_CHANNELS; i++)
|
|
_channels[i] = CHAN_FLAG_SLOW;
|
|
|
|
timdac_ll_init();
|
|
}
|
|
|
|
void timdac_start(void)
|
|
{
|
|
timdac_ll_idle();
|
|
}
|
|
|
|
bool timdac_set(uint8_t chan, int32_t value)
|
|
{
|
|
if (chan >= TIMDAC_N_CHANNELS)
|
|
return false;
|
|
|
|
int32_t prev = CHAN_VALUE(atomic_load(&_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(
|
|
&_channels[chan],
|
|
&prev,
|
|
ENCODE_CHAN(value, slow ? CHAN_FLAG_SLOW : 0)
|
|
);
|
|
|
|
if (!stable)
|
|
atomic_store(&_channels[chan],
|
|
ENCODE_CHAN(value, CHAN_FLAG_SLOW));
|
|
|
|
unsigned no_pend = NO_PEND;
|
|
atomic_compare_exchange_strong(&_pend, &no_pend, chan);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool timdac_pending(void)
|
|
{
|
|
return atomic_load(&_pend) < PEND_SLOT;
|
|
}
|
|
|
|
void timdac_poll(void)
|
|
{
|
|
if (timdac_ll_poll())
|
|
return;
|
|
|
|
_Static_assert(
|
|
TIMDAC_TUNE_INTERVAL <= UINT8_MAX,
|
|
"Calibration interval too high!");
|
|
|
|
if (_count >= TIMDAC_TUNE_INTERVAL || !timdac_ll_tuned())
|
|
{
|
|
// Reached the end, time to tune
|
|
_count = 0;
|
|
if (timdac_ll_tune())
|
|
return;
|
|
}
|
|
|
|
uint8_t chan;
|
|
unsigned pend = atomic_exchange(&_pend, PEND_SLOT);
|
|
|
|
if (pend < PEND_SLOT)
|
|
{
|
|
chan = pend;
|
|
}
|
|
else
|
|
{
|
|
chan = _chan;
|
|
uint8_t next_chan = chan + 1;
|
|
if (next_chan >= TIMDAC_N_CHANNELS)
|
|
next_chan = 0;
|
|
_chan = next_chan;
|
|
|
|
atomic_compare_exchange_strong(&_pend, &pend, NO_PEND);
|
|
}
|
|
|
|
uint32_t chancode = atomic_fetch_and(
|
|
&_channels[chan],
|
|
~CHAN_FLAG_SLOW
|
|
);
|
|
|
|
_count++;
|
|
timdac_ll_emit(
|
|
chan,
|
|
CHAN_VALUE(chancode),
|
|
chancode & CHAN_FLAG_SLOW
|
|
);
|
|
}
|