diff --git a/framework/unit_registry.c b/framework/unit_registry.c index 482dda8..ec3e5d3 100644 --- a/framework/unit_registry.c +++ b/framework/unit_registry.c @@ -495,7 +495,8 @@ void ureg_deliver_unit_request(TF_Msg *msg) if (rv == E_SUCCESS) { if (confirmed) com_respond_ok(msg->frame_id); } - else { + else if (rv != E_FAILURE) { + // Failure is returned when the handler already sent an error response. com_respond_error(msg->frame_id, rv); } goto quit; diff --git a/units/1wire/_ow_api.c b/units/1wire/_ow_api.c index ea96635..6c9b94b 100644 --- a/units/1wire/_ow_api.c +++ b/units/1wire/_ow_api.c @@ -125,5 +125,5 @@ error_t UU_1WIRE_Search(Unit *unit, bool with_alarm, bool restart, return priv->searchState.error; } - return E_FAILURE; + return E_INTERNAL_ERROR; } diff --git a/units/adc/_adc_core.c b/units/adc/_adc_core.c index ddb53fd..aa56d81 100644 --- a/units/adc/_adc_core.c +++ b/units/adc/_adc_core.c @@ -8,6 +8,11 @@ #define ADC_INTERNAL #include "_adc_internal.h" +void UADC_ReportEndOfStream(Unit *unit) +{ + dbg("~~End Of Stream msg~~"); +} + void UADC_DMA_Handler(void *arg) { Unit *unit = arg; @@ -59,6 +64,7 @@ void UADC_DMA_Handler(void *arg) if (m_trig || m_fixcpt) { if (priv->trig_stream_remain == 0) { dbg("End of capture"); + UADC_ReportEndOfStream(unit); UADC_SwitchMode(unit, (priv->auto_rearm && m_trig) ? ADC_OPMODE_ARMED : ADC_OPMODE_IDLE); } } @@ -117,32 +123,31 @@ void UADC_ADC_EOS_Handler(void *arg) uint16_t val = priv->dma_buffer[sample_pos]; dbg("Trig line level %d", (int)val); - if (priv->enable_averaging) { - priv->averaging_bins[i] = - priv->averaging_bins[i] * (1.0f - priv->avg_factor_as_float) + - ((float) val) * priv->avg_factor_as_float; - } else { - priv->last_sample[i] = val; - } + priv->averaging_bins[i] = + priv->averaging_bins[i] * (1.0f - priv->avg_factor_as_float) + + ((float) val) * priv->avg_factor_as_float; + + priv->last_samples[i] = val; if (priv->opmode == ADC_OPMODE_ARMED) { if (i == priv->trigger_source) { bool trigd = false; - bool rising = false; + uint8_t edge_type = 0; if (priv->trig_prev_level < priv->trig_level && val >= priv->trig_level) { dbg("******** Rising edge"); // Rising edge trigd = (bool) (priv->trig_edge & 0b01); - rising = true; + edge_type = 1; } else if (priv->trig_prev_level > priv->trig_level && val <= priv->trig_level) { dbg("******** Falling edge"); // Falling edge trigd = (bool) (priv->trig_edge & 0b10); + edge_type = 2; } if (trigd) { - UADC_HandleTrigger(unit, rising, timestamp); + UADC_HandleTrigger(unit, edge_type, timestamp); } priv->trig_prev_level = val; @@ -154,7 +159,7 @@ void UADC_ADC_EOS_Handler(void *arg) dbg(" EOS ISR end."); } -void UADC_HandleTrigger(Unit *unit, bool rising, uint64_t timestamp) +void UADC_HandleTrigger(Unit *unit, uint8_t edge_type, uint64_t timestamp) { assert_param(unit); struct priv *priv = unit->data; @@ -172,7 +177,7 @@ void UADC_HandleTrigger(Unit *unit, bool rising, uint64_t timestamp) unit->_tick_cnt = 0; } - dbg("Trigger condition hit, rising=%d", rising); + dbg("Trigger condition hit, edge=%d", edge_type); // TODO Send pre-trigger priv->stream_startpos = (uint16_t) priv->DMA_CHx->CNDTR; @@ -180,6 +185,44 @@ void UADC_HandleTrigger(Unit *unit, bool rising, uint64_t timestamp) UADC_SwitchMode(unit, ADC_OPMODE_TRIGD); } +void UADC_StartBlockCapture(Unit *unit, uint32_t len, TF_ID frame_id) +{ + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + priv->stream_frame_id = frame_id; + priv->stream_startpos = (uint16_t) priv->DMA_CHx->CNDTR; + priv->trig_stream_remain = len; + UADC_SwitchMode(unit, ADC_OPMODE_FIXCAPT); +} + +/** Start stream */ +void UADC_StartStream(Unit *unit, TF_ID frame_id) +{ + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + priv->stream_frame_id = frame_id; + priv->stream_startpos = (uint16_t) priv->DMA_CHx->CNDTR; + dbg("Start streaming."); + UADC_SwitchMode(unit, ADC_OPMODE_STREAM); +} + +/** End stream */ +void UADC_StopStream(Unit *unit) +{ + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + dbg("Stop stream."); + UADC_ReportEndOfStream(unit); + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); +} + +/** Handle unit update tick - expire the trigger hold-off */ void UADC_updateTick(Unit *unit) { assert_param(unit); @@ -234,6 +277,7 @@ void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) else if (new_mode == ADC_OPMODE_IDLE) { dbg("ADC switch -> IDLE"); // IDLE and ARMED are identical with the exception that the trigger condition is not checked + // ARMED can be only entered from IDLE, thus we do the init only here. // In IDLE, we don't need the DMA interrupts LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum); @@ -254,8 +298,11 @@ void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) assert_param(priv->opmode == ADC_OPMODE_IDLE); // there's nothing else to do here } - else if (new_mode == ADC_OPMODE_TRIGD || new_mode == ADC_OPMODE_STREAM) { - dbg("ADC switch -> TRIG'D or STREAM"); + else if (new_mode == ADC_OPMODE_TRIGD || + new_mode == ADC_OPMODE_STREAM || + new_mode == ADC_OPMODE_FIXCAPT) { + + dbg("ADC switch -> TRIG'D / STREAM / BLOCK"); assert_param(priv->opmode == ADC_OPMODE_ARMED || priv->opmode == ADC_OPMODE_IDLE); // during the capture, we disallow direct readout and averaging to reduce overhead diff --git a/units/adc/_adc_init.c b/units/adc/_adc_init.c index 5f5ed11..e3e129b 100644 --- a/units/adc/_adc_init.c +++ b/units/adc/_adc_init.c @@ -21,7 +21,6 @@ error_t UADC_preInit(Unit *unit) priv->sample_time = 0b010; // 13.5c priv->frequency = 1000; priv->buffer_size = 512; - priv->enable_averaging = false; priv->averaging_factor = 500; priv->opmode = ADC_OPMODE_UNINIT; diff --git a/units/adc/_adc_internal.h b/units/adc/_adc_internal.h index 91c6591..bda6c61 100644 --- a/units/adc/_adc_internal.h +++ b/units/adc/_adc_internal.h @@ -29,8 +29,6 @@ struct priv { 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 uint16_t buffer_size; //!< Buffer size in bytes (count 2 bytes per channel per measurement) - faster sampling freq needs bigger buffer - - bool enable_averaging; //!< Enable exponential averaging uint16_t averaging_factor; //!< Exponential averaging factor 0-1000 // internal state @@ -44,25 +42,25 @@ struct priv { DMA_Channel_TypeDef *DMA_CHx; //!< DMA channel instance uint16_t *dma_buffer; //!< malloc'd buffer for the samples uint8_t nb_channels; //!< nbr of enabled adc channels - uint16_t dma_buffer_itemcount; //!< real size of the buffer (adjusted from the configured size to evenly encompass 2*size of one sample) + uint16_t dma_buffer_itemcount; //!< real size of the buffer in samples (adjusted to fit 2x whole multiple of sample group) + 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 + uint16_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) - union { - float averaging_bins[18]; //!< Averaging buffers, enough space to accommodate all channels (16 external + 2 internal) - uint16_t last_sample[18]; //!< If averaging is disabled, the last captured sample is stored here. - }; + 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. + uint8_t trigger_source; //!< number of the pin selected as a trigger source uint16_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 - uint16_t trig_prev_level; //!< Value of the previous sample, used to detect trigger edge uint8_t trig_edge; //!< Which edge we want to trigger on. 1-rising, 2-falling, 3-both - uint32_t trig_stream_remain; //!< Counter of samples remaining to be sent in the post-trigger stream 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 - uint16_t trig_holdoff_remain; //!< Tmp counter for the currently active hold-off - uint16_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) + TF_ID stream_frame_id; //!< Session ID for multi-part stream (response or report) }; /** Allocate data structure and set defaults */ @@ -102,9 +100,21 @@ void UADC_ADC_EOS_Handler(void *arg); 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, bool rising, uint64_t timestamp); +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); + #endif //GEX_F072_ADC_INTERNAL_H diff --git a/units/adc/_adc_settings.c b/units/adc/_adc_settings.c index cd2e71c..e19811e 100644 --- a/units/adc/_adc_settings.c +++ b/units/adc/_adc_settings.c @@ -25,6 +25,9 @@ void UADC_loadBinary(Unit *unit, PayloadParser *pp) if (version >= 1) { priv->buffer_size = pp_u16(pp); } + if (version >= 2) { + priv->averaging_factor = pp_u16(pp); + } } /** Write to a binary buffer for storing in Flash */ @@ -32,7 +35,7 @@ void UADC_writeBinary(Unit *unit, PayloadBuilder *pb) { struct priv *priv = unit->data; - pb_u8(pb, 1); // version + pb_u8(pb, 2); // version pb_u16(pb, priv->channels); pb_bool(pb, priv->enable_tsense); @@ -40,6 +43,7 @@ void UADC_writeBinary(Unit *unit, PayloadBuilder *pb) pb_u8(pb, priv->sample_time); pb_u32(pb, priv->frequency); pb_u16(pb, priv->buffer_size); + pb_u16(pb, priv->averaging_factor); } // ------------------------------------------------------------------------ @@ -69,6 +73,10 @@ error_t UADC_loadIni(Unit *unit, const char *key, const char *value) else if (streq(key, "buffer_size")) { priv->buffer_size = (uint16_t) avr_atoi(value); } + else if (streq(key, "avg_factor")) { + priv->averaging_factor = (uint16_t) avr_atoi(value); + if (priv->averaging_factor > 1000) return E_BAD_VALUE; + } else { return E_BAD_KEY; } @@ -106,10 +114,9 @@ void UADC_writeIni(Unit *unit, IniWriter *iw) iw_entry(iw, "buffer_size", "%d", (int)priv->buffer_size); iw_cmt_newline(iw); - iw_comment(iw, "Enable exponential averaging (only when not streaming)"); - iw_comment(iw, "Used formula: y[t]=(1-k)*y[t-1]+k*u[t]"); - iw_entry(iw, "averaging", str_yn(priv->enable_averaging)); - iw_comment(iw, "Averaging factor k (permil, range 0-1000 ~ 0.000-1.000)"); + 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, "- available only for direct readout (i.e. not used in block capture)"); iw_entry(iw, "avg_factor", "%d", priv->averaging_factor); } diff --git a/units/adc/unit_adc.c b/units/adc/unit_adc.c index 37760fe..0e5213f 100644 --- a/units/adc/unit_adc.c +++ b/units/adc/unit_adc.c @@ -11,13 +11,195 @@ // ------------------------------------------------------------------------ enum TplCmd_ { - CMD_DUMMY, + CMD_READ_RAW = 0, + CMD_READ_SMOOTHED = 1, + + CMD_GET_ENABLED_CHANNELS = 10, + + 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, }; /** 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) { + case CMD_GET_ENABLED_CHANNELS: + dbg("> Query channels"); + for (uint8_t i = 0; i < 18; i++) { + if (priv->extended_channels_mask & (1 << i)) { + pb_u8(&pb, i); + } + } + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + case CMD_READ_RAW: + dbg("> 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->extended_channels_mask & (1 << i)) { + pb_u8(&pb, i); + pb_u16(&pb, priv->last_samples[i]); + } + } + assert_param(pb.ok); + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + case CMD_READ_SMOOTHED: + dbg("> Read smoothed"); + if(priv->opmode != ADC_OPMODE_IDLE && priv->opmode != ADC_OPMODE_ARMED) { + return E_BUSY; + } + + for (uint8_t i = 0; i < 18; i++) { + if (priv->extended_channels_mask & (1 << i)) { + pb_u8(&pb, i); + pb_float(&pb, priv->averaging_bins[i]); + } + } + assert_param(pb.ok); + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + case CMD_SETUP_TRIGGER: + dbg("> Setup trigger"); + if(priv->opmode != ADC_OPMODE_IDLE) return E_BUSY; + { + uint8_t source = pp_u8(pp); + uint16_t level = pp_u16(pp); + uint8_t edge = pp_u8(pp); + uint16_t pretrig = pp_u16(pp); + uint32_t count = pp_u32(pp); + uint16_t holdoff = pp_u16(pp); + bool auto_rearm = pp_bool(pp); + + if (source > 17) { + com_respond_str(MSG_ERROR, frame_id, "Invalid trig source"); + return E_FAILURE; + } + + if (0 == (priv->extended_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 + uint16_t max_pretrig = (priv->dma_buffer_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; + + case CMD_ARM: + dbg("> Arm"); + if(priv->opmode != ADC_OPMODE_IDLE) return E_BUSY; + + if (priv->trig_len == 0) { + com_respond_str(MSG_ERROR, frame_id, "Trigger not configured."); + return E_FAILURE; + } + + UADC_SwitchMode(unit, ADC_OPMODE_ARMED); + return E_SUCCESS; + + case CMD_DISARM: + dbg("> Disarm"); + 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) return E_BUSY; + + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + return E_SUCCESS; + + case CMD_ABORT:; + dbg("> Abort capture"); + enum uadc_opmode old_opmode = priv->opmode; + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + + if (old_opmode == ADC_OPMODE_FIXCAPT || + old_opmode == ADC_OPMODE_STREAM || + old_opmode == ADC_OPMODE_TRIGD) { + UADC_ReportEndOfStream(unit); + } + return E_SUCCESS; + + case CMD_FORCE_TRIGGER: + dbg("> Force trigger"); + if(priv->opmode == ADC_OPMODE_IDLE) { + com_respond_str(MSG_ERROR, frame_id, "Not armed"); + return E_FAILURE; + } + if(priv->opmode != ADC_OPMODE_ARMED) return E_BUSY; + + UADC_HandleTrigger(unit, 0b11, PTIM_GetMicrotime()); + return E_SUCCESS; + + case CMD_BLOCK_CAPTURE: + dbg("> Block cpt"); + if(priv->opmode != ADC_OPMODE_ARMED && + priv->opmode != ADC_OPMODE_IDLE) return E_BUSY; + + uint32_t count = pp_u32(pp); + + UADC_StartBlockCapture(unit, count, frame_id); + return E_SUCCESS; + + case CMD_STREAM_START: + dbg("> Stream ON"); + if(priv->opmode != ADC_OPMODE_ARMED && + priv->opmode != ADC_OPMODE_IDLE) return E_BUSY; + + UADC_StartStream(unit, frame_id); + return E_SUCCESS; + + case CMD_STREAM_STOP: + 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; } @@ -28,7 +210,7 @@ static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, P /** Unit template */ const UnitDriver UNIT_ADC = { .name = "ADC", - .description = "Analog inputs", + .description = "Analog/Digital converter", // Settings .preInit = UADC_preInit, .cfgLoadBinary = UADC_loadBinary, diff --git a/utils/error.h b/utils/error.h index b0daf02..3b165af 100644 --- a/utils/error.h +++ b/utils/error.h @@ -13,7 +13,7 @@ #define X_ERROR_CODES \ /* Shared errors */ \ X(SUCCESS, NULL) /* operation succeeded / unit loaded. Must be 0 */ \ - X(FAILURE, NULL) /* generic error */ \ + X(FAILURE, NULL) /* generic error. If returned from a unit handler, does NOT generate a response. */ \ X(INTERNAL_ERROR, NULL) /* a bug */ \ X(LOADING, NULL) /* unit is loading */ \ X(UNKNOWN_COMMAND, NULL) \