commit
19a0040324
@ -0,0 +1,108 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/04.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "ll_extension.h" |
||||||
|
|
||||||
|
const uint32_t LL_SYSCFG_EXTI_PORTS[PORTS_COUNT] = { |
||||||
|
LL_SYSCFG_EXTI_PORTA, |
||||||
|
LL_SYSCFG_EXTI_PORTB, |
||||||
|
LL_SYSCFG_EXTI_PORTC, |
||||||
|
LL_SYSCFG_EXTI_PORTD, |
||||||
|
LL_SYSCFG_EXTI_PORTE, |
||||||
|
#if PORTS_COUNT>5 |
||||||
|
LL_SYSCFG_EXTI_PORTF, |
||||||
|
#endif |
||||||
|
#if PORTS_COUNT>6 |
||||||
|
LL_SYSCFG_EXTI_PORTG, |
||||||
|
#endif |
||||||
|
}; |
||||||
|
|
||||||
|
const uint32_t LL_SYSCFG_EXTI_LINES[16] = { |
||||||
|
LL_SYSCFG_EXTI_LINE0, |
||||||
|
LL_SYSCFG_EXTI_LINE1, |
||||||
|
LL_SYSCFG_EXTI_LINE2, |
||||||
|
LL_SYSCFG_EXTI_LINE3, |
||||||
|
LL_SYSCFG_EXTI_LINE4, |
||||||
|
LL_SYSCFG_EXTI_LINE5, |
||||||
|
LL_SYSCFG_EXTI_LINE6, |
||||||
|
LL_SYSCFG_EXTI_LINE7, |
||||||
|
LL_SYSCFG_EXTI_LINE8, |
||||||
|
LL_SYSCFG_EXTI_LINE9, |
||||||
|
LL_SYSCFG_EXTI_LINE10, |
||||||
|
LL_SYSCFG_EXTI_LINE11, |
||||||
|
LL_SYSCFG_EXTI_LINE12, |
||||||
|
LL_SYSCFG_EXTI_LINE13, |
||||||
|
LL_SYSCFG_EXTI_LINE14, |
||||||
|
LL_SYSCFG_EXTI_LINE15, |
||||||
|
}; |
||||||
|
COMPILER_ASSERT(16 == ELEMENTS_IN_ARRAY(LL_SYSCFG_EXTI_LINES)); |
||||||
|
|
||||||
|
const uint32_t LL_EXTI_LINES[16] = { |
||||||
|
LL_EXTI_LINE_0, |
||||||
|
LL_EXTI_LINE_1, |
||||||
|
LL_EXTI_LINE_2, |
||||||
|
LL_EXTI_LINE_3, |
||||||
|
LL_EXTI_LINE_4, |
||||||
|
LL_EXTI_LINE_5, |
||||||
|
LL_EXTI_LINE_6, |
||||||
|
LL_EXTI_LINE_7, |
||||||
|
LL_EXTI_LINE_8, |
||||||
|
LL_EXTI_LINE_9, |
||||||
|
LL_EXTI_LINE_10, |
||||||
|
LL_EXTI_LINE_11, |
||||||
|
LL_EXTI_LINE_12, |
||||||
|
LL_EXTI_LINE_13, |
||||||
|
LL_EXTI_LINE_14, |
||||||
|
LL_EXTI_LINE_15, |
||||||
|
}; |
||||||
|
COMPILER_ASSERT(16 == ELEMENTS_IN_ARRAY(LL_EXTI_LINES)); |
||||||
|
|
||||||
|
/** Pin number to LL bitfield mapping */ |
||||||
|
const uint32_t LL_GPIO_PINS[16] = { |
||||||
|
LL_GPIO_PIN_0, |
||||||
|
LL_GPIO_PIN_1, |
||||||
|
LL_GPIO_PIN_2, |
||||||
|
LL_GPIO_PIN_3, |
||||||
|
LL_GPIO_PIN_4, |
||||||
|
LL_GPIO_PIN_5, |
||||||
|
LL_GPIO_PIN_6, |
||||||
|
LL_GPIO_PIN_7, |
||||||
|
LL_GPIO_PIN_8, |
||||||
|
LL_GPIO_PIN_9, |
||||||
|
LL_GPIO_PIN_10, |
||||||
|
LL_GPIO_PIN_11, |
||||||
|
LL_GPIO_PIN_12, |
||||||
|
LL_GPIO_PIN_13, |
||||||
|
LL_GPIO_PIN_14, |
||||||
|
LL_GPIO_PIN_15, |
||||||
|
}; |
||||||
|
COMPILER_ASSERT(16 == ELEMENTS_IN_ARRAY(LL_GPIO_PINS)); |
||||||
|
|
||||||
|
/** Port number (A=0) to config struct pointer mapping */ |
||||||
|
GPIO_TypeDef * const GPIO_PERIPHS[PORTS_COUNT] = { |
||||||
|
GPIOA, |
||||||
|
GPIOB, |
||||||
|
GPIOC, |
||||||
|
GPIOD, |
||||||
|
GPIOE, |
||||||
|
#if PORTS_COUNT>5 |
||||||
|
GPIOF, |
||||||
|
#endif |
||||||
|
#if PORTS_COUNT>6 |
||||||
|
GPIOG, |
||||||
|
#endif |
||||||
|
}; |
||||||
|
COMPILER_ASSERT(PORTS_COUNT == ELEMENTS_IN_ARRAY(GPIO_PERIPHS)); |
||||||
|
|
||||||
|
const uint32_t LL_ADC_SAMPLETIMES[8] = { |
||||||
|
LL_ADC_SAMPLINGTIME_1CYCLE_5, |
||||||
|
LL_ADC_SAMPLINGTIME_7CYCLES_5, |
||||||
|
LL_ADC_SAMPLINGTIME_13CYCLES_5, |
||||||
|
LL_ADC_SAMPLINGTIME_28CYCLES_5, |
||||||
|
LL_ADC_SAMPLINGTIME_41CYCLES_5, |
||||||
|
LL_ADC_SAMPLINGTIME_55CYCLES_5, |
||||||
|
LL_ADC_SAMPLINGTIME_71CYCLES_5, |
||||||
|
LL_ADC_SAMPLINGTIME_239CYCLES_5, |
||||||
|
}; |
@ -0,0 +1,59 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/04.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GEX_F072_LL_EXTENSION_H |
||||||
|
#define GEX_F072_LL_EXTENSION_H |
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
|
||||||
|
extern const uint32_t LL_SYSCFG_EXTI_PORTS[PORTS_COUNT]; |
||||||
|
extern const uint32_t LL_SYSCFG_EXTI_LINES[16]; |
||||||
|
extern GPIO_TypeDef * const GPIO_PERIPHS[PORTS_COUNT]; |
||||||
|
extern const uint32_t LL_GPIO_PINS[16]; |
||||||
|
extern const uint32_t LL_EXTI_LINES[16]; |
||||||
|
extern const uint32_t LL_ADC_SAMPLETIMES[8]; |
||||||
|
|
||||||
|
|
||||||
|
static inline bool LL_DMA_IsActiveFlag_G(uint32_t isr_snapshot, uint8_t channel) |
||||||
|
{ |
||||||
|
return 0 != (isr_snapshot & (DMA_ISR_GIF1 << (uint32_t)((channel-1) * 4))); |
||||||
|
} |
||||||
|
|
||||||
|
static inline bool LL_DMA_IsActiveFlag_TC(uint32_t isr_snapshot, uint8_t channel) |
||||||
|
{ |
||||||
|
return 0 != (isr_snapshot & (DMA_ISR_TCIF1 << (uint32_t)((channel-1) * 4))); |
||||||
|
} |
||||||
|
|
||||||
|
static inline bool LL_DMA_IsActiveFlag_HT(uint32_t isr_snapshot, uint8_t channel) |
||||||
|
{ |
||||||
|
return 0 != (isr_snapshot & (DMA_ISR_HTIF1 << (uint32_t)((channel-1) * 4))); |
||||||
|
} |
||||||
|
|
||||||
|
static inline bool LL_DMA_IsActiveFlag_TE(uint32_t isr_snapshot, uint8_t channel) |
||||||
|
{ |
||||||
|
return 0 != (isr_snapshot & (DMA_ISR_TEIF1 << (uint32_t)((channel-1) * 4))); |
||||||
|
} |
||||||
|
|
||||||
|
static inline void LL_DMA_ClearFlag_HT(DMA_TypeDef *DMAx, uint8_t channel) |
||||||
|
{ |
||||||
|
DMAx->IFCR = (DMA_IFCR_CHTIF1 << (uint32_t)((channel-1) * 4)); |
||||||
|
} |
||||||
|
|
||||||
|
static inline void LL_DMA_ClearFlag_TC(DMA_TypeDef *DMAx, uint8_t channel) |
||||||
|
{ |
||||||
|
DMAx->IFCR = (DMA_IFCR_CTCIF1 << (uint32_t)((channel-1) * 4)); |
||||||
|
} |
||||||
|
|
||||||
|
static inline void LL_DMA_ClearFlag_TE(DMA_TypeDef *DMAx, uint8_t channel) |
||||||
|
{ |
||||||
|
DMAx->IFCR = (DMA_IFCR_CTEIF1 << (uint32_t)((channel-1) * 4)); |
||||||
|
} |
||||||
|
|
||||||
|
static inline void LL_DMA_ClearFlags(DMA_TypeDef *DMAx, uint8_t channel) |
||||||
|
{ |
||||||
|
DMAx->IFCR = (DMA_IFCR_CGIF1 << (uint32_t)((channel-1) * 4)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#endif //GEX_F072_LL_EXTENSION_H
|
@ -0,0 +1,29 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
#include "unit_adc.h" |
||||||
|
|
||||||
|
#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; |
||||||
|
} |
@ -0,0 +1,568 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/04.
|
||||||
|
//
|
||||||
|
|
||||||
|
#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; |
||||||
|
assert_param(unit); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
assert_param(priv); |
||||||
|
|
||||||
|
uint32_t start = job->data1; |
||||||
|
uint32_t count = job->data2; |
||||||
|
bool close = (bool) job->data3; |
||||||
|
|
||||||
|
// dbg("Send indices [%d -> %d)", (int)start, (int)(start+count));
|
||||||
|
|
||||||
|
TF_TYPE type = close ? EVT_CAPT_DONE : EVT_CAPT_MORE; |
||||||
|
|
||||||
|
TF_Msg msg = { |
||||||
|
.frame_id = priv->stream_frame_id, |
||||||
|
.len = (TF_LEN) (1 + count*sizeof(uint16_t)), |
||||||
|
.type = type, |
||||||
|
}; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
priv->stream_serial++; |
||||||
|
} |
||||||
|
|
||||||
|
static void UADC_JobSendTriggerCaptureHeader(Job *job) |
||||||
|
{ |
||||||
|
Unit *unit = job->unit; |
||||||
|
assert_param(unit); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
assert_param(priv); |
||||||
|
|
||||||
|
EventReport er = { |
||||||
|
.unit = unit, |
||||||
|
.type = EVT_CAPT_START, |
||||||
|
.timestamp = job->timestamp, |
||||||
|
.length = (priv->pretrig_len+1)*priv->nb_channels*sizeof(uint16_t) + 2 /*pretrig len*/ + 1 /*edge*/ + 1 /* seq */ |
||||||
|
}; |
||||||
|
|
||||||
|
uint16_t index_trigd = (uint16_t) job->data1; |
||||||
|
uint8_t edge = (uint8_t) job->data2; |
||||||
|
|
||||||
|
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);
|
||||||
|
{ |
||||||
|
// preamble
|
||||||
|
uint8_t buf[4]; |
||||||
|
PayloadBuilder pb = pb_start(buf, 4, NULL); |
||||||
|
pb_u16(&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
|
||||||
|
uint16_t pretrig_remain = (uint16_t) ((priv->pretrig_len + 1) * priv->nb_channels); // +1 because we want pretrig 0 to exactly start with the triggering sample
|
||||||
|
|
||||||
|
assert_param(index_trigd <= priv->dma_buffer_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
|
||||||
|
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
|
||||||
|
// );
|
||||||
|
|
||||||
|
EventReport_Data( |
||||||
|
(uint8_t *) &priv->dma_buffer[priv->dma_buffer_itemcount - |
||||||
|
items_from_end], |
||||||
|
items_from_end * sizeof(uint16_t)); |
||||||
|
|
||||||
|
assert_param(items_from_end <= pretrig_remain); |
||||||
|
pretrig_remain -= items_from_end; |
||||||
|
} |
||||||
|
|
||||||
|
// 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], |
||||||
|
pretrig_remain * sizeof(uint16_t)); |
||||||
|
} |
||||||
|
} |
||||||
|
EventReport_End(); |
||||||
|
} |
||||||
|
|
||||||
|
static void UADC_JobSendEndOfStreamMsg(Job *job) |
||||||
|
{ |
||||||
|
Unit *unit = job->unit; |
||||||
|
assert_param(unit); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
assert_param(priv); |
||||||
|
|
||||||
|
TF_Msg msg = { |
||||||
|
.type = EVT_CAPT_DONE, |
||||||
|
.frame_id = (TF_ID) job->data1 |
||||||
|
}; |
||||||
|
TF_Respond(comm, &msg); |
||||||
|
} |
||||||
|
|
||||||
|
void UADC_ReportEndOfStream(Unit *unit) |
||||||
|
{ |
||||||
|
assert_param(unit); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
assert_param(priv); |
||||||
|
|
||||||
|
Job j = { |
||||||
|
.unit = unit, |
||||||
|
.data1 = priv->stream_frame_id, |
||||||
|
.cb = UADC_JobSendEndOfStreamMsg |
||||||
|
}; |
||||||
|
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; |
||||||
|
|
||||||
|
assert_param(unit); |
||||||
|
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)) { |
||||||
|
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_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) { |
||||||
|
if (ht || tc) { |
||||||
|
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 { |
||||||
|
handle_httc(unit, tc); |
||||||
|
} |
||||||
|
} |
||||||
|
} 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) |
||||||
|
{ |
||||||
|
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; |
||||||
|
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) { |
||||||
|
sample_pos = (uint32_t) (priv->dma_buffer_itemcount); |
||||||
|
} else { |
||||||
|
sample_pos = dmapos; |
||||||
|
} |
||||||
|
sample_pos -= priv->nb_channels; |
||||||
|
|
||||||
|
int cnt = 0; // index of the sample within the group
|
||||||
|
|
||||||
|
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++; |
||||||
|
|
||||||
|
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 (priv->opmode == ADC_OPMODE_ARMED) { |
||||||
|
uint16_t val = priv->last_samples[priv->trigger_source]; |
||||||
|
|
||||||
|
// 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; |
||||||
|
} |
||||||
|
|
||||||
|
// 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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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.");
|
||||||
|
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 = (uint16_t) DMA_POS(priv); |
||||||
|
priv->trig_stream_remain = priv->trig_len; |
||||||
|
priv->stream_serial = 0; |
||||||
|
|
||||||
|
// dbg("Trigger condition hit, edge=%d, startpos %d", edge_type, (int)priv->stream_startpos);
|
||||||
|
|
||||||
|
Job j = { |
||||||
|
.unit = unit, |
||||||
|
.timestamp = timestamp, |
||||||
|
.data1 = priv->stream_startpos, |
||||||
|
.data2 = edge_type, |
||||||
|
.cb = UADC_JobSendTriggerCaptureHeader |
||||||
|
}; |
||||||
|
scheduleJob(&j); |
||||||
|
|
||||||
|
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); |
||||||
|
if (priv->opmode == ADC_OPMODE_UNINIT) return; |
||||||
|
|
||||||
|
priv->stream_frame_id = frame_id; |
||||||
|
priv->stream_startpos = (uint16_t) DMA_POS(priv); |
||||||
|
priv->trig_stream_remain = len; |
||||||
|
priv->stream_serial = 0; |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_BLCAP); |
||||||
|
} |
||||||
|
|
||||||
|
/** Start stream */ |
||||||
|
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; |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_STREAM); |
||||||
|
} |
||||||
|
|
||||||
|
/** End stream */ |
||||||
|
void UADC_StopStream(Unit *unit) |
||||||
|
{ |
||||||
|
assert_param(unit); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
assert_param(priv); |
||||||
|
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 */ |
||||||
|
void UADC_updateTick(Unit *unit) |
||||||
|
{ |
||||||
|
assert_param(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--; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
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) { |
||||||
|
// 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 || 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.
|
||||||
|
|
||||||
|
// 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) { |
||||||
|
// 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(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) { |
||||||
|
|
||||||
|
// dbg("ADC switch -> TRIG'D / STREAM / BLOCK");
|
||||||
|
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); |
||||||
|
|
||||||
|
LL_DMA_EnableIT_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_EnableIT_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
} |
||||||
|
|
||||||
|
priv->opmode = new_mode; |
||||||
|
} |
@ -0,0 +1,274 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <stm32f072xb.h> |
||||||
|
#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->channels = 1; // PA0
|
||||||
|
priv->enable_tsense = false; |
||||||
|
priv->enable_vref = false; |
||||||
|
priv->sample_time = 0b010; // 13.5c
|
||||||
|
priv->frequency = 1000; |
||||||
|
priv->buffer_size = 512; |
||||||
|
priv->averaging_factor = 500; |
||||||
|
|
||||||
|
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 (!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) |
||||||
|
{ |
||||||
|
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 < 16; i++) { |
||||||
|
if (priv->channels & (1 << i)) { |
||||||
|
char c; |
||||||
|
uint8_t num; |
||||||
|
if (i <= 7) { |
||||||
|
c = 'A'; |
||||||
|
num = i; |
||||||
|
} |
||||||
|
else if (i <= 9) { |
||||||
|
c = 'B'; |
||||||
|
num = (uint8_t) (i - 8); |
||||||
|
} |
||||||
|
else { |
||||||
|
c = 'C'; |
||||||
|
num = (uint8_t) (i - 10); |
||||||
|
} |
||||||
|
|
||||||
|
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); |
||||||
|
priv->nb_channels++; |
||||||
|
} |
||||||
|
} |
||||||
|
if (priv->enable_tsense) priv->nb_channels++; |
||||||
|
if (priv->enable_vref) priv->nb_channels++; |
||||||
|
|
||||||
|
if (priv->nb_channels == 0) { |
||||||
|
dbg("!! Need at least 1 channel"); |
||||||
|
return E_BAD_CONFIG; |
||||||
|
} |
||||||
|
|
||||||
|
if (priv->buffer_size < priv->nb_channels*2*2) { |
||||||
|
dbg("Insufficient buf size"); |
||||||
|
return E_BAD_CONFIG; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------- 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 --------------------------
|
||||||
|
dbg("Setting up TIMER"); |
||||||
|
{ |
||||||
|
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); |
||||||
|
LL_TIM_GenerateEvent_UPDATE(priv->TIMx); // load the prescaller value
|
||||||
|
} |
||||||
|
|
||||||
|
// --------------------- CONFIGURE THE ADC ---------------------------
|
||||||
|
dbg("Setting up ADC"); |
||||||
|
{ |
||||||
|
// Calibrate the ADC
|
||||||
|
dbg("Wait for calib"); |
||||||
|
LL_ADC_StartCalibration(priv->ADCx); |
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
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->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);
|
||||||
|
} |
||||||
|
|
||||||
|
// --------------------- CONFIGURE DMA -------------------------------
|
||||||
|
dbg("Setting up DMA"); |
||||||
|
{ |
||||||
|
// 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_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 = 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
|
||||||
|
|
||||||
|
{ |
||||||
|
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)); |
||||||
|
|
||||||
|
// 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_EnableChannel(priv->DMAx, priv->dma_chnum); |
||||||
|
} |
||||||
|
|
||||||
|
// 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."); |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** 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_ck(priv->dma_buffer); |
||||||
|
} |
||||||
|
|
||||||
|
// Release all resources, deinit pins
|
||||||
|
rsc_teardown(unit); |
||||||
|
|
||||||
|
// Free memory
|
||||||
|
free_ck(unit->data); |
||||||
|
} |
@ -0,0 +1,137 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GEX_F072_ADC_INTERNAL_H |
||||||
|
#define GEX_F072_ADC_INTERNAL_H |
||||||
|
|
||||||
|
#ifndef ADC_INTERNAL |
||||||
|
#error bad include! |
||||||
|
#endif |
||||||
|
|
||||||
|
#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.
|
||||||
|
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 { |
||||||
|
// 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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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 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)
|
||||||
|
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
|
||||||
|
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; |
||||||
|
}; |
||||||
|
|
||||||
|
/** 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); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** 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); |
||||||
|
|
||||||
|
#endif //GEX_F072_ADC_INTERNAL_H
|
@ -0,0 +1,122 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#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->channels = pp_u16(pp); |
||||||
|
priv->enable_tsense = pp_bool(pp); |
||||||
|
priv->enable_vref = pp_bool(pp); |
||||||
|
priv->sample_time = pp_u8(pp); |
||||||
|
priv->frequency = pp_u32(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 */ |
||||||
|
void UADC_writeBinary(Unit *unit, PayloadBuilder *pb) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
pb_u8(pb, 2); // version
|
||||||
|
|
||||||
|
pb_u16(pb, priv->channels); |
||||||
|
pb_bool(pb, priv->enable_tsense); |
||||||
|
pb_bool(pb, priv->enable_vref); |
||||||
|
pb_u8(pb, priv->sample_time); |
||||||
|
pb_u32(pb, priv->frequency); |
||||||
|
pb_u16(pb, priv->buffer_size); |
||||||
|
pb_u16(pb, priv->averaging_factor); |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** 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->channels = parse_pinmask(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "enable_tsense")) { |
||||||
|
priv->enable_tsense = str_parse_yn(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "enable_vref")) { |
||||||
|
priv->enable_vref = str_parse_yn(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "sample_time")) { |
||||||
|
priv->sample_time = (uint8_t) avr_atoi(value); |
||||||
|
if (priv->sample_time > 7) return E_BAD_VALUE; |
||||||
|
} |
||||||
|
else if (streq(key, "frequency")) { |
||||||
|
priv->frequency = (uint32_t) avr_atoi(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; |
||||||
|
} |
||||||
|
|
||||||
|
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-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 (#16)"); |
||||||
|
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_vref)); |
||||||
|
|
||||||
|
iw_cmt_newline(iw); |
||||||
|
iw_comment(iw, "Sampling time (0-7)"); |
||||||
|
iw_entry(iw, "sample_time", "%d", (int)priv->sample_time); |
||||||
|
|
||||||
|
iw_comment(iw, "Sampling frequency (Hz)"); |
||||||
|
iw_entry(iw, "frequency", "%d", (int)priv->frequency); |
||||||
|
|
||||||
|
iw_comment(iw, "Sample buffer size (bytes, 2 per channels per sample)"); |
||||||
|
iw_comment(iw, "- a report is sent when 1/2 of the circular buffer is filled"); |
||||||
|
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, "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); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,338 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2017/11/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "unit_base.h" |
||||||
|
#include "unit_adc.h" |
||||||
|
|
||||||
|
#define ADC_INTERNAL |
||||||
|
#include "_adc_internal.h" |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
enum TplCmd_ { |
||||||
|
CMD_READ_RAW = 0, |
||||||
|
CMD_READ_SMOOTHED = 1, |
||||||
|
|
||||||
|
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, |
||||||
|
}; |
||||||
|
|
||||||
|
/** 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); |
||||||
|
|
||||||
|
// TODO toggling individual channels - would require DMA re-init and various changes in the usage of the struct
|
||||||
|
|
||||||
|
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->extended_channels_mask & (1 << i)) { |
||||||
|
pb_u8(&pb, i); |
||||||
|
} |
||||||
|
} |
||||||
|
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 |
||||||
|
*/ |
||||||
|
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; |
||||||
|
|
||||||
|
/**
|
||||||
|
* 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->extended_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->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]); |
||||||
|
} |
||||||
|
} |
||||||
|
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: |
||||||
|
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 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); |
||||||
|
|
||||||
|
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
|
||||||
|
const 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; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Arm (permissible only if idle and the trigger is configured) |
||||||
|
*/ |
||||||
|
case CMD_ARM: |
||||||
|
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: |
||||||
|
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:; |
||||||
|
dbg("> Abort capture"); |
||||||
|
TRY(UU_ADC_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: |
||||||
|
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: |
||||||
|
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: |
||||||
|
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: |
||||||
|
dbg("> Stream OFF"); |
||||||
|
if (priv->opmode != ADC_OPMODE_STREAM) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Not streaming"); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
UADC_StopStream(unit); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
default: |
||||||
|
return E_UNKNOWN_COMMAND; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Unit template */ |
||||||
|
const UnitDriver UNIT_ADC = { |
||||||
|
.name = "ADC", |
||||||
|
.description = "Analog/digital converter", |
||||||
|
// Settings
|
||||||
|
.preInit = UADC_preInit, |
||||||
|
.cfgLoadBinary = UADC_loadBinary, |
||||||
|
.cfgWriteBinary = UADC_writeBinary, |
||||||
|
.cfgLoadIni = UADC_loadIni, |
||||||
|
.cfgWriteIni = UADC_writeIni, |
||||||
|
// Init
|
||||||
|
.init = UADC_init, |
||||||
|
.deInit = UADC_deInit, |
||||||
|
// Function
|
||||||
|
.handleRequest = UADC_handleRequest, |
||||||
|
.updateTick = UADC_updateTick, |
||||||
|
}; |
@ -0,0 +1,16 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2017/11/25.
|
||||||
|
//
|
||||||
|
// Digital input unit; single or multiple pin read access on one port (A-F)
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef U_TPL_H |
||||||
|
#define U_TPL_H |
||||||
|
|
||||||
|
#include "unit.h" |
||||||
|
|
||||||
|
extern const UnitDriver UNIT_ADC; |
||||||
|
|
||||||
|
error_t UU_ADC_AbortCapture(Unit *unit); |
||||||
|
|
||||||
|
#endif //U_TPL_H
|
Loading…
Reference in new issue