133 lines
2.9 KiB
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
|
|
);
|
|
}
|