From 02f69b0e37e641dd37a156881dc9dd395e156330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 4 Feb 2018 17:43:59 +0100 Subject: [PATCH] work on ADC sampling, not tested, missing commands --- platform/hw_utils.c | 3 + platform/hw_utils.h | 12 ++ platform/irq_dispatcher.c | 13 +- platform/plat_init.c | 5 +- units/adc/_adc_core.c | 267 ++++++++++++++++++++++++++++++++++++++ units/adc/_adc_init.c | 102 ++++++--------- units/adc/_adc_internal.h | 75 ++++++++--- units/adc/_adc_settings.c | 14 +- units/adc/unit_adc.c | 1 + utils/malloc_safe.c | 6 + 10 files changed, 416 insertions(+), 82 deletions(-) diff --git a/platform/hw_utils.c b/platform/hw_utils.c index 9f08f31..9cfb4f1 100644 --- a/platform/hw_utils.c +++ b/platform/hw_utils.c @@ -349,6 +349,9 @@ bool solve_timer(uint32_t base_freq, uint32_t required_freq, bool is16bit, uint16_t *presc, uint32_t *count, float *real_freq) { if (required_freq == 0) return false; + + // XXX consider using the LL macros __LL_TIM_CALC_PSC and __LL_TIM_CALC_ARR + const float fPresc = base_freq / required_freq; uint32_t wCount = (uint32_t) lrintf(fPresc); diff --git a/platform/hw_utils.h b/platform/hw_utils.h index d89a285..94195ea 100644 --- a/platform/hw_utils.h +++ b/platform/hw_utils.h @@ -183,4 +183,16 @@ void hw_periph_clock_disable(void *periph); bool solve_timer(uint32_t base_freq, uint32_t required_freq, bool is16bit, uint16_t *presc, uint32_t *count, float *real_freq); +#define hw_wait_while(call, timeout) \ + do { \ + uint32_t _ts = HAL_GetTick(); \ + while (1 == (call)) { \ + if (HAL_GetTick() - _ts > (timeout)) { \ + trap("Timeout"); \ + } \ + } \ + } while (0) + +#define hw_wait_until(call, timeout) hw_wait_while(!(call), (timeout)) + #endif //GEX_PIN_UTILS_H diff --git a/platform/irq_dispatcher.c b/platform/irq_dispatcher.c index 13e18a0..be62258 100644 --- a/platform/irq_dispatcher.c +++ b/platform/irq_dispatcher.c @@ -65,6 +65,8 @@ static struct callbacks_ { struct cbslot tim7; struct cbslot tim15; + struct cbslot adc1; + // XXX add more callbacks here when needed } callbacks; @@ -96,7 +98,9 @@ void irqd_init(void) HAL_NVIC_SetPriority(DMA1_Channel2_3_IRQn, 2, 0); HAL_NVIC_SetPriority(DMA1_Channel4_5_6_7_IRQn, 2, 0); -// NVIC_EnableIRQ(ADC1_COMP_IRQn); /*!< ADC1 and COMP interrupts (ADC interrupt combined with EXTI Lines 21 and 22 */ + NVIC_EnableIRQ(ADC1_COMP_IRQn); /*!< ADC1 and COMP interrupts (ADC interrupt combined with EXTI Lines 21 and 22 */ + HAL_NVIC_SetPriority(ADC1_COMP_IRQn, 1, 0); // ADC group completion - higher prio than DMA to let it handle the last halfword first + // NVIC_EnableIRQ(TIM1_IRQn); /*!< TIM1 global Interrupt */ // NVIC_EnableIRQ(TIM2_IRQn); /*!< TIM2 global Interrupt */ // NVIC_EnableIRQ(TIM3_IRQn); /*!< TIM3 global Interrupt */ @@ -159,6 +163,8 @@ static struct cbslot *get_slot_for_periph(void *periph) else if (periph == TIM7) slot = &callbacks.tim7; else if (periph == TIM15) slot = &callbacks.tim15; + else if (periph == ADC1) slot = &callbacks.adc1; + else if (periph >= EXTIS[0] && periph <= EXTIS[15]) { slot = &callbacks.exti[periph - EXTIS[0]]; } @@ -313,6 +319,11 @@ void TIM15_IRQHandler(void) CALL_IRQ_HANDLER(callbacks.tim15); } +void ADC1_COMP_IRQHandler(void) +{ + CALL_IRQ_HANDLER(callbacks.adc1); +} + // other ISRs... diff --git a/platform/plat_init.c b/platform/plat_init.c index 0015e4f..f233d6d 100644 --- a/platform/plat_init.c +++ b/platform/plat_init.c @@ -18,6 +18,8 @@ void plat_init(void) { + // GPIO clocks are enabled earlier in the GEX start-up hook + // Load system defaults systemsettings_init(); @@ -27,8 +29,9 @@ void plat_init(void) LockJumper_Init(); Indicator_Init(); - DebugUart_Init(); // resource claim + DebugUart_Init(); // resource claim (was inited earlier to allow debug outputs) + // Enable interrupts and set priorities irqd_init(); dbg("Loading settings ..."); diff --git a/units/adc/_adc_core.c b/units/adc/_adc_core.c index a2bcd11..ddb53fd 100644 --- a/units/adc/_adc_core.c +++ b/units/adc/_adc_core.c @@ -2,3 +2,270 @@ // Created by MightyPork on 2018/02/04. // +#include "platform.h" +#include "unit_base.h" + +#define ADC_INTERNAL +#include "_adc_internal.h" + +void UADC_DMA_Handler(void *arg) +{ + Unit *unit = arg; + + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + const uint32_t isrsnapshot = priv->DMAx->ISR; + + if (LL_DMA_IsActiveFlag_G(isrsnapshot, priv->dma_chnum)) { + const bool tc = LL_DMA_IsActiveFlag_TC(isrsnapshot, priv->dma_chnum); + const bool ht = LL_DMA_IsActiveFlag_HT(isrsnapshot, priv->dma_chnum); + const bool te = LL_DMA_IsActiveFlag_TE(isrsnapshot, priv->dma_chnum); + + // check what mode we're in + const bool m_trig = priv->opmode == ADC_OPMODE_TRIGD; + const bool m_stream = priv->opmode == ADC_OPMODE_STREAM; + const bool m_fixcpt = priv->opmode == ADC_OPMODE_FIXCAPT; + + if (m_trig || m_stream || m_fixcpt) { + if (ht || tc) { + const uint16_t start = priv->stream_startpos; + uint16_t end; + + if (ht) { + dbg("HT"); + end = (uint16_t) (priv->dma_buffer_itemcount / 2); + LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); + } + else { + dbg("TC"); + end = (uint16_t) priv->dma_buffer_itemcount; + LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); + } + + assert_param(start < end); + + uint32_t sgcount = (end - start) / priv->nb_channels; + + if (m_trig || m_fixcpt) { + sgcount = MIN(priv->trig_stream_remain, sgcount); + priv->trig_stream_remain -= sgcount; + } + + dbg("Would send %d groups (u16 offset %d -> %d)", (int)sgcount, (int)start, (int)(start+sgcount*priv->nb_channels)); + // TODO send the data together with remaining count (used to detect end of transmission) + + if (m_trig || m_fixcpt) { + if (priv->trig_stream_remain == 0) { + dbg("End of capture"); + UADC_SwitchMode(unit, (priv->auto_rearm && m_trig) ? ADC_OPMODE_ARMED : ADC_OPMODE_IDLE); + } + } + + if (end == priv->dma_buffer_itemcount) { + priv->stream_startpos = 0; + } + else { + priv->stream_startpos = end; + } + } + } else { + // This shouldn't happen, the interrupt should be disabled in this opmode + dbg("(!) not streaming, DMA IT should be disabled"); + + if (ht) { + LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); + } + else { + LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); + } + } + + if (te) { + // this shouldn't happen - error + dbg("ADC DMA TE!"); + LL_DMA_ClearFlag_TE(priv->DMAx, priv->dma_chnum); + } + } +} + +void UADC_ADC_EOS_Handler(void *arg) +{ + uint64_t timestamp = PTIM_GetMicrotime(); + Unit *unit = arg; + + dbg("ADC EOS ISR hit"); + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + // Wait for the DMA to complete copying the last sample + while (priv->DMA_CHx->CNDTR % priv->nb_channels != 0); + + uint32_t sample_pos; + if (priv->DMA_CHx->CNDTR == 0) { + sample_pos = (uint32_t) (priv->dma_buffer_itemcount - 1); + } else { + sample_pos = priv->DMA_CHx->CNDTR; + } + sample_pos -= priv->nb_channels; + dbg("Sample pos %d", (int)sample_pos); + + for (uint32_t i = 0; i < 18; i++) { + if (priv->extended_channels_mask & (1 << i)) { + 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; + } + + if (priv->opmode == ADC_OPMODE_ARMED) { + if (i == priv->trigger_source) { + bool trigd = false; + bool rising = false; + 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; + } + else if (priv->trig_prev_level > priv->trig_level && val <= priv->trig_level) { + dbg("******** Falling edge"); + // Falling edge + trigd = (bool) (priv->trig_edge & 0b10); + } + + if (trigd) { + UADC_HandleTrigger(unit, rising, timestamp); + } + + priv->trig_prev_level = val; + } + } + } + } + + dbg(" EOS ISR end."); +} + +void UADC_HandleTrigger(Unit *unit, bool rising, uint64_t timestamp) +{ + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + if (priv->trig_holdoff != 0 && priv->trig_holdoff_remain > 0) { + dbg("Trig discarded due to holdoff."); + return; + } + + if (priv->trig_holdoff > 0) { + priv->trig_holdoff_remain = priv->trig_holdoff; + // Start the tick + unit->tick_interval = 1; + unit->_tick_cnt = 0; + } + + dbg("Trigger condition hit, rising=%d", rising); + // TODO Send pre-trigger + + priv->stream_startpos = (uint16_t) priv->DMA_CHx->CNDTR; + priv->trig_stream_remain = priv->trig_len; + UADC_SwitchMode(unit, ADC_OPMODE_TRIGD); +} + +void UADC_updateTick(Unit *unit) +{ + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + if (priv->trig_holdoff_remain > 0) { + priv->trig_holdoff_remain--; + + if (priv->trig_holdoff_remain == 0) { + unit->tick_interval = 0; + unit->_tick_cnt = 0; + } + } +} + +void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) +{ + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + if (new_mode == priv->opmode) return; // nothing to do + + // if un-itied, can go only to IDLE + assert_param((priv->opmode != ADC_OPMODE_UNINIT) || (new_mode == ADC_OPMODE_IDLE)); + + if (new_mode == ADC_OPMODE_UNINIT) { + dbg("ADC switch -> UNINIT"); + // Stop the DMA, timer and disable ADC - this is called before tearing down the unit + LL_TIM_DisableCounter(priv->TIMx); + + // Switch off the ADC + if (LL_ADC_IsEnabled(priv->ADCx)) { + // Cancel ongoing conversion + if (LL_ADC_REG_IsConversionOngoing(priv->ADCx)) { + dbg("Stopping ADC conv"); + LL_ADC_REG_StopConversion(priv->ADCx); + hw_wait_while(LL_ADC_REG_IsStopConversionOngoing(priv->ADCx), 100); + } + + LL_ADC_Disable(priv->ADCx); + dbg("Disabling ADC"); + hw_wait_while(LL_ADC_IsDisableOngoing(priv->ADCx), 100); + } + + dbg("Disabling DMA"); + LL_DMA_DisableChannel(priv->DMAx, priv->dma_chnum); + LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum); + } + else if (new_mode == ADC_OPMODE_IDLE) { + dbg("ADC switch -> IDLE"); + // IDLE and ARMED are identical with the exception that the trigger condition is not checked + + // In IDLE, we don't need the DMA interrupts + LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum); + + // Use End Of Sequence to recover results for averaging from the DMA buffer and DR + LL_ADC_EnableIT_EOS(priv->ADCx); + + if (priv->opmode == ADC_OPMODE_UNINIT) { + // Nothing is started yet - this is the only way to leave UNINIT + LL_ADC_Enable(priv->ADCx); + LL_DMA_EnableChannel(priv->DMAx, priv->dma_chnum); + LL_TIM_EnableCounter(priv->TIMx); + } + } + else if (new_mode == ADC_OPMODE_ARMED) { + dbg("ADC switch -> ARMED"); + 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"); + assert_param(priv->opmode == ADC_OPMODE_ARMED || priv->opmode == ADC_OPMODE_IDLE); + + // during the capture, we disallow direct readout and averaging to reduce overhead + LL_ADC_DisableIT_EOS(priv->ADCx); + + // Enable the DMA buffer interrupts + LL_DMA_EnableIT_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_EnableIT_TC(priv->DMAx, priv->dma_chnum); + } + + // the actual switch + priv->opmode = new_mode; +} diff --git a/units/adc/_adc_init.c b/units/adc/_adc_init.c index 1dd0a2d..5f5ed11 100644 --- a/units/adc/_adc_init.c +++ b/units/adc/_adc_init.c @@ -2,6 +2,7 @@ // Created by MightyPork on 2018/02/03. // +#include #include "platform.h" #include "unit_base.h" @@ -20,48 +21,12 @@ 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; - return E_SUCCESS; -} - -static void UADC_DMA_Handler(void *arg) -{ - Unit *unit = arg; - - dbg("ADC DMA ISR hit"); - assert_param(unit); - struct priv *priv = unit->data; - assert_param(priv); - - const uint32_t isrsnapshot = priv->DMAx->ISR; - - if (LL_DMA_IsActiveFlag_G(isrsnapshot, priv->dma_chnum)) { - bool tc = LL_DMA_IsActiveFlag_TC(isrsnapshot, priv->dma_chnum); - bool ht = LL_DMA_IsActiveFlag_HT(isrsnapshot, priv->dma_chnum); - - // Here we have to either copy it somewhere else, or notify another thread (queue?) - // that the data is ready for reading - - if (ht) { - uint16_t start = 0; - uint16_t end = (uint16_t) (priv->dma_buffer_size / 2); - // TODO handle first half - LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); - } - - if (tc) { - uint16_t start = (uint16_t) (priv->dma_buffer_size / 2); - uint16_t end = (uint16_t) priv->dma_buffer_size; - // TODO handle second half - LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); - } + priv->opmode = ADC_OPMODE_UNINIT; - if (LL_DMA_IsActiveFlag_TE(isrsnapshot, priv->dma_chnum)) { - // this shouldn't happen - error - dbg("ADC DMA TE!"); - LL_DMA_ClearFlag_TE(priv->DMAx, priv->dma_chnum); - } - } + return E_SUCCESS; } /** Finalize unit set-up */ @@ -133,6 +98,7 @@ error_t UADC_init(Unit *unit) // 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 -------------------------- @@ -142,8 +108,7 @@ error_t UADC_init(Unit *unit) uint16_t presc; uint32_t count; float real_freq; - if (!solve_timer(PLAT_APB1_HZ, priv->frequency, true, &presc, &count, - &real_freq)) { + if (!solve_timer(PLAT_APB1_HZ, priv->frequency, true, &presc, &count, &real_freq)) { dbg("Failed to resolve timer params."); return E_BAD_VALUE; } @@ -167,24 +132,29 @@ error_t UADC_init(Unit *unit) while (LL_ADC_IsCalibrationOnGoing(priv->ADCx)) {} dbg("ADC calibrated."); - uint32_t mask = 0; - if (priv->enable_vref) mask |= LL_ADC_PATH_INTERNAL_VREFINT; - if (priv->enable_tsense) mask |= LL_ADC_PATH_INTERNAL_TEMPSENSOR; - LL_ADC_SetCommonPathInternalCh(priv->ADCx_Common, mask); + { + uint32_t mask = 0; + if (priv->enable_vref) mask |= LL_ADC_PATH_INTERNAL_VREFINT; + if (priv->enable_tsense) mask |= LL_ADC_PATH_INTERNAL_TEMPSENSOR; + LL_ADC_SetCommonPathInternalCh(priv->ADCx_Common, mask); + } + 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 - LL_ADC_REG_SetSequencerChannels(priv->ADCx, priv->channels); - if (priv->enable_tsense) LL_ADC_REG_SetSequencerChAdd(priv->ADCx, LL_ADC_CHANNEL_TEMPSENSOR); - if (priv->enable_vref) LL_ADC_REG_SetSequencerChAdd(priv->ADCx, LL_ADC_CHANNEL_VREFINT); + priv->extended_channels_mask = priv->channels; + if (priv->enable_tsense) priv->extended_channels_mask |= (1<<16); + if (priv->enable_vref) priv->extended_channels_mask |= (1<<17); + + priv->ADCx->CHSELR = priv->extended_channels_mask; LL_ADC_REG_SetTriggerSource(priv->ADCx, LL_ADC_REG_TRIG_EXT_TIM15_TRGO); LL_ADC_SetSamplingTimeCommonChannels(priv->ADCx, LL_ADC_SAMPLETIMES[priv->sample_time]); - LL_ADC_Enable(priv->ADCx); +// LL_ADC_Enable(priv->ADCx); } // --------------------- CONFIGURE DMA ------------------------------- @@ -193,10 +163,14 @@ error_t UADC_init(Unit *unit) // The length must be a 2*multiple of the number of channels, in bytes uint16_t itemcount = (uint16_t) ((priv->nb_channels) * (uint16_t) (priv->buffer_size / (2 * priv->nb_channels))); if (itemcount % 2 == 1) itemcount -= priv->nb_channels; - priv->dma_buffer_size = (uint16_t) (itemcount * 2); - dbg("DMA item count is %d (%d bytes)", itemcount, priv->dma_buffer_size); + priv->dma_buffer_itemcount = itemcount; + + dbg("DMA item count is %d (%d bytes), There are %d 2-byte samples per group.", + priv->dma_buffer_itemcount, + priv->dma_buffer_itemcount*sizeof(uint16_t), + priv->nb_channels); - priv->dma_buffer = malloc_ck(priv->dma_buffer_size); + priv->dma_buffer = malloc_ck(priv->dma_buffer_itemcount * 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 @@ -218,21 +192,25 @@ error_t UADC_init(Unit *unit) assert_param(SUCCESS == LL_DMA_Init(priv->DMAx, priv->dma_chnum, &init)); - irqd_attach(priv->DMA_CHx, UADC_DMA_Handler, unit); // Interrupt on transfer 1/2 and complete // We will capture the first and second half and send it while the other half is being filled. - LL_DMA_EnableIT_HT(priv->DMAx, priv->dma_chnum); - LL_DMA_EnableIT_TC(priv->DMAx, priv->dma_chnum); +// LL_DMA_EnableIT_HT(priv->DMAx, priv->dma_chnum); +// LL_DMA_EnableIT_TC(priv->DMAx, priv->dma_chnum); } LL_DMA_EnableChannel(priv->DMAx, priv->dma_chnum); } - dbg("ADC inited, starting the timer ..."); + // prepare the avg factor float for the ISR + if (priv->averaging_factor > 1000) priv->averaging_factor = 1000; // normalize + priv->avg_factor_as_float = priv->averaging_factor/1000.0f; + + dbg("ADC peripherals configured."); - // FIXME - temporary demo - counter start... - LL_ADC_REG_StartConversion(priv->ADCx); // the first conversion must be started manually - LL_TIM_EnableCounter(priv->TIMx); + irqd_attach(priv->DMA_CHx, UADC_DMA_Handler, unit); + irqd_attach(priv->ADCx, UADC_ADC_EOS_Handler, unit); + + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); return E_SUCCESS; } @@ -246,11 +224,15 @@ void UADC_deInit(Unit *unit) // 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_ck(priv->dma_buffer); diff --git a/units/adc/_adc_internal.h b/units/adc/_adc_internal.h index e3d0cdf..91c6591 100644 --- a/units/adc/_adc_internal.h +++ b/units/adc/_adc_internal.h @@ -11,36 +11,60 @@ #include "unit_base.h" +enum uadc_opmode { + ADC_OPMODE_UNINIT, //!< Not yet switched to any mode + ADC_OPMODE_IDLE, //!< Idle, each sample overwrites the previous. Allows immediate value readout and averaging. + 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_FIXCAPT,//!< Capture of fixed length without a trigger + ADC_OPMODE_STREAM, //!< Unlimited capture +}; + /** Private data structure */ struct priv { // settings uint16_t channels; //!< bit flags (will be recorded in order 0-15) bool enable_tsense; //!< append a signal from the temperature channel (voltage proportional to Tj) - bool enable_vref; //!< append a signal from the internal voltage reference + bool enable_vref; //!< append a signal from the internal voltage reference 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 - // TODO averaging (maybe a separate component?) - // TODO threshold watchdog with hysteresis (maybe a separate component?) - // TODO trigger level, edge direction, hold-off, pre-trigger buffer (extract from the DMA buffer) + bool enable_averaging; //!< Enable exponential averaging + uint16_t averaging_factor; //!< Exponential averaging factor 0-1000 // internal state - ADC_TypeDef *ADCx; - ADC_Common_TypeDef *ADCx_Common; - TIM_TypeDef *TIMx; - DMA_TypeDef *DMAx; - uint8_t dma_chnum; - DMA_Channel_TypeDef *DMA_CHx; - uint16_t *dma_buffer; - uint8_t nb_channels; // nr of enabled adc channels - uint16_t dma_buffer_size; // real number of bytes + uint32_t extended_channels_mask; //!< channels bitfield including tsense and vref + float avg_factor_as_float; + 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 + 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) + + 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. + }; + 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) }; -// max size of the DMA buffer. The actual buffer size will be adjusted to accommodate -// an even number of sample groups (sets of channels) -#define UADC_DMA_MAX_BUF_LEN 512 - /** Allocate data structure and set defaults */ error_t UADC_preInit(Unit *unit); @@ -66,4 +90,21 @@ error_t UADC_init(Unit *unit); /** Tear down the unit */ void UADC_deInit(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, bool rising, uint64_t timestamp); + +/** Handle a periodic tick - expiring the hold-off */ +void UADC_updateTick(Unit *unit); + #endif //GEX_F072_ADC_INTERNAL_H diff --git a/units/adc/_adc_settings.c b/units/adc/_adc_settings.c index e44b457..cd2e71c 100644 --- a/units/adc/_adc_settings.c +++ b/units/adc/_adc_settings.c @@ -83,15 +83,16 @@ void UADC_writeIni(Unit *unit, IniWriter *iw) struct priv *priv = unit->data; iw_comment(iw, "Enabled channels, comma separated"); - iw_comment(iw, "0-7 = A0-A7, 8-9 = B0-B1, 10-15 = C0-C5"); + iw_comment(iw, "0-7 = A0-A7, 8-9 = B0-B1, 10-15 = C0-C5"); iw_entry(iw, "channels", "%s", pinmask2str_up(priv->channels, unit_tmp512)); - iw_comment(iw, "Enable Tsense channel"); + iw_comment(iw, "Enable Tsense channel (#16)"); iw_entry(iw, "enable_tsense", str_yn(priv->enable_tsense)); - iw_comment(iw, "Enable Vref channel"); + iw_comment(iw, "Enable Vref channel (#17)"); iw_entry(iw, "enable_vref", str_yn(priv->enable_tsense)); + iw_cmt_newline(iw); iw_comment(iw, "Sampling time (0-7)"); iw_entry(iw, "sample_time", "%d", (int)priv->sample_time); @@ -103,5 +104,12 @@ void UADC_writeIni(Unit *unit, IniWriter *iw) iw_comment(iw, "- the buffer is shared by all channels"); iw_comment(iw, "- insufficient buffer size can lead to data loss"); 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_entry(iw, "avg_factor", "%d", priv->averaging_factor); } diff --git a/units/adc/unit_adc.c b/units/adc/unit_adc.c index c1a3e73..37760fe 100644 --- a/units/adc/unit_adc.c +++ b/units/adc/unit_adc.c @@ -40,4 +40,5 @@ const UnitDriver UNIT_ADC = { .deInit = UADC_deInit, // Function .handleRequest = UADC_handleRequest, + .updateTick = UADC_updateTick, }; diff --git a/utils/malloc_safe.c b/utils/malloc_safe.c index fa57637..77b4b8d 100644 --- a/utils/malloc_safe.c +++ b/utils/malloc_safe.c @@ -5,6 +5,11 @@ void *malloc_ck_do(size_t size, const char *file, uint32_t line) { + if (size == 0) { + _warn_msg(file, line, "MALLOC OF SIZE 0"); + return NULL; + } + void *mem = pvPortMalloc(size); _malloc_trace(size, mem, file, line); if (mem == NULL) { @@ -16,6 +21,7 @@ void *malloc_ck_do(size_t size, const char *file, uint32_t line) void *calloc_ck_do(size_t nmemb, size_t size, const char *file, uint32_t line) { void *mem = malloc_ck_do(nmemb*size, file, line); + if (mem == NULL) return NULL; memset(mem, 0, size*nmemb); return mem; }