diff --git a/FreeRTOSConfig.h b/FreeRTOSConfig.h index 2be418b..4b9bfd9 100644 --- a/FreeRTOSConfig.h +++ b/FreeRTOSConfig.h @@ -111,7 +111,7 @@ #define configUSE_TIMERS 1 #define configTIMER_TASK_PRIORITY 4 // above normal -#define configTIMER_TASK_STACK_DEPTH 128 +#define configTIMER_TASK_STACK_DEPTH TSK_STACK_TIMERS //128 #define configTIMER_QUEUE_LENGTH 4 #define configTOTAL_HEAP_SIZE 4096 diff --git a/USB/STM32_USB_Device_Library/Class/MSC_CDC/usbd_msc_cdc.c b/USB/STM32_USB_Device_Library/Class/MSC_CDC/usbd_msc_cdc.c index 7d05557..dca206d 100644 --- a/USB/STM32_USB_Device_Library/Class/MSC_CDC/usbd_msc_cdc.c +++ b/USB/STM32_USB_Device_Library/Class/MSC_CDC/usbd_msc_cdc.c @@ -112,7 +112,7 @@ __ALIGN_BEGIN uint8_t USBD_MSC_CDC_CfgFSDesc[USBD_MSC_CDC_CONFIG_DESC_SIZ] __AL 0x03, /* bmAttributes: Interrupt */ LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: TODO: 2?*/ HIBYTE(CDC_CMD_PACKET_SIZE), - 0xFF, /* bInterval: TODO was 0x10?*/ + 0x10, //0xFF, /* bInterval: TODO was 0x10?*/ /********** CDC Data Class Interface Descriptor ***********/ /*75*/ 0x09, /* bLength: Endpoint Descriptor size */ diff --git a/framework/unit_registry.c b/framework/unit_registry.c index bb86421..23d2ae8 100644 --- a/framework/unit_registry.c +++ b/framework/unit_registry.c @@ -612,14 +612,17 @@ void ureg_tick_units(void) UlistEntry *li = ulist_head; while (li != NULL) { Unit *const pUnit = &li->unit; - if (pUnit && pUnit->data && pUnit->status == E_SUCCESS && pUnit->tick_interval > 0) { + if (pUnit && pUnit->data && pUnit->status == E_SUCCESS && (pUnit->tick_interval > 0 || pUnit->_tick_cnt > 0)) { + if (pUnit->_tick_cnt > 0) { + pUnit->_tick_cnt--; // check for 0 allows one-off timers + } + if (pUnit->_tick_cnt == 0) { if (pUnit->driver->updateTick) { pUnit->driver->updateTick(pUnit); } pUnit->_tick_cnt = pUnit->tick_interval; } - pUnit->_tick_cnt--; } li = li->next; } diff --git a/freertos.c b/freertos.c index 3c69f35..da8accd 100644 --- a/freertos.c +++ b/freertos.c @@ -114,13 +114,13 @@ __weak void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTas /* USER CODE BEGIN GET_IDLE_TASK_MEMORY */ static StaticTask_t xIdleTaskTCBBuffer; -static StackType_t xIdleStack[configMINIMAL_STACK_SIZE]; +static StackType_t xIdleStack[TSK_STACK_IDLE]; void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize ) { *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer; *ppxIdleTaskStackBuffer = &xIdleStack[0]; - *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; + *pulIdleTaskStackSize = TSK_STACK_IDLE; /* place for user code */ } /* USER CODE END GET_IDLE_TASK_MEMORY */ @@ -128,13 +128,13 @@ void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackTy /* USER CODE BEGIN GET_IDLE_TASK_MEMORY */ static StaticTask_t xTimersTaskTCBBuffer; -static StackType_t xTimersStack[configTIMER_TASK_STACK_DEPTH]; +static StackType_t xTimersStack[TSK_STACK_TIMERS]; void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimersTaskTCBBuffer, StackType_t **ppxTimersTaskStackBuffer, uint32_t *pulTimersTaskStackSize ) { *ppxTimersTaskTCBBuffer = &xTimersTaskTCBBuffer; *ppxTimersTaskStackBuffer = &xTimersStack[0]; - *pulTimersTaskStackSize = configTIMER_TASK_STACK_DEPTH; + *pulTimersTaskStackSize = TSK_STACK_TIMERS; /* place for user code */ } /* USER CODE END GET_IDLE_TASK_MEMORY */ diff --git a/platform/hw_utils.c b/platform/hw_utils.c index 9cfb4f1..c082ca4 100644 --- a/platform/hw_utils.c +++ b/platform/hw_utils.c @@ -371,7 +371,10 @@ bool solve_timer(uint32_t base_freq, uint32_t required_freq, bool is16bit, *presc = (uint16_t) wPresc; if (wPresc * wCount == 0) return false; - *real_freq = (base_freq / (wPresc * wCount)); + + if (real_freq != NULL) { + *real_freq = (base_freq / (wPresc * wCount)); + } return true; } diff --git a/platform/plat_compat.h b/platform/plat_compat.h index bd6d7b3..03b1a1f 100644 --- a/platform/plat_compat.h +++ b/platform/plat_compat.h @@ -18,13 +18,17 @@ // 180 is normally enough if not doing extensive debug logging #define TSK_STACK_MSG 200 // TF message handler task stack size (all unit commands run on this thread) +#define TSK_STACK_IDLE 64 //configMINIMAL_STACK_SIZE +#define TSK_STACK_TIMERS 64 //configTIMER_TASK_STACK_DEPTH + + #define BULK_READ_BUF_LEN 256 // Buffer for TF bulk reads #define UNIT_TMP_LEN 512 // Buffer for internal unit operations #define FLASH_SAVE_BUF_LEN 128 // Malloc'd buffer for saving to flash #define MSG_QUE_SLOT_SIZE 64 // FIXME this should be possible to lower, but there's some bug with bulk transfer / INI parser -#define RX_QUE_CAPACITY 16 // TinyFrame rx queue size (64 bytes each) +#define RX_QUE_CAPACITY 36 // TinyFrame rx queue size (64 bytes each) #define TF_MAX_PAYLOAD_RX 512 // TF max Rx payload #define TF_SENDBUF_LEN 64 // TF transmit buffer (can be less than a full frame) diff --git a/tasks/task_msg.c b/tasks/task_msg.c index e58c260..c3a8b91 100644 --- a/tasks/task_msg.c +++ b/tasks/task_msg.c @@ -8,7 +8,7 @@ volatile uint32_t msgQueHighWaterMark = 0; -static void que_safe_post(struct rx_sched_combined_que_item *slot) +static bool que_safe_post(struct rx_sched_combined_que_item *slot) { uint32_t count = 0; assert_param(slot != NULL); @@ -18,7 +18,7 @@ static void que_safe_post(struct rx_sched_combined_que_item *slot) BaseType_t status = xQueueSendFromISR(queMsgJobHandle, slot, &xHigherPriorityTaskWoken); if (pdPASS != status) { dbg("! Que post from ISR failed"); - return; + return false; } #if USE_STACK_MONITOR @@ -30,7 +30,7 @@ static void que_safe_post(struct rx_sched_combined_que_item *slot) BaseType_t status = xQueueSend(queMsgJobHandle, slot, MSG_QUE_POST_TIMEOUT); if (pdPASS != status) { dbg("! Que post failed"); - return; + return false; } #if USE_STACK_MONITOR @@ -41,6 +41,8 @@ static void que_safe_post(struct rx_sched_combined_que_item *slot) #if USE_STACK_MONITOR msgQueHighWaterMark = MAX(msgQueHighWaterMark, count); #endif + + return true; } /** @@ -48,7 +50,7 @@ static void que_safe_post(struct rx_sched_combined_que_item *slot) * * @param callback - the callback function */ -void scheduleJob(Job *job) +bool scheduleJob(Job *job) { assert_param(job->cb != NULL); @@ -57,7 +59,7 @@ void scheduleJob(Job *job) .job = *job, // copy content of the struct }; - que_safe_post(&slot); + return que_safe_post(&slot); } /** @@ -112,7 +114,10 @@ void rxQuePostMsg(uint8_t *buf, uint32_t len) slot.msg.len = len > MSG_QUE_SLOT_SIZE ? MSG_QUE_SLOT_SIZE : len; memcpy(slot.msg.data, buf, slot.msg.len); - que_safe_post(&slot); + if (!que_safe_post(&slot)) { + dbg("rxQuePostMsg fail!"); + break; + } len -= slot.msg.len; buf += slot.msg.len; diff --git a/tasks/task_msg.h b/tasks/task_msg.h index 3b99d6a..f0a9990 100644 --- a/tasks/task_msg.h +++ b/tasks/task_msg.h @@ -25,7 +25,7 @@ void TaskMsgJob(const void *argument); * * @param job */ -void scheduleJob(Job *job); +bool scheduleJob(Job *job); /** * Add a message to the queue. This always takes the entire slot (64 bytes) or multiple diff --git a/units/adc/_adc_api.c b/units/adc/_adc_api.c index 872d3a1..b91ce37 100644 --- a/units/adc/_adc_api.c +++ b/units/adc/_adc_api.c @@ -9,3 +9,21 @@ #define ADC_INTERNAL #include "_adc_internal.h" +error_t UU_ADC_AbortCapture(Unit *unit) +{ + CHECK_TYPE(unit, &UNIT_ADC); + struct priv *priv = unit->data; + + enum uadc_opmode old_opmode = priv->opmode; + + priv->auto_rearm = false; + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + + if (old_opmode == ADC_OPMODE_BLCAP || + old_opmode == ADC_OPMODE_STREAM || + old_opmode == ADC_OPMODE_TRIGD) { + UADC_ReportEndOfStream(unit); + } + + return E_SUCCESS; +} diff --git a/units/adc/_adc_core.c b/units/adc/_adc_core.c index 586b55a..1ea4312 100644 --- a/units/adc/_adc_core.c +++ b/units/adc/_adc_core.c @@ -4,12 +4,16 @@ #include "platform.h" #include "unit_base.h" +#include "unit_adc.h" #define ADC_INTERNAL #include "_adc_internal.h" #define DMA_POS(priv) ((priv)->dma_buffer_itemcount - (priv)->DMA_CHx->CNDTR) +volatile bool emergency = false; +//#define CRUMB() if(emergency) trap("crumb") + static void UADC_JobSendBlockChunk(Job *job) { Unit *unit = job->unit; @@ -21,7 +25,7 @@ static void UADC_JobSendBlockChunk(Job *job) uint32_t count = job->data2; bool close = (bool) job->data3; - dbg("Send indices [%d -> %d)", (int)start, (int)(start+count)); +// dbg("Send indices [%d -> %d)", (int)start, (int)(start+count)); TF_TYPE type = close ? EVT_CAPT_DONE : EVT_CAPT_MORE; @@ -58,7 +62,7 @@ static void UADC_JobSendTriggerCaptureHeader(Job *job) EventReport_Start(&er); priv->stream_frame_id = er.sent_msg_id; - dbg("Sending TRIG HEADER with id %d (idx %d)", (int)er.sent_msg_id, (int)index_trigd); +// dbg("Sending TRIG HEADER with id %d (idx %d)", (int)er.sent_msg_id, (int)index_trigd); { // preamble uint8_t buf[4]; @@ -81,10 +85,10 @@ static void UADC_JobSendTriggerCaptureHeader(Job *job) uint16_t items_from_end = pretrig_remain - index_trigd; assert_param(priv->dma_buffer_itemcount - items_from_end >= index_trigd); - dbg("Pretrig wraparound part: start %d, len %d", - (int) (priv->dma_buffer_itemcount - items_from_end), - (int) items_from_end - ); +// dbg("Pretrig wraparound part: start %d, len %d", +// (int) (priv->dma_buffer_itemcount - items_from_end), +// (int) items_from_end +// ); EventReport_Data( (uint8_t *) &priv->dma_buffer[priv->dma_buffer_itemcount - @@ -95,10 +99,10 @@ static void UADC_JobSendTriggerCaptureHeader(Job *job) pretrig_remain -= items_from_end; } - dbg("Pretrig front part: start %d, len %d", - (int) (index_trigd - pretrig_remain), - (int) pretrig_remain - ); +// dbg("Pretrig front part: start %d, len %d", +// (int) (index_trigd - pretrig_remain), +// (int) pretrig_remain +// ); assert_param(pretrig_remain <= index_trigd); EventReport_Data((uint8_t *) &priv->dma_buffer[index_trigd - pretrig_remain], @@ -136,6 +140,80 @@ void UADC_ReportEndOfStream(Unit *unit) scheduleJob(&j); } +static void handle_httc(Unit *unit, bool tc) +{ + struct priv *priv = unit->data; + uint16_t start = priv->stream_startpos; + uint16_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) { +// 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); + } + + if (ht == tc) { + // This shouldn't happen - looks like we missed the TC flag + dbg("!! %d -> %d", (int) start, (int) end); + // TODO we could try to catch up. for now, just take what is easy to grab and hope it doesnt matter + if (end == 64) start = 0; + } + + if (start != end) { + uint32_t sgcount = (end - start) / priv->nb_channels; + + if (m_trigd || m_fixcpt) { + sgcount = MIN(priv->trig_stream_remain, sgcount); + priv->trig_stream_remain -= sgcount; + } + + bool close = !m_stream && priv->trig_stream_remain == 0; + + Job j = { + .unit = unit, + .data1 = start, + .data2 = sgcount * priv->nb_channels, + .data3 = (uint32_t) close, + .cb = UADC_JobSendBlockChunk + }; + if (!scheduleJob(&j)) { + // Abort if we can't queue - the stream would tear and we'd hog the system with error messages + dbg("(!) Buffers overflow, abort capture"); + emergency = true; + UADC_SwitchMode(unit, ADC_OPMODE_EMERGENCY_SHUTDOWN); + return; + } + + if (close) { +// dbg("End of capture"); + // If auto-arm enabled, we need to re-arm again. + // However, EOS irq is disabled during the capture. + // We have to wait for the next EOS interrupt to occur. + // TODO verify if keeping the EOS irq enabled during capture has significant performance penalty. If not, we can leave it enabled. + UADC_SwitchMode(unit, (priv->auto_rearm && m_trigd) ? ADC_OPMODE_REARM_PENDING : ADC_OPMODE_IDLE); + } + } else { +// dbg("start==end, skip this irq"); + } + + if (tc) { + priv->stream_startpos = 0; + } + else { + priv->stream_startpos = end; + } +} + void UADC_DMA_Handler(void *arg) { Unit *unit = arg; @@ -144,6 +222,13 @@ void UADC_DMA_Handler(void *arg) struct priv *priv = unit->data; assert_param(priv); + if (priv->opmode == ADC_OPMODE_UNINIT) { + 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; + } + const uint32_t isrsnapshot = priv->DMAx->ISR; if (LL_DMA_IsActiveFlag_G(isrsnapshot, priv->dma_chnum)) { @@ -158,59 +243,17 @@ void UADC_DMA_Handler(void *arg) if (m_trigd || m_stream || m_fixcpt) { if (ht || tc) { - const uint16_t start = priv->stream_startpos; - uint16_t end; - - if (ht) { -// dbg("HT"); - end = (uint16_t) (priv->dma_buffer_itemcount / 2); - LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); - } - else { -// dbg("TC"); - end = (uint16_t) priv->dma_buffer_itemcount; - LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); - } - -// dbg("start %d, end %d", (int)start, (int)end); - assert_param(start <= end); - - if (start != end) { - uint32_t sgcount = (end - start) / priv->nb_channels; - - if (m_trigd || m_fixcpt) { - sgcount = MIN(priv->trig_stream_remain, sgcount); - priv->trig_stream_remain -= sgcount; - } - - bool close = !m_stream && priv->trig_stream_remain == 0; - - Job j = { - .unit = unit, - .data1 = start, - .data2 = sgcount * priv->nb_channels, - .data3 = (uint32_t) close, - .cb = UADC_JobSendBlockChunk - }; - scheduleJob(&j); - - if (close) { -// dbg("End of capture"); - // If auto-arm enabled, we need to re-arm again. - // However, EOS irq is disabled during the capture. - // We have to wait for the next EOS interrupt to occur. - // TODO verify if keeping the EOS irq enabled during capture has significant performance penalty. If not, we can leave it enabled. - UADC_SwitchMode(unit, (priv->auto_rearm && m_trigd) ? ADC_OPMODE_REARM_PENDING : ADC_OPMODE_IDLE); + if (ht && tc) { + uint16_t half = (uint16_t) (priv->dma_buffer_itemcount / 2); + if (priv->stream_startpos > half) { + handle_httc(unit, true); + handle_httc(unit, false); + } else { + handle_httc(unit, false); + handle_httc(unit, true); } } else { -// dbg("start==end, skip this irq"); - } - - if (tc) { - priv->stream_startpos = 0; - } - else { - priv->stream_startpos = end; + handle_httc(unit, tc); } } } else { @@ -235,18 +278,21 @@ void UADC_DMA_Handler(void *arg) void UADC_ADC_EOS_Handler(void *arg) { - uint64_t timestamp = PTIM_GetMicrotime(); Unit *unit = arg; - assert_param(unit); struct priv *priv = unit->data; assert_param(priv); + // 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) return; // Wait for the DMA to complete copying the last sample uint16_t dmapos; - while ((dmapos = (uint16_t) DMA_POS(priv)) % priv->nb_channels != 0); + hw_wait_while((dmapos = (uint16_t) DMA_POS(priv)) % priv->nb_channels != 0, 100); // XXX this could be changed to reading it from the DR instead uint32_t sample_pos; if (dmapos == 0) { @@ -257,53 +303,51 @@ void UADC_ADC_EOS_Handler(void *arg) sample_pos -= priv->nb_channels; int cnt = 0; // index of the sample within the group - for (uint32_t i = 0; i < 18; i++) { - if (priv->extended_channels_mask & (1 << i)) { + + const bool can_average = priv->real_frequency_int < UADC_MAX_FREQ_FOR_AVERAGING; + const uint32_t channels_mask = priv->extended_channels_mask; + + for (uint8_t i = 0; i < 18; i++) { + if (channels_mask & (1 << i)) { uint16_t val = priv->dma_buffer[sample_pos+cnt]; cnt++; - priv->averaging_bins[i] = - priv->averaging_bins[i] * (1.0f - priv->avg_factor_as_float) + - ((float) val) * priv->avg_factor_as_float; + 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; + } + } - if (i == priv->trigger_source) { - if (priv->opmode == ADC_OPMODE_ARMED) { -// dbg("Trig line level %d", (int)val); - - bool trigd = false; - uint8_t edge_type = 0; - if (priv->trig_prev_level < priv->trig_level && val >= priv->trig_level) { -// dbg("******** Rising edge"); - // Rising edge - trigd = (bool) (priv->trig_edge & 0b01); - edge_type = 1; - } - else if (priv->trig_prev_level > priv->trig_level && val <= priv->trig_level) { -// dbg("******** Falling edge"); - // Falling edge - trigd = (bool) (priv->trig_edge & 0b10); - edge_type = 2; - } + if (priv->opmode == ADC_OPMODE_ARMED) { + uint16_t val = priv->last_samples[priv->trigger_source]; - if (trigd) { - UADC_HandleTrigger(unit, edge_type, timestamp); - } - } - else if (priv->opmode == ADC_OPMODE_REARM_PENDING) { - if (!priv->auto_rearm) { - // It looks like the flag was cleared by DISARM before we got a new sample. - // Let's just switch to IDLE - UADC_SwitchMode(unit, ADC_OPMODE_IDLE); - } else { - // Re-arming for a new trigger - UADC_SwitchMode(unit, ADC_OPMODE_ARMED); - } - } +// dbg("Trig line level %d", (int)val); + if ((priv->trig_prev_level < priv->trig_level) && val >= priv->trig_level && (bool) (priv->trig_edge & 0b01)) { +// dbg("******** Rising edge"); + // Rising edge + UADC_HandleTrigger(unit, 1, timestamp); + } + else if ((priv->trig_prev_level > priv->trig_level) && val <= priv->trig_level && (bool) (priv->trig_edge & 0b10)) { +// dbg("******** Falling edge"); + // Falling edge + UADC_HandleTrigger(unit, 2, timestamp); + } + priv->trig_prev_level = val; + } - priv->trig_prev_level = val; - } + // auto-rearm was waiting for the next sample + if (priv->opmode == ADC_OPMODE_REARM_PENDING) { + if (!priv->auto_rearm) { + // It looks like the flag was cleared by DISARM before we got a new sample. + // Let's just switch to IDLE + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + } else { + // Re-arming for a new trigger + UADC_SwitchMode(unit, ADC_OPMODE_ARMED); } } } @@ -313,6 +357,7 @@ void UADC_HandleTrigger(Unit *unit, uint8_t edge_type, uint64_t timestamp) assert_param(unit); struct priv *priv = unit->data; assert_param(priv); + if (priv->opmode == ADC_OPMODE_UNINIT) return; if (priv->trig_holdoff != 0 && priv->trig_holdoff_remain > 0) { // dbg("Trig discarded due to holdoff."); @@ -323,7 +368,7 @@ void UADC_HandleTrigger(Unit *unit, uint8_t edge_type, uint64_t timestamp) priv->trig_holdoff_remain = priv->trig_holdoff; // Start the tick unit->tick_interval = 1; - unit->_tick_cnt = 0; + unit->_tick_cnt = 1; } priv->stream_startpos = (uint16_t) DMA_POS(priv); @@ -349,6 +394,7 @@ void UADC_StartBlockCapture(Unit *unit, uint32_t len, TF_ID frame_id) assert_param(unit); struct priv *priv = unit->data; assert_param(priv); + if (priv->opmode == ADC_OPMODE_UNINIT) return; priv->stream_frame_id = frame_id; priv->stream_startpos = (uint16_t) DMA_POS(priv); @@ -363,11 +409,11 @@ void UADC_StartStream(Unit *unit, TF_ID frame_id) assert_param(unit); struct priv *priv = unit->data; assert_param(priv); + if (priv->opmode == ADC_OPMODE_UNINIT) return; priv->stream_frame_id = frame_id; priv->stream_startpos = (uint16_t) DMA_POS(priv); priv->stream_serial = 0; -// dbg("Start streaming."); UADC_SwitchMode(unit, ADC_OPMODE_STREAM); } @@ -377,8 +423,8 @@ void UADC_StopStream(Unit *unit) assert_param(unit); struct priv *priv = unit->data; assert_param(priv); + if (priv->opmode == ADC_OPMODE_UNINIT) return; -// dbg("Stop stream."); UADC_ReportEndOfStream(unit); UADC_SwitchMode(unit, ADC_OPMODE_IDLE); } @@ -390,6 +436,16 @@ void UADC_updateTick(Unit *unit) struct priv *priv = unit->data; assert_param(priv); + // Recover from shutdown after a delay + if (priv->opmode == ADC_OPMODE_EMERGENCY_SHUTDOWN) { + dbg("Recovering from emergency shutdown"); + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + LL_TIM_EnableCounter(priv->TIMx); + UADC_ReportEndOfStream(unit); + unit->tick_interval = 0; + return; + } + if (priv->trig_holdoff_remain > 0) { priv->trig_holdoff_remain--; @@ -406,10 +462,14 @@ void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) struct priv *priv = unit->data; assert_param(priv); - if (new_mode == priv->opmode) return; // nothing to do + 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((priv->opmode != ADC_OPMODE_UNINIT) || (new_mode == ADC_OPMODE_IDLE)); + assert_param((old_mode != ADC_OPMODE_UNINIT) || (new_mode == ADC_OPMODE_IDLE)); + + priv->opmode = ADC_OPMODE_UNINIT; if (new_mode == ADC_OPMODE_UNINIT) { // dbg("ADC switch -> UNINIT"); @@ -436,11 +496,12 @@ void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum); } else if (new_mode == ADC_OPMODE_IDLE || new_mode == ADC_OPMODE_REARM_PENDING) { -// dbg("ADC switch -> IDLE or IDLE/REARM_PENDING"); // IDLE and ARMED are identical with the exception that the trigger condition is not checked // ARMED can be only entered from IDLE, thus we do the init only here. // In IDLE, we don't need the DMA interrupts + LL_DMA_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); @@ -448,7 +509,7 @@ void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) LL_ADC_ClearFlag_EOS(priv->ADCx); LL_ADC_EnableIT_EOS(priv->ADCx); - if (priv->opmode == ADC_OPMODE_UNINIT) { + 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); @@ -457,9 +518,28 @@ void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) LL_ADC_REG_StartConversion(priv->ADCx); } } + else if (new_mode == ADC_OPMODE_EMERGENCY_SHUTDOWN) { + // 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) { // dbg("ADC switch -> ARMED"); - assert_param(priv->opmode == ADC_OPMODE_IDLE || priv->opmode == ADC_OPMODE_REARM_PENDING); + 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]; @@ -469,7 +549,7 @@ void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) new_mode == ADC_OPMODE_BLCAP) { // dbg("ADC switch -> TRIG'D / STREAM / BLOCK"); - assert_param(priv->opmode == ADC_OPMODE_ARMED || priv->opmode == ADC_OPMODE_IDLE); + 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); @@ -484,6 +564,5 @@ void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) 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 e3e129b..eb3d3f7 100644 --- a/units/adc/_adc_init.c +++ b/units/adc/_adc_init.c @@ -28,6 +28,32 @@ error_t UADC_preInit(Unit *unit) 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 (!solve_timer(PLAT_APB1_HZ, hertz, true, &presc, &count, + &priv->real_frequency)) { + dbg("Failed to resolve timer params."); + return E_BAD_VALUE; + } + 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; +} + /** Finalize unit set-up */ error_t UADC_init(Unit *unit) { @@ -103,19 +129,20 @@ error_t UADC_init(Unit *unit) // ------------------- CONFIGURE THE TIMER -------------------------- dbg("Setting up TIMER"); { - // Find suitable timer values - uint16_t presc; - uint32_t count; - float 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; - } - dbg("Frequency error %d ppm, presc %d, count %d", - (int) lrintf(1000000.0f * ((real_freq - priv->frequency) / (float)priv->frequency)), (int) presc, (int) count); - - LL_TIM_SetPrescaler(priv->TIMx, (uint32_t) (presc - 1)); - LL_TIM_SetAutoReload(priv->TIMx, count - 1); + TRY(UADC_SetSampleRate(unit, priv->frequency)); +// // Find suitable timer values +// uint16_t presc; +// uint32_t count; +// float 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; +// } +// dbg("Frequency error %d ppm, presc %d, count %d", +// (int) lrintf(1000000.0f * ((real_freq - priv->frequency) / (float)priv->frequency)), (int) presc, (int) count); +// +// LL_TIM_SetPrescaler(priv->TIMx, (uint32_t) (presc - 1)); +// LL_TIM_SetAutoReload(priv->TIMx, count - 1); LL_TIM_EnableARRPreload(priv->TIMx); LL_TIM_EnableUpdateEvent(priv->TIMx); LL_TIM_SetTriggerOutput(priv->TIMx, LL_TIM_TRGO_UPDATE); @@ -169,7 +196,7 @@ error_t UADC_init(Unit *unit) priv->dma_buffer_itemcount*sizeof(uint16_t), priv->nb_channels); - priv->dma_buffer = malloc_ck(priv->dma_buffer_itemcount * sizeof(uint16_t)); + priv->dma_buffer = calloc_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 @@ -208,8 +235,10 @@ error_t UADC_init(Unit *unit) irqd_attach(priv->DMA_CHx, UADC_DMA_Handler, unit); irqd_attach(priv->ADCx, UADC_ADC_EOS_Handler, unit); + dbg("irqs attached"); UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + dbg("ADC done"); return E_SUCCESS; } diff --git a/units/adc/_adc_internal.h b/units/adc/_adc_internal.h index c850536..8972fff 100644 --- a/units/adc/_adc_internal.h +++ b/units/adc/_adc_internal.h @@ -11,6 +11,8 @@ #include "unit_base.h" +#define UADC_MAX_FREQ_FOR_AVERAGING 20000 + enum uadc_opmode { ADC_OPMODE_UNINIT, //!< Not yet switched to any mode ADC_OPMODE_IDLE, //!< Idle. Allows immediate value readout and averaging. @@ -19,6 +21,7 @@ enum uadc_opmode { 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 { @@ -40,6 +43,8 @@ struct priv { uint16_t averaging_factor; //!< Exponential averaging factor 0-1000 // internal state + float real_frequency; + uint32_t real_frequency_int; uint32_t extended_channels_mask; //!< channels bitfield including tsense and vref float avg_factor_as_float; ADC_TypeDef *ADCx; //!< The ADC peripheral used @@ -126,4 +131,7 @@ 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); + #endif //GEX_F072_ADC_INTERNAL_H diff --git a/units/adc/_adc_settings.c b/units/adc/_adc_settings.c index e19811e..e723c19 100644 --- a/units/adc/_adc_settings.c +++ b/units/adc/_adc_settings.c @@ -98,7 +98,7 @@ void UADC_writeIni(Unit *unit, IniWriter *iw) iw_entry(iw, "enable_tsense", str_yn(priv->enable_tsense)); iw_comment(iw, "Enable Vref channel (#17)"); - iw_entry(iw, "enable_vref", str_yn(priv->enable_tsense)); + iw_entry(iw, "enable_vref", str_yn(priv->enable_vref)); iw_cmt_newline(iw); iw_comment(iw, "Sampling time (0-7)"); diff --git a/units/adc/unit_adc.c b/units/adc/unit_adc.c index a01ff02..715b7bc 100644 --- a/units/adc/unit_adc.c +++ b/units/adc/unit_adc.c @@ -15,6 +15,7 @@ enum TplCmd_ { CMD_READ_SMOOTHED = 1, CMD_GET_ENABLED_CHANNELS = 10, + CMD_GET_SAMPLE_RATE = 11, CMD_SETUP_TRIGGER = 20, CMD_ARM = 21, @@ -25,6 +26,7 @@ enum TplCmd_ { CMD_STREAM_START = 26, CMD_STREAM_STOP = 27, CMD_SET_SMOOTHING_FACTOR = 28, + CMD_SET_SAMPLE_RATE = 29, }; /** Handle a request message */ @@ -33,6 +35,8 @@ static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, P struct priv *priv = unit->data; PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); + // TODO toggling individual channels - would require DMA re-init and various changes in the usage of the struct + switch (command) { /** * Get enabled channels. @@ -47,6 +51,24 @@ static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, P com_respond_pb(frame_id, MSG_SUCCESS, &pb); return E_SUCCESS; + 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 @@ -85,6 +107,11 @@ static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, P return E_BUSY; } + if (priv->real_frequency_int > UADC_MAX_FREQ_FOR_AVERAGING) { + com_respond_str(MSG_ERROR, frame_id, "Too fast for smoothing"); + return E_FAILURE; + } + for (uint8_t i = 0; i < 18; i++) { if (priv->extended_channels_mask & (1 << i)) { pb_float(&pb, priv->averaging_bins[i]); @@ -117,7 +144,7 @@ static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, P const uint8_t source = pp_u8(pp); const uint16_t level = pp_u16(pp); const uint8_t edge = pp_u8(pp); - const uint16_t pretrig = pp_u16(pp); // TODO test pre-trigger ... + const uint16_t pretrig = pp_u16(pp); const uint32_t count = pp_u32(pp); const uint16_t holdoff = pp_u16(pp); const bool auto_rearm = pp_bool(pp); @@ -219,18 +246,7 @@ static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, P */ case CMD_ABORT:; dbg("> Abort capture"); - { - enum uadc_opmode old_opmode = priv->opmode; - - priv->auto_rearm = false; - UADC_SwitchMode(unit, ADC_OPMODE_IDLE); - - if (old_opmode == ADC_OPMODE_BLCAP || - old_opmode == ADC_OPMODE_STREAM || - old_opmode == ADC_OPMODE_TRIGD) { - UADC_ReportEndOfStream(unit); - } - } + TRY(UU_ADC_AbortCapture(unit)); return E_SUCCESS; /** @@ -260,7 +276,6 @@ static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, P * u32 - sample count (for each channel) */ case CMD_BLOCK_CAPTURE: - // TODO test dbg("> Block cpt"); if (priv->opmode != ADC_OPMODE_ARMED && priv->opmode != ADC_OPMODE_REARM_PENDING && @@ -276,7 +291,6 @@ static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, P * The stream can be terminated by the stop command. */ case CMD_STREAM_START: - // TODO test dbg("> Stream ON"); if (priv->opmode != ADC_OPMODE_ARMED && priv->opmode != ADC_OPMODE_REARM_PENDING && @@ -289,7 +303,6 @@ static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, P * Stop a stream. */ case CMD_STREAM_STOP: - // TODO test dbg("> Stream OFF"); if (priv->opmode != ADC_OPMODE_STREAM) { com_respond_str(MSG_ERROR, frame_id, "Not streaming"); diff --git a/units/adc/unit_adc.h b/units/adc/unit_adc.h index 84d48ba..1ee3501 100644 --- a/units/adc/unit_adc.h +++ b/units/adc/unit_adc.h @@ -11,6 +11,6 @@ extern const UnitDriver UNIT_ADC; -// UU_ prototypes +error_t UU_ADC_AbortCapture(Unit *unit); #endif //U_TPL_H diff --git a/units/usart/unit_usart.c b/units/usart/unit_usart.c index 985074a..e8aca86 100644 --- a/units/usart/unit_usart.c +++ b/units/usart/unit_usart.c @@ -65,7 +65,7 @@ void UUSART_DMA_HandleRxFromIRQ(Unit *unit, uint16_t endpos) .data2 = count, .cb = UUSART_SendReceivedDataToMaster }; - scheduleJob(&j); + scheduleJob(&j); // TODO disable unit on failure // Move the read cursor, wrap around if needed if (endpos == UUSART_RXBUF_LEN) endpos = 0;