Modularization, stage 1

This commit is contained in:
2018-07-07 19:55:21 +02:00
parent e5679e90f8
commit a1fa0821cf
105 changed files with 11851 additions and 268 deletions
+700
View File
@@ -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;
}
+269
View File
@@ -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);
}
+161
View File
@@ -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
+117
View File
@@ -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);
}
+428
View File
@@ -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,
};
+15
View File
@@ -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