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