commit 6c6451c92cad22792418b8ab6f98e4121c090580 Author: Lazarewicz Julien Date: Tue Jul 22 15:27:00 2025 +0200 first commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..52998af --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +This software is Copyright (C) Mike McCauley. Use is subject to license +conditions. The main licensing options available are GPL V3 or Commercial: + +Open Source Licensing GPL V3 + +This is the appropriate option if you want to share the source code of your +application with everyone you distribute it to, and you also want to give them +the right to share who uses it. If you wish to use this software under Open +Source Licensing, you must contribute all your source code to the open source +community in accordance with the GPL Version 3 when your application is +distributed. See http://www.gnu.org/copyleft/gpl.html + +Commercial Licensing + +This is the appropriate option if you are creating proprietary applications +and you are not prepared to distribute and share the source code of your +application. Contact info@airspayce for details. diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..17d0f89 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,207 @@ +RadioHead/LICENSE +RadioHead/MANIFEST +RadioHead/project.cfg +RadioHead/library.properties +RadioHead/RadioHead.h +RadioHead/RH_ASK.cpp +RadioHead/RH_ASK.h +RadioHead/RH_ABZ.cpp +RadioHead/RH_ABZ.h +RadioHead/RHCRC.cpp +RadioHead/RHCRC.h +RadioHead/RHDatagram.cpp +RadioHead/RHDatagram.h +RadioHead/RHEncryptedDriver.h +RadioHead/RHEncryptedDriver.cpp +RadioHead/RHGenericDriver.cpp +RadioHead/RHGenericDriver.h +RadioHead/RHGenericSPI.cpp +RadioHead/RHGenericSPI.h +RadioHead/RHHardwareSPI.cpp +RadioHead/RHHardwareSPI.h +RadioHead/RHMesh.cpp +RadioHead/RHMesh.h +RadioHead/RHReliableDatagram.cpp +RadioHead/RHReliableDatagram.h +RadioHead/RH_CC110.cpp +RadioHead/RH_CC110.h +RadioHead/RH_E32.cpp +RadioHead/RH_E32.h +RadioHead/RH_LoRaFileOps.cpp +RadioHead/RH_LoRaFileOps.h +RadioHead/RH_NRF24.cpp +RadioHead/RH_NRF24.h +RadioHead/RH_NRF51.cpp +RadioHead/RH_NRF51.h +RadioHead/RH_NRF905.cpp +RadioHead/RH_NRF905.h +RadioHead/RH_RF22.cpp +RadioHead/RH_RF22.h +RadioHead/RH_RF24.cpp +RadioHead/RH_RF24.h +RadioHead/RH_RF69.cpp +RadioHead/RH_RF69.h +RadioHead/RH_MRF89.cpp +RadioHead/RH_MRF89.h +RadioHead/RH_RF95.cpp +RadioHead/RH_RF95.h +RadioHead/RH_SX126x.h +RadioHead/RH_SX126x.cpp +RadioHead/RH_STM32WLx.h +RadioHead/RH_STM32WLx.cpp +RadioHead/RH_TCP.cpp +RadioHead/RH_TCP.h +RadioHead/RHRouter.cpp +RadioHead/RHRouter.h +RadioHead/RH_Serial.cpp +RadioHead/RH_Serial.h +RadioHead/RHSoftwareSPI.cpp +RadioHead/RHSoftwareSPI.h +RadioHead/RHSPIDriver.cpp +RadioHead/RHSPIDriver.h +RadioHead/RHTcpProtocol.h +RadioHead/RHNRFSPIDriver.cpp +RadioHead/RHNRFSPIDriver.h +RadioHead/RHSUBGHZSPI.h +RadioHead/RHSUBGHZSPI.cpp +RadioHead/RHutil +RadioHead/RHutil/atomic.h +RadioHead/RHutil/simulator.h +RadioHead/RHutil/HardwareSerial.h +RadioHead/RHutil/HardwareSerial.cpp +RadioHead/RHutil/RasPi.cpp +RadioHead/RHutil/RasPi.h +RadioHead/RHutil_pigpio/RasPi.cpp +RadioHead/RHutil_pigpio/RasPi.h +RadioHead/examples/ask/ask_reliable_datagram_client/ask_reliable_datagram_client.ino +RadioHead/examples/ask/ask_reliable_datagram_server/ask_reliable_datagram_server.ino +RadioHead/examples/ask/ask_transmitter/ask_transmitter.ino +RadioHead/examples/ask/ask_receiver/ask_receiver.ino +RadioHead/examples/cc110/cc110_client/cc110_client.ino +RadioHead/examples/cc110/cc110_server/cc110_server.ino +RadioHead/examples/e32/e32_client/e32_client.ino +RadioHead/examples/e32/e32_server/e32_server.ino +RadioHead/examples/abz/abz_client/abz_client.ino +RadioHead/examples/abz/abz_server/abz_server.ino +RadioHead/examples/rf95/rf95_client/rf95_client.ino +RadioHead/examples/rf95/rf95_server/rf95_server.ino +RadioHead/examples/rf95/rf95_encrypted_client/rf95_encrypted_client.ino +RadioHead/examples/rf95/rf95_encrypted_server/rf95_encrypted_server.ino +RadioHead/examples/rf95/rf95_reliable_datagram_client/rf95_reliable_datagram_client.ino +RadioHead/examples/rf95/rf95_reliable_datagram_server/rf95_reliable_datagram_server.ino +RadioHead/examples/rf22/rf22_client/rf22_client.ino +RadioHead/examples/rf22/rf22_mesh_client/rf22_mesh_client.ino +RadioHead/examples/rf22/rf22_mesh_server1/rf22_mesh_server1.ino +RadioHead/examples/rf22/rf22_mesh_server2/rf22_mesh_server2.ino +RadioHead/examples/rf22/rf22_mesh_server3/rf22_mesh_server3.ino +RadioHead/examples/rf22/rf22_reliable_datagram_client/rf22_reliable_datagram_client.ino +RadioHead/examples/rf22/rf22_reliable_datagram_server/rf22_reliable_datagram_server.ino +RadioHead/examples/rf22/rf22_router_client/rf22_router_client.ino +RadioHead/examples/rf22/rf22_router_server1/rf22_router_server1.ino +RadioHead/examples/rf22/rf22_router_server2/rf22_router_server2.ino +RadioHead/examples/rf22/rf22_router_server3/rf22_router_server3.ino +RadioHead/examples/rf22/rf22_router_test/rf22_router_test.ino +RadioHead/examples/rf22/rf22_server/rf22_server.ino +RadioHead/examples/rf22/rf22_cw/rf22_cw.ino +RadioHead/examples/rf24/rf24_client/rf24_client.ino +RadioHead/examples/rf24/rf24_lowpower_client/rf24_lowpower_client.ino +RadioHead/examples/rf24/rf24_reliable_datagram_client/rf24_reliable_datagram_client.ino +RadioHead/examples/rf24/rf24_reliable_datagram_server/rf24_reliable_datagram_server.ino +RadioHead/examples/rf24/rf24_server/rf24_server.ino +RadioHead/examples/rf69/rf69_client/rf69_client.ino +RadioHead/examples/rf69/rf69_reliable_datagram_client/rf69_reliable_datagram_client.ino +RadioHead/examples/rf69/rf69_reliable_datagram_server/rf69_reliable_datagram_server.ino +RadioHead/examples/rf69/rf69_server/rf69_server.ino +RadioHead/examples/mrf89/mrf89_client/mrf89_client.ino +RadioHead/examples/mrf89/mrf89_server/mrf89_server.ino +RadioHead/examples/nrf24/nrf24_client/nrf24_client.ino +RadioHead/examples/nrf24/nrf24_encrypted_server/nrf24_encrypted_server.ino +RadioHead/examples/nrf24/nrf24_encrypted_client/nrf24_encrypted_client.ino +RadioHead/examples/nrf24/nrf24_server/nrf24_server.ino +RadioHead/examples/nrf24/nrf24_reliable_datagram_client/nrf24_reliable_datagram_client.ino +RadioHead/examples/nrf24/nrf24_reliable_datagram_server/nrf24_reliable_datagram_server.ino +RadioHead/examples/nrf51/nrf51_client/nrf51_client.ino +RadioHead/examples/nrf51/nrf51_reliable_datagram_client/nrf51_reliable_datagram_client.ino +RadioHead/examples/nrf51/nrf51_reliable_datagram_server/nrf51_reliable_datagram_server.ino +RadioHead/examples/nrf51/nrf51_server/nrf51_server.ino +RadioHead/examples/nrf51/nrf51_audio_tx/nrf51_audio_tx.ino +RadioHead/examples/nrf51/nrf51_audio_tx/nrf51_audio.pdf +RadioHead/examples/nrf51/nrf51_audio_rx/nrf51_audio_rx.ino +RadioHead/examples/nrf905/nrf905_client/nrf905_client.ino +RadioHead/examples/nrf905/nrf905_reliable_datagram_client/nrf905_reliable_datagram_client.ino +RadioHead/examples/nrf905/nrf905_reliable_datagram_server/nrf905_reliable_datagram_server.ino +RadioHead/examples/nrf905/nrf905_server/nrf905_server.ino +RadioHead/examples/serial/serial_reliable_datagram_client/serial_reliable_datagram_client.ino +RadioHead/examples/serial/serial_reliable_datagram_server/serial_reliable_datagram_server.ino +RadioHead/examples/serial/serial_gateway/serial_gateway.ino +RadioHead/examples/simulator/simulator_reliable_datagram_client/simulator_reliable_datagram_client.ino +RadioHead/examples/simulator/simulator_reliable_datagram_server/simulator_reliable_datagram_server.ino +RadioHead/examples/raspi/RasPiRH.cpp +RadioHead/examples/raspi/Makefile +RadioHead/examples/raspi/rf95/shared +RadioHead/examples/raspi/rf95/shared/help_functions.h +RadioHead/examples/raspi/rf95/shared/gpsMT3339.h +RadioHead/examples/raspi/rf95/shared/help_functions.cpp +RadioHead/examples/raspi/rf95/shared/gpsMT3339.cpp +RadioHead/examples/raspi/rf95/rf95_reliable_datagram_client/Makefile +RadioHead/examples/raspi/rf95/rf95_reliable_datagram_client/rf95_reliable_datagram_client.cpp +RadioHead/examples/raspi/rf95/rf95_reliable_datagram_server/Makefile +RadioHead/examples/raspi/rf95/rf95_reliable_datagram_server/rf95_reliable_datagram_server.cpp +RadioHead/examples/raspi/rf95/rf95_server/Makefile +RadioHead/examples/raspi/rf95/rf95_server/rf95_server1.cpp +RadioHead/examples/raspi/rf95/rf95_server/rf95_server2.cpp +RadioHead/examples/raspi/rf95/rf95_router_test/Makefile +RadioHead/examples/raspi/rf95/rf95_router_test/rf95_router_test.cpp +RadioHead/examples/raspi/rf95/rf95_mesh_server3/rf95_mesh_server3.cpp +RadioHead/examples/raspi/rf95/rf95_mesh_server3/Makefile +RadioHead/examples/raspi/rf95/rf95_mesh_client/Makefile +RadioHead/examples/raspi/rf95/rf95_mesh_client/rf95_mesh_client.cpp +RadioHead/examples/raspi/rf95/rf95_mesh_server2/Makefile +RadioHead/examples/raspi/rf95/rf95_mesh_server2/rf95_mesh_server2.cpp +RadioHead/examples/raspi/rf95/rf95_client/Makefile +RadioHead/examples/raspi/rf95/rf95_client/rf95_client1.cpp +RadioHead/examples/raspi/rf95/rf95_client/rf95_client2.cpp +RadioHead/examples/raspi/rf95/rf95_router_client/Makefile +RadioHead/examples/raspi/rf95/rf95_router_client/rf95_router_client.cpp +RadioHead/examples/raspi/rf95/rf95_router_server2/Makefile +RadioHead/examples/raspi/rf95/rf95_router_server2/rf95_router_server2.cpp +RadioHead/examples/raspi/rf95/rf95_router_server3/rf95_router_server3.cpp +RadioHead/examples/raspi/rf95/rf95_router_server3/Makefile +RadioHead/examples/raspi/rf95/rf95_router_server1/rf95_router_server1.cpp +RadioHead/examples/raspi/rf95/rf95_router_server1/Makefile +RadioHead/examples/raspi/rf95/rf95_mesh_server1/Makefile +RadioHead/examples/raspi/rf95/rf95_mesh_server1/rf95_mesh_server1.cpp +RadioHead/examples/lorafileops/lorafileops_client/lorafileops_client.cpp +RadioHead/examples/lorafileops/lorafileops_server/lorafileops_server.cpp +RadioHead/examples/sx126x/stm32wlx_client/stm32wlx_client.ino +RadioHead/examples/sx126x/stm32wlx_server/stm32wlx_server.ino +RadioHead/examples/sx126x/stm32wlx_continuous/stm32wlx_continuous.ino +RadioHead/examples/sx126x/sx1262_client/sx1262_client.ino +RadioHead/examples/sx126x/sx1262_server/sx1262_server.ino +RadioHead/tools/etherSimulator.pl +RadioHead/tools/chain.conf +RadioHead/tools/simMain.cpp +RadioHead/tools/simBuild +RadioHead/tools/createGPX.pl +RadioHead/doc +RadioHead/STM32ArduinoCompat/HardwareSerial.cpp +RadioHead/STM32ArduinoCompat/HardwareSerial.h +RadioHead/STM32ArduinoCompat/HardwareSPI.cpp +RadioHead/STM32ArduinoCompat/HardwareSPI.h +RadioHead/STM32ArduinoCompat/wirish.cpp +RadioHead/STM32ArduinoCompat/wirish.h +RadioHead/STM32ArduinoCompat/README +RadioHead/RH_RF24_property_data/convert.pl +RadioHead/RF24configs/radio_config_Si4464_27_434_2GFSK_5_10.h +RadioHead/RF24configs/radio_config_Si4464_30_915_2GFSK_10_20.h +RadioHead/RF24configs/radio_config_Si4464_30_434_2GFSK_10_20.h +RadioHead/RF24configs/radio_config_Si4464_30_915_2GFSK_5_10.h +RadioHead/RF24configs/radio_config_Si4464_30_434_2GFSK_5_10.h +RadioHead/RF24configs/README +RadioHead/MGOSCompat/MGOS.h +RadioHead/MGOSCompat/HardwareSPI.cpp +RadioHead/MGOSCompat/MGOS.cpp +RadioHead/MGOSCompat/HardwareSerial.h +RadioHead/MGOSCompat/README +RadioHead/MGOSCompat/HardwareSerial.cpp +RadioHead/MGOSCompat/HardwareSPI.h diff --git a/MGOSCompat/HardwareSPI.cpp b/MGOSCompat/HardwareSPI.cpp new file mode 100644 index 0000000..d7d74cf --- /dev/null +++ b/MGOSCompat/HardwareSPI.cpp @@ -0,0 +1,154 @@ +// ArduinoCompat/HardwareSPI.cpp +// +// Interface between Arduino-like SPI interface and STM32F4 Discovery and similar +// using STM32F4xx_DSP_StdPeriph_Lib_V1.3.0 + +#include +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + +#include +#include +#include + +HardwareSPI::HardwareSPI(uint32_t spiPortNumber) : spiPortNumber(spiPortNumber) +{ +} + +void HardwareSPI::begin(int frequency, uint32_t bitOrder, uint32_t mode) +{ + //Set the SPI tx/rx buffer pointers. + txn.fd.tx_data = spiTXBuf; + txn.fd.rx_data = spiRXBuf; + + txn.freq = frequency; + this->bitOrder = bitOrder; + txn.mode = mode; +#ifdef RH_USE_SPI + txn.cs = mgos_sys_config_get_rh_spi_cs(); +#else + txn.cs = -1; +#endif +} + +void HardwareSPI::end(void) +{ + struct mgos_spi *spi = mgos_spi_get_global(); + mgos_spi_close(spi); +} + +uint8_t HardwareSPI::reverseBits(uint8_t value) +{ + value = (value & 0xF0) >> 4 | (value & 0x0F) << 4; + value = (value & 0xCC) >> 2 | (value & 0x33) << 2; + value = (value & 0xAA) >> 1 | (value & 0x55) << 1; + return value; +} + +uint8_t HardwareSPI::transfer(uint8_t data) +{ + uint8_t status=0; + txn.fd.len=1; + spiTXBuf[0]=data; + if( bitOrder != MSBFIRST ) { + spiTXBuf[0]=reverseBits(spiTXBuf[0]); + } + bool success = mgos_spi_run_txn( mgos_spi_get_global(), true, &txn); + if( !success ) { + LOG(LL_INFO, ("%s: Failed SPI transfer()", __FUNCTION__) ); + } + status = spiRXBuf[0]; + if( bitOrder != MSBFIRST ) { + status = reverseBits(status); + } + return status; +} + +uint8_t HardwareSPI::transfer2B(uint8_t byte0, uint8_t byte1) +{ + uint8_t status=0; + txn.fd.len=2; + spiTXBuf[0]=byte0; + spiTXBuf[1]=byte1; + if( bitOrder != MSBFIRST ) { + spiTXBuf[0]=reverseBits(spiTXBuf[0]); + spiTXBuf[1]=reverseBits(spiTXBuf[1]); + } + bool success = mgos_spi_run_txn( mgos_spi_get_global(), true, &txn); + if( !success ) { + LOG(LL_INFO, ("%s: Failed SPI transfer()", __FUNCTION__) ); + } + + status = spiRXBuf[0]; + if( bitOrder != MSBFIRST ) { + status = reverseBits(status); + } + return status; +} + +uint8_t HardwareSPI::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) { + uint8_t status=0; + if( len+1 <= SPI_RX_BUFFER_SIZE ) { + txn.fd.len=len+1; + memset(spiTXBuf, 0, SPI_RX_BUFFER_SIZE); + spiTXBuf[0]=reg; + if( bitOrder != MSBFIRST ) { + spiTXBuf[0]=reverseBits(spiTXBuf[0]); + } + bool success = mgos_spi_run_txn( mgos_spi_get_global(), true, &txn); + if( !success ) { + LOG(LL_INFO, ("%s: Failed SPI transfer()", __FUNCTION__) ); + } + if( bitOrder != MSBFIRST ) { + uint8_t index=0; + for( index=0 ; index +#include +#include + +extern "C" +{ + struct mgos_spi *mgos_spi_get_global(void); + bool mgos_spi_run_txn(struct mgos_spi *spi, bool full_duplex, const struct mgos_spi_txn *txn); +} + +//Not used on MGOS as SPI config is set in mos.yml +#define SPI_MODE0 0x00 +#define SPI_MODE1 0x01 +#define SPI_MODE2 0x03 +#define SPI_MODE3 0x02 + +#define SPI_TX_BUFFER_SIZE 64 +#define SPI_RX_BUFFER_SIZE 64 + +class HardwareSPI +{ +public: + HardwareSPI(uint32_t spiPortNumber); // Only port SPI1 is currently supported + void begin(int frequency, uint32_t bitOrder, uint32_t mode); + void end(void); + uint8_t reverseBits(uint8_t value); + int8_t getCSGpio(); + uint8_t transfer(uint8_t data); + uint8_t transfer2B(uint8_t byte0, uint8_t byte1); + uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len); + uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len); +private: + uint32_t spiPortNumber; // Not used + struct mgos_spi_txn txn; + uint32_t bitOrder; + //Define spi TX and RX buffers.This is a little wasteful of memory + //but no dynamic memory allocation fits with the RadioHead library. + uint8_t spiTXBuf[SPI_TX_BUFFER_SIZE]; + uint8_t spiRXBuf[SPI_RX_BUFFER_SIZE]; +}; +extern HardwareSPI SPI; + + +#endif diff --git a/MGOSCompat/HardwareSerial.cpp b/MGOSCompat/HardwareSerial.cpp new file mode 100644 index 0000000..7e226d6 --- /dev/null +++ b/MGOSCompat/HardwareSerial.cpp @@ -0,0 +1,189 @@ +// ArduinoCompat/HardwareSerial.cpp +// +// Author: mikem@airspayce.com + +#include +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) +#include +#include + +extern "C" { + static inline void mgos_sys_config_set_rh_serial_baud(int v); + static inline void mgos_sys_config_set_rh_serial_databits(int v); + static inline void mgos_sys_config_set_rh_serial_parity(int v); + static inline void mgos_sys_config_set_rh_serial_stopbits(int v); + + void mgos_uart_config_set_defaults(int uart_no, struct mgos_uart_config *cfg); + bool mgos_uart_configure(int uart_no, const struct mgos_uart_config *cfg); + void mgos_uart_set_rx_enabled(int uart_no, bool enabled); + size_t mgos_uart_read_avail(int uart_no); + size_t mgos_uart_read(int uart_no, void *buf, size_t len); + size_t mgos_uart_write(int uart_no, const void *buf, size_t len); +}; + + +// instantiate Serial objects +HardwareSerial Serial0(0); +HardwareSerial Serial1(1); +HardwareSerial Serial2(2); + +/* + * Serial ports + * + * ESP8266 + * The esp8266 device has two uarts (0 and 1), Uart 1 TX is typically used for debugging. No Uart 1 RX is available on the esp8266. + * + * Uart 0 + * RX = GPIO3 + * TX = GPIO1 + * + * Uart 1 + * TX = GPIO2 + * + * ESP32 + * The esp32 device has three uarts (0,1 and 2). Uart 0 is typically used for debugging/loading code. + * + * Uart 0 + * RX = GPIO3 + * TX = GPIO1 + * CTS = GPIO19 + * RTS = GPIO22 + * + * Uart 1 + * RX = GPIO25 + * TX = GPIO26 + * CTS = GPIO27 + * RTS = GPIO13 + * + * Uart 2 + * RX = GPIO16 + * TX = GPIO17 + * CTS = GPIO14 + * RTS = GPIO15 + */ + +/////////////////////////////////////////////////////////////// +// HardwareSerial +/////////////////////////////////////////////////////////////// + +HardwareSerial::HardwareSerial(int uartIndex) +{ + this->uartIndex=uartIndex; +} + +/** + * @brief Init the serial port. + * @param baud If the baud rate is 0 or -ve then the persistent sorage value + * is used. Starting at the value in mos.yml. + */ +void HardwareSerial::begin(int baud) +{ + struct mgos_uart_config ucfg; + + if( mgos_sys_config_get_rh_serial_baud() != baud ) { + mgos_sys_config_set_rh_serial_baud(baud); + } + + mgos_uart_config_set_defaults(this->uartIndex, &ucfg); + ucfg.baud_rate = mgos_sys_config_get_rh_serial_baud(); + ucfg.num_data_bits = mgos_sys_config_get_rh_serial_databits(); + ucfg.parity = (mgos_uart_parity)mgos_sys_config_get_rh_serial_parity(); + ucfg.stop_bits = (mgos_uart_stop_bits)mgos_sys_config_get_rh_serial_stopbits(); + mgos_uart_configure(this->uartIndex, &ucfg); + if( mgos_uart_configure(this->uartIndex, &ucfg) ) { + mgos_uart_set_rx_enabled(this->uartIndex, true); + mgos_uart_set_dispatcher(this->uartIndex, NULL, NULL); + } + +#ifdef NO_ESP32_RXD_PULLUP + if( this->uartIndex == 0 ) { + mgos_gpio_setup_input(3, MGOS_GPIO_PULL_NONE); + } + else if( this->uartIndex == 1 ) { + mgos_gpio_setup_input(25, MGOS_GPIO_PULL_NONE); + } + else if( this->uartIndex == 2 ) { + mgos_gpio_setup_input(16, MGOS_GPIO_PULL_NONE); + } +#endif +} + +void HardwareSerial::end() +{ + mgos_uart_set_rx_enabled(this->uartIndex, false); +} + +int HardwareSerial::available(void) +{ + size_t reqRxByteCount=1; + //We have to read the byte because Mongoose OS requires a return + //to the RTOS in order to update the value read by mgos_uart_read_avail() + rxByteCountAvail = mgos_uart_read(this->uartIndex, &rxByte, reqRxByteCount); + return rxByteCountAvail; +} + +int HardwareSerial::read(void) +{ + return rxByte; +} + +size_t HardwareSerial::write(uint8_t ch) +{ + size_t wr_byte_count = 0; + + wr_byte_count = mgos_uart_write(this->uartIndex, &ch, 1); + + return wr_byte_count; +} + +size_t HardwareSerial::print(char ch) +{ + printf("%c", ch); + return 0; +} + +size_t HardwareSerial::println(char ch) +{ + printf("%c\n", ch); + return 0; +} + +size_t HardwareSerial::print(unsigned char ch, int base) +{ + if( base == DEC ) { + printf("%d", ch); + } + else if( base == HEX ) { + printf("%02x", ch); + } + else if( base == OCT ) { + printf("%o", ch); + } + //TODO Add binary print + return 0; +} + +size_t HardwareSerial::println(unsigned char ch, int base) +{ + print((unsigned int)ch, base); + printf("\n"); + return 0; +} + +size_t HardwareSerial::println(const char* s) +{ + if( s ) { + printf("%s\n",s); + } + return 0; +} + +size_t HardwareSerial::print(const char* s) +{ + if( s) { + printf(s); + } + return 0; +} + +#endif diff --git a/MGOSCompat/HardwareSerial.h b/MGOSCompat/HardwareSerial.h new file mode 100644 index 0000000..4ffb6b5 --- /dev/null +++ b/MGOSCompat/HardwareSerial.h @@ -0,0 +1,49 @@ +// ArduinoCompat/HardwareSerial.h +// Mongoose OS implementation of Arduino compatible serial class + +#include +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) +#ifndef _HardwareSerial_h +#define _HardwareSerial_h + +#include +#include +#include + +// Mostly compatible wuith Arduino HardwareSerial +// There is just enough here to support RadioHead RH_Serial +class HardwareSerial +{ +public: + HardwareSerial(int uart_index); + void begin(int baud); + void end(); + virtual int available(void); + virtual int read(void); + virtual size_t write(uint8_t); + inline size_t write(unsigned long n) { return write((uint8_t)n); } + inline size_t write(long n) { return write((uint8_t)n); } + inline size_t write(unsigned int n) { return write((uint8_t)n); } + inline size_t write(int n) { return write((uint8_t)n); } + + //These methods will send debug info on the debug serial port (if enabled) + size_t println(unsigned char ch, int base); + size_t print(unsigned char ch, int base); + size_t println(const char ch); + size_t print(const char ch); + size_t println(const char* s); + size_t print(const char* s); + +private: + int uartIndex; + size_t rxByteCountAvail; + uint8_t rxByte; +}; + +extern HardwareSerial Serial0; +extern HardwareSerial Serial1; +extern HardwareSerial Serial2; + +#endif + +#endif diff --git a/MGOSCompat/MGOS.cpp b/MGOSCompat/MGOS.cpp new file mode 100644 index 0000000..bf8ecc8 --- /dev/null +++ b/MGOSCompat/MGOS.cpp @@ -0,0 +1,159 @@ +// RasPi.cpp +// +// Routines for implementing RadioHead on Raspberry Pi +// using BCM2835 library for GPIO +// +// Contributed by Mike Poublon and used with permission + + +#include + +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + +#include "mgos.h" + +int pwmFreq = 1000; +float pwmDutyCycle = 0.5; + +/** + * @brief Set the direction of a GPIO pin. + * @param pin the Pin whose direction is to be set. + * @param mode The direction of the pin (OUTPUT or INPUT) + **/ +void pinMode(uint8_t pin, WiringPinMode mode) +{ + + //SPI CS GPIO controlled by MGOS lib call so don't allow it to be set here + if( SPI.getCSGpio() != pin ) { + if (mode == OUTPUT) + { + mgos_gpio_set_mode(pin, MGOS_GPIO_MODE_OUTPUT); + } + else if (mode == OUTPUT_OPEN_DRAIN) + { + mgos_gpio_set_pull(pin, MGOS_GPIO_PULL_UP); + mgos_gpio_set_mode(pin, MGOS_GPIO_MODE_OUTPUT); + } + else if (mode == INPUT || mode == INPUT_FLOATING ) + { + mgos_gpio_set_mode(pin, MGOS_GPIO_MODE_INPUT); + } + else if (mode == INPUT_ANALOG) + { + mgos_adc_enable(pin); + } + else if (mode == INPUT_PULLUP) + { + mgos_gpio_set_pull(pin, MGOS_GPIO_PULL_UP); + mgos_gpio_set_mode(pin, MGOS_GPIO_MODE_INPUT); + } + else if (mode == INPUT_PULLDOWN) + { + mgos_gpio_set_pull(pin, MGOS_GPIO_PULL_DOWN); + mgos_gpio_set_mode(pin, MGOS_GPIO_MODE_INPUT); + } + else if (mode == PWM) + { + mgos_pwm_set(pin, pwmFreq, pwmDutyCycle); + } + else if (mode == PWM_OPEN_DRAIN) { + mgos_pwm_set(pin, pwmFreq, pwmDutyCycle); + } + } +} + +/** + * @brief Set the state of a GPIO pin. + * @param pin the Pin whose state is to be set. + * @param value The state of the pin. + */ +void digitalWrite(unsigned char pin, unsigned char value) +{ + + //SPI CS GPIO controlled by MGOS lib call so don't allow it to be set here + if( SPI.getCSGpio() != pin ) { + mgos_gpio_write(pin,value); + } +} + +/** + * @brief Read the state of a GPIO pin. + * @param pin the Pin whose state is to be set. + * @return 1 If high, 0 if low. + */ +uint8_t digitalRead(uint8_t pin) +{ + uint8_t pinState=0; + //SPI CS GPIO controlled by MGOS lib call so don't allow it to be set here + if( SPI.getCSGpio() != pin ) { + pinState = (uint8_t)mgos_gpio_read(pin); + } + return pinState; +} + +/** + * @brief Get the number of elapsed milliseconds since the last boot. + */ +uint32_t millis(void) +{ + return (uint32_t)mgos_uptime_micros()/1000; +} + +/** + * @brief Provide a delay in milliseconds. + * @param ms The number of Milli Seconds to delay. + */ +void delay (unsigned long ms) +{ + mgos_msleep(ms); +} + +/** + * @brief Generate a random number between limits. + * @param min The minimum random value to be generated. + * @param max The maximum random value to be generated. + */ +long random(long min, long max) +{ + return mgos_rand_range( (float)min, (float)max); +} + +static void mgos_gpio_int_handler(int pin, void *arg) { + void (*handler)(void) = (void (*)())arg; + //Note that this handler is executed in interrupt context (ISR) + //therefore ensure that actions performed here are acceptable for the + //platform on which the code will execute. + //E.G + //Use of the LOG macro to send debug data on the serial port crashes + //esp8266 and esp32 code. + handler(); + (void) pin; + (void) arg; +} + +void attachInterrupt(uint8_t pin, void (*handler)(void), int rh_mode) +{ + mgos_gpio_int_mode mgos_mode = MGOS_GPIO_INT_NONE; + if( rh_mode == CHANGE ) { + mgos_mode = MGOS_GPIO_INT_EDGE_ANY; + } else if( rh_mode == FALLING ) { + mgos_mode = MGOS_GPIO_INT_EDGE_NEG; + } else if( rh_mode == RISING ) { + mgos_mode = MGOS_GPIO_INT_EDGE_POS; + } + mgos_gpio_set_int_handler_isr((int)pin, mgos_mode, mgos_gpio_int_handler, (void*)handler); +} + +void enableInterupt(uint8_t pin) { + mgos_gpio_enable_int(pin); +} + +/** + * @brief Perform functions that under Mongoose OS we would normally return + * to the RTOS. E,G flush the TX UART buffer. + */ +void mgosYield(void) { + mgos_uart_flush(RH_SERIAL_PORT); +} + +#endif diff --git a/MGOSCompat/MGOS.h b/MGOSCompat/MGOS.h new file mode 100644 index 0000000..31f0da2 --- /dev/null +++ b/MGOSCompat/MGOS.h @@ -0,0 +1,101 @@ +// MGOS.h +// +// Routines for implementing RadioHead when using Mongoose OS +// see https://mongoose-os.com/mos.html for H/W support when using Mongoose OS +// Contributed by Paul Austen + +#ifndef MGOS_h +#define MGOS_h + +#define PROGMEM +#define memcpy_P memcpy + +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 + +#define HIGH 0x1 +#define LOW 0x0 + +#define LSBFIRST 0 +#define MSBFIRST 1 + +#define CHANGE 1 +#define FALLING 2 +#define RISING 3 + +typedef enum WiringPinMode { + OUTPUT, /**< Basic digital output: when the pin is HIGH, the + voltage is held at +3.3v (Vcc) and when it is LOW, it + is pulled down to ground. */ + + OUTPUT_OPEN_DRAIN, /**< In open drain mode, the pin indicates + "low" by accepting current flow to ground + and "high" by providing increased + impedance. An example use would be to + connect a pin to a bus line (which is pulled + up to a positive voltage by a separate + supply through a large resistor). When the + pin is high, not much current flows through + to ground and the line stays at positive + voltage; when the pin is low, the bus + "drains" to ground with a small amount of + current constantly flowing through the large + resistor from the external supply. In this + mode, no current is ever actually sourced + from the pin. */ + + INPUT, /**< Basic digital input. The pin voltage is sampled; when + it is closer to 3.3v (Vcc) the pin status is high, and + when it is closer to 0v (ground) it is low. If no + external circuit is pulling the pin voltage to high or + low, it will tend to randomly oscillate and be very + sensitive to noise (e.g., a breath of air across the pin + might cause the state to flip). */ + + INPUT_ANALOG, /**< This is a special mode for when the pin will be + used for analog (not digital) reads. Enables ADC + conversion to be performed on the voltage at the + pin. */ + + INPUT_PULLUP, /**< The state of the pin in this mode is reported + the same way as with INPUT, but the pin voltage + is gently "pulled up" towards +3.3v. This means + the state will be high unless an external device + is specifically pulling the pin down to ground, + in which case the "gentle" pull up will not + affect the state of the input. */ + + INPUT_PULLDOWN, /**< The state of the pin in this mode is reported + the same way as with INPUT, but the pin voltage + is gently "pulled down" towards 0v. This means + the state will be low unless an external device + is specifically pulling the pin up to 3.3v, in + which case the "gentle" pull down will not + affect the state of the input. */ + + INPUT_FLOATING, /**< Synonym for INPUT. */ + + PWM, /**< This is a special mode for when the pin will be used for + PWM output (a special case of digital output). */ + + PWM_OPEN_DRAIN, /**< Like PWM, except that instead of alternating + cycles of LOW and HIGH, the voltage on the pin + consists of alternating cycles of LOW and + floating (disconnected). */ +} WiringPinMode; + +extern "C" { + void pinMode(uint8_t pin, WiringPinMode mode); + void digitalWrite(unsigned char pin, unsigned char value); + uint8_t digitalRead(uint8_t pin); + uint32_t millis(void); + void delay (unsigned long ms); + long random(long min, long max); + void attachInterrupt(uint8_t pin, void (*handler)(void), int rh_mode); + void mgosYield(void); + void enableInterupt(uint8_t pin); +} + +#endif diff --git a/MGOSCompat/README b/MGOSCompat/README new file mode 100644 index 0000000..1342355 --- /dev/null +++ b/MGOSCompat/README @@ -0,0 +1 @@ +This directory contains some files to allow RadioHead to be built on Mongoose OS. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/RF24configs/README b/RF24configs/README new file mode 100644 index 0000000..d3926a1 --- /dev/null +++ b/RF24configs/README @@ -0,0 +1,19 @@ +This directory contains a selection of radio configuiration files for use by RH_RF24.cpp +They were generated by Silicon Labs Wireless Development Suite (WDS) 3.2.11.0 +The configuration file controls all the basic frequency aand modulation parameters for the radio. +The appropriate one for your application or sketch must be #included by RH_RF24.cpp + +You can generate your own custom configuration file by generatng a new one with WDS, +copying it to this directory with a unique anme and #include it in RH_RF24.cpp + +The file names encode the basic parameters: + +radio_config_Siaaaa_bb_ccc_dddd_ee_ff_gg.h + +where +aaaa = Chip typenukber eg 4464 +bb = Crytsyal frequency in MHz +ccc = RF base frequency in MHz +dddd = Modulation type eg 2GFSK +ee = Data rate in kbps +ff = Deviation in kHz diff --git a/RF24configs/radio_config_Si4464_27_434_2GFSK_5_10.h b/RF24configs/radio_config_Si4464_27_434_2GFSK_5_10.h new file mode 100644 index 0000000..8812706 --- /dev/null +++ b/RF24configs/radio_config_Si4464_27_434_2GFSK_5_10.h @@ -0,0 +1,516 @@ +/*! @file radio_config.h + * @brief This file contains the automatically generated + * configurations. + * + * @n WDS GUI Version: 3.2.11.0 + * @n Device: Si4464 Rev.: B1 + * + * @b COPYRIGHT + * @n Silicon Laboratories Confidential + * @n Copyright 2017 Silicon Laboratories, Inc. + * @n http://www.silabs.com + */ + +#ifndef RADIO_CONFIG_H_ +#define RADIO_CONFIG_H_ + +// USER DEFINED PARAMETERS +// Define your own parameters here + +// INPUT DATA +/* +// Crys_freq(Hz): 27000000 Crys_tol(ppm): 20 IF_mode: 2 High_perf_Ch_Fil: 1 OSRtune: 0 Ch_Fil_Bw_AFC: 0 ANT_DIV: 0 PM_pattern: 0 +// MOD_type: 3 Rsymb(sps): 5000 Fdev(Hz): 10000 RXBW(Hz): 150000 Manchester: 0 AFC_en: 0 Rsymb_error: 0.0 Chip-Version: 3 +// RF Freq.(MHz): 434 API_TC: 29 fhst: 250000 inputBW: 0 BERT: 0 RAW_dout: 0 D_source: 0 Hi_pfm_div: 1 +// +// # RX IF frequency is -421875 Hz +// # WB filter 2 (BW = 61.84 kHz); NB-filter 2 (BW = 61.84 kHz) +// +// Modulation index: 4 +*/ + + +// CONFIGURATION PARAMETERS +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ 27000000L +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH 0x07 +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP 0x03 +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET 0xF000 + + +// CONFIGURATION COMMANDS + +/* +// Command: RF_POWER_UP +// Description: Command to power-up the device and select the operational mode and functionality. +*/ +#define RF_POWER_UP 0x02, 0x01, 0x00, 0x01, 0x9B, 0xFC, 0xC0 + +/* +// Command: RF_GPIO_PIN_CFG +// Description: Configures the GPIO pins. +*/ +#define RF_GPIO_PIN_CFG 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +/* +// Set properties: RF_GLOBAL_XO_TUNE_2 +// Number of properties: 2 +// Group ID: 0x00 +// Start ID: 0x00 +// Default values: 0x40, 0x00, +// Descriptions: +// GLOBAL_XO_TUNE - Configure the internal capacitor frequency tuning bank for the crystal oscillator. +// GLOBAL_CLK_CFG - Clock configuration options. +*/ +#define RF_GLOBAL_XO_TUNE_2 0x11, 0x00, 0x02, 0x00, 0x52, 0x00 + +/* +// Set properties: RF_GLOBAL_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x00 +// Start ID: 0x03 +// Default values: 0x20, +// Descriptions: +// GLOBAL_CONFIG - Global configuration settings. +*/ +#define RF_GLOBAL_CONFIG_1 0x11, 0x00, 0x01, 0x03, 0x60 + +/* +// Set properties: RF_INT_CTL_ENABLE_2 +// Number of properties: 2 +// Group ID: 0x01 +// Start ID: 0x00 +// Default values: 0x04, 0x00, +// Descriptions: +// INT_CTL_ENABLE - This property provides for global enabling of the three interrupt groups (Chip, Modem and Packet Handler) in order to generate HW interrupts at the NIRQ pin. +// INT_CTL_PH_ENABLE - Enable individual interrupt sources within the Packet Handler Interrupt Group to generate a HW interrupt on the NIRQ output pin. +*/ +#define RF_INT_CTL_ENABLE_2 0x11, 0x01, 0x02, 0x00, 0x01, 0x20 + +/* +// Set properties: RF_FRR_CTL_A_MODE_4 +// Number of properties: 4 +// Group ID: 0x02 +// Start ID: 0x00 +// Default values: 0x01, 0x02, 0x09, 0x00, +// Descriptions: +// FRR_CTL_A_MODE - Fast Response Register A Configuration. +// FRR_CTL_B_MODE - Fast Response Register B Configuration. +// FRR_CTL_C_MODE - Fast Response Register C Configuration. +// FRR_CTL_D_MODE - Fast Response Register D Configuration. +*/ +#define RF_FRR_CTL_A_MODE_4 0x11, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 + +/* +// Set properties: RF_PREAMBLE_TX_LENGTH_1 +// Number of properties: 1 +// Group ID: 0x10 +// Start ID: 0x00 +// Default values: 0x08, +// Descriptions: +// PREAMBLE_TX_LENGTH - Configure length of TX Preamble. +*/ +#define RF_PREAMBLE_TX_LENGTH_1 0x11, 0x10, 0x01, 0x00, 0x0A + +/* +// Set properties: RF_PREAMBLE_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x10 +// Start ID: 0x04 +// Default values: 0x21, +// Descriptions: +// PREAMBLE_CONFIG - General configuration bits for the Preamble field. +*/ +#define RF_PREAMBLE_CONFIG_1 0x11, 0x10, 0x01, 0x04, 0x31 + +/* +// Set properties: RF_SYNC_CONFIG_3 +// Number of properties: 3 +// Group ID: 0x11 +// Start ID: 0x00 +// Default values: 0x01, 0x2D, 0xD4, +// Descriptions: +// SYNC_CONFIG - Sync Word configuration bits. +// SYNC_BITS_31_24 - Sync word. +// SYNC_BITS_23_16 - Sync word. +*/ +#define RF_SYNC_CONFIG_3 0x11, 0x11, 0x03, 0x00, 0x01, 0xB4, 0x2B + +/* +// Set properties: RF_PKT_CONFIG1_1 +// Number of properties: 1 +// Group ID: 0x12 +// Start ID: 0x06 +// Default values: 0x00, +// Descriptions: +// PKT_CONFIG1 - General configuration bits for transmission or reception of a packet. +*/ +#define RF_PKT_CONFIG1_1 0x11, 0x12, 0x01, 0x06, 0x02 + +/* +// Set properties: RF_PKT_FIELD_1_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x12 +// Start ID: 0x0F +// Default values: 0x00, +// Descriptions: +// PKT_FIELD_1_CONFIG - General data processing and packet configuration bits for Field 1. +*/ +#define RF_PKT_FIELD_1_CONFIG_1 0x11, 0x12, 0x01, 0x0F, 0x04 + +/* +// Set properties: RF_MODEM_MOD_TYPE_12 +// Number of properties: 12 +// Group ID: 0x20 +// Start ID: 0x00 +// Default values: 0x02, 0x80, 0x07, 0x0F, 0x42, 0x40, 0x01, 0xC9, 0xC3, 0x80, 0x00, 0x06, +// Descriptions: +// MODEM_MOD_TYPE - Selects the type of modulation. In TX mode, additionally selects the source of the modulation. +// MODEM_MAP_CONTROL - Controls polarity and mapping of transmit and receive bits. +// MODEM_DSM_CTRL - Miscellaneous control bits for the Delta-Sigma Modulator (DSM) in the PLL Synthesizer. +// MODEM_DATA_RATE_2 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_DATA_RATE_1 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_DATA_RATE_0 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_TX_NCO_MODE_3 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_2 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_1 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_0 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_FREQ_DEV_2 - 17-bit unsigned TX frequency deviation word. +// MODEM_FREQ_DEV_1 - 17-bit unsigned TX frequency deviation word. +*/ +#define RF_MODEM_MOD_TYPE_12 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x03, 0x0D, 0x40, 0x05, 0x9B, 0xFC, 0xC0, 0x00, 0x03 + +/* +// Set properties: RF_MODEM_FREQ_DEV_0_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x0C +// Default values: 0xD3, +// Descriptions: +// MODEM_FREQ_DEV_0 - 17-bit unsigned TX frequency deviation word. +*/ +#define RF_MODEM_FREQ_DEV_0_1 0x11, 0x20, 0x01, 0x0C, 0x09 + +/* +// Set properties: RF_MODEM_TX_RAMP_DELAY_8 +// Number of properties: 8 +// Group ID: 0x20 +// Start ID: 0x18 +// Default values: 0x01, 0x00, 0x08, 0x03, 0xC0, 0x00, 0x10, 0x20, +// Descriptions: +// MODEM_TX_RAMP_DELAY - TX ramp-down delay setting. +// MODEM_MDM_CTRL - MDM control. +// MODEM_IF_CONTROL - Selects Fixed-IF, Scaled-IF, or Zero-IF mode of RX Modem operation. +// MODEM_IF_FREQ_2 - the IF frequency setting (an 18-bit signed number). +// MODEM_IF_FREQ_1 - the IF frequency setting (an 18-bit signed number). +// MODEM_IF_FREQ_0 - the IF frequency setting (an 18-bit signed number). +// MODEM_DECIMATION_CFG1 - Specifies three decimator ratios for the Cascaded Integrator Comb (CIC) filter. +// MODEM_DECIMATION_CFG0 - Specifies miscellaneous parameters and decimator ratios for the Cascaded Integrator Comb (CIC) filter. +*/ +#define RF_MODEM_TX_RAMP_DELAY_8 0x11, 0x20, 0x08, 0x18, 0x01, 0x80, 0x08, 0x03, 0x80, 0x00, 0x20, 0x10 + +/* +// Set properties: RF_MODEM_BCR_OSR_1_9 +// Number of properties: 9 +// Group ID: 0x20 +// Start ID: 0x22 +// Default values: 0x00, 0x4B, 0x06, 0xD3, 0xA0, 0x06, 0xD3, 0x02, 0xC0, +// Descriptions: +// MODEM_BCR_OSR_1 - RX BCR/Slicer oversampling rate (12-bit unsigned number). +// MODEM_BCR_OSR_0 - RX BCR/Slicer oversampling rate (12-bit unsigned number). +// MODEM_BCR_NCO_OFFSET_2 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_NCO_OFFSET_1 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_NCO_OFFSET_0 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_GAIN_1 - The unsigned 11-bit RX BCR loop gain value. +// MODEM_BCR_GAIN_0 - The unsigned 11-bit RX BCR loop gain value. +// MODEM_BCR_GEAR - RX BCR loop gear control. +// MODEM_BCR_MISC1 - Miscellaneous control bits for the RX BCR loop. +*/ +#define RF_MODEM_BCR_OSR_1_9 0x11, 0x20, 0x09, 0x22, 0x01, 0xC2, 0x01, 0x23, 0x45, 0x00, 0x92, 0x02, 0xC2 + +/* +// Set properties: RF_MODEM_AFC_GEAR_7 +// Number of properties: 7 +// Group ID: 0x20 +// Start ID: 0x2C +// Default values: 0x00, 0x23, 0x83, 0x69, 0x00, 0x40, 0xA0, +// Descriptions: +// MODEM_AFC_GEAR - RX AFC loop gear control. +// MODEM_AFC_WAIT - RX AFC loop wait time control. +// MODEM_AFC_GAIN_1 - Sets the gain of the PLL-based AFC acquisition loop, and provides miscellaneous control bits for AFC functionality. +// MODEM_AFC_GAIN_0 - Sets the gain of the PLL-based AFC acquisition loop, and provides miscellaneous control bits for AFC functionality. +// MODEM_AFC_LIMITER_1 - Set the AFC limiter value. +// MODEM_AFC_LIMITER_0 - Set the AFC limiter value. +// MODEM_AFC_MISC - Specifies miscellaneous AFC control bits. +*/ +#define RF_MODEM_AFC_GEAR_7 0x11, 0x20, 0x07, 0x2C, 0x04, 0x36, 0x80, 0x10, 0x18, 0x36, 0x80 + +/* +// Set properties: RF_MODEM_AGC_CONTROL_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x35 +// Default values: 0xE0, +// Descriptions: +// MODEM_AGC_CONTROL - Miscellaneous control bits for the Automatic Gain Control (AGC) function in the RX Chain. +*/ +#define RF_MODEM_AGC_CONTROL_1 0x11, 0x20, 0x01, 0x35, 0xE2 + +/* +// Set properties: RF_MODEM_AGC_WINDOW_SIZE_9 +// Number of properties: 9 +// Group ID: 0x20 +// Start ID: 0x38 +// Default values: 0x11, 0x10, 0x10, 0x0B, 0x1C, 0x40, 0x00, 0x00, 0x2B, +// Descriptions: +// MODEM_AGC_WINDOW_SIZE - Specifies the size of the measurement and settling windows for the AGC algorithm. +// MODEM_AGC_RFPD_DECAY - Sets the decay time of the RF peak detectors. +// MODEM_AGC_IFPD_DECAY - Sets the decay time of the IF peak detectors. +// MODEM_FSK4_GAIN1 - Specifies the gain factor of the secondary branch in 4(G)FSK ISI-suppression. +// MODEM_FSK4_GAIN0 - Specifies the gain factor of the primary branch in 4(G)FSK ISI-suppression. +// MODEM_FSK4_TH1 - 16 bit 4(G)FSK slicer threshold. +// MODEM_FSK4_TH0 - 16 bit 4(G)FSK slicer threshold. +// MODEM_FSK4_MAP - 4(G)FSK symbol mapping code. +// MODEM_OOK_PDTC - Configures the attack and decay times of the OOK Peak Detector. +*/ +#define RF_MODEM_AGC_WINDOW_SIZE_9 0x11, 0x20, 0x09, 0x38, 0x11, 0x62, 0x62, 0x00, 0x1A, 0xFF, 0xFF, 0x00, 0x2A + +/* +// Set properties: RF_MODEM_OOK_CNT1_8 +// Number of properties: 8 +// Group ID: 0x20 +// Start ID: 0x42 +// Default values: 0xA4, 0x03, 0x56, 0x02, 0x00, 0xA3, 0x02, 0x80, +// Descriptions: +// MODEM_OOK_CNT1 - OOK control. +// MODEM_OOK_MISC - Selects the detector(s) used for demodulation of an OOK signal, or for demodulation of a (G)FSK signal when using the asynchronous demodulator. +// MODEM_RAW_SEARCH - Defines and controls the search period length for the Moving Average and Min-Max detectors. +// MODEM_RAW_CONTROL - Defines gain and enable controls for raw / nonstandard mode. +// MODEM_RAW_EYE_1 - 11 bit eye-open detector threshold. +// MODEM_RAW_EYE_0 - 11 bit eye-open detector threshold. +// MODEM_ANT_DIV_MODE - Antenna diversity mode settings. +// MODEM_ANT_DIV_CONTROL - Specifies controls for the Antenna Diversity algorithm. +*/ +#define RF_MODEM_OOK_CNT1_8 0x11, 0x20, 0x08, 0x42, 0xA4, 0x02, 0xD6, 0x83, 0x00, 0x90, 0x01, 0x80 + +/* +// Set properties: RF_MODEM_RSSI_COMP_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x4E +// Default values: 0x32, +// Descriptions: +// MODEM_RSSI_COMP - RSSI compensation value. +*/ +#define RF_MODEM_RSSI_COMP_1 0x11, 0x20, 0x01, 0x4E, 0x40 + +/* +// Set properties: RF_MODEM_CLKGEN_BAND_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x51 +// Default values: 0x08, +// Descriptions: +// MODEM_CLKGEN_BAND - Select PLL Synthesizer output divider ratio as a function of frequency band. +*/ +#define RF_MODEM_CLKGEN_BAND_1 0x11, 0x20, 0x01, 0x51, 0x0A + +/* +// Set properties: RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x00 +// Default values: 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01, +// Descriptions: +// MODEM_CHFLT_RX1_CHFLT_COE13_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE12_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE11_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE10_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE9_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE8_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE7_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE6_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE5_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE4_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE3_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE2_7_0 - Filter coefficients for the first set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12 0x11, 0x21, 0x0C, 0x00, 0xFF, 0xC4, 0x30, 0x7F, 0xF5, 0xB5, 0xB8, 0xDE, 0x05, 0x17, 0x16, 0x0C + +/* +// Set properties: RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x0C +// Default values: 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F, 0xFF, 0xC4, 0x30, 0x7F, 0xF5, 0xB5, +// Descriptions: +// MODEM_CHFLT_RX1_CHFLT_COE1_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE0_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM1 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM2 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM3 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE13_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE12_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE11_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE10_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE9_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE8_7_0 - Filter coefficients for the second set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12 0x11, 0x21, 0x0C, 0x0C, 0x03, 0x00, 0x15, 0xFF, 0x00, 0x00, 0xFF, 0xC4, 0x30, 0x7F, 0xF5, 0xB5 + +/* +// Set properties: RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x18 +// Default values: 0xB8, 0xDE, 0x05, 0x17, 0x16, 0x0C, 0x03, 0x00, 0x15, 0xFF, 0x00, 0x00, +// Descriptions: +// MODEM_CHFLT_RX2_CHFLT_COE7_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE6_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE5_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE4_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE3_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE2_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE1_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE0_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM1 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM2 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM3 - Filter coefficients for the second set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12 0x11, 0x21, 0x0C, 0x18, 0xB8, 0xDE, 0x05, 0x17, 0x16, 0x0C, 0x03, 0x00, 0x15, 0xFF, 0x00, 0x00 + +/* +// Set properties: RF_PA_MODE_4 +// Number of properties: 4 +// Group ID: 0x22 +// Start ID: 0x00 +// Default values: 0x08, 0x7F, 0x00, 0x5D, +// Descriptions: +// PA_MODE - Selects the PA operating mode, and selects resolution of PA power adjustment (i.e., step size). +// PA_PWR_LVL - Configuration of PA output power level. +// PA_BIAS_CLKDUTY - Configuration of the PA Bias and duty cycle of the TX clock source. +// PA_TC - Configuration of PA ramping parameters. +*/ +#define RF_PA_MODE_4 0x11, 0x22, 0x04, 0x00, 0x08, 0x7F, 0x00, 0x3D + +/* +// Set properties: RF_SYNTH_PFDCP_CPFF_7 +// Number of properties: 7 +// Group ID: 0x23 +// Start ID: 0x00 +// Default values: 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03, +// Descriptions: +// SYNTH_PFDCP_CPFF - Feed forward charge pump current selection. +// SYNTH_PFDCP_CPINT - Integration charge pump current selection. +// SYNTH_VCO_KV - Gain scaling factors (Kv) for the VCO tuning varactors on both the integrated-path and feed forward path. +// SYNTH_LPFILT3 - Value of resistor R2 in feed-forward path of loop filter. +// SYNTH_LPFILT2 - Value of capacitor C2 in feed-forward path of loop filter. +// SYNTH_LPFILT1 - Value of capacitors C1 and C3 in feed-forward path of loop filter. +// SYNTH_LPFILT0 - Bias current of the active amplifier in the feed-forward loop filter. +*/ +#define RF_SYNTH_PFDCP_CPFF_7 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 + +/* +// Set properties: RF_FREQ_CONTROL_INTE_8 +// Number of properties: 8 +// Group ID: 0x40 +// Start ID: 0x00 +// Default values: 0x3C, 0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, +// Descriptions: +// FREQ_CONTROL_INTE - Frac-N PLL Synthesizer integer divide number. +// FREQ_CONTROL_FRAC_2 - Frac-N PLL fraction number. +// FREQ_CONTROL_FRAC_1 - Frac-N PLL fraction number. +// FREQ_CONTROL_FRAC_0 - Frac-N PLL fraction number. +// FREQ_CONTROL_CHANNEL_STEP_SIZE_1 - EZ Frequency Programming channel step size. +// FREQ_CONTROL_CHANNEL_STEP_SIZE_0 - EZ Frequency Programming channel step size. +// FREQ_CONTROL_W_SIZE - Set window gating period (in number of crystal reference clock cycles) for counting VCO frequency during calibration. +// FREQ_CONTROL_VCOCNT_RX_ADJ - Adjust target count for VCO calibration in RX mode. +*/ +#define RF_FREQ_CONTROL_INTE_8 0x11, 0x40, 0x08, 0x00, 0x3F, 0x0A, 0x5E, 0xD0, 0x4B, 0xDA, 0x20, 0xFE + + +// AUTOMATICALLY GENERATED CODE! +// DO NOT EDIT/MODIFY BELOW THIS LINE! +// -------------------------------------------- + +#ifndef FIRMWARE_LOAD_COMPILE +#define RADIO_CONFIGURATION_DATA_ARRAY { \ + 0x07, RF_POWER_UP, \ + 0x08, RF_GPIO_PIN_CFG, \ + 0x06, RF_GLOBAL_XO_TUNE_2, \ + 0x05, RF_GLOBAL_CONFIG_1, \ + 0x06, RF_INT_CTL_ENABLE_2, \ + 0x08, RF_FRR_CTL_A_MODE_4, \ + 0x05, RF_PREAMBLE_TX_LENGTH_1, \ + 0x05, RF_PREAMBLE_CONFIG_1, \ + 0x07, RF_SYNC_CONFIG_3, \ + 0x05, RF_PKT_CONFIG1_1, \ + 0x05, RF_PKT_FIELD_1_CONFIG_1, \ + 0x10, RF_MODEM_MOD_TYPE_12, \ + 0x05, RF_MODEM_FREQ_DEV_0_1, \ + 0x0C, RF_MODEM_TX_RAMP_DELAY_8, \ + 0x0D, RF_MODEM_BCR_OSR_1_9, \ + 0x0B, RF_MODEM_AFC_GEAR_7, \ + 0x05, RF_MODEM_AGC_CONTROL_1, \ + 0x0D, RF_MODEM_AGC_WINDOW_SIZE_9, \ + 0x0C, RF_MODEM_OOK_CNT1_8, \ + 0x05, RF_MODEM_RSSI_COMP_1, \ + 0x05, RF_MODEM_CLKGEN_BAND_1, \ + 0x10, RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12, \ + 0x10, RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12, \ + 0x10, RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12, \ + 0x08, RF_PA_MODE_4, \ + 0x0B, RF_SYNTH_PFDCP_CPFF_7, \ + 0x0C, RF_FREQ_CONTROL_INTE_8, \ + 0x00 \ + } +#else +#define RADIO_CONFIGURATION_DATA_ARRAY { 0 } +#endif + +// DEFAULT VALUES FOR CONFIGURATION PARAMETERS +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ_DEFAULT 30000000L +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER_DEFAULT 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH_DEFAULT 0x10 +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP_DEFAULT 0x01 +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET_DEFAULT 0x1000 + +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH_INCLUDED 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH_SIZE 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH { } + +#ifndef RADIO_CONFIGURATION_DATA_ARRAY +#error "This property must be defined!" +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET_DEFAULT +#endif + +#define RADIO_CONFIGURATION_DATA { \ + Radio_Configuration_Data_Array, \ + RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER, \ + RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH, \ + RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP, \ + RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET \ + } + +#endif /* RADIO_CONFIG_H_ */ diff --git a/RF24configs/radio_config_Si4464_30_434_2GFSK_10_20.h b/RF24configs/radio_config_Si4464_30_434_2GFSK_10_20.h new file mode 100644 index 0000000..8b8c28c --- /dev/null +++ b/RF24configs/radio_config_Si4464_30_434_2GFSK_10_20.h @@ -0,0 +1,516 @@ +/*! @file radio_config.h + * @brief This file contains the automatically generated + * configurations. + * + * @n WDS GUI Version: 3.2.11.0 + * @n Device: Si4464 Rev.: B1 + * + * @b COPYRIGHT + * @n Silicon Laboratories Confidential + * @n Copyright 2017 Silicon Laboratories, Inc. + * @n http://www.silabs.com + */ + +#ifndef RADIO_CONFIG_H_ +#define RADIO_CONFIG_H_ + +// USER DEFINED PARAMETERS +// Define your own parameters here + +// INPUT DATA +/* +// Crys_freq(Hz): 30000000 Crys_tol(ppm): 20 IF_mode: 2 High_perf_Ch_Fil: 1 OSRtune: 0 Ch_Fil_Bw_AFC: 0 ANT_DIV: 0 PM_pattern: 0 +// MOD_type: 3 Rsymb(sps): 10000 Fdev(Hz): 20000 RXBW(Hz): 150000 Manchester: 0 AFC_en: 0 Rsymb_error: 0.0 Chip-Version: 3 +// RF Freq.(MHz): 434 API_TC: 29 fhst: 250000 inputBW: 0 BERT: 0 RAW_dout: 0 D_source: 0 Hi_pfm_div: 1 +// +// # RX IF frequency is -468750 Hz +// # WB filter 4 (BW = 82.64 kHz); NB-filter 4 (BW = 82.64 kHz) +// +// Modulation index: 4 +*/ + + +// CONFIGURATION PARAMETERS +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ 30000000L +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH 0x07 +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP 0x03 +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET 0xF000 + + +// CONFIGURATION COMMANDS + +/* +// Command: RF_POWER_UP +// Description: Command to power-up the device and select the operational mode and functionality. +*/ +#define RF_POWER_UP 0x02, 0x01, 0x00, 0x01, 0xC9, 0xC3, 0x80 + +/* +// Command: RF_GPIO_PIN_CFG +// Description: Configures the GPIO pins. +*/ +#define RF_GPIO_PIN_CFG 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +/* +// Set properties: RF_GLOBAL_XO_TUNE_2 +// Number of properties: 2 +// Group ID: 0x00 +// Start ID: 0x00 +// Default values: 0x40, 0x00, +// Descriptions: +// GLOBAL_XO_TUNE - Configure the internal capacitor frequency tuning bank for the crystal oscillator. +// GLOBAL_CLK_CFG - Clock configuration options. +*/ +#define RF_GLOBAL_XO_TUNE_2 0x11, 0x00, 0x02, 0x00, 0x52, 0x00 + +/* +// Set properties: RF_GLOBAL_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x00 +// Start ID: 0x03 +// Default values: 0x20, +// Descriptions: +// GLOBAL_CONFIG - Global configuration settings. +*/ +#define RF_GLOBAL_CONFIG_1 0x11, 0x00, 0x01, 0x03, 0x60 + +/* +// Set properties: RF_INT_CTL_ENABLE_2 +// Number of properties: 2 +// Group ID: 0x01 +// Start ID: 0x00 +// Default values: 0x04, 0x00, +// Descriptions: +// INT_CTL_ENABLE - This property provides for global enabling of the three interrupt groups (Chip, Modem and Packet Handler) in order to generate HW interrupts at the NIRQ pin. +// INT_CTL_PH_ENABLE - Enable individual interrupt sources within the Packet Handler Interrupt Group to generate a HW interrupt on the NIRQ output pin. +*/ +#define RF_INT_CTL_ENABLE_2 0x11, 0x01, 0x02, 0x00, 0x01, 0x20 + +/* +// Set properties: RF_FRR_CTL_A_MODE_4 +// Number of properties: 4 +// Group ID: 0x02 +// Start ID: 0x00 +// Default values: 0x01, 0x02, 0x09, 0x00, +// Descriptions: +// FRR_CTL_A_MODE - Fast Response Register A Configuration. +// FRR_CTL_B_MODE - Fast Response Register B Configuration. +// FRR_CTL_C_MODE - Fast Response Register C Configuration. +// FRR_CTL_D_MODE - Fast Response Register D Configuration. +*/ +#define RF_FRR_CTL_A_MODE_4 0x11, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 + +/* +// Set properties: RF_PREAMBLE_TX_LENGTH_1 +// Number of properties: 1 +// Group ID: 0x10 +// Start ID: 0x00 +// Default values: 0x08, +// Descriptions: +// PREAMBLE_TX_LENGTH - Configure length of TX Preamble. +*/ +#define RF_PREAMBLE_TX_LENGTH_1 0x11, 0x10, 0x01, 0x00, 0x0A + +/* +// Set properties: RF_PREAMBLE_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x10 +// Start ID: 0x04 +// Default values: 0x21, +// Descriptions: +// PREAMBLE_CONFIG - General configuration bits for the Preamble field. +*/ +#define RF_PREAMBLE_CONFIG_1 0x11, 0x10, 0x01, 0x04, 0x31 + +/* +// Set properties: RF_SYNC_CONFIG_3 +// Number of properties: 3 +// Group ID: 0x11 +// Start ID: 0x00 +// Default values: 0x01, 0x2D, 0xD4, +// Descriptions: +// SYNC_CONFIG - Sync Word configuration bits. +// SYNC_BITS_31_24 - Sync word. +// SYNC_BITS_23_16 - Sync word. +*/ +#define RF_SYNC_CONFIG_3 0x11, 0x11, 0x03, 0x00, 0x01, 0xB4, 0x2B + +/* +// Set properties: RF_PKT_CONFIG1_1 +// Number of properties: 1 +// Group ID: 0x12 +// Start ID: 0x06 +// Default values: 0x00, +// Descriptions: +// PKT_CONFIG1 - General configuration bits for transmission or reception of a packet. +*/ +#define RF_PKT_CONFIG1_1 0x11, 0x12, 0x01, 0x06, 0x02 + +/* +// Set properties: RF_PKT_FIELD_1_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x12 +// Start ID: 0x0F +// Default values: 0x00, +// Descriptions: +// PKT_FIELD_1_CONFIG - General data processing and packet configuration bits for Field 1. +*/ +#define RF_PKT_FIELD_1_CONFIG_1 0x11, 0x12, 0x01, 0x0F, 0x04 + +/* +// Set properties: RF_MODEM_MOD_TYPE_12 +// Number of properties: 12 +// Group ID: 0x20 +// Start ID: 0x00 +// Default values: 0x02, 0x80, 0x07, 0x0F, 0x42, 0x40, 0x01, 0xC9, 0xC3, 0x80, 0x00, 0x06, +// Descriptions: +// MODEM_MOD_TYPE - Selects the type of modulation. In TX mode, additionally selects the source of the modulation. +// MODEM_MAP_CONTROL - Controls polarity and mapping of transmit and receive bits. +// MODEM_DSM_CTRL - Miscellaneous control bits for the Delta-Sigma Modulator (DSM) in the PLL Synthesizer. +// MODEM_DATA_RATE_2 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_DATA_RATE_1 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_DATA_RATE_0 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_TX_NCO_MODE_3 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_2 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_1 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_0 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_FREQ_DEV_2 - 17-bit unsigned TX frequency deviation word. +// MODEM_FREQ_DEV_1 - 17-bit unsigned TX frequency deviation word. +*/ +#define RF_MODEM_MOD_TYPE_12 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x06, 0x1A, 0x80, 0x05, 0xC9, 0xC3, 0x80, 0x00, 0x05 + +/* +// Set properties: RF_MODEM_FREQ_DEV_0_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x0C +// Default values: 0xD3, +// Descriptions: +// MODEM_FREQ_DEV_0 - 17-bit unsigned TX frequency deviation word. +*/ +#define RF_MODEM_FREQ_DEV_0_1 0x11, 0x20, 0x01, 0x0C, 0x76 + +/* +// Set properties: RF_MODEM_TX_RAMP_DELAY_8 +// Number of properties: 8 +// Group ID: 0x20 +// Start ID: 0x18 +// Default values: 0x01, 0x00, 0x08, 0x03, 0xC0, 0x00, 0x10, 0x20, +// Descriptions: +// MODEM_TX_RAMP_DELAY - TX ramp-down delay setting. +// MODEM_MDM_CTRL - MDM control. +// MODEM_IF_CONTROL - Selects Fixed-IF, Scaled-IF, or Zero-IF mode of RX Modem operation. +// MODEM_IF_FREQ_2 - the IF frequency setting (an 18-bit signed number). +// MODEM_IF_FREQ_1 - the IF frequency setting (an 18-bit signed number). +// MODEM_IF_FREQ_0 - the IF frequency setting (an 18-bit signed number). +// MODEM_DECIMATION_CFG1 - Specifies three decimator ratios for the Cascaded Integrator Comb (CIC) filter. +// MODEM_DECIMATION_CFG0 - Specifies miscellaneous parameters and decimator ratios for the Cascaded Integrator Comb (CIC) filter. +*/ +#define RF_MODEM_TX_RAMP_DELAY_8 0x11, 0x20, 0x08, 0x18, 0x01, 0x80, 0x08, 0x03, 0x80, 0x00, 0x20, 0x20 + +/* +// Set properties: RF_MODEM_BCR_OSR_1_9 +// Number of properties: 9 +// Group ID: 0x20 +// Start ID: 0x22 +// Default values: 0x00, 0x4B, 0x06, 0xD3, 0xA0, 0x06, 0xD3, 0x02, 0xC0, +// Descriptions: +// MODEM_BCR_OSR_1 - RX BCR/Slicer oversampling rate (12-bit unsigned number). +// MODEM_BCR_OSR_0 - RX BCR/Slicer oversampling rate (12-bit unsigned number). +// MODEM_BCR_NCO_OFFSET_2 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_NCO_OFFSET_1 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_NCO_OFFSET_0 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_GAIN_1 - The unsigned 11-bit RX BCR loop gain value. +// MODEM_BCR_GAIN_0 - The unsigned 11-bit RX BCR loop gain value. +// MODEM_BCR_GEAR - RX BCR loop gear control. +// MODEM_BCR_MISC1 - Miscellaneous control bits for the RX BCR loop. +*/ +#define RF_MODEM_BCR_OSR_1_9 0x11, 0x20, 0x09, 0x22, 0x01, 0x77, 0x01, 0x5D, 0x86, 0x00, 0xAF, 0x02, 0xC2 + +/* +// Set properties: RF_MODEM_AFC_GEAR_7 +// Number of properties: 7 +// Group ID: 0x20 +// Start ID: 0x2C +// Default values: 0x00, 0x23, 0x83, 0x69, 0x00, 0x40, 0xA0, +// Descriptions: +// MODEM_AFC_GEAR - RX AFC loop gear control. +// MODEM_AFC_WAIT - RX AFC loop wait time control. +// MODEM_AFC_GAIN_1 - Sets the gain of the PLL-based AFC acquisition loop, and provides miscellaneous control bits for AFC functionality. +// MODEM_AFC_GAIN_0 - Sets the gain of the PLL-based AFC acquisition loop, and provides miscellaneous control bits for AFC functionality. +// MODEM_AFC_LIMITER_1 - Set the AFC limiter value. +// MODEM_AFC_LIMITER_0 - Set the AFC limiter value. +// MODEM_AFC_MISC - Specifies miscellaneous AFC control bits. +*/ +#define RF_MODEM_AFC_GEAR_7 0x11, 0x20, 0x07, 0x2C, 0x04, 0x36, 0x80, 0x1D, 0x10, 0x04, 0x80 + +/* +// Set properties: RF_MODEM_AGC_CONTROL_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x35 +// Default values: 0xE0, +// Descriptions: +// MODEM_AGC_CONTROL - Miscellaneous control bits for the Automatic Gain Control (AGC) function in the RX Chain. +*/ +#define RF_MODEM_AGC_CONTROL_1 0x11, 0x20, 0x01, 0x35, 0xE2 + +/* +// Set properties: RF_MODEM_AGC_WINDOW_SIZE_9 +// Number of properties: 9 +// Group ID: 0x20 +// Start ID: 0x38 +// Default values: 0x11, 0x10, 0x10, 0x0B, 0x1C, 0x40, 0x00, 0x00, 0x2B, +// Descriptions: +// MODEM_AGC_WINDOW_SIZE - Specifies the size of the measurement and settling windows for the AGC algorithm. +// MODEM_AGC_RFPD_DECAY - Sets the decay time of the RF peak detectors. +// MODEM_AGC_IFPD_DECAY - Sets the decay time of the IF peak detectors. +// MODEM_FSK4_GAIN1 - Specifies the gain factor of the secondary branch in 4(G)FSK ISI-suppression. +// MODEM_FSK4_GAIN0 - Specifies the gain factor of the primary branch in 4(G)FSK ISI-suppression. +// MODEM_FSK4_TH1 - 16 bit 4(G)FSK slicer threshold. +// MODEM_FSK4_TH0 - 16 bit 4(G)FSK slicer threshold. +// MODEM_FSK4_MAP - 4(G)FSK symbol mapping code. +// MODEM_OOK_PDTC - Configures the attack and decay times of the OOK Peak Detector. +*/ +#define RF_MODEM_AGC_WINDOW_SIZE_9 0x11, 0x20, 0x09, 0x38, 0x11, 0x52, 0x52, 0x00, 0x1A, 0xFF, 0xFF, 0x00, 0x2A + +/* +// Set properties: RF_MODEM_OOK_CNT1_8 +// Number of properties: 8 +// Group ID: 0x20 +// Start ID: 0x42 +// Default values: 0xA4, 0x03, 0x56, 0x02, 0x00, 0xA3, 0x02, 0x80, +// Descriptions: +// MODEM_OOK_CNT1 - OOK control. +// MODEM_OOK_MISC - Selects the detector(s) used for demodulation of an OOK signal, or for demodulation of a (G)FSK signal when using the asynchronous demodulator. +// MODEM_RAW_SEARCH - Defines and controls the search period length for the Moving Average and Min-Max detectors. +// MODEM_RAW_CONTROL - Defines gain and enable controls for raw / nonstandard mode. +// MODEM_RAW_EYE_1 - 11 bit eye-open detector threshold. +// MODEM_RAW_EYE_0 - 11 bit eye-open detector threshold. +// MODEM_ANT_DIV_MODE - Antenna diversity mode settings. +// MODEM_ANT_DIV_CONTROL - Specifies controls for the Antenna Diversity algorithm. +*/ +#define RF_MODEM_OOK_CNT1_8 0x11, 0x20, 0x08, 0x42, 0xA4, 0x02, 0xD6, 0x83, 0x00, 0xAD, 0x01, 0x80 + +/* +// Set properties: RF_MODEM_RSSI_COMP_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x4E +// Default values: 0x32, +// Descriptions: +// MODEM_RSSI_COMP - RSSI compensation value. +*/ +#define RF_MODEM_RSSI_COMP_1 0x11, 0x20, 0x01, 0x4E, 0x40 + +/* +// Set properties: RF_MODEM_CLKGEN_BAND_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x51 +// Default values: 0x08, +// Descriptions: +// MODEM_CLKGEN_BAND - Select PLL Synthesizer output divider ratio as a function of frequency band. +*/ +#define RF_MODEM_CLKGEN_BAND_1 0x11, 0x20, 0x01, 0x51, 0x0A + +/* +// Set properties: RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x00 +// Default values: 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01, +// Descriptions: +// MODEM_CHFLT_RX1_CHFLT_COE13_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE12_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE11_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE10_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE9_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE8_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE7_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE6_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE5_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE4_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE3_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE2_7_0 - Filter coefficients for the first set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12 0x11, 0x21, 0x0C, 0x00, 0xA2, 0x81, 0x26, 0xAF, 0x3F, 0xEE, 0xC8, 0xC7, 0xDB, 0xF2, 0x02, 0x08 + +/* +// Set properties: RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x0C +// Default values: 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F, 0xFF, 0xC4, 0x30, 0x7F, 0xF5, 0xB5, +// Descriptions: +// MODEM_CHFLT_RX1_CHFLT_COE1_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE0_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM1 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM2 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM3 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE13_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE12_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE11_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE10_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE9_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE8_7_0 - Filter coefficients for the second set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12 0x11, 0x21, 0x0C, 0x0C, 0x07, 0x03, 0x15, 0xFC, 0x0F, 0x00, 0xA2, 0x81, 0x26, 0xAF, 0x3F, 0xEE + +/* +// Set properties: RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x18 +// Default values: 0xB8, 0xDE, 0x05, 0x17, 0x16, 0x0C, 0x03, 0x00, 0x15, 0xFF, 0x00, 0x00, +// Descriptions: +// MODEM_CHFLT_RX2_CHFLT_COE7_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE6_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE5_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE4_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE3_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE2_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE1_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE0_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM1 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM2 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM3 - Filter coefficients for the second set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12 0x11, 0x21, 0x0C, 0x18, 0xC8, 0xC7, 0xDB, 0xF2, 0x02, 0x08, 0x07, 0x03, 0x15, 0xFC, 0x0F, 0x00 + +/* +// Set properties: RF_PA_MODE_4 +// Number of properties: 4 +// Group ID: 0x22 +// Start ID: 0x00 +// Default values: 0x08, 0x7F, 0x00, 0x5D, +// Descriptions: +// PA_MODE - Selects the PA operating mode, and selects resolution of PA power adjustment (i.e., step size). +// PA_PWR_LVL - Configuration of PA output power level. +// PA_BIAS_CLKDUTY - Configuration of the PA Bias and duty cycle of the TX clock source. +// PA_TC - Configuration of PA ramping parameters. +*/ +#define RF_PA_MODE_4 0x11, 0x22, 0x04, 0x00, 0x08, 0x7F, 0x00, 0x3D + +/* +// Set properties: RF_SYNTH_PFDCP_CPFF_7 +// Number of properties: 7 +// Group ID: 0x23 +// Start ID: 0x00 +// Default values: 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03, +// Descriptions: +// SYNTH_PFDCP_CPFF - Feed forward charge pump current selection. +// SYNTH_PFDCP_CPINT - Integration charge pump current selection. +// SYNTH_VCO_KV - Gain scaling factors (Kv) for the VCO tuning varactors on both the integrated-path and feed forward path. +// SYNTH_LPFILT3 - Value of resistor R2 in feed-forward path of loop filter. +// SYNTH_LPFILT2 - Value of capacitor C2 in feed-forward path of loop filter. +// SYNTH_LPFILT1 - Value of capacitors C1 and C3 in feed-forward path of loop filter. +// SYNTH_LPFILT0 - Bias current of the active amplifier in the feed-forward loop filter. +*/ +#define RF_SYNTH_PFDCP_CPFF_7 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 + +/* +// Set properties: RF_FREQ_CONTROL_INTE_8 +// Number of properties: 8 +// Group ID: 0x40 +// Start ID: 0x00 +// Default values: 0x3C, 0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, +// Descriptions: +// FREQ_CONTROL_INTE - Frac-N PLL Synthesizer integer divide number. +// FREQ_CONTROL_FRAC_2 - Frac-N PLL fraction number. +// FREQ_CONTROL_FRAC_1 - Frac-N PLL fraction number. +// FREQ_CONTROL_FRAC_0 - Frac-N PLL fraction number. +// FREQ_CONTROL_CHANNEL_STEP_SIZE_1 - EZ Frequency Programming channel step size. +// FREQ_CONTROL_CHANNEL_STEP_SIZE_0 - EZ Frequency Programming channel step size. +// FREQ_CONTROL_W_SIZE - Set window gating period (in number of crystal reference clock cycles) for counting VCO frequency during calibration. +// FREQ_CONTROL_VCOCNT_RX_ADJ - Adjust target count for VCO calibration in RX mode. +*/ +#define RF_FREQ_CONTROL_INTE_8 0x11, 0x40, 0x08, 0x00, 0x38, 0x0E, 0xEE, 0xEE, 0x44, 0x44, 0x20, 0xFE + + +// AUTOMATICALLY GENERATED CODE! +// DO NOT EDIT/MODIFY BELOW THIS LINE! +// -------------------------------------------- + +#ifndef FIRMWARE_LOAD_COMPILE +#define RADIO_CONFIGURATION_DATA_ARRAY { \ + 0x07, RF_POWER_UP, \ + 0x08, RF_GPIO_PIN_CFG, \ + 0x06, RF_GLOBAL_XO_TUNE_2, \ + 0x05, RF_GLOBAL_CONFIG_1, \ + 0x06, RF_INT_CTL_ENABLE_2, \ + 0x08, RF_FRR_CTL_A_MODE_4, \ + 0x05, RF_PREAMBLE_TX_LENGTH_1, \ + 0x05, RF_PREAMBLE_CONFIG_1, \ + 0x07, RF_SYNC_CONFIG_3, \ + 0x05, RF_PKT_CONFIG1_1, \ + 0x05, RF_PKT_FIELD_1_CONFIG_1, \ + 0x10, RF_MODEM_MOD_TYPE_12, \ + 0x05, RF_MODEM_FREQ_DEV_0_1, \ + 0x0C, RF_MODEM_TX_RAMP_DELAY_8, \ + 0x0D, RF_MODEM_BCR_OSR_1_9, \ + 0x0B, RF_MODEM_AFC_GEAR_7, \ + 0x05, RF_MODEM_AGC_CONTROL_1, \ + 0x0D, RF_MODEM_AGC_WINDOW_SIZE_9, \ + 0x0C, RF_MODEM_OOK_CNT1_8, \ + 0x05, RF_MODEM_RSSI_COMP_1, \ + 0x05, RF_MODEM_CLKGEN_BAND_1, \ + 0x10, RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12, \ + 0x10, RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12, \ + 0x10, RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12, \ + 0x08, RF_PA_MODE_4, \ + 0x0B, RF_SYNTH_PFDCP_CPFF_7, \ + 0x0C, RF_FREQ_CONTROL_INTE_8, \ + 0x00 \ + } +#else +#define RADIO_CONFIGURATION_DATA_ARRAY { 0 } +#endif + +// DEFAULT VALUES FOR CONFIGURATION PARAMETERS +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ_DEFAULT 30000000L +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER_DEFAULT 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH_DEFAULT 0x10 +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP_DEFAULT 0x01 +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET_DEFAULT 0x1000 + +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH_INCLUDED 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH_SIZE 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH { } + +#ifndef RADIO_CONFIGURATION_DATA_ARRAY +#error "This property must be defined!" +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET_DEFAULT +#endif + +#define RADIO_CONFIGURATION_DATA { \ + Radio_Configuration_Data_Array, \ + RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER, \ + RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH, \ + RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP, \ + RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET \ + } + +#endif /* RADIO_CONFIG_H_ */ diff --git a/RF24configs/radio_config_Si4464_30_434_2GFSK_5_10.h b/RF24configs/radio_config_Si4464_30_434_2GFSK_5_10.h new file mode 100644 index 0000000..e5b4553 --- /dev/null +++ b/RF24configs/radio_config_Si4464_30_434_2GFSK_5_10.h @@ -0,0 +1,516 @@ +/*! @file radio_config.h + * @brief This file contains the automatically generated + * configurations. + * + * @n WDS GUI Version: 3.2.11.0 + * @n Device: Si4464 Rev.: B1 + * + * @b COPYRIGHT + * @n Silicon Laboratories Confidential + * @n Copyright 2017 Silicon Laboratories, Inc. + * @n http://www.silabs.com + */ + +#ifndef RADIO_CONFIG_H_ +#define RADIO_CONFIG_H_ + +// USER DEFINED PARAMETERS +// Define your own parameters here + +// INPUT DATA +/* +// Crys_freq(Hz): 30000000 Crys_tol(ppm): 20 IF_mode: 2 High_perf_Ch_Fil: 1 OSRtune: 0 Ch_Fil_Bw_AFC: 0 ANT_DIV: 0 PM_pattern: 0 +// MOD_type: 3 Rsymb(sps): 5000 Fdev(Hz): 10000 RXBW(Hz): 150000 Manchester: 0 AFC_en: 0 Rsymb_error: 0.0 Chip-Version: 3 +// RF Freq.(MHz): 434 API_TC: 29 fhst: 250000 inputBW: 0 BERT: 0 RAW_dout: 0 D_source: 0 Hi_pfm_div: 1 +// +// # RX IF frequency is -468750 Hz +// # WB filter 1 (BW = 57.23 kHz); NB-filter 1 (BW = 57.23 kHz) +// +// Modulation index: 4 +*/ + + +// CONFIGURATION PARAMETERS +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ 30000000L +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH 0x07 +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP 0x03 +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET 0xF000 + + +// CONFIGURATION COMMANDS + +/* +// Command: RF_POWER_UP +// Description: Command to power-up the device and select the operational mode and functionality. +*/ +#define RF_POWER_UP 0x02, 0x01, 0x00, 0x01, 0xC9, 0xC3, 0x80 + +/* +// Command: RF_GPIO_PIN_CFG +// Description: Configures the GPIO pins. +*/ +#define RF_GPIO_PIN_CFG 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +/* +// Set properties: RF_GLOBAL_XO_TUNE_2 +// Number of properties: 2 +// Group ID: 0x00 +// Start ID: 0x00 +// Default values: 0x40, 0x00, +// Descriptions: +// GLOBAL_XO_TUNE - Configure the internal capacitor frequency tuning bank for the crystal oscillator. +// GLOBAL_CLK_CFG - Clock configuration options. +*/ +#define RF_GLOBAL_XO_TUNE_2 0x11, 0x00, 0x02, 0x00, 0x52, 0x00 + +/* +// Set properties: RF_GLOBAL_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x00 +// Start ID: 0x03 +// Default values: 0x20, +// Descriptions: +// GLOBAL_CONFIG - Global configuration settings. +*/ +#define RF_GLOBAL_CONFIG_1 0x11, 0x00, 0x01, 0x03, 0x60 + +/* +// Set properties: RF_INT_CTL_ENABLE_2 +// Number of properties: 2 +// Group ID: 0x01 +// Start ID: 0x00 +// Default values: 0x04, 0x00, +// Descriptions: +// INT_CTL_ENABLE - This property provides for global enabling of the three interrupt groups (Chip, Modem and Packet Handler) in order to generate HW interrupts at the NIRQ pin. +// INT_CTL_PH_ENABLE - Enable individual interrupt sources within the Packet Handler Interrupt Group to generate a HW interrupt on the NIRQ output pin. +*/ +#define RF_INT_CTL_ENABLE_2 0x11, 0x01, 0x02, 0x00, 0x01, 0x20 + +/* +// Set properties: RF_FRR_CTL_A_MODE_4 +// Number of properties: 4 +// Group ID: 0x02 +// Start ID: 0x00 +// Default values: 0x01, 0x02, 0x09, 0x00, +// Descriptions: +// FRR_CTL_A_MODE - Fast Response Register A Configuration. +// FRR_CTL_B_MODE - Fast Response Register B Configuration. +// FRR_CTL_C_MODE - Fast Response Register C Configuration. +// FRR_CTL_D_MODE - Fast Response Register D Configuration. +*/ +#define RF_FRR_CTL_A_MODE_4 0x11, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 + +/* +// Set properties: RF_PREAMBLE_TX_LENGTH_1 +// Number of properties: 1 +// Group ID: 0x10 +// Start ID: 0x00 +// Default values: 0x08, +// Descriptions: +// PREAMBLE_TX_LENGTH - Configure length of TX Preamble. +*/ +#define RF_PREAMBLE_TX_LENGTH_1 0x11, 0x10, 0x01, 0x00, 0x0A + +/* +// Set properties: RF_PREAMBLE_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x10 +// Start ID: 0x04 +// Default values: 0x21, +// Descriptions: +// PREAMBLE_CONFIG - General configuration bits for the Preamble field. +*/ +#define RF_PREAMBLE_CONFIG_1 0x11, 0x10, 0x01, 0x04, 0x31 + +/* +// Set properties: RF_SYNC_CONFIG_3 +// Number of properties: 3 +// Group ID: 0x11 +// Start ID: 0x00 +// Default values: 0x01, 0x2D, 0xD4, +// Descriptions: +// SYNC_CONFIG - Sync Word configuration bits. +// SYNC_BITS_31_24 - Sync word. +// SYNC_BITS_23_16 - Sync word. +*/ +#define RF_SYNC_CONFIG_3 0x11, 0x11, 0x03, 0x00, 0x01, 0xB4, 0x2B + +/* +// Set properties: RF_PKT_CONFIG1_1 +// Number of properties: 1 +// Group ID: 0x12 +// Start ID: 0x06 +// Default values: 0x00, +// Descriptions: +// PKT_CONFIG1 - General configuration bits for transmission or reception of a packet. +*/ +#define RF_PKT_CONFIG1_1 0x11, 0x12, 0x01, 0x06, 0x02 + +/* +// Set properties: RF_PKT_FIELD_1_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x12 +// Start ID: 0x0F +// Default values: 0x00, +// Descriptions: +// PKT_FIELD_1_CONFIG - General data processing and packet configuration bits for Field 1. +*/ +#define RF_PKT_FIELD_1_CONFIG_1 0x11, 0x12, 0x01, 0x0F, 0x04 + +/* +// Set properties: RF_MODEM_MOD_TYPE_12 +// Number of properties: 12 +// Group ID: 0x20 +// Start ID: 0x00 +// Default values: 0x02, 0x80, 0x07, 0x0F, 0x42, 0x40, 0x01, 0xC9, 0xC3, 0x80, 0x00, 0x06, +// Descriptions: +// MODEM_MOD_TYPE - Selects the type of modulation. In TX mode, additionally selects the source of the modulation. +// MODEM_MAP_CONTROL - Controls polarity and mapping of transmit and receive bits. +// MODEM_DSM_CTRL - Miscellaneous control bits for the Delta-Sigma Modulator (DSM) in the PLL Synthesizer. +// MODEM_DATA_RATE_2 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_DATA_RATE_1 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_DATA_RATE_0 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_TX_NCO_MODE_3 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_2 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_1 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_0 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_FREQ_DEV_2 - 17-bit unsigned TX frequency deviation word. +// MODEM_FREQ_DEV_1 - 17-bit unsigned TX frequency deviation word. +*/ +#define RF_MODEM_MOD_TYPE_12 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x03, 0x0D, 0x40, 0x05, 0xC9, 0xC3, 0x80, 0x00, 0x02 + +/* +// Set properties: RF_MODEM_FREQ_DEV_0_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x0C +// Default values: 0xD3, +// Descriptions: +// MODEM_FREQ_DEV_0 - 17-bit unsigned TX frequency deviation word. +*/ +#define RF_MODEM_FREQ_DEV_0_1 0x11, 0x20, 0x01, 0x0C, 0xBB + +/* +// Set properties: RF_MODEM_TX_RAMP_DELAY_8 +// Number of properties: 8 +// Group ID: 0x20 +// Start ID: 0x18 +// Default values: 0x01, 0x00, 0x08, 0x03, 0xC0, 0x00, 0x10, 0x20, +// Descriptions: +// MODEM_TX_RAMP_DELAY - TX ramp-down delay setting. +// MODEM_MDM_CTRL - MDM control. +// MODEM_IF_CONTROL - Selects Fixed-IF, Scaled-IF, or Zero-IF mode of RX Modem operation. +// MODEM_IF_FREQ_2 - the IF frequency setting (an 18-bit signed number). +// MODEM_IF_FREQ_1 - the IF frequency setting (an 18-bit signed number). +// MODEM_IF_FREQ_0 - the IF frequency setting (an 18-bit signed number). +// MODEM_DECIMATION_CFG1 - Specifies three decimator ratios for the Cascaded Integrator Comb (CIC) filter. +// MODEM_DECIMATION_CFG0 - Specifies miscellaneous parameters and decimator ratios for the Cascaded Integrator Comb (CIC) filter. +*/ +#define RF_MODEM_TX_RAMP_DELAY_8 0x11, 0x20, 0x08, 0x18, 0x01, 0x80, 0x08, 0x03, 0x80, 0x00, 0x30, 0x20 + +/* +// Set properties: RF_MODEM_BCR_OSR_1_9 +// Number of properties: 9 +// Group ID: 0x20 +// Start ID: 0x22 +// Default values: 0x00, 0x4B, 0x06, 0xD3, 0xA0, 0x06, 0xD3, 0x02, 0xC0, +// Descriptions: +// MODEM_BCR_OSR_1 - RX BCR/Slicer oversampling rate (12-bit unsigned number). +// MODEM_BCR_OSR_0 - RX BCR/Slicer oversampling rate (12-bit unsigned number). +// MODEM_BCR_NCO_OFFSET_2 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_NCO_OFFSET_1 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_NCO_OFFSET_0 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_GAIN_1 - The unsigned 11-bit RX BCR loop gain value. +// MODEM_BCR_GAIN_0 - The unsigned 11-bit RX BCR loop gain value. +// MODEM_BCR_GEAR - RX BCR loop gear control. +// MODEM_BCR_MISC1 - Miscellaneous control bits for the RX BCR loop. +*/ +#define RF_MODEM_BCR_OSR_1_9 0x11, 0x20, 0x09, 0x22, 0x01, 0x77, 0x01, 0x5D, 0x86, 0x00, 0xAF, 0x02, 0xC2 + +/* +// Set properties: RF_MODEM_AFC_GEAR_7 +// Number of properties: 7 +// Group ID: 0x20 +// Start ID: 0x2C +// Default values: 0x00, 0x23, 0x83, 0x69, 0x00, 0x40, 0xA0, +// Descriptions: +// MODEM_AFC_GEAR - RX AFC loop gear control. +// MODEM_AFC_WAIT - RX AFC loop wait time control. +// MODEM_AFC_GAIN_1 - Sets the gain of the PLL-based AFC acquisition loop, and provides miscellaneous control bits for AFC functionality. +// MODEM_AFC_GAIN_0 - Sets the gain of the PLL-based AFC acquisition loop, and provides miscellaneous control bits for AFC functionality. +// MODEM_AFC_LIMITER_1 - Set the AFC limiter value. +// MODEM_AFC_LIMITER_0 - Set the AFC limiter value. +// MODEM_AFC_MISC - Specifies miscellaneous AFC control bits. +*/ +#define RF_MODEM_AFC_GEAR_7 0x11, 0x20, 0x07, 0x2C, 0x04, 0x36, 0x80, 0x0F, 0x15, 0x87, 0x80 + +/* +// Set properties: RF_MODEM_AGC_CONTROL_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x35 +// Default values: 0xE0, +// Descriptions: +// MODEM_AGC_CONTROL - Miscellaneous control bits for the Automatic Gain Control (AGC) function in the RX Chain. +*/ +#define RF_MODEM_AGC_CONTROL_1 0x11, 0x20, 0x01, 0x35, 0xE2 + +/* +// Set properties: RF_MODEM_AGC_WINDOW_SIZE_9 +// Number of properties: 9 +// Group ID: 0x20 +// Start ID: 0x38 +// Default values: 0x11, 0x10, 0x10, 0x0B, 0x1C, 0x40, 0x00, 0x00, 0x2B, +// Descriptions: +// MODEM_AGC_WINDOW_SIZE - Specifies the size of the measurement and settling windows for the AGC algorithm. +// MODEM_AGC_RFPD_DECAY - Sets the decay time of the RF peak detectors. +// MODEM_AGC_IFPD_DECAY - Sets the decay time of the IF peak detectors. +// MODEM_FSK4_GAIN1 - Specifies the gain factor of the secondary branch in 4(G)FSK ISI-suppression. +// MODEM_FSK4_GAIN0 - Specifies the gain factor of the primary branch in 4(G)FSK ISI-suppression. +// MODEM_FSK4_TH1 - 16 bit 4(G)FSK slicer threshold. +// MODEM_FSK4_TH0 - 16 bit 4(G)FSK slicer threshold. +// MODEM_FSK4_MAP - 4(G)FSK symbol mapping code. +// MODEM_OOK_PDTC - Configures the attack and decay times of the OOK Peak Detector. +*/ +#define RF_MODEM_AGC_WINDOW_SIZE_9 0x11, 0x20, 0x09, 0x38, 0x11, 0x52, 0x52, 0x00, 0x1A, 0xFF, 0xFF, 0x00, 0x2A + +/* +// Set properties: RF_MODEM_OOK_CNT1_8 +// Number of properties: 8 +// Group ID: 0x20 +// Start ID: 0x42 +// Default values: 0xA4, 0x03, 0x56, 0x02, 0x00, 0xA3, 0x02, 0x80, +// Descriptions: +// MODEM_OOK_CNT1 - OOK control. +// MODEM_OOK_MISC - Selects the detector(s) used for demodulation of an OOK signal, or for demodulation of a (G)FSK signal when using the asynchronous demodulator. +// MODEM_RAW_SEARCH - Defines and controls the search period length for the Moving Average and Min-Max detectors. +// MODEM_RAW_CONTROL - Defines gain and enable controls for raw / nonstandard mode. +// MODEM_RAW_EYE_1 - 11 bit eye-open detector threshold. +// MODEM_RAW_EYE_0 - 11 bit eye-open detector threshold. +// MODEM_ANT_DIV_MODE - Antenna diversity mode settings. +// MODEM_ANT_DIV_CONTROL - Specifies controls for the Antenna Diversity algorithm. +*/ +#define RF_MODEM_OOK_CNT1_8 0x11, 0x20, 0x08, 0x42, 0xA4, 0x02, 0xD6, 0x83, 0x00, 0xAD, 0x01, 0x80 + +/* +// Set properties: RF_MODEM_RSSI_COMP_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x4E +// Default values: 0x32, +// Descriptions: +// MODEM_RSSI_COMP - RSSI compensation value. +*/ +#define RF_MODEM_RSSI_COMP_1 0x11, 0x20, 0x01, 0x4E, 0x40 + +/* +// Set properties: RF_MODEM_CLKGEN_BAND_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x51 +// Default values: 0x08, +// Descriptions: +// MODEM_CLKGEN_BAND - Select PLL Synthesizer output divider ratio as a function of frequency band. +*/ +#define RF_MODEM_CLKGEN_BAND_1 0x11, 0x20, 0x01, 0x51, 0x0A + +/* +// Set properties: RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x00 +// Default values: 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01, +// Descriptions: +// MODEM_CHFLT_RX1_CHFLT_COE13_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE12_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE11_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE10_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE9_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE8_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE7_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE6_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE5_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE4_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE3_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE2_7_0 - Filter coefficients for the first set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12 0x11, 0x21, 0x0C, 0x00, 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01 + +/* +// Set properties: RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x0C +// Default values: 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F, 0xFF, 0xC4, 0x30, 0x7F, 0xF5, 0xB5, +// Descriptions: +// MODEM_CHFLT_RX1_CHFLT_COE1_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE0_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM1 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM2 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM3 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE13_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE12_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE11_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE10_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE9_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE8_7_0 - Filter coefficients for the second set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12 0x11, 0x21, 0x0C, 0x0C, 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F, 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9 + +/* +// Set properties: RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x18 +// Default values: 0xB8, 0xDE, 0x05, 0x17, 0x16, 0x0C, 0x03, 0x00, 0x15, 0xFF, 0x00, 0x00, +// Descriptions: +// MODEM_CHFLT_RX2_CHFLT_COE7_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE6_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE5_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE4_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE3_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE2_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE1_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE0_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM1 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM2 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM3 - Filter coefficients for the second set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12 0x11, 0x21, 0x0C, 0x18, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01, 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F + +/* +// Set properties: RF_PA_MODE_4 +// Number of properties: 4 +// Group ID: 0x22 +// Start ID: 0x00 +// Default values: 0x08, 0x7F, 0x00, 0x5D, +// Descriptions: +// PA_MODE - Selects the PA operating mode, and selects resolution of PA power adjustment (i.e., step size). +// PA_PWR_LVL - Configuration of PA output power level. +// PA_BIAS_CLKDUTY - Configuration of the PA Bias and duty cycle of the TX clock source. +// PA_TC - Configuration of PA ramping parameters. +*/ +#define RF_PA_MODE_4 0x11, 0x22, 0x04, 0x00, 0x08, 0x7F, 0x00, 0x3D + +/* +// Set properties: RF_SYNTH_PFDCP_CPFF_7 +// Number of properties: 7 +// Group ID: 0x23 +// Start ID: 0x00 +// Default values: 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03, +// Descriptions: +// SYNTH_PFDCP_CPFF - Feed forward charge pump current selection. +// SYNTH_PFDCP_CPINT - Integration charge pump current selection. +// SYNTH_VCO_KV - Gain scaling factors (Kv) for the VCO tuning varactors on both the integrated-path and feed forward path. +// SYNTH_LPFILT3 - Value of resistor R2 in feed-forward path of loop filter. +// SYNTH_LPFILT2 - Value of capacitor C2 in feed-forward path of loop filter. +// SYNTH_LPFILT1 - Value of capacitors C1 and C3 in feed-forward path of loop filter. +// SYNTH_LPFILT0 - Bias current of the active amplifier in the feed-forward loop filter. +*/ +#define RF_SYNTH_PFDCP_CPFF_7 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 + +/* +// Set properties: RF_FREQ_CONTROL_INTE_8 +// Number of properties: 8 +// Group ID: 0x40 +// Start ID: 0x00 +// Default values: 0x3C, 0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, +// Descriptions: +// FREQ_CONTROL_INTE - Frac-N PLL Synthesizer integer divide number. +// FREQ_CONTROL_FRAC_2 - Frac-N PLL fraction number. +// FREQ_CONTROL_FRAC_1 - Frac-N PLL fraction number. +// FREQ_CONTROL_FRAC_0 - Frac-N PLL fraction number. +// FREQ_CONTROL_CHANNEL_STEP_SIZE_1 - EZ Frequency Programming channel step size. +// FREQ_CONTROL_CHANNEL_STEP_SIZE_0 - EZ Frequency Programming channel step size. +// FREQ_CONTROL_W_SIZE - Set window gating period (in number of crystal reference clock cycles) for counting VCO frequency during calibration. +// FREQ_CONTROL_VCOCNT_RX_ADJ - Adjust target count for VCO calibration in RX mode. +*/ +#define RF_FREQ_CONTROL_INTE_8 0x11, 0x40, 0x08, 0x00, 0x38, 0x0E, 0xEE, 0xEE, 0x44, 0x44, 0x20, 0xFE + + +// AUTOMATICALLY GENERATED CODE! +// DO NOT EDIT/MODIFY BELOW THIS LINE! +// -------------------------------------------- + +#ifndef FIRMWARE_LOAD_COMPILE +#define RADIO_CONFIGURATION_DATA_ARRAY { \ + 0x07, RF_POWER_UP, \ + 0x08, RF_GPIO_PIN_CFG, \ + 0x06, RF_GLOBAL_XO_TUNE_2, \ + 0x05, RF_GLOBAL_CONFIG_1, \ + 0x06, RF_INT_CTL_ENABLE_2, \ + 0x08, RF_FRR_CTL_A_MODE_4, \ + 0x05, RF_PREAMBLE_TX_LENGTH_1, \ + 0x05, RF_PREAMBLE_CONFIG_1, \ + 0x07, RF_SYNC_CONFIG_3, \ + 0x05, RF_PKT_CONFIG1_1, \ + 0x05, RF_PKT_FIELD_1_CONFIG_1, \ + 0x10, RF_MODEM_MOD_TYPE_12, \ + 0x05, RF_MODEM_FREQ_DEV_0_1, \ + 0x0C, RF_MODEM_TX_RAMP_DELAY_8, \ + 0x0D, RF_MODEM_BCR_OSR_1_9, \ + 0x0B, RF_MODEM_AFC_GEAR_7, \ + 0x05, RF_MODEM_AGC_CONTROL_1, \ + 0x0D, RF_MODEM_AGC_WINDOW_SIZE_9, \ + 0x0C, RF_MODEM_OOK_CNT1_8, \ + 0x05, RF_MODEM_RSSI_COMP_1, \ + 0x05, RF_MODEM_CLKGEN_BAND_1, \ + 0x10, RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12, \ + 0x10, RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12, \ + 0x10, RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12, \ + 0x08, RF_PA_MODE_4, \ + 0x0B, RF_SYNTH_PFDCP_CPFF_7, \ + 0x0C, RF_FREQ_CONTROL_INTE_8, \ + 0x00 \ + } +#else +#define RADIO_CONFIGURATION_DATA_ARRAY { 0 } +#endif + +// DEFAULT VALUES FOR CONFIGURATION PARAMETERS +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ_DEFAULT 30000000L +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER_DEFAULT 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH_DEFAULT 0x10 +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP_DEFAULT 0x01 +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET_DEFAULT 0x1000 + +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH_INCLUDED 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH_SIZE 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH { } + +#ifndef RADIO_CONFIGURATION_DATA_ARRAY +#error "This property must be defined!" +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET_DEFAULT +#endif + +#define RADIO_CONFIGURATION_DATA { \ + Radio_Configuration_Data_Array, \ + RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER, \ + RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH, \ + RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP, \ + RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET \ + } + +#endif /* RADIO_CONFIG_H_ */ diff --git a/RF24configs/radio_config_Si4464_30_915_2GFSK_10_20.h b/RF24configs/radio_config_Si4464_30_915_2GFSK_10_20.h new file mode 100644 index 0000000..fa23d9c --- /dev/null +++ b/RF24configs/radio_config_Si4464_30_915_2GFSK_10_20.h @@ -0,0 +1,516 @@ +/*! @file radio_config.h + * @brief This file contains the automatically generated + * configurations. + * + * @n WDS GUI Version: 3.2.11.0 + * @n Device: Si4464 Rev.: B1 + * + * @b COPYRIGHT + * @n Silicon Laboratories Confidential + * @n Copyright 2017 Silicon Laboratories, Inc. + * @n http://www.silabs.com + */ + +#ifndef RADIO_CONFIG_H_ +#define RADIO_CONFIG_H_ + +// USER DEFINED PARAMETERS +// Define your own parameters here + +// INPUT DATA +/* +// Crys_freq(Hz): 30000000 Crys_tol(ppm): 20 IF_mode: 2 High_perf_Ch_Fil: 1 OSRtune: 0 Ch_Fil_Bw_AFC: 0 ANT_DIV: 0 PM_pattern: 0 +// MOD_type: 3 Rsymb(sps): 10000 Fdev(Hz): 20000 RXBW(Hz): 150000 Manchester: 0 AFC_en: 0 Rsymb_error: 0.0 Chip-Version: 3 +// RF Freq.(MHz): 915 API_TC: 29 fhst: 250000 inputBW: 0 BERT: 0 RAW_dout: 0 D_source: 0 Hi_pfm_div: 1 +// +// # RX IF frequency is -468750 Hz +// # WB filter 3 (BW = 123.48 kHz); NB-filter 3 (BW = 123.48 kHz) +// +// Modulation index: 4 +*/ + + +// CONFIGURATION PARAMETERS +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ 30000000L +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH 0x07 +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP 0x03 +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET 0xF000 + + +// CONFIGURATION COMMANDS + +/* +// Command: RF_POWER_UP +// Description: Command to power-up the device and select the operational mode and functionality. +*/ +#define RF_POWER_UP 0x02, 0x01, 0x00, 0x01, 0xC9, 0xC3, 0x80 + +/* +// Command: RF_GPIO_PIN_CFG +// Description: Configures the GPIO pins. +*/ +#define RF_GPIO_PIN_CFG 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +/* +// Set properties: RF_GLOBAL_XO_TUNE_2 +// Number of properties: 2 +// Group ID: 0x00 +// Start ID: 0x00 +// Default values: 0x40, 0x00, +// Descriptions: +// GLOBAL_XO_TUNE - Configure the internal capacitor frequency tuning bank for the crystal oscillator. +// GLOBAL_CLK_CFG - Clock configuration options. +*/ +#define RF_GLOBAL_XO_TUNE_2 0x11, 0x00, 0x02, 0x00, 0x52, 0x00 + +/* +// Set properties: RF_GLOBAL_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x00 +// Start ID: 0x03 +// Default values: 0x20, +// Descriptions: +// GLOBAL_CONFIG - Global configuration settings. +*/ +#define RF_GLOBAL_CONFIG_1 0x11, 0x00, 0x01, 0x03, 0x60 + +/* +// Set properties: RF_INT_CTL_ENABLE_2 +// Number of properties: 2 +// Group ID: 0x01 +// Start ID: 0x00 +// Default values: 0x04, 0x00, +// Descriptions: +// INT_CTL_ENABLE - This property provides for global enabling of the three interrupt groups (Chip, Modem and Packet Handler) in order to generate HW interrupts at the NIRQ pin. +// INT_CTL_PH_ENABLE - Enable individual interrupt sources within the Packet Handler Interrupt Group to generate a HW interrupt on the NIRQ output pin. +*/ +#define RF_INT_CTL_ENABLE_2 0x11, 0x01, 0x02, 0x00, 0x01, 0x20 + +/* +// Set properties: RF_FRR_CTL_A_MODE_4 +// Number of properties: 4 +// Group ID: 0x02 +// Start ID: 0x00 +// Default values: 0x01, 0x02, 0x09, 0x00, +// Descriptions: +// FRR_CTL_A_MODE - Fast Response Register A Configuration. +// FRR_CTL_B_MODE - Fast Response Register B Configuration. +// FRR_CTL_C_MODE - Fast Response Register C Configuration. +// FRR_CTL_D_MODE - Fast Response Register D Configuration. +*/ +#define RF_FRR_CTL_A_MODE_4 0x11, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 + +/* +// Set properties: RF_PREAMBLE_TX_LENGTH_1 +// Number of properties: 1 +// Group ID: 0x10 +// Start ID: 0x00 +// Default values: 0x08, +// Descriptions: +// PREAMBLE_TX_LENGTH - Configure length of TX Preamble. +*/ +#define RF_PREAMBLE_TX_LENGTH_1 0x11, 0x10, 0x01, 0x00, 0x0A + +/* +// Set properties: RF_PREAMBLE_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x10 +// Start ID: 0x04 +// Default values: 0x21, +// Descriptions: +// PREAMBLE_CONFIG - General configuration bits for the Preamble field. +*/ +#define RF_PREAMBLE_CONFIG_1 0x11, 0x10, 0x01, 0x04, 0x31 + +/* +// Set properties: RF_SYNC_CONFIG_3 +// Number of properties: 3 +// Group ID: 0x11 +// Start ID: 0x00 +// Default values: 0x01, 0x2D, 0xD4, +// Descriptions: +// SYNC_CONFIG - Sync Word configuration bits. +// SYNC_BITS_31_24 - Sync word. +// SYNC_BITS_23_16 - Sync word. +*/ +#define RF_SYNC_CONFIG_3 0x11, 0x11, 0x03, 0x00, 0x01, 0xB4, 0x2B + +/* +// Set properties: RF_PKT_CONFIG1_1 +// Number of properties: 1 +// Group ID: 0x12 +// Start ID: 0x06 +// Default values: 0x00, +// Descriptions: +// PKT_CONFIG1 - General configuration bits for transmission or reception of a packet. +*/ +#define RF_PKT_CONFIG1_1 0x11, 0x12, 0x01, 0x06, 0x02 + +/* +// Set properties: RF_PKT_FIELD_1_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x12 +// Start ID: 0x0F +// Default values: 0x00, +// Descriptions: +// PKT_FIELD_1_CONFIG - General data processing and packet configuration bits for Field 1. +*/ +#define RF_PKT_FIELD_1_CONFIG_1 0x11, 0x12, 0x01, 0x0F, 0x04 + +/* +// Set properties: RF_MODEM_MOD_TYPE_12 +// Number of properties: 12 +// Group ID: 0x20 +// Start ID: 0x00 +// Default values: 0x02, 0x80, 0x07, 0x0F, 0x42, 0x40, 0x01, 0xC9, 0xC3, 0x80, 0x00, 0x06, +// Descriptions: +// MODEM_MOD_TYPE - Selects the type of modulation. In TX mode, additionally selects the source of the modulation. +// MODEM_MAP_CONTROL - Controls polarity and mapping of transmit and receive bits. +// MODEM_DSM_CTRL - Miscellaneous control bits for the Delta-Sigma Modulator (DSM) in the PLL Synthesizer. +// MODEM_DATA_RATE_2 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_DATA_RATE_1 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_DATA_RATE_0 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_TX_NCO_MODE_3 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_2 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_1 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_0 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_FREQ_DEV_2 - 17-bit unsigned TX frequency deviation word. +// MODEM_FREQ_DEV_1 - 17-bit unsigned TX frequency deviation word. +*/ +#define RF_MODEM_MOD_TYPE_12 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x06, 0x1A, 0x80, 0x05, 0xC9, 0xC3, 0x80, 0x00, 0x02 + +/* +// Set properties: RF_MODEM_FREQ_DEV_0_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x0C +// Default values: 0xD3, +// Descriptions: +// MODEM_FREQ_DEV_0 - 17-bit unsigned TX frequency deviation word. +*/ +#define RF_MODEM_FREQ_DEV_0_1 0x11, 0x20, 0x01, 0x0C, 0xBB + +/* +// Set properties: RF_MODEM_TX_RAMP_DELAY_8 +// Number of properties: 8 +// Group ID: 0x20 +// Start ID: 0x18 +// Default values: 0x01, 0x00, 0x08, 0x03, 0xC0, 0x00, 0x10, 0x20, +// Descriptions: +// MODEM_TX_RAMP_DELAY - TX ramp-down delay setting. +// MODEM_MDM_CTRL - MDM control. +// MODEM_IF_CONTROL - Selects Fixed-IF, Scaled-IF, or Zero-IF mode of RX Modem operation. +// MODEM_IF_FREQ_2 - the IF frequency setting (an 18-bit signed number). +// MODEM_IF_FREQ_1 - the IF frequency setting (an 18-bit signed number). +// MODEM_IF_FREQ_0 - the IF frequency setting (an 18-bit signed number). +// MODEM_DECIMATION_CFG1 - Specifies three decimator ratios for the Cascaded Integrator Comb (CIC) filter. +// MODEM_DECIMATION_CFG0 - Specifies miscellaneous parameters and decimator ratios for the Cascaded Integrator Comb (CIC) filter. +*/ +#define RF_MODEM_TX_RAMP_DELAY_8 0x11, 0x20, 0x08, 0x18, 0x01, 0x80, 0x08, 0x03, 0xC0, 0x00, 0x10, 0x10 + +/* +// Set properties: RF_MODEM_BCR_OSR_1_9 +// Number of properties: 9 +// Group ID: 0x20 +// Start ID: 0x22 +// Default values: 0x00, 0x4B, 0x06, 0xD3, 0xA0, 0x06, 0xD3, 0x02, 0xC0, +// Descriptions: +// MODEM_BCR_OSR_1 - RX BCR/Slicer oversampling rate (12-bit unsigned number). +// MODEM_BCR_OSR_0 - RX BCR/Slicer oversampling rate (12-bit unsigned number). +// MODEM_BCR_NCO_OFFSET_2 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_NCO_OFFSET_1 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_NCO_OFFSET_0 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_GAIN_1 - The unsigned 11-bit RX BCR loop gain value. +// MODEM_BCR_GAIN_0 - The unsigned 11-bit RX BCR loop gain value. +// MODEM_BCR_GEAR - RX BCR loop gear control. +// MODEM_BCR_MISC1 - Miscellaneous control bits for the RX BCR loop. +*/ +#define RF_MODEM_BCR_OSR_1_9 0x11, 0x20, 0x09, 0x22, 0x01, 0xF4, 0x01, 0x06, 0x25, 0x00, 0x83, 0x02, 0xC2 + +/* +// Set properties: RF_MODEM_AFC_GEAR_7 +// Number of properties: 7 +// Group ID: 0x20 +// Start ID: 0x2C +// Default values: 0x00, 0x23, 0x83, 0x69, 0x00, 0x40, 0xA0, +// Descriptions: +// MODEM_AFC_GEAR - RX AFC loop gear control. +// MODEM_AFC_WAIT - RX AFC loop wait time control. +// MODEM_AFC_GAIN_1 - Sets the gain of the PLL-based AFC acquisition loop, and provides miscellaneous control bits for AFC functionality. +// MODEM_AFC_GAIN_0 - Sets the gain of the PLL-based AFC acquisition loop, and provides miscellaneous control bits for AFC functionality. +// MODEM_AFC_LIMITER_1 - Set the AFC limiter value. +// MODEM_AFC_LIMITER_0 - Set the AFC limiter value. +// MODEM_AFC_MISC - Specifies miscellaneous AFC control bits. +*/ +#define RF_MODEM_AFC_GEAR_7 0x11, 0x20, 0x07, 0x2C, 0x04, 0x36, 0x80, 0x0F, 0x16, 0xFE, 0x80 + +/* +// Set properties: RF_MODEM_AGC_CONTROL_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x35 +// Default values: 0xE0, +// Descriptions: +// MODEM_AGC_CONTROL - Miscellaneous control bits for the Automatic Gain Control (AGC) function in the RX Chain. +*/ +#define RF_MODEM_AGC_CONTROL_1 0x11, 0x20, 0x01, 0x35, 0xE2 + +/* +// Set properties: RF_MODEM_AGC_WINDOW_SIZE_9 +// Number of properties: 9 +// Group ID: 0x20 +// Start ID: 0x38 +// Default values: 0x11, 0x10, 0x10, 0x0B, 0x1C, 0x40, 0x00, 0x00, 0x2B, +// Descriptions: +// MODEM_AGC_WINDOW_SIZE - Specifies the size of the measurement and settling windows for the AGC algorithm. +// MODEM_AGC_RFPD_DECAY - Sets the decay time of the RF peak detectors. +// MODEM_AGC_IFPD_DECAY - Sets the decay time of the IF peak detectors. +// MODEM_FSK4_GAIN1 - Specifies the gain factor of the secondary branch in 4(G)FSK ISI-suppression. +// MODEM_FSK4_GAIN0 - Specifies the gain factor of the primary branch in 4(G)FSK ISI-suppression. +// MODEM_FSK4_TH1 - 16 bit 4(G)FSK slicer threshold. +// MODEM_FSK4_TH0 - 16 bit 4(G)FSK slicer threshold. +// MODEM_FSK4_MAP - 4(G)FSK symbol mapping code. +// MODEM_OOK_PDTC - Configures the attack and decay times of the OOK Peak Detector. +*/ +#define RF_MODEM_AGC_WINDOW_SIZE_9 0x11, 0x20, 0x09, 0x38, 0x11, 0x6D, 0x6D, 0x00, 0x1A, 0xFF, 0xFF, 0x00, 0x2A + +/* +// Set properties: RF_MODEM_OOK_CNT1_8 +// Number of properties: 8 +// Group ID: 0x20 +// Start ID: 0x42 +// Default values: 0xA4, 0x03, 0x56, 0x02, 0x00, 0xA3, 0x02, 0x80, +// Descriptions: +// MODEM_OOK_CNT1 - OOK control. +// MODEM_OOK_MISC - Selects the detector(s) used for demodulation of an OOK signal, or for demodulation of a (G)FSK signal when using the asynchronous demodulator. +// MODEM_RAW_SEARCH - Defines and controls the search period length for the Moving Average and Min-Max detectors. +// MODEM_RAW_CONTROL - Defines gain and enable controls for raw / nonstandard mode. +// MODEM_RAW_EYE_1 - 11 bit eye-open detector threshold. +// MODEM_RAW_EYE_0 - 11 bit eye-open detector threshold. +// MODEM_ANT_DIV_MODE - Antenna diversity mode settings. +// MODEM_ANT_DIV_CONTROL - Specifies controls for the Antenna Diversity algorithm. +*/ +#define RF_MODEM_OOK_CNT1_8 0x11, 0x20, 0x08, 0x42, 0xA4, 0x02, 0xD6, 0x83, 0x00, 0x82, 0x01, 0x80 + +/* +// Set properties: RF_MODEM_RSSI_COMP_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x4E +// Default values: 0x32, +// Descriptions: +// MODEM_RSSI_COMP - RSSI compensation value. +*/ +#define RF_MODEM_RSSI_COMP_1 0x11, 0x20, 0x01, 0x4E, 0x40 + +/* +// Set properties: RF_MODEM_CLKGEN_BAND_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x51 +// Default values: 0x08, +// Descriptions: +// MODEM_CLKGEN_BAND - Select PLL Synthesizer output divider ratio as a function of frequency band. +*/ +#define RF_MODEM_CLKGEN_BAND_1 0x11, 0x20, 0x01, 0x51, 0x08 + +/* +// Set properties: RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x00 +// Default values: 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01, +// Descriptions: +// MODEM_CHFLT_RX1_CHFLT_COE13_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE12_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE11_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE10_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE9_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE8_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE7_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE6_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE5_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE4_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE3_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE2_7_0 - Filter coefficients for the first set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12 0x11, 0x21, 0x0C, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11 + +/* +// Set properties: RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x0C +// Default values: 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F, 0xFF, 0xC4, 0x30, 0x7F, 0xF5, 0xB5, +// Descriptions: +// MODEM_CHFLT_RX1_CHFLT_COE1_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE0_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM1 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM2 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM3 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE13_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE12_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE11_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE10_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE9_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE8_7_0 - Filter coefficients for the second set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12 0x11, 0x21, 0x0C, 0x0C, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1 + +/* +// Set properties: RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x18 +// Default values: 0xB8, 0xDE, 0x05, 0x17, 0x16, 0x0C, 0x03, 0x00, 0x15, 0xFF, 0x00, 0x00, +// Descriptions: +// MODEM_CHFLT_RX2_CHFLT_COE7_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE6_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE5_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE4_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE3_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE2_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE1_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE0_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM1 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM2 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM3 - Filter coefficients for the second set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12 0x11, 0x21, 0x0C, 0x18, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00 + +/* +// Set properties: RF_PA_MODE_4 +// Number of properties: 4 +// Group ID: 0x22 +// Start ID: 0x00 +// Default values: 0x08, 0x7F, 0x00, 0x5D, +// Descriptions: +// PA_MODE - Selects the PA operating mode, and selects resolution of PA power adjustment (i.e., step size). +// PA_PWR_LVL - Configuration of PA output power level. +// PA_BIAS_CLKDUTY - Configuration of the PA Bias and duty cycle of the TX clock source. +// PA_TC - Configuration of PA ramping parameters. +*/ +#define RF_PA_MODE_4 0x11, 0x22, 0x04, 0x00, 0x08, 0x7F, 0x00, 0x3D + +/* +// Set properties: RF_SYNTH_PFDCP_CPFF_7 +// Number of properties: 7 +// Group ID: 0x23 +// Start ID: 0x00 +// Default values: 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03, +// Descriptions: +// SYNTH_PFDCP_CPFF - Feed forward charge pump current selection. +// SYNTH_PFDCP_CPINT - Integration charge pump current selection. +// SYNTH_VCO_KV - Gain scaling factors (Kv) for the VCO tuning varactors on both the integrated-path and feed forward path. +// SYNTH_LPFILT3 - Value of resistor R2 in feed-forward path of loop filter. +// SYNTH_LPFILT2 - Value of capacitor C2 in feed-forward path of loop filter. +// SYNTH_LPFILT1 - Value of capacitors C1 and C3 in feed-forward path of loop filter. +// SYNTH_LPFILT0 - Bias current of the active amplifier in the feed-forward loop filter. +*/ +#define RF_SYNTH_PFDCP_CPFF_7 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 + +/* +// Set properties: RF_FREQ_CONTROL_INTE_8 +// Number of properties: 8 +// Group ID: 0x40 +// Start ID: 0x00 +// Default values: 0x3C, 0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, +// Descriptions: +// FREQ_CONTROL_INTE - Frac-N PLL Synthesizer integer divide number. +// FREQ_CONTROL_FRAC_2 - Frac-N PLL fraction number. +// FREQ_CONTROL_FRAC_1 - Frac-N PLL fraction number. +// FREQ_CONTROL_FRAC_0 - Frac-N PLL fraction number. +// FREQ_CONTROL_CHANNEL_STEP_SIZE_1 - EZ Frequency Programming channel step size. +// FREQ_CONTROL_CHANNEL_STEP_SIZE_0 - EZ Frequency Programming channel step size. +// FREQ_CONTROL_W_SIZE - Set window gating period (in number of crystal reference clock cycles) for counting VCO frequency during calibration. +// FREQ_CONTROL_VCOCNT_RX_ADJ - Adjust target count for VCO calibration in RX mode. +*/ +#define RF_FREQ_CONTROL_INTE_8 0x11, 0x40, 0x08, 0x00, 0x3C, 0x08, 0x00, 0x00, 0x22, 0x22, 0x20, 0xFF + + +// AUTOMATICALLY GENERATED CODE! +// DO NOT EDIT/MODIFY BELOW THIS LINE! +// -------------------------------------------- + +#ifndef FIRMWARE_LOAD_COMPILE +#define RADIO_CONFIGURATION_DATA_ARRAY { \ + 0x07, RF_POWER_UP, \ + 0x08, RF_GPIO_PIN_CFG, \ + 0x06, RF_GLOBAL_XO_TUNE_2, \ + 0x05, RF_GLOBAL_CONFIG_1, \ + 0x06, RF_INT_CTL_ENABLE_2, \ + 0x08, RF_FRR_CTL_A_MODE_4, \ + 0x05, RF_PREAMBLE_TX_LENGTH_1, \ + 0x05, RF_PREAMBLE_CONFIG_1, \ + 0x07, RF_SYNC_CONFIG_3, \ + 0x05, RF_PKT_CONFIG1_1, \ + 0x05, RF_PKT_FIELD_1_CONFIG_1, \ + 0x10, RF_MODEM_MOD_TYPE_12, \ + 0x05, RF_MODEM_FREQ_DEV_0_1, \ + 0x0C, RF_MODEM_TX_RAMP_DELAY_8, \ + 0x0D, RF_MODEM_BCR_OSR_1_9, \ + 0x0B, RF_MODEM_AFC_GEAR_7, \ + 0x05, RF_MODEM_AGC_CONTROL_1, \ + 0x0D, RF_MODEM_AGC_WINDOW_SIZE_9, \ + 0x0C, RF_MODEM_OOK_CNT1_8, \ + 0x05, RF_MODEM_RSSI_COMP_1, \ + 0x05, RF_MODEM_CLKGEN_BAND_1, \ + 0x10, RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12, \ + 0x10, RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12, \ + 0x10, RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12, \ + 0x08, RF_PA_MODE_4, \ + 0x0B, RF_SYNTH_PFDCP_CPFF_7, \ + 0x0C, RF_FREQ_CONTROL_INTE_8, \ + 0x00 \ + } +#else +#define RADIO_CONFIGURATION_DATA_ARRAY { 0 } +#endif + +// DEFAULT VALUES FOR CONFIGURATION PARAMETERS +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ_DEFAULT 30000000L +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER_DEFAULT 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH_DEFAULT 0x10 +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP_DEFAULT 0x01 +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET_DEFAULT 0x1000 + +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH_INCLUDED 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH_SIZE 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH { } + +#ifndef RADIO_CONFIGURATION_DATA_ARRAY +#error "This property must be defined!" +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET_DEFAULT +#endif + +#define RADIO_CONFIGURATION_DATA { \ + Radio_Configuration_Data_Array, \ + RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER, \ + RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH, \ + RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP, \ + RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET \ + } + +#endif /* RADIO_CONFIG_H_ */ diff --git a/RF24configs/radio_config_Si4464_30_915_2GFSK_5_10.h b/RF24configs/radio_config_Si4464_30_915_2GFSK_5_10.h new file mode 100644 index 0000000..54a6a00 --- /dev/null +++ b/RF24configs/radio_config_Si4464_30_915_2GFSK_5_10.h @@ -0,0 +1,516 @@ +/*! @file radio_config.h + * @brief This file contains the automatically generated + * configurations. + * + * @n WDS GUI Version: 3.2.11.0 + * @n Device: Si4464 Rev.: B1 + * + * @b COPYRIGHT + * @n Silicon Laboratories Confidential + * @n Copyright 2017 Silicon Laboratories, Inc. + * @n http://www.silabs.com + */ + +#ifndef RADIO_CONFIG_H_ +#define RADIO_CONFIG_H_ + +// USER DEFINED PARAMETERS +// Define your own parameters here + +// INPUT DATA +/* +// Crys_freq(Hz): 30000000 Crys_tol(ppm): 20 IF_mode: 2 High_perf_Ch_Fil: 1 OSRtune: 0 Ch_Fil_Bw_AFC: 0 ANT_DIV: 0 PM_pattern: 0 +// MOD_type: 3 Rsymb(sps): 5000 Fdev(Hz): 10000 RXBW(Hz): 150000 Manchester: 0 AFC_en: 0 Rsymb_error: 0.0 Chip-Version: 3 +// RF Freq.(MHz): 915 API_TC: 29 fhst: 250000 inputBW: 0 BERT: 0 RAW_dout: 0 D_source: 0 Hi_pfm_div: 1 +// +// # RX IF frequency is -468750 Hz +// # WB filter 2 (BW = 103.06 kHz); NB-filter 2 (BW = 103.06 kHz) +// +// Modulation index: 4 +*/ + + +// CONFIGURATION PARAMETERS +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ 30000000L +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH 0x07 +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP 0x03 +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET 0xF000 + + +// CONFIGURATION COMMANDS + +/* +// Command: RF_POWER_UP +// Description: Command to power-up the device and select the operational mode and functionality. +*/ +#define RF_POWER_UP 0x02, 0x01, 0x00, 0x01, 0xC9, 0xC3, 0x80 + +/* +// Command: RF_GPIO_PIN_CFG +// Description: Configures the GPIO pins. +*/ +#define RF_GPIO_PIN_CFG 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +/* +// Set properties: RF_GLOBAL_XO_TUNE_2 +// Number of properties: 2 +// Group ID: 0x00 +// Start ID: 0x00 +// Default values: 0x40, 0x00, +// Descriptions: +// GLOBAL_XO_TUNE - Configure the internal capacitor frequency tuning bank for the crystal oscillator. +// GLOBAL_CLK_CFG - Clock configuration options. +*/ +#define RF_GLOBAL_XO_TUNE_2 0x11, 0x00, 0x02, 0x00, 0x52, 0x00 + +/* +// Set properties: RF_GLOBAL_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x00 +// Start ID: 0x03 +// Default values: 0x20, +// Descriptions: +// GLOBAL_CONFIG - Global configuration settings. +*/ +#define RF_GLOBAL_CONFIG_1 0x11, 0x00, 0x01, 0x03, 0x60 + +/* +// Set properties: RF_INT_CTL_ENABLE_2 +// Number of properties: 2 +// Group ID: 0x01 +// Start ID: 0x00 +// Default values: 0x04, 0x00, +// Descriptions: +// INT_CTL_ENABLE - This property provides for global enabling of the three interrupt groups (Chip, Modem and Packet Handler) in order to generate HW interrupts at the NIRQ pin. +// INT_CTL_PH_ENABLE - Enable individual interrupt sources within the Packet Handler Interrupt Group to generate a HW interrupt on the NIRQ output pin. +*/ +#define RF_INT_CTL_ENABLE_2 0x11, 0x01, 0x02, 0x00, 0x01, 0x20 + +/* +// Set properties: RF_FRR_CTL_A_MODE_4 +// Number of properties: 4 +// Group ID: 0x02 +// Start ID: 0x00 +// Default values: 0x01, 0x02, 0x09, 0x00, +// Descriptions: +// FRR_CTL_A_MODE - Fast Response Register A Configuration. +// FRR_CTL_B_MODE - Fast Response Register B Configuration. +// FRR_CTL_C_MODE - Fast Response Register C Configuration. +// FRR_CTL_D_MODE - Fast Response Register D Configuration. +*/ +#define RF_FRR_CTL_A_MODE_4 0x11, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 + +/* +// Set properties: RF_PREAMBLE_TX_LENGTH_1 +// Number of properties: 1 +// Group ID: 0x10 +// Start ID: 0x00 +// Default values: 0x08, +// Descriptions: +// PREAMBLE_TX_LENGTH - Configure length of TX Preamble. +*/ +#define RF_PREAMBLE_TX_LENGTH_1 0x11, 0x10, 0x01, 0x00, 0x0A + +/* +// Set properties: RF_PREAMBLE_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x10 +// Start ID: 0x04 +// Default values: 0x21, +// Descriptions: +// PREAMBLE_CONFIG - General configuration bits for the Preamble field. +*/ +#define RF_PREAMBLE_CONFIG_1 0x11, 0x10, 0x01, 0x04, 0x31 + +/* +// Set properties: RF_SYNC_CONFIG_3 +// Number of properties: 3 +// Group ID: 0x11 +// Start ID: 0x00 +// Default values: 0x01, 0x2D, 0xD4, +// Descriptions: +// SYNC_CONFIG - Sync Word configuration bits. +// SYNC_BITS_31_24 - Sync word. +// SYNC_BITS_23_16 - Sync word. +*/ +#define RF_SYNC_CONFIG_3 0x11, 0x11, 0x03, 0x00, 0x01, 0xB4, 0x2B + +/* +// Set properties: RF_PKT_CONFIG1_1 +// Number of properties: 1 +// Group ID: 0x12 +// Start ID: 0x06 +// Default values: 0x00, +// Descriptions: +// PKT_CONFIG1 - General configuration bits for transmission or reception of a packet. +*/ +#define RF_PKT_CONFIG1_1 0x11, 0x12, 0x01, 0x06, 0x02 + +/* +// Set properties: RF_PKT_FIELD_1_CONFIG_1 +// Number of properties: 1 +// Group ID: 0x12 +// Start ID: 0x0F +// Default values: 0x00, +// Descriptions: +// PKT_FIELD_1_CONFIG - General data processing and packet configuration bits for Field 1. +*/ +#define RF_PKT_FIELD_1_CONFIG_1 0x11, 0x12, 0x01, 0x0F, 0x04 + +/* +// Set properties: RF_MODEM_MOD_TYPE_12 +// Number of properties: 12 +// Group ID: 0x20 +// Start ID: 0x00 +// Default values: 0x02, 0x80, 0x07, 0x0F, 0x42, 0x40, 0x01, 0xC9, 0xC3, 0x80, 0x00, 0x06, +// Descriptions: +// MODEM_MOD_TYPE - Selects the type of modulation. In TX mode, additionally selects the source of the modulation. +// MODEM_MAP_CONTROL - Controls polarity and mapping of transmit and receive bits. +// MODEM_DSM_CTRL - Miscellaneous control bits for the Delta-Sigma Modulator (DSM) in the PLL Synthesizer. +// MODEM_DATA_RATE_2 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_DATA_RATE_1 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_DATA_RATE_0 - Unsigned 24-bit value used to determine the TX data rate +// MODEM_TX_NCO_MODE_3 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_2 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_1 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_TX_NCO_MODE_0 - TX Gaussian filter oversampling ratio and Byte 3 of unsigned 26-bit TX Numerically Controlled Oscillator (NCO) modulus. +// MODEM_FREQ_DEV_2 - 17-bit unsigned TX frequency deviation word. +// MODEM_FREQ_DEV_1 - 17-bit unsigned TX frequency deviation word. +*/ +#define RF_MODEM_MOD_TYPE_12 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x03, 0x0D, 0x40, 0x05, 0xC9, 0xC3, 0x80, 0x00, 0x01 + +/* +// Set properties: RF_MODEM_FREQ_DEV_0_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x0C +// Default values: 0xD3, +// Descriptions: +// MODEM_FREQ_DEV_0 - 17-bit unsigned TX frequency deviation word. +*/ +#define RF_MODEM_FREQ_DEV_0_1 0x11, 0x20, 0x01, 0x0C, 0x5E + +/* +// Set properties: RF_MODEM_TX_RAMP_DELAY_8 +// Number of properties: 8 +// Group ID: 0x20 +// Start ID: 0x18 +// Default values: 0x01, 0x00, 0x08, 0x03, 0xC0, 0x00, 0x10, 0x20, +// Descriptions: +// MODEM_TX_RAMP_DELAY - TX ramp-down delay setting. +// MODEM_MDM_CTRL - MDM control. +// MODEM_IF_CONTROL - Selects Fixed-IF, Scaled-IF, or Zero-IF mode of RX Modem operation. +// MODEM_IF_FREQ_2 - the IF frequency setting (an 18-bit signed number). +// MODEM_IF_FREQ_1 - the IF frequency setting (an 18-bit signed number). +// MODEM_IF_FREQ_0 - the IF frequency setting (an 18-bit signed number). +// MODEM_DECIMATION_CFG1 - Specifies three decimator ratios for the Cascaded Integrator Comb (CIC) filter. +// MODEM_DECIMATION_CFG0 - Specifies miscellaneous parameters and decimator ratios for the Cascaded Integrator Comb (CIC) filter. +*/ +#define RF_MODEM_TX_RAMP_DELAY_8 0x11, 0x20, 0x08, 0x18, 0x01, 0x80, 0x08, 0x03, 0xC0, 0x00, 0x20, 0x20 + +/* +// Set properties: RF_MODEM_BCR_OSR_1_9 +// Number of properties: 9 +// Group ID: 0x20 +// Start ID: 0x22 +// Default values: 0x00, 0x4B, 0x06, 0xD3, 0xA0, 0x06, 0xD3, 0x02, 0xC0, +// Descriptions: +// MODEM_BCR_OSR_1 - RX BCR/Slicer oversampling rate (12-bit unsigned number). +// MODEM_BCR_OSR_0 - RX BCR/Slicer oversampling rate (12-bit unsigned number). +// MODEM_BCR_NCO_OFFSET_2 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_NCO_OFFSET_1 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_NCO_OFFSET_0 - RX BCR NCO offset value (an unsigned 22-bit number). +// MODEM_BCR_GAIN_1 - The unsigned 11-bit RX BCR loop gain value. +// MODEM_BCR_GAIN_0 - The unsigned 11-bit RX BCR loop gain value. +// MODEM_BCR_GEAR - RX BCR loop gear control. +// MODEM_BCR_MISC1 - Miscellaneous control bits for the RX BCR loop. +*/ +#define RF_MODEM_BCR_OSR_1_9 0x11, 0x20, 0x09, 0x22, 0x02, 0xEE, 0x00, 0xAE, 0xC3, 0x00, 0x57, 0x02, 0xC2 + +/* +// Set properties: RF_MODEM_AFC_GEAR_7 +// Number of properties: 7 +// Group ID: 0x20 +// Start ID: 0x2C +// Default values: 0x00, 0x23, 0x83, 0x69, 0x00, 0x40, 0xA0, +// Descriptions: +// MODEM_AFC_GEAR - RX AFC loop gear control. +// MODEM_AFC_WAIT - RX AFC loop wait time control. +// MODEM_AFC_GAIN_1 - Sets the gain of the PLL-based AFC acquisition loop, and provides miscellaneous control bits for AFC functionality. +// MODEM_AFC_GAIN_0 - Sets the gain of the PLL-based AFC acquisition loop, and provides miscellaneous control bits for AFC functionality. +// MODEM_AFC_LIMITER_1 - Set the AFC limiter value. +// MODEM_AFC_LIMITER_0 - Set the AFC limiter value. +// MODEM_AFC_MISC - Specifies miscellaneous AFC control bits. +*/ +#define RF_MODEM_AFC_GEAR_7 0x11, 0x20, 0x07, 0x2C, 0x04, 0x36, 0x80, 0x07, 0x29, 0x3F, 0x80 + +/* +// Set properties: RF_MODEM_AGC_CONTROL_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x35 +// Default values: 0xE0, +// Descriptions: +// MODEM_AGC_CONTROL - Miscellaneous control bits for the Automatic Gain Control (AGC) function in the RX Chain. +*/ +#define RF_MODEM_AGC_CONTROL_1 0x11, 0x20, 0x01, 0x35, 0xE2 + +/* +// Set properties: RF_MODEM_AGC_WINDOW_SIZE_9 +// Number of properties: 9 +// Group ID: 0x20 +// Start ID: 0x38 +// Default values: 0x11, 0x10, 0x10, 0x0B, 0x1C, 0x40, 0x00, 0x00, 0x2B, +// Descriptions: +// MODEM_AGC_WINDOW_SIZE - Specifies the size of the measurement and settling windows for the AGC algorithm. +// MODEM_AGC_RFPD_DECAY - Sets the decay time of the RF peak detectors. +// MODEM_AGC_IFPD_DECAY - Sets the decay time of the IF peak detectors. +// MODEM_FSK4_GAIN1 - Specifies the gain factor of the secondary branch in 4(G)FSK ISI-suppression. +// MODEM_FSK4_GAIN0 - Specifies the gain factor of the primary branch in 4(G)FSK ISI-suppression. +// MODEM_FSK4_TH1 - 16 bit 4(G)FSK slicer threshold. +// MODEM_FSK4_TH0 - 16 bit 4(G)FSK slicer threshold. +// MODEM_FSK4_MAP - 4(G)FSK symbol mapping code. +// MODEM_OOK_PDTC - Configures the attack and decay times of the OOK Peak Detector. +*/ +#define RF_MODEM_AGC_WINDOW_SIZE_9 0x11, 0x20, 0x09, 0x38, 0x11, 0xA4, 0xA4, 0x00, 0x1A, 0xFF, 0xFF, 0x00, 0x2B + +/* +// Set properties: RF_MODEM_OOK_CNT1_8 +// Number of properties: 8 +// Group ID: 0x20 +// Start ID: 0x42 +// Default values: 0xA4, 0x03, 0x56, 0x02, 0x00, 0xA3, 0x02, 0x80, +// Descriptions: +// MODEM_OOK_CNT1 - OOK control. +// MODEM_OOK_MISC - Selects the detector(s) used for demodulation of an OOK signal, or for demodulation of a (G)FSK signal when using the asynchronous demodulator. +// MODEM_RAW_SEARCH - Defines and controls the search period length for the Moving Average and Min-Max detectors. +// MODEM_RAW_CONTROL - Defines gain and enable controls for raw / nonstandard mode. +// MODEM_RAW_EYE_1 - 11 bit eye-open detector threshold. +// MODEM_RAW_EYE_0 - 11 bit eye-open detector threshold. +// MODEM_ANT_DIV_MODE - Antenna diversity mode settings. +// MODEM_ANT_DIV_CONTROL - Specifies controls for the Antenna Diversity algorithm. +*/ +#define RF_MODEM_OOK_CNT1_8 0x11, 0x20, 0x08, 0x42, 0xA4, 0x02, 0xD6, 0x83, 0x00, 0x57, 0x01, 0x80 + +/* +// Set properties: RF_MODEM_RSSI_COMP_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x4E +// Default values: 0x32, +// Descriptions: +// MODEM_RSSI_COMP - RSSI compensation value. +*/ +#define RF_MODEM_RSSI_COMP_1 0x11, 0x20, 0x01, 0x4E, 0x40 + +/* +// Set properties: RF_MODEM_CLKGEN_BAND_1 +// Number of properties: 1 +// Group ID: 0x20 +// Start ID: 0x51 +// Default values: 0x08, +// Descriptions: +// MODEM_CLKGEN_BAND - Select PLL Synthesizer output divider ratio as a function of frequency band. +*/ +#define RF_MODEM_CLKGEN_BAND_1 0x11, 0x20, 0x01, 0x51, 0x08 + +/* +// Set properties: RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x00 +// Default values: 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01, +// Descriptions: +// MODEM_CHFLT_RX1_CHFLT_COE13_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE12_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE11_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE10_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE9_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE8_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE7_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE6_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE5_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE4_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE3_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE2_7_0 - Filter coefficients for the first set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12 0x11, 0x21, 0x0C, 0x00, 0xFF, 0xC4, 0x30, 0x7F, 0xF5, 0xB5, 0xB8, 0xDE, 0x05, 0x17, 0x16, 0x0C + +/* +// Set properties: RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x0C +// Default values: 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F, 0xFF, 0xC4, 0x30, 0x7F, 0xF5, 0xB5, +// Descriptions: +// MODEM_CHFLT_RX1_CHFLT_COE1_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COE0_7_0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM0 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM1 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM2 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX1_CHFLT_COEM3 - Filter coefficients for the first set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE13_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE12_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE11_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE10_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE9_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE8_7_0 - Filter coefficients for the second set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12 0x11, 0x21, 0x0C, 0x0C, 0x03, 0x00, 0x15, 0xFF, 0x00, 0x00, 0xFF, 0xC4, 0x30, 0x7F, 0xF5, 0xB5 + +/* +// Set properties: RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12 +// Number of properties: 12 +// Group ID: 0x21 +// Start ID: 0x18 +// Default values: 0xB8, 0xDE, 0x05, 0x17, 0x16, 0x0C, 0x03, 0x00, 0x15, 0xFF, 0x00, 0x00, +// Descriptions: +// MODEM_CHFLT_RX2_CHFLT_COE7_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE6_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE5_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE4_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE3_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE2_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE1_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COE0_7_0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM0 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM1 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM2 - Filter coefficients for the second set of RX filter coefficients. +// MODEM_CHFLT_RX2_CHFLT_COEM3 - Filter coefficients for the second set of RX filter coefficients. +*/ +#define RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12 0x11, 0x21, 0x0C, 0x18, 0xB8, 0xDE, 0x05, 0x17, 0x16, 0x0C, 0x03, 0x00, 0x15, 0xFF, 0x00, 0x00 + +/* +// Set properties: RF_PA_MODE_4 +// Number of properties: 4 +// Group ID: 0x22 +// Start ID: 0x00 +// Default values: 0x08, 0x7F, 0x00, 0x5D, +// Descriptions: +// PA_MODE - Selects the PA operating mode, and selects resolution of PA power adjustment (i.e., step size). +// PA_PWR_LVL - Configuration of PA output power level. +// PA_BIAS_CLKDUTY - Configuration of the PA Bias and duty cycle of the TX clock source. +// PA_TC - Configuration of PA ramping parameters. +*/ +#define RF_PA_MODE_4 0x11, 0x22, 0x04, 0x00, 0x08, 0x7F, 0x00, 0x3D + +/* +// Set properties: RF_SYNTH_PFDCP_CPFF_7 +// Number of properties: 7 +// Group ID: 0x23 +// Start ID: 0x00 +// Default values: 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03, +// Descriptions: +// SYNTH_PFDCP_CPFF - Feed forward charge pump current selection. +// SYNTH_PFDCP_CPINT - Integration charge pump current selection. +// SYNTH_VCO_KV - Gain scaling factors (Kv) for the VCO tuning varactors on both the integrated-path and feed forward path. +// SYNTH_LPFILT3 - Value of resistor R2 in feed-forward path of loop filter. +// SYNTH_LPFILT2 - Value of capacitor C2 in feed-forward path of loop filter. +// SYNTH_LPFILT1 - Value of capacitors C1 and C3 in feed-forward path of loop filter. +// SYNTH_LPFILT0 - Bias current of the active amplifier in the feed-forward loop filter. +*/ +#define RF_SYNTH_PFDCP_CPFF_7 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 + +/* +// Set properties: RF_FREQ_CONTROL_INTE_8 +// Number of properties: 8 +// Group ID: 0x40 +// Start ID: 0x00 +// Default values: 0x3C, 0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, +// Descriptions: +// FREQ_CONTROL_INTE - Frac-N PLL Synthesizer integer divide number. +// FREQ_CONTROL_FRAC_2 - Frac-N PLL fraction number. +// FREQ_CONTROL_FRAC_1 - Frac-N PLL fraction number. +// FREQ_CONTROL_FRAC_0 - Frac-N PLL fraction number. +// FREQ_CONTROL_CHANNEL_STEP_SIZE_1 - EZ Frequency Programming channel step size. +// FREQ_CONTROL_CHANNEL_STEP_SIZE_0 - EZ Frequency Programming channel step size. +// FREQ_CONTROL_W_SIZE - Set window gating period (in number of crystal reference clock cycles) for counting VCO frequency during calibration. +// FREQ_CONTROL_VCOCNT_RX_ADJ - Adjust target count for VCO calibration in RX mode. +*/ +#define RF_FREQ_CONTROL_INTE_8 0x11, 0x40, 0x08, 0x00, 0x3C, 0x08, 0x00, 0x00, 0x22, 0x22, 0x20, 0xFF + + +// AUTOMATICALLY GENERATED CODE! +// DO NOT EDIT/MODIFY BELOW THIS LINE! +// -------------------------------------------- + +#ifndef FIRMWARE_LOAD_COMPILE +#define RADIO_CONFIGURATION_DATA_ARRAY { \ + 0x07, RF_POWER_UP, \ + 0x08, RF_GPIO_PIN_CFG, \ + 0x06, RF_GLOBAL_XO_TUNE_2, \ + 0x05, RF_GLOBAL_CONFIG_1, \ + 0x06, RF_INT_CTL_ENABLE_2, \ + 0x08, RF_FRR_CTL_A_MODE_4, \ + 0x05, RF_PREAMBLE_TX_LENGTH_1, \ + 0x05, RF_PREAMBLE_CONFIG_1, \ + 0x07, RF_SYNC_CONFIG_3, \ + 0x05, RF_PKT_CONFIG1_1, \ + 0x05, RF_PKT_FIELD_1_CONFIG_1, \ + 0x10, RF_MODEM_MOD_TYPE_12, \ + 0x05, RF_MODEM_FREQ_DEV_0_1, \ + 0x0C, RF_MODEM_TX_RAMP_DELAY_8, \ + 0x0D, RF_MODEM_BCR_OSR_1_9, \ + 0x0B, RF_MODEM_AFC_GEAR_7, \ + 0x05, RF_MODEM_AGC_CONTROL_1, \ + 0x0D, RF_MODEM_AGC_WINDOW_SIZE_9, \ + 0x0C, RF_MODEM_OOK_CNT1_8, \ + 0x05, RF_MODEM_RSSI_COMP_1, \ + 0x05, RF_MODEM_CLKGEN_BAND_1, \ + 0x10, RF_MODEM_CHFLT_RX1_CHFLT_COE13_7_0_12, \ + 0x10, RF_MODEM_CHFLT_RX1_CHFLT_COE1_7_0_12, \ + 0x10, RF_MODEM_CHFLT_RX2_CHFLT_COE7_7_0_12, \ + 0x08, RF_PA_MODE_4, \ + 0x0B, RF_SYNTH_PFDCP_CPFF_7, \ + 0x0C, RF_FREQ_CONTROL_INTE_8, \ + 0x00 \ + } +#else +#define RADIO_CONFIGURATION_DATA_ARRAY { 0 } +#endif + +// DEFAULT VALUES FOR CONFIGURATION PARAMETERS +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ_DEFAULT 30000000L +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER_DEFAULT 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH_DEFAULT 0x10 +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP_DEFAULT 0x01 +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET_DEFAULT 0x1000 + +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH_INCLUDED 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH_SIZE 0x00 +#define RADIO_CONFIGURATION_DATA_RADIO_PATCH { } + +#ifndef RADIO_CONFIGURATION_DATA_ARRAY +#error "This property must be defined!" +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ +#define RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER +#define RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH +#define RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP +#define RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP_DEFAULT +#endif + +#ifndef RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET +#define RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET_DEFAULT +#endif + +#define RADIO_CONFIGURATION_DATA { \ + Radio_Configuration_Data_Array, \ + RADIO_CONFIGURATION_DATA_CHANNEL_NUMBER, \ + RADIO_CONFIGURATION_DATA_RADIO_PACKET_LENGTH, \ + RADIO_CONFIGURATION_DATA_RADIO_STATE_AFTER_POWER_UP, \ + RADIO_CONFIGURATION_DATA_RADIO_DELAY_CNT_AFTER_RESET \ + } + +#endif /* RADIO_CONFIG_H_ */ diff --git a/RHCRC.cpp b/RHCRC.cpp new file mode 100644 index 0000000..b636bab --- /dev/null +++ b/RHCRC.cpp @@ -0,0 +1,104 @@ +/* Copyright (c) 2002, 2003, 2004 Marek Michalkiewicz + Copyright (c) 2005, 2007 Joerg Wunsch + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of the copyright holders nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. */ + +// Port to Energia / MPS430 by Yannick DEVOS XV4Y - (c) 2013 +// http://xv4y.radioclub.asia/ +// + +// Adapted to RadioHead use by Mike McCauley 2014 +// This is to prevent name collisions with other similar library functions +// and to provide a consistent API amonng all processors +// + +/* $Id: RHCRC.cpp,v 1.1 2014/06/24 02:40:12 mikem Exp $ */ + +#include + +#define lo8(x) ((x)&0xff) +#define hi8(x) ((x)>>8) + +uint16_t RHcrc16_update(uint16_t crc, uint8_t a) +{ + int i; + + crc ^= a; + for (i = 0; i < 8; ++i) + { + if (crc & 1) + crc = (crc >> 1) ^ 0xA001; + else + crc = (crc >> 1); + } + return crc; +} + +uint16_t RHcrc_xmodem_update (uint16_t crc, uint8_t data) +{ + int i; + + crc = crc ^ ((uint16_t)data << 8); + for (i=0; i<8; i++) + { + if (crc & 0x8000) + crc = (crc << 1) ^ 0x1021; + else + crc <<= 1; + } + + return crc; +} + +uint16_t RHcrc_ccitt_update (uint16_t crc, uint8_t data) +{ + data ^= lo8 (crc); + data ^= data << 4; + + return ((((uint16_t)data << 8) | hi8 (crc)) ^ (uint8_t)(data >> 4) + ^ ((uint16_t)data << 3)); +} + +uint8_t RHcrc_ibutton_update(uint8_t crc, uint8_t data) +{ + uint8_t i; + + crc = crc ^ data; + for (i = 0; i < 8; i++) + { + if (crc & 0x01) + crc = (crc >> 1) ^ 0x8C; + else + crc >>= 1; + } + + return crc; +} + + diff --git a/RHCRC.h b/RHCRC.h new file mode 100644 index 0000000..42aeb4d --- /dev/null +++ b/RHCRC.h @@ -0,0 +1,19 @@ +// RHCRC.h +// +// Definitions for RadioHead compatible CRC outines. +// +// These routines originally derived from Arduino source code. See RHCRC.cpp +// for copyright information +// $Id: RHCRC.h,v 1.1 2014/06/24 02:40:12 mikem Exp $ + +#ifndef RHCRC_h +#define RHCRC_h + +#include + +extern uint16_t RHcrc16_update(uint16_t crc, uint8_t a); +extern uint16_t RHcrc_xmodem_update (uint16_t crc, uint8_t data); +extern uint16_t RHcrc_ccitt_update (uint16_t crc, uint8_t data); +extern uint8_t RHcrc_ibutton_update(uint8_t crc, uint8_t data); + +#endif diff --git a/RHDatagram.cpp b/RHDatagram.cpp new file mode 100644 index 0000000..cf70177 --- /dev/null +++ b/RHDatagram.cpp @@ -0,0 +1,123 @@ +// RHDatagram.cpp +// +// Copyright (C) 2011 Mike McCauley +// $Id: RHDatagram.cpp,v 1.6 2014/05/23 02:20:17 mikem Exp $ + +#include + +RHDatagram::RHDatagram(RHGenericDriver& driver, uint8_t thisAddress) + : + _driver(driver), + _thisAddress(thisAddress) +{ +} + +//////////////////////////////////////////////////////////////////// +// Public methods +bool RHDatagram::init() +{ + bool ret = _driver.init(); + if (ret) + setThisAddress(_thisAddress); + return ret; +} + +void RHDatagram::setThisAddress(uint8_t thisAddress) +{ + _driver.setThisAddress(thisAddress); + // Use this address in the transmitted FROM header + setHeaderFrom(thisAddress); + _thisAddress = thisAddress; +} + +bool RHDatagram::sendto(uint8_t* buf, uint8_t len, uint8_t address) +{ + setHeaderTo(address); + return _driver.send(buf, len); +} + +bool RHDatagram::recvfrom(uint8_t* buf, uint8_t* len, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags) +{ + if (_driver.recv(buf, len)) + { + if (from) *from = headerFrom(); + if (to) *to = headerTo(); + if (id) *id = headerId(); + if (flags) *flags = headerFlags(); + return true; + } + return false; +} + +bool RHDatagram::available() +{ + return _driver.available(); +} + +void RHDatagram::waitAvailable(uint16_t polldelay) +{ + _driver.waitAvailable(polldelay); +} + +bool RHDatagram::waitPacketSent() +{ + return _driver.waitPacketSent(); +} + +bool RHDatagram::waitPacketSent(uint16_t timeout) +{ + return _driver.waitPacketSent(timeout); +} + +bool RHDatagram::waitAvailableTimeout(uint16_t timeout, uint16_t polldelay) +{ + return _driver.waitAvailableTimeout(timeout, polldelay); +} + +uint8_t RHDatagram::thisAddress() +{ + return _thisAddress; +} + +void RHDatagram::setHeaderTo(uint8_t to) +{ + _driver.setHeaderTo(to); +} + +void RHDatagram::setHeaderFrom(uint8_t from) +{ + _driver.setHeaderFrom(from); +} + +void RHDatagram::setHeaderId(uint8_t id) +{ + _driver.setHeaderId(id); +} + +void RHDatagram::setHeaderFlags(uint8_t set, uint8_t clear) +{ + _driver.setHeaderFlags(set, clear); +} + +uint8_t RHDatagram::headerTo() +{ + return _driver.headerTo(); +} + +uint8_t RHDatagram::headerFrom() +{ + return _driver.headerFrom(); +} + +uint8_t RHDatagram::headerId() +{ + return _driver.headerId(); +} + +uint8_t RHDatagram::headerFlags() +{ + return _driver.headerFlags(); +} + + + diff --git a/RHDatagram.h b/RHDatagram.h new file mode 100644 index 0000000..f01f1cc --- /dev/null +++ b/RHDatagram.h @@ -0,0 +1,168 @@ +// RHDatagram.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHDatagram.h,v 1.14 2015/08/12 23:18:51 mikem Exp $ + +#ifndef RHDatagram_h +#define RHDatagram_h + +#include + +// This is the maximum possible message size for radios supported by RadioHead. +// Not all radios support this length, and many are much smaller +#define RH_MAX_MESSAGE_LEN 255 + +///////////////////////////////////////////////////////////////////// +/// \class RHDatagram RHDatagram.h +/// \brief Manager class for addressed, unreliable messages +/// +/// Every RHDatagram node has an 8 bit address (defaults to 0). +/// Addresses (DEST and SRC) are 8 bit integers with an address of RH_BROADCAST_ADDRESS (0xff) +/// reserved for broadcast. +/// +/// \par Media Access Strategy +/// +/// RHDatagram and the underlying drivers always transmit as soon as sendto() is called. +/// +/// \par Message Lengths +/// +/// Not all Radio drivers supported by RadioHead can handle the same message lengths. Some radios can handle +/// up to 255 octets, and some as few as 28. If you attempt to send a message that is too long for +/// the underlying driver, sendTo() will return false and will not transmit the message. +/// It is the programmers responsibility to make +/// sure that messages passed to sendto() do not exceed the capability of the radio. You can use the +/// *_MAX_MESSAGE_LENGTH definitions or driver->maxMessageLength() to help. +/// +/// \par Headers +/// +/// Each message sent and received by a RadioHead driver includes 4 headers:
+/// \b TO The node address that the message is being sent to (broadcast RH_BROADCAST_ADDRESS (255) is permitted)
+/// \b FROM The node address of the sending node
+/// \b ID A message ID, distinct (over short time scales) for each message sent by a particilar node
+/// \b FLAGS A bitmask of flags. The most significant 4 bits are reserved for use by RadioHead. The least +/// significant 4 bits are reserved for applications.
+/// +class RHDatagram +{ +public: + /// Constructor. + /// \param[in] driver The RadioHead driver to use to transport messages. + /// \param[in] thisAddress The address to assign to this node. Defaults to 0 + RHDatagram(RHGenericDriver& driver, uint8_t thisAddress = 0); + + /// Initialise this instance and the + /// driver connected to it. + bool init(); + + /// Sets the address of this node. Defaults to 0. + /// This will be used to set the FROM address of all messages sent by this node. + /// In a conventional multinode system, all nodes will have a unique address + /// (which you could store in EEPROM). + /// \param[in] thisAddress The address of this node + void setThisAddress(uint8_t thisAddress); + + /// Sends a message to the node(s) with the given address + /// RH_BROADCAST_ADDRESS is a valid address which will cause the message + /// to be accepted by all RHDatagram nodes within range. + /// \param[in] buf Pointer to the binary message to send + /// \param[in] len Number of octets to send (> 0) + /// \param[in] address The address to send the message to. + /// \return true if the message not too loing fot eh driver, and the message was transmitted. + bool sendto(uint8_t* buf, uint8_t len, uint8_t address); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available for this node, copy it to buf and return true + /// The SRC address is placed in *from if present and not NULL. + /// The DEST address is placed in *to if present and not NULL. + /// If a message is copied, *len is set to the length. + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \param[in] from If present and not NULL, the referenced uint8_t will be set to the FROM address + /// \param[in] to If present and not NULL, the referenced uint8_t will be set to the TO address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// (not just those addressed to this node). + /// \return true if a valid message was copied to buf + bool recvfrom(uint8_t* buf, uint8_t* len, uint8_t* from = NULL, uint8_t* to = NULL, uint8_t* id = NULL, uint8_t* flags = NULL); + + /// Tests whether a new message is available + /// from the Driver. + /// On most drivers, this will also put the Driver into RHModeRx mode until + /// a message is actually received bythe transport, when it will be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop. + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + bool available(); + + /// Starts the Driver receiver and blocks until a valid received + /// message is available. + /// \param[in] polldelay Time between polling available() in milliseconds. This can be useful + /// in multitaking environment like Linux to prevent waitAvailableTimeout + /// using all the CPU while polling for receiver activity + void waitAvailable(uint16_t polldelay = 0); + + /// Blocks until the transmitter + /// is no longer transmitting. + bool waitPacketSent(); + + /// Blocks until the transmitter is no longer transmitting. + /// or until the timeout occuers, whichever happens first + /// \param[in] timeout Maximum time to wait in milliseconds. + /// \return true if the radio completed transmission within the timeout period. False if it timed out. + bool waitPacketSent(uint16_t timeout); + + /// Starts the Driver receiver and blocks until a received message is available or a timeout + /// \param[in] timeout Maximum time to wait in milliseconds. + /// \param[in] polldelay Time between polling available() in milliseconds. This can be useful + /// in multitaking environment like Linux to prevent waitAvailableTimeout + /// using all the CPU while polling for receiver activity + /// \return true if a message is available + bool waitAvailableTimeout(uint16_t timeout, uint16_t polldelay = 0); + + /// Sets the TO header to be sent in all subsequent messages + /// \param[in] to The new TO header value + void setHeaderTo(uint8_t to); + + /// Sets the FROM header to be sent in all subsequent messages + /// \param[in] from The new FROM header value + void setHeaderFrom(uint8_t from); + + /// Sets the ID header to be sent in all subsequent messages + /// \param[in] id The new ID header value + void setHeaderId(uint8_t id); + + /// Sets and clears bits in the FLAGS header to be sent in all subsequent messages + /// \param[in] set bitmask of bits to be set + /// \param[in] clear bitmask of flags to clear + void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_NONE); + + /// Returns the TO header of the last received message + /// \return The TO header of the most recently received message. + uint8_t headerTo(); + + /// Returns the FROM header of the last received message + /// \return The FROM header of the most recently received message. + uint8_t headerFrom(); + + /// Returns the ID header of the last received message + /// \return The ID header of the most recently received message. + uint8_t headerId(); + + /// Returns the FLAGS header of the last received message + /// \return The FLAGS header of the most recently received message. + uint8_t headerFlags(); + + /// Returns the address of this node. + /// \return The address of this node + uint8_t thisAddress(); + +protected: + /// The Driver we are to use + RHGenericDriver& _driver; + + /// The address of this node + uint8_t _thisAddress; +}; + +#endif diff --git a/RHEncryptedDriver.cpp b/RHEncryptedDriver.cpp new file mode 100644 index 0000000..53987d1 --- /dev/null +++ b/RHEncryptedDriver.cpp @@ -0,0 +1,156 @@ +// RHEncryptedDriver.cpp +// +// Author: Philippe.Rochat'at'gmail.com +// Contributed to the RadioHead project by the author +// $Id: RHEncryptedDriver.cpp,v 1.7 2020/08/04 09:02:14 mikem Exp $ + +#include +#ifdef RH_ENABLE_ENCRYPTION_MODULE +#include + +RHEncryptedDriver::RHEncryptedDriver(RHGenericDriver& driver, BlockCipher& blockcipher) + : _driver(driver), + _blockcipher(blockcipher) +{ + _buffer = (uint8_t *)calloc(_driver.maxMessageLength(), sizeof(uint8_t)); +} + +bool RHEncryptedDriver::recv(uint8_t* buf, uint8_t* len) +{ + int h = 0; // Index of output _buffer + + bool status = _driver.recv(_buffer, len); + if (status && buf && len) + { + int blockSize = _blockcipher.blockSize(); // Size of blocks used by encryption + int nbBlocks = *len / blockSize; // Number of blocks in that message + if (nbBlocks * blockSize == *len) + { + // Or we have a missmatch ... this is probably not symetrically encrypted + for (int k = 0; k < nbBlocks; k++) + { + // Decrypt each block + _blockcipher.decryptBlock(&buf[h], &_buffer[k*blockSize]); // Decrypt that block into buf + h += blockSize; +#ifdef STRICT_CONTENT_LEN + if (k == 0) + { +// if (buf[0] > *len - 1) +// return false; // Bogus payload length + *len = buf[0]; // First byte contains length + h--; // First block is of length-- + memmove(buf, buf+1, blockSize - 1); + } +#endif + } + } + } + + return status; +} + +bool RHEncryptedDriver::send(const uint8_t* data, uint8_t len) +{ + if (len > maxMessageLength()) + return false; + + bool status = true; + int blockSize = _blockcipher.blockSize(); // Size of blocks used by encryption + + if (len == 0) // PassThru + return _driver.send(data, len); + + if (_cipheringBlocks.blockSize != blockSize) + { + // Cipher has changed it's block size + _cipheringBlocks.inputBlock = (uint8_t *)realloc(_cipheringBlocks.inputBlock, blockSize); + _cipheringBlocks.blockSize = blockSize; + } + + int max_message_length = maxMessageLength(); +#ifdef STRICT_CONTENT_LEN + uint8_t nbBlocks = len / blockSize + 1; // How many blocks do we need for that message + uint8_t nbBpM = (max_message_length + 1) / blockSize; // Max number of blocks per message +#else + uint8_t nbBlocks = (len - 1) / blockSize + 1; // How many blocks do we need for that message + uint8_t nbBpM = max_message_length / blockSize; // Max number of blocks per message +#endif + int k = 0, j = 0; // k is block index, j is original message index +#ifndef ALLOW_MULTIPLE_MSG +#ifdef STRICT_CONTENT_LEN + for (k = 0; k < nbBpM && k * blockSize < len + 1; k++) +#else + for (k = 0; k < nbBpM && k * blockSize < len; k++) +#endif + { + // k blocks in that message + int h = 0; // h is block content index +#ifdef STRICT_CONTENT_LEN + if (k == 0) + _cipheringBlocks.inputBlock[h++] = len; // put in first byte of first block the message length +#endif + while (h < blockSize) + { + // Copy each msg byte into inputBlock, and trail with 0 if necessary + if (j < len) + _cipheringBlocks.inputBlock[h++] = data[j++]; + else + _cipheringBlocks.inputBlock[h++] = 0; // Completing with trailing 0 + } + _blockcipher.encryptBlock(&_buffer[k * blockSize], _cipheringBlocks.inputBlock); // Cipher that message into _buffer + } +// Serial.println(max_message_length); +// Serial.println(nbBlocks); +// Serial.println(nbBpM); +// Serial.println(k); +// Serial.println(blockSize); +// printBuffer("single send", _buffer, k * blockSize); + if (!_driver.send(_buffer, k*blockSize)) // We now send that message with it's new length + status = false; +#else + uint8_t nbMsg = (nbBlocks * blockSize) / max_message_length + 1; // How many message do we need + + for (int i = 0; i < nbMsg; i++) + { + // i is message index + for (k = 0; k < nbBpM && k * blockSize < len ; k++) + { + // k blocks in that message + int h = 0; +#ifdef STRICT_CONTENT_LEN + if (k == 0 && i == 0) + _cipheringBlocks.inputBlock[h++] = len; // put in first byte of first block of first message the message length +#endif + while (h < blockSize) + { + // Copy each msg byte into inputBlock, and trail with 0 if necessary + if (j < len) + _cipheringBlocks.inputBlock[h++] = data[j++]; + else + _cipheringBlocks.inputBlock[h++] = 0; + } + _blockcipher.encryptBlock(&_buffer[k * blockSize], _cipheringBlocks.inputBlock); // Cipher that message into buffer + } +// printBuffer("multiple send", _buffer, k * blockSize); + if (!_driver.send(_buffer, k * blockSize)) // We now send that message with it's new length + status = false; + } +#endif + return status; +} + +uint8_t RHEncryptedDriver::maxMessageLength() +{ + int driver_len = _driver.maxMessageLength(); + +#ifndef ALLOW_MULTIPLE_MSG + driver_len = ((int)(driver_len/_blockcipher.blockSize()) ) * _blockcipher.blockSize(); +#endif + +#ifdef STRICT_CONTENT_LEN + driver_len--; +#endif + return driver_len; +} + +#endif diff --git a/RHEncryptedDriver.h b/RHEncryptedDriver.h new file mode 100644 index 0000000..d95dc76 --- /dev/null +++ b/RHEncryptedDriver.h @@ -0,0 +1,251 @@ +// RHEncryptedDriver.h + +// Generic encryption layer that could use any driver +// But will encrypt all data. +// Requires the Arduinolibs/Crypto library: +// https://github.com/rweather/arduinolibs +// +// Author: Philippe.Rochat'at'gmail.com +// Contributed to the RadioHead project by the author +// $Id: RHEncryptedDriver.h,v 1.4 2020/07/05 08:52:21 mikem Exp $ + +#ifndef RHEncryptedDriver_h +#define RHEncryptedDriver_h + +#include +#if defined(RH_ENABLE_ENCRYPTION_MODULE) || defined(DOXYGEN) +#include + +// Undef this if trailing 0 on each enrypted message is ok. +// This defined means a first byte of the payload is used to encode content length +// And the received message content is trimmed to this length +#define STRICT_CONTENT_LEN + +// Define this to allow encrypted content to span over 2 messages +// STRICT_CONTENT_LEN and ALLOW_MULTIPLE_MSG aren't compatible !!! +// With STRICT_CONTENT_LEN, receiver will try to extract length from every message !!!! +//#define ALLOW_MULTIPLE_MSG + +///////////////////////////////////////////////////////////////////// +/// \class RHEncryptedDriver RHEncryptedDriver +/// \brief Virtual Driver to encrypt/decrypt data. Can be used with any other RadioHead driver. +/// +/// This driver acts as a wrapper for any other RadioHead driver, adding encryption and decryption of +/// messages that are passed to and from the actual radio driver. Only the message payload is encrypted, +/// and not the to/from address or flags. Any of the encryption ciphers supported by +/// ArduinoLibs Cryptographic Library http://rweather.github.io/arduinolibs/crypto.html may be used. +/// +/// For successful communications, both sender and receiver must use the same cipher and the same key. +/// +/// In order to enable this module you must uncomment #define RH_ENABLE_ENCRYPTION_MODULE at the bottom of RadioHead.h +/// But ensure you have installed the Crypto directory from arduinolibs first: +/// http://rweather.github.io/arduinolibs/index.html + +class RHEncryptedDriver : public RHGenericDriver +{ +public: + /// Constructor. + /// Adds a ciphering layer to messages sent and received by the actual transport driver. + /// \param[in] driver The RadioHead driver to use to transport messages. + /// \param[in] blockcipher The blockcipher (from arduinolibs) that crypt/decrypt data. Ensure that + /// the blockcipher has had its key set before sending or receiving messages. + RHEncryptedDriver(RHGenericDriver& driver, BlockCipher& blockcipher); + + /// Calls the real driver's init() + /// \return The value returned from the driver init() method; + virtual bool init() { return _driver.init();}; + + /// Tests whether a new message is available + /// from the Driver. + /// On most drivers, this will also put the Driver into RHModeRx mode until + /// a message is actually received by the transport, when it wil be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available() { return _driver.available();}; + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied. + /// \return true if a valid message was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then optionally waits for Channel Activity Detection (CAD) + /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send + /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting. + /// \return true if the message length was valid and it was correctly queued for transmit. Return false + /// if CAD was requested and the CAD timeout timed out before clear channel was detected. + virtual bool send(const uint8_t* data, uint8_t len); + + /// Returns the maximum message length + /// available in this Driver, which depends on the maximum length supported by the underlying transport driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + /// Blocks until the transmitter + /// is no longer transmitting. + virtual bool waitPacketSent() { return _driver.waitPacketSent();} ; + + /// Blocks until the transmitter is no longer transmitting. + /// or until the timeout occuers, whichever happens first + /// \param[in] timeout Maximum time to wait in milliseconds. + /// \return true if the radio completed transmission within the timeout period. False if it timed out. + virtual bool waitPacketSent(uint16_t timeout) {return _driver.waitPacketSent(timeout);} ; + + /// Starts the receiver and blocks until a received message is available or a timeout + /// \param[in] timeout Maximum time to wait in milliseconds. + /// \return true if a message is available + virtual bool waitAvailableTimeout(uint16_t timeout) {return _driver.waitAvailableTimeout(timeout);}; + + /// Calls the waitCAD method in the driver + /// \return The return value from teh drivers waitCAD() method + virtual bool waitCAD() { return _driver.waitCAD();}; + + /// Sets the Channel Activity Detection timeout in milliseconds to be used by waitCAD(). + /// The default is 0, which means do not wait for CAD detection. + /// CAD detection depends on support for isChannelActive() by your particular radio. + void setCADTimeout(unsigned long cad_timeout) {_driver.setCADTimeout(cad_timeout);}; + + /// Determine if the currently selected radio channel is active. + /// This is expected to be subclassed by specific radios to implement their Channel Activity Detection + /// if supported. If the radio does not support CAD, returns true immediately. If a RadioHead radio + /// supports isChannelActive() it will be documented in the radio specific documentation. + /// This is called automatically by waitCAD(). + /// \return true if the radio-specific CAD (as returned by override of isChannelActive()) shows the + /// current radio channel as active, else false. If there is no radio-specific CAD, returns false. + virtual bool isChannelActive() { return _driver.isChannelActive();}; + + /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. + /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, + /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. + /// In promiscuous mode, all messages will be accepted regardless of the TO header. + /// In a conventional multinode system, all nodes will have a unique address + /// (which you could store in EEPROM). + /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, + /// allowing the possibilty of address spoofing). + /// \param[in] thisAddress The address of this node. + virtual void setThisAddress(uint8_t thisAddress) { _driver.setThisAddress(thisAddress);}; + + /// Sets the TO header to be sent in all subsequent messages + /// \param[in] to The new TO header value + virtual void setHeaderTo(uint8_t to){ _driver.setHeaderTo(to);}; + + /// Sets the FROM header to be sent in all subsequent messages + /// \param[in] from The new FROM header value + virtual void setHeaderFrom(uint8_t from){ _driver.setHeaderFrom(from);}; + + /// Sets the ID header to be sent in all subsequent messages + /// \param[in] id The new ID header value + virtual void setHeaderId(uint8_t id){ _driver.setHeaderId(id);}; + + /// Sets and clears bits in the FLAGS header to be sent in all subsequent messages + /// First it clears he FLAGS according to the clear argument, then sets the flags according to the + /// set argument. The default for clear always clears the application specific flags. + /// \param[in] set bitmask of bits to be set. Flags are cleared with the clear mask before being set. + /// \param[in] clear bitmask of flags to clear. Defaults to RH_FLAGS_APPLICATION_SPECIFIC + /// which clears the application specific flags, resulting in new application specific flags + /// identical to the set. + virtual void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_APPLICATION_SPECIFIC) { _driver.setHeaderFlags(set, clear);}; + + /// Tells the receiver to accept messages with any TO address, not just messages + /// addressed to thisAddress or the broadcast address + /// \param[in] promiscuous true if you wish to receive messages with any TO address + virtual void setPromiscuous(bool promiscuous){ _driver.setPromiscuous(promiscuous);}; + + /// Returns the TO header of the last received message + /// \return The TO header + virtual uint8_t headerTo() { return _driver.headerTo();}; + + /// Returns the FROM header of the last received message + /// \return The FROM header + virtual uint8_t headerFrom() { return _driver.headerFrom();}; + + /// Returns the ID header of the last received message + /// \return The ID header + virtual uint8_t headerId() { return _driver.headerId();}; + + /// Returns the FLAGS header of the last received message + /// \return The FLAGS header + virtual uint8_t headerFlags() { return _driver.headerFlags();}; + + /// Returns the most recent RSSI (Receiver Signal Strength Indicator). + /// Usually it is the RSSI of the last received message, which is measured when the preamble is received. + /// If you called readRssi() more recently, it will return that more recent value. + /// \return The most recent RSSI measurement in dBm. + int16_t lastRssi() { return _driver.lastRssi();}; + + /// Returns the operating mode of the library. + /// \return the current mode, one of RF69_MODE_* + RHMode mode() { return _driver.mode();}; + + /// Sets the operating mode of the transport. + void setMode(RHMode mode) { _driver.setMode(mode);}; + + /// Sets the transport hardware into low-power sleep mode + /// (if supported). May be overridden by specific drivers to initialte sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// \return true if sleep mode is supported by transport hardware and the RadioHead driver, and if sleep mode + /// was successfully entered. If sleep mode is not suported, return false. + virtual bool sleep() { return _driver.sleep();}; + + /// Returns the count of the number of bad received packets (ie packets with bad lengths, checksum etc) + /// which were rejected and not delivered to the application. + /// Caution: not all drivers can correctly report this count. Some underlying hardware only report + /// good packets. + /// \return The number of bad packets received. + virtual uint16_t rxBad() { return _driver.rxBad();}; + + /// Returns the count of the number of + /// good received packets + /// \return The number of good packets received. + virtual uint16_t rxGood() { return _driver.rxGood();}; + + /// Returns the count of the number of + /// packets successfully transmitted (though not necessarily received by the destination) + /// \return The number of packets successfully transmitted + virtual uint16_t txGood() { return _driver.txGood();}; + +private: + /// The underlying transport river we are to use + RHGenericDriver& _driver; + + /// The CipherBlock we are to use for encrypting/decrypting + BlockCipher& _blockcipher; + + /// Struct for with buffers for ciphering + typedef struct + { + size_t blockSize = 0; + uint8_t *inputBlock = NULL; + //uint8_t *outputBlock = NULL; + } CipherBlocks; + + CipherBlocks _cipheringBlocks; + + /// Buffer to store encrypted/decrypted message + uint8_t* _buffer; +}; + +/// @example nrf24_encrypted_client.ino +/// @example nrf24_encrypted_server.ino +/// @example rf95_encrypted_client.ino +/// @example rf95_encrypted_server.ino +/// @example serial_encrypted_reliable_datagram_client.ino +/// @example serial_encrypted_reliable_datagram_server.ino + + +#else // RH_ENABLE_ENCRYPTION_MODULE +#error "You have included RHEncryptedDriver.h, but not enabled RH_ENABLE_ENCRYPTION_MODULE in RadioHead.h" +#endif + +#endif diff --git a/RHGenericDriver.cpp b/RHGenericDriver.cpp new file mode 100644 index 0000000..213331c --- /dev/null +++ b/RHGenericDriver.cpp @@ -0,0 +1,227 @@ +// RHGenericDriver.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RHGenericDriver.cpp,v 1.24 2020/01/07 23:35:02 mikem Exp $ + +#include + +RHGenericDriver::RHGenericDriver() + : + _mode(RHModeInitialising), + _thisAddress(RH_BROADCAST_ADDRESS), + _txHeaderTo(RH_BROADCAST_ADDRESS), + _txHeaderFrom(RH_BROADCAST_ADDRESS), + _txHeaderId(0), + _txHeaderFlags(0), + _rxBad(0), + _rxGood(0), + _txGood(0), + _cad_timeout(0) +{ +} + +bool RHGenericDriver::init() +{ + return true; +} + +// Blocks until a valid message is received +void RHGenericDriver::waitAvailable(uint16_t polldelay) +{ + while (!available()) + { + YIELD; + if (polldelay) + delay(polldelay); + } +} + +// Blocks until a valid message is received or timeout expires +// Return true if there is a message available +// Works correctly even on millis() rollover +bool RHGenericDriver::waitAvailableTimeout(uint16_t timeout, uint16_t polldelay) +{ + unsigned long starttime = millis(); + while ((millis() - starttime) < timeout) + { + if (available()) + { + return true; + } + YIELD; + if (polldelay) + delay(polldelay); + } + return false; +} + +bool RHGenericDriver::waitPacketSent() +{ + while (_mode == RHModeTx) + YIELD; // Wait for any previous transmit to finish + return true; +} + +bool RHGenericDriver::waitPacketSent(uint16_t timeout) +{ + unsigned long starttime = millis(); + while ((millis() - starttime) < timeout) + { + if (_mode != RHModeTx) // Any previous transmit finished? + return true; + YIELD; + } + return false; +} + +// Wait until no channel activity detected or timeout +bool RHGenericDriver::waitCAD() +{ + if (!_cad_timeout) + return true; + + // Wait for any channel activity to finish or timeout + // Sophisticated DCF function... + // DCF : BackoffTime = random() x aSlotTime + // 100 - 1000 ms + // 10 sec timeout + unsigned long t = millis(); + while (isChannelActive()) + { + if (millis() - t > _cad_timeout) + return false; +#if (RH_PLATFORM == RH_PLATFORM_STM32) // stdlib on STMF103 gets confused if random is redefined + delay(_random(1, 10) * 100); +#else + delay(random(1, 10) * 100); // Should these values be configurable? Macros? +#endif + } + + return true; +} + +// subclasses are expected to override if CAD is available for that radio +bool RHGenericDriver::isChannelActive() +{ + return false; +} + +void RHGenericDriver::setPromiscuous(bool promiscuous) +{ + _promiscuous = promiscuous; +} + +void RHGenericDriver::setThisAddress(uint8_t address) +{ + _thisAddress = address; +} + +void RHGenericDriver::setHeaderTo(uint8_t to) +{ + _txHeaderTo = to; +} + +void RHGenericDriver::setHeaderFrom(uint8_t from) +{ + _txHeaderFrom = from; +} + +void RHGenericDriver::setHeaderId(uint8_t id) +{ + _txHeaderId = id; +} + +void RHGenericDriver::setHeaderFlags(uint8_t set, uint8_t clear) +{ + _txHeaderFlags &= ~clear; + _txHeaderFlags |= set; +} + +uint8_t RHGenericDriver::headerTo() +{ + return _rxHeaderTo; +} + +uint8_t RHGenericDriver::headerFrom() +{ + return _rxHeaderFrom; +} + +uint8_t RHGenericDriver::headerId() +{ + return _rxHeaderId; +} + +uint8_t RHGenericDriver::headerFlags() +{ + return _rxHeaderFlags; +} + +int16_t RHGenericDriver::lastRssi() +{ + return _lastRssi; +} + +RHGenericDriver::RHMode RHGenericDriver::mode() +{ + return _mode; +} + +void RHGenericDriver::setMode(RHMode mode) +{ + _mode = mode; +} + +bool RHGenericDriver::sleep() +{ + return false; +} + +// Diagnostic help +void RHGenericDriver::printBuffer(const char* prompt, const uint8_t* buf, uint8_t len) +{ +#ifdef RH_HAVE_SERIAL + Serial.println(prompt); + uint8_t i; + for (i = 0; i < len; i++) + { + if (i % 16 == 15) + Serial.println(buf[i], HEX); + else + { + Serial.print(buf[i], HEX); + Serial.print(' '); + } + } + Serial.println(""); +#endif +} + +uint16_t RHGenericDriver::rxBad() +{ + return _rxBad; +} + +uint16_t RHGenericDriver::rxGood() +{ + return _rxGood; +} + +uint16_t RHGenericDriver::txGood() +{ + return _txGood; +} + +void RHGenericDriver::setCADTimeout(unsigned long cad_timeout) +{ + _cad_timeout = cad_timeout; +} + +#if (RH_PLATFORM == RH_PLATFORM_ATTINY) +// Tinycore does not have __cxa_pure_virtual, so without this we +// get linking complaints from the default code generated for pure virtual functions +extern "C" void __cxa_pure_virtual() +{ + while (1); +} +#endif diff --git a/RHGenericDriver.h b/RHGenericDriver.h new file mode 100644 index 0000000..ac842ad --- /dev/null +++ b/RHGenericDriver.h @@ -0,0 +1,318 @@ +// RHGenericDriver.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RHGenericDriver.h,v 1.24 2020/04/09 23:40:34 mikem Exp $ + +#ifndef RHGenericDriver_h +#define RHGenericDriver_h + +#include + +// Defines bits of the FLAGS header reserved for use by the RadioHead library and +// the flags available for use by applications +#define RH_FLAGS_RESERVED 0xf0 +#define RH_FLAGS_APPLICATION_SPECIFIC 0x0f +#define RH_FLAGS_NONE 0 + +// Default timeout for waitCAD() in ms +#define RH_CAD_DEFAULT_TIMEOUT 10000 + +///////////////////////////////////////////////////////////////////// +/// \class RHGenericDriver RHGenericDriver.h +/// \brief Abstract base class for a RadioHead driver. +/// +/// This class defines the functions that must be provided by any RadioHead driver. +/// Different types of driver will implement all the abstract functions, and will perhaps override +/// other functions in this subclass, or perhaps add new functions specifically required by that driver. +/// Do not directly instantiate this class: it is only to be subclassed by driver classes. +/// +/// Subclasses are expected to implement a half-duplex, unreliable, error checked, unaddressed packet transport. +/// They are expected to carry a message payload with an appropriate maximum length for the transport hardware +/// and to also carry unaltered 4 message headers: TO, FROM, ID, FLAGS +/// +/// \par Headers +/// +/// Each message sent and received by a RadioHead driver includes 4 headers: +/// -TO The node address that the message is being sent to (broadcast RH_BROADCAST_ADDRESS (255) is permitted) +/// -FROM The node address of the sending node +/// -ID A message ID, distinct (over short time scales) for each message sent by a particilar node +/// -FLAGS A bitmask of flags. The most significant 4 bits are reserved for use by RadioHead. The least +/// significant 4 bits are reserved for applications. +class RHGenericDriver +{ +public: + /// \brief Defines different operating modes for the transport hardware + /// + /// These are the different values that can be adopted by the _mode variable and + /// returned by the mode() member function, + typedef enum + { + RHModeInitialising = 0, ///< Transport is initialising. Initial default value until init() is called.. + RHModeSleep, ///< Transport hardware is in low power sleep mode (if supported) + RHModeIdle, ///< Transport is idle. + RHModeTx, ///< Transport is in the process of transmitting a message. + RHModeRx, ///< Transport is in the process of receiving a message. + RHModeCad ///< Transport is in the process of detecting channel activity (if supported) + } RHMode; + + /// Constructor + RHGenericDriver(); + + /// Generic destructor to prevent warnings when objects are dynamically allocated + virtual ~RHGenericDriver() {}; + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Tests whether a new message is available + /// from the Driver. + /// On most drivers, if there is an uncollected received message, and there is no message + /// currently bing transmitted, this will also put the Driver into RHModeRx mode until + /// a message is actually received by the transport, when it will be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop. + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv(). + virtual bool available() = 0; + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied. + /// \return true if a valid message was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len) = 0; + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then optionally waits for Channel Activity Detection (CAD) + /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is NOT permitted. If the message is too long for the underlying radio technology, send() will + /// return false and will not send the message. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send (> 0) + /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting. + /// \return true if the message length was valid and it was correctly queued for transmit. Return false + /// if CAD was requested and the CAD timeout timed out before clear channel was detected. + virtual bool send(const uint8_t* data, uint8_t len) = 0; + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength() = 0; + + /// Starts the receiver and blocks until a valid received + /// message is available. + /// Default implementation calls available() repeatedly until it returns true; + /// \param[in] polldelay Time between polling available() in milliseconds. This can be useful + /// in multitaking environment like Linux to prevent waitAvailableTimeout + /// using all the CPU while polling for receiver activity + virtual void waitAvailable(uint16_t polldelay = 0); + + /// Blocks until the transmitter + /// is no longer transmitting. + virtual bool waitPacketSent(); + + /// Blocks until the transmitter is no longer transmitting. + /// or until the timeout occuers, whichever happens first + /// \param[in] timeout Maximum time to wait in milliseconds. + /// \return true if the radio completed transmission within the timeout period. False if it timed out. + virtual bool waitPacketSent(uint16_t timeout); + + /// Starts the receiver and blocks until a received message is available or a timeout. + /// Default implementation calls available() repeatedly until it returns true; + /// \param[in] timeout Maximum time to wait in milliseconds. + /// \param[in] polldelay Time between polling available() in milliseconds. This can be useful + /// in multitaking environment like Linux to prevent waitAvailableTimeout + /// using all the CPU while polling for receiver activity + /// \return true if a message is available + virtual bool waitAvailableTimeout(uint16_t timeout, uint16_t polldelay = 0); + + // Bent G Christensen (bentor@gmail.com), 08/15/2016 + /// Channel Activity Detection (CAD). + /// Blocks until channel activity is finished or CAD timeout occurs. + /// Uses the radio's CAD function (if supported) to detect channel activity. + /// Implements random delays of 100 to 1000ms while activity is detected and until timeout. + /// Caution: the random() function is not seeded. If you want non-deterministic behaviour, consider + /// using something like randomSeed(analogRead(A0)); in your sketch. + /// Permits the implementation of listen-before-talk mechanism (Collision Avoidance). + /// Calls the isChannelActive() member function for the radio (if supported) + /// to determine if the channel is active. If the radio does not support isChannelActive(), + /// always returns true immediately + /// \return true if the radio-specific CAD (as returned by isChannelActive()) + /// shows the channel is clear within the timeout period (or the timeout period is 0), else returns false. + virtual bool waitCAD(); + + /// Sets the Channel Activity Detection timeout in milliseconds to be used by waitCAD(). + /// The default is 0, which means do not wait for CAD detection. + /// CAD detection depends on support for isChannelActive() by your particular radio. + void setCADTimeout(unsigned long cad_timeout); + + /// Determine if the currently selected radio channel is active. + /// This is expected to be subclassed by specific radios to implement their Channel Activity Detection + /// if supported. If the radio does not support CAD, returns true immediately. If a RadioHead radio + /// supports isChannelActive() it will be documented in the radio specific documentation. + /// This is called automatically by waitCAD(). + /// \return true if the radio-specific CAD (as returned by override of isChannelActive()) shows the + /// current radio channel as active, else false. If there is no radio-specific CAD, returns false. + virtual bool isChannelActive(); + + /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. + /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, + /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. + /// In promiscuous mode, all messages will be accepted regardless of the TO header. + /// In a conventional multinode system, all nodes will have a unique address + /// (which you could store in EEPROM). + /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, + /// allowing the possibilty of address spoofing). + /// \param[in] thisAddress The address of this node. + virtual void setThisAddress(uint8_t thisAddress); + + /// Sets the TO header to be sent in all subsequent messages + /// \param[in] to The new TO header value + virtual void setHeaderTo(uint8_t to); + + /// Sets the FROM header to be sent in all subsequent messages + /// \param[in] from The new FROM header value + virtual void setHeaderFrom(uint8_t from); + + /// Sets the ID header to be sent in all subsequent messages + /// \param[in] id The new ID header value + virtual void setHeaderId(uint8_t id); + + /// Sets and clears bits in the FLAGS header to be sent in all subsequent messages + /// First it clears he FLAGS according to the clear argument, then sets the flags according to the + /// set argument. The default for clear always clears the application specific flags. + /// \param[in] set bitmask of bits to be set. Flags are cleared with the clear mask before being set. + /// \param[in] clear bitmask of flags to clear. Defaults to RH_FLAGS_APPLICATION_SPECIFIC + /// which clears the application specific flags, resulting in new application specific flags + /// identical to the set. + virtual void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_APPLICATION_SPECIFIC); + + /// Tells the receiver to accept messages with any TO address, not just messages + /// addressed to thisAddress or the broadcast address + /// \param[in] promiscuous true if you wish to receive messages with any TO address + virtual void setPromiscuous(bool promiscuous); + + /// Returns the TO header of the last received message + /// \return The TO header + virtual uint8_t headerTo(); + + /// Returns the FROM header of the last received message + /// \return The FROM header + virtual uint8_t headerFrom(); + + /// Returns the ID header of the last received message + /// \return The ID header + virtual uint8_t headerId(); + + /// Returns the FLAGS header of the last received message + /// \return The FLAGS header + virtual uint8_t headerFlags(); + + /// Returns the most recent RSSI (Receiver Signal Strength Indicator). + /// Usually it is the RSSI of the last received message, which is measured when the preamble is received. + /// If you called readRssi() more recently, it will return that more recent value. + /// \return The most recent RSSI measurement in dBm. + virtual int16_t lastRssi(); + + /// Returns the operating mode of the library. + /// \return the current mode, one of RF69_MODE_* + virtual RHMode mode(); + + /// Sets the operating mode of the transport. + virtual void setMode(RHMode mode); + + /// Sets the transport hardware into low-power sleep mode + /// (if supported). May be overridden by specific drivers to initialte sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// \return true if sleep mode is supported by transport hardware and the RadioHead driver, and if sleep mode + /// was successfully entered. If sleep mode is not suported, return false. + virtual bool sleep(); + + /// Prints a data buffer in HEX. + /// For diagnostic use + /// \param[in] prompt string to preface the print + /// \param[in] buf Location of the buffer to print + /// \param[in] len Length of the buffer in octets. + static void printBuffer(const char* prompt, const uint8_t* buf, uint8_t len); + + /// Returns the count of the number of bad received packets (ie packets with bad lengths, checksum etc) + /// which were rejected and not delivered to the application. + /// Caution: not all drivers can correctly report this count. Some underlying hardware only report + /// good packets. + /// \return The number of bad packets received. + virtual uint16_t rxBad(); + + /// Returns the count of the number of + /// good received packets + /// \return The number of good packets received. + virtual uint16_t rxGood(); + + /// Returns the count of the number of + /// packets successfully transmitted (though not necessarily received by the destination) + /// \return The number of packets successfully transmitted + virtual uint16_t txGood(); + +protected: + + /// The current transport operating mode + volatile RHMode _mode; + + /// This node id + uint8_t _thisAddress; + + /// Whether the transport is in promiscuous mode + bool _promiscuous; + + /// TO header in the last received mesasge + volatile uint8_t _rxHeaderTo; + + /// FROM header in the last received mesasge + volatile uint8_t _rxHeaderFrom; + + /// ID header in the last received mesasge + volatile uint8_t _rxHeaderId; + + /// FLAGS header in the last received mesasge + volatile uint8_t _rxHeaderFlags; + + /// TO header to send in all messages + uint8_t _txHeaderTo; + + /// FROM header to send in all messages + uint8_t _txHeaderFrom; + + /// ID header to send in all messages + uint8_t _txHeaderId; + + /// FLAGS header to send in all messages + uint8_t _txHeaderFlags; + + /// The value of the last received RSSI value, in some transport specific units + volatile int16_t _lastRssi; + + /// Count of the number of bad messages (eg bad checksum etc) received + volatile uint16_t _rxBad; + + /// Count of the number of successfully transmitted messaged + volatile uint16_t _rxGood; + + /// Count of the number of bad messages (correct checksum etc) received + volatile uint16_t _txGood; + + /// Channel activity detected + volatile bool _cad; + + /// Channel activity timeout in ms + unsigned int _cad_timeout; + +private: + +}; + +#endif diff --git a/RHGenericSPI.cpp b/RHGenericSPI.cpp new file mode 100644 index 0000000..ec43cd4 --- /dev/null +++ b/RHGenericSPI.cpp @@ -0,0 +1,31 @@ +// RHGenericSPI.cpp +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHGenericSPI.cpp,v 1.2 2014/04/12 05:26:05 mikem Exp $ + +#include + +RHGenericSPI::RHGenericSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode) + : + _frequency(frequency), + _bitOrder(bitOrder), + _dataMode(dataMode) +{ +} + +void RHGenericSPI::setBitOrder(BitOrder bitOrder) +{ + _bitOrder = bitOrder; +} + +void RHGenericSPI::setDataMode(DataMode dataMode) +{ + _dataMode = dataMode; +} + +void RHGenericSPI::setFrequency(Frequency frequency) +{ + _frequency = frequency; +} + diff --git a/RHGenericSPI.h b/RHGenericSPI.h new file mode 100644 index 0000000..4116a56 --- /dev/null +++ b/RHGenericSPI.h @@ -0,0 +1,183 @@ +// RHGenericSPI.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHGenericSPI.h,v 1.9 2020/01/05 07:02:23 mikem Exp $ + +#ifndef RHGenericSPI_h +#define RHGenericSPI_h + +#include + +///////////////////////////////////////////////////////////////////// +/// \class RHGenericSPI RHGenericSPI.h +/// \brief Base class for SPI interfaces +/// +/// This generic abstract class is used to encapsulate hardware or software SPI interfaces for +/// a variety of platforms. +/// The intention is so that driver classes can be configured to use hardware or software SPI +/// without changing the main code. +/// +/// You must provide a subclass of this class to driver constructors that require SPI. +/// A concrete subclass that encapsualates the standard Arduino hardware SPI and a bit-banged +/// software implementation is included. +/// +/// Do not directly use this class: it must be subclassed and the following abstract functions at least +/// must be implemented: +/// - begin() +/// - end() +/// - transfer() +class RHGenericSPI +{ +public: + + /// \brief Defines constants for different SPI modes + /// + /// Defines constants for different SPI modes + /// that can be passed to the constructor or setMode() + /// We need to define these in a device and platform independent way, because the + /// SPI implementation is different on each platform. + typedef enum + { + DataMode0 = 0, ///< SPI Mode 0: CPOL = 0, CPHA = 0 + DataMode1, ///< SPI Mode 1: CPOL = 0, CPHA = 1 + DataMode2, ///< SPI Mode 2: CPOL = 1, CPHA = 0 + DataMode3, ///< SPI Mode 3: CPOL = 1, CPHA = 1 + } DataMode; + + /// \brief Defines constants for different SPI bus frequencies + /// + /// Defines constants for different SPI bus frequencies + /// that can be passed to setFrequency(). + /// The frequency you get may not be exactly the one according to the name. + /// We need to define these in a device and platform independent way, because the + /// SPI implementation is different on each platform. + typedef enum + { + Frequency1MHz = 0, ///< SPI bus frequency close to 1MHz + Frequency2MHz, ///< SPI bus frequency close to 2MHz + Frequency4MHz, ///< SPI bus frequency close to 4MHz + Frequency8MHz, ///< SPI bus frequency close to 8MHz + Frequency16MHz ///< SPI bus frequency close to 16MHz + } Frequency; + + /// \brief Defines constants for different SPI endianness + /// + /// Defines constants for different SPI endianness + /// that can be passed to setBitOrder() + /// We need to define these in a device and platform independent way, because the + /// SPI implementation is different on each platform. + typedef enum + { + BitOrderMSBFirst = 0, ///< SPI MSB first + BitOrderLSBFirst, ///< SPI LSB first + } BitOrder; + + /// Constructor + /// Creates an instance of an abstract SPI interface. + /// Do not use this contructor directly: you must instead use on of the concrete subclasses provided + /// such as RHHardwareSPI or RHSoftwareSPI + /// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency + /// is mapped to the closest available bus frequency on the platform. + /// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or + /// RHGenericSPI::BitOrderLSBFirst. + /// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode + RHGenericSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0); + + /// Transfer a single octet to and from the SPI interface + /// \param[in] data The octet to send + /// \return The octet read from SPI while the data octet was sent + virtual uint8_t transfer(uint8_t data) = 0; + +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + /// Transfer up to 2 bytes on the SPI interface + /// \param[in] byte0 The first byte to be sent on the SPI interface + /// \param[in] byte1 The second byte to be sent on the SPI interface + /// \return The second byte clocked in as the second byte is sent. + virtual uint8_t transfer2B(uint8_t byte0, uint8_t byte1) = 0; + + /// Read a number of bytes on the SPI interface from an NRF device + /// \param[in] reg The NRF device register to read + /// \param[out] dest The buffer to hold the bytes read + /// \param[in] len The number of bytes to read + /// \return The NRF status byte + virtual uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) = 0; + + /// Wrte a number of bytes on the SPI interface to an NRF device + /// \param[in] reg The NRF device register to read + /// \param[out] src The buffer to hold the bytes write + /// \param[in] len The number of bytes to write + /// \return The NRF status byte + virtual uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) = 0; + +#endif + + /// SPI Configuration methods + /// Enable SPI interrupts (if supported) + /// This can be used in an SPI slave to indicate when an SPI message has been received + virtual void attachInterrupt() {}; + + /// Disable SPI interrupts (if supported) + /// This can be used to diable the SPI interrupt in slaves where that is supported. + virtual void detachInterrupt() {}; + + /// Initialise the SPI library. + /// Call this after configuring and before using the SPI library + virtual void begin() = 0; + + /// Disables the SPI bus (leaving pin modes unchanged). + /// Call this after you have finished using the SPI interface + virtual void end() = 0; + + /// Sets the bit order the SPI interface will use + /// Sets the order of the bits shifted out of and into the SPI bus, either + /// LSBFIRST (least-significant bit first) or MSBFIRST (most-significant bit first). + /// \param[in] bitOrder Bit order to be used: one of RHGenericSPI::BitOrder + virtual void setBitOrder(BitOrder bitOrder); + + /// Sets the SPI data mode: that is, clock polarity and phase. + /// See the Wikipedia article on SPI for details. + /// \param[in] dataMode The mode to use: one of RHGenericSPI::DataMode + virtual void setDataMode(DataMode dataMode); + + /// Sets the SPI clock divider relative to the system clock. + /// On AVR based boards, the dividers available are 2, 4, 8, 16, 32, 64 or 128. + /// The default setting is SPI_CLOCK_DIV4, which sets the SPI clock to one-quarter + /// the frequency of the system clock (4 Mhz for the boards at 16 MHz). + /// \param[in] frequency The data rate to use: one of RHGenericSPI::Frequency + virtual void setFrequency(Frequency frequency); + + /// Signal the start of an SPI transaction that must not be interrupted by other SPI actions + /// In subclasses that support transactions this will ensure that other SPI transactions + /// are blocked until this one is completed by endTransaction(). + /// Base does nothing + /// Might be overridden in subclass + virtual void beginTransaction(){} + + /// Signal the end of an SPI transaction + /// Base does nothing + /// Might be overridden in subclass + virtual void endTransaction(){} + + /// Specify the interrupt number of the interrupt that will use SPI transactions + /// Tells the SPI support software that SPI transactions will occur with the interrupt + /// handler assocated with interruptNumber + /// Base does nothing + /// Might be overridden in subclass + /// \param[in] interruptNumber The number of the interrupt + virtual void usingInterrupt(uint8_t interruptNumber){ + (void)interruptNumber; + } + +protected: + + /// The configure SPI Bus frequency, one of RHGenericSPI::Frequency + Frequency _frequency; // Bus frequency, one of RHGenericSPI::Frequency + + /// Bit order, one of RHGenericSPI::BitOrder + BitOrder _bitOrder; + + /// SPI bus mode, one of RHGenericSPI::DataMode + DataMode _dataMode; +}; +#endif diff --git a/RHHardwareSPI.cpp b/RHHardwareSPI.cpp new file mode 100644 index 0000000..c9f2bb3 --- /dev/null +++ b/RHHardwareSPI.cpp @@ -0,0 +1,517 @@ +// RHHardwareSPI.cpp +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHHardwareSPI.cpp,v 1.29 2020/08/04 09:02:14 mikem Exp $ + +#include + +#ifdef RH_HAVE_HARDWARE_SPI + +// Declare a single default instance of the hardware SPI interface class +RHHardwareSPI hardware_spi; + + +// This is very ugly and there should be a better way. Problem is that +// not all types hardware/platform dependent of SPI classes have the +// same name, so its hard to abstract it away on a per-instance basis +#if (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc +// Declare an SPI interface to use +HardwareSPI SPI(1); +#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32F4 Discovery +// Declare an SPI interface to use +HardwareSPI SPI(1); +#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) // Mongoose OS platform +HardwareSPI SPI(1); +#elif (RH_PLATFORM == RH_PLATFORM_STM32L0) && (defined STM32L082xx || defined STM32L072xx) + extern SPIClass radio_spi; // Created in RH_ABZ.cpp + #define SPI radio_spi +#elif (RH_PLATFORM == RH_PLATFORM_ESP32 && defined(RH_ESP32_USE_HSPI)) + SPIClass SPI_HSPI(HSPI); + #define SPI SPI_HSPI +#elif defined(RAK4630) // RAKwireless RAK4630 + extern SPIClass SPI_LORA(NRF_SPIM2, 45, 43, 44); + #define SPI SPI_LORA +#endif + +// Arduino Due has default SPI pins on central SPI headers, and not on 10, 11, 12, 13 +// as per other Arduinos +// http://21stdigitalhome.blogspot.com.au/2013/02/arduino-due-hardware-spi.html +#if defined (__arm__) && !defined(CORE_TEENSY) && !defined(SPI_CLOCK_DIV16) && !defined(RH_PLATFORM_NRF52) + // Arduino Due in 1.5.5 has no definitions for SPI dividers + // SPI clock divider is based on MCK of 84MHz + #define SPI_CLOCK_DIV16 (VARIANT_MCK/84000000) // 1MHz + #define SPI_CLOCK_DIV8 (VARIANT_MCK/42000000) // 2MHz + #define SPI_CLOCK_DIV4 (VARIANT_MCK/21000000) // 4MHz + #define SPI_CLOCK_DIV2 (VARIANT_MCK/10500000) // 8MHz + #define SPI_CLOCK_DIV1 (VARIANT_MCK/5250000) // 16MHz +#endif + +RHHardwareSPI::RHHardwareSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode) + : + RHGenericSPI(frequency, bitOrder, dataMode) +{ +} + +uint8_t RHHardwareSPI::transfer(uint8_t data) +{ + return SPI.transfer(data); +} + +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) +uint8_t RHHardwareSPI::transfer2B(uint8_t byte0, uint8_t byte1) +{ + return SPI.transfer2B(byte0, byte1); +} + +uint8_t RHHardwareSPI::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) +{ + return SPI.spiBurstRead(reg, dest, len); +} + +uint8_t RHHardwareSPI::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) +{ + uint8_t status = SPI.spiBurstWrite(reg, src, len); + return status; +} +#endif + +void RHHardwareSPI::attachInterrupt() +{ +#ifdef RH_HAVE_SPI_ATTACH_INTERRUPT + SPI.attachInterrupt(); +#endif +} + +void RHHardwareSPI::detachInterrupt() +{ +#ifdef RH_HAVE_SPI_ATTACH_INTERRUPT + SPI.detachInterrupt(); +#endif +} + +void RHHardwareSPI::begin() +{ +#if defined(SPI_HAS_TRANSACTION) + // Perhaps this is a uniform interface for SPI? + // Currently Teensy and ESP32 only + uint32_t frequency; + if (_frequency == Frequency16MHz) + frequency = 16000000; + else if (_frequency == Frequency8MHz) + frequency = 8000000; + else if (_frequency == Frequency4MHz) + frequency = 4000000; + else if (_frequency == Frequency2MHz) + frequency = 2000000; + else + frequency = 1000000; + +#if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))) || defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI) || defined(ARDUINO_ARCH_STM32L0) || defined(NRF52) || defined (ARDUINO_ARCH_RP2040) + // Arduino Due in 1.5.5 has its own BitOrder :-( + // So too does Arduino Zero + // So too does rogerclarkmelbourne/Arduino_STM32 + // So too does GrumpyOldPizza/ArduinoCore-stm32l0 + // So too does RPI Pico + ::BitOrder bitOrder; +// This no longer relevant: new versions is uint8_t +//#elif (RH_PLATFORM == RH_PLATFORM_ATTINY_MEGA) +// ::BitOrder bitOrder; +#else + uint8_t bitOrder; +#endif + + if (_bitOrder == BitOrderLSBFirst) + bitOrder = LSBFIRST; + else + bitOrder = MSBFIRST; + + uint8_t dataMode; + if (_dataMode == DataMode0) + dataMode = SPI_MODE0; + else if (_dataMode == DataMode1) + dataMode = SPI_MODE1; + else if (_dataMode == DataMode2) + dataMode = SPI_MODE2; + else if (_dataMode == DataMode3) + dataMode = SPI_MODE3; + else + dataMode = SPI_MODE0; + + // Save the settings for use in transactions + _settings = SPISettings(frequency, bitOrder, dataMode); + SPI.begin(); + +#else // SPI_HAS_TRANSACTION + + // Sigh: there are no common symbols for some of these SPI options across all platforms + #if (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_UNO32) || (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE || RH_PLATFORM == RH_PLATFORM_NRF52) + uint8_t dataMode; + if (_dataMode == DataMode0) + dataMode = SPI_MODE0; + else if (_dataMode == DataMode1) + dataMode = SPI_MODE1; + else if (_dataMode == DataMode2) + dataMode = SPI_MODE2; + else if (_dataMode == DataMode3) + dataMode = SPI_MODE3; + else + dataMode = SPI_MODE0; + #if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY) + // Temporary work-around due to problem where avr_emulation.h does not work properly for the setDataMode() cal + SPCR &= ~SPI_MODE_MASK; + #else + #if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && defined(ARDUINO_ARCH_SAMD)) || defined(ARDUINO_ARCH_NRF52) + // Zero requires begin() before anything else :-) + SPI.begin(); + #endif + + SPI.setDataMode(dataMode); + #endif + + #if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))) || defined(ARDUINO_ARCH_NRF52) || defined (ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32F4) || defined(ARDUINO_ARCH_STM32F1) + // Arduino Due in 1.5.5 has its own BitOrder :-( + // So too does Arduino Zero + // So too does rogerclarkmelbourne/Arduino_STM32 + // So too does stm32duino F1, F4 + ::BitOrder bitOrder; + #else + uint8_t bitOrder; + #endif + if (_bitOrder == BitOrderLSBFirst) + bitOrder = LSBFIRST; + else + bitOrder = MSBFIRST; + SPI.setBitOrder(bitOrder); + uint8_t divider; + switch (_frequency) + { + case Frequency1MHz: + default: + #if F_CPU == 8000000 + divider = SPI_CLOCK_DIV8; + #else + divider = SPI_CLOCK_DIV16; + #endif + break; + + case Frequency2MHz: + #if F_CPU == 8000000 + divider = SPI_CLOCK_DIV4; + #else + divider = SPI_CLOCK_DIV8; + #endif + break; + + case Frequency4MHz: + #if F_CPU == 8000000 + divider = SPI_CLOCK_DIV2; + #else + divider = SPI_CLOCK_DIV4; + #endif + break; + + case Frequency8MHz: + divider = SPI_CLOCK_DIV2; // 4MHz on an 8MHz Arduino + break; + + case Frequency16MHz: + divider = SPI_CLOCK_DIV2; // Not really 16MHz, only 8MHz. 4MHz on an 8MHz Arduino + break; + + } + + SPI.setClockDivider(divider); + + SPI.begin(); + // Teensy requires it to be set _after_ begin() + SPI.setClockDivider(divider); + + #elif (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc + spi_mode dataMode; + // Hmmm, if we do this as a switch, GCC on maple gets v confused! + if (_dataMode == DataMode0) + dataMode = SPI_MODE_0; + else if (_dataMode == DataMode1) + dataMode = SPI_MODE_1; + else if (_dataMode == DataMode2) + dataMode = SPI_MODE_2; + else if (_dataMode == DataMode3) + dataMode = SPI_MODE_3; + else + dataMode = SPI_MODE_0; + + uint32 bitOrder; + if (_bitOrder == BitOrderLSBFirst) + bitOrder = LSBFIRST; + else + bitOrder = MSBFIRST; + + SPIFrequency frequency; // Yes, I know these are not exact equivalents. + switch (_frequency) + { + case Frequency1MHz: + default: + frequency = SPI_1_125MHZ; + break; + + case Frequency2MHz: + frequency = SPI_2_25MHZ; + break; + + case Frequency4MHz: + frequency = SPI_4_5MHZ; + break; + + case Frequency8MHz: + frequency = SPI_9MHZ; + break; + + case Frequency16MHz: + frequency = SPI_18MHZ; + break; + + } + SPI.begin(frequency, bitOrder, dataMode); + + #elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32F4 discovery + uint8_t dataMode; + if (_dataMode == DataMode0) + dataMode = SPI_MODE0; + else if (_dataMode == DataMode1) + dataMode = SPI_MODE1; + else if (_dataMode == DataMode2) + dataMode = SPI_MODE2; + else if (_dataMode == DataMode3) + dataMode = SPI_MODE3; + else + dataMode = SPI_MODE0; + + uint32_t bitOrder; + if (_bitOrder == BitOrderLSBFirst) + bitOrder = LSBFIRST; + else + bitOrder = MSBFIRST; + + SPIFrequency frequency; // Yes, I know these are not exact equivalents. + switch (_frequency) + { + case Frequency1MHz: + default: + frequency = SPI_1_3125MHZ; + break; + + case Frequency2MHz: + frequency = SPI_2_625MHZ; + break; + + case Frequency4MHz: + frequency = SPI_5_25MHZ; + break; + + case Frequency8MHz: + frequency = SPI_10_5MHZ; + break; + + case Frequency16MHz: + frequency = SPI_21_0MHZ; + break; + + } + SPI.begin(frequency, bitOrder, dataMode); + + #elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Photon + uint8_t dataMode; + if (_dataMode == DataMode0) + dataMode = SPI_MODE0; + else if (_dataMode == DataMode1) + dataMode = SPI_MODE1; + else if (_dataMode == DataMode2) + dataMode = SPI_MODE2; + else if (_dataMode == DataMode3) + dataMode = SPI_MODE3; + else + dataMode = SPI_MODE0; + SPI.setDataMode(dataMode); + if (_bitOrder == BitOrderLSBFirst) + SPI.setBitOrder(LSBFIRST); + else + SPI.setBitOrder(MSBFIRST); + + switch (_frequency) + { + case Frequency1MHz: + default: + SPI.setClockSpeed(1, MHZ); + break; + + case Frequency2MHz: + SPI.setClockSpeed(2, MHZ); + break; + + case Frequency4MHz: + SPI.setClockSpeed(4, MHZ); + break; + + case Frequency8MHz: + SPI.setClockSpeed(8, MHZ); + break; + + case Frequency16MHz: + SPI.setClockSpeed(16, MHZ); + break; + } + +// SPI.setClockDivider(SPI_CLOCK_DIV4); // 72MHz / 4MHz = 18MHz +// SPI.setClockSpeed(1, MHZ); + SPI.begin(); + + #elif RH_PLATFORM == (RH_PLATFORM_ESP8266) || (RH_PLATFORM == RH_PLATFORM_ESP32) + // Requires SPI driver for ESP8266 from https://github.com/esp8266/Arduino/tree/master/libraries/SPI + // Which ppears to be in Arduino Board Manager ESP8266 Community version 2.1.0 + // Contributed by David Skinner + // begin comes first + SPI.begin(); + + // datamode + switch ( _dataMode ) + { + case DataMode1: + SPI.setDataMode ( SPI_MODE1 ); + break; + case DataMode2: + SPI.setDataMode ( SPI_MODE2 ); + break; + case DataMode3: + SPI.setDataMode ( SPI_MODE3 ); + break; + case DataMode0: + default: + SPI.setDataMode ( SPI_MODE0 ); + break; + } + + // bitorder + SPI.setBitOrder(_bitOrder == BitOrderLSBFirst ? LSBFIRST : MSBFIRST); + + // frequency (this sets the divider) + switch (_frequency) + { + case Frequency1MHz: + default: + SPI.setFrequency(1000000); + break; + case Frequency2MHz: + SPI.setFrequency(2000000); + break; + case Frequency4MHz: + SPI.setFrequency(4000000); + break; + case Frequency8MHz: + SPI.setFrequency(8000000); + break; + case Frequency16MHz: + SPI.setFrequency(16000000); + break; + } + + #elif (RH_PLATFORM == RH_PLATFORM_RASPI) // Raspberry PI + uint8_t dataMode; + if (_dataMode == DataMode0) + dataMode = BCM2835_SPI_MODE0; + else if (_dataMode == DataMode1) + dataMode = BCM2835_SPI_MODE1; + else if (_dataMode == DataMode2) + dataMode = BCM2835_SPI_MODE2; + else if (_dataMode == DataMode3) + dataMode = BCM2835_SPI_MODE3; + + uint8_t bitOrder; + if (_bitOrder == BitOrderLSBFirst) + bitOrder = BCM2835_SPI_BIT_ORDER_LSBFIRST; + else + bitOrder = BCM2835_SPI_BIT_ORDER_MSBFIRST; + + uint32_t divider; + switch (_frequency) + { + case Frequency1MHz: + default: + divider = BCM2835_SPI_CLOCK_DIVIDER_256; + break; + case Frequency2MHz: + divider = BCM2835_SPI_CLOCK_DIVIDER_128; + break; + case Frequency4MHz: + divider = BCM2835_SPI_CLOCK_DIVIDER_64; + break; + case Frequency8MHz: + divider = BCM2835_SPI_CLOCK_DIVIDER_32; + break; + case Frequency16MHz: + divider = BCM2835_SPI_CLOCK_DIVIDER_16; + break; + } + SPI.begin(divider, bitOrder, dataMode); + #elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + uint8_t dataMode = SPI_MODE0; + uint32_t frequency = 4000000; //!!! ESP32/NRF902 works ok at 4MHz but not at 8MHz SPI clock. + uint32_t bitOrder = MSBFIRST; + + if (_dataMode == DataMode0) { + dataMode = SPI_MODE0; + } else if (_dataMode == DataMode1) { + dataMode = SPI_MODE1; + } else if (_dataMode == DataMode2) { + dataMode = SPI_MODE2; + } else if (_dataMode == DataMode3) { + dataMode = SPI_MODE3; + } + + if (_bitOrder == BitOrderLSBFirst) { + bitOrder = LSBFIRST; + } + + if (_frequency == Frequency4MHz) + frequency = 4000000; + else if (_frequency == Frequency2MHz) + frequency = 2000000; + else + frequency = 1000000; + + SPI.begin(frequency, bitOrder, dataMode); + #else + #warning RHHardwareSPI does not support this platform yet. Consider adding it and contributing a patch. + #endif + +#endif // SPI_HAS_TRANSACTION +} + +void RHHardwareSPI::end() +{ + return SPI.end(); +} + +void RHHardwareSPI::beginTransaction() +{ +#if defined(SPI_HAS_TRANSACTION) + SPI.beginTransaction(_settings); +#endif +} + +void RHHardwareSPI::endTransaction() +{ +#if defined(SPI_HAS_TRANSACTION) + SPI.endTransaction(); +#endif +} + +void RHHardwareSPI::usingInterrupt(uint8_t interrupt) +{ +#if defined(SPI_HAS_TRANSACTION) && !defined(RH_MISSING_SPIUSINGINTERRUPT) + SPI.usingInterrupt(interrupt); +#endif + (void)interrupt; +} + +#endif diff --git a/RHHardwareSPI.h b/RHHardwareSPI.h new file mode 100644 index 0000000..952f953 --- /dev/null +++ b/RHHardwareSPI.h @@ -0,0 +1,116 @@ +// RHHardwareSPI.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHHardwareSPI.h,v 1.12 2020/01/05 07:02:23 mikem Exp $ + +#ifndef RHHardwareSPI_h +#define RHHardwareSPI_h + +#include + +///////////////////////////////////////////////////////////////////// +/// \class RHHardwareSPI RHHardwareSPI.h +/// \brief Encapsulate a hardware SPI bus interface +/// +/// This concrete subclass of GenericSPIClass encapsulates the standard Arduino hardware and other +/// hardware SPI interfaces. +/// +/// SPI transactions are supported in development environments that support it with SPI_HAS_TRANSACTION. +class RHHardwareSPI : public RHGenericSPI +{ +#ifdef RH_HAVE_HARDWARE_SPI +public: + /// Constructor + /// Creates an instance of a hardware SPI interface, using whatever SPI hardware is available on + /// your processor platform. On Arduino and Uno32, uses SPI. On Maple, uses HardwareSPI. + /// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency + /// is mapped to the closest available bus frequency on the platform. + /// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or + /// RHGenericSPI::BitOrderLSBFirst. + /// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode + RHHardwareSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0); + + /// Transfer a single octet to and from the SPI interface + /// \param[in] data The octet to send + /// \return The octet read from SPI while the data octet was sent + uint8_t transfer(uint8_t data); + +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + /// Transfer (write) 2 bytes on the SPI interface to an NRF device + /// \param[in] byte0 The first byte to be sent on the SPI interface + /// \param[in] byte1 The second byte to be sent on the SPI interface + /// \return The second byte clocked in as the second byte is sent. + uint8_t transfer2B(uint8_t byte0, uint8_t byte1); + + /// Read a number of bytes on the SPI interface from an NRF device + /// \param[in] reg The NRF device register to read + /// \param[out] dest The buffer to hold the bytes read + /// \param[in] len The number of bytes to read + /// \return The NRF status byte + uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len); + + /// Wrte a number of bytes on the SPI interface to an NRF device + /// \param[in] reg The NRF device register to read + /// \param[out] src The buffer to hold the bytes write + /// \param[in] len The number of bytes to write + /// \return The NRF status byte + uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len); + +#endif + + // SPI Configuration methods + /// Enable SPI interrupts + /// This can be used in an SPI slave to indicate when an SPI message has been received + /// It will cause the SPI_STC_vect interrupt vectr to be executed + void attachInterrupt(); + + /// Disable SPI interrupts + /// This can be used to diable the SPI interrupt in slaves where that is supported. + void detachInterrupt(); + + /// Initialise the SPI library + /// Call this after configuring the SPI interface and before using it to transfer data. + /// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high. + void begin(); + + /// Disables the SPI bus (leaving pin modes unchanged). + /// Call this after you have finished using the SPI interface. + void end(); +#else + // not supported on ATTiny etc + uint8_t transfer(uint8_t /*data*/) {return 0;} + void begin(){} + void end(){} + +#endif + + /// Signal the start of an SPI transaction that must not be interrupted by other SPI actions + /// In subclasses that support transactions this will ensure that other SPI transactions + /// are blocked until this one is completed by endTransaction(). + /// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION. + virtual void beginTransaction(); + + /// Signal the end of an SPI transaction + /// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION. + virtual void endTransaction(); + + /// Specify the interrupt number of the interrupt that will use SPI transactions + /// Tells the SPI support software that SPI transactions will occur with the interrupt + /// handler assocated with interruptNumber + /// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION. + /// \param[in] interruptNumber The number of the interrupt + virtual void usingInterrupt(uint8_t interruptNumber); + +protected: + +#if defined(SPI_HAS_TRANSACTION) + // Storage for SPI settings used in SPI transactions + SPISettings _settings; +#endif +}; + +// Built in default instance +extern RHHardwareSPI hardware_spi; + +#endif diff --git a/RHMesh.cpp b/RHMesh.cpp new file mode 100644 index 0000000..91fd343 --- /dev/null +++ b/RHMesh.cpp @@ -0,0 +1,253 @@ +// RHMesh.cpp +// +// Define addressed datagram +// +// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers +// (see http://www.hoperf.com) +// RHDatagram will be received only by the addressed node or all nodes within range if the +// to address is RH_BROADCAST_ADDRESS +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHMesh.cpp,v 1.12 2020/08/04 09:02:14 mikem Exp $ + +#include + +uint8_t RHMesh::_tmpMessage[RH_ROUTER_MAX_MESSAGE_LEN]; + +//////////////////////////////////////////////////////////////////// +// Constructors +RHMesh::RHMesh(RHGenericDriver& driver, uint8_t thisAddress) + : RHRouter(driver, thisAddress) +{ +} + +//////////////////////////////////////////////////////////////////// +// Public methods + +//////////////////////////////////////////////////////////////////// +// Discovers a route to the destination (if necessary), sends and +// waits for delivery to the next hop (but not for delivery to the final destination) +uint8_t RHMesh::sendtoWait(uint8_t* buf, uint8_t len, uint8_t address, uint8_t flags) +{ + if (len > RH_MESH_MAX_MESSAGE_LEN) + return RH_ROUTER_ERROR_INVALID_LENGTH; + + if (address != RH_BROADCAST_ADDRESS) + { + RoutingTableEntry* route = getRouteTo(address); + if (!route && !doArp(address)) + return RH_ROUTER_ERROR_NO_ROUTE; + } + + // Now have a route. Contruct an application layer message and send it via that route + MeshApplicationMessage* a = (MeshApplicationMessage*)&_tmpMessage; + a->header.msgType = RH_MESH_MESSAGE_TYPE_APPLICATION; + memcpy(a->data, buf, len); + return RHRouter::sendtoWait(_tmpMessage, sizeof(RHMesh::MeshMessageHeader) + len, address, flags); +} + +//////////////////////////////////////////////////////////////////// +bool RHMesh::doArp(uint8_t address) +{ + // Need to discover a route + // Broadcast a route discovery message with nothing in it + MeshRouteDiscoveryMessage* p = (MeshRouteDiscoveryMessage*)&_tmpMessage; + p->header.msgType = RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST; + p->destlen = 1; + p->dest = address; // Who we are looking for + uint8_t error = RHRouter::sendtoWait((uint8_t*)p, sizeof(RHMesh::MeshMessageHeader) + 2, RH_BROADCAST_ADDRESS); + if (error != RH_ROUTER_ERROR_NONE) + return false; + + // Wait for a reply, which will be unicast back to us + // It will contain the complete route to the destination + uint8_t messageLen = sizeof(_tmpMessage); + // FIXME: timeout should be configurable + unsigned long starttime = millis(); + int32_t timeLeft; + while ((timeLeft = RH_MESH_ARP_TIMEOUT - (millis() - starttime)) > 0) + { + if (waitAvailableTimeout(timeLeft)) + { + if (RHRouter::recvfromAck(_tmpMessage, &messageLen)) + { + if ( messageLen > 1 + && p->header.msgType == RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE) + { + // Got a reply, now add the next hop to the dest to the routing table + // The first hop taken is the first octet + addRouteTo(address, headerFrom()); + return true; + } + } + } + YIELD; + } + return false; +} + +//////////////////////////////////////////////////////////////////// +// Called by RHRouter::recvfromAck whenever a message goes past +void RHMesh::peekAtMessage(RoutedMessage* message, uint8_t messageLen) +{ + MeshMessageHeader* m = (MeshMessageHeader*)message->data; + if ( messageLen > 1 + && m->msgType == RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE) + { + // This is a unicast RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE messages + // being routed back to the originator here. Want to scrape some routing data out of the response + // We can find the routes to all the nodes between here and the responding node + MeshRouteDiscoveryMessage* d = (MeshRouteDiscoveryMessage*)message->data; + addRouteTo(d->dest, headerFrom()); + uint8_t numRoutes = messageLen - sizeof(RoutedMessageHeader) - sizeof(MeshMessageHeader) - 2; + uint8_t i; + // Find us in the list of nodes that were traversed to get to the responding node + for (i = 0; i < numRoutes; i++) + if (d->route[i] == _thisAddress) + break; + i++; + while (i < numRoutes) + addRouteTo(d->route[i++], headerFrom()); + } + else if ( messageLen > 1 + && m->msgType == RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE) + { + MeshRouteFailureMessage* d = (MeshRouteFailureMessage*)message->data; + deleteRouteTo(d->dest); + } +} + +//////////////////////////////////////////////////////////////////// +// This is called when a message is to be delivered to the next hop +uint8_t RHMesh::route(RoutedMessage* message, uint8_t messageLen) +{ + uint8_t from = headerFrom(); // Might get clobbered during call to superclass route() + uint8_t ret = RHRouter::route(message, messageLen); + if ( ret == RH_ROUTER_ERROR_NO_ROUTE + || ret == RH_ROUTER_ERROR_UNABLE_TO_DELIVER) + { + // Cant deliver to the next hop. Delete the route + deleteRouteTo(message->header.dest); + if (message->header.source != _thisAddress) + { + // This is being proxied, so tell the originator about it + MeshRouteFailureMessage* p = (MeshRouteFailureMessage*)&_tmpMessage; + p->header.msgType = RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE; + p->dest = message->header.dest; // Who you were trying to deliver to + // Make sure there is a route back towards whoever sent the original message + addRouteTo(message->header.source, from); + ret = RHRouter::sendtoWait((uint8_t*)p, sizeof(RHMesh::MeshMessageHeader) + 1, message->header.source); + } + } + return ret; +} + +//////////////////////////////////////////////////////////////////// +// Subclasses may want to override +bool RHMesh::isPhysicalAddress(uint8_t* address, uint8_t addresslen) +{ + // Can only handle physical addresses 1 octet long, which is the physical node address + return addresslen == 1 && address[0] == _thisAddress; +} + +//////////////////////////////////////////////////////////////////// +bool RHMesh::recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* source, uint8_t* dest, uint8_t* id, uint8_t* flags, uint8_t* hops) +{ + uint8_t tmpMessageLen = sizeof(_tmpMessage); + uint8_t _source; + uint8_t _dest; + uint8_t _id; + uint8_t _flags; + uint8_t _hops; + if (RHRouter::recvfromAck(_tmpMessage, &tmpMessageLen, &_source, &_dest, &_id, &_flags, &_hops)) + { + MeshMessageHeader* p = (MeshMessageHeader*)&_tmpMessage; + + if ( tmpMessageLen >= 1 + && p->msgType == RH_MESH_MESSAGE_TYPE_APPLICATION) + { + MeshApplicationMessage* a = (MeshApplicationMessage*)p; + // Handle application layer messages, presumably for our caller + if (source) *source = _source; + if (dest) *dest = _dest; + if (id) *id = _id; + if (flags) *flags = _flags; + if (hops) *hops = _hops; + uint8_t msgLen = tmpMessageLen - sizeof(MeshMessageHeader); + if (*len > msgLen) + *len = msgLen; + memcpy(buf, a->data, *len); + + return true; + } + else if ( _dest == RH_BROADCAST_ADDRESS + && tmpMessageLen > 1 + && p->msgType == RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST) + { + MeshRouteDiscoveryMessage* d = (MeshRouteDiscoveryMessage*)p; + // Handle Route discovery requests + // Message is an array of node addresses the route request has already passed through + // If it originally came from us, ignore it + if (_source == _thisAddress) + return false; + + uint8_t numRoutes = tmpMessageLen - sizeof(MeshMessageHeader) - 2; + uint8_t i; + // Are we already mentioned? + for (i = 0; i < numRoutes; i++) + if (d->route[i] == _thisAddress) + return false; // Already been through us. Discard + + + addRouteTo(_source, headerFrom()); // The originator needs to be added regardless of node type + + // Hasnt been past us yet, record routes back to the earlier nodes + // No need to waste memory if we are not participating in routing + if (_isa_router) + { + for (i = 0; i < numRoutes; i++) + addRouteTo(d->route[i], headerFrom()); + } + + if (isPhysicalAddress(&d->dest, d->destlen)) + { + // This route discovery is for us. Unicast the whole route back to the originator + // as a RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE + // We are certain to have a route there, because we just got it + d->header.msgType = RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE; + RHRouter::sendtoWait((uint8_t*)d, tmpMessageLen, _source); + } + else if ((i < _max_hops) && _isa_router) + { + // Its for someone else, rebroadcast it, after adding ourselves to the list + d->route[numRoutes] = _thisAddress; + tmpMessageLen++; + // Have to impersonate the source + // REVISIT: if this fails what can we do? + RHRouter::sendtoFromSourceWait(_tmpMessage, tmpMessageLen, RH_BROADCAST_ADDRESS, _source); + } + } + } + return false; +} + +//////////////////////////////////////////////////////////////////// +bool RHMesh::recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags, uint8_t* hops) +{ + unsigned long starttime = millis(); + int32_t timeLeft; + while ((timeLeft = timeout - (millis() - starttime)) > 0) + { + if (waitAvailableTimeout(timeLeft)) + { + if (recvfromAck(buf, len, from, to, id, flags, hops)) + return true; + YIELD; + } + } + return false; +} + + + diff --git a/RHMesh.h b/RHMesh.h new file mode 100644 index 0000000..c0efb32 --- /dev/null +++ b/RHMesh.h @@ -0,0 +1,264 @@ +// RHMesh.h +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHMesh.h,v 1.17 2020/08/04 09:02:14 mikem Exp $ + +#ifndef RHMesh_h +#define RHMesh_h + +#include + +// Types of RHMesh message, used to set msgType in the RHMeshHeader +#define RH_MESH_MESSAGE_TYPE_APPLICATION 0 +#define RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST 1 +#define RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE 2 +#define RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE 3 + +// Timeout for address resolution in milliecs +#define RH_MESH_ARP_TIMEOUT 4000 + +///////////////////////////////////////////////////////////////////// +/// \class RHMesh RHMesh.h +/// \brief RHRouter subclass for sending addressed, optionally acknowledged datagrams +/// multi-hop routed across a network, with automatic route discovery +/// +/// Manager class that extends RHRouter to add automatic route discovery within a mesh of adjacent nodes, +/// and route signalling. +/// +/// Unlike RHRouter, RHMesh can be used in networks where the network topology is fluid, or unknown, +/// or if nodes can mode around or go in or out of service. When a node wants to send a +/// message to another node, it will automatically discover a route to the destination node and use it. +/// If the route becomes unavailable, a new route will be discovered. +/// +/// \par Route Discovery +/// +/// When a RHMesh mesh node is initialised, it doe not know any routes to any other nodes +/// (see RHRouter for details on route and the routing table). +/// When you attempt to send a message with sendtoWait, will first check to see if there is a route to the +/// destinastion node in the routing tabl;e. If not, it wil initialite 'Route Discovery'. +/// When a node needs to discover a route to another node, it broadcasts MeshRouteDiscoveryMessage +/// with a message type of RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST. +/// Any node that receives such a request checks to see if it is a request for a route to itself +/// (in which case it makes a unicast reply to the originating node with a +/// MeshRouteDiscoveryMessage +/// with a message type of RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE) +/// otherwise it rebroadcasts the request, after adding itself to the list of nodes visited so +/// far by the request. +/// +/// If a node receives a RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST that already has itself +/// listed in the visited nodes, it knows it has already seen and rebroadcast this request, +/// and threfore ignores it. This prevents broadcast storms. +/// When a node receives a RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST it can use the list of +/// nodes aready visited to deduce routes back towards the originating (requesting node). +/// This also means that when the destination node of the request is reached, it (and all +/// the previous nodes the request visited) will have a route back to the originating node. +/// This means the unicast RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE +/// reply will be routed successfully back to the original route requester. +/// +/// The RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE sent back by the destination node contains +/// the full list of nodes that were visited on the way to the destination. +/// Therefore, intermediate nodes that route the reply back towards the originating node can use the +/// node list in the reply to deduce routes to all the nodes between it and the destination node. +/// +/// Therefore, RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST and +/// RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE together ensure the original requester and all +/// the intermediate nodes know how to route to the source and destination nodes and every node along the path. +/// +/// Note that there is a race condition here that can effect routing on multipath routes. For example, +/// if the route to the destination can traverse several paths, last reply from the destination +/// will be the one used. +/// +/// \par Route Failure +/// +/// RHRouter (and therefore RHMesh) use reliable hop-to-hop delivery of messages using +/// hop-to-hop acknowledgements, but not end-to-end acknowledgements. When sendtoWait() returns, +/// you know that the message has been delivered to the next hop, but not if it is (or even if it can be) +/// delivered to the destination node. If during the course of hop-to-hop routing of a message, +/// one of the intermediate RHMesh nodes finds it cannot deliver to the next hop +/// (say due to a lost route or no acknwledgement from the next hop), it replies to the +/// originator with a unicast MeshRouteFailureMessage RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE message. +/// Intermediate nodes (on the way beack to the originator) +/// and the originating node use this message to delete the route to the destination +/// node of the original message. This means that if a route to a destination becomes unusable +/// (either because an intermediate node is off the air, or has moved out of range) a new route +/// will be established the next time a message is to be sent. +/// +/// \par Message Format +/// +/// RHMesh uses a number of message formats layered on top of RHRouter: +/// - MeshApplicationMessage (message type RH_MESH_MESSAGE_TYPE_APPLICATION). +/// Carries an application layer message for the caller of RHMesh +/// - MeshRouteDiscoveryMessage (message types RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST +/// and RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE). Carries Route Discovery messages +/// (broadcast) and replies (unicast). +/// - MeshRouteFailureMessage (message type RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE) Informs nodes of +/// route failures. +/// +/// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers +/// (see http://www.hoperf.com) +/// +/// \par Memory +/// +/// RHMesh programs require significant amount of SRAM, often approaching 2kbytes, +/// which is beyond or at the limits of some Arduinos and other processors. Programs +/// with additional software besides basic RHMesh programs may well require even more. If you have insufficient +/// SRAM for your program, it may result in failure to run, or wierd crashes and other hard to trace behaviour. +/// In this event you should consider a processor with more SRAM, such as the MotienoMEGA with 16k +/// (https://lowpowerlab.com/shop/moteinomega) or others. +/// +/// \par Performance +/// This class (in the interests of simple implemtenation and low memory use) does not have +/// message queueing. This means that only one message at a time can be handled. Message transmission +/// failures can have a severe impact on network performance. +/// If you need high performance mesh networking under all conditions consider XBee or similar. +class RHMesh : public RHRouter +{ +public: + + /// The maximum length permitted for the application payload data in a RHMesh message + #define RH_MESH_MAX_MESSAGE_LEN (RH_ROUTER_MAX_MESSAGE_LEN - sizeof(RHMesh::MeshMessageHeader)) + + /// Structure of the basic RHMesh header. + typedef struct + { + uint8_t msgType; ///< Type of RHMesh message, one of RH_MESH_MESSAGE_TYPE_* + } MeshMessageHeader; + + /// Signals an application layer message for the caller of RHMesh + typedef struct + { + MeshMessageHeader header; ///< msgType = RH_MESH_MESSAGE_TYPE_APPLICATION + uint8_t data[RH_MESH_MAX_MESSAGE_LEN]; ///< Application layer payload data + } MeshApplicationMessage; + + /// Signals a route discovery request or reply (At present only supports physical dest addresses of length 1 octet) + typedef struct + { + MeshMessageHeader header; ///< msgType = RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_* + uint8_t destlen; ///< Reserved. Must be 1 + uint8_t dest; ///< The address of the destination node whose route is being sought + uint8_t route[RH_MESH_MAX_MESSAGE_LEN - 2]; ///< List of node addresses visited so far. Length is implcit + } MeshRouteDiscoveryMessage; + + /// Signals a route failure + typedef struct + { + MeshMessageHeader header; ///< msgType = RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE + uint8_t dest; ///< The address of the destination towards which the route failed + } MeshRouteFailureMessage; + + /// Constructor. + /// \param[in] driver The RadioHead driver to use to transport messages. + /// \param[in] thisAddress The address to assign to this node. Defaults to 0 + RHMesh(RHGenericDriver& driver, uint8_t thisAddress = 0); + + /// Sends a message to the destination node. Initialises the RHRouter message header + /// (the SOURCE address is set to the address of this node, HOPS to 0) and calls + /// route() which looks up in the routing table the next hop to deliver to. + /// If no route is known, initiates route discovery and waits for a reply. + /// Then sends the message to the next hop + /// Then waits for an acknowledgement from the next hop + /// (but not from the destination node (if that is different). + /// \param [in] buf The application message data + /// \param [in] len Number of octets in the application message data. 0 is permitted + /// \param [in] dest The destination node address. If the address is RH_BROADCAST_ADDRESS (255) + /// the message will be broadcast to all the nearby nodes, but not routed or relayed. + /// \param [in] flags Optional flags for use by subclasses or application layer, + /// delivered end-to-end to the dest address. The receiver can recover the flags with recvFromAck(). + /// \return The result code: + /// - RH_ROUTER_ERROR_NONE Message was routed and delivered to the next hop + /// (not necessarily to the final dest address) + /// - RH_ROUTER_ERROR_NO_ROUTE There was no route for dest in the local routing table + /// - RH_ROUTER_ERROR_UNABLE_TO_DELIVER Not able to deliver to the next hop + /// (usually because it dod not acknowledge due to being off the air or out of range + uint8_t sendtoWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t flags = 0); + + /// Starts the receiver if it is not running already, processes and possibly routes any received messages + /// addressed to other nodes + /// and delivers any messages addressed to this node. + /// If there is a valid application layer message available for this node (or RH_BROADCAST_ADDRESS), + /// send an acknowledgement to the last hop + /// address (blocking until this is complete), then copy the application message payload data + /// to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length.. + /// If from is not NULL, the originator SOURCE address is placed in *source. + /// If to is not NULL, the DEST address is placed in *dest. This might be this nodes address or + /// RH_BROADCAST_ADDRESS. + /// This is the preferred function for getting messages addressed to this node. + /// If the message is not a broadcast, acknowledge to the sender before returning. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \param[in] source If present and not NULL, the referenced uint8_t will be set to the SOURCE address + /// \param[in] dest If present and not NULL, the referenced uint8_t will be set to the DEST address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// \param[in] hops If present and not NULL, the referenced uint8_t will be set to the HOPS + /// (not just those addressed to this node). + /// \return true if a valid message was received for this node and copied to buf + bool recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* source = NULL, uint8_t* dest = NULL, uint8_t* id = NULL, uint8_t* flags = NULL, uint8_t* hops = NULL); + + /// Starts the receiver if it is not running already. + /// Similar to recvfromAck(), this will block until either a valid application layer + /// message available for this node + /// or the timeout expires. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \param[in] timeout Maximum time to wait in milliseconds + /// \param[in] source If present and not NULL, the referenced uint8_t will be set to the SOURCE address + /// \param[in] dest If present and not NULL, the referenced uint8_t will be set to the DEST address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// \param[in] hops If present and not NULL, the referenced uint8_t will be set to the HOPS + /// (not just those addressed to this node). + /// \return true if a valid message was copied to buf + bool recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* source = NULL, uint8_t* dest = NULL, uint8_t* id = NULL, uint8_t* flags = NULL, uint8_t* hops = NULL); + +protected: + + /// Internal function that inspects messages being received and adjusts the routing table if necessary. + /// Called by recvfromAck() immediately after it gets the message from RHReliableDatagram + /// \param [in] message Pointer to the RHRouter message that was received. + /// \param [in] messageLen Length of message in octets + virtual void peekAtMessage(RoutedMessage* message, uint8_t messageLen); + + /// Internal function that inspects messages being received and adjusts the routing table if necessary. + /// This is virtual, which lets subclasses override or intercept the route() function. + /// Called by sendtoWait after the message header has been filled in. + /// \param [in] message Pointer to the RHRouter message to be sent. + /// \param [in] messageLen Length of message in octets + virtual uint8_t route(RoutedMessage* message, uint8_t messageLen); + + /// Try to resolve a route for the given address. Blocks while discovering the route + /// which may take up to 4000 msec. + /// Virtual so subclasses can override. + /// \param [in] address The physical address to resolve + /// \return true if the address was resolved and added to the local routing table + virtual bool doArp(uint8_t address); + + /// Tests if the given address of length addresslen is indentical to the + /// physical address of this node. + /// RHMesh always implements physical addresses as the 1 octet address of the node + /// given by _thisAddress + /// Called by recvfromAck() to test whether a RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST + /// is for this node. + /// Subclasses may want to override to implement more complicated or longer physical addresses + /// \param [in] address Address of the pyysical addres being tested + /// \param [in] addresslen Lengthof the address in bytes + /// \return true if the physical address of this node is identical to address + virtual bool isPhysicalAddress(uint8_t* address, uint8_t addresslen); + +private: + /// Temporary message buffer + static uint8_t _tmpMessage[RH_ROUTER_MAX_MESSAGE_LEN]; + +}; + +/// @example rf22_mesh_client.ino +/// @example rf22_mesh_server1.ino +/// @example rf22_mesh_server2.ino +/// @example rf22_mesh_server3.ino + +#endif + diff --git a/RHNRFSPIDriver.cpp b/RHNRFSPIDriver.cpp new file mode 100644 index 0000000..46fcfa4 --- /dev/null +++ b/RHNRFSPIDriver.cpp @@ -0,0 +1,139 @@ +// RHNRFSPIDriver.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RHNRFSPIDriver.cpp,v 1.6 2020/08/04 09:02:14 mikem Exp $ + +#include + +RHNRFSPIDriver::RHNRFSPIDriver(uint8_t slaveSelectPin, RHGenericSPI& spi) + : + _spi(spi), + _slaveSelectPin(slaveSelectPin) +{ +} + +bool RHNRFSPIDriver::init() +{ + // start the SPI library with the default speeds etc: + // On Arduino Due this defaults to SPI1 on the central group of 6 SPI pins + _spi.begin(); + + // Initialise the slave select pin + // On Maple, this must be _after_ spi.begin + pinMode(_slaveSelectPin, OUTPUT); + digitalWrite(_slaveSelectPin, HIGH); + + delay(100); + return true; +} + +// Low level commands for interfacing with the device +uint8_t RHNRFSPIDriver::spiCommand(uint8_t command) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + status = _spi.transfer(command); +#else + beginTransaction(); + status = _spi.transfer(command); + endTransaction(); +#endif + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RHNRFSPIDriver::spiRead(uint8_t reg) +{ + uint8_t val = 0; + ATOMIC_BLOCK_START; +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + val = _spi.transfer2B(reg, 0); // Send the address, discard the status, The written value is ignored, reg value is read +#else + beginTransaction(); + _spi.transfer(reg); // Send the address, discard the status + val = _spi.transfer(0); // The written value is ignored, reg value is read + endTransaction(); +#endif + ATOMIC_BLOCK_END; + return val; +} + +uint8_t RHNRFSPIDriver::spiWrite(uint8_t reg, uint8_t val) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + status = _spi.transfer2B(reg, val); +#else + beginTransaction(); + status = _spi.transfer(reg); // Send the address + _spi.transfer(val); // New value follows +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY) + // Sigh: some devices, such as MRF89XA dont work properly on Teensy 3.1: + // At 1MHz, the clock returns low _after_ slave select goes high, which prevents SPI + // write working. This delay gixes time for the clock to return low. +delayMicroseconds(5); +#endif + endTransaction(); +#endif + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RHNRFSPIDriver::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + status = _spi.spiBurstRead(reg, dest, len); +#else + beginTransaction(); + status = _spi.transfer(reg); // Send the start address + while (len--) + *dest++ = _spi.transfer(0); + endTransaction(); +#endif + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RHNRFSPIDriver::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + status = _spi.spiBurstWrite(reg, src, len); +#else + beginTransaction(); + status = _spi.transfer(reg); // Send the start address + while (len--) + _spi.transfer(*src++); + endTransaction(); +#endif + ATOMIC_BLOCK_END; + return status; +} + +void RHNRFSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin) +{ + _slaveSelectPin = slaveSelectPin; +} + +void RHNRFSPIDriver::spiUsingInterrupt(uint8_t interruptNumber) +{ + _spi.usingInterrupt(interruptNumber); +} + +void RHNRFSPIDriver::beginTransaction() +{ + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); +} + +void RHNRFSPIDriver::endTransaction() +{ + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); +} + diff --git a/RHNRFSPIDriver.h b/RHNRFSPIDriver.h new file mode 100644 index 0000000..7a55d3b --- /dev/null +++ b/RHNRFSPIDriver.h @@ -0,0 +1,109 @@ +// RHNRFSPIDriver.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RHNRFSPIDriver.h,v 1.5 2017/11/06 00:04:08 mikem Exp $ + +#ifndef RHNRFSPIDriver_h +#define RHNRFSPIDriver_h + +#include +#include + +class RHGenericSPI; + +///////////////////////////////////////////////////////////////////// +/// \class RHNRFSPIDriver RHNRFSPIDriver.h +/// \brief Base class for RadioHead drivers that use the SPI bus +/// to communicate with its NRF family transport hardware. +/// +/// This class can be subclassed by Drivers that require to use the SPI bus. +/// It can be configured to use either the RHHardwareSPI class (if there is one available on the platform) +/// of the bitbanged RHSoftwareSPI class. The dfault behaviour is to use a pre-instantiated built-in RHHardwareSPI +/// interface. +/// +/// SPI bus access is protected by ATOMIC_BLOCK_START and ATOMIC_BLOCK_END, which will ensure interrupts +/// are disabled during access. +/// +/// The read and write routines use SPI conventions as used by Nordic NRF radios and otehr devices, +/// but these can be overriden +/// in subclasses if necessary. +/// +/// Application developers are not expected to instantiate this class directly: +/// it is for the use of Driver developers. +class RHNRFSPIDriver : public RHGenericDriver +{ +public: + /// Constructor + /// \param[in] slaveSelectPin The controller pin to use to select the desired SPI device. This pin will be driven LOW + /// during SPI communications with the SPI device that uis iused by this Driver. + /// \param[in] spi Reference to the SPI interface to use. The default is to use a default built-in Hardware interface. + RHNRFSPIDriver(uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + bool init(); + + /// Sends a single command to the device + /// \param[in] command The command code to send to the device. + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiCommand(uint8_t command); + + /// Reads a single register from the SPI device + /// \param[in] reg Register number + /// \return The value of the register + uint8_t spiRead(uint8_t reg); + + /// Writes a single byte to the SPI device + /// \param[in] reg Register number + /// \param[in] val The value to write + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiWrite(uint8_t reg, uint8_t val); + + /// Reads a number of consecutive registers from the SPI device using burst read mode + /// \param[in] reg Register number of the first register + /// \param[in] dest Array to write the register values to. Must be at least len bytes + /// \param[in] len Number of bytes to read + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len); + + /// Write a number of consecutive registers using burst write mode + /// \param[in] reg Register number of the first register + /// \param[in] src Array of new register values to write. Must be at least len bytes + /// \param[in] len Number of bytes to write + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len); + + /// Set or change the pin to be used for SPI slave select. + /// This can be called at any time to change the + /// pin that will be used for slave select in subsquent SPI operations. + /// \param[in] slaveSelectPin The pin to use + void setSlaveSelectPin(uint8_t slaveSelectPin); + + /// Set the SPI interrupt number + /// If SPI transactions can occur within an interrupt, tell the low level SPI + /// interface which interrupt is used + /// \param[in] interruptNumber the interrupt number + void spiUsingInterrupt(uint8_t interruptNumber); + +protected: + /// Signal the start of an SPI transaction that must not be interrupted by other SPI actions + /// In subclasses that support transactions this will ensure that other SPI transactions + /// are blocked until this one is completed by endTransaction(). + virtual void beginTransaction(); + + /// Signal the end of an SPI transaction + virtual void endTransaction(); + + /// Reference to the RHGenericSPI instance to use to trasnfer data with teh SPI device + RHGenericSPI& _spi; + + /// The pin number of the Slave Select pin that is used to select the desired device. + uint8_t _slaveSelectPin; +}; + +#endif diff --git a/RHReliableDatagram.cpp b/RHReliableDatagram.cpp new file mode 100644 index 0000000..ef48cd6 --- /dev/null +++ b/RHReliableDatagram.cpp @@ -0,0 +1,218 @@ +// RHReliableDatagram.cpp +// +// Define addressed datagram +// +// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers +// (see http://www.hoperf.com) +// RHDatagram will be received only by the addressed node or all nodes within range if the +// to address is RH_BROADCAST_ADDRESS +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHReliableDatagram.cpp,v 1.18 2018/11/08 02:31:43 mikem Exp $ + +#include + +//////////////////////////////////////////////////////////////////// +// Constructors +RHReliableDatagram::RHReliableDatagram(RHGenericDriver& driver, uint8_t thisAddress) + : RHDatagram(driver, thisAddress) +{ + _retransmissions = 0; + _lastSequenceNumber = 0; + _timeout = RH_DEFAULT_TIMEOUT; + _retries = RH_DEFAULT_RETRIES; + memset(_seenIds, 0, sizeof(_seenIds)); +} + +//////////////////////////////////////////////////////////////////// +// Public methods +void RHReliableDatagram::setTimeout(uint16_t timeout) +{ + _timeout = timeout; +} + +//////////////////////////////////////////////////////////////////// +void RHReliableDatagram::setRetries(uint8_t retries) +{ + _retries = retries; +} + +//////////////////////////////////////////////////////////////////// +uint8_t RHReliableDatagram::retries() +{ + return _retries; +} + +//////////////////////////////////////////////////////////////////// +bool RHReliableDatagram::sendtoWait(uint8_t* buf, uint8_t len, uint8_t address) +{ + // Assemble the message + uint8_t thisSequenceNumber = ++_lastSequenceNumber; + uint8_t retries = 0; + while (retries++ <= _retries) + { + setHeaderId(thisSequenceNumber); + + // Set and clear header flags depending on if this is an + // initial send or a retry. + uint8_t headerFlagsToSet = RH_FLAGS_NONE; + // Always clear the ACK flag + uint8_t headerFlagsToClear = RH_FLAGS_ACK; + if (retries == 1) { + // On an initial send, clear the RETRY flag in case + // it was previously set + headerFlagsToClear |= RH_FLAGS_RETRY; + } else { + // Not an initial send, set the RETRY flag + headerFlagsToSet = RH_FLAGS_RETRY; + } + setHeaderFlags(headerFlagsToSet, headerFlagsToClear); + + sendto(buf, len, address); + waitPacketSent(); + + // Never wait for ACKS to broadcasts: + if (address == RH_BROADCAST_ADDRESS) + return true; + + if (retries > 1) + _retransmissions++; + unsigned long thisSendTime = millis(); // Timeout does not include original transmit time + + // Compute a new timeout, random between _timeout and _timeout*2 + // This is to prevent collisions on every retransmit + // if 2 nodes try to transmit at the same time +#if (RH_PLATFORM == RH_PLATFORM_RASPI) // use standard library random(), bugs in random(min, max) + uint16_t timeout = _timeout + (_timeout * (random() & 0xFF) / 256); +#else + uint16_t timeout = _timeout + (_timeout * random(0, 256) / 256); +#endif + int32_t timeLeft; + while ((timeLeft = timeout - (millis() - thisSendTime)) > 0) + { + if (waitAvailableTimeout(timeLeft)) + { + uint8_t from, to, id, flags; + if (recvfrom(0, 0, &from, &to, &id, &flags)) // Discards the message + { + // Now have a message: is it our ACK? + if ( from == address + && to == _thisAddress + && (flags & RH_FLAGS_ACK) + && (id == thisSequenceNumber)) + { + // Its the ACK we are waiting for + return true; + } + else if ( !(flags & RH_FLAGS_ACK) + && (id == _seenIds[from])) + { + // This is a request we have already received. ACK it again + acknowledge(id, from); + } + // Else discard it + } + } + // Not the one we are waiting for, maybe keep waiting until timeout exhausted + YIELD; + } + // Timeout exhausted, maybe retry + YIELD; + } + // Retries exhausted + return false; +} + +//////////////////////////////////////////////////////////////////// +bool RHReliableDatagram::recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags) +{ + uint8_t _from; + uint8_t _to; + uint8_t _id; + uint8_t _flags; + // Get the message before its clobbered by the ACK (shared rx and tx buffer in some drivers + if (available() && recvfrom(buf, len, &_from, &_to, &_id, &_flags)) + { + // Never ACK an ACK + if (!(_flags & RH_FLAGS_ACK)) + { + // Its a normal message not an ACK + if (_to ==_thisAddress) + { + // In some networks with mixed processor speeds, may need to delay + // the ack with a define in say platformio.ini: + #if defined(RH_ACK_DELAY) + // a fast processor should wait a little before sending the acknowledge message + unsigned long ts = millis(); + while ((millis() - ts) <= RH_ACK_DELAY) + YIELD; + #endif + + // Its for this node and + // Its not a broadcast, so ACK it + // Acknowledge message with ACK set in flags and ID set to received ID + acknowledge(_id, _from); + } + // Filter out retried messages that we have seen before. This explicitly + // only filters out messages that are marked as retries to protect against + // the scenario where a transmitting device sends just one message and + // shuts down between transmissions. Devices that do this will report the + // the same ID each time since their internal sequence number will reset + // to zero each time the device starts up. + if ((RH_ENABLE_EXPLICIT_RETRY_DEDUP && !(_flags & RH_FLAGS_RETRY)) || _id != _seenIds[_from]) + { + if (from) *from = _from; + if (to) *to = _to; + if (id) *id = _id; + if (flags) *flags = _flags; + _seenIds[_from] = _id; + return true; + } + // Else just re-ack it and wait for a new one + } + } + // No message for us available + return false; +} + +bool RHReliableDatagram::recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags) +{ + unsigned long starttime = millis(); + int32_t timeLeft; + while ((timeLeft = timeout - (millis() - starttime)) > 0) + { + if (waitAvailableTimeout(timeLeft)) + { + if (recvfromAck(buf, len, from, to, id, flags)) + return true; + } + YIELD; + } + return false; +} + +uint32_t RHReliableDatagram::retransmissions() +{ + return _retransmissions; +} + +void RHReliableDatagram::resetRetransmissions() +{ + _retransmissions = 0; +} + +void RHReliableDatagram::acknowledge(uint8_t id, uint8_t from) +{ + setHeaderId(id); + setHeaderFlags(RH_FLAGS_ACK); + // We would prefer to send a zero length ACK, + // but if an RH_RF22 receives a 0 length message with a CRC error, it will never receive + // a 0 length message again, until its reset, which makes everything hang :-( + // So we send an ACK of 1 octet + // REVISIT: should we send the RSSI for the information of the sender? + uint8_t ack = '!'; + sendto(&ack, sizeof(ack), from); + waitPacketSent(); +} + diff --git a/RHReliableDatagram.h b/RHReliableDatagram.h new file mode 100644 index 0000000..fd090cf --- /dev/null +++ b/RHReliableDatagram.h @@ -0,0 +1,223 @@ +// RHReliableDatagram.h +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHReliableDatagram.h,v 1.19 2019/07/14 00:18:48 mikem Exp $ + +#ifndef RHReliableDatagram_h +#define RHReliableDatagram_h + +#include + +/// The acknowledgement bit in the header FLAGS. This indicates if the payload is for an +/// ack for a successfully received message. +#define RH_FLAGS_ACK 0x80 +/// The retry bit in the header FLAGS. This indicates that the payload is a retry for a +/// previously sent message. +#define RH_FLAGS_RETRY 0x40 + +/// This macro enables enhanced message deduplication behavior. This currently defaults +/// to 0 (off), but this may change to default to 1 (on) in future releases. Consumers who +/// want to enable this behavior should override this macro in their code and set it to 1. +/// It is most useful where a transmitter periodically wakes up and starts to transmit +/// starting again from the first sequence number. +/// +/// Enhanced deduplication: Only messages containing the retry bit in the header +/// FLAGS will be evaluated for deduplication. This ensures that only messages that are +/// genuine retries will potentially be deduped. Note that this should not be enabled +/// if you will receive messages from devices using older versions of this library that +/// do not support the RETRY header. If you do, deduping of messages will be broken. +#ifndef RH_ENABLE_EXPLICIT_RETRY_DEDUP + #define RH_ENABLE_EXPLICIT_RETRY_DEDUP 0 +#endif + +/// the default retry timeout in milliseconds +#define RH_DEFAULT_TIMEOUT 200 + +/// The default number of retries +#define RH_DEFAULT_RETRIES 3 + +///////////////////////////////////////////////////////////////////// +/// \class RHReliableDatagram RHReliableDatagram.h +/// \brief RHDatagram subclass for sending addressed, acknowledged, retransmitted datagrams. +/// +/// Manager class that extends RHDatagram to define addressed, reliable datagrams with acknowledgement and retransmission. +/// Based on RHDatagram, adds flags and sequence numbers. RHReliableDatagram is reliable in the sense +/// that messages are acknowledged by the recipient, and unacknowledged messages are retransmitted until acknowledged or the +/// retries are exhausted. +/// When addressed messages are sent (by sendtoWait()), it will wait for an ack, and retransmit +/// after timeout until an ack is received or retries are exhausted. +/// When addressed messages are collected by the application (by recvfromAck()), +/// an acknowledgement is automatically sent to the sender. +/// +/// You can use RHReliableDatagram to send broadcast messages, with a TO address of RH_BROADCAST_ADDRESS, +/// however broadcasts are not acknowledged or retransmitted and are therefore NOT actually reliable. +/// +/// The retransmit timeout is randomly varied between timeout and timeout*2 to prevent collisions on all +/// retries when 2 nodes happen to start sending at the same time . +/// +/// Each new message sent by sendtoWait() has its ID incremented. +/// +/// An ack consists of a message with: +/// - TO set to the from address of the original message +/// - FROM set to this node address +/// - ID set to the ID of the original message +/// - FLAGS with the RH_FLAGS_ACK bit set +/// - 1 octet of payload containing ASCII '!' (since some drivers cannot handle 0 length payloads) +/// +/// \par Media Access Strategy +/// +/// RHReliableDatagram and the underlying drivers always transmit as soon as +/// sendtoWait() is called. RHReliableDatagram waits for an acknowledgement, +/// and if one is not received after a timeout period the message is +/// transmitted again. If no acknowledgement is received after several +/// retries, the transmissions is deemed to have failed. +/// No contention for media is detected. +/// This will be recognised as "pure ALOHA". +/// The addition of Clear Channel Assessment (CCA) is desirable and planned. +/// +/// There is no message queuing or threading in RHReliableDatagram. +/// sendtoWait() waits until an acknowledgement is received, retransmitting +/// up to (by default) 3 retries time with a default 200ms timeout. +/// During this transmit-acknowledge phase, any received message (other than the expected +/// acknowledgement) will be ignored. Your sketch will be unresponsive to new messages +/// until an acknowledgement is received or the retries are exhausted. +/// Central server-type sketches should be very cautious about their +/// retransmit strategy and configuration lest they hang for a long time +/// trying to reply to clients that are unreachable. +/// +/// Caution: if you have a radio network with a mixture of slow and fast +/// processors and ReliableDatagrams, you may be affected by race conditions +/// where the fast processor acknowledges a message before the sender is ready +/// to process the acknowledgement. Best practice is to use the same processors (and +/// radios) throughout your network. +/// +class RHReliableDatagram : public RHDatagram +{ +public: + /// Constructor. + /// \param[in] driver The RadioHead driver to use to transport messages. + /// \param[in] thisAddress The address to assign to this node. Defaults to 0 + RHReliableDatagram(RHGenericDriver& driver, uint8_t thisAddress = 0); + + /// Sets the minimum retransmit timeout. If sendtoWait is waiting for an ack + /// longer than this time (in milliseconds), + /// it will retransmit the message. Defaults to 200ms. The timeout is measured from the end of + /// transmission of the message. It must be at least longer than the the transmit + /// time of the acknowledgement (preamble+6 octets) plus the latency/poll time of the receiver. + /// For fast modulation schemes you can considerably shorten this time. + /// Caution: if you are using slow packet rates and long packets + /// you may need to change the timeout for reliable operations. + /// The actual timeout is randomly varied between timeout and timeout*2. + /// \param[in] timeout The new timeout period in milliseconds + void setTimeout(uint16_t timeout); + + /// Sets the maximum number of retries. Defaults to 3 at construction time. + /// If set to 0, each message will only ever be sent once. + /// sendtoWait will give up and return false if there is no ack received after all transmissions time out + /// and the retries count is exhausted. + /// param[in] retries The maximum number a retries. + void setRetries(uint8_t retries); + + /// Returns the currently configured maximum retries count. + /// Can be changed with setRetries(). + /// \return The currently configured maximum number of retries. + uint8_t retries(); + + /// Send the message (with retries) and waits for an ack. Returns true if an acknowledgement is received. + /// Synchronous: any message other than the desired ACK received while waiting is discarded. + /// Blocks until an ACK is received or all retries are exhausted (ie up to retries*timeout milliseconds). + /// If the destination address is the broadcast address RH_BROADCAST_ADDRESS (255), the message will + /// be sent as a broadcast, but receiving nodes do not acknowledge, and sendtoWait() returns true immediately + /// without waiting for any acknowledgements. + /// \param[in] address The address to send the message to. + /// \param[in] buf Pointer to the binary message to send + /// \param[in] len Number of octets to send + /// \return true if the message was transmitted and an acknowledgement was received. + bool sendtoWait(uint8_t* buf, uint8_t len, uint8_t address); + + /// If there is a valid message available for this node, send an acknowledgement to the SRC + /// address (blocking until this is complete), then copy the message to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length. + /// If from is not NULL, the SRC address is placed in *from. + /// If to is not NULL, the DEST address is placed in *to. + /// This is the preferred function for getting messages addressed to this node. + /// If the message is not a broadcast, acknowledge to the sender before returning. + /// You should be sure to call this function frequently enough to not miss any messages. + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \param[in] from If present and not NULL, the referenced uint8_t will be set to the SRC address + /// \param[in] to If present and not NULL, the referenced uint8_t will be set to the DEST address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// (not just those addressed to this node). + /// \return true if a valid message was copied to buf. False if + /// - 1. There was no message received and waiting to be collected, or + /// - 2. There was a message received but it was not addressed to this node, or + /// - 3. There was a correctly addressed message but it was a duplicate of an earlier correctly received message + bool recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* from = NULL, uint8_t* to = NULL, uint8_t* id = NULL, uint8_t* flags = NULL); + + /// Similar to recvfromAck(), this will block until either a valid message available for this node + /// or the timeout expires. Starts the receiver automatically. + /// You should be sure to call this function frequently enough to not miss any messages. + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \param[in] timeout Maximum time to wait in milliseconds + /// \param[in] from If present and not NULL, the referenced uint8_t will be set to the SRC address + /// \param[in] to If present and not NULL, the referenced uint8_t will be set to the DEST address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// (not just those addressed to this node). + /// \return true if a valid message was copied to buf + bool recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* from = NULL, uint8_t* to = NULL, uint8_t* id = NULL, uint8_t* flags = NULL); + + /// Returns the number of retransmissions + /// we have had to send since starting or since the last call to resetRetransmissions(). + /// \return The number of retransmissions since initialisation. + uint32_t retransmissions(); + + /// Resets the count of the number of retransmissions + /// to 0. + void resetRetransmissions(); + +protected: + /// Send an ACK for the message id to the given from address + /// Blocks until the ACK has been sent + void acknowledge(uint8_t id, uint8_t from); + + /// Checks whether the message currently in the Rx buffer is a new message, not previously received + /// based on the from address and the sequence. If it is new, it is acknowledged and returns true + /// \return true if there is a message received and it is a new message + bool haveNewMessage(); + +private: + /// Count of retransmissions we have had to send + uint32_t _retransmissions; + + /// The last sequence number to be used + /// Defaults to 0 + uint8_t _lastSequenceNumber; + + // Retransmit timeout (milliseconds) + /// Defaults to 200 + uint16_t _timeout; + + // Retries (0 means one try only) + /// Defaults to 3 + uint8_t _retries; + + /// Array of the last seen sequence number indexed by node address that sent it + /// It is used for duplicate detection. Duplicated messages are re-acknowledged when received + /// (this is generally due to lost ACKs, causing the sender to retransmit, even though we have already + /// received that message) + uint8_t _seenIds[256]; +}; + +/// @example rf22_reliable_datagram_client.ino +/// @example rf22_reliable_datagram_server.ino + +#endif + diff --git a/RHRouter.cpp b/RHRouter.cpp new file mode 100644 index 0000000..d90639d --- /dev/null +++ b/RHRouter.cpp @@ -0,0 +1,359 @@ +// RHRouter.cpp +// +// Define addressed datagram +// +// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers +// (see http://www.hoperf.com) +// RHDatagram will be received only by the addressed node or all nodes within range if the +// to address is RH_BROADCAST_ADDRESS +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHRouter.cpp,v 1.10 2020/08/04 09:02:14 mikem Exp $ + +#include + +RHRouter::RoutedMessage RHRouter::_tmpMessage; + +//////////////////////////////////////////////////////////////////// +// Constructors +RHRouter::RHRouter(RHGenericDriver& driver, uint8_t thisAddress) + : RHReliableDatagram(driver, thisAddress) +{ + _max_hops = RH_DEFAULT_MAX_HOPS; + _isa_router = true; + clearRoutingTable(); +} + +//////////////////////////////////////////////////////////////////// +// Public methods +bool RHRouter::init() +{ + bool ret = RHReliableDatagram::init(); + if (ret) + _max_hops = RH_DEFAULT_MAX_HOPS; + return ret; +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::setMaxHops(uint8_t max_hops) +{ + _max_hops = max_hops; +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::setIsaRouter(bool isa_router) +{ + _isa_router = isa_router; +} +//////////////////////////////////////////////////////////////////// +void RHRouter::addRouteTo(uint8_t dest, uint8_t next_hop, uint8_t state) +{ + uint8_t i; + + // First look for an existing entry we can update + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + { + if (_routes[i].dest == dest) + { + _routes[i].dest = dest; + _routes[i].next_hop = next_hop; + _routes[i].state = state; + return; + } + } + + // Look for an invalid entry we can use + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + { + if (_routes[i].state == Invalid) + { + _routes[i].dest = dest; + _routes[i].next_hop = next_hop; + _routes[i].state = state; + return; + } + } + + // Need to make room for a new one + retireOldestRoute(); + // Should be an invalid slot now + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + { + if (_routes[i].state == Invalid) + { + _routes[i].dest = dest; + _routes[i].next_hop = next_hop; + _routes[i].state = state; + } + } +} + +//////////////////////////////////////////////////////////////////// +RHRouter::RoutingTableEntry* RHRouter::getRouteTo(uint8_t dest) +{ + uint8_t i; + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + if (_routes[i].dest == dest && _routes[i].state != Invalid) + return &_routes[i]; + return NULL; +} + +//////////////////////////////////////////////////////////////////// +//blase 7/27/20 +//allows one to scan through the routing table. +bool RHRouter::getNextValidRoutingTableEntry(RoutingTableEntry *RTE_p, int *lastIndex_p) +{ + bool retval = false; // default + bool stop = false; + uint8_t startIndex; + + if (*lastIndex_p < 0) + startIndex = 0; + else + startIndex = *lastIndex_p + 1; + + if (startIndex >= RH_ROUTING_TABLE_SIZE) + { + return true; // finished, safety. + } + else + { + uint8_t i = startIndex; + do + { + if (_routes[i].state == Valid) + { + *RTE_p = _routes[i]; + *lastIndex_p = i; + retval = true; //found one + stop = true; + } + else + { + i++; + if (i >= RH_ROUTING_TABLE_SIZE) + stop = true; // no more entries + } + } while (!stop); + } + return retval; +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::deleteRoute(uint8_t index) +{ + // Delete a route by copying following routes on top of it + memcpy(&_routes[index], &_routes[index+1], + sizeof(RoutingTableEntry) * (RH_ROUTING_TABLE_SIZE - index - 1)); + _routes[RH_ROUTING_TABLE_SIZE - 1].state = Invalid; +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::printRoutingTable() +{ +#ifdef RH_HAVE_SERIAL + uint8_t i; + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + { + Serial.print(i, DEC); + Serial.print(" Dest: "); + Serial.print(_routes[i].dest, DEC); + Serial.print(" Next Hop: "); + Serial.print(_routes[i].next_hop, DEC); + Serial.print(" State: "); + Serial.println(_routes[i].state, DEC); + } +#endif +} + +//////////////////////////////////////////////////////////////////// +bool RHRouter::deleteRouteTo(uint8_t dest) +{ + uint8_t i; + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + { + if (_routes[i].dest == dest) + { + deleteRoute(i); + return true; + } + } + return false; +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::retireOldestRoute() +{ + // We just obliterate the first in the table and clear the last + deleteRoute(0); +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::clearRoutingTable() +{ + uint8_t i; + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + _routes[i].state = Invalid; +} + + +uint8_t RHRouter::sendtoWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t flags) +{ + return sendtoFromSourceWait(buf, len, dest, _thisAddress, flags); +} + +//////////////////////////////////////////////////////////////////// +// Waits for delivery to the next hop (but not for delivery to the final destination) +uint8_t RHRouter::sendtoFromSourceWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t source, uint8_t flags) +{ + if (((uint16_t)len + sizeof(RoutedMessageHeader)) > _driver.maxMessageLength()) + return RH_ROUTER_ERROR_INVALID_LENGTH; + + // Construct a RH RouterMessage message + _tmpMessage.header.source = source; + _tmpMessage.header.dest = dest; + _tmpMessage.header.hops = 0; + _tmpMessage.header.id = _lastE2ESequenceNumber++; + _tmpMessage.header.flags = flags; + memcpy(_tmpMessage.data, buf, len); + + return route(&_tmpMessage, sizeof(RoutedMessageHeader)+len); +} + +//////////////////////////////////////////////////////////////////// +uint8_t RHRouter::route(RoutedMessage* message, uint8_t messageLen) +{ + // Reliably deliver it if possible. See if we have a route: + uint8_t next_hop = RH_BROADCAST_ADDRESS; + if (message->header.dest != RH_BROADCAST_ADDRESS) + { + RoutingTableEntry* route = getRouteTo(message->header.dest); + if (!route) + return RH_ROUTER_ERROR_NO_ROUTE; + next_hop = route->next_hop; + } + + if (!RHReliableDatagram::sendtoWait((uint8_t*)message, messageLen, next_hop)) + return RH_ROUTER_ERROR_UNABLE_TO_DELIVER; + + return RH_ROUTER_ERROR_NONE; +} + +//////////////////////////////////////////////////////////////////// +// Subclasses may want to override this to peek at messages going past +void RHRouter::peekAtMessage(RoutedMessage* message, uint8_t messageLen) +{ + // Default does nothing + (void)message; // Not used + (void)messageLen; // Not used +} + +//////////////////////////////////////////////////////////////////// +bool RHRouter::recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* source, uint8_t* dest, uint8_t* id, uint8_t* flags, uint8_t* hops) +{ + uint8_t tmpMessageLen = sizeof(_tmpMessage); + uint8_t _from; + uint8_t _to; + uint8_t _id; + uint8_t _flags; + if (RHReliableDatagram::recvfromAck((uint8_t*)&_tmpMessage, &tmpMessageLen, &_from, &_to, &_id, &_flags)) + { + // Here we simulate networks with limited visibility between nodes + // so we can test routing +#ifdef RH_TEST_NETWORK + if ( +#if RH_TEST_NETWORK==1 + // This network looks like 1-2-3-4 + (_thisAddress == 1 && _from == 2) + || (_thisAddress == 2 && (_from == 1 || _from == 3)) + || (_thisAddress == 3 && (_from == 2 || _from == 4)) + || (_thisAddress == 4 && _from == 3) + +#elif RH_TEST_NETWORK==2 + // This network looks like 1-2-4 + // | | | + // --3-- + (_thisAddress == 1 && (_from == 2 || _from == 3)) + || _thisAddress == 2 + || _thisAddress == 3 + || (_thisAddress == 4 && (_from == 2 || _from == 3)) + +#elif RH_TEST_NETWORK==3 + // This network looks like 1-2-4 + // | | + // --3-- + (_thisAddress == 1 && (_from == 2 || _from == 3)) + || (_thisAddress == 2 && (_from == 1 || _from == 4)) + || (_thisAddress == 3 && (_from == 1 || _from == 4)) + || (_thisAddress == 4 && (_from == 2 || _from == 3)) + +#elif RH_TEST_NETWORK==4 + // This network looks like 1-2-3 + // | + // 4 + (_thisAddress == 1 && _from == 2) + || _thisAddress == 2 + || (_thisAddress == 3 && _from == 2) + || (_thisAddress == 4 && _from == 2) + +#endif + ) + { + // OK + } + else + { + return false; // Pretend we got nothing + } +#endif + + peekAtMessage(&_tmpMessage, tmpMessageLen); + // See if its for us or has to be routed + if (_tmpMessage.header.dest == _thisAddress || _tmpMessage.header.dest == RH_BROADCAST_ADDRESS) + { + // Deliver it here + if (source) *source = _tmpMessage.header.source; + if (dest) *dest = _tmpMessage.header.dest; + if (id) *id = _tmpMessage.header.id; + if (flags) *flags = _tmpMessage.header.flags; + if (hops) *hops = _tmpMessage.header.hops; + uint8_t msgLen = tmpMessageLen - sizeof(RoutedMessageHeader); + if (*len > msgLen) + *len = msgLen; + memcpy(buf, _tmpMessage.data, *len); + return true; // Its for you! + } + else if ( _tmpMessage.header.dest != RH_BROADCAST_ADDRESS + && _tmpMessage.header.hops++ < _max_hops) + { + // Maybe it has to be routed to the next hop + // REVISIT: if it fails due to no route or unable to deliver to the next hop, + // tell the originator. BUT HOW? + + // If we are forwarding packets, do so. Otherwise, drop. + if (_isa_router) + route(&_tmpMessage, tmpMessageLen); + } + // Discard it and maybe wait for another + } + return false; +} + +//////////////////////////////////////////////////////////////////// +bool RHRouter::recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* source, uint8_t* dest, uint8_t* id, uint8_t* flags, uint8_t* hops) +{ + unsigned long starttime = millis(); + int32_t timeLeft; + while ((timeLeft = timeout - (millis() - starttime)) > 0) + { + if (waitAvailableTimeout(timeLeft)) + { + if (recvfromAck(buf, len, source, dest, id, flags, hops)) + return true; + } + YIELD; + } + return false; +} + diff --git a/RHRouter.h b/RHRouter.h new file mode 100644 index 0000000..888f9cf --- /dev/null +++ b/RHRouter.h @@ -0,0 +1,351 @@ +// RHRouter.h +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHRouter.h,v 1.13 2020/08/04 09:02:14 mikem Exp $ + +#ifndef RHRouter_h +#define RHRouter_h + +#include + +// Default max number of hops we will route +#define RH_DEFAULT_MAX_HOPS 30 + +// The default size of the routing table we keep +#define RH_ROUTING_TABLE_SIZE 10 + +// Error codes +#define RH_ROUTER_ERROR_NONE 0 +#define RH_ROUTER_ERROR_INVALID_LENGTH 1 +#define RH_ROUTER_ERROR_NO_ROUTE 2 +#define RH_ROUTER_ERROR_TIMEOUT 3 +#define RH_ROUTER_ERROR_NO_REPLY 4 +#define RH_ROUTER_ERROR_UNABLE_TO_DELIVER 5 + +// This size of RH_ROUTER_MAX_MESSAGE_LEN is OK for Arduino Mega, but too big for +// Duemilanove. Size of 50 works with the sample router programs on Duemilanove. +#define RH_ROUTER_MAX_MESSAGE_LEN (RH_MAX_MESSAGE_LEN - sizeof(RHRouter::RoutedMessageHeader)) +//#define RH_ROUTER_MAX_MESSAGE_LEN 50 + +// These allow us to define a simulated network topology for testing purposes +// See RHRouter.cpp for details +//#define RH_TEST_NETWORK 1 +//#define RH_TEST_NETWORK 2 +//#define RH_TEST_NETWORK 3 +//#define RH_TEST_NETWORK 4 + +///////////////////////////////////////////////////////////////////// +/// \class RHRouter RHRouter.h +/// \brief RHReliableDatagram subclass for sending addressed, optionally acknowledged datagrams +/// multi-hop routed across a network. +/// +/// This is a Manager class that extends RHReliableDatagram to handle addressed messages +/// that are reliably transmitted and routed across a network of multiple RHRouter nodes. +/// Each message is transmitted reliably +/// between each hop in order to get from the source node to the destination node. +/// +/// With RHRouter, routes are hard wired. This means that each node must have programmed +/// in it how to reach each of the other nodes it will be trying to communicate with. +/// This means you must specify the next-hop node address for each of the destination nodes, +/// using the addRouteTo() function. +/// +/// When sendtoWait() is called with a new message to deliver, and the destination address, +/// RHRouter looks up the next hop node for the destination node. It then uses +/// RHReliableDatagram to (reliably) deliver the message to the next hop +/// (which is expected also to be running an RHRouter). If that next-hop node is not +/// the final destination, it will also look up the next hop for the destination node and +/// (reliably) deliver the message to the next hop. By this method, messages can be delivered +/// across a network of nodes, even if each node cannot hear all of the others in the network. +/// Each time a message is received for another node and retransmitted to the next hop, +/// the HOPS field in the header is incremented. If a message is received for routing to another node +/// which has exceed the routers max_hops, the message will be dropped and ignored. +/// This helps prevent infinite routing loops. +/// +/// RHRouter supports messages with a dest of RH_BROADCAST_ADDRESS. Such messages are not routed, +/// and are broadcast (once) to all nodes within range. +/// +/// The recvfromAck() function is responsible not just for receiving and delivering +/// messages addressed to this node (or RH_BROADCAST_ADDRESS), but +/// it is also responsible for routing other message to their next hop. This means that it is important to +/// call recvfromAck() or recvfromAckTimeout() frequently in your main loop. recvfromAck() will return +/// false if it receives a message but it is not for this node. +/// +/// RHRouter does not provide reliable end-to-end delivery, but uses reliable hop-to-hop delivery. +/// If a message is unable to be delivered to an end node during to a delivery failure between 2 hops, +/// the source node will not be told about it. +/// +/// Note: This class is most useful for networks of nodes that are essentially static +/// (i.e. the nodes dont move around), and for which the +/// routing never changes. If that is not the case for your proposed network, see RHMesh instead. +/// +/// \par The Routing Table +/// +/// The routing table is a local table in RHRouter that holds the information about the next hop node +/// address for each destination address you may want to send a message to. It is your responsibility +/// to make sure every node in an RHRouter network has been configured with a unique address and the +/// routing information so that messages are correctly routed across the network from source node to +/// destination node. This is usually done once in setup() by calling addRouteTo(). +/// The hardwired routing will in general be different on each node, and will depend on the physical +/// topololgy of the network. +/// You can also use addRouteTo() to change a route and +/// deleteRouteTo() to delete a route at run time. Youcan also clear the entire routing table +/// +/// The Routing Table has limited capacity for entries (defined by RH_ROUTING_TABLE_SIZE, which is 10) +/// if more than RH_ROUTING_TABLE_SIZE are added, the oldest (first) one will be removed by calling +/// retireOldestRoute() +/// +/// \par Message Format +/// +/// RHRouter add to the lower level RHReliableDatagram (and even lower level RH) class message formats. +/// In those lower level classes, the hop-to-hop message headers are in the RH message headers, +/// and are handled automcatically by tyhe RH hardware. +/// RHRouter and its subclasses add an end-to-end addressing header in the payload of the RH message, +/// and before the RHRouter application data. +/// - 1 octet DEST, the destination node address (ie the address of the final +/// destination node for this message) +/// - 1 octet SOURCE, the source node address (ie the address of the originating node that first sent +/// the message). +/// - 1 octet HOPS, the number of hops this message has traversed so far. +/// - 1 octet ID, an incrementing message ID for end-to-end message tracking for use by subclasses. +/// Not used by RHRouter. +/// - 1 octet FLAGS, a bitmask for use by subclasses. Not used by RHRouter. +/// - 0 or more octets DATA, the application payload data. The length of this data is implicit +/// in the length of the entire message. +/// +/// You should be careful to note that there are ID and FLAGS fields in the low level per-hop +/// message header too. These are used only for hop-to-hop, and in general will be different to +/// the ones at the RHRouter level. +/// +/// \par Testing +/// +/// Bench testing of such networks is notoriously difficult, especially simulating limited radio +/// connectivity between some nodes. +/// To assist testing (both during RH development and for your own networks) +/// RHRouter.cpp has the ability to +/// simulate a number of different small network topologies. Each simulated network supports 4 nodes with +/// addresses 1 to 4. It operates by pretending to not hear RH messages from certain other nodes. +/// You can enable testing with a \#define TEST_NETWORK in RHRouter.h +/// The sample programs rf22_mesh_* rely on this feature. +/// +/// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers +/// (see http://www.hoperf.com) +class RHRouter : public RHReliableDatagram +{ +public: + + /// Defines the structure of the RHRouter message header, used to keep track of end-to-end delivery parameters + typedef struct + { + uint8_t dest; ///< Destination node address + uint8_t source; ///< Originator node address + uint8_t hops; ///< Hops traversed so far + uint8_t id; ///< Originator sequence number + uint8_t flags; ///< Originator flags + // Data follows, Length is implicit in the overall message length + } RoutedMessageHeader; + + /// Defines the structure of a RHRouter message + typedef struct + { + RoutedMessageHeader header; ///< end-to-end delivery header + uint8_t data[RH_ROUTER_MAX_MESSAGE_LEN]; ///< Application payload data + } RoutedMessage; + + /// Values for the possible states for routes + typedef enum + { + Invalid = 0, ///< No valid route is known + Discovering, ///< Discovering a route (not currently used) + Valid ///< Route is valid + } RouteState; + + /// Defines an entry in the routing table + typedef struct + { + uint8_t dest; ///< Destination node address + uint8_t next_hop; ///< Send via this next hop address + uint8_t state; ///< State of this route, one of RouteState + } RoutingTableEntry; + + /// Constructor. + /// \param[in] driver The RadioHead driver to use to transport messages. + /// \param[in] thisAddress The address to assign to this node. Defaults to 0 + RHRouter(RHGenericDriver& driver, uint8_t thisAddress = 0); + + /// Initialises this instance and the radio module connected to it. + /// Overrides the init() function in RH. + /// Sets max_hops to the default of RH_DEFAULT_MAX_HOPS (30) + bool init(); + + + /// Sets the flag determining if the node will participate in routing. + /// if isa_router is true, the node will be a full participant. If false the node + /// will only respond to + /// packets directed to its address, and act only as a leaf node in the network. The default is true. + /// \param[in] isa_router true or false + void setIsaRouter(bool isa_router); + + /// Sets the max_hops to the given value + /// This controls the maximum number of hops allowed between source and destination nodes + /// Messages that are not delivered by the time their HOPS field exceeds max_hops on a + /// routing node will be dropped and ignored. + /// \param [in] max_hops The new value for max_hops + void setMaxHops(uint8_t max_hops); + + /// Adds a route to the local routing table, or updates it if already present. + /// If there is not enough room the oldest (first) route will be deleted by calling retireOldestRoute(). + /// \param [in] dest The destination node address. RH_BROADCAST_ADDRESS is permitted. + /// \param [in] next_hop The address of the next hop to send messages destined for dest + /// \param [in] state The satte of the route. Defaults to Valid + void addRouteTo(uint8_t dest, uint8_t next_hop, uint8_t state = Valid); + + /// Finds and returns a RoutingTableEntry for the given destination node + /// \param [in] dest The desired destination node address. + /// \return pointer to a RoutingTableEntry for dest + RoutingTableEntry* getRouteTo(uint8_t dest); + + /// Deletes from the local routing table any route for the destination node. + /// \param [in] dest The destination node address + /// \return true if the route was present + bool deleteRouteTo(uint8_t dest); + + /// Deletes the oldest (first) route from the + /// local routing table + void retireOldestRoute(); + + /// Clears all entries from the + /// local routing table + void clearRoutingTable(); + + /// If RH_HAVE_SERIAL is defined, this will print out the contents of the local + /// routing table using Serial + void printRoutingTable(); + + /// Method for iterating through the current routing table + /// \param [inout] RTE_p If a valid entry is found, the entry is copied to this structure + /// caller is responsible for alloocating and deallocating the structure. + /// \param [inout] lastIndex_p points to the index to start searching from. Set to the + /// index of the next valid route found. Set this to -1 to start the search. + /// \return false if next entry is valid, true if finished with table. + bool getNextValidRoutingTableEntry(RoutingTableEntry *RTE_p, int *lastIndex_p); //blase 7/27/20 + + + /// Sends a message to the destination node. Initialises the RHRouter message header + /// (the SOURCE address is set to the address of this node, HOPS to 0) and calls + /// route() which looks up in the routing table the next hop to deliver to and sends the + /// message to the next hop. Waits for an acknowledgement from the next hop + /// (but not from the destination node (if that is different). + /// \param [in] buf The application message data + /// \param [in] len Number of octets in the application message data. 0 is permitted + /// \param [in] dest The destination node address + /// \param [in] flags Optional flags for use by subclasses or application layer, + /// delivered end-to-end to the dest address. The receiver can recover the flags with recvFromAck(). + /// \return The result code: + /// - RH_ROUTER_ERROR_NONE Message was routed and delivered to the next hop + /// (not necessarily to the final dest address) + /// - RH_ROUTER_ERROR_NO_ROUTE There was no route for dest in the local routing table + /// - RH_ROUTER_ERROR_UNABLE_TO_DELIVER Not able to deliver to the next hop + /// (usually because it dod not acknowledge due to being off the air or out of range + uint8_t sendtoWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t flags = 0); + + /// Similar to sendtoWait() above, but spoofs the source address. + /// For internal use only during routing + /// \param [in] buf The application message data. + /// \param [in] len Number of octets in the application message data. 0 is permitted. + /// \param [in] dest The destination node address. + /// \param [in] source The (fake) originating node address. + /// \param [in] flags Optional flags for use by subclasses or application layer, + /// delivered end-to-end to the dest address. The receiver can recover the flags with recvFromAck(). + /// \return The result code: + /// - RH_ROUTER_ERROR_NONE Message was routed and deliverd to the next hop + /// (not necessarily to the final dest address) + /// - RH_ROUTER_ERROR_NO_ROUTE There was no route for dest in the local routing table + /// - RH_ROUTER_ERROR_UNABLE_TO_DELIVER Noyt able to deliver to the next hop + /// (usually because it dod not acknowledge due to being off the air or out of range + uint8_t sendtoFromSourceWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t source, uint8_t flags = 0); + + /// Starts the receiver if it is not running already. + /// If there is a valid message available for this node (or RH_BROADCAST_ADDRESS), + /// send an acknowledgement to the last hop + /// address (blocking until this is complete), then copy the application message payload data + /// to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length.. + /// If from is not NULL, the originator SOURCE address is placed in *source. + /// If to is not NULL, the DEST address is placed in *dest. This might be this nodes address or + /// RH_BROADCAST_ADDRESS. + /// This is the preferred function for getting messages addressed to this node. + /// If the message is not a broadcast, acknowledge to the sender before returning. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \param[in] source If present and not NULL, the referenced uint8_t will be set to the SOURCE address + /// \param[in] dest If present and not NULL, the referenced uint8_t will be set to the DEST address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// \param[in] hops If present and not NULL, the referenced uint8_t will be set to the HOPS + /// (not just those addressed to this node). + /// \return true if a valid message was recvived for this node copied to buf + bool recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* source = NULL, uint8_t* dest = NULL, uint8_t* id = NULL, uint8_t* flags = NULL, uint8_t* hops = NULL); + + /// Starts the receiver if it is not running already. + /// Similar to recvfromAck(), this will block until either a valid message available for this node + /// or the timeout expires. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \param[in] timeout Maximum time to wait in milliseconds + /// \param[in] source If present and not NULL, the referenced uint8_t will be set to the SOURCE address + /// \param[in] dest If present and not NULL, the referenced uint8_t will be set to the DEST address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// \param[in] hops If present and not NULL, the referenced uint8_t will be set to the HOPS + /// (not just those addressed to this node). + /// \return true if a valid message was copied to buf + bool recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* source = NULL, uint8_t* dest = NULL, uint8_t* id = NULL, uint8_t* flags = NULL, uint8_t* hops = NULL); + +protected: + + /// Lets sublasses peek at messages going + /// past before routing or local delivery. + /// Called by recvfromAck() immediately after it gets the message from RHReliableDatagram + /// \param [in] message Pointer to the RHRouter message that was received. + /// \param [in] messageLen Length of message in octets + virtual void peekAtMessage(RoutedMessage* message, uint8_t messageLen); + + /// Finds the next-hop route and sends the message via RHReliableDatagram::sendtoWait(). + /// This is virtual, which lets subclasses override or intercept the route() function. + /// Called by sendtoWait after the message header has been filled in. + /// \param [in] message Pointer to the RHRouter message to be sent. + /// \param [in] messageLen Length of message in octets + virtual uint8_t route(RoutedMessage* message, uint8_t messageLen); + + /// Deletes a specific rout entry from therouting table + /// \param [in] index The 0 based index of the routing table entry to delete + void deleteRoute(uint8_t index); + + /// The last end-to-end sequence number to be used + /// Defaults to 0 + uint8_t _lastE2ESequenceNumber; + + /// The maximum number of hops permitted in routed messages. + /// If a routed message would exceed this number of hops it is dropped and ignored. + uint8_t _max_hops; + + /// Flag to set if packets are forwarded or not + bool _isa_router; + +private: + + /// Temporary mesage buffer + static RoutedMessage _tmpMessage; + + /// Local routing table + RoutingTableEntry _routes[RH_ROUTING_TABLE_SIZE]; +}; + +/// @example rf22_router_client.ino +/// @example rf22_router_server1.ino +/// @example rf22_router_server2.ino +/// @example rf22_router_server3.ino +#endif + diff --git a/RHSPIDriver.cpp b/RHSPIDriver.cpp new file mode 100644 index 0000000..226b43f --- /dev/null +++ b/RHSPIDriver.cpp @@ -0,0 +1,133 @@ +// RHSPIDriver.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RHSPIDriver.cpp,v 1.13 2020/08/04 09:02:14 mikem Exp $ + +#include + +RHSPIDriver::RHSPIDriver(uint8_t slaveSelectPin, RHGenericSPI& spi) + : + _spi(spi), + _slaveSelectPin(slaveSelectPin) +{ +} + +bool RHSPIDriver::init() +{ + // start the SPI library with the default speeds etc: + // On Arduino Due this defaults to SPI1 on the central group of 6 SPI pins + _spi.begin(); + +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO && defined(ARDUINO_LORA_E5_MINI)) + SubGhz.setResetActive(false); +// while (SubGhz.isBusy()) ; // Wait for radio to wake up, hmm sometimes hangs forever! +#endif + + // Initialise the slave select pin + // On Maple, this must be _after_ spi.begin + + // Sometimes we dont want to work the _slaveSelectPin here + if (_slaveSelectPin != 0xff) + pinMode(_slaveSelectPin, OUTPUT); + + deselectSlave(); + + // This delay is needed for ATMega and maybe some others, but + // 100ms is too long for STM32L0, and somehow can cause the USB interface to fail + // in some versions of the core. +#if (RH_PLATFORM == RH_PLATFORM_STM32L0) && (defined STM32L082xx || defined STM32L072xx) + delay(10); +#else + delay(100); +#endif + + return true; +} + +uint8_t RH_INTERRUPT_ATTR RHSPIDriver::spiRead(uint8_t reg) +{ + uint8_t val = 0; + ATOMIC_BLOCK_START; + beginTransaction(); + _spi.transfer(reg & ~RH_SPI_WRITE_MASK); // Send the address with the write mask off + val = _spi.transfer(0); // The written value is ignored, reg value is read + endTransaction(); + ATOMIC_BLOCK_END; + return val; +} + +uint8_t RH_INTERRUPT_ATTR RHSPIDriver::spiWrite(uint8_t reg, uint8_t val) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; + beginTransaction(); + status = _spi.transfer(reg | RH_SPI_WRITE_MASK); // Send the address with the write mask on + _spi.transfer(val); // New value follows + // Based on https://forum.pjrc.com/attachment.php?attachmentid=10948&d=1499109224 + // Need this delay from some processors when running fast: + delayMicroseconds(1); + endTransaction(); + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RH_INTERRUPT_ATTR RHSPIDriver::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; + beginTransaction(); + status = _spi.transfer(reg & ~RH_SPI_WRITE_MASK); // Send the start address with the write mask off + while (len--) + *dest++ = _spi.transfer(0); + endTransaction(); + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RH_INTERRUPT_ATTR RHSPIDriver::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; + beginTransaction(); + status = _spi.transfer(reg | RH_SPI_WRITE_MASK); // Send the start address with the write mask on + while (len--) + _spi.transfer(*src++); + endTransaction(); + ATOMIC_BLOCK_END; + return status; +} + +void RHSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin) +{ + _slaveSelectPin = slaveSelectPin; +} + +void RHSPIDriver::spiUsingInterrupt(uint8_t interruptNumber) +{ + _spi.usingInterrupt(interruptNumber); +} + +void RHSPIDriver::beginTransaction() +{ + _spi.beginTransaction(); + selectSlave(); +} + +void RHSPIDriver::endTransaction() +{ + deselectSlave(); + _spi.endTransaction(); +} + +// Some platforms (ABZ) need to override just selectSlave and deselectSlave +void RHSPIDriver::selectSlave() +{ + if (_slaveSelectPin != 0xff) + digitalWrite(_slaveSelectPin, LOW); +} + +void RHSPIDriver::deselectSlave() +{ + if (_slaveSelectPin != 0xff) + digitalWrite(_slaveSelectPin, HIGH); +} diff --git a/RHSPIDriver.h b/RHSPIDriver.h new file mode 100644 index 0000000..00d8684 --- /dev/null +++ b/RHSPIDriver.h @@ -0,0 +1,120 @@ +// RHSPIDriver.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RHSPIDriver.h,v 1.16 2020/06/15 23:39:39 mikem Exp $ + +#ifndef RHSPIDriver_h +#define RHSPIDriver_h + +#include +#include + +// This is the bit in the SPI address that marks it as a write +#define RH_SPI_WRITE_MASK 0x80 + +class RHGenericSPI; + +///////////////////////////////////////////////////////////////////// +/// \class RHSPIDriver RHSPIDriver.h +/// \brief Base class for RadioHead drivers that use the SPI bus +/// to communicate with its transport hardware. +/// +/// This class can be subclassed by Drivers that require to use the SPI bus. +/// It can be configured to use either the RHHardwareSPI class (if there is one available on the platform) +/// of the bitbanged RHSoftwareSPI class. The default behaviour is to use a pre-instantiated built-in RHHardwareSPI +/// interface. +/// +/// SPI bus access is protected by ATOMIC_BLOCK_START and ATOMIC_BLOCK_END, which will ensure interrupts +/// are disabled during access. +/// +/// The read and write routines implement commonly used SPI conventions: specifically that the MSB +/// of the first byte transmitted indicates that it is a write and the remaining bits indicate the rehgister to access) +/// This can be overriden +/// in subclasses if necessaryor an alternative class, RHNRFSPIDriver can be used to access devices like +/// Nordic NRF series radios, which have different requirements. +/// +/// Application developers are not expected to instantiate this class directly: +/// it is for the use of Driver developers. +class RHSPIDriver : public RHGenericDriver +{ +public: + /// Constructor + /// \param[in] slaveSelectPin The controler pin to use to select the desired SPI device. This pin will be driven LOW + /// during SPI communications with the SPI device that uis iused by this Driver. + /// If slaveSelectPin is 0xff, then the pin will not be initialised or activated by this class. + /// \param[in] spi Reference to the SPI interface to use. The default is to use a default built-in Hardware interface. + RHSPIDriver(uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + bool init(); + + /// Reads a single register from the SPI device + /// \param[in] reg Register number + /// \return The value of the register + uint8_t spiRead(uint8_t reg); + + /// Writes a single byte to the SPI device + /// \param[in] reg Register number + /// \param[in] val The value to write + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiWrite(uint8_t reg, uint8_t val); + + /// Reads a number of consecutive registers from the SPI device using burst read mode + /// \param[in] reg Register number of the first register + /// \param[in] dest Array to write the register values to. Must be at least len bytes + /// \param[in] len Number of bytes to read + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len); + + /// Write a number of consecutive registers using burst write mode + /// \param[in] reg Register number of the first register + /// \param[in] src Array of new register values to write. Must be at least len bytes + /// \param[in] len Number of bytes to write + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len); + + /// Set or change the pin to be used for SPI slave select. + /// This can be called at any time to change the + /// pin that will be used for slave select in subsquent SPI operations. + /// \param[in] slaveSelectPin The pin to use + void setSlaveSelectPin(uint8_t slaveSelectPin); + + /// Set the SPI interrupt number + /// If SPI transactions can occur within an interrupt, tell the low level SPI + /// interface which interrupt is used + /// \param[in] interruptNumber the interrupt number + void spiUsingInterrupt(uint8_t interruptNumber); + + protected: + + /// Signal the start of an SPI transaction that must not be interrupted by other SPI actions + /// In subclasses that support transactions this will ensure that other SPI transactions + /// are blocked until this one is completed by endTransaction(). + /// Selects the slave with selectSlave() + virtual void beginTransaction(); + + /// Signal the end of an SPI transaction + /// Deelects the slave with deselectSlave() + virtual void endTransaction(); + + // Override this if you need an unusual way of selecting the slave before SPI transactions + // The default uses digitalWrite(_slaveSelectPin, LOW) + virtual void selectSlave(); + + // Override this if you need an unusual way of selecting the slave before SPI transactions + // The default uses digitalWrite(_slaveSelectPin, HIGH) + virtual void deselectSlave(); + + /// Reference to the RHGenericSPI instance to use to transfer data with the SPI device + RHGenericSPI& _spi; + + /// The pin number of the Slave Select pin that is used to select the desired device. + uint8_t _slaveSelectPin; +}; + +#endif diff --git a/RHSUBGHZSPI.cpp b/RHSUBGHZSPI.cpp new file mode 100644 index 0000000..fb878c5 --- /dev/null +++ b/RHSUBGHZSPI.cpp @@ -0,0 +1,8 @@ +// RHSUBGHZSPI.cpp +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: $ + +#include + + diff --git a/RHSUBGHZSPI.h b/RHSUBGHZSPI.h new file mode 100644 index 0000000..73d3a04 --- /dev/null +++ b/RHSUBGHZSPI.h @@ -0,0 +1,48 @@ +// RHSUBGHZSPI.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2023 Mike McCauley + +#ifndef RHSUBGHZSPI_h +#define RHSUBGHZSPI_h + +#include + +// Are we building for a suitable STM processor +#if defined(SUBGHZSPI_BASE) + +///////////////////////////////////////////////////////////////////// +/// \class RHSUBGHZSPI RHSUBGHZSPI.h +/// \brief Base class for SPI interfacesInterface for SUBGHZSPIClass +/// as used on eg Wio-E5 mini and other boards that use the Seeed LoRa-E5-HF, LoRa-E5-LF modules +/// which use the STM32WLE5JC family processors. +/// +/// SubGhz is the SPI interface used on the STM32WLE5JC to communicate with the internal SX1261 radio, +/// and it has some special methods needed to talk to the chip. +/// +class RHSUBGHZSPI : public RHGenericSPI +{ +public: + uint8_t transfer(uint8_t data) { return SubGhz.SPI.transfer(data); }; + + /// Initialise the software SPI library + /// Call this after configuring the SPI interface and before using it to transfer data. + /// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high. + void begin() { SubGhz.SPI.begin(); }; + + /// Disables the SPI bus usually, in this case + /// there is no hardware controller to disable. + void end() { SubGhz.SPI.end(); }; + + void beginTransaction() { + SubGhz.SPI.beginTransaction(SubGhz.spi_settings); + SubGhz.setNssActive(true); + while (SubGhz.isBusy()) /* wait */; + }; + void endTransaction() { + SubGhz.setNssActive(false); + SubGhz.SPI.endTransaction(); + }; +}; + +#endif +#endif diff --git a/RHSoftwareSPI.cpp b/RHSoftwareSPI.cpp new file mode 100644 index 0000000..f1959cb --- /dev/null +++ b/RHSoftwareSPI.cpp @@ -0,0 +1,166 @@ +// SoftwareSPI.cpp +// Author: Chris Lapa (chris@lapa.com.au) +// Copyright (C) 2014 Chris Lapa +// Contributed by Chris Lapa + +#include + +RHSoftwareSPI::RHSoftwareSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode) + : + RHGenericSPI(frequency, bitOrder, dataMode) +{ + setPins(12, 11, 13); +} + +// Caution: on Arduino Uno and many other CPUs, digitalWrite is quite slow, taking about 4us +// digitalWrite is also slow, taking about 3.5us +// resulting in very slow SPI bus speeds using this technique, up to about 120us per octet of transfer +uint8_t RHSoftwareSPI::transfer(uint8_t data) +{ + uint8_t readData; + uint8_t writeData; + uint8_t builtReturn; + uint8_t mask; + + if (_bitOrder == BitOrderMSBFirst) + { + mask = 0x80; + } + else + { + mask = 0x01; + } + builtReturn = 0; + readData = 0; + + for (uint8_t count=0; count<8; count++) + { + if (data & mask) + { + writeData = HIGH; + } + else + { + writeData = LOW; + } + + if (_clockPhase == 1) + { + // CPHA=1, miso/mosi changing state now + digitalWrite(_mosi, writeData); + digitalWrite(_sck, ~_clockPolarity); + delayPeriod(); + + // CPHA=1, miso/mosi stable now + readData = digitalRead(_miso); + digitalWrite(_sck, _clockPolarity); + delayPeriod(); + } + else + { + // CPHA=0, miso/mosi changing state now + digitalWrite(_mosi, writeData); + digitalWrite(_sck, _clockPolarity); + delayPeriod(); + + // CPHA=0, miso/mosi stable now + readData = digitalRead(_miso); + digitalWrite(_sck, ~_clockPolarity); + delayPeriod(); + } + + if (_bitOrder == BitOrderMSBFirst) + { + mask >>= 1; + builtReturn |= (readData << (7 - count)); + } + else + { + mask <<= 1; + builtReturn |= (readData << count); + } + } + + digitalWrite(_sck, _clockPolarity); + + return builtReturn; +} + +/// Initialise the SPI library +void RHSoftwareSPI::begin() +{ + if (_dataMode == DataMode0 || + _dataMode == DataMode1) + { + _clockPolarity = LOW; + } + else + { + _clockPolarity = HIGH; + } + + if (_dataMode == DataMode0 || + _dataMode == DataMode2) + { + _clockPhase = 0; + } + else + { + _clockPhase = 1; + } + digitalWrite(_sck, _clockPolarity); + + // Caution: these counts assume that digitalWrite is very fast, which is usually not true + switch (_frequency) + { + case Frequency1MHz: + _delayCounts = 8; + break; + + case Frequency2MHz: + _delayCounts = 4; + break; + + case Frequency4MHz: + _delayCounts = 2; + break; + + case Frequency8MHz: + _delayCounts = 1; + break; + + case Frequency16MHz: + _delayCounts = 0; + break; + } +} + +/// Disables the SPI bus usually, in this case +/// there is no hardware controller to disable. +void RHSoftwareSPI::end() { } + +/// Sets the pins used by this SoftwareSPIClass instance. +/// \param[in] miso master in slave out pin used +/// \param[in] mosi master out slave in pin used +/// \param[in] sck clock pin used +void RHSoftwareSPI::setPins(uint8_t miso, uint8_t mosi, uint8_t sck) +{ + _miso = miso; + _mosi = mosi; + _sck = sck; + + pinMode(_miso, INPUT); + pinMode(_mosi, OUTPUT); + pinMode(_sck, OUTPUT); + digitalWrite(_sck, _clockPolarity); +} + + +void RHSoftwareSPI::delayPeriod() +{ + for (uint8_t count = 0; count < _delayCounts; count++) + { + __asm__ __volatile__ ("nop"); + } +} + diff --git a/RHSoftwareSPI.h b/RHSoftwareSPI.h new file mode 100644 index 0000000..5e7e1a5 --- /dev/null +++ b/RHSoftwareSPI.h @@ -0,0 +1,90 @@ +// SoftwareSPI.h +// Author: Chris Lapa (chris@lapa.com.au) +// Copyright (C) 2014 Chris Lapa +// Contributed by Chris Lapa + +#ifndef RHSoftwareSPI_h +#define RHSoftwareSPI_h + +#include + +///////////////////////////////////////////////////////////////////// +/// \class RHSoftwareSPI RHSoftwareSPI.h +/// \brief Encapsulate a software SPI interface +/// +/// This concrete subclass of RHGenericSPI enapsulates a bit-banged software SPI interface. +/// Caution: this software SPI interface will be much slower than hardware SPI on most +/// platforms. +/// +/// SPI transactions are not supported, and associated functions do nothing. +/// +/// \par Usage +/// +/// Usage varies slightly depending on what driver you are using. +/// +/// For RF22, for example: +/// \code +/// #include +/// RHSoftwareSPI spi; +/// RH_RF22 driver(SS, 2, spi); +/// RHReliableDatagram(driver, CLIENT_ADDRESS); +/// void setup() +/// { +/// spi.setPins(6, 5, 7); // Or whatever SPI pins you need +/// .... +/// } +/// \endcode +class RHSoftwareSPI : public RHGenericSPI +{ +public: + + /// Constructor + /// Creates an instance of a bit-banged software SPI interface. + /// Sets the SPI pins to the defaults of + /// MISO = 12, MOSI = 11, SCK = 13. If you need other assigments, call setPins() before + /// calling manager.init() or driver.init(). + /// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency + /// is mapped to the closest available bus frequency on the platform. CAUTION: the achieved + /// frequency will almost certainly be very much slower on most platforms. eg on Arduino Uno, the + /// the clock rate is likely to be at best around 46kHz. + /// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or + /// RHGenericSPI::BitOrderLSBFirst. + /// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode + RHSoftwareSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0); + + /// Transfer a single octet to and from the SPI interface + /// \param[in] data The octet to send + /// \return The octet read from SPI while the data octet was sent. + uint8_t transfer(uint8_t data); + + /// Initialise the software SPI library + /// Call this after configuring the SPI interface and before using it to transfer data. + /// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high. + void begin(); + + /// Disables the SPI bus usually, in this case + /// there is no hardware controller to disable. + void end(); + + /// Sets the pins used by this SoftwareSPIClass instance. + /// The defaults are: MISO = 12, MOSI = 11, SCK = 13. + /// \param[in] miso master in slave out pin used + /// \param[in] mosi master out slave in pin used + /// \param[in] sck clock pin used + void setPins(uint8_t miso = 12, uint8_t mosi = 11, uint8_t sck = 13); + +private: + + /// Delay routine for bus timing. + void delayPeriod(); + +private: + uint8_t _miso; + uint8_t _mosi; + uint8_t _sck; + uint8_t _delayCounts; + uint8_t _clockPolarity; + uint8_t _clockPhase; +}; + +#endif diff --git a/RHTcpProtocol.h b/RHTcpProtocol.h new file mode 100644 index 0000000..205ad95 --- /dev/null +++ b/RHTcpProtocol.h @@ -0,0 +1,67 @@ +// RH_TcpProtocol.h +// Author: Mike McCauley (mikem@aierspayce.com) +// Definition of protocol messages sent and received by RH_TCP +// Copyright (C) 2014 Mike McCauley +// $Id: RHTcpProtocol.h,v 1.3 2014/05/22 06:07:09 mikem Exp $ + +/// This file contains the definitions of message structures passed between +/// RH_TCP and the etherSimulator +#ifndef RH_TcpProtocol_h +#define RH_TcpProtocol_h + +#define RH_TCP_MESSAGE_TYPE_NOP 0 +#define RH_TCP_MESSAGE_TYPE_THISADDRESS 1 +#define RH_TCP_MESSAGE_TYPE_PACKET 2 + +// Maximum message length (including the headers) we are willing to support +#define RH_TCP_MAX_PAYLOAD_LEN 255 + +// The length of the headers we add. +// The headers are inside the RF69's payload and are therefore encrypted if encryption is enabled +#define RH_TCP_HEADER_LEN 4 + + +// This is the maximum message length that can be supported by this protocol. +#define RH_TCP_MAX_MESSAGE_LEN (RH_TCP_MAX_PAYLOAD_LEN - RH_TCP_HEADER_LEN) + +#pragma pack(push, 1) // No padding + +/// \brief Generic RH_TCP simulator message structure +typedef struct +{ + uint32_t length; ///< Number of octets following, in network byte order + uint8_t payload[RH_TCP_MAX_PAYLOAD_LEN + 1]; ///< Payload +} RHTcpMessage; + +/// \brief Generic RH_TCP message structure with message type +typedef struct +{ + uint32_t length; ///< Number of octets following, in network byte order + uint8_t type; ///< One of RH_TCP_MESSAGE_TYPE_* + uint8_t payload[RH_TCP_MAX_PAYLOAD_LEN]; ///< Payload +} RHTcpTypeMessage; + +/// \brief RH_TCP message Notifies the server of thisAddress of this client +typedef struct +{ + uint32_t length; ///< Number of octets following, in network byte order + uint8_t type; ///< == RH_TCP_MESSAGE_TYPE_THISADDRESS + uint8_t thisAddress; ///< Node address +} RHTcpThisAddress; + +/// \brief RH_TCP radio message passed to or from the simulator +typedef struct +{ + uint32_t length; ///< Number of octets following, in network byte order + // 5 octets of header to follow for total of 9 octets + uint8_t type; ///< == RH_TCP_MESSAGE_TYPE_PACKET + uint8_t to; ///< Node address of the recipient + uint8_t from; ///< Node address of the sender + uint8_t id; ///< Message sequence number + uint8_t flags; ///< Message flags + uint8_t payload[RH_TCP_MAX_MESSAGE_LEN]; ///< 0 or more, length deduced from length above +} RHTcpPacket; + +#pragma pack(pop) + +#endif diff --git a/RH_ABZ.cpp b/RH_ABZ.cpp new file mode 100644 index 0000000..c1c493f --- /dev/null +++ b/RH_ABZ.cpp @@ -0,0 +1,123 @@ +// RH_ABZ.cpp +// +// Copyright (C) 2020 Mike McCauley +// $Id: RH_ABZ.cpp,v 1.1 2020/06/15 23:39:39 mikem Exp $ + +#if (RH_PLATFORM == RH_PLATFORM_STM32L0) && (defined STM32L082xx || defined STM32L072xx) + +#include + +// Pointer to the _only_ permitted ABZ instance (there is only one radio connected to this device) +RH_ABZ* RH_ABZ::_thisDevice; + +// The muRata cmwx1zzabz module has its builtin SX1276 radio connected to the processor's SPI1 port, +// but the Arduino compatible SPI interface in Grumpy Pizzas Arduino Core is configured for SPI1 or SPI2 +// depending on the exact board variant selected. +// So here we define our own Arduino compatible SPI interface +// so we are _sure_ to get the one connected to the radio, independent of the board variant selected +#include +static const stm32l0_spi_params_t RADIO_SPI_PARAMS = { + STM32L0_SPI_INSTANCE_SPI1, + 0, + STM32L0_DMA_CHANNEL_NONE, + STM32L0_DMA_CHANNEL_NONE, + { + STM32L0_GPIO_PIN_PA7_SPI1_MOSI, + STM32L0_GPIO_PIN_PA6_SPI1_MISO, + STM32L0_GPIO_PIN_PB3_SPI1_SCK, + STM32L0_GPIO_PIN_NONE, + }, +}; + +// Create and configure an Arduino compatible SPI interface. This will be referred to in RHHardwareSPI.cpp +// and used as the SPI interface to the radio. +static stm32l0_spi_t RADIO_SPI; +SPIClass radio_spi(&RADIO_SPI, &RADIO_SPI_PARAMS); + +// Glue code between the 'C' DIO0 interrupt and the C++ interrupt handler in RH_RF95 +void RH_INTERRUPT_ATTR RH_ABZ::isr() +{ + _thisDevice->handleInterrupt(); +} + +RH_ABZ::RH_ABZ(): + RH_RF95(RH_INVALID_PIN, RH_INVALID_PIN) +{ +} + +bool RH_ABZ::init() +{ + _thisDevice = this; + + // REVISIT: RESET THE RADIO??? + + // The SX1276 radio DIO0 is connected to STM32 pin PB4 + // It will later be configured as an interrupt + stm32l0_gpio_pin_configure(STM32L0_GPIO_PIN_PB4, (STM32L0_GPIO_PARK_NONE | STM32L0_GPIO_PUPD_PULLDOWN | STM32L0_GPIO_OSPEED_HIGH | STM32L0_GPIO_OTYPE_PUSHPULL | STM32L0_GPIO_MODE_INPUT)); + + // Here we configure the interrupt handler for DIO0 to call the C++ + // interrupt handler in RH_RF95, in a roundabout way +#ifdef STM32L0_EXTI_CONTROL_PRIORITY_CRITICAL + stm32l0_exti_attach(STM32L0_GPIO_PIN_PB4, (STM32L0_EXTI_CONTROL_PRIORITY_CRITICAL | STM32L0_EXTI_CONTROL_EDGE_RISING), (stm32l0_exti_callback_t)isr, NULL); // STM32L0_EXTI_CONTROL_PRIORITY_CRITICAL not in 0.0.10 +#else + stm32l0_exti_attach(STM32L0_GPIO_PIN_PB4, STM32L0_EXTI_CONTROL_EDGE_RISING, (stm32l0_exti_callback_t)isr, NULL); +#endif + // The SX1276 radio slave select (NSS) is connected to STM32 pin PA15 + stm32l0_gpio_pin_configure(STM32L0_GPIO_PIN_PA15, (STM32L0_GPIO_PARK_HIZ | STM32L0_GPIO_PUPD_NONE | STM32L0_GPIO_OSPEED_HIGH | STM32L0_GPIO_OTYPE_PUSHPULL | STM32L0_GPIO_MODE_OUTPUT)); + + // muRata cmwx1zzabz module has an antenna switch which must be driven the right way to connect the antenna + // to the appropriate SX1276 pins. + // Antenna switch might be something like NJG180K64, but not sure. + // See Application note: AN-ZZABZ-001 P. 20/20 + // in typeABZ_hardware_design_guide_revC.pdf + // with 3 pins connected to STM32L0_GPIO_PIN_PA1, STM32L0_GPIO_PIN_PC2, STM32L0_GPIO_PIN_PC1 + // which select RX, RFO or PA_BOOST respecitvely + // See modeWillChange() for implementation of pin twiddling when the transmitter is on + // + // We use native STM32 calls because the various different variants in the Grumpy Pizza + // Arduino core and various forks of that core have inconsistent definitions of the Arduino compatible + // pins. We want to be sure we get the right ones for the muRata modules connections to the Radio + stm32l0_gpio_pin_configure(STM32L0_GPIO_PIN_PA1, (STM32L0_GPIO_PARK_NONE | STM32L0_GPIO_PUPD_NONE | STM32L0_GPIO_OSPEED_LOW | STM32L0_GPIO_OTYPE_PUSHPULL | STM32L0_GPIO_MODE_OUTPUT)); + stm32l0_gpio_pin_configure(STM32L0_GPIO_PIN_PC2, (STM32L0_GPIO_PARK_NONE | STM32L0_GPIO_PUPD_NONE | STM32L0_GPIO_OSPEED_LOW | STM32L0_GPIO_OTYPE_PUSHPULL | STM32L0_GPIO_MODE_OUTPUT)); + stm32l0_gpio_pin_configure(STM32L0_GPIO_PIN_PC1, (STM32L0_GPIO_PARK_NONE | STM32L0_GPIO_PUPD_NONE | STM32L0_GPIO_OSPEED_LOW | STM32L0_GPIO_OTYPE_PUSHPULL | STM32L0_GPIO_MODE_OUTPUT)); + + return RH_RF95::init(); +} + +bool RH_ABZ::deinit() +{ + setModeIdle(); + stm32l0_exti_detach(STM32L0_GPIO_PIN_PB4); + return true; +} + +void RH_ABZ::selectSlave() +{ + stm32l0_gpio_pin_write(STM32L0_GPIO_PIN_PA15, 0); +} + +void RH_ABZ::deselectSlave() +{ + stm32l0_gpio_pin_write(STM32L0_GPIO_PIN_PA15, 1); +} + +bool RH_ABZ::modeWillChange(RHMode mode) +{ + if (mode == RHModeTx) + { + // Tell the antenna switch to connect to one of the transmitter output pins + stm32l0_gpio_pin_write(STM32L0_GPIO_PIN_PA1, 0); // RX + stm32l0_gpio_pin_write(STM32L0_GPIO_PIN_PC2, _useRFO ? 1 : 0); // RFO + stm32l0_gpio_pin_write(STM32L0_GPIO_PIN_PC1, _useRFO ? 0 : 1); // BOOST + } + else + { + // Enabling the RX from the antenna switch improves reception RSSI by about 5 + stm32l0_gpio_pin_write(STM32L0_GPIO_PIN_PA1, 1); // RX + stm32l0_gpio_pin_write(STM32L0_GPIO_PIN_PC2, 0); // RFO + stm32l0_gpio_pin_write(STM32L0_GPIO_PIN_PC1, 0); // BOOST + } + return true; +} + +#endif diff --git a/RH_ABZ.h b/RH_ABZ.h new file mode 100644 index 0000000..032675e --- /dev/null +++ b/RH_ABZ.h @@ -0,0 +1,198 @@ +// RH_ABZ.h +// +// Definitions for SX1276 radio in muRata CMWX1ZZABZ (TypeABZ) module +// as used in GrumpyOldPizza Grasshopper-L082CZ, EcoNode SmartTrap etc with +// GrumpyOldPizza / ArduinoCore-stm32l0 installed per https://github.com/GrumpyOldPizza/ArduinoCore-stm32l0 +// +// For data refer to muRata Application note: AN-ZZABZ-001 +// muRata Preliminary Specification Number : SP-ABZ-093-E +// $p/EcoNode +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2020 Mike McCauley +// $Id: RH_ABZ.h,v 1.1 2020/06/15 23:39:39 mikem Exp $ +// +#ifndef RH_ABZ_h +#define RH_ABZ_h + +#include + +///////////////////////////////////////////////////////////////////// +/// \class RH_ABZ RH_ABZ.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via +/// radio transceiver in a muRata cmwx1zzabz module, which includes an STM32L0 processor, +/// a SX1276 LoRa radio and an antenna switch. +/// +/// Requires the Grumpy Old Pizza Arduino Core installed per https://github.com/GrumpyOldPizza/ArduinoCore-stm32l0 +/// +/// Works with EcoNode SmartTrap, Tlera Grasshopper and family. Almost any board equipped with a muRata cmwx1zzabz module +/// should work. Tested with EcoNode SmartTrap, Arduino 1.8.9, GrumpyOldPizza Arduino Core for STM32L0. +/// When building for EcoNode SmartTrap in Arduino IDE, select board type Grasshopper-L082CZ. +/// This chip and GrumpyOldPizza Arduino Core for STM32L0 are now supported by PlatformIO: +/// https://docs.platformio.org/en/latest/platforms/ststm32.html#arduino-stm32l0-configuration-system +/// +/// \par Overview +/// +/// This class is a specialisation of the RH_RF95 class, but with overridden functions to ensure that RH_RF95 communicates +/// with the SX1276 radio on the correct SPI interface (STM32L0_SPI_INSTANCE_SPI1). The class configures the SPI and +/// interfaces with the radio using native stm32l0 calls. It also uses stm32l0 calls to intialise the radio interface pins, +/// antenna interface pins, and to toggle the radio NSS pin for SPI communicaitons. The reason for this speicalisaiton is that +/// all the STM32L0 variants in the Grumpy Pizzas Arduino Core define the Arduino compatible SPI interface and the +/// Arduino compatible IO pins in varying and inconsistent ways. So we use native stm32l0 calls to make _sure_ we get the right +/// pins and interfaces. +/// +/// All the comments in the RH_RF95 class concerning modulation, packet formats etc apply equally to this module. +/// +/// \par Temperature Controlled Crystal Oscillator (TCXO) +/// +/// The muRata cmwx1zzabz module includes a TCXO. Pins to enable the TCXO and to connect to 32MHz output to the radio +/// are exposed on the module. Some boards (Econode SmartTrap for example) permanently power the TCXO and permanenetly +/// connect it to the radio. Other boards (Grasshopper for example) have the TCXO enable connected to a GPIO pin, allowing +/// the TCXO to be controlled by software. Different boards may use different GPIO pins to control the TCXO. +/// +/// The SX1276 radio can be configured to use the TCXO, and the Arduino Core defaults the radio to using TCXO. +/// Therefore it is important that you ensure the TCXO is powered up, at least when you want the radio to operate. +/// If the TCXO is not powered, the radio will not work. +/// +/// On the Tlera boards supported the Arduino Core, you can call SX1276SetBoardTcxo() to enable or disable the TCXO +/// by controlling the correct pin for your board. +/// By default the core disables TCXO at the end of initialisation, so by the time your sketch starts to run +/// the TCXO is powered off. +/// You will almost certainly need to call +/// \code +/// SX1276SetBoardTcxo(true); +/// \endcode +/// in your setup() or at other times when you want the radio to operate. +/// +/// If you have a board where the TCXO is permanently powered, this is unnecessary. +/// +/// \par Connecting and configuring the radio +/// +/// There is no special configuration for the SX1276 radio in the muRata cmwx1zzabz module: the CPU, radio and +/// antenna switch are all hardwired within the module can, and cannot be changed. Initialise the radio like this +/// with the default constructor: +/// \code +/// RH_ABZ driver; +/// \endcode +/// +/// \par Range +/// +/// We made some primitive range tests with 2 identical EcoNode SmartTrap at 868MHz, 20dBm transmit power with +/// modem config RH_RF95::Bw125Cr45Sf2048, using the abz_client and abz_server sketches included in this distribution. +/// We monitored for reliabilty of 2-way communications (ie how reliably can the client get a reply from the server, +/// which is a 2-way comms that depends on both send and receive. The SmartTrap has a simple small helical antenna. +/// The environment was a beach in a developed area, one node was stationary +/// on a rock about 1m above sand level. The mobile node was handl-held at 1 m above ground level. All measurements were line-of-sight. +/// +/// \code +/// Location Distance (km) % successful round trip +/// Elephant Rock 0 100 +/// Dune St 1.48 100 +/// Shell St 1.71 100 +/// Sand St 1.93 100 +/// Sea St 2.15 0 +/// Short St 2.36 50 +/// John St 2.59 50 +/// Surf St 2.80 100 +/// Matters St 2.98 100 +/// Mills St 3.92 100 +/// North Kirra 4.62 0 +/// North Kirra SLSC 4.81 80 +/// Haig St 5.20 0 +/// Kirra SLSC 5.91 0 +/// \endcode +/// +/// \par Transmitter Power +/// +/// We have made some actual power measurements against +/// programmed power on an EcoNode SmartTrap. +/// - EcoNode SmartTrap at 868MHz +/// - 15cm RG316 soldered direct to SmartTrap antenna pin +/// - SMA/BNC connector +/// - 12db attenuator (calibrated as 13.5dB at 868MHz) +/// - SMA/BNC connector +/// - 30cm RG316 +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// useRFO==false (ie uses PA_BOOST for higher power) +/// Program power Measured Power +/// dBm dBm +/// 2 2.7 +/// 5 5 +/// 7 6.5 +/// 10 9.5 +/// 13 12.5 +/// 15 14.5 +/// 16 15.5 +/// 17 16.5 +/// 18 17.2 +/// 19 17.6 +/// 20 18.2 +/// +/// useRFO==true (ie no PA_BOOST) +/// Program power Measured Power +/// dBm dBm +/// 0 -5.5 +/// 2 -2.5 +/// 4 -0.5 +/// 6 2 +/// 8 4 +/// 10 6.5 +/// 12 9 +/// 13 10.5 +/// 14 11.5 +/// 15 12.5 +/// \endcode + +/// In the the Grumpy Old Pizza Arduino Core, there is a function for turning the +/// TCXO power source on and off, which depends on exactly which board is being compiled for +/// If the Radio in your boards has its TCXO connected to a programmable power pin, +/// and if you enable TCXO on the radio (by default it is on these boards) +/// then this function needs to be called to enable the TCXO before the radio will work. +extern "C" void SX1276SetBoardTcxo( bool state ); + +class RH_ABZ : public RH_RF95 +{ +public: + /// Constructor + RH_ABZ(); + + /// Initialise the Driver transport hardware and software. Leaves the radio in idle mode, + /// with default configuration of: 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Deinitialise the interrupt handler, allowing the radio to be temporarily used by another stack. + /// If you wish to use the radio again after this call, you wil need to call init() again. + /// \return true if deinitialisation succeeded. + bool deinit(); + +protected: + /// Called by RH_RF95 when the radio mode is about to change to a new setting. + /// Configures the antenna switch to connect to the right radio pin. + /// \param[in] mode RHMode the new mode about to take effect + /// \return true if the subclasses changes successful + virtual bool modeWillChange(RHMode mode); + + /// Called by RHSPIDriver when the SPI is about to talk to the radio. + /// Uses native spi32l0 calls to enable the radio NSS pin + virtual void selectSlave(); + + /// Called by RHSPIDriver when the SPI is finished talking to the radio. + /// Uses native spi32l0 calls to disable the radio NSS pin + virtual void deselectSlave(); + +private: + /// Glue code between the DIO0 interrupt the interrupt handler in RH_RF95 + static void RH_INTERRUPT_ATTR isr(); + + /// Pointer to the one and only instance permitted, for interrupt linkage + static RH_ABZ* _thisDevice; + +}; + +/// @example abz_client.ino +/// @example abz_server.ino + +#endif diff --git a/RH_ASK.cpp b/RH_ASK.cpp new file mode 100644 index 0000000..8d4329e --- /dev/null +++ b/RH_ASK.cpp @@ -0,0 +1,1104 @@ +// RH_ASK.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RH_ASK.cpp,v 1.32 2020/08/04 09:02:14 mikem Exp $ + +#include +#include + +#ifndef __SAMD51__ + +#if (RH_PLATFORM == RH_PLATFORM_STM32) + // Maple etc + HardwareTimer timer(MAPLE_TIMER); +#elif defined(ARDUINO_ARCH_RP2040) + +#elif defined(BOARD_NAME) + // ST's Arduino Core STM32, https://github.com/stm32duino/Arduino_Core_STM32 + #if defined(RH_HW_TIMER) + // Can define your own timer name based on macros defs passed to compiler eg in platformio.ini + HardwareTimer timer(RH_HW_TIMER); + #else + HardwareTimer timer(TIM1); + #endif + +#elif defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH_STM32F3) || defined(ARDUINO_ARCH_STM32F4) + // Roger Clark Arduino STM32, https://github.com/rogerclarkmelbourne/Arduino_STM32 + // And stm32duino + HardwareTimer timer(1); + +#elif defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI) + #include "FspTimer.h" + FspTimer ask_timer; + +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(RH_CUBE_CELL_BOARD) + static TimerEvent_t timer; +#endif + + +#if (RH_PLATFORM == RH_PLATFORM_ESP32) + // Michael Cain + DRAM_ATTR hw_timer_t * timer; + //jPerotto Non-constant static data from ESP32 https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html#dram-data-ram + #define RH_DRAM_ATTR DRAM_ATTR +#else + #define RH_DRAM_ATTR +#endif + +// RH_ASK on Arduino uses Timer 1 to generate interrupts 8 times per bit interval +// Define RH_ASK_ARDUINO_USE_TIMER2 if you want to use Timer 2 instead of Timer 1 on Arduino +// You may need this to work around other libraries that insist on using timer 1 +// Should be moved to header file +//#define RH_ASK_ARDUINO_USE_TIMER2 + +// RH_ASK on ATtiny8x uses Timer 0 to generate interrupts 8 times per bit interval. +// Timer 0 is used by Arduino platform for millis()/micros() which is used by delay() +// Uncomment the define RH_ASK_ATTINY_USE_TIMER1 bellow, if you want to use Timer 1 instead of Timer 0 on ATtiny +// Timer 1 is also used by some other libraries, e.g. Servo. Alway check usage of Timer 1 before enabling this. +// Should be moved to header file +//#define RH_ASK_ATTINY_USE_TIMER1 + +// Interrupt handler uses this to find the most recently initialised instance of this driver +static RH_ASK* thisASKDriver; + +// 4 bit to 6 bit symbol converter table +// Used to convert the high and low nybbles of the transmitted data +// into 6 bit symbols for transmission. Each 6-bit symbol has 3 1s and 3 0s +// with at most 3 consecutive identical bits +RH_DRAM_ATTR static uint8_t symbols[] = +{ + 0xd, 0xe, 0x13, 0x15, 0x16, 0x19, 0x1a, 0x1c, + 0x23, 0x25, 0x26, 0x29, 0x2a, 0x2c, 0x32, 0x34 +}; + +// This is the value of the start symbol after 6-bit conversion and nybble swapping +#define RH_ASK_START_SYMBOL 0xb38 + +RH_ASK::RH_ASK(uint16_t speed, uint8_t rxPin, uint8_t txPin, uint8_t pttPin, bool pttInverted) + : + _speed(speed), + _rxPin(rxPin), + _txPin(txPin), + _pttPin(pttPin), + _rxInverted(false), + _pttInverted(pttInverted) +{ + // Initialise the first 8 nibbles of the tx buffer to be the standard + // preamble. We will append messages after that. 0x38, 0x2c is the start symbol before + // 6-bit conversion to RH_ASK_START_SYMBOL + uint8_t preamble[RH_ASK_PREAMBLE_LEN] = {0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x38, 0x2c}; + memcpy(_txBuf, preamble, sizeof(preamble)); +} + +bool RH_ASK::init() +{ + if (!RHGenericDriver::init()) + return false; + thisASKDriver = this; + +#if (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8) + #ifdef RH_ASK_PTT_PIN + RH_ASK_PTT_DDR |= (1< 1) && (ulticks < max_ticks)) + break; // found prescaler + + // Won't fit, check with next prescaler value + } + + + // Check for error + if ((prescaler == 6) || (ulticks < 2) || (ulticks > max_ticks)) + { + // signal fault + *nticks = 0; + return 0; + } + + *nticks = ulticks; + return prescaler; +#else + return 0; // not implemented or needed on other platforms +#endif +} + +#if defined(RH_PLATFORM_ARDUINO) && defined(ARDUINO_ARCH_RP2040) +void set_pico_alarm(uint32_t speed) +{ + uint32_t period = (1000000 / 8) / speed; // In microseconds + uint64_t target = timer_hw->timerawl + period; + timer_hw->alarm[RH_ASK_PICO_ALARM_NUM] = (uint32_t) target; +} +#endif + +// The idea here is to get 8 timer interrupts per bit period +void RH_ASK::timerSetup() +{ +#if (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8) + uint16_t nticks; + uint8_t prescaler = timerCalc(_speed, (uint16_t)-1, &nticks); + if (!prescaler) return; + _COMB(TCCR,RH_ASK_TIMER_INDEX,A)= 0; + _COMB(TCCR,RH_ASK_TIMER_INDEX,B)= _BV(WGM12); + _COMB(TCCR,RH_ASK_TIMER_INDEX,B)|= prescaler; + _COMB(OCR,RH_ASK_TIMER_INDEX,A)= nticks; + _COMB(TI,MSK,RH_ASK_TIMER_INDEX)|= _BV(_COMB(OCIE,RH_ASK_TIMER_INDEX,A)); + +#elif (RH_PLATFORM == RH_PLATFORM_MSP430) // LaunchPad specific + // Calculate the counter overflow count based on the required bit speed + // and CPU clock rate + uint16_t ocr1a = (F_CPU / 8UL) / _speed; + + // This code is for Energia/MSP430 + TA0CCR0 = ocr1a; // Ticks for 62,5 us + TA0CTL = TASSEL_2 + MC_1; // SMCLK, up mode + TA0CCTL0 |= CCIE; // CCR0 interrupt enabled + +#elif (RH_PLATFORM == RH_PLATFORM_STM32L0) + Serial.println("STM32L0 RH_ASK NOT YET IMPLEMENTED "); + +#elif (RH_PLATFORM == RH_PLATFORM_STM32) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH_STM32F3) || defined(ARDUINO_ARCH_STM32F4) + // Maple etc + // or rogerclarkmelbourne/Arduino_STM32 + // or stm32duino + // Pause the timer while we're configuring it + timer.pause(); + + #ifdef BOARD_NAME + // ST's Arduino Core STM32, https://github.com/stm32duino/Arduino_Core_STM32 + // Declaration of the callback function changed in 1.9. Sigh + #if (STM32_CORE_VERSION >= 0x01090000) + void interrupt(); + #else + void interrupt(HardwareTimer*); // defined below + #endif + uint16_t us=(1000000/8)/_speed; + timer.setMode(1, TIMER_OUTPUT_COMPARE); + timer.setOverflow(us, MICROSEC_FORMAT); + timer.setCaptureCompare(1, us - 1, MICROSEC_COMPARE_FORMAT); + #if (STM32_CORE_VERSION >= 0x01090000) + timer.attachInterrupt(interrupt); + #else + timer.attachInterrupt(1, interrupt); + #endif + + #else + void interrupt(); // defined below + // Roger Clark Arduino STM32, https://github.com/rogerclarkmelbourne/Arduino_STM32 + timer.setPeriod((1000000/8)/_speed); + // Set up an interrupt on channel 1 + timer.setChannel1Mode(TIMER_OUTPUT_COMPARE); + timer.setCompare(TIMER_CH1, 1); // Interrupt 1 count after each update + void interrupt(); // defined below + timer.attachCompare1Interrupt(interrupt); + #endif + // Refresh the timer's count, prescale, and overflow + timer.refresh(); + + // Start the timer counting + timer.resume(); + +#elif (RH_PLATFORM == RH_PLATFORM_ATTINY) + // figure out prescaler value and counter match value + // REVISIT: does not correctly handle 1MHz clock speeds, only works with 8MHz clocks + // At 1MHz clock, get 1/8 of the expected baud rate + uint16_t nticks; + uint8_t prescaler = timerCalc(_speed, (uint8_t)-1, &nticks); + if (!prescaler) + return; // fault + #if defined(RH_ASK_ATTINY_USE_TIMER1) + #if defined(TCCR1) + // ATtiny85 + TCCR1 = 0; + TCCR1 = _BV(CTC1); // Turn on CTC mode / Output Compare pins disconnected + + // convert prescaler index to TCCR1 prescaler bits CS10, CS11, CS12, CS13 + TCCR1 |= prescaler; // set CS10, CS11, CS12, CS13 (other bits not needed) + + // Number of ticks to count before firing interrupt + OCR1A = uint8_t(nticks); + // Number of ticks to count before counter reset (CTC mode) + OCR1C = uint8_t(nticks); + // Synchronous mode and PLL disabled + PLLCSR = 0; + // Set mask to fire interrupt when OCF1A bit is set in TIFR1 + TIMSK |= _BV(OCIE1A); + #else //TCCR1 + // ATtiny84 + TCCR1A = 0; // Output Compare pins disconnected + TCCR1B = _BV(WGM12); // Turn on CTC mode + + // convert prescaler index to TCCRnB prescaler bits CS10, CS11, CS12 + TCCR1B |= prescaler; + TCCR1C = 0; + + // Caution: special procedures for setting 16 bit regs + // is handled by the compiler + OCR1A = nticks; + //enable interupt + TIMSK1 |= _BV(OCIE1A); + #endif // + #else //RH_ASK_ATTINY_USE_TIMER1 + TCCR0A = 0; + TCCR0A = _BV(WGM01); // Turn on CTC mode / Output Compare pins disconnected + + // convert prescaler index to TCCRnB prescaler bits CS00, CS01, CS02 + TCCR0B = 0; + TCCR0B = prescaler; // set CS00, CS01, CS02 (other bits not needed) + + + // Number of ticks to count before firing interrupt + OCR0A = uint8_t(nticks); + + // Set mask to fire interrupt when OCF0A bit is set in TIFR0 + #ifdef TIMSK0 + // ATtiny84 + TIMSK0 |= _BV(OCIE0A); + #else + // ATtiny85 + TIMSK |= _BV(OCIE0A); + #endif + #endif //RH_ASK_ATTINY_USE_TIMER1 +#elif (RH_PLATFORM == RH_PLATFORM_ATTINY_MEGA) + // If your processor does not have a TCB1, you can change the timer used in RadioHead.h + volatile TCB_t* timer = &RH_ATTINY_MEGA_ASK_TIMER; + + // Calculate compare value + uint32_t compare_val = F_CPU / _speed / 8 - 1; + // If compare larger than 16bits, need to prescale (will be DIV64) + if (compare_val > 0xFFFF) + { + // recalculate with new prescaler + compare_val = F_CPU / _speed / 8 / 64 - 1; + // Prescaler needed + timer->CTRLA = TCB_CLKSEL_CLKTCA_gc; + } + else + { + // No prescaler needed + timer->CTRLA = TCB_CLKSEL_CLKDIV1_gc; + } + + // Timer to Periodic interrupt mode + // This write will also disable any active PWM outputs + timer->CTRLB = TCB_CNTMODE_INT_gc; + // Write compare register + timer->CCMP = compare_val; + // Enable interrupt + timer->INTCTRL = TCB_CAPTEI_bm; + // Enable Timer + timer->CTRLA |= TCB_ENABLE_bm; + +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(RH_CUBE_CELL_BOARD) + // Arduino CubeCell board 1.0.0 has non-standard timer support + // Forward declaration of the callback below + void timer_callback(); + TimerInit(&timer, timer_callback); + // Sigh: This timer takes the timeout in milliseconds, not microseconds. That means the fastest bit rate + // we can support is 1000 / 8 = 125 bits per second. + uint32_t period = (1000 / 8) / _speed; // In milliseconds + TimerSetValue(&timer, period); // mseconds + TimerStart(&timer); + +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) // Arduino specific + + + #if defined(__arm__) && defined(CORE_TEENSY) + // on Teensy 3.0 (32 bit ARM), use an interval timer + IntervalTimer *t = new IntervalTimer(); + void TIMER1_COMPA_vect(void); + t->begin(TIMER1_COMPA_vect, 125000 / _speed); + + #elif defined (__arm__) && defined(ARDUINO_ARCH_SAMD) + // Arduino Zero + #define RH_ASK_ZERO_TIMER TC3 + // Clock speed is 48MHz, prescaler of 64 gives a good range of available speeds vs precision + #define RH_ASK_ZERO_PRESCALER 64 + #define RH_ASK_ZERO_TIMER_IRQ TC3_IRQn + + // Enable clock for TC + REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TCC2_TC3)) ; + while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync + + // The type cast must fit with the selected timer mode + TcCount16* TC = (TcCount16*)RH_ASK_ZERO_TIMER; // get timer struct + + TC->CTRLA.reg &= ~TC_CTRLA_ENABLE; // Disable TC + while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync + + TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16; // Set Timer counter Mode to 16 bits + while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync + TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as Match Frequency + while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync + + // Compute the count required to achieve the requested baud (with 8 interrupts per bit) + uint32_t rc = (VARIANT_MCK / _speed) / RH_ASK_ZERO_PRESCALER / 8; + + TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV64; // Set prescaler to agree with RH_ASK_ZERO_PRESCALER + while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync + + TC->CC[0].reg = rc; // FIXME + while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync + + // Interrupts + TC->INTENSET.reg = 0; // disable all interrupts + TC->INTENSET.bit.MC0 = 1; // enable compare match to CC0 + + // Enable InterruptVector + NVIC_ClearPendingIRQ(RH_ASK_ZERO_TIMER_IRQ); + NVIC_EnableIRQ(RH_ASK_ZERO_TIMER_IRQ); + + // Enable TC + TC->CTRLA.reg |= TC_CTRLA_ENABLE; + while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync + + #elif defined(__arm__) && defined(ARDUINO_SAM_DUE) + // Arduino Due + // Clock speed is 84MHz + // Due has 9 timers in 3 blocks of 3. + // We use timer 1 TC1_IRQn on TC0 channel 1, since timers 0, 2, 3, 4, 5 are used by the Servo library + #define RH_ASK_DUE_TIMER TC0 + #define RH_ASK_DUE_TIMER_CHANNEL 1 + #define RH_ASK_DUE_TIMER_IRQ TC1_IRQn + pmc_set_writeprotect(false); + pmc_enable_periph_clk(RH_ASK_DUE_TIMER_IRQ); + + // Clock speed 4 can handle all reasonable _speeds we might ask for. Its divisor is 128 + // and we want 8 interrupts per bit + uint32_t rc = (VARIANT_MCK / _speed) / 128 / 8; + TC_Configure(RH_ASK_DUE_TIMER, RH_ASK_DUE_TIMER_CHANNEL, + TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4); + TC_SetRC(RH_ASK_DUE_TIMER, RH_ASK_DUE_TIMER_CHANNEL, rc); + // Enable the RC Compare Interrupt + RH_ASK_DUE_TIMER->TC_CHANNEL[RH_ASK_DUE_TIMER_CHANNEL].TC_IER = TC_IER_CPCS; + NVIC_ClearPendingIRQ(RH_ASK_DUE_TIMER_IRQ); + NVIC_EnableIRQ(RH_ASK_DUE_TIMER_IRQ); + TC_Start(RH_ASK_DUE_TIMER, RH_ASK_DUE_TIMER_CHANNEL); + + #elif defined(ARDUINO_ARCH_RP2040) + // Per https://emalliab.wordpress.com/2021/04/18/raspberry-pi-pico-arduino-core-and-timers/ + hw_set_bits(&timer_hw->inte, 1u << RH_ASK_PICO_ALARM_NUM); + void picoInterrupt(); // Forward declaration of interrupt handler + irq_set_exclusive_handler(RH_ASK_PICO_ALARM_IRQ, picoInterrupt); + irq_set_enabled(RH_ASK_PICO_ALARM_IRQ, true); + set_pico_alarm(_speed); + + #elif defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI) + uint8_t timer_type = GPT_TIMER; + int8_t tindex = FspTimer::get_available_timer(timer_type); + if (tindex < 0) + tindex = FspTimer::get_available_timer(timer_type, true); + + if (tindex < 0) + return; + + FspTimer::force_use_of_pwm_reserved_timer(); + void timer_callback(timer_callback_args_t __attribute((unused)) *p_args); // Forward declaration + if (!ask_timer.begin(TIMER_MODE_PERIODIC, timer_type, tindex, _speed * 8, 0.0f, timer_callback)) + return; + + if (!ask_timer.setup_overflow_irq()) + return; + + if (!ask_timer.open()) + return; + + if (!ask_timer.start()) + return; + + #else + uint16_t nticks; // number of prescaled ticks needed + uint8_t prescaler; // Bit values for CS0[2:0] + + // This is the path for most Arduinos + // figure out prescaler value and counter match value + #if defined(RH_ASK_ARDUINO_USE_TIMER2) + prescaler = timerCalc(_speed, (uint8_t)-1, &nticks); + if (!prescaler) + return; // fault + // Use timer 2 + TCCR2A = _BV(WGM21); // Turn on CTC mode) + // convert prescaler index to TCCRnB prescaler bits CS10, CS11, CS12 + TCCR2B = prescaler; + + // Caution: special procedures for setting 16 bit regs + // is handled by the compiler + OCR2A = nticks; + // Enable interrupt + #ifdef TIMSK2 + // atmega168 + TIMSK2 |= _BV(OCIE2A); + #else + // others + TIMSK |= _BV(OCIE2A); + #endif // TIMSK2 + #else + // Use timer 1 + prescaler = timerCalc(_speed, (uint16_t)-1, &nticks); + if (!prescaler) + return; // fault + TCCR1A = 0; // Output Compare pins disconnected + TCCR1B = _BV(WGM12); // Turn on CTC mode + + // convert prescaler index to TCCRnB prescaler bits CS10, CS11, CS12 + TCCR1B |= prescaler; + + // Caution: special procedures for setting 16 bit regs + // is handled by the compiler + OCR1A = nticks; + // Enable interrupt + #ifdef TIMSK1 + // atmega168 + TIMSK1 |= _BV(OCIE1A); + #else + // others + TIMSK |= _BV(OCIE1A); + #endif // TIMSK1 + #endif + #endif + +#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Photon + // Inspired by SparkIntervalTimer + // We use Timer 6 + void TimerInterruptHandler(); // Forward declaration for interrupt handler + #define SYSCORECLOCK 60000000UL // Timer clock tree uses core clock / 2 + TIM_TimeBaseInitTypeDef timerInitStructure; + NVIC_InitTypeDef nvicStructure; + TIM_TypeDef* TIMx; + uint32_t period = (1000000 / 8) / _speed; // In microseconds + uint16_t prescaler = (uint16_t)(SYSCORECLOCK / 1000000UL) - 1; //To get TIM counter clock = 1MHz + + attachSystemInterrupt(SysInterrupt_TIM6_Update, TimerInterruptHandler); + RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); + nvicStructure.NVIC_IRQChannel = TIM6_DAC_IRQn; + TIMx = TIM6; + nvicStructure.NVIC_IRQChannelPreemptionPriority = 10; + nvicStructure.NVIC_IRQChannelSubPriority = 1; + nvicStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&nvicStructure); + timerInitStructure.TIM_Prescaler = prescaler; + timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up; + timerInitStructure.TIM_Period = period; + timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; + timerInitStructure.TIM_RepetitionCounter = 0; + + TIM_TimeBaseInit(TIMx, &timerInitStructure); + TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE); + TIM_Cmd(TIMx, ENABLE); + +#elif (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) + // UsingChipKIT Core on Arduino IDE + uint32_t chipkit_timer_interrupt_handler(uint32_t currentTime); // Forward declaration + attachCoreTimerService(chipkit_timer_interrupt_handler); + +#elif (RH_PLATFORM == RH_PLATFORM_UNO32) + // Under old MPIDE, which has been discontinued: + // ON Uno32 we use timer1 + OpenTimer1(T1_ON | T1_PS_1_1 | T1_SOURCE_INT, (F_CPU / 8) / _speed); + ConfigIntTimer1(T1_INT_ON | T1_INT_PRIOR_1); + +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) + void RH_INTERRUPT_ATTR esp8266_timer_interrupt_handler(); // Forward declaration + // The - 120 is a heuristic to correct for interrupt handling overheads + _timerIncrement = (clockCyclesPerMicrosecond() * 1000000 / 8 / _speed) - 120; + timer0_isr_init(); + timer0_attachInterrupt(esp8266_timer_interrupt_handler); + timer0_write(ESP.getCycleCount() + _timerIncrement); +// timer0_write(ESP.getCycleCount() + 41660000); +#elif (RH_PLATFORM == RH_PLATFORM_ESP32) + void RH_INTERRUPT_ATTR esp32_timer_interrupt_handler(); // Forward declaration + #if ESP_ARDUINO_VERSION_MAJOR >= 3 + // Sigh, this changed, apparently in version 3. + timer = timerBegin(_speed * 8); + timerAttachInterrupt(timer, &esp32_timer_interrupt_handler); + timerAlarm(timer, 1, true, 0); + #else + // Prior to version 3 + timer = timerBegin(0, 80, true); // Alarm value will be in in us + timerAttachInterrupt(timer, &esp32_timer_interrupt_handler, true); + timerAlarmWrite(timer, 1000000 / _speed / 8, true); + timerAlarmEnable(timer); + #endif +#endif + +} + +void RH_INTERRUPT_ATTR RH_ASK::setModeIdle() +{ + if (_mode != RHModeIdle) + { + // Disable the transmitter hardware + writePtt(LOW); + writeTx(LOW); + _mode = RHModeIdle; + } +} + +void RH_INTERRUPT_ATTR RH_ASK::setModeRx() +{ + if (_mode != RHModeRx) + { + // Disable the transmitter hardware + writePtt(LOW); + writeTx(LOW); + _mode = RHModeRx; + } +} + +void RH_ASK::setModeTx() +{ + if (_mode != RHModeTx) + { + // PRepare state varibles for a new transmission + _txIndex = 0; + _txBit = 0; + _txSample = 0; + + // Enable the transmitter hardware + writePtt(HIGH); + + _mode = RHModeTx; + } +} + +// Call this often +bool RH_ASK::available() +{ + if (_mode == RHModeTx) + return false; + setModeRx(); + if (_rxBufFull) + { + validateRxBuf(); + _rxBufFull= false; + } + return _rxBufValid; +} + +bool RH_INTERRUPT_ATTR RH_ASK::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + + if (buf && len) + { + // Skip the length and 4 headers that are at the beginning of the rxBuf + // and drop the trailing 2 bytes of FCS + uint8_t message_len = _rxBufLen-RH_ASK_HEADER_LEN - 3; + if (*len > message_len) + *len = message_len; + memcpy(buf, _rxBuf+RH_ASK_HEADER_LEN+1, *len); + } + _rxBufValid = false; // Got the most recent message, delete it +// printBuffer("recv:", buf, *len); + return true; +} + +// Caution: this may block +bool RH_ASK::send(const uint8_t* data, uint8_t len) +{ + uint8_t i; + uint16_t index = 0; + uint16_t crc = 0xffff; + uint8_t *p = _txBuf + RH_ASK_PREAMBLE_LEN; // start of the message area + uint8_t count = len + 3 + RH_ASK_HEADER_LEN; // Added byte count and FCS and headers to get total number of bytes + + if (len > RH_ASK_MAX_MESSAGE_LEN) + return false; + + // Wait for transmitter to become available + waitPacketSent(); + + if (!waitCAD()) + return false; // Check channel activity + + // Encode the message length + crc = RHcrc_ccitt_update(crc, count); + p[index++] = symbols[count >> 4]; + p[index++] = symbols[count & 0xf]; + + // Encode the headers + crc = RHcrc_ccitt_update(crc, _txHeaderTo); + p[index++] = symbols[_txHeaderTo >> 4]; + p[index++] = symbols[_txHeaderTo & 0xf]; + crc = RHcrc_ccitt_update(crc, _txHeaderFrom); + p[index++] = symbols[_txHeaderFrom >> 4]; + p[index++] = symbols[_txHeaderFrom & 0xf]; + crc = RHcrc_ccitt_update(crc, _txHeaderId); + p[index++] = symbols[_txHeaderId >> 4]; + p[index++] = symbols[_txHeaderId & 0xf]; + crc = RHcrc_ccitt_update(crc, _txHeaderFlags); + p[index++] = symbols[_txHeaderFlags >> 4]; + p[index++] = symbols[_txHeaderFlags & 0xf]; + + // Encode the message into 6 bit symbols. Each byte is converted into + // 2 6-bit symbols, high nybble first, low nybble second + for (i = 0; i < len; i++) + { + crc = RHcrc_ccitt_update(crc, data[i]); + p[index++] = symbols[data[i] >> 4]; + p[index++] = symbols[data[i] & 0xf]; + } + + // Append the fcs, 16 bits before encoding (4 6-bit symbols after encoding) + // Caution: VW expects the _ones_complement_ of the CCITT CRC-16 as the FCS + // VW sends FCS as low byte then hi byte + crc = ~crc; + p[index++] = symbols[(crc >> 4) & 0xf]; + p[index++] = symbols[crc & 0xf]; + p[index++] = symbols[(crc >> 12) & 0xf]; + p[index++] = symbols[(crc >> 8) & 0xf]; + + // Total number of 6-bit symbols to send + _txBufLen = index + RH_ASK_PREAMBLE_LEN; + + // Start the low level interrupt handler sending symbols + setModeTx(); + + return true; +} + +// Read the RX data input pin, taking into account platform type and inversion. +bool RH_INTERRUPT_ATTR RH_ASK::readRx() +{ + bool value; +#if (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8) + value = ((RH_ASK_RX_PORT & (1<handleTimerInterrupt(); +} + +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && defined(ARDUINO_ARCH_SAMD) +// Arduino Zero +void TC3_Handler() +{ + // The type cast must fit with the selected timer mode + TcCount16* TC = (TcCount16*)RH_ASK_ZERO_TIMER; // get timer struct + TC->INTFLAG.bit.MC0 = 1; + thisASKDriver->handleTimerInterrupt(); +} + +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(ARDUINO_SAM_DUE) +// Arduino Due +void TC1_Handler() +{ + TC_GetStatus(RH_ASK_DUE_TIMER, 1); + thisASKDriver->handleTimerInterrupt(); +} + +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(ARDUINO_ARCH_RP2040) +void picoInterrupt() +{ + hw_clear_bits(&timer_hw->intr, 1u << RH_ASK_PICO_ALARM_NUM); + set_pico_alarm(thisASKDriver->speed()); + thisASKDriver->handleTimerInterrupt(); +} + +#elif defined(BOARD_NAME) +// ST's Arduino Core STM32, https://github.com/stm32duino/Arduino_Core_STM32 +// Declaration of the callback function changed in 1.9 + #if (STM32_CORE_VERSION >= 0x01090000) +// This really should be callback_function_t interrupt() but some platform compilers +// warn/error, thinking there should be a return value +void interrupt() + #else +void interrupt(HardwareTimer*) + #endif +{ + thisASKDriver->handleTimerInterrupt(); +} +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) && (defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH_STM32F3) || defined(ARDUINO_ARCH_STM32F4) || defined(ARDUINO_ARCH_RP2040)) +// Roger Clark Arduino STM32, https://github.com/rogerclarkmelbourne/Arduino_STM32 +void interrupt() +{ + thisASKDriver->handleTimerInterrupt(); +} + +#elif defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI) +// callback method used by timer +void timer_callback(timer_callback_args_t __attribute((unused)) *p_args) +{ + thisASKDriver->handleTimerInterrupt(); +} + +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(RH_CUBE_CELL_BOARD) +// Cube cell interrupt +void timer_callback(void) +{ + TimerStart(&timer); + thisASKDriver->handleTimerInterrupt(); +} + +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8) || (RH_PLATFORM == RH_PLATFORM_ATTINY) +// This is the interrupt service routine called when timer1 overflows +// Its job is to output the next bit from the transmitter (every 8 calls) +// and to call the PLL code if the receiver is enabled +//ISR(SIG_OUTPUT_COMPARE1A) +ISR(RH_ASK_TIMER_VECTOR) +{ + thisASKDriver->handleTimerInterrupt(); +} + +#elif (RH_PLATFORM == RH_PLATFORM_MSP430) || (RH_PLATFORM == RH_PLATFORM_STM32) +// LaunchPad, Maple +void interrupt() +{ + thisASKDriver->handleTimerInterrupt(); +} + +#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Photon +void TimerInterruptHandler() +{ + thisASKDriver->handleTimerInterrupt(); +} + +#elif (RH_PLATFORM == RH_PLATFORM_MSP430) +interrupt(TIMER0_A0_VECTOR) Timer_A_int(void) +{ + thisASKDriver->handleTimerInterrupt(); +}; + +#elif (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) +// Using ChipKIT Core on Arduino IDE +uint32_t chipkit_timer_interrupt_handler(uint32_t currentTime) +{ + thisASKDriver->handleTimerInterrupt(); + return (currentTime + ((CORE_TICK_RATE * 1000)/8)/thisASKDriver->speed()); +} + +#elif (RH_PLATFORM == RH_PLATFORM_UNO32) +// Under old MPIDE, which has been discontinued: +extern "C" +{ + void __ISR(_TIMER_1_VECTOR, ipl1) timerInterrupt(void) + { + thisASKDriver->handleTimerInterrupt(); + mT1ClearIntFlag(); // Clear timer 1 interrupt flag +} +} +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) +void RH_INTERRUPT_ATTR esp8266_timer_interrupt_handler() +{ +// timer0_write(ESP.getCycleCount() + 41660000); +// timer0_write(ESP.getCycleCount() + (clockCyclesPerMicrosecond() * 100) - 120 ); + timer0_write(ESP.getCycleCount() + thisASKDriver->_timerIncrement); +// static int toggle = 0; +// toggle = (toggle == 1) ? 0 : 1; +// digitalWrite(4, toggle); + thisASKDriver->handleTimerInterrupt(); +} +#elif (RH_PLATFORM == RH_PLATFORM_ESP32) +void RH_INTERRUPT_ATTR esp32_timer_interrupt_handler() +{ + thisASKDriver->handleTimerInterrupt(); +} +#elif (RH_PLATFORM == RH_PLATFORM_ATTINY_MEGA) +ISR(RH_ATTINY_MEGA_ASK_TIMER_VECTOR) +{ + thisASKDriver->handleTimerInterrupt(); + RH_ATTINY_MEGA_ASK_TIMER.INTFLAGS = TCB_CAPT_bm; +} +#endif + +// Convert a 6 bit encoded symbol into its 4 bit decoded equivalent +uint8_t RH_INTERRUPT_ATTR RH_ASK::symbol_6to4(uint8_t symbol) +{ + uint8_t i; + uint8_t count; + + // Linear search :-( Could have a 64 byte reverse lookup table? + // There is a little speedup here courtesy Ralph Doncaster: + // The shortcut works because bit 5 of the symbol is 1 for the last 8 + // symbols, and it is 0 for the first 8. + // So we only have to search half the table + for (i = (symbol>>2) & 8, count=8; count-- ; i++) + if (symbol == symbols[i]) return i; + + return 0; // Not found +} + +// Check whether the latest received message is complete and uncorrupted +// We should always check the FCS at user level, not interrupt level +// since it is slow +void RH_ASK::validateRxBuf() +{ + uint16_t crc = 0xffff; + // The CRC covers the byte count, headers and user data + for (uint8_t i = 0; i < _rxBufLen; i++) + crc = RHcrc_ccitt_update(crc, _rxBuf[i]); + if (crc != 0xf0b8) // CRC when buffer and expected CRC are CRC'd + { + // Reject and drop the message + _rxBad++; + _rxBufValid = false; + return; + } + + // Extract the 4 headers that follow the message length + _rxHeaderTo = _rxBuf[1]; + _rxHeaderFrom = _rxBuf[2]; + _rxHeaderId = _rxBuf[3]; + _rxHeaderFlags = _rxBuf[4]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +void RH_INTERRUPT_ATTR RH_ASK::receiveTimer() +{ + bool rxSample = readRx(); + + // Integrate each sample + if (rxSample) + _rxIntegrator++; + + if (rxSample != _rxLastSample) + { + // Transition, advance if ramp > 80, retard if < 80 + _rxPllRamp += ((_rxPllRamp < RH_ASK_RAMP_TRANSITION) + ? RH_ASK_RAMP_INC_RETARD + : RH_ASK_RAMP_INC_ADVANCE); + _rxLastSample = rxSample; + } + else + { + // No transition + // Advance ramp by standard 20 (== 160/8 samples) + _rxPllRamp += RH_ASK_RAMP_INC; + } + if (_rxPllRamp >= RH_ASK_RX_RAMP_LEN) + { + // Add this to the 12th bit of _rxBits, LSB first + // The last 12 bits are kept + _rxBits >>= 1; + + // Check the integrator to see how many samples in this cycle were high. + // If < 5 out of 8, then its declared a 0 bit, else a 1; + if (_rxIntegrator >= 5) + _rxBits |= 0x800; + + _rxPllRamp -= RH_ASK_RX_RAMP_LEN; + _rxIntegrator = 0; // Clear the integral for the next cycle + + if (_rxActive) + { + // We have the start symbol and now we are collecting message bits, + // 6 per symbol, each which has to be decoded to 4 bits + if (++_rxBitCount >= 12) + { + // Have 12 bits of encoded message == 1 byte encoded + // Decode as 2 lots of 6 bits into 2 lots of 4 bits + // The 6 lsbits are the high nybble + uint8_t this_byte = + (symbol_6to4(_rxBits & 0x3f)) << 4 + | symbol_6to4(_rxBits >> 6); + + // The first decoded byte is the byte count of the following message + // the count includes the byte count and the 2 trailing FCS bytes + // REVISIT: may also include the ACK flag at 0x40 + if (_rxBufLen == 0) + { + // The first byte is the byte count + // Check it for sensibility. It cant be less than 7, since it + // includes the byte count itself, the 4 byte header and the 2 byte FCS + _rxCount = this_byte; + if (_rxCount < 7 || _rxCount > RH_ASK_MAX_PAYLOAD_LEN) + { + // Stupid message length, drop the whole thing + _rxActive = false; + _rxBad++; + return; + } + } + _rxBuf[_rxBufLen++] = this_byte; + + if (_rxBufLen >= _rxCount) + { + // Got all the bytes now + _rxActive = false; + _rxBufFull = true; + setModeIdle(); + } + _rxBitCount = 0; + } + } + // Not in a message, see if we have a start symbol + else if (_rxBits == RH_ASK_START_SYMBOL) + { + // Have start symbol, start collecting message + _rxActive = true; + _rxBitCount = 0; + _rxBufLen = 0; + } + } +} + +void RH_INTERRUPT_ATTR RH_ASK::transmitTimer() +{ + if (_txSample++ == 0) + { + // Send next bit + // Symbols are sent LSB first + // Finished sending the whole message? (after waiting one bit period + // since the last bit) + if (_txIndex >= _txBufLen) + { + setModeIdle(); + _txGood++; + } + else + { + writeTx(_txBuf[_txIndex] & (1 << _txBit++)); + if (_txBit >= 6) + { + _txBit = 0; + _txIndex++; + } + } + } + + if (_txSample > 7) + _txSample = 0; +} + +void RH_INTERRUPT_ATTR RH_ASK::handleTimerInterrupt() +{ + if (_mode == RHModeRx) + receiveTimer(); // Receiving + else if (_mode == RHModeTx) + transmitTimer(); // Transmitting +} + +#endif //_SAMD51__ diff --git a/RH_ASK.h b/RH_ASK.h new file mode 100644 index 0000000..b1846cd --- /dev/null +++ b/RH_ASK.h @@ -0,0 +1,452 @@ +// RH_ASK.h +// +// Copyright (C) 2014 Mike McCauley +// $Id: RH_ASK.h,v 1.22 2020/05/06 22:26:45 mikem Exp $ + +#ifndef RH_ASK_h +#define RH_ASK_h + +#include + +// Maximum message length (including the headers, byte count and FCS) we are willing to support +// This is pretty arbitrary +#define RH_ASK_MAX_PAYLOAD_LEN 67 + +// The length of the headers we add (To, From, Id, Flags) +// The headers are inside the payload and are therefore protected by the FCS +#define RH_ASK_HEADER_LEN 4 + +// This is the maximum message length that can be supported by this library. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS +#ifndef RH_ASK_MAX_MESSAGE_LEN + #define RH_ASK_MAX_MESSAGE_LEN (RH_ASK_MAX_PAYLOAD_LEN - RH_ASK_HEADER_LEN - 3) +#endif + +#if !defined(RH_ASK_RX_SAMPLES_PER_BIT) +/// Number of samples per bit + #define RH_ASK_RX_SAMPLES_PER_BIT 8 +#endif //RH_ASK_RX_SAMPLES_PER_BIT + +/// The size of the receiver ramp. Ramp wraps modulo this number +#define RH_ASK_RX_RAMP_LEN 160 + +// Ramp adjustment parameters +// Standard is if a transition occurs before RH_ASK_RAMP_TRANSITION (80) in the ramp, +// the ramp is retarded by adding RH_ASK_RAMP_INC_RETARD (11) +// else by adding RH_ASK_RAMP_INC_ADVANCE (29) +// If there is no transition it is adjusted by RH_ASK_RAMP_INC (20) +/// Internal ramp adjustment parameter +#define RH_ASK_RAMP_INC (RH_ASK_RX_RAMP_LEN/RH_ASK_RX_SAMPLES_PER_BIT) +/// Internal ramp adjustment parameter +#define RH_ASK_RAMP_TRANSITION RH_ASK_RX_RAMP_LEN/2 +/// Internal ramp adjustment parameter +#define RH_ASK_RAMP_ADJUST 9 +/// Internal ramp adjustment parameter +#define RH_ASK_RAMP_INC_RETARD (RH_ASK_RAMP_INC-RH_ASK_RAMP_ADJUST) +/// Internal ramp adjustment parameter +#define RH_ASK_RAMP_INC_ADVANCE (RH_ASK_RAMP_INC+RH_ASK_RAMP_ADJUST) + +/// Outgoing message bits grouped as 6-bit words +/// 36 alternating 1/0 bits, followed by 12 bits of start symbol (together called the preamble) +/// Followed immediately by the 4-6 bit encoded byte count, +/// message buffer and 2 byte FCS +/// Each byte from the byte count on is translated into 2x6-bit words +/// Caution, each symbol is transmitted LSBit first, +/// but each byte is transmitted high nybble first +/// This is the number of 6 bit nibbles in the preamble +#define RH_ASK_PREAMBLE_LEN 8 + +///////////////////////////////////////////////////////////////////// +/// \class RH_ASK RH_ASK.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via inexpensive ASK (Amplitude Shift Keying) or +/// OOK (On Off Keying) RF transceivers. +/// +/// The message format and software technology is based on our earlier VirtualWire library +/// (http://www.airspayce.com/mikem/arduino/VirtualWire), with which it is compatible. +/// See http://www.airspayce.com/mikem/arduino/VirtualWire.pdf for more details. +/// VirtualWire is now obsolete and unsupported and is replaced by this library. +/// +/// RH_ASK is a Driver for Arduino, Maple and others that provides features to send short +/// messages, without addressing, retransmit or acknowledgment, a bit like UDP +/// over wireless, using ASK (amplitude shift keying). Supports a number of +/// inexpensive radio transmitters and receivers. All that is required is +/// transmit data, receive data and (for transmitters, optionally) a PTT +/// transmitter enable. Can also be used over various analog connections (not just a data radio), +/// such as the audio channel of an A/V sender, or long TTL lines. +/// +/// It is intended to be compatible with the RF Monolithics (www.rfm.com) +/// Virtual Wire protocol, but this has not been tested. +/// +/// Does not use the Arduino UART. Messages are sent with a training preamble, +/// message length and checksum. Messages are sent with 4-to-6 bit encoding +/// for good DC balance, and a CRC checksum for message integrity. +/// +/// But why not just use a UART connected directly to the +/// transmitter/receiver? As discussed in the RFM documentation, ASK receivers +/// require a burst of training pulses to synchronize the transmitter and +/// receiver, and also requires good balance between 0s and 1s in the message +/// stream in order to maintain the DC balance of the message. UARTs do not +/// provide these. They work a bit with ASK wireless, but not as well as this +/// code. +/// +/// \par Theory of operation +/// +/// See ASH Transceiver Software Designer's Guide of 2002.08.07 +/// http://wireless.murata.com/media/products/apnotes/tr_swg05.pdf +/// +/// http://web.engr.oregonstate.edu/~moon/research/files/cas2_mar_07_dpll.pdf while not directly relevant +/// is also interesting. +/// +/// \par Implementation Details +/// +/// Messages of up to RH_ASK_MAX_PAYLOAD_LEN (67) bytes can be sent +/// Each message is transmitted as: +/// +/// - 36 bit training preamble consisting of 0-1 bit pairs +/// - 12 bit start symbol 0xb38 +/// - 1 byte of message length byte count (4 to 30), count includes byte count and FCS bytes +/// - n message bytes (including 4 bytes of header), maximum n is RH_ASK_MAX_MESSAGE_LEN + 4 (64) +/// - 2 bytes FCS, sent low byte-hi byte +/// +/// Everything after the start symbol is encoded 4 to 6 bits, Therefore a byte in the message +/// is encoded as 2x6 bit symbols, sent hi nybble, low nybble. Each symbol is sent LSBit +/// first. The message may consist of any binary digits. +/// +/// The Arduino Diecimila clock rate is 16MHz => 62.5ns/cycle. +/// For an RF bit rate of 2000 bps, need 500microsec bit period. +/// The ramp requires 8 samples per bit period, so need 62.5microsec per sample => interrupt tick is 62.5microsec. +/// +/// The maximum packet length consists of +/// (6 + 2 + RH_ASK_MAX_MESSAGE_LEN*2) * 6 = 768 bits = 0.384 secs (at 2000 bps). +/// where RH_ASK_MAX_MESSAGE_LEN is RH_ASK_MAX_PAYLOAD_LEN - 7 (= 60). +/// The code consists of an ISR interrupt handler. Most of the work is done in the interrupt +/// handler for both transmit and receive, but some is done from the user level. Expensive +/// functions like CRC computations are always done in the user level. +/// +/// \par Supported Hardware +/// +/// A range of communications +/// hardware is supported. The ones listed below are available in common retail +/// outlets in Australia and other countries for under $10 per unit. Many +/// other modules may also work with this software. +/// +/// Runs on a wide range of Arduino processors using Arduino IDE 1.0 or later. +/// Also runs on on Energia, +/// with MSP430G2553 / G2452 and Arduino with ATMega328 (courtesy Yannick DEVOS - XV4Y), +/// but untested by us. It also runs on Teensy 3.0 (courtesy of Paul +/// Stoffregen), but untested by us. Also compiles and runs on ATtiny85 in +/// Arduino environment, courtesy r4z0r7o3. Also compiles on maple-ide-v0.0.12, +/// and runs on Maple, flymaple 1.1 etc. Runs on ATmega8/168 (Arduino Diecimila, +/// Uno etc), ATmega328 and can run on almost any other AVR8 platform, +/// without relying on the Arduino framework, by properly configuring the +/// library editing the RH_ASK.h header file for describing the access +/// to IO pins and for setting up the timer. +/// Runs on ChipKIT Core supported processors such as Uno32 etc. +/// +/// - Receivers +/// - RX-B1 (433.92MHz) (also known as ST-RX04-ASK) +/// - RFM83C from HopeRF http://www.hoperfusa.com/details.jsp?pid=126 +/// - SYN480R and other similar ASK receivers +/// - Transmitters: +/// - TX-C1 (433.92MHz) +/// - RFM85 from HopeRF http://www.hoperfusa.com/details.jsp?pid=127 +/// - SYN115, F115 and other similar ASK transmitters +/// - Transceivers +/// - DR3100 (433.92MHz) +/// +/// \par Interoperation with other systems +/// RH_ASK is reported to be supported by RTL-SDR https://www.rtl-sdr.com/tag/rtl_433/ +/// +/// \par Connecting to Arduino +/// +/// Most transmitters can be connected to Arduino like this: +/// \code +/// Arduino Transmitter +/// GND------------------------------GND +/// D12------------------------------Data +/// 5V-------------------------------VCC +/// \endcode +/// +/// Most receivers can be connected to Arduino like this: +/// \code +/// Arduino Receiver +/// GND------------------------------GND +/// D11------------------------------Data +/// 5V-------------------------------VCC +/// SHUT (not connected) +/// WAKEB (not connected) +/// GND | +/// ANT |- connect to your antenna syetem +/// \endcode +/// +/// RH_ASK works with ATTiny85, using Arduino 1.0.5 and tinycore from +/// https://code.google.com/p/arduino-tiny/downloads/detail?name=arduino-tiny-0100-0018.zip +/// Tested with the examples ask_transmitter and ask_receiver on ATTiny85. +/// Caution: The RAM memory requirements on an ATTiny85 are *very* tight. Even the bare bones +/// ask_transmitter sketch barely fits in eh RAM available on the ATTiny85. Its unlikely to work on +/// smaller ATTinys such as the ATTiny45 etc. If you have wierd behaviour, consider +/// reducing the size of RH_ASK_MAX_PAYLOAD_LEN to the minimum you can work with. +/// Caution: the default internal clock speed on an ATTiny85 is 1MHz. You MUST set the internal clock speed +/// to 8MHz. You can do this with Arduino IDE, tineycore and ArduinoISP by setting the board type to "ATtiny85@8MHz', +/// setting theProgrammer to 'Arduino as ISP' and selecting Tools->Burn Bootloader. This does not actually burn a +/// bootloader into the tiny, it just changes the fuses so the chip runs at 8MHz. +/// If you run the chip at 1MHz, you will get RK_ASK speeds 1/8th of the expected. +/// +/// Initialise RH_ASK for ATTiny85 like this: +/// \code +/// // #include // comment this out, not needed +/// RH_ASK driver(2000, 4, 3); // 200bps, TX on D3 (pin 2), RX on D4 (pin 3) +/// \endcode +/// then: +/// Connect D3 (pin 2) as the output to the transmitter +/// Connect D4 (pin 3) as the input from the receiver. +/// +/// With AtTiny x17 (such as 3217 etc) using Spencer Kondes megaTinyCore, You can initialise like this: +/// RH_ASK driver(2000, 6, 7); +/// which will transmit on digital pin 7 == PB4 == physical pin 12 on Attiny x17 +/// and receive on digital pin 6 == PB5 == physical pin 11 on Attiny x17 +/// Uses Timer B1. +/// +/// With AtTiny x16 (such as 3216 etc) using Spencer Kondes megaTinyCore, You can initialise like this: +/// RH_ASK driver(2000, 11, 12); +/// which will transmit on digital pin 12 == PC2 == physical pin 14 on Attiny x16 +/// and receive on digital pin 11 == PC1 == physical pin 13 on Attiny x16 +/// Uses Timer B1. +/// +/// With AtTiny x14 (such as 1614 etc) using Spencer Kondes megaTinyCore, You can initialise like this: +/// RH_ASK driver(2000, 6, 7); +/// which will transmit on digital pin 7 == PB0 == physical pin 9 on Attiny x14 +/// and receive on digital pin 6 == PB1 == physical pin 8 on Attiny x16 +/// Uses Timer B1. +/// +/// For testing purposes you can connect 2 Arduino RH_ASK instances directly, by +/// connecting pin 12 of one to 11 of the other and vice versa, like this for a duplex connection: +/// +/// \code +/// Arduino 1 wires Arduino 1 +/// D11-----------------------------D12 +/// D12-----------------------------D11 +/// GND-----------------------------GND +/// \endcode +/// +/// You can also connect 2 RH_ASK instances over a suitable analog +/// transmitter/receiver, such as the audio channel of an A/V transmitter/receiver. You may need +/// buffers at each end of the connection to convert the 0-5V digital output to a suitable analog voltage. +/// +/// Measured power output from RFM85 at 5V was 18dBm. +/// +/// \par ESP8266 +/// This module has been tested with the ESP8266 using an ESP-12 on a breakout board +/// ESP-12E SMD Adaptor Board with Power Regulator from tronixlabs +/// http://tronixlabs.com.au/wireless/esp8266/esp8266-esp-12e-smd-adaptor-board-with-power-regulator-australia/ +/// compiled on Arduino 1.6.5 and the ESP8266 support 2.0 installed with Board Manager. +/// CAUTION: do not use pin 11 for IO with this chip: it will cause the sketch to hang. Instead +/// use constructor arguments to configure different pins, eg: +/// \code +/// RH_ASK driver(2000, 2, 4, 5); +/// \endcode +/// Which will initialise the driver at 2000 bps, recieve on GPIO2, transmit on GPIO4, PTT on GPIO5. +/// Caution: on the tronixlabs breakout board, pins 4 and 5 may be labelled vice-versa. +/// +/// \par Timers +/// The RH_ASK driver uses a timer-driven interrupt to generate 8 interrupts per bit period. RH_ASK +/// takes over a timer on Arduino-like platforms. By default it takes over Timer 1. You can force it +/// to use Timer 2 instead by enabling the define RH_ASK_ARDUINO_USE_TIMER2 near the top of RH_ASK.cpp +/// On Arduino Zero it takes over timer TC3. On Arduino Due it takes over timer +/// TC0. On ESP8266, takes over timer0 (which conflicts with ServoTimer0). +/// +/// Caution: ATTiny85 has only 2 timers, one (timer 0) usually used for +/// millis() and one (timer 1) for PWM analog outputs. The RH_ASK Driver +/// library, when built for ATTiny85, takes over timer 0, which prevents use +/// of millis() etc but does permit analog outputs. This will affect the accuracy of millis() and time +/// measurement. +/// +/// \par STM32 F4 Discovery with Arduino and Arduino_STM32 +/// You can initialise the driver like this: +/// \code +/// RH_ASK driver(2000, PA3, PA4); +/// \endcode +/// and connect the serial to pins PA3 and PA4 +class RH_ASK : public RHGenericDriver +{ +public: + /// Constructor. + /// At present only one instance of RH_ASK per sketch is supported. + /// \param[in] speed The desired bit rate in bits per second + /// \param[in] rxPin The pin that is used to get data from the receiver + /// \param[in] txPin The pin that is used to send data to the transmitter + /// \param[in] pttPin The pin that is connected to the transmitter controller. It will be set HIGH to enable the transmitter (unless pttInverted is true). + /// \param[in] pttInverted true if you desire the pttin to be inverted so that LOW wil enable the transmitter. + RH_ASK(uint16_t speed = 2000, uint8_t rxPin = 11, uint8_t txPin = 12, uint8_t pttPin = 10, bool pttInverted = false); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Tests whether a new message is available + /// from the Driver. + /// On most drivers, this will also put the Driver into RHModeRx mode until + /// a message is actually received bythe transport, when it wil be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available(); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number will be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + RH_INTERRUPT_ATTR virtual bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is NOT permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send (> 0) + /// \return true if the message length was valid and it was correctly queued for transmit + virtual bool send(const uint8_t* data, uint8_t len); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + RH_INTERRUPT_ATTR void setModeIdle(); + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver in the RF69. + RH_INTERRUPT_ATTR void setModeRx(); + + /// If current mode is Rx or Idle, changes it to Rx. F + /// Starts the transmitter in the RF69. + void setModeTx(); + + /// dont call this it used by the interrupt handler + void RH_INTERRUPT_ATTR handleTimerInterrupt(); + + /// Returns the current speed in bits per second + /// \return The current speed in bits per second + uint16_t speed() { return _speed;} + +#if (RH_PLATFORM == RH_PLATFORM_ESP8266) + /// ESP8266 timer0 increment value + uint32_t _timerIncrement; +#endif + +protected: + /// Helper function for calculating timer ticks + uint8_t timerCalc(uint16_t speed, uint16_t max_ticks, uint16_t *nticks); + + /// Set up the timer and its interrutps so the interrupt handler is called at the right frequency + void timerSetup(); + + /// Read the rxPin in a platform dependent way, taking into account whether it is inverted or not + RH_INTERRUPT_ATTR bool readRx(); + + /// Write the txPin in a platform dependent way + void writeTx(bool value); + + /// Write the txPin in a platform dependent way, taking into account whether it is inverted or not + void writePtt(bool value); + + /// Translates a 6 bit symbol to its 4 bit plaintext equivalent + RH_INTERRUPT_ATTR uint8_t symbol_6to4(uint8_t symbol); + + /// The receiver handler function, called a 8 times the bit rate + void receiveTimer(); + + /// The transmitter handler function, called a 8 times the bit rate + void transmitTimer(); + + /// Check whether the latest received message is complete and uncorrupted + /// We should always check the FCS at user level, not interrupt level + /// since it is slow + void validateRxBuf(); + + /// Configure bit rate in bits per second + uint16_t _speed; + + /// The configure receiver pin + uint8_t _rxPin; + + /// The configure transmitter pin + uint8_t _txPin; + + /// The configured transmitter enable pin + uint8_t _pttPin; + + /// True of the sense of the rxPin is to be inverted + bool _rxInverted; + + /// True of the sense of the pttPin is to be inverted + bool _pttInverted; + + // Used in the interrupt handlers + /// Buf is filled but not validated + volatile bool _rxBufFull; + + /// Buf is full and valid + volatile bool _rxBufValid; + + /// Last digital input from the rx data pin + volatile bool _rxLastSample; + + /// This is the integrate and dump integral. If there are <5 0 samples in the PLL cycle + /// the bit is declared a 0, else a 1 + volatile uint8_t _rxIntegrator; + + /// PLL ramp, varies between 0 and RH_ASK_RX_RAMP_LEN-1 (159) over + /// RH_ASK_RX_SAMPLES_PER_BIT (8) samples per nominal bit time. + /// When the PLL is synchronised, bit transitions happen at about the + /// 0 mark. + volatile uint8_t _rxPllRamp; + + /// Flag indicates if we have seen the start symbol of a new message and are + /// in the processes of reading and decoding it + volatile uint8_t _rxActive; + + /// Last 12 bits received, so we can look for the start symbol + volatile uint16_t _rxBits; + + /// How many bits of message we have received. Ranges from 0 to 12 + volatile uint8_t _rxBitCount; + + /// The incoming message buffer + uint8_t _rxBuf[RH_ASK_MAX_PAYLOAD_LEN]; + + /// The incoming message expected length + volatile uint8_t _rxCount; + + /// The incoming message buffer length received so far + volatile uint8_t _rxBufLen; + + /// Index of the next symbol to send. Ranges from 0 to vw_tx_len + uint8_t _txIndex; + + /// Bit number of next bit to send + uint8_t _txBit; + + /// Sample number for the transmitter. Runs 0 to 7 during one bit interval + uint8_t _txSample; + + /// The transmitter buffer in _symbols_ not data octets + uint8_t _txBuf[(RH_ASK_MAX_PAYLOAD_LEN * 2) + RH_ASK_PREAMBLE_LEN]; + + /// Number of symbols in _txBuf to be sent; + uint8_t _txBufLen; + +}; + +/// @example ask_reliable_datagram_client.ino +/// @example ask_reliable_datagram_server.ino +/// @example ask_transmitter.ino +/// @example ask_receiver.ino +#endif diff --git a/RH_CC110.cpp b/RH_CC110.cpp new file mode 100644 index 0000000..4eb0c58 --- /dev/null +++ b/RH_CC110.cpp @@ -0,0 +1,506 @@ +// RH_CC110.cpp +// +// Driver for Texas Instruments CC110L transceiver. +// +// Copyright (C) 2016 Mike McCauley +// $Id: RH_CC110.cpp,v 1.11 2020/01/05 07:02:23 mikem Exp $ + +#include + +// Interrupt vectors for the 3 Arduino interrupt pins +// Each interrupt can be handled by a different instance of RH_CC110, allowing you to have +// 2 or more LORAs per Arduino +RH_CC110* RH_CC110::_deviceForInterrupt[RH_CC110_NUM_INTERRUPTS] = {0, 0, 0}; +uint8_t RH_CC110::_interruptCount = 0; // Index into _deviceForInterrupt for next device + +// We need 2 tables of modem configuration registers, since some values change depending on the Xtal frequency +// These are indexed by the values of ModemConfigChoice +// Canned modem configurations generated with the TI SmartRF Studio v7 version 2.3.0 on boodgie +// based on the sample 'Typical settings' +// Stored in flash (program) memory to save SRAM +// For 26MHz crystals +PROGMEM static const RH_CC110::ModemConfig MODEM_CONFIG_TABLE_26MHZ[] = +{ + // 0B 0C 10 11 12 15 19 1A 1B 1C 1D 21 22 23 24 25 26 2C 2D 2E + {0x06, 0x00, 0xf5, 0x83, 0x13, 0x15, 0x16, 0x6c, 0x03, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb1_2Fd5_2 + {0x06, 0x00, 0xf6, 0x83, 0x13, 0x15, 0x16, 0x6c, 0x03, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb2_4Fd5_2 + {0x06, 0x00, 0xc7, 0x83, 0x13, 0x40, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb4_8Fd25_4 + {0x06, 0x00, 0xc8, 0x93, 0x13, 0x34, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb10Fd19 + {0x06, 0x00, 0xca, 0x83, 0x13, 0x35, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb38_4Fd20 + {0x08, 0x00, 0x7b, 0x83, 0x13, 0x42, 0x1d, 0x1c, 0xc7, 0x00, 0xb2, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb76_8Fd32 + {0x08, 0x00, 0x5b, 0xf8, 0x13, 0x47, 0x1d, 0x1c, 0xc7, 0x00, 0xb2, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x81, 0x31, 0x09}, // GFSK_Rb100Fd47 + {0x0c, 0x00, 0x2d, 0x3b, 0x13, 0x62, 0x1d, 0x1c, 0xc7, 0x00, 0xb0, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x88, 0x31, 0x09}, // GFSK_Rb250Fd127 +}; + +// For 27MHz crystals +PROGMEM static const RH_CC110::ModemConfig MODEM_CONFIG_TABLE_27MHZ[] = +{ + // 0B 0C 10 11 12 15 19 1A 1B 1C 1D 21 22 23 24 25 26 2C 2D 2E + {0x06, 0x00, 0xf5, 0x75, 0x13, 0x14, 0x16, 0x6c, 0x03, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb1_2Fd5_2 + {0x06, 0x00, 0xf6, 0x75, 0x13, 0x14, 0x16, 0x6c, 0x03, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb2_4Fd5_2 + {0x06, 0x00, 0xc7, 0x75, 0x13, 0x37, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb4_8Fd25_4 + {0x06, 0x00, 0xc8, 0x84, 0x13, 0x33, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb10Fd19 + {0x06, 0x00, 0xca, 0x75, 0x13, 0x34, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb38_4Fd20 + {0x08, 0x00, 0x7b, 0x75, 0x13, 0x42, 0x1d, 0x1c, 0xc7, 0x00, 0xb2, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb76_8Fd32 + {0x08, 0x00, 0x5b, 0xf8, 0x13, 0x47, 0x1d, 0x1c, 0xc7, 0x00, 0xb2, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x81, 0x31, 0x09}, // GFSK_Rb100Fd47 + {0x0c, 0x00, 0x2d, 0x2f, 0x13, 0x62, 0x1d, 0x1c, 0xc7, 0x00, 0xb0, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x88, 0x31, 0x09}, // GFSK_Rb250Fd127 +}; + +// These power outputs are based on the suggested optimum values for +// multilayer inductors in the 915MHz frequency band. Per table 5-15. +// Yes these are not linear. +// Caution: this table is indexed by the values of enum TransmitPower +// Do not change one without changing the other. +// If you do not like these values, use setPaTable() directly. +PROGMEM static const uint8_t paPowerValues[] = +{ + 0x03, // -30dBm + 0x0e, // -20dBm + 0x1e, // -15dBm + 0x27, // -10dBm + 0x8e, // 0dBm + 0xcd, // 5dBm + 0xc7, // 7dBm + 0xc0, // 10dBm +}; + +RH_CC110::RH_CC110(uint8_t slaveSelectPin, uint8_t interruptPin, bool is27MHz, RHGenericSPI& spi) + : + RHNRFSPIDriver(slaveSelectPin, spi), + _rxBufValid(false), + _is27MHz(is27MHz) +{ + _interruptPin = interruptPin; + _myInterruptIndex = 0xff; // Not allocated yet +} + +bool RH_CC110::init() +{ + if (!RHNRFSPIDriver::init()) + return false; + + // Determine the interrupt number that corresponds to the interruptPin + int interruptNumber = digitalPinToInterrupt(_interruptPin); + if (interruptNumber == NOT_AN_INTERRUPT) + return false; +#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER + interruptNumber = _interruptPin; +#endif + + // Tell the low level SPI interface we will use SPI within this interrupt + spiUsingInterrupt(interruptNumber); + + // Reset the chip + // Strobe the reset + uint8_t val = spiCommand(RH_CC110_STROBE_30_SRES); // Reset + delay(100); + val = spiCommand(RH_CC110_STROBE_36_SIDLE); // IDLE + if (val != 0x0f) + return false; // No chip there or reset failed. + + // Add by Adrien van den Bossche for Teensy + // ARM M4 requires the below. else pin interrupt doesn't work properly. + // On all other platforms, its innocuous, belt and braces + pinMode(_interruptPin, INPUT); + + // Set up interrupt handler + // Since there are a limited number of interrupt glue functions isr*() available, + // we can only support a limited number of devices simultaneously + // ON some devices, notably most Arduinos, the interrupt pin passed in is actuallt the + // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping + // yourself based on knwledge of what Arduino board you are running on. + if (_myInterruptIndex == 0xff) + { + // First run, no interrupt allocated yet + if (_interruptCount <= RH_CC110_NUM_INTERRUPTS) + _myInterruptIndex = _interruptCount++; + else + return false; // Too many devices, not enough interrupt vectors + } + _deviceForInterrupt[_myInterruptIndex] = this; + if (_myInterruptIndex == 0) + attachInterrupt(interruptNumber, isr0, RISING); + else if (_myInterruptIndex == 1) + attachInterrupt(interruptNumber, isr1, RISING); + else if (_myInterruptIndex == 2) + attachInterrupt(interruptNumber, isr2, RISING); + else + return false; // Too many devices, not enough interrupt vectors + + spiWriteRegister(RH_CC110_REG_02_IOCFG0, RH_CC110_GDO_CFG_CRC_OK_AUTORESET); // gdo0 interrupt on CRC_OK + spiWriteRegister(RH_CC110_REG_06_PKTLEN, RH_CC110_MAX_PAYLOAD_LEN); // max packet length + spiWriteRegister(RH_CC110_REG_07_PKTCTRL1, RH_CC110_CRC_AUTOFLUSH | RH_CC110_APPEND_STATUS); // append status, crc autoflush, no addr check + spiWriteRegister(RH_CC110_REG_08_PKTCTRL0, RH_CC110_PKT_FORMAT_NORMAL | RH_CC110_CRC_EN | RH_CC110_LENGTH_CONFIG_VARIABLE); + spiWriteRegister(RH_CC110_REG_13_MDMCFG1, RH_CC110_NUM_PREAMBLE_4); // 4 preamble bytes, chan spacing not used + spiWriteRegister(RH_CC110_REG_17_MCSM1, RH_CC110_CCA_MODE_RSSI_PACKET | RH_CC110_RXOFF_MODE_RX | RH_CC110_TXOFF_MODE_IDLE); + spiWriteRegister(RH_CC110_REG_18_MCSM0, RH_CC110_FS_AUTOCAL_FROM_IDLE | RH_CC110_PO_TIMEOUT_64); // cal when going to tx or rx + spiWriteRegister(RH_CC110_REG_20_WORCTRL, 0xfb); // from smartrf + spiWriteRegister(RH_CC110_REG_29_FSTEST, 0x59); // from smartrf + spiWriteRegister(RH_CC110_REG_2A_PTEST, 0x7f); // from smartrf + spiWriteRegister(RH_CC110_REG_2B_AGCTEST, 0x3f); // from smartrf + + // Set some reasonable default values + uint8_t syncWords[] = { 0xd3, 0x91 }; + setSyncWords(syncWords, sizeof(syncWords)); + setTxPower(TransmitPower5dBm); + setFrequency(915.0); + setModemConfig(GFSK_Rb1_2Fd5_2); + return true; +} + +void RH_CC110::setIs27MHz(bool is27MHz) +{ + _is27MHz = is27MHz; +} + +// C++ level interrupt handler for this instance +// We use this to get RxDone and TxDone interrupts +void RH_CC110::handleInterrupt() +{ +// Serial.println("I"); + if (_mode == RHModeRx) + { + // Radio is configured to stay in RX until we move it to IDLE after a CRC_OK message for us + // We only get interrupts in RX mode, on CRC_OK + +#if 0 + // SIGH: reading RH_CC110_REG_34_RSSI here does not yield the RSSI of when the packet was received + // Must get it from the end of the packet read + uint8_t raw_rssi = spiBurstReadRegister(RH_CC110_REG_34_RSSI); // Was set when sync word was detected + // Conversion of RSSI value to received power level in dBm per TI section 5.18.2 + if (raw_rssi >= 128) + _lastRssi = (((int16_t)raw_rssi - 256) / 2) - 74; + else + _lastRssi = ((int16_t)raw_rssi / 2) - 74; +#endif + + _bufLen = spiReadRegister(RH_CC110_REG_3F_FIFO); + if (_bufLen < 4) + { + // Something wrong there, flush the FIFO + spiCommand(RH_CC110_STROBE_3A_SFRX); + clearRxBuf(); + return; + } + + // Because we have RH_CC110_APPEND_STATUS set, we can read another 2 octets, the RSSI and the CRC state + spiBurstRead(RH_CC110_REG_3F_FIFO | RH_CC110_SPI_BURST_MASK | RH_CC110_SPI_READ_MASK, _buf, _bufLen + 2); + // And then decode the RSSI + int raw_rssi = _buf[_bufLen]; + if (raw_rssi >= 128) + _lastRssi = (((int16_t)raw_rssi - 256) / 2) - 74; + else + _lastRssi = ((int16_t)raw_rssi / 2) - 74; + + // All good so far. See if its for us + validateRxBuf(); + if (_rxBufValid) + setModeIdle(); // Done + } +} + +// These are low level functions that call the interrupt handler for the correct +// instance of RH_CC110. +// 3 interrupts allows us to have 3 different devices +void RH_INTERRUPT_ATTR RH_CC110::isr0() +{ + if (_deviceForInterrupt[0]) + _deviceForInterrupt[0]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_CC110::isr1() +{ + if (_deviceForInterrupt[1]) + _deviceForInterrupt[1]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_CC110::isr2() +{ + if (_deviceForInterrupt[2]) + _deviceForInterrupt[2]->handleInterrupt(); +} + +uint8_t RH_CC110::spiReadRegister(uint8_t reg) +{ + return spiRead((reg & 0x3f) | RH_CC110_SPI_READ_MASK); +} + +uint8_t RH_CC110::spiBurstReadRegister(uint8_t reg) +{ + return spiRead((reg & 0x3f) | RH_CC110_SPI_READ_MASK | RH_CC110_SPI_BURST_MASK); +} + +uint8_t RH_CC110::spiWriteRegister(uint8_t reg, uint8_t val) +{ + return spiWrite((reg & 0x3f), val); +} + +uint8_t RH_CC110::spiBurstWriteRegister(uint8_t reg, const uint8_t* src, uint8_t len) +{ + return spiBurstWrite((reg & 0x3f) | RH_CC110_SPI_BURST_MASK, src, len); +} + +bool RH_CC110::printRegisters() +{ +#ifdef RH_HAVE_SERIAL + uint8_t i; + for (i = 0; i <= 0x2f; i++) + { + Serial.print(i, HEX); + Serial.print(": "); + Serial.println(spiReadRegister(i), HEX); + } + // Burst registers + for (i = 0x30; i <= 0x3e; i++) + { + Serial.print(i, HEX); + Serial.print(": "); + Serial.println(spiBurstReadRegister(i), HEX); + } +#endif + return true; +} + +// Check whether the latest received message is complete and uncorrupted +void RH_CC110::validateRxBuf() +{ + if (_bufLen < 4) + return; // Too short to be a real message + // Extract the 4 headers + _rxHeaderTo = _buf[0]; + _rxHeaderFrom = _buf[1]; + _rxHeaderId = _buf[2]; + _rxHeaderFlags = _buf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +bool RH_CC110::available() +{ + if (_mode == RHModeTx) + return false; + if (_rxBufValid) // Will be set by the interrupt handler when a good message is received + return true; + setModeRx(); // Make sure we are receiving + return false; // Nothing yet +} + +void RH_CC110::clearRxBuf() +{ + ATOMIC_BLOCK_START; + _rxBufValid = false; + _bufLen = 0; + ATOMIC_BLOCK_END; +} + +bool RH_CC110::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + + if (buf && len) + { + ATOMIC_BLOCK_START; + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen - RH_CC110_HEADER_LEN) + *len = _bufLen - RH_CC110_HEADER_LEN; + memcpy(buf, _buf + RH_CC110_HEADER_LEN, *len); + ATOMIC_BLOCK_END; + } + clearRxBuf(); // This message accepted and cleared + + return true; +} + +bool RH_CC110::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_CC110_MAX_MESSAGE_LEN) + return false; + + waitPacketSent(); // Make sure we dont interrupt an outgoing message + setModeIdle(); + + if (!waitCAD()) + return false; // Check channel activity + + spiWriteRegister(RH_CC110_REG_3F_FIFO, len + RH_CC110_HEADER_LEN); + spiWriteRegister(RH_CC110_REG_3F_FIFO,_txHeaderTo); + spiWriteRegister(RH_CC110_REG_3F_FIFO,_txHeaderFrom); + spiWriteRegister(RH_CC110_REG_3F_FIFO,_txHeaderId); + spiWriteRegister(RH_CC110_REG_3F_FIFO,_txHeaderFlags); + spiBurstWriteRegister(RH_CC110_REG_3F_FIFO, data, len); + + // Radio returns to Idle when TX is finished + // need waitPacketSent() to detect change of _mode and TX completion + setModeTx(); + + return true; +} + +uint8_t RH_CC110::maxMessageLength() +{ + return RH_CC110_MAX_MESSAGE_LEN; +} + +void RH_CC110::handleOverFlows(uint8_t status) +{ + spiCommand(RH_CC110_STROBE_3A_SFRX); + //Handle RX and TX overflows so we don't get stuck in either state + if( (status&RH_CC110_STATUS_RXFIFO_OVERFLOW) == RH_CC110_STATUS_RXFIFO_OVERFLOW ) { + spiCommand(RH_CC110_STROBE_3A_SFRX); + clearRxBuf(); + } + else if( (status&RH_CC110_STATUS_TXFIFO_UNDERFLOW) == RH_CC110_STATUS_TXFIFO_UNDERFLOW ) { + spiCommand(RH_CC110_STROBE_3B_SFTX); + } +} + +void RH_CC110::setModeIdle() +{ + if (_mode != RHModeIdle) + { + uint8_t status = spiCommand(RH_CC110_STROBE_36_SIDLE); + _mode = RHModeIdle; + handleOverFlows(status); + } +} + +bool RH_CC110::sleep() +{ + if (_mode != RHModeSleep) + { + spiCommand(RH_CC110_STROBE_36_SIDLE); //preceeding sleep IDLE first + spiCommand(RH_CC110_STROBE_39_SPWD); + _mode = RHModeSleep; + } + return true; +} + +void RH_CC110::setModeRx() +{ + if (_mode != RHModeRx) + { + // Radio is configuewd to stay in RX mode + // only receipt of a CRC_OK wil cause us to return it to IDLE + spiCommand(RH_CC110_STROBE_34_SRX); + _mode = RHModeRx; + } +} + +void RH_CC110::setModeTx() +{ + if (_mode != RHModeTx) + { + spiCommand(RH_CC110_STROBE_35_STX); + _mode = RHModeTx; + } +} + +uint8_t RH_CC110::statusRead() +{ + uint8_t status = spiCommand(RH_CC110_STROBE_3D_SNOP); + handleOverFlows(status); + return status; +} + +// Sigh, this chip has no TXDONE type interrupt, so we have to poll +bool RH_CC110::waitPacketSent() +{ + // If we are not currently in transmit mode, there is no packet to wait for + if (_mode != RHModeTx) + return false; + + // Caution: may transition through CALIBRATE + while ((statusRead() & RH_CC110_STATUS_STATE) != RH_CC110_STATUS_IDLE) + YIELD; + + _mode = RHModeIdle; + return true; +} + +bool RH_CC110::setTxPower(TransmitPower power) +{ + if (power > sizeof(paPowerValues)) + return false; + + uint8_t patable[2]; + memcpy_P(&patable[0], (void*)&paPowerValues[power], sizeof(uint8_t)); + patable[1] = 0x00; + setPaTable(patable, sizeof(patable)); + return true; +} + +void RH_CC110::setPaTable(uint8_t* patable, uint8_t patablesize) +{ + spiBurstWriteRegister(RH_CC110_REG_3E_PATABLE, patable, patablesize); +} + +bool RH_CC110::setFrequency(float centre) +{ + // From section 5.21: fcarrier = fxosc / 2^16 * FREQ + uint32_t FREQ; + float fxosc = _is27MHz ? 27.0 : 26.0; + FREQ = (uint32_t)(centre * 65536 / fxosc); + // Some trivial checks + if (FREQ & 0xff000000) + return false; + spiWriteRegister(RH_CC110_REG_0D_FREQ2, (FREQ >> 16) & 0xff); + spiWriteRegister(RH_CC110_REG_0E_FREQ1, (FREQ >> 8) & 0xff); + spiWriteRegister(RH_CC110_REG_0F_FREQ0, FREQ & 0xff); + + // Radio is configured to calibrate automatically whenever it enters RX or TX mode + // so no need to check for PLL lock here + return true; +} + +// Sets registers from a canned modem configuration structure +void RH_CC110::setModemRegisters(const ModemConfig* config) +{ + spiWriteRegister(RH_CC110_REG_0B_FSCTRL1, config->reg_0b); + spiWriteRegister(RH_CC110_REG_0C_FSCTRL0, config->reg_0c); + spiWriteRegister(RH_CC110_REG_10_MDMCFG4, config->reg_10); + spiWriteRegister(RH_CC110_REG_11_MDMCFG3, config->reg_11); + spiWriteRegister(RH_CC110_REG_12_MDMCFG2, config->reg_12); + spiWriteRegister(RH_CC110_REG_15_DEVIATN, config->reg_15); + spiWriteRegister(RH_CC110_REG_19_FOCCFG, config->reg_19); + spiWriteRegister(RH_CC110_REG_1A_BSCFG, config->reg_1a); + spiWriteRegister(RH_CC110_REG_1B_AGCCTRL2, config->reg_1b); + spiWriteRegister(RH_CC110_REG_1C_AGCCTRL1, config->reg_1c); + spiWriteRegister(RH_CC110_REG_1D_AGCCTRL0, config->reg_1d); + spiWriteRegister(RH_CC110_REG_21_FREND1, config->reg_21); + spiWriteRegister(RH_CC110_REG_22_FREND0, config->reg_22); + spiWriteRegister(RH_CC110_REG_23_FSCAL3, config->reg_23); + spiWriteRegister(RH_CC110_REG_24_FSCAL2, config->reg_24); + spiWriteRegister(RH_CC110_REG_25_FSCAL1, config->reg_25); + spiWriteRegister(RH_CC110_REG_26_FSCAL0, config->reg_26); + spiWriteRegister(RH_CC110_REG_2C_TEST2, config->reg_2c); + spiWriteRegister(RH_CC110_REG_2D_TEST1, config->reg_2d); + spiWriteRegister(RH_CC110_REG_2E_TEST0, config->reg_2e); +} + +// Set one of the canned Modem configs +// Returns true if its a valid choice +bool RH_CC110::setModemConfig(ModemConfigChoice index) +{ + if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE_27MHZ) / sizeof(ModemConfig))) + return false; + + const RH_CC110::ModemConfig *p = _is27MHz ? MODEM_CONFIG_TABLE_27MHZ : MODEM_CONFIG_TABLE_26MHZ ; + RH_CC110::ModemConfig cfg; + memcpy_P(&cfg, p + index, sizeof(RH_CC110::ModemConfig)); + setModemRegisters(&cfg); + + return true; +} + +void RH_CC110::setSyncWords(const uint8_t* syncWords, uint8_t len) +{ + if (!syncWords || len != 2) + return; // Only 2 byte sync words are supported + + spiWriteRegister(RH_CC110_REG_04_SYNC1, syncWords[0]); + spiWriteRegister(RH_CC110_REG_05_SYNC0, syncWords[1]); +} diff --git a/RH_CC110.h b/RH_CC110.h new file mode 100644 index 0000000..57fc5b2 --- /dev/null +++ b/RH_CC110.h @@ -0,0 +1,896 @@ +// RH_CC110.h +// +// Definitions for Texas Instruments CC110L transceiver. +// http://www.ti.com/lit/ds/symlink/cc110l.pdf +// As used in Anaren CC110L Air Module BoosterPack +// https://www.anaren.com/air/cc110l-air-module-boosterpack-embedded-antenna-module-anaren +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2016 Mike McCauley +// $Id: RH_CC110.h,v 1.9 2020/01/05 07:02:23 mikem Exp $ +// + +#ifndef RH_CC110_h +#define RH_CC110_h + + +#include + +// This is the maximum number of interrupts the driver can support +// Most Arduinos can handle 2, Megas can handle more +#define RH_CC110_NUM_INTERRUPTS 3 + +// Max number of octets the FIFO can hold +#define RH_CC110_FIFO_SIZE 64 + +// This is the maximum number of bytes that can be carried by the chip +// We use some for headers, keeping fewer for RadioHead messages +#define RH_CC110_MAX_PAYLOAD_LEN RH_CC110_FIFO_SIZE + +// The length of the headers we add. +// The headers are inside the chip payload +#define RH_CC110_HEADER_LEN 4 + +// This is the maximum message length that can be supported by this driver. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for 1 byte message length, 4 bytes headers, user data +#ifndef RH_CC110_MAX_MESSAGE_LEN + #define RH_CC110_MAX_MESSAGE_LEN (RH_CC110_MAX_PAYLOAD_LEN - RH_CC110_HEADER_LEN - 1) +#endif + +#define RH_CC110_SPI_READ_MASK 0x80 +#define RH_CC110_SPI_BURST_MASK 0x40 + +// Register definitions from Table 5-22 +#define RH_CC110_REG_00_IOCFG2 0x00 +#define RH_CC110_REG_01_IOCFG1 0x01 +#define RH_CC110_REG_02_IOCFG0 0x02 +#define RH_CC110_REG_03_FIFOTHR 0x03 +#define RH_CC110_REG_04_SYNC1 0x04 +#define RH_CC110_REG_05_SYNC0 0x05 +#define RH_CC110_REG_06_PKTLEN 0x06 +#define RH_CC110_REG_07_PKTCTRL1 0x07 +#define RH_CC110_REG_08_PKTCTRL0 0x08 +#define RH_CC110_REG_09_ADDR 0x09 +#define RH_CC110_REG_0A_CHANNR 0x0a +#define RH_CC110_REG_0B_FSCTRL1 0x0b +#define RH_CC110_REG_0C_FSCTRL0 0x0c +#define RH_CC110_REG_0D_FREQ2 0x0d +#define RH_CC110_REG_0E_FREQ1 0x0e +#define RH_CC110_REG_0F_FREQ0 0x0f +#define RH_CC110_REG_10_MDMCFG4 0x10 +#define RH_CC110_REG_11_MDMCFG3 0x11 +#define RH_CC110_REG_12_MDMCFG2 0x12 +#define RH_CC110_REG_13_MDMCFG1 0x13 +#define RH_CC110_REG_14_MDMCFG0 0x14 +#define RH_CC110_REG_15_DEVIATN 0x15 +#define RH_CC110_REG_16_MCSM2 0x16 +#define RH_CC110_REG_17_MCSM1 0x17 +#define RH_CC110_REG_18_MCSM0 0x18 +#define RH_CC110_REG_19_FOCCFG 0x19 +#define RH_CC110_REG_1A_BSCFG 0x1a +#define RH_CC110_REG_1B_AGCCTRL2 0x1b +#define RH_CC110_REG_1C_AGCCTRL1 0x1c +#define RH_CC110_REG_1D_AGCCTRL0 0x1d +#define RH_CC110_REG_1E_WOREVT1 0x1e +#define RH_CC110_REG_1F_WOREVT0 0x1f +#define RH_CC110_REG_20_WORCTRL 0x20 +#define RH_CC110_REG_21_FREND1 0x21 +#define RH_CC110_REG_22_FREND0 0x22 +#define RH_CC110_REG_23_FSCAL3 0x23 +#define RH_CC110_REG_24_FSCAL2 0x24 +#define RH_CC110_REG_25_FSCAL1 0x25 +#define RH_CC110_REG_26_FSCAL0 0x26 +#define RH_CC110_REG_27_RCCTRL1 0x28 +#define RH_CC110_REG_28_RCCTRL0 0x29 +#define RH_CC110_REG_29_FSTEST 0x2a +#define RH_CC110_REG_2A_PTEST 0x2b +#define RH_CC110_REG_2B_AGCTEST 0x2c +#define RH_CC110_REG_2C_TEST2 0x2c +#define RH_CC110_REG_2D_TEST1 0x2d +#define RH_CC110_REG_2E_TEST0 0x2e + +// Single byte read and write version of registers 0x30 to 0x3f. Strobes +// use spiCommand() +#define RH_CC110_STROBE_30_SRES 0x30 +#define RH_CC110_STROBE_31_SFSTXON 0x31 +#define RH_CC110_STROBE_32_SXOFF 0x32 +#define RH_CC110_STROBE_33_SCAL 0x33 +#define RH_CC110_STROBE_34_SRX 0x34 +#define RH_CC110_STROBE_35_STX 0x35 +#define RH_CC110_STROBE_36_SIDLE 0x36 + +#define RH_CC110_STROBE_39_SPWD 0x39 +#define RH_CC110_STROBE_3A_SFRX 0x3a +#define RH_CC110_STROBE_3B_SFTX 0x3b + +#define RH_CC110_STROBE_3D_SNOP 0x3d + + +// Burst read from these registers gives more data: +// use spiBurstReadRegister() +#define RH_CC110_REG_30_PARTNUM 0x30 +#define RH_CC110_REG_31_VERSION 0x31 +#define RH_CC110_REG_32_FREQEST 0x32 +#define RH_CC110_REG_33_CRC_REG 0x33 +#define RH_CC110_REG_34_RSSI 0x34 +#define RH_CC110_REG_35_MARCSTATE 0x35 + +#define RH_CC110_REG_38_PKTSTATUS 0x38 + +#define RH_CC110_REG_3A_TXBYTES 0x3a +#define RH_CC110_REG_3B_RXBYTES 0x3b + +// PATABLE, TXFIFO, RXFIFO also support burst +#define RH_CC110_REG_3E_PATABLE 0x3e +#define RH_CC110_REG_3F_FIFO 0x3f + +// Status Byte +#define RH_CC110_STATUS_CHIP_RDY 0x80 +#define RH_CC110_STATUS_STATE 0x70 +#define RH_CC110_STATUS_IDLE 0x00 +#define RH_CC110_STATUS_RX 0x10 +#define RH_CC110_STATUS_TX 0x20 +#define RH_CC110_STATUS_FSTXON 0x30 +#define RH_CC110_STATUS_CALIBRATE 0x40 +#define RH_CC110_STATUS_SETTLING 0x50 +#define RH_CC110_STATUS_RXFIFO_OVERFLOW 0x60 +#define RH_CC110_STATUS_TXFIFO_UNDERFLOW 0x70 +#define RH_CC110_STATUS_FIFOBYTES_AVAILABLE 0x0f + +// Register contents +// Chip Status Byte, read from header, data or command strobe +#define RH_CC110_CHIP_RDY 0x80 +#define RH_CC110_STATE 0x70 +#define RH_CC110_FIFO_BYTES_AVAILABLE 0x0f + +// Register bit field definitions +// #define RH_CC110_REG_00_IOCFG2 0x00 +// #define RH_CC110_REG_01_IOCFG1 0x01 +// #define RH_CC110_REG_02_IOCFG0 0x02 +#define RH_CC110_GDO_CFG_RX_FIFO_THR 0x00 +#define RH_CC110_GDO_CFG_RX_FIFO_FULL 0x01 +#define RH_CC110_GDO_CFG_TX_FIFO_THR 0x02 +#define RH_CC110_GDO_CFG_TX_FIFO_EMPTY 0x03 +#define RH_CC110_GDO_CFG_RX_FIFO_OVERFLOW 0x04 +#define RH_CC110_GDO_CFG_TX_FIFO_UNDEFLOOW 0x05 +#define RH_CC110_GDO_CFG_SYNC 0x06 +#define RH_CC110_GDO_CFG_CRC_OK_AUTORESET 0x07 +#define RH_CC110_GDO_CFG_CCA 0x09 +#define RH_CC110_GDO_CFG_LOCK_DETECT 0x0a +#define RH_CC110_GDO_CFG_SERIAL_CLOCK 0x0b +#define RH_CC110_GDO_CFG_SYNCHRONOUS_SDO 0x0c +#define RH_CC110_GDO_CFG_SDO 0x0d +#define RH_CC110_GDO_CFG_CARRIER 0x0e +#define RH_CC110_GDO_CFG_CRC_OK 0x0f +#define RH_CC110_GDO_CFG_PA_PD 0x1b +#define RH_CC110_GDO_CFG_LNA_PD 0x1c +#define RH_CC110_GDO_CFG_CLK_32K 0x27 +#define RH_CC110_GDO_CFG_CHIP_RDYN 0x29 +#define RH_CC110_GDO_CFG_XOSC_STABLE 0x2b +#define RH_CC110_GDO_CFG_HIGH_IMPEDANCE 0x2e +#define RH_CC110_GDO_CFG_0 0x2f +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_1 0x30 +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_1_5 0x31 +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_2 0x32 +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_3 0x33 +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_4 0x34 +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_6 0x35 +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_8 0x36 +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_12 0x37 +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_16 0x38 +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_24 0x39 +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_32 0x3a +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_48 0x3b +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_64 0x3c +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_96 0x3d +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_128 0x3e +#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_192 0x3f + +// #define RH_CC110_REG_03_FIFOTHR 0x03 +#define RH_CC110_ADC_RETENTION 0x80 + +#define RH_CC110_CLOSE_IN_RX 0x30 +#define RH_CC110_CLOSE_IN_RX_0DB 0x00 +#define RH_CC110_CLOSE_IN_RX_6DB 0x10 +#define RH_CC110_CLOSE_IN_RX_12DB 0x20 +#define RH_CC110_CLOSE_IN_RX_18DB 0x30 + +#define RH_CC110_FIFO_THR 0x0f + +// #define RH_CC110_REG_04_SYNC1 0x04 +// #define RH_CC110_REG_05_SYNC0 0x05 +// #define RH_CC110_REG_06_PKTLEN 0x06 +// #define RH_CC110_REG_07_PKTCTRL1 0x07 +#define RH_CC110_CRC_AUTOFLUSH 0x08 +#define RH_CC110_APPEND_STATUS 0x04 +#define RH_CC110_ADDR_CHK 0x03 +// can or the next 2: +#define RH_CC110_ADDR_CHK_ADDRESS 0x01 +#define RH_CC110_ADDR_CHK_BROADCAST 0x02 + + +// #define RH_CC110_REG_08_PKTCTRL0 0x08 +#define RH_CC110_PKT_FORMAT 0x30 +#define RH_CC110_PKT_FORMAT_NORMAL 0x00 +#define RH_CC110_PKT_FORMAT_SYNC_SERIAL 0x10 +#define RH_CC110_PKT_FORMAT_RANDOM_TX 0x20 +#define RH_CC110_PKT_FORMAT_ASYNC_SERIAL 0x30 + +#define RH_CC110_CRC_EN 0x04 + +#define RH_CC110_LENGTH_CONFIG 0x03 +#define RH_CC110_LENGTH_CONFIG_FIXED 0x00 +#define RH_CC110_LENGTH_CONFIG_VARIABLE 0x01 +#define RH_CC110_LENGTH_CONFIG_INFINITE 0x02 + +// #define RH_CC110_REG_09_ADDR 0x09 +// #define RH_CC110_REG_0A_CHANNR 0x0a +// #define RH_CC110_REG_0B_FSCTRL1 0x0b +// #define RH_CC110_REG_0C_FSCTRL0 0x0c +// #define RH_CC110_REG_0D_FREQ2 0x0d +// #define RH_CC110_REG_0E_FREQ1 0x0e +// #define RH_CC110_REG_0F_FREQ0 0x0f +// #define RH_CC110_REG_10_MDMCFG4 0x10 +#define RH_CC110_CHANBW_E 0xc0 +#define RH_CC110_CHANBW_M 0x30 +#define RH_CC110_DRATE_E 0x0f + +// #define RH_CC110_REG_11_MDMCFG3 0x11 +// #define RH_CC110_REG_12_MDMCFG2 0x12 +#define RH_CC110_DEM_DCFILT_OFF 0x80 +#define RH_CC110_MOD_FORMAT 0x70 +#define RH_CC110_MOD_FORMAT_2FSK 0x00 +#define RH_CC110_MOD_FORMAT_GFSK 0x10 +#define RH_CC110_MOD_FORMAT_OOK 0x30 +#define RH_CC110_MOD_FORMAT_4FSK 0x40 +#define RH_CC110_MANCHESTER_EN 0x08 +#define RH_CC110_SYNC_MODE 0x07 +#define RH_CC110_SYNC_MODE_NONE 0x00 +#define RH_CC110_SYNC_MODE_15_16 0x01 +#define RH_CC110_SYNC_MODE_16_16 0x02 +#define RH_CC110_SYNC_MODE_30_32 0x03 +#define RH_CC110_SYNC_MODE_NONE_CARRIER 0x04 +#define RH_CC110_SYNC_MODE_15_16_CARRIER 0x05 +#define RH_CC110_SYNC_MODE_16_16_CARRIER 0x06 +#define RH_CC110_SYNC_MODE_30_32_CARRIER 0x07 + +// #define RH_CC110_REG_13_MDMCFG1 0x13 +#define RH_CC110_NUM_PREAMBLE 0x70 +#define RH_CC110_NUM_PREAMBLE_2 0x00 +#define RH_CC110_NUM_PREAMBLE_3 0x10 +#define RH_CC110_NUM_PREAMBLE_4 0x20 +#define RH_CC110_NUM_PREAMBLE_6 0x30 +#define RH_CC110_NUM_PREAMBLE_8 0x40 +#define RH_CC110_NUM_PREAMBLE_12 0x50 +#define RH_CC110_NUM_PREAMBLE_16 0x60 +#define RH_CC110_NUM_PREAMBLE_24 0x70 + +#define RH_CC110_CHANSPC_E 0x03 + +// #define RH_CC110_REG_14_MDMCFG0 0x14 +// #define RH_CC110_REG_15_DEVIATN 0x15 +#define RH_CC110_DEVIATION_E 0x70 +#define RH_CC110_DEVIATION_M 0x07 + +// #define RH_CC110_REG_16_MCSM2 0x16 +#define RH_CC110_RX_TIME_RSSI 0x10 + +// #define RH_CC110_REG_17_MCSM1 0x17 +#define RH_CC110_CCA_MODE 0x30 +#define RH_CC110_CCA_MODE_ALWAYS 0x00 +#define RH_CC110_CCA_MODE_RSSI 0x10 +#define RH_CC110_CCA_MODE_PACKET 0x20 +#define RH_CC110_CCA_MODE_RSSI_PACKET 0x30 +#define RH_CC110_RXOFF_MODE 0x0c +#define RH_CC110_RXOFF_MODE_IDLE 0x00 +#define RH_CC110_RXOFF_MODE_FSTXON 0x04 +#define RH_CC110_RXOFF_MODE_TX 0x08 +#define RH_CC110_RXOFF_MODE_RX 0x0c +#define RH_CC110_TXOFF_MODE 0x03 +#define RH_CC110_TXOFF_MODE_IDLE 0x00 +#define RH_CC110_TXOFF_MODE_FSTXON 0x01 +#define RH_CC110_TXOFF_MODE_TX 0x02 +#define RH_CC110_TXOFF_MODE_RX 0x03 + +// #define RH_CC110_REG_18_MCSM0 0x18 +#define RH_CC110_FS_AUTOCAL 0x30 +#define RH_CC110_FS_AUTOCAL_NEVER 0x00 +#define RH_CC110_FS_AUTOCAL_FROM_IDLE 0x10 +#define RH_CC110_FS_AUTOCAL_TO_IDLE 0x20 +#define RH_CC110_FS_AUTOCAL_TO_IDLE_4 0x30 +#define RH_CC110_PO_TIMEOUT 0x0c +#define RH_CC110_PO_TIMEOUT_1 0x00 +#define RH_CC110_PO_TIMEOUT_16 0x04 +#define RH_CC110_PO_TIMEOUT_64 0x08 +#define RH_CC110_PO_TIMEOUT_256 0x0c +#define RH_CC110_XOSC_FORCE_ON 0x01 + +// #define RH_CC110_REG_19_FOCCFG 0x19 +#define RH_CC110_FOC_BS_CS_GATE 0x20 +#define RH_CC110_FOC_PRE_K 0x18 +#define RH_CC110_FOC_PRE_K_0 0x00 +#define RH_CC110_FOC_PRE_K_1 0x08 +#define RH_CC110_FOC_PRE_K_2 0x10 +#define RH_CC110_FOC_PRE_K_3 0x18 +#define RH_CC110_FOC_POST_K 0x04 +#define RH_CC110_FOC_LIMIT 0x03 +#define RH_CC110_FOC_LIMIT_0 0x00 +#define RH_CC110_FOC_LIMIT_8 0x01 +#define RH_CC110_FOC_LIMIT_4 0x02 +#define RH_CC110_FOC_LIMIT_2 0x03 + +// #define RH_CC110_REG_1A_BSCFG 0x1a +#define RH_CC110_BS_PRE_K 0xc0 +#define RH_CC110_BS_PRE_K_1 0x00 +#define RH_CC110_BS_PRE_K_2 0x40 +#define RH_CC110_BS_PRE_K_3 0x80 +#define RH_CC110_BS_PRE_K_4 0xc0 +#define RH_CC110_BS_PRE_KP 0x30 +#define RH_CC110_BS_PRE_KP_1 0x00 +#define RH_CC110_BS_PRE_KP_2 0x10 +#define RH_CC110_BS_PRE_KP_3 0x20 +#define RH_CC110_BS_PRE_KP_4 0x30 +#define RH_CC110_BS_POST_KI 0x08 +#define RH_CC110_BS_POST_KP 0x04 +#define RH_CC110_BS_LIMIT 0x03 +#define RH_CC110_BS_LIMIT_0 0x00 +#define RH_CC110_BS_LIMIT_3 0x01 +#define RH_CC110_BS_LIMIT_6 0x02 +#define RH_CC110_BS_LIMIT_12 0x03 + +// #define RH_CC110_REG_1B_AGCCTRL2 0x1b +#define RH_CC110_MAX_DVA_GAIN 0xc0 +#define RH_CC110_MAX_DVA_GAIN_ALL 0x00 +#define RH_CC110_MAX_DVA_GAIN_ALL_LESS_1 0x40 +#define RH_CC110_MAX_DVA_GAIN_ALL_LESS_2 0x80 +#define RH_CC110_MAX_DVA_GAIN_ALL_LESS_3 0xc0 +#define RH_CC110_MAX_LNA_GAIN 0x38 + +#define RH_CC110_MAGN_TARGET 0x07 +#define RH_CC110_MAGN_TARGET_24DB 0x00 +#define RH_CC110_MAGN_TARGET_27DB 0x01 +#define RH_CC110_MAGN_TARGET_30DB 0x02 +#define RH_CC110_MAGN_TARGET_33DB 0x03 +#define RH_CC110_MAGN_TARGET_36DB 0x04 +#define RH_CC110_MAGN_TARGET_38DB 0x05 +#define RH_CC110_MAGN_TARGET_40DB 0x06 +#define RH_CC110_MAGN_TARGET_42DB 0x07 + +// #define RH_CC110_REG_1C_AGCCTRL1 0x1c +#define RH_CC110_AGC_LNA_PRIORITY 0x40 +#define RH_CC110_CARRIER_SENSE_REL_THR 0x30 +#define RH_CC110_CARRIER_SENSE_REL_THR_0DB 0x00 +#define RH_CC110_CARRIER_SENSE_REL_THR_6DB 0x10 +#define RH_CC110_CARRIER_SENSE_REL_THR_10DB 0x20 +#define RH_CC110_CARRIER_SENSE_REL_THR_14DB 0x30 +#define RH_CC110_CARRIER_SENSE_ABS_THR 0x0f + +// #define RH_CC110_REG_1D_AGCCTRL0 0x1d +#define RH_CC110_HYST_LEVEL 0xc0 +#define RH_CC110_HYST_LEVEL_NONE 0x00 +#define RH_CC110_HYST_LEVEL_LOW 0x40 +#define RH_CC110_HYST_LEVEL_MEDIUM 0x80 +#define RH_CC110_HYST_LEVEL_HIGH 0xc0 +#define RH_CC110_WAIT_TIME 0x30 +#define RH_CC110_WAIT_TIME_8 0x00 +#define RH_CC110_WAIT_TIME_16 0x10 +#define RH_CC110_WAIT_TIME_24 0x20 +#define RH_CC110_WAIT_TIME_32 0x30 +#define RH_CC110_AGC_FREEZE 0x0c +#define RH_CC110_AGC_FILTER_LENGTH 0x03 +#define RH_CC110_AGC_FILTER_LENGTH_8 0x00 +#define RH_CC110_AGC_FILTER_LENGTH_16 0x01 +#define RH_CC110_AGC_FILTER_LENGTH_32 0x02 +#define RH_CC110_AGC_FILTER_LENGTH_64 0x03 + +// #define RH_CC110_REG_1E_WOREVT1 0x1e +// #define RH_CC110_REG_1F_WOREVT0 0x1f +// #define RH_CC110_REG_20_WORCTRL 0x20 +// #define RH_CC110_REG_21_FREND1 0x21 +#define RH_CC110_LNA_CURRENT 0xc0 +#define RH_CC110_LNA2MIX_CURRENT 0x30 +#define RH_CC110_LODIV_BUF_CURRENT_RX 0x0c +#define RH_CC110_MIX_CURRENT 0x03 + +// #define RH_CC110_REG_22_FREND0 0x22 +#define RH_CC110_LODIV_BUF_CURRENT_TX 0x30 +#define RH_CC110_PA_POWER 0x07 + +// #define RH_CC110_REG_23_FSCAL3 0x23 +#define RH_CC110_FSCAL3_7_6 0xc0 +#define RH_CC110_CHP_CURR_CAL_EN 0x30 +#define RH_CC110_FSCAL3_3_0 0x0f + +// #define RH_CC110_REG_24_FSCAL2 0x24 +#define RH_CC110_VCO_CORE_H_EN 0x20 +#define RH_CC110_FSCAL2 0x1f + +// #define RH_CC110_REG_25_FSCAL1 0x25 +#define RH_CC110_FSCAL1 0x3f + +// #define RH_CC110_REG_26_FSCAL0 0x26 +#define RH_CC110_FSCAL0 0x7f + +// #define RH_CC110_REG_27_RCCTRL1 0x28 +// #define RH_CC110_REG_28_RCCTRL0 0x29 +// #define RH_CC110_REG_29_FSTEST 0x2a +// #define RH_CC110_REG_2A_PTEST 0x2b +// #define RH_CC110_REG_2B_AGCTEST 0x2c +// #define RH_CC110_REG_2C_TEST2 0x2c +// #define RH_CC110_REG_2D_TEST1 0x2d +// #define RH_CC110_REG_2E_TEST0 0x2e +#define RH_CC110_TEST0_7_2 0xfc +#define RH_CC110_VCO_SEL_CAL_EN 0x02 +#define RH_CC110_TEST0_0 0x01 + +// #define RH_CC110_REG_30_PARTNUM 0x30 +// #define RH_CC110_REG_31_VERSION 0x31 +// #define RH_CC110_REG_32_FREQEST 0x32 +// #define RH_CC110_REG_33_CRC_REG 0x33 +#define RH_CC110_CRC_REG_CRC_OK 0x80 + +// #define RH_CC110_REG_34_RSSI 0x34 +// #define RH_CC110_REG_35_MARCSTATE 0x35 +#define RH_CC110_MARC_STATE 0x1f +#define RH_CC110_MARC_STATE_SLEEP 0x00 +#define RH_CC110_MARC_STATE_IDLE 0x01 +#define RH_CC110_MARC_STATE_XOFF 0x02 +#define RH_CC110_MARC_STATE_VCOON_MC 0x03 +#define RH_CC110_MARC_STATE_REGON_MC 0x04 +#define RH_CC110_MARC_STATE_MANCAL 0x05 +#define RH_CC110_MARC_STATE_VCOON 0x06 +#define RH_CC110_MARC_STATE_REGON 0x07 +#define RH_CC110_MARC_STATE_STARTCAL 0x08 +#define RH_CC110_MARC_STATE_BWBOOST 0x09 +#define RH_CC110_MARC_STATE_FS_LOCK 0x0a +#define RH_CC110_MARC_STATE_IFADCON 0x0b +#define RH_CC110_MARC_STATE_ENDCAL 0x0c +#define RH_CC110_MARC_STATE_RX 0x0d +#define RH_CC110_MARC_STATE_RX_END 0x0e +#define RH_CC110_MARC_STATE_RX_RST 0x0f +#define RH_CC110_MARC_STATE_TXRX_SWITCH 0x10 +#define RH_CC110_MARC_STATE_RXFIFO_OVERFLOW 0x11 +#define RH_CC110_MARC_STATE_FSTXON 0x12 +#define RH_CC110_MARC_STATE_TX 0x13 +#define RH_CC110_MARC_STATE_TX_END 0x14 +#define RH_CC110_MARC_STATE_RXTX_SWITCH 0x15 +#define RH_CC110_MARC_STATE_TXFIFO_UNDERFLOW 0x16 + +// #define RH_CC110_REG_38_PKTSTATUS 0x38 +#define RH_CC110_PKTSTATUS_CRC_OK 0x80 +#define RH_CC110_PKTSTATUS_CS 0x40 +#define RH_CC110_PKTSTATUS_CCA 0x10 +#define RH_CC110_PKTSTATUS_SFD 0x08 +#define RH_CC110_PKTSTATUS_GDO2 0x04 +#define RH_CC110_PKTSTATUS_GDO0 0x01 + +// #define RH_CC110_REG_3A_TXBYTES 0x3a +#define RH_CC110_TXFIFO_UNDERFLOW 0x80 +#define RH_CC110_NUM_TXBYTES 0x7f + +// #define RH_CC110_REG_3B_RXBYTES 0x3b +#define RH_CC110_RXFIFO_UNDERFLOW 0x80 +#define RH_CC110_NUM_RXBYTES 0x7f + +///////////////////////////////////////////////////////////////////// +/// \class RH_CC110 RH_CC110.h +/// \brief Send and receive unaddressed, unreliable, datagrams by Texas Instruments CC110L and compatible transceivers and modules. +/// +/// The TI CC110L is a low cost tranceiver chip capable of 300 to 928MHz and with a wide range of modulation types and speeds. +/// The chip is typically provided on a module that also includes the antenna and coupling hardware +/// and is therefore capable of a more restricted frequency range. +/// +/// Supported modules include: +/// - Anaren AIR BoosterPack 430BOOST-CC110L +/// +/// This base class provides basic functions for sending and receiving unaddressed, unreliable datagrams +/// of arbitrary length to 59 octets per packet at a selected data rate and modulation type. +/// Use one of the Manager classes to get addressing and +/// acknowledgement reliability, routing, meshes etc. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// data rate, and with identical network addresses. +/// +/// Several CC110L modules can be connected to an Arduino, permitting the construction of translators +/// and frequency changers, etc. +/// +/// Several GFSK modulation schemes are provided and may be selected by calling setModemConfig(). No FSK or OOK +/// modulation schemes are provided though the implementor may configure the mnodem characteristics directly +/// by calling setModemRegisters(). +/// +/// Implementation based on: +/// http://www.ti.com/lit/ds/symlink/cc110l.pdf +/// and +/// https://www.anaren.com/air/cc110l-air-module-boosterpack-embedded-antenna-module-anaren +/// +/// \par Crystal Frequency +/// +/// Modules based on the CC110L may contain a crystal oscillator with one of 2 possible frequencies: 26MHz or 27MHz. +/// A number of radio configuration parameters (including carrier frequency and data rates) depend on the +/// crystal oscillator frequency. The chip has no knowledge of the frequency, so it is up to the implementer +/// to tell the driver the oscillator frequency by passing in the appropriate value of is27MHz to the constructor (default 26MHz) +/// or by calling setIs27MHz() before calling init(). +/// Failure to correctly set this flag will cause incorrect frequency and modulation +/// characteristics to be used. +/// +/// Caution: it is not easy to determine what the actual crystal frequency is on some modules. For example, +/// the documentation for the Anaren BoosterPack indictes a 26MHz crystal, but measurements on the devices delivered here +/// indicate a 27MHz crystal is actually installed. TI recommend 27MHz for +/// +/// \par Packet Format +/// +/// - 2 octets sync (a configurable network address) +/// - 1 octet message length +/// - 4 to 63 octets of payload consisting of: +/// - 1 octet TO header +/// - 1 octet FROM header +/// - 1 octet ID header +/// - 1 octet FLAGS header +/// - 0 to 59 octets of user message +/// - 2 octets CRC +/// +/// \par Connecting CC110L to Arduino +/// +/// Warning: the CC110L is a 3.3V part, and exposing it to 5V on any pin will damage it. Ensure you are using a 3.3V +/// MCU or use level shifters. We tested with Teensy 3.1. +/// +/// The electrical connection between a CC110L module and the Arduino or other processor +/// require 3.3V, the 3 x SPI pins (SCK, SDI, SDO), +/// a Chip Select pin and an Interrupt pin. +/// Examples below assume the Anaren BoosterPack. Caution: the pin numbering on the Anaren BoosterPack +/// is a bit counter-intuitive: the direction of number on J1 is the reverse of J2. Check the pin numbers +/// stenciled on the front of the board to be sure. +/// +/// \code +/// Teensy 3.1 CC110L pin name Anaren BoosterPack pin +/// 3.3V---------VDD (3.3V in) J1-1 +/// SS pin D10----------CSn (chip select in) J2-8 +/// SCK pin D13----------SCLK (SPI clock in) J1-7 +/// MOSI pin D11----------MOSI (SPI data in) J2-5 +/// MISO pin D12----------MISO (SPI data out) J2-4 +/// D2-----------GDO0 (Interrupt output) J2-9 +/// GND----------GND (ground in) J2-10 +/// \endcode +/// and use the default RH_CC110 constructor. You can use other pins by passing the appropriate arguments +/// to the RH_CC110 constructor, depending on what your MCU supports. +/// +/// For the Particle Photon: +/// \code +/// Photon CC110L pin name Anaren BoosterPack pin +/// 3.3V---------VDD (3.3V in) J1-1 +/// SS pin A2-----------CSn (chip select in) J2-8 +/// SCK pin A3-----------SCLK (SPI clock in) J1-7 +/// MOSI pin A5-----------MOSI (SPI data in) J2-5 +/// MISO pin A4-----------MISO (SPI data out) J2-4 +/// D2-----------GDO0 (Interrupt output) J2-9 +/// GND----------GND (ground in) J2-10 +/// \endcode +/// and use the default RH_CC110 constructor. You can use other pins by passing the appropriate arguments +/// to the RH_CC110 constructor, depending on what your MCU supports. +/// +/// \par Example programs +/// +/// Several example programs are provided. +/// +/// \par Radio operating strategy and defaults +/// +/// The radio is enabled at all times and switched between RX, TX and IDLE modes. +/// When RX is enabled (by calling available() or setModeRx()) the radio will stay in RX mode until a +/// valid CRC correct message addressed to thiis node is received, when it will transition to IDLE. +/// When TX is enabled (by calling send()) it will stay in TX mode until the message has ben sent +/// and waitPacketSent() is called when it wil transition to IDLE +///(this radio has no 'packet sent' interrupt that could be used, so polling +/// with waitPacketSent() is required +/// +/// The modulation schemes supported include the GFSK schemes provided by default in the TI SmartRF Suite. +/// This software allows you to get the correct register values for diferent modulation schemes. All the modulation +/// schemes prvided in the driver are based on the recommended register values given by SmartRF. +/// Other schemes such a 2-FSK, 4-FSK and OOK are suported by the chip, but canned configurations are not provided with this driver. +/// The implementer may choose to create their own modem configurations and pass them to setModemRegisters(). +/// +class RH_CC110 : public RHNRFSPIDriver +{ +public: + + /// \brief Defines register configuration values for a desired modulation + /// + /// Defines values for various configuration fields and registers to + /// achieve a desired modulation speed and frequency deviation. + typedef struct + { + uint8_t reg_0b; ///< RH_CC110_REG_0B_FSCTRL1 + uint8_t reg_0c; ///< RH_CC110_REG_0C_FSCTRL0 + uint8_t reg_10; ///< RH_CC110_REG_10_MDMCFG4 + uint8_t reg_11; ///< RH_CC110_REG_11_MDMCFG3 + uint8_t reg_12; ///< RH_CC110_REG_12_MDMCFG2 + uint8_t reg_15; ///< RH_CC110_REG_15_DEVIATN + uint8_t reg_19; ///< RH_CC110_REG_19_FOCCFG + uint8_t reg_1a; ///< RH_CC110_REG_1A_BSCFG + uint8_t reg_1b; ///< RH_CC110_REG_1B_AGCCTRL2 + uint8_t reg_1c; ///< RH_CC110_REG_1C_AGCCTRL1 + uint8_t reg_1d; ///< RH_CC110_REG_1D_AGCCTRL0 + uint8_t reg_21; ///< RH_CC110_REG_21_FREND1 + uint8_t reg_22; ///< RH_CC110_REG_22_FREND0 + uint8_t reg_23; ///< RH_CC110_REG_23_FSCAL3 + uint8_t reg_24; ///< RH_CC110_REG_24_FSCAL2 + uint8_t reg_25; ///< RH_CC110_REG_25_FSCAL1 + uint8_t reg_26; ///< RH_CC110_REG_26_FSCAL0 + uint8_t reg_2c; ///< RH_CC110_REG_2C_TEST2 + uint8_t reg_2d; ///< RH_CC110_REG_2D_TEST1 + uint8_t reg_2e; ///< RH_CC110_REG_2E_TEST0 + } ModemConfig; + + + /// Choices for setModemConfig() for a selected subset of common modulation types, + /// and data rates. If you need another configuration, use the register calculator. + /// and call setModemRegisters() with your desired settings. + /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic + /// definitions and not their integer equivalents: its possible that new values will be + /// introduced in later versions (though we will try to avoid it). + /// All configs use SYNC_MODE = RH_CC110_SYNC_MODE_16_16 (2 byte sync) + typedef enum + { + GFSK_Rb1_2Fd5_2 = 0, ///< GFSK, Data Rate: 1.2kBaud, Dev: 5.2kHz, RX BW 58kHz, optimised for sensitivity + GFSK_Rb2_4Fd5_2, ///< GFSK, Data Rate: 2.4kBaud, Dev: 5.2kHz, RX BW 58kHz, optimised for sensitivity + GFSK_Rb4_8Fd25_4, ///< GFSK, Data Rate: 4.8kBaud, Dev: 25.4kHz, RX BW 100kHz, optimised for sensitivity + GFSK_Rb10Fd19, ///< GFSK, Data Rate: 10kBaud, Dev: 19kHz, RX BW 100kHz, optimised for sensitivity + GFSK_Rb38_4Fd20, ///< GFSK, Data Rate: 38.4kBaud, Dev: 20kHz, RX BW 100kHz, optimised for sensitivity + GFSK_Rb76_8Fd32, ///< GFSK, Data Rate: 76.8kBaud, Dev: 32kHz, RX BW 232kHz, optimised for sensitivity + GFSK_Rb100Fd47, ///< GFSK, Data Rate: 100kBaud, Dev: 47kHz, RX BW 325kHz, optimised for sensitivity + GFSK_Rb250Fd127, ///< GFSK, Data Rate: 250kBaud, Dev: 127kHz, RX BW 540kHz, optimised for sensitivity + } ModemConfigChoice; + + /// These power outputs are based on the suggested optimum values for + /// multilayer inductors in the 915MHz frequency band. Per table 5-15. + /// Caution: these enum values are indexes into PaPowerValues. + /// Do not change one without changing the other. Use the symbolic names, not the integer values + typedef enum + { + TransmitPowerM30dBm = 0, ///< -30dBm + TransmitPowerM20dBm, ///< -20dBm + TransmitPowerM15dBm, ///< -15dBm + TransmitPowerM10dBm, ///< -10dBm + TransmitPower0dBm, ///< 0dBm + TransmitPower5dBm, ///< 5dBm + TransmitPower7dBm, ///< 7dBm + TransmitPower10dBm, ///< 10dBm + } TransmitPower; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// interrupt and slave select pin. After constructing, you must call init() to initialise the interface + /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient + /// distinct interrupt lines, one for each instance. + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the CC110L before + /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) + /// \param[in] interruptPin The interrupt Pin number that is connected to the CC110L GDO0 interrupt line. + /// Defaults to pin 2. + /// Caution: You must specify an interrupt capable pin. + /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. + /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. + /// On other Arduinos pins 2 or 3. + /// See http://arduino.cc/en/Reference/attachInterrupt for more details. + /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. + /// On other boards, any digital pin may be used. + /// \param[in] is27MHz Set to true if your CC110 is equipped with a 27MHz crystal oscillator. Defaults to false. + /// \param[in] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + RH_CC110(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, bool is27MHz = false, RHGenericSPI& spi = hardware_spi); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// In particular, ensure you have called setIs27MHz(true) if your module has a 27MHz crystal oscillator. + /// After init(), the following default characteristics are set: + /// TxPower: TransmitPower5dBm + /// Frequency: 915.0 + /// Modulation: GFSK_Rb1_2Fd5_2 (GFSK, Data Rate: 1.2kBaud, Dev: 5.2kHz, RX BW 58kHz, optimised for sensitivity) + /// Sync Words: 0xd3, 0x91 + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Prints the value of all chip registers + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging purposes only. + /// \return true on success + bool printRegisters(); + + /// Blocks until the current message (if any) + /// has been transmitted + /// \return true on success, false if the chip is not in transmit mode or other transmit failure + virtual bool waitPacketSent(); + + /// Tests whether a new message is available + /// from the Driver. + /// On most drivers, this will also put the Driver into RHModeRx mode until + /// a message is actually received by the transport, when it will be returned to RHModeIdle + /// and available() will return true. + /// This can be called multiple times in a timeout loop + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available(); + + /// Turns the receiver on if it not already on (after wiaint gor any currenly transmitting message to complete). + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf. The message cannot be retreived again. + virtual bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send + /// \return true if the message length was valid and it was correctly queued for transmit + virtual bool send(const uint8_t* data, uint8_t len); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + /// If current mode is Sleep, Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + void setModeIdle(); + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver. The radio will stay in Rx mode until a CRC correct message addressed to this node + /// is received, or the ode is changed to Tx, Idle or Sleep. + void setModeRx(); + + /// If current mode is Rx or Idle, changes it to Tx. + /// Starts the transmitter sending the current message. + void setModeTx(); + + /// Sets the radio into low-power sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode to idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. + /// Caution: waking up from sleep loses values from registers 0x29 through 0x2e + /// \return true if sleep mode was successfully entered. + virtual bool sleep(); + + /// Set the Power Amplifier power setting. + /// The PaTable settings are based on are based on the suggested optimum values for + /// multilayer inductors in the 915MHz frequency band. Per table 5-15. + /// If these values are not suitable, use setPaTable() directly. + /// Caution: be a good neighbour and use the lowest power setting compatible with your application. + /// Caution: Permissable power settings for your area may depend on frequency and modulation characteristics: + /// consult local authorities. + /// param[in] power One of TransmitPower enum values + bool setTxPower(TransmitPower power); + + /// Indicates the presence of 27MHz crystal oscillator. + /// You must indicate to the driver if your CC110L is equipped with a 27MHz crystal oscillator (26MHz is the default + /// in the constructor). + /// This should be called before calling init() if you have a 27MHz crystal. + /// It can be called after calling init() but you must reset the frequency (with setFrequency()) and modulation + /// (with setModemConfig()) afterwards. + /// \param[in] is27MHz Pass true if the CC110L has a 27MHz crystal (default is true). + void setIs27MHz(bool is27MHz = true); + + /// Sets the transmitter and receiver + /// centre frequency. + /// Caution: permissable frequency bands will depend on you country and area: consult local authorities. + /// \param[in] centre Frequency in MHz. 300.0 to 928.0 + /// \return true if the selected frquency centre is within range + bool setFrequency(float centre); + + /// Sets all the registers required to configure the data modem in the CC110, including the data rate, + /// bandwidths etc. You cas use this to configure the modem with custom configuraitons if none of the + /// canned configurations in ModemConfigChoice suit you. + /// \param[in] config A ModemConfig structure containing values for the modem configuration registers. + void setModemRegisters(const ModemConfig* config); + + /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// here, use setModemRegisters() with your own ModemConfig. + /// \param[in] index The configuration choice. + /// \return true if index is a valid choice. + bool setModemConfig(ModemConfigChoice index); + + /// Sets the sync words for transmit and receive in registers RH_CC110_REG_04_SYNC1 and RH_CC110_REG_05_SYNC0. + /// Caution: SyncWords should be set to the same + /// value on all nodes in your network. Nodes with different SyncWords set will never receive + /// each others messages, so different SyncWords can be used to isolate different + /// networks from each other. Default is { 0xd3, 0x91 }. + /// \param[in] syncWords Array of sync words, 2 octets long + /// \param[in] len Number of sync words to set. MUST be 2. + void setSyncWords(const uint8_t* syncWords, uint8_t len); + + /// Sets the PaTable registers directly. + /// Ensure you use suitable PATABLE values per Tbale 5-15 or 5-16 + /// You may need to do this to implement an OOK modulation scheme. + void setPaTable(uint8_t* patable, uint8_t patablesize); + +protected: + /// This is a low level function to handle the interrupts for one instance of RH_RF95. + /// Called automatically by isr*() + /// Should not need to be called by user code. + void handleInterrupt(); + + /// Reads a single register from the CC110L + /// \param[in] reg Register number, one of RH_CC110_REG + /// \return The value of the register + uint8_t spiReadRegister(uint8_t reg); + + /// Reads a single register in burst mode. + /// On the CC110L, some registers yield different data when read in burst mode + /// as opposed to single byte mode. + /// \param[in] reg Register number, one of RH_CC110_REG (burst mode readable) + /// \return The value of the register after a burst read + uint8_t spiBurstReadRegister(uint8_t reg); + + /// Writes to a single single register on the CC110L + /// \param[in] reg Register number, one of RH_CC110L_REG_* + /// \param[in] val The value to write + /// \return returns the chip status byte per table 5.2 + uint8_t spiWriteRegister(uint8_t reg, uint8_t val); + + /// Write a number of bytes to a burst capable register + /// \param[in] reg Register number of the first register, one of RH_CC110L_REG_* + /// \param[in] src Array of new register values to write. Must be at least len bytes + /// \param[in] len Number of bytes to write + /// \return the chip status byte per table 5.2 + uint8_t spiBurstWriteRegister(uint8_t reg, const uint8_t* src, uint8_t len); + + /// Examine the receive buffer to determine whether the message is for this node + /// Sets _rxBufValid. + void validateRxBuf(); + + /// Clear our local receive buffer + void clearRxBuf(); + + /// Reads and returns the status byte by issuing the SNOP strobe + /// \return The value of the status byte per Table 5-2 + uint8_t statusRead(); + + /// Handle the TX or RX overflow state of the given status + /// \param status The status byte read from the last SPI command + /// \return void + void handleOverFlows(uint8_t status); + + +private: + /// Low level interrupt service routine for device connected to interrupt 0 + static void isr0(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr1(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr2(); + + /// Array of instances connected to interrupts 0 and 1 + static RH_CC110* _deviceForInterrupt[]; + + /// Index of next interrupt number to use in _deviceForInterrupt + static uint8_t _interruptCount; + + /// The configured interrupt pin connected to this instance + uint8_t _interruptPin; + + /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) + /// else 0xff + uint8_t _myInterruptIndex; + + /// Number of octets in the buffer + volatile uint8_t _bufLen; + + /// The receiver/transmitter buffer + /// Allow for 2 status bytes so we can read packet RSSI + uint8_t _buf[RH_CC110_MAX_PAYLOAD_LEN + 2]; + + /// True when there is a valid message in the buffer + volatile bool _rxBufValid; + + /// True if crystal oscillator is 26 MHz, not 26MHz. + bool _is27MHz; +}; + +/// @example cc110_client.ino +/// @example cc110_server.ino + +#endif diff --git a/RH_E32.cpp b/RH_E32.cpp new file mode 100644 index 0000000..86c71ed --- /dev/null +++ b/RH_E32.cpp @@ -0,0 +1,346 @@ +// RH_E32.cpp +// +// Copyright (C) 2017 Mike McCauley +// $Id: RH_E32.cpp,v 1.6 2020/01/07 23:35:02 mikem Exp $ + +#include +#ifdef RH_HAVE_SERIAL // No serial + +#include + +RH_E32::RH_E32(Stream *s, uint8_t m0_pin, uint8_t m1_pin, uint8_t aux_pin) + : + _s(s), + _m0_pin(m0_pin), + _m1_pin(m1_pin), + _aux_pin(aux_pin) +{ + // Prevent glitches at startup + pinMode(_aux_pin, INPUT); + digitalWrite(_m0_pin, HIGH); + digitalWrite(_m1_pin, HIGH); + pinMode(_m0_pin, OUTPUT); + pinMode(_m1_pin, OUTPUT); +} + +bool RH_E32::init() +{ + // When a message is available, Aux will go low 5 msec before the first character is output + // So if we ever wait more than this period of time after Aux low, can conclude there will be no data + _s->setTimeout(10); + + // Wait until the module is connected + waitAuxHigh(); + + if (!getVersion()) + return false; // Could not communicate with module or wrong type of module + + setMode(RHModeRx); + clearRxBuf(); + + if (!setDataRate(DataRate5kbps)) + return false; + + if (!setPower(Power21dBm)) + return false; + + // if (!setBaudRate(BaudRate9600, Parity8N1)) + // return false; + + if (!setFrequency(433)) + return false; + + return true; +} + +bool RH_E32::reset() +{ + setOperatingMode(ModeSleep); + uint8_t resetCommand[] = { RH_E32_COMMAND_RESET, RH_E32_COMMAND_RESET, RH_E32_COMMAND_RESET }; + size_t result = _s->write(resetCommand, sizeof(resetCommand)); + setOperatingMode(ModeNormal); + return (result == sizeof(resetCommand)); +} + +bool RH_E32::readParameters(Parameters& params) +{ + setOperatingMode(ModeSleep); + uint8_t readParamsCommand[] = { RH_E32_COMMAND_READ_PARAMS, RH_E32_COMMAND_READ_PARAMS, RH_E32_COMMAND_READ_PARAMS }; + _s->write(readParamsCommand, sizeof(readParamsCommand)); + size_t result = _s->readBytes((char*)¶ms, sizeof(params)); // default 1 sec timeout + setOperatingMode(ModeNormal); + return (result == sizeof(Parameters)); +} + +bool RH_E32::writeParameters(Parameters& params, bool save) +{ + setOperatingMode(ModeSleep); + params.head = save ? RH_E32_COMMAND_WRITE_PARAMS_SAVE : RH_E32_COMMAND_WRITE_PARAMS_NOSAVE; + // printBuffer("writing now", (uint8_t*)¶ms, sizeof(params)); + size_t result = _s->write((uint8_t*)¶ms, sizeof(params)); + if (result != sizeof(params)) + return false; + + // Now we expect to get the same data back + result = _s->readBytes((char*)¶ms, sizeof(params)); + if (result != sizeof(params)) + return false; + // printBuffer("additional read", (uint8_t*)¶ms, sizeof(params)); + // Without a little delay here, writing params often fails + delay(20); + + setOperatingMode(ModeNormal); + return result == sizeof(params); +} + +void RH_E32::setOperatingMode(OperatingMode mode) +{ + waitAuxHigh(); + switch (mode) + { + case ModeNormal: + digitalWrite(_m0_pin, LOW); + digitalWrite(_m1_pin, LOW); + break; + + case ModeWakeUp: + digitalWrite(_m0_pin, HIGH); + digitalWrite(_m1_pin, LOW); + break; + + case ModePowerSaving: + digitalWrite(_m0_pin, LOW); + digitalWrite(_m1_pin, HIGH); + break; + + case ModeSleep: + digitalWrite(_m0_pin, HIGH); + digitalWrite(_m1_pin, HIGH); + break; + + } + delay(10); // Takes a little while to start its response + waitAuxHigh(); +} + +bool RH_E32::getVersion() +{ + setOperatingMode(ModeSleep); + uint8_t readVersionCommand[] = { RH_E32_COMMAND_READ_VERSION, RH_E32_COMMAND_READ_VERSION, RH_E32_COMMAND_READ_VERSION }; + _s->write(readVersionCommand, sizeof(readVersionCommand)); + uint8_t version[4]; + size_t result = _s->readBytes((char *)version, sizeof(version)); // default 1 sec timeout + setOperatingMode(ModeNormal); + if (result == 4) + { + // Successful read + // printBuffer("read version", version, sizeof(version)); + if (version[0] != 0xc3 || version [1] != 0x32) + { + // Not an E32 + return false; + } + else + { + // REVISIT: do something with it? + } + } + else + { + // Read failed: no module? Wrong baud? + return false; + } + return true; +} + +void RH_E32::waitAuxHigh() +{ + // REVISIT: timeout needed? + while (digitalRead(_aux_pin) == false) + ; +} + +void RH_E32::waitAuxLow() +{ + while (digitalRead(_aux_pin) == true) + ; +} + +// Check whether the latest received message is complete and uncorrupted +void RH_E32::validateRxBuf() +{ + if (_bufLen < RH_E32_HEADER_LEN) + return; // Too short to be a real message + if (_bufLen != _buf[0]) + return; // Do we have all the message? + + // Extract the 4 headers + _rxHeaderTo = _buf[1]; + _rxHeaderFrom = _buf[2]; + _rxHeaderId = _buf[3]; + _rxHeaderFlags = _buf[4]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +void RH_E32::clearRxBuf() +{ + _rxBufValid = false; + _bufLen = 0; +} + +bool RH_E32::available() +{ + // Caution: long packets could be sent in several bursts + if (!_rxBufValid) + { + if (_mode == RHModeTx) + return false; + + if (!_s->available()) + return false; + + // Suck up all the characters we can + uint8_t data; + while (_s->readBytes((char *)&data, 1) == 1) // Not read timeout + { + _buf[_bufLen++] = data; + } + // Now assess what we have + if (_bufLen < RH_E32_HEADER_LEN) + { + // Serial.println("Incomplete header"); + return false; + } + else if (_bufLen < _buf[0]) + { + // Serial.println("Incomplete message"); + return false; + } + else if ( _bufLen > _buf[0] + || _bufLen > RH_E32_MAX_PAYLOAD_LEN) + { + // Serial.println("Overrun"); + clearRxBuf(); + _rxBad++; + return false; + } + + // Else it a partial or complete message, test it + // printBuffer("read success", _buf, _bufLen); + validateRxBuf(); + } + return _rxBufValid; +} + +bool RH_E32::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + if (buf && len) + { + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen - RH_E32_HEADER_LEN) + *len = _bufLen - RH_E32_HEADER_LEN; + memcpy(buf, _buf + RH_E32_HEADER_LEN, *len); + } + clearRxBuf(); // This message accepted and cleared + return true; +} + +bool RH_E32::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_E32_MAX_MESSAGE_LEN) + return false; + + waitPacketSent(); // Make sure we dont collide with previous message + + // Set up the headers + _buf[0] = len + RH_E32_HEADER_LEN; // Number of octets in teh whole message + _buf[1] = _txHeaderTo; + _buf[2] = _txHeaderFrom; + _buf[3] = _txHeaderId; + _buf[4] = _txHeaderFlags; + + // REVISIT: do we really have to do this? perhaps just write it after writing the header? + memcpy(_buf+RH_E32_HEADER_LEN, data, len); + + _s->write(_buf, len + RH_E32_HEADER_LEN); + setMode(RHModeTx); + _txGood++; + // Aux will return high when the TX buffer is empty + + return true; +} + +uint8_t RH_E32::maxMessageLength() +{ + return RH_E32_MAX_MESSAGE_LEN; +} + +bool RH_E32::waitPacketSent() +{ + if (_mode == RHModeTx) + waitAuxHigh(); + setMode(RHModeRx); + return true; +} + +bool RH_E32::setDataRate(DataRate rate) +{ + Parameters params; + if (!readParameters(params)) + return false; + // The DataRate enums are the same values as the register bitmasks + params.sped &= ~RH_E32_PARAM_SPED_DATARATE_MASK; + params.sped |= (rate & RH_E32_PARAM_SPED_DATARATE_MASK); + return writeParameters(params); +} + +bool RH_E32::setPower(PowerLevel level) +{ + Parameters params; + if (!readParameters(params)) + return false; + // The DataRate enums are the same values as the register bitmasks + params.option &= ~RH_E32_PARAM_OPTION_POWER_MASK; + params.option |= (level & RH_E32_PARAM_OPTION_POWER_MASK); + return writeParameters(params); +} + +bool RH_E32::setBaudRate(BaudRate rate, Parity parity) +{ + Parameters params; + if (!readParameters(params)) + return false; + // The DataRate enums are the same values as the register bitmasks + params.sped &= ~RH_E32_PARAM_SPED_UART_BAUD_MASK; + params.sped |= (rate & RH_E32_PARAM_SPED_UART_BAUD_MASK); + + // Also set the parity + params.sped &= ~RH_E32_PARAM_SPED_UART_MODE_MASK; + params.sped |= (parity & RH_E32_PARAM_SPED_UART_MODE_MASK); + + return writeParameters(params); +} + + +bool RH_E32::setFrequency(uint16_t frequency) +{ + if (frequency < 410 || frequency > 441) + return false; + + Parameters params; + if (!readParameters(params)) + return false; + params.chan = frequency - 410; + return writeParameters(params); + +} + +#endif // RH_HAVE_SERIAL diff --git a/RH_E32.h b/RH_E32.h new file mode 100644 index 0000000..ce930a5 --- /dev/null +++ b/RH_E32.h @@ -0,0 +1,453 @@ +// RH_E32.h +// +// Definitions for E32-TTL-1W family serial radios: +// http://www.cdebyte.com/en/product-view-news.aspx?id=108 +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2017 Mike McCauley +// $Id: RH_E32.h,v 1.5 2020/04/09 23:40:34 mikem Exp $ +// + +#ifndef RH_E32_h +#define RH_E32_h + +#include +#include + +// The buffer in the E32 is 512 bytes, but we arbitrarily limit messages to a maximum of 58 octets +// We use some for headers, keeping fewer for RadioHead messages +// The E32 sends messages longer than 58 octets in several packets of 58 octets each, and since we dont have any +// framing or message level checksums there is a risk that long messages from 2 different sources +// could be incorrectly interpreted as a single message +#define RH_E32_MAX_PAYLOAD_LEN 58 + +// The length of the headers we add: +// message length (including these headers) +// to +// from +// id +// flags +// The headers are inside the E32 payload +#define RH_E32_HEADER_LEN 5 + +// This is the maximum RadioHead user message length that can be supported by this module. Limited by +#define RH_E32_MAX_MESSAGE_LEN (RH_E32_MAX_PAYLOAD_LEN-RH_E32_HEADER_LEN) + +// Commands to alter module behaviour +#define RH_E32_COMMAND_WRITE_PARAMS_SAVE 0xC0 +#define RH_E32_COMMAND_READ_PARAMS 0xC1 +#define RH_E32_COMMAND_WRITE_PARAMS_NOSAVE 0xC2 +#define RH_E32_COMMAND_READ_VERSION 0xC3 +#define RH_E32_COMMAND_RESET 0xC4 + +// Various flags and masks for param bytes +#define RH_E32_PARAM_SPED_UART_MODE_MASK 0xC0 +#define RH_E32_PARAM_SPED_UART_MODE_8N1 0x00 +#define RH_E32_PARAM_SPED_UART_MODE_8O1 0x40 +#define RH_E32_PARAM_SPED_UART_MODE_8E1 0x80 + +#define RH_E32_PARAM_SPED_UART_BAUD_MASK 0x38 +#define RH_E32_PARAM_SPED_UART_BAUD_1200 0x00 +#define RH_E32_PARAM_SPED_UART_BAUD_2400 0x08 +#define RH_E32_PARAM_SPED_UART_BAUD_4800 0x10 +#define RH_E32_PARAM_SPED_UART_BAUD_9600 0x18 +#define RH_E32_PARAM_SPED_UART_BAUD_19200 0x20 +#define RH_E32_PARAM_SPED_UART_BAUD_38400 0x28 +#define RH_E32_PARAM_SPED_UART_BAUD_57600 0x30 +#define RH_E32_PARAM_SPED_UART_BAUD_115200 0x38 + +#define RH_E32_PARAM_SPED_DATARATE_MASK 0x07 +#define RH_E32_PARAM_SPED_DATARATE_1KBPS 0x00 +#define RH_E32_PARAM_SPED_DATARATE_2KBPS 0x01 +#define RH_E32_PARAM_SPED_DATARATE_5KBPS 0x02 +#define RH_E32_PARAM_SPED_DATARATE_8KBPS 0x03 +#define RH_E32_PARAM_SPED_DATARATE_10KBPS 0x04 +#define RH_E32_PARAM_SPED_DATARATE_15KBPS 0x05 +#define RH_E32_PARAM_SPED_DATARATE_20KBPS 0x06 +#define RH_E32_PARAM_SPED_DATARATE_25KBPS 0x07 + +#define RH_E32_PARAM_OPTION_FIXED_MASK 0x80 + +#define RH_E32_PARAM_OPTION_IODRIVE_MASK 0x40 + +#define RH_E32_PARAM_OPTION_WAKEUP_TIME_MASK 0x38 +#define RH_E32_PARAM_OPTION_WAKEUP_TIME_250MS 0x00 +#define RH_E32_PARAM_OPTION_WAKEUP_TIME_500MS 0x08 +#define RH_E32_PARAM_OPTION_WAKEUP_TIME_750MS 0x10 +#define RH_E32_PARAM_OPTION_WAKEUP_TIME_1000MS 0x18 +#define RH_E32_PARAM_OPTION_WAKEUP_TIME_1250MS 0x20 +#define RH_E32_PARAM_OPTION_WAKEUP_TIME_1500MS 0x28 +#define RH_E32_PARAM_OPTION_WAKEUP_TIME_1750MS 0x30 +#define RH_E32_PARAM_OPTION_WAKEUP_TIME_2000MS 0x38 + +#define RH_E32_PARAM_OPTION_FEC_MASK 0x04 + +#define RH_E32_PARAM_OPTION_POWER_MASK 0x03 +#define RH_E32_PARAM_OPTION_POWER_30DBM 0x00 +#define RH_E32_PARAM_OPTION_POWER_27DBM 0x01 +#define RH_E32_PARAM_OPTION_POWER_24DBM 0x02 +#define RH_E32_PARAM_OPTION_POWER_21DBM 0x03 + +///////////////////////////////////////////////////////////////////// +/// \class RH_E32 RH_E32.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via a EBYTE E32-TTL-1W +/// and similar serial radio transceiver. +/// +/// Works with +/// E32-TTL-1W +/// +/// Note: it should also be possible to use the E32-TTL-1W with the RadioHead RH_Serial module, +/// which will also you to send longer packets, but will require you to use the EBYTE Wireless Module Setting program +/// to configure the radio first. In this arrangement the E32 would act as a transparent serial connection. +/// This has not been tested by us. +/// +/// \par Overview +/// +/// This class provides basic functions for sending and receiving unaddressed, +/// unreliable datagrams of arbitrary length to 53 octets per packet. +/// +/// Manager classes may use this class to implement reliable, addressed datagrams and streams, +/// mesh routers, repeaters, translators etc. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// modulation scheme. +/// +/// This Driver provides an object-oriented interface for sending and receiving data messages with EBYTE +/// RFM95/96/97/98(W), Semtech SX1276/77/78/7E32-TTL-1W9 and compatible radio modules. These modules implement +/// long range LORA transcivers with a transparent serial interface. With 1W power output the manufacturer +/// claims up to 6km range. +/// +/// This Driver provides functions for sending and receiving messages of up +/// to 53 octets on any frequency supported by the radio, in a range of +/// data rates and power outputs. Frequency can be set with +/// 1MHz precision to any frequency from 410 to 441MHz. +/// +/// You can use either a hardware or software serial connection. +/// +/// Tested with Arduino Uno and software serial. +/// +/// \par Packet Format +/// +/// All messages sent and received by this Driver conform to this packet format: +/// +/// - 5 octets HEADER: (LENGTH, TO, FROM, ID, FLAGS) +/// - 0 to 53 octets DATA +/// +/// \par Connecting E32-TTL-1W to Arduino +/// +/// We tested with Arduino Uno. We used SoftwareSerial on pins 6 and 7) to connect to the E32 module, so +/// we could continue to use the only hardware serial port for debugging +/// \code +/// Arduino E32 +/// GND----------GND (ground in) +/// 5V-----------VCC (5V in) +/// pin D4-----------M0 (mode control pin input to radio) +/// pin D5-----------M1 (mode control pin input to radio) +/// pin D6-----------RXD (serial data input from Arduino to radio) +/// pin D7-----------TXD (serial data output from radio to Arduino) +/// pin D8-----------AUX (Aux pin output from radio to Arduino) +/// \endcode +/// With this connection, you can initialise the serial port and RH_E32 like this: +/// \code +/// SoftwareSerial mySerial(7, 6); +/// RH_E32 driver(&mySerial, 4, 5, 8); +/// \endcode +/// +/// For Adafruit M0 Feather: +/// \code +/// Feather E32 +/// GND----------GND (ground in) +/// 3V-----------VCC (3.3V in) +/// pin D5-----------M0 (mode control pin input to radio) +/// pin D6-----------M1 (mode control pin input to radio) +/// pin D1/Tx--------RXD (serial data input from M0 to radio) +/// pin D0/Rx--------TXD (serial data output from radio to M0) +/// pin D9-----------AUX (Aux pin output from radio to M0) +/// \endcode +/// With this connection, you can initialise serial port 1 and RH_E32 like this: +/// \code +/// RH_E32 driver(&Serial1, 5, 6, 9); +/// \endcode +/// Other connection schems are possible provided the approporiate constructors are used for SoftwareSerial and RH_E32 +/// +/// \par Memory +/// +/// The RH_RF95 driver requires non-trivial amounts of memory. The sample +/// programs all compile to about 8kbytes each, which will fit in the +/// flash proram memory of most Arduinos. However, the RAM requirements are +/// more critical. Therefore, you should be vary sparing with RAM use in +/// programs that use the RH_E32 driver. +/// +/// It is often hard to accurately identify when you are hitting RAM limits on Arduino. +/// The symptoms can include: +/// - Mysterious crashes and restarts +/// - Changes in behaviour when seemingly unrelated changes are made (such as adding print() statements) +/// - Hanging +/// - Output from Serial.print() not appearing +/// +/// \par Performance +/// +/// This radio supports a range of different data rates and powers. +/// The lowest speeds are the most reliable, however you should note that at 1kbps and with an 13 octet payload, +/// the transmission time for one packet approaches 5 seconds. Therefore you should be cautious about trying to +/// send too many or too long messages per unit of time, lest you monopolise the airwaves. +/// Be a good neighbour and use the lowest power and fastest speed that you can. +/// +/// Forward Error Correction (FEC) is always enabled in these radios by RH_E32. +/// +/// \par Range +/// +/// When running with a power output of 1W and at the slowest speed of 1kbps, this module has an impressive range. +/// We tested with: +/// E32-TTL-1W (1 W power, 1kbps data rate) +/// Single wire antenna with a small meta ground plane at about 1m above ground level +/// Arduino Uno +/// RadioHead RH_E32 module with e32_client and e32_server sketches +/// Packet length 13 octets (total payload 18 octets) +/// (and yes, we have an appropriate radio license for that power output) +/// +/// We were able to get reliable reception over 7km (6 km over ocean and 1 km through low rise residential area) +/// +/// You can expect less range with lower power outputs and faster speeds. +/// You can expect less range in highrise cities. +/// You can expect more range with directional antennas. +/// You can expect more range with shorter messages. +/// +/// \par Transmitter Power +/// TBA +/// +/// Caution: the maximum power output of this radio (1W = 30dbM) is almost certainly more than the +/// permitted power level for unlicensed users in the ISM bands in most countries. Be sure you comply with your local +/// regulations. Be a good neighbour and use the lowest power and fastest speed that you can. +/// +class RH_E32 : public RHGenericDriver +{ + public: + + /// \brief Values to be passed to setDataRate() to control the on-air data rate + /// + /// This is NOT to be used to control the baud rate of the serial connection to the radio + typedef enum + { + DataRate1kbps = RH_E32_PARAM_SPED_DATARATE_1KBPS, + DataRate2kbps = RH_E32_PARAM_SPED_DATARATE_2KBPS, + DataRate5kbps = RH_E32_PARAM_SPED_DATARATE_5KBPS, + DataRate8kbps = RH_E32_PARAM_SPED_DATARATE_8KBPS, + DataRate10kbps = RH_E32_PARAM_SPED_DATARATE_10KBPS, + DataRate15kbps = RH_E32_PARAM_SPED_DATARATE_15KBPS, + DataRate20kbps = RH_E32_PARAM_SPED_DATARATE_20KBPS, + DataRate25kbps = RH_E32_PARAM_SPED_DATARATE_25KBPS + } DataRate; + + /// \brief Values to be passed to setPower() to control the transmitter power + /// + typedef enum + { + Power30dBm = RH_E32_PARAM_OPTION_POWER_30DBM, + Power27dBm = RH_E32_PARAM_OPTION_POWER_27DBM, + Power24dBm = RH_E32_PARAM_OPTION_POWER_24DBM, + Power21dBm = RH_E32_PARAM_OPTION_POWER_21DBM, + } PowerLevel; + + /// \brief Values to be passed to setBaudRate() to control the radio serial connection baud rate + /// + /// This is NOT to be used to control the on-air data rate the radio transmits and receives at + typedef enum + { + BaudRate1200 = RH_E32_PARAM_SPED_UART_BAUD_1200, + BaudRate2400 = RH_E32_PARAM_SPED_UART_BAUD_2400, + BaudRate4800 = RH_E32_PARAM_SPED_UART_BAUD_4800, + BaudRate9600 = RH_E32_PARAM_SPED_UART_BAUD_9600, + BaudRate19200 = RH_E32_PARAM_SPED_UART_BAUD_19200, + BaudRate38400 = RH_E32_PARAM_SPED_UART_BAUD_38400, + BaudRate57600 = RH_E32_PARAM_SPED_UART_BAUD_57600, + BaudRate115200 = RH_E32_PARAM_SPED_UART_BAUD_115200, + } BaudRate; + + /// \brief Values to be passed to setBaudRate() to control the parity of the serial connection to the radio + typedef enum + { + Parity8N1 = RH_E32_PARAM_SPED_UART_MODE_8N1, + Parity8O1 = RH_E32_PARAM_SPED_UART_MODE_8O1, + Parity8E1 = RH_E32_PARAM_SPED_UART_MODE_8E1, + } Parity; + + /// Contructor. You can have multiple instances, but each instance must have its own + /// serial connection, M0 M1 and AUX connections. Initialises the mode of the referenced pins + /// Does NOT set the baud rate of the serial connection to the radio. + /// \param[in] s Reference to the SoftwareSerial or HardwareSerial port used to connect to the radio + /// \param[in] m0_pin Pin number of the Arduino pin that connects to the radio M0 input + /// \param[in] m1_pin Pin number of the Arduino pin that connects to the radio M1 input + /// \param[in] aux_pin Pin number of the Arduino pin that connects to the radio AUX output + RH_E32(Stream *s=&Serial, uint8_t m0_pin = 4, uint8_t m1_pin = 5, uint8_t aux_pin = 8); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly, including setting the serial port baud rate and parity to that + /// configured in the radio (typically 9600 baud, 8N1) before calling init(). + /// Sets the module to 443MHz, 21dBm power and 5kbps data rate (you can change these after initialisation with + /// the various set* functions). + /// This function may not return if the AUX pin is not connected. + /// Initialisation failure can be caused by: + /// Electrical connections to the radio incorrect or incomplete + /// Radio configured to use a different baud rate to the one configured to the Ardiono serial port + /// Incorrect radio module connected tot he serial port. + /// Other serial communicaitons problems between the Arduino and the radio + /// \return true if initialisation succeeded. + bool init(); + + /// Tests whether a new message is available + /// from the Driver. + /// This can and should be called multiple times in a timeout loop. You should call this as frequently as possible + /// whenever a message might be received + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv(). + bool available(); + + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send + /// \return true if the message length was valid and it was correctly queued for transmit. Return false + /// if CAD was requested and the CAD timeout timed out before clear channel was detected. + bool send(const uint8_t* data, uint8_t len); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + uint8_t maxMessageLength(); + + /// Waits for any currently transmitting packet to be completely sent + /// Returns true if successful + bool waitPacketSent(); + + /// Sets the on-air data rate to be used by the transmitter and receiver + /// \param[in] rate A valid data rate from the DataRate enum + /// \return true if successful + bool setDataRate(DataRate rate); + + /// Sets the transmitter power output + /// \param[in] level A valid power setting from the Power enum + /// \return true if successful + bool setPower(PowerLevel level); + + /// Sets the radio serial port baud rate and parity (not the on-air data rate) + /// Does not set the Aruino rate or parity: you wil nned to do this afterwards + /// \param[in] rate A valid baud rate from the BaudRate enum + /// \param[in] parity A valid parity from the PArity enum + /// \return true if successful + bool setBaudRate(BaudRate rate = BaudRate9600, Parity parity = Parity8N1); + + /// Sets the tarnsmitter and receiver frequency. + /// \param[in] frequency Desired frequency in MHx from 410 to 441 MHz inclusive + /// \return true if successful + bool setFrequency(uint16_t frequency); + +protected: + + /// \brief Defines values to be passed to setOperatinMode + /// + /// For internal driver user only + typedef enum + { + ModeNormal = 0, ///< Normal mode for sending and receiving messages + ModeWakeUp, ///< Adds a long preamble to transmission to allow destination receivers to wake up + ModePowerSaving, ///< Receiver sleeps until a message is received + ModeSleep ///< Use during parameter setting + } OperatingMode; + + /// \brief Structure for reading and writing radio control parameters + /// + /// For internal driver user only + typedef struct + { + uint8_t head; ///< 0xc2 (no save) or 0xc0 (save) + uint8_t addh; ///< High address byte (not used by this driver) + uint8_t addl; ///< Low address byte (not used by this driver) + uint8_t sped; ///< Data and baud rate parameters + uint8_t chan; ///< Radio channel + uint8_t option; ///< Various control options + + } Parameters; + + /// Sets the operating mode of the radio. + /// For internal use only + void setOperatingMode(OperatingMode mode); + + /// Retrieves the version number for the radio and checks that it is valid + /// \return true if the version could be retrieved and is radio model number is correct + bool getVersion(); + + /// Waits for the AUX pin to go high + /// For internal use only + void waitAuxHigh(); + + /// Waits for the AUX pin to go low + /// For internal use only + void waitAuxLow(); + + /// Issues a reset command to the radio + /// WARNING: this seems to break reception. Why? + /// \return true if successful + bool reset(); + + /// Read the radio configuration parameters into + /// local memory + /// \param[in] params Reference to a Parameter structure which will be filled if successful + /// \return true if successful + bool readParameters(Parameters& params); + + /// Write radio configuration parameters from local memory + /// to the radio. You can choose whether the parameter will be saved across power down or not + /// \param[in] params Reference to a Parameter structure containing the radio configuration parameters + /// to be written to the radio. + /// \param[in] save If true, the parameters will be saved across power down in the radio + /// \return true if successful + bool writeParameters(Parameters& params, bool save = false); + + /// Examine the receive buffer to determine whether the message is for this node + /// For internal use only + void validateRxBuf(); + + /// Clear our local receive buffer + /// For internal use only + void clearRxBuf(); + +private: + /// Serial stream (hardware or software serial) + Stream* _s; + + /// Pin number connected to M0 + uint8_t _m0_pin; + + /// Pin number connected to M1 + uint8_t _m1_pin; + + /// Pin number connected to AUX + uint8_t _aux_pin; + + /// Number of octets in the buffer + uint8_t _bufLen; + + /// The receiver/transmitter buffer + uint8_t _buf[RH_E32_MAX_PAYLOAD_LEN]; + + /// True when there is a valid message in the buffer + bool _rxBufValid; + +}; + +/// @example e32_client.ino +/// @example e32_server.ino + +#endif + diff --git a/RH_LoRaFileOps.cpp b/RH_LoRaFileOps.cpp new file mode 100644 index 0000000..a2ad2ca --- /dev/null +++ b/RH_LoRaFileOps.cpp @@ -0,0 +1,279 @@ +// RH_LoRaFileOpscpp +// +// Copyright (C) 2021 Mike McCauley + +#include + +// This can only build on Linux and compatible systems +// Caution also requires Lora-file-ops driver to be installed +// See https://github.com/starnight/LoRa/tree/file-ops +#if (RH_PLATFORM == RH_PLATFORM_UNIX) + +#include +#include +#include +#include + +RH_LoRaFileOps::RH_LoRaFileOps(const char* port) + : + _port(port), + _fd(-1) +{ +} + +bool RH_LoRaFileOps::init() +{ + if (!RHGenericDriver::init()) + return false; + + _fd = open(_port, O_RDWR); + if (_fd == -1) + { + Serial.print("RH_LoRaFileOps::init LORA device port open failed: "); + Serial.println(strerror(errno)); + return false; + } + + // These settings are compatible with RH_RF95::Bw125Cr45Sf2048 and the other defaults + // for RH_RF95 + setFrequency(434000000); + setTxPower(13); + setSpreadingFactor(2048); + setBW(125000); + setCRC(true); + setLNA(0); + + return true; +} + +bool RH_LoRaFileOps::available() +{ + // Test if the port can be read + fd_set read_fds; + struct timeval tv = {.tv_sec = 0, .tv_usec = 0}; + FD_ZERO(&read_fds); + FD_SET(_fd, &read_fds); + if (select(_fd+1, &read_fds, NULL, NULL, &tv) == -1) + { + Serial.println("Select failed"); + return false; + } + + return FD_ISSET(_fd, &read_fds) ? true : false; + +} + +bool RH_LoRaFileOps::recv(uint8_t* buf, uint8_t* len) +{ + if (_fd == -1) return false; + + if (!available()) return false; + + // Read the available packet from the driver + uint8_t readbuf[RH_LORAFILEOPS_MAX_PAYLOAD_LEN]; + ssize_t sz; + sz = read(_fd, readbuf, RH_LORAFILEOPS_MAX_PAYLOAD_LEN); + + // Remember the last signal to noise ratio, LORA mode + // Per page 111, SX1276/77/78/79 datasheet + _lastSNR = getSNR(); + + // Remember the RSSI of this packet, LORA mode + // This figure has already been massaged by the driver + _lastRssi = getRSSI(); + + // Test if its really for us + if (sz < 4) + return false; // Too short to be a real message + // Extract the 4 headers + _rxHeaderTo = readbuf[0]; + _rxHeaderFrom = readbuf[1]; + _rxHeaderId = readbuf[2]; + _rxHeaderFlags = readbuf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + // Yes its for us + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > (sz - RH_LORAFILEOPS_HEADER_LEN)) + *len = sz - RH_LORAFILEOPS_HEADER_LEN; + memcpy(buf, readbuf + RH_LORAFILEOPS_HEADER_LEN, *len); + Serial.println("recv OK"); + return true; + } + + // Not for us + return false; + + +} + +bool RH_LoRaFileOps::send(const uint8_t* data, uint8_t len) +{ + if (_fd == -1) return false; + + if (len > RH_LORAFILEOPS_MAX_MESSAGE_LEN) + return false; + + // Should never need to wait for a packet to be sent since the driver always + // waits before returning from write + + + // Assemble the entire message, including RadioHead header + // Header bytes, total size of RH_LORAFILEOPS_HEADER_LEN + uint8_t buf[RH_LORAFILEOPS_MAX_PAYLOAD_LEN]; + buf[0] = _txHeaderTo; + buf[1] = _txHeaderFrom; + buf[2] = _txHeaderId; + buf[3] = _txHeaderFlags; + memcpy(buf+4, data, len); + + // Send the message to the driver + + ssize_t sz; + // Returns when the packet has been transmtted: + sz = write(_fd, buf, len+RH_LORAFILEOPS_HEADER_LEN); + return sz == len+RH_LORAFILEOPS_HEADER_LEN; +} + +uint8_t RH_LoRaFileOps::maxMessageLength() +{ + return RH_LORAFILEOPS_MAX_MESSAGE_LEN; +} + +bool RH_LoRaFileOps::setFrequency(uint32_t centre) +{ + if (_fd == -1) return false; + + ioctl(_fd, LORA_SET_FREQUENCY, ¢re); + return true; // FIXME?? +} + +uint32_t RH_LoRaFileOps::getFrequency() +{ + if (_fd == -1) return 0; + + uint32_t freq; + ioctl(_fd, LORA_GET_FREQUENCY, &freq); + return freq; +} + +void RH_LoRaFileOps::setTxPower(int32_t power) +{ + if (_fd == -1) return; + + ioctl(_fd, LORA_SET_POWER, &power); +} + +int32_t RH_LoRaFileOps::getTxPower() +{ + if (_fd == -1) return 0; + + int32_t power; + ioctl(_fd, LORA_GET_POWER, &power); + return power; +} + +void RH_LoRaFileOps::setState(uint32_t state) +{ + if (_fd == -1) return; + + ioctl(_fd, LORA_SET_STATE, &state); +} + +uint32_t RH_LoRaFileOps::getState() +{ + if (_fd == -1) return LORA_STATE_SLEEP; + + uint32_t state; + ioctl(_fd, LORA_GET_STATE, &state); + return state; +} + +void RH_LoRaFileOps::setSpreadingFactor(int32_t sf) +{ + ioctl(_fd, LORA_SET_SPRFACTOR, &sf); +} + +int32_t RH_LoRaFileOps::getSpreadingFactor() +{ + if (_fd == -1) return 0; + + uint32_t sprf; + ioctl(_fd, LORA_GET_SPRFACTOR, &sprf); + return sprf; +} + +// This gets the raw RSSI figure. Its not adjusted to dBm +int32_t RH_LoRaFileOps::getRSSI() +{ + if (_fd == -1) return 0; + + int32_t rssi; + ioctl(_fd, LORA_GET_RSSI, &rssi); + return rssi; +} + +// Last packet SNR +int32_t RH_LoRaFileOps::getSNR() +{ + if (_fd == -1) return 0; + + int32_t snr; + ioctl(_fd, LORA_GET_SNR, &snr); + return snr; +} + +void RH_LoRaFileOps::setLNA(int32_t lna) +{ + if (_fd == -1) return; + + ioctl(_fd, LORA_SET_LNA, &lna); +} + +int32_t RH_LoRaFileOps::getLNA() +{ + if (_fd == -1) return 0; + + int32_t lna; + ioctl(_fd, LORA_GET_LNA, &lna); + return lna; +} + +void RH_LoRaFileOps::setLNAAGC(int32_t lnaagc) +{ + if (_fd == -1) return; + + ioctl(_fd, LORA_SET_LNAAGC, &lnaagc); +} + +void RH_LoRaFileOps::setBW(int32_t bw) +{ + if (_fd == -1) return; + + ioctl(_fd, LORA_SET_BANDWIDTH, &bw); +} + +int32_t RH_LoRaFileOps::getBW() +{ + if (_fd == -1) return 0; + + uint32_t bw; + ioctl(_fd, LORA_GET_BANDWIDTH, &bw); + return bw; +} + +void RH_LoRaFileOps::setCRC(uint32_t crc) +{ + if (_fd == -1) return; + + ioctl(_fd, LORA_SET_CRC, &crc); +} + +int RH_LoRaFileOps::lastSNR() +{ + return _lastSNR; +} + +#endif diff --git a/RH_LoRaFileOps.h b/RH_LoRaFileOps.h new file mode 100644 index 0000000..e167557 --- /dev/null +++ b/RH_LoRaFileOps.h @@ -0,0 +1,368 @@ +// RH_LoRaFileOps.h +// +// Definitions for RadioHead driver on RPi+Linux +// and using LoRa-file-ops Linux driver ioctls to +// transmit and receive RadioHead compatible messages via SX1276/77/78/79 +// and compatible radios. +// Requires a modified version of LoRa-file-ops driver to be installed, +// and a compatible radio to be connected +// appropriately +// https://github.com/starnight/LoRa/tree/file-ops +// +// Tested with: +// RPi 2 + Debian 2021-03-04 (kernel 5.10.17-v7+ #1403) +// Dragino LoRa/GPS HAT, https://wiki.dragino.com/index.php?title=Lora/GPS_HAT +// modified so RFM95 pin 5 NSS was no longer connected to RPi +// pin P1-22 (GPIO6), but is instead connected to RPi Pin P1-24 (CE0) which +// is the one used by /dev/loraSPI0.0 +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2021 Mike McCauley +// + +#ifndef RH_LORAFILEOPS_h +#define RH_LORAFILEOPS_h + +#include +#warning RH_LoRaFileOps unfinished + +// This can only build on Linux and compatible systems +// Caution also requires Lora-file-ops driver to be installed +// See https://github.com/starnight/LoRa/tree/file-ops +#if (RH_PLATFORM == RH_PLATFORM_UNIX) || defined(DOXYGEN) + +// Driver constant definitions +// These are copied from LoRa file-ops branch pull request #16 +// See the instructions in the RH_LoRaFileOps documentation for getting that version from github +// which is absolutely necessary if you want to support RadioHead messages with CRC enabled, +// which we strongly recommend. +// since they are not necessarily available in the compile host file +// system. +// CAUTION: these must be kept in sync with LoRa-file-ops if it changes +/* I/O control by each command. */ +#include +#define LORA_IOC_MAGIC '\x74' + +#define LORA_SET_STATE (_IOW(LORA_IOC_MAGIC, 0, int)) +#define LORA_GET_STATE (_IOR(LORA_IOC_MAGIC, 1, int)) +#define LORA_SET_FREQUENCY (_IOW(LORA_IOC_MAGIC, 2, int)) +#define LORA_GET_FREQUENCY (_IOR(LORA_IOC_MAGIC, 3, int)) +#define LORA_SET_POWER (_IOW(LORA_IOC_MAGIC, 4, int)) +#define LORA_GET_POWER (_IOR(LORA_IOC_MAGIC, 5, int)) +#define LORA_SET_LNA (_IOW(LORA_IOC_MAGIC, 6, int)) +#define LORA_GET_LNA (_IOR(LORA_IOC_MAGIC, 7, int)) +#define LORA_SET_LNAAGC (_IOR(LORA_IOC_MAGIC, 8, int)) +#define LORA_SET_SPRFACTOR (_IOW(LORA_IOC_MAGIC, 9, int)) +#define LORA_GET_SPRFACTOR (_IOR(LORA_IOC_MAGIC, 10, int)) +#define LORA_SET_BANDWIDTH (_IOW(LORA_IOC_MAGIC, 11, int)) +#define LORA_GET_BANDWIDTH (_IOR(LORA_IOC_MAGIC, 12, int)) +#define LORA_GET_RSSI (_IOR(LORA_IOC_MAGIC, 13, int)) +#define LORA_GET_SNR (_IOR(LORA_IOC_MAGIC, 14, int)) +/* Mikem added 2021-04-19 fro pull request 16: */ +#define LORA_SET_CRC (_IOW(LORA_IOC_MAGIC, 15, int)) +#define LORA_SET_CODINGRATE (_IOW(LORA_IOC_MAGIC, 16, int)) +#define LORA_GET_CODINGRATE (_IOR(LORA_IOC_MAGIC, 17, int)) +#define LORA_SET_IMPLICIT (_IOW(LORA_IOC_MAGIC, 18, int)) +#define LORA_SET_LDRO (_IOW(LORA_IOC_MAGIC, 19, int)) +#define LORA_SET_PREAMBLE (_IOW(LORA_IOC_MAGIC, 20, int)) +#define LORA_GET_PREAMBLE (_IOR(LORA_IOC_MAGIC, 21, int)) +#define LORA_SET_PARAMP (_IOW(LORA_IOC_MAGIC, 22, int)) +#define LORA_GET_PARAMP (_IOR(LORA_IOC_MAGIC, 23, int)) +#define LORA_SET_OCPIMAX (_IOW(LORA_IOC_MAGIC, 24, int)) +#define LORA_GET_OCPIMAX (_IOR(LORA_IOC_MAGIC, 25, int)) +#define LORA_SET_LNABOOSTHF (_IOW(LORA_IOC_MAGIC, 26, int)) +#define LORA_SET_PMAX20DBM (_IOW(LORA_IOC_MAGIC, 27, int)) + + +/* List the state of the LoRa device. */ +#define LORA_STATE_SLEEP 0 +#define LORA_STATE_STANDBY 1 +#define LORA_STATE_TX 2 +#define LORA_STATE_RX 3 +#define LORA_STATE_CAD 4 + +// Max number of octets the SX1278 LORA Rx/Tx FIFO can hold +#define RH_LORAFILEOPS_FIFO_SIZE 255 + +// This is the maximum number of bytes that can be carried by the LORA. +// We use some for headers, keeping fewer for RadioHead messages +#define RH_LORAFILEOPS_MAX_PAYLOAD_LEN RH_LORAFILEOPS_FIFO_SIZE + +// The length of the headers we add. +// The headers are inside the LORA's payload +#define RH_LORAFILEOPS_HEADER_LEN 4 + +// This is the maximum message length that can be supported by this driver. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS +#ifndef RH_LORAFILEOPS_MAX_MESSAGE_LEN +#define RH_LORAFILEOPS_MAX_MESSAGE_LEN (RH_LORAFILEOPS_MAX_PAYLOAD_LEN - RH_LORAFILEOPS_HEADER_LEN) +#endif + +///////////////////////////////////////////////////////////////////// +/// \class RH_LoRaFileOps RH_LoRaFileOps.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via a LoRa +/// capable radio transceiver on a Linux platform (possibly Raspberry Pi), using the +/// lora-file-ops driver by Jian-Hong Pan (starnight): +/// https://github.com/starnight/LoRa/tree/file-ops +/// +/// This RadioHead driver is only available to Commercial licensees. Apply to info@airspayce.com. +/// +/// For an excellent discussion of LoRa range and modulations, see +/// https://medium.com/home-wireless/testing-lora-radios-with-the-limesdr-mini-part-2-37fa481217ff +/// Works with Dragino LoRa/GPS HAT, https://wiki.dragino.com/index.php?title=Lora/GPS_HAT +/// modified so RFM95 pin 5 NSS was no longer connected to RPi +/// pin P1-22 (GPIO6), but is instead connected to RPi Pin P1-24 (CE0) which +/// is the one used by /dev/loraSPI0.0. Interoperates with RH_RF95 +/// with modem config RH_RF95::Bw125Cr45Sf2048 +/// +/// \par Overview +/// +/// This class provides basic functions for sending and receiving unaddressed, +/// unreliable datagrams of arbitrary length up to 251 octets per packet. +/// +/// Manager classes may use this class to implement reliable, addressed datagrams and streams, +/// mesh routers, repeaters, translators etc. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// modulation scheme. +/// +/// This RadioHead Driver provides an object-oriented interface for sending and receiving +/// data messages with Semtech SX1276/77/78/79 +/// and compatible radio modules in LoRa mode, using the lora-file-ops Linux driver. It only runs on Linux +/// such as Raspberry Pi Debian etc.is a low-cost ISM transceiver +/// chip. It supports FSK, GFSK, OOK over a wide range of frequencies and +/// programmable data rates, and it also supports the proprietary LoRA (Long Range) mode, which +/// is the only mode supported in this RadioHead driver (because that is the only mode supported by the +/// underlying lora-file-ops Linux driver. +/// +/// This Driver provides functions for sending and receiving messages of up +/// to 251 octets on any frequency supported by the radio, in a range of +/// predefined Bandwidths, Spreading Factors and Coding Rates. Frequency can be set with +/// 61Hz precision to any frequency from 240.0MHz to 960.0MHz. Caution: most modules only support a more limited +/// range of frequencies due to antenna tuning. +/// +/// Up to 2 modules are supported by lora-file-ops +/// permitting the construction of translators and frequency changers, etc. +/// +/// Support for other features such as transmitter power control etc is +/// also provided. +/// +/// Tested with: +/// RPi 2 + Debian 2021-03-04 (kernel 5.10.17-v7+ #1403) +/// Dragino LoRa/GPS HAT, https://wiki.dragino.com/index.php?title=Lora/GPS_HAT +/// modified so RFM95 pin 5 NSS was no longer connected to RPi +/// pin P1-22 (GPIO6), but is instead connected to RPi Pin P1-24 (CE0) which +/// is the one used by /dev/loraSPI0.0 +/// \par Packet Format +/// +/// All messages sent and received by this RH_RF95 Driver conform to this packet format: +/// +/// - LoRa mode: +/// - 8 symbol PREAMBLE +/// - Explicit header with header CRC (default CCITT, handled internally by the radio) +/// - 4 octets HEADER: (TO, FROM, ID, FLAGS) +/// - 0 to 251 octets DATA +/// - CRC (default CCITT, handled internally by the radio) +/// +/// This format is compatible with the one used by RH_RF95 by default. +/// +/// \par Modulation +/// +/// The default modulation scheme implemented by this driver is: +/// 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on +/// which is compatible withthe RH_RF95 modem config RH_RF95::Bw125Cr45Sf2048 and so this RadioHead driver will +/// interoperate with RH_RF95. +/// +/// \par Installing lora-file-ops +/// For this driver to work on a Linux platform such as Raspberry Pi, it is absolutely necessary to install the +/// LoRa-file-ops Linux driver written by starnight, and which is available from github. +/// The version currently available (2021-04-19) does not support enabling CRCs in the radio, which is +/// strongly recommended, and necessary to work with any other RadioHead lora driver/ +/// At this date, code to add CRC suport to the driver is avalable as a pull request on github +/// as a Git pull request #16 from flyskywhy. We strongly recommend using it as described below. +/// +/// To get LoRa-file-ops from starnight, plus the necessary patches and fixes from pull request #16 from flyskywhy +/// and to build it and install it and load it into the kernel for testing: +/// \code +/// # ON a recent Debian kernel: +/// sudo apt-get install linux-headers-rpi raspberrypi-kernel-headers +/// # Enable the SPI interface in the kernel +/// sudo raspi-config: +/// -> 3 Interface Options +/// -> P4 SPI +/// -> Would you like the SPI interface to be enabled? select Yes, press Return, Return, Select Finish +/// # in a working directory, not as root: +/// git clone https://github.com/starnight/LoRa.git +/// cd LoRa/ +/// git checkout file-ops +/// git fetch origin pull/16/head:file-ops-patched # only until pull #16 is not merged into master +/// git checkout file-ops-patched # only until pull #16 is not merged into master +/// cd LoRa/ +/// make +/// make install +/// cd ../dts-overlay +/// make +/// cd ../ +/// # and after every reboot: +/// sudo dtoverlay rpi-lora-spi +/// sudo modprobe sx1278 +/// \endcode +/// +/// If you want to permanently add the LoRa-file-ops Linux driver so it loads automatically +/// on every boot, add this to /boot/config.txt +/// \code +/// dtparam=rpi-lora-spi=on +/// \endcode +/// +/// Note: it may be the case in the future that pull request 16 is merged into the master of LoRa-File-Ops +/// in which case 2 steps are not needed above +class RH_LoRaFileOps : public RHGenericDriver +{ +public: + /// Constructor. You can have multiple instances each connected to a different LoRa port + /// \param[in] port Name of the lora-file-ops port, typically something like /dev/loraSPI0.0 + RH_LoRaFileOps(const char* port); + + /// Initialise the Driver transport hardware and software. + /// Opens the LorFileOps driver port and initalises the radio to default settings + /// Leaves the radio in receive mode, + /// with default configuration of: 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 2048chips/symbol, CRC on + /// which is compatible with RH_RF95::Bw125Cr45Sf2048 + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Tests whether a new message is available from the lora-file-ops Linux driver. + /// This can be called multiple times in a timeout loop + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available(); + + // Sigh, its not possible to implement waitAvailable and waitAvailableTimout in terms + // of select(), since the LoRa-file-ops driver does not detect any interrupts, and + // therefore select will not return when a packet is received by the radio. + // So we have to live with the RHGenericDriver implementations that call available() to poll the port. + // waitAvailableTimeout() supports an optional delay between each poll + // of available() so that on Linux at least another process can get the CPU. + + /// If there is a valid message available and it is for this node, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// or call it aafter available(), waitAvailable() or waitAvailableTimeout() + /// indicate that a message is avalable + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied. + /// \return true if a valid message addressed to this node was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len); + + /// Loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is permitted. CAD is not supported yet. + /// The lora-file-ops driver waits for the entire message to be transmitted before resuming operations. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send + /// \return true if the message length was valid and it was correctly transmitted. + virtual bool send(const uint8_t* data, uint8_t len); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + /// Sets the transmitter and receiver + /// centre (carrier) frequency. + /// \param[in] centre Frequency in Hz. 137000000 to 1020000000. Caution: SX1276/77/78/79 comes in several + /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work + /// correctly becasue the antenna coupling or antenna wont work outside their designed frequency range + /// \return true if the selected frequency centre is within range + bool setFrequency(uint32_t centre); + + /// Returns the current transmitter and receiver + /// centre frequency. + /// \return Centre frequency in Hz. + uint32_t getFrequency(); + + /// Returns the Signal-to-noise ratio (SNR) of the last received message, as measured + /// by the receiver. + /// \return SNR of the last received message in dB + int lastSNR(); + + /// Sets the transmitter power output level + /// Be a good neighbour and set the lowest power level you need. + /// Caution: legal power limits may apply in certain countries. + /// After init(), the power will be set to 13dBm + /// \param[in] power Transmitter power level in dBm. Max 20dBm. + void setTxPower(int32_t power); + + /// Gets the currently set transmitter power output level + /// \return Current poer level in dbM + int32_t getTxPower(); + + /// Set the LoRa Spreading Factor + /// \param[in] sf The spreading factor. Valid values are 64, 128, 256, 512, 1024, 2048, 4096. + void setSpreadingFactor(int32_t sf); + + /// Get the LoRa Spreading Factor + /// \return The current Spreading Factor + int32_t getSpreadingFactor(); + + /// Gets the RSSI of the last received packet + /// \return RSSI of the last received packet + int32_t getRSSI(); + + /// Gets the Signal To Noise (SNR) of the last received packet + /// \return SNR of the last received packet + int32_t getSNR(); + + /// Set the receiver Low Noise Amplifier (LNA) gain + /// \param[in] lna LNA gain in dBm + void setLNA(int32_t lna); + + /// Get the current LNA gain + /// \return The current LNA gain in dBm + int32_t getLNA(); + + /// Set the LNA Automatic Gain Control (AGC) enabled + /// \param[in] lnaagc 1 to enable LNA AGC, 0 to disable it + void setLNAAGC(int32_t lnaagc); + + /// Set the transmitter and receiver modulation bandwidth + /// \param[in] bw Modulation bandwidth in Hz. Valid values are 7800, 10400, 15600, 20800, + /// 312500, 41700, 62500, 125000, 250000, 500000. + void setBW(int32_t bw); + + /// Get the transmitter and receiver modulation bandwidth + /// \return Modulation bandwidth in Hz + int32_t getBW(); + + /// Enable Cyclic Redundancy Check (CRC) in the transmitter and receiver. If enabled, + /// the transmitter will always appenda CRC to every packet, and the receiver will + /// always check the CRC on received packets, ignoring packets with incorrect CRC + /// \param[in] crc 1 to enable CRC generation and detection, 0 to disable it + void setCRC(uint32_t crc); + + +protected: + /// Set the current radio state, one of LORA_STATE_* + void setState(uint32_t state); + + /// Get the current radio state + uint32_t getState(); + +private: + // The name of the Unix filesystm port for the Lora SX1278 compatible radio + // typically /dev/loraSPI0.0 or similar + const char* _port; + + /// Unix file system device number of the LoRa device port. -1 if not open + int _fd; + + /// Last measured SNR, dB + int8_t _lastSNR; +}; + +/// @example lorafileops_client.cpp +/// @example lorafileops_server.cpp + +#endif +#endif diff --git a/RH_MRF89.cpp b/RH_MRF89.cpp new file mode 100644 index 0000000..1e8c282 --- /dev/null +++ b/RH_MRF89.cpp @@ -0,0 +1,576 @@ +// RH_MRF89.cpp +// +// Copyright (C) 2015 Mike McCauley +// $Id: RH_MRF89.cpp,v 1.10 2019/09/02 05:21:52 mikem Exp $ + +#include +#define BAND_915 +#define DATA_RATE_200 +#define LNA_GAIN LNA_GAIN_0_DB +#define TX_POWER TX_POWER_13_DB + +// Interrupt vectors for the 3 Arduino interrupt pins +// Each interrupt can be handled by a different instance of RH_MRF89, allowing you to have +// 2 or more LORAs per Arduino +RH_MRF89* RH_MRF89::_deviceForInterrupt[RH_MRF89_NUM_INTERRUPTS] = {0, 0, 0}; +uint8_t RH_MRF89::_interruptCount = 0; // Index into _deviceForInterrupt for next device + +// These are indexed by the values of ModemConfigChoice +// Values based on sample modulation values from MRF89XA.h +// TXIPOLFV set to be more than Fd +PROGMEM static const RH_MRF89::ModemConfig MODEM_CONFIG_TABLE[] = +{ + // MODSEL, FDVAL, BRVAL, FILCREG=(PASFILV|BUTFILV), TXIPOLFV + // FSK, No Manchester, Whitening + { RH_MRF89_MODSEL_FSK, 0x0B, 0x63, 0x40 | 0x01, 0x20 }, // FSK_Rb2Fd33 + { RH_MRF89_MODSEL_FSK, 0x0B, 0x27, 0x40 | 0x01, 0x20 }, // FSK_Rb5Fd33 + { RH_MRF89_MODSEL_FSK, 0x0B, 0x13, 0x40 | 0x01, 0x20 }, // FSK_Rb10Fd33 + { RH_MRF89_MODSEL_FSK, 0x09, 0x09, 0x70 | 0x02, 0x20 }, // FSK_Rb20Fd40 + { RH_MRF89_MODSEL_FSK, 0x04, 0x04, 0xB0 | 0x05, 0x40 }, // FSK_Rb40Fd80 + { RH_MRF89_MODSEL_FSK, 0x03, 0x03, 0xD0 | 0x06, 0x40 }, // FSK_Rb50Fd100 + { RH_MRF89_MODSEL_FSK, 0x02, 0x02, 0xE0 | 0x09, 0x60 }, // FSK_Rb66Fd133 + { RH_MRF89_MODSEL_FSK, 0x01, 0x01, 0xF0 | 0x0F, 0x80 }, // FSK_Rb100Fd200 + { RH_MRF89_MODSEL_FSK, 0x01, 0x00, 0xF0 | 0x0F, 0x80 } // FSK_Rb200Fd200 + +}; + + +RH_MRF89::RH_MRF89(uint8_t csconPin, uint8_t csdatPin, uint8_t interruptPin, RHGenericSPI& spi) + : + RHNRFSPIDriver(csconPin, spi), + _csconPin(csconPin), + _csdatPin(csdatPin), + _interruptPin(interruptPin) +{ + _myInterruptIndex = 0xff; // Not allocated yet +} + +bool RH_MRF89::init() +{ + // MRF89 data cant handle SPI greater than 1MHz. + // Sigh on teensy at 1MHz, need special delay after writes, see RHNRFSPIDriver::spiWrite + _spi.setFrequency(RHGenericSPI::Frequency1MHz); + if (!RHNRFSPIDriver::init()) + return false; + + // Initialise the chip select pins + pinMode(_csconPin, OUTPUT); + digitalWrite(_csconPin, HIGH); + pinMode(_csdatPin, OUTPUT); + digitalWrite(_csdatPin, HIGH); + + // Determine the interrupt number that corresponds to the interruptPin + int interruptNumber = digitalPinToInterrupt(_interruptPin); + if (interruptNumber == NOT_AN_INTERRUPT) + return false; +#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER + interruptNumber = _interruptPin; +#endif + + // Tell the low level SPI interface we will use SPI within this interrupt + spiUsingInterrupt(interruptNumber); + + // Make sure we are not in some unexpected mode from a previous run + setOpMode(RH_MRF89_CMOD_STANDBY); + + // No way to check the device type but lets trivially check there is something there + // by trying to change a register: + spiWriteRegister(RH_MRF89_REG_02_FDEVREG, 0xaa); + if (spiReadRegister(RH_MRF89_REG_02_FDEVREG) != 0xaa) + return false; + spiWriteRegister(RH_MRF89_REG_02_FDEVREG, 0x3); // Back to the default for FDEV + if (spiReadRegister(RH_MRF89_REG_02_FDEVREG) != 0x3) + return false; + + // Add by Adrien van den Bossche for Teensy + // ARM M4 requires the below. else pin interrupt doesn't work properly. + // On all other platforms, its innocuous, belt and braces + pinMode(_interruptPin, INPUT); + + // Set up interrupt handler + // Since there are a limited number of interrupt glue functions isr*() available, + // we can only support a limited number of devices simultaneously + // On some devices, notably most Arduinos, the interrupt pin passed in is actually the + // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping + // yourself based on knowledge of what Arduino board you are running on. + if (_myInterruptIndex == 0xff) + { + // First run, no interrupt allocated yet + if (_interruptCount <= RH_MRF89_NUM_INTERRUPTS) + _myInterruptIndex = _interruptCount++; + else + return false; // Too many devices, not enough interrupt vectors + } + _deviceForInterrupt[_myInterruptIndex] = this; + if (_myInterruptIndex == 0) + attachInterrupt(interruptNumber, isr0, RISING); + else if (_myInterruptIndex == 1) + attachInterrupt(interruptNumber, isr1, RISING); + else if (_myInterruptIndex == 2) + attachInterrupt(interruptNumber, isr2, RISING); + else + return false; // Too many devices, not enough interrupt vectors + + // When used with the MRF89XAM9A module, per 75017B.pdf section 1.3, need: + // crystal freq = 12.8MHz + // clock output disabled + // frequency bands 902-915 or 915-928 + // VCOT 60mV + // OOK max 28kbps + // Based on 70622C.pdf, section 3.12: + + spiWriteRegister(RH_MRF89_REG_00_GCONREG, RH_MRF89_CMOD_STANDBY | RH_MRF89_FBS_902_915 | RH_MRF89_VCOT_60MV); + spiWriteRegister(RH_MRF89_REG_01_DMODREG, RH_MRF89_MODSEL_FSK | RH_MRF89_OPMODE_PACKET); // FSK, Packet mode, LNA 0dB + spiWriteRegister(RH_MRF89_REG_02_FDEVREG, 0); // Set by setModemConfig + spiWriteRegister(RH_MRF89_REG_03_BRSREG, 0); // Set by setModemConfig + spiWriteRegister(RH_MRF89_REG_04_FLTHREG, 0); // Set by setModemConfig (OOK only) + spiWriteRegister(RH_MRF89_REG_05_FIFOCREG, RH_MRF89_FSIZE_64); + spiWriteRegister(RH_MRF89_REG_06_R1CREG, 0); // Set by setFrequency + spiWriteRegister(RH_MRF89_REG_07_P1CREG, 0); // Set by setFrequency + spiWriteRegister(RH_MRF89_REG_08_S1CREG, 0); // Set by setFrequency + spiWriteRegister(RH_MRF89_REG_09_R2CREG, 0); // Frequency set 2 not used + spiWriteRegister(RH_MRF89_REG_0A_P2CREG, 0); // Frequency set 2 not used + spiWriteRegister(RH_MRF89_REG_0B_S2CREG, 0); // Frequency set 2 not used + spiWriteRegister(RH_MRF89_REG_0C_PACREG, RH_MRF89_PARC_23); + // IRQ0 rx mode: SYNC (not used) + // IRQ1 rx mode: CRCOK + // IRQ1 tx mode: TXDONE + spiWriteRegister(RH_MRF89_REG_0D_FTXRXIREG, RH_MRF89_IRQ0RXS_PACKET_SYNC | RH_MRF89_IRQ1RXS_PACKET_CRCOK | RH_MRF89_IRQ1TX); + spiWriteRegister(RH_MRF89_REG_0E_FTPRIREG, RH_MRF89_LENPLL); + spiWriteRegister(RH_MRF89_REG_0F_RSTHIREG, 0x00); // default not used if no RSSI interrupts + spiWriteRegister(RH_MRF89_REG_10_FILCREG, 0); // Set by setModemConfig + + spiWriteRegister(RH_MRF89_REG_11_PFCREG, 0x38);// 100kHz, recommended, but not used, see RH_MRF89_REG_12_SYNCREG OOK only? + spiWriteRegister(RH_MRF89_REG_12_SYNCREG, RH_MRF89_SYNCREN | RH_MRF89_SYNCWSZ_32); // No polyphase, no bsync, sync, 0 errors + spiWriteRegister(RH_MRF89_REG_13_RSVREG, 0x07);//default +// spiWriteRegister(RH_MRF89_REG_14_RSTSREG, 0x00); // NO, read only + spiWriteRegister(RH_MRF89_REG_15_OOKCREG, 0x00); // Set by setModemConfig OOK only + spiWriteRegister(RH_MRF89_REG_16_SYNCV31REG, 0x69); // Set by setSyncWords + spiWriteRegister(RH_MRF89_REG_17_SYNCV23REG, 0x81); // Set by setSyncWords + spiWriteRegister(RH_MRF89_REG_18_SYNCV15REG, 0x7E); // Set by setSyncWords + spiWriteRegister(RH_MRF89_REG_19_SYNCV07REG, 0x96); // Set by setSyncWords + // TXIPOLFV set by setModemConfig. power set by setTxPower + spiWriteRegister(RH_MRF89_REG_1A_TXCONREG, 0xf0 | RH_MRF89_TXOPVAL_13DBM); // TX cutoff freq=375kHz, + spiWriteRegister(RH_MRF89_REG_1B_CLKOREG, 0x00); // Disable clock output to save power + spiWriteRegister(RH_MRF89_REG_1C_PLOADREG, 0x40); // payload=64bytes (no RX-filtering on packet length) + spiWriteRegister(RH_MRF89_REG_1D_NADDSREG, 0x00); // Node Address (0=default) Not used + spiWriteRegister(RH_MRF89_REG_1E_PKTCREG, RH_MRF89_PKTLENF | RH_MRF89_PRESIZE_4 | RH_MRF89_WHITEON | RH_MRF89_CHKCRCEN | RH_MRF89_ADDFIL_OFF); + spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, 0x00); // default (FIFO access in standby=write, clear FIFO on CRC mismatch) + + // Looking OK now + // Set some suitable defaults: + setPreambleLength(3); // The default + uint8_t syncwords[] = { 0x69, 0x81, 0x7e, 0x96 }; // Same as RH_MRF89XA + setSyncWords(syncwords, sizeof(syncwords)); + setTxPower(RH_MRF89_TXOPVAL_1DBM); + // try first MRF89XAM9A then MRF89XAM8A + if (!setFrequency(915.4) && !setFrequency(865.0)) + return false; + // Some slow, reliable default speed and modulation + if (!setModemConfig(FSK_Rb20Fd40)) + return false; + + return true; +} + +bool RH_MRF89::printRegisters() +{ +#ifdef RH_HAVE_SERIAL + uint8_t i; + for (i = 0; i <= 0x1f; i++) + { + Serial.print(i, HEX); + Serial.print(": "); + Serial.println(spiReadRegister(i), HEX); + } +#endif + return true; +} + +// C++ level interrupt handler for this instance +// MRF89XA is unusual in that it has 2 interrupt lines, and not a single, combined one. +// Only one of the several interrupt lines (IRQ1) from the RFM95 needs to be +// connnected to the processor. +// We use this to get CRCOK and TXDONE interrupts +void RH_MRF89::handleInterrupt() +{ +// Serial.println("I"); + if (_mode == RHModeTx) + { +// Serial.println("T"); + // TXDONE + // Transmit is complete + _txGood++; + setModeIdle(); + } + else if (_mode == RHModeRx) + { +// Serial.println("R"); + // CRCOK + // We have received a packet. + // First byte in FIFO is packet length + + // REVISIT: Capture last rssi from RSTSREG + // based roughly on Figure 3-9 + _lastRssi = (spiReadRegister(RH_MRF89_REG_14_RSTSREG) >> 1) - 120; + + _bufLen = spiReadData(); + if (_bufLen < 4) + { + // Drain the FIFO + uint8_t i; + for (i = 0; spiReadRegister(RH_MRF89_REG_0D_FTXRXIREG) & RH_MRF89_FIFOEMPTY; i++) + spiReadData(); + clearRxBuf(); + return; + } + + // Now drain all the data from the FIFO into _buf + uint8_t i; + for (i = 0; spiReadRegister(RH_MRF89_REG_0D_FTXRXIREG) & RH_MRF89_FIFOEMPTY; i++) + _buf[i] = spiReadData(); + + // All good. See if its for us + validateRxBuf(); + if (_rxBufValid) + setModeIdle(); // Got one + } +} + +// These are low level functions that call the interrupt handler for the correct +// instance of RH_MRF89. +// 3 interrupts allows us to have 3 different devices +void RH_INTERRUPT_ATTR RH_MRF89::isr0() +{ + if (_deviceForInterrupt[0]) + _deviceForInterrupt[0]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_MRF89::isr1() +{ + if (_deviceForInterrupt[1]) + _deviceForInterrupt[1]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_MRF89::isr2() +{ + if (_deviceForInterrupt[2]) + _deviceForInterrupt[2]->handleInterrupt(); +} + +uint8_t RH_MRF89::spiReadRegister(uint8_t reg) +{ + // Tell the chip we want to talk to the configuration registers + setSlaveSelectPin(_csconPin); + digitalWrite(_csdatPin, HIGH); + return spiRead(((reg & 0x1f) << 1) | RH_MRF89_SPI_READ_MASK); +} + +uint8_t RH_MRF89::spiWriteRegister(uint8_t reg, uint8_t val) +{ + // Tell the chip we want to talk to the configuration registers + setSlaveSelectPin(_csconPin); + digitalWrite(_csdatPin, HIGH); + // Hmmm, on teensy 3.1, needed some special behaviour in RHNRFSPIDriver::spiWrite + // because otherwise, CSCON returns high before the final clock goes low, + // which prevents the MRF89XA spi write succeeding. Clock must be low when CSCON goes high. + return spiWrite(((reg & 0x1f) << 1), val); +} + +uint8_t RH_MRF89::spiWriteData(uint8_t data) +{ + spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, RH_MRF89_ACFCRC); // Write to FIFO + setSlaveSelectPin(_csdatPin); + digitalWrite(_csconPin, HIGH); + return spiCommand(data); +} + +uint8_t RH_MRF89::spiWriteData(const uint8_t* data, uint8_t len) +{ + spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, RH_MRF89_ACFCRC); // Write to FIFO + setSlaveSelectPin(_csdatPin); + digitalWrite(_csconPin, HIGH); + + uint8_t status = 0; + ATOMIC_BLOCK_START; + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + while (len--) + _spi.transfer(*data++); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + ATOMIC_BLOCK_END; + return status; + +} + +uint8_t RH_MRF89::spiReadData() +{ + spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, RH_MRF89_ACFCRC | RH_MRF89_FRWAXS); // Read from FIFO + setSlaveSelectPin(_csdatPin); + digitalWrite(_csconPin, HIGH); + return spiCommand(0); +} + +void RH_MRF89::setOpMode(uint8_t mode) +{ + // REVISIT: do we need to have time delays when switching between modes? + uint8_t val = spiReadRegister(RH_MRF89_REG_00_GCONREG); + val = (val & ~RH_MRF89_CMOD) | (mode & RH_MRF89_CMOD); + spiWriteRegister(RH_MRF89_REG_00_GCONREG, val); +} + +void RH_MRF89::setModeIdle() +{ + if (_mode != RHModeIdle) + { + setOpMode(RH_MRF89_CMOD_STANDBY); + _mode = RHModeIdle; + } +} + +bool RH_MRF89::sleep() +{ + if (_mode != RHModeSleep) + { + setOpMode(RH_MRF89_CMOD_SLEEP); + _mode = RHModeSleep; + } + return true; +} + +void RH_MRF89::setModeRx() +{ + if (_mode != RHModeRx) + { + setOpMode(RH_MRF89_CMOD_RECEIVE); + _mode = RHModeRx; + } +} + +void RH_MRF89::setModeTx() +{ + if (_mode != RHModeTx) + { + setOpMode(RH_MRF89_CMOD_TRANSMIT); + _mode = RHModeTx; + } +} + +void RH_MRF89::setTxPower(uint8_t power) +{ + uint8_t txconreg = spiReadRegister(RH_MRF89_REG_1A_TXCONREG); + txconreg |= (power & RH_MRF89_TXOPVAL); + spiWriteRegister(RH_MRF89_REG_1A_TXCONREG, txconreg); +} + +bool RH_MRF89::available() +{ + if (_mode == RHModeTx) + return false; + setModeRx(); + + return _rxBufValid; // Will be set by the interrupt handler when a good message is received +} + +bool RH_MRF89::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + + if (buf && len) + { + ATOMIC_BLOCK_START; + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen - RH_MRF89_HEADER_LEN) + *len = _bufLen - RH_MRF89_HEADER_LEN; + memcpy(buf, _buf + RH_MRF89_HEADER_LEN, *len); + ATOMIC_BLOCK_END; + } + clearRxBuf(); // This message accepted and cleared + + return true; +} + +bool RH_MRF89::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_MRF89_MAX_MESSAGE_LEN) + return false; + + waitPacketSent(); // Make sure we dont interrupt an outgoing message + setModeIdle(); + + if (!waitCAD()) + return false; // Check channel activity + + // First octet is the length of the chip payload + // 0 length messages are transmitted but never trigger a receive! + spiWriteData(len + RH_MRF89_HEADER_LEN); + spiWriteData(_txHeaderTo); + spiWriteData(_txHeaderFrom); + spiWriteData(_txHeaderId); + spiWriteData(_txHeaderFlags); + spiWriteData(data, len); + setModeTx(); // Start transmitting + + return true; +} + +uint8_t RH_MRF89::maxMessageLength() +{ + return RH_MRF89_MAX_MESSAGE_LEN; +} + +// Check whether the latest received message is complete and uncorrupted +void RH_MRF89::validateRxBuf() +{ + if (_bufLen < 4) + return; // Too short to be a real message + // Extract the 4 headers + _rxHeaderTo = _buf[0]; + _rxHeaderFrom = _buf[1]; + _rxHeaderId = _buf[2]; + _rxHeaderFlags = _buf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +void RH_MRF89::clearRxBuf() +{ + ATOMIC_BLOCK_START; + _rxBufValid = false; + _bufLen = 0; + ATOMIC_BLOCK_END; +} + +bool RH_MRF89::verifyPLLLock() +{ + // Verify PLL-lock per instructions in Note 1 section 3.12 + // Need to do this after changing frequency. + uint8_t ftpriVal = spiReadRegister(RH_MRF89_REG_0E_FTPRIREG); + spiWriteRegister(RH_MRF89_REG_0E_FTPRIREG, ftpriVal | RH_MRF89_LSTSPLL); // Clear PLL lock bit + setOpMode(RH_MRF89_CMOD_FS); + unsigned long ulStartTime = millis(); + while ((millis() - ulStartTime < 1000)) + { + ftpriVal = spiReadRegister(RH_MRF89_REG_0E_FTPRIREG); + if ((ftpriVal & RH_MRF89_LSTSPLL) != 0) + break; + } + setOpMode(RH_MRF89_CMOD_STANDBY); + return ((ftpriVal & RH_MRF89_LSTSPLL) != 0); +} + +bool RH_MRF89::setFrequency(float centre) +{ + // REVISIT: FSK only: its different for OOK :-( + + uint8_t FBS; + if (centre >= 902.0 && centre < 915.0) + { + // The MRF89XAM9A does support this frequency band + FBS = RH_MRF89_FBS_902_915; + } + else if (centre >= 915.0 && centre <= 928.0) + { + // The MRF89XAM9A does support this frequency band + FBS = RH_MRF89_FBS_915_928; + } + else if (centre >= 950.0 && centre <= 960.0) + { + // Not all modules support this frequency band: + // The MRF98XAM9A does not + // The MRF89XA does support this frequency band + FBS = RH_MRF89_FBS_950_960_or_863_870; + } + else if (centre >= 863.0 && centre <= 870.0) + { + // Not all modules support this frequency band: + // The MRF98XAM9A does not + // The MRF89XAM8A does support this frequency band + FBS = RH_MRF89_FBS_950_960_or_863_870; + } + else + { + // Cant do this freq + return false; + } + + // Based on frequency calcs done in MRF89XA.h +// uint8_t R = 100; // Recommended + uint8_t R = 119; // Also recommended :-( + uint32_t centre_kHz = centre * 1000; + uint32_t xtal_kHz = (RH_MRF89_XTAL_FREQ * 1000); + uint32_t compare = (centre_kHz * 8 * (R + 1)) / (9 * xtal_kHz); + uint8_t P = ((compare - 75) / 76) + 1; + uint8_t S = compare - (75 * (P + 1)); + + // Now set the new register values: + uint8_t val = spiReadRegister(RH_MRF89_REG_00_GCONREG); + val = (val & ~RH_MRF89_FBS) | (FBS & RH_MRF89_FBS); + spiWriteRegister(RH_MRF89_REG_00_GCONREG, val); + + spiWriteRegister(RH_MRF89_REG_06_R1CREG, R); + spiWriteRegister(RH_MRF89_REG_07_P1CREG, P); + spiWriteRegister(RH_MRF89_REG_08_S1CREG, S); + + return verifyPLLLock(); +} + +// Set one of the canned FSK Modem configs +// Returns true if its a valid choice +bool RH_MRF89::setModemConfig(ModemConfigChoice index) +{ + if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig))) + return false; + + RH_MRF89::ModemConfig cfg; + memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(cfg)); + + // Now update the registers + uint8_t val = spiReadRegister(RH_MRF89_REG_01_DMODREG); + val = (val & ~RH_MRF89_MODSEL) | cfg.MODSEL; + spiWriteRegister(RH_MRF89_REG_01_DMODREG, val); + + spiWriteRegister(RH_MRF89_REG_02_FDEVREG, cfg.FDVAL); + spiWriteRegister(RH_MRF89_REG_03_BRSREG, cfg.BRVAL); + spiWriteRegister(RH_MRF89_REG_10_FILCREG, cfg.FILCREG); + + // The sample configs in MRF89XA.h all use TXIPOLFV = 0xf0 => 375kHz, which is too wide for most modulations + val = spiReadRegister(RH_MRF89_REG_1A_TXCONREG); + val = (val & ~RH_MRF89_TXIPOLFV) | (cfg.TXIPOLFV & RH_MRF89_TXIPOLFV); + spiWriteRegister(RH_MRF89_REG_1A_TXCONREG, val); + + return true; +} + +void RH_MRF89::setPreambleLength(uint8_t bytes) +{ + if (bytes >= 1 && bytes <= 4) + { + bytes--; + uint8_t pktcreg = spiReadRegister(RH_MRF89_REG_1E_PKTCREG); + pktcreg = (pktcreg & ~RH_MRF89_PRESIZE) | ((bytes << 5) & RH_MRF89_PRESIZE); + spiWriteRegister(RH_MRF89_REG_1E_PKTCREG, pktcreg); + } +} + +void RH_MRF89::setSyncWords(const uint8_t* syncWords, uint8_t len) +{ + if (syncWords && (len > 0 and len <= 4)) + { + uint8_t syncreg = spiReadRegister(RH_MRF89_REG_12_SYNCREG); + syncreg = (syncreg & ~RH_MRF89_SYNCWSZ) | (((len - 1) << 3) & RH_MRF89_SYNCWSZ); + spiWriteRegister(RH_MRF89_REG_12_SYNCREG, syncreg); + uint8_t i; + for (i = 0; i < 4; i++) + { + if (len > i) + spiWriteRegister(RH_MRF89_REG_16_SYNCV31REG + i, syncWords[i]); + } + } +} + diff --git a/RH_MRF89.h b/RH_MRF89.h new file mode 100644 index 0000000..616f09a --- /dev/null +++ b/RH_MRF89.h @@ -0,0 +1,647 @@ +// RH_MRF89.h +// +// Definitions for Microchip MRF89XA family radios radios per: +// http://ww1.microchip.com/downloads/en/DeviceDoc/70622C.pdf +// http://ww1.microchip.com/downloads/en/DeviceDoc/75017B.pdf +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2015 Mike McCauley +// $Id: RH_MRF89.h,v 1.7 2017/07/25 05:26:50 mikem Exp $ +// + +#ifndef RH_RF95_h +#define RH_RF95_h + +#include + +// This is the maximum number of interrupts the driver can support +// Most Arduinos can handle 2, Megas can handle more +#define RH_MRF89_NUM_INTERRUPTS 3 + +// Max number of octets the MRF89XA Rx/Tx FIFO can hold +#define RH_MRF89_FIFO_SIZE 64 + +// This is the maximum number of bytes that can be carried by the MRF89XA. +// We use some for headers, keeping fewer for RadioHead messages +#define RH_MRF89_MAX_PAYLOAD_LEN RH_MRF89_FIFO_SIZE + +// The length of the headers we add. +// The headers are inside the MRF89XA payload +#define RH_MRF89_HEADER_LEN 4 + +// This is the maximum user message length that can be supported by this driver. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for 4 bytes headers, user data. Message length and CRC are automatically encoded and decoded by +// the MRF89XA +#ifndef RH_MRF89_MAX_MESSAGE_LEN + #define RH_MRF89_MAX_MESSAGE_LEN (RH_MRF89_MAX_PAYLOAD_LEN - RH_MRF89_HEADER_LEN) +#endif + +// Bits that must be set to do a SPI read +#define RH_MRF89_SPI_READ_MASK 0x40 + +// The MRF89XA crystal frequency in MHz +#define RH_MRF89_XTAL_FREQ 12.8 + +// Register names from Figure 2-18 +#define RH_MRF89_REG_00_GCONREG 0x00 +#define RH_MRF89_REG_01_DMODREG 0x01 +#define RH_MRF89_REG_02_FDEVREG 0x02 +#define RH_MRF89_REG_03_BRSREG 0x03 +#define RH_MRF89_REG_04_FLTHREG 0x04 +#define RH_MRF89_REG_05_FIFOCREG 0x05 +#define RH_MRF89_REG_06_R1CREG 0x06 +#define RH_MRF89_REG_07_P1CREG 0x07 +#define RH_MRF89_REG_08_S1CREG 0x08 +#define RH_MRF89_REG_09_R2CREG 0x09 +#define RH_MRF89_REG_0A_P2CREG 0x0a +#define RH_MRF89_REG_0B_S2CREG 0x0b +#define RH_MRF89_REG_0C_PACREG 0x0c +#define RH_MRF89_REG_0D_FTXRXIREG 0x0d +#define RH_MRF89_REG_0E_FTPRIREG 0x0e +#define RH_MRF89_REG_0F_RSTHIREG 0x0f +#define RH_MRF89_REG_10_FILCREG 0x10 +#define RH_MRF89_REG_11_PFCREG 0x11 +#define RH_MRF89_REG_12_SYNCREG 0x12 +// Hmm the addresses of the next 2 is ambiguous in the docs +// this seems to agree with whats in the chip: +#define RH_MRF89_REG_13_RSVREG 0x13 +#define RH_MRF89_REG_14_RSTSREG 0x14 +#define RH_MRF89_REG_15_OOKCREG 0x15 +#define RH_MRF89_REG_16_SYNCV31REG 0x16 +#define RH_MRF89_REG_17_SYNCV23REG 0x17 +#define RH_MRF89_REG_18_SYNCV15REG 0x18 +#define RH_MRF89_REG_19_SYNCV07REG 0x19 +#define RH_MRF89_REG_1A_TXCONREG 0x1a +#define RH_MRF89_REG_1B_CLKOREG 0x1b +#define RH_MRF89_REG_1C_PLOADREG 0x1c +#define RH_MRF89_REG_1D_NADDSREG 0x1d +#define RH_MRF89_REG_1E_PKTCREG 0x1e +#define RH_MRF89_REG_1F_FCRCREG 0x1f + +// Register bitfield definitions +//#define RH_MRF89_REG_00_GCONREG 0x00 +#define RH_MRF89_CMOD 0xe0 +#define RH_MRF89_CMOD_TRANSMIT 0x80 +#define RH_MRF89_CMOD_RECEIVE 0x60 +#define RH_MRF89_CMOD_FS 0x40 +#define RH_MRF89_CMOD_STANDBY 0x20 +#define RH_MRF89_CMOD_SLEEP 0x00 + +#define RH_MRF89_FBS 0x18 +#define RH_MRF89_FBS_950_960_or_863_870 0x10 +#define RH_MRF89_FBS_915_928 0x08 +#define RH_MRF89_FBS_902_915 0x00 + +#define RH_MRF89_VCOT 0x06 +#define RH_MRF89_VCOT_180MV 0x06 +#define RH_MRF89_VCOT_120MV 0x04 +#define RH_MRF89_VCOT_60MV 0x02 +#define RH_MRF89_VCOT_TANK 0x00 + +#define RH_MRF89_RPS 0x01 + +//#define RH_MRF89_REG_01_DMODREG 0x01 +#define RH_MRF89_MODSEL 0xc0 +#define RH_MRF89_MODSEL_FSK 0x80 +#define RH_MRF89_MODSEL_OOK 0x40 + +#define RH_MRF89_DMODE0 0x20 + +#define RH_MRF89_OOKTYP 0x18 +#define RH_MRF89_OOKTYP_AVERAGE 0x10 +#define RH_MRF89_OOKTYP_PEAK 0x08 +#define RH_MRF89_OOKTYP_FIXED 0x00 + +#define RH_MRF89_DMODE1 0x04 + +#define RH_MRF89_IFGAIN 0x03 +#define RH_MRF89_IFGAIN_M13P5 0x03 +#define RH_MRF89_IFGAIN_M9 0x02 +#define RH_MRF89_IFGAIN_M4P5 0x01 +#define RH_MRF89_IFGAIN_0 0x00 + +// DMODE1 and DMODE1: +#define RH_MRF89_OPMODE_CONTINUOUS 0x00 +#define RH_MRF89_OPMODE_BUFFER RH_MRF89_DMODE0 +#define RH_MRF89_OPMODE_PACKET RH_MRF89_DMODE1 + +//#define RH_MRF89_REG_03_BRSREG 0x03 +#define RH_MRF89_BRVAL 0x7f + +//#define RH_MRF89_REG_05_FIFOCREG 0x05 +#define RH_MRF89_FSIZE 0xc0 +#define RH_MRF89_FSIZE_64 0xc0 +#define RH_MRF89_FSIZE_48 0x80 +#define RH_MRF89_FSIZE_32 0x40 +#define RH_MRF89_FSIZE_16 0x00 + +#define RH_MRF89_FTINT 0x3f + +//#define RH_MRF89_REG_0C_PACREG 0x0c +#define RH_MRF89_PARC 0x18 +#define RH_MRF89_PARC_23 0x18 +#define RH_MRF89_PARC_15 0x10 +#define RH_MRF89_PARC_8P5 0x08 +#define RH_MRF89_PARC_3 0x00 + +//#define RH_MRF89_REG_0D_FTXRXIREG 0x0d +#define RH_MRF89_IRQ0RXS 0xc0 +#define RH_MRF89_IRQ0RXS_CONT_RSSI 0x40 +#define RH_MRF89_IRQ0RXS_CONT_SYNC 0x00 +#define RH_MRF89_IRQ0RXS_BUFFER_SYNC 0xc0 +#define RH_MRF89_IRQ0RXS_BUFFER_FIFOEMPTY 0x80 +#define RH_MRF89_IRQ0RXS_BUFFER_WRITEBYTE 0x40 +#define RH_MRF89_IRQ0RXS_BUFFER_NONE 0x00 +#define RH_MRF89_IRQ0RXS_PACKET_SYNC 0xc0 +#define RH_MRF89_IRQ0RXS_PACKET_FIFOEMPTY 0x80 +#define RH_MRF89_IRQ0RXS_PACKET_WRITEBYTE 0x40 +#define RH_MRF89_IRQ0RXS_PACKET_PLREADY 0x00 + +#define RH_MRF89_IRQ1RXS 0x30 +#define RH_MRF89_IRQ1RXS_CONT_DCLK 0x00 +#define RH_MRF89_IRQ1RXS_BUFFER_FIFO_THRESH 0x30 +#define RH_MRF89_IRQ1RXS_BUFFER_RSSI 0x20 +#define RH_MRF89_IRQ1RXS_BUFFER_FIFOFULL 0x10 +#define RH_MRF89_IRQ1RXS_BUFFER_NONE 0x00 +#define RH_MRF89_IRQ1RXS_PACKET_FIFO_THRESH 0x30 +#define RH_MRF89_IRQ1RXS_PACKET_RSSI 0x20 +#define RH_MRF89_IRQ1RXS_PACKET_FIFOFULL 0x10 +#define RH_MRF89_IRQ1RXS_PACKET_CRCOK 0x00 + +#define RH_MRF89_IRQ1TX 0x08 +#define RH_MRF89_FIFOFULL 0x04 +#define RH_MRF89_FIFOEMPTY 0x02 +#define RH_MRF89_FOVRUN 0x01 + +//#define RH_MRF89_REG_0E_FTPRIREG 0x0e +#define RH_MRF89_FIFOFM 0x80 +#define RH_MRF89_FIFOFSC 0x40 +#define RH_MRF89_TXDONE 0x20 +#define RH_MRF89_IRQ0TXST 0x10 +#define RH_MRF89_RIRQS 0x04 +#define RH_MRF89_LSTSPLL 0x02 +#define RH_MRF89_LENPLL 0x01 + +//#define RH_MRF89_REG_10_FILCREG 0x10 +#define RH_MRF89_PASFILV 0xf0 +#define RH_MRF89_PASFILV_987KHZ 0xf0 +#define RH_MRF89_PASFILV_676KHZ 0xe0 +#define RH_MRF89_PASFILV_514KHZ 0xd0 +#define RH_MRF89_PASFILV_458KHZ 0xc0 +#define RH_MRF89_PASFILV_414KHZ 0xb0 +#define RH_MRF89_PASFILV_378KHZ 0xa0 +#define RH_MRF89_PASFILV_321KHZ 0x90 +#define RH_MRF89_PASFILV_262KHZ 0x80 +#define RH_MRF89_PASFILV_234KHZ 0x70 +#define RH_MRF89_PASFILV_211KHZ 0x60 +#define RH_MRF89_PASFILV_184KHZ 0x50 +#define RH_MRF89_PASFILV_157KHZ 0x40 +#define RH_MRF89_PASFILV_137KHZ 0x30 +#define RH_MRF89_PASFILV_109KHZ 0x20 +#define RH_MRF89_PASFILV_82KHZ 0x10 +#define RH_MRF89_PASFILV_65KHZ 0x00 + +#define RH_MRF89_BUTFILV 0x0f +#define RH_MRF89_BUTFILV_25KHZ 0x00 +#define RH_MRF89_BUTFILV_50KHZ 0x01 +#define RH_MRF89_BUTFILV_75KHZ 0x02 +#define RH_MRF89_BUTFILV_100KHZ 0x03 +#define RH_MRF89_BUTFILV_125KHZ 0x04 +#define RH_MRF89_BUTFILV_150KHZ 0x05 +#define RH_MRF89_BUTFILV_175KHZ 0x06 +#define RH_MRF89_BUTFILV_200KHZ 0x07 +#define RH_MRF89_BUTFILV_225KHZ 0x08 +#define RH_MRF89_BUTFILV_250KHZ 0x09 +#define RH_MRF89_BUTFILV_275KHZ 0x0a +#define RH_MRF89_BUTFILV_300KHZ 0x0b +#define RH_MRF89_BUTFILV_325KHZ 0x0c +#define RH_MRF89_BUTFILV_350KHZ 0x0d +#define RH_MRF89_BUTFILV_375KHZ 0x0e +#define RH_MRF89_BUTFILV_400KHZ 0x0f + +//#define RH_MRF89_REG_11_PFCREG 0x11 +#define RH_MRF89_POLCFV 0xf0 + +//#define RH_MRF89_REG_12_SYNCREG 0x12 +#define RH_MRF89_POLFILEN 0x80 +#define RH_MRF89_BSYNCEN 0x40 +#define RH_MRF89_SYNCREN 0x20 +#define RH_MRF89_SYNCWSZ 0x18 +#define RH_MRF89_SYNCWSZ_32 0x18 +#define RH_MRF89_SYNCWSZ_24 0x10 +#define RH_MRF89_SYNCWSZ_16 0x08 +#define RH_MRF89_SYNCWSZ_8 0x00 +#define RH_MRF89_SYNCTEN 0x06 +#define RH_MRF89_SYNCTEN_3 0x06 +#define RH_MRF89_SYNCTEN_2 0x04 +#define RH_MRF89_SYNCTEN_1 0x02 +#define RH_MRF89_SYNCTEN_0 0x00 + +//#define RH_MRF89_REG_15_OOKCREG 0x15 +#define RH_MRF89_OOTHSV 0xe0 +#define RH_MRF89_OOTHSV_6P0DB 0xe0 +#define RH_MRF89_OOTHSV_5P0DB 0xc0 +#define RH_MRF89_OOTHSV_4P0DB 0xa0 +#define RH_MRF89_OOTHSV_3P0DB 0x80 +#define RH_MRF89_OOTHSV_2P0DB 0x60 +#define RH_MRF89_OOTHSV_1P5DB 0x40 +#define RH_MRF89_OOTHSV_1P0DB 0x20 +#define RH_MRF89_OOTHSV_0P5DB 0x00 + +#define RH_MRF89_OOKTHPV 0x1c +#define RH_MRF89_OOKTHPV_16 0x1c +#define RH_MRF89_OOKTHPV_8 0x18 +#define RH_MRF89_OOKTHPV_4 0x14 +#define RH_MRF89_OOKTHPV_2 0x10 +#define RH_MRF89_OOKTHPV_1_IN_8 0x0c +#define RH_MRF89_OOKTHPV_1_IN_4 0x08 +#define RH_MRF89_OOKTHPV_1_IN_2 0x04 +#define RH_MRF89_OOKTHPV_1_IN_1 0x00 + +#define RH_MRF89_OOKATHC 0x03 +#define RH_MRF89_OOKATHC_32PI 0x03 +#define RH_MRF89_OOKATHC_8PI 0x00 + +//#define RH_MRF89_REG_1A_TXCONREG 0x1a +#define RH_MRF89_TXIPOLFV 0xf0 + +#define RH_MRF89_TXOPVAL 0x0e +#define RH_MRF89_TXOPVAL_M8DBM 0x0e +#define RH_MRF89_TXOPVAL_M5DBM 0x0c +#define RH_MRF89_TXOPVAL_M2DBM 0x0a +#define RH_MRF89_TXOPVAL_1DBM 0x08 +#define RH_MRF89_TXOPVAL_4DBM 0x06 +#define RH_MRF89_TXOPVAL_7DBM 0x04 +#define RH_MRF89_TXOPVAL_10DBM 0x02 +#define RH_MRF89_TXOPVAL_13DBM 0x00 + +//#define RH_MRF89_REG_1B_CLKOREG 0x1b +#define RH_MRF89_CLKOCNTRL 0x80 +#define RH_MRF89_CLKOFREQ 0x7c + +//#define RH_MRF89_REG_1C_PLOADREG 0x1c +#define RH_MRF89_MCHSTREN 0x80 +#define RH_MRF89_PLDPLEN 0x7f + +//#define RH_MRF89_REG_1E_PKTCREG 0x1e +#define RH_MRF89_PKTLENF 0x80 + +#define RH_MRF89_PRESIZE 0x60 +#define RH_MRF89_PRESIZE_4 0x60 +#define RH_MRF89_PRESIZE_3 0x40 +#define RH_MRF89_PRESIZE_2 0x20 +#define RH_MRF89_PRESIZE_1 0x00 + +#define RH_MRF89_WHITEON 0x10 +#define RH_MRF89_CHKCRCEN 0x08 + +#define RH_MRF89_ADDFIL 0x06 +#define RH_MRF89_ADDFIL_NODEADDR_00_FF 0x06 +#define RH_MRF89_ADDFIL_NODEADDR_00 0x04 +#define RH_MRF89_ADDFIL_NODEADDR 0x02 +#define RH_MRF89_ADDFIL_OFF 0x00 + +#define RH_MRF89_STSCRCEN 0x01 + +//#define RH_MRF89_REG_1F_FCRCREG 0x1f +#define RH_MRF89_ACFCRC 0x80 +#define RH_MRF89_FRWAXS 0x40 + + +///////////////////////////////////////////////////////////////////// +/// \class RH_MRF89 RH_MRF89.h +/// \brief Send and receive unaddressed, unreliable datagrams by Microchip MRF89XA and compatible transceivers. +/// and modules. +/// +/// The Microchip MRF89XA http://ww1.microchip.com/downloads/en/DeviceDoc/70622C.pdf is a low cost 900MHz +/// bancd transceiver chip. +/// It is commonly used on preassembled modules with supporting circcuits and antennas, such as +/// the MRF89XAM9A http://www.microchip.com/wwwproducts/Devices.aspx?product=MRF89XAM9A +/// This class supports all such modules +/// +/// This base class provides basic functions for sending and receiving unaddressed, unreliable datagrams +/// of arbitrary length to 59 octets per packet. Use one of the Manager classes to get addressing and +/// acknowledgement reliability, routing, meshes etc. +/// +/// Several MRF89XA modules can be connected to an Arduino, permitting the construction of translators +/// and frequency changers, etc. Each instance requires 2 chip select pins, and interrupt pin the standard 3 SPI pins. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// data rate, and with identical network addresses. +/// +/// Example Arduino programs are included to show the main modes of use. +/// +/// All messages sent and received by this class conform to this packet format: +/// +/// - 3 octets PREAMBLE +/// - 2 to 4 octets NETWORK ADDRESS (also call Sync Word) +/// - 1 octet message length bits packet control field +/// - 4 to 63 octets PAYLOAD, consisting of: +/// - 1 octet TO header +/// - 1 octet FROM header +/// - 1 octet ID header +/// - 1 octet FLAGS header +/// - 0 to 59 octets of user message +/// - 2 octets CRC +/// +/// The payload is whitened. No Manchester encoding is used. +/// +/// \par Connecting MRF89XA to Arduino +/// +/// The electrical connection between the MRF89XA and the Arduino require 3.3V, the 3 x SPI pins (SCK, SDI, SDO), +/// a 2 Chip Select pins (/CSCON and /CSDAT) and an interrupt. +/// +/// Caution: the MRF89XA is a 3.3V part and is not tolerant of 5V inputs. Connecting MRF89XA directly to a 5V +/// MCU such as most Arduinos will damage the MRF89XA. +/// +/// Connect the MRF89XA to most 3.3V Arduinos or Teensy 3.1 like this (use 3.3V not 5V). +/// \code +/// Teensy MRF89XAM9A +/// 3.3V-----------VIN (3.3V in) +/// pin D9-----------/CSDAT (data chip select in) +/// SS pin D10----------/CSCON (configuration chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------SDI (SPI Data in) +/// MISO pin D12----------SDO (SPI data out) +/// D2-----------IRQ1 (Interrupt 1 output) +/// IRQ0 (Interrupt 0 output, not connected) +/// GND----------GND (ground in) +/// \endcode +/// You can use other pins for /CSDAT, /CSCON, IRQ1 by passing appropriate arguments to the constructor. +/// +/// \par Low Power Mode +/// +/// Fernando Faria reports that: +/// +/// In order for low power mode to work to achieve 1μA power consumption in this chip, you will need to do a few extra things: +/// +/// 1. the datasheet states that IRQ0 and IRQ1 must have a valid logic state at +/// all times, so you will need to apply pull-down resistors to those pins (if not already present). +/// See the data sheet Table 2-4 Note 3 +/// +/// 2. You must also ensure the SPI pins are in a certain state after calling mrf89.sleep(); +/// This may be suitable for your electrical connections: +/// \code +/// digitalWrite(9, HIGH); // CSDAT +/// digitalWrite(10, LOW); // SS +/// digitalWrite(11, LOW); //MOSI +/// digitalWrite(13, LOW); // MISO +/// \endcode +/// +/// \par Example programs +/// +/// Several example programs are provided. +/// +class RH_MRF89 : public RHNRFSPIDriver +{ +public: + + /// \brief Defines register configuration values for a desired modulation + /// + /// Defines values for various configuration fields and registers to + /// achieve a desired modulation speed and frequency deviation. + typedef struct + { + uint8_t MODSEL; ///< Value for MODSEL in RH_MRF89_REG_01_DMODREG + uint8_t FDVAL; ///< Value for FDVAL in RH_MRF89_REG_02_FDEVREG + uint8_t BRVAL; ///< Value for BRVAL RH_MRF89_REG_03_BRSREG + uint8_t FILCREG; ///< Value for PASFILV | BUTFILV in RH_MRF89_REG_10_FILCREG + uint8_t TXIPOLFV; ///< Value for TXIPOLFV in RH_MRF89_REG_1A_TXCONREG + } ModemConfig; + + /// Choices for setModemConfig() for a selected subset of common + /// data rates and frequency deviations. + /// Rb is the data rate in kbps. Fd is the FSK Frequency deviation in kHz. + /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic + /// definitions and not their integer equivalents: its possible that new values will be + /// introduced in later versions (though we will try to avoid it). + /// OOK is not yet supported. + /// Based on sample configs in MRF89XA.h from Microchip + typedef enum + { + FSK_Rb2Fd33 = 0, ///< FSK, No Manchester, Whitened, Rb = 2kbs, Fd = 33kHz + FSK_Rb5Fd33, ///< FSK, No Manchester, Whitened, Rb = 5kbs, Fd = 33kHz + FSK_Rb10Fd33, ///< FSK, No Manchester, Whitened, Rb = 10kbs, Fd = 33kHz + FSK_Rb20Fd40, ///< FSK, No Manchester, Whitened, Rb = 20kbs, Fd = 40kHz + FSK_Rb40Fd80, ///< FSK, No Manchester, Whitened, Rb = 40kbs, Fd = 80kHz + FSK_Rb50Fd100, ///< FSK, No Manchester, Whitened, Rb = 50kbs, Fd = 100kHz + FSK_Rb66Fd133, ///< FSK, No Manchester, Whitened, Rb = 66kbs, Fd = 133kHz + FSK_Rb100Fd200, ///< FSK, No Manchester, Whitened, Rb = 100kbs, Fd = 200kHz + FSK_Rb200Fd200 ///< FSK, No Manchester, Whitened, Rb = 200kbs, Fd = 200kHz + } ModemConfigChoice; + + /// Constructor. + /// Constructor. You can have multiple instances, but each instance must have its own + /// interrupt and 2 slave select pins. After constructing, you must call init() to initialise the interface + /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient + /// distinct interrupt lines, one for each instance. + /// \param[in] csconPin the Arduino pin number connected to the CSCON pin of the MRF89XA. + /// Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) + /// \param[in] csdatPin the Arduino pin number connected to the CSDAT pin of the MRF89XA. + /// Defaults to 9. + /// \param[in] interruptPin The interrupt Pin number that is connected to the IRQ1 pin of the MRF89XA. + /// Defaults to pin 2. (IRQ0 pin of the MRF89XA does not need to be connected). + /// \param[in] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + RH_MRF89(uint8_t csconPin = SS, uint8_t csdatPin = 9, uint8_t interruptPin = 2, RHGenericSPI& spi = hardware_spi); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Prints the value of all chip registers + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging purposes only. + /// \return true on success + bool printRegisters(); + + /// Sets the radio into low-power sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode to idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. + /// \return true if sleep mode was successfully entered. + virtual bool sleep(); + + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + void setModeIdle(); + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver in the radio. + // the next valid packet received will cause available() to be true. + void setModeRx(); + + /// If current mode is Rx or Idle, changes it to Rx. F + /// Starts the transmitter in the radio. + void setModeTx(); + + /// Sets the transmitter power output level in register RH_MRF89_REG_1A_TXCONREG. + /// Be a good neighbour and set the lowest power level you need. + /// After init(), the power will be set to RH_MRF89_TXOPVAL_1DBM (1dBm) + /// The highest power available is RH_MRF89_TXOPVAL_13DBM (13dBm) + /// Caution: In some countries you may only select certain higher power levels if you + /// are also using frequency hopping. Make sure you are aware of the legal + /// limitations and regulations in your region. + /// Caution: in some countries the maximum permitted power level may depend on the Bit rate + /// \param[in] power Transmitter power level, one of RH_MRF89_TXOPVAL* + void setTxPower(uint8_t power); + + /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// here, use setModemRegisters() with your own ModemConfig. + /// \param[in] index The configuration choice. + /// \return true if index is a valid choice. + bool setModemConfig(ModemConfigChoice index); + + /// Tests whether a new message is available + /// from the Driver. + /// On most drivers, this will also put the Driver into RHModeRx mode until + /// a message is actually received by the transport, when it will be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available(); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send + /// \return true if the message length was valid and it was correctly queued for transmit + virtual bool send(const uint8_t* data, uint8_t len); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + /// Sets the centre frequency in MHz. + /// Permitted ranges are: 902.0 to 928.0 and 950.0 to 960.0 (inclusive) + /// Caution not all freqs are supported on all modules: check your module specifications + /// Caution: not all available and supported frequencies are legal in every country: check + /// Regulatory Approval eg for MRF89XAM9A (in 75015B.pdf) + /// Australia 915.0 to 928.0 + bool setFrequency(float centre); + + /// Sets the length of the preamble + /// in bytes. + /// Caution: this should be set to the same + /// value on all nodes in your network. Default is 4. + /// Sets the message preamble length in RH_MRF89_REG_1E_PKTCREG + /// \param[in] bytes Preamble length in bytes of 8 bits each. + void setPreambleLength(uint8_t bytes); + + /// Sets the sync words for transmit and receive in registers RH_MRF89_REG_16_SYNCV31REG + /// et seq. + /// Caution: SyncWords should be set to the same + /// value on all nodes in your network. Nodes with different SyncWords set will never receive + /// each others messages, so different SyncWords can be used to isolate different + /// networks from each other. Default is { 0x69, 0x81, 0x7e, 0x96 }. + /// Caution, sync words of 2 bytes and less do not work well with this chip. + /// \param[in] syncWords Array of sync words, 1 to 4 octets long + /// \param[in] len Number of sync words to set, 1 to 4. + void setSyncWords(const uint8_t* syncWords = NULL, uint8_t len = 0); + +protected: + + /// Called automatically when a CRCOK or TXDONE interrupt occurs. + /// Handles the interrupt. + void handleInterrupt(); + + /// Reads a single register from the MRF89XA + /// \param[in] reg Register number, one of RH_MRF89_REG + /// \return The value of the register + uint8_t spiReadRegister(uint8_t reg); + + /// Writes to a single single register on the MRF89XA + /// \param[in] reg Register number, one of RH_MRF89_REG_* + /// \param[in] val The value to write + /// \return the current value of RH_MRF89_REG_00_GCONREG (read while the command is sent) + uint8_t spiWriteRegister(uint8_t reg, uint8_t val); + + /// Writes a single byte to the MRF89XA data FIFO. + /// \param[in] data The data value to write + /// \return 0 + uint8_t spiWriteData(uint8_t data); + + /// Write a number of bytes from a buffer to the MRF89XA data FIFO. + /// \param[in] data Pointer to a buffer containing the len bytes to be written + /// \param[in] len The number of bytes to write to teh FIFO + /// \return 0; + uint8_t spiWriteData(const uint8_t* data, uint8_t len); + + /// Reads a single byte from the MRF89XA data FIFO. + /// \return The next data byte in the FIFO + uint8_t spiReadData(); + + /// Sets the operating mode in the CMOD bits in RH_MRF89_REG_00_GCONREG + /// which controls what mode the MRF89XA is running in + /// \param[in] mode One of RH_MRF89_CMOD_* + void setOpMode(uint8_t mode); + + /// Verifies that the MRF89XA PLL has locked on the slected frequency. + /// This needs to be called if the frequency is changed + bool verifyPLLLock(); + + /// Examine the revceive buffer to determine whether the message is for this node + void validateRxBuf(); + + /// Clear our local receive buffer + void clearRxBuf(); + + +private: + /// Low level interrupt service routine for device connected to interrupt 0 + static void isr0(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr1(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr2(); + + /// Array of instances connected to interrupts 0 and 1 + static RH_MRF89* _deviceForInterrupt[]; + + /// Index of next interrupt number to use in _deviceForInterrupt + static uint8_t _interruptCount; + + // Sigh: this chip has 2 differnt chip selects. + // We have to set one or the other as the SPI slave select pin depending + // on which block of registers we are accessing + uint8_t _csconPin; + uint8_t _csdatPin; + + /// The configured interrupt pin connected to this instance + uint8_t _interruptPin; + + /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) + /// else 0xff + uint8_t _myInterruptIndex; + + /// Number of octets in the buffer + volatile uint8_t _bufLen; + + /// The receiver/transmitter buffer + uint8_t _buf[RH_MRF89_MAX_PAYLOAD_LEN]; + + /// True when there is a valid message in the buffer + volatile bool _rxBufValid; + +}; + +/// @example mrf89_client.ino +/// @example mrf89_server.ino + +#endif diff --git a/RH_NRF24.cpp b/RH_NRF24.cpp new file mode 100644 index 0000000..dabf619 --- /dev/null +++ b/RH_NRF24.cpp @@ -0,0 +1,349 @@ +// NRF24.cpp +// +// Copyright (C) 2012 Mike McCauley +// $Id: RH_NRF24.cpp,v 1.26 2018/01/06 23:50:45 mikem Exp $ + +#include + +RH_NRF24::RH_NRF24(uint8_t chipEnablePin, uint8_t slaveSelectPin, RHGenericSPI& spi) + : + RHNRFSPIDriver(slaveSelectPin, spi), + _rxBufValid(0) +{ + _configuration = RH_NRF24_EN_CRC | RH_NRF24_CRCO; // Default: 2 byte CRC enabled + _chipEnablePin = chipEnablePin; +} + +bool RH_NRF24::init() +{ + // Teensy with nRF24 is unreliable at 8MHz: + // so is Arduino with RF73 + _spi.setFrequency(RHGenericSPI::Frequency1MHz); + if (!RHNRFSPIDriver::init()) + return false; + + // Initialise the slave select pin + pinMode(_chipEnablePin, OUTPUT); + digitalWrite(_chipEnablePin, LOW); + + // Clear interrupts + spiWriteRegister(RH_NRF24_REG_07_STATUS, RH_NRF24_RX_DR | RH_NRF24_TX_DS | RH_NRF24_MAX_RT); + // Enable dynamic payload length on all pipes + spiWriteRegister(RH_NRF24_REG_1C_DYNPD, RH_NRF24_DPL_ALL); + // Enable dynamic payload length, disable payload-with-ack, enable noack + spiWriteRegister(RH_NRF24_REG_1D_FEATURE, RH_NRF24_EN_DPL | RH_NRF24_EN_DYN_ACK); + // Test if there is actually a device connected and responding + // CAUTION: RFM73 and version 2.0 silicon may require ACTIVATE + if (spiReadRegister(RH_NRF24_REG_1D_FEATURE) != (RH_NRF24_EN_DPL | RH_NRF24_EN_DYN_ACK)) + { + spiWrite(RH_NRF24_COMMAND_ACTIVATE, 0x73); + // Enable dynamic payload length, disable payload-with-ack, enable noack + spiWriteRegister(RH_NRF24_REG_1D_FEATURE, RH_NRF24_EN_DPL | RH_NRF24_EN_DYN_ACK); + if (spiReadRegister(RH_NRF24_REG_1D_FEATURE) != (RH_NRF24_EN_DPL | RH_NRF24_EN_DYN_ACK)) + return false; + } + + clearRxBuf(); + + // Make sure we are powered down + setModeIdle(); + + // Flush FIFOs + flushTx(); + flushRx(); + + setChannel(2); // The default, in case it was set by another app without powering down + setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm); + + return true; +} + +// Use the register commands to read and write the registers +uint8_t RH_NRF24::spiReadRegister(uint8_t reg) +{ + return spiRead((reg & RH_NRF24_REGISTER_MASK) | RH_NRF24_COMMAND_R_REGISTER); +} + +uint8_t RH_NRF24::spiWriteRegister(uint8_t reg, uint8_t val) +{ + return spiWrite((reg & RH_NRF24_REGISTER_MASK) | RH_NRF24_COMMAND_W_REGISTER, val); +} + +uint8_t RH_NRF24::spiBurstReadRegister(uint8_t reg, uint8_t* dest, uint8_t len) +{ + return spiBurstRead((reg & RH_NRF24_REGISTER_MASK) | RH_NRF24_COMMAND_R_REGISTER, dest, len); +} + +uint8_t RH_NRF24::spiBurstWriteRegister(uint8_t reg, uint8_t* src, uint8_t len) +{ + return spiBurstWrite((reg & RH_NRF24_REGISTER_MASK) | RH_NRF24_COMMAND_W_REGISTER, src, len); +} + +uint8_t RH_NRF24::statusRead() +{ + // status is a side-effect of NOP, faster than reading reg 07 + return spiCommand(RH_NRF24_COMMAND_NOP); +} + +uint8_t RH_NRF24::flushTx() +{ + return spiCommand(RH_NRF24_COMMAND_FLUSH_TX); +} + +uint8_t RH_NRF24::flushRx() +{ + return spiCommand(RH_NRF24_COMMAND_FLUSH_RX); +} + +bool RH_NRF24::setChannel(uint8_t channel) +{ + spiWriteRegister(RH_NRF24_REG_05_RF_CH, channel & RH_NRF24_RF_CH); + return true; +} + +bool RH_NRF24::setOpMode(uint8_t mode) +{ + _configuration = mode; + return true; +} + +bool RH_NRF24::setNetworkAddress(uint8_t* address, uint8_t len) +{ + if (len < 3 || len > 5) + return false; + + // Set both TX_ADDR and RX_ADDR_P0 for auto-ack with Enhanced shockwave + spiWriteRegister(RH_NRF24_REG_03_SETUP_AW, len-2); // Mapping [3..5] = [1..3] + spiBurstWriteRegister(RH_NRF24_REG_0A_RX_ADDR_P0, address, len); + spiBurstWriteRegister(RH_NRF24_REG_10_TX_ADDR, address, len); + return true; +} + +bool RH_NRF24::setRF(DataRate data_rate, TransmitPower power) +{ + uint8_t value = (power << 1) & RH_NRF24_PWR; + // Ugly mapping of data rates to noncontiguous 2 bits: + if (data_rate == DataRate250kbps) + value |= RH_NRF24_RF_DR_LOW; + else if (data_rate == DataRate2Mbps) + value |= RH_NRF24_RF_DR_HIGH; + // else DataRate1Mbps, 00 + + // RFM73 needs this: + value |= RH_NRF24_LNA_HCURR; + + spiWriteRegister(RH_NRF24_REG_06_RF_SETUP, value); + // If we were using auto-ack, we would have to set the appropriate timeout in reg 4 here + // see NRF24::setRF() + return true; +} + +void RH_NRF24::setModeIdle() +{ + if (_mode != RHModeIdle) + { + spiWriteRegister(RH_NRF24_REG_00_CONFIG, _configuration); + digitalWrite(_chipEnablePin, LOW); + _mode = RHModeIdle; + } +} + +bool RH_NRF24::sleep() +{ + if (_mode != RHModeSleep) + { + spiWriteRegister(RH_NRF24_REG_00_CONFIG, 0); // Power Down mode + digitalWrite(_chipEnablePin, LOW); + _mode = RHModeSleep; + return true; + } + return false; // Already there? +} + +void RH_NRF24::setModeRx() +{ + if (_mode != RHModeRx) + { + spiWriteRegister(RH_NRF24_REG_00_CONFIG, _configuration | RH_NRF24_PWR_UP | RH_NRF24_PRIM_RX); + digitalWrite(_chipEnablePin, HIGH); + _mode = RHModeRx; + } +} + +void RH_NRF24::setModeTx() +{ + if (_mode != RHModeTx) + { + // Its the CE rising edge that puts us into TX mode + // CE staying high makes us go to standby-II when the packet is sent + digitalWrite(_chipEnablePin, LOW); + // Ensure DS is not set + spiWriteRegister(RH_NRF24_REG_07_STATUS, RH_NRF24_TX_DS | RH_NRF24_MAX_RT); + spiWriteRegister(RH_NRF24_REG_00_CONFIG, _configuration | RH_NRF24_PWR_UP); + digitalWrite(_chipEnablePin, HIGH); + _mode = RHModeTx; + } +} + +bool RH_NRF24::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_NRF24_MAX_MESSAGE_LEN) + return false; + + if (!waitCAD()) + return false; // Check channel activity + + // Set up the headers + _buf[0] = _txHeaderTo; + _buf[1] = _txHeaderFrom; + _buf[2] = _txHeaderId; + _buf[3] = _txHeaderFlags; + memcpy(_buf+RH_NRF24_HEADER_LEN, data, len); + spiBurstWrite(RH_NRF24_COMMAND_W_TX_PAYLOAD_NOACK, _buf, len + RH_NRF24_HEADER_LEN); + setModeTx(); + // Radio will return to Standby II mode after transmission is complete + _txGood++; + return true; +} + +bool RH_NRF24::waitPacketSent() +{ + // If we are not currently in transmit mode, there is no packet to wait for + if (_mode != RHModeTx) + return false; + + // Wait for either the Data Sent or Max ReTries flag, signalling the + // end of transmission + // We dont actually use auto-ack, so prob dont expect to see RH_NRF24_MAX_RT + uint8_t status; + uint32_t start = millis(); + while (!((status = statusRead()) & (RH_NRF24_TX_DS | RH_NRF24_MAX_RT))) + { + if (((uint32_t)millis() - start) > 100) // Longer than any possible message + break; // Should never happen: TX never completed. Why? + YIELD; + } + + // Must clear RH_NRF24_MAX_RT if it is set, else no further comm + if (status & RH_NRF24_MAX_RT) + flushTx(); + setModeIdle(); + spiWriteRegister(RH_NRF24_REG_07_STATUS, RH_NRF24_TX_DS | RH_NRF24_MAX_RT); + // Return true if data sent, false if MAX_RT + return status & RH_NRF24_TX_DS; +} + +bool RH_NRF24::isSending() +{ + return !(spiReadRegister(RH_NRF24_REG_00_CONFIG) & RH_NRF24_PRIM_RX) && + !(statusRead() & (RH_NRF24_TX_DS | RH_NRF24_MAX_RT)); +} + +bool RH_NRF24::printRegisters() +{ +#ifdef RH_HAVE_SERIAL + // Iterate over register range, but don't process registers not in use. + for (uint8_t r = RH_NRF24_REG_00_CONFIG; r <= RH_NRF24_REG_1D_FEATURE; r++) + { + if ((r <= RH_NRF24_REG_17_FIFO_STATUS) || (r >= RH_NRF24_REG_1C_DYNPD)) + { + Serial.print(r, HEX); + Serial.print(": "); + uint8_t len = 1; + // Address registers are 5 bytes in size + if ( (RH_NRF24_REG_0A_RX_ADDR_P0 == r) + || (RH_NRF24_REG_0B_RX_ADDR_P1 == r) + || (RH_NRF24_REG_10_TX_ADDR == r) ) + { + len = 5; + } + uint8_t buf[5]; + spiBurstReadRegister(r, buf, len); + for (uint8_t j = 0; j < len; ++j) + { + Serial.print(buf[j], HEX); + Serial.print(" "); + } + Serial.println(""); + } + } +#endif + + return true; +} + +// Check whether the latest received message is complete and uncorrupted +void RH_NRF24::validateRxBuf() +{ + if (_bufLen < 4) + return; // Too short to be a real message + // Extract the 4 headers + _rxHeaderTo = _buf[0]; + _rxHeaderFrom = _buf[1]; + _rxHeaderId = _buf[2]; + _rxHeaderFlags = _buf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +bool RH_NRF24::available() +{ + if (!_rxBufValid) + { + if (_mode == RHModeTx) + return false; + setModeRx(); + if (spiReadRegister(RH_NRF24_REG_17_FIFO_STATUS) & RH_NRF24_RX_EMPTY) + return false; + // Manual says that messages > 32 octets should be discarded + uint8_t len = spiRead(RH_NRF24_COMMAND_R_RX_PL_WID); + if (len > 32) + { + flushRx(); + clearRxBuf(); + setModeIdle(); + return false; + } + // Clear read interrupt + spiWriteRegister(RH_NRF24_REG_07_STATUS, RH_NRF24_RX_DR); + // Get the message into the RX buffer, so we can inspect the headers + spiBurstRead(RH_NRF24_COMMAND_R_RX_PAYLOAD, _buf, len); + _bufLen = len; + // 140 microsecs (32 octet payload) + validateRxBuf(); + if (_rxBufValid) + setModeIdle(); // Got one + } + return _rxBufValid; +} + +void RH_NRF24::clearRxBuf() +{ + _rxBufValid = false; + _bufLen = 0; +} + +bool RH_NRF24::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + if (buf && len) + { + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen-RH_NRF24_HEADER_LEN) + *len = _bufLen-RH_NRF24_HEADER_LEN; + memcpy(buf, _buf+RH_NRF24_HEADER_LEN, *len); + } + clearRxBuf(); // This message accepted and cleared + return true; +} + +uint8_t RH_NRF24::maxMessageLength() +{ + return RH_NRF24_MAX_MESSAGE_LEN; +} diff --git a/RH_NRF24.h b/RH_NRF24.h new file mode 100644 index 0000000..4d631c2 --- /dev/null +++ b/RH_NRF24.h @@ -0,0 +1,647 @@ +// RH_NRF24.h +// Author: Mike McCauley +// Copyright (C) 2012 Mike McCauley +// $Id: RH_NRF24.h,v 1.21 2020/06/15 23:39:39 mikem Exp $ +// + +#ifndef RH_NRF24_h +#define RH_NRF24_h + +#include +#include + +// This is the maximum number of bytes that can be carried by the nRF24. +// We use some for headers, keeping fewer for RadioHead messages +#define RH_NRF24_MAX_PAYLOAD_LEN 32 + +// The length of the headers we add. +// The headers are inside the nRF24 payload +#define RH_NRF24_HEADER_LEN 4 + +// This is the maximum RadioHead user message length that can be supported by this library. Limited by +// the supported message lengths in the nRF24 +#define RH_NRF24_MAX_MESSAGE_LEN (RH_NRF24_MAX_PAYLOAD_LEN-RH_NRF24_HEADER_LEN) + +// SPI Command names +#define RH_NRF24_COMMAND_R_REGISTER 0x00 +#define RH_NRF24_COMMAND_W_REGISTER 0x20 +#define RH_NRF24_COMMAND_ACTIVATE 0x50 // only on RFM73 ? +#define RH_NRF24_COMMAND_R_RX_PAYLOAD 0x61 +#define RH_NRF24_COMMAND_W_TX_PAYLOAD 0xa0 +#define RH_NRF24_COMMAND_FLUSH_TX 0xe1 +#define RH_NRF24_COMMAND_FLUSH_RX 0xe2 +#define RH_NRF24_COMMAND_REUSE_TX_PL 0xe3 +#define RH_NRF24_COMMAND_R_RX_PL_WID 0x60 +#define RH_NRF24_COMMAND_W_ACK_PAYLOAD(pipe) (0xa8|(pipe&0x7)) +#define RH_NRF24_COMMAND_W_TX_PAYLOAD_NOACK 0xb0 +#define RH_NRF24_COMMAND_NOP 0xff + +// Register names +#define RH_NRF24_REGISTER_MASK 0x1f +#define RH_NRF24_REG_00_CONFIG 0x00 +#define RH_NRF24_REG_01_EN_AA 0x01 +#define RH_NRF24_REG_02_EN_RXADDR 0x02 +#define RH_NRF24_REG_03_SETUP_AW 0x03 +#define RH_NRF24_REG_04_SETUP_RETR 0x04 +#define RH_NRF24_REG_05_RF_CH 0x05 +#define RH_NRF24_REG_06_RF_SETUP 0x06 +#define RH_NRF24_REG_07_STATUS 0x07 +#define RH_NRF24_REG_08_OBSERVE_TX 0x08 +#define RH_NRF24_REG_09_RPD 0x09 +#define RH_NRF24_REG_0A_RX_ADDR_P0 0x0a +#define RH_NRF24_REG_0B_RX_ADDR_P1 0x0b +#define RH_NRF24_REG_0C_RX_ADDR_P2 0x0c +#define RH_NRF24_REG_0D_RX_ADDR_P3 0x0d +#define RH_NRF24_REG_0E_RX_ADDR_P4 0x0e +#define RH_NRF24_REG_0F_RX_ADDR_P5 0x0f +#define RH_NRF24_REG_10_TX_ADDR 0x10 +#define RH_NRF24_REG_11_RX_PW_P0 0x11 +#define RH_NRF24_REG_12_RX_PW_P1 0x12 +#define RH_NRF24_REG_13_RX_PW_P2 0x13 +#define RH_NRF24_REG_14_RX_PW_P3 0x14 +#define RH_NRF24_REG_15_RX_PW_P4 0x15 +#define RH_NRF24_REG_16_RX_PW_P5 0x16 +#define RH_NRF24_REG_17_FIFO_STATUS 0x17 +#define RH_NRF24_REG_1C_DYNPD 0x1c +#define RH_NRF24_REG_1D_FEATURE 0x1d + +// These register masks etc are named wherever possible +// corresponding to the bit and field names in the nRF24L01 Product Specification +// #define RH_NRF24_REG_00_CONFIG 0x00 +#define RH_NRF24_MASK_RX_DR 0x40 +#define RH_NRF24_MASK_TX_DS 0x20 +#define RH_NRF24_MASK_MAX_RT 0x10 +#define RH_NRF24_EN_CRC 0x08 +#define RH_NRF24_CRCO 0x04 +#define RH_NRF24_PWR_UP 0x02 +#define RH_NRF24_PRIM_RX 0x01 + +// #define RH_NRF24_REG_01_EN_AA 0x01 +#define RH_NRF24_ENAA_P5 0x20 +#define RH_NRF24_ENAA_P4 0x10 +#define RH_NRF24_ENAA_P3 0x08 +#define RH_NRF24_ENAA_P2 0x04 +#define RH_NRF24_ENAA_P1 0x02 +#define RH_NRF24_ENAA_P0 0x01 + +// #define RH_NRF24_REG_02_EN_RXADDR 0x02 +#define RH_NRF24_ERX_P5 0x20 +#define RH_NRF24_ERX_P4 0x10 +#define RH_NRF24_ERX_P3 0x08 +#define RH_NRF24_ERX_P2 0x04 +#define RH_NRF24_ERX_P1 0x02 +#define RH_NRF24_ERX_P0 0x01 + +// #define RH_NRF24_REG_03_SETUP_AW 0x03 +#define RH_NRF24_AW_3_BYTES 0x01 +#define RH_NRF24_AW_4_BYTES 0x02 +#define RH_NRF24_AW_5_BYTES 0x03 + +// #define RH_NRF24_REG_04_SETUP_RETR 0x04 +#define RH_NRF24_ARD 0xf0 +#define RH_NRF24_ARC 0x0f + +// #define RH_NRF24_REG_05_RF_CH 0x05 +#define RH_NRF24_RF_CH 0x7f + +// #define RH_NRF24_REG_06_RF_SETUP 0x06 +#define RH_NRF24_CONT_WAVE 0x80 +#define RH_NRF24_RF_DR_LOW 0x20 +#define RH_NRF24_PLL_LOCK 0x10 +#define RH_NRF24_RF_DR_HIGH 0x08 +#define RH_NRF24_PWR 0x06 +#define RH_NRF24_PWR_m18dBm 0x00 +#define RH_NRF24_PWR_m12dBm 0x02 +#define RH_NRF24_PWR_m6dBm 0x04 +#define RH_NRF24_PWR_0dBm 0x06 +#define RH_NRF24_LNA_HCURR 0x01 + +// #define RH_NRF24_REG_07_STATUS 0x07 +#define RH_NRF24_RX_DR 0x40 +#define RH_NRF24_TX_DS 0x20 +#define RH_NRF24_MAX_RT 0x10 +#define RH_NRF24_RX_P_NO 0x0e +#define RH_NRF24_STATUS_TX_FULL 0x01 + +// #define RH_NRF24_REG_08_OBSERVE_TX 0x08 +#define RH_NRF24_PLOS_CNT 0xf0 +#define RH_NRF24_ARC_CNT 0x0f + +// #define RH_NRF24_REG_09_RPD 0x09 +#define RH_NRF24_RPD 0x01 + +// #define RH_NRF24_REG_17_FIFO_STATUS 0x17 +#define RH_NRF24_TX_REUSE 0x40 +#define RH_NRF24_TX_FULL 0x20 +#define RH_NRF24_TX_EMPTY 0x10 +#define RH_NRF24_RX_FULL 0x02 +#define RH_NRF24_RX_EMPTY 0x01 + +// #define RH_NRF24_REG_1C_DYNPD 0x1c +#define RH_NRF24_DPL_ALL 0x3f +#define RH_NRF24_DPL_P5 0x20 +#define RH_NRF24_DPL_P4 0x10 +#define RH_NRF24_DPL_P3 0x08 +#define RH_NRF24_DPL_P2 0x04 +#define RH_NRF24_DPL_P1 0x02 +#define RH_NRF24_DPL_P0 0x01 + +// #define RH_NRF24_REG_1D_FEATURE 0x1d +#define RH_NRF24_EN_DPL 0x04 +#define RH_NRF24_EN_ACK_PAY 0x02 +#define RH_NRF24_EN_DYN_ACK 0x01 + + +///////////////////////////////////////////////////////////////////// +/// \class RH_NRF24 RH_NRF24.h +/// \brief Send and receive unaddressed, unreliable datagrams by nRF24L01 and compatible transceivers. +/// +/// Supported transceivers include: +/// - Nordic nRF24 based 2.4GHz radio modules, such as nRF24L01 http://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01 +/// and other compatible transceivers. +/// - nRF24L01p with PA and LNA modules that produce a higher power output similar to this one: +/// http://www.elecfreaks.com/wiki/index.php?title=2.4G_Wireless_nRF24L01p_with_PA_and_LNA +/// - Sparkfun WRL-00691 module with nRF24L01 https://www.sparkfun.com/products/691 +/// or WRL-00705 https://www.sparkfun.com/products/705 etc. +/// - Hope-RF RFM73 http://www.hoperf.com/rf/2.4g_module/RFM73.htm and +/// http://www.anarduino.com/details.jsp?pid=121 +/// and compatible devices (such as BK2423). nRF24L01 and RFM73 can interoperate +/// with each other. +/// +/// This base class provides basic functions for sending and receiving unaddressed, unreliable datagrams +/// of arbitrary length to 28 octets per packet. Use one of the Manager classes to get addressing and +/// acknowledgement reliability, routing, meshes etc. +/// +/// The nRF24L01 (http://www.sparkfun.com/datasheets/Wireless/Nordic/nRF24L01P_Product_Specification_1_0.pdf) +/// is a low-cost 2.4GHz ISM transceiver module. It supports a number of channel frequencies in the 2.4GHz band +/// and a range of data rates. +/// +/// This library provides functions for sending and receiving messages of up to 28 octets on any +/// frequency supported by the nRF24L01, at a selected data rate. +/// +/// Several nRF24L01 modules can be connected to an Arduino, permitting the construction of translators +/// and frequency changers, etc. +/// +/// The nRF24 transceiver is configured to use Enhanced Shockburst with no acknowledgement and no retransmits. +/// TX_ADDR and RX_ADDR_P0 are set to the network address. If you need the low level auto-acknowledgement +/// feature supported by this chip, you can use our original NRF24 library +/// at http://www.airspayce.com/mikem/arduino/NRF24 +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// data rate, and with identical network addresses. +/// +/// Example Arduino programs are included to show the main modes of use. +/// +/// \par Packet Format +/// +/// All messages sent and received by this class conform to this packet format, as specified by +/// the nRF24L01 product specification: +/// +/// - 1 octets PREAMBLE +/// - 3 to 5 octets NETWORK ADDRESS +/// - 9 bits packet control field +/// - 0 to 32 octets PAYLOAD, consisting of: +/// - 1 octet TO header +/// - 1 octet FROM header +/// - 1 octet ID header +/// - 1 octet FLAGS header +/// - 0 to 28 octets of user message +/// - 2 octets CRC +/// +/// \par Connecting nRF24L01 to Arduino +/// +/// The electrical connection between the nRF24L01 and the Arduino require 3.3V, the 3 x SPI pins (SCK, SDI, SDO), +/// a Chip Enable pin and a Slave Select pin. +/// If you are using the Sparkfun WRL-00691 module, it has a voltage regulator on board and +/// can be should with 5V VCC if possible. +/// The examples below assume the Sparkfun WRL-00691 module +/// +/// Connect the nRF24L01 to most Arduino's like this (Caution, Arduino Mega has different pins for SPI, +/// see below). Use these same connections for Teensy 3.1 (use 3.3V not 5V Vcc). +/// \code +/// Arduino Sparkfun WRL-00691 +/// 5V-----------VCC (3.3V to 7V in) +/// pin D8-----------CE (chip enable in) +/// SS pin D10----------CSN (chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------SDI (SPI Data in) +/// MISO pin D12----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND----------GND (ground in) +/// \endcode +/// +/// For an Arduino Leonardo (the SPI pins do not come out on the Digital pins as for normal Arduino, but only +/// appear on the ICSP header) +/// \code +/// Leonardo Sparkfun WRL-00691 +/// 5V-----------VCC (3.3V to 7V in) +/// pin D8-----------CE (chip enable in) +/// SS pin D10----------CSN (chip select in) +/// SCK ICSP pin 3----------SCK (SPI clock in) +/// MOSI ICSP pin 4----------SDI (SPI Data in) +/// MISO ICSP pin 1----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND----------GND (ground in) +/// \endcode +/// and initialise the NRF24 object like this to explicitly set the SS pin +/// NRF24 nrf24(8, 10); +/// +/// For an Arduino Due (the SPI pins do not come out on the Digital pins as for normal Arduino, but only +/// appear on the SPI header). Use the same connections for Yun with 5V or 3.3V. +/// \code +/// Due Sparkfun WRL-00691 +/// 3.3V-----------VCC (3.3V to 7V in) +/// pin D8-----------CE (chip enable in) +/// SS pin D10----------CSN (chip select in) +/// SCK SPI pin 3----------SCK (SPI clock in) +/// MOSI SPI pin 4----------SDI (SPI Data in) +/// MISO SPI pin 1----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND----------GND (ground in) +/// \endcode +/// and initialise the NRF24 object with the default constructor +/// NRF24 nrf24; +/// +/// For an Arduino Mega: +/// \code +/// Mega Sparkfun WRL-00691 +/// 5V-----------VCC (3.3V to 7V in) +/// pin D8-----------CE (chip enable in) +/// SS pin D53----------CSN (chip select in) +/// SCK pin D52----------SCK (SPI clock in) +/// MOSI pin D51----------SDI (SPI Data in) +/// MISO pin D50----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND----------GND (ground in) +/// \endcode +/// and you can then use the constructor RH_NRF24(8, 53). +/// +/// For an Itead Studio IBoard Pro http://imall.iteadstudio.com/iboard-pro.html, connected by hardware SPI to the +/// ITDB02 Parallel LCD Module Interface pins: +/// \code +/// IBoard Signal=ITDB02 pin Sparkfun WRL-00691 +/// 3.3V 37-----------VCC (3.3V to 7V in) +/// D2 28-----------CE (chip enable in) +/// D29 27----------CSN (chip select in) +/// SCK D52 32----------SCK (SPI clock in) +/// MOSI D51 34----------SDI (SPI Data in) +/// MISO D50 30----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND 39----------GND (ground in) +/// \endcode +/// And initialise like this: +/// \code +/// RH_NRF24 nrf24(2, 29); +/// \endcode +/// +/// For an Itead Studio IBoard Pro http://imall.iteadstudio.com/iboard-pro.html, connected by software SPI to the +/// nRF24L01+ Module Interface pins. CAUTION: performance of software SPI is very slow and is not +/// compatible with other modules running hardware SPI. +/// \code +/// IBoard Signal=Module pin Sparkfun WRL-00691 +/// 3.3V 2----------VCC (3.3V to 7V in) +/// D12 3-----------CE (chip enable in) +/// D29 4----------CSN (chip select in) +/// D9 5----------SCK (SPI clock in) +/// D8 6----------SDI (SPI Data in) +/// D7 7----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND 1----------GND (ground in) +/// \endcode +/// And initialise like this: +/// \code +/// #include +/// #include +/// #include +/// Singleton instance of the radio driver +/// RHSoftwareSPI spi; +/// RH_NRF24 nrf24(12, 11, spi); +/// void setup() { +/// spi.setPins(7, 8, 9); +/// .... +/// \endcode +/// +/// +/// For Raspberry Pi with Sparkfun WRL-00691 +/// \code +/// Raspberry Pi P1 pin Sparkfun WRL-00691 +/// 5V 2-----------VCC (3.3V to 7V in) +/// GPIO25 22-----------CE (chip enable in) +/// GPIO8 24----------CSN (chip select in) +/// GPIO11 23----------SCK (SPI clock in) +/// GPIO10 19----------SDI (SPI Data in) +/// GPIO9 21----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND 6----------GND (ground in) +/// \endcode +/// and initialise like this: +/// \code +/// RH_NRF24 nrf24(RPI_V2_GPIO_P1_22, RPI_V2_GPIO_P1_24); +/// \endcode +/// See the example program and Makefile in examples/raspi. Requires bcm2835 library to be previously installed. +/// \code +/// cd examples/raspi +/// make +/// sudo ./RasPiRH +/// \endcode +/// \code +/// +/// You can override the default settings for the CSN and CE pins +/// in the NRF24() constructor if you wish to connect the slave select CSN to other than the normal one for your +/// +/// Caution: on the Raspberry Pi Zero, the hardware SPI, is only connected to the +/// ICSP-header. So in order to use the RF, one must either connect it to the SPI-pins +/// of the ICSP-header or use the software SPI provided by RHSoftwareSPI. +/// the mapping of the SPI-Pins for each board here: +/// https://www.arduino.cc/en/Reference/SPI +/// Arduino (D10 for Diecimila, Uno etc and D53 for Mega) +/// +/// Caution: on some Arduinos such as the Mega 2560, if you set the slave select pin to be other than the usual SS +/// pin (D53 on Mega 2560), you may need to set the usual SS pin to be an output to force the Arduino into SPI +/// master mode. +/// +/// Caution: this module has not been proved to work with Leonardo, at least without level +/// shifters between the nRF24 and the Leonardo. Tests seem to indicate that such level shifters would be required +/// with Leonardo to make it work. +/// +/// It is possible to have 2 radios conected to one arduino, provided each radio has its own +/// CSN and CE line (SCK, SDI and SDO are common to both radios) +/// +/// \par SPI Interface +/// +/// You can interface to nRF24L01 with with hardware or software SPI. Use of software SPI with the RHSoftwareSPI +/// class depends on a fast enough processor and digitalOut() functions to achieve a high enough SPI bus frequency. +/// If you observe reliable behaviour with the default hardware SPI RHHardwareSPI, but unreliable behaviour +/// with Software SPI RHSoftwareSPI, it may be due to slow CPU performance. +/// +/// Initialisation example with hardware SPI +/// \code +/// #include +/// RH_NRF24 driver; +/// RHReliableDatagram manager(driver, CLIENT_ADDRESS); +/// \endcode +/// +/// Initialisation example with software SPI +/// \code +/// #include +/// #include +/// RHSoftwareSPI spi; +/// RH_NRF24 driver(8, 10, spi); +/// RHReliableDatagram manager(driver, CLIENT_ADDRESS); +/// \endcode +/// +/// \par Example programs +/// +/// Several example programs are provided. +/// +/// \par Radio Performance +/// +/// Frequency accuracy may be debatable. For nominal frequency of 2401.000 MHz (ie channel 1), +/// my Yaesu VR-5000 receiver indicated the center frequency for my test radios +/// was 2401.121 MHz. Its not clear to me if the Yaesu +/// is the source of the error, but I tend to believe it, which would make the nRF24l01 frequency out by 121kHz. +/// +/// The measured power output for a nRF24L01p with PA and LNA set to 0dBm output is about 18dBm. +/// +/// \par Radio operating strategy and defaults +/// +/// The radio is enabled all the time, and switched between TX and RX modes depending on +/// whether there is any data to send. Sending data sets the radio to TX mode. +/// After data is sent, the radio automatically returns to Standby II mode. Calling waitAvailable() or +/// waitAvailableTimeout() starts the radio in RX mode. +/// +/// The radio is configured by default to Channel 2, 2Mbps, 0dBm power, 5 bytes address, payload width 1, CRC enabled +/// 2 byte CRC, No Auto-Ack mode. Enhanced shockburst is used. +/// TX and P0 are set to the Network address. Node addresses and decoding are handled with the RH_NRF24 module. +/// +/// \par Memory +/// +/// Memory usage of this class is minimal. The compiled client and server sketches are about 6000 bytes on Arduino. +/// The reliable client and server sketches compile to about 8500 bytes on Arduino. +/// RAM requirements are minimal. +/// +class RH_NRF24 : public RHNRFSPIDriver +{ +public: + + /// \brief Defines convenient values for setting data rates in setRF() + typedef enum + { + DataRate1Mbps = 0, ///< 1 Mbps + DataRate2Mbps, ///< 2 Mbps + DataRate250kbps ///< 250 kbps + } DataRate; + + /// \brief Convenient values for setting transmitter power in setRF() + /// These are designed to agree with the values for RF_PWR in RH_NRF24_REG_06_RF_SETUP + /// To be passed to setRF(); + typedef enum + { + // Add 20dBm for nRF24L01p with PA and LNA modules + TransmitPowerm18dBm = 0, ///< On nRF24, -18 dBm + TransmitPowerm12dBm, ///< On nRF24, -12 dBm + TransmitPowerm6dBm, ///< On nRF24, -6 dBm + TransmitPower0dBm, ///< On nRF24, 0 dBm + // Sigh, different power levels for the same bit patterns on RFM73: + // On RFM73P-S, there is a Tx power amp, so expect higher power levels, up to 20dBm. Alas + // there is no clear documentation on the power for different settings :-( + RFM73TransmitPowerm10dBm = 0, ///< On RFM73, -10 dBm + RFM73TransmitPowerm5dBm, ///< On RFM73, -5 dBm + RFM73TransmitPowerm0dBm, ///< On RFM73, 0 dBm + RFM73TransmitPower5dBm ///< On RFM73, 5 dBm. 20dBm on RFM73P-S2 ? + + } TransmitPower; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// chip enable and slave select pin. + /// After constructing, you must call init() to initialise the interface + /// and the radio module + /// \param[in] chipEnablePin the Arduino pin to use to enable the chip for transmit/receive + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the NRF24 before + /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, + /// D10 for Maple) + /// \param[in] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + RH_NRF24(uint8_t chipEnablePin = 8, uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi); + + /// Initialises this instance and the radio module connected to it. + /// The following steps are taken:g + /// - Set the chip enable and chip select pins to output LOW, HIGH respectively. + /// - Initialise the SPI output pins + /// - Initialise the SPI interface library to 8MHz (Hint, if you want to lower + /// the SPI frequency (perhaps where you have other SPI shields, low voltages etc), + /// call SPI.setClockDivider() after init()). + /// -Flush the receiver and transmitter buffers + /// - Set the radio to receive with powerUpRx(); + /// \return true if everything was successful + bool init(); + + /// Reads a single register from the NRF24 + /// \param[in] reg Register number, one of RH_NRF24_REG_* + /// \return The value of the register + uint8_t spiReadRegister(uint8_t reg); + + /// Writes a single byte to the NRF24, and at the same time reads the current STATUS register + /// \param[in] reg Register number, one of RH_NRF24_REG_* + /// \param[in] val The value to write + /// \return the current STATUS (read while the command is sent) + uint8_t spiWriteRegister(uint8_t reg, uint8_t val); + + /// Reads a number of consecutive registers from the NRF24 using burst read mode + /// \param[in] reg Register number of the first register, one of RH_NRF24_REG_* + /// \param[in] dest Array to write the register values to. Must be at least len bytes + /// \param[in] len Number of bytes to read + /// \return the current STATUS (read while the command is sent) + uint8_t spiBurstReadRegister(uint8_t reg, uint8_t* dest, uint8_t len); + + /// Write a number of consecutive registers using burst write mode + /// \param[in] reg Register number of the first register, one of RH_NRF24_REG_* + /// \param[in] src Array of new register values to write. Must be at least len bytes + /// \param[in] len Number of bytes to write + /// \return the current STATUS (read while the command is sent) + uint8_t spiBurstWriteRegister(uint8_t reg, uint8_t* src, uint8_t len); + + /// Reads and returns the device status register NRF24_REG_02_DEVICE_STATUS + /// \return The value of the device status register + uint8_t statusRead(); + + /// Sets the transmit and receive channel number. + /// The frequency used is (2400 + channel) MHz + /// \return true on success + bool setChannel(uint8_t channel); + + /// Sets the chip configuration that will be used to set + /// the NRF24 NRF24_REG_00_CONFIG register when in Idle mode. This allows you to change some + /// chip configuration for compatibility with libraries other than this one. + /// You should not normally need to call this. + /// Defaults to NRF24_EN_CRC| RH_NRF24_CRCO, which is the standard configuration for this library + /// (2 byte CRC enabled). + /// \param[in] mode The chip configuration to be used whe in Idle mode. + /// \return true on success + bool setOpMode(uint8_t mode); + + /// Sets the Network address. + /// Only nodes with the same network address can communicate with each other. You + /// can set different network addresses in different sets of nodes to isolate them from each other. + /// Internally, this sets the nRF24 TX_ADDR and RX_ADDR_P0 to be the given network address. + /// The default network address is 0xE7E7E7E7E7 + /// \param[in] address The new network address. Must match the network address of any receiving node(s). + /// \param[in] len Number of bytes of address to set (3 to 5). + /// \return true on success, false if len is not in the range 3-5 inclusive. + bool setNetworkAddress(uint8_t* address, uint8_t len); + + /// Sets the data rate and transmitter power to use. Note that the nRF24 and the RFM73 have different + /// available power levels, and for convenience, 2 different sets of values are available in the + /// RH_NRF24::TransmitPower enum. The ones with the RFM73 only have meaning on the RFM73 and compatible + /// devces. The others are for the nRF24. + /// \param [in] data_rate The data rate to use for all packets transmitted and received. One of RH_NRF24::DataRate. + /// \param [in] power Transmitter power. One of RH_NRF24::TransmitPower. + /// \return true on success + bool setRF(DataRate data_rate, TransmitPower power); + + /// Sets the radio in power down mode, with the configuration set to the + /// last value from setOpMode(). + /// Sets chip enable to LOW. + void setModeIdle(); + + /// Sets the radio in RX mode. + /// Sets chip enable to HIGH to enable the chip in RX mode. + void setModeRx(); + + /// Sets the radio in TX mode. + /// Pulses the chip enable LOW then HIGH to enable the chip in TX mode. + void setModeTx(); + + /// Sends data to the address set by setTransmitAddress() + /// Sets the radio to TX mode + /// \param [in] data Data bytes to send. + /// \param [in] len Number of data bytes to send + /// \return true on success (which does not necessarily mean the receiver got the message, only that the message was + /// successfully transmitted). + bool send(const uint8_t* data, uint8_t len); + + /// Blocks until the current message (if any) + /// has been transmitted + /// \return true on success, false if the chip is not in transmit mode or other transmit failure + virtual bool waitPacketSent(); + + /// Indicates if the chip is in transmit mode and + /// there is a packet currently being transmitted + /// \return true if the chip is in transmit mode and there is a transmission in progress + bool isSending(); + + /// Prints the value of all chip registers + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging purposes only. + /// \return true on success + bool printRegisters(); + + /// Checks whether a received message is available. + /// This can be called multiple times in a timeout loop + /// \return true if a complete, valid message has been received and is able to be retrieved by + /// recv() + bool available(); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + bool recv(uint8_t* buf, uint8_t* len); + + /// The maximum message length supported by this driver + /// \return The maximum message length supported by this driver + uint8_t maxMessageLength(); + + /// Sets the radio into Power Down mode. + /// If successful, the radio will stay in Power Down mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. + /// \return true if sleep mode was successfully entered. + virtual bool sleep(); + +protected: + /// Flush the TX FIFOs + /// \return the value of the device status register + uint8_t flushTx(); + + /// Flush the RX FIFOs + /// \return the value of the device status register + uint8_t flushRx(); + + /// Examine the receive buffer to determine whether the message is for this node + void validateRxBuf(); + + /// Clear our local receive buffer + void clearRxBuf(); + +private: + /// This idle mode chip configuration + uint8_t _configuration; + + /// the number of the chip enable pin + uint8_t _chipEnablePin; + + /// Number of octets in the buffer + uint8_t _bufLen; + + /// The receiver/transmitter buffer + uint8_t _buf[RH_NRF24_MAX_PAYLOAD_LEN]; + + /// True when there is a valid message in the buffer + bool _rxBufValid; +}; + +/// @example nrf24_client.ino +/// @example nrf24_server.ino +/// @example nrf24_encrypted_client.ino +/// @example nrf24_encrypted_server.ino +/// @example nrf24_reliable_datagram_client.ino +/// @example nrf24_reliable_datagram_server.ino +/// @example RasPiRH.cpp + +#endif diff --git a/RH_NRF51.cpp b/RH_NRF51.cpp new file mode 100644 index 0000000..a537eec --- /dev/null +++ b/RH_NRF51.cpp @@ -0,0 +1,400 @@ +// NRF51.cpp +// +// Per: nRF51_Series_Reference_manual v3.0.pdf +// Copyright (C) 2012 Mike McCauley +// $Id: RH_NRF51.cpp,v 1.4 2017/02/01 21:46:02 mikem Exp $ + +// Set by Arduino IDE and RadioHead.h when compiling for nRF51 or nRF52 chips: + +#include + +#if RH_PLATFORM==RH_PLATFORM_NRF51 + + +RH_NRF51::RH_NRF51() + : _rxBufValid(false) +#if RH_NRF51_HAVE_ENCRYPTION + , _encrypting(false) +#endif +{ +} + +bool RH_NRF51::init() +{ + // Enable the High Frequency clock to the system as a whole + NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; + NRF_CLOCK->TASKS_HFCLKSTART = 1; + /* Wait for the external oscillator to start up */ + while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) + ; + + // Disable and reset the radio + NRF_RADIO->POWER = RADIO_POWER_POWER_Disabled; + NRF_RADIO->POWER = RADIO_POWER_POWER_Enabled; + NRF_RADIO->EVENTS_DISABLED = 0; + NRF_RADIO->TASKS_DISABLE = 1; + // Wait until we are in DISABLE state + while (NRF_RADIO->EVENTS_DISABLED == 0) {} + + // Physical on-air address is set in PREFIX0 + BASE0 by setNetworkAddress + NRF_RADIO->TXADDRESS = 0x00; // Use logical address 0 (PREFIX0 + BASE0) + NRF_RADIO->RXADDRESSES = 0x01; // Enable reception on logical address 0 (PREFIX0 + BASE0) + + // Configure the CRC + NRF_RADIO->CRCCNF = (RADIO_CRCCNF_LEN_Two << RADIO_CRCCNF_LEN_Pos); // Number of checksum bits + NRF_RADIO->CRCINIT = 0xFFFFUL; // Initial value + NRF_RADIO->CRCPOLY = 0x11021UL; // CRC poly: x^16+x^12^x^5+1 + + // These shorts will make the radio transition from Ready to Start to Disable automatically + // for both TX and RX, which makes for much shorter on-air times + NRF_RADIO->SHORTS = (RADIO_SHORTS_READY_START_Enabled << RADIO_SHORTS_READY_START_Pos) + | (RADIO_SHORTS_END_DISABLE_Enabled << RADIO_SHORTS_END_DISABLE_Pos); + + NRF_RADIO->PCNF0 = (8 << RADIO_PCNF0_LFLEN_Pos) // Payload size length in bits + | (1 << RADIO_PCNF0_S0LEN_Pos) // S0 is 1 octet + | (8 << RADIO_PCNF0_S1LEN_Pos); // S1 is 1 octet + + // Make sure we are powered down + setModeIdle(); + + // Set a default network address + uint8_t default_network_address[] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7}; + setNetworkAddress(default_network_address, sizeof(default_network_address)); + + setChannel(2); // The default, in case it was set by another app without powering down + setRF(RH_NRF51::DataRate2Mbps, RH_NRF51::TransmitPower0dBm); + setEncryptionKey(NULL); + return true; +} + +bool RH_NRF51::setChannel(uint8_t channel) +{ + NRF_RADIO->FREQUENCY = ((channel << RADIO_FREQUENCY_FREQUENCY_Pos) & RADIO_FREQUENCY_FREQUENCY_Msk); + return true; +} + +bool RH_NRF51::setNetworkAddress(uint8_t* address, uint8_t len) +{ + if (len < 3 || len > 5) + return false; + + // First byte is the prefix, remainder are base + NRF_RADIO->PREFIX0 = ((address[0] << RADIO_PREFIX0_AP0_Pos) & RADIO_PREFIX0_AP0_Msk); + uint32_t base; + memcpy(&base, address+1, len-1); + NRF_RADIO->BASE0 = base; + + NRF_RADIO->PCNF1 = ( + (((sizeof(_buf)) << RADIO_PCNF1_MAXLEN_Pos) & RADIO_PCNF1_MAXLEN_Msk) // maximum length of payload + | (((0UL) << RADIO_PCNF1_STATLEN_Pos) & RADIO_PCNF1_STATLEN_Msk) // expand the payload with 0 bytes + | (((len-1) << RADIO_PCNF1_BALEN_Pos) & RADIO_PCNF1_BALEN_Msk)); // base address length in number of bytes. + + return true; +} + +bool RH_NRF51::setRF(DataRate data_rate, TransmitPower power) +{ + uint8_t mode; + uint8_t p; + + if (data_rate == DataRate2Mbps) + mode = RADIO_MODE_MODE_Nrf_2Mbit; + else if (data_rate == DataRate1Mbps) + mode = RADIO_MODE_MODE_Nrf_1Mbit; + else if (data_rate == DataRate250kbps) + mode = RADIO_MODE_MODE_Nrf_250Kbit; + else + return false;// Invalid + + if (power == TransmitPower4dBm) + p = RADIO_TXPOWER_TXPOWER_Pos4dBm; + else if (power == TransmitPower0dBm) + p = RADIO_TXPOWER_TXPOWER_0dBm; + else if (power == TransmitPowerm4dBm) + p = RADIO_TXPOWER_TXPOWER_Neg4dBm; + else if (power == TransmitPowerm8dBm) + p = RADIO_TXPOWER_TXPOWER_Neg8dBm; + else if (power == TransmitPowerm12dBm) + p = RADIO_TXPOWER_TXPOWER_Neg12dBm; + else if (power == TransmitPowerm16dBm) + p = RADIO_TXPOWER_TXPOWER_Neg16dBm; + else if (power == TransmitPowerm20dBm) + p = RADIO_TXPOWER_TXPOWER_Neg20dBm; + else if (power == TransmitPowerm30dBm) + p = RADIO_TXPOWER_TXPOWER_Neg30dBm; + else + return false; // Invalid + + + NRF_RADIO->TXPOWER = ((p << RADIO_TXPOWER_TXPOWER_Pos) & RADIO_TXPOWER_TXPOWER_Msk); + NRF_RADIO->MODE = ((mode << RADIO_MODE_MODE_Pos) & RADIO_MODE_MODE_Msk); + + return true; +} + +void RH_NRF51::setModeIdle() +{ + if (_mode != RHModeIdle) + { + NRF_RADIO->EVENTS_DISABLED = 0U; + NRF_RADIO->TASKS_DISABLE = 1; + while (NRF_RADIO->EVENTS_DISABLED == 0U) + ; // wait for the radio to be disabled + NRF_RADIO->EVENTS_END = 0U; + _mode = RHModeIdle; + } +} + +void RH_NRF51::setModeRx() +{ + if (_mode != RHModeRx) + { + setModeIdle(); // Can only start RX from DISABLE state + +#if RH_NRF51_HAVE_ENCRYPTION + // Maybe set the AES CCA module for the correct encryption mode + if (_encrypting) + NRF_CCM->MODE = (CCM_MODE_MODE_Decryption << CCM_MODE_MODE_Pos); // Decrypt + NRF_CCM->MICSTATUS = 0; +#endif + + // Radio will transition automatically to Disable state when a message is received + NRF_RADIO->PACKETPTR = (uint32_t)_buf; + NRF_RADIO->EVENTS_READY = 0U; + NRF_RADIO->TASKS_RXEN = 1; + NRF_RADIO->EVENTS_END = 0U; // So we can detect end of reception + _mode = RHModeRx; + } +} + +void RH_NRF51::setModeTx() +{ + if (_mode != RHModeTx) + { + setModeIdle(); // Can only start RX from DISABLE state + + // Sigh: it seems that it takes longer to start the receiver than the transmitter for this type + // of radio, so if a message is received and an ACK or reply is sent to soon, the original transmitter + // may not see the reply. So we delay here to make sure the receiver is ready. + // Yes, I know this is very ugly + delay(1); + +#if RH_NRF51_HAVE_ENCRYPTION + // Maybe set the AES CCA module for the correct encryption mode + if (_encrypting) + NRF_CCM->MODE = (CCM_MODE_MODE_Encryption << CCM_MODE_MODE_Pos); // Encrypt +#endif + // Radio will transition automatically to Disable state at the end of transmission + NRF_RADIO->PACKETPTR = (uint32_t)_buf; + NRF_RADIO->EVENTS_READY = 0U; + NRF_RADIO->TASKS_TXEN = 1; + NRF_RADIO->EVENTS_END = 0U; // So we can detect end of transmission + _mode = RHModeTx; + } +} + +bool RH_NRF51::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_NRF51_MAX_MESSAGE_LEN) + return false; + +#if RH_NRF51_HAVE_ENCRYPTION + if (_encrypting && len > RH_NRF51_MAX_ENCRYPTED_MESSAGE_LEN) + return false; +#endif + + if (!waitCAD()) + return false; // Check channel activity + + // Set up the headers + _buf[0] = 0; // S0 + _buf[1] = len + RH_NRF51_HEADER_LEN; + _buf[2] = 0; // S1 + // The following octets are subject to encryption + _buf[3] = _txHeaderTo; + _buf[4] = _txHeaderFrom; + _buf[5] = _txHeaderId; + _buf[6] = _txHeaderFlags; + memcpy(_buf+RH_NRF51_HEADER_LEN, data, len); + _rxBufValid = false; + setModeTx(); + + // Radio will return to Disabled state after transmission is complete + _txGood++; + return true; +} + +bool RH_NRF51::waitPacketSent() +{ + // If we are not currently in transmit mode, there is no packet to wait for + if (_mode != RHModeTx) + return false; + + // When the Disabled event occurs we know the transmission has completed + while (!NRF_RADIO->EVENTS_END) + { + YIELD; + } + setModeIdle(); + + return true; +} + +bool RH_NRF51::isSending() +{ + return (NRF_RADIO->STATE == RADIO_STATE_STATE_Tx) ? true : false; +} + +bool RH_NRF51::printRegisters() +{ +#ifdef RH_HAVE_SERIAL + uint16_t i; + uint32_t* p = (uint32_t*)NRF_RADIO; + for (i = 0; (p + i) < (uint32_t*) (((NRF_RADIO_Type*)NRF_RADIO) + 1); i++) + { + Serial.print("Offset: "); + Serial.print(i, DEC); + Serial.print(" "); + Serial.println(*(p+i), HEX); + } +#endif + + return true; +} + +// Check whether the latest received message is complete and uncorrupted +void RH_NRF51::validateRxBuf() +{ + if (_buf[1] < RH_NRF51_HEADER_LEN) + return; // Too short to be a real message + // Extract the 4 headers following S0, LEN and S1 + _rxHeaderTo = _buf[3]; + _rxHeaderFrom = _buf[4]; + _rxHeaderId = _buf[5]; + _rxHeaderFlags = _buf[6]; + + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +void RH_NRF51::setEncryptionKey(uint8_t* key) +{ +#if RH_NRF51_HAVE_ENCRYPTION + if (key) + { + // Configure for on-the-fly encryption + // Set the key + memset(_encryption_cnf, 0, sizeof(_encryption_cnf)); + memcpy(_encryption_cnf, key, RH_NRF51_ENCRYPTION_KEY_LENGTH); + // AES configuration data area + // Note that the IV (Nonce) is not set, defaults to 0s + NRF_CCM->CNFPTR = (uint32_t)_encryption_cnf; + + // Set AES CCM input and putput buffers + // Make sure the _buf is encrypted and put back into _buf + NRF_CCM->INPTR = (uint32_t)_buf; + NRF_CCM->OUTPTR = (uint32_t)_buf; + // Also need to set SCRATCHPTR temp buffer os size 16+MAXPACKETSIZE in RAM + // FIXME: shared buffers if several radios + NRF_CCM->SCRATCHPTR = (uint32_t)_scratch; + + // SHORT from RADIO READY to AESCCM KSGEN using PPI predefined channel 24 + // Also RADIO ADDRESS to AESCCM CRYPT using PPI predefined channel 25 + NRF_PPI->CHENSET = (PPI_CHENSET_CH24_Enabled << PPI_CHENSET_CH24_Pos) + | (PPI_CHENSET_CH25_Enabled << PPI_CHENSET_CH25_Pos) + ; + + // SHORT from AESCCM ENDKSGEN to AESCCM CRYPT + NRF_CCM->SHORTS = (CCM_SHORTS_ENDKSGEN_CRYPT_Enabled << CCM_SHORTS_ENDKSGEN_CRYPT_Pos); + + // Enable the CCM module + NRF_CCM->ENABLE = (CCM_ENABLE_ENABLE_Enabled << CCM_ENABLE_ENABLE_Pos); + + _encrypting = true; + } + else + { + // Disable the CCM module + NRF_CCM->ENABLE = (CCM_ENABLE_ENABLE_Disabled << CCM_ENABLE_ENABLE_Pos); + _encrypting = false; + } +#endif +} + +bool RH_NRF51::available() +{ + if (!_rxBufValid) + { + if (_mode == RHModeTx) + return false; + setModeRx(); + if (!NRF_RADIO->EVENTS_END) + return false; // No message yet + setModeIdle(); +#if RH_NRF51_HAVE_ENCRYPTION + // If encryption is enabled, the decrypted message is not available yet, and there seems + // to be no way to be sure when its ready, but a delay of 2ms is enough + if (_encrypting) + delay(2); +#endif + if (!NRF_RADIO->CRCSTATUS) + { + // Bad CRC, restart the radio + _rxBad++; + setModeRx(); + return false; + } + validateRxBuf(); + if (!_rxBufValid) + setModeRx(); // Try for another + } + return _rxBufValid; +} + +void RH_NRF51::clearRxBuf() +{ + _rxBufValid = false; + _buf[1] = 0; +} + +bool RH_NRF51::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + if (buf && len) + { + // Skip the 4 headers that are at the beginning of the rxBuf + // the payload length is the first octet in _buf + if (*len > _buf[1]-RH_NRF51_HEADER_LEN) + *len = _buf[1]-RH_NRF51_HEADER_LEN; + memcpy(buf, _buf+RH_NRF51_HEADER_LEN, *len); + } + clearRxBuf(); // This message accepted and cleared + return true; +} + +uint8_t RH_NRF51::maxMessageLength() +{ +#if RH_NRF51_HAVE_ENCRYPTION + if (_encrypting) + return RH_NRF51_MAX_ENCRYPTED_MESSAGE_LEN; +#endif + return RH_NRF51_MAX_MESSAGE_LEN; +} + +float RH_NRF51::get_temperature() +{ + NRF_TEMP->EVENTS_DATARDY = 0; + NRF_TEMP->TASKS_START = 1; + + while (!NRF_TEMP->EVENTS_DATARDY) + ; + return NRF_TEMP->TEMP * 0.25; +} + +#endif // NRF51 diff --git a/RH_NRF51.h b/RH_NRF51.h new file mode 100644 index 0000000..9d6a17c --- /dev/null +++ b/RH_NRF51.h @@ -0,0 +1,302 @@ +// RH_NRF51.h +// Author: Mike McCauley +// Copyright (C) 2015 Mike McCauley +// $Id: RH_NRF51.h,v 1.5 2017/07/25 05:26:50 mikem Exp $ +// + +#ifndef RH_NRF51_h +#define RH_NRF51_h + +#include + +// This is the maximum number of bytes that can be carried by the nRF51. +// We use some for headers, keeping fewer for RadioHead messages +#define RH_NRF51_MAX_PAYLOAD_LEN 254 + +// The length of the headers we add. +// The headers are inside the nRF51 payload +// We add: +// S0 (not used) +// LEN +// S1 (not used) +// to +// from +// id +// flags +#define RH_NRF51_HEADER_LEN 7 + +// This is the maximum RadioHead user message length that can be supported by this library. Limited by +// the supported message lengths in the nRF51 +#define RH_NRF51_MAX_MESSAGE_LEN (RH_NRF51_MAX_PAYLOAD_LEN-RH_NRF51_HEADER_LEN) + +// Define to be 1 if you want to support AES CCA encryption using the built-in +// encryption engine. +#define RH_NRF51_HAVE_ENCRYPTION 1 + +// When encryption is enabled, have a much shorter max message length +#define RH_NRF51_MAX_ENCRYPTED_MESSAGE_LEN (27-4) + +// The required length of the AES encryption key +#define RH_NRF51_ENCRYPTION_KEY_LENGTH 16 + +// This is the size of the CCM data structure for AES encryption +// REVISIT: use a struct? +#define RH_NRF51_AES_CCM_CNF_SIZE 33 + +///////////////////////////////////////////////////////////////////// +/// \class RH_NRF51 RH_NRF51.h +/// \brief Send and receive unaddressed, unreliable datagrams by nRF51 and nRF52 compatible transceivers. +/// +/// Supported transceivers include: +/// - Nordic nRF51 based 2.4GHz radio modules, such as nRF51822 +/// and other compatible chips, such as used in RedBearLabs devices like: +/// http://store.redbearlab.com/products/redbearlab-nrf51822 +/// http://store.redbearlab.com/products/blenano +/// and +/// Sparkfun nRF52832 breakout board, with Arduino 1.6.13 and +/// Sparkfun nRF52 boards manager 0.2.3 +/// +/// This base class provides basic functions for sending and receiving unaddressed, unreliable datagrams +/// of arbitrary length to 254 octets per packet. Use one of the Manager classes to get addressing and +/// acknowledgement reliability, routing, meshes etc. +/// +/// The nRF51822 (https://www.nordicsemi.com/eng/Products/Bluetooth-Smart-Bluetooth-low-energy/nRF51822) +/// and nRF52832 (https://learn.sparkfun.com/tutorials/nrf52832-breakout-board-hookup-guide) +/// is a complete SoC (system on a chip) with ARM microprocessor and 2.4 GHz radio, which supports a range of channels +/// and transmission bit rates. Chip antenna is on-board. +/// +/// This library provides functions for sending and receiving messages of up to 254 octets on any +/// frequency supported by the nRF51822/nRF52832, at a selected data rate. +/// +/// The nRF51 transceiver is configured to use Enhanced Shockburst with no acknowledgement and no retransmits. +/// TXADDRESS and RXADDRESSES:RXADDR0 (ie pipe 0) are the logical address used. The on-air network address +/// is set in BASE0 and PREFIX0. SHORTS is used to automatically transition the radio between Ready, Start and Disable. +/// No interrupts are used. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// data rate, and with identical network addresses. +/// +/// Example programs are included to show the main modes of use. +/// +/// \par Packet Format +/// +/// All messages sent and received by this class conform to this packet format. It is NOT compatible +/// with the one used by RH_NRF24 and the nRF24L01 product specification, mainly because the nRF24 only supports +/// 6 bits of message length. +/// +/// - 1 octets PREAMBLE +/// - 3 to 5 octets NETWORK ADDRESS +/// - 1 octet S0 (not used, required if encryption used) +/// - 8 bits PAYLOAD LENGTH +/// - 1 octet S1 (not used, required if encryption used) +/// - 0 to 251 octets PAYLOAD (possibly encrypted), consisting of: +/// - 1 octet TO header +/// - 1 octet FROM header +/// - 1 octet ID header +/// - 1 octet FLAGS header +/// - 0 to 247 octets of user message +/// - 2 octets CRC (Algorithm x^16+x^12^x^5+1 with initial value 0xFFFF). +/// +/// \par Example programs +/// +/// Several example programs are provided. +/// +/// The sample programs are designed to be built using Arduino 1.6.4 or later using the procedures outlined +/// in http://redbearlab.com/getting-started-nrf51822/ +/// or with Sparkfun nRF52832 breakout board, with Arduino 1.6.13 and +/// Sparkfun nRF52 boards manager 0.2.3 using the procedures outlined in +/// https://learn.sparkfun.com/tutorials/nrf52832-breakout-board-hookup-guide +/// +/// \par Radio Performance +/// +/// At DataRate2Mbps (2Mb/s), payload length vs airtime: +/// 0 bytes takes about 70us, 128 bytes takes 520us, 254 bytes take 1020us. +/// You can extrapolate linearly to slower data rates. +/// +/// The RF powers claimed by the chip manufacturer have not been independently verified here. +/// +/// \par Memory +/// +/// The compiled client and server sketches are about 42k bytes on Arduino. +/// The reliable client and server sketches compile to about 43k bytes on Arduino. Unfortunately the +/// Arduino build environmnet does not drop unused clsses and code, so the resulting programs include +/// all the unused classes ad code. This needs to be revisited. +/// RAM requirements are minimal. +/// +class RH_NRF51 : public RHGenericDriver +{ +public: + + /// \brief Defines convenient values for setting data rates in setRF() + typedef enum + { + DataRate1Mbps = 0, ///< 1 Mbps + DataRate2Mbps, ///< 2 Mbps + DataRate250kbps ///< 250 kbps + } DataRate; + + /// \brief Convenient values for setting transmitter power in setRF() + typedef enum + { + // Add 20dBm for nRF24L01p with PA and LNA modules + TransmitPower4dBm = 0, ///< 4 dBm + TransmitPower0dBm, ///< 0 dBm + TransmitPowerm4dBm, ///< -4 dBm + TransmitPowerm8dBm, ///< -8 dBm + TransmitPowerm12dBm, ///< -12 dBm + TransmitPowerm16dBm, ///< -16 dBm + TransmitPowerm20dBm, ///< -20 dBm + TransmitPowerm30dBm, ///< -30 dBm + } TransmitPower; + + /// Constructor. + /// After constructing, you must call init() to initialise the interface + /// and the radio module + RH_NRF51(); + + /// Initialises this instance and the radio module connected to it. + /// The following steps are taken: + /// - Start the processors High Frequency clock DC/DC converter and + /// - Disable and reset the radio + /// - Set the logical channel to 0 for transmit and receive (only pipe 0 is used) + /// - Configure the CRC (2 octets, algorithm x^16+x^12^x^5+1 with initial value 0xffff) + /// - Set the default network address of 0xE7E7E7E7E7 + /// - Set channel to 2 + /// - Set data rate to DataRate2Mbps + /// - Set TX power to TransmitPower0dBm + /// \return true if everything was successful + bool init(); + + /// Sets the transmit and receive channel number. + /// The frequency used is (2400 + channel) MHz + /// \return true on success + bool setChannel(uint8_t channel); + + /// Sets the Network address. + /// Only nodes with the same network address can communicate with each other. You + /// can set different network addresses in different sets of nodes to isolate them from each other. + /// Internally, this sets the nRF51 BASE0 and PREFIX0 to be the given network address. + /// The first octet of the address is used for PREFIX0 and the rest is used for BASE0. BALEN is + /// set to the approprtae base length. + /// The default network address is 0xE7E7E7E7E7. + /// \param[in] address The new network address. Must match the network address of any receiving node(s). + /// \param[in] len Number of bytes of address to set (3 to 5). + /// \return true on success, false if len is not in the range 3-5 inclusive. + bool setNetworkAddress(uint8_t* address, uint8_t len); + + /// Sets the data rate and transmitter power to use. + /// \param [in] data_rate The data rate to use for all packets transmitted and received. One of RH_NRF51::DataRate. + /// \param [in] power Transmitter power. One of RH_NRF51::TransmitPower. + /// \return true on success + bool setRF(DataRate data_rate, TransmitPower power); + + /// Sets the radio in power down mode, with the configuration set to the + /// last value from setOpMode(). + /// Sets chip enable to LOW. + void setModeIdle(); + + /// Sets the radio in RX mode. + void setModeRx(); + + /// Sets the radio in TX mode. + void setModeTx(); + + /// Sends data to the address set by setTransmitAddress() + /// Sets the radio to TX mode. + /// Caution: when encryption is enabled, the maximum message length is reduced to 23 octets. + /// \param [in] data Data bytes to send. + /// \param [in] len Number of data bytes to send + /// \return true on success (which does not necessarily mean the receiver got the message, only that the message was + /// successfully transmitted). False if the message is too long or was otherwise not transmitted. + bool send(const uint8_t* data, uint8_t len); + + /// Blocks until the current message (if any) + /// has been transmitted + /// \return true on success, false if the chip is not in transmit mode or other transmit failure + virtual bool waitPacketSent(); + + /// Indicates if the chip is in transmit mode and + /// there is a packet currently being transmitted + /// \return true if the chip is in transmit mode and there is a transmission in progress + bool isSending(); + + /// Prints the value of all NRF_RADIO registers. + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging purposes only. + /// Caution: there are 1024 of them (many reserved and set to 0). + /// \return true on success + bool printRegisters(); + + /// Checks whether a received message is available. + /// This can be called multiple times in a timeout loop + /// \return true if a complete, valid message has been received and is able to be retrieved by + /// recv() + bool available(); + + /// Turns the receiver on if it not already on. + /// Once a message with CRC correct is received, the receiver will be returned to Idle mode. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + bool recv(uint8_t* buf, uint8_t* len); + + /// Enables AES encryption and sets the AES encryption key, used + /// to encrypt and decrypt all messages using the on-chip AES CCM mode encryption engine. + /// The default is disabled. + /// In the AES configuration, the message counter and IV is always set to 0, which + /// means the same keystream is used for every message with a given key. + /// Caution: when encryption is enabled, the maximum message length is reduced to 23 octets. + /// \param[in] key The key to use. Must be 16 bytes long. The same key must be installed + /// in other instances of RH_RF51, otherwise communications will not work correctly. If key is NULL, + /// encryption is disabled, which is the default. + void setEncryptionKey(uint8_t* key = NULL); + + /// The maximum message length supported by this driver + /// \return The maximum message length supported by this driver + uint8_t maxMessageLength(); + + /// Reeads the current die temperature using the built in TEMP peripheral. + /// Blocks while the temperature is measured, which takes about 30 microseconds. + // \return the current die temperature in degrees C. + float get_temperature(); + +protected: + /// Examine the receive buffer to determine whether the message is for this node + void validateRxBuf(); + + /// Clear our local receive buffer + void clearRxBuf(); + +private: + /// The receiver/transmitter buffer + /// First octet is the payload length, remainder is the payload + uint8_t _buf[RH_NRF51_MAX_PAYLOAD_LEN+1]; + + /// True when there is a valid message in the buffer + bool _rxBufValid; + +#if RH_NRF51_HAVE_ENCRYPTION + /// True if an AES key has been specified and that we are therfore encrypting + /// and decrypting messages on the fly + bool _encrypting; + + /// Scratch area for AES encryption + uint8_t _scratch[RH_NRF51_MAX_PAYLOAD_LEN+1+16]; + + /// Where the AES encryption key and IV are stored + uint8_t _encryption_cnf[RH_NRF51_AES_CCM_CNF_SIZE]; +#endif +}; + +/// @example nrf51_client.ino +/// @example nrf51_server.ino +/// @example nrf51_reliable_datagram_client.ino +/// @example nrf51_reliable_datagram_server.ino +/// @example nrf51_audio_tx.ino +/// @example nrf51_audio_rx.ino +#endif diff --git a/RH_NRF905.cpp b/RH_NRF905.cpp new file mode 100644 index 0000000..3706e5c --- /dev/null +++ b/RH_NRF905.cpp @@ -0,0 +1,270 @@ +// RH_NRF905.cpp +// +// Copyright (C) 2012 Mike McCauley +// $Id: RH_NRF905.cpp,v 1.7 2017/01/12 23:58:00 mikem Exp $ + +#include + +RH_NRF905::RH_NRF905(uint8_t chipEnablePin, uint8_t txEnablePin, uint8_t slaveSelectPin, RHGenericSPI& spi) + : + RHNRFSPIDriver(slaveSelectPin, spi) +{ + _chipEnablePin = chipEnablePin; + _txEnablePin = txEnablePin; +} + +bool RH_NRF905::init() +{ +#if defined (__MK20DX128__) || defined (__MK20DX256__) + // Teensy is unreliable at 8MHz: + _spi.setFrequency(RHGenericSPI::Frequency1MHz); +#else + _spi.setFrequency(RHGenericSPI::Frequency8MHz); +#endif + if (!RHNRFSPIDriver::init()) + return false; + + // Initialise the slave select pin and the tx Enable pin + pinMode(_chipEnablePin, OUTPUT); + pinMode(_txEnablePin, OUTPUT); + digitalWrite(_chipEnablePin, LOW); + digitalWrite(_txEnablePin, LOW); + + // Configure the chip + // CRC 16 bits enabled. 16MHz crystal freq + spiWriteRegister(RH_NRF905_CONFIG_9, RH_NRF905_CONFIG_9_CRC_EN | RH_NRF905_CONFIG_9_CRC_MODE_16BIT | RH_NRF905_CONFIG_9_XOF_16MHZ); + + // Make sure we are powered down + setModeIdle(); + + // Some innocuous defaults + setChannel(108, LOW); // 433.2 MHz + setRF(RH_NRF905::TransmitPowerm10dBm); + + return true; +} + +// Use the register commands to read and write the registers +uint8_t RH_NRF905::spiReadRegister(uint8_t reg) +{ + return spiRead((reg & RH_NRF905_REG_MASK) | RH_NRF905_REG_R_CONFIG); +} + +uint8_t RH_NRF905::spiWriteRegister(uint8_t reg, uint8_t val) +{ + return spiWrite((reg & RH_NRF905_REG_MASK) | RH_NRF905_REG_W_CONFIG, val); +} + +uint8_t RH_NRF905::spiBurstReadRegister(uint8_t reg, uint8_t* dest, uint8_t len) +{ + return spiBurstRead((reg & RH_NRF905_REG_MASK) | RH_NRF905_REG_R_CONFIG, dest, len); +} + +uint8_t RH_NRF905::spiBurstWriteRegister(uint8_t reg, uint8_t* src, uint8_t len) +{ + return spiBurstWrite((reg & RH_NRF905_REG_MASK) | RH_NRF905_REG_W_CONFIG, src, len); +} + +uint8_t RH_NRF905::statusRead() +{ + // The status is a byproduct of sending a command + return spiCommand(0); +} + +bool RH_NRF905::setChannel(uint16_t channel, bool hiFrequency) +{ + spiWriteRegister(RH_NRF905_CONFIG_0, channel & RH_NRF905_CONFIG_0_CH_NO); + // Set or clear the high bit of the channel + uint8_t bit8 = (channel >> 8) & 0x01; + uint8_t reg1 = spiReadRegister(RH_NRF905_CONFIG_1); + reg1 = (reg1 & ~0x01) | bit8; + // Set or clear the HFREQ_PLL bit + reg1 &= ~RH_NRF905_CONFIG_1_HFREQ_PLL; + if (hiFrequency) + reg1 |= RH_NRF905_CONFIG_1_HFREQ_PLL; + spiWriteRegister(RH_NRF905_CONFIG_1, reg1); + return true; +} + +bool RH_NRF905::setNetworkAddress(uint8_t* address, uint8_t len) +{ + if (len < 1 || len > 4) + return false; + // Set RX_AFW and TX_AFW + spiWriteRegister(RH_NRF905_CONFIG_2, len | (len << 4)); + spiBurstWrite(RH_NRF905_REG_W_TX_ADDRESS, address, len); + spiBurstWriteRegister(RH_NRF905_CONFIG_5, address, len); + return true; +} + +bool RH_NRF905::setRF(TransmitPower power) +{ + // Enum definitions of power are the same numerical values as the register + uint8_t reg1 = spiReadRegister(RH_NRF905_CONFIG_1); + reg1 &= ~RH_NRF905_CONFIG_1_PA_PWR; + reg1 |= ((power & 0x3) << 2) & RH_NRF905_CONFIG_1_PA_PWR; + spiWriteRegister(RH_NRF905_CONFIG_1, reg1); + return true; +} + +void RH_NRF905::setModeIdle() +{ + if (_mode != RHModeIdle) + { + digitalWrite(_chipEnablePin, LOW); + digitalWrite(_txEnablePin, LOW); + _mode = RHModeIdle; + } +} + +void RH_NRF905::setModeRx() +{ + if (_mode != RHModeRx) + { + digitalWrite(_txEnablePin, LOW); + digitalWrite(_chipEnablePin, HIGH); + _mode = RHModeRx; + } +} + +void RH_NRF905::setModeTx() +{ + if (_mode != RHModeTx) + { + // Its the high transition that puts us into TX mode + digitalWrite(_txEnablePin, HIGH); + digitalWrite(_chipEnablePin, HIGH); + _mode = RHModeTx; + } +} + +bool RH_NRF905::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_NRF905_MAX_MESSAGE_LEN) + return false; + + if (!waitCAD()) + return false; // Check channel activity + + // Set up the headers + _buf[0] = _txHeaderTo; + _buf[1] = _txHeaderFrom; + _buf[2] = _txHeaderId; + _buf[3] = _txHeaderFlags; + _buf[4] = len; + memcpy(_buf+RH_NRF905_HEADER_LEN, data, len); + spiBurstWrite(RH_NRF905_REG_W_TX_PAYLOAD, _buf, len + RH_NRF905_HEADER_LEN); + setModeTx(); + // Radio will return to Standby mode after transmission is complete + _txGood++; + return true; +} + +bool RH_NRF905::waitPacketSent() +{ + if (_mode != RHModeTx) + return false; + + while (!(statusRead() & RH_NRF905_STATUS_DR)) + YIELD; + setModeIdle(); + return true; +} + +bool RH_NRF905::isSending() +{ + if (_mode != RHModeTx) + return false; + + return !(statusRead() & RH_NRF905_STATUS_DR); +} + +bool RH_NRF905::printRegister(uint8_t reg) +{ +#ifdef RH_HAVE_SERIAL + Serial.print(reg, HEX); + Serial.print(": "); + Serial.println(spiReadRegister(reg), HEX); +#endif + + return true; +} + +bool RH_NRF905::printRegisters() +{ + uint8_t registers[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}; + + uint8_t i; + for (i = 0; i < sizeof(registers); i++) + printRegister(registers[i]); + return true; +} + +// Check whether the latest received message is complete and uncorrupted +void RH_NRF905::validateRxBuf() +{ + // Check the length + uint8_t len = _buf[4]; + if (len > RH_NRF905_MAX_MESSAGE_LEN) + return; // Silly LEN header + + // Extract the 4 headers + _rxHeaderTo = _buf[0]; + _rxHeaderFrom = _buf[1]; + _rxHeaderId = _buf[2]; + _rxHeaderFlags = _buf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _bufLen = len + RH_NRF905_HEADER_LEN; // _buf still includes the headers + _rxBufValid = true; + } +} + +bool RH_NRF905::available() +{ + if (!_rxBufValid) + { + if (_mode == RHModeTx) + return false; + setModeRx(); + if (!(statusRead() & RH_NRF905_STATUS_DR)) + return false; + // Get the message into the RX buffer, so we can inspect the headers + // we still dont know how long is the user message + spiBurstRead(RH_NRF905_REG_R_RX_PAYLOAD, _buf, RH_NRF905_MAX_PAYLOAD_LEN); + validateRxBuf(); + if (_rxBufValid) + setModeIdle(); // Got one + + } + return _rxBufValid; +} + +void RH_NRF905::clearRxBuf() +{ + _rxBufValid = false; + _bufLen = 0; +} + +bool RH_NRF905::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + if (buf && len) + { + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen-RH_NRF905_HEADER_LEN) + *len = _bufLen-RH_NRF905_HEADER_LEN; + memcpy(buf, _buf+RH_NRF905_HEADER_LEN, *len); + } + clearRxBuf(); // This message accepted and cleared + return true; +} + +uint8_t RH_NRF905::maxMessageLength() +{ + return RH_NRF905_MAX_MESSAGE_LEN; +} diff --git a/RH_NRF905.h b/RH_NRF905.h new file mode 100644 index 0000000..76dda56 --- /dev/null +++ b/RH_NRF905.h @@ -0,0 +1,424 @@ +// RH_NRF905.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RH_NRF905.h,v 1.11 2017/07/25 05:26:50 mikem Exp $ +// + +#ifndef RH_NRF905_h +#define RH_NRF905_h + +#include +#include + +// This is the maximum (and only) number of bytes that can be carried by the nRF905. +// We use some for headers, leaving fewer for RadioHead messages +#define RH_NRF905_MAX_PAYLOAD_LEN 32 + +// The length of the headers we add. +// The headers are inside the nRF905 payload +// As well as the usual TO, FROM, ID, FLAGS, we also need LEN, since +// nRF905 only has fixed width messages. +// REVISIT: could we have put the LEN into the FLAGS field? +#define RH_NRF905_HEADER_LEN 5 + +// This is the maximum RadioHead user message length that can be supported by this library. Limited by +// the supported message lengths in the nRF905 +#define RH_NRF905_MAX_MESSAGE_LEN (RH_NRF905_MAX_PAYLOAD_LEN-RH_NRF905_HEADER_LEN) + +// Register names +#define RH_NRF905_REG_MASK 0x0f +#define RH_NRF905_REG_W_CONFIG 0x00 +#define RH_NRF905_REG_R_CONFIG 0x10 +#define RH_NRF905_REG_W_TX_PAYLOAD 0x20 +#define RH_NRF905_REG_R_TX_PAYLOAD 0x21 +#define RH_NRF905_REG_W_TX_ADDRESS 0x22 +#define RH_NRF905_REG_R_TX_ADDRESS 0x23 +#define RH_NRF905_REG_R_RX_PAYLOAD 0x24 +#define RH_NRF905_REG_CHANNEL_CONFIG 0x80 + +// Configuration register +#define RH_NRF905_CONFIG_0 0x00 +#define RH_NRF905_CONFIG_0_CH_NO 0xff + +#define RH_NRF905_CONFIG_1 0x01 +#define RH_NRF905_CONFIG_1_AUTO_RETRAN 0x20 +#define RH_NRF905_CONFIG_1_RX_RED_PWR 0x10 +#define RH_NRF905_CONFIG_1_PA_PWR 0x0c +#define RH_NRF905_CONFIG_1_PA_PWR_N10DBM 0x00 +#define RH_NRF905_CONFIG_1_PA_PWR_N2DBM 0x04 +#define RH_NRF905_CONFIG_1_PA_PWR_6DBM 0x08 +#define RH_NRF905_CONFIG_1_PA_PWR_10DBM 0x0c +#define RH_NRF905_CONFIG_1_HFREQ_PLL 0x02 +#define RH_NRF905_CONFIG_1_CH_NO 0x01 + +#define RH_NRF905_CONFIG_2 0x02 +#define RH_NRF905_CONFIG_2_TX_AFW 0x70 +#define RH_NRF905_CONFIG_2_RX_AFW 0x07 + +#define RH_NRF905_CONFIG_3 0x03 +#define RH_NRF905_CONFIG_3_RX_PW 0x3f + +#define RH_NRF905_CONFIG_4 0x04 +#define RH_NRF905_CONFIG_4_TX_PW 0x3f + +#define RH_NRF905_CONFIG_5 0x05 +#define RH_NRF905_CONFIG_5_RX_ADDRESS 0xff + +#define RH_NRF905_CONFIG_6 0x06 +#define RH_NRF905_CONFIG_6_RX_ADDRESS 0xff + +#define RH_NRF905_CONFIG_7 0x07 +#define RH_NRF905_CONFIG_7_RX_ADDRESS 0xff + +#define RH_NRF905_CONFIG_8 0x08 +#define RH_NRF905_CONFIG_8_RX_ADDRESS 0xff + +#define RH_NRF905_CONFIG_9 0x09 +#define RH_NRF905_CONFIG_9_CRC_MODE_16BIT 0x80 +#define RH_NRF905_CONFIG_9_CRC_EN 0x40 +#define RH_NRF905_CONFIG_9_XOF 0x38 +#define RH_NRF905_CONFIG_9_XOF_4MHZ 0x00 +#define RH_NRF905_CONFIG_9_XOF_8MHZ 0x08 +#define RH_NRF905_CONFIG_9_XOF_12MHZ 0x10 +#define RH_NRF905_CONFIG_9_XOF_16MHZ 0x18 +#define RH_NRF905_CONFIG_9_XOF_20MHZ 0x20 +#define RH_NRF905_CONFIG_9_UP_CLK_EN 0x04 +#define RH_NRF905_CONFIG_9_UP_CLK_FREQ 0x03 +#define RH_NRF905_CONFIG_9_UP_CLK_FREQ_4MHZ 0x00 +#define RH_NRF905_CONFIG_9_UP_CLK_FREQ_2MHZ 0x01 +#define RH_NRF905_CONFIG_9_UP_CLK_FREQ_1MHZ 0x02 +#define RH_NRF905_CONFIG_9_UP_CLK_FREQ_500KHZ 0x03 + +// Status register is always read as first byte +#define RH_NRF905_STATUS_AM 0x80 +#define RH_NRF905_STATUS_DR 0x20 + +///////////////////////////////////////////////////////////////////// +/// \class RH_NRF905 RH_NRF905.h +/// \brief Send and receive unaddressed, unreliable datagrams by nRF905 and compatible transceivers. +/// +/// This base class provides basic functions for sending and receiving unaddressed, unreliable datagrams +/// of arbitrary length to 28 octets per packet. Use one of the Manager classes to get addressing and +/// acknowledgement reliability, routing, meshes etc. +/// +/// The nRF905 transceiver is configured to use Enhanced Shockburst with 16 Bit CRC, and 32 octet packets. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency +/// and with identical network addresses. +/// +/// The nRF905 from Nordic Semiconductor http://www.nordicsemi.com/eng/Products/Sub-1-GHz-RF/nRF905 +/// (http://www.nordicsemi.com/jpn/nordic/content_download/2452/29528/file/Product_Specification_nRF905_v1.5.pdf) +/// is a low-cost 433/868/915 MHz ISM transceiver module. It supports a number of channel frequencies at +/// 100kHz deviation and 50kHz bandwidth with Manchester encoding. +/// +/// We tested with inexpensive nRF905 modules from eBay, similar to: +/// http://www.aliexpress.com/store/product/Free-ship-NRF905-433MHz-Wireless-Transmission-Module-Transceiver-Module-with-Antenna-for-the-433MHz-ISM-band/513046_607163305.html +/// +/// This library provides functions for sending and receiving messages of up to 27 octets on any +/// frequency supported by the nRF905. +/// +/// Several nRF905 modules can be connected to an Arduino, permitting the construction of translators +/// and frequency changers, etc. +/// +/// Example Arduino programs are included to show the main modes of use. +/// +/// \par Packet Format +/// +/// All messages sent and received by this class conform to this fixed length packet format +/// +/// - 4 octets NETWORK ADDRESS +/// - 32 octets PAYLOAD, consisting of: +/// - 1 octet TO header +/// - 1 octet FROM header +/// - 1 octet ID header +/// - 1 octet FLAGS header +/// - 1 octet user message length header +/// - 0 to 27 octets of user message, trailing octets after the user message length are ignored +/// - 2 octets CRC +/// +/// All messages sent and received by this driver are 32 octets. The user message length is embedded in the message. +/// +/// \par Connecting nRF905 +/// +/// The nRF905 is a 3.3V part is is *NOT* 5V tolerant. So you MUST use a 3.3V CPU such as Teensy, Arduino Due etc +/// or else provide for level shifters between the CPU and the nRF905. Failure to consider this will probably +/// break your nRF905. +/// +/// The electrical connection between the nRF905 and the CPU require 3.3V, the 3 x SPI pins (SCK, SDI, SDO), +/// a Chip Enable pin, a Transmit Enable pin and a Slave Select pin. +/// +/// The examples below assume the commonly found cheap Chinese nRF905 modules. The RH_RF905 driver assumes the +/// the nRF905 has a 16MHz crystal. +/// +/// Connect the nRF905 to Teensy (or Arduino with suitable level shifters) like this +/// \code +/// CPU nRF905 module +/// 3V3----------VCC (3.3V) +/// pin D8-----------CE (chip enable in) +/// pin D9-----------TX_EN (transmit enable in) +/// SS pin D10----------CSN (chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------MOSI (SPI Data in) +/// MISO pin D12----------MISO (SPI data out) +/// GND----------GND (ground in) +/// \endcode +/// +/// Caution: Arduino Due is a 3.3V part and is not 5V tolerant (so too is the nRF905 module +/// so they can be connected directly together. Unlike other Arduinos the Due has it default SPI +/// connections on a dedicated 6 pin SPI header in the center of the board, which is +/// physically compatible with Uno, Leonardo and Mega2560. A little dot marks pin 1 on the header. +/// You must connect to these +/// and *not* to the usual Arduino SPI pins Digital 11, 12 and 13. +/// See http://21stdigitalhome.blogspot.com.au/2013/02/arduino-due-hardware-spi.html +/// +/// Connect the nRF905 to Arduino Due like this +/// \code +/// CPU nRF905 module +/// 3V3----------VCC (3.3V) +/// pin D8-----------CE (chip enable in) +/// pin D9-----------TX_EN (transmit enable in) +/// SS pin D10----------CSN (chip select in) +/// SCK on SPI header pin 3----------SCK (SPI clock in) +/// MOSI on SPI header pin 4----------MOSI (SPI Data in) +/// MISO on SPI header pin 1----------MISO (SPI data out) +/// GND----------GND (ground in) +/// \endcode +/// +/// and you can then use the default constructor RH_NRF905(). +/// You can override the default settings for the CE, TX_EN and CSN pins +/// in the NRF905() constructor if you wish to connect the slave select CSN to other than the normal one for your +/// CPU. +/// +/// It is possible to have 2 radios conected to one CPU, provided each radio has its own +/// CSN, TX_EN and CE line (SCK, MOSI and MISO are common to both radios) +/// +/// \par Transmitter Power +/// +/// You can control the transmitter power to be one of 4 power levels: -10, -2, 6 or 10dBm, +/// using the setRF() function, eg: +/// \code +/// nrf905.setRF(RH_NRF905::TransmitPower10dBm); +/// \endcode +/// +/// We have made some actual power measurements against +/// programmed power for an nRF905 module from www.rfinchina.com under the following conditions: +/// - Teensy 3.1 +/// - nRF905 module (with SMA antenna connector) wired to Teensy as described above, channel 108. +/// - 20cm SMA-SMA cable +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// Program power Measured Power +/// dBm dBm +/// -10 -16 +/// -2 -8 +/// 6 0 +/// 10 8 +/// \endcode +/// (Caution: we dont claim laboratory accuracy for these measurements) +/// You would not expect to get anywhere near these powers to air with a simple 1/4 wavelength wire antenna. +/// +/// \par Example programs +/// +/// Several example programs are provided. They work out of the box with Teensy 3.1 and Arduino Due +/// connected as show above. +/// +/// \par Radio Performance +/// +/// Frequency accuracy may be debatable. +/// +/// \par Memory +/// +/// Memory usage of this class is minimal. The compiled client and server sketches are about 16000 bytes on Teensy. +/// +class RH_NRF905 : public RHNRFSPIDriver +{ +public: + /// \brief Convenient values for setting transmitter power in setRF() + /// These are designed to agree with the values for RH_NRF905_CONFIG_1_PA_PWR after + /// left shifting by 2 + /// To be passed to setRF(); + typedef enum + { + TransmitPowerm10dBm = 0, ///< -10 dBm + TransmitPowerm2dBm, ///< -2 dBm + TransmitPower6dBm, ///< 6 dBm + TransmitPower10dBm ///< 10 dBm + } TransmitPower; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// chip enable and slave select pin. + /// After constructing, you must call init() to initialise the interface + /// and the radio module + /// \param[in] chipEnablePin the Arduino pin to use to enable the chip for transmit/receive + /// \param[in] txEnablePin the Arduino pin cponnected to the txEn pin on the radio that enable transmit mode + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the NRF905 before + /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, + /// D10 for Maple, Teensy) + /// \param[in] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + RH_NRF905(uint8_t chipEnablePin = 8, uint8_t txEnablePin = 9, uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi); + + /// Initialises this instance and the radio module connected to it. + /// The following steps are taken:g + /// - Set the chip enable and chip select pins to output LOW, HIGH respectively. + /// - Initialise the SPI output pins + /// - Initialise the SPI interface library to 8MHz (Hint, if you want to lower + /// the SPI frequency (perhaps where you have other SPI shields, low voltages etc), + /// call SPI.setClockDivider() after init()). + /// -Flush the receiver and transmitter buffers + /// - Set the radio to receive with powerUpRx(); + /// \return true if everything was successful + bool init(); + + /// Reads a single register from the NRF905 + /// \param[in] reg Register number, one of NR905_REG_* + /// \return The value of the register + uint8_t spiReadRegister(uint8_t reg); + + /// Writes a single byte to the NRF905, and at the ame time reads the current STATUS register + /// \param[in] reg Register number, one of NRF905_REG_* + /// \param[in] val The value to write + /// \return the current STATUS (read while the command is sent) + uint8_t spiWriteRegister(uint8_t reg, uint8_t val); + + /// Reads a number of consecutive registers from the NRF905 using burst read mode + /// \param[in] reg Register number of the first register, one of NRF905_REG_* + /// \param[in] dest Array to write the register values to. Must be at least len bytes + /// \param[in] len Number of bytes to read + /// \return the current STATUS (read while the command is sent) + uint8_t spiBurstReadRegister(uint8_t reg, uint8_t* dest, uint8_t len); + + /// Write a number of consecutive registers using burst write mode + /// \param[in] reg Register number of the first register, one of NRF905_REG_* + /// \param[in] src Array of new register values to write. Must be at least len bytes + /// \param[in] len Number of bytes to write + /// \return the current STATUS (read while the command is sent) + uint8_t spiBurstWriteRegister(uint8_t reg, uint8_t* src, uint8_t len); + + /// Reads and returns the device status register NRF905_REG_02_DEVICE_STATUS + /// \return The value of the device status register + uint8_t statusRead(); + + /// Sets the transmit and receive channel number. + /// The RF frequency used is (422.4 + channel/10) * (1+hiFrequency) MHz + /// \param[in] channel The channel number. + /// \param[in] hiFrequency false for low frequency band (422.4MHz and up), true for high frequency band (845MHz and up) + /// \return true on success + bool setChannel(uint16_t channel, bool hiFrequency = false); + + /// Sets the Network address. + /// Only nodes with the same network address can communicate with each other. You + /// can set different network addresses in different sets of nodes to isolate them from each other. + /// The default network address is 0xE7E7E7E7 + /// \param[in] address The new network address. Must match the network address of any receiving node(s). + /// \param[in] len Number of bytes of address to set (1 to 4). + /// \return true on success, false if len is not in the range 1-4 inclusive. + bool setNetworkAddress(uint8_t* address, uint8_t len); + + /// Sets the transmitter power to use + /// \param [in] power Transmitter power. One of NRF905::TransmitPower. + /// \return true on success + bool setRF(TransmitPower power); + + /// Sets the radio in power down mode. + /// Sets chip enable to LOW. + /// \return true on success + void setModeIdle(); + + /// Sets the radio in RX mode. + /// Sets chip enable to HIGH to enable the chip in RX mode. + /// \return true on success + void setModeRx(); + + /// Sets the radio in TX mode. + /// Pulses the chip enable LOW then HIGH to enable the chip in TX mode. + /// \return true on success + void setModeTx(); + + /// Sends data to the address set by setTransmitAddress() + /// Sets the radio to TX mode + /// \param [in] data Data bytes to send. + /// \param [in] len Number of data bytes to set in the TX buffer. The actual size of the + /// transmitted data payload is set by setPayloadSize. Maximum message length actually + /// transmitted is RH_NRF905_MAX_MESSAGE_LEN = 27. + /// \return true on success (which does not necessarily mean the receiver got the message, only that the message was + /// successfully transmitted). Returns false if the requested message length exceeds RH_NRF905_MAX_MESSAGE_LEN. + bool send(const uint8_t* data, uint8_t len); + + /// Blocks until the current message (if any) + /// has been transmitted + /// \return true on success, false if the chip is not in transmit mode + virtual bool waitPacketSent(); + + /// Indicates if the chip is in transmit mode and + /// there is a packet currently being transmitted + /// \return true if the chip is in transmit mode and there is a transmission in progress + bool isSending(); + + /// Prints the value of a single chip register + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging purposes only. + /// \return true on success + bool printRegister(uint8_t reg); + + /// Prints the value of all chip registers + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging purposes only. + /// \return true on success + bool printRegisters(); + + /// Checks whether a received message is available. + /// This can be called multiple times in a timeout loop + /// \return true if a complete, valid message has been received and is able to be retrieved by + /// recv() + bool available(); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + bool recv(uint8_t* buf, uint8_t* len); + + /// The maximum message length supported by this driver + /// \return The maximum message length supported by this driver + uint8_t maxMessageLength(); + +protected: + /// Examine the revceive buffer to determine whether the message is for this node + void validateRxBuf(); + + /// Clear our local receive buffer + void clearRxBuf(); + +private: + /// This idle mode chip configuration + uint8_t _configuration; + + /// the number of the chip enable pin + uint8_t _chipEnablePin; + + /// The number of the transmit enable pin + uint8_t _txEnablePin; + + /// Number of octets in the buffer + uint8_t _bufLen; + + /// The receiver/transmitter buffer + uint8_t _buf[RH_NRF905_MAX_PAYLOAD_LEN]; + + /// True when there is a valid message in the buffer + bool _rxBufValid; +}; + +/// @example nrf905_client.ino +/// @example nrf905_server.ino +/// @example nrf905_reliable_datagram_client.ino +/// @example nrf905_reliable_datagram_server.ino + +#endif diff --git a/RH_RF22.cpp b/RH_RF22.cpp new file mode 100644 index 0000000..6b8a9d5 --- /dev/null +++ b/RH_RF22.cpp @@ -0,0 +1,814 @@ +// RH_RF22.cpp +// +// Copyright (C) 2011 Mike McCauley +// $Id: RH_RF22.cpp,v 1.33 2020/07/05 08:52:21 mikem Exp $ + +#include + +#if RH_PLATFORM == RH_PLATFORM_ESP8266 +// This voltatile array is used in the ESP8266 platform to manage the interrupt +// service routines in a main loop, avoiding SPI calls inside the isr functions. +volatile bool flagIsr[3] = {false, false, false}; +#endif + +// Interrupt vectors for the 2 Arduino interrupt pins +// Each interrupt can be handled by a different instance of RH_RF22, allowing you to have +// 2 RH_RF22s per Arduino +RH_RF22* RH_RF22::_deviceForInterrupt[RH_RF22_NUM_INTERRUPTS] = {0, 0, 0}; +uint8_t RH_RF22::_interruptCount = 0; // Index into _deviceForInterrupt for next device + +// These are indexed by the values of ModemConfigChoice +// Canned modem configurations generated with +// http://www.hoperf.com/upload/rf/RH_RF22B%2023B%2031B%2042B%2043B%20Register%20Settings_RevB1-v5.xls +// Stored in flash (program) memory to save SRAM +PROGMEM static const RH_RF22::ModemConfig MODEM_CONFIG_TABLE[] = +{ + { 0x2b, 0x03, 0xf4, 0x20, 0x41, 0x89, 0x00, 0x36, 0x40, 0x0a, 0x1d, 0x80, 0x60, 0x10, 0x62, 0x2c, 0x00, 0x08 }, // Unmodulated carrier + { 0x2b, 0x03, 0xf4, 0x20, 0x41, 0x89, 0x00, 0x36, 0x40, 0x0a, 0x1d, 0x80, 0x60, 0x10, 0x62, 0x2c, 0x33, 0x08 }, // FSK, PN9 random modulation, 2, 5 + + // All the following enable FIFO with reg 71 + // 1c, 1f, 20, 21, 22, 23, 24, 25, 2c, 2d, 2e, 58, 69, 6e, 6f, 70, 71, 72 + // FSK, No Manchester, Max Rb err <1%, Xtal Tol 20ppm + { 0x2b, 0x03, 0xf4, 0x20, 0x41, 0x89, 0x00, 0x36, 0x40, 0x0a, 0x1d, 0x80, 0x60, 0x10, 0x62, 0x2c, 0x22, 0x08 }, // 2, 5 + { 0x1b, 0x03, 0x41, 0x60, 0x27, 0x52, 0x00, 0x07, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x13, 0xa9, 0x2c, 0x22, 0x3a }, // 2.4, 36 + { 0x1d, 0x03, 0xa1, 0x20, 0x4e, 0xa5, 0x00, 0x13, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x27, 0x52, 0x2c, 0x22, 0x48 }, // 4.8, 45 + { 0x1e, 0x03, 0xd0, 0x00, 0x9d, 0x49, 0x00, 0x45, 0x40, 0x0a, 0x20, 0x80, 0x60, 0x4e, 0xa5, 0x2c, 0x22, 0x48 }, // 9.6, 45 + { 0x2b, 0x03, 0x34, 0x02, 0x75, 0x25, 0x07, 0xff, 0x40, 0x0a, 0x1b, 0x80, 0x60, 0x9d, 0x49, 0x2c, 0x22, 0x0f }, // 19.2, 9.6 + { 0x02, 0x03, 0x68, 0x01, 0x3a, 0x93, 0x04, 0xd5, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x09, 0xd5, 0x0c, 0x22, 0x1f }, // 38.4, 19.6 + { 0x06, 0x03, 0x45, 0x01, 0xd7, 0xdc, 0x07, 0x6e, 0x40, 0x0a, 0x2d, 0x80, 0x60, 0x0e, 0xbf, 0x0c, 0x22, 0x2e }, // 57.6. 28.8 + { 0x8a, 0x03, 0x60, 0x01, 0x55, 0x55, 0x02, 0xad, 0x40, 0x0a, 0x50, 0x80, 0x60, 0x20, 0x00, 0x0c, 0x22, 0xc8 }, // 125, 125 + + { 0x2b, 0x03, 0xa1, 0xe0, 0x10, 0xc7, 0x00, 0x09, 0x40, 0x0a, 0x1d, 0x80, 0x60, 0x04, 0x32, 0x2c, 0x22, 0x04 }, // 512 baud, FSK, 2.5 Khz fd for POCSAG compatibility + { 0x27, 0x03, 0xa1, 0xe0, 0x10, 0xc7, 0x00, 0x06, 0x40, 0x0a, 0x1d, 0x80, 0x60, 0x04, 0x32, 0x2c, 0x22, 0x07 }, // 512 baud, FSK, 4.5 Khz fd for POCSAG compatibility + + // GFSK, No Manchester, Max Rb err <1%, Xtal Tol 20ppm + // These differ from FSK only in register 71, for the modulation type + { 0x2b, 0x03, 0xf4, 0x20, 0x41, 0x89, 0x00, 0x36, 0x40, 0x0a, 0x1d, 0x80, 0x60, 0x10, 0x62, 0x2c, 0x23, 0x08 }, // 2, 5 + { 0x1b, 0x03, 0x41, 0x60, 0x27, 0x52, 0x00, 0x07, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x13, 0xa9, 0x2c, 0x23, 0x3a }, // 2.4, 36 + { 0x1d, 0x03, 0xa1, 0x20, 0x4e, 0xa5, 0x00, 0x13, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x27, 0x52, 0x2c, 0x23, 0x48 }, // 4.8, 45 + { 0x1e, 0x03, 0xd0, 0x00, 0x9d, 0x49, 0x00, 0x45, 0x40, 0x0a, 0x20, 0x80, 0x60, 0x4e, 0xa5, 0x2c, 0x23, 0x48 }, // 9.6, 45 + { 0x2b, 0x03, 0x34, 0x02, 0x75, 0x25, 0x07, 0xff, 0x40, 0x0a, 0x1b, 0x80, 0x60, 0x9d, 0x49, 0x2c, 0x23, 0x0f }, // 19.2, 9.6 + { 0x02, 0x03, 0x68, 0x01, 0x3a, 0x93, 0x04, 0xd5, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x09, 0xd5, 0x0c, 0x23, 0x1f }, // 38.4, 19.6 + { 0x06, 0x03, 0x45, 0x01, 0xd7, 0xdc, 0x07, 0x6e, 0x40, 0x0a, 0x2d, 0x80, 0x60, 0x0e, 0xbf, 0x0c, 0x23, 0x2e }, // 57.6. 28.8 + { 0x8a, 0x03, 0x60, 0x01, 0x55, 0x55, 0x02, 0xad, 0x40, 0x0a, 0x50, 0x80, 0x60, 0x20, 0x00, 0x0c, 0x23, 0xc8 }, // 125, 125 + + // OOK, No Manchester, Max Rb err <1%, Xtal Tol 20ppm + { 0x51, 0x03, 0x68, 0x00, 0x3a, 0x93, 0x01, 0x3d, 0x2c, 0x11, 0x28, 0x80, 0x60, 0x09, 0xd5, 0x2c, 0x21, 0x08 }, // 1.2, 75 + { 0xc8, 0x03, 0x39, 0x20, 0x68, 0xdc, 0x00, 0x6b, 0x2a, 0x08, 0x2a, 0x80, 0x60, 0x13, 0xa9, 0x2c, 0x21, 0x08 }, // 2.4, 335 + { 0xc8, 0x03, 0x9c, 0x00, 0xd1, 0xb7, 0x00, 0xd4, 0x29, 0x04, 0x29, 0x80, 0x60, 0x27, 0x52, 0x2c, 0x21, 0x08 }, // 4.8, 335 + { 0xb8, 0x03, 0x9c, 0x00, 0xd1, 0xb7, 0x00, 0xd4, 0x28, 0x82, 0x29, 0x80, 0x60, 0x4e, 0xa5, 0x2c, 0x21, 0x08 }, // 9.6, 335 + { 0xa8, 0x03, 0x9c, 0x00, 0xd1, 0xb7, 0x00, 0xd4, 0x28, 0x41, 0x29, 0x80, 0x60, 0x9d, 0x49, 0x2c, 0x21, 0x08 }, // 19.2, 335 + { 0x98, 0x03, 0x9c, 0x00, 0xd1, 0xb7, 0x00, 0xd4, 0x28, 0x20, 0x29, 0x80, 0x60, 0x09, 0xd5, 0x0c, 0x21, 0x08 }, // 38.4, 335 + { 0x98, 0x03, 0x96, 0x00, 0xda, 0x74, 0x00, 0xdc, 0x28, 0x1f, 0x29, 0x80, 0x60, 0x0a, 0x3d, 0x0c, 0x21, 0x08 }, // 40, 335 +}; + +RH_RF22::RH_RF22(uint8_t slaveSelectPin, uint8_t interruptPin, RHGenericSPI& spi) + : + RHSPIDriver(slaveSelectPin, spi) +{ + _interruptPin = interruptPin; + _idleMode = RH_RF22_XTON; // Default idle state is READY mode + _polynomial = CRC_16_IBM; // Historical + _myInterruptIndex = 0xff; // Not allocated yet +} + +void RH_RF22::setIdleMode(uint8_t idleMode) +{ + _idleMode = idleMode; +} + +bool RH_RF22::init() +{ +#if RH_PLATFORM == RH_PLATFORM_ESP8266 + flagIsr[0] = false; + flagIsr[1] = false; + flagIsr[2] = false; +#endif + + if (!RHSPIDriver::init()) + return false; + + // Determine the interrupt number that corresponds to the interruptPin + int interruptNumber = digitalPinToInterrupt(_interruptPin); + if (interruptNumber == NOT_AN_INTERRUPT) + return false; +#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER + interruptNumber = _interruptPin; +#endif + + // Tell the low level SPI interface we will use SPI within this interrupt + spiUsingInterrupt(interruptNumber); + + // Software reset the device + reset(); + + // Get the device type and check it + // This also tests whether we are really connected to a device + _deviceType = spiRead(RH_RF22_REG_00_DEVICE_TYPE); + if ( _deviceType != RH_RF22_DEVICE_TYPE_RX_TRX + && _deviceType != RH_RF22_DEVICE_TYPE_TX) + { +// Serial.print("unknown device type: "); +// Serial.println(_deviceType); + return false; + } + + // Issue software reset to get all registers to default state + spiWrite(RH_RF22_REG_07_OPERATING_MODE1, RH_RF22_SWRES); + // Wait for chip ready + while (!(spiRead(RH_RF22_REG_04_INTERRUPT_STATUS2) & RH_RF22_ICHIPRDY)) + ; + + // Add by Adrien van den Bossche for Teensy + // ARM M4 requires the below. else pin interrupt doesn't work properly. + // On all other platforms, its innocuous, belt and braces + pinMode(_interruptPin, INPUT); + + // Enable interrupt output on the radio. Interrupt line will now go high until + // an interrupt occurs + spiWrite(RH_RF22_REG_05_INTERRUPT_ENABLE1, RH_RF22_ENTXFFAEM | RH_RF22_ENRXFFAFULL | RH_RF22_ENPKSENT | RH_RF22_ENPKVALID | RH_RF22_ENCRCERROR | RH_RF22_ENFFERR); + spiWrite(RH_RF22_REG_06_INTERRUPT_ENABLE2, RH_RF22_ENPREAVAL); + + // Set up interrupt handler + // Since there are a limited number of interrupt glue functions isr*() available, + // we can only support a limited number of devices simultaneously + // On some devices, notably most Arduinos, the interrupt pin passed in is actually the + // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping + // yourself based on knowledge of what Arduino board you are running on. + if (_myInterruptIndex == 0xff) + { + // First run, no interrupt allocated yet + if (_interruptCount <= RH_RF22_NUM_INTERRUPTS) + _myInterruptIndex = _interruptCount++; + else + return false; // Too many devices, not enough interrupt vectors + } + _deviceForInterrupt[_myInterruptIndex] = this; + if (_myInterruptIndex == 0) + attachInterrupt(interruptNumber, isr0, FALLING); + else if (_myInterruptIndex == 1) + attachInterrupt(interruptNumber, isr1, FALLING); + else if (_myInterruptIndex == 2) + attachInterrupt(interruptNumber, isr2, FALLING); + else + return false; // Too many devices, not enough interrupt vectors + + setModeIdle(); + + clearTxBuf(); + clearRxBuf(); + + // Most of these are the POR default + spiWrite(RH_RF22_REG_7D_TX_FIFO_CONTROL2, RH_RF22_TXFFAEM_THRESHOLD); + spiWrite(RH_RF22_REG_7E_RX_FIFO_CONTROL, RH_RF22_RXFFAFULL_THRESHOLD); + spiWrite(RH_RF22_REG_30_DATA_ACCESS_CONTROL, RH_RF22_ENPACRX | RH_RF22_ENPACTX | RH_RF22_ENCRC | (_polynomial & RH_RF22_CRC)); + + // Configure the message headers + // Here we set up the standard packet format for use by the RH_RF22 library + // 8 nibbles preamble + // 2 SYNC words 2d, d4 + // Header length 4 (to, from, id, flags) + // 1 octet of data length (0 to 255) + // 0 to 255 octets data + // 2 CRC octets as CRC16(IBM), computed on the header, length and data + // On reception the to address is check for validity against RH_RF22_REG_3F_CHECK_HEADER3 + // or the broadcast address of 0xff + // If no changes are made after this, the transmitted + // to address will be 0xff, the from address will be 0xff + // and all such messages will be accepted. This permits the out-of the box + // RH_RF22 config to act as an unaddresed, unreliable datagram service + spiWrite(RH_RF22_REG_32_HEADER_CONTROL1, RH_RF22_BCEN_HEADER3 | RH_RF22_HDCH_HEADER3); + spiWrite(RH_RF22_REG_33_HEADER_CONTROL2, RH_RF22_HDLEN_4 | RH_RF22_SYNCLEN_2); + + setPreambleLength(8); + uint8_t syncwords[] = { 0x2d, 0xd4 }; + setSyncWords(syncwords, sizeof(syncwords)); + setPromiscuous(false); + + // Set some defaults. An innocuous ISM frequency, and reasonable pull-in + setFrequency(434.0, 0.05); +// setFrequency(900.0); + // Some slow, reliable default speed and modulation + setModemConfig(FSK_Rb2_4Fd36); +// setModemConfig(FSK_Rb125Fd125); + setGpioReversed(false); + // Lowish power + setTxPower(RH_RF22_TXPOW_8DBM); + + return true; +} + +// C++ level interrupt handler for this instance +void RH_RF22::handleInterrupt() +{ + uint8_t _lastInterruptFlags[2]; + // Read the interrupt flags which clears the interrupt + spiBurstRead(RH_RF22_REG_03_INTERRUPT_STATUS1, _lastInterruptFlags, 2); + +#if 0 + // DEVELOPER TESTING ONLY + // Caution: Serial printing in this interrupt routine can cause mysterious crashes + Serial.print("interrupt "); + Serial.print(_lastInterruptFlags[0], HEX); + Serial.print(" "); + Serial.println(_lastInterruptFlags[1], HEX); + if (_lastInterruptFlags[0] == 0 && _lastInterruptFlags[1] == 0) + Serial.println("FUNNY: no interrupt!"); +#endif + +#if 0 + // DEVELOPER TESTING ONLY + // TESTING: fake an RH_RF22_IFFERROR + static int counter = 0; + if (_lastInterruptFlags[0] & RH_RF22_IPKSENT && counter++ == 10) + { + _lastInterruptFlags[0] = RH_RF22_IFFERROR; + counter = 0; + } +#endif + + if (_lastInterruptFlags[0] & RH_RF22_IFFERROR) + { + resetFifos(); // Clears the interrupt + if (_mode == RHModeTx) + restartTransmit(); + else if (_mode == RHModeRx) + clearRxBuf(); +// Serial.println("IFFERROR"); + } + // Caution, any delay here may cause a FF underflow or overflow + if (_lastInterruptFlags[0] & RH_RF22_ITXFFAEM) + { + // See if more data has to be loaded into the Tx FIFO + sendNextFragment(); +// Serial.println("ITXFFAEM"); + } + if (_lastInterruptFlags[0] & RH_RF22_IRXFFAFULL) + { + // Caution, any delay here may cause a FF overflow + // Read some data from the Rx FIFO + readNextFragment(); +// Serial.println("IRXFFAFULL"); + } + if (_lastInterruptFlags[0] & RH_RF22_IEXT) + { + // This is not enabled by the base code, but users may want to enable it + handleExternalInterrupt(); +// Serial.println("IEXT"); + } + if (_lastInterruptFlags[1] & RH_RF22_IWUT) + { + // This is not enabled by the base code, but users may want to enable it + handleWakeupTimerInterrupt(); +// Serial.println("IWUT"); + } + if (_lastInterruptFlags[0] & RH_RF22_IPKSENT) + { +// Serial.println("IPKSENT"); + _txGood++; + // Transmission does not automatically clear the tx buffer. + // Could retransmit if we wanted + // RH_RF22 transitions automatically to Idle + _mode = RHModeIdle; + } + if (_lastInterruptFlags[0] & RH_RF22_IPKVALID) + { + uint8_t len = spiRead(RH_RF22_REG_4B_RECEIVED_PACKET_LENGTH); +// Serial.println("IPKVALID"); + + // May have already read one or more fragments + // Get any remaining unread octets, based on the expected length + // First make sure we dont overflow the buffer in the case of a stupid length + // or partial bad receives + if ( len > RH_RF22_MAX_MESSAGE_LEN + || len < _bufLen) + { + _rxBad++; + _mode = RHModeIdle; + clearRxBuf(); + return; // Hmmm receiver buffer overflow. + } + + spiBurstRead(RH_RF22_REG_7F_FIFO_ACCESS, _buf + _bufLen, len - _bufLen); + _rxHeaderTo = spiRead(RH_RF22_REG_47_RECEIVED_HEADER3); + _rxHeaderFrom = spiRead(RH_RF22_REG_48_RECEIVED_HEADER2); + _rxHeaderId = spiRead(RH_RF22_REG_49_RECEIVED_HEADER1); + _rxHeaderFlags = spiRead(RH_RF22_REG_4A_RECEIVED_HEADER0); + _rxGood++; + _bufLen = len; + _mode = RHModeIdle; + _rxBufValid = true; + } + if (_lastInterruptFlags[0] & RH_RF22_ICRCERROR) + { +// Serial.println("ICRCERR"); + _rxBad++; + clearRxBuf(); + resetRxFifo(); + _mode = RHModeIdle; + setModeRx(); // Keep trying + } + if (_lastInterruptFlags[1] & RH_RF22_IPREAVAL) + { +// Serial.println("IPREAVAL"); + _lastRssi = (int8_t)(-120 + ((spiRead(RH_RF22_REG_26_RSSI) / 2))); + _lastPreambleTime = millis(); + resetRxFifo(); + clearRxBuf(); + } +} + +#if RH_PLATFORM == RH_PLATFORM_ESP8266 +void RH_RF22::loopIsr() +{ + if (flagIsr[0]) + { + if (_deviceForInterrupt[0]) + _deviceForInterrupt[0]->handleInterrupt(); + flagIsr[0] = false; + } + if (flagIsr[1]) + { + if (_deviceForInterrupt[1]) + _deviceForInterrupt[1]->handleInterrupt(); + flagIsr[1] = false; + } + if (flagIsr[2]) + { + if (_deviceForInterrupt[2]) + _deviceForInterrupt[2]->handleInterrupt(); + flagIsr[2] = false; + } +} +#endif + +// These are low level functions that call the interrupt handler for the correct +// instance of RH_RF22. +// 3 interrupts allows us to have 3 different devices +void RH_INTERRUPT_ATTR RH_RF22::isr0() +{ +#if RH_PLATFORM == RH_PLATFORM_ESP8266 + flagIsr[0] = true; +#else + if (_deviceForInterrupt[0]) + _deviceForInterrupt[0]->handleInterrupt(); +#endif +} +void RH_INTERRUPT_ATTR RH_RF22::isr1() +{ +#if RH_PLATFORM == RH_PLATFORM_ESP8266 + flagIsr[1] = true; +#else + if (_deviceForInterrupt[1]) + _deviceForInterrupt[1]->handleInterrupt(); +#endif +} +void RH_INTERRUPT_ATTR RH_RF22::isr2() +{ +#if RH_PLATFORM == RH_PLATFORM_ESP8266 + flagIsr[2] = true; +#else + if (_deviceForInterrupt[2]) + _deviceForInterrupt[2]->handleInterrupt(); +#endif +} + +void RH_RF22::reset() +{ + spiWrite(RH_RF22_REG_07_OPERATING_MODE1, RH_RF22_SWRES); + // Wait for it to settle + delay(1); // SWReset time is nominally 100usec +} + +uint8_t RH_RF22::statusRead() +{ + return spiRead(RH_RF22_REG_02_DEVICE_STATUS); +} + +uint8_t RH_RF22::adcRead(uint8_t adcsel, + uint8_t adcref , + uint8_t adcgain, + uint8_t adcoffs) +{ + uint8_t configuration = adcsel | adcref | (adcgain & RH_RF22_ADCGAIN); + spiWrite(RH_RF22_REG_0F_ADC_CONFIGURATION, configuration | RH_RF22_ADCSTART); + spiWrite(RH_RF22_REG_10_ADC_SENSOR_AMP_OFFSET, adcoffs); + + // Conversion time is nominally 305usec + // Wait for the DONE bit + while (!(spiRead(RH_RF22_REG_0F_ADC_CONFIGURATION) & RH_RF22_ADCDONE)) + ; + // Return the value + return spiRead(RH_RF22_REG_11_ADC_VALUE); +} + +uint8_t RH_RF22::temperatureRead(uint8_t tsrange, uint8_t tvoffs) +{ + spiWrite(RH_RF22_REG_12_TEMPERATURE_SENSOR_CALIBRATION, tsrange | RH_RF22_ENTSOFFS); + spiWrite(RH_RF22_REG_13_TEMPERATURE_VALUE_OFFSET, tvoffs); + return adcRead(RH_RF22_ADCSEL_INTERNAL_TEMPERATURE_SENSOR | RH_RF22_ADCREF_BANDGAP_VOLTAGE); +} + +uint16_t RH_RF22::wutRead() +{ + uint8_t buf[2]; + spiBurstRead(RH_RF22_REG_17_WAKEUP_TIMER_VALUE1, buf, 2); + return ((uint16_t)buf[0] << 8) | buf[1]; // Dont rely on byte order +} + +// RFM-22 doc appears to be wrong: WUT for wtm = 10000, r, = 0, d = 0 is about 1 sec +void RH_RF22::setWutPeriod(uint16_t wtm, uint8_t wtr, uint8_t wtd) +{ + uint8_t period[3]; + + period[0] = ((wtr & 0xf) << 2) | (wtd & 0x3); + period[1] = wtm >> 8; + period[2] = wtm & 0xff; + spiBurstWrite(RH_RF22_REG_14_WAKEUP_TIMER_PERIOD1, period, sizeof(period)); +} + +// Returns true if centre + (fhch * fhs) is within limits +// Caution, different versions of the RH_RF22 support different max freq +// so YMMV +bool RH_RF22::setFrequency(float centre, float afcPullInRange) +{ + uint8_t fbsel = RH_RF22_SBSEL; + uint8_t afclimiter; + if (centre < 240.0 || centre > 960.0) // 930.0 for early silicon + return false; + if (centre >= 480.0) + { + if (afcPullInRange < 0.0 || afcPullInRange > 0.318750) + return false; + centre /= 2; + fbsel |= RH_RF22_HBSEL; + afclimiter = afcPullInRange * 1000000.0 / 1250.0; + } + else + { + if (afcPullInRange < 0.0 || afcPullInRange > 0.159375) + return false; + afclimiter = afcPullInRange * 1000000.0 / 625.0; + } + centre /= 10.0; + float integerPart = floor(centre); + float fractionalPart = centre - integerPart; + + uint8_t fb = (uint8_t)integerPart - 24; // Range 0 to 23 + fbsel |= fb; + uint16_t fc = fractionalPart * 64000; + spiWrite(RH_RF22_REG_73_FREQUENCY_OFFSET1, 0); // REVISIT + spiWrite(RH_RF22_REG_74_FREQUENCY_OFFSET2, 0); + spiWrite(RH_RF22_REG_75_FREQUENCY_BAND_SELECT, fbsel); + spiWrite(RH_RF22_REG_76_NOMINAL_CARRIER_FREQUENCY1, fc >> 8); + spiWrite(RH_RF22_REG_77_NOMINAL_CARRIER_FREQUENCY0, fc & 0xff); + spiWrite(RH_RF22_REG_2A_AFC_LIMITER, afclimiter); + return !(statusRead() & RH_RF22_FREQERR); +} + +// Step size in 10kHz increments +// Returns true if centre + (fhch * fhs) is within limits +bool RH_RF22::setFHStepSize(uint8_t fhs) +{ + spiWrite(RH_RF22_REG_7A_FREQUENCY_HOPPING_STEP_SIZE, fhs); + return !(statusRead() & RH_RF22_FREQERR); +} + +// Adds fhch * fhs to centre frequency +// Returns true if centre + (fhch * fhs) is within limits +bool RH_RF22::setFHChannel(uint8_t fhch) +{ + spiWrite(RH_RF22_REG_79_FREQUENCY_HOPPING_CHANNEL_SELECT, fhch); + return !(statusRead() & RH_RF22_FREQERR); +} + +uint8_t RH_RF22::rssiRead() +{ + return spiRead(RH_RF22_REG_26_RSSI); +} + +uint8_t RH_RF22::ezmacStatusRead() +{ + return spiRead(RH_RF22_REG_31_EZMAC_STATUS); +} + +void RH_RF22::setOpMode(uint8_t mode) +{ + spiWrite(RH_RF22_REG_07_OPERATING_MODE1, mode); +} + +void RH_RF22::setModeIdle() +{ + if (_mode != RHModeIdle) + { + setOpMode(_idleMode); + _mode = RHModeIdle; + } +} + +bool RH_RF22::sleep() +{ + if (_mode != RHModeSleep) + { + setOpMode(0); + _mode = RHModeSleep; + } + return true; +} + +void RH_RF22::setModeRx() +{ + if (_mode != RHModeRx) + { + setOpMode(_idleMode | RH_RF22_RXON); + _mode = RHModeRx; + } +} + +void RH_RF22::setModeTx() +{ + if (_mode != RHModeTx) + { + setOpMode(_idleMode | RH_RF22_TXON); + // Hmmm, if you dont clear the RX FIFO here, then it appears that going + // to transmit mode in the middle of a receive can corrupt the + // RX FIFO + resetRxFifo(); + _mode = RHModeTx; + } +} + +void RH_RF22::setTxPower(uint8_t power) +{ + spiWrite(RH_RF22_REG_6D_TX_POWER, power | RH_RF22_LNA_SW); // On RF23, LNA_SW must be set. +} + +// Sets registers from a canned modem configuration structure +void RH_RF22::setModemRegisters(const ModemConfig* config) +{ + spiWrite(RH_RF22_REG_1C_IF_FILTER_BANDWIDTH, config->reg_1c); + spiWrite(RH_RF22_REG_1F_CLOCK_RECOVERY_GEARSHIFT_OVERRIDE, config->reg_1f); + spiBurstWrite(RH_RF22_REG_20_CLOCK_RECOVERY_OVERSAMPLING_RATE, &config->reg_20, 6); + spiBurstWrite(RH_RF22_REG_2C_OOK_COUNTER_VALUE_1, &config->reg_2c, 3); + spiWrite(RH_RF22_REG_58_CHARGE_PUMP_CURRENT_TRIMMING, config->reg_58); + spiWrite(RH_RF22_REG_69_AGC_OVERRIDE1, config->reg_69); + spiBurstWrite(RH_RF22_REG_6E_TX_DATA_RATE1, &config->reg_6e, 5); +} + +// Set one of the canned FSK Modem configs +// Returns true if its a valid choice +bool RH_RF22::setModemConfig(ModemConfigChoice index) +{ + if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig))) + return false; + + RH_RF22::ModemConfig cfg; + memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(RH_RF22::ModemConfig)); + setModemRegisters(&cfg); + + return true; +} + +// REVISIT: top bit is in Header Control 2 0x33 +void RH_RF22::setPreambleLength(uint8_t nibbles) +{ + spiWrite(RH_RF22_REG_34_PREAMBLE_LENGTH, nibbles); +} + +// Caution doesnt set sync word len in Header Control 2 0x33 +void RH_RF22::setSyncWords(const uint8_t* syncWords, uint8_t len) +{ + spiBurstWrite(RH_RF22_REG_36_SYNC_WORD3, syncWords, len); +} + +void RH_RF22::clearRxBuf() +{ + ATOMIC_BLOCK_START; + _bufLen = 0; + _rxBufValid = false; + ATOMIC_BLOCK_END; +} + +bool RH_RF22::available() +{ + if (!_rxBufValid) + { +#if RH_PLATFORM == RH_PLATFORM_ESP8266 + loopIsr(); +#endif + if (_mode == RHModeTx) + return false; + setModeRx(); // Make sure we are receiving + YIELD; // Wait for any previous transmit to finish + } + return _rxBufValid; +} + +#if RH_PLATFORM == RH_PLATFORM_ESP8266 +bool RH_RF22::waitPacketSent() +{ + while (_mode == RHModeTx) + { + loopIsr(); + YIELD; // Make sure the watchdog is fed + } + return true; +} +#endif + +bool RH_RF22::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + + if (buf && len) + { + ATOMIC_BLOCK_START; + if (*len > _bufLen) + *len = _bufLen; + memcpy(buf, _buf, *len); + ATOMIC_BLOCK_END; + } + clearRxBuf(); +// printBuffer("recv:", buf, *len); + return true; +} + +void RH_RF22::clearTxBuf() +{ + ATOMIC_BLOCK_START; + _bufLen = 0; + _txBufSentIndex = 0; + ATOMIC_BLOCK_END; +} + +void RH_RF22::startTransmit() +{ + sendNextFragment(); // Actually the first fragment + spiWrite(RH_RF22_REG_3E_PACKET_LENGTH, _bufLen); // Total length that will be sent + setModeTx(); // Start the transmitter, turns off the receiver +} + +// Restart the transmission of a packet that had a problem +void RH_RF22::restartTransmit() +{ + _mode = RHModeIdle; + _txBufSentIndex = 0; +// Serial.println("Restart"); + startTransmit(); +} + +bool RH_RF22::send(const uint8_t* data, uint8_t len) +{ + bool ret = true; + waitPacketSent(); + + if (!waitCAD()) + return false; // Check channel activity + + ATOMIC_BLOCK_START; + spiWrite(RH_RF22_REG_3A_TRANSMIT_HEADER3, _txHeaderTo); + spiWrite(RH_RF22_REG_3B_TRANSMIT_HEADER2, _txHeaderFrom); + spiWrite(RH_RF22_REG_3C_TRANSMIT_HEADER1, _txHeaderId); + spiWrite(RH_RF22_REG_3D_TRANSMIT_HEADER0, _txHeaderFlags); + if (!fillTxBuf(data, len)) + ret = false; + else + startTransmit(); + ATOMIC_BLOCK_END; +// printBuffer("send:", data, len); + return ret; +} + +bool RH_RF22::fillTxBuf(const uint8_t* data, uint8_t len) +{ + clearTxBuf(); + if (!len) + return false; + return appendTxBuf(data, len); +} + +bool RH_RF22::appendTxBuf(const uint8_t* data, uint8_t len) +{ + if (((uint16_t)_bufLen + len) > RH_RF22_MAX_MESSAGE_LEN) + return false; + ATOMIC_BLOCK_START; + memcpy(_buf + _bufLen, data, len); + _bufLen += len; + ATOMIC_BLOCK_END; +// printBuffer("txbuf:", _buf, _bufLen); + return true; +} + +// Assumption: there is currently <= RH_RF22_TXFFAEM_THRESHOLD bytes in the Tx FIFO +void RH_RF22::sendNextFragment() +{ + if (_txBufSentIndex < _bufLen) + { + // Some left to send? + uint8_t len = _bufLen - _txBufSentIndex; + // But dont send too much + if (len > (RH_RF22_FIFO_SIZE - RH_RF22_TXFFAEM_THRESHOLD - 1)) + len = (RH_RF22_FIFO_SIZE - RH_RF22_TXFFAEM_THRESHOLD - 1); + spiBurstWrite(RH_RF22_REG_7F_FIFO_ACCESS, _buf + _txBufSentIndex, len); +// printBuffer("frag:", _buf + _txBufSentIndex, len); + _txBufSentIndex += len; + } +} + +// Assumption: there are at least RH_RF22_RXFFAFULL_THRESHOLD in the RX FIFO +// That means it should only be called after a RXFFAFULL interrupt +void RH_RF22::readNextFragment() +{ + if (((uint16_t)_bufLen + RH_RF22_RXFFAFULL_THRESHOLD) > RH_RF22_MAX_MESSAGE_LEN) + return; // Hmmm receiver overflow. Should never occur + + // Read the RH_RF22_RXFFAFULL_THRESHOLD octets that should be there + spiBurstRead(RH_RF22_REG_7F_FIFO_ACCESS, _buf + _bufLen, RH_RF22_RXFFAFULL_THRESHOLD); + _bufLen += RH_RF22_RXFFAFULL_THRESHOLD; +} + +// Clear the FIFOs +void RH_RF22::resetFifos() +{ + spiWrite(RH_RF22_REG_08_OPERATING_MODE2, RH_RF22_FFCLRRX | RH_RF22_FFCLRTX); + spiWrite(RH_RF22_REG_08_OPERATING_MODE2, 0); +} + +// Clear the Rx FIFO +void RH_RF22::resetRxFifo() +{ + spiWrite(RH_RF22_REG_08_OPERATING_MODE2, RH_RF22_FFCLRRX); + spiWrite(RH_RF22_REG_08_OPERATING_MODE2, 0); + _rxBufValid = false; +} + +// CLear the TX FIFO +void RH_RF22::resetTxFifo() +{ + spiWrite(RH_RF22_REG_08_OPERATING_MODE2, RH_RF22_FFCLRTX); + spiWrite(RH_RF22_REG_08_OPERATING_MODE2, 0); +} + +// Default implmentation does nothing. Override if you wish +void RH_RF22::handleExternalInterrupt() +{ +} + +// Default implmentation does nothing. Override if you wish +void RH_RF22::handleWakeupTimerInterrupt() +{ +} + +void RH_RF22::setPromiscuous(bool promiscuous) +{ + RHSPIDriver::setPromiscuous(promiscuous); + spiWrite(RH_RF22_REG_43_HEADER_ENABLE3, promiscuous ? 0x00 : 0xff); +} + +bool RH_RF22::setCRCPolynomial(CRCPolynomial polynomial) +{ + if (polynomial >= CRC_CCITT && + polynomial <= CRC_Biacheva) + { + _polynomial = polynomial; + return true; + } + else + return false; +} + +uint8_t RH_RF22::maxMessageLength() +{ + return RH_RF22_MAX_MESSAGE_LEN; +} + +void RH_RF22::setThisAddress(uint8_t thisAddress) +{ + RHSPIDriver::setThisAddress(thisAddress); + spiWrite(RH_RF22_REG_3F_CHECK_HEADER3, thisAddress); +} + +uint32_t RH_RF22::getLastPreambleTime() +{ + return _lastPreambleTime; +} + +void RH_RF22::setGpioReversed(bool gpioReversed) +{ + // Ensure the antenna can be switched automatically according to transmit and receive + // This assumes GPIO0(out) is connected to TX_ANT(in) to enable tx antenna during transmit + // This assumes GPIO1(out) is connected to RX_ANT(in) to enable rx antenna during receive + if (gpioReversed) + { + // Reversed for HAB-RFM22B-BOA HAB-RFM22B-BO, also Si4432 sold by Dorji.com via Tindie.com. + spiWrite(RH_RF22_REG_0B_GPIO_CONFIGURATION0, 0x15) ; // RX state + spiWrite(RH_RF22_REG_0C_GPIO_CONFIGURATION1, 0x12) ; // TX state + } + else + { + spiWrite(RH_RF22_REG_0B_GPIO_CONFIGURATION0, 0x12) ; // TX state + spiWrite(RH_RF22_REG_0C_GPIO_CONFIGURATION1, 0x15) ; // RX state + } +} + diff --git a/RH_RF22.h b/RH_RF22.h new file mode 100644 index 0000000..6479003 --- /dev/null +++ b/RH_RF22.h @@ -0,0 +1,1414 @@ +// RH_RF22.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RH_RF22.h,v 1.39 2020/08/04 09:02:14 mikem Exp $ +// + +#ifndef RH_RF22_h +#define RH_RF22_h + +#include +#include + +// This is the maximum number of interrupts the library can support +// Most Arduinos can handle 2, Megas can handle more +#define RH_RF22_NUM_INTERRUPTS 3 + +// This is the bit in the SPI address that marks it as a write +#define RH_RF22_SPI_WRITE_MASK 0x80 + +// This is the maximum message length that can be supported by this library. Limited by +// the single message length octet in the header. +// Yes, 255 is correct even though the FIFO size in the RF22 is only +// 64 octets. We use interrupts to refill the Tx FIFO during transmission and to empty the +// Rx FIFO during reception +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +#ifndef RH_RF22_MAX_MESSAGE_LEN +//#define RH_RF22_MAX_MESSAGE_LEN 255 +#define RH_RF22_MAX_MESSAGE_LEN 50 +#endif + +// Max number of octets the RF22 Rx and Tx FIFOs can hold +#define RH_RF22_FIFO_SIZE 64 + +// These values we set for FIFO thresholds (4, 55) are actually the same as the POR values +#define RH_RF22_TXFFAEM_THRESHOLD 4 +#define RH_RF22_RXFFAFULL_THRESHOLD 55 + +// Number of registers to be passed to setModemConfig(). Obsolete. +#define RH_RF22_NUM_MODEM_CONFIG_REGS 18 + +// Register names +#define RH_RF22_REG_00_DEVICE_TYPE 0x00 +#define RH_RF22_REG_01_VERSION_CODE 0x01 +#define RH_RF22_REG_02_DEVICE_STATUS 0x02 +#define RH_RF22_REG_03_INTERRUPT_STATUS1 0x03 +#define RH_RF22_REG_04_INTERRUPT_STATUS2 0x04 +#define RH_RF22_REG_05_INTERRUPT_ENABLE1 0x05 +#define RH_RF22_REG_06_INTERRUPT_ENABLE2 0x06 +#define RH_RF22_REG_07_OPERATING_MODE1 0x07 +#define RH_RF22_REG_08_OPERATING_MODE2 0x08 +#define RH_RF22_REG_09_OSCILLATOR_LOAD_CAPACITANCE 0x09 +#define RH_RF22_REG_0A_UC_OUTPUT_CLOCK 0x0a +#define RH_RF22_REG_0B_GPIO_CONFIGURATION0 0x0b +#define RH_RF22_REG_0C_GPIO_CONFIGURATION1 0x0c +#define RH_RF22_REG_0D_GPIO_CONFIGURATION2 0x0d +#define RH_RF22_REG_0E_IO_PORT_CONFIGURATION 0x0e +#define RH_RF22_REG_0F_ADC_CONFIGURATION 0x0f +#define RH_RF22_REG_10_ADC_SENSOR_AMP_OFFSET 0x10 +#define RH_RF22_REG_11_ADC_VALUE 0x11 +#define RH_RF22_REG_12_TEMPERATURE_SENSOR_CALIBRATION 0x12 +#define RH_RF22_REG_13_TEMPERATURE_VALUE_OFFSET 0x13 +#define RH_RF22_REG_14_WAKEUP_TIMER_PERIOD1 0x14 +#define RH_RF22_REG_15_WAKEUP_TIMER_PERIOD2 0x15 +#define RH_RF22_REG_16_WAKEUP_TIMER_PERIOD3 0x16 +#define RH_RF22_REG_17_WAKEUP_TIMER_VALUE1 0x17 +#define RH_RF22_REG_18_WAKEUP_TIMER_VALUE2 0x18 +#define RH_RF22_REG_19_LDC_MODE_DURATION 0x19 +#define RH_RF22_REG_1A_LOW_BATTERY_DETECTOR_THRESHOLD 0x1a +#define RH_RF22_REG_1B_BATTERY_VOLTAGE_LEVEL 0x1b +#define RH_RF22_REG_1C_IF_FILTER_BANDWIDTH 0x1c +#define RH_RF22_REG_1D_AFC_LOOP_GEARSHIFT_OVERRIDE 0x1d +#define RH_RF22_REG_1E_AFC_TIMING_CONTROL 0x1e +#define RH_RF22_REG_1F_CLOCK_RECOVERY_GEARSHIFT_OVERRIDE 0x1f +#define RH_RF22_REG_20_CLOCK_RECOVERY_OVERSAMPLING_RATE 0x20 +#define RH_RF22_REG_21_CLOCK_RECOVERY_OFFSET2 0x21 +#define RH_RF22_REG_22_CLOCK_RECOVERY_OFFSET1 0x22 +#define RH_RF22_REG_23_CLOCK_RECOVERY_OFFSET0 0x23 +#define RH_RF22_REG_24_CLOCK_RECOVERY_TIMING_LOOP_GAIN1 0x24 +#define RH_RF22_REG_25_CLOCK_RECOVERY_TIMING_LOOP_GAIN0 0x25 +#define RH_RF22_REG_26_RSSI 0x26 +#define RH_RF22_REG_27_RSSI_THRESHOLD 0x27 +#define RH_RF22_REG_28_ANTENNA_DIVERSITY1 0x28 +#define RH_RF22_REG_29_ANTENNA_DIVERSITY2 0x29 +#define RH_RF22_REG_2A_AFC_LIMITER 0x2a +#define RH_RF22_REG_2B_AFC_CORRECTION_READ 0x2b +#define RH_RF22_REG_2C_OOK_COUNTER_VALUE_1 0x2c +#define RH_RF22_REG_2D_OOK_COUNTER_VALUE_2 0x2d +#define RH_RF22_REG_2E_SLICER_PEAK_HOLD 0x2e +#define RH_RF22_REG_30_DATA_ACCESS_CONTROL 0x30 +#define RH_RF22_REG_31_EZMAC_STATUS 0x31 +#define RH_RF22_REG_32_HEADER_CONTROL1 0x32 +#define RH_RF22_REG_33_HEADER_CONTROL2 0x33 +#define RH_RF22_REG_34_PREAMBLE_LENGTH 0x34 +#define RH_RF22_REG_35_PREAMBLE_DETECTION_CONTROL1 0x35 +#define RH_RF22_REG_36_SYNC_WORD3 0x36 +#define RH_RF22_REG_37_SYNC_WORD2 0x37 +#define RH_RF22_REG_38_SYNC_WORD1 0x38 +#define RH_RF22_REG_39_SYNC_WORD0 0x39 +#define RH_RF22_REG_3A_TRANSMIT_HEADER3 0x3a +#define RH_RF22_REG_3B_TRANSMIT_HEADER2 0x3b +#define RH_RF22_REG_3C_TRANSMIT_HEADER1 0x3c +#define RH_RF22_REG_3D_TRANSMIT_HEADER0 0x3d +#define RH_RF22_REG_3E_PACKET_LENGTH 0x3e +#define RH_RF22_REG_3F_CHECK_HEADER3 0x3f +#define RH_RF22_REG_40_CHECK_HEADER2 0x40 +#define RH_RF22_REG_41_CHECK_HEADER1 0x41 +#define RH_RF22_REG_42_CHECK_HEADER0 0x42 +#define RH_RF22_REG_43_HEADER_ENABLE3 0x43 +#define RH_RF22_REG_44_HEADER_ENABLE2 0x44 +#define RH_RF22_REG_45_HEADER_ENABLE1 0x45 +#define RH_RF22_REG_46_HEADER_ENABLE0 0x46 +#define RH_RF22_REG_47_RECEIVED_HEADER3 0x47 +#define RH_RF22_REG_48_RECEIVED_HEADER2 0x48 +#define RH_RF22_REG_49_RECEIVED_HEADER1 0x49 +#define RH_RF22_REG_4A_RECEIVED_HEADER0 0x4a +#define RH_RF22_REG_4B_RECEIVED_PACKET_LENGTH 0x4b +#define RH_RF22_REG_50_ANALOG_TEST_BUS_SELECT 0x50 +#define RH_RF22_REG_51_DIGITAL_TEST_BUS_SELECT 0x51 +#define RH_RF22_REG_52_TX_RAMP_CONTROL 0x52 +#define RH_RF22_REG_53_PLL_TUNE_TIME 0x53 +#define RH_RF22_REG_55_CALIBRATION_CONTROL 0x55 +#define RH_RF22_REG_56_MODEM_TEST 0x56 +#define RH_RF22_REG_57_CHARGE_PUMP_TEST 0x57 +#define RH_RF22_REG_58_CHARGE_PUMP_CURRENT_TRIMMING 0x58 +#define RH_RF22_REG_59_DIVIDER_CURRENT_TRIMMING 0x59 +#define RH_RF22_REG_5A_VCO_CURRENT_TRIMMING 0x5a +#define RH_RF22_REG_5B_VCO_CALIBRATION 0x5b +#define RH_RF22_REG_5C_SYNTHESIZER_TEST 0x5c +#define RH_RF22_REG_5D_BLOCK_ENABLE_OVERRIDE1 0x5d +#define RH_RF22_REG_5E_BLOCK_ENABLE_OVERRIDE2 0x5e +#define RH_RF22_REG_5F_BLOCK_ENABLE_OVERRIDE3 0x5f +#define RH_RF22_REG_60_CHANNEL_FILTER_COEFFICIENT_ADDRESS 0x60 +#define RH_RF22_REG_61_CHANNEL_FILTER_COEFFICIENT_VALUE 0x61 +#define RH_RF22_REG_62_CRYSTAL_OSCILLATOR_POR_CONTROL 0x62 +#define RH_RF22_REG_63_RC_OSCILLATOR_COARSE_CALIBRATION 0x63 +#define RH_RF22_REG_64_RC_OSCILLATOR_FINE_CALIBRATION 0x64 +#define RH_RF22_REG_65_LDO_CONTROL_OVERRIDE 0x65 +#define RH_RF22_REG_66_LDO_LEVEL_SETTINGS 0x66 +#define RH_RF22_REG_67_DELTA_SIGMA_ADC_TUNING1 0x67 +#define RH_RF22_REG_68_DELTA_SIGMA_ADC_TUNING2 0x68 +#define RH_RF22_REG_69_AGC_OVERRIDE1 0x69 +#define RH_RF22_REG_6A_AGC_OVERRIDE2 0x6a +#define RH_RF22_REG_6B_GFSK_FIR_FILTER_COEFFICIENT_ADDRESS 0x6b +#define RH_RF22_REG_6C_GFSK_FIR_FILTER_COEFFICIENT_VALUE 0x6c +#define RH_RF22_REG_6D_TX_POWER 0x6d +#define RH_RF22_REG_6E_TX_DATA_RATE1 0x6e +#define RH_RF22_REG_6F_TX_DATA_RATE0 0x6f +#define RH_RF22_REG_70_MODULATION_CONTROL1 0x70 +#define RH_RF22_REG_71_MODULATION_CONTROL2 0x71 +#define RH_RF22_REG_72_FREQUENCY_DEVIATION 0x72 +#define RH_RF22_REG_73_FREQUENCY_OFFSET1 0x73 +#define RH_RF22_REG_74_FREQUENCY_OFFSET2 0x74 +#define RH_RF22_REG_75_FREQUENCY_BAND_SELECT 0x75 +#define RH_RF22_REG_76_NOMINAL_CARRIER_FREQUENCY1 0x76 +#define RH_RF22_REG_77_NOMINAL_CARRIER_FREQUENCY0 0x77 +#define RH_RF22_REG_79_FREQUENCY_HOPPING_CHANNEL_SELECT 0x79 +#define RH_RF22_REG_7A_FREQUENCY_HOPPING_STEP_SIZE 0x7a +#define RH_RF22_REG_7C_TX_FIFO_CONTROL1 0x7c +#define RH_RF22_REG_7D_TX_FIFO_CONTROL2 0x7d +#define RH_RF22_REG_7E_RX_FIFO_CONTROL 0x7e +#define RH_RF22_REG_7F_FIFO_ACCESS 0x7f + +// These register masks etc are named wherever possible +// corresponding to the bit and field names in the RF-22 Manual +// RH_RF22_REG_00_DEVICE_TYPE 0x00 +#define RH_RF22_DEVICE_TYPE_RX_TRX 0x08 +#define RH_RF22_DEVICE_TYPE_TX 0x07 + +// RH_RF22_REG_02_DEVICE_STATUS 0x02 +#define RH_RF22_FFOVL 0x80 +#define RH_RF22_FFUNFL 0x40 +#define RH_RF22_RXFFEM 0x20 +#define RH_RF22_HEADERR 0x10 +#define RH_RF22_FREQERR 0x08 +#define RH_RF22_LOCKDET 0x04 +#define RH_RF22_CPS 0x03 +#define RH_RF22_CPS_IDLE 0x00 +#define RH_RF22_CPS_RX 0x01 +#define RH_RF22_CPS_TX 0x10 + +// RH_RF22_REG_03_INTERRUPT_STATUS1 0x03 +#define RH_RF22_IFFERROR 0x80 +#define RH_RF22_ITXFFAFULL 0x40 +#define RH_RF22_ITXFFAEM 0x20 +#define RH_RF22_IRXFFAFULL 0x10 +#define RH_RF22_IEXT 0x08 +#define RH_RF22_IPKSENT 0x04 +#define RH_RF22_IPKVALID 0x02 +#define RH_RF22_ICRCERROR 0x01 + +// RH_RF22_REG_04_INTERRUPT_STATUS2 0x04 +#define RH_RF22_ISWDET 0x80 +#define RH_RF22_IPREAVAL 0x40 +#define RH_RF22_IPREAINVAL 0x20 +#define RH_RF22_IRSSI 0x10 +#define RH_RF22_IWUT 0x08 +#define RH_RF22_ILBD 0x04 +#define RH_RF22_ICHIPRDY 0x02 +#define RH_RF22_IPOR 0x01 + +// RH_RF22_REG_05_INTERRUPT_ENABLE1 0x05 +#define RH_RF22_ENFFERR 0x80 +#define RH_RF22_ENTXFFAFULL 0x40 +#define RH_RF22_ENTXFFAEM 0x20 +#define RH_RF22_ENRXFFAFULL 0x10 +#define RH_RF22_ENEXT 0x08 +#define RH_RF22_ENPKSENT 0x04 +#define RH_RF22_ENPKVALID 0x02 +#define RH_RF22_ENCRCERROR 0x01 + +// RH_RF22_REG_06_INTERRUPT_ENABLE2 0x06 +#define RH_RF22_ENSWDET 0x80 +#define RH_RF22_ENPREAVAL 0x40 +#define RH_RF22_ENPREAINVAL 0x20 +#define RH_RF22_ENRSSI 0x10 +#define RH_RF22_ENWUT 0x08 +#define RH_RF22_ENLBDI 0x04 +#define RH_RF22_ENCHIPRDY 0x02 +#define RH_RF22_ENPOR 0x01 + +// RH_RF22_REG_07_OPERATING_MODE 0x07 +#define RH_RF22_SWRES 0x80 +#define RH_RF22_ENLBD 0x40 +#define RH_RF22_ENWT 0x20 +#define RH_RF22_X32KSEL 0x10 +#define RH_RF22_TXON 0x08 +#define RH_RF22_RXON 0x04 +#define RH_RF22_PLLON 0x02 +#define RH_RF22_XTON 0x01 + +// RH_RF22_REG_08_OPERATING_MODE2 0x08 +#define RH_RF22_ANTDIV 0xc0 +#define RH_RF22_RXMPK 0x10 +#define RH_RF22_AUTOTX 0x08 +#define RH_RF22_ENLDM 0x04 +#define RH_RF22_FFCLRRX 0x02 +#define RH_RF22_FFCLRTX 0x01 + +// RH_RF22_REG_0F_ADC_CONFIGURATION 0x0f +#define RH_RF22_ADCSTART 0x80 +#define RH_RF22_ADCDONE 0x80 +#define RH_RF22_ADCSEL 0x70 +#define RH_RF22_ADCSEL_INTERNAL_TEMPERATURE_SENSOR 0x00 +#define RH_RF22_ADCSEL_GPIO0_SINGLE_ENDED 0x10 +#define RH_RF22_ADCSEL_GPIO1_SINGLE_ENDED 0x20 +#define RH_RF22_ADCSEL_GPIO2_SINGLE_ENDED 0x30 +#define RH_RF22_ADCSEL_GPIO0_GPIO1_DIFFERENTIAL 0x40 +#define RH_RF22_ADCSEL_GPIO1_GPIO2_DIFFERENTIAL 0x50 +#define RH_RF22_ADCSEL_GPIO0_GPIO2_DIFFERENTIAL 0x60 +#define RH_RF22_ADCSEL_GND 0x70 +#define RH_RF22_ADCREF 0x0c +#define RH_RF22_ADCREF_BANDGAP_VOLTAGE 0x00 +#define RH_RF22_ADCREF_VDD_ON_3 0x08 +#define RH_RF22_ADCREF_VDD_ON_2 0x0c +#define RH_RF22_ADCGAIN 0x03 + +// RH_RF22_REG_10_ADC_SENSOR_AMP_OFFSET 0x10 +#define RH_RF22_ADCOFFS 0x0f + +// RH_RF22_REG_12_TEMPERATURE_SENSOR_CALIBRATION 0x12 +#define RH_RF22_TSRANGE 0xc0 +#define RH_RF22_TSRANGE_M64_64C 0x00 +#define RH_RF22_TSRANGE_M64_192C 0x40 +#define RH_RF22_TSRANGE_0_128C 0x80 +#define RH_RF22_TSRANGE_M40_216F 0xc0 +#define RH_RF22_ENTSOFFS 0x20 +#define RH_RF22_ENTSTRIM 0x10 +#define RH_RF22_TSTRIM 0x0f + +// RH_RF22_REG_14_WAKEUP_TIMER_PERIOD1 0x14 +#define RH_RF22_WTR 0x3c +#define RH_RF22_WTD 0x03 + +// RH_RF22_REG_1D_AFC_LOOP_GEARSHIFT_OVERRIDE 0x1d +#define RH_RF22_AFBCD 0x80 +#define RH_RF22_ENAFC 0x40 +#define RH_RF22_AFCGEARH 0x38 +#define RH_RF22_AFCGEARL 0x07 + +// RH_RF22_REG_1E_AFC_TIMING_CONTROL 0x1e +#define RH_RF22_SWAIT_TIMER 0xc0 +#define RH_RF22_SHWAIT 0x38 +#define RH_RF22_ANWAIT 0x07 + +// RH_RF22_REG_30_DATA_ACCESS_CONTROL 0x30 +#define RH_RF22_ENPACRX 0x80 +#define RH_RF22_MSBFRST 0x00 +#define RH_RF22_LSBFRST 0x40 +#define RH_RF22_CRCHDRS 0x00 +#define RH_RF22_CRCDONLY 0x20 +#define RH_RF22_SKIP2PH 0x10 +#define RH_RF22_ENPACTX 0x08 +#define RH_RF22_ENCRC 0x04 +#define RH_RF22_CRC 0x03 +#define RH_RF22_CRC_CCITT 0x00 +#define RH_RF22_CRC_CRC_16_IBM 0x01 +#define RH_RF22_CRC_IEC_16 0x02 +#define RH_RF22_CRC_BIACHEVA 0x03 + +// RH_RF22_REG_32_HEADER_CONTROL1 0x32 +#define RH_RF22_BCEN 0xf0 +#define RH_RF22_BCEN_NONE 0x00 +#define RH_RF22_BCEN_HEADER0 0x10 +#define RH_RF22_BCEN_HEADER1 0x20 +#define RH_RF22_BCEN_HEADER2 0x40 +#define RH_RF22_BCEN_HEADER3 0x80 +#define RH_RF22_HDCH 0x0f +#define RH_RF22_HDCH_NONE 0x00 +#define RH_RF22_HDCH_HEADER0 0x01 +#define RH_RF22_HDCH_HEADER1 0x02 +#define RH_RF22_HDCH_HEADER2 0x04 +#define RH_RF22_HDCH_HEADER3 0x08 + +// RH_RF22_REG_33_HEADER_CONTROL2 0x33 +#define RH_RF22_HDLEN 0x70 +#define RH_RF22_HDLEN_0 0x00 +#define RH_RF22_HDLEN_1 0x10 +#define RH_RF22_HDLEN_2 0x20 +#define RH_RF22_HDLEN_3 0x30 +#define RH_RF22_HDLEN_4 0x40 +#define RH_RF22_VARPKLEN 0x00 +#define RH_RF22_FIXPKLEN 0x08 +#define RH_RF22_SYNCLEN 0x06 +#define RH_RF22_SYNCLEN_1 0x00 +#define RH_RF22_SYNCLEN_2 0x02 +#define RH_RF22_SYNCLEN_3 0x04 +#define RH_RF22_SYNCLEN_4 0x06 +#define RH_RF22_PREALEN8 0x01 + +// RH_RF22_REG_6D_TX_POWER 0x6d +// https://www.sparkfun.com/datasheets/Wireless/General/RFM22B.pdf +#define RH_RF22_PAPEAKVAL 0x80 +#define RH_RF22_PAPEAKEN 0x40 +#define RH_RF22_PAPEAKLVL 0x30 +#define RH_RF22_PAPEAKLVL6_5 0x00 +#define RH_RF22_PAPEAKLVL7 0x10 +#define RH_RF22_PAPEAKLVL7_5 0x20 +#define RH_RF22_PAPEAKLVL8 0x30 +#define RH_RF22_LNA_SW 0x08 +#define RH_RF22_TXPOW 0x07 +#define RH_RF22_TXPOW_4X31 0x08 // Not used in RFM22B +// For RFM22B: +#define RH_RF22_TXPOW_1DBM 0x00 +#define RH_RF22_TXPOW_2DBM 0x01 +#define RH_RF22_TXPOW_5DBM 0x02 +#define RH_RF22_TXPOW_8DBM 0x03 +#define RH_RF22_TXPOW_11DBM 0x04 +#define RH_RF22_TXPOW_14DBM 0x05 +#define RH_RF22_TXPOW_17DBM 0x06 +#define RH_RF22_TXPOW_20DBM 0x07 +// RFM23B only: +#define RH_RF22_RF23B_TXPOW_M8DBM 0x00 // -8dBm +#define RH_RF22_RF23B_TXPOW_M5DBM 0x01 // -5dBm +#define RH_RF22_RF23B_TXPOW_M2DBM 0x02 // -2dBm +#define RH_RF22_RF23B_TXPOW_1DBM 0x03 // 1dBm +#define RH_RF22_RF23B_TXPOW_4DBM 0x04 // 4dBm +#define RH_RF22_RF23B_TXPOW_7DBM 0x05 // 7dBm +#define RH_RF22_RF23B_TXPOW_10DBM 0x06 // 10dBm +#define RH_RF22_RF23B_TXPOW_13DBM 0x07 // 13dBm +// RFM23BP only: +#define RH_RF22_RF23BP_TXPOW_28DBM 0x05 // 28dBm +#define RH_RF22_RF23BP_TXPOW_29DBM 0x06 // 29dBm +#define RH_RF22_RF23BP_TXPOW_30DBM 0x07 // 30dBm + +// RH_RF22_REG_71_MODULATION_CONTROL2 0x71 +#define RH_RF22_TRCLK 0xc0 +#define RH_RF22_TRCLK_NONE 0x00 +#define RH_RF22_TRCLK_GPIO 0x40 +#define RH_RF22_TRCLK_SDO 0x80 +#define RH_RF22_TRCLK_NIRQ 0xc0 +#define RH_RF22_DTMOD 0x30 +#define RH_RF22_DTMOD_DIRECT_GPIO 0x00 +#define RH_RF22_DTMOD_DIRECT_SDI 0x10 +#define RH_RF22_DTMOD_FIFO 0x20 +#define RH_RF22_DTMOD_PN9 0x30 +#define RH_RF22_ENINV 0x08 +#define RH_RF22_FD8 0x04 +#define RH_RF22_MODTYP 0x30 +#define RH_RF22_MODTYP_UNMODULATED 0x00 +#define RH_RF22_MODTYP_OOK 0x01 +#define RH_RF22_MODTYP_FSK 0x02 +#define RH_RF22_MODTYP_GFSK 0x03 + + +// RH_RF22_REG_75_FREQUENCY_BAND_SELECT 0x75 +#define RH_RF22_SBSEL 0x40 +#define RH_RF22_HBSEL 0x20 +#define RH_RF22_FB 0x1f + +// Define this to include Serial printing in diagnostic routines +#define RH_RF22_HAVE_SERIAL + +///////////////////////////////////////////////////////////////////// +/// \class RH_RF22 RH_RF22.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via an RF22 and compatible radio transceiver. +/// +/// Works with RF22, RF23 based radio modules, and compatible chips and modules, including: +/// - RF22 bare module: http://www.sparkfun.com/products/10153 +/// (Caution, that is a 3.3V part, and requires a 3.3V CPU such as Teensy etc or level shifters) +/// - RF22 shield: http://www.sparkfun.com/products/11018 +/// - RF22 integrated board http://www.anarduino.com/miniwireless +/// - RFM23BP bare module: http://www.anarduino.com/details.jsp?pid=130 +/// - Silicon Labs Si4430/31/32 based modules. S4432 is equivalent to RF22. Si4431/30 is equivalent to RF23. +/// +/// Data based on https://www.sparkfun.com/datasheets/Wireless/General/RFM22B.pdf +/// +/// \par Overview +/// +/// This base class provides basic functions for sending and receiving unaddressed, +/// unreliable datagrams of arbitrary length to 255 octets per packet. +/// +/// Manager classes may use this class to implement reliable, addressed datagrams and streams, +/// mesh routers, repeaters, translators etc. +/// +/// On transmission, the TO and FROM addresses default to 0x00, unless changed by a subclass. +/// On reception the TO addressed is checked against the node address (defaults to 0x00) or the +/// broadcast address (which is 0xff). The ID and FLAGS are set to 0, and not checked by this class. +/// This permits use of the this base RH_RF22 class as an +/// unaddressed, unreliable datagram service without the use of one the RadioHead Manager classes. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// modulation scheme. +/// +/// \par Details +/// +/// This Driver provides an object-oriented interface for sending and receiving data messages with Hope-RF +/// RF22 and RF23 based radio modules, and compatible chips and modules, +/// including the RFM22B transceiver module such as +/// this bare module: http://www.sparkfun.com/products/10153 +/// and this shield: http://www.sparkfun.com/products/11018 +/// and this module: http://www.hoperfusa.com/details.jsp?pid=131 +/// and this integrated board: http://www.anarduino.com/miniwireless +/// and RF23BP modules such as this http://www.anarduino.com/details.jsp?pid=130 +/// +/// The Hope-RF (http://www.hoperf.com) RFM22B (http://www.hoperf.com/rf_fsk/fsk/RFM22B.htm) +/// is a low-cost ISM transceiver module. It supports FSK, GFSK, OOK over a wide +/// range of frequencies and programmable data rates. +/// Manual can be found at https://www.sparkfun.com/datasheets/Wireless/General/RFM22.PDF +/// +/// This library provides functions for sending and receiving messages of up to 255 octets on any +/// frequency supported by the RF22B, in a range of predefined data rates and frequency deviations. +/// Frequency can be set with 312Hz precision to any frequency from 240.0MHz to 960.0MHz. +/// +/// Up to 3 RF22B modules can be connected to an Arduino, permitting the construction of translators +/// and frequency changers, etc. +/// +/// The following modulation types are suppported with a range of modem configurations for +/// common data rates and frequency deviations: +/// - GFSK Gaussian Frequency Shift Keying +/// - FSK Frequency Shift Keying +/// - OOK On-Off Keying +/// +/// Support for other RF22B features such as on-chip temperature measurement, analog-digital +/// converter, transmitter power control etc is also provided. +/// +/// Tested on Arduino Diecimila, Uno and Mega with arduino-0021, 1.0.5 +/// on OpenSuSE 13.1 and avr-libc-1.6.1-1.15, +/// cross-avr-binutils-2.19-9.1, cross-avr-gcc-4.1.3_20080612-26.5. +/// With HopeRF RFM22 modules that appear to have RF22B chips on board: +/// - Device Type Code = 0x08 (RX/TRX) +/// - Version Code = 0x06 +/// Works on Duo. Works with Sparkfun RFM22 Wireless shields. Works with RFM22 modules from http://www.hoperfusa.com/ +/// Works with Arduino 1.0 to at least 1.0.5. Works on Maple, Flymaple, Uno32 (with ChipKIT Core with Arduino IDE). +/// +/// \par Packet Format +/// +/// All messages sent and received by this Driver must conform to this packet format: +/// +/// - 8 nibbles (4 octets) PREAMBLE +/// - 2 octets SYNC 0x2d, 0xd4 +/// - 4 octets HEADER: (TO, FROM, ID, FLAGS) +/// - 1 octet LENGTH (0 to 255), number of octets in DATA +/// - 0 to 255 octets DATA +/// - 2 octets CRC computed with CRC16(IBM), computed on HEADER, LENGTH and DATA +/// +/// For technical reasons, the message format is not protocol compatible with the +/// 'HopeRF Radio Transceiver Message Library for Arduino' http://www.airspayce.com/mikem/arduino/HopeRF from the same author. Nor is it compatible with +/// 'Virtual Wire' http://www.airspayce.com/mikem/arduino/VirtualWire.pdf also from the same author. +/// +/// \par Connecting RFM-22 to Arduino +/// +/// If you have the Sparkfun RFM22 Shield (https://www.sparkfun.com/products/11018) +/// the connections described below are done for you on the shield, no changes required, +/// just add headers and plug it in to an Arduino (but not and Arduino Mega, see below) +/// +/// The physical connection between the RF22B and the Arduino requires 3.3V, +/// the 3 x SPI pins (SCK, SDI, SDO), a Slave Select pin and an interrupt pin. +/// +/// Note: some devices may need a pullup resister on the SDO line. +/// +/// Note also that on the RFM22B (but not the RFM23B), it is required to control the TX_ANT and +/// RX_ANT pins of the RFM22 in order to control the antenna connection properly. The RH_RF22 +/// driver is configured by default so that GPIO0 and GPIO1 outputs can +/// control TX_ANT and RX_ANT input pins respectively automatically. On RFM22, +/// you must connect GPIO0 +/// to TX_ANT and GPIO1 to RX_ANT for this automatic antenna switching to +/// occur. See setGpioReversed() for more details. These connections are not required on RFM23B. +/// +/// If you are using the Sparkfun RF22 shield, it will work with any 5V arduino without modification. +/// Connect the RFM-22 module to most Arduino's like this (Caution, Arduino Mega has different pins for SPI, +/// see below). +/// \code +/// Arduino RFM-22B +/// GND----------GND-\ (ground in) +/// SDN-/ (shutdown in) +/// 3V3----------VCC (3.3V in) +/// interrupt 0 pin D2-----------NIRQ (interrupt request out) +/// SS pin D10----------NSEL (chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------SDI (SPI Data in) +/// MISO pin D12----------SDO (SPI data out) +/// /--GPIO0 (GPIO0 out to control transmitter antenna TX_ANT) +/// \--TX_ANT (TX antenna control in) RFM22B only +/// /--GPIO1 (GPIO1 out to control receiver antenna RX_ANT) +/// \--RX_ANT (RX antenna control in) RFM22B only +/// \endcode +/// For an Arduino Mega: +/// \code +/// Mega RFM-22B +/// GND----------GND-\ (ground in) +/// SDN-/ (shutdown in) +/// 3V3----------VCC (3.3V in) +/// interrupt 0 pin D2-----------NIRQ (interrupt request out) +/// SS pin D10----------NSEL (chip select in) +/// SCK pin D52----------SCK (SPI clock in) +/// MOSI pin D51----------SDI (SPI Data in) +/// MISO pin D50----------SDO (SPI data out) +/// /--GPIO0 (GPIO0 out to control transmitter antenna TX_ANT) +/// \--TX_ANT (TX antenna control in) RFM22B only +/// /--GPIO1 (GPIO1 out to control receiver antenna RX_ANT) +/// \--RX_ANT (RX antenna control in) RFM22B only +/// \endcode +/// For Chipkit Uno32. Caution: you must also ensure jumper JP4 on the Uno32 is set to RD4 +/// \code +/// Arduino RFM-22B +/// GND----------GND-\ (ground in) +/// SDN-/ (shutdown in) +/// 3V3----------VCC (3.3V in) +/// interrupt 0 pin D38----------NIRQ (interrupt request out) +/// SS pin D10----------NSEL (chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------SDI (SPI Data in) +/// MISO pin D12----------SDO (SPI data out) +/// /--GPIO0 (GPIO0 out to control transmitter antenna TX_ANT) +/// \--TX_ANT (TX antenna control in) RFM22B only +/// /--GPIO1 (GPIO1 out to control receiver antenna RX_ANT) +/// \--RX_ANT (RX antenna control in) RFM22B only +/// \endcode +/// For Teensy 3.1 +/// \code +/// Teensy RFM-22B +/// GND----------GND-\ (ground in) +/// SDN-/ (shutdown in) +/// 3V3----------VCC (3.3V in) +/// interrupt 2 pin D2-----------NIRQ (interrupt request out) +/// SS pin D10----------NSEL (chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------SDI (SPI Data in) +/// MISO pin D12----------SDO (SPI data out) +/// /--GPIO0 (GPIO0 out to control transmitter antenna TX_ANT) +/// \--TX_ANT (TX antenna control in) RFM22B only +/// /--GPIO1 (GPIO1 out to control receiver antenna RX_ANT) +/// \--RX_ANT (RX antenna control in) RFM22B only +/// \endcode +/// For an Arduino Due (the SPI pins do not come out on the Digital pins as for normal Arduino, but only +/// appear on the SPI header) +/// \code +/// Due RFM-22B +/// GND----------GND-\ (ground in) +/// SDN-/ (shutdown in) +/// 5V-----------VCC (5V in) +/// interrupt 0 pin D2-----------NIRQ (interrupt request out) +/// SS pin D10----------NSEL (chip select in) +/// SCK SPI pin 3----------SCK (SPI clock in) +/// MOSI SPI pin 4----------SDI (SPI Data in) +/// MISO SPI pin 1----------SDO (SPI data out) +/// /--GPIO0 (GPIO0 out to control transmitter antenna TX_ANT) +/// \--TX_ANT (TX antenna control in) RFM22B only +/// /--GPIO1 (GPIO1 out to control receiver antenna RX_ANT) +/// \--RX_ANT (RX antenna control in) RFM22B only +/// \endcode +/// and use the default constructor: +/// RH_RF22 driver; +/// For connecting an Arduino to an RFM23BP module. Note that the antenna control pins are reversed +/// compared to the RF22. +/// \code +/// Arduino RFM-23BP +/// GND----------GND-\ (ground in) +/// SDN-/ (shutdown in) +/// 5V-----------VCC (5V in) +/// interrupt 0 pin D2-----------NIRQ (interrupt request out) +/// SS pin D10----------NSEL (chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------SDI (SPI Data in) +/// MISO pin D12----------SDO (SPI data out) +/// /--GPIO0 (GPIO0 out to control receiver antenna RXON) +/// \--RXON (RX antenna control in) +/// /--GPIO1 (GPIO1 out to control transmitter antenna TXON) +/// \--TXON (TX antenna control in) +/// \endcode +/// +/// and you can then use the default constructor RH_RF22(). +/// You can override the default settings for the SS pin and the interrupt +/// in the RH_RF22 constructor if you wish to connect the slave select SS to other than the normal one for your +/// Arduino (D10 for Diecimila, Uno etc and D53 for Mega) +/// or the interrupt request to other than pin D2 (Caution, different processors have different constraints as to the +/// pins available for interrupts). +/// +/// Caution: some people have had problems with some batches of +/// RFM23BP chips burning out their nIRQ outputs for unknown +/// reasons when run at 5V. Some users assert that running RFM23BP with voltage +/// dividers at 3.3V is to be preferred. We have not tested or verified +/// either the cause or the supposed cure. +// +/// +/// If you have an Arduino Zero, you should note that you cannot use Pin 2 for the interrupt line +/// (Pin 2 is for the NMI only), instead you can use any other pin (we use Pin 3) and initialise RH_RF69 like this: +/// \code +/// // Slave Select is pin 10, interrupt is Pin 3 +/// RH_RF22 driver(10, 3); +/// \endcode +/// +/// If you have an ESP32 (we tested with the Geekworm EASY-KIT ESP32-B1 which has a ESP-WROOM-32 chip) +/// \code +/// ESP32 RFM-22B +/// GND----------GND-\ (ground in) +/// SDN-/ (shutdown in) +/// 3V3----------VCC (3.3V in) +/// interrupt pin GPIO15-------NIRQ (interrupt request out) +/// SS pin GPIO13-------NSEL (chip select in) +/// SCK pin GPIO18-------SCK (SPI clock in) +/// MOSI pin GPIO23-------SDI (SPI Data in) +/// MISO pin GPIO19-------SDO (SPI data out) +/// /--GPIO0 (GPIO0 out to control transmitter antenna TX_ANT) +/// \--TX_ANT (TX antenna control in) RFM22B only +/// /--GPIO1 (GPIO1 out to control receiver antenna RX_ANT) +/// \--RX_ANT (RX antenna control in) RFM22B only +/// \endcode +/// and initialise like this: +/// \code +/// RH_RF22 driver(13, 15); +/// \endcode +/// You can of course use other pins for NSEL and NIRQ if you prefer. +/// +/// To connect an STM32 F4 Discovery board to RF22 using Arduino and Arduino_STM32 +/// connect the pins like this: +/// \code +/// STM32 RFM-22B +/// GND----------GND-\ (ground in) +/// SDN-/ (shutdown in) +/// VDD----------VCC (3.3V in) +/// interrupt pin PB1----------NIRQ (interrupt request out) +/// SS pin PB0----------NSEL (chip select in) +/// SCK pin PB3----------SCK (SPI clock in) +/// MOSI pin PB5----------SDI (SPI Data in) +/// MISO pin PB4----------SDO (SPI data out) +/// /--GPIO0 (GPIO0 out to control transmitter antenna TX_ANT) +/// \--TX_ANT (TX antenna control in) RFM22B only +/// /--GPIO1 (GPIO1 out to control receiver antenna RX_ANT) +/// \--RX_ANT (RX antenna control in) RFM22B only +/// \endcode +/// and initialise like this: +/// \code +/// RH_RF22 driver(PB0, PB1); +/// \endcode +/// You can of use other pins for NSEL and NIRQ if you prefer. +/// +/// To connect an ATTiny Mega x16 such as AtTiny 3216 etc +/// (running at 5V) etc RF22 using Arduino using Spencer Kondes +/// megaTinyCore https://github.com/SpenceKonde/megaTinyCore connect the pins like this: +/// (pin numbering based on https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/ATtiny_x16.md) +/// \code +/// AtTiny x16 RFM-22B +/// GND----------GND-\ (ground in) +/// SDN-/ (shutdown in) +/// VDD----------VCC (5V in) +/// interrupt pin PA6----------NIRQ (interrupt request out) +/// SS pin PC0----------NSEL (chip select in) +/// SCK pin PA3----------SCK (SPI clock in) +/// MOSI pin PA1----------SDI (SPI Data in) +/// MISO pin PA2----------SDO (SPI data out) +/// /--GPIO0 (GPIO0 out to control transmitter antenna TX_ANT) +/// \--TX_ANT (TX antenna control in) RFM22B only +/// /--GPIO1 (GPIO1 out to control receiver antenna RX_ANT) +/// \--RX_ANT (RX antenna control in) RFM22B only +/// \endcode +/// and initialise like this: +/// \code +/// RH_RF22 driver(10, 2); +/// \endcode +/// You can of use other pins for NSEL and NIRQ if you prefer. +/// +/// For ESP8266-based ESP-12 modules. Caution: on some breakout boards we have seen +/// labels for D4 and D5 reversed. +/// \code +/// ESP-12 RFM-22B +/// GND----------GND-\ (ground in) +/// SDN-/ (shutdown in) +/// 3V3----------VCC (3.3V in) +/// interrupt 0 pin D4-----------NIRQ (interrupt request out) +/// SS pin D5-----------NSEL (chip select in) +/// SCK pin D14----------SCK (SPI clock in) +/// MOSI pin D13----------SDI (SPI Data in) +/// MISO pin D12----------SDO (SPI data out) +/// /--GPIO0 (GPIO0 out to control transmitter antenna TX_ANT) +/// \--TX_ANT (TX antenna control in) RFM22B only +/// /--GPIO1 (GPIO1 out to control receiver antenna RX_ANT) +/// \--RX_ANT (RX antenna control in) RFM22B only +/// \endcode +/// and initialise like this: +/// \code +/// RH_RF22 driver(5, 4); +/// \endcode +/// +/// Note: It is possible to have 2 radios connected to one Arduino, provided each radio has its own +/// SS and interrupt line (SCK, SDI and SDO are common to both radios) +/// +/// Caution: on some Arduinos such as the Mega 2560, if you set the slave select pin to be other than the usual SS +/// pin (D53 on Mega 2560), you may need to set the usual SS pin to be an output to force the Arduino into SPI +/// master mode. +/// +/// Caution: Power supply requirements of the RF22 module may be relevant in some circumstances: +/// RF22 modules are capable of pulling 80mA+ at full power, where Arduino's 3.3V line can +/// give 50mA. You may need to make provision for alternate power supply for +/// the RF22, especially if you wish to use full transmit power, and/or you have +/// other shields demanding power. Inadequate power for the RF22 is reported to cause symptoms such as: +/// - reset's/bootups terminate with "init failed" messages +/// -random termination of communication after 5-30 packets sent/received +/// -"fake ok" state, where initialization passes fluently, but communication doesn't happen +/// -shields hang Arduino boards, especially during the flashing +/// +/// Caution: some RF22 breakout boards (such as the HAB-RFM22B-BOA HAB-RFM22B-BO) reportedly +/// have the TX_ANT and RX_ANT pre-connected to GPIO0 and GPIO1 round the wrong way. You can work with this +/// if you use setGpioReversed(). +/// +/// Caution: If you are using a bare RF22 module without IO level shifters, you may have difficulty connecting +/// to a 5V arduino. The RF22 module is 3.3V and its IO pins are 3.3V not 5V. Some Arduinos (Diecimila and +/// Uno) seem to work OK with this, and some (Mega) do not always work reliably. Your Mileage May Vary. +/// For best result, use level shifters, or use a RF22 shield or board with level shifters built in, +/// such as the Sparkfun RFM22 shield http://www.sparkfun.com/products/11018. +/// You could also use a 3.3V IO Arduino such as a Pro. +/// It is recognised that it is difficult to connect +/// the Sparkfun RFM22 shield to a Mega, since the SPI pins on the Mega are different to other Arduinos, +/// But it is possible, by bending the SPI pins (D10, D11, D12, D13) on the +/// shield out of the way before plugging it in to the Mega and jumpering the shield pins to the Mega like this: +/// \code +/// RF22 Shield Mega +/// D10 D53 +/// D13 D52 +/// D11 D51 +/// D12 D50 +/// \endcode +/// +/// \par Interrupts +/// +/// The Driver uses interrupts to react to events in the RF22 module, +/// such as the reception of a new packet, or the completion of transmission of a packet. +/// The RH_RF22 interrupt service routine reads status from and writes data +/// to the the RF22 module via the SPI interface. It is very important therefore, +/// that if you are using the RF22 library with another SPI based deviced, that you +/// disable interrupts while you transfer data to and from that other device. +/// Use cli() to disable interrupts and sei() to reenable them. +/// +/// \par SPI Interface +/// +/// The RF22 module uses the SPI bus to communicate with the Arduino. Arduino +/// IDE includes a hardware SPI class to communicate with SPI devices using +/// the SPI facilities built into the Atmel chips, over the standard designated +/// SPI pins MOSI, MISO, SCK, which are usually on Arduino pins 11, 12 and 13 +/// respectively (or 51, 50, 52 on a Mega). +/// +/// By default, the RH_RF22 Driver uses the Hardware SPI interface to +/// communicate with the RF22 module. However, if your RF22 SPI is connected to +/// the Arduino through non-standard pins, or the standard Hardware SPI +/// interface will not work for you, you can instead use a bit-banged Software +/// SPI class RHSoftwareSPI, which can be configured to work on any Arduino digital IO pins. +/// See the documentation of RHSoftwareSPI for details. +/// +/// The advantages of the Software SPI interface are that it can be used on +/// any Arduino pins, not just the usual dedicated hardware pins. The +/// disadvantage is that it is significantly slower then hardware. +/// If you observe reliable behaviour with the default hardware SPI RHHardwareSPI, but unreliable behaviour +/// with Software SPI RHSoftwareSPI, it may be due to slow CPU performance. +/// +/// Initialisation example with hardware SPI +/// \code +/// #include +/// RH_RF22 driver; +/// RHReliableDatagram manager(driver, CLIENT_ADDRESS); +/// \endcode +/// +/// Initialisation example with software SPI +/// \code +/// #include +/// #include +/// RHSoftwareSPI spi; +/// RH_RF22 driver(10, 2, spi); +/// RHReliableDatagram manager(driver, CLIENT_ADDRESS); +/// \endcode +/// +/// \par Memory +/// +/// The RH_RF22 Driver requires non-trivial amounts of memory. The sample programs all compile to +/// about 9 to 14kbytes each on Arduino, which will fit in the flash proram memory of most Arduinos. However, +/// the RAM requirements are more critical. Most sample programs above will run on Duemilanova, +/// but not on Diecimila. Even on Duemilanova, the RAM requirements are very close to the +/// available memory of 2kbytes. Therefore, you should be vary sparing with RAM use in programs that use +/// the RH_RF22 Driver on Duemilanova. +/// +/// The sample RHRouter and RHMesh programs compile to about 14kbytes, +/// and require more RAM than the others. +/// They will not run on Duemilanova or Diecimila, but will run on Arduino Mega. +/// +/// It is often hard to accurately identify when you are hitting RAM limits on Arduino. +/// The symptoms can include: +/// - Mysterious crashes and restarts +/// - Changes in behaviour when seemingly unrelated changes are made (such as adding print() statements) +/// - Hanging +/// - Output from Serial.print() not appearing +/// +/// With an Arduino Mega, with 8 kbytes of SRAM, there is much more RAM headroom for +/// your own elaborate programs. +/// This library is reported to work with Arduino Pro Mini, but that has not been tested by me. +/// +/// The RF22M modules use an inexpensive crystal to control the frequency synthesizer, and therfore you can expect +/// the transmitter and receiver frequencies to be subject to the usual inaccuracies of such crystals. The RF22 +/// contains an AFC circuit to compensate for differences in transmitter and receiver frequencies. +/// It does this by altering the receiver frequency during reception by up to the pull-in frequency range. +/// This RF22 library enables the AFC and by default sets the pull-in frequency range to +/// 0.05MHz, which should be sufficient to handle most situations. However, if you observe unexplained packet losses +/// or failure to operate correctly all the time it may be because your modules have a wider frequency difference, and +/// you may need to set the afcPullInRange to a different value, using setFrequency(); +/// +/// \par Transmitter Power +/// +/// You can control the transmitter power on the RF22 and RF23 transceivers +/// with the RH_RF22::setTxPower() function. The argument can be any of the +/// RH_RF22_TXPOW_* (for RFM22) or RH_RF22_RF23B_TXPOW_* (for RFM23) values. +/// The default is RH_RF22_TXPOW_8DBM/RH_RF22_RF23B_TXPOW_1DBM . Eg: +/// \code +/// driver.setTxPower(RH_RF22_TXPOW_2DBM); +/// \endcode +/// +/// The RF23BP has higher power capability, there are +/// several power settings that are specific to the RF23BP only: +/// +/// - RH_RF22_RF23BP_TXPOW_28DBM +/// - RH_RF22_RF23BP_TXPOW_29DBM +/// - RH_RF22_RF23BP_TXPOW_30DBM +/// +/// CAUTION: the high power settings available on the RFM23BP require +/// significant power supply current. For example at +30dBm, the typical chip +/// supply current is 550mA. This will overwhelm some small CPU board power +/// regulators and USB supplies. If you use this chip at high power make sure +/// you have an adequate supply current providing full 5V to the RFM23BP (and +/// the CPU if required), otherwise you can expect strange behaviour like +/// hanging, stopping, incorrect power levels, RF power amp overheating etc. +/// You must also ensure that the RFM23BP GPIO pins are connected to the +/// antenna switch control pins like so: +//// +/// \code +/// GPIO0 <-> RXON +/// GPIO1 <-> TXON +/// \endcode +/// +/// The RF output impedance of the RFM22BP module is 50 ohms. In our +/// experiments we found that the most critical issue (besides a suitable +/// power supply) is to ensure that the antenna impedance is also near 50 +/// ohms. Connecting a simple 1/4 wavelength (ie a 17.3cm single wire) +/// directly to the antenna output will not work at full 30dBm power, +/// and will result in the transmitter hanging and/or the power amp +/// overheating. Connect a proper 50 ohm impedance transmission line or +/// antenna, and prevent RF radiation into the radio and arduino modules, +/// in order to get full, reliable power. Our tests show that a 433MHz +/// RFM23BP feeding a 50 ohm transmission line with a VHF discone antenna at +/// the end results in full power output and the power amp transistor on the +/// RFM22BP module runnning slightly warm but not hot. We recommend you use +/// the services of a competent RF engineer when trying to use this high power +/// module. +/// +/// Note: with RFM23BP, the reported maximum possible power when operating on 3.3V is 27dBm. +/// The BP version is an RFM23 with a PA +/// external to the Silicon Labs radio chip. +/// The RFM23BP only supports the top three power settings because those three +/// output levels from the RFM23 provide enough drive to the PA to make it +/// saturate. Less drive and the PA will dissipate more heat. However, those +/// three levels don't change the output power from the PA. +/// +/// We have made some actual power measurements against +/// programmed power for Sparkfun RFM22 wireless module under the following conditions: +/// - Sparkfun RFM22 wireless module, Duemilanove, USB power +/// - 10cm RG58C/U soldered direct to RFM22 module ANT and GND +/// - bnc connecteor +/// - 12dB attenuator +/// - BNC-SMA adapter +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// Program power Measured Power +/// dBm dBm +/// 1 -5.6 +/// 2 -3.8 +/// 5 -2.2 +/// 8 -0.6 +/// 11 1.2 +/// 14 11.6 +/// 17 14.4 +/// 20 18.0 +/// \endcode +/// (Caution: we dont claim laboratory accuracy for these measurements) +/// You would not expect to get anywhere near these powers to air with a simple 1/4 wavelength wire antenna. +/// +/// \par Performance +/// +/// Some simple speed performance tests have been conducted. +/// In general packet transmission rate will be limited by the modulation scheme. +/// Also, if your code does any slow operations like Serial printing it will also limit performance. +/// We disabled any printing in the tests below. +/// We tested with RH_RF22::GFSK_Rb125Fd125, which is probably the fastest scheme available. +/// We tested with a 13 octet message length, over a very short distance of 10cm. +/// +/// Transmission (no reply) tests with modulation RH_RF22::GFSK_Rb125Fd125 and a +/// 13 octet message show about 330 messages per second transmitted. +/// +/// Transmit-and-wait-for-a-reply tests with modulation RH_RF22::GFSK_Rb125Fd125 and a +/// 13 octet message (send and receive) show about 160 round trips per second. +/// +/// \par Compatibility with RF22 library +/// The RH_RF22 driver is based on our earlier RF22 library http://www.airspayce.com/mikem/arduino/RF22 +/// We have tried hard to be as compatible as possible with the earlier RF22 library, but there are some differences: +/// - Different constructor. +/// - Indexes for some modem configurations have changed (we recommend you use the symbolic names, not integer indexes). +/// +/// The major difference is that under RadioHead, you are +/// required to create 2 objects (ie RH_RF22 and a manager) instead of just one object under RF22 +/// (ie RHMesh, RHRouter, RHReliableDatagram or RHDatagram). +/// It may be sufficient or you to change for example: +/// \code +/// RF22ReliableDatagram rf22(CLIENT_ADDRESS); +/// \endcode +/// to: +/// \code +/// RH_RF22 driver; +/// RHReliableDatagram rf22(driver, CLIENT_ADDRESS); +/// \endcode +/// and any instance of RF22_MAX_MESSAGE_LEN to RH_RF22_MAX_MESSAGE_LEN +/// +/// RadioHead version 1.6 changed the way the interrupt pin number is +/// specified on Arduino and Uno32 platforms. If your code previously +/// specifed a non-default interrupt pin number in the RH_RF22 constructor, +/// you may need to review your code to specify the correct interrrupt pin +/// (and not the interrupt number as before). +class RH_RF22 : public RHSPIDriver +{ +public: + /// \brief Defines register values for a set of modem configuration registers + /// + /// Defines register values for a set of modem configuration registers + /// that can be passed to setModemConfig() + /// if none of the choices in ModemConfigChoice suit your need + /// setModemConfig() writes the register values to the appropriate RH_RF22 registers + /// to set the desired modulation type, data rate and deviation/bandwidth. + /// Suitable values for these registers can be computed using the register calculator at + /// http://www.hoperf.com/upload/rf/RF22B%2023B%2031B%2042B%2043B%20Register%20Settings_RevB1-v5.xls + typedef struct + { + uint8_t reg_1c; ///< Value for register RH_RF22_REG_1C_IF_FILTER_BANDWIDTH + uint8_t reg_1f; ///< Value for register RH_RF22_REG_1F_CLOCK_RECOVERY_GEARSHIFT_OVERRIDE + uint8_t reg_20; ///< Value for register RH_RF22_REG_20_CLOCK_RECOVERY_OVERSAMPLING_RATE + uint8_t reg_21; ///< Value for register RH_RF22_REG_21_CLOCK_RECOVERY_OFFSET2 + uint8_t reg_22; ///< Value for register RH_RF22_REG_22_CLOCK_RECOVERY_OFFSET1 + uint8_t reg_23; ///< Value for register RH_RF22_REG_23_CLOCK_RECOVERY_OFFSET0 + uint8_t reg_24; ///< Value for register RH_RF22_REG_24_CLOCK_RECOVERY_TIMING_LOOP_GAIN1 + uint8_t reg_25; ///< Value for register RH_RF22_REG_25_CLOCK_RECOVERY_TIMING_LOOP_GAIN0 + uint8_t reg_2c; ///< Value for register RH_RF22_REG_2C_OOK_COUNTER_VALUE_1 + uint8_t reg_2d; ///< Value for register RH_RF22_REG_2D_OOK_COUNTER_VALUE_2 + uint8_t reg_2e; ///< Value for register RH_RF22_REG_2E_SLICER_PEAK_HOLD + uint8_t reg_58; ///< Value for register RH_RF22_REG_58_CHARGE_PUMP_CURRENT_TRIMMING + uint8_t reg_69; ///< Value for register RH_RF22_REG_69_AGC_OVERRIDE1 + uint8_t reg_6e; ///< Value for register RH_RF22_REG_6E_TX_DATA_RATE1 + uint8_t reg_6f; ///< Value for register RH_RF22_REG_6F_TX_DATA_RATE0 + uint8_t reg_70; ///< Value for register RH_RF22_REG_70_MODULATION_CONTROL1 + uint8_t reg_71; ///< Value for register RH_RF22_REG_71_MODULATION_CONTROL2 + uint8_t reg_72; ///< Value for register RH_RF22_REG_72_FREQUENCY_DEVIATION + } ModemConfig; + + /// Choices for setModemConfig() for a selected subset of common modulation types, + /// and data rates. If you need another configuration, use the register calculator. + /// and call setModemRegisters() with your desired settings. + /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic + /// definitions and not their integer equivalents: its possible that new values will be + /// introduced in later versions (though we will try to avoid it). + typedef enum + { + UnmodulatedCarrier = 0, ///< Unmodulated carrier for testing + FSK_PN9_Rb2Fd5, ///< FSK, No Manchester, Rb = 2kbs, Fd = 5kHz, PN9 random modulation for testing + + FSK_Rb2Fd5, ///< FSK, No Manchester, Rb = 2kbs, Fd = 5kHz + FSK_Rb2_4Fd36, ///< FSK, No Manchester, Rb = 2.4kbs, Fd = 36kHz + FSK_Rb4_8Fd45, ///< FSK, No Manchester, Rb = 4.8kbs, Fd = 45kHz + FSK_Rb9_6Fd45, ///< FSK, No Manchester, Rb = 9.6kbs, Fd = 45kHz + FSK_Rb19_2Fd9_6, ///< FSK, No Manchester, Rb = 19.2kbs, Fd = 9.6kHz + FSK_Rb38_4Fd19_6, ///< FSK, No Manchester, Rb = 38.4kbs, Fd = 19.6kHz + FSK_Rb57_6Fd28_8, ///< FSK, No Manchester, Rb = 57.6kbs, Fd = 28.8kHz + FSK_Rb125Fd125, ///< FSK, No Manchester, Rb = 125kbs, Fd = 125kHz + FSK_Rb_512Fd2_5, ///< FSK, No Manchester, Rb = 512bs, Fd = 2.5kHz, for POCSAG compatibility + FSK_Rb_512Fd4_5, ///< FSK, No Manchester, Rb = 512bs, Fd = 4.5kHz, for POCSAG compatibility + + GFSK_Rb2Fd5, ///< GFSK, No Manchester, Rb = 2kbs, Fd = 5kHz + GFSK_Rb2_4Fd36, ///< GFSK, No Manchester, Rb = 2.4kbs, Fd = 36kHz + GFSK_Rb4_8Fd45, ///< GFSK, No Manchester, Rb = 4.8kbs, Fd = 45kHz + GFSK_Rb9_6Fd45, ///< GFSK, No Manchester, Rb = 9.6kbs, Fd = 45kHz + GFSK_Rb19_2Fd9_6, ///< GFSK, No Manchester, Rb = 19.2kbs, Fd = 9.6kHz + GFSK_Rb38_4Fd19_6, ///< GFSK, No Manchester, Rb = 38.4kbs, Fd = 19.6kHz + GFSK_Rb57_6Fd28_8, ///< GFSK, No Manchester, Rb = 57.6kbs, Fd = 28.8kHz + GFSK_Rb125Fd125, ///< GFSK, No Manchester, Rb = 125kbs, Fd = 125kHz + + OOK_Rb1_2Bw75, ///< OOK, No Manchester, Rb = 1.2kbs, Rx Bandwidth = 75kHz + OOK_Rb2_4Bw335, ///< OOK, No Manchester, Rb = 2.4kbs, Rx Bandwidth = 335kHz + OOK_Rb4_8Bw335, ///< OOK, No Manchester, Rb = 4.8kbs, Rx Bandwidth = 335kHz + OOK_Rb9_6Bw335, ///< OOK, No Manchester, Rb = 9.6kbs, Rx Bandwidth = 335kHz + OOK_Rb19_2Bw335, ///< OOK, No Manchester, Rb = 19.2kbs, Rx Bandwidth = 335kHz + OOK_Rb38_4Bw335, ///< OOK, No Manchester, Rb = 38.4kbs, Rx Bandwidth = 335kHz + OOK_Rb40Bw335 ///< OOK, No Manchester, Rb = 40kbs, Rx Bandwidth = 335kHz + + } ModemConfigChoice; + + /// \brief Defines the available choices for CRC + /// Types of permitted CRC polynomials, to be passed to setCRCPolynomial() + /// They deliberately have the same numeric values as the crc[1:0] field of Register + /// RH_RF22_REG_30_DATA_ACCESS_CONTROL + typedef enum + { + CRC_CCITT = 0, ///< CCITT + CRC_16_IBM = 1, ///< CRC-16 (IBM) The default used by RH_RF22 driver + CRC_IEC_16 = 2, ///< IEC-16 + CRC_Biacheva = 3 ///< Biacheva + } CRCPolynomial; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// interrupt and slave select pin. After constructing, you must call init() to initialise the interface + /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient + /// distinct interrupt lines, one for each instance. + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RH_RF22 before + /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) + /// \param[in] interruptPin The interrupt Pin number that is connected to the RF22 NIRQ interrupt line. + /// Defaults to pin 2, as required by sparkfun RFM22 module shields. + /// Caution: You must specify an interrupt capable pin. + /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. + /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. + /// On other Arduinos pins 2 or 3. + /// See http://arduino.cc/en/Reference/attachInterrupt for more details. + /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. + /// On other boards, any digital pin may be used. + /// \param[in] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + RH_RF22(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, RHGenericSPI& spi = hardware_spi); + + /// Initialises this instance and the radio module connected to it. + /// The following steps are taken: + /// - Initialise the slave select pin and the SPI interface library + /// - Software reset the RH_RF22 module + /// - Checks the connected RH_RF22 module is either a RH_RF22_DEVICE_TYPE_RX_TRX or a RH_RF22_DEVICE_TYPE_TX + /// - Attaches an interrupt handler + /// - Configures the RH_RF22 module + /// - Sets the frequency to 434.0 MHz + /// - Sets the modem data rate to FSK_Rb2_4Fd36 + /// \return true if everything was successful + bool init(); + + /// Issues a software reset to the + /// RH_RF22 module. Blocks for 1ms to ensure the reset is complete. + void reset(); + + /// Reads and returns the device status register RH_RF22_REG_02_DEVICE_STATUS + /// \return The value of the device status register + uint8_t statusRead(); + + /// Reads a value from the on-chip analog-digital converter + /// \param[in] adcsel Selects the ADC input to measure. One of RH_RF22_ADCSEL_*. Defaults to the + /// internal temperature sensor + /// \param[in] adcref Specifies the refernce voltage to use. One of RH_RF22_ADCREF_*. + /// Defaults to the internal bandgap voltage. + /// \param[in] adcgain Amplifier gain selection. + /// \param[in] adcoffs Amplifier offseet (0 to 15). + /// \return The analog value. 0 to 255. + uint8_t adcRead(uint8_t adcsel = RH_RF22_ADCSEL_INTERNAL_TEMPERATURE_SENSOR, + uint8_t adcref = RH_RF22_ADCREF_BANDGAP_VOLTAGE, + uint8_t adcgain = 0, + uint8_t adcoffs = 0); + + /// Reads the on-chip temperature sensor + /// \param[in] tsrange Specifies the temperature range to use. One of RH_RF22_TSRANGE_* + /// \param[in] tvoffs Specifies the temperature value offset. This is actually signed value + /// added to the measured temperature value + /// \return The measured temperature. + uint8_t temperatureRead(uint8_t tsrange = RH_RF22_TSRANGE_M64_64C, uint8_t tvoffs = 0); + + /// Reads the wakeup timer value in registers RH_RF22_REG_17_WAKEUP_TIMER_VALUE1 + /// and RH_RF22_REG_18_WAKEUP_TIMER_VALUE2 + /// \return The wakeup timer value + uint16_t wutRead(); + + /// Sets the wakeup timer period registers RH_RF22_REG_14_WAKEUP_TIMER_PERIOD1, + /// RH_RF22_REG_15_WAKEUP_TIMER_PERIOD2 and RH_RF22_R 0) + /// \return true if the message length was valid and it was correctly queued for transmit + bool send(const uint8_t* data, uint8_t len); + + /// Sets the length of the preamble + /// in 4-bit nibbles. + /// Caution: this should be set to the same + /// value on all nodes in your network. Default is 8. + /// Sets the message preamble length in RH_RF22_REG_34_PREAMBLE_LENGTH + /// \param[in] nibbles Preamble length in nibbles of 4 bits each. + void setPreambleLength(uint8_t nibbles); + + /// Sets the sync words for transmit and receive in registers RH_RF22_REG_36_SYNC_WORD3 + /// to RH_RF22_REG_39_SYNC_WORD0 + /// Caution: SyncWords should be set to the same + /// value on all nodes in your network. Nodes with different SyncWords set will never receive + /// each others messages, so different SyncWords can be used to isolate different + /// networks from each other. Default is { 0x2d, 0xd4 }. + /// \param[in] syncWords Array of sync words, 1 to 4 octets long + /// \param[in] len Number of sync words to set, 1 to 4. + void setSyncWords(const uint8_t* syncWords, uint8_t len); + + /// Tells the receiver to accept messages with any TO address, not just messages + /// addressed to thisAddress or the broadcast address + /// \param[in] promiscuous true if you wish to receive messages with any TO address + virtual void setPromiscuous(bool promiscuous); + + /// Sets the CRC polynomial to be used to generate the CRC for both receive and transmit + /// otherwise the default of CRC_16_IBM will be used. + /// \param[in] polynomial One of RH_RF22::CRCPolynomial choices CRC_* + /// \return true if polynomial is a valid option for this radio. + bool setCRCPolynomial(CRCPolynomial polynomial); + + /// Configures GPIO pins for reversed GPIO connections to the antenna switch. + /// Normally on RF22 modules, GPIO0(out) is connected to TX_ANT(in) to enable tx antenna during transmit + /// and GPIO1(out) is connected to RX_ANT(in) to enable rx antenna during receive. The RH_RF22 driver + /// configures the GPIO pins during init() so the antenna switch works as expected. + /// However, some RF22 modules, such as HAB-RFM22B-BOA HAB-RFM22B-BO, also Si4432 sold by Dorji.com via Tindie.com + /// have these GPIO pins reversed, so that GPIO0 is connected to RX_ANT. + /// Call this function with a true argument after init() and before transmitting + /// in order to configure the module for reversed GPIO pins. + /// \param[in] gpioReversed Set to true if your RF22 module has reversed GPIO antenna switch connections. + void setGpioReversed(bool gpioReversed = false); + + /// Returns the time in millis since the last preamble was received, and when the last + /// RSSI measurement was made. + uint32_t getLastPreambleTime(); + + /// The maximum message length supported by this driver + /// \return The maximum message length supported by this driver + uint8_t maxMessageLength(); + + /// Sets the radio into low-power sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. + /// \return true if sleep mode was successfully entered. + virtual bool sleep(); + +protected: + /// This is a low level function to handle the interrupts for one instance of RH_RF22. + /// Called automatically by isr*() + /// Should not need to be called. + void handleInterrupt(); + + /// Clears the receiver buffer. + /// Internal use only + void clearRxBuf(); + + /// Clears the transmitter buffer + /// Internal use only + void clearTxBuf(); + + /// Fills the transmitter buffer with the data of a mesage to be sent + /// \param[in] data Array of data bytes to be sent (1 to 255) + /// \param[in] len Number of data bytes in data (> 0) + /// \return true if the message length is valid + bool fillTxBuf(const uint8_t* data, uint8_t len); + + /// Appends the transmitter buffer with the data of a mesage to be sent + /// \param[in] data Array of data bytes to be sent (0 to 255) + /// \param[in] len Number of data bytes in data + /// \return false if the resulting message would exceed RH_RF22_MAX_MESSAGE_LEN, else true + bool appendTxBuf(const uint8_t* data, uint8_t len); + + /// Internal function to load the next fragment of + /// the current message into the transmitter FIFO + /// Internal use only + void sendNextFragment(); + + /// function to copy the next fragment from + /// the receiver FIF) into the receiver buffer + void readNextFragment(); + + /// Clears the RF22 Rx and Tx FIFOs + /// Internal use only + void resetFifos(); + + /// Clears the RF22 Rx FIFO + /// Internal use only + void resetRxFifo(); + + /// Clears the RF22 Tx FIFO + /// Internal use only + void resetTxFifo(); + + /// This function will be called by handleInterrupt() if an RF22 external interrupt occurs. + /// This can only happen if external interrupts are enabled in the RF22 + /// (which they are not by default). + /// Subclasses may override this function to get control when an RF22 external interrupt occurs. + virtual void handleExternalInterrupt(); + + /// This function will be called by handleInterrupt() if an RF22 wakeup timer interrupt occurs. + /// This can only happen if wakeup timer interrupts are enabled in theRF22 + /// (which they are not by default). + /// Subclasses may override this function to get control when an RF22 wakeup timer interrupt occurs. + virtual void handleWakeupTimerInterrupt(); + + /// Start the transmission of the contents + /// of the Tx buffer + void startTransmit(); + + /// ReStart the transmission of the contents + /// of the Tx buffer after a atransmission failure + void restartTransmit(); + + void setThisAddress(uint8_t thisAddress); + + /// Sets the radio operating mode for the case when the driver is idle (ie not + /// transmitting or receiving), allowing you to control the idle mode power requirements + /// at the expense of slower transitions to transmit and receive modes. + /// By default, the idle mode is RH_RF22_XTON, + /// but eg setIdleMode(RH_RF22_PLL) will provide a much lower + /// idle current but slower transitions. Call this function after init(). + /// \param[in] idleMode The chip operating mode to use when the driver is idle. One of the valid definitions for RH_RF22_REG_07_OPERATING_MODE + void setIdleMode(uint8_t idleMode); + +#if RH_PLATFORM == RH_PLATFORM_ESP8266 + /// \brief Method only for ESP8266 to avoid SPI calls from the ISRs + /// + /// This method is used only with ESP8266 platform and must be called from + /// the main loop. It checks if the Isr flags have been asserted and calls, + /// from the main loop, the interrupt handler methods (where SPI calls are + /// needed to process the communication). + void loopIsr(); +#endif + +protected: + /// Low level interrupt service routine for RF22 connected to interrupt 0 + static void isr0(); + + /// Low level interrupt service routine for RF22 connected to interrupt 1 + static void isr1(); + + /// Low level interrupt service routine for RF22 connected to interrupt 1 + static void isr2(); + + /// Array of instances connected to interrupts 0 and 1 + static RH_RF22* _deviceForInterrupt[]; + + /// Index of next interrupt number to use in _deviceForInterrupt + static uint8_t _interruptCount; + + /// The configured interrupt pin connected to this instance + uint8_t _interruptPin; + + /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) + /// else 0xff + uint8_t _myInterruptIndex; + + /// The radio mode to use when mode is idle + uint8_t _idleMode; + + /// The device type reported by the RF22 + uint8_t _deviceType; + + /// The selected CRC polynomial + CRCPolynomial _polynomial; + + // These volatile members may get changed in the interrupt service routine + /// Number of octets in the receiver buffer + volatile uint8_t _bufLen; + + /// The receiver buffer + uint8_t _buf[RH_RF22_MAX_MESSAGE_LEN]; + + /// True when there is a valid message in the Rx buffer + volatile bool _rxBufValid; + + /// Index into TX buffer of the next to send chunk + volatile uint8_t _txBufSentIndex; + + /// Time in millis since the last preamble was received (and the last time the RSSI was measured) + uint32_t _lastPreambleTime; +}; + +/// @example rf22_client.ino +/// @example rf22_server.ino +/// @example rf22_cw.ino + +#endif diff --git a/RH_RF24.cpp b/RH_RF24.cpp new file mode 100644 index 0000000..ebe43c1 --- /dev/null +++ b/RH_RF24.cpp @@ -0,0 +1,1060 @@ +// RH_RF24.cpp +// +// Copyright (C) 2011 Mike McCauley +// $Id: RH_RF24.cpp,v 1.24 2019/09/02 05:21:52 mikem Exp $ + +#include + +// Use one of the pre-built radio configuration files +// You can use other WDS generated sample configs accorinding to your needs +// or generate a custom one with WDS and include it here +// See RF24configs/README for file name encoding standard +//#include "RF24configs/radio_config_Si4464_27_434_2GFSK_5_10.h" +#include "RF24configs/radio_config_Si4464_30_434_2GFSK_5_10.h" +//#include "RF24configs/radio_config_Si4464_30_434_2GFSK_10_20.h" +//#include "RF24configs/radio_config_Si4464_30_915_2GFSK_5_10.h" +//#include "RF24configs/radio_config_Si4464_30_915_2GFSK_10_20.h" + + +// Interrupt vectors for the 3 Arduino interrupt pins +// Each interrupt can be handled by a different instance of RH_RF24, allowing you to have +// 2 or more RF24s per Arduino +RH_RF24* RH_RF24::_deviceForInterrupt[RH_RF24_NUM_INTERRUPTS] = {0, 0, 0}; +uint8_t RH_RF24::_interruptCount = 0; // Index into _deviceForInterrupt for next device + +// This configuration data is defined in radio_config_Si4460.h +// which was generated with the Silicon Labs WDS program +PROGMEM const uint8_t RF24_CONFIGURATION_DATA[] = RADIO_CONFIGURATION_DATA_ARRAY; + +RH_RF24::RH_RF24(uint8_t slaveSelectPin, uint8_t interruptPin, uint8_t sdnPin, RHGenericSPI& spi) + : + RHSPIDriver(slaveSelectPin, spi) +{ + _interruptPin = interruptPin; + _sdnPin = sdnPin; + _idleMode = RH_RF24_DEVICE_STATE_READY; + _myInterruptIndex = 0xff; // Not allocated yet +} + +void RH_RF24::setIdleMode(uint8_t idleMode) +{ + _idleMode = idleMode; +} + +bool RH_RF24::init() +{ + if (!RHSPIDriver::init()) + return false; + + // Determine the interrupt number that corresponds to the interruptPin + int interruptNumber = digitalPinToInterrupt(_interruptPin); + if (interruptNumber == NOT_AN_INTERRUPT) + return false; +#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER + interruptNumber = _interruptPin; +#endif + + // Tell the low level SPI interface we will use SPI within this interrupt + spiUsingInterrupt(interruptNumber); + + // Initialise the radio + power_on_reset(); + cmd_clear_all_interrupts(); + + // Get the device type and check it + // This also tests whether we are really connected to a device + uint8_t buf[8]; + if (!command(RH_RF24_CMD_PART_INFO, 0, 0, buf, sizeof(buf))) + return false; // SPI error? Not connected? + _deviceType = (buf[1] << 8) | buf[2]; + // Check PART to be either 0x4460, 0x4461, 0x4463, 0x4464 + if (_deviceType != 0x4460 && + _deviceType != 0x4461 && + _deviceType != 0x4463 && + _deviceType != 0x4464) + return false; // Unknown radio type, or not connected + + // Here we use a configuration generated by the Silicon Labs Wireless Development Suite + // #included above + // We override a few things later that we ned to be sure of. + configure(RF24_CONFIGURATION_DATA); + + // Add by Adrien van den Bossche for Teensy + // ARM M4 requires the below. else pin interrupt doesn't work properly. + // On all other platforms, its innocuous, belt and braces + pinMode(_interruptPin, INPUT); + + // Set up interrupt handler + // Since there are a limited number of interrupt glue functions isr*() available, + // we can only support a limited number of devices simultaneously + // ON some devices, notably most Arduinos, the interrupt pin passed in is actuallt the + // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping + // yourself based on knwledge of what Arduino board you are running on. + if (_myInterruptIndex == 0xff) + { + // First run, no interrupt allocated yet + if (_interruptCount <= RH_RF24_NUM_INTERRUPTS) + _myInterruptIndex = _interruptCount++; + else + return false; // Too many devices, not enough interrupt vectors + } + _deviceForInterrupt[_myInterruptIndex] = this; + if (_myInterruptIndex == 0) + attachInterrupt(interruptNumber, isr0, FALLING); + else if (_myInterruptIndex == 1) + attachInterrupt(interruptNumber, isr1, FALLING); + else if (_myInterruptIndex == 2) + attachInterrupt(interruptNumber, isr2, FALLING); + else + return false; // Too many devices, not enough interrupt vectors + + // Ensure we get the interrupts we need, irrespective of whats in the radio_config + uint8_t int_ctl[] = {RH_RF24_MODEM_INT_STATUS_EN | RH_RF24_PH_INT_STATUS_EN, 0xff, 0xff, 0x00 }; + set_properties(RH_RF24_PROPERTY_INT_CTL_ENABLE, int_ctl, sizeof(int_ctl)); + + // RSSI Latching should be configured in MODEM_RSSI_CONTROL in radio_config + + // PKT_TX_THRESHOLD and PKT_RX_THRESHOLD should be set to about 0x30 in radio_config + + // Configure important RH_RF24 registers + // Here we set up the standard packet format for use by the RH_RF24 library: + // We will use FIFO Mode, with automatic packet generation + // We have 2 fields: + // Field 1 contains only the (variable) length of field 2, with CRC + // Field 2 contains the variable length payload and the CRC + // Hmmm, having no CRC on field 1 and CRC on field 2 causes CRC errors when resetting after an odd + // number of packets! Anyway its prob a good thing at the cost of some airtime. + // Hmmm, enabling WHITEN stops it working! + uint8_t pkt_config1[] = { 0x00 }; + set_properties(RH_RF24_PROPERTY_PKT_CONFIG1, pkt_config1, sizeof(pkt_config1)); + + uint8_t pkt_len[] = { 0x02, 0x01, 0x00 }; + set_properties(RH_RF24_PROPERTY_PKT_LEN, pkt_len, sizeof(pkt_len)); + + uint8_t pkt_field1[] = { 0x00, 0x01, 0x00, RH_RF24_FIELD_CONFIG_CRC_START | RH_RF24_FIELD_CONFIG_SEND_CRC | RH_RF24_FIELD_CONFIG_CHECK_CRC | RH_RF24_FIELD_CONFIG_CRC_ENABLE }; + set_properties(RH_RF24_PROPERTY_PKT_FIELD_1_LENGTH_12_8, pkt_field1, sizeof(pkt_field1)); + + uint8_t pkt_field2[] = { 0x00, sizeof(_buf), 0x00, RH_RF24_FIELD_CONFIG_CRC_START | RH_RF24_FIELD_CONFIG_SEND_CRC | RH_RF24_FIELD_CONFIG_CHECK_CRC | RH_RF24_FIELD_CONFIG_CRC_ENABLE }; + set_properties(RH_RF24_PROPERTY_PKT_FIELD_2_LENGTH_12_8, pkt_field2, sizeof(pkt_field2)); + + // Clear all other fields so they are never used, irrespective of the radio_config + uint8_t pkt_fieldn[] = { 0x00, 0x00, 0x00, 0x00 }; + set_properties(RH_RF24_PROPERTY_PKT_FIELD_3_LENGTH_12_8, pkt_fieldn, sizeof(pkt_fieldn)); + set_properties(RH_RF24_PROPERTY_PKT_FIELD_4_LENGTH_12_8, pkt_fieldn, sizeof(pkt_fieldn)); + set_properties(RH_RF24_PROPERTY_PKT_FIELD_5_LENGTH_12_8, pkt_fieldn, sizeof(pkt_fieldn)); + + // The following can be changed later by the user if necessary. + // Set up default configuration + setCRCPolynomial(CRC_16_IBM); + uint8_t syncwords[] = { 0x2d, 0xd4 }; + setSyncWords(syncwords, sizeof(syncwords)); // Same as RF22's + // 3 would be sufficient, but this is the same as RF22's + // actualy, 4 seems to work much better for some modulations + setPreambleLength(4); + // Default freq comes from the radio config file + // About 2.4dBm on RFM24: + setTxPower(0x10); + + return true; +} + +// C++ level interrupt handler for this instance +void RH_RF24::handleInterrupt() +{ + uint8_t status[8]; + command(RH_RF24_CMD_GET_INT_STATUS, NULL, 0, status, sizeof(status)); + + // Decode and handle the interrupt bits we are interested in +// if (status[0] & RH_RF24_INT_STATUS_CHIP_INT_STATUS) + if (status[0] & RH_RF24_INT_STATUS_MODEM_INT_STATUS) + { +// if (status[4] & RH_RF24_INT_STATUS_INVALID_PREAMBLE) + if (status[4] & RH_RF24_INT_STATUS_INVALID_SYNC) + { + // After INVALID_SYNC, sometimes the radio gets into a silly state and subsequently reports it for every packet + // Need to reset the radio and clear the RX FIFO, cause sometimes theres junk there too + _mode = RHModeIdle; + clearRxFifo(); + clearBuffer(); + } + } + if (status[0] & RH_RF24_INT_STATUS_PH_INT_STATUS) + { + if (status[2] & RH_RF24_INT_STATUS_CRC_ERROR) + { + // CRC Error + // Radio automatically went to _idleMode + _mode = RHModeIdle; + _rxBad++; + + clearRxFifo(); + clearBuffer(); + } + if (status[2] & RH_RF24_INT_STATUS_PACKET_SENT) + { + _txGood++; + // Transmission does not automatically clear the tx buffer. + // Could retransmit if we wanted + // RH_RF24 configured to transition automatically to Idle after packet sent + _mode = RHModeIdle; + clearBuffer(); + } + if (status[2] & RH_RF24_INT_STATUS_PACKET_RX) + { + // A complete message has been received with good CRC + // Get the RSSI, configured to latch at sync detect in radio_config + uint8_t modem_status[6]; + command(RH_RF24_CMD_GET_MODEM_STATUS, NULL, 0, modem_status, sizeof(modem_status)); + _lastRssi = modem_status[3]; + _lastPreambleTime = millis(); + + // Save it in our buffer + readNextFragment(); + // And see if we have a valid message + validateRxBuf(); + // Radio will have transitioned automatically to the _idleMode + _mode = RHModeIdle; + } + if (status[2] & RH_RF24_INT_STATUS_TX_FIFO_ALMOST_EMPTY) + { + // TX FIFO almost empty, maybe send another chunk, if there is one + sendNextFragment(); + } + if (status[2] & RH_RF24_INT_STATUS_RX_FIFO_ALMOST_FULL) + { + // Some more data to read, get it + readNextFragment(); + } + } +} + +// Check whether the latest received message is complete and uncorrupted +void RH_RF24::validateRxBuf() +{ + // Validate headers etc + if (_bufLen >= RH_RF24_HEADER_LEN) + { + _rxHeaderTo = _buf[0]; + _rxHeaderFrom = _buf[1]; + _rxHeaderId = _buf[2]; + _rxHeaderFlags = _buf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + // Its for us + _rxGood++; + _rxBufValid = true; + } + } +} + +bool RH_RF24::clearRxFifo() +{ + uint8_t fifo_clear[] = { 0x02 }; + return command(RH_RF24_CMD_FIFO_INFO, fifo_clear, sizeof(fifo_clear)); +} + +void RH_RF24::clearBuffer() +{ + _bufLen = 0; + _txBufSentIndex = 0; + _rxBufValid = false; +} + +// These are low level functions that call the interrupt handler for the correct +// instance of RH_RF24. +// 3 interrupts allows us to have 3 different devices +void RH_INTERRUPT_ATTR RH_RF24::isr0() +{ + if (_deviceForInterrupt[0]) + _deviceForInterrupt[0]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_RF24::isr1() +{ + if (_deviceForInterrupt[1]) + _deviceForInterrupt[1]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_RF24::isr2() +{ + if (_deviceForInterrupt[2]) + _deviceForInterrupt[2]->handleInterrupt(); +} + +bool RH_RF24::available() +{ + if (_mode == RHModeTx) + return false; + if (!_rxBufValid) + setModeRx(); // Make sure we are receiving + return _rxBufValid; +} + +bool RH_RF24::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + // CAUTION: first 4 octets of _buf contain the headers + if (buf && len && (_bufLen >= RH_RF24_HEADER_LEN)) + { + ATOMIC_BLOCK_START; + if (*len > _bufLen - RH_RF24_HEADER_LEN) + *len = _bufLen - RH_RF24_HEADER_LEN; + memcpy(buf, _buf + RH_RF24_HEADER_LEN, *len); + ATOMIC_BLOCK_END; + } + clearBuffer(); // Got the most recent message + return true; +} + +bool RH_RF24::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_RF24_MAX_MESSAGE_LEN) + return false; + + waitPacketSent(); // Make sure we dont interrupt an outgoing message + setModeIdle(); // Prevent RX while filling the fifo + + if (!waitCAD()) + return false; // Check channel activity + + // Put the payload in the FIFO + // First the length in fixed length field 1. This wont appear in the receiver fifo since + // we have turned off IN_FIFO in PKT_LEN + _buf[0] = len + RH_RF24_HEADER_LEN; + // Now the rest of the payload in variable length field 2 + // First the headers + _buf[1] = _txHeaderTo; + _buf[2] = _txHeaderFrom; + _buf[3] = _txHeaderId; + _buf[4] = _txHeaderFlags; + // Then the message + memcpy(_buf + 1 + RH_RF24_HEADER_LEN, data, len); + _bufLen = len + 1 + RH_RF24_HEADER_LEN; + _txBufSentIndex = 0; + + // Set the field 2 length to the variable payload length + uint8_t l[] = { (uint8_t)(len + RH_RF24_HEADER_LEN)}; + set_properties(RH_RF24_PROPERTY_PKT_FIELD_2_LENGTH_7_0, l, sizeof(l)); + + sendNextFragment(); + setModeTx(); + return true; +} + +// This is different to command() since we must not wait for CTS +bool RH_RF24::writeTxFifo(uint8_t *data, uint8_t len) +{ + ATOMIC_BLOCK_START; + // First send the command + digitalWrite(_slaveSelectPin, LOW); + _spi.beginTransaction(); + _spi.transfer(RH_RF24_CMD_TX_FIFO_WRITE); + // Now write any write data + while (len--) + _spi.transfer(*data++); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + ATOMIC_BLOCK_END; + return true; +} + +void RH_RF24::sendNextFragment() +{ + if (_txBufSentIndex < _bufLen) + { + // Some left to send? + uint8_t len = _bufLen - _txBufSentIndex; + // But dont send too much, see how much room is left + uint8_t fifo_info[2]; + command(RH_RF24_CMD_FIFO_INFO, NULL, 0, fifo_info, sizeof(fifo_info)); + // fifo_info[1] is space left in TX FIFO + if (len > fifo_info[1]) + len = fifo_info[1]; + + writeTxFifo(_buf + _txBufSentIndex, len); + _txBufSentIndex += len; + } +} + +void RH_RF24::readNextFragment() +{ + // Get the packet length from the RX FIFO length + uint8_t fifo_info[1]; + command(RH_RF24_CMD_FIFO_INFO, NULL, 0, fifo_info, sizeof(fifo_info)); + uint8_t fifo_len = fifo_info[0]; + + // Check for overflow + if ((_bufLen + fifo_len) > sizeof(_buf)) + { + // Overflow pending + _rxBad++; + setModeIdle(); + clearRxFifo(); + clearBuffer(); + return; + } + // So we have room + // Now read the fifo_len bytes from the RX FIFO + // This is different to command() since we dont wait for CTS + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + _spi.transfer(RH_RF24_CMD_RX_FIFO_READ); + uint8_t* p = _buf + _bufLen; + uint8_t l = fifo_len; + while (l--) + *p++ = _spi.transfer(0); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + _bufLen += fifo_len; +} + +uint8_t RH_RF24::maxMessageLength() +{ + return RH_RF24_MAX_MESSAGE_LEN; +} + +// Sets registers from a canned modem configuration structure +void RH_RF24::setModemRegisters(const ModemConfig* config) +{ +#ifdef RH_HAVE_SERIAL + Serial.println("Programming Error: setModemRegisters is obsolete. Generate custom radio config file with WDS instead"); +#endif + (void)config; // Prevent warnings +} + +// Set one of the canned Modem configs +// Returns true if its a valid choice +bool RH_RF24::setModemConfig(ModemConfigChoice index) +{ +#ifdef RH_HAVE_SERIAL + Serial.println("Programming Error: setModemRegisters is obsolete. Generate custom radio config file with WDS instead"); + (void)index; // Prevent warnings +#endif + return false; +} + +void RH_RF24::setPreambleLength(uint16_t bytes) +{ + uint8_t config[] = { (uint8_t)bytes, 0x14, 0x00, 0x00, + RH_RF24_PREAMBLE_FIRST_1 | RH_RF24_PREAMBLE_LENGTH_BYTES | RH_RF24_PREAMBLE_STANDARD_1010}; + set_properties(RH_RF24_PROPERTY_PREAMBLE_TX_LENGTH, config, sizeof(config)); +} + +bool RH_RF24::setCRCPolynomial(CRCPolynomial polynomial) +{ + if (polynomial >= CRC_NONE && + polynomial <= CRC_Castagnoli) + { + // Caution this only has effect if CRCs are enabled + uint8_t config[] = { (uint8_t)((polynomial & RH_RF24_CRC_MASK) | RH_RF24_CRC_SEED_ALL_1S) }; + return set_properties(RH_RF24_PROPERTY_PKT_CRC_CONFIG, config, sizeof(config)); + } + else + return false; +} + +void RH_RF24::setSyncWords(const uint8_t* syncWords, uint8_t len) +{ + if (len > 4 || len < 1) + return; + uint8_t config[] = { (uint8_t)(len-1), 0, 0, 0, 0}; + memcpy(config+1, syncWords, len); + set_properties(RH_RF24_PROPERTY_SYNC_CONFIG, config, sizeof(config)); +} + +bool RH_RF24::setFrequency(float centre, float afcPullInRange) +{ + (void)afcPullInRange; // Not used + // See Si446x Data Sheet section 5.3.1 + // Also the Si446x PLL Synthesizer / VCO_CNT Calculator Rev 0.4 + uint8_t outdiv; + uint8_t band; + if (_deviceType == 0x4460 || + _deviceType == 0x4461 || + _deviceType == 0x4463) + { + // Non-continuous frequency bands + if (centre <= 1050.0 && centre >= 850.0) + outdiv = 4, band = 0; + else if (centre <= 525.0 && centre >= 425.0) + outdiv = 8, band = 2; + else if (centre <= 350.0 && centre >= 284.0) + outdiv = 12, band = 3; + else if (centre <= 175.0 && centre >= 142.0) + outdiv = 24, band = 5; + else + return false; + } + else + { + // 0x4464 + // Continuous frequency bands + if (centre <= 960.0 && centre >= 675.0) + outdiv = 4, band = 1; + else if (centre < 675.0 && centre >= 450.0) + outdiv = 6, band = 2; + else if (centre < 450.0 && centre >= 338.0) + outdiv = 8, band = 3; + else if (centre < 338.0 && centre >= 225.0) + outdiv = 12, band = 4; + else if (centre < 225.0 && centre >= 169.0) + outdiv = 16, band = 4; + else if (centre < 169.0 && centre >= 119.0) + outdiv = 24, band = 5; + else + return false; + } + + // Set the MODEM_CLKGEN_BAND (not documented) + uint8_t modem_clkgen[] = { (uint8_t)(band + 8) }; + if (!set_properties(RH_RF24_PROPERTY_MODEM_CLKGEN_BAND, modem_clkgen, sizeof(modem_clkgen))) + return false; + + centre *= 1000000.0; // Convert to Hz + + // Now generate the RF frequency properties + // Need the Xtal/XO freq from the radio_config file: + uint32_t xtal_frequency = RADIO_CONFIGURATION_DATA_RADIO_XO_FREQ; + unsigned long f_pfd = 2 * xtal_frequency / outdiv; + unsigned int n = ((unsigned int)(centre / f_pfd)) - 1; + float ratio = centre / (float)f_pfd; + float rest = ratio - (float)n; + unsigned long m = (unsigned long)(rest * 524288UL); + unsigned int m2 = m / 0x10000; + unsigned int m1 = (m - m2 * 0x10000) / 0x100; + unsigned int m0 = (m - m2 * 0x10000 - m1 * 0x100); + + // PROP_FREQ_CONTROL_GROUP + uint8_t freq_control[] = { (uint8_t)n, (uint8_t)m2, (uint8_t)m1, (uint8_t)m0 }; + return set_properties(RH_RF24_PROPERTY_FREQ_CONTROL_INTE, freq_control, sizeof(freq_control)); +} + +void RH_RF24::setModeIdle() +{ + if (_mode != RHModeIdle) + { + // Set the antenna switch pins using the GPIO, assuming we have an RFM module with antenna switch + uint8_t config[] = { RH_RF24_GPIO_HIGH, RH_RF24_GPIO_HIGH }; + command(RH_RF24_CMD_GPIO_PIN_CFG, config, sizeof(config)); + + uint8_t state[] = { _idleMode }; + command(RH_RF24_CMD_CHANGE_STATE, state, sizeof(state)); + _mode = RHModeIdle; + } +} + +bool RH_RF24::sleep() +{ + if (_mode != RHModeSleep) + { + // This will change to SLEEP or STANDBY, depending on the value of GLOBAL_CLK_CFG:CLK_32K_SEL. + // which default to 0, eg STANDBY + uint8_t state[] = { RH_RF24_DEVICE_STATE_SLEEP }; + command(RH_RF24_CMD_CHANGE_STATE, state, sizeof(state)); + + _mode = RHModeSleep; + } + return true; +} + +void RH_RF24::setModeRx() +{ + if (_mode != RHModeRx) + { + // CAUTION: we cant clear the rx buffers here, else we set up a race condition + // with the _rxBufValid test in available() + + // Tell the receiver the max data length we will accept (a TX may have changed it) + uint8_t l[] = { sizeof(_buf) }; + set_properties(RH_RF24_PROPERTY_PKT_FIELD_2_LENGTH_7_0, l, sizeof(l)); + + // Set the antenna switch pins using the GPIO, assuming we have an RFM module with antenna switch + uint8_t gpio_config[] = { RH_RF24_GPIO_HIGH, RH_RF24_GPIO_LOW }; + command(RH_RF24_CMD_GPIO_PIN_CFG, gpio_config, sizeof(gpio_config)); + + uint8_t rx_config[] = { 0x00, RH_RF24_CONDITION_RX_START_IMMEDIATE, 0x00, 0x00, _idleMode, _idleMode, _idleMode}; + command(RH_RF24_CMD_START_RX, rx_config, sizeof(rx_config)); + _mode = RHModeRx; + } +} + +void RH_RF24::setModeTx() +{ + if (_mode != RHModeTx) + { + // Set the antenna switch pins using the GPIO, assuming we have an RFM module with antenna switch + uint8_t config[] = { RH_RF24_GPIO_LOW, RH_RF24_GPIO_HIGH }; + command(RH_RF24_CMD_GPIO_PIN_CFG, config, sizeof(config)); + + uint8_t tx_params[] = { 0x00, + (uint8_t)((_idleMode << 4) | RH_RF24_CONDITION_RETRANSMIT_NO | RH_RF24_CONDITION_START_IMMEDIATE)}; + command(RH_RF24_CMD_START_TX, tx_params, sizeof(tx_params)); + _mode = RHModeTx; + } +} + +void RH_RF24::setTxPower(uint8_t power) +{ + uint8_t pa_bias_clkduty = 0; + // These calculations valid for advertised power from Si chips at Vcc = 3.3V + // you may get lower power from RFM modules, depending on Vcc voltage, antenna etc + if (_deviceType == 0x4460) + { + // 0x4f = 13dBm + pa_bias_clkduty = 0xc0; + if (power > 0x4f) + power = 0x4f; + } + else if (_deviceType == 0x4461) + { + // 0x7f = 16dBm + pa_bias_clkduty = 0xc0; + if (power > 0x7f) + power = 0x7f; + } + else if (_deviceType == 0x4463 || _deviceType == 0x4464 ) + { + // 0x7f = 20dBm + pa_bias_clkduty = 0x00; // Per WDS suggestion + if (power > 0x7f) + power = 0x7f; + } + uint8_t power_properties[] = {0x08, 0x00, 0x00 }; // PA_MODE from WDS sugggestions (why?) + power_properties[1] = power; + power_properties[2] = pa_bias_clkduty; + set_properties(RH_RF24_PROPERTY_PA_MODE, power_properties, sizeof(power_properties)); +} + +// Caution: There was a bug in A1 hardware that will not handle 1 byte commands. +bool RH_RF24::command(uint8_t cmd, const uint8_t* write_buf, uint8_t write_len, uint8_t* read_buf, uint8_t read_len) +{ + bool done = false; + + ATOMIC_BLOCK_START; + // First send the command + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + _spi.transfer(cmd); + + // Now write any write data + if (write_buf && write_len) + { + while (write_len--) + _spi.transfer(*write_buf++); + } + // Sigh, the RFM26 at least has problems if we deselect too quickly :-( + // Innocuous timewaster: + digitalWrite(_slaveSelectPin, LOW); + // And finalise the command + digitalWrite(_slaveSelectPin, HIGH); + + uint16_t count; // Number of times we have tried to get CTS + for (count = 0; !done && count < RH_RF24_CTS_RETRIES; count++) + { + // Wait for the CTS + digitalWrite(_slaveSelectPin, LOW); + + _spi.transfer(RH_RF24_CMD_READ_BUF); + if (_spi.transfer(0) == RH_RF24_REPLY_CTS) + { + // Now read any expected reply data + if (read_buf && read_len) + { + while (read_len--) + *read_buf++ = _spi.transfer(0); + } + done = true; + } + // Sigh, the RFM26 at least has problems if we deselect too quickly :-( + // Innocuous timewaster: + digitalWrite(_slaveSelectPin, LOW); + // Finalise the read + digitalWrite(_slaveSelectPin, HIGH); + } + _spi.endTransaction(); + ATOMIC_BLOCK_END; + return done; // False if too many attempts at CTS +} + +bool RH_RF24::configure(const uint8_t* commands) +{ + // Command strings are constructed in radio_config_Si4460.h + // Each command starts with a count of the bytes in that command: + // + uint8_t next_cmd_len; + + while (memcpy_P(&next_cmd_len, commands, 1), next_cmd_len > 0) + { + uint8_t buf[20]; // As least big as the biggest permitted command/property list of 15 + memcpy_P(buf, commands+1, next_cmd_len); + command(buf[0], buf+1, next_cmd_len - 1); + commands += (next_cmd_len + 1); + } + return true; +} + +void RH_RF24::power_on_reset() +{ + // Sigh: its necessary to control the SDN pin to reset this ship. + // Tying it to GND does not produce reliable startups + // Per Si4464 Data Sheet 3.3.2 + digitalWrite(_sdnPin, HIGH); // So we dont get a glitch after setting pinMode OUTPUT + pinMode(_sdnPin, OUTPUT); + delay(10); + digitalWrite(_sdnPin, LOW); + delay(10); +} + +bool RH_RF24::cmd_clear_all_interrupts() +{ + uint8_t write_buf[] = { 0x00, 0x00, 0x00 }; + return command(RH_RF24_CMD_GET_INT_STATUS, write_buf, sizeof(write_buf)); +} + +bool RH_RF24::set_properties(uint16_t firstProperty, const uint8_t* values, uint8_t count) +{ + uint8_t buf[15]; + + buf[0] = firstProperty >> 8; // GROUP + buf[1] = count; // NUM_PROPS + buf[2] = firstProperty & 0xff; // START_PROP + uint8_t i; + for (i = 0; i < 12 && i < count; i++) + buf[3 + i] = values[i]; // DATAn + return command(RH_RF24_CMD_SET_PROPERTY, buf, count + 3); +} + +bool RH_RF24::get_properties(uint16_t firstProperty, uint8_t* values, uint8_t count) +{ + if (count > 16) + count = 16; + uint8_t buf[3]; + buf[0] = firstProperty >> 8; // GROUP + buf[1] = count; // NUM_PROPS + buf[2] = firstProperty & 0xff; // START_PROP + return command(RH_RF24_CMD_GET_PROPERTY, buf, sizeof(buf), values, count); +} + +float RH_RF24::get_temperature() +{ + uint8_t write_buf[] = { 0x10 }; + uint8_t read_buf[8]; + // Takes nearly 4ms + command(RH_RF24_CMD_GET_ADC_READING, write_buf, sizeof(write_buf), read_buf, sizeof(read_buf)); + uint16_t temp_adc = (read_buf[4] << 8) | read_buf[5]; + return ((800 + read_buf[6]) / 4096.0) * temp_adc - ((read_buf[7] / 2) + 256); +} + +float RH_RF24::get_battery_voltage() +{ + uint8_t write_buf[] = { 0x08 }; + uint8_t read_buf[8]; + // Takes nearly 4ms + command(RH_RF24_CMD_GET_ADC_READING, write_buf, sizeof(write_buf), read_buf, sizeof(read_buf)); + uint16_t battery_adc = (read_buf[2] << 8) | read_buf[3]; + return 3.0 * battery_adc / 1280; +} + +float RH_RF24::get_gpio_voltage(uint8_t gpio) +{ + uint8_t write_buf[] = { 0x04 }; + uint8_t read_buf[8]; + write_buf[0] |= (gpio & 0x3); + // Takes nearly 4ms + command(RH_RF24_CMD_GET_ADC_READING, write_buf, sizeof(write_buf), read_buf, sizeof(read_buf)); + uint16_t gpio_adc = (read_buf[0] << 8) | read_buf[1]; + return 3.0 * gpio_adc / 1280; +} + +uint8_t RH_RF24::frr_read(uint8_t reg) +{ + uint8_t ret = 0; // Init to prevent compiler warnings + + // Do not wait for CTS + ATOMIC_BLOCK_START; + // First send the command + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + _spi.transfer(RH_RF24_PROPERTY_FRR_CTL_A_MODE + reg); + // Get the fast response + ret = _spi.transfer(0); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + ATOMIC_BLOCK_END; + return ret; +} + +// List of command replies to be printed by prinRegisters() +PROGMEM static const RH_RF24::CommandInfo commands[] = +{ + { RH_RF24_CMD_PART_INFO, 8 }, + { RH_RF24_CMD_FUNC_INFO, 6 }, + { RH_RF24_CMD_GPIO_PIN_CFG, 7 }, + { RH_RF24_CMD_FIFO_INFO, 2 }, + { RH_RF24_CMD_PACKET_INFO, 2 }, + { RH_RF24_CMD_GET_INT_STATUS, 8 }, + { RH_RF24_CMD_GET_PH_STATUS, 2 }, + { RH_RF24_CMD_GET_MODEM_STATUS, 8 }, + { RH_RF24_CMD_GET_CHIP_STATUS, 3 }, + { RH_RF24_CMD_REQUEST_DEVICE_STATE, 2 }, +}; +#define NUM_COMMAND_INFO (sizeof(commands)/sizeof(CommandInfo)) + +// List of properties to be printed by printRegisters() +PROGMEM static const uint16_t properties[] = +{ + RH_RF24_PROPERTY_GLOBAL_XO_TUNE, + RH_RF24_PROPERTY_GLOBAL_CLK_CFG, + RH_RF24_PROPERTY_GLOBAL_LOW_BATT_THRESH, + RH_RF24_PROPERTY_GLOBAL_CONFIG, + RH_RF24_PROPERTY_GLOBAL_WUT_CONFIG, + RH_RF24_PROPERTY_GLOBAL_WUT_M_15_8, + RH_RF24_PROPERTY_GLOBAL_WUT_M_7_0, + RH_RF24_PROPERTY_GLOBAL_WUT_R, + RH_RF24_PROPERTY_GLOBAL_WUT_LDC, + RH_RF24_PROPERTY_INT_CTL_ENABLE, + RH_RF24_PROPERTY_INT_CTL_PH_ENABLE, + RH_RF24_PROPERTY_INT_CTL_MODEM_ENABLE, + RH_RF24_PROPERTY_INT_CTL_CHIP_ENABLE, + RH_RF24_PROPERTY_FRR_CTL_A_MODE, + RH_RF24_PROPERTY_FRR_CTL_B_MODE, + RH_RF24_PROPERTY_FRR_CTL_C_MODE, + RH_RF24_PROPERTY_FRR_CTL_D_MODE, + RH_RF24_PROPERTY_PREAMBLE_TX_LENGTH, + RH_RF24_PROPERTY_PREAMBLE_CONFIG_STD_1, + RH_RF24_PROPERTY_PREAMBLE_CONFIG_NSTD, + RH_RF24_PROPERTY_PREAMBLE_CONFIG_STD_2, + RH_RF24_PROPERTY_PREAMBLE_CONFIG, + RH_RF24_PROPERTY_PREAMBLE_PATTERN_31_24, + RH_RF24_PROPERTY_PREAMBLE_PATTERN_23_16, + RH_RF24_PROPERTY_PREAMBLE_PATTERN_15_8, + RH_RF24_PROPERTY_PREAMBLE_PATTERN_7_0, + RH_RF24_PROPERTY_SYNC_CONFIG, + RH_RF24_PROPERTY_SYNC_BITS_31_24, + RH_RF24_PROPERTY_SYNC_BITS_23_16, + RH_RF24_PROPERTY_SYNC_BITS_15_8, + RH_RF24_PROPERTY_SYNC_BITS_7_0, + RH_RF24_PROPERTY_PKT_CRC_CONFIG, + RH_RF24_PROPERTY_PKT_CONFIG1, + RH_RF24_PROPERTY_PKT_LEN, + RH_RF24_PROPERTY_PKT_LEN_FIELD_SOURCE, + RH_RF24_PROPERTY_PKT_LEN_ADJUST, + RH_RF24_PROPERTY_PKT_TX_THRESHOLD, + RH_RF24_PROPERTY_PKT_RX_THRESHOLD, + RH_RF24_PROPERTY_PKT_FIELD_1_LENGTH_12_8, + RH_RF24_PROPERTY_PKT_FIELD_1_LENGTH_7_0, + RH_RF24_PROPERTY_PKT_FIELD_1_CONFIG, + RH_RF24_PROPERTY_PKT_FIELD_1_CRC_CONFIG, + RH_RF24_PROPERTY_PKT_FIELD_2_LENGTH_12_8, + RH_RF24_PROPERTY_PKT_FIELD_2_LENGTH_7_0, + RH_RF24_PROPERTY_PKT_FIELD_2_CONFIG, + RH_RF24_PROPERTY_PKT_FIELD_2_CRC_CONFIG, + RH_RF24_PROPERTY_PKT_FIELD_3_LENGTH_12_8, + RH_RF24_PROPERTY_PKT_FIELD_3_LENGTH_7_0, + RH_RF24_PROPERTY_PKT_FIELD_3_CONFIG, + RH_RF24_PROPERTY_PKT_FIELD_3_CRC_CONFIG, + RH_RF24_PROPERTY_PKT_FIELD_4_LENGTH_12_8, + RH_RF24_PROPERTY_PKT_FIELD_4_LENGTH_7_0, + RH_RF24_PROPERTY_PKT_FIELD_4_CONFIG, + RH_RF24_PROPERTY_PKT_FIELD_4_CRC_CONFIG, + RH_RF24_PROPERTY_PKT_FIELD_5_LENGTH_12_8, + RH_RF24_PROPERTY_PKT_FIELD_5_LENGTH_7_0, + RH_RF24_PROPERTY_PKT_FIELD_5_CONFIG, + RH_RF24_PROPERTY_PKT_FIELD_5_CRC_CONFIG, + RH_RF24_PROPERTY_PKT_RX_FIELD_1_LENGTH_12_8, + RH_RF24_PROPERTY_PKT_RX_FIELD_1_LENGTH_7_0, + RH_RF24_PROPERTY_PKT_RX_FIELD_1_CONFIG, + RH_RF24_PROPERTY_PKT_RX_FIELD_1_CRC_CONFIG, + RH_RF24_PROPERTY_PKT_RX_FIELD_2_LENGTH_12_8, + RH_RF24_PROPERTY_PKT_RX_FIELD_2_LENGTH_7_0, + RH_RF24_PROPERTY_PKT_RX_FIELD_2_CONFIG, + RH_RF24_PROPERTY_PKT_RX_FIELD_2_CRC_CONFIG, + RH_RF24_PROPERTY_PKT_RX_FIELD_3_LENGTH_12_8, + RH_RF24_PROPERTY_PKT_RX_FIELD_3_LENGTH_7_0, + RH_RF24_PROPERTY_PKT_RX_FIELD_3_CONFIG, + RH_RF24_PROPERTY_PKT_RX_FIELD_3_CRC_CONFIG, + RH_RF24_PROPERTY_PKT_RX_FIELD_4_LENGTH_12_8, + RH_RF24_PROPERTY_PKT_RX_FIELD_4_LENGTH_7_0, + RH_RF24_PROPERTY_PKT_RX_FIELD_4_CONFIG, + RH_RF24_PROPERTY_PKT_RX_FIELD_4_CRC_CONFIG, + RH_RF24_PROPERTY_PKT_RX_FIELD_5_LENGTH_12_8, + RH_RF24_PROPERTY_PKT_RX_FIELD_5_LENGTH_7_0, + RH_RF24_PROPERTY_PKT_RX_FIELD_5_CONFIG, + RH_RF24_PROPERTY_PKT_RX_FIELD_5_CRC_CONFIG, + RH_RF24_PROPERTY_MODEM_MOD_TYPE, + RH_RF24_PROPERTY_MODEM_MAP_CONTROL, + RH_RF24_PROPERTY_MODEM_DSM_CTRL, + RH_RF24_PROPERTY_MODEM_DATA_RATE_2, + RH_RF24_PROPERTY_MODEM_DATA_RATE_1, + RH_RF24_PROPERTY_MODEM_DATA_RATE_0, + RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_3, + RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_2, + RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_1, + RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_0, + RH_RF24_PROPERTY_MODEM_FREQ_DEV_2, + RH_RF24_PROPERTY_MODEM_FREQ_DEV_1, + RH_RF24_PROPERTY_MODEM_FREQ_DEV_0, + RH_RF24_PROPERTY_MODEM_TX_RAMP_DELAY, + RH_RF24_PROPERTY_MODEM_MDM_CTRL, + RH_RF24_PROPERTY_MODEM_IF_CONTROL, + RH_RF24_PROPERTY_MODEM_IF_FREQ_2, + RH_RF24_PROPERTY_MODEM_IF_FREQ_1, + RH_RF24_PROPERTY_MODEM_IF_FREQ_0, + RH_RF24_PROPERTY_MODEM_DECIMATION_CFG1, + RH_RF24_PROPERTY_MODEM_DECIMATION_CFG0, + RH_RF24_PROPERTY_MODEM_BCR_OSR_1, + RH_RF24_PROPERTY_MODEM_BCR_OSR_0, + RH_RF24_PROPERTY_MODEM_BCR_NCO_OFFSET_2, + RH_RF24_PROPERTY_MODEM_BCR_NCO_OFFSET_1, + RH_RF24_PROPERTY_MODEM_BCR_NCO_OFFSET_0, + RH_RF24_PROPERTY_MODEM_BCR_GAIN_1, + RH_RF24_PROPERTY_MODEM_BCR_GAIN_0, + RH_RF24_PROPERTY_MODEM_BCR_GEAR, + RH_RF24_PROPERTY_MODEM_BCR_MISC1, + RH_RF24_PROPERTY_MODEM_AFC_GEAR, + RH_RF24_PROPERTY_MODEM_AFC_WAIT, + RH_RF24_PROPERTY_MODEM_AFC_GAIN_1, + RH_RF24_PROPERTY_MODEM_AFC_GAIN_0, + RH_RF24_PROPERTY_MODEM_AFC_LIMITER_1, + RH_RF24_PROPERTY_MODEM_AFC_LIMITER_0, + RH_RF24_PROPERTY_MODEM_AFC_MISC, + RH_RF24_PROPERTY_MODEM_AGC_CONTROL, + RH_RF24_PROPERTY_MODEM_AGC_WINDOW_SIZE, + RH_RF24_PROPERTY_MODEM_AGC_RFPD_DECAY, + RH_RF24_PROPERTY_MODEM_AGC_IFPD_DECAY, + RH_RF24_PROPERTY_MODEM_FSK4_GAIN1, + RH_RF24_PROPERTY_MODEM_FSK4_GAIN0, + RH_RF24_PROPERTY_MODEM_FSK4_TH1, + RH_RF24_PROPERTY_MODEM_FSK4_TH0, + RH_RF24_PROPERTY_MODEM_FSK4_MAP, + RH_RF24_PROPERTY_MODEM_OOK_PDTC, + RH_RF24_PROPERTY_MODEM_OOK_CNT1, + RH_RF24_PROPERTY_MODEM_OOK_MISC, + RH_RF24_PROPERTY_MODEM_RAW_SEARCH, + RH_RF24_PROPERTY_MODEM_RAW_CONTROL, + RH_RF24_PROPERTY_MODEM_RAW_EYE_1, + RH_RF24_PROPERTY_MODEM_RAW_EYE_0, + RH_RF24_PROPERTY_MODEM_ANT_DIV_MODE, + RH_RF24_PROPERTY_MODEM_ANT_DIV_CONTROL, + RH_RF24_PROPERTY_MODEM_RSSI_THRESH, + RH_RF24_PROPERTY_MODEM_RSSI_JUMP_THRESH, + RH_RF24_PROPERTY_MODEM_RSSI_CONTROL, + RH_RF24_PROPERTY_MODEM_RSSI_CONTROL2, + RH_RF24_PROPERTY_MODEM_RSSI_COMP, + RH_RF24_PROPERTY_MODEM_ANT_DIV_CONT, + RH_RF24_PROPERTY_MODEM_CLKGEN_BAND, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE13_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE12_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE11_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE10_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE9_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE8_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE7_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE6_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE5_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE4_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE3_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE2_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE1_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE0_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM1, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM2, + RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM3, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE13_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE12_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE11_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE10_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE9_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE8_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE7_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE6_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE5_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE4_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE3_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE2_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE1_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE0_7_0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM0, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM1, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM2, + RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM3, + RH_RF24_PROPERTY_PA_MODE, + RH_RF24_PROPERTY_PA_PWR_LVL, + RH_RF24_PROPERTY_PA_BIAS_CLKDUTY, + RH_RF24_PROPERTY_PA_TC, + RH_RF24_PROPERTY_SYNTH_PFDCP_CPFF, + RH_RF24_PROPERTY_SYNTH_PFDCP_CPINT, + RH_RF24_PROPERTY_SYNTH_VCO_KV, + RH_RF24_PROPERTY_SYNTH_LPFILT3, + RH_RF24_PROPERTY_SYNTH_LPFILT2, + RH_RF24_PROPERTY_SYNTH_LPFILT1, + RH_RF24_PROPERTY_SYNTH_LPFILT0, + RH_RF24_PROPERTY_MATCH_VALUE_1, + RH_RF24_PROPERTY_MATCH_MASK_1, + RH_RF24_PROPERTY_MATCH_CTRL_1, + RH_RF24_PROPERTY_MATCH_VALUE_2, + RH_RF24_PROPERTY_MATCH_MASK_2, + RH_RF24_PROPERTY_MATCH_CTRL_2, + RH_RF24_PROPERTY_MATCH_VALUE_3, + RH_RF24_PROPERTY_MATCH_MASK_3, + RH_RF24_PROPERTY_MATCH_CTRL_3, + RH_RF24_PROPERTY_MATCH_VALUE_4, + RH_RF24_PROPERTY_MATCH_MASK_4, + RH_RF24_PROPERTY_MATCH_CTRL_4, + RH_RF24_PROPERTY_FREQ_CONTROL_INTE, + RH_RF24_PROPERTY_FREQ_CONTROL_FRAC_2, + RH_RF24_PROPERTY_FREQ_CONTROL_FRAC_1, + RH_RF24_PROPERTY_FREQ_CONTROL_FRAC_0, + RH_RF24_PROPERTY_FREQ_CONTROL_CHANNEL_STEP_SIZE_1, + RH_RF24_PROPERTY_FREQ_CONTROL_CHANNEL_STEP_SIZE_0, + RH_RF24_PROPERTY_FREQ_CONTROL_VCOCNT_RX_ADJ, + RH_RF24_PROPERTY_RX_HOP_CONTROL, + RH_RF24_PROPERTY_RX_HOP_TABLE_SIZE, + RH_RF24_PROPERTY_RX_HOP_TABLE_ENTRY_0, +}; +#define NUM_PROPERTIES (sizeof(properties)/sizeof(uint16_t)) + +bool RH_RF24::printRegisters() +{ +#ifdef RH_HAVE_SERIAL + uint8_t i; + // First print the commands that return interesting data + for (i = 0; i < NUM_COMMAND_INFO; i++) + { + CommandInfo cmd; + memcpy_P(&cmd, &commands[i], sizeof(cmd)); + uint8_t buf[10]; // Big enough for the biggest command reply + if (command(cmd.cmd, NULL, 0, buf, cmd.replyLen)) + { + // Print the results: + Serial.print("cmd: "); + Serial.print(cmd.cmd, HEX); + Serial.print(" : "); + uint8_t j; + for (j = 0; j < cmd.replyLen; j++) + { + Serial.print(buf[j], HEX); + Serial.print(" "); + } + Serial.println(""); + } + } + + // Now print the properties + for (i = 0; i < NUM_PROPERTIES; i++) + { + uint16_t prop; + memcpy_P(&prop, &properties[i], sizeof(prop)); + uint8_t result; + get_properties(prop, &result, 1); + Serial.print("prop: "); + Serial.print(prop, HEX); + Serial.print(": "); + Serial.print(result, HEX); + Serial.println(""); + } +#endif + return true; +} diff --git a/RH_RF24.h b/RH_RF24.h new file mode 100644 index 0000000..ca3ed48 --- /dev/null +++ b/RH_RF24.h @@ -0,0 +1,1164 @@ +// RH_RF24.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RH_RF24.h,v 1.18 2017/07/25 05:26:50 mikem Exp $ +// +// Supports RF24/RF26 and RFM24/RFM26 modules in FIFO mode +// also Si4464/63/62/61/60-A1 +// Si4063 is the same but Tx only +// +// Per http://www.hoperf.cn/upload/rf/RFM24.pdf +// and http://www.hoperf.cn/upload/rf/RFM26.pdf +// Sigh: the HopeRF documentation is utter rubbish: full of errors and incomplete. The Si446x docs are better: +// http://www.silabs.com/Support%20Documents/TechnicalDocs/Si4464-63-61-60.pdf +// http://www.silabs.com/Support%20Documents/TechnicalDocs/AN626.pdf +// http://www.silabs.com/Support%20Documents/TechnicalDocs/AN627.pdf +// http://www.silabs.com/Support%20Documents/TechnicalDocs/AN647.pdf +// http://www.silabs.com/Support%20Documents/TechnicalDocs/AN633.pdf +// http://www.silabs.com/Support%20Documents/TechnicalDocs/AN736.pdf +// http://nicerf.com/manage/upfile/indexbanner/635231050196868750.pdf (API description) +// http://www.silabs.com/Support%20Documents/Software/Si446x%20RX_HOP%20PLL%20Calculator.xlsx +#ifndef RH_RF24_h +#define RH_RF24_h + +#include +#include + +// This is the maximum number of interrupts the driver can support +// Most Arduinos can handle 2, Megas can handle more +#define RH_RF24_NUM_INTERRUPTS 3 + +// Maximum payload length the RF24 can support, limited by our 1 octet message length +#define RH_RF24_MAX_PAYLOAD_LEN 255 + +// The length of the headers we add. +// The headers are inside the RF24's payload +#define RH_RF24_HEADER_LEN 4 + +// This is the maximum message length that can be supported by this driver. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for message length 4 bytes of address and header and payload to be included in payload size limit. +#ifndef RH_RF24_MAX_MESSAGE_LEN +#define RH_RF24_MAX_MESSAGE_LEN (RH_RF24_MAX_PAYLOAD_LEN - RH_RF24_HEADER_LEN - 1) +#endif + +// Max number of times we will try to read CTS from the radio +#define RH_RF24_CTS_RETRIES 2500 + +// RF24/RF26 API commands from table 10 +// also Si446X API DESCRIPTIONS table 1 +#define RH_RF24_CMD_NOP 0x00 +#define RH_RF24_CMD_PART_INFO 0x01 +#define RH_RF24_CMD_POWER_UP 0x02 +#define RH_RF24_CMD_PATCH_IMAGE 0x04 +#define RH_RF24_CMD_FUNC_INFO 0x10 +#define RH_RF24_CMD_SET_PROPERTY 0x11 +#define RH_RF24_CMD_GET_PROPERTY 0x12 +#define RH_RF24_CMD_GPIO_PIN_CFG 0x13 +#define RH_RF24_CMD_GET_ADC_READING 0x14 +#define RH_RF24_CMD_FIFO_INFO 0x15 +#define RH_RF24_CMD_PACKET_INFO 0x16 +#define RH_RF24_CMD_IRCAL 0x17 +#define RH_RF24_CMD_PROTOCOL_CFG 0x18 +#define RH_RF24_CMD_GET_INT_STATUS 0x20 +#define RH_RF24_CMD_GET_PH_STATUS 0x21 +#define RH_RF24_CMD_GET_MODEM_STATUS 0x22 +#define RH_RF24_CMD_GET_CHIP_STATUS 0x23 +#define RH_RF24_CMD_START_TX 0x31 +#define RH_RF24_CMD_START_RX 0x32 +#define RH_RF24_CMD_REQUEST_DEVICE_STATE 0x33 +#define RH_RF24_CMD_CHANGE_STATE 0x34 +#define RH_RF24_CMD_RX_HOP 0x36 +#define RH_RF24_CMD_READ_BUF 0x44 +#define RH_RF24_CMD_FAST_RESPONSE_A 0x50 +#define RH_RF24_CMD_FAST_RESPONSE_B 0x51 +#define RH_RF24_CMD_FAST_RESPONSE_C 0x53 +#define RH_RF24_CMD_FAST_RESPONSE_D 0x57 +#define RH_RF24_CMD_TX_FIFO_WRITE 0x66 +#define RH_RF24_CMD_RX_FIFO_READ 0x77 + +// The Clear To Send signal from the radio +#define RH_RF24_REPLY_CTS 0xff + +//#define RH_RF24_CMD_START_TX 0x31 +#define RH_RF24_CONDITION_TX_COMPLETE_STATE 0xf0 +#define RH_RF24_CONDITION_RETRANSMIT_NO 0x00 +#define RH_RF24_CONDITION_RETRANSMIT_YES 0x04 +#define RH_RF24_CONDITION_START_IMMEDIATE 0x00 +#define RH_RF24_CONDITION_START_AFTER_WUT 0x01 + +//#define RH_RF24_CMD_START_RX 0x32 +#define RH_RF24_CONDITION_RX_START_IMMEDIATE 0x00 + +//#define RH_RF24_CMD_REQUEST_DEVICE_STATE 0x33 +#define RH_RF24_DEVICE_STATE_NO_CHANGE 0x00 +#define RH_RF24_DEVICE_STATE_SLEEP 0x01 +#define RH_RF24_DEVICE_STATE_SPI_ACTIVE 0x02 +#define RH_RF24_DEVICE_STATE_READY 0x03 +#define RH_RF24_DEVICE_STATE_ALSO_READY 0x04 +#define RH_RF24_DEVICE_STATE_TUNE_TX 0x05 +#define RH_RF24_DEVICE_STATE_TUNE_RX 0x06 +#define RH_RF24_DEVICE_STATE_TX 0x07 +#define RH_RF24_DEVICE_STATE_RX 0x08 + +// Properties for API Description AN625 Section 2.2 +#define RH_RF24_PROPERTY_GLOBAL_XO_TUNE 0x0000 +#define RH_RF24_PROPERTY_GLOBAL_CLK_CFG 0x0001 +#define RH_RF24_PROPERTY_GLOBAL_LOW_BATT_THRESH 0x0002 +#define RH_RF24_PROPERTY_GLOBAL_CONFIG 0x0003 +#define RH_RF24_PROPERTY_GLOBAL_WUT_CONFIG 0x0004 +#define RH_RF24_PROPERTY_GLOBAL_WUT_M_15_8 0x0005 +#define RH_RF24_PROPERTY_GLOBAL_WUT_M_7_0 0x0006 +#define RH_RF24_PROPERTY_GLOBAL_WUT_R 0x0007 +#define RH_RF24_PROPERTY_GLOBAL_WUT_LDC 0x0008 +#define RH_RF24_PROPERTY_INT_CTL_ENABLE 0x0100 +#define RH_RF24_PROPERTY_INT_CTL_PH_ENABLE 0x0101 +#define RH_RF24_PROPERTY_INT_CTL_MODEM_ENABLE 0x0102 +#define RH_RF24_PROPERTY_INT_CTL_CHIP_ENABLE 0x0103 +#define RH_RF24_PROPERTY_FRR_CTL_A_MODE 0x0200 +#define RH_RF24_PROPERTY_FRR_CTL_B_MODE 0x0201 +#define RH_RF24_PROPERTY_FRR_CTL_C_MODE 0x0202 +#define RH_RF24_PROPERTY_FRR_CTL_D_MODE 0x0203 +#define RH_RF24_PROPERTY_PREAMBLE_TX_LENGTH 0x1000 +#define RH_RF24_PROPERTY_PREAMBLE_CONFIG_STD_1 0x1001 +#define RH_RF24_PROPERTY_PREAMBLE_CONFIG_NSTD 0x1002 +#define RH_RF24_PROPERTY_PREAMBLE_CONFIG_STD_2 0x1003 +#define RH_RF24_PROPERTY_PREAMBLE_CONFIG 0x1004 +#define RH_RF24_PROPERTY_PREAMBLE_PATTERN_31_24 0x1005 +#define RH_RF24_PROPERTY_PREAMBLE_PATTERN_23_16 0x1006 +#define RH_RF24_PROPERTY_PREAMBLE_PATTERN_15_8 0x1007 +#define RH_RF24_PROPERTY_PREAMBLE_PATTERN_7_0 0x1008 +#define RH_RF24_PROPERTY_SYNC_CONFIG 0x1100 +#define RH_RF24_PROPERTY_SYNC_BITS_31_24 0x1101 +#define RH_RF24_PROPERTY_SYNC_BITS_23_16 0x1102 +#define RH_RF24_PROPERTY_SYNC_BITS_15_8 0x1103 +#define RH_RF24_PROPERTY_SYNC_BITS_7_0 0x1104 +#define RH_RF24_PROPERTY_PKT_CRC_CONFIG 0x1200 +#define RH_RF24_PROPERTY_PKT_CONFIG1 0x1206 +#define RH_RF24_PROPERTY_PKT_LEN 0x1208 +#define RH_RF24_PROPERTY_PKT_LEN_FIELD_SOURCE 0x1209 +#define RH_RF24_PROPERTY_PKT_LEN_ADJUST 0x120a +#define RH_RF24_PROPERTY_PKT_TX_THRESHOLD 0x120b +#define RH_RF24_PROPERTY_PKT_RX_THRESHOLD 0x120c +#define RH_RF24_PROPERTY_PKT_FIELD_1_LENGTH_12_8 0x120d +#define RH_RF24_PROPERTY_PKT_FIELD_1_LENGTH_7_0 0x120e +#define RH_RF24_PROPERTY_PKT_FIELD_1_CONFIG 0x120f +#define RH_RF24_PROPERTY_PKT_FIELD_1_CRC_CONFIG 0x1210 +#define RH_RF24_PROPERTY_PKT_FIELD_2_LENGTH_12_8 0x1211 +#define RH_RF24_PROPERTY_PKT_FIELD_2_LENGTH_7_0 0x1212 +#define RH_RF24_PROPERTY_PKT_FIELD_2_CONFIG 0x1213 +#define RH_RF24_PROPERTY_PKT_FIELD_2_CRC_CONFIG 0x1214 +#define RH_RF24_PROPERTY_PKT_FIELD_3_LENGTH_12_8 0x1215 +#define RH_RF24_PROPERTY_PKT_FIELD_3_LENGTH_7_0 0x1216 +#define RH_RF24_PROPERTY_PKT_FIELD_3_CONFIG 0x1217 +#define RH_RF24_PROPERTY_PKT_FIELD_3_CRC_CONFIG 0x1218 +#define RH_RF24_PROPERTY_PKT_FIELD_4_LENGTH_12_8 0x1219 +#define RH_RF24_PROPERTY_PKT_FIELD_4_LENGTH_7_0 0x121a +#define RH_RF24_PROPERTY_PKT_FIELD_4_CONFIG 0x121b +#define RH_RF24_PROPERTY_PKT_FIELD_4_CRC_CONFIG 0x121c +#define RH_RF24_PROPERTY_PKT_FIELD_5_LENGTH_12_8 0x121d +#define RH_RF24_PROPERTY_PKT_FIELD_5_LENGTH_7_0 0x121e +#define RH_RF24_PROPERTY_PKT_FIELD_5_CONFIG 0x121f +#define RH_RF24_PROPERTY_PKT_FIELD_5_CRC_CONFIG 0x1220 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_1_LENGTH_12_8 0x1221 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_1_LENGTH_7_0 0x1222 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_1_CONFIG 0x1223 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_1_CRC_CONFIG 0x1224 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_2_LENGTH_12_8 0x1225 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_2_LENGTH_7_0 0x1226 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_2_CONFIG 0x1227 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_2_CRC_CONFIG 0x1228 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_3_LENGTH_12_8 0x1229 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_3_LENGTH_7_0 0x122a +#define RH_RF24_PROPERTY_PKT_RX_FIELD_3_CONFIG 0x122b +#define RH_RF24_PROPERTY_PKT_RX_FIELD_3_CRC_CONFIG 0x122c +#define RH_RF24_PROPERTY_PKT_RX_FIELD_4_LENGTH_12_8 0x122d +#define RH_RF24_PROPERTY_PKT_RX_FIELD_4_LENGTH_7_0 0x122e +#define RH_RF24_PROPERTY_PKT_RX_FIELD_4_CONFIG 0x122f +#define RH_RF24_PROPERTY_PKT_RX_FIELD_4_CRC_CONFIG 0x1230 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_5_LENGTH_12_8 0x1231 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_5_LENGTH_7_0 0x1232 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_5_CONFIG 0x1233 +#define RH_RF24_PROPERTY_PKT_RX_FIELD_5_CRC_CONFIG 0x1234 +#define RH_RF24_PROPERTY_MODEM_MOD_TYPE 0x2000 +#define RH_RF24_PROPERTY_MODEM_MAP_CONTROL 0x2001 +#define RH_RF24_PROPERTY_MODEM_DSM_CTRL 0x2002 +#define RH_RF24_PROPERTY_MODEM_DATA_RATE_2 0x2003 +#define RH_RF24_PROPERTY_MODEM_DATA_RATE_1 0x2004 +#define RH_RF24_PROPERTY_MODEM_DATA_RATE_0 0x2005 +#define RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_3 0x2006 +#define RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_2 0x2007 +#define RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_1 0x2008 +#define RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_0 0x2009 +#define RH_RF24_PROPERTY_MODEM_FREQ_DEV_2 0x200a +#define RH_RF24_PROPERTY_MODEM_FREQ_DEV_1 0x200b +#define RH_RF24_PROPERTY_MODEM_FREQ_DEV_0 0x200c +#define RH_RF24_PROPERTY_MODEM_TX_RAMP_DELAY 0x2018 +#define RH_RF24_PROPERTY_MODEM_MDM_CTRL 0x2019 +#define RH_RF24_PROPERTY_MODEM_IF_CONTROL 0x201a +#define RH_RF24_PROPERTY_MODEM_IF_FREQ_2 0x201b +#define RH_RF24_PROPERTY_MODEM_IF_FREQ_1 0x201c +#define RH_RF24_PROPERTY_MODEM_IF_FREQ_0 0x201d +#define RH_RF24_PROPERTY_MODEM_DECIMATION_CFG1 0x201e +#define RH_RF24_PROPERTY_MODEM_DECIMATION_CFG0 0x201f +#define RH_RF24_PROPERTY_MODEM_BCR_OSR_1 0x2022 +#define RH_RF24_PROPERTY_MODEM_BCR_OSR_0 0x2023 +#define RH_RF24_PROPERTY_MODEM_BCR_NCO_OFFSET_2 0x2024 +#define RH_RF24_PROPERTY_MODEM_BCR_NCO_OFFSET_1 0x2025 +#define RH_RF24_PROPERTY_MODEM_BCR_NCO_OFFSET_0 0x2026 +#define RH_RF24_PROPERTY_MODEM_BCR_GAIN_1 0x2027 +#define RH_RF24_PROPERTY_MODEM_BCR_GAIN_0 0x2028 +#define RH_RF24_PROPERTY_MODEM_BCR_GEAR 0x2029 +#define RH_RF24_PROPERTY_MODEM_BCR_MISC1 0x202a +#define RH_RF24_PROPERTY_MODEM_AFC_GEAR 0x202c +#define RH_RF24_PROPERTY_MODEM_AFC_WAIT 0x202d +#define RH_RF24_PROPERTY_MODEM_AFC_GAIN_1 0x202e +#define RH_RF24_PROPERTY_MODEM_AFC_GAIN_0 0x202f +#define RH_RF24_PROPERTY_MODEM_AFC_LIMITER_1 0x2030 +#define RH_RF24_PROPERTY_MODEM_AFC_LIMITER_0 0x2031 +#define RH_RF24_PROPERTY_MODEM_AFC_MISC 0x2032 +#define RH_RF24_PROPERTY_MODEM_AGC_CONTROL 0x2035 +#define RH_RF24_PROPERTY_MODEM_AGC_WINDOW_SIZE 0x2038 +#define RH_RF24_PROPERTY_MODEM_AGC_RFPD_DECAY 0x2039 +#define RH_RF24_PROPERTY_MODEM_AGC_IFPD_DECAY 0x203a +#define RH_RF24_PROPERTY_MODEM_FSK4_GAIN1 0x203b +#define RH_RF24_PROPERTY_MODEM_FSK4_GAIN0 0x203c +#define RH_RF24_PROPERTY_MODEM_FSK4_TH1 0x203d +#define RH_RF24_PROPERTY_MODEM_FSK4_TH0 0x203e +#define RH_RF24_PROPERTY_MODEM_FSK4_MAP 0x203f +#define RH_RF24_PROPERTY_MODEM_OOK_PDTC 0x2040 +#define RH_RF24_PROPERTY_MODEM_OOK_CNT1 0x2042 +#define RH_RF24_PROPERTY_MODEM_OOK_MISC 0x2043 +#define RH_RF24_PROPERTY_MODEM_RAW_SEARCH 0x2044 +#define RH_RF24_PROPERTY_MODEM_RAW_CONTROL 0x2045 +#define RH_RF24_PROPERTY_MODEM_RAW_EYE_1 0x2046 +#define RH_RF24_PROPERTY_MODEM_RAW_EYE_0 0x2047 +#define RH_RF24_PROPERTY_MODEM_ANT_DIV_MODE 0x2048 +#define RH_RF24_PROPERTY_MODEM_ANT_DIV_CONTROL 0x2049 +#define RH_RF24_PROPERTY_MODEM_RSSI_THRESH 0x204a +#define RH_RF24_PROPERTY_MODEM_RSSI_JUMP_THRESH 0x204b +#define RH_RF24_PROPERTY_MODEM_RSSI_CONTROL 0x204c +#define RH_RF24_PROPERTY_MODEM_RSSI_CONTROL2 0x204d +#define RH_RF24_PROPERTY_MODEM_RSSI_COMP 0x204e +#define RH_RF24_PROPERTY_MODEM_ANT_DIV_CONT 0x2049 +#define RH_RF24_PROPERTY_MODEM_CLKGEN_BAND 0x2051 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE13_7_0 0x2100 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE12_7_0 0x2101 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE11_7_0 0x2102 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE10_7_0 0x2103 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE9_7_0 0x2104 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE8_7_0 0x2105 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE7_7_0 0x2106 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE6_7_0 0x2107 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE5_7_0 0x2108 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE4_7_0 0x2109 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE3_7_0 0x210a +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE2_7_0 0x210b +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE1_7_0 0x210c +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE0_7_0 0x210d +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM0 0x210e +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM1 0x210f +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM2 0x2110 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM3 0x2111 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE13_7_0 0x2112 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE12_7_0 0x2113 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE11_7_0 0x2114 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE10_7_0 0x2115 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE9_7_0 0x2116 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE8_7_0 0x2117 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE7_7_0 0x2118 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE6_7_0 0x2119 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE5_7_0 0x211a +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE4_7_0 0x211b +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE3_7_0 0x211c +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE2_7_0 0x211d +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE1_7_0 0x211e +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE0_7_0 0x211f +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM0 0x2120 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM1 0x2121 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM2 0x2122 +#define RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM3 0x2123 +#define RH_RF24_PROPERTY_PA_MODE 0x2200 +#define RH_RF24_PROPERTY_PA_PWR_LVL 0x2201 +#define RH_RF24_PROPERTY_PA_BIAS_CLKDUTY 0x2202 +#define RH_RF24_PROPERTY_PA_TC 0x2203 +#define RH_RF24_PROPERTY_SYNTH_PFDCP_CPFF 0x2300 +#define RH_RF24_PROPERTY_SYNTH_PFDCP_CPINT 0x2301 +#define RH_RF24_PROPERTY_SYNTH_VCO_KV 0x2302 +#define RH_RF24_PROPERTY_SYNTH_LPFILT3 0x2303 +#define RH_RF24_PROPERTY_SYNTH_LPFILT2 0x2304 +#define RH_RF24_PROPERTY_SYNTH_LPFILT1 0x2305 +#define RH_RF24_PROPERTY_SYNTH_LPFILT0 0x2306 +#define RH_RF24_PROPERTY_MATCH_VALUE_1 0x3000 +#define RH_RF24_PROPERTY_MATCH_MASK_1 0x3001 +#define RH_RF24_PROPERTY_MATCH_CTRL_1 0x3002 +#define RH_RF24_PROPERTY_MATCH_VALUE_2 0x3003 +#define RH_RF24_PROPERTY_MATCH_MASK_2 0x3004 +#define RH_RF24_PROPERTY_MATCH_CTRL_2 0x3005 +#define RH_RF24_PROPERTY_MATCH_VALUE_3 0x3006 +#define RH_RF24_PROPERTY_MATCH_MASK_3 0x3007 +#define RH_RF24_PROPERTY_MATCH_CTRL_3 0x3008 +#define RH_RF24_PROPERTY_MATCH_VALUE_4 0x3009 +#define RH_RF24_PROPERTY_MATCH_MASK_4 0x300a +#define RH_RF24_PROPERTY_MATCH_CTRL_4 0x300b +#define RH_RF24_PROPERTY_FREQ_CONTROL_INTE 0x4000 +#define RH_RF24_PROPERTY_FREQ_CONTROL_FRAC_2 0x4001 +#define RH_RF24_PROPERTY_FREQ_CONTROL_FRAC_1 0x4002 +#define RH_RF24_PROPERTY_FREQ_CONTROL_FRAC_0 0x4003 +#define RH_RF24_PROPERTY_FREQ_CONTROL_CHANNEL_STEP_SIZE_1 0x4004 +#define RH_RF24_PROPERTY_FREQ_CONTROL_CHANNEL_STEP_SIZE_0 0x4005 +#define RH_RF24_PROPERTY_FREQ_CONTROL_VCOCNT_RX_ADJ 0x4007 +#define RH_RF24_PROPERTY_RX_HOP_CONTROL 0x5000 +#define RH_RF24_PROPERTY_RX_HOP_TABLE_SIZE 0x5001 +#define RH_RF24_PROPERTY_RX_HOP_TABLE_ENTRY_0 0x5002 + +//#define RH_RF24_CMD_GPIO_PIN_CFG 0x13 +#define RH_RF24_GPIO_NO_CHANGE 0 +#define RH_RF24_GPIO_DISABLED 1 +#define RH_RF24_GPIO_LOW 2 +#define RH_RF24_GPIO_HIGH 3 +#define RH_RF24_GPIO_INPUT 4 +#define RH_RF24_GPIO_32_KHZ_CLOCK 5 +#define RH_RF24_GPIO_BOOT_CLOCK 6 +#define RH_RF24_GPIO_DIVIDED_MCU_CLOCK 7 +#define RH_RF24_GPIO_CTS 8 +#define RH_RF24_GPIO_INV_CTS 9 +#define RH_RF24_GPIO_HIGH_ON_CMD_OVERLAP 10 +#define RH_RF24_GPIO_SPI_DATA_OUT 11 +#define RH_RF24_GPIO_HIGH_AFTER_RESET 12 +#define RH_RF24_GPIO_HIGH_AFTER_CALIBRATION 13 +#define RH_RF24_GPIO_HIGH_AFTER_WUT 14 +#define RH_RF24_GPIO_UNUSED_0 15 +#define RH_RF24_GPIO_TX_DATA_CLOCK 16 +#define RH_RF24_GPIO_RX_DATA_CLOCK 17 +#define RH_RF24_GPIO_UNUSED_1 18 +#define RH_RF24_GPIO_TX_DATA 19 +#define RH_RF24_GPIO_RX_DATA 20 +#define RH_RF24_GPIO_RX_RAW_DATA 21 +#define RH_RF24_GPIO_ANTENNA_1_SWITCH 22 +#define RH_RF24_GPIO_ANTENNA_2_SWITCH 23 +#define RH_RF24_GPIO_VALID_PREAMBLE 24 +#define RH_RF24_GPIO_INVALID_PREAMBLE 25 +#define RH_RF24_GPIO_SYNC_DETECTED 26 +#define RH_RF24_GPIO_RSSI_ABOVE_CAT 27 +#define RH_RF24_GPIO_TX_STATE 32 +#define RH_RF24_GPIO_RX_STATE 33 +#define RH_RF24_GPIO_RX_FIFO_ALMOST_FULL 34 +#define RH_RF24_GPIO_TX_FIFO_ALMOST_EMPTY 35 +#define RH_RF24_GPIO_BATT_LOW 36 +#define RH_RF24_GPIO_RSSI_ABOVE_CAT_LOW 37 +#define RH_RF24_GPIO_HOP 38 +#define RH_RF24_GPIO_HOP_TABLE_WRAPPED 39 + +// #define RH_RF24_CMD_GET_INT_STATUS 0x20 +#define RH_RF24_INT_STATUS_CHIP_INT_STATUS 0x04 +#define RH_RF24_INT_STATUS_MODEM_INT_STATUS 0x02 +#define RH_RF24_INT_STATUS_PH_INT_STATUS 0x01 +#define RH_RF24_INT_STATUS_FILTER_MATCH 0x80 +#define RH_RF24_INT_STATUS_FILTER_MISS 0x40 +#define RH_RF24_INT_STATUS_PACKET_SENT 0x20 +#define RH_RF24_INT_STATUS_PACKET_RX 0x10 +#define RH_RF24_INT_STATUS_CRC_ERROR 0x08 +#define RH_RF24_INT_STATUS_TX_FIFO_ALMOST_EMPTY 0x02 +#define RH_RF24_INT_STATUS_RX_FIFO_ALMOST_FULL 0x01 +#define RH_RF24_INT_STATUS_INVALID_SYNC 0x20 +#define RH_RF24_INT_STATUS_RSSI_JUMP 0x10 +#define RH_RF24_INT_STATUS_RSSI 0x08 +#define RH_RF24_INT_STATUS_INVALID_PREAMBLE 0x04 +#define RH_RF24_INT_STATUS_PREAMBLE_DETECT 0x02 +#define RH_RF24_INT_STATUS_SYNC_DETECT 0x01 +#define RH_RF24_INT_STATUS_CAL 0x40 +#define RH_RF24_INT_STATUS_FIFO_UNDERFLOW_OVERFLOW_ERROR 0x20 +#define RH_RF24_INT_STATUS_STATE_CHANGE 0x10 +#define RH_RF24_INT_STATUS_CMD_ERROR 0x08 +#define RH_RF24_INT_STATUS_CHIP_READY 0x04 +#define RH_RF24_INT_STATUS_LOW_BATT 0x02 +#define RH_RF24_INT_STATUS_WUT 0x01 + +//#define RH_RF24_PROPERTY_GLOBAL_CLK_CFG 0x0001 +#define RH_RF24_CLK_CFG_DIVIDED_CLK_EN 0x40 +#define RH_RF24_CLK_CFG_DIVIDED_CLK_SEL_30 0x30 +#define RH_RF24_CLK_CFG_DIVIDED_CLK_SEL_15 0x28 +#define RH_RF24_CLK_CFG_DIVIDED_CLK_SEL_10 0x20 +#define RH_RF24_CLK_CFG_DIVIDED_CLK_SEL_7_5 0x18 +#define RH_RF24_CLK_CFG_DIVIDED_CLK_SEL_3 0x10 +#define RH_RF24_CLK_CFG_DIVIDED_CLK_SEL_2 0x08 +#define RH_RF24_CLK_CFG_DIVIDED_CLK_SEL_1 0x00 +#define RH_RF24_CLK_CFG_CLK_32K_SEL_EXTERNAL 0x02 +#define RH_RF24_CLK_CFG_CLK_32K_SEL_RC 0x01 +#define RH_RF24_CLK_CFG_CLK_32K_SEL_DISABLED 0x00 + +//#define RH_RF24_PROPERTY_FRR_CTL_A_MODE 0x0200 +//#define RH_RF24_PROPERTY_FRR_CTL_B_MODE 0x0201 +//#define RH_RF24_PROPERTY_FRR_CTL_C_MODE 0x0202 +//#define RH_RF24_PROPERTY_FRR_CTL_D_MODE 0x0203 +#define RH_RF24_FRR_MODE_DISABLED 0 +#define RH_RF24_FRR_MODE_GLOBAL_STATUS 1 +#define RH_RF24_FRR_MODE_GLOBAL_INTERRUPT_PENDING 2 +#define RH_RF24_FRR_MODE_PACKET_HANDLER_STATUS 3 +#define RH_RF24_FRR_MODE_PACKET_HANDLER_INTERRUPT_PENDING 4 +#define RH_RF24_FRR_MODE_MODEM_STATUS 5 +#define RH_RF24_FRR_MODE_MODEM_INTERRUPT_PENDING 6 +#define RH_RF24_FRR_MODE_CHIP_STATUS 7 +#define RH_RF24_FRR_MODE_CHIP_INTERRUPT_PENDING 8 +#define RH_RF24_FRR_MODE_CURRENT_STATE 9 +#define RH_RF24_FRR_MODE_LATCHED_RSSI 10 + +//#define RH_RF24_PROPERTY_INT_CTL_ENABLE 0x0100 +#define RH_RF24_CHIP_INT_STATUS_EN 0x04 +#define RH_RF24_MODEM_INT_STATUS_EN 0x02 +#define RH_RF24_PH_INT_STATUS_EN 0x01 + +//#define RH_RF24_PROPERTY_PREAMBLE_CONFIG 0x1004 +#define RH_RF24_PREAMBLE_FIRST_1 0x20 +#define RH_RF24_PREAMBLE_FIRST_0 0x00 +#define RH_RF24_PREAMBLE_LENGTH_NIBBLES 0x00 +#define RH_RF24_PREAMBLE_LENGTH_BYTES 0x10 +#define RH_RF24_PREAMBLE_MAN_CONST 0x08 +#define RH_RF24_PREAMBLE_MAN_ENABLE 0x02 +#define RH_RF24_PREAMBLE_NON_STANDARD 0x00 +#define RH_RF24_PREAMBLE_STANDARD_1010 0x01 +#define RH_RF24_PREAMBLE_STANDARD_0101 0x02 + +//#define RH_RF24_PROPERTY_SYNC_CONFIG 0x1100 +#define RH_RF24_SYNC_CONFIG_SKIP_TX 0x80 +#define RH_RF24_SYNC_CONFIG_RX_ERRORS_MASK 0x70 +#define RH_RF24_SYNC_CONFIG_4FSK 0x08 +#define RH_RF24_SYNC_CONFIG_MANCH 0x04 +#define RH_RF24_SYNC_CONFIG_LENGTH_MASK 0x03 + +//#define RH_RF24_PROPERTY_PKT_CRC_CONFIG 0x1200 +#define RH_RF24_CRC_SEED_ALL_0S 0x00 +#define RH_RF24_CRC_SEED_ALL_1S 0x80 +#define RH_RF24_CRC_MASK 0x0f +#define RH_RF24_CRC_NONE 0x00 +#define RH_RF24_CRC_ITU_T 0x01 +#define RH_RF24_CRC_IEC_16 0x02 +#define RH_RF24_CRC_BIACHEVA 0x03 +#define RH_RF24_CRC_16_IBM 0x04 +#define RH_RF24_CRC_CCITT 0x05 +#define RH_RF24_CRC_KOOPMAN 0x06 +#define RH_RF24_CRC_IEEE_802_3 0x07 +#define RH_RF24_CRC_CASTAGNOLI 0x08 + +//#define RH_RF24_PROPERTY_PKT_CONFIG1 0x1206 +#define RH_RF24_PH_FIELD_SPLIT 0x80 +#define RH_RF24_PH_RX_DISABLE 0x40 +#define RH_RF24_4FSK_EN 0x20 +#define RH_RF24_RX_MULTI_PKT 0x10 +#define RH_RF24_MANCH_POL 0x08 +#define RH_RF24_CRC_INVERT 0x04 +#define RH_RF24_CRC_ENDIAN 0x02 +#define RH_RF24_BIT_ORDER 0x01 + +//#define RH_RF24_PROPERTY_PKT_FIELD_1_CONFIG 0x120f +//#define RH_RF24_PROPERTY_PKT_FIELD_2_CONFIG 0x1213 +//#define RH_RF24_PROPERTY_PKT_FIELD_3_CONFIG 0x1217 +//#define RH_RF24_PROPERTY_PKT_FIELD_4_CONFIG 0x121b +//#define RH_RF24_PROPERTY_PKT_FIELD_5_CONFIG 0x121f +#define RH_RF24_FIELD_CONFIG_4FSK 0x10 +#define RH_RF24_FIELD_CONFIG_WHITEN 0x02 +#define RH_RF24_FIELD_CONFIG_MANCH 0x01 + +//#define RH_RF24_PROPERTY_PKT_RX_FIELD_1_CRC_CONFIG 0x1224 +//#define RH_RF24_PROPERTY_PKT_RX_FIELD_2_CRC_CONFIG 0x1228 +//#define RH_RF24_PROPERTY_PKT_RX_FIELD_3_CRC_CONFIG 0x122c +//#define RH_RF24_PROPERTY_PKT_RX_FIELD_4_CRC_CONFIG 0x1230 +//#define RH_RF24_PROPERTY_PKT_RX_FIELD_5_CRC_CONFIG 0x1234 +#define RH_RF24_FIELD_CONFIG_CRC_START 0x80 +#define RH_RF24_FIELD_CONFIG_SEND_CRC 0x20 +#define RH_RF24_FIELD_CONFIG_CHECK_CRC 0x08 +#define RH_RF24_FIELD_CONFIG_CRC_ENABLE 0x02 + + + + +//#define RH_RF24_PROPERTY_MODEM_MOD_TYPE 0x2000 +#define RH_RF24_TX_DIRECT_MODE_TYPE_SYNCHRONOUS 0x00 +#define RH_RF24_TX_DIRECT_MODE_TYPE_ASYNCHRONOUS 0x80 +#define RH_RF24_TX_DIRECT_MODE_GPIO0 0x00 +#define RH_RF24_TX_DIRECT_MODE_GPIO1 0x20 +#define RH_RF24_TX_DIRECT_MODE_GPIO2 0x40 +#define RH_RF24_TX_DIRECT_MODE_GPIO3 0x60 +#define RH_RF24_MOD_SOURCE_PACKET_HANDLER 0x00 +#define RH_RF24_MOD_SOURCE_DIRECT_MODE 0x08 +#define RH_RF24_MOD_SOURCE_RANDOM_GENERATOR 0x10 +#define RH_RF24_MOD_TYPE_CW 0x00 +#define RH_RF24_MOD_TYPE_OOK 0x01 +#define RH_RF24_MOD_TYPE_2FSK 0x02 +#define RH_RF24_MOD_TYPE_2GFSK 0x03 +#define RH_RF24_MOD_TYPE_4FSK 0x04 +#define RH_RF24_MOD_TYPE_4GFSK 0x05 + +// RH_RF24_PROPERTY_PA_MODE 0x2200 +#define RH_RF24_PA_MODE_1_GROUP 0x04 +#define RH_RF24_PA_MODE_2_GROUPS 0x08 +#define RH_RF24_PA_MODE_CLASS_E 0x00 +#define RH_RF24_PA_MODE_SWITCH_CURRENT 0x01 + + +///////////////////////////////////////////////////////////////////// +/// \class RH_RF24 RH_RF24.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via an RF24 and compatible radio transceiver. +/// +/// Works with +/// - Silicon Labs Si4460/1/2/3/4 transceiver chips +/// - The equivalent HopeRF RF24/25/26/27 transceiver chips +/// - HopeRF Complete modules: RFM24W/26W/27W +/// +/// \par Overview +/// +/// This class provides basic functions for sending and receiving unaddressed, +/// unreliable datagrams of arbitrary length to 250 octets per packet. +/// +/// Manager classes may use this class to implement reliable, addressed datagrams and streams, +/// mesh routers, repeaters, translators etc. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// modulation scheme. +/// +/// This Driver provides an object-oriented interface for sending and receiving data messages with Hope-RF +/// RF24 and compatible radio modules, such as the RFM24W module. +/// +/// The Hope-RF (http://www.hoperf.com) RF24 family is a low-cost ISM transceiver +/// chip. It supports FSK, GFSK, OOK over a wide range of frequencies and +/// programmable data rates. HopeRF also sell these chips on modules which includes +/// a crystal and antenna coupling circuits: RFM24W, RFM26W and RFM27W +/// +/// This Driver provides functions for sending and receiving messages of up +/// to 250 octets on any frequency supported by the RF24, in a range of +/// predefined data rates and frequency deviations. Frequency can be set +/// to any frequency from 142.0MHz to 1050.0MHz. Caution: most modules only support a more limited +/// range of frequencies due to antenna tuning. +/// +/// Up to 2 RFM24 modules can be connected to an Arduino (3 on a Mega), +/// permitting the construction of translators and frequency changers, etc. +/// +/// The following modulation types are suppported with a range of modem configurations for +/// common data rates and frequency deviations: +/// - OOK On-Off Keying +/// - GFSK Gaussian Frequency Shift Keying +/// - FSK Frequency Shift Keying +/// +/// Support for other RF24 features such as on-chip temperature measurement, +/// transmitter power control etc is also provided. +/// +/// RH_RF24 uses interrupts to detect and handle events in the radio chip. The RF24 family has +/// TX and RX FIFOs of 64 bytes, but through the use of interrupt, the RH_RF24 driver can send longer +/// messages by filling or emptying the FIFOs on-the-fly. +/// +/// Tested on Anarduino Mini http://www.anarduino.com/mini/ with arduino-1.0.5 +/// on OpenSuSE 13.1. Also on Anarduino Mini with arduino-1.8.1 on Kubuntu 16.04 +/// +/// \par Packet Format +/// +/// All messages sent and received by this RH_RF24 Driver conform to this packet format: +/// +/// - 4 octets PREAMBLE (configurable) +/// - 2 octets SYNC 0x2d, 0xd4 (configurable, so you can use this as a network filter) +/// - Field containing 1 octet of message length and 2 octet CRC protecting this field +/// - Field 2 containing at least 4 octets, and 2 octet CRC protecting this field: +/// + 4 octets HEADER: (TO, FROM, ID, FLAGS) +/// + 0 to 250 octets DATA +/// + 2 octets CRC, computed on HEADER and DATA +/// +/// \par Connecting RFM-24 to Arduino +/// +/// For RFM24/RFM26 and Teensy 3.1 or Anarduino Mini +/// \code +/// Teensy RFM-24/RFM26 +/// GND----------GND (ground in) +/// 3V3----------VCC (3.3V in) +/// interrupt 2 pin D2-----------NIRQ (interrupt request out) +/// SS pin D10----------NSEL (chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------SDI (SPI Data in) +/// MISO pin D12----------SDO (SPI data out) +/// D9-----------SDN (shutdown in) +/// /--GPIO0 (GPIO0 out to control transmitter antenna TX_ANT) +/// \--TX_ANT (TX antenna control in) RFM22B only +/// /--GPIO1 (GPIO1 out to control receiver antenna RX_ANT) +/// \--RX_ANT (RX antenna control in) RFM22B only +/// \endcode +/// Caution: tying the radio SDN pin to ground (though it might appear from the data sheets to make sense) +/// does not always produce a reliable radio startup. So this driver controls the SDN pin directly. +/// Note: the GPIO0-TX_ANT and GPIO1-RX_ANT connections are not required for the 11dBm RFM24W, +/// which has no antenna switch. +/// +/// If you have an Arduino Zero, you should note that you cannot use Pin 2 for the interrupt line +/// (Pin 2 is for the NMI only), instead you can use any other pin (we use Pin 3) and initialise RH_RF69 like this: +/// \code +/// // Slave Select is pin 10, interrupt is Pin 3 +/// RH_RF24 driver(10, 3); +/// \endcode +/// +/// \par Customising and configuring +/// +/// The RH_RF24 module uses a radio configuration header file to configure the basic radio operation +/// frequency and modulation scheme. The radio configuration header file must be generated with the +/// Silicon Labs Wireless Development Suite (WDS) program and \#included by RH_RF24.cpp +/// +/// The library will work out of the box and without further configuring with these parameters: +/// - Si4464 or equvalent +/// - 30MHz Crytstal +/// - 434MHz base frequncy band +/// - 2GFSK modulation +/// - 5kbps data rate +/// - 10kHz deviation +/// using the radio configuration header file +/// RF24configs/radio_config_Si4464_30_434_2GFSK_5_10.h +/// which is included in RadioHead. +/// +/// In order to use different frequency bands or modulation schemes, you must generate a new +/// radio configuration header file +/// with WDS, or select one of a small set of prebuilt headers from the RF24configs folder (see README in that +/// folder for details of the filename format). +/// +/// To generate a new header file: +/// +/// - Install Silicon Labs Wireless Development Suite (WDS) 3.2.11.0 or later +/// (Windows only, we were not able to get it to run under Wine on Linux) +/// - Run WDS +/// - Menu->Start Simulation +/// - Select radio chip type Si4464, press Select Radio +/// - Select Radio Configuration Application, press Select Application +/// - Click on Standard Packet Tx +/// - On the Frequency and Power tab, Select the Frequency, crystal frequency etc. The PA power level is irrelevant, +/// since power is set programatically +/// - On the RF parameters tab, select the modulation type and rates +/// - Press Generate Source, Save custom radio configuration header file +/// - Enter a new unique file name in the RF24configs folder in RadioHead +/// - Edit RH_RF24.cpp to use this new header file +/// - Recompile RH_RF24 +/// +/// \par RSSI +/// +/// The RSSI (Received Signal Strength Indicator) is measured and latched after the message sync bytes are received. +/// The latched RSSI is available from the lastRssi() member functionafter the complete message is received. +/// Although lastRssi() +/// supposedly returns a signed integer, in the case of this radio it actually returns an unsigned 8 bit integer (uint8_t) +/// and you will have to cast the return value to use it: +/// \code +/// uint8_t lastRssi = (uint8_t)rf24.lastRssi(); +/// \endcode +/// The units of RSSI are arbitrary and relative, with larger unsigned numbers indicating a stronger signal. Values up to 255 +/// are seen with radios in close proximity to each other. Lower limit of receivable strength is about 70. +/// +/// \par Transmitter Power +/// +/// You can control the transmitter power on the RF24/25/26/27 transceiver +/// with the RH_RF24::setTxPower() function. The argument can be any of +/// 0x00 to 0x4f (for RFM24/Si4460) or +/// 0x00 to 0x7f (for others) +/// 0x00 will yield no measurable power. For other settings there is a non-linear correlation with actual +/// RF power output (see below) +/// The default is 0x10. Eg: +/// \code +/// driver.setTxPower(0x10); +/// \endcode +/// +/// We have made some actual power measurements against +/// programmed power +/// - Anarduino Mini with RFM24-433 and RFM26-433 at Vcc = 3.3V, in CW mode, 434MHz +/// - 10cm RG58C/U soldered direct to RFM69 module ANT and GND +/// - bnc connecteor +/// - 12dB attenuator +/// - BNC-SMA adapter +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Digitech QM-1460 digital multimeter +/// \code +/// Program power Measured Power dBm +/// HEX RFM24 RFM26 +/// 0x00 not measurable not measurable +/// 0x01 -20.4 -20.6 +/// 0x0f 2.4 4.8 +/// 0x1f 9.4 11.0 +/// 0x2f 11.2 14.2 +/// 0x3f 11.6 16.4 +/// 0x4f 11.6 18.0 +/// 0x5f 18.6 +/// 0x6f 19.0 +/// 0x7f 19.2 +/// \endcode +/// Caution: the actual radiated power output will depend heavily on the power supply voltage and the antenna. + +class RH_RF24 : public RHSPIDriver +{ +public: + /// \brief Defines property values for a set of modem configuration registers + /// + /// Defines property values for a set of modem configuration registers + /// that can be passed to setModemRegisters() if none of the choices in + /// ModemConfigChoice suit your need setModemRegisters() writes the + /// property values from this structure to the appropriate RF24 properties + /// to set the desired modulation type, data rate and deviation/bandwidth. + /// OBSOLETE: no need ever to use this now + typedef struct + { + uint8_t prop_2000; ///< Value for property RH_RF24_PROPERTY_MODEM_MOD_TYPE + uint8_t prop_2003; ///< Value for property RH_RF24_PROPERTY_MODEM_DATA_RATE_2 + uint8_t prop_2004; ///< Value for property RH_RF24_PROPERTY_MODEM_DATA_RATE_1 + uint8_t prop_2005; ///< Value for property RH_RF24_PROPERTY_MODEM_DATA_RATE_0 + uint8_t prop_2006; ///< Value for property RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_3 + uint8_t prop_2007; ///< Value for property RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_2 + uint8_t prop_2008; ///< Value for property RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_1 + uint8_t prop_2009; ///< Value for property RH_RF24_PROPERTY_MODEM_TX_NCO_MODE_0 + uint8_t prop_200a; ///< Value for property RH_RF24_PROPERTY_MODEM_FREQ_DEV_2 + uint8_t prop_200b; ///< Value for property RH_RF24_PROPERTY_MODEM_FREQ_DEV_1 + uint8_t prop_200c; ///< Value for property RH_RF24_PROPERTY_MODEM_FREQ_DEV_0 + uint8_t prop_2018; ///< Value for property RH_RF24_PROPERTY_MODEM_TX_RAMP_DELAY + uint8_t prop_201e; ///< Value for property RH_RF24_PROPERTY_MODEM_DECIMATION_CFG1 + uint8_t prop_201f; ///< Value for property RH_RF24_PROPERTY_MODEM_DECIMATION_CFG0 + uint8_t prop_2022; ///< Value for property RH_RF24_PROPERTY_MODEM_BCR_OSR_1 + uint8_t prop_2023; ///< Value for property RH_RF24_PROPERTY_MODEM_BCR_OSR_0 + uint8_t prop_2024; ///< Value for property RH_RF24_PROPERTY_MODEM_BCR_NCO_OFFSET_2 + uint8_t prop_2025; ///< Value for property RH_RF24_PROPERTY_MODEM_BCR_NCO_OFFSET_1 + uint8_t prop_2026; ///< Value for property RH_RF24_PROPERTY_MODEM_BCR_NCO_OFFSET_0 + uint8_t prop_2027; ///< Value for property RH_RF24_PROPERTY_MODEM_BCR_GAIN_1 + uint8_t prop_2028; ///< Value for property RH_RF24_PROPERTY_MODEM_BCR_GAIN_0 + uint8_t prop_2029; ///< Value for property RH_RF24_PROPERTY_MODEM_BCR_GEAR + uint8_t prop_202d; ///< Value for property RH_RF24_PROPERTY_MODEM_AFC_WAIT + uint8_t prop_202e; ///< Value for property RH_RF24_PROPERTY_MODEM_AFC_GAIN_1 + uint8_t prop_202f; ///< Value for property RH_RF24_PROPERTY_MODEM_AFC_GAIN_0 + uint8_t prop_2030; ///< Value for property RH_RF24_PROPERTY_MODEM_AFC_LIMITER_1 + uint8_t prop_2031; ///< Value for property RH_RF24_PROPERTY_MODEM_AFC_LIMITER_0 + uint8_t prop_2035; ///< Value for property RH_RF24_PROPERTY_MODEM_AGC_CONTROL + uint8_t prop_2038; ///< Value for property RH_RF24_PROPERTY_MODEM_AGC_WINDOW_SIZE + uint8_t prop_2039; ///< Value for property RH_RF24_PROPERTY_MODEM_AGC_RFPD_DECAY + uint8_t prop_203a; ///< Value for property RH_RF24_PROPERTY_MODEM_AGC_IFPD_DECAY + uint8_t prop_203b; ///< Value for property RH_RF24_PROPERTY_MODEM_FSK4_GAIN1 + uint8_t prop_203c; ///< Value for property RH_RF24_PROPERTY_MODEM_FSK4_GAIN0 + uint8_t prop_203d; ///< Value for property RH_RF24_PROPERTY_MODEM_FSK4_TH1 + uint8_t prop_203e; ///< Value for property RH_RF24_PROPERTY_MODEM_FSK4_TH0 + uint8_t prop_203f; ///< Value for property RH_RF24_PROPERTY_MODEM_FSK4_MAP + uint8_t prop_2040; ///< Value for property RH_RF24_PROPERTY_MODEM_OOK_PDTC + uint8_t prop_2043; ///< Value for property RH_RF24_PROPERTY_MODEM_OOK_MISC + uint8_t prop_2045; ///< Value for property RH_RF24_PROPERTY_MODEM_RAW_CONTROL + uint8_t prop_2046; ///< Value for property RH_RF24_PROPERTY_MODEM_RAW_EYE_1 + uint8_t prop_2047; ///< Value for property RH_RF24_PROPERTY_MODEM_RAW_EYE_0 + uint8_t prop_204e; ///< Value for property RH_RF24_PROPERTY_MODEM_RSSI_COMP + uint8_t prop_2100; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE13_7_0 + uint8_t prop_2101; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE12_7_0 + uint8_t prop_2102; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE11_7_0 + uint8_t prop_2103; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE10_7_0 + uint8_t prop_2104; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE9_7_0 + uint8_t prop_2105; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE8_7_0 + uint8_t prop_2106; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE7_7_0 + uint8_t prop_2107; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE6_7_0 + uint8_t prop_2108; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE5_7_0 + uint8_t prop_2109; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE4_7_0 + uint8_t prop_210a; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE3_7_0 + uint8_t prop_210b; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE2_7_0 + uint8_t prop_210c; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE1_7_0 + uint8_t prop_210d; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COE0_7_0 + uint8_t prop_210e; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM0 + uint8_t prop_210f; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM1 + uint8_t prop_2110; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM2 + uint8_t prop_2111; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX1_CHFLT_COEM3 + uint8_t prop_2112; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE13_7_0 + uint8_t prop_2113; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE12_7_0 + uint8_t prop_2114; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE11_7_0 + uint8_t prop_2115; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE10_7_0 + uint8_t prop_2116; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE9_7_0 + uint8_t prop_2117; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE8_7_0 + uint8_t prop_2118; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE7_7_0 + uint8_t prop_2119; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE6_7_0 + uint8_t prop_211a; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE5_7_0 + uint8_t prop_211b; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE4_7_0 + uint8_t prop_211c; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE3_7_0 + uint8_t prop_211d; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE2_7_0 + uint8_t prop_211e; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE1_7_0 + uint8_t prop_211f; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COE0_7_0 + uint8_t prop_2120; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM0 + uint8_t prop_2121; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM1 + uint8_t prop_2122; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM2 + uint8_t prop_2123; ///< Value for property RH_RF24_PROPERTY_MODEM_CHFLT_RX2_CHFLT_COEM3 + uint8_t prop_2203; ///< Value for property RH_RF24_PROPERTY_PA_TC + uint8_t prop_2300; ///< Value for property RH_RF24_PROPERTY_SYNTH_PFDCP_CPFF + uint8_t prop_2301; ///< Value for property RH_RF24_PROPERTY_SYNTH_PFDCP_CPINT + uint8_t prop_2303; ///< Value for property RH_RF24_PROPERTY_SYNTH_LPFILT3 + uint8_t prop_2304; ///< Value for property RH_RF24_PROPERTY_SYNTH_LPFILT2 + uint8_t prop_2305; ///< Value for property RH_RF24_PROPERTY_SYNTH_LPFILT1 + } ModemConfig; + + /// Choices for setModemConfig() for a selected subset of common + /// modulation types, and data rates. If you need another configuration, + /// use the register calculator. and call setModemRegisters() with your + /// desired settings. + /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic + /// definitions and not their integer equivalents: its possible that values will be + /// changed in later versions (though we will try to avoid it). + /// Contributions of new complete and tested ModemConfigs ready to add to this list will be readily accepted. + /// OBSOLETE: no need ever to use this now + typedef enum + { + FSK_Rb0_5Fd1 = 0, ///< FSK Rb = 0.5kbs, Fd = 1kHz + FSK_Rb5Fd10, ///< FSK Rb = 5kbs, Fd = 10kHz + FSK_Rb50Fd100, ///< FSK Rb = 50kbs, Fd = 100kHz + FSK_Rb150Fd300, ///< FSK Rb = 50kbs, Fd = 100kHz + + GFSK_Rb0_5Fd1, ///< GFSK Rb = 0.5kbs, Fd = 1kHz + GFSK_Rb5Fd10, ///< GFSK Rb = 5kbs, Fd = 10kHz + GFSK_Rb50Fd100, ///< GFSK Rb = 50kbs, Fd = 100kHz + GFSK_Rb150Fd300, ///< GFSK Rb = 150kbs, Fd = 300kHz + + // We were unable to get any other OOKs to work + OOK_Rb5Bw30, ///< OOK Rb = 5kbs, Bw = 30kHz + OOK_Rb10Bw40, ///< OOK Rb = 10kbs, Bw = 40kHz + + // We were unable to get any 4FSK or 4GFSK schemes to work + + } ModemConfigChoice; + + /// \brief Defines the available choices for CRC + /// Types of permitted CRC polynomials, to be passed to setCRCPolynomial() + /// They deliberately have the same numeric values as the CRC_POLYNOMIAL field of PKT_CRC_CONFIG + typedef enum + { + CRC_NONE = 0, + CRC_ITU_T, + CRC_IEC_16, + CRC_Biacheva, + CRC_16_IBM, + CRC_CCITT, + CRC_Koopman, + CRC_IEEE_802_3, + CRC_Castagnoli, + } CRCPolynomial; + + /// \brief Defines the commands we can interrogate in printRegisters + typedef struct + { + uint8_t cmd; ///< The command number + uint8_t replyLen; ///< Number of bytes in the reply stream (after the CTS) + } CommandInfo; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// interrupt and slave select pin. After constructing, you must call init() to initialise the interface + /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient + /// distinct interrupt lines, one for each instance. + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RF24 before + /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) + /// \param[in] interruptPin The interrupt Pin number that is connected to the RF24 DIO0 interrupt line. + /// Defaults to pin 2. + /// Caution: You must specify an interrupt capable pin. + /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. + /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. + /// On other Arduinos pins 2 or 3. + /// See http://arduino.cc/en/Reference/attachInterrupt for more details. + /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. + /// On other boards, any digital pin may be used. + /// \param [in] sdnPin The pin number connected to SDN on the radio. Defaults to pin 9. + /// Connecting SDN directly to ground does not aloways provide reliable radio startup. + /// \param[in] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + RH_RF24(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, uint8_t sdnPin = 9, RHGenericSPI& spi = hardware_spi); + + /// Initialises this instance and the radio module connected to it. + /// The following steps are taken: + /// - Initialise the slave select and shutdown pins and the SPI interface library + /// - Checks the connected RF24 module can be communicated + /// - Attaches an interrupt handler + /// - Configures the RF24 module + /// - Sets the frequency to 434.0 MHz + /// - Sets the modem data rate to GFSK_Rb5Fd10 + /// - Sets the tranmitter power level to 16 (about 2.4dBm on RFM4) + /// \return true if everything was successful + bool init(); + + /// Sets the chip mode that will be used when the RH_RF24 driver is idle (ie not transmitting or receiving) + /// You can use this to control the power level consumed while idle, at the cost of slower + /// transition to tranmit or receive states + /// \param[in] idleMode The chip state to use when idle. Sensible choices might be RH_RF24_DEVICE_STATE_SLEEP or RH_RF24_DEVICE_STATE_READY + void setIdleMode(uint8_t idleMode); + + /// Sets the transmitter and receiver + /// centre frequency. + /// Valid frequency ranges for RFM24/Si4460, Si4461, RFM25/Si4463 are: + /// 142MHz to 175Mhz, 284MHz to 350MHz, 425MHz to 525MHz, 850MHz to 1050MHz. + /// Valid frequency ranges for RFM26/Si4464 are: + /// 119MHz to 960MHz. + /// Caution: RFM modules are designed with antenna coupling components to suit a limited band + /// of frequencies (marked underneath the module). It is possible to set frequencies in other bands, + /// but you may only get little or no power radiated. + /// Caution, you can only use this function to change frequency within the frequency band configured by + /// the radio configuration header file. To use a frequency in a different band, you must recompile with + /// the appropriate radio configuration header file. Setting a frequency in anotehr band will + /// have unpredicatble results. + /// \param[in] centre Frequency in MHz. + /// \param[in] afcPullInRange Not used + /// \return true if the selected frequency is within a valid range for the connected radio and if + /// setting the new frequency succeeded. + bool setFrequency(float centre, float afcPullInRange = 0.05); + + /// OBSOLETE, do not use. + /// To get different modulation schemes, you must generate a new radio config file + /// as described in this documentation. + /// Sets all the properties required to configure the data modem in the RF24, including the data rate, + /// bandwidths etc. You can use this to configure the modem with custom configurations if none of the + /// canned configurations in ModemConfigChoice suit you. + /// \param[in] config A ModemConfig structure containing values for the modem configuration registers. + void setModemRegisters(const ModemConfig* config); + + /// OBSOLETE, do not use. + /// To get different modulation schemes, you must generate a new radio config file + /// as described in this documentation. + /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// here, use setModemRegisters() with your own ModemConfig. The default after init() is RH_RF24::GFSK_Rb5Fd10. + /// \param[in] index The configuration choice. + /// \return true if index is a valid choice. + bool setModemConfig(ModemConfigChoice index); + + /// Starts the receiver and checks whether a received message is available. + /// This can be called multiple times in a timeout loop + /// \return true if a complete, valid message has been received and is able to be retrieved by + /// recv() + bool available(); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is NOT permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send (> 0) + /// \return true if the message length was valid and it was correctly queued for transmit + bool send(const uint8_t* data, uint8_t len); + + /// The maximum message length supported by this driver + /// \return The maximum message length supported by this driver + uint8_t maxMessageLength(); + + /// Sets the length of the preamble + /// in bytes. + /// Caution: this should be set to the same + /// value on all nodes in your network. Default is 4. + /// \param[in] bytes Preamble length in bytes. + void setPreambleLength(uint16_t bytes); + + /// Sets the sync words for transmit and receive + /// Caution: SyncWords should be set to the same + /// value on all nodes in your network. Nodes with different SyncWords set will never receive + /// each others messages, so different SyncWords can be used to isolate different + /// networks from each other. Default is { 0x2d, 0xd4 }. + /// \param[in] syncWords Array of sync words, 1 to 4 octets long. NULL if no sync words to be used. + /// \param[in] len Number of sync words to set, 1 to 4. 0 if no sync words to be used. + void setSyncWords(const uint8_t* syncWords = NULL, uint8_t len = 0); + + /// Sets the CRC polynomial to be used to generate the CRC for both receive and transmit + /// otherwise the default of CRC_16_IBM will be used. + /// \param[in] polynomial One of RH_RF24::CRCPolynomial choices CRC_* + /// \return true if polynomial is a valid option for this radio. + bool setCRCPolynomial(CRCPolynomial polynomial); + + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + void setModeIdle(); + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver in the RF24. + void setModeRx(); + + /// If current mode is Rx or Idle, changes it to Rx. F + /// Starts the transmitter in the RF24. + void setModeTx(); + + /// Sets the transmitter power output level register PA_PWR_LVL + /// The power argument to this function has a non-linear correlation with the actual RF power output. + /// See the transmitter power table above for some examples. + /// Also the Si446x Data Sheet section 5.4.2 may be helpful. + /// Be a good neighbour and set the lowest power level you need. + /// Caution: legal power limits may apply in certain countries. + /// After init(), the power will be set to 0x10. + /// \param[in] power Transmitter power level. For RFM24/Si4460, valid values are 0x00 to 0x4f. For others, 0x00 to 0x7f + void setTxPower(uint8_t power); + + /// Dump the values of available command replies and properties + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// Not all commands have valid replies, therefore they are not all printed. + /// Caution: the list is very long + bool printRegisters(); + + /// Send a string of command bytes to the chip and get a string of reply bytes + /// Different RFM24 commands take different numbers of command bytes and send back different numbers + /// of reply bytes. See the Si446x documentaiton for more details. + /// Both command bytes and reply bytes are optional + /// \param[in] cmd The command number. One of RH_RF24_CMD_* + /// \param[in] write_buf Pointer to write_len bytes of command input bytes to send. If there are none, set to NULL. + /// \param[in] write_len The number of bytes to send from write_buf. If there are none, set to 0 + /// \param[out] read_buf Pointer to read_len bytes of storage where the reply stream from the comand will be written. + /// If none are required, set to NULL + /// \param[in] read_len The number of bytes to read from the reply stream. If none required, set to 0. + /// \return true if the command succeeeded. + bool command(uint8_t cmd, const uint8_t* write_buf = 0, uint8_t write_len = 0, uint8_t* read_buf = 0, uint8_t read_len = 0); + + /// Set one or more chip properties using the RH_RF24_CMD_SET_PROPERTY + /// command. See the Si446x API Description AN625 for details on what properties are available. + /// param[in] firstProperty The property number of the first property to set. The first value in the values array + /// will be used to set this property, and any subsequent values will be used to set the following properties. + /// One of RH_RF24_PROPERTY_* + /// param[in] values Array of 0 or more values to write the firstProperty and subsequent proerties + /// param[in] count The number of values in the values array + /// \return true if the command succeeeded. + bool set_properties(uint16_t firstProperty, const uint8_t* values, uint8_t count); + + /// Get one or more chip properties using the RH_RF24_CMD_GET_PROPERTY + /// command. See the Si446x API Description AN625 for details on what properties are available. + /// param[in] firstProperty The property number of the first property to get. The first value in the values array + /// will be set with this property, and any subsequent values will be set from the following properties. + /// One of RH_RF24_PROPERTY_* + /// param[out] values Array of 0 or more values to receive the firstProperty and subsequent proerties + /// param[in] count The number of values in the values array + /// \return true if the command succeeeded. + bool get_properties(uint16_t firstProperty, uint8_t* values, uint8_t count); + + /// Measures and returns the current + /// Chip temperature. + /// \return The current chip temperature in degrees Centigrade + float get_temperature(); + + /// Measures and returns the current + /// Chip Vcc supply voltage. + /// \return The current chip Vcc supply voltage in Volts. + float get_battery_voltage(); + + /// Measures and returns the current + /// voltage applied to a GPIO pin (which has previously been configured as a voltage input) + /// \param[in] gpio The GPIO pin to read. 0 to 3. + /// \return The current pin voltage in Volts. + float get_gpio_voltage(uint8_t gpio); + + /// Read one of the Fast Read Response registers. + /// The Fast Read Response register must be previously configured with the matching + /// RH_RF24_PROPERTY_FRR_CTL_?_MODE property to select what chip property will be available in that register. + /// \param[in] reg The index of the FRR register to read. 0 means FRR A, 1 means B etc. + /// \return the value read from the specified Fast Read Response register. + uint8_t frr_read(uint8_t reg); + + /// Sets the radio into low-power sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// Caution: there is a time penalty as the radio takes a finte time to wake from sleep mode. + /// \return true if sleep mode was successfully entered. + virtual bool sleep(); + + /// Return the integer value of the device type + /// as read from the device in from RH_RF24_CMD_PART_INFO. + /// One of 0x4460, 0x4461, 0x4462 or 0x4463, depending on the type of device actually + /// connected. + /// \return The integer device type + uint16_t deviceType() {return _deviceType;}; + +protected: + /// This is a low level function to handle the interrupts for one instance of RF24. + /// Called automatically by isr*() + /// Should not need to be called by user code. + void handleInterrupt(); + + /// Clears the chips RX FIFO + /// \return true if successful + bool clearRxFifo(); + + /// Clears RH_RF24's internal TX and RX buffers and counters + void clearBuffer(); + + /// Loads the next part of the currently transmitting message + /// into the chips TX buffer + void sendNextFragment(); + + /// Copies the next part of the currenrtly received message from the chips RX FIFO to the + /// receive buffer + void readNextFragment(); + + /// Loads data into the chips TX FIFO + /// \param[in] data Array of data bytes to be loaded + /// \param[in] len Number of bytes in data to be loaded + /// \return true if successful + bool writeTxFifo(uint8_t *data, uint8_t len); + + /// Checks the contents of the RX buffer. + /// If it contans a valid message adressed to this node + /// sets _rxBufValid. + void validateRxBuf(); + + /// Cycles the Shutdown pin to force the cradio chip to reset + void power_on_reset(); + + /// Sets registers, commands and properties + /// in the ratio according to the data in the commands array + /// \param[in] commands Array of data containing radio commands in the format provided by radio_config_Si4460.h + /// \return true if successful + bool configure(const uint8_t* commands); + + /// Clears all pending interrutps in the radio chip. + bool cmd_clear_all_interrupts(); + +private: + + /// Low level interrupt service routine for RF24 connected to interrupt 0 + static void isr0(); + + /// Low level interrupt service routine for RF24 connected to interrupt 1 + static void isr1(); + + /// Low level interrupt service routine for RF24 connected to interrupt 1 + static void isr2(); + + /// Array of instances connected to interrupts 0 and 1 + static RH_RF24* _deviceForInterrupt[]; + + /// Index of next interrupt number to use in _deviceForInterrupt + static uint8_t _interruptCount; + + /// The configured interrupt pin connected to this instance + uint8_t _interruptPin; + + /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) + /// else 0xff + uint8_t _myInterruptIndex; + + /// The configured pin connected to the SDN pin of the radio + uint8_t _sdnPin; + + /// The radio OP mode to use when mode is RHModeIdle + uint8_t _idleMode; + + /// The reported PART device type + uint16_t _deviceType; + + /// The selected output power in dBm + int8_t _power; + + /// The message length in _buf + volatile uint8_t _bufLen; + + /// Array of octets of the last received message or the next to transmit message + uint8_t _buf[RH_RF24_MAX_PAYLOAD_LEN]; + + /// True when there is a valid message in the Rx buffer + volatile bool _rxBufValid; + + /// Index into TX buffer of the next to send chunk + volatile uint8_t _txBufSentIndex; + + /// Time in millis since the last preamble was received (and the last time the RSSI was measured) + uint32_t _lastPreambleTime; + +}; + +/// @example rf24_client.ino +/// @example rf24_server.ino +/// @example rf24_lowpower_client.ino +/// @example rf24_reliable_datagram_client.ino +/// @example rf24_reliable_datagram_server.ino + +#endif diff --git a/RH_RF24_property_data/convert.pl b/RH_RF24_property_data/convert.pl new file mode 100644 index 0000000..502bb79 --- /dev/null +++ b/RH_RF24_property_data/convert.pl @@ -0,0 +1,132 @@ +#!/usr/bin/perl +# +# Convert a RH_RFM24 dump of a desired modulation made with printRegisters into an entry suitable for +# inclusion in RH_RF24::ModemConfig MODEM_CONFIG_TABLE + +use strict; + +# List of the properties that are relevant to modulation schemes and speeds +my @wanted_properties = ( + 0x2000, + 0x2003, + 0x2004, + 0x2005, + 0x2006, + 0x2007, + 0x2008, + 0x2009, + 0x200a, + 0x200b, + 0x200c, + 0x2018, + 0x201e, + 0x201f, + 0x2022, + 0x2023, + 0x2024, + 0x2025, + 0x2026, + 0x2027, + 0x2028, + 0x2029, + 0x202d, + 0x202e, + 0x202f, + 0x2030, + 0x2031, + 0x2035, + 0x2038, + 0x2039, + 0x203a, + 0x203b, + 0x203c, + 0x203d, + 0x203e, + 0x203f, + 0x2040, + 0x2043, + 0x2045, + 0x2046, + 0x2047, + 0x204e, + 0x2100, + 0x2101, + 0x2102, + 0x2103, + 0x2104, + 0x2105, + 0x2106, + 0x2107, + 0x2108, + 0x2109, + 0x210a, + 0x210b, + 0x210c, + 0x210d, + 0x210e, + 0x210f, + 0x2110, + 0x2111, + 0x2112, + 0x2113, + 0x2114, + 0x2115, + 0x2116, + 0x2117, + 0x2118, + 0x2119, + 0x211a, + 0x211b, + 0x211c, + 0x211d, + 0x211e, + 0x211f, + 0x2120, + 0x2121, + 0x2122, + 0x2123, + 0x2203, + 0x2300, + 0x2301, + 0x2303, + 0x2304, + 0x2305, + ); + +my %properties; + +while (<>) +{ + if (/prop: (\S+): (\S+)/) + { + my $prop_num = hex($1); + my $prop_value = hex($2); + $properties{$prop_num} = $prop_value; + } +} + +# now have all the properties in %properties +# dump the ones we are interested in + +my $prop_num; + +print " { "; +foreach $prop_num (@wanted_properties) +{ + if (exists($properties{$prop_num})) + { + printf "0x%02x, ", $properties{$prop_num}; + } + else + { + printf "not present: 0x%04x\n", $prop_num; + } +} +print "},\n"; + +print "\nPut these lines in RH_RF24::setModemRegisters\n\n"; +# Generate lines for RH_RF24::setModemRegisters +foreach $prop_num (@wanted_properties) +{ + printf " set_properties(0x%04x, &config->prop_%04x, 1);\n", $prop_num, $prop_num; +} diff --git a/RH_RF69.cpp b/RH_RF69.cpp new file mode 100644 index 0000000..1180fb0 --- /dev/null +++ b/RH_RF69.cpp @@ -0,0 +1,573 @@ +// RH_RF69.cpp +// +// Copyright (C) 2011 Mike McCauley +// $Id: RH_RF69.cpp,v 1.31 2019/09/02 05:21:52 mikem Exp $ + +#include + +// Interrupt vectors for the 3 Arduino interrupt pins +// Each interrupt can be handled by a different instance of RH_RF69, allowing you to have +// 2 or more RF69s per Arduino +RH_RF69* RH_RF69::_deviceForInterrupt[RH_RF69_NUM_INTERRUPTS] = {0, 0, 0}; +uint8_t RH_RF69::_interruptCount = 0; // Index into _deviceForInterrupt for next device + +// These are indexed by the values of ModemConfigChoice +// Stored in flash (program) memory to save SRAM +// It is important to keep the modulation index for FSK between 0.5 and 10 +// modulation index = 2 * Fdev / BR +// Note that I have not had much success with FSK with Fd > ~5 +// You have to construct these by hand, using the data from the RF69 Datasheet :-( +// or use the SX1231 starter kit software (Ctl-Alt-N to use that without a connected radio) +#define CONFIG_FSK (RH_RF69_DATAMODUL_DATAMODE_PACKET | RH_RF69_DATAMODUL_MODULATIONTYPE_FSK | RH_RF69_DATAMODUL_MODULATIONSHAPING_FSK_NONE) +#define CONFIG_GFSK (RH_RF69_DATAMODUL_DATAMODE_PACKET | RH_RF69_DATAMODUL_MODULATIONTYPE_FSK | RH_RF69_DATAMODUL_MODULATIONSHAPING_FSK_BT1_0) +#define CONFIG_OOK (RH_RF69_DATAMODUL_DATAMODE_PACKET | RH_RF69_DATAMODUL_MODULATIONTYPE_OOK | RH_RF69_DATAMODUL_MODULATIONSHAPING_OOK_NONE) + +// Choices for RH_RF69_REG_37_PACKETCONFIG1: +#define CONFIG_NOWHITE (RH_RF69_PACKETCONFIG1_PACKETFORMAT_VARIABLE | RH_RF69_PACKETCONFIG1_DCFREE_NONE | RH_RF69_PACKETCONFIG1_CRC_ON | RH_RF69_PACKETCONFIG1_ADDRESSFILTERING_NONE) +#define CONFIG_WHITE (RH_RF69_PACKETCONFIG1_PACKETFORMAT_VARIABLE | RH_RF69_PACKETCONFIG1_DCFREE_WHITENING | RH_RF69_PACKETCONFIG1_CRC_ON | RH_RF69_PACKETCONFIG1_ADDRESSFILTERING_NONE) +#define CONFIG_MANCHESTER (RH_RF69_PACKETCONFIG1_PACKETFORMAT_VARIABLE | RH_RF69_PACKETCONFIG1_DCFREE_MANCHESTER | RH_RF69_PACKETCONFIG1_CRC_ON | RH_RF69_PACKETCONFIG1_ADDRESSFILTERING_NONE) +PROGMEM static const RH_RF69::ModemConfig MODEM_CONFIG_TABLE[] = +{ + // 02, 03, 04, 05, 06, 19, 1a, 37 + // FSK, No Manchester, no shaping, whitening, CRC, no address filtering + // AFC BW == RX BW == 2 x bit rate + // Low modulation indexes of ~ 1 at slow speeds do not seem to work very well. Choose MI of 2. + { CONFIG_FSK, 0x3e, 0x80, 0x00, 0x52, 0xf4, 0xf4, CONFIG_WHITE}, // FSK_Rb2Fd5 + { CONFIG_FSK, 0x34, 0x15, 0x00, 0x4f, 0xf4, 0xf4, CONFIG_WHITE}, // FSK_Rb2_4Fd4_8 + { CONFIG_FSK, 0x1a, 0x0b, 0x00, 0x9d, 0xf4, 0xf4, CONFIG_WHITE}, // FSK_Rb4_8Fd9_6 + + { CONFIG_FSK, 0x0d, 0x05, 0x01, 0x3b, 0xf4, 0xf4, CONFIG_WHITE}, // FSK_Rb9_6Fd19_2 + { CONFIG_FSK, 0x06, 0x83, 0x02, 0x75, 0xf3, 0xf3, CONFIG_WHITE}, // FSK_Rb19_2Fd38_4 + { CONFIG_FSK, 0x03, 0x41, 0x04, 0xea, 0xf2, 0xf2, CONFIG_WHITE}, // FSK_Rb38_4Fd76_8 + + { CONFIG_FSK, 0x02, 0x2c, 0x07, 0xae, 0xe2, 0xe2, CONFIG_WHITE}, // FSK_Rb57_6Fd120 + { CONFIG_FSK, 0x01, 0x00, 0x08, 0x00, 0xe1, 0xe1, CONFIG_WHITE}, // FSK_Rb125Fd125 + { CONFIG_FSK, 0x00, 0x80, 0x10, 0x00, 0xe0, 0xe0, CONFIG_WHITE}, // FSK_Rb250Fd250 + { CONFIG_FSK, 0x02, 0x40, 0x03, 0x33, 0x42, 0x42, CONFIG_WHITE}, // FSK_Rb55555Fd50 + + // 02, 03, 04, 05, 06, 19, 1a, 37 + // GFSK (BT=1.0), No Manchester, whitening, CRC, no address filtering + // AFC BW == RX BW == 2 x bit rate + { CONFIG_GFSK, 0x3e, 0x80, 0x00, 0x52, 0xf4, 0xf5, CONFIG_WHITE}, // GFSK_Rb2Fd5 + { CONFIG_GFSK, 0x34, 0x15, 0x00, 0x4f, 0xf4, 0xf4, CONFIG_WHITE}, // GFSK_Rb2_4Fd4_8 + { CONFIG_GFSK, 0x1a, 0x0b, 0x00, 0x9d, 0xf4, 0xf4, CONFIG_WHITE}, // GFSK_Rb4_8Fd9_6 + + { CONFIG_GFSK, 0x0d, 0x05, 0x01, 0x3b, 0xf4, 0xf4, CONFIG_WHITE}, // GFSK_Rb9_6Fd19_2 + { CONFIG_GFSK, 0x06, 0x83, 0x02, 0x75, 0xf3, 0xf3, CONFIG_WHITE}, // GFSK_Rb19_2Fd38_4 + { CONFIG_GFSK, 0x03, 0x41, 0x04, 0xea, 0xf2, 0xf2, CONFIG_WHITE}, // GFSK_Rb38_4Fd76_8 + + { CONFIG_GFSK, 0x02, 0x2c, 0x07, 0xae, 0xe2, 0xe2, CONFIG_WHITE}, // GFSK_Rb57_6Fd120 + { CONFIG_GFSK, 0x01, 0x00, 0x08, 0x00, 0xe1, 0xe1, CONFIG_WHITE}, // GFSK_Rb125Fd125 + { CONFIG_GFSK, 0x00, 0x80, 0x10, 0x00, 0xe0, 0xe0, CONFIG_WHITE}, // GFSK_Rb250Fd250 + { CONFIG_GFSK, 0x02, 0x40, 0x03, 0x33, 0x42, 0x42, CONFIG_WHITE}, // GFSK_Rb55555Fd50 + + // 02, 03, 04, 05, 06, 19, 1a, 37 + // OOK, No Manchester, no shaping, whitening, CRC, no address filtering + // with the help of the SX1231 configuration program + // AFC BW == RX BW + // All OOK configs have the default: + // Threshold Type: Peak + // Peak Threshold Step: 0.5dB + // Peak threshiold dec: ONce per chip + // Fixed threshold: 6dB + { CONFIG_OOK, 0x7d, 0x00, 0x00, 0x10, 0x88, 0x88, CONFIG_WHITE}, // OOK_Rb1Bw1 + { CONFIG_OOK, 0x68, 0x2b, 0x00, 0x10, 0xf1, 0xf1, CONFIG_WHITE}, // OOK_Rb1_2Bw75 + { CONFIG_OOK, 0x34, 0x15, 0x00, 0x10, 0xf5, 0xf5, CONFIG_WHITE}, // OOK_Rb2_4Bw4_8 + { CONFIG_OOK, 0x1a, 0x0b, 0x00, 0x10, 0xf4, 0xf4, CONFIG_WHITE}, // OOK_Rb4_8Bw9_6 + { CONFIG_OOK, 0x0d, 0x05, 0x00, 0x10, 0xf3, 0xf3, CONFIG_WHITE}, // OOK_Rb9_6Bw19_2 + { CONFIG_OOK, 0x06, 0x83, 0x00, 0x10, 0xf2, 0xf2, CONFIG_WHITE}, // OOK_Rb19_2Bw38_4 + { CONFIG_OOK, 0x03, 0xe8, 0x00, 0x10, 0xe2, 0xe2, CONFIG_WHITE}, // OOK_Rb32Bw64 + +// { CONFIG_FSK, 0x68, 0x2b, 0x00, 0x52, 0x55, 0x55, CONFIG_WHITE}, // works: Rb1200 Fd 5000 bw10000, DCC 400 +// { CONFIG_FSK, 0x0c, 0x80, 0x02, 0x8f, 0x52, 0x52, CONFIG_WHITE}, // works 10/40/80 +// { CONFIG_FSK, 0x0c, 0x80, 0x02, 0x8f, 0x53, 0x53, CONFIG_WHITE}, // works 10/40/40 + +}; +RH_RF69::RH_RF69(uint8_t slaveSelectPin, uint8_t interruptPin, RHGenericSPI& spi) + : + RHSPIDriver(slaveSelectPin, spi) +{ + _interruptPin = interruptPin; + _idleMode = RH_RF69_OPMODE_MODE_STDBY; + _myInterruptIndex = 0xff; // Not allocated yet +} + +void RH_RF69::setIdleMode(uint8_t idleMode) +{ + _idleMode = idleMode; +} + +bool RH_RF69::init() +{ + if (!RHSPIDriver::init()) + return false; + + // Determine the interrupt number that corresponds to the interruptPin + int interruptNumber = digitalPinToInterrupt(_interruptPin); + if (interruptNumber == NOT_AN_INTERRUPT) + return false; +#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER + interruptNumber = _interruptPin; +#endif + + // Tell the low level SPI interface we will use SPI within this interrupt + spiUsingInterrupt(interruptNumber); + + // Get the device type and check it + // This also tests whether we are really connected to a device + // My test devices return 0x24 + _deviceType = spiRead(RH_RF69_REG_10_VERSION); + Serial.println( _deviceType); + if (_deviceType == 00 || + _deviceType == 0xff) + return false; + + // Add by Adrien van den Bossche for Teensy + // ARM M4 requires the below. else pin interrupt doesn't work properly. + // On all other platforms, its innocuous, belt and braces + pinMode(_interruptPin, INPUT); + + // Set up interrupt handler + // Since there are a limited number of interrupt glue functions isr*() available, + // we can only support a limited number of devices simultaneously + // ON some devices, notably most Arduinos, the interrupt pin passed in is actuallt the + // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping + // yourself based on knwledge of what Arduino board you are running on. + if (_myInterruptIndex == 0xff) + { + // First run, no interrupt allocated yet + if (_interruptCount <= RH_RF69_NUM_INTERRUPTS) + _myInterruptIndex = _interruptCount++; + else + return false; // Too many devices, not enough interrupt vectors + } + _deviceForInterrupt[_myInterruptIndex] = this; + if (_myInterruptIndex == 0) + attachInterrupt(interruptNumber, isr0, RISING); + else if (_myInterruptIndex == 1) + attachInterrupt(interruptNumber, isr1, RISING); + else if (_myInterruptIndex == 2) + attachInterrupt(interruptNumber, isr2, RISING); + else + return false; // Too many devices, not enough interrupt vectors + + setModeIdle(); + + // Configure important RH_RF69 registers + // Here we set up the standard packet format for use by the RH_RF69 library: + // 4 bytes preamble + // 2 SYNC words 2d, d4 + // 2 CRC CCITT octets computed on the header, length and data (this in the modem config data) + // 0 to 60 bytes data + // RSSI Threshold -114dBm + // We dont use the RH_RF69s address filtering: instead we prepend our own headers to the beginning + // of the RH_RF69 payload + spiWrite(RH_RF69_REG_3C_FIFOTHRESH, RH_RF69_FIFOTHRESH_TXSTARTCONDITION_NOTEMPTY | 0x0f); // thresh 15 is default + // RSSITHRESH is default +// spiWrite(RH_RF69_REG_29_RSSITHRESH, 220); // -110 dbM + // SYNCCONFIG is default. SyncSize is set later by setSyncWords() +// spiWrite(RH_RF69_REG_2E_SYNCCONFIG, RH_RF69_SYNCCONFIG_SYNCON); // auto, tolerance 0 + // PAYLOADLENGTH is default +// spiWrite(RH_RF69_REG_38_PAYLOADLENGTH, RH_RF69_FIFO_SIZE); // max size only for RX + // PACKETCONFIG 2 is default + spiWrite(RH_RF69_REG_6F_TESTDAGC, RH_RF69_TESTDAGC_CONTINUOUSDAGC_IMPROVED_LOWBETAOFF); + // If high power boost set previously, disable it + spiWrite(RH_RF69_REG_5A_TESTPA1, RH_RF69_TESTPA1_NORMAL); + spiWrite(RH_RF69_REG_5C_TESTPA2, RH_RF69_TESTPA2_NORMAL); + + // The following can be changed later by the user if necessary. + // Set up default configuration + uint8_t syncwords[] = { 0x2d, 0xd4 }; + setSyncWords(syncwords, sizeof(syncwords)); // Same as RF22's + // Reasonably fast and reliable default speed and modulation + setModemConfig(GFSK_Rb250Fd250); + + // 3 would be sufficient, but this is the same as RF22's + setPreambleLength(4); + // An innocuous ISM frequency, same as RF22's + setFrequency(434.0); + // No encryption + setEncryptionKey(NULL); + // +13dBm, same as power-on default + setTxPower(13); + + return true; +} + +// C++ level interrupt handler for this instance +// RH_RF69 is unusual in that it has several interrupt lines, and not a single, combined one. +// On Moteino, only one of the several interrupt lines (DI0) from the RH_RF69 is connnected to the processor. +// We use the single interrupt line to get PACKETSENT and PAYLOADREADY interrupts. +void RH_RF69::handleInterrupt() +{ + // Get the interrupt cause + uint8_t irqflags2 = spiRead(RH_RF69_REG_28_IRQFLAGS2); + if (_mode == RHModeTx && (irqflags2 & RH_RF69_IRQFLAGS2_PACKETSENT)) + { + // A transmitter message has been fully sent + setModeIdle(); // Clears FIFO + _txGood++; +// Serial.println("PACKETSENT"); + } + + // Must look for PAYLOADREADY, not CRCOK, since only PAYLOADREADY occurs _after_ AES decryption + // has been done + if (_mode == RHModeRx && (irqflags2 & RH_RF69_IRQFLAGS2_PAYLOADREADY)) + { + // A complete message has been received with good CRC + _lastRssi = -((int8_t)(spiRead(RH_RF69_REG_24_RSSIVALUE) >> 1)); + _lastPreambleTime = millis(); + + setModeIdle(); + // Save it in our buffer + readFifo(); +// Serial.println("PAYLOADREADY"); + } +} + +// Low level function reads the FIFO and checks the address +// Caution: since we put our headers in what the RH_RF69 considers to be the payload, if encryption is enabled +// we have to suffer the cost of decryption before we can determine whether the address is acceptable. +// Performance issue? +void RH_RF69::readFifo() +{ + ATOMIC_BLOCK_START; + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + _spi.transfer(RH_RF69_REG_00_FIFO); // Send the start address with the write mask off + uint8_t payloadlen = _spi.transfer(0); // First byte is payload len (counting the headers) + if (payloadlen <= RH_RF69_MAX_ENCRYPTABLE_PAYLOAD_LEN && + payloadlen >= RH_RF69_HEADER_LEN) + { + _rxHeaderTo = _spi.transfer(0); + // Check addressing + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + // Get the rest of the headers + _rxHeaderFrom = _spi.transfer(0); + _rxHeaderId = _spi.transfer(0); + _rxHeaderFlags = _spi.transfer(0); + // And now the real payload + for (_bufLen = 0; _bufLen < (payloadlen - RH_RF69_HEADER_LEN); _bufLen++) + _buf[_bufLen] = _spi.transfer(0); + _rxGood++; + _rxBufValid = true; + } + } + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + ATOMIC_BLOCK_END; + // Any junk remaining in the FIFO will be cleared next time we go to receive mode. +} + +// These are low level functions that call the interrupt handler for the correct +// instance of RH_RF69. +// 3 interrupts allows us to have 3 different devices +void RH_INTERRUPT_ATTR RH_RF69::isr0() +{ + if (_deviceForInterrupt[0]) + _deviceForInterrupt[0]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_RF69::isr1() +{ + if (_deviceForInterrupt[1]) + _deviceForInterrupt[1]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_RF69::isr2() +{ + if (_deviceForInterrupt[2]) + _deviceForInterrupt[2]->handleInterrupt(); +} + +int8_t RH_RF69::temperatureRead() +{ + // Caution: must be ins standby. +// setModeIdle(); + spiWrite(RH_RF69_REG_4E_TEMP1, RH_RF69_TEMP1_TEMPMEASSTART); // Start the measurement + while (spiRead(RH_RF69_REG_4E_TEMP1) & RH_RF69_TEMP1_TEMPMEASRUNNING) + ; // Wait for the measurement to complete + return 166 - spiRead(RH_RF69_REG_4F_TEMP2); // Very approximate, based on observation +} + +bool RH_RF69::setFrequency(float centre, float afcPullInRange) +{ + // Frf = FRF / FSTEP + uint32_t frf = (uint32_t)((centre * 1000000.0) / RH_RF69_FSTEP); + spiWrite(RH_RF69_REG_07_FRFMSB, (frf >> 16) & 0xff); + spiWrite(RH_RF69_REG_08_FRFMID, (frf >> 8) & 0xff); + spiWrite(RH_RF69_REG_09_FRFLSB, frf & 0xff); + + // afcPullInRange is not used + (void)afcPullInRange; + return true; +} + +int8_t RH_RF69::rssiRead() +{ + // Force a new value to be measured + // Hmmm, this hangs forever! +#if 0 + spiWrite(RH_RF69_REG_23_RSSICONFIG, RH_RF69_RSSICONFIG_RSSISTART); + while (!(spiRead(RH_RF69_REG_23_RSSICONFIG) & RH_RF69_RSSICONFIG_RSSIDONE)) + ; +#endif + return -((int8_t)(spiRead(RH_RF69_REG_24_RSSIVALUE) >> 1)); +} + +void RH_RF69::setOpMode(uint8_t mode) +{ + uint8_t opmode = spiRead(RH_RF69_REG_01_OPMODE); + opmode &= ~RH_RF69_OPMODE_MODE; + opmode |= (mode & RH_RF69_OPMODE_MODE); + spiWrite(RH_RF69_REG_01_OPMODE, opmode); + + // Wait for mode to change. + while (!(spiRead(RH_RF69_REG_27_IRQFLAGS1) & RH_RF69_IRQFLAGS1_MODEREADY)) + ; +} + +void RH_RF69::setModeIdle() +{ + if (_mode != RHModeIdle) + { + if (_power >= 18) + { + // If high power boost, return power amp to receive mode + spiWrite(RH_RF69_REG_5A_TESTPA1, RH_RF69_TESTPA1_NORMAL); + spiWrite(RH_RF69_REG_5C_TESTPA2, RH_RF69_TESTPA2_NORMAL); + } + setOpMode(_idleMode); + _mode = RHModeIdle; + } +} + +bool RH_RF69::sleep() +{ + if (_mode != RHModeSleep) + { + spiWrite(RH_RF69_REG_01_OPMODE, RH_RF69_OPMODE_MODE_SLEEP); + _mode = RHModeSleep; + } + return true; +} + +void RH_RF69::setModeRx() +{ + if (_mode != RHModeRx) + { + if (_power >= 18) + { + // If high power boost, return power amp to receive mode + spiWrite(RH_RF69_REG_5A_TESTPA1, RH_RF69_TESTPA1_NORMAL); + spiWrite(RH_RF69_REG_5C_TESTPA2, RH_RF69_TESTPA2_NORMAL); + } + spiWrite(RH_RF69_REG_25_DIOMAPPING1, RH_RF69_DIOMAPPING1_DIO0MAPPING_01); // Set interrupt line 0 PayloadReady + setOpMode(RH_RF69_OPMODE_MODE_RX); // Clears FIFO + _mode = RHModeRx; + } +} + +void RH_RF69::setModeTx() +{ + if (_mode != RHModeTx) + { + if (_power >= 18) + { + // Set high power boost mode + // Note that OCP defaults to ON so no need to change that. + spiWrite(RH_RF69_REG_5A_TESTPA1, RH_RF69_TESTPA1_BOOST); + spiWrite(RH_RF69_REG_5C_TESTPA2, RH_RF69_TESTPA2_BOOST); + } + spiWrite(RH_RF69_REG_25_DIOMAPPING1, RH_RF69_DIOMAPPING1_DIO0MAPPING_00); // Set interrupt line 0 PacketSent + setOpMode(RH_RF69_OPMODE_MODE_TX); // Clears FIFO + _mode = RHModeTx; + } +} + +void RH_RF69::setTxPower(int8_t power, bool ishighpowermodule) +{ + _power = power; + uint8_t palevel; + + if (ishighpowermodule) + { + if (_power < -2) + _power = -2; //RFM69HW only works down to -2. + if (_power <= 13) + { + // -2dBm to +13dBm + //Need PA1 exclusivelly on RFM69HW + palevel = RH_RF69_PALEVEL_PA1ON | ((_power + 18) & + RH_RF69_PALEVEL_OUTPUTPOWER); + } + else if (_power >= 18) + { + // +18dBm to +20dBm + // Need PA1+PA2 + // Also need PA boost settings change when tx is turned on and off, see setModeTx() + palevel = RH_RF69_PALEVEL_PA1ON + | RH_RF69_PALEVEL_PA2ON + | ((_power + 11) & RH_RF69_PALEVEL_OUTPUTPOWER); + } + else + { + // +14dBm to +17dBm + // Need PA1+PA2 + palevel = RH_RF69_PALEVEL_PA1ON + | RH_RF69_PALEVEL_PA2ON + | ((_power + 14) & RH_RF69_PALEVEL_OUTPUTPOWER); + } + } + else + { + if (_power < -18) _power = -18; + if (_power > 13) _power = 13; //limit for RFM69W + palevel = RH_RF69_PALEVEL_PA0ON + | ((_power + 18) & RH_RF69_PALEVEL_OUTPUTPOWER); + } + spiWrite(RH_RF69_REG_11_PALEVEL, palevel); +} + +// Sets registers from a canned modem configuration structure +void RH_RF69::setModemRegisters(const ModemConfig* config) +{ + spiBurstWrite(RH_RF69_REG_02_DATAMODUL, &config->reg_02, 5); + spiBurstWrite(RH_RF69_REG_19_RXBW, &config->reg_19, 2); + spiWrite(RH_RF69_REG_37_PACKETCONFIG1, config->reg_37); +} + +// Set one of the canned FSK Modem configs +// Returns true if its a valid choice +bool RH_RF69::setModemConfig(ModemConfigChoice index) +{ + if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig))) + return false; + + ModemConfig cfg; + memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(RH_RF69::ModemConfig)); + setModemRegisters(&cfg); + + return true; +} + +void RH_RF69::setPreambleLength(uint16_t bytes) +{ + spiWrite(RH_RF69_REG_2C_PREAMBLEMSB, bytes >> 8); + spiWrite(RH_RF69_REG_2D_PREAMBLELSB, bytes & 0xff); +} + +void RH_RF69::setSyncWords(const uint8_t* syncWords, uint8_t len) +{ + uint8_t syncconfig = spiRead(RH_RF69_REG_2E_SYNCCONFIG); + if (syncWords && len && len <= 4) + { + spiBurstWrite(RH_RF69_REG_2F_SYNCVALUE1, syncWords, len); + syncconfig |= RH_RF69_SYNCCONFIG_SYNCON; + } + else + syncconfig &= ~RH_RF69_SYNCCONFIG_SYNCON; + syncconfig &= ~RH_RF69_SYNCCONFIG_SYNCSIZE; + syncconfig |= (len-1) << 3; + spiWrite(RH_RF69_REG_2E_SYNCCONFIG, syncconfig); +} + +void RH_RF69::setEncryptionKey(uint8_t* key) +{ + if (key) + { + spiBurstWrite(RH_RF69_REG_3E_AESKEY1, key, 16); + spiWrite(RH_RF69_REG_3D_PACKETCONFIG2, spiRead(RH_RF69_REG_3D_PACKETCONFIG2) | RH_RF69_PACKETCONFIG2_AESON); + } + else + { + spiWrite(RH_RF69_REG_3D_PACKETCONFIG2, spiRead(RH_RF69_REG_3D_PACKETCONFIG2) & ~RH_RF69_PACKETCONFIG2_AESON); + } +} + +bool RH_RF69::available() +{ + if (_mode == RHModeTx) + return false; + setModeRx(); // Make sure we are receiving + return _rxBufValid; +} + +bool RH_RF69::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + + if (buf && len) + { + ATOMIC_BLOCK_START; + if (*len > _bufLen) + *len = _bufLen; + memcpy(buf, _buf, *len); + ATOMIC_BLOCK_END; + } + _rxBufValid = false; // Got the most recent message +// printBuffer("recv:", buf, *len); + return true; +} + +bool RH_RF69::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_RF69_MAX_MESSAGE_LEN) + return false; + + waitPacketSent(); // Make sure we dont interrupt an outgoing message + setModeIdle(); // Prevent RX while filling the fifo + + if (!waitCAD()) + return false; // Check channel activity + + ATOMIC_BLOCK_START; + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + _spi.transfer(RH_RF69_REG_00_FIFO | RH_RF69_SPI_WRITE_MASK); // Send the start address with the write mask on + _spi.transfer(len + RH_RF69_HEADER_LEN); // Include length of headers + // First the 4 headers + _spi.transfer(_txHeaderTo); + _spi.transfer(_txHeaderFrom); + _spi.transfer(_txHeaderId); + _spi.transfer(_txHeaderFlags); + // Now the payload + while (len--) + _spi.transfer(*data++); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + ATOMIC_BLOCK_END; + + setModeTx(); // Start the transmitter + return true; +} + +uint8_t RH_RF69::maxMessageLength() +{ + return RH_RF69_MAX_MESSAGE_LEN; +} + +bool RH_RF69::printRegister(uint8_t reg) +{ +#ifdef RH_HAVE_SERIAL + Serial.print(reg, HEX); + Serial.print(" "); + Serial.println(spiRead(reg), HEX); +#endif + return true; +} + +bool RH_RF69::printRegisters() +{ + uint8_t i; + for (i = 0; i < 0x50; i++) + printRegister(i); + // Non-contiguous registers + printRegister(RH_RF69_REG_58_TESTLNA); + printRegister(RH_RF69_REG_6F_TESTDAGC); + printRegister(RH_RF69_REG_71_TESTAFC); + + return true; +} diff --git a/RH_RF69.h b/RH_RF69.h new file mode 100644 index 0000000..e8e4c8d --- /dev/null +++ b/RH_RF69.h @@ -0,0 +1,1052 @@ +// RH_RF69.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RH_RF69.h,v 1.38 2020/04/09 23:40:34 mikem Exp $ +// +/// + + +#ifndef RH_RF69_h +#define RH_RF69_h + +#include +#include + +// The crystal oscillator frequency of the RF69 module +#define RH_RF69_FXOSC 32000000.0 + +// The Frequency Synthesizer step = RH_RF69_FXOSC / 2^^19 +#define RH_RF69_FSTEP (RH_RF69_FXOSC / 524288) + +// This is the maximum number of interrupts the driver can support +// Most Arduinos can handle 2, Megas can handle more +#define RH_RF69_NUM_INTERRUPTS 3 + +// This is the bit in the SPI address that marks it as a write +#define RH_RF69_SPI_WRITE_MASK 0x80 + +// Max number of octets the RH_RF69 Rx and Tx FIFOs can hold +#define RH_RF69_FIFO_SIZE 66 + +// Maximum encryptable payload length the RF69 can support +#define RH_RF69_MAX_ENCRYPTABLE_PAYLOAD_LEN 64 + +// The length of the headers we add. +// The headers are inside the RF69's payload and are therefore encrypted if encryption is enabled +#define RH_RF69_HEADER_LEN 4 + +// This is the maximum message length that can be supported by this driver. Limited by +// the size of the FIFO, since we are unable to support on-the-fly filling and emptying +// of the FIFO. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for 4 bytes of address and header and payload to be included in the 64 byte encryption limit. +// the one byte payload length is not encrpyted +#ifndef RH_RF69_MAX_MESSAGE_LEN +#define RH_RF69_MAX_MESSAGE_LEN (RH_RF69_MAX_ENCRYPTABLE_PAYLOAD_LEN - RH_RF69_HEADER_LEN) +#endif + +// Keep track of the mode the RF69 is in +#define RH_RF69_MODE_IDLE 0 +#define RH_RF69_MODE_RX 1 +#define RH_RF69_MODE_TX 2 + +// This is the default node address, +#define RH_RF69_DEFAULT_NODE_ADDRESS 0 + +// You can define the following macro (either by editing here or by passing it as a compiler definition +// to change the default value of the ishighpowermodule argument to setTxPower to true +// +// #define RFM69_HW +#ifdef RFM69_HW +#define RH_RF69_DEFAULT_HIGHPOWER true +#else +#define RH_RF69_DEFAULT_HIGHPOWER false +#endif + +// Register names +#define RH_RF69_REG_00_FIFO 0x00 +#define RH_RF69_REG_01_OPMODE 0x01 +#define RH_RF69_REG_02_DATAMODUL 0x02 +#define RH_RF69_REG_03_BITRATEMSB 0x03 +#define RH_RF69_REG_04_BITRATELSB 0x04 +#define RH_RF69_REG_05_FDEVMSB 0x05 +#define RH_RF69_REG_06_FDEVLSB 0x06 +#define RH_RF69_REG_07_FRFMSB 0x07 +#define RH_RF69_REG_08_FRFMID 0x08 +#define RH_RF69_REG_09_FRFLSB 0x09 +#define RH_RF69_REG_0A_OSC1 0x0a +#define RH_RF69_REG_0B_AFCCTRL 0x0b +#define RH_RF69_REG_0C_RESERVED 0x0c +#define RH_RF69_REG_0D_LISTEN1 0x0d +#define RH_RF69_REG_0E_LISTEN2 0x0e +#define RH_RF69_REG_0F_LISTEN3 0x0f +#define RH_RF69_REG_10_VERSION 0x10 +#define RH_RF69_REG_11_PALEVEL 0x11 +#define RH_RF69_REG_12_PARAMP 0x12 +#define RH_RF69_REG_13_OCP 0x13 +#define RH_RF69_REG_14_RESERVED 0x14 +#define RH_RF69_REG_15_RESERVED 0x15 +#define RH_RF69_REG_16_RESERVED 0x16 +#define RH_RF69_REG_17_RESERVED 0x17 +#define RH_RF69_REG_18_LNA 0x18 +#define RH_RF69_REG_19_RXBW 0x19 +#define RH_RF69_REG_1A_AFCBW 0x1a +#define RH_RF69_REG_1B_OOKPEAK 0x1b +#define RH_RF69_REG_1C_OOKAVG 0x1c +#define RH_RF69_REG_1D_OOKFIX 0x1d +#define RH_RF69_REG_1E_AFCFEI 0x1e +#define RH_RF69_REG_1F_AFCMSB 0x1f +#define RH_RF69_REG_20_AFCLSB 0x20 +#define RH_RF69_REG_21_FEIMSB 0x21 +#define RH_RF69_REG_22_FEILSB 0x22 +#define RH_RF69_REG_23_RSSICONFIG 0x23 +#define RH_RF69_REG_24_RSSIVALUE 0x24 +#define RH_RF69_REG_25_DIOMAPPING1 0x25 +#define RH_RF69_REG_26_DIOMAPPING2 0x26 +#define RH_RF69_REG_27_IRQFLAGS1 0x27 +#define RH_RF69_REG_28_IRQFLAGS2 0x28 +#define RH_RF69_REG_29_RSSITHRESH 0x29 +#define RH_RF69_REG_2A_RXTIMEOUT1 0x2a +#define RH_RF69_REG_2B_RXTIMEOUT2 0x2b +#define RH_RF69_REG_2C_PREAMBLEMSB 0x2c +#define RH_RF69_REG_2D_PREAMBLELSB 0x2d +#define RH_RF69_REG_2E_SYNCCONFIG 0x2e +#define RH_RF69_REG_2F_SYNCVALUE1 0x2f +// another 7 sync word bytes follow, 30 through 36 inclusive +#define RH_RF69_REG_37_PACKETCONFIG1 0x37 +#define RH_RF69_REG_38_PAYLOADLENGTH 0x38 +#define RH_RF69_REG_39_NODEADRS 0x39 +#define RH_RF69_REG_3A_BROADCASTADRS 0x3a +#define RH_RF69_REG_3B_AUTOMODES 0x3b +#define RH_RF69_REG_3C_FIFOTHRESH 0x3c +#define RH_RF69_REG_3D_PACKETCONFIG2 0x3d +#define RH_RF69_REG_3E_AESKEY1 0x3e +// Another 15 AES key bytes follow +#define RH_RF69_REG_4E_TEMP1 0x4e +#define RH_RF69_REG_4F_TEMP2 0x4f +#define RH_RF69_REG_58_TESTLNA 0x58 +#define RH_RF69_REG_5A_TESTPA1 0x5a +#define RH_RF69_REG_5C_TESTPA2 0x5c +#define RH_RF69_REG_6F_TESTDAGC 0x6f +#define RH_RF69_REG_71_TESTAFC 0x71 + +// These register masks etc are named wherever possible +// corresponding to the bit and field names in the RFM69 Manual + +// RH_RF69_REG_01_OPMODE +#define RH_RF69_OPMODE_SEQUENCEROFF 0x80 +#define RH_RF69_OPMODE_LISTENON 0x40 +#define RH_RF69_OPMODE_LISTENABORT 0x20 +#define RH_RF69_OPMODE_MODE 0x1c +#define RH_RF69_OPMODE_MODE_SLEEP 0x00 +#define RH_RF69_OPMODE_MODE_STDBY 0x04 +#define RH_RF69_OPMODE_MODE_FS 0x08 +#define RH_RF69_OPMODE_MODE_TX 0x0c +#define RH_RF69_OPMODE_MODE_RX 0x10 + +// RH_RF69_REG_02_DATAMODUL +#define RH_RF69_DATAMODUL_DATAMODE 0x60 +#define RH_RF69_DATAMODUL_DATAMODE_PACKET 0x00 +#define RH_RF69_DATAMODUL_DATAMODE_CONT_WITH_SYNC 0x40 +#define RH_RF69_DATAMODUL_DATAMODE_CONT_WITHOUT_SYNC 0x60 +#define RH_RF69_DATAMODUL_MODULATIONTYPE 0x18 +#define RH_RF69_DATAMODUL_MODULATIONTYPE_FSK 0x00 +#define RH_RF69_DATAMODUL_MODULATIONTYPE_OOK 0x08 +#define RH_RF69_DATAMODUL_MODULATIONSHAPING 0x03 +#define RH_RF69_DATAMODUL_MODULATIONSHAPING_FSK_NONE 0x00 +#define RH_RF69_DATAMODUL_MODULATIONSHAPING_FSK_BT1_0 0x01 +#define RH_RF69_DATAMODUL_MODULATIONSHAPING_FSK_BT0_5 0x02 +#define RH_RF69_DATAMODUL_MODULATIONSHAPING_FSK_BT0_3 0x03 +#define RH_RF69_DATAMODUL_MODULATIONSHAPING_OOK_NONE 0x00 +#define RH_RF69_DATAMODUL_MODULATIONSHAPING_OOK_BR 0x01 +#define RH_RF69_DATAMODUL_MODULATIONSHAPING_OOK_2BR 0x02 + +// RH_RF69_REG_11_PALEVEL +#define RH_RF69_PALEVEL_PA0ON 0x80 +#define RH_RF69_PALEVEL_PA1ON 0x40 +#define RH_RF69_PALEVEL_PA2ON 0x20 +#define RH_RF69_PALEVEL_OUTPUTPOWER 0x1f + +// RH_RF69_REG_23_RSSICONFIG +#define RH_RF69_RSSICONFIG_RSSIDONE 0x02 +#define RH_RF69_RSSICONFIG_RSSISTART 0x01 + +// RH_RF69_REG_25_DIOMAPPING1 +#define RH_RF69_DIOMAPPING1_DIO0MAPPING 0xc0 +#define RH_RF69_DIOMAPPING1_DIO0MAPPING_00 0x00 +#define RH_RF69_DIOMAPPING1_DIO0MAPPING_01 0x40 +#define RH_RF69_DIOMAPPING1_DIO0MAPPING_10 0x80 +#define RH_RF69_DIOMAPPING1_DIO0MAPPING_11 0xc0 + +#define RH_RF69_DIOMAPPING1_DIO1MAPPING 0x30 +#define RH_RF69_DIOMAPPING1_DIO1MAPPING_00 0x00 +#define RH_RF69_DIOMAPPING1_DIO1MAPPING_01 0x10 +#define RH_RF69_DIOMAPPING1_DIO1MAPPING_10 0x20 +#define RH_RF69_DIOMAPPING1_DIO1MAPPING_11 0x30 + +#define RH_RF69_DIOMAPPING1_DIO2MAPPING 0x0c +#define RH_RF69_DIOMAPPING1_DIO2MAPPING_00 0x00 +#define RH_RF69_DIOMAPPING1_DIO2MAPPING_01 0x04 +#define RH_RF69_DIOMAPPING1_DIO2MAPPING_10 0x08 +#define RH_RF69_DIOMAPPING1_DIO2MAPPING_11 0x0c + +#define RH_RF69_DIOMAPPING1_DIO3MAPPING 0x03 +#define RH_RF69_DIOMAPPING1_DIO3MAPPING_00 0x00 +#define RH_RF69_DIOMAPPING1_DIO3MAPPING_01 0x01 +#define RH_RF69_DIOMAPPING1_DIO3MAPPING_10 0x02 +#define RH_RF69_DIOMAPPING1_DIO3MAPPING_11 0x03 + +// RH_RF69_REG_26_DIOMAPPING2 +#define RH_RF69_DIOMAPPING2_DIO4MAPPING 0xc0 +#define RH_RF69_DIOMAPPING2_DIO4MAPPING_00 0x00 +#define RH_RF69_DIOMAPPING2_DIO4MAPPING_01 0x40 +#define RH_RF69_DIOMAPPING2_DIO4MAPPING_10 0x80 +#define RH_RF69_DIOMAPPING2_DIO4MAPPING_11 0xc0 + +#define RH_RF69_DIOMAPPING2_DIO5MAPPING 0x30 +#define RH_RF69_DIOMAPPING2_DIO5MAPPING_00 0x00 +#define RH_RF69_DIOMAPPING2_DIO5MAPPING_01 0x10 +#define RH_RF69_DIOMAPPING2_DIO5MAPPING_10 0x20 +#define RH_RF69_DIOMAPPING2_DIO5MAPPING_11 0x30 + +#define RH_RF69_DIOMAPPING2_CLKOUT 0x07 +#define RH_RF69_DIOMAPPING2_CLKOUT_FXOSC_ 0x00 +#define RH_RF69_DIOMAPPING2_CLKOUT_FXOSC_2 0x01 +#define RH_RF69_DIOMAPPING2_CLKOUT_FXOSC_4 0x02 +#define RH_RF69_DIOMAPPING2_CLKOUT_FXOSC_8 0x03 +#define RH_RF69_DIOMAPPING2_CLKOUT_FXOSC_16 0x04 +#define RH_RF69_DIOMAPPING2_CLKOUT_FXOSC_32 0x05 +#define RH_RF69_DIOMAPPING2_CLKOUT_FXOSC_RC 0x06 +#define RH_RF69_DIOMAPPING2_CLKOUT_FXOSC_OFF 0x07 + +// RH_RF69_REG_27_IRQFLAGS1 +#define RH_RF69_IRQFLAGS1_MODEREADY 0x80 +#define RH_RF69_IRQFLAGS1_RXREADY 0x40 +#define RH_RF69_IRQFLAGS1_TXREADY 0x20 +#define RH_RF69_IRQFLAGS1_PLLLOCK 0x10 +#define RH_RF69_IRQFLAGS1_RSSI 0x08 +#define RH_RF69_IRQFLAGS1_TIMEOUT 0x04 +#define RH_RF69_IRQFLAGS1_AUTOMODE 0x02 +#define RH_RF69_IRQFLAGS1_SYNADDRESSMATCH 0x01 + +// RH_RF69_REG_28_IRQFLAGS2 +#define RH_RF69_IRQFLAGS2_FIFOFULL 0x80 +#define RH_RF69_IRQFLAGS2_FIFONOTEMPTY 0x40 +#define RH_RF69_IRQFLAGS2_FIFOLEVEL 0x20 +#define RH_RF69_IRQFLAGS2_FIFOOVERRUN 0x10 +#define RH_RF69_IRQFLAGS2_PACKETSENT 0x08 +#define RH_RF69_IRQFLAGS2_PAYLOADREADY 0x04 +#define RH_RF69_IRQFLAGS2_CRCOK 0x02 + +// RH_RF69_REG_2E_SYNCCONFIG +#define RH_RF69_SYNCCONFIG_SYNCON 0x80 +#define RH_RF69_SYNCCONFIG_FIFOFILLCONDITION_MANUAL 0x40 +#define RH_RF69_SYNCCONFIG_SYNCSIZE 0x38 +#define RH_RF69_SYNCCONFIG_SYNCSIZE_1 0x00 +#define RH_RF69_SYNCCONFIG_SYNCSIZE_2 0x08 +#define RH_RF69_SYNCCONFIG_SYNCSIZE_3 0x10 +#define RH_RF69_SYNCCONFIG_SYNCSIZE_4 0x18 +#define RH_RF69_SYNCCONFIG_SYNCSIZE_5 0x20 +#define RH_RF69_SYNCCONFIG_SYNCSIZE_6 0x28 +#define RH_RF69_SYNCCONFIG_SYNCSIZE_7 0x30 +#define RH_RF69_SYNCCONFIG_SYNCSIZE_8 0x38 +#define RH_RF69_SYNCCONFIG_SYNCSIZE_SYNCTOL 0x07 + +// RH_RF69_REG_37_PACKETCONFIG1 +#define RH_RF69_PACKETCONFIG1_PACKETFORMAT_VARIABLE 0x80 +#define RH_RF69_PACKETCONFIG1_DCFREE 0x60 +#define RH_RF69_PACKETCONFIG1_DCFREE_NONE 0x00 +#define RH_RF69_PACKETCONFIG1_DCFREE_MANCHESTER 0x20 +#define RH_RF69_PACKETCONFIG1_DCFREE_WHITENING 0x40 +#define RH_RF69_PACKETCONFIG1_DCFREE_RESERVED 0x60 +#define RH_RF69_PACKETCONFIG1_CRC_ON 0x10 +#define RH_RF69_PACKETCONFIG1_CRCAUTOCLEAROFF 0x08 +#define RH_RF69_PACKETCONFIG1_ADDRESSFILTERING 0x06 +#define RH_RF69_PACKETCONFIG1_ADDRESSFILTERING_NONE 0x00 +#define RH_RF69_PACKETCONFIG1_ADDRESSFILTERING_NODE 0x02 +#define RH_RF69_PACKETCONFIG1_ADDRESSFILTERING_NODE_BC 0x04 +#define RH_RF69_PACKETCONFIG1_ADDRESSFILTERING_RESERVED 0x06 + +// RH_RF69_REG_3B_AUTOMODES +#define RH_RF69_AUTOMODE_ENTER_COND_NONE 0x00 +#define RH_RF69_AUTOMODE_ENTER_COND_FIFO_NOT_EMPTY 0x20 +#define RH_RF69_AUTOMODE_ENTER_COND_FIFO_LEVEL 0x40 +#define RH_RF69_AUTOMODE_ENTER_COND_CRC_OK 0x60 +#define RH_RF69_AUTOMODE_ENTER_COND_PAYLOAD_READY 0x80 +#define RH_RF69_AUTOMODE_ENTER_COND_SYNC_ADDRESS 0xa0 +#define RH_RF69_AUTOMODE_ENTER_COND_PACKET_SENT 0xc0 +#define RH_RF69_AUTOMODE_ENTER_COND_FIFO_EMPTY 0xe0 + +#define RH_RF69_AUTOMODE_EXIT_COND_NONE 0x00 +#define RH_RF69_AUTOMODE_EXIT_COND_FIFO_EMPTY 0x04 +#define RH_RF69_AUTOMODE_EXIT_COND_FIFO_LEVEL 0x08 +#define RH_RF69_AUTOMODE_EXIT_COND_CRC_OK 0x0c +#define RH_RF69_AUTOMODE_EXIT_COND_PAYLOAD_READY 0x10 +#define RH_RF69_AUTOMODE_EXIT_COND_SYNC_ADDRESS 0x14 +#define RH_RF69_AUTOMODE_EXIT_COND_PACKET_SENT 0x18 +#define RH_RF69_AUTOMODE_EXIT_COND_TIMEOUT 0x1c + +#define RH_RF69_AUTOMODE_INTERMEDIATE_MODE_SLEEP 0x00 +#define RH_RF69_AUTOMODE_INTERMEDIATE_MODE_STDBY 0x01 +#define RH_RF69_AUTOMODE_INTERMEDIATE_MODE_RX 0x02 +#define RH_RF69_AUTOMODE_INTERMEDIATE_MODE_TX 0x03 + +// RH_RF69_REG_3C_FIFOTHRESH +#define RH_RF69_FIFOTHRESH_TXSTARTCONDITION_NOTEMPTY 0x80 +#define RH_RF69_FIFOTHRESH_FIFOTHRESHOLD 0x7f + +// RH_RF69_REG_3D_PACKETCONFIG2 +#define RH_RF69_PACKETCONFIG2_INTERPACKETRXDELAY 0xf0 +#define RH_RF69_PACKETCONFIG2_RESTARTRX 0x04 +#define RH_RF69_PACKETCONFIG2_AUTORXRESTARTON 0x02 +#define RH_RF69_PACKETCONFIG2_AESON 0x01 + +// RH_RF69_REG_4E_TEMP1 +#define RH_RF69_TEMP1_TEMPMEASSTART 0x08 +#define RH_RF69_TEMP1_TEMPMEASRUNNING 0x04 + +// RH_RF69_REG_5A_TESTPA1 +#define RH_RF69_TESTPA1_NORMAL 0x55 +#define RH_RF69_TESTPA1_BOOST 0x5d + +// RH_RF69_REG_5C_TESTPA2 +#define RH_RF69_TESTPA2_NORMAL 0x70 +#define RH_RF69_TESTPA2_BOOST 0x7c + +// RH_RF69_REG_6F_TESTDAGC +#define RH_RF69_TESTDAGC_CONTINUOUSDAGC_NORMAL 0x00 +#define RH_RF69_TESTDAGC_CONTINUOUSDAGC_IMPROVED_LOWBETAON 0x20 +#define RH_RF69_TESTDAGC_CONTINUOUSDAGC_IMPROVED_LOWBETAOFF 0x30 + +// Define this to include Serial printing in diagnostic routines +#define RH_RF69_HAVE_SERIAL + + +///////////////////////////////////////////////////////////////////// +/// \class RH_RF69 RH_RF69.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via an RF69 and compatible radio transceiver. +/// +/// Works with +/// - the excellent Moteino and Moteino-USB +/// boards from LowPowerLab http://lowpowerlab.com/moteino/ +/// - compatible chips and modules such as RFM69W, RFM69HW, RFM69CW, RFM69HCW (Semtech SX1231, SX1231H), +/// - RFM69 modules from http://www.hoperfusa.com such as http://www.hoperfusa.com/details.jsp?pid=145 +/// - Anarduino MiniWireless -CW and -HW boards http://www.anarduino.com/miniwireless/ including +/// the marvellous high powered MinWireless-HW (with 20dBm output for excellent range) +/// - the excellent Rocket Scream Mini Ultra Pro with the RFM69HCW +/// http://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/ +/// - The excellent Talk2 Whisper Node boards +/// (https://talk2.wisen.com.au/ and https://bitbucket.org/talk2/whisper-node-avr), +/// an Arduino compatible board, which include an on-board RF69 radio, external antenna, +/// run on 2xAAA batteries and support low power operations. RF69 examples work without modification. +/// Use Arduino Board Manager to install the Talk2 code support as described in +/// https://bitbucket.org/talk2/whisper-node-avr. Upeload the code with an FTDI adapter set to 3.3V. +/// - The excellent Adafruit Feather. These are excellent boards that are available with a variety of radios. +/// We tested with the +/// Feather 32u4 with RFM69HCW radio, with Arduino IDE 1.6.8 and the Adafruit AVR Boards board manager version 1.6.10. +/// https://www.adafruit.com/products/3076 +/// +/// \par Overview +/// +/// This class provides basic functions for sending and receiving unaddressed, +/// unreliable datagrams of arbitrary length to 64 octets per packet. +/// +/// Manager classes may use this class to implement reliable, addressed datagrams and streams, +/// mesh routers, repeaters, translators etc. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// modulation scheme. +/// +/// This Driver provides an object-oriented interface for sending and receiving data messages with Hope-RF +/// RF69B and compatible radio modules, such as the RFM69 module. +/// +/// The Hope-RF (http://www.hoperf.com) RF69 is a low-cost ISM transceiver +/// chip. It supports FSK, GFSK, OOK over a wide range of frequencies and +/// programmable data rates. It also suports AES encryption of up to 64 octets +/// of payload It is available prepackaged on modules such as the RFM69W. And +/// such modules can be prepacked on processor boards such as the Moteino from +/// LowPowerLabs (which is what we used to develop the RH_RF69 driver) +/// +/// This Driver provides functions for sending and receiving messages of up +/// to 60 octets on any frequency supported by the RF69, in a range of +/// predefined data rates and frequency deviations. Frequency can be set with +/// 61Hz precision to any frequency from 240.0MHz to 960.0MHz. Caution: most modules only support a more limited +/// range of frequencies due to antenna tuning. +/// +/// Up to 2 RF69B modules can be connected to an Arduino (3 on a Mega), +/// permitting the construction of translators and frequency changers, etc. +/// +/// The following modulation types are suppported with a range of modem configurations for +/// common data rates and frequency deviations: +/// - GFSK Gaussian Frequency Shift Keying +/// - FSK Frequency Shift Keying +/// +/// Support for other RF69 features such as on-chip temperature measurement, +/// transmitter power control etc is also provided. +/// +/// Tested on USB-Moteino with arduino-1.0.5 +/// on OpenSuSE 13.1 +/// +/// \par Packet Format +/// +/// All messages sent and received by this RH_RF69 Driver conform to this packet format: +/// +/// - 4 octets PREAMBLE +/// - 2 octets SYNC 0x2d, 0xd4 (configurable, so you can use this as a network filter) +/// - 1 octet RH_RF69 payload length +/// - 4 octets HEADER: (TO, FROM, ID, FLAGS) +/// - 0 to 60 octets DATA +/// - 2 octets CRC computed with CRC16(IBM), computed on HEADER and DATA +/// +/// For technical reasons, the message format is not protocol compatible with the +/// 'HopeRF Radio Transceiver Message Library for Arduino' +/// http://www.airspayce.com/mikem/arduino/HopeRF from the same author. Nor is +/// it compatible with messages sent by 'Virtual Wire' +/// http://www.airspayce.com/mikem/arduino/VirtualWire.pdf also from the same +/// author. Nor is it compatible with messages sent by 'RF22' +/// http://www.airspayce.com/mikem/arduino/RF22 also from the same author. +/// +/// \par Connecting RFM-69 to Arduino +/// +/// We tested with Moteino, which is an Arduino Uno compatible with the RFM69W +/// module on-board. Therefore it needs no connections other than the USB +/// programming connection and an antenna to make it work. +/// +/// If you have a bare RFM69W that you want to connect to an Arduino, you +/// might use these connections: CAUTION: you must use a 3.3V type +/// Arduino, otherwise you will also need voltage level shifters between the +/// Arduino and the RFM69. CAUTION, you must also ensure you connect an +/// antenna +/// +/// \code +/// Arduino RFM69W +/// GND----------GND (ground in) +/// 3V3----------3.3V (3.3V in) +/// interrupt 0 pin D2-----------DIO0 (interrupt request out) +/// SS pin D10----------NSS (chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------MOSI (SPI Data in) +/// MISO pin D12----------MISO (SPI Data out) +/// \endcode +/// +/// For Arduino Due, use these connections: +/// \code +/// Arduino RFM69W +/// GND----------GND (ground in) +/// 3V3----------3.3V (3.3V in) +/// interrupt 0 pin D2-----------DIO0 (interrupt request out) +/// SS pin D10----------NSS (chip select in) +/// SCK SPI pin 3----------SCK (SPI clock in) +/// MOSI SPI pin 4----------MOSI (SPI Data in) +/// MISO SPI pin 1----------MISO (SPI Data out) +/// \endcode +/// +/// With these connections, you can then use the default constructor RH_RF69(). +/// You can override the default settings for the SS pin and the interrupt in +/// the RH_RF69 constructor if you wish to connect the slave select SS to other +/// than the normal one for your Arduino (D10 for Diecimila, Uno etc and D53 +/// for Mega) or the interrupt request to other than pin D2 (Caution, +/// different processors have different constraints as to the pins available +/// for interrupts). +/// +/// If you have a Teensy 3.1 and a compatible RFM69 breakout board, you will need to +/// construct the RH_RF69 instance like this: +/// \code +/// RH_RF69 driver(15, 16); +/// \endcode +/// +/// If you have a MoteinoMEGA https://lowpowerlab.com/shop/moteinomega +/// with RFM69 on board, you dont need to make any wiring connections +/// (the RFM69 module is soldered onto the MotienoMEGA), but you must initialise the RH_RF69 +/// constructor like this: +/// \code +/// RH_RF69 driver(4, 2); +/// \endcode +/// Make sure you have the MoteinoMEGA core installed in your Arduino hardware folder as described in the +/// documentation for the MoteinoMEGA. +/// +/// If you have an Arduino M0 Pro from arduino.org, +/// you should note that you cannot use Pin 2 for the interrupt line +/// (Pin 2 is for the NMI only). The same comments apply to Pin 4 on Arduino Zero from arduino.cc. +/// Instead you can use any other pin (we use Pin 3) and initialise RH_RF69 like this: +/// \code +/// // Slave Select is pin 10, interrupt is Pin 3 +/// RH_RF69 driver(10, 3); +/// \endcode +/// +/// If you have a Rocket Scream Mini Ultra Pro with the RFM69HCW +/// - Ensure you have Arduino SAMD board support 1.6.5 or later in Arduino IDE 1.6.8 or later. +/// - The radio SS is hardwired to pin D5 and the DIO0 interrupt to pin D2, +/// so you need to initialise the radio like this: +/// \code +/// RH_RF69 driver(5, 2); +/// \endcode +/// - The name of the serial port on that board is 'SerialUSB', not 'Serial', so this may be helpful at the top of our +/// sample sketches: +/// \code +/// #define Serial SerialUSB +/// \endcode +/// - You also need this in setup before radio initialisation +/// \code +/// // Ensure serial flash is not interfering with radio communication on SPI bus +/// pinMode(4, OUTPUT); +/// digitalWrite(4, HIGH); +/// \endcode +/// - and if you have a 915MHz part, you need this after driver/manager intitalisation: +/// \code +/// rf69.setFrequency(915.0); +/// rf69.setTxPower(20); +/// \endcode +/// which adds up to modifying sample sketches something like: +/// \code +/// #include +/// #include +/// RH_RF69 rf69(5, 2); // Rocket Scream Mini Ultra Pro with the RFM69HCW +/// #define Serial SerialUSB +/// +/// void setup() +/// { +/// // Ensure serial flash is not interfering with radio communication on SPI bus +/// pinMode(4, OUTPUT); +/// digitalWrite(4, HIGH); +/// +/// Serial.begin(9600); +/// while (!Serial) ; // Wait for serial port to be available +/// if (!rf69.init()) +/// Serial.println("init failed"); +/// rf69.setFrequency(915.0); +/// rf69.setTxPower(20); +/// } +/// ... +/// \endcode +/// +/// If you have a talk2 Whisper Node board with on-board RF69 radio, +/// the example rf69_* sketches work without modifications. Initialise the radio like +/// with the default constructor: +/// \code +/// RH_RF69 driver; +/// \endcode +/// +/// If you have a Feather 32u4 with RFM69HCW you need to initialise the driver like: +/// \code +/// RH_RF69 driver(8, 7); +/// \endcode +/// and since the radio is the high power HCW model, you must set the Tx power in the +/// range 14 to 20 like this: +/// \code +/// driver.setTxPower(14); +/// \endcode +/// +/// If you are connecting an RF69 to a ESP8266 board breakout board that exposes pins +/// 12, 13, 14, 15 (ie NOT an ESP-01) you can connect like this: +/// \code +/// ESP8266 RFM69W +/// GND-----------GND (ground in) +/// VIN-----------3.3V (3.3V in) +/// interrupt D0 pin GPIO0-----------DIO0 (interrupt request out) +/// SS pin GPIO15----------NSS (chip select in) +/// SCK SPI pin GPIO14----------SCK (SPI clock in) +/// MOSI SPI pin GPIO13----------MOSI (SPI Data in) +/// MISO SPI pin GPIO12----------MISO (SPI Data out) +/// \endcode +/// and initialise with +/// \code +/// RH_RF69 driver(15, 0); +/// \endcode +/// If you are connecting an RF69 to a Sparkfun nRF52832 Breakout board +/// with Arduino 1.8.9 with board: +/// "SparkFun nRF52 Boards by Sparkfun Electronics version 0.2.3", +/// you can connect like this: +/// \code +/// nRF52832 RFM69W +/// GND----------GND (ground in) +/// 3V3----------3.3V (3.3V in) +/// interrupt 0 pin 02-----------DIO0 (interrupt request out) +/// SS pin 08-----------NSS (chip select in) +/// SCK SPI pin 13-----------SCK (SPI clock in) +/// MOSI SPI pin 11-----------MOSI (SPI Data in) +/// MISO SPI pin 12-----------MISO (SPI Data out) +/// \endcode +/// and initialise with +/// \code +/// RHSoftwareSPI softwarespi; +/// RH_RF69 driver(8, 2, softwarespi); +/// and inside your setup() function: +/// softwarespi.setPins(12, 11, 13); +/// \endcode +/// +/// It is possible to have 2 or more radios connected to one Arduino, provided +/// each radio has its own SS and interrupt line (SCK, SDI and SDO are common +/// to all radios) +/// +/// Caution: on some Arduinos such as the Mega 2560, if you set the slave +/// select pin to be other than the usual SS pin (D53 on Mega 2560), you may +/// need to set the usual SS pin to be an output to force the Arduino into SPI +/// master mode. +/// +/// Caution: Power supply requirements of the RF69 module may be relevant in some circumstances: +/// RF69 modules are capable of pulling 45mA+ at full power, where Arduino's 3.3V line can +/// give 50mA. You may need to make provision for alternate power supply for +/// the RF69, especially if you wish to use full transmit power, and/or you have +/// other shields demanding power. Inadequate power for the RF69 is likely to cause symptoms such as: +/// -reset's/bootups terminate with "init failed" messages +/// -random termination of communication after 5-30 packets sent/received +/// -"fake ok" state, where initialization passes fluently, but communication doesn't happen +/// -shields hang Arduino boards, especially during the flashing +/// +/// \par Encryption +/// +/// This driver support the on-chip AES encryption provided by the RF69. +/// You can enable encryption by calling setEncryptionKey() after init() has been called. +/// If both transmitter and receiver have been configured with the same AES key, +/// then the receiver will recover the unencrypted message sent by the receiver. +/// However, you should note that there is no way for RF69 nor for the RadioHead +/// drivers to know whether the AES +/// key for a message is 'correct' or not. This is because the RF69 CRC covers the +/// _encrypted_ payload not the plaintext. +/// +/// In RadioHead managers that support addressing, +/// the RF69 AES encryption includes the RadioHead payload and the TO and FROM addresses, so +/// occasionally (average one in 256 messages), a message encrypted with the +/// 'wrong' key will have the 'correct' destination address, and will therefore be +/// accepted by RadioHead as a 'random' message content from a 'random' sender. +/// Its up to your code to figure out whether the message makes sense or not. +/// +/// \par Interrupts +/// +/// The RH_RF69 driver uses interrupts to react to events in the RF69 module, +/// such as the reception of a new packet, or the completion of transmission +/// of a packet. The RH_RF69 driver interrupt service routine reads status from +/// and writes data to the the RF69 module via the SPI interface. It is very +/// important therefore, that if you are using the RH_RF69 driver with another +/// SPI based deviced, that you disable interrupts while you transfer data to +/// and from that other device. Use cli() to disable interrupts and sei() to +/// reenable them. +/// +/// \par Memory +/// +/// The RH_RF69 driver requires non-trivial amounts of memory. The sample +/// programs above all compile to about 8kbytes each, which will fit in the +/// flash proram memory of most Arduinos. However, the RAM requirements are +/// more critical. Therefore, you should be vary sparing with RAM use in +/// programs that use the RH_RF69 driver. +/// +/// It is often hard to accurately identify when you are hitting RAM limits on Arduino. +/// The symptoms can include: +/// - Mysterious crashes and restarts +/// - Changes in behaviour when seemingly unrelated changes are made (such as adding print() statements) +/// - Hanging +/// - Output from Serial.print() not appearing +/// +/// \par Automatic Frequency Control (AFC) +/// +/// The RF69 module is configured by the RH_RF69 driver to always use AFC. +/// +/// \par Transmitter Power +/// +/// You can control the transmitter power on the RF69 transceiver +/// with the RH_RF69::setTxPower() function. The argument can be any of +/// -18 to +13 (for RF69W) or -14 to 20 (for RF69HW) +/// The default is 13. Eg: +/// \code +/// driver.setTxPower(-5); +/// \endcode +/// +/// We have made some actual power measurements against +/// programmed power for Moteino (with RF69W) +/// - Moteino (with RF69W), USB power +/// - 10cm RG58C/U soldered direct to RFM69 module ANT and GND +/// - bnc connecteor +/// - 12dB attenuator +/// - BNC-SMA adapter +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// Program power Measured Power +/// dBm dBm +/// -18 -17 +/// -16 -16 +/// -14 -14 +/// -12 -12 +/// -10 -9 +/// -8 -7 +/// -6 -4 +/// -4 -3 +/// -2 -2 +/// 0 0.2 +/// 2 3 +/// 4 5 +/// 6 7 +/// 8 10 +/// 10 13 +/// 12 14 +/// 13 15 +/// 14 -51 +/// 20 -51 +/// \endcode +/// We have also made some actual power measurements against +/// programmed power for Anarduino MiniWireless with RFM69-HW +/// Anarduino MiniWireless (with RFM69-HW), USB power +/// - 10cm RG58C/U soldered direct to RFM69 module ANT and GND +/// - bnc connecteor +/// - 2x12dB attenuators +/// - BNC-SMA adapter +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// Program power Measured Power +/// dBm dBm +/// -18 no measurable output +/// 0 no measurable output +/// 13 no measurable output +/// 14 11 +/// 15 12 +/// 16 12.4 +/// 17 14 +/// 18 15 +/// 19 15.8 +/// 20 17 +/// \endcode +/// (Caution: we dont claim laboratory accuracy for these measurements) +/// You would not expect to get anywhere near these powers to air with a simple 1/4 wavelength wire antenna. +/// Caution: although the RFM69 appears to have a PC antenna on board, you will get much better power and range even +/// with just a 1/4 wave wire antenna. +/// +/// \par Performance +/// +/// Some simple speed performance tests have been conducted. +/// In general packet transmission rate will be limited by the modulation scheme. +/// Also, if your code does any slow operations like Serial printing it will also limit performance. +/// We disabled any printing in the tests below. +/// We tested with RH_RF69::GFSK_Rb250Fd250, which is probably the fastest scheme available. +/// We tested with a 13 octet message length, over a very short distance of 10cm. +/// +/// Transmission (no reply) tests with modulation RH_RF69::GFSK_Rb250Fd250 and a +/// 13 octet message show about 152 messages per second transmitted and received. +/// +/// Transmit-and-wait-for-a-reply tests with modulation RH_RF69::GFSK_Rb250Fd250 and a +/// 13 octet message (send and receive) show about 68 round trips per second. +/// +class RH_RF69 : public RHSPIDriver +{ +public: + + /// \brief Defines register values for a set of modem configuration registers + /// + /// Defines register values for a set of modem configuration registers + /// that can be passed to setModemRegisters() if none of the choices in + /// ModemConfigChoice suit your need setModemRegisters() writes the + /// register values from this structure to the appropriate RF69 registers + /// to set the desired modulation type, data rate and deviation/bandwidth. + typedef struct + { + uint8_t reg_02; ///< Value for register RH_RF69_REG_02_DATAMODUL + uint8_t reg_03; ///< Value for register RH_RF69_REG_03_BITRATEMSB + uint8_t reg_04; ///< Value for register RH_RF69_REG_04_BITRATELSB + uint8_t reg_05; ///< Value for register RH_RF69_REG_05_FDEVMSB + uint8_t reg_06; ///< Value for register RH_RF69_REG_06_FDEVLSB + uint8_t reg_19; ///< Value for register RH_RF69_REG_19_RXBW + uint8_t reg_1a; ///< Value for register RH_RF69_REG_1A_AFCBW + uint8_t reg_37; ///< Value for register RH_RF69_REG_37_PACKETCONFIG1 + } ModemConfig; + + /// Choices for setModemConfig() for a selected subset of common + /// modulation types, and data rates. If you need another configuration, + /// use the register calculator. and call setModemRegisters() with your + /// desired settings. + /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic + /// definitions and not their integer equivalents: its possible that new values will be + /// introduced in later versions (though we will try to avoid it). + /// CAUTION: some of these configurations do not work corectly and are marked as such. + typedef enum + { + FSK_Rb2Fd5 = 0, ///< FSK, Whitening, Rb = 2kbs, Fd = 5kHz + FSK_Rb2_4Fd4_8, ///< FSK, Whitening, Rb = 2.4kbs, Fd = 4.8kHz + FSK_Rb4_8Fd9_6, ///< FSK, Whitening, Rb = 4.8kbs, Fd = 9.6kHz + FSK_Rb9_6Fd19_2, ///< FSK, Whitening, Rb = 9.6kbs, Fd = 19.2kHz + FSK_Rb19_2Fd38_4, ///< FSK, Whitening, Rb = 19.2kbs, Fd = 38.4kHz + FSK_Rb38_4Fd76_8, ///< FSK, Whitening, Rb = 38.4kbs, Fd = 76.8kHz + FSK_Rb57_6Fd120, ///< FSK, Whitening, Rb = 57.6kbs, Fd = 120kHz + FSK_Rb125Fd125, ///< FSK, Whitening, Rb = 125kbs, Fd = 125kHz + FSK_Rb250Fd250, ///< FSK, Whitening, Rb = 250kbs, Fd = 250kHz + FSK_Rb55555Fd50, ///< FSK, Whitening, Rb = 55555kbs,Fd = 50kHz for RFM69 lib compatibility + + GFSK_Rb2Fd5, ///< GFSK, Whitening, Rb = 2kbs, Fd = 5kHz + GFSK_Rb2_4Fd4_8, ///< GFSK, Whitening, Rb = 2.4kbs, Fd = 4.8kHz + GFSK_Rb4_8Fd9_6, ///< GFSK, Whitening, Rb = 4.8kbs, Fd = 9.6kHz + GFSK_Rb9_6Fd19_2, ///< GFSK, Whitening, Rb = 9.6kbs, Fd = 19.2kHz + GFSK_Rb19_2Fd38_4, ///< GFSK, Whitening, Rb = 19.2kbs, Fd = 38.4kHz + GFSK_Rb38_4Fd76_8, ///< GFSK, Whitening, Rb = 38.4kbs, Fd = 76.8kHz + GFSK_Rb57_6Fd120, ///< GFSK, Whitening, Rb = 57.6kbs, Fd = 120kHz + GFSK_Rb125Fd125, ///< GFSK, Whitening, Rb = 125kbs, Fd = 125kHz + GFSK_Rb250Fd250, ///< GFSK, Whitening, Rb = 250kbs, Fd = 250kHz + GFSK_Rb55555Fd50, ///< GFSK, Whitening, Rb = 55555kbs,Fd = 50kHz + + OOK_Rb1Bw1, ///< OOK, Whitening, Rb = 1kbs, Rx Bandwidth = 1kHz. + OOK_Rb1_2Bw75, ///< OOK, Whitening, Rb = 1.2kbs, Rx Bandwidth = 75kHz. + OOK_Rb2_4Bw4_8, ///< OOK, Whitening, Rb = 2.4kbs, Rx Bandwidth = 4.8kHz. + OOK_Rb4_8Bw9_6, ///< OOK, Whitening, Rb = 4.8kbs, Rx Bandwidth = 9.6kHz. + OOK_Rb9_6Bw19_2, ///< OOK, Whitening, Rb = 9.6kbs, Rx Bandwidth = 19.2kHz. + OOK_Rb19_2Bw38_4, ///< OOK, Whitening, Rb = 19.2kbs, Rx Bandwidth = 38.4kHz. + OOK_Rb32Bw64, ///< OOK, Whitening, Rb = 32kbs, Rx Bandwidth = 64kHz. + +// Test, + } ModemConfigChoice; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// interrupt and slave select pin. After constructing, you must call init() to initialise the interface + /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient + /// distinct interrupt lines, one for each instance. + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RF69 before + /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) + /// \param[in] interruptPin The interrupt Pin number that is connected to the RF69 DIO0 interrupt line. + /// Defaults to pin 2. + /// Caution: You must specify an interrupt capable pin. + /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. + /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. + /// On Arduino Zero from arduino.cc, any digital pin other than 4. + /// On Arduino M0 Pro from arduino.org, any digital pin other than 2. + /// On other Arduinos pins 2 or 3. + /// See http://arduino.cc/en/Reference/attachInterrupt for more details. + /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. + /// On other boards, any digital pin may be used. + /// \param[in] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + RH_RF69(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, RHGenericSPI& spi = hardware_spi); + + /// Initialises this instance and the radio module connected to it. + /// The following steps are taken: + /// - Initialise the slave select pin and the SPI interface library + /// - Checks the connected RF69 module can be communicated + /// - Attaches an interrupt handler + /// - Configures the RF69 module + /// - Sets the frequency to 434.0 MHz + /// - Sets the modem data rate to FSK_Rb2Fd5 + /// \return true if everything was successful + bool init(); + + /// Reads the on-chip temperature sensor. + /// The RF69 must be in Idle mode (= RF69 Standby) to measure temperature. + /// The measurement is uncalibrated and without calibration, you can expect it to be far from + /// correct. + /// \return The measured temperature, in degrees C from -40 to 85 (uncalibrated) + int8_t temperatureRead(); + + /// Sets the transmitter and receiver + /// centre frequency + /// \param[in] centre Frequency in MHz. 240.0 to 960.0. Caution, RF69 comes in several + /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work + /// \param[in] afcPullInRange Not used + /// \return true if the selected frquency centre is within range + bool setFrequency(float centre, float afcPullInRange = 0.05); + + /// Reads and returns the current RSSI value. + /// Causes the current signal strength to be measured and returned + /// If you want to find the RSSI + /// of the last received message, use lastRssi() instead. + /// \return The current RSSI value on units of 0.5dB. + int8_t rssiRead(); + + /// Sets the parameters for the RF69 OPMODE. + /// This is a low level device access function, and should not normally ned to be used by user code. + /// Instead can use stModeRx(), setModeTx(), setModeIdle() + /// \param[in] mode RF69 OPMODE to set, one of RH_RF69_OPMODE_MODE_*. + void setOpMode(uint8_t mode); + + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + void setModeIdle(); + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver in the RF69. + void setModeRx(); + + /// If current mode is Rx or Idle, changes it to Rx. F + /// Starts the transmitter in the RF69. + void setModeTx(); + + /// Sets the transmitter power output level. + /// Be a good neighbour and set the lowest power level you need. + /// Caution: legal power limits may apply in certain countries. + /// After init(), the power will be set to 13dBm for a low power module. + /// If you are using a high p[ower modfule such as an RFM69HW, you MUST set the power level + /// with the ishighpowermodule flag set to true. Else you wil get no measurable power output. + /// Simlarly if you are not using a high power module, you must NOT set the ishighpowermodule + /// (which is the default) + /// \param[in] power Transmitter power level in dBm. For RF69W (ishighpowermodule = false), + /// valid values are from -18 to +13.; Values outside this range are trimmed. + /// For RF69HW (ishighpowermodule = true), valid values are from -2 to +20. + /// Caution: at +20dBm, duty cycle is limited to 1% and a + /// maximum VSWR of 3:1 at the antenna port. + /// \param ishighpowermodule Set to true if the connected module is a high power module RFM69HW + void setTxPower(int8_t power, bool ishighpowermodule = RH_RF69_DEFAULT_HIGHPOWER); + + /// Sets all the registers required to configure the data modem in the RF69, including the data rate, + /// bandwidths etc. You can use this to configure the modem with custom configurations if none of the + /// canned configurations in ModemConfigChoice suit you. + /// \param[in] config A ModemConfig structure containing values for the modem configuration registers. + void setModemRegisters(const ModemConfig* config); + + /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// here, use setModemRegisters() with your own ModemConfig. The default after init() is RH_RF69::GFSK_Rb250Fd250. + /// \param[in] index The configuration choice. + /// \return true if index is a valid choice. + bool setModemConfig(ModemConfigChoice index); + + /// Starts the receiver and checks whether a received message is available. + /// This can be called multiple times in a timeout loop + /// \return true if a complete, valid message has been received and is able to be retrieved by + /// recv() + bool available(); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is NOT permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send (> 0) + /// \return true if the message length was valid and it was correctly queued for transmit + bool send(const uint8_t* data, uint8_t len); + + /// Sets the length of the preamble + /// in bytes. + /// Caution: this should be set to the same + /// value on all nodes in your network. Default is 4. + /// Sets the message preamble length in REG_0?_PREAMBLE?SB + /// \param[in] bytes Preamble length in bytes. + void setPreambleLength(uint16_t bytes); + + /// Sets the sync words for transmit and receive + /// Caution: SyncWords should be set to the same + /// value on all nodes in your network. Nodes with different SyncWords set will never receive + /// each others messages, so different SyncWords can be used to isolate different + /// networks from each other. Default is { 0x2d, 0xd4 }. + /// Caution: tests here show that with a single sync word (ie where len == 1), + /// RFM69 reception can be unreliable. + /// To disable sync word generation and detection, call with the defaults: setSyncWords(); + /// \param[in] syncWords Array of sync words, 1 to 4 octets long. NULL if no sync words to be used. + /// \param[in] len Number of sync words to set, 1 to 4. 0 if no sync words to be used. + void setSyncWords(const uint8_t* syncWords = NULL, uint8_t len = 0); + + /// Enables AES encryption and sets the AES encryption key, used + /// to encrypt and decrypt all messages. The default is disabled. + /// \param[in] key The key to use. Must be 16 bytes long. The same key must be installed + /// in other instances of RF69, otherwise communications will not work correctly. If key is NULL, + /// encryption is disabled, which is the default. + void setEncryptionKey(uint8_t* key = NULL); + + /// Returns the time in millis since the most recent preamble was received, and when the most recent + /// RSSI measurement was made. + uint32_t getLastPreambleTime(); + + /// The maximum message length supported by this driver + /// \return The maximum message length supported by this driver + uint8_t maxMessageLength(); + + /// Prints the value of a single register + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging/testing only + /// \return true if successful + bool printRegister(uint8_t reg); + + /// Prints the value of all the RF69 registers + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging/testing only + /// \return true if successful + bool printRegisters(); + + /// Sets the radio operating mode for the case when the driver is idle (ie not + /// transmitting or receiving), allowing you to control the idle mode power requirements + /// at the expense of slower transitions to transmit and receive modes. + /// By default, the idle mode is RH_RF69_OPMODE_MODE_STDBY, + /// but eg setIdleMode(RH_RF69_OPMODE_MODE_SLEEP) will provide a much lower + /// idle current but slower transitions. Call this function after init(). + /// \param[in] idleMode The chip operating mode to use when the driver is idle. One of RH_RF69_OPMODE_* + void setIdleMode(uint8_t idleMode); + + /// Sets the radio into low-power sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. + /// \return true if sleep mode was successfully entered. + virtual bool sleep(); + + /// Return the integer value of the device type + /// as read from the device in from RH_RF69_REG_10_VERSION. + /// Expect 0x24, depending on the type of device actually + /// connected. + /// \return The integer device type + uint16_t deviceType() {return _deviceType;}; + +protected: + /// This is a low level function to handle the interrupts for one instance of RF69. + /// Called automatically by isr*() + /// Should not need to be called by user code. + void handleInterrupt(); + + /// Low level function to read the FIFO and put the received data into the receive buffer + /// Should not need to be called by user code. + void readFifo(); + +protected: + /// Low level interrupt service routine for RF69 connected to interrupt 0 + static void isr0(); + + /// Low level interrupt service routine for RF69 connected to interrupt 1 + static void isr1(); + + /// Low level interrupt service routine for RF69 connected to interrupt 1 + static void isr2(); + + /// Array of instances connected to interrupts 0 and 1 + static RH_RF69* _deviceForInterrupt[]; + + /// Index of next interrupt number to use in _deviceForInterrupt + static uint8_t _interruptCount; + + /// The configured interrupt pin connected to this instance + uint8_t _interruptPin; + + /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) + /// else 0xff + uint8_t _myInterruptIndex; + + /// The radio OP mode to use when mode is RHModeIdle + uint8_t _idleMode; + + /// The reported device type + uint8_t _deviceType; + + /// The selected output power in dBm + int8_t _power; + + /// The message length in _buf + volatile uint8_t _bufLen; + + /// Array of octets of teh last received message or the next to transmit message + uint8_t _buf[RH_RF69_MAX_MESSAGE_LEN]; + + /// True when there is a valid message in the Rx buffer + volatile bool _rxBufValid; + + /// Time in millis since the last preamble was received (and the last time the RSSI was measured) + uint32_t _lastPreambleTime; +}; + +/// @example rf69_client.ino +/// @example rf69_server.ino +/// @example rf69_reliable_datagram_client.ino +/// @example rf69_reliable_datagram_server.ino + + +#endif diff --git a/RH_RF95.cpp b/RH_RF95.cpp new file mode 100644 index 0000000..b1bc7bc --- /dev/null +++ b/RH_RF95.cpp @@ -0,0 +1,718 @@ +// RH_RF95.cpp +// +// Copyright (C) 2011 Mike McCauley +// $Id: RH_RF95.cpp,v 1.27 2020/07/05 08:52:21 mikem Exp $ + +#include + +// Maybe a mutex for multithreading on Raspberry Pi? +#ifdef RH_USE_MUTEX +RH_DECLARE_MUTEX(lock); +#endif + +// Interrupt vectors for the 3 Arduino interrupt pins +// Each interrupt can be handled by a different instance of RH_RF95, allowing you to have +// 2 or more LORAs per Arduino +RH_RF95* RH_RF95::_deviceForInterrupt[RH_RF95_NUM_INTERRUPTS] = {0, 0, 0}; + +// These are indexed by the values of ModemConfigChoice +// Stored in flash (program) memory to save SRAM +PROGMEM static const RH_RF95::ModemConfig MODEM_CONFIG_TABLE[] = +{ + // 1d, 1e, 26 + { 0x72, 0x74, 0x04}, // Bw125Cr45Sf128 (the chip default), AGC enabled + { 0x92, 0x74, 0x04}, // Bw500Cr45Sf128, AGC enabled + { 0x48, 0x94, 0x04}, // Bw31_25Cr48Sf512, AGC enabled + { 0x78, 0xc4, 0x0c}, // Bw125Cr48Sf4096, AGC enabled + { 0x72, 0xb4, 0x04}, // Bw125Cr45Sf2048, AGC enabled + +}; + +RH_RF95::RH_RF95(uint8_t slaveSelectPin, uint8_t interruptPin, RHGenericSPI& spi) + : + RHSPIDriver(slaveSelectPin, spi), + _rxBufValid(0) +{ + _interruptPin = interruptPin; + _myInterruptIndex = 0xff; // Not allocated yet + _enableCRC = true; + _useRFO = false; +} + +bool RH_RF95::init() +{ + if (!RHSPIDriver::init()) + return false; + +#ifdef RH_USE_MUTEX + if (RH_MUTEX_INIT(lock) != 0) + { + Serial.println("\n mutex init has failed\n"); + return false; + } +#endif + + if (!setupInterruptHandler()) + return false; + + // No way to check the device type :-( + + // Set sleep mode, so we can also set LORA mode: + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE); + delay(10); // Wait for sleep mode to take over from say, CAD + // Check we are in sleep mode, with LORA set + if (spiRead(RH_RF95_REG_01_OP_MODE) != (RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE)) + { +// Serial.println(spiRead(RH_RF95_REG_01_OP_MODE), HEX); + return false; // No device present? + } + + + // Set up FIFO + // We configure so that we can use the entire 256 byte FIFO for either receive + // or transmit, but not both at the same time + spiWrite(RH_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0); + spiWrite(RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0); + + // Packet format is preamble + explicit-header + payload + crc + // Explicit Header Mode + // payload is TO + FROM + ID + FLAGS + message data + // RX mode is implmented with RXCONTINUOUS + // max message data length is 255 - 4 = 251 octets + + setModeIdle(); + + // Set up default configuration + // No Sync Words in LORA mode. ACTUALLY thats not correct, and for tehRF95, the default LoRaSync Word is 0x12 + // (ie a private network) and it can be changed at RH_RF95_REG_39_SYNC_WORD + setModemConfig(Bw125Cr45Sf128); // Radio default +// setModemConfig(Bw125Cr48Sf4096); // slow and reliable? + setPreambleLength(8); // Default is 8 + // An innocuous ISM frequency, same as RF22's + setFrequency(434.0); + // Lowish power + setTxPower(13); + + return true; +} + +bool RH_RF95::setupInterruptHandler() +{ + // For some subclasses (eg RH_ABZ) we dont want to set up interrupt + int interruptNumber = NOT_AN_INTERRUPT; + if (_interruptPin != RH_INVALID_PIN) + { + // Determine the interrupt number that corresponds to the interruptPin + interruptNumber = digitalPinToInterrupt(_interruptPin); + if (interruptNumber == NOT_AN_INTERRUPT) + return false; +#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER + interruptNumber = _interruptPin; +#endif + + // Tell the low level SPI interface we will use SPI within this interrupt + spiUsingInterrupt(interruptNumber); + } + + if (_interruptPin != RH_INVALID_PIN) + { + // Add by Adrien van den Bossche for Teensy + // ARM M4 requires the below. else pin interrupt doesn't work properly. + // On all other platforms, its innocuous, belt and braces + pinMode(_interruptPin, INPUT); + + // Set up interrupt handler + // Since there are a limited number of interrupt glue functions isr*() available, + // we can only support a limited number of devices simultaneously + // ON some devices, notably most Arduinos, the interrupt pin passed in is actually the + // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping + // yourself based on knwledge of what Arduino board you are running on. + if (_myInterruptIndex == 0xff) + { + static uint8_t interruptCount = 0; // Index into _deviceForInterrupt for next device + // First run, no interrupt allocated yet + if (interruptCount <= RH_RF95_NUM_INTERRUPTS) + _myInterruptIndex = interruptCount++; + else + return false; // Too many devices, not enough interrupt vectors + } + _deviceForInterrupt[_myInterruptIndex] = this; + + if (_myInterruptIndex == 0) + attachInterrupt(interruptNumber, isr0, RISING); + else if (_myInterruptIndex == 1) + attachInterrupt(interruptNumber, isr1, RISING); + else if (_myInterruptIndex == 2) + attachInterrupt(interruptNumber, isr2, RISING); + else + return false; // Too many devices, not enough interrupt vectors + } + return true; +} + +// C++ level interrupt handler for this instance +// LORA is unusual in that it has several interrupt lines, and not a single, combined one. +// On MiniWirelessLoRa, only one of the several interrupt lines (DI0) from the RFM95 is usefuly +// connnected to the processor. +// We use this to get RxDone and TxDone interrupts +void RH_RF95::handleInterrupt() +{ + RH_MUTEX_LOCK(lock); // Multithreading support + + // we need the RF95 IRQ to be level triggered, or we ……have slim chance of missing events + // https://github.com/geeksville/Meshtastic-esp32/commit/78470ed3f59f5c84fbd1325bcff1fd95b2b20183 + + // Read the interrupt register + uint8_t irq_flags = spiRead(RH_RF95_REG_12_IRQ_FLAGS); + // Read the RegHopChannel register to check if CRC presence is signalled + // in the header. If not it might be a stray (noise) packet.* + uint8_t hop_channel = spiRead(RH_RF95_REG_1C_HOP_CHANNEL); +// Serial.println(irq_flags, HEX); +// Serial.println(_mode, HEX); +// Serial.println(hop_channel, HEX); +// Serial.println(_enableCRC, HEX); + + // ack all interrupts, + // Sigh: on some processors, for some unknown reason, doing this only once does not actually + // clear the radio's interrupt flag. So we do it twice. Why? (kevinh - I think the root cause we want level + // triggered interrupts here - not edge. Because edge allows us to miss handling secondard interrupts that occurred + // while this ISR was running. Better to instead, configure the interrupts as level triggered and clear pending + // at the _beginning_ of the ISR. If any interrupts occur while handling the ISR, the signal will remain asserted and + // our ISR will be reinvoked to handle that case) + // kevinh: turn this off until root cause is known, because it can cause missed interrupts! + // spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags + spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags + + // error if: + // timeout + // bad CRC + // CRC is required but it is not present + if (_mode == RHModeRx + && ( (irq_flags & (RH_RF95_RX_TIMEOUT | RH_RF95_PAYLOAD_CRC_ERROR)) + || (_enableCRC && !(hop_channel & RH_RF95_RX_PAYLOAD_CRC_IS_ON)) )) +// if (_mode == RHModeRx && irq_flags & (RH_RF95_RX_TIMEOUT | RH_RF95_PAYLOAD_CRC_ERROR)) + { +// Serial.println("E"); + _rxBad++; + clearRxBuf(); + } + // It is possible to get RX_DONE and CRC_ERROR and VALID_HEADER all at once + // so this must be an else + else if (_mode == RHModeRx && irq_flags & RH_RF95_RX_DONE) + { + // Packet received, no CRC error +// Serial.println("R"); + // Have received a packet + uint8_t len = spiRead(RH_RF95_REG_13_RX_NB_BYTES); + + // Reset the fifo read ptr to the beginning of the packet + spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, spiRead(RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR)); + spiBurstRead(RH_RF95_REG_00_FIFO, _buf, len); + _bufLen = len; + + // Remember the last signal to noise ratio, LORA mode + // Per page 111, SX1276/77/78/79 datasheet + _lastSNR = (int8_t)spiRead(RH_RF95_REG_19_PKT_SNR_VALUE) / 4; + + // Remember the RSSI of this packet, LORA mode + // this is according to the doc, but is it really correct? + // weakest receiveable signals are reported RSSI at about -66 + _lastRssi = spiRead(RH_RF95_REG_1A_PKT_RSSI_VALUE); + // Adjust the RSSI, datasheet page 87 + if (_lastSNR < 0) + _lastRssi = _lastRssi + _lastSNR; + else + _lastRssi = (int)_lastRssi * 16 / 15; + if (_usingHFport) + _lastRssi -= 157; + else + _lastRssi -= 164; + + // We have received a message. + validateRxBuf(); + if (_rxBufValid) + setModeIdle(); // Got one + } + else if (_mode == RHModeTx && irq_flags & RH_RF95_TX_DONE) + { +// Serial.println("T"); + _txGood++; + setModeIdle(); + } + else if (_mode == RHModeCad && irq_flags & RH_RF95_CAD_DONE) + { +// Serial.println("C"); + _cad = irq_flags & RH_RF95_CAD_DETECTED; + setModeIdle(); + } + else + { +// Serial.println("?"); + } + + // Sigh: on some processors, for some unknown reason, doing this only once does not actually + // clear the radio's interrupt flag. So we do it twice. Why? + spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags + spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags + RH_MUTEX_UNLOCK(lock); +} + +// These are low level functions that call the interrupt handler for the correct +// instance of RH_RF95. +// 3 interrupts allows us to have 3 different devices +void RH_INTERRUPT_ATTR RH_RF95::isr0() +{ + if (_deviceForInterrupt[0]) + _deviceForInterrupt[0]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_RF95::isr1() +{ + if (_deviceForInterrupt[1]) + _deviceForInterrupt[1]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_RF95::isr2() +{ + if (_deviceForInterrupt[2]) + _deviceForInterrupt[2]->handleInterrupt(); +} + +// Check whether the latest received message is complete and uncorrupted +void RH_RF95::validateRxBuf() +{ + if (_bufLen < 4) + return; // Too short to be a real message + // Extract the 4 headers + _rxHeaderTo = _buf[0]; + _rxHeaderFrom = _buf[1]; + _rxHeaderId = _buf[2]; + _rxHeaderFlags = _buf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +bool RH_RF95::available() +{ + RH_MUTEX_LOCK(lock); // Multithreading support + if (_mode == RHModeTx) + { + RH_MUTEX_UNLOCK(lock); + return false; + } + setModeRx(); + RH_MUTEX_UNLOCK(lock); + return _rxBufValid; // Will be set by the interrupt handler when a good message is received +} + +void RH_RF95::clearRxBuf() +{ + ATOMIC_BLOCK_START; + _rxBufValid = false; + _bufLen = 0; + ATOMIC_BLOCK_END; +} + +bool RH_RF95::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + RH_MUTEX_LOCK(lock); // Multithread support + if (buf && len) + { + ATOMIC_BLOCK_START; + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen-RH_RF95_HEADER_LEN) + *len = _bufLen-RH_RF95_HEADER_LEN; + memcpy(buf, _buf+RH_RF95_HEADER_LEN, *len); + ATOMIC_BLOCK_END; + } + clearRxBuf(); // This message accepted and cleared + RH_MUTEX_UNLOCK(lock); + return true; +} + +bool RH_RF95::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_RF95_MAX_MESSAGE_LEN) + return false; + + waitPacketSent(); // Make sure we dont interrupt an outgoing message + setModeIdle(); + + if (!waitCAD()) + return false; // Check channel activity + + // Position at the beginning of the FIFO + spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, 0); + // The headers + spiWrite(RH_RF95_REG_00_FIFO, _txHeaderTo); + spiWrite(RH_RF95_REG_00_FIFO, _txHeaderFrom); + spiWrite(RH_RF95_REG_00_FIFO, _txHeaderId); + spiWrite(RH_RF95_REG_00_FIFO, _txHeaderFlags); + // The message data + spiBurstWrite(RH_RF95_REG_00_FIFO, data, len); + spiWrite(RH_RF95_REG_22_PAYLOAD_LENGTH, len + RH_RF95_HEADER_LEN); + + RH_MUTEX_LOCK(lock); // Multithreading support + setModeTx(); // Start the transmitter + RH_MUTEX_UNLOCK(lock); + + // when Tx is done, interruptHandler will fire and radio mode will return to STANDBY + return true; +} + +bool RH_RF95::printRegisters() +{ +#ifdef RH_HAVE_SERIAL + uint8_t registers[] = { 0x01, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x014, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x4b}; + + uint8_t i; + for (i = 0; i < sizeof(registers); i++) + { + Serial.print(registers[i], HEX); + Serial.print(": "); + Serial.println(spiRead(registers[i]), HEX); + } +#endif + return true; +} + +uint8_t RH_RF95::maxMessageLength() +{ + return RH_RF95_MAX_MESSAGE_LEN; +} + +bool RH_RF95::setFrequency(float centre) +{ + // Frf = FRF / FSTEP + uint32_t frf = (centre * 1000000.0) / RH_RF95_FSTEP; + spiWrite(RH_RF95_REG_06_FRF_MSB, (frf >> 16) & 0xff); + spiWrite(RH_RF95_REG_07_FRF_MID, (frf >> 8) & 0xff); + spiWrite(RH_RF95_REG_08_FRF_LSB, frf & 0xff); + _usingHFport = (centre >= 779.0); + + return true; +} + +void RH_RF95::setModeIdle() +{ + if (_mode != RHModeIdle) + { + modeWillChange(RHModeIdle); + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_STDBY); + _mode = RHModeIdle; + } +} + +bool RH_RF95::sleep() +{ + if (_mode != RHModeSleep) + { + modeWillChange(RHModeSleep); + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP); + _mode = RHModeSleep; + } + return true; +} + +void RH_RF95::setModeRx() +{ + if (_mode != RHModeRx) + { + modeWillChange(RHModeRx); + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_RXCONTINUOUS); + spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x00); // Interrupt on RxDone + _mode = RHModeRx; + } +} + +void RH_RF95::setModeTx() +{ + if (_mode != RHModeTx) + { + modeWillChange(RHModeTx); + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_TX); + spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone + _mode = RHModeTx; + } +} + +void RH_RF95::setTxPower(int8_t power, bool useRFO) +{ + _useRFO = useRFO; + + // Sigh, different behaviours depending on whether the module use PA_BOOST or the RFO pin + // for the transmitter output + if (useRFO) + { + if (power > 15) + power = 15; + if (power < 0) + power = 0; + // Set the MaxPower register to 0x7 => MaxPower = 10.8 + 0.6 * 7 = 15dBm + // So Pout = Pmax - (15 - power) = 15 - 15 + power + spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_MAX_POWER | power); + spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_DISABLE); + } + else + { + if (power > 20) + power = 20; + if (power < 2) + power = 2; + + // For RH_RF95_PA_DAC_ENABLE, manual says '+20dBm on PA_BOOST when OutputPower=0xf' + // RH_RF95_PA_DAC_ENABLE actually adds about 3dBm to all power levels. We will use it + // for 8, 19 and 20dBm + if (power > 17) + { + spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_ENABLE); + power -= 3; + } + else + { + spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_DISABLE); + } + + // RFM95/96/97/98 does not have RFO pins connected to anything. Only PA_BOOST + // pin is connected, so must use PA_BOOST + // Pout = 2 + OutputPower (+3dBm if DAC enabled) + spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_PA_SELECT | (power-2)); + } +} + +// Sets registers from a canned modem configuration structure +void RH_RF95::setModemRegisters(const ModemConfig* config) +{ + spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, config->reg_1d); + spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, config->reg_1e); + spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, config->reg_26); +} + +// Set one of the canned FSK Modem configs +// Returns true if its a valid choice +bool RH_RF95::setModemConfig(ModemConfigChoice index) +{ + if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig))) + return false; + + ModemConfig cfg; + memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(RH_RF95::ModemConfig)); + setModemRegisters(&cfg); + + return true; +} + +void RH_RF95::setPreambleLength(uint16_t bytes) +{ + spiWrite(RH_RF95_REG_20_PREAMBLE_MSB, bytes >> 8); + spiWrite(RH_RF95_REG_21_PREAMBLE_LSB, bytes & 0xff); +} + +bool RH_RF95::isChannelActive() +{ + // Set mode RHModeCad + if (_mode != RHModeCad) + { + modeWillChange(RHModeCad); + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_CAD); + spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x80); // Interrupt on CadDone + _mode = RHModeCad; + } + + while (_mode == RHModeCad) + YIELD; + + return _cad; +} + +void RH_RF95::enableTCXO(bool on) +{ + if (on) + { + while ((spiRead(RH_RF95_REG_4B_TCXO) & RH_RF95_TCXO_TCXO_INPUT_ON) != RH_RF95_TCXO_TCXO_INPUT_ON) + { + sleep(); + spiWrite(RH_RF95_REG_4B_TCXO, (spiRead(RH_RF95_REG_4B_TCXO) | RH_RF95_TCXO_TCXO_INPUT_ON)); + } + } + else + { + while ((spiRead(RH_RF95_REG_4B_TCXO) & RH_RF95_TCXO_TCXO_INPUT_ON)) + { + sleep(); + spiWrite(RH_RF95_REG_4B_TCXO, (spiRead(RH_RF95_REG_4B_TCXO) & ~RH_RF95_TCXO_TCXO_INPUT_ON)); + } + } +} + +// From section 4.1.5 of SX1276/77/78/79 +// Ferror = FreqError * 2**24 * BW / Fxtal / 500 +int RH_RF95::frequencyError() +{ + int32_t freqerror = 0; + + // Convert 2.5 bytes (5 nibbles, 20 bits) to 32 bit signed int + // Caution: some C compilers make errors with eg: + // freqerror = spiRead(RH_RF95_REG_28_FEI_MSB) << 16 + // so we go more carefully. + freqerror = spiRead(RH_RF95_REG_28_FEI_MSB); + freqerror <<= 8; + freqerror |= spiRead(RH_RF95_REG_29_FEI_MID); + freqerror <<= 8; + freqerror |= spiRead(RH_RF95_REG_2A_FEI_LSB); + // Sign extension into top 3 nibbles + if (freqerror & 0x80000) + freqerror |= 0xfff00000; + + int error = 0; // In hertz + float bw_tab[] = {7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250, 500}; + uint8_t bwindex = spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) >> 4; + if (bwindex < (sizeof(bw_tab) / sizeof(float))) + error = (float)freqerror * bw_tab[bwindex] * ((float)(1L << 24) / (float)RH_RF95_FXOSC / 500.0); + // else not defined + + return error; +} + +int RH_RF95::lastSNR() +{ + return _lastSNR; +} + + /////////////////////////////////////////////////// + // + // additions below by Brian Norman 9th Nov 2018 + // brian.n.norman@gmail.com + // + // Routines intended to make changing BW, SF and CR + // a bit more intuitive + // + /////////////////////////////////////////////////// + + void RH_RF95::setSpreadingFactor(uint8_t sf) + { + if (sf <= 6) + sf = RH_RF95_SPREADING_FACTOR_64CPS; + else if (sf == 7) + sf = RH_RF95_SPREADING_FACTOR_128CPS; + else if (sf == 8) + sf = RH_RF95_SPREADING_FACTOR_256CPS; + else if (sf == 9) + sf = RH_RF95_SPREADING_FACTOR_512CPS; + else if (sf == 10) + sf = RH_RF95_SPREADING_FACTOR_1024CPS; + else if (sf == 11) + sf = RH_RF95_SPREADING_FACTOR_2048CPS; + else if (sf >= 12) + sf = RH_RF95_SPREADING_FACTOR_4096CPS; + + // set the new spreading factor + spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, (spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) & ~RH_RF95_SPREADING_FACTOR) | sf); + // check if Low data Rate bit should be set or cleared + setLowDatarate(); + } + +void RH_RF95::setSignalBandwidth(long sbw) +{ + uint8_t bw; //register bit pattern + + if (sbw <= 7800) + bw = RH_RF95_BW_7_8KHZ; + else if (sbw <= 10400) + bw = RH_RF95_BW_10_4KHZ; + else if (sbw <= 15600) + bw = RH_RF95_BW_15_6KHZ ; + else if (sbw <= 20800) + bw = RH_RF95_BW_20_8KHZ; + else if (sbw <= 31250) + bw = RH_RF95_BW_31_25KHZ; + else if (sbw <= 41700) + bw = RH_RF95_BW_41_7KHZ; + else if (sbw <= 62500) + bw = RH_RF95_BW_62_5KHZ; + else if (sbw <= 125000) + bw = RH_RF95_BW_125KHZ; + else if (sbw <= 250000) + bw = RH_RF95_BW_250KHZ; + else + bw = RH_RF95_BW_500KHZ; + + // top 4 bits of reg 1D control bandwidth + spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, (spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) & ~RH_RF95_BW) | bw); + // check if low data rate bit should be set or cleared + setLowDatarate(); +} + +void RH_RF95::setCodingRate4(uint8_t denominator) +{ + int cr = RH_RF95_CODING_RATE_4_5; + +// if (denominator <= 5) +// cr = RH_RF95_CODING_RATE_4_5; + if (denominator == 6) + cr = RH_RF95_CODING_RATE_4_6; + else if (denominator == 7) + cr = RH_RF95_CODING_RATE_4_7; + else if (denominator >= 8) + cr = RH_RF95_CODING_RATE_4_8; + + // CR is bits 3..1 of RH_RF95_REG_1D_MODEM_CONFIG1 + spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, (spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) & ~RH_RF95_CODING_RATE) | cr); +} + +void RH_RF95::setLowDatarate() +{ + // called after changing bandwidth and/or spreading factor + // Semtech modem design guide AN1200.13 says + // "To avoid issues surrounding drift of the crystal reference oscillator due to either temperature change + // or motion,the low data rate optimization bit is used. Specifically for 125 kHz bandwidth and SF = 11 and 12, + // this adds a small overhead to increase robustness to reference frequency variations over the timescale of the LoRa packet." + + // read current value for BW and SF + uint8_t BW = spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) >> 4; // bw is in bits 7..4 + uint8_t SF = spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) >> 4; // sf is in bits 7..4 + + // calculate symbol time (see Semtech AN1200.22 section 4) + float bw_tab[] = {7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000}; + + float bandwidth = bw_tab[BW]; + + float symbolTime = 1000.0 * pow(2, SF) / bandwidth; // ms + + // the symbolTime for SF 11 BW 125 is 16.384ms. + // and, according to this :- + // https://www.thethingsnetwork.org/forum/t/a-point-to-note-lora-low-data-rate-optimisation-flag/12007 + // the LDR bit should be set if the Symbol Time is > 16ms + // So the threshold used here is 16.0ms + + // the LDR is bit 3 of RH_RF95_REG_26_MODEM_CONFIG3 + uint8_t current = spiRead(RH_RF95_REG_26_MODEM_CONFIG3) & ~RH_RF95_LOW_DATA_RATE_OPTIMIZE; // mask off the LDR bit + if (symbolTime > 16.0) + spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, current | RH_RF95_LOW_DATA_RATE_OPTIMIZE); + else + spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, current); +} + +void RH_RF95::setPayloadCRC(bool on) +{ + // Payload CRC is bit 2 of register 1E + uint8_t current = spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) & ~RH_RF95_PAYLOAD_CRC_ON; // mask off the CRC + + if (on) + spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, current | RH_RF95_PAYLOAD_CRC_ON); + else + spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, current); + _enableCRC = on; +} + +uint8_t RH_RF95::getDeviceVersion() +{ + _deviceVersion = spiRead(RH_RF95_REG_42_VERSION); + return _deviceVersion; +} + diff --git a/RH_RF95.h b/RH_RF95.h new file mode 100644 index 0000000..25ef280 --- /dev/null +++ b/RH_RF95.h @@ -0,0 +1,933 @@ +// RH_RF95.h +// +// Definitions for HopeRF LoRa radios per: +// http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf +// http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RH_RF95.h,v 1.26 2020/06/15 23:39:39 mikem Exp $ +// + +#ifndef RH_RF95_h +#define RH_RF95_h + +#include + +// This is the maximum number of interrupts the driver can support +// Most Arduinos can handle 2, Megas can handle more +#define RH_RF95_NUM_INTERRUPTS 3 + +// Max number of octets the LORA Rx/Tx FIFO can hold +#define RH_RF95_FIFO_SIZE 255 + +// This is the maximum number of bytes that can be carried by the LORA. +// We use some for headers, keeping fewer for RadioHead messages +#define RH_RF95_MAX_PAYLOAD_LEN RH_RF95_FIFO_SIZE + +// The length of the headers we add. +// The headers are inside the LORA's payload +#define RH_RF95_HEADER_LEN 4 + +// This is the maximum message length that can be supported by this driver. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS +#ifndef RH_RF95_MAX_MESSAGE_LEN + #define RH_RF95_MAX_MESSAGE_LEN (RH_RF95_MAX_PAYLOAD_LEN - RH_RF95_HEADER_LEN) +#endif + +// The crystal oscillator frequency of the module +#define RH_RF95_FXOSC 32000000.0 + +// The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19 +#define RH_RF95_FSTEP (RH_RF95_FXOSC / 524288) + + +// Register names (LoRa Mode, from table 85) +#define RH_RF95_REG_00_FIFO 0x00 +#define RH_RF95_REG_01_OP_MODE 0x01 +#define RH_RF95_REG_02_RESERVED 0x02 +#define RH_RF95_REG_03_RESERVED 0x03 +#define RH_RF95_REG_04_RESERVED 0x04 +#define RH_RF95_REG_05_RESERVED 0x05 +#define RH_RF95_REG_06_FRF_MSB 0x06 +#define RH_RF95_REG_07_FRF_MID 0x07 +#define RH_RF95_REG_08_FRF_LSB 0x08 +#define RH_RF95_REG_09_PA_CONFIG 0x09 +#define RH_RF95_REG_0A_PA_RAMP 0x0a +#define RH_RF95_REG_0B_OCP 0x0b +#define RH_RF95_REG_0C_LNA 0x0c +#define RH_RF95_REG_0D_FIFO_ADDR_PTR 0x0d +#define RH_RF95_REG_0E_FIFO_TX_BASE_ADDR 0x0e +#define RH_RF95_REG_0F_FIFO_RX_BASE_ADDR 0x0f +#define RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR 0x10 +#define RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11 +#define RH_RF95_REG_12_IRQ_FLAGS 0x12 +#define RH_RF95_REG_13_RX_NB_BYTES 0x13 +#define RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB 0x14 +#define RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB 0x15 +#define RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB 0x16 +#define RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB 0x17 +#define RH_RF95_REG_18_MODEM_STAT 0x18 +#define RH_RF95_REG_19_PKT_SNR_VALUE 0x19 +#define RH_RF95_REG_1A_PKT_RSSI_VALUE 0x1a +#define RH_RF95_REG_1B_RSSI_VALUE 0x1b +#define RH_RF95_REG_1C_HOP_CHANNEL 0x1c +#define RH_RF95_REG_1D_MODEM_CONFIG1 0x1d +#define RH_RF95_REG_1E_MODEM_CONFIG2 0x1e +#define RH_RF95_REG_1F_SYMB_TIMEOUT_LSB 0x1f +#define RH_RF95_REG_20_PREAMBLE_MSB 0x20 +#define RH_RF95_REG_21_PREAMBLE_LSB 0x21 +#define RH_RF95_REG_22_PAYLOAD_LENGTH 0x22 +#define RH_RF95_REG_23_MAX_PAYLOAD_LENGTH 0x23 +#define RH_RF95_REG_24_HOP_PERIOD 0x24 +#define RH_RF95_REG_25_FIFO_RX_BYTE_ADDR 0x25 +#define RH_RF95_REG_26_MODEM_CONFIG3 0x26 + +#define RH_RF95_REG_27_PPM_CORRECTION 0x27 +#define RH_RF95_REG_28_FEI_MSB 0x28 +#define RH_RF95_REG_29_FEI_MID 0x29 +#define RH_RF95_REG_2A_FEI_LSB 0x2a +#define RH_RF95_REG_2C_RSSI_WIDEBAND 0x2c +#define RH_RF95_REG_31_DETECT_OPTIMIZE 0x31 +#define RH_RF95_REG_33_INVERT_IQ 0x33 +#define RH_RF95_REG_37_DETECTION_THRESHOLD 0x37 +#define RH_RF95_REG_39_SYNC_WORD 0x39 + +#define RH_RF95_REG_40_DIO_MAPPING1 0x40 +#define RH_RF95_REG_41_DIO_MAPPING2 0x41 +#define RH_RF95_REG_42_VERSION 0x42 + +#define RH_RF95_REG_4B_TCXO 0x4b +#define RH_RF95_REG_4D_PA_DAC 0x4d +#define RH_RF95_REG_5B_FORMER_TEMP 0x5b +#define RH_RF95_REG_61_AGC_REF 0x61 +#define RH_RF95_REG_62_AGC_THRESH1 0x62 +#define RH_RF95_REG_63_AGC_THRESH2 0x63 +#define RH_RF95_REG_64_AGC_THRESH3 0x64 + +// RH_RF95_REG_01_OP_MODE 0x01 +#define RH_RF95_LONG_RANGE_MODE 0x80 +#define RH_RF95_ACCESS_SHARED_REG 0x40 +#define RH_RF95_LOW_FREQUENCY_MODE 0x08 +#define RH_RF95_MODE 0x07 +#define RH_RF95_MODE_SLEEP 0x00 +#define RH_RF95_MODE_STDBY 0x01 +#define RH_RF95_MODE_FSTX 0x02 +#define RH_RF95_MODE_TX 0x03 +#define RH_RF95_MODE_FSRX 0x04 +#define RH_RF95_MODE_RXCONTINUOUS 0x05 +#define RH_RF95_MODE_RXSINGLE 0x06 +#define RH_RF95_MODE_CAD 0x07 + +// RH_RF95_REG_09_PA_CONFIG 0x09 +#define RH_RF95_PA_SELECT 0x80 +#define RH_RF95_MAX_POWER 0x70 +#define RH_RF95_OUTPUT_POWER 0x0f + +// RH_RF95_REG_0A_PA_RAMP 0x0a +#define RH_RF95_LOW_PN_TX_PLL_OFF 0x10 +#define RH_RF95_PA_RAMP 0x0f +#define RH_RF95_PA_RAMP_3_4MS 0x00 +#define RH_RF95_PA_RAMP_2MS 0x01 +#define RH_RF95_PA_RAMP_1MS 0x02 +#define RH_RF95_PA_RAMP_500US 0x03 +#define RH_RF95_PA_RAMP_250US 0x04 +#define RH_RF95_PA_RAMP_125US 0x05 +#define RH_RF95_PA_RAMP_100US 0x06 +#define RH_RF95_PA_RAMP_62US 0x07 +#define RH_RF95_PA_RAMP_50US 0x08 +#define RH_RF95_PA_RAMP_40US 0x09 +#define RH_RF95_PA_RAMP_31US 0x0a +#define RH_RF95_PA_RAMP_25US 0x0b +#define RH_RF95_PA_RAMP_20US 0x0c +#define RH_RF95_PA_RAMP_15US 0x0d +#define RH_RF95_PA_RAMP_12US 0x0e +#define RH_RF95_PA_RAMP_10US 0x0f + +// RH_RF95_REG_0B_OCP 0x0b +#define RH_RF95_OCP_ON 0x20 +#define RH_RF95_OCP_TRIM 0x1f + +// RH_RF95_REG_0C_LNA 0x0c +#define RH_RF95_LNA_GAIN 0xe0 +#define RH_RF95_LNA_GAIN_G1 0x20 +#define RH_RF95_LNA_GAIN_G2 0x40 +#define RH_RF95_LNA_GAIN_G3 0x60 +#define RH_RF95_LNA_GAIN_G4 0x80 +#define RH_RF95_LNA_GAIN_G5 0xa0 +#define RH_RF95_LNA_GAIN_G6 0xc0 +#define RH_RF95_LNA_BOOST_LF 0x18 +#define RH_RF95_LNA_BOOST_LF_DEFAULT 0x00 +#define RH_RF95_LNA_BOOST_HF 0x03 +#define RH_RF95_LNA_BOOST_HF_DEFAULT 0x00 +#define RH_RF95_LNA_BOOST_HF_150PC 0x03 + +// RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11 +#define RH_RF95_RX_TIMEOUT_MASK 0x80 +#define RH_RF95_RX_DONE_MASK 0x40 +#define RH_RF95_PAYLOAD_CRC_ERROR_MASK 0x20 +#define RH_RF95_VALID_HEADER_MASK 0x10 +#define RH_RF95_TX_DONE_MASK 0x08 +#define RH_RF95_CAD_DONE_MASK 0x04 +#define RH_RF95_FHSS_CHANGE_CHANNEL_MASK 0x02 +#define RH_RF95_CAD_DETECTED_MASK 0x01 + +// RH_RF95_REG_12_IRQ_FLAGS 0x12 +#define RH_RF95_RX_TIMEOUT 0x80 +#define RH_RF95_RX_DONE 0x40 +#define RH_RF95_PAYLOAD_CRC_ERROR 0x20 +#define RH_RF95_VALID_HEADER 0x10 +#define RH_RF95_TX_DONE 0x08 +#define RH_RF95_CAD_DONE 0x04 +#define RH_RF95_FHSS_CHANGE_CHANNEL 0x02 +#define RH_RF95_CAD_DETECTED 0x01 + +// RH_RF95_REG_18_MODEM_STAT 0x18 +#define RH_RF95_RX_CODING_RATE 0xe0 +#define RH_RF95_MODEM_STATUS_CLEAR 0x10 +#define RH_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08 +#define RH_RF95_MODEM_STATUS_RX_ONGOING 0x04 +#define RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02 +#define RH_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01 + +// RH_RF95_REG_1C_HOP_CHANNEL 0x1c +#define RH_RF95_PLL_TIMEOUT 0x80 +#define RH_RF95_RX_PAYLOAD_CRC_IS_ON 0x40 +#define RH_RF95_FHSS_PRESENT_CHANNEL 0x3f + +// RH_RF95_REG_1D_MODEM_CONFIG1 0x1d +#define RH_RF95_BW 0xf0 + +#define RH_RF95_BW_7_8KHZ 0x00 +#define RH_RF95_BW_10_4KHZ 0x10 +#define RH_RF95_BW_15_6KHZ 0x20 +#define RH_RF95_BW_20_8KHZ 0x30 +#define RH_RF95_BW_31_25KHZ 0x40 +#define RH_RF95_BW_41_7KHZ 0x50 +#define RH_RF95_BW_62_5KHZ 0x60 +#define RH_RF95_BW_125KHZ 0x70 +#define RH_RF95_BW_250KHZ 0x80 +#define RH_RF95_BW_500KHZ 0x90 +#define RH_RF95_CODING_RATE 0x0e +#define RH_RF95_CODING_RATE_4_5 0x02 +#define RH_RF95_CODING_RATE_4_6 0x04 +#define RH_RF95_CODING_RATE_4_7 0x06 +#define RH_RF95_CODING_RATE_4_8 0x08 +#define RH_RF95_IMPLICIT_HEADER_MODE_ON 0x01 + +// RH_RF95_REG_1E_MODEM_CONFIG2 0x1e +#define RH_RF95_SPREADING_FACTOR 0xf0 +#define RH_RF95_SPREADING_FACTOR_64CPS 0x60 +#define RH_RF95_SPREADING_FACTOR_128CPS 0x70 +#define RH_RF95_SPREADING_FACTOR_256CPS 0x80 +#define RH_RF95_SPREADING_FACTOR_512CPS 0x90 +#define RH_RF95_SPREADING_FACTOR_1024CPS 0xa0 +#define RH_RF95_SPREADING_FACTOR_2048CPS 0xb0 +#define RH_RF95_SPREADING_FACTOR_4096CPS 0xc0 +#define RH_RF95_TX_CONTINUOUS_MODE 0x08 + +#define RH_RF95_PAYLOAD_CRC_ON 0x04 +#define RH_RF95_SYM_TIMEOUT_MSB 0x03 + +// RH_RF95_REG_26_MODEM_CONFIG3 +#define RH_RF95_MOBILE_NODE 0x08 // HopeRF term +#define RH_RF95_LOW_DATA_RATE_OPTIMIZE 0x08 // Semtechs term +#define RH_RF95_AGC_AUTO_ON 0x04 + +// RH_RF95_REG_4B_TCXO 0x4b +#define RH_RF95_TCXO_TCXO_INPUT_ON 0x10 + +// RH_RF95_REG_4D_PA_DAC 0x4d +#define RH_RF95_PA_DAC_DISABLE 0x04 +#define RH_RF95_PA_DAC_ENABLE 0x07 + + +///////////////////////////////////////////////////////////////////// +/// \class RH_RF95 RH_RF95.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via a LoRa +/// capable radio transceiver. +/// +/// For an excellent discussion of LoRa range and modulations, see +/// https://medium.com/home-wireless/testing-lora-radios-with-the-limesdr-mini-part-2-37fa481217ff +/// +/// For Semtech SX1276/77/78/79 and HopeRF RF95/96/97/98 and other similar LoRa capable radios. +/// Based on http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf +/// and http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf +/// and http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf +/// and http://www.semtech.com/images/datasheet/sx1276.pdf +/// and http://www.semtech.com/images/datasheet/sx1276_77_78_79.pdf +/// FSK/GFSK/OOK modes are not (yet) supported. +/// +/// Works with +/// - the excellent MiniWirelessLoRa from Anarduino http://www.anarduino.com/miniwireless +/// - The excellent Modtronix inAir4 http://modtronix.com/inair4.html +/// and inAir9 modules http://modtronix.com/inair9.html. +/// - the excellent Rocket Scream Mini Ultra Pro with the RFM95W +/// http://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/ +/// - Lora1276 module from NiceRF http://www.nicerf.com/product_view.aspx?id=99 +/// - Adafruit Feather M0 with RFM95 +/// - The very fine Talk2 Whisper Node LoRa boards https://wisen.com.au/store/products/whisper-node-lora +/// an Arduino compatible board, which include an on-board RFM95/96 LoRa Radio (Semtech SX1276), external antenna, +/// run on 2xAAA batteries and support low power operations. RF95 examples work without modification. +/// Use Arduino Board Manager to install the Talk2 code support. Upload the code with an FTDI adapter set to 5V. +/// - heltec / TTGO ESP32 LoRa OLED https://www.aliexpress.com/item/Internet-Development-Board-SX1278-ESP32-WIFI-chip-0-96-inch-OLED-Bluetooth-WIFI-Lora-Kit-32/32824535649.html +/// +/// \par Overview +/// +/// This class provides basic functions for sending and receiving unaddressed, +/// unreliable datagrams of arbitrary length to 251 octets per packet. +/// +/// Manager classes may use this class to implement reliable, addressed datagrams and streams, +/// mesh routers, repeaters, translators etc. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// modulation scheme. +/// +/// This Driver provides an object-oriented interface for sending and receiving data messages with Hope-RF +/// RFM95/96/97/98(W), Semtech SX1276/77/78/79 and compatible radio modules in LoRa mode. +/// +/// The Hope-RF (http://www.hoperf.com) RFM95/96/97/98(W) and Semtech SX1276/77/78/79 is a low-cost ISM transceiver +/// chip. It supports FSK, GFSK, OOK over a wide range of frequencies and +/// programmable data rates, and it also supports the proprietary LoRA (Long Range) mode, which +/// is the only mode supported in this RadioHead driver. +/// +/// This Driver provides functions for sending and receiving messages of up +/// to 251 octets on any frequency supported by the radio, in a range of +/// predefined Bandwidths, Spreading Factors and Coding Rates. Frequency can be set with +/// 61Hz precision to any frequency from 240.0MHz to 960.0MHz. Caution: most modules only support a more limited +/// range of frequencies due to antenna tuning. +/// +/// Up to 2 modules can be connected to an Arduino (3 on a Mega), +/// permitting the construction of translators and frequency changers, etc. +/// +/// Support for other features such as transmitter power control etc is +/// also provided. +/// +/// Tested on MinWirelessLoRa with arduino-1.0.5 +/// on OpenSuSE 13.1. +/// Also tested with Teensy3.1, Modtronix inAir4 and Arduino 1.6.5 on OpenSuSE 13.1 +/// +/// \par Packet Format +/// +/// All messages sent and received by this RH_RF95 Driver conform to this packet format, which is compatible with RH_SX126x: +/// +/// - LoRa mode: +/// - 8 symbol PREAMBLE +/// - Explicit header with header CRC (default CCITT, handled internally by the radio) +/// - 4 octets HEADER: (TO, FROM, ID, FLAGS) +/// - 0 to 251 octets DATA +/// - CRC (default CCITT, handled internally by the radio) +/// +/// \par Connecting RFM95/96/97/98 and Semtech SX1276/77/78/79 to Arduino +/// +/// We tested with Anarduino MiniWirelessLoRA, which is an Arduino Duemilanove compatible with a RFM96W +/// module on-board. Therefore it needs no connections other than the USB +/// programming connection and an antenna to make it work. +/// +/// If you have a bare RFM95/96/97/98 that you want to connect to an Arduino, you +/// might use these connections (untested): CAUTION: you must use a 3.3V type +/// Arduino, otherwise you will also need voltage level shifters between the +/// Arduino and the RFM95. CAUTION, you must also ensure you connect an +/// antenna. +/// +/// \code +/// Arduino RFM95/96/97/98 +/// GND----------GND (ground in) +/// 3V3----------3.3V (3.3V in) +/// interrupt 0 pin D2-----------DIO0 (interrupt request out) +/// SS pin D10----------NSS (CS chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------MOSI (SPI Data in) +/// MISO pin D12----------MISO (SPI Data out) +/// \endcode +/// With these connections, you can then use the default constructor RH_RF95(). +/// You can override the default settings for the SS pin and the interrupt in +/// the RH_RF95 constructor if you wish to connect the slave select SS to other +/// than the normal one for your Arduino (D10 for Diecimila, Uno etc and D53 +/// for Mega) or the interrupt request to other than pin D2 (Caution, +/// different processors have different constraints as to the pins available +/// for interrupts). +/// +/// You can connect a Modtronix inAir4 or inAir9 directly to a 3.3V part such as a Teensy 3.1 like +/// this (tested). +/// \code +/// Teensy inAir4 inAir9 +/// GND----------0V (ground in) +/// 3V3----------3.3V (3.3V in) +/// interrupt 0 pin D2-----------D0 (interrupt request out) +/// SS pin D10----------CS (CS chip select in) +/// SCK pin D13----------CK (SPI clock in) +/// MOSI pin D11----------SI (SPI Data in) +/// MISO pin D12----------SO (SPI Data out) +/// \endcode +/// With these connections, you can then use the default constructor RH_RF95(). +/// you must also set the transmitter power with useRFO: +/// driver.setTxPower(13, true); +/// +/// Note that if you are using Modtronix inAir4 or inAir9,or any other module which uses the +/// transmitter RFO pins and not the PA_BOOST pins +/// that you must configure the power transmitter power for -1 to 14 dBm and with useRFO true. +/// Failure to do that will result in extremely low transmit powers. +/// +/// If you have an Arduino M0 Pro from arduino.org, +/// you should note that you cannot use Pin 2 for the interrupt line +/// (Pin 2 is for the NMI only). The same comments apply to Pin 4 on Arduino Zero from arduino.cc. +/// Instead you can use any other pin (we use Pin 3) and initialise RH_RF69 like this: +/// \code +/// // Slave Select is pin 10, interrupt is Pin 3 +/// RH_RF95 driver(10, 3); +/// \endcode +/// You can use the same constructor for Arduino Due, and this pinout diagram may be useful: +/// http://www.robgray.com/temp/Due-pinout-WEB.png +/// +/// If you have a Rocket Scream Mini Ultra Pro with the RFM95W: +/// - Ensure you have Arduino SAMD board support 1.6.5 or later in Arduino IDE 1.6.8 or later. +/// - The radio SS is hardwired to pin D5 and the DIO0 interrupt to pin D2, +/// so you need to initialise the radio like this: +/// \code +/// RH_RF95 driver(5, 2); +/// \endcode +/// - The name of the serial port on that board is 'SerialUSB', not 'Serial', so this may be helpful at the top of our +/// sample sketches: +/// \code +/// #define Serial SerialUSB +/// \endcode +/// - You also need this in setup before radio initialisation +/// \code +/// // Ensure serial flash is not interfering with radio communication on SPI bus +/// pinMode(4, OUTPUT); +/// digitalWrite(4, HIGH); +/// \endcode +/// - and if you have a 915MHz part, you need this after driver/manager intitalisation: +/// \code +/// rf95.setFrequency(915.0); +/// \endcode +/// which adds up to modifying sample sketches something like: +/// \code +/// #include +/// #include +/// RH_RF95 rf95(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W +/// #define Serial SerialUSB +/// +/// void setup() +/// { +/// // Ensure serial flash is not interfering with radio communication on SPI bus +/// pinMode(4, OUTPUT); +/// digitalWrite(4, HIGH); +/// +/// Serial.begin(9600); +/// while (!Serial) ; // Wait for serial port to be available +/// if (!rf95.init()) +/// Serial.println("init failed"); +/// rf95.setFrequency(915.0); +/// } +/// ... +/// \endcode +/// +/// For Adafruit Feather M0 with RFM95, construct the driver like this: +/// \code +/// RH_RF95 rf95(8, 3); +/// \endcode +/// +/// If you have a talk2 Whisper Node LoRa board with on-board RF95 radio, +/// the example rf95_* sketches work without modification. Initialise the radio like +/// with the default constructor: +/// \code +/// RH_RF95 driver; +/// \endcode +/// +/// It is possible to have 2 or more radios connected to one Arduino, provided +/// each radio has its own SS and interrupt line (SCK, SDI and SDO are common +/// to all radios) +/// +/// Caution: on some Arduinos such as the Mega 2560, if you set the slave +/// select pin to be other than the usual SS pin (D53 on Mega 2560), you may +/// need to set the usual SS pin to be an output to force the Arduino into SPI +/// master mode. +/// +/// Caution: Power supply requirements of the RFM module may be relevant in some circumstances: +/// RFM95/96/97/98 modules are capable of pulling 120mA+ at full power, where Arduino's 3.3V line can +/// give 50mA. You may need to make provision for alternate power supply for +/// the RFM module, especially if you wish to use full transmit power, and/or you have +/// other shields demanding power. Inadequate power for the RFM is likely to cause symptoms such as: +/// - reset's/bootups terminate with "init failed" messages +/// - random termination of communication after 5-30 packets sent/received +/// - "fake ok" state, where initialization passes fluently, but communication doesn't happen +/// - shields hang Arduino boards, especially during the flashing +/// +/// \par Interrupts +/// +/// The RH_RF95 driver uses interrupts to react to events in the RFM module, +/// such as the reception of a new packet, or the completion of transmission +/// of a packet. The driver configures the radio so the required interrupt is generated by the radio's DIO0 pin. +/// The RH_RF95 driver interrupt service routine reads status from +/// and writes data to the the RFM module via the SPI interface. It is very +/// important therefore, that if you are using the RH_RF95 driver with another +/// SPI based deviced, that you disable interrupts while you transfer data to +/// and from that other device. Use cli() to disable interrupts and sei() to +/// reenable them. +/// +/// \par Memory +/// +/// The RH_RF95 driver requires non-trivial amounts of memory. The sample +/// programs all compile to about 8kbytes each, which will fit in the +/// flash proram memory of most Arduinos. However, the RAM requirements are +/// more critical. Therefore, you should be vary sparing with RAM use in +/// programs that use the RH_RF95 driver. +/// +/// It is often hard to accurately identify when you are hitting RAM limits on Arduino. +/// The symptoms can include: +/// - Mysterious crashes and restarts +/// - Changes in behaviour when seemingly unrelated changes are made (such as adding print() statements) +/// - Hanging +/// - Output from Serial.print() not appearing +/// +/// \par Range +/// +/// We have made some simple range tests under the following conditions: +/// - rf95_client base station connected to a VHF discone antenna at 8m height above ground +/// - rf95_server mobile connected to 17.3cm 1/4 wavelength antenna at 1m height, no ground plane. +/// - Both configured for 13dBm, 434MHz, Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range +/// - Minimum reported RSSI seen for successful comms was about -91 +/// - Range over flat ground through heavy trees and vegetation approx 2km. +/// - At 20dBm (100mW) otherwise identical conditions approx 3km. +/// - At 20dBm, along salt water flat sandy beach, 3.2km. +/// +/// It should be noted that at this data rate, a 12 octet message takes 2 seconds to transmit. +/// +/// At 20dBm (100mW) with Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. +/// (Default medium range) in the conditions described above. +/// - Range over flat ground through heavy trees and vegetation approx 2km. +/// +/// Caution: the performance of this radio, especially with narrow bandwidths is strongly dependent on the +/// accuracy and stability of the chip clock. HopeRF and Semtech do not appear to +/// recommend bandwidths of less than 62.5 kHz +/// unless you have the optional Temperature Compensated Crystal Oscillator (TCXO) installed and +/// enabled on your radio module. See the refernece manual for more data. +/// Also https://lowpowerlab.com/forum/rf-range-antennas-rfm69-library/lora-library-experiences-range/15/ +/// and http://www.semtech.com/images/datasheet/an120014-xo-guidance-lora-modulation.pdf +/// +/// \par Transmitter Power +/// +/// You can control the transmitter power on the RF transceiver +/// with the RH_RF95::setTxPower() function. The argument can be any of +/// +2 to +20 (for modules that use PA_BOOST) +/// 0 to +15 (for modules that use RFO transmitter pin) +/// The default is 13. Eg: +/// \code +/// driver.setTxPower(10); // use PA_BOOST transmitter pin +/// driver.setTxPower(10, true); // use PA_RFO pin transmitter pin instead of PA_BOOST +/// \endcode +/// +/// We have made some actual power measurements against +/// programmed power for Anarduino MiniWirelessLoRa (which has RFM96W-433Mhz installed, and which includes an RF power +/// amp for addition 3dBm of power +/// - MiniWirelessLoRa RFM96W-433Mhz, USB power +/// - 30cm RG316 soldered direct to RFM96W module ANT and GND +/// - SMA connector +/// - 12db attenuator +/// - SMA connector +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// Program power Measured Power +/// dBm dBm +/// 2 5 +/// 4 7 +/// 6 8 +/// 8 11 +/// 10 13 +/// 12 15 +/// 14 16 +/// 16 18 +/// 17 20 +/// 18 21 +/// 19 22 +/// 20 23 +/// \endcode +/// +/// We have also measured the actual power output from a Modtronix inAir4 http://modtronix.com/inair4.html +/// connected to a Teensy 3.1: +/// Teensy 3.1 this is a 3.3V part, connected directly to: +/// Modtronix inAir4 with SMA antenna connector, connected as above: +/// 10cm SMA-SMA cable +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// Program power Measured Power +/// dBm dBm +/// 0 0 +/// 2 2 +/// 3 4 +/// 6 7 +/// 8 10 +/// 10 13 +/// 12 14.2 +/// 14 15 +/// 15 16 +/// \endcode +/// (Caution: we dont claim laboratory accuracy for these power measurements) +/// You would not expect to get anywhere near these powers to air with a simple 1/4 wavelength wire antenna. +class RH_RF95 : public RHSPIDriver +{ +public: + /// \brief Defines register values for a set of modem configuration registers + /// + /// Defines register values for a set of modem configuration registers + /// that can be passed to setModemRegisters() if none of the choices in + /// ModemConfigChoice suit your need setModemRegisters() writes the + /// register values from this structure to the appropriate registers + /// to set the desired spreading factor, coding rate and bandwidth + typedef struct + { + uint8_t reg_1d; ///< Value for register RH_RF95_REG_1D_MODEM_CONFIG1 + uint8_t reg_1e; ///< Value for register RH_RF95_REG_1E_MODEM_CONFIG2 + uint8_t reg_26; ///< Value for register RH_RF95_REG_26_MODEM_CONFIG3 + } ModemConfig; + + /// Choices for setModemConfig() for a selected subset of common + /// data rates. If you need another configuration, + /// determine the necessary settings and call setModemRegisters() with your + /// desired settings. It might be helpful to use the LoRa calculator mentioned in + /// http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf + /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic + /// definitions and not their integer equivalents: its possible that new values will be + /// introduced in later versions (though we will try to avoid it). + /// Caution: if you are using slow packet rates and long packets with RHReliableDatagram or subclasses + /// you may need to change the RHReliableDatagram timeout for reliable operations. + /// Caution: for some slow rates nad with ReliableDatagrams you may need to increase the reply timeout + /// with manager.setTimeout() to + /// deal with the long transmission times. + /// Caution: SX1276 family errata suggests alternate settings for some LoRa registers when 500kHz bandwidth + /// is in use. See the Semtech SX1276/77/78 Errata Note. These are not implemented by RH_RF95. + typedef enum + { + Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range + Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range + Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range + Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, low data rate, CRC on. Slow+long range + Bw125Cr45Sf2048, ///< Bw = 125 kHz, Cr = 4/5, Sf = 2048chips/symbol, CRC on. Slow+long range + } ModemConfigChoice; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// interrupt and slave select pin. After constructing, you must call init() to initialise the interface + /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient + /// distinct interrupt lines, one for each instance. + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RH_RF22 before + /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) + /// \param[in] interruptPin The interrupt Pin number that is connected to the RFM DIO0 interrupt line. + /// Defaults to pin 2, as required by Anarduino MinWirelessLoRa module. + /// Caution: You must specify an interrupt capable pin. + /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. + /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. + /// On Arduino Zero from arduino.cc, any digital pin other than 4. + /// On Arduino M0 Pro from arduino.org, any digital pin other than 2. + /// On other Arduinos pins 2 or 3. + /// See http://arduino.cc/en/Reference/attachInterrupt for more details. + /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. + /// On other boards, any digital pin may be used. + /// \param[in] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + RH_RF95(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, RHGenericSPI& spi = hardware_spi); + + /// Initialise the Driver transport hardware and software. + /// Leaves the radio in idle mode, + /// with default configuration of: 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Prints the value of all chip registers + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging purposes only. + /// \return true on success + bool printRegisters(); + + /// Sets all the registers required to configure the data modem in the radio, including the bandwidth, + /// spreading factor etc. You can use this to configure the modem with custom configurations if none of the + /// canned configurations in ModemConfigChoice suit you. + /// \param[in] config A ModemConfig structure containing values for the modem configuration registers. + void setModemRegisters(const ModemConfig* config); + + /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// here, use setModemRegisters() with your own ModemConfig. + /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator + /// for reliable operation. + /// \param[in] index The configuration choice. + /// \return true if index is a valid choice. + bool setModemConfig(ModemConfigChoice index); + + /// Tests whether a new message is available from the Driver. + /// On most drivers, this will also put the Driver into RHModeRx mode until + /// a message is actually received by the transport, when it will be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available(); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then optionally waits for Channel Activity Detection (CAD) + /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send + /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting. + /// \return true if the message length was valid and it was correctly queued for transmit. Return false + /// if CAD was requested and the CAD timeout timed out before clear channel was detected. + virtual bool send(const uint8_t* data, uint8_t len); + + /// Sets the length of the preamble + /// in bytes. + /// Caution: this should be set to the same + /// value on all nodes in your network. Default is 8. + /// Sets the message preamble length in RH_RF95_REG_??_PREAMBLE_?SB + /// \param[in] bytes Preamble length in bytes. + void setPreambleLength(uint16_t bytes); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + /// Sets the transmitter and receiver + /// centre frequency. + /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several + /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work + /// \return true if the selected frquency centre is within range + bool setFrequency(float centre); + + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + void setModeIdle(); + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver in the RF95/96/97/98. + void setModeRx(); + + /// If current mode is Rx or Idle, changes it to Rx. F + /// Starts the transmitter in the RF95/96/97/98. + void setModeTx(); + + /// Sets the transmitter power output level, and configures the transmitter pin. + /// Be a good neighbour and set the lowest power level you need. + /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) + /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) + /// while some (such as the Modtronix inAir4 and inAir9) + /// use the RFO transmitter pin for lower power but higher efficiency. + /// You must set the appropriate power level and useRFO argument for your module. + /// Check with your module manufacturer which transmtter pin is used on your module + /// to ensure you are setting useRFO correctly. + /// Failure to do so will result in very low + /// transmitter power output. + /// Caution: legal power limits may apply in certain countries. + /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). + /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, + /// valid values are from +2 to +20. For 18, 19 and 20, PA_DAC is enabled, + /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), + /// valid values are from 0 to 15. + /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of + /// the PA_BOOST pin (false). Choose the correct setting for your module. + void setTxPower(int8_t power, bool useRFO = false); + + /// Sets the radio into low-power sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. + /// \return true if sleep mode was successfully entered. + virtual bool sleep(); + + // Bent G Christensen (bentor@gmail.com), 08/15/2016 + /// Use the radio's Channel Activity Detect (CAD) function to detect channel activity. + /// Sets the RF95 radio into CAD mode and waits until CAD detection is complete. + /// To be used in a listen-before-talk mechanism (Collision Avoidance) + /// with a reasonable time backoff algorithm. + /// This is called automatically by waitCAD(). + /// \return true if channel is in use. + virtual bool isChannelActive(); + + /// Enable TCXO mode + /// Call this immediately after init(), to force your radio to use an external + /// frequency source, such as a Temperature Compensated Crystal Oscillator (TCXO), if available. + /// See the comments in the main documentation about the sensitivity of this radio to + /// clock frequency especially when using narrow bandwidths. + /// Leaves the module in sleep mode. + /// Caution: the TCXO model radios are not low power when in sleep (consuming + /// about ~600 uA, reported by Phang Moh Lim.
+ /// Caution: if you enable TCXO and there is no exernal TCXO signal connected to the radio + /// or if the exerrnal TCXO is not + /// powered up, the radio will not work<\b> + /// \param[in] on If true (the default) enables the radio to use the external TCXO. + void enableTCXO(bool on = true); + + /// Returns the last measured frequency error. + /// The LoRa receiver estimates the frequency offset between the receiver centre frequency + /// and that of the received LoRa signal. This function returns the estimates offset (in Hz) + /// of the last received message. Caution: this measurement is not absolute, but is measured + /// relative to the local receiver's oscillator. + /// Apparent errors may be due to the transmitter, the receiver or both. + /// \return The estimated centre frequency offset in Hz of the last received message. + /// If the modem bandwidth selector in + /// register RH_RF95_REG_1D_MODEM_CONFIG1 is invalid, returns 0. + int frequencyError(); + + /// Returns the Signal-to-noise ratio (SNR) of the last received message, as measured + /// by the receiver. + /// \return SNR of the last received message in dB + int lastSNR(); + + /// brian.n.norman@gmail.com 9th Nov 2018 + /// Sets the radio spreading factor. + /// valid values are 6 through 12. + /// Out of range values below 6 are clamped to 6 + /// Out of range values above 12 are clamped to 12 + /// See Semtech DS SX1276/77/78/79 page 27 regarding SF6 configuration. + /// + /// \param[in] uint8_t sf (spreading factor 6..12) + /// \return nothing + void setSpreadingFactor(uint8_t sf); + + /// brian.n.norman@gmail.com 9th Nov 2018 + /// Sets the radio signal bandwidth + /// sbw ranges and resultant settings are as follows:- + /// sbw range actual bw (kHz) + /// 0-7800 7.8 + /// 7801-10400 10.4 + /// 10401-15600 15.6 + /// 15601-20800 20.8 + /// 20801-31250 31.25 + /// 31251-41700 41.7 + /// 41701-62500 62.5 + /// 62501-12500 125.0 + /// 12501-250000 250.0 + /// >250000 500.0 + /// NOTE caution Earlier - Semtech do not recommend BW below 62.5 although, in testing + /// I managed 31.25 with two devices in close proximity. + /// \param[in] sbw long, signal bandwidth e.g. 125000 + void setSignalBandwidth(long sbw); + + /// brian.n.norman@gmail.com 9th Nov 2018 + /// Sets the coding rate to 4/5, 4/6, 4/7 or 4/8. + /// Valid denominator values are 5, 6, 7 or 8. A value of 5 sets the coding rate to 4/5 etc. + /// Values below 5 are clamped at 5 + /// values above 8 are clamped at 8. + /// Default for all standard modem config options is 4/5. + /// \param[in] denominator uint8_t range 5..8 + void setCodingRate4(uint8_t denominator); + + /// brian.n.norman@gmail.com 9th Nov 2018 + /// sets the low data rate flag if symbol time exceeds 16ms + /// ref: https://www.thethingsnetwork.org/forum/t/a-point-to-note-lora-low-data-rate-optimisation-flag/12007 + /// called by setBandwidth() and setSpreadingfactor() since these affect the symbol time. + void setLowDatarate(); + + /// brian.n.norman@gmail.com 9th Nov 2018 + /// Allows the CRC to be turned on/off. Default is true (enabled) + /// When true, RH_RF95 sends a CRC in outgoing packets and requires a valid CRC to be + /// present and correct on incoming packets. + /// When false, does not send CRC in outgoing packets and does not require a CRC to be + /// present on incoming packets. However if a CRC is present, it must be correct. + /// Normally this should be left on (the default) + /// so that packets with a bad CRC are rejected. If turned off you will be much more likely to receive + /// false noise packets. + /// \param[in] on bool, true enables CRCs in incoming and outgoing packets, false disables them + void setPayloadCRC(bool on); + + /// tilman_1@gloetzner.net + /// Returns device version from register 42 + /// \param none + /// \return uint8_t deviceID + uint8_t getDeviceVersion(); + +protected: + + /// Do whatever is necesary to establish the interrupt handler. Subclasses may have different needs + bool setupInterruptHandler(); + + /// This is a low level function to handle the interrupts for one instance of RH_RF95. + /// Called automatically by isr*() + /// Should not need to be called by user code. + void handleInterrupt(); + + /// Examine the revceive buffer to determine whether the message is for this node + void validateRxBuf(); + + /// Clear our local receive buffer + void clearRxBuf(); + + /// Called by RH_RF95 when the radio mode is about to change to a new setting. + /// Can be used by subclasses to implement antenna switching etc. + /// \param[in] mode RHMode the new mode about to take effect + /// \return true if the subclasses changes successful + virtual bool modeWillChange(RHMode) {return true;} + + /// False if the PA_BOOST transmitter output pin is to be used. + /// True if the RFO transmitter output pin is to be used. + bool _useRFO; + +private: + /// Low level interrupt service routine for device connected to interrupt 0 + static void isr0(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr1(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr2(); + + /// Array of instances connected to interrupts 0 and 1 + static RH_RF95* _deviceForInterrupt[]; + + /// Index of next interrupt number to use in _deviceForInterrupt + static uint8_t _interruptCount; + + /// The configured interrupt pin connected to this instance + uint8_t _interruptPin; + + /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) + /// else 0xff + uint8_t _myInterruptIndex; + + /// Number of octets in the buffer + volatile uint8_t _bufLen; + + /// The receiver/transmitter buffer + uint8_t _buf[RH_RF95_MAX_PAYLOAD_LEN]; + + /// True when there is a valid message in the buffer + volatile bool _rxBufValid; + + /// True if we are using the HF port (779.0 MHz and above) + bool _usingHFport; + + /// Last measured SNR, dB + int8_t _lastSNR; + + /// If true, sends CRCs in every packet and requires a valid CRC in every received packet + bool _enableCRC; + + /// device ID + uint8_t _deviceVersion = 0x00; + +}; + +/// @example rf95_client.ino +/// @example rf95_client.ino +/// @example rf95_server.ino +/// @example rf95_encrypted_client.ino +/// @example rf95_encrypted_server.ino +/// @example rf95_reliable_datagram_client.ino +/// @example rf95_reliable_datagram_server.ino + +#endif + diff --git a/RH_STM32WLx.cpp b/RH_STM32WLx.cpp new file mode 100644 index 0000000..731c88f --- /dev/null +++ b/RH_STM32WLx.cpp @@ -0,0 +1,86 @@ +// RH_STM32WLx.cpp +// +// Copyright (C) 2023 Mike McCauley +// $Id: RH_STM32WLx.cpp,v 1.27 2020/07/05 08:52:21 mikem Exp $ +// + +#include +#include + +// Are we building for a suitable STM processor +#if defined(SUBGHZSPI_BASE) + +// On this chip, the radio is connected by a dedicated, internal SPI interface called SubGhz, +// and accessed via our RHSUBGHZSPI +static RHSUBGHZSPI subghzspi; + +// Set up radio management pins for various modes for your specific radio wiring +// YOU MUST DO THIS FOR YOUR PARTICULAR RADIO. FAILURE TO SET +// THIS UP PROPERLY WILL CERTAINLY PREVENT TRANSMISSION AT FULL +// POWER AND POSSIBLY RECEPTION TOO +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO && defined(ARDUINO_LORA_E5_MINI)) + // EG: Seeed WiO-E5 mini + // These LoRa-E5 modules have an RF switch but only use the high power PA + // Only 2 control pins are used + + static RH_SX126x::RadioPinConfig radioPinConfig = { + .pinNumber = {PA4, PA5, RH_INVALID_PIN}, + .configState = { + {RH_SX126x::RadioPinConfigMode_IDLE, LOW, LOW}, + {RH_SX126x::RadioPinConfigMode_RX, HIGH, LOW}, + {RH_SX126x::RadioPinConfigMode_TX_HIGH_POWER, LOW, HIGH}, // low power not supported on this module + {RH_SX126x::RadioPinConfigMode_EOT}, + } + }; +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO && defined(ARDUINO_NUCLEO_WL55JC1)) + // These devices have an RF switch and low and high power PAs + // 3 control pins are used + .pinNumber = {PC3, PC4, PC5}, + .configState = { + {RH_SX126x::RadioPinConfigMode_IDLE, LOW, LOW, LOW}, + {RH_SX126x::RadioPinConfigMode_RX, HIGH, HIGH, LOW}, + {RH_SX126x::RadioPinConfigMode_TX_LOW_POWER, HIGH, HIGH, HIGH}, + {RH_SX126x::RadioPinConfigMode_TX_HIGH_POWER, HIGH, LOW, HIGH}, + {RH_SX126x::RadioPinConfigMode_EOT}, + } + }; +#else +#warning RH_STM32WLx Do not know how to set radio control pins for this processor +#endif + +RH_STM32WLx::RH_STM32WLx() + : + RH_SX126x(RH_INVALID_PIN, RH_INVALID_PIN, RH_INVALID_PIN, RH_INVALID_PIN, subghzspi, &radioPinConfig) +{ + SubGhz.setResetActive(true); + delay(2); + SubGhz.setResetActive(false); + delay(200); // After reset: else can get runt transmission during startup FIXME: wait until ready? No doesnt work +} + +// Override the init() function becaue we need to adjust some things afterwards to suit this radio module +bool RH_STM32WLx::init() +{ + bool ret = RH_SX126x::init(); // In STDBY mode after this + + setDIO2AsRfSwitchCtrl(false); // Dont use DIO2 as RF control for Wio-E5, which uses separate pins FIXME: not correct for NICERF + setTCXO(1.7, 5000); // MUST do this (in standby mode) else get no output. volts, us + return ret; +} + +bool RH_STM32WLx::setupInterruptHandler() +{ + // It is necessary to use the SUBGHZ SPI radio interface and its built-in interrupt support + // BUT: only the first one can be used if RHSUBGHZSPI is in use + // This is is a modern lambda to attach a C++ function as an ordinary C callback + // Hopefuly this will compile successfully everywhere. If not let us know and we will + // change it to something more backwards compatible + SubGhz.attachInterrupt([this]() { + handleInterrupt(); + }); + + return true; +} + + +#endif diff --git a/RH_STM32WLx.h b/RH_STM32WLx.h new file mode 100644 index 0000000..ab8ce9c --- /dev/null +++ b/RH_STM32WLx.h @@ -0,0 +1,203 @@ +// RH_STM32WLx.h +// +// Definitions for the ST Microelectronics STM32WLE5JC chip which contains +// a SX1261/1262 LoRa capable radio. +// https://wiki.seeedstudio.com/LoRa_E5_mini/ +// https://www.rfsolutions.co.uk/downloads/1537522406DS_SX1261-2_V1.1_SEMTECH.pdf +// https://cdn.sparkfun.com/assets/6/b/5/1/4/SX1262_datasheet.pdf +// https://files.seeedstudio.com/products/317990687/res/LoRa-E5+module+datasheet_V1.0.pdf +// https://forum.seeedstudio.com/t/lora-e5-register-settings-for-oscillators/262635 +// file:///home/mikem/Downloads/es0506-stm32wle5xx-stm32wle4xx-device-errata-stmicroelectronics.pdf +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2023 Mike McCauley +// + +#ifndef RH_STM32WLx_h +#define RH_STM32WLx_h + +#include + +///////////////////////////////////////////////////////////////////// +/*! \class RH_STM32WLx RH_STM32WLx.h +\brief Driver to send and receive unaddressed, unreliable datagrams via a +SX1261 LoRa capable radio transceiver embedded in a ST Microelectronics STM32WLE5xx and +STM32WLE4xx processors. + +\par Overview + +The ST Microelectronics STM32WLE5xx and STM32WLE4xx Arm Cortex +processors contain a SX1261 radio with 2 power amplifiers (PAs), a bit +like a combination of a SX1261 and SX1262. There is a dedicated SPI +interface (called the SubGhzSPI that is used only to communicate with +the radio. + +The Seeed LoRa-E5-HF and LoRa-E5-LF modules encapsulate a STM32WLE5JC +processor, along with an antenna switch, Temperature Controlled +Crystal Oscillator (TCXO) and some support components. The Seeed +Wio-E5 mini developement board (described below) has a LoRa-E5-HF +along with voltage regulator, USB interface, antenna connector etc. + +This class is a subclass of RH_SX126x. See the documentation for that +class for more details about configuring and using this class. + +The stmx32wl_* examples provided should compile out of the box on Arduino 2.1 or later +and run with either modules equipped with +LoRa-E4 or on the NUCLEO_WL55JC1. Make sure you select the appropriate board in Arduino. + +\par The Seeed Wio-E5 mini + +One example of a development board that includes a STM32WLE5xx and +SX1261 radio is the Wio-E5 mini. + +The Wio-E5 mini is powered by the ST Microelectronics STM32WLE5JC, +which contains an ARM Cortex-M4 core and Semtech SX126X LoRa capable +radio. The core and radio are enclosed in a sealed Seeed LoRa-E5-HF +module, and which includes an antenna switch. + +https://wiki.seeedstudio.com/LoRa_E5_mini/ +https://files.seeedstudio.com/products/317990687/res/LoRa-E5+module+datasheet_V1.0.pdf + +It comes with a 868 to 915MHz compatible antenna and USB-C cable. + +As shipped, the Wio-E5 mini contains firmware that provides a serial AT +command set that allows the device to connect to join a LoRaWAN +network (via a LoRaWAN gateway) using LoRA radio modulation. +https://files.seeedstudio.com/products/317990687/res/LoRa-E5+AT+Command+Specification_V1.0+.pdf + +You can plug the WiO E5 into a USB port, and use a serial port +program (gtkterm, putty, Hyperterm or whatever) to communicate (using +AT commands) with the Wio-E5, configure it and then use it to send +LoRaWAN messages to a LoRaWAN server. + +However, what we want to do is to upload RadioHead based firmware into +the Wio-E5 and use the built in SX126X radio to communicate via LoRa +radio modulation with other similar LoRA radio nodes. + +Note: LoRa is NOT the same thing as LoRaWAN. LoRaWAN is a complete +networking system that uses LoRa radio modulation as the transport +layer. If you want to work with a LoRaWAN network, you should be using +LoRaWAN software and libraries, not RadioHead. LoRa is a much simpler +and lower level transport layer that can he used to send short +messages over significant distances withglow power. + +So, we want to upload RadioHead based software (containing the +RadioHead SX126X driver) to the STM32WLE5JC on the Wio-E5. Before we +can do that we have to disable the firmware write protection on the +STM32WLE5JC. + +The Wio-E5 (as shipped) AT command firmware is protected by the ARM +processor's Read Out Protection (RDP) byte, meaning you cant read out +the AT command set firmware, nor can you upload new firmware, The RDP +byte has to be reset first. + +But CAUTION: resetting the RDP byte will permanently ERASE the +as-shipped AT command firmware, and THERE IS NO WAY TO GET IT BACK. So +make sure thats what you want to do. + +In order to be able to upload our own firmware we must reset the RDP +byte from the as-shipped value of 0xBB (Level 1 read protection) to +0xAA (Level 0, no protection). In order to do this you will need: + +- Wio-E5 mini as shipped +- STLink/V2 or STLink/V2 emulator (we used the Adafruit 2548 + https://www.adafruit.com/product/2548). Its an emulator (a red cased USB dongle) +- Host computer with USB ports (Linux, Mac or Windows) +- STM32CubeProgrammer software + https://www.st.com/en/development-tools/stm32cubeprog.html + 2.15.0 or later for Linux Mac or Windows. Registration required. + +1. Install STM32CubeProgrammer +2. Wire the STLink/V2 to the Wio-E5 pins with the jumper wires + provided. This will provide both power and + communications with the STM32WLE5JC: + Wio-E5 STLink/V2 (writing on the end shows the pinout) + 3.3V 3.3V + GND GND + DIO SWDIO + CLK SWCLK +3. Plug the STLink/V2 into a USB port. Red light should appear on the STLink/V2. +4. Run STM32CubeProgrammer (appears in Ubuntu Applications menu if installed on Linux with wine) +5. At the top right you should see ST-LINK selected, and below that, +in the ST-LINK Configuration box, the Serial number of your STLink/V2. +6a. If this is the first time you have used the STLink/V2, you will need + to upgrade its firmware. Click on 'Firmware upgrade' in the ST-LINK + Configuration box, get the Upgrade dialog. +6b. Click on 'Open in update mode'. If it complains the STlink is not +in DFU mode, unplug and replug the STlink, click on 'Refresh device +list', then 'Open in update mode' +6c Click on Upgrade. Wait 20 seconds. See success. +6d Close the dialog. +7a Press RST reset pin on the Wio-E5 and then immediately: +7b Click on 'Connect' top right of STM32CubeProgrammer. You should see + some blue progress lines in the Log, maybe finally a red 'Data read + failed'. Thats OK, because the RDP byte is still set. +8. Click on the 'OB' icon, 3rd from the top in the far left margin. +9. Select Read Out Protection item, see RDP value set to 'BB'. Change to + 'AA', click Apply. See pop-up 'Option Bytes successfully + programmed'. + +Good! The STM32WLE5JC firmware can now be written and read as we need (note +that the factory supplied LoRaWan firmware has now been erased), by +either STM32CubeProgrammer or Arduino IDE (2.1.1 or later) via the +STLink/V2, so leave it connected + +In order to use the Arduino IDE to program the Wio-E5, you must +install the stm32duino package using these instructions: + https://community.st.com/t5/stm32-mcus/stm32-arduino-stm32duino-tutorial/ta-p/49649 +We installed and tested with 2.7.1 + +Leave STLink/V2 connected but close STM32CubeProgrammer +In Arduino IDE: select the following menu options: +Tools -> Board -> STM32 MCU based boards -> LoRa boards +Tools -> Board part number -> LoRa-E5 mini +Tools -> U(S)ART support -> Enabled (generic Serial) +You will then be able to edit and upload directly from Arduino IDE through the STLink/V2 +You can connect the Wio-E5 USB-C to your hosts USB port and use +Arduino IDE to monitor the serial output to Serial +(Note: programming does not occur over the USB connection). + +There are other options for programming the STM32WLE5JC, +including STM32CubeIDE from ST. We did not test them. + +https://www.rfsolutions.co.uk/downloads/1537522406DS_SX1261-2_V1.1_SEMTECH.pdf + +Lora-net software: +https://github.com/Lora-net/SWL2001/releases/tag/v3.3.0 + +\par Interrupts + +This driver uses the SubGhzClass class (part of the Arduino stm32duino +board support) to interface with the internal SX1261/2 radio. That +class implements the dedicated radio SPI interface. The radio also has a +dedicated internal pin for the radio interrupt and the SPI device select +pin and the radio reset pin. The SubGhzClass class includes methods +for using all those pins. + +Nevertheless it should be possible to use this processor with the +normal external SPI interfaces to also interface with another SPI +based RadioHEad supported radio, in oprder to make a radio gateway +etc. + +*/ +class RH_STM32WLx : public RH_SX126x +{ +public: + // Contructor + RH_STM32WLx(); + + // Override the init() function becaue we need to adjust some things afterwards to suit this radio module + virtual bool init(); + +protected: + // Override this because waiting is built in to the SUBGhz driver + virtual bool waitUntilNotBusy() { return true;}; + + /// Do whatever is necessary to establish the interrupt handler. + /// This device has special requirements for setting up the interupt handler + /// through the SUBGHZSPI interface so we override + bool setupInterruptHandler(); +}; + + +#endif + diff --git a/RH_SX126x.cpp b/RH_SX126x.cpp new file mode 100644 index 0000000..b7b0cbe --- /dev/null +++ b/RH_SX126x.cpp @@ -0,0 +1,1288 @@ +// RH_SX126x.cpp +// +// Copyright (C) 2023 Mike McCauley +// $Id: RH_SX126x.cpp,v 1.27 2020/07/05 08:52:21 mikem Exp $ +// +// UNFINISHED. TODO: +// power setting for different device types +// specific subclasses for 1261, 1262, 1268? + +#include + +// Maybe a mutex for multithreading on Raspberry Pi? +#ifdef RH_USE_MUTEX +RH_DECLARE_MUTEX(lock); +#endif + +// Interrupt vectors for the 3 Arduino interrupt pins +// Each interrupt can be handled by a different instance of RH_SX126x, allowing you to have +// 2 or more LORAs per Arduino +RH_SX126x* RH_SX126x::_deviceForInterrupt[RH_SX126x_NUM_INTERRUPTS] = {0, 0, 0}; + +// These are indexed by the values of ModemConfigChoice +// Stored in flash (program) memory to save SRAM +PROGMEM static const RH_SX126x::ModemConfig MODEM_CONFIG_TABLE[] = +{ + // packetType, p1, p2, p3, p4, p5, p6, p7, p8 + // LoRa_Bw125Cr45Sf128 Works with RH_RF95 + { RH_SX126x::PacketTypeLoRa, RH_SX126x_LORA_SF_128, RH_SX126x_LORA_BW_125_0, RH_SX126x_LORA_CR_4_5, RH_SX126x_LORA_LOW_DATA_RATE_OPTIMIZE_OFF, 0, 0, 0, 0}, + // LoRa_Bw500Cr45Sf128 Works with RH_RF95 + { RH_SX126x::PacketTypeLoRa, RH_SX126x_LORA_SF_128, RH_SX126x_LORA_BW_500_0, RH_SX126x_LORA_CR_4_5, RH_SX126x_LORA_LOW_DATA_RATE_OPTIMIZE_OFF, 0, 0, 0, 0}, + + // LoRa_Bw31_25Cr48Sf512 no interop with RH_RF95 WHY???? + { RH_SX126x::PacketTypeLoRa, RH_SX126x_LORA_SF_512, RH_SX126x_LORA_BW_31_25, RH_SX126x_LORA_CR_4_8, RH_SX126x_LORA_LOW_DATA_RATE_OPTIMIZE_ON, 0, 0, 0, 0}, + + // LoRa_Bw125Cr48Sf4096 Works with RH_RF95 + { RH_SX126x::PacketTypeLoRa, RH_SX126x_LORA_SF_4096, RH_SX126x_LORA_BW_125_0, RH_SX126x_LORA_CR_4_8, RH_SX126x_LORA_LOW_DATA_RATE_OPTIMIZE_ON, 0, 0, 0, 0}, + + // LoRa_Bw125Cr45Sf2048 Works with RH_RF95 + { RH_SX126x::PacketTypeLoRa, RH_SX126x_LORA_SF_2048, RH_SX126x_LORA_BW_125_0, RH_SX126x_LORA_CR_4_5, RH_SX126x_LORA_LOW_DATA_RATE_OPTIMIZE_ON, 0, 0, 0, 0}, +}; + +RH_SX126x::RH_SX126x(uint8_t slaveSelectPin, uint8_t interruptPin, uint8_t busyPin, uint8_t resetPin, + RHGenericSPI& spi, RadioPinConfig* radioPinConfig) + : + RHSPIDriver(slaveSelectPin, spi), + _rxBufValid(0) +{ + _interruptPin = interruptPin; + _busyPin = busyPin; + _resetPin = resetPin; + _myInterruptIndex = 0xff; // Not allocated yet + _enableCRC = true; + // There should be (but may not be) a configuration structure toi tell us how to manage the + // radio RF switch control pins + setRadioPinConfig(radioPinConfig); +} + +bool RH_SX126x::init() +{ + if (!RHSPIDriver::init()) + return false; + +#ifdef RH_USE_MUTEX + if (RH_MUTEX_INIT(lock) != 0) + { + Serial.println("\n mutex init has failed\n"); + return false; + } +#endif + + if (!setupInterruptHandler()) + return false; + + // Reset the radio, if we know the reset pin: + if (_resetPin != RH_INVALID_PIN) + { + pinMode(_resetPin, OUTPUT); + digitalWrite(_resetPin, HIGH); + digitalWrite(_resetPin, LOW); + delay(2); + digitalWrite(_resetPin, HIGH); + // Expect to be busy for about 1ms after this + delay(200); // After reset: else can get runt transmission during startup + } + + // Configure the BUSY pin if available + if (_busyPin != RH_INVALID_PIN) + pinMode(_busyPin, INPUT); + + // No way to check the device type :-( + // Can read data like 'SX1261 TKF 1A11', at register 0x0320 RH_SX126x_REG_VERSION_STRING but it does not change for SX1261 + + // Get the current status and guess if we are connected + uint8_t status = getStatus(); + if (status == 0x00 || status == 0xff) // Should never get this: probably not connected by SPI + return false; + setModeIdle(); + + setRegulatorMode(RH_SX126x_REGULATOR_DC_DC); // == SMPS mode + clearDeviceErrors(); + setRxFallbackMode(RH_SX126x_RX_TX_FALLBACK_MODE_STDBY_RC); + calibrate(RH_SX126x_CALIBRATE_ALL); // All blocks get (re)calibrated when setFrequency() is called with calibrate true + // This LoRa Sync word 0x1424 is compatible with single byte 0x12 default for RH_RF95. + // https://forum.lora-developers.semtech.com/t/sx1272-and-sx1262-lora-sync-word-compatibility/988/13 + setLoRaSyncWord(0x1424); + + // You may need to change these after init to suit your radio and its wiring: + setDIO2AsRfSwitchCtrl(true); // Use the radios DIO2 pin control the transmitter. This is common in 3rd party RF modules + setTCXO(3.3, 100); // Enable the TCXO. Typical values for the control voltage and delay + + // These are the interrupts we are willing to process + setDioIrqParams(_irqMask, _irqMask, RH_SX126x_IRQ_NONE, RH_SX126x_IRQ_NONE); + + // Set up default configuration + setModemConfig(LoRa_Bw125Cr45Sf128); // Radio default +// setModemConfig(LoRa_Bw125Cr48Sf4096); // slow and reliable? + // Default preamble length is 8, so dont set it here + // An innocuous ISM frequency, in the HF range for the WiO-E5, compatible with RH_RF95 + setFrequency(915.0); + // Lowish power + setTxPower(13); + + return true; +} + +void RH_SX126x::enableCrcErrorIrq(bool enable) +{ + if ( enable ) + _irqMask |= RH_SX126x_IRQ_CRC_ERR; + else + _irqMask &= ~RH_SX126x_IRQ_CRC_ERR; + setDioIrqParams(_irqMask, _irqMask, RH_SX126x_IRQ_NONE, RH_SX126x_IRQ_NONE); +} + +void RH_SX126x::enableRawMode(bool enable) +{ + _raw = enable; +} + +// Some subclasses may need to override +bool RH_SX126x::setupInterruptHandler() +{ + // For some subclasses (eg RH_ABZ) we dont want to set up interrupt + int interruptNumber = NOT_AN_INTERRUPT; + if (_interruptPin != RH_INVALID_PIN) + { + // Determine the interrupt number that corresponds to the interruptPin + interruptNumber = digitalPinToInterrupt(_interruptPin); + if (interruptNumber == NOT_AN_INTERRUPT) + return false; +#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER + interruptNumber = _interruptPin; +#endif + + // Tell the low level SPI interface we will use SPI within this interrupt + spiUsingInterrupt(interruptNumber); + } + + if (_interruptPin != RH_INVALID_PIN) + { + // Add by Adrien van den Bossche for Teensy + // ARM M4 requires the below. else pin interrupt doesn't work properly. + // On all other platforms, its innocuous, belt and braces + pinMode(_interruptPin, INPUT); + + // Set up interrupt handler + // Since there are a limited number of interrupt glue functions isr*() available, + // we can only support a limited number of devices simultaneously + // ON some devices, notably most Arduinos, the interrupt pin passed in is actually the + // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping + // yourself based on knwledge of what Arduino board you are running on. + if (_myInterruptIndex == 0xff) + { + static uint8_t interruptCount = 0; // Index into _deviceForInterrupt for next device + // First run, no interrupt allocated yet + if (interruptCount <= RH_SX126x_NUM_INTERRUPTS) + _myInterruptIndex = interruptCount++; + else + return false; // Too many devices, not enough interrupt vectors + } + _deviceForInterrupt[_myInterruptIndex] = this; + + if (_myInterruptIndex == 0) + attachInterrupt(interruptNumber, isr0, RISING); + else if (_myInterruptIndex == 1) + attachInterrupt(interruptNumber, isr1, RISING); + else if (_myInterruptIndex == 2) + attachInterrupt(interruptNumber, isr2, RISING); + else + return false; // Too many devices, not enough interrupt vectors + } + return true; +} + +// C++ level interrupt handler for this instance +// LORA is unusual in that it has several interrupt lines, and not a single, combined one. +// On MiniWirelessLoRa, only one of the several interrupt lines (DI0) from the RFM95 is usefuly +// connnected to the processor. +// We use this to get RxDone and TxDone interrupts +void RH_SX126x::handleInterrupt() +{ + RH_MUTEX_LOCK(lock); // Multithreading support + uint16_t interrupts = getIrqStatus(); + _lastirq = interrupts; + _iflag = true; // Debugging + clearIrqStatus(interrupts); +// Serial.print("int: "); +// Serial.println(interrupts, HEX); + if (_mode == RHModeRx && (interrupts & (RH_SX126x_IRQ_CRC_ERR | RH_SX126x_IRQ_HEADER_ERR))) + { + // CrcErr HeaderErr + _rxBad++; + clearRxBuf(); + // If there was an error, the SX126x is now in standby mode. Need to force RH state back to idle too + _mode = RHModeIdle; +// setModeRx(); // Keep trying? + } + else if (_mode == RHModeRx && (interrupts & RH_SX126x_IRQ_RX_DONE)) + { + // RxDone + // Should now be in STDBY + // Get received packet length + uint8_t rxbufferstatus[2]; // PayloadLengthRx, RxStartBufferPointer + getCommand(RH_SX126x_CMD_GET_RX_BUFFER_STATUS, rxbufferstatus, sizeof(rxbufferstatus)); + _bufLen = rxbufferstatus[0]; + uint8_t offset = rxbufferstatus[1]; + // Get the packet + readBuffer(offset, _buf, _bufLen); + + uint8_t packetstatus[3]; + getCommand(RH_SX126x_CMD_GET_PKT_STATUS, packetstatus, sizeof(packetstatus)); + if (_packetType == PacketTypeLoRa) + { + _lastRssi = -(packetstatus[0] / 2); // dBm + _lastSNR = (packetstatus[1] / 4); + } + else if (_packetType == PacketTypeGFSK) + { + _lastRssi = -(packetstatus[2] / 2); // dBm + _lastSNR = 0; // Unobtainable + } + + validateRxBuf(); + if (_rxBufValid) + setModeIdle(); // Got one + } + else if (_mode == RHModeTx && (interrupts & RH_SX126x_IRQ_TX_DONE)) + { + // TxDone + // Should now be in STDBY + _txGood++; + setModeIdle(); + } + else if (_mode == RHModeCad && (interrupts & RH_SX126x_IRQ_CAD_DONE)) + { +// Serial.println("caddone"); + // CadDone + // Should now be in STDBY_RX + setModeIdle(); + } + else if (_mode == RHModeCad && (interrupts & RH_SX126x_IRQ_CAD_DETECTED)) + { + // CadDetected: there was activity +// Serial.println("caddetected"); + _cad = true; + setModeIdle(); + } + RH_MUTEX_UNLOCK(lock); +} + +// These are low level functions that call the interrupt handler for the correct +// instance of RH_SX126x. +// 3 interrupts allows us to have 3 different devices +void RH_INTERRUPT_ATTR RH_SX126x::isr0() +{ + if (_deviceForInterrupt[0]) + _deviceForInterrupt[0]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_SX126x::isr1() +{ + if (_deviceForInterrupt[1]) + _deviceForInterrupt[1]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_SX126x::isr2() +{ + if (_deviceForInterrupt[2]) + _deviceForInterrupt[2]->handleInterrupt(); +} + +// Check whether the latest received message is complete and uncorrupted +void RH_SX126x::validateRxBuf() +{ + if (_bufLen < 4) + return; // Too short to be a real message + // Extract the 4 headers + _rxHeaderTo = _buf[0]; + _rxHeaderFrom = _buf[1]; + _rxHeaderId = _buf[2]; + _rxHeaderFlags = _buf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +bool RH_SX126x::available() +{ + RH_MUTEX_LOCK(lock); // Multithreading support + if (_mode == RHModeTx) + { + RH_MUTEX_UNLOCK(lock); + return false; + } + setModeRx(); + RH_MUTEX_UNLOCK(lock); + return _rxBufValid; // Will be set by the interrupt handler when a good message is received +} + +void RH_SX126x::clearRxBuf() +{ + waitUntilNotBusy(); + ATOMIC_BLOCK_START; + _rxBufValid = false; + _bufLen = 0; + ATOMIC_BLOCK_END; +} + +bool RH_SX126x::recv(uint8_t* buf, uint8_t* len) +{ + uint8_t hdr_len = RH_SX126x_HEADER_LEN; + if (!available()) + return false; + if ( _raw ) + hdr_len = 0; + if (buf && len) + { + ATOMIC_BLOCK_START; + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen-hdr_len) + *len = _bufLen-hdr_len; + memcpy(buf, _buf+hdr_len, *len); + ATOMIC_BLOCK_END; + } + clearRxBuf(); // This message accepted and cleared + RH_MUTEX_UNLOCK(lock); + return true; +} + +bool RH_SX126x::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_SX126x_MAX_MESSAGE_LEN) + return false; + + waitPacketSent(); // Make sure we dont interrupt an outgoing message + setModeIdle(); + + if (!waitCAD()) + return false; // Check channel activity + + setBufferBaseAddress(0, 0); + // The headers + if ( _raw ) + { + // The message data + writeBuffer(0, data, len); + setPacketParametersLoRa(len); // Tell modem how much to send + } + else + { + uint8_t headers[RH_SX126x_HEADER_LEN] = {_txHeaderTo, _txHeaderFrom, _txHeaderId, _txHeaderFlags }; + writeBuffer(0, headers, sizeof(headers)); + + // The message data + writeBuffer(RH_SX126x_HEADER_LEN, data, len); + setPacketParametersLoRa(len + RH_SX126x_HEADER_LEN); // Tell modem how much to send + } + + RH_MUTEX_LOCK(lock); // Multithreading support + setModeTx(); // Start the transmitter + RH_MUTEX_UNLOCK(lock); + + // Hmmmm, some chips fail to enter TX mode the first time,and need to be tried again + // Why? + if (getStatus() == 0x2a) + { + // Retry Tx mode directly + RH_MUTEX_LOCK(lock); // Multithreading support + setTx(RH_SX126x_RX_TIMEOUT_NONE); // Timeout 0 + RH_MUTEX_UNLOCK(lock); + } + + // when Tx is done, interruptHandler will fire and radio mode will return to STANDBY + return true; +} + + +uint8_t RH_SX126x::maxMessageLength() +{ + return RH_SX126x_MAX_MESSAGE_LEN; +} + +bool RH_SX126x::setFrequency(float centre, bool calibrate) +{ + if (calibrate) + { + if (centre > 900.0) + calibrateImage(RH_SX126x_CAL_IMG_902_MHZ_1, RH_SX126x_CAL_IMG_902_MHZ_2); + else if (centre > 850.0) + calibrateImage(RH_SX126x_CAL_IMG_863_MHZ_1, RH_SX126x_CAL_IMG_863_MHZ_2); + else if (centre > 770.0) + calibrateImage(RH_SX126x_CAL_IMG_779_MHZ_1, RH_SX126x_CAL_IMG_779_MHZ_2); + else if (centre > 460.0) + calibrateImage(RH_SX126x_CAL_IMG_470_MHZ_1, RH_SX126x_CAL_IMG_470_MHZ_2); + else + calibrateImage(RH_SX126x_CAL_IMG_430_MHZ_1, RH_SX126x_CAL_IMG_430_MHZ_2); + } + + // Frf = FRF / FSTEP + uint32_t frf = (centre * 1000000.0) / RH_SX126x_FSTEP; + + uint8_t setting[] = + {static_cast(frf >> 24), + static_cast(frf >> 16), + static_cast(frf >> 8), + static_cast(frf)}; + return sendCommand(RH_SX126x_CMD_SET_RF_FREQUENCY, setting, sizeof(setting)); +} + +void RH_SX126x::setModeIdle() +{ + if (_mode != RHModeIdle) + { + modeWillChange(RHModeIdle); + setStandby(RH_SX126x_STANDBY_RC); + _mode = RHModeIdle; + } +} + +bool RH_SX126x::sleep() +{ + if (_mode != RHModeSleep) + { + modeWillChange(RHModeSleep); + setSleep(RH_SX126x_SLEEP_START_WARM); + _mode = RHModeSleep; + } + return true; +} + +void RH_SX126x::setModeRx() +{ + if (_mode != RHModeRx) + { + modeWillChange(RHModeRx); + setPacketParametersLoRa(RH_SX126x_MAX_PAYLOAD_LEN); + setRx(RH_SX126x_RX_TIMEOUT_NONE); // Timeout 0 + _mode = RHModeRx; + } +} + +void RH_SX126x::setModeTx() +{ + if (_mode != RHModeTx) + { + modeWillChange(RHModeTx); + setTx(RH_SX126x_RX_TIMEOUT_NONE); // Timeout 0 + // Expect to be busy for about 0.5ms + _mode = RHModeTx; + } +} + +// Sets registers from a canned modem configuration structure +bool RH_SX126x::setModemRegisters(const ModemConfig* config) +{ + _packetType = config->packetType; + switch (_packetType) + { + case PacketTypeLoRa: + sendCommand(RH_SX126x_CMD_SET_PKT_TYPE, RH_SX126x_PACKET_TYPE_LORA); + break; + + case PacketTypeGFSK: + sendCommand(RH_SX126x_CMD_SET_PKT_TYPE, RH_SX126x_PACKET_TYPE_GFSK); + break; + } + return setModulationParameters( + config->p1, + config->p2, + config->p3, + config->p4, + config->p5, + config->p6, + config->p7, + config->p8); +} + +// Set one of the canned FSK Modem configs +// Returns true if its a valid choice +bool RH_SX126x::setModemConfig(ModemConfigChoice index) +{ + if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig))) + return false; + + // Get it out of PROG_MEM + ModemConfig cfg; + memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(RH_SX126x::ModemConfig)); + return setModemRegisters(&cfg); +} + +void RH_SX126x::setPreambleLength(uint16_t bytes) +{ + _preambleLength = bytes; +} + +bool RH_SX126x::isChannelActive() +{ + // Set mode RHModeCad + if (_mode != RHModeCad) + { + modeWillChange(RHModeCad); + setCad(); + _mode = RHModeCad; + } + + while (_mode == RHModeCad) + YIELD; + + return _cad; +} + +int RH_SX126x::lastSNR() +{ + return _lastSNR; +} + +////////////////////////////////////////////////// +// Specialised SPI routines. +// This device has slightly unusual SPI behaviour: including no RH_SPI_WRITE_MASK, so need to rewrite +// most SPI access functions + +bool RH_SX126x::waitUntilNotBusy() +{ + uint8_t busy_timeout_cnt = 0; + if (_busyPin == RH_INVALID_PIN) + return false; + + while (digitalRead(_busyPin)) + { + delay(1); + busy_timeout_cnt++; + + // Need to wait up to 150us when in sleep mode, see table 8-1 + if (busy_timeout_cnt > 10) // Should be configurable + { + Serial.println("ERROR: waitUntilNotBusy TIMEOUT"); + return false; + } + } + return true; // OK +} + +uint8_t RH_SX126x::getStatus() +{ + waitUntilNotBusy(); + uint8_t status; + ATOMIC_BLOCK_START; + beginTransaction(); + _spi.transfer(RH_SX126x_CMD_GET_STATUS); + status = _spi.transfer(RH_SX126x_CMD_NOP); + endTransaction(); + ATOMIC_BLOCK_END; + return status; +} + +bool RH_SX126x::sendCommand(uint8_t command, uint8_t data[], uint8_t len) +{ +#if 0 + Serial.print("sendCommand "); + Serial.print(command, HEX); + Serial.print(" "); + for (uint8_t i = 0; i < len; i++) + { + Serial.print(data[i], HEX); + Serial.print(" "); + } + Serial.println(""); +#endif + + ATOMIC_BLOCK_START; + beginTransaction(); + waitUntilNotBusy(); + _spi.transfer(command); + for (uint8_t i = 0; i < len; i++) + _spi.transfer(data[i]); + endTransaction(); + ATOMIC_BLOCK_END; + + return true; +} + +// Single value command +bool RH_SX126x::sendCommand(uint8_t command, uint8_t value) +{ + uint8_t data = value; + return sendCommand(command, &data, sizeof(data)); +} + +bool RH_SX126x::sendCommand(uint8_t command) +{ + ATOMIC_BLOCK_START; + beginTransaction(); + waitUntilNotBusy(); + _spi.transfer(command); + endTransaction(); + ATOMIC_BLOCK_END; + return true; +} + +bool RH_SX126x::getCommand(uint8_t command, uint8_t data[], uint8_t len) +{ + ATOMIC_BLOCK_START; + beginTransaction(); + waitUntilNotBusy(); + _spi.transfer(command); + _spi.transfer(0); // Wait for data + for (uint8_t i = 0; i < len; i++) + data[i] = _spi.transfer(0); + endTransaction(); + ATOMIC_BLOCK_END; + return true; +} + +bool RH_SX126x::readRegisters(uint16_t address, uint8_t data[], uint8_t len) +{ + ATOMIC_BLOCK_START; + beginTransaction(); + waitUntilNotBusy(); + _spi.transfer(RH_SX126x_CMD_READ_REGISTER); + _spi.transfer(static_cast(address >> 8)); + _spi.transfer(static_cast(address)); + _spi.transfer(RH_SX126x_CMD_NOP); // Wait for data + + for (uint8_t i = 0; i < len; i++) + data[i] = _spi.transfer(0); + endTransaction(); + ATOMIC_BLOCK_END; + return true; +} + +uint8_t RH_SX126x::readRegister(uint16_t address) +{ + uint8_t data = 0; + readRegisters(address, &data, 1); + return data; +} + +bool RH_SX126x::writeRegisters(uint16_t address, uint8_t data[], uint8_t len) +{ +#if 0 + Serial.print("writeRegisters "); + Serial.print(address, HEX); + Serial.print(" "); + for (uint8_t i = 0; i < len; i++) + { + Serial.print(data[i], HEX); + Serial.print(" "); + } + Serial.println(""); +#endif + + ATOMIC_BLOCK_START; + beginTransaction(); + waitUntilNotBusy(); + _spi.transfer(RH_SX126x_CMD_WRITE_REGISTER); + _spi.transfer(static_cast(address >> 8)); + _spi.transfer(static_cast(address)); + for (uint8_t i = 0; i < len; i++) + _spi.transfer(data[i]); + endTransaction(); + ATOMIC_BLOCK_END; + return true; +} + +bool RH_SX126x::writeRegister(uint16_t address, uint8_t data) +{ + uint8_t d = data; // Single register bye to write + return writeRegisters(address, &d, 1); +} + + +bool RH_SX126x::writeBuffer(uint8_t offset, const uint8_t data[], uint8_t len) +{ + // A bit different to sendCommand because of offset + ATOMIC_BLOCK_START; + beginTransaction(); + waitUntilNotBusy(); + _spi.transfer(RH_SX126x_CMD_WRITE_BUFFER); + _spi.transfer(offset); + for (uint8_t i = 0; i < len; i++) + _spi.transfer(data[i]); + endTransaction(); + ATOMIC_BLOCK_END; + return true; +} + +bool RH_SX126x::writeBuffer(uint8_t offset, const char* text) +{ + return writeBuffer(offset, (uint8_t*) text, strlen(text)); +} + +bool RH_SX126x::readBuffer(uint8_t offset, uint8_t data[], uint8_t len) +{ + // This is a bit different from getCommand, because of offset + ATOMIC_BLOCK_START; + beginTransaction(); + waitUntilNotBusy(); + _spi.transfer(RH_SX126x_CMD_READ_BUFFER); + _spi.transfer(offset); + _spi.transfer(RH_SX126x_CMD_NOP); // Wait for data + for (uint8_t i = 0; i < len; i++) + data[i] = _spi.transfer(0); + endTransaction(); + ATOMIC_BLOCK_END; + return true; +} + +bool RH_SX126x::setPaConfig(uint8_t paDutyCycle, uint8_t hpMax, uint8_t deviceSel, uint8_t paLut) +{ + uint8_t settings[] = {paDutyCycle, hpMax, deviceSel, paLut}; + return sendCommand(RH_SX126x_CMD_SET_PA_CFG, settings, sizeof(settings)); +} + +bool RH_SX126x::setTxParams(uint8_t power, uint8_t rampTime) +{ + uint8_t settings[] = {power, rampTime}; + return sendCommand(RH_SX126x_CMD_SET_TX_PARAMS, settings, sizeof(settings)); +} + +bool RH_SX126x::setTxPower(int8_t power) +{ + // The device may have 2 transmitter power amps (PAs), low power and high power + // However, depending on the specific model and how it is connected, one or the other might not be available + // so we depend on configuration to tell what PAs are available + // STM32WLE5JC has 2, but perhaps only 1 is connected to the antenna, depending on the actual circuit + // SX1261 only has low power + // SX1262 only has high power + // SX1268 only has high power + bool lp_supported = findRadioPinConfigEntry(RadioPinConfigMode_TX_LOW_POWER) != nullptr; + bool hp_supported = findRadioPinConfigEntry(RadioPinConfigMode_TX_HIGH_POWER) != nullptr; + bool useHP = false; // Whether we are to use the high or low power amp + + // REVISIT: needs more work for reduced powers + // Depends on if 1261 or 1262 or 1268 + if (hp_supported && lp_supported) + { + // -17 to +22 dBm + power = constrain(power, -17, 22); + if (power > 14) + useHP = true; + else + useHP = false; + } + else if (!hp_supported && lp_supported) + { + // -17 to +15 dBm + power = constrain(power, -17, 15); + useHP = false; + } + else // Assume only HP is available + { + // -9 to +22 dBm + power = constrain(power, -9, 22); + useHP = true; + } + + // Adjust for optimal settings per Table 13-1 + // CAUTION: needs to change for SX1268 + if (useHP) + { + // CAUTION: device power supply needs to be able to provide 3.3V at currents up to 118 mA with high power settings + // -9 to 22 dBm SX1262 + _requiredPAMode = RadioPinConfigMode_TX_HIGH_POWER; + fixPAClamping(true); + switch (power) + { + case 22: + case 21: + setPaConfig(0x04, 0x07, RH_SX126x_PA_CONFIG_DEVICE_SEL_SX1262, RH_SX126x_PA_CONFIG_PA_LUT); + break; + + case 20: + case 19: + case 18: + setPaConfig(0x03, 0x05, RH_SX126x_PA_CONFIG_DEVICE_SEL_SX1262, RH_SX126x_PA_CONFIG_PA_LUT); + power = power + 2; + break; + + case 17: + case 16: + case 15: + setPaConfig(0x02, 0x03, RH_SX126x_PA_CONFIG_DEVICE_SEL_SX1262, RH_SX126x_PA_CONFIG_PA_LUT); + power = power + 5; + break; + + default: + // 14 and less + setPaConfig(0x02, 0x02, RH_SX126x_PA_CONFIG_DEVICE_SEL_SX1262, RH_SX126x_PA_CONFIG_PA_LUT); + power = power + 8; + break; + } + } + else + { + // CAUTION: device power supply needs to be able to provide 3.3V at currents up to 32.5 mA with high power settings + // -17 to 14 dBm SX1262 + _requiredPAMode = RadioPinConfigMode_TX_LOW_POWER; + switch (power) + { + case 15: + setPaConfig(0x06, 0x00, RH_SX126x_PA_CONFIG_DEVICE_SEL_SX1261, RH_SX126x_PA_CONFIG_PA_LUT); + power = 14; + break; + + case 14: + case 13: + case 12: + case 11: + setPaConfig(0x04, 0x00, RH_SX126x_PA_CONFIG_DEVICE_SEL_SX1261, RH_SX126x_PA_CONFIG_PA_LUT); + break; + + default: + // 10 and less + setPaConfig(0x01, 0x00, RH_SX126x_PA_CONFIG_DEVICE_SEL_SX1261, RH_SX126x_PA_CONFIG_PA_LUT); + power = power + 3; + break; + } + } + // SetTxParams with the corrected power level + return setTxParams(power, RH_SX126x_PA_RAMP_200U); // power, RampTime = 200us. REVISIT: should be configurable ramp time? +} + +bool RH_SX126x::printRegisters(uint16_t address, uint8_t count) +{ +#ifdef RH_HAVE_SERIAL + uint8_t buf[256]; + readRegisters(address, buf, count); + Serial.print("registers starting at: "); + Serial.println(address, HEX); + for (uint8_t i = 0; i < count; i++) + { + Serial.println(buf[i], HEX); + } +#endif + return true; +} + +bool RH_SX126x::clearDeviceErrors() +{ + uint8_t data[] = {0x00, 0x00}; + return sendCommand(RH_SX126x_CMD_CLR_DEVICE_ERRORS, data, sizeof(data)); +} + +bool RH_SX126x::setDIO2AsRfSwitchCtrl(bool value) +{ + return sendCommand(RH_SX126x_CMD_SET_DIO2_AS_RF_SWITCH_CTRL, value); +} + +bool RH_SX126x::setRxFallbackMode(uint8_t mode) +{ + return sendCommand(RH_SX126x_CMD_SET_RX_TX_FALLBACK_MODE, mode); +} + +bool RH_SX126x::setModulationParameters(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4, uint8_t p5, uint8_t p6, uint8_t p7, uint8_t p8) +{ + _lorabw500 = (p2 == RH_SX126x_LORA_BW_500_0); // Need to remember this for modulation quality workaround + switch(p2) + { + case RH_SX126x_LORA_BW_7_8: + _bandwidth = 7.8; + break; + case RH_SX126x_LORA_BW_10_4: + _bandwidth = 10.4; + break; + case RH_SX126x_LORA_BW_15_6: + _bandwidth = 15.6; + break; + case RH_SX126x_LORA_BW_20_8: + _bandwidth = 20.8; + break; + case RH_SX126x_LORA_BW_31_25: + _bandwidth = 31.25; + break; + case RH_SX126x_LORA_BW_41_7: + _bandwidth = 41.7; + break; + case RH_SX126x_LORA_BW_62_5: + _bandwidth = 62.5; + break; + case RH_SX126x_LORA_BW_125_0: + _bandwidth = 125.0; + break; + case RH_SX126x_LORA_BW_250_0: + _bandwidth = 250.0; + break; + case RH_SX126x_LORA_BW_500_0: + _bandwidth = 500.0; + break; + default: + _bandwidth = 0; + } + uint8_t params[] = {p1, p2, p3, p4, p5, p6, p7, p8}; + return sendCommand(RH_SX126x_CMD_SET_MODULATION_PARAMS, params, sizeof(params)); +} + +bool RH_SX126x::setModulationParametersLoRa(uint8_t sf, float bw, uint8_t cr, bool ldro) +{ + _packetType = PacketTypeLoRa; + + // Set packet type LoRa. CAUTION: Must be done in STDBY_RC mode (ie in RH idle mode) + sendCommand(RH_SX126x_CMD_SET_PKT_TYPE, RH_SX126x_PACKET_TYPE_LORA); + + // REVISIT: could automatically calculate LDRO based on symbollength = (1 << SF) / BW + _bandwidth = bw; + // sf must be 5 to 12 + // bw must be 0.0 to 510.0 + // cr must be 5 - 8 inclusive (represents 4/5, 4/6, 4/7, 4/8 respectively) + uint8_t bw_div2 = bw / 2 + 0.01; + uint8_t bwsetting = RH_SX126x_LORA_BW_125_0; + switch (bw_div2) + { + case 3: // 7.8: + bwsetting = RH_SX126x_LORA_BW_7_8; + break; + case 5: // 10.4: + bwsetting = RH_SX126x_LORA_BW_10_4; + break; + case 7: // 15.6: + bwsetting = RH_SX126x_LORA_BW_15_6; + break; + case 10: // 20.8: + bwsetting = RH_SX126x_LORA_BW_20_8; + break; + case 15: // 31.25: + bwsetting = RH_SX126x_LORA_BW_31_25; + break; + case 20: // 41.7: + bwsetting = RH_SX126x_LORA_BW_41_7; + break; + case 31: // 62.5: + bwsetting = RH_SX126x_LORA_BW_62_5; + break; + case 62: // 125.0: + bwsetting = RH_SX126x_LORA_BW_125_0; + break; + case 125: // 250.0 + bwsetting = RH_SX126x_LORA_BW_250_0; + break; + case 250: // 500.0 + bwsetting = RH_SX126x_LORA_BW_500_0; + break; + } + return setModulationParameters(sf, bwsetting, cr - 4, ldro, 0, 0, 0, 0); +} + + +bool RH_SX126x::setModulationParametersGFSK(uint32_t br, uint8_t sh, uint8_t rxBw, uint32_t freqDev) +{ + _packetType = PacketTypeGFSK; + return false; +} + +bool RH_SX126x::calibrate(uint8_t calib_param) +{ + return sendCommand(RH_SX126x_CMD_CALIBRATE, calib_param); +} + +bool RH_SX126x::calibrateImage(uint8_t f1, uint8_t f2) +{ + uint8_t frequencies[] = {f1, f2}; + return sendCommand(RH_SX126x_CMD_CALIBRATE_IMAGE, frequencies, sizeof(frequencies)); +} + +bool RH_SX126x::setLoRaSyncWord(uint16_t sync) +{ + uint8_t sync_word[] = {static_cast(sync >> 8), static_cast(sync)}; + return writeRegisters(RH_SX126x_REG_LR_SYNCWORD, sync_word, sizeof(sync_word)); +} + +bool RH_SX126x::setOCPConfiguration(uint8_t setting) +{ + return writeRegister(RH_SX126x_REG_OCP, setting); +} + +bool RH_SX126x::setDIO3AsTcxoCtrl(uint8_t voltage, uint32_t delay) +{ + uint8_t settings[] = {voltage, + static_cast(delay >> 16), + static_cast(delay >> 8), + static_cast(delay)}; + return sendCommand(RH_SX126x_CMD_SET_DIO3_AS_TCXO_CTRL, settings, sizeof(settings)); +} + +bool RH_SX126x::setTCXO(float voltage, uint32_t delay) +{ + uint8_t voltageValue = RH_SX126x_DIO3_OUTPUT_1_7; + + if (fabs(voltage - 1.6) <= 0.001) + voltageValue = RH_SX126x_DIO3_OUTPUT_1_6; + else if (fabs(voltage - 1.7) <= 0.001) + voltageValue = RH_SX126x_DIO3_OUTPUT_1_7; + else if (fabs(voltage - 1.8) <= 0.001) + voltageValue = RH_SX126x_DIO3_OUTPUT_1_8; + else if (fabs(voltage - 2.2) <= 0.001) + voltageValue = RH_SX126x_DIO3_OUTPUT_2_2; + else if (fabs(voltage - 2.4) <= 0.001) + voltageValue = RH_SX126x_DIO3_OUTPUT_2_4; + else if (fabs(voltage - 2.7) <= 0.001) + voltageValue = RH_SX126x_DIO3_OUTPUT_2_7; + else if (fabs(voltage - 3.0) <= 0.001) + voltageValue = RH_SX126x_DIO3_OUTPUT_3_0; + else if (fabs(voltage - 3.3) <= 0.001) + voltageValue = RH_SX126x_DIO3_OUTPUT_3_3; + + uint32_t delayValue = (float)delay / 15.625; + return setDIO3AsTcxoCtrl(voltageValue, delayValue); +} + +bool RH_SX126x::setPacketParams(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4, uint8_t p5, uint8_t p6, uint8_t p7, uint8_t p8, uint8_t p9) +{ + uint8_t settings[] = {p1, p2, p3, p4, p5, p6, p7, p8, p9}; + return sendCommand(RH_SX126x_CMD_SET_PKT_PARAMS, settings, sizeof(settings)); +} + +bool RH_SX126x::setPacketParametersLoRa(uint8_t payload_length) +{ + _packetType = PacketTypeLoRa; + // IQ polarity fix per SX1262_datasheet.pdf section 15.4 + uint8_t value = readRegister(RH_SX126x_REG_IQ_POLARITY); + if (_invertIQ) + value = value & ~0x04; + else + value = value | 0x04; + writeRegister(RH_SX126x_REG_IQ_POLARITY, value); + + // _preambleLength, fixedlength = false, _enableCRC, invertIQ + return setPacketParams(0, _preambleLength, RH_SX126x_LORA_PACKET_VARIABLE, payload_length, _enableCRC, _invertIQ, 0, 0, 0); +} + +bool RH_SX126x::setBufferBaseAddress(uint8_t txbase, uint8_t rxbase) +{ + uint8_t settings[] = {txbase, rxbase}; + return sendCommand(RH_SX126x_CMD_SET_BUFFER_BASE_ADDRESS, settings, sizeof(settings)); +} + +bool RH_SX126x::setSleep(uint8_t config) +{ + setRadioPinsForMode(RadioPinConfigMode_IDLE); + uint8_t sleep_config = config; + return sendCommand(RH_SX126x_CMD_SET_SLEEP, &sleep_config, sizeof(sleep_config)); +} + +bool RH_SX126x::setStandby(uint8_t config) +{ + setRadioPinsForMode(RadioPinConfigMode_IDLE); + uint8_t standby_config = config; // 0 == STDBY_RC, 1 == STDBY_XOSC + return sendCommand(RH_SX126x_CMD_SET_STANDBY, &standby_config, sizeof(standby_config)); +} + +bool RH_SX126x::setTx(uint32_t timeout) +{ + // Need workaround for Modulation Qulity if LoRa BW is 500kHz + // Per SX1262_datasheet.pdf section 15.1 + if (_packetType == PacketTypeLoRa) + { + if (_lorabw500) + { + uint8_t value = readRegister(RH_SX126x_REG_TX_MODULATION); + value &= 0xfb; + writeRegister(RH_SX126x_REG_TX_MODULATION, value); + } + else + { + uint8_t value = readRegister(RH_SX126x_REG_TX_MODULATION); + value |= 0x04; + writeRegister(RH_SX126x_REG_TX_MODULATION, value); + } + } + + setRadioPinsForMode(_requiredPAMode); + uint8_t settings[] = + {static_cast(timeout >> 16), + static_cast(timeout >> 8), + static_cast(timeout)}; + return sendCommand(RH_SX126x_CMD_SET_TX, settings, sizeof(settings)); +} + +bool RH_SX126x::setTxContinuous() +{ + setRadioPinsForMode(_requiredPAMode); + return sendCommand(RH_SX126x_CMD_SET_TX_CONTINUOUS_WAVE); +} + +bool RH_SX126x::setRx(uint32_t timeout) +{ + setBufferBaseAddress(0, 0); + setRadioPinsForMode(RadioPinConfigMode_RX); + uint8_t settings[] = + {static_cast(timeout >> 16), + static_cast(timeout >> 8), + static_cast(timeout)}; + // Sigh, on some chips it doesnt enter RX mode on the first try, so try again. + // Why? + bool status = sendCommand(RH_SX126x_CMD_SET_RX, settings, sizeof(settings)); + status = sendCommand(RH_SX126x_CMD_SET_RX, settings, sizeof(settings)); + return status; +} + +bool RH_SX126x::setCad() +{ + // REVISIT: CAD is not yet working. See comments in datasheet: + + // Choosing the right value is not easy and the values selected must + // be carefully tested to ensure a good detection at sensitivity level, and also to limit the number of false detections. + // Application note AN1200.48 provides guidance for the selection of these parameters. + // See Semtech AN1200.48, page 41. + return false; + if (_packetType == PacketTypeLoRa) + { + // REVISIT: detPeak depends on spreading factor + uint8_t cadparams[] = {RH_SX126x_CAD_ON_2_SYMB, 22, RH_SX126x_CAD_PARAM_DET_MIN, RH_SX126x_CAD_GOTO_STDBY, 0, 0, 0}; + sendCommand(RH_SX126x_CMD_SET_CAD_PARAMS, cadparams, sizeof(cadparams)); + return sendCommand(RH_SX126x_CMD_SET_CAD); // Only available in LoRa mode + } + else + return false; +} + +bool RH_SX126x::setRxBoostMode(bool boost, bool retain) +{ + if (boost) + writeRegister(RH_SX126x_REG_RXGAIN, RH_SX126x_RX_GAIN_BOOSTED); + else + writeRegister(RH_SX126x_REG_RXGAIN, RH_SX126x_RX_GAIN_POWER_SAVING); + + if (retain) + { + // Include this register in the retention memory + uint8_t settings[] = {0x01, 0x08, 0xac}; + writeRegisters(RH_SX126x_REG_RETENTION_LIST_BASE_ADDRESS, settings, sizeof(settings)); + } + return true; +} + +bool RH_SX126x::setRegulatorMode(uint8_t mode) +{ + return sendCommand(RH_SX126x_CMD_SET_REGULATOR_MODE, mode); +} + +bool RH_SX126x::setDioIrqParams(uint16_t irqmask, uint16_t dio1mask, uint16_t dio2mask, uint16_t dio3mask) +{ + uint8_t settings[] = {static_cast(irqmask >> 8), static_cast(irqmask), + static_cast(dio1mask >> 8), static_cast(dio1mask), + static_cast(dio2mask >> 8), static_cast(dio2mask), + static_cast(dio3mask >> 8), static_cast(dio3mask)}; + return sendCommand(RH_SX126x_CMD_SET_DIO_IRQ_PARAMS, settings, sizeof(settings)); +} + +bool RH_SX126x::clearIrqStatus(uint16_t mask) +{ + uint8_t settings[] = {static_cast(mask >> 8), static_cast(mask)}; + return sendCommand(RH_SX126x_CMD_CLR_IRQ_STATUS, settings, sizeof(settings)); +} + +uint16_t RH_SX126x::getIrqStatus() +{ + uint8_t status[2]; + getCommand(RH_SX126x_CMD_GET_IRQ_STATUS, status, sizeof(status)); + return ((uint16_t)status[0] << 8) | status[1]; +} + +uint8_t RH_SX126x::getPacketType() +{ + uint8_t ptype[1]; + getCommand(RH_SX126x_CMD_GET_PKT_TYPE, ptype, sizeof(ptype)); + return ptype[0]; +} + +void RH_SX126x::setInvertIQ(bool invertIQ) +{ + _invertIQ = invertIQ; +} + +bool RH_SX126x::fixPAClamping(bool enable) +{ + // Per SX1262_datasheet.pdf Rev 1.2 section 15.2 + uint8_t clamp; + clamp = readRegister(RH_SX126x_REG_TX_CLAMP_CFG); + if (enable) + clamp |= 0x1E; + else + clamp = (clamp & ~0x1E) | 0x08; + return writeRegister(RH_SX126x_REG_TX_CLAMP_CFG, clamp); +} + +bool RH_SX126x::setRadioPinsForMode(RadioPinConfigMode mode) +{ + if (!_radioPinConfig) + return false; + + RadioPinConfigEntry* entry = findRadioPinConfigEntry(mode); + if (!entry) + return false; + + for (uint8_t i = 0; i < RH_SX126x_MAX_RADIO_CONTROL_PINS ; i++) + { +#if 0 + Serial.print("set pin "); + Serial.print(_radioPinConfig->pinNumber[i]); + Serial.print(" to "); + Serial.print(entry->pinState[i]); + Serial.println(""); +#endif + if (_radioPinConfig->pinNumber[i] != RH_INVALID_PIN) + digitalWrite(_radioPinConfig->pinNumber[i], entry->pinState[i]); + } + return true; +} + +void RH_SX126x::setRadioPinConfig(RadioPinConfig* config) +{ + for (uint8_t i = 0; i < RH_SX126x_MAX_RADIO_CONTROL_PINS; i++) + { + if (config && config->pinNumber[i] != RH_INVALID_PIN) + pinMode(config->pinNumber[i], OUTPUT); + } + _radioPinConfig = config; +} + +// Can be overridden +RH_SX126x::RadioPinConfigEntry* RH_SX126x::findRadioPinConfigEntry(RadioPinConfigMode mode) +{ + // Sigh, linear search, max of 5 entries + if (!_radioPinConfig) + return nullptr; // No configurations, Not found + + for (uint8_t i = 0; i < RH_SX126x_MAX_RADIO_PIN_CONFIG_MODES; i++) + { + if (_radioPinConfig->configState[i].mode == RadioPinConfigMode_EOT) + return nullptr; // End of table, Not found + else if (_radioPinConfig->configState[i].mode == mode) + return &_radioPinConfig->configState[i]; // Found the one we want + } + return nullptr; // Table too big, Not found +} + +float RH_SX126x::getFrequencyError() +{ + // check packetType + + if( _packetType != PacketTypeLoRa) + return(0.0); + + // read the raw frequency error register values + uint8_t regBytes[3] = {0}; + regBytes[0] = readRegister(RH_SX126x_REG_FREQ_ERROR); + regBytes[1] = readRegister(RH_SX126x_REG_FREQ_ERROR + 1); + regBytes[2] = readRegister(RH_SX126x_REG_FREQ_ERROR + 2); + uint32_t tmp = ((uint32_t) regBytes[0] << 16) | ((uint32_t) regBytes[1] << 8) | regBytes[2]; + tmp &= 0x0FFFFF; + + float error = 0; + + // check the first bit + if (tmp & 0x80000) { + // frequency error is negative + tmp |= (uint32_t) 0xFFF00000; + tmp = ~tmp + 1; + error = 1.55 * (float) tmp / (1600.0 / _bandwidth) * -1.0; + } + else + { + error = 1.55 * (float) tmp / (1600.0 / _bandwidth); + } + return(error); +} diff --git a/RH_SX126x.h b/RH_SX126x.h new file mode 100644 index 0000000..3a8c193 --- /dev/null +++ b/RH_SX126x.h @@ -0,0 +1,1301 @@ +// SX126X.h +// +// Definitions for the Semtech SX126X series of LoRa capable radios +// https://wiki.seeedstudio.com/LoRa_E5_mini/ +// https://www.rfsolutions.co.uk/downloads/1537522406DS_SX1261-2_V1.1_SEMTECH.pdf +// https://cdn.sparkfun.com/assets/6/b/5/1/4/SX1262_datasheet.pdf +// https://files.seeedstudio.com/products/317990687/res/LoRa-E5+module+datasheet_V1.0.pdf +// https://forum.seeedstudio.com/t/lora-e5-register-settings-for-oscillators/262635 +// file:///home/mikem/Downloads/es0506-stm32wle5xx-stm32wle4xx-device-errata-stmicroelectronics.pdf +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2023 Mike McCauley +// + +#ifndef RH_SX126x_h +#define RH_SX126x_h + +#include + +// This is the maximum number of interrupts the driver can support +// Most Arduinos can handle 2, Megas can handle more +#define RH_SX126x_NUM_INTERRUPTS 3 + +// Max number of octets the LORA Rx/Tx FIFO can hold +#define RH_SX126x_FIFO_SIZE 255 + +// This is the maximum number of bytes that can be carried by the LORA. +// We use some for headers, keeping fewer for RadioHead messages +#define RH_SX126x_MAX_PAYLOAD_LEN RH_SX126x_FIFO_SIZE + +// The length of the headers we add. +// The headers are inside the LORA's payload +#define RH_SX126x_HEADER_LEN 4 + +// This is the maximum message length that can be supported by this driver. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS +#ifndef RH_SX126x_MAX_MESSAGE_LEN + #define RH_SX126x_MAX_MESSAGE_LEN (RH_SX126x_MAX_PAYLOAD_LEN - RH_SX126x_HEADER_LEN) +#endif + +// Radio chip internal crystal frequency +#define RH_SX126x_XTAL_FREQ 32000000.0 + +// The Frequency Synthesizer step = RH_SX126x_XTAL_FREQ / 2^^25 +#define RH_SX126x_FSTEP (RH_SX126x_XTAL_FREQ / 33554432) + +// Operational Modes Functions +#define RH_SX126x_CMD_NOP 0x00 +#define RH_SX126x_CMD_SET_SLEEP 0x84 +#define RH_SX126x_CMD_SET_STANDBY 0x80 +#define RH_SX126x_CMD_SET_FS 0xC1 +#define RH_SX126x_CMD_SET_TX 0x83 +#define RH_SX126x_CMD_SET_RX 0x82 +#define RH_SX126x_CMD_SET_STOP_TIMER_ON_PREAMBLE 0x9F +#define RH_SX126x_CMD_SET_RX_DUTY_CYCLE 0x94 +#define RH_SX126x_CMD_SET_CAD 0xC5 +#define RH_SX126x_CMD_SET_TX_CONTINUOUS_WAVE 0xD1 +#define RH_SX126x_CMD_SET_TX_INFINITE_PREAMBLE 0xD2 +#define RH_SX126x_CMD_SET_REGULATOR_MODE 0x96 +#define RH_SX126x_CMD_CALIBRATE 0x89 +#define RH_SX126x_CMD_CALIBRATE_IMAGE 0x98 +#define RH_SX126x_CMD_SET_PA_CFG 0x95 +#define RH_SX126x_CMD_SET_RX_TX_FALLBACK_MODE 0x93 + +// Registers and buffer Access +#define RH_SX126x_CMD_WRITE_REGISTER 0x0D +#define RH_SX126x_CMD_READ_REGISTER 0x1D +#define RH_SX126x_CMD_WRITE_BUFFER 0x0E +#define RH_SX126x_CMD_READ_BUFFER 0x1E + +// DIO and IRQ Control Functions +#define RH_SX126x_CMD_SET_DIO_IRQ_PARAMS 0x08 +#define RH_SX126x_CMD_GET_IRQ_STATUS 0x12 +#define RH_SX126x_CMD_CLR_IRQ_STATUS 0x02 +#define RH_SX126x_CMD_SET_DIO2_AS_RF_SWITCH_CTRL 0x9D +#define RH_SX126x_CMD_SET_DIO3_AS_TCXO_CTRL 0x97 + +// RF Modulation and Packet-Related Functions +#define RH_SX126x_CMD_SET_RF_FREQUENCY 0x86 +#define RH_SX126x_CMD_SET_PKT_TYPE 0x8A +#define RH_SX126x_CMD_GET_PKT_TYPE 0x11 +#define RH_SX126x_CMD_SET_TX_PARAMS 0x8E +#define RH_SX126x_CMD_SET_MODULATION_PARAMS 0x8B +#define RH_SX126x_CMD_SET_PKT_PARAMS 0x8C +#define RH_SX126x_CMD_SET_CAD_PARAMS 0x88 +#define RH_SX126x_CMD_SET_BUFFER_BASE_ADDRESS 0x8F +#define RH_SX126x_CMD_SET_LORA_SYMB_NUM_TIMEOUT 0xA0 + +// Communication Status Information +#define RH_SX126x_CMD_GET_STATUS 0xC0 +#define RH_SX126x_CMD_GET_RX_BUFFER_STATUS 0x13 +#define RH_SX126x_CMD_GET_PKT_STATUS 0x14 +#define RH_SX126x_CMD_GET_RSSI_INST 0x15 +#define RH_SX126x_CMD_GET_STATS 0x10 +#define RH_SX126x_CMD_RESET_STATS 0x00 + +// Miscellaneous +#define RH_SX126x_CMD_GET_DEVICE_ERRORS 0x17 +#define RH_SX126x_CMD_CLR_DEVICE_ERRORS 0x07 + +// Registers + +// Base address of the register retention list, 3 bytes +#define RH_SX126x_REG_RETENTION_LIST_BASE_ADDRESS 0x029F + +#define RH_SX126x_REG_VERSION_STRING 0x0320 +#define RH_SX126x_REG_HOPPING_ENABLE 0x0385 +#define RH_SX126x_REG_LR_FHSS_PACKET_LENGTH 0x0386 +#define RH_SX126x_REG_LR_FHSS_NUM_HOPPING_BLOCKS 0x0387 +#define RH_SX126x_REG_LR_FHSS_NUM_SYMBOLS_FREQX_MSB(X) (0x0388 + (X)*6) +#define RH_SX126x_REG_LR_FHSS_NUM_SYMBOLS_FREQX_LSB(X) (0x0389 + (X)*6) +#define RH_SX126x_REG_LR_FHSS_FREQX_0(X) (0x038A + (X)*6) +#define RH_SX126x_REG_LR_FHSS_FREQX_1(X) (0x038B + (X)*6) +#define RH_SX126x_REG_LR_FHSS_FREQX_2(X) (0x038C + (X)*6) +#define RH_SX126x_REG_LR_FHSS_FREQX_3(X) (0x038D + (X)*6) +#define RH_SX126x_REG_SPECTRAL_SCAN_RESULT 0x0401 + +// Output disable +#define RH_SX126x_REG_OUT_DIS_REG 0x0580 +#define RH_SX126x_REG_OUT_DIS_REG_DIO3_POS ( 3U ) +#define RH_SX126x_REG_OUT_DIS_REG_DIO3_MASK ( 0x01UL << SX126X_REG_OUT_DIS_REG_DIO3_POS ) + +#define RH_SX126x_REG_DIOX_DRIVE_STRENGTH 0x0582 + +// Input enable +#define RH_SX126x_REG_IN_EN_REG 0x0583 +#define RH_SX126x_REG_IN_EN_REG_DIO3_POS ( 3U ) +#define RH_SX126x_REG_IN_EN_REG_DIO3_MASK ( 0x01UL << SX126X_REG_IN_EN_REG_DIO3_POS ) + +#define RH_SX126x_REG_DIOX_PULLUP 0x0584 +#define RH_SX126x_REG_DIOX_PULLDOWN 0x0585 + +// TX bitbang B +#define RH_SX126x_REG_BITBANG_B_REG 0x0587 +#define RH_SX126x_REG_BITBANG_B_REG_ENABLE_POS ( 0U ) +#define RH_SX126x_REG_BITBANG_B_REG_ENABLE_MASK ( 0x0FUL << SX126X_REG_BITBANG_B_REG_ENABLE_POS ) +#define RH_SX126x_REG_BITBANG_B_REG_ENABLE_VAL ( 0x0CUL << SX126X_REG_BITBANG_B_REG_ENABLE_POS ) + +#define RH_SX126x_REG_PATCH_UPDATE_ENABLE 0x0610 + +// TX bitbang A +#define RH_SX126x_REG_BITBANG_A_REG 0x0680 +#define RH_SX126x_REG_BITBANG_A_REG_ENABLE_POS ( 4U ) +#define RH_SX126x_REG_BITBANG_A_REG_ENABLE_MASK ( 0x07UL << SX126X_REG_BITBANG_A_REG_ENABLE_POS ) +#define RH_SX126x_REG_BITBANG_A_REG_ENABLE_VAL ( 0x01UL << SX126X_REG_BITBANG_A_REG_ENABLE_POS ) + +// The address of the register holding the first byte defining the whitening seed. 2 bytes +#define RH_SX126x_REG_WHITSEEDBASEADDRESS 0x06B8 + +// RX/TX payload length +#define RH_SX126x_REG_RXTX_PAYLOAD_LEN 0x06BB + +// The address of the register holding the first byte defining the CRC seed. 2 bytes +#define RH_SX126x_REG_CRCSEEDBASEADDRESS 0x06BC + +// The address of the register holding the first byte defining the CRC polynomial. 2 bytes +#define RH_SX126x_REG_CRCPOLYBASEADDRESS 0x06BE + +// The addresses of the registers holding SyncWords values, 8 bytes +#define RH_SX126x_REG_SYNCWORDBASEADDRESS 0x06C0 + +// GFSK node address +// Reset value is 0x00 +#define RH_SX126x_REG_GFSK_NODE_ADDRESS 0x06CD + +// GFSK broadcast address +// Reset value is 0x00 +#define RH_SX126x_REG_GFSK_BROADCAST_ADDRESS 0x06CE + +#define RH_SX126x_REG_PAYLOAD_LENGTH 0x0702 +#define RH_SX126x_REG_PACKET_PARAMS 0x0704 + +// Number of symbols given as SX126X_REG_LR_SYNCH_TIMEOUT[7:3] * 2 ^ (2*SX126X_REG_LR_SYNCH_TIMEOUT[2:0] + 1) +#define RH_SX126x_REG_LR_SYNCH_TIMEOUT 0x0706 + +// WORKAROUND - Optimizing the Inverted IQ Operation, see DS_SX1261-2_V1.2 datasheet chapter 15.4 +#define RH_SX126x_REG_IQ_POLARITY 0x0736 + +// The addresses of the register holding LoRa Modem SyncWord value, 2 bytes +// 0x1424: LoRaWAN private network, +// 0x3444: LoRaWAN public network +#define RH_SX126x_REG_LR_SYNCWORD 0x0740 + +// The address of the register holding the coding rate configuration extracted from a received LoRa header +#define RH_SX126x_REG_LR_HEADER_CR 0x0749 +#define RH_SX126x_REG_LR_HEADER_CR_POS ( 4U ) +#define RH_SX126x_REG_LR_HEADER_CR_MASK ( 0x07UL << SX126X_REG_LR_HEADER_CR_POS ) + +// The address of the register holding the CRC configuration extracted from a received LoRa header +#define RH_SX126x_REG_FREQ_ERROR 0x076B +#define RH_SX126x_REG_LR_HEADER_CRC 0x076B +#define RH_SX126x_REG_LR_HEADER_CRC_POS ( 4U ) +#define RH_SX126x_REG_LR_HEADER_CRC_MASK ( 0x01UL << SX126X_REG_LR_HEADER_CRC_POS ) + +#define RH_SX126x_REG_SPECTRAL_SCAN_STATUS 0x07CD + +// RX address pointer +#define RH_SX126x_REG_RX_ADDRESS_POINTER 0x0803 + +// The address of the register giving a 32-bit random number, 4 bytes +#define RH_SX126x_REG_RNGBASEADDRESS 0x0819 + +// WORKAROUND - Modulation Quality with 500 kHz LoRa Bandwidth, see DS_SX1261-2_V1.2 datasheet chapter 15.1 +#define RH_SX126x_REG_TX_MODULATION 0x0889 + +#define RH_SX126x_REG_RF_FREQUENCY_0 0x088B +#define RH_SX126x_REG_RF_FREQUENCY_1 0x088C +#define RH_SX126x_REG_RF_FREQUENCY_2 0x088D +#define RH_SX126x_REG_RF_FREQUENCY_3 0x088E + +#define RH_SX126x_REG_RSSI_AVG_WINDOW 0x089B + +// The address of the register holding RX Gain value +// 0x94: power saving, +// 0x96: rx boosted +#define RH_SX126x_REG_RXGAIN 0x08AC + +// WORKAROUND - Better resistance to antenna mismatch, see DS_SX1261-2_V1.2 datasheet chapter 15.2 +#define RH_SX126x_REG_TX_CLAMP_CFG 0x08D8 +#define RH_SX126x_REG_TX_CLAMP_CFG_POS ( 1U ) +#define RH_SX126x_REG_TX_CLAMP_CFG_MASK ( 0x0FUL << SX126X_REG_TX_CLAMP_CFG_POS ) + +// The address of the register used to disable the LNA +#define RH_SX126x_REG_ANA_LNA 0x08E2 + +#define RH_SX126x_REG_LNA_CAP_TUNE_N 0x08E3 +#define RH_SX126x_REG_LNA_CAP_TUNE_P 0x08E4 + +// The address of the register used to disable the mixer +#define RH_SX126x_REG_ANA_MIXER 0x08E5 + +// Set the current max value in the over current protection +#define RH_SX126x_REG_OCP 0x08E7 + +// RTC control +#define RH_SX126x_REG_RTC_CTRL 0x0902 + +// Change the value on the device internal trimming capacitor, 2 bytes +#define RH_SX126x_REG_XTATRIM 0x0911 + +// Value of the trimming cap on XTB pin This register should only be +// changed while the radio is in STDBY_XOSC mode +#define RH_SX126x_REG_DIO3_OUTPUT_VOLTAGE 0x0920 + +// Event clear +#define RH_SX126x_REG_EVT_CLR 0x0944 +#define RH_SX126x_REG_EVT_CLR_TIMEOUT_POS ( 1U ) +#define RH_SX126x_REG_EVT_CLR_TIMEOUT_MASK ( 0x01UL << SX126X_REG_EVT_CLR_TIMEOUT_POS ) + +#define RH_SX126x_REG_PATCH_MEMORY_BASE 0x8000 + +// Values used in commands and registers +// RH_SX126x_CMD_SET_SLEEP +#define RH_SX126x_SLEEP_START_COLD 0b00000000 // sleep mode: cold start, configuration is lost (default) +#define RH_SX126x_SLEEP_START_WARM 0b00000100 // warm start, configuration is retained +#define RH_SX126x_SLEEP_RTC_OFF 0b00000000 // wake on RTC timeout: disabled +#define RH_SX126x_SLEEP_RTC_ON 0b00000001 // enabled + +// RH_SX126x_CMD_SET_STANDBY +#define RH_SX126x_STANDBY_RC 0x00 // standby mode: 13 MHz RC oscillator +#define RH_SX126x_STANDBY_XOSC 0x01 // 32 MHz crystal oscillator + +// RH_SX126x_CMD_SET_RX +#define RH_SX126x_RX_TIMEOUT_NONE 0x000000 // Rx timeout duration: no timeout (Rx single mode) +#define RH_SX126x_RX_TIMEOUT_INF 0xFFFFFF // infinite (Rx continuous mode) + +// RH_SX126x_CMD_SET_TX +#define RH_SX126x_TX_TIMEOUT_NONE 0x000000 // Tx timeout duration: no timeout (Tx single mode) + +// RH_SX126x_CMD_STOP_TIMER_ON_PREAMBLE +#define RH_SX126x_STOP_ON_PREAMBLE_OFF 0x00 // stop timer on: sync word or header (default) +#define RH_SX126x_STOP_ON_PREAMBLE_ON 0x01 // preamble detection + +// RH_SX126x_CMD_SET_REGULATOR_MODE +#define RH_SX126x_REGULATOR_LDO 0x00 // set regulator mode: LDO (default) +#define RH_SX126x_REGULATOR_DC_DC 0x01 // DC-DC + +// RH_SX126x_CMD_CALIBRATE +#define RH_SX126x_CALIBRATE_IMAGE_OFF 0b00000000 // image calibration: disabled +#define RH_SX126x_CALIBRATE_IMAGE_ON 0b01000000 // enabled +#define RH_SX126x_CALIBRATE_ADC_BULK_P_OFF 0b00000000 // ADC bulk P calibration: disabled +#define RH_SX126x_CALIBRATE_ADC_BULK_P_ON 0b00100000 // enabled +#define RH_SX126x_CALIBRATE_ADC_BULK_N_OFF 0b00000000 // ADC bulk N calibration: disabled +#define RH_SX126x_CALIBRATE_ADC_BULK_N_ON 0b00010000 // enabled +#define RH_SX126x_CALIBRATE_ADC_PULSE_OFF 0b00000000 // ADC pulse calibration: disabled +#define RH_SX126x_CALIBRATE_ADC_PULSE_ON 0b00001000 // enabled +#define RH_SX126x_CALIBRATE_PLL_OFF 0b00000000 // PLL calibration: disabled +#define RH_SX126x_CALIBRATE_PLL_ON 0b00000100 // enabled +#define RH_SX126x_CALIBRATE_RC13M_OFF 0b00000000 // 13 MHz RC osc. calibration: disabled +#define RH_SX126x_CALIBRATE_RC13M_ON 0b00000010 // enabled +#define RH_SX126x_CALIBRATE_RC64K_OFF 0b00000000 // 64 kHz RC osc. calibration: disabled +#define RH_SX126x_CALIBRATE_RC64K_ON 0b00000001 // enabled +#define RH_SX126x_CALIBRATE_ALL 0b01111111 // calibrate all blocks + +// RH_SX126x_CMD_CALIBRATE_IMAGE +#define RH_SX126x_CAL_IMG_430_MHZ_1 0x6B +#define RH_SX126x_CAL_IMG_430_MHZ_2 0x6F +#define RH_SX126x_CAL_IMG_470_MHZ_1 0x75 +#define RH_SX126x_CAL_IMG_470_MHZ_2 0x81 +#define RH_SX126x_CAL_IMG_779_MHZ_1 0xC1 +#define RH_SX126x_CAL_IMG_779_MHZ_2 0xC5 +#define RH_SX126x_CAL_IMG_863_MHZ_1 0xD7 +#define RH_SX126x_CAL_IMG_863_MHZ_2 0xDB +#define RH_SX126x_CAL_IMG_902_MHZ_1 0xE1 +#define RH_SX126x_CAL_IMG_902_MHZ_2 0xE9 + +// RH_SX126x_CMD_SET_PA_CONFIG +#define RH_SX126x_PA_CONFIG_HP_MAX 0x07 + +#define RH_SX126x_PA_CONFIG_DEVICE_SEL_SX1261 0x01 +#define RH_SX126x_PA_CONFIG_DEVICE_SEL_SX1262 0x00 + +#define RH_SX126x_PA_CONFIG_PA_LUT 0x01 +#define RH_SX126x_PA_CONFIG_SX1262_8 0x00 + +// RH_SX126x_CMD_SET_RX_TX_FALLBACK_MODE +#define RH_SX126x_RX_TX_FALLBACK_MODE_FS 0x40 // after Rx/Tx go to: FS mode +#define RH_SX126x_RX_TX_FALLBACK_MODE_STDBY_XOSC 0x30 // standby with crystal oscillator +#define RH_SX126x_RX_TX_FALLBACK_MODE_STDBY_RC 0x20 // standby with RC oscillator (default) + +// RH_SX126x_CMD_SET_DIO_IRQ_PARAMS +#define RH_SX126x_IRQ_LR_FHSS_HOP 0b0100000000000000 // PA ramped up during LR-FHSS hop +#define RH_SX126x_IRQ_TIMEOUT 0b0000001000000000 // Rx or Tx timeout +#define RH_SX126x_IRQ_CAD_DETECTED 0b0000000100000000 // channel activity detected +#define RH_SX126x_IRQ_CAD_DONE 0b0000000010000000 // channel activity detection finished +#define RH_SX126x_IRQ_CRC_ERR 0b0000000001000000 // wrong CRC received +#define RH_SX126x_IRQ_HEADER_ERR 0b0000000000100000 // LoRa header CRC error +#define RH_SX126x_IRQ_HEADER_VALID 0b0000000000010000 // valid LoRa header received +#define RH_SX126x_IRQ_SYNC_WORD_VALID 0b0000000000001000 // valid sync word detected +#define RH_SX126x_IRQ_PREAMBLE_DETECTED 0b0000000000000100 // preamble detected +#define RH_SX126x_IRQ_RX_DONE 0b0000000000000010 // packet received +#define RH_SX126x_IRQ_TX_DONE 0b0000000000000001 // packet transmission completed +#define RH_SX126x_IRQ_RX_DEFAULT 0b0000001001100010 // default for Rx (RX_DONE, TIMEOUT, CRC_ERR and HEADER_ERR) +#define RH_SX126x_IRQ_ALL 0b0100001111111111 // all interrupts +#define RH_SX126x_IRQ_NONE 0b0000000000000000 // no interrupts + +// RH_SX126x_CMD_SET_DIO2_AS_RF_SWITCH_CTRL +#define RH_SX126x_DIO2_AS_IRQ 0x00 // DIO2 configuration: IRQ +#define RH_SX126x_DIO2_AS_RF_SWITCH 0x01 // RF switch control + +// RH_SX126x_CMD_SET_DIO3_AS_TCXO_CTRL +#define RH_SX126x_DIO3_OUTPUT_1_6 0x00 // DIO3 voltage output for TCXO: 1.6 V +#define RH_SX126x_DIO3_OUTPUT_1_7 0x01 // 1.7 V +#define RH_SX126x_DIO3_OUTPUT_1_8 0x02 // 1.8 V +#define RH_SX126x_DIO3_OUTPUT_2_2 0x03 // 2.2 V +#define RH_SX126x_DIO3_OUTPUT_2_4 0x04 // 2.4 V +#define RH_SX126x_DIO3_OUTPUT_2_7 0x05 // 2.7 V +#define RH_SX126x_DIO3_OUTPUT_3_0 0x06 // 3.0 V +#define RH_SX126x_DIO3_OUTPUT_3_3 0x07 // 3.3 V + +// RH_SX126x_CMD_SET_PACKET_TYPE +#define RH_SX126x_PACKET_TYPE_GFSK 0x00 // packet type: GFSK +#define RH_SX126x_PACKET_TYPE_LORA 0x01 // LoRa +#define RH_SX126x_PACKET_TYPE_LR_FHSS 0x03 // LR-FHSS + +// RH_SX126x_CMD_SET_TX_PARAMS +#define RH_SX126x_PA_RAMP_10U 0x00 // ramp time: 10 us +#define RH_SX126x_PA_RAMP_20U 0x01 // 20 us +#define RH_SX126x_PA_RAMP_40U 0x02 // 40 us +#define RH_SX126x_PA_RAMP_80U 0x03 // 80 us +#define RH_SX126x_PA_RAMP_200U 0x04 // 200 us +#define RH_SX126x_PA_RAMP_800U 0x05 // 800 us +#define RH_SX126x_PA_RAMP_1700U 0x06 // 1700 us +#define RH_SX126x_PA_RAMP_3400U 0x07 // 3400 us + +// RH_SX126x_CMD_SET_MODULATION_PARAMS +// GFSK bandwidths +#define RH_SX126x_GFSK_RX_BW_4_8 0x1F // 4.8 kHz +#define RH_SX126x_GFSK_RX_BW_5_8 0x17 // 5.8 kHz +#define RH_SX126x_GFSK_RX_BW_7_3 0x0F // 7.3 kHz +#define RH_SX126x_GFSK_RX_BW_9_7 0x1E // 9.7 kHz +#define RH_SX126x_GFSK_RX_BW_11_7 0x16 // 11.7 kHz +#define RH_SX126x_GFSK_RX_BW_14_6 0x0E // 14.6 kHz +#define RH_SX126x_GFSK_RX_BW_19_5 0x1D // 19.5 kHz +#define RH_SX126x_GFSK_RX_BW_23_4 0x15 // 23.4 kHz +#define RH_SX126x_GFSK_RX_BW_29_3 0x0D // 29.3 kHz +#define RH_SX126x_GFSK_RX_BW_39_0 0x1C // 39.0 kHz +#define RH_SX126x_GFSK_RX_BW_46_9 0x14 // 46.9 kHz +#define RH_SX126x_GFSK_RX_BW_58_6 0x0C // 58.6 kHz +#define RH_SX126x_GFSK_RX_BW_78_2 0x1B // 78.2 kHz +#define RH_SX126x_GFSK_RX_BW_93_8 0x13 // 93.8 kHz +#define RH_SX126x_GFSK_RX_BW_117_3 0x0B // 117.3 kHz +#define RH_SX126x_GFSK_RX_BW_156_2 0x1A // 156.2 kHz +#define RH_SX126x_GFSK_RX_BW_187_2 0x12 // 187.2 kHz +#define RH_SX126x_GFSK_RX_BW_234_3 0x0A // 234.3 kHz +#define RH_SX126x_GFSK_RX_BW_312_0 0x19 // 312.0 kHz +#define RH_SX126x_GFSK_RX_BW_373_6 0x11 // 373.6 kHz +#define RH_SX126x_GFSK_RX_BW_467_0 0x09 // 467.0 kHz +// LORA bandwidths +#define RH_SX126x_LORA_BW_7_8 0x00 // 7.8 kHz +#define RH_SX126x_LORA_BW_10_4 0x08 // 10.4 kHz +#define RH_SX126x_LORA_BW_15_6 0x01 // 15.6 kHz +#define RH_SX126x_LORA_BW_20_8 0x09 // 20.8 kHz +#define RH_SX126x_LORA_BW_31_25 0x02 // 31.25 kHz +#define RH_SX126x_LORA_BW_41_7 0x0A // 41.7 kHz +#define RH_SX126x_LORA_BW_62_5 0x03 // 62.5 kHz +#define RH_SX126x_LORA_BW_125_0 0x04 // 125.0 kHz +#define RH_SX126x_LORA_BW_250_0 0x05 // 250.0 kHz +#define RH_SX126x_LORA_BW_500_0 0x06 // 500.0 kHz +// LORA Coding rates +#define RH_SX126x_LORA_CR_4_5 0x01 // 4/5 +#define RH_SX126x_LORA_CR_4_6 0x02 // 4/6 +#define RH_SX126x_LORA_CR_4_7 0x03 // 4/7 +#define RH_SX126x_LORA_CR_4_8 0x04 // 4/8 +// LORA Spreading Factors, actually powers of 2 +#define RH_SX126x_LORA_SF_32 5 // SF5 +#define RH_SX126x_LORA_SF_64 6 // SF6 +#define RH_SX126x_LORA_SF_128 7 // SF7 +#define RH_SX126x_LORA_SF_256 8 // SF8 +#define RH_SX126x_LORA_SF_512 9 // SF9 +#define RH_SX126x_LORA_SF_1024 10 // SF10 +#define RH_SX126x_LORA_SF_2048 11 // SF11 +#define RH_SX126x_LORA_SF_4096 12 // SF12 + +#define RH_SX126x_LORA_LOW_DATA_RATE_OPTIMIZE_OFF 0x00 // LoRa low data rate optimization: disabled +#define RH_SX126x_LORA_LOW_DATA_RATE_OPTIMIZE_ON 0x01 // enabled + +// RH_SX126x_CMD_SET_PACKET_PARAMS +#define RH_SX126x_GFSK_PREAMBLE_DETECT_OFF 0x00 // GFSK minimum preamble length before reception starts: detector disabled +#define RH_SX126x_GFSK_PREAMBLE_DETECT_8 0x04 // 8 bits +#define RH_SX126x_GFSK_PREAMBLE_DETECT_16 0x05 // 16 bits +#define RH_SX126x_GFSK_PREAMBLE_DETECT_24 0x06 // 24 bits +#define RH_SX126x_GFSK_PREAMBLE_DETECT_32 0x07 // 32 bits +#define RH_SX126x_GFSK_ADDRESS_FILT_OFF 0x00 // GFSK address filtering: disabled +#define RH_SX126x_GFSK_ADDRESS_FILT_NODE 0x01 // node only +#define RH_SX126x_GFSK_ADDRESS_FILT_NODE_BROADCAST 0x02 // node and broadcast +#define RH_SX126x_GFSK_PACKET_FIXED 0x00 // GFSK packet type: fixed (payload length known in advance to both sides) +#define RH_SX126x_GFSK_PACKET_VARIABLE 0x01 // variable (payload length added to packet) +#define RH_SX126x_GFSK_CRC_OFF 0x01 // GFSK packet CRC: disabled +#define RH_SX126x_GFSK_CRC_1_BYTE 0x00 // 1 byte +#define RH_SX126x_GFSK_CRC_2_BYTE 0x02 // 2 byte +#define RH_SX126x_GFSK_CRC_1_BYTE_INV 0x04 // 1 byte, inverted +#define RH_SX126x_GFSK_CRC_2_BYTE_INV 0x06 // 2 byte, inverted +#define RH_SX126x_GFSK_WHITENING_OFF 0x00 // GFSK data whitening: disabled +#define RH_SX126x_GFSK_WHITENING_ON 0x01 // enabled +#define RH_SX126x_LORA_PACKET_VARIABLE 0x00 +#define RH_SX126x_LORA_PACKET_FIXED 0x01 +#define RH_SX126x_LORA_HEADER_EXPLICIT 0x00 // LoRa header mode: explicit +#define RH_SX126x_LORA_HEADER_IMPLICIT 0x01 // implicit +#define RH_SX126x_LORA_CRC_OFF 0x00 // LoRa CRC mode: disabled +#define RH_SX126x_LORA_CRC_ON 0x01 // enabled +#define RH_SX126x_LORA_IQ_STANDARD 0x00 // LoRa IQ setup: standard +#define RH_SX126x_LORA_IQ_INVERTED 0x01 // inverted + +// RH_SX126x_CMD_SET_CAD_PARAMS +#define RH_SX126x_CAD_ON_1_SYMB 0x00 // number of symbols used for CAD: 1 +#define RH_SX126x_CAD_ON_2_SYMB 0x01 // 2 +#define RH_SX126x_CAD_ON_4_SYMB 0x02 // 4 +#define RH_SX126x_CAD_ON_8_SYMB 0x03 // 8 +#define RH_SX126x_CAD_ON_16_SYMB 0x04 // 16 +#define RH_SX126x_CAD_GOTO_STDBY 0x00 // after CAD is done, always go to STDBY_RC mode +#define RH_SX126x_CAD_GOTO_RX 0x01 // after CAD is done, go to Rx mode if activity is detected +#define RH_SX126x_CAD_PARAM_DEFAULT 0xFF // used by the CAD methods to specify default parameter value +#define RH_SX126x_CAD_PARAM_DET_MIN 10 // default detMin CAD parameter + +// RH_SX126x_CMD_GET_STATUS +#define RH_SX126x_STATUS_MODE_STDBY_RC 0b00100000 // current chip mode: STDBY_RC +#define RH_SX126x_STATUS_MODE_STDBY_XOSC 0b00110000 // STDBY_XOSC +#define RH_SX126x_STATUS_MODE_FS 0b01000000 // FS +#define RH_SX126x_STATUS_MODE_RX 0b01010000 // RX +#define RH_SX126x_STATUS_MODE_TX 0b01100000 // TX +#define RH_SX126x_STATUS_DATA_AVAILABLE 0b00000100 // command status: packet received and data can be retrieved +#define RH_SX126x_STATUS_CMD_TIMEOUT 0b00000110 // SPI command timed out +#define RH_SX126x_STATUS_CMD_INVALID 0b00001000 // invalid SPI command +#define RH_SX126x_STATUS_CMD_FAILED 0b00001010 // SPI command failed to execute +#define RH_SX126x_STATUS_TX_DONE 0b00001100 // packet transmission done +#define RH_SX126x_STATUS_SPI_FAILED 0b11111111 // SPI transaction failed + +// RH_SX126x_CMD_GET_PACKET_STATUS +#define RH_SX126x_GFSK_RX_STATUS_PREAMBLE_ERR 0b10000000 // GFSK Rx status: preamble error +#define RH_SX126x_GFSK_RX_STATUS_SYNC_ERR 0b01000000 // sync word error +#define RH_SX126x_GFSK_RX_STATUS_ADRS_ERR 0b00100000 // address error +#define RH_SX126x_GFSK_RX_STATUS_CRC_ERR 0b00010000 // CRC error +#define RH_SX126x_GFSK_RX_STATUS_LENGTH_ERR 0b00001000 // length error +#define RH_SX126x_GFSK_RX_STATUS_ABORT_ERR 0b00000100 // abort error +#define RH_SX126x_GFSK_RX_STATUS_PACKET_RECEIVED 0b00000010 // packet received +#define RH_SX126x_GFSK_RX_STATUS_PACKET_SENT 0b00000001 // packet sent + +// RH_SX126x_CMD_GET_DEVICE_ERRORS +#define RH_SX126x_PA_RAMP_ERR 0b100000000 // device errors: PA ramping failed +#define RH_SX126x_PLL_LOCK_ERR 0b001000000 // PLL failed to lock +#define RH_SX126x_XOSC_START_ERR 0b000100000 // crystal oscillator failed to start +#define RH_SX126x_IMG_CALIB_ERR 0b000010000 // image calibration failed +#define RH_SX126x_ADC_CALIB_ERR 0b000001000 // ADC calibration failed +#define RH_SX126x_PLL_CALIB_ERR 0b000000100 // PLL calibration failed +#define RH_SX126x_RC13M_CALIB_ERR 0b000000010 // RC13M calibration failed +#define RH_SX126x_RC64K_CALIB_ERR 0b000000001 // RC64K calibration failed + +// RH_SX126x_CMD_SET_LBT_SCAN_PARAMS + RH_SX126x_CMD_SET_SPECTR_SCAN_PARAMS +#define RH_SX126x_SCAN_INTERVAL_7_68_US 10 // RSSI reading interval: 7.68 us +#define RH_SX126x_SCAN_INTERVAL_8_20_US 11 // 8.20 us +#define RH_SX126x_SCAN_INTERVAL_8_68_US 12 // 8.68 us + +// SX126X SPI register variables +// RH_SX126x_REG_HOPPING_ENABLE +#define RH_SX126x_HOPPING_ENABLED 0b00000001 // intra-packet hopping for LR-FHSS: enabled +#define RH_SX126x_HOPPING_DISABLED 0b00000000 // (disabled) + +// RH_SX126x_REG_LORA_SYNC_WORD_MSB + LSB +#define RH_SX126x_SYNC_WORD_PUBLIC 0x34 // actually 0x3444 NOTE: The low nibbles in each byte (0x_4_4) are masked out since apparently, they're reserved. +#define RH_SX126x_SYNC_WORD_PRIVATE 0x12 // actually 0x1424 You couldn't make this up if you tried. + +// RH_SX126x_REG_TX_BITBANG_ENABLE_1 +#define RH_SX126x_TX_BITBANG_1_DISABLED 0b00000000 // Tx bitbang: disabled (default) +#define RH_SX126x_TX_BITBANG_1_ENABLED 0b00010000 // enabled + +// RH_SX126x_REG_TX_BITBANG_ENABLE_0 +#define RH_SX126x_TX_BITBANG_0_DISABLED 0b00000000 // Tx bitbang: disabled (default) +#define RH_SX126x_TX_BITBANG_0_ENABLED 0b00001100 // enabled + +// RH_SX126x_REG_DIOX_OUT_ENABLE +#define RH_SX126x_DIO1_OUT_DISABLED 0b00000010 // DIO1 output: disabled +#define RH_SX126x_DIO1_OUT_ENABLED 0b00000000 // enabled +#define RH_SX126x_DIO2_OUT_DISABLED 0b00000100 // DIO2 output: disabled +#define RH_SX126x_DIO2_OUT_ENABLED 0b00000000 // enabled +#define RH_SX126x_DIO3_OUT_DISABLED 0b00001000 // DIO3 output: disabled +#define RH_SX126x_DIO3_OUT_ENABLED 0b00000000 // enabled + +// RH_SX126x_REG_DIOX_IN_ENABLE +#define RH_SX126x_DIO1_IN_DISABLED 0b00000000 // DIO1 input: disabled +#define RH_SX126x_DIO1_IN_ENABLED 0b00000010 // enabled +#define RH_SX126x_DIO2_IN_DISABLED 0b00000000 // DIO2 input: disabled +#define RH_SX126x_DIO2_IN_ENABLED 0b00000100 // enabled +#define RH_SX126x_DIO3_IN_DISABLED 0b00000000 // DIO3 input: disabled +#define RH_SX126x_DIO3_IN_ENABLED 0b00001000 // enabled + +// RH_SX126x_REG_RX_GAIN +#define RH_SX126x_RX_GAIN_BOOSTED 0x96 // Rx gain: boosted +#define RH_SX126x_RX_GAIN_POWER_SAVING 0x94 // power saving +#define RH_SX126x_RX_GAIN_SPECTRAL_SCAN 0xCB // spectral scan + +// RH_SX126x_REG_PATCH_UPDATE_ENABLE +#define RH_SX126x_PATCH_UPDATE_DISABLED 0b00000000 // patch update: disabled +#define RH_SX126x_PATCH_UPDATE_ENABLED 0b00010000 // enabled + +// RH_SX126x_REG_SPECTRAL_SCAN_STATUS +#define RH_SX126x_SPECTRAL_SCAN_NONE 0x00 // spectral scan status: none +#define RH_SX126x_SPECTRAL_SCAN_ONGOING 0x0F // ongoing +#define RH_SX126x_SPECTRAL_SCAN_ABORTED 0xF0 // aborted +#define RH_SX126x_SPECTRAL_SCAN_COMPLETED 0xFF // completed + +// RH_SX126x_REG_RSSI_AVG_WINDOW +#define RH_SX126x_SPECTRAL_SCAN_WINDOW_DEFAULT (0x05 << 2) // default RSSI average window + +// RH_SX126x_REG_ANA_LNA +#define RH_SX126x_LNA_RNG_DISABLED 0b00000001 // random number: disabled +#define RH_SX126x_LNA_RNG_ENABLED 0b00000000 // enabled + +// RH_SX126x_REG_ANA_MIXER +#define RH_SX126x_MIXER_RNG_DISABLED 0b00000001 // random number: disabled +#define RH_SX126x_MIXER_RNG_ENABLED 0b00000000 // enabled + +// size of the spectral scan result +#define RH_SX126x_SPECTRAL_SCAN_RES_SIZE (33) + + + + + +///////////////////////////////////////////////////////////////////// +/*! \class RH_SX126x RH_SX126x.h +\brief Driver to send and receive unaddressed, unreliable datagrams via a +Semtech SX126X family LoRa capable radio transceiver. + +Works with NiceRF LoRa1262-915 and Teensy 3.1. Will probably work with any other SX1262 module. + +\par Overview + +This class provides basic functions for sending and receiving unaddressed, +unreliable datagrams of arbitrary length to 251 octets per packet. + +Manager classes may use this class to implement reliable, addressed datagrams and streams, +mesh routers, repeaters, translators etc. + +Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +modulation scheme. + +Predefined modulation schemes are available for various LoRa +modulation speeds and bandwidths. GFSK modulation is also supported. + +The SX126x family of radio chips are availabel as discrete comonents with an SPI interface. +In some hardware (eg the STM32WLE5xx STM32WLE4xx processors) the radio +is built into a microprocessor. + +\par Packet Format + +All messages sent and received by this RH_SX126x Driver conform to this packet format, which is compatible with RH_RF95: + +- LoRa mode: +- 8 symbol PREAMBLE +- Explicit header with header CRC (default CCITT, handled internally by the radio) +- 4 octets HEADER: (TO, FROM, ID, FLAGS) +- 0 to 251 octets DATA +- CRC (default CCITT, handled internally by the radio) + +\par Interrupts + +The RH_SX126x driver uses interrupts to react to events in the radio, +such as the reception of a new packet, or the completion of +transmission of a packet. The driver configures the radio so the +required interrupt is generated by the radio's DIO1 pin. The +RH_SX126x driver interrupt service routine reads status from and +writes data to the the radio module via an SPI interface. It is very +important therefore, that if you are using the RH_SX126x driver with +another SPI based deviced, that you disable interrupts while you +transfer data to and from that other device. Use cli() to disable +interrupts and sei() to reenable them. (however note that the +RH_STM32WLx subclass uses the dedicated internal SPI interface that is +connected only to the radio). + +\par Memory + +The RH_SX126x driver requires non-trivial amounts of memory. The sample +programs all compile to about 35 kbytes each, which will fit in the +flash proram memory of most Arduinos. However, the RAM requirements are +more critical. Therefore, you should be vary sparing with RAM use in +programs that use the RH_SX126x driver. + +\par Compatibility with RH_RF95 + +The predefined modulation schemes have been show to interoperate with +the RH_RF95 driver with similarly named modulation schemes. + +For example the (default) RH_SX126x::LoRa_Bw125Cr45Sf128 is compatible +with the (default) RH_RF95::Bw125Cr45Sf128. + +The RH_SX126x driver sets the LoRa Sync word to 0x1424, which is compatible with single byte 0x12 default for RH_RF95. +// https://forum.lora-developers.semtech.com/t/sx1272-and-sx1262-lora-sync-word-compatibility/988/13 + +\par Transmitter Power + +We measured the RF power output from a Wio-E5 mini at 868.0 MHz, with +the radio set to continuous CW transmission using +setTxContinuous(). On this chip that implies the high power amplifier. +We set various power outputs with setTxPower() from -9 to 22 and +measured the RF output power with a HP 5342A Microwave Frequency +Counter. Note that the drivers setTxPower() sets the optimum +transmitter control registers per section 13.1.14.1 of the datasheet +SX1262_datasheet.pdf + +\code +Program power Measured power + dBm dBm + -9 -5.0 + 0 -2.9 + 5 6.9 + 10 8.9 + 15 13.3 + 16 14.1 + 17 14.6 + 18 16.7 + 19 17.3 + 20 17.8 + 21 18.7 + 22 19.4 +\endcode + +With the transmitter frequency set to 868.0 MHz, the actual centre +frequency measured with the HP 5342A Microwave Frequency Counter on 2 +instances of Wio-E5 mini were 867.999826 and 867.999652 MHz. + +\par Differences between models + +SX126x compatible chips are available in at least 4 types: + +-SX1261 Has only one (low power) PA, -17 to +15 dBm + +-SX1262 Has only one (high power) PA, -9 to +22 dBm + +-SX1268 Has 2 PAs, low power (-17 to +15 dBm) and high power (-9 to +22 dBm) + +-STM32WLE5JC has 2 PAs, low power (-17 to +15 dBm) and high power (-9 to +22 dBm). + +Even if the radio has 2 PAs, depending on your radio module, maybe +only one is connected. It also includes a dedicated SPI interface for +the radio plus some internllay connected reset and interrupt pins + +Some radio modules might also include an antenna switch, and the +driver MUST be configured so that it knows how to turn any +control pins on and off for receiving and transmitting. See setRadioPinConfig(). + +\par Configuring the driver for your particular type of radio + +You will almost certainly have to configure this driver to suit the +particular radio hardware in your system. Its boring but you MUST pay +attention to this otherwise you may not be able to transmit or receive +successfully. + +This issues you will have to consider are: + +- What model radio do you have? + +- What SPI bus is the radio connected to? + +- Is there a TCXO to configure? + +- What pin is used for the SPI slave select for the radio chip? + +- Is the radio reset pin connected to the CPU? + +- Are there any pins required to be set to control the external radio + interface, such as RF switches, external PAs etc, + +If you are using a ST Microelectronics STM32WLE5xx or STM32WLE4xx +processors and its built in radio, you can use the RH_STM32WLx and +ignore most or all of these issues. + +If you are using a Heltec CubeCell, such as HTCC-AB01, initialise the driver with: +\code +RH_SX126x driver(RADIO_NSS, RADIO_DIO_1, RADIO_BUSY, RADIO_RESET); +\endcode + +\par Range + +No range tests have yet been conducted. + +\par Connecting SX126x modules to Arduino + +Note, if you are using a STM32WLE5JC, see the intructions for that in RH_STM32WLx.h + +Connecting a NiceRF LoRa1262-915 to a Teensy 3.1: + +https://www.nicerf.com/lora-module/915mhz-lora-module-lora1262.html + +We got one on a breakout board +which already has a small helical antenna connected. The module appears to contain a 3.3V TCXO, nd an antenna switch connected to DIO2 +You should be able to use a +similar pinout for any 3.3V Arduino compatible board. + + +\code + Teensy 3.1 G-Nice RF LoRa1262-915 + GND----------GND (Ground) + 3V3----------VCC (3.3V in) + pin D7-----------DIO1 (radio interrupt request out, active high) + pin D8-----------BUSY (radio busy output, active high) + pin D9-----------NRESET (radio reset in: pulled low for 2ms at startup) + SS pin D10----------NSS (chip select in) + SCK pin D13----------SCK (SPI clock in) + MOSI pin D11----------MOSI (SPI Data in) + MISO pin D12----------MISO (SPI Data out) + +With these connections you can then use the constructor: + +RH_SX126x driver(SS, 7, 8, 9); +\endcode + +RAKwireless RAK4360/RAK4361 + +RHHardwareSPI uses the default LoRa radio SPI pins as defined by the platform. You can use the contructor: + +\code +RH_SX126x driver(42, 47, 46, 38); // NSS, DIO1, BUSY, NRESET +\endcode + + + + + +*/ + +class RH_SX126x : public RHSPIDriver +{ +public: + /// Packet types the modem can be configured for + typedef enum + { + PacketTypeLoRa = 0, ///< Use LoRA packets + PacketTypeGFSK, ///< Use GFSK packets + } PacketType; + + /// \brief Defines register values for a set of modem configuration registers + /// + /// Defines register values for a set of modem configuration registers + /// that can be passed to setModulationParameters() if none of the choices in + /// ModemConfigChoice suit your need setModemRegisters() writes the + /// register values from this structure to the appropriate registers + /// to set the desired spreading factor, coding rate and bandwidth + typedef struct + { + PacketType packetType; + uint8_t p1; ///< Value for setModulationParameters parameter 1 + uint8_t p2; ///< Value for setModulationParameters parameter 2 + uint8_t p3; ///< Value for setModulationParameters parameter 3 + uint8_t p4; ///< Value for setModulationParameters parameter 4 + uint8_t p5; ///< Value for setModulationParameters parameter 5 + uint8_t p6; ///< Value for setModulationParameters parameter 6 + uint8_t p7; ///< Value for setModulationParameters parameter 7 + uint8_t p8; ///< Value for setModulationParameters parameter 8 + } ModemConfig; + + /// Choices for setModemConfig() for a selected subset of common + /// data rates. If you need another configuration, + /// determine the necessary settings and call setModemRegisters() with your + /// desired settings. It might be helpful to use the LoRa calculator mentioned in + /// http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf + /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic + /// definitions and not their integer equivalents: its possible that new values will be + /// introduced in later versions (though we will try to avoid it). + /// Caution: if you are using slow packet rates and long packets with RHReliableDatagram or subclasses + /// you may need to change the RHReliableDatagram timeout for reliable operations. + /// Caution: for some slow rates nad with ReliableDatagrams you may need to increase the reply timeout + /// with manager.setTimeout() to + /// deal with the long transmission times. + /// Caution: SX1276 family errata suggests alternate settings for some LoRa registers when 500kHz bandwidth + /// is in use. See the Semtech SX1276/77/78 Errata Note. These are not implemented by RH_SX126x. + /// In general, the LoRa_* configurations are compatible with the similarly named RH_RF95 configurations + typedef enum + { + LoRa_Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range + LoRa_Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range + LoRa_Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range + LoRa_Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, low data rate, CRC on. Slow+long range + LoRa_Bw125Cr45Sf2048, ///< Bw = 125 kHz, Cr = 4/5, Sf = 2048chips/symbol, CRC on. Slow+long range + } ModemConfigChoice; + + /// Structures and enums for Tx/Rx pin configuration + /// These structures allow you to specify what pins are to be automaticall set or cleared to control radio power amp and receivers + /// and how there are to be set for each radio mode, IDLE, TX or RX. + /// They can be used to automatically configure any RF switch or external power amp etc. + /// You will probably need these to configure the driver for your specific hardware + typedef enum + { + RadioPinConfigMode_EOT = 0, // End of table. Must be the last item in the pin configuration table + RadioPinConfigMode_IDLE, // This config is for the radio idle + RadioPinConfigMode_RX, // This config is for receiving + RadioPinConfigMode_TX_LOW_POWER, // This config is for transmitting with low power PA + RadioPinConfigMode_TX_HIGH_POWER, // This config is for transmitting with high power PA + } RadioPinConfigMode; + + // Maximum bumber of entries permitted in a RadioPinConfigTable + #define RH_SX126x_MAX_RADIO_PIN_CONFIG_MODES (RadioPinConfigMode_TX_HIGH_POWER + 1) + + // The number of pins that might need to be controlled + #define RH_SX126x_MAX_RADIO_CONTROL_PINS (3) + + // Tells how to set the pins in PinConfig for each a particular transmit or receive condition + typedef struct + { + /// The type of radio condition for these pin settings. PinConfigEntry_EOT for last item in table + RadioPinConfigMode mode = RadioPinConfigMode_EOT; + /// The state (HIGH or LOW) to set each of the radio control pins to when this state is reached + bool pinState[RH_SX126x_MAX_RADIO_CONTROL_PINS]; + } RadioPinConfigEntry; + + /// Pointer to structure can be passed to the contructor or setRadioPinConfig() to configure the + /// how various pins are to be set to configure your radio hardware (RF swithes etc) for various radio modes. + typedef struct + { + /// Pin number of each pin to be automcatically controlled + uint8_t pinNumber[RH_SX126x_MAX_RADIO_CONTROL_PINS]; // Pin number or RH_INVALID_PIN + /// One entry for each radio state supported by your hardware + // The last entry must have mode = RadioPinConfigMode_EOT + RadioPinConfigEntry configState[RH_SX126x_MAX_RADIO_PIN_CONFIG_MODES]; + } RadioPinConfig; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// interrupt and slave select pin. After constructing, you must call init() to initialise the interface + /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient + /// distinct interrupt lines, one for each instance. + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RH_RF22 before + /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) + /// \param[in] interruptPin The interrupt Pin number that is connected to the RFM DIO0 interrupt line. + /// Defaults to pin 2, as required by Anarduino MinWirelessLoRa module. + /// Caution: You must specify an interrupt capable pin. + /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. + /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. + /// On Arduino Zero from arduino.cc, any digital pin other than 4. + /// On Arduino M0 Pro from arduino.org, any digital pin other than 2. + /// On other Arduinos pins 2 or 3. + /// See http://arduino.cc/en/Reference/attachInterrupt for more details. + /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. + /// On other boards, any digital pin may be used. + /// \param[in] busyPin Pin number of pin connected to the radio's busy pin. The radio sets the busy pin high while it is busy + /// If this is not set to RH_INVALID_PIN (the default) then this module will wait for the busy pin to go low before + /// initialting the next SPI transfer. It is strongly recommended that you use this. + /// \param[in] resetPin Pin number of the pin connected to the radio's reset pin. If this is not set to RH_INVALID_PIN (the default) then this module will + /// assert the reset pin low for 2 ms during init() in order to reset the radio. It is strongly recommended that you use this + /// \param[in] spi Pointer to the SPI interface object to use. + /// \param[in] radioPinConfig pinter to a strucure that describes what pins are to be automatically set when changing + /// the radio mode. This can be used to configure any external RF switches, RF amplifiers etc. + /// Defaults to the standard Arduino hardware SPI interface + RH_SX126x(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, uint8_t busyPin = RH_INVALID_PIN, uint8_t resetPin = RH_INVALID_PIN, + RHGenericSPI& spi = hardware_spi, RadioPinConfig* radioPinConfig = NULL); + + /// Initialise the Driver transport hardware and software. + /// Leaves the radio in idle mode, + /// with default configuration of: 915.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Prints the value of selected radio chip registers + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging purposes only. + /// \param[in] address The register adddress of the first register to print + /// \param[in] count The number of registers to print + /// \return true on success + bool printRegisters(uint16_t address, uint8_t count); + + /// Sets all the registers required to configure the data modem in the radio, including the bandwidth, + /// spreading factor etc. You can use this to configure the modem with custom configurations if none of the + /// canned configurations in ModemConfigChoice suit you. + /// \param[in] config A ModemConfig structure containing values for the modem configuration registers. + /// \return true if modem was successfully reconfigured + bool setModemRegisters(const ModemConfig* config); + + /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// here, use setModemRegisters() with your own ModemConfig. + /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator + /// for reliable operation. + /// \param[in] index The configuration choice. + /// \return true if index is a valid choice. + bool setModemConfig(ModemConfigChoice index); + + /// Tests whether a new message is available from the Driver. + /// On most drivers, this will also put the Driver into RHModeRx mode until + /// a message is actually received by the transport, when it will be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available(); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then optionally waits for Channel Activity Detection (CAD) + /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send + /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting. + /// \return true if the message length was valid and it was correctly queued for transmit. Return false + /// if CAD was requested and the CAD timeout timed out before clear channel was detected. + virtual bool send(const uint8_t* data, uint8_t len); + + /// Sets the length of the preamble + /// in bytes. + /// Caution: this should be set to the same + /// value on all nodes in your network. Default is 8. + /// Sets the message preamble length in RH_SX126x_REG_??_PREAMBLE_?SB + /// \param[in] bytes Preamble length in bytes. + void setPreambleLength(uint16_t bytes); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + /// Sets the transmitter and receiver + /// centre frequency. + /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several + /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work + /// \param[i] calibrate set true if the radio modules are to be automatically recalibrated for this frequency + /// \return true if the selected frquency centre is within range, and the radio frequency is successfully set + bool setFrequency(float centre, bool calibrate = true); + + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + void setModeIdle(); + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver in the SX126X/96/97/98. + void setModeRx(); + + /// If current mode is Rx or Idle, changes it to Rx. F + /// Starts the transmitter in the SX126X/96/97/98. + void setModeTx(); + + /// Sets the transmitter power output level + /// Be a good neighbour and set the lowest power level you need. + /// Caution: legal power limits may apply in certain countries. + /// After init(), the power will be set to 13dBm. + /// \param[in] power Transmitter power level in dBm. + /// For SX1261, limits are -17 to +15 dBm + /// For SX1262, limits are -9 to +22 dBm + /// For STM32WLx with low power PA configured by radioPinConfig, same as SX1261. + /// For STM32WLx with high power PA configured by radioPinConfig, same as SX1262. + /// \return true if successful. Returns false if radioPinConfig has not been properly + /// configured for the requested power setting + virtual bool setTxPower(int8_t power); + + /// Sets the radio into low-power sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. + /// \return true if sleep mode was successfully entered. + virtual bool sleep(); + + /// Use the radio's Channel Activity Detect (CAD) function to detect channel activity. + /// Sets the SX126X radio into CAD mode and waits until CAD detection is complete. + /// To be used in a listen-before-talk mechanism (Collision Avoidance) + /// with a reasonable time backoff algorithm. + /// This is called automatically by waitCAD(). + /// NOT YET WORKING. + /// \return true if channel is in use. + virtual bool isChannelActive(); + + /// Returns the Signal-to-noise ratio (SNR) of the last received message, as measured + /// by the receiver. + /// \return SNR of the last received message in dB + int lastSNR(); + + // Support for configurable RX and TX pins + void setRadioPinConfig(RadioPinConfig* config); + + /// Set the radio into continuous transmission mode. A carrier + /// wave will be transmitted on the configured centre frequency until available(), recv() or send() are called + /// CAUTION: use this only for testing in controlled conditions with a dummy load. It may be illegal for you to transmit + /// a continuous carrier wave to air. + bool setTxContinuous(); + + /// Read and return the radio status byte + uint8_t getStatus(); + + /// Return the last interrupt mask, for debugging + uint16_t lastIrq() {return _lastirq;}; + + /// Return true if an interrupt has occurred since the last clearIflag(). For debugging + bool getIflag() {return _iflag;}; + + /// Reset the interrupt flag. For debugging + void clearIflag() {_iflag=false;}; + + /// REsets the last interrupt mask. For debugging + void clearLastIrq() {_lastirq=0;}; + + /// Enable or disable the ability to detect CRC errors + void enableCrcErrorIrq(bool enable); + + /// Tells the driver to enable RAW mode, which prevents the transmissions of the 4 byte address header. + void enableRawMode(bool enable); + + /// Returns the frequency error from the last received packet + float getFrequencyError(); + +protected: + + /////////////////////////////////////////////////////////////////// + // Follow are low level functions for communicating with the SX126x + // Caution should be used if accessing them in subclasses + + /// Wait until the busy pin (if speecified in the contructor) is no longer low + /// On timeout, prints an error to eSerial and returns false. Else returns true. + virtual bool waitUntilNotBusy(); + + + /// Send a command with multi-byte data to the radio + bool sendCommand(uint8_t command, uint8_t data[], uint8_t len); + + /// Send a command with a single data byte to the radio + bool sendCommand(uint8_t command, uint8_t value); + + /// Send a command without any data to the radio + bool sendCommand(uint8_t command); + + /// Send a command to the radio and get a multi-byte respose + bool getCommand(uint8_t command, uint8_t data[], uint8_t len); + + /// Read multiple registers from the radio + bool readRegisters(uint16_t address, uint8_t data[], uint8_t len); + + /// Read and return a single register byte from the radio + uint8_t readRegister(uint16_t address); + + /// Write multibyte data to the given register and sunbsequent registers + bool writeRegisters(uint16_t address, uint8_t data[], uint8_t len); + + /// Write a single byte to the given register + bool writeRegister(uint16_t address, uint8_t data); + + /// Write multibyte data to the radio IO buffer at the current buffer address + bool writeBuffer(uint8_t offset, const uint8_t data[], uint8_t len); + + /// Write a single byte to the radio IO bufferat the current buffer address + bool writeBuffer(uint8_t offset, const char* text); + + /// Read multibyte data from the radio IO buffer at the current buffer address + bool readBuffer(uint8_t offset, uint8_t data[], uint8_t len); + + /// Set the radio Power Amplifier configuration + bool setPaConfig(uint8_t paDutyCycle, uint8_t hpMax, uint8_t deviceSel, uint8_t paLut); + + /// Set the radio power output. CAUTION: for internal use only. Users should use setTxPower() + bool setTxParams(uint8_t power, uint8_t rampTime); + + /// Clear the radio error byte + bool clearDeviceErrors(); + + /// Sets whether DIO2 is to be used to automatically control an external radio RF switch. + /// Normall you should use the pinConfig in the constructor or setRadioPinConfig() to automatically control any + /// radio control pins + bool setDIO2AsRfSwitchCtrl(bool value); + + /// Sets the mode that the radio will change to after a transmit or receive is complete + bool setRxFallbackMode(uint8_t mode); + + /// Set the low-level registers for any desired modulation scheme + bool setModulationParameters(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4, uint8_t p5, uint8_t p6, uint8_t p7, uint8_t p8); + + /// Set the low-level registers for the desired LoRA modulation scheme + bool setModulationParametersLoRa(uint8_t sf, float bw, uint8_t cr, bool ldro); + + /// Set the low-level registers for the desired GFSK modulation scheme + bool setModulationParametersGFSK(uint32_t br, uint8_t sh, uint8_t rxBw, uint32_t freqDev); + + /// Cause the radio to calibrate all its sections at the currently selected frequency + bool calibrate(uint8_t calib_param); + + /// Allows the user to calibrate the image rejection of the device for the device operating frequency band + bool calibrateImage(uint8_t f1, uint8_t f2); + + /// Set the 16 bit LoRa sync word + bool setLoRaSyncWord(uint16_t sync); + + /// Set the radio power amplifier over-current protection + bool setOCPConfiguration(uint8_t setting); + + /// Configures the radio to use an external temperature controlled crystal oscillator (TCXO) and the oven voltage to use. + /// For low level internal use only + bool setDIO3AsTcxoCtrl(uint8_t voltage, uint32_t delay); + + /// Set the voltage to use for the TCXO oven + bool setTCXO(float voltage, uint32_t delay); + + /// Low level function to set the radio packet confiuration + bool setPacketParams(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4, uint8_t p5, uint8_t p6, uint8_t p7, uint8_t p8, uint8_t p9); + + /// Set the necessary radio packet parameters for a forthcoming transmission of payload_length bytes + bool setPacketParametersLoRa(uint8_t payload_length); + + /// Low level function to set the address wherge the next radBuffer or writeBuffer will occur + bool setBufferBaseAddress(uint8_t txbase, uint8_t rxbase); + + /// Set the radio to sleep mode. Automatically configures the radio control pins to the configuration RadioPinConfigMode_IDLE. + bool setSleep(uint8_t config); + + /// Set the radio to sleep mode. Automatically configures the radio control pins to the configuration RadioPinConfigMode_IDLE. + bool setStandby(uint8_t config); + + /// Set the radio to transmit mode. Automatically configures the radio control pins to the configuration required + /// for the most recently requested power in setTxPower (RadioPinConfigMode_TX_HIGH_POWER or RadioPinConfigMode_TX_LOW_POWER ) + bool setTx(uint32_t timeout); + + /// Starts the radio in Clear Air Detect (CAD) mode. CAUTION: NOT YET WORKING, always retuns false. + bool setCad(); + + /// Set the radio to receive mode. Automatically configures the radio control pins to the configuration RadioPinConfigMode_RX. + bool setRx(uint32_t timeout); + + /// Sets whether radios receiver gain boost should be enabled instead of the default power saving mode. + bool setRxBoostMode(bool boost, bool retain); + + /// Sets the chip regulator mode to either LDO or DC-DC SMPS. + /// Set to DC-DC SMPS by default + bool setRegulatorMode(uint8_t mode); + + /// Configures the conditions under which the radio will enable an interrupt, and for which DIO pins + bool setDioIrqParams(uint16_t irqmask, uint16_t dio1mask, uint16_t dio2mask, uint16_t dio3mask); + + /// Clear the radio IRQ state + bool clearIrqStatus(uint16_t mask); + + /// Return the radio IRQ state + uint16_t getIrqStatus(); + + /// return the current packet type + uint8_t getPacketType(); + + /// From SX1262_datasheet.pdf: "When exchanging LoRa® packets with inverted IQ polarity, + /// some packet losses may be observed for longer packet". THis function enables the workaround described in that section + void setInvertIQ(bool invertIQ); + + /// Per SX1262_datasheet.pdf Rev 1.2 section 15.2, this fixes an error in the radio Power Amplifier clamping + bool fixPAClamping(bool enable); + + /// Do whatever is necesary to establish the interrupt handler. Subclasses may have different needs + virtual bool setupInterruptHandler(); + + /// This is a low level function to handle the interrupts for one instance of RH_SX126x. + /// Called automatically by isr*() + /// Should not need to be called by user code. + void handleInterrupt(); + + /// Examine the revceive buffer to determine whether the message is for this node + void validateRxBuf(); + + /// Clear our local receive buffer + void clearRxBuf(); + + /// Called by RH_SX126x when the radio mode is about to change to a new setting. + /// Can be used by subclasses to implement antenna switching etc. + /// \param[in] mode RHMode the new mode about to take effect + /// \return true if the subclasses changes successful + virtual bool modeWillChange(RHMode) {return true;} + + /// Sets the pins configured in radioPinConfig as required for the desired mode. + /// Called just before the radio is set to the new mode. + /// \return true if succcessful, false is there is no radioPinConfig configuration, + /// or no entry for the requested mode + virtual bool setRadioPinsForMode(RadioPinConfigMode mode); + + /// Find the pin configuration entry for a desired radio mode + virtual RadioPinConfigEntry* findRadioPinConfigEntry(RadioPinConfigMode mode); + +private: + /// Low level interrupt service routine for device connected to interrupt 0 + static void isr0(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr1(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr2(); + + /// Array of instances connected to interrupts 0 and 1 + static RH_SX126x* _deviceForInterrupt[]; + + /// The configured interrupt pin connected to this instance + uint8_t _interruptPin; + + /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) + /// else 0xff + uint8_t _myInterruptIndex; + + /// Number of octets in the buffer + volatile uint8_t _bufLen; + + /// The receiver/transmitter buffer + uint8_t _buf[RH_SX126x_MAX_PAYLOAD_LEN]; + + /// True when there is a valid message in the buffer + volatile bool _rxBufValid; + + /// True if we are using the HF port (779.0 MHz and above) + bool _usingHFport; + + /// Last measured SNR, dB + int8_t _lastSNR; + + /// If true, sends CRCs in every packet and requires a valid CRC in every received packet + bool _enableCRC; + + /// Sets the preamble length for LoRa packets + uint16_t _preambleLength = 8; + + /// Whether the modem is to be configured for INverted IQ + bool _invertIQ = false; + + /// The type of packet to configure the modem for + PacketType _packetType = PacketTypeLoRa; + + /// If the current LoRa bandwidth is 500kHz, we need to remeber this in order to implement the + /// modulation quality workaround in setTx() + bool _lorabw500 = false; + + /// Support for optional configurable radio control pins for RX and TX modes + RadioPinConfig* _radioPinConfig = NULL; + + /// Remember what PA type is required, depending on device type and radio pin configurations + RadioPinConfigMode _requiredPAMode = RadioPinConfigMode_IDLE; // One of PinConfigMode_TX_LOW_POWER PinConfigMode_TX_HIGH_POWER + + /// Pin number of the radio BUSY pin, if available, else RH_INVALID_PIN + uint8_t _busyPin; + + /// Pin number of the radio NRESET pin, if available, else RH_INVALID_PIN + uint8_t _resetPin; + + /// Currently selected bandwidth, required for frequencey error calculations + float _bandwidth = 0.0; + + /// Whether we are in raw mode, bypassing address bytes prefix + bool _raw = false; + + /// Vale of the last interrupt flags, for debugging + volatile uint16_t _lastirq; + + /// Whether an interupt has occurred since the last clearIflag(). For debugging + volatile bool _iflag = false; + + // These are the interrupts we are willing to process + uint16_t _irqMask = RH_SX126x_IRQ_CAD_DETECTED + | RH_SX126x_IRQ_CAD_DONE + | RH_SX126x_IRQ_CRC_ERR + | RH_SX126x_IRQ_HEADER_ERR + | RH_SX126x_IRQ_RX_DONE + | RH_SX126x_IRQ_TX_DONE; +}; + +/// @example sx1262_client.ino +/// @example sx1262_server.ino + +#endif diff --git a/RH_Serial.cpp b/RH_Serial.cpp new file mode 100644 index 0000000..c428f29 --- /dev/null +++ b/RH_Serial.cpp @@ -0,0 +1,243 @@ +// RH_Serial.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RH_Serial.cpp,v 1.17 2020/01/07 23:35:02 mikem Exp $ + +#include +#include + +#ifdef RH_HAVE_SERIAL +RH_Serial::RH_Serial(HardwareSerial& serial) + : + _serial(serial), + _rxState(RxStateInitialising) +{ +} + +HardwareSerial& RH_Serial::serial() +{ + return _serial; +} + +bool RH_Serial::init() +{ + if (!RHGenericDriver::init()) + return false; + _rxState = RxStateIdle; + return true; +} + +// Call this often +bool RH_Serial::available() +{ + while (!_rxBufValid &&_serial.available()) + handleRx(_serial.read()); + return _rxBufValid; +} + +void RH_Serial::waitAvailable(uint16_t polldelay) +{ +#if (RH_PLATFORM == RH_PLATFORM_UNIX) + // Unix version driver in RHutil/HardwareSerial knows how to wait without polling + while (!available()) + _serial.waitAvailable(); +#else + RHGenericDriver::waitAvailable(polldelay); +#endif +} + +bool RH_Serial::waitAvailableTimeout(uint16_t timeout, uint16_t polldelay) +{ +#if (RH_PLATFORM == RH_PLATFORM_UNIX) + // Unix version driver in RHutil/HardwareSerial knows how to wait without polling + unsigned long starttime = millis(); + while ((millis() - starttime) < timeout) + { + _serial.waitAvailableTimeout(timeout - (millis() - starttime)); + if (available()) + return true; + YIELD; + if (polldelay) + delay(polldelay); + } + return false; +#else + return RHGenericDriver::waitAvailableTimeout(timeout, polldelay); +#endif +} + +void RH_Serial::handleRx(uint8_t ch) +{ + // State machine for receiving chars + switch(_rxState) + { + case RxStateIdle: + { + if (ch == DLE) + _rxState = RxStateDLE; + } + break; + + case RxStateDLE: + { + if (ch == STX) + { + clearRxBuf(); + _rxState = RxStateData; + } + else + _rxState = RxStateIdle; + } + break; + + case RxStateData: + { + if (ch == DLE) + _rxState = RxStateEscape; + else + appendRxBuf(ch); + } + break; + + case RxStateEscape: + { + if (ch == ETX) + { + // add fcs for DLE, ETX + _rxFcs = RHcrc_ccitt_update(_rxFcs, DLE); + _rxFcs = RHcrc_ccitt_update(_rxFcs, ETX); + _rxState = RxStateWaitFCS1; // End frame + } + else if (ch == DLE) + { + appendRxBuf(ch); + _rxState = RxStateData; + } + else + _rxState = RxStateIdle; // Unexpected + } + break; + + case RxStateWaitFCS1: + { + _rxRecdFcs = ch << 8; + _rxState = RxStateWaitFCS2; + } + break; + + case RxStateWaitFCS2: + { + _rxRecdFcs |= ch; + _rxState = RxStateIdle; + validateRxBuf(); + } + break; + + default: // Else some compilers complain + break; + } +} + +void RH_Serial::clearRxBuf() +{ + _rxBufValid = false; + _rxFcs = 0xffff; + _rxBufLen = 0; +} + +void RH_Serial::appendRxBuf(uint8_t ch) +{ + if (_rxBufLen < RH_SERIAL_MAX_PAYLOAD_LEN) + { + // Normal data, save and add to FCS + _rxBuf[_rxBufLen++] = ch; + _rxFcs = RHcrc_ccitt_update(_rxFcs, ch); + } + // If the buffer overflows, we dont record the trailing data, and the FCS will be wrong, + // causing the message to be dropped when the FCS is received +} + +// Check whether the latest received message is complete and uncorrupted +void RH_Serial::validateRxBuf() +{ + if (_rxRecdFcs != _rxFcs) + { + _rxBad++; + return; + } + + // Extract the 4 headers + _rxHeaderTo = _rxBuf[0]; + _rxHeaderFrom = _rxBuf[1]; + _rxHeaderId = _rxBuf[2]; + _rxHeaderFlags = _rxBuf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +bool RH_Serial::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + if (buf && len) + { + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _rxBufLen-RH_SERIAL_HEADER_LEN) + *len = _rxBufLen-RH_SERIAL_HEADER_LEN; + memcpy(buf, _rxBuf+RH_SERIAL_HEADER_LEN, *len); + } + clearRxBuf(); // This message accepted and cleared + return true; +} + +// Caution: this may block +bool RH_Serial::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_SERIAL_MAX_MESSAGE_LEN) + return false; + + if (!waitCAD()) + return false; // Check channel activity + + _txFcs = 0xffff; // Initial value + _serial.write(DLE); // Not in FCS + _serial.write(STX); // Not in FCS + // First the 4 headers + txData(_txHeaderTo); + txData(_txHeaderFrom); + txData(_txHeaderId); + txData(_txHeaderFlags); + // Now the payload + while (len--) + txData(*data++); + // End of message + _serial.write(DLE); + _txFcs = RHcrc_ccitt_update(_txFcs, DLE); + _serial.write(ETX); + _txFcs = RHcrc_ccitt_update(_txFcs, ETX); + + // Now send the calculated FCS for this message + _serial.write((_txFcs >> 8) & 0xff); + _serial.write(_txFcs & 0xff); + return true; +} + +void RH_Serial::txData(uint8_t ch) +{ + if (ch == DLE) // DLE stuffing required? + _serial.write(DLE); // Not in FCS + _serial.write(ch); + _txFcs = RHcrc_ccitt_update(_txFcs, ch); +} + +uint8_t RH_Serial::maxMessageLength() +{ + return RH_SERIAL_MAX_MESSAGE_LEN; +} + +#endif // HAVE_SERIAL diff --git a/RH_Serial.h b/RH_Serial.h new file mode 100644 index 0000000..9f86508 --- /dev/null +++ b/RH_Serial.h @@ -0,0 +1,277 @@ +// RH_Serial.h +// +// Copyright (C) 2014 Mike McCauley +// $Id: RH_Serial.h,v 1.14 2020/01/07 23:35:02 mikem Exp $ + +// Works with any serial port. Tested with Arduino Mega connected to Serial1 +// Also works with 3DR Radio V1.3 Telemetry kit (serial at 57600baud) + +#ifndef RH_Serial_h +#define RH_Serial_h + +#include +#if (RH_PLATFORM == RH_PLATFORM_STM32F2) + #define HardwareSerial USARTSerial +#elif defined (ARDUINO_ARCH_STM32F4) + #include +#elif (RH_PLATFORM == RH_PLATFORM_ATTINY_MEGA) + #include +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(ARDUINO_attinyxy6) +// AT Tiny Mega 3216 etc + #define HardwareSerial UartClass +#else + #include +#endif + +// Special characters +#define STX 0x02 +#define ETX 0x03 +#define DLE 0x10 +#define SYN 0x16 + +// Maximum message length (including the headers) we are willing to support +#define RH_SERIAL_MAX_PAYLOAD_LEN 64 + +// The length of the headers we add. +// The headers are inside the payload and are therefore protected by the FCS +#define RH_SERIAL_HEADER_LEN 4 + +// This is the maximum message length that can be supported by this library. +// It is an arbitrary limit. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for 4 bytes of address and header and payload to be included in the 64 byte encryption limit. +// the one byte payload length is not encrpyted +#ifndef RH_SERIAL_MAX_MESSAGE_LEN +#define RH_SERIAL_MAX_MESSAGE_LEN (RH_SERIAL_MAX_PAYLOAD_LEN - RH_SERIAL_HEADER_LEN) +#endif + + +///////////////////////////////////////////////////////////////////// +/// \class RH_Serial RH_Serial.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via a serial connection +/// +/// This class sends and received packetized messages over a serial connection. +/// It can be used for point-to-point or multidrop, RS232, RS488 or other serial connections as +/// supported by your controller hardware. +/// It can also be used to communicate via radios with serial interfaces such as: +/// - APC220 Radio Data Module http://www.dfrobot.com/image/data/TEL0005/APC220_Datasheet.pdf +/// http://www.dfrobot.com/image/data/TEL0005/APC220_Datasheet.pdf +/// - 3DR Telemetry Radio https://store.3drobotics.com/products/3dr-radio +/// - HopeRF HM-TR module http://www.hoperf.com/upload/rf_app/HM-TRS.pdf +/// - Others +/// +/// Compiles and runs on Linux, OSX and all the microprocessers and MCUs suported by +/// radiohead. On Linux and OSX, a RadioHead specific version of HardwareSerial (in RHutil/HardwareSerial.*) +/// encapsulates access to any serial port (or suported USB-serial converter) +/// +/// The packetised messages include message encapsulation, headers, a message payload and a checksum. +/// It therefore can support robust binary message passing with error-detection and retransmission +/// when used with the appropriate manager. This allows reliable serial communicaitons even over very long +/// lines where noise might otherwise affect reliablity of the communications. +/// +/// \par Packet Format +/// +/// All messages sent and received by this RH_Serial Driver conform to this packet format: +/// \code +/// DLE +/// STX +/// TO Header (1 octet) +/// FROM Header (1 octet) +/// ID Header (1 octet) +/// FLAGS Header (1 octet) +/// Message payload (0 to 60 octets) +/// DLE +/// ETX +/// Frame Check Sequence FCS CCITT CRC-16 (2 octets) +/// \endcode +/// +/// If any of octets from TO header through to the end of the payload are a DLE, +/// then they are preceded by a DLE (ie DLE stuffing). +/// The FCS covers everything from the TO header to the ETX inclusive, but not any stuffed DLEs +/// +/// \par Physical connection +/// +/// The physical connection to your serial port will depend on the type of platform you are on. +/// +/// For example, many arduinos only support a single Serial port on pins 0 and 1, +/// which is shared with the USB host connections. On such Arduinos, it is not possible to use both +/// RH_Serial on the Serial port as well as using the Serial port for debugand other printing or communications. +/// +/// On Arduino Mega and Due, there are 4 serial ports: +/// - Serial: this is the serial port connected to the USB interface and the programming host. +/// - Serial1: on pins 18 (Tx) and 19 (Rx) +/// - Serial2: on pins 16 (Tx) and 17 (Rx) +/// - Serial3: on pins 14 (Tx) and 15 (Rx) +/// +/// On Uno32, there are 2 serial ports: +/// - SerialUSB: this is the port for the USB host connection. +/// - Serial1: on pins 39 (Rx) and 40 (Tx) +/// +/// On Maple and Flymaple, there are 4 serial ports: +/// - SerialUSB: this is the port for the USB host connection. +/// - Serial1: on pins 7 (Tx) and 8 (Rx) +/// - Serial2: on pins 0 (Rx) and 1 (Tx) +/// - Serial3: on pins 29 (Tx) and 30 (Rx) +/// +/// On Linux and OSX there can be any number of serial ports. +/// - On Linux, names like /dev/ttyUSB0 (for a FTDO USB-serial converter) +/// - On OSX, names like /dev/tty.usbserial-A501YSWL (for a FTDO USB-serial converter) +/// +/// On STM32 F4 Discovery with Arduino and Arduino_STM32, there are 4 serial ports. We had success with port 2 +/// (TX on pin PA2 and RX on pin PA3) and initialising the driver like this: +/// RH_Serial driver(Serial2); +/// +/// Note that it is necessary for you to select which Serial port your RF_Serial will use and pass it to the +/// contructor. On Linux you must pass an instance of HardwareSerial. +/// +/// \par Testing +/// +/// You can test this class and the RHReliableDatagram manager +/// on Unix and OSX with back-to-back connected FTDI USB-serial adapters. +/// Back-to-back means the TX of one is connected to the RX of the other and vice-versa. +/// You should also join the ground pins. +/// +/// Assume the 2 USB-serial adapters are connected by USB +/// and have been assigned device names: +/// /dev/ttyUSB0 and /dev/ttyUSB1. +/// Build the example RHReliableDatagram client and server programs: +/// \code +/// tools/simBuild examples/serial/serial_reliable_datagram_server/serial_reliable_datagram_server.ino +/// tools/simBuild examples/serial/serial_reliable_datagram_client/serial_reliable_datagram_client.ino +/// \endcode +/// In one window run the server, specifying the device to use as an environment variable: +/// \code +/// RH_HARDWARESERIAL_DEVICE_NAME=/dev/ttyUSB1 ./serial_reliable_datagram_server +/// \endcode +/// And in another window run the client, specifying the other device to use as an environment variable: +/// \code +/// RH_HARDWARESERIAL_DEVICE_NAME=/dev/ttyUSB0 ./serial_reliable_datagram_client +/// \endcode +/// You should see the 2 programs passing messages to each other. +/// +class RH_Serial : public RHGenericDriver +{ +public: + /// Constructor + /// \param[in] serial Reference to the HardwareSerial port which will be used by this instance. + /// On Unix and OSX, this is an instance of RHutil/HardwareSerial. On + /// Arduino and other, it is an instance of the built in HardwareSerial class. + RH_Serial(HardwareSerial& serial); + + /// Return the HardwareSerial port in use by this instance + /// \return The current HardwareSerial as a reference + HardwareSerial& serial(); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Tests whether a new message is available + /// This can be called multiple times in a timeout loop. + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available(); + + /// Wait until a new message is available from the driver. + /// Blocks until a complete message is received as reported by available() + /// \param[in] polldelay Time between polling available() in milliseconds. This can be useful + /// in multitaking environment like Linux to prevent waitAvailableTimeout + /// using all the CPU while polling for receiver activity + virtual void waitAvailable(uint16_t polldelay = 0); + + /// Wait until a new message is available from the driver or the timeout expires. + /// Blocks until a complete message is received as reported by available() or the timeout expires. + /// \param[in] timeout The maximum time to wait in milliseconds + /// \param[in] polldelay Time between polling available() in milliseconds. This can be useful + /// in multitaking environment like Linux to prevent waitAvailableTimeout + /// using all the CPU while polling for receiver activity + /// \return true if a message is available as reported by available(), false on timeout. + virtual bool waitAvailableTimeout(uint16_t timeout, uint16_t polldelay = 0); + + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is NOT permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send (> 0) + /// \return true if the message length was valid and it was correctly queued for transmit + virtual bool send(const uint8_t* data, uint8_t len); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + +protected: + /// \brief Defines different receiver states in teh receiver state machine + typedef enum + { + RxStateInitialising = 0, ///< Before init() is called + RxStateIdle, ///< Waiting for an STX + RxStateDLE, ///< Waiting for the DLE after STX + RxStateData, ///< Receiving data + RxStateEscape, ///< Got a DLE while receiving data. + RxStateWaitFCS1, ///< Got DLE ETX, waiting for first FCS octet + RxStateWaitFCS2 ///< Waiting for second FCS octet + } RxState; + + /// HAndle a character received from the serial port. IMplements + /// the receiver state machine + void handleRx(uint8_t ch); + + /// Empties the Rx buffer + void clearRxBuf(); + + /// Adds a charater to the Rx buffer + void appendRxBuf(uint8_t ch); + + /// Checks whether the Rx buffer contains valid data that is complete and uncorrupted + /// Check the FCS, the TO address, and extracts the headers + void validateRxBuf(); + + /// Sends a single data octet to the serial port. + /// Implements DLE stuffing and keeps track of the senders FCS + void txData(uint8_t ch); + + /// Reference to the HardwareSerial port we will use + HardwareSerial& _serial; + + /// The current state of the Rx state machine + RxState _rxState; + + /// Progressive FCS calc (CCITT CRC-16 covering all received data (but not stuffed DLEs), plus trailing DLE, ETX) + uint16_t _rxFcs; + + /// The received FCS at the end of the current message + uint16_t _rxRecdFcs; + + /// The Rx buffer + uint8_t _rxBuf[RH_SERIAL_MAX_PAYLOAD_LEN]; + + /// Current length of data in the Rx buffer + uint8_t _rxBufLen; + + /// True if the data in the Rx buffer is value and uncorrupted and complete message is available for collection + bool _rxBufValid; + + /// FCS for transmitted data + uint16_t _txFcs; +}; + +/// @example serial_reliable_datagram_client.ino +/// @example serial_reliable_datagram_server.ino +/// @example serial_gateway.ino +/// @example serial_encrypted_reliable_datagram_client.ino +/// @example serial_encrypted_reliable_datagram_server.ino + +#endif diff --git a/RH_TCP.cpp b/RH_TCP.cpp new file mode 100644 index 0000000..08682b9 --- /dev/null +++ b/RH_TCP.cpp @@ -0,0 +1,315 @@ +// RH_TCP.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RH_TCP.cpp,v 1.6 2017/01/12 23:58:00 mikem Exp $ + +#include + +// This can only build on Linux and compatible systems +#if (RH_PLATFORM == RH_PLATFORM_UNIX) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +RH_TCP::RH_TCP(const char* server) + : _server(server), + _rxBufLen(0), + _rxBufValid(false), + _socket(-1) +{ +} + +bool RH_TCP::init() +{ + if (!connectToServer()) + return false; + return sendThisAddress(_thisAddress); +} + +bool RH_TCP::connectToServer() +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int sfd, s; + struct sockaddr_storage peer_addr; + socklen_t peer_addr_len; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6 + hints.ai_socktype = SOCK_STREAM; // Stream socket + hints.ai_flags = AI_PASSIVE; // For wildcard IP address + hints.ai_protocol = 0; // Any protocol + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + std::string server(_server); + std::string port("4000"); + size_t indexOfSeparator = server.find_first_of(':'); + if (indexOfSeparator != std::string::npos) + { + port = server.substr(indexOfSeparator+1); + server.erase(indexOfSeparator); + } + + s = getaddrinfo(server.c_str(), port.c_str(), &hints, &result); + if (s != 0) + { + fprintf(stderr, "RH_TCP::connect getaddrinfo failed: %s\n", gai_strerror(s)); + return false; + } + + // getaddrinfo() returns a list of address structures. + // Try each address until we successfully connect(2). + // If socket(2) (or connect(2)) fails, we (close the socket + // and) try the next address. */ + + for (rp = result; rp != NULL; rp = rp->ai_next) + { + _socket = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (_socket == -1) + continue; + + if (connect(_socket, rp->ai_addr, rp->ai_addrlen) == 0) + break; /* Success */ + + close(_socket); + } + + if (rp == NULL) + { /* No address succeeded */ + fprintf(stderr, "RH_TCP::connect could not connect to %s\n", _server); + return false; + } + + freeaddrinfo(result); /* No longer needed */ + + // Now make the socket non-blocking + int on = 1; + int rc = ioctl(_socket, FIONBIO, (char *)&on); + if (rc < 0) + { + fprintf(stderr,"RH_TCP::init failed to set socket non-blocking: %s\n", strerror(errno)); + close(_socket); + _socket = -1; + return false; + } + return true; +} + +void RH_TCP::clearRxBuf() +{ + _rxBufValid = false; + _rxBufLen = 0; +} + +bool RH_TCP::checkForEvents() +{ + #define RH_TCP_SOCKETBUF_LEN 500 + static uint8_t socketBuf[RH_TCP_SOCKETBUF_LEN]; // Room for several messages + static uint16_t socketBufLen = 0; + + if (_socket < 0) + return false; + + // Read at most the amount of space we have left in the buffer + ssize_t count = read(_socket, socketBuf + socketBufLen, sizeof(socketBuf) - socketBufLen); + if (count < 0) + { + if (errno != EAGAIN) + { + fprintf(stderr,"RH_TCP::checkForEvents read error: %s\n", strerror(errno)); + close(_socket); + _socket = -1; + return false; + } + } + else if (count == 0) + { + // End of file + fprintf(stderr,"RH_TCP::checkForEvents unexpected end of file on read\n"); + close(_socket); + _socket = -1; + return false; + } + else + { + socketBufLen += count; + while (socketBufLen >= 5) + { + RHTcpTypeMessage* message = ((RHTcpTypeMessage*)socketBuf); + uint32_t len = ntohl(message->length); + uint32_t messageLen = len + sizeof(message->length); + if (len > sizeof(socketBuf) - sizeof(message->length)) + { + // Bogus length + fprintf(stderr, "RH_TCP::checkForEvents read ridiculous length: %d. Corrupt message stream? Aborting\n", len); + close(_socket); + _socket = -1; + return false; + } + if (socketBufLen >= len + sizeof(message->length)) + { + // Got at least all of this message + if (message->type == RH_TCP_MESSAGE_TYPE_PACKET && len >= 5) + { + // REVISIT: need to check if we are actually receiving? + // Its a new packet, extract the headers and payload + RHTcpPacket* packet = ((RHTcpPacket*)socketBuf); + _rxHeaderTo = packet->to; + _rxHeaderFrom = packet->from; + _rxHeaderId = packet->id; + _rxHeaderFlags = packet->flags; + uint32_t payloadLen = len - 5; + if (payloadLen <= sizeof(_rxBuf)) + { + // Enough room in our receiver buffer + memcpy(_rxBuf, packet->payload, payloadLen); + _rxBufLen = payloadLen; + _rxBufFull = true; + } + } + // check for other message types here + // Now remove the used message by copying the trailing bytes (maybe start of a new message?) + // to the top of the buffer + memcpy(socketBuf, socketBuf + messageLen, sizeof(socketBuf) - messageLen); + socketBufLen -= messageLen; + } + } + } + return true; // No faults +} + +void RH_TCP::validateRxBuf() +{ + // The headers have already been extracted + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +bool RH_TCP::available() +{ + if (_socket < 0) + return false; + if (checkForEvents()) + return false; // Som sort of IO failre + if (_rxBufFull) + { + validateRxBuf(); + _rxBufFull= false; + } + return _rxBufValid; +} + +// Block until something is available +void RH_TCP::waitAvailable(uint16_t polldelay) +{ + waitAvailableTimeout(0); // 0 = Wait forever, no polldelay +} + +// Block until something is available or timeout expires +bool RH_TCP::waitAvailableTimeout(uint16_t timeout, uint16_t polldelay) +{ + int max_fd; + fd_set input; + int result; + + FD_ZERO(&input); + FD_SET(_socket, &input); + max_fd = _socket + 1; + + if (timeout) + { + struct timeval timer; + // Timeout is in milliseconds + timer.tv_sec = timeout / 1000; + timer.tv_usec = (timeout % 1000) * 1000; + result = select(max_fd, &input, NULL, NULL, &timer); + } + else + { + result = select(max_fd, &input, NULL, NULL, NULL); + } + if (result < 0) + fprintf(stderr, "RH_TCP::waitAvailableTimeout: select failed %s\n", strerror(errno)); + return result > 0; +} + +bool RH_TCP::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + + if (buf && len) + { + if (*len > _rxBufLen) + *len = _rxBufLen; + memcpy(buf, _rxBuf, *len); + } + clearRxBuf(); + return true; +} + +bool RH_TCP::send(const uint8_t* data, uint8_t len) +{ + if (!waitCAD()) + return false; // Check channel activity (prob not possible for this driver?) + + bool ret = sendPacket(data, len); + delay(10); // Wait for transmit to succeed. REVISIT: depends on length and speed + return ret; +} + +uint8_t RH_TCP::maxMessageLength() +{ + return RH_TCP_MAX_MESSAGE_LEN; +} + +void RH_TCP::setThisAddress(uint8_t address) +{ + RHGenericDriver::setThisAddress(address); + sendThisAddress(_thisAddress); +} + +bool RH_TCP::sendThisAddress(uint8_t thisAddress) +{ + if (_socket < 0) + return false; + RHTcpThisAddress m; + m.length = htonl(2); + m.type = RH_TCP_MESSAGE_TYPE_THISADDRESS; + m.thisAddress = thisAddress; + ssize_t sent = write(_socket, &m, sizeof(m)); + return sent > 0; +} + +bool RH_TCP::sendPacket(const uint8_t* data, uint8_t len) +{ + if (_socket < 0) + return false; + RHTcpPacket m; + m.length = htonl(len + 5); // 5 octets of header + m.type = RH_TCP_MESSAGE_TYPE_PACKET; + m.to = _txHeaderTo; + m.from = _txHeaderFrom; + m.id = _txHeaderId; + m.flags = _txHeaderFlags; + memcpy(m.payload, data, len); + ssize_t sent = write(_socket, &m, len + 9); // length + 5 octets header + return sent > 0; +} + +#endif diff --git a/RH_TCP.h b/RH_TCP.h new file mode 100644 index 0000000..34ef978 --- /dev/null +++ b/RH_TCP.h @@ -0,0 +1,194 @@ +// RH_TCP.h +// Author: Mike McCauley (mikem@aierspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RH_TCP.h,v 1.4 2015/08/13 02:45:47 mikem Exp $ +#ifndef RH_TCP_h +#define RH_TCP_h + +#include +#include + +///////////////////////////////////////////////////////////////////// +/// \class RH_TCP RH_TCP.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via sockets on a Linux simulator +/// +/// \par Overview +/// +/// This class is intended to support the testing of RadioHead manager classes and simulated sketches +/// on a Linux host. +/// RH_TCP class sends messages to and from other simulator sketches via sockets to a 'Luminiferous Ether' +/// simulator server (provided). +/// Multiple instances of simulated clients and servers can run on a single Linux server, +/// passing messages to each other via the etherSimulator.pl server. +/// +/// Simple RadioHead sketches can be compiled and run on Linux using a build script and some support files. +/// +/// \par Running simulated sketches +/// +/// \code +/// cd whatever/RadioHead +/// # build the client for Linux: +/// tools/simBuild examples/simulator/simulator_reliable_datagram_client/simulator_reliable_datagram_client.ino +/// # build the server for Linux: +/// tools/simBuild examples/simulator/simulator_reliable_datagram_server/simulator_reliable_datagram_server.ino +/// # in one window, run the simulator server: +/// tools/etherSimulator.pl +/// # in another window, run the server +/// ./simulator_reliable_datagram_server +/// # in another window, run the client: +/// ./simulator_reliable_datagram_client +/// # see output: +/// Sending to simulator_reliable_datagram_server +/// got reply from : 0x02: And hello back to you +/// Sending to simulator_reliable_datagram_server +/// got reply from : 0x02: And hello back to you +/// Sending to simulator_reliable_datagram_server +/// got reply from : 0x02: And hello back to you +/// ... +/// \endcode +/// +/// You can change the listen port and the simulated baud rate with +/// command line arguments passed to etherSimulator.pl +/// +/// \par Implementation +/// +/// etherServer.pl is a conventional server written in Perl. +/// listens on a TCP socket (defaults to port 4000) for connections from sketch simulators +/// using RH_TCP as theur driver. +/// The simulated sketches send messages out to the 'ether' over the TCP connection to the etherServer. +/// etherServer manages the delivery of each message to any other RH_TCP sketches that are running. +/// +/// \par Prerequisites +/// +/// g++ compiler installed and in your $PATH +/// Perl +/// Perl POE library +/// +class RH_TCP : public RHGenericDriver +{ +public: + /// Constructor + /// \param[in] server Name and optionally the port number of the ether simulator server to contact. + /// Format is "name[:port]", where name can be any valid host name or address (IPV4 or IPV6). + /// The trailing :port is optional, and port can be any valid + /// port name or port number. + RH_TCP(const char* server = "localhost:4000"); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Tests whether a new message is available + /// from the Driver. + /// On most drivers, this will also put the Driver into RHModeRx mode until + /// a message is actually received by the transport, when it will be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available(); + + /// Wait until a new message is available from the driver. + /// Blocks until a complete message is received as reported by available() + /// \param[in] polldelay Time between polling available() in milliseconds. This can be useful + /// in multitaking environment like Linux to prevent waitAvailableTimeout + /// using all the CPU while polling for receiver activity + virtual void waitAvailable(uint16_t polldelay = 0); + + /// Wait until a new message is available from the driver + /// or the timeout expires + /// Blocks until a complete message is received as reported by available() + /// \param[in] timeout The maximum time to wait in milliseconds + /// \param[in] polldelay Time between polling available() in milliseconds. This can be useful + /// in multitaking environment like Linux to prevent waitAvailableTimeout + /// using all the CPU while polling for receiver activity + /// \return true if a message is available as reported by available() + virtual bool waitAvailableTimeout(uint16_t timeout, uint16_t polldelay = 0); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to the number of octets available in buf. The number be reset to the actual number of octets copied. + /// \return true if a valid message was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is NOT permitted. If the message is too long for the underlying radio technology, send() will + /// return false and will not send the message. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send (> 0) + /// \return true if the message length was valid and it was correctly queued for transmit + virtual bool send(const uint8_t* data, uint8_t len); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. + /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, + /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. + /// In promiscuous mode, all messages will be accepted regardless of the TO header. + /// In a conventional multinode system, all nodes will have a unique address + /// (which you could store in EEPROM). + /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, + /// allowing the possibilty of address spoofing). + /// \param[in] address The address of this node. + void setThisAddress(uint8_t address); + +protected: + +private: + /// Connect to the address and port specified by the server constructor argument. + /// Prepares the socket for use. + bool connectToServer(); + + /// Check for new messages from the ether simulator server + /// \return true if no faults (not necessarily if there was an event) + bool checkForEvents(); + + /// Clear the receive buffer + void clearRxBuf(); + + /// Sends thisAddress to the ether simulator server + /// in a RHTcpThisAddress message. + /// \param[in] thisAddress The node address of this node + /// \return true if successful + bool sendThisAddress(uint8_t thisAddress); + + /// Sends a message to the ether simulator server for delivery to + /// other nodes + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send (> 0) + /// \return true if successful + bool sendPacket(const uint8_t* data, uint8_t len); + + /// Address and port of the server to which messages are sent + /// and received using the protocol RHTcpPRotocol + const char* _server; + + /// The TCP socket used to communicate with the message server + int _socket; + + /// Buffer to receive RHTcpProtocol messages + uint8_t _rxBuf[RH_TCP_MAX_PAYLOAD_LEN + 5]; + uint16_t _rxBufLen; + bool _rxBufValid; + + /// Check whether the latest received message is complete and uncorrupted + void validateRxBuf(); + + // Used in the interrupt handlers + /// Buf is filled but not validated + volatile bool _rxBufFull; + +}; + +/// @example simulator_reliable_datagram_client.ino +/// @example simulator_reliable_datagram_server.ino + +#endif diff --git a/RHutil/HardwareSerial.cpp b/RHutil/HardwareSerial.cpp new file mode 100644 index 0000000..a2d8a75 --- /dev/null +++ b/RHutil/HardwareSerial.cpp @@ -0,0 +1,246 @@ +// HardwareSerial.cpp +// +// Copyright (C) 2015 Mike McCauley +// $Id: HardwareSerial.cpp,v 1.4 2020/08/05 04:32:19 mikem Exp mikem $ + +#include +#if (RH_PLATFORM == RH_PLATFORM_UNIX) + +#include + +#include +#include +#include +#include +#include +#include +#include + +HardwareSerial::HardwareSerial(const char* deviceName) + : _deviceName(deviceName), + _device(-1) +{ + // Override device name from environment + char* e = getenv("RH_HARDWARESERIAL_DEVICE_NAME"); + if (e) + _deviceName = e; +} + +void HardwareSerial::begin(int baud) +{ + if (openDevice()) + setBaud(baud); +} + +void HardwareSerial::end() +{ + closeDevice(); +} + +void HardwareSerial::flush() +{ + tcdrain(_device); +} + +int HardwareSerial::peek(void) +{ + printf("HardwareSerial::peek not implemented\n"); + return 0; +} + +int HardwareSerial::available() +{ + int bytes; + + if (ioctl(_device, FIONREAD, &bytes) != 0) + { + fprintf(stderr, "HardwareSerial::available ioctl failed: %s\n", strerror(errno)); + return 0; + } + return bytes; +} + +int HardwareSerial::read() +{ + uint8_t data; + ssize_t result = ::read(_device, &data, 1); + if (result != 1) + { + fprintf(stderr, "HardwareSerial::read read failed: %s\n", strerror(errno)); + return 0; + } +// printf("got: %02x\n", data); + return data; +} + +size_t HardwareSerial::write(uint8_t ch) +{ + size_t result = ::write(_device, &ch, 1); + if (result != 1) + { + fprintf(stderr, "HardwareSerial::write failed: %s\n", strerror(errno)); + return 0; + } +// printf("sent: %02x\n", ch); + return 1; // OK +} + +bool HardwareSerial::openDevice() +{ + if (_device == -1) + closeDevice(); + _device = open(_deviceName, O_RDWR | O_NOCTTY | O_NDELAY); + if (_device == -1) + { + // Could not open the port. + fprintf(stderr, "HardwareSerial::openDevice could not open %s: %s\n", _deviceName, strerror(errno)); + return false; + } + + // Device opened + fcntl(_device, F_SETFL, 0); + return true; +} + +bool HardwareSerial::closeDevice() +{ + if (_device != -1) + close(_device); + _device = -1; + return true; +} + +bool HardwareSerial::setBaud(int baud) +{ + speed_t speed; + + // This is kind of ugly, but its prob better than a case + if (baud == 50) + speed = B50; + else if (baud == 75) + speed = B75; + else if (baud == 110) + speed = B110; + else if (baud == 134) + speed = B134; + else if (baud == 150) + speed = B150; + else if (baud == 200) + speed = B200; + else if (baud == 300) + speed = B300; + else if (baud == 600) + speed = B600; + else if (baud == 1200) + speed = B1200; + else if (baud == 1800) + speed = B1800; + else if (baud == 2400) + speed = B2400; + else if (baud == 4800) + speed = B4800; + else if (baud == 9600) + speed = B9600; + else if (baud == 19200) + speed = B19200; + else if (baud == 38400) + speed = B38400; + else if (baud == 57600) + speed = B57600; +#ifdef B76800 + else if (baud == 76800) // Not available on Linux + speed = B76800; +#endif + else if (baud == 115200) + speed = B115200; + else if (baud == 230400) + speed = B230400; +#ifdef B460800 + else if (baud == 460800) // Not available on OSX + speed = B460800; +#endif +#ifdef B921600 + else if (baud == 921600) // Not available on OSX + speed = B921600; +#endif + else + { + fprintf(stderr, "HardwareSerial::setBaud: unsupported baud rate %d\n", baud); + return false; + } + + struct termios options; + // Get current options + if (tcgetattr(_device, &options) != 0) + { + fprintf(stderr, "HardwareSerial::setBaud: could not tcgetattr %s\n", strerror(errno)); + return false; + } + + // Set new speed options + cfsetispeed(&options, speed); + cfsetospeed(&options, speed); + // Enable the receiver and set local mode... + options.c_cflag |= (CLOCAL | CREAD); + + // Force mode to 8,N,1 + // to be compatible with Arduino HardwareSerial + // Should this be configurable? Prob not, must have 8 bits, dont need parity. + options.c_cflag &= ~(PARENB | CSTOPB | CSIZE); + options.c_cflag |= CS8; + + // Disable flow control and input character conversions + options.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL | INLCR); + + // Raw input: + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + // Raw output + options.c_oflag &= ~(OPOST | OCRNL | ONLCR); + + // Set the options in the port + if (tcsetattr(_device, TCSANOW, &options) != 0) + { + fprintf(stderr, "HardwareSerial::setBaud: could not tcsetattr %s\n", strerror(errno)); + return false; + } + + _baud = baud; + return true; +} + +// Block until something is available +void HardwareSerial::waitAvailable() +{ + waitAvailableTimeout(0); // 0 = Wait forever +} + +// Block until something is available or timeout expires +bool HardwareSerial::waitAvailableTimeout(uint16_t timeout) +{ + int max_fd; + fd_set input; + int result; + + FD_ZERO(&input); + FD_SET(_device, &input); + max_fd = _device + 1; + + if (timeout) + { + struct timeval timer; + // Timeout is in milliseconds + timer.tv_sec = timeout / 1000; + timer.tv_usec = (timeout % 1000) * 1000; + result = select(max_fd, &input, NULL, NULL, &timer); + } + else + { + result = select(max_fd, &input, NULL, NULL, NULL); + } + if (result < 0) + fprintf(stderr, "HardwareSerial::waitAvailableTimeout: select failed %s\n", strerror(errno)); + return result > 0; +} + +#endif diff --git a/RHutil/HardwareSerial.h b/RHutil/HardwareSerial.h new file mode 100644 index 0000000..5962189 --- /dev/null +++ b/RHutil/HardwareSerial.h @@ -0,0 +1,100 @@ +// HardwareSerial.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2015 Mike McCauley +// $Id: HardwareSerial.h,v 1.4 2020/08/05 04:32:19 mikem Exp mikem $ +#ifndef HardwareSerial_h +#define HardwareSerial_h + +#include + +///////////////////////////////////////////////////////////////////// +/// \class HardwareSerial HardwareSerial.h +/// \brief Encapsulates a Posix compliant serial port as a HarwareSerial +/// +/// This class provides access to a serial port on Unix and OSX. +/// It is equivalent to HardwareSerial in Arduino, and can be used by RH_Serial +/// We implement just enough to provide the services RadioHead needs. +/// Additional methods not present on Arduino are also provided for waiting for characters. +/// +/// The device port is configured for 8 bits, no parity, 1 stop bit and full raw transparency, so it can be used +/// to send and receive any 8 bit character. A limited range of baud rates is supported. +/// +/// \par Device Names +/// +/// Device naming conventions vary from OS to OS. ON linux, an FTDI serial port may have a name like +/// /dev/ttyUSB0. On OSX, it might be something like /dev/tty.usbserial-A501YSWL +/// \par errors +/// +/// A number of these methods print error messages to stderr in the event of an IO error. +class HardwareSerial +{ +public: + /// Constructor + // \param [in] deviceName Name of the derial port device to connect to + HardwareSerial(const char* deviceName); + + /// Open and configure the port. + /// The named port is opened, and the given baud rate is set. + /// The port is configure for raw input and output and 8,N,1 protocol + /// with no flow control. + /// This must be called before any other operations are attempted. + /// IO failures and unsupported baud rates will result in an error message on stderr. + /// \param[in] baud The desired baud rate. The only rates supported are: 50, 75, 110, 134, 150 + /// 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400. On some platform + /// such as Linux you may also use: 460800, 921600. + void begin(int baud); + + /// Close the port. + /// If begin() has previously been called successfully, the device port will be closed. + /// It may be reopened again with another call to begin(). + void end(); + + /// Flush remaining data. + /// Blocks until any data yet to be transmtted is sent. + void flush(); + + /// Peek at the nex available character without consuming it. + /// CAUTION: Not implemented. + int peek(void); + + /// Returns the number of bytes immediately available to be read from the + /// device. + /// \return 0 if none available else the number of characters available for immediate reading + int available(); + + /// Read and return the next available character. + /// If no character is available prints a message to stderr and returns 0; + /// \return The next available character + int read(); + + /// Transmit a single character oin the serial port. + /// Returns immediately. + /// IO errors are repored by printing aa message to stderr. + /// \param[in] ch The character to send. Anything in the range 0x00 to 0xff is permitted + /// \return 1 if successful else 0 + size_t write(uint8_t ch); + + // These are not usually in HardwareSerial but we + // need them in a Unix environment + + /// Wait until a character is available from the port. + void waitAvailable(); + + /// Wait until a a character is available from the port. + /// or the timeout expires + /// \param[in] timeout The maximum time to wait in milliseconds. 0 means wait forever. + /// \return true if a message is available as reported by available() + bool waitAvailableTimeout(uint16_t timeout); + +protected: + bool openDevice(); + bool closeDevice(); + bool setBaud(int baud); + +private: + const char* _deviceName; + int _device; // file desriptor + int _baud; +}; + +#endif diff --git a/RHutil/RasPi.cpp b/RHutil/RasPi.cpp new file mode 100644 index 0000000..f993980 --- /dev/null +++ b/RHutil/RasPi.cpp @@ -0,0 +1,176 @@ +// RasPi.cpp +// +// Routines for implementing RadioHead on Raspberry Pi +// using BCM2835 library for GPIO +// +// Contributed by Mike Poublon and used with permission + + +#include + +#if (RH_PLATFORM == RH_PLATFORM_RASPI) +#include +#include +#include "RasPi.h" + +//Initialize the values for sanity +timeval RHStartTime; + +void SPIClass::begin() +{ + //Set SPI Defaults + uint16_t divider = BCM2835_SPI_CLOCK_DIVIDER_256; + uint8_t bitorder = BCM2835_SPI_BIT_ORDER_MSBFIRST; + uint8_t datamode = BCM2835_SPI_MODE0; + + begin(divider, bitorder, datamode); +} + +void SPIClass::begin(uint16_t divider, uint8_t bitOrder, uint8_t dataMode) +{ + setClockDivider(divider); + setBitOrder(bitOrder); + setDataMode(dataMode); + + //Set CS pins polarity to low + bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, 0); + + bcm2835_spi_begin(); + + //Initialize a timestamp for millis calculation + gettimeofday(&RHStartTime, NULL); +} + +void SPIClass::end() +{ + //End the SPI + bcm2835_spi_end(); +} + +void SPIClass::setBitOrder(uint8_t bitOrder) +{ + //Set the SPI bit Order + bcm2835_spi_setBitOrder(bitOrder); +} + +void SPIClass::setDataMode(uint8_t mode) +{ + //Set SPI data mode + bcm2835_spi_setDataMode(mode); +} + +void SPIClass::setClockDivider(uint16_t rate) +{ + //Set SPI clock divider + bcm2835_spi_setClockDivider(rate); +} + +byte SPIClass::transfer(byte _data) +{ + //Set which CS pin to use for next transfers + bcm2835_spi_chipSelect(BCM2835_SPI_CS0); + //Transfer 1 byte + byte data; + data = bcm2835_spi_transfer((uint8_t)_data); + return data; +} + +void pinMode(unsigned char pin, unsigned char mode) +{ + if (mode == OUTPUT) + { + bcm2835_gpio_fsel(pin,BCM2835_GPIO_FSEL_OUTP); + } + else + { + bcm2835_gpio_fsel(pin,BCM2835_GPIO_FSEL_INPT); + } +} + +void digitalWrite(unsigned char pin, unsigned char value) +{ + bcm2835_gpio_write(pin,value); +} + +unsigned long millis() +{ + //Declare a variable to store current time + struct timeval RHCurrentTime; + //Get current time + gettimeofday(&RHCurrentTime,NULL); + //Calculate the difference between our start time and the end time + unsigned long difference = ((RHCurrentTime.tv_sec - RHStartTime.tv_sec) * 1000); + difference += ((RHCurrentTime.tv_usec - RHStartTime.tv_usec)/1000); + //Return the calculated value + return difference; +} + +void delay (unsigned long ms) +{ + //Implement Delay function + struct timespec ts; + ts.tv_sec=0; + ts.tv_nsec=(ms * 1000); + nanosleep(&ts,&ts); +} + +long random(long min, long max) +{ + long diff = max - min; + long ret = diff * rand() + min; + return ret; +} + +void SerialSimulator::begin(int baud) +{ + //No implementation neccesary - Serial emulation on Linux = standard console + // + //Initialize a timestamp for millis calculation - we do this here as well in case SPI + //isn't used for some reason + gettimeofday(&RHStartTime, NULL); +} + +size_t SerialSimulator::println(const char* s) +{ + print(s); + printf("\n"); +} + +size_t SerialSimulator::print(const char* s) +{ + printf(s); +} + +size_t SerialSimulator::print(unsigned int n, int base) +{ + if (base == DEC) + printf("%d", n); + else if (base == HEX) + printf("%02x", n); + else if (base == OCT) + printf("%o", n); + // TODO: BIN +} + +size_t SerialSimulator::print(char ch) +{ + printf("%c", ch); +} + +size_t SerialSimulator::println(char ch) +{ + printf("%c\n", ch); +} + +size_t SerialSimulator::print(unsigned char ch, int base) +{ + return print((unsigned int)ch, base); +} + +size_t SerialSimulator::println(unsigned char ch, int base) +{ + print((unsigned int)ch, base); + printf("\n"); +} + +#endif diff --git a/RHutil/RasPi.h b/RHutil/RasPi.h new file mode 100644 index 0000000..e32bc11 --- /dev/null +++ b/RHutil/RasPi.h @@ -0,0 +1,75 @@ +// RasPi.h +// +// Routines for implementing RadioHead on Raspberry Pi +// using BCM2835 library for GPIO +// Contributed by Mike Poublon and used with permission + +#ifndef RASPI_h +#define RASPI_h + +#include + +#include +#include +#include +#include + +typedef unsigned char byte; + +#ifndef NULL + #define NULL 0 +#endif + +#ifndef OUTPUT + #define OUTPUT BCM2835_GPIO_FSEL_OUTP +#endif + +class SPIClass +{ + public: + static byte transfer(byte _data); + // SPI Configuration methods + static void begin(); // Default + static void begin(uint16_t, uint8_t, uint8_t); + static void end(); + static void setBitOrder(uint8_t); + static void setDataMode(uint8_t); + static void setClockDivider(uint16_t); +}; + +extern SPIClass SPI; + +class SerialSimulator +{ + public: + #define DEC 10 + #define HEX 16 + #define OCT 8 + #define BIN 2 + + // TODO: move these from being inlined + static void begin(int baud); + static size_t println(const char* s); + static size_t print(const char* s); + static size_t print(unsigned int n, int base = DEC); + static size_t print(char ch); + static size_t println(char ch); + static size_t print(unsigned char ch, int base = DEC); + static size_t println(unsigned char ch, int base = DEC); +}; + +extern SerialSimulator Serial; + +void RasPiSetup(); + +void pinMode(unsigned char pin, unsigned char mode); + +void digitalWrite(unsigned char pin, unsigned char value); + +unsigned long millis(); + +void delay (unsigned long delay); + +long random(long min, long max); + +#endif diff --git a/RHutil/atomic.h b/RHutil/atomic.h new file mode 100644 index 0000000..0192198 --- /dev/null +++ b/RHutil/atomic.h @@ -0,0 +1,71 @@ +/* +* This is port of Dean Camera's ATOMIC_BLOCK macros for AVR to ARM Cortex M3 +* v1.0 +* Mark Pendrith, Nov 27, 2012. +* +* From Mark: +* >When I ported the macros I emailed Dean to ask what attribution would be +* >appropriate, and here is his response: +* > +* >>Mark, +* >>I think it's great that you've ported the macros; consider them +* >>public domain, to do with whatever you wish. I hope you find them >useful . +* >> +* >>Cheers! +* >>- Dean +*/ + +#ifdef __arm__ +#ifndef _CORTEX_M3_ATOMIC_H_ +#define _CORTEX_M3_ATOMIC_H_ + +static __inline__ uint32_t __get_primask(void) \ +{ uint32_t primask = 0; \ + __asm__ volatile ("MRS %[result], PRIMASK\n\t":[result]"=r"(primask)::); \ + return primask; } // returns 0 if interrupts enabled, 1 if disabled + +static __inline__ void __set_primask(uint32_t setval) \ +{ __asm__ volatile ("MSR PRIMASK, %[value]\n\t""dmb\n\t""dsb\n\t""isb\n\t"::[value]"r"(setval):); + __asm__ volatile ("" ::: "memory");} + +static __inline__ uint32_t __iSeiRetVal(void) \ +{ __asm__ volatile ("CPSIE i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ + __asm__ volatile ("" ::: "memory"); return 1; } + +static __inline__ uint32_t __iCliRetVal(void) \ +{ __asm__ volatile ("CPSID i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ + __asm__ volatile ("" ::: "memory"); return 1; } + +static __inline__ void __iSeiParam(const uint32_t *__s) \ +{ __asm__ volatile ("CPSIE i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ + __asm__ volatile ("" ::: "memory"); (void)__s; } + +static __inline__ void __iCliParam(const uint32_t *__s) \ +{ __asm__ volatile ("CPSID i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ + __asm__ volatile ("" ::: "memory"); (void)__s; } + +static __inline__ void __iRestore(const uint32_t *__s) \ +{ __set_primask(*__s); __asm__ volatile ("dmb\n\t""dsb\n\t""isb\n\t"); \ + __asm__ volatile ("" ::: "memory"); } + + +#define ATOMIC_BLOCK(type) \ +for ( type, __ToDo = __iCliRetVal(); __ToDo ; __ToDo = 0 ) + +#define ATOMIC_RESTORESTATE \ +uint32_t primask_save __attribute__((__cleanup__(__iRestore))) = __get_primask() + +#define ATOMIC_FORCEON \ +uint32_t primask_save __attribute__((__cleanup__(__iSeiParam))) = 0 + +#define NONATOMIC_BLOCK(type) \ +for ( type, __ToDo = __iSeiRetVal(); __ToDo ; __ToDo = 0 ) + +#define NONATOMIC_RESTORESTATE \ +uint32_t primask_save __attribute__((__cleanup__(__iRestore))) = __get_primask() + +#define NONATOMIC_FORCEOFF \ +uint32_t primask_save __attribute__((__cleanup__(__iCliParam))) = 0 + +#endif +#endif diff --git a/RHutil/simulator.h b/RHutil/simulator.h new file mode 100644 index 0000000..c2ed4bc --- /dev/null +++ b/RHutil/simulator.h @@ -0,0 +1,84 @@ +// simulator.h +// Lets Arduino RadioHead sketches run within a simulator on Linux as a single process +// Copyright (C) 2014 Mike McCauley +// $Id: simulator.h,v 1.4 2015/08/13 02:45:47 mikem Exp $ + +#ifndef simulator_h +#define simulator_h + +#include +#include +#include +#include + +// Equivalent types for common Arduino types like uint8_t are in stdint.h + +// Access to some globals +// Command line args passed to the process. +extern int _simulator_argc; +extern char** _simulator_argv; + +// Definitions for various Arduino functions +extern void delay(unsigned long ms); +extern unsigned long millis(); +extern long random(long to); +extern long random(long from, long to); + +// Equavalent to HardwareSerial in Arduino +// but outputs to stdout +class SerialSimulator +{ +public: +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 + + // TODO: move these from being inlined + void begin(int baud) {} + + size_t println(const char* s) + { + print(s); + return printf("\n"); + } + size_t print(const char* s) + { + return printf("%s", s); // This style prevent warnings from [-Wformat-security] + } + size_t print(unsigned int n, int base = DEC) + { + if (base == DEC) + return printf("%d", n); + else if (base == HEX) + return printf("%02x", n); + else if (base == OCT) + return printf("%o", n); + // TODO: BIN + else + return 0; + } + size_t print(char ch) + { + return printf("%c", ch); + } + size_t println(char ch) + { + return printf("%c\n", ch); + } + size_t print(unsigned char ch, int base = DEC) + { + return print((unsigned int)ch, base); + } + size_t println(unsigned char ch, int base = DEC) + { + print((unsigned int)ch, base); + return printf("\n"); + } + +}; + +// Global instance of the Serial output +extern SerialSimulator Serial; + +#endif diff --git a/RHutil_pigpio/RasPi.cpp b/RHutil_pigpio/RasPi.cpp new file mode 100644 index 0000000..3087ab5 --- /dev/null +++ b/RHutil_pigpio/RasPi.cpp @@ -0,0 +1,264 @@ +// RasPi.cpp +//(9/22/2019) Contributed by Brody M. This file is based off RHutil\RasPi.cpp +// but modified for the pigpio library instead of BCM2835. Original +// code maintained where possible. Unused code commented out and +// left in place. + +// Routines for implementing RadioHead on Raspberry Pi +// using BCM2835 library for GPIO +// +// Contributed by Mike Poublon and used with permission + + +#include + +#if (RH_PLATFORM == RH_PLATFORM_RASPI) +#include +#include +#include "RasPi.h" +#include + +int spiHandle; + +//Initialize the values for sanity +timeval RHStartTime; + +void SPIClass::begin() +{ + //Set SPI Defaults + //Retaining BCM2835 macros for compatibility with RadioHead + uint16_t divider = BCM2835_SPI_CLOCK_DIVIDER_256; + uint8_t bitorder = BCM2835_SPI_BIT_ORDER_MSBFIRST; + uint8_t datamode = BCM2835_SPI_MODE0; + begin(divider, bitorder, datamode); +} + +//void SPIClass::begin(uint32_t spiChannel, uint32_t spiBaud, uint32_t spiFlags) +void SPIClass::begin(uint16_t divider, uint8_t bitOrder, uint8_t dataMode) +{ + + //Set CS pins polarity to low + //bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, 0); + + //pigpio SPI Defailts + //SPI Speed + //BCM2835 divider of 256 is approx 1MHz SCLK, depending on model + //uint32_t spiBaud = 1000000; + //Spi Flag Settings + //21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + //b b b b b b R T n n n n W A u2 u1 u0 p2 p1 p0 m m + //m m bits = mode + //Mode 0 = 0 0 + + uint32_t spiBaud = convertClockDivider(divider); + //datamode is 0 to 3 on BCM2835 + uint32_t spiFlags = 0; //Zero is a good default start. + //on pigpio, the least sig 2 bits set datamode, which will probably be zero. + spiFlags = 0x00000000 | (uint32_t) dataMode; + //According to documentation, bitOrder for SPI MAIN in pigpio is always MSBFIRST. So bitOrder ignored. + printf("\nSPI Settings:\nBaud rate=%d\nFlags=%d\n\n", spiBaud, spiFlags); + spiHandle = spiOpen(0, spiBaud, spiFlags); //spiChannel assumed to be zero. + + //Initialize a timestamp for millis calculation + gettimeofday(&RHStartTime, NULL); +} + +void SPIClass::end() +{ + //End the SPI + //bcm2835_spi_end(); + spiClose(spiHandle); +} + +uint32_t SPIClass::convertClockDivider(uint16_t rate) +{ + //Simple divide default RPi SPI clock by divider amount. + //Nominal clock at 250MHz for Zero. + return 250000000/rate; +} +/* +//Thes functions aren't necessary +void SPIClass::setBitOrder(uint8_t bitOrder) +{ + //Set the SPI bit Order + bcm2835_spi_setBitOrder(bitOrder); +} + +void SPIClass::setDataMode(uint8_t mode) +{ + //Set SPI data mode + bcm2835_spi_setDataMode(mode); +} + +void SPIClass::setClockDivider(uint16_t rate) +{ + //Set SPI clock divider + bcm2835_spi_setClockDivider(rate); +} +*/ + +byte SPIClass::transfer(byte _data) +{ + char txByte[1] = {(char)_data}; + char rxByte[1]; + //For RF Compatibility, just transfer 1 byte + spiXfer(spiHandle, txByte, rxByte, 1); + return (byte)rxByte[0]; +} + + +//void pinMode(unsigned char pin, unsigned char mode) +void pinMode(uint8_t pin, WiringPinMode mode) +{ + if (mode == OUTPUT) + { + gpioSetMode(pin, PI_OUTPUT); + //bcm2835_gpio_fsel(pin,BCM2835_GPIO_FSEL_OUTP); + } + else if (mode == INPUT) + { + gpioSetMode(pin, PI_INPUT); + //bcm2835_gpio_fsel(pin,BCM2835_GPIO_FSEL_INPT); + } + else if (mode == INPUT_PULLUP) + { + gpioSetMode(pin, PI_INPUT); + gpioSetPullUpDown(pin, PI_PUD_UP); + } + else if (mode == INPUT_PULLDOWN) + { + gpioSetMode(pin, PI_INPUT); + gpioSetPullUpDown(pin, PI_PUD_DOWN); + } + else + { + //For safety + gpioSetMode(pin, PI_INPUT); + } +} + +void digitalWrite(unsigned char pin, unsigned char value) +{ + //bcm2835_gpio_write(pin,value); + //Could have just written gpioWrite(pin, value) + if(value == HIGH) + { + gpioWrite(pin, PI_ON); + } + else + { + gpioWrite(pin, PI_OFF); + } +} + +unsigned long millis() +{ + //Declare a variable to store current time + struct timeval RHCurrentTime; + //Get current time + gettimeofday(&RHCurrentTime,NULL); + //Calculate the difference between our start time and the end time + unsigned long difference = ((RHCurrentTime.tv_sec - RHStartTime.tv_sec) * 1000); + difference += ((RHCurrentTime.tv_usec - RHStartTime.tv_usec)/1000); + //Return the calculated value + return difference; +} + +void delay (unsigned long ms) +{ + //Implement Delay function + struct timespec ts; + ts.tv_sec=0; + ts.tv_nsec=(ms * 1000); + nanosleep(&ts,&ts); +} + +long random(long min, long max) +{ + long diff = max - min; + long ret = diff * rand() + min; + return ret; +} + +//****************************** +//* Attach Interupt +//* Emulate Arduino Function +//****************************** + +void attachInterrupt(unsigned char pin, void (*handler)(void), int mode) +{ + switch(mode) + { + case CHANGE: + gpioSetISRFunc(pin, EITHER_EDGE, 0, (void (*)(int,int,unsigned int))handler); + break; + case RISING: + gpioSetISRFunc(pin, RISING_EDGE, 0, (void (*)(int,int,unsigned int))handler); + break; + case FALLING: + gpioSetISRFunc(pin, FALLING_EDGE, 0, (void (*)(int,int,unsigned int))handler); + break; + default: + break; + } +} + +void SerialSimulator::begin(int baud) +{ + //No implementation neccesary - Serial emulation on Linux = standard console + // + //Initialize a timestamp for millis calculation - we do this here as well in case SPI + //isn't used for some reason + gettimeofday(&RHStartTime, NULL); +} + +size_t SerialSimulator::println(const char* s) +{ + size_t charsPrinted = 0; + charsPrinted = print(s); + printf("\n"); + return charsPrinted + 1; +} + +size_t SerialSimulator::print(const char* s) +{ + return (size_t)printf(s); +} + +size_t SerialSimulator::print(unsigned int n, int base) +{ + if (base == DEC) + return (size_t)printf("%d", n); + else if (base == HEX) + return (size_t)printf("%02x", n); + else if (base == OCT) + return (size_t)printf("%o", n); + // TODO: BIN + else + return 0; +} + +size_t SerialSimulator::print(char ch) +{ + return (size_t)printf("%c", ch); +} + +size_t SerialSimulator::println(char ch) +{ + return (size_t)printf("%c\n", ch); +} + +size_t SerialSimulator::print(unsigned char ch, int base) +{ + return print((unsigned int)ch, base); +} + +size_t SerialSimulator::println(unsigned char ch, int base) +{ + size_t charsPrinted = 0; + charsPrinted = print((unsigned int)ch, base); + printf("\n"); + return charsPrinted + 1; +} + +#endif diff --git a/RHutil_pigpio/RasPi.h b/RHutil_pigpio/RasPi.h new file mode 100644 index 0000000..cd5c9dd --- /dev/null +++ b/RHutil_pigpio/RasPi.h @@ -0,0 +1,214 @@ +// RasPi.h +//(9/22/2019) Contributed by Brody M. This file is based off RHutil\RasPi.h +// but modified for the pigpio library instead of BCM2835. Original +// code maintained where possible. Unused code commented out and +// left in place. WiringPinMode enumeration declaration borrowed from +// STM32ArduinoCompat\wirish.h. Also some enumeration declarations +// "borrowed" from bcm2835.h to maintain original code and for simplicity. + +// Routines for implementing RadioHead on Raspberry Pi +// using BCM2835 library for GPIO +// Contributed by Mike Poublon and used with permission + +#ifndef RASPI_h +#define RASPI_h + +#include + +#include +#include +#include +#include +#include + +typedef unsigned char byte; + +#ifndef NULL + #define NULL 0 +#endif + +#define HIGH 0x1 +#define LOW 0x0 + +#define CHANGE 1 +#define FALLING 2 +#define RISING 3 + +#define memcpy_P memcpy + +//#ifndef OUTPUT +// #define OUTPUT BCM2835_GPIO_FSEL_OUTP +//#endif + +class SPIClass +{ + public: + //pigpio SPI ID + //We need to make sure this handle can be accessed by all SPI Functions + static byte transfer(byte _data); + // SPI Configuration methods + static void begin(); // Default + //static void begin(uint32_t,uint32_t,uint32_t); + static void begin(uint16_t, uint8_t, uint8_t); + static void end(); + //static void setBitOrder(uint8_t); + //static void setDataMode(uint8_t); + //static void setClockDivider(uint16_t); + static uint32_t convertClockDivider(uint16_t); +}; + +extern SPIClass SPI; + +class SerialSimulator +{ + public: + #define DEC 10 + #define HEX 16 + #define OCT 8 + #define BIN 2 + + // TODO: move these from being inlined + static void begin(int baud); + static size_t println(const char* s); + static size_t print(const char* s); + static size_t print(unsigned int n, int base = DEC); + static size_t print(char ch); + static size_t println(char ch); + static size_t print(unsigned char ch, int base = DEC); + static size_t println(unsigned char ch, int base = DEC); +}; + +extern SerialSimulator Serial; + +void RasPiSetup(); + +//The WiringPinMode enumeration declaration is borrowed from STM32ArduinoCompat\wirish.h + +typedef enum WiringPinMode { + OUTPUT, /**< Basic digital output: when the pin is HIGH, the + voltage is held at +3.3v (Vcc) and when it is LOW, it + is pulled down to ground. */ + + OUTPUT_OPEN_DRAIN, /**< In open drain mode, the pin indicates + "low" by accepting current flow to ground + and "high" by providing increased + impedance. An example use would be to + connect a pin to a bus line (which is pulled + up to a positive voltage by a separate + supply through a large resistor). When the + pin is high, not much current flows through + to ground and the line stays at positive + voltage; when the pin is low, the bus + "drains" to ground with a small amount of + current constantly flowing through the large + resistor from the external supply. In this + mode, no current is ever actually sourced + from the pin. */ + + INPUT, /**< Basic digital input. The pin voltage is sampled; when + it is closer to 3.3v (Vcc) the pin status is high, and + when it is closer to 0v (ground) it is low. If no + external circuit is pulling the pin voltage to high or + low, it will tend to randomly oscillate and be very + sensitive to noise (e.g., a breath of air across the pin + might cause the state to flip). */ + + INPUT_ANALOG, /**< This is a special mode for when the pin will be + used for analog (not digital) reads. Enables ADC + conversion to be performed on the voltage at the + pin. */ + + INPUT_PULLUP, /**< The state of the pin in this mode is reported + the same way as with INPUT, but the pin voltage + is gently "pulled up" towards +3.3v. This means + the state will be high unless an external device + is specifically pulling the pin down to ground, + in which case the "gentle" pull up will not + affect the state of the input. */ + + INPUT_PULLDOWN, /**< The state of the pin in this mode is reported + the same way as with INPUT, but the pin voltage + is gently "pulled down" towards 0v. This means + the state will be low unless an external device + is specifically pulling the pin up to 3.3v, in + which case the "gentle" pull down will not + affect the state of the input. */ + + INPUT_FLOATING, /**< Synonym for INPUT. */ + + PWM, /**< This is a special mode for when the pin will be used for + PWM output (a special case of digital output). */ + + PWM_OPEN_DRAIN, /**< Like PWM, except that instead of alternating + cycles of LOW and HIGH, the voltage on the pin + consists of alternating cycles of LOW and + floating (disconnected). */ +} WiringPinMode; + +void pinMode(uint8_t pin, WiringPinMode mode); + +//void pinMode(unsigned char pin, unsigned char mode); + +void digitalWrite(unsigned char pin, unsigned char value); + +unsigned long millis(); + +void delay (unsigned long delay); + +long random(long min, long max); + +void attachInterrupt(unsigned char pin, void (*handler)(void), int mode); + + + +//The following lines are borrowed from bcm2835.h, which is part of the BCM2835 library +//(https://www.airspayce.com/mikem/bcm2835/). The original RadioHead library expects +//BCM2835. We could eliminate this by modifying more RadioHead code, but this leaves +//the library more "original". Another option would be to include bcm2835.h directly, +//but this method is easier. + + typedef enum + { + BCM2835_SPI_BIT_ORDER_LSBFIRST = 0, + BCM2835_SPI_BIT_ORDER_MSBFIRST = 1 + }bcm2835SPIBitOrder; + + typedef enum + { + BCM2835_SPI_MODE0 = 0, + BCM2835_SPI_MODE1 = 1, + BCM2835_SPI_MODE2 = 2, + BCM2835_SPI_MODE3 = 3 + }bcm2835SPIMode; + + typedef enum + { + BCM2835_SPI_CS0 = 0, + BCM2835_SPI_CS1 = 1, + BCM2835_SPI_CS2 = 2, + BCM2835_SPI_CS_NONE = 3 + } bcm2835SPIChipSelect; + + typedef enum + { + BCM2835_SPI_CLOCK_DIVIDER_65536 = 0, + BCM2835_SPI_CLOCK_DIVIDER_32768 = 32768, + BCM2835_SPI_CLOCK_DIVIDER_16384 = 16384, + BCM2835_SPI_CLOCK_DIVIDER_8192 = 8192, + BCM2835_SPI_CLOCK_DIVIDER_4096 = 4096, + BCM2835_SPI_CLOCK_DIVIDER_2048 = 2048, + BCM2835_SPI_CLOCK_DIVIDER_1024 = 1024, + BCM2835_SPI_CLOCK_DIVIDER_512 = 512, + BCM2835_SPI_CLOCK_DIVIDER_256 = 256, + BCM2835_SPI_CLOCK_DIVIDER_128 = 128, + BCM2835_SPI_CLOCK_DIVIDER_64 = 64, + BCM2835_SPI_CLOCK_DIVIDER_32 = 32, + BCM2835_SPI_CLOCK_DIVIDER_16 = 16, + BCM2835_SPI_CLOCK_DIVIDER_8 = 8, + BCM2835_SPI_CLOCK_DIVIDER_4 = 4, + BCM2835_SPI_CLOCK_DIVIDER_2 = 2, + BCM2835_SPI_CLOCK_DIVIDER_1 = 1 + } bcm2835SPIClockDivider; + + +#endif diff --git a/RadioHead.h b/RadioHead.h new file mode 100644 index 0000000..6cf7c37 --- /dev/null +++ b/RadioHead.h @@ -0,0 +1,2095 @@ +// RadioHead.h +// Author: Mike McCauley DO NOT CONTACT THE AUTHOR DIRECTLY +// Copyright (C) 2014 Mike McCauley +// $Id: RadioHead.h,v 1.89 2020/08/05 04:32:19 mikem Exp mikem $ + +/*! \mainpage RadioHead Packet Radio library for embedded microprocessors + +This is the RadioHead Packet Radio library for embedded microprocessors. +It provides a complete object-oriented library for sending and receiving packetized messages +via a variety of common data radios and other transports on a range of embedded microprocessors. + +\par Download + +The version of the package that this documentation refers to can be downloaded +from https://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.143.zip + +You can always find the latest version of this documentation at +https://www.airspayce.com/mikem/arduino/RadioHead + +You can also find online help and discussion at +https://groups.google.com/group/radiohead-arduino +Please use that group for all questions and discussions on this topic. +Do not contact the author directly, unless it is to discuss commercial licensing. +Before asking a question or reporting a bug, please read +- https://en.wikipedia.org/wiki/Wikipedia:Reference_desk/How_to_ask_a_software_question +- https://www.catb.org/esr/faqs/smart-questions.html +- https://www.chiark.greenend.org.uk/~shgtatham/bugs.html + +Caution: Developing this type of software and using data radios +successfully is challenging and requires a substantial knowledge +base in software and radio and data transmission technologies and +theory. It may not be an appropriate project for beginners. If +you are a beginner, you will need to spend some time gaining +knowledge in these areas first. + +\par Overview + +RadioHead consists of 2 main sets of classes: Drivers and Managers. + +- Drivers provide low level access to a range of different packet radios and other packetized message transports. +- Managers provide high level message sending and receiving facilities for a range of different requirements. + +Every RadioHead program will have an instance of a Driver to +provide access to the data radio or transport, and usually an instance of a +Manager that uses that driver to send and receive messages for the +application. The programmer is required to instantiate a Driver +and a Manager, and to initialise the Manager. Thereafter the +facilities of the Manager can be used to send and receive +messages. + +It is also possible to use a Driver on its own, without a Manager, although this only allows unaddressed, +unreliable transport via the Driver's facilities. + +In some specialised use cases, it is possible to instantiate more than one Driver and more than one Manager. + +A range of different common embedded microprocessor platforms are supported, allowing your project to run +on your choice of processor. + +Example programs are included to show the main modes of use. + +\par Drivers + +The following Drivers are provided: + +- RH_RF22 +Works with Hope-RF +RF22B and RF23B based transceivers, and compatible chips and modules, +including the RFM22B transceiver module such as +hthis bare module: https://www.sparkfun.com/products/10153 +and this shield: https://www.sparkfun.com/products/11018 +and this board: https://www.anarduino.com/miniwireless +and RF23BP modules such as: https://www.anarduino.com/details.jsp?pid=130 +Supports GFSK, FSK and OOK. Access to other chip +features such as on-chip temperature measurement, analog-digital +converter, transmitter power control etc is also provided. + +- RH_RF24 +Works with Silicon Labs Si4460/4461/4463/4464 family of transceivers chip, and the equivalent +HopeRF RF24/26/27 family of chips and the HopeRF RFM24W/26W/27W modules. +Supports GFSK, FSK and OOK. Access to other chip +features such as on-chip temperature measurement, analog-digital +converter, transmitter power control etc is also provided. + +- RH_RF69 +Works with Hope-RF +RF69B based radio modules, such as the RFM69 module, (as used on the excellent Moteino and Moteino-USB +boards from LowPowerLab https://lowpowerlab.com/moteino/ ) +and compatible chips and modules such as RFM69W, RFM69HW, RFM69CW, RFM69HCW (Semtech SX1231, SX1231H). +Also works with Anarduino MiniWireless -CW and -HW boards https://www.anarduino.com/miniwireless/ including +the marvellous high powered MinWireless-HW (with 20dBm output for excellent range). +Supports GFSK, FSK. + +- RH_NRF24 +Works with Nordic nRF24 based 2.4GHz radio modules, such as nRF24L01 and others. +Also works with Hope-RF RFM73 +and compatible devices (such as BK2423). nRF24L01 and RFM73 can interoperate +with each other. + +- RH_NRF905 +Works with Nordic nRF905 based 433/868/915 MHz radio modules. + +- RH_NRF51 +Works with Nordic nRF51 compatible 2.4 GHz SoC/devices such as the nRF51822. +Also works with Sparkfun nRF52832 breakout board, with Arduino 1.8.9 and +Sparkfun nRF52 boards manager 0.2.3. + +- RH_RF95 +Works with Semtech SX1276/77/78/79, Modtronix inAir4 and inAir9, +and HopeRF RFM95/96/97/98 and other similar LoRa capable radios. +Supports Long Range (LoRa) with spread spectrum frequency hopping, large payloads etc. +FSK/GFSK/OOK modes are not (yet) supported. +Also works with the same chips on Linux using LoRa-file-ops Linux driver ioctls to +transmit and receive RadioHead compatible messages. +Requires a modified version of LoRa-file-ops driver to be installed, +and a compatible radio to be connected appropriately: +https://github.com/starnight/LoRa/tree/file-ops +See the RH_LoRaFileOps class documentation for more information. + +- RH_MRF89 +Works with Microchip MRF89XA and compatible transceivers. +and modules such as MRF89XAM9A and MRF89XAM8A. + +- RH_CC110 +Works with Texas Instruments CC110L transceivers and compatible modules such as +Anaren AIR BoosterPack 430BOOST-CC110L + +- RH_E32 +Works with EBYTE E32-TTL-1W serial radio transceivers (and possibly other transceivers in the same family) + +- RH_ASK +Works with a range of inexpensive ASK (amplitude shift keying) RF transceivers such as RX-B1 +(also known as ST-RX04-ASK) receiver; TX-C1 transmitter and DR3100 transceiver; FS1000A/XY-MK-5V transceiver; +HopeRF RFM83C / RFM85. Supports ASK (OOK). + +- RH_ABZ +Works with EcoNode SmartTrap, Tlera Grasshopper and family. Almost any board equipped with a muRata cmwx1zzabz module +should work. Tested with EcoNode SmartTrap, Arduino 1.8.9, GrumpyOldPizza Arduino Core for STM32L0. +When building for EcoNode SmartTrap in Arduino IDE, select board type Grasshopper-L082CZ. +This chip and GrumpyOldPizza Arduino Core for STM32L0 are now supported by PlatformIO: +https://docs.platformio.org/en/latest/platforms/ststm32.html#arduino-stm32l0-configuration-system + +- RH_SX126x +Works with Semtech SX1261/2/8 LoRa capable transceivers. +Can be confgigured to automatically controls any digital output pins required to control external RF switches or amplifiers. +Tested with NiceRF LoRa1268-915 and Teensy 3.1. +https://www.nicerf.com/lora-module/915mhz-lora-module-lora1262.html + +- RH_STM32WLx +Works with STM32WLE5xx and STM32WLE4xx family processors that include a SX1261/2 multi power amplifier transceiver. +Automatically manages the dedicated SPI interface and interrupt, and the internal SPI slave select and reset pins to the radio. +Examples provided. Requires the stm32duino package using these instructions: +https://community.st.com/t5/stm32-mcus/stm32-arduino-stm32duino-tutorial/ta-p/49649 + +- RH_Serial +Works with RS232, RS422, RS485, RS488 and other point-to-point and multidropped serial connections, +or with TTL serial UARTs such as those on Arduino and many other processors, +or with data radios with a +serial port interface. RH_Serial provides packetization and error detection over any hardware or +virtual serial connection. Also builds and runs on Linux and OSX. + +- RH_TCP +For use with simulated sketches compiled and running on Linux. +Works with tools/etherSimulator.pl to pass messages between simulated sketches, allowing +testing of Manager classes on Linux and without need for real radios or other transport hardware. + +- RHEncryptedDriver +Adds encryption and decryption to any RadioHead transport driver, using any encrpytion cipher +supported by ArduinoLibs Cryptographic Library https://rweather.github.io/arduinolibs/crypto.html +which also supports the newly adopted ASCON lightweight cryptography standard for IoT, as announced by +National Institute of Standards and Technology (NIST). + +Drivers can be used on their own to provide unaddressed, unreliable datagrams. +All drivers have the same identical API. +Or you can use any Driver with any of the Managers described below. + +We welcome contributions of well tested and well documented code to support other transports. + +If your radio or transciever is not on the list above, there is a good chance it +won't work without modifying RadioHead to suit it. If you wish for +support for another radio or transciever, and you send at least 2 of them to +AirSpayce Pty Ltd, we will consider adding support for it. + +\par Managers + +The drivers above all provide for unaddressed, unreliable (ie unacknowleged), variable +length messages, but if you need more than that, the following +Managers are provided: + +- RHDatagram + Addressed, unreliable variable length messages, with optional broadcast facilities. + +- RHReliableDatagram + Addressed, reliable, retransmitted, acknowledged variable length messages. + +- RHRouter + Multi-hop delivery of RHReliableDatagrams from source node to destination node via 0 or more + intermediate nodes, with manual, pre-programmed routing. + +- RHMesh + Multi-hop delivery of RHReliableDatagrams with automatic route discovery and rediscovery. + +Any Manager may be used with any Driver. + +\par Platforms + +A range of processors and platforms are supported: + +- Arduino and the Arduino IDE (version 1.0 to 1.8.1 and 2.1.1 and later) +Including Diecimila, Uno, Mega, Leonardo, Yun, Due, Zero, Minima and possibly others from https://arduino.cc/, + Also similar boards such as + - Moteino https://lowpowerlab.com/moteino/ + - Anarduino Mini https://www.anarduino.com/mini/ + - RedBearLab Blend V1.0 https://redbearlab.com/blend/ (with Arduino 1.0.5 and RedBearLab Blend Add-On version 20140701) + - MoteinoMEGA https://lowpowerlab.com/shop/moteinomega + (with Arduino 1.0.5 and the MoteinoMEGA Arduino Core + https://github.com/LowPowerLab/Moteino/tree/master/MEGA/Core) + - ESP8266 on Arduino IDE and Boards Manager per https://github.com/esp8266/Arduino + Tested using Arduino 1.6.8 with esp8266 by ESP8266 Community version 2.1.0 + Also Arduino 1.8.1 with esp8266 by SparkFun Electronics 2.5.2 + Examples serial_reliable_datagram_* and ask_* are shown to work. + CAUTION: The GHz radio included in the ESP8266 is + not yet supported. + CAUTION: tests here show that when powered by an FTDI USB-Serial converter, + the ESP8266 can draw so much power when transmitting on its GHz WiFi that VCC will sag + causing random crashes. We strongly recommend a large cap, say 1000uF 10V on VCC if you are also using the WiFi. + - Various Talk2 Whisper boards eg https://wisen.com.au/store/products/whisper-node-lora. + Use Arduino Board Manager to install the Talk2 code support. + - etc. + +- STM32 F4 Discover board, using Arduino 1.8.2 or later and + Roger Clarkes Arduino_STM from https://github.com/rogerclarkmelbourne/Arduino_STM32 + Caution: with this library and board, sending text to Serial causes the board to hang in mysterious ways. + Serial2 emits to PA2. The default SPI pins are SCK: PB3, MOSI PB5, MISO PB4. + We tested with PB0 as slave select and PB1 as interrupt pin for various radios. RH_ASK and RH_Serial also work. + Also works with stm32duino 1.8.0 from https://github.com/stm32duino/Arduino_Core_STM32, wich can be + installed on Arduino with BoardManager. Select board: STM32 Discovery F407. + +- ChipKIT Core with Arduino IDE on any ChipKIT Core supported Digilent processor (tested on Uno32) + https://chipkit.net/wiki/index.php?title=ChipKIT_core + +- Maple and Flymaple boards with libmaple and the Maple-IDE development environment + https://leaflabs.com/devices/maple/ and https://www.open-drone.org/flymaple + +- Teensy including Teensy 3.1 and earlier built using Arduino IDE 1.0.5 to 1.6.4 and later with + teensyduino addon 1.18 to 1.23 and later. + https://www.pjrc.com/teensy + +- Particle Photon https://store.particle.io/collections/photon and ARM3 based CPU with built-in + Wi-Fi transceiver and extensive IoT software suport. RadioHead does not support the built-in transceiver + but can be used to control other SPI based radios, Serial ports etc. + See below for details on how to build RadioHead for Photon + +- ATTiny built using Arduino IDE 1.8 and the ATTiny core from + https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json + using the instructions at + https://medium.com/jungletronics/attiny85-easy-flashing-through-arduino-b5f896c48189 + (Caution: these are very small processors and not all RadioHead features may be available, depending on memory requirements) + (Caution: we have not had good success building RH_ASK sketches for ATTiny 85 with SpenceKonde ATTinyCore) + +- ATtiny Mega (tinyAVR 1-series) chips supported by Spencer Konde's megaTinyCore + (https://github.com/SpenceKonde/megaTinyCore) + (on Arduino 1.8.9 or later) such as AtTiny 3216, ATtiny 1616 etc. These chips can be easily programmed through their + UPDI pin, using an ordinary Arduino board programmed as a jtag2updi programmer as described in + https://github.com/SpenceKonde/megaTinyCore/blob/master/MakeUPDIProgrammer.md. + Make sure you set the programmer type to jtag2updi in the Arduino Tools->Programmer menu. + See https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/ImportantInfo.md for links to pinouts + and pin numbering information for all the suported chips. + +- nRF51 compatible Arm chips such as nRF51822 with Arduino 1.6.4 and later using the procedures + in https://redbearlab.com/getting-started-nrf51822/ + +- nRF52 compatible Arm chips such as as Adafruit BLE Feather board + https://www.adafruit.com/product/3406 + +- Adafruit Feather. These are excellent boards that are available with a variety of radios. We tested with the + Feather 32u4 with RFM69HCW radio, with Arduino IDE 1.6.8 and the Adafruit AVR Boards board manager version 1.6.10. + https://www.adafruit.com/products/3076 + +- Adafruit Feather M0 boards with Arduino 1.8.1 and later, using the Arduino and Adafruit SAMD board support. + https://learn.adafruit.com/adafruit-feather-m0-basic-proto/using-with-arduino-ide + +- ESP32 built using Arduino IDE 1.8.9 or later using the ESP32 toolchain installed per + https://github.com/espressif/arduino-esp32 or from the Arduino IDE Board Manager. Tested up to version 3.0.2. + The internal 2.4GHz radio is not yet supported. Tested with RFM22 using SPI interface. + Uses the VSPI SPI bus by default. + You can enable use of the alternative HSPI bus for SPI by defining RH_ESP32_USE_HSPI in RadioHead.h. + +- Raspberry Pi + Uses BCM2835 library for GPIO https://www.airspayce.com/mikem/bcm2835/ + Currently works only with RH_NRF24 driver or other drivers that do not require interrupt support. + Contributed by Mike Poublon. + +- Linux and OSX + Using the RHutil/HardwareSerial class, the RH_Serial driver and any manager will + build and run on Linux and OSX. These can be used to build programs that talk securely and reliably to + Arduino and other processors or to other Linux or OSX hosts on a reliable, error detected (and possibly encrypted) datagram + protocol over various types of serial line. + +- Mongoose OS, courtesy Paul Austen. Mongoose OSis an Internet of Things Firmware Development Framework + available under Apache License Version 2.0. It supports low power, connected microcontrollers such as: + ESP32, ESP8266, TI CC3200, TI CC3220, STM32. + https://mongoose-os.com/ + +- muRata cmwx1zzabz module, which includes an STM32L0 processor, + a SX1276 LoRa radio and an antenna switch. + +- Raspberry Pi Pico, on Arduino, using either the Raspberry Pi Pico/RP2040 core by Earle F. Philhower, version 1.93, + installed per https://arduino-pico.readthedocs.io/_/downloads/en/latest/pdf/ + or the alternative Arduino MBED OS RP2040 core version 2.4.1. At this stage support is partial: RH_ASK works + but radio drivers that use interrupts do not (yet) work in either core. + +- Heltec Cube Cell family, eg HTCC-AB01: + https://heltec.org/project/htcc-ab01-v2/ + https://resource.heltec.cn/download/CubeCell/HTCC-AB01/HTCC-AB01_SchematicDiagram.pdf + These modules have an ASR6501 or ASR6502 ARM Cortex M0+ processor https://www.microchip.ua/wireless/ASR6501.pdf + which includes a built-in LoRa capable SX1262 radio, and which is supported by the RadioHead RH_SX126x driver. + Demonstrated interoperation with RH_RF95 driver. + Build with the CubeCell board support 1.5.0 via the Arduino Board Manager per + https://docs.heltec.org/en/node/asr650x/asr650x_general_docs/quick_start/cubecell-use-arduino-board-manager.html + Tested with 1.5.0 in Arduino IDE. + RH_ASK driver works with this board, but since the timer interrupt + takes the timeout in milliseconds, not microseconds, the fastest bit rate + we can support is 1000 / 8 = 125 bits per second. + +Other platforms are partially supported, such as Generic AVR 8 bit processors, MSP430. +We welcome contributions that will expand the range of supported platforms. + +If your processor is not on the list above, there is a good chance it +wont work without modifying RadioHead to suit it. If you wish for +support for another processor, and you send 2 of them to +AirSpayce Pty Ltd, we will consider adding support for it. + +RadioHead is available (through the efforts of others) +for PlatformIO. PlatformIO is a cross-platform code builder and the missing library manager. +https://platformio.org/#!/lib/show/124/RadioHead + +\par History + +RadioHead was created in April 2014, substantially based on code from some of our other earlier Radio libraries: + +- RHMesh, RHRouter, RHReliableDatagram and RHDatagram are derived from the RF22 library version 1.39. +- RH_RF22 is derived from the RF22 library version 1.39. +- RH_RF69 is derived from the RF69 library version 1.2. +- RH_ASK is based on the VirtualWire library version 1.26, after significant conversion to C++. +- RH_Serial was new. +- RH_NRF24 is based on the NRF24 library version 1.12, with some significant changes. + +During this combination and redevelopment, we have tried to retain all the processor dependencies and support from +the libraries that were contributed by other people. However not all platforms can be tested by us, so if you +find that support from some platform has not been successfully migrated, please feel free to fix it and send us a +patch. + +Users of RHMesh, RHRouter, RHReliableDatagram and RHDatagram in the previous RF22 library will find that their +existing code will run mostly without modification. See the RH_RF22 documentation for more details. + +\par Installation + +For Arduino IDE, install in the usual way: unzip the distribution zip file to the libraries +sub-folder of your sketchbook. +The example sketches will be visible in in your Arduino, mpide, maple-ide or whatever. +https://arduino.cc/en/Guide/Libraries + +\par Building for Particle Photon + +The Photon is not supported by the Arduino IDE, so it takes a little effort to set up a build environment. +Heres what we did to enable building of RadioHead example sketches on Linux, +but there are other ways to skin this cat. +Basic reference for getting started is: https://particle-firmware.readthedocs.org/en/develop/build/ +- Download the ARM gcc cross compiler binaries and unpack it in a suitable place: +\code +cd /tmp +wget https://launchpad.net/gcc-arm-embedded/5.0/5-2015-q4-major/+download/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 +tar xvf gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 +\endcode +- If dfu-util and friends not installed on your platform, download dfu-util and friends to somewhere in your path +\code +cd ~/bin +wget https://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-util +wget https://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-suffix +wget https://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-prefix +\endcode +- Download the Particle firmware (contains headers and libraries require to compile Photon sketches) + to a suitable place: +\code +cd /tmp +wget https://github.com/spark/firmware/archive/develop.zip +unzip develop.zip +\endcode +- Make a working area containing the RadioHead library source code and your RadioHead sketch. You must + rename the sketch from .pde or .ino to application.cpp +\code +cd /tmp +mkdir RadioHead +cd RadioHead +cp /usr/local/projects/arduino/libraries/RadioHead/ *.h . +cp /usr/local/projects/arduino/libraries/RadioHead/ *.cpp . +cp /usr/local/projects/arduino/libraries/RadioHead/examples/cc110/cc110_client/cc110_client.pde application.cpp +\endcode +- Edit application.cpp and comment out any \#include so it looks like: +\code + // #include +\endcode +- Connect your Photon by USB. Put it in DFU mode as descibed in Photon documentation. Light should be flashing yellow +- Compile the RadioHead sketch and install it as the user program (this does not update the rest of the + Photon firmware, just the user part: +\code +cd /tmp/firmware-develop/main +PATH=$PATH:/tmp/gcc-arm-none-eabi-5_2-2015q4/bin make APPDIR=/tmp/RadioHead all PLATFORM=photon program-dfu +\endcode +- You should see RadioHead compile without errors and download the finished sketch into the Photon. + +\par Compatible Hardware Suppliers + +We have had good experiences with the following suppliers of RadioHead compatible hardware: + +- LittleBird https://littlebirdelectronics.com.au in Australia for all manner of Arduinos and radios. +- LowPowerLab https://lowpowerlab.com/moteino in USA for the excellent Moteino and Moteino-USB + boards which include Hope-RF RF69B radios on-board. +- Anarduino and HopeRF USA (https://www.hoperfusa.com and https://www.anarduino.com) who have a wide range + of HopeRF radios and Arduino integrated modules. +- SparkFun https://www.sparkfun.com/ in USA who design and sell a wide range of Arduinos and radio modules. +- Wisen https://wisen.com.au who design and sell a wide range of integrated radio/processor modules including the + excellent Talk2 range. + +\par Coding Style + +RadioHead is designed so it can run on small processors with very +limited resources and strict timing contraints. As a result, we +tend only to use the simplest and least demanding (in terms of memory and CPU) C++ +facilities. In particular we avoid as much as possible dynamic +memory allocation, and the use of complex objects like C++ +strings, IO and buffers. We are happy with this, but we are aware +that some people may think we are leaving useful tools on the +table. You should not use this code as an example of how to do +generalised C++ programming on well resourced processors. + +\par Code Contributions + +We welcome, and will consider for merging into the mainline, contributions of fixes, patches, improvements etc. +that meet the following criteria: + +- Are generally useful to more than a few people. +- Are thoroughly tested. +- Dont break anything else. +- Are backwards compatible. +- Are properly and completely documented. +- Conform to the coding style of the rest of the library. +- Clearly transfer the ownership of the intellectual property to Mike McCauley. +- Are posted on the Google group as a patch in unified Diff format, + made against the latest version of the library, downloaded from airspayce.com, as described above. + +There is currently no known fully up-to-date git repository, and at +present we have no intentions of making one ourselves. + +\par Donations + +This library is offered under a free GPL license for those who want to use it that way. +We try hard to keep it up to date, fix bugs +and to provide free support. If this library has helped you save time or money, please consider donating at +https://www.airspayce.com or here: + +\htmlonly
\endhtmlonly + +\subpage packingdata "Passing Sensor Data Between RadioHead nodes" + +\par Trademarks + +RadioHead is a trademark of AirSpayce Pty Ltd. The RadioHead mark was first used on April 12 2014 for +international trade, and is used only in relation to data communications hardware and software and related services. +It is not to be confused with any other similar marks covering other goods and services. + +\par Copyright + +This software is Copyright (C) 2011-2022 Mike McCauley. Use is subject to license +conditions. The main licensing options available are GPL V3 or Commercial: + +\par Open Source Licensing GPL V3 + +This is the appropriate option if you want to share the source code of your +application with everyone you distribute it to, and you also want to give them +the right to share who uses it. If you wish to use this software under Open +Source Licensing, you must contribute all your source code to the open source +community in accordance with the GPL Version 3 when your application is +distributed. See https://www.gnu.org/licenses/gpl-3.0.html + +\par Commercial Licensing + +This is the appropriate option if you are creating proprietary applications +and you are not prepared to distribute and share the source code of your +application. To purchase a commercial license, contact info@airspayce.com + +\par Revision History +\version 1.1 2014-04-14
+ Initial public release +\version 1.2 2014-04-23
+ Fixed various typos.
+ Added links to compatible Anarduino products.
+ Added RHNRFSPIDriver, RH_NRF24 classes to support Nordic NRF24 based radios. +\version 1.3 2014-04-28
+ Various documentation fixups.
+ RHDatagram::setThisAddress() did not set the local copy of thisAddress. Reported by Steve Childress.
+ Fixed a problem on Teensy with RF22 and RF69, where the interrupt pin needs to be set for input,
+ else pin interrupt doesn't work properly. Reported by Steve Childress and patched by + Adrien van den Bossche. Thanks.
+ Fixed a problem that prevented RF22 honouring setPromiscuous(true). Reported by Steve Childress.
+ Updated documentation to clarify some issues to do with maximum message lengths + reported by Steve Childress.
+ Added support for yield() on systems that support it (currently Arduino 1.5.5 and later) + so that spin-loops can suport multitasking. Suggested by Steve Childress.
+ Added RH_RF22::setGpioReversed() so the reversal it can be configured at run-time after + radio initialisation. It must now be called _after_ init(). Suggested by Steve Childress.
+\version 1.4 2014-04-29
+ Fixed further problems with Teensy compatibility for RH_RF22. Tested on Teensy 3.1. + The example/rf22_* examples now run out of the box with the wiring connections as documented for Teensy + in RH_RF22.
+ Added YIELDs to spin-loops in RHRouter, RHMesh and RHReliableDatagram, RH_NRF24.
+ Tested RH_Serial examples with Teensy 3.1: they now run out of the box.
+ Tested RH_ASK examples with Teensy 3.1: they now run out of the box.
+ Reduced default SPI speed for NRF24 from 8MHz to 1MHz on Teensy, to improve reliability when + poor wiring is in use.
+ on some devices such as Teensy.
+ Tested RH_NRF24 examples with Teensy 3.1: they now run out of the box.
+\version 1.5 2014-04-29
+ Added support for Nordic Semiconductor nRF905 transceiver with RH_NRF905 driver. Also + added examples for nRF905 and tested on Teensy 3.1 +\version 1.6 2014-04-30
+ NRF905 examples were missing +\version 1.7 2014-05-03
+ Added support for Arduino Due. Tested with RH_NRF905, RH_Serial, RH_ASK. + IMPORTANT CHANGE to interrupt pins on Arduino with RH_RF22 and RH_RF69 constructors: + previously, you had to specify the interrupt _number_ not the interrupt _pin_. Arduinos and Uno32 + are now consistent with all other platforms: you must specify the interrupt pin number. Default + changed to pin 2 (a common choice with RF22 shields). + Removed examples/maple/maple_rf22_reliable_datagram_client and + examples/maple/maple_rf22_reliable_datagram_client since the rf22 examples now work out + of the box with Flymaple. + Removed examples/uno32/uno32_rf22_reliable_datagram_client and + examples/uno32/uno32_rf22_reliable_datagram_client since the rf22 examples now work out + of the box with ChipKit Uno32. +\version 1.8 2014-05-08
+ Added support for YIELD in Teensy 2 and 3, suggested by Steve Childress.
+ Documentation updates. Clarify use of headers and Flags
+ Fixed misalignment in RH_RF69 between ModemConfigChoice definitions and the implemented choices + which meant you didnt get the choice you thought and GFSK_Rb55555Fd50 hung the transmitter.
+ Preliminary work on Linux simulator. +\version 1.9 2014-05-14
+ Added support for using Timer 2 instead of Timer 1 on Arduino in RH_ASK when + RH_ASK_ARDUINO_USE_TIMER2 is defined. With the kind assistance of + Luc Small. Thanks!
+ Updated comments in RHReliableDatagram concerning servers, retries, timeouts and delays. + Fixed an error in RHReliableDatagram where recvfrom return value was not checked. + Reported by Steve Childress.
+ Added Linux simulator support so simple RadioHead sketches can be compiled and run on Linux.
+ Added RH_TCP driver to permit message passing between simulated sketches on Linux.
+ Added example simulator sketches.
+ Added tools/etherSimulator.pl, a simulator of the 'Luminiferous Ether' that passes + messages between simulated sketches and can simulate random message loss etc.
+ Fixed a number of typos and improved some documentation.
+\version 1.10 2014-05-15
+ Added support for RFM73 modules to RH_NRF24. These 2 radios are very similar, and can interoperate + with each other. Added new RH_NRF24::TransmitPower enums for the RFM73, which has a different + range of available powers
+ reduced the default SPI bus speed for RH_NRF24 to 1MHz, since so many modules and CPU have problems + with 8MHz.
+\version 1.11 2014-05-18
+ Testing RH_RF22 with RFM23BP and 3.3V Teensy 3.1 and 5V Arduinos. + Updated documentation with respect to GPIO and antenna + control pins for RFM23. Updated documentation with respect to transmitter power control for RFM23
+ Fixed a problem with RH_RF22 driver, where GPIO TX and RX pins were not configured during + initialisation, causing poor transmit power and sensitivity on those RF22/RF23 devices where GPIO controls + the antenna selection pins. +\version 1.12 2014-05-20
+ Testing with RF69HW and the RH_RF69 driver. Works well with the Anarduino MiniWireless -CW and -HW + boards https://www.anarduino.com/miniwireless/ including + the marvellous high powered MinWireless-HW (with 20dBm output for excellent range).
+ Clarified documentation of RH_RF69::setTxPower values for different models of RF69.
+ Added RHReliableDatagram::resetRetransmissions().
+ Retransmission count precision increased to uin32_t.
+ Added data about actual power measurements from RFM22 module.
+\version 1.13 2014-05-23
+ setHeaderFlags(flags) changed to setHeaderFlags(set, clear), enabling any flags to be + individually set and cleared by either RadioHead or application code. Requested by Steve Childress.
+ Fixed power output setting for boost power on RF69HW for 18, 19 and 20dBm.
+ Added data about actual power measurements from RFM69W and RFM69HW modules.
+\version 1.14 2014-05-26
+ RH_RF69::init() now always sets the PA boost back to the default settings, else can get invalid + PA power modes after uploading new sketches without a power cycle. Reported by Bryan.
+ Added new macros RH_VERSION_MAJOR RH_VERSION_MINOR, with automatic maintenance in Makefile.
+ Improvements to RH_TCP: constructor now honours the server argument in the form "servername:port".
+ Added YIELD to RHReliableDatagram::recvfromAckTimeout. Requested by Steve Childress.
+ Fixed a problem with RH_RF22 reliable datagram acknowledgements that was introduced in version 1.13. + Reported by Steve Childress.
+\version 1.15 2014-05-27
+ Fixed a problem with the RadioHead .zip link. +\version 1.16 2014-05-30
+ Fixed RH_RF22 so that lastRssi() returns the signal strength in dBm. Suggested by Steve Childress.
+ Added support for getLastPreambleTime() to RH_RF69. Requested by Steve Childress.
+ RH_NRF24::init() now checks if there is a device connected and responding, else init() will fail. + Suggested by Steve Brown.
+ RHSoftwareSPI now initialises default values for SPI pins MOSI = 12, MISO = 11 and SCK = 13.
+ Fixed some problems that prevented RH_NRF24 working with mixed software and hardware SPI + on different devices: a race condition + due to slow SPI transfers and fast acknowledgement.
+\version 1.17 2014-06-02
+ Fixed a debug typo in RHReliableDatagram that was introduced in 1.16.
+ RH_NRF24 now sets default power, data rate and channel in init(), in case another + app has previously set different values without powerdown.
+ Caution: there are still problems with RH_NRF24 and Software SPI. Do not use.
+\version 1.18 2014-06-02
+ Improvements to performance of RH_NRF24 statusRead, allowing RH_NRF24 and Software SPI + to operate on slow devices like Arduino Uno.
+\version 1.19 2014-06-19
+ Added examples ask_transmitter.pde and ask_receiver.pde.
+ Fixed an error in the RH_RF22 doc for connection of Teensy to RF22.
+ Improved documentation of start symbol bit patterns in RH_ASK.cpp +\version 1.20 2014-06-24
+ Fixed a problem with compiling on platforms such as ATtiny where SS is not defined.
+ Added YIELD to RHMesh::recvfromAckTimeout().
+\version 1.21 2014-06-24
+ Fixed an issue in RH_Serial where characters might be lost with back-to-back frames. + Suggested by Steve Childress.
+ Brought previous RHutil/crc16.h code into mainline RHCRC.cpp to prevent name collisions + with other similarly named code in other libraries. Suggested by Steve Childress.
+k Fix SPI bus speed errors on 8MHz Arduinos. +\version 1.22 2014-07-01
+ Update RH_ASK documentation for common wiring connections.
+ Testing RH_ASK with HopeRF RFM83C/RFM85 courtesy Anarduino https://www.anarduino.com/
+ Testing RH_NRF24 with Itead Studio IBoard Pro https://imall.iteadstudio.com/iboard-pro.html + using both hardware SPI on the ITDB02 Parallel LCD Module Interface pins and software SPI + on the nRF24L01+ Module Interface pins. Documented wiring required.
+ Added support for AVR 1284 and 1284p, contributed by Peter Scargill. + Added support for Semtech SX1276/77/78 and HopeRF RFM95/96/97/98 and other similar LoRa capable radios + in LoRa mode only. Tested with the excellent MiniWirelessLoRa from + Anarduino https://www.anarduino.com/miniwireless
+\version 1.23 2014-07-03
+ Changed the default modulation for RH_RF69 to GFSK_Rb250Fd250, since the previous default + was not very reliable.
+ Documented RH_RF95 range tests.
+ Improvements to RH_RF22 RSSI readings so that lastRssi correctly returns the last message in dBm.
+\version 1.24 2014-07-18 + Added support for building RadioHead for STM32F4 Discovery boards, using the native STM Firmware libraries, + in order to support Codec2WalkieTalkie (https://www.airspayce.com/mikem/Codec2WalkieTalkie) + and other projects. See STM32ArduinoCompat.
+ Default modulation for RH_RF95 was incorrectly set to a very slow Bw125Cr48Sf4096 +\version 1.25 2014-07-25 + The available() function will longer terminate any current transmission, and force receive mode. + Now, if there is no unprocessed incoming message and an outgoing message is currently being transmitted, + available() will return false.
+ RHRouter::sendtoWait(uint8_t*, uint8_t, uint8_t, uint8_t) renamed to sendtoFromSourceWait due to conflicts + with new sendtoWait() with optional flags.
+ RHMEsh and RHRouter already supported end-to-end application layer flags, but RHMesh::sendtoWait() + and RHRouter::sendToWait have now been extended to expose a way to send optional application layer flags. +\version 1.26 2014-08-12 + Fixed a Teensy 2.0 compile problem due yield() not available on Teensy < 3.0.
+ Adjusted the algorithm of RH_RF69::temperatureRead() to more closely reflect reality.
+ Added functions to RHGenericDriver to get driver packet statistics: rxBad(), rxGood(), txGood().
+ Added RH_RF69::printRegisters().
+ RH_RF95::printRegisters() was incorrectly printing the register index instead of the address. + Reported by Phang Moh Lim.
+ RH_RF95, added definitions for some more registers that are usable in LoRa mode.
+ RH_RF95::setTxPower now uses RH_RF95_PA_DAC_ENABLE to achieve 21, 22 and 23dBm.
+ RH_RF95, updated power output measurements.
+ Testing RH_RF69 on Teensy 3.1 with RF69 on PJRC breakout board. OK.
+ Improvements so RadioHead will build under Arduino where SPI is not supported, such as + ATtiny.
+ Improvements so RadioHead will build for ATTiny using Arduino IDE and tinycore arduino-tiny-0100-0018.zip.
+ Testing RH_ASK on ATTiny85. Reduced RAM footprint. + Added helpful documentation. Caution: RAM memory is *very* tight on this platform.
+ RH_RF22 and RH_RF69, added setIdleMode() function to allow the idle mode radio operating state + to be controlled for lower idle power consumption at the expense of slower transitions to TX and RX.
+\version 1.27 2014-08-13 + All RH_RF69 modulation schemes now have data whitening enabled by default.
+ Tested and added a number of OOK modulation schemes to RH_RF69 Modem config table.
+ Minor improvements to a number of the faster RH_RF69 modulation schemes, but some slower ones + are still not working correctly.
+\version 1.28 2014-08-20 + Added new RH_RF24 driver to support Si446x, RF24/26/26, RFM24/26/27 family of transceivers. + Tested with the excellent + Anarduino Mini and RFM24W and RFM26W with the generous assistance of the good people at + Anarduino https://www.anarduino.com. +\version 1.29 2014-08-21 + Fixed a compile error in RH_RF24 introduced at the last minute in hte previous release.
+ Improvements to RH_RF69 modulation schemes: now include the AFCBW in teh ModemConfig.
+ ModemConfig RH_RF69::FSK_Rb2Fd5 and RH_RF69::GFSK_Rb2Fd5 are now working.
+\version 1.30 2014-08-25 + Fixed some compile problems with ATtiny84 on Arduino 1.5.5 reported by Glen Cook.
+\version 1.31 2014-08-27 + Changed RH_RF69 FSK and GFSK modulations from Rb2_4Fd2_4 to Rb2_4Fd4_8 and FSK_Rb4_8Fd4_8 to FSK_Rb4_8Fd9_6 + since the previous ones were unreliable (they had modulation indexes of 1).
+\version 1.32 2014-08-28 + Testing with RedBearLab Blend board https://redbearlab.com/blend/. OK.
+ Changed more RH_RF69 FSK and GFSK slowish modulations to have modulation index of 2 instead of 1. + This required chnaging the symbolic names.
+\version 1.33 2014-09-01 + Added support for sleep mode in RHGeneric driver, with new mode + RHModeSleep and new virtual function sleep().
+ Added support for sleep to RH_RF69, RH_RF22, RH_NRF24, RH_RF24, RH_RF95 drivers.
+\version 1.34 2014-09-19 + Fixed compile errors in example rf22_router_test.
+ Fixed a problem with RH_NRF24::setNetworkAddress, also improvements to RH_NRF24 register printing. + Patched by Yveaux.
+ Improvements to RH_NRF24 initialisation for version 2.0 silicon.
+ Fixed problem with ambigiguous print call in RH_RFM69 when compiling for Codec2.
+ Fixed a problem with RH_NRF24 on RFM73 where the LNA gain was not set properly, reducing the sensitivity + of the receiver. +\version 1.35 2014-09-19 + Fixed a problem with interrupt setup on RH_RF95 with Teensy3.1. Reported by AD.
+\version 1.36 2014-09-22 + Improvements to interrupt pin assignments for __AVR_ATmega1284__ and__AVR_ATmega1284P__, provided by + Peter Scargill.
+ Work around a bug in Arduino 1.0.6 where digitalPinToInterrupt is defined but NOT_AN_INTERRUPT is not.
+ \version 1.37 2014-10-19 + Updated doc for connecting RH_NRF24 to Arduino Mega.
+ Changes to RHGenericDriver::setHeaderFlags(), so that the default for the clear argument + is now RH_FLAGS_APPLICATION_SPECIFIC, which is less surprising to users. + Testing with the excellent MoteinoMEGA from LowPowerLab + https://lowpowerlab.com/shop/moteinomega with on-board RFM69W. + \version 1.38 2014-12-29 + Fixed compile warning on some platforms where RH_RF24::send and RH_RF24::writeTxFifo + did not return a value.
+ Fixed some more compiler warnings in RH_RF24 on some platforms.
+ Refactored printRegisters for some radios. Printing to Serial + is now controlled by the definition of RH_HAVE_SERIAL.
+ Added partial support for ARM M4 w/CMSIS with STM's Hardware Abstraction lib for + Steve Childress.
+ \version 1.39 2014-12-30 + Fix some compiler warnings under IAR.
+ RH_HAVE_SERIAL and Serial.print calls removed for ATTiny platforms.
+ \version 1.40 2015-03-09 + Added notice about availability on PlatformIO, thanks to Ivan Kravets.
+ Fixed a problem with RH_NRF24 where short packet lengths would occasionally not be trasmitted + due to a race condition with RH_NRF24_TX_DS. Reported by Mark Fox.
+ \version 1.41 2015-03-29 + RH_RF22, RH_RF24, RH_RF69 and RH_RF95 improved to allow driver.init() to be called multiple + times without reallocating a new interrupt, allowing the driver to be reinitialised + after sleeping or powering down. + \version 1.42 2015-05-17 + Added support for RH_NRF24 driver on Raspberry Pi, using BCM2835 + library for GPIO pin IO. Contributed by Mike Poublon.
+ Tested RH_NRF24 module with NRF24L01+PA+LNA SMA Antenna Wireless Transceiver modules + similar to: https://www.elecfreaks.com/wiki/index.php?title=2.4G_Wireless_nRF24L01p_with_PA_and_LNA + works with no software changes. Measured max power output 18dBm.
+ \version 1.43 2015-08-02 + Added RH_NRF51 driver to support Nordic nRF51 family processor with 2.4GHz radio such + as nRF51822, to be built on Arduino 1.6.4 and later. Tested with RedBearLabs nRF51822 board + and BLE Nano kit
+ \version 1.44 2015-08-08 + Fixed errors with compiling on some platforms without serial, such as ATTiny. + Reported by Friedrich Müller.
+ \version 1.45 2015-08-13 + Added support for using RH_Serial on Linux and OSX (new class RHutil/HardwareSerial + encapsulates serial ports on those platforms). Example examples/serial upgraded + to build and run on Linux and OSX using the tools/simBuild builder. + RHMesh, RHRouter and RHReliableDatagram updated so they can use RH_Serial without + polling loops on Linux and OSX for CPU efficiency.
+ \version 1.46 2015-08-14 + Amplified some doc concerning Linux and OSX RH_Serial. Added support for 230400 + baud rate in HardwareSerial.
+ Added sample sketches nrf51_audio_tx and nrf51_audio_rx which show how to + build an audio TX/RX pair with RedBear nRF51822 boards and a SparkFun MCP4725 DAC board. + Uses the built-in ADC of the nRF51822 to sample audio at 5kHz and transmit packets + to the receiver which plays them via the DAC.
+\version 1.47 2015-09-18 + Removed top level Makefile from distribution: its only used by the developer and + its presence confuses some people.
+ Fixed a problem with RHReliableDatagram with some versions of Raspberry Pi random() that causes + problems: random(min, max) sometimes exceeds its max limit. +\version 1.48 2015-09-30 + Added support for Arduino Zero. Tested on Arduino Zero Pro. +\version 1.49 2015-10-01 + Fixed problems that prevented interrupts working correctly on Arduino Zero and Due. + Builds and runs with 1.6.5 (with 'Arduino SAMD Boards' for Zero version 1.6.1) from arduino.cc. + Arduino version 1.7.7 from arduino.org is not currently supported. +\version 1.50 2015-10-25 + Verified correct building and operation with Arduino 1.7.7 from arduino.org. + Caution: You must burn the bootloader from 1.7.7 to the Arduino Zero before it will + work with Arduino 1.7.7 from arduino.org. Conversely, you must burn the bootloader from 1.6.5 + to the Arduino Zero before it will + work with Arduino 1.6.5 from arduino.cc. Sigh. + Fixed a problem with RH_NRF905 that prevented the power and frequency ranges being set + properly. Reported by Alan Webber. +\version 1.51 2015-12-11 + Changes to RH_RF6::setTxPower() to be compatible with SX1276/77/78/79 modules that + use RFO transmitter pins instead of PA_BOOST, such as the excellent + Modtronix inAir4 https://modtronix.com/inair4.html + and inAir9 modules https://modtronix.com/inair9.html. With the kind assistance of + David from Modtronix. +\version 1.52 2015-12-17 + Added RH_MRF89 module to suport Microchip MRF89XA and compatible transceivers. + and modules.
+\version 1.53 2016-01-02 + Added RH_CC110 module to support Texas Instruments CC110L and compatible transceivers and modules.
+\version 1.54 2016-01-29 + Added support for ESP8266 processor on Arduino IDE. Examples serial_reliable_datagram_* are shown to work. + CAUTION: SPI not supported yet. Timers used by RH_ASK are not tested. + The GHz radio included in the ESP8266 is not yet supported. +\version 1.55 2016-02-12 + Added macros for htons() and friends to RadioHead.h. + Added example sketch serial_gateway.pde. Acts as a transparent gateway between RH_RF22 and RH_Serial, + and with minor mods acts as a universal gateway between any 2 RadioHead driver networks. + Initial work on supporting STM32 F2 on Particle Photon: new platform type defined. + Fixed many warnings exposed by test building for Photon. + Particle Photon tested support for RH_Serial, RH_ASK, SPI, RH_CC110 etc. + Added notes on how to build RadioHead sketches for Photon. +\version 1.56 2016-02-18 + Implemented timers for RH_ASK on ESP8266, added some doc on IO pin selection. +\version 1.57 2016-02-23 + Fixed an issue reported by S3B, where RH_RF22 would sometimes not clear the rxbufvalid flag. +\version 1.58 2-16-04-04 + Tested RH_RF69 with Arduino Due. OK. Updated doc.
+ Added support for all ChipKIT Core supported boards + https://chipkit.net/wiki/index.php?title=ChipKIT_core + Tested on ChipKIT Uno32.
+ Digilent Uno32 under the old MPIDE is no longer formally + supported but may continue to work for some time.
+\version 1.59 2016-04-12 + Testing with the excellent Rocket Scream Mini Ultra Pro with the RFM95W and RFM69HCW modules from + https://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/ (915MHz versions). Updated + documentation with hints to suit. Caution: requires Arduino 1.6.8 and Arduino SAMD Boards 1.6.5. + See also https://www.rocketscream.com/blog/2016/03/10/radio-range-test-with-rfm69hcw/ + for the vendors tests and range with the RFM69HCW version. They also have an RF95 version equipped with + TCXO temperature controllled oscillator for extra frequency stability and support of very slow and + long range protocols. + These boards are highly recommended. They also include battery charging support. +\version 1.60 2016-06-25 + Tested with the excellent talk2 Whisper Node boards + (https://talk2.wisen.com.au/ and https://bitbucket.org/talk2/), + an Arduino Nano compatible board, which include an on-board RF69 radio, external antenna, + run on 2xAA batteries and support low power operations. RF69 examples work without modification. + Added support for ESP8266 SPI, provided by David Skinner. +\version 1.61 2016-07-07 + Patch to RH_ASK.cpp for ESP8266, to prevent crashes in interrupt handlers. Patch from Alexander Mamchits. +\version 1.62 2016-08-17 + Fixed a problem in RH_ASK where _rxInverted was not properly initialised. Reported by "gno.sun.sop". + Added support for waitCAD() and isChannelActive() and setCADTimeout() to RHGeneric. + Implementation of RH_RF95::isChannelActive() allows the RF95 module to support + Channel Activity Detection (CAD). Based on code contributed by Bent Guldbjerg Christensen. + Implmentations of isChannelActive() plus documentation for other radio modules wil be welcomed. +\version 1.63 2016-10-20 + Testing with Adafruit Feather 32u4 with RFM69HCW. Updated documentation to reflect.
+\version 1.64 2016-12-10 + RHReliableDatagram now initialises _seenids. Fix from Ben Lim.
+ In RH_NRF51, added get_temperature().
+ In RH_NRF51, added support for AES packet encryption, which required a slight change + to the on-air message format.
+\version 1.65 2017-01-11 + Fixed a race condition with RH_NRF51 that prevented ACKs being reliably received.
+ Removed code in RH_NRF51 that enabled the DC-DC converter. This seems not to be a necessary condition + for the radio to work and is now left to the application if that is required.
+ Proven interoperation between nRF51822 and nRF52832.
+ Modification and testing of RH_NRF51 so it works with nRF52 family processors, + such Sparkfun nRF52832 breakout board, with Arduino 1.6.13 and + Sparkfun nRF52 boards manager 0.2.3 using the procedures outlined in + https://learn.sparkfun.com/tutorials/nrf52832-breakout-board-hookup-guide
+ Caution, the Sparkfun development system for Arduino is still immature. We had to + rebuild the nrfutil program since the supplied one was not suitable for + the Linux host we were developing on. See https://forum.sparkfun.com/viewtopic.php?f=32&t=45071 + Also, after downloading a sketch in the nRF52832, the program does not start executing cleanly: + you have to reset the processor again by pressing the reset button. + This appears to be a problem with nrfutil, rather than a bug in RadioHead. +\version 1.66 2017-01-15 + Fixed some errors in (unused) register definitions in RH_RF95.h.
+ Fixed a problem that caused compilation errors in RH_NRF51 if the appropriate board + support was not installed. +\version 1.67 2017-01-24 + Added RH_RF95::frequencyError() to return the estimated centre frequency offset in Hz + of the last received message +\version 1.68 2017-01-25 + Fixed arithmetic error in RH_RF95::frequencyError() for some platforms. +\version 1.69 2017-02-02 + Added RH_RF95::lastSNR() and improved lastRssi() calculations per the manual. +\version 1.70 2017-02-03 + Added link to Binpress commercial license purchasing. +\version 1.71 2017-02-07 + Improved support for STM32. Patch from Bent Guldbjerg Christensen. +\version 1.72 2017-03-02 + In RH_RF24, fixed a problem where some important properties were not set by the ModemConfig. + Added properties 2007, 2008, 2009. Also properties 200a was not being set in the chip. + Reported by Shannon Bailey and Alan Adamson. + Fixed corresponding convert.pl and added it to the distribution. +\version 1.73 2017-03-04 + Significant changes to RH_RF24 and its API. It is no longer possible to change the modulation scheme + programatically: it proved impossible to cater for all the possible crystal frequencies, + base frequency and modulation schemes. Instead you can use one of a small set of supplied radio + configuration header files, or generate your own with Silicon Labs WDS application. Changing + modulation scheme required editing RH_RF24.cpp to specify the appropriate header and recompiling. + convert.pl is now redundant and removed from the distribution. +\version 1.74 2017-03-08 + Changed RHReliableDatagram so it would not ACK messages heard addressed to other nodes + in promiscuous mode.
+ Added RH_RF24::deviceType() to return the integer value of the connected device.
+ Added documentation about how to connect RFM69 to an ESP8266. Tested OK.
+ RH_RF24 was not correctly changing state in sleep() and setModeIdle().
+ Added example rf24_lowpower_client.pde showing how to put an arduino and radio into a low power + mode between transmissions to save battery power.
+ Improvements to RH_RF69::setTxPower so it now takes an optional ishighpowermodule + flag to indicate if the connected module is a high power RFM69HW, and so set the power level + correctly. Based on code contributed by bob. +\version 1.75 2017-06-22 + Fixed broken compiler issues with RH_RF95::frequencyError() reported by Steve Rogerson.
+ Testing with the very excellent Rocket Scream boards equipped with RF95 TCXO modules. The + temperature controlled oscillator stabilises the chip enough to be able to use even the slowest + protocol Bw125Cr48Sf4096. Caution, the TCXO model radios are not low power when in sleep (consuming + about ~600 uA, reported by Phang Moh Lim).
+ Added support for EBYTE E32-TTL-1W and family serial radio transceivers. These RF95 LoRa based radios + can deliver reliable messages at up to 7km measured. +\version 1.76 2017-06-23 + Fixed a problem with RH_RF95 hanging on transmit under some mysterious circumstances. + Reported by several people at https://forum.pjrc.com/threads/41878-Probable-race-condition-in-Radiohead-library?p=146601#post146601
+ Increased the size of rssi variables to 16 bits to permit RSSI less than -128 as reported by RF95. +\version 1.77 2017-06-25 + Fixed a compilation error with lastRssi().
+\version 1.78 2017-07-19 + Fixed a number of unused variable warnings from g++.
+ Added new module RHEncryptedDriver and examples, contributed by Philippe Rochat, which + adds encryption and decryption to any RadioHead transport driver, using any encryption cipher + supported by ArduinoLibs Cryptographic Library https://rweather.github.io/arduinolibs/crypto.html + Includes several examples.
+\version 1.79 2017-07-25 + Added documentation about 'Passing Sensor Data Between RadioHead nodes'.
+ Changes to RH_CC110 driver to calculate RSSI in dBm, based on a patch from Jurie Pieterse.
+ Added missing passthroughmethoids to RHEncryptedDriver, allowing it to be used with RHDatagram, + RHReliableDatagram etc. Tested with RH_Serial. Added examples +\version 1.80 2017-10-04 + Testing with the very fine Talk2 Whisper Node LoRa boards https://wisen.com.au/store/products/whisper-node-lora + an Arduino compatible board, which include an on-board RFM95/96 LoRa Radio (Semtech SX1276), external antenna, + run on 2xAAA batteries and support low power operations. RF95 examples work without modification. + Use Arduino Board Manager to install the Talk2 code support. Upload the code with an FTDI adapter set to 5V.
+ Added support for SPI transactions in development environments that support it with SPI_HAS_TRANSACTION. + Tested on ESP32 with RFM-22 and Teensy 3.1 with RF69 + Added support for ESP32, tested with RFM-22 connected by SPI.
+\version 1.81 2017-11-15 + RH_CC110, moved setPaTable() from protected to public.
+ RH_RF95 modem config Bw125Cr48Sf4096 altered to enable slow daat rate in register 26 + as suggested by Dieter Kneffel. + Added support for nRF52 compatible Arm chips such as as Adafruit BLE Feather board + https://www.adafruit.com/product/3406, with a patch from Mike Bell.
+ Fixed a problem where rev 1.80 broke Adafruit M0 LoRa support by declaring + bitOrder variable always as a unsigned char. Reported by Guilherme Jardim.
+ In RH_RF95, all modes now have AGC enabled, as suggested by Dieter Kneffel.
+\version 1.82 2018-01-07 + Added guard code to RH_NRF24::waitPacketSent() so that if the transmit never completes for some + reason, the code will eventually return with FALSE. + Added the low-datarate-optimization bit to config for RH_RF95::Bw125Cr48Sf4096. + Fix from Jurie Pieterse to ensure RH_CC110::sleep always enters sleep mode. + Update ESP32 support to include ASK timers. RH_ASK module is now working on ESP32. +\version 1.83 2018-02-12 + Testing adafruit M0 Feather with E32. Updated RH_E32 documentation to show suggested connections + and contructor initialisation.
+ Fixed a problem with RHEncryptedDriver that could cause a crash on some platforms when used + with RHReliableDatagram. Reported by Joachim Baumann.
+ Improvments to doxygen doc layout in RadioHead.h +\version 1.84 2018-05-07 + Compiles with Roger Clarkes Arduino_STM32 https://github.com/rogerclarkmelbourne/Arduino_STM32, + to support STM32F103C etc, and STM32 F4 Discovery etc.
+ Tested STM32 F4 Discovery board with RH_RF22, RH_ASK and RH_Serial. + +\version 1.85 2018-07-09 + RHGenericDriver methods changed to virtual, to allow overriding by RHEncrypredDriver: + lastRssi(), mode(), setMode(). Reported by Eyal Gal.
+ Fixed a problem with compiling RH_E32 on some older IDEs, contributed by Philippe Rochat.
+ Improvements to RH_RF95 to improve detection of bad packets, contributed by PiNi.
+ Fixed an error in RHEncryptedDriver that caused incorrect message lengths for messages multiples of 16 bytes + when STRICT_CONTENT_LEN is defined.
+ Fixed a bug in RHMesh which causes the creation of a route to the address which is the byte + behind the end of the route array. Reported by Pascal Gillès de Pélichy.
+\version 1.86 2018-08-28 + Update commercial licensing, remove binpress. +\version 1.87 2018-10-06 + RH_RF22 now resets all registers to default state before initialisation commences. Suggested by Wothke.
+ Added RH_ENABLE_EXPLICIT_RETRY_DEDUP which improves the handling of duplicate detection especially + in the case where a transmitter periodically wakes up and start transmitting from the first sequence number. + Patch courtesy Justin Newitter. Thanks. +\version 1.88 2018-11-13 + Updated to support ATTiny using instructions in + https://medium.com/jungletronics/attiny85-easy-flashing-through-arduino-b5f896c48189 + Updated examples ask_transmitter and ask_receiver to compile cleanly on ATTiny. + Tested using ATTiny85 and Arduino 1.8.1.
+\version 1.89 2018-11-15 + Testing with ATTiny core from https://github.com/SpenceKonde/ATTinyCore and RH_ASK, + using example ask_transmitter. This resulted in 'Low Memory, instability may occur', + and the resulting sketch would transmit only one packet. Suggest ATTiny users do not use this core, but use + the one from https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json + as described in https://medium.com/jungletronics/attiny85-easy-flashing-through-arduino-b5f896c48189
+ Added support for RH_RF95::setSpreadingFactor(), RH_RF95::setSignalBandwidth(), RH_RF95::setLowDatarate() and + RH_RF95::setPayloadCRC(). Patch from Brian Norman. Thanks.
+ +\version 1.90 2019-05-21 + Fixed a block size error in RhEncryptedDriver for the case when + using STRICT_CONTENT_LEN and sending messages of exactly _blockcipher.blockSize() bytes in length. + Reported and patched by Philippe Rochat. + Patch from Samuel Archibald to prevent compile errors with RH_AAK.cpp fo ATSAMD51. + Fixed a probem in RH_RF69::setSyncWords that prevented setSyncWords(NULL, 0) correctly + disabling sync detection and generation. Reported by Federico Maggi. + RHHardwareSPI::usingInterrupt() was a noop. Fixed to call SPI.usingInterrupt(interrupt);. + +\version 1.91 2019-06-01 + Fixed a problem with new RHHardwareSPI::usingInterrupt() that prevented compilation on ESP8266 + which does not have that call. + +\version 1.92 2019-07-14 + Retested serial_reliable_datagram_client.pde and serial_reliable_datagram_server.pde built on Linux + as described in their headers, and with USB-RS485 adapters. No changes, working correctly. + Testing of nRF5232 with Sparkfun nRF52 board support 0.2.3 shows that there appears to be a problem with + interrupt handlers on this board, and none of the interrupt based radio drivers can be expected to work + with this chip. + Ensured all interrupt routines are flagged with ICACHE_RAM_ATTR when compiled for ESP8266, to prevent crashes. + +\version 1.94 2019-09-02 + Fixed a bug in RHSoftwareSPI where RHGenericSPI::setBitOrder() has no effect for + on RHSoftwareSPI. Reported by Peter.
+ Added support in RHRouter for a node to optionally be leaf node, and not participate as a router in the + network. See RHRouter::setNodeTypePatch from Alex Evans.
+ Fixed a problem with ESP32 causing compile errors over missing SPI.usingInterrupt().
+ +\version 1.95 2019-10-14 + Fixed some typos in RH_RF05.h macro definitions reported by Clayton Smith.
+ Patch from Michael Cain from RH_ASK on ESP32, untested by me.
+ Added support for RPi Zero and Zero W for the RF95, contributed by Brody Mahoney. + Not tested by me.
+ +\version 1.96 2019-10-14 + Added examples for RPi Zero and Zero W to examples/raspi/rf95, contributed by Brody Mahoney + not tested by me.
+ +\version 1.97 2019-11-02 + Added support for Mongoose OS, contributed by Paul Austen. + +\version 1.98 2020-01-06 + Rationalised use of RH_PLATFORM_ATTINY to be consistent with other platforms.
+ Added support for RH_PLATFORM_ATTINY_MEGA, for use with Spencer Konde's megaTinyCore + https://github.com/SpenceKonde/megaTinyCore on Atmel megaAVR ATtiny 1-series chips. + Tested with AtTiny 3217, 3216 and 1614, using + RH_Serial, RH_ASK, and RH_RF22 drivers.
+ +\version 1.99 2020-03-07 + Release under GPL V3 + +\version 1.100 2020-03-12 + Fixed a problem that prevented compilation of RH_NRF51 + on Arduino for Sparkfun nRF52832 Breakout board.
+ +\version 1.101 2020-04-10 + Tested nRF52832 with RFM69W module and RH_RF69, using Software SPI and hardware interrutps OK.
+ Fixed warnings about 'deleting object of polymorphic class' if driver is dynamically allocated.
+ Fixed problems in RH_ASK and HardwareSPI to work with STM32F4 Discovery with latest + version of stm32duino https://github.com/stm32duino/Arduino_Core_STM32. + Testing with stm32duino 1.8.0 downloaded with Board Manager per + https://github.com/stm32duino/Arduino_Core_STM32 . + Now builds and run RH_ASK examples with STM32F4 Discovery board. + Build without error for STM32 F1 and F4 but Does not compile for Generic STM32F3. + +\version 1.102 2020-05-15 + Updated RH_RF95::setPayloadCRC to affect CRC generation on outgoing packets as well + as CRC detection and checking on incoming packets. + Added new modem config for RH_RF95. RH_RF95::Bw125Cr45Sf2048 + Bw = 125 kHz, Cr = 4/5, Sf = 2048chips/symbol, CRC on. Slow+long range. Tested + against RPI with LoRa-file-ops driver https://github.com/starnight/LoRa/tree/file-ops + and send.c test program. + Fixed a problem with (re-)definition of SS on ESP32, reported and fixed by Justin Newitter. + +\version 1.103 2020-05-30 + Fixed some errors in RH_RF95::setTxPower which cased the power levels to be set incorrectly. + Checked operation and improved documentation. Valid settings are: + 2 to 20 (useRFO false) and 0 to 15 (useRFO true). 18, 19 and 20 (useRFO false) turn on the PA_DAC. + Fixed RF95 examples to reflect correct use. + Added RH_ABZ driver, which supports the muRata CMWX1ZZABZ (TypeABZ) module + which includes an STM32L0 processor, a SX1276 LoRa radio and an antenna switch. + Requires the Grumpy Old Pizza Arduino Core installed per https://github.com/GrumpyOldPizza/ArduinoCore-stm32l0 + Examples provided. + +\version 1.104 2020-06-08 + Fixed a problem with new RH_ABZ module that prevents compilation with standard 0.0.10 version of STM32L0
+ Arduino Core installed with Board Manager: STM32L0_EXTI_CONTROL_PRIORITY_CRITICAL + is only available in later versions.
+ +\version 1.105 2020-06-03 + Added support for RH_ABZ on STM32L072xx on Grumpy Old Pizza Arduino Core
+ +\version 1.106 2020-06-16 + Patch from Livio Tenze for RH_RF22 to fix a problem with interrupts on on ESP8266.
+ Added examples/rf22/rf22_cw, with example showing how to emit a carrier wave (CW).
+ Reverted delay in RHSPIDriver::init() back to 100ms for all platforms except ABZ, where + 100ms interferes with the USB serial port with at least some versions of the core.
+ Updated and clarified documentation about TCXO use in RH_ABZ and examples.
+ Fixed documentation SS->NSEL pin for RH_RF22 with AtMega. Seems that SS on that platform + is now defined as pin 10, not 53. Dont know when that changed.
+ +\version 1.107 2020-06-26 + Improvements to RHEncryptedDriver, so that you will get an explanatory error message if you + include RHEncryptedDriver.h without enabling the class with RH_ENABLE_ENCRYPTION_MODULE in RadioHead.h.
+ Fixed newly introduced errors when compiling for ATTiny 25/45/85.
+ +\version 1.108 2020-07-05 + Fixed a problem with RH_RF22 on ESP8266 introcuced in version 1.106 which prevented + messages being received. Some other cleanups in RH_RF22
+ +\version 1.109 2020-07-23 + Fixed a problem that prevented RHEncryptedDriver compiling when RH_ENCRYPTED_DRIVER was enabled.
+ Added optiona hops argument to RHRouter and RHMesh recvfromAck() and receivedFomAckTimeout() + to allow the hop count to be retreived if desired. +\version 1.110 2020-07-28 + Fixed a problem where _spi.beginTransaction and _spi.endTransaction were missing from RHSPIDriver::spiRead. +\version 1.111 2020-07-30 + Fixed some compilation problems on some platforms with RH_PLATFORM_ATTINY_MEGA and Kondes MegaTinyCore 1.1.2.
+ Fixed some other errors with Kondes MegaTinyCore 2.0.5: F_CPU_CORRECTED no longer exists, + bitOrder and PinStatus no longer needed.
+ Improved detection of RH_PLATFORM_ATTINY_MEGA by looking for defined(MEGATINYCORE), defined + in later versions of MegaTinyCore. + +\version 1.112 2020-08-05 + Fixed some compiler warnings in STM32 Discovery and other processors.
+ Added support for ST's Arduino Core STM32, https://github.com/stm32duino/Arduino_Core_STM32 + to RH_ASK, per https://github.com/r-map/rmap/commit/edf9931b21cb7df5b4f67835307255e0fcb301bb.
+ Added documentation about requirements for code contributions.
+ Added library.properties file that some IDEs need.
+ Added support for multithreaded RH_RF95 support on Raspberry Pi, courtesy Tilman Glötzner.
+ Includes example programs, including for the Dragino Lora/GPS Hat which + send GPS coordinates.
+ +\version 1.113 2020-10-15 + Changes to RH_ASK on SpenceKonde's megaTinyCore so it will use TCB0 on platforms + where TCB1 is not available.
+ Minor imprvements to documentation.
+ Move to local git for source code control.
+ Added support for RH_ABZ::deinit() for Matt Way. + Fixed compilation error in RH_ASK caused by changes to interrupt API in Arduino_Core_STM32 1.9 + +\version 1.114 2021-03-24 + Fixed compile warnings, errors on some platforms, in RH_ASK.cpp for STM32 core 1.9.0 + to do with odd behaviour of callback_function_t.
+ Fixed some problems with SPI transactions in RH_RF69 and RH_RF24 that affected + operations with STM32F030F4. Reported and patched by Adam De Muri.
+ Added tools/createGPX.pl, contributed by Tilman Glötzner: A perl script to process the dump files that can be + produced with the Dragino LoRa-GPS-hat and rf95_client2 + rf95_server2. + The programs exchange sequence numbers, timestamps and gps coordinates + via Lora which can be used for range measurements or visualization. The + perl script is meant for post processing and converts the log files + into Gpx-files which in turn can be imported to google maps.
+ Added support for MRF89XAM8A, which has support for different frequency bands to RH_MRF89. Patch courtesy + Michael Kefeder. + +\version 1.115 2021-03-24 + Fix compile error in new MRF89XAM8A support. + +\version 1.116 2021-03-30 + Patch from Vlasta Hajek: 1. Made RH_ASK usable again for ATTINY after breaking changes. + 2. Added the possibility to use Timer 1 on ATtiny8x. Tested on ATtiny 85 + and ATtiny84 for more than a year. + 3. Added patch for ESP32 from jPerotto. This finally makes receiving via + RH_ASK on ESP32 without panics. Tested for more than 6 months.
+ +\version 1.117 2021-05-26 + Added documention to RH_MRF89 about how to achieve 1μA power consumption in sleep mode. + Courtesy Fernando Faria.
+ Improved documentation for the meaning of 'len' in all recv*, as suggested by Sean. E. Sean.
+ waitAvailable and waitAvaiableTimeout now take an optional polldelay argument that + can be useful for introducing on multitasking systems where a driver requires polling.
+ Fixed compile errors with forward declarations in RH_E32.h and RH_Serial.h on Ardiono IDE on Windows + reported by Skywodd. + +\version 1.118 2021-07-16 + Added a 1 us delay in RHSPIDriver::spiWrite as recommended in + https://forum.pjrc.com/attachment.php?attachmentid=10948&d=1499109224 + for corect operation with some fast processors.
+ Tested Ebyte E22-900T22S radios with serial interface and the examples from + examples/serial/serial_reliable_datagram_* on Linux. + These radios implement a transparent serial connection + using LORA. They can also be configured as independent relays. + You can alter the default configuration of the radios (including frequency, + transmission rates, node addresses etc) by using the + Ebyte RF Settings for E22 tool from https://www.cdebyte.net/download-tools and after + setting the device to configuraiotn mode (M1 high: temporarily remove the M1 link). + Caution, to use this + tool on Windows, you also must install the CH340 USB-Serial port driver, see + https://learn.sparkfun.com/tutorials/how-to-install-ch340-drivers/all#windows-710 + These modules can also be configured programatically, but this is not supported by RadioHead.
+ Fixed compile errors to do with RH_DRAM_ATTR on non-ESP-32 platforms.
+ Fixed problems reported with crashes in RH_ASK on STM32 boards with core >= 1.9.0 due to + incorrect declaration of interrupt function.
+ +\version 1.119 2021-08-02 + Changes to RadioHead.h RHHardwareSPI.cpp and RHSPIDriver.cpp for ESP32 support + provided by Juliano Perotto. + +\version 1.120 2021-11-13 + Added intial support for Raspberry Pi Pico, using the Raspberry Pi Pico/RP2040 core by Earle F. Philhower, version 1.93, + installed per https://arduino-pico.readthedocs.io/_/downloads/en/latest/pdf/ + RH_ASK works with default pins (11 and 12). But it seems that interrupt callbacks (needed by most SPI based + radio drivers in RadioHead) are not working (yet) in this core, at least when compiled on our Linux platform. + It appears + there is some bug in std::map that causes the interrupt handling to not work correctly + (which I have been able to reproduce in a simple sketch that only uses std::map). This is very strange. + Interrupts _are_ supported and work in the alternative Arduino MBED OS RP2040 core version 2.4.1, + but there are other mysterious crashes RadioHead when a radio driver reads SPI inside an interrupt. + But RH_ASK also works with this core. + So the result (so far) is partial support of RadioHead using either core (so far). + We will revisit this when either core is updated
+ On ESP32, added support for using the non-default HSPI bus by defining RH_ESP32_USE_HSPI.
+ Improved the reading of RSSI for the last packet in RH_CC110: seems that it was being read too soon. Now + read it from the end received packet with RH_CC110_APPEND_STATUS.
+ Added RH_LoRaFileOps, driver on RPi+Linux and using LoRa-file-ops Linux driver ioctls to + transmit and receive RadioHead compatible messages via SX1276/77/78/79 + and compatible radios. Requires a modified version of LoRa-file-ops driver to be installed, + and a compatible radio to be connected appropriately: + https://github.com/starnight/LoRa/tree/file-ops
+ +\version 1.121 2022-02-02 + Restored RH_RF95 code to clear the IRQ flags (twice).
+ +\version 1.122 2023-05-20 + Added RHRouter::getNextValidRoutingTableEntry() contributed by w...
+ Various compatibility imnprovements for STM32L0 etc from Calin Radoni.
+ Added ability to overriding the value of RH_ENABLE_EXPLICIT_RETRY_DEDUP + via a compiler arg. Example from PlatformIO: + build_flags = + -D"RH_ENABLE_EXPLICIT_RETRY_DEDUP=1" + Contributed by Justin Newitter.
+ Fixed an error in the header lengths in RH_TCP, which could result in the last octet + of the payload being lost. checkForEvents() now returns false instead of exit() in case of IO failure
+ +\version 1.123 2023-08-07 + Added support for Arduino Uno R4 Minima. + +\version 1.124 2023-08-09 + Extended support for Arduino Uno R4 to Arduino Uno R4 WIFI + +\version 1.125 2023-08-14 + Improved support for Arduino Uno R4: ATOMIC_BLOCK_START and ATOMIC_BLOCK_END are no + longer defined since they hang on R4, and are not necessary since they have SPI_HAS_TRANSACTION + +\version 1.126 2023-11-24 + ESP8266 support now uses IRAM_ATTR instead of ICACHE_RAM_ATTR. This should work fine with core 2.7.4 and later.
+ Changes to definitions of ATOMIC_BLOCK_START and ATOMIC_BLOCK_END for RH_PLATFORM_ESP32: Previous + use of XTOS_DISABLE_ALL_INTERRUPTS not suported for ESP32C3. Now uses ATOMIC_ENTER_CRITICAL() + and ATOMIC_EXIT_CRITICAL() which appear to be supported for all platforms in Arduino esp32 package.
+ +\version 1.127 2024-01-07 + Added support for SX126x family radios, and also for the STM32WLE5xx and STM32WLE4xx families of ARM procedssors that + have a SX126x radio built in, and as used in the LoRa-E5-HF module (which is used in WiO-E5 mini development board), + the LoRa-E5-LF module and the NUCLEO_WL55JC1 development board. Tested with WiO-E5 mini development board, but no standalone + SPI versions of this radio. + +\version 1.128 2024-01-12 + Added missing sx126x examples to distribution. + +\version 1.129 2024-03-21 + Rename all .pde sketches to .ino + Significant changes to RH_SX126x to support other modules such as the NiceRF LoRa1262 family. + https://www.nicerf.com/lora-module/915mhz-lora-module-lora1262.html + Sample client and server sketches demonstrating use, compatible with Teensy and other 3.3V compatible Arduinos. + +\version 1.130 2024-04-12 + Adjustments to RHHardwareSPI fopr compatibility with RAKwireless RAK4360/RAK4361, contributed by gfja. + Fixed a preprocessor typo in unused code in RHHardwareSPI.cpp, reported by brodymahoney. + +\version 1.131 2024-07-02 + Fixed problem with failed compilation in RH_ASK.cpp, when used with ESP 32 board library version 3.o or greated because + the Espressif library timer API changed.
+ +\version 1.132 2024-07-08 + Fixed problem with SX126x where a packet recieved with a CRC error would leave the chip in standby mode + but RH_SX126x thought it was still in RX mode. Reported by kalev.
+ +\version 1.133 2024-07-08 + Revisit SX126x RX CRC error handling with the assistance of kalev. + +\version 1.134 2024-07-10 + Improvements to SX126x contributed by kalev:
+ 1. Save IRQ flags for debugging outside interrupt handler. + every time the handler is invoked save IRQ flags and rise _iflag.
+ 2. runtime set on/off IRQ mask. This gives possibility to + investigate erroneous packets.
+ 3. introduce raw mode send and receive for debugging.
+ 4. add getFrequencyError() If receiver and sender + main clock frequencies are off ( usually when pairing sx126x and + sx127x ) it's difficult to adjust other party's ( usually sx127x ) frequency.
+ +\version 1.135 2024-07-12 + Fixed a problem in RHSPIDriver: if the Slave Select pin was set to 0xff, then it would still drive the + SS pin. This broke use of EEPROM on STM32wl. Reported and fixed by Craig Zych. + +\version 1.136 2024-07-25 + Fixed a typo in the comments for the MOSI pin number for the alternative ESP32 HSPI interface.
+ Examples were not included in idstribution since names changed to .ino + +\version 1.137 2025-01-03 + Fixed a problem where Timer definitions for RP2350 are differnet to RP2340 + Reported and patched by Conor O'Neill. + +\version 1.138 2025-01-05 + Added support for Heltec CubeCell modules, including the builtin SX126x radio. + Updated sx1262_client.ino and sx1262_server.ino to demonstrate usage. + +\version 1.139 2025-01-07 + Updated CubeCell support for Heltec CubeCell board 1.5.0 installed in Arduino IDE + per https://docs.heltec.org/en/node/asr650x/asr650x_general_docs/quick_start/cubecell-use-arduino-board-manager.html + +\version 1.140 2025-01-07 + Fixed a compile problem on some platforms like PlatformIO to do with nullptr. + +\version 1.141 2025-01-08 + Fixed another nullptr compile problem on Arduino 2.3.4 + +\version 1.142 2025-01-11 + Improvements to RH_SX126x to support sleep mode better, This requires us to waituntilNotBusy() _after_ NSS has been + asserted, not before. + +\version 1.143 2025-01-21 + Extended Cube Cell Board support to all Cube Cell board variants currently supported by Cube Cell 1.5.0. + + +\author Mike McCauley. DO NOT CONTACT THE AUTHOR DIRECTLY. USE THE GOOGLE GROUP GIVEN ABOVE +*/ + +/*! \page packingdata +\par Passing Sensor Data Between RadioHead nodes + +People often ask about how to send data (such as numbers, sensor +readings etc) from one RadioHead node to another. Although this issue +is not specific to RadioHead, and more properly lies in the area of +programming for networks, we will try to give some guidance here. + +One reason for the uncertainty and confusion in this area, especially +amongst beginners, is that there is no *best* way to do it. The best +solution for your project may depend on the range of processors and +data that you have to deal with. Also, it gets more difficult if you +need to send several numbers in one packet, and/or deal with floating +point numbers and/or different types of processors. + +The principal cause of difficulty is that different microprocessors of +the kind that run RadioHead may have different ways of representing +binary data such as integers. Some processors are little-endian and +some are big-endian in the way they represent multi-byte integers +(https://en.wikipedia.org/wiki/Endianness). And different processors +and maths libraries may represent floating point numbers in radically +different ways: +(https://en.wikipedia.org/wiki/Floating-point_arithmetic) + +All the RadioHead examples show how to send and receive simple NUL +terminated ASCII strings, and if thats all you want, refer to the +examples folder in your RadioHead distribution. But your needs may be +more complicated than that. + +The essence of all engineering is compromise so it will be up to you to +decide whats best for your particular needs. The main choices are: +- Raw Binary +- Network Order Binary +- ASCII + +\par Raw Binary + +With this technique you just pack the raw binary numbers into the packet: + +\code +// Sending a single 16 bit unsigned integer +// in the transmitter: +... +uint16_t data = getsomevalue(); +if (!driver.send((uint8_t*)&data, sizeof(data))) +{ + ... +\endcode + +\code +// and in the receiver: +... +uint16_t data; +uint8_t datalen = sizeof(data); +if ( driver.recv((uint8_t*)&data, &datalen) + && datalen == sizeof(data)) +{ + // Have the data, so do something with it + uint16_t xyz = data; + ... +\endcode + +If you need to send more than one number at a time, its best to pack +them into a structure + +\code +// Sending several 16 bit unsigned integers in a structure +// in a common header for your project: +typedef struct +{ + uint16_t dataitem1; + uint16_t dataitem2; +} MyDataStruct; +... +\endcode + +\code +// In the transmitter +... +MyDataStruct data; +data.dataitem1 = getsomevalue(); +data.dataitem2 = getsomeothervalue(); +if (!driver.send((uint8_t*)&data, sizeof(data))) +{ + ... +\endcode + +\code +// in the receiver +MyDataStruct data; +uint8_t datalen = sizeof(data); +if ( driver.recv((uint8_t*)&data, &datalen) + && datalen == sizeof(data)) +{ + // Have the data, so do something with it + uint16_t pqr = data.dataitem1; + uint16_t xyz = data.dataitem2; + .... +\endcode + + +The disadvantage with this simple technique becomes apparent if your +transmitter and receiver have different endianness: the integers you +receive will not be the same as the ones you sent (actually they are, +but with the internal bytes swapped around, so they probably wont make +sense to you). Endianness is not a problem if *every* data item you +send is a just single byte (uint8_t or int8_t or char), or if the +transmitter and receiver have the same endianness. + +So you should only adopt this technique if: +- You only send data items of a single byte each, or +- You are absolutely sure (now and forever into the future) that you +will only ever use the same processor endianness in the transmitter and receiver. + +\par Network Order Binary + +One solution to the issue of endianness in your processors is to +always convert your data from the processor's native byte order to +'network byte order' before transmission and then convert it back to +the receiver's native byte order on reception. You do this with the +htons (host to network short) macro and friends. These functions may +be a no-op on big-endian processors. + +With this technique you convert every multi-byte number to and from +network byte order (note that in most Arduino processors an integer is +in fact a short, and is the same as int16_t. We prefer to use types +that explicitly specify their size so we can be sure of applying the +right conversions): + +\code +// Sending a single 16 bit unsigned integer +// in the transmitter: +... +uint16_t data = htons(getsomevalue()); +if (!driver.send((uint8_t*)&data, sizeof(data))) +{ + ... +\endcode +\code +// and in the receiver: +... +uint16_t data; +uint8_t datalen = sizeof(data); +if ( driver.recv((uint8_t*)&data, &datalen) + && datalen == sizeof(data)) +{ + // Have the data, so do something with it + uint16_t xyz = ntohs(data); + ... +\endcode + +If you need to send more than one number at a time, its best to pack +them into a structure + +\code +// Sending several 16 bit unsigned integers in a structure +// in a common header for your project: +typedef struct +{ + uint16_t dataitem1; + uint16_t dataitem2; +} MyDataStruct; +... +\endcode +\code +// In the transmitter +... +MyDataStruct data; +data.dataitem1 = htons(getsomevalue()); +data.dataitem2 = htons(getsomeothervalue()); +if (!driver.send((uint8_t*)&data, sizeof(data))) +{ + ... +\endcode +\code +// in the receiver +MyDataStruct data; +uint8_t datalen = sizeof(data); +if ( driver.recv((uint8_t*)&data, &datalen) + && datalen == sizeof(data)) +{ + // Have the data, so do something with it + uint16_t pqr = ntohs(data.dataitem1); + uint16_t xyz = ntohs(data.dataitem2); + .... +\endcode + +This technique is quite general for integers but may not work if you +want to send floating point number between transmitters and receivers +that have different floating point number representations. + + +\par ASCII + +In this technique, you transmit the printable ASCII equivalent of +each floating point and then convert it back to a float in the receiver: + +\code +// In the transmitter +... +float data = getsomevalue(); +uint8_t buf[15]; // Bigger than the biggest possible ASCII +snprintf(buf, sizeof(buf), "%f", data); +if (!driver.send(buf, strlen(buf) + 1)) // Include the trailing NUL +{ + ... +\endcode +\code + +// In the receiver +... +float data; +uint8_t buf[15]; // Bigger than the biggest possible ASCII +uint8_t buflen = sizeof(buf); +if (driver.recv(buf, &buflen)) +{ + // Have the data, so do something with it + float data = atof(buf); // String to float + ... +\endcode + +\par Conclusion: + +- This is just a basic introduction to the issues. You may need to +extend your study into related C/C++ programming techniques. + +- You can extend these ideas to signed 16 bit (int16_t) and 32 bit +(uint32_t, int32_t) numbers. + +- Things can be simple or complicated depending on the needs of your +project. + +- We are not going to write your code for you: its up to you to take +these examples and explanations and extend them to suit your needs. + +*/ + + + +#ifndef RadioHead_h +#define RadioHead_h + +// Official version numbers are maintained automatically by Makefile: +#define RH_VERSION_MAJOR 1 +#define RH_VERSION_MINOR 143 + +// Symbolic names for currently supported platform types +#define RH_PLATFORM_ARDUINO 1 +#define RH_PLATFORM_MSP430 2 +#define RH_PLATFORM_STM32 3 +#define RH_PLATFORM_GENERIC_AVR8 4 +#define RH_PLATFORM_UNO32 5 +#define RH_PLATFORM_UNIX 6 +#define RH_PLATFORM_STM32STD 7 +#define RH_PLATFORM_STM32F4_HAL 8 +#define RH_PLATFORM_RASPI 9 +#define RH_PLATFORM_NRF51 10 +#define RH_PLATFORM_ESP8266 11 +#define RH_PLATFORM_STM32F2 12 +#define RH_PLATFORM_CHIPKIT_CORE 13 +#define RH_PLATFORM_ESP32 14 +#define RH_PLATFORM_NRF52 15 +#define RH_PLATFORM_MONGOOSE_OS 16 +#define RH_PLATFORM_ATTINY 17 +// Spencer Kondes megaTinyCore: +#define RH_PLATFORM_ATTINY_MEGA 18 +#define RH_PLATFORM_STM32L0 19 +#define RH_PLATFORM_RASPI_PICO 20 + +//////////////////////////////////////////////////// +// Select platform automatically, if possible +#ifndef RH_PLATFORM + #if (defined(MPIDE) && MPIDE>=150 && defined(ARDUINO)) + // Using ChipKIT Core on Arduino IDE + #define RH_PLATFORM RH_PLATFORM_CHIPKIT_CORE + #elif defined(MPIDE) + // Uno32 under old MPIDE, which has been discontinued: + #define RH_PLATFORM RH_PLATFORM_UNO32 + #elif defined(NRF51) || defined(NRF52) + #define RH_PLATFORM RH_PLATFORM_NRF51 + #elif defined(NRF52) + #define RH_PLATFORM RH_PLATFORM_NRF52 + #elif defined(ESP8266) + #define RH_PLATFORM RH_PLATFORM_ESP8266 + #elif defined(ESP32) + #define RH_PLATFORM RH_PLATFORM_ESP32 + #elif defined(STM32L0) || defined(ARDUINO_ARCH_STM32L0) + #define RH_PLATFORM RH_PLATFORM_STM32L0 + #elif defined(MGOS) + #define RH_PLATFORM RH_PLATFORM_MONGOOSE_OS +#elif defined(MEGATINYCORE) || defined(ARDUINO_attinyxy2) || defined(ARDUINO_attinyxy4) || defined(ARDUINO_attinyxy6) || defined(ARDUINO_attinyxy7) + #define RH_PLATFORM RH_PLATFORM_ATTINY_MEGA + #elif defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtinyX4__) || defined(__AVR_ATtinyX5__) || defined(__AVR_ATtiny2313__) || defined(__AVR_ATtiny4313__) || defined(__AVR_ATtinyX313__) || defined(ARDUINO_attiny) + #define RH_PLATFORM RH_PLATFORM_ATTINY + #elif defined(ARDUINO) + #define RH_PLATFORM RH_PLATFORM_ARDUINO + #elif defined(__MSP430G2452__) || defined(__MSP430G2553__) + #define RH_PLATFORM RH_PLATFORM_MSP430 + #elif defined(MCU_STM32F103RE) + #define RH_PLATFORM RH_PLATFORM_STM32 + #elif defined(STM32F2XX) + #define RH_PLATFORM RH_PLATFORM_STM32F2 + #elif defined(USE_STDPERIPH_DRIVER) + #define RH_PLATFORM RH_PLATFORM_STM32STD + #elif defined(RASPBERRY_PI) + #define RH_PLATFORM RH_PLATFORM_RASPI + #elif defined(__unix__) // Linux + #define RH_PLATFORM RH_PLATFORM_UNIX + #elif defined(__APPLE__) // OSX + #define RH_PLATFORM RH_PLATFORM_UNIX + + #else + #error Platform not defined! + #endif +#endif + +//////////////////////////////////////////////////// +// Platform specific headers: +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) + #if (ARDUINO >= 100) + #include + #else + #include + #endif + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + + #if defined(ARDUINO_ARCH_STM32F4) + // output to Serial causes hangs on STM32 F4 Discovery board + // There seems to be no way to output text to the USB connection + #undef Serial + #define Serial Serial2 + + #elif defined(ARDUINO_ARCH_RP2040) + #if defined(PICO_RP2350) + // Raspi Pico 2350 + #define RH_ASK_PICO_ALARM_IRQ TIMER0_IRQ_1 + #else + // Raspi Pico 2040 + #define RH_ASK_PICO_ALARM_IRQ TIMER_IRQ_1 + #endif + #define RH_ASK_PICO_ALARM_NUM 1 + + #elif defined(ARDUINO_LORA_E5_MINI) + // WiO-E5 mini, or boards conating Seeed LoRa-E5-LF or LoRa-E5-HF, processor is STM32WLE5JC + #include + +#elif defined(CubeCell_Board) || defined(CubeCell_Board_V2) || defined(CubeCell_Capsule) || defined(CubeCell_Module) || defined(CubeCell_Module_V2) || defined(CubeCell_BoardPlus) || defined(CubeCell_GPS) || defined(CubeCell_ModulePlus) || defined(CubeCell_HalfAA) || defined(CubeCell_BoardP) + #define RH_CUBE_CELL_BOARD + #define RH_MISSING_SPIUSINGINTERRUPT + #include // For Radio pin definitions + + #endif + +#elif (RH_PLATFORM == RH_PLATFORM_ATTINY) + #include +// #warning Arduino TinyCore does not support hardware SPI. Use software SPI instead. +#elif (RH_PLATFORM == RH_PLATFORM_ATTINY_MEGA) + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + // On most AT_TINY_MEGA, Timer A is used for millis/micros, and B 0 or 1 for Tone by default. + // But not all devices support TCB1, so we use TCB0 on some variants. + // This is the same strategy for timer selection that Tone uses: + #if defined(MILLIS_USE_TIMERB0) && defined(TCB1) + #define RH_ATTINY_MEGA_ASK_TIMER TCB1 + #define RH_ATTINY_MEGA_ASK_TIMER_VECTOR TCB1_INT_vect + #else + #define RH_ATTINY_MEGA_ASK_TIMER TCB0 + #define RH_ATTINY_MEGA_ASK_TIMER_VECTOR TCB0_INT_vect + #endif + +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) // ESP8266 processor on Arduino IDE + #include + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + #define RH_MISSING_SPIUSINGINTERRUPT + +#elif (RH_PLATFORM == RH_PLATFORM_ESP32) // ESP32 processor on Arduino IDE + #include + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + #define RH_MISSING_SPIUSINGINTERRUPT + // ESP32 has 2 user SPI buses: VSPI and HSPI. They are essentially identical, but use different pins. + // The usual, default bus VSPI (available as SPI object in Arduino) uses pins: + // SCLK: 18 + // MISO: 19 + // MOSI: 23 + // SS: 5 + // The other HSPI bus uses pins + // SCLK: 14 + // MISO: 12 + // MOSI: 13 + // SS: 15 + // By default RadioHead uses VSPI, but you can make it use HSPI by defining this: + //#define RH_ESP32_USE_HSPI + +#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) // Mongoose OS platform + #include + #include + #include + #include + #include + #include + #include // We use the floor() math function. + #define RH_HAVE_HARDWARE_SPI + //If a Radio is connected via a serial port then this defines the serial + //port the radio is connected to. + #if defined(RH_SERIAL_PORT) + #if RH_SERIAL_PORT == 0 + #define Serial Serial0 + #elif RH_SERIAL_PORT == 1 + #define Serial Serial1 + #elif RH_SERIAL_PORT == 2 + #define Serial Serial2 + #endif + #else + #warning "RH_SERIAL_PORT not defined. Therefore serial port 0 selected" + #define Serial Serial0 + #endif + #define RH_HAVE_SERIAL + +#elif (RH_PLATFORM == RH_PLATFORM_MSP430) // LaunchPad specific + #include "legacymsp430.h" + #include "Energia.h" + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + +#elif (RH_PLATFORM == RH_PLATFORM_UNO32 || RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) + #include + #include + #include + #define RH_HAVE_HARDWARE_SPI + #define memcpy_P memcpy + #define RH_HAVE_SERIAL + +#elif (RH_PLATFORM == RH_PLATFORM_STM32) // Maple, Flymaple etc + #include + #include + #include + #include + #define RH_HAVE_HARDWARE_SPI + // Defines which timer to use on Maple + #define MAPLE_TIMER 1 + #define PROGMEM + #define memcpy_P memcpy + #define Serial SerialUSB + #define RH_HAVE_SERIAL + +#elif (RH_PLATFORM == RH_PLATFORM_STM32L0) + #include + #include + // Can define this in platformio.ini + #ifndef RH_EXCLUDE_STM32L0_INCLUDES + // https://github.com/GrumpyOldPizza/ArduinoCore-stm32l0 + #include + #include + #include + #endif + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + +#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Particle Photon with firmware-develop + #include + #include + #include // floor + #define RH_HAVE_SERIAL + #define RH_HAVE_HARDWARE_SPI + +#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32 with STM32F4xx_StdPeriph_Driver + #include + #include + #include + #include + #include + #include + #define RH_HAVE_HARDWARE_SPI + #define Serial SerialUSB + #define RH_HAVE_SERIAL + +#elif (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8) + #include + #include + #include + #include + #include + #define RH_HAVE_HARDWARE_SPI + #include + +// For Steve Childress port to ARM M4 w/CMSIS with STM's Hardware Abstraction lib. +// See ArduinoWorkarounds.h (not supplied) +#elif (RH_PLATFORM == RH_PLATFORM_STM32F4_HAL) + #include + #include // Also using ST's CubeMX to generate I/O and CPU setup source code for IAR/EWARM, not GCC ARM. + #include + #include + #include + #define RH_HAVE_HARDWARE_SPI // using HAL (Hardware Abstraction Libraries from ST along with CMSIS, not arduino libs or pins concept. + +#elif (RH_PLATFORM == RH_PLATFORM_RASPI) + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + #define PROGMEM +// You can enable MUTEX to protect critical sections for multithreading +// #define RH_USE_MUTEX + #if (__has_include ()) + #include + #else + #include + #endif + #include + //Define SS for CS0 or pin 24 + #define SS 8 + +#elif (RH_PLATFORM == RH_PLATFORM_NRF51) + #define RH_HAVE_SERIAL + #define PROGMEM + #include + +#elif (RH_PLATFORM == RH_PLATFORM_NRF52) + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + #define RH_HAVE_SPI_ATTACH_INTERRUPT + #define PROGMEM + #include + +#elif (RH_PLATFORM == RH_PLATFORM_UNIX) + // Simulate the sketch on Linux and OSX + #include + #define RH_HAVE_SERIAL +#include // For htons and friends + +#else + #error Platform unknown! +#endif + +//////////////////////////////////////////////////// +// This is an attempt to make a portable atomic block +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) + #if defined(RH_CUBE_CELL_BOARD) + // No atomic header file available + #elif defined(__arm__) + #include + #else + #include + #endif + + #if defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI) || defined(RH_CUBE_CELL_BOARD) + // Standard arduino ATOMIC block crashes on MBED version of Pico as at 2021-08-12 // and ius not available/required for UNO R4 + #define ATOMIC_BLOCK_START { + #define ATOMIC_BLOCK_END } + #else + // Most Arduinos + #define ATOMIC_BLOCK_START ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + #define ATOMIC_BLOCK_END } + #endif +#elif (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) + // UsingChipKIT Core on Arduino IDE + #define ATOMIC_BLOCK_START unsigned int __status = disableInterrupts(); { + #define ATOMIC_BLOCK_END } restoreInterrupts(__status); +#elif (RH_PLATFORM == RH_PLATFORM_UNO32) + // Under old MPIDE, which has been discontinued: + #include + #define ATOMIC_BLOCK_START unsigned int __status = INTDisableInterrupts(); { + #define ATOMIC_BLOCK_END } INTRestoreInterrupts(__status); +#elif (RH_PLATFORM == RH_PLATFORM_STM32L0) + #define ATOMIC_BLOCK_START uint32_t primask = __get_PRIMASK(); __disable_irq(); { + #define ATOMIC_BLOCK_END } __set_PRIMASK(primask); +#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Particle Photon with firmware-develop + #define ATOMIC_BLOCK_START { int __prev = HAL_disable_irq(); + #define ATOMIC_BLOCK_END HAL_enable_irq(__prev); } +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) +// See hardware/esp8266/2.0.0/cores/esp8266/Arduino.h + #define ATOMIC_BLOCK_START { uint32_t __savedPS = xt_rsil(15); + #define ATOMIC_BLOCK_END xt_wsr_ps(__savedPS);} +#elif (RH_PLATFORM == RH_PLATFORM_ESP32) +// jPerotto see hardware/esp32/1.0.4/tools/sdk/include/esp32/xtensa/xruntime.h +// No, not supported on ESP32C3 +// #define ATOMIC_BLOCK_START uint32_t volatile register ilevel = XTOS_DISABLE_ALL_INTERRUPTS; +// #define ATOMIC_BLOCK_END XTOS_RESTORE_INTLEVEL(ilevel); +// This compiles for all ESP32 but is it correct? +// #define ATOMIC_BLOCK_START int RH_ATOMIC_state = portSET_INTERRUPT_MASK_FROM_ISR(); +// #define ATOMIC_BLOCK_END portCLEAR_INTERRUPT_MASK_FROM_ISR(RH_ATOMIC_state); +// These appear to be defined for all ESP32 type: + #include "freertos/atomic.h" + #define ATOMIC_BLOCK_START ATOMIC_ENTER_CRITICAL() + #define ATOMIC_BLOCK_END ATOMIC_EXIT_CRITICAL() +#else + // TO BE DONE: + #define ATOMIC_BLOCK_START + #define ATOMIC_BLOCK_END +#endif + +//////////////////////////////////////////////////// +// Try to be compatible with systems that support yield() and multitasking +// instead of spin-loops +// Recent Arduino IDE or Teensy 3 has yield() +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(RH_CUBE_CELL_BOARD) + #define YIELD +#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO && ARDUINO >= 155) || (defined(TEENSYDUINO) && defined(__MK20DX128__)) + #define YIELD yield(); +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) +// ESP8266 also has it + #define YIELD yield(); +#elif (RH_PLATFORM == RH_PLATFORM_STM32L0) + #define YIELD yield(); +#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + //ESP32 and ESP8266 use freertos so we include calls + //that we would normall exit a function and return to + //the rtos in mgosYield() (E.G flush TX uart buffer + extern "C" { + void mgosYield(void); + } + #define YIELD mgosYield() +#elif (RH_PLATFORM == RH_PLATFORM_ESP32) + // ESP32 also has it + #define YIELD yield(); +#else + #define YIELD +#endif + +//////////////////////////////////////////////////// +// digitalPinToInterrupt is not available prior to Arduino 1.5.6 and 1.0.6 +// See https://arduino.cc/en/Reference/attachInterrupt +#ifndef NOT_AN_INTERRUPT + #define NOT_AN_INTERRUPT -1 +#endif +#ifndef digitalPinToInterrupt + #if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && !defined(__arm__) + + #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + // Arduino Mega, Mega ADK, Mega Pro + // 2->0, 3->1, 21->2, 20->3, 19->4, 18->5 + #define digitalPinToInterrupt(p) ((p) == 2 ? 0 : ((p) == 3 ? 1 : ((p) >= 18 && (p) <= 21 ? 23 - (p) : NOT_AN_INTERRUPT))) + + #elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) + // Arduino 1284 and 1284P - See Manicbug and Optiboot + // 10->0, 11->1, 2->2 + #define digitalPinToInterrupt(p) ((p) == 10 ? 0 : ((p) == 11 ? 1 : ((p) == 2 ? 2 : NOT_AN_INTERRUPT))) + + #elif defined(__AVR_ATmega32U4__) + // Leonardo, Yun, Micro, Pro Micro, Flora, Esplora + // 3->0, 2->1, 0->2, 1->3, 7->4 + #define digitalPinToInterrupt(p) ((p) == 0 ? 2 : ((p) == 1 ? 3 : ((p) == 2 ? 1 : ((p) == 3 ? 0 : ((p) == 7 ? 4 : NOT_AN_INTERRUPT))))) + + #else + // All other arduino except Due: + // Serial Arduino, Extreme, NG, BT, Uno, Diecimila, Duemilanove, Nano, Menta, Pro, Mini 04, Fio, LilyPad, Ethernet etc + // 2->0, 3->1 + #define digitalPinToInterrupt(p) ((p) == 2 ? 0 : ((p) == 3 ? 1 : NOT_AN_INTERRUPT)) + + #endif + + #elif (RH_PLATFORM == RH_PLATFORM_UNO32) || (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) + // Hmmm, this is correct for Uno32, but what about other boards on ChipKIT Core? + #define digitalPinToInterrupt(p) ((p) == 38 ? 0 : ((p) == 2 ? 1 : ((p) == 7 ? 2 : ((p) == 8 ? 3 : ((p) == 735 ? 4 : NOT_AN_INTERRUPT))))) + + #elif (RH_PLATFORM == RH_PLATFORM_ESP32) + #define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) + + #elif (RH_PLATFORM == RH_PLATFORM_ESP8266) + #define digitalPinToInterrupt(p) (((p) < EXTERNAL_NUM_INTERRUPTS)? (p) : NOT_AN_INTERRUPT) + + + #else + // Everything else (including Due and Teensy) interrupt number the same as the interrupt pin number + #define digitalPinToInterrupt(p) (p) + #endif +#endif + +// On some platforms, attachInterrupt() takes a pin number, not an interrupt number +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_SAM_DUE)) || defined(ARDUINO_ARCH_STM32L0) + #define RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER +#endif + +// Slave select pin, some platforms such as ATTiny do not define it. +// ESP32 pins_arduino.h uses static const uint8_t SS = ; instead +// of a #define to declare the SS constant. +#if (RH_PLATFORM != RH_PLATFORM_ESP32) + #ifndef SS + #define SS 10 + #endif +#endif + +// Some platforms require special attributes for interrupt routines +#if (RH_PLATFORM == RH_PLATFORM_ESP8266) + // interrupt handler and related code must be in RAM on ESP8266, + // according to issue #46. + // 2023-09-19: ESP8266 is now compatible with ESP 32 and uses IRAM_ATTR + #define RH_INTERRUPT_ATTR IRAM_ATTR + +#elif (RH_PLATFORM == RH_PLATFORM_ESP32) + #define RH_INTERRUPT_ATTR IRAM_ATTR +#else + #define RH_INTERRUPT_ATTR +#endif + +// These defs cause trouble on some versions of Arduino +#undef abs +#undef round +#undef double + +// Sigh: there is no widespread adoption of htons and friends in the base code, only in some WiFi headers etc +// that have a lot of excess baggage +#if RH_PLATFORM != RH_PLATFORM_UNIX && !defined(htons) +// #ifndef htons +// These predefined macros available on modern GCC compilers + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + // Atmel processors + #define htons(x) ( ((x)<<8) | (((x)>>8)&0xFF) ) + #define ntohs(x) htons(x) + #define htonl(x) ( ((x)<<24 & 0xFF000000UL) | \ + ((x)<< 8 & 0x00FF0000UL) | \ + ((x)>> 8 & 0x0000FF00UL) | \ + ((x)>>24 & 0x000000FFUL) ) + #define ntohl(x) htonl(x) + + #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // Others + #define htons(x) (x) + #define ntohs(x) (x) + #define htonl(x) (x) + #define ntohl(x) (x) + + #else + #error "RadioHead.h: Dont know how to define htons and friends for this processor" + #endif +#endif + +// Some platforms need a mutex for multihreaded case +#ifdef RH_USE_MUTEX + #include + #define RH_DECLARE_MUTEX(X) pthread_mutex_t X; + #define RH_MUTEX_INIT(X) pthread_mutex_init(&X, NULL) + #define RH_MUTEX_LOCK(X) pthread_mutex_lock(&X) + #define RH_MUTEX_UNLOCK(X) pthread_mutex_unlock(&X) +#else + #define RH_DECLARE_MUTEX(X) + #define RH_MUTEX_INIT(X) + #define RH_MUTEX_LOCK(X) + #define RH_MUTEX_UNLOCK(X) +#endif + +// This is the address that indicates a broadcast +#define RH_BROADCAST_ADDRESS 0xff + +// Specifies an invalid IO pin selection +#define RH_INVALID_PIN 0xff + +// Here we have some system wide macros you can define to alter the baviour of RadioHead +// in various ways. The Ardiono IDE has no way to configure such things at compile time so +// on that pltform you are forced to edit these macros here. +// On platformio you can add them to platformio.ini like, say: +// -D RH_ACK_DELAY=10` + +// Uncomment this to add a delay before acknowledgement in RHReliableDatagram. +// In some networks with mixed processor speeds, may need this delay to prevent a +// fast processor sending an ack before the receiver is ready for it. +// The time is in milliseconds +// #define RH_ACK_DELAY 10 + +// RH_Uncomment this to control which timer used by RH_ASK in some platforms like +// STM32: +// #define RH_HW_TIMER TIM21` + +// Uncomment this is to enable Encryption (see RHEncryptedDriver): +// But ensure you have installed the Crypto directory from arduinolibs first: +// https://rweather.github.io/arduinolibs/index.html +//#define RH_ENABLE_ENCRYPTION_MODULE + +// Some platforms like RocketScream need this to see debug Serial output from within RH +// and if it goes to Serial, get a hang after a few minutes. +//#define Serial SerialUSB + +#endif diff --git a/STM32ArduinoCompat/HardwareSPI.cpp b/STM32ArduinoCompat/HardwareSPI.cpp new file mode 100644 index 0000000..14cc9b9 --- /dev/null +++ b/STM32ArduinoCompat/HardwareSPI.cpp @@ -0,0 +1,181 @@ +// ArduinoCompat/HardwareSPI.cpp +// +// Interface between Arduino-like SPI interface and STM32F4 Discovery and similar +// using STM32F4xx_DSP_StdPeriph_Lib_V1.3.0 + +#include +#if (RH_PLATFORM == RH_PLATFORM_STM32STD) + +#include +#include +#include "stm32f4xx.h" +#include "stm32f4xx_spi.h" +extern "C" +{ +#include "gdb_stdio.h" +} + +// Symbolic definitions for the SPI pins we intend to use +// Currently we only support SPI1 +#define SPIx SPI1 +#define SPIx_CLK RCC_APB2Periph_SPI1 +#define SPIx_CLK_INIT RCC_APB2PeriphClockCmd +#define SPIx_IRQn SPI2_IRQn +#define SPIx_IRQHANDLER SPI2_IRQHandler + +#define SPIx_SCK_PIN GPIO_Pin_5 +#define SPIx_SCK_GPIO_PORT GPIOA +#define SPIx_SCK_GPIO_CLK RCC_AHB1Periph_GPIOA +#define SPIx_SCK_SOURCE GPIO_PinSource5 +#define SPIx_SCK_AF GPIO_AF_SPI1 + +#define SPIx_MISO_PIN GPIO_Pin_6 +#define SPIx_MISO_GPIO_PORT GPIOA +#define SPIx_MISO_GPIO_CLK RCC_AHB1Periph_GPIOA +#define SPIx_MISO_SOURCE GPIO_PinSource6 +#define SPIx_MISO_AF GPIO_AF_SPI1 + +#define SPIx_MOSI_PIN GPIO_Pin_7 +#define SPIx_MOSI_GPIO_PORT GPIOA +#define SPIx_MOSI_GPIO_CLK RCC_AHB1Periph_GPIOA +#define SPIx_MOSI_SOURCE GPIO_PinSource7 +#define SPIx_MOSI_AF GPIO_AF_SPI1 + +HardwareSPI::HardwareSPI(uint32_t spiPortNumber) : + _spiPortNumber(spiPortNumber) +{ +} + +void HardwareSPI::begin(SPIFrequency frequency, uint32_t bitOrder, uint32_t mode) +{ + GPIO_InitTypeDef GPIO_InitStructure; +// NVIC_InitTypeDef NVIC_InitStructure; + SPI_InitTypeDef SPI_InitStructure; + + /* Peripheral Clock Enable -------------------------------------------------*/ + /* Enable the SPI clock */ + RCC_APB2PeriphClockCmd(SPIx_CLK, ENABLE); + + /* Enable GPIO clocks */ + RCC_AHB1PeriphClockCmd(SPIx_SCK_GPIO_CLK | SPIx_MISO_GPIO_CLK | SPIx_MOSI_GPIO_CLK, ENABLE); + + /* SPI GPIO Configuration --------------------------------------------------*/ + /* GPIO Deinitialisation */ + GPIO_DeInit(SPIx_SCK_GPIO_PORT); + GPIO_DeInit(SPIx_MISO_GPIO_PORT); + GPIO_DeInit(SPIx_MOSI_GPIO_PORT); + + /* Connect SPI pins to AF5 */ + GPIO_PinAFConfig(SPIx_SCK_GPIO_PORT, SPIx_SCK_SOURCE, SPIx_SCK_AF); + GPIO_PinAFConfig(SPIx_MISO_GPIO_PORT, SPIx_MISO_SOURCE, SPIx_MISO_AF); + GPIO_PinAFConfig(SPIx_MOSI_GPIO_PORT, SPIx_MOSI_SOURCE, SPIx_MOSI_AF); + + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; + + /* SPI SCK pin configuration */ + GPIO_InitStructure.GPIO_Pin = SPIx_SCK_PIN; + GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStructure); + + /* SPI MISO pin configuration */ + GPIO_InitStructure.GPIO_Pin = SPIx_MISO_PIN; + GPIO_Init(SPIx_MISO_GPIO_PORT, &GPIO_InitStructure); + + /* SPI MOSI pin configuration */ + GPIO_InitStructure.GPIO_Pin = SPIx_MOSI_PIN; + GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStructure); + + /* SPI configuration -------------------------------------------------------*/ + SPI_I2S_DeInit(SPIx); + SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; + SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; + if (mode == SPI_MODE0) + { + SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; + SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; + } + else if (mode == SPI_MODE1) + { + SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; + SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; + } + else if (mode == SPI_MODE2) + { + SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; + SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; + } + else if (mode == SPI_MODE3) + { + SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; + SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; + } + + SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; + // Prescaler is divided into PCLK2 (84MHz) to get SPI baud rate/clock speed + // 256 => 328.125kHz + // 128 => 656.25kHz + // 64 => 1.3125MHz + // 32 => 2.625MHz + // 16 => 5.25MHz + // 8 => 10.5MHz + // 4 => 21.0MHz + switch (frequency) + { + case SPI_21_0MHZ: + SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; + break; + case SPI_10_5MHZ: + SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; + break; + case SPI_5_25MHZ: + SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; + break; + case SPI_2_625MHZ: + default: + SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; + break; + case SPI_1_3125MHZ: + SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; + break; + case SPI_656_25KHZ: + SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; + break; + case SPI_328_125KHZ: + SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; + break; + + } + + if (bitOrder == LSBFIRST) + SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_LSB; + else + SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; + SPI_InitStructure.SPI_CRCPolynomial = 7; + SPI_InitStructure.SPI_Mode = SPI_Mode_Master; + + /* Initializes the SPI communication */ + SPI_Init(SPIx, &SPI_InitStructure); + /* Enable SPI1 */ + SPI_Cmd(SPIx, ENABLE); +} + +void HardwareSPI::end(void) +{ + SPI_DeInit(SPIx); +} + +uint8_t HardwareSPI::transfer(uint8_t data) +{ + // Wait for TX empty + while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET) + ; + SPI_SendData(SPIx, data); + // Wait for RX not empty + while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET) + ; + return SPI_ReceiveData(SPIx); +} + +#endif diff --git a/STM32ArduinoCompat/HardwareSPI.h b/STM32ArduinoCompat/HardwareSPI.h new file mode 100644 index 0000000..ccb0084 --- /dev/null +++ b/STM32ArduinoCompat/HardwareSPI.h @@ -0,0 +1,38 @@ +// ArduinoCompat/HardwareSPI.h +// STM32 implementattion of Arduino compatible SPI class + +#ifndef _HardwareSPI_h +#define _HardwareSPI_h + +#include + +typedef enum SPIFrequency { + SPI_21_0MHZ = 0, /**< 21 MHz */ + SPI_10_5MHZ = 1, /**< 10.5 MHz */ + SPI_5_25MHZ = 2, /**< 5.25 MHz */ + SPI_2_625MHZ = 3, /**< 2.625 MHz */ + SPI_1_3125MHZ = 4, /**< 1.3125 MHz */ + SPI_656_25KHZ = 5, /**< 656.25 KHz */ + SPI_328_125KHZ = 6, /**< 328.125 KHz */ +} SPIFrequency; + +#define SPI_MODE0 0x00 +#define SPI_MODE1 0x04 +#define SPI_MODE2 0x08 +#define SPI_MODE3 0x0C + +class HardwareSPI +{ +public: + HardwareSPI(uint32_t spiPortNumber); // Only port SPI1 is currently supported + void begin(SPIFrequency frequency, uint32_t bitOrder, uint32_t mode); + void end(void); + uint8_t transfer(uint8_t data); + +private: + uint32_t _spiPortNumber; // Not used yet. +}; +extern HardwareSPI SPI; + + +#endif diff --git a/STM32ArduinoCompat/HardwareSerial.cpp b/STM32ArduinoCompat/HardwareSerial.cpp new file mode 100644 index 0000000..d59d879 --- /dev/null +++ b/STM32ArduinoCompat/HardwareSerial.cpp @@ -0,0 +1,349 @@ +// ArduinoCompat/HardwareSerial.cpp +// +// Author: mikem@airspayce.com + +#include +#if (RH_PLATFORM == RH_PLATFORM_STM32STD) +#include +#include + +// Preinstantiated Serial objects +HardwareSerial Serial1(USART1); +HardwareSerial Serial2(USART2); +HardwareSerial Serial3(USART3); +HardwareSerial Serial4(UART4); +HardwareSerial Serial5(UART5); +HardwareSerial Serial6(USART6); + +/////////////////////////////////////////////////////////////// +// RingBuffer +/////////////////////////////////////////////////////////////// + +RingBuffer::RingBuffer() + : _head(0), + _tail(0), + _overruns(0), + _underruns(0) +{ +} + +bool RingBuffer::isEmpty() +{ + return _head == _tail; +} + +bool RingBuffer::isFull() +{ + return ((_head + 1) % ARDUINO_RINGBUFFER_SIZE) == _tail; +} + +bool RingBuffer::write(uint8_t ch) +{ + if (isFull()) + { + _overruns++; + return false; + } + _buffer[_head] = ch; + if (++_head >= ARDUINO_RINGBUFFER_SIZE) + _head = 0; + return true; +} + +uint8_t RingBuffer::read() +{ + if (isEmpty()) + { + _underruns++; + return 0; // What else can we do? + } + uint8_t ret = _buffer[_tail]; + if (++_tail >= ARDUINO_RINGBUFFER_SIZE) + _tail = 0; + return ret; +} + +/////////////////////////////////////////////////////////////// +// HardwareSerial +/////////////////////////////////////////////////////////////// + +// On STM32F4 Discovery, USART 1 is not very useful conflicts with the Green lED +HardwareSerial::HardwareSerial(USART_TypeDef* usart) + : _usart(usart) +{ +} + +void HardwareSerial::begin(unsigned long baud) +{ + USART_InitTypeDef USART_InitStructure; + GPIO_InitTypeDef GPIO_InitStructure_TX; + GPIO_InitTypeDef GPIO_InitStructure_RX; + + // Common GPIO structure init: + GPIO_InitStructure_TX.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure_TX.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure_TX.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure_TX.GPIO_PuPd = GPIO_PuPd_UP; + + GPIO_InitStructure_RX.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure_RX.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure_RX.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure_RX.GPIO_PuPd = GPIO_PuPd_UP; + // CTS or SCLK outputs are not supported. + + USART_InitStructure.USART_BaudRate = baud * 25/8; // Why? + // Only 8N1 is currently supported + USART_InitStructure.USART_WordLength = USART_WordLength_8b; + USART_InitStructure.USART_StopBits = USART_StopBits_1; + USART_InitStructure.USART_Parity = USART_Parity_No; + USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; + USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; + + // Different for each USART: + if (_usart == USART1) + { + // Initialise the clocks for this USART and its RX, TX pins port + RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); + + GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); + GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); + + GPIO_InitStructure_TX.GPIO_Pin = GPIO_Pin_9; + GPIO_InitStructure_RX.GPIO_Pin = GPIO_Pin_10; + GPIO_Init(GPIOA, &GPIO_InitStructure_TX); + GPIO_Init(GPIOA, &GPIO_InitStructure_RX); + // Initialise the USART + USART_Init(USART1, &USART_InitStructure); + // Enable the RXNE interrupt + USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); + // Enable global interrupt + NVIC_EnableIRQ(USART1_IRQn); + } + else if (_usart == USART2) + { + // Initialise the clocks for this USART and its RX, TX pins port + RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); + + GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); + GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2); + + GPIO_InitStructure_TX.GPIO_Pin = GPIO_Pin_2; + GPIO_InitStructure_RX.GPIO_Pin = GPIO_Pin_3; + GPIO_Init(GPIOA, &GPIO_InitStructure_TX); + GPIO_Init(GPIOA, &GPIO_InitStructure_RX); + // Initialise the USART + USART_Init(USART2, &USART_InitStructure); + // Enable the RXNE interrupt + USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); + // Enable global interrupt + NVIC_EnableIRQ(USART2_IRQn); + } + else if (_usart == USART3) + { + // Initialise the clocks for this USART and its RX, TX pins port + RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); + + GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_USART3); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_USART3); + + GPIO_InitStructure_TX.GPIO_Pin = GPIO_Pin_8; + GPIO_InitStructure_RX.GPIO_Pin = GPIO_Pin_9; + GPIO_Init(GPIOD, &GPIO_InitStructure_TX); + GPIO_Init(GPIOD, &GPIO_InitStructure_RX); + // Initialise the USART + USART_Init(USART3, &USART_InitStructure); + // Enable the RXNE interrupt + USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); + // Enable global interrupt + NVIC_EnableIRQ(USART3_IRQn); + } + else if (_usart == UART4) + { + // Initialise the clocks for this USART and its RX, TX pins port + RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE); + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); + + GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_UART4); + GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_UART4); + + GPIO_InitStructure_TX.GPIO_Pin = GPIO_Pin_0; + GPIO_InitStructure_RX.GPIO_Pin = GPIO_Pin_1; + GPIO_Init(GPIOA, &GPIO_InitStructure_TX); + GPIO_Init(GPIOA, &GPIO_InitStructure_RX); + // Initialise the USART + USART_Init(UART4, &USART_InitStructure); + // Enable the RXNE interrupt + USART_ITConfig(UART4, USART_IT_RXNE, ENABLE); + // Enable global interrupt + NVIC_EnableIRQ(UART4_IRQn); + } + else if (_usart == UART5) + { + // Initialise the clocks for this USART and its RX, TX pins port + RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5, ENABLE); + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD, ENABLE); + + GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_UART5); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_UART5); + + GPIO_InitStructure_TX.GPIO_Pin = GPIO_Pin_12; + GPIO_InitStructure_RX.GPIO_Pin = GPIO_Pin_2; + GPIO_Init(GPIOC, &GPIO_InitStructure_TX); + GPIO_Init(GPIOD, &GPIO_InitStructure_RX); + // Initialise the USART + USART_Init(UART5, &USART_InitStructure); + // Enable the RXNE interrupt + USART_ITConfig(UART5, USART_IT_RXNE, ENABLE); + // Enable global interrupt + NVIC_EnableIRQ(UART5_IRQn); + } + else if (_usart == USART6) + { + // Initialise the clocks for this USART and its RX, TX pins port + RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART6, ENABLE); + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); + + GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_USART6); + GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_USART6); + + GPIO_InitStructure_TX.GPIO_Pin = GPIO_Pin_6; + GPIO_InitStructure_RX.GPIO_Pin = GPIO_Pin_7; + GPIO_Init(GPIOC, &GPIO_InitStructure_TX); + GPIO_Init(GPIOC, &GPIO_InitStructure_RX); + // Initialise the USART + USART_Init(USART6, &USART_InitStructure); + // Enable the RXNE interrupt + USART_ITConfig(USART6, USART_IT_RXNE, ENABLE); + // Enable global interrupt + NVIC_EnableIRQ(USART6_IRQn); + } + + USART_Cmd(_usart, ENABLE); +} + +void HardwareSerial::end() +{ + USART_Cmd(_usart, DISABLE); + USART_DeInit(_usart); +} + +int HardwareSerial::available(void) +{ + return !_rxRingBuffer.isEmpty(); +} + +int HardwareSerial::read(void) +{ + return _rxRingBuffer.read(); +} + +size_t HardwareSerial::write(uint8_t ch) +{ + _txRingBuffer.write(ch); // Queue it + USART_ITConfig(_usart, USART_IT_TXE, ENABLE); // Enable the TX interrupt + return 1; +} + +extern "C" +{ + void USART1_IRQHandler(void) + { + if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) + { + Serial1._rxRingBuffer.write(USART_ReceiveData(USART1)); + } + if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET) + { + // Transmitter is empty, maybe send another char? + if (Serial1._txRingBuffer.isEmpty()) + USART_ITConfig(USART1, USART_IT_TXE, DISABLE); // No more to send, disable the TX interrupt + else + USART_SendData(USART1, Serial1._txRingBuffer.read()); + } + } + void USART2_IRQHandler(void) + { + if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) + { + // Newly received char, try to put it in our rx buffer + Serial2._rxRingBuffer.write(USART_ReceiveData(USART2)); + } + if (USART_GetITStatus(USART2, USART_IT_TXE) != RESET) + { + // Transmitter is empty, maybe send another char? + if (Serial2._txRingBuffer.isEmpty()) + USART_ITConfig(USART2, USART_IT_TXE, DISABLE); // No more to send, disable the TX interrupt + else + USART_SendData(USART2, Serial2._txRingBuffer.read()); + } + } + void USART3_IRQHandler(void) + { + if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) + { + // Newly received char, try to put it in our rx buffer + Serial3._rxRingBuffer.write(USART_ReceiveData(USART3)); + } + if (USART_GetITStatus(USART3, USART_IT_TXE) != RESET) + { + // Transmitter is empty, maybe send another char? + if (Serial3._txRingBuffer.isEmpty()) + USART_ITConfig(USART3, USART_IT_TXE, DISABLE); // No more to send, disable the TX interrupt + else + USART_SendData(USART3, Serial3._txRingBuffer.read()); + } + } + void UART4_IRQHandler(void) + { + if (USART_GetITStatus(UART4, USART_IT_RXNE) != RESET) + { + // Newly received char, try to put it in our rx buffer + Serial4._rxRingBuffer.write(USART_ReceiveData(UART4)); + } + if (USART_GetITStatus(UART4, USART_IT_TXE) != RESET) + { + // Transmitter is empty, maybe send another char? + if (Serial4._txRingBuffer.isEmpty()) + USART_ITConfig(UART4, USART_IT_TXE, DISABLE); // No more to send, disable the TX interrupt + else + USART_SendData(UART4, Serial4._txRingBuffer.read()); + } + } + void UART5_IRQHandler(void) + { + if (USART_GetITStatus(UART5, USART_IT_RXNE) != RESET) + { + // Newly received char, try to put it in our rx buffer + Serial5._rxRingBuffer.write(USART_ReceiveData(UART5)); + } + if (USART_GetITStatus(UART5, USART_IT_TXE) != RESET) + { + // Transmitter is empty, maybe send another char? + if (Serial5._txRingBuffer.isEmpty()) + USART_ITConfig(UART5, USART_IT_TXE, DISABLE); // No more to send, disable the TX interrupt + else + USART_SendData(UART5, Serial5._txRingBuffer.read()); + } + } + void USART6_IRQHandler(void) + { + if (USART_GetITStatus(USART6, USART_IT_RXNE) != RESET) + { + // Newly received char, try to put it in our rx buffer + Serial6._rxRingBuffer.write(USART_ReceiveData(USART6)); + } + if (USART_GetITStatus(USART6, USART_IT_TXE) != RESET) + { + // Transmitter is empty, maybe send another char? + if (Serial6._txRingBuffer.isEmpty()) + USART_ITConfig(USART6, USART_IT_TXE, DISABLE); // No more to send, disable the TX interrupt + else + USART_SendData(USART6, Serial6._txRingBuffer.read()); + } + } +} + +#endif diff --git a/STM32ArduinoCompat/HardwareSerial.h b/STM32ArduinoCompat/HardwareSerial.h new file mode 100644 index 0000000..f9f2089 --- /dev/null +++ b/STM32ArduinoCompat/HardwareSerial.h @@ -0,0 +1,78 @@ +// ArduinoCompat/HardwareSerial.h +// STM32 implementation of Arduino compatible serial class + +#include +#if (RH_PLATFORM == RH_PLATFORM_STM32STD) +#ifndef _HardwareSerial_h +#define _HardwareSerial_h + +#include +#include +#include + +#ifndef ARDUINO_RINGBUFFER_SIZE +#define ARDUINO_RINGBUFFER_SIZE 64 +#endif + +class RingBuffer +{ +public: + RingBuffer(); + bool isEmpty(); + bool isFull(); + bool write(uint8_t ch); + uint8_t read(); + +private: + uint8_t _buffer[ARDUINO_RINGBUFFER_SIZE]; // In fact we can hold up to ARDUINO_RINGBUFFER_SIZE-1 bytes + uint16_t _head; // Index of next write + uint16_t _tail; // Index of next read + uint32_t _overruns; // Write attempted when buffer full + uint32_t _underruns; // Read attempted when buffer empty +}; + +// Mostly compatible wuith Arduino HardwareSerial +// Theres just enough here to support RadioHead RH_Serial +class HardwareSerial +{ +public: + HardwareSerial(USART_TypeDef* usart); + void begin(unsigned long baud); + void end(); + virtual int available(void); + virtual int read(void); + virtual size_t write(uint8_t); + inline size_t write(unsigned long n) { return write((uint8_t)n); } + inline size_t write(long n) { return write((uint8_t)n); } + inline size_t write(unsigned int n) { return write((uint8_t)n); } + inline size_t write(int n) { return write((uint8_t)n); } + + // These need to be public so the IRQ handler can read and write to them: + RingBuffer _rxRingBuffer; + RingBuffer _txRingBuffer; + +private: + USART_TypeDef* _usart; + +}; + +// Predefined serial ports are configured so: +// Serial STM32 UART RX pin Tx Pin Comments +// Serial1 USART1 PA10 PA9 TX Conflicts with GREEN LED on Discovery +// Serial2 USART2 PA3 PA2 +// Serial3 USART3 PD9 PD10 +// Serial4 UART4 PA1 PA0 TX conflicts with USER button on Discovery +// Serial5 UART5 PD2 PC12 TX conflicts with CS43L22 SDIN on Discovery +// Serial6 USART6 PC7 PC6 RX conflicts with CS43L22 MCLK on Discovery +// +// All ports are idle HIGH, LSB first, 8 bits, No parity, 1 stop bit +extern HardwareSerial Serial1; +extern HardwareSerial Serial2; +extern HardwareSerial Serial3; +extern HardwareSerial Serial4; +extern HardwareSerial Serial5; +extern HardwareSerial Serial6; + +#endif + +#endif diff --git a/STM32ArduinoCompat/README b/STM32ArduinoCompat/README new file mode 100644 index 0000000..aecef6f --- /dev/null +++ b/STM32ArduinoCompat/README @@ -0,0 +1,6 @@ +This directory contains some files to allow RadioHead to be built on STM32F4 +Discovery boards, using the native STM Firmware libraries, in order to support +Codec2WalkieTalkie and other projects. + +The files provide just enough Arduino compatibility to allow RadioHead to +build in that environment. diff --git a/STM32ArduinoCompat/wirish.cpp b/STM32ArduinoCompat/wirish.cpp new file mode 100644 index 0000000..fb9d432 --- /dev/null +++ b/STM32ArduinoCompat/wirish.cpp @@ -0,0 +1,413 @@ +// ArduinoCompat/wirish.cpp +// +// Arduino-like API for STM32F4 Discovery and similar +// using STM32F4xx_DSP_StdPeriph_Lib_V1.3.0 + +#include +#if (RH_PLATFORM == RH_PLATFORM_STM32STD) +#include + +SerialUSBClass SerialUSB; + +// Describes all the STM32 things we need to know about a digital IO pin to +// make it input or output or to configure as an interrupt +typedef struct +{ + uint32_t ahbperiph; + GPIO_TypeDef* port; + uint16_t pin; + uint8_t extiportsource; + uint8_t extipinsource; +} GPIOPin; + +// These describe the registers and bits for each digital IO pin to allow us to +// provide Arduino-like pin addressing, digitalRead etc. +// Indexed by pin number +GPIOPin pins[] = +{ + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_0, EXTI_PortSourceGPIOA, EXTI_PinSource0 }, // 0 = PA0 + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_1, EXTI_PortSourceGPIOA, EXTI_PinSource1 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_2, EXTI_PortSourceGPIOA, EXTI_PinSource2 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_3, EXTI_PortSourceGPIOA, EXTI_PinSource3 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_4, EXTI_PortSourceGPIOA, EXTI_PinSource4 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_5, EXTI_PortSourceGPIOA, EXTI_PinSource5 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_6, EXTI_PortSourceGPIOA, EXTI_PinSource6 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_7, EXTI_PortSourceGPIOA, EXTI_PinSource7 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_8, EXTI_PortSourceGPIOA, EXTI_PinSource8 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_9, EXTI_PortSourceGPIOA, EXTI_PinSource9 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_10, EXTI_PortSourceGPIOA, EXTI_PinSource10 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_11, EXTI_PortSourceGPIOA, EXTI_PinSource11 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_12, EXTI_PortSourceGPIOA, EXTI_PinSource12 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_13, EXTI_PortSourceGPIOA, EXTI_PinSource13 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_14, EXTI_PortSourceGPIOA, EXTI_PinSource14 }, + { RCC_AHB1Periph_GPIOA, GPIOA, GPIO_Pin_15, EXTI_PortSourceGPIOA, EXTI_PinSource15 }, // 15 = PA15 + + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_0, EXTI_PortSourceGPIOB, EXTI_PinSource0 }, // 16 = PB0 + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_1, EXTI_PortSourceGPIOB, EXTI_PinSource1 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_2, EXTI_PortSourceGPIOB, EXTI_PinSource2 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_3, EXTI_PortSourceGPIOB, EXTI_PinSource3 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_4, EXTI_PortSourceGPIOB, EXTI_PinSource4 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_5, EXTI_PortSourceGPIOB, EXTI_PinSource5 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_6, EXTI_PortSourceGPIOB, EXTI_PinSource6 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_7, EXTI_PortSourceGPIOB, EXTI_PinSource7 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_8, EXTI_PortSourceGPIOB, EXTI_PinSource8 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_9, EXTI_PortSourceGPIOB, EXTI_PinSource9 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_10, EXTI_PortSourceGPIOB, EXTI_PinSource10 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_11, EXTI_PortSourceGPIOB, EXTI_PinSource11 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_12, EXTI_PortSourceGPIOB, EXTI_PinSource12 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_13, EXTI_PortSourceGPIOB, EXTI_PinSource13 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_14, EXTI_PortSourceGPIOB, EXTI_PinSource14 }, + { RCC_AHB1Periph_GPIOB, GPIOB, GPIO_Pin_15, EXTI_PortSourceGPIOB, EXTI_PinSource15 }, // 31 = PB15 + + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_0, EXTI_PortSourceGPIOC, EXTI_PinSource0 }, // 32 = PC0 + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_1, EXTI_PortSourceGPIOC, EXTI_PinSource1 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_2, EXTI_PortSourceGPIOC, EXTI_PinSource2 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_3, EXTI_PortSourceGPIOC, EXTI_PinSource3 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_4, EXTI_PortSourceGPIOC, EXTI_PinSource4 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_5, EXTI_PortSourceGPIOC, EXTI_PinSource5 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_6, EXTI_PortSourceGPIOC, EXTI_PinSource6 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_7, EXTI_PortSourceGPIOC, EXTI_PinSource7 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_8, EXTI_PortSourceGPIOC, EXTI_PinSource8 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_9, EXTI_PortSourceGPIOC, EXTI_PinSource9 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_10, EXTI_PortSourceGPIOC, EXTI_PinSource10 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_11, EXTI_PortSourceGPIOC, EXTI_PinSource11 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_12, EXTI_PortSourceGPIOC, EXTI_PinSource12 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_13, EXTI_PortSourceGPIOC, EXTI_PinSource13 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_14, EXTI_PortSourceGPIOC, EXTI_PinSource14 }, + { RCC_AHB1Periph_GPIOC, GPIOC, GPIO_Pin_15, EXTI_PortSourceGPIOC, EXTI_PinSource15 }, // 47 = PC15 + + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_0, EXTI_PortSourceGPIOD, EXTI_PinSource0 }, // 48 = PD0 + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_1, EXTI_PortSourceGPIOD, EXTI_PinSource1 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_2, EXTI_PortSourceGPIOD, EXTI_PinSource2 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_3, EXTI_PortSourceGPIOD, EXTI_PinSource3 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_4, EXTI_PortSourceGPIOD, EXTI_PinSource4 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_5, EXTI_PortSourceGPIOD, EXTI_PinSource5 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_6, EXTI_PortSourceGPIOD, EXTI_PinSource6 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_7, EXTI_PortSourceGPIOD, EXTI_PinSource7 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_8, EXTI_PortSourceGPIOD, EXTI_PinSource8 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_9, EXTI_PortSourceGPIOD, EXTI_PinSource9 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_10, EXTI_PortSourceGPIOD, EXTI_PinSource10 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_11, EXTI_PortSourceGPIOD, EXTI_PinSource11 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_12, EXTI_PortSourceGPIOD, EXTI_PinSource12 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_13, EXTI_PortSourceGPIOD, EXTI_PinSource13 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_14, EXTI_PortSourceGPIOD, EXTI_PinSource14 }, + { RCC_AHB1Periph_GPIOD, GPIOD, GPIO_Pin_15, EXTI_PortSourceGPIOD, EXTI_PinSource15 }, // 63 = PD15 + + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_0, EXTI_PortSourceGPIOE, EXTI_PinSource0 }, // 64 = PE0 + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_1, EXTI_PortSourceGPIOE, EXTI_PinSource1 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_2, EXTI_PortSourceGPIOE, EXTI_PinSource2 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_3, EXTI_PortSourceGPIOE, EXTI_PinSource3 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_4, EXTI_PortSourceGPIOE, EXTI_PinSource4 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_5, EXTI_PortSourceGPIOE, EXTI_PinSource5 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_6, EXTI_PortSourceGPIOE, EXTI_PinSource6 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_7, EXTI_PortSourceGPIOE, EXTI_PinSource7 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_8, EXTI_PortSourceGPIOE, EXTI_PinSource8 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_9, EXTI_PortSourceGPIOE, EXTI_PinSource9 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_10, EXTI_PortSourceGPIOE, EXTI_PinSource10 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_11, EXTI_PortSourceGPIOE, EXTI_PinSource11 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_12, EXTI_PortSourceGPIOE, EXTI_PinSource12 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_13, EXTI_PortSourceGPIOE, EXTI_PinSource13 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_14, EXTI_PortSourceGPIOE, EXTI_PinSource14 }, + { RCC_AHB1Periph_GPIOE, GPIOE, GPIO_Pin_15, EXTI_PortSourceGPIOE, EXTI_PinSource15 }, // 79 = PE15 + +}; +#define NUM_PINS (sizeof(pins) / sizeof(GPIOPin)) + +typedef struct +{ + uint32_t extiline; + uint8_t extiirqn; + void (*handler)(void); +} IRQLine; + +// IRQ line data indexed by pin source number with its port +// and the programmable handler that will handle interrupts on that line +IRQLine irqlines[] = +{ + { EXTI_Line0, EXTI0_IRQn, 0 }, + { EXTI_Line1, EXTI1_IRQn, 0 }, + { EXTI_Line2, EXTI2_IRQn, 0 }, + { EXTI_Line3, EXTI3_IRQn, 0 }, + { EXTI_Line4, EXTI4_IRQn, 0 }, + { EXTI_Line5, EXTI9_5_IRQn, 0 }, + { EXTI_Line6, EXTI9_5_IRQn, 0 }, + { EXTI_Line7, EXTI9_5_IRQn, 0 }, + { EXTI_Line8, EXTI9_5_IRQn, 0 }, + { EXTI_Line9, EXTI9_5_IRQn, 0 }, + { EXTI_Line10, EXTI15_10_IRQn, 0 }, + { EXTI_Line11, EXTI15_10_IRQn, 0 }, + { EXTI_Line12, EXTI15_10_IRQn, 0 }, + { EXTI_Line13, EXTI15_10_IRQn, 0 }, + { EXTI_Line14, EXTI15_10_IRQn, 0 }, + { EXTI_Line15, EXTI15_10_IRQn, 0 }, +}; + +#define NUM_IRQ_LINES (sizeof(irqlines) / sizeof(IRQLine)) + +// Functions we expect to find in the sketch +extern void setup(); +extern void loop(); + +volatile unsigned long systick_count = 0; + +void SysTickConfig() +{ + /* Setup SysTick Timer for 1ms interrupts */ + if (SysTick_Config(SystemCoreClock / 1000)) + { + /* Capture error */ + while (1); + } + + /* Configure the SysTick handler priority */ + NVIC_SetPriority(SysTick_IRQn, 0x0); + // SysTick_Handler will now be called every 1 ms +} + +// These interrupt handlers have to be extern C else they dont get linked in to the interrupt vectors +extern "C" +{ + // Called every 1 ms + void SysTick_Handler(void) + { + systick_count++; + } + + // Interrupt handlers for optional external GPIO interrupts + void EXTI0_IRQHandler(void) + { + if (EXTI_GetITStatus(EXTI_Line0) != RESET) + { + if (irqlines[0].handler) + irqlines[0].handler(); + EXTI_ClearITPendingBit(EXTI_Line0); + } + } + void EXTI1_IRQHandler(void) + { + if (EXTI_GetITStatus(EXTI_Line1) != RESET) + { + if (irqlines[1].handler) + irqlines[1].handler(); + EXTI_ClearITPendingBit(EXTI_Line1); + } + } + void EXTI2_IRQHandler(void) + { + if (EXTI_GetITStatus(EXTI_Line2) != RESET) + { + if (irqlines[2].handler) + irqlines[2].handler(); + EXTI_ClearITPendingBit(EXTI_Line2); + } + } + void EXTI3_IRQHandler(void) + { + if (EXTI_GetITStatus(EXTI_Line3) != RESET) + { + if (irqlines[3].handler) + irqlines[3].handler(); + EXTI_ClearITPendingBit(EXTI_Line3); + } + } + void EXTI4_IRQHandler(void) + { + if (EXTI_GetITStatus(EXTI_Line4) != RESET) + { + if (irqlines[4].handler) + irqlines[4].handler(); + EXTI_ClearITPendingBit(EXTI_Line4); + } + } + void EXTI9_5_IRQHandler(void) + { + if (EXTI_GetITStatus(EXTI_Line5) != RESET) + { + if (irqlines[5].handler) + irqlines[5].handler(); + EXTI_ClearITPendingBit(EXTI_Line5); + } + if (EXTI_GetITStatus(EXTI_Line6) != RESET) + { + if (irqlines[6].handler) + irqlines[6].handler(); + EXTI_ClearITPendingBit(EXTI_Line6); + } + if (EXTI_GetITStatus(EXTI_Line7) != RESET) + { + if (irqlines[7].handler) + irqlines[7].handler(); + EXTI_ClearITPendingBit(EXTI_Line7); + } + if (EXTI_GetITStatus(EXTI_Line8) != RESET) + { + if (irqlines[8].handler) + irqlines[8].handler(); + EXTI_ClearITPendingBit(EXTI_Line8); + } + if (EXTI_GetITStatus(EXTI_Line9) != RESET) + { + if (irqlines[9].handler) + irqlines[9].handler(); + EXTI_ClearITPendingBit(EXTI_Line9); + } + } + void EXTI15_10_IRQHandler(void) + { + if (EXTI_GetITStatus(EXTI_Line10) != RESET) + { + if (irqlines[10].handler) + irqlines[10].handler(); + EXTI_ClearITPendingBit(EXTI_Line10); + } + if (EXTI_GetITStatus(EXTI_Line11) != RESET) + { + if (irqlines[11].handler) + irqlines[11].handler(); + EXTI_ClearITPendingBit(EXTI_Line11); + } + if (EXTI_GetITStatus(EXTI_Line12) != RESET) + { + if (irqlines[12].handler) + irqlines[12].handler(); + EXTI_ClearITPendingBit(EXTI_Line12); + } + if (EXTI_GetITStatus(EXTI_Line13) != RESET) + { + if (irqlines[13].handler) + irqlines[13].handler(); + EXTI_ClearITPendingBit(EXTI_Line13); + } + if (EXTI_GetITStatus(EXTI_Line14) != RESET) + { + if (irqlines[14].handler) + irqlines[14].handler(); + EXTI_ClearITPendingBit(EXTI_Line14); + } + if (EXTI_GetITStatus(EXTI_Line15) != RESET) + { + if (irqlines[15].handler) + irqlines[15].handler(); + EXTI_ClearITPendingBit(EXTI_Line15); + } + } +} + +// The sketch we want to run +//#include "examples/rf22/rf22_client/rf22_client.pde" + +// Run the Arduino standard functions in the main loop +int main(int argc, char** argv) +{ + SysTickConfig(); + // Seed the random number generator +// srand(getpid() ^ (unsigned) time(NULL)/2); + setup(); + while (1) + loop(); +} + +void pinMode(uint8_t pin, WiringPinMode mode) +{ + if (pin > NUM_PINS) + return; + // Enable the GPIO clock + RCC_AHB1PeriphClockCmd(pins[pin].ahbperiph, ENABLE); + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Pin = pins[pin].pin; + if (mode == INPUT) + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; // REVISIT + else if (mode == OUTPUT) + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // REVISIT + else + return; // Unknown so far + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(pins[pin].port, &GPIO_InitStructure); +} + +// This takes about 150ns on STM32F4 Discovery +void digitalWrite(uint8_t pin, uint8_t val) +{ + if (pin > NUM_PINS) + return; + if (val) + GPIO_SetBits(pins[pin].port, pins[pin].pin); + else + GPIO_ResetBits(pins[pin].port, pins[pin].pin); +} + +uint8_t digitalRead(uint8_t pin) +{ + if (pin > NUM_PINS) + return 0; + return GPIO_ReadInputDataBit(pins[pin].port, pins[pin].pin); +} + +void attachInterrupt(uint8_t pin, void (*handler)(void), int mode) +{ + EXTI_InitTypeDef EXTI_InitStructure; + NVIC_InitTypeDef NVIC_InitStructure; + + // Record the handler to call when the interrupt occurs + irqlines[pins[pin].extipinsource].handler = handler; + + /* Connect EXTI Line to GPIO Pin */ + SYSCFG_EXTILineConfig(pins[pin].extiportsource, pins[pin].extipinsource); + + /* Configure EXTI line */ + EXTI_InitStructure.EXTI_Line = irqlines[pins[pin].extipinsource].extiline; + + EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; + if (mode == RISING) + EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; + else if (mode == FALLING) + EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; + else if (mode == CHANGE) + EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; + EXTI_InitStructure.EXTI_LineCmd = ENABLE; + EXTI_Init(&EXTI_InitStructure); + + /* Enable and set EXTI Interrupt to the lowest priority */ + NVIC_InitStructure.NVIC_IRQChannel = irqlines[pins[pin].extipinsource].extiirqn; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + + NVIC_Init(&NVIC_InitStructure); + + // The relevant EXTI?_IRQHandler + // will now be called when the pin makes the selected transition +} + +void delay(unsigned long ms) +{ + unsigned long start = millis(); + + while (millis() - start < ms) + ; +} + +unsigned long millis() +{ + return systick_count; +} + +long random(long from, long to) +{ + return from + (RNG_GetRandomNumber() % (to - from)); +} + +long random(long to) +{ + return random(0, to); +} + +extern "C" +{ + // These need to be in C land for correct linking + void _init() {} + void _fini() {} +} + +#endif diff --git a/STM32ArduinoCompat/wirish.h b/STM32ArduinoCompat/wirish.h new file mode 100644 index 0000000..91d7ada --- /dev/null +++ b/STM32ArduinoCompat/wirish.h @@ -0,0 +1,157 @@ +// ArduinoCompat/wirish.h + +#ifndef _wirish_h +#define _wirish_h + +#include +#include +#include +#include +#include + +#define PROGMEM +#define memcpy_P memcpy + +typedef enum WiringPinMode { + OUTPUT, /**< Basic digital output: when the pin is HIGH, the + voltage is held at +3.3v (Vcc) and when it is LOW, it + is pulled down to ground. */ + + OUTPUT_OPEN_DRAIN, /**< In open drain mode, the pin indicates + "low" by accepting current flow to ground + and "high" by providing increased + impedance. An example use would be to + connect a pin to a bus line (which is pulled + up to a positive voltage by a separate + supply through a large resistor). When the + pin is high, not much current flows through + to ground and the line stays at positive + voltage; when the pin is low, the bus + "drains" to ground with a small amount of + current constantly flowing through the large + resistor from the external supply. In this + mode, no current is ever actually sourced + from the pin. */ + + INPUT, /**< Basic digital input. The pin voltage is sampled; when + it is closer to 3.3v (Vcc) the pin status is high, and + when it is closer to 0v (ground) it is low. If no + external circuit is pulling the pin voltage to high or + low, it will tend to randomly oscillate and be very + sensitive to noise (e.g., a breath of air across the pin + might cause the state to flip). */ + + INPUT_ANALOG, /**< This is a special mode for when the pin will be + used for analog (not digital) reads. Enables ADC + conversion to be performed on the voltage at the + pin. */ + + INPUT_PULLUP, /**< The state of the pin in this mode is reported + the same way as with INPUT, but the pin voltage + is gently "pulled up" towards +3.3v. This means + the state will be high unless an external device + is specifically pulling the pin down to ground, + in which case the "gentle" pull up will not + affect the state of the input. */ + + INPUT_PULLDOWN, /**< The state of the pin in this mode is reported + the same way as with INPUT, but the pin voltage + is gently "pulled down" towards 0v. This means + the state will be low unless an external device + is specifically pulling the pin up to 3.3v, in + which case the "gentle" pull down will not + affect the state of the input. */ + + INPUT_FLOATING, /**< Synonym for INPUT. */ + + PWM, /**< This is a special mode for when the pin will be used for + PWM output (a special case of digital output). */ + + PWM_OPEN_DRAIN, /**< Like PWM, except that instead of alternating + cycles of LOW and HIGH, the voltage on the pin + consists of alternating cycles of LOW and + floating (disconnected). */ +} WiringPinMode; + +extern void pinMode(uint8_t pin, WiringPinMode mode); +extern uint32_t millis(); +extern void delay(uint32_t millis); +extern void attachInterrupt(uint8_t, void (*)(void), int mode); +extern void digitalWrite(uint8_t pin, uint8_t val); +extern uint8_t digitalRead(uint8_t pin); + +//extern long random(long to); +//extern long random(long from, long to); + +#define HIGH 0x1 +#define LOW 0x0 + +#define LSBFIRST 0 +#define MSBFIRST 1 + +#define CHANGE 1 +#define FALLING 2 +#define RISING 3 + +// Equivalent to HardwareSerial in Arduino +class SerialUSBClass +{ +public: +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 + + // TODO: move these from being inlined + void begin(int baud) {} + + size_t println(const char* s) + { + print(s); + printf("\n"); + return 0; + } + size_t print(const char* s) + { + printf(s); + return 0; + } + size_t print(unsigned int n, int base = DEC) + { + if (base == DEC) + printf("%d", n); + else if (base == HEX) + printf("%02x", n); + else if (base == OCT) + printf("%o", n); + // TODO: BIN + return 0; + } + size_t print(char ch) + { + printf("%c", ch); + return 0; + } + size_t println(char ch) + { + printf("%c\n", ch); + return 0; + } + size_t print(unsigned char ch, int base = DEC) + { + return print((unsigned int)ch, base); + } + size_t println(unsigned char ch, int base = DEC) + { + print((unsigned int)ch, base); + printf("\n"); + return 0; + } + +}; + +// Global instance of the Serial output +extern SerialUSBClass SerialUSB; + +#endif + diff --git a/examples/abz/abz_client/abz_client.ino b/examples/abz/abz_client/abz_client.ino new file mode 100644 index 0000000..2fda91f --- /dev/null +++ b/examples/abz/abz_client/abz_client.ino @@ -0,0 +1,102 @@ +// abz_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_ABZ class. RH_ABZ class does not provide for addressing or +// reliability, so you should only use RH_ABZ directly if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example abz_server +// Tested with Tested with EcoNode SmartTrap, Arduino 1.8.9, GrumpyOldPizza Arduino Core for STM32L0. + +#include +#include + +// Singleton instance of the radio driver +RH_ABZ abz; + +// Valid for SmartTrap, maybe not other boards + +#define GREEN_LED 13 +#define YELLOW_LED 12 +#define RED_LED 11 + + +void setup() +{ + pinMode(GREEN_LED, OUTPUT); + pinMode(YELLOW_LED, OUTPUT); + pinMode(RED_LED, OUTPUT); + + Serial.begin(9600); + // Wait for serial port to be available + // If you do this, it will block here until a USB serial connection is made. + // If not, it will continue without a Serial connection, but DFU mode will not be available + // to the host without resetting the CPU with the Boot button +// while (!Serial) ; + + // You must be sure that the TCXO settings are appropriate for your board and radio. + // See the RH_ABZ documentation for more information. + // This call is adequate for Tlera boards supported by the Grumpy Old Pizza Arduino Core + // It may or may not be innocuous for others + SX1276SetBoardTcxo(true); + delay(1); + + if (!abz.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + + abz.setFrequency(868.0); + + // You can change the modulation speed etc from the default + //abz.setModemConfig(RH_RF95::Bw125Cr45Sf128); + //abz.setModemConfig(RH_RF95::Bw125Cr45Sf2048); + + // The default transmitter power is 13dBm, using PA_BOOST. + // You can set transmitter powers from 2 to 20 dBm: + //abz.setTxPower(20); // Max power +} + +void loop() +{ + digitalWrite(YELLOW_LED, 1); + digitalWrite(GREEN_LED, 0); + digitalWrite(RED_LED, 0); + + Serial.println("Sending to abz_server"); + // Send a message to abz_server + uint8_t data[] = "Hello World!"; + abz.send(data, sizeof(data)); + abz.waitPacketSent(); + + // Now wait for a reply + uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + +// You might need a longer timeout for slow modulatiuon schemes and/or long messages + if (abz.waitAvailableTimeout(3000)) + { + // Should be a reply message for us now + if (abz.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(abz.lastRssi(), DEC); + digitalWrite(GREEN_LED, 1); + + } + else + { + Serial.println("recv failed"); + digitalWrite(RED_LED, 1); + + } + } + else + { + Serial.println("No reply, is abz_server running?"); + digitalWrite(RED_LED, 1); + } + digitalWrite(YELLOW_LED, 0); + + delay(400); +} diff --git a/examples/abz/abz_server/abz_server.ino b/examples/abz/abz_server/abz_server.ino new file mode 100644 index 0000000..5b5ba38 --- /dev/null +++ b/examples/abz/abz_server/abz_server.ino @@ -0,0 +1,90 @@ +// abz_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_ABZ class. RH_ABZ class does not provide for addressing or +// reliability, so you should only use RH_ABZ directly if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example abz_server +// Tested with Tested with EcoNode SmartTrap, Arduino 1.8.9, GrumpyOldPizza Arduino Core for STM32L0. + +#include +#include + +// Singleton instance of the radio driver +RH_ABZ abz; + +// Valid for SmartTrap, maybe not other boards +#define GREEN_LED 13 +#define YELLOW_LED 12 +#define RED_LED 11 + +void setup() +{ + pinMode(GREEN_LED, OUTPUT); + pinMode(YELLOW_LED, OUTPUT); + pinMode(RED_LED, OUTPUT); + + Serial.begin(9600); + // Wait for serial port to be available + // If you do this, it will block here until a USB serial connection is made. + // If not, it will continue without a Serial connection, but DFU mode will not be available + // to the host without resetting the CPU with the Boot button +// while (!Serial) ; + + // You must be sure that the TCXO settings are appropriate for your board and radio. + // See the RH_ABZ documentation for more information. + // This call is adequate for Tlera boards supported by the Grumpy Old Pizza Arduino Core + // It may or may not be innocuous for others + SX1276SetBoardTcxo(true); + delay(1); + + if (!abz.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + + abz.setFrequency(868.0); + + // You can change the modulation speed etc from the default + //abz.setModemConfig(RH_RF95::Bw125Cr45Sf128); + //abz.setModemConfig(RH_RF95::Bw125Cr45Sf2048); + + // The default transmitter power is 13dBm, using PA_BOOST. + // You can set transmitter powers from 2 to 20 dBm: + //abz.setTxPower(20); // Max power + +} + +void loop() +{ + digitalWrite(YELLOW_LED, 1); + digitalWrite(GREEN_LED, 0); + digitalWrite(RED_LED, 0); + + if (abz.available()) + { + // Should be a message for us now + uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (abz.recv(buf, &len)) + { + digitalWrite(GREEN_LED, 1); + +// RH_ABZ::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(abz.lastRssi(), DEC); + + // Send a reply + uint8_t data[] = "And hello back to you"; + abz.send(data, sizeof(data)); + abz.waitPacketSent(); + Serial.println("Sent a reply"); + digitalWrite(GREEN_LED, 0); + } + else + { + Serial.println("recv failed"); + } + } +} diff --git a/examples/ask/ask_receiver/ask_receiver.ino b/examples/ask/ask_receiver/ask_receiver.ino new file mode 100644 index 0000000..68ccab8 --- /dev/null +++ b/examples/ask/ask_receiver/ask_receiver.ino @@ -0,0 +1,43 @@ +// ask_receiver.pde +// -*- mode: C++ -*- +// Simple example of how to use RadioHead to receive messages +// with a simple ASK transmitter in a very simple way. +// Implements a simplex (one-way) receiver with an Rx-B1 module +// Tested on Arduino Mega, Duemilanova, Uno, Due, Teensy, ESP-12 + +#include +#ifdef RH_HAVE_HARDWARE_SPI +#include // Not actually used but needed to compile +#endif + +RH_ASK driver; +// RH_ASK driver(2000, 4, 5, 0); // ESP8266 or ESP32: do not use pin 11 or 2 +// RH_ASK driver(2000, 3, 4, 0); // ATTiny, RX on D3 (pin 2 on attiny85) TX on D4 (pin 3 on attiny85), +// RH_ASK driver(2000, PD14, PD13, 0); STM32F4 Discovery: see tx and rx on Orange and Red LEDS + +void setup() +{ +#ifdef RH_HAVE_SERIAL + Serial.begin(9600); // Debugging only +#endif + if (!driver.init()) +#ifdef RH_HAVE_SERIAL + Serial.println("init failed"); +#else + ; +#endif +} + +void loop() +{ + uint8_t buf[RH_ASK_MAX_MESSAGE_LEN]; + uint8_t buflen = sizeof(buf); + + if (driver.recv(buf, &buflen)) // Non-blocking + { + int i; + + // Message with a good checksum received, dump it. + driver.printBuffer("Got:", buf, buflen); + } +} diff --git a/examples/ask/ask_reliable_datagram_client/ask_reliable_datagram_client.ino b/examples/ask/ask_reliable_datagram_client/ask_reliable_datagram_client.ino new file mode 100644 index 0000000..9683e90 --- /dev/null +++ b/examples/ask/ask_reliable_datagram_client/ask_reliable_datagram_client.ino @@ -0,0 +1,60 @@ +// ask_reliable_datagram_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging client +// with the RHReliableDatagram class, using the RH_ASK driver to control a ASK radio. +// It is designed to work with the other example ask_reliable_datagram_server +// Tested on Arduino Mega, Duemilanova, Uno, Due, Teensy, ESP-12 + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_ASK driver; +// RH_ASK driver(2000, 4, 5, 0); // ESP8266 or ESP32: do not use pin 11 or 2 +// RH_ASK driver(2000, PD14, PD13, 0); STM32F4 Discovery: see tx and rx on Orange and Red LEDS + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_ASK_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to ask_reliable_datagram_server"); + + // Send a message to manager_server + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is ask_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); + delay(500); +} + diff --git a/examples/ask/ask_reliable_datagram_server/ask_reliable_datagram_server.ino b/examples/ask/ask_reliable_datagram_server/ask_reliable_datagram_server.ino new file mode 100644 index 0000000..c347a9d --- /dev/null +++ b/examples/ask/ask_reliable_datagram_server/ask_reliable_datagram_server.ino @@ -0,0 +1,54 @@ +// ask_reliable_datagram_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_ASK driver to control a ASK radio. +// It is designed to work with the other example ask_reliable_datagram_client +// Tested on Arduino Mega, Duemilanova, Uno, Due, Teensy, ESP-12 + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_ASK driver; +// RH_ASK driver(2000, 4, 5, 0); // ESP8266 or ESP32: do not use pin 11 or 2 +// RH_ASK driver(2000, PD14, PD13, 0); STM32F4 Discovery: see tx and rx on Orange and Red LEDS + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, SERVER_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); +} + +uint8_t data[] = "And hello back to you"; +// Dont put this on the stack: +uint8_t buf[RH_ASK_MAX_MESSAGE_LEN]; + +void loop() +{ + if (manager.available()) + { + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); + } + } +} + diff --git a/examples/ask/ask_transmitter/ask_transmitter.ino b/examples/ask/ask_transmitter/ask_transmitter.ino new file mode 100644 index 0000000..2959d40 --- /dev/null +++ b/examples/ask/ask_transmitter/ask_transmitter.ino @@ -0,0 +1,38 @@ +// ask_transmitter.pde +// -*- mode: C++ -*- +// Simple example of how to use RadioHead to transmit messages +// with a simple ASK transmitter in a very simple way. +// Implements a simplex (one-way) transmitter with an TX-C1 module +// Tested on Arduino Mega, Duemilanova, Uno, Due, Teensy, ESP-12 + +#include +#ifdef RH_HAVE_HARDWARE_SPI +#include // Not actually used but needed to compile +#endif + +RH_ASK driver; +// RH_ASK driver(2000, 4, 5, 0); // ESP8266 or ESP32: do not use pin 11 or 2 +// RH_ASK driver(2000, 3, 4, 0); // ATTiny, RX on D3 (pin 2 on attiny85) TX on D4 (pin 3 on attiny85), +// RH_ASK driver(2000, PD14, PD13, 0); STM32F4 Discovery: see tx and rx on Orange and Red LEDS + +void setup() +{ +#ifdef RH_HAVE_SERIAL + Serial.begin(9600); // Debugging only +#endif + if (!driver.init()) +#ifdef RH_HAVE_SERIAL + Serial.println("init failed"); +#else + ; +#endif +} + +void loop() +{ + const char *msg = "hello"; + + driver.send((uint8_t *)msg, strlen(msg)); + driver.waitPacketSent(); + delay(200); +} diff --git a/examples/cc110/cc110_client/cc110_client.ino b/examples/cc110/cc110_client/cc110_client.ino new file mode 100644 index 0000000..ef73716 --- /dev/null +++ b/examples/cc110/cc110_client/cc110_client.ino @@ -0,0 +1,75 @@ +// cc110_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_CC110 class. RH_CC110 class does not provide for addressing or +// reliability, so you should only use RH_CC110 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example cc110_server +// Tested with Teensy 3.1 and Anaren 430BOOST-CC110L + +#include +#include + +// Singleton instance of the radio driver +RH_CC110 cc110; + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for native USB + + // CC110L may be equipped with either 26 or 27MHz crystals. You MUST + // tell the driver if a 27MHz crystal is installed for the correct configuration to + // occur. Failure to correctly set this flag will cause incorrect frequency and modulation + // characteristics to be used. You can call this function, or pass it to the constructor + cc110.setIs27MHz(true); // Anaren 430BOOST-CC110L Air BoosterPack test boards have 27MHz + if (!cc110.init()) + Serial.println("init failed"); + // After init(), the following default values apply: + // TxPower: TransmitPower5dBm + // Frequency: 915.0 + // Modulation: GFSK_Rb1_2Fd5_2 (GFSK, Data Rate: 1.2kBaud, Dev: 5.2kHz, RX BW 58kHz, optimised for sensitivity) + // Sync Words: 0xd3, 0x91 + // But you can change them: +// cc110.setTxPower(RH_CC110::TransmitPowerM30dBm); +// cc110.setModemConfig(RH_CC110::GFSK_Rb250Fd127); +//cc110.setFrequency(928.0); +} + +void loop() +{ + Serial.println("Sending to cc110_server"); + // Send a message to cc110_server + uint8_t data[] = "Hello World!"; + cc110.send(data, sizeof(data)); + + cc110.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_CC110_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (cc110.waitAvailableTimeout(3000)) + { + // Should be a reply message for us now + if (cc110.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(cc110.lastRssi(), DEC); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is cc110_server running?"); + } + + delay(400); +} + + diff --git a/examples/cc110/cc110_server/cc110_server.ino b/examples/cc110/cc110_server/cc110_server.ino new file mode 100644 index 0000000..5055685 --- /dev/null +++ b/examples/cc110/cc110_server/cc110_server.ino @@ -0,0 +1,69 @@ +// cc110_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_CC110 class. RH_CC110 class does not provide for addressing or +// reliability, so you should only use RH_CC110 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example cc110_client +// Tested with Teensy 3.1 and Anaren 430BOOST-CC110L + + +#include +#include + +// Singleton instance of the radio driver +RH_CC110 cc110; + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for native USB + + // CC110L may be equipped with either 26 or 27MHz crystals. You MUST + // tell the driver if a 27MHz crystal is installed for the correct configuration to + // occur. Failure to correctly set this flag will cause incorrect frequency and modulation + // characteristics to be used. You can call this function, or pass it to the constructor + cc110.setIs27MHz(true); // Anaren 430BOOST-CC110L Air BoosterPack test boards have 27MHz + if (!cc110.init()) + Serial.println("init failed"); + // After init(), the following default values apply: + // TxPower: TransmitPower5dBm + // Frequency: 915.0 + // Modulation: GFSK_Rb1_2Fd5_2 (GFSK, Data Rate: 1.2kBaud, Dev: 5.2kHz, RX BW 58kHz, optimised for sensitivity) + // Sync Words: 0xd3, 0x91 + // But you can change them: +// cc110.setTxPower(RH_CC110::TransmitPowerM30dBm); +// cc110.setModemConfig(RH_CC110::GFSK_Rb250Fd127); +//cc110.setFrequency(928.0); +} + +void loop() +{ + if (cc110.available()) + { + // Should be a message for us now + uint8_t buf[RH_CC110_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (cc110.recv(buf, &len)) + { +// RH_CC110::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(cc110.lastRssi(), DEC); + + // Send a reply + uint8_t data[] = "And hello back to you"; + cc110.send(data, sizeof(data)); + cc110.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} + + diff --git a/examples/e32/e32_client/e32_client.ino b/examples/e32/e32_client/e32_client.ino new file mode 100644 index 0000000..de0722b --- /dev/null +++ b/examples/e32/e32_client/e32_client.ino @@ -0,0 +1,70 @@ +// e32_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_E32 class. RH_E32 class does not provide for addressing or +// reliability, so you should only use RH_E32 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example e32_server +// Tested on Uno with E32-TTL-1W + +#include +#include "SoftwareSerial.h" + +SoftwareSerial mySerial(7, 6); +RH_E32 driver(&mySerial, 4, 5, 8); + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + + // Init the serial connection to the E32 module + // which is assumned to be running at 9600baud. + // If your E32 has been configured to another baud rate, change this: + mySerial.begin(9600); + while (!mySerial) ; + + if (!driver.init()) + Serial.println("init failed"); + // Defaults after initialising are: + // 433MHz, 21dBm, 5kbps + // You can change these as below +// if (!driver.setDataRate(RH_E32::DataRate1kbps)) +// Serial.println("setDataRate failed"); +// if (!driver.setPower(RH_E32::Power30dBm)) +// Serial.println("setPower failed"); +// if (!driver.setFrequency(434)) +// Serial.println("setFrequency failed"); +} + +void loop() +{ + Serial.println("Sending to e32_server"); + // Send a message to e32_server + uint8_t data[] = "Hello World!"; + driver.send(data, sizeof(data)); + + driver.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_E32_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (driver.waitAvailableTimeout(10000)) // At 1kbps, reply can take a long time + { + // Should be a reply message for us now + if (driver.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is e32_server running?"); + } + delay(1000); +} diff --git a/examples/e32/e32_server/e32_server.ino b/examples/e32/e32_server/e32_server.ino new file mode 100644 index 0000000..174143c --- /dev/null +++ b/examples/e32/e32_server/e32_server.ino @@ -0,0 +1,64 @@ +// e32_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_E32 class. RH_E32 class does not provide for addressing or +// reliability, so you should only use RH_E32 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example e32_client +// Tested on Uno with E32-TTL-1W + +#include +#include "SoftwareSerial.h" + +SoftwareSerial mySerial(7, 6); +RH_E32 driver(&mySerial, 4, 5, 8); + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + + // Init the serial connection to the E32 module + // which is assumned to be running at 9600baud. + // If your E32 has been configured to another baud rate, change this: + mySerial.begin(9600); + while (!mySerial) ; + + if (!driver.init()) + Serial.println("init failed"); + // Defaults after initialising are: + // 433MHz, 21dBm, 5kbps + // You can change these as below +// if (!driver.setDataRate(RH_E32::DataRate1kbps)) +// Serial.println("setDataRate failed"); +// if (!driver.setPower(RH_E32::Power30dBm)) +// Serial.println("setPower failed"); +// if (!driver.setFrequency(434)) +// Serial.println("setFrequency failed"); +} + +void loop() +{ + if (driver.available()) + { + // Should be a message for us now + uint8_t buf[RH_E32_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (driver.recv(buf, &len)) + { +// RH_E32::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); + + // Send a reply + uint8_t data[] = "And hello back to you"; + driver.send(data, sizeof(data)); + driver.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} diff --git a/examples/lorafileops/lorafileops_client/lorafileops_client.cpp b/examples/lorafileops/lorafileops_client/lorafileops_client.cpp new file mode 100644 index 0000000..53b5bba --- /dev/null +++ b/examples/lorafileops/lorafileops_client/lorafileops_client.cpp @@ -0,0 +1,81 @@ +// lorafileops_client.cpp +// -*- mode: C++ -*- +// +// Example program demonstrating how to use +// RH_LoRaFileOps driver on RPi+Linux with a radio supported by +// the LoRa-file-ops Linix driver, such as the SX1278. +// See See https://github.com/starnight/LoRa/tree/file-ops +// +// You can build this in the top level RadioHead directory with something like: +// cd RadioHead +// g++ -o lorafileops_client -I . RH_LoRaFileOps.cpp RHGenericDriver.cpp tools/simMain.cpp examples/lorafileops/lorafileops_client/lorafileops_client.cpp +// +// And run with +// sudo ./lorafileops_client +// (root needed to open /dev/loraSPI0.0 +// +// Ensure a RadioHead LoRa compatible receiver is running +// with modem config RH_RF95::Bw125Cr45Sf2048 +// eg examples/rf95/rf95_server/rf95_server.pde +// or +// examples/lorafileops/lorafileops_server/lorafileops_server.cpp + +#include + +// An instance of the RadioHead LoraFileOps driver +RH_LoRaFileOps lora("/dev/loraSPI0.0"); + +void setup() +{ + if (!lora.init()) + Serial.println("init failed"); + // Defaults after init are: + // Centre frequency 434.0 MHz + // 13dBm transmit power + // Bandwidth 125kHz + // Spreading Factor 2045 + // CRC on + + // But you can change them all: + //lora.setFrequency(434000000); + lora.setTxPower(17); + //lora.setSpreadingFactor(1024); + //lora.setLNA(10); + //lora.setBW(250000); + +} + +void loop() +{ + Serial.println("Sending to server"); + uint8_t data[] = "Hello World!"; + lora.send(data, sizeof(data)); + lora.waitPacketSent(); + + // Now wait for a reply + uint8_t buf[RH_LORAFILEOPS_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (lora.waitAvailableTimeout(6000, 100)) + { + // Should be a reply message for us now + if (lora.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); + Serial.print("RSSI: "); + Serial.println(lora.lastRssi(), DEC); + Serial.print("SNR: "); + Serial.println(lora.lastSNR(), DEC); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is server running?"); + } + delay(1000); +} diff --git a/examples/lorafileops/lorafileops_server/lorafileops_server.cpp b/examples/lorafileops/lorafileops_server/lorafileops_server.cpp new file mode 100644 index 0000000..849b4f8 --- /dev/null +++ b/examples/lorafileops/lorafileops_server/lorafileops_server.cpp @@ -0,0 +1,73 @@ +// lorafileops_server.cpp +// -*- mode: C++ -*- +// +// Example program demonstrating how to use +// RH_LoRaFileOps driver on RPi+Linux with a radio supported by +// the LoRa-file-ops Linix driver, such as the SX1278. +// See See https://github.com/starnight/LoRa/tree/file-ops +// +// You can build this in the top level RadioHead directory with something like: +// cd RadioHead +// g++ -o lorafileops_server -I . RH_LoRaFileOps.cpp RHGenericDriver.cpp tools/simMain.cpp examples/lorafileops/lorafileops_server/lorafileops_server.cpp +// +// And run with +// sudo ./lorafileops_server +// (root needed to open /dev/loraSPI0.0 +// +// Ensure a RadioHead LoRa compatible transmitter is running +// with modem config RH_RF95::Bw125Cr45Sf2048 +// eg examples/rf95/rf95_client/rf95_client.pde +// or +// examples/lorafileops/lorafileops_client/lorafileops_client.cpp + +#include + +// An instance of the RadioHead LoraFileOps driver +RH_LoRaFileOps lora("/dev/loraSPI0.0"); + +void setup() +{ + if (!lora.init()) + Serial.println("init failed"); + // Defaults after init are: + // Centre frequency 434.0 MHz + // 13dBm transmit power + // Bandwidth 125kHz + // Spreading Factor 2045 + // CRC on + + // But you can change them all: + //lora.setFrequency(434000000); + lora.setTxPower(17); + //lora.setSpreadingFactor(1024); + //lora.setLNA(10); + //lora.setBW(250000); + +} + +void loop() +{ + lora.waitAvailable(100); + + // Should be a message for us now + uint8_t buf[RH_LORAFILEOPS_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (lora.recv(buf, &len)) + { + RH_LoRaFileOps::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); + Serial.print("RSSI: "); + Serial.println(lora.lastRssi(), DEC); + + // Send a reply + uint8_t data[] = "And hello back to you"; + lora.send(data, sizeof(data)); // Returns when packet fully transmitted + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + +} diff --git a/examples/mrf89/mrf89_client/mrf89_client.ino b/examples/mrf89/mrf89_client/mrf89_client.ino new file mode 100644 index 0000000..c1955e3 --- /dev/null +++ b/examples/mrf89/mrf89_client/mrf89_client.ino @@ -0,0 +1,68 @@ +// mrf89_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_MRF89 class. RH_MRF89 class does not provide for addressing or +// reliability, so you should only use RH_RF95 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example mrf89_server +// Tested with Teensy and MRF89XAM9A + +#include +#include + +// Singleton instance of the radio driver +RH_MRF89 mrf89; + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for native USB + + if (!mrf89.init()) + Serial.println("init failed"); + // Default after init is 1dBm, 915.4MHz, FSK_Rb20Fd40 + // But you can change that if you want: +// mrf89.setTxPower(RH_MRF89_TXOPVAL_M8DBM); // Min power -8dBm +// mrf89.setTxPower(RH_MRF89_TXOPVAL_13DBM); // Max power 13dBm +// if (!mrf89.setFrequency(920.0)) +// Serial.println("setFrequency failed"); +// if (!mrf89.setModemConfig(RH_MRF89::FSK_Rb200Fd200)) // Fastest +// Serial.println("setModemConfig failed"); +} + +void loop() +{ + Serial.println("Sending to mrf89_server"); + // Send a message to mrf89_server + uint8_t data[] = "Hello World!"; + mrf89.send(data, sizeof(data)); + + mrf89.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_MRF89_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (mrf89.waitAvailableTimeout(3000)) + { + // Should be a reply message for us now + if (mrf89.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(mrf89.lastRssi(), DEC); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is mrf89_server running?"); + } + delay(400); +} + + diff --git a/examples/mrf89/mrf89_server/mrf89_server.ino b/examples/mrf89/mrf89_server/mrf89_server.ino new file mode 100644 index 0000000..dd2b6bf --- /dev/null +++ b/examples/mrf89/mrf89_server/mrf89_server.ino @@ -0,0 +1,67 @@ +// mrf89_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_MRF89 class. RH_MRF89 class does not provide for addressing or +// reliability, so you should only use RH_MRF89 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example mrf89_client +// Tested with Teensy and MRF89XAM9A + + +#include +#include + +// Singleton instance of the radio driver +RH_MRF89 mrf89; + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for native USB + + if (!mrf89.init()) + Serial.println("init failed"); + + // Default after init is 1dBm, 915.4MHz, FSK_Rb20Fd40 + // But you can change that if you want: +// mrf89.setTxPower(RH_MRF89_TXOPVAL_M8DBM); // Min power -8dBm +// mrf89.setTxPower(RH_MRF89_TXOPVAL_13DBM); // Max power 13dBm +// if (!mrf89.setFrequency(920.0)) +// Serial.println("setFrequency failed"); +// if (!mrf89.setModemConfig(RH_MRF89::FSK_Rb200Fd200)) // Fastest +// Serial.println("setModemConfig failed"); +} + +void loop() +{ + if (mrf89.available()) + { + // Should be a message for us now + uint8_t buf[RH_MRF89_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (mrf89.recv(buf, &len)) + { +// RH_MRF89::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(mrf89.lastRssi(), DEC); + + // Send a reply + uint8_t data[] = "And hello back to you"; + mrf89.send(data, sizeof(data)); + mrf89.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +// delay(10000); +// mrf89.printRegisters(); +// while (1); +} + + diff --git a/examples/nrf24/nrf24_client/nrf24_client.ino b/examples/nrf24/nrf24_client/nrf24_client.ino new file mode 100644 index 0000000..d9a09c5 --- /dev/null +++ b/examples/nrf24/nrf24_client/nrf24_client.ino @@ -0,0 +1,67 @@ +// nrf24_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_NRF24 class. RH_NRF24 class does not provide for addressing or +// reliability, so you should only use RH_NRF24 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example nrf24_server. +// Tested on Uno with Sparkfun NRF25L01 module +// Tested on Anarduino Mini (http://www.anarduino.com/mini/) with RFM73 module +// Tested on Arduino Mega with Sparkfun WRL-00691 NRF25L01 module + +#include +#include + +// Singleton instance of the radio driver +RH_NRF24 nrf24; +// RH_NRF24 nrf24(8, 7); // use this to be electrically compatible with Mirf +// RH_NRF24 nrf24(8, 10);// For Leonardo, need explicit SS pin +// RH_NRF24 nrf24(8, 7); // For RFM73 on Anarduino Mini + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for Leonardo only + if (!nrf24.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm + if (!nrf24.setChannel(1)) + Serial.println("setChannel failed"); + if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm)) + Serial.println("setRF failed"); +} + + +void loop() +{ + Serial.println("Sending to nrf24_server"); + // Send a message to nrf24_server + uint8_t data[] = "Hello World!"; + nrf24.send(data, sizeof(data)); + + nrf24.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (nrf24.waitAvailableTimeout(500)) + { + // Should be a reply message for us now + if (nrf24.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is nrf24_server running?"); + } + delay(400); +} + diff --git a/examples/nrf24/nrf24_encrypted_client/nrf24_encrypted_client.ino b/examples/nrf24/nrf24_encrypted_client/nrf24_encrypted_client.ino new file mode 100644 index 0000000..c98f21b --- /dev/null +++ b/examples/nrf24/nrf24_encrypted_client/nrf24_encrypted_client.ino @@ -0,0 +1,78 @@ +// nrf24_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple encrypted messageing client +// with the RH_NRF24 class. RH_NRF24 class does not provide for addressing or +// reliability, so you should only use RH_NRF24 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example nrf24_encrypted_server. +// Tested on Duemilanove with Sparkfun NRF25L01 module + +#include +#include +#include +#include + +// Singleton instance of the radio driver +RH_NRF24 nrf24; +// RH_NRF24 nrf24(8, 7); // use this to be electrically compatible with Mirf +// RH_NRF24 nrf24(8, 10);// For Leonardo, need explicit SS pin +// RH_NRF24 nrf24(8, 7); // For RFM73 on Anarduino Mini + +// You can choose any of several encryption ciphers +Speck myCipher; // Instantiate a Speck block ciphering +// The RHEncryptedDriver acts as a wrapper for the actual radio driver +RHEncryptedDriver driver(nrf24, myCipher); +// The key MUST be the same as the one in the server +unsigned char encryptkey[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for Leonardo only + if (!nrf24.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm + if (!nrf24.setChannel(1)) + Serial.println("setChannel failed"); + if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm)) + Serial.println("setRF failed"); + + // Now set up the encryption key in our cipher + myCipher.setKey(encryptkey, sizeof(encryptkey)); + +} + + +void loop() +{ + Serial.println("Sending to nrf24_encrypted_server"); + // Send a message to nrf24_server + uint8_t data[] = "Hello World!"; // Dont make this too long + driver.send(data, sizeof(data)); + + driver.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (driver.waitAvailableTimeout(500)) + { + // Should be a reply message for us now + if (driver.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is nrf24_encrypted_server running?"); + } + delay(400); +} + diff --git a/examples/nrf24/nrf24_encrypted_server/nrf24_encrypted_server.ino b/examples/nrf24/nrf24_encrypted_server/nrf24_encrypted_server.ino new file mode 100644 index 0000000..632119e --- /dev/null +++ b/examples/nrf24/nrf24_encrypted_server/nrf24_encrypted_server.ino @@ -0,0 +1,69 @@ +// nrf24_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple encrypted messageing server +// with the RH_NRF24 class. RH_NRF24 class does not provide for addressing or +// reliability, so you should only use RH_NRF24 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example nrf24_encrypted_client + +#include +#include +#include +#include + +// Singleton instance of the radio driver +RH_NRF24 nrf24; +// RH_NRF24 nrf24(8, 7); // use this to be electrically compatible with Mirf +// RH_NRF24 nrf24(8, 10);// For Leonardo, need explicit SS pin +// RH_NRF24 nrf24(8, 7); // For RFM73 on Anarduino Mini + +// You can choose any of several encryption ciphers +Speck myCipher; // Instantiate a Speck block ciphering +// The RHEncryptedDriver acts as a wrapper for the actual radio driver +RHEncryptedDriver driver(nrf24, myCipher); // Instantiate the driver with those two +// The key MUST be the same as the one in the client +unsigned char encryptkey[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for Leonardo only + if (!nrf24.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm + if (!nrf24.setChannel(1)) + Serial.println("setChannel failed"); + if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm)) + Serial.println("setRF failed"); + + // Now set up the encryption key in our cipher + myCipher.setKey(encryptkey, sizeof(encryptkey)); +} + +void loop() +{ + if (driver.available()) + { + // Should be a message for us now + uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (driver.recv(buf, &len)) + { +// RH_NRF24::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); + + // Send a reply + uint8_t data[] = "And hello back"; // Dont make this too long + driver.send(data, sizeof(data)); + driver.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} + diff --git a/examples/nrf24/nrf24_reliable_datagram_client/nrf24_reliable_datagram_client.ino b/examples/nrf24/nrf24_reliable_datagram_client/nrf24_reliable_datagram_client.ino new file mode 100644 index 0000000..0a9b2de --- /dev/null +++ b/examples/nrf24/nrf24_reliable_datagram_client/nrf24_reliable_datagram_client.ino @@ -0,0 +1,63 @@ +// nrf24_reliable_datagram_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging client +// with the RHReliableDatagram class, using the RH_NRF24 driver to control a NRF24 radio. +// It is designed to work with the other example nrf24_reliable_datagram_server +// Tested on Uno with Sparkfun WRL-00691 NRF24L01 module +// Tested on Teensy with Sparkfun WRL-00691 NRF24L01 module +// Tested on Anarduino Mini (http://www.anarduino.com/mini/) with RFM73 module +// Tested on Arduino Mega with Sparkfun WRL-00691 NRF25L01 module + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_NRF24 driver; +// RH_NRF24 driver(8, 7); // For RFM73 on Anarduino Mini + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to nrf24_reliable_datagram_server"); + + // Send a message to manager_server + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is nrf24_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); + delay(500); +} + diff --git a/examples/nrf24/nrf24_reliable_datagram_server/nrf24_reliable_datagram_server.ino b/examples/nrf24/nrf24_reliable_datagram_server/nrf24_reliable_datagram_server.ino new file mode 100644 index 0000000..43560b0 --- /dev/null +++ b/examples/nrf24/nrf24_reliable_datagram_server/nrf24_reliable_datagram_server.ino @@ -0,0 +1,57 @@ +// nrf24_reliable_datagram_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_NRF24 driver to control a NRF24 radio. +// It is designed to work with the other example nrf24_reliable_datagram_client +// Tested on Uno with Sparkfun WRL-00691 NRF24L01 module +// Tested on Teensy with Sparkfun WRL-00691 NRF24L01 module +// Tested on Anarduino Mini (http://www.anarduino.com/mini/) with RFM73 module +// Tested on Arduino Mega with Sparkfun WRL-00691 NRF25L01 module + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_NRF24 driver; +// RH_NRF24 driver(8, 7); // For RFM73 on Anarduino Mini + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, SERVER_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm +} + +uint8_t data[] = "And hello back to you"; +// Dont put this on the stack: +uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN]; + +void loop() +{ + if (manager.available()) + { + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); + } + } +} + diff --git a/examples/nrf24/nrf24_server/nrf24_server.ino b/examples/nrf24/nrf24_server/nrf24_server.ino new file mode 100644 index 0000000..c8dbc8c --- /dev/null +++ b/examples/nrf24/nrf24_server/nrf24_server.ino @@ -0,0 +1,60 @@ +// nrf24_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_NRF24 class. RH_NRF24 class does not provide for addressing or +// reliability, so you should only use RH_NRF24 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example nrf24_client +// Tested on Uno with Sparkfun NRF25L01 module +// Tested on Anarduino Mini (http://www.anarduino.com/mini/) with RFM73 module +// Tested on Arduino Mega with Sparkfun WRL-00691 NRF25L01 module + +#include +#include + +// Singleton instance of the radio driver +RH_NRF24 nrf24; +// RH_NRF24 nrf24(8, 7); // use this to be electrically compatible with Mirf +// RH_NRF24 nrf24(8, 10);// For Leonardo, need explicit SS pin +// RH_NRF24 nrf24(8, 7); // For RFM73 on Anarduino Mini + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for Leonardo only + if (!nrf24.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm + if (!nrf24.setChannel(1)) + Serial.println("setChannel failed"); + if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm)) + Serial.println("setRF failed"); +} + +void loop() +{ + if (nrf24.available()) + { + // Should be a message for us now + uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (nrf24.recv(buf, &len)) + { +// NRF24::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); + + // Send a reply + uint8_t data[] = "And hello back to you"; + nrf24.send(data, sizeof(data)); + nrf24.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} + diff --git a/examples/nrf51/nrf51_audio_rx/nrf51_audio_rx.ino b/examples/nrf51/nrf51_audio_rx/nrf51_audio_rx.ino new file mode 100644 index 0000000..965fd89 --- /dev/null +++ b/examples/nrf51/nrf51_audio_rx/nrf51_audio_rx.ino @@ -0,0 +1,113 @@ +// nrf51_audio_rx.pde +// Sample sketch for nRF51822 and RadioHead +// +// Plays audio samples received in the radio receiver +// through a MCP4725 DAC such as on a SparkFun I2C DAC Breakout - MCP4725 (BOB-12918) +// works with matching transmitter (see nrf51_audio_tx.pde) +// Works with RedBear nRF51822 board. +// See examples/nrf51_audiotx/nrf51_audio.pdf for connection details + +#include +#include +#include +#include +#include + +// Number of samples per second to play at. +// Should match SAMPLE_RATE in nrf51_audio_tx +// The limiting factor is the time it takes to output a new sample through the DAC +#define SAMPLE_RATE 5000 + +// Number of 8 bit samples per packet +// Should equal or exceed the PACKET_SIZE in nrf51_audio_tx +#define MAX_PACKET_SIZE 255 + +// Singleton instance of the radio driver +RH_NRF51 driver; + +void setup() +{ + delay(1000); + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. + + if (!driver.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm + + // Set up TIMER + // Use TIMER0 + // Timer freq before prescaling is 16MHz (VARIANT_MCK) + // We set up a 32 bit timer that restarts every 100us and outputs a new sample + NRF_TIMER0->PRESCALER = 0 << TIMER_PRESCALER_PRESCALER_Pos; + NRF_TIMER0->MODE = TIMER_MODE_MODE_Timer << TIMER_BITMODE_BITMODE_Pos; + NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos; + NRF_TIMER0->CC[0] = VARIANT_MCK / SAMPLE_RATE; // Counts per cycle + // When timer count expires, its cleared and restarts + NRF_TIMER0->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Msk; + NRF_TIMER0->TASKS_START = 1; + // Enable an interrupt when timer completes + NRF_TIMER0->INTENSET = TIMER_INTENSET_COMPARE0_Msk; + + // Enable the TIMER0 interrupt, and set the priority + // TIMER0_IRQHandler() will be called after each sample is available + NVIC_SetPriority(TIMER0_IRQn, 1); + NVIC_EnableIRQ(TIMER0_IRQn); + + // Initialise comms with the I2C DAC as fast as we can + // Shame the 51822 does not suport the high speed I2C mode that the DAC does + Wire.begin(TWI_SCL, TWI_SDA, TWI_FREQUENCY_400K); +} + +volatile uint32_t count = 0; + +uint8_t buffer_length = 0; +uint8_t buffer[MAX_PACKET_SIZE]; +uint16_t buffer_play_index = 0; + +// Write this sample to analog out +void analog_out(uint8_t val) +{ + // This takes about 120usecs, which + // is the limiting factor for our sample rate of 5kHz + // Writes to MCP4725 DAC over I2C using the Wire library + Wire.beginTransmission(0x60); // 7 bit addressing + Wire.write((val >> 4) & 0x0f); + Wire.write((val << 4) & 0xf0); + Wire.endTransmission(); +} + +// Called by timer interrupt +// Output the next available sample +void output_next_sample() +{ + if (buffer_play_index < buffer_length) + { + analog_out(buffer[buffer_play_index++]); + } +} + +void loop() +{ + // Look for a new packet of samples + if (driver.available()) + { + // expect one of these every 40ms = 25Hz + // This takes about 400us: + buffer_length = sizeof(buffer); + driver.recv(buffer, &buffer_length); + buffer_play_index = 0; // Trigger the interrupt playing of this buffer from the start + } +} + +// This interrupt handler called when the timer interrupt fires +// Time to output the next sample +void TIMER0_IRQHandler(void) +{ + // It is vitally important that analog output completes before + // the next interrupt becomes due! + output_next_sample(); + NRF_TIMER0->EVENTS_COMPARE[0] = 0; // Clear the COMPARE[0] event and the interrupt +} + diff --git a/examples/nrf51/nrf51_audio_tx/nrf51_audio.pdf b/examples/nrf51/nrf51_audio_tx/nrf51_audio.pdf new file mode 100644 index 0000000..f223703 Binary files /dev/null and b/examples/nrf51/nrf51_audio_tx/nrf51_audio.pdf differ diff --git a/examples/nrf51/nrf51_audio_tx/nrf51_audio_tx.ino b/examples/nrf51/nrf51_audio_tx/nrf51_audio_tx.ino new file mode 100644 index 0000000..c94519c --- /dev/null +++ b/examples/nrf51/nrf51_audio_tx/nrf51_audio_tx.ino @@ -0,0 +1,143 @@ +// nrf51_audio_tx.pde +// Sample sketch for nRF51822 and RadioHead +// +// Reads audio samples from an electret microphone +// via the built in ADC in the nRF51822 +// Blocks of samples are sent by RadioHEad RH_NRF51 driver +// to a matching receiver (see nrf51_audio_rx.pde) +// Works with RedBear nRF51822 board. +// See examples/nrf51_audiotx/nrf51_audio.pdf for connection details + +#include +#include +#include +#include + +// Number of audio samples per second +// Should match SAMPLE_RATE in nrf51_audio_rx +// Limited by the rate we can output samples in the receiver +#define SAMPLE_RATE 5000 + +// Number of 8 bit samples per packet +#define PACKET_SIZE 200 + +// Number of ADC data buffers +#define NUM_BUFFERS 2 + +// Minimum diff between smallest and largest reading in a given buffer +// before we will send that buffer. We dont transmit quiet signals or silence +#define USE_SQUELCH 0 +#define SQUELCH_THRESHOLD 2 + +// These provide data transfer between the low level ADC interrupt handler and the +// higher level packet assembly and transmission +volatile uint8_t buffers[NUM_BUFFERS][PACKET_SIZE]; +volatile uint16_t sample_index = 0; // Of the next sample to write +volatile uint8_t buffer_index = 0; // Of the bufferbeing filled +volatile bool buffer_ready[NUM_BUFFERS]; // Set when a buffer is full + +// These hold the state of the high level transmitter code +uint8_t next_tx_buffer = 0; + +// Singleton instance of the radio driver +RH_NRF51 driver; + +void setup() +{ + delay(1000); + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. + + if (!driver.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm + + // Set up ADC + // Uses the builtin 1.2V bandgap reference and no prescaling + // AnalogInput2 is A0 on RedBear nrf51822 board + // Input voltage range is 0.0 to 1.2 V + NRF_ADC->CONFIG = ADC_CONFIG_RES_8bit << ADC_CONFIG_RES_Pos + | ADC_CONFIG_INPSEL_AnalogInputNoPrescaling << ADC_CONFIG_INPSEL_Pos + | ADC_CONFIG_REFSEL_VBG << ADC_CONFIG_REFSEL_Pos + | ADC_CONFIG_PSEL_AnalogInput2 << ADC_CONFIG_PSEL_Pos; + NRF_ADC->ENABLE = 1; + NRF_ADC->INTENSET = ADC_INTENSET_END_Msk; // Interrupt at completion of each sample + + // Set up TIMER to trigger ADC samples + // Use TIMER0 + // Timer freq before prescaling is 16MHz (VARIANT_MCK) + // We set up a 32 bit timer that restarts every 100us and trggers a new ADC sample + NRF_TIMER0->PRESCALER = 0 << TIMER_PRESCALER_PRESCALER_Pos; + NRF_TIMER0->MODE = TIMER_MODE_MODE_Timer << TIMER_BITMODE_BITMODE_Pos; + NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos; + NRF_TIMER0->CC[0] = VARIANT_MCK / SAMPLE_RATE; // Counts per cycle + // When timer count expires, its cleared and restarts + NRF_TIMER0->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Msk; + NRF_TIMER0->TASKS_START = 1; + + // When the timer expires, trigger an ADC conversion + NRF_PPI->CH[0].EEP = (uint32_t)(&NRF_TIMER0->EVENTS_COMPARE[0]); + NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_ADC->TASKS_START); + NRF_PPI->CHENSET = PPI_CHEN_CH0_Msk; + + // Enable the ADC interrupt, and set the priority + // ADC_IRQHandler() will be called after each sample is available + NVIC_SetPriority(ADC_IRQn, 1); + NVIC_EnableIRQ(ADC_IRQn); +} + +// Called when a new sample is available from the ADC. +// Add it to the current buffer. +// when the buffer is full, signal that and switch to the other buffer. +void handle_sample() +{ + buffers[buffer_index][sample_index++] = NRF_ADC->RESULT; + if (sample_index >= PACKET_SIZE) + { + sample_index = 0; + buffer_ready[buffer_index] = true; + buffer_index = (buffer_index + 1) % NUM_BUFFERS; + // If the next buffer is still still full, we have an overrun + if (buffer_ready[buffer_index]) + Serial.println("Overrun"); + } +} + +void loop() { + // Wait for the adc to fill the current buffer + if (buffer_ready[next_tx_buffer]) + { +#if USE_SQUELCH + // Honour squelch settings + uint8_t min_value = 255; + uint8_t max_value = 0; + uint16_t i; + for (i = 0; i < PACKET_SIZE; i++) + { + if (buffers[next_tx_buffer][i] > max_value) + max_value = buffers[next_tx_buffer][i]; + if (buffers[next_tx_buffer][i] < min_value) + min_value = buffers[next_tx_buffer][i]; + } + if (max_value - min_value > SQUELCH_THRESHOLD) +#endif + { + // OK to send this one + driver.waitPacketSent(); // Make sure the previous packet has gone + driver.send((uint8_t*)buffers[next_tx_buffer], PACKET_SIZE); + } + + // Now get ready to wait for the next buffer + buffer_ready[next_tx_buffer] = false; + next_tx_buffer = (next_tx_buffer + 1) % NUM_BUFFERS; + } +} + +// Called as an interrupt after each new ADC sample is complete. +void ADC_IRQHandler(void) +{ + NRF_ADC->EVENTS_END = 0; // Clear the end event + handle_sample(); +} + diff --git a/examples/nrf51/nrf51_client/nrf51_client.ino b/examples/nrf51/nrf51_client/nrf51_client.ino new file mode 100644 index 0000000..03e857b --- /dev/null +++ b/examples/nrf51/nrf51_client/nrf51_client.ino @@ -0,0 +1,73 @@ +// nrf51_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_NRF51 class. RH_NRF51 class does not provide for addressing or +// reliability, so you should only use RH_NRF51 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example nrf51_server. +// Tested on RedBearLabs nRF51822 and BLE Nano kit, built with Arduino 1.6.4. +// See http://redbearlab.com/getting-started-nrf51822/ +// for how to set up your Arduino build environment +// Also tested with Sparkfun nRF52832 breakout board, witth Arduino 1.6.13 and +// Sparkfun nRF52 boards manager 0.2.3 +#include + +// Singleton instance of the radio driver +RH_NRF51 nrf51; + +void setup() +{ + delay(1000); // Wait for serial port etc to be ready + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. + if (!nrf51.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm + if (!nrf51.setChannel(1)) + Serial.println("setChannel failed"); + if (!nrf51.setRF(RH_NRF51::DataRate2Mbps, RH_NRF51::TransmitPower0dBm)) + Serial.println("setRF failed"); + + // AES encryption can be enabled by setting the same key in the sender and receiver +// uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, +// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; +// nrf51.setEncryptionKey(key); + +// nrf51.printRegisters(); +} + + +void loop() +{ + Serial.println("Sending to nrf51_server"); + // Send a message to nrf51_server + uint8_t data[] = "Hello World!"; + nrf51.send(data, sizeof(data)); + nrf51.waitPacketSent(); + + // Now wait for a reply + uint8_t buf[RH_NRF51_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (nrf51.waitAvailableTimeout(500)) + { + // Should be a reply message for us now + if (nrf51.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is nrf51_server running?"); + } + + delay(400); +} + diff --git a/examples/nrf51/nrf51_reliable_datagram_client/nrf51_reliable_datagram_client.ino b/examples/nrf51/nrf51_reliable_datagram_client/nrf51_reliable_datagram_client.ino new file mode 100644 index 0000000..2f9c3ca --- /dev/null +++ b/examples/nrf51/nrf51_reliable_datagram_client/nrf51_reliable_datagram_client.ino @@ -0,0 +1,66 @@ +// nrf51_reliable_datagram_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging client +// with the RHReliableDatagram class, using the RH_NRF51 driver to control a NRF51 radio. +// It is designed to work with the other example nrf51_reliable_datagram_server +// Tested on RedBearLabs nRF51822 and BLE Nano kit, built with Arduino 1.6.4. +// See http://redbearlab.com/getting-started-nrf51822/ +// for how to set up your Arduino build environment +// Also tested with Sparkfun nRF52832 breakout board, witth Arduino 1.6.13 and +// Sparkfun nRF52 boards manager 0.2.3 + +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_NRF51 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, CLIENT_ADDRESS); + +void setup() +{ + delay(1000); // Wait for serial port etc to be ready + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. + + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_NRF51_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to nrf51_reliable_datagram_server"); + + // Send a message to manager_server + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is nrf51_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); + delay(500); +} + diff --git a/examples/nrf51/nrf51_reliable_datagram_server/nrf51_reliable_datagram_server.ino b/examples/nrf51/nrf51_reliable_datagram_server/nrf51_reliable_datagram_server.ino new file mode 100644 index 0000000..9e105b4 --- /dev/null +++ b/examples/nrf51/nrf51_reliable_datagram_server/nrf51_reliable_datagram_server.ino @@ -0,0 +1,61 @@ +// nrf51_reliable_datagram_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_NRF51 driver to control a NRF51 radio. +// It is designed to work with the other example nrf51_reliable_datagram_client +// Tested on RedBearLabs nRF51822 and BLE Nano kit, built with Arduino 1.6.4. +// See http://redbearlab.com/getting-started-nrf51822/ +// for how to set up your Arduino build environment +// Also tested with Sparkfun nRF52832 breakout board, witth Arduino 1.6.13 and +// Sparkfun nRF52 boards manager 0.2.3 + + +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_NRF51 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, SERVER_ADDRESS); + +void setup() +{ + delay(1000); // Wait for serial port etc to be ready + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. + + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm +} + +uint8_t data[] = "And hello back to you"; +// Dont put this on the stack: +uint8_t buf[RH_NRF51_MAX_MESSAGE_LEN]; + +void loop() +{ + if (manager.available()) + { + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); + } + } +} + diff --git a/examples/nrf51/nrf51_server/nrf51_server.ino b/examples/nrf51/nrf51_server/nrf51_server.ino new file mode 100644 index 0000000..f9b1f45 --- /dev/null +++ b/examples/nrf51/nrf51_server/nrf51_server.ino @@ -0,0 +1,64 @@ +// nrf51_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_NRF51 class. RH_NRF51 class does not provide for addressing or +// reliability, so you should only use RH_NRF51 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example nrf51_client +// Tested on RedBearLabs nRF51822 and BLE Nano kit, built with Arduino 1.6.4. +// See http://redbearlab.com/getting-started-nrf51822/ +// for how to set up your Arduino build environment +// Also tested with Sparkfun nRF52832 breakout board, witth Arduino 1.6.13 and +// Sparkfun nRF52 boards manager 0.2.3 + +#include + +// Singleton instance of the radio driver +RH_NRF51 nrf51; + +void setup() +{ + delay(1000); // Wait for serial port etc to be ready + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for Leonardo only + if (!nrf51.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm + if (!nrf51.setChannel(1)) + Serial.println("setChannel failed"); + if (!nrf51.setRF(RH_NRF51::DataRate2Mbps, RH_NRF51::TransmitPower0dBm)) + Serial.println("setRF failed"); + + // AES encryption can be enabled by setting the same key in the sender and receiver +// uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, +// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; +// nrf51.setEncryptionKey(key); +} + +void loop() +{ + if (nrf51.available()) + { + // Should be a message for us now + uint8_t buf[RH_NRF51_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (nrf51.recv(buf, &len)) + { +// NRF51::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); + + // Send a reply + uint8_t data[] = "And hello back to you"; + nrf51.send(data, sizeof(data)); + nrf51.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} + diff --git a/examples/nrf905/nrf905_client/nrf905_client.ino b/examples/nrf905/nrf905_client/nrf905_client.ino new file mode 100644 index 0000000..6dfcee3 --- /dev/null +++ b/examples/nrf905/nrf905_client/nrf905_client.ino @@ -0,0 +1,59 @@ +// nrf905_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_NRF905 class. RH_NRF905 class does not provide for addressing or +// reliability, so you should only use RH_NRF905 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example nrf905_server. +// Tested on Teensy3.1 with nRF905 module +// Tested on Arduino Due with nRF905 module (Caution: use the SPI headers for connecting) + +#include +#include + +// Singleton instance of the radio driver +RH_NRF905 nrf905; + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for Leonardo only + if (!nrf905.init()) + Serial.println("init failed"); + // Defaults after init are 433.2 MHz (channel 108), -10dBm +} + + +void loop() +{ + Serial.println("Sending to nrf905_server"); + // Send a message to nrf905_server + uint8_t data[] = "Hello World!"; + nrf905.send(data, sizeof(data)); + + nrf905.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_NRF905_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (nrf905.waitAvailableTimeout(500)) + { + // Should be a reply message for us now + if (nrf905.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is nrf905_server running?"); + } + delay(400); +} + diff --git a/examples/nrf905/nrf905_reliable_datagram_client/nrf905_reliable_datagram_client.ino b/examples/nrf905/nrf905_reliable_datagram_client/nrf905_reliable_datagram_client.ino new file mode 100644 index 0000000..b76f13b --- /dev/null +++ b/examples/nrf905/nrf905_reliable_datagram_client/nrf905_reliable_datagram_client.ino @@ -0,0 +1,60 @@ +// nrf905_reliable_datagram_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging client +// with the RHReliableDatagram class, using the RH_NRF905 driver to control a NRF905 radio. +// It is designed to work with the other example nrf905_reliable_datagram_server +// Tested on Teensy3.1 with nRF905 module +// Tested on Arduino Due with nRF905 module (Caution: use the SPI headers for connecting) + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_NRF905 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 433.2 MHz (channel 108), -10dBm +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_NRF905_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to nrf905_reliable_datagram_server"); + + // Send a message to manager_server + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is nrf905_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); + delay(500); +} + diff --git a/examples/nrf905/nrf905_reliable_datagram_server/nrf905_reliable_datagram_server.ino b/examples/nrf905/nrf905_reliable_datagram_server/nrf905_reliable_datagram_server.ino new file mode 100644 index 0000000..b76f9ee --- /dev/null +++ b/examples/nrf905/nrf905_reliable_datagram_server/nrf905_reliable_datagram_server.ino @@ -0,0 +1,54 @@ +// nrf905_reliable_datagram_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_NRF905 driver to control a NRF905 radio. +// It is designed to work with the other example nrf905_reliable_datagram_client +// Tested on Teensy3.1 with nRF905 module +// Tested on Arduino Due with nRF905 module (Caution: use the SPI headers for connecting) + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_NRF905 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, SERVER_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 433.2 MHz (channel 108), -10dBm +} + +uint8_t data[] = "And hello back to you"; +// Dont put this on the stack: +uint8_t buf[RH_NRF905_MAX_MESSAGE_LEN]; + +void loop() +{ + if (manager.available()) + { + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); + } + } +} + diff --git a/examples/nrf905/nrf905_server/nrf905_server.ino b/examples/nrf905/nrf905_server/nrf905_server.ino new file mode 100644 index 0000000..507e7f6 --- /dev/null +++ b/examples/nrf905/nrf905_server/nrf905_server.ino @@ -0,0 +1,52 @@ +// nrf905_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_NRF905 class. RH_NRF905 class does not provide for addressing or +// reliability, so you should only use RH_NRF905 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example nrf905_client +// Tested on Teensy3.1 with nRF905 module +// Tested on Arduino Due with nRF905 module (Caution: use the SPI headers for connecting) + +#include +#include + +// Singleton instance of the radio driver +RH_NRF905 nrf905; + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; // wait for serial port to connect. Needed for Leonardo only + if (!nrf905.init()) + Serial.println("init failed"); + // Defaults after init are 433.2 MHz (channel 108), -10dBm +} + +void loop() +{ + if (nrf905.available()) + { + // Should be a message for us now + uint8_t buf[RH_NRF905_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (nrf905.recv(buf, &len)) + { +// nrf905.printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); + + // Send a reply + uint8_t data[] = "And hello back to you"; + nrf905.send(data, sizeof(data)); + nrf905.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} + diff --git a/examples/raspi/Makefile b/examples/raspi/Makefile new file mode 100644 index 0000000..bb24a0a --- /dev/null +++ b/examples/raspi/Makefile @@ -0,0 +1,53 @@ +# Makefile +# Sample for RH_NRF24 on Raspberry Pi +# Caution: requires bcm2835 library to be already installed +# http://www.airspayce.com/mikem/bcm2835/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -DBCM2835_NO_DELAY_COMPATIBILITY +LIBS = -lbcm2835 +RADIOHEADBASE = ../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: RasPiRH + +RasPi.o: $(RADIOHEADBASE)/RHutil/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil/RasPi.cpp $(INCLUDE) + +RasPiRH.o: RasPiRH.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_NRF24.o: $(RADIOHEADBASE)/RH_NRF24.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHMesh.o: $(RADIOHEADBASE)/RHMesh.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHRouter.o: $(RADIOHEADBASE)/RHRouter.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHNRFSPIDriver.o: $(RADIOHEADBASE)/RHNRFSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RasPiRH: RasPiRH.o RH_NRF24.o RHMesh.o RHRouter.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHNRFSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o RasPiRH + + +clean: + rm -rf *.o RasPiRH + diff --git a/examples/raspi/RasPiRH.cpp b/examples/raspi/RasPiRH.cpp new file mode 100644 index 0000000..1864620 --- /dev/null +++ b/examples/raspi/RasPiRH.cpp @@ -0,0 +1,138 @@ +// RasPiRH.cpp +// +// Example program showing how to use RH_NRF24 on Raspberry Pi +// Uses the bcm2835 library to access the GPIO pins to drive the NRF24L01 +// Requires bcm2835 library to be already installed +// http://www.airspayce.com/mikem/bcm2835/ +// Use the Makefile in this directory: +// cd example/raspi +// make +// sudo ./RasPiRH +// +// Creates a RHReliableDatagram manager and listens and prints for reliable datagrams +// sent to it on the default Channel 2. +// +// Contributed by Mike Poublon + +#include +#include +#include +#include + +#include +#include + +//Function Definitions +void sig_handler(int sig); +void printbuffer(uint8_t buff[], int len); + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Create an instance of a driver +// Chip enable is pin 22 +// Slave Select is pin 24 +RH_NRF24 nrf24(RPI_V2_GPIO_P1_22, RPI_V2_GPIO_P1_24); +RHReliableDatagram manager(nrf24, SERVER_ADDRESS); + +//Flag for Ctrl-C +volatile sig_atomic_t flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + signal(SIGINT, sig_handler); + + if (!bcm2835_init()) + { + printf( "\n\nRasPiRH Tester Startup Failed\n\n" ); + return 1; + } + + printf( "\nRasPiRH Tester Startup\n\n" ); + + /* Begin Driver Only Init Code + if (!nrf24.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm + if (!nrf24.setChannel(1)) + Serial.println("setChannel failed"); + if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm)) + Serial.println("setRF failed"); + End Driver Only Init Code */ + + /* Begin Reliable Datagram Init Code */ + if (!manager.init()) + { + printf( "Init failed\n" ); + } + /* End Reliable Datagram Init Code */ + + uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN]; + + //Begin the main body of code + while (true) + { + uint8_t len = sizeof(buf); + uint8_t from, to, id, flags; + + /* Begin Driver Only code + if (nrf24.available()) + { + // Should be a message for us now + //uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (nrf24.recv(buf, &len)) + { + Serial.print("got request: "); + Serial.println((char*)buf); + Serial.println(""); + } + else + { + Serial.println("recv failed"); + } + } + End Driver Only Code*/ + + /* Begin Reliable Datagram Code */ + if (manager.available()) + { + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + } + /* End Reliable Datagram Code */ + + if (flag) + { + printf("\n---CTRL-C Caught - Exiting---\n"); + break; + } + //sleep(1); + delay(25); + } + printf( "\nRasPiRH Tester Ending\n" ); + bcm2835_close(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} + +void printbuffer(uint8_t buff[], int len) +{ + for (int i = 0; i< len; i++) + { + printf(" %2X", buff[i]); + } +} diff --git a/examples/raspi/rf95/rf95_client/Makefile b/examples/raspi/rf95/rf95_client/Makefile new file mode 100644 index 0000000..5080da4 --- /dev/null +++ b/examples/raspi/rf95/rf95_client/Makefile @@ -0,0 +1,84 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + + +CROSSCOMPILER=$(shell if [ -z `which 'arm-linux-gnueabihf-g++'` ]; then echo "no"; else echo "yes"; fi) +ifeq ($(CROSSCOMPILER),yes) +CC = arm-linux-gnueabihf-g++ +CFLAGS = -DRASPBERRY_PI -DRH_USE_MUTEX -pthread +RADIOHEADBASE = ../../../.. +PIGPIOBASE = ../../../../../pigpio/ +SHARED = ../shared/ +INCLUDE = -I$(RADIOHEADBASE) -I$(PIGPIOBASE) -I$(SHARED) +LIBS = -lpigpio -lrt -L$(PIGPIOBASE) -lpthread -D_REENTRANT +else +CC = g++ +CFLAGS = -DRASPBERRY_PI -DRH_USE_MUTEX -pthread +RADIOHEADBASE = ../../../.. +PIGPIOBASE = ../../../../../pigpio/ +SHARED = ../shared/ +INCLUDE = -I$(RADIOHEADBASE) -I$(PIGPIOBASE) -I$(SHARED) +LIBS = -lpigpio -lrt -L$(PIGPIOBASE) -lpthread -D_REENTRANT +endif + +ifeq ($(REGION),Europe) +CFLAGS += -DEUROPE +endif + +ifeq ($(LORABOARD),DraginoLoraGPSHat) +CFLAGS += -DDRAGINO_GPS_HAT +endif + +ifeq ($(DEBUG),1) +CFLAGS += -g3 +endif + +all: rf95_client1 rf95_client2 + + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +help_functions.o: $(SHARED)/help_functions.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_client1.o: rf95_client1.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_client2.o: rf95_client2.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +gpsMT3339.o: $(SHARED)/gpsMT3339.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_client1: rf95_client1.o RH_RF95.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_client1 + +rf95_client2: rf95_client2.o help_functions.o RH_RF95.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o gpsMT3339.o + $(CC) $^ $(LIBS) -o rf95_client2 + +clean: + rm -rf *.o rf95_client1 rf95_client2 + +deploy: rf95_client1 rf95_client2 + sshpass -p"$(PASSWD)" scp rf95_client1 pi@$(HOST):/home/pi + sshpass -p"$(PASSWD)" scp rf95_client2 pi@$(HOST):/home/pi diff --git a/examples/raspi/rf95/rf95_client/rf95_client1.cpp b/examples/raspi/rf95/rf95_client/rf95_client1.cpp new file mode 100644 index 0000000..17f5379 --- /dev/null +++ b/examples/raspi/rf95/rf95_client/rf95_client1.cpp @@ -0,0 +1,161 @@ +// rf95_client.cpp +// -*- mode: C++ -*- +// Example app showing how to create a simple messaging client +// with the RH_RF95 class. RH_RF95 class does not provide for addressing or +// reliability, so you should only use RH_RF95 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf95_server. +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_client. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_client.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + +#include +#include +#include +#include + +#include + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#ifdef DRAGINO_GPS_HAT +// Pin definitions for lora dragino gps hat +#define RFM95_CS_PIN 25 // Slave Select on GPIO25 so P1 connector pin #22 +#define RFM95_IRQ_PIN 4 // IRQ on GPIO4 so P1 connector pin #7 +#define RFM95_RESET_PIN 17 // Reset on GPIO17 so P1 connector pin #11 +#undef RFM95_LED // Dragino Board as no LED to drive +#else +//Pin definitions for other board +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 +#endif + +//Client and Server Addresses +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +//RFM95 Configuration +#ifdef EUROPE +#define RFM95_FREQUENCY 868.00 +#else +#define RFM95_FREQUENCY 915.00 +#endif +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_client startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_client startup OK.\n" ); + printf( "\nRPI GPIO settings:\n" ); + printf("CS-> GPIO %d\n", (uint8_t) RFM95_CS_PIN); + printf("IRQ-> GPIO %d\n", (uint8_t) RFM95_IRQ_PIN); +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!rf95.init()) + { + printf( "\n\nRF95 driver failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client(This) Address= %d\n", CLIENT_ADDRESS); + printf("Server Address= %d\n", SERVER_ADDRESS); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + rf95.setThisAddress(CLIENT_ADDRESS); + rf95.setHeaderFrom(CLIENT_ADDRESS); + rf95.setHeaderTo(SERVER_ADDRESS); + rf95.setModemConfig(RH_RF95::Bw125Cr48Sf4096); + /* End Manager/Driver settings code */ + + /* Begin Datagram Client Code */ + while(!flag) + { + Serial.println("Sending to rf95_server"); + // Send a message to rf95_server +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + uint8_t data[] = "Hello World!"; + rf95.send(data, sizeof(data)); + + rf95.waitPacketSent(); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + // Now wait for a reply + uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (rf95.waitAvailableTimeout(3000)) + { + // Should be a reply message for us now + if (rf95.recv(buf, &len)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + Serial.print("got reply: "); + Serial.println((char*)buf); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is rf95_server running?"); + } + gpioDelay(400000); + } + printf( "\nrf95_client Tester Ending\n" ); + gpioTerminate(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} + diff --git a/examples/raspi/rf95/rf95_client/rf95_client2.cpp b/examples/raspi/rf95/rf95_client/rf95_client2.cpp new file mode 100644 index 0000000..ff5298e --- /dev/null +++ b/examples/raspi/rf95/rf95_client/rf95_client2.cpp @@ -0,0 +1,250 @@ +// rf95_client.cpp +// -*- mode: C++ -*- +// Example app showing how to create a simple messaging client +// with the RH_RF95 class. RH_RF95 class does not provide for addressing or +// reliability, so you should only use RH_RF95 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf95_server. +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_client. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_client.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + +#include +#include +#include +#include + +#include +#include + +//Function Definitions +void sig_handler(int sig); + + + +#ifdef DRAGINO_GPS_HAT +pthread_mutex_t gps_data_mutex; +#include + +// Pin definitions for dragino gps hat +#define RFM95_CS_PIN 25 // Slave Select on GPIO25 so P1 connector pin #22 +#define RFM95_IRQ_PIN 4 // IRQ on GPIO4 so P1 connector pin #7 +#define RFM95_RESET_PIN 17 // Reset on GPIO17 so P1 connector pin #11 +#undef RFM95_LED // Dragino Board as no LED to drive +#else +//Pin definitions for other board +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 +#endif + +//Client and Server Addresses +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +//RFM95 Configuration +#ifdef EUROPE +#define RFM95_FREQUENCY 868.00 +#else +#define RFM95_FREQUENCY 915.00 +#endif +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); +#ifdef DRAGINO_GPS_HAT +gps_MT3339 gps(GPS_DEVICE, &gps_data_mutex); +#endif +//Flag for Ctrl-C +int flag = 0; + + +// function to handle Lora device +void* lora_main(void* ptr) +{ +#ifndef DRAGINO_GPS_HAT + char temp[10]; + uint8_t data[] = "Hello World(xxxxx)!"; +#else + uint8_t data[50]; +#endif + uint16_t sequenceCounter = 0; + + printf("\nLORA Handler started\n"); + print_scheduler(); + print_scope(); + + while(!flag) + { +// printf("Sending to rf95_server"); + // Send a message to rf95_server +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + + +#ifndef DRAGINO_GPS_HAT + snprintf(temp,6,"%05d",sequenceCounter); + strncpy((char*)data+12,temp,5); +#else + pthread_mutex_lock(&gps_data_mutex); + snprintf((char*)data,45,"(%05d):%10s,%10s,%11s",sequenceCounter,gps.getTimestamp(),gps.getLatitude(),gps.getLongitude()); + pthread_mutex_unlock(&gps_data_mutex); +#endif + printf("Message=\""); + printf("%s",(char*)data); + printf("\"\n"); + rf95.send(data, sizeof(data)); + sequenceCounter++; + rf95.waitPacketSent(); + +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + // Now wait for a reply + uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (rf95.waitAvailableTimeout(5000)) + { + // Should be a reply message for us now + if (rf95.recv(buf, &len)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + printf("got reply: "); + printf("\"%s\"\n",(char*)buf); + +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + else + { + printf("recv failed"); + } + } + else + { + printf("No reply, is rf95_server running?\n"); + } +#ifdef DRAGINO_GPS_HAT + pthread_yield(); +#endif + } +} + + +//Main Function +int main (int argc, const char* argv[] ) +{ + // startup + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_client startup Failed.\n" ); + gpioTerminate(); + return 1; + } + + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_client startup OK.\n" ); + printf( "\nRPI GPIO settings:\n" ); + printf("CS-> GPIO %d\n", (uint8_t) RFM95_CS_PIN); + printf("IRQ-> GPIO %d\n", (uint8_t) RFM95_IRQ_PIN); +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + +#ifdef RFM95_RESET_PIN + printf( "RST-> GPIO %d\n", (uint8_t) RFM95_RESET_PIN ); + // Pulse a reset on module + gpioSetMode(RFM95_RESET_PIN, PI_OUTPUT); + digitalWrite(RFM95_RESET_PIN, LOW ); + gpioDelay(150); + digitalWrite(RFM95_RESET_PIN, HIGH ); + gpioDelay(100); +#endif + + if (!rf95.init()) + { + printf( "\n\nRF95 driver failed to initialize.\n\n" ); + gpioTerminate(); + return 1; + } + + printf("\nRFM 95 Device Version:0x%x\n",rf95.getDeviceVersion()); + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client(This) Address= %d\n", CLIENT_ADDRESS); + printf("Server Address= %d\n", SERVER_ADDRESS); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + rf95.setThisAddress(CLIENT_ADDRESS); + rf95.setHeaderFrom(CLIENT_ADDRESS); + rf95.setHeaderTo(SERVER_ADDRESS); + rf95.setModemConfig(RH_RF95::Bw125Cr48Sf4096); + /* End Manager/Driver settings code */ + + /* Begin Datagram Client Code */ + +#ifdef DRAGINO_GPS_HAT + pthread_t read_gps_thread, lora_thread; + int iret; + pthread_mutex_init(&gps_data_mutex,NULL); + iret = pthread_create( &read_gps_thread, NULL, ((void* (*)(void*))&gps_MT3339::read_gps), &gps); + if (iret != 0) + printf("\nCould not start thread to read gps data\n"); + iret = pthread_create( &lora_thread, NULL, lora_main, NULL); + if (iret != 0) + printf("\nCould not start thread to handle lora\n"); + + print_scheduler(); +#endif + +// main loop + while(!flag) + { +#ifndef DRAGINO_GPS_HAT + lora_main(NULL); +#else + sleep(1); +#endif + } + printf( "\nrf95_client Tester Ending\n" ); +#ifdef DRAGINO_GPS_HAT + pthread_join(lora_thread,NULL); + pthread_join(read_gps_thread,NULL); +#endif + gpioTerminate(); + return 0; +} + +// signal handler terminating the program on CTRL-c +void sig_handler(int sig) +{ + flag=1; +#ifdef DRAGINO_GPS_HAT + gps.stop(); +#endif +} + diff --git a/examples/raspi/rf95/rf95_mesh_client/Makefile b/examples/raspi/rf95/rf95_mesh_client/Makefile new file mode 100644 index 0000000..97344a9 --- /dev/null +++ b/examples/raspi/rf95/rf95_mesh_client/Makefile @@ -0,0 +1,52 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_mesh_client + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_mesh_client.o: rf95_mesh_client.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHMesh.o: $(RADIOHEADBASE)/RHMesh.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHRouter.o: $(RADIOHEADBASE)/RHRouter.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_mesh_client: rf95_mesh_client.o RH_RF95.o RHMesh.o RHRouter.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_mesh_client + + +clean: + rm -rf *.o rf95_mesh_client + diff --git a/examples/raspi/rf95/rf95_mesh_client/rf95_mesh_client.cpp b/examples/raspi/rf95/rf95_mesh_client/rf95_mesh_client.cpp new file mode 100644 index 0000000..08fc88a --- /dev/null +++ b/examples/raspi/rf95/rf95_mesh_client/rf95_mesh_client.cpp @@ -0,0 +1,151 @@ +// rf95_mesh_client.cpp +// -*- mode: C++ -*- +// Example application showing how to create a simple addressed, routed reliable messaging client +// with the RHMesh class. +// It is designed to work with the other examples rf95_mesh_server* +// Hint: you can simulate other network topologies by setting the +// RH_TEST_NETWORK define in RHRouter.h + +// Mesh has much greater memory requirements, and you may need to limit the +// max message length to prevent wierd crashes +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_mesh_client. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_mesh_client.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + +#include +#include +#include +#include + +#include +#include + +#define RH_MESH_MAX_MESSAGE_LEN 50 + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +// In this small artifical network of 4 nodes, +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHMesh manager(rf95, CLIENT_ADDRESS); + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_mesh_client startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_mesh_client startup OK.\n" ); + +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!manager.init()) + { + printf( "\n\nMesh Manager Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client(This) Address= %d\n", CLIENT_ADDRESS); + printf("Server Address 1= %d\n", SERVER1_ADDRESS); + printf("Server Address 2= %d\n", SERVER2_ADDRESS); + printf("Server Address 3= %d\n", SERVER3_ADDRESS); + printf("Route: Client->Server 3 is automatic in MESH.\n"); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + /* End Manager/Driver settings code */ + + + uint8_t data[] = "Hello World!"; + // Dont put this on the stack: + uint8_t buf[RH_MESH_MAX_MESSAGE_LEN]; + + while(!flag) + { + Serial.println("Sending to manager_mesh_server3"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + + // Send a message to a rf95_mesh_server + // A route to the destination will be automatically discovered. + if (manager.sendtoWait(data, sizeof(data), SERVER3_ADDRESS) == RH_ROUTER_ERROR_NONE) + { + // It has been reliably delivered to the next node. + // Now wait for a reply from the ultimate server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 3000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is rf95_mesh_server1, rf95_mesh_server2 and rf95_mesh_server3 running?"); + } + } + else + Serial.println("sendtoWait failed. Are the intermediate mesh servers running?"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + gpioDelay(400000); + } + printf( "\nrf95_mesh_client Tester Ending\n" ); + gpioTerminate(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} + diff --git a/examples/raspi/rf95/rf95_mesh_server1/Makefile b/examples/raspi/rf95/rf95_mesh_server1/Makefile new file mode 100644 index 0000000..9922d64 --- /dev/null +++ b/examples/raspi/rf95/rf95_mesh_server1/Makefile @@ -0,0 +1,52 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_mesh_server1 + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_mesh_server1.o: rf95_mesh_server1.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHMesh.o: $(RADIOHEADBASE)/RHMesh.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHRouter.o: $(RADIOHEADBASE)/RHRouter.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_mesh_server1: rf95_mesh_server1.o RH_RF95.o RHMesh.o RHRouter.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_mesh_server1 + + +clean: + rm -rf *.o rf95_mesh_server1 + diff --git a/examples/raspi/rf95/rf95_mesh_server1/rf95_mesh_server1.cpp b/examples/raspi/rf95/rf95_mesh_server1/rf95_mesh_server1.cpp new file mode 100644 index 0000000..c1ac234 --- /dev/null +++ b/examples/raspi/rf95/rf95_mesh_server1/rf95_mesh_server1.cpp @@ -0,0 +1,138 @@ +// rf95_mesh_server1.cpp +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHMesh class. +// It is designed to work with the other examples rf95_mesh_* +// Hint: you can simulate other network topologies by setting the +// RH_TEST_NETWORK define in RHRouter.h + +// Mesh has much greater memory requirements, and you may need to limit the +// max message length to prevent wierd crashes +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_mesh_server1. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_mesh_server1.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + +#include +#include +#include +#include + +#include +#include + +#define RH_MESH_MAX_MESSAGE_LEN 50 + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +// In this small artifical network of 4 nodes, +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHMesh manager(rf95, SERVER1_ADDRESS); + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_mesh_server1 startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_mesh_server1 startup OK.\n" ); + +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!manager.init()) + { + printf( "\n\nMesh Manager Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client Address= %d\n", CLIENT_ADDRESS); + printf("Server(This) Address 1= %d\n", SERVER1_ADDRESS); + printf("Server Address 2= %d\n", SERVER2_ADDRESS); + printf("Server Address 3= %d\n", SERVER3_ADDRESS); + printf("Route: Client->Server 3 is automatic in MESH.\n"); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + /* End Manager/Driver settings code */ + + uint8_t data[] = "And hello back to you from server1"; + // Dont put this on the stack: + uint8_t buf[RH_MESH_MAX_MESSAGE_LEN]; + + while(!flag) + { + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + } + + printf( "\nrf95_mesh_server1 Tester Ending\n" ); + gpioTerminate(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} diff --git a/examples/raspi/rf95/rf95_mesh_server2/Makefile b/examples/raspi/rf95/rf95_mesh_server2/Makefile new file mode 100644 index 0000000..2e3a6cb --- /dev/null +++ b/examples/raspi/rf95/rf95_mesh_server2/Makefile @@ -0,0 +1,52 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_mesh_server2 + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_mesh_server2.o: rf95_mesh_server2.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHMesh.o: $(RADIOHEADBASE)/RHMesh.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHRouter.o: $(RADIOHEADBASE)/RHRouter.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_mesh_server2: rf95_mesh_server2.o RH_RF95.o RHMesh.o RHRouter.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_mesh_server2 + + +clean: + rm -rf *.o rf95_mesh_server2 + diff --git a/examples/raspi/rf95/rf95_mesh_server2/rf95_mesh_server2.cpp b/examples/raspi/rf95/rf95_mesh_server2/rf95_mesh_server2.cpp new file mode 100644 index 0000000..3e2cf78 --- /dev/null +++ b/examples/raspi/rf95/rf95_mesh_server2/rf95_mesh_server2.cpp @@ -0,0 +1,138 @@ +// rf95_mesh_server2.cpp +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHMesh class. +// It is designed to work with the other examples rf95_mesh_* +// Hint: you can simulate other network topologies by setting the +// RH_TEST_NETWORK define in RHRouter.h + +// Mesh has much greater memory requirements, and you may need to limit the +// max message length to prevent wierd crashes +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_mesh_server2. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_mesh_server2.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + +#include +#include +#include +#include + +#include +#include + +#define RH_MESH_MAX_MESSAGE_LEN 50 + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +// In this small artifical network of 4 nodes, +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHMesh manager(rf95, SERVER2_ADDRESS); + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_mesh_server2 startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_mesh_server2 startup OK.\n" ); + +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!manager.init()) + { + printf( "\n\nMesh Manager Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client Address= %d\n", CLIENT_ADDRESS); + printf("Server Address 1= %d\n", SERVER1_ADDRESS); + printf("Server(This) Address 2= %d\n", SERVER2_ADDRESS); + printf("Server Address 3= %d\n", SERVER3_ADDRESS); + printf("Route: Client->Server 3 is automatic in MESH.\n"); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + /* End Manager/Driver settings code */ + + uint8_t data[] = "And hello back to you from server2"; + // Dont put this on the stack: + uint8_t buf[RH_MESH_MAX_MESSAGE_LEN]; + + while(!flag) + { + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + } + + printf( "\nrf95_mesh_server2 Tester Ending\n" ); + gpioTerminate(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} diff --git a/examples/raspi/rf95/rf95_mesh_server3/Makefile b/examples/raspi/rf95/rf95_mesh_server3/Makefile new file mode 100644 index 0000000..b7d154e --- /dev/null +++ b/examples/raspi/rf95/rf95_mesh_server3/Makefile @@ -0,0 +1,52 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_mesh_server3 + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_mesh_server3.o: rf95_mesh_server3.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHMesh.o: $(RADIOHEADBASE)/RHMesh.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHRouter.o: $(RADIOHEADBASE)/RHRouter.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_mesh_server3: rf95_mesh_server3.o RH_RF95.o RHMesh.o RHRouter.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_mesh_server3 + + +clean: + rm -rf *.o rf95_mesh_server3 + diff --git a/examples/raspi/rf95/rf95_mesh_server3/rf95_mesh_server3.cpp b/examples/raspi/rf95/rf95_mesh_server3/rf95_mesh_server3.cpp new file mode 100644 index 0000000..f9e79ef --- /dev/null +++ b/examples/raspi/rf95/rf95_mesh_server3/rf95_mesh_server3.cpp @@ -0,0 +1,138 @@ +// rf95_mesh_server3.cpp +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHMesh class. +// It is designed to work with the other examples rf95_mesh_* +// Hint: you can simulate other network topologies by setting the +// RH_TEST_NETWORK define in RHRouter.h + +// Mesh has much greater memory requirements, and you may need to limit the +// max message length to prevent wierd crashes +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_mesh_server3. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_mesh_server3.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + +#include +#include +#include +#include + +#include +#include + +#define RH_MESH_MAX_MESSAGE_LEN 50 + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +// In this small artifical network of 4 nodes, +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHMesh manager(rf95, SERVER3_ADDRESS); + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_mesh_server3 startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_mesh_server2 startup OK.\n" ); + +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!manager.init()) + { + printf( "\n\nMesh Manager Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client Address= %d\n", CLIENT_ADDRESS); + printf("Server Address 1= %d\n", SERVER1_ADDRESS); + printf("Server Address 2= %d\n", SERVER2_ADDRESS); + printf("Server(This) Address 3= %d\n", SERVER3_ADDRESS); + printf("Route: Client->Server 3 is automatic in MESH.\n"); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + /* End Manager/Driver settings code */ + + uint8_t data[] = "And hello back to you from server3"; + // Dont put this on the stack: + uint8_t buf[RH_MESH_MAX_MESSAGE_LEN]; + + while(!flag) + { + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + } + + printf( "\nrf95_mesh_server3 Tester Ending\n" ); + gpioTerminate(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} diff --git a/examples/raspi/rf95/rf95_reliable_datagram_client/Makefile b/examples/raspi/rf95/rf95_reliable_datagram_client/Makefile new file mode 100644 index 0000000..29ba2da --- /dev/null +++ b/examples/raspi/rf95/rf95_reliable_datagram_client/Makefile @@ -0,0 +1,46 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_reliable_datagram_client + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_reliable_datagram_client.o: rf95_reliable_datagram_client.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_reliable_datagram_client: rf95_reliable_datagram_client.o RH_RF95.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_reliable_datagram_client + + +clean: + rm -rf *.o rf95_reliable_datagram_client + diff --git a/examples/raspi/rf95/rf95_reliable_datagram_client/rf95_reliable_datagram_client.cpp b/examples/raspi/rf95/rf95_reliable_datagram_client/rf95_reliable_datagram_client.cpp new file mode 100644 index 0000000..f78b8a6 --- /dev/null +++ b/examples/raspi/rf95/rf95_reliable_datagram_client/rf95_reliable_datagram_client.cpp @@ -0,0 +1,141 @@ +// rf95_reliable_datagram_client.cpp +// -*- mode: C++ -*- +// Example app showing how to create a simple addressed, reliable messaging client +// with the RHReliableDatagram class, using the RH_RF95 driver to control a RF95 radio. +// It is designed to work with the other example rf95_reliable_datagram_server. +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_reliable_datagram_client. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_reliable_datagram_client.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + + +#include +#include +#include +#include + +#include +#include + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +//Client and Server Addresses +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(rf95, CLIENT_ADDRESS); + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_reliable_datagram_client startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_reliable_datagram_client startup OK.\n" ); + printf( "\nRPI GPIO settings:\n" ); + printf("CS-> GPIO %d\n", (uint8_t) RFM95_CS_PIN); + printf("IRQ-> GPIO %d\n", (uint8_t) RFM95_IRQ_PIN); +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!rf95.init()) + { + printf( "\n\nRF95 Driver Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client(This) Address= %d\n", CLIENT_ADDRESS); + printf("Server Address= %d\n", SERVER_ADDRESS); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + rf95.setThisAddress(CLIENT_ADDRESS); + rf95.setHeaderFrom(CLIENT_ADDRESS); + rf95.setHeaderTo(SERVER_ADDRESS); + /* End Manager/Driver settings code */ + + /* Begin Datagram Client Code */ + uint8_t data[] = "Hello World!"; + // Dont put this on the stack: + uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + + while(!flag) + { + Serial.println("Sending to rf95_reliable_datagram_server"); + // Send a message to manager_server +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is rf95_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + gpioDelay(500000); + } + printf( "\nrf95_reliable_datagram_client Tester Ending\n" ); + gpioTerminate(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} diff --git a/examples/raspi/rf95/rf95_reliable_datagram_server/Makefile b/examples/raspi/rf95/rf95_reliable_datagram_server/Makefile new file mode 100644 index 0000000..218d78c --- /dev/null +++ b/examples/raspi/rf95/rf95_reliable_datagram_server/Makefile @@ -0,0 +1,46 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_reliable_datagram_server + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_reliable_datagram_server.o: rf95_reliable_datagram_server.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_reliable_datagram_server: rf95_reliable_datagram_server.o RH_RF95.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_reliable_datagram_server + + +clean: + rm -rf *.o rf95_reliable_datagram_server + diff --git a/examples/raspi/rf95/rf95_reliable_datagram_server/rf95_reliable_datagram_server.cpp b/examples/raspi/rf95/rf95_reliable_datagram_server/rf95_reliable_datagram_server.cpp new file mode 100644 index 0000000..7b7f158 --- /dev/null +++ b/examples/raspi/rf95/rf95_reliable_datagram_server/rf95_reliable_datagram_server.cpp @@ -0,0 +1,136 @@ +// rf95_reliable_datagram_server.cpp +// -*- mode: C++ -*- +// Example app showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_RF95 driver to control a RF95 radio. +// It is designed to work with the other example rf95_reliable_datagram_client. +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_reliable_datagram_server. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_reliable_datagram_server.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + + +#include +#include +#include +#include + +#include +#include + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +//Client and Server Addresses +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(rf95, SERVER_ADDRESS); + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_reliable_datagram_server startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_reliable_datagram_server startup OK.\n" ); + printf( "\nRPI GPIO settings:\n" ); + printf("CS-> GPIO %d\n", (uint8_t) RFM95_CS_PIN); + printf("IRQ-> GPIO %d\n", (uint8_t) RFM95_IRQ_PIN); +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!rf95.init()) + { + printf( "\n\nRF95 Driver Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client Address= %d\n", CLIENT_ADDRESS); + printf("Server(This) Address= %d\n", SERVER_ADDRESS); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + rf95.setThisAddress(SERVER_ADDRESS); + rf95.setHeaderFrom(SERVER_ADDRESS); + /* End Manager/Driver settings code */ + + /* Begin Reliable Datagram Server Code */ + uint8_t data[] = "And hello back to you"; + // Dont put this on the stack: + uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + + while(!flag) + { + if (manager.available()) + { + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + } + } + printf( "\nrf95_reliable_datagram_server Tester Ending\n" ); + gpioTerminate(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} + diff --git a/examples/raspi/rf95/rf95_router_client/Makefile b/examples/raspi/rf95/rf95_router_client/Makefile new file mode 100644 index 0000000..67178d4 --- /dev/null +++ b/examples/raspi/rf95/rf95_router_client/Makefile @@ -0,0 +1,49 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_router_client + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_router_client.o: rf95_router_client.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHRouter.o: $(RADIOHEADBASE)/RHRouter.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_router_client: rf95_router_client.o RH_RF95.o RHRouter.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_router_client + + +clean: + rm -rf *.o rf95_router_client + diff --git a/examples/raspi/rf95/rf95_router_client/rf95_router_client.cpp b/examples/raspi/rf95/rf95_router_client/rf95_router_client.cpp new file mode 100644 index 0000000..77bab45 --- /dev/null +++ b/examples/raspi/rf95/rf95_router_client/rf95_router_client.cpp @@ -0,0 +1,155 @@ +// rf95_router_client.cpp +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging client +// with the RHRouter class. +// It is designed to work with the other examples rf95_router_server*. +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_router_client. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_router_client.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + +#include +#include +#include +#include + +#include +#include + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +// In this small artifical network of 4 nodes, +// messages are routed via intermediate nodes to their destination +// node. All nodes can act as routers +// CLIENT_ADDRESS <-> SERVER1_ADDRESS <-> SERVER2_ADDRESS<->SERVER3_ADDRESS +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Create an instance of a driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHRouter manager(rf95, CLIENT_ADDRESS); + + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_router_client startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_router_client startup OK.\n" ); + +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!manager.init()) + { + printf( "\n\nRouter Manager Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client(This) Address= %d\n", CLIENT_ADDRESS); + printf("Server Address 1= %d\n", SERVER1_ADDRESS); + printf("Server Address 2= %d\n", SERVER2_ADDRESS); + printf("Server Address 3= %d\n", SERVER3_ADDRESS); + printf("Route: Client-> Server 1-> Server 2-> Server 3\n"); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + + // Manually define the routes for this network + manager.addRouteTo(SERVER1_ADDRESS, SERVER1_ADDRESS); + manager.addRouteTo(SERVER2_ADDRESS, SERVER2_ADDRESS); + manager.addRouteTo(SERVER3_ADDRESS, SERVER3_ADDRESS); + /* End Manager/Driver settings code */ + + uint8_t data[] = "Hello World!"; + // Dont put this on the stack: + uint8_t buf[RH_ROUTER_MAX_MESSAGE_LEN]; + + while(!flag) + { + Serial.println("Sending to rf95_router_server3"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + + // Send a message to a rf95_router_server + // It will be routed by the intermediate + // nodes to the destination node, accorinding to the + // routing tables in each node + if (manager.sendtoWait(data, sizeof(data), SERVER3_ADDRESS) == RH_ROUTER_ERROR_NONE) + { + // It has been reliably delivered to the next node. + // Now wait for a reply from the ultimate server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 3000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is rf95_router_server1, rf95_router_server2 and rf95_router_server3 running?"); + } + } + else + Serial.println("sendtoWait failed. Are the intermediate router servers running?"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + gpioDelay(500000); + } + printf( "\nrf95_router_client Tester Ending\n" ); + gpioTerminate(); + return 0; + +} + +void sig_handler(int sig) +{ + flag=1; +} + diff --git a/examples/raspi/rf95/rf95_router_server1/Makefile b/examples/raspi/rf95/rf95_router_server1/Makefile new file mode 100644 index 0000000..fc857a0 --- /dev/null +++ b/examples/raspi/rf95/rf95_router_server1/Makefile @@ -0,0 +1,49 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_router_server1 + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_router_server1.o: rf95_router_server1.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHRouter.o: $(RADIOHEADBASE)/RHRouter.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_router_server1: rf95_router_server1.o RH_RF95.o RHRouter.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_router_server1 + + +clean: + rm -rf *.o rf95_router_server1 + diff --git a/examples/raspi/rf95/rf95_router_server1/rf95_router_server1.cpp b/examples/raspi/rf95/rf95_router_server1/rf95_router_server1.cpp new file mode 100644 index 0000000..538a5c1 --- /dev/null +++ b/examples/raspi/rf95/rf95_router_server1/rf95_router_server1.cpp @@ -0,0 +1,143 @@ +// rf95_router_server1.cpp +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHRouter class. +// It is designed to work with the other example rf95_router_client. +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_router_server1. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_router_server1.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + + +#include +#include +#include +#include + +#include +#include + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +// In this small artifical network of 4 nodes, +// messages are routed via intermediate nodes to their destination +// node. All nodes can act as routers +// CLIENT_ADDRESS <-> SERVER1_ADDRESS <-> SERVER2_ADDRESS<->SERVER3_ADDRESS +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Create an instance of a driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHRouter manager(rf95, SERVER1_ADDRESS); + + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_router_server1 startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_router_server1 startup OK.\n" ); + +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!manager.init()) + { + printf( "\n\nRouter Manager Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client Address= %d\n", CLIENT_ADDRESS); + printf("Server(This) Address 1= %d\n", SERVER1_ADDRESS); + printf("Server Address 2= %d\n", SERVER2_ADDRESS); + printf("Server Address 3= %d\n", SERVER3_ADDRESS); + printf("Route: Client-> Server 1-> Server 2-> Server 3\n"); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + rf95.setThisAddress(SERVER1_ADDRESS); + + // Manually define the routes for this network + manager.addRouteTo(CLIENT_ADDRESS, CLIENT_ADDRESS); + manager.addRouteTo(SERVER2_ADDRESS, SERVER2_ADDRESS); + manager.addRouteTo(SERVER3_ADDRESS, SERVER2_ADDRESS); + /* End Manager/Driver settings code */ + + uint8_t data[] = "And hello back to you from server1"; + // Dont put this on the stack: + uint8_t buf[RH_ROUTER_MAX_MESSAGE_LEN]; + + while(!flag) + { + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + } + printf( "\nrf95_router_server1 Tester Ending\n" ); + gpioTerminate(); + return 0; + +} + +void sig_handler(int sig) +{ + flag=1; +} + diff --git a/examples/raspi/rf95/rf95_router_server2/Makefile b/examples/raspi/rf95/rf95_router_server2/Makefile new file mode 100644 index 0000000..6f0f2d5 --- /dev/null +++ b/examples/raspi/rf95/rf95_router_server2/Makefile @@ -0,0 +1,49 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_router_server2 + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_router_server2.o: rf95_router_server2.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHRouter.o: $(RADIOHEADBASE)/RHRouter.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_router_server2: rf95_router_server2.o RH_RF95.o RHRouter.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_router_server2 + + +clean: + rm -rf *.o rf95_router_server2 + diff --git a/examples/raspi/rf95/rf95_router_server2/rf95_router_server2.cpp b/examples/raspi/rf95/rf95_router_server2/rf95_router_server2.cpp new file mode 100644 index 0000000..f918a3c --- /dev/null +++ b/examples/raspi/rf95/rf95_router_server2/rf95_router_server2.cpp @@ -0,0 +1,143 @@ +// rf95_router_server2.cpp +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHRouter class. +// It is designed to work with the other example rf95_router_client. +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_router_server2. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_router_server2.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + + +#include +#include +#include +#include + +#include +#include + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +// In this small artifical network of 4 nodes, +// messages are routed via intermediate nodes to their destination +// node. All nodes can act as routers +// CLIENT_ADDRESS <-> SERVER1_ADDRESS <-> SERVER2_ADDRESS<->SERVER3_ADDRESS +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Create an instance of a driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHRouter manager(rf95, SERVER2_ADDRESS); + + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_router_server2 startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_router_server2 startup OK.\n" ); + +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!manager.init()) + { + printf( "\n\nRouter Manager Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client Address= %d\n", CLIENT_ADDRESS); + printf("Server Address 1 = %d\n", SERVER1_ADDRESS); + printf("Server(This) Address 2 = %d\n", SERVER2_ADDRESS); + printf("Server Address 3= %d\n", SERVER3_ADDRESS); + printf("Route: Client-> Server 1-> Server 2-> Server 3\n"); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + rf95.setThisAddress(SERVER2_ADDRESS); + + // Manually define the routes for this network + manager.addRouteTo(CLIENT_ADDRESS, CLIENT_ADDRESS); + manager.addRouteTo(SERVER2_ADDRESS, SERVER2_ADDRESS); + manager.addRouteTo(SERVER3_ADDRESS, SERVER2_ADDRESS); + /* End Manager/Driver settings code */ + + uint8_t data[] = "And hello back to you from server2"; + // Dont put this on the stack: + uint8_t buf[RH_ROUTER_MAX_MESSAGE_LEN]; + + while(!flag) + { + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + } + printf( "\nrf95_router_server2 Tester Ending\n" ); + gpioTerminate(); + return 0; + +} + +void sig_handler(int sig) +{ + flag=1; +} + diff --git a/examples/raspi/rf95/rf95_router_server3/Makefile b/examples/raspi/rf95/rf95_router_server3/Makefile new file mode 100644 index 0000000..4210d4c --- /dev/null +++ b/examples/raspi/rf95/rf95_router_server3/Makefile @@ -0,0 +1,49 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_router_server3 + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_router_server3.o: rf95_router_server3.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHRouter.o: $(RADIOHEADBASE)/RHRouter.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_router_server3: rf95_router_server3.o RH_RF95.o RHRouter.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_router_server3 + + +clean: + rm -rf *.o rf95_router_server3 + diff --git a/examples/raspi/rf95/rf95_router_server3/rf95_router_server3.cpp b/examples/raspi/rf95/rf95_router_server3/rf95_router_server3.cpp new file mode 100644 index 0000000..0903f4f --- /dev/null +++ b/examples/raspi/rf95/rf95_router_server3/rf95_router_server3.cpp @@ -0,0 +1,143 @@ +// rf95_router_server3.cpp +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHRouter class. +// It is designed to work with the other example rf95_router_client. +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_router_server3. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_router_server3.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + + +#include +#include +#include +#include + +#include +#include + +//Function Definitions +void sig_handler(int sig); + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +// In this small artifical network of 4 nodes, +// messages are routed via intermediate nodes to their destination +// node. All nodes can act as routers +// CLIENT_ADDRESS <-> SERVER1_ADDRESS <-> SERVER2_ADDRESS<->SERVER3_ADDRESS +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Create an instance of a driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHRouter manager(rf95, SERVER3_ADDRESS); + + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_router_server3 startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_router_server3 startup OK.\n" ); + +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!manager.init()) + { + printf( "\n\nRouter Manager Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client Address= %d\n", CLIENT_ADDRESS); + printf("Server Address 1 = %d\n", SERVER1_ADDRESS); + printf("Server Address 2 = %d\n", SERVER2_ADDRESS); + printf("Server(This) Address 3 = %d\n", SERVER3_ADDRESS); + printf("Route: Client-> Server 1-> Server 2-> Server 3\n"); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + rf95.setThisAddress(SERVER3_ADDRESS); + + // Manually define the routes for this network + manager.addRouteTo(CLIENT_ADDRESS, CLIENT_ADDRESS); + manager.addRouteTo(SERVER2_ADDRESS, SERVER2_ADDRESS); + manager.addRouteTo(SERVER3_ADDRESS, SERVER2_ADDRESS); + /* End Manager/Driver settings code */ + + uint8_t data[] = "And hello back to you from server3"; + // Dont put this on the stack: + uint8_t buf[RH_ROUTER_MAX_MESSAGE_LEN]; + + while(!flag) + { + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + } + printf( "\nrf95_router_server3 Tester Ending\n" ); + gpioTerminate(); + return 0; + +} + +void sig_handler(int sig) +{ + flag=1; +} + diff --git a/examples/raspi/rf95/rf95_router_test/Makefile b/examples/raspi/rf95/rf95_router_test/Makefile new file mode 100644 index 0000000..e85cb15 --- /dev/null +++ b/examples/raspi/rf95/rf95_router_test/Makefile @@ -0,0 +1,49 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CC = g++ +CFLAGS = -DRASPBERRY_PI -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +INCLUDE = -I$(RADIOHEADBASE) + +all: rf95_router_test + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +rf95_router_test.o: rf95_router_test.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHRouter.o: $(RADIOHEADBASE)/RHRouter.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHReliableDatagram.o: $(RADIOHEADBASE)/RHReliableDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_router_test: rf95_router_test.o RH_RF95.o RHRouter.o RHReliableDatagram.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o + $(CC) $^ $(LIBS) -o rf95_router_test + + +clean: + rm -rf *.o rf95_router_test + diff --git a/examples/raspi/rf95/rf95_router_test/rf95_router_test.cpp b/examples/raspi/rf95/rf95_router_test/rf95_router_test.cpp new file mode 100644 index 0000000..b12c52e --- /dev/null +++ b/examples/raspi/rf95/rf95_router_test/rf95_router_test.cpp @@ -0,0 +1,183 @@ +// rf95_router_test.cpp +// -*- mode: C++ -*- +// +// Test code used during library development, showing how +// to do various things, and how to call various functions +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_router_test. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(10/6/2019) Contributed by Brody M. Based off rf22_router_tester.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + +#include +#include +#include +#include + +#include +#include + +//Function Definitions +void sig_handler(int sig); +void test_tx(void); +void test_routes(void); + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +//uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 + +#define CLIENT_ADDRESS 1 +#define ROUTER_ADDRESS 2 +#define SERVER_ADDRESS 3 + +//RFM95 Configuration +#define RFM95_FREQUENCY 915.00 +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +// Class to manage message delivery and receipt, using the driver declared above +RHRouter manager(rf95, CLIENT_ADDRESS); + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_router_tester startup Failed.\n" ); + return 1; + } + + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_router_tester startup OK.\n" ); + +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("\nINFO: LED on GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!manager.init()) + { + printf( "\n\nRouter Manager Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client(This) Address= %d\n", CLIENT_ADDRESS); + printf("Router Address = %d\n", ROUTER_ADDRESS); + printf("Server Address = %d\n", SERVER_ADDRESS); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + /* End Manager/Driver settings code */ + + while(!flag) + { + Serial.println("Running test function..."); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif + // test_routes(); + test_tx(); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + gpioDelay(500000); + } + printf( "\nrf95_router_test Tester Ending\n" ); + gpioTerminate(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} + +void test_routes() +{ + manager.clearRoutingTable(); +// manager.printRoutingTable(); + manager.addRouteTo(1, 101); + manager.addRouteTo(2, 102); + manager.addRouteTo(3, 103); + RHRouter::RoutingTableEntry* e; + e = manager.getRouteTo(0); + if (e) // Should fail + Serial.println("getRouteTo 0 failed"); + + e = manager.getRouteTo(1); + if (!e) + Serial.println("getRouteTo 1 failed"); + if (e->dest != 1) + Serial.println("getRouteTo 2 failed"); + if (e->next_hop != 101) + Serial.println("getRouteTo 3 failed"); + if (e->state != RHRouter::Valid) + Serial.println("getRouteTo 4 failed"); + + e = manager.getRouteTo(2); + if (!e) + Serial.println("getRouteTo 5 failed"); + if (e->dest != 2) + Serial.println("getRouteTo 6 failed"); + if (e->next_hop != 102) + Serial.println("getRouteTo 7 failed"); + if (e->state != RHRouter::Valid) + Serial.println("getRouteTo 8 failed"); + + if (!manager.deleteRouteTo(1)) + Serial.println("deleteRouteTo 1 failed"); + // Route to 1 should now be gone + e = manager.getRouteTo(1); + if (e) + Serial.println("deleteRouteTo 2 failed"); + + Serial.println("-------------------"); + +// manager.printRoutingTable(); + delay(500); + +} + +void test_tx() +{ + manager.addRouteTo(SERVER_ADDRESS, ROUTER_ADDRESS); + uint8_t errorcode; + errorcode = manager.sendtoWait(data, sizeof(data), 100); // Should fail with no route + if (errorcode != RH_ROUTER_ERROR_NO_ROUTE) + Serial.println("sendtoWait 1 failed"); + errorcode = manager.sendtoWait(data, 255, 10); // Should fail too big + if (errorcode != RH_ROUTER_ERROR_INVALID_LENGTH) + Serial.println("sendtoWait 2 failed"); + errorcode = manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS); // Should fail after timeouts to 110 + if (errorcode != RH_ROUTER_ERROR_UNABLE_TO_DELIVER) + Serial.println("sendtoWait 3 failed"); + Serial.println("-------------------"); + delay(500); +} + diff --git a/examples/raspi/rf95/rf95_server/Makefile b/examples/raspi/rf95/rf95_server/Makefile new file mode 100644 index 0000000..54f8ac1 --- /dev/null +++ b/examples/raspi/rf95/rf95_server/Makefile @@ -0,0 +1,84 @@ +# Makefile +# Example for RH_RF95 on Raspberry Pi +# Requires pigpio to be installed: http://abyz.me.uk/rpi/pigpio/ + +CROSSCOMPILER=$(shell if [ -z `which 'arm-linux-gnueabihf-g++'` ]; then echo "no"; else echo "yes"; fi) +ifeq ($(CROSSCOMPILER),yes) +CC = arm-linux-gnueabihf-g++ +CFLAGS = -DRASPBERRY_PI -DRH_USE_MUTEX -pthread +LIBS = -lpigpio -lrt -L$(PIGPIOBASE) -lpthread -D_REENTRANT +RADIOHEADBASE = ../../../.. +PIGPIOBASE = ../../../../../pigpio/ +SHARED = ../shared/ +INCLUDE = -I$(RADIOHEADBASE) -I$(PIGPIOBASE) -I$(SHARED) +else +CC = g++ +CFLAGS = -DRASPBERRY_PI -DRH_USE_MUTEX -pthread +LIBS = -lpigpio -lrt +RADIOHEADBASE = ../../../.. +PIGPIOBASE = ../../../../../pigpio/ +SHARED = ../shared/ +INCLUDE = -I$(RADIOHEADBASE) -I$(PIGPIOBASE) -I$(SHARED) +endif + +ifeq ($(REGION),Europe) +CFLAGS += -DEUROPE +endif + +ifeq ($(LORABOARD),DraginoLoraGPSHat) +CFLAGS += -DDRAGINO_GPS_HAT +endif + +ifeq ($(DEBUG),1) +CFLAGS += -g3 +endif + +all: rf95_server1 rf95_server2 + +RasPi.o: $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp + $(CC) $(CFLAGS) -c $(RADIOHEADBASE)/RHutil_pigpio/RasPi.cpp $(INCLUDE) + +help_functions.o: $(SHARED)/help_functions.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_server1.o: rf95_server1.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_server2.o: rf95_server2.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RH_RF95.o: $(RADIOHEADBASE)/RH_RF95.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHDatagram.o: $(RADIOHEADBASE)/RHDatagram.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHHardwareSPI.o: $(RADIOHEADBASE)/RHHardwareSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHSPIDriver.o: $(RADIOHEADBASE)/RHSPIDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericDriver.o: $(RADIOHEADBASE)/RHGenericDriver.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +RHGenericSPI.o: $(RADIOHEADBASE)/RHGenericSPI.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +gpsMT3339.o: $(SHARED)/gpsMT3339.cpp + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +rf95_server1: rf95_server1.o help_functions.o RH_RF95.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o gpsMT3339.o + $(CC) $^ $(LIBS) -o rf95_server1 + +rf95_server2: rf95_server2.o help_functions.o RH_RF95.o RHDatagram.o RasPi.o RHHardwareSPI.o RHSPIDriver.o RHGenericDriver.o RHGenericSPI.o gpsMT3339.o + $(CC) $^ $(LIBS) -o rf95_server2 + + +clean: + rm -rf *.o rf95_server1 rf95_server2 + +deploy: rf95_server1 rf95_server2 + sshpass -p"$(PASSWD)" scp rf95_server1 pi@$(HOST):/home/pi + sshpass -p"$(PASSWD)" scp rf95_server2 pi@$(HOST):/home/pi + diff --git a/examples/raspi/rf95/rf95_server/rf95_server1.cpp b/examples/raspi/rf95/rf95_server/rf95_server1.cpp new file mode 100644 index 0000000..4161e9f --- /dev/null +++ b/examples/raspi/rf95/rf95_server/rf95_server1.cpp @@ -0,0 +1,147 @@ +// rf95_server.cpp +// -*- mode: C++ -*- +// Example app showing how to create a simple messageing server +// with the RH_RF95 class. RH_RF95 class does not provide for addressing or +// reliability, so you should only use RH_RF95 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf95_client. +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_server. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_server.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + +#include +#include +#include +#include + +#include + +//Function Definitions +void sig_handler(int sig); + +#ifdef DRAGINO_GPS_HAT +// Pin definitions for dragino gps hat +#define RFM95_CS_PIN 25 // Slave Select on GPIO25 so P1 connector pin #22 +#define RFM95_IRQ_PIN 4 // IRQ on GPIO4 so P1 connector pin #7 +#define RFM95_RESET_PIN 17 // Reset on GPIO17 so P1 connector pin #11 +#undef RFM95_LED // Dragino Board as no LED to drive +#else +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 +#endif + +//Client and Server Addresses +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +//RFM95 Configuration +#ifdef EUROPE +#define RFM95_FREQUENCY 868.00 +#else +#define RFM95_FREQUENCY 915.00 +#endif +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); + +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_server startup Failed.\n" ); + return 1; + } + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_server startup OK.\n" ); + printf( "\nRPI GPIO settings:\n" ); + printf("CS-> GPIO %d\n", (uint8_t) RFM95_CS_PIN); + printf("IRQ-> GPIO %d\n", (uint8_t) RFM95_IRQ_PIN); +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("LED-> GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!rf95.init()) + { + printf( "\n\nRF95 Driver Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client Address= %d\n", CLIENT_ADDRESS); + printf("Server(This) Address= %d\n", SERVER_ADDRESS); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + rf95.setThisAddress(SERVER_ADDRESS); + rf95.setModemConfig(RH_RF95::Bw125Cr48Sf4096); + /* End Manager/Driver settings code */ + + /* Begin Datagram Server Code */ + while(!flag) + { + if (rf95.available()) + { + // Should be a message for us now + uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (rf95.recv(buf, &len)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif +// RF95::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(rf95.lastRssi(), DEC); + + //Send a reply + uint8_t data[] = "And hello back to you"; + rf95.send(data, sizeof(data)); + rf95.waitPacketSent(); + Serial.println("Sent a reply"); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + else + { + Serial.println("recv failed"); + } + } + } + /* End Datagram Server Code */ + printf( "\nrf95_server Tester Ending\n" ); + gpioTerminate(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} diff --git a/examples/raspi/rf95/rf95_server/rf95_server2.cpp b/examples/raspi/rf95/rf95_server/rf95_server2.cpp new file mode 100644 index 0000000..76ea0f9 --- /dev/null +++ b/examples/raspi/rf95/rf95_server/rf95_server2.cpp @@ -0,0 +1,195 @@ +// rf95_server.cpp +// -*- mode: C++ -*- +// Example app showing how to create a simple messageing server +// with the RH_RF95 class. RH_RF95 class does not provide for addressing or +// reliability, so you should only use RH_RF95 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf95_client. +// +// Requires Pigpio GPIO library. Install by downloading and compiling from +// http://abyz.me.uk/rpi/pigpio/, or install via command line with +// "sudo apt install pigpio". To use, run "make" at the command line in +// the folder where this source code resides. Then execute application with +// sudo ./rf95_server. +// Tested on Raspberry Pi Zero and Zero W with LoRaWan/TTN RPI Zero Shield +// by ElectronicTricks. Although this application builds and executes on +// Raspberry Pi 3, there seems to be missed messages and hangs. +// Strategically adding delays does seem to help in some cases. + +//(9/20/2019) Contributed by Brody M. Based off rf22_server.pde. +// Raspberry Pi mods influenced by nrf24 example by Mike Poublon, +// and Charles-Henri Hallard (https://github.com/hallard/RadioHead) + +#include +#include +#include +#include + +#include +#include + +//Function Definitions +void sig_handler(int sig); + + + +#ifdef DRAGINO_GPS_HAT +pthread_mutex_t gps_data_mutex; +#include + +// Pin definitions for dragino gps hat +#define RFM95_CS_PIN 25 // Slave Select on GPIO25 so P1 connector pin #22 +#define RFM95_IRQ_PIN 4 // IRQ on GPIO4 so P1 connector pin #7 +#define RFM95_RESET_PIN 17 // Reset on GPIO17 so P1 connector pin #11 +#undef RFM95_LED // Dragino Board as no LED to drive +#else +//Pin Definitions +#define RFM95_CS_PIN 8 +#define RFM95_IRQ_PIN 25 +#define RFM95_LED 4 +#endif + +//Client and Server Addresses +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +//RFM95 Configuration +#ifdef EUROPE +#define RFM95_FREQUENCY 868.00 +#else +#define RFM95_FREQUENCY 915.00 +#endif +#define RFM95_TXPOWER 14 + +// Singleton instance of the radio driver +RH_RF95 rf95(RFM95_CS_PIN, RFM95_IRQ_PIN); +#ifdef DRAGINO_GPS_HAT +gps_MT3339 gps(GPS_DEVICE, &gps_data_mutex); +#endif +//Flag for Ctrl-C +int flag = 0; + +//Main Function +int main (int argc, const char* argv[] ) +{ + if (gpioInitialise()<0) + { + printf( "\n\nRPI rf95_server startup Failed.\n" ); + return 1; + } + gpioSetSignalFunc(2, sig_handler); //2 is SIGINT. Ctrl+C will cause signal. + + printf( "\nRPI rf95_server startup OK.\n" ); + printf( "\nRPI GPIO settings:\n" ); + printf("CS-> GPIO %d\n", (uint8_t) RFM95_CS_PIN); + printf("IRQ-> GPIO %d\n", (uint8_t) RFM95_IRQ_PIN); +#ifdef RFM95_LED + gpioSetMode(RFM95_LED, PI_OUTPUT); + printf("LED-> GPIO %d\n", (uint8_t) RFM95_LED); + gpioWrite(RFM95_LED, PI_ON); + gpioDelay(500000); + gpioWrite(RFM95_LED, PI_OFF); +#endif + + if (!rf95.init()) + { + printf( "\n\nRF95 Driver Failed to initialize.\n\n" ); + return 1; + } + + /* Begin Manager/Driver settings code */ + printf("\nRFM 95 Settings:\n"); + printf("Frequency= %d MHz\n", (uint16_t) RFM95_FREQUENCY); + printf("Power= %d\n", (uint8_t) RFM95_TXPOWER); + printf("Client Address= %d\n", CLIENT_ADDRESS); + printf("Server(This) Address= %d\n", SERVER_ADDRESS); + rf95.setTxPower(RFM95_TXPOWER, false); + rf95.setFrequency(RFM95_FREQUENCY); + rf95.setThisAddress(SERVER_ADDRESS); + rf95.setModemConfig(RH_RF95::Bw125Cr48Sf4096); + /* End Manager/Driver settings code */ + + rf95.printRegisters(); + /* Begin Datagram Server Code */ + + + // start gps of dragino gps hat +#ifdef DRAGINO_GPS_HAT + pthread_t read_gps_thread; + int iret; + pthread_mutex_init(&gps_data_mutex,NULL); + iret = pthread_create( &read_gps_thread, NULL, ((void* (*)(void*))&gps_MT3339::read_gps), &gps); + if (iret != 0) + printf("\nCould not start thread to read gps data\n"); + + + +#endif + + print_scheduler(); + /* Begin Datagram Server Code */ + while(!flag) + { + if (rf95.available()) + { + // Should be a message for us now + uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (rf95.recv(buf, &len)) + { +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_ON); +#endif +// RF95::printBuffer("request: ", buf, len); + printf("got request: \"%s\"\n", (char*)buf); + printf("RSSI: %d\n",rf95.lastRssi()); + + uint8_t data[50] = "And hello back to you"; + + //cut out sequence number from received message (format: "(...)") if in message + // and copy to outgoing message + char* temp1 = strchr((char*)buf,'('); + char* temp2 = strchr((char*)buf,')'); + if ((temp1 != NULL) && (temp2 != NULL)) + { + // if received message contains sequence number, + // reply with received sequence number + temp1++; + uint8_t wordlen = temp2 - temp1; + temp1[wordlen] = 0x00; +#ifndef DRAGINO_GPS_HAT + snprintf((char*)data+21,8,"(%5s)",temp1); +#else + // In Case of Dragino Lora/GPS hat add timestamp and gps position of server to reply + pthread_mutex_lock(&gps_data_mutex); + + snprintf((char*)data,47,"R:(%5s):%10s,%10s,%11s", + temp1,gps.getTimestamp(),gps.getLatitude(),gps.getLongitude()); + pthread_mutex_unlock(&gps_data_mutex); +#endif + } + + //Send a reply including a trailing 0x00 + rf95.send(data, strlen((char*)data)+1); + rf95.waitPacketSent(); + printf("Sent a reply: \"%s\"\n",data); +#ifdef RFM95_LED + gpioWrite(RFM95_LED, PI_OFF); +#endif + } + else + { + Serial.println("recv failed"); + } + } + } + /* End Datagram Server Code */ + printf( "\nrf95_server Tester Ending\n" ); + gpioTerminate(); + return 0; +} + +void sig_handler(int sig) +{ + flag=1; +} diff --git a/examples/raspi/rf95/shared/gpsMT3339.cpp b/examples/raspi/rf95/shared/gpsMT3339.cpp new file mode 100644 index 0000000..4a9aac3 --- /dev/null +++ b/examples/raspi/rf95/shared/gpsMT3339.cpp @@ -0,0 +1,219 @@ +// -*- mode: C++ -*- +/* + * gpsMT3339.cpp + * + * Created on: Jun 11, 2020 + * Author: Tilman Glötzner + */ + +#include +#include +#include +#include +#include + + + +gps_MT3339::gps_MT3339(const char* SerialDevice, pthread_mutex_t* mutex) { + // TODO Auto-generated constructor stub + this->mutex = mutex; + + gpsDevice = (char*)malloc(strlen(SerialDevice)*sizeof(char)); + if (gpsDevice == NULL) + return; + strcpy(gpsDevice,SerialDevice); +} + +gps_MT3339::~gps_MT3339() { + // TODO Auto-generated destructor stub + stop_reading = true; +} + +char* gps_MT3339::getLatitude() { + return latitude; +} + +char* gps_MT3339::getLongitude() { + return longitude; +} + +char* gps_MT3339::getTimestamp() { + return timestamp; +} + +//function to read gps device of Dragino GPS hat (to be executed by a thread) +char* gps_MT3339::strnstr(const char *haystack, const char *needle, size_t len) +{ + int i; + size_t needle_len; + + if (0 == (needle_len = strnlen(needle, len))) + return (char *)haystack; + + for (i=0; i<=(int)(len-needle_len); i++) + { + if ((haystack[0] == needle[0]) && + (0 == strncmp(haystack, needle, needle_len))) + return (char *)haystack; + + haystack++; + } + return NULL; +} + +void* gps_MT3339::read_gps(void* ptr) +{ + ssize_t n, gga_len,i, comma_count; + char* start_ptr; + char* end_ptr; + char gps_buffer[LEN_GPS_READ_BUFFER]; + char GPGGA_sentence[LEN_GPS_SENTENCE_BUFFER]; + int gps_fd = -1; + int nofchar; + unsigned int empty_read_counter = 0; + + printf("\nGPS READER started\n"); + print_scheduler(); + print_scope(); + memset(gps_buffer,0x00,LEN_GPS_READ_BUFFER); + + while (!stop_reading) + { + if (gps_fd < 0) + { + // Open the device in non-blocking mode + gps_fd = open(gpsDevice, O_RDWR | O_NONBLOCK); + if(gps_fd < 0) + { + printf("\nWarning: Could not open device file of GPS\n" ); + } + } + else + { + n = read(gps_fd, gps_buffer, 200); + + if (n == 0) + { + // counter incremented if there was nothing read back + empty_read_counter++; + } + if (n <= 0) + { + if (((errno != EAGAIN) and (errno != EWOULDBLOCK)) + || (empty_read_counter > MAX_EMPTY_READ)) + { + // on read error close file descriptor and restart + printf("GPS device being reset\n"); + strcpy(longitude,"NoValue"); + strcpy(latitude,"NoValue"); + strcpy(timestamp,"NoValue"); + + close(gps_fd); + gps_fd = -1; + empty_read_counter = 0; + memset(gps_buffer,0x00,LEN_GPS_READ_BUFFER); + } + } + else + { + // sucessful read => reset counter tracking empty reads + empty_read_counter = 0; + + // Scan for sentence read from GPS device containing position + start_ptr = strnstr (gps_buffer, "$GPGGA", LEN_GPS_READ_BUFFER); + if( start_ptr != NULL) + { + end_ptr = (char*)memchr(start_ptr, '*', + LEN_GPS_READ_BUFFER - (start_ptr - gps_buffer)); + + if (end_ptr != NULL) + { + + gga_len = (end_ptr-start_ptr) < LEN_GPS_SENTENCE_BUFFER ? + (end_ptr-start_ptr) : LEN_GPS_SENTENCE_BUFFER - 1; + memcpy(GPGGA_sentence,gps_buffer,gga_len); + // make sure that string is delimited + GPGGA_sentence[gga_len+1] = '\0'; + //printf(GPGGA_sentence); + + // extract time and gps coordinates from GPGGA sentence + // Sample: GPGGA,193630.000,4846.8772,N,00912.2288,E,1,5,1.57,131.5,M,48.0,M,, + comma_count = 0; + //printf("%s\n", GPGGA_sentence); + start_ptr = strchr(GPGGA_sentence,',') + 1; + end_ptr=start_ptr; + pthread_mutex_lock(mutex); + while (end_ptr != NULL && start_ptr != NULL) + { + end_ptr = strchr(start_ptr,','); + if (end_ptr != NULL) + { + nofchar = (end_ptr - start_ptr); + if (nofchar < LEN_GPS_INFO_BUFFER) + end_ptr[0] = '\0'; + else + break; + + int temp = nofchar; + if (comma_count == 0) + { + nofchar = temp < LEN_GPS_INFO_BUFFER ? + temp : LEN_GPS_INFO_BUFFER - 1; + strncpy(timestamp,start_ptr,nofchar); + timestamp[nofchar] = '\0'; + + } + if (comma_count == 1) + { + nofchar = temp < LEN_GPS_INFO_BUFFER - 2 ? + temp : LEN_GPS_INFO_BUFFER - 2; + strncpy(latitude,start_ptr,nofchar); + latitude[nofchar] = '\0'; + + } + if (comma_count == 2) + { + nofchar = strlen(latitude); + strncpy(latitude+nofchar,start_ptr,1); + latitude[nofchar+1] = '\0'; + + } + if (comma_count == 3) + { + nofchar = temp < LEN_GPS_INFO_BUFFER - 2 ? + temp : LEN_GPS_INFO_BUFFER - 2; + strncpy(longitude,start_ptr,nofchar); + longitude[nofchar] = '\0'; + + } + if (comma_count == 4) + { + nofchar = strlen(longitude); + strncpy(longitude+nofchar,start_ptr,1); + longitude[nofchar+1] = '\0'; + // all data extracted => bail out + end_ptr = NULL; + start_ptr = NULL; + } + start_ptr = end_ptr+1; + + comma_count++; + } + } + pthread_mutex_unlock(mutex); + } + } + + } + } + pthread_yield(); + } + free(gpsDevice); + close(gps_fd); +} + +void gps_MT3339::stop() { + stop_reading = true; +} + + diff --git a/examples/raspi/rf95/shared/gpsMT3339.h b/examples/raspi/rf95/shared/gpsMT3339.h new file mode 100644 index 0000000..30f4804 --- /dev/null +++ b/examples/raspi/rf95/shared/gpsMT3339.h @@ -0,0 +1,52 @@ +// -*- mode: C++ -*- +/* + * gpsMT3339.h + * + * Created on: Jun 11, 2020 + * Author: Tilman Glötzner + */ + +#ifndef GPSMT3339_H_ +#define GPSMT3339_H_ + +#include +#include +#include +#include +#include +#include + +#define LEN_GPS_READ_BUFFER 255 +#define LEN_GPS_INFO_BUFFER 20 +#define LEN_GPS_SENTENCE_BUFFER 100 + +// reading the serial port may eventually lock up. +// MAX_EMPTY_READ determines how many consecutive reads yielding no results, i.e. +// nofCharRead = 0, may occur before the program tries to reopen the gps device +#define MAX_EMPTY_READ 15 +#define GPS_DEVICE "/dev/ttyS0" + + + +class gps_MT3339 { +public: + gps_MT3339(const char* SerialDevice,pthread_mutex_t* mutex); + virtual ~gps_MT3339(); + void* read_gps(void* ptr); + char* getLatitude(); + char* getLongitude(); + char* getTimestamp(); + void stop(); + + +private: + char *strnstr(const char *haystack, const char *needle, size_t len); + char longitude[LEN_GPS_INFO_BUFFER]="NoInit"; + char latitude[LEN_GPS_INFO_BUFFER]="NoInit"; + char timestamp[LEN_GPS_INFO_BUFFER]="NoInit"; + bool stop_reading = false; + char *gpsDevice; + pthread_mutex_t* mutex; +}; + +#endif /* GPSMT3339_H_ */ diff --git a/examples/raspi/rf95/shared/help_functions.cpp b/examples/raspi/rf95/shared/help_functions.cpp new file mode 100644 index 0000000..d91887d --- /dev/null +++ b/examples/raspi/rf95/shared/help_functions.cpp @@ -0,0 +1,56 @@ +// -*- mode: C++ -*- +/* + * help_functions.cpp + * + * Created on: Jun 11, 2020 + * Author: Tilman Glötzner + */ +// help functions +#include +#include +#include +#include +#include + +void print_scheduler(void) +{ + int schedType; + + schedType = sched_getscheduler(getpid()); + + switch(schedType) + { + case SCHED_FIFO: + printf("Scheduling Policy is SCHED_FIFO\n"); + break; + case SCHED_OTHER: + printf("Scheduling Policy is SCHED_OTHER\n"); + break; + case SCHED_RR: + printf("Scheduling Policy is SCHED_RR\n"); + break; + default: + printf("Scheduling Policy is UNKNOWN\n"); + } +} + +void print_scope(void) +{ + pthread_attr_t tattr; + int scope; + int ret; + + /* get scope of thread */ + ret = pthread_attr_getscope(&tattr, &scope); + switch(scope) + { + case PTHREAD_SCOPE_SYSTEM: + printf("Scheduling Scope is SYSTEM\n"); + break; + case PTHREAD_SCOPE_PROCESS: + printf("Scheduling Scope is PROCESS\n"); + break; + default: + printf("Scheduling Scope is UNKNOWN\n"); + } +} diff --git a/examples/raspi/rf95/shared/help_functions.h b/examples/raspi/rf95/shared/help_functions.h new file mode 100644 index 0000000..c01a02f --- /dev/null +++ b/examples/raspi/rf95/shared/help_functions.h @@ -0,0 +1,13 @@ +// -*- mode: C++ -*- +/* + * help_functions.h + * + * Created on: Jun 11, 2020 + * Author: Tilman Glötzner + */ + +#ifndef HELP_FUNCTIONS_H_ +#define HELP_FUNCTIONS_H_ +void print_scheduler(void); +void print_scope(void); +#endif diff --git a/examples/rf22/rf22_client/rf22_client.ino b/examples/rf22/rf22_client/rf22_client.ino new file mode 100644 index 0000000..fdfed3b --- /dev/null +++ b/examples/rf22/rf22_client/rf22_client.ino @@ -0,0 +1,56 @@ +// rf22_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_RF22 class. RH_RF22 class does not provide for addressing or +// reliability, so you should only use RH_RF22 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf22_server +// Tested on Duemilanove, Uno with Sparkfun RFM22 wireless shield +// Tested on Flymaple with sparkfun RFM22 wireless shield +// Tested on ChiKit Uno32 with sparkfun RFM22 wireless shield + +#include +#include + +// Singleton instance of the radio driver +RH_RF22 rf22; + +void setup() +{ + Serial.begin(9600); + if (!rf22.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 +} + +void loop() +{ + Serial.println("Sending to rf22_server"); + // Send a message to rf22_server + uint8_t data[] = "Hello World!"; + rf22.send(data, sizeof(data)); + + rf22.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_RF22_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (rf22.waitAvailableTimeout(500)) + { + // Should be a reply message for us now + if (rf22.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is rf22_server running?"); + } + delay(400); +} diff --git a/examples/rf22/rf22_cw/rf22_cw.ino b/examples/rf22/rf22_cw/rf22_cw.ino new file mode 100644 index 0000000..69a4d40 --- /dev/null +++ b/examples/rf22/rf22_cw/rf22_cw.ino @@ -0,0 +1,31 @@ +// rf22_cw.ino +// -*- mode: C++ -*- +// Example sketch showing how to emit a continuous carrier wave (CW) +// for test purposes +// Tested on Duemilanove, Uno with Sparkfun RFM22 wireless shieldg + +#include +#include + +// Singleton instance of the radio driver +RH_RF22 rf22; + +void setup() +{ + Serial.begin(9600); + if (!rf22.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 + + // CW mode: + rf22.setModemConfig(RH_RF22::UnmodulatedCarrier); + +} + +void loop() +{ + rf22.setModeTx(); + delay(1000); + rf22.setModeIdle(); + delay(1000); +} diff --git a/examples/rf22/rf22_mesh_client/rf22_mesh_client.ino b/examples/rf22/rf22_mesh_client/rf22_mesh_client.ino new file mode 100644 index 0000000..dd8e472 --- /dev/null +++ b/examples/rf22/rf22_mesh_client/rf22_mesh_client.ino @@ -0,0 +1,68 @@ +// rf22_mesh_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging client +// with the RHMesh class. +// It is designed to work with the other examples rf22_mesh_server* +// Hint: you can simulate other network topologies by setting the +// RH_TEST_NETWORK define in RHRouter.h + +// Mesh has much greater memory requirements, and you may need to limit the +// max message length to prevent wierd crashes +#define RH_MESH_MAX_MESSAGE_LEN 50 + +#include +#include +#include + +// In this small artifical network of 4 nodes, +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +// Singleton instance of the radio driver +RH_RF22 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHMesh manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_MESH_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to manager_mesh_server3"); + + // Send a message to a rf22_mesh_server + // A route to the destination will be automatically discovered. + if (manager.sendtoWait(data, sizeof(data), SERVER3_ADDRESS) == RH_ROUTER_ERROR_NONE) + { + // It has been reliably delivered to the next node. + // Now wait for a reply from the ultimate server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 3000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is rf22_mesh_server1, rf22_mesh_server2 and rf22_mesh_server3 running?"); + } + } + else + Serial.println("sendtoWait failed. Are the intermediate mesh servers running?"); +} + diff --git a/examples/rf22/rf22_mesh_server1/rf22_mesh_server1.ino b/examples/rf22/rf22_mesh_server1/rf22_mesh_server1.ino new file mode 100644 index 0000000..90ded81 --- /dev/null +++ b/examples/rf22/rf22_mesh_server1/rf22_mesh_server1.ino @@ -0,0 +1,56 @@ +// rf22_mesh_server1.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHMesh class. +// It is designed to work with the other examples rf22_mesh_* +// Hint: you can simulate other network topologies by setting the +// RH_TEST_NETWORK define in RHRouter.h + +// Mesh has much greater memory requirements, and you may need to limit the +// max message length to prevent wierd crashes +#define RH_MESH_MAX_MESSAGE_LEN 50 + +#include +#include +#include + +// In this small artifical network of 4 nodes, +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +// Singleton instance of the radio driver +RH_RF22 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHMesh manager(driver, SERVER1_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("RF22 init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 +} + +uint8_t data[] = "And hello back to you from server1"; +// Dont put this on the stack: +uint8_t buf[RH_MESH_MAX_MESSAGE_LEN]; +void loop() +{ + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); + } +} + diff --git a/examples/rf22/rf22_mesh_server2/rf22_mesh_server2.ino b/examples/rf22/rf22_mesh_server2/rf22_mesh_server2.ino new file mode 100644 index 0000000..4132000 --- /dev/null +++ b/examples/rf22/rf22_mesh_server2/rf22_mesh_server2.ino @@ -0,0 +1,56 @@ +// rf22_mesh_server1.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHMesh class. +// It is designed to work with the other examples rf22_mesh_* +// Hint: you can simulate other network topologies by setting the +// RH_TEST_NETWORK define in RHRouter.h + +// Mesh has much greater memory requirements, and you may need to limit the +// max message length to prevent wierd crashes +#define RH_MESH_MAX_MESSAGE_LEN 50 + +#include +#include +#include + +// In this small artifical network of 4 nodes, +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +// Singleton instance of the radio driver +RH_RF22 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHMesh manager(driver, SERVER2_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("RF22 init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 +} + +uint8_t data[] = "And hello back to you from server2"; +// Dont put this on the stack: +uint8_t buf[RH_MESH_MAX_MESSAGE_LEN]; +void loop() +{ + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); + } +} + diff --git a/examples/rf22/rf22_mesh_server3/rf22_mesh_server3.ino b/examples/rf22/rf22_mesh_server3/rf22_mesh_server3.ino new file mode 100644 index 0000000..7da1912 --- /dev/null +++ b/examples/rf22/rf22_mesh_server3/rf22_mesh_server3.ino @@ -0,0 +1,56 @@ +// rf22_mesh_server3.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHMesh class. +// It is designed to work with the other examples rf22_mesh_* +// Hint: you can simulate other network topologies by setting the +// RH_TEST_NETWORK define in RHRouter.h + +// Mesh has much greater memory requirements, and you may need to limit the +// max message length to prevent wierd crashes +#define RH_MESH_MAX_MESSAGE_LEN 50 + +#include +#include +#include + +// In this small artifical network of 4 nodes, +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +// Singleton instance of the radio driver +RH_RF22 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHMesh manager(driver, SERVER3_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("RF22 init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 +} + +uint8_t data[] = "And hello back to you from server3"; +// Dont put this on the stack: +uint8_t buf[RH_MESH_MAX_MESSAGE_LEN]; +void loop() +{ + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); + } +} + diff --git a/examples/rf22/rf22_reliable_datagram_client/rf22_reliable_datagram_client.ino b/examples/rf22/rf22_reliable_datagram_client/rf22_reliable_datagram_client.ino new file mode 100644 index 0000000..96f3cd3 --- /dev/null +++ b/examples/rf22/rf22_reliable_datagram_client/rf22_reliable_datagram_client.ino @@ -0,0 +1,61 @@ +// rf22_reliable_datagram_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging client +// with the RHReliableDatagram class, using the RH_RF22 driver to control a RF22 radio. +// It is designed to work with the other example rf22_reliable_datagram_server +// Tested on Duemilanove, Uno with Sparkfun RFM22 wireless shield +// Tested on Flymaple with sparkfun RFM22 wireless shield +// Tested on ChiKit Uno32 with sparkfun RFM22 wireless shield + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_RF22 driver; +//RH_RF22 driver(5, 4); // ESP8266 + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_RF22_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to rf22_reliable_datagram_server"); + + // Send a message to manager_server + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is rf22_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); + delay(500); +} diff --git a/examples/rf22/rf22_reliable_datagram_server/rf22_reliable_datagram_server.ino b/examples/rf22/rf22_reliable_datagram_server/rf22_reliable_datagram_server.ino new file mode 100644 index 0000000..897d4a9 --- /dev/null +++ b/examples/rf22/rf22_reliable_datagram_server/rf22_reliable_datagram_server.ino @@ -0,0 +1,56 @@ +// rf22_reliable_datagram_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_RF22 driver to control a RF22 radio. +// It is designed to work with the other example rf22_reliable_datagram_client +// Tested on Duemilanove, Uno with Sparkfun RFM22 wireless shield +// Tested on Flymaple with sparkfun RFM22 wireless shield +// Tested on ChiKit Uno32 with sparkfun RFM22 wireless shield + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_RF22 driver; +//RH_RF22 driver(5, 4); // ESP8266 + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, SERVER_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 +} + +uint8_t data[] = "And hello back to you"; +// Dont put this on the stack: +uint8_t buf[RH_RF22_MAX_MESSAGE_LEN]; + +void loop() +{ + if (manager.available()) + { + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); + } + } +} + diff --git a/examples/rf22/rf22_router_client/rf22_router_client.ino b/examples/rf22/rf22_router_client/rf22_router_client.ino new file mode 100644 index 0000000..f9959c2 --- /dev/null +++ b/examples/rf22/rf22_router_client/rf22_router_client.ino @@ -0,0 +1,72 @@ +// rf22_router_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging client +// with the RHRouter class. +// It is designed to work with the other examples rf22_router_server* + +#include +#include +#include + +// In this small artifical network of 4 nodes, +// messages are routed via intermediate nodes to their destination +// node. All nodes can act as routers +// CLIENT_ADDRESS <-> SERVER1_ADDRESS <-> SERVER2_ADDRESS<->SERVER3_ADDRESS +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +// Singleton instance of the radio driver +RH_RF22 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHRouter manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 + + // Manually define the routes for this network + manager.addRouteTo(SERVER1_ADDRESS, SERVER1_ADDRESS); + manager.addRouteTo(SERVER2_ADDRESS, SERVER2_ADDRESS); + manager.addRouteTo(SERVER3_ADDRESS, SERVER3_ADDRESS); +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_ROUTER_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to rf22_router_server3"); + + // Send a message to a rf22_router_server + // It will be routed by the intermediate + // nodes to the destination node, accorinding to the + // routing tables in each node + if (manager.sendtoWait(data, sizeof(data), SERVER3_ADDRESS) == RH_ROUTER_ERROR_NONE) + { + // It has been reliably delivered to the next node. + // Now wait for a reply from the ultimate server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 3000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is rf22_router_server1, rf22_router_server2 and rf22_router_server3 running?"); + } + } + else + Serial.println("sendtoWait failed. Are the intermediate router servers running?"); +} + diff --git a/examples/rf22/rf22_router_server1/rf22_router_server1.ino b/examples/rf22/rf22_router_server1/rf22_router_server1.ino new file mode 100644 index 0000000..e7e4ffd --- /dev/null +++ b/examples/rf22/rf22_router_server1/rf22_router_server1.ino @@ -0,0 +1,59 @@ +// rf22_router_server1.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHRouter class. +// It is designed to work with the other example rf22_router_client + +#include +#include +#include + +// In this small artifical network of 4 nodes, +// messages are routed via intermediate nodes to their destination +// node. All nodes can act as routers +// CLIENT_ADDRESS <-> SERVER1_ADDRESS <-> SERVER2_ADDRESS<->SERVER3_ADDRESS +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +// Singleton instance of the radio +RH_RF22 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHRouter manager(driver, SERVER1_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 + + // Manually define the routes for this network + manager.addRouteTo(CLIENT_ADDRESS, CLIENT_ADDRESS); + manager.addRouteTo(SERVER2_ADDRESS, SERVER2_ADDRESS); + manager.addRouteTo(SERVER3_ADDRESS, SERVER2_ADDRESS); +} + +uint8_t data[] = "And hello back to you from server1"; +// Dont put this on the stack: +uint8_t buf[RH_ROUTER_MAX_MESSAGE_LEN]; + +void loop() +{ + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); + } +} + diff --git a/examples/rf22/rf22_router_server2/rf22_router_server2.ino b/examples/rf22/rf22_router_server2/rf22_router_server2.ino new file mode 100644 index 0000000..c9e86cb --- /dev/null +++ b/examples/rf22/rf22_router_server2/rf22_router_server2.ino @@ -0,0 +1,59 @@ +// rf22_router_server2.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHRouter class. +// It is designed to work with the other example rf22_router_client + +#include +#include +#include + +// In this small artifical network of 4 nodes, +// messages are routed via intermediate nodes to their destination +// node. All nodes can act as routers +// CLIENT_ADDRESS <-> SERVER1_ADDRESS <-> SERVER2_ADDRESS<->SERVER3_ADDRESS +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +// Singleton instance of the radio +RH_RF22 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHRouter manager(driver, SERVER2_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 + + // Manually define the routes for this network + manager.addRouteTo(CLIENT_ADDRESS, CLIENT_ADDRESS); + manager.addRouteTo(SERVER2_ADDRESS, SERVER2_ADDRESS); + manager.addRouteTo(SERVER3_ADDRESS, SERVER2_ADDRESS); +} + +uint8_t data[] = "And hello back to you from server2"; +// Dont put this on the stack: +uint8_t buf[RH_ROUTER_MAX_MESSAGE_LEN]; + +void loop() +{ + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); + } +} + diff --git a/examples/rf22/rf22_router_server3/rf22_router_server3.ino b/examples/rf22/rf22_router_server3/rf22_router_server3.ino new file mode 100644 index 0000000..9a9f1e5 --- /dev/null +++ b/examples/rf22/rf22_router_server3/rf22_router_server3.ino @@ -0,0 +1,59 @@ +// rf22_router_server3.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, routed reliable messaging server +// with the RHRouter class. +// It is designed to work with the other example rf22_router_client + +#include +#include +#include + +// In this small artifical network of 4 nodes, +// messages are routed via intermediate nodes to their destination +// node. All nodes can act as routers +// CLIENT_ADDRESS <-> SERVER1_ADDRESS <-> SERVER2_ADDRESS<->SERVER3_ADDRESS +#define CLIENT_ADDRESS 1 +#define SERVER1_ADDRESS 2 +#define SERVER2_ADDRESS 3 +#define SERVER3_ADDRESS 4 + +// Singleton instance of the radio +RH_RF22 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHRouter manager(driver, SERVER3_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 + + // Manually define the routes for this network + manager.addRouteTo(CLIENT_ADDRESS, CLIENT_ADDRESS); + manager.addRouteTo(SERVER2_ADDRESS, SERVER2_ADDRESS); + manager.addRouteTo(SERVER3_ADDRESS, SERVER2_ADDRESS); +} + +uint8_t data[] = "And hello back to you from server3"; +// Dont put this on the stack: +uint8_t buf[RH_ROUTER_MAX_MESSAGE_LEN]; + +void loop() +{ + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE) + Serial.println("sendtoWait failed"); + } +} + diff --git a/examples/rf22/rf22_router_test/rf22_router_test.ino b/examples/rf22/rf22_router_test/rf22_router_test.ino new file mode 100644 index 0000000..f0ffb45 --- /dev/null +++ b/examples/rf22/rf22_router_test/rf22_router_test.ino @@ -0,0 +1,102 @@ +// rf22_router_test.pde +// -*- mode: C++ -*- +// +// Test code used during library development, showing how +// to do various things, and how to call various functions + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define ROUTER_ADDRESS 2 +#define SERVER_ADDRESS 3 + +// Singleton instance of the radio driver +RH_RF22 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHRouter manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("RF22 init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 +} + +void test_routes() +{ + manager.clearRoutingTable(); +// manager.printRoutingTable(); + manager.addRouteTo(1, 101); + manager.addRouteTo(2, 102); + manager.addRouteTo(3, 103); + RHRouter::RoutingTableEntry* e; + e = manager.getRouteTo(0); + if (e) // Should fail + Serial.println("getRouteTo 0 failed"); + + e = manager.getRouteTo(1); + if (!e) + Serial.println("getRouteTo 1 failed"); + if (e->dest != 1) + Serial.println("getRouteTo 2 failed"); + if (e->next_hop != 101) + Serial.println("getRouteTo 3 failed"); + if (e->state != RHRouter::Valid) + Serial.println("getRouteTo 4 failed"); + + e = manager.getRouteTo(2); + if (!e) + Serial.println("getRouteTo 5 failed"); + if (e->dest != 2) + Serial.println("getRouteTo 6 failed"); + if (e->next_hop != 102) + Serial.println("getRouteTo 7 failed"); + if (e->state != RHRouter::Valid) + Serial.println("getRouteTo 8 failed"); + + if (!manager.deleteRouteTo(1)) + Serial.println("deleteRouteTo 1 failed"); + // Route to 1 should now be gone + e = manager.getRouteTo(1); + if (e) + Serial.println("deleteRouteTo 2 failed"); + + Serial.println("-------------------"); + +// manager.printRoutingTable(); + delay(500); + +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +//uint8_t buf[RH_RF22_MAX_MESSAGE_LEN]; + +void test_tx() +{ + manager.addRouteTo(SERVER_ADDRESS, ROUTER_ADDRESS); + uint8_t errorcode; + errorcode = manager.sendtoWait(data, sizeof(data), 100); // Should fail with no route + if (errorcode != RH_ROUTER_ERROR_NO_ROUTE) + Serial.println("sendtoWait 1 failed"); + errorcode = manager.sendtoWait(data, 255, 10); // Should fail too big + if (errorcode != RH_ROUTER_ERROR_INVALID_LENGTH) + Serial.println("sendtoWait 2 failed"); + errorcode = manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS); // Should fail after timeouts to 110 + if (errorcode != RH_ROUTER_ERROR_UNABLE_TO_DELIVER) + Serial.println("sendtoWait 3 failed"); + Serial.println("-------------------"); + delay(500); +} + +void loop() +{ +// test_routes(); + test_tx(); +} + + diff --git a/examples/rf22/rf22_server/rf22_server.ino b/examples/rf22/rf22_server/rf22_server.ino new file mode 100644 index 0000000..6d09a2f --- /dev/null +++ b/examples/rf22/rf22_server/rf22_server.ino @@ -0,0 +1,53 @@ +// rf22_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_RF22 class. RH_RF22 class does not provide for addressing or +// reliability, so you should only use RH_RF22 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf22_client +// Tested on Duemilanove, Uno with Sparkfun RFM22 wireless shield +// Tested on Flymaple with sparkfun RFM22 wireless shield +// Tested on ChiKit Uno32 with sparkfun RFM22 wireless shield + +#include +#include + +// Singleton instance of the radio driver +RH_RF22 rf22; + +void setup() +{ + Serial.begin(9600); + if (!rf22.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 +} + +void loop() +{ + if (rf22.available()) + { + // Should be a message for us now + uint8_t buf[RH_RF22_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (rf22.recv(buf, &len)) + { +// RF22::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(rf22.lastRssi(), DEC); + + // Send a reply + uint8_t data[] = "And hello back to you"; + rf22.send(data, sizeof(data)); + rf22.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} + diff --git a/examples/rf24/rf24_client/rf24_client.ino b/examples/rf24/rf24_client/rf24_client.ino new file mode 100644 index 0000000..a623c1a --- /dev/null +++ b/examples/rf24/rf24_client/rf24_client.ino @@ -0,0 +1,62 @@ +// rf24_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_RF24 class. RH_RF24 class does not provide for addressing or +// reliability, so you should only use RH_RF24 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf24_server. +// Tested on Anarduino Mini http://www.anarduino.com/mini/ with RFM24W and RFM26W + +#include +#include + +// Singleton instance of the radio driver +RH_RF24 rf24; + +void setup() +{ + Serial.begin(9600); + if (!rf24.init()) + Serial.println("init failed"); + // The default radio config is for 30MHz Xtal, 434MHz base freq 2GFSK 5kbps 10kHz deviation + // power setting 0x10 + // If you want a different frequency mand or modulation scheme, you must generate a new + // radio config file as per the RH_RF24 module documentation and recompile + // You can change a few other things programatically: + //rf24.setFrequency(435.0); // Only within the same frequency band + //rf24.setTxPower(0x7f); +} + + +void loop() +{ + Serial.println("Sending to rf24_server"); + // Send a message to rf24_server + uint8_t data[] = "Hello World!"; + rf24.send(data, sizeof(data)); + + rf24.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_RF24_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (rf24.waitAvailableTimeout(500)) + { + // Should be a reply message for us now + if (rf24.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is rf24_server running?"); + } + delay(400); +} + diff --git a/examples/rf24/rf24_lowpower_client/rf24_lowpower_client.ino b/examples/rf24/rf24_lowpower_client/rf24_lowpower_client.ino new file mode 100644 index 0000000..718b260 --- /dev/null +++ b/examples/rf24/rf24_lowpower_client/rf24_lowpower_client.ino @@ -0,0 +1,80 @@ +// rf24_lowpower_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple message transmitter +// which sleeps between transmissions (every 8 secs) to reduce power consumnption +// It uses the watchdog timer and the CPU sleep mode and the radio sleep mode +// to reduce quiescent power to 1.7mA +// Tested on Anarduino Mini http://www.anarduino.com/mini/ with RFM24W and RFM26W + +#include +#include +#include +#include + +// Singleton instance of the radio driver +RH_RF24 rf24; + +// Watchdog timer interrupt handler +ISR(WDT_vect) +{ + // Dont need to do anything, just override the default vector which causes a reset +} + +// Go into sleep mode until WDT interrupt +void sleep() +{ + // Select the sleep mode we want. This is the lowest power + // that can wake with WDT interrupt + set_sleep_mode(SLEEP_MODE_PWR_DOWN); + sleep_mode(); // Sleep here and wake on WDT interrupt every 8 secs +} + +void setup() +{ + Serial.begin(9600); + if (!rf24.init()) + Serial.println("init failed"); + // The default radio config is for 30MHz Xtal, 434MHz base freq 2GFSK 5kbps 10kHz deviation + // power setting 0x10 + // If you want a different frequency mand or modulation scheme, you must generate a new + // radio config file as per the RH_RF24 module documentation and recompile + // You can change a few other things programatically: + //rf24.setFrequency(435.0); // Only within the same frequency band + //rf24.setTxPower(0x7f); + + // Set the watchdog timer to interrupt every 8 secs + noInterrupts(); + // Set the watchdog reset bit in the MCU status register to 0. + MCUSR &= ~(1< +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_RF24 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // The default radio config is for 30MHz Xtal, 434MHz base freq 2GFSK 5kbps 10kHz deviation + // power setting 0x10 + // If you want a different frequency mand or modulation scheme, you must generate a new + // radio config file as per the RH_RF24 module documentation and recompile + // You can change a few other things programatically: + //driver.setFrequency(435.0); // Only within the same frequency band + //driver.setTxPower(0x7f); +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_RF24_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to rf24_reliable_datagram_server"); + + // Send a message to manager_server + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is rf24_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); + delay(500); +} + diff --git a/examples/rf24/rf24_reliable_datagram_server/rf24_reliable_datagram_server.ino b/examples/rf24/rf24_reliable_datagram_server/rf24_reliable_datagram_server.ino new file mode 100644 index 0000000..50f0522 --- /dev/null +++ b/examples/rf24/rf24_reliable_datagram_server/rf24_reliable_datagram_server.ino @@ -0,0 +1,59 @@ +// rf24_reliable_datagram_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_RF24 driver to control a RF24 radio. +// It is designed to work with the other example rf24_reliable_datagram_client +// Tested on Anarduino Mini http://www.anarduino.com/mini/ with RFM24W and RFM26W + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_RF24 driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, SERVER_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // The default radio config is for 30MHz Xtal, 434MHz base freq 2GFSK 5kbps 10kHz deviation + // power setting 0x10 + // If you want a different frequency mand or modulation scheme, you must generate a new + // radio config file as per the RH_RF24 module documentation and recompile + // You can change a few other things programatically: + //driver.setFrequency(435.0); // Only within the same frequency band + //driver.setTxPower(0x7f); +} + +uint8_t data[] = "And hello back to you"; +// Dont put this on the stack: +uint8_t buf[RH_RF24_MAX_MESSAGE_LEN]; + +void loop() +{ + if (manager.available()) + { + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); + } + } +} + diff --git a/examples/rf24/rf24_server/rf24_server.ino b/examples/rf24/rf24_server/rf24_server.ino new file mode 100644 index 0000000..ad03c56 --- /dev/null +++ b/examples/rf24/rf24_server/rf24_server.ino @@ -0,0 +1,57 @@ +// rf24_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_RF24 class. RH_RF24 class does not provide for addressing or +// reliability, so you should only use RH_RF24 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf24_client +// Tested on Anarduino Mini http://www.anarduino.com/mini/ with RFM24W and RFM26W + +#include +#include + +// Singleton instance of the radio driver +RH_RF24 rf24; + +void setup() +{ + Serial.begin(9600); + if (!rf24.init()) + Serial.println("init failed"); + // The default radio config is for 30MHz Xtal, 434MHz base freq 2GFSK 5kbps 10kHz deviation + // power setting 0x10 + // If you want a different frequency mand or modulation scheme, you must generate a new + // radio config file as per the RH_RF24 module documentation and recompile + // You can change a few other things programatically: + //rf24.setFrequency(435.0); // Only within the same frequency band + //rf24.setTxPower(0x7f); +} + +void loop() +{ + if (rf24.available()) + { + // Should be a message for us now + uint8_t buf[RH_RF24_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (rf24.recv(buf, &len)) + { +// RF24::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println((uint8_t)rf24.lastRssi(), DEC); + + // Send a reply + uint8_t data[] = "And hello back to you"; + rf24.send(data, sizeof(data)); + rf24.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} + diff --git a/examples/rf69/rf69_client/rf69_client.ino b/examples/rf69/rf69_client/rf69_client.ino new file mode 100644 index 0000000..64aa927 --- /dev/null +++ b/examples/rf69/rf69_client/rf69_client.ino @@ -0,0 +1,77 @@ +// rf69_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_RF69 class. RH_RF69 class does not provide for addressing or +// reliability, so you should only use RH_RF69 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf69_server. +// Demonstrates the use of AES encryption, setting the frequency and modem +// configuration +// Tested on Moteino with RFM69 http://lowpowerlab.com/moteino/ +// Tested on miniWireless with RFM69 www.anarduino.com/miniwireless +// Tested on Teensy 3.1 with RF69 on PJRC breakout board + +#include +#include + +// Singleton instance of the radio driver +RH_RF69 rf69; +//RH_RF69 rf69(15, 16); // For RF69 on PJRC breakout board with Teensy 3.1 +//RH_RF69 rf69(4, 2); // For MoteinoMEGA https://lowpowerlab.com/shop/moteinomega +//RH_RF69 rf69(8, 7); // Adafruit Feather 32u4 + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; + if (!rf69.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM (for low power module) + // No encryption + if (!rf69.setFrequency(433.0)) + Serial.println("setFrequency failed"); + + // If you are using a high power RF69 eg RFM69HW, you *must* set a Tx power with the + // ishighpowermodule flag set like this: + //rf69.setTxPower(14, true); + + // The encryption key has to be the same as the one in the server + uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + rf69.setEncryptionKey(key); +} + + +void loop() +{ + Serial.println("Sending to rf69_server"); + // Send a message to rf69_server + uint8_t data[] = "Hello World!"; + rf69.send(data, sizeof(data)); + + rf69.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_RF69_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (rf69.waitAvailableTimeout(500)) + { + // Should be a reply message for us now + if (rf69.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is rf69_server running?"); + } + delay(400); +} + diff --git a/examples/rf69/rf69_reliable_datagram_client/rf69_reliable_datagram_client.ino b/examples/rf69/rf69_reliable_datagram_client/rf69_reliable_datagram_client.ino new file mode 100644 index 0000000..7dba21d --- /dev/null +++ b/examples/rf69/rf69_reliable_datagram_client/rf69_reliable_datagram_client.ino @@ -0,0 +1,70 @@ +// rf69_reliable_datagram_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging client +// with the RHReliableDatagram class, using the RH_RF69 driver to control a RF69 radio. +// It is designed to work with the other example rf69_reliable_datagram_server +// Tested on Moteino with RFM69 http://lowpowerlab.com/moteino/ +// Tested on miniWireless with RFM69 www.anarduino.com/miniwireless +// Tested on Teensy 3.1 with RF69 on PJRC breakout board + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_RF69 driver; +//RH_RF69 driver(15, 16); // For RF69 on PJRC breakout board with Teensy 3.1 +//RH_RF69 driver(4, 2); // For MoteinoMEGA https://lowpowerlab.com/shop/moteinomega +//RH_RF69 driver(8, 7); // Adafruit Feather 32u4 + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM (for low power module) + + // If you are using a high power RF69 eg RFM69HW, you *must* set a Tx power with the + // ishighpowermodule flag set like this: + //driver.setTxPower(14, true); +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_RF69_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to rf69_reliable_datagram_server"); + + // Send a message to manager_server + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is rf69_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); + delay(500); +} + diff --git a/examples/rf69/rf69_reliable_datagram_server/rf69_reliable_datagram_server.ino b/examples/rf69/rf69_reliable_datagram_server/rf69_reliable_datagram_server.ino new file mode 100644 index 0000000..dd9654e --- /dev/null +++ b/examples/rf69/rf69_reliable_datagram_server/rf69_reliable_datagram_server.ino @@ -0,0 +1,64 @@ +// rf69_reliable_datagram_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_RF69 driver to control a RF69 radio. +// It is designed to work with the other example rf69_reliable_datagram_client +// Tested on Moteino with RFM69 http://lowpowerlab.com/moteino/ +// Tested on miniWireless with RFM69 www.anarduino.com/miniwireless +// Tested on Teensy 3.1 with RF69 on PJRC breakout board + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_RF69 driver; +//RH_RF69 driver(15, 16); // For RF69 on PJRC breakout board with Teensy 3.1 +//RH_RF69 rf69(4, 2); // For MoteinoMEGA https://lowpowerlab.com/shop/moteinomega +//RH_RF69 driver(8, 7); // Adafruit Feather 32u4 + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, SERVER_ADDRESS); + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM (for low power module) + + // If you are using a high power RF69 eg RFM69HW, you *must* set a Tx power with the + // ishighpowermodule flag set like this: + //driver.setTxPower(14, true); +} + +uint8_t data[] = "And hello back to you"; +// Dont put this on the stack: +uint8_t buf[RH_RF69_MAX_MESSAGE_LEN]; + +void loop() +{ + if (manager.available()) + { + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); + } + } +} + diff --git a/examples/rf69/rf69_server/rf69_server.ino b/examples/rf69/rf69_server/rf69_server.ino new file mode 100644 index 0000000..726f03d --- /dev/null +++ b/examples/rf69/rf69_server/rf69_server.ino @@ -0,0 +1,81 @@ +// rf69_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_RF69 class. RH_RF69 class does not provide for addressing or +// reliability, so you should only use RH_RF69 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf69_client +// Demonstrates the use of AES encryption, setting the frequency and modem +// configuration. +// Tested on Moteino with RFM69 http://lowpowerlab.com/moteino/ +// Tested on miniWireless with RFM69 www.anarduino.com/miniwireless +// Tested on Teensy 3.1 with RF69 on PJRC breakout board + +#include +#include + +// Singleton instance of the radio driver +RH_RF69 rf69; +//RH_RF69 rf69(15, 16); // For RF69 on PJRC breakout board with Teensy 3.1 +//RH_RF69 rf69(4, 2); // For MoteinoMEGA https://lowpowerlab.com/shop/moteinomega +//RH_RF69 rf69(8, 7); // Adafruit Feather 32u4 + +void setup() +{ + Serial.begin(9600); + while (!Serial) + ; + if (!rf69.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM (for low power module) + // No encryption + if (!rf69.setFrequency(433.0)) + Serial.println("setFrequency failed"); + + // If you are using a high power RF69 eg RFM69HW, you *must* set a Tx power with the + // ishighpowermodule flag set like this: + //rf69.setTxPower(14, true); + + // The encryption key has to be the same as the one in the client + uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + rf69.setEncryptionKey(key); + +#if 0 + // For compat with RFM69 Struct_send + rf69.setModemConfig(RH_RF69::GFSK_Rb250Fd250); + rf69.setPreambleLength(3); + uint8_t syncwords[] = { 0x2d, 0x64 }; + rf69.setSyncWords(syncwords, sizeof(syncwords)); + rf69.setEncryptionKey((uint8_t*)"thisIsEncryptKey"); +#endif +} + +void loop() +{ + if (rf69.available()) + { + // Should be a message for us now + uint8_t buf[RH_RF69_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (rf69.recv(buf, &len)) + { +// RH_RF69::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(rf69.lastRssi(), DEC); + + // Send a reply + uint8_t data[] = "And hello back to you"; + rf69.send(data, sizeof(data)); + rf69.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} + diff --git a/examples/rf95/rf95_client/rf95_client.ino b/examples/rf95/rf95_client/rf95_client.ino new file mode 100644 index 0000000..c2dd867 --- /dev/null +++ b/examples/rf95/rf95_client/rf95_client.ino @@ -0,0 +1,83 @@ +// rf95_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_RF95 class. RH_RF95 class does not provide for addressing or +// reliability, so you should only use RH_RF95 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf95_server +// Tested with Anarduino MiniWirelessLoRa, Rocket Scream Mini Ultra Pro with +// the RFM95W, Adafruit Feather M0 with RFM95 + +#include +#include + +// Singleton instance of the radio driver +RH_RF95 rf95; +//RH_RF95 rf95(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W +//RH_RF95 rf95(8, 3); // Adafruit Feather M0 with RFM95 + +// Need this on Arduino Zero with SerialUSB port (eg RocketScream Mini Ultra Pro) +//#define Serial SerialUSB + +void setup() +{ + // Rocket Scream Mini Ultra Pro with the RFM95W only: + // Ensure serial flash is not interfering with radio communication on SPI bus +// pinMode(4, OUTPUT); +// digitalWrite(4, HIGH); + + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + if (!rf95.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + + // You can change the modulation parameters with eg + // rf95.setModemConfig(RH_RF95::Bw500Cr45Sf128); + + // The default transmitter power is 13dBm, using PA_BOOST. + // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then + // you can set transmitter powers from 2 to 20 dBm: +// rf95.setTxPower(20, false); + // If you are using Modtronix inAir4 or inAir9, or any other module which uses the + // transmitter RFO pins and not the PA_BOOST pins + // then you can configure the power transmitter power for 0 to 15 dBm and with useRFO true. + // Failure to do that will result in extremely low transmit powers. +// rf95.setTxPower(14, true); +} + +void loop() +{ + Serial.println("Sending to rf95_server"); + // Send a message to rf95_server + uint8_t data[] = "Hello World!"; + rf95.send(data, sizeof(data)); + + rf95.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (rf95.waitAvailableTimeout(3000)) + { + // Should be a reply message for us now + if (rf95.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(rf95.lastRssi(), DEC); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is rf95_server running?"); + } + delay(400); +} + + diff --git a/examples/rf95/rf95_encrypted_client/rf95_encrypted_client.ino b/examples/rf95/rf95_encrypted_client/rf95_encrypted_client.ino new file mode 100644 index 0000000..467a81f --- /dev/null +++ b/examples/rf95/rf95_encrypted_client/rf95_encrypted_client.ino @@ -0,0 +1,47 @@ +// LoRa Simple Hello World Client with encrypted communications +// In order for this to compile you MUST uncomment the #define RH_ENABLE_ENCRYPTION_MODULE line +// at the bottom of RadioHead.h, AND you MUST have installed the Crypto directory from arduinolibs: +// http://rweather.github.io/arduinolibs/index.html +// Philippe.Rochat'at'gmail.com +// 06.07.2017 + +#include +#include +#include + +RH_RF95 rf95; // Instanciate a LoRa driver +Speck myCipher; // Instanciate a Speck block ciphering +RHEncryptedDriver myDriver(rf95, myCipher); // Instantiate the driver with those two + +float frequency = 868.0; // Change the frequency here. +unsigned char encryptkey[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; // The very secret key +char HWMessage[] = "Hello World ! I'm happy if you can read me"; +uint8_t HWMessageLen; + +void setup() +{ + HWMessageLen = strlen(HWMessage); + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + Serial.println("LoRa Simple_Encrypted Client"); + if (!rf95.init()) + Serial.println("LoRa init failed"); + // Setup ISM frequency + rf95.setFrequency(frequency); + // Setup Power,dBm + rf95.setTxPower(13); + myCipher.setKey(encryptkey, sizeof(encryptkey)); + Serial.println("Waiting for radio to setup"); + delay(1000); + Serial.println("Setup completed"); +} + +void loop() +{ + uint8_t data[HWMessageLen+1] = {0}; + for(uint8_t i = 0; i<= HWMessageLen; i++) data[i] = (uint8_t)HWMessage[i]; + myDriver.send(data, sizeof(data)); // Send out ID + Sensor data to LoRa gateway + Serial.print("Sent: "); + Serial.println((char *)&data); + delay(4000); +} diff --git a/examples/rf95/rf95_encrypted_server/rf95_encrypted_server.ino b/examples/rf95/rf95_encrypted_server/rf95_encrypted_server.ino new file mode 100644 index 0000000..445b858 --- /dev/null +++ b/examples/rf95/rf95_encrypted_server/rf95_encrypted_server.ino @@ -0,0 +1,48 @@ +// LoRa simple server with encrypted communications +// In order for this to compile you MUST uncomment the #define RH_ENABLE_ENCRYPTION_MODULE line +// at the bottom of RadioHead.h, AND you MUST have installed the Crypto directory from arduinolibs: +// http://rweather.github.io/arduinolibs/index.html +// Philippe.Rochat'at'gmail.com +// 06.07.2017 + +#include +#include +#include + +RH_RF95 rf95; // Instanciate a LoRa driver +Speck myCipher; // Instanciate a Speck block ciphering +RHEncryptedDriver myDriver(rf95, myCipher); // Instantiate the driver with those two + +float frequency = 868.0; // Change the frequency here. +unsigned char encryptkey[16]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; // The very secret key + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + Serial.println("LoRa Simple_Encrypted Server"); + if (!rf95.init()) + Serial.println("LoRa init failed"); + // Setup ISM frequency + rf95.setFrequency(frequency); + // Setup Power,dBm + rf95.setTxPower(13); + myCipher.setKey(encryptkey, 16); + delay(4000); + Serial.println("Setup completed"); +} + +void loop() { + if (myDriver.available()) { + // Should be a message for us now + uint8_t buf[myDriver.maxMessageLength()]; + uint8_t len = sizeof(buf); + if (myDriver.recv(buf, &len)) { + Serial.print("Received: "); + Serial.println((char *)&buf); + } + else + { + Serial.println("recv failed"); + } + } +} diff --git a/examples/rf95/rf95_reliable_datagram_client/rf95_reliable_datagram_client.ino b/examples/rf95/rf95_reliable_datagram_client/rf95_reliable_datagram_client.ino new file mode 100644 index 0000000..c934df6 --- /dev/null +++ b/examples/rf95/rf95_reliable_datagram_client/rf95_reliable_datagram_client.ino @@ -0,0 +1,84 @@ +// rf95_reliable_datagram_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging client +// with the RHReliableDatagram class, using the RH_RF95 driver to control a RF95 radio. +// It is designed to work with the other example rf95_reliable_datagram_server +// Tested with Anarduino MiniWirelessLoRa, Rocket Scream Mini Ultra Pro with the RFM95W + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_RF95 driver; +//RH_RF95 driver(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, CLIENT_ADDRESS); + +// Need this on Arduino Zero with SerialUSB port (eg RocketScream Mini Ultra Pro) +//#define Serial SerialUSB + +void setup() +{ + // Rocket Scream Mini Ultra Pro with the RFM95W only: + // Ensure serial flash is not interfering with radio communication on SPI bus +// pinMode(4, OUTPUT); +// digitalWrite(4, HIGH); + + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + + // The default transmitter power is 13dBm, using PA_BOOST. + // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then + // you can set transmitter powers from 2 to 20 dBm: +// driver.setTxPower(20, false); + // If you are using Modtronix inAir4 or inAir9, or any other module which uses the + // transmitter RFO pins and not the PA_BOOST pins + // then you can configure the power transmitter power for 0 to 15 dBm and with useRFO true. + // Failure to do that will result in extremely low transmit powers. +// driver.setTxPower(14, true); + + // You can optionally require this module to wait until Channel Activity + // Detection shows no activity on the channel before transmitting by setting + // the CAD timeout to non-zero: +// driver.setCADTimeout(10000); +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to rf95_reliable_datagram_server"); + + // Send a message to manager_server + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is rf95_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); + delay(500); +} + diff --git a/examples/rf95/rf95_reliable_datagram_server/rf95_reliable_datagram_server.ino b/examples/rf95/rf95_reliable_datagram_server/rf95_reliable_datagram_server.ino new file mode 100644 index 0000000..8594418 --- /dev/null +++ b/examples/rf95/rf95_reliable_datagram_server/rf95_reliable_datagram_server.ino @@ -0,0 +1,78 @@ +// rf95_reliable_datagram_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_RF95 driver to control a RF95 radio. +// It is designed to work with the other example rf95_reliable_datagram_client +// Tested with Anarduino MiniWirelessLoRa, Rocket Scream Mini Ultra Pro with the RFM95W + +#include +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_RF95 driver; +//RH_RF95 driver(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, SERVER_ADDRESS); + +// Need this on Arduino Zero with SerialUSB port (eg RocketScream Mini Ultra Pro) +//#define Serial SerialUSB + +void setup() +{ + // Rocket Scream Mini Ultra Pro with the RFM95W only: + // Ensure serial flash is not interfering with radio communication on SPI bus +// pinMode(4, OUTPUT); +// digitalWrite(4, HIGH); + + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + + // The default transmitter power is 13dBm, using PA_BOOST. + // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then + // you can set transmitter powers from 2 to 20 dBm: +// driver.setTxPower(20, false); + // If you are using Modtronix inAir4 or inAir9, or any other module which uses the + // transmitter RFO pins and not the PA_BOOST pins + // then you can configure the power transmitter power for 0 to 15 dBm and with useRFO true. + // Failure to do that will result in extremely low transmit powers. +// driver.setTxPower(14, true); + + // You can optionally require this module to wait until Channel Activity + // Detection shows no activity on the channel before transmitting by setting + // the CAD timeout to non-zero: +// driver.setCADTimeout(10000); +} + +uint8_t data[] = "And hello back to you"; +// Dont put this on the stack: +uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + +void loop() +{ + if (manager.available()) + { + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); + } + } +} + diff --git a/examples/rf95/rf95_server/rf95_server.ino b/examples/rf95/rf95_server/rf95_server.ino new file mode 100644 index 0000000..4eabec0 --- /dev/null +++ b/examples/rf95/rf95_server/rf95_server.ino @@ -0,0 +1,82 @@ +// rf95_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_RF95 class. RH_RF95 class does not provide for addressing or +// reliability, so you should only use RH_RF95 if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example rf95_client +// Tested with Anarduino MiniWirelessLoRa, Rocket Scream Mini Ultra Pro with +// the RFM95W, Adafruit Feather M0 with RFM95 + +#include +#include + +// Singleton instance of the radio driver +RH_RF95 rf95; +//RH_RF95 rf95(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W +//RH_RF95 rf95(8, 3); // Adafruit Feather M0 with RFM95 + +// Need this on Arduino Zero with SerialUSB port (eg RocketScream Mini Ultra Pro) +//#define Serial SerialUSB + +int led = 9; + +void setup() +{ + // Rocket Scream Mini Ultra Pro with the RFM95W only: + // Ensure serial flash is not interfering with radio communication on SPI bus +// pinMode(4, OUTPUT); +// digitalWrite(4, HIGH); + + pinMode(led, OUTPUT); + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + if (!rf95.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + + // You can change the modulation parameters with eg + // rf95.setModemConfig(RH_RF95::Bw500Cr45Sf128); + +// The default transmitter power is 13dBm, using PA_BOOST. + // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then + // you can set transmitter powers from 2 to 20 dBm: +// driver.setTxPower(20, false); + // If you are using Modtronix inAir4 or inAir9, or any other module which uses the + // transmitter RFO pins and not the PA_BOOST pins + // then you can configure the power transmitter power for 0 to 15 dBm and with useRFO true. + // Failure to do that will result in extremely low transmit powers. +// driver.setTxPower(14, true); +} + +void loop() +{ + if (rf95.available()) + { + // Should be a message for us now + uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (rf95.recv(buf, &len)) + { + digitalWrite(led, HIGH); +// RH_RF95::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(rf95.lastRssi(), DEC); + + // Send a reply + uint8_t data[] = "And hello back to you"; + rf95.send(data, sizeof(data)); + rf95.waitPacketSent(); + Serial.println("Sent a reply"); + digitalWrite(led, LOW); + } + else + { + Serial.println("recv failed"); + } + } +} + + diff --git a/examples/serial/serial_gateway/serial_gateway.ino b/examples/serial/serial_gateway/serial_gateway.ino new file mode 100644 index 0000000..0c340ba --- /dev/null +++ b/examples/serial/serial_gateway/serial_gateway.ino @@ -0,0 +1,69 @@ +// serial_gateway.pde +// This sketch can be used as a gateway between 2 RadioHead radio networks (connected by a serial line), +// or between +// 1 RadioHead radio network and a Unix host. +// It relays all messages received on the radio driver to the serial port +// (using the RH_Serial driver and protocol). And it relays all messages received on the RH_Serial +// driver port to the radio driver. +// Both drivers operate in promiscuous mode and preserve all headers, so this sketch acts as +// a transparent gateway between RH_Serial and and other RadioHead driver. +// +// By replacing RH_Serial with another RadioHead driver, this can act as a universal gateway +// between any 2 RadioHead networks. +// +// Tested with RF22 driver and RH_Serial driver. The serial port was connected to a Unix host, where the +// serial_reliable_datagram_server.pde was built and running like this: +// tools/simBuild examples/serial/serial_reliable_datagram_server/serial_reliable_datagram_server.pde +// RH_HARDWARESERIAL_DEVICE_NAME=/dev/ttyUSB1 ./serial_reliable_datagram_server +// -*- mode: C++ -*- +// + +#include +#include +#include + +// Singleton instance of the radio driver +// You can use other radio drivers if you want +RH_RF22 radio; +// Singleton instance of the serial driver which relays all messages +// via Serial to another RadioHead RH_Serial driver, perhaps on a Unix host. +// You could use a different Serial if the arduino has more than 1, eg Serial1 on a Mega +RH_Serial serial(Serial); + +void setup() +{ + Serial.begin(9600); + if (!radio.init()) + Serial.println("radio init failed"); + radio.setPromiscuous(true); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 + if (!serial.init()) + Serial.println("serial init failed"); + serial.setPromiscuous(true); +} + +uint8_t buf[RH_SERIAL_MAX_MESSAGE_LEN]; +void loop() +{ + if (radio.available()) + { + uint8_t len = sizeof(buf); + radio.recv(buf, &len); + serial.setHeaderTo(radio.headerTo()); + serial.setHeaderFrom(radio.headerFrom()); + serial.setHeaderId(radio.headerId()); + serial.setHeaderFlags(radio.headerFlags(), 0xFF); // Must clear all flags + serial.send(buf, len); + } + if (serial.available()) + { + uint8_t len = sizeof(buf); + serial.recv(buf, &len); + radio.setHeaderTo(serial.headerTo()); + radio.setHeaderFrom(serial.headerFrom()); + radio.setHeaderId(serial.headerId()); + radio.setHeaderFlags(serial.headerFlags(), 0xFF); // Must clear all flags + radio.send(buf, len); + } +} + diff --git a/examples/serial/serial_reliable_datagram_client/serial_reliable_datagram_client.ino b/examples/serial/serial_reliable_datagram_client/serial_reliable_datagram_client.ino new file mode 100644 index 0000000..306f1d4 --- /dev/null +++ b/examples/serial/serial_reliable_datagram_client/serial_reliable_datagram_client.ino @@ -0,0 +1,83 @@ +// serial_reliable_datagram_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging client +// with the RHReliableDatagram class, using the RH_Serial driver to +// communicate using packets over a serial port (or a radio connected to a +// serial port, such as the 3DR Telemetry radio V1 and others). +// It is designed to work with the other example serial_reliable_datagram_server +// Tested on Arduino Mega and ChipKit Uno32 (normal Arduinos only have one +// serial port and so it not possible to test on them and still have debug +// output) +// Tested with Arduino Mega, Teensy 3.1, Moteino, Arduino Due +// Also works on Linux and OSX. Build and test with: +// tools/simBuild examples/serial/serial_reliable_datagram_client/serial_reliable_datagram_client.pde +// RH_HARDWARESERIAL_DEVICE_NAME=/dev/ttyUSB1 ./serial_reliable_datagram_client + +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +#if (RH_PLATFORM == RH_PLATFORM_UNIX) + #include + // On Unix we connect to a physical serial port + // You can override this with RH_HARDWARESERIAL_DEVICE_NAME environment variable + HardwareSerial hardwareserial("/dev/ttyUSB0"); + RH_Serial driver(hardwareserial); + +#else + // On arduino etc, use a predefined local serial port + // eg Serial1 on a Mega + #include + // Singleton instance of the Serial driver, configured + // to use the port Serial1. Caution: on Uno32, Serial1 is on pins 39 (Rx) and + // 40 (Tx) + RH_Serial driver(Serial1); +#endif + + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + // Configure the port RH_Serial will use: + driver.serial().begin(9600); + if (!manager.init()) + Serial.println("init failed"); + //manager.setTimeout(2000); // Might need this at slow data rates or if a radio is involved +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_SERIAL_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to serial_reliable_datagram_server"); + + // Send a message to manager_server + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 6000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is serial_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); + delay(500); +} + diff --git a/examples/serial/serial_reliable_datagram_server/serial_reliable_datagram_server.ino b/examples/serial/serial_reliable_datagram_server/serial_reliable_datagram_server.ino new file mode 100644 index 0000000..f60c15e --- /dev/null +++ b/examples/serial/serial_reliable_datagram_server/serial_reliable_datagram_server.ino @@ -0,0 +1,76 @@ +// serial_reliable_datagram_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_Serial driver to +// communicate using packets over a serial port (or a radio connected to a +// serial port, such as the 3DR Telemetry radio V1 and others). +// It is designed to work with the other example serial_reliable_datagram_client +// Tested on Arduino Mega and ChipKit Uno32 (normal Arduinos only have one +// serial port and so it not possible to test on them and still have debug +// output) +// Tested with Arduino Mega, Teensy 3.1, Moteino, Arduino Due +// Also works on Linux an OSX. Build and test with: +// tools/simBuild examples/serial/serial_reliable_datagram_server/serial_reliable_datagram_server.pde +// RH_HARDWARESERIAL_DEVICE_NAME=/dev/ttyUSB0 ./serial_reliable_datagram_server + +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +#if (RH_PLATFORM == RH_PLATFORM_UNIX) + #include + // On Unix we connect to a physical serial port + // You can override this with RH_HARDWARESERIAL_DEVICE_NAME environment variable + HardwareSerial hardwareserial("/dev/ttyUSB0"); + RH_Serial driver(hardwareserial); + +#else + // On arduino etc, use a predefined local serial port + // eg Serial1 on a Mega + #include + // Singleton instance of the Serial driver, configured + // to use the port Serial1. Caution: on Uno32, Serial1 is on pins 39 (Rx) and + // 40 (Tx) + RH_Serial driver(Serial1); +#endif + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, SERVER_ADDRESS); + +void setup() +{ + Serial.begin(9600); + // Configure the port RH_Serial will use: + driver.serial().begin(9600); + if (!manager.init()) + Serial.println("init failed"); +// manager.setTimeout(2000); // Might need this at slow data rates or if a radio is involved +} + +uint8_t data[] = "And hello back to you"; +// Dont put this on the stack: +uint8_t buf[RH_SERIAL_MAX_MESSAGE_LEN]; + +void loop() +{ + + // Wait for a message addressed to us from the client + manager.waitAvailable(); + + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); + } +} + diff --git a/examples/simulator/simulator_reliable_datagram_client/simulator_reliable_datagram_client.ino b/examples/simulator/simulator_reliable_datagram_client/simulator_reliable_datagram_client.ino new file mode 100644 index 0000000..2d77598 --- /dev/null +++ b/examples/simulator/simulator_reliable_datagram_client/simulator_reliable_datagram_client.ino @@ -0,0 +1,67 @@ +// simulator_reliable_datagram_client.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging client +// with the RHReliableDatagram class, using the RH_SIMULATOR driver to control a SIMULATOR radio. +// It is designed to work with the other example simulator_reliable_datagram_server +// Tested on Linux +// Build with +// cd whatever/RadioHead +// tools/simBuild examples/simulator/simulator_reliable_datagram_client/simulator_reliable_datagram_client.pde +// Run with ./simulator_reliable_datagram_client +// Make sure you also have the 'Luminiferous Ether' simulator tools/etherSimulator.pl running + +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_TCP driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, CLIENT_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 + + // Maybe set this address from teh command line + if (_simulator_argc >= 2) + manager.setThisAddress(atoi(_simulator_argv[1])); +} + +uint8_t data[] = "Hello World!"; +// Dont put this on the stack: +uint8_t buf[RH_TCP_MAX_MESSAGE_LEN]; + +void loop() +{ + Serial.println("Sending to simulator_reliable_datagram_server"); + + // Send a message to manager_server + if (manager.sendtoWait(data, sizeof(data), SERVER_ADDRESS)) + { + // Now wait for a reply from the server + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAckTimeout(buf, &len, 2000, &from)) + { + Serial.print("got reply from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + } + else + { + Serial.println("No reply, is simulator_reliable_datagram_server running?"); + } + } + else + Serial.println("sendtoWait failed"); + delay(500); +} + diff --git a/examples/simulator/simulator_reliable_datagram_server/simulator_reliable_datagram_server.ino b/examples/simulator/simulator_reliable_datagram_server/simulator_reliable_datagram_server.ino new file mode 100644 index 0000000..74a020b --- /dev/null +++ b/examples/simulator/simulator_reliable_datagram_server/simulator_reliable_datagram_server.ino @@ -0,0 +1,59 @@ +// simulator_reliable_datagram_server.pde +// -*- mode: C++ -*- +// Example sketch showing how to create a simple addressed, reliable messaging server +// with the RHReliableDatagram class, using the RH_SIMULATOR driver to control a SIMULATOR radio. +// It is designed to work with the other example simulator_reliable_datagram_client +// Tested on Linux +// Build with +// cd whatever/RadioHead +// tools/simBuild examples/simulator/simulator_reliable_datagram_server/simulator_reliable_datagram_server.pde +// Run with ./simulator_reliable_datagram_server +// Make sure you also have the 'Luminiferous Ether' simulator tools/etherSimulator.pl running + +#include +#include + +#define CLIENT_ADDRESS 1 +#define SERVER_ADDRESS 2 + +// Singleton instance of the radio driver +RH_TCP driver; + +// Class to manage message delivery and receipt, using the driver declared above +RHReliableDatagram manager(driver, SERVER_ADDRESS); + +void setup() +{ + Serial.begin(9600); + if (!manager.init()) + Serial.println("init failed"); + // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36 + manager.setRetries(0); // Client will ping us if no ack received +// manager.setTimeout(50); +} + +uint8_t data[] = "And hello back to you"; +// Dont put this on the stack: +uint8_t buf[RH_TCP_MAX_MESSAGE_LEN]; + +void loop() +{ + // Wait for a message addressed to us from the client + manager.waitAvailable(); + + // Wait for a message addressed to us from the client + uint8_t len = sizeof(buf); + uint8_t from; + if (manager.recvfromAck(buf, &len, &from)) + { + Serial.print("got request from : 0x"); + Serial.print(from, HEX); + Serial.print(": "); + Serial.println((char*)buf); + + // Send a reply back to the originator client + if (!manager.sendtoWait(data, sizeof(data), from)) + Serial.println("sendtoWait failed"); + } +} + diff --git a/examples/sx126x/stm32wlx_client/stm32wlx_client.ino b/examples/sx126x/stm32wlx_client/stm32wlx_client.ino new file mode 100644 index 0000000..134e3b8 --- /dev/null +++ b/examples/sx126x/stm32wlx_client/stm32wlx_client.ino @@ -0,0 +1,74 @@ +// stm32wlx_client.ino +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the STM32WLx class. STM32WLx class does not provide for addressing or +// reliability, so you should only use on its own RH_SX126X if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example stm32wlx_server +// (dont forget to make sure the madulation schemes and frequencies are the same). +// In order to use the Arduino IDE to program the Wio-E5, you must +// install the stm32duino package using these instructions: +// https://community.st.com/t5/stm32-mcus/stm32-arduino-stm32duino-tutorial/ta-p/49649 +// Tested with Wio-E5 mini. In Arduino, select: +// Tools -> Board -> STM32 MCU based boards -> LoRa boards +// Tools -> Board part number -> LoRa-E5 mini +// Tools -> U(S)ART support -> Enabled (generic Serial) + +#include +#include + +// Single instance of the driver. The defaults will work for Seed WiO-E5 mini board, and other boards +// equipped with Seeed LoRa-E5 modules, such as NUCLEO_WL55JC1 +RH_STM32WLx driver; + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + + if (!driver.init()) + Serial.println("init failed"); + // Defaults after init are 915.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + // You can change the frequency: + // driver.setFrequency(868.0); + + // You can change the modulation parameters with eg + // driver.setModemConfig(RH_SX126x::LoRa_Bw125Cr45Sf2048); + + // You can change the power level in dBm +// driver.setTxPower(14); +} + +void loop() +{ + Serial.println("Sending to stm32wlx_server"); + // Send a message to stm32wlx_server + uint8_t data[] = "Hello World!"; + driver.send(data, sizeof(data)); + + driver.waitPacketSent(); + // Now wait for a reply + uint8_t buf[RH_SX126x_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (driver.waitAvailableTimeout(3000)) + { + // Should be a reply message for us now + if (driver.recv(buf, &len)) + { + Serial.print("got reply: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(driver.lastRssi(), DEC); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is stm32wlx_server running?"); + } + delay(400); +} diff --git a/examples/sx126x/stm32wlx_continuous/stm32wlx_continuous.ino b/examples/sx126x/stm32wlx_continuous/stm32wlx_continuous.ino new file mode 100644 index 0000000..a3d4355 --- /dev/null +++ b/examples/sx126x/stm32wlx_continuous/stm32wlx_continuous.ino @@ -0,0 +1,46 @@ +// stm32wlx_continuous.ino +// -*- mode: C++ -*- +// Example sketch showing how to start a continuous carrier wave transmission +// for measuring power output. CAUTION: it may be illegal for you to transmit continuously +// without a dummy load. +// +// In order to use the Arduino IDE to program the Wio-E5, you must +// install the stm32duino package using these instructions: +// https://community.st.com/t5/stm32-mcus/stm32-arduino-stm32duino-tutorial/ta-p/49649 +// Tested with Wio-E5 mini. In Arduino, select: +// Tools -> Board -> STM32 MCU based boards -> LoRa boards +// Tools -> Board part number -> LoRa-E5 mini +// Tools -> U(S)ART support -> Enabled (generic Serial) +// Caution: it is probably possible to damage your chip by setting high power levels +// without a suitable RF termination. + +#include +#include + +// Single instance of the driver. The defaults will work for Seed WiO-E5 mini board, and other boards +// equipped with Seeed LoRa-E5 modules, such as NUCLEO_WL55JC1 +RH_STM32WLx driver; + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + + if (!driver.init()) + Serial.println("init failed"); + + // You can change the frequency: + driver.setFrequency(868.0); // Specs are at this frequency + + // You can change the power level in dBm(-9 to +22 on the Wio-E5 mini) + // Default is 13dBm + driver.setTxPower(0); + driver.setTxContinuous(); // Send continuous carrier wave at the power and frequency set above +} + +void loop() +{ + return; +} + + diff --git a/examples/sx126x/stm32wlx_server/stm32wlx_server.ino b/examples/sx126x/stm32wlx_server/stm32wlx_server.ino new file mode 100644 index 0000000..5cf5265 --- /dev/null +++ b/examples/sx126x/stm32wlx_server/stm32wlx_server.ino @@ -0,0 +1,74 @@ +// stm32wlx_server.ino +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_SX126X class. RH_SX126X class does not provide for addressing or +// reliability, so you should only use on its own RH_SX126X if you do not need the higher +// level messaging abilities. +// It is designed to work with the other example stm32wlx_client or rf95_client +// (dont forget to make sure the madulation schemes and frequencies are the same). +// +// In order to use the Arduino IDE to program the Wio-E5, you must +// install the stm32duino package using these instructions: +// https://community.st.com/t5/stm32-mcus/stm32-arduino-stm32duino-tutorial/ta-p/49649 +// Tested with Wio-E5 mini. In Arduino, select: +// Tools -> Board -> STM32 MCU based boards -> LoRa boards +// Tools -> Board part number -> LoRa-E5 mini +// Tools -> U(S)ART support -> Enabled (generic Serial) +// +// Program with eg ST-LINK V2 (a red USB dongle). See the instructions in RH_STM32WLx.h + +#include + +// Single instance of the driver. The defaults will work for Seed WiO-E5 mini board, and other boards +// equipped with Seeed LoRa-E5 modules, such as NUCLEO_WL55JC1 +RH_STM32WLx driver; + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Wait for serial port to be available + + if (!driver.init()) + Serial.println("init failed"); + + // Defaults after init are 915.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + // You can change the frequency: + //driver.setFrequency(868.0); + + // You can change the modulation parameters with eg + //driver.setModemConfig(RH_SX126x::LoRa_Bw125Cr45Sf2048); + + // You can change the power level in dBm (-9 to +22 on the Wio-E5 mini) + // Default is 13dBm + //driver.setTxPower(0); +} + +void loop() +{ + if (driver.available()) + { + // Should be a message for us now + uint8_t buf[RH_SX126x_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (driver.recv(buf, &len)) + { +// RH_SX126X::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); + Serial.print("RSSI: "); + Serial.println(driver.lastRssi(), DEC); + + // Send a reply + uint8_t data[] = "And hello back to you"; + driver.send(data, sizeof(data)); + driver.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} + + diff --git a/examples/sx126x/sx1262_client/sx1262_client.ino b/examples/sx126x/sx1262_client/sx1262_client.ino new file mode 100644 index 0000000..739b035 --- /dev/null +++ b/examples/sx126x/sx1262_client/sx1262_client.ino @@ -0,0 +1,74 @@ +// sx1262_client.ino +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing client +// with the RH_SX126x class and a basic SX1262 module connected to an Arduino compatible processor +// It is designed to work with the examples sx1262_server and sx1262_server. +// Tested with G-Nice LoRa1262-915 and Teensy 3.1 +// also Heltec Cube Cell HTCC-AB01 + +#include + +RH_SX126x driver(SS, 7, 8, 9); // NSS, DIO1, BUSY, NRESET + +// For Heltec Cube Cell, using CubeCell board 1.5.0 or greater +//RH_SX126x driver(RADIO_NSS, RADIO_DIO_1, RADIO_BUSY, RADIO_RESET); + +void setup() +{ + Serial.begin(9600); + // May need this on some platforms but definitely not on CubeCell: + //while (!Serial) ; // Wait for serial port to be available + + if (!driver.init()) + Serial.println("init failed"); + + // Defaults after init are 915.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on (LoRa_Bw125Cr45Sf128) + // You can change the frequency: + //driver.setFrequency(868.0); + + // You can change the modulation parameters with eg + //driver.setModemConfig(RH_SX126x::LoRa_Bw125Cr45Sf2048); + + // You can change the power level in dBm + // Default is 13dBm + //driver.setTxPower(0); +} + +void loop() +{ + Serial.println("Sending to sx126x_server"); + // Send a message to stm32wlx_server + uint8_t data[] = "Hello World!"; + + driver.send(data, sizeof(data)); + + driver.waitPacketSent(3000); + + + // Now wait for a reply + uint8_t buf[RH_SX126x_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + + if (driver.waitAvailableTimeout(3000)) + { + // Should be a reply message for us now + if (driver.recv(buf, &len)) + { + + Serial.print("got reply: "); + Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(driver.lastRssi(), DEC); + } + else + { + Serial.println("recv failed"); + } + } + else + { + Serial.println("No reply, is sx126x_server running?"); + } + + delay(1000); +} \ No newline at end of file diff --git a/examples/sx126x/sx1262_server/sx1262_server.ino b/examples/sx126x/sx1262_server/sx1262_server.ino new file mode 100644 index 0000000..5e0370f --- /dev/null +++ b/examples/sx126x/sx1262_server/sx1262_server.ino @@ -0,0 +1,65 @@ +// sx1262_server.ino +// -*- mode: C++ -*- +// Example sketch showing how to create a simple messageing server +// with the RH_SX126x class and a basic SX1262 module connected to an Arduino compatible processor +// It is designed to work with the examples stm32wlx_client and sx1262_client. +// Tested with G-Nice LoRa1262-915 and Teensy 3.1 +// also Heltec Cube Cell HTCC-AB01 + +#include + +RH_SX126x driver(SS, 7, 8, 9); // NSS, DIO1, BUSY, NRESET + +// For Heltec Cube Cell, using CubeCell board 1.5.0 or greater +//RH_SX126x driver(RADIO_NSS, RADIO_DIO_1, RADIO_BUSY, RADIO_RESET); + +void setup() +{ + Serial.begin(9600); + // May need this on some platforms but definitely not on CubeCell: + //while (!Serial) ; // Wait for serial port to be available + + if (!driver.init()) + Serial.println("init failed"); + + // Defaults after init are 915.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + // You can change the frequency: + //driver.setFrequency(868.0); + + // You can change the modulation parameters with eg + //driver.setModemConfig(RH_SX126x::LoRa_Bw125Cr45Sf2048); + + // You can change the power level in dBm (-9 to +22 on the Wio-E5 mini) + // Default is 13dBm + //driver.setTxPower(0); +} + +void loop() +{ + if (driver.available()) + { + // Should be a message for us now + uint8_t buf[RH_SX126x_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + if (driver.recv(buf, &len)) + { +// RH_SX126X::printBuffer("request: ", buf, len); + Serial.print("got request: "); + Serial.println((char*)buf); + Serial.print("RSSI: "); + Serial.println(driver.lastRssi(), DEC); + + // Send a reply + uint8_t data[] = "And hello back to you"; + driver.send(data, sizeof(data)); + driver.waitPacketSent(); + Serial.println("Sent a reply"); + } + else + { + Serial.println("recv failed"); + } + } +} + + diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..0ef27a8 --- /dev/null +++ b/library.properties @@ -0,0 +1,11 @@ +name=RadioHead +version=1.143 +author=Mike McCauley +maintainer=Mike McCauley +sentence= Packet Radio library for embedded microprocessors +paragraph=Provides a complete object-oriented library for sending and receiving packetized messages via a variety of common data radios and other transports on a range of embedded microprocessors. +category=Communication +url=https://www.airspayce.com/mikem/arduino/RadioHead/ +architectures=* +includes=RadioHead.h +depends= \ No newline at end of file diff --git a/project.cfg b/project.cfg new file mode 100644 index 0000000..98bb863 --- /dev/null +++ b/project.cfg @@ -0,0 +1,2523 @@ +# Doxyfile 1.8.17 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = RadioHead + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is +# Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# (including Cygwin) ands Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen +# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = examples + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = YES + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files +# were built. This is equivalent to specifying the "-p" option to a clang tool, +# such as clang-check. These options will then be passed to the parser. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = NO + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = doc + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/xcode/), introduced with OSX +# 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /