From 2168085ac76c280880a0c2262a7e63fd0ce7c952 Mon Sep 17 00:00:00 2001 From: andru <7698720+AndruPol@users.noreply.github.com> Date: Tue, 8 Jan 2019 11:22:01 +0300 Subject: added NRF52 pwm, icu, i2c, radio esb drivers --- os/hal/boards/NRF52-E73-2G4M04S/board.c | 81 ++ os/hal/boards/NRF52-E73-2G4M04S/board.h | 164 ++++ os/hal/boards/NRF52-E73-2G4M04S/board.mk | 8 + os/hal/ports/NRF5/LLD/PWMv2/driver.mk | 9 + os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.c | 402 ++++++++++ os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.h | 243 ++++++ os/hal/ports/NRF5/LLD/TIMERv1/driver.mk | 4 + os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.c | 768 ++++++++++++++++++ os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.h | 424 ++++++++++ os/hal/ports/NRF5/LLD/TWIMv1/driver.mk | 9 + os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.c | 420 ++++++++++ os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.h | 210 +++++ os/hal/ports/NRF5/NRF52832/hal_lld.c | 26 +- os/hal/ports/NRF5/NRF52832/hal_lld.h | 32 +- os/hal/ports/NRF5/NRF52832/nrf_delay.h | 335 +++++--- os/hal/ports/NRF5/NRF52832/platform.mk | 3 +- os/various/devices_lib/rf/nrf52_radio.c | 1111 +++++++++++++++++++++++++++ os/various/devices_lib/rf/nrf52_radio.h | 256 ++++++ 18 files changed, 4396 insertions(+), 109 deletions(-) create mode 100644 os/hal/boards/NRF52-E73-2G4M04S/board.c create mode 100644 os/hal/boards/NRF52-E73-2G4M04S/board.h create mode 100644 os/hal/boards/NRF52-E73-2G4M04S/board.mk create mode 100644 os/hal/ports/NRF5/LLD/PWMv2/driver.mk create mode 100644 os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.c create mode 100644 os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.h create mode 100644 os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.c create mode 100644 os/hal/ports/NRF5/LLD/TIMERv1/hal_icu_lld.h create mode 100644 os/hal/ports/NRF5/LLD/TWIMv1/driver.mk create mode 100644 os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.c create mode 100644 os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.h create mode 100644 os/various/devices_lib/rf/nrf52_radio.c create mode 100644 os/various/devices_lib/rf/nrf52_radio.h (limited to 'os') diff --git a/os/hal/boards/NRF52-E73-2G4M04S/board.c b/os/hal/boards/NRF52-E73-2G4M04S/board.c new file mode 100644 index 0000000..adf002d --- /dev/null +++ b/os/hal/boards/NRF52-E73-2G4M04S/board.c @@ -0,0 +1,81 @@ +/* + Copyright (C) 2016 Stéphane D'Alu + + 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. +*/ + +#include "hal.h" + +#if HAL_USE_PAL || defined(__DOXYGEN__) + +/** + * @brief PAL setup. + * @details Digital I/O ports static configuration as defined in @p board.h. + * This variable is used by the HAL when initializing the PAL driver. + */ +const PALConfig pal_default_config = +{ + .pads = { + PAL_MODE_UNCONNECTED, /* P0.0 : XTAL (32MHz) */ + PAL_MODE_UNCONNECTED, /* P0.1 : XTAL (32MHz) */ + PAL_MODE_UNCONNECTED, /* P0.2 */ + PAL_MODE_UNCONNECTED, /* P0.3 */ + PAL_MODE_UNCONNECTED, /* P0.4 */ + PAL_MODE_OUTPUT_PUSHPULL, /* P0.5 : UART_RTS */ + PAL_MODE_OUTPUT_PUSHPULL, /* P0.6 : UART_TX */ + PAL_MODE_INPUT_PULLUP, /* P0.7 : UART_CTS */ + PAL_MODE_INPUT_PULLUP, /* P0.8 : UART_RX */ + PAL_MODE_UNCONNECTED, /* P0.9 */ + PAL_MODE_UNCONNECTED, /* P0.10 */ + PAL_MODE_UNCONNECTED, /* P0.11 */ + PAL_MODE_UNCONNECTED, /* P0.12 */ + PAL_MODE_INPUT, /* P0.13: BTN1 */ + PAL_MODE_INPUT, /* P0.14: BTN2 */ + PAL_MODE_INPUT_PULLUP, /* P0.15: BTN3 */ + PAL_MODE_INPUT_PULLUP, /* P0.16: BTN4 */ + PAL_MODE_OUTPUT_PUSHPULL, /* P0.17: LED1 */ + PAL_MODE_OUTPUT_PUSHPULL, /* P0.18: LED2 */ + PAL_MODE_OUTPUT_PUSHPULL, /* P0.19: LED3 */ + PAL_MODE_OUTPUT_PUSHPULL, /* P0.20: LED4 */ + PAL_MODE_UNCONNECTED, /* P0.21 */ + PAL_MODE_OUTPUT_PUSHPULL, /* P0.22: SPI_SS */ + PAL_MODE_INPUT_PULLUP, /* P0.23: SPI_MISO */ + PAL_MODE_OUTPUT_PUSHPULL, /* P0.24: SPI_MOSI */ + PAL_MODE_OUTPUT_PUSHPULL, /* P0.25: SPI_SCK */ + PAL_MODE_OUTPUT_OPENDRAIN, /* P0.26: SDA */ + PAL_MODE_OUTPUT_OPENDRAIN, /* P0.27: SCL */ + PAL_MODE_UNCONNECTED, /* P0.28 */ + PAL_MODE_UNCONNECTED, /* P0.29 */ + PAL_MODE_UNCONNECTED, /* P0.30 */ + PAL_MODE_UNCONNECTED, /* P0.31 */ + }, +}; +#endif + +/** + * @brief Early initialization code. + * @details This initialization is performed just after reset before BSS and + * DATA segments initialization. + */ +void __early_init(void) +{ +} + +/** + * @brief Late initialization code. + * @note This initialization is performed after BSS and DATA segments + * initialization and before invoking the main() function. + */ +void boardInit(void) +{ +} diff --git a/os/hal/boards/NRF52-E73-2G4M04S/board.h b/os/hal/boards/NRF52-E73-2G4M04S/board.h new file mode 100644 index 0000000..c78909f --- /dev/null +++ b/os/hal/boards/NRF52-E73-2G4M04S/board.h @@ -0,0 +1,164 @@ +/* + Copyright (C) 2016 Stephane D'Alu + + 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 _BOARD_H_ +#define _BOARD_H_ + +/* Board identifier. */ +#define BOARD_NRF52_EBYTE_E73 +#define BOARD_NAME "nRF52 EBYTE E73-2G4M04S" + +/* Board oscillators-related settings. */ +#define NRF5_XTAL_VALUE 32000000 +#define NRF5_HFCLK_SOURCE NRF5_HFCLK_HFXO +#define NRF5_LFCLK_SOURCE NRF5_LFCLK_XTAL + +#define NRF5_HFCLK_HFINT 0 +#define NRF5_HFCLK_HFXO 1 + +#define NRF5_LFCLK_RC 0 +#define NRF5_LFCLK_XTAL 1 +#define NRF5_LFCLK_SYNTH 2 + +/* + * GPIO pins. + */ +/* Defined by board */ +#define BTN1 13U +#define BTN2 14U +#define BTN3 15U +#define BTN4 16U +#define LED1 17U +#define LED2 18U +#define LED3 19U +#define LED4 20U +#define UART_RTS 5U +#define UART_TX 6U +#define UART_CTS 7U +#define UART_RX 8U +#define NFC1 9U +#define NFC2 10U +#define I2C_SCL 27U +#define I2C_SDA 26U + +/* Our definitions */ +#define SPI_SCK 25U +#define SPI_MOSI 24U +#define SPI_MISO 23U +#define SPI_SS 22U + +/* Analog input */ +#define AIN0 2U +#define AIN1 3U +#define AIN2 4U +#define AIN3 5U +#define AIN4 28U +#define AIN5 29U +#define AIN6 30U +#define AIN7 31U +#define AREF0 AIN0 +#define AREF1 AIN1 + +/* + * IO pins assignments. + */ +/* Defined by board */ +#define IOPORT1_BTN1 13U +#define IOPORT1_BTN2 14U +#define IOPORT1_BTN3 15U +#define IOPORT1_BTN4 16U +#define IOPORT1_LED1 17U +#define IOPORT1_LED2 18U +#define IOPORT1_LED3 19U +#define IOPORT1_LED4 20U +#define IOPORT1_UART_RTS 5U +#define IOPORT1_UART_TX 6U +#define IOPORT1_UART_CTS 7U +#define IOPORT1_UART_RX 8U +#define IOPORT1_NFC1 9U +#define IOPORT1_NFC2 10U +#define IOPORT1_I2C_SCL 27U +#define IOPORT1_I2C_SDA 26U +#define IOPORT1_RESET 21U + +/* Our definitions */ +#define IOPORT1_SPI_SCK 25U +#define IOPORT1_SPI_MOSI 24U +#define IOPORT1_SPI_MISO 23U +#define IOPORT1_SPI_SS 22U + +/* Analog inpupt */ +#define IOPORT1_AIN0 2U +#define IOPORT1_AIN1 3U +#define IOPORT1_AIN2 4U +#define IOPORT1_AIN3 5U +#define IOPORT1_AIN4 28U +#define IOPORT1_AIN5 29U +#define IOPORT1_AIN6 30U +#define IOPORT1_AIN7 31U +#define IOPORT1_AREF0 IOPORT1_AIN0 +#define IOPORT1_AREF1 IOPORT1_AIN1 + +/* + * IO lines assignments. + */ +/* Board defined */ +#define LINE_BTN1 PAL_LINE(IOPORT1, IOPORT1_BTN1) +#define LINE_BTN2 PAL_LINE(IOPORT1, IOPORT1_BTN2) +#define LINE_BTN3 PAL_LINE(IOPORT1, IOPORT1_BTN3) +#define LINE_BTN4 PAL_LINE(IOPORT1, IOPORT1_BTN4) +#define LINE_LED1 PAL_LINE(IOPORT1, IOPORT1_LED1) +#define LINE_LED2 PAL_LINE(IOPORT1, IOPORT1_LED2) +#define LINE_LED3 PAL_LINE(IOPORT1, IOPORT1_LED3) +#define LINE_LED4 PAL_LINE(IOPORT1, IOPORT1_LED4) +#define LINE_UART_RTS PAL_LINE(IOPORT1, IOPORT1_UART_RTS) +#define LINE_UART_TX PAL_LINE(IOPORT1, IOPORT1_UART_TX) +#define LINE_UART_CTS PAL_LINE(IOPORT1, IOPORT1_UART_CTS) +#define LINE_UART_RX PAL_LINE(IOPORT1, IOPORT1_UART_RX) +#define LINE_NFC1 PAL_LINE(IOPORT1, IOPORT1_NFC1) +#define LINE_NFC2 PAL_LINE(IOPORT1, IOPORT1_NFC2) +#define LINE_I2C_SCL PAL_LINE(IOPORT1, IOPORT1_I2C_SCL) +#define LINE_I2C_SDA PAL_LINE(IOPORT1, IOPORT1_I2C_SDA) + +/* Our definitions */ +#define LINE_SPI_SCK PAL_LINE(IOPORT1, IOPORT1_SPI_SCK) +#define LINE_SPI_MOSI PAL_LINE(IOPORT1, IOPORT1_SPI_MOSI) +#define LINE_SPI_MISO PAL_LINE(IOPORT1, IOPORT1_SPI_MISO) +#define LINE_SPI_SS PAL_LINE(IOPORT1, IOPORT1_SPI_SS) + +/* Analog line */ +#define LINE_AIN0 PAL_LINE(IOPORT1, IOPORT1_AIN0) +#define LINE_AIN1 PAL_LINE(IOPORT1, IOPORT1_AIN1) +#define LINE_AIN2 PAL_LINE(IOPORT1, IOPORT1_AIN2) +#define LINE_AIN3 PAL_LINE(IOPORT1, IOPORT1_AIN3) +#define LINE_AIN4 PAL_LINE(IOPORT1, IOPORT1_AIN4) +#define LINE_AIN5 PAL_LINE(IOPORT1, IOPORT1_AIN5) +#define LINE_AIN6 PAL_LINE(IOPORT1, IOPORT1_AIN6) +#define LINE_AIN7 PAL_LINE(IOPORT1, IOPORT1_AIN7) +#define LINE_AREF0 PAL_LINE(IOPORT1, IOPORT1_AREF0) +#define LINE_AREF1 PAL_LINE(IOPORT1, IOPORT1_AREF1) + +#if !defined(_FROM_ASM_) +#ifdef __cplusplus +extern "C" { +#endif + void boardInit(void); +#ifdef __cplusplus +} +#endif +#endif /* _FROM_ASM_ */ + +#endif /* _BOARD_H_ */ diff --git a/os/hal/boards/NRF52-E73-2G4M04S/board.mk b/os/hal/boards/NRF52-E73-2G4M04S/board.mk new file mode 100644 index 0000000..8876668 --- /dev/null +++ b/os/hal/boards/NRF52-E73-2G4M04S/board.mk @@ -0,0 +1,8 @@ +# List of all the board related files. +BOARDSRC = ${CHIBIOS_CONTRIB}/os/hal/boards/NRF52-E73-2G4M04S/board.c + +# Required include directories +BOARDINC = ${CHIBIOS_CONTRIB}/os/hal/boards/NRF52-E73-2G4M04S + +ALLCSRC += $(BOARDSRC) +ALLINC += $(BOARDINC) diff --git a/os/hal/ports/NRF5/LLD/PWMv2/driver.mk b/os/hal/ports/NRF5/LLD/PWMv2/driver.mk new file mode 100644 index 0000000..ce247df --- /dev/null +++ b/os/hal/ports/NRF5/LLD/PWMv2/driver.mk @@ -0,0 +1,9 @@ +ifeq ($(USE_SMART_BUILD),yes) +ifneq ($(findstring HAL_USE_PWM TRUE,$(HALCONF)),) +PLATFORMSRC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.c +endif +else +PLATFORMSRC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.c +endif + +PLATFORMINC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/PWMv2 diff --git a/os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.c b/os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.c new file mode 100644 index 0000000..7162b1b --- /dev/null +++ b/os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.c @@ -0,0 +1,402 @@ +/* + ChibiOS/HAL - Copyright (C) 2018 Andru + + 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. +*/ + +/** + * @file hal_pwm_lld.c + * @brief NRF52 PWM subsystem low level driver source. + * + * @addtogroup PWM + * @{ + */ + +#include "hal.h" + +#if HAL_USE_PWM || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ +uint16_t pwm_seq[PWM_CHANNELS]; + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/** + * @brief PWMD1 driver identifier. + * @note The driver PWMD1 enabled. + */ +#if NRF5_PWM_USE_PWM0 || defined(__DOXYGEN__) +PWMDriver PWMD1; +#endif + +/** + * @brief PWMD2 driver identifier. + * @note The driver PWMD2 enabled. + */ +#if NRF5_PWM_USE_PWM1 || defined(__DOXYGEN__) +PWMDriver PWMD2; +#endif + +/** + * @brief PWMD3 driver identifier. + * @note The driver PWMD3 enabled. + */ +#if NRF5_PWM_USE_PWM2 || defined(__DOXYGEN__) +PWMDriver PWMD3; +#endif + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +static void pwm_lld_serve_interrupt(PWMDriver *pwmp) { + /* Deal with PWM period + */ + if (pwmp->config->callback == NULL) { + return; + } + + if ((pwmp->pwm->INTEN & PWM_INTEN_PWMPERIODEND_Msk) && (pwmp->pwm->EVENTS_PWMPERIODEND)) { + pwmp->config->callback(pwmp); + pwmp->pwm->EVENTS_PWMPERIODEND = 0; + (void)pwmp->pwm->EVENTS_PWMPERIODEND; + } +} + +/*===========================================================================*/ +/* Driver interrupt handlers. */ +/*===========================================================================*/ + +#if NRF5_PWM_USE_PWM0 +/** + * @brief PWM0 interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(VectorB0) { + OSAL_IRQ_PROLOGUE(); + pwm_lld_serve_interrupt(&PWMD1); + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF5_PWM_USE_PWM0 */ + +#if NRF5_PWM_USE_PWM1 +/** + * @brief PWM1 interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(VectorC4) { + OSAL_IRQ_PROLOGUE(); + pwm_lld_serve_interrupt(&PWMD2); + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF5_PWM_USE_PWM1 */ + +#if NRF5_PWM_USE_PWM2 +/** + * @brief PWM2 interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(VectorC8) { + OSAL_IRQ_PROLOGUE(); + pwm_lld_serve_interrupt(&PWMD3); + OSAL_IRQ_EPILOGUE(); +} +#endif /* NRF5_PWM_USE_PWM2 */ + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Low level PWM driver initialization. + * + * @notapi + */ +void pwm_lld_init(void) { + +#if NRF5_PWM_USE_PWM0 + pwmObjectInit(&PWMD1); + PWMD1.channels = PWM_CHANNELS; + PWMD1.pwm = NRF_PWM0; +#endif + +#if NRF5_PWM_USE_PWM1 + pwmObjectInit(&PWMD2); + PWMD2.channels = PWM_CHANNELS; + PWMD2.pwm = NRF_PWM1; +#endif + +#if NRF5_PWM_USE_PWM2 + pwmObjectInit(&PWMD3); + PWMD3.channels = PWM_CHANNELS; + PWMD3.pwm = NRF_PWM2; +#endif +} + +/** + * @brief Configures and activates the PWM peripheral. + * @note Starting a driver that is already in the @p PWM_READY state + * disables all the active channels. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @notapi + */ +void pwm_lld_start(PWMDriver *pwmp) { + /* Prescaler value calculation: ftimer = 16MHz / 2^PRESCALER */ + /* Prescaler value as a power of 2, must be 0..7 */ + uint8_t i, psc_value; + for (psc_value = 0; psc_value < 8; psc_value++) + if (pwmp->config->frequency == (uint32_t)(16000000 >> psc_value)) + break; + + /* Prescaler value must be between 0..7, and a power of two. */ + osalDbgAssert(psc_value <= 7, "invalid frequency"); + + /* Set PWM output lines */ + for (i=0; iconfig->channels[i]; + uint32_t gpio_pin = PAL_PAD(cfg_channel->ioline); + if (cfg_channel->mode == PWM_OUTPUT_DISABLED) { + gpio_pin = PAL_NOLINE; + } + + pwmp->pwm->PSEL.OUT[i] = gpio_pin << PWM_PSEL_OUT_PIN_Pos; + } + + /* Enable PWM */ + pwmp->pwm->ENABLE = PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos; + + /* Set mode */ + pwmp->pwm->MODE = PWM_MODE_UPDOWN_Up & PWM_MODE_UPDOWN_Msk; + + /* Set prescaler */ + pwmp->pwm->PRESCALER = psc_value & PWM_PRESCALER_PRESCALER_Msk; + + /* Set period */ + pwmp->pwm->COUNTERTOP = pwmp->period & PWM_COUNTERTOP_COUNTERTOP_Msk; + + pwmp->pwm->LOOP = PWM_LOOP_CNT_Disabled & PWM_LOOP_CNT_Msk; + pwmp->pwm->DECODER = (PWM_DECODER_LOAD_Individual << PWM_DECODER_LOAD_Pos) | + (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos); + + pwmp->pwm->SEQ[0].PTR = ((uint32_t)(pwm_seq) << PWM_SEQ_PTR_PTR_Pos); + pwmp->pwm->SEQ[0].CNT = ((sizeof(pwm_seq) / sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos); + pwmp->pwm->SEQ[0].REFRESH = 0; + pwmp->pwm->SEQ[0].ENDDELAY = 0; + + pwmp->pwm->SEQ[1].PTR = ((uint32_t)(pwm_seq) << PWM_SEQ_PTR_PTR_Pos); + pwmp->pwm->SEQ[1].CNT = ((sizeof(pwm_seq) / sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos); + pwmp->pwm->SEQ[1].REFRESH = 0; + pwmp->pwm->SEQ[1].ENDDELAY = 0; + + /* With clear shortcuts for period */ + pwmp->pwm->SHORTS = 0; + + /* Disable and reset interrupts */ + pwmp->pwm->INTEN = 0; +} + +/** + * @brief Deactivates the PWM peripheral. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @notapi + */ +void pwm_lld_stop(PWMDriver *pwmp) { +#if NRF5_PWM_USE_PWM0 + if (&PWMD1 == pwmp) { + nvicDisableVector(PWM0_IRQn); + } +#endif + +#if NRF5_PWM_USE_PWM1 + if (&PWMD2 == pwmp) { + nvicDisableVector(PWM1_IRQn); + } +#endif + +#if NRF5_PWM_USE_PWM2 + if (&PWMD3 == pwmp) { + nvicDisableVector(PWM2_IRQn); + } +#endif + + /* Stop PWM generation */ + pwmp->pwm->TASKS_STOP = 1; + + /* Disable PWM */ + pwmp->pwm->ENABLE = (PWM_ENABLE_ENABLE_Disabled << PWM_ENABLE_ENABLE_Pos); +} + +/** + * @brief Enables a PWM channel. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is active using the specified configuration. + * @note The function has effect at the next cycle start. + * @note Channel notification is not enabled. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier (0...channels-1) + * @param[in] width PWM pulse width as clock pulses number + * + * @notapi + */ +void pwm_lld_enable_channel(PWMDriver *pwmp, + pwmchannel_t channel, + pwmcnt_t width) { + const PWMChannelConfig *cfg_channel = &pwmp->config->channels[channel]; + + /* Deal with corner case: 0% and 100% */ + if ((width <= 0) || (width >= pwmp->period)) { + pwm_seq[channel] = pwmp->period & PWM_COUNTERTOP_COUNTERTOP_Msk; + if (cfg_channel->mode == PWM_OUTPUT_ACTIVE_LOW) pwm_seq[channel] |= 0x8000; + /* Really doing PWM */ + } else { + pwm_seq[channel] = width & PWM_COUNTERTOP_COUNTERTOP_Msk; + if (cfg_channel->mode == PWM_OUTPUT_ACTIVE_HIGH) pwm_seq[channel] |= 0x8000; + } + + pwmp->pwm->EVENTS_STOPPED = 0; + (void)pwmp->pwm->EVENTS_STOPPED; + + pwmp->pwm->TASKS_SEQSTART[0] = 1; +} + +/** + * @brief Disables a PWM channel and its notification. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is disabled and its output line returned to the + * idle state. + * @note The function has effect at the next cycle start. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier (0...channels-1) + * + * @notapi + */ +void pwm_lld_disable_channel(PWMDriver *pwmp, pwmchannel_t channel) { + const PWMChannelConfig *cfg_channel = &pwmp->config->channels[channel]; + + pwm_seq[channel] = pwmp->period & PWM_COUNTERTOP_COUNTERTOP_Msk; + if (cfg_channel->mode == PWM_OUTPUT_ACTIVE_LOW) pwm_seq[channel] |= 0x8000; + + pwmp->pwm->EVENTS_STOPPED = 0; + (void)pwmp->pwm->EVENTS_STOPPED; + + pwmp->pwm->TASKS_SEQSTART[0] = 1; +} + +/** + * @brief Enables the periodic activation edge notification. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @note If the notification is already enabled then the call has no effect. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @notapi + */ +void pwm_lld_enable_periodic_notification(PWMDriver *pwmp) { + + /* Events clear */ + pwmp->pwm->EVENTS_LOOPSDONE = 0; + pwmp->pwm->EVENTS_SEQEND[0] = 0; + pwmp->pwm->EVENTS_SEQEND[1] = 0; + pwmp->pwm->EVENTS_STOPPED = 0; +#if CORTEX_MODEL >= 4 + (void)pwmp->pwm->EVENTS_LOOPSDONE; + (void)pwmp->pwm->EVENTS_SEQEND[0]; + (void)pwmp->pwm->EVENTS_SEQEND[1]; + (void)pwmp->pwm->EVENTS_STOPPED; +#endif + + pwmp->pwm->INTENSET = PWM_INTENSET_PWMPERIODEND_Msk; + + /* Enable interrupt */ +#if NRF5_PWM_USE_PWM0 + if (&PWMD1 == pwmp) { + nvicEnableVector(PWM0_IRQn, NRF5_PWM_PWM0_PRIORITY); + } +#endif + +#if NRF5_PWM_USE_PWM1 + if (&PWMD2 == pwmp) { + nvicEnableVector(PWM1_IRQn, NRF5_PWM_PWM1_PRIORITY); + } +#endif + +#if NRF5_PWM_USE_PWM2 + if (&PWMD3 == pwmp) { + nvicEnableVector(PWM2_IRQn, NRF5_PWM_PWM2_PRIORITY); + } +#endif +} + +/** + * @brief Disables the periodic activation edge notification. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @note If the notification is already disabled then the call has no effect. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @notapi + */ +void pwm_lld_disable_periodic_notification(PWMDriver *pwmp) { + pwmp->pwm->INTENCLR = PWM_INTENCLR_PWMPERIODEND_Msk; +} + +/** + * @brief Enables a channel de-activation edge notification. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @pre The channel must have been activated using @p pwmEnableChannel(). + * @note If the notification is already enabled then the call has no effect. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier (0...channels-1) + * + * @notapi + */ +void pwm_lld_enable_channel_notification(PWMDriver *pwmp, + pwmchannel_t channel) { +} + +/** + * @brief Disables a channel de-activation edge notification. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @pre The channel must have been activated using @p pwmEnableChannel(). + * @note If the notification is already disabled then the call has no effect. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier (0...channels-1) + * + * @notapi + */ +void pwm_lld_disable_channel_notification(PWMDriver *pwmp, + pwmchannel_t channel) { +} + +#endif /* HAL_USE_PWM */ + +/** @} */ diff --git a/os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.h b/os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.h new file mode 100644 index 0000000..647b95e --- /dev/null +++ b/os/hal/ports/NRF5/LLD/PWMv2/hal_pwm_lld.h @@ -0,0 +1,243 @@ +/* + ChibiOS/HAL - Copyright (C) 2018 Andru + + 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. +*/ + +/** + * @file hal_pwm_lld.h + * @brief NRF52 PWM subsystem low level driver header. + * + * @addtogroup PWM + * @{ + */ + +#ifndef HAL_PWM_LLD_H_ +#define HAL_PWM_LLD_H_ + +#if HAL_USE_PWM || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/** + * @brief Number of PWM channels per PWM driver. + */ +#define PWM_CHANNELS 4 + +typedef enum { + PWM_FREQUENCY_16MHZ = 16000000, /** @brief 16MHz */ + PWM_FREQUENCY_8MHZ = 8000000, /** @brief 8MHz */ + PWM_FREQUENCY_4MHZ = 4000000, /** @brief 4MHz */ + PWM_FREQUENCY_2MHZ = 2000000, /** @brief 2MHz */ + PWM_FREQUENCY_1MHZ = 1000000, /** @brief 1MHz */ + PWM_FREQUENCY_500KHZ = 500000, /** @brief 500kHz */ + PWM_FREQUENCY_250KHZ = 250000, /** @brief 250kHz */ + PWM_FREQUENCY_125KHZ = 125000, /** @brief 125kHz */ +} pwm_frequency_t; + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @name Configuration options + * @{ + */ + +/** @} */ + +/*===========================================================================*/ +/* Configuration checks. */ +/*===========================================================================*/ + + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/** + * @brief Type of a PWM mode. + */ +typedef uint32_t pwmmode_t; + +/** + * @brief Type of a PWM channel. + */ +typedef uint8_t pwmchannel_t; + +/** + * @brief Type of a channels mask. + */ +typedef uint32_t pwmchnmsk_t; + +/** + * @brief Type of a PWM counter. + */ +typedef uint16_t pwmcnt_t; + +/** + * @brief Type of a PWM driver channel configuration structure. + */ +typedef struct { + /** + * @brief Channel active logic level. + */ + pwmmode_t mode; + + /** + * @brief Channel callback pointer. + * @note This callback is invoked on the channel compare event. If set to + * @p NULL then the callback is disabled. + */ + pwmcallback_t callback; + /* End of the mandatory fields.*/ + + /** + * @brief PAL line to toggle. + * @note Only used if mode is PWM_OUTPUT_HIGH or PWM_OUTPUT_LOW. + * @note When channel enabled it wont be possible to access this PAL line using the PAL + * driver. + */ + ioline_t ioline; + +} PWMChannelConfig; + +/** + * @brief Type of a PWM driver configuration structure. + */ +typedef struct { + /** + * @brief Timer clock in Hz. + * @note The low level can use assertions in order to catch invalid + * frequency specifications. + */ + pwm_frequency_t frequency; + /** + * @brief PWM period in ticks. + * @note The low level can use assertions in order to catch invalid + * period specifications. + */ + pwmcnt_t period; + /** + * @brief Periodic callback pointer. + * @note This callback is invoked on PWM counter reset. If set to + * @p NULL then the callback is disabled. + */ + pwmcallback_t callback; + /** + * @brief Channels configurations. + */ + PWMChannelConfig channels[PWM_CHANNELS]; + /* End of the mandatory fields.*/ +} PWMConfig; + +/** + * @brief Structure representing a PWM driver. + */ +struct PWMDriver { + /** + * @brief Driver state. + */ + pwmstate_t state; + /** + * @brief Current driver configuration data. + */ + const PWMConfig *config; + /** + * @brief Current PWM period in ticks. + */ + pwmcnt_t period; + /** + * @brief Mask of the enabled channels. + */ + pwmchnmsk_t enabled; + /** + * @brief Number of channels in this instance. + */ + pwmchannel_t channels; +#if defined(PWM_DRIVER_EXT_FIELDS) + PWM_DRIVER_EXT_FIELDS +#endif + /* End of the mandatory fields.*/ + /** + * @brief Pointer to the PWM registers block. + */ + NRF_PWM_Type *pwm; +}; + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/** + * @brief Changes the period the PWM peripheral. + * @details This function changes the period of a PWM unit that has already + * been activated using @p pwmStart(). + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The PWM unit period is changed to the new value. + * @note The function has effect at the next cycle start. + * @note If a period is specified that is shorter than the pulse width + * programmed in one of the channels then the behavior is not + * guaranteed. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] period new cycle time in ticks + * + * @notapi + */ +#define pwm_lld_change_period(pwmp, period) \ + do { \ + (pwmp)->pwm->COUNTERTOP = period & PWM_COUNTERTOP_COUNTERTOP_Msk; \ + } while(0) + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#if NRF5_PWM_USE_PWM0 || defined(__DOXYGEN__) +extern PWMDriver PWMD1; +#endif +#if NRF5_PWM_USE_PWM1 || defined(__DOXYGEN__) +extern PWMDriver PWMD2; +#endif +#if NRF5_PWM_USE_PWM2 || defined(__DOXYGEN__) +extern PWMDriver PWMD3; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + void pwm_lld_init(void); + void pwm_lld_start(PWMDriver *pwmp); + void pwm_lld_stop(PWMDriver *pwmp); + void pwm_lld_enable_channel(PWMDriver *pwmp, + pwmchannel_t channel, + pwmcnt_t width); + void pwm_lld_disable_channel(PWMDriver *pwmp, pwmchannel_t channel); + void pwm_lld_enable_periodic_notification(PWMDriver *pwmp); + void pwm_lld_disable_periodic_notification(PWMDriver *pwmp); + void pwm_lld_enable_channel_notification(PWMDriver *pwmp, + pwmchannel_t channel); + void pwm_lld_disable_channel_notification(PWMDriver *pwmp, + pwmchannel_t channel); +#ifdef __cplusplus +} +#endif + +#endif /* HAL_USE_PWM */ + +#endif /* HAL_PWM_LLD_H_ */ + +/** @} */ 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; chconfig->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; chtimer->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; channelconfig->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; chchannel[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; channelconfig->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; channelconfig->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; channelconfig->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; channelconfig->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; channelconfig->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 */ diff --git a/os/hal/ports/NRF5/LLD/TWIMv1/driver.mk b/os/hal/ports/NRF5/LLD/TWIMv1/driver.mk new file mode 100644 index 0000000..43ad8e3 --- /dev/null +++ b/os/hal/ports/NRF5/LLD/TWIMv1/driver.mk @@ -0,0 +1,9 @@ +ifeq ($(USE_SMART_BUILD),yes) +ifneq ($(findstring HAL_USE_I2C TRUE,$(HALCONF)),) +PLATFORMSRC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.c +endif +else +PLATFORMSRC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.c +endif + +PLATFORMINC_CONTRIB += ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TWIMv1 diff --git a/os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.c b/os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.c new file mode 100644 index 0000000..957c0be --- /dev/null +++ b/os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.c @@ -0,0 +1,420 @@ +/* + Copyright (C) 2018 andru + + 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. +*/ + +/** + * @file NRF5/NRF52832/hal_i2c_lld.c + * @brief NRF52 I2C subsystem low level driver source. + * + * @addtogroup I2C + * @{ + */ + +#include "osal.h" +#include "hal.h" +#include "nrf_delay.h" + +#if HAL_USE_I2C || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +/* These macros are needed to see if the slave is stuck and we as master send dummy clock cycles to end its wait */ +#define I2C_HIGH(p) do { IOPORT1->OUTSET = (1UL << (p)); } while(0) /*!< Pulls I2C line high */ +#define I2C_LOW(p) do { IOPORT1->OUTCLR = (1UL << (p)); } while(0) /*!< Pulls I2C line low */ +#define I2C_INPUT(p) do { IOPORT1->DIRCLR = (1UL << (p)); } while(0) /*!< Configures I2C pin as input */ +#define I2C_OUTPUT(p) do { IOPORT1->DIRSET = (1UL << (p)); } while(0) /*!< Configures I2C pin as output */ + +#define I2C_PIN_CNF \ + ((GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) \ + | (GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) \ + | (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) \ + | (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) \ + | (GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos)) + +#define I2C_PIN_CNF_CLR \ + ((GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) \ + | (GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) \ + | (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) \ + | (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) \ + | (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos)) + +#if NRF5_I2C_USE_I2C0 +#define I2C0_IRQ_NUM SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn +#define I2C0_IRQ_PRI NRF5_I2C_I2C0_IRQ_PRIORITY +#endif +#if NRF5_I2C_USE_I2C1 +#define I2C1_IRQ_NUM SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQn +#define I2C1_IRQ_PRI NRF5_I2C_I2C1_IRQ_PRIORITY +#endif + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/** + * @brief I2C0 driver identifier. + */ +#if NRF5_I2C_USE_I2C0 || defined(__DOXYGEN__) +I2CDriver I2CD1; +#endif + +/** + * @brief I2C1 driver identifier. + */ +#if NRF5_I2C_USE_I2C1 || defined(__DOXYGEN__) +I2CDriver I2CD2; +#endif + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ +/** + * @brief Function for detecting stuck slaves (SDA = 0 and SCL = 1) and tries to clear the bus. + * + * @return + * @retval false Bus is stuck. + * @retval true Bus is clear. + */ +static void i2c_clear_bus(I2CDriver *i2cp) { + const I2CConfig *cfg = i2cp->config; + uint8_t i; + + IOPORT1->PIN_CNF[cfg->scl_pad] = I2C_PIN_CNF; + IOPORT1->PIN_CNF[cfg->sda_pad] = I2C_PIN_CNF; + + I2C_HIGH(cfg->sda_pad); + I2C_HIGH(cfg->scl_pad); + + IOPORT1->PIN_CNF[cfg->scl_pad] = I2C_PIN_CNF_CLR; + IOPORT1->PIN_CNF[cfg->sda_pad] = I2C_PIN_CNF_CLR; + + nrf_delay_us(4); + + for(i = 0; i < 9; i++) { + if (palReadPad(IOPORT1, cfg->sda_pad)) { + if(i > 0) + break; + else + return; + } + + I2C_LOW(cfg->scl_pad); + nrf_delay_us(4); + I2C_HIGH(cfg->scl_pad); + nrf_delay_us(4); + } + + I2C_LOW(cfg->sda_pad); + nrf_delay_us(4); + I2C_HIGH(cfg->sda_pad); +} + +#if defined(__GNUC__) +__attribute__((noinline)) +#endif +/** + * @brief Common IRQ handler. + * @note Tries hard to clear all the pending interrupt sources, we don't + * want to go through the whole ISR and have another interrupt soon + * after. + * + * @param[in] i2cp pointer to an I2CDriver + */ +static void i2c_serve_interrupt(I2CDriver *i2cp) { + NRF_TWIM_Type *i2c = i2cp->i2c; + + if (i2c->EVENTS_ERROR) { + uint32_t err = i2c->ERRORSRC; + i2c->EVENTS_ERROR = 0; + (void)i2c->EVENTS_ERROR; + + if (err & 0x01) // nRF52832 Product Specification v1.3 p.314 TWIM_ERRORSRC OVERRUN bit = 0x01 + i2cp->errors |= I2C_OVERRUN; + if (err & (TWIM_ERRORSRC_ANACK_Msk | TWIM_ERRORSRC_DNACK_Msk)) + i2cp->errors |= I2C_ACK_FAILURE; + + i2c->TASKS_STOP = 1; + + _i2c_wakeup_error_isr(i2cp); + } else if(i2c->EVENTS_STOPPED) { + + i2c->EVENTS_STOPPED = 0; + (void)i2c->EVENTS_STOPPED; + + _i2c_wakeup_isr(i2cp); + } +} + +/*===========================================================================*/ +/* Driver interrupt handlers. */ +/*===========================================================================*/ + +#if NRF5_I2C_USE_I2C0 || defined(__DOXYGEN__) + +OSAL_IRQ_HANDLER(Vector4C) { + + OSAL_IRQ_PROLOGUE(); + i2c_serve_interrupt(&I2CD1); + OSAL_IRQ_EPILOGUE(); +} + +#endif + +#if NRF5_I2C_USE_I2C1 || defined(__DOXYGEN__) + +OSAL_IRQ_HANDLER(Vector50) { + + OSAL_IRQ_PROLOGUE(); + i2c_serve_interrupt(&I2CD2); + OSAL_IRQ_EPILOGUE(); +} + +#endif + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Low level I2C driver initialization. + * + * @notapi + */ +void i2c_lld_init(void) { + +#if NRF5_I2C_USE_I2C0 + i2cObjectInit(&I2CD1); + I2CD1.thread = NULL; + I2CD1.i2c = NRF_TWIM0; +#endif + +#if NRF5_I2C_USE_I2C1 + i2cObjectInit(&I2CD2); + I2CD2.thread = NULL; + I2CD2.i2c = NRF_TWIM1; +#endif + +} + +/** + * @brief Configures and activates the I2C peripheral. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * + * @notapi + */ +void i2c_lld_start(I2CDriver *i2cp) { + NRF_TWIM_Type *i2c = i2cp->i2c; + + const I2CConfig *cfg = i2cp->config; + + if (i2cp->state != I2C_STOP) + return; + + osalDbgAssert(i2c->ENABLE == 0, "already in use"); + + i2c_clear_bus(i2cp); + + IOPORT1->PIN_CNF[cfg->scl_pad] = I2C_PIN_CNF; + IOPORT1->PIN_CNF[cfg->sda_pad] = I2C_PIN_CNF; + + i2c->SHORTS = 0; + + i2c->EVENTS_STOPPED = 0; + i2c->EVENTS_ERROR = 0; + (void)i2c->EVENTS_STOPPED; + (void)i2c->EVENTS_ERROR; + + i2c->PSEL.SCL = cfg->scl_pad; + i2c->PSEL.SDA = cfg->sda_pad; + + switch (cfg->clock) { + case 100000: + i2c->FREQUENCY = TWIM_FREQUENCY_FREQUENCY_K100 << TWIM_FREQUENCY_FREQUENCY_Pos; + break; + case 250000: + i2c->FREQUENCY = TWIM_FREQUENCY_FREQUENCY_K250 << TWIM_FREQUENCY_FREQUENCY_Pos; + break; + case 400000: + i2c->FREQUENCY = TWIM_FREQUENCY_FREQUENCY_K400 << TWIM_FREQUENCY_FREQUENCY_Pos; + break; + default: + osalDbgAssert(0, "invalid I2C frequency"); + break; + }; + +#if NRF5_I2C_USE_I2C0 + nvicEnableVector(I2C0_IRQ_NUM, I2C0_IRQ_PRI); +#endif + +#if NRF5_I2C_USE_I2C1 + nvicEnableVector(I2C1_IRQ_NUM, I2C1_IRQ_PRI); +#endif + + i2c->INTENSET = TWIM_INTENSET_STOPPED_Msk | TWIM_INTENSET_ERROR_Msk; + + i2c->ENABLE = TWIM_ENABLE_ENABLE_Enabled << TWIM_ENABLE_ENABLE_Pos; +} + +/** + * @brief Deactivates the I2C peripheral. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * + * @notapi + */ +void i2c_lld_stop(I2CDriver *i2cp) { + NRF_TWIM_Type *i2c = i2cp->i2c; + const I2CConfig *cfg = i2cp->config; + + if (i2cp->state != I2C_STOP) { + i2c->SHORTS = 0; + + i2c->INTENCLR = TWIM_INTENCLR_STOPPED_Msk | TWIM_INTENCLR_ERROR_Msk; + +#if NRF5_I2C_USE_I2C0 + nvicDisableVector(I2C0_IRQ_NUM); +#endif + +#if NRF5_I2C_USE_I2C1 + nvicDisableVector(I2C1_IRQ_NUM); +#endif + + i2c->ENABLE = TWIM_ENABLE_ENABLE_Disabled << TWIM_ENABLE_ENABLE_Pos; + + IOPORT1->PIN_CNF[cfg->scl_pad] = I2C_PIN_CNF_CLR; + IOPORT1->PIN_CNF[cfg->sda_pad] = I2C_PIN_CNF_CLR; + } +} + +static inline msg_t _i2c_txrx_timeout(I2CDriver *i2cp, i2caddr_t addr, + const uint8_t *txbuf, size_t txbytes, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout) { + + NRF_TWIM_Type *i2c = i2cp->i2c; + msg_t msg; + + i2cp->errors = I2C_NO_ERROR; + i2cp->addr = addr; + + uint8_t tx_bytes = txbytes; + uint8_t rx_bytes = rxbytes; + + i2cp->i2c->SHORTS = 0; + i2c->ADDRESS = addr; + + if (tx_bytes && rx_bytes) { + i2c->TXD.PTR = (uint32_t)txbuf; + i2c->TXD.MAXCNT = tx_bytes; + i2c->TXD.LIST = TWIM_TXD_LIST_LIST_ArrayList << TWIM_TXD_LIST_LIST_Pos; + i2c->RXD.PTR = (uint32_t)rxbuf; + i2c->RXD.MAXCNT = rx_bytes; + i2c->RXD.LIST = TWIM_RXD_LIST_LIST_ArrayList << TWIM_RXD_LIST_LIST_Pos; + i2cp->i2c->SHORTS = TWIM_SHORTS_LASTTX_STARTRX_Enabled << TWIM_SHORTS_LASTTX_STARTRX_Pos | + TWIM_SHORTS_LASTRX_STOP_Enabled << TWIM_SHORTS_LASTRX_STOP_Pos; + i2c->TASKS_STARTTX = 1; + } else if (tx_bytes && !rx_bytes) { + i2c->TXD.PTR = (uint32_t)txbuf; + i2c->TXD.MAXCNT = tx_bytes; + i2c->TXD.LIST = TWIM_TXD_LIST_LIST_ArrayList << TWIM_TXD_LIST_LIST_Pos; + i2cp->i2c->SHORTS = TWIM_SHORTS_LASTTX_STOP_Enabled << TWIM_SHORTS_LASTTX_STOP_Pos; + i2c->TASKS_STARTTX = 1; + } else if (!tx_bytes && rx_bytes) { + i2c->RXD.PTR = (uint32_t)rxbuf; + i2c->RXD.MAXCNT = rx_bytes; + i2c->RXD.LIST = TWIM_RXD_LIST_LIST_ArrayList << TWIM_RXD_LIST_LIST_Pos; + i2cp->i2c->SHORTS = TWIM_SHORTS_LASTRX_STOP_Enabled << TWIM_SHORTS_LASTRX_STOP_Pos; + i2c->TASKS_STARTRX = 1; + } else { + osalDbgAssert(0, "no bytes to transfer"); + } + + msg = osalThreadSuspendTimeoutS(&i2cp->thread, timeout); + + if (msg == MSG_TIMEOUT) + i2c->TASKS_STOP = 1; + + return msg; +} + +/** + * @brief Receives data via the I2C bus as master. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * @param[in] addr slave device address + * @param[out] rxbuf pointer to the receive buffer + * @param[in] rxbytes number of bytes to be received + * @param[in] timeout the number of ticks before the operation timeouts, + * the following special values are allowed: + * - @a TIME_INFINITE no timeout. + * . + * @return The operation status. + * @retval MSG_OK if the function succeeded. + * @retval MSG_RESET if one or more I2C errors occurred, the errors can + * be retrieved using @p i2cGetErrors(). + * @retval MSG_TIMEOUT if a timeout occurred before operation end. After a + * timeout the driver must be stopped and restarted + * because the bus is in an uncertain state. + * + * @notapi + */ +msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout) { + + return _i2c_txrx_timeout(i2cp, addr, NULL, 0, rxbuf, rxbytes, timeout); +} + +/** + * @brief Transmits data via the I2C bus as master. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * @param[in] addr slave device address + * @param[in] txbuf pointer to the transmit buffer + * @param[in] txbytes number of bytes to be transmitted + * @param[out] rxbuf pointer to the receive buffer + * @param[in] rxbytes number of bytes to be received + * @param[in] timeout the number of ticks before the operation timeouts, + * the following special values are allowed: + * - @a TIME_INFINITE no timeout. + * . + * @return The operation status. + * @retval MSG_OK if the function succeeded. + * @retval MSG_RESET if one or more I2C errors occurred, the errors can + * be retrieved using @p i2cGetErrors(). + * @retval MSG_TIMEOUT if a timeout occurred before operation end. After a + * timeout the driver must be stopped and restarted + * because the bus is in an uncertain state. + * + * @notapi + */ +msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr, + const uint8_t *txbuf, size_t txbytes, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout) { + + return _i2c_txrx_timeout(i2cp, addr, txbuf, txbytes, rxbuf, rxbytes, timeout); +} + +#endif /* HAL_USE_I2C */ + +/** @} */ diff --git a/os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.h b/os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.h new file mode 100644 index 0000000..fb88a5e --- /dev/null +++ b/os/hal/ports/NRF5/LLD/TWIMv1/hal_i2c_lld.h @@ -0,0 +1,210 @@ +/* + Copyright (C) 2018 andru + + 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. +*/ + +/** + * @file NRF5/NRF52832/hal_i2c_lld.h + * @brief NRF52 I2C subsystem low level driver header. + * + * @addtogroup I2C + * @{ + */ + +#ifndef HAL_I2C_LLD_H +#define HAL_I2C_LLD_H + +#if HAL_USE_I2C || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @name Configuration options + * @{ + */ +/** + * @brief I2C0 driver enable switch. + * @details If set to @p TRUE the support for I2C0 is included. + * @note The default is @p FALSE. + */ +#if !defined(NRF5_I2C_USE_I2C0) || defined(__DOXYGEN__) +#define NRF5_I2C_USE_I2C0 FALSE +#endif + +/** + * @brief I2C1 driver enable switch. + * @details If set to @p TRUE the support for I2C1 is included. + * @note The default is @p FALSE. + */ +#if !defined(NRF5_I2C_USE_I2C1) || defined(__DOXYGEN__) +#define NRF5_I2C_USE_I2C1 FALSE +#endif + +/** + * @brief I2C0 interrupt priority level setting. + */ +#if !defined(NRF5_I2C_I2C0_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define NRF5_I2C_I2C0_IRQ_PRIORITY 3 +#endif + +/** + * @brief I2C1 interrupt priority level setting. + */ +#if !defined(NRF5_I2C_I2C1_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define NRF5_I2C_I2C1_IRQ_PRIORITY 3 +#endif +/** @} */ + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +#if NRF5_I2C_USE_I2C0 && \ + !OSAL_IRQ_IS_VALID_PRIORITY(NRF5_I2C_I2C0_IRQ_PRIORITY) +#error "Invalid IRQ priority assigned to I2C0" +#endif + +#if NRF5_I2C_USE_I2C1 && \ + !OSAL_IRQ_IS_VALID_PRIORITY(NRF5_I2C_I2C1_IRQ_PRIORITY) +#error "Invalid IRQ priority assigned to I2C1" +#endif + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/* @brief Type representing I2C address. */ +typedef uint8_t i2caddr_t; + +/* @brief Type of I2C Driver condition flags. */ +typedef uint32_t i2cflags_t; + +/** + * @brief Driver configuration structure. + * @note Implementations may extend this structure to contain more, + * architecture dependent, fields. + */ + +/** + * @brief Driver configuration structure. + */ +typedef struct { + + /* @brief Clock to be used for the I2C bus. */ + uint32_t clock; + /* @brief Pad number for SCL */ + uint8_t scl_pad; + /* @brief Pad number for SDA */ + uint8_t sda_pad; + +} I2CConfig; + +/** + * @brief Type of a structure representing an I2C driver. + */ +typedef struct I2CDriver I2CDriver; + +/** + * @brief Structure representing an I2C driver. + */ +struct I2CDriver { + /** + * @brief Driver state. + */ + i2cstate_t state; + /** + * @brief Current configuration data. + */ + const I2CConfig *config; + /** + * @brief Error flags. + */ + i2cflags_t errors; +#if I2C_USE_MUTUAL_EXCLUSION || defined(__DOXYGEN__) + /** + * @brief Mutex protecting the bus. + */ + mutex_t mutex; +#endif /* I2C_USE_MUTUAL_EXCLUSION */ +#if defined(I2C_DRIVER_EXT_FIELDS) + I2C_DRIVER_EXT_FIELDS +#endif + /* @brief Thread waiting for I/O completion. */ + thread_reference_t thread; + /* @brief Current slave address without R/W bit. */ + i2caddr_t addr; + + /* End of the mandatory fields.*/ + + /* @brief Low-level register access. */ + NRF_TWIM_Type *i2c; +}; + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/** + * @brief Get errors from I2C driver. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * + * @notapi + */ +#define i2c_lld_get_errors(i2cp) ((i2cp)->errors) + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#if !defined(__DOXYGEN__) + +#if NRF5_I2C_USE_I2C0 +extern I2CDriver I2CD1; +#endif + +#if NRF5_I2C_USE_I2C1 +extern I2CDriver I2CD2; +#endif + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + void i2c_lld_init(void); + void i2c_lld_start(I2CDriver *i2cp); + void i2c_lld_stop(I2CDriver *i2cp); + msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr, + const uint8_t *txbuf, size_t txbytes, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout); + msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout); +#ifdef __cplusplus +} +#endif + +#endif /* HAL_USE_I2C */ + +#endif /* HAL_I2C_LLD_H */ + +/** @} */ diff --git a/os/hal/ports/NRF5/NRF52832/hal_lld.c b/os/hal/ports/NRF5/NRF52832/hal_lld.c index 500de13..e63e57e 100644 --- a/os/hal/ports/NRF5/NRF52832/hal_lld.c +++ b/os/hal/ports/NRF5/NRF52832/hal_lld.c @@ -55,23 +55,37 @@ */ void hal_lld_init(void) { - /* High frequency clock initialisation + /* High frequency clock initialization */ NRF_CLOCK->TASKS_HFCLKSTOP = 1; + #if !defined(NRF5_XTAL_VALUE) && (NRF5_XTAL_VALUE != 32000000) #error "A 32Mhz crystal is mandatory on nRF52 boards." #endif +#if (NRF5_HFCLK_SOURCE == NRF5_HFCLK_HFXO) + NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; + NRF_CLOCK->TASKS_HFCLKSTART = 1; + while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0); +#endif - /* Low frequency clock initialisation - * Clock is only started if st driver requires it + /* Low frequency clock initialization */ +#if (OSAL_ST_MODE != OSAL_ST_MODE_NONE) +#if (NRF5_ST_USE_RTC0 || NRF5_ST_USE_RTC1) && \ + (NRF5_LFCLK_SOURCE == NRF5_LFCLK_RC) +#error "A NRF5_SYSTEM_TICKS_AS_RTC requires LFCLK clock to be started." +#endif +#endif + NRF_CLOCK->TASKS_LFCLKSTOP = 1; + +#if (NRF5_LFCLK_SOURCE != NRF5_LFCLK_RC) NRF_CLOCK->LFCLKSRC = NRF5_LFCLK_SOURCE; - -#if (OSAL_ST_MODE != OSAL_ST_MODE_NONE) && \ - (NRF5_SYSTEM_TICKS == NRF5_SYSTEM_TICKS_AS_RTC) + + NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; NRF_CLOCK->TASKS_LFCLKSTART = 1; + while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0); #endif } diff --git a/os/hal/ports/NRF5/NRF52832/hal_lld.h b/os/hal/ports/NRF5/NRF52832/hal_lld.h index 24784d3..a2a8cc3 100644 --- a/os/hal/ports/NRF5/NRF52832/hal_lld.h +++ b/os/hal/ports/NRF5/NRF52832/hal_lld.h @@ -59,23 +59,37 @@ /* Driver pre-compile time settings. */ /*===========================================================================*/ +/** + * @brief Select source of High Frequency Clock (HFCLK) + * @details Possible values for source are: + * 0 : 64 MHz internal oscillator (HFINT) + * 1 : 32 MHz external crystal oscillator (HFXO) + */ +#if !defined(NRF5_HFCLK_SOURCE) || defined(__DOXYGEN__) +#define NRF5_HFCLK_SOURCE NRF5_HFCLK_HFINT +#endif + /** * @brief Select source of Low Frequency Clock (LFCLK) * @details Possible values for source are: * 0 : RC oscillator - * 1 : External cristal - * 2 : Synthetized clock from High Frequency Clock (HFCLK) - * When cristal is not available it's preferable to use the - * internal RC oscillator that synthezing the clock. + * 1 : External crystal + * 2 : Synthesized clock from High Frequency Clock (HFCLK) + * When crystal is not available it's preferable to use the + * internal RC oscillator that synthesizing the clock. */ #if !defined(NRF5_LFCLK_SOURCE) || defined(__DOXYGEN__) -#define NRF5_LFCLK_SOURCE 0 +#define NRF5_LFCLK_SOURCE NRF5_LFCLK_RC #endif /*===========================================================================*/ /* Derived constants and error checks. */ /*===========================================================================*/ +#if (NRF5_HFCLK_SOURCE < 0) || (NRF5_HFCLK_SOURCE > 1) +#error "Possible value for NRF5_HFCLK_SOURCE are HFINT=0, HFXO=1" +#endif + #if (NRF5_LFCLK_SOURCE < 0) || (NRF5_LFCLK_SOURCE > 2) #error "Possible value for NRF5_LFCLK_SOURCE are 0=RC, 1=XTAL, 2=Synth" #endif @@ -91,6 +105,14 @@ /*===========================================================================*/ /* External declarations. */ /*===========================================================================*/ +#if 0 // moved to board.h +#define NRF5_HFCLK_HFINT 0 +#define NRF5_HFCLK_HFXO 1 + +#define NRF5_LFCLK_RC 0 +#define NRF5_LFCLK_XTAL 1 +#define NRF5_LFCLK_SYNTH 2 +#endif #include "nvic.h" diff --git a/os/hal/ports/NRF5/NRF52832/nrf_delay.h b/os/hal/ports/NRF5/NRF52832/nrf_delay.h index 9b5df64..b73d8ae 100644 --- a/os/hal/ports/NRF5/NRF52832/nrf_delay.h +++ b/os/hal/ports/NRF5/NRF52832/nrf_delay.h @@ -1,97 +1,238 @@ -/* - Copyright (C) 2015 Stephen Caudle - - 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. -*/ - -/** - * @file NRF5/NRF52832/nrf_delay.h - * @brief NRF5 Delay routines - * - * @{ - */ - -#ifndef _NRF_DELAY_H -#define _NRF_DELAY_H - -inline static void nrf_delay_us(uint32_t volatile number_of_us) __attribute__((always_inline)); -inline static void nrf_delay_us(uint32_t volatile number_of_us) -{ -register uint32_t delay __asm ("r0") = number_of_us; -__asm volatile ( -".syntax unified\n" - "1:\n" - " SUBS %0, %0, #1\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " NOP\n" - " BNE 1b\n" - ".syntax divided\n" - : "+r" (delay)); -} -#endif //__NRF_DELAY_H +#ifndef _NRF_DELAY_H +#define _NRF_DELAY_H + +/** + * @brief Function for delaying execution for number of microseconds. + * + * @note NRF52 has instruction cache and because of that delay is not precise. + * + * @param number_of_ms + */ +/*lint --e{438, 522} "Variable not used" "Function lacks side-effects" */ +#if defined ( __CC_ARM ) + +static __ASM void __INLINE nrf_delay_us(uint32_t volatile number_of_us) +{ +loop + SUBS R0, R0, #1 + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP +#ifdef NRF52 + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP + NOP +#endif + BNE loop + BX LR +} + +#elif defined ( __ICCARM__ ) + +static void __INLINE nrf_delay_us(uint32_t volatile number_of_us) +{ +__ASM ( +"loop:\n\t" + " SUBS R0, R0, #1\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" +#ifdef NRF52 + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" + " NOP\n\t" +#endif + " BNE.n loop\n\t"); +} + +#elif defined ( _WIN32 ) || defined ( __unix ) || defined( __APPLE__ ) + +__STATIC_INLINE void nrf_delay_us(uint32_t volatile number_of_us); + +#ifndef CUSTOM_NRF_DELAY_US +__STATIC_INLINE void nrf_delay_us(uint32_t volatile number_of_us) +{} +#endif + +#elif defined ( __GNUC__ ) + +inline static void nrf_delay_us(uint32_t volatile number_of_us) __attribute__((always_inline)); +inline static void nrf_delay_us(uint32_t volatile number_of_us) +{ +register uint32_t delay __ASM ("r0") = number_of_us; +__ASM volatile ( +#ifdef NRF51 + ".syntax unified\n" +#endif + "1:\n" + " SUBS %0, %0, #1\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" +#ifdef NRF52 + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" +#endif + " BNE 1b\n" +#ifdef NRF51 + ".syntax divided\n" +#endif + : "+r" (delay)); +} +#endif + +#endif diff --git a/os/hal/ports/NRF5/NRF52832/platform.mk b/os/hal/ports/NRF5/NRF52832/platform.mk index e71f591..f1fcfe7 100644 --- a/os/hal/ports/NRF5/NRF52832/platform.mk +++ b/os/hal/ports/NRF5/NRF52832/platform.mk @@ -20,7 +20,8 @@ endif include ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/GPIOv1/driver.mk include ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/UARTv1/driver.mk include ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/SPIv1/driver.mk -include ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TWIv1/driver.mk +include ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TWIMv1/driver.mk +include ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/PWMv2/driver.mk include ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/TIMERv1/driver.mk include ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/WDTv1/driver.mk include ${CHIBIOS_CONTRIB}/os/hal/ports/NRF5/LLD/RNGv1/driver.mk diff --git a/os/various/devices_lib/rf/nrf52_radio.c b/os/various/devices_lib/rf/nrf52_radio.c new file mode 100644 index 0000000..e55870f --- /dev/null +++ b/os/various/devices_lib/rf/nrf52_radio.c @@ -0,0 +1,1111 @@ +/* Copyright (c) 2014 Nordic Semiconductor. All Rights Reserved. + * + * The information contained herein is property of Nordic Semiconductor ASA. + * Terms and conditions of usage are described in detail in NORDIC + * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. + * + * Licensees are granted free, non-transferable use of the information. NO + * WARRANTY of ANY KIND is provided. This heading must NOT be removed from + * the file. + * + * Enhanced ShockBurst proprietary protocol to ChibiOS port + * + * ported on: 25/10/2018, by andru + * + */ + +#include +#include + +#include "ch.h" +#include "hal.h" + +#include "nrf52_radio.h" + + +#define BIT_MASK_UINT_8(x) (0xFF >> (8 - (x))) +#define NRF52_PIPE_COUNT 9 + +#define RADIO_SHORTS_COMMON ( RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk | \ + RADIO_SHORTS_ADDRESS_RSSISTART_Msk | RADIO_SHORTS_DISABLED_RSSISTOP_Msk ) + +// Constant parameters +#define RX_WAIT_FOR_ACK_TIMEOUT_US_2MBPS (48) /**< 2MBit RX wait for ack timeout value. Smallest reliable value - 43 */ +#define RX_WAIT_FOR_ACK_TIMEOUT_US_1MBPS (64) /**< 1MBit RX wait for ack timeout value. Smallest reliable value - 59 */ + +#define NRF52_ADDR_UPDATE_MASK_BASE0 (1 << 0) /*< Mask value to signal updating BASE0 radio address. */ +#define NRF52_ADDR_UPDATE_MASK_BASE1 (1 << 1) /*< Mask value to signal updating BASE1 radio address. */ +#define NRF52_ADDR_UPDATE_MASK_PREFIX (1 << 2) /*< Mask value to signal updating radio prefixes */ + +#define NRF52_PID_RESET_VALUE 0xFF /**< Invalid PID value which is guaranteed to not collide with any valid PID value. */ +#define NRF52_PID_MAX 3 /**< Maximum value for PID. */ +#define NRF52_CRC_RESET_VALUE 0xFFFF /**< CRC reset value*/ + +#ifndef NRF52_RADIO_USE_TIMER0 +#define NRF52_RADIO_USE_TIMER0 FALSE +#endif + +#ifndef NRF52_RADIO_USE_TIMER1 +#define NRF52_RADIO_USE_TIMER1 FALSE +#endif + +#ifndef NRF52_RADIO_USE_TIMER2 +#define NRF52_RADIO_USE_TIMER2 FALSE +#endif + +#ifndef NRF52_RADIO_USE_TIMER3 +#define NRF52_RADIO_USE_TIMER3 FALSE +#endif + +#ifndef NRF52_RADIO_USE_TIMER4 +#define NRF52_RADIO_USE_TIMER4 FALSE +#endif + +#ifndef NRF52_RADIO_IRQ_PRIORITY +#define NRF52_RADIO_IRQ_PRIORITY 3 /**< RADIO interrupt priority. */ +#endif + +#ifndef NRF52_RADIO_PPI_TIMER_START +#error "PPI channel NRF52_RADIO_PPI_TIMER_START need to be defined" +#endif + +#ifndef NRF52_RADIO_PPI_TIMER_STOP +#error "PPI channel NRF52_RADIO_PPI_TIMER_STOP need to be defined" +#endif + +#ifndef NRF52_RADIO_PPI_RX_TIMEOUT +#error "PPI channel NRF52_RADIO_PPI_RX_TIMEOUT need to be defined" +#endif + +#ifndef NRF52_RADIO_PPI_TX_START +#error "PPI channel NRF52_RADIO_PPI_TX_START need to be defined" +#endif + +#if (NRF52_RADIO_USE_TIMER0 == FALSE) && (NRF52_RADIO_USE_TIMER1 == FALSE) && \ + (NRF52_RADIO_USE_TIMER2 == FALSE) && (NRF52_RADIO_USE_TIMER3 == FALSE) && \ + (NRF52_RADIO_USE_TIMER4 == FALSE) +#error "At least one hardware TIMER must be defined" +#endif + +#ifndef NRF52_RADIO_INTTHD_PRIORITY +#error "Interrupt handle thread priority need to be defined" +#endif + +#ifndef NRF52_RADIO_EVTTHD_PRIORITY +#error "Event thread priority need to be defined" +#endif + +#define VERIFY_PAYLOAD_LENGTH(p) \ +do \ +{ \ + if(p->length == 0 || \ + p->length > NRF52_MAX_PAYLOAD_LENGTH || \ + (RFD1.config.protocol == NRF52_PROTOCOL_ESB && \ + p->length > RFD1.config.payload_length)) \ + { \ + return NRF52_ERROR_INVALID_LENGTH; \ + } \ +}while(0) + +//Structure holding pipe info PID and CRC and ack payload. +typedef struct +{ + uint16_t m_crc; + uint8_t m_pid; + uint8_t m_ack_payload; +} pipe_info_t; + +// First in first out queue of payloads to be transmitted. +typedef struct +{ + nrf52_payload_t * p_payload[NRF52_TX_FIFO_SIZE]; /**< Pointer to the actual queue. */ + uint32_t entry_point; /**< Current start of queue. */ + uint32_t exit_point; /**< Current end of queue. */ + uint32_t count; /**< Current number of elements in the queue. */ +} nrf52_payload_tx_fifo_t; + +// First in first out queue of received payloads. +typedef struct +{ + nrf52_payload_t * p_payload[NRF52_RX_FIFO_SIZE]; /**< Pointer to the actual queue. */ + uint32_t entry_point; /**< Current start of queue. */ + uint32_t exit_point; /**< Current end of queue. */ + uint32_t count; /**< Current number of elements in the queue. */ +} nrf52_payload_rx_fifo_t; + +// These function pointers are changed dynamically, depending on protocol configuration and state. +//static void (*on_radio_end)(RFDriver *rfp) = NULL; +static void (*set_rf_payload_format)(RFDriver *rfp, uint32_t payload_length) = NULL; + +// The following functions are assigned to the function pointers above. +static void on_radio_disabled_tx_noack(RFDriver *rfp); +static void on_radio_disabled_tx(RFDriver *rfp); +static void on_radio_disabled_tx_wait_for_ack(RFDriver *rfp); +static void on_radio_disabled_rx(RFDriver *rfp); +static void on_radio_disabled_rx_ack(RFDriver *rfp); + +static volatile uint16_t wait_for_ack_timeout_us; +static nrf52_payload_t * p_current_payload; + +// TX FIFO +static nrf52_payload_t tx_fifo_payload[NRF52_TX_FIFO_SIZE]; +static nrf52_payload_tx_fifo_t tx_fifo; + +// RX FIFO +static nrf52_payload_t rx_fifo_payload[NRF52_RX_FIFO_SIZE]; +static nrf52_payload_rx_fifo_t rx_fifo; + +// Payload buffers +static uint8_t tx_payload_buffer[NRF52_MAX_PAYLOAD_LENGTH + 2]; +static uint8_t rx_payload_buffer[NRF52_MAX_PAYLOAD_LENGTH + 2]; + +static uint8_t pids[NRF52_PIPE_COUNT]; +static pipe_info_t rx_pipe_info[NRF52_PIPE_COUNT]; + + // disable and events semaphores. +static binary_semaphore_t disable_sem; +static binary_semaphore_t events_sem; + +RFDriver RFD1; + +// Function to do bytewise bit-swap on a unsigned 32 bit value +static uint32_t bytewise_bit_swap(uint8_t const * p_inp) { + uint32_t inp = (*(uint32_t*)p_inp); + + return __REV((uint32_t)__RBIT(inp)); //lint -esym(628, __rev) -esym(526, __rev) -esym(628, __rbit) -esym(526, __rbit) */ +} + +// Internal function to convert base addresses from nRF24L type addressing to nRF52 type addressing +static uint32_t addr_conv(uint8_t const* p_addr) { + return __REV(bytewise_bit_swap(p_addr)); //lint -esym(628, __rev) -esym(526, __rev) */ +} + +static thread_t *rfEvtThread_p; +static THD_WORKING_AREA(waRFEvtThread, 64); +static THD_FUNCTION(rfEvtThread, arg) { + (void)arg; + + chRegSetThreadName("rfevent"); + + while (!chThdShouldTerminateX()) { + chBSemWait(&events_sem); + + nrf52_int_flags_t interrupts = RFD1.flags; + RFD1.flags = 0; + + if (interrupts & NRF52_INT_TX_SUCCESS_MSK) { + chEvtBroadcastFlags(&RFD1.eventsrc, (eventflags_t) NRF52_EVENT_TX_SUCCESS); + } + if (interrupts & NRF52_INT_TX_FAILED_MSK) { + chEvtBroadcastFlags(&RFD1.eventsrc, (eventflags_t) NRF52_EVENT_TX_FAILED); + } + if (interrupts & NRF52_INT_RX_DR_MSK) { + chEvtBroadcastFlags(&RFD1.eventsrc, (eventflags_t) NRF52_EVENT_RX_RECEIVED); + } + } + chThdExit((msg_t) 0); +} + +static thread_t *rfIntThread_p; +static THD_WORKING_AREA(waRFIntThread, 64); +static THD_FUNCTION(rfIntThread, arg) { + (void)arg; + + chRegSetThreadName("rfint"); + + while (!chThdShouldTerminateX()) { + chBSemWait(&disable_sem); + switch (RFD1.state) { + case NRF52_STATE_PTX_TX: + on_radio_disabled_tx_noack(&RFD1); + break; + case NRF52_STATE_PTX_TX_ACK: + on_radio_disabled_tx(&RFD1); + break; + case NRF52_STATE_PTX_RX_ACK: + on_radio_disabled_tx_wait_for_ack(&RFD1); + break; + case NRF52_STATE_PRX: + on_radio_disabled_rx(&RFD1); + break; + case NRF52_STATE_PRX_SEND_ACK: + on_radio_disabled_rx_ack(&RFD1); + break; + default: + break; + } + } + chThdExit((msg_t) 0); +} + +static void serve_radio_interrupt(RFDriver *rfp) { + (void) rfp; + if ((NRF_RADIO->INTENSET & RADIO_INTENSET_READY_Msk) && NRF_RADIO->EVENTS_READY) { + NRF_RADIO->EVENTS_READY = 0; + (void) NRF_RADIO->EVENTS_READY; + } + if ((NRF_RADIO->INTENSET & RADIO_INTENSET_DISABLED_Msk) && NRF_RADIO->EVENTS_DISABLED) { + NRF_RADIO->EVENTS_DISABLED = 0; + (void) NRF_RADIO->EVENTS_DISABLED; + chSysLockFromISR(); + chBSemSignalI(&disable_sem); + chSysUnlockFromISR(); + } +} + +/** + * @brief RADIO events interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(Vector44) { + + OSAL_IRQ_PROLOGUE(); + + serve_radio_interrupt(&RFD1); + + OSAL_IRQ_EPILOGUE(); +} + +static void set_rf_payload_format_esb_dpl(RFDriver *rfp, uint32_t payload_length) { + (void)payload_length; +#if (NRF52_MAX_PAYLOAD_LENGTH <= 32) + // Using 6 bits for length + NRF_RADIO->PCNF0 = (0 << RADIO_PCNF0_S0LEN_Pos) | + (6 << RADIO_PCNF0_LFLEN_Pos) | + (3 << RADIO_PCNF0_S1LEN_Pos) ; +#else + // Using 8 bits for length + NRF_RADIO->PCNF0 = (0 << RADIO_PCNF0_S0LEN_Pos) | + (8 << RADIO_PCNF0_LFLEN_Pos) | + (3 << RADIO_PCNF0_S1LEN_Pos) ; +#endif + NRF_RADIO->PCNF1 = (RADIO_PCNF1_WHITEEN_Disabled << RADIO_PCNF1_WHITEEN_Pos) | + (RADIO_PCNF1_ENDIAN_Big << RADIO_PCNF1_ENDIAN_Pos) | + ((rfp->config.address.addr_length - 1) << RADIO_PCNF1_BALEN_Pos) | + (0 << RADIO_PCNF1_STATLEN_Pos) | + (NRF52_MAX_PAYLOAD_LENGTH << RADIO_PCNF1_MAXLEN_Pos); +} + +static void set_rf_payload_format_esb(RFDriver *rfp, uint32_t payload_length) { + NRF_RADIO->PCNF0 = (1 << RADIO_PCNF0_S0LEN_Pos) | + (0 << RADIO_PCNF0_LFLEN_Pos) | + (1 << RADIO_PCNF0_S1LEN_Pos); + + NRF_RADIO->PCNF1 = (RADIO_PCNF1_WHITEEN_Disabled << RADIO_PCNF1_WHITEEN_Pos) | + (RADIO_PCNF1_ENDIAN_Big << RADIO_PCNF1_ENDIAN_Pos) | + ((rfp->config.address.addr_length - 1) << RADIO_PCNF1_BALEN_Pos) | + (payload_length << RADIO_PCNF1_STATLEN_Pos) | + (payload_length << RADIO_PCNF1_MAXLEN_Pos); +} + +/* Set BASE0 and BASE1 addresses & prefixes registers + * NRF52 { prefixes[0], base0_addr[0], base0_addr[1], base0_addr[2], base0_addr[3] } == + * NRF24 { addr[0], addr[1], addr[2], addr[3], addr[4] } + */ +static void set_addresses(RFDriver *rfp, uint8_t update_mask) { + if (update_mask & NRF52_ADDR_UPDATE_MASK_BASE0) { + NRF_RADIO->BASE0 = addr_conv(rfp->config.address.base_addr_p0); + NRF_RADIO->DAB[0] = addr_conv(rfp->config.address.base_addr_p0); + } + + if (update_mask & NRF52_ADDR_UPDATE_MASK_BASE1) { + NRF_RADIO->BASE1 = addr_conv(rfp->config.address.base_addr_p1); + NRF_RADIO->DAB[1] = addr_conv(rfp->config.address.base_addr_p1); + } + + if (update_mask & NRF52_ADDR_UPDATE_MASK_PREFIX) { + NRF_RADIO->PREFIX0 = bytewise_bit_swap(&rfp->config.address.pipe_prefixes[0]); + NRF_RADIO->DAP[0] = bytewise_bit_swap(&rfp->config.address.pipe_prefixes[0]); + NRF_RADIO->PREFIX1 = bytewise_bit_swap(&rfp->config.address.pipe_prefixes[4]); + NRF_RADIO->DAP[1] = bytewise_bit_swap(&rfp->config.address.pipe_prefixes[4]); + } +} + +static void set_tx_power(RFDriver *rfp) { + NRF_RADIO->TXPOWER = rfp->config.tx_power << RADIO_TXPOWER_TXPOWER_Pos; +} + +static void set_bitrate(RFDriver *rfp) { + NRF_RADIO->MODE = rfp->config.bitrate << RADIO_MODE_MODE_Pos; + + switch (rfp->config.bitrate) { + case NRF52_BITRATE_2MBPS: + wait_for_ack_timeout_us = RX_WAIT_FOR_ACK_TIMEOUT_US_2MBPS; + break; + case NRF52_BITRATE_1MBPS: + wait_for_ack_timeout_us = RX_WAIT_FOR_ACK_TIMEOUT_US_1MBPS; + break; + } +} + +static void set_protocol(RFDriver *rfp) { + switch (rfp->config.protocol) { + case NRF52_PROTOCOL_ESB_DPL: + set_rf_payload_format = set_rf_payload_format_esb_dpl; + break; + case NRF52_PROTOCOL_ESB: + set_rf_payload_format = set_rf_payload_format_esb; + break; + } +} + +static void set_crc(RFDriver *rfp) { + NRF_RADIO->CRCCNF = rfp->config.crc << RADIO_CRCCNF_LEN_Pos; + + if (rfp->config.crc == RADIO_CRCCNF_LEN_Two) + { + NRF_RADIO->CRCINIT = 0xFFFFUL; // Initial value + NRF_RADIO->CRCPOLY = 0x11021UL; // CRC poly: x^16+x^12^x^5+1 + } + else if (rfp->config.crc == RADIO_CRCCNF_LEN_One) + { + NRF_RADIO->CRCINIT = 0xFFUL; // Initial value + NRF_RADIO->CRCPOLY = 0x107UL; // CRC poly: x^8+x^2^x^1+1 + } +} + +static void ppi_init(RFDriver *rfp) { + NRF_PPI->CH[NRF52_RADIO_PPI_TIMER_START].EEP = (uint32_t)&NRF_RADIO->EVENTS_READY; + NRF_PPI->CH[NRF52_RADIO_PPI_TIMER_START].TEP = (uint32_t)&rfp->timer->TASKS_START; + + NRF_PPI->CH[NRF52_RADIO_PPI_TIMER_STOP].EEP = (uint32_t)&NRF_RADIO->EVENTS_ADDRESS; + NRF_PPI->CH[NRF52_RADIO_PPI_TIMER_STOP].TEP = (uint32_t)&rfp->timer->TASKS_STOP; + + NRF_PPI->CH[NRF52_RADIO_PPI_RX_TIMEOUT].EEP = (uint32_t)&rfp->timer->EVENTS_COMPARE[0]; + NRF_PPI->CH[NRF52_RADIO_PPI_RX_TIMEOUT].TEP = (uint32_t)&NRF_RADIO->TASKS_DISABLE; + + NRF_PPI->CH[NRF52_RADIO_PPI_TX_START].EEP = (uint32_t)&rfp->timer->EVENTS_COMPARE[1]; + NRF_PPI->CH[NRF52_RADIO_PPI_TX_START].TEP = (uint32_t)&NRF_RADIO->TASKS_TXEN; +} + +static void set_parameters(RFDriver *rfp) { + set_tx_power(rfp); + set_bitrate(rfp); + set_protocol(rfp); + set_crc(rfp); + set_rf_payload_format(rfp, rfp->config.payload_length); +} + +static void reset_fifo(void) { + tx_fifo.entry_point = 0; + tx_fifo.exit_point = 0; + tx_fifo.count = 0; + + rx_fifo.entry_point = 0; + rx_fifo.exit_point = 0; + rx_fifo.count = 0; +} + +static void init_fifo(void) { + uint8_t i; + reset_fifo(); + + for (i = 0; i < NRF52_TX_FIFO_SIZE; i++) { + tx_fifo.p_payload[i] = &tx_fifo_payload[i]; + } + + for (i = 0; i < NRF52_RX_FIFO_SIZE; i++) { + rx_fifo.p_payload[i] = &rx_fifo_payload[i]; + } +} + +static void tx_fifo_remove_last(void) { + if (tx_fifo.count > 0) { + nvicDisableVector(RADIO_IRQn); + + tx_fifo.count--; + if (++tx_fifo.exit_point >= NRF52_TX_FIFO_SIZE) { + tx_fifo.exit_point = 0; + } + + nvicEnableVector(RADIO_IRQn, NRF52_RADIO_IRQ_PRIORITY); + } +} + +/** @brief Function to push the content of the rx_buffer to the RX FIFO. + * + * The module will point the register NRF_RADIO->PACKETPTR to a buffer for receiving packets. + * After receiving a packet the module will call this function to copy the received data to + * the RX FIFO. + * + * @param pipe Pipe number to set for the packet. + * @param pid Packet ID. + * + * @retval true Operation successful. + * @retval false Operation failed. + */ +static bool rx_fifo_push_rfbuf(RFDriver *rfp, uint8_t pipe, uint8_t pid) { + if (rx_fifo.count < NRF52_RX_FIFO_SIZE) { + if (rfp->config.protocol == NRF52_PROTOCOL_ESB_DPL) { + if (rx_payload_buffer[0] > NRF52_MAX_PAYLOAD_LENGTH) { + return false; + } + + rx_fifo.p_payload[rx_fifo.entry_point]->length = rx_payload_buffer[0]; + } + else if (rfp->state == NRF52_STATE_PTX_RX_ACK) { + // Received packet is an acknowledgment + rx_fifo.p_payload[rx_fifo.entry_point]->length = 0; + } + else { + rx_fifo.p_payload[rx_fifo.entry_point]->length = rfp->config.payload_length; + } + + memcpy(rx_fifo.p_payload[rx_fifo.entry_point]->data, &rx_payload_buffer[2], + rx_fifo.p_payload[rx_fifo.entry_point]->length); + + rx_fifo.p_payload[rx_fifo.entry_point]->pipe = pipe; + rx_fifo.p_payload[rx_fifo.entry_point]->rssi = NRF_RADIO->RSSISAMPLE; + rx_fifo.p_payload[rx_fifo.entry_point]->pid = pid; + if (++rx_fifo.entry_point >= NRF52_RX_FIFO_SIZE) { + rx_fifo.entry_point = 0; + } + rx_fifo.count++; + + return true; + } + + return false; +} + +static void timer_init(RFDriver *rfp) { + // Configure the system timer with a 1 MHz base frequency + rfp->timer->PRESCALER = 4; + rfp->timer->BITMODE = TIMER_BITMODE_BITMODE_16Bit; + rfp->timer->SHORTS = TIMER_SHORTS_COMPARE1_CLEAR_Msk | TIMER_SHORTS_COMPARE1_STOP_Msk; +} + +static void start_tx_transaction(RFDriver *rfp) { + bool ack; + + rfp->tx_attempt = 1; + rfp->tx_remaining = rfp->config.retransmit.count; + + // Prepare the payload + p_current_payload = tx_fifo.p_payload[tx_fifo.exit_point]; + + // Handling ack if noack is set to false or if selctive auto ack is turned turned off + ack = !p_current_payload->noack || !rfp->config.selective_auto_ack; + + switch (rfp->config.protocol) { + case NRF52_PROTOCOL_ESB: + set_rf_payload_format(rfp, p_current_payload->length); + tx_payload_buffer[0] = p_current_payload->pid; + tx_payload_buffer[1] = 0; + memcpy(&tx_payload_buffer[2], p_current_payload->data, p_current_payload->length); + + NRF_RADIO->SHORTS = RADIO_SHORTS_COMMON | RADIO_SHORTS_DISABLED_RXEN_Msk; + NRF_RADIO->INTENSET = RADIO_INTENSET_DISABLED_Msk | RADIO_INTENSET_READY_Msk; + + // Configure the retransmit counter + rfp->tx_remaining = rfp->config.retransmit.count; + rfp->state = NRF52_STATE_PTX_TX_ACK; + break; + + case NRF52_PROTOCOL_ESB_DPL: + tx_payload_buffer[0] = p_current_payload->length; + tx_payload_buffer[1] = p_current_payload->pid << 1; + tx_payload_buffer[1] |= ack ? 0x00 : 0x01; + memcpy(&tx_payload_buffer[2], p_current_payload->data, p_current_payload->length); + + if (ack) { + NRF_RADIO->SHORTS = RADIO_SHORTS_COMMON | RADIO_SHORTS_DISABLED_RXEN_Msk; + NRF_RADIO->INTENSET = RADIO_INTENSET_DISABLED_Msk | RADIO_INTENSET_READY_Msk; + + // Configure the retransmit counter + rfp->tx_remaining = rfp->config.retransmit.count; + rfp->state = NRF52_STATE_PTX_TX_ACK; + } + else { + NRF_RADIO->SHORTS = RADIO_SHORTS_COMMON; + NRF_RADIO->INTENSET = RADIO_INTENSET_DISABLED_Msk; + rfp->state = NRF52_STATE_PTX_TX; + } + break; + } + + NRF_RADIO->TXADDRESS = p_current_payload->pipe; + NRF_RADIO->RXADDRESSES = 1 << p_current_payload->pipe; + + NRF_RADIO->FREQUENCY = rfp->config.address.rf_channel; + NRF_RADIO->PACKETPTR = (uint32_t)tx_payload_buffer; + + NRF_RADIO->EVENTS_READY = 0; + NRF_RADIO->EVENTS_DISABLED = 0; + (void)NRF_RADIO->EVENTS_READY; + (void)NRF_RADIO->EVENTS_DISABLED; + + nvicClearPending(RADIO_IRQn); + nvicEnableVector(RADIO_IRQn, NRF52_RADIO_IRQ_PRIORITY); + + NRF_RADIO->TASKS_TXEN = 1; +} + +static void on_radio_disabled_tx_noack(RFDriver *rfp) { + rfp->flags |= NRF52_INT_TX_SUCCESS_MSK; + tx_fifo_remove_last(); + + chBSemSignal(&events_sem); + + if (tx_fifo.count == 0) { + rfp->state = NRF52_STATE_IDLE; + } + else { + start_tx_transaction(rfp); + } +} + +static void on_radio_disabled_tx(RFDriver *rfp) { + // Remove the DISABLED -> RXEN shortcut, to make sure the radio stays + // disabled after the RX window + NRF_RADIO->SHORTS = RADIO_SHORTS_COMMON; + + // Make sure the timer is started the next time the radio is ready, + // and that it will disable the radio automatically if no packet is + // received by the time defined in m_wait_for_ack_timeout_us + rfp->timer->CC[0] = wait_for_ack_timeout_us + 130; + rfp->timer->CC[1] = rfp->config.retransmit.delay - 130; + rfp->timer->TASKS_CLEAR = 1; + rfp->timer->EVENTS_COMPARE[0] = 0; + rfp->timer->EVENTS_COMPARE[1] = 0; + (void)rfp->timer->EVENTS_COMPARE[0]; + (void)rfp->timer->EVENTS_COMPARE[1]; + + NRF_PPI->CHENSET = (1 << NRF52_RADIO_PPI_TIMER_START) | + (1 << NRF52_RADIO_PPI_RX_TIMEOUT) | + (1 << NRF52_RADIO_PPI_TIMER_STOP); + NRF_PPI->CHENCLR = (1 << NRF52_RADIO_PPI_TX_START); + + NRF_RADIO->EVENTS_END = 0; + (void)NRF_RADIO->EVENTS_END; + + if (rfp->config.protocol == NRF52_PROTOCOL_ESB) { + set_rf_payload_format(rfp, 0); + } + + NRF_RADIO->PACKETPTR = (uint32_t)rx_payload_buffer; + rfp->state = NRF52_STATE_PTX_RX_ACK; +} + +static void on_radio_disabled_tx_wait_for_ack(RFDriver *rfp) { + // This marks the completion of a TX_RX sequence (TX with ACK) + + // Make sure the timer will not deactivate the radio if a packet is received + NRF_PPI->CHENCLR = (1 << NRF52_RADIO_PPI_TIMER_START) | + (1 << NRF52_RADIO_PPI_RX_TIMEOUT) | + (1 << NRF52_RADIO_PPI_TIMER_STOP); + + // If the radio has received a packet and the CRC status is OK + if (NRF_RADIO->EVENTS_END && NRF_RADIO->CRCSTATUS != 0) { + rfp->timer->TASKS_STOP = 1; + NRF_PPI->CHENCLR = (1 << NRF52_RADIO_PPI_TX_START); + rfp->flags |= NRF52_INT_TX_SUCCESS_MSK; + rfp->tx_attempt++;// = rfp->config.retransmit.count - rfp->tx_remaining + 1; + + tx_fifo_remove_last(); + + if (rfp->config.protocol != NRF52_PROTOCOL_ESB && rx_payload_buffer[0] > 0) { + if (rx_fifo_push_rfbuf(rfp, (uint8_t)NRF_RADIO->TXADDRESS, 0)) { + rfp->flags |= NRF52_INT_RX_DR_MSK; + } + } + + chBSemSignal(&events_sem); + + if ((tx_fifo.count == 0) || (rfp->config.tx_mode == NRF52_TXMODE_MANUAL)) { + rfp->state = NRF52_STATE_IDLE; + } + else { + start_tx_transaction(rfp); + } + } + else { + if (rfp->tx_remaining-- == 0) { + rfp->timer->TASKS_STOP = 1; + NRF_PPI->CHENCLR = (1 << NRF52_RADIO_PPI_TX_START); + // All retransmits are expended, and the TX operation is suspended + rfp->tx_attempt = rfp->config.retransmit.count + 1; + rfp->flags |= NRF52_INT_TX_FAILED_MSK; + + chBSemSignal(&events_sem); + + rfp->state = NRF52_STATE_IDLE; + } + else { + // There are still have more retransmits left, TX mode should be + // entered again as soon as the system timer reaches CC[1]. + NRF_RADIO->SHORTS = RADIO_SHORTS_COMMON | RADIO_SHORTS_DISABLED_RXEN_Msk; + set_rf_payload_format(rfp, p_current_payload->length); + NRF_RADIO->PACKETPTR = (uint32_t)tx_payload_buffer; + rfp->state = NRF52_STATE_PTX_TX_ACK; + rfp->timer->TASKS_START = 1; + NRF_PPI->CHENSET = (1 << NRF52_RADIO_PPI_TX_START); + if (rfp->timer->EVENTS_COMPARE[1]) + NRF_RADIO->TASKS_TXEN = 1; + } + } +} + +static void clear_events_restart_rx(RFDriver *rfp) { + NRF_RADIO->SHORTS = RADIO_SHORTS_COMMON; + set_rf_payload_format(rfp, rfp->config.payload_length); + NRF_RADIO->PACKETPTR = (uint32_t)rx_payload_buffer; + + NRF_RADIO->INTENCLR = RADIO_INTENCLR_DISABLED_Msk; + NRF_RADIO->EVENTS_DISABLED = 0; + (void) NRF_RADIO->EVENTS_DISABLED; + + NRF_RADIO->TASKS_DISABLE = 1; + while (NRF_RADIO->EVENTS_DISABLED == 0); + + NRF_RADIO->EVENTS_DISABLED = 0; + (void) NRF_RADIO->EVENTS_DISABLED; + NRF_RADIO->INTENSET = RADIO_INTENSET_DISABLED_Msk; + + NRF_RADIO->SHORTS = RADIO_SHORTS_COMMON | RADIO_SHORTS_DISABLED_TXEN_Msk; + NRF_RADIO->TASKS_RXEN = 1; +} + +static void on_radio_disabled_rx(RFDriver *rfp) { + bool ack = false; + bool retransmit_payload = false; + bool send_rx_event = true; + pipe_info_t * p_pipe_info; + + if (NRF_RADIO->CRCSTATUS == 0) { + clear_events_restart_rx(rfp); + return; + } + + if(rx_fifo.count >= NRF52_RX_FIFO_SIZE) { + clear_events_restart_rx(rfp); + return; + } + + p_pipe_info = &rx_pipe_info[NRF_RADIO->RXMATCH]; + if (NRF_RADIO->RXCRC == p_pipe_info->m_crc && + (rx_payload_buffer[1] >> 1) == p_pipe_info->m_pid ) { + retransmit_payload = true; + send_rx_event = false; + } + + p_pipe_info->m_pid = rx_payload_buffer[1] >> 1; + p_pipe_info->m_crc = NRF_RADIO->RXCRC; + + if(rfp->config.selective_auto_ack == false || ((rx_payload_buffer[1] & 0x01) == 0)) + ack = true; + + if(ack) { + NRF_RADIO->SHORTS = RADIO_SHORTS_COMMON | RADIO_SHORTS_DISABLED_RXEN_Msk; + + switch(rfp->config.protocol) { + case NRF52_PROTOCOL_ESB_DPL: + { + if (tx_fifo.count > 0 && + (tx_fifo.p_payload[tx_fifo.exit_point]->pipe == NRF_RADIO->RXMATCH)) + { + // Pipe stays in ACK with payload until TX fifo is empty + // Do not report TX success on first ack payload or retransmit + if (p_pipe_info->m_ack_payload != 0 && !retransmit_payload) { + if(++tx_fifo.exit_point >= NRF52_TX_FIFO_SIZE) { + tx_fifo.exit_point = 0; + } + + tx_fifo.count--; + + // ACK payloads also require TX_DS + // (page 40 of the 'nRF24LE1_Product_Specification_rev1_6.pdf'). + rfp->flags |= NRF52_INT_TX_SUCCESS_MSK; + } + + p_pipe_info->m_ack_payload = 1; + + p_current_payload = tx_fifo.p_payload[tx_fifo.exit_point]; + + set_rf_payload_format(rfp, p_current_payload->length); + tx_payload_buffer[0] = p_current_payload->length; + memcpy(&tx_payload_buffer[2], + p_current_payload->data, + p_current_payload->length); + } + else { + p_pipe_info->m_ack_payload = 0; + set_rf_payload_format(rfp, 0); + tx_payload_buffer[0] = 0; + } + + tx_payload_buffer[1] = rx_payload_buffer[1]; + } + break; + + case NRF52_PROTOCOL_ESB: + { + set_rf_payload_format(rfp, 0); + tx_payload_buffer[0] = rx_payload_buffer[0]; + tx_payload_buffer[1] = 0; + } + break; + } + + rfp->state = NRF52_STATE_PRX_SEND_ACK; + NRF_RADIO->TXADDRESS = NRF_RADIO->RXMATCH; + NRF_RADIO->PACKETPTR = (uint32_t)tx_payload_buffer; + } + else { + clear_events_restart_rx(rfp); + } + + if (send_rx_event) { + // Push the new packet to the RX buffer and trigger a received event if the operation was + // successful. + if (rx_fifo_push_rfbuf(rfp, NRF_RADIO->RXMATCH, p_pipe_info->m_pid)) { + rfp->flags |= NRF52_INT_RX_DR_MSK; + chBSemSignal(&events_sem); + } + } +} + +static void on_radio_disabled_rx_ack(RFDriver *rfp) { + NRF_RADIO->SHORTS = RADIO_SHORTS_COMMON | RADIO_SHORTS_DISABLED_TXEN_Msk; + set_rf_payload_format(rfp, rfp->config.payload_length); + + NRF_RADIO->PACKETPTR = (uint32_t)rx_payload_buffer; + + rfp->state = NRF52_STATE_PRX; +} + +nrf52_error_t radio_disable(void) { + RFD1.state = NRF52_STATE_IDLE; + + // Clear PPI + NRF_PPI->CHENCLR = (1 << NRF52_RADIO_PPI_TIMER_START) | + (1 << NRF52_RADIO_PPI_TIMER_STOP) | + (1 << NRF52_RADIO_PPI_RX_TIMEOUT); + + reset_fifo(); + + memset(rx_pipe_info, 0, sizeof(rx_pipe_info)); + memset(pids, 0, sizeof(pids)); + + // Disable the radio + NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Enabled << RADIO_SHORTS_READY_START_Pos | + RADIO_SHORTS_END_DISABLE_Enabled << RADIO_SHORTS_END_DISABLE_Pos; + + nvicDisableVector(RADIO_IRQn); + + // Terminate interrupts handle thread + chThdTerminate(rfIntThread_p); + chBSemSignal(&disable_sem); + chThdWait(rfIntThread_p); + + // Terminate events handle thread + chThdTerminate(rfEvtThread_p); + RFD1.flags = 0; + chBSemSignal(&events_sem); + chThdWait(rfEvtThread_p); + + RFD1.state = NRF52_STATE_UNINIT; + + return NRF52_SUCCESS; +} + +// +nrf52_error_t radio_init(nrf52_config_t const *config) { + osalDbgAssert(config != NULL, + "config must be defined"); + osalDbgAssert(&config->address != NULL, + "address must be defined"); + osalDbgAssert(NRF52_RADIO_IRQ_PRIORITY <= 7, + "wrong radio irq priority"); + + if (RFD1.state != NRF52_STATE_UNINIT) { + nrf52_error_t err = radio_disable(); + if (err != NRF52_SUCCESS) + return err; + } + + RFD1.radio = NRF_RADIO; + RFD1.config = *config; + RFD1.flags = 0; + + init_fifo(); + +#if NRF52_RADIO_USE_TIMER0 + RFD1.timer = NRF_TIMER0; +#endif +#if NRF52_RADIO_USE_TIMER1 + RFD1.timer = NRF_TIMER1; +#endif +#if NRF52_RADIO_USE_TIMER2 + RFD1.timer = NRF_TIMER2; +#endif +#if NRF52_RADIO_USE_TIMER3 + RFD1.timer = NRF_TIMER3; +#endif +#if NRF52_RADIO_USE_TIMER4 + RFD1.timer = NRF_TIMER4; +#endif + + set_parameters(&RFD1); + + set_addresses(&RFD1, NRF52_ADDR_UPDATE_MASK_BASE0); + set_addresses(&RFD1, NRF52_ADDR_UPDATE_MASK_BASE1); + set_addresses(&RFD1, NRF52_ADDR_UPDATE_MASK_PREFIX); + + ppi_init(&RFD1); + timer_init(&RFD1); + + chBSemObjectInit(&disable_sem, TRUE); + chBSemObjectInit(&events_sem, TRUE); + + chEvtObjectInit(&RFD1.eventsrc); + + // interrupt handle thread + rfIntThread_p = chThdCreateStatic(waRFIntThread, sizeof(waRFIntThread), + NRF52_RADIO_INTTHD_PRIORITY, rfIntThread, NULL); + + // events handle thread + rfEvtThread_p = chThdCreateStatic(waRFEvtThread, sizeof(waRFEvtThread), + NRF52_RADIO_EVTTHD_PRIORITY, rfEvtThread, NULL); + + nvicEnableVector(RADIO_IRQn, NRF52_RADIO_IRQ_PRIORITY); + + RFD1.state = NRF52_STATE_IDLE; + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_write_payload(nrf52_payload_t const * p_payload) { + if (RFD1.state == NRF52_STATE_UNINIT) + return NRF52_INVALID_STATE; + if(p_payload == NULL) + return NRF52_ERROR_NULL; + VERIFY_PAYLOAD_LENGTH(p_payload); + if (tx_fifo.count >= NRF52_TX_FIFO_SIZE) + return NRF52_ERROR_INVALID_LENGTH; + + if (RFD1.config.mode == NRF52_MODE_PTX && + p_payload->noack && !RFD1.config.selective_auto_ack ) + { + return NRF52_ERROR_NOT_SUPPORTED; + } + + nvicDisableVector(RADIO_IRQn); + + memcpy(tx_fifo.p_payload[tx_fifo.entry_point], p_payload, sizeof(nrf52_payload_t)); + + pids[p_payload->pipe] = (pids[p_payload->pipe] + 1) % (NRF52_PID_MAX + 1); + tx_fifo.p_payload[tx_fifo.entry_point]->pid = pids[p_payload->pipe]; + + if (++tx_fifo.entry_point >= NRF52_TX_FIFO_SIZE) { + tx_fifo.entry_point = 0; + } + + tx_fifo.count++; + + nvicEnableVector(RADIO_IRQn, NRF52_RADIO_IRQ_PRIORITY); + + if (RFD1.config.mode == NRF52_MODE_PTX && + RFD1.config.tx_mode == NRF52_TXMODE_AUTO && + RFD1.state == NRF52_STATE_IDLE) + { + start_tx_transaction(&RFD1); + } + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_read_rx_payload(nrf52_payload_t * p_payload) { + if (RFD1.state == NRF52_STATE_UNINIT) + return NRF52_INVALID_STATE; + if (p_payload == NULL) + return NRF52_ERROR_NULL; + + if (rx_fifo.count == 0) { + return NRF52_ERROR_INVALID_LENGTH; + } + + nvicDisableVector(RADIO_IRQn); + + p_payload->length = rx_fifo.p_payload[rx_fifo.exit_point]->length; + p_payload->pipe = rx_fifo.p_payload[rx_fifo.exit_point]->pipe; + p_payload->rssi = rx_fifo.p_payload[rx_fifo.exit_point]->rssi; + p_payload->pid = rx_fifo.p_payload[rx_fifo.exit_point]->pid; + memcpy(p_payload->data, rx_fifo.p_payload[rx_fifo.exit_point]->data, p_payload->length); + + if (++rx_fifo.exit_point >= NRF52_RX_FIFO_SIZE) { + rx_fifo.exit_point = 0; + } + + rx_fifo.count--; + + nvicEnableVector(RADIO_IRQn, NRF52_RADIO_IRQ_PRIORITY); + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_start_tx(void) { + if (RFD1.state != NRF52_STATE_IDLE) + return NRF52_ERROR_BUSY; + + if (tx_fifo.count == 0) { + return NRF52_ERROR_INVALID_LENGTH; + } + + start_tx_transaction(&RFD1); + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_start_rx(void) { + if (RFD1.state != NRF52_STATE_IDLE) + return NRF52_ERROR_BUSY; + + NRF_RADIO->INTENCLR = 0xFFFFFFFF; + NRF_RADIO->EVENTS_DISABLED = 0; + (void) NRF_RADIO->EVENTS_DISABLED; + + NRF_RADIO->SHORTS = RADIO_SHORTS_COMMON | RADIO_SHORTS_DISABLED_TXEN_Msk; + NRF_RADIO->INTENSET = RADIO_INTENSET_DISABLED_Msk; + RFD1.state = NRF52_STATE_PRX; + + NRF_RADIO->RXADDRESSES = RFD1.config.address.rx_pipes; + NRF_RADIO->FREQUENCY = RFD1.config.address.rf_channel; + NRF_RADIO->PACKETPTR = (uint32_t)rx_payload_buffer; + + nvicClearPending(RADIO_IRQn); + nvicEnableVector(RADIO_IRQn, NRF52_RADIO_IRQ_PRIORITY); + + NRF_RADIO->EVENTS_ADDRESS = 0; + NRF_RADIO->EVENTS_PAYLOAD = 0; + NRF_RADIO->EVENTS_DISABLED = 0; + (void) NRF_RADIO->EVENTS_ADDRESS; + (void) NRF_RADIO->EVENTS_PAYLOAD; + (void) NRF_RADIO->EVENTS_DISABLED; + + NRF_RADIO->TASKS_RXEN = 1; + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_stop_rx(void) { + if (RFD1.state != NRF52_STATE_PRX) { + return NRF52_INVALID_STATE; + } + + NRF_RADIO->SHORTS = 0; + NRF_RADIO->INTENCLR = 0xFFFFFFFF; + NRF_RADIO->EVENTS_DISABLED = 0; + (void) NRF_RADIO->EVENTS_DISABLED; + NRF_RADIO->TASKS_DISABLE = 1; + while (NRF_RADIO->EVENTS_DISABLED == 0); + RFD1.state = NRF52_STATE_IDLE; + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_flush_tx(void) { + if (RFD1.state == NRF52_STATE_UNINIT) + return NRF52_INVALID_STATE; + + nvicDisableVector(RADIO_IRQn); + + tx_fifo.count = 0; + tx_fifo.entry_point = 0; + tx_fifo.exit_point = 0; + + nvicEnableVector(RADIO_IRQn, NRF52_RADIO_IRQ_PRIORITY); + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_pop_tx(void) { + if (RFD1.state == NRF52_STATE_UNINIT) + return NRF52_INVALID_STATE; + if (tx_fifo.count == 0) + return NRF52_ERROR_INVALID_LENGTH; + + nvicDisableVector(RADIO_IRQn); + + if (++tx_fifo.entry_point >= NRF52_TX_FIFO_SIZE) { + tx_fifo.entry_point = 0; + } + tx_fifo.count--; + + nvicEnableVector(RADIO_IRQn, NRF52_RADIO_IRQ_PRIORITY); + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_flush_rx(void) { + if (RFD1.state == NRF52_STATE_UNINIT) + return NRF52_INVALID_STATE; + + nvicDisableVector(RADIO_IRQn); + + rx_fifo.count = 0; + rx_fifo.entry_point = 0; + rx_fifo.exit_point = 0; + + memset(rx_pipe_info, 0, sizeof(rx_pipe_info)); + + nvicEnableVector(RADIO_IRQn, NRF52_RADIO_IRQ_PRIORITY); + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_set_base_address_0(uint8_t const * p_addr) { + if (RFD1.state != NRF52_STATE_IDLE) + return NRF52_ERROR_BUSY; + if (p_addr == NULL) + return NRF52_ERROR_NULL; + + memcpy(RFD1.config.address.base_addr_p0, p_addr, 4); + set_addresses(&RFD1, NRF52_ADDR_UPDATE_MASK_BASE0); + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_set_base_address_1(uint8_t const * p_addr) { + if (RFD1.state != NRF52_STATE_IDLE) + return NRF52_ERROR_BUSY; + if (p_addr == NULL) + return NRF52_ERROR_NULL; + + memcpy(RFD1.config.address.base_addr_p1, p_addr, 4); + set_addresses(&RFD1, NRF52_ADDR_UPDATE_MASK_BASE1); + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_set_prefixes(uint8_t const * p_prefixes, uint8_t num_pipes) { + if (RFD1.state != NRF52_STATE_IDLE) + return NRF52_ERROR_BUSY; + if (p_prefixes == NULL) + return NRF52_ERROR_NULL; + if (num_pipes > 8) + return NRF52_ERROR_INVALID_PARAM; + + memcpy(RFD1.config.address.pipe_prefixes, p_prefixes, num_pipes); + RFD1.config.address.num_pipes = num_pipes; + RFD1.config.address.rx_pipes = BIT_MASK_UINT_8(num_pipes); + + set_addresses(&RFD1, NRF52_ADDR_UPDATE_MASK_PREFIX); + + return NRF52_SUCCESS; +} + +nrf52_error_t radio_set_prefix(uint8_t pipe, uint8_t prefix) { + if (RFD1.state != NRF52_STATE_IDLE) + return NRF52_ERROR_BUSY; + if (pipe > 8) + return NRF52_ERROR_INVALID_PARAM; + + RFD1.config.address.pipe_prefixes[pipe] = prefix; + + NRF_RADIO->PREFIX0 = bytewise_bit_swap(&RFD1.config.address.pipe_prefixes[0]); + NRF_RADIO->PREFIX1 = bytewise_bit_swap(&RFD1.config.address.pipe_prefixes[4]); + + return NRF52_SUCCESS; +} diff --git a/os/various/devices_lib/rf/nrf52_radio.h b/os/various/devices_lib/rf/nrf52_radio.h new file mode 100644 index 0000000..2f94465 --- /dev/null +++ b/os/various/devices_lib/rf/nrf52_radio.h @@ -0,0 +1,256 @@ +/* Copyright (c) 2014 Nordic Semiconductor. All Rights Reserved. + * + * The information contained herein is property of Nordic Semiconductor ASA. + * Terms and conditions of usage are described in detail in NORDIC + * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. + * + * Licensees are granted free, non-transferable use of the information. NO + * WARRANTY of ANY KIND is provided. This heading must NOT be removed from + * the file. + * + * @brief Enhanced ShockBurst (ESB) is a basic protocol supporting two-way data + * packet communication including packet buffering, packet acknowledgment + * and automatic retransmission of lost packets. + * + * ported on: 25/10/2018, by andru + * + */ + +#ifndef NRF52_RADIO_H_ +#define NRF52_RADIO_H_ + +// Hard coded parameters - change if necessary +#ifndef NRF52_MAX_PAYLOAD_LENGTH +#define NRF52_MAX_PAYLOAD_LENGTH 32 /**< The max size of the payload. Valid values are 1 to 252 */ +#endif + +#define NRF52_CRC_RESET_VALUE 0xFFFF /**< CRC reset value*/ + +#define NRF52_TX_FIFO_SIZE 8 /**< The size of the transmission first in first out buffer. */ +#define NRF52_RX_FIFO_SIZE 8 /**< The size of the reception first in first out buffer. */ + +#define NRF52_RADIO_USE_TIMER0 FALSE /**< TIMER0 will be used by the module. */ +#define NRF52_RADIO_USE_TIMER1 TRUE /**< TIMER1 will be used by the module. */ +#define NRF52_RADIO_USE_TIMER2 FALSE /**< TIMER2 will be used by the module. */ +#define NRF52_RADIO_USE_TIMER3 FALSE /**< TIMER3 will be used by the module. */ +#define NRF52_RADIO_USE_TIMER4 FALSE /**< TIMER4 will be used by the module. */ + +#define NRF52_RADIO_IRQ_PRIORITY 3 /**< RADIO interrupt priority. */ +#define NRF52_RADIO_INTTHD_PRIORITY (NORMALPRIO+2) /**< Interrupts handle thread priority. */ +#define NRF52_RADIO_EVTTHD_PRIORITY (NORMALPRIO+1) /**< Events handle thread priority */ + +#define NRF52_RADIO_PPI_TIMER_START 10 /**< The PPI channel used for timer start. */ +#define NRF52_RADIO_PPI_TIMER_STOP 11 /**< The PPI channel used for timer stop. */ +#define NRF52_RADIO_PPI_RX_TIMEOUT 12 /**< The PPI channel used for RX timeout. */ +#define NRF52_RADIO_PPI_TX_START 13 /**< The PPI channel used for starting TX. */ + + +typedef enum { + NRF52_SUCCESS, /* Call was successful. */ + NRF52_INVALID_STATE, /* Module is not initialized. */ + NRF52_ERROR_BUSY, /* Module was not in idle state. */ + NRF52_ERROR_NULL, /* Required parameter was NULL. */ + NRF52_ERROR_INVALID_PARAM, /* Required parameter is invalid */ + NRF52_ERROR_NOT_SUPPORTED, /* p_payload->noack was false while selective ack was not enabled. */ + NRF52_ERROR_INVALID_LENGTH, /* Payload length was invalid (zero or larger than max allowed). */ +} nrf52_error_t; + +// Internal radio module state. +typedef enum { + NRF52_STATE_UNINIT, /**< Module not initialized. */ + NRF52_STATE_IDLE, /**< Module idle. */ + NRF52_STATE_PTX_TX, /**< Module transmitting without ack. */ + NRF52_STATE_PTX_TX_ACK, /**< Module transmitting with ack. */ + NRF52_STATE_PTX_RX_ACK, /**< Module transmitting with ack and reception of payload with the ack response. */ + NRF52_STATE_PRX, /**< Module receiving packets without ack. */ + NRF52_STATE_PRX_SEND_ACK, /**< Module transmitting ack in RX mode. */ +} nrf52_state_t; + +/**@brief Events to indicate the last transmission/receiving status. */ +typedef enum { + NRF52_EVENT_TX_SUCCESS = 0x01, /**< Event triggered on TX success. */ + NRF52_EVENT_TX_FAILED = 0x02, /**< Event triggered on TX failed. */ + NRF52_EVENT_RX_RECEIVED = 0x04, /**< Event triggered on RX Received. */ +} nrf52_event_t; + +// Interrupt flags +typedef enum { + NRF52_INT_TX_SUCCESS_MSK = 0x01, /**< The flag used to indicate a success since last event. */ + NRF52_INT_TX_FAILED_MSK = 0x02, /**< The flag used to indicate a failiure since last event. */ + NRF52_INT_RX_DR_MSK = 0x04, /**< The flag used to indicate a received packet since last event. */ +} nrf52_int_flags_t; + +/**Macro to create initializer for a TX data packet. + * + * @details This macro generates an initializer. It is more efficient + * than setting the individual parameters dynamically. + * + * @param[in] _pipe The pipe to use for the data packet. + * @param[in] ... Comma separated list of character data to put in the TX buffer. + * Supported values are from 1 to 63 characters. + * + * @return Initializer that sets up pipe, length and the byte array for content of the TX data. + */ +#define NRF52_CREATE_PAYLOAD(_pipe, ...) \ + {.pipe = _pipe, .length = NUM_VA_ARGS(__VA_ARGS__), .data = {__VA_ARGS__}}; \ + STATIC_ASSERT(NUM_VA_ARGS(__VA_ARGS__) > 0 && NUM_VA_ARGS(__VA_ARGS__) <= 63) + +/**@brief Enhanced ShockBurst protocol. */ +typedef enum { + NRF52_PROTOCOL_ESB, /*< Enhanced ShockBurst with fixed payload length. */ + NRF52_PROTOCOL_ESB_DPL /*< Enhanced ShockBurst with dynamic payload length. */ +} nrf52_protocol_t; + +/**@brief Enhanced ShockBurst mode. */ +typedef enum { + NRF52_MODE_PTX, /*< Primary transmitter mode. */ + NRF52_MODE_PRX /*< Primary receiver mode. */ +} nrf52_mode_t; + +/**@brief Enhanced ShockBurst bitrate mode. */ +typedef enum { + NRF52_BITRATE_2MBPS = RADIO_MODE_MODE_Nrf_2Mbit, /**< 2Mbit radio mode. */ + NRF52_BITRATE_1MBPS = RADIO_MODE_MODE_Nrf_1Mbit, /**< 1Mbit radio mode. */ +} nrf52_bitrate_t; + +/**@brief Enhanced ShockBurst CRC modes. */ +typedef enum { + NRF52_CRC_16BIT = RADIO_CRCCNF_LEN_Two, /**< Use two byte CRC. */ + NRF52_CRC_8BIT = RADIO_CRCCNF_LEN_One, /**< Use one byte CRC. */ + NRF52_CRC_OFF = RADIO_CRCCNF_LEN_Disabled /**< Disable CRC. */ +} nrf52_crc_t; + +/**@brief Enhanced ShockBurst radio transmission power modes. */ +typedef enum { + NRF52_TX_POWER_4DBM = RADIO_TXPOWER_TXPOWER_Pos4dBm, /**< 4 dBm radio transmit power. */ + NRF52_TX_POWER_0DBM = RADIO_TXPOWER_TXPOWER_0dBm, /**< 0 dBm radio transmit power. */ + NRF52_TX_POWER_NEG4DBM = RADIO_TXPOWER_TXPOWER_Neg4dBm, /**< -4 dBm radio transmit power. */ + NRF52_TX_POWER_NEG8DBM = RADIO_TXPOWER_TXPOWER_Neg8dBm, /**< -8 dBm radio transmit power. */ + NRF52_TX_POWER_NEG12DBM = RADIO_TXPOWER_TXPOWER_Neg12dBm, /**< -12 dBm radio transmit power. */ + NRF52_TX_POWER_NEG16DBM = RADIO_TXPOWER_TXPOWER_Neg16dBm, /**< -16 dBm radio transmit power. */ + NRF52_TX_POWER_NEG20DBM = RADIO_TXPOWER_TXPOWER_Neg20dBm, /**< -20 dBm radio transmit power. */ + NRF52_TX_POWER_NEG30DBM = RADIO_TXPOWER_TXPOWER_Neg30dBm /**< -30 dBm radio transmit power. */ +} nrf52_tx_power_t; + +/**@brief Enhanced ShockBurst transmission modes. */ +typedef enum { + NRF52_TXMODE_AUTO, /*< Automatic TX mode - When the TX fifo is non-empty and the radio is idle packets will be sent automatically. */ + NRF52_TXMODE_MANUAL, /*< Manual TX mode - Packets will not be sent until radio_start_tx() is called. Can be used to ensure consistent packet timing. */ + NRF52_TXMODE_MANUAL_START /*< Manual start TX mode - Packets will not be sent until radio_start_tx() is called, but transmission will continue automatically until the TX fifo is empty. */ +} nrf52_tx_mode_t; + +/**@brief Enhanced ShockBurst addresses. + * + * @details The module is able to transmit packets with the TX address stored in tx_address. + The module can also receive packets from peers with up to eight different tx_addresses + stored in esb_addr_p0 - esb_addr_p7. esb_addr_p0 can have 5 arbitrary bytes + independent of the other addresses. esb_addr_p1 - esb_addr_p7 will share the + same four byte base address found in the last four bytes of esb_addr_p1. + They have an independent prefix byte found in esb_addr_p1[0] and esb_addr_p2 - + esb_addr_p7. +*/ +typedef struct { + uint8_t base_addr_p0[4]; /**< Base address for pipe 0 encoded in big endian. */ + uint8_t base_addr_p1[4]; /**< Base address for pipe 1-7 encoded in big endian. */ + uint8_t pipe_prefixes[8]; /**< Address prefix for pipe P0 to P7. */ + uint8_t num_pipes; /**< Number of pipes available. */ + uint8_t addr_length; /**< Length of address including prefix */ + uint8_t rx_pipes; /**< Bitfield for enabled RX pipes. */ + uint8_t rf_channel; /**< Which channel is to be used. Must be in range 0 and 125 to be valid. */ +} nrf52_address_t; + +/**@brief Enhanced ShockBurst payload. + * + * @note The payload is used both for transmission and receive with ack and payload. +*/ +typedef struct +{ + uint8_t length; /**< Length of the packet. Should be equal or less than NRF_ESB_MAX_PAYLOAD_LENGTH. */ + uint8_t pipe; /**< Pipe used for this payload. */ + int8_t rssi; /**< RSSI for received packet. */ + uint8_t noack; /**< Flag indicating that this packet will not be acknowledged. */ + uint8_t pid; /**< PID assigned during communication. */ + uint8_t data[NRF52_MAX_PAYLOAD_LENGTH]; /**< The payload data. */ +} nrf52_payload_t; + +/**@brief Retransmit attempts delay and counter. */ +typedef struct { + uint16_t delay; /**< The delay between each retransmission of unacked packets. */ + uint16_t count; /**< The number of retransmissions attempts before transmission fail. */ +} nrf52_retransmit_t; + +/**@brief Main nrf_esb configuration struct. */ +typedef struct { + nrf52_protocol_t protocol; /**< Enhanced ShockBurst protocol. */ + nrf52_mode_t mode; /**< Enhanced ShockBurst default RX or TX mode. */ + + // General RF parameters + nrf52_bitrate_t bitrate; /**< Enhanced ShockBurst bitrate mode. */ + nrf52_crc_t crc; /**< Enhanced ShockBurst CRC mode. */ + nrf52_tx_power_t tx_power; /**< Enhanced ShockBurst radio transmission power mode.*/ + + // Control settings + nrf52_tx_mode_t tx_mode; /**< Enhanced ShockBurst transmit mode. */ + + bool selective_auto_ack; /**< Enable or disable selective auto acknowledgement. */ + + nrf52_retransmit_t retransmit; /**< Packet retransmit parameters */ + + uint8_t payload_length; /**< Enhanced ShockBurst static payload length */ + + nrf52_address_t address; /**< Address parameters structure */ +} nrf52_config_t; + +typedef struct { + /** + * @brief NRF52 radio peripheral. + */ + NRF_RADIO_Type *radio; + /** + * @brief NRF52 timer peripheral. + */ + NRF_TIMER_Type *timer; + /** + * @brief Driver state. + */ + nrf52_state_t state; + /** + * @brief RF parameters. + */ + nrf52_config_t config; + /** + * @brief Interrupts flag. + */ + nrf52_int_flags_t flags; + /** + * @brief TX attempt number. + */ + uint16_t tx_attempt; + /** + * @brief TX retransmits remaining. + */ + uint16_t tx_remaining; + /** + * @brief Radio events source. + */ + event_source_t eventsrc; +} RFDriver; + +extern RFDriver RFD1; + +nrf52_error_t radio_init(nrf52_config_t const *config); +nrf52_error_t radio_disable(void); +nrf52_error_t radio_write_payload(nrf52_payload_t const * p_payload); +nrf52_error_t radio_read_rx_payload(nrf52_payload_t * p_payload); +nrf52_error_t radio_start_tx(void); +nrf52_error_t radio_start_rx(void); +nrf52_error_t radio_stop_rx(void); +nrf52_error_t radio_flush_tx(void); +nrf52_error_t radio_flush_rx(void); +nrf52_error_t radio_pop_tx(void); +nrf52_error_t radio_set_base_address_0(uint8_t const * p_addr); +nrf52_error_t radio_set_base_address_1(uint8_t const * p_addr); +nrf52_error_t radio_set_prefixes(uint8_t const * p_prefixes, uint8_t num_pipes); +nrf52_error_t radio_set_prefix(uint8_t pipe, uint8_t prefix); + +#endif /* NRF52_RADIO_H_ */ -- cgit v1.2.3