diff --git a/.github/workflows/compile-arduino-examples.yml b/.github/workflows/compile-arduino-examples.yml new file mode 100644 index 0000000..83ce17a --- /dev/null +++ b/.github/workflows/compile-arduino-examples.yml @@ -0,0 +1,74 @@ +name: Compile Arduino examples +on: [push, pull_request] +jobs: + build: + name: ${{ matrix.board }} / ${{ matrix.region }} / ${{ matrix.transceiver }} + runs-on: ubuntu-latest + env: + PLATFORM_DEFAULT_URL: https://arduino.esp8266.com/stable/package_esp8266com_index.json,https://dl.espressif.com/dl/package_esp32_index.json,https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json,https://lacunaspace.github.io/arduino-STM32L4-Lacuna/package_STM32L4_Lacuna_boards_index.json + strategy: + matrix: + board: + - arduino:avr:mega + - arduino:samd:arduino_zero_edbg + - esp8266:esp8266:generic + - esp32:esp32:ttgo-lora32-v1 + - lacunaspace:stm32l4:Lacuna-LS200 + # Just a random STM32 board + - STM32:stm32:Nucleo_64:pnum=NUCLEO_F401RE + region: + - CFG_eu868 + - CFG_us915 + transceiver: + - BRD_sx1272_radio + - BRD_sx1276_radio + - BRD_sx1261_radio + - BRD_sx1262_radio + include: + # Older compilers (used by STM32 and ESP) seem to get confused + # by the REGION initializers and produce (seemingly unfixable) + # missing-field-initializers warnings. So just ignore these + # for these boards. + - board: esp8266:esp8266:generic + extra_flags: -Wno-error=missing-field-initializers + # The STM32 cores have unused-parameter warnings in the core, + # so these (this also ignores the same warnings in the sketch + # and library, which is not ideal, but this seems to be the + # only way to fix this). + - board: lacunaspace:stm32l4:Lacuna-LS200 + extra_flags: -Wno-error=unused-parameter -Wno-error=missing-field-initializers + - board: STM32:stm32:Nucleo_64:pnum=NUCLEO_F401RE + extra_flags: -Wno-error=unused-parameter -Wno-error=missing-field-initializers + + # Run the entire matrix, even if one failed + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Generate Arduino library + # Generate twice, once to be moved into the Arduino directory and + # once to compile the examples from + run: | + ./target/arduino/export.sh BasicMacCustom + ./target/arduino/export.sh BasicMacArduino + + - name: Compile all examples + uses: ArminJo/arduino-test-compile@v3 + with: + arduino-board-fqbn: ${{ matrix.board }} + platform-default-url: ${{ env.PLATFORM_DEFAULT_URL }} + sketch-names-find-start: ./BasicMacArduino/examples/ + # Normally, all .ino files are treated as separate sketches, but + # this is just a secondary .ino file that does not need to be + # separately compiled. + sketches-exclude: standard-pinmaps + build-properties: > + {"All": " + -DBASICMAC_DUMMY_PINMAP + -D${{matrix.region}} + -D${{matrix.transceiver}} + -Werror + ${{matrix.extra_flags}} + "} diff --git a/.github/workflows/compile-projects.yml b/.github/workflows/compile-projects.yml new file mode 100644 index 0000000..df4f0d7 --- /dev/null +++ b/.github/workflows/compile-projects.yml @@ -0,0 +1,42 @@ +name: Compile projects +on: [push, pull_request] +jobs: + build: + name: ${{matrix.target}} + runs-on: ubuntu-latest + strategy: + matrix: + target: + # List taken from ex-join project, there might be more valid + # combinations. + - b_l072z_lrwan1 + - nucleo_l073rz-sx1276mb1las + - nucleo_l073rz-sx1272mbed + - nucleo_l073rz-sx1276mb1mas + - nucleo_l053r8-sx1276mb1las + - nucleo_l053r8-sx1261mbed + - nucleo_l053r8-sx1262mbed + + # Run the entire matrix, even if one failed + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: Install compiler + run: sudo apt install gcc-arm-none-eabi + + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: '3.x' # Version range or exact version of a Python version to use, using SemVer's version range syntax + + - name: Install python packages + run: python -m pip install lz4 pycryptodome click intelhex pyyaml + + - name: Compile project + run: | + make -C projects/ex-join TARGET="${{matrix.target}}" || exit 1 diff --git a/.github/workflows/create-arduino-lib.yml b/.github/workflows/create-arduino-lib.yml new file mode 100644 index 0000000..42f178b --- /dev/null +++ b/.github/workflows/create-arduino-lib.yml @@ -0,0 +1,21 @@ +name: Create Arduino Library +on: [push, pull_request] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Generate Arduino library + run: | + # Create two directory levels, so the inner one ends up in the + # generated zip + mkdir ArduinoLibrary + ./target/arduino/export.sh ArduinoLibrary/Basicmac + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: ArduinoLibrary + path: ArduinoLibrary diff --git a/README.md b/README.md index fa2a3bd..ad3cfd7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Basic MAC +# BasicMAC [Basic MAC](https://basicmac.io) is a portable implementation of the LoRa Alliance®'s LoRaWAN® specification in the C programming language. It is a fork @@ -13,3 +13,143 @@ Branch | Travis CI #### Getting Started View the [Getting Started Guide](https://basicmac.io/guide/gettingstarted.html). + + +## This repository +This repository contains an Arduino port of the BasicMAC library. +However, the port is done without changing the structure of the +repository itself, so development can continue on other targets at the +same time. + +### Development status + +This master branch should be working, testing and contributions are +welcome. There are still some rough edges, so this should not be +considered a finished version. The final 2.2 release by Semtech was only +recently integrated and has seen limited testing. + +Initially, development of this repository has been quite unstable, with +lots of history editing and rebasing, but the intention is to keep the +master branch stable now (i.e. no force pushes). Rebasing and history +editing might still happen in other branches and is encourage in +pullrequests to keep history clean. + +The API is still likely to change in a breaking way, especially wrt +configuration. + +### Notable and breaking changes + +This section lists notable changes, in particular those that can break +existing sketches. For full details, see the git changelog. + + - 2020-07-14: On Arduino, the default config is changed to SX1262. If + your board uses SX1276 (the old default), you need to modify + `target-config.h` in the library. + + - 2020-07-14: On SX126x, DIO3 is no longer configured to control a TCXO + by default. If your board needs this, it must be explicitly enabled. + On Arduino, set the `tcxo` field of the pin map to + `LMIC_CONTROLLED_BY_DIO3`. With Makefile-based stm32, define + `LMIC_DIO3_CONTROLS_TCXO` on the compiler commandline. + + - 2020-07-14: On SX126x, DIO2 is no longer configured to control a TXRX + antenna switch by default. If your board needs this (most likely it + does), it must be explicitly enabled. On Arduino, set the `tx` + field of the pin map to `LMIC_CONTROLLED_BY_DIO2`. With + Makefile-based stm32, define `LMIC_DIO2_CONTROLS_TXRX` on the + compiler commandline. + +### Relation to other projects + +BasicMAC is a privately developed fork of LMIC, which was released +publically by Semtech in 2019. In 2020, Semtech has announced it no +longer intends to develop or support BasicMAC, so this repository is +intended to become the primary repository for BasicMAC. + +This repository borrows heavily from the Arduino LMIC port that was +first published at https://github.com/matthijskooijman/arduino-lmic/. +There are some other LMIC versions (notably +https://github.com/mcci-catena/arduino-lmic) for Arduino based off +matthijskooijman's version and have seen more development, but no effort +has been made to incorporate any changes from there. + +Ideally, all of these LMIC and BasicMAC-based projects would unify their +efforts in a repository that is not Arduino-specific, but given that +there have been signficant changes in both BasicMAC and the MCCI +versions compared to the original IBM LMIC version, this might be a +challenge (both technically, and project-wise). + +### Using this repository on Arduino + +This repository is not directly usable as an Arduino library, in order +to keep the directory structure more generic. To use it in Arduino, +there is a bash script that generates an Arduino library out of it. To do so, +make a git checkout of this repository and from the root of it, run: + + ./target/arduino/export.sh --link ~/Arduino/libraries/BasicMAC + +The `--link` parameter makes the script generate symlinks into the +original repository, allowing you to make changes to the library there +and have them reflected in the Arduino version directly. If you omit +`--link`, copies will be made instead. + +The directory name passed is created and becomes the library directory, +it should be in your Arduino sketchbook's `libraries` directly (the path +shown should work with the default Arduino IDE configuration). + +This script was tested on Linux, should work on OSX as well, might work +on Windows (if you have bash). + +### Using this repository without Arduino + +To build and upload without Arduino, a makefile-based build system is +present in the `projects` subdirectory. It currently only supports +STM32, on some specific boards. + +To use it, go into the example project directory (`projects/ex-join`) +and run `make`. + +Some effort has been made to keep these builds working, but actual +testing of the code in this repository has only been done under Arduino +(Makefile-based builds are only automatically compile-tested), so the +Makefile-based builds might very well be broken. + +### Hardware support + +This port is intended to work on any Arduino board, regardless of +architecture. It was tested on AVR, SAMD and STM32 boards, and with +SX1272, SX1276 and SX1262 radios. + +Unfortunately, BasicMAC is quite a bit bigger than LMIC, so it seems it +does not fit in an atmega328p anymore. PROGMEM optimizations have not +been applied yet, but those would only free up RAM, not flash (and now +the ttn-otaa example compiles down to 35k of flash, with only 32k +available). + +So far, only EU868 has been tested. Other regions are supported, but are +not currently tested. + +### Converting from LMIC + +BasicMAC is based on LMIC and largely works in the same way. Sketches +can likely be largely reused, though some small changes need to be made. +See for example [this commit][migrate-examples] for the changes needed to the examples. + +Additionally the pinmap was significantly changed, so look at one of the +current examples to see how that looks now. + +[migrate-examples]: https://github.com/LacunaSpace/basicmac/commit/1505722c912c8cb0cfff2e18b115f9f2c1a62d0f +======= +[Basic MAC](https://basicmac.io) is a portable implementation of the LoRa +Alliance®'s LoRaWAN® specification in the C programming language. It is a fork +of IBM's LMiC library, and supports multiple regions, which are selectable at +compile and/or run time. It can handle Class A, Class B, and Class C devices. + +#### Status +Branch | Travis CI +-------|---------- +[`master`](https://github.com/mkuyper/basicmac/tree/master) | [![Build Status](https://travis-ci.com/mkuyper/basicmac.svg?branch=master)](https://travis-ci.com/mkuyper/basicmac) + +#### Getting Started + +View the [Getting Started Guide](https://basicmac.io/guide/gettingstarted.html). diff --git a/aes/aes-common.c b/aes/aes-common.c new file mode 100644 index 0000000..2238fd6 --- /dev/null +++ b/aes/aes-common.c @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2016 Matthijs Kooijman + * + * LICENSE + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and + * redistribution. + * + * NO WARRANTY OF ANY KIND IS PROVIDED. + *******************************************************************************/ + +/* + * The original LMIC AES implementation integrates raw AES encryption + * with CMAC and AES-CTR in a single piece of code. Most other AES + * implementations (only) offer raw single block AES encryption, so this + * file contains an implementation of CMAC and AES-CTR, and offers the + * same API through the os_aes() function as the original AES + * implementation. This file assumes that there is an encryption + * function available with this signature: + * + * extern "C" void lmic_aes_encrypt(u1_t *data, u1_t *key); + * + * That takes a single 16-byte buffer and encrypts it wit the given + * 16-byte key. + */ + +#include "../lmic/aes.h" + +#if !defined(USE_ORIGINAL_AES) + +// This should be defined elsewhere +void lmic_aes_encrypt(u1_t *data, u1_t *key); + +// global area for passing parameters (aux, key) and for storing round keys +u4_t AESAUX[16/sizeof(u4_t)]; +u4_t AESKEY[11*16/sizeof(u4_t)]; + +// Shift the given buffer left one bit +static void shift_left(u1_t *buf, u1_t len) { + while (len--) { + u1_t next = len ? buf[1] : 0; + + u1_t val = (*buf << 1); + if (next & 0x80) + val |= 1; + *buf++ = val; + } +} + +// Apply RFC4493 CMAC, using AESKEY as the key. If prepend_aux is true, +// AESAUX is prepended to the message. AESAUX is used as working memory +// in any case. The CMAC result is returned in AESAUX as well. +static void os_aes_cmac(u1_t *buf, u2_t len, u1_t prepend_aux) { + if (prepend_aux) + lmic_aes_encrypt(AESaux, AESkey); + else + memset (AESaux, 0, 16); + + while (len > 0) { + u1_t need_padding = 0; + for (u1_t i = 0; i < 16; ++i, ++buf, --len) { + if (len == 0) { + // The message is padded with 0x80 and then zeroes. + // Since zeroes are no-op for xor, we can just skip them + // and leave AESAUX unchanged for them. + AESaux[i] ^= 0x80; + need_padding = 1; + break; + } + AESaux[i] ^= *buf; + } + + if (len == 0) { + // Final block, xor with K1 or K2. K1 and K2 are calculated + // by encrypting the all-zeroes block and then applying some + // shifts and xor on that. + u1_t final_key[16]; + memset(final_key, 0, sizeof(final_key)); + lmic_aes_encrypt(final_key, AESkey); + + // Calculate K1 + u1_t msb = final_key[0] & 0x80; + shift_left(final_key, sizeof(final_key)); + if (msb) + final_key[sizeof(final_key)-1] ^= 0x87; + + // If the final block was not complete, calculate K2 from K1 + if (need_padding) { + msb = final_key[0] & 0x80; + shift_left(final_key, sizeof(final_key)); + if (msb) + final_key[sizeof(final_key)-1] ^= 0x87; + } + + // Xor with K1 or K2 + for (u1_t i = 0; i < sizeof(final_key); ++i) + AESaux[i] ^= final_key[i]; + } + + lmic_aes_encrypt(AESaux, AESkey); + } +} + +// Run AES-CTR using the key in AESKEY and using AESAUX as the +// counter block. The last byte of the counter block will be incremented +// for every block. The given buffer will be encrypted in place. +static void os_aes_ctr (u1_t *buf, u2_t len) { + u1_t ctr[16]; + while (len) { + // Encrypt the counter block with the selected key + memcpy(ctr, AESaux, sizeof(ctr)); + lmic_aes_encrypt(ctr, AESkey); + + // Xor the payload with the resulting ciphertext + for (u1_t i = 0; i < 16 && len > 0; i++, len--, buf++) + *buf ^= ctr[i]; + + // Increment the block index byte + AESaux[15]++; + } +} + +u4_t os_aes (u1_t mode, u1_t *buf, u2_t len) { + switch (mode & ~AES_MICNOAUX) { + case AES_MIC: + os_aes_cmac(buf, len, /* prepend_aux */ !(mode & AES_MICNOAUX)); + return os_rmsbf4(AESaux); + + case AES_ENC: + // TODO: Check / handle when len is not a multiple of 16 + for (u1_t i = 0; i < len; i += 16) + lmic_aes_encrypt(buf+i, AESkey); + break; + + case AES_CTR: + os_aes_ctr(buf, len); + break; + } + return 0; +} + +#endif // !defined(USE_ORIGINAL_AES) diff --git a/aes/aes-ideetron.c b/aes/aes-ideetron.c new file mode 100644 index 0000000..1f51a60 --- /dev/null +++ b/aes/aes-ideetron.c @@ -0,0 +1,340 @@ +/****************************************************************************************** +#if defined(USE_IDEETRON_AES) +* Copyright 2015, 2016 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/****************************************************************************************** +* +* File: AES-128_V10.cpp +* Author: Gerben den Hartog +* Compagny: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +******************************************************************************************/ +/**************************************************************************************** +* +* Created on: 20-10-2015 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +* +* Firmware Version 1.0 +* First version +****************************************************************************************/ + +// This file was taken from +// https://github.com/Ideetron/RFM95W_Nexus/tree/master/LoRaWAN_V31 for +// use with LMIC. It was only cosmetically modified: +// - AES_Encrypt was renamed to lmic_aes_encrypt. +// - All other functions and variables were made static +// - Tabs were converted to 2 spaces +// - An #include and #if guard was added + +#include "../lmic/oslmic.h" + +#if defined(USE_IDEETRON_AES) + +/* +******************************************************************************************** +* Global Variables +******************************************************************************************** +*/ + +static unsigned char State[4][4]; + +static unsigned char S_Table[16][16] = { + {0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76}, + {0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0}, + {0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15}, + {0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75}, + {0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84}, + {0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF}, + {0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8}, + {0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2}, + {0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73}, + {0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB}, + {0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79}, + {0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08}, + {0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A}, + {0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E}, + {0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF}, + {0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16} +}; + +void lmic_aes_encrypt(unsigned char *Data, unsigned char *Key); +static void AES_Add_Round_Key(unsigned char *Round_Key); +static unsigned char AES_Sub_Byte(unsigned char Byte); +static void AES_Shift_Rows(); +static void AES_Mix_Collums(); +static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key); + +/* +***************************************************************************************** +* Description : Function for encrypting data using AES-128 +* +* Arguments : *Data Data to encrypt is a 16 byte long arry +* *Key Key to encrypt data with is a 16 byte long arry +***************************************************************************************** +*/ +void lmic_aes_encrypt(unsigned char *Data, unsigned char *Key) +{ + unsigned char i; + unsigned char Row,Collum; + unsigned char Round = 0x00; + unsigned char Round_Key[16]; + + //Copy input to State arry + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = Data[Row + (4*Collum)]; + } + } + + //Copy key to round key + for(i = 0; i < 16; i++) + { + Round_Key[i] = Key[i]; + } + + //Add round key + AES_Add_Round_Key(Round_Key); + + //Preform 9 full rounds + for(Round = 1; Round < 10; Round++) + { + //Preform Byte substitution with S table + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = AES_Sub_Byte(State[Row][Collum]); + } + } + + //Preform Row Shift + AES_Shift_Rows(); + + //Mix Collums + AES_Mix_Collums(); + + //Calculate new round key + AES_Calculate_Round_Key(Round,Round_Key); + + //Add round key + AES_Add_Round_Key(Round_Key); + } + + //Last round whitout mix collums + //Preform Byte substitution with S table + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = AES_Sub_Byte(State[Row][Collum]); + } + } + + //Shift rows + AES_Shift_Rows(); + + //Calculate new round key + AES_Calculate_Round_Key(Round,Round_Key); + + //Add round Key + AES_Add_Round_Key(Round_Key); + + //Copy the State into the data array + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + Data[Row + (4*Collum)] = State[Row][Collum]; + } + } + +} + +/* +***************************************************************************************** +* Description : Function that add's the round key for the current round +* +* Arguments : *Round_Key 16 byte long array holding the Round Key +***************************************************************************************** +*/ +static void AES_Add_Round_Key(unsigned char *Round_Key) +{ + unsigned char Row,Collum; + + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = State[Row][Collum] ^ Round_Key[Row + (4*Collum)]; + } + } +} + +/* +***************************************************************************************** +* Description : Function that substitutes a byte with a byte from the S_Table +* +* Arguments : Byte The byte that will be substituted +* +* Return : The return is the found byte in the S_Table +***************************************************************************************** +*/ +static unsigned char AES_Sub_Byte(unsigned char Byte) +{ + unsigned char S_Row,S_Collum; + unsigned char S_Byte; + + //Split byte up in Row and Collum + S_Row = ((Byte >> 4) & 0x0F); + S_Collum = (Byte & 0x0F); + + //Find the correct byte in the S_Table + S_Byte = S_Table[S_Row][S_Collum]; + + return S_Byte; +} + +/* +***************************************************************************************** +* Description : Function that preforms the shift row operation described in the AES standard +***************************************************************************************** +*/ +static void AES_Shift_Rows() +{ + unsigned char Buffer; + + //Row 0 doesn't change + + //Shift Row 1 one left + //Store firt byte in buffer + Buffer = State[1][0]; + //Shift all bytes + State[1][0] = State[1][1]; + State[1][1] = State[1][2]; + State[1][2] = State[1][3]; + State[1][3] = Buffer; + + //Shift row 2 two left + Buffer = State[2][0]; + State[2][0] = State[2][2]; + State[2][2] = Buffer; + Buffer = State[2][1]; + State[2][1] = State[2][3]; + State[2][3] = Buffer; + + //Shift row 3 three left + Buffer = State[3][3]; + State[3][3] = State[3][2]; + State[3][2] = State[3][1]; + State[3][1] = State[3][0]; + State[3][0] = Buffer; +} + +/* +***************************************************************************************** +* Description : Function that preforms the Mix Collums operation described in the AES standard +***************************************************************************************** +*/ +static void AES_Mix_Collums() +{ + unsigned char Row,Collum; + unsigned char a[4], b[4]; + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + a[Row] = State[Row][Collum]; + b[Row] = (State[Row][Collum] << 1); + + if((State[Row][Collum] & 0x80) == 0x80) + { + b[Row] = b[Row] ^ 0x1B; + } + } + State[0][Collum] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; + State[1][Collum] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; + State[2][Collum] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; + State[3][Collum] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; + } +} + +/* +***************************************************************************************** +* Description : Function that calculaties the round key for the current round +* +* Arguments : Round Number of current Round +* *Round_Key 16 byte long array holding the Round Key +***************************************************************************************** +*/ +static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key) +{ + unsigned char i,j; + unsigned char b; + unsigned char Temp[4]; + unsigned char Buffer; + unsigned char Rcon; + + //Calculate first Temp + //Copy laste byte from previous key + for(i = 0; i < 4; i++) + { + Temp[i] = Round_Key[i+12]; + } + + //Rotate Temp + Buffer = Temp[0]; + Temp[0] = Temp[1]; + Temp[1] = Temp[2]; + Temp[2] = Temp[3]; + Temp[3] = Buffer; + + //Substitute Temp + for(i = 0; i < 4; i++) + { + Temp[i] = AES_Sub_Byte(Temp[i]); + } + + //Calculate Rcon + Rcon = 0x01; + while(Round != 1) + { + b = Rcon & 0x80; + Rcon = Rcon << 1; + if(b == 0x80) + { + Rcon = Rcon ^ 0x1b; + } + Round--; + } + + //XOR Rcon + Temp[0] = Temp[0] ^ Rcon; + + //Calculate new key + for(i = 0; i < 4; i++) + { + for(j = 0; j < 4; j++) + { + Round_Key[j + (4*i)] = Round_Key[j + (4*i)] ^ Temp[j]; + Temp[j] = Round_Key[j + (4*i)]; + } + } +} + +#endif // defined(USE_IDEETRON_AES) diff --git a/lmic/aes.c b/aes/aes-original.c similarity index 97% rename from lmic/aes.c rename to aes/aes-original.c index a461ac6..7d7e9a3 100644 --- a/lmic/aes.c +++ b/aes/aes-original.c @@ -4,7 +4,9 @@ // This file is subject to the terms and conditions defined in file 'LICENSE', // which is part of this source code package. -#include "aes.h" +#if defined(USE_ORIGINAL_AES) + +#include "../lmic/aes.h" // global area for passing parameters (aux, key) and for storing round keys @@ -205,10 +207,10 @@ static const u4_t AES_E4[256] = { r0 ^= AES_E1[ (i>>24)] #define AES_expr(a,r0,r1,r2,r3,i) a = ki[i]; \ - a ^= (AES_S[ r0>>24 ]<<24); \ - a ^= (AES_S[u1(r1>>16)]<<16); \ - a ^= (AES_S[u1(r2>> 8)]<< 8); \ - a ^= AES_S[u1(r3) ] + a ^= ((u4_t)AES_S[ r0>>24 ]<<24); \ + a ^= ((u4_t)AES_S[u1(r1>>16)]<<16); \ + a ^= ((u4_t)AES_S[u1(r2>> 8)]<< 8); \ + a ^= (u4_t)AES_S[u1(r3) ] // generate 1+10 roundkeys for encryption with 128-bit key // read 128-bit key from AESKEY in MSBF, generate roundkey words in place @@ -224,10 +226,10 @@ static void aesroundkeys () { for( ; i<44; i++ ) { if( i%4==0 ) { // b = SubWord(RotWord(b)) xor Rcon[i/4] - b = (AES_S[u1(b >> 16)] << 24) ^ - (AES_S[u1(b >> 8)] << 16) ^ - (AES_S[u1(b) ] << 8) ^ - (AES_S[ b >> 24 ] ) ^ + b = ((u4_t)AES_S[u1(b >> 16)] << 24) ^ + ((u4_t)AES_S[u1(b >> 8)] << 16) ^ + ((u4_t)AES_S[u1(b) ] << 8) ^ + ((u4_t)AES_S[ b >> 24 ] ) ^ AES_RCON[(i-4)/4]; } AESKEY[i] = b ^= AESKEY[i-4]; @@ -376,3 +378,5 @@ u4_t os_aes (u1_t mode, u1_t* buf, u2_t len) { #pragma GCC diagnostic pop #endif + +#endif // defined(USE_ORIGINAL_AES) diff --git a/lmic/aes.h b/lmic/aes.h index 131981c..9c19dd0 100644 --- a/lmic/aes.h +++ b/lmic/aes.h @@ -8,6 +8,10 @@ #include "oslmic.h" +#ifdef __cplusplus +extern "C"{ +#endif + // ====================================================================== // AES support // !!Keep in sync with lorabase.hpp!! @@ -28,4 +32,8 @@ extern u1_t* AESaux; u4_t os_aes (u1_t mode, u1_t* buf, u2_t len); #endif +#ifdef __cplusplus +} // extern "C" +#endif + #endif // _aes_h_ diff --git a/lmic/debug.c b/lmic/debug.c index 4a7f161..dd432a7 100644 --- a/lmic/debug.c +++ b/lmic/debug.c @@ -4,12 +4,12 @@ // This file is subject to the terms and conditions defined in file 'LICENSE', // which is part of this source code package. +#include "lmic.h" + #ifdef CFG_DEBUG #include -#include "lmic.h" - void debug_led (int val) { hal_debug_led(val); } @@ -18,26 +18,26 @@ void debug_str (const char* str) { hal_debug_str(str); } -static int itoa (char* buf, unsigned int val, int base, int mindigits, int exp, int prec, char sign) { +static int debug_itoa (char* buf, u4_t val, int base, int mindigits, int exp, int prec, char sign) { char num[33], *p = num, *b = buf; if (sign) { - if ((int) val < 0) { - val = -val; - *b++ = '-'; - } else if (sign != '-') { - *b++ = sign; // space or plus - } + if ((s4_t) val < 0) { + val = -val; + *b++ = '-'; + } else if (sign != '-') { + *b++ = sign; // space or plus + } } if (mindigits > 32) { - mindigits = 32; + mindigits = 32; } do { - int m = val % base; + int m = val % base; *p++ = (m <= 9) ? m + '0' : m - 10 + 'A'; - if (p - num == exp) *p++ = '.'; + if (p - num == exp) *p++ = '.'; } while ( (val /= base) || p - num < mindigits ); do { - *b++ = *--p; + *b++ = *--p; } while (p > num + exp - prec); *b = 0; return b - buf; @@ -45,13 +45,13 @@ static int itoa (char* buf, unsigned int val, int base, int mindigits, int exp, static int strpad (char *buf, int size, const char *str, int len, int width, int leftalign, char pad) { if (len > width) { - width = len; + width = len; } if (width > size) { - width = size; + width = size; } for (int i = 0, npad = width - len; i < width; i++) { - buf[i] = (leftalign) ? ((i < len) ? str[i] : pad) : ((i < npad) ? pad : str[i - npad]); + buf[i] = (leftalign) ? ((i < len) ? str[i] : pad) : ((i < npad) ? pad : str[i - npad]); } return width; } @@ -70,7 +70,6 @@ static const char* const evnames[] = { [EV_LOST_TSYNC] = "LOST_TSYNC", [EV_RESET] = "RESET", [EV_RXCOMPLETE] = "RXCOMPLETE", - [EV_ADR_BACKOFF] = "ADR_BACKOFF", [EV_LINK_DEAD] = "LINK_DEAD", [EV_LINK_ALIVE] = "LINK_ALIVE", [EV_SCAN_FOUND] = "SCAN_FOUND", @@ -83,154 +82,167 @@ static const char* const evnames[] = { static int debug_vsnprintf(char *str, int size, const char *format, va_list arg) { char c, *dst = str, *end = str + size - 1; - int width, left, base, zero, space, plus, prec, sign; + int width, left, base, zero, space, plus, prec, sign, longint; while ( (c = *format++) && dst < end ) { - if (c != '%') { - *dst++ = c; - } else { - // flags - width = prec = left = zero = sign = space = plus = 0; - while ( (c = *format++) ) { - if (c == '-') left = 1; - else if (c == ' ') space = 1; - else if (c == '+') plus = 1; - else if (c == '0') zero = 1; - else break; - } - // width - if (c == '*') { - width = va_arg(arg, int); - c = *format++; - } else { - while (c >= '0' && c <= '9') { - width = width * 10 + c - '0'; - c = *format++; - } - } - // precision - if (c == '.') { - c = *format++; - if (c == '*') { - prec = va_arg(arg, int); - c = *format++; - } else { - while (c >= '0' && c <= '9') { - prec = prec * 10 + c - '0'; - c = *format++; - } - } - } - // conversion specifiers - switch (c) { - case 'c': // character - c = va_arg(arg, int); - // fallthrough - case '%': // percent literal - *dst++ = c; - break; - case 's': { // nul-terminated string - char *s = va_arg(arg, char *); - int l = strlen(s); - if(prec && l > prec) { - l = prec; - } - dst += strpad(dst, end - dst, s, l, width, left, ' '); - break; - } - case 'd': // signed integer as decimal - sign = (plus) ? '+' : (space) ? ' ' : '-'; - // fallthrough - case 'u': // unsigned integer as decimal - base = 10; - goto numeric; - case 'x': - case 'X': // unsigned integer as hex - base = 16; - goto numeric; - case 'b': // unsigned integer as binary - base = 2; - numeric: { - char num[33], pad = ' '; - if (zero && left == 0 && prec == 0) { - prec = width - 1; // have itoa() do the leading zero-padding for correct placement of sign - pad = '0'; - } - int len = itoa(num, va_arg(arg, int), base, prec, 0, 0, sign); - dst += strpad(dst, end - dst, num, len, width, left, pad); - break; - } - case 'F': { // signed integer and exponent as fixed-point decimal - char num[33], pad = (zero && left == 0) ? '0' : ' '; - int val = va_arg(arg, int); - int exp = va_arg(arg, int); - int len = itoa(num, val, 10, exp + 2, exp, (prec) ? prec : exp, (plus) ? '+' : (space) ? ' ' : '-'); - dst += strpad(dst, end - dst, num, len, width, left, pad); - break; - } - case 'e': { // LMIC event name - int ev = va_arg(arg, int); - const char *evn = (ev < sizeof(evnames) / sizeof(evnames[0]) && evnames[ev]) ? evnames[ev] : "UNKNOWN"; - dst += strpad(dst, end - dst, evn, strlen(evn), width, left, ' '); - break; - } - case 'E': { // EUI64, lsbf (xx-xx-xx-xx-xx-xx-xx-xx) - char buf[23], *p = buf; - unsigned char *eui = va_arg(arg, unsigned char *); - for (int i = 7; i >= 0; i--) { - p += itoa(p, eui[i], 16, 2, 0, 0, 0); - if (i) *p++ = '-'; - } - dst += strpad(dst, end - dst, buf, 23, width, left, ' '); - break; - } - case 't': // ostime_t (hh:mm:ss.mmm) - case 'T': { // osxtime_t (ddd.hh:mm:ss) - char buf[12], *p = buf; - uint64_t t = ((c == 'T') ? va_arg(arg, uint64_t) : va_arg(arg, uint32_t)) * 1000 / OSTICKS_PER_SEC; - int ms = t % 1000; - t /= 1000; - int sec = t % 60; - t /= 60; - int min = t % 60; - t /= 60; - int hr = t % 24; - t /= 24; - int day = t; - if (c == 'T') { - p += itoa(p, day, 10, 3, 0, 0, 0); - *p++ = '.'; - } - p += itoa(p, hr, 10, 2, 0, 0, 0); - *p++ = ':'; - p += itoa(p, min, 10, 2, 0, 0, 0); - *p++ = ':'; - p += itoa(p, sec, 10, 2, 0, 0, 0); - if (c == 't') { - *p++ = '.'; - p += itoa(p, ms, 10, 3, 0, 0, 0); - } - dst += strpad(dst, end - dst, buf, 12, width, left, ' '); - break; - } - case 'h': { // buffer+length as hex dump (no field padding, but precision/maxsize truncation) - unsigned char *buf = va_arg(arg, unsigned char *); - int len = va_arg(arg, int); - char *top = (prec == 0 || dst + prec > end) ? end : dst + prec; - while (len--) { - if ((len == 0 && top - dst >= 2) || top - dst >= 2 + space + 2) { - dst += itoa(dst, *buf++, 16, 2, 0, 0, 0); - if(space && len && dst < top) *dst++ = ' '; - } else { - while (dst < top) *dst++ = '.'; - } - } - break; - } - default: // (also catch '\0') - goto stop; - } - } + if (c != '%') { + *dst++ = c; + } else { + // flags + width = prec = left = zero = sign = space = plus = longint = 0; + while ( (c = *format++) ) { + if (c == '-') left = 1; + else if (c == ' ') space = 1; + else if (c == '+') plus = 1; + else if (c == '0') zero = 1; + else break; + } + // width + if (c == '*') { + width = va_arg(arg, int); + c = *format++; + } else { + while (c >= '0' && c <= '9') { + width = width * 10 + c - '0'; + c = *format++; + } + } + // precision + if (c == '.') { + c = *format++; + if (c == '*') { + prec = va_arg(arg, int); + c = *format++; + } else { + while (c >= '0' && c <= '9') { + prec = prec * 10 + c - '0'; + c = *format++; + } + } + } + // length + if(c == 'l') { + c = *format++; + longint = 1; + } + // conversion specifiers + switch (c) { + case 'c': // character + c = va_arg(arg, int); + // fallthrough + case '%': // percent literal + *dst++ = c; + break; + case 's': { // nul-terminated string + char *s = va_arg(arg, char *); + int l = strlen(s); + if(prec && l > prec) { + l = prec; + } + dst += strpad(dst, end - dst, s, l, width, left, ' '); + break; + } + case 'd': // signed integer as decimal + sign = (plus) ? '+' : (space) ? ' ' : '-'; + // fallthrough + case 'u': // unsigned integer as decimal + base = 10; + goto numeric; + case 'x': + case 'X': // unsigned integer as hex + base = 16; + goto numeric; + case 'b': // unsigned integer as binary + base = 2; + numeric: { + char num[33], pad = ' '; + if (zero && left == 0 && prec == 0) { + prec = width - 1; // have debug_itoa() do the leading zero-padding for correct placement of sign + pad = '0'; + } + u4_t val = longint ? va_arg(arg, long) : va_arg(arg, int); + int len = debug_itoa(num, val, base, prec, 0, 0, sign); + dst += strpad(dst, end - dst, num, len, width, left, pad); + break; + } + case 'F': { // signed integer and exponent as fixed-point decimal + char num[33], pad = (zero && left == 0) ? '0' : ' '; + u4_t val = va_arg(arg, u4_t); + int exp = va_arg(arg, int); + int len = debug_itoa(num, val, 10, exp + 2, exp, (prec) ? prec : exp, (plus) ? '+' : (space) ? ' ' : '-'); + dst += strpad(dst, end - dst, num, len, width, left, pad); + break; + } + case 'e': { // LMIC event name + unsigned ev = va_arg(arg, unsigned); + const char *evn = (ev < sizeof(evnames) / sizeof(evnames[0]) && evnames[ev]) ? evnames[ev] : "UNKNOWN"; + dst += strpad(dst, end - dst, evn, strlen(evn), width, left, ' '); + break; + } + case 'E': { // EUI64, lsbf (xx-xx-xx-xx-xx-xx-xx-xx) + char buf[23], *p = buf; + unsigned char *eui = va_arg(arg, unsigned char *); + for (int i = 7; i >= 0; i--) { + p += debug_itoa(p, eui[i], 16, 2, 0, 0, 0); + if (i) *p++ = '-'; + } + dst += strpad(dst, end - dst, buf, 23, width, left, ' '); + break; + } + case 't': // ostime_t (hh:mm:ss.mmm) + case 'T': { // osxtime_t (ddd.hh:mm:ss) + #if defined(CFG_DEBUG_RAW_TIMESTAMPS) + if (c == 't') { + longint = 1; + base = 10; + goto numeric; + } + #endif + char buf[12], *p = buf; + uint64_t t = ((c == 'T') ? va_arg(arg, uint64_t) : va_arg(arg, uint32_t)) * 1000 / OSTICKS_PER_SEC; + int ms = t % 1000; + t /= 1000; + int sec = t % 60; + t /= 60; + int min = t % 60; + t /= 60; + int hr = t % 24; + t /= 24; + int day = t; + if (c == 'T') { + p += debug_itoa(p, day, 10, 3, 0, 0, 0); + *p++ = '.'; + } + p += debug_itoa(p, hr, 10, 2, 0, 0, 0); + *p++ = ':'; + p += debug_itoa(p, min, 10, 2, 0, 0, 0); + *p++ = ':'; + p += debug_itoa(p, sec, 10, 2, 0, 0, 0); + if (c == 't') { + *p++ = '.'; + p += debug_itoa(p, ms, 10, 3, 0, 0, 0); + } + dst += strpad(dst, end - dst, buf, 12, width, left, ' '); + break; + } + case 'h': { // buffer+length as hex dump (no field padding, but precision/maxsize truncation) + unsigned char *buf = va_arg(arg, unsigned char *); + int len = va_arg(arg, int); + char *top = (prec == 0 || dst + prec > end) ? end : dst + prec; + while (len--) { + if ((len == 0 && top - dst >= 2) || top - dst >= 2 + space + 2) { + dst += debug_itoa(dst, *buf++, 16, 2, 0, 0, 0); + if(space && len && dst < top) *dst++ = ' '; + } else { + while (dst < top) *dst++ = '.'; + } + } + break; + } + default: // (also catch '\0') + goto stop; + } + } } stop: *dst++ = 0; @@ -247,7 +259,7 @@ int debug_snprintf (char *str, int size, const char *format, ...) { return length; } -void debug_printf (char const *format, ...) { +void debug_printf_real (char const *format, ...) { char buf[256]; va_list arg; diff --git a/lmic/debug.h b/lmic/debug.h index 23ced6c..205064e 100644 --- a/lmic/debug.h +++ b/lmic/debug.h @@ -9,18 +9,42 @@ #ifndef CFG_DEBUG -#define debug_snprintf(s,n,f,...) do { } while (0) -#define debug_printf(f,...) do { } while (0) -#define debug_str(s) do { } while (0) -#define debug_led(val) do { } while (0) +#define debug_snprintf(s,n,f,...) do { } while (0) +#define debug_printf(f,...) do { } while (0) +#define debug_printf_continue(f,...) do { } while (0) +#define debug_str(s) do { } while (0) +#define debug_led(val) do { } while (0) +#define debug_verbose_printf(f,...) do { } while (0) + +#ifdef CFG_DEBUG_VERBOSE +#error CFG_DEBUG_VERBOSE requires CFG_DEBUG +#endif #else +#include + +// When printing 16-bit values, the code uses %d and friends (which +// accept an `int`-sized argument). This works, because these arguments +// are integer-promoted automatically (provided int is at least 16-bits, +// but the C language requires this +// When printing 32-bit values, the code uses %ld (which accepts a +// `long`-sized argument). This only works when `long` is actually +// 32-bits. This is the case on at least ARM and AVR, but to be sure, +// check at compiletime. Since there is no portable LONG_WIDTH, we use +// LONG_MAX instead. +#if LONG_MAX != ((1 << 31) - 1) +#error "long is not exactly 32 bits, printing will fail" +#endif + // write formatted string to buffer int debug_snprintf (char *str, int size, const char *format, ...); // write formatted string to USART -void debug_printf (char const *format, ...); +void debug_printf_real (char const *format, ...); +#define debug_printf(format, ...) debug_printf_real("%10t: " format, os_getTime(), ## __VA_ARGS__) +// To continue a line, omit the timestamp +#define debug_printf_continue(format, ...) debug_printf_real(format, ## __VA_ARGS__) // write nul-terminated string to USART void debug_str (const char* str); @@ -28,6 +52,14 @@ void debug_str (const char* str); // set LED state void debug_led (int val); +#ifndef CFG_DEBUG_VERBOSE +#define debug_verbose_printf(f,...) do { } while (0) +#define debug_verbose_printf_continue(f,...) do { } while (0) +#else +#define debug_verbose_printf debug_printf +#define debug_verbose_printf_continue debug_printf_continue +#endif + #endif #endif diff --git a/lmic/hal.h b/lmic/hal.h index 05737c2..acc9545 100644 --- a/lmic/hal.h +++ b/lmic/hal.h @@ -8,6 +8,10 @@ #ifndef _hal_hpp_ #define _hal_hpp_ +#ifdef __cplusplus +extern "C"{ +#endif + #ifdef HAL_IMPL_INC #include HAL_IMPL_INC #endif @@ -31,16 +35,19 @@ void hal_watchcount (int cnt); #define HAL_ANTSW_TX2 3 void hal_ant_switch (u1_t val); +#if defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) /* * control radio TCXO power (0=off, 1=on) * (return if TCXO is present and in use) */ bool hal_pin_tcxo (u1_t val); +#endif // defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) /* * control radio RST pin (0=low, 1=high, 2=floating) + * (return true if reset pin is connected) */ -void hal_pin_rst (u1_t val); +bool hal_pin_rst (u1_t val); /* * wait until radio BUSY pin is low @@ -56,6 +63,22 @@ void hal_pin_busy_wait (void); #define HAL_IRQMASK_DIO3 (1<<3) void hal_irqmask_set (int mask); +#if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) +/* + * Returns true if DIO3 should control the TCXO. + * TODO: Reconsider this HAL function, maybe integrate with hal_pin_tcxo + * somehow? + */ +bool hal_dio3_controls_tcxo (void); + +/* + * Returns true if DIO2 should control the RXTX switch. + * TODO: Reconsider this HAL function, maybe integrate with + * hal_ant_switch somehow? + */ +bool hal_dio2_controls_rxtx (void); +#endif // defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + /* * drive radio NSS pin (on=low, off=high). */ @@ -160,4 +183,8 @@ bool hal_set_update (void* ptr); void hal_logEv (uint8_t evcat, uint8_t evid, uint32_t evparam); +#ifdef __cplusplus +} // extern "C" +#endif + #endif // _hal_hpp_ diff --git a/lmic/lce.c b/lmic/lce.c index 256f9b8..e7f89a3 100644 --- a/lmic/lce.c +++ b/lmic/lce.c @@ -21,17 +21,17 @@ bool lce_processJoinAccept (u1_t* jacc, u1_t jacclen, u2_t devnonce) { #if defined(CFG_lorawan11) u1_t optneg = jacc[OFF_JA_DLSET] & JA_DLS_OPTNEG; if( optneg ) { - os_moveMem(jacc+OFF_JA_JOINNONCE+2, jacc+OFF_JA_JOINNONCE, jacclen-OFF_JA_JOINNONCE); - os_wlsbf2(jacc+OFF_JA_JOINNONCE, devnonce); - jacclen += 2; + os_moveMem(jacc+OFF_JA_JOINNONCE+2, jacc+OFF_JA_JOINNONCE, jacclen-OFF_JA_JOINNONCE); + os_wlsbf2(jacc+OFF_JA_JOINNONCE, devnonce); + jacclen += 2; } #endif os_getNwkKey(AESkey); u4_t mic2 = os_aes(AES_MIC|AES_MICNOAUX, jacc, jacclen); #if defined(CFG_lorawan11) if( optneg ) { // Restore orig frame - jacclen -= 2; - os_moveMem(jacc+OFF_JA_JOINNONCE, jacc+OFF_JA_JOINNONCE+2, jacclen-OFF_JA_JOINNONCE); + jacclen -= 2; + os_moveMem(jacc+OFF_JA_JOINNONCE, jacc+OFF_JA_JOINNONCE+2, jacclen-OFF_JA_JOINNONCE); os_wlsbf4(jacc+jacclen, mic1); } #endif @@ -54,12 +54,12 @@ bool lce_processJoinAccept (u1_t* jacc, u1_t jacclen, u2_t devnonce) { os_aes(AES_ENC, nwkskey, 16); #if defined(CFG_lorawan11) if( optneg ) { - os_getNwkKey(AESkey); - os_aes(AES_ENC, LMIC.lceCtx.nwkSKeyDn, 16); - os_getAppKey(AESkey); + os_getNwkKey(AESkey); + os_aes(AES_ENC, LMIC.lceCtx.nwkSKeyDn, 16); + os_getAppKey(AESkey); } else { - os_copyMem(LMIC.lceCtx.nwkSKeyDn, nwkskey, 16); - os_getNwkKey(AESkey); + os_copyMem(LMIC.lceCtx.nwkSKeyDn, nwkskey, 16); + os_getNwkKey(AESkey); } #else os_getNwkKey(AESkey); @@ -131,7 +131,7 @@ u4_t lce_micKey0 (u4_t devaddr, u4_t seqno, u1_t* pdu, int len) { void lce_cipher (s1_t keyid, u4_t devaddr, u4_t seqno, int cat, u1_t* payload, int len) { if(len <= 0 || (cat==LCE_SCC_UP && (LMIC.opmode & OP_NOCRYPT)) ) { - return; + return; } const u1_t* key; if( keyid == LCE_NWKSKEY ) { diff --git a/lmic/lce.h b/lmic/lce.h index fb91310..e863636 100644 --- a/lmic/lce.h +++ b/lmic/lce.h @@ -8,6 +8,10 @@ #include "oslmic.h" +#ifdef __cplusplus +extern "C"{ +#endif + // Some keyids: #define LCE_APPSKEY (-2) #define LCE_NWKSKEY (-1) @@ -55,4 +59,8 @@ typedef struct lce_ctx { } lce_ctx_t; +#ifdef __cplusplus +} // extern "C" +#endif + #endif // _lce_h_ diff --git a/lmic/lmic.c b/lmic/lmic.c index 74517bd..6ebdef9 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -41,11 +41,9 @@ #endif DEFINE_LMIC; -DECL_ON_LMIC_EVENT; // Fwd decls. static void engineUpdate(void); -static void startScan (void); // ================================================================================ @@ -55,7 +53,7 @@ static void startScan (void); #if !defined(os_rlsbf2) u2_t os_rlsbf2 (const u1_t* buf) { - return (u2_t)(buf[0] | (buf[1]<<8)); + return (u2_t)((u2_t)buf[0] | ((u2_t)buf[1]<<8)); } #endif @@ -67,14 +65,14 @@ u2_t os_rmsbf2 (const u1_t* buf) { #if !defined(os_rlsbf4) u4_t os_rlsbf4 (const u1_t* buf) { - return (u4_t)(buf[0] | (buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24)); + return (u4_t)((u4_t)buf[0] | ((u4_t)buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24)); } #endif #if !defined(os_rmsbf4) u4_t os_rmsbf4 (const u1_t* buf) { - return (u4_t)(buf[3] | (buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24)); + return (u4_t)((u4_t)buf[3] | ((u4_t)buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24)); } #endif @@ -146,45 +144,49 @@ u2_t os_crc16 (u1_t* data, uint len) { // ================================================================================ // BEG NEW REGION STUFF +#define LORA_UP_RPS(sf,bw) (MAKE_LORA_RPS((sf),(bw),CR_4_5,0,0)) +#define LORA_DN_RPS(sf,bw) (MAKE_LORA_RPS((sf),(bw),CR_4_5,0,1)) +#define FSK_UP_RPS() (MAKE_FSK_RPS(0)) + // data rate tables #ifdef REG_DRTABLE_EU static const u1_t DR2RPS_EU[16] = { - LWUPDR(SF12, BW125), LWUPDR(SF11, BW125), LWUPDR(SF10, BW125), LWUPDR(SF9, BW125), - LWUPDR(SF8, BW125), LWUPDR(SF7, BW125), LWUPDR(SF7, BW250), LWUPDR(FSK, BW125), - ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, - ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + LORA_UP_RPS(SF12, BW125), LORA_UP_RPS(SF11, BW125), LORA_UP_RPS(SF10, BW125), LORA_UP_RPS(SF9, BW125), + LORA_UP_RPS(SF8, BW125), LORA_UP_RPS(SF7, BW125), LORA_UP_RPS(SF7, BW250), FSK_UP_RPS(), + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, }; #endif #ifdef REG_DRTABLE_125kHz static const u1_t DR2RPS_125kHz[16] = { - LWUPDR(SF12, BW125), LWUPDR(SF11, BW125), LWUPDR(SF10, BW125), LWUPDR(SF9, BW125), - LWUPDR(SF8, BW125), LWUPDR(SF7, BW125), LWUPDR(SF7, BW250), ILLEGAL_RPS, - ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, - ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + LORA_UP_RPS(SF12, BW125), LORA_UP_RPS(SF11, BW125), LORA_UP_RPS(SF10, BW125), LORA_UP_RPS(SF9, BW125), + LORA_UP_RPS(SF8, BW125), LORA_UP_RPS(SF7, BW125), LORA_UP_RPS(SF7, BW250), ILLEGAL_RPS, + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, }; #endif #ifdef REG_DRTABLE_US static const u1_t DR2RPS_US[16] = { - LWUPDR(SF10, BW125), LWUPDR(SF9, BW125), LWUPDR(SF8, BW125), LWUPDR(SF7, BW125), - LWUPDR(SF8, BW500), ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, - LWDNDR(SF12, BW500), LWDNDR(SF11, BW500), LWDNDR(SF10, BW500), LWDNDR(SF9, BW500), - LWDNDR(SF8, BW500), LWDNDR(SF7, BW500), ILLEGAL_RPS, ILLEGAL_RPS, + LORA_UP_RPS(SF10, BW125), LORA_UP_RPS(SF9, BW125), LORA_UP_RPS(SF8, BW125), LORA_UP_RPS(SF7, BW125), + LORA_UP_RPS(SF8, BW500), ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + LORA_DN_RPS(SF12, BW500), LORA_DN_RPS(SF11, BW500), LORA_DN_RPS(SF10, BW500), LORA_DN_RPS(SF9, BW500), + LORA_DN_RPS(SF8, BW500), LORA_DN_RPS(SF7, BW500), ILLEGAL_RPS, ILLEGAL_RPS, }; #endif #ifdef REG_DRTABLE_AU static const u1_t DR2RPS_AU[16] = { - LWUPDR(SF12, BW125), LWUPDR(SF11, BW125), LWUPDR(SF10, BW125), LWUPDR(SF9, BW125), - LWUPDR(SF8, BW125), LWUPDR(SF7, BW125), LWUPDR(SF7, BW500), ILLEGAL_RPS, - LWDNDR(SF12, BW500), LWDNDR(SF11, BW500), LWDNDR(SF10, BW500), LWDNDR(SF9, BW500), - LWDNDR(SF8, BW500), LWDNDR(SF7, BW500), ILLEGAL_RPS, ILLEGAL_RPS, + LORA_UP_RPS(SF12, BW125), LORA_UP_RPS(SF11, BW125), LORA_UP_RPS(SF10, BW125), LORA_UP_RPS(SF9, BW125), + LORA_UP_RPS(SF8, BW125), LORA_UP_RPS(SF7, BW125), LORA_UP_RPS(SF8, BW500), ILLEGAL_RPS, + LORA_DN_RPS(SF12, BW500), LORA_DN_RPS(SF11, BW500), LORA_DN_RPS(SF10, BW500), LORA_DN_RPS(SF9, BW500), + LORA_DN_RPS(SF8, BW500), LORA_DN_RPS(SF7, BW500), ILLEGAL_RPS, ILLEGAL_RPS, }; #endif #ifdef REG_DRTABLE_IN static const u1_t DR2RPS_IN[16] = { - LWUPDR(SF12, BW125), LWUPDR(SF11, BW125), LWUPDR(SF10, BW125), LWUPDR(SF9, BW125), - LWUPDR(SF8, BW125), LWUPDR(SF7, BW125), ILLEGAL_RPS, LWUPDR(FSK, BW125), - ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, - ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + LORA_UP_RPS(SF12, BW125), LORA_UP_RPS(SF11, BW125), LORA_UP_RPS(SF10, BW125), LORA_UP_RPS(SF9, BW125), + LORA_UP_RPS(SF8, BW125), LORA_UP_RPS(SF7, BW125), ILLEGAL_RPS, FSK_UP_RPS(), + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, }; #endif #ifdef REG_DYN @@ -204,7 +206,7 @@ static const rfuncs_t RFUNCS_FIX; // fwd decl static const region_t REGIONS[REGIONS_COUNT] = { #ifdef CFG_eu868 [REGION_EU868] = { - .regcode = REGCODE_EU868, + .regcode = REGCODE_EU868, .flags = 0, .minFreq = 863000000, .maxFreq = 870000000, @@ -220,6 +222,7 @@ static const region_t REGIONS[REGIONS_COUNT] = { { 865000000, 868000000, CAP_CENTI, 16 }, // h1.4 { 868000000, 868600000, CAP_CENTI, 16 }, // h1.5 { 869700000, 870000000, CAP_CENTI, 16 }, // h1.9 + { 862000000, 863000000, CAP_MILLI, 16 }, // h0, max 350kHz BW (not enforced) { 863000000, 865000000, CAP_MILLI, 16 }, // h1.3 { 868700000, 869200000, CAP_MILLI, 16 }, // h1.6 }, @@ -236,14 +239,14 @@ static const region_t REGIONS[REGIONS_COUNT] = { .rx1DrOff = RX1DR_OFFSETS(0, 0, 1, 2, 3, 4, 5, ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff), .dr2rps = DR2RPS_EU, - .dr2maxAppPload = {51, 51, 51, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0}, + .dr2maxAppPload = {51, 51, 51, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0}, .rfuncs = &RFUNCS_DYN, }, #endif #ifdef CFG_as923 [REGION_AS923] = { - .regcode = REGCODE_AS923, + .regcode = REGCODE_AS923, .flags = 0, .minFreq = 920000000, .maxFreq = 928000000, @@ -264,13 +267,13 @@ static const region_t REGIONS[REGIONS_COUNT] = { .maxEirp = 16, .rx1DrOff = RX1DR_OFFSETS(0, 0, 1, 2, 3, 4, 5, -1, -2), .dr2rps = DR2RPS_EU, - .dr2maxAppPload = { 0, 0, 11, 53, 125, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0}, + .dr2maxAppPload = { 0, 0, 11, 53, 125, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0}, .rfuncs = &RFUNCS_DYN, }, #endif #ifdef CFG_us915 [REGION_US915] = { - .regcode = REGCODE_US915, + .regcode = REGCODE_US915, .flags = REG_FIXED, .minFreq = 902000000, .maxFreq = 928000000, @@ -292,13 +295,13 @@ static const region_t REGIONS[REGIONS_COUNT] = { ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff), .dr2rps = DR2RPS_US, - .dr2maxAppPload = { 11, 53, 125, 242, 242, 0, 0, 0, 53, 129, 242, 242, 242, 242, 0, 0}, + .dr2maxAppPload = { 11, 53, 125, 242, 242, 0, 0, 0, 53, 129, 242, 242, 242, 242, 0, 0}, .rfuncs = &RFUNCS_FIX, }, #endif #ifdef CFG_au915 [REGION_AU915] = { - .regcode = REGCODE_AU915, + .regcode = REGCODE_AU915, .flags = REG_FIXED, .minFreq = 915000000, .maxFreq = 928000000, @@ -320,13 +323,13 @@ static const region_t REGIONS[REGIONS_COUNT] = { .rx1DrOff = RX1DR_OFFSETS(8, 0, 1, 2, 3, 4, 5, ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff), .dr2rps = DR2RPS_AU, - .dr2maxAppPload = {0, 0, 11, 53, 125, 242, 242, 0, 53, 129, 242, 242, 242, 242, 0, 0}, + .dr2maxAppPload = {0, 0, 11, 53, 125, 242, 242, 0, 53, 129, 242, 242, 242, 242, 0, 0}, .rfuncs = &RFUNCS_FIX, }, #endif #ifdef CFG_cn470 [REGION_CN470] = { - .regcode = REGCODE_CN470, + .regcode = REGCODE_CN470, .flags = REG_FIXED, .minFreq = 470000000, .maxFreq = 510000000, @@ -347,7 +350,7 @@ static const region_t REGIONS[REGIONS_COUNT] = { .rx1DrOff = RX1DR_OFFSETS(0, 0, 1, 2, 3, 4, ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff), .dr2rps = DR2RPS_125kHz, - .dr2maxAppPload = {51, 51, 51, 115, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .dr2maxAppPload = {51, 51, 51, 115, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, .rfuncs = &RFUNCS_FIX, }, #endif @@ -379,6 +382,8 @@ static const region_t REGIONS[REGIONS_COUNT] = { #endif }; +// Workaround for Lacuna LS200 core defining this on the gcc commandline +#undef REGION #define REGION (*LMIC.region) #define isREGION(reg) (®ION == ®IONS[REGION_##reg]) @@ -413,7 +418,7 @@ static void adjAvail (avail_t* pavail, osxtime_t base) { static void setAvail (avail_t* pavail, osxtime_t t) { osxtime_t base = LMIC.baseAvail; - int v; + u4_t v; if( base > t ) { t = base; // make sure t is not in the past } @@ -438,11 +443,9 @@ static void setAvail (avail_t* pavail, osxtime_t t) { *pavail = v; } -static dr_t decDR (dr_t dr) { // move to slower DR - return dr == 0 ? 0 : dr-1; -} - static dr_t lowerDR (dr_t dr, u1_t n) { + if (dr == CUSTOM_DR) + return dr; return dr <= n ? 0 : dr-n; } @@ -461,6 +464,8 @@ static inline bit_t validDR (dr_t dr) { } static rps_t updr2rps (dr_t dr) { + if (dr == CUSTOM_DR) + return LMIC.custom_rps; return REGION.dr2rps[dr]; } @@ -468,12 +473,14 @@ static rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr), 1); } +#if !defined(DISABLE_CLASSB) static u1_t numBcnChannels() { return REG_IS_FIX() ? 8 : 1; } +#endif // !defined(DISABLE_CLASSB) static dr_t rps2dndr (rps_t rps) { - for( dr_t dr = 15; dr >= 0; dr-- ) { + for( dr_t dr = 15; dr != (dr_t)-1; dr-- ) { if( setNocrc(REGION.dr2rps[dr], 1) == rps ) { return dr; } @@ -506,12 +513,12 @@ int getSensitivity (rps_t rps) { } ostime_t calcAirTime (rps_t rps, u1_t plen) { - u1_t bw = getBw(rps); // 0,1,2 = 125,250,500kHz - u1_t sf = getSf(rps); // 0=FSK, 1..6 = SF7..12 - if( sf == FSK ) { + if( isFsk(rps) ) { return (plen+/*preamble*/5+/*syncword*/3+/*len*/1+/*crc*/2) * /*bits/byte*/8 * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000; } + u1_t bw = getBw(rps); // 0,1,2 = 125,250,500kHz + u1_t sf = getSf(rps); u1_t sfx = 4*(sf+(7-SF7)); u1_t q = sfx - 8*enDro(rps); int tmp = 8*plen - sfx + 28 + (getNocrc(rps)?0:16) - (getIh(rps)?20:0); @@ -543,6 +550,8 @@ ostime_t calcAirTime (rps_t rps, u1_t plen) { return (((ostime_t)tmp << sfx) * OSTICKS_PER_SEC + div/2) / div; } +extern inline rps_t makeLoraRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc); +extern inline rps_t makeFskRps (int nocrc); extern inline sf_t getSf (rps_t params); extern inline rps_t setSf (rps_t params, sf_t sf); extern inline bw_t getBw (rps_t params); @@ -553,8 +562,8 @@ extern inline int getNocrc (rps_t params); extern inline rps_t setNocrc (rps_t params, int nocrc); extern inline int getIh (rps_t params); extern inline rps_t setIh (rps_t params, int ih); -extern inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc); -extern inline int sameSfBw (rps_t r1, rps_t r2); +extern inline sf_t isLora (rps_t params); +extern inline sf_t isFsk (rps_t params); extern inline int enDro (rps_t params); @@ -585,14 +594,16 @@ static void decPollcnt (void) { // 500kHz | 8 9 10 11 12 13 // static ostime_t dr2hsym (dr_t dr, s1_t num) { - rps_t rps = REGION.dr2rps[dr]; + rps_t rps = updr2rps(dr); u1_t sf = getSf(rps); u1_t bw = getBw(rps); - ASSERT(sf >= SF7 && sf <=SF12 && bw >= BW125 && bw <= BW500); + LMIC_STATIC_ASSERT(BW125 == 0, "BW125 assumed to be 0"); + ASSERT(sf >= SF7 && sf <= SF12 && bw <= BW500); s4_t us = num << (9 + sf - SF7 - bw); // 10 => 9 half symbol time return us2osticks(us); } +#if !defined(DISABLE_CLASSB) static ostime_t calcRxWindow (u1_t secs, dr_t dr) { ostime_t rxoff, err; @@ -636,6 +647,7 @@ static void calcBcnRxWindowFromMillis (u1_t ms, bit_t ini) { LMIC.bcnRxsyms = wsyms; LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks + cpre - whspan; } +#endif static void iniRxdErr () { // Avg(rxdErrs) == 0 @@ -655,6 +667,7 @@ static void addRxdErr (u1_t rxdelay) { LMIC.rxdErrIdx = (LMIC.rxdErrIdx + 1) % RXDERR_NUM; } +#if defined(CFG_testpin) || defined(CFG_extapi) static s4_t evalRxdErr (u4_t* span) { s4_t min = 0x7FFFFFFF, min2=0x7FFFFFFF; s4_t max = 0x80000000, max2=0x80000000; @@ -672,6 +685,7 @@ static s4_t evalRxdErr (u4_t* span) { *span = max2-min2; return (sum - max - min + ((RXDERR_NUM-2)/2)) / (RXDERR_NUM-2); } +#endif static void adjustByRxdErr (u1_t rxdelay, u1_t dr) { #ifdef CFG_testpin @@ -683,10 +697,13 @@ static void adjustByRxdErr (u1_t rxdelay, u1_t dr) { span /= dr2hsym(dr,1); // additional half symbols LMIC.rxsyms += (span + 1) >> 1; LMIC.rxtime -= span*hsym; +#else // CFG_testpin + (void)rxdelay; (void)dr; // unused #endif // CFG_testpin } +#if !defined(DISABLE_CLASSB) // Setup scheduled RX window (ping/multicast slot) static void rxschedInit (rxsched_t* rxsched) { // Relates to the standard in the following way: @@ -725,6 +742,7 @@ static bit_t rxschedNext (rxsched_t* rxsched, ostime_t cando) { rxsched->rxsyms = LMIC.rxsyms; goto again; } +#endif static ostime_t rndDelay (u1_t secSpan) { @@ -748,11 +766,13 @@ static void txDelay (ostime_t reftime, u1_t secSpan) { static void setDrJoin (u1_t reason, dr_t dr) { + (void)reason; // unused LMIC.datarate = dr; } static void setDrTxpow (u1_t reason, dr_t dr, s1_t powadj) { + (void)reason; // unused if( powadj != KEEP_TXPOWADJ ) LMIC.txPowAdj = powadj; if( LMIC.datarate != dr ) { @@ -762,6 +782,7 @@ static void setDrTxpow (u1_t reason, dr_t dr, s1_t powadj) { } +#if !defined(DISABLE_CLASSB) void LMIC_stopPingable (void) { LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI); } @@ -783,10 +804,11 @@ u1_t LMIC_setPingable (u1_t intvExp) { LMIC_enableTracking(3); return 2; } +#endif static freq_t rdFreq (u1_t* p) { - freq_t freq = ((p[2] << 16) | (p[1] << 8) | p[0]) * 100; + freq_t freq = (((freq_t)p[2] << 16) | ((freq_t)p[1] << 8) | (freq_t)p[0]) * 100; if( freq != 0 && (freq < REGION.minFreq || freq > REGION.maxFreq) ) return -1; return freq; @@ -795,6 +817,8 @@ static freq_t rdFreq (u1_t* p) { // Shard between REG_DYN/REG_FIX static dr_t prepareDnDr (dr_t updr) { + ASSERT(updr != CUSTOM_DR); + s1_t dndr = (s1_t) updr + REGION.rx1DrOff[LMIC.dn1DrOffIdx]; dr_t dr8 = REGION.dr2rps[8]; s1_t mindr = (dr8 != ILLEGAL_RPS && getNocrc(dr8)) ? 8 : 0; @@ -813,7 +837,6 @@ static dr_t prepareDnDr (dr_t updr) { #ifdef REG_DYN static void prepareDn_dyn () { - LMIC.dndr = prepareDnDr(LMIC.dndr); // Check reconfigured DN link freq freq_t dnfreq = LMIC.dyn.chDnFreq[LMIC.txChnl]; if( dnfreq ) { @@ -838,7 +861,7 @@ static drmap_t all125up () { drmap_t map = 0; for( u1_t dr=0; dr < 16; dr++ ) { rps_t rps = REGION.dr2rps[dr]; - if( rps != ILLEGAL_RPS && getSf(rps) != FSK + if( rps != ILLEGAL_RPS && isLora(rps) && getBw(rps) == BW125 && !getNocrc(rps) ) // not DN only DR map |= 1<= REGION.ccaThreshold) { // channel is not available + debug_verbose_printf("Channel %u not available due to CCA\r\n", chnl); goto unavailable; } } else { // channel decision is stable (i.e. won't change even if not used immediately) LMIC.opmode &= ~OP_NEXTCHNL; // XXX - not sure if that's necessary, since we only consider channels that can be used NOW } + debug_verbose_printf("Selected channel %u (%.2F)\r\n", chnl, LMIC.dyn.chUpFreq[chnl] & ~BAND_MASK, 6); // good to go! LMIC.txChnl = chnl; return now; @@ -1068,14 +1101,17 @@ static ostime_t nextTx_dyn (ostime_t now) { txavail = os_getXTime() + ms2osticks(100); } // Earliest duty cycle expiry or earliest time a channel might be tested again + debug_verbose_printf("Channel(s) will become available at %t\r\n", (ostime_t)txavail); return (ostime_t) txavail; } +#if !defined(DISABLE_CLASSB) static void setBcnRxParams_dyn (void) { LMIC.dataLen = 0; LMIC.freq = LMIC.bcnFreq ? LMIC.bcnFreq : REGION.beaconFreq; LMIC.rps = setIh(setNocrc(dndr2rps(REGION.beaconDr), 1), REGION.beaconLen); } +#endif #endif // REG_DYN @@ -1133,7 +1169,7 @@ static ostime_t nextJoinState (void) { LMIC.txChnl = LMIC.fix.hoplist[LMIC.refChnl]; done: LMIC.opmode &= ~OP_NEXTCHNL; - delay = rndDelay(32); + delay = rndDelay(32); #endif } else { #ifdef REG_DYN @@ -1149,12 +1185,16 @@ static ostime_t nextJoinState (void) { failed = 1; // we have tried all DR - signal EV_JOIN_FAILED } else { - setDrJoin(DRCHG_NOJACC, decDR(LMIC.datarate)); + setDrJoin(DRCHG_NOJACC, lowerDR(LMIC.datarate, 1)); } } - delay = rndDelay(255 >> LMIC.datarate); + delay = rndDelay(255 >> LMIC.datarate); #endif } + if (failed) + debug_verbose_printf("Join failed\r\n"); + else + debug_verbose_printf("Scheduling next join at %t\r\n", os_getTime() + delay); // 1 - triggers EV_JOIN_FAILED event return (delay & ~1) | failed; @@ -1182,16 +1222,16 @@ u1_t prng_next (u1_t* prngbuf) { // generate pseudo-random permutation from start..end-1 static void perm (unsigned char* p, int start, int end, u1_t* prng) { for (int i = 0; i < (end - start); i++) { - uint32_t j = (prng_next(prng) << 8 ) | prng_next(prng); - j %= (i + 1); // has small bias - p[i] = p[j]; - p[j] = i + start; + uint32_t j = (prng_next(prng) << 8 ) | prng_next(prng); + j %= (i + 1); // has small bias + p[i] = p[j]; + p[j] = i + start; } } // generate a pseudo-random hoplist static void generateHopList (u1_t* hoplist, int nch) { - int nb = nch >> 3; // number of 8-ch blocks + int nb = nch >> 3; // number of 8-ch blocks u1_t prng[16]; // prng state memcpy(prng, "\x10hoplist", 8); @@ -1202,11 +1242,11 @@ static void generateHopList (u1_t* hoplist, int nch) { perm(bp + 1, 1, nb, prng); for (int b = 0; b < nb; b++) { - unsigned char cp[8]; - perm(cp, 0, 8, prng); // channel permutation - for (int c = 0; c < 8; c++) { - hoplist[c*nb+b] = bp[b]*8+cp[c]; - } + unsigned char cp[8]; + perm(cp, 0, 8, prng); // channel permutation + for (int c = 0; c < 8; c++) { + hoplist[c*nb+b] = bp[b]*8+cp[c]; + } } } @@ -1227,8 +1267,9 @@ static u1_t enableAllChannels_fix (void) { } } if (nch & 0xf) { - if (LMIC.fix.channelMap[nch >> 4] != (1 << (nch & 0xf)) - 1) { - LMIC.fix.channelMap[nch >> 4] = (1 << (nch & 0xf)) - 1; + u1_t newval = (1 << (nch & 0xf)) - 1; + if (LMIC.fix.channelMap[nch >> 4] != newval) { + LMIC.fix.channelMap[nch >> 4] = newval; rv = 1; } } @@ -1265,7 +1306,6 @@ static void initDefaultChannels_fix (void) { } static void prepareDn_fix () { - LMIC.dndr = prepareDnDr(LMIC.dndr); LMIC.freq = REGION.baseFreqDn + (LMIC.txChnl % (REGION.numChDnBlocks*8)) * (REGION.baseFreqFix ? DNCHSPACING_500kHz : DNCHSPACING_125kHz); @@ -1319,7 +1359,7 @@ static u1_t applyChannelMap_fix (u1_t chpage, u2_t chmap, u2_t* dest) { return 0; } if ((nch & 15) && chpage == (REGION.numChBlocks >> 1)) { // partial map in last 16bit word - chmap &= ~(~0 << (nch & 15)); + chmap &= ~(0xffff << (nch & 15)); } dest[chpage] = chmap; } @@ -1367,6 +1407,10 @@ static void updateTx_fix (ostime_t txbeg) { //XXX:BUG: this is US915/AU915 cent if( LMIC.globalDutyRate != 0 ) { LMIC.globalDutyAvail = txbeg + (airtime< maxDnLen(LMIC.rps) || (hdr & HDR_MAJOR) != HDR_MAJOR_V1 || (ftype != HDR_FTYPE_DADN && ftype != HDR_FTYPE_DCDN) ) { // Basic sanity checks failed norx: + debug_printf("Invalid downlink[window=%s]\r\n", window); LMIC.dataLen = 0; return 0; } @@ -1690,10 +1744,14 @@ static bit_t decodeFrame (void) { debug_printf("ADR: p1=%02x,dr=%d,powadj=%d,chmap=%04x,chpage=%d,nbtrans=%d\r\n", p1, dr, powadj, chmap, chpage, nbtrans); #endif - if( dr == 15 ) { + if( LMIC.datarate == CUSTOM_DR ) { + // Reject modifying a custom DR. The network probably + // does not really know how to handle this, but at least + // they know we did not change the DR. + ans &= ~MCMD_LADR_ANS_DRACK; + } else if( dr == 15 ) { dr = LMIC.datarate; // Do not change DR - } - if( !validDR(dr) || ( REG_IS_FIX() + } else if( !validDR(dr) || ( REG_IS_FIX() #ifdef REG_FIX && !checkChannel_fix(dmap, dr) #endif @@ -1740,7 +1798,7 @@ static bit_t decodeFrame (void) { freq_t freq = rdFreq(&opts[oidx+2]); oidx += 5; u1_t ans = 0; - if( validDR(dr) ) //XXX:BUG validDNDR + if( validDR(dr) && LMIC.dn2Dr != CUSTOM_DR) //XXX:BUG validDNDR ans |= MCMD_DN2P_ANS_DRACK; if( freq > 0 ) ans |= MCMD_DN2P_ANS_CHACK; @@ -1834,6 +1892,7 @@ static bit_t decodeFrame (void) { oidx += 2; continue; } +#if !defined(DISABLE_CLASSB) case MCMD_PITV_ANS: { if( (LMIC.ping.intvExp & 0x80) ) { LMIC.ping.intvExp &= 0x7F; // clear pending bit @@ -1909,6 +1968,7 @@ static bit_t decodeFrame (void) { oidx += 4; continue; } +#endif #if defined(CFG_lorawan11) case MCMD_ADRP_REQ: { LMIC_setLinkCheck(1 << (opts[oidx+1] >> 4), 1 << (opts[oidx+1] & 0xf)); @@ -1967,10 +2027,10 @@ static bit_t decodeFrame (void) { LMIC.dataBeg = poff; LMIC.dataLen = pend-poff; } + debug_printf("Received downlink[window=%s,port=%d,ack=%d]\r\n", window, port, ackup); return 1; } - static bit_t decodeMultiCastFrame (void) { u1_t* d = LMIC.frame; u1_t hdr = d[0]; @@ -2065,7 +2125,7 @@ static void setupRx2 (void) { static void schedRx2 (u1_t delay, osjobcb_t func) { #if defined(CFG_eu868) && !defined(CFG_kr920) - if( getSf(dndr2rps(LMIC.dn2Dr)) == FSK ) { + if( isFsk(dndr2rps(LMIC.dn2Dr)) ) { LMIC.rxtime = LMIC.txend + delay*sec2osticks(1) - PRERX_FSK*us2osticksRound(160); // (8bit/50kbps=160us) LMIC.rxsyms = RXLEN_FSK; } @@ -2091,14 +2151,16 @@ static void setupRx1 (osjobcb_t func) { // Called by HAL once TX complete and delivers exact end of TX time stamp in LMIC.rxtime static void txDone (u1_t delay, osjobcb_t func) { +#if !defined(DISABLE_CLASSB) if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE|OP_PINGINI)) == (OP_TRACK|OP_PINGABLE) ) { rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! LMIC.opmode |= OP_PINGINI; } +#endif prepareDn(); LMIC.rps = dndr2rps(LMIC.dndr); - if( getSf(LMIC.rps) == FSK ) { + if( isFsk(LMIC.rps) ) { LMIC.rxtime = LMIC.txend + delay*sec2osticks(1) - PRERX_FSK*us2osticksRound(160); // (8bit/50kbps=160us) LMIC.rxsyms = RXLEN_FSK; } else { @@ -2114,6 +2176,7 @@ static void txDone (u1_t delay, osjobcb_t func) { static void onJoinFailed (osjob_t* osjob) { + (void)osjob; // unused // Notify app - must call LMIC_reset() to stop joining // otherwise join procedure continues. reportEvent(EV_JOIN_FAILED); @@ -2137,15 +2200,15 @@ static bit_t processJoinAccept (void) { } LMIC.opmode &= ~OP_TXRXPEND; ostime_t delay = nextJoinState(); - // update txend - LMIC.txend = os_getTime() + delay; + // update txend + LMIC.txend = os_getTime() + delay; // Build next JOIN REQUEST with next engineUpdate call // Optionally, report join failed. // Both after a random/chosen amount of ticks. os_setApproxTimedCallback(&LMIC.osjob, LMIC.txend, - ((delay&1) != 0) - ? FUNC_ADDR(onJoinFailed) // one JOIN iteration done and failed - : FUNC_ADDR(runEngineUpdate)); // next step to be delayed + ((delay&1) != 0) + ? FUNC_ADDR(onJoinFailed) // one JOIN iteration done and failed + : FUNC_ADDR(runEngineUpdate)); // next step to be delayed return 1; } u1_t hdr = LMIC.frame[0]; @@ -2194,6 +2257,7 @@ static bit_t processJoinAccept (void) { s4_t freq = rdFreq(&LMIC.frame[dlen]); if( freq > 0 ) { setupChannel_dyn(chidx, freq, 0); + debug_printf("Setup channel[idx=%d,freq=%.1F]\r\n", chidx, freq, 6); } } #endif // REG_DYN @@ -2213,6 +2277,7 @@ static bit_t processJoinAccept (void) { static void processRx2Jacc (osjob_t* osjob) { + (void)osjob; // unused if( LMIC.dataLen == 0 ) LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot processJoinAccept(); @@ -2220,23 +2285,27 @@ static void processRx2Jacc (osjob_t* osjob) { static void setupRx2Jacc (osjob_t* osjob) { + (void)osjob; // unused LMIC.osjob.func = FUNC_ADDR(processRx2Jacc); setupRx2(); } static void processRx1Jacc (osjob_t* osjob) { + (void)osjob; // unused if( LMIC.dataLen == 0 || !processJoinAccept() ) schedRx2(DELAY_JACC2, FUNC_ADDR(setupRx2Jacc)); } static void setupRx1Jacc (osjob_t* osjob) { + (void)osjob; // unused setupRx1(FUNC_ADDR(processRx1Jacc)); } static void jreqDone (osjob_t* osjob) { + (void)osjob; // unused txDone(DELAY_JACC1, FUNC_ADDR(setupRx1Jacc)); reportEvent(EV_TXDONE); } @@ -2247,10 +2316,12 @@ static void jreqDone (osjob_t* osjob) { static bit_t processDnData(void); static void processRx2DnDataDelay (osjob_t* osjob) { + (void)osjob; // unused processDnData(); } static void processRx2DnData (osjob_t* osjob) { + (void)osjob; // unused if( LMIC.dataLen == 0 ) { LMIC.txrxFlags = (LMIC.txrxFlags & TXRX_NOTX) | 0; // nothing in 1st/2nd DN slot // Delay callback processing to avoid up TX while gateway is txing our missed frame! @@ -2265,21 +2336,24 @@ static void processRx2DnData (osjob_t* osjob) { static void setupRx2DnData (osjob_t* osjob) { + (void)osjob; // unused LMIC.osjob.func = FUNC_ADDR(processRx2DnData); setupRx2(); } static void processRx1DnData (osjob_t* osjob) { + (void)osjob; // unused if( LMIC.dataLen == 0 || !processDnData() ) { schedRx2(LMIC.dn1Dly+DELAY_EXTDNW2, FUNC_ADDR(setupRx2DnData)); } } static void processRx2ClassC (osjob_t* osjob) { + (void)osjob; // unused if( LMIC.dataLen != 0 ) { LMIC.txrxFlags = TXRX_DNW2; - if ((LMIC.devaddr == os_rlsbf4(&LMIC.frame[OFF_DAT_ADDR]) && decodeFrame()) || decodeMultiCastFrame() ) { + if ((LMIC.devaddr == os_rlsbf4(&LMIC.frame[OFF_DAT_ADDR]) && decodeFrame()) || decodeMultiCastFrame() ) { reportEvent(EV_RXCOMPLETE); return; } @@ -2297,16 +2371,19 @@ static void setupRx2ClassC () { } static void processRx1ClassC (osjob_t* osjob) { + (void)osjob; // unused if( !processDnData() ) { setupRx2ClassC(); } } static void setupRx1DnData (osjob_t* osjob) { + (void)osjob; // unused setupRx1(FUNC_ADDR(processRx1DnData)); } static void setupRx1ClassC (osjob_t* osjob) { + (void)osjob; // unused setupRx1(FUNC_ADDR(processRx1ClassC)); } @@ -2321,7 +2398,7 @@ static void txError (void) { static void updataDone (osjob_t* osjob) { - reportEvent(EV_TXDONE); + (void)osjob; // unused // check if rx window is to be scheduled // (dataLen is reset by radio if tx didn't complete regularly and txend is unknown) if( LMIC.pendTxNoRx || LMIC.dataLen == 0 ) { @@ -2334,6 +2411,7 @@ static void updataDone (osjob_t* osjob) { ? FUNC_ADDR(setupRx1DnData) : FUNC_ADDR(setupRx1ClassC)); } + reportEvent(EV_TXDONE); } // ======================================== @@ -2363,18 +2441,20 @@ static void buildDataFrame (void) { os_copyMem(&LMIC.frame[end], LMIC.foptsUp, n); end += n; } +#if !defined(DISABLE_CLASSB) if( LMIC.ping.intvExp & 0x80 ) { // Announce ping interval - LNS hasn't acked it yet LMIC.frame[end] = MCMD_PITV_REQ; LMIC.frame[end+1] = LMIC.ping.intvExp & 0x7; end += 2; } +#endif if( LMIC.dutyCapAns ) { if( end <= OFF_DAT_OPTS + 15 - 1 ) { LMIC.frame[end] = MCMD_DCAP_ANS; - end += 1; - } - LMIC.dutyCapAns = 0; + end += 1; + } + LMIC.dutyCapAns = 0; } if( LMIC.dn2Ans ) { // Note: this is cleared with reception of a frame in a class A RX1/RX2 window @@ -2410,6 +2490,7 @@ static void buildDataFrame (void) { LMIC.devsAns = 0; end += 3; } +#if !defined(DISABLE_CLASSB) if( LMIC.askForTime > 0 ) { LMIC.frame[end] = MCMD_TIME_REQ; end += 1; @@ -2420,6 +2501,7 @@ static void buildDataFrame (void) { LMIC.bcnfAns = 0; end += 2; } +#endif if( LMIC.gwmargin == 255 ) { LMIC.frame[end] = MCMD_LCHK_REQ; end += 1; @@ -2428,12 +2510,15 @@ static void buildDataFrame (void) { int foptslen = end - OFF_DAT_OPTS; if( foptslen && txdata && LMIC.pendTxPort == 0 ) { + debug_printf("Payload and MAC commands on port 0, sending only MAC commands\n"); // app layer wants to transmit on port 0, but we have fopts txdata = 0; LMIC.txrxFlags |= TXRX_NOTX; } - if( foptslen > 15 ) { + int foptslen_max = 15; + if( foptslen > foptslen_max ) { + debug_printf("MAC commands large (%u > %u), sending only MAC commands\n", foptslen, foptslen_max); // too big for FOpts, send as MAC frame with port=0 (cancels application payload) memcpy(LMIC.pendTxData, LMIC.frame+OFF_DAT_OPTS, foptslen); dlen = foptslen; @@ -2447,7 +2532,9 @@ static void buildDataFrame (void) { end = OFF_DAT_OPTS; } - int flen, flen_max = LMIC_maxAppPayload() + 13; + int flen, flen_max = MAX_LEN_FRAME; + if (LMIC.datarate != CUSTOM_DR) + flen_max = LMIC_maxAppPayload() + 13; again: flen = end + (txdata ? 5+dlen : 4); if( flen > flen_max ) { @@ -2455,11 +2542,13 @@ static void buildDataFrame (void) { if( txdata ) { if( LMIC.pendTxPort ) { if( foptslen ) { + debug_printf("Frame too large (%u > %u), sending only MAC commands\n", flen, flen_max); // cancel application payload txdata = 0; LMIC.txrxFlags |= TXRX_NOTX; goto again; } else { + debug_printf("Frame too large (%u > %u), not sending\n", flen, flen_max); // cancel transmission completely LMIC.dataLen = 0; return; @@ -2503,6 +2592,7 @@ static void buildDataFrame (void) { } +#if !defined(DISABLE_CLASSB) // Callback from HAL during scan mode or when job timer expires. static void onBcnScanRx (osjob_t* job) { // stop radio and its job @@ -2648,6 +2738,7 @@ int LMIC_track (ostime_t when) { os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, track_start); return 1; } +#endif // ================================================================================ @@ -2670,6 +2761,7 @@ static void buildJoinRequest (u1_t ftype) { } static void startJoining (osjob_t* osjob) { + (void)osjob; // unused // reset and calibrate radio LMIC.freq = (REGION.maxFreq - REGION.minFreq) / 2; os_radio(RADIO_INIT); @@ -2706,7 +2798,9 @@ bit_t LMIC_startJoining (void) { // // ================================================================================ +#if !defined(DISABLE_CLASSB) static void processPingRx (osjob_t* osjob) { + (void)osjob; // unused if( LMIC.dataLen != 0 ) { LMIC.txrxFlags = TXRX_PING; if( decodeFrame() ) { @@ -2717,6 +2811,7 @@ static void processPingRx (osjob_t* osjob) { // Pick next ping slot engineUpdate(); } +#endif static bit_t processDnData (void) { @@ -2761,8 +2856,8 @@ static bit_t processDnData (void) { if (LMIC.txPowAdj) { setDrTxpow(DRCHG_NOADRACK, LMIC.datarate, 0); } - if (decDR(LMIC.datarate) != LMIC.datarate) { - setDrTxpow(DRCHG_NOADRACK, decDR(LMIC.datarate), KEEP_TXPOWADJ); + if (lowerDR(LMIC.datarate, 1) != LMIC.datarate) { + setDrTxpow(DRCHG_NOADRACK, lowerDR(LMIC.datarate, 1), KEEP_TXPOWADJ); } else if (REG_IS_FIX() #ifdef REG_FIX && enableAllChannels_fix() @@ -2780,6 +2875,7 @@ static bit_t processDnData (void) { } reportEvent((LMIC.opmode & OP_LINKDEAD) ? EV_LINK_DEAD : EV_ADR_BACKOFF); } +#if !defined(DISABLE_CLASSB) // If this falls to zero the NWK did not answer our MCMD_TIME_REQ commands - try full scan if( LMIC.askForTime > 0 ) { if( --LMIC.askForTime == 0 ) { @@ -2790,6 +2886,7 @@ static bit_t processDnData (void) { opmodePoll(); } } +#endif txcontinue: return 1; } @@ -2813,7 +2910,9 @@ static bit_t processDnData (void) { } +#if !defined(DISABLE_CLASSB) static void processBeacon (osjob_t* osjob) { + (void)osjob; // unused ostime_t lasttx = LMIC.bcninfo.txtime; // save previous - decodeBeacon overwrites u1_t flags = LMIC.bcninfo.flags; ev_t ev; @@ -2866,21 +2965,25 @@ static void processBeacon (osjob_t* osjob) { reportEvent(ev); } - static void startRxBcn (osjob_t* osjob) { + (void)osjob; // unused LMIC.osjob.func = FUNC_ADDR(processBeacon); os_radio(RADIO_RX); } static void startRxPing (osjob_t* osjob) { + (void)osjob; // unused LMIC.osjob.func = FUNC_ADDR(processPingRx); os_radio(RADIO_RX); } +#endif + // Decide what to do next for the MAC layer of a device static void engineUpdate (void) { + debug_printf("engineUpdate[opmode=0x%x]\r\n", LMIC.opmode); // Check for ongoing state: scan or TX/RX transaction if( (LMIC.opmode & (OP_NOENGINE|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) { return; @@ -2894,9 +2997,11 @@ static void engineUpdate (void) { #endif // CFG_autojoin ostime_t now = os_getTime(); - ostime_t rxtime = 0; ostime_t txbeg = 0; +#if !defined(DISABLE_CLASSB) + ostime_t rxtime = 0; + if( (LMIC.opmode & OP_SCAN) != 0 ) { // Looking for a beacon - LMIC.bcninfo.txtime is timeout for scan // Cancel onging TX/RX transaction @@ -2923,6 +3028,7 @@ static void engineUpdate (void) { ASSERT( (ostime_t)(rxtime-now) >= 0 ); #endif } +#endif if( LMIC.pollcnt ) opmodePoll(); @@ -2931,27 +3037,41 @@ static void engineUpdate (void) { // Need to TX some data... // Assuming txChnl points to channel which first becomes available again. bit_t jacc = ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) != 0 ? 1 : 0); + if (jacc) + debug_verbose_printf("Uplink join pending\r\n", os_getTime()); + else + debug_verbose_printf("Uplink data pending\r\n", os_getTime()); // Find next suitable channel and return availability time if( (LMIC.opmode & OP_NEXTCHNL) != 0 ) { - txbeg = LMIC.txend = nextTx(now); + txbeg = LMIC.txend = nextTx(now+TX_RAMPUP); + debug_verbose_printf("Airtime available at %t (channel duty limit)\r\n", txbeg); } else { txbeg = LMIC.txend; + debug_verbose_printf("Airtime available at %t (previously determined)\r\n", txbeg); } // Delayed TX or waiting for duty cycle? - if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0) && (txbeg - LMIC.globalDutyAvail) < 0 ) + if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0) && (txbeg - LMIC.globalDutyAvail) < 0 ) { txbeg = LMIC.globalDutyAvail; + debug_verbose_printf("Airtime available at %t (global duty limit)\r\n", txbeg); + } +#if !defined(DISABLE_CLASSB) // If we're tracking a beacon... // then make sure TX-RX transaction is complete before beacon if( (LMIC.opmode & OP_TRACK) != 0 && txbeg + (jacc ? JOIN_GUARD_osticks : TXRX_GUARD_osticks) - rxtime > 0 ) { + + debug_verbose_printf("Awaiting beacon before uplink\r\n"); + // Not enough time to complete TX-RX before beacon - postpone after beacon. // In order to avoid clustering of postponed TX right after beacon randomize start! txDelay(rxtime + BCN_RESERVE_osticks, 16); txbeg = 0; goto checkrx; } +#endif // Earliest possible time vs overhead to setup radio if( txbeg - (now + TX_RAMPUP) <= 0 ) { + debug_verbose_printf("Ready for uplink\r\n"); // We could send right now! txbeg = now + TX_RAMPUP; dr_t txdr = LMIC.datarate; @@ -2972,11 +3092,13 @@ static void engineUpdate (void) { // Imminent roll over - proactively reset MAC // Device has to react! NWK will not roll over and just stop sending. // Thus, we have N frames to detect a possible lock up. + debug_printf("Down FCNT about to rollover (0x%lx), resetting session\n", LMIC.seqnoDn); reset: os_setCallback(&LMIC.osjob, FUNC_ADDR(runReset)); return; } if( (LMIC.txCnt==0 && LMIC.seqnoUp == 0xFFFFFFFF) ) { + debug_printf("Up FCNT about to rollover (0x%lx), resetting session\n", LMIC.seqnoUp); // Roll over of up seq counter // Do not run RESET event callback from here! // App code might do some stuff after send unaware of RESET. @@ -2985,19 +3107,27 @@ static void engineUpdate (void) { LMIC.txrxFlags = 0; buildDataFrame(); if( LMIC.dataLen == 0 ) { + debug_printf("Zero data length, not sending\n"); txError(); return; } LMIC.osjob.func = FUNC_ADDR(updataDone); } - LMIC.rps = setCr(updr2rps(txdr), (cr_t)LMIC.errcr); - LMIC.dndr = txdr; // carry TX datarate (can be != LMIC.datarate) over to txDone/setupRx1 + LMIC.rps = updr2rps(txdr); + // For CUSTOM_DR, assume that rps already has the right CR + // and that dndr is also preset to the right DR. + if (txdr != CUSTOM_DR) { + LMIC.rps = setCr(LMIC.rps, LMIC.errcr); + // Calculate dndr to use based on txdr (can be != LMIC.datarate for joins) + LMIC.dndr = prepareDnDr(txdr); + } LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL; updateTx(txbeg); reportEvent(EV_TXSTART); os_radio(RADIO_TX); return; } + debug_verbose_printf("Uplink delayed until %t\r\n", txbeg); // Cannot yet TX if( (LMIC.opmode & OP_TRACK) == 0 ) goto txdelay; // We don't track the beacon - nothing else to do - so wait for the time to TX @@ -3014,6 +3144,7 @@ static void engineUpdate (void) { } } +#if !defined(DISABLE_CLASSB) // Are we pingable? checkrx: if( (LMIC.opmode & OP_PINGINI) != 0 ) { @@ -3046,6 +3177,7 @@ static void engineUpdate (void) { } os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn)); return; +#endif txdelay: if( (LMIC.clmode & CLASS_C) ) { @@ -3062,8 +3194,26 @@ void LMIC_setAdrMode (bit_t enabled) { // Should we have/need an ext. API like this? void LMIC_setDrTxpow (dr_t dr, s1_t txpowadj) { + ASSERT(dr != CUSTOM_DR); // Use setCustomDr() for that setDrTxpow(DRCHG_SET, dr, txpowadj); syncDatarate(); + // Make sure dndr is not CUSTOM_DR anymore (actual value does not + // matter, it will be set later *if* not CUSTOM_DR). + LMIC.dndr = dr; +} + +dr_t LMIC_setCustomDr (rps_t custom_rps, dr_t dndr) { + dr_t old_dr = LMIC.datarate; + setDrTxpow(DRCHG_SET, CUSTOM_DR, KEEP_TXPOWADJ); + LMIC.dndr = dndr; + LMIC.custom_rps = custom_rps; + return old_dr; +} + +void LMIC_selectChannel(u1_t channel) { + LMIC.opmode &= ~OP_NEXTCHNL; + LMIC.txChnl = channel; + LMIC.txend = os_getTime(); } @@ -3079,9 +3229,9 @@ int LMIC_regionIdx (u1_t regionCode) { return 0; } for( int idx = 0; idx < REGIONS_COUNT; idx++ ) { - if (REGIONS[idx].regcode == regionCode) { - return idx; - } + if (REGIONS[idx].regcode == regionCode) { + return idx; + } } return -1; } @@ -3113,9 +3263,11 @@ void LMIC_reset_ex (u1_t regionCode) { LMIC.dn1Dly = 1; LMIC.dn2Dr = REGION.rx2Dr; // we need this for 2nd DN window of join accept LMIC.dn2Freq = REGION.rx2Freq; // ditto +#if !defined(DISABLE_CLASSB) LMIC.ping.freq = REGION.pingFreq; // defaults for ping LMIC.ping.dr = REGION.pingDr; // ditto LMIC.ping.intvExp = 8; // no ping interval ever sent up +#endif LMIC.refChnl = REG_IS_FIX() ? 0 : os_getRndU1(); // fix uses randomized hoplist starting with block0 LMIC.adrAckLimit = ADR_ACK_LIMIT; LMIC.adrAckDelay = ADR_ACK_DELAY; @@ -3188,8 +3340,10 @@ void LMIC_setClassC (u1_t enabled) { return; // already in that mode if( (LMIC.clmode & PEND_CLASS_C) ) return; // change is already pending +#if !defined(DISABLE_CLASSB) if( enabled ) LMIC_stopPingable(); // stop class B +#endif #if defined(CFG_lorawan11) if( enabled != UNILATERAL_CLASS_C ) { LMIC.clmode |= PEND_CLASS_C; @@ -3249,19 +3403,19 @@ int LMIC_setMultiCastSession (devaddr_t grpaddr, const u1_t* nwkKeyDn, const u1_ session_t* s; for(s = LMIC.sessions; sgrpaddr!=0 && s->grpaddr!=grpaddr; s++); if (s >= LMIC.sessions+MAX_MULTICAST_SESSIONS) - return 0; + return 0; s->grpaddr = grpaddr; s->seqnoADn = seqnoADn; if( nwkKeyDn != (u1_t*)0 ) { os_copyMem(s->nwkKeyDn, nwkKeyDn, 16); - os_copyMem(&LMIC.lceCtx.mcgroup[LCE_MCGRP_0 + (s-LMIC.sessions)].nwkSKeyDn, nwkKeyDn, 16); + os_copyMem(&LMIC.lceCtx.mcgroup[LCE_MCGRP_0 + (s-LMIC.sessions)].nwkSKeyDn, nwkKeyDn, 16); } if( appKey != (u1_t*)0 ) { os_copyMem(s->appKey, appKey, 16); - os_copyMem(&LMIC.lceCtx.mcgroup[LCE_MCGRP_0 + (s-LMIC.sessions)].appSKey, appKey, 16); + os_copyMem(&LMIC.lceCtx.mcgroup[LCE_MCGRP_0 + (s-LMIC.sessions)].appSKey, appKey, 16); } return 1; } @@ -3286,6 +3440,7 @@ void LMIC_setLinkCheck (u4_t limit, u4_t delay) { bit_t LMIC_setupChannel (u1_t chidx, freq_t freq, u2_t drmap) { if (REG_IS_FIX()) { + (void)chidx; (void)freq; (void)drmap; // unused return 0; } else { #ifdef REG_DYN @@ -3342,6 +3497,11 @@ ostime_t LMIC_nextTx (ostime_t now) { return nextTx(now); } +// Remove duty cycle limitations +void LMIC_disableDC (void) { + LMIC.noDC = 1; +} + #if defined(CFG_simul) #include "addr2func.h" #include "arr2len.h" @@ -3356,11 +3516,6 @@ void LMIC_enableFastJoin (void) { // XXX deprecated } -// Remove duty cycle limitations -void LMIC_disableDC (void) { - LMIC.noDC = 1; -} - /// Used for regression testing ostime_t LMIC_dr2hsym (dr_t dr, s1_t num) { return dr2hsym(dr,num); @@ -3389,7 +3544,9 @@ static const rfuncs_t RFUNCS_DYN = { __dyn(syncDatarate), __dyn(updateTx), __dyn(nextTx), +#if !defined(DISABLE_CLASSB) __dyn(setBcnRxParams), +#endif }; #undef __dyn #endif // REG_DYN @@ -3404,7 +3561,9 @@ static const rfuncs_t RFUNCS_FIX = { __fix(syncDatarate), __fix(updateTx), __fix(nextTx), +#if !defined(DISABLE_CLASSB) __fix(setBcnRxParams), +#endif }; #undef __fix #endif // REG_FIX diff --git a/lmic/lmic.h b/lmic/lmic.h index e6758a5..7fb8542 100644 --- a/lmic/lmic.h +++ b/lmic/lmic.h @@ -15,6 +15,10 @@ #include "lorabase.h" #include "lce.h" +#ifdef __cplusplus +extern "C"{ +#endif + // LMIC version #define LMIC_VERSION_MAJOR 2 #define LMIC_VERSION_MINOR 1 @@ -129,7 +133,7 @@ typedef struct { u1_t beaconLen; // beacon length ostime_t beaconAirtime; // beacon air time eirp_t maxEirp; // max. EIRP (initial value) - u1_t rx1DrOff[8]; // RX1 data rate offsets + s1_t rx1DrOff[8]; // RX1 data rate offsets u1_t dr2maxAppPload[16]; // max application payload (assuming no repeater and no fopts) u1_t regcode; // external region code @@ -150,8 +154,8 @@ enum { MAX_RXSYMS = 100 }; // stop tracking beacon beyond this #define RXDERR_INI 50 // ppm #endif -#define LINK_CHECK_OFF (0x80000000) -#define LINK_CHECK_INIT (-LMIC.adrAckLimit) +#define LINK_CHECK_OFF ((s4_t)0x80000000) +#define LINK_CHECK_INIT ((s4_t)(-LMIC.adrAckLimit)) #define LINK_CHECK_DEAD (LMIC.adrAckDelay) enum { TIME_RESYNC = 6*128 }; // secs @@ -165,6 +169,7 @@ enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD }; enum { KEEP_TXPOWADJ = -128 }; +#if !defined(DISABLE_CLASSB) //! \internal typedef struct { u1_t dr; @@ -176,7 +181,6 @@ typedef struct { u4_t freq; } rxsched_t; - //! Parsing and tracking states of beacons. enum { BCN_NONE = 0x00, //!< No beacon received BCN_PARTIAL = 0x01, //!< Only first (common) part could be decoded (info,lat,lon invalid/previous) @@ -195,11 +199,13 @@ typedef struct { s4_t lat; //!< Lat field of last beacon (valid only if BCN_FULL set) s4_t lon; //!< Lon field of last beacon (valid only if BCN_FULL set) } bcninfo_t; +#endif + // purpose of receive window - lmic_t.rxState enum { RADIO_STOP=0, RADIO_TX=1, RADIO_RX=2, RADIO_RXON=3, RADIO_TXCW, RADIO_CCA, RADIO_INIT, RADIO_CAD, RADIO_TXCONT }; // Netid values / lmic_t.netid -enum { NETID_NONE=(int)~0U, NETID_MASK=(int)0xFFFFFF }; +enum { NETID_NONE=~0U, NETID_MASK=0xFFFFFF }; // MAC operation modes (lmic_t.opmode). enum { OP_NONE = 0x0000, OP_SCAN = 0x0001, // radio scan to find a beacon @@ -235,7 +241,7 @@ enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND, EV_JOINED, EV_RFU1, EV_JOIN_FAILED, EV_REJOIN_FAILED, EV_TXCOMPLETE, EV_LOST_TSYNC, EV_RESET, EV_RXCOMPLETE, EV_LINK_DEAD, EV_LINK_ALIVE, EV_SCAN_FOUND, EV_TXSTART, - EV_TXDONE, EV_DATARATE, EV_START_SCAN, EV_ADR_BACKOFF, EV_SHUTDOWN }; + EV_TXDONE, EV_DATARATE, EV_START_SCAN, EV_ADR_BACKOFF }; typedef enum _ev_t ev_t; @@ -290,6 +296,7 @@ struct lmic_t { s1_t rssi; s1_t snr; rps_t rps; + rps_t custom_rps; // Used for CUSTOM_DR u1_t rxsyms; u1_t dndr; s1_t txpow; // dBm -- needs to be combined with brdTxPowOff @@ -336,7 +343,7 @@ struct lmic_t { s1_t txPowAdj; // adjustment for txpow (ADR controlled) s1_t brdTxPowOff; // board-specific power adjustment offset dr_t datarate; // current data rate - u1_t errcr; // error coding rate (used for TX only) + cr_t errcr; // error coding rate (used for TX only) u1_t rejoinCnt; // adjustment for rejoin datarate s2_t drift; // last measured drift s2_t lastDriftDiff; @@ -393,11 +400,13 @@ struct lmic_t { u1_t opts; // negotiated protocol options #endif +#if !defined(DISABLE_CLASSB) // Class B state u1_t missedBcns; // unable to track last N beacons s1_t askForTime; // how often to ask for time //XXX:old: u1_t pingSetAns; // answer set cmd and ACK bits rxsched_t ping; // pingable setup +#endif // Public part of MAC state u1_t txCnt; @@ -406,12 +415,14 @@ struct lmic_t { u1_t dataLen; // 0 no data or zero length data, >0 byte count of data u1_t frame[MAX_LEN_FRAME]; +#if !defined(DISABLE_CLASSB) u1_t bcnfAns; // mcmd beacon freq: bit7:pending, bit0:ACK/NACK u1_t bcnChnl; u4_t bcnFreq; // 0=default, !=0: specific BCN freq/no hopping u1_t bcnRxsyms; // ostime_t bcnRxtime; bcninfo_t bcninfo; // Last received beacon info +#endif u1_t noRXIQinversion; @@ -441,6 +452,80 @@ DECLARE_LMIC; //!< \internal bit_t LMIC_setupChannel (u1_t channel, freq_t freq, u2_t drmap); void LMIC_disableChannel (u1_t channel); +// Manually select the next channel for the next transmission. +// +// Warning: This does not do any checking. In particular, this bypasses +// duty cycle limits, allows selecting a channel that is not configured +// for the current datarate, and breaks when you select an invalid or +// disabled channel. +// +// The selected channel applies only to the next transmission. Call this +// *after* setting the datarate (if needed) with LMIC_setDrTxpow(), +// since that forces a new channel to be selected automatically. +void LMIC_selectChannel(u1_t channel); + +// Use a custom datrate and rps value. +// +// This causes the uplink to use the radio settings described by the +// given rps value, which can be any valid rps setting (even when the +// region does not normally enable it). The rps setting is used +// unmodified for uplink, and will have nocrc set for downlink. +// +// While the custom datarate is active, it will not be modified +// automatically (e.g. LinkADRReq is rejected and lowring DR for ADR is +// suspended), except when it is not enabled for any channel (in dynamic +// regions). +// +// However, if you call this function again to change the rps value for +// RX1 or RX2 (see below), it will also apply to subsequent uplinks, so +// you might need to set a new rps or standard datarate before the next +// uplink. +// +// This returns the old uplink DR, which can be later be passed to +// LMIC_setDrTxpow() to disable the custom datarate again, if needed. +// +// RX1 +// +// Normally, the RX1 datarate is derived from the uplink datarate. When +// using a custom datarate, it must be set explicitly using the dndr +// parameter to this function. This can be either a standard datarate +// value, or CUSTOM_DR to use the same custom rps value as the uplink. +// +// To use a custom rps for RX1 that is different from the uplink (or +// use a custom rps just for RX1), call this function (again) after the +// EV_TXSTART event (but before EV_TXDONE). +// +// +// RX2 +// +// To also use a custom datarate for the RX2 window, call this function +// and set `LMIC.dn2Dr` to CUSTOM_DR. This also causes RXParamSetupReq +// to be rejected, keeping dn2Dr unmodified. +// +// To use a custom rps for RX2 that is different from the uplink and/or +// RX1 (or use a custom rps just for RX2), call this function (again) +// after the EV_TXDONE event (but before RX2 starts). +// +// +// Channel selection as normal +// +// For fixed regions, any enabled (125kHz) channel will be used. +// +// For dynamic regions, any channel that supports CUSTOM_DR will be +// considered. LMIC_setupChannel() can be used normally, to create one +// or more channels enabled for CUSTOM_DR. Since the network can +// potentially disable or reconfigure channels, it is recommended to set +// up these channels again before every transmission. +// +// Disabling CUSTOM_DR +// +// To revert uplink and RX1 back to a normal datarate and allow ADR to +// work again (if enabled), call LMIC_setDrTxpow as normal, passing the +// DR to use. +// +// To revert RX2 back to a normal datarate, just set LMIC.dn2Dr to the +// appropriate datarate directly. +dr_t LMIC_setCustomDr (rps_t custom_rps, dr_t dndr); void LMIC_setDrTxpow (dr_t dr, s1_t txpow); // set default/start DR/txpow void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off) bit_t LMIC_startJoining (void); @@ -456,16 +541,22 @@ void LMIC_setTxData (void); int LMIC_setTxData2 (u1_t port, u1_t* data, u1_t dlen, u1_t confirmed); void LMIC_sendAlive (void); +#if !defined(DISABLE_CLASSB) u1_t LMIC_enableTracking (u1_t tryBcnInfo); void LMIC_disableTracking (void); +#endif void LMIC_setClassC (u1_t enabled); +#if !defined(DISABLE_CLASSB) void LMIC_stopPingable (void); u1_t LMIC_setPingable (u1_t intvExp); +#endif void LMIC_tryRejoin (void); +#if !defined(DISABLE_CLASSB) int LMIC_scan (ostime_t timeout); int LMIC_track (ostime_t when); +#endif int LMIC_setMultiCastSession (devaddr_t grpaddr, const u1_t* nwkKeyDn, const u1_t* appKey, u4_t seqnoAdn); void LMIC_setSession (u4_t netid, devaddr_t devaddr, const u1_t* nwkKey, @@ -484,6 +575,7 @@ rps_t LMIC_dndr2rps (u1_t dr); ostime_t LMIC_calcAirTime (rps_t rps, u1_t plen); u1_t LMIC_maxAppPayload(); ostime_t LMIC_nextTx (ostime_t now); +void LMIC_disableDC (void); // Simulation only APIs #if defined(CFG_simul) @@ -491,11 +583,14 @@ const char* LMIC_addr2func (void* addr); int LMIC_arr2len (const char* name); #endif +// Declare onEvent() function, to make sure any definition will have the +// C conventions, even when in a C++ file. +DECL_ON_LMIC_EVENT; + // Special APIs - for development or testing // !!!See implementation for caveats!!! #if defined(CFG_extapi) void LMIC_enableFastJoin (void); -void LMIC_disableDC (void); ostime_t LMIC_dr2hsym (dr_t dr, s1_t num); void LMIC_updateTx (ostime_t now); void LMIC_getRxdErrInfo (s4_t* skew, u4_t* span); @@ -512,4 +607,8 @@ void LMIC_getRxdErrInfo (s4_t* skew, u4_t* span); #define TRACE_ADDR(a) #endif +#ifdef __cplusplus +} // extern "C" +#endif + #endif // _lmic_h_ diff --git a/lmic/lorabase.h b/lmic/lorabase.h index a2c7ee0..0bdad15 100644 --- a/lmic/lorabase.h +++ b/lmic/lorabase.h @@ -7,6 +7,10 @@ #ifndef _lorabase_h_ #define _lorabase_h_ +#ifdef __cplusplus +extern "C"{ +#endif + enum _cr_t { CR_4_5=0, CR_4_6, CR_4_7, CR_4_8 }; enum _sf_t { FSK=0, SF7, SF8, SF9, SF10, SF11, SF12, SFrfu }; enum _bw_t { BW125=0, BW250, BW500, BWrfu }; @@ -23,6 +27,11 @@ typedef s1_t eirp_t; enum { ILLEGAL_DR = 0xFF }; enum { ILLEGAL_RPS = 0xFF }; enum { ILLEGAL_MAS = 0x00 }; +// DR 15 is used in the LinkADRReq to indicate "no DR changes", so it +// will never be allocated as a standard DR. So it is used here for a +// custom DR. By using 15, rather than 0xFF or similar, it can be used +// in per-channel drmap as normal. +enum { CUSTOM_DR = 0xF }; // Global maximum frame length enum { BCN_PREAMBLE_LEN = 10 }; // length in symbols - actual time depends on DR @@ -196,7 +205,7 @@ enum { MCMD_RKEY_CNF = 0x0B, // - reset confirmation : u1: opt1, [n opts...] MCMD_ADRP_REQ = 0x0C, // - adr params : u1: 7-4: limit_exp, 3-0: delay_exp MCMD_TIME_ANS = 0x0D, // - time answer : u4:epoch_secs, u1:fracs - // Class B - + // Class B - MCMD_PITV_ANS = 0x10, // - ping interval ack : - MCMD_PNGC_REQ = 0x11, // - set ping freq/dr : u3: freq, u1:7-4:RFU/3-0:datarate MCMD_BCNI_ANS = 0x12, // - next beacon start : u2: delay(in TUNIT millis), u1:channel -- DEPRECATED @@ -286,6 +295,12 @@ typedef u4_t devaddr_t; // RX quality (device) enum { RSSI_OFF=64, SNR_SCALEUP=4 }; +#define MAKE_LORA_RPS(sf,bw,cr,ih,nocrc) ((rps_t)((sf) | ((bw)<<3) | ((cr)<<5) | ((nocrc)?(1<<7):0) | ((ih&0xFF)<<8))) +// FSK uses a subset of LORA fields, so just use MAKE_LORA_RPS here +#define MAKE_FSK_RPS(nocrc) (MAKE_LORA_RPS(FSK, 0, 0, 0, nocrc)) + +inline rps_t makeLoraRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc) { return MAKE_LORA_RPS(sf, bw, cr, ih, nocrc); } +inline rps_t makeFskRps (int nocrc) { return MAKE_FSK_RPS(nocrc); } inline sf_t getSf (rps_t params) { return (sf_t)(params & 0x7); } inline rps_t setSf (rps_t params, sf_t sf) { return (rps_t)((params & ~0x7) | sf); } inline bw_t getBw (rps_t params) { return (bw_t)((params >> 3) & 0x3); } @@ -296,14 +311,8 @@ inline int getNocrc(rps_t params) { return ((params >> 7) & inline rps_t setNocrc(rps_t params, int nocrc) { return (rps_t)((params & ~0x80) | (nocrc<<7)); } inline int getIh (rps_t params) { return ((params >> 8) & 0xFF); } inline rps_t setIh (rps_t params, int ih) { return (rps_t)((params & ~0xFF00) | (ih<<8)); } -inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc) { - return sf | (bw<<3) | (cr<<5) | (nocrc?(1<<7):0) | ((ih&0xFF)<<8); -} -#define MAKERPS(sf,bw,cr,ih,nocrc) ((rps_t)((sf) | ((bw)<<3) | ((cr)<<5) | ((nocrc)?(1<<7):0) | ((ih&0xFF)<<8))) -#define LWUPDR(sf,bw) ((u1_t)MAKERPS((sf),(bw),CR_4_5,0,0)) -#define LWDNDR(sf,bw) ((u1_t)MAKERPS((sf),(bw),CR_4_5,0,1)) -// Two frames with params r1/r2 would interfere on air: same SFx + BWx -inline int sameSfBw(rps_t r1, rps_t r2) { return ((r1^r2)&0x1F) == 0; } +inline sf_t isLora (rps_t params) { return getSf(params) >= SF7 && getSf(params) <= SF12; } +inline sf_t isFsk (rps_t params) { return getSf(params) == FSK; } // return 1 for low data rate optimize should be enabled (symbol time equal or above 16.384 ms) else 0 // Must be enabled for: SF11/BW125, SF12/BW125, SF12/BW250 @@ -321,5 +330,8 @@ ostime_t calcAirTime (rps_t rps, u1_t plen); // Sensitivity at given SF/BW int getSensitivity (rps_t rps); +#ifdef __cplusplus +} // extern "C" +#endif #endif // _lorabase_h_ diff --git a/lmic/oslmic.c b/lmic/oslmic.c index 7137e84..d67b039 100644 --- a/lmic/oslmic.c +++ b/lmic/oslmic.c @@ -18,12 +18,15 @@ static struct { } /* anonymous */; } OS; +void rng_init (void); + void os_init (void* bootarg) { memset(&OS, 0x00, sizeof(OS)); hal_init(bootarg); #ifndef CFG_noradio radio_init(false); #endif + rng_init(); LMIC_init(); } @@ -33,21 +36,21 @@ void os_init (void* bootarg) { void rng_init (void) { #ifdef PERIPH_TRNG trng_next(OS.randwrds, 4); +#elif defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + radio_generate_random(OS.randwrds, 4); #else memcpy(OS.randbuf, __TIME__, 8); os_getDevEui(OS.randbuf + 8); #endif + OS.randbuf[0] = 16; } u1_t os_getRndU1 (void) { u1_t i = OS.randbuf[0]; - switch( i ) { - case 0: - rng_init(); // lazy initialization - // fall-thru - case 16: - os_aes(AES_ENC, OS.randbuf, 16); // encrypt seed with any key - i = 0; + ASSERT(i != 0); + if (i == 16) { + os_aes(AES_ENC, OS.randbuf, 16); // encrypt seed with any key + i = 0; } u1_t v = OS.randbuf[i++]; OS.randbuf[0] = i; @@ -55,6 +58,7 @@ u1_t os_getRndU1 (void) { } bit_t os_cca (u2_t rps, u4_t freq) { //XXX:this belongs into os_radio module + (void) rps; (void)freq; // unused return 0; // never grant access } @@ -79,9 +83,9 @@ static int unlinkjob (osjob_t** pnext, osjob_t* job) { for( ; *pnext; pnext = &((*pnext)->next)) { if(*pnext == job) { // unlink *pnext = job->next; - if ((job->flags & OSJOB_FLAG_APPROX) == 0) { - OS.exact -= 1; - } + if ((job->flags & OSJOB_FLAG_APPROX) == 0) { + OS.exact -= 1; + } return 1; } } @@ -97,11 +101,11 @@ static void extendedjobcb (osxjob_t* xjob) { hal_disableIRQs(); osxtime_t now = os_getXTime(); if (xjob->deadline - now > XJOBTIME_MAX_DIFF) { - // schedule intermediate callback - os_setTimedCallbackEx((osjob_t*) xjob, (ostime_t) (now + XJOBTIME_MAX_DIFF), (osjobcb_t) extendedjobcb, OSJOB_FLAG_APPROX); + // schedule intermediate callback + os_setTimedCallbackEx((osjob_t*) xjob, (ostime_t) (now + XJOBTIME_MAX_DIFF), (osjobcb_t) extendedjobcb, OSJOB_FLAG_APPROX); } else { - // schedule final callback - os_setTimedCallbackEx((osjob_t*) xjob, (ostime_t) xjob->deadline, xjob->func, OSJOB_FLAG_APPROX); + // schedule final callback + os_setTimedCallbackEx((osjob_t*) xjob, (ostime_t) xjob->deadline, xjob->func, OSJOB_FLAG_APPROX); } hal_enableIRQs(); } @@ -114,6 +118,9 @@ void os_setExtendedTimedCallback (osxjob_t* xjob, osxtime_t xtime, osjobcb_t cb) xjob->deadline = xtime; extendedjobcb(xjob); hal_enableIRQs(); +#ifdef DEBUG_JOBS + debug_verbose_printf("Scheduled job %u, cb %u at %t\r\n", (unsigned)xjob, (unsigned)cb, xtime); +#endif // DEBUG_JOBS } // clear scheduled job, return 1 if job was removed @@ -121,6 +128,10 @@ int os_clearCallback (osjob_t* job) { hal_disableIRQs(); int r = unlinkjob(&OS.scheduledjobs, job); hal_enableIRQs(); +#ifdef DEBUG_JOBS + if (r) + debug_verbose_printf("Cleared job %u\r\n", (unsigned)job); +#endif // DEBUG_JOBS return r; } @@ -135,14 +146,14 @@ void os_setTimedCallbackEx (osjob_t* job, ostime_t time, osjobcb_t cb, unsigned if( flags & OSJOB_FLAG_NOW ) { time = now; } else if ( time - now <= 0 ) { - flags |= OSJOB_FLAG_NOW; + flags |= OSJOB_FLAG_NOW; } job->deadline = time; job->func = cb; job->next = NULL; job->flags = flags; if ((flags & OSJOB_FLAG_APPROX) == 0) { - OS.exact += 1; + OS.exact += 1; } // insert into schedule for(pnext=&OS.scheduledjobs; *pnext; pnext=&((*pnext)->next)) { @@ -154,6 +165,12 @@ void os_setTimedCallbackEx (osjob_t* job, ostime_t time, osjobcb_t cb, unsigned } *pnext = job; hal_enableIRQs(); +#ifdef DEBUG_JOBS + if (flags & OSJOB_FLAG_NOW) + debug_verbose_printf("Scheduled job %u, cb %u ASAP\r\n", (unsigned)job, (unsigned)cb); + else + debug_verbose_printf("Scheduled job %u, cb %u%s at %s%t\r\n", (unsigned)job, (unsigned)cb, flags & OSJOB_FLAG_IRQDISABLED ? " (irq disabled)" : "", flags & OSJOB_FLAG_APPROX ? "approx " : "", time); +#endif // DEBUG_JOBS } void os_runstep (void) { @@ -166,12 +183,20 @@ void os_runstep (void) { deadline = j->deadline; if( (deadline - now) <= 0 ) { OS.scheduledjobs = j->next; // de-queue - if( (j->flags & OSJOB_FLAG_IRQDISABLED) == 0 ) { + if( (j->flags & OSJOB_FLAG_IRQDISABLED) == 0) { hal_enableIRQs(); +#ifdef DEBUG_JOBS + debug_verbose_printf("Running job 0x%08x, cb 0x%08x, deadline %t\r\n", + (unsigned int) j, (unsigned int) j->func, (ostime_t)j->deadline); +#endif } hal_watchcount(30); // max 60 sec XXX j->func(j); hal_watchcount(0); +#ifdef DEBUG_JOBS + debug_verbose_printf("Ran job 0x%08x, cb 0x%08x, deadline %t\r\n", + (unsigned int) j, (unsigned int) j->func, (ostime_t)j->deadline); +#endif return; } } else { @@ -184,7 +209,7 @@ void os_runstep (void) { // execute jobs from timer and from run queue void os_runloop (void) { while (1) { - os_runstep(); + os_runstep(); } } diff --git a/lmic/oslmic.h b/lmic/oslmic.h index a84bd55..af1961a 100644 --- a/lmic/oslmic.h +++ b/lmic/oslmic.h @@ -20,17 +20,17 @@ // Target platform as C library #include #include -typedef uint8_t bit_t; -typedef uint8_t u1_t; -typedef int8_t s1_t; -typedef uint16_t u2_t; -typedef int16_t s2_t; -typedef uint32_t u4_t; -typedef int32_t s4_t; -typedef uint64_t u8_t; -typedef int64_t s8_t; -typedef unsigned int uint; -typedef const char* str_t; +typedef uint8_t bit_t; +typedef uint8_t u1_t; +typedef int8_t s1_t; +typedef uint16_t u2_t; +typedef int16_t s2_t; +typedef uint32_t u4_t; +typedef int32_t s4_t; +typedef uint64_t u8_t; +typedef int64_t s8_t; +typedef unsigned int uint; +typedef const char* str_t; #include #if !defined(CFG_simul) @@ -41,9 +41,9 @@ typedef const char* str_t; #include #define ASSERT(cond) do { \ if(!(cond)) { fprintf(stderr, "ASSERTION FAILED: %s at %s:%d\n", \ - #cond, __FILE__, __LINE__); hal_failed(); } } while (0) + #cond, __FILE__, __LINE__); hal_failed(); } } while (0) #elif defined(CFG_DEBUG) -#define ASSERT(cond) do { if(!(cond)) { debug_printf("%s:%d: assertion failed\r\n", __FILE__, __LINE__); hal_failed(); } } while (0) +#define ASSERT(cond) do { if(!(cond)) { hal_enableIRQs(); debug_printf("%s:%d: assertion failed\r\n", __FILE__, __LINE__); hal_failed(); } } while (0) #else #define ASSERT(cond) do { if(!(cond)) hal_failed(); } while (0) #endif @@ -51,17 +51,28 @@ typedef const char* str_t; #define ASSERT(cond) do { } while (0) #endif -#define max(a,b) \ +#include +#if __cplusplus >= 201103L || defined(static_assert) +#define LMIC_STATIC_ASSERT static_assert +#else +#define LMIC_STATIC_ASSERT(expr, msg) +#endif + +#ifdef __cplusplus +extern "C"{ +#endif + +#define os_max(a,b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a > _b ? _a : _b; }) -#define min(a,b) \ +#define os_min(a,b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a < _b ? _a : _b; }) -#define os_minmax(vmin,v,vmax) (max(vmin,min(v,vmax))) +#define os_minmax(vmin,v,vmax) (os_max(vmin,os_min(v,vmax))) #define os_clearMem(a,b) memset(a,0,b) #define os_copyMem(a,b,c) memcpy(a,b,c) #define os_moveMem(a,b,c) memmove(a,b,c) @@ -133,10 +144,10 @@ extern u4_t AESKEY[]; #define APP(t) (APPDATA) #endif -#define LOGCHECK(lvl,block) do { \ - if( lvl <= log_lvl ) { \ - block; \ - } \ +#define LOGCHECK(lvl,block) do { \ + if( lvl <= log_lvl ) { \ + block; \ + } \ } while(0) #if defined(CFG_simul) extern int log_lvl; @@ -184,8 +195,8 @@ u1_t os_getRndU1 (void); typedef s4_t ostime_t; typedef s8_t osxtime_t; -#define OSXTIME_MAX INT64_MAX -#define OSTIME_MAX_DIFF ((1U << 31) - 1) +#define OSXTIME_MAX INT64_MAX +#define OSTIME_MAX_DIFF INT32_MAX #if !HAS_ostick_conv #define us2osticks(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC) / 1000000)) @@ -358,5 +369,10 @@ void radio_sleep (void); void radio_cca (void); void radio_cad (void); void radio_cw (void); +void radio_generate_random (u4_t *words, u1_t len); + +#ifdef __cplusplus +} // extern "C" +#endif #endif // _oslmic_h_ diff --git a/lmic/persodata.c b/lmic/persodata.c index b388737..a314f6b 100644 --- a/lmic/persodata.c +++ b/lmic/persodata.c @@ -34,7 +34,7 @@ static struct { static persodata_v1* pd_check_v1 (void* ptr) { persodata_v1* ppd = ptr; if( ppd->magic == PERSODATA_MAGIC_V1 ) { - uint32_t hash[8]; + uint32_t hash[8]; sha256(hash, ptr, sizeof(persodata_v1) - 32); if( memcmp(hash, ppd->hash, 32) != 0 ) { return NULL; diff --git a/lmic/radio-sx126x.c b/lmic/radio-sx126x.c index 45ccb3e..67ad4dd 100644 --- a/lmic/radio-sx126x.c +++ b/lmic/radio-sx126x.c @@ -13,122 +13,138 @@ // ---------------------------------------- // Commands Selecting the Operating Modes of the Radio -#define CMD_SETSLEEP 0x84 -#define CMD_SETSTANDBY 0x80 -#define CMD_SETFS 0xC1 -#define CMD_SETTX 0x83 -#define CMD_SETRX 0x82 -#define CMD_STOPTIMERONPREAMBLE 0x9F -#define CMD_SETRXDUTYCYCLE 0x94 -#define CMD_SETCAD 0xC5 -#define CMD_SETTXCONTINUOUSWAVE 0xD1 -#define CMD_SETTXINFINITEPREAMBLE 0xD2 -#define CMD_SETREGULATORMODE 0x96 -#define CMD_CALIBRATE 0x89 -#define CMD_CALIBRATEIMAGE 0x98 -#define CMD_SETPACONFIG 0x95 -#define CMD_SETRXTXFALLBACKMODE 0x93 +#define CMD_SETSLEEP 0x84 +#define CMD_SETSTANDBY 0x80 +#define CMD_SETFS 0xC1 +#define CMD_SETTX 0x83 +#define CMD_SETRX 0x82 +#define CMD_STOPTIMERONPREAMBLE 0x9F +#define CMD_SETRXDUTYCYCLE 0x94 +#define CMD_SETCAD 0xC5 +#define CMD_SETTXCONTINUOUSWAVE 0xD1 +#define CMD_SETTXINFINITEPREAMBLE 0xD2 +#define CMD_SETREGULATORMODE 0x96 +#define CMD_CALIBRATE 0x89 +#define CMD_CALIBRATEIMAGE 0x98 +#define CMD_SETPACONFIG 0x95 +#define CMD_SETRXTXFALLBACKMODE 0x93 // Commands to Access the Radio Registers and FIFO Buffer -#define CMD_WRITEREGISTER 0x0D -#define CMD_READREGISTER 0x1D -#define CMD_WRITEBUFFER 0x0E -#define CMD_READBUFFER 0x1E +#define CMD_WRITEREGISTER 0x0D +#define CMD_READREGISTER 0x1D +#define CMD_WRITEBUFFER 0x0E +#define CMD_READBUFFER 0x1E // Commands Controlling the Radio IRQs and DIOs -#define CMD_SETDIOIRQPARAMS 0x08 -#define CMD_GETIRQSTATUS 0x12 -#define CMD_CLEARIRQSTATUS 0x02 -#define CMD_SETDIO2ASRFSWITCHCTRL 0x9D -#define CMD_SETDIO3ASTCXOCTRL 0x97 +#define CMD_SETDIOIRQPARAMS 0x08 +#define CMD_GETIRQSTATUS 0x12 +#define CMD_CLEARIRQSTATUS 0x02 +#define CMD_SETDIO2ASRFSWITCHCTRL 0x9D +#define CMD_SETDIO3ASTCXOCTRL 0x97 // Commands Controlling the RF and Packets Settings -#define CMD_SETRFFREQUENCY 0x86 -#define CMD_SETPACKETTYPE 0x8A -#define CMD_GETPACKETTYPE 0x11 -#define CMD_SETTXPARAMS 0x8E -#define CMD_SETMODULATIONPARAMS 0x8B -#define CMD_SETPACKETPARAMS 0x8C -#define CMD_SETCADPARAMS 0x88 -#define CMD_SETBUFFERBASEADDRESS 0x8F -#define CMD_SETLORASYMBNUMTIMEOUT 0xA0 +#define CMD_SETRFFREQUENCY 0x86 +#define CMD_SETPACKETTYPE 0x8A +#define CMD_GETPACKETTYPE 0x11 +#define CMD_SETTXPARAMS 0x8E +#define CMD_SETMODULATIONPARAMS 0x8B +#define CMD_SETPACKETPARAMS 0x8C +#define CMD_SETCADPARAMS 0x88 +#define CMD_SETBUFFERBASEADDRESS 0x8F +#define CMD_SETLORASYMBNUMTIMEOUT 0xA0 // Commands Returning the Radio Status -#define CMD_GETSTATUS 0xC0 -#define CMD_GETRSSIINST 0x15 -#define CMD_GETRXBUFFERSTATUS 0x13 -#define CMD_GETPACKETSTATUS 0x14 -#define CMD_GETDEVICEERRORS 0x17 -#define CMD_CLEARDEVICEERRORS 0x07 -#define CMD_GETSTATS 0x10 -#define CMD_RESETSTATS 0x00 +#define CMD_GETSTATUS 0xC0 +#define CMD_GETRSSIINST 0x15 +#define CMD_GETRXBUFFERSTATUS 0x13 +#define CMD_GETPACKETSTATUS 0x14 +#define CMD_GETDEVICEERRORS 0x17 +#define CMD_CLEARDEVICEERRORS 0x07 +#define CMD_GETSTATS 0x10 +#define CMD_RESETSTATS 0x00 // ---------------------------------------- // List of Registers -#define REG_WHITENINGMSB 0x06B8 -#define REG_WHITENINGLSB 0x06B9 -#define REG_CRCINITVALMSB 0x06BC -#define REG_CRCINITVALLSB 0x06BD -#define REG_CRCPOLYVALMSB 0x06BE -#define REG_CRCPOLYVALLSB 0x06BF -#define REG_SYNCWORD0 0x06C0 -#define REG_SYNCWORD1 0x06C1 -#define REG_SYNCWORD2 0x06C2 -#define REG_SYNCWORD3 0x06C3 -#define REG_SYNCWORD4 0x06C4 -#define REG_SYNCWORD5 0x06C5 -#define REG_SYNCWORD6 0x06C6 -#define REG_SYNCWORD7 0x06C7 -#define REG_NODEADDRESS 0x06CD -#define REG_BROADCASTADDR 0x06CE -#define REG_LORASYNCWORDMSB 0x0740 -#define REG_LORASYNCWORDLSB 0x0741 -#define REG_RANDOMNUMBERGEN0 0x0819 -#define REG_RANDOMNUMBERGEN1 0x081A -#define REG_RANDOMNUMBERGEN2 0x081B -#define REG_RANDOMNUMBERGEN3 0x081C -#define REG_RXGAIN 0x08AC -#define REG_OCPCONFIG 0x08E7 -#define REG_XTATRIM 0x0911 -#define REG_XTBTRIM 0x0912 +#define REG_WHITENINGMSB 0x06B8 +#define REG_WHITENINGLSB 0x06B9 +#define REG_CRCINITVALMSB 0x06BC +#define REG_CRCINITVALLSB 0x06BD +#define REG_CRCPOLYVALMSB 0x06BE +#define REG_CRCPOLYVALLSB 0x06BF +#define REG_SYNCWORD0 0x06C0 +#define REG_SYNCWORD1 0x06C1 +#define REG_SYNCWORD2 0x06C2 +#define REG_SYNCWORD3 0x06C3 +#define REG_SYNCWORD4 0x06C4 +#define REG_SYNCWORD5 0x06C5 +#define REG_SYNCWORD6 0x06C6 +#define REG_SYNCWORD7 0x06C7 +#define REG_NODEADDRESS 0x06CD +#define REG_BROADCASTADDR 0x06CE +#define REG_LORASYNCWORDMSB 0x0740 +#define REG_LORASYNCWORDLSB 0x0741 +#define REG_RANDOMNUMBERGEN0 0x0819 +#define REG_RANDOMNUMBERGEN1 0x081A +#define REG_RANDOMNUMBERGEN2 0x081B +#define REG_RANDOMNUMBERGEN3 0x081C +#define REG_RXGAIN 0x08AC +#define REG_OCPCONFIG 0x08E7 +#define REG_XTATRIM 0x0911 +#define REG_XTBTRIM 0x0912 // sleep modes -#define SLEEP_COLD 0x00 // (no rtc timeout) -#define SLEEP_WARM 0x04 // (no rtc timeout) +#define SLEEP_COLD 0x00 // (no rtc timeout) +#define SLEEP_WARM 0x04 // (no rtc timeout) // standby modes -#define STDBY_RC 0x00 -#define STDBY_XOSC 0x01 +#define STDBY_RC 0x00 +#define STDBY_XOSC 0x01 // regulator modes -#define REGMODE_LDO 0x00 -#define REGMODE_DCDC 0x01 +#define REGMODE_LDO 0x00 +#define REGMODE_DCDC 0x01 // packet types -#define PACKET_TYPE_FSK 0x00 -#define PACKET_TYPE_LORA 0x01 +#define PACKET_TYPE_FSK 0x00 +#define PACKET_TYPE_LORA 0x01 // crc types -#define CRC_OFF 0x01 -#define CRC_1_BYTE 0x00 -#define CRC_2_BYTE 0x02 -#define CRC_1_BYTE_INV 0x04 -#define CRC_2_BYTE_INV 0x06 +#define CRC_OFF 0x01 +#define CRC_1_BYTE 0x00 +#define CRC_2_BYTE 0x02 +#define CRC_1_BYTE_INV 0x04 +#define CRC_2_BYTE_INV 0x06 // irq types -#define IRQ_TXDONE (1 << 0) -#define IRQ_RXDONE (1 << 1) -#define IRQ_PREAMBLEDETECTED (1 << 2) -#define IRQ_SYNCWORDVALID (1 << 3) -#define IRQ_HEADERVALID (1 << 4) -#define IRQ_HEADERERR (1 << 5) -#define IRQ_CRCERR (1 << 6) -#define IRQ_CADDONE (1 << 7) -#define IRQ_CADDETECTED (1 << 8) -#define IRQ_TIMEOUT (1 << 9) -#define IRQ_ALL 0x3FF +#define IRQ_TXDONE (1 << 0) +#define IRQ_RXDONE (1 << 1) +#define IRQ_PREAMBLEDETECTED (1 << 2) +#define IRQ_SYNCWORDVALID (1 << 3) +#define IRQ_HEADERVALID (1 << 4) +#define IRQ_HEADERERR (1 << 5) +#define IRQ_CRCERR (1 << 6) +#define IRQ_CADDONE (1 << 7) +#define IRQ_CADDETECTED (1 << 8) +#define IRQ_TIMEOUT (1 << 9) +#define IRQ_ALL 0x3FF + +// TCXO voltages (limited to VDD - 200mV) +#define TCXO_VOLTAGE1_6V 0x00 +#define TCXO_VOLTAGE1_7V 0x01 +#define TCXO_VOLTAGE1_8V 0x02 +#define TCXO_VOLTAGE2_2V 0x03 +#define TCXO_VOLTAGE2_4V 0x04 +#define TCXO_VOLTAGE2_7V 0x05 +#define TCXO_VOLTAGE3_0V 0x06 +#define TCXO_VOLTAGE3_3V 0x07 + +// XXX: These should probably be configurable +// XXX: The startup time delays TX/RX by 320*15.625=5ms, maybe switch on +// TCXO early? +#define TCXO_VOLTAGE TCXO_VOLTAGE1_7V +#define TCXO_STARTUP_TIME 320 // In multiples of 15.625μs #define LORA_TXDONE_FIXUP us2osticks(269) // determined by lwtestapp using device pin wired to sx1301 pps... #define FSK_TXDONE_FIXUP us2osticks(0) // XXX @@ -190,11 +206,10 @@ static void WriteRegs (uint16_t addr, const uint8_t* data, uint8_t len) { hal_spi_select(0); } -#if 0 // not used +static void WriteReg (uint16_t addr, uint8_t val) __attribute__((__unused__)); // Ok if this is unused static void WriteReg (uint16_t addr, uint8_t val) { WriteRegs(addr, &val, 1); } -#endif static void WriteBuffer (uint8_t off, const uint8_t* data, uint8_t len) { uint8_t buf[2+len]; @@ -275,6 +290,15 @@ static void SetDIO2AsRfSwitchCtrl (uint8_t enable) { writecmd(CMD_SETDIO2ASRFSWITCHCTRL, &enable, 1); } +// use DIO3 to drive crystal enable switch +static void SetDIO3AsTcxoCtrl () { + uint32_t timeout = TCXO_STARTUP_TIME; + uint8_t voltage = TCXO_VOLTAGE1_7V; + uint8_t data[] = {voltage, (timeout >> 16) & 0xff, (timeout >> 8) & 0xff, timeout & 0xff }; + + writecmd(CMD_SETDIO3ASTCXOCTRL, data, sizeof(data)); +} + // write payload to fifo buffer at offset 0 static void WriteFifo (uint8_t *buf, uint8_t len) { static const uint8_t txrxbase[] = { 0, 0 }; @@ -328,20 +352,20 @@ static void SetPacketType (uint8_t type) { // calibrate the image rejection static void CalibrateImage (uint32_t freq) { static const struct { - uint32_t min; - uint32_t max; - uint8_t freq[2]; + uint32_t min; + uint32_t max; + uint8_t freq[2]; } bands[] = { - { 430000000, 440000000, (uint8_t[]) { 0x6B, 0x6F } }, - { 470000000, 510000000, (uint8_t[]) { 0x75, 0x81 } }, - { 779000000, 787000000, (uint8_t[]) { 0xC1, 0xC5 } }, - { 863000000, 870000000, (uint8_t[]) { 0xD7, 0xDB } }, - { 902000000, 928000000, (uint8_t[]) { 0xE1, 0xE9 } }, + { 430000000, 440000000, { 0x6B, 0x6F } }, + { 470000000, 510000000, { 0x75, 0x81 } }, + { 779000000, 787000000, { 0xC1, 0xC5 } }, + { 863000000, 870000000, { 0xD7, 0xDB } }, + { 902000000, 928000000, { 0xE1, 0xE9 } }, }; - for (int i = 0; i < sizeof(bands) / sizeof(bands[0]); i++) { - if (freq >= bands[i].min && freq <= bands[i].max) { - writecmd(CMD_CALIBRATEIMAGE, bands[i].freq, 2); - } + for (size_t i = 0; i < sizeof(bands) / sizeof(bands[0]); i++) { + if (freq >= bands[i].min && freq <= bands[i].max) { + writecmd(CMD_CALIBRATEIMAGE, bands[i].freq, 2); + } } } @@ -356,9 +380,9 @@ static void SetRfFrequency (uint32_t freq) { // configure modulation parameters for LoRa static void SetModulationParamsLora (u2_t rps) { uint8_t param[4]; - param[0] = getSf(rps) + 6; // SF (sf7=1) - param[1] = getBw(rps) + 4; // BW (bw125=0) - param[2] = getCr(rps) + 1; // CR (cr45=0) + param[0] = getSf(rps) - SF7 + 7; // SF (sf7 -> 7) + param[1] = getBw(rps) - BW125 + 4; // BW (bw125 -> 4) + param[2] = getCr(rps) - CR_4_5 + 1; // CR (cr45 -> 1) param[3] = enDro(rps); // low-data-rate-opt (symbol time equal or above 16.38 ms) writecmd(CMD_SETMODULATIONPARAMS, param, 4); } @@ -501,32 +525,43 @@ static void SetCrc16 (uint16_t seed, uint16_t polynomial) { WriteRegs(REG_CRCPOLYVALMSB, buf, 2); } -#if 0 // not used +void radio_sleep (void) { + // cache sleep state to avoid unneccessary wakeup (waking up from cold sleep takes about 4ms) + if (state.sleeping == 0) { + SetSleep(SLEEP_COLD); + state.sleeping = 1; + } +} + +// Do config common to all RF modes +static void CommonSetup (void) { + SetRegulatorMode(REGMODE_DCDC); + if (hal_dio2_controls_rxtx()) + SetDIO2AsRfSwitchCtrl(1); + if (hal_dio3_controls_tcxo()) + SetDIO3AsTcxoCtrl(); +} + +static uint32_t GetRandom (void) __attribute__((__unused__)); // Ok if unused static uint32_t GetRandom (void) { - uint8_t buf[4]; + uint32_t value; + + // Set up oscillator and rx/tx + CommonSetup(); + // continuous rx SetRx(0xFFFFFF); // wait 1ms hal_waitUntil(os_getTime() + ms2osticks(1)); // read random register - ReadRegs(REG_RANDOMNUMBERGEN0, buf, 4); + ReadRegs(REG_RANDOMNUMBERGEN0, (uint8_t*)&value, sizeof(value)); // standby SetStandby(STDBY_RC); - return *((uint32_t*) buf); -} -#endif - -void radio_sleep (void) { - // cache sleep state to avoid unneccessary wakeup (waking up from cold sleep takes about 4ms) - if (state.sleeping == 0) { - SetSleep(SLEEP_COLD); - state.sleeping = 1; - } + return value; } static void txlora (void) { - SetRegulatorMode(REGMODE_DCDC); - SetDIO2AsRfSwitchCtrl(1); + CommonSetup(); SetStandby(STDBY_RC); SetPacketType(PACKET_TYPE_LORA); SetRfFrequency(LMIC.freq); @@ -550,8 +585,7 @@ static void txlora (void) { } static void txfsk (void) { - SetRegulatorMode(REGMODE_DCDC); - SetDIO2AsRfSwitchCtrl(1); + CommonSetup(); SetStandby(STDBY_RC); SetPacketType(PACKET_TYPE_FSK); SetRfFrequency(LMIC.freq); @@ -576,9 +610,8 @@ static void txfsk (void) { SetTx(64000); // timeout 1s (should not happen, TXDONE irq will be raised) } -static void txcw (void) { - SetRegulatorMode(REGMODE_DCDC); - SetDIO2AsRfSwitchCtrl(1); +void radio_cw (void) { + CommonSetup(); SetStandby(STDBY_RC); SetRfFrequency(LMIC.freq); SetTxPower(LMIC.txpow + LMIC.brdTxPowOff); @@ -595,23 +628,21 @@ static void txcw (void) { void radio_starttx (bool txcontinuous) { if (txcontinuous) { ASSERT(0); // not yet supported (continuous packet mode) - txcw(); } else { - if (getSf(LMIC.rps) == FSK) { // FSK modem - txfsk(); - } else { // LoRa modem - txlora(); - } - // the radio will go back to STANDBY mode as soon as the TX is finished - // the corresponding IRQ will inform us about completion. + if (isFsk(LMIC.rps)) { // FSK modem + txfsk(); + } else { // LoRa modem + txlora(); + } + // the radio will go back to STANDBY mode as soon as the TX is finished + // the corresponding IRQ will inform us about completion. } } static void rxfsk (bool rxcontinuous) { // configure radio (needs rampup time) ostime_t t0 = os_getTime(); - SetRegulatorMode(REGMODE_DCDC); - SetDIO2AsRfSwitchCtrl(1); + CommonSetup(); SetStandby(STDBY_RC); SetPacketType(PACKET_TYPE_FSK); SetRfFrequency(LMIC.freq); @@ -631,27 +662,28 @@ static void rxfsk (bool rxcontinuous) { // enable IRQs in HAL hal_irqmask_set(HAL_IRQMASK_DIO1); + ostime_t now = os_getTime(); + if (!rxcontinuous && LMIC.rxtime - now < 0) { + debug_printf("WARNING: rxtime is %ld ticks in the past! (ramp-up time %ld ms / %ld ticks)\r\n", + now - LMIC.rxtime, osticks2ms(now - t0), now - t0); + } + // now receive (lock interrupts only for final fine tuned rx timing...) hal_disableIRQs(); if (rxcontinuous) { // continous rx - BACKTRACE(); - // enable antenna switch for RX (and account power consumption) - hal_ant_switch(HAL_ANTSW_RX); - // rx infinitely (no timeout, until rxdone, will be restarted) - SetRx(0); + BACKTRACE(); + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // rx infinitely (no timeout, until rxdone, will be restarted) + SetRx(0); } else { // single rx - BACKTRACE(); - // busy wait until exact rx time - ostime_t now = os_getTime(); - if (LMIC.rxtime - now < 0) { - debug_printf("WARNING: rxtime is %d ticks in the past! (ramp-up time %d ms / %d ticks)\r\n", - now - LMIC.rxtime, osticks2ms(now - t0), now - t0); - } + BACKTRACE(); + // busy wait until exact rx time hal_waitUntil(LMIC.rxtime); - // enable antenna switch for RX (and account power consumption) - hal_ant_switch(HAL_ANTSW_RX); - // rx for max LMIC.rxsyms symbols (rxsyms = nbytes for FSK) - SetRx((LMIC.rxsyms << 9) / 50); // nbytes * 8 * 64 * 1000 / 50000 + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // rx for max LMIC.rxsyms symbols (rxsyms = nbytes for FSK) + SetRx((LMIC.rxsyms << 9) / 50); // nbytes * 8 * 64 * 1000 / 50000 } hal_enableIRQs(); } @@ -659,8 +691,7 @@ static void rxfsk (bool rxcontinuous) { static void rxlora (bool rxcontinuous) { // configure radio (needs rampup time) ostime_t t0 = os_getTime(); - SetRegulatorMode(REGMODE_DCDC); - SetDIO2AsRfSwitchCtrl(1); + CommonSetup(); SetStandby(STDBY_RC); SetPacketType(PACKET_TYPE_LORA); SetRfFrequency(LMIC.freq); @@ -679,27 +710,30 @@ static void rxlora (bool rxcontinuous) { // enable IRQs in HAL hal_irqmask_set(HAL_IRQMASK_DIO1); + ostime_t now = os_getTime(); + if (!rxcontinuous && LMIC.rxtime - now < 0) { + // Print before disabling IRQs, to work around deadlock on some + // Arduino cores that doe not really support printing without IRQs + debug_printf("WARNING: rxtime is %ld ticks in the past! (ramp-up time %ld ms / %ld ticks)\r\n", + now - LMIC.rxtime, osticks2ms(now - t0), now - t0); + } + // now receive (lock interrupts only for final fine tuned rx timing...) hal_disableIRQs(); if (rxcontinuous) { // continous rx - BACKTRACE(); - // enable antenna switch for RX (and account power consumption) - hal_ant_switch(HAL_ANTSW_RX); - // rx infinitely (no timeout, until rxdone, will be restarted) - SetRx(0); + BACKTRACE(); + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // rx infinitely (no timeout, until rxdone, will be restarted) + SetRx(0); } else { // single rx - BACKTRACE(); - // busy wait until exact rx time - ostime_t now = os_getTime(); - if (LMIC.rxtime - now < 0) { - debug_printf("WARNING: rxtime is %d ticks in the past! (ramp-up time %d ms / %d ticks)\r\n", - now - LMIC.rxtime, osticks2ms(now - t0), now - t0); - } + BACKTRACE(); + // busy wait until exact rx time hal_waitUntil(LMIC.rxtime); - // enable antenna switch for RX (and account power consumption) - hal_ant_switch(HAL_ANTSW_RX); - // rx for max LMIC.rxsyms symbols - SetRx(0); // (infinite, timeout set via SetLoRaSymbNumTimeout) + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // rx for max LMIC.rxsyms symbols + SetRx(0); // (infinite, timeout set via SetLoRaSymbNumTimeout) } hal_enableIRQs(); } @@ -718,7 +752,7 @@ void radio_cw (void) { } void radio_startrx (bool rxcontinuous) { - if (getSf(LMIC.rps) == FSK) { // FSK modem + if (isFsk(LMIC.rps)) { // FSK modem rxfsk(rxcontinuous); } else { // LoRa modem rxlora(rxcontinuous); @@ -728,7 +762,11 @@ void radio_startrx (bool rxcontinuous) { // reset radio static void radio_reset (void) { // drive RST pin low - hal_pin_rst(0); + bool has_reset = hal_pin_rst(0); + + // If reset is not connected, just continue and hope for the best + if (!has_reset) + return; // wait > 100us hal_waitUntil(os_getTime() + ms2osticks(1)); @@ -756,7 +794,7 @@ void radio_init (bool calibrate) { ASSERT( ReadReg(REG_LORASYNCWORDLSB) == 0x24 ); if (calibrate) { - CalibrateImage(LMIC.freq); + CalibrateImage(LMIC.freq); } // go to SLEEP mode @@ -765,62 +803,69 @@ void radio_init (bool calibrate) { hal_enableIRQs(); } +void radio_generate_random(u4_t *words, u1_t len) { + while (len--) + *words++ = GetRandom (); +} + // (run by irqjob) bool radio_irq_process (ostime_t irqtime, u1_t diomask) { + (void)diomask; // unused + uint16_t irqflags = GetIrqStatus(); // dispatch modem - if (getSf(LMIC.rps) == FSK) { // FSK modem - if (irqflags & IRQ_TXDONE) { // TXDONE - BACKTRACE(); + if (isFsk(LMIC.rps)) { // FSK modem + if (irqflags & IRQ_TXDONE) { // TXDONE + BACKTRACE(); // save exact tx time LMIC.txend = irqtime - FSK_TXDONE_FIXUP; } else if (irqflags & IRQ_RXDONE) { // RXDONE - BACKTRACE(); + BACKTRACE(); // read rx quality parameters - LMIC.rssi = GetPacketStatusFsk(); - LMIC.snr = 0; // N/A + LMIC.rssi = GetPacketStatusFsk(); + LMIC.snr = 0; // N/A - // read FIFO - LMIC.dataLen = ReadFifo(LMIC.frame); + // read FIFO + LMIC.dataLen = ReadFifo(LMIC.frame); // save exact rx timestamps LMIC.rxtime = irqtime - FSK_RXDONE_FIXUP; // end of frame timestamp - LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp + LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp #ifdef DEBUG_RX - debug_printf("RX[freq=%.1F,FSK,rssi=%d,len=%d]: %h\r\n", - LMIC.freq, 6, LMIC.rssi - RSSI_OFF, LMIC.dataLen, LMIC.frame, LMIC.dataLen); + debug_printf("RX[rssi=%d,len=%d]: %h\r\n", + LMIC.rssi - RSSI_OFF, LMIC.dataLen, LMIC.frame, LMIC.dataLen); #endif - } else if (irqflags & IRQ_TIMEOUT) { // TIMEOUT - BACKTRACE(); + } else if (irqflags & IRQ_TIMEOUT) { // TIMEOUT + BACKTRACE(); // indicate timeout LMIC.dataLen = 0; #ifdef DEBUG_RX - debug_printf("RX[freq=%.1F,FSK]: TIMEOUT\r\n", LMIC.freq, 6); + debug_printf("RX: TIMEOUT\r\n"); #endif } else { - // unexpected irq - debug_printf("UNEXPECTED RADIO IRQ %04x (after %d ticks, %.1Fms)\r\n", irqflags, irqtime - LMIC.rxtime, osticks2us(irqtime - LMIC.rxtime), 3); - TRACE_VAL(irqflags); - ASSERT(0); - } + // unexpected irq + debug_printf("UNEXPECTED RADIO IRQ %04x (after %ld ticks, %.1Fms)\r\n", irqflags, irqtime - LMIC.rxtime, osticks2us(irqtime - LMIC.rxtime), 3); + TRACE_VAL(irqflags); + ASSERT(0); + } } else { // LORA modem - if (irqflags & IRQ_TXDONE) { // TXDONE - BACKTRACE(); + if (irqflags & IRQ_TXDONE) { // TXDONE + BACKTRACE(); // save exact tx time LMIC.txend = irqtime - LORA_TXDONE_FIXUP; } else if (irqflags & IRQ_RXDONE) { // RXDONE - BACKTRACE(); + BACKTRACE(); // read rx quality parameters - GetPacketStatusLora(&LMIC.rssi, &LMIC.snr); + GetPacketStatusLora(&LMIC.rssi, &LMIC.snr); - // read FIFO - LMIC.dataLen = ReadFifo(LMIC.frame); + // read FIFO + LMIC.dataLen = ReadFifo(LMIC.frame); // save exact rx timestamps LMIC.rxtime = irqtime; // end of frame timestamp @@ -830,27 +875,25 @@ bool radio_irq_process (ostime_t irqtime, u1_t diomask) { else if (getBw(LMIC.rps) == BW500) { LMIC.rxtime -= LORA_RXDONE_FIXUP_500[getSf(LMIC.rps)]; } - LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp + LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp #ifdef DEBUG_RX - debug_printf("RX[freq=%.1F,sf=%d,bw=%d,rssi=%d,snr=%.2F,len=%d]: %h\r\n", - LMIC.freq, 6, getSf(LMIC.rps) + 6, 125 << getBw(LMIC.rps), - LMIC.rssi - RSSI_OFF, LMIC.snr * 100 / SNR_SCALEUP, 2, - LMIC.dataLen, LMIC.frame, LMIC.dataLen); + debug_printf("RX[rssi=%d,snr=%.2F,len=%d]: %h\r\n", + LMIC.rssi - RSSI_OFF, (s4_t)(LMIC.snr * 100 / SNR_SCALEUP), 2, + LMIC.dataLen, LMIC.frame, LMIC.dataLen); #endif - } else if (irqflags & IRQ_TIMEOUT) { // TIMEOUT - BACKTRACE(); + } else if (irqflags & IRQ_TIMEOUT) { // TIMEOUT + BACKTRACE(); // indicate timeout LMIC.dataLen = 0; #ifdef DEBUG_RX - debug_printf("RX[freq=%.1F,sf=%d,bw=%d]: TIMEOUT\r\n", - LMIC.freq, 6, getSf(LMIC.rps) + 6, 125 << getBw(LMIC.rps)); + debug_printf("RX: TIMEOUT\r\n"); #endif } else { - // unexpected irq - debug_printf("UNEXPECTED RADIO IRQ %04x\r\n", irqflags); - TRACE_VAL(irqflags); - ASSERT(0); - } + // unexpected irq + debug_printf("UNEXPECTED RADIO IRQ %04x\r\n", irqflags); + TRACE_VAL(irqflags); + ASSERT(0); + } } // mask all IRQs diff --git a/lmic/radio-sx127x.c b/lmic/radio-sx127x.c index 5d5a364..9e43bf9 100644 --- a/lmic/radio-sx127x.c +++ b/lmic/radio-sx127x.c @@ -373,35 +373,35 @@ static void setopmode (u1_t opmode) { writeReg(RegOpMode, opmode); ostime_t t0 = os_getTime(); while (readReg(RegOpMode) != opmode) { - if (os_getTime() - t0 > ms2osticks(20)) { - // panic when opmode is not reached within 20ms - debug_printf("FAILED TO SET OPMODE %02x within 20ms\r\n", opmode); - ASSERT(0); - } + if (os_getTime() - t0 > ms2osticks(20)) { + // panic when opmode is not reached within 20ms + debug_printf("FAILED TO SET OPMODE %02x within 20ms\r\n", opmode); + ASSERT(0); + } } } // fill fifo when empty static void LoadFifo (void) { if (state.fifolen > 0) { - int n = (state.fifolen > FIFOTHRESH) ? FIFOTHRESH : state.fifolen; - radio_writeBuf(RegFifo, state.fifoptr, n); - state.fifoptr += n; - state.fifolen -= n; + int n = (state.fifolen > FIFOTHRESH) ? FIFOTHRESH : state.fifolen; + radio_writeBuf(RegFifo, state.fifoptr, n); + state.fifoptr += n; + state.fifolen -= n; } } // read fifo when level or ready static void UnloadFifo (void) { if (state.fifolen < 0) { // first byte - state.fifolen = 0; - radio_readBuf(RegFifo, &LMIC.dataLen, 1); + state.fifolen = 0; + radio_readBuf(RegFifo, &LMIC.dataLen, 1); } int n = (LMIC.dataLen - state.fifolen > (FIFOTHRESH-1)) ? (FIFOTHRESH-1) : (LMIC.dataLen - state.fifolen); // errata: unload one byte less if (n) { - radio_readBuf(RegFifo, state.fifoptr, n); - state.fifoptr += n; - state.fifolen += n; + radio_readBuf(RegFifo, state.fifoptr, n); + state.fifoptr += n; + state.fifolen += n; } } @@ -410,47 +410,47 @@ static void configLoraModem (bool txcont) { #if defined(BRD_sx1276_radio) // set ModemConfig1 'bbbbccch' (bw=xxxx, cr=xxx, implicitheader=x) writeReg(LORARegModemConfig1, - ((getBw(LMIC.rps) + 7) << 4) | // BW125=0 --> 7 - ((getCr(LMIC.rps) + 1) << 1) | // CR4_5=0 --> 1 - (getIh(LMIC.rps) != 0)); // implicit header + ((getBw(LMIC.rps) - BW125 + 7) << 4) | // BW125 --> 7 + ((getCr(LMIC.rps) - CR_4_5 + 1) << 1) | // CR_4_5 --> 1 + (getIh(LMIC.rps) != 0)); // implicit header // set ModemConfig2 'sssstcmm' (sf=xxxx, txcont=x, rxpayloadcrc=x, symtimeoutmsb=00) writeReg(LORARegModemConfig2, - ((getSf(LMIC.rps)-1+7) << 4) | // SF7=1 --> 7 + ((getSf(LMIC.rps)-SF7+7) << 4) | // SF7 --> 7 (txcont ? 0x08 : 0x00) | // txcont: 0x08 - ((getNocrc(LMIC.rps) == 0) << 2)); // rxcrc + ((getNocrc(LMIC.rps) == 0) << 2)); // rxcrc // set ModemConfig3 'uuuuoarr' (unused=0000, lowdatarateoptimize=x, agcauto=1, reserved=00) writeReg(LORARegModemConfig3, - (enDro(LMIC.rps) << 3) | // symtime >= 16ms - (1 << 2)); // autoagc + (enDro(LMIC.rps) << 3) | // symtime >= 16ms + (1 << 2)); // autoagc // SX1276 Errata: 2.1 Sensitivity Optimization with a 500kHz Bandwith if (getBw(LMIC.rps) == BW500) { - writeReg(0x36, 0x02); - writeReg(0x3A, 0x64); + writeReg(0x36, 0x02); + writeReg(0x3A, 0x64); } else { - writeReg(0x36, 0x03); - // no need to reset register 0x3a + writeReg(0x36, 0x03); + // no need to reset register 0x3a } #elif defined(BRD_sx1272_radio) // set ModemConfig1 'bbccchco' (bw=xx, cr=xxx, implicitheader=x, rxpayloadcrc=x, lowdatarateoptimize=x) writeReg(LORARegModemConfig1, - (getBw(LMIC.rps) << 6) | // BW125=0 --> 0 - ((getCr(LMIC.rps) + 1) << 3) | // CR4_5=0 --> 1 - ((getIh(LMIC.rps) != 0) << 2) | // implicit header - ((getNocrc(LMIC.rps) == 0) << 1) | // rxcrc - enDro(LMIC.rps)); // symtime >= 16ms + ((getBw(LMIC.rps) - BW125) << 6) | // BW125 --> 0 + ((getCr(LMIC.rps) - CR_4_5 + 1) << 3) | // CR_4_5 --> 1 + ((getIh(LMIC.rps) != 0) << 2) | // implicit header + ((getNocrc(LMIC.rps) == 0) << 1) | // rxcrc + enDro(LMIC.rps)); // symtime >= 16ms // set ModemConfig2 'sssstamm' (sf=xxxx, txcont=x, agcauto=1 symtimeoutmsb=00) writeReg(LORARegModemConfig2, - ((getSf(LMIC.rps)-1+7) << 4) | // SF7=1 --> 7 + ((getSf(LMIC.rps)-SF7+7) << 4) | // SF7 --> 7 (txcont ? 0x08 : 0x00) | // txcont: 0x08 - (1 << 2)); // autoagc + (1 << 2)); // autoagc #endif // BRD_sx1272_radio if (getIh(LMIC.rps)) { - writeReg(LORARegPayloadLength, getIh(LMIC.rps)); // required length + writeReg(LORARegPayloadLength, getIh(LMIC.rps)); // required length } } @@ -514,6 +514,7 @@ static void setRadioConsumption_ua (bool boost, u1_t pow) { ua = RFOPOW[pow]; } #else + (void)boost; (void)pow; // unused ua = 120000; // something better than nothing? #endif LMIC.radioPwr_ua = ua; @@ -552,47 +553,47 @@ static void configPower (int pw) { // XXX - TODO - externalize this somehow // wailmer/wailord can only use 17dBm at DR4 (US) if (getBw(LMIC.rps) == BW500 && pw > 17) { - pw = 17; + pw = 17; } #endif if (BRD_PABOOSTSEL(LMIC.freq, pw)) { // use PA_BOOST - if (pw > 17) { // use high-power +20dBm option - if (pw > 20) { - pw = 20; - } - writeReg(RegPaDac, 0x87); // high power - writeReg(RegPaConfig, 0x80 | (pw - 5)); // BOOST (5..20dBm) - } else { - if (pw < 2) { - pw = 2; - } - writeReg(RegPaDac, 0x84); // normal power - writeReg(RegPaConfig, 0x80 | (pw - 2)); // BOOST (2..17dBm) - } + if (pw > 17) { // use high-power +20dBm option + if (pw > 20) { + pw = 20; + } + writeReg(RegPaDac, 0x87); // high power + writeReg(RegPaConfig, 0x80 | (pw - 5)); // BOOST (5..20dBm) + } else { + if (pw < 2) { + pw = 2; + } + writeReg(RegPaDac, 0x84); // normal power + writeReg(RegPaConfig, 0x80 | (pw - 2)); // BOOST (2..17dBm) + } setRadioConsumption_ua(true, pw); } else { // use PA_RFO #if defined(BRD_sx1276_radio) - if (pw > 0) { - if (pw > 15) { - pw = 15; - } - writeReg(RegPaConfig, 0x70 | pw); // RFO, maxpower=111 (0..15dBm) - } else { - if (pw < -4) { - pw = -4; - } - writeReg(RegPaConfig, pw + 4); // RFO, maxpower=000 (-4..11dBm) - } - writeReg(RegPaDac, 0x84); // normal power + if (pw > 0) { + if (pw > 15) { + pw = 15; + } + writeReg(RegPaConfig, 0x70 | pw); // RFO, maxpower=111 (0..15dBm) + } else { + if (pw < -4) { + pw = -4; + } + writeReg(RegPaConfig, pw + 4); // RFO, maxpower=000 (-4..11dBm) + } + writeReg(RegPaDac, 0x84); // normal power #elif defined(BRD_sx1272_radio) - if (pw < -1) { - pw = -1; - } else if (pw > 14) { - pw = 14; - } - writeReg(RegPaConfig, pw + 1); // RFO (-1..14dBm) - writeReg(RegPaDac, 0x84); // normal power + if (pw < -1) { + pw = -1; + } else if (pw > 14) { + pw = 14; + } + writeReg(RegPaConfig, pw + 1); // RFO (-1..14dBm) + writeReg(RegPaDac, 0x84); // normal power #endif setRadioConsumption_ua(false, (pw < 0) ? 0 : pw); } @@ -604,9 +605,9 @@ static void configPower (int pw) { static void power_tcxo (void) { // power-up TCXO and set tcxo as input if ( hal_pin_tcxo(1) ) { - writeReg(RegTcxo, 0b00011001); // reserved=000, tcxo=1, reserved=1001 - // delay to allow TCXO to wake up - hal_waitUntil(os_getTime() + ms2osticks(1)); + writeReg(RegTcxo, 0b00011001); // reserved=000, tcxo=1, reserved=1001 + // delay to allow TCXO to wake up + hal_waitUntil(os_getTime() + ms2osticks(1)); } } @@ -694,11 +695,11 @@ static void txfsk (bool txcont) { LoadFifo(); if (!txcont) { - // enable IRQs in HAL - hal_irqmask_set(HAL_IRQMASK_DIO0 | HAL_IRQMASK_DIO1); + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO0 | HAL_IRQMASK_DIO1); - // set tx timeout - radio_set_irq_timeout(os_getTime() + us2osticks((FIFOTHRESH+10)*8*1000/50)); + // set tx timeout + radio_set_irq_timeout(os_getTime() + us2osticks((u4_t)(FIFOTHRESH+10)*8*1000/50)); } // enable antenna switch for TX @@ -859,8 +860,8 @@ static void rxlorasingle (void) { hal_enableIRQs(); // warn about delayed rx if( rxtime - now < 0 ) { - debug_printf("WARNING: rxtime is %d ticks in the past! (ramp-up time %d ms / %d ticks)\r\n", - now - rxtime, osticks2ms(now - t0), now - t0); + debug_printf("WARNING: rxtime is %ld ticks in the past! (ramp-up time %ld ms / %ld ticks)\r\n", + now - rxtime, osticks2ms(now - t0), now - t0); } } @@ -980,22 +981,22 @@ static void rxfsk (bool rxcontinuous) { hal_disableIRQs(); if (rxcontinuous) { - BACKTRACE(); - // XXX not suppported - receiver does not automatically restart - radio_set_irq_timeout(os_getTime() + sec2osticks(5)); // time out after 5 sec + BACKTRACE(); + // XXX not suppported - receiver does not automatically restart + radio_set_irq_timeout(os_getTime() + sec2osticks(5)); // time out after 5 sec } else { - BACKTRACE(); - // set preamble timeout - writeReg(FSKRegRxTimeout2, (LMIC.rxsyms + 1) / 2); // (TimeoutRxPreamble * 16 * Tbit) - // set rx timeout - radio_set_irq_timeout(LMIC.rxtime + us2osticks((2*FIFOTHRESH)*8*1000/50)); - // busy wait until exact rx time - ostime_t now = os_getTime(); - if (LMIC.rxtime - now < 0) { - debug_printf("WARNING: rxtime is %d ticks in the past! (ramp-up time %d ms / %d ticks)\r\n", - now - LMIC.rxtime, osticks2ms(now - t0), now - t0); - } - hal_waitUntil(LMIC.rxtime); + BACKTRACE(); + // set preamble timeout + writeReg(FSKRegRxTimeout2, (LMIC.rxsyms + 1) / 2); // (TimeoutRxPreamble * 16 * Tbit) + // set rx timeout + radio_set_irq_timeout(LMIC.rxtime + us2osticks((u4_t)(2*FIFOTHRESH)*8*1000/50)); + // busy wait until exact rx time + ostime_t now = os_getTime(); + if (LMIC.rxtime - now < 0) { + debug_printf("WARNING: rxtime is %ld ticks in the past! (ramp-up time %ld ms / %ld ticks)\r\n", + now - LMIC.rxtime, osticks2ms(now - t0), now - t0); + } + hal_waitUntil(LMIC.rxtime); } // enable antenna switch for RX (and account power consumption) @@ -1012,14 +1013,14 @@ void radio_startrx (bool rxcontinuous) { // set power consumption for statistics LMIC.radioPwr_ua = 11500; - if (getSf(LMIC.rps) == FSK) { // FSK modem + if (isFsk(LMIC.rps)) { // FSK modem rxfsk(rxcontinuous); } else { // LoRa modem if (rxcontinuous) { - rxloracont(); - } else { - rxlorasingle(); - } + rxloracont(); + } else { + rxlorasingle(); + } } } @@ -1031,7 +1032,7 @@ void radio_cad (void) { void radio_starttx (bool txcontinuous) { ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); - if (getSf(LMIC.rps) == FSK) { // FSK modem + if (isFsk(LMIC.rps)) { // FSK modem txfsk(txcontinuous); } else { // LoRa modem txlora(txcontinuous); @@ -1058,8 +1059,8 @@ void radio_cca (void) { writeReg(RegLna, 0b00100011); // highest gain, boost enable // set receiver bandwidth (SSB) - writeReg(FSKRegRxBw, (getSf(LMIC.rps) == FSK) ? 0x0B /* 50kHz SSB */ : - 3 - getBw(LMIC.rps)); // 62.5/125/250kHz SSB (RxBwMant=0, RxBwExp 3/2/1) + writeReg(FSKRegRxBw, (isFsk(LMIC.rps)) ? 0x0B /* 50kHz SSB */ : + 3 - getBw(LMIC.rps)); // 62.5/125/250kHz SSB (RxBwMant=0, RxBwExp 3/2/1) // set power consumption for statistics LMIC.radioPwr_ua = 11500; @@ -1079,9 +1080,9 @@ void radio_cca (void) { // sample rssi values do { rssi = -readReg(FSKRegRssiValue) / 2 + RSSI_OFF; - if (rssi > rssi_max) { - rssi_max = rssi; - } + if (rssi > rssi_max) { + rssi_max = rssi; + } } while (rssi < rssi_th && os_getTime() - t0 < LMIC.rxtime); // return max observed rssi value @@ -1100,7 +1101,14 @@ void radio_cca (void) { // reset radio static void radio_reset (void) { // drive RST pin - hal_pin_rst(RST_PIN_RESET_STATE); + bool has_reset = hal_pin_rst(RST_PIN_RESET_STATE); + + // power-down TCXO + hal_pin_tcxo(0); + + // If reset is not connected, just continue and hope for the best + if (!has_reset) + return; // wait > 100us hal_waitUntil(os_getTime() + ms2osticks(1)); @@ -1133,12 +1141,12 @@ void radio_init (bool calibrate) { // optionally perform receiver chain calibration in FSK/STANDBY mode if (calibrate) { - // set band/frequency - configChannel(); + // set band/frequency + configChannel(); - // run receiver chain calibration - writeReg(FSKRegImageCal, RF_IMAGECAL_IMAGECAL_START); // (clear auto-cal) - while ( readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_RUNNING ); + // run receiver chain calibration + writeReg(FSKRegImageCal, RF_IMAGECAL_IMAGECAL_START); // (clear auto-cal) + while ( readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_RUNNING ); } // go to SLEEP mode @@ -1152,96 +1160,98 @@ void radio_init (bool calibrate) { // (run by irqjob) bool radio_irq_process (ostime_t irqtime, u1_t diomask) { + (void)diomask; //unused + // dispatch modem - if (getSf(LMIC.rps) == FSK) { // FSK modem - u1_t irqflags1 = readReg(FSKRegIrqFlags1); - u1_t irqflags2 = readReg(FSKRegIrqFlags2); + if (isFsk(LMIC.rps)) { // FSK modem + u1_t irqflags1 = readReg(FSKRegIrqFlags1); + u1_t irqflags2 = readReg(FSKRegIrqFlags2); - if (irqflags2 & IRQ_FSK2_PACKETSENT_MASK) { // TXDONE - BACKTRACE(); + if (irqflags2 & IRQ_FSK2_PACKETSENT_MASK) { // TXDONE + BACKTRACE(); // save exact tx time LMIC.txend = irqtime - FSK_TXDONE_FIXUP; - } else if (irqflags2 & IRQ_FSK2_PAYLOADREADY_MASK) { // RXDONE - BACKTRACE(); + } else if (irqflags2 & IRQ_FSK2_PAYLOADREADY_MASK) { // RXDONE + BACKTRACE(); // read rx quality parameters (at end of packet, not optimal since energy might already be gone) - // (unfortunately in SX1272/SX1276 no averaged RSSI available in FSK mode, better in SX1261) - LMIC.rssi = -readReg(FSKRegRssiValue) / 2 + RSSI_OFF; - LMIC.snr = 0; // N/A + // (unfortunately in SX1272/SX1276 no averaged RSSI available in FSK mode, better in SX1261) + LMIC.rssi = -readReg(FSKRegRssiValue) / 2 + RSSI_OFF; + LMIC.snr = 0; // N/A - // read FIFO - UnloadFifo(); + // read FIFO + UnloadFifo(); // save exact rx timestamps LMIC.rxtime = irqtime - FSK_RXDONE_FIXUP; // end of frame timestamp - LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp + LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp #ifdef DEBUG_RX - debug_printf("RX[freq=%.1F,FSK,rssi=%d,len=%d]: %h\r\n", - LMIC.freq, 6, LMIC.rssi - RSSI_OFF, LMIC.dataLen, LMIC.frame, LMIC.dataLen); + debug_printf("RX[rssi=%d,len=%d]: %h\r\n", + LMIC.rssi - RSSI_OFF, LMIC.dataLen, LMIC.frame, LMIC.dataLen); #endif - } else if (irqflags1 & IRQ_FSK1_TIMEOUT_MASK) { // TIMEOUT - BACKTRACE(); + } else if (irqflags1 & IRQ_FSK1_TIMEOUT_MASK) { // TIMEOUT + BACKTRACE(); // indicate timeout LMIC.dataLen = 0; #ifdef DEBUG_RX - debug_printf("RX[freq=%.1F,FSK]: TIMEOUT (%d us)\r\n", LMIC.freq, 6, osticks2us(irqtime - LMIC.rxtime)); + debug_printf("RX: TIMEOUT (%d us)\r\n", osticks2us(irqtime - LMIC.rxtime)); #endif - } else if( irqflags2 & IRQ_FSK2_FIFOEMPTY_MASK ) { // FIFOEMPTY (TX) - BACKTRACE(); + } else if( irqflags2 & IRQ_FSK2_FIFOEMPTY_MASK ) { // FIFOEMPTY (TX) + BACKTRACE(); - // fill FIFO buffer - LoadFifo(); + // fill FIFO buffer + LoadFifo(); - // update tx timeout - radio_set_irq_timeout(irqtime + us2osticks((FIFOTHRESH+10)*8*1000/50)); + // update tx timeout + radio_set_irq_timeout(irqtime + us2osticks((u4_t)(FIFOTHRESH+10)*8*1000/50)); - // keep waiting for FifoEmpty or PacketSent interrupt - return false; + // keep waiting for FifoEmpty or PacketSent interrupt + return false; - } else if( irqflags2 & IRQ_FSK2_FIFOLEVEL_MASK ) { // FIFOLEVEL (RX) - BACKTRACE(); + } else if( irqflags2 & IRQ_FSK2_FIFOLEVEL_MASK ) { // FIFOLEVEL (RX) + BACKTRACE(); - // read FIFO buffer - UnloadFifo(); + // read FIFO buffer + UnloadFifo(); - // update rx timeout - radio_set_irq_timeout(irqtime + us2osticks((FIFOTHRESH+10)*8*1000/50)); + // update rx timeout + radio_set_irq_timeout(irqtime + us2osticks((u4_t)(FIFOTHRESH+10)*8*1000/50)); - // keep waiting for FifoLevel or PayloadReady interrupt - return false; + // keep waiting for FifoLevel or PayloadReady interrupt + return false; - } else { - // unexpected irq - debug_printf("UNEXPECTED FSK IRQ %02x %02x\r\n", irqflags1, irqflags2); - ASSERT(0); - } + } else { + // unexpected irq + debug_printf("UNEXPECTED FSK IRQ %02x %02x\r\n", irqflags1, irqflags2); + ASSERT(0); + } - // clear FSK IRQ flags - writeReg(FSKRegIrqFlags1, 0xFF); - writeReg(FSKRegIrqFlags2, 0xFF); + // clear FSK IRQ flags + writeReg(FSKRegIrqFlags1, 0xFF); + writeReg(FSKRegIrqFlags2, 0xFF); } else { // LORA modem - u1_t irqflags = readReg(LORARegIrqFlags); + u1_t irqflags = readReg(LORARegIrqFlags); if (irqflags & IRQ_LORA_TXDONE_MASK) { // TXDONE - BACKTRACE(); + BACKTRACE(); // save exact tx time LMIC.txend = irqtime - LORA_TXDONE_FIXUP; } else if (irqflags & IRQ_LORA_RXDONE_MASK) { // RXDONE (rx or scan) - BACKTRACE(); + BACKTRACE(); // read rx quality parameters (averaged over packet) LMIC.snr = readReg(LORARegPktSnrValue); // SNR [dB] * 4 - LMIC.rssi = readReg(LORARegPktRssiValue); // final values -128..127 correspond to -196...+63 dBm (subtract RSSI_OFF) - if (LMIC.snr < 0) { - LMIC.rssi = -RSSI_HF_CONST + LMIC.rssi + LMIC.snr/4 + RSSI_OFF; - } else { - LMIC.rssi = -RSSI_HF_CONST + LMIC.rssi * 16/15 + RSSI_OFF; - } + LMIC.rssi = readReg(LORARegPktRssiValue); // final values -128..127 correspond to -196...+63 dBm (subtract RSSI_OFF) + if (LMIC.snr < 0) { + LMIC.rssi = -RSSI_HF_CONST + LMIC.rssi + LMIC.snr/4 + RSSI_OFF; + } else { + LMIC.rssi = -RSSI_HF_CONST + LMIC.rssi * 16/15 + RSSI_OFF; + } // get PDU length LMIC.dataLen = readReg(LORARegRxNbBytes); @@ -1254,49 +1264,47 @@ bool radio_irq_process (ostime_t irqtime, u1_t diomask) { else if (getBw(LMIC.rps) == BW500) { LMIC.rxtime -= LORA_RXDONE_FIXUP_500[getSf(LMIC.rps)]; } - LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp + LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp - // set FIFO read address pointer (to address of last packet received) - writeReg(LORARegFifoAddrPtr, readReg(LORARegFifoRxCurrentAddr)); + // set FIFO read address pointer (to address of last packet received) + writeReg(LORARegFifoAddrPtr, readReg(LORARegFifoRxCurrentAddr)); - // read FIFO - radio_readBuf(RegFifo, LMIC.frame, LMIC.dataLen); + // read FIFO + radio_readBuf(RegFifo, LMIC.frame, LMIC.dataLen); #ifdef DEBUG_RX - debug_printf("RX[freq=%.1F,sf=%d,bw=%d,rssi=%d,snr=%.2F,len=%d]: %.80h\r\n", - LMIC.freq, 6, getSf(LMIC.rps) + 6, 125 << getBw(LMIC.rps), - LMIC.rssi - RSSI_OFF, LMIC.snr * 100 / SNR_SCALEUP, 2, - LMIC.dataLen, LMIC.frame, LMIC.dataLen); + debug_printf("RX[rssi=%d,snr=%.2F,len=%d]: %.80h\r\n", + LMIC.rssi - RSSI_OFF, (s4_t)(LMIC.snr * 100 / SNR_SCALEUP), 2, + LMIC.dataLen, LMIC.frame, LMIC.dataLen); #endif - } else if (irqflags & IRQ_LORA_RXTOUT_MASK) { // RXTOUT - BACKTRACE(); + } else if (irqflags & IRQ_LORA_RXTOUT_MASK) { // RXTOUT + BACKTRACE(); // indicate timeout LMIC.dataLen = 0; #ifdef DEBUG_RX - debug_printf("RX[freq=%.1F,sf=%d,bw=%d]: TIMEOUT (%d us)\r\n", - LMIC.freq, 6, getSf(LMIC.rps) + 6, 125 << getBw(LMIC.rps), osticks2us(irqtime - LMIC.rxtime)); + debug_printf("RX: TIMEOUT (%d us)\r\n"); #endif - } else if (irqflags & IRQ_LORA_CDDONE_MASK) { // CDDONE - BACKTRACE(); - // check if preamble symbol was detected - if (irqflags & IRQ_LORA_CDDETD_MASK) { - // switch to receiving (continuous) - writeReg(RegOpMode, OPMODE_LORA_RX); - // continue waiting - return false; - } else { - // indicate timeout - LMIC.dataLen = 0; - } + } else if (irqflags & IRQ_LORA_CDDONE_MASK) { // CDDONE + BACKTRACE(); + // check if preamble symbol was detected + if (irqflags & IRQ_LORA_CDDETD_MASK) { + // switch to receiving (continuous) + writeReg(RegOpMode, OPMODE_LORA_RX); + // continue waiting + return false; + } else { + // indicate timeout + LMIC.dataLen = 0; + } } else { - // unexpected irq - ASSERT(0); - } + // unexpected irq + ASSERT(0); + } - // mask all LoRa IRQs - writeReg(LORARegIrqFlagsMask, 0xFF); + // mask all LoRa IRQs + writeReg(LORARegIrqFlagsMask, 0xFF); - // clear LoRa IRQ flags - writeReg(LORARegIrqFlags, 0xFF); + // clear LoRa IRQ flags + writeReg(LORARegIrqFlags, 0xFF); } // radio operation completed diff --git a/lmic/radio.c b/lmic/radio.c index e1e702b..6bab834 100644 --- a/lmic/radio.c +++ b/lmic/radio.c @@ -26,8 +26,11 @@ static void radio_stop (void) { radio_sleep(); // disable antenna switch hal_ant_switch(HAL_ANTSW_OFF); +#if defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) // power-down TCXO hal_pin_tcxo(0); +#endif // defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) + // disable antenna switch // disable IRQs in HAL hal_irqmask_set(0); // cancel radio job @@ -41,13 +44,14 @@ static void radio_stop (void) { // protected job - runs with irqs disabled! static void radio_irq_timeout (osjob_t* j) { BACKTRACE(); + (void)j; // unused // stop everything (antenna switch, hal irqs, sleep, irq job) radio_stop(); // re-initialize radio if tx operation timed out if (state.txmode) { - radio_init(true); + radio_init(true); } // enable IRQs! @@ -69,13 +73,14 @@ void radio_set_irq_timeout (ostime_t timeout) { // (run by irqjob) static void radio_irq_func (osjob_t* j) { + (void)j; // unused // call radio-specific processing function if( radio_irq_process(state.irqtime, state.diomask) ) { - // current radio operation has completed - radio_stop(); // (disable antenna switch and HAL irqs, make radio sleep) + // current radio operation has completed + radio_stop(); // (disable antenna switch and HAL irqs, make radio sleep) - // run LMIC job (use preset func ptr) - os_setCallback(&LMIC.osjob, LMIC.osjob.func); + // run LMIC job (use preset func ptr) + os_setCallback(&LMIC.osjob, LMIC.osjob.func); } // clear irq state (job has been run) @@ -101,71 +106,104 @@ void radio_irq_handler (u1_t diomask, ostime_t ticks) { void os_radio (u1_t mode) { switch (mode) { - case RADIO_STOP: - radio_stop(); - break; + case RADIO_STOP: + radio_stop(); + break; - case RADIO_TX: - radio_stop(); + case RADIO_TX: + radio_stop(); #ifdef DEBUG_TX - debug_printf("TX[fcnt=%d,freq=%.1F,sf=%d,bw=%d,pow=%d,len=%d%s]: %.80h\r\n", - LMIC.seqnoUp - 1, LMIC.freq, 6, getSf(LMIC.rps) + 6, 125 << getBw(LMIC.rps), - LMIC.txpow, LMIC.dataLen, - (LMIC.pendTxPort != 0 && (LMIC.frame[OFF_DAT_FCT] & FCT_ADRARQ)) ? ",ADRARQ" : "", - LMIC.frame, LMIC.dataLen); + if( isFsk(LMIC.rps) ) { + debug_printf("TX[mod=FSK,nocrc=%d", getNocrc(LMIC.rps)); + } else { + ASSERT(isLora(LMIC.rps)); + debug_printf("TX[mod=LoRa,sf=%d,bw=%d,cr=4/%d,nocrc=%d,ih=%d", + getSf(LMIC.rps) - SF7 + 7, 125 << (getBw(LMIC.rps) - BW125), + getCr(LMIC.rps) - CR_4_5 + 5, getNocrc(LMIC.rps), getIh(LMIC.rps)); + } + debug_printf_continue(",fcnt=%lu,freq=%.1F,pow=%d,len=%d%s]: %.80h\r\n", + (LMIC.seqnoUp ? LMIC.seqnoUp - 1 : 0), + LMIC.freq, 6, + LMIC.txpow, LMIC.dataLen, + (LMIC.pendTxPort != 0 && (LMIC.frame[OFF_DAT_FCT] & FCT_ADRARQ)) ? ",ADRARQ" : "", + LMIC.frame, LMIC.dataLen); +#endif + // transmit frame now (wait for completion interrupt) + radio_starttx(false); + // set timeout for tx operation (should not happen) + state.txmode = 1; + radio_set_irq_timeout(os_getTime() + ms2osticks(20) + LMIC_calcAirTime(LMIC.rps, LMIC.dataLen) * 110 / 100); + break; + + case RADIO_RX: + radio_stop(); +#ifdef DEBUG_RX + if( isFsk(LMIC.rps) ) { + debug_printf("RX_MODE[mod=FSK,nocrc=%d", getNocrc(LMIC.rps)); + } else { + ASSERT(isLora(LMIC.rps)); + debug_printf("RX_MODE[mod=LoRa,sf=%d,bw=%d,cr=4/%d,nocrc=%d,ih=%d", + getSf(LMIC.rps) - SF7 + 7, 125 << (getBw(LMIC.rps) - BW125), + getCr(LMIC.rps) - CR_4_5 + 5, getNocrc(LMIC.rps), getIh(LMIC.rps)); + } + debug_printf_continue(",freq=%.1F,rxtime=%.0F]\r\n", + LMIC.freq, 6, + LMIC.rxtime, 0); +#endif + // receive frame at rxtime/now (wait for completion interrupt) + radio_startrx(false); + // set timeout for rx operation (should not happen, might be updated by radio driver) + state.txmode = 0; + radio_set_irq_timeout(LMIC.rxtime + ms2osticks(5) + LMIC_calcAirTime(LMIC.rps, 255) * 110 / 100); + break; + + case RADIO_RXON: + radio_stop(); +#ifdef DEBUG_RX + if( isFsk(LMIC.rps) ) { + debug_printf("RXON_MODE[mod=FSK,nocrc=%d", getNocrc(LMIC.rps)); + } else { + ASSERT(isLora(LMIC.rps)); + debug_printf("RXON_MODE[mod=LoRa,sf=%d,bw=%d,cr=4/%d,nocrc=%d,ih=%d", + getSf(LMIC.rps) - SF7 + 7, 125 << (getBw(LMIC.rps) - BW125), + getCr(LMIC.rps) - CR_4_5 + 5, getNocrc(LMIC.rps), getIh(LMIC.rps)); + } + debug_printf_continue(",freq=%.1F]\r\n", LMIC.freq, 6); #endif - // set timeout for tx operation (should not happen) - state.txmode = 1; - radio_set_irq_timeout(os_getTime() + ms2osticks(20) + LMIC_calcAirTime(LMIC.rps, LMIC.dataLen) * 110 / 100); - // transmit frame now (wait for completion interrupt) - radio_starttx(false); - break; - - case RADIO_RX: - radio_stop(); - // set timeout for rx operation (should not happen, might be updated by radio driver) - state.txmode = 0; - radio_set_irq_timeout(LMIC.rxtime + ms2osticks(5) + LMIC_calcAirTime(LMIC.rps, 255) * 110 / 100); - // receive frame at rxtime/now (wait for completion interrupt) - radio_startrx(false); - break; - - case RADIO_RXON: - radio_stop(); - // start scanning for frame now (wait for completion interrupt) - state.txmode = 0; - radio_startrx(true); - break; - - case RADIO_TXCW: - radio_stop(); - // transmit continuous wave (until abort) - radio_cw(); - break; - - case RADIO_CCA: - radio_stop(); - // clear channel assessment - radio_cca(); - break; - - case RADIO_INIT: - // reset and calibrate radio (uses LMIC.freq) - radio_init(true); - break; - - case RADIO_TXCONT: - radio_stop(); - radio_starttx(true); - break; - - case RADIO_CAD: - radio_stop(); - // set timeout for cad/rx operation (should not happen, might be updated by radio driver) - state.txmode = 0; - radio_set_irq_timeout(os_getTime() + ms2osticks(10) + LMIC_calcAirTime(LMIC.rps, 255) * 110 / 100); - // channel activity detection and rx if preamble symbol found - radio_cad(); - break; + // start scanning for frame now (wait for completion interrupt) + state.txmode = 0; + radio_startrx(true); + break; + + case RADIO_TXCW: + radio_stop(); + // transmit continuous wave (until abort) + radio_cw(); + break; + + case RADIO_CCA: + radio_stop(); + // clear channel assessment + radio_cca(); + break; + + case RADIO_INIT: + // reset and calibrate radio (uses LMIC.freq) + radio_init(true); + break; + + case RADIO_TXCONT: + radio_stop(); + radio_starttx(true); + break; + + case RADIO_CAD: + radio_stop(); + // set timeout for cad/rx operation (should not happen, might be updated by radio driver) + state.txmode = 0; + radio_set_irq_timeout(os_getTime() + ms2osticks(10) + LMIC_calcAirTime(LMIC.rps, 255) * 110 / 100); + // channel activity detection and rx if preamble symbol found + radio_cad(); + break; } } diff --git a/lmic/region.h b/lmic/region.h index 6dd4f43..7a4bd58 100644 --- a/lmic/region.h +++ b/lmic/region.h @@ -6,6 +6,8 @@ #ifndef _region_h_ #define _region_h_ +#include "board.h" + // public region codes - DO NOT CHANGE! enum { REGCODE_UNDEF = 0, @@ -138,7 +140,7 @@ enum { #define MAX_FIX_CHNLS_500 0 #endif -#define MAX_FIX_CHNLS (MAX_FIX_CHNLS_125 + MAX_FIX_CHNLS_500) +#define MAX_FIX_CHNLS (MAX_FIX_CHNLS_125 + MAX_FIX_CHNLS_500) #endif diff --git a/projects/ex-join/Makefile b/projects/ex-join/Makefile index 40130ba..769fb4c 100644 --- a/projects/ex-join/Makefile +++ b/projects/ex-join/Makefile @@ -20,6 +20,7 @@ SVCS += app DEFS += -DDEBUG_RX DEFS += -DDEBUG_TX +DEFS += -DUSE_IDEETRON_AES DEFS += -DSVC_FRAG_TEST # enable proprietary test command in frag service diff --git a/projects/projects.gmk b/projects/projects.gmk index 1b9f2d7..d4abb45 100644 --- a/projects/projects.gmk +++ b/projects/projects.gmk @@ -14,6 +14,7 @@ include $(TOPDIR)/projects/platform.mk #TARGET ?= $(error "TARGET not specified (See platform.gmk for possible settings)") LMICDIR := $(TOPDIR)/lmic +AESDIR := $(TOPDIR)/aes TOOLSDIR := $(TOPDIR)/tools COMMONDIR := $(TOPDIR)/common SVCSDIR := $(TOPDIR)/services @@ -36,6 +37,7 @@ ASDEFS += $(PDEFS) CFLAGS += -I$(LMICDIR) -I$(HALDIR) CFLAGS += -I$(COMMONDIR) +CFLAGS += -Werror BUILDTIME := $(shell date -u +'%FT%TZ') @@ -46,9 +48,9 @@ else PATCHFLAGS += -v endif -SRCS += $(notdir $(wildcard $(LMICDIR)/*.c $(HALDIR)/*.c *.c $(HALDIR)/*.S)) +SRCS += $(notdir $(wildcard $(LMICDIR)/*.c $(AESDIR)/*.c $(HALDIR)/*.c *.c $(HALDIR)/*.S)) -VPATH += $(LMICDIR) $(HALDIR) +VPATH += $(LMICDIR) $(AESDIR) $(HALDIR) ifneq ($(COMMON),) SRCS += $(COMMON) diff --git a/services/appstart/main.c b/services/appstart/main.c index ab330f3..2b32f6d 100644 --- a/services/appstart/main.c +++ b/services/appstart/main.c @@ -25,11 +25,11 @@ static void initfunc (osjob_t* job) { fwi.blversion, PROJECT_STR, VARIANT_STR, fwi.version, fwi.crc, #if 0 - (BOOT_DEVINFO->bootmode == TABS_BOOT_UFT) ? "uft" : - (BOOT_DEVINFO->bootmode == TABS_BOOT_SHIPPING) ? "ship" : - (BOOT_DEVINFO->bootmode == TABS_BOOT_FLIGHT) ? "flight" : + (BOOT_DEVINFO->bootmode == TABS_BOOT_UFT) ? "uft" : + (BOOT_DEVINFO->bootmode == TABS_BOOT_SHIPPING) ? "ship" : + (BOOT_DEVINFO->bootmode == TABS_BOOT_FLIGHT) ? "flight" : #endif - "normal"); + "normal"); #endif #ifdef SVC_backtrace diff --git a/services/lwmux/lwmux.c b/services/lwmux/lwmux.c index 7cb1e0b..a5a4f09 100644 --- a/services/lwmux/lwmux.c +++ b/services/lwmux/lwmux.c @@ -9,23 +9,23 @@ DECL_ON_LMIC_EVENT; enum { - FLAG_MODESWITCH = (1 << 0), // mode switch pending - FLAG_BUSY = (1 << 1), // radio is busy - FLAG_JOINING = (1 << 2), // trying to join - FLAG_SHUTDOWN = (1 << 3), // shutdown reqested + FLAG_MODESWITCH = (1 << 0), // mode switch pending + FLAG_BUSY = (1 << 1), // radio is busy + FLAG_JOINING = (1 << 2), // trying to join + FLAG_SHUTDOWN = (1 << 3), // shutdown reqested }; static struct { - unsigned int flags; // flags (FLAG_*) - unsigned int mode; // current mode (LWM_MODE_*) - unsigned int nextmode; // next mode (if FLAG_MODESWITCH is set) + unsigned int flags; // flags (FLAG_*) + unsigned int mode; // current mode (LWM_MODE_*) + unsigned int nextmode; // next mode (if FLAG_MODESWITCH is set) - lwm_job* queue; // job queue head + lwm_job* queue; // job queue head unsigned int runprio; // minimum priority level for runnning jobs - lwm_complete completefunc; // current job completion function - osjob_t job; // tx opportunity job + lwm_complete completefunc; // current job completion function + osjob_t job; // tx opportunity job - unsigned int jcnt; // join attempt counter + unsigned int jcnt; // join attempt counter struct { bool use_profile; // Use ADR profile instead of network-managed @@ -37,21 +37,21 @@ static struct { #ifdef LWM_SLOTTED struct { - ostime_t interval; // beacon interval - u4_t freq; // beacon frequency - dr_t dndr; // beacon datarate + ostime_t interval; // beacon interval + u4_t freq; // beacon frequency + dr_t dndr; // beacon datarate - u1_t slotsz; // slot size (payload length in bytes) - ostime_t t_slots; // time span available for uplink slots - ostime_t off_slots; // offset to first slot relative to beacon start + u1_t slotsz; // slot size (payload length in bytes) + ostime_t t_slots; // time span available for uplink slots + ostime_t off_slots; // offset to first slot relative to beacon start - ostime_t nextrx; // time of next beacon + ostime_t nextrx; // time of next beacon - int missed; // number of missed beacons - int missed_max; // max. number of missed beacons before going back to scanning + int missed; // number of missed beacons + int missed_max; // max. number of missed beacons before going back to scanning - int timeouts; // number of beacon scan timeouts - int timeouts_max; // max. number of beacon scan timeouts before creating an unaligned TX opportunity + int timeouts; // number of beacon scan timeouts + int timeouts_max; // max. number of beacon scan timeouts before creating an unaligned TX opportunity } bcn; #endif } state; @@ -253,13 +253,13 @@ static void reschedule_join (void) { os_setApproxTimedCallback(&state.job, os_getTime() + ( #if defined(CFG_eu868) || defined(CFG_in865) - (state.jcnt < 10) ? sec2osticks(360) : // first hour: every 6 minutes - (state.jcnt < 20) ? sec2osticks(3600) : // next 10 hours: every hour - sec2osticks(3600 * 12) // after: every 12 hours + (state.jcnt < 10) ? sec2osticks(360) : // first hour: every 6 minutes + (state.jcnt < 20) ? sec2osticks(3600) : // next 10 hours: every hour + sec2osticks(3600 * 12) // after: every 12 hours #elif defined(CFG_us915) - (state.jcnt < 6) ? sec2osticks(600) : // first hour: every 10 minutes - (state.jcnt < 12) ? sec2osticks(6000) : // next 10 hours: every 100 minutes - sec2osticks(3600 * 12) // after: every 12 hours + (state.jcnt < 6) ? sec2osticks(600) : // first hour: every 10 minutes + (state.jcnt < 12) ? sec2osticks(6000) : // next 10 hours: every 100 minutes + sec2osticks(3600 * 12) // after: every 12 hours #else #warning "Unsupported region" sec2osticks(3600) @@ -301,7 +301,7 @@ static bool mode_switch (void) { #ifdef LWM_SLOTTED else if (state.nextmode == LWM_MODE_SLOTTED) { debug_printf("slotted\r\n"); - state.bcn.missed = state.bcn.missed_max; // start with scanning + state.bcn.missed = state.bcn.missed_max; // start with scanning state.bcn.timeouts = 0; if (!(state.flags & (FLAG_BUSY | FLAG_JOINING))) { bcn_continue(); @@ -433,7 +433,7 @@ DECL_ON_LMIC_EVENT { if (e == EV_TXCOMPLETE || e == EV_RXCOMPLETE) { if ((LMIC.txrxFlags & TXRX_PORT) && LMIC.frame[LMIC.dataBeg-1]) { SVCHOOK_lwm_downlink(LMIC.frame[LMIC.dataBeg-1], - LMIC.frame + LMIC.dataBeg, LMIC.dataLen, LMIC.txrxFlags & LWM_FLAG_MASK); + LMIC.frame + LMIC.dataBeg, LMIC.dataLen, LMIC.txrxFlags & LWM_FLAG_MASK); } } diff --git a/services/lwtest/testmode.c b/services/lwtest/testmode.c index a0fdb8c..b2677d0 100644 --- a/services/lwtest/testmode.c +++ b/services/lwtest/testmode.c @@ -74,28 +74,28 @@ static void stoptestmode (void) { static bool txfunc (lwm_txinfo* txi) { if (testmode.confirmed && (LMIC.txrxFlags & TXRX_NACK) && testmode.retrans < 8) { - // no ACK received - retransmit last uplink data in LMIC.pendTxData with same seqnoUp - txi->dlen = LMIC.pendTxLen; - LMIC.seqnoUp -= 1; - testmode.retrans += 1; + // no ACK received - retransmit last uplink data in LMIC.pendTxData with same seqnoUp + txi->dlen = LMIC.pendTxLen; + LMIC.seqnoUp -= 1; + testmode.retrans += 1; } else if (LMIC.frame[LMIC.dataBeg] == TESTCMD_ECHO) { txi->data[0] = TESTCMD_ECHO; for( int i = 1; i < LMIC.dataLen; i++ ) { txi->data[i] = LMIC.frame[LMIC.dataBeg + i] + 1; } - txi->dlen = LMIC.dataLen; - testmode.retrans = 0; + txi->dlen = LMIC.dataLen; + testmode.retrans = 0; } else { - txi->data[0] = testmode.dncnt >> 8; // fill in downlink_counter - txi->data[1] = testmode.dncnt; // (2 bytes, big endian) - txi->dlen = 2; - testmode.retrans = 0; + txi->data[0] = testmode.dncnt >> 8; // fill in downlink_counter + txi->data[1] = testmode.dncnt; // (2 bytes, big endian) + txi->dlen = 2; + testmode.retrans = 0; } txi->port = TESTMODE_PORT; txi->confirmed = testmode.confirmed; LMIC_setAdrMode(1); debug_printf("TESTMODE UPLINK #%d (%sconfirmed, seq=%d, len=%d): %h\r\n", - testmode.uptotal++, (testmode.confirmed) ? "" : "un", LMIC.seqnoUp, txi->dlen, txi->data, txi->dlen); + testmode.uptotal++, (testmode.confirmed) ? "" : "un", LMIC.seqnoUp, txi->dlen, txi->data, txi->dlen); return true; } @@ -113,104 +113,104 @@ static void stopcw (osjob_t* job) { // referenced by tabs / rm_event() void testmode_handleEvent (ev_t ev) { switch (ev) { - case EV_TXCOMPLETE: { - // check for downlink - if (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2)) { - unsigned char *buf = LMIC.frame + LMIC.dataBeg; - int port = -1; - if (LMIC.txrxFlags & TXRX_PORT) { - port = LMIC.frame[LMIC.dataBeg - 1]; - } - - // save timestamp of last downlink - testmode.dntime = os_getTime(); - - // reset uplink-without-downlink counter - testmode.upcnt = 0; - - if (testmode.active) { - debug_printf("TESTMODE DOWNLINK (seq=%d, port=%d, len=%d%s%s%s%s): %h\r\n", - LMIC.seqnoDn, port, LMIC.dataLen, - (LMIC.txrxFlags & TXRX_DNW1) ? ", RX1" : "", - (LMIC.txrxFlags & TXRX_DNW2) ? ", RX2" : "", - (LMIC.txrxFlags & TXRX_ACK) ? ", ACK" : "", - (LMIC.txrxFlags & TXRX_NACK) ? ", NACK" : "", - buf, LMIC.dataLen); - - // update downlink counter + case EV_TXCOMPLETE: { + // check for downlink + if (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2)) { + unsigned char *buf = LMIC.frame + LMIC.dataBeg; + int port = -1; + if (LMIC.txrxFlags & TXRX_PORT) { + port = LMIC.frame[LMIC.dataBeg - 1]; + } + + // save timestamp of last downlink + testmode.dntime = os_getTime(); + + // reset uplink-without-downlink counter + testmode.upcnt = 0; + + if (testmode.active) { + debug_printf("TESTMODE DOWNLINK (seq=%d, port=%d, len=%d%s%s%s%s): %h\r\n", + LMIC.seqnoDn, port, LMIC.dataLen, + (LMIC.txrxFlags & TXRX_DNW1) ? ", RX1" : "", + (LMIC.txrxFlags & TXRX_DNW2) ? ", RX2" : "", + (LMIC.txrxFlags & TXRX_ACK) ? ", ACK" : "", + (LMIC.txrxFlags & TXRX_NACK) ? ", NACK" : "", + buf, LMIC.dataLen); + + // update downlink counter if( testmode.lastdf != LMIC.seqnoDn) { testmode.lastdf = LMIC.seqnoDn; testmode.dncnt += 1; } - if (port == TESTMODE_PORT && LMIC.dataLen > 0) { - - // dispatch test commands - switch (buf[0]) { - - case TESTCMD_STOP: // deactivate test mode - stoptestmode(); - break; - - case TESTCMD_CONFIRMED: // activate confirmations - testmode.confirmed = 1; - break; - - case TESTCMD_UNCONFIRMED: // deactivate confirmations - testmode.confirmed = 0; - break; - - case TESTCMD_LINKCHECK: // XXX undocumented?!? - // XXX - break; - - case TESTCMD_JOIN: // trigger join request - stoptestmode(); // (activation command will be resent after join) - lwm_setmode(LWM_MODE_SHUTDOWN); - lwm_setmode(LWM_MODE_NORMAL); - break; - - case TESTCMD_ECHO: // modify and echo frame - LMIC.pendTxData[0] = buf[0]; - for (int i = 1; i < LMIC.dataLen; i++) { - LMIC.pendTxData[i] = buf[i] + 1; - } - LMIC.pendTxLen = LMIC.dataLen; - break; - - case TESTCMD_CW: // continous wave - // set timeout and parameters - os_setApproxTimedCallback(&testmode.timer, os_getTime() + sec2osticks((buf[1] << 8) | buf[2]), stopcw); // duration [s] - LMIC.freq = ((buf[3] << 16) | (buf[4] << 8) | buf[5]) * 100; // [Hz] - LMIC.txpow = buf[6]; // dBm - // start continuous wave - os_radio(RADIO_TXCW); - return; // no uplink now - } - } - } else { // test mode not active - if (port == TESTMODE_PORT && LMIC.dataLen == 4 && os_rlsbf4(buf) == 0x01010101) { - // activate test mode - starttestmode(); - } - } - } else { // no downlink - if (testmode.active && - (++testmode.upcnt > TESTMODE_MAXCNT || - (os_getTime() - testmode.dntime) > sec2osticks(TESTMODE_TIMEOUT))) { - // test mode timed out - debug_printf("TEST MODE TIMEOUT\r\n"); - stoptestmode(); - } - } - - if (testmode.active) { - // schedule next uplink - os_setApproxTimedCallback(&testmode.timer, os_getTime() + sec2osticks(TESTMODE_INTERVAL), uplink); - } - } - - default: // ignore other events - break; + if (port == TESTMODE_PORT && LMIC.dataLen > 0) { + + // dispatch test commands + switch (buf[0]) { + + case TESTCMD_STOP: // deactivate test mode + stoptestmode(); + break; + + case TESTCMD_CONFIRMED: // activate confirmations + testmode.confirmed = 1; + break; + + case TESTCMD_UNCONFIRMED: // deactivate confirmations + testmode.confirmed = 0; + break; + + case TESTCMD_LINKCHECK: // XXX undocumented?!? + // XXX + break; + + case TESTCMD_JOIN: // trigger join request + stoptestmode(); // (activation command will be resent after join) + lwm_setmode(LWM_MODE_SHUTDOWN); + lwm_setmode(LWM_MODE_NORMAL); + break; + + case TESTCMD_ECHO: // modify and echo frame + LMIC.pendTxData[0] = buf[0]; + for (int i = 1; i < LMIC.dataLen; i++) { + LMIC.pendTxData[i] = buf[i] + 1; + } + LMIC.pendTxLen = LMIC.dataLen; + break; + + case TESTCMD_CW: // continous wave + // set timeout and parameters + os_setApproxTimedCallback(&testmode.timer, os_getTime() + sec2osticks((buf[1] << 8) | buf[2]), stopcw); // duration [s] + LMIC.freq = ((buf[3] << 16) | (buf[4] << 8) | buf[5]) * 100; // [Hz] + LMIC.txpow = buf[6]; // dBm + // start continuous wave + os_radio(RADIO_TXCW); + return; // no uplink now + } + } + } else { // test mode not active + if (port == TESTMODE_PORT && LMIC.dataLen == 4 && os_rlsbf4(buf) == 0x01010101) { + // activate test mode + starttestmode(); + } + } + } else { // no downlink + if (testmode.active && + (++testmode.upcnt > TESTMODE_MAXCNT || + (os_getTime() - testmode.dntime) > sec2osticks(TESTMODE_TIMEOUT))) { + // test mode timed out + debug_printf("TEST MODE TIMEOUT\r\n"); + stoptestmode(); + } + } + + if (testmode.active) { + // schedule next uplink + os_setApproxTimedCallback(&testmode.timer, os_getTime() + sec2osticks(TESTMODE_INTERVAL), uplink); + } + } + + default: // ignore other events + break; } } diff --git a/services/pwrman/pwrman.c b/services/pwrman/pwrman.c index b6ab0e0..921a8f2 100644 --- a/services/pwrman/pwrman.c +++ b/services/pwrman/pwrman.c @@ -48,7 +48,7 @@ static void add (ptime* ppt, uint64_t uaticks) { uint32_t uah = uaticks / (OSTICKS_PER_SEC * 60 * 60); if( uah != 0 ) { ppt->hr += uah; - uaticks -= ((int64_t) uah * (OSTICKS_PER_SEC * 60 * 60)); + uaticks -= ((int64_t) uah * (OSTICKS_PER_SEC * 60 * 60)); } ppt->ticks = uaticks; } diff --git a/stm32/eeprom.c b/stm32/eeprom.c index 9b14aec..badbcde 100644 --- a/stm32/eeprom.c +++ b/stm32/eeprom.c @@ -32,7 +32,7 @@ void eeprom_write (void* dest, unsigned int val) { FLASH->PECR |= FLASH_PECR_PELOCK; // verify value - ASSERT( *((volatile u4_t*) addr) == val ); + ASSERT( *((volatile u4_t*) addr) == val ); } } diff --git a/stm32/gpio.c b/stm32/gpio.c index 48b8f94..3a15372 100644 --- a/stm32/gpio.c +++ b/stm32/gpio.c @@ -80,10 +80,10 @@ void gpio_cfg_extirq_ex (int port, int pin, bool rising, bool falling) { EXTI->RTSR &= ~mask; // clear trigger EXTI->FTSR &= ~mask; // clear trigger if( rising ) { - EXTI->RTSR |= mask; + EXTI->RTSR |= mask; } if( falling ) { - EXTI->FTSR |= mask; + EXTI->FTSR |= mask; } // configure the NVIC @@ -104,10 +104,10 @@ void gpio_cfg_extirq (int port, int pin, int irqcfg) { void gpio_set_extirq (int pin, int on) { if (on) { - EXTI->PR = (1 << pin); - EXTI->IMR |= (1 << pin); + EXTI->PR = (1 << pin); + EXTI->IMR |= (1 << pin); } else { - EXTI->IMR &= ~(1 << pin); + EXTI->IMR &= ~(1 << pin); } } diff --git a/stm32/hal.c b/stm32/hal.c index dd78e51..d92b2bc 100644 --- a/stm32/hal.c +++ b/stm32/hal.c @@ -589,7 +589,7 @@ static u4_t sleep0 (u4_t hticks, u4_t htt, u4_t ltt) { } static void sleep (int stype, u4_t htt, u4_t ltt) { - static const u4_t(*sleepfuncs[])(u4_t,u4_t,u4_t) = { + static u4_t(* const sleepfuncs[])(u4_t,u4_t,u4_t) = { sleep0, sleep1, sleep2, @@ -744,6 +744,7 @@ static void hal_io_init () { #endif } +#if defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) bool hal_pin_tcxo (u1_t val) { #if defined(GPIO_TCXO_PWR) if (val != 0) { @@ -758,6 +759,7 @@ bool hal_pin_tcxo (u1_t val) { return false; #endif } +#endif // defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) void hal_ant_switch (u1_t val) { #ifdef SVC_pwrman @@ -796,13 +798,18 @@ void hal_ant_switch (u1_t val) { } // set radio RST pin to given value (or keep floating!) -void hal_pin_rst (u1_t val) { +bool hal_pin_rst (u1_t val) { + #ifdef GPIO_RST if(val == 0 || val == 1) { // drive pin SET_PIN(GPIO_RST, val); CFG_PIN(GPIO_RST, GPIOCFG_MODE_OUT | GPIOCFG_OSPEED_40MHz | GPIOCFG_OTYPE_PUPD | GPIOCFG_PUPD_NONE); } else { // keep pin floating CFG_PIN(GPIO_RST, GPIOCFG_MODE_ANA | GPIOCFG_OSPEED_400kHz | GPIOCFG_OTYPE_OPEN | GPIOCFG_PUPD_NONE); } + return true; + #else + return false; + #endif } void hal_pin_busy_wait (void) { @@ -815,6 +822,24 @@ void hal_pin_busy_wait (void) { #endif } +#if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) +bool hal_dio3_controls_tcxo (void) { + #if defined(LMIC_DIO3_CONTROLS_TCXO) + return true; + #else + return false; + #endif +} + +bool hal_dio2_controls_rxtx (void) { + #if defined(LMIC_DIO2_CONTROLS_RXTX) + return true; + #else + return false; + #endif +} +#endif // defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + #define DIO_UPDATE(dio,mask,time) do { \ if( (EXTI->PR & (1 << BRD_PIN(GPIO_DIO ## dio))) ) { \ EXTI->PR = (1 << BRD_PIN(GPIO_DIO ## dio)); \ diff --git a/stm32/hal_stm32.h b/stm32/hal_stm32.h index 1e34c10..dee2985 100644 --- a/stm32/hal_stm32.h +++ b/stm32/hal_stm32.h @@ -44,8 +44,8 @@ void hal_rtstats_collect (hal_rtstats* stats); // NVIC interrupt definition typedef struct { - uint32_t num; // NVIC interrupt number - void* handler; // Pointer to handler function + uint32_t num; // NVIC interrupt number + void* handler; // Pointer to handler function } irqdef; extern const irqdef HAL_irqdefs[]; diff --git a/stm32/hw.h b/stm32/hw.h index 9e835fa..60f0f50 100644 --- a/stm32/hw.h +++ b/stm32/hw.h @@ -80,63 +80,63 @@ enum { GPIO_LOHI = 0, GPIO_HILO = 1 }; // generate a transition, then switch to HiZ int gpio_transition (int port, int pin, int type, int duration, unsigned int config); -#define GPIO_DEFAULT_CFG (GPIOCFG_MODE_ANA | GPIOCFG_OSPEED_400kHz | GPIOCFG_OTYPE_OPEN | GPIOCFG_PUPD_NONE) -#define SET_PIN(gpio, val) gpio_set_pin(BRD_PORT(gpio), BRD_PIN(gpio), (val)) -#define GET_PIN(gpio) gpio_get_pin(BRD_PORT(gpio), BRD_PIN(gpio)) -#define CFG_PIN(gpio, opts) gpio_cfg_pin(BRD_PORT(gpio), BRD_PIN(gpio), (opts)) -#define CFG_PIN_VAL(gpio, o, s) gpio_cfg_set_pin(BRD_PORT(gpio), BRD_PIN(gpio), (o), (s)) -#define CFG_PIN_AF(gpio, opts) gpio_cfg_pin(BRD_PORT(gpio), BRD_PIN(gpio), GPIOCFG_MODE_ALT | BRD_AF(gpio) | (opts)) -#define CFG_PIN_DEFAULT(gpio) gpio_cfg_pin(BRD_PORT(gpio), BRD_PIN(gpio), GPIO_DEFAULT_CFG) -#define IRQ_PIN(gpio, opts) gpio_cfg_extirq(BRD_PORT(gpio), BRD_PIN(gpio), (opts)) -#define IRQ_PIN_SET(gpio, on) gpio_set_extirq(BRD_PIN(gpio), (on)) -#define TXN_PIN(gpio, t, d, c) gpio_transition(BRD_PORT(gpio), BRD_PIN(gpio), (t), (d), (c)) +#define GPIO_DEFAULT_CFG (GPIOCFG_MODE_ANA | GPIOCFG_OSPEED_400kHz | GPIOCFG_OTYPE_OPEN | GPIOCFG_PUPD_NONE) +#define SET_PIN(gpio, val) gpio_set_pin(BRD_PORT(gpio), BRD_PIN(gpio), (val)) +#define GET_PIN(gpio) gpio_get_pin(BRD_PORT(gpio), BRD_PIN(gpio)) +#define CFG_PIN(gpio, opts) gpio_cfg_pin(BRD_PORT(gpio), BRD_PIN(gpio), (opts)) +#define CFG_PIN_VAL(gpio, o, s) gpio_cfg_set_pin(BRD_PORT(gpio), BRD_PIN(gpio), (o), (s)) +#define CFG_PIN_AF(gpio, opts) gpio_cfg_pin(BRD_PORT(gpio), BRD_PIN(gpio), GPIOCFG_MODE_ALT | BRD_AF(gpio) | (opts)) +#define CFG_PIN_DEFAULT(gpio) gpio_cfg_pin(BRD_PORT(gpio), BRD_PIN(gpio), GPIO_DEFAULT_CFG) +#define IRQ_PIN(gpio, opts) gpio_cfg_extirq(BRD_PORT(gpio), BRD_PIN(gpio), (opts)) +#define IRQ_PIN_SET(gpio, on) gpio_set_extirq(BRD_PIN(gpio), (on)) +#define TXN_PIN(gpio, t, d, c) gpio_transition(BRD_PORT(gpio), BRD_PIN(gpio), (t), (d), (c)) #define SET_PIN_ONOFF(gpio, on) gpio_set_pin(BRD_PORT(gpio), BRD_PIN(gpio), (((gpio) & BRD_GPIO_ACTIVE_LOW) ? 1 : 0) ^ ((on) ? 1 : 0)) // Convenience macros to set GPIO configuration registers -#define GPIO_AF_BITS 4 // width of bit field -#define GPIO_AF_MASK 0x0F // mask in AFR[0/1] -#define GPIO_AFRLR(i) ((i) >> 3) -#define GPIO_AF_PINi(i,af) ((af) << (((i) & 7) * GPIO_AF_BITS)) -#define GPIO_AF_set(p,i,af) do { \ +#define GPIO_AF_BITS 4 // width of bit field +#define GPIO_AF_MASK 0x0F // mask in AFR[0/1] +#define GPIO_AFRLR(i) ((i) >> 3) +#define GPIO_AF_PINi(i,af) ((af) << (((i) & 7) * GPIO_AF_BITS)) +#define GPIO_AF_set(p,i,af) do { \ (p)->AFR[GPIO_AFRLR(i)] = ((p)->AFR[GPIO_AFRLR(i)] \ - & ~GPIO_AF_PINi(i, GPIO_AF_MASK)) | GPIO_AF_PINi(i, af); \ + & ~GPIO_AF_PINi(i, GPIO_AF_MASK)) | GPIO_AF_PINi(i, af); \ } while (0) -#define HW_CFG_PIN(p,i,cfg) do { \ +#define HW_CFG_PIN(p,i,cfg) do { \ if (((cfg) & GPIOCFG_MODE_MASK) == GPIOCFG_MODE_ALT) { \ - GPIO_AF_set(p, i, (cfg) & GPIOCFG_AF_MASK); \ - (p)->MODER = ((p)->MODER & ~(3 << (2*(i)))) | ((((cfg) >> GPIOCFG_MODE_SHIFT ) & 3) << (2*(i))); \ + GPIO_AF_set(p, i, (cfg) & GPIOCFG_AF_MASK); \ + (p)->MODER = ((p)->MODER & ~(3 << (2*(i)))) | ((((cfg) >> GPIOCFG_MODE_SHIFT ) & 3) << (2*(i))); \ } \ (p)->OSPEEDR = ((p)->OSPEEDR & ~(3 << (2*(i)))) | ((((cfg) >> GPIOCFG_OSPEED_SHIFT) & 3) << (2*(i))); \ (p)->OTYPER = ((p)->OTYPER & ~(1 << (1*(i)))) | ((((cfg) >> GPIOCFG_OTYPE_SHIFT ) & 1) << (1*(i))); \ (p)->PUPDR = ((p)->PUPDR & ~(3 << (2*(i)))) | ((((cfg) >> GPIOCFG_PUPD_SHIFT ) & 3) << (2*(i))); \ if (((cfg) & GPIOCFG_MODE_MASK) != GPIOCFG_MODE_ALT) { \ - (p)->MODER = ((p)->MODER & ~(3 << (2*(i)))) | ((((cfg) >> GPIOCFG_MODE_SHIFT ) & 3) << (2*(i))); \ + (p)->MODER = ((p)->MODER & ~(3 << (2*(i)))) | ((((cfg) >> GPIOCFG_MODE_SHIFT ) & 3) << (2*(i))); \ } \ } while (0) -#define HW_SET_PIN(p,i,state) do { \ +#define HW_SET_PIN(p,i,state) do { \ (p)->BSRR |= (1 << (i + ((state) ? 0 : 16))); \ } while (0) -#define HW_GET_PIN(p,i) ((p)->IDR & (1 << (i))) +#define HW_GET_PIN(p,i) ((p)->IDR & (1 << (i))) // Theses macros manipulate the GPIO registers directly, without generating a function call -#define SET_PIN_DIRECT(gpio, val) HW_SET_PIN(GPIOx(BRD_PORT(gpio)), BRD_PIN(gpio), (val)) -#define GET_PIN_DIRECT(gpio) HW_GET_PIN(GPIOx(BRD_PORT(gpio)), BRD_PIN(gpio)) -#define CFG_PIN_DIRECT(gpio, opts) HW_CFG_PIN(GPIOx(BRD_PORT(gpio)), BRD_PIN(gpio), (opts)) +#define SET_PIN_DIRECT(gpio, val) HW_SET_PIN(GPIOx(BRD_PORT(gpio)), BRD_PIN(gpio), (val)) +#define GET_PIN_DIRECT(gpio) HW_GET_PIN(GPIOx(BRD_PORT(gpio)), BRD_PIN(gpio)) +#define CFG_PIN_DIRECT(gpio, opts) HW_CFG_PIN(GPIOx(BRD_PORT(gpio)), BRD_PIN(gpio), (opts)) // Determine RCC enable bit for GPIO port #if defined(STM32L0) -#define GPIO_RCC_ENR (RCC->IOPENR) -#define GPIO_EN(gpio) (((gpio) == PORT_A) ? RCC_IOPENR_GPIOAEN \ - : ((gpio) == PORT_B) ? RCC_IOPENR_GPIOBEN \ - : ((gpio) == PORT_C) ? RCC_IOPENR_GPIOCEN \ - : 0) +#define GPIO_RCC_ENR (RCC->IOPENR) +#define GPIO_EN(gpio) (((gpio) == PORT_A) ? RCC_IOPENR_GPIOAEN \ + : ((gpio) == PORT_B) ? RCC_IOPENR_GPIOBEN \ + : ((gpio) == PORT_C) ? RCC_IOPENR_GPIOCEN \ + : 0) #elif defined(STM32L1) -#define GPIO_RCC_ENR (RCC->AHBENR) -#define GPIO_EN(gpio) (((gpio) == PORT_A) ? RCC_AHBENR_GPIOAEN \ - : ((gpio) == PORT_B) ? RCC_AHBENR_GPIOBEN \ - : ((gpio) == PORT_C) ? RCC_AHBENR_GPIOCEN \ - : 0) +#define GPIO_RCC_ENR (RCC->AHBENR) +#define GPIO_EN(gpio) (((gpio) == PORT_A) ? RCC_AHBENR_GPIOAEN \ + : ((gpio) == PORT_B) ? RCC_AHBENR_GPIOBEN \ + : ((gpio) == PORT_C) ? RCC_AHBENR_GPIOCEN \ + : 0) #endif typedef struct { @@ -163,13 +163,13 @@ typedef struct { #define UNIQUE_ID2 (UNIQUE_ID_BASE+0x14) // EEPROM (these macros are addresses, type uint32_t) -#define EEPROM_BASE DATA_EEPROM_BASE +#define EEPROM_BASE DATA_EEPROM_BASE #ifdef DATA_EEPROM_BANK2_END -#define EEPROM_END (DATA_EEPROM_BANK2_END + 1) +#define EEPROM_END (DATA_EEPROM_BANK2_END + 1) #else -#define EEPROM_END DATA_EEPROM_END +#define EEPROM_END DATA_EEPROM_END #endif -#define EEPROM_SZ (EEPROM_END - EEPROM_BASE) +#define EEPROM_SZ (EEPROM_END - EEPROM_BASE) // EEPROM layout for STM32 @@ -288,8 +288,8 @@ u1_t spi_xfer (u1_t out); #define PERIPH_FLASH -#define FLASH_PAGE_SZ 128 -#define FLASH_PAGE_NW (FLASH_PAGE_SZ >> 2) +#define FLASH_PAGE_SZ 128 +#define FLASH_PAGE_NW (FLASH_PAGE_SZ >> 2) #ifdef FLASH_END #undef FLASH_END // already defined by some STM32L0XXXxx header files diff --git a/stm32/i2c.c b/stm32/i2c.c index 077a3df..2b7319c 100644 --- a/stm32/i2c.c +++ b/stm32/i2c.c @@ -8,10 +8,10 @@ #ifdef BRD_I2C #if BRD_I2C == 1 -#define I2Cx I2C1 -#define I2Cx_enable() do { RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; } while (0) -#define I2Cx_disable() do { RCC->APB1ENR &= ~RCC_APB1ENR_I2C1EN; } while (0) -#define I2Cx_IRQn I2C1_IRQn +#define I2Cx I2C1 +#define I2Cx_enable() do { RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; } while (0) +#define I2Cx_disable() do { RCC->APB1ENR &= ~RCC_APB1ENR_I2C1EN; } while (0) +#define I2Cx_IRQn I2C1_IRQn #else #error "Unsupported I2C peripheral" #endif @@ -47,9 +47,9 @@ static void i2c_stop (int status) { // schedule callback *(xfr.pstatus) = status; if (xfr.job != NULL) { - os_setCallback(xfr.job, xfr.cb); + os_setCallback(xfr.job, xfr.cb); } else { - xfr.cb(NULL); + xfr.cb(NULL); } // re-enable sleep hal_clearMaxSleep(HAL_SLEEP_S0); @@ -75,47 +75,47 @@ static void i2c_start (int addr) { static void i2c_cont (void) { if (xfr.wlen) { - // calculate length; TODO: handle >255 - int n = xfr.wlen & 0xff; - xfr.wlen -= n; - // set direction & number of bytes - I2Cx->CR2 = (I2Cx->CR2 & ~(I2C_CR2_RD_WRN | I2C_CR2_NBYTES)) | (n << 16); - // enable interrupts - I2Cx->CR1 = (I2Cx->CR1 & ~0xfe) | I2C_CR1_TXIE | I2C_CR1_TCIE | I2C_CR1_NACKIE | I2C_CR1_ERRIE; - // start TX - I2Cx->CR2 |= I2C_CR2_START; + // calculate length; TODO: handle >255 + int n = xfr.wlen & 0xff; + xfr.wlen -= n; + // set direction & number of bytes + I2Cx->CR2 = (I2Cx->CR2 & ~(I2C_CR2_RD_WRN | I2C_CR2_NBYTES)) | (n << 16); + // enable interrupts + I2Cx->CR1 = (I2Cx->CR1 & ~0xfe) | I2C_CR1_TXIE | I2C_CR1_TCIE | I2C_CR1_NACKIE | I2C_CR1_ERRIE; + // start TX + I2Cx->CR2 |= I2C_CR2_START; } else if (xfr.rlen) { - // calculate length; TODO: handle >255 - int n = xfr.rlen & 0xff; - xfr.rlen -= n; - // set direction & number of bytes - I2Cx->CR2 = (I2Cx->CR2 & ~(I2C_CR2_RD_WRN | I2C_CR2_NBYTES)) | I2C_CR2_RD_WRN | (n << 16); - // enable interrupts - I2Cx->CR1 = (I2Cx->CR1 & ~0xfe) | I2C_CR1_RXIE | I2C_CR1_TCIE | I2C_CR1_NACKIE | I2C_CR1_ERRIE; - // start RX - I2Cx->CR2 |= I2C_CR2_START; + // calculate length; TODO: handle >255 + int n = xfr.rlen & 0xff; + xfr.rlen -= n; + // set direction & number of bytes + I2Cx->CR2 = (I2Cx->CR2 & ~(I2C_CR2_RD_WRN | I2C_CR2_NBYTES)) | I2C_CR2_RD_WRN | (n << 16); + // enable interrupts + I2Cx->CR1 = (I2Cx->CR1 & ~0xfe) | I2C_CR1_RXIE | I2C_CR1_TCIE | I2C_CR1_NACKIE | I2C_CR1_ERRIE; + // start RX + I2Cx->CR2 |= I2C_CR2_START; } else { - // done - i2c_stop(I2C_OK); + // done + i2c_stop(I2C_OK); } } void i2c_irq (void) { unsigned int isr = I2Cx->ISR; if (isr & I2C_ISR_NACKF) { - // NACK detected, transfer failed! - i2c_stop(I2C_NAK); + // NACK detected, transfer failed! + i2c_stop(I2C_NAK); } else if (isr & I2C_ISR_TC) { - // transfer complete, move on - i2c_cont(); + // transfer complete, move on + i2c_cont(); } else if (isr & I2C_ISR_TXIS) { - // write next byte - I2Cx->TXDR = *xfr.wptr++; + // write next byte + I2Cx->TXDR = *xfr.wptr++; } else if (isr & I2C_ISR_RXNE) { - // next byte received - *xfr.rptr++ = I2Cx->RXDR; + // next byte received + *xfr.rptr++ = I2Cx->RXDR; } else { - hal_failed(); // XXX + hal_failed(); // XXX } } @@ -124,7 +124,7 @@ static void i2c_timeout (osjob_t* job) { } void i2c_xfer_ex (unsigned int addr, unsigned char* buf, unsigned int wlen, unsigned int rlen, ostime_t timeout, - osjob_t* job, osjobcb_t cb, int* pstatus) { + osjob_t* job, osjobcb_t cb, int* pstatus) { // setup xfr structure xfr.wlen = wlen; xfr.rlen = rlen; @@ -135,7 +135,7 @@ void i2c_xfer_ex (unsigned int addr, unsigned char* buf, unsigned int wlen, unsi *xfr.pstatus = I2C_BUSY; // set timeout if (timeout) { - os_setTimedCallback(job, os_getTime() + timeout, i2c_timeout); + os_setTimedCallback(job, os_getTime() + timeout, i2c_timeout); } // prepare peripheral i2c_start(addr); diff --git a/stm32/leds.c b/stm32/leds.c index 2c9b43e..b3f8e6d 100644 --- a/stm32/leds.c +++ b/stm32/leds.c @@ -8,10 +8,10 @@ #if defined(BRD_LED_TIM) #if BRD_LED_TIM == 2 -#define TIMx TIM2 -#define TIMx_enable() do { RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; } while (0) -#define TIMx_disable() do { RCC->APB1ENR &= ~RCC_APB1ENR_TIM2EN; } while (0) -#define TIMx_IRQn TIM2_IRQn +#define TIMx TIM2 +#define TIMx_enable() do { RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; } while (0) +#define TIMx_disable() do { RCC->APB1ENR &= ~RCC_APB1ENR_TIM2EN; } while (0) +#define TIMx_IRQn TIM2_IRQn #else #error "Unsupported timer" #endif @@ -19,11 +19,11 @@ static struct { unsigned int state; struct { - int step; - unsigned int n; - unsigned int delay; - unsigned int min; - unsigned int max; + int step; + unsigned int n; + unsigned int delay; + unsigned int min; + unsigned int max; } pulse[4]; } pwm; @@ -35,11 +35,11 @@ void leds_init (void) { TIMx->PSC = 4; TIMx->ARR = 0xffff; TIMx->CCMR1 = - TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE | - TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE; + TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE | + TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE; TIMx->CCMR2 = - TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE | - TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4PE; + TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE | + TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4PE; TIMx_disable(); #endif } @@ -53,56 +53,56 @@ static void pwm_set_gpio (unsigned int gpio, bool enable, bool pulse, unsigned i unsigned int state1 = state0; if (enable) { - state1 |= (0x01 << ch); - if (pulse) { - state1 |= (0x10 << ch); - } + state1 |= (0x01 << ch); + if (pulse) { + state1 |= (0x10 << ch); + } } else { - state1 &= ~(0x11 << ch); + state1 &= ~(0x11 << ch); } if (state0 == state1) { - return; + return; } hal_disableIRQs(); if (state1) { - if (state0 == 0) { - TIMx_enable(); // enable peripheral clock + if (state0 == 0) { + TIMx_enable(); // enable peripheral clock hal_setMaxSleep(HAL_SLEEP_S0); // disable sleep (keep clock at full speed) - TIMx->CR1 |= TIM_CR1_CEN; // enable timer peripheral - TIMx->EGR |= TIM_EGR_UG; // start pwm - } - if (state1 & 0xf0) { - if ((state0 & 0xf0) == 0) { - TIMx->DIER |= TIM_DIER_UIE; // enable update interrupt - NVIC_EnableIRQ(TIMx_IRQn); // enable interrupt in NVIC - } - } else { - if ((state0 & 0xf0) == 0) { - TIMx->DIER &= ~TIM_DIER_UIE; // disable update interrupt - NVIC_DisableIRQ(TIMx_IRQn); // disable interrupt in NVIC - } - } + TIMx->CR1 |= TIM_CR1_CEN; // enable timer peripheral + TIMx->EGR |= TIM_EGR_UG; // start pwm + } + if (state1 & 0xf0) { + if ((state0 & 0xf0) == 0) { + TIMx->DIER |= TIM_DIER_UIE; // enable update interrupt + NVIC_EnableIRQ(TIMx_IRQn); // enable interrupt in NVIC + } + } else { + if ((state0 & 0xf0) == 0) { + TIMx->DIER &= ~TIM_DIER_UIE; // disable update interrupt + NVIC_DisableIRQ(TIMx_IRQn); // disable interrupt in NVIC + } + } } else if (state0) { - TIMx->CR1 &= ~TIM_CR1_CEN; // disable timer - TIMx->DIER &= ~TIM_DIER_UIE; // disable update interrupt - TIMx_disable(); // disable peripheral clock + TIMx->CR1 &= ~TIM_CR1_CEN; // disable timer + TIMx->DIER &= ~TIM_DIER_UIE; // disable update interrupt + TIMx_disable(); // disable peripheral clock hal_clearMaxSleep(HAL_SLEEP_S0); // re-enable sleep - NVIC_DisableIRQ(TIMx_IRQn); // disable interrupt in NVIC + NVIC_DisableIRQ(TIMx_IRQn); // disable interrupt in NVIC } if (enable) { - *((&(TIMx->CCR1)) + ch) = ccr; // set initial CCR value - if ((state0 & (1 << ch)) == 0) { - unsigned int ccer = TIM_CCER_CC1E; - if (gpio & BRD_GPIO_ACTIVE_LOW) { - ccer |= TIM_CCER_CC1P; - } - TIMx->CCER |= (ccer << (4 * ch)); // enable channel - CFG_PIN_AF(gpio, GPIOCFG_OSPEED_40MHz | GPIOCFG_OTYPE_PUPD | GPIOCFG_PUPD_NONE); - } + *((&(TIMx->CCR1)) + ch) = ccr; // set initial CCR value + if ((state0 & (1 << ch)) == 0) { + unsigned int ccer = TIM_CCER_CC1E; + if (gpio & BRD_GPIO_ACTIVE_LOW) { + ccer |= TIM_CCER_CC1P; + } + TIMx->CCER |= (ccer << (4 * ch)); // enable channel + CFG_PIN_AF(gpio, GPIOCFG_OSPEED_40MHz | GPIOCFG_OTYPE_PUPD | GPIOCFG_PUPD_NONE); + } } pwm.state = state1; @@ -131,14 +131,14 @@ void leds_pulse (unsigned int gpio, unsigned int min, unsigned int max, int step void leds_set (unsigned int gpio, int state) { if (state) { - if (gpio & BRD_GPIO_ACTIVE_LOW) { - SET_PIN(gpio, 0); - } else { - SET_PIN(gpio, 1); - } - CFG_PIN(gpio, GPIOCFG_MODE_OUT | GPIOCFG_OSPEED_400kHz | GPIOCFG_OTYPE_PUPD | GPIOCFG_PUPD_NONE); + if (gpio & BRD_GPIO_ACTIVE_LOW) { + SET_PIN(gpio, 0); + } else { + SET_PIN(gpio, 1); + } + CFG_PIN(gpio, GPIOCFG_MODE_OUT | GPIOCFG_OSPEED_400kHz | GPIOCFG_OTYPE_PUPD | GPIOCFG_PUPD_NONE); } else { - CFG_PIN(gpio, GPIOCFG_MODE_ANA | GPIOCFG_OSPEED_400kHz | GPIOCFG_OTYPE_OPEN | GPIOCFG_PUPD_NONE); + CFG_PIN(gpio, GPIOCFG_MODE_ANA | GPIOCFG_OSPEED_400kHz | GPIOCFG_OTYPE_OPEN | GPIOCFG_PUPD_NONE); } #if defined(BRD_LED_TIM) pwm_set_gpio(gpio, false, false, 0); @@ -148,29 +148,29 @@ void leds_set (unsigned int gpio, int state) { #if defined(BRD_LED_TIM) void leds_pwm_irq (void) { if (TIMx->SR & TIM_SR_UIF) { // update event - TIMx->SR = ~TIM_SR_UIF; // clear flag - unsigned int ps = pwm.state & 0x0f; - while (ps) { - unsigned int ch = __builtin_ctz(ps); - if (pwm.pulse[ch].step) { - if (pwm.pulse[ch].n < pwm.pulse[ch].delay) { - pwm.pulse[ch].n += 1; - } else { - pwm.pulse[ch].n = 0; - int ccr = *((&(TIMx->CCR1)) + ch); - ccr += pwm.pulse[ch].step; - if (ccr <= pwm.pulse[ch].min) { - ccr = pwm.pulse[ch].min; - pwm.pulse[ch].step = -pwm.pulse[ch].step; - } else if (ccr >= pwm.pulse[ch].max) { - ccr = pwm.pulse[ch].max; - pwm.pulse[ch].step = -pwm.pulse[ch].step; - } - *((&(TIMx->CCR1)) + ch) = ccr; - } - } - ps &= ~(1 << ch); - } + TIMx->SR = ~TIM_SR_UIF; // clear flag + unsigned int ps = pwm.state & 0x0f; + while (ps) { + unsigned int ch = __builtin_ctz(ps); + if (pwm.pulse[ch].step) { + if (pwm.pulse[ch].n < pwm.pulse[ch].delay) { + pwm.pulse[ch].n += 1; + } else { + pwm.pulse[ch].n = 0; + int ccr = *((&(TIMx->CCR1)) + ch); + ccr += pwm.pulse[ch].step; + if (ccr <= pwm.pulse[ch].min) { + ccr = pwm.pulse[ch].min; + pwm.pulse[ch].step = -pwm.pulse[ch].step; + } else if (ccr >= pwm.pulse[ch].max) { + ccr = pwm.pulse[ch].max; + pwm.pulse[ch].step = -pwm.pulse[ch].step; + } + *((&(TIMx->CCR1)) + ch) = ccr; + } + } + ps &= ~(1 << ch); + } } } #endif diff --git a/stm32/startup.c b/stm32/startup.c index f54a51c..49b777f 100644 --- a/stm32/startup.c +++ b/stm32/startup.c @@ -25,20 +25,20 @@ void _start (boot_boottab* boottab) { uint32_t* src = &_sidata; uint32_t* dst = &_sdata; while( dst < &_edata ) { - *dst++ = *src++; + *dst++ = *src++; } // initialize bss dst = &_sbss; while( dst < &_ebss ) { - *dst++ = 0; + *dst++ = 0; } // copy current Cortex M IRQ + NVIC vector to RAM src = (uint32_t*) 0; dst = irqvector; for( int i = 0; i < (16 + MAX_IRQn); i++ ) { - *dst++ = *src++; + *dst++ = *src++; } // fix-up NVIC vector with handlers from firmware for( const irqdef* id = HAL_irqdefs; id->handler; id++ ) { diff --git a/target/arduino/basicmac.h b/target/arduino/basicmac.h new file mode 100644 index 0000000..9dbf0dc --- /dev/null +++ b/target/arduino/basicmac.h @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * 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 holder nor the names of its 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 SEMTECH 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. + *******************************************************************************/ + +#ifdef __cplusplus +extern "C"{ +#endif + +#include "lmic/lmic.h" + +#ifdef __cplusplus +} +#endif diff --git a/target/arduino/board.h b/target/arduino/board.h new file mode 100644 index 0000000..2a7505e --- /dev/null +++ b/target/arduino/board.h @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * 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 holder nor the names of its 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 SEMTECH 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. + *******************************************************************************/ + +#include "../hal/target-config.h" diff --git a/target/arduino/examples-common-files/standard-pinmaps.ino b/target/arduino/examples-common-files/standard-pinmaps.ino new file mode 100644 index 0000000..5fa2e87 --- /dev/null +++ b/target/arduino/examples-common-files/standard-pinmaps.ino @@ -0,0 +1,151 @@ +// This file defines pinmaps, which tell basicmac what pins to use to +// talk to the radio. +// +// Below, a lot of different pinmaps for various standard boards are +// defined. These are used when USE_STANDARD_PINMAP is defined, +// otherwise the main sketch file should define its own pinmap. +// +// These pinmaps live in their own file, to make it easier to share them +// between example sketches. + +#if defined(USE_STANDARD_PINMAP) + +#if defined(BASICMAC_DUMMY_PINMAP) +// Dummy minimal pinmap, just used for CI compile-testing. +const lmic_pinmap lmic_pins = { + .nss = 10, + // RX/TX is controlled through RXTX by the SX1272 directly on the RFM95W + .tx = LMIC_UNUSED_PIN, + .rx = LMIC_UNUSED_PIN, + // RST is hardwired to MCU reset + .rst = LMIC_UNUSED_PIN, + .dio = {1, 2, 3}, + .busy = LMIC_UNUSED_PIN, + .tcxo = LMIC_UNUSED_PIN, +}; +#elif defined(ARDUINO_AVR_MINI) +#if !defined(BRD_sx1276_radio) +#error "Wrong radio defined for this board (fix in BasicMAC target-config.h)" +#endif +// Assume this is a Nexus board +// https://github.com/Ideetron/RFM95W_Nexus/blob/master/RFM95W_NEXUS_02.pdf +// Note: BasicMAC is currently too big to fit into the 328p on this board. +const lmic_pinmap lmic_pins = { + .nss = 10, + // RX/TX is controlled through RXTX by the SX1272 directly on the RFM95W + .tx = LMIC_UNUSED_PIN, + .rx = LMIC_UNUSED_PIN, + // RST is hardwarid to MCU reset + .rst = LMIC_UNUSED_PIN, + .dio = {4, 5, 7}, + .busy = LMIC_UNUSED_PIN, + .tcxo = LMIC_UNUSED_PIN, +}; +#elif defined(ARDUINO_MJS_V1) +#if !defined(BRD_sx1276_radio) +#error "Wrong radio defined for this board (fix in BasicMAC target-config.h)" +#endif +// https://github.com/meetjestad/mjs_pcb +// Note: BasicMAC is currently too big to fit into the 328p on this board. +const lmic_pinmap lmic_pins = { + .nss = 10, + // RX/TX is controlled through RXTX by the SX1272 directly on the RFM95W + .tx = LMIC_UNUSED_PIN, + .rx = LMIC_UNUSED_PIN, + .rst = 9, + .dio = {2, 3, 4}, + .busy = LMIC_UNUSED_PIN, + .tcxo = LMIC_UNUSED_PIN, +}; +#elif defined(ADAFRUIT_FEATHER_M0) +#if !defined(BRD_sx1276_radio) +#error "Wrong radio defined for this board (fix in BasicMAC target-config.h)" +#endif +// Assume this a Feather M0 LoRa +// https://learn.adafruit.com/adafruit-feather-m0-radio-with-lora-radio-module/pinouts +const lmic_pinmap lmic_pins = { + .nss = 8, + // RX/TX is controlled through RXTX by the SX1272 directly on the RFM95W + .tx = LMIC_UNUSED_PIN, + .rx = LMIC_UNUSED_PIN, + .rst = 4, + // This needs additional external connections: DIO1 (labeled IO1 in + // docs and board) to 5 and DIO2 (labeled D2 on the board and IO1 in + // the docs) to 6. DIO0 is internally connected. + .dio = {3, 5, 6}, + .busy = LMIC_UNUSED_PIN, + .tcxo = LMIC_UNUSED_PIN, +}; +#elif defined(ARDUINO_STM32L4_LS200) +#if !defined(BRD_sx1262_radio) +#error "Wrong radio defined for this board (fix in BasicMAC target-config.h)" +#endif +// LacunaSpace LS200 development board +// Uses SPI bus at PC10/11/12 +// This uses E22_* constants from the board variant file +const lmic_pinmap lmic_pins = { + .nss = E22_NSS, // PD2 + // DIO2 connected to TXEN on LS200 board + .tx = LMIC_CONTROLLED_BY_DIO2, + .rx = E22_RXEN, // PC4 + .rst = E22_NRST, // PA4 + .dio = {LMIC_UNUSED_PIN, E22_DIO1 /* PC7 */, LMIC_UNUSED_PIN}, + .busy = E22_BUSY, // PB12 + // DIO3 connected to TCXO on E22 board + .tcxo = LMIC_CONTROLLED_BY_DIO3, +}; +#elif defined(ARDUINO_TTGO_LoRa32_V1) +#if !defined(BRD_sx1276_radio) +#error "Wrong radio defined for this board (fix in BasicMAC target-config.h)" +#endif +// https://github.com/LilyGO/TTGO-LORA32/tree/LilyGO-868-V1.0 +// Pinout: https://github.com/LilyGO/TTGO-LORA32/blob/LilyGO-868-V1.0/images/image1.jpg +const lmic_pinmap lmic_pins = { + .nss = 18, + // RX/TX is controlled through RXTX by the SX1272 directly on the RFM95W + .tx = LMIC_UNUSED_PIN, + .rx = LMIC_UNUSED_PIN, + .rst = 14, + .dio = {26, 33, 32}, + .busy = LMIC_UNUSED_PIN, + .tcxo = LMIC_UNUSED_PIN, +}; +#elif defined(ARDUINO_TBeam) +// TTGO T-Beam development board +// https://github.com/LilyGO/TTGO-T-Beam +// This board is available with an SX1276 or SX1262 soldered on. The board is +// otherwise identical and does not have a separate board entry in the +// Arduino IDE, so decide based on the radio constant which one to use. +// +// Uses LoRa SPI bus at 5/19/27 +// This uses LORA_* constants from the board variant file +#if defined(BRD_sx1276_radio) +const lmic_pinmap lmic_pins = { + .nss = LORA_CS, // 18 + // RX/TX is controlled through RXTX by the SX1276/8 directly on the HPD13/4A + .tx = LMIC_UNUSED_PIN, + .rx = LMIC_UNUSED_PIN, + .rst = LORA_RST, // 23 + .dio = {LORA_IO0 /* 26 */ , LORA_IO1 /* 33 */, LORA_IO2 /* 32 */}, + .busy = LMIC_UNUSED_PIN, + .tcxo = LMIC_UNUSED_PIN, +}; +#elif defined(BRD_sx1262_radio) +const lmic_pinmap lmic_pins = { + .nss = LORA_CS, // 18 + // TXEN is controlled through DIO2 by the SX1262 (HPD16A) directly + .tx = LMIC_CONTROLLED_BY_DIO2, + .rx = LMIC_UNUSED_PIN, + .rst = LORA_RST, // 23 + .dio = {LMIC_UNUSED_PIN, LORA_IO1 /* 33 */, LMIC_UNUSED_PIN}, + .busy = LORA_IO2, // 32 + // TCXO is controlled through DIO3 by the SX1262 directly + .tcxo = LMIC_CONTROLLED_BY_DIO3, +}; +#else +#error "Wrong radio defined for this board (fix in BasicMAC target-config.h)" +#endif +#else +#error "Unknown board, no standard pimap available. Define your own in the main sketch file." +#endif +#endif // defined(USE_STANDARD_PINMAP) diff --git a/target/arduino/examples/basicmac-abp/basicmac-abp.ino b/target/arduino/examples/basicmac-abp/basicmac-abp.ino new file mode 100644 index 0000000..1d50e52 --- /dev/null +++ b/target/arduino/examples/basicmac-abp/basicmac-abp.ino @@ -0,0 +1,313 @@ +/******************************************************************************* + * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example sends a valid LoRaWAN packet with payload "Hello, + * world!", using frequency and encryption settings matching those of + * the The Things Network. + * + * This uses ABP (Activation-by-personalisation), where a DevAddr and + * Session keys are preconfigured (unlike OTAA, where a DevEUI and + * application key is configured, while the DevAddr and session keys are + * assigned/generated in the over-the-air-activation procedure). + * + * Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in + * g1, 0.1% in g2), but not the TTN fair usage policy (which is probably + * violated by this sketch when left running for longer)! + * + * To use this sketch, first register your application and device with + * the things network, to set or generate a DevAddr, NwkSKey and + * AppSKey. Each device should have their own unique values for these + * fields. + * + * Do not forget to define the radio type correctly in config.h. + * + *******************************************************************************/ + +#include +#include +#include + +// LoRaWAN NwkSKey, network session key +// This is the default Semtech key, which is used by the early prototype TTN +// network. +static const PROGMEM u1_t NWKSKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; + +// LoRaWAN AppSKey, application session key +// This is the default Semtech key, which is used by the early prototype TTN +// network. +static const u1_t PROGMEM APPSKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; + +// LoRaWAN end-device address (DevAddr) +static const u4_t DEVADDR = 0x03FF0001 ; // <-- Change this address for every node! + +// These callbacks are only used in over-the-air activation, so they are +// left empty here (we cannot leave them out completely unless +// DISABLE_JOIN is set in config.h, otherwise the linker will complain). +void os_getJoinEui (u1_t* /* buf */) { } +void os_getDevEui (u1_t* /* buf */) { } +void os_getNwkKey (u1_t* /* buf */) { } + +// The region to use, this just uses the first one (can be changed if +// multiple regions are enabled). +u1_t os_getRegion (void) { return LMIC_regionCode(0); } + +// Schedule TX every this many milliseconds (might become longer due to duty +// cycle limitations). +const unsigned TX_INTERVAL = 60000; + +// When this is defined, a standard pinmap from standard-pinmaps.ino +// will be used. If you need to use a custom pinmap, comment this line +// and enter the pin numbers in the lmic_pins variable below. +#define USE_STANDARD_PINMAP + +#if !defined(USE_STANDARD_PINMAP) +// All pin assignments use Arduino pin numbers (e.g. what you would pass +// to digitalWrite), or LMIC_UNUSED_PIN when a pin is not connected. +const lmic_pinmap lmic_pins = { + // NSS input pin for SPI communication (required) + .nss = 0, + // If needed, these pins control the RX/TX antenna switch (active + // high outputs). When you have both, the antenna switch can + // powerdown when unused. If you just have a RXTX pin it should + // usually be assigned to .tx, reverting to RX mode when idle). + // + // The SX127x has an RXTX pin that can automatically control the + // antenna switch (if internally connected on the transceiver + // board). This pin is always active, so no configuration is needed + // for that here. + // On SX126x, the DIO2 can be used for the same thing, but this is + // disabled by default. To enable this, set .tx to + // LMIC_CONTROLLED_BY_DIO2 below (this seems to be common and + // enabling it when not needed is probably harmless, unless DIO2 is + // connected to GND or VCC directly inside the transceiver board). + .tx = LMIC_UNUSED_PIN, + .rx = LMIC_UNUSED_PIN, + // Radio reset output pin (active high for SX1276, active low for + // others). When omitted, reset is skipped which might cause problems. + .rst = 1, + // DIO input pins. + // For SX127x, LoRa needs DIO0 and DIO1, FSK needs DIO0, DIO1 and DIO2 + // For SX126x, Only DIO1 is needed (so leave DIO0 and DIO2 as LMIC_UNUSED_PIN) + .dio = {/* DIO0 */ 2, /* DIO1 */ 3, /* DIO2 */ 4}, + // Busy input pin (SX126x only). When omitted, a delay is used which might + // cause problems. + .busy = LMIC_UNUSED_PIN, + // TCXO oscillator enable output pin (active high). + // + // For SX127x this should be an I/O pin that controls the TCXO, or + // LMIC_UNUSED_PIN when a crystal is used instead of a TCXO. + // + // For SX126x this should be LMIC_CONTROLLED_BY_DIO3 when a TCXO is + // directly connected to the transceiver DIO3 to let the transceiver + // start and stop the TCXO, or LMIC_UNUSED_PIN when a crystal is + // used instead of a TCXO. Controlling the TCXO from the MCU is not + // supported. + .tcxo = LMIC_UNUSED_PIN, +}; +#endif // !defined(USE_STANDARD_PINMAP) + +void onLmicEvent (ev_t ev) { + Serial.print(os_getTime()); + Serial.print(": "); + switch(ev) { + case EV_SCAN_TIMEOUT: + Serial.println(F("EV_SCAN_TIMEOUT")); + break; + case EV_BEACON_FOUND: + Serial.println(F("EV_BEACON_FOUND")); + break; + case EV_BEACON_MISSED: + Serial.println(F("EV_BEACON_MISSED")); + break; + case EV_BEACON_TRACKED: + Serial.println(F("EV_BEACON_TRACKED")); + break; + case EV_JOINING: + Serial.println(F("EV_JOINING")); + break; + case EV_JOINED: + Serial.println(F("EV_JOINED")); + break; + case EV_RFU1: + Serial.println(F("EV_RFU1")); + break; + case EV_JOIN_FAILED: + Serial.println(F("EV_JOIN_FAILED")); + break; + case EV_REJOIN_FAILED: + Serial.println(F("EV_REJOIN_FAILED")); + break; + case EV_TXCOMPLETE: + Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); + if (LMIC.txrxFlags & TXRX_ACK) + Serial.println(F("Received ack")); + if (LMIC.dataLen) { + Serial.print(F("Received ")); + Serial.print(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + break; + case EV_LOST_TSYNC: + Serial.println(F("EV_LOST_TSYNC")); + break; + case EV_RESET: + Serial.println(F("EV_RESET")); + break; + case EV_RXCOMPLETE: + // data received in ping slot + Serial.println(F("EV_RXCOMPLETE")); + break; + case EV_LINK_DEAD: + Serial.println(F("EV_LINK_DEAD")); + break; + case EV_LINK_ALIVE: + Serial.println(F("EV_LINK_ALIVE")); + break; + case EV_SCAN_FOUND: + Serial.println(F("EV_SCAN_FOUND")); + break; + case EV_TXSTART: + Serial.println(F("EV_TXSTART")); + break; + case EV_TXDONE: + Serial.println(F("EV_TXDONE")); + break; + case EV_DATARATE: + Serial.println(F("EV_DATARATE")); + break; + case EV_START_SCAN: + Serial.println(F("EV_START_SCAN")); + break; + case EV_ADR_BACKOFF: + Serial.println(F("EV_ADR_BACKOFF")); + break; + default: + Serial.print(F("Unknown event: ")); + Serial.println(ev); + break; + } +} + +void setup() { + Serial.begin(115200); + + // Wait up to 5 seconds for serial to be opened, to allow catching + // startup messages on native USB boards (that do not reset when + // serial is opened). + unsigned long start = millis(); + while (millis() - start < 5000 && !Serial); + + Serial.println(); + Serial.println(); + Serial.println(F("Starting")); + Serial.println(); + + // LMIC init + os_init(nullptr); + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + // Set static session parameters. Instead of dynamically establishing a session + // by joining the network, precomputed session parameters are be provided. + #ifdef PROGMEM + // On AVR, these values are stored in flash and only copied to RAM + // once. Copy them to a temporary buffer here, LMIC_setSession will + // copy them into a buffer of its own again. + uint8_t appskey[sizeof(APPSKEY)]; + uint8_t nwkskey[sizeof(NWKSKEY)]; + memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); + memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); + LMIC_setSession (0x1, DEVADDR, nwkskey, appskey); + #else + // If not running an AVR with PROGMEM, just use the arrays directly + LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY); + #endif + + #if defined(CFG_eu868) + // These are defined by the LoRaWAN specification + enum { + EU_DR_SF12 = 0, + EU_DR_SF11 = 1, + EU_DR_SF10 = 2, + EU_DR_SF9 = 3, + EU_DR_SF8 = 4, + EU_DR_SF7 = 5, + EU_DR_SF7_BW250 = 6, + EU_DR_FSK = 7, + }; + + // Set up the channels used by the Things Network, which corresponds + // to the defaults of most gateways. Without this, only three base + // channels from the LoRaWAN specification are used, which certainly + // works, so it is good for debugging, but can overload those + // frequencies, so be sure to configure the full frequency range of + // your network here (unless your network autoconfigures them). + // Setting up channels should happen after LMIC_setSession, as that + // configures the minimal channel set. + LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(EU_DR_SF12, EU_DR_SF7)); // g-band + LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(EU_DR_SF12, EU_DR_SF7_BW250)); // g-band + LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(EU_DR_SF12, EU_DR_SF7)); // g-band + LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(EU_DR_SF12, EU_DR_SF7)); // g-band + LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(EU_DR_SF12, EU_DR_SF7)); // g-band + LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(EU_DR_SF12, EU_DR_SF7)); // g-band + LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(EU_DR_SF12, EU_DR_SF7)); // g-band + LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(EU_DR_SF12, EU_DR_SF7)); // g-band + LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(EU_DR_FSK, EU_DR_FSK)); // g2-band + + // TTN uses SF9 at 869.525Mhz for its RX2 window (frequency is + // default LoRaWAN, SF is different, but set them both to be + // explicit). + LMIC.dn2Freq = 869525000; + LMIC.dn2Dr = EU_DR_SF9; + + // Set data rate for uplink + LMIC_setDrTxpow(EU_DR_SF7, KEEP_TXPOWADJ); + #elif defined(CFG_us915) + // NA-US channels 0-71 are configured automatically + // but only one group of 8 should (a subband) should be active + // TTN recommends the second sub band, 1 in a zero based count. + // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json + // TODO: How to configure these channels? LMIC had LMIC_selectSubBand, + // but it seems BasicMac only has LMIC_disableChannel. + #endif + + // Disable link check validation + LMIC_setLinkCheckMode(0); + + // Enable this to increase the receive window size, to compensate + // for an inaccurate clock. // This compensate for +/- 10% clock + // error, a lower value will likely be more appropriate. + //LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100); + + // Queue first packet + send_packet(); +} + +uint32_t last_packet = 0; + +void loop() { + // Let LMIC handle background tasks + os_runstep(); + + // If TX_INTERVAL passed, *and* our previous packet is not still + // pending (which can happen due to duty cycle limitations), send + // the next packet. + if (millis() - last_packet > TX_INTERVAL && !(LMIC.opmode & (OP_JOINING|OP_TXRXPEND))) + send_packet(); +} + +void send_packet(){ + // Prepare upstream data transmission at the next possible time. + uint8_t mydata[] = "Hello, world!"; + LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0); + Serial.println(F("Packet queued")); + + last_packet = millis(); +} + diff --git a/target/arduino/examples/basicmac-otaa/basicmac-otaa.ino b/target/arduino/examples/basicmac-otaa/basicmac-otaa.ino new file mode 100644 index 0000000..4d1e69e --- /dev/null +++ b/target/arduino/examples/basicmac-otaa/basicmac-otaa.ino @@ -0,0 +1,257 @@ +/******************************************************************************* + * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example sends a valid LoRaWAN packet with payload "Hello, + * world!", using frequency and encryption settings matching those of + * the The Things Network. + * + * This uses OTAA (Over-the-air activation), where where a DevEUI and + * application key is configured, which are used in an over-the-air + * activation procedure where a DevAddr and session keys are + * assigned/generated for use with all further communication. + * + * Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in + * g1, 0.1% in g2), but not the TTN fair usage policy (which is probably + * violated by this sketch when left running for longer)! + + * To use this sketch, first register your application and device with + * the things network, to set or generate an AppEUI, DevEUI and AppKey. + * Multiple devices can use the same AppEUI, but each device has its own + * DevEUI and AppKey. + * + * Do not forget to define the radio type correctly in config.h. + * + *******************************************************************************/ + +#include +#include +#include + +// This EUI must be in little-endian format, so least-significant-byte +// first. When copying an EUI from ttnctl output, this means to reverse +// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, +// 0x70. +static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +void os_getJoinEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);} + +// This should also be in little endian format, see above. +static const u1_t PROGMEM DEVEUI[8]={ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);} + +// This key should be in big endian format (or, since it is not really a +// number but a block of memory, endianness does not really apply). In +// practice, a key taken from ttnctl can be copied as-is. +// The key shown here is the semtech default key. +static const u1_t PROGMEM APPKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; +void os_getNwkKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);} + +// The region to use, this just uses the first one (can be changed if +// multiple regions are enabled). +u1_t os_getRegion (void) { return LMIC_regionCode(0); } + +// Schedule TX every this many milliseconds (might become longer due to duty +// cycle limitations). +const unsigned TX_INTERVAL = 60000; + +// Timestamp of last packet sent +uint32_t last_packet = 0; + +// When this is defined, a standard pinmap from standard-pinmaps.ino +// will be used. If you need to use a custom pinmap, comment this line +// and enter the pin numbers in the lmic_pins variable below. +#define USE_STANDARD_PINMAP + +#if !defined(USE_STANDARD_PINMAP) +// All pin assignments use Arduino pin numbers (e.g. what you would pass +// to digitalWrite), or LMIC_UNUSED_PIN when a pin is not connected. +const lmic_pinmap lmic_pins = { + // NSS input pin for SPI communication (required) + .nss = 0, + // If needed, these pins control the RX/TX antenna switch (active + // high outputs). When you have both, the antenna switch can + // powerdown when unused. If you just have a RXTX pin it should + // usually be assigned to .tx, reverting to RX mode when idle). + // + // The SX127x has an RXTX pin that can automatically control the + // antenna switch (if internally connected on the transceiver + // board). This pin is always active, so no configuration is needed + // for that here. + // On SX126x, the DIO2 can be used for the same thing, but this is + // disabled by default. To enable this, set .tx to + // LMIC_CONTROLLED_BY_DIO2 below (this seems to be common and + // enabling it when not needed is probably harmless, unless DIO2 is + // connected to GND or VCC directly inside the transceiver board). + .tx = LMIC_UNUSED_PIN, + .rx = LMIC_UNUSED_PIN, + // Radio reset output pin (active high for SX1276, active low for + // others). When omitted, reset is skipped which might cause problems. + .rst = 1, + // DIO input pins. + // For SX127x, LoRa needs DIO0 and DIO1, FSK needs DIO0, DIO1 and DIO2 + // For SX126x, Only DIO1 is needed (so leave DIO0 and DIO2 as LMIC_UNUSED_PIN) + .dio = {/* DIO0 */ 2, /* DIO1 */ 3, /* DIO2 */ 4}, + // Busy input pin (SX126x only). When omitted, a delay is used which might + // cause problems. + .busy = LMIC_UNUSED_PIN, + // TCXO oscillator enable output pin (active high). + // + // For SX127x this should be an I/O pin that controls the TCXO, or + // LMIC_UNUSED_PIN when a crystal is used instead of a TCXO. + // + // For SX126x this should be LMIC_CONTROLLED_BY_DIO3 when a TCXO is + // directly connected to the transceiver DIO3 to let the transceiver + // start and stop the TCXO, or LMIC_UNUSED_PIN when a crystal is + // used instead of a TCXO. Controlling the TCXO from the MCU is not + // supported. + .tcxo = LMIC_UNUSED_PIN, +}; +#endif // !defined(USE_STANDARD_PINMAP) + +void onLmicEvent (ev_t ev) { + Serial.print(os_getTime()); + Serial.print(": "); + switch(ev) { + case EV_SCAN_TIMEOUT: + Serial.println(F("EV_SCAN_TIMEOUT")); + break; + case EV_BEACON_FOUND: + Serial.println(F("EV_BEACON_FOUND")); + break; + case EV_BEACON_MISSED: + Serial.println(F("EV_BEACON_MISSED")); + break; + case EV_BEACON_TRACKED: + Serial.println(F("EV_BEACON_TRACKED")); + break; + case EV_JOINING: + Serial.println(F("EV_JOINING")); + break; + case EV_JOINED: + Serial.println(F("EV_JOINED")); + + // Disable link check validation (automatically enabled + // during join, but not supported by TTN at this time). + LMIC_setLinkCheckMode(0); + break; + case EV_RFU1: + Serial.println(F("EV_RFU1")); + break; + case EV_JOIN_FAILED: + Serial.println(F("EV_JOIN_FAILED")); + break; + case EV_REJOIN_FAILED: + Serial.println(F("EV_REJOIN_FAILED")); + break; + break; + case EV_TXCOMPLETE: + Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); + if (LMIC.txrxFlags & TXRX_ACK) + Serial.println(F("Received ack")); + if (LMIC.dataLen) { + Serial.print(F("Received ")); + Serial.print(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + break; + case EV_LOST_TSYNC: + Serial.println(F("EV_LOST_TSYNC")); + break; + case EV_RESET: + Serial.println(F("EV_RESET")); + break; + case EV_RXCOMPLETE: + // data received in ping slot + Serial.println(F("EV_RXCOMPLETE")); + break; + case EV_LINK_DEAD: + Serial.println(F("EV_LINK_DEAD")); + break; + case EV_LINK_ALIVE: + Serial.println(F("EV_LINK_ALIVE")); + break; + case EV_SCAN_FOUND: + Serial.println(F("EV_SCAN_FOUND")); + break; + case EV_TXSTART: + Serial.println(F("EV_TXSTART")); + break; + case EV_TXDONE: + Serial.println(F("EV_TXDONE")); + break; + case EV_DATARATE: + Serial.println(F("EV_DATARATE")); + break; + case EV_START_SCAN: + Serial.println(F("EV_START_SCAN")); + break; + case EV_ADR_BACKOFF: + Serial.println(F("EV_ADR_BACKOFF")); + break; + + default: + Serial.print(F("Unknown event: ")); + Serial.println(ev); + break; + } +} + +void setup() { + Serial.begin(115200); + + // Wait up to 5 seconds for serial to be opened, to allow catching + // startup messages on native USB boards (that do not reset when + // serial is opened). + unsigned long start = millis(); + while (millis() - start < 5000 && !Serial); + + Serial.println(); + Serial.println(); + Serial.println(F("Starting")); + Serial.println(); + + // LMIC init + os_init(nullptr); + LMIC_reset(); + + // Enable this to increase the receive window size, to compensate + // for an inaccurate clock. // This compensate for +/- 10% clock + // error, a lower value will likely be more appropriate. + //LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100); + + // Start join + LMIC_startJoining(); + + // Make sure the first packet is scheduled ASAP after join completes + last_packet = millis() - TX_INTERVAL; + + // Optionally wait for join to complete (uncomment this is you want + // to run the loop while joining). + while ((LMIC.opmode & (OP_JOINING))) + os_runstep(); +} + +void loop() { + // Let LMIC handle background tasks + os_runstep(); + + // If TX_INTERVAL passed, *and* our previous packet is not still + // pending (which can happen due to duty cycle limitations), send + // the next packet. + if (millis() - last_packet > TX_INTERVAL && !(LMIC.opmode & (OP_JOINING|OP_TXRXPEND))) + send_packet(); +} + +void send_packet(){ + // Prepare upstream data transmission at the next possible time. + uint8_t mydata[] = "Hello, world!"; + LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0); + Serial.println(F("Packet queued")); + + last_packet = millis(); +} diff --git a/target/arduino/export.sh b/target/arduino/export.sh new file mode 100755 index 0000000..bbed1ad --- /dev/null +++ b/target/arduino/export.sh @@ -0,0 +1,106 @@ +#!/bin/sh +# +# Copyright (C) 2016, Matthijs Kooijman +# +# --- Revised 3-Clause BSD License --- +# 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 holder nor the names of its 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 SEMTECH 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. + +# +# Export script for unix operating systems (e.g. Linux and OSX). +# + +SRC=$(cd "$(dirname "$0")"; pwd) + +usage() { + echo "$0 [--link] TARGET_DIR" + echo "Create an Arduino-compatible library in the given directory, overwriting existing files. If --link is given, creates symbolic links for easy testing." +} + + +# This recursively copies $1 (file or directory) into $2 (directory) +# This is essentially cp --symbolic-link, but POSIX-compatible. +create_links() { + local TARGET=$(cd "$2" && pwd) + local SRCDIR=$(cd "$(dirname "$1")" && pwd) + local SRCNAME=$(basename "$1") + (cd "$SRCDIR" && find "$SRCNAME" -type d -exec mkdir -p "$TARGET/{}" \; -o -exec ln -s -v "$SRCDIR/{}" "$TARGET/{}" \; ) +} + + +CMD="cp -r -v" +case "$1" in + --help) + usage + exit 0 + ;; + --link) + CMD="create_links" + shift; + ;; + --*) + echo "Unknown option: $1" 1>&2 + exit 1 + ;; +esac + +TARGET=$1 + +if [ -z "$TARGET" ]; then + usage + exit 1 +fi + +if ! [ -d "$(dirname "$TARGET")" ]; then + echo "Parent of $TARGET should exist" 1>&2 + exit 1 +fi + + +if [ -e "$TARGET" ]; then + echo -n "$TARGET exists, remove before export? [yN]" + read answer + if [ "$answer" = "y" ]; then + rm -rf "$TARGET" + fi +fi + +mkdir -p "$TARGET"/src + +# This copies or links the relevant directories. For the hal and lmic +# directories, the contained files are copied or linked, so that when +# linking relative includes still work as expected +$CMD "$SRC"/library.properties "$TARGET" +$CMD "$SRC"/basicmac.h "$TARGET"/src +$CMD "$SRC"/../../lmic "$TARGET"/src +$CMD "$SRC"/hal "$TARGET"/src +$CMD "$SRC"/../../aes "$TARGET"/src +$CMD "$SRC"/examples "$TARGET" +$CMD "$SRC"/board.h "$TARGET"/src/lmic +$CMD "$SRC"/hw.h "$TARGET"/src/lmic + +# Then copy/link the common files (e.g. pinmap) into each example +# directory +for E in "$TARGET"/examples/*; do + $CMD "$SRC"/examples-common-files/* "$E" +done diff --git a/target/arduino/hal/hal.cpp b/target/arduino/hal/hal.cpp new file mode 100644 index 0000000..b275b7f --- /dev/null +++ b/target/arduino/hal/hal.cpp @@ -0,0 +1,495 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * 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 holder nor the names of its 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 SEMTECH 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. + * + * This the HAL to run LMIC on top of the Arduino environment. + *******************************************************************************/ + +#define _GNU_SOURCE 1 // For fopencookie +// Must be first, otherwise it might have already been included without _GNU_SOURCE +#include +#undef _GNU_SOURCE +// Prevent warning on samd where samd21g18a.h from CMSIS defines this +#undef LITTLE_ENDIAN +#include +#include +#include "../basicmac.h" +#include "hal.h" + +// Datasheet defins typical times until busy goes low. Most are < 200us, +// except when waking up from sleep, which typically takes 3500us. Since +// we cannot know here if we are in sleep, we'll have to assume we are. +// Since 3500 is typical, not maximum, wait a bit more than that. +static unsigned long MAX_BUSY_TIME = 5000; + +// ----------------------------------------------------------------------------- +// I/O + +static void hal_io_init () { + uint8_t i; + // Checks below assume that all special pin values are >= LMIC_UNUSED_PIN, so check that. + #if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + LMIC_STATIC_ASSERT(LMIC_UNUSED_PIN < LMIC_CONTROLLED_BY_DIO3, "Unexpected constant values"); + LMIC_STATIC_ASSERT(LMIC_UNUSED_PIN < LMIC_CONTROLLED_BY_DIO2, "Unexpected constant values"); + #endif // defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + + ASSERT(lmic_pins.nss < LMIC_UNUSED_PIN); + ASSERT(lmic_pins.rst <= LMIC_UNUSED_PIN); + ASSERT(lmic_pins.rx <= LMIC_UNUSED_PIN); + +#if defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) + //DIO0 is required, DIO1 is required for LoRa, DIO2 for FSK + ASSERT(lmic_pins.dio[0] < LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio[1] < LMIC_UNUSED_PIN || lmic_pins.dio[2] < LMIC_UNUSED_PIN); + + ASSERT(lmic_pins.busy == LMIC_UNUSED_PIN); + ASSERT(lmic_pins.tcxo == LMIC_UNUSED_PIN); + ASSERT(lmic_pins.tx <= LMIC_UNUSED_PIN); +#elif defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + // Only DIO1 should be specified + ASSERT(lmic_pins.dio[0] == LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio[1] < LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio[2] == LMIC_UNUSED_PIN); + + ASSERT(lmic_pins.busy <= LMIC_UNUSED_PIN); + ASSERT(lmic_pins.tcxo == LMIC_UNUSED_PIN || lmic_pins.tcxo == LMIC_CONTROLLED_BY_DIO3); + ASSERT(lmic_pins.tx <= LMIC_UNUSED_PIN || lmic_pins.tx == LMIC_CONTROLLED_BY_DIO2); +#else + #error "Unknown radio type?" +#endif + + // Write HIGH to deselect (NSS is active low). Do this before + // setting output, to prevent a moment of OUTPUT LOW on e.g. AVR. + digitalWrite(lmic_pins.nss, HIGH); + pinMode(lmic_pins.nss, OUTPUT); + // Write HIGH again after setting output, for architectures that + // reset to LOW when setting OUTPUT (e.g. arduino-STM32L4). + digitalWrite(lmic_pins.nss, HIGH); + + if (lmic_pins.tx < LMIC_UNUSED_PIN) + pinMode(lmic_pins.tx, OUTPUT); + if (lmic_pins.rx < LMIC_UNUSED_PIN) + pinMode(lmic_pins.rx, OUTPUT); + if (lmic_pins.rst < LMIC_UNUSED_PIN) + pinMode(lmic_pins.rst, OUTPUT); + if (lmic_pins.busy < LMIC_UNUSED_PIN) + pinMode(lmic_pins.busy, INPUT); + if (lmic_pins.tcxo < LMIC_UNUSED_PIN) + pinMode(lmic_pins.tcxo, OUTPUT); + + for (i = 0; i < NUM_DIO; ++i) { + if (lmic_pins.dio[i] < LMIC_UNUSED_PIN) + pinMode(lmic_pins.dio[i], INPUT); + } +} + +// rx = 0, tx = 1, off = -1 +void hal_ant_switch (u1_t val) { + // TODO: Support separate pin for TX2 (PA_BOOST output) + if (lmic_pins.tx < LMIC_UNUSED_PIN) + digitalWrite(lmic_pins.tx, val == HAL_ANTSW_TX || val == HAL_ANTSW_TX2); + if (lmic_pins.rx < LMIC_UNUSED_PIN) + digitalWrite(lmic_pins.rx, val == HAL_ANTSW_RX); +} + +// set radio RST pin to given value (or keep floating!) +bool hal_pin_rst (u1_t val) { + if (lmic_pins.rst >= LMIC_UNUSED_PIN) + return false; + + if(val == 0 || val == 1) { // drive pin + pinMode(lmic_pins.rst, OUTPUT); + digitalWrite(lmic_pins.rst, val); + } else { // keep pin floating + pinMode(lmic_pins.rst, INPUT); + } + return true; +} + +void hal_irqmask_set (int /* mask */) { + // Not implemented +} + +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, hal_ticks()); + } + } +} + +#if defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) +bool hal_pin_tcxo (u1_t val) { + if (lmic_pins.tcxo >= LMIC_UNUSED_PIN) + return false; + + digitalWrite(lmic_pins.tcxo, val); + return true; +} +#endif // defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) + +void hal_pin_busy_wait (void) { + if (lmic_pins.busy >= LMIC_UNUSED_PIN) { + // TODO: We could probably keep some state so we know the chip + // is in sleep, since otherwise the delay can be much shorter. + // Also, all delays after commands (rather than waking up from + // sleep) are measured from the *end* of the previous SPI + // transaction, so we could wait shorter if we remember when + // that was. + delayMicroseconds(MAX_BUSY_TIME); + } else { + unsigned long start = micros(); + + while((micros() - start) < MAX_BUSY_TIME && digitalRead(lmic_pins.busy)) /* wait */; + } +} + +#if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) +bool hal_dio3_controls_tcxo (void) { + return lmic_pins.tcxo == LMIC_CONTROLLED_BY_DIO3; +} +bool hal_dio2_controls_rxtx (void) { + return lmic_pins.tx == LMIC_CONTROLLED_BY_DIO2; +} +#endif // defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + +// ----------------------------------------------------------------------------- +// SPI + +static const SPISettings settings(10E6, MSBFIRST, SPI_MODE0); + +static void hal_spi_init () { + SPI.begin(); +} + +void hal_spi_select (int on) { + if (on) + SPI.beginTransaction(settings); + else + SPI.endTransaction(); + + //Serial.println(val?">>":"<<"); + digitalWrite(lmic_pins.nss, !on); +} + +// perform SPI transaction with radio +u1_t hal_spi (u1_t out) { + u1_t res = SPI.transfer(out); +/* + Serial.print(">"); + Serial.print(out, HEX); + Serial.print("<"); + Serial.println(res, HEX); + */ + return res; +} + +// ----------------------------------------------------------------------------- +// TIME + +static void hal_time_init () { + // Nothing to do +} + +u4_t hal_ticks () { + // Because micros() is scaled down in this function, micros() will + // overflow before the tick timer should, causing the tick timer to + // miss a significant part of its values if not corrected. To fix + // this, the "overflow" serves as an overflow area for the micros() + // counter. It consists of three parts: + // - The US_PER_OSTICK upper bits are effectively an extension for + // the micros() counter and are added to the result of this + // function. + // - The next bit overlaps with the most significant bit of + // micros(). This is used to detect micros() overflows. + // - The remaining bits are always zero. + // + // By comparing the overlapping bit with the corresponding bit in + // the micros() return value, overflows can be detected and the + // upper bits are incremented. This is done using some clever + // bitwise operations, to remove the need for comparisons and a + // jumps, which should result in efficient code. By avoiding shifts + // other than by multiples of 8 as much as possible, this is also + // efficient on AVR (which only has 1-bit shifts). + static uint8_t overflow = 0; + + // Scaled down timestamp. The top US_PER_OSTICK_EXPONENT bits are 0, + // the others will be the lower bits of our return value. + uint32_t scaled = micros() >> US_PER_OSTICK_EXPONENT; + // Most significant byte of scaled + uint8_t msb = scaled >> 24; + // Mask pointing to the overlapping bit in msb and overflow. + const uint8_t mask = (1 << (7 - US_PER_OSTICK_EXPONENT)); + // Update overflow. If the overlapping bit is different + // between overflow and msb, it is added to the stored value, + // so the overlapping bit becomes equal again and, if it changed + // from 1 to 0, the upper bits are incremented. + overflow += (msb ^ overflow) & mask; + + // Return the scaled value with the upper bits of stored added. The + // overlapping bit will be equal and the lower bits will be 0, so + // bitwise or is a no-op for them. + return scaled | ((uint32_t)overflow << 24); + + // 0 leads to correct, but overly complex code (it could just return + // micros() unmodified), 8 leaves no room for the overlapping bit. + static_assert(US_PER_OSTICK_EXPONENT > 0 && US_PER_OSTICK_EXPONENT < 8, "Invalid US_PER_OSTICK_EXPONENT value"); +} + +u8_t hal_xticks (void) { + // TODO + return hal_ticks(); +} +/* Not actually used now +s2_t hal_subticks (void) { + // TODO + return 0; +} +*/ + +// Returns the number of ticks until time. Negative values indicate that +// time has already passed. +static s4_t delta_time(u4_t time) { + return (s4_t)(time - hal_ticks()); +} + +void hal_waitUntil (u4_t time) { + s4_t delta = delta_time(time); + // From delayMicroseconds docs: Currently, the largest value that + // will produce an accurate delay is 16383. + while (delta > (16000 / US_PER_OSTICK)) { + delay(16); + delta -= (16000 / US_PER_OSTICK); + } + if (delta > 0) + delayMicroseconds(delta * US_PER_OSTICK); +} + +// check and rewind for target time +u1_t hal_checkTimer (u4_t time) { + // No need to schedule wakeup, since we're not sleeping + return delta_time(time) <= 0; +} + +static uint8_t irqlevel = 0; + +void hal_disableIRQs () { + noInterrupts(); + irqlevel++; +} + +void hal_enableIRQs () { + if(--irqlevel == 0) { + interrupts(); + + // Instead of using proper interrupts (which are a bit tricky + // and/or not available on all pins on AVR), just poll the pin + // values. Since os_runloop disables and re-enables interrupts, + // putting this here makes sure we check at least once every + // loop. + // + // As an additional bonus, this prevents the can of worms that + // we would otherwise get for running SPI transfers inside ISRs + hal_io_check(); + } +} + +u1_t hal_sleep (u1_t type, u4_t targettime) { + // Actual sleeping not implemented, but jobs are only run when this + // function returns 0, so make sure we only do that when the + // targettime is close. When asked to sleep forever (until woken up + // by an interrupt), just return immediately to keep polling. + if (type == HAL_SLEEP_FOREVER) + return 0; + + // TODO: What value should we use for "close"? + return delta_time(targettime) < 10 ? 0 : 1; +} + +void hal_watchcount (int /* cnt */) { + // Not implemented +} + +// ----------------------------------------------------------------------------- +// DEBUG + +#ifdef CFG_DEBUG +static void hal_debug_init() { + #ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); + #endif +} + +#if !defined(CFG_DEBUG_STREAM) +#error "CFG_DEBUG needs CFG_DEBUG_STREAM defined in target-config.h" +#endif + +void hal_debug_str (const char* str) { + CFG_DEBUG_STREAM.print(str); +} + +void hal_debug_led (int val) { + #ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, val); + #endif +} +#endif // CFG_DEBUG + +// ----------------------------------------------------------------------------- + +#if defined(LMIC_PRINTF_TO) +#if defined(__AVR__) +// On AVR, use the AVR-specific fdev_setup_stream to redirect stdout +static int uart_putchar (char c, FILE *) +{ + 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 ; +} +#else +// On non-AVR platforms, use the somewhat more complex "cookie"-based +// approach to custom streams. This is a GNU-specific extension to libc. +static ssize_t uart_putchar (void *, const char *buf, size_t len) { + auto res = LMIC_PRINTF_TO.write(buf, len); + // Since the C interface has no meaningful way to flush (fflush() is a + // no-op on AVR since stdio does not introduce any buffering), just flush + // every byte. + LMIC_PRINTF_TO.flush(); + return res; +} + +static cookie_io_functions_t functions = { + .read = NULL, + .write = uart_putchar, + .seek = NULL, + .close = NULL +}; + +void hal_printf_init() { + stdout = fopencookie(NULL, "w", functions); + // Disable buffering, so the callbacks get called right away + setbuf(stdout, nullptr); +} +#endif // !defined(__AVR__) +#endif // defined(LMIC_PRINTF_TO) + +void hal_init (void * /* bootarg */) { + // configure radio I/O and interrupt handler + hal_io_init(); + // configure radio SPI + hal_spi_init(); + // configure timer and interrupt handler + hal_time_init(); +#if defined(LMIC_PRINTF_TO) + // printf support + hal_printf_init(); +#endif +#ifdef CFG_DEBUG + hal_debug_init(); +#endif +} + +void hal_failed () { +#ifdef CFG_DEBUG + CFG_DEBUG_STREAM.flush(); +#endif + // keep IRQs enabled, to allow e.g. USB to continue to run and allow + // firmware uploads on boards with native USB. + while(1); +} + +void hal_reboot (void) { + // TODO + hal_failed(); +} + +u1_t hal_getBattLevel (void) { + // Not implemented + return 0; +} + +void hal_setBattLevel (u1_t /* level */) { + // Not implemented +} + +void hal_fwinfo (hal_fwi* /* fwi */) { + // Not implemented +} + +u1_t* hal_joineui (void) { + return nullptr; +} + +u1_t* hal_deveui (void) { + return nullptr; +} + +u1_t* hal_nwkkey (void) { + return nullptr; +} + +u1_t* hal_appkey (void) { + return nullptr; +} + +u1_t* hal_serial (void) { + return nullptr; +} + +u4_t hal_region (void) { + return 0; +} + +u4_t hal_hwid (void) { + return 0; +} + +u4_t hal_unique (void) { + return 0; +} + +u4_t hal_dnonce_next (void) { + return os_getRndU2(); +} diff --git a/target/arduino/hal/hal.h b/target/arduino/hal/hal.h new file mode 100644 index 0000000..4f8fb75 --- /dev/null +++ b/target/arduino/hal/hal.h @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * 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 holder nor the names of its 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 SEMTECH 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. + * + * This the HAL to run LMIC on top of the Arduino environment. + *******************************************************************************/ +#ifndef _hal_hal_h_ +#define _hal_hal_h_ + +static const int NUM_DIO = 3; + +struct lmic_pinmap { + u1_t nss; + // Written HIGH in TX mode, LOW otherwise. + // Typically used with a single RXTX switch pin. + u1_t tx; + // Written HIGH in RX mode, LOW otherwise. + // Typicaly used with separate RX/TX pins, to allow switching off + // the antenna switch completely. + u1_t rx; + u1_t rst; + u1_t dio[NUM_DIO]; + u1_t busy; + u1_t tcxo; +}; + +// Use this for any unused pins. +const u1_t LMIC_UNUSED_PIN = 0xfd; + +#if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) +// Used for lmic_pinmap.tcxo only +const u1_t LMIC_CONTROLLED_BY_DIO3 = 0xff; +const u1_t LMIC_CONTROLLED_BY_DIO2 = 0xfe; +#endif // defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + +// Declared here, to be defined an initialized by the application +extern const lmic_pinmap lmic_pins; + +#endif // _hal_hal_h_ diff --git a/target/arduino/hal/target-config.h b/target/arduino/hal/target-config.h new file mode 100644 index 0000000..a124d27 --- /dev/null +++ b/target/arduino/hal/target-config.h @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * 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 holder nor the names of its 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 SEMTECH 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. + * + * This file defines the configuration of the library and HAL. + *******************************************************************************/ +#ifndef _lmic_arduino_hal_config_h_ +#define _lmic_arduino_hal_config_h_ + +// This defines the region(s) to use. You can enable more than one and +// then select the right region at runtime using os_getRegion() and/or +// LMIC_reset_ex(). +#if !defined(CFG_eu868) && !defined(CFG_us915) +#define CFG_eu868 1 +//#define CFG_us915 1 +//#define CFG_as923 1 +//#define CFG_il915 1 +//#define CFG_kr920 1 +//#define CFG_au915 1 +#endif // !defined(CFG_eu868) && !defined(CFG_us915) + +#define CFG_autojoin + +#if !defined(BRD_sx1272_radio) && !defined(BRD_sx1276_radio) && !defined(BRD_sx1261_radio) && !defined(BRD_sx1262_radio) +// This is the SX1272/SX1273 radio, which is also used on the HopeRF +// RFM92 boards. +//#define BRD_sx1272_radio 1 +// This is the SX1276/SX1277/SX1278/SX1279 radio, which is also used on +// the HopeRF RFM95 boards. +//#define BRD_sx1276_radio 1 +// This is the newer SX1261 radio (up to +15dBM). +//#define BRD_sx1261_radio 1 +// This is the newer SX1262 radio (up to +22dBM). +#define BRD_sx1262_radio 1 +#endif // !defined(BRD_sx1272_radio) && !defined(BRD_sx1276_radio) && !defined(BRD_sx1262_radio) + +// 16 μs per tick +// LMIC requires ticks to be 15.5μs - 100 μs long +#define US_PER_OSTICK_EXPONENT 4 +#define US_PER_OSTICK (1 << US_PER_OSTICK_EXPONENT) +#define OSTICKS_PER_SEC (1000000 / US_PER_OSTICK) + +// When this is defined, some debug output will be printed and +// debug_printf(...) is available (which is a slightly non-standard +// printf implementation). +// Without this, assertion failures are *not* printed! +#define CFG_DEBUG +// When this is defined, additional debug output is printed. +//#define CFG_DEBUG_VERBOSE +// Debug output (and assertion failures) are printed to this Stream +#define CFG_DEBUG_STREAM Serial +// Define these to add some TX or RX specific debug output (needs +// CFG_DEBUG) +#define DEBUG_TX +#define DEBUG_RX +// Define these to add some job scheduling specific debug output (needs +// CFG_DEBUG_VERBOSE) +//#define DEBUG_JOBS +// Uncomment to display timestamps in ticks rather than milliseconds +//#define CFG_DEBUG_RAW_TIMESTAMPS + +// When this is defined, the standard libc printf function will print to +// this Stream. You should probably use CFG_DEBUG and debug_printf() +// instead, though. +//#define LMIC_PRINTF_TO Serial + +// Remove/comment this to enable code related to beacon tracking. +#define DISABLE_CLASSB + +// This allows choosing between multiple included AES implementations. +// Make sure exactly one of these is uncommented. +// +// This selects the original AES implementation included LMIC. This +// implementation is optimized for speed on 32-bit processors using +// fairly big lookup tables, but it takes up big amounts of flash on the +// AVR architecture. +// #define USE_ORIGINAL_AES +// +// This selects the AES implementation written by Ideetroon for their +// own LoRaWAN library. It also uses lookup tables, but smaller +// byte-oriented ones, making it use a lot less flash space (but it is +// also about twice as slow as the original). +#define USE_IDEETRON_AES + +#endif // _lmic_arduino_hal_config_h_ diff --git a/target/arduino/hw.h b/target/arduino/hw.h new file mode 100644 index 0000000..a4a149e --- /dev/null +++ b/target/arduino/hw.h @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * 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 holder nor the names of its 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 SEMTECH 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. + *******************************************************************************/ + +#ifndef _hw_h_ +#define _hw_h_ + +// Dummy file, not used by this HAL (but must be present, since other +// files include it). + +#endif diff --git a/target/arduino/library.properties b/target/arduino/library.properties new file mode 100644 index 0000000..822b6e0 --- /dev/null +++ b/target/arduino/library.properties @@ -0,0 +1,9 @@ +name=Basicmac LoRaWAN stack +version=2.2.1 +author=Various +maintainer=Matthijs Kooijman +sentence=LoRaWAN framework originally released by Semtech and based on the LMIC library by IBM. +paragraph=Supports SX1272/SX1276/SX1261/SX1262 LoRa tranceivers +category=Communication +url=https://github.com/lacunaspace/basicmac +architectures=* diff --git a/unicorn/startup.c b/unicorn/startup.c index b463837..691761a 100644 --- a/unicorn/startup.c +++ b/unicorn/startup.c @@ -15,13 +15,13 @@ void _start (boot_boottab* boottab) { uint32_t* src = &_sidata; uint32_t* dst = &_sdata; while( dst < &_edata ) { - *dst++ = *src++; + *dst++ = *src++; } // initialize bss dst = &_sbss; while( dst < &_ebss ) { - *dst++ = 0; + *dst++ = 0; } // call main function