Skip to content

Added support for SAMR34-based boards #286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

This is an [Arduino Library](https://www.arduino.cc/en/Guide/Libraries) for Arduino devices like [The Things Uno](https://www.thethingsnetwork.org/docs/devices/uno/) and [Node](https://www.thethingsnetwork.org/docs/devices/node/) to communicate via [The Things Network](https://www.thethingsnetwork.org).

> At the moment this library requires devices to feature a [Microchip RN2xx3 module](http://www.microchip.com/design-centers/wireless-connectivity/embedded-wireless/lora-technology).
> At the moment this library requires devices to feature a [Microchip RN2xx3 module](http://www.microchip.com/design-centers/wireless-connectivity/embedded-wireless/lora-technology). You may also use a `SAMR34`-based board, for more information on that see [SAM34 Usage](#user-content-samr34-usage).

## Installation

Expand All @@ -17,6 +17,16 @@ This is an [Arduino Library](https://www.arduino.cc/en/Guide/Libraries) for Ardu
* [TheThingsNetwork](docs/TheThingsNetwork.md)
* [TheThingsMessage](docs/TheThingsMessage.md)

## SAM34 Usage

Compatibility between this library and the `SAMR34`-based boards is, at the moment, experimental. Boards that can be used are the [SAMR34 Xplained Pro](https://www.microchip.com/en-us/development-tool/dm320111), the [WLR089 Xplained Pro](https://www.microchip.com/en-us/development-tool/EV23M25A) or a proprietary board containing the `SAMR34`.

Before usage, please note the following:

1. If using a `SAMR34`-based board, you must first program your board with the [RN Parser](https://github.com/MicrochipTech/atsamr34_lorawan_rn_parser) firmware. This firmware emulates the behaviour of the `RN2xx3` devices on the `SAMR34`. Specifically, you **must** use the `MLS 1_0_P_6 (Parser_ECC608)` firmware contained [here](https://github.com/MicrochipTech/atsamr34_lorawan_rn_parser/tree/master/software/MLS_1_0_P_6/Parser_ECC608), since several bugs that made the firmware unusable where fixed for this release.
2. Some commands available in the `RN2xx3` modems are not implemented in the RN Parser firmware for `SAMR34`. For a complete list of the implemented commands and their possible differences, see the [Command User Guide](https://github.com/MicrochipTech/atsamr34_lorawan_rn_parser/blob/master/02_command_guide/README.md#top) for the RN Parser firmware.
3. The `autoBaud` feature is not available in the RN parser firmware, but this is easily circumvented by manually modifying the default baud rate in the `conf_sio2host.h` file within the firmware's source code. Default baud rate is `115200`.

## Examples

The library comes with [examples](examples). After installing the library you need to restart the Arduino IDE before they can be found under **File > Examples > TheThingsNetwork**.
27 changes: 23 additions & 4 deletions docs/TheThingsNetwork.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ void hardReset(uint8_t resetPin);

- `uint8_t resetPin`: The output pin that is connected to the module's reset pin. The output pin should be configured as output and set to high by the user.

## Method: `macReset`

Resets the LoRaWAN stack and initializes it with the parameters for the selected band.

```c
void macReset()
```

Note that, for `SAMR34`-based devices, where this command requires a `<band>` parameter, it will use the configured FP in the initialization of the TTN object (e.g. `TTN_FP_US915`).

## Method: `getHardwareEui`

Gets the unique hardware EUI, often used as the DevEUI.
Expand Down Expand Up @@ -82,6 +92,8 @@ RX Delay 1: 1000
RX Delay 2: 2000
```

Note that for `SAMR34`-based boards, it will not print out the `Battery` and `AppEUI` parameters, since the `sys get vdd` and `mac get appeui` commands are not currently implemented in the RN parser firmware for these modems.

See the [DeviceInfo](https://github.com/TheThingsNetwork/arduino-device-lib/blob/master/examples/DeviceInfo/DeviceInfo.ino) example.

## Method: `onMessage`
Expand Down Expand Up @@ -216,16 +228,21 @@ Sleep the LoRa module for a specified number of milliseconds.
void sleep(unsigned long mseconds);
```

- `unsigned long mseconds`: number of milliseconds to sleep.
- `unsigned long mseconds`: number of milliseconds to sleep. Must be >= 100 ms for `RN2xx3` modems, >= 1000 ms for `SAMR34`-based boards.

Note that, for `SAMR34`-based boards, this command will send `sys sleep standby <mseconds>` to the modem. For all other `RN2xx3` modems, it will only send `sys sleep <mseconds>`.

## Method: `wake`

Wake up the LoRa module from sleep before the expiration of the defined time.

```c
void wake();
void wake(uint8_t interruptPin);
```

- `uint8_t interruptPin`: Only required for `SAMR34`-based boards. Will not be used for `RN2XX3` modems, as these are woken up by a call to `autoBaud()`. Default value is `3`.
- On `SAMR34`-based boards, this pin must be set to `OUTPUT` and `HIGH` by the user, and provided in the call to `wake(interruptPin)`. More information on how this works available [here](https://github.com/MicrochipTech/atsamr34_lorawan_rn_parser/blob/master/02_command_guide/README.md#sys-sleep-mode-length).

## Method: `linkCheck`

Sets the time interval for the link check process to be triggered. The next uplink will include a Link Check Request MAC command when the interval expires. This method should be called after joining has been completed.
Expand Down Expand Up @@ -449,10 +466,10 @@ bool setRX1Delay(uint16_t delay);
Checks if a valid module is connected to the configured serial port. Useful to check for connectivity with a supported module before performing any other actions.

```c
bool checkValidModuleConnected(bool autobaud_first);
bool checkValidModuleConnected(bool autoBaudFirst);
```

- `bool autobaud_first`: Perform a call to `autoBaud()` before checking connection. Default is `false`.
- `bool autoBaudFirst`: Perform a call to `autoBaud()` before checking connection. Default is `false`.

Returns:

Expand All @@ -462,7 +479,9 @@ Returns:
* `RN2483A`
* `RN2903`
* `RN2903AS`
* `SAMR34` (or boards based on this device)
* `true` if the module responded (i.e. `needsHardReset` is `false`) and is valid (supported).
* Also sets the `modemType` attribute to either `TTN_MODEM_TYPE_RN` (for all `RN2xx3` devices) or `TTN_MODEM_TYPE_SAMR34`, depending on the detected modem.

See the [CheckModule](https://github.com/TheThingsNetwork/arduino-device-lib/blob/master/examples/CheckModule/CheckModule.ino) example.

Expand Down
143 changes: 131 additions & 12 deletions src/TheThingsNetwork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
debugStream->print(__VA_ARGS__); \
}

#define TTN_HEX_CHAR_TO_NIBBLE(c) ((c >= 'A') ? (c - 'A' + 0x0A) : (c - '0'))
#define TTN_HEX_CHAR_DETECT_CASE(c) ((c >= 'a') ? (c - 'a' + 0x0A) : (c - 'A' + 0x0A))
#define TTN_HEX_CHAR_TO_NIBBLE(c) ((c >= 'A') ? (TTN_HEX_CHAR_DETECT_CASE(c)) : (c - '0'))
#define TTN_HEX_PAIR_TO_BYTE(h, l) ((TTN_HEX_CHAR_TO_NIBBLE(h) << 4) + TTN_HEX_CHAR_TO_NIBBLE(l))

const char ok[] PROGMEM = "ok";
Expand All @@ -28,8 +29,9 @@ const char rn2483[] PROGMEM = "RN2483";
const char rn2483a[] PROGMEM = "RN2483A";
const char rn2903[] PROGMEM = "RN2903";
const char rn2903as[] PROGMEM = "RN2903AS";
const char samr34[] PROGMEM = "SAMR34";

const char *const compare_table[] PROGMEM = {ok, on, off, accepted, mac_tx_ok, mac_rx, mac_err, rn2483, rn2483a, rn2903, rn2903as};
const char *const compare_table[] PROGMEM = {ok, on, off, accepted, mac_tx_ok, mac_rx, mac_err, rn2483, rn2483a, rn2903, rn2903as, samr34};

#define CMP_OK 0
#define CMP_ON 1
Expand All @@ -42,6 +44,7 @@ const char *const compare_table[] PROGMEM = {ok, on, off, accepted, mac_tx_ok, m
#define CMP_RN2483A 8
#define CMP_RN2903 9
#define CMP_RN2903AS 10
#define CMP_SAMR34 11

// CMP OK
const char busy[] PROGMEM = "busy";
Expand Down Expand Up @@ -115,7 +118,7 @@ const char response_is_not_ok[] PROGMEM = "Response is not OK: ";
const char error_key_length[] PROGMEM = "One or more keys are of invalid length.";
const char check_configuration[] PROGMEM = "Check your coverage, keys and backend status.";
const char no_response[] PROGMEM = "No response from RN module.";
const char invalid_module[] PROGMEM = "Invalid module (must be RN2xx3[xx]).";
const char invalid_module[] PROGMEM = "Invalid module (must be RN2xx3[xx]/SAMR34).";

const char *const error_msg[] PROGMEM = {invalid_sf, invalid_fp, unexpected_response, send_command_failed, join_failed, join_not_accepted, personalize_not_accepted, response_is_not_ok, error_key_length, check_configuration, no_response, invalid_module};

Expand Down Expand Up @@ -190,8 +193,9 @@ const char sys_get_vdd[] PROGMEM = "vdd";
const char sys_get_hweui[] PROGMEM = "hweui";
const char sys_set_get_nvm[] PROGMEM = "nvm";
const char sys_set_pindig[] PROGMEM = "pindig";
const char sys_sleep_standby[] PROGMEM = "standby";

const char *const sys_table[] PROGMEM = {sys_prefix, sys_sleep, sys_reset, sys_erase_fw, sys_factory_rst, sys_set, sys_get, sys_get_ver, sys_get_vdd, sys_get_hweui, sys_set_get_nvm, sys_set_pindig};
const char *const sys_table[] PROGMEM = {sys_prefix, sys_sleep, sys_reset, sys_erase_fw, sys_factory_rst, sys_set, sys_get, sys_get_ver, sys_get_vdd, sys_get_hweui, sys_set_get_nvm, sys_set_pindig, sys_sleep_standby};

#define SYS_PREFIX 0
#define SYS_SLEEP 1
Expand All @@ -205,6 +209,7 @@ const char *const sys_table[] PROGMEM = {sys_prefix, sys_sleep, sys_reset, sys_e
#define SYS_GET_HWEUI 9
#define SYS_SET_GET_NVM 10
#define SYS_SET_PINDIG 11
#define SYS_SLEEP_STANDBY 12

const char mac_prefix[] PROGMEM = "mac";
const char mac_reset[] PROGMEM = "reset";
Expand All @@ -230,6 +235,44 @@ const char *const mac_table[] PROGMEM = {mac_prefix, mac_reset, mac_tx, mac_join
#define MAC_SET 8
#define MAC_GET 9

const char mac_reset_868[] PROGMEM = "868"; // EU868
const char mac_reset_433[] PROGMEM = "433"; // EU433
const char mac_reset_na915[] PROGMEM = "na915"; // NA915
const char mac_reset_au915[] PROGMEM = "au915"; // AU915
const char mac_reset_kr920[] PROGMEM = "kr920"; // KR920
const char mac_reset_jpn923[] PROGMEM = "jpn923"; // JPN932
const char mac_reset_brn923[] PROGMEM = "brn923"; // AS923
const char mac_reset_cmb923[] PROGMEM = "cmb923"; // AS923
const char mac_reset_ins923[] PROGMEM = "ins923"; // AS923
const char mac_reset_laos923[] PROGMEM = "laos923"; // AS923
const char mac_reset_nz923[] PROGMEM = "nz923"; // AS923
const char mac_reset_sp923[] PROGMEM = "sp923"; // AS923
const char mac_reset_twn923[] PROGMEM = "twn923"; // AS923
const char mac_reset_thai923[] PROGMEM = "thai923"; // AS923
const char mac_reset_vtm923[] PROGMEM = "vtm923"; // AS923
const char mac_reset_ind865[] PROGMEM = "ind865"; // IND865

const char *const mac_reset_table[] PROGMEM = {mac_reset_868, mac_reset_433, mac_reset_na915, mac_reset_au915, mac_reset_kr920,
mac_reset_jpn923, mac_reset_brn923, mac_reset_cmb923, mac_reset_ins923, mac_reset_laos923, mac_reset_nz923, mac_reset_sp923,
mac_reset_twn923, mac_reset_thai923, mac_reset_vtm923, mac_reset_ind865};

#define MAC_RESET_868 0
#define MAC_RESET_433 1
#define MAC_RESET_NA915 2
#define MAC_RESET_AU915 3
#define MAC_RESET_KR920 4
#define MAC_RESET_JPN923 5
#define MAC_RESET_BRN923 6
#define MAC_RESET_CMB923 7
#define MAC_RESET_INS923 8
#define MAC_RESET_LAOS923 9
#define MAC_RESET_NZ923 10
#define MAC_RESET_SP923 11
#define MAC_RESET_TWN923 12
#define MAC_RESET_THAI923 13
#define MAC_RESET_VTM923 14
#define MAC_RESET_IND865 15

const char mac_devaddr[] PROGMEM = "devaddr";
const char mac_deveui[] PROGMEM = "deveui";
const char mac_appeui[] PROGMEM = "appeui";
Expand Down Expand Up @@ -322,6 +365,7 @@ const char *const mac_tx_table[] PROGMEM = {mac_tx_type_cnf, mac_tx_type_ucnf};
#define SUCCESS_MESSAGE 8
#define CMP_TABLE 9
#define CMP_ERR_TABLE 10
#define MAC_RESET_TABLE 11

int pgmstrcmp(const char *str1, uint8_t str2Index, uint8_t table = CMP_TABLE)
{
Expand Down Expand Up @@ -645,6 +689,50 @@ void TheThingsNetwork::autoBaud()
baudDetermined = true;
}

bool TheThingsNetwork::macReset()
{
clearReadBuffer();
sendCommand(MAC_TABLE, MAC_PREFIX, true, false);
// only for SAMR34-based boards
if(this->modemType == TTN_MODEM_TYPE_SAMR34)
{
sendCommand(MAC_TABLE, MAC_RESET, true, false);
// check which default FP will be set
switch (fp)
{
case TTN_FP_EU868:
sendCommand(MAC_RESET_TABLE, MAC_RESET_868, false, false);
break;
case TTN_FP_US915:
sendCommand(MAC_RESET_TABLE, MAC_RESET_NA915, false, false);
break;
case TTN_FP_AU915:
sendCommand(MAC_RESET_TABLE, MAC_RESET_AU915, false, false);
break;
case TTN_FP_KR920_923:
sendCommand(MAC_RESET_TABLE, MAC_RESET_KR920, false, false);
break;
case TTN_FP_AS920_923:
case TTN_FP_AS923_925:
sendCommand(MAC_RESET_TABLE, MAC_RESET_BRN923, false, false); // TODO: fix, SAMR34 implements individual FPs for each AS923 country, while TTN only sets a general AS923/AS925 FP
break;
case TTN_FP_IN865_867:
sendCommand(MAC_RESET_TABLE, MAC_RESET_IND865, false, false);
break;
default:
debugPrintMessage(ERR_MESSAGE, ERR_INVALID_FP);
break;
}
}
// for RN2XX3-based boards
else
{
sendCommand(MAC_TABLE, MAC_RESET, false, false);
}
modemStream->write(SEND_MSG);
return waitForOk();
}

void TheThingsNetwork::reset(bool adr)
{
// autobaud and send "sys reset"
Expand Down Expand Up @@ -925,10 +1013,14 @@ void TheThingsNetwork::showStatus()
{
readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_HWEUI, buffer, sizeof(buffer));
debugPrintIndex(SHOW_EUI, buffer);
readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_VDD, buffer, sizeof(buffer));
debugPrintIndex(SHOW_BATTERY, buffer);
readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_APPEUI, buffer, sizeof(buffer));
debugPrintIndex(SHOW_APPEUI, buffer);
// these commands are not implemented for RN parser firmware for SAMR34/WLR089
if(this->modemType != TTN_MODEM_TYPE_SAMR34)
{
readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_VDD, buffer, sizeof(buffer));
debugPrintIndex(SHOW_BATTERY, buffer);
readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_APPEUI, buffer, sizeof(buffer));
debugPrintIndex(SHOW_APPEUI, buffer);
}
readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_DEVEUI, buffer, sizeof(buffer));
debugPrintIndex(SHOW_DEVEUI, buffer);
readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_DR, buffer, sizeof(buffer));
Expand Down Expand Up @@ -957,12 +1049,19 @@ bool TheThingsNetwork::checkValidModuleConnected(bool autoBaudFirst)
// buffer contains "RN2xx3[xx] x.x.x ...", getting only model (RN2xx3[xx])
char *model = strtok(buffer, " ");
debugPrintIndex(SHOW_MODEL, model);
// check if module is valid (must be RN2483, RN2483A, RN2903 or RN2903AS)
// check if module is valid (must be RN2483, RN2483A, RN2903, RN2903AS or SAMR34)
if(pgmstrcmp(model, CMP_RN2483) == 0 || pgmstrcmp(model, CMP_RN2483A) == 0 || pgmstrcmp(model, CMP_RN2903) == 0 || pgmstrcmp(model, CMP_RN2903AS) == 0)
{
this->modemType = TTN_MODEM_TYPE_RN;
debugPrintMessage(SUCCESS_MESSAGE, SCS_VALID_MODULE);
return true; // module responded and is valid (recognized/supported)
}
else if(pgmstrcmp(model, CMP_SAMR34) == 0)
{
this->modemType = TTN_MODEM_TYPE_SAMR34;
debugPrintMessage(SUCCESS_MESSAGE, SCS_VALID_MODULE); // module responded and is valid (recognized/supported)
return true;
}
debugPrintMessage(ERR_MESSAGE, ERR_INVALID_MODULE);
return false; // module responded but is invalid (unrecognized/unsupported)
}
Expand Down Expand Up @@ -1338,6 +1437,9 @@ void TheThingsNetwork::sendCommand(uint8_t table, uint8_t index, bool appendSpac
case RADIO_TABLE:
strcpy_P(command, (char *)pgm_read_word(&(radio_table[index])));
break;
case MAC_RESET_TABLE:
strcpy_P(command, (char *)pgm_read_word(&(mac_reset_table[index])));
break;
default:
return;
}
Expand Down Expand Up @@ -1486,24 +1588,41 @@ bool TheThingsNetwork::sendPayload(uint8_t mode, uint8_t port, uint8_t *payload,

void TheThingsNetwork::sleep(uint32_t mseconds)
{
if (mseconds < 100)
uint16_t minSleepTime = (this->modemType == TTN_MODEM_TYPE_SAMR34) ? 1000 : 100; // min sleep time for SAMR34 is 1000 ms
if (mseconds < minSleepTime)
{
return;
}

debugPrint(F(SENDING));
sendCommand(SYS_TABLE, SYS_PREFIX, true);
sendCommand(SYS_TABLE, SYS_SLEEP, true);
// send standby for SAMR34-based boards
if(this->modemType == TTN_MODEM_TYPE_SAMR34)
{
sendCommand(SYS_TABLE, SYS_SLEEP_STANDBY, true);
}

sprintf(buffer, "%lu", mseconds);
modemStream->write(buffer);
modemStream->write(SEND_MSG);
debugPrintLn(buffer);
}

void TheThingsNetwork::wake()
void TheThingsNetwork::wake(uint8_t interruptPin)
{
autoBaud();
// only for SAMR34-based boards
if(this->modemType == TTN_MODEM_TYPE_SAMR34)
{
digitalWrite(interruptPin, LOW);
delay(1000);
digitalWrite(interruptPin, HIGH);
}
// for RN2XX3-based boards
else
{
autoBaud();
}
}

void TheThingsNetwork::linkCheck(uint16_t seconds)
Expand Down
10 changes: 9 additions & 1 deletion src/TheThingsNetwork.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ enum ttn_modem_status_t
TTN_MODEM_C_RX2
};

enum ttn_modem_type_t
{
TTN_MODEM_TYPE_RN,
TTN_MODEM_TYPE_SAMR34
};

class TheThingsNetwork
{
private:
Expand Down Expand Up @@ -133,8 +139,10 @@ class TheThingsNetwork

public:
bool needsHardReset = false;
ttn_modem_type_t modemType = TTN_MODEM_TYPE_RN; // assume RN modem, this can be changed using checkValidModuleConnected

TheThingsNetwork(Stream &modemStream, Stream &debugStream, ttn_fp_t fp, uint8_t sf = TTN_DEFAULT_SF, uint8_t fsb = TTN_DEFAULT_FSB);
bool macReset();
void reset(bool adr = true);
void resetHard(uint8_t resetPin);
void showStatus();
Expand Down Expand Up @@ -166,7 +174,7 @@ class TheThingsNetwork
ttn_response_t sendBytes(const uint8_t *payload, size_t length, port_t port = 1, bool confirm = false, uint8_t sf = 0);
ttn_response_t poll(port_t port = 1, bool confirm = false, bool modem_only = false);
void sleep(uint32_t mseconds);
void wake();
void wake(uint8_t interruptPin = 3);
void saveState();
void linkCheck(uint16_t seconds);
uint8_t getLinkCheckGateways();
Expand Down