diff --git a/README.md b/README.md index 9aae9002..44b7df10 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,7 @@ library what pin you used through the pin mapping (see below). [SPI]: https://www.arduino.cc/en/Reference/SPI ### DIO pins + The DIO (digitial I/O) pins on the transceiver board can be configured for various functions. The LMIC library uses them to get instant status information from the transceiver. For example, when a LoRa transmission @@ -255,6 +256,11 @@ connect to any I/O pin, since the current implementation does not use interrupts or other special hardware features (though this might be added in the feature, see also the "Timing" section). +A new software feature has been added to remove needing DIO connections. +Of course, you can continue to use DIO mapping has described above +but in case you're restricted in GPIO available, but for now you can use +LMIC using no DIO to any GPIO connection (see below). + In LoRa mode the DIO pins are used as follows: * DIO0: TxDone and RxDone * DIO1: RxTimeout @@ -324,22 +330,108 @@ The names refer to the pins on the transceiver side, the numbers refer to the Arduino pin numbers (to use the analog pins, use constants like `A0`). For the DIO pins, the three numbers refer to DIO0, DIO1 and DIO2 respectively. Any pins that are not needed should be specified as -`LMIC_UNUSED_PIN`. The nss and dio0 pin is required, the others can +`LMIC_UNUSED_PIN`. Only the nss pin is required, the others can potentially left out (depending on the environments and requirements, see the notes above for when a pin can or cannot be left out). The name of this struct must always be `lmic_pins`, which is a special name recognized by the library. +Since now, a software feature has been added to remove needing DIO connections. +Of course, you can continue to use DIO mapping has follow. to activate this +feature, you just need to declare 3 .dio to LMIC_UNUSED_PIN, in your sketch as +detailled below. + +If you want to use hardware IRQ but not having 3 IO pins, another trick is +to OR DIO0/DOI1/DIO2 into one. This is possible because the stack check +all IRQs, even if only one is triggered. Doing this is quite easy, just add 3 +1N4148 diodes to each output and a pulldown resistor, see schematic example +on [WeMos Lora shield](https://github.com/hallard/WeMos-Lora#schematic). + +If you still have DIO connection, following original lib readme is explaining +how they work. + +If you don't have any DIO pins connected to GPIO (new software feature) +you just need to declare 3 .dio to LMIC_UNUSED_PIN, in your sketch +That's all, stack will do the job for you. + +#### [WeMos Lora Shield](https://github.com/hallard/WeMos-Lora) +```arduino +// Example with NO DIO pin connected +const lmic_pinmap lmic_pins = { + .nss = 16, + .rxtx = LMIC_UNUSED_PIN, + .rst = LMIC_UNUSED_PIN, + .dio = {LMIC_UNUSED_PIN, LMIC_UNUSED_PIN, LMIC_UNUSED_PIN}, +}; +``` + +If you used 3 diodes OR hardware trick like in this [schematic](https://github.com/hallard/WeMos-Lora), +just indicate which GPIO is used on DIO0 definition as follow: + +```arduino +// Example with 3 DIO OR'ed on one pin connected to GPIO15 +const lmic_pinmap lmic_pins = { + .nss = 16, + .rxtx = LMIC_UNUSED_PIN, + .rst = LMIC_UNUSED_PIN, + .dio = {15, LMIC_UNUSED_PIN, LMIC_UNUSED_PIN}, +}; +``` + #### LoRa Nexus by Ideetron This board uses the following pin mapping: +```arduino const lmic_pinmap lmic_pins = { .nss = 10, .rxtx = LMIC_UNUSED_PIN, .rst = LMIC_UNUSED_PIN, // hardwired to AtMega RESET .dio = {4, 5, 7}, }; +``` + +### LoPy (ESP32) + When using the LoPy you will need the following pin mapping: + + ``` + const lmic_pinmap lmic_pins = { + .mosi = 27, + .miso = 19, + .sck = 5, + .nss = 17, + .rxtx = LMIC_UNUSED_PIN, + .rst = 18, + .dio = {23, LMIC_UNUSED_PIN, LMIC_UNUSED_PIN}, + }; + ``` + You will also need Arduino ESP32 support from + https://github.com/espressif/arduino-esp32. Use the "ESP32 Dev Module" as target + device. To program the LoPy you need to be in bootloader mode. While shorting + pin P3 (G23) to ground, push the reset button to put the LoPy in bootloader + mode. After programming reset the board without P3 shorted to ground to start + in normal mode. + + ### Heltec Lora32 (ESP32) + When using Heltec Lora32 you will need the following pin mapping: + + ``` + const lmic_pinmap lmic_pins = { + .mosi = 27, + .miso = 19, + .sck = 5, + .nss = 18, + .rxtx = LMIC_UNUSED_PIN, + .rst = 14, + .dio = {26, 33, 32}, + }; + + ``` + On Board OLED is I2C with SCL=GPIO15, SDA=GPIO4 and OLED_RST=GPIO16. + You will also need Arduino ESP32 support from + https://github.com/espressif/arduino-esp32. Use the "ESP32 Dev Module" as target + device. + Examples -------- diff --git a/project_config/lmic_project_config.h b/project_config/lmic_project_config.h index 5e93a0dd..410fbc02 100644 --- a/project_config/lmic_project_config.h +++ b/project_config/lmic_project_config.h @@ -1,4 +1,31 @@ // project-specific definitions for otaa sensor + +// even if you have lora_project_config.h in your sketch directory. +// some define are not used in all stack at compilation time, +// even adding #include "lora_project_config.h" at top of the sketch +// so put all in this file and it works all time + +// Arduino Zero remap Serial to SerialUSB +#ifdef ARDUINO_ARCH_SAMD +#define Serial SerialUSB +#endif + #define CFG_us915 1 +//#define CFG_eu868 1 #define CFG_sx1276_radio 1 + +// Use real interrupts //#define LMIC_USE_INTERRUPTS + +// Set SPI 8MHz +//#define LMIC_SPI_FREQ 8000000 + +// Enable Debug (uncomment both lines) +//#define LMIC_DEBUG_LEVEL 2 +//#define LMIC_PRINTF_TO Serial + +// Use Original AES (More space but quicker) +//#define USE_ORIGINAL_AES + + + diff --git a/src/hal/hal.cpp b/src/hal/hal.cpp index 32452e97..95b16db2 100644 --- a/src/hal/hal.cpp +++ b/src/hal/hal.cpp @@ -13,17 +13,24 @@ #include "../lmic.h" #include "hal.h" #include +#include // ----------------------------------------------------------------------------- // I/O +// in, case we have no DIO mapping to a GPIO pin, we'll need to read +// Lora Module IRQ register +static bool check_dio = 0; static void hal_interrupt_init(); // Fwd declaration static void hal_io_init () { // NSS and DIO0 are required, DIO1 is required for LoRa, DIO2 for FSK ASSERT(lmic_pins.nss != LMIC_UNUSED_PIN); - ASSERT(lmic_pins.dio[0] != LMIC_UNUSED_PIN); - ASSERT(lmic_pins.dio[1] != LMIC_UNUSED_PIN || lmic_pins.dio[2] != LMIC_UNUSED_PIN); + + // No more needed, if dio pins are declared as unused, then LIMC will check + // interrputs directly into Lora module register, avoiding needed GPIO line to IRQ + //ASSERT(lmic_pins.dio[0] != LMIC_UNUSED_PIN); + //ASSERT(lmic_pins.dio[1] != LMIC_UNUSED_PIN || lmic_pins.dio[2] != LMIC_UNUSED_PIN); pinMode(lmic_pins.nss, OUTPUT); if (lmic_pins.rxtx != LMIC_UNUSED_PIN) @@ -55,26 +62,38 @@ void hal_pin_rst (u1_t val) { #if !defined(LMIC_USE_INTERRUPTS) static void hal_interrupt_init() { - pinMode(lmic_pins.dio[0], INPUT); - if (lmic_pins.dio[1] != LMIC_UNUSED_PIN) - pinMode(lmic_pins.dio[1], INPUT); - if (lmic_pins.dio[2] != LMIC_UNUSED_PIN) - pinMode(lmic_pins.dio[2], INPUT); + // Loop to check / configure all DIO input pin + for (uint8_t i = 0; i < NUM_DIO; ++i) { + if (lmic_pins.dio[i] != LMIC_UNUSED_PIN) { + // we need to check at least one DIO line + check_dio = 1; + pinMode(lmic_pins.dio[i], INPUT); + } + } } static bool dio_states[NUM_DIO] = {0}; static void hal_io_check() { uint8_t i; - for (i = 0; i < NUM_DIO; ++i) { - if (lmic_pins.dio[i] == LMIC_UNUSED_PIN) - continue; - - if (dio_states[i] != digitalRead(lmic_pins.dio[i])) { - dio_states[i] = !dio_states[i]; - if (dio_states[i]) - radio_irq_handler(i); + // At least one DIO Line to check ? + if (check_dio) { + for (i = 0; i < NUM_DIO; ++i) { + if (lmic_pins.dio[i] == LMIC_UNUSED_PIN) + continue; + + if (dio_states[i] != digitalRead(lmic_pins.dio[i])) { + dio_states[i] = !dio_states[i]; + if (dio_states[i]) + radio_irq_handler(i); + } + } + } else { + // Check IRQ flags in radio module + if ( radio_has_irq() ) { + radio_irq_handler(0); } } + } #else @@ -123,9 +142,22 @@ static void hal_io_check() { static const SPISettings settings(LMIC_SPI_FREQ, MSBFIRST, SPI_MODE0); static void hal_spi_init () { - SPI.begin(); + #if defined(ESP32) + // On the ESP32 the default is _use_hw_ss(false), + // so we can set the last parameter to anything. + SPI.begin(lmic_pins.sck, lmic_pins.miso, lmic_pins.mosi, 0x00); + #elif defined(NRF51) + SPI.begin(lmic_pins.sck, lmic_pins.mosi, lmic_pins.miso); + #else + //unknown board, or board without SPI pin select ability + SPI.begin(); + #endif } + + + + void hal_pin_nss (u1_t val) { if (!val) SPI.beginTransaction(settings); @@ -254,24 +286,16 @@ void hal_sleep () { // ----------------------------------------------------------------------------- #if defined(LMIC_PRINTF_TO) -static int uart_putchar (char c, FILE *) +void hal_printf(char *fmt, ... ) { - LMIC_PRINTF_TO.write(c) ; - return 0 ; -} - -void hal_printf_init() { - // create a FILE structure to reference our UART output function - static FILE uartout; - memset(&uartout, 0, sizeof(uartout)); - - // fill in the UART file descriptor with pointer to writer. - fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE); - - // The uart is the standard output device STDOUT. - stdout = &uartout ; + char buf[80]; // resulting string limited to 128 chars + va_list args; + va_start (args, fmt ); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end (args); + LMIC_PRINTF_TO.print(buf); } -#endif // defined(LMIC_PRINTF_TO) +#endif void hal_init () { // configure radio I/O and interrupt handler @@ -280,10 +304,6 @@ void hal_init () { hal_spi_init(); // configure timer and interrupt handler hal_time_init(); -#if defined(LMIC_PRINTF_TO) - // printf support - hal_printf_init(); -#endif } void hal_failed (const char *file, u2_t line) { diff --git a/src/hal/hal.h b/src/hal/hal.h index e096569f..2cc94c7f 100644 --- a/src/hal/hal.h +++ b/src/hal/hal.h @@ -12,12 +12,25 @@ static const int NUM_DIO = 3; -struct lmic_pinmap { - u1_t nss; - u1_t rxtx; - u1_t rst; - u1_t dio[NUM_DIO]; -}; +#if defined(ESP32) || defined(NRF51) + #define LMIC_SPI_PINS_IN_MAPPING + struct lmic_pinmap { + u1_t mosi; + u1_t miso; + u1_t sck; + u1_t nss; + u1_t rxtx; + u1_t rst; + u1_t dio[NUM_DIO]; + }; +#else + struct lmic_pinmap { + u1_t nss; + u1_t rxtx; + u1_t rst; + u1_t dio[NUM_DIO]; + }; +#endif // Use this for any unused pins. const u1_t LMIC_UNUSED_PIN = 0xff; diff --git a/src/lmic/config.h b/src/lmic/config.h index 36686d96..e630be88 100644 --- a/src/lmic/config.h +++ b/src/lmic/config.h @@ -60,6 +60,10 @@ // current implementation only works on AVR, though. //#define LMIC_PRINTF_TO Serial +#if LMIC_DEBUG_LEVEL > 0 && ! defined(LMIC_PRINTF_TO) +# error "You defined LMIC_DEBUG_LEVEL, please also define LMIC_PRINTF_TO to see debug log" +#endif + // Enable this to use interrupt handler routines listening for RISING signals. // Otherwise, the library polls digital input lines for changes. //#define LMIC_USE_INTERRUPTS diff --git a/src/lmic/hal.h b/src/lmic/hal.h index 920317a4..ece9ee55 100644 --- a/src/lmic/hal.h +++ b/src/lmic/hal.h @@ -100,6 +100,20 @@ u1_t hal_checkTimer (u4_t targettime); */ void hal_failed (const char *file, u2_t line); +// printf Wrapper +#ifdef LMIC_PRINTF_TO + +/* + * perform printf like action. + * - called when LMIC_PRINTF_TO used + * - action printf + */ +void hal_printf(char *fmt, ... ); + +#define printf hal_printf + +#endif + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/lmic/oslmic.h b/src/lmic/oslmic.h index dad81141..262f476e 100644 --- a/src/lmic/oslmic.h +++ b/src/lmic/oslmic.h @@ -97,6 +97,7 @@ u1_t radio_rand1 (void); #define DECLARE_LMIC extern struct lmic_t LMIC void radio_init (void); +u1_t radio_has_irq (void); void radio_irq_handler (u1_t dio); void os_init (void); void os_runloop (void); diff --git a/src/lmic/radio.c b/src/lmic/radio.c index cf87dfd9..0928b2fa 100644 --- a/src/lmic/radio.c +++ b/src/lmic/radio.c @@ -788,6 +788,27 @@ static CONST_TABLE(u2_t, LORA_RXDONE_FIXUP)[] = { [SF12] = us2osticks(31189), // (1022 ticks) }; + +// called by hal to check if we got one IRQ +// This trick directly read the Lora module IRQ register +// and thus avoid any IRQ line used to controler +u1_t radio_has_irq (void) { + u1_t flags ; + if( (readReg(RegOpMode) & OPMODE_LORA) != 0) { // LORA modem + flags = readReg(LORARegIrqFlags); + if( flags & ( IRQ_LORA_TXDONE_MASK | IRQ_LORA_RXDONE_MASK | IRQ_LORA_RXTOUT_MASK ) ) + return 1; + } else { // FSK modem + flags = readReg(FSKRegIrqFlags2); + if ( flags & ( IRQ_FSK2_PACKETSENT_MASK | IRQ_FSK2_PAYLOADREADY_MASK) ) + return 1; + flags = readReg(FSKRegIrqFlags1); + if ( flags & IRQ_FSK1_TIMEOUT_MASK ) + return 1; + } + return 0; +} + // called by hal ext IRQ handler // (radio goes to stanby mode after tx/rx operations) void radio_irq_handler (u1_t dio) {