timdac/src/timdac.c

133 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>
// 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
);
}