diff options
author | Fabien Poussin <fabien.poussin@gmail.com> | 2019-01-09 02:51:33 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-09 02:51:33 +0100 |
commit | e19ff53f02131d5a083bfda00d6481a7d7701b93 (patch) | |
tree | fb5e9103f986743d0fee5ce90cf2720753c4d0df /os/hal/ports/NRF5/LLD/TIMERv1 | |
parent | ae22efae8bc0d6ac7a4f013c2a41641726344687 (diff) | |
parent | 2168085ac76c280880a0c2262a7e63fd0ce7c952 (diff) | |
download | ChibiOS-Contrib-e19ff53f02131d5a083bfda00d6481a7d7701b93.tar.gz ChibiOS-Contrib-e19ff53f02131d5a083bfda00d6481a7d7701b93.tar.bz2 ChibiOS-Contrib-e19ff53f02131d5a083bfda00d6481a7d7701b93.zip |
Merge pull request #178 from AndruPol/master
added NRF52 pwm, icu, i2c, radio esb drivers
Diffstat (limited to 'os/hal/ports/NRF5/LLD/TIMERv1')
-rw-r--r-- | os/hal/ports/NRF5/LLD/TIMERv1/driver.mk | 4 | ||||
-rw-r--r-- | os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.c | 768 | ||||
-rw-r--r-- | os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.h | 424 |
3 files changed, 1196 insertions, 0 deletions
diff --git a/os/hal/ports/NRF5/LLD/TIMERv1/driver.mk b/os/hal/ports/NRF5/LLD/TIMERv1/driver.mk index 6f3ce0e..333fe3c 100644 --- a/os/hal/ports/NRF5/LLD/TIMERv1/driver.mk +++ b/os/hal/ports/NRF5/LLD/TIMERv1/driver.mk @@ -2,8 +2,12 @@ ifeq ($(USE_SMART_BUILD),yes) ifneq ($(findstring HAL_USE_GPT TRUE,$(HALCONF)),)
PLATFORMSRC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TIMERv1/hal_gpt_lld.c
endif
+ifneq ($(findstring HAL_USE_ICU TRUE,$(HALCONF)),)
+PLATFORMSRC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.c
+endif
else
PLATFORMSRC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TIMERv1/hal_gpt_lld.c
+PLATFORMSRC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.c
endif
PLATFORMINC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TIMERv1
diff --git a/os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.c b/os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.c new file mode 100644 index 0000000..4966007 --- /dev/null +++ b/os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.c @@ -0,0 +1,768 @@ +/* + ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + * Hardware Abstraction Layer for Extended Input Capture Unit + */ +#include "hal.h" + +#if (HAL_USE_ICU == TRUE) || defined(__DOXYGEN__) + +/** + * @brief Returns the compare value of the latest cycle. + * + * @param[in] chp Pointer to channel structure that fired the interrupt. + * @return The number of ticks. + * + * @notapi + */ +//#define icu_lld_get_compare(chp) (*((chp)->ccp) + 1) + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/** + * @brief ICUD1 driver identifier. + * @note The driver ICUD1 allocates the complex timer TIMER0 when enabled. + */ +#if NRF5_ICU_USE_TIMER0 && !defined(__DOXYGEN__) +ICUDriver ICUD1; +#endif + +/** + * @brief ICUD2 driver identifier. + * @note The driver ICUD2 allocates the timer TIMER1 when enabled. + */ +#if NRF5_ICU_USE_TIMER1 && !defined(__DOXYGEN__) +ICUDriver ICUD2; +#endif + +/** + * @brief ICUD3 driver identifier. + * @note The driver ICUD3 allocates the timer TIMER2 when enabled. + */ +#if NRF5_ICU_USE_TIMER2 && !defined(__DOXYGEN__) +ICUDriver ICUD3; +#endif + +/** + * @brief ICUD4 driver identifier. + * @note The driver ICUD4 allocates the timer TIMER3 when enabled. + */ +#if NRF5_ICU_USE_TIMER3 && !defined(__DOXYGEN__) +ICUDriver ICUD4; +#endif + +/** + * @brief ICUD5 driver identifier. + * @note The driver ICUD5 allocates the timer TIMER4 when enabled. + */ +#if NRF5_ICU_USE_TIMER4 && !defined(__DOXYGEN__) +ICUDriver ICUD5; +#endif + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ +/** + * @brief Returns pulse width. + * @details The time is defined as number of ticks. + * + * @param[in] icup Pointer to the ICUDriver object. + * @param[in] channel The timer channel that fired the interrupt. + * @param[in] compare Content of the CC register. + * @return The number of ticks. + * + * @notapi + */ +static icucnt_t get_time_width(const ICUDriver *icup, + uint8_t channel, + icucnt_t compare) { + + const ICUChannel *chp = &icup->channel[channel]; + + /* Note! there is no overflow check because it handles under the hood of + unsigned subtraction math.*/ + return compare - chp->last_idle; +} + +/** + * @brief Returns pulse period. + * @details The time is defined as number of ticks. + * + * @param[in] icup Pointer to the ICUDriver object. + * @param[in] channel The timer channel that fired the interrupt. + * @param[in] compare Content of the CC register. + * @return The number of ticks. + * + * @notapi + */ +static icucnt_t get_time_period(const ICUDriver *icup, + uint8_t channel, + icucnt_t compare) { + + const ICUChannel *chp = &icup->channel[channel]; + + /* Note! there is no overflow check because it handles under the hood of + unsigned subtraction math.*/ + return compare - chp->last_idle; +} + +/** + * @brief ICU width event. + * + * @param[in] icup Pointer to the @p ICUDriver object + * @param[in] channel The timer channel that fired the interrupt. + * + * @notapi + */ +static void _isr_invoke_width_cb(ICUDriver *icup, uint8_t channel) { + ICUChannel *chp = &icup->channel[channel]; + icucnt_t compare = icup->timer->CC[channel+2]; + chp->last_active = compare; + if (ICU_CH_ACTIVE == chp->state) { + icup->result.width = get_time_width(icup, channel, compare); + if ((icup->state == ICU_ACTIVE) && (icup->config->width_cb != NULL)) + icup->config->width_cb(icup); + chp->state = ICU_CH_IDLE; + } +} + +/** + * @brief ICU period detect event. + * + * @param[in] icup Pointer to the @p ICUDriver object + * @param[in] channel The timer channel that fired the interrupt. + * + * @notapi + */ +static void _isr_invoke_period_cb(ICUDriver *icup, uint8_t channel) { + ICUChannel *chp = &icup->channel[channel]; + icucnt_t compare = (uint32_t)icup->timer->CC[channel]; + icup->result.period = get_time_period(icup, channel, compare); + chp->last_idle = compare; + chp->state = ICU_CH_ACTIVE; + if ((icup->state == ICU_ACTIVE) && (icup->config->period_cb != NULL)) + icup->config->period_cb(icup); + icup->state = ICU_ACTIVE; + /* Set overflow timeout */ + icup->timer->CC[channel] = compare + ICU_WAIT_TIMEOUT; +} + +/** + * @brief Shared IRQ handler. + * + * @param[in] icup Pointer to the @p ICUDriver object + */ +void icu_lld_serve_gpiote_interrupt(ICUDriver *icup) { + uint8_t ch; + for (ch=0; ch<ICU_CHANNELS; ch++) { + const ICUChannelConfig *cfg_channel = &icup->config->iccfgp[ch]; + const uint8_t *gpiote_channel = cfg_channel->gpiote_channel; + + /* Period event */ + if (NRF_GPIOTE->INTENSET & (1 << gpiote_channel[0]) && NRF_GPIOTE->EVENTS_IN[gpiote_channel[0]]) { + _isr_invoke_period_cb(icup, ch); + NRF_GPIOTE->EVENTS_IN[gpiote_channel[0]] = 0; + (void) NRF_GPIOTE->EVENTS_IN[gpiote_channel[0]]; + } + /* Width event */ + if (NRF_GPIOTE->INTENSET & (1 << gpiote_channel[1]) && NRF_GPIOTE->EVENTS_IN[gpiote_channel[1]]) { + _isr_invoke_width_cb(icup, ch); + NRF_GPIOTE->EVENTS_IN[gpiote_channel[1]] = 0; + (void) NRF_GPIOTE->EVENTS_IN[gpiote_channel[1]]; + } + } +} + +/** + * @brief Overflow IRQ handler. + * + * @param[in] icup Pointer to the @p ICUDriver object + */ +void icu_lld_serve_interrupt(ICUDriver *icup) { + uint8_t ch; + for (ch=0; ch<ICU_CHANNELS; ch++) { + /* Clear overflow events */ + if (icup->timer->INTENSET & (1 << (TIMER_INTENSET_COMPARE0_Pos + ch)) && + icup->timer->EVENTS_COMPARE[ch]) { + icup->timer->EVENTS_COMPARE[ch] = 0; + (void) icup->timer->EVENTS_COMPARE[ch]; + /* Set next overlow compare */ + icup->timer->CC[ch] = icup->timer->CC[ch] + ICU_WAIT_TIMEOUT; + } + } + if (icup->config->overflow_cb != NULL) + icup->config->overflow_cb(icup); + icup->state = ICU_WAITING; +} + +/** + * @brief Starts every channel. + * + * @param[in] icup Pointer to the @p ICUDriver object + * + * @note GPIO Line[0] -> GPIOTE channel[0] will detect start edge. + * @note GPIO Line[1] -> GPIOTE channel[1] will detect end edge. + */ +static void start_channels(ICUDriver *icup) { + + /* Set each input channel that is used as: a normal input capture channel. */ +#if NRF5_ICU_USE_GPIOTE_PPI + uint8_t channel; + for (channel = 0; channel<ICU_CHANNELS; channel++) { + const ICUChannelConfig *cfg_channel = &icup->config->iccfgp[channel]; + if (cfg_channel->mode == ICU_INPUT_DISABLED) continue; + + const uint32_t gpio_pin0 = PAL_PAD(cfg_channel->ioline[0]); + const uint32_t gpio_pin1 = PAL_PAD(cfg_channel->ioline[1]); + osalDbgAssert((gpio_pin0 < 32) && + (gpio_pin1 < 32) && + (gpio_pin0 != gpio_pin1), + "invalid Line configuration"); + + /* NRF52832 GPIOTE channels 0..7 */ + const uint8_t *gpiote_channel = cfg_channel->gpiote_channel; + osalDbgAssert((gpiote_channel[0] < 8) && + (gpiote_channel[1] < 8) && + (gpiote_channel[0] != gpiote_channel[1]), + "invalid GPIOTE configuration"); + + /* NRF52832 PPI channels 0..19 */ + const uint8_t *ppi_channel = cfg_channel->ppi_channel; + osalDbgAssert((gpiote_channel[0] < 20) && + (gpiote_channel[1] < 20) && + (gpiote_channel[0] != gpiote_channel[1]), + "invalid PPI configuration"); + + /* Program PPI events for period */ + NRF_PPI->CH[ppi_channel[0]].EEP = (uint32_t) &NRF_GPIOTE->EVENTS_IN[gpiote_channel[0]]; + NRF_PPI->CH[ppi_channel[0]].TEP = (uint32_t) &icup->timer->TASKS_CAPTURE[channel]; + + /* Program PPI events for width */ + NRF_PPI->CH[ppi_channel[1]].EEP = (uint32_t) &NRF_GPIOTE->EVENTS_IN[gpiote_channel[1]]; + NRF_PPI->CH[ppi_channel[1]].TEP = (uint32_t) &icup->timer->TASKS_CAPTURE[channel+2]; + + /* Disable GPIOTE interrupts */ + NRF_GPIOTE->INTENCLR = (GPIOTE_INTENCLR_PORT_Clear << GPIOTE_INTENCLR_PORT_Pos) | + (1 << gpiote_channel[0]) | (1 << gpiote_channel[1]); + NRF_GPIOTE->EVENTS_PORT = 1; + + /* Clear GPIOTE channels */ + NRF_GPIOTE->CONFIG[gpiote_channel[0]] &= ~(GPIOTE_CONFIG_PSEL_Msk | GPIOTE_CONFIG_POLARITY_Msk); + NRF_GPIOTE->CONFIG[gpiote_channel[1]] &= ~(GPIOTE_CONFIG_PSEL_Msk | GPIOTE_CONFIG_POLARITY_Msk); + + /* Set GPIOTE channels */ + if (cfg_channel->mode == ICU_INPUT_ACTIVE_HIGH) { + NRF_GPIOTE->CONFIG[gpiote_channel[0]] = + (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) | + ((gpio_pin0 << GPIOTE_CONFIG_PSEL_Pos) & GPIOTE_CONFIG_PSEL_Msk) | + ((GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos) & GPIOTE_CONFIG_POLARITY_Msk); + NRF_GPIOTE->CONFIG[gpiote_channel[1]] = + (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) | + ((gpio_pin1 << GPIOTE_CONFIG_PSEL_Pos) & GPIOTE_CONFIG_PSEL_Msk) | + ((GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) & GPIOTE_CONFIG_POLARITY_Msk); + } else { + NRF_GPIOTE->CONFIG[gpiote_channel[0]] = + (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) | + ((gpio_pin0 << GPIOTE_CONFIG_PSEL_Pos) & GPIOTE_CONFIG_PSEL_Msk) | + ((GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) + & GPIOTE_CONFIG_POLARITY_Msk); + NRF_GPIOTE->CONFIG[gpiote_channel[1]] = + (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) | + ((gpio_pin1 << GPIOTE_CONFIG_PSEL_Pos) & GPIOTE_CONFIG_PSEL_Msk) | + ((GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos) + & GPIOTE_CONFIG_POLARITY_Msk); + } + } +#endif + +} + +/*===========================================================================*/ +/* Driver interrupt handlers. */ +/*===========================================================================*/ + +#if NRF5_ICU_USE_GPIOTE_PPI +/** + * @brief GPIOTE events interrupt handler. + * @note It is assumed that the various sources are only activated if the + * associated callback pointer is not equal to @p NULL in order to not + * perform an extra check in a potentially critical interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(Vector58) { + + OSAL_IRQ_PROLOGUE(); + + icu_lld_serve_gpiote_interrupt(&ICUD1); + + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF5_ICU_USE_GPIOTE_PPI */ + +#if NRF5_ICU_USE_TIMER0 +/** + * @brief TIMER0 compare interrupt handler. + * @note It is assumed that the various sources are only activated if the + * associated callback pointer is not equal to @p NULL in order to not + * perform an extra check in a potentially critical interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(Vector60) { + + OSAL_IRQ_PROLOGUE(); + + icu_lld_serve_interrupt(&ICUD1); + + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF5_ICU_USE_TIMER0 */ + +#if NRF5_ICU_USE_TIMER1 +/** + * @brief TIMER1 interrupt handler. + * @note It is assumed that the various sources are only activated if the + * associated callback pointer is not equal to @p NULL in order to not + * perform an extra check in a potentially critical interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(Vector64) { + + OSAL_IRQ_PROLOGUE(); + + icu_lld_serve_interrupt(&ICUD2); + + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF5_ICU_USE_TIMER1 */ + +#if NRF5_ICU_USE_TIMER2 +/** + * @brief TIMER2 interrupt handler. + * @note It is assumed that the various sources are only activated if the + * associated callback pointer is not equal to @p NULL in order to not + * perform an extra check in a potentially critical interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(Vector68) { + + OSAL_IRQ_PROLOGUE(); + + icu_lld_serve_interrupt(&ICUD3); + + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF5_ICU_USE_TIMER2 */ + +#if NRF5_ICU_USE_TIMER3 +/** + * @brief TIMER3 interrupt handler. + * @note It is assumed that the various sources are only activated if the + * associated callback pointer is not equal to @p NULL in order to not + * perform an extra check in a potentially critical interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(VectorA8) { + + OSAL_IRQ_PROLOGUE(); + + icu_lld_serve_interrupt(&ICUD4); + + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF5_ICU_USE_TIMER3 */ + +#if NRF5_ICU_USE_TIMER4 +/** + * @brief TIMER4 interrupt handler. + * @note It is assumed that the various sources are only activated if the + * associated callback pointer is not equal to @p NULL in order to not + * perform an extra check in a potentially critical interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(VectorAC) { + + OSAL_IRQ_PROLOGUE(); + + icu_lld_serve_interrupt(&ICUD5); + + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF5_ICU_USE_TIMER4 */ + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Low level ICU driver initialization. + * + * @notapi + */ +void icu_lld_init(void) { +#if NRF5_ICU_USE_TIMER0 + /* Driver initialization.*/ + icuObjectInit(&ICUD1); + ICUD1.timer = NRF_TIMER0; +#endif + +#if NRF5_ICU_USE_TIMER1 + /* Driver initialization.*/ + icuObjectInit(&ICUD2); + ICUD2.timer = NRF_TIMER1; +#endif + +#if NRF5_ICU_USE_TIMER2 + /* Driver initialization.*/ + icuObjectInit(&ICUD3); + ICUD3.timer = NRF_TIMER2; +#endif + +#if NRF5_ICU_USE_TIMER3 + /* Driver initialization.*/ + icuObjectInit(&ICUD4); + ICUD4.timer = NRF_TIMER3; +#endif + +#if NRF5_ICU_USE_TIMER4 + /* Driver initialization.*/ + icuObjectInit(&ICUD5); + ICUD5.timer = NRF_TIMER4; +#endif +} + +/** + * @brief Configures and activates the ICU peripheral. + * + * @param[in] icup Pointer to the @p ICUDriver object + * + * @notapi + */ +void icu_lld_start(ICUDriver *icup) { + size_t ch; + osalDbgAssert((&icup->config->iccfgp[0] != NULL) || + (&icup->config->iccfgp[1] != NULL), + "invalid input configuration"); + /* Prescaler value calculation: ftimer = 16MHz / 2^PRESCALER */ + uint16_t psc_ratio = 16000000 / icup->config->frequency; + /* Prescaler ratio must be between 1 and 512, and a power of two. */ + osalDbgAssert(psc_ratio <= 512 && !(psc_ratio & (psc_ratio - 1)), + "invalid frequency"); + /* Prescaler value as a power of 2, must be 0..9 */ + uint32_t psc_value; + for (psc_value = 0; psc_value < 10; psc_value++) + if (psc_ratio == (unsigned)(1 << psc_value)) + break; + + /* Configure as 32bits timer */ + icup->timer->BITMODE = TIMER_BITMODE_BITMODE_32Bit; + + /* Set timer mode */ + icup->timer->MODE = TIMER_MODE_MODE_Timer; + + /* Set prescaler */ + icup->timer->PRESCALER = psc_value; + + /* With clear shortcuts. */ + icup->timer->SHORTS = 0; + + /* Clear Timer */ + icup->timer->TASKS_CLEAR = 1; + + /* Disable and reset interrupts for compare events */ + icup->timer->INTENCLR = (TIMER_INTENCLR_COMPARE0_Msk | + TIMER_INTENCLR_COMPARE1_Msk | + TIMER_INTENCLR_COMPARE2_Msk | + TIMER_INTENCLR_COMPARE3_Msk ); + + icup->timer->EVENTS_COMPARE[0] = 0; + icup->timer->EVENTS_COMPARE[1] = 0; + icup->timer->EVENTS_COMPARE[2] = 0; + icup->timer->EVENTS_COMPARE[3] = 0; + (void) icup->timer->EVENTS_COMPARE[0]; + (void) icup->timer->EVENTS_COMPARE[1]; + (void) icup->timer->EVENTS_COMPARE[2]; + (void) icup->timer->EVENTS_COMPARE[3]; + + /* Enable GPIOTE and TIMER interrupt vectors.*/ +#if NRF5_ICU_USE_GPIOTE_PPI + nvicEnableVector(GPIOTE_IRQn, NRF5_ICU_GPIOTE_IRQ_PRIORITY); +#endif +#if NRF5_ICU_USE_TIMER0 + if (&ICUD1 == icup) { + nvicEnableVector(TIMER0_IRQn, NRF5_ICU_TIMER0_IRQ_PRIORITY); + } +#endif +#if NRF5_ICU_USE_TIMER1 + if (&ICUD2 == icup) { + nvicEnableVector(TIMER1_IRQn, NRF5_ICU_TIMER1_IRQ_PRIORITY); + } +#endif +#if NRF5_ICU_USE_TIMER2 + if (&ICUD3 == icup) { + nvicEnableVector(TIMER2_IRQn, NRF5_ICU_TIMER2_IRQ_PRIORITY); + } +#endif +#if NRF5_ICU_USE_TIMER3 + if (&ICUD4 == icup) { + nvicEnableVector(TIMER3_IRQn, NRF5_ICU_TIMER3_IRQ_PRIORITY); + } +#endif +#if NRF5_ICU_USE_TIMER4 + if (&ICUD5 == icup) { + nvicEnableVector(TIMER4_IRQn, NRF5_ICU_TIMER4_IRQ_PRIORITY); + } +#endif + + /* clean channel structures and set pointers to channel configs */ + for (ch=0; ch<ICU_CHANNELS; ch++) { + icup->channel[ch].last_active = 0; + icup->channel[ch].last_idle = 0; + icup->channel[ch].state = ICU_CH_IDLE; + } + /* Set GPIOTE & PPI channels */ + start_channels(icup); +} + +/** + * @brief Deactivates the ICU peripheral. + * + * @param[in] icup Pointer to the @p ICUDriver object + * + * @notapi + */ +void icu_lld_stop(ICUDriver *icup) { + + if (icup->state == ICU_READY) { + /* Timer stop.*/ + icup->timer->TASKS_STOP = 1; + +#if NRF5_ICU_USE_GPIOTE_PPI + uint8_t channel; + for (channel = 0; channel<ICU_CHANNELS; channel++) { + const ICUChannelConfig *cfg_channel = &icup->config->iccfgp[channel]; + if (cfg_channel == NULL) continue; + + const uint8_t *gpiote_channel = cfg_channel->gpiote_channel; + const uint8_t *ppi_channel = cfg_channel->ppi_channel; + + /* Disable Timer interrupt */ + icup->timer->INTENCLR = 1 << (TIMER_INTENCLR_COMPARE0_Pos + channel); + + /* Disable GPIOTE interrupts */ + NRF_GPIOTE->INTENCLR = (1 << gpiote_channel[0]) | (1 << gpiote_channel[1]); + + /* Disable PPI channels */ + NRF_PPI->CHENCLR = ((1 << ppi_channel[0]) | (1 << ppi_channel[1])); + + /* Clear GPIOTE channels */ + NRF_GPIOTE->CONFIG[gpiote_channel[0]] &= ~(GPIOTE_CONFIG_PSEL_Msk | GPIOTE_CONFIG_POLARITY_Msk); + NRF_GPIOTE->CONFIG[gpiote_channel[1]] &= ~(GPIOTE_CONFIG_PSEL_Msk | GPIOTE_CONFIG_POLARITY_Msk); + } +#endif + +#if NRF5_ICU_USE_GPIOTE_PPI + nvicDisableVector(GPIOTE_IRQn); +#endif +#if NRF5_ICU_USE_TIMER0 + if (&ICUD1 == icup) { + nvicDisableVector(TIMER0_IRQn); + } +#endif +#if NRF5_ICU_USE_TIMER1 + if (&ICUD2 == icup) { + nvicDisableVector(TIMER1_IRQn); + } +#endif +#if NRF5_ICU_USE_TIMER2 + if (&ICUD3 == icup) { + nvicDisableVector(TIMER2_IRQn); + } +#endif +#if NRF5_ICU_USE_TIMER3 + if (&ICUD4 == icup) { + nvicDisableVector(TIMER3_IRQn); + } +#endif +#if NRF5_ICU_USE_TIMER4 + if (&ICUD5 == icup) { + nvicDisableVector(TIMER4_IRQn); + } +#endif + } +} + +/** + * @brief Starts the input capture. + * + * @param[in] icup pointer to the @p ICUDriver object + * + * @notapi + */ +void icu_lld_start_capture(ICUDriver *icup) { + /* Clear and start Timer */ + icup->timer->TASKS_CLEAR = 1; + icup->timer->TASKS_START = 1; + +#if NRF5_ICU_USE_GPIOTE_PPI + uint8_t channel; + for (channel = 0; channel<ICU_CHANNELS; channel++) { + const ICUChannelConfig *cfg_channel = &icup->config->iccfgp[channel]; + if (cfg_channel == NULL) continue; + + const uint8_t *gpiote_channel = cfg_channel->gpiote_channel; + const uint8_t *ppi_channel = cfg_channel->ppi_channel; + + /* Enable interrupt for overflow events */ + icup->timer->CC[channel] = ICU_WAIT_TIMEOUT; + icup->timer->INTENSET = 1 << (TIMER_INTENSET_COMPARE0_Pos + channel); + + /* Enable PPI channels */ + NRF_PPI->CHENSET = ((1 << ppi_channel[0]) | (1 << ppi_channel[1])); + + /* Enable GPIOTE interrupt */ + NRF_GPIOTE->INTENSET = (1 << gpiote_channel[0]) | (1 << gpiote_channel[1]); + } +#endif +} + +/** + * @brief Waits for a completed capture. + * @note The operation is performed in polled mode. + * @note In order to use this function notifications must be disabled. + * + * @param[in] icup pointer to the @p ICUDriver object + * @return The capture status. + * @retval false if the capture is successful. + * @retval true if a timer overflow occurred. + * + * @notapi + */ +bool icu_lld_wait_capture(ICUDriver *icup) { + + /* If the driver is still in the ICU_WAITING state then we need to wait + for the first activation edge.*/ + if (icup->state == ICU_WAITING) + if (icu_lld_wait_edge(icup)) + return true; + + /* This edge marks the availability of a capture result.*/ + return icu_lld_wait_edge(icup); +} + +/** + * @brief Stops the input capture. + * + * @param[in] icup pointer to the @p ICUDriver object + * + * @notapi + */ +void icu_lld_stop_capture(ICUDriver *icup) { + /* Timer stopped.*/ + icup->timer->TASKS_STOP = 1; + +#if NRF5_ICU_USE_GPIOTE_PPI + uint8_t channel; + for (channel = 0; channel<ICU_CHANNELS; channel++) { + const ICUChannelConfig *cfg_channel = &icup->config->iccfgp[channel]; + if (cfg_channel == NULL) continue; + + const uint8_t *gpiote_channel = cfg_channel->gpiote_channel; + const uint8_t *ppi_channel = cfg_channel->ppi_channel; + + /* Disable Timer interrupt for overflow events */ + icup->timer->INTENCLR = 1 << (TIMER_INTENCLR_COMPARE0_Pos + channel); + + /* Disable GPIOTE interrupt */ + NRF_GPIOTE->INTENCLR = (1 << gpiote_channel[0]) | (1 << gpiote_channel[1]); + + /* Disable PPI channels */ + NRF_PPI->CHENCLR = ((1 << ppi_channel[0]) | (1 << ppi_channel[1])); + } +#endif +} + +/** + * @brief Enables notifications. + * @pre The ICU unit must have been activated using @p icuStart() and the + * capture started using @p icuStartCapture(). + * @note If the notification is already enabled then the call has no effect. + * + * @param[in] icup pointer to the @p ICUDriver object + * + * @notapi + */ +void icu_lld_enable_notifications(ICUDriver *icup) { +#if NRF5_ICU_USE_GPIOTE_PPI + uint8_t channel; + for (channel = 0; channel<ICU_CHANNELS; channel++) { + const ICUChannelConfig *cfg_channel = &icup->config->iccfgp[channel]; + if (cfg_channel == NULL) continue; + + const uint8_t *gpiote_channel = cfg_channel->gpiote_channel; + + /* Enable Timer interrupt */ + icup->timer->INTENSET = 1 << (TIMER_INTENSET_COMPARE0_Pos + channel); + + /* Enable GPIOTE interrupt */ + NRF_GPIOTE->INTENSET = (1 << gpiote_channel[0]) | (1 << gpiote_channel[1]); + } +#endif +} + +/** + * @brief Disables notifications. + * @pre The ICU unit must have been activated using @p icuStart() and the + * capture started using @p icuStartCapture(). + * @note If the notification is already disabled then the call has no effect. + * + * @param[in] icup pointer to the @p ICUDriver object + * + * @notapi + */ +void icu_lld_disable_notifications(ICUDriver *icup) { + /* All interrupts disabled.*/ +#if NRF5_ICU_USE_GPIOTE_PPI + uint8_t channel; + for (channel = 0; channel<ICU_CHANNELS; channel++) { + const ICUChannelConfig *cfg_channel = &icup->config->iccfgp[channel]; + if (cfg_channel == NULL) continue; + + const uint8_t *gpiote_channel = cfg_channel->gpiote_channel; + + /* Disable Timer interrupt for overflow events */ + icup->timer->INTENCLR = 1 << (TIMER_INTENCLR_COMPARE0_Pos + channel); + + /* Disable GPIOTE interrupt */ + NRF_GPIOTE->INTENCLR = (1 << gpiote_channel[0]) | (1 << gpiote_channel[1]); + } +#endif + +} + +#endif /* HAL_USE_ICU */ diff --git a/os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.h b/os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.h new file mode 100644 index 0000000..7fcace7 --- /dev/null +++ b/os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.h @@ -0,0 +1,424 @@ +/* + ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +/* +*/ + +#ifndef HAL_ICU_LLD_H +#define HAL_ICU_LLD_H + +#if (HAL_USE_ICU == TRUE) || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ +/** + * @brief Number of ICU channels per ICU driver. + */ +#define ICU_CHANNELS 2 /* max channels */ +#define ICU_WAIT_TIMEOUT ( 0xFFFF ) /* first edge wait timeout */ + +#define ICU_FREQUENCY_16MHZ 16000000 /** @brief 16MHz */ +#define ICU_FREQUENCY_8MHZ 8000000 /** @brief 8MHz */ +#define ICU_FREQUENCY_4MHZ 4000000 /** @brief 4MHz */ +#define ICU_FREQUENCY_2MHZ 2000000 /** @brief 2MHz */ +#define ICU_FREQUENCY_1MHZ 1000000 /** @brief 1MHz */ +#define ICU_FREQUENCY_500KHZ 500000 /** @brief 500kHz */ +#define ICU_FREQUENCY_250KHZ 250000 /** @brief 250kHz */ +#define ICU_FREQUENCY_125KHZ 125000 /** @brief 125kHz */ +#define ICU_FREQUENCY_62500HZ 62500 /** @brief 62500Hz */ +#define ICU_FREQUENCY_31250HZ 31250 /** @brief 31250Hz */ + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @name Configuration options + * @{ + */ +/** + * @brief ICUD1 driver enable switch. + * @details If set to @p TRUE the support for ICUD1 is included. + * @note The default is @p TRUE. + */ +#if !defined(NRF5_ICU_USE_TIMER0) || defined(__DOXYGEN__) +#define NRF5_ICU_USE_TIMER0 FALSE +#endif + +/** + * @brief ICUD2 driver enable switch. + * @details If set to @p TRUE the support for ICUD2 is included. + * @note The default is @p TRUE. + */ +#if !defined(NRF5_ICU_USE_TIMER1) || defined(__DOXYGEN__) +#define NRF5_ICU_USE_TIMER1 FALSE +#endif + +/** + * @brief ICUD3 driver enable switch. + * @details If set to @p TRUE the support for ICUD3 is included. + * @note The default is @p TRUE. + */ +#if !defined(NRF5_ICU_USE_TIMER2) || defined(__DOXYGEN__) +#define NRF5_ICU_USE_TIMER2 FALSE +#endif + +/** + * @brief ICUD4 driver enable switch. + * @details If set to @p TRUE the support for ICUD4 is included. + * @note The default is @p TRUE. + */ +#if !defined(NRF5_ICU_USE_TIMER3) || defined(__DOXYGEN__) +#define NRF5_ICU_USE_TIMER3 FALSE +#endif + +/** + * @brief ICUD5 driver enable switch. + * @details If set to @p TRUE the support for ICUD5 is included. + * @note The default is @p TRUE. + */ +#if !defined(NRF5_ICU_USE_TIMER4) || defined(__DOXYGEN__) +#define NRF5_ICU_USE_TIMER4 FALSE +#endif + +/** + * @brief ICUD1 interrupt priority level setting. + */ +#if !defined(NRF5_ICU_TIMER0_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define NRF5_ICU_TIMER0_IRQ_PRIORITY 3 +#endif + +/** + * @brief ICUD2 interrupt priority level setting. + */ +#if !defined(NRF5_ICU_TIMER1_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define NRF5_ICU_TIMER1_IRQ_PRIORITY 3 +#endif + +/** + * @brief ICUD3 interrupt priority level setting. + */ +#if !defined(NRF5_ICU_TIMER2_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define NRF5_ICU_TIMER2_IRQ_PRIORITY 3 +#endif + +/** + * @brief ICUD4 interrupt priority level setting. + */ +#if !defined(NRF5_ICU_TIMER3_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define NRF5_ICU_TIMER3_IRQ_PRIORITY 3 +#endif + +/** + * @brief ICUD5 interrupt priority level setting. + */ +#if !defined(NRF5_ICU_TIMER4_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define NRF5_ICU_TIMER4_IRQ_PRIORITY 3 +#endif + +/** + * @brief Allow driver to use GPIOTE/PPI to capture PAL line + */ +#if !defined(NRF5_ICU_USE_GPIOTE_PPI) +#define NRF5_ICU_USE_GPIOTE_PPI TRUE +#endif + +/** @} */ + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +#if !NRF5_ICU_USE_TIMER0 && !NRF5_ICU_USE_TIMER1 && \ + !NRF5_ICU_USE_TIMER2 && !NRF5_ICU_USE_TIMER3 && \ + !NRF5_ICU_USE_TIMER4 +#error "ICU driver activated but no TIMER peripheral assigned" +#endif + +#if NRF5_ICU_USE_TIMER0 && \ + !OSAL_IRQ_IS_VALID_PRIORITY(NRF5_ICU_TIMER0_IRQ_PRIORITY) +#error "Invalid IRQ priority assigned to TIMER0" +#endif + +#if NRF5_ICU_USE_TIMER1 && \ + !OSAL_IRQ_IS_VALID_PRIORITY(NRF5_ICU_TIMER1_IRQ_PRIORITY) +#error "Invalid IRQ priority assigned to TIMER1" +#endif + +#if NRF5_ICU_USE_TIMER2 && \ + !OSAL_IRQ_IS_VALID_PRIORITY(NRF5_ICU_TIMER2_IRQ_PRIORITY) +#error "Invalid IRQ priority assigned to TIMER2" +#endif + +#if NRF5_ICU_USE_TIMER3 && \ + !OSAL_IRQ_IS_VALID_PRIORITY(NRF5_ICU_TIMER3_IRQ_PRIORITY) +#error "Invalid IRQ priority assigned to TIMER3" +#endif + +#if NRF5_ICU_USE_TIMER4 && \ + !OSAL_IRQ_IS_VALID_PRIORITY(NRF5_ICU_TIMER4_IRQ_PRIORITY) +#error "Invalid IRQ priority assigned to TIMER4" +#endif + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ +/** + * @brief Active level selector. + */ +typedef enum { + ICU_INPUT_DISABLED, /**< Channel disabled . */ + ICU_INPUT_ACTIVE_HIGH, /**< Trigger on rising edge. */ + ICU_INPUT_ACTIVE_LOW, /**< Trigger on falling edge. */ +} icumode_t; + +/** + * @brief ICU channel state. + */ +typedef enum { + ICU_CH_IDLE = 0, /**< Not initialized. */ + ICU_CH_ACTIVE = 1 /**< First front detected. */ +} icuchannelstate_t; + +/** + * @brief ICU frequency type. + */ +typedef uint32_t icufreq_t; + +/** + * @brief ICU channel type. + */ +typedef enum { + ICU_CHANNEL_1 = 0, /**< Use TIMERx channel 0,2 */ + ICU_CHANNEL_2 = 1, /**< Use TIMERx channel 1,3 */ +} icuchannel_t; + +/** + * @brief ICU counter type. + */ +typedef uint32_t icucnt_t; + +/** + * @brief ICU captured width and (or) period. + */ +typedef struct { + /** + * @brief Pulse width. + */ + icucnt_t width; + /** + * @brief Pulse period. + */ + icucnt_t period; +} icuresult_t; + +/** + * @brief ICU Capture Channel Config structure definition. + */ +typedef struct { + /** + * @brief Specifies the channel capture mode. + */ + icumode_t mode; + +#if NRF5_ICU_USE_GPIOTE_PPI || defined(__DOXYGEN__) + /** + * @brief PAL line to capture. + * @note When NRF5_ICU_USE_GPIOTE_PPI is used and channel enabled, + * it wont be possible to access this PAL line using the PAL + * driver. + */ + ioline_t ioline[2]; + + /** + * @brief Unique GPIOTE channel to use. (2 channel) + * @note Only 8 GPIOTE channels are available on nRF52. + */ + uint8_t gpiote_channel[2]; + + /** + * @brief Unique PPI channels to use. (2 channels) + * @note Only 20 PPI channels are available on nRF52 + * (When Softdevice is enabled, only channels 0-7 are available) + */ + uint8_t ppi_channel[2]; +#endif +} ICUChannelConfig; + +/** + * @brief ICU Capture Channel structure definition. + */ +typedef struct { + /** + * @brief Channel state for the internal state machine. + */ + icuchannelstate_t state; + /** + * @brief Cached value for pulse width calculation. + */ + icucnt_t last_active; + /** + * @brief Cached value for period calculation. + */ + icucnt_t last_idle; + /** + * @brief Pointer to Input Capture channel configuration. + */ +// const ICUChannelConfig *config; +} ICUChannel; + +/** + * @brief ICU Config structure definition. + */ +typedef struct { + /** + * @brief Specifies the Timer clock in Hz. + */ + icufreq_t frequency; + /** + * @brief Callback for pulse width measurement. + */ + icucallback_t width_cb; + /** + * @brief Callback for cycle period measurement. + */ + icucallback_t period_cb; + /** + * @brief Callback for timer overflow. + */ + icucallback_t overflow_cb; + /** + * @brief Pointer to each Input Capture channel configuration. + * @note A NULL parameter indicates the channel as unused. + * @note In ICU mode, only Channel 1 OR Channel 2 may be used. + */ + const ICUChannelConfig iccfgp[ICU_CHANNELS]; +} ICUConfig; + +/** + * @brief ICU Driver structure definition + */ +struct ICUDriver { + /** + * @brief NRF52 timer peripheral for Input Capture. + */ + NRF_TIMER_Type *timer; + /** + * @brief Driver state for the internal state machine. + */ + icustate_t state; + /** + * @brief Channels' data structures. + */ + ICUChannel channel[ICU_CHANNELS]; + /** + * @brief Timer base clock. + */ + uint32_t clock; + /** + * @brief Pointer to configuration for the driver. + */ + const ICUConfig *config; + /** + * @brief Period, width last value. + */ + icuresult_t result; +#if defined(ICU_DRIVER_EXT_FIELDS) + ICU_DRIVER_EXT_FIELDS +#endif +}; + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ +/** + * @brief Returns the width of the latest pulse. + * @details The pulse width is defined as number of ticks between the start + * edge and the stop edge. + * + * @param[in] icup pointer to the @p ICUDriver object + * @return The number of ticks. + * + * @notapi + */ +#define icu_lld_get_width(icup) ((uint32_t)((icup)->result.width)) + +/** + * @brief Returns the width of the latest cycle. + * @details The cycle width is defined as number of ticks between a start + * edge and the next start edge. + * + * @param[in] icup pointer to the @p ICUDriver object + * @return The number of ticks. + * + * @notapi + */ +#define icu_lld_get_period(icup) ((uint32_t)((icup)->result.period)) + +/** + * @brief Check on notifications status. + * + * @param[in] icup pointer to the @p ICUDriver object + * @return The notifications status. + * @retval false if notifications are not enabled. + * @retval true if notifications are enabled. + * + * @notapi + */ +#define icu_lld_are_notifications_enabled(icup) ( 1 ) + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ +#if NRF5_ICU_USE_TIMER0 && !defined(__DOXYGEN__) +extern ICUDriver ICUD1; +#endif + +#if NRF5_ICU_USE_TIMER1 && !defined(__DOXYGEN__) +extern ICUDriver ICUD2; +#endif + +#if NRF5_ICU_USE_TIMER2 && !defined(__DOXYGEN__) +extern ICUDriver ICUD3; +#endif + +#if NRF5_ICU_USE_TIMER3 && !defined(__DOXYGEN__) +extern ICUDriver ICUD4; +#endif + +#if NRF5_ICU_USE_TIMER4 && !defined(__DOXYGEN__) +extern ICUDriver ICUD5; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + void icu_lld_init(void); + void icu_lld_start(ICUDriver *icup); + void icu_lld_stop(ICUDriver *icup); + void icu_lld_start_capture(ICUDriver *icup); + bool icu_lld_wait_capture(ICUDriver *icup); + void icu_lld_stop_capture(ICUDriver *icup); + void icu_lld_enable_notifications(ICUDriver *icup); + void icu_lld_disable_notifications(ICUDriver *icup); + void icu_lld_serve_interrupt(ICUDriver *icup); +#ifdef __cplusplus +} +#endif + +#endif /* HAL_USE_ICU */ + +#endif /* HAL_ICU_LLD_H */ |