diff --git a/gex.mk b/gex.mk index 922d0d6..91ca96f 100644 --- a/gex.mk +++ b/gex.mk @@ -15,6 +15,7 @@ GEX_SRC_DIR = \ User/units/spi \ User/units/adc \ User/units/sipo \ + User/units/fcap \ User/TinyFrame \ User/CWPack \ User/tasks diff --git a/platform/irq_dispatcher.c b/platform/irq_dispatcher.c index be62258..39a175a 100644 --- a/platform/irq_dispatcher.c +++ b/platform/irq_dispatcher.c @@ -61,6 +61,7 @@ static struct callbacks_ { struct cbslot dma2_7; struct cbslot dma2_8; + struct cbslot tim2; struct cbslot tim6; struct cbslot tim7; struct cbslot tim15; @@ -102,7 +103,7 @@ void irqd_init(void) HAL_NVIC_SetPriority(ADC1_COMP_IRQn, 1, 0); // ADC group completion - higher prio than DMA to let it handle the last halfword first // NVIC_EnableIRQ(TIM1_IRQn); /*!< TIM1 global Interrupt */ -// NVIC_EnableIRQ(TIM2_IRQn); /*!< TIM2 global Interrupt */ + NVIC_EnableIRQ(TIM2_IRQn); /*!< TIM2 global Interrupt */ // NVIC_EnableIRQ(TIM3_IRQn); /*!< TIM3 global Interrupt */ NVIC_EnableIRQ(TIM6_DAC_IRQn); /*!< TIM6 global and DAC channel underrun error Interrupt */ @@ -159,6 +160,7 @@ static struct cbslot *get_slot_for_periph(void *periph) else if (periph == USART5) slot = &callbacks.usart5; #endif + else if (periph == TIM2) slot = &callbacks.tim2; else if (periph == TIM6) slot = &callbacks.tim6; else if (periph == TIM7) slot = &callbacks.tim7; else if (periph == TIM15) slot = &callbacks.tim15; @@ -304,6 +306,11 @@ void EXTI4_15_IRQHandler(void) // TIM14 is used to generate HAL timebase and its handler is in the file "timebase.c" +void TIM2_IRQHandler(void) +{ + CALL_IRQ_HANDLER(callbacks.tim2); +} + void TIM6_DAC_IRQHandler(void) { CALL_IRQ_HANDLER(callbacks.tim6); diff --git a/platform/platform.c b/platform/platform.c index c94b85f..27b73e2 100644 --- a/platform/platform.c +++ b/platform/platform.c @@ -2,6 +2,7 @@ // Created by MightyPork on 2017/11/26. // +#include #include "platform.h" #include "usbd_core.h" #include "USB/usb_device.h" @@ -88,6 +89,7 @@ void plat_init_resources(void) ureg_add_type(&UNIT_1WIRE); ureg_add_type(&UNIT_ADC); ureg_add_type(&UNIT_SIPO); + ureg_add_type(&UNIT_FCAP); // Free all present resources { diff --git a/units/fcap/_fcap_api.c b/units/fcap/_fcap_api.c new file mode 100644 index 0000000..81e1d9e --- /dev/null +++ b/units/fcap/_fcap_api.c @@ -0,0 +1,11 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_fcap.h" + +#define FCAP_INTERNAL +#include "_fcap_internal.h" + diff --git a/units/fcap/_fcap_core.c b/units/fcap/_fcap_core.c new file mode 100644 index 0000000..639b167 --- /dev/null +++ b/units/fcap/_fcap_core.c @@ -0,0 +1,197 @@ +// +// Created by MightyPork on 2018/02/20. +// + +#include "platform.h" + +#define FCAP_INTERNAL +#include "_fcap_internal.h" + +static void UFCAP_PWMBurstReportJob(Job *job) +{ + Unit *unit = job->unit; + struct priv * const priv = unit->data; + + uint8_t buf[20]; + PayloadBuilder pb = pb_start(buf, 20, NULL); + + pb_u16(&pb, PLAT_AHB_MHZ); + pb_u16(&pb, priv->pwm_burst.n_count); + pb_u64(&pb, priv->pwm_burst.period_acu); + pb_u64(&pb, priv->pwm_burst.ontime_acu); + + assert_param(pb.ok); + + com_respond_pb(priv->request_id, MSG_SUCCESS, &pb); + + // timer is already stopped, now in OPMODE_BUSY + priv->opmode = OPMODE_IDLE; +} + +void UFCAP_TimerHandler(void *arg) +{ + Unit *unit = arg; + assert_param(unit); + struct priv * const priv = unit->data; + assert_param(priv); + + TIM_TypeDef * const TIMx = priv->TIMx; + + if (priv->opmode == OPMODE_PWM_CONT) { + if (LL_TIM_IsActiveFlag_CC1(TIMx)) { +// assert_param(!LL_TIM_IsActiveFlag_CC1OVR(TIMx)); + priv->pwm_cont.last_period = LL_TIM_IC_GetCaptureCH1(TIMx); + priv->pwm_cont.last_ontime = priv->pwm_cont.ontime; + LL_TIM_ClearFlag_CC1(TIMx); + LL_TIM_ClearFlag_CC1OVR(TIMx); + } + + if (LL_TIM_IsActiveFlag_CC2(TIMx)) { +// assert_param(!LL_TIM_IsActiveFlag_CC2OVR(TIMx)); + priv->pwm_cont.ontime = LL_TIM_IC_GetCaptureCH2(TIMx); + LL_TIM_ClearFlag_CC2(TIMx); + LL_TIM_ClearFlag_CC2OVR(TIMx); + } + } + else if (priv->opmode == OPMODE_PWM_BURST) { + if (LL_TIM_IsActiveFlag_CC1(TIMx)) { +// assert_param(!LL_TIM_IsActiveFlag_CC1OVR(TIMx)); + const uint32_t period = LL_TIM_IC_GetCaptureCH1(TIMx); + const uint32_t ontime = priv->pwm_burst.ontime; + + if (priv->pwm_burst.n_skip > 0) { + priv->pwm_burst.n_skip--; + } else { + priv->pwm_burst.ontime_acu += ontime; + priv->pwm_burst.period_acu += period; + if (++priv->pwm_burst.n_count == priv->pwm_burst.n_target) { + priv->opmode = OPMODE_BUSY; + UFCAP_StopMeasurement(unit); + + Job j = { + .cb = UFCAP_PWMBurstReportJob, + .unit = unit, + }; + scheduleJob(&j); + } + } + + LL_TIM_ClearFlag_CC1(TIMx); + LL_TIM_ClearFlag_CC1OVR(TIMx); + } + + if (LL_TIM_IsActiveFlag_CC2(TIMx)) { +// assert_param(!LL_TIM_IsActiveFlag_CC2OVR(TIMx)); + priv->pwm_burst.ontime = LL_TIM_IC_GetCaptureCH2(TIMx); + LL_TIM_ClearFlag_CC2(TIMx); + LL_TIM_ClearFlag_CC2OVR(TIMx); + } + } + else if (priv->opmode == OPMODE_IDLE) { + // clear everything - in idle it would cycle in the handler forever + TIMx->SR = 0; + } +} + +static void UFCAP_ClearTimerConfig(Unit *unit) +{ + struct priv * const priv = unit->data; + TIM_TypeDef * const TIMx = priv->TIMx; + + // CLEAR CURRENT STATE, STOP + UFCAP_StopMeasurement(unit); + + // CONFIGURE TIMER BASIC PARAMS + LL_TIM_SetPrescaler(TIMx, 0); + LL_TIM_SetAutoReload(TIMx, 0xFFFFFFFF); + LL_TIM_EnableARRPreload(TIMx); + LL_TIM_GenerateEvent_UPDATE(TIMx); +} + +void UFCAP_StopMeasurement(Unit *unit) +{ + struct priv * const priv = unit->data; + TIM_TypeDef * const TIMx = priv->TIMx; + + LL_TIM_DisableCounter(TIMx); + LL_TIM_DisableIT_CC1(TIMx); + LL_TIM_DisableIT_CC2(TIMx); + LL_TIM_ClearFlag_CC1(TIMx); + LL_TIM_ClearFlag_CC2(TIMx); + LL_TIM_ClearFlag_CC1OVR(TIMx); + LL_TIM_ClearFlag_CC2OVR(TIMx); + LL_TIM_SetCounter(TIMx, 0); +} + +void UFCAP_SwitchMode(Unit *unit, enum fcap_opmode opmode) +{ + struct priv * const priv = unit->data; + + if (opmode == priv->opmode) return; + + priv->opmode = opmode; + + switch (opmode) { + case OPMODE_IDLE: + // XXX maybe we should report the abort to the PC-side listener + UFCAP_StopMeasurement(unit); + break; + + case OPMODE_PWM_CONT: + priv->pwm_cont.last_ontime = 0; + priv->pwm_cont.last_period = 0; + priv->pwm_cont.ontime = 0; + UFCAP_ConfigureForPWMCapture(unit); // is also stopped and restarted + break; + + case OPMODE_PWM_BURST: + priv->pwm_burst.ontime = 0; + priv->pwm_burst.n_count = 0; + priv->pwm_burst.n_skip = 1; // discard the first cycle (will be incomplete) + priv->pwm_burst.period_acu = 0; + priv->pwm_burst.ontime_acu = 0; + UFCAP_ConfigureForPWMCapture(unit); // is also stopped and restarted + break; + default: + trap("Unhandled opmode %d", (int)opmode); + } +} + +void UFCAP_ConfigureForPWMCapture(Unit *unit) +{ + struct priv * const priv = unit->data; + TIM_TypeDef * const TIMx = priv->TIMx; + const uint32_t ll_ch_a = priv->ll_ch_a; + const uint32_t ll_ch_b = priv->ll_ch_b; + + UFCAP_ClearTimerConfig(unit); + + // Enable channels and select mapping to TIx signals + + // A - will be used to measure period + // B - will be used to measure the duty cycle + + // _________ ______ + // _______| |________________| + // A B A + // irq irq,cap irq + // reset + + // B irq may be used if we want to measure a pulse width + + // Normally TI1 = CH1, TI2 = CH2. + // It's possible to select the other channel, which we use to connect both TIx to the shame CHx. + LL_TIM_IC_SetActiveInput(TIMx, ll_ch_a, priv->a_direct ? LL_TIM_ACTIVEINPUT_DIRECTTI : LL_TIM_ACTIVEINPUT_INDIRECTTI); + LL_TIM_IC_SetActiveInput(TIMx, ll_ch_b, priv->a_direct ? LL_TIM_ACTIVEINPUT_INDIRECTTI : LL_TIM_ACTIVEINPUT_DIRECTTI); + LL_TIM_CC_EnableChannel(TIMx, ll_ch_a | ll_ch_b); + + LL_TIM_IC_SetPolarity(TIMx, ll_ch_a, LL_TIM_IC_POLARITY_RISING); + LL_TIM_IC_SetPolarity(TIMx, ll_ch_b, LL_TIM_IC_POLARITY_FALLING); + LL_TIM_SetSlaveMode(TIMx, LL_TIM_SLAVEMODE_RESET); + LL_TIM_SetTriggerInput(TIMx, LL_TIM_TS_TI1FP1); // Use Filtered Input 1 (TI1) + LL_TIM_EnableMasterSlaveMode(TIMx); + + LL_TIM_EnableIT_CC1(TIMx); + LL_TIM_EnableIT_CC2(TIMx); + LL_TIM_EnableCounter(TIMx); +} diff --git a/units/fcap/_fcap_init.c b/units/fcap/_fcap_init.c new file mode 100644 index 0000000..469e309 --- /dev/null +++ b/units/fcap/_fcap_init.c @@ -0,0 +1,120 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define FCAP_INTERNAL +#include "_fcap_internal.h" + +/** Allocate data structure and set defaults */ +error_t UFCAP_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + priv->signal_pname = 'A'; + priv->signal_pnum = 0; + + priv->opmode = OPMODE_PWM_CONT; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t UFCAP_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + // ---- Resolve what to configure ---- + + TIM_TypeDef * const TIMx = TIM2; + Resource timRsc = R_TIM2; + + uint32_t ll_ch_a = 0; + uint32_t ll_ch_b = 0; + + switch (priv->signal_pname) { + case 'A': + switch (priv->signal_pnum) { + case 5: + case 15: + case 0: ll_ch_a = LL_TIM_CHANNEL_CH1; break; + case 1: ll_ch_a = LL_TIM_CHANNEL_CH2; break; + default: + dbg("Bad signal pin!"); + return E_BAD_CONFIG; + } + break; + case 'B': + switch (priv->signal_pnum) { + case 3: ll_ch_a = LL_TIM_CHANNEL_CH2; break; + default: + dbg("Bad signal pin!"); + return E_BAD_CONFIG; + } + break; + + default: + dbg("Bad signal pin port!"); + return E_BAD_CONFIG; + } + const uint32_t ll_timpin_af = LL_GPIO_AF_2; + + bool a_direct = true; + switch (ll_ch_a) { + case LL_TIM_CHANNEL_CH1: + ll_ch_b = LL_TIM_CHANNEL_CH2; + break; + + case LL_TIM_CHANNEL_CH2: + ll_ch_b = LL_TIM_CHANNEL_CH1; + a_direct = false; + break; + } + + // ---- CLAIM ---- + + TRY(rsc_claim_pin(unit, priv->signal_pname, priv->signal_pnum)); + TRY(rsc_claim(unit, timRsc)); + + // ---- INIT ---- + assert_param(ll_ch_a != ll_ch_b); + + priv->TIMx = TIMx; + priv->ll_ch_a = ll_ch_a; + priv->ll_ch_b = ll_ch_b; + priv->a_direct = a_direct; + + TRY(hw_configure_gpio_af(priv->signal_pname, priv->signal_pnum, ll_timpin_af)); + + hw_periph_clock_enable(TIMx); + irqd_attach(TIMx, UFCAP_TimerHandler, unit); + + UFCAP_SwitchMode(unit, OPMODE_IDLE); + + return E_SUCCESS; +} + +/** Tear down the unit */ +void UFCAP_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init peripherals + if (unit->status == E_SUCCESS ) { + UFCAP_SwitchMode(unit, OPMODE_IDLE); + + TIM_TypeDef *TIMx = priv->TIMx; + LL_TIM_DeInit(TIMx); + irqd_attach(TIMx, UFCAP_TimerHandler, unit); + } + + // Release all resources, deinit pins + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/units/fcap/_fcap_internal.h b/units/fcap/_fcap_internal.h new file mode 100644 index 0000000..4c9c08d --- /dev/null +++ b/units/fcap/_fcap_internal.h @@ -0,0 +1,90 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_FCAP_INTERNAL_H +#define GEX_F072_FCAP_INTERNAL_H + +#ifndef FCAP_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +enum fcap_opmode { + OPMODE_IDLE = 0, + OPMODE_BUSY = 1, // used after capture is done, before it's reported + OPMODE_PWM_CONT = 2, + OPMODE_PWM_BURST = 3, // averaging +}; + +/** Private data structure */ +struct priv { + // settings + char signal_pname; // the input pin - one of TIM2 channels + uint8_t signal_pnum; + + // internal state + TIM_TypeDef *TIMx; + uint32_t ll_ch_b; + uint32_t ll_ch_a; + bool a_direct; + + enum fcap_opmode opmode; + + TF_ID request_id; + + union { + struct { + uint32_t ontime; // length of the captured positive pulse in the current interval + uint32_t last_period; //!< length of the captured interval between two rising edges + uint32_t last_ontime; //!< length of the last captured ontime + } pwm_cont; + + struct { + uint32_t ontime; // length of the captured positive pulse in the current interval + uint64_t period_acu; //!< length of the captured interval between two rising edges, sum + uint64_t ontime_acu; //!< length of the last captured ontime, sum + uint16_t n_count; //!< Periods captured + uint16_t n_target; //!< Periods captured - requested count + uint8_t n_skip; //!< Periods to skip before starting the real capture + } pwm_burst; + }; +}; + +/** Allocate data structure and set defaults */ +error_t UFCAP_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void UFCAP_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void UFCAP_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UFCAP_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void UFCAP_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t UFCAP_init(Unit *unit); + +/** Tear down the unit */ +void UFCAP_deInit(Unit *unit); + +// ------------------------------------------------------------------------ + +void UFCAP_TimerHandler(void *arg); + +void UFCAP_StopMeasurement(Unit *unit); + +void UFCAP_ConfigureForPWMCapture(Unit *unit); + +void UFCAP_SwitchMode(Unit *unit, enum fcap_opmode opmode); + +#endif //GEX_F072_FCAP_INTERNAL_H diff --git a/units/fcap/_fcap_settings.c b/units/fcap/_fcap_settings.c new file mode 100644 index 0000000..de702de --- /dev/null +++ b/units/fcap/_fcap_settings.c @@ -0,0 +1,62 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define FCAP_INTERNAL +#include "_fcap_internal.h" + +/** Load from a binary buffer stored in Flash */ +void UFCAP_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->signal_pname = pp_char(pp); + priv->signal_pnum = pp_u8(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void UFCAP_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_char(pb, priv->signal_pname); + pb_u8(pb, priv->signal_pnum); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UFCAP_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "signal-pin")) { + suc = parse_pin(value, &priv->signal_pname, &priv->signal_pnum); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void UFCAP_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Signal input pin"); + iw_comment(iw, "One of: A0, A1, A5, A15, B3"); + iw_entry(iw, "signal-pin", "%c%d", priv->signal_pname, priv->signal_pnum); +} + diff --git a/units/fcap/unit_fcap.c b/units/fcap/unit_fcap.c new file mode 100644 index 0000000..53c62cc --- /dev/null +++ b/units/fcap/unit_fcap.c @@ -0,0 +1,84 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_fcap.h" + +#define FCAP_INTERNAL +#include "_fcap_internal.h" + +// ------------------------------------------------------------------------ + +enum FcapCmd_ { + CMD_STOP = 0, + CMD_PWM_CONT_START = 1, + CMD_PWM_BURST_START = 2, + CMD_PWM_CONT_READ = 10, +}; + +/** Handle a request message */ +static error_t UFCAP_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + switch (command) { + case CMD_STOP: + UFCAP_SwitchMode(unit, OPMODE_IDLE); + return E_SUCCESS; + + case CMD_PWM_CONT_START: + if (priv->opmode == OPMODE_PWM_CONT) return E_SUCCESS; // no-op + if (priv->opmode != OPMODE_IDLE) return E_BUSY; + + UFCAP_SwitchMode(unit, OPMODE_PWM_CONT); + return E_SUCCESS; + + case CMD_PWM_BURST_START: + if (priv->opmode != OPMODE_IDLE) return E_BAD_MODE; + + uint16_t count = pp_u16(pp); + priv->pwm_burst.n_target = count; + priv->request_id = frame_id; + UFCAP_SwitchMode(unit, OPMODE_PWM_BURST); + return E_SUCCESS; + + case CMD_PWM_CONT_READ: + if (priv->opmode != OPMODE_PWM_CONT) { + return E_BAD_MODE; + } + if (priv->pwm_cont.last_period == 0) { + return E_BUSY; + } + + PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); + pb_u16(&pb, PLAT_AHB_MHZ); + pb_u32(&pb, priv->pwm_cont.last_period); + pb_u32(&pb, priv->pwm_cont.last_ontime); + + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Frequency capture */ +const UnitDriver UNIT_FCAP = { + .name = "FCAP", + .description = "Frequency and pulse measurement", + // Settings + .preInit = UFCAP_preInit, + .cfgLoadBinary = UFCAP_loadBinary, + .cfgWriteBinary = UFCAP_writeBinary, + .cfgLoadIni = UFCAP_loadIni, + .cfgWriteIni = UFCAP_writeIni, + // Init + .init = UFCAP_init, + .deInit = UFCAP_deInit, + // Function + .handleRequest = UFCAP_handleRequest, +}; diff --git a/units/fcap/unit_fcap.h b/units/fcap/unit_fcap.h new file mode 100644 index 0000000..f3faf58 --- /dev/null +++ b/units/fcap/unit_fcap.h @@ -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_FCAP_H +#define U_FCAP_H + +#include "unit.h" + +extern const UnitDriver UNIT_FCAP; + +// UU_ prototypes + +#endif //U_FCAP_H diff --git a/utils/error.h b/utils/error.h index 3b165af..54de8ce 100644 --- a/utils/error.h +++ b/utils/error.h @@ -28,6 +28,7 @@ X(CHECKSUM_MISMATCH, NULL) /* bus checksum failed */ \ X(PROTOCOL_BREACH, NULL) /* eating with the wrong spoon */ \ X(BUSY, NULL) /* Unit is busy */ \ + X(BAD_MODE, NULL) /* Command not permissible in current opmode */ \ \ /* VFS user errors (those are meant to be shown to user) */ \ X(VFS_ERROR_DURING_TRANSFER, "Error during transfer") \