Modularization, stage 1
This commit is contained in:
@@ -0,0 +1,700 @@
|
||||
//
|
||||
// Created by MightyPork on 2018/02/04.
|
||||
//
|
||||
// The core functionality of the ADC unit is defined here.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit_base.h"
|
||||
#include "unit_adc.h"
|
||||
|
||||
#define ADC_INTERNAL
|
||||
#include "_adc_internal.h"
|
||||
|
||||
#define DMA_POS(priv) ((priv)->buf_itemcount - (priv)->DMA_CHx->CNDTR)
|
||||
|
||||
/**
|
||||
* Async job to send a chunk of the DMA buffer to PC.
|
||||
* This can't be done directly because the interrupt couldn't wait for the TinyFrame mutex.
|
||||
*
|
||||
* unit - unit
|
||||
* data1 - start index
|
||||
* data2 - number of samples to send
|
||||
* data3 - bit flags: 0x80 if this is the last sample and we should close
|
||||
* 0x01 if this was the TC interrupt (otherwise it's HT)
|
||||
*/
|
||||
static void UADC_JobSendBlockChunk(Job *job)
|
||||
{
|
||||
Unit *unit = job->unit;
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
const uint32_t start = job->data1;
|
||||
const uint32_t count = job->data2;
|
||||
const bool close = (bool) (job->data3 & 0x80);
|
||||
const bool tc = (bool) (job->data3 & 0x01);
|
||||
|
||||
const TF_TYPE type = close ? EVT_CAPT_DONE : EVT_CAPT_MORE;
|
||||
|
||||
TF_Msg msg = {
|
||||
.frame_id = priv->stream_frame_id,
|
||||
.len = (TF_LEN) (1 /*seq*/ + count * sizeof(uint16_t)),
|
||||
.type = type,
|
||||
};
|
||||
|
||||
assert_param(true == 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);
|
||||
|
||||
// Clear the "busy" flags - those are checked in the DMA ISR to detect overrun
|
||||
if (tc) priv->tc_pending = false;
|
||||
else priv->ht_pending = false;
|
||||
|
||||
priv->stream_serial++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async job to send the trigger header.
|
||||
* The header includes info about the trigger + the pre-trigger buffer.
|
||||
*
|
||||
* data1 - index in the DMA buffer at which the captured data willl start
|
||||
* data2 - edge type - 1 rise, 2 fall, 3 forced
|
||||
* timestamp - event stamp
|
||||
* unit - unit
|
||||
*/
|
||||
static void UADC_JobSendTriggerCaptureHeader(Job *job)
|
||||
{
|
||||
Unit *unit = job->unit;
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
EventReport er = {
|
||||
.unit = unit,
|
||||
.type = EVT_CAPT_START,
|
||||
.timestamp = job->timestamp,
|
||||
.length = (priv->pretrig_len + ((priv->pretrig_len > 0)?1:0)) * // see below why +1
|
||||
priv->nb_channels *
|
||||
sizeof(uint16_t) +
|
||||
4 /*pretrig len*/ +
|
||||
1 /*edge*/ +
|
||||
1 /* seq */
|
||||
};
|
||||
|
||||
uint32_t index_trigd = job->data1;
|
||||
uint8_t edge = (uint8_t) job->data2;
|
||||
|
||||
EventReport_Start(&er);
|
||||
priv->stream_frame_id = er.sent_msg_id;
|
||||
{
|
||||
// preamble
|
||||
uint8_t buf[6];
|
||||
PayloadBuilder pb = pb_start(buf, 6, NULL);
|
||||
pb_u32(&pb, priv->pretrig_len);
|
||||
pb_u8(&pb, edge);
|
||||
pb_u8(&pb, priv->stream_serial++); // This is the serial counter for the first chunk
|
||||
// (containing the pre-trigger, or empty if no pretrig configured)
|
||||
EventReport_PB(&pb);
|
||||
|
||||
if (priv->pretrig_len > 0) {
|
||||
// pretrig
|
||||
|
||||
// +1 because we want pretrig 0 to exactly start with the triggering sample
|
||||
uint32_t pretrig_remain = (priv->pretrig_len + 1) * priv->nb_channels;
|
||||
|
||||
assert_param(index_trigd <= priv->buf_itemcount);
|
||||
|
||||
// this is one past the last entry of the triggering capture group
|
||||
if (pretrig_remain > index_trigd) {
|
||||
// used items in the wrap-around part of the buffer
|
||||
uint32_t items_from_end = pretrig_remain - index_trigd;
|
||||
assert_param(priv->buf_itemcount - items_from_end >= index_trigd);
|
||||
|
||||
EventReport_Data((uint8_t *) &priv->dma_buffer[priv->buf_itemcount - items_from_end],
|
||||
items_from_end * sizeof(uint16_t));
|
||||
|
||||
assert_param(items_from_end <= pretrig_remain);
|
||||
pretrig_remain -= items_from_end;
|
||||
}
|
||||
|
||||
assert_param(pretrig_remain <= index_trigd);
|
||||
EventReport_Data((uint8_t *) &priv->dma_buffer[index_trigd - pretrig_remain],
|
||||
pretrig_remain * sizeof(uint16_t));
|
||||
}
|
||||
}
|
||||
EventReport_End();
|
||||
}
|
||||
|
||||
/**
|
||||
* Async job to notify about end of stream
|
||||
*/
|
||||
static void UADC_JobSendEndOfStreamMsg(Job *job)
|
||||
{
|
||||
TF_Msg msg = {
|
||||
.type = EVT_CAPT_DONE,
|
||||
.frame_id = (TF_ID) job->data1
|
||||
};
|
||||
TF_Respond(comm, &msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule sending a event report to the PC that the current stream has ended.
|
||||
* The client library should handle this appropriately.
|
||||
*/
|
||||
void UADC_ReportEndOfStream(Unit *unit)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
Job j = {
|
||||
.unit = unit,
|
||||
.data1 = priv->stream_frame_id, // copy the ID, it may be invalid by the time the cb gets executed
|
||||
.cb = UADC_JobSendEndOfStreamMsg
|
||||
};
|
||||
scheduleJob(&j);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a helper function for the ADC DMA interrupt for handing the different interrupt types (half / full transfer).
|
||||
* It sends the part of the buffer that was just captured via an async job, or aborts on overrun.
|
||||
*
|
||||
* It's split off here to allow calling it for the different flags without repeating code.
|
||||
*
|
||||
* @param unit
|
||||
* @param tc - true if this is the TC interrupt, else HT
|
||||
*/
|
||||
static void handle_httc(Unit *unit, bool tc)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
uint32_t start = priv->stream_startpos;
|
||||
uint32_t end;
|
||||
const bool ht = !tc;
|
||||
|
||||
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 (ht) {
|
||||
end = (priv->buf_itemcount >> 1); // div2
|
||||
}
|
||||
else {
|
||||
end = priv->buf_itemcount;
|
||||
}
|
||||
|
||||
if (start != end) { // this sometimes happened after a trigger, may be unnecessary now
|
||||
if (end < start) {
|
||||
// this was a trap for a bug with missed TC irq, it's hopefully fixed now
|
||||
trap("end < start! %d < %d, tc %d", (int)end, (int)start, (int)tc);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Check for the closing condition
|
||||
const bool close = !m_stream && priv->trig_stream_remain == 0;
|
||||
|
||||
if ((tc && priv->tc_pending) || (ht && priv->ht_pending)) {
|
||||
dbg("(!) ADC DMA not handled in time, abort capture");
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_EMERGENCY_SHUTDOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
// Here we set the tc/ht pending flags for detecting overrun
|
||||
|
||||
Job j = {
|
||||
.unit = unit,
|
||||
.data1 = start,
|
||||
.data2 = sgcount * priv->nb_channels,
|
||||
.data3 = (uint32_t) (close*0x80) | (tc*1), // bitfields to indicate what's happening
|
||||
.cb = UADC_JobSendBlockChunk
|
||||
};
|
||||
|
||||
if (tc)
|
||||
priv->tc_pending = true;
|
||||
else
|
||||
priv->ht_pending = true;
|
||||
|
||||
if (!scheduleJob(&j)) {
|
||||
// Abort if we can't queue - the stream would tear and we'd hog the system with error messages
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_EMERGENCY_SHUTDOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (close) {
|
||||
// If auto-arm is enabled, we need to re-arm again.
|
||||
// However, EOS irq is disabled during the capture so the trigger edge detection would
|
||||
// work on stale data from before this trigger. We have to wait for the next full
|
||||
// conversion (EOS) before arming.
|
||||
UADC_SwitchMode(unit, (priv->auto_rearm && m_trigd) ? ADC_OPMODE_REARM_PENDING : ADC_OPMODE_IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
// Advance the starting position
|
||||
if (tc) {
|
||||
priv->stream_startpos = 0;
|
||||
}
|
||||
else {
|
||||
priv->stream_startpos = priv->buf_itemcount >> 1; // div2
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IRQ handler for the DMA flags.
|
||||
*
|
||||
* We handle flags:
|
||||
* TC - transfer complete
|
||||
* HT - half transfer
|
||||
* TE - transfer error (this should never happen unless there's a bug)
|
||||
*
|
||||
* The buffer works in a circular mode, so we always handle the previous half
|
||||
* or what of it should be sent (if capture started somewhere inside).
|
||||
*
|
||||
* @param arg - the unit, passed via the irq dispatcher
|
||||
*/
|
||||
void UADC_DMA_Handler(void *arg)
|
||||
{
|
||||
Unit *unit = arg;
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
// First thing, grab the flags. They may change during the function.
|
||||
// Working on the live register might cause race conditions.
|
||||
const uint32_t isrsnapshot = priv->DMAx->ISR;
|
||||
|
||||
if (priv->opmode == ADC_OPMODE_UNINIT) {
|
||||
// the IRQ occured while switching mode, clear flags and do nothing else
|
||||
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum);
|
||||
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum);
|
||||
LL_DMA_ClearFlag_TE(priv->DMAx, priv->dma_chnum);
|
||||
return;
|
||||
}
|
||||
|
||||
if (LL_DMA_IsActiveFlag_G(isrsnapshot, priv->dma_chnum)) {
|
||||
// we have some flags set - check which
|
||||
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);
|
||||
|
||||
if (ht) LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum);
|
||||
if (tc) LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum);
|
||||
|
||||
if (te) {
|
||||
// this shouldn't happen - error
|
||||
adc_dbg("ADC DMA TE!");
|
||||
LL_DMA_ClearFlag_TE(priv->DMAx, priv->dma_chnum);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
const uint32_t half = (uint32_t) (priv->buf_itemcount >> 1); // div2
|
||||
if (ht && tc) {
|
||||
// dual event interrupt - may happen if we missed both and they were pending after
|
||||
// interrupts became enabled again (this can happen due to the EOS or other higher prio irq's)
|
||||
|
||||
if (priv->stream_startpos > half) {
|
||||
handle_httc(unit, true); // TC
|
||||
handle_httc(unit, false); // HT
|
||||
} else {
|
||||
handle_httc(unit, false); // HT
|
||||
handle_httc(unit, true); // TC
|
||||
}
|
||||
} else {
|
||||
if (ht && priv->stream_startpos > half) {
|
||||
// We missed the TC interrupt while e.g. setting up the stream / interrupt. catch up!
|
||||
// This fixes a bug with "negative size" for report.
|
||||
handle_httc(unit, true); // TC
|
||||
}
|
||||
|
||||
handle_httc(unit, tc);
|
||||
}
|
||||
} else {
|
||||
// This shouldn't happen, the interrupt should be disabled in this opmode
|
||||
dbg("(!) not streaming, ADC DMA IT should be disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* End of measurement group interrupt handler.
|
||||
* This interrupt records the measured values and checks for trigger.
|
||||
*
|
||||
* @param arg - unit, passed b y irq dispatcher
|
||||
*/
|
||||
void UADC_ADC_EOS_Handler(void *arg)
|
||||
{
|
||||
Unit *unit = arg;
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
const bool can_average = priv->cfg.enable_averaging &&
|
||||
priv->real_frequency_int < UADC_MAX_FREQ_FOR_AVERAGING;
|
||||
|
||||
// Normally
|
||||
uint64_t timestamp = 0;
|
||||
if (priv->opmode == ADC_OPMODE_ARMED) {
|
||||
timestamp = PTIM_GetMicrotime();
|
||||
}
|
||||
|
||||
LL_ADC_ClearFlag_EOS(priv->ADCx);
|
||||
|
||||
if (priv->opmode == ADC_OPMODE_UNINIT) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
uint32_t dmapos = DMA_POS(priv);
|
||||
|
||||
// Wait for the DMA to complete copying the last sample
|
||||
// XXX
|
||||
// experiments revealed this was actually a bug somewhere else and DMA
|
||||
// is quick enough so we don't have to worry about this
|
||||
#if 0
|
||||
uint32_t err = (dmapos % priv->nb_channels);
|
||||
if (err != 0) {
|
||||
GPIOC->BSRR = 0x02;
|
||||
hw_wait_while(((dmapos = DMA_POS(priv)) % priv->nb_channels) != 0, 10);
|
||||
GPIOC->BRR = 0x02;
|
||||
}
|
||||
#endif
|
||||
|
||||
// wrap dmapos to be past the last sample, even if outside the buffer
|
||||
// - so we can subtract nb_channels
|
||||
uint32_t sample_pos;
|
||||
if (dmapos == 0) {
|
||||
sample_pos = (uint32_t) (priv->buf_itemcount);
|
||||
} else {
|
||||
sample_pos = dmapos;
|
||||
}
|
||||
sample_pos -= priv->nb_channels;
|
||||
|
||||
uint16_t val;
|
||||
|
||||
#if 1
|
||||
for (uint32_t j = 0; j < priv->nb_channels; j++) {
|
||||
const uint8_t i = priv->channel_nums[j];
|
||||
val = priv->dma_buffer[sample_pos+j];
|
||||
|
||||
if (can_average) {
|
||||
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;
|
||||
}
|
||||
#else
|
||||
for (uint8_t i = 0; i < 18; i++) {
|
||||
if (channels_mask & (1 << i)) {
|
||||
val = priv->dma_buffer[sample_pos+cnt];
|
||||
|
||||
cnt++;
|
||||
|
||||
if (can_average) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (priv->opmode) {
|
||||
// Triggering condition test
|
||||
case ADC_OPMODE_ARMED:
|
||||
val = priv->last_samples[priv->trigger_source];
|
||||
|
||||
if ((priv->trig_prev_level < priv->trig_level) &&
|
||||
val >= priv->trig_level &&
|
||||
(bool) (priv->trig_edge & 0b01)) {
|
||||
// Rising edge
|
||||
UADC_HandleTrigger(unit, 0b01, timestamp);
|
||||
}
|
||||
else if ((priv->trig_prev_level > priv->trig_level) &&
|
||||
val <= priv->trig_level &&
|
||||
(bool) (priv->trig_edge & 0b10)) {
|
||||
// Falling edge
|
||||
UADC_HandleTrigger(unit, 0b10, timestamp);
|
||||
}
|
||||
priv->trig_prev_level = val;
|
||||
break;
|
||||
|
||||
// auto-rearm was waiting for the next sample
|
||||
case 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);
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a detected trigger - start capture if we're not in hold-off
|
||||
*
|
||||
* @param unit
|
||||
* @param edge_type - edge type, is included in the report
|
||||
* @param timestamp - event time
|
||||
*/
|
||||
void UADC_HandleTrigger(Unit *unit, uint8_t edge_type, uint64_t timestamp)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
if (priv->opmode == ADC_OPMODE_UNINIT) return;
|
||||
|
||||
if (priv->trig_holdoff != 0 && priv->trig_holdoff_remain > 0) {
|
||||
// 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 = 1;
|
||||
}
|
||||
|
||||
priv->stream_startpos = DMA_POS(priv);
|
||||
priv->trig_stream_remain = priv->trig_len;
|
||||
priv->stream_serial = 0;
|
||||
|
||||
// This func may be called from the EOS interrupt, so it's safer to send the header message asynchronously
|
||||
Job j = {
|
||||
.unit = unit,
|
||||
.timestamp = timestamp,
|
||||
.data1 = priv->stream_startpos,
|
||||
.data2 = edge_type,
|
||||
.cb = UADC_JobSendTriggerCaptureHeader
|
||||
};
|
||||
scheduleJob(&j);
|
||||
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_TRIGD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort ongoing capture.
|
||||
*/
|
||||
void UADC_AbortCapture(Unit *unit)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
const enum uadc_opmode old_opmode = priv->opmode;
|
||||
|
||||
priv->auto_rearm = false;
|
||||
|
||||
if (old_opmode == ADC_OPMODE_BLCAP ||
|
||||
old_opmode == ADC_OPMODE_STREAM ||
|
||||
old_opmode == ADC_OPMODE_TRIGD) {
|
||||
UADC_ReportEndOfStream(unit);
|
||||
}
|
||||
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a manual block capture.
|
||||
*
|
||||
* @param unit
|
||||
* @param len - number of samples (groups)
|
||||
* @param frame_id - TF session to re-use for the report (client has a listener set up)
|
||||
*/
|
||||
void UADC_StartBlockCapture(Unit *unit, uint32_t len, TF_ID frame_id)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
if (priv->opmode == ADC_OPMODE_UNINIT) return;
|
||||
|
||||
priv->stream_frame_id = frame_id;
|
||||
priv->stream_startpos = DMA_POS(priv);
|
||||
priv->trig_stream_remain = len;
|
||||
priv->stream_serial = 0;
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_BLCAP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a stream
|
||||
*
|
||||
* @param frame_id - TF session to re-use for the frames (client has a listener set up)
|
||||
*/
|
||||
void UADC_StartStream(Unit *unit, TF_ID frame_id)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
if (priv->opmode == ADC_OPMODE_UNINIT) return;
|
||||
|
||||
priv->stream_frame_id = frame_id;
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* End a stream by user request.
|
||||
*/
|
||||
void UADC_StopStream(Unit *unit)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
if (priv->opmode == ADC_OPMODE_UNINIT) return;
|
||||
|
||||
UADC_ReportEndOfStream(unit);
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unit update tick - expire the trigger hold-off.
|
||||
* We also check for the emergency shutdown condition and clear it.
|
||||
*/
|
||||
void UADC_updateTick(Unit *unit)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
// Recover from shutdown after a delay
|
||||
if (priv->opmode == ADC_OPMODE_EMERGENCY_SHUTDOWN) {
|
||||
adc_dbg("ADC recovering from emergency shutdown");
|
||||
|
||||
UADC_ReportEndOfStream(unit);
|
||||
LL_TIM_EnableCounter(priv->TIMx);
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
|
||||
unit->tick_interval = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (priv->trig_holdoff_remain > 0) {
|
||||
priv->trig_holdoff_remain--;
|
||||
|
||||
if (priv->trig_holdoff_remain == 0) {
|
||||
unit->tick_interval = 0;
|
||||
unit->_tick_cnt = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the ADC operational mode.
|
||||
*
|
||||
* @param unit
|
||||
* @param new_mode - mode to set
|
||||
*/
|
||||
void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
const enum uadc_opmode old_mode = priv->opmode;
|
||||
if (new_mode == old_mode) return; // nothing to do
|
||||
|
||||
// if un-itied, can go only to IDLE
|
||||
assert_param((old_mode != ADC_OPMODE_UNINIT) || (new_mode == ADC_OPMODE_IDLE));
|
||||
|
||||
priv->opmode = ADC_OPMODE_UNINIT;
|
||||
|
||||
if (new_mode == ADC_OPMODE_UNINIT) {
|
||||
adc_dbg("ADC switch -> UNINIT");
|
||||
// Stop the DMA, timer and disable ADC - this is called before tearing down the unit
|
||||
LL_TIM_DisableCounter(priv->TIMx);
|
||||
LL_ADC_ClearFlag_EOS(priv->ADCx);
|
||||
LL_ADC_DisableIT_EOS(priv->ADCx);
|
||||
|
||||
// Switch off the ADC
|
||||
if (LL_ADC_IsEnabled(priv->ADCx)) {
|
||||
// Cancel ongoing conversion
|
||||
if (LL_ADC_REG_IsConversionOngoing(priv->ADCx)) {
|
||||
LL_ADC_REG_StopConversion(priv->ADCx);
|
||||
hw_wait_while(LL_ADC_REG_IsStopConversionOngoing(priv->ADCx), 100);
|
||||
}
|
||||
|
||||
LL_ADC_Disable(priv->ADCx);
|
||||
hw_wait_while(LL_ADC_IsDisableOngoing(priv->ADCx), 100);
|
||||
}
|
||||
|
||||
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);
|
||||
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum);
|
||||
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum);
|
||||
}
|
||||
else if (new_mode == ADC_OPMODE_IDLE || new_mode == ADC_OPMODE_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.
|
||||
|
||||
priv->tc_pending = false;
|
||||
priv->ht_pending = false;
|
||||
|
||||
// In IDLE, we don't need the DMA interrupts
|
||||
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum);
|
||||
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum);
|
||||
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 (old_mode == 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_EMERGENCY_SHUTDOWN) {
|
||||
adc_dbg("ADC switch -> EMERGENCY_STOP");
|
||||
// Emergency shutdown is used when the job queue overflows and the stream is torn
|
||||
// This however doesn't help in the case when user sets such a high frequency
|
||||
// that the whole app becomes unresponsive due to the completion ISR, need to verify the value manually.
|
||||
|
||||
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum);
|
||||
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum);
|
||||
LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum);
|
||||
LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum);
|
||||
|
||||
LL_TIM_DisableCounter(priv->TIMx);
|
||||
UADC_SetSampleRate(unit, 10000); // fallback to a known safe value
|
||||
|
||||
LL_ADC_ClearFlag_EOS(priv->ADCx);
|
||||
LL_ADC_DisableIT_EOS(priv->ADCx);
|
||||
|
||||
unit->tick_interval = 0;
|
||||
unit->_tick_cnt = 250; // 1-off
|
||||
}
|
||||
else if (new_mode == ADC_OPMODE_ARMED) {
|
||||
adc_dbg("ADC switch -> ARMED");
|
||||
assert_param(old_mode == ADC_OPMODE_IDLE || old_mode == 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) {
|
||||
adc_dbg("ADC switch -> CAPTURE");
|
||||
|
||||
assert_param(old_mode == ADC_OPMODE_ARMED || old_mode == 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);
|
||||
|
||||
// those must be as close as possible to the enabling
|
||||
// if not trig'd, we don't care for lost samples before (this could cause a DMA irq miss / ht/tc mismatch with the startpos)
|
||||
if (new_mode != ADC_OPMODE_TRIGD) {
|
||||
priv->stream_startpos = DMA_POS(priv);
|
||||
priv->stream_serial = 0;
|
||||
}
|
||||
LL_DMA_EnableIT_HT(priv->DMAx, priv->dma_chnum);
|
||||
LL_DMA_EnableIT_TC(priv->DMAx, priv->dma_chnum);
|
||||
}
|
||||
|
||||
priv->opmode = new_mode;
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
// ADC unit init and de-init functions
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit_base.h"
|
||||
|
||||
#define ADC_INTERNAL
|
||||
#include "_adc_internal.h"
|
||||
|
||||
/** Allocate data structure and set defaults */
|
||||
error_t UADC_preInit(Unit *unit)
|
||||
{
|
||||
struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv));
|
||||
if (priv == NULL) return E_OUT_OF_MEM;
|
||||
|
||||
priv->cfg.channels = 1<<16; // Tsense by default - always available, easy testing
|
||||
priv->cfg.sample_time = 0b010; // 13.5c - good enough and the default 0b00 value really is useless
|
||||
priv->cfg.frequency = 1000;
|
||||
priv->cfg.buffer_size = 256; // in half-words
|
||||
priv->cfg.averaging_factor = 500; // 0.5
|
||||
priv->cfg.enable_averaging = true;
|
||||
|
||||
priv->opmode = ADC_OPMODE_UNINIT;
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/** Configure frequency */
|
||||
error_t UADC_SetSampleRate(Unit *unit, uint32_t hertz)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
uint16_t presc;
|
||||
uint32_t count;
|
||||
if (!hw_solve_timer(PLAT_APB1_HZ, hertz, true, &presc, &count, &priv->real_frequency)) {
|
||||
dbg("Failed to resolve timer params.");
|
||||
return E_BAD_VALUE;
|
||||
}
|
||||
adc_dbg("Frequency error %d ppm, presc %d, count %d",
|
||||
(int) lrintf(1000000.0f * ((priv->real_frequency - hertz) / (float) hertz)),
|
||||
(int) presc, (int) count);
|
||||
|
||||
LL_TIM_SetPrescaler(priv->TIMx, (uint32_t) (presc - 1));
|
||||
LL_TIM_SetAutoReload(priv->TIMx, count - 1);
|
||||
|
||||
priv->real_frequency_int = hertz;
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the ADC DMA.
|
||||
* This is split to its own function because it's also called when the user adjusts the
|
||||
* enabled channels and we need to re-configure it.
|
||||
*
|
||||
* @param unit
|
||||
*/
|
||||
void UADC_SetupDMA(Unit *unit)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
adc_dbg("Setting up DMA");
|
||||
{
|
||||
uint32_t itemcount = priv->nb_channels * (priv->cfg.buffer_size / (priv->nb_channels));
|
||||
if (itemcount % 2 == 1) itemcount -= priv->nb_channels; // ensure the count is even
|
||||
priv->buf_itemcount = itemcount;
|
||||
|
||||
adc_dbg("DMA item count is %d (%d bytes), There are %d samples per group.",
|
||||
(int)priv->buf_itemcount,
|
||||
(int)(priv->buf_itemcount * sizeof(uint16_t)),
|
||||
(int)priv->nb_channels);
|
||||
|
||||
{
|
||||
LL_DMA_InitTypeDef init;
|
||||
LL_DMA_StructInit(&init);
|
||||
init.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
|
||||
|
||||
init.Mode = LL_DMA_MODE_CIRCULAR;
|
||||
init.NbData = itemcount;
|
||||
|
||||
init.PeriphOrM2MSrcAddress = (uint32_t) &priv->ADCx->DR;
|
||||
init.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD;
|
||||
init.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
||||
|
||||
init.MemoryOrM2MDstAddress = (uint32_t) priv->dma_buffer;
|
||||
init.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD;
|
||||
init.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
||||
|
||||
assert_param(SUCCESS == LL_DMA_Init(priv->DMAx, priv->dma_chnum, &init));
|
||||
}
|
||||
|
||||
// LL_DMA_EnableChannel(priv->DMAx, priv->dma_chnum); // this is done in the switch mode func now
|
||||
}
|
||||
}
|
||||
|
||||
/** Finalize unit set-up */
|
||||
error_t UADC_init(Unit *unit)
|
||||
{
|
||||
bool suc = true;
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
// Written for F072 which has only one ADC
|
||||
|
||||
TRY(rsc_claim(unit, R_ADC1));
|
||||
TRY(rsc_claim(unit, R_DMA1_1));
|
||||
TRY(rsc_claim(unit, R_TIM15));
|
||||
|
||||
priv->DMAx = DMA1;
|
||||
priv->DMA_CHx = DMA1_Channel1;
|
||||
priv->dma_chnum = 1;
|
||||
priv->ADCx = ADC1;
|
||||
priv->ADCx_Common = ADC1_COMMON;
|
||||
priv->TIMx = TIM15;
|
||||
|
||||
// ----------------------- CONFIGURE PINS --------------------------
|
||||
{
|
||||
// Claim and configure all analog pins
|
||||
priv->nb_channels = 0;
|
||||
for (uint8_t i = 0; i <= UADC_MAX_CHANNEL; i++) {
|
||||
if (priv->cfg.channels & (1UL << i)) {
|
||||
priv->channel_nums[priv->nb_channels] = (uint8_t) i;
|
||||
priv->nb_channels++;
|
||||
|
||||
do {
|
||||
char c;
|
||||
uint8_t num;
|
||||
if (i <= 7) {
|
||||
c = 'A';
|
||||
num = i;
|
||||
}
|
||||
else if (i <= 9) {
|
||||
c = 'B';
|
||||
num = (uint8_t) (i - 8);
|
||||
}
|
||||
else if (i <= 15) {
|
||||
c = 'C';
|
||||
num = (uint8_t) (i - 10);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
|
||||
TRY(rsc_claim_pin(unit, c, num));
|
||||
uint32_t ll_pin = hw_pin2ll(num, &suc);
|
||||
GPIO_TypeDef *port = hw_port2periph(c, &suc);
|
||||
assert_param(suc);
|
||||
|
||||
LL_GPIO_SetPinPull(port, ll_pin, LL_GPIO_PULL_NO);
|
||||
LL_GPIO_SetPinMode(port, ll_pin, LL_GPIO_MODE_ANALOG);
|
||||
} while (0);
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->nb_channels == 0) {
|
||||
dbg("Need at least 1 channel");
|
||||
return E_BAD_CONFIG;
|
||||
}
|
||||
|
||||
// ensure some minimal space is available
|
||||
if (priv->cfg.buffer_size < priv->nb_channels * 2) {
|
||||
dbg("Insufficient buf size");
|
||||
return E_BAD_CONFIG;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- Alloc the buffer ----------------------
|
||||
adc_dbg("Allocating buffer of size %d half-words", (int)priv->cfg.buffer_size);
|
||||
priv->dma_buffer = calloc_ck(priv->cfg.buffer_size, sizeof(uint16_t));
|
||||
if (NULL == priv->dma_buffer) return E_OUT_OF_MEM;
|
||||
assert_param(((uint32_t) priv->dma_buffer & 3) == 0); // must be aligned
|
||||
|
||||
// ------------------- ENABLE CLOCKS --------------------------
|
||||
{
|
||||
// enable peripherals clock
|
||||
hw_periph_clock_enable(priv->ADCx);
|
||||
hw_periph_clock_enable(priv->TIMx);
|
||||
// DMA and GPIO clocks are enabled on startup automatically
|
||||
}
|
||||
|
||||
// ------------------- CONFIGURE THE TIMER --------------------------
|
||||
adc_dbg("Setting up TIMER");
|
||||
{
|
||||
TRY(UADC_SetSampleRate(unit, priv->cfg.frequency));
|
||||
|
||||
LL_TIM_EnableARRPreload(priv->TIMx);
|
||||
LL_TIM_EnableUpdateEvent(priv->TIMx);
|
||||
LL_TIM_SetTriggerOutput(priv->TIMx, LL_TIM_TRGO_UPDATE);
|
||||
LL_TIM_GenerateEvent_UPDATE(priv->TIMx); // load the prescaller value
|
||||
}
|
||||
|
||||
// --------------------- CONFIGURE THE ADC ---------------------------
|
||||
adc_dbg("Setting up ADC");
|
||||
{
|
||||
// Calibrate the ADC
|
||||
adc_dbg("Wait for calib");
|
||||
LL_ADC_StartCalibration(priv->ADCx);
|
||||
while (LL_ADC_IsCalibrationOnGoing(priv->ADCx)) {}
|
||||
adc_dbg("ADC calibrated.");
|
||||
|
||||
// Let's just enable the internal channels always - makes toggling them on-line easier
|
||||
LL_ADC_SetCommonPathInternalCh(priv->ADCx_Common, LL_ADC_PATH_INTERNAL_VREFINT | LL_ADC_PATH_INTERNAL_TEMPSENSOR);
|
||||
|
||||
LL_ADC_SetDataAlignment(priv->ADCx, LL_ADC_DATA_ALIGN_RIGHT);
|
||||
LL_ADC_SetResolution(priv->ADCx, LL_ADC_RESOLUTION_12B);
|
||||
LL_ADC_REG_SetDMATransfer(priv->ADCx, LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
|
||||
|
||||
// configure channels
|
||||
priv->channels_mask = priv->cfg.channels;
|
||||
|
||||
priv->ADCx->CHSELR = priv->channels_mask;
|
||||
|
||||
LL_ADC_REG_SetTriggerSource(priv->ADCx, LL_ADC_REG_TRIG_EXT_TIM15_TRGO);
|
||||
|
||||
LL_ADC_SetSamplingTimeCommonChannels(priv->ADCx, LL_ADC_SAMPLETIMES[priv->cfg.sample_time]);
|
||||
|
||||
// will be enabled when switching to INIT mode
|
||||
}
|
||||
|
||||
// --------------------- CONFIGURE DMA -------------------------------
|
||||
UADC_SetupDMA(unit);
|
||||
|
||||
// prepare the avg factor float for the ISR
|
||||
if (priv->cfg.averaging_factor > 1000) priv->cfg.averaging_factor = 1000; // normalize
|
||||
priv->avg_factor_as_float = priv->cfg.averaging_factor/1000.0f;
|
||||
|
||||
adc_dbg("ADC peripherals configured.");
|
||||
|
||||
irqd_attach(priv->DMA_CHx, UADC_DMA_Handler, unit);
|
||||
irqd_attach(priv->ADCx, UADC_ADC_EOS_Handler, unit);
|
||||
adc_dbg("irqs attached");
|
||||
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
|
||||
adc_dbg("ADC done");
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
/** Tear down the unit */
|
||||
void UADC_deInit(Unit *unit)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
// de-init peripherals
|
||||
if (unit->status == E_SUCCESS ) {
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_UNINIT);
|
||||
|
||||
//LL_ADC_DeInit(priv->ADCx);
|
||||
LL_ADC_CommonDeInit(priv->ADCx_Common);
|
||||
LL_TIM_DeInit(priv->TIMx);
|
||||
|
||||
irqd_detach(priv->DMA_CHx, UADC_DMA_Handler);
|
||||
irqd_detach(priv->ADCx, UADC_ADC_EOS_Handler);
|
||||
|
||||
LL_DMA_DeInit(priv->DMAx, priv->dma_chnum);
|
||||
}
|
||||
|
||||
// free buffer if not NULL
|
||||
free_ck(priv->dma_buffer);
|
||||
|
||||
// Release all resources, deinit pins
|
||||
rsc_teardown(unit);
|
||||
|
||||
// Free memory
|
||||
free_ck(unit->data);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
// Defines and prototypes used internally by the ADC unit.
|
||||
//
|
||||
|
||||
#ifndef GEX_F072_ADC_INTERNAL_H
|
||||
#define GEX_F072_ADC_INTERNAL_H
|
||||
|
||||
#ifndef ADC_INTERNAL
|
||||
#error bad include!
|
||||
#endif
|
||||
|
||||
#include "unit_base.h"
|
||||
|
||||
//#define adc_dbg dbg
|
||||
#define adc_dbg(...) do {} while(0)
|
||||
|
||||
#define UADC_MAX_FREQ_FOR_AVERAGING 20000
|
||||
|
||||
#define UADC_MAX_CHANNEL 17
|
||||
|
||||
enum uadc_opmode {
|
||||
ADC_OPMODE_UNINIT, //!< Not yet switched to any mode
|
||||
ADC_OPMODE_IDLE, //!< Idle. Allows immediate value readout and averaging.
|
||||
ADC_OPMODE_REARM_PENDING, //!< Idle, waiting for the next sample to re-arm (auto trigger).
|
||||
ADC_OPMODE_ARMED, //!< Armed for a trigger. Direct access and averaging are disabled.
|
||||
ADC_OPMODE_TRIGD, //!< Triggered, sending pre-trigger and streaming captured data.
|
||||
ADC_OPMODE_BLCAP, //!< Capture of fixed length without a trigger
|
||||
ADC_OPMODE_STREAM, //!< Unlimited capture
|
||||
ADC_OPMODE_EMERGENCY_SHUTDOWN, //!< Used when the buffers overrun to safely transition to IDLE after a delay
|
||||
};
|
||||
|
||||
enum uadc_event {
|
||||
EVT_CAPT_START = 50, //!< Capture start (used in event in the first frame when trigger is detected)
|
||||
EVT_CAPT_MORE = 51, //!< Capture data payload (used as TYPE for all capture types)
|
||||
EVT_CAPT_DONE = 52, //!< End of trig'd or block capture payload (last frame with data),
|
||||
//!< or a farewell message after closing stream using abort(), in this case without data.
|
||||
};
|
||||
|
||||
/** Private data structure */
|
||||
struct priv {
|
||||
struct {
|
||||
uint32_t channels; //!< bit flags (will be recorded in order 0-15)
|
||||
uint8_t sample_time; //!< 0-7 (corresponds to 1.5-239.5 cycles) - time for the sampling capacitor to charge
|
||||
uint32_t frequency; //!< Timer frequency in Hz. Note: not all frequencies can be achieved accurately
|
||||
uint32_t buffer_size; //!< Buffer size in bytes (count 2 bytes per channel per measurement) - faster sampling freq needs bigger buffer
|
||||
uint16_t averaging_factor; //!< Exponential averaging factor 0-1000
|
||||
bool enable_averaging;
|
||||
} cfg;
|
||||
|
||||
// Peripherals
|
||||
ADC_TypeDef *ADCx; //!< The ADC peripheral used
|
||||
ADC_Common_TypeDef *ADCx_Common; //!< The ADC common control block
|
||||
TIM_TypeDef *TIMx; //!< ADC timing timer instance
|
||||
DMA_TypeDef *DMAx; //!< DMA isnatnce used
|
||||
uint8_t dma_chnum; //!< DMA channel number
|
||||
DMA_Channel_TypeDef *DMA_CHx; //!< DMA channel instance
|
||||
|
||||
uint8_t channel_nums[18];
|
||||
|
||||
// Live config
|
||||
float real_frequency;
|
||||
uint32_t real_frequency_int;
|
||||
uint32_t channels_mask; //!< channels bitfield including tsense and vref
|
||||
float avg_factor_as_float;
|
||||
|
||||
uint16_t *dma_buffer; //!< malloc'd buffer for the samples
|
||||
uint8_t nb_channels; //!< nbr of enabled adc channels
|
||||
uint32_t buf_itemcount; //!< real size of the buffer in samples (adjusted to fit 2x whole multiple of sample group)
|
||||
|
||||
// Trigger state
|
||||
uint32_t trig_stream_remain; //!< Counter of samples remaining to be sent in the post-trigger stream
|
||||
uint16_t trig_holdoff_remain; //!< Tmp counter for the currently active hold-off
|
||||
uint16_t trig_prev_level; //!< Value of the previous sample, used to detect trigger edge
|
||||
uint32_t stream_startpos; //!< Byte offset in the DMA buffer where the next capture for a stream should start.
|
||||
//!< Updated in TH/TC and on trigger (after the preceding data is sent as a pretrig buffer)
|
||||
|
||||
enum uadc_opmode opmode; //!< OpMode (state machine state)
|
||||
float averaging_bins[18]; //!< Averaging buffers, enough space to accommodate all channels (16 external + 2 internal)
|
||||
uint16_t last_samples[18]; //!< If averaging is disabled, the last captured sample is stored here.
|
||||
|
||||
// Trigger config
|
||||
uint8_t trigger_source; //!< number of the pin selected as a trigger source
|
||||
uint32_t pretrig_len; //!< Pre-trigger length, nbr of historical samples to report when trigger occurs
|
||||
uint32_t trig_len; //!< Trigger length, nbr of samples to report AFTER a trigger occurs
|
||||
uint16_t trig_level; //!< Triggering level in LSB
|
||||
uint8_t trig_edge; //!< Which edge we want to trigger on. 1-rising, 2-falling, 3-both
|
||||
bool auto_rearm; //!< Flag that the trigger should be re-armed after the stream finishes
|
||||
uint16_t trig_holdoff; //!< Trigger hold-off time, set when configuring the trigger
|
||||
TF_ID stream_frame_id; //!< Session ID for multi-part stream (response or report)
|
||||
uint8_t stream_serial; //!< Serial nr of a stream frame
|
||||
|
||||
bool tc_pending;
|
||||
bool ht_pending;
|
||||
};
|
||||
|
||||
/** Allocate data structure and set defaults */
|
||||
error_t UADC_preInit(Unit *unit);
|
||||
|
||||
/** Load from a binary buffer stored in Flash */
|
||||
void UADC_loadBinary(Unit *unit, PayloadParser *pp);
|
||||
|
||||
/** Write to a binary buffer for storing in Flash */
|
||||
void UADC_writeBinary(Unit *unit, PayloadBuilder *pb);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Parse a key-value pair from the INI file */
|
||||
error_t UADC_loadIni(Unit *unit, const char *key, const char *value);
|
||||
|
||||
/** Generate INI file section for the unit */
|
||||
void UADC_writeIni(Unit *unit, IniWriter *iw);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Finalize unit set-up */
|
||||
error_t UADC_init(Unit *unit);
|
||||
|
||||
/** Tear down the unit */
|
||||
void UADC_deInit(Unit *unit);
|
||||
|
||||
/** Configure DMA (buffer count etc) */
|
||||
void UADC_SetupDMA(Unit *unit);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** DMA half/complete handler */
|
||||
void UADC_DMA_Handler(void *arg);
|
||||
|
||||
/** ADC eod of sequence handler */
|
||||
void UADC_ADC_EOS_Handler(void *arg);
|
||||
|
||||
/** Switch to a different opmode */
|
||||
void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode);
|
||||
|
||||
/** Handle trigger - process pre-trigger and start streaming the requested number of samples */
|
||||
void UADC_HandleTrigger(Unit *unit, uint8_t edge_type, uint64_t timestamp);
|
||||
|
||||
/** Handle a periodic tick - expiring the hold-off */
|
||||
void UADC_updateTick(Unit *unit);
|
||||
|
||||
/** Send a end-of-stream message to PC's stream listener so it can shut down. */
|
||||
void UADC_ReportEndOfStream(Unit *unit);
|
||||
|
||||
/** Start a block capture */
|
||||
void UADC_StartBlockCapture(Unit *unit, uint32_t len, TF_ID frame_id);
|
||||
|
||||
/** Start stream */
|
||||
void UADC_StartStream(Unit *unit, TF_ID frame_id);
|
||||
|
||||
/** End stream */
|
||||
void UADC_StopStream(Unit *unit);
|
||||
|
||||
/** Configure frequency */
|
||||
error_t UADC_SetSampleRate(Unit *unit, uint32_t hertz);
|
||||
|
||||
/** Abort capture */
|
||||
void UADC_AbortCapture(Unit *unit);
|
||||
|
||||
#endif //GEX_F072_ADC_INTERNAL_H
|
||||
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
// ADC unit settings reading / parsing
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit_base.h"
|
||||
|
||||
#define ADC_INTERNAL
|
||||
#include "_adc_internal.h"
|
||||
|
||||
/** Load from a binary buffer stored in Flash */
|
||||
void UADC_loadBinary(Unit *unit, PayloadParser *pp)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
uint8_t version = pp_u8(pp);
|
||||
(void)version;
|
||||
|
||||
priv->cfg.channels = pp_u32(pp);
|
||||
priv->cfg.sample_time = pp_u8(pp);
|
||||
priv->cfg.frequency = pp_u32(pp);
|
||||
priv->cfg.buffer_size = pp_u32(pp);
|
||||
priv->cfg.averaging_factor = pp_u16(pp);
|
||||
|
||||
if (version >= 1) {
|
||||
priv->cfg.enable_averaging = pp_bool(pp);
|
||||
}
|
||||
}
|
||||
|
||||
/** Write to a binary buffer for storing in Flash */
|
||||
void UADC_writeBinary(Unit *unit, PayloadBuilder *pb)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
pb_u8(pb, 1); // version
|
||||
|
||||
pb_u32(pb, priv->cfg.channels);
|
||||
pb_u8(pb, priv->cfg.sample_time);
|
||||
pb_u32(pb, priv->cfg.frequency);
|
||||
pb_u32(pb, priv->cfg.buffer_size);
|
||||
pb_u16(pb, priv->cfg.averaging_factor);
|
||||
pb_bool(pb, priv->cfg.enable_averaging);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Parse a key-value pair from the INI file */
|
||||
error_t UADC_loadIni(Unit *unit, const char *key, const char *value)
|
||||
{
|
||||
bool suc = true;
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
if (streq(key, "channels")) {
|
||||
priv->cfg.channels = cfg_pinmask_parse_32(value, &suc);
|
||||
}
|
||||
else if (streq(key, "sample_time")) {
|
||||
priv->cfg.sample_time = cfg_u8_parse(value, &suc);
|
||||
if (priv->cfg.sample_time > 7) return E_BAD_VALUE;
|
||||
}
|
||||
else if (streq(key, "frequency")) {
|
||||
priv->cfg.frequency = cfg_u32_parse(value, &suc);
|
||||
}
|
||||
else if (streq(key, "buffer_size")) {
|
||||
priv->cfg.buffer_size = cfg_u32_parse(value, &suc);
|
||||
}
|
||||
else if (streq(key, "avg_factor")) {
|
||||
priv->cfg.averaging_factor = cfg_u16_parse(value, &suc);
|
||||
if (priv->cfg.averaging_factor > 1000) return E_BAD_VALUE;
|
||||
}
|
||||
else if (streq(key, "averaging")) {
|
||||
priv->cfg.enable_averaging = cfg_bool_parse(value, &suc);
|
||||
}
|
||||
else {
|
||||
return E_BAD_KEY;
|
||||
}
|
||||
|
||||
if (!suc) return E_BAD_VALUE;
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
/** Generate INI file section for the unit */
|
||||
void UADC_writeIni(Unit *unit, IniWriter *iw)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
iw_comment(iw, "Enabled channels, comma separated");
|
||||
iw_comment(iw, " 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17");
|
||||
iw_comment(iw, "A0 A1 A2 A3 A4 A5 A6 A7 B0 B1 C0 C1 C2 C3 C4 C5 Tsens Vref");
|
||||
iw_entry_s(iw, "channels", cfg_pinmask_encode(priv->cfg.channels, unit_tmp512, true));
|
||||
|
||||
iw_cmt_newline(iw);
|
||||
iw_comment(iw, "Sampling time (0-7)");
|
||||
iw_entry_d(iw, "sample_time", priv->cfg.sample_time);
|
||||
|
||||
iw_comment(iw, "Sampling frequency (Hz)");
|
||||
iw_entry_d(iw, "frequency", priv->cfg.frequency);
|
||||
|
||||
iw_cmt_newline(iw);
|
||||
iw_comment(iw, "Sample buffer size");
|
||||
iw_comment(iw, "- shared by all enabled channels");
|
||||
iw_comment(iw, "- defines the maximum pre-trigger size (divide by # of channels)");
|
||||
iw_comment(iw, "- captured data is sent in half-buffer chunks");
|
||||
iw_comment(iw, "- buffer overrun aborts the data capture");
|
||||
iw_entry_d(iw, "buffer_size", priv->cfg.buffer_size);
|
||||
|
||||
iw_cmt_newline(iw);
|
||||
iw_comment(iw, "Enable continuous sampling with averaging");
|
||||
iw_comment(iw, "Caution: This can cause DAC output glitches");
|
||||
iw_entry_s(iw, "averaging", str_yn(priv->cfg.enable_averaging));
|
||||
iw_comment(iw, "Exponential averaging coefficient (permil, range 0-1000 ~ 0.000-1.000)");
|
||||
iw_comment(iw, "- used formula: y[t]=(1-k)*y[t-1]+k*u[t]");
|
||||
iw_comment(iw, "- not available when a capture is running");
|
||||
iw_entry_d(iw, "avg_factor", priv->cfg.averaging_factor);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/25.
|
||||
//
|
||||
|
||||
#include "unit_base.h"
|
||||
#include "unit_adc.h"
|
||||
|
||||
#define ADC_INTERNAL
|
||||
#include "_adc_internal.h"
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
enum AdcCmd_ {
|
||||
CMD_READ_RAW = 0,
|
||||
CMD_READ_SMOOTHED = 1,
|
||||
CMD_READ_CAL_CONSTANTS = 2,
|
||||
|
||||
CMD_GET_ENABLED_CHANNELS = 10,
|
||||
CMD_GET_SAMPLE_RATE = 11,
|
||||
|
||||
CMD_SETUP_TRIGGER = 20,
|
||||
CMD_ARM = 21,
|
||||
CMD_DISARM = 22,
|
||||
CMD_ABORT = 23, // abort any ongoing capture or stream
|
||||
CMD_FORCE_TRIGGER = 24,
|
||||
CMD_BLOCK_CAPTURE = 25,
|
||||
CMD_STREAM_START = 26,
|
||||
CMD_STREAM_STOP = 27,
|
||||
CMD_SET_SMOOTHING_FACTOR = 28,
|
||||
CMD_SET_SAMPLE_RATE = 29,
|
||||
CMD_ENABLE_CHANNELS = 30,
|
||||
CMD_SET_SAMPLE_TIME = 31,
|
||||
};
|
||||
|
||||
/** Handle a request message */
|
||||
static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL);
|
||||
|
||||
switch (command) {
|
||||
/**
|
||||
* Get enabled channels.
|
||||
* Response: bytes with indices of enabled channels, ascending order.
|
||||
*/
|
||||
case CMD_GET_ENABLED_CHANNELS:
|
||||
for (uint8_t i = 0; i < 18; i++) {
|
||||
if (priv->channels_mask & (1 << i)) {
|
||||
pb_u8(&pb, i);
|
||||
}
|
||||
}
|
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Set the sample rate in Hz
|
||||
* plad: hz:u32
|
||||
*/
|
||||
case CMD_SET_SAMPLE_RATE:
|
||||
{
|
||||
uint32_t freq = pp_u32(pp);
|
||||
if (freq == 0) return E_BAD_VALUE;
|
||||
|
||||
TRY(UADC_SetSampleRate(unit, freq));
|
||||
}
|
||||
// Pass through - send back the obtained sample rate
|
||||
/**
|
||||
* Read the real used frequency, expressed as float.
|
||||
* May differ from the configured or requested value due to prescaller limitations.
|
||||
*/
|
||||
case CMD_GET_SAMPLE_RATE:
|
||||
pb_u32(&pb, priv->real_frequency_int);
|
||||
pb_float(&pb, priv->real_frequency);
|
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Set smoothing factor 0-1000.
|
||||
* pld: u16:factor
|
||||
*/
|
||||
case CMD_SET_SMOOTHING_FACTOR:
|
||||
{
|
||||
uint16_t fac = pp_u16(pp);
|
||||
if (fac > 1000) return E_BAD_VALUE;
|
||||
priv->avg_factor_as_float = fac / 1000.0f;
|
||||
}
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Set sample time
|
||||
* pld: u8:0-7
|
||||
*/
|
||||
case CMD_SET_SAMPLE_TIME:
|
||||
{
|
||||
uint8_t tim = pp_u8(pp);
|
||||
if (tim > 7) return E_BAD_VALUE;
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_UNINIT);
|
||||
{
|
||||
LL_ADC_SetSamplingTimeCommonChannels(priv->ADCx, LL_ADC_SAMPLETIMES[tim]);
|
||||
}
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
|
||||
}
|
||||
return E_SUCCESS;
|
||||
|
||||
/** Read ADC calibration constants */
|
||||
case CMD_READ_CAL_CONSTANTS:
|
||||
{
|
||||
pb_u16(&pb, *VREFINT_CAL_ADDR); // VREFINT_CAL
|
||||
pb_u16(&pb, VREFINT_CAL_VREF); // Vref pin voltage during calibration (usually bonded to Vdd)
|
||||
|
||||
pb_u16(&pb, *TEMPSENSOR_CAL1_ADDR); // TEMPSENSOR_CAL1
|
||||
pb_u16(&pb, *TEMPSENSOR_CAL2_ADDR); // TEMPSENSOR_CAL2
|
||||
pb_u8(&pb, TEMPSENSOR_CAL1_TEMP); // temperature for CAL1
|
||||
pb_u8(&pb, TEMPSENSOR_CAL2_TEMP); // temperature for CAL2
|
||||
pb_u16(&pb, TEMPSENSOR_CAL_VREFANALOG); // VREFINT_CAL_VREF - Vref pin voltage during calibration (usually bonded to Vdd)
|
||||
|
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb);
|
||||
}
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Enable channels. The channels must've been configured in the settings (except ch 16 and 17 which are available always)
|
||||
* pld: u32: bitmap of channels
|
||||
*/
|
||||
case CMD_ENABLE_CHANNELS:
|
||||
{
|
||||
uint32_t new_channels = pp_u32(pp);
|
||||
|
||||
// this tears down the peripherals sufficiently so we can re-configure them. Going back to IDLE re-inits this
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_UNINIT);
|
||||
|
||||
uint32_t illegal_channels = new_channels & ~(priv->cfg.channels | (1<<16) | (1<<17)); // 16 and 17 may be enabled always
|
||||
if (illegal_channels != 0) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Some requested channels not available");
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
uint8_t nb_channels = 0;
|
||||
// count the enabled channels
|
||||
for(int i = 0; i < 32; i++) {
|
||||
if (new_channels & (1<<i)) {
|
||||
priv->channel_nums[nb_channels] = (uint8_t) i;
|
||||
nb_channels++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nb_channels == 0) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Need at least 1 channel");
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
if (priv->cfg.buffer_size < nb_channels * 2) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Insufficient buf size");
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
|
||||
return E_BAD_CONFIG;
|
||||
}
|
||||
|
||||
priv->nb_channels = nb_channels;
|
||||
priv->ADCx->CHSELR = new_channels; // apply it to the ADC
|
||||
priv->channels_mask = new_channels;
|
||||
|
||||
UADC_SetupDMA(unit);
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
|
||||
}
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Read raw values from the last measurement.
|
||||
* Response: interleaved (u8:channel, u16:value) for all channels
|
||||
*/
|
||||
case CMD_READ_RAW:
|
||||
if(priv->opmode != ADC_OPMODE_IDLE && priv->opmode != ADC_OPMODE_ARMED) {
|
||||
return E_BUSY;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 18; i++) {
|
||||
if (priv->channels_mask & (1 << i)) {
|
||||
pb_u16(&pb, priv->last_samples[i]);
|
||||
}
|
||||
}
|
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Read smoothed values.
|
||||
* Response: interleaved (u8:channel, f32:value) for all channels
|
||||
*/
|
||||
case CMD_READ_SMOOTHED:
|
||||
if(priv->opmode != ADC_OPMODE_IDLE && priv->opmode != ADC_OPMODE_ARMED) {
|
||||
return E_BUSY;
|
||||
}
|
||||
|
||||
if (! priv->cfg.enable_averaging) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Averaging disabled");
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
if (priv->real_frequency_int > UADC_MAX_FREQ_FOR_AVERAGING) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Too fast for averaging");
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 18; i++) {
|
||||
if (priv->channels_mask & (1 << i)) {
|
||||
pb_float(&pb, priv->averaging_bins[i]);
|
||||
}
|
||||
}
|
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Configure a trigger. This is legal only if the current state is IDLE or ARMED (will re-arm).
|
||||
*
|
||||
* Payload:
|
||||
* u8 - source channel
|
||||
* u16 - triggering level
|
||||
* u8 - edge to trigger on: 1-rising, 2-falling, 3-both
|
||||
* u16 - pre-trigger samples count
|
||||
* u32 - post-trigger samples count
|
||||
* u16 - trigger hold-off in ms (dead time after firing, before it cna fire again if armed)
|
||||
* u8(bool) - auto re-arm after firing and completing the capture
|
||||
*/
|
||||
case CMD_SETUP_TRIGGER:
|
||||
adc_dbg("> Setup trigger");
|
||||
if (priv->opmode != ADC_OPMODE_IDLE &&
|
||||
priv->opmode != ADC_OPMODE_ARMED &&
|
||||
priv->opmode != ADC_OPMODE_REARM_PENDING) {
|
||||
return E_BUSY;
|
||||
}
|
||||
|
||||
{
|
||||
const uint8_t source = pp_u8(pp);
|
||||
const uint16_t level = pp_u16(pp);
|
||||
const uint8_t edge = pp_u8(pp);
|
||||
const uint32_t pretrig = pp_u32(pp);
|
||||
const uint32_t count = pp_u32(pp);
|
||||
const uint16_t holdoff = pp_u16(pp);
|
||||
const bool auto_rearm = pp_bool(pp);
|
||||
|
||||
if (source > UADC_MAX_CHANNEL) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Invalid trig source");
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
if (0 == (priv->channels_mask & (1 << source))) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Channel not enabled");
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
if (level > 4095) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Level out of range (0-4095)");
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
if (edge == 0 || edge > 3) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Bad edge");
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
// XXX the max size may be too much
|
||||
const uint32_t max_pretrig = (priv->buf_itemcount / priv->nb_channels);
|
||||
if (pretrig > max_pretrig) {
|
||||
com_respond_snprintf(frame_id, MSG_ERROR,
|
||||
"Pretrig too large (max %d)", (int) max_pretrig);
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
priv->trigger_source = source;
|
||||
priv->trig_level = level;
|
||||
priv->trig_prev_level = priv->last_samples[source];
|
||||
priv->trig_edge = edge;
|
||||
priv->pretrig_len = pretrig;
|
||||
priv->trig_len = count;
|
||||
priv->trig_holdoff = holdoff;
|
||||
priv->auto_rearm = auto_rearm;
|
||||
}
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Arm (permissible only if idle and the trigger is configured)
|
||||
*/
|
||||
case CMD_ARM:
|
||||
adc_dbg("> Arm");
|
||||
uint8_t sticky = pp_u8(pp);
|
||||
|
||||
if(priv->opmode == ADC_OPMODE_ARMED || priv->opmode == ADC_OPMODE_REARM_PENDING) {
|
||||
// We are armed or will re-arm promptly, act like the call succeeded
|
||||
// The auto flag is set regardless
|
||||
} else {
|
||||
if (priv->opmode != ADC_OPMODE_IDLE) {
|
||||
return E_BUSY; // capture in progress
|
||||
}
|
||||
|
||||
if (priv->trig_len == 0) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Trigger not configured.");
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_ARMED);
|
||||
}
|
||||
|
||||
if (sticky != 255) {
|
||||
priv->auto_rearm = (bool)sticky;
|
||||
}
|
||||
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Dis-arm. Permissible only when idle or armed.
|
||||
* Switches to idle.
|
||||
*/
|
||||
case CMD_DISARM:
|
||||
adc_dbg("> Disarm");
|
||||
|
||||
priv->auto_rearm = false;
|
||||
|
||||
if(priv->opmode == ADC_OPMODE_IDLE) {
|
||||
return E_SUCCESS; // already idle, success - no work to do
|
||||
}
|
||||
|
||||
// capture in progress
|
||||
if (priv->opmode != ADC_OPMODE_ARMED &&
|
||||
priv->opmode != ADC_OPMODE_REARM_PENDING) {
|
||||
// Capture in progress, we already cleared auto rearm, so we're done for now
|
||||
// auto_rearm is checked in the EOS isr and if cleared, does not re-arm.
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
UADC_SwitchMode(unit, ADC_OPMODE_IDLE);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Abort any ongoing capture and dis-arm.
|
||||
*/
|
||||
case CMD_ABORT:;
|
||||
adc_dbg("> Abort capture");
|
||||
UADC_AbortCapture(unit);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Force a trigger (complete with pre-trigger capture and hold-off)
|
||||
* The reported edge will be 0b11, here meaning "manual trigger"
|
||||
*/
|
||||
case CMD_FORCE_TRIGGER:
|
||||
adc_dbg("> Force trigger");
|
||||
// This is similar to block capture, but includes the pre-trig buffer and has fixed size based on trigger config
|
||||
// FORCE is useful for checking if the trigger is set up correctly
|
||||
if (priv->opmode != ADC_OPMODE_ARMED &&
|
||||
priv->opmode != ADC_OPMODE_IDLE &&
|
||||
priv->opmode != ADC_OPMODE_REARM_PENDING) return E_BUSY;
|
||||
|
||||
if (priv->trig_len == 0) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Trigger not configured.");
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
UADC_HandleTrigger(unit, 0b11, PTIM_GetMicrotime());
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Start a block capture (like manual trigger, but without pre-trigger and arming)
|
||||
*
|
||||
* Payload:
|
||||
* u32 - sample count (for each channel)
|
||||
*/
|
||||
case CMD_BLOCK_CAPTURE:
|
||||
adc_dbg("> Block cpt");
|
||||
if (priv->opmode != ADC_OPMODE_ARMED &&
|
||||
priv->opmode != ADC_OPMODE_REARM_PENDING &&
|
||||
priv->opmode != ADC_OPMODE_IDLE) return E_BUSY;
|
||||
|
||||
uint32_t count = pp_u32(pp);
|
||||
|
||||
UADC_StartBlockCapture(unit, count, frame_id);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Start streaming (like block capture, but unlimited)
|
||||
* The stream can be terminated by the stop command.
|
||||
*/
|
||||
case CMD_STREAM_START:
|
||||
adc_dbg("> Stream ON");
|
||||
if (priv->opmode != ADC_OPMODE_ARMED &&
|
||||
priv->opmode != ADC_OPMODE_REARM_PENDING &&
|
||||
priv->opmode != ADC_OPMODE_IDLE) return E_BUSY;
|
||||
|
||||
UADC_StartStream(unit, frame_id);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Stop a stream.
|
||||
*/
|
||||
case CMD_STREAM_STOP:
|
||||
adc_dbg("> Stream OFF");
|
||||
if (priv->opmode != ADC_OPMODE_STREAM) {
|
||||
com_respond_str(MSG_ERROR, frame_id, "Not streaming");
|
||||
return E_FAILURE;
|
||||
}
|
||||
|
||||
UADC_StopStream(unit);
|
||||
return E_SUCCESS;
|
||||
|
||||
default:
|
||||
return E_UNKNOWN_COMMAND;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Unit template */
|
||||
const UnitDriver UNIT_ADC = {
|
||||
.name = "ADC",
|
||||
.description = "Analog/digital converter",
|
||||
// Settings
|
||||
.preInit = UADC_preInit,
|
||||
.cfgLoadBinary = UADC_loadBinary,
|
||||
.cfgWriteBinary = UADC_writeBinary,
|
||||
.cfgLoadIni = UADC_loadIni,
|
||||
.cfgWriteIni = UADC_writeIni,
|
||||
// Init
|
||||
.init = UADC_init,
|
||||
.deInit = UADC_deInit,
|
||||
// Function
|
||||
.handleRequest = UADC_handleRequest,
|
||||
.updateTick = UADC_updateTick,
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/25.
|
||||
//
|
||||
// ADC unit with several DSO-like features, like triggering, pre-trigger, block capture,
|
||||
// streaming, smoothing...
|
||||
//
|
||||
|
||||
#ifndef U_TPL_H
|
||||
#define U_TPL_H
|
||||
|
||||
#include "unit.h"
|
||||
|
||||
extern const UnitDriver UNIT_ADC;
|
||||
|
||||
#endif //U_TPL_H
|
||||
Reference in New Issue
Block a user