text: 
  #include "freertos/FreeRTOS.h"
  #include "freertos/task.h"
  #include "esp_system.h"
  #include "driver/spi_master.h"
  #include "soc/gpio_struct.h"
  #include "driver/gpio.h"
  #include <string.h>
  
  /*
   * Register definitions
   */
  #define REG_FIFO                       0x00
  #define REG_OP_MODE                    0x01
  #define REG_FRF_MSB                    0x06
  #define REG_FRF_MID                    0x07
  #define REG_FRF_LSB                    0x08
  #define REG_PA_CONFIG                  0x09
  #define REG_LNA                        0x0c
  #define REG_FIFO_ADDR_PTR              0x0d
  #define REG_FIFO_TX_BASE_ADDR          0x0e
  #define REG_FIFO_RX_BASE_ADDR          0x0f
  #define REG_FIFO_RX_CURRENT_ADDR       0x10
  #define REG_IRQ_FLAGS                  0x12
  #define REG_RX_NB_BYTES                0x13
  #define REG_PKT_SNR_VALUE              0x19
  #define REG_PKT_RSSI_VALUE             0x1a
  #define REG_MODEM_CONFIG_1             0x1d
  #define REG_MODEM_CONFIG_2             0x1e
  #define REG_PREAMBLE_MSB               0x20
  #define REG_PREAMBLE_LSB               0x21
  #define REG_PAYLOAD_LENGTH             0x22
  #define REG_MODEM_CONFIG_3             0x26
  #define REG_RSSI_WIDEBAND              0x2c
  #define REG_DETECTION_OPTIMIZE         0x31
  #define REG_DETECTION_THRESHOLD        0x37
  #define REG_SYNC_WORD                  0x39
  #define REG_DIO_MAPPING_1              0x40
  #define REG_VERSION                    0x42
  
  /*
   * Transceiver modes
   */
  #define MODE_LONG_RANGE_MODE           0x80
  #define MODE_SLEEP                     0x00
  #define MODE_STDBY                     0x01
  #define MODE_TX                        0x03
  #define MODE_RX_CONTINUOUS             0x05
  #define MODE_RX_SINGLE                 0x06
  
  /*
   * PA configuration
   */
  #define PA_BOOST                       0x80
  
  /*
   * IRQ masks
   */
  #define IRQ_TX_DONE_MASK               0x08
  #define IRQ_PAYLOAD_CRC_ERROR_MASK     0x20
  #define IRQ_RX_DONE_MASK               0x40
  
  #define PA_OUTPUT_RFO_PIN              0
  #define PA_OUTPUT_PA_BOOST_PIN         1
  
  #define TIMEOUT_RESET                  100
  
  static spi_device_handle_t __spi;
  
  static int __implicit;
  static long __frequency;
  
  #ifndef CONFIG_CS_GPIO
      #define CONFIG_CS_GPIO      5
  #endif
  
  #ifndef CONFIG_RST_GPIO
      #define CONFIG_RST_GPIO     13
  #endif
  
  #ifndef CONFIG_MISO_GPIO
      #define CONFIG_MISO_GPIO    19
  #endif
  
  #ifndef CONFIG_MOSI_GPIO
      #define CONFIG_MOSI_GPIO    23
  #endif
  
  #ifndef CONFIG_SCK_GPIO
      #define CONFIG_SCK_GPIO     18
  #endif
  
  static int CS_GPIO = CONFIG_CS_GPIO;
  static int RST_GPIO = CONFIG_RST_GPIO;
  static int MISO_GPIO = CONFIG_MISO_GPIO;
  static int MOSI_GPIO = CONFIG_MOSI_GPIO;
  static int SCK_GPIO = CONFIG_SCK_GPIO;
  
  void
  lora_set_pins(int cs, int rst, int miso, int mosi, int sck) {
      CS_GPIO = cs;
      RST_GPIO = rst;
      MISO_GPIO = miso;
      MOSI_GPIO = mosi;
      SCK_GPIO = sck;
  }
  /**
   * Write a value to a register.
   * @param reg Register index.
   * @param val Value to write.
   */
  void 
  lora_write_reg(int reg, int val)
  {
     uint8_t out[2] = { 0x80 | reg, val };
     uint8_t in[2];
  
     spi_transaction_t t = {
        .flags = 0,
        .length = 8 * sizeof(out),
        .tx_buffer = out,
        .rx_buffer = in,
        .cmd = 0,
        .addr = 0,
        .rxlength = 8 * sizeof(in),
        .user = NULL,
     };
  
     gpio_set_level(CS_GPIO, 0);
     spi_device_transmit(__spi, &t);
     gpio_set_level(CS_GPIO, 1);
  }
  
  /**
   * Read the current value of a register.
   * @param reg Register index.
   * @return Value of the register.
   */
  int
  lora_read_reg(int reg)
  {
     uint8_t out[2] = { reg, 0xff };
     uint8_t in[2];
  
     spi_transaction_t t = {
        .flags = 0,
        .length = 8 * sizeof(out),
        .tx_buffer = out,
        .rx_buffer = in,
        .cmd = 0,
        .addr = 0,
        .rxlength = 8 * sizeof(in),
        .user = NULL,
     };
  
     gpio_set_level(CS_GPIO, 0);
     spi_device_transmit(__spi, &t);
     gpio_set_level(CS_GPIO, 1);
     return in[1];
  }
  
  /**
   * Perform physical reset on the Lora chip
   */
  void 
  lora_reset(void)
  {
     if (RST_GPIO < 0)
         return;
     gpio_set_level(RST_GPIO, 0);
     vTaskDelay(pdMS_TO_TICKS(1));
     gpio_set_level(RST_GPIO, 1);
     vTaskDelay(pdMS_TO_TICKS(10));
  }
  
  /**
   * Configure explicit header mode.
   * Packet size will be included in the frame.
   */
  void 
  lora_explicit_header_mode(void)
  {
     __implicit = 0;
     lora_write_reg(REG_MODEM_CONFIG_1, lora_read_reg(REG_MODEM_CONFIG_1) & 0xfe);
  }
  
  /**
   * Configure implicit header mode.
   * All packets will have a predefined size.
   * @param size Size of the packets.
   */
  void 
  lora_implicit_header_mode(int size)
  {
     __implicit = 1;
     lora_write_reg(REG_MODEM_CONFIG_1, lora_read_reg(REG_MODEM_CONFIG_1) | 0x01);
     lora_write_reg(REG_PAYLOAD_LENGTH, size);
  }
  
  /**
   * Sets the radio transceiver in idle mode.
   * Must be used to change registers and access the FIFO.
   */
  void 
  lora_idle(void)
  {
     lora_write_reg(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY);
  }
  
  /**
   * Sets the radio transceiver in sleep mode.
   * Low power consumption and FIFO is lost.
   */
  void 
  lora_sleep(void)
  { 
     lora_write_reg(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP);
  }
  
  /**
   * Sets the radio transceiver in receive mode.
   * Incoming packets will be received.
   */
  void 
  lora_receive(void)
  {
     lora_write_reg(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS);
  }
  
  /**
   * Configure power level for transmission
   * @param level 2-17, from least to most power
   */
  void 
  lora_set_tx_power(int level)
  {
     // RF9x module uses PA_BOOST pin
     if (level < 2) level = 2;
     else if (level > 17) level = 17;
     lora_write_reg(REG_PA_CONFIG, PA_BOOST | (level - 2));
  }
  
  /**
   * Set carrier frequency.
   * @param frequency Frequency in Hz
   */
  void 
  lora_set_frequency(long frequency)
  {
     __frequency = frequency;
  
     uint64_t frf = ((uint64_t)frequency << 19) / 32000000;
  
     lora_write_reg(REG_FRF_MSB, (uint8_t)(frf >> 16));
     lora_write_reg(REG_FRF_MID, (uint8_t)(frf >> 8));
     lora_write_reg(REG_FRF_LSB, (uint8_t)(frf >> 0));
  }
  
  /**
   * Set spreading factor.
   * @param sf 6-12, Spreading factor to use.
   */
  void 
  lora_set_spreading_factor(int sf)
  {
     if (sf < 6) sf = 6;
     else if (sf > 12) sf = 12;
  
     if (sf == 6) {
        lora_write_reg(REG_DETECTION_OPTIMIZE, 0xc5);
        lora_write_reg(REG_DETECTION_THRESHOLD, 0x0c);
     } else {
        lora_write_reg(REG_DETECTION_OPTIMIZE, 0xc3);
        lora_write_reg(REG_DETECTION_THRESHOLD, 0x0a);
     }
  
     lora_write_reg(REG_MODEM_CONFIG_2, (lora_read_reg(REG_MODEM_CONFIG_2) & 0x0f) | ((sf << 4) & 0xf0));
  }
  
  /**
   * Set bandwidth (bit rate)
   * @param sbw Bandwidth in Hz (up to 500000)
   */
  void 
  lora_set_bandwidth(long sbw)
  {
     int bw;
  
     if (sbw <= 7.8E3) bw = 0;
     else if (sbw <= 10.4E3) bw = 1;
     else if (sbw <= 15.6E3) bw = 2;
     else if (sbw <= 20.8E3) bw = 3;
     else if (sbw <= 31.25E3) bw = 4;
     else if (sbw <= 41.7E3) bw = 5;
     else if (sbw <= 62.5E3) bw = 6;
     else if (sbw <= 125E3) bw = 7;
     else if (sbw <= 250E3) bw = 8;
     else bw = 9;
     lora_write_reg(REG_MODEM_CONFIG_1, (lora_read_reg(REG_MODEM_CONFIG_1) & 0x0f) | (bw << 4));
  }
  
  /**
   * Set coding rate 
   * @param denominator 5-8, Denominator for the coding rate 4/x
   */ 
  void 
  lora_set_coding_rate(int denominator)
  {
     if (denominator < 5) denominator = 5;
     else if (denominator > 8) denominator = 8;
  
     int cr = denominator - 4;
     lora_write_reg(REG_MODEM_CONFIG_1, (lora_read_reg(REG_MODEM_CONFIG_1) & 0xf1) | (cr << 1));
  }
  
  /**
   * Set the size of preamble.
   * @param length Preamble length in symbols.
   */
  void 
  lora_set_preamble_length(long length)
  {
     lora_write_reg(REG_PREAMBLE_MSB, (uint8_t)(length >> 8));
     lora_write_reg(REG_PREAMBLE_LSB, (uint8_t)(length >> 0));
  }
  
  /**
   * Change radio sync word.
   * @param sw New sync word to use.
   */
  void 
  lora_set_sync_word(int sw)
  {
     lora_write_reg(REG_SYNC_WORD, sw);
  }
  
  /**
   * Enable appending/verifying packet CRC.
   */
  void 
  lora_enable_crc(void)
  {
     lora_write_reg(REG_MODEM_CONFIG_2, lora_read_reg(REG_MODEM_CONFIG_2) | 0x04);
  }
  
  /**
   * Disable appending/verifying packet CRC.
   */
  void 
  lora_disable_crc(void)
  {
     lora_write_reg(REG_MODEM_CONFIG_2, lora_read_reg(REG_MODEM_CONFIG_2) & 0xfb);
  }
  
  /**
   * Perform hardware initialization.
   */
  int 
  lora_init(void)
  {
     esp_err_t ret;
  
     /*
      * Configure CPU hardware to communicate with the radio chip
      */
     if (RST_GPIO >= 0) {
         gpio_pad_select_gpio(RST_GPIO);
         gpio_set_direction(RST_GPIO, GPIO_MODE_OUTPUT);
     }
     gpio_pad_select_gpio(CS_GPIO);
     gpio_set_direction(CS_GPIO, GPIO_MODE_OUTPUT);
  
     gpio_pad_select_gpio(MISO_GPIO);
     gpio_set_direction(MISO_GPIO, GPIO_MODE_INPUT);
  
     gpio_pad_select_gpio(MOSI_GPIO);
     gpio_set_direction(MOSI_GPIO, GPIO_MODE_OUTPUT);
  
     gpio_pad_select_gpio(SCK_GPIO);
     gpio_set_direction(SCK_GPIO, GPIO_MODE_OUTPUT);
  
     spi_bus_config_t bus;
     memset(&bus, 0, sizeof(spi_bus_config_t));
     
     bus.miso_io_num = MISO_GPIO;
     bus.mosi_io_num = MOSI_GPIO;
     bus.sclk_io_num = SCK_GPIO;
     bus.quadwp_io_num = -1;
     bus.quadhd_io_num = -1;
             
     ret = spi_bus_initialize(VSPI_HOST, &bus, 0);
     assert(ret == ESP_OK);
  
     spi_device_interface_config_t dev;
     memset(&dev, 0, sizeof(spi_device_interface_config_t));
  
     dev.clock_speed_hz = 9000000;
     dev.mode = 0;
     dev.spics_io_num = -1;
     dev.queue_size = 1;
  
     ret = spi_bus_add_device(VSPI_HOST, &dev, &__spi);
     assert(ret == ESP_OK);
  
     /*
      * Perform hardware reset.
      */
     lora_reset();
  
     /*
      * Check version.
      */
     uint8_t version;
     uint8_t i = 0;
     while(i++ < TIMEOUT_RESET) {
        version = lora_read_reg(REG_VERSION);
        if(version == 0x12) break;
        vTaskDelay(2);
     }
     assert(i <= TIMEOUT_RESET + 1); // at the end of the loop above, the max value i can reach is TIMEOUT_RESET + 1
  
     /*
      * Default configuration.
      */
     lora_sleep();
     lora_write_reg(REG_FIFO_RX_BASE_ADDR, 0);
     lora_write_reg(REG_FIFO_TX_BASE_ADDR, 0);
     lora_write_reg(REG_LNA, lora_read_reg(REG_LNA) | 0x03);
     lora_write_reg(REG_MODEM_CONFIG_3, 0x04);
     lora_set_tx_power(17);
  
     lora_idle();
     return 1;
  }
  
  /**
   * Send a packet.
   * @param buf Data to be sent
   * @param size Size of data.
   */
  void 
  lora_send_packet(uint8_t *buf, int size)
  {
     /*
      * Transfer data to radio.
      */
     lora_idle();
     lora_write_reg(REG_FIFO_ADDR_PTR, 0);
  
     for(int i=0; i<size; i++) 
        lora_write_reg(REG_FIFO, *buf++);
     
     lora_write_reg(REG_PAYLOAD_LENGTH, size);
     
     /*
      * Start transmission and wait for conclusion.
      */
     lora_write_reg(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX);
     while((lora_read_reg(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0)
        vTaskDelay(2);
  
     lora_write_reg(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK);
  }
  
  /**
   * Read a received packet.
   * @param buf Buffer for the data.
   * @param size Available size in buffer (bytes).
   * @return Number of bytes received (zero if no packet available).
   */
  int 
  lora_receive_packet(uint8_t *buf, int size)
  {
     int len = 0;
  
     /*
      * Check interrupts.
      */
     int irq = lora_read_reg(REG_IRQ_FLAGS);
     lora_write_reg(REG_IRQ_FLAGS, irq);
     if((irq & IRQ_RX_DONE_MASK) == 0) return 0;
     if(irq & IRQ_PAYLOAD_CRC_ERROR_MASK) return 0;
  
     /*
      * Find packet size.
      */
     if (__implicit) len = lora_read_reg(REG_PAYLOAD_LENGTH);
     else len = lora_read_reg(REG_RX_NB_BYTES);
  
     /*
      * Transfer data from radio.
      */
     lora_idle();   
     lora_write_reg(REG_FIFO_ADDR_PTR, lora_read_reg(REG_FIFO_RX_CURRENT_ADDR));
     if(len > size) len = size;
     for(int i=0; i<len; i++) 
        *buf++ = lora_read_reg(REG_FIFO);
  
     return len;
  }
  
  /**
   * Returns non-zero if there is data to read (packet received).
   */
  int
  lora_received(void)
  {
     if(lora_read_reg(REG_IRQ_FLAGS) & IRQ_RX_DONE_MASK) return 1;
     return 0;
  }
  
  /**
   * Return last packet's RSSI.
   */
  int 
  lora_packet_rssi(void)
  {
     return (lora_read_reg(REG_PKT_RSSI_VALUE) - (__frequency < 868E6 ? 164 : 157));
  }
  
  /**
   * Return last packet's SNR (signal to noise ratio).
   */
  float 
  lora_packet_snr(void)
  {
     return ((int8_t)lora_read_reg(REG_PKT_SNR_VALUE)) * 0.25;
  }
  
  /**
   * Shutdown hardware.
   */
  void 
  lora_close(void)
  {
     lora_sleep();
  //   close(__spi);  FIXME: end hardware features after lora_close
  //   close(__cs);
  //   close(__rst);
  //   __spi = -1;
  //   __cs = -1;
  //   __rst = -1;
  }
  
  void 
  lora_dump_registers(void)
  {
     int i;
     printf("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n");
     for(i=0; i<0x40; i++) {
        printf("%02X ", lora_read_reg(i));
        if((i & 0x0f) == 0x0f) printf("\n");
     }
     printf("\n");
  }
  
  