text: // *****************************************************************************
  // * occupancy-pyd1698-efr32.c
  // *
  // * API for interfacing with a PYD-1698 occupancy sensor
  // *
  // * Copyright 2015 Silicon Laboratories, Inc.                              *80*
  // *****************************************************************************
  
  #include PLATFORM_HEADER
  #include "stack/include/ember-types.h"
  #include "stack/include/event.h"
  #include "hal/hal.h"
  #include "hal/micro/micro.h"
  #include "em_gpio.h"
  #include "em_core.h"
  #include "em_cmu.h"
  #include "gpiointerrupt.h"
  #include "occupancy-pyd1698.h"
  #include EMBER_AF_API_GENERIC_INTERRUPT_CONTROL
  #include EMBER_AF_API_OCCUPANCY
  
  //------------------------------------------------------------------------------
  // Plugin private macros
  
  // helper macros, for shortening long plugin option macros
  // Some of these have conversion addition or subtractions.  The plugin options
  // are meant to control the resulting behavior, not the value that needs to be
  // written to the corresponding register.  For example, the blind time register
  // accepts 0..15, but corresponds to 1..16 half seconds, so the macro for
  // DEFAULT_BLIND_TIME_HS subtracts 1 from the user entered plugin value
  #define DEFAULT_THRESHOLD       EMBER_AF_PLUGIN_OCCUPANCY_PYD1698_THRESHOLD
  #define DEFAULT_BLIND_TIME_HS \
            (EMBER_AF_PLUGIN_OCCUPANCY_PYD1698_BLIND_TIME - 1)
  #define DEFAULT_WINDOW_TIME     EMBER_AF_PLUGIN_OCCUPANCY_PYD1698_WINDOW_TIME
  #define DEFAULT_PULSE_COUNTER \
            (EMBER_AF_PLUGIN_OCCUPANCY_PYD1698_PULSE_COUNTER - 1)
  #define DEFAULT_FILTER          EMBER_AF_PLUGIN_OCCUPANCY_PYD1698_FILTER_SOURCE
  #define DEFAULT_MODE            EMBER_AF_PLUGIN_OCCUPANCY_PYD1698_OPERATION_MODE
  #define OCCUPANCY_TIMEOUT_MINUTES \
            EMBER_AF_PLUGIN_OCCUPANCY_PYD1698_OCCUPANCY_TIMEOUT
  #define OCCUPANCY_CALIBRATION_TIMEOUT_S \
            EMBER_AF_PLUGIN_OCCUPANCY_PYD1698_OCCUPANCY_CALIBRATION_TIMEOUT
  #define FIRMWARE_BLIND_TIME_M \
            EMBER_AF_PLUGIN_OCCUPANCY_PYD1698_FIRMWARE_BLIND_TIME
  #define SENSOR_WARMUP_TIME_QS   (4*5)
  
  // Pin configuration checks.  These should be defined in the board.h file, but
  // default definitions are provided here to match the IST-A78 reference design
  #ifndef OCCUPANCY_PYD1698_SERIN_PORT
  #error "OCCUPANCY_PYD1698_SERIN_PORT not defined in board.h.  Select a port by defining that macro to something like gpioPortF"
  #endif
  
  #ifndef OCCUPANCY_PYD1698_SERIN_PIN
  #error "OCCUPANCY_PYD1698_SERIN_PIN not defined in board.h.  Select a port by defining that macro to something like 4"
  #define OCCUPANCY_PYD1698_SERIN_PIN   4
  #endif
  
  #ifndef OCCUPANCY_PYD1698_DLINK_PORT
  #error "OCCUPANCY_PYD1698_DLINK_PORT not defined in board.h.  Select a port by defining that macro to something like gpioPortF"
  #endif
  
  #ifndef OCCUPANCY_PYD1698_DLINK_PIN
  #error "OCCUPANCY_PYD1698_DLINK_PIN not defined in board.h.  Select a port by defining that macro to something like 7"
  #endif
  
  #ifndef OCCUPANCY_PYD1698_INSTALLATION_JP_PORT
  #error "OCCUPANCY_PYD1698_INSTALLATION_JP_PORT not defined in board.h.  Select a port by defining that macro to something like gpioPortB"
  #endif
  
  #ifndef OCCUPANCY_PYD1698_INSTALLATION_JP_PIN
  #error "OCCUPANCY_PYD1698_INSTALLATION_JP_PIN not defined in board.h.  Select a port by defining that macro to something like 11"
  #endif
  
  // This driver requires a substantial amount of bitbanging with very precise
  // timing windows.  Unfortunately, the emlib GPIO functions introduce too much
  // delay to be effective, and as such register macros with precalculated values
  // will be used to control all gpio setting and reading during SERIN and DLINK
  // transactions.
  #define DLINK_DOUT_REG (&GPIO->P[OCCUPANCY_PYD1698_DLINK_PORT].DOUT)
  #define DLINK_DIN_REG (&GPIO->P[OCCUPANCY_PYD1698_DLINK_PORT].DIN)
  #define SERIN_DOUT_REG (&GPIO->P[OCCUPANCY_PYD1698_SERIN_PORT].DOUT)
  #define SERIN_DIN_REG (&GPIO->P[OCCUPANCY_PYD1698_SERIN_PORT].DIN)
  
  #if OCCUPANCY_PYD1698_DLINK_PIN < 8
  #define DLINK_MODE_REG  (&GPIO->P[OCCUPANCY_PYD1698_DLINK_PORT].MODEL)
  #define DLINK_MODE_SHIFT (OCCUPANCY_PYD1698_DLINK_PIN * 4)
  #else
  #define DLINK_MODE_REG (&GPIO->P[OCCUPANCY_PYD1698_DLINK_PORT].MODEH)
  #define DLINK_MODE_SHIFT ((OCCUPANCY_PYD1698_DLINK_PIN - 8) * 4)
  #endif
  #define DLINK_MODE_MASK (0xF << DLINK_MODE_SHIFT)
  #define DLINK_MODE_IN   (_GPIO_P_MODEL_MODE0_INPUT << DLINK_MODE_SHIFT)
  #define DLINK_MODE_OUT  (_GPIO_P_MODEL_MODE0_PUSHPULL << DLINK_MODE_SHIFT)
  
  #if OCCUPANCY_PYD1698_SERIN_PIN < 8
  #define SERIN_MODE_REG  (&GPIO->P[OCCUPANCY_PYD1698_SERIN_PORT].MODEL)
  #define SERIN_MODE_SHIFT (OCCUPANCY_PYD1698_SERIN_PIN * 4)
  #else
  #define SERIN_MODE_REG (&GPIO->P[OCCUPANCY_PYD1698_SERIN_PORT].MODEH)
  #define SERIN_MODE_SHIFT ((OCCUPANCY_PYD1698_SERIN_PIN - 8) * 4)
  #endif
  #define SERIN_MODE_MASK (0xF << SERIN_MODE_SHIFT)
  #define SERIN_MODE_IN   (_GPIO_P_MODEL_MODE0_INPUT << SERIN_MODE_SHIFT)
  #define SERIN_MODE_OUT  (_GPIO_P_MODEL_MODE0_PUSHPULL << SERIN_MODE_SHIFT)
  
  //------------------------------------------------------------------------------
  // Plugin events
  EmberEventControl emberAfPluginOccupancyPyd1698NotifyEventControl;
  EmberEventControl emberAfPluginOccupancyPyd1698OccupancyTimeoutEventControl;
  EmberEventControl emberAfPluginOccupancyPyd1698FirmwareBlindTimeoutEventControl;
  EmberEventControl emberAfPluginOccupancyPyd1698InitEventControl;
  
  //------------------------------------------------------------------------------
  // Forward declaration of private functions
  static uint32_t cfgMsgToData(HalPydCfg_t *cfgMsg);
  static void ackPydPin(uint32_t volatile * port, uint8_t pin);
  static bool handleJumperChange(bool isInit);
  static void clearInterupt(void);
  static void interruptIsr(void);
  
  //------------------------------------------------------------------------------
  // Private global variables
  
  // structure used to track configuration data for sensor
  static HalPydCfg_t currentPydCfg;
  
  static HalGenericInterruptControlIrqCfg *irqCfg;
  
  static bool initialized = false;
  
  //------------------------------------------------------------------------------
  // Plugin public APIs, callbacks, and event handlers
  
  // Configure the GIC to call the occupancySensedEvent on IRQ from the PYD
  // Send an initial configuration message to the occupancy sensor
  void halOccupancyInit(void)
  {
    // Set up GPIO pins as inputs
    GPIO_PinModeSet(OCCUPANCY_PYD1698_INSTALLATION_JP_PORT,
                OCCUPANCY_PYD1698_INSTALLATION_JP_PIN,
                gpioModeInput,
                0);
    GPIO_PinModeSet(OCCUPANCY_PYD1698_SERIN_PORT,
                    OCCUPANCY_PYD1698_SERIN_PIN,
                    gpioModePushPull,
                    0);
    GPIO_PinModeSet(OCCUPANCY_PYD1698_DLINK_PORT,
                    OCCUPANCY_PYD1698_DLINK_PIN,
                    gpioModeInput,
                    0);
    GPIO_PinOutClear(OCCUPANCY_PYD1698_SERIN_PORT, OCCUPANCY_PYD1698_SERIN_PIN);
  
    currentPydCfg.reserved = PYD_CONFIG_RESERVED_VALUE;
    currentPydCfg.filterSource = DEFAULT_FILTER;
    currentPydCfg.operationMode = DEFAULT_MODE;
    currentPydCfg.windowTime = DEFAULT_WINDOW_TIME;
    currentPydCfg.pulseCounter = DEFAULT_PULSE_COUNTER;
    currentPydCfg.blindTime = DEFAULT_BLIND_TIME_HS;
    currentPydCfg.sensitivity = DEFAULT_THRESHOLD;
  
    // The sensor tends to provide false positives during the first ~5 seconds of
    // operation, so a short delay should be added between setting up the device
    // and honoring occupancy detect events
    clearInterupt();
    handleJumperChange(true);
  
    // Set up GIC to interrupt on high level of DATALINK pin
    irqCfg = halGenericInterruptControlIrqCfgInitialize(
               OCCUPANCY_PYD1698_DLINK_PIN,
               OCCUPANCY_PYD1698_DLINK_PORT,
               OCCUPANCY_PYD1698_DLINK_EM4_PIN);
    halGenericInterruptControlIrqIsrAssignFxn(irqCfg, &interruptIsr);
    halGenericInterruptControlIrqEdgeConfig(irqCfg, HAL_GIC_INT_CFG_LEVEL_POS);
    halGenericInterruptControlIrqClear(irqCfg);
    halGenericInterruptControlIrqEnable(irqCfg);
  
    emberEventControlSetDelayQS(emberAfPluginOccupancyPyd1698InitEventControl,
                                SENSOR_WARMUP_TIME_QS);
  }
  
  void emberAfPluginOccupancyPyd1698InitEventHandler(void)
  {
    emberEventControlSetInactive(emberAfPluginOccupancyPyd1698InitEventControl);
    initialized = true;
  }
  
  // This event will be called by the GIC plugin whenever an interrupt is detected
  // from the occupancy sensor.
  void emberAfPluginOccupancyPyd1698NotifyEventHandler(void)
  {
    emberEventControlSetInactive(
      emberAfPluginOccupancyPyd1698NotifyEventControl);
  
    if (!initialized) {
      return;
    }
  
    halOccupancyStateChangedCallback(HAL_OCCUPANCY_STATE_OCCUPIED);
  
    if (handleJumperChange(false) == true) {
      // Ensure that the device doesn't remain in firmware blind mode when the
      // installation mode jumper is in place
      halGenericInterruptControlIrqEnable(irqCfg);
      emberEventControlSetInactive(
        emberAfPluginOccupancyPyd1698FirmwareBlindTimeoutEventControl);
    } else {
      // If the jumper is not in place, enter the firmware blind mode
      halGenericInterruptControlIrqDisable(irqCfg);
      emberEventControlSetDelayMinutes(
        emberAfPluginOccupancyPyd1698FirmwareBlindTimeoutEventControl,
        FIRMWARE_BLIND_TIME_M);
    }
  }
  
  // This event is activated at the end of the firmware blind time.  It must
  // determine if an occupancy event occurred during that blind time and act
  // accordingly.  The firmware blind is the amount of time (in minutes) during
  // which the firmware will ignore interrupts from the occupancy sensor.
  // This can be used to generate a large blind window than the 16 seconds
  // available through hardware, which allows the MCU to sleep more and thus use
  // less battery.  Assign a value of 0 to disable the firmware blind time.
  void emberAfPluginOccupancyPyd1698FirmwareBlindTimeoutEventHandler(void)
  {
    emberEventControlSetInactive(
      emberAfPluginOccupancyPyd1698FirmwareBlindTimeoutEventControl);
  
    if (GPIO_PinInGet(OCCUPANCY_PYD1698_DLINK_PORT,
                      OCCUPANCY_PYD1698_DLINK_PIN)) {
      // If an occupancy event occurred while the device was sleeping, handle it.
      emberEventControlSetActive(
        emberAfPluginOccupancyPyd1698NotifyEventControl);
    } else {
      // Otherwise, enable interrupts, and the device will wake either the next
      // time the room is occupied or when enough time has passed for the room to
      // be considered unoccupied.
      halGenericInterruptControlIrqEnable(irqCfg);
    }
  }
  
  // This event is triggered when no occupancy events have occurred for a long
  // enough time that the room should now be considered unoccupied
  void emberAfPluginOccupancyPyd1698OccupancyTimeoutEventHandler(void)
  {
    emberEventControlSetInactive(
      emberAfPluginOccupancyPyd1698OccupancyTimeoutEventControl);
  
    halOccupancyStateChangedCallback(HAL_OCCUPANCY_STATE_UNOCCUPIED);
  }
  
  // The init callback, which will be called by the framework on init.
  void emberAfPluginOccupancyPyd1698InitCallback(void)
  {
    halOccupancyInit();
  }
  
  HalOccupancySensorType halOccupancyGetSensorType(void)
  {
    return HAL_OCCUPANCY_SENSOR_TYPE_PIR;
  }
  
  //------------------------------------------------------------------------------
  // Private plugin functions
  
  static void interruptIsr(void)
  {
    clearInterupt();
    emberEventControlSetActive(emberAfPluginOccupancyPyd1698NotifyEventControl);
  }
  
  // returns true if the jumper is set, false if the jumper is not set.  isInit
  // should be true if this is the first time the jumper is being queried.  This
  // will activate the occupancyTimeout event, update the static jumper state
  // variable, and write the new blind time if this is the first transition to a
  // new jumper state.
  static bool handleJumperChange(bool isInit)
  {
    static bool jumperInserted;
  
    // on initialization, the jumper will be assumed to be set
    if (isInit) {
      jumperInserted = true;
    }
  
    // pin will be low when jumper is in place (in installation mode)
    if (GPIO_PinInGet(OCCUPANCY_PYD1698_INSTALLATION_JP_PORT,
                  OCCUPANCY_PYD1698_INSTALLATION_JP_PIN)) {
      if ((jumperInserted == true) || (isInit == true)) {
        // if the jumper just transitioned from in place to not in place, reset
        // the blind time to the plugin option specified value, standard operation
        // mode.
        currentPydCfg.blindTime = DEFAULT_BLIND_TIME_HS;
        halOccupancyPyd1698WriteConfiguration(&currentPydCfg);
      }
      jumperInserted = false;
      emberEventControlSetDelayMinutes(
        emberAfPluginOccupancyPyd1698OccupancyTimeoutEventControl,
        OCCUPANCY_TIMEOUT_MINUTES);
    } else {
      if ((jumperInserted == false) || (isInit == true)) {
        // if the jumper just transition from not in place to in place, the user
        // has entered  installation mode, so the blind time should be set to its
        // minimum value
        currentPydCfg.blindTime = 0;
        halOccupancyPyd1698WriteConfiguration(&currentPydCfg);
      }
      jumperInserted = true;
      emberEventControlSetDelayQS(
        emberAfPluginOccupancyPyd1698OccupancyTimeoutEventControl,
        OCCUPANCY_CALIBRATION_TIMEOUT_S*4);
    }
    return jumperInserted;
  }
  
  void halOccupancyPyd1698WriteConfiguration(HalPydCfg_t *cfgMsg)
  {
    uint32_t outData;
    volatile uint8_t i;
    uint32_t outHi = *SERIN_DOUT_REG | (1 << OCCUPANCY_PYD1698_SERIN_PIN);
    uint32_t outLo = *SERIN_DOUT_REG & ~(1 << OCCUPANCY_PYD1698_SERIN_PIN);
  
    outData = cfgMsgToData(cfgMsg);
  
    // The timing windows are very tight in this section.  Adding tens of cycles
    // between GPIO transitions is enough to exceed device tolerances.  As such,
    // all interrupts will be disabled during this time sensitive bit banged
    // transaction
  ATOMIC(
    for (i=0; i<PYD_MESSAGE_WRITE_LENGTH_BITS; i++) {
      // Drive the SerIn pin hi for ~5 cycles, then low for ~5 cycles, informing
      // the PYD that the next bit is going to be written
      ackPydPin(SERIN_DOUT_REG, OCCUPANCY_PYD1698_SERIN_PIN);
  
      // Set hi or low based on the data to be written
      if (outData & (1 << (PYD_MESSAGE_WRITE_LENGTH_BITS - 1 - i))) {
        *SERIN_DOUT_REG = outHi;
      } else {
        *SERIN_DOUT_REG = outLo;
      }
  
      // Delay long enough for the PYD to read the bit, 72 uS
      halCommonDelayMicroseconds(PYD_MESSAGE_WRITE_BIT_DELAY_US);
    }
    *SERIN_DOUT_REG = outLo;
  )
  }
  
  void halOccupancyPyd1698Read(HalPydInMsg_t *readMsg)
  {
    uint8_t i;
    volatile int64u data = 0;
    uint32_t outCfg;
    uint32_t inCfg;
    uint32_t bitHiVal = *DLINK_DOUT_REG | (1 << OCCUPANCY_PYD1698_DLINK_PIN);
    uint32_t bitLoVal = *DLINK_DOUT_REG & ~(1 << OCCUPANCY_PYD1698_DLINK_PIN);
  
    outCfg = *DLINK_MODE_REG & ~DLINK_MODE_MASK;
    inCfg = outCfg | DLINK_MODE_IN;
    outCfg |= DLINK_MODE_OUT;
  
  
    halGenericInterruptControlIrqDisable(irqCfg);
    // To initiate a read, first reconfigure the interrupt/ser_in pin as an output
    // Then, drive it high for 110-150 uS.
    // Then, switch to an input, wait for the input to be driven by the PYD, and
    //   read the input bit
    // Finally, switch back to an output and drive the signal high for another
    //   110-150 uS, signalling to the PYD that the bit was received and it can
    //   transmit another.
  
    // Set the DLINK bit of the GPIO set register, to guarantee output will be
    // driven high once pin is enabled as output
    GPIO_PinOutSet(OCCUPANCY_PYD1698_DLINK_PORT, OCCUPANCY_PYD1698_DLINK_PIN);
  
  ATOMIC(
  
    // Set DLINK as output
    GPIO_PinModeSet(OCCUPANCY_PYD1698_DLINK_PORT, OCCUPANCY_PYD1698_DLINK_PIN, gpioModePushPull, 0);
    GPIO_PinOutSet(OCCUPANCY_PYD1698_DLINK_PORT, OCCUPANCY_PYD1698_DLINK_PIN);
  
    // Hold hi for 110-150 uS.  Obtained from datasheet.
    halCommonDelayMicroseconds(110);
  
    // The timing windows are very tight in this section.  Adding tens of cycles
    // between GPIO transitions is enough to exceed device tolerances.  As such,
    // all interrupts will be disabled during this time sensitive bit banged
    // transaction
    for (i=0; i<PYD_MESSAGE_READ_LENGTH_BITS; i++) {
      // Drive the output high for ~5 cycles, then low for ~5 cycles, triggering
      // the next bit of data from the PYD
      ackPydPin(DLINK_DOUT_REG, OCCUPANCY_PYD1698_DLINK_PIN);
  
      // Set the pin to input
      *DLINK_MODE_REG = inCfg;
  
      // we need to wait long enough for the PYD to drive the signal low.  16 uS
      // should do, derived from experimentation.
      halCommonDelayMicroseconds(16);
  
      // read in the data
      if (*DLINK_DIN_REG & (1 << OCCUPANCY_PYD1698_DLINK_PIN)) {
        data |= 1 << (PYD_MESSAGE_READ_LENGTH_BITS - 1 - i);
      }
  
      *DLINK_DOUT_REG = bitLoVal;
      *DLINK_MODE_REG = outCfg;
      *DLINK_DOUT_REG = bitLoVal;
    }
  )
  
    // convert the read message to configuration structure format
    readMsg->config->reserved = (data & PYD_CONFIG_RESERVED_MASK) >>
                                 PYD_CONFIG_RESERVED_BIT;
    readMsg->config->filterSource = (data & PYD_CONFIG_FILTER_SRC_MASK) >>
                                     PYD_CONFIG_FILTER_SRC_BIT;
    readMsg->config->operationMode = (data & PYD_CONFIG_OPERATION_MODE_MASK) >>
                                      PYD_CONFIG_OPERATION_MODE_BIT;
    readMsg->config->windowTime = (data & PYD_CONFIG_WINDOW_TIME_MASK) >>
                                   PYD_CONFIG_WINDOW_TIME_BIT;
    readMsg->config->pulseCounter = (data & PYD_CONFIG_PULSE_COUNTER_MASK) >>
                                     PYD_CONFIG_PULSE_COUNTER_BIT;
    readMsg->config->blindTime = (data & PYD_CONFIG_BLIND_TIME_MASK) >>
                                  PYD_CONFIG_BLIND_TIME_BIT;
    readMsg->config->sensitivity = (data & PYD_CONFIG_SENSITIVITY_MASK) >>
                                    PYD_CONFIG_SENSITIVITY_BIT;
    readMsg->AdcVoltage = ((data & PYD_ADC_VOLTAGE_MASK) >> PYD_ADC_VOLTAGE_BIT);
  
    // return the GPIO to its previous configuration
    GPIO_PinModeSet(OCCUPANCY_PYD1698_DLINK_PORT, OCCUPANCY_PYD1698_DLINK_PIN, gpioModeInput, 0);
  
    halGenericInterruptControlIrqClear(irqCfg);
    halGenericInterruptControlIrqEnable(irqCfg);
    return;
  }
  
  void halOccupancyPyd1698GetCurrentConfiguration(HalPydCfg_t *config)
  {
    config->reserved = currentPydCfg.reserved;
    config->filterSource = currentPydCfg.filterSource;
    config->operationMode = currentPydCfg.operationMode;
    config->windowTime = currentPydCfg.windowTime;
    config->pulseCounter = currentPydCfg.pulseCounter;
    config->blindTime = currentPydCfg.blindTime;
    config->sensitivity = currentPydCfg.sensitivity;
  }
  
  static uint32_t cfgMsgToData(HalPydCfg_t *cfgMsg)
  {
    uint32_t retVal;
  
    retVal = 0;
  
    cfgMsg->reserved = PYD_CONFIG_RESERVED_VALUE;
  
    retVal |= (cfgMsg->reserved << PYD_CONFIG_RESERVED_BIT) &
               PYD_CONFIG_RESERVED_MASK;
    retVal |= (cfgMsg->filterSource << PYD_CONFIG_FILTER_SRC_BIT) &
               PYD_CONFIG_FILTER_SRC_MASK;
    retVal |= (cfgMsg->operationMode << PYD_CONFIG_OPERATION_MODE_BIT) &
               PYD_CONFIG_OPERATION_MODE_MASK;
    retVal |= (cfgMsg->windowTime << PYD_CONFIG_WINDOW_TIME_BIT) &
               PYD_CONFIG_WINDOW_TIME_MASK;
    retVal |= (cfgMsg->pulseCounter << PYD_CONFIG_PULSE_COUNTER_BIT) &
               PYD_CONFIG_PULSE_COUNTER_MASK;
    retVal |= (cfgMsg->blindTime << PYD_CONFIG_BLIND_TIME_BIT) &
               PYD_CONFIG_BLIND_TIME_MASK;
    retVal |= (cfgMsg->sensitivity << PYD_CONFIG_SENSITIVITY_BIT) &
               PYD_CONFIG_SENSITIVITY_MASK;
  
    return retVal;
  }
  
  static void clearInterupt(void)
  {
    // drive the DLink pin low to reset interrupt
    GPIO_PinOutClear(OCCUPANCY_PYD1698_DLINK_PORT, OCCUPANCY_PYD1698_DLINK_PIN);
  
    // Set DLINK as output
    GPIO_PinModeSet(OCCUPANCY_PYD1698_DLINK_PORT, OCCUPANCY_PYD1698_DLINK_PIN, gpioModePushPull, 0);
  
  ATOMIC(
    // Wait two cycles of the PYD's clock using NO OP instructions
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
  )
    // Revert the config register to what it was before we modified it
    GPIO_PinModeSet(OCCUPANCY_PYD1698_DLINK_PORT, OCCUPANCY_PYD1698_DLINK_PIN, gpioModeInput, 0);
  }
  
  __STATIC_INLINE void ackPydPin(uint32_t volatile * port, uint8_t pin)
  {
    uint16_t bitHiVal = *port | (1 << pin);
    uint16_t bitLoVal = *port & ~(1 << pin);
  
    *port = bitLoVal;
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    *port = bitHiVal;
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
    asm("NOP");
  }
  