GEX core repository.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
gex-core/units/adc/_adc_core.c

413 lines
13 KiB

//
// Created by MightyPork on 2018/02/04.
//
#include "platform.h"
#include "unit_base.h"
#define ADC_INTERNAL
#include "_adc_internal.h"
#define DMA_POS(priv) ((priv)->dma_buffer_itemcount - (priv)->DMA_CHx->CNDTR)
static void UADC_JobSendBlockChunk(Job *job)
{
Unit *unit = job->unit;
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
uint32_t start = job->data1;
uint32_t count = job->data2;
bool close = (bool) job->data3;
dbg("Send indices [%d -> %d)", (int)start, (int)(start+count));
TF_TYPE type = close ? EVT_CAPT_DONE : EVT_CAPT_MORE;
TF_Msg msg = {
.frame_id = priv->stream_frame_id,
.len = (TF_LEN) (1 + count*sizeof(uint16_t)),
.type = type,
};
TF_Respond_Multipart(comm, &msg);
TF_Multipart_Payload(comm, &priv->stream_serial, 1);
TF_Multipart_Payload(comm, (uint8_t *) (priv->dma_buffer + start), count * sizeof(uint16_t));
TF_Multipart_Close(comm);
priv->stream_serial++;
}
static void UADC_JobSendEndOfStreamMsg(Job *job)
{
Unit *unit = job->unit;
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
TF_Msg msg = {
.type = EVT_CAPT_DONE,
.frame_id = (TF_ID) job->data1
};
TF_Respond(comm, &msg);
}
void UADC_ReportEndOfStream(Unit *unit)
{
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
Job j = {
.unit = unit,
.data1 = priv->stream_frame_id,
.cb = UADC_JobSendEndOfStreamMsg
};
scheduleJob(&j);
}
void UADC_DMA_Handler(void *arg)
{
Unit *unit = arg;
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
const uint32_t isrsnapshot = priv->DMAx->ISR;
if (LL_DMA_IsActiveFlag_G(isrsnapshot, priv->dma_chnum)) {
const bool tc = LL_DMA_IsActiveFlag_TC(isrsnapshot, priv->dma_chnum);
const bool ht = LL_DMA_IsActiveFlag_HT(isrsnapshot, priv->dma_chnum);
const bool te = LL_DMA_IsActiveFlag_TE(isrsnapshot, priv->dma_chnum);
// check what mode we're in
const bool m_trigd = priv->opmode == ADC_OPMODE_TRIGD;
const bool m_stream = priv->opmode == ADC_OPMODE_STREAM;
const bool m_fixcpt = priv->opmode == ADC_OPMODE_BLCAP;
if (m_trigd || m_stream || m_fixcpt) {
if (ht || tc) {
const uint16_t start = priv->stream_startpos;
uint16_t end;
if (ht) {
dbg("HT");
end = (uint16_t) (priv->dma_buffer_itemcount / 2);
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum);
}
else {
dbg("TC");
end = (uint16_t) priv->dma_buffer_itemcount;
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum);
}
dbg("start %d, end %d", (int)start, (int)end);
assert_param(start <= end);
if (start != end) {
uint32_t sgcount = (end - start) / priv->nb_channels;
if (m_trigd || m_fixcpt) {
sgcount = MIN(priv->trig_stream_remain, sgcount);
priv->trig_stream_remain -= sgcount;
}
bool close = !m_stream && priv->trig_stream_remain == 0;
Job j = {
.unit = unit,
.data1 = start,
.data2 = sgcount * priv->nb_channels,
.data3 = (uint32_t) close,
.cb = UADC_JobSendBlockChunk
};
scheduleJob(&j);
if (close) {
dbg("End of capture");
// If auto-arm enabled, we need to re-arm again.
// However, EOS irq is disabled during the capture.
// We have to wait for the next EOS interrupt to occur.
// TODO verify if keeping the EOS irq enabled during capture has significant performance penalty. If not, we can leave it enabled.
UADC_SwitchMode(unit, (priv->auto_rearm && m_trigd) ? ADC_OPMODE_REARM_PENDING : ADC_OPMODE_IDLE);
}
} else {
dbg("start==end, skip this irq");
}
if (tc) {
priv->stream_startpos = 0;
}
else {
priv->stream_startpos = end;
}
}
} else {
// This shouldn't happen, the interrupt should be disabled in this opmode
dbg("(!) not streaming, DMA IT should be disabled");
if (ht) {
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum);
}
else {
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum);
}
}
if (te) {
// this shouldn't happen - error
dbg("ADC DMA TE!");
LL_DMA_ClearFlag_TE(priv->DMAx, priv->dma_chnum);
}
}
}
void UADC_ADC_EOS_Handler(void *arg)
{
uint64_t timestamp = PTIM_GetMicrotime();
Unit *unit = arg;
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
LL_ADC_ClearFlag_EOS(priv->ADCx);
// Wait for the DMA to complete copying the last sample
uint16_t dmapos;
while ((dmapos = (uint16_t) DMA_POS(priv)) % priv->nb_channels != 0);
uint32_t sample_pos;
if (dmapos == 0) {
sample_pos = (uint32_t) (priv->dma_buffer_itemcount);
} else {
sample_pos = dmapos;
}
sample_pos -= priv->nb_channels;
int cnt = 0; // index of the sample within the group
for (uint32_t i = 0; i < 18; i++) {
if (priv->extended_channels_mask & (1 << i)) {
uint16_t val = priv->dma_buffer[sample_pos+cnt];
cnt++;
priv->averaging_bins[i] =
priv->averaging_bins[i] * (1.0f - priv->avg_factor_as_float) +
((float) val) * priv->avg_factor_as_float;
priv->last_samples[i] = val;
if (i == priv->trigger_source) {
if (priv->opmode == ADC_OPMODE_ARMED) {
dbg("Trig line level %d", (int)val);
bool trigd = false;
uint8_t edge_type = 0;
if (priv->trig_prev_level < priv->trig_level && val >= priv->trig_level) {
dbg("******** Rising edge");
// Rising edge
trigd = (bool) (priv->trig_edge & 0b01);
edge_type = 1;
}
else if (priv->trig_prev_level > priv->trig_level && val <= priv->trig_level) {
dbg("******** Falling edge");
// Falling edge
trigd = (bool) (priv->trig_edge & 0b10);
edge_type = 2;
}
if (trigd) {
UADC_HandleTrigger(unit, edge_type, timestamp);
}
}
else if (priv->opmode == ADC_OPMODE_REARM_PENDING) {
if (!priv->auto_rearm) {
// It looks like the flag was cleared by DISARM before we got a new sample.
// Let's just switch to IDLE
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
} else {
// Re-arming for a new trigger
UADC_SwitchMode(unit, ADC_OPMODE_ARMED);
}
}
priv->trig_prev_level = val;
}
}
}
}
void UADC_HandleTrigger(Unit *unit, uint8_t edge_type, uint64_t timestamp)
{
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
if (priv->trig_holdoff != 0 && priv->trig_holdoff_remain > 0) {
dbg("Trig discarded due to holdoff.");
return;
}
if (priv->trig_holdoff > 0) {
priv->trig_holdoff_remain = priv->trig_holdoff;
// Start the tick
unit->tick_interval = 1;
unit->_tick_cnt = 0;
}
priv->stream_startpos = (uint16_t) DMA_POS(priv);
priv->trig_stream_remain = priv->trig_len;
priv->stream_serial = 0;
// TODO Send pre-trigger
dbg("Trigger condition hit, edge=%d, startpos %d", edge_type, (int)priv->stream_startpos);
UADC_SwitchMode(unit, ADC_OPMODE_TRIGD);
}
void UADC_StartBlockCapture(Unit *unit, uint32_t len, TF_ID frame_id)
{
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
priv->stream_frame_id = frame_id;
priv->stream_startpos = (uint16_t) DMA_POS(priv);
priv->trig_stream_remain = len;
priv->stream_serial = 0;
UADC_SwitchMode(unit, ADC_OPMODE_BLCAP);
}
/** Start stream */
void UADC_StartStream(Unit *unit, TF_ID frame_id)
{
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
priv->stream_frame_id = frame_id;
priv->stream_startpos = (uint16_t) DMA_POS(priv);
priv->stream_serial = 0;
dbg("Start streaming.");
UADC_SwitchMode(unit, ADC_OPMODE_STREAM);
}
/** End stream */
void UADC_StopStream(Unit *unit)
{
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
dbg("Stop stream.");
UADC_ReportEndOfStream(unit);
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
}
/** Handle unit update tick - expire the trigger hold-off */
void UADC_updateTick(Unit *unit)
{
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
if (priv->trig_holdoff_remain > 0) {
priv->trig_holdoff_remain--;
if (priv->trig_holdoff_remain == 0) {
unit->tick_interval = 0;
unit->_tick_cnt = 0;
}
}
}
void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode)
{
assert_param(unit);
struct priv *priv = unit->data;
assert_param(priv);
if (new_mode == priv->opmode) return; // nothing to do
// if un-itied, can go only to IDLE
assert_param((priv->opmode != ADC_OPMODE_UNINIT) || (new_mode == ADC_OPMODE_IDLE));
if (new_mode == ADC_OPMODE_UNINIT) {
dbg("ADC switch -> UNINIT");
// Stop the DMA, timer and disable ADC - this is called before tearing down the unit
LL_TIM_DisableCounter(priv->TIMx);
// Switch off the ADC
if (LL_ADC_IsEnabled(priv->ADCx)) {
// Cancel ongoing conversion
if (LL_ADC_REG_IsConversionOngoing(priv->ADCx)) {
dbg("Stopping ADC conv");
LL_ADC_REG_StopConversion(priv->ADCx);
hw_wait_while(LL_ADC_REG_IsStopConversionOngoing(priv->ADCx), 100);
}
LL_ADC_Disable(priv->ADCx);
dbg("Disabling ADC");
hw_wait_while(LL_ADC_IsDisableOngoing(priv->ADCx), 100);
}
dbg("Disabling DMA");
LL_DMA_DisableChannel(priv->DMAx, priv->dma_chnum);
LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum);
LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum);
}
else if (new_mode == ADC_OPMODE_IDLE || new_mode == ADC_OPMODE_REARM_PENDING) {
dbg("ADC switch -> IDLE or IDLE/REARM_PENDING");
// IDLE and ARMED are identical with the exception that the trigger condition is not checked
// ARMED can be only entered from IDLE, thus we do the init only here.
// In IDLE, we don't need the DMA interrupts
LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum);
LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum);
// Use End Of Sequence to recover results for averaging from the DMA buffer and DR
LL_ADC_ClearFlag_EOS(priv->ADCx);
LL_ADC_EnableIT_EOS(priv->ADCx);
if (priv->opmode == ADC_OPMODE_UNINIT) {
// Nothing is started yet - this is the only way to leave UNINIT
LL_ADC_Enable(priv->ADCx);
LL_DMA_EnableChannel(priv->DMAx, priv->dma_chnum);
LL_TIM_EnableCounter(priv->TIMx);
LL_ADC_REG_StartConversion(priv->ADCx);
}
}
else if (new_mode == ADC_OPMODE_ARMED) {
dbg("ADC switch -> ARMED");
assert_param(priv->opmode == ADC_OPMODE_IDLE || priv->opmode == ADC_OPMODE_REARM_PENDING);
// avoid firing immediately by the value jumping across the scale
priv->trig_prev_level = priv->last_samples[priv->trigger_source];
}
else if (new_mode == ADC_OPMODE_TRIGD ||
new_mode == ADC_OPMODE_STREAM ||
new_mode == ADC_OPMODE_BLCAP) {
dbg("ADC switch -> TRIG'D / STREAM / BLOCK");
assert_param(priv->opmode == ADC_OPMODE_ARMED || priv->opmode == ADC_OPMODE_IDLE);
// during the capture, we disallow direct readout and averaging to reduce overhead
LL_ADC_DisableIT_EOS(priv->ADCx);
// Enable the DMA buffer interrupts
// we must first clear the flags, otherwise it will cause WEIRD bugs in the handler
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum);
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum);
LL_DMA_EnableIT_HT(priv->DMAx, priv->dma_chnum);
LL_DMA_EnableIT_TC(priv->DMAx, priv->dma_chnum);
}
// the actual switch
priv->opmode = new_mode;
}