{"id":2179,"date":"2015-07-16T11:35:05","date_gmt":"2015-07-16T09:35:05","guid":{"rendered":"https:\/\/playembedded.org\/?p=2179"},"modified":"2021-07-29T11:00:15","modified_gmt":"2021-07-29T09:00:15","slug":"a-radio-frequency-transceiver-library-nrf24l01-and-chibiosrt","status":"publish","type":"post","link":"https:\/\/playembedded.org\/blog\/a-radio-frequency-transceiver-library-nrf24l01-and-chibiosrt\/","title":{"rendered":"A Radio Frequency transceiver library: nRF24L01 and ChibiOS\/RT"},"content":{"rendered":"<h3 id=\"1_Cheapest_and_most_popular_RF_transceiver_than_ever\" class=\"level_1\">Cheapest and most popular RF transceiver than ever<\/h3>\n<p>The <b>nRF24L01<\/b> is one of the most popular RF transceiver as it is very cheap and it could be found already configured on small PCBs with needed passive components. This means that only a MCU is needed to design a radio system with the <b>nRF24L01<\/b>.<\/p>\n<h4 id=\"2_Description\" class=\"level_2\">Description<\/h4>\n<p>The <b>nRF24L01<\/b> is a highly integrated, ultra low power 2Mbps RF transceiver IC for the 2.4GHz ISM band. It provides an hardware protocol accelerator, the Enhanced ShockBurst, enabling the implementation of advanced and robust wireless connectivity with low cost MCUs.<\/p>\n<h3 id=\"3_Documentation\" class=\"level_1\">Documentation<\/h3>\n<p>By this article we want to provide a full library for <b>nRF24L01<\/b> compatible with ChibiOS\/RT 3.0 ad a demo. That requires, as always, a preliminary read of the device datasheet.<\/p>\n<p><a href=\"http:\/\/www.nordicsemi.com\/eng\/nordic\/download_resource\/8041\/1\/52888422\">nRF24L01 Datasheet<\/a><\/p>\n<h3 id=\"4_Features\" class=\"level_1\">Features<\/h3>\n<h4 id=\"5_Design_and_connections\" class=\"level_2\">Design and connections<\/h4>\n<figure id=\"attachment_2582\" aria-describedby=\"caption-attachment-2582\" style=\"width: 175px\" class=\"wp-caption alignright\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2016\/04\/art_010_NRF24L01_pin.jpg\" rel=\"attachment wp-att-2582\"><img loading=\"lazy\" decoding=\"async\" class=\" wp-image-2582\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2016\/04\/art_010_NRF24L01_pin-300x166.jpg\" alt=\"nRF24L01 pin map\" width=\"175\" height=\"97\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_NRF24L01_pin-300x166.jpg 300w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_NRF24L01_pin-150x83.jpg 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_NRF24L01_pin-24x13.jpg 24w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_NRF24L01_pin-36x20.jpg 36w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_NRF24L01_pin-48x27.jpg 48w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_NRF24L01_pin.jpg 450w\" sizes=\"auto, (max-width: 175px) 100vw, 175px\"><\/a><figcaption id=\"caption-attachment-2582\" class=\"wp-caption-text\">The pin map of a nRF24L01 transceiver.<\/figcaption><\/figure>\n<p>With peak RX\/TX currents lower than 14mA, a sub &mu;A power down mode, advanced power management, and a 1.9 to 3.6V supply range, the <b>nRF24L01<\/b> provides a true ULP solution enabling months to years of battery lifetime when running on coin cells or AA\/AAA batteries. The <b>nRF24L01<\/b> is configured and operated through the SPI and using this interface we can access to its register maps contains all configuration registers of this chip.<\/p>\n<p>Still using SPI we can send command to the transceiver making it accessible in all its operation modes.The Enhanced ShockBurst is based on packet communication and supports various modes from manual operation to advanced autonomous protocol operation. Internal FIFOs ensure a smooth data flow between the radio front end and the system&rsquo;s MCU. The radio front end uses GFSK modulation and has user configurable parameters like frequency channel, output power and air data rate.<\/p>\n<h4 id=\"6_Pin_Description\" class=\"level_2\">Pin Description<\/h4>\n<p>Just few pin are required to use this transceiver:2 pin for power supply, 4 pin for SPI communication and 2 additional pins for <b>IRQ<\/b> and <b>CE<\/b>. Follows the detailed pin map:<\/p>\n<ol>\n<li>GND, connection to ground<\/li>\n<li>VCC, power supply 1.9~3.6V DC<\/li>\n<li>CE, Chip enable used to perform some operation<\/li>\n<li>CSN, SPI chip select<\/li>\n<li>SCK, SPI serial clock<\/li>\n<li>MOSI, SPI MOSI<\/li>\n<li>MISO, SPI MISO<\/li>\n<li>IRQ, interrupt request pin<\/li>\n<\/ol>\n<h4 id=\"7_Enhanced_ShockBurst\" class=\"level_2\">Enhanced ShockBurst<\/h4>\n<p>Enhanced ShockBurst is a packet based data link layer. It features automatic packet assembly and timing, automatic acknowledgement and re-transmissions of packets. It enables the implementation of ultra low power, high performance communication with low cost host MCUs. The features enable significant improvements of power efficiency for bi-directional and uni-directional systems, without adding complexity on the host controller side. This data link layer is enough complex and a reader can found a whole chapter (Chapter 7) about this argument on <b>nRF24L01<\/b> reference manual.<\/p>\n<p>Anyway, from our point of view we just need to know few thinks:<\/p>\n<ol>\n<li>We can set-up Enhanced ShockBurst using some commands clocked thought the SPI;<\/li>\n<li>Every receiver and transmitter need for a unique address which lenght is 5 bytes;<\/li>\n<li>Enhanced ShockBurst allows multiceiving (receive from 6 different addresses simultaneously);<\/li>\n<li>Max payload lenght is 32 bytes;<\/li>\n<li>Receiver is able to auto-detect payload lenght;<\/li>\n<\/ol>\n<p>In this first library version we will not consider the multiceiver option: this requires just some additional APIs, but testing and debuging part would be much more difficult.<!--more--><\/p>\n<h3 id=\"8_SPI_Operation_mode\" class=\"level_1\">SPI Operation mode<\/h3>\n<p>Reading the reference manual (Chapter 8) we can understand that <b>nRF24L01<\/b> uses a standard SPI with a maximum data rate of 8 Mbps. The serial commands are shifted out in this way: for each command word(one byte) most significant bit must be sent first; if the command is longer than one word less significant byte must be sent first. Chip select must be lowered before start communication and should be pulled up only when every word is shifted out.<\/p>\n<p>A SPI timing diagram (see Fig.2) found on manual give us all the still missing information to configure SPI properly (If you are not expert about uses of SPI, we suggest our quick tutorial <a href=\"https:\/\/playembedded.org\/en\/2015\/02\/15\/meeting-spi\/\" target=\"_blank\" rel=\"noopener noreferrer\">Meeting SPI<\/a>).<\/p>\n<figure id=\"attachment_2584\" aria-describedby=\"caption-attachment-2584\" style=\"width: 980px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2016\/04\/art_010_SPI_timing.jpg\" rel=\"attachment wp-att-2584\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2584 size-full\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2016\/04\/art_010_SPI_timing.jpg\" alt=\"nRF24L01 SPI timing diagram\" width=\"980\" height=\"180\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_timing.jpg 980w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_timing-150x28.jpg 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_timing-300x55.jpg 300w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_timing-24x4.jpg 24w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_timing-36x7.jpg 36w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_timing-48x9.jpg 48w\" sizes=\"auto, (max-width: 980px) 100vw, 980px\"><\/a><figcaption id=\"caption-attachment-2584\" class=\"wp-caption-text\">nRF24L01 SPI timing diagram.<\/figcaption><\/figure>\n<p>Summarizing, we need to configure SPI clock frequency to be equal or less of 8 MHz, Data size should be 8 bit, CPOL as low and CPHA as low.<\/p>\n<p>We can found in the same chapter a table containing all the commands available (Figure 3), note that every SPI exchange length depends on commands and that we can access to every feature thought that commands. Furthermore during the first exchange <b>nRF24L01<\/b> shift out the status register map.<\/p>\n<figure id=\"attachment_2583\" aria-describedby=\"caption-attachment-2583\" style=\"width: 980px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2016\/04\/art_010_SPI_commands.jpg\" rel=\"attachment wp-att-2583\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2583 size-full\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2016\/04\/art_010_SPI_commands.jpg\" alt=\"nRF24L01 SPI commands\" width=\"980\" height=\"840\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_commands.jpg 980w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_commands-150x129.jpg 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_commands-300x257.jpg 300w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_commands-24x21.jpg 24w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_commands-36x31.jpg 36w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_SPI_commands-48x41.jpg 48w\" sizes=\"auto, (max-width: 980px) 100vw, 980px\"><\/a><figcaption id=\"caption-attachment-2583\" class=\"wp-caption-text\">nRF24L01 SPI commands.<\/figcaption><\/figure>\n<p>Equally important is the register map table (Chapter 9) We don&rsquo;t report it here since it is very long, anyway remember that some configuration could be set-up writing in some of these registers.<\/p>\n<h3 id=\"9_The_interrupt_request\" class=\"level_1\">The interrupt request<\/h3>\n<p>The <b>nRF24L01<\/b> has an active low <b>IRQ<\/b> pin. The <b>IRQ<\/b> pin has three sources: it is activated when TX_DS (Transmit Data Sent), RX_DR (Receive Data Ready) or MAX_RT (Max Retransmit) bit are set high by the Enhanced ShockBurst in the <b>STATUS<\/b> register. The <b>IRQ<\/b> pin resets when MCU writes &lsquo;1&rsquo; in the <b>STATUS<\/b> register on the bit associated to the <b>IRQ<\/b> active source. As example, we can suppose that a MAX_RT events happens. So, we will detect an <b>IRQ<\/b> transition from high to low and checking <b>STATUS<\/b> register we will found that MAX_RT bit is high. So we should take necessary actions and than set MAX_RT to high in the <b>STATUS<\/b> register to clear IRQ.<\/p>\n<p>To detect <b>IRQ<\/b> we can use EXT driver from ChibiOS\/HAL. This driver launches a callback when detects a transition (on rising, falling or both edge according to its configuration), anyway, we will discuss this callback in detail later.<\/p>\n<h3 id=\"10_From_finite_state_machine_to_driver_design\" class=\"level_1\">From finite state machine to driver design<\/h3>\n<figure id=\"attachment_2581\" aria-describedby=\"caption-attachment-2581\" style=\"width: 300px\" class=\"wp-caption alignleft\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2016\/04\/art_010_fsm.jpg\" rel=\"attachment wp-att-2581\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2581\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2016\/04\/art_010_fsm-300x180.jpg\" alt=\"Library FSM\" width=\"300\" height=\"180\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_fsm-300x180.jpg 300w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_fsm-150x90.jpg 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_fsm-24x14.jpg 24w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_fsm-36x22.jpg 36w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_fsm-48x29.jpg 48w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_010_fsm.jpg 897w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\"><\/a><figcaption id=\"caption-attachment-2581\" class=\"wp-caption-text\">The proposed FSM for this transceiver library.<\/figcaption><\/figure>\n<p>Before starting, please allow me a little note. Not everyone knows that PLAY Embedded takes its name by an open-source library that we create some time ago. That library (a Pretty LAYer for ChibiOS aka PLAY for ChibiOS) is born to be an abstraction layer for most used devices in embedded applications and it already supports nRF24L01. The project is still active on sourceforge (<a href=\"http:\/\/sourceforge.net\/projects\/playforchibios\/\" target=\"_blank\" rel=\"noopener noreferrer\">PLAY<\/a>) but from next versions it will support MEMS only. All this to say that code and ideas here presented are originating from that project.<\/p>\n<p>Resuming, on chapter 6.1 we can found a finite state machine representing the modes the <b>nRF24L01<\/b> can operate in and how they are accessed. Inspired by this diagram we made our driver FSM here represented (See Fig.4).<\/p>\n<p>This is a good starting point for our driver design. About details on library organization we are confident that our loyal readers have already read articles like <a href=\"https:\/\/playembedded.org\/en\/2015\/07\/15\/chibioshal-design-an-object-oriented-approach\/\" target=\"_blank\" rel=\"noopener noreferrer\">ChibiOS\/HAL design<\/a> and <a href=\"https:\/\/playembedded.org\/en\/2015\/07\/04\/c-library-design-for-embedded-applications-tips-and-hints\/\" target=\"_blank\" rel=\"noopener noreferrer\">C Library design for embedded applications<\/a>. Like for other libraries, this one has just a configuration header for user configuration or to disable driver, a main header <i>rf.h<\/i> and <i>rf.c<\/i>.<\/p>\n<h4 id=\"11_rf_h_typedefs_and_enumerations\" class=\"level_2\">rf.h: typedefs and enumerations<\/h4>\n<p>As always, header contains a certain number of bit-mask and enumeration. We will just show important parts. First of all, we defined an RX packet and a TX packet. Note that Payload data structure is included into the frame and its length is fixed removing additional complications. The assumption considered during the design phase is that in the case of a communication using multiple packets it is expected that these have the same length. Anyway, payload length could be chosen thought <i>userconf.h<\/i>.<\/p>\n<pre>\/**\n * @brief   RF transmission frame.\n *\/\ntypedef struct {\n  \/**\n   * @brief   Address of the receiver.\n   *\/\n  uint8_t tx_address[RF_ADDLEN];\n  \/**\n   * @brief   Static TX payload lenght.\n   *\/\n  uint8_t tx_paylen;\n  \/**\n   * @brief   TX payload data structure.\n   *\/\n  uint8_t tx_payload[RF_PAYLEN];\n} RFTxFrame;\n\/**\n * @brief   RF received frame.\n *\/\ntypedef struct {\n  \/**\n   * @brief   Address of the transmitter.\n   *\/\n  uint8_t rx_address[RF_ADDLEN];\n  \/**\n   * @brief   Static RX payload lenght.\n   *\n   * @detail  It is rewritten during a receive if Dynamic Payload Lenght\n   *          feature is available.\n   *\/\n  uint8_t rx_paylen;\n  \/**\n   * @brief   RX payload data structure.\n   *\/\n  uint8_t rx_payload[RF_PAYLEN];\n} RFRxFrame;<\/pre>\n<p>Staring from enumeration and defines we can create a configuration structure. Additionally we need for <b>CE<\/b> and <b>IRQ<\/b> information, a pointer to an SPI driver and its configuration and a pointer to the an EXT driver and its configuration. We need for SPI for communication and for EXT to manage external IRQ.<\/p>\n<pre>\/**\n * @brief   RF Transceiver configuration structure.\n *\/\ntypedef struct {\n  \/**\n   * @brief The chip enable line port.\n   *\/\n  ioportid_t                ceport;\n  \/**\n   * @brief The chip enable line pad number.\n   *\/\n  uint16_t                  cepad;\n  \/**\n   * @brief The interrupt line port.\n   *\/\n  ioportid_t                irqport;\n  \/**\n   * @brief The interrupt line pad number.\n   *\/\n  uint16_t                  irqpad;\n  \/**\n   * @brief Pointer to the SPI driver associated to this RF.\n   *\/\n  SPIDriver                 *spip;\n  \/**\n   * @brief Pointer to the SPI configuration .\n   *\/\n  const SPIConfig           *spicfg;\n  \/**\n   * @brief Pointer to the SPI driver associated to this RF.\n   *\/\n  EXTDriver                 *extp;\n  \/**\n   * @brief Pointer to the SPI configuration .\n   *\/\n  const EXTConfig           *extcfg;\n  \/**\n   * @brief RF Transceiver auto retransmit count.\n   *\/\n  NRF24L01_ARC_t            auto_retr_count;\n  \/**\n   * @brief RF Transceiver auto retransmit delay.\n   *\/\n  NRF24L01_ARD_t            auto_retr_delay;\n  \/**\n   * @brief RF Transceiver address width.\n   *\/\n  NRF24L01_AW_t             address_width;\n  \/**\n   * @brief RF Transceiver channel frequency.\n   *\/\n  NRF24L01_RF_CH_t          channel_freq;\n  \/**\n   * @brief RF Transceiver air data rate.\n   *\/\n  NRF24L01_ADR_t            data_rate;\n  \/**\n   * @brief RF Transceiver output power.\n   *\/\n  NRF24L01_PWR_t            out_pwr;\n  \/**\n   * @brief   RF Transceiver Low Noise Amplifier\n   *\/\n  NRF24L01_LNA_t            lna;\n  \/**\n  * @brief    RF Transceiver Dynamic Payload enabler\n  *\/\n  NRF24L01_DPL_t            en_dpl;\n  \/**\n   * @brief   RF Transceiver Dynamic Acknowledge with Payload enabler\n   *\/\n  NRF24L01_ACK_PAY_t        en_ack_pay;\n  \/**\n   * @brief   RF Transceiver Dynamic Acknowledge enabler\n   *\/\n  NRF24L01_DYN_ACK_t        en_dyn_ack;\n} RFConfig;<\/pre>\n<p>According to our diagram these are RF available states:<\/p>\n<pre>\/**\n * @brief   Driver state machine possible states.\n *\/\ntypedef enum {\n  RF_UNINIT = 0,                     \/**&lt; Not initialized.                   *\/\n  RF_STOP = 1,                       \/**&lt; Stopped.                           *\/\n  RF_READY = 2,                      \/**&lt; Ready for TX or RX.                *\/\n  RF_RX = 3,                         \/**&lt; Receiving data.                    *\/\n  RF_TX = 4                          \/**&lt; Sending data.                      *\/\n} rf_state_t;<\/pre>\n<p>Before to introduce RF driver typedef let&rsquo;s spend some words about callback that occurs on IRQ. This callback could be considered like an ISR and in Real Time systems these routines should be atomic and their lifetime should be deterministic. We said above that <b>IRQ<\/b> has many sources and that we need to read <b>STATUS<\/b> register to understand the source that generated the IRQ. The lifetime of an SPI operation is by its very nature non-deterministic and as a general rule a driver should not be used in ISR.<\/p>\n<p>We can solve this problem using another kernel instruments: events! The ISR broadcast an event, in this way we can manage <b>STATUS<\/b> register out of kernel critical section. That explains why in driver typedef we have some variable related to events: we have an event sources and an event listener.<\/p>\n<pre>\/**\n * @brief   Structure representing a RF driver.\n *\/\ntypedef struct {\n  \/**\n   * @brief Driver state.\n   *\/\n  rf_state_t         state;\n  \/**\n   * @brief Current configuration data.\n   *\/\n  RFConfig           *config;\n  \/**\n   * @brief   <b>IRQ<\/b> event.\n   * @note    The flags associated to the listeners will indicate the\n   *          <b>IRQ<\/b> mask that have occurred.\n   *\/\n  event_source_t      irq_event;\n  \/**\n   * @brief   RF event listner.\n   *\/\n  event_listener_t    el;\n  \/**\n   * @brief   RF event listner id.\n   *\/\n  eventmask_t         el_id;\n#if RF_USE_MUTUAL_EXCLUSION || defined(__DOXYGEN__)\n  \/**\n   * @brief   Mutex protecting the peripheral.\n   *\/\n  mutex_t                   mutex;\n#endif \/* RF_USE_MUTUAL_EXCLUSION *\/\n} RFDriver;<\/pre>\n<h4 id=\"12_rf_c_needed_local_functions\" class=\"level_2\">rf.c: needed local functions<\/h4>\n<p>A simple read\\write register is not enough for this device. We need to implement some function to write register or send command to nRF24L01. We will not explain these function because they just perform some SPI exchange composing properly transmission buffer. Just remember that before call one of these functions the SPI interface must be initialized and the driver started.<\/p>\n<pre class=\"\">\/**\n * @brief   Resets all Status flags.\n *\n * @pre     The SPI interface must be initialized and the driver started.\n *\n * @param[in] spip      pointer to the SPI interface\n *\n * @return              the status register value\n *\/\n#define  nrf24l01Reset(spip) {                                               \\\n                                                                             \\\n  nrf24l01WriteRegister(spip, NRF24L01_AD_STATUS,                            \\\n                                NRF24L01_DI_STATUS_MAX_RT |                  \\\n                                NRF24L01_DI_STATUS_RX_DR |                   \\\n                                NRF24L01_DI_STATUS_TX_DS);                   \\\n}\n...\n\/*===========================================================================*\/\n\/* Driver local functions.                                                   *\/\n\/*===========================================================================*\/\n\/**\n * @brief   Gets the status register value.\n * @pre     The SPI interface must be initialized and the driver started.\n *\n * @param[in] spip      pointer to the SPI interface\n *\n * @return              the status register value\n *\/\nstatic uint8_t nrf24l01GetStatus(SPIDriver *spip) {\n  uint8_t txbuf = NRF24L01_CMD_NOP;\n  uint8_t status;\n  spiSelect(spip);\n  spiExchange(spip, 1, &amp;txbuf, &amp;status);\n  spiUnselect(spip);\n  return status;\n}\n\/**\n * @brief   Reads a generic register value.\n *\n * @note    Cannot be used to set addresses\n * @pre     The SPI interface must be initialized and the driver started.\n *\n * @param[in] spip      pointer to the SPI interface\n * @param[in] reg       register number\n * @param[out] pvalue   pointer to a data buffer\n *\n * @return              the status register value\n *\/\nstatic uint8_t nrf24l01ReadRegister(SPIDriver *spip, uint8_t reg,\n                                       uint8_t* pvalue) {\n  uint8_t txbuf = (NRF24L01_CMD_READ | reg);\n  uint8_t status = 0xFF;\n  spiSelect(spip);\n  spiExchange(spip, 1, &amp;txbuf, &amp;status);\n  spiReceive(spip, 1, pvalue);\n  spiUnselect(spip);\n  return status;\n}\n\/**\n * @brief   Writes a generic register value.\n *\n * @note    Cannot be used to set addresses\n * @pre     The SPI interface must be initialized and the driver started.\n *\n * @param[in] spip      pointer to the SPI interface\n * @param[in] reg       register number\n * @param[in] value     data value\n *\n * @return              the status register value\n *\/\nstatic uint8_t nrf24l01WriteRegister(SPIDriver *spip, uint8_t reg,\n                                        uint8_t value) {\n  uint8_t txbuf[2] = {(NRF24L01_CMD_WRITE | reg), value};\n  uint8_t rxbuf[2] = {0xFF, 0xFF};\n  switch (reg) {\n    default:\n      \/* Reserved register must not be written, according to the datasheet\n       * this could permanently damage the device.\n       *\/\n      osalDbgAssert(FALSE, \"lg3d20WriteRegister(), reserved register\");\n    case NRF24L01_AD_OBSERVE_TX:\n    case NRF24L01_AD_CD:\n    case NRF24L01_AD_RX_ADDR_P0:\n    case NRF24L01_AD_RX_ADDR_P1:\n    case NRF24L01_AD_RX_ADDR_P2:\n    case NRF24L01_AD_RX_ADDR_P3:\n    case NRF24L01_AD_RX_ADDR_P4:\n    case NRF24L01_AD_RX_ADDR_P5:\n    case NRF24L01_AD_TX_ADDR:\n    \/* Read only or addresses registers cannot be written,\n     * the command is ignored.\n     *\/\n      return 0;\n    case NRF24L01_AD_CONFIG:\n    case NRF24L01_AD_EN_AA:\n    case NRF24L01_AD_EN_RXADDR:\n    case NRF24L01_AD_SETUP_AW:\n    case NRF24L01_AD_SETUP_RETR:\n    case NRF24L01_AD_RF_CH:\n    case NRF24L01_AD_RF_SETUP:\n    case NRF24L01_AD_STATUS:\n    case NRF24L01_AD_RX_PW_P0:\n    case NRF24L01_AD_RX_PW_P1:\n    case NRF24L01_AD_RX_PW_P2:\n    case NRF24L01_AD_RX_PW_P3:\n    case NRF24L01_AD_RX_PW_P4:\n    case NRF24L01_AD_RX_PW_P5:\n    case NRF24L01_AD_FIFO_STATUS:\n    case NRF24L01_AD_DYNPD:\n    case NRF24L01_AD_FEATURE:\n      spiSelect(spip);\n      spiExchange(spip, 2, txbuf, rxbuf);\n      spiUnselect(spip);\n      return rxbuf[0];\n  }\n}\n\/**\n * @brief   Writes an address.\n *\n * @pre     The SPI interface must be initialized and the driver started.\n *\n * @param[in] spip      pointer to the SPI interface\n * @param[in] reg       register number\n * @param[in] pvalue    pointer to address value\n * @param[in] addlen    address len\n *\n * @return              the status register value\n *\/\nstatic uint8_t nrf24l01WriteAddress(SPIDriver *spip, uint8_t reg,\n                                       uint8_t *pvalue, uint8_t addlen) {\n  uint8_t txbuf[NRF24L01_MAX_ADD_LENGHT + 1];\n  uint8_t rxbuf[NRF24L01_MAX_ADD_LENGHT + 1];\n  unsigned i;\n  if(addlen &gt; NRF24L01_MAX_ADD_LENGHT) {\n    osalDbgAssert(FALSE, \"nrf24l01WriteAddress(), wrong address length\");\n    return 0;\n  }\n  txbuf[0] = (NRF24L01_CMD_WRITE | reg);\n  rxbuf[0] = 0xFF;\n  for(i = 1; i &lt;= addlen; i++) { txbuf[i] = *(pvalue + (i - 1)); rxbuf[i] = 0xFF; } switch (reg) { default: \/* Reserved register must not be written, according to the datasheet * this could permanently damage the device. *\/ osalDbgAssert(FALSE, \"nrf24l01WriteAddress(), reserved register\"); case NRF24L01_AD_OBSERVE_TX: case NRF24L01_AD_CD: case NRF24L01_AD_CONFIG: case NRF24L01_AD_EN_AA: case NRF24L01_AD_EN_RXADDR: case NRF24L01_AD_SETUP_AW: case NRF24L01_AD_SETUP_RETR: case NRF24L01_AD_RF_CH: case NRF24L01_AD_RF_SETUP: case NRF24L01_AD_STATUS: case NRF24L01_AD_RX_PW_P0: case NRF24L01_AD_RX_PW_P1: case NRF24L01_AD_RX_PW_P2: case NRF24L01_AD_RX_PW_P3: case NRF24L01_AD_RX_PW_P4: case NRF24L01_AD_RX_PW_P5: case NRF24L01_AD_FIFO_STATUS: case NRF24L01_AD_DYNPD: case NRF24L01_AD_FEATURE: \/* Not address registers cannot be written, the command is ignored.*\/ return 0; case NRF24L01_AD_RX_ADDR_P0: case NRF24L01_AD_RX_ADDR_P1: case NRF24L01_AD_RX_ADDR_P2: case NRF24L01_AD_RX_ADDR_P3: case NRF24L01_AD_RX_ADDR_P4: case NRF24L01_AD_RX_ADDR_P5: case NRF24L01_AD_TX_ADDR: spiSelect(spip); spiExchange(spip, addlen + 1, txbuf, rxbuf); spiUnselect(spip); return rxbuf[0]; } } \/** * @brief Reads RX payload from FIFO. * * @note Payload is deleted from FIFO after it is read. Used in RX mode. * @pre The SPI interface must be initialized and the driver started. * * @param[in] spip pointer to the SPI interface * @param[in] paylen payload length * @param[in] rxbuf pointer to a buffer * * @return the status register value *\/ static uint8_t nrf24l01GetRxPl(SPIDriver *spip, uint8_t paylen, uint8_t* rxbuf) { uint8_t txbuf = NRF24L01_CMD_R_RX_PAYLOAD; uint8_t status; if(paylen &gt; NRF24L01_MAX_PL_LENGHT) {\n    return 0;\n  }\n  spiSelect(spip);\n  spiExchange(spip, 1, &amp;txbuf, &amp;status);\n  spiReceive(spip, paylen, rxbuf);\n  spiUnselect(spip);\n  return status;\n}\n\/**\n * @brief   Writes TX payload on FIFO.\n *\n * @note    Used in TX mode.\n * @pre     The SPI interface must be initialized and the driver started.\n *\n * @param[in] spip      pointer to the SPI interface\n * @param[in] paylen    payload length\n * @param[in] rxbuf     pointer to a buffer\n *\n * @return              the status register value\n *\/\nstatic uint8_t nrf24l01WriteTxPl(SPIDriver *spip, uint8_t paylen,\n                                    uint8_t* txbuf) {\n  uint8_t cmd = NRF24L01_CMD_W_TX_PAYLOAD;\n  uint8_t status;\n  if(paylen &gt; NRF24L01_MAX_PL_LENGHT) {\n    return 0;\n  }\n  spiSelect(spip);\n  spiExchange(spip, 1, &amp;cmd, &amp;status);\n  spiSend(spip, paylen, txbuf);\n  spiUnselect(spip);\n  return status;\n}\n\/**\n * @brief   Flush TX FIFO.\n *\n * @note    Used in TX mode.\n * @pre     The SPI interface must be initialized and the driver started.\n *\n * @param[in] spip      pointer to the SPI interface\n *\n * @return              the status register value\n *\/\nstatic uint8_t nrf24l01FlushTx(SPIDriver *spip) {\n  uint8_t txbuf = NRF24L01_CMD_FLUSH_TX;\n  uint8_t status;\n  spiSelect(spip);\n  spiExchange(spip, 1, &amp;txbuf, &amp;status);\n  spiUnselect(spip);\n  return status;\n}\n\/**\n * @brief   Flush RX FIFO.\n *\n * @note    Used in RX mode. Should not be executed during transmission of\n            acknowledge, that is, acknowledge package will not be completed.\n * @pre     The SPI interface must be initialized and the driver started.\n *\n * @param[in] spip      pointer to the SPI interface\n *\n * @return              the status register value\n *\/\nstatic uint8_t nrf24l01FlushRx(SPIDriver *spip) {\n  uint8_t txbuf = NRF24L01_CMD_FLUSH_RX;\n  uint8_t status;\n  spiSelect(spip);\n  spiExchange(spip, 1, &amp;txbuf, &amp;status);\n  spiUnselect(spip);\n  return status;\n}\n\/**\n * @brief   Activates the following features:\n *          R_RX_PL_WID        -&gt; (In order to enable DPL the EN_DPL bit in the\n *                                 FEATURE register must be set)\n *          W_ACK_PAYLOAD      -&gt; (In order to enable PL with ACK the EN_ACK_PAY\n *                                 bit in the FEATURE register must be set)\n *          W_TX_PAYLOAD_NOACK -&gt; (In order to send a PL without ACK\n *                                 the EN_DYN_ACK it in the FEATURE register\n *                                 must be set)\n *\n * @note    A new ACTIVATE command with the same data deactivates them again.\n *          This is executable in power down or stand by modes only.\n * @pre     The SPI interface must be initialized and the driver started.\n *\n * @param[in] spip      pointer to the SPI interface\n *\n * @return              the status register value\n *\/\nstatic uint8_t nrf24l01Activate(SPIDriver *spip) {\n  uint8_t txbuf[2] = {NRF24L01_CMD_FLUSH_RX, ACTIVATE};\n  uint8_t rxbuf[2];\n  spiSelect(spip);\n  spiExchange(spip, 2, txbuf, rxbuf);\n  spiUnselect(spip);\n  return rxbuf[0];\n}\n\/**\n * @brief   Reads RX payload lenght for the top R_RX_PAYLOAD\n *          in the RX FIFO when Dynamic Payload Length is activated.\n *\n * @note    R_RX_PL_WID must be set and activated.\n * @pre     The SPI interface must be initialized and the driver started.\n *\n * @param[in] spip      pointer to the SPI interface\n * @param[in] ppaylen   pointer to the payload length variable\n *\n * @return              the status register value\n *\/\nstatic uint8_t nrf24l01ReadRxPlWid(SPIDriver *spip, uint8_t *ppaylen) {\n  uint8_t txbuf[2] = {NRF24L01_CMD_R_RX_PL_WID, 0xFF};\n  uint8_t rxbuf[2];\n  spiSelect(spip);\n  spiExchange(spip, 2, txbuf, rxbuf);\n  spiUnselect(spip);\n  *ppaylen = rxbuf[1];\n  return rxbuf[0];\n}<\/pre>\n<h4 id=\"13_APIs_explained\" class=\"level_2\">APIs explained<\/h4>\n<p>Here the full list of our APIs with code-box and its explanation.<\/p>\n<h5 id=\"14_rfInit_\" class=\"level_3\">rfInit()<\/h5>\n<p>This function should be called by <b>main()<\/b> right after <b>halInit()<\/b> and <b>chSysInit()<\/b>. This function initializes every Driver enabled calling <b>rfObjectInit()<\/b> (In this case we have only <b>RFD1<\/b> and it is always enabled).<\/p>\n<pre>\/**\n * @brief   RF Complex Driver initialization.\n *\n * @init\n *\/\nvoid rfInit(void) {\n  rfObjectInit(&amp;RFD1);\n}<\/pre>\n<h5 id=\"15_rfObjectInit_\" class=\"level_3\">rfObjectInit()<\/h5>\n<p>This function is not an API. It is typically called by <b>rfInit()<\/b> receiving pointer to a driver which must be initialized. Note that in this function we update driver state to <b>RF_STOP<\/b> and initialize driver variables. In our case we need to initialize event sources and a mutexes that is used by some others APIs.<\/p>\n<pre>\/**\n * @brief   Initializes an instance.\n *\n * @param[out] rfp         pointer to the @p RFDriver object\n *\n * @init\n *\/\nvoid rfObjectInit(RFDriver *rfp){\n  rfp-&gt;state  = RF_STOP;\n  rfp-&gt;config = NULL;\n  osalEventObjectInit(&amp;rfp-&gt;irq_event);\n  if(rfp == &amp;RFD1)\n    rfp-&gt;el_id = 1;\n#if RF_USE_MUTUAL_EXCLUSION == TRUE\n  osalMutexObjectInit(&amp;rfp-&gt;mutex);\n#endif\n}<\/pre>\n<h5 id=\"16_rfStart_\" class=\"level_3\">rfStart()<\/h5>\n<p>This <b>API<\/b> and must be called before use this driver and only after the <b>rfInit()<\/b>. The <b>rfStart()<\/b> receives in addition to the driver also a pointer to <b>RFConfig<\/b>: configuration structure should be declared by user in its application but is needed by driver when we start the driver.<br>\nThis function updates configuration pointer in driver structure so from here on out functions will not require configuration pointer as parameter. It register the <b>IRQ<\/b> event on the driver event listener (from here on, the listener will be able to listen to events generated by the source called IRQ), then starts SPI and EXT driver, resets <b>STATUS<\/b> register, configures Enhanced ShockBurst according to <b>RFConfig<\/b> and pulls down <b>CE<\/b> pin. After that updates driver state from <b>RF_STOP<\/b> to <b>RF_READY<\/b>: now we are ready to perform transmission or to receive.<\/p>\n<pre>\/**\n * @brief   Configures and activates RF Complex Driver peripheral.\n *\n * @param[in] rfp   pointer to the @p RFDriver object\n * @param[in] config    pointer to the @p RFConfig object\n *\n * @api\n *\/\nvoid rfStart(RFDriver *rfp, RFConfig *config) {\n  osalDbgCheck((rfp != NULL) &amp;&amp; (config != NULL));\n  osalDbgAssert((rfp-&gt;state == RF_STOP) || (rfp-&gt;state == RF_READY),\n              \"rfStart(), invalid state\");\n  rfp-&gt;config = config;\n  chEvtRegister(&amp;rfp-&gt;irq_event, &amp;rfp-&gt;el, rfp-&gt;el_id);\n  extStart(rfp-&gt;config-&gt;extp, rfp-&gt;config-&gt;extcfg);\n  spiStart(rfp-&gt;config-&gt;spip, rfp-&gt;config-&gt;spicfg);\n  nrf24l01Reset(rfp-&gt;config-&gt;spip);\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_CONFIG,\n                        NRF24L01_DI_CONFIG_PWR_UP | NRF24L01_DI_CONFIG_EN_CRC);\n  osalThreadSleepMilliseconds(2);\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_EN_AA,\n                        NRF24L01_DI_EN_AA);\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_EN_RXADDR,\n                        NRF24L01_DI_EN_RXADDR);\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_RF_CH,\n                        rfp-&gt;config-&gt;channel_freq);\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_SETUP_RETR,\n                        rfp-&gt;config-&gt;auto_retr_count |\n                        rfp-&gt;config-&gt;auto_retr_delay);\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_SETUP_AW,\n                        rfp-&gt;config-&gt;address_width);\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_RF_SETUP,\n                        rfp-&gt;config-&gt;data_rate |\n                        rfp-&gt;config-&gt;out_pwr |\n                        rfp-&gt;config-&gt;lna);\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_FEATURE,\n                        NRF24L01_DI_FEATURE_EN_DPL);\n  nrf24l01Activate(rfp-&gt;config-&gt;spip);\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_DYNPD,\n                        NRF24L01_DI_DYNPD);\n  palClearPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n  rfp-&gt;state = RF_READY;\n}<\/pre>\n<h5 id=\"17_rfStop_\" class=\"level_3\">rfStop()<\/h5>\n<p>This API, as always, makes some checks and assertion then turns off the nRF24L01, stop SPI and EXT driver, unregisters the <b>IRQ<\/b> event and updates status to <b>LCD_STOP<\/b><\/p>\n<pre>\/**\n * @brief   Deactivates the RF Complex Driver peripheral.\n *\n * @param[in] rfp      pointer to the @p RFDriver object\n *\n * @api\n *\/\nvoid rfStop(RFDriver *rfp) {\n  osalDbgCheck(rfp != NULL);\n  osalDbgAssert((rfp-&gt;state == RF_STOP) || (rfp-&gt;state == RF_READY),\n              \"rfStop(), invalid state\");\n  if (rfp-&gt;state == RF_READY) {\n      nrf24l01WriteRegister(rfp-&gt;config-&gt;spip,\n                            NRF24L01_AD_CONFIG, 0);\n      spiStop(rfp-&gt;config-&gt;spip);\n      extStop(rfp-&gt;config-&gt;extp);\n      chEvtUnregister(&amp;rfp-&gt;irq_event, &amp;rfp-&gt;el);\n  }\n  rfp-&gt;state = RF_STOP;\n}<\/pre>\n<h5 id=\"18_rfTxIsEmpty_\" class=\"level_3\">rfTxIsEmpty()<\/h5>\n<p>This <b>API<\/b> just read <b>STATUS<\/b> register and checks TX FIFO status returning TRUE if there are some empty spaces.<\/p>\n<pre>\/**\n * @brief   Checks if there are empty spaces in the TX FIFO.\n *\n * @param[in] rfp        Pointer to the @p RFDriver object\n * @return               The operation result.\n * @retval TRUE          There is an empty space.\n * @retval FALSE         No empty space available.\n * @api\n *\/\nbool rfTxIsEmpty(RFDriver *rfp) {\n  uint8_t fifo_status;\n  nrf24l01ReadRegister(rfp-&gt;config-&gt;spip,\n                       NRF24L01_AD_FIFO_STATUS, &amp;fifo_status);\n  return(!(fifo_status &amp; NRF24L01_DI_FIFO_STATUS_TX_FULL));\n}<\/pre>\n<h5 id=\"19_rfRxIsNonEmpty_\" class=\"level_3\">rfRxIsNonEmpty()<\/h5>\n<p>This <b>API<\/b> just read <b>STATUS<\/b> register and checks RX FIFO status returning TRUE if there re packets in it.<\/p>\n<pre>\/**\n * @brief   Checks if there are packets in the RX FIFO.\n *\n * @param[in] rfp        Pointer to the @p RFDriver object\n * @return               The operation result.\n * @retval TRUE          There is a packet.\n * @retval FALSE         RX FIFO is empty.\n * @api\n *\/\nbool rfRxIsNonEmpty(RFDriver *rfp) {\n  uint8_t fifo_status;\n    nrf24l01ReadRegister(rfp-&gt;config-&gt;spip,\n                         NRF24L01_AD_FIFO_STATUS, &amp;fifo_status);\n    return (!(fifo_status &amp; NRF24L01_DI_FIFO_STATUS_RX_EMPTY));\n  return FALSE;\n}<\/pre>\n<h5 id=\"20_rfReceive_\" class=\"level_3\">rfReceive()<\/h5>\n<p>This <b>API<\/b> set <b>nRF24L01<\/b> to receive packets. Operation performed here are described in Appendix A of the <b>nRF24L01<\/b> reference manual. Anyway, here thinks are a little bit more complicated since we don&rsquo;t want to transfer a single packet but <b>n<\/b> packets. The user must provide a pointer to an array of RX frames which lenght is <b>n<\/b>. To avoid unnecessary complications here it is supposed that:<\/p>\n<ol>\n<li>Packets arrive from the same transmitter;<\/li>\n<li>Transmitter sends at least n packet sequentially (or operation will go in timeout);<\/li>\n<li>Someone send an event when <b>IRQ<\/b> is active (actually we do this in EXT callback);<\/li>\n<\/ol>\n<p>This function configures <b>nRF24L01<\/b> for receive acting on <b>CONFIG<\/b> register, then set <b>TX_ADDR<\/b> and <b>RX_ADDR<\/b> according to address reported into the first RX frame (<b>TX_ADDR<\/b> is required to be the same of <b>RX_ADDR<\/b> by Enhanced ShockBurst to send out acknowledge). After that flush RX FIFO, reset a counter, updates driver state and set <b>CE<\/b> pin: from now on <b>nRF24L01<\/b> is listening.<\/p>\n<p>We now enter in a while that terminates when receive is completed or is prematurely interrupted for timeout or error occurrence. Entering into the loop we immediately wait for an event. If event goes timed out wait returns 0 so we update the driver state, pull down the <b>CE<\/b> and return a <b>RF_TIMEOUT<\/b> message. If events occurs we check <b>STATUS<\/b> register and RX FIFO and if everything is ok we get a payload, increment the counter, reset the <b>IRQ<\/b> and continue the loop, otherwise we terminate the loop with <b>RF_ERROR<\/b> message. If we terminate the loop without a return, communication succeeds and we can return an <b>RF_OK<\/b> message.<\/p>\n<pre>\/**\n * @brief   Receives n Rx frames from the RF Complex Driver peripheral.\n *\n * @param[in] rfp        Pointer to the @p RFDriver object\n * @param[in] n          RFTxFrame array length\n * @param[in] rxbuff     Pointer to an array of @p RFRxFrame\n * @param[in] time       The number of ticks before the operation timeouts,\n *                       the following special values are allowed:\n *                      - @a TIME_IMMEDIATE immediate timeout.\n *                      - @a TIME_INFINITE no timeout.\n *                      .\n * @return               The operation result.\n * @retval RF_OK         The operation succeeds.\n * @retval RF_ERROR      Error during the transmission.\n * @api\n *\/\nrf_msg_t rfReceive(RFDriver *rfp, uint32_t n, RFRxFrame *rxbuff,\n                   systime_t time) {\n  uint8_t status;\n  uint32_t cnt;\n  osalDbgCheck((rfp != NULL) &amp;&amp; (rxbuff != NULL) &amp;&amp; (n &gt; 0));\n  osalDbgAssert((rfp-&gt;state == RF_READY),\n              \"rfReceive(), invalid state\");\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_CONFIG,\n                        NRF24L01_DI_CONFIG_PWR_UP |\n                        NRF24L01_DI_CONFIG_EN_CRC |\n                        NRF24L01_DI_CONFIG_PRIM_RX);\n  nrf24l01WriteAddress(rfp-&gt;config-&gt;spip, NRF24L01_AD_TX_ADDR,\n                       rxbuff-&gt;rx_address, RF_ADDLEN);\n  nrf24l01WriteAddress(rfp-&gt;config-&gt;spip,\n                       NRF24L01_AD_RX_ADDR_P1,\n                       rxbuff-&gt;rx_address, RF_ADDLEN);\n  nrf24l01FlushRx(rfp-&gt;config-&gt;spip);\n  nrf24l01Reset(rfp-&gt;config-&gt;spip);\n  cnt = 0;\n  rfp-&gt;state = RF_RX;\n  palSetPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n  while(cnt &lt; n) { if(chEvtWaitOneTimeout(ALL_EVENTS, time) == 0) { rfp-&gt;state = RF_READY;\n      palClearPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n      return RF_TIMEOUT;\n    }\n    status = nrf24l01GetStatus(rfp-&gt;config-&gt;spip);\n    if (((status &amp; NRF24L01_DI_STATUS_RX_DR) ||\n        (status &amp; NRF24L01_DI_STATUS_TX_DS)) &amp;&amp; (rfRxIsNonEmpty(rfp))) {\n      nrf24l01ReadRxPlWid(rfp-&gt;config-&gt;spip, &amp;(rxbuff + cnt)-&gt;rx_paylen);\n      osalDbgCheck((rxbuff + cnt)-&gt;rx_paylen &lt;= RF_PAYLEN); nrf24l01GetRxPl(rfp-&gt;config-&gt;spip, (rxbuff + cnt)-&gt;rx_paylen,\n                      (rxbuff + cnt)-&gt;rx_payload);\n      cnt++;\n      nrf24l01Reset(rfp-&gt;config-&gt;spip);\n      continue;\n    }\n    else {\n      nrf24l01Reset(rfp-&gt;config-&gt;spip);\n      rfp-&gt;state = RF_READY;\n      palClearPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n      return RF_ERROR;\n    }\n    palClearPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n  }\n  rfp-&gt;state = RF_READY;\n  palClearPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n  return RF_OK;\n}<\/pre>\n<h5 id=\"21_rfTransmit_\" class=\"level_3\">rfTransmit()<\/h5>\n<p>This function configures <b>nRF24L01<\/b> for transmit acting on <b>CONFIG<\/b> register, then set <b>TX_ADDR<\/b> and <b>RX_ADDR<\/b> according to address reported into the first TX frame (like <b>rfReceive()<\/b>, <b>RX_ADDR<\/b> is required to be the same of <b>TX_ADDR<\/b> by Enhanced ShockBurst to receive a transmission acknowledge). After that makes simple operation to prepare transmission and enter into the loop.<\/p>\n<p>Note that, unlike <b>rfReceive()<\/b> we have here a flag. We use it to ensure that we send a new packet only if previous has been delivered correctly.<\/p>\n<p>Like <b>rfReceive()<\/b> loop terminates when transmission is completed or is prematurely interrupted for timeout or max retransmit occurrence. Entering into the loop we check for empty spaces in TX FIFO and if flag is TRUE (of course this condition is verified on first cycle), if this condition occurs we perform a transmission and we lower the flag. After that we wait for an event: on timeout we made the usual operations returning a <b>RF_TIMEOUT<\/b> message. Otherwise (if events occurs), we check <b>STATUS<\/b> register and we can have two conditions.<\/p>\n<ol>\n<li>TX_DS is high, that means that packet has been delivered. We can reset the <b>IRQ<\/b>, set flag as TRUE, update the counter and continue;<\/li>\n<li>MAX_RT is high, that means Enhanced ShockBurst tries to resend packet for <b>Auto Retransmit Count<\/b> times with a delay of <b>Auto Retransmit Delay<\/b> unsuccessfully and we terminate the loop with <b>RF_ERROR<\/b> message.<\/li>\n<\/ol>\n<p>If we terminate the loop without a return, communication succeeds and we can return an <b>RF_OK<\/b> message.<\/p>\n<pre>\/**\n * @brief   Transmits n Tx frames on the RF Complex Driver peripheral.\n *\n * @param[in] rfp        Pointer to the @p RFDriver object\n * @param[in] n          RFTxFrame array length\n * @param[in] txbuff     Pointer to an array of @p RFTxFrame\n * @param[in] time       The number of ticks before the operation timeouts,\n *                       the following special values are allowed:\n *                      - @a TIME_IMMEDIATE immediate timeout.\n *                      - @a TIME_INFINITE no timeout.\n *                      .\n *\n * @return               The operation result.\n * @retval RF_OK         The operation succeeds.\n * @retval RF_ERROR      Error during the transmission.\n * @api\n *\/\nrf_msg_t rfTransmit(RFDriver *rfp, uint32_t n, RFTxFrame *txbuff,\n                    systime_t time) {\n  uint8_t status;\n  uint32_t cnt;\n  bool flag;\n  osalDbgCheck((rfp != NULL) &amp;&amp; (txbuff != NULL));\n  osalDbgAssert((rfp-&gt;state == RF_READY),\n              \"rfTransmit(), invalid state\");\n  nrf24l01WriteRegister(rfp-&gt;config-&gt;spip, NRF24L01_AD_CONFIG,\n                        NRF24L01_DI_CONFIG_PWR_UP |\n                        NRF24L01_DI_CONFIG_EN_CRC);\n  nrf24l01WriteAddress(rfp-&gt;config-&gt;spip, NRF24L01_AD_TX_ADDR,\n                       txbuff-&gt;tx_address, RF_ADDLEN);\n  nrf24l01WriteAddress(rfp-&gt;config-&gt;spip,\n                       NRF24L01_AD_RX_ADDR_P0,\n                       txbuff-&gt;tx_address, RF_ADDLEN);\n  nrf24l01Reset(rfp-&gt;config-&gt;spip);\n  nrf24l01FlushTx(rfp-&gt;config-&gt;spip);\n  cnt = 0;\n  flag = TRUE;\n  rfp-&gt;state = RF_TX;\n  while(cnt &lt; n) { if(rfTxIsEmpty(rfp) &amp;&amp; flag) { osalDbgCheck((txbuff + cnt)-&gt;tx_paylen &lt;= RF_PAYLEN); nrf24l01WriteTxPl(rfp-&gt;config-&gt;spip, (txbuff + cnt)-&gt;tx_paylen,\n                        (txbuff + cnt)-&gt;tx_payload);\n      palSetPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n      osalThreadSleepMilliseconds(1);\n      palClearPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n      flag = FALSE;\n    }\n    if(chEvtWaitOneTimeout(ALL_EVENTS, time) == 0) {\n      rfp-&gt;state = RF_READY;\n      palClearPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n      return RF_TIMEOUT;\n    }\n    status = nrf24l01GetStatus(rfp-&gt;config-&gt;spip);\n    if (status &amp; NRF24L01_DI_STATUS_TX_DS) {\n      nrf24l01Reset(rfp-&gt;config-&gt;spip);\n      flag = TRUE;\n      cnt++;\n      continue;\n    }\n    if (status &amp; NRF24L01_DI_STATUS_MAX_RT) {\n      nrf24l01Reset(rfp-&gt;config-&gt;spip);\n      rfp-&gt;state = RF_READY;\n      palClearPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n      return RF_ERROR;\n    }\n  }\n  rfp-&gt;state = RF_READY;\n  palClearPad(rfp-&gt;config-&gt;ceport, rfp-&gt;config-&gt;cepad);\n  return RF_OK;\n}<\/pre>\n<h5 id=\"22_rfReceiveString_\" class=\"level_3\">rfReceiveString()<\/h5>\n<p>This API receive a packet using <b>rfReceive()<\/b> and use payload to compose a string. User must provide a buffer which length is at least <b>RF_PAYLEN<\/b> + 1 to avoid buffer overflow.<\/p>\n<pre>\/**\n * @brief   Receives a string from an addressed channel.\n *\n * @param[in] rfp        Pointer to the @p RFDriver object\n * @param[in] sp         String pointer. The associated buffer should be long at\n *                       least RF_PAYLEN + 1\n * @param[in] addp       Channel address as string\n * @param[in] time       The number of ticks before the operation timeouts,\n *                       the following special values are allowed:\n *                      - @a TIME_IMMEDIATE immediate timeout.\n *                      - @a TIME_INFINITE no timeout.\n *\n * @return               The operation result.\n * @retval RF_OK         The operation succeeds.\n * @retval RF_ERROR      Error during the transmission.\n * @api\n *\/\nrf_msg_t rfReceiveString(RFDriver *rfp, char* sp, char* addp,\n                   systime_t time) {\n  RFRxFrame _rxframe;\n  rf_msg_t msg;\n  unsigned i;\n  osalDbgCheck((rfp != NULL) &amp;&amp; (sp != NULL) &amp;&amp; (addp != NULL));\n  osalDbgAssert((rfp-&gt;state == RF_READY),\n              \"rfReceive(), invalid state\");\n  if(strlen(addp) &lt; RF_ADDLEN){\n    *sp = '\\0';\n    return RF_ERROR;\n  }\n  for (i = 0; i &lt; RF_ADDLEN; i++) {\n    _rxframe.rx_address[i] = (uint8_t)*addp;\n    addp++;\n  }\n  _rxframe.rx_paylen = RF_PAYLEN;\n  msg = rfReceive(rfp, 1, &amp;_rxframe, time);\n  if(msg == RF_OK){\n    for (i = 0; i &lt; RF_PAYLEN; i++) {\n      if(_rxframe.rx_payload[i] == '\\0'){\n        *sp = '\\0';\n        return msg;\n      }\n      else{\n        *sp = _rxframe.rx_payload[i];\n        sp++;\n      }\n    }\n    *sp = '\\0';\n  }\n  return msg;\n}<\/pre>\n<h5 id=\"23_rfTransmitString_\" class=\"level_3\">rfTransmitString()<\/h5>\n<p>This API compose a packet from a string and send out it using <b>rfTransmit()<\/b>. Max string lenght allowed is <b>RF_PAYLEN<\/b>, otherwise string will be partially transmitted.<\/p>\n<pre>\/**\n * @brief   Transmits a string from an addressed channel.\n *\n * @param[in] rfp        Pointer to the @p RFDriver object\n * @param[in] sp         String pointer. String cannot be longer than\n *                       RF_PAYLEN\n * @param[in] addp       Channel address as string\n * @param[in] time       The number of ticks before the operation timeouts,\n *                       the following special values are allowed:\n *                      - @a TIME_IMMEDIATE immediate timeout.\n *                      - @a TIME_INFINITE no timeout.\n *\n * @return               The operation result.\n * @retval RF_OK         The operation succeeds.\n * @retval RF_ERROR      Error during the transmission.\n * @api\n *\/\nrf_msg_t rfTransmitString(RFDriver *rfp, char* sp, char* addp,\n                          systime_t time){\n  RFTxFrame _txframe;\n  rf_msg_t msg;\n  unsigned i;\n  uint32_t len = strlen(sp);\n  uint8_t* p = (uint8_t*)addp;\n  osalDbgCheck((rfp != NULL) &amp;&amp; (sp != NULL) &amp;&amp; (addp != NULL));\n  osalDbgAssert((rfp-&gt;state == RF_READY),\n              \"rfReceive(), invalid state\");\n  for (i = 0; i &lt; 5; i++) { _txframe.tx_address[i] = (uint8_t)*p ; p++; } _txframe.tx_paylen = RF_PAYLEN; if (len &gt; RF_MAX_STRLEN)\n    len = RF_MAX_STRLEN;\n  for(i = 0; i &lt; len; i++){\n    _txframe.tx_payload[i] = *sp ;\n    sp++;\n  }\n  _txframe.tx_payload[len] = '\\0' ;\n  msg = rfTransmit(rfp, 1, &amp;_txframe, time);\n  return msg;\n}<\/pre>\n<h5 id=\"24_rfExtCallBack_\" class=\"level_3\">rfExtCallBack()<\/h5>\n<p>I don&rsquo;t kow if this function could be considered an API, anyway it must be used by user in <b>EXTConf<\/b>. This is the callback used by EXT when an <b>IRQ<\/b> occurs. It lock the system from ISR, update a counter, broadcasts the event flag and unlock the system. Note that <b>cbcounter<\/b> has debug purposes.<\/p>\n<pre>\/**\n * @brief   This is the callback used by EXT on interrupt request.\n *\n * @notapi\n *\/\nvoid rfExtCallBack(EXTDriver *extp, expchannel_t channel) {\n  (void) extp;\n  osalSysLockFromISR();\n  cbcounter++;\n  if(channel == RFD1.config-&gt;irqpad) {\n    osalEventBroadcastFlagsI(&amp;RFD1.irq_event, RF_GENERIC_IRQ);\n  }\n  osalSysUnlockFromISR();\n}<\/pre>\n<h5 id=\"25_rfAcquireBus_\" class=\"level_3\">rfAcquireBus()<\/h5>\n<p>This API lock the driver mutex and it is used to gains exclusive access to the RF driver. It could be used when concurrent threads try to access the RF resource.<\/p>\n<pre>\/**\n * @brief   Gains exclusive access to the RF.\n * @details This function tries to gain ownership to the RF, if the bus\n *          is already being used then the invoking thread is queued.\n * @pre     In order to use this function the option @p RF_USE_MUTUAL_EXCLUSION\n *          must be enabled.\n *\n * @param[in] rfp      pointer to the @p RFDriver object\n *\n * @api\n *\/\nvoid rfAcquireBus(RFDriver *rfp) {\n  osalDbgCheck(rfp != NULL);\n  osalMutexLock(&amp;rfp-&gt;mutex);\n}<\/pre>\n<h5 id=\"26_rfReleaseBus_\" class=\"level_3\">rfReleaseBus()<\/h5>\n<p>This is the dual of <b>rfAcquireBus()<\/b>.<\/p>\n<pre>\/**\n * @brief   Releases exclusive access to the RF.\n * @pre     In order to use this function the option @p RF_USE_MUTUAL_EXCLUSION\n *          must be enabled.\n *\n * @param[in] rfp      pointer to the @p RFDriver object\n *\n * @api\n *\/\nvoid rfReleaseBus(RFDriver *rfp) {\n  osalDbgCheck(rfp != NULL);\n  osalMutexUnlock(&amp;rfp-&gt;mutex);\n}<\/pre>\n<h3 id=\"27_Proposed_demo\" class=\"level_1\">Proposed demo<\/h3>\n<h4 id=\"28_Demo_explained\" class=\"level_2\">Demo explained<\/h4>\n<p>The proposed demo uses two <b>STM32 Nucleo-64 F4<\/b> (<b>STM32F401RE<\/b>) connected to two <b>nRF24L01<\/b>. We configure RF and use it to exchange strings. We have a receiver and a transmitter both using serial communication to provide a simple user interface.<\/p>\n<pre>\/*\n    PLAY Embedded demos - Copyright (C) 2014-2015 Rocco Marco Guglielmi\n    This file is part of PLAY Embedded demos.\n    This demo is free software; you can redistribute it and\/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 3 of the License, or\n    (at your option) any later version.\n    This demo is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see &lt;http:\/\/www.gnu.org\/licenses\/&gt;.\n*\/\n#include \"ch.h\"\n#include \"hal.h\"\n#include \"rf.h\"\n#include \"string.h\"\n#define  TRANSMITTER                    TRUE\n#define  GPIOA_RF_CE                       9\n#define  GPIOC_RF_IRQ                      7\n#define  GPIOB_RF_SPID1_CS                 6\n#define  GPIOA_RF_SPID1_SCK                5\n#define  GPIOA_RF_SPID1_MISO               6\n#define  GPIOA_RF_SPID1_MOSI               7\n#define  FRAME_LEN                         5\nstatic const SPIConfig std_spi_cfg = {\n  NULL,\n  GPIOB,                                   \/*   port of CS  *\/\n  GPIOB_RF_SPID1_CS,                       \/*   pin of CS   *\/\n  SPI_CR1_BR_1 | SPI_CR1_BR_0              \/*   CR1 register*\/\n};\nstatic const EXTConfig extcfg = {\n  {\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_FALLING_EDGE |\n     EXT_CH_MODE_AUTOSTART |\n     EXT_MODE_GPIOC, rfExtCallBack},            \/* IRQ line connected to PC7 *\/\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL},\n    {EXT_CH_MODE_DISABLED, NULL}\n  }\n};\nstatic RFConfig nrf24l01_cfg = {\n  GPIOA,\n  GPIOA_RF_CE,\n  GPIOC,\n  GPIOC_RF_IRQ,\n  &amp;SPID1,\n  &amp;std_spi_cfg,\n  &amp;EXTD1,\n  &amp;extcfg,\n  NRF24L01_ARC_15_times,     \/* auto_retr_count *\/\n  NRF24L01_ARD_4000us,       \/* auto_retr_delay *\/\n  NRF24L01_AW_5_bytes,       \/* address_width *\/\n  120,                       \/* channel_freq 2.4 + 0.12 GHz *\/\n  NRF24L01_ADR_2Mbps,        \/* data_rate *\/\n  NRF24L01_PWR_0dBm,         \/* out_pwr *\/\n  NRF24L01_LNA_disabled,     \/* lna *\/\n  NRF24L01_DPL_enabled ,     \/* en_dpl *\/\n  NRF24L01_ACK_PAY_disabled, \/* en_ack_pay *\/\n  NRF24L01_DYN_ACK_disabled  \/* en_dyn_ack *\/\n};\n\/*===========================================================================*\/\n\/* Generic code.                                                             *\/\n\/*===========================================================================*\/\n\/*\n * Green LED blinker thread, times are in milliseconds.\n *\/\nstatic THD_WORKING_AREA(waThread, 512);\nstatic THD_FUNCTION(Thread, arg) {\n  char string[RF_MAX_STRLEN + 1];\n  uint32_t strl;\n  (void)arg;\n  chRegSetThreadName(\"RF thread\");\n  rfStart(&amp;RFD1, &amp;nrf24l01_cfg);\n  while (TRUE) {\n#if TRANSMITTER == TRUE\n    char c;\n    rf_msg_t msg;\n    uint8_t counter = 0;\n    string[0] = '\\0';\n    while(TRUE){\n      c = chnGetTimeout((&amp;SD2), TIME_INFINITE);\n      if((c == '\\r') || (counter == RF_MAX_STRLEN)){\n        string[counter] = '\\0';\n        chnWrite(&amp;SD2, (const uint8_t *)\"\\n\\r\", 2);\n        break;\n      }\n      else if((c &gt; 31) &amp;&amp; (c &lt; 127)){\n        string[counter] = c;\n        counter++;\n        chnPutTimeout((&amp;SD2), c, TIME_INFINITE);\n      }\n    }\n    strl = strlen(string);\n    if(strl){\n      msg = rfTransmitString(&amp;RFD1, string, \"RXadd\", MS2ST(75));\n      if(msg == RF_OK){\n        chnWrite(&amp;SD2, (const uint8_t *)\"Message sent\\n\\r\", 14);\n      }\n      else if(msg == RF_ERROR){\n        chnWrite(&amp;SD2, (const uint8_t *)\"Message not sent (MAX_RT)\\n\\r\", 27);\n      }\n      else{\n        chnWrite(&amp;SD2, (const uint8_t *)\"Message not sent (TIMEOUT)\\n\\r\", 28);\n      }\n    }\n    chThdSleepMilliseconds(50);\n#else\n    string[0] = '\\0';\n    rfReceiveString(&amp;RFD1, string, \"RXadd\", MS2ST(2));\n    strl = strlen(string);\n    if(strl){\n      chnWrite(&amp;SD2, (const uint8_t *)string, strl);\n      chnWrite(&amp;SD2, (const uint8_t *)\"\\n\\r\", 2);\n    }\n#endif\n  }\n  rfStop(&amp;RFD1);\n}\n\/*\n * Application entry point.\n *\/\nint main(void) {\n  \/*\n   * System initializations.\n   * - HAL initialization, this also initializes the configured device drivers\n   *   and performs the board-specific initializations.\n   * - Kernel initialization, the main() function becomes a thread and the\n   *   RTOS is active.\n   *\/\n  halInit();\n  chSysInit();\n  \/*\n   * Application library initialization.\n   * - PLAY initialization, this also initializes the configured device drivers\n   *   and performs the board-specific initializations.\n   *\/\n  rfInit();\n  \/*\n   * SPID1 I\/O pins setup.(It bypasses board.h configurations)\n   *\/\n  palSetPadMode(GPIOA, GPIOA_RF_SPID1_SCK,\n                 PAL_MODE_ALTERNATE(5) | PAL_STM32_OSPEED_HIGHEST);   \/* New SCK *\/\n  palSetPadMode(GPIOA, GPIOA_RF_SPID1_MISO,\n                 PAL_MODE_ALTERNATE(5) | PAL_STM32_OSPEED_HIGHEST);   \/* New MISO*\/\n  palSetPadMode(GPIOA, GPIOA_RF_SPID1_MOSI,\n                 PAL_MODE_ALTERNATE(5) | PAL_STM32_OSPEED_HIGHEST);   \/* New MOSI*\/\n  palSetPadMode(GPIOB, GPIOB_RF_SPID1_CS,\n                 PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);\/* New CS  *\/\n  \/*\n   * CE and IRQ pins setup.\n   *\/\n  palSetPadMode(GPIOA, GPIOA_RF_CE,\n                 PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);\/* New CE  *\/\n  palSetPadMode(GPIOC, GPIOC_RF_IRQ,\n                 PAL_MODE_INPUT | PAL_STM32_OSPEED_HIGHEST);          \/* New IRQ  *\/\n  sdStart(&amp;SD2, NULL);\n  chThdCreateStatic(waThread, sizeof(waThread), NORMALPRIO, Thread, NULL);\n  while(TRUE){\n    chThdSleepMilliseconds(500);\n  }\n}<\/pre>\n<p>To use this demo you need to connect <b>nRF24L01<\/b> to your <b>ST NUCLEO<\/b> using these pins:<\/p>\n<ol>\n<li>GND -&gt; GND<\/li>\n<li>VCC -&gt; 3.3V<\/li>\n<li>CE -&gt; PA9<\/li>\n<li>CSN -&gt; PB6<\/li>\n<li>SCK -&gt; PA5<\/li>\n<li>MOSI -&gt; PA7<\/li>\n<li>MISO -&gt; PA6<\/li>\n<li>IRQ -&gt; PC7<\/li>\n<\/ol>\n<p>You can choose other pin but you should edit <i>main.c<\/i> to make it working. Note that <b>EXTConf<\/b> is written to detect high to low transition on pin PC7: changing IRQ pin requires some edit to this configuration.<\/p>\n<p>Note that a <b>TRANSMITTER<\/b> allow to switch part of code so you need to set this define TRUE for transmitter and FALSE for receiver. Remember to save and rebuild before flashing.<\/p>\n<p>Now you have two MCUs with different code. Connect to them using two terminal instances and try to write something on transmitter terminal.<\/p>\n<h3 id=\"29_Project_download\" class=\"level_1\">Project download<\/h3>\n<p>The attached demo has been tested under ChibiOS 20.3.x.<\/p>\n<p><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2021\/07\/RT-STM32F401RE-NUCLEO64-NRF24L01-String-216.7z\" rel=\"\">RT-STM32F401RE-NUCLEO64-NRF24L01-String-216<\/a><\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>Cheapest and most popular RF transceiver than ever The nRF24L01 is one of the most popular RF transceiver as it is very cheap and it could be found already configured on small PCBs with needed passive components. This means that only a MCU is needed to design a radio system with the nRF24L01. Description The [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":2384,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1225],"tags":[],"coauthors":[241],"class_list":["post-2179","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-interfacing-externals","red"],"views":18674,"jetpack_featured_media_url":"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/A-Radio-Frequency-transceiver-library-nRF24L01-and-ChibiOSRT.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts\/2179","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/comments?post=2179"}],"version-history":[{"count":0,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts\/2179\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/media\/2384"}],"wp:attachment":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/media?parent=2179"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/categories?post=2179"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/tags?post=2179"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/coauthors?post=2179"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}