Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: lora-temperature LoRaWAN-lmic-app_HS LoRaWAN-lmic-app_huynh
LoRa WAN in C for sx1276 shield
Currently version 1.5
LoRaWAN network configuration for end-device
The following three pieces of information uniquely identifies end-device to network to allow over-the-air activation. These are stored in the end-device prior to join procedure.
AppEUI
Uniquely identifies application provider of end-device.
Least-significant byte first, 8 bytes, use reverse memcpy() to keep same order as shown on lora server.
example C code
static const u1_t APPEUI[8] = { 0x01, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x25, 0x00 };
This is copied into LMIC by os_getArtEui() callback function in application.
DevEUI
End-device ID, unique to each end-node.
Least-significant byte first, 8 bytes, use reverse memcpy() to keep same order as shown on lora server.
example C code
static const u1_t DEVEUI[8] = { 0x00, 0x00, 0x00, 0x00, 0x01, 0x0C, 0x25, 0x00 };
This is copied into LMIC by os_getDevEui() callback function in application.
AppKey (aka DevKey)
128-bit (16byte) AES key.
example C code
static const u1_t DEVKEY[16] = { 0xe4, 0x72, 0x71, 0xc5, 0xf5, 0x30, 0xa9, 0x9f, 0xcf, 0xc4, 0x0e, 0xab, 0xea, 0xd7, 0x19, 0x42 };
This is copied into LMIC by os_getDevKey() callback function in application.
Using over-the air activation, the end-device (LMIC) performs a join procedure every time it starts for first time, or has lost session context information. When join procedure has successfully completed, the end-device will have a network session key (NwkSKey) and an application session key (AppSKey), which are used for encryption and message integrity check.
US915 configuration with http://us01-iot.semtech.com/
- log in to server
- click on Applications
- find your application and click it
- go to configure motes
- to create a mote, you may enter a new DevEUI
- you may copy-paste the 16byte application key from an already existing mote, if you desire.
CHNL_HYBRID | 125KHz | 500KHz |
|---|---|---|
| defined value | channels | channel |
| 0 | 0 to 7 | 64 |
| 1 | 8 to 15 | 65 |
| 2 | 16 to 23 | 66 |
| 3 | 24 to 31 | 67 |
| 4 | 32 to 39 | 68 |
| 5 | 40 to 47 | 69 |
| 6 | 48 to 55 | 70 |
| 7 | 56 to 63 | 71 |
| undef | 0 to 63 | 64 to 71 |
Revision 0:62d1edcc13d1, committed 2015-01-22
- Comitter:
- mluis
- Date:
- Thu Jan 22 12:50:49 2015 +0000
- Child:
- 1:d3b7bde3995c
- Commit message:
- Porting of IBM LoRa MAC in C (LMiC) to the mbed platform.
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/aes.cpp Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,367 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#include "oslmic.h"
+
+#define AES_MICSUB 0x30 // internal use only
+
+static const u4_t AES_RCON[10] = {
+ 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
+ 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000
+};
+
+static const u1_t AES_S[256] = {
+ 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,
+};
+
+static const u4_t AES_E1[256] = {
+ 0xC66363A5, 0xF87C7C84, 0xEE777799, 0xF67B7B8D, 0xFFF2F20D, 0xD66B6BBD, 0xDE6F6FB1, 0x91C5C554,
+ 0x60303050, 0x02010103, 0xCE6767A9, 0x562B2B7D, 0xE7FEFE19, 0xB5D7D762, 0x4DABABE6, 0xEC76769A,
+ 0x8FCACA45, 0x1F82829D, 0x89C9C940, 0xFA7D7D87, 0xEFFAFA15, 0xB25959EB, 0x8E4747C9, 0xFBF0F00B,
+ 0x41ADADEC, 0xB3D4D467, 0x5FA2A2FD, 0x45AFAFEA, 0x239C9CBF, 0x53A4A4F7, 0xE4727296, 0x9BC0C05B,
+ 0x75B7B7C2, 0xE1FDFD1C, 0x3D9393AE, 0x4C26266A, 0x6C36365A, 0x7E3F3F41, 0xF5F7F702, 0x83CCCC4F,
+ 0x6834345C, 0x51A5A5F4, 0xD1E5E534, 0xF9F1F108, 0xE2717193, 0xABD8D873, 0x62313153, 0x2A15153F,
+ 0x0804040C, 0x95C7C752, 0x46232365, 0x9DC3C35E, 0x30181828, 0x379696A1, 0x0A05050F, 0x2F9A9AB5,
+ 0x0E070709, 0x24121236, 0x1B80809B, 0xDFE2E23D, 0xCDEBEB26, 0x4E272769, 0x7FB2B2CD, 0xEA75759F,
+ 0x1209091B, 0x1D83839E, 0x582C2C74, 0x341A1A2E, 0x361B1B2D, 0xDC6E6EB2, 0xB45A5AEE, 0x5BA0A0FB,
+ 0xA45252F6, 0x763B3B4D, 0xB7D6D661, 0x7DB3B3CE, 0x5229297B, 0xDDE3E33E, 0x5E2F2F71, 0x13848497,
+ 0xA65353F5, 0xB9D1D168, 0x00000000, 0xC1EDED2C, 0x40202060, 0xE3FCFC1F, 0x79B1B1C8, 0xB65B5BED,
+ 0xD46A6ABE, 0x8DCBCB46, 0x67BEBED9, 0x7239394B, 0x944A4ADE, 0x984C4CD4, 0xB05858E8, 0x85CFCF4A,
+ 0xBBD0D06B, 0xC5EFEF2A, 0x4FAAAAE5, 0xEDFBFB16, 0x864343C5, 0x9A4D4DD7, 0x66333355, 0x11858594,
+ 0x8A4545CF, 0xE9F9F910, 0x04020206, 0xFE7F7F81, 0xA05050F0, 0x783C3C44, 0x259F9FBA, 0x4BA8A8E3,
+ 0xA25151F3, 0x5DA3A3FE, 0x804040C0, 0x058F8F8A, 0x3F9292AD, 0x219D9DBC, 0x70383848, 0xF1F5F504,
+ 0x63BCBCDF, 0x77B6B6C1, 0xAFDADA75, 0x42212163, 0x20101030, 0xE5FFFF1A, 0xFDF3F30E, 0xBFD2D26D,
+ 0x81CDCD4C, 0x180C0C14, 0x26131335, 0xC3ECEC2F, 0xBE5F5FE1, 0x359797A2, 0x884444CC, 0x2E171739,
+ 0x93C4C457, 0x55A7A7F2, 0xFC7E7E82, 0x7A3D3D47, 0xC86464AC, 0xBA5D5DE7, 0x3219192B, 0xE6737395,
+ 0xC06060A0, 0x19818198, 0x9E4F4FD1, 0xA3DCDC7F, 0x44222266, 0x542A2A7E, 0x3B9090AB, 0x0B888883,
+ 0x8C4646CA, 0xC7EEEE29, 0x6BB8B8D3, 0x2814143C, 0xA7DEDE79, 0xBC5E5EE2, 0x160B0B1D, 0xADDBDB76,
+ 0xDBE0E03B, 0x64323256, 0x743A3A4E, 0x140A0A1E, 0x924949DB, 0x0C06060A, 0x4824246C, 0xB85C5CE4,
+ 0x9FC2C25D, 0xBDD3D36E, 0x43ACACEF, 0xC46262A6, 0x399191A8, 0x319595A4, 0xD3E4E437, 0xF279798B,
+ 0xD5E7E732, 0x8BC8C843, 0x6E373759, 0xDA6D6DB7, 0x018D8D8C, 0xB1D5D564, 0x9C4E4ED2, 0x49A9A9E0,
+ 0xD86C6CB4, 0xAC5656FA, 0xF3F4F407, 0xCFEAEA25, 0xCA6565AF, 0xF47A7A8E, 0x47AEAEE9, 0x10080818,
+ 0x6FBABAD5, 0xF0787888, 0x4A25256F, 0x5C2E2E72, 0x381C1C24, 0x57A6A6F1, 0x73B4B4C7, 0x97C6C651,
+ 0xCBE8E823, 0xA1DDDD7C, 0xE874749C, 0x3E1F1F21, 0x964B4BDD, 0x61BDBDDC, 0x0D8B8B86, 0x0F8A8A85,
+ 0xE0707090, 0x7C3E3E42, 0x71B5B5C4, 0xCC6666AA, 0x904848D8, 0x06030305, 0xF7F6F601, 0x1C0E0E12,
+ 0xC26161A3, 0x6A35355F, 0xAE5757F9, 0x69B9B9D0, 0x17868691, 0x99C1C158, 0x3A1D1D27, 0x279E9EB9,
+ 0xD9E1E138, 0xEBF8F813, 0x2B9898B3, 0x22111133, 0xD26969BB, 0xA9D9D970, 0x078E8E89, 0x339494A7,
+ 0x2D9B9BB6, 0x3C1E1E22, 0x15878792, 0xC9E9E920, 0x87CECE49, 0xAA5555FF, 0x50282878, 0xA5DFDF7A,
+ 0x038C8C8F, 0x59A1A1F8, 0x09898980, 0x1A0D0D17, 0x65BFBFDA, 0xD7E6E631, 0x844242C6, 0xD06868B8,
+ 0x824141C3, 0x299999B0, 0x5A2D2D77, 0x1E0F0F11, 0x7BB0B0CB, 0xA85454FC, 0x6DBBBBD6, 0x2C16163A,
+};
+
+static const u4_t AES_E2[256] = {
+ 0xA5C66363, 0x84F87C7C, 0x99EE7777, 0x8DF67B7B, 0x0DFFF2F2, 0xBDD66B6B, 0xB1DE6F6F, 0x5491C5C5,
+ 0x50603030, 0x03020101, 0xA9CE6767, 0x7D562B2B, 0x19E7FEFE, 0x62B5D7D7, 0xE64DABAB, 0x9AEC7676,
+ 0x458FCACA, 0x9D1F8282, 0x4089C9C9, 0x87FA7D7D, 0x15EFFAFA, 0xEBB25959, 0xC98E4747, 0x0BFBF0F0,
+ 0xEC41ADAD, 0x67B3D4D4, 0xFD5FA2A2, 0xEA45AFAF, 0xBF239C9C, 0xF753A4A4, 0x96E47272, 0x5B9BC0C0,
+ 0xC275B7B7, 0x1CE1FDFD, 0xAE3D9393, 0x6A4C2626, 0x5A6C3636, 0x417E3F3F, 0x02F5F7F7, 0x4F83CCCC,
+ 0x5C683434, 0xF451A5A5, 0x34D1E5E5, 0x08F9F1F1, 0x93E27171, 0x73ABD8D8, 0x53623131, 0x3F2A1515,
+ 0x0C080404, 0x5295C7C7, 0x65462323, 0x5E9DC3C3, 0x28301818, 0xA1379696, 0x0F0A0505, 0xB52F9A9A,
+ 0x090E0707, 0x36241212, 0x9B1B8080, 0x3DDFE2E2, 0x26CDEBEB, 0x694E2727, 0xCD7FB2B2, 0x9FEA7575,
+ 0x1B120909, 0x9E1D8383, 0x74582C2C, 0x2E341A1A, 0x2D361B1B, 0xB2DC6E6E, 0xEEB45A5A, 0xFB5BA0A0,
+ 0xF6A45252, 0x4D763B3B, 0x61B7D6D6, 0xCE7DB3B3, 0x7B522929, 0x3EDDE3E3, 0x715E2F2F, 0x97138484,
+ 0xF5A65353, 0x68B9D1D1, 0x00000000, 0x2CC1EDED, 0x60402020, 0x1FE3FCFC, 0xC879B1B1, 0xEDB65B5B,
+ 0xBED46A6A, 0x468DCBCB, 0xD967BEBE, 0x4B723939, 0xDE944A4A, 0xD4984C4C, 0xE8B05858, 0x4A85CFCF,
+ 0x6BBBD0D0, 0x2AC5EFEF, 0xE54FAAAA, 0x16EDFBFB, 0xC5864343, 0xD79A4D4D, 0x55663333, 0x94118585,
+ 0xCF8A4545, 0x10E9F9F9, 0x06040202, 0x81FE7F7F, 0xF0A05050, 0x44783C3C, 0xBA259F9F, 0xE34BA8A8,
+ 0xF3A25151, 0xFE5DA3A3, 0xC0804040, 0x8A058F8F, 0xAD3F9292, 0xBC219D9D, 0x48703838, 0x04F1F5F5,
+ 0xDF63BCBC, 0xC177B6B6, 0x75AFDADA, 0x63422121, 0x30201010, 0x1AE5FFFF, 0x0EFDF3F3, 0x6DBFD2D2,
+ 0x4C81CDCD, 0x14180C0C, 0x35261313, 0x2FC3ECEC, 0xE1BE5F5F, 0xA2359797, 0xCC884444, 0x392E1717,
+ 0x5793C4C4, 0xF255A7A7, 0x82FC7E7E, 0x477A3D3D, 0xACC86464, 0xE7BA5D5D, 0x2B321919, 0x95E67373,
+ 0xA0C06060, 0x98198181, 0xD19E4F4F, 0x7FA3DCDC, 0x66442222, 0x7E542A2A, 0xAB3B9090, 0x830B8888,
+ 0xCA8C4646, 0x29C7EEEE, 0xD36BB8B8, 0x3C281414, 0x79A7DEDE, 0xE2BC5E5E, 0x1D160B0B, 0x76ADDBDB,
+ 0x3BDBE0E0, 0x56643232, 0x4E743A3A, 0x1E140A0A, 0xDB924949, 0x0A0C0606, 0x6C482424, 0xE4B85C5C,
+ 0x5D9FC2C2, 0x6EBDD3D3, 0xEF43ACAC, 0xA6C46262, 0xA8399191, 0xA4319595, 0x37D3E4E4, 0x8BF27979,
+ 0x32D5E7E7, 0x438BC8C8, 0x596E3737, 0xB7DA6D6D, 0x8C018D8D, 0x64B1D5D5, 0xD29C4E4E, 0xE049A9A9,
+ 0xB4D86C6C, 0xFAAC5656, 0x07F3F4F4, 0x25CFEAEA, 0xAFCA6565, 0x8EF47A7A, 0xE947AEAE, 0x18100808,
+ 0xD56FBABA, 0x88F07878, 0x6F4A2525, 0x725C2E2E, 0x24381C1C, 0xF157A6A6, 0xC773B4B4, 0x5197C6C6,
+ 0x23CBE8E8, 0x7CA1DDDD, 0x9CE87474, 0x213E1F1F, 0xDD964B4B, 0xDC61BDBD, 0x860D8B8B, 0x850F8A8A,
+ 0x90E07070, 0x427C3E3E, 0xC471B5B5, 0xAACC6666, 0xD8904848, 0x05060303, 0x01F7F6F6, 0x121C0E0E,
+ 0xA3C26161, 0x5F6A3535, 0xF9AE5757, 0xD069B9B9, 0x91178686, 0x5899C1C1, 0x273A1D1D, 0xB9279E9E,
+ 0x38D9E1E1, 0x13EBF8F8, 0xB32B9898, 0x33221111, 0xBBD26969, 0x70A9D9D9, 0x89078E8E, 0xA7339494,
+ 0xB62D9B9B, 0x223C1E1E, 0x92158787, 0x20C9E9E9, 0x4987CECE, 0xFFAA5555, 0x78502828, 0x7AA5DFDF,
+ 0x8F038C8C, 0xF859A1A1, 0x80098989, 0x171A0D0D, 0xDA65BFBF, 0x31D7E6E6, 0xC6844242, 0xB8D06868,
+ 0xC3824141, 0xB0299999, 0x775A2D2D, 0x111E0F0F, 0xCB7BB0B0, 0xFCA85454, 0xD66DBBBB, 0x3A2C1616,
+};
+
+static const u4_t AES_E3[256] = {
+ 0x63A5C663, 0x7C84F87C, 0x7799EE77, 0x7B8DF67B, 0xF20DFFF2, 0x6BBDD66B, 0x6FB1DE6F, 0xC55491C5,
+ 0x30506030, 0x01030201, 0x67A9CE67, 0x2B7D562B, 0xFE19E7FE, 0xD762B5D7, 0xABE64DAB, 0x769AEC76,
+ 0xCA458FCA, 0x829D1F82, 0xC94089C9, 0x7D87FA7D, 0xFA15EFFA, 0x59EBB259, 0x47C98E47, 0xF00BFBF0,
+ 0xADEC41AD, 0xD467B3D4, 0xA2FD5FA2, 0xAFEA45AF, 0x9CBF239C, 0xA4F753A4, 0x7296E472, 0xC05B9BC0,
+ 0xB7C275B7, 0xFD1CE1FD, 0x93AE3D93, 0x266A4C26, 0x365A6C36, 0x3F417E3F, 0xF702F5F7, 0xCC4F83CC,
+ 0x345C6834, 0xA5F451A5, 0xE534D1E5, 0xF108F9F1, 0x7193E271, 0xD873ABD8, 0x31536231, 0x153F2A15,
+ 0x040C0804, 0xC75295C7, 0x23654623, 0xC35E9DC3, 0x18283018, 0x96A13796, 0x050F0A05, 0x9AB52F9A,
+ 0x07090E07, 0x12362412, 0x809B1B80, 0xE23DDFE2, 0xEB26CDEB, 0x27694E27, 0xB2CD7FB2, 0x759FEA75,
+ 0x091B1209, 0x839E1D83, 0x2C74582C, 0x1A2E341A, 0x1B2D361B, 0x6EB2DC6E, 0x5AEEB45A, 0xA0FB5BA0,
+ 0x52F6A452, 0x3B4D763B, 0xD661B7D6, 0xB3CE7DB3, 0x297B5229, 0xE33EDDE3, 0x2F715E2F, 0x84971384,
+ 0x53F5A653, 0xD168B9D1, 0x00000000, 0xED2CC1ED, 0x20604020, 0xFC1FE3FC, 0xB1C879B1, 0x5BEDB65B,
+ 0x6ABED46A, 0xCB468DCB, 0xBED967BE, 0x394B7239, 0x4ADE944A, 0x4CD4984C, 0x58E8B058, 0xCF4A85CF,
+ 0xD06BBBD0, 0xEF2AC5EF, 0xAAE54FAA, 0xFB16EDFB, 0x43C58643, 0x4DD79A4D, 0x33556633, 0x85941185,
+ 0x45CF8A45, 0xF910E9F9, 0x02060402, 0x7F81FE7F, 0x50F0A050, 0x3C44783C, 0x9FBA259F, 0xA8E34BA8,
+ 0x51F3A251, 0xA3FE5DA3, 0x40C08040, 0x8F8A058F, 0x92AD3F92, 0x9DBC219D, 0x38487038, 0xF504F1F5,
+ 0xBCDF63BC, 0xB6C177B6, 0xDA75AFDA, 0x21634221, 0x10302010, 0xFF1AE5FF, 0xF30EFDF3, 0xD26DBFD2,
+ 0xCD4C81CD, 0x0C14180C, 0x13352613, 0xEC2FC3EC, 0x5FE1BE5F, 0x97A23597, 0x44CC8844, 0x17392E17,
+ 0xC45793C4, 0xA7F255A7, 0x7E82FC7E, 0x3D477A3D, 0x64ACC864, 0x5DE7BA5D, 0x192B3219, 0x7395E673,
+ 0x60A0C060, 0x81981981, 0x4FD19E4F, 0xDC7FA3DC, 0x22664422, 0x2A7E542A, 0x90AB3B90, 0x88830B88,
+ 0x46CA8C46, 0xEE29C7EE, 0xB8D36BB8, 0x143C2814, 0xDE79A7DE, 0x5EE2BC5E, 0x0B1D160B, 0xDB76ADDB,
+ 0xE03BDBE0, 0x32566432, 0x3A4E743A, 0x0A1E140A, 0x49DB9249, 0x060A0C06, 0x246C4824, 0x5CE4B85C,
+ 0xC25D9FC2, 0xD36EBDD3, 0xACEF43AC, 0x62A6C462, 0x91A83991, 0x95A43195, 0xE437D3E4, 0x798BF279,
+ 0xE732D5E7, 0xC8438BC8, 0x37596E37, 0x6DB7DA6D, 0x8D8C018D, 0xD564B1D5, 0x4ED29C4E, 0xA9E049A9,
+ 0x6CB4D86C, 0x56FAAC56, 0xF407F3F4, 0xEA25CFEA, 0x65AFCA65, 0x7A8EF47A, 0xAEE947AE, 0x08181008,
+ 0xBAD56FBA, 0x7888F078, 0x256F4A25, 0x2E725C2E, 0x1C24381C, 0xA6F157A6, 0xB4C773B4, 0xC65197C6,
+ 0xE823CBE8, 0xDD7CA1DD, 0x749CE874, 0x1F213E1F, 0x4BDD964B, 0xBDDC61BD, 0x8B860D8B, 0x8A850F8A,
+ 0x7090E070, 0x3E427C3E, 0xB5C471B5, 0x66AACC66, 0x48D89048, 0x03050603, 0xF601F7F6, 0x0E121C0E,
+ 0x61A3C261, 0x355F6A35, 0x57F9AE57, 0xB9D069B9, 0x86911786, 0xC15899C1, 0x1D273A1D, 0x9EB9279E,
+ 0xE138D9E1, 0xF813EBF8, 0x98B32B98, 0x11332211, 0x69BBD269, 0xD970A9D9, 0x8E89078E, 0x94A73394,
+ 0x9BB62D9B, 0x1E223C1E, 0x87921587, 0xE920C9E9, 0xCE4987CE, 0x55FFAA55, 0x28785028, 0xDF7AA5DF,
+ 0x8C8F038C, 0xA1F859A1, 0x89800989, 0x0D171A0D, 0xBFDA65BF, 0xE631D7E6, 0x42C68442, 0x68B8D068,
+ 0x41C38241, 0x99B02999, 0x2D775A2D, 0x0F111E0F, 0xB0CB7BB0, 0x54FCA854, 0xBBD66DBB, 0x163A2C16,
+};
+
+static const u4_t AES_E4[256] = {
+ 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491,
+ 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC,
+ 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB,
+ 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B,
+ 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83,
+ 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A,
+ 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F,
+ 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA,
+ 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B,
+ 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713,
+ 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6,
+ 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85,
+ 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411,
+ 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B,
+ 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1,
+ 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF,
+ 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E,
+ 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6,
+ 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B,
+ 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD,
+ 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8,
+ 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2,
+ 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049,
+ 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810,
+ 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197,
+ 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F,
+ 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C,
+ 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927,
+ 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733,
+ 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5,
+ 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0,
+ 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C,
+};
+
+#define msbf4_read(p) ((p)[0]<<24 | (p)[1]<<16 | (p)[2]<<8 | (p)[3])
+#define msbf4_write(p,v) (p)[0]=(v)>>24,(p)[1]=(v)>>16,(p)[2]=(v)>>8,(p)[3]=(v)
+#define swapmsbf(x) ( (x&0xFF)<<24 | (x&0xFF00)<<8 | (x&0xFF0000)>>8 | (x>>24) )
+
+#define u1(v) ((u1_t)(v))
+
+#define AES_key4(r1,r2,r3,r0,i) r1 = ki[i+1]; \
+ r2 = ki[i+2]; \
+ r3 = ki[i+3]; \
+ r0 = ki[i]
+
+#define AES_expr4(r1,r2,r3,r0,i) r1 ^= AES_E4[u1(i)]; \
+ r2 ^= AES_E3[u1(i>>8)]; \
+ r3 ^= AES_E2[u1(i>>16)]; \
+ 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) ]
+
+// 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)];
+
+// generate 1+10 roundkeys for encryption with 128-bit key
+// read 128-bit key from AESKEY in MSBF, generate roundkey words in place
+static void aesroundkeys (void) {
+ int i;
+ u4_t b;
+
+ for( i=0; i<4; i++) {
+ AESKEY[i] = swapmsbf(AESKEY[i]);
+ }
+
+ b = AESKEY[3];
+ 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 ] ) ^
+ AES_RCON[(i-4)/4];
+ }
+ AESKEY[i] = b ^= AESKEY[i-4];
+ }
+}
+
+u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) {
+
+ aesroundkeys();
+
+ if( mode & AES_MICNOAUX ) {
+ AESAUX[0] = AESAUX[1] = AESAUX[2] = AESAUX[3] = 0;
+ } else {
+ AESAUX[0] = swapmsbf(AESAUX[0]);
+ AESAUX[1] = swapmsbf(AESAUX[1]);
+ AESAUX[2] = swapmsbf(AESAUX[2]);
+ AESAUX[3] = swapmsbf(AESAUX[3]);
+ }
+
+ while( (signed char)len > 0 ) {
+ u4_t a0, a1, a2, a3;
+ u4_t t0, t1, t2, t3;
+ u4_t *ki, *ke;
+
+ // load input block
+ if( (mode & AES_CTR) || ((mode & AES_MIC) && (mode & AES_MICNOAUX)==0) ) { // load CTR block or first MIC block
+ a0 = AESAUX[0];
+ a1 = AESAUX[1];
+ a2 = AESAUX[2];
+ a3 = AESAUX[3];
+ }
+ else if( (mode & AES_MIC) && len <= 16 ) { // last MIC block
+ a0 = a1 = a2 = a3 = 0; // load null block
+ mode |= ((len == 16) ? 1 : 2) << 4; // set MICSUB: CMAC subkey K1 or K2
+ } else
+ LOADDATA: { // load data block (partially)
+ for(t0=0; t0<16; t0++) {
+ t1 = (t1<<8) | ((t0<len) ? buf[t0] : (t0==len) ? 0x80 : 0x00);
+ if((t0&3)==3) {
+ a0 = a1;
+ a1 = a2;
+ a2 = a3;
+ a3 = t1;
+ }
+ }
+ if( mode & AES_MIC ) {
+ a0 ^= AESAUX[0];
+ a1 ^= AESAUX[1];
+ a2 ^= AESAUX[2];
+ a3 ^= AESAUX[3];
+ }
+ }
+
+ // perform AES encryption on block in a0-a3
+ ki = AESKEY;
+ ke = ki + 8*4;
+ a0 ^= ki[0];
+ a1 ^= ki[1];
+ a2 ^= ki[2];
+ a3 ^= ki[3];
+ do {
+ AES_key4 (t1,t2,t3,t0,4);
+ AES_expr4(t1,t2,t3,t0,a0);
+ AES_expr4(t2,t3,t0,t1,a1);
+ AES_expr4(t3,t0,t1,t2,a2);
+ AES_expr4(t0,t1,t2,t3,a3);
+
+ AES_key4 (a1,a2,a3,a0,8);
+ AES_expr4(a1,a2,a3,a0,t0);
+ AES_expr4(a2,a3,a0,a1,t1);
+ AES_expr4(a3,a0,a1,a2,t2);
+ AES_expr4(a0,a1,a2,a3,t3);
+ } while( (ki+=8) < ke );
+
+ AES_key4 (t1,t2,t3,t0,4);
+ AES_expr4(t1,t2,t3,t0,a0);
+ AES_expr4(t2,t3,t0,t1,a1);
+ AES_expr4(t3,t0,t1,t2,a2);
+ AES_expr4(t0,t1,t2,t3,a3);
+
+ AES_expr(a0,t0,t1,t2,t3,8);
+ AES_expr(a1,t1,t2,t3,t0,9);
+ AES_expr(a2,t2,t3,t0,t1,10);
+ AES_expr(a3,t3,t0,t1,t2,11);
+ // result of AES encryption in a0-a3
+
+ if( mode & AES_MIC ) {
+ if( (t1 = ((mode & AES_MICSUB) >> 4)) != 0 ) { // last block
+ do {
+ // compute CMAC subkey K1 and K2
+ t0 = a0 >> 31; // save MSB
+ a0 = (a0 << 1) | (a1 >> 31);
+ a1 = (a1 << 1) | (a2 >> 31);
+ a2 = (a2 << 1) | (a3 >> 31);
+ a3 = (a3 << 1);
+ if( t0 ) a3 ^= 0x87;
+ } while( --t1 );
+
+ AESAUX[0] ^= a0;
+ AESAUX[1] ^= a1;
+ AESAUX[2] ^= a2;
+ AESAUX[3] ^= a3;
+ mode &= ~AES_MICSUB;
+ goto LOADDATA;
+ } else {
+ // save cipher block as new iv
+ AESAUX[0] = a0;
+ AESAUX[1] = a1;
+ AESAUX[2] = a2;
+ AESAUX[3] = a3;
+ }
+ } else { // CIPHER
+ if( mode & AES_CTR ) { // xor block (partially)
+ t0 = (len > 16) ? 16: len;
+ for(t1=0; t1<t0; t1++) {
+ buf[t1] ^= (a0>>24);
+ a0 <<= 8;
+ if((t1&3)==3) {
+ a0 = a1;
+ a1 = a2;
+ a2 = a3;
+ }
+ }
+ // update counter
+ AESAUX[3]++;
+ } else { // ECB
+ // store block
+ msbf4_write(buf+0, a0);
+ msbf4_write(buf+4, a1);
+ msbf4_write(buf+8, a2);
+ msbf4_write(buf+12, a3);
+ }
+ }
+
+ // update block state
+ if( (mode & AES_MIC)==0 || (mode & AES_MICNOAUX) ) {
+ buf += 16;
+ len -= 16;
+ }
+ mode |= AES_MICNOAUX;
+ }
+ return AESAUX[0];
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hal.h Thu Jan 22 12:50:49 2015 +0000 @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corporation. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Zurich Research Lab - initial API, implementation and documentation + *******************************************************************************/ + +#ifndef _hal_hpp_ +#define _hal_hpp_ + +/* + * initialize hardware (IO, SPI, TIMER, IRQ). + */ +void hal_init (void); + +/* + * drive radio NSS pin (0=low, 1=high). + */ +void hal_pin_nss (u1_t val); + +/* + * drive radio RX/TX pins (0=rx, 1=tx). + */ +void hal_pin_rxtx (u1_t val); + +/* + * control radio RST pin (0=low, 1=high, 2=floating) + */ +void hal_pin_rst (u1_t val); + +/* + * perform 8-bit SPI transaction with radio. + * - write given byte 'outval' + * - read byte and return value + */ +u1_t hal_spi (u1_t outval); + +/* + * disable all CPU interrupts. + * - might be invoked nested + * - will be followed by matching call to hal_enableIRQs() + */ +void hal_disableIRQs (void); + +/* + * enable CPU interrupts. + */ +void hal_enableIRQs (void); + +/* + * put system and CPU in low-power mode, sleep until interrupt. + */ +void hal_sleep (void); + +/* + * return 32-bit system time in ticks. + */ +u4_t hal_ticks (void); + +/* + * busy-wait until specified timestamp (in ticks) is reached. + */ +void hal_waitUntil (u4_t time); + +/* + * check and rewind timer for target time. + * - return 1 if target time is close + * - otherwise rewind timer for target time or full period and return 0 + */ +u1_t hal_checkTimer (u4_t targettime); + +/* + * perform fatal failure action. + * - called by assertions + * - action could be HALT or reboot + */ +void hal_failed (void); + +////////////////////////////////////////////////////////////////////// +#ifdef CFG_DEBUG +void debug_init (void); +void debug_led (u1_t val); +void debug_char (u1_t c); +void debug_hex (u1_t b); +void debug_buf (const u1_t* buf, u2_t len); +void debug_uint (u4_t v); +void debug_str (const u1_t* str); +void debug_event (int ev); +void debug_val (const u1_t* label, u4_t val); +#define DEBUG_INIT() debug_init() +#define DEBUG_LED(v) debug_led(v) +#define DEBUG_CHAR(c) debug_char(c) +#define DEBUG_HEX(b) debug_hex(b) +#define DEBUG_BUF(b,l) debug_buf(b,l) +#define DEBUG_UINT(v) debug_uint(v) +#define DEBUG_STR(s) debug_str(s) +#define DEBUG_EVENT(e) debug_event(e) +#define DEBUG_VAL(l,v) debug_val(l, v) +#else // CFG_DEBUG +#define DEBUG_INIT() +#define DEBUG_LED(v) +#define DEBUG_CHAR(c) +#define DEBUG_HEX(b) +#define DEBUG_BUF(b,l) +#define DEBUG_UINT(v) +#define DEBUG_STR(s) +#define DEBUG_EVENT(e) +#define DEBUG_VAL(l,v) +#endif // CFG_DEBUG + +#endif // _hal_hpp_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hal/debug.h Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,61 @@
+/* Copyright (c) 2012 mbed.org, MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+ * and associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+ * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef DEBUG_H
+#define DEBUG_H
+
+/** @file debug.h */
+
+#ifndef NDEBUG
+
+#include <stdarg.h>
+#include <stdio.h>
+
+/** Output a debug message
+ *
+ * @param format printf-style format string, followed by variables
+ */
+static inline void debug(const char *format, ...) {
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+}
+
+/** Conditionally output a debug message
+ *
+ * @param condition output only if condition is true
+ * @param format printf-style format string, followed by variables
+ */
+static inline void debug_if(bool condition, const char *format, ...) {
+ if(condition) {
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ }
+}
+
+#else
+
+static inline void debug(const char *format, ...) {}
+static inline void debug(bool condition, const char *format, ...) {}
+
+#endif
+
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hal/hal.cpp Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,270 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Zurich Research Lab - initial API, implementation and documentation
+ * Semtech Apps Team - Modified to support the MBED sx1276 driver
+ * library.
+ * Possibility to use original or Semtech's MBED
+ * radio driver. The selection is done by setting
+ * USE_SMTC_RADIO_DRIVER preprocessing directive
+ * in lmic.h
+ *******************************************************************************/
+#include "mbed.h"
+#include "lmic.h"
+#include "mbed_debug.h"
+
+#if USE_SMTC_RADIO_DRIVER
+
+#else
+
+extern void radio_irq_handler( u1_t dio );
+
+static DigitalOut nss( D10 );
+static SPI spi( D11, D12, D13 ); // ( mosi, miso, sclk )
+
+static DigitalInOut rst( A0 );
+static DigitalOut rxtx( A4 );
+
+static InterruptIn dio0( D2 );
+static InterruptIn dio1( D3 );
+static InterruptIn dio2( D4 );
+
+static void dio0Irq( void )
+{
+ radio_irq_handler( 0 );
+}
+
+static void dio1Irq( void )
+{
+ radio_irq_handler( 1 );
+}
+static void dio2Irq( void )
+{
+ radio_irq_handler( 2 );
+}
+
+#endif
+
+static u1_t irqlevel = 0;
+static u4_t ticks = 0;
+
+static Timer timer;
+static Ticker ticker;
+
+static void reset_timer( void )
+{
+ ticks += timer.read_us( ) >> 6;
+ timer.reset( );
+}
+
+void hal_init( void )
+{
+ __disable_irq( );
+ irqlevel = 0;
+
+#if USE_SMTC_RADIO_DRIVER
+
+#else
+ // configure input lines
+ dio0.mode( PullDown );
+ dio0.rise( dio0Irq );
+ dio0.enable_irq( );
+ dio1.mode( PullDown );
+ dio1.rise( dio1Irq );
+ dio1.enable_irq( );
+ dio2.mode( PullDown );
+ dio2.rise( dio2Irq );
+ dio2.enable_irq( );
+ // configure reset line
+ rst.input( );
+ // configure spi
+ spi.frequency( 8000000 );
+ spi.format( 8, 0 );
+ nss = 1;
+#endif
+ // configure timer
+ timer.start( );
+ ticker.attach_us( reset_timer, 10000000 ); // reset timer every 10sec
+ __enable_irq( );
+}
+
+#if USE_SMTC_RADIO_DRIVER
+
+#else
+
+void hal_pin_rxtx( u1_t val )
+{
+ rxtx = !val;
+}
+
+void hal_pin_nss( u1_t val )
+{
+ nss = val;
+}
+
+void hal_pin_rst( u1_t val )
+{
+ if( val == 0 || val == 1 )
+ { // drive pin
+ rst.output( );
+ rst = val;
+ }
+ else
+ { // keep pin floating
+ rst.input( );
+ }
+}
+
+u1_t hal_spi( u1_t out )
+{
+ return spi.write( out );
+}
+
+#endif
+
+void hal_disableIRQs( void )
+{
+ __disable_irq( );
+ irqlevel++;
+}
+
+void hal_enableIRQs( void )
+{
+ if( --irqlevel == 0 )
+ {
+ __enable_irq( );
+ }
+}
+
+void hal_sleep( void )
+{
+ // NOP
+}
+
+u4_t hal_ticks( void )
+{
+ hal_disableIRQs( );
+ int t = ticks + ( timer.read_us( ) >> 6 );
+ hal_enableIRQs( );
+ return t;
+}
+
+static u2_t deltaticks( u4_t time )
+{
+ u4_t t = hal_ticks( );
+ s4_t d = time - t;
+ if( d <= 0 )
+ {
+ return 0; // in the past
+ }
+ if( ( d >> 16 ) != 0 )
+ {
+ return 0xFFFF; // far ahead
+ }
+ return ( u2_t )d;
+}
+
+void hal_waitUntil( u4_t time )
+{
+ while( deltaticks( time ) != 0 ); // busy wait until timestamp is reached
+}
+
+u1_t hal_checkTimer( u4_t time )
+{
+ return ( deltaticks( time ) < 2 );
+}
+
+void hal_failed( void )
+{
+ while( 1 );
+}
+
+//////////////////////////////////////////////////////////////////////
+// DEBUG CODE BELOW (use CFG_DEBUG)
+//////////////////////////////////////////////////////////////////////
+#ifdef CFG_DEBUG
+
+void debug_init( void )
+{
+ // print banner
+ debug( "\r\n============== DEBUG STARTED ==============\r\n" );
+}
+
+void debug_char( u1_t c )
+{
+ debug( "%c", c );
+}
+
+void debug_hex( u1_t b )
+{
+ debug( "%02X", b );
+}
+
+void debug_buf( const u1_t* buf, u2_t len )
+{
+ while( len-- )
+ {
+ debug_hex( *buf++ );
+ debug_char( ' ' );
+ }
+ debug_char( '\r' );
+ debug_char( '\n' );
+}
+
+void debug_uint( u4_t v )
+{
+ for( s1_t n = 24; n >= 0; n -= 8 )
+ {
+ debug_hex( v >> n );
+ }
+}
+
+void debug_str( const u1_t* str )
+{
+ while( *str )
+ {
+ debug_char( *str++ );
+ }
+}
+
+void debug_val( const u1_t* label, u4_t val )
+{
+ debug_str( label );
+ debug_uint( val );
+ debug_char( '\r' );
+ debug_char( '\n' );
+}
+
+void debug_led( u1_t val )
+{
+ debug_val( "LED = ", val );
+}
+
+void debug_event( int ev )
+{
+ static const u1_t* evnames[] =
+ {
+ [EV_SCAN_TIMEOUT] = "SCAN_TIMEOUT",
+ [EV_BEACON_FOUND] = "BEACON_FOUND",
+ [EV_BEACON_MISSED] = "BEACON_MISSED",
+ [EV_BEACON_TRACKED] = "BEACON_TRACKED",
+ [EV_JOINING] = "JOINING",
+ [EV_JOINED] = "JOINED",
+ [EV_RFU1] = "RFU1",
+ [EV_JOIN_FAILED] = "JOIN_FAILED",
+ [EV_REJOIN_FAILED] = "REJOIN_FAILED",
+ [EV_TXCOMPLETE] = "TXCOMPLETE",
+ [EV_LOST_TSYNC] = "LOST_TSYNC",
+ [EV_RESET] = "RESET",
+ [EV_RXCOMPLETE] = "RXCOMPLETE",
+ [EV_LINK_DEAD] = "LINK_DEAD",
+ [EV_LINK_ALIVE] = "LINK_ALIVE",
+ };
+ debug( "%s\r\n", evnames[ev] );
+}
+#endif // CFG_DEBUG
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lmic.cpp Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,2078 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#include "lmic.h"
+
+#define PAMBL_SYMS 8
+#define MINRX_SYMS 5
+#define PAMBL_FSK 5
+#define PRERX_FSK 1
+#define RXLEN_FSK (1+5+2)
+
+#define BCN_INTV_osticks sec2osticks(BCN_INTV_sec)
+#define TXRX_GUARD_osticks ms2osticks(TXRX_GUARD_ms)
+#define JOIN_GUARD_osticks ms2osticks(JOIN_GUARD_ms)
+#define DELAY_DNW1_osticks sec2osticks(DELAY_DNW1)
+#define DELAY_DNW2_osticks sec2osticks(DELAY_DNW2)
+#define DELAY_JACC1_osticks sec2osticks(DELAY_JACC1)
+#define DELAY_JACC2_osticks sec2osticks(DELAY_JACC2)
+#define DELAY_EXTDNW2_osticks sec2osticks(DELAY_EXTDNW2)
+#define BCN_RESERVE_osticks ms2osticks(BCN_RESERVE_ms)
+#define BCN_GUARD_osticks ms2osticks(BCN_GUARD_ms)
+#define BCN_WINDOW_osticks ms2osticks(BCN_WINDOW_ms)
+#define AIRTIME_BCN_osticks us2osticks(AIRTIME_BCN)
+
+DEFINE_LMIC;
+DECL_ON_LMIC_EVENT;
+
+
+// Fwd decls.
+static void engineUpdate(void);
+static void startScan (void);
+
+
+// ================================================================================
+// BEG OS - default implementations for certain OS suport functions
+
+#if !HAS_os_calls
+
+#ifndef os_rlsbf2
+u2_t os_rlsbf2 (xref2cu1_t buf) {
+ return (u2_t)(buf[0] | (buf[1]<<8));
+}
+#endif
+
+#ifndef os_rlsbf4
+u4_t os_rlsbf4 (xref2cu1_t buf) {
+ return (u4_t)(buf[0] | (buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24));
+}
+#endif
+
+
+#ifndef os_rmsbf4
+u4_t os_rmsbf4 (xref2cu1_t buf) {
+ return (u4_t)(buf[3] | (buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24));
+}
+#endif
+
+
+#ifndef os_wlsbf2
+void os_wlsbf2 (xref2u1_t buf, u2_t v) {
+ buf[0] = v;
+ buf[1] = v>>8;
+}
+#endif
+
+#ifndef os_wlsbf4
+void os_wlsbf4 (xref2u1_t buf, u4_t v) {
+ buf[0] = v;
+ buf[1] = v>>8;
+ buf[2] = v>>16;
+ buf[3] = v>>24;
+}
+#endif
+
+#ifndef os_wmsbf4
+void os_wmsbf4 (xref2u1_t buf, u4_t v) {
+ buf[3] = v;
+ buf[2] = v>>8;
+ buf[1] = v>>16;
+ buf[0] = v>>24;
+}
+#endif
+
+#ifndef os_getBattLevel
+u1_t os_getBattLevel (void) {
+ return MCMD_DEVS_BATT_NOINFO;
+}
+#endif
+
+#ifndef os_crc16
+static const u2_t CRC_TABLE[] = {
+ 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+ 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+ 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+ 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+ 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+ 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+ 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+ 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+ 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+ 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+ 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+ 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+ 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+ 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+ 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+ 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+ 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+ 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+ 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+ 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+ 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+ 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+ 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+ 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+ 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+ 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+ 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+ 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+ 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+ 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+ 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+ 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+};
+u2_t os_crc16 (xref2u1_t data, uint len) {
+ u2_t fcs = 0;
+ for( uint u=0; u<len; u++ ) {
+ u1_t b = data[u];
+ fcs = (CRC_TABLE[(fcs ^ b) & 0xFF] ^ ((fcs >> 8) & 0xFF));
+ }
+ return fcs;
+}
+#endif
+
+#endif // !HAS_os_calls
+
+// END OS - default implementations for certain OS suport functions
+// ================================================================================
+
+// ================================================================================
+// BEG AES
+
+static void micB0 (u4_t devaddr, u4_t seqno, int dndir, int len) {
+ os_clearMem(AESaux,16);
+ AESaux[0] = 0x49;
+ AESaux[5] = dndir?1:0;
+ AESaux[15] = len;
+ os_wlsbf4(AESaux+ 6,devaddr);
+ os_wlsbf4(AESaux+10,seqno);
+}
+
+
+static int aes_verifyMic (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t pdu, int len) {
+ micB0(devaddr, seqno, dndir, len);
+ os_copyMem(AESkey,key,16);
+ return os_aes(AES_MIC, pdu, len) == os_rmsbf4(pdu+len);
+}
+
+
+static void aes_appendMic (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t pdu, int len) {
+ micB0(devaddr, seqno, dndir, len);
+ os_copyMem(AESkey,key,16);
+ // MSB because of internal structure of AES
+ os_wmsbf4(pdu+len, os_aes(AES_MIC, pdu, len));
+}
+
+
+static void aes_appendMic0 (xref2u1_t pdu, int len) {
+ os_getDevKey(AESkey);
+ os_wmsbf4(pdu+len, os_aes(AES_MIC|AES_MICNOAUX, pdu, len)); // MSB because of internal structure of AES
+}
+
+
+static int aes_verifyMic0 (xref2u1_t pdu, int len) {
+ os_getDevKey(AESkey);
+ return os_aes(AES_MIC|AES_MICNOAUX, pdu, len) == os_rmsbf4(pdu+len);
+}
+
+
+static void aes_encrypt (xref2u1_t pdu, int len) {
+ os_getDevKey(AESkey);
+ os_aes(AES_ENC, pdu, len);
+}
+
+
+static void aes_cipher (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t payload, int len) {
+ if( len <= 0 )
+ return;
+ os_clearMem(AESaux, 16);
+ AESaux[0] = AESaux[15] = 1; // mode=cipher / dir=down / block counter=1
+ AESaux[5] = dndir?1:0;
+ os_wlsbf4(AESaux+ 6,devaddr);
+ os_wlsbf4(AESaux+10,seqno);
+ os_copyMem(AESkey,key,16);
+ os_aes(AES_CTR, payload, len);
+}
+
+
+static void aes_sessKeys (u2_t devnonce, xref2cu1_t artnonce, xref2u1_t nwkkey, xref2u1_t artkey) {
+ os_clearMem(nwkkey, 16);
+ nwkkey[0] = 0x01;
+ os_copyMem(nwkkey+1, artnonce, LEN_ARTNONCE+LEN_NETID);
+ os_wlsbf2(nwkkey+1+LEN_ARTNONCE+LEN_NETID, devnonce);
+ os_copyMem(artkey, nwkkey, 16);
+ artkey[0] = 0x02;
+
+ os_getDevKey(AESkey);
+ os_aes(AES_ENC, nwkkey, 16);
+ os_getDevKey(AESkey);
+ os_aes(AES_ENC, artkey, 16);
+}
+
+// END AES
+// ================================================================================
+
+
+// ================================================================================
+// BEG LORA
+
+#if CFG_eu868 // ========================================
+
+#define maxFrameLen(dr) ((dr)<=DR_SF9 ? maxFrameLens[(dr)] : 0xFF)
+const u1_t maxFrameLens [] = { 64,64,64,123 };
+
+const u1_t _DR2RPS_CRC[] = {
+ ILLEGAL_RPS,
+ (u1_t)MAKERPS(SF12, BW125, CR_4_5, 0, 0),
+ (u1_t)MAKERPS(SF11, BW125, CR_4_5, 0, 0),
+ (u1_t)MAKERPS(SF10, BW125, CR_4_5, 0, 0),
+ (u1_t)MAKERPS(SF9, BW125, CR_4_5, 0, 0),
+ (u1_t)MAKERPS(SF8, BW125, CR_4_5, 0, 0),
+ (u1_t)MAKERPS(SF7, BW125, CR_4_5, 0, 0),
+ (u1_t)MAKERPS(SF7, BW250, CR_4_5, 0, 0),
+ (u1_t)MAKERPS(FSK, BW125, CR_4_5, 0, 0),
+ ILLEGAL_RPS
+};
+
+static const s1_t TXPOWLEVELS[] = {
+ 20, 14, 11, 8, 5, 2, 0,0, 0,0,0,0, 0,0,0,0
+};
+#define pow2dBm(mcmd_ladr_p1) (TXPOWLEVELS[(mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT])
+
+#elif CFG_us915 // ========================================
+
+#define maxFrameLen(dr) ((dr)<=DR_SF11CR ? maxFrameLens[(dr)] : 0xFF)
+const u1_t maxFrameLens [] = { 24,66,142,255,255,255,255,255, 66,142 };
+
+const u1_t _DR2RPS_CRC[] = {
+ ILLEGAL_RPS,
+ MAKERPS(SF10, BW125, CR_4_5, 0, 0),
+ MAKERPS(SF9 , BW125, CR_4_5, 0, 0),
+ MAKERPS(SF8 , BW125, CR_4_5, 0, 0),
+ MAKERPS(SF7 , BW125, CR_4_5, 0, 0),
+ MAKERPS(SF8 , BW500, CR_4_5, 0, 0),
+ ILLEGAL_RPS ,
+ ILLEGAL_RPS ,
+ ILLEGAL_RPS ,
+ MAKERPS(SF12, BW500, CR_4_5, 0, 0),
+ MAKERPS(SF11, BW500, CR_4_5, 0, 0),
+ MAKERPS(SF10, BW500, CR_4_5, 0, 0),
+ MAKERPS(SF9 , BW500, CR_4_5, 0, 0),
+ MAKERPS(SF8 , BW500, CR_4_5, 0, 0),
+ MAKERPS(SF7 , BW500, CR_4_5, 0, 0),
+ ILLEGAL_RPS
+};
+
+#define pow2dBm(mcmd_ladr_p1) ((s1_t)(30 - (((mcmd_ladr_p1)&MCMD_LADR_POW_MASK)<<1)))
+
+#endif // ================================================
+
+static const u1_t SENSITIVITY[7][3] = {
+ // ------------bw----------
+ // 125kHz 250kHz 500kHz
+ { 141-109, 141-109, 141-109 }, // FSK
+ { 141-127, 141-124, 141-121 }, // SF7
+ { 141-129, 141-126, 141-123 }, // SF8
+ { 141-132, 141-129, 141-126 }, // SF9
+ { 141-135, 141-132, 141-129 }, // SF10
+ { 141-138, 141-135, 141-132 }, // SF11
+ { 141-141, 141-138, 141-135 } // SF12
+};
+
+int getSensitivity (rps_t rps) {
+ return -141 + SENSITIVITY[getSf(rps)][getBw(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 ) {
+ // MLu - 12012015
+ return (plen+/*preamble*/5+/*syncword*/3+/*len*/1+/*crc*/2) * /*bits/byte*/8
+ * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000;
+ //return (plen+/*preamble*/5+/*syncword*/2+/*len*/1+/*crc*/2) * /*bits/byte*/8
+ // * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000;
+ }
+ u1_t sfx = 4*(sf+(7-SF7));
+ u1_t q = sfx - (sf >= SF11 ? 8 : 0);
+ int tmp = 8*plen - sfx + 28 + (getNocrc(rps)?0:16) - (getIh(rps)?20:0);
+ if( tmp > 0 ) {
+ tmp = (tmp + q - 1) / q;
+ tmp *= getCr(rps)+5;
+ tmp += 8;
+ } else {
+ tmp = 8;
+ }
+ tmp = (tmp<<2) + /*preamble*/49 /* 4 * (8 + 4.25) */;
+ // bw = 125000 = 15625 * 2^3
+ // 250000 = 15625 * 2^4
+ // 500000 = 15625 * 2^5
+ // sf = 7..12
+ //
+ // osticks = tmp * OSTICKS_PER_SEC * 1<<sf / bw
+ //
+ // 3 => counter reduced divisor 125000/8 => 15625
+ // 2 => counter 2 shift on tmp
+ sfx = sf+(7-SF7) - (3+2) - bw;
+ int div = 15625;
+ if( sfx > 4 ) {
+ // prevent 32bit signed int overflow in last step
+ div >>= sfx-4;
+ sfx = 4;
+ }
+ // Need 32bit arithmetic for this last step
+ return (((ostime_t)tmp << sfx) * OSTICKS_PER_SEC + div/2) / div;
+}
+
+extern inline s1_t rssi2s1 (int v);
+extern inline int s12rssi (s1_t v);
+extern inline float s12snr (s1_t v);
+extern inline s1_t snr2s1 (double v);
+extern inline int getRssi (rxqu_t*rxq);
+extern inline void setRssi (rxqu_t*rxq, int v);
+
+extern inline rps_t updr2rps (dr_t dr);
+extern inline rps_t dndr2rps (dr_t dr);
+extern inline int isFasterDR (dr_t dr1, dr_t dr2);
+extern inline int isSlowerDR (dr_t dr1, dr_t dr2);
+extern inline dr_t incDR (dr_t dr);
+extern inline dr_t decDR (dr_t dr);
+extern inline dr_t assertDR (dr_t dr);
+extern inline dr_t validDR (dr_t dr);
+extern inline dr_t lowerDR (dr_t dr, u1_t n);
+
+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);
+extern inline rps_t setBw (rps_t params, bw_t cr);
+extern inline cr_t getCr (rps_t params);
+extern inline rps_t setCr (rps_t params, cr_t cr);
+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);
+
+// END LORA
+// ================================================================================
+
+
+// Adjust DR for TX retries
+// - indexed by retry count
+// - return steps to lower DR
+static const u1_t DRADJUST[2+TXCONF_ATTEMPTS] = {
+ // normal frames - 1st try / no retry
+ 0,
+ // confirmed frames
+ 0,0,0,0,0,1,1,1,2
+};
+
+
+// Table below defines the size of one symbol as
+// symtime = 256us * 2^T(sf,bw)
+// 256us is called one symunit.
+// SF:
+// BW: |__7___8___9__10__11__12
+// 125kHz | 2 3 4 5 6 7
+// 250kHz | 1 2 3 4 5 6
+// 500kHz | 0 1 2 3 4 5
+//
+// Times for half symbol per DR
+// Per DR table to minimize rounding errors
+static const ostime_t DR2HSYM_osticks[] = {
+#if CFG_eu868
+#define dr2hsym(dr) (DR2HSYM_osticks[(dr)])
+ us2osticksRound(128<<7), // DR_SF12
+ us2osticksRound(128<<6), // DR_SF11
+ us2osticksRound(128<<5), // DR_SF10
+ us2osticksRound(128<<4), // DR_SF9
+ us2osticksRound(128<<3), // DR_SF8
+ us2osticksRound(128<<2), // DR_SF7
+ us2osticksRound(128<<1), // DR_SF7B
+ us2osticksRound(80) // FSK -- not used (time for 1/2 byte)
+#elif CFG_us915
+#define dr2hsym(dr) (DR2HSYM_osticks[(dr)&7]) // map DR_SFnCR -> 0-6
+ us2osticksRound(128<<5), // DR_SF10 DR_SF12CR
+ us2osticksRound(128<<4), // DR_SF9 DR_SF11CR
+ us2osticksRound(128<<3), // DR_SF8 DR_SF10CR
+ us2osticksRound(128<<2), // DR_SF7 DR_SF9CR
+ us2osticksRound(128<<1), // DR_SF8C DR_SF8CR
+ us2osticksRound(128<<0) // ------ DR_SF7CR
+#endif
+};
+
+
+static ostime_t calcRxWindow (u1_t secs, dr_t dr) {
+ ostime_t rxoff, err;
+ if( secs==0 ) {
+ // aka 128 secs (next becaon)
+ rxoff = LMIC.drift;
+ err = LMIC.lastDriftDiff;
+ } else {
+ // scheduled RX window within secs into current beacon period
+ rxoff = (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp;
+ err = (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp;
+ }
+ u1_t rxsyms = MINRX_SYMS;
+ err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns;
+ LMIC.rxsyms = MINRX_SYMS + (err / dr2hsym(dr));
+
+ return (rxsyms-PAMBL_SYMS) * dr2hsym(dr) + rxoff;
+}
+
+
+// Setup beacon RX parameters assuming we have an error of ms (aka +/-(ms/2))
+static void calcBcnRxWindowFromMillis (u1_t ms, bit_t ini) {
+ if( ini ) {
+ LMIC.drift = 0;
+ LMIC.maxDriftDiff = 0;
+ LMIC.missedBcns = 0;
+ LMIC.bcninfo.flags |= BCN_NODRIFT|BCN_NODDIFF;
+ }
+ ostime_t hsym = dr2hsym(DR_BCN);
+ LMIC.bcnRxsyms = MINRX_SYMS + ms2osticksCeil(ms) / hsym;
+ LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-PAMBL_SYMS) * hsym;
+}
+
+
+// Setup scheduled RX window (ping/multicast slot)
+static void rxschedInit (xref2rxsched_t rxsched) {
+ os_clearMem(AESkey,16);
+ os_clearMem(LMIC.frame+8,8);
+ os_wlsbf4(LMIC.frame, LMIC.bcninfo.time);
+ os_wlsbf4(LMIC.frame+4, LMIC.devaddr);
+ os_aes(AES_ENC,LMIC.frame,16);
+ u1_t intvExp = rxsched->intvExp;
+ ostime_t off = os_rlsbf2(LMIC.frame) & (0x0FFF >> (7 - intvExp)); // random offset (slot units)
+ rxsched->rxbase = (LMIC.bcninfo.txtime +
+ BCN_RESERVE_osticks +
+ ms2osticks(BCN_SLOT_SPAN_ms * off)); // random offset osticks
+ rxsched->slot = 0;
+ rxsched->rxtime = rxsched->rxbase - calcRxWindow(/*secs BCN_RESERVE*/2+(1<<intvExp),rxsched->dr);
+ rxsched->rxsyms = LMIC.rxsyms;
+}
+
+
+static bit_t rxschedNext (xref2rxsched_t rxsched, ostime_t cando) {
+ again:
+ if( rxsched->rxtime - cando >= 0 )
+ return 1;
+ u1_t slot;
+ if( (slot=rxsched->slot) >= 128 )
+ return 0;
+ u1_t intv = 1<<rxsched->intvExp;
+ if( (rxsched->slot = (slot += (intv))) >= 128 )
+ return 0;
+ rxsched->rxtime = rxsched->rxbase
+ + ((BCN_WINDOW_osticks * (ostime_t)slot) >> BCN_INTV_exp)
+ - calcRxWindow(/*secs BCN_RESERVE*/2+slot+intv,rxsched->dr);
+ rxsched->rxsyms = LMIC.rxsyms;
+ goto again;
+}
+
+
+static ostime_t rndDelay (u1_t secSpan) {
+ u2_t r = os_getRndU2();
+ ostime_t delay = r;
+ if( delay > OSTICKS_PER_SEC )
+ delay = r % (u2_t)OSTICKS_PER_SEC;
+ if( secSpan > 0 )
+ delay += ((u1_t)r % secSpan) * OSTICKS_PER_SEC;
+ return delay;
+}
+
+
+static void txDelay (ostime_t reftime, u1_t secSpan) {
+ reftime += rndDelay(secSpan);
+ if( LMIC.globalDutyRate == 0 || (reftime - LMIC.globalDutyAvail) > 0 ) {
+ LMIC.globalDutyAvail = reftime;
+ LMIC.opmode |= OP_RNDTX;
+ }
+}
+
+
+static void setDrJoin (u1_t reason, u1_t dr) {
+ EV(drChange, INFO, (e_.reason = reason,
+ e_.deveui = MAIN::CDEV->getEui(),
+ e_.dr = dr|DR_PAGE,
+ e_.txpow = LMIC.adrTxPow,
+ e_.prevdr = LMIC.datarate|DR_PAGE,
+ e_.prevtxpow = LMIC.adrTxPow));
+ LMIC.datarate = dr;
+ DO_DEVDB(updateDatarate, dr);
+}
+
+
+static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) {
+ EV(drChange, INFO, (e_.reason = reason,
+ e_.deveui = MAIN::CDEV->getEui(),
+ e_.dr = dr|DR_PAGE,
+ e_.txpow = pow,
+ e_.prevdr = LMIC.datarate|DR_PAGE,
+ e_.prevtxpow = LMIC.adrTxPow));
+ if( pow != KEEP_TXPOW )
+ LMIC.adrTxPow = pow;
+ LMIC.datarate = dr;
+ DO_DEVDB(updateDatarate, dr);
+ LMIC.opmode |= OP_NEXTCHNL;
+}
+
+
+void LMIC_stopPingable (void) {
+ LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI);
+}
+
+
+void LMIC_setPingable (u1_t intvExp) {
+ // Change setting
+ LMIC.ping.intvExp = (intvExp & 0x7);
+ LMIC.opmode |= OP_PINGABLE;
+ // App may call LMIC_enableTracking() explicitely before
+ // Otherwise tracking is implicitly enabled here
+ if( (LMIC.opmode & (OP_TRACK|OP_SCAN)) == 0 && LMIC.bcninfoTries == 0 )
+ LMIC_enableTracking(0);
+}
+
+
+#if CFG_eu868
+// ================================================================================
+//
+// BEG: EU868 related stuff
+//
+enum { BAND_MILLI=0, BAND_CENTI=1, BAND_DECI=2 };
+static const u4_t iniChannelFreq[12] = {
+ // Join frequencies and duty cycle limit (0.1%)
+ EU868_F1|BAND_MILLI, EU868_F2|BAND_MILLI, EU868_F3|BAND_MILLI,
+ EU868_J4|BAND_MILLI, EU868_J5|BAND_MILLI, EU868_J6|BAND_MILLI,
+ // Default operational frequencies
+ EU868_F1|BAND_CENTI, EU868_F2|BAND_CENTI, EU868_F3|BAND_CENTI,
+ EU868_F4|BAND_MILLI, EU868_F5|BAND_MILLI, EU868_F6|BAND_DECI
+};
+
+static void initDefaultChannels (bit_t join) {
+ LMIC.channelMap = 0x3F;
+ u1_t su = join ? 0 : 6;
+ for( u1_t fu=0; fu<6; fu++,su++ ) {
+ LMIC.channelFreq[fu] = iniChannelFreq[su];
+ LMIC.channelDrs[fu] = DR_SF12 | (DR_SF7<<4);
+ }
+ if( !join )
+ LMIC.channelDrs[1] = DR_SF12 | (DR_FSK<<4);
+
+ LMIC.bands[BAND_MILLI].txcap = 1000; // 0.1%
+ LMIC.bands[BAND_MILLI].txpow = 14;
+ LMIC.bands[BAND_CENTI].txcap = 100; // 1%
+ LMIC.bands[BAND_CENTI].txpow = 14;
+ LMIC.bands[BAND_DECI ].txcap = 10; // 10%
+ LMIC.bands[BAND_DECI ].txpow = 27;
+ LMIC.bands[BAND_MILLI].avail =
+ LMIC.bands[BAND_CENTI].avail =
+ LMIC.bands[BAND_DECI ].avail = os_getTime();
+}
+
+static u4_t convFreq (xref2u1_t ptr) {
+ u4_t freq = (os_rlsbf4(ptr-1) >> 8) * 100;
+ if( freq >= EU868_FREQ_MIN && freq <= EU868_FREQ_MAX )
+ freq = 0;
+ return freq;
+}
+
+static bit_t setupChannel (u1_t chidx, u4_t freq, int drs) {
+ if( chidx >= MAX_CHANNELS )
+ return 0;
+ if( freq >= 869400000 && freq <= 869650000 )
+ freq |= BAND_DECI; // 10% 27dBm
+ else if( (freq >= 868000000 && freq <= 868600000) ||
+ (freq >= 869700000 && freq <= 870000000) )
+ freq |= BAND_CENTI; // 1% 14dBm
+ //else
+ // freq |= BAND_MILLI; // 0.1% 14dBm
+ LMIC.channelFreq[chidx] = freq;
+ LMIC.channelDrs [chidx] = drs < 0 ? (DR_SF12|(DR_SF7<<4)) : drs;
+ return 1;
+}
+
+static u1_t mapChannels (u1_t chpage, u2_t chmap) {
+ if( chpage != 0 || (chmap & ~((u2_t)(1<<MAX_CHANNELS)-1)) != 0 )
+ return 0; // illegal input
+ for( u1_t chnl=0; chnl<MAX_CHANNELS; chnl++ ) {
+ if( (chmap & (1<<chnl)) != 0 && LMIC.channelFreq[chnl] == 0 )
+ chmap &= ~(1<<chnl); // ignore - channel is not defined
+ }
+ LMIC.channelMap = chmap;
+ return 1;
+}
+
+
+static void updateTx (ostime_t txbeg) {
+ u4_t freq = LMIC.channelFreq[LMIC.txChnl];
+ // Update global/band specific duty cycle stats
+ ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
+ // Update channel/global duty cycle stats
+ xref2band_t band = &LMIC.bands[freq & 0x3];
+ LMIC.freq = freq & ~(u4_t)3;
+ LMIC.txpow = band->txpow;
+ band->avail = txbeg + airtime * band->txcap;
+ // Update obsolete avail's to prevent rollover
+ // (needed for devices that send rarely - e.g. once/twice a day)
+ for( u1_t b=0; b<MAX_BANDS; b++ ) {
+ if( LMIC.bands[b].avail - txbeg < 0 )
+ LMIC.bands[b].avail = txbeg;
+ }
+ if( LMIC.globalDutyRate != 0 )
+ LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate);
+}
+
+static ostime_t nextTx (ostime_t now) {
+ // If we do not find a suitable channel stop sending (misconfigured device)
+ ostime_t mintime = now + /*10h*/36000*OSTICKS_PER_SEC;
+ s1_t bmask = 0;
+ for( u1_t b=0; b<MAX_BANDS; b++ ) {
+ xref2band_t band = &LMIC.bands[b];
+ if( band->txcap == 0 ) // band not setup
+ continue;
+ if( now - band->avail >= 0 ) {
+ bmask = (bmask < 0 ? 0 : bmask) | (1<<b);
+ }
+ else if( mintime - band->avail > 0 ) {
+ mintime = band->avail;
+ }
+ }
+ if( bmask == 0 )
+ return mintime;
+ u1_t chnl, ccnt;
+ u2_t cset, mask;
+ chnl = cset = ccnt = 0;
+ mask = 1;
+ while( mask && mask <= LMIC.channelMap ) {
+ // Channel is enabled AND and in a ready band and can handle the datarate
+ if( (mask & LMIC.channelMap) != 0 && (bmask & (1 << (LMIC.channelFreq[chnl] & 0x3))) != 0) {
+ u1_t drs = LMIC.channelDrs[chnl];
+ if( !(isSlowerDR((dr_t)LMIC.datarate, (dr_t)(drs & 0xF)) || // lowest datarate
+ isFasterDR((dr_t)LMIC.datarate, (dr_t)(drs >> 4))) ) { // fastest datarate
+ cset |= mask;
+ ccnt++;
+ }
+ }
+ mask <<= 1;
+ chnl++;
+ }
+ if( ccnt == 0 ) // No eligible channel - misconfigured device?
+ return mintime;
+ ccnt = os_getRndU2() % ccnt;
+ mask = 1;
+ chnl = 0;
+ do {
+ if( (cset & mask) != 0 ) {
+ if( ccnt==0 ) {
+ LMIC.txChnl = chnl;
+ return now;
+ }
+ ccnt--;
+ }
+ mask <<= 1;
+ chnl++;
+ } while(1);
+}
+
+static void setBcnRxParams (void) {
+ LMIC.dataLen = 0;
+ LMIC.freq = LMIC.channelFreq[LMIC.bcnChnl] & ~(u4_t)3;
+ LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN);
+}
+
+#define setRx1Params() /*LMIC.freq/rps remain unchanged*/
+
+static void initJoinLoop (void) {
+ LMIC.txChnl = os_getRndU1() % 3;
+ LMIC.adrTxPow = 14;
+ setDrJoin(DRCHG_SET, DR_SF7);
+ initDefaultChannels(1);
+ ASSERT((LMIC.opmode & OP_NEXTCHNL)==0);
+ LMIC.txend = LMIC.bands[BAND_MILLI].avail;
+}
+
+
+static ostime_t nextJoinState (void) {
+ // Try 869.x and then 864.x with same DR
+ // If both fail try next lower datarate
+ LMIC.opmode &= ~OP_NEXTCHNL;
+ LMIC.txend = LMIC.bands[BAND_MILLI].avail;
+ if( LMIC.txChnl < 3 ) {
+ LMIC.txChnl += 3;
+ } else {
+ if( LMIC.datarate == DR_SF12 ) {
+ setDrJoin(DRCHG_NOJACC, DR_SF7);
+ // We tried one round of FSK..SF12
+ // Now pick new random channel and insert a random delay and try another round.
+ LMIC.txChnl = os_getRndU1() % 3;
+ if( LMIC.txCnt < 5 )
+ LMIC.txCnt += 1;
+ // Delay by 7,15,31,63,127,255 secs
+ return rndDelay((4<<LMIC.txCnt)-1) | 1; // |1 - trigger EV_JOIN_FAILED event
+ }
+ LMIC.txChnl = LMIC.txChnl==5 ? 0 : LMIC.txChnl-2;
+ setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
+ }
+ // Avoid collision with JOIN ACCEPT being sent by GW (but we missed it)
+ return 3*OSTICKS_PER_SEC;
+}
+
+//
+// END: EU868 related stuff
+//
+// ================================================================================
+#elif CFG_us915
+// ================================================================================
+//
+// BEG: US915 related stuff
+//
+
+static void initDefaultChannels (void) {
+ for( u1_t i=0; i<4; i++ )
+ LMIC.channelMap[i] = 0xFFFF;
+ LMIC.channelMap[4] = 0x00FF;
+}
+
+static u4_t convFreq (xref2u1_t ptr) {
+ u4_t freq = (os_rlsbf4(ptr-1) >> 8) * 100;
+ if( freq >= US915_FREQ_MIN && freq <= US915_FREQ_MAX )
+ freq = 0;
+ return freq;
+}
+
+static bit_t setupChannel (u1_t chidx, u4_t freq, int drs) {
+ if( chidx < 72 || chidx >= 72+MAX_XCHANNELS )
+ return 0; // channels 0..71 are hardwired
+ chidx -= 72;
+ LMIC.xchFreq[chidx] = freq;
+ LMIC.xchDrs[chidx] = drs < 0 ? (DR_SF10|(DR_SF8C<<4)) : drs;
+ LMIC.channelMap[chidx>>4] |= (1<<(chidx&0xF));
+ return 1;
+}
+
+
+static u1_t mapChannels (u1_t chpage, u2_t chmap) {
+ if( chpage == MCMD_LADR_CHP_125ON || chpage == MCMD_LADR_CHP_125OFF ) {
+ u2_t en125 = chpage == MCMD_LADR_CHP_125ON ? 0xFFFF : 0x0000;
+ for( u1_t u=0; u<4; u++ )
+ LMIC.channelMap[u] = en125;
+ LMIC.channelMap[64/16] = chmap;
+ } else {
+ if( chpage >= (72+MAX_XCHANNELS+15)/16 )
+ return 0;
+ LMIC.channelMap[chpage] = chmap;
+ }
+ return 1;
+}
+
+static void updateTx (ostime_t txbeg) {
+ u1_t chnl = LMIC.txChnl;
+ if( chnl < 64 ) {
+ LMIC.freq = US915_125kHz_UPFBASE + chnl*US915_125kHz_UPFSTEP;
+ LMIC.txpow = 30;
+ return;
+ }
+ LMIC.txpow = 26;
+ if( chnl < 64+8 ) {
+ LMIC.freq = US915_500kHz_UPFBASE + (chnl-64)*US915_500kHz_UPFSTEP;
+ } else {
+ ASSERT(chnl < 64+8+MAX_XCHANNELS);
+ LMIC.freq = LMIC.xchFreq[chnl-72];
+ }
+
+ // Update global duty cycle stats
+ if( LMIC.globalDutyRate != 0 ) {
+ ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
+ LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate);
+ }
+}
+
+// US does not have duty cycling - return now as earliest TX time
+#define nextTx(now) (_nextTx(),(now))
+static void _nextTx (void) {
+ u1_t chnl = LMIC.txChnl;
+ u1_t cnt = 0;
+ bit_t bw500 = (LMIC.datarate >= DR_SF8C);
+
+ if( (LMIC.chRnd & 0xF) == 0 ) {
+ chnl += LMIC.chRnd>>4;
+ LMIC.chRnd = os_getRndU1();
+ }
+ LMIC.chRnd--;
+ again:
+ chnl += 1;
+ if( bw500 ) {
+ // Only channels 64..71
+ chnl = 64+(chnl&0x7);
+ // At least one 500kHz channel must be enabled
+ // - otherwise MAC should not have used such a datarate
+ ASSERT((LMIC.channelMap[64/16]&0xFF)!=0x00);
+ if( ++cnt == 8 )
+ return; // no appropriate channel enabled stay where we are
+ } else {
+ // At least one 125kHz channel must be enabled
+ // - otherwise MAC should not have used DR_SF10-7
+ ASSERT((LMIC.channelMap[0]|LMIC.channelMap[1]|LMIC.channelMap[2]|LMIC.channelMap[3])!=0x0000);
+ chnl = chnl&0x3F;
+ if( ++cnt == 64 )
+ return; // no appropriate channel enabled stay where we are
+ }
+ if( (LMIC.channelMap[(chnl >> 4)] & (1<<(chnl&0xF))) == 0 )
+ goto again; // not enabled
+ LMIC.txChnl = chnl;
+}
+
+static void setBcnRxParams (void) {
+ LMIC.dataLen = 0;
+ LMIC.freq = US915_500kHz_DNFBASE + LMIC.bcnChnl*US915_500kHz_DNFSTEP;
+ LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN);
+}
+
+#define setRx1Params(void) { \
+ LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; \
+ if( /* TX datarate */LMIC.rxsyms < DR_SF8C ) \
+ LMIC.rxsyms += DR_SF10CR - DR_SF10; \
+ else if( LMIC.rxsyms == DR_SF8C ) \
+ LMIC.rxsyms = DR_SF7CR; \
+ LMIC.rps = dndr2rps(LMIC.rxsyms); \
+}
+
+static void initJoinLoop (void) {
+ LMIC.txChnl = os_getRndU1() & 0x3F;
+ LMIC.adrTxPow = 20;
+ ASSERT((LMIC.opmode & OP_NEXTCHNL)==0);
+ LMIC.txend = os_getTime();
+ setDrJoin(DRCHG_SET, DR_SF7);
+}
+
+static ostime_t nextJoinState (void) {
+ // Try the following:
+ // SF7/8/9/10 on a random channel 0..63
+ // SF8C on a random channel 64..71
+ //
+ LMIC.opmode &= ~OP_NEXTCHNL;
+ LMIC.txend = os_getTime();
+ if( LMIC.datarate != DR_SF8C ) {
+ LMIC.txChnl = 64+(LMIC.txChnl&7);
+ LMIC.datarate = DR_SF8C;
+ } else {
+ LMIC.txChnl = os_getRndU1() & 0x3F;
+ LMIC.datarate = DR_SF7 - (LMIC.txCnt++ & 3);
+ if( (LMIC.txCnt & 3) == 0 ) {
+ // Tried SF10/SF8C
+ // Delay by 7,15,31,63,127,255 secs
+ return rndDelay((4<<(LMIC.txCnt>>2))-1) | 1; // |1 - trigger EV_JOIN_FAILED event
+ }
+ }
+ // Always wait 500ms in case there was a TX and we just missed it.
+ // Sending immediately would not be picked up because GW is still busy txing missed frame.
+ return (OSTICKS_PER_SEC/2) & ~1;
+}
+
+//
+// END: US915 related stuff
+//
+// ================================================================================
+#else
+#error Unsupported frequency band!
+#endif
+
+
+static void runEngineUpdate (xref2osjob_t osjob) {
+ engineUpdate();
+}
+
+
+static void reportEvent (ev_t ev) {
+ EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = ev));
+ ON_LMIC_EVENT(ev);
+ engineUpdate();
+}
+
+
+static void stateJustJoined (void) {
+ LMIC.seqnoDn = LMIC.seqnoUp = 0;
+ LMIC.rejoinCnt = 0;
+ LMIC.dnConf = LMIC.adrChanged = LMIC.ladrAns = LMIC.devsAns = 0;
+ LMIC.moreData = LMIC.dn2Ans = LMIC.snchAns = LMIC.dutyCapAns = 0;
+ LMIC.pingSetAns = 0;
+ LMIC.upRepeat = 0;
+ LMIC.adrAckReq = LINK_CHECK_INIT;
+ LMIC.dn2Dr = DR_DNW2;
+ LMIC.dn2Freq = FREQ_DNW2;
+ LMIC.bcnChnl = CHNL_BCN;
+ LMIC.ping.freq = FREQ_PING;
+ LMIC.ping.dr = DR_PING;
+}
+
+
+// ================================================================================
+// Decoding frames
+
+
+// Decode beacon - do not overwrite bcninfo unless we have a match!
+static int decodeBeacon (void) {
+ ASSERT(LMIC.dataLen == LEN_BCN); // implicit header RX guarantees this
+ xref2u1_t d = LMIC.frame;
+ if( os_rlsbf2(&d[OFF_BCN_CRC1]) != os_crc16(d, OFF_BCN_CRC1) )
+ return 0; // first (common) part fails CRC check
+ // First set of fields is ok
+ u4_t bcnnetid = os_rlsbf4(&d[OFF_BCN_NETID]) & 0xFFFFFF;
+ if( bcnnetid != LMIC.netid )
+ return -1; // not the beacon we're looking for
+
+ LMIC.bcninfo.flags &= ~(BCN_PARTIAL|BCN_FULL);
+ // Match - update bcninfo structure
+ os_copyMem((xref2u1_t)&LMIC.bcninfo.rxq, (xref2u1_t)&LMIC.rxq, SIZEOFEXPR(LMIC.rxq));
+ LMIC.bcninfo.txtime = LMIC.rxtime - AIRTIME_BCN_osticks;
+ LMIC.bcninfo.time = os_rlsbf4(&d[OFF_BCN_TIME]);
+ LMIC.bcninfo.flags |= BCN_PARTIAL;
+
+ // Check 2nd set
+ if( os_rlsbf2(&d[OFF_BCN_CRC2]) != os_crc16(d,OFF_BCN_CRC2) )
+ return 1;
+ // Second set of fields is ok
+ LMIC.bcninfo.lat = (s4_t)os_rlsbf4(&d[OFF_BCN_LAT-1]) >> 8; // read as signed 24-bit
+ LMIC.bcninfo.lon = (s4_t)os_rlsbf4(&d[OFF_BCN_LON-1]) >> 8; // ditto
+ LMIC.bcninfo.info = d[OFF_BCN_INFO];
+ LMIC.bcninfo.flags |= BCN_FULL;
+ return 2;
+}
+
+
+static bit_t decodeFrame (void) {
+ xref2u1_t d = LMIC.frame;
+ u1_t hdr = d[0];
+ u1_t ftype = hdr & HDR_FTYPE;
+ int dlen = LMIC.dataLen;
+ if( dlen < OFF_DAT_OPTS+4 ||
+ (hdr & HDR_MAJOR) != HDR_MAJOR_V1 ||
+ (ftype != HDR_FTYPE_DADN && ftype != HDR_FTYPE_DCDN) ) {
+ // Basic sanity checks failed
+ EV(specCond, WARN, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = dlen < 4 ? 0 : os_rlsbf4(&d[dlen-4]),
+ e_.info2 = hdr + (dlen<<8)));
+ norx:
+ LMIC.dataLen = 0;
+ return 0;
+ }
+ // Validate exact frame length
+ // Note: device address was already read+evaluated in order to arrive here.
+ int fct = d[OFF_DAT_FCT];
+ u4_t addr = os_rlsbf4(&d[OFF_DAT_ADDR]);
+ u4_t seqno = os_rlsbf2(&d[OFF_DAT_SEQNO]);
+ int olen = fct & FCT_OPTLEN;
+ int ackup = (fct & FCT_ACK) != 0 ? 1 : 0; // ACK last up frame
+ int poff = OFF_DAT_OPTS+olen;
+ int pend = dlen-4; // MIC
+
+ if( addr != LMIC.devaddr ) {
+ EV(specCond, WARN, (e_.reason = EV::specCond_t::ALIEN_ADDRESS,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = addr,
+ e_.info2 = LMIC.devaddr));
+ goto norx;
+ }
+ if( poff > pend ) {
+ EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = 0x1000000 + (poff-pend) + (fct<<8) + (dlen<<16)));
+ goto norx;
+ }
+
+ int port = -1;
+ int replayConf = 0;
+
+ if( pend > poff )
+ port = d[poff++];
+
+ seqno = LMIC.seqnoDn + (u2_t)(seqno - LMIC.seqnoDn);
+
+ if( !aes_verifyMic(LMIC.nwkKey, LMIC.devaddr, seqno, /*dn*/1, d, pend) ) {
+ EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_MIC,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = Base::lsbf4(&d[pend]),
+ e_.info2 = seqno));
+ goto norx;
+ }
+ if( seqno < LMIC.seqnoDn ) {
+ if( (s4_t)seqno > (s4_t)LMIC.seqnoDn ) {
+ EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = LMIC.seqnoDn,
+ e_.info2 = seqno));
+ goto norx;
+ }
+ if( seqno != LMIC.seqnoDn-1 || !LMIC.dnConf || ftype != HDR_FTYPE_DCDN ) {
+ EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_OBSOLETE,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = LMIC.seqnoDn,
+ e_.info2 = seqno));
+ goto norx;
+ }
+ // Replay of previous sequence number allowed only if
+ // previous frame and repeated both requested confirmation
+ replayConf = 1;
+ }
+ else {
+ if( seqno > LMIC.seqnoDn ) {
+ EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_SKIP,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = LMIC.seqnoDn,
+ e_.info2 = seqno));
+ }
+ LMIC.seqnoDn = seqno+1; // next number to be expected
+ DO_DEVDB(updateSeqnoDn, LMIC.seqnoDn);
+ // DN frame requested confirmation - provide ACK once with next UP frame
+ LMIC.dnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0);
+ }
+
+ if( LMIC.dnConf || (fct & FCT_MORE) )
+ LMIC.opmode |= OP_POLL;
+
+ // We heard from network
+ LMIC.adrChanged = 0;
+ LMIC.adrAckReq = LINK_CHECK_INIT;
+
+ // Process OPTS
+ int m = LMIC.rxq.rssi - RSSI_OFF - getSensitivity(LMIC.rps);
+ LMIC.margin = m < 0 ? 0 : m > 254 ? 254 : m;
+
+ xref2u1_t opts = &d[OFF_DAT_OPTS];
+ int oidx = 0;
+ while( oidx < olen ) {
+ switch( opts[oidx] ) {
+ case MCMD_LCHK_ANS: {
+ //int gwmargin = opts[oidx+1];
+ //int ngws = opts[oidx+2];
+ oidx += 3;
+ continue;
+ }
+ case MCMD_LADR_REQ: {
+ u1_t p1 = opts[oidx+1]; // txpow + DR
+ u2_t chmap = os_rlsbf2(&opts[oidx+2]);// list of enabled channel
+ u1_t chpage = opts[oidx+4] & MCMD_LADR_CHPAGE_MASK; // channel page
+ u1_t uprpt = opts[oidx+4] & MCMD_LADR_REPEAT_MASK; // up repeat count
+ oidx += 5;
+
+ LMIC.ladrAns = 0x80 | // Include an answer into next frame up
+ MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK;
+ if( !mapChannels(chpage, chmap) )
+ LMIC.ladrAns &= ~MCMD_LADR_ANS_CHACK;
+ dr_t dr = (dr_t)(p1>>MCMD_LADR_DR_SHIFT);
+ if( !validDR(dr) ) {
+ LMIC.ladrAns &= ~MCMD_LADR_ANS_DRACK;
+ dr = (dr_t)LMIC.datarate;
+ EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = Base::lsbf4(&d[pend]),
+ e_.info2 = Base::msbf4(&opts[oidx-4])));
+ }
+ LMIC.upRepeat = uprpt;
+ setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1));
+ LMIC.adrChanged = 1; // Trigger an ACK to NWK
+ continue;
+ }
+ case MCMD_DEVS_REQ: {
+ LMIC.devsAns = 1;
+ oidx += 1;
+ continue;
+ }
+ case MCMD_DN2P_SET: {
+ dr_t dr = (dr_t)(opts[oidx+1] & 0x0F);
+ u4_t freq = convFreq(&opts[oidx+2]);
+ oidx += 5;
+ LMIC.dn2Ans = 0x80; // answer pending
+ if( validDR(dr) )
+ LMIC.dn2Ans |= MCMD_DN2P_ANS_DRACK;
+ if( freq != 0 )
+ LMIC.dn2Ans |= MCMD_DN2P_ANS_CHACK;
+ if( LMIC.dn2Ans == (0x80|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK) ) {
+ LMIC.dn2Dr = dr;
+ LMIC.dn2Freq = freq;
+ DO_DEVDB(updateDn2,LMIC.dn2Dr,LMIC.dn2Freq);
+ }
+ continue;
+ }
+ case MCMD_DCAP_REQ: {
+ u1_t cap = opts[oidx+1];
+ oidx += 2;
+ // A value cap=0xFF means device is OFF unless enabled again manually
+ // We just set duty cap to 0xF which is 0.003% -- pretty much off.
+ // We don't check 0xF0 bits if cap!=0xFF
+ LMIC.globalDutyRate = cap & 0xF;
+ LMIC.globalDutyAvail = os_getTime();
+ DO_DEVDB(updateDutyCap,cap);
+ LMIC.dutyCapAns = 1;
+ continue;
+ }
+ case MCMD_SNCH_REQ: {
+ u1_t chidx = opts[oidx+1]; // channel
+ u4_t freq = convFreq(&opts[oidx+2]); // freq
+ u1_t drs = opts[oidx+5]; // datarate span
+ LMIC.snchAns = 0x80;
+ if( freq != 0 && setupChannel(chidx,freq,drs) )
+ LMIC.snchAns |= MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK;
+ oidx += 6;
+ }
+ case MCMD_PING_SET: {
+ u4_t freq = convFreq(&opts[oidx+1]);
+ oidx += 4;
+ u1_t flags = 0x80;
+ if( freq != 0 ) {
+ flags |= MCMD_PING_ANS_FQACK;
+ LMIC.ping.freq = freq;
+ DO_DEVDB(updateClassB,LMIC.ping.intvExp,freq,LMIC.ping.dr);
+ }
+ LMIC.pingSetAns = flags;
+ continue;
+ }
+ case MCMD_BCNI_ANS: {
+ // Ignore if tracking already enabled
+ if( (LMIC.opmode & OP_TRACK) == 0 ) {
+ LMIC.bcnChnl = opts[oidx+3];
+ // Enable tracking - bcninfoTries
+ LMIC.opmode |= OP_TRACK;
+ // Cleared later in txComplete handling - triggers EV_BEACON_FOUND
+ ASSERT(LMIC.bcninfoTries!=0);
+ // Setup RX parameters
+ LMIC.bcninfo.txtime = (LMIC.rxtime
+ - calcAirTime(LMIC.rps, LMIC.dataLen)
+ + ms2osticks(os_rlsbf2(&opts[oidx+1]) * 10)
+ + ms2osticksCeil(5)
+ - BCN_INTV_osticks);
+ LMIC.bcninfo.flags = 0; // txtime above cannot be used as reference (BCN_PARTIAL|BCN_FULL cleared)
+ calcBcnRxWindowFromMillis(10,1); // error of +/-5 ms
+
+ EV(lostFrame, INFO, (e_.reason = EV::lostFrame_t::MCMD_BCNI_ANS,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.lostmic = Base::lsbf4(&d[pend]),
+ e_.info = (LMIC.missedBcns |
+ (osticks2us(LMIC.bcninfo.txtime + BCN_INTV_osticks
+ - LMIC.bcnRxtime) << 8)),
+ e_.time = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks)));
+ }
+ oidx += 4;
+ continue;
+ }
+ }
+ EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = Base::lsbf4(&d[pend]),
+ e_.info2 = Base::msbf4(&opts[oidx])));
+ break;
+ }
+ if( oidx != olen ) {
+ EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = 0x1000000 + (oidx) + (olen<<8)));
+ }
+
+ if( !replayConf ) {
+ // Handle payload only if not a replay
+ // Decrypt payload - if any
+ if( port >= 0 && pend-poff > 0 )
+ aes_cipher(port <= 0 ? LMIC.nwkKey : LMIC.artKey, LMIC.devaddr, seqno, /*dn*/1, d+poff, pend-poff);
+
+ EV(dfinfo, DEBUG, (e_.deveui = MAIN::CDEV->getEui(),
+ e_.devaddr = LMIC.devaddr,
+ e_.seqno = seqno,
+ e_.flags = (port < 0 ? EV::dfinfo_t::NOPORT : 0) | EV::dfinfo_t::DN,
+ e_.mic = Base::lsbf4(&d[pend]),
+ e_.hdr = d[LORA::OFF_DAT_HDR],
+ e_.fct = d[LORA::OFF_DAT_FCT],
+ e_.port = port,
+ e_.plen = dlen,
+ e_.olen = olen,
+ memcpy(e_.opts, opts, olen)));
+ } else {
+ EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_REPLAY,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = Base::lsbf4(&d[pend]),
+ e_.info2 = seqno));
+ }
+
+ if( // NWK acks but we don't have a frame pending
+ (ackup && LMIC.txCnt == 0) ||
+ // We sent up confirmed and we got a response in DNW1/DNW2
+ // BUT it did not carry an ACK - this should never happen
+ // Do not resend and assume frame was not ACKed.
+ (!ackup && LMIC.txCnt != 0) ) {
+ EV(specCond, ERR, (e_.reason = EV::specCond_t::SPURIOUS_ACK,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = seqno,
+ e_.info2 = ackup));
+ }
+
+ if( LMIC.txCnt != 0 ) // we requested an ACK
+ LMIC.txrxFlags |= ackup ? TXRX_ACK : TXRX_NACK;
+
+ if( port < 0 ) {
+ LMIC.txrxFlags |= TXRX_NOPORT;
+ LMIC.dataBeg = LMIC.dataLen = 0;
+ } else {
+ LMIC.dataBeg = poff;
+ LMIC.dataLen = pend-poff;
+ }
+ return 1;
+}
+
+
+// ================================================================================
+// TX/RX transaction support
+
+
+static void setupRx2 (void) {
+ LMIC.txrxFlags = TXRX_DNW2;
+ LMIC.rps = dndr2rps(LMIC.dn2Dr);
+ LMIC.freq = LMIC.dn2Freq;
+ LMIC.dataLen = 0;
+ os_radio(RADIO_RX);
+}
+
+
+static void schedRx2 (ostime_t delay, osjobcb_t func) {
+ // Add 1.5 symbols we need 5 out of 8. Try to sync 1.5 symbols into the preamble.
+ LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(LMIC.dn2Dr);
+ os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
+}
+
+static void setupRx1 (osjobcb_t func) {
+ LMIC.txrxFlags = TXRX_DNW1;
+ // Turn LMIC.rps from TX over to RX
+ LMIC.rps = setNocrc(LMIC.rps,1);
+ LMIC.dataLen = 0;
+ LMIC.osjob.func = func;
+ os_radio(RADIO_RX);
+}
+
+
+// Called by HAL once TX complete and delivers exact end of TX time stamp in LMIC.rxtime
+static void txDone (ostime_t delay, osjobcb_t func) {
+ // NOTE: LMIC.rxsyms carries the modified DR for TX (LMIC.datarate is the base DR - [CONFIRM mode])
+ u1_t dr = LMIC.rxsyms; // rxschedInit - would overwrite rxsyms
+ 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;
+ }
+ // Change RX frequency / rps (US only) before we increment txChnl
+ setRx1Params();
+ // LMIC.rxsyms carries the TX datarate (can be != LMIC.datarate [confirm retries etc.])
+ // Setup receive - LMIC.rxtime is preloaded with 1.5 symbols offset to tune
+ // into the middle of the 8 symbols preamble.
+#if CFG_eu868
+ if( /* TX datarate */LMIC.rxsyms == DR_FSK ) {
+ LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160);
+ LMIC.rxsyms = RXLEN_FSK;
+ }
+ else
+#endif
+ {
+ LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(dr);
+ LMIC.rxsyms = MINRX_SYMS;
+ }
+ os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
+}
+
+
+// ======================================== Join frames
+
+
+static void onJoinFailed (xref2osjob_t osjob) {
+ // Notify app - must call LMIC_reset() to stop joining
+ // otherwise join procedure continues.
+ reportEvent(EV_JOIN_FAILED);
+}
+
+
+static bit_t processJoinAccept (void) {
+ ASSERT(LMIC.txrxFlags != TXRX_DNW1 || LMIC.dataLen != 0);
+ ASSERT((LMIC.opmode & OP_TXRXPEND)!=0);
+
+ if( LMIC.dataLen == 0 ) {
+ nojoinframe:
+ if( (LMIC.opmode & OP_OTA) != 0 ) {
+ if( (LMIC.opmode & OP_JOINING) == 0 ) {
+ ASSERT((LMIC.opmode & OP_REJOIN) != 0);
+ // REJOIN attempt for roaming
+ LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND);
+ if( LMIC.rejoinCnt < 10 )
+ LMIC.rejoinCnt++;
+ reportEvent(EV_REJOIN_FAILED);
+ return 1;
+ }
+ }
+ LMIC.opmode &= ~OP_TXRXPEND;
+ ostime_t delay = nextJoinState();
+ EV(devCond, DEBUG, (e_.reason = EV::devCond_t::NO_JACC,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = LMIC.datarate|DR_PAGE,
+ e_.info2 = osticks2ms(delay)));
+ // Build next JOIN REQUEST with next engineUpdate call
+ // Optionally, report join failed.
+ // Both after a random/chosen amount of ticks.
+ os_setTimedCallback(&LMIC.osjob, os_getTime()+delay,
+ (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];
+ u1_t dlen = LMIC.dataLen;
+ u4_t mic = os_rlsbf4(&LMIC.frame[dlen-4]); // safe before modified by encrypt!
+ if( (dlen != LEN_JA && dlen != LEN_JAEXT)
+ || (hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) {
+ EV(specCond, ERR, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = dlen < 4 ? 0 : mic,
+ e_.info2 = hdr + (dlen<<8)));
+ badframe:
+ goto nojoinframe;
+ }
+ aes_encrypt(LMIC.frame+1, dlen-1);
+ if( !aes_verifyMic0(LMIC.frame, dlen-4) ) {
+ EV(specCond, ERR, (e_.reason = EV::specCond_t::JOIN_BAD_MIC,
+ e_.info = mic));
+ goto badframe;
+ }
+
+ u4_t addr = os_rlsbf4(LMIC.frame+OFF_JA_DEVADDR);
+ LMIC.devaddr = addr;
+ LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_BCN_NETID-1]) >> 8;
+
+#if CFG_eu868
+ initDefaultChannels(0);
+#endif
+ if( dlen > LEN_JA ) {
+ dlen = OFF_CFLIST;
+#if CFG_eu868
+ u1_t chidx=3;
+#elif CFG_us915
+ u1_t chidx=72;
+#endif
+ for( ; chidx<8; chidx++, dlen+=3 )
+ setupChannel(chidx, os_rlsbf4(&LMIC.frame[dlen-1]) >> 8, -1);
+ }
+
+ // already incremented when JOIN REQ got sent off
+ aes_sessKeys(LMIC.devNonce-1, &LMIC.frame[OFF_JA_ARTNONCE], LMIC.nwkKey, LMIC.artKey);
+ DO_DEVDB(updateJoinAcc, LMIC.devaddr, LMIC.nwkKey, LMIC.artKey);
+
+ EV(joininfo, INFO, (e_.arteui = MAIN::CDEV->getArtEui(),
+ e_.deveui = MAIN::CDEV->getEui(),
+ e_.devaddr = LMIC.devaddr,
+ e_.oldaddr = oldaddr,
+ e_.nonce = LMIC.devNonce-1,
+ e_.mic = mic,
+ e_.flags = ((LMIC.opmode & OP_REJOIN) != 0
+ ? EV::joininfo_t::REJOIN_ACCEPT
+ : EV::joininfo_t::ACCEPT)));
+
+ ASSERT((LMIC.opmode & (OP_JOINING|OP_REJOIN))!=0);
+ if( (LMIC.opmode & OP_REJOIN) != 0 ) {
+ LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt);
+ }
+ LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL;
+ stateJustJoined();
+ reportEvent(EV_JOINED);
+ return 1;
+}
+
+static void processRx1Jacc (xref2osjob_t osjob) {
+ if( LMIC.dataLen == 0 )
+ LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot
+ processJoinAccept();
+}
+
+static void setupRx1Jacc (xref2osjob_t osjob) {
+ setupRx1(FUNC_ADDR(processRx1Jacc));
+}
+
+
+static void jreqDone (xref2osjob_t osjob) {
+ txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc));
+}
+
+// ======================================== Data frames
+
+// Fwd decl.
+static bit_t processDnData(void);
+
+static void processRx2DnData (xref2osjob_t osjob) {
+ if( LMIC.dataLen == 0 )
+ LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot
+ processDnData();
+}
+
+
+static void setupRx2DnData (xref2osjob_t osjob) {
+ LMIC.osjob.func = FUNC_ADDR(processRx2DnData);
+ setupRx2();
+}
+
+
+static void processRx1DnData (xref2osjob_t osjob) {
+ if( LMIC.dataLen == 0 || !processDnData() )
+ schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData));
+}
+
+
+static void setupRx1DnData (xref2osjob_t osjob) {
+ setupRx1(FUNC_ADDR(processRx1DnData));
+}
+
+
+static void updataDone (xref2osjob_t osjob) {
+ txDone(DELAY_DNW1_osticks, FUNC_ADDR(setupRx1DnData));
+}
+
+// ========================================
+
+
+static void buildDataFrame (void) {
+ bit_t txdata = ((LMIC.opmode & (OP_TXDATA|OP_POLL)) != OP_POLL);
+ u1_t dlen = txdata ? LMIC.pendTxLen : 0;
+
+ // Piggyback MAC options
+ // Prioritize by importance
+ int end = OFF_DAT_OPTS;
+ if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE)) == (OP_TRACK|OP_PINGABLE) ) {
+ // Indicate pingability in every UP frame
+ LMIC.frame[end] = MCMD_PING_IND;
+ LMIC.frame[end+1] = LMIC.ping.dr | (LMIC.ping.intvExp<<4);
+ end += 2;
+ }
+ if( LMIC.dutyCapAns ) {
+ LMIC.frame[end] = MCMD_DCAP_ANS;
+ end += 1;
+ LMIC.dutyCapAns = 0;
+ }
+ if( LMIC.dn2Ans ) {
+ LMIC.frame[end+0] = MCMD_DN2P_ANS;
+ LMIC.frame[end+1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU;
+ end += 2;
+ LMIC.dn2Ans = 0;
+ }
+ if( LMIC.devsAns ) { // answer to device status
+ LMIC.frame[end+0] = MCMD_DEVS_ANS;
+ LMIC.frame[end+1] = LMIC.margin;
+ LMIC.frame[end+2] = os_getBattLevel();
+ end += 3;
+ LMIC.devsAns = 0;
+ }
+ if( LMIC.ladrAns ) { // answer to ADR change
+ LMIC.frame[end+0] = MCMD_LADR_ANS;
+ LMIC.frame[end+1] = LMIC.ladrAns & ~MCMD_LADR_ANS_RFU;
+ end += 2;
+ LMIC.ladrAns = 0;
+ }
+ if( LMIC.bcninfoTries > 0 ) {
+ LMIC.frame[end] = MCMD_BCNI_REQ;
+ end += 1;
+ }
+ if( LMIC.adrChanged ) {
+ LMIC.adrAckReq = LMIC.adrAckReq < 0 ? 0 : LMIC.adrAckReq;
+ LMIC.adrChanged = 0;
+ }
+ if( LMIC.pingSetAns != 0 ) {
+ LMIC.frame[end+0] = MCMD_PING_ANS;
+ LMIC.frame[end+1] = LMIC.pingSetAns & ~MCMD_PING_ANS_RFU;
+ end += 2;
+ LMIC.pingSetAns = 0;
+ }
+ if( LMIC.snchAns ) {
+ LMIC.frame[end+0] = MCMD_SNCH_ANS;
+ LMIC.frame[end+1] = LMIC.snchAns;
+ end += 2;
+ LMIC.snchAns = 0;
+ }
+ ASSERT(end <= OFF_DAT_OPTS+16);
+
+ u1_t flen = end + (txdata ? 5+dlen : 4);
+ if( flen > MAX_LEN_FRAME ) {
+ // Options and payload too big - delay payload
+ txdata = 0;
+ flen = end+4;
+ }
+ LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DAUP | HDR_MAJOR_V1;
+ LMIC.frame[OFF_DAT_FCT] = (LMIC.dnConf | LMIC.adrEnabled
+ | (LMIC.adrAckReq >= 0 ? FCT_ADRARQ : 0)
+ | (end-OFF_DAT_OPTS));
+ os_wlsbf4(LMIC.frame+OFF_DAT_ADDR, LMIC.devaddr);
+
+ if( LMIC.txCnt == 0 ) {
+ LMIC.seqnoUp += 1;
+ DO_DEVDB(updateSeqnoUp, LMIC.seqnoUp);
+ } else {
+ EV(devCond, INFO, (e_.reason = EV::devCond_t::RE_TX,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = LMIC.seqnoUp-1,
+ e_.info2 = (LMIC.txCnt+1) | (DRADJUST[LMIC.txCnt+1] << 8) | ((LMIC.datarate|DR_PAGE)<<16)));
+ }
+ os_wlsbf2(LMIC.frame+OFF_DAT_SEQNO, LMIC.seqnoUp-1);
+
+ // Clear pending DN confirmation
+ LMIC.dnConf = 0;
+
+ if( txdata ) {
+ if( LMIC.pendTxConf ) {
+ // Confirmed only makes sense if we have a payload (or at least a port)
+ LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DCUP | HDR_MAJOR_V1;
+ LMIC.txCnt += 1;
+ }
+ LMIC.frame[end] = LMIC.pendTxPort;
+ os_copyMem(LMIC.frame+end+1, LMIC.pendTxData, dlen);
+ aes_cipher(LMIC.pendTxPort==0 ? LMIC.nwkKey : LMIC.artKey,
+ LMIC.devaddr, LMIC.seqnoUp-1,
+ /*up*/0, LMIC.frame+end+1, dlen);
+ }
+ aes_appendMic(LMIC.nwkKey, LMIC.devaddr, LMIC.seqnoUp-1, /*up*/0, LMIC.frame, flen-4);
+
+ EV(dfinfo, DEBUG, (e_.deveui = MAIN::CDEV->getEui(),
+ e_.devaddr = LMIC.devaddr,
+ e_.seqno = LMIC.seqnoUp-1,
+ e_.flags = (LMIC.pendTxPort < 0 ? EV::dfinfo_t::NOPORT : EV::dfinfo_t::NOP),
+ e_.mic = Base::lsbf4(&LMIC.frame[flen-4]),
+ e_.hdr = LMIC.frame[LORA::OFF_DAT_HDR],
+ e_.fct = LMIC.frame[LORA::OFF_DAT_FCT],
+ e_.port = LMIC.pendTxPort,
+ e_.plen = txdata ? dlen : 0,
+ e_.olen = end-LORA::OFF_DAT_OPTS,
+ memcpy(e_.opts, LMIC.frame+LORA::OFF_DAT_OPTS, end-LORA::OFF_DAT_OPTS)));
+ LMIC.dataLen = flen;
+}
+
+
+// Callback from HAL during scan mode or when job timer expires.
+static void onBcnRx (xref2osjob_t job) {
+ // If we arrive via job timer make sure to put radio to rest.
+ os_radio(RADIO_RST);
+ os_clearCallback(&LMIC.osjob);
+ if( LMIC.dataLen == 0 ) {
+ // Nothing received - timeout
+ LMIC.opmode &= ~(OP_SCAN | OP_TRACK);
+ reportEvent(EV_SCAN_TIMEOUT);
+ return;
+ }
+ if( decodeBeacon() <= 0 ) {
+ // Something is wrong with the beacon - continue scan
+ LMIC.dataLen = 0;
+ os_radio(RADIO_RXON);
+ os_setTimedCallback(&LMIC.osjob, LMIC.bcninfo.txtime, FUNC_ADDR(onBcnRx));
+ return;
+ }
+ // Found our 1st beacon
+ // We don't have a previous beacon to calc some drift - assume
+ // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm
+ calcBcnRxWindowFromMillis(13,1);
+ LMIC.opmode &= ~OP_SCAN; // turn SCAN off
+ LMIC.opmode |= OP_TRACK; // auto enable tracking
+ reportEvent(EV_BEACON_FOUND); // can be disabled in callback
+}
+
+
+// Enable receiver to listen to incoming beacons
+// netid defines when scan stops (any or specific beacon)
+// This mode ends with events: EV_SCAN_TIMEOUT/EV_SCAN_BEACON
+// Implicitely cancels any pending TX/RX transaction.
+// Also cancels an onpoing joining procedure.
+static void startScan (void) {
+ ASSERT(LMIC.devaddr!=0 && (LMIC.opmode & OP_JOINING)==0);
+ if( (LMIC.opmode & OP_SHUTDOWN) != 0 )
+ return;
+ // Cancel onging TX/RX transaction
+ LMIC.txCnt = LMIC.dnConf = LMIC.bcninfo.flags = 0;
+ LMIC.opmode = (LMIC.opmode | OP_SCAN) & ~(OP_TXRXPEND);
+ setBcnRxParams();
+ LMIC.rxtime = LMIC.bcninfo.txtime = os_getTime() + sec2osticks(BCN_INTV_sec+1);
+ os_setTimedCallback(&LMIC.osjob, LMIC.rxtime, FUNC_ADDR(onBcnRx));
+ os_radio(RADIO_RXON);
+}
+
+
+bit_t LMIC_enableTracking (u1_t tryBcnInfo) {
+ if( (LMIC.opmode & (OP_SCAN|OP_TRACK|OP_SHUTDOWN)) != 0 )
+ return 0; // already in progress or failed to enable
+ // If BCN info requested from NWK then app has to take are
+ // of sending data up so that MCMD_BCNI_REQ can be attached.
+ if( (LMIC.bcninfoTries = tryBcnInfo) == 0 )
+ startScan();
+ return 1; // enabled
+}
+
+
+void LMIC_disableTracking (void) {
+ LMIC.opmode &= ~(OP_SCAN|OP_TRACK);
+ LMIC.bcninfoTries = 0;
+ engineUpdate();
+}
+
+
+// ================================================================================
+//
+// Join stuff
+//
+// ================================================================================
+
+static void buildJoinRequest (u1_t ftype) {
+ // Do not use pendTxData since we might have a pending
+ // user level frame in there. Use RX holding area instead.
+ xref2u1_t d = LMIC.frame;
+ d[OFF_JR_HDR] = ftype;
+ os_getArtEui(d + OFF_JR_ARTEUI);
+ os_getDevEui(d + OFF_JR_DEVEUI);
+ os_wlsbf2(d + OFF_JR_DEVNONCE, LMIC.devNonce);
+ aes_appendMic0(d, OFF_JR_MIC);
+
+ EV(joininfo,INFO,(e_.deveui = MAIN::CDEV->getEui(),
+ e_.arteui = MAIN::CDEV->getArtEui(),
+ e_.nonce = LMIC.devNonce,
+ e_.oldaddr = LMIC.devaddr,
+ e_.mic = Base::lsbf4(&d[LORA::OFF_JR_MIC]),
+ e_.flags = ((LMIC.opmode & OP_REJOIN) != 0
+ ? EV::joininfo_t::REJOIN_REQUEST
+ : EV::joininfo_t::REQUEST)));
+ LMIC.dataLen = LEN_JR;
+ LMIC.devNonce++;
+ DO_DEVDB(updateDevNonce, LMIC.devNonce);
+}
+
+static void startJoining (xref2osjob_t osjob) {
+ reportEvent(EV_JOINING);
+}
+
+// Start join procedure if not already joined.
+bit_t LMIC_startJoining (void) {
+ if( LMIC.devaddr == 0 ) {
+ // There should be no TX/RX going on
+ ASSERT((LMIC.opmode & (OP_POLL|OP_TXRXPEND)) == 0);
+ // Cancel scanning
+ LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL);
+ // Setup state
+ LMIC.rejoinCnt = LMIC.txCnt = 0;
+ initJoinLoop();
+ LMIC.opmode |= OP_OTA|OP_JOINING;
+ // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS
+ os_setCallback(&LMIC.osjob, FUNC_ADDR(startJoining));
+ return 1;
+ }
+ return 0;
+}
+
+
+// ================================================================================
+//
+//
+//
+// ================================================================================
+
+static void processPingRx (xref2osjob_t osjob) {
+ if( LMIC.dataLen != 0 ) {
+ LMIC.txrxFlags = TXRX_PING;
+ if( decodeFrame() ) {
+ reportEvent(EV_RXCOMPLETE);
+ return;
+ }
+ }
+ // Pick next ping slot
+ engineUpdate();
+}
+
+
+static bit_t processDnData (void) {
+ ASSERT((LMIC.opmode & OP_TXRXPEND)!=0);
+
+ if( LMIC.dataLen == 0 ) {
+ norx:
+ if( LMIC.txCnt != 0 ) {
+ if( LMIC.txCnt <= TXCONF_ATTEMPTS ) {
+ // Schedule another retransmission
+ txDelay(LMIC.rxtime, RETRY_PERIOD_secs);
+ LMIC.opmode &= ~OP_TXRXPEND;
+ engineUpdate();
+ return 1;
+ }
+ LMIC.txrxFlags = TXRX_NACK | TXRX_NOPORT;
+ } else {
+ // Nothing received - implies no port
+ LMIC.txrxFlags = TXRX_NOPORT;
+ }
+ LMIC.adrAckReq += 1;
+ LMIC.dataBeg = LMIC.dataLen = 0;
+ txcomplete:
+ LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND);
+ if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0 && (LMIC.opmode & OP_LINKDEAD) != 0 ) {
+ LMIC.opmode &= ~OP_LINKDEAD;
+ reportEvent(EV_LINK_ALIVE);
+ }
+ reportEvent(EV_TXCOMPLETE);
+ // If we haven't heard from NWK in a while although we asked for a sign
+ // assume link is dead - notify application and keep going
+ if( LMIC.adrAckReq > LINK_CHECK_DEAD ) {
+ // We haven't heard from NWK for some time although
+ // We asked for a response for some time - assume we're disconnected. Lower DR one notch.
+ EV(devCond, ERR, (e_.reason = EV::devCond_t::LINK_DEAD,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = LMIC.adrAckReq));
+ setDrTxpow(DRCHG_NOADRACK, decDR((dr_t)LMIC.datarate), KEEP_TXPOW);
+ LMIC.adrAckReq = LINK_CHECK_CONT;
+ if( (LMIC.opmode & OP_OTA) != 0 ) {
+ LMIC.opmode |= OP_REJOIN|OP_LINKDEAD;
+ }
+ else {
+ LMIC.opmode |= OP_LINKDEAD;
+ }
+ reportEvent(EV_LINK_DEAD);
+ }
+ // If this falls to zero the NWK did not answer our MCMD_BCNI_REQ commands - try full scan
+ if( LMIC.bcninfoTries > 0 ) {
+ if( (LMIC.opmode & OP_TRACK) != 0 ) {
+ reportEvent(EV_BEACON_FOUND);
+ LMIC.bcninfoTries = 0;
+ }
+ else if( --LMIC.bcninfoTries == 0 ) {
+ startScan(); // NWK did not answer - try scan
+ }
+ }
+ return 1;
+ }
+ if( !decodeFrame() ) {
+ if( (LMIC.txrxFlags & TXRX_DNW1) != 0 )
+ return 0;
+ goto norx;
+ }
+ // If we received a frame reset counter
+ LMIC.adrAckReq = LINK_CHECK_INIT;
+ LMIC.rejoinCnt = 0;
+ goto txcomplete;
+}
+
+
+static void processBeacon (xref2osjob_t osjob) {
+ ostime_t lasttx = LMIC.bcninfo.txtime; // save here - decodeBeacon might overwrite
+ u1_t flags = LMIC.bcninfo.flags;
+ ev_t ev;
+
+ if( LMIC.dataLen != 0 && decodeBeacon() >= 1 ) {
+ ev = EV_BEACON_TRACKED;
+ if( (flags & (BCN_PARTIAL|BCN_FULL)) == 0 ) {
+ // We don't have a previous beacon to calc some drift - assume
+ // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm
+ calcBcnRxWindowFromMillis(13,0);
+ goto rev;
+ }
+ // We have a previous BEACON to calculate some drift
+ s2_t drift = BCN_INTV_osticks - (LMIC.bcninfo.txtime - lasttx);
+ if( LMIC.missedBcns > 0 ) {
+ drift = LMIC.drift + (drift - LMIC.drift) / (LMIC.missedBcns+1);
+ }
+ if( (LMIC.bcninfo.flags & BCN_NODRIFT) == 0 ) {
+ s2_t diff = LMIC.drift - drift;
+ if( diff < 0 ) diff = -diff;
+ LMIC.lastDriftDiff = diff;
+ if( LMIC.maxDriftDiff < diff )
+ LMIC.maxDriftDiff = diff;
+ LMIC.bcninfo.flags &= ~BCN_NODDIFF;
+ }
+ LMIC.drift = drift;
+ LMIC.missedBcns = LMIC.rejoinCnt = 0;
+ LMIC.bcninfo.flags &= ~BCN_NODRIFT;
+ EV(devCond,INFO,(e_.reason = EV::devCond_t::CLOCK_DRIFT,
+ e_.info = drift,
+ e_.info2 = /*occasion BEACON*/0));
+ ASSERT((LMIC.bcninfo.flags & (BCN_PARTIAL|BCN_FULL)) != 0);
+ } else {
+ ev = EV_BEACON_MISSED;
+ LMIC.bcninfo.txtime += BCN_INTV_osticks - LMIC.drift;
+ LMIC.bcninfo.time += BCN_INTV_sec;
+ LMIC.missedBcns++;
+ // Delay any possible TX after surmised beacon - it's there although we missed it
+ txDelay(LMIC.bcninfo.txtime + BCN_RESERVE_osticks, 4);
+ if( (LMIC.opmode & OP_OTA) != 0 ) {
+ if( LMIC.missedBcns > MAX_MISSED_BCNS )
+ LMIC.opmode |= OP_REJOIN; // try if we can roam to another network
+ }
+ if( LMIC.bcnRxsyms > MAX_RXSYMS ) {
+ LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN);
+ reportEvent(EV_LOST_TSYNC);
+ return;
+ }
+ }
+ LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - calcRxWindow(0,DR_BCN);
+ LMIC.bcnRxsyms = LMIC.rxsyms;
+ rev:
+ if( (LMIC.opmode & OP_PINGINI) != 0 )
+ rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer!
+ reportEvent(ev);
+}
+
+
+static void startRxBcn (xref2osjob_t osjob) {
+ LMIC.osjob.func = FUNC_ADDR(processBeacon);
+ os_radio(RADIO_RX);
+}
+
+
+static void startRxPing (xref2osjob_t osjob) {
+ LMIC.osjob.func = FUNC_ADDR(processPingRx);
+ os_radio(RADIO_RX);
+}
+
+
+// Decide what to do next for the MAC layer of a device
+static void engineUpdate (void) {
+ // Check for ongoing state: scan or TX/RX transaction
+ if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 )
+ return;
+
+ if( LMIC.devaddr == 0 && (LMIC.opmode & OP_JOINING) == 0 ) {
+ LMIC_startJoining();
+ return;
+ }
+
+ ostime_t now = os_getTime();
+ ostime_t rxtime = 0;
+ ostime_t txbeg = 0;
+
+ if( (LMIC.opmode & OP_TRACK) != 0 ) {
+ // We are tracking a beacon
+ ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 );
+ rxtime = LMIC.bcnRxtime - RX_RAMPUP;
+ }
+
+ if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) {
+ // 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);
+ // Find next suitable channel and return availability time
+ if( (LMIC.opmode & OP_NEXTCHNL) != 0 ) {
+ txbeg = LMIC.txend = nextTx(now);
+ LMIC.opmode &= ~OP_NEXTCHNL;
+ } else {
+ txbeg = LMIC.txend;
+ }
+ // Delayed TX or waiting for duty cycle?
+ if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0) && (txbeg - LMIC.globalDutyAvail) < 0 )
+ txbeg = LMIC.globalDutyAvail;
+ // 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 ) {
+ // 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;
+ }
+ // Earliest possible time vs overhead to setup radio
+ if( txbeg - (now + TX_RAMPUP) < 0 ) {
+ // We could send right now!
+ dr_t txdr = (dr_t)LMIC.datarate;
+ if( jacc ) {
+ u1_t ftype;
+ if( (LMIC.opmode & OP_REJOIN) != 0 ) {
+ txdr = lowerDR(txdr, LMIC.rejoinCnt);
+ ftype = HDR_FTYPE_REJOIN;
+ } else {
+ ftype = HDR_FTYPE_JREQ;
+ }
+ buildJoinRequest(ftype);
+ LMIC.osjob.func = FUNC_ADDR(jreqDone);
+ } else {
+ if( LMIC.seqnoUp == 0xFFFFFFFF ) {
+ // Roll over of up seq counter
+ EV(specCond, ERR, (e_.reason = EV::specCond_t::UPSEQNO_ROLL_OVER,
+ e_.eui = MAIN::CDEV->getEui()));
+ // Rerun join procedure - start with current datarate
+ LMIC.devaddr = 0;
+ LMIC_startJoining();
+ reportEvent(EV_RESET);
+ return;
+ }
+ buildDataFrame();
+ // NOTE: a channel decision for LMIC.txChnl above will never be rendered invalid
+ // by chosing a slower datarate (it could be invalided ONLY by a faster datarate).
+ txdr = lowerDR(txdr, DRADJUST[LMIC.txCnt]);
+ LMIC.osjob.func = FUNC_ADDR(updataDone);
+ }
+ LMIC.rps = setCr(updr2rps(txdr), (cr_t)LMIC.errcr);
+ LMIC.rxsyms = txdr; // carry TX datarate (can be != LMIC.datarate) over to txDone/setupRx1
+ LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL;
+ updateTx(txbeg);
+ os_radio(RADIO_TX);
+ return;
+ }
+ // 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
+ // Consider RX tasks
+ if( txbeg == 0 ) // zero indicates no TX pending
+ txbeg += 1; // TX delayed by one tick (insignificant amount of time)
+ } else {
+ // No TX pending - no scheduled RX
+ if( (LMIC.opmode & OP_TRACK) == 0 )
+ return;
+ }
+
+ // Are we pingable?
+ checkrx:
+ if( (LMIC.opmode & OP_PINGINI) != 0 ) {
+ // One more RX slot in this beacon period?
+ if( rxschedNext(&LMIC.ping, now+RX_RAMPUP) ) {
+ if( txbeg != 0 && (txbeg - LMIC.ping.rxtime) < 0 )
+ goto txdelay;
+ LMIC.rxsyms = LMIC.ping.rxsyms;
+ LMIC.rxtime = LMIC.ping.rxtime;
+ LMIC.freq = LMIC.ping.freq;
+ LMIC.rps = dndr2rps(LMIC.ping.dr);
+ LMIC.dataLen = 0;
+ ASSERT(LMIC.rxtime - now+RX_RAMPUP >= 0 );
+ os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, FUNC_ADDR(startRxPing));
+ return;
+ }
+ // no - just wait for the beacon
+ }
+
+ if( txbeg != 0 && (txbeg - rxtime) < 0 )
+ goto txdelay;
+
+ setBcnRxParams();
+ LMIC.rxsyms = LMIC.bcnRxsyms;
+ LMIC.rxtime = LMIC.bcnRxtime;
+ if( now - rxtime >= 0 ) {
+ LMIC.osjob.func = FUNC_ADDR(processBeacon);
+ os_radio(RADIO_RX);
+ return;
+ }
+ os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn));
+ return;
+
+ txdelay:
+ EV(devCond, INFO, (e_.reason = EV::devCond_t::TX_DELAY,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = osticks2ms(txbeg-now),
+ e_.info2 = LMIC.seqnoUp-1));
+ os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate));
+}
+
+
+void LMIC_setAdrMode (bit_t enabled) {
+ LMIC.adrEnabled = enabled ? FCT_ADREN : 0;
+}
+
+
+// Should we have/need an ext. API like this?
+void LMIC_setDrTxpow (dr_t dr, s1_t txpow) {
+ setDrTxpow(DRCHG_SET, dr, txpow);
+}
+
+
+void LMIC_shutdown (void) {
+ os_clearCallback(&LMIC.osjob);
+ os_radio(RADIO_RST);
+ LMIC.opmode |= OP_SHUTDOWN;
+}
+
+
+void LMIC_reset (void) {
+ EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV,
+ e_.eui = MAIN::CDEV->getEui(),
+ e_.info = EV_RESET));
+ os_radio(RADIO_RST);
+ os_clearCallback(&LMIC.osjob);
+
+ os_clearMem((xref2u1_t)&LMIC,SIZEOFEXPR(LMIC));
+ LMIC.devaddr = 0;
+ LMIC.devNonce = os_getRndU2();
+ LMIC.opmode = OP_NONE;
+ LMIC.errcr = CR_4_5;
+ LMIC.adrEnabled = FCT_ADREN;
+ LMIC.dn2Dr = DR_DNW2; // we need this for 2ns DN window of join accept
+ LMIC.dn2Freq = FREQ_DNW2; // ditto
+#if CFG_us915
+ initDefaultChannels();
+#endif
+}
+
+
+void LMIC_init (void) {
+ LMIC.opmode = OP_SHUTDOWN;
+}
+
+
+void LMIC_clrTxData (void) {
+ LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND|OP_POLL);
+ LMIC.pendTxLen = 0;
+ if( (LMIC.opmode & (OP_JOINING|OP_SCAN)) != 0 ) // do not interfere with JOINING
+ return;
+ os_clearCallback(&LMIC.osjob);
+ os_radio(RADIO_RST);
+ engineUpdate();
+}
+
+
+void LMIC_setTxData (void) {
+ LMIC.opmode |= OP_TXDATA;
+ if( (LMIC.opmode & OP_JOINING) == 0 )
+ LMIC.txCnt = 0; // cancel any ongoing TX/RX retries
+ engineUpdate();
+}
+
+
+//
+int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) {
+ if( dlen > SIZEOFEXPR(LMIC.pendTxData) )
+ return -2;
+ if( data != (xref2u1_t)0 )
+ os_copyMem(LMIC.pendTxData, data, dlen);
+ LMIC.pendTxConf = confirmed;
+ LMIC.pendTxPort = port;
+ LMIC.pendTxLen = dlen;
+ LMIC_setTxData();
+ return 0;
+}
+
+
+// Send a payload-less message to signal device is alive
+void LMIC_sendAlive (void) {
+ LMIC.opmode |= OP_POLL;
+ engineUpdate();
+}
+
+
+// Check if other networks are around.
+void LMIC_tryRejoin (void) {
+ LMIC.opmode |= OP_REJOIN;
+ engineUpdate();
+}
+
+void LMIC_startABP(u4_t netid, devaddr_t devaddr, u1_t* nwkKey, u1_t* artKey)
+{
+ LMIC.netid = netid;
+ LMIC.devaddr = devaddr;
+ memcpy(LMIC.nwkKey, nwkKey, 16);
+ memcpy(LMIC.artKey, artKey, 16);
+
+#if CFG_eu868
+ initDefaultChannels(0);
+#endif
+
+ LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL;
+ stateJustJoined();
+ //engineUpdate();
+ reportEvent(EV_JOINED);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lmic.h Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,244 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#define NEW_CHNL 1
+
+#ifndef _lmic_h_
+#define _lmic_h_
+
+#define CFG_DEBUG 1
+#define CFG_eu868 1
+//#define CFG_us915 0
+
+#define USE_SMTC_RADIO_DRIVER 1
+
+//#define CFG_sx1272_radio 0
+#define CFG_sx1276_radio 1
+
+#include "mbed.h"
+#include "oslmic.h"
+#include "lorabase.h"
+
+enum { MAX_FRAME_LEN = 64 }; // Library cap on max frame length
+enum { TXCONF_ATTEMPTS = 8 }; // transmit attempts for confirmed frames
+enum { MAX_MISSED_BCNS = 20 }; // threshold for triggering rejoin requests
+enum { MAX_RXSYMS = 100 }; // stop tracking beacon beyond this
+
+enum { LINK_CHECK_CONT = 6 }; // continue with this after reported dead link
+enum { LINK_CHECK_DEAD = 12 }; // after this UP frames and no response from NWK assume link is dead
+enum { LINK_CHECK_INIT = -12 }; // UP frame count until we inc datarate
+
+enum { TIME_RESYNC = 6*128 }; // secs
+enum { TXRX_GUARD_ms = 6000 }; // msecs - don't start TX-RX transaction before beacon
+enum { JOIN_GUARD_ms = 9000 }; // msecs - don't start Join Req/Acc transaction before beacon
+enum { TXRX_BCNEXT_secs = 2 }; // secs - earliest start after beacon time
+enum { RETRY_PERIOD_secs = 3 }; // secs - random period for retrying a confirmed send
+
+#if CFG_eu868 // EU868 spectrum ====================================================
+
+enum { MAX_CHANNELS = 8 }; // library may not support all 16 channels
+enum { MAX_BANDS = 4 };
+
+enum { LIMIT_CHANNELS = (1<<4) }; // EU868 will never have more channels
+struct band_t {
+ u2_t txcap; // duty cycle limitation: 1/txcap
+ s1_t txpow; // maximum TX power
+ ostime_t avail; // channel is blocked until this time
+};
+TYPEDEF_xref2band_t;
+
+#elif CFG_us915 // US915 spectrum =================================================
+
+enum { MAX_XCHANNELS = 2 }; // extra channels in RAM, channels 0-71 are immutable
+enum { MAX_TXPOW_125kHz = 30 };
+
+#endif // ==========================================================================
+
+// Keep in sync with evdefs.hpp::drChange
+enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD };
+enum { KEEP_TXPOW = -128 };
+
+
+struct rxsched_t {
+ u1_t dr;
+ u1_t intvExp; // 0..7
+ u1_t slot; // runs from 0 to 128
+ u1_t rxsyms;
+ ostime_t rxbase;
+ ostime_t rxtime; // start of next spot
+ u4_t freq;
+};
+TYPEDEF_xref2rxsched_t;
+
+
+// Information extracted from (last) beacon
+enum { BCN_NONE = 0x00,
+ BCN_PARTIAL = 0x01,
+ BCN_FULL = 0x02,
+ BCN_NODRIFT = 0x04, // no drift value measured
+ BCN_NODDIFF = 0x08 }; // no differential drift measured yet
+struct bcninfo_t {
+ rxqu_t rxq;
+ ostime_t txtime; // time when beacon was sent
+ u1_t flags;
+ u4_t time; // GPS time in seconds
+ // This part is valid only if flags==BCN_FULL
+ u1_t info; // info field
+ s4_t lat;
+ s4_t lon;
+};
+
+// purpose of receive window - lmic_t.rxState
+enum { RADIO_RST=0, RADIO_TX=1, RADIO_RX=2, RADIO_RXON=3 };
+// Netid values / lmic_t.netid
+enum { NETID_NONE=(int)~0U, NETID_MASK=0xFFFFFF };
+// MAC operation modes (lmic_t.opmode).
+enum { OP_NONE = 0x0000,
+ OP_SCAN = 0x0001, // radio scan to find a beacon
+ OP_TRACK = 0x0002, // track my networks beacon (netid)
+ OP_JOINING = 0x0004, // device joining in progress (blocks other activities)
+ OP_TXDATA = 0x0008, // TX user data (buffered in pendTxData)
+ OP_POLL = 0x0010, // send empty UP frame to ACK confirmed DN/fetch more DN data
+ OP_REJOIN = 0x0020, // occasionally send JOIN REQUEST
+ OP_SHUTDOWN = 0x0040, // prevent MAC from doing anything
+ OP_TXRXPEND = 0x0080, // TX/RX transaction pending
+ OP_RNDTX = 0x0100, // prevent TX lining up after a beacon
+ OP_PINGINI = 0x0200, // pingable is initialized and scheduling active
+ OP_PINGABLE = 0x0400, // we're pingable
+ OP_NEXTCHNL = 0x0800, // find a new channel
+ OP_LINKDEAD = 0x1000, // link was reported as dead
+ OP_OTA = 0x2000, // Over the Air Activation in use
+};
+// TX-RX transaction flags - report back to user
+enum { TXRX_ACK = 0x80, // confirmed UP frame was acked
+ TXRX_NACK = 0x40, // confirmed UP frame was not acked
+ TXRX_NOPORT = 0x20, // set if a frame with a port was RXed, clr if no frame/no port
+ TXRX_DNW1 = 0x01, // received in 1st DN slot
+ TXRX_DNW2 = 0x02, // received in 2dn DN slot
+ TXRX_PING = 0x04 }; // received in a scheduled RX slot
+// Event types for event callback
+enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND,
+ EV_BEACON_MISSED, EV_BEACON_TRACKED, EV_JOINING,
+ 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 };
+typedef enum _ev_t ev_t;
+
+
+struct lmic_t {
+ // Radio settings TX/RX (also accessed by HAL)
+ ostime_t txend;
+ ostime_t rxtime;
+ rxqu_t rxq;
+ u4_t freq;
+ rps_t rps;
+ u1_t rxsyms;
+ s1_t txpow; // dBm
+
+ osjob_t osjob;
+
+ // Channel scheduling
+#if CFG_eu868
+ band_t bands[MAX_BANDS];
+ u4_t channelFreq[MAX_CHANNELS];
+ u1_t channelDrs[MAX_CHANNELS];
+ u2_t channelMap;
+#elif CFG_us915
+ u4_t xchFreq[MAX_XCHANNELS]; // extra channel frequencies (if device is behind a repeater)
+ u1_t xchDrs[MAX_XCHANNELS]; // extra channel datarate ranges ---XXX: ditto
+ u2_t channelMap[(72+MAX_XCHANNELS+15)/16]; // enabled bits
+ u1_t chRnd; // channel randomizer
+#endif
+ u1_t txChnl; // channel for next TX
+ u1_t globalDutyRate; // max rate: 1/2^k
+ ostime_t globalDutyAvail; // time device can send again
+
+ u4_t netid; // current network id (~0 - none)
+ u2_t opmode;
+ u1_t upRepeat; // configured up repeat
+ s1_t adrTxPow; // ADR adjusted TX power
+ u1_t datarate; // current data rate
+ u1_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;
+ s2_t maxDriftDiff;
+
+ u1_t pendTxPort;
+ u1_t pendTxConf; // confirmed data
+ u1_t pendTxLen; // +0x80 = confirmed
+ u1_t pendTxData[MAX_LEN_PAYLOAD];
+
+ u2_t devNonce; // last generated nonce
+ u1_t nwkKey[16]; // network session key
+ u1_t artKey[16]; // application router session key
+ devaddr_t devaddr;
+ u4_t seqnoDn; // device level down stream seqno
+ u4_t seqnoUp;
+
+ u1_t dnConf; // dn frame confirm pending: LORA::FCT_ACK or 0
+ s1_t adrAckReq; // counter until we reset data rate (0=off)
+ u1_t adrChanged;
+
+ u1_t margin;
+ bit_t ladrAns; // link adr adapt answer pending
+ bit_t devsAns; // device status answer pending
+ u1_t adrEnabled;
+ u1_t moreData; // NWK has more data pending
+ bit_t dutyCapAns; // have to ACK duty cycle settings
+ u1_t snchAns; // answer set new channel
+ // 2nd RX window (after up stream)
+ u1_t dn2Dr;
+ u4_t dn2Freq;
+ u1_t dn2Ans; // 0=no answer pend, 0x80+ACKs
+
+ // Class B state
+ u1_t missedBcns; // unable to track last N beacons
+ u1_t bcninfoTries; // how often to try (scan mode only)
+ u1_t pingSetAns; // answer set cmd and ACK bits
+ rxsched_t ping; // pingable setup
+
+ // Public part of MAC state
+ u1_t txCnt;
+ u1_t txrxFlags; // transaction flags (TX-RX combo)
+ u1_t dataBeg; // 0 or start of data (dataBeg-1 is port)
+ u1_t dataLen; // 0 no data or zero length data, >0 byte count of data
+ u1_t frame[MAX_LEN_FRAME];
+
+ u1_t bcnChnl;
+ u1_t bcnRxsyms; //
+ ostime_t bcnRxtime;
+ bcninfo_t bcninfo; // Last received beacon info
+};
+DECLARE_LMIC;
+
+
+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);
+
+void LMIC_shutdown (void);
+void LMIC_init (void);
+void LMIC_reset (void);
+void LMIC_clrTxData (void);
+void LMIC_setTxData (void);
+int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed);
+void LMIC_sendAlive (void);
+
+bit_t LMIC_enableTracking (u1_t tryBcnInfo);
+void LMIC_disableTracking (void);
+
+void LMIC_stopPingable (void);
+void LMIC_setPingable (u1_t intvExp);
+void LMIC_tryRejoin (void);
+void LMIC_startABP (u4_t netid, devaddr_t devaddr, u1_t* nwkKey, u1_t* artKey);
+
+#endif // _lmic_h_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lorabase.h Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,372 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#ifndef _lorabase_h_
+#define _lorabase_h_
+
+// ================================================================================
+// BEG: Keep in sync with lorabase.hpp
+//
+
+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 };
+typedef u1_t cr_t;
+typedef u1_t sf_t;
+typedef u1_t bw_t;
+typedef u1_t dr_t;
+// Radio parameter set (encodes SF/BW/CR/IH/NOCRC)
+typedef u2_t rps_t;
+TYPEDEF_xref2rps_t;
+
+enum { ILLEGAL_RPS = 0xFF };
+enum { DR_PAGE_EU868 = 0x00 };
+enum { DR_PAGE_US915 = 0x10 };
+
+// Global maximum frame length
+enum { STD_PREAMBLE_LEN = 8 };
+enum { MAX_LEN_FRAME = 64 };
+enum { LEN_DEVNONCE = 2 };
+enum { LEN_ARTNONCE = 3 };
+enum { LEN_NETID = 3 };
+enum { DELAY_JACC1 = 5 }; // in secs
+enum { DELAY_DNW1 = 1 }; // in secs down window #1
+enum { DELAY_EXTDNW2 = 1 }; // in secs
+enum { DELAY_JACC2 = DELAY_JACC1+(int)DELAY_EXTDNW2 }; // in secs
+enum { DELAY_DNW2 = DELAY_DNW1 +(int)DELAY_EXTDNW2 }; // in secs down window #1
+enum { BCN_INTV_exp = 7 };
+enum { BCN_INTV_sec = 1<<BCN_INTV_exp };
+enum { BCN_INTV_ms = BCN_INTV_sec*1000L };
+enum { BCN_INTV_us = BCN_INTV_ms*1000L };
+enum { BCN_RESERVE_ms = 2120 }; // space reserved for beacon and NWK management
+enum { BCN_GUARD_ms = 3000 }; // end of beacon period to prevent interference with beacon
+enum { BCN_SLOT_SPAN_ms = 30 }; // 2^12 reception slots a this span
+enum { BCN_WINDOW_ms = BCN_INTV_ms-(int)BCN_GUARD_ms-(int)BCN_RESERVE_ms };
+enum { BCN_RESERVE_us = 2120000 };
+enum { BCN_GUARD_us = 3000000 };
+enum { BCN_SLOT_SPAN_us = 30000 };
+
+#if CFG_eu868 // ==============================================
+
+enum _dr_eu868_t { DR_SF12=0, DR_SF11, DR_SF10, DR_SF9, DR_SF8, DR_SF7, DR_SF7B, DR_FSK, DR_NONE };
+enum { DR_DFLTMIN = DR_SF7 };
+enum { DR_PAGE = DR_PAGE_EU868 };
+
+// Default frequency plan for EU 868MHz ISM band
+// Bands:
+// g1 : 1% 14dBm
+// g2 : 0.1% 14dBm
+// g3 : 10% 27dBm
+// freq band datarates
+enum { EU868_F1 = 868100000, // g1 SF7-12
+ EU868_F2 = 868300000, // g1 SF7-12 FSK SF7/250
+ EU868_F3 = 868500000, // g1 SF7-12
+ EU868_F4 = 868850000, // g2 SF7-12
+ EU868_F5 = 869050000, // g2 SF7-12
+ EU868_F6 = 869525000, // g3 SF7-12
+ EU868_J4 = 864100000, // g2 SF7-12 used during join
+ EU868_J5 = 864300000, // g2 SF7-12 ditto
+ EU868_J6 = 864500000, // g2 SF7-12 ditto
+};
+enum { EU868_FREQ_MIN = 863000000,
+ EU868_FREQ_MAX = 870000000 };
+
+enum { CHNL_PING = 5 };
+enum { FREQ_PING = EU868_F6 }; // default ping freq
+enum { DR_PING = SF9 }; // default ping DR
+enum { CHNL_DNW2 = 5 };
+enum { FREQ_DNW2 = EU868_F6 };
+enum { DR_DNW2 = DR_SF9 };
+enum { CHNL_BCN = 5 };
+enum { FREQ_BCN = EU868_F6 };
+enum { DR_BCN = DR_SF9 };
+enum { AIRTIME_BCN = 185344 }; // micros
+
+#elif CFG_us915 // =========================================
+
+enum _dr_us915_t { DR_SF10=0, DR_SF9, DR_SF8, DR_SF7, DR_SF8C, DR_NONE,
+ // Devices behind a router:
+ DR_SF12CR=8, DR_SF11CR, DR_SF10CR, DR_SF9CR, DR_SF8CR, DR_SF7CR };
+enum { DR_DFLTMIN = DR_SF8C };
+enum { DR_PAGE = DR_PAGE_US915 };
+
+// Default frequency plan for US 915MHz
+enum { US915_125kHz_UPFBASE = 902300000,
+ US915_125kHz_UPFSTEP = 200000,
+ US915_500kHz_UPFBASE = 903000000,
+ US915_500kHz_UPFSTEP = 1600000,
+ US915_500kHz_DNFBASE = 923300000,
+ US915_500kHz_DNFSTEP = 600000
+};
+enum { US915_FREQ_MIN = 902000000,
+ US915_FREQ_MAX = 928000000 };
+
+enum { CHNL_PING = 2 };
+enum { FREQ_PING = US915_500kHz_DNFBASE + CHNL_PING*US915_500kHz_DNFSTEP }; // default ping freq
+enum { DR_PING = DR_SF10CR }; // default ping DR
+enum { CHNL_DNW2 = 1 };
+enum { FREQ_DNW2 = US915_500kHz_DNFBASE + CHNL_DNW2*US915_500kHz_DNFSTEP };
+enum { DR_DNW2 = DR_SF10CR };
+enum { CHNL_BCN = 0 };
+enum { FREQ_BCN = US915_500kHz_DNFBASE + CHNL_BCN*US915_500kHz_DNFSTEP };
+enum { DR_BCN = DR_SF10CR };
+enum { AIRTIME_BCN = 82432 }; // micros
+
+#endif // ===================================================
+
+
+enum {
+ // Beacon frame format
+ OFF_BCN_RFU = 0,
+ OFF_BCN_NETID = 4,
+ OFF_BCN_CMAP = 7,
+ OFF_BCN_TIME = 9,
+ OFF_BCN_CRC1 = 13,
+ OFF_BCN_INFO = 15,
+ OFF_BCN_LAT = 16,
+ OFF_BCN_LON = 19,
+ OFF_BCN_CRC2 = 22,
+ LEN_BCN = 24
+};
+enum {
+ // Join Request frame format
+ OFF_JR_HDR = 0,
+ OFF_JR_ARTEUI = 1,
+ OFF_JR_DEVEUI = 9,
+ OFF_JR_DEVNONCE = 17,
+ OFF_JR_MIC = 19,
+ LEN_JR = 23
+};
+enum {
+ // Join Accept frame format
+ OFF_JA_HDR = 0,
+ OFF_JA_ARTNONCE = 1,
+ OFF_JA_NETID = 4,
+ OFF_JA_DEVADDR = 7,
+ OFF_JA_RFU = 11,
+ OFF_CFLIST = 14,
+ LEN_JA = 17,
+ LEN_JAEXT = 17+16
+};
+enum {
+ // Data frame format
+ OFF_DAT_HDR = 0,
+ OFF_DAT_ADDR = 1,
+ OFF_DAT_FCT = 5,
+ OFF_DAT_SEQNO = 6,
+ OFF_DAT_OPTS = 8,
+};
+enum { MAX_LEN_PAYLOAD = MAX_LEN_FRAME-(int)OFF_DAT_OPTS-4 };
+enum {
+ // Bitfields in frame format octet
+ HDR_FTYPE = 0xE0,
+ HDR_RFU = 0x1C,
+ HDR_MAJOR = 0x03
+};
+enum { HDR_FTYPE_DNFLAG = 0x20 }; // flags DN frame except for HDR_FTYPE_PROP
+enum {
+ // Values of frame type bit field
+ HDR_FTYPE_JREQ = 0x00,
+ HDR_FTYPE_JACC = 0x20,
+ HDR_FTYPE_DAUP = 0x40, // data (unconfirmed) up
+ HDR_FTYPE_DADN = 0x60, // data (unconfirmed) dn
+ HDR_FTYPE_DCUP = 0x80, // data confirmed up
+ HDR_FTYPE_DCDN = 0xA0, // data confirmed dn
+ HDR_FTYPE_REJOIN = 0xC0, // rejoin for roaming
+ HDR_FTYPE_PROP = 0xE0
+};
+enum {
+ HDR_MAJOR_V1 = 0x00,
+};
+enum {
+ // Bitfields in frame control octet
+ FCT_ADREN = 0x80,
+ FCT_ADRARQ = 0x40,
+ FCT_ACK = 0x20,
+ FCT_MORE = 0x10,
+ FCT_OPTLEN = 0x0F,
+};
+enum {
+ NWKID_MASK = (int)0xFE000000,
+ NWKID_BITS = 7
+};
+
+// MAC uplink commands downwlink too
+enum {
+ // Class A
+ MCMD_LCHK_REQ = 0x02, // - link check request : -
+ MCMD_LADR_ANS = 0x03, // - link ADR answer : u1:7-3:RFU, 3/2/1: pow/DR/Ch ACK
+ MCMD_DCAP_ANS = 0x04, // - duty cycle answer : -
+ MCMD_DN2P_ANS = 0x05, // - 2nd DN slot status : u1:7-2:RFU 1/0:datarate/channel ack
+ MCMD_DEVS_ANS = 0x06, // - device status ans : u1:battery 0,1-254,255=?, u1:7-6:RFU,5-0:margin(-32..31)
+ MCMD_SNCH_ANS = 0x07, // - set new channel : u1: 7-2=RFU, 1/0:DR/freq ACK
+ // Class B
+ MCMD_PING_IND = 0x10, // - pingability indic : u1: 7=RFU, 6-4:interval, 3-0:datarate
+ MCMD_PING_ANS = 0x11, // - ack ping freq : u1: 7-1:RFU, 0:freq ok
+ MCMD_BCNI_REQ = 0x12, // - next beacon start : -
+};
+
+// MAC downlink commands
+enum {
+ // Class A
+ MCMD_LCHK_ANS = 0x02, // link check answer : u1:margin 0-254,255=unknown margin / u1:gwcnt
+ MCMD_LADR_REQ = 0x03, // link ADR request : u1:DR/TXPow, u2:chmask, u1:chpage/repeat
+ MCMD_DCAP_REQ = 0x04, // duty cycle cap : u1:255 dead [7-4]:RFU, [3-0]:cap 2^-k
+ MCMD_DN2P_SET = 0x05, // 2nd DN window param: u1:7-4:RFU/3-0:datarate, u3:freq
+ MCMD_DEVS_REQ = 0x06, // device status req : -
+ MCMD_SNCH_REQ = 0x07, // set new channel : u1:chidx, u3:freq, u1:DRrange
+ // Class B
+ MCMD_PING_SET = 0x11, // set ping freq : u3: freq
+ MCMD_BCNI_ANS = 0x12, // next beacon start : u2: delay(10ms), u1:channel
+};
+
+enum {
+ MCMD_LADR_ANS_RFU = 0xF8, // RFU bits
+ MCMD_LADR_ANS_POWACK = 0x04, // 0=not supported power level
+ MCMD_LADR_ANS_DRACK = 0x02, // 0=unknown data rate
+ MCMD_LADR_ANS_CHACK = 0x01, // 0=unknown channel enabled
+};
+enum {
+ MCMD_DN2P_ANS_RFU = 0xFC, // RFU bits
+ MCMD_DN2P_ANS_DRACK = 0x02, // 0=unknown data rate
+ MCMD_DN2P_ANS_CHACK = 0x01, // 0=unknown channel enabled
+};
+enum {
+ MCMD_SNCH_ANS_RFU = 0xFC, // RFU bits
+ MCMD_SNCH_ANS_DRACK = 0x02, // 0=unknown data rate
+ MCMD_SNCH_ANS_FQACK = 0x01, // 0=rejected channel frequency
+};
+enum {
+ MCMD_PING_ANS_RFU = 0xFE,
+ MCMD_PING_ANS_FQACK = 0x01
+};
+
+enum {
+ MCMD_DEVS_EXT_POWER = 0x00, // external power supply
+ MCMD_DEVS_BATT_MIN = 0x01, // min battery value
+ MCMD_DEVS_BATT_MAX = 0xFE, // max battery value
+ MCMD_DEVS_BATT_NOINFO = 0xFF, // unknown battery level
+};
+
+// Bit fields byte#3 of MCMD_LADR_REQ payload
+enum {
+ MCMD_LADR_CHP_125ON = 0x60, // special channel page enable, bits applied to 64..71
+ MCMD_LADR_CHP_125OFF = 0x70, // ditto
+ MCMD_LADR_N3RFU_MASK = 0x80,
+ MCMD_LADR_CHPAGE_MASK = 0xF0,
+ MCMD_LADR_REPEAT_MASK = 0x0F,
+ MCMD_LADR_REPEAT_1 = 0x01,
+ MCMD_LADR_CHPAGE_1 = 0x10
+};
+// Bit fields byte#0 of MCMD_LADR_REQ payload
+enum {
+ MCMD_LADR_DR_MASK = 0xF0,
+ MCMD_LADR_POW_MASK = 0x0F,
+ MCMD_LADR_DR_SHIFT = 4,
+ MCMD_LADR_POW_SHIFT = 0,
+#if CFG_eu868
+ MCMD_LADR_SF12 = DR_SF12<<4,
+ MCMD_LADR_SF11 = DR_SF11<<4,
+ MCMD_LADR_SF10 = DR_SF10<<4,
+ MCMD_LADR_SF9 = DR_SF9 <<4,
+ MCMD_LADR_SF8 = DR_SF8 <<4,
+ MCMD_LADR_SF7 = DR_SF7 <<4,
+ MCMD_LADR_SF7B = DR_SF7B<<4,
+ MCMD_LADR_FSK = DR_FSK <<4,
+
+ MCMD_LADR_20dBm = 0,
+ MCMD_LADR_14dBm = 1,
+ MCMD_LADR_11dBm = 2,
+ MCMD_LADR_8dBm = 3,
+ MCMD_LADR_5dBm = 4,
+ MCMD_LADR_2dBm = 5,
+#elif CFG_us915
+ MCMD_LADR_SF10 = DR_SF10<<4,
+ MCMD_LADR_SF9 = DR_SF9 <<4,
+ MCMD_LADR_SF8 = DR_SF8 <<4,
+ MCMD_LADR_SF7 = DR_SF7 <<4,
+ MCMD_LADR_SF8C = DR_SF8C<<4,
+ MCMD_LADR_SF12CR = DR_SF12CR<<4,
+ MCMD_LADR_SF11CR = DR_SF11CR<<4,
+ MCMD_LADR_SF10CR = DR_SF10CR<<4,
+ MCMD_LADR_SF9CR = DR_SF9CR<<4,
+ MCMD_LADR_SF8CR = DR_SF8CR<<4,
+ MCMD_LADR_SF7CR = DR_SF7CR<<4,
+
+ MCMD_LADR_30dBm = 0,
+ MCMD_LADR_28dBm = 1,
+ MCMD_LADR_26dBm = 2,
+ MCMD_LADR_24dBm = 3,
+ MCMD_LADR_22dBm = 4,
+ MCMD_LADR_20dBm = 5,
+ MCMD_LADR_18dBm = 6,
+ MCMD_LADR_16dBm = 7,
+ MCMD_LADR_14dBm = 8,
+ MCMD_LADR_12dBm = 9,
+ MCMD_LADR_10dBm = 10
+#endif
+};
+
+// Device address
+typedef u4_t devaddr_t;
+
+
+// RX quality (device)
+enum { RSSI_OFF=64, SNR_SCALEUP=4 };
+struct rxqu_t {
+ s1_t rssi; // offset by RSSI_OFF, max physical RSSI range -198..+63
+ s1_t snr; // scaled by SNR_SCALEUP, max physical SNR range -32..+31.75
+};
+TYPEDEF_xref2rxqu_t;
+
+
+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); }
+inline rps_t setBw (rps_t params, bw_t cr) { return (rps_t)((params & ~0x18) | (cr<<3)); }
+inline cr_t getCr (rps_t params) { return (cr_t)((params >> 5) & 0x3); }
+inline rps_t setCr (rps_t params, cr_t cr) { return (rps_t)((params & ~0x60) | (cr<<5)); }
+inline int getNocrc(rps_t params) { return ((params >> 7) & 0x1); }
+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)))
+// 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; }
+
+extern const u1_t _DR2RPS_CRC[];
+inline rps_t updr2rps (dr_t dr) { return (rps_t)_DR2RPS_CRC[dr+1]; }
+inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); }
+inline int isFasterDR (dr_t dr1, dr_t dr2) { return dr1 > dr2; }
+inline int isSlowerDR (dr_t dr1, dr_t dr2) { return dr1 < dr2; }
+inline dr_t incDR (dr_t dr) { return _DR2RPS_CRC[dr+2]==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate
+inline dr_t decDR (dr_t dr) { return _DR2RPS_CRC[dr ]==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate
+inline dr_t assertDR (dr_t dr) { return _DR2RPS_CRC[dr+1]==ILLEGAL_RPS ? DR_DFLTMIN : dr; } // force into a valid DR
+inline bit_t validDR (dr_t dr) { return _DR2RPS_CRC[dr+1]!=ILLEGAL_RPS; } // in range
+inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps
+
+//
+// BEG: Keep in sync with lorabase.hpp
+// ================================================================================
+
+
+// Convert between dBm values and power codes (MCMD_LADR_XdBm)
+s1_t pow2dBm (u1_t mcmd_ladr_p1);
+// Calculate airtime
+ostime_t calcAirTime (rps_t rps, u1_t plen);
+// Sensitivity at given SF/BW
+int getSensitivity (rps_t rps);
+
+
+#endif // _lorabase_h_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/oslmic.cpp Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#include "lmic.h"
+
+// RUNTIME STATE
+static struct {
+ osjob_t* scheduledjobs;
+ osjob_t* runnablejobs;
+} OS;
+
+void os_init (void) {
+ memset(&OS, 0x00, sizeof(OS));
+ hal_init();
+ radio_init();
+ LMIC_init();
+}
+
+ostime_t os_getTime (void) {
+ return hal_ticks();
+}
+
+static u1_t unlinkjob (osjob_t** pnext, osjob_t* job) {
+ for( ; *pnext; pnext = &((*pnext)->next)) {
+ if(*pnext == job) { // unlink
+ *pnext = job->next;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+// clear scheduled job
+void os_clearCallback (osjob_t* job) {
+ hal_disableIRQs();
+ unlinkjob(&OS.scheduledjobs, job) || unlinkjob(&OS.runnablejobs, job);
+ hal_enableIRQs();
+}
+
+// schedule immediately runnable job
+void os_setCallback (osjob_t* job, osjobcb_t cb) {
+ osjob_t** pnext;
+ hal_disableIRQs();
+ // remove if job was already queued
+ os_clearCallback(job);
+ // fill-in job
+ job->func = cb;
+ job->next = NULL;
+ // add to end of run queue
+ for(pnext=&OS.runnablejobs; *pnext; pnext=&((*pnext)->next));
+ *pnext = job;
+ hal_enableIRQs();
+}
+
+// schedule timed job
+void os_setTimedCallback (osjob_t* job, ostime_t time, osjobcb_t cb) {
+ osjob_t** pnext;
+ hal_disableIRQs();
+ // remove if job was already queued
+ os_clearCallback(job);
+ // fill-in job
+ job->deadline = time;
+ job->func = cb;
+ job->next = NULL;
+ // insert into schedule
+ for(pnext=&OS.scheduledjobs; *pnext; pnext=&((*pnext)->next)) {
+ if(time < (*pnext)->deadline) {
+ // enqueue before next element and stop
+ job->next = *pnext;
+ break;
+ }
+ }
+ *pnext = job;
+ hal_enableIRQs();
+}
+
+// execute jobs from timer and from run queue
+void os_runloop (void) {
+ while(1) {
+ osjob_t* j = NULL;
+ hal_disableIRQs();
+ // check for runnable jobs
+ if(OS.runnablejobs) {
+ j = OS.runnablejobs;
+ OS.runnablejobs = j->next;
+ } else if(OS.scheduledjobs && hal_checkTimer(OS.scheduledjobs->deadline)) { // check for expired timed jobs
+ j = OS.scheduledjobs;
+ OS.scheduledjobs = j->next;
+ } else { // nothing pending
+ hal_sleep(); // wake by irq (timer already restarted)
+ }
+ hal_enableIRQs();
+ if(j) { // run job callback
+ if(j->func) {
+ j->func(j);
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/oslmic.h Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#ifndef _oslmic_h_
+#define _oslmic_h_
+
+// Dependencies required for the LoRa MAC in C to run.
+// These settings can be adapted to the underlying system.
+// You should not, however, change the lmic.[hc]
+
+
+
+//================================================================================
+//================================================================================
+// Target platform as C library
+typedef unsigned char bit_t;
+typedef unsigned char u1_t;
+typedef signed char s1_t;
+typedef unsigned short u2_t;
+typedef short s2_t;
+typedef unsigned int u4_t;
+typedef int s4_t;
+typedef unsigned long long u8_t;
+typedef long long s8_t;
+typedef unsigned int uint;
+typedef const char* str_t;
+
+#define CFG_noassert 1
+#include <string.h>
+#include "hal.h"
+#define EV(a,b,c) /**/
+#define DO_DEVDB(meth,...) /**/
+#if !CFG_noassert
+#define ASSERT(cond) if(!(cond)) hal_failed()
+#else
+#define ASSERT(cond) /**/
+#endif
+
+#define os_clearMem(a,b) memset(a,0,b)
+#define os_copyMem(a,b,c) memcpy(a,b,c)
+
+typedef struct osjob_t osjob_t;
+typedef struct band_t band_t;
+typedef struct rxqu_t rxqu_t;
+typedef struct chnldef_t chnldef_t;
+typedef struct rxsched_t rxsched_t;
+typedef struct bcninfo_t bcninfo_t;
+typedef const u1_t* xref2cu1_t;
+typedef u1_t* xref2u1_t;
+#define TYPEDEF_xref2rps_t typedef rps_t* xref2rps_t
+#define TYPEDEF_xref2rxqu_t typedef rxqu_t* xref2rxqu_t
+#define TYPEDEF_xref2rxsched_t typedef rxsched_t* xref2rxsched_t
+#define TYPEDEF_xref2chnldef_t typedef chnldef_t* xref2chnldef_t
+#define TYPEDEF_xref2band_t typedef band_t* xref2band_t
+#define TYPEDEF_xref2osjob_t typedef osjob_t* xref2osjob_t
+
+#define SIZEOFEXPR(x) sizeof(x)
+
+#define ON_LMIC_EVENT(ev) onEvent(ev)
+#define DECL_ON_LMIC_EVENT void onEvent(ev_t e)
+
+extern u4_t AESAUX[];
+extern u4_t AESKEY[];
+#define AESkey ((u1_t*)AESKEY)
+#define AESaux ((u1_t*)AESAUX)
+#define FUNC_ADDR(func) (&(func))
+
+u1_t radio_rand1 (void);
+#define os_getRndU1() radio_rand1()
+
+#define DEFINE_LMIC struct lmic_t LMIC
+#define DECLARE_LMIC extern struct lmic_t LMIC
+
+void radio_init(void);
+void radio_irq_handler (u1_t dio);
+void os_init (void);
+void os_runloop (void);
+
+//================================================================================
+
+
+#ifndef RX_RAMPUP
+#define RX_RAMPUP (us2osticks(2000))
+#endif
+#ifndef TX_RAMPUP
+#define TX_RAMPUP (us2osticks(2000))
+#endif
+
+#ifndef OSTICKS_PER_SEC
+#define OSTICKS_PER_SEC 15625
+#elif OSTICKS_PER_SEC < 10000 || OSTICKS_PER_SEC > 64516
+#error Illegal OSTICKS_PER_SEC - must be in range [10000:64516]. One tick must be 15.5us .. 100us long.
+#endif
+
+typedef s4_t ostime_t;
+
+#if !HAS_ostick_conv
+#define us2osticks(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC) / 1000000))
+#define ms2osticks(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC) / 1000))
+#define sec2osticks(sec) ((ostime_t)( (s8_t)(sec) * OSTICKS_PER_SEC))
+#define osticks2ms(os) ((s4_t)(((os)*(s8_t)1000 ) / OSTICKS_PER_SEC))
+#define osticks2us(os) ((s4_t)(((os)*(s8_t)1000000 ) / OSTICKS_PER_SEC))
+// Special versions
+#define us2osticksCeil(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC + 999999) / 1000000))
+#define us2osticksRound(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC + 500000) / 1000000))
+#define ms2osticksCeil(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC + 999) / 1000))
+#define ms2osticksRound(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC + 500) / 1000))
+#endif
+
+
+struct osjob_t; // fwd decl.
+typedef void (*osjobcb_t) (struct osjob_t*);
+struct osjob_t {
+ struct osjob_t* next;
+ ostime_t deadline;
+ osjobcb_t func;
+};
+TYPEDEF_xref2osjob_t;
+
+
+#if !HAS_os_calls
+
+#ifndef os_getDevKey
+void os_getDevKey (xref2u1_t buf);
+#endif
+#ifndef os_getArtEui
+void os_getArtEui (xref2u1_t buf);
+#endif
+#ifndef os_getDevEui
+void os_getDevEui (xref2u1_t buf);
+#endif
+#ifndef os_setCallback
+void os_setCallback (xref2osjob_t job, osjobcb_t cb);
+#endif
+#ifndef os_setTimedCallback
+void os_setTimedCallback (xref2osjob_t job, ostime_t time, osjobcb_t cb);
+#endif
+#ifndef os_clearCallback
+void os_clearCallback (xref2osjob_t job);
+#endif
+#ifndef os_getTime
+ostime_t os_getTime (void);
+#endif
+#ifndef os_getTimeSecs
+uint os_getTimeSecs (void);
+#endif
+#ifndef os_radio
+void os_radio (u1_t mode);
+#endif
+#ifndef os_getBattLevel
+u1_t os_getBattLevel (void);
+#endif
+
+#ifndef os_rlsbf4
+//! Read 32-bit quantity from given pointer in little endian byte order.
+u4_t os_rlsbf4 (xref2cu1_t buf);
+#endif
+#ifndef os_wlsbf4
+//! Write 32-bit quntity into buffer in little endian byte order.
+void os_wlsbf4 (xref2u1_t buf, u4_t value);
+#endif
+#ifndef os_rmsbf4
+//! Read 32-bit quantity from given pointer in big endian byte order.
+u4_t os_rmsbf4 (xref2cu1_t buf);
+#endif
+#ifndef os_wmsbf4
+//! Write 32-bit quntity into buffer in big endian byte order.
+void os_wmsbf4 (xref2u1_t buf, u4_t value);
+#endif
+#ifndef os_rlsbf2
+//! Read 16-bit quantity from given pointer in little endian byte order.
+u2_t os_rlsbf2 (xref2cu1_t buf);
+#endif
+#ifndef os_wlsbf2
+//! Write 16-bit quntity into buffer in little endian byte order.
+void os_wlsbf2 (xref2u1_t buf, u2_t value);
+#endif
+
+//! Get random number (default impl for u2_t).
+#ifndef os_getRndU2
+#define os_getRndU2() ((u2_t)((os_getRndU1()<<8)|os_getRndU1()))
+#endif
+#ifndef os_crc16
+u2_t os_crc16 (xref2u1_t d, uint len);
+#endif
+
+#endif // !HAS_os_calls
+
+// ======================================================================
+// AES support
+// !!Keep in sync with lorabase.hpp!!
+
+#ifndef AES_ENC // if AES_ENC is defined as macro all other values must be too
+#define AES_ENC 0x00
+#define AES_DEC 0x01
+#define AES_MIC 0x02
+#define AES_CTR 0x04
+#define AES_MICNOAUX 0x08
+#endif
+#ifndef AESkey // if AESkey is defined as macro all other values must be too
+extern xref2u1_t AESkey;
+extern xref2u1_t AESaux;
+#endif
+#ifndef os_aes
+u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len);
+#endif
+
+
+
+#endif // _oslmic_h_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/radio.cpp Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,1092 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Zurich Research Lab - initial API, implementation and documentation
+ * Semtech Apps Team - Modified to support the MBED sx1276 driver
+ * library.
+ * Possibility to use original or Semtech's MBED
+ * radio driver. The selection is done by setting
+ * USE_SMTC_RADIO_DRIVER preprocessing directive
+ * in lmic.h
+ *******************************************************************************/
+#include "lmic.h"
+
+#if USE_SMTC_RADIO_DRIVER
+#include "sx1276-hal.h"
+
+/*!
+ * Syncword for lora networks (nibbles swapped)
+ */
+#define LORA_MAC_SYNCWORD 0x34
+
+/*
+ * Callback functions prototypes
+ */
+/*!
+ * @brief Function to be executed on Radio Tx Done event
+ */
+void OnTxDone( void );
+
+/*!
+ * @brief Function to be executed on Radio Rx Done event
+ */
+void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr );
+
+/*!
+ * @brief Function executed on Radio Tx Timeout event
+ */
+void OnTxTimeout( void );
+
+/*!
+ * @brief Function executed on Radio Rx Timeout event
+ */
+void OnRxTimeout( void );
+
+/*!
+ * @brief Function executed on Radio Rx Error event
+ */
+void OnRxError( void );
+
+/*!
+ * @brief Function executed on Radio Fhss Change Channel event
+ */
+void OnFhssChangeChannel( uint8_t channelIndex );
+
+/*!
+ * @brief Function executed on CAD Done event
+ */
+void OnCadDone( void );
+
+/*
+ * Radio object declraration
+ */
+SX1276MB1xAS Radio( OnTxDone, OnTxTimeout, OnRxDone, OnRxTimeout, OnRxError, NULL, NULL );
+
+static const u2_t LORA_RXDONE_FIXUP[] = {
+ [FSK] = us2osticks(0), // ( 0 ticks)
+ [SF7] = us2osticks(0), // ( 0 ticks)
+ [SF8] = us2osticks(1648), // ( 54 ticks)
+ [SF9] = us2osticks(3265), // ( 107 ticks)
+ [SF10] = us2osticks(7049), // ( 231 ticks)
+ [SF11] = us2osticks(13641), // ( 447 ticks)
+ [SF12] = us2osticks(31189), // (1022 ticks)
+};
+
+void OnTxDone( void )
+{
+ ostime_t now = os_getTime( );
+ // save exact tx time
+ LMIC.txend = now - us2osticks( 43 ); // TXDONE FIXUP
+
+ // go from stanby to sleep
+ Radio.Sleep( );
+ // run os job (use preset func ptr)
+ os_setCallback( &LMIC.osjob, LMIC.osjob.func );
+}
+
+void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
+{
+ ostime_t now = os_getTime( );
+ // save exact rx time
+ LMIC.rxtime = now - LORA_RXDONE_FIXUP[getSf( LMIC.rps )];
+ // read the PDU and inform the MAC that we received something
+ LMIC.dataLen = size;
+ // now read the FIFO
+ memcpy( LMIC.frame, payload, size );
+ // read rx quality parameters
+ LMIC.rxq.snr = snr; // SNR [dB] * 4
+ LMIC.rxq.rssi = rssi; // RSSI [dBm] (-196...+63)
+
+ // go from stanby to sleep
+ Radio.Sleep( );
+ // run os job (use preset func ptr)
+ os_setCallback( &LMIC.osjob, LMIC.osjob.func );
+}
+
+void OnTxTimeout( void )
+{
+ ostime_t now = os_getTime( );
+
+ // indicate error
+
+ // go from stanby to sleep
+ Radio.Sleep( );
+ // run os job (use preset func ptr)
+ os_setCallback( &LMIC.osjob, LMIC.osjob.func );
+}
+
+void OnRxTimeout( void )
+{
+ ostime_t now = os_getTime( );
+ // indicate timeout
+ LMIC.dataLen = 0;
+
+ // go from stanby to sleep
+ Radio.Sleep( );
+ // run os job (use preset func ptr)
+ os_setCallback( &LMIC.osjob, LMIC.osjob.func );
+}
+
+void OnRxError( void )
+{
+ ostime_t now = os_getTime( );
+
+ // indicate error
+ LMIC.dataLen = 0;
+
+ // go from stanby to sleep
+ Radio.Sleep( );
+ // run os job (use preset func ptr)
+ os_setCallback( &LMIC.osjob, LMIC.osjob.func );
+}
+
+/*!
+ * LMIC API implementation
+ */
+// RADIO STATE
+// (initialized by radio_init( ), used by radio_rand1( ))
+static u1_t randbuf[16];
+
+// get random seed from wideband noise rssi
+void radio_init( void )
+{
+ hal_disableIRQs( );
+
+ // seed 15-byte randomness via noise rssi
+ // Set LoRa modem ON
+ Radio.SetModem( MODEM_LORA );
+ // Disable LoRa modem interrupts
+ Radio.Write( REG_LR_IRQFLAGSMASK, RFLR_IRQFLAGS_RXTIMEOUT |
+ RFLR_IRQFLAGS_RXDONE |
+ RFLR_IRQFLAGS_PAYLOADCRCERROR |
+ RFLR_IRQFLAGS_VALIDHEADER |
+ RFLR_IRQFLAGS_TXDONE |
+ RFLR_IRQFLAGS_CADDONE |
+ RFLR_IRQFLAGS_FHSSCHANGEDCHANNEL |
+ RFLR_IRQFLAGS_CADDETECTED );
+
+ // Set radio in continuous reception
+ Radio.Rx( 0 );
+
+ for( int i = 1; i < 16; i++ )
+ {
+ for( int j = 0; j < 8; j++ )
+ {
+ u1_t b; // wait for two non-identical subsequent least-significant bits
+ while( ( b = Radio.Read( REG_LR_RSSIWIDEBAND ) & 0x01 ) == ( Radio.Read( REG_LR_RSSIWIDEBAND ) & 0x01 ) );
+ randbuf[i] = ( randbuf[i] << 1 ) | b;
+ }
+ }
+ randbuf[0] = 16; // set initial index
+
+ // Change LoRa modem SyncWord
+ Radio.Write( REG_LR_SYNCWORD, LORA_MAC_SYNCWORD );
+
+ Radio.Sleep( );
+
+ hal_enableIRQs( );
+}
+
+// return next random byte derived from seed buffer
+// (buf[0] holds index of next byte to be returned)
+u1_t radio_rand1( void )
+{
+ u1_t i = randbuf[0];
+ ASSERT( i != 0 );
+ if( i == 16 )
+ {
+ os_aes( AES_ENC, randbuf, 16 ); // encrypt seed with any key
+ i = 0;
+ }
+ u1_t v = randbuf[i++];
+ randbuf[0] = i;
+ return v;
+}
+
+void os_radio( u1_t mode )
+{
+ hal_disableIRQs( );
+ switch( mode )
+ {
+ case RADIO_RST:
+ // put radio to sleep
+ Radio.Sleep( );
+ break;
+
+ case RADIO_TX:
+ // transmit frame now
+ //ASSERT( Radio.GetState( ) == IDLE );
+
+ Radio.SetChannel( LMIC.freq );
+ if( getSf( LMIC.rps ) == FSK )
+ { // FSK modem
+ Radio.SetTxConfig( MODEM_FSK, LMIC.txpow, 25e3, 0, 50e3, 0, 5, false, true, 0, 0, false, 3e6 );
+ }
+ else
+ { // LoRa modem
+
+ Radio.SetTxConfig( MODEM_LORA, LMIC.txpow, 0, getBw( LMIC.rps ), getSf( LMIC.rps ) + 6, getCr( LMIC.rps ) + 1, 8, getIh( LMIC.rps ) ? true : false, ( getNocrc(LMIC.rps) == 0 ) ? true : false, 0, 0, false, 3e6 );
+ }
+
+ //starttx( ); // buf=LMIC.frame, len=LMIC.dataLen
+ Radio.Send( LMIC.frame, LMIC.dataLen );
+ break;
+
+ case RADIO_RX:
+ // receive frame now (exactly at rxtime)
+ //ASSERT( Radio.GetState( ) == IDLE );
+
+ Radio.SetChannel( LMIC.freq );
+ if( getSf( LMIC.rps ) == FSK )
+ { // FSK modem
+ //Radio.SetRxConfig( MODEM_FSK, 50e3, 50e3, 0, 83.333e3, 5, 0, false, 0, true, 0, 0, false, false );
+ Radio.SetRxConfig( MODEM_FSK, 50e3, 50e3, 0, 83.333e3, 5, 0, false, 0, true, 0, 0, false, true );
+ }
+ else
+ { // LoRa modem
+
+ Radio.SetRxConfig( MODEM_LORA, getBw( LMIC.rps ), getSf( LMIC.rps ) + 6, getCr( LMIC.rps ) + 1, 0, 8, LMIC.rxsyms, getIh( LMIC.rps ) ? true : false, getIh(LMIC.rps), ( getNocrc(LMIC.rps) == 0 ) ? true : false, 0, 0, true, false );
+ }
+
+ // now instruct the radio to receive
+ hal_waitUntil( LMIC.rxtime ); // busy wait until exact rx time
+
+ //startrx( RXMODE_SINGLE ); // buf = LMIC.frame, time = LMIC.rxtime, timeout=LMIC.rxsyms
+ if( getSf( LMIC.rps ) == FSK )
+ { // FSK modem
+ Radio.Rx( 50e3 ); // Max Rx window 50 ms
+ }
+ else
+ { // LoRa modem
+ Radio.Rx( 3e6 ); // Max Rx window 3 seconds
+ }
+ break;
+
+ case RADIO_RXON:
+ // start scanning for beacon now
+
+ //ASSERT( Radio.GetState( ) == IDLE );
+
+ Radio.SetChannel( LMIC.freq );
+ if( getSf( LMIC.rps ) == FSK )
+ { // FSK modem
+ Radio.SetRxConfig( MODEM_FSK, 50e3, 50e3, 0, 83.333e3, 5, 0, false, 0, true, 0, 0, false, true );
+ }
+ else
+ { // LoRa modem
+ Radio.SetRxConfig( MODEM_LORA, getBw( LMIC.rps ), getSf( LMIC.rps ) + 6, getCr( LMIC.rps ) + 1, 0, 8, LMIC.rxsyms, getIh( LMIC.rps ) ? true : false, getIh(LMIC.rps), ( getNocrc(LMIC.rps) == 0 ) ? true : false, 0, 0, true, true );
+ }
+
+ //startrx( RXMODE_SCAN) ; // buf = LMIC.frame
+ Radio.Rx( 0 );
+ break;
+ }
+ hal_enableIRQs( );
+}
+
+#else
+
+// ----------------------------------------
+// Registers Mapping
+#define RegFifo 0x00 // common
+#define RegOpMode 0x01 // common
+#define FSKRegBitrateMsb 0x02
+#define FSKRegBitrateLsb 0x03
+#define FSKRegFdevMsb 0x04
+#define FSKRegFdevLsb 0x05
+#define RegFrfMsb 0x06 // common
+#define RegFrfMid 0x07 // common
+#define RegFrfLsb 0x08 // common
+#define RegPaConfig 0x09 // common
+#define RegPaRamp 0x0A // common
+#define RegOcp 0x0B // common
+#define RegLna 0x0C // common
+#define FSKRegRxConfig 0x0D
+#define LORARegFifoAddrPtr 0x0D
+#define FSKRegRssiConfig 0x0E
+#define LORARegFifoTxBaseAddr 0x0E
+#define FSKRegRssiCollision 0x0F
+#define LORARegFifoRxBaseAddr 0x0F
+#define FSKRegRssiThresh 0x10
+#define LORARegFifoRxCurrentAddr 0x10
+#define FSKRegRssiValue 0x11
+#define LORARegIrqFlagsMask 0x11
+#define FSKRegRxBw 0x12
+#define LORARegIrqFlags 0x12
+#define FSKRegAfcBw 0x13
+#define LORARegRxNbBytes 0x13
+#define FSKRegOokPeak 0x14
+#define LORARegRxHeaderCntValueMsb 0x14
+#define FSKRegOokFix 0x15
+#define LORARegRxHeaderCntValueLsb 0x15
+#define FSKRegOokAvg 0x16
+#define LORARegRxPacketCntValueMsb 0x16
+#define LORARegRxpacketCntValueLsb 0x17
+#define LORARegModemStat 0x18
+#define LORARegPktSnrValue 0x19
+#define FSKRegAfcFei 0x1A
+#define LORARegPktRssiValue 0x1A
+#define FSKRegAfcMsb 0x1B
+#define LORARegRssiValue 0x1B
+#define FSKRegAfcLsb 0x1C
+#define LORARegHopChannel 0x1C
+#define FSKRegFeiMsb 0x1D
+#define LORARegModemConfig1 0x1D
+#define FSKRegFeiLsb 0x1E
+#define LORARegModemConfig2 0x1E
+#define FSKRegPreambleDetect 0x1F
+#define LORARegSymbTimeoutLsb 0x1F
+#define FSKRegRxTimeout1 0x20
+#define LORARegPreambleMsb 0x20
+#define FSKRegRxTimeout2 0x21
+#define LORARegPreambleLsb 0x21
+#define FSKRegRxTimeout3 0x22
+#define LORARegPayloadLength 0x22
+#define FSKRegRxDelay 0x23
+#define LORARegPayloadMaxLength 0x23
+#define FSKRegOsc 0x24
+#define LORARegHopPeriod 0x24
+#define FSKRegPreambleMsb 0x25
+#define LORARegFifoRxByteAddr 0x25
+#define LORARegModemConfig3 0x26
+#define FSKRegPreambleLsb 0x26
+#define FSKRegSyncConfig 0x27
+#define LORARegFeiMsb 0x28
+#define FSKRegSyncValue1 0x28
+#define LORAFeiMib 0x29
+#define FSKRegSyncValue2 0x29
+#define LORARegFeiLsb 0x2A
+#define FSKRegSyncValue3 0x2A
+#define FSKRegSyncValue4 0x2B
+#define LORARegRssiWideband 0x2C
+#define FSKRegSyncValue5 0x2C
+#define FSKRegSyncValue6 0x2D
+#define FSKRegSyncValue7 0x2E
+#define FSKRegSyncValue8 0x2F
+#define FSKRegPacketConfig1 0x30
+#define FSKRegPacketConfig2 0x31
+#define LORARegDetectOptimize 0x31
+#define FSKRegPayloadLength 0x32
+#define FSKRegNodeAdrs 0x33
+#define LORARegInvertIQ 0x33
+#define FSKRegBroadcastAdrs 0x34
+#define FSKRegFifoThresh 0x35
+#define FSKRegSeqConfig1 0x36
+#define FSKRegSeqConfig2 0x37
+#define LORARegDetectionThreshold 0x37
+#define FSKRegTimerResol 0x38
+#define FSKRegTimer1Coef 0x39
+#define LORARegSyncWord 0x39
+#define FSKRegTimer2Coef 0x3A
+#define FSKRegImageCal 0x3B
+#define FSKRegTemp 0x3C
+#define FSKRegLowBat 0x3D
+#define FSKRegIrqFlags1 0x3E
+#define FSKRegIrqFlags2 0x3F
+#define RegDioMapping1 0x40 // common
+#define RegDioMapping2 0x41 // common
+#define RegVersion 0x42 // common
+// #define RegAgcRef 0x43 // common
+// #define RegAgcThresh1 0x44 // common
+// #define RegAgcThresh2 0x45 // common
+// #define RegAgcThresh3 0x46 // common
+// #define RegPllHop 0x4B // common
+// #define RegTcxo 0x58 // common
+#define RegPaDac 0x5A // common
+// #define RegPll 0x5C // common
+// #define RegPllLowPn 0x5E // common
+// #define RegFormerTemp 0x6C // common
+// #define RegBitRateFrac 0x70 // common
+
+// ----------------------------------------
+// spread factors and mode for RegModemConfig2
+#define SX1272_MC2_FSK 0x00
+#define SX1272_MC2_SF7 0x70
+#define SX1272_MC2_SF8 0x80
+#define SX1272_MC2_SF9 0x90
+#define SX1272_MC2_SF10 0xA0
+#define SX1272_MC2_SF11 0xB0
+#define SX1272_MC2_SF12 0xC0
+// bandwidth for RegModemConfig1
+#define SX1272_MC1_BW_125 0x00
+#define SX1272_MC1_BW_250 0x40
+#define SX1272_MC1_BW_500 0x80
+// coding rate for RegModemConfig1
+#define SX1272_MC1_CR_4_5 0x08
+#define SX1272_MC1_CR_4_6 0x10
+#define SX1272_MC1_CR_4_7 0x18
+#define SX1272_MC1_CR_4_8 0x20
+#define SX1272_MC1_IMPLICIT_HEADER_MODE_ON 0x04 // required for receive
+#define SX1272_MC1_RX_PAYLOAD_CRCON 0x02
+#define SX1272_MC1_LOW_DATA_RATE_OPTIMIZE 0x01 // mandated for SF11 and SF12
+// transmit power configuration for RegPaConfig
+#define SX1272_PAC_PA_SELECT_PA_BOOST 0x80
+#define SX1272_PAC_PA_SELECT_RFIO_PIN 0x00
+
+
+// sx1276 RegModemConfig1
+#define SX1276_MC1_BW_125 0x70
+#define SX1276_MC1_BW_250 0x80
+#define SX1276_MC1_BW_500 0x90
+#define SX1276_MC1_CR_4_5 0x02
+#define SX1276_MC1_CR_4_6 0x04
+#define SX1276_MC1_CR_4_7 0x06
+#define SX1276_MC1_CR_4_8 0x08
+
+#define SX1276_MC1_IMPLICIT_HEADER_MODE_ON 0x01
+
+// sx1276 RegModemConfig2
+#define SX1276_MC2_RX_PAYLOAD_CRCON 0x04
+
+// sx1276 RegModemConfig3
+#define SX1276_MC3_LOW_DATA_RATE_OPTIMIZE 0x08
+#define SX1276_MC3_AGCAUTO 0x04
+
+// preamble for lora networks (nibbles swapped)
+#define LORA_MAC_PREAMBLE 0x34
+
+#define RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG1 0x0A
+#ifdef CFG_sx1276_radio
+#define RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG2 0x70
+#elif CFG_sx1272_radio
+#define RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG2 0x74
+#endif
+
+
+
+// ----------------------------------------
+// Constants for radio registers
+#define OPMODE_LORA 0x80
+#define OPMODE_MASK 0x07
+#define OPMODE_SLEEP 0x00
+#define OPMODE_STANDBY 0x01
+#define OPMODE_FSTX 0x02
+#define OPMODE_TX 0x03
+#define OPMODE_FSRX 0x04
+#define OPMODE_RX 0x05
+#define OPMODE_RX_SINGLE 0x06
+#define OPMODE_CAD 0x07
+
+// ----------------------------------------
+// Bits masking the corresponding IRQs from the radio
+#define IRQ_LORA_RXTOUT_MASK 0x80
+#define IRQ_LORA_RXDONE_MASK 0x40
+#define IRQ_LORA_CRCERR_MASK 0x20
+#define IRQ_LORA_HEADER_MASK 0x10
+#define IRQ_LORA_TXDONE_MASK 0x08
+#define IRQ_LORA_CDDONE_MASK 0x04
+#define IRQ_LORA_FHSSCH_MASK 0x02
+#define IRQ_LORA_CDDETD_MASK 0x01
+
+#define IRQ_FSK1_MODEREADY_MASK 0x80
+#define IRQ_FSK1_RXREADY_MASK 0x40
+#define IRQ_FSK1_TXREADY_MASK 0x20
+#define IRQ_FSK1_PLLLOCK_MASK 0x10
+#define IRQ_FSK1_RSSI_MASK 0x08
+#define IRQ_FSK1_TIMEOUT_MASK 0x04
+#define IRQ_FSK1_PREAMBLEDETECT_MASK 0x02
+#define IRQ_FSK1_SYNCADDRESSMATCH_MASK 0x01
+#define IRQ_FSK2_FIFOFULL_MASK 0x80
+#define IRQ_FSK2_FIFOEMPTY_MASK 0x40
+#define IRQ_FSK2_FIFOLEVEL_MASK 0x20
+#define IRQ_FSK2_FIFOOVERRUN_MASK 0x10
+#define IRQ_FSK2_PACKETSENT_MASK 0x08
+#define IRQ_FSK2_PAYLOADREADY_MASK 0x04
+#define IRQ_FSK2_CRCOK_MASK 0x02
+#define IRQ_FSK2_LOWBAT_MASK 0x01
+
+// ----------------------------------------
+// DIO function mappings D0D1D2D3
+#define MAP_DIO0_LORA_RXDONE 0x00 // 00------
+#define MAP_DIO0_LORA_TXDONE 0x40 // 01------
+#define MAP_DIO1_LORA_RXTOUT 0x00 // --00----
+#define MAP_DIO1_LORA_NOP 0x30 // --11----
+#define MAP_DIO2_LORA_NOP 0xC0 // ----11--
+
+#define MAP_DIO0_FSK_READY 0x00 // 00------ (packet sent / payload ready)
+#define MAP_DIO1_FSK_NOP 0x30 // --11----
+#define MAP_DIO2_FSK_TXNOP 0x04 // ----01--
+#define MAP_DIO2_FSK_TIMEOUT 0x08 // ----10--
+
+
+// FSK IMAGECAL defines
+#define RF_IMAGECAL_AUTOIMAGECAL_MASK 0x7F
+#define RF_IMAGECAL_AUTOIMAGECAL_ON 0x80
+#define RF_IMAGECAL_AUTOIMAGECAL_OFF 0x00 // Default
+
+#define RF_IMAGECAL_IMAGECAL_MASK 0xBF
+#define RF_IMAGECAL_IMAGECAL_START 0x40
+
+#define RF_IMAGECAL_IMAGECAL_RUNNING 0x20
+#define RF_IMAGECAL_IMAGECAL_DONE 0x00 // Default
+
+
+// RADIO STATE
+// (initialized by radio_init(), used by radio_rand1())
+static u1_t randbuf[16];
+
+
+#ifdef CFG_sx1276_radio
+#define LNA_RX_GAIN (0x20|0x1)
+#elif CFG_sx1272_radio
+#define LNA_RX_GAIN (0x20|0x03)
+#else
+#error Missing CFG_sx1272_radio/CFG_sx1276_radio
+#endif
+
+static void writeReg (u1_t addr, u1_t data ) {
+ hal_pin_nss(0);
+ hal_spi(addr | 0x80);
+ hal_spi(data);
+ hal_pin_nss(1);
+}
+
+static u1_t readReg (u1_t addr) {
+ hal_pin_nss(0);
+ hal_spi(addr & 0x7F);
+ u1_t val = hal_spi(0x00);
+ hal_pin_nss(1);
+ return val;
+}
+
+static void writeBuf (u1_t addr, xref2u1_t buf, u1_t len) {
+ hal_pin_nss(0);
+ hal_spi(addr | 0x80);
+ for (u1_t i=0; i<len; i++) {
+ hal_spi(buf[i]);
+ }
+ hal_pin_nss(1);
+}
+
+static void readBuf (u1_t addr, xref2u1_t buf, u1_t len) {
+ hal_pin_nss(0);
+ hal_spi(addr & 0x7F);
+ for (u1_t i=0; i<len; i++) {
+ buf[i] = hal_spi(0x00);
+ }
+ hal_pin_nss(1);
+}
+
+static void opmode (u1_t mode) {
+ writeReg(RegOpMode, (readReg(RegOpMode) & ~OPMODE_MASK) | mode);
+}
+
+static void opmodeLora(void) {
+ u1_t u = OPMODE_LORA;
+#ifdef CFG_sx1276_radio
+ u |= 0x8; // TBD: sx1276 high freq
+#endif
+ writeReg(RegOpMode, u);
+}
+
+static void opmodeFSK(void) {
+ u1_t u = 0;
+#ifdef CFG_sx1276_radio
+ u |= 0x8; // TBD: sx1276 high freq
+#endif
+ writeReg(RegOpMode, u);
+}
+
+// configure LoRa modem (cfg1, cfg2)
+static void configLoraModem (void) {
+ sf_t sf = getSf(LMIC.rps);
+
+#ifdef CFG_sx1276_radio
+ u1_t mc1 = 0, mc2 = 0, mc3 = 0;
+
+ switch (getBw(LMIC.rps)) {
+ case BW125: mc1 |= SX1276_MC1_BW_125; break;
+ case BW250: mc1 |= SX1276_MC1_BW_250; break;
+ case BW500: mc1 |= SX1276_MC1_BW_500; break;
+ default:
+ ASSERT(0);
+ }
+ switch( getCr(LMIC.rps) ) {
+ case CR_4_5: mc1 |= SX1276_MC1_CR_4_5; break;
+ case CR_4_6: mc1 |= SX1276_MC1_CR_4_6; break;
+ case CR_4_7: mc1 |= SX1276_MC1_CR_4_7; break;
+ case CR_4_8: mc1 |= SX1276_MC1_CR_4_8; break;
+ default:
+ ASSERT(0);
+ }
+
+ if (getIh(LMIC.rps)) {
+ mc1 |= SX1276_MC1_IMPLICIT_HEADER_MODE_ON;
+ writeReg(LORARegPayloadLength, getIh(LMIC.rps)); // required length
+ }
+ // set ModemConfig1
+ writeReg(LORARegModemConfig1, mc1);
+
+ mc2 = (SX1272_MC2_SF7 + ((sf-1)<<4));
+ if (getNocrc(LMIC.rps) == 0) {
+ mc2 |= SX1276_MC2_RX_PAYLOAD_CRCON;
+ }
+ writeReg(LORARegModemConfig2, mc2);
+
+ mc3 = SX1276_MC3_AGCAUTO;
+ if (sf == SF11 || sf == SF12) {
+ mc3 |= SX1276_MC3_LOW_DATA_RATE_OPTIMIZE;
+ }
+ writeReg(LORARegModemConfig3, mc3);
+#elif CFG_sx1272_radio
+ u1_t mc1 = (getBw(LMIC.rps)<<6);
+
+ switch( getCr(LMIC.rps) ) {
+ case CR_4_5: mc1 |= SX1272_MC1_CR_4_5; break;
+ case CR_4_6: mc1 |= SX1272_MC1_CR_4_6; break;
+ case CR_4_7: mc1 |= SX1272_MC1_CR_4_7; break;
+ case CR_4_8: mc1 |= SX1272_MC1_CR_4_8; break;
+ }
+
+ if (sf == SF11 || sf == SF12) {
+ mc1 |= SX1272_MC1_LOW_DATA_RATE_OPTIMIZE;
+ }
+
+ if (getNocrc(LMIC.rps) == 0) {
+ mc1 |= SX1272_MC1_RX_PAYLOAD_CRCON;
+ }
+
+ if (getIh(LMIC.rps)) {
+ mc1 |= SX1272_MC1_IMPLICIT_HEADER_MODE_ON;
+ writeReg(LORARegPayloadLength, getIh(LMIC.rps)); // required length
+ }
+ // set ModemConfig1
+ writeReg(LORARegModemConfig1, mc1);
+
+ // set ModemConfig2 (sf, AgcAutoOn=1 SymbTimeoutHi=00)
+ writeReg(LORARegModemConfig2, (SX1272_MC2_SF7 + ((sf-1)<<4)) | 0x04);
+#else
+#error Missing CFG_sx1272_radio/CFG_sx1276_radio
+#endif /* CFG_sx1272_radio */
+}
+
+static void configChannel (void) {
+ // set frequency: FQ = (FRF * 32 Mhz) / (2 ^ 19)
+ u8_t frf = ((u8_t)LMIC.freq << 19) / 32000000;
+ writeReg(RegFrfMsb, (u1_t)(frf>>16));
+ writeReg(RegFrfMid, (u1_t)(frf>> 8));
+ writeReg(RegFrfLsb, (u1_t)(frf>> 0));
+}
+
+
+
+static void configPower (void) {
+#ifdef CFG_sx1276_radio
+ // no boost used for now
+ s1_t pw = (s1_t)LMIC.txpow;
+ if(pw > 14) {
+ pw = 14;
+ } else if(pw < -1) {
+ pw = -1;
+ }
+ // check board type for BOOST pin
+ writeReg(RegPaConfig, (u1_t)(0x00|((pw+1)&0xf)));
+ writeReg(RegPaDac, readReg(RegPaDac)|0x4);
+
+#elif CFG_sx1272_radio
+ // set PA config (2-17 dBm using PA_BOOST)
+ s1_t pw = (s1_t)LMIC.txpow;
+ if(pw > 17) {
+ pw = 17;
+ } else if(pw < 2) {
+ pw = 2;
+ }
+ writeReg(RegPaConfig, (u1_t)(0x80|(pw-2)));
+#else
+#error Missing CFG_sx1272_radio/CFG_sx1276_radio
+#endif /* CFG_sx1272_radio */
+}
+
+static void txfsk (void) {
+ // select FSK modem (from sleep mode)
+ writeReg(RegOpMode, 0x10); // FSK, BT=0.5
+ ASSERT(readReg(RegOpMode) == 0x10);
+ // enter standby mode (required for FIFO loading))
+ opmode(OPMODE_STANDBY);
+ // set bitrate
+ writeReg(FSKRegBitrateMsb, 0x02); // 50kbps
+ writeReg(FSKRegBitrateLsb, 0x80);
+ // set frequency deviation
+ writeReg(FSKRegFdevMsb, 0x01); // +/- 25kHz
+ writeReg(FSKRegFdevLsb, 0x99);
+ // frame and packet handler settings
+ writeReg(FSKRegPreambleMsb, 0x00);
+ writeReg(FSKRegPreambleLsb, 0x05);
+ writeReg(FSKRegSyncConfig, 0x12);
+ writeReg(FSKRegPacketConfig1, 0xD0);
+ writeReg(FSKRegPacketConfig2, 0x40);
+ writeReg(FSKRegSyncValue1, 0xC1);
+ writeReg(FSKRegSyncValue2, 0x94);
+ writeReg(FSKRegSyncValue3, 0xC1);
+ // configure frequency
+ configChannel();
+ // configure output power
+ configPower();
+
+ // set the IRQ mapping DIO0=PacketSent DIO1=NOP DIO2=NOP
+ writeReg(RegDioMapping1, MAP_DIO0_FSK_READY|MAP_DIO1_FSK_NOP|MAP_DIO2_FSK_TXNOP);
+
+ // initialize the payload size and address pointers
+ writeReg(FSKRegPayloadLength, LMIC.dataLen+1); // (insert length byte into payload))
+
+ // download length byte and buffer to the radio FIFO
+ writeReg(RegFifo, LMIC.dataLen);
+ writeBuf(RegFifo, LMIC.frame, LMIC.dataLen);
+
+ // enable antenna switch for TX
+ hal_pin_rxtx(1);
+
+ // now we actually start the transmission
+ opmode(OPMODE_TX);
+}
+
+static void txlora (void) {
+ // select LoRa modem (from sleep mode)
+ //writeReg(RegOpMode, OPMODE_LORA);
+ opmodeLora();
+ ASSERT((readReg(RegOpMode) & OPMODE_LORA) != 0);
+
+ // enter standby mode (required for FIFO loading))
+ opmode(OPMODE_STANDBY);
+ // configure LoRa modem (cfg1, cfg2)
+ configLoraModem();
+ // configure frequency
+ configChannel();
+ // configure output power
+ writeReg(RegPaRamp, (readReg(RegPaRamp) & 0xF0) | 0x08); // set PA ramp-up time 50 uSec
+ configPower();
+ // set sync word
+ writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE);
+
+ // set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP
+ writeReg(RegDioMapping1, MAP_DIO0_LORA_TXDONE|MAP_DIO1_LORA_NOP|MAP_DIO2_LORA_NOP);
+ // clear all radio IRQ flags
+ writeReg(LORARegIrqFlags, 0xFF);
+ // mask all IRQs but TxDone
+ writeReg(LORARegIrqFlagsMask, ~IRQ_LORA_TXDONE_MASK);
+
+ // initialize the payload size and address pointers
+ writeReg(LORARegFifoTxBaseAddr, 0x00);
+ writeReg(LORARegFifoAddrPtr, 0x00);
+ writeReg(LORARegPayloadLength, LMIC.dataLen);
+
+ // download buffer to the radio FIFO
+ writeBuf(RegFifo, LMIC.frame, LMIC.dataLen);
+
+ // enable antenna switch for TX
+ hal_pin_rxtx(1);
+
+ // now we actually start the transmission
+ opmode(OPMODE_TX);
+}
+
+// start transmitter (buf=LMIC.frame, len=LMIC.dataLen)
+static void starttx (void) {
+ ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP );
+ 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.
+}
+
+enum { RXMODE_SINGLE, RXMODE_SCAN, RXMODE_RSSI };
+
+static const u1_t rxlorairqmask[] = {
+ [RXMODE_SINGLE] = IRQ_LORA_RXDONE_MASK|IRQ_LORA_RXTOUT_MASK,
+ [RXMODE_SCAN] = IRQ_LORA_RXDONE_MASK,
+ [RXMODE_RSSI] = 0x00,
+};
+
+// start LoRa receiver (time=LMIC.rxtime, timeout=LMIC.rxsyms, result=LMIC.frame[LMIC.dataLen])
+static void rxlora (u1_t rxmode) {
+ // select LoRa modem (from sleep mode)
+ opmodeLora();
+ ASSERT((readReg(RegOpMode) & OPMODE_LORA) != 0);
+ // enter standby mode (warm up))
+ opmode(OPMODE_STANDBY);
+ // don't use MAC settings at startup
+ if(rxmode == RXMODE_RSSI) { // use fixed settings for rssi scan
+ writeReg(LORARegModemConfig1, RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG1);
+ writeReg(LORARegModemConfig2, RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG2);
+ } else { // single or continuous rx mode
+ // configure LoRa modem (cfg1, cfg2)
+ configLoraModem();
+ // configure frequency
+ configChannel();
+ }
+ // set LNA gain
+ writeReg(RegLna, LNA_RX_GAIN);
+ // set max payload size
+ writeReg(LORARegPayloadMaxLength, 64);
+ // use inverted I/Q signal (prevent mote-to-mote communication)
+ writeReg(LORARegInvertIQ, readReg(LORARegInvertIQ)|(1<<6));
+ // set symbol timeout (for single rx)
+ writeReg(LORARegSymbTimeoutLsb, LMIC.rxsyms);
+ // set sync word
+ writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE);
+
+ // configure DIO mapping DIO0=RxDone DIO1=RxTout DIO2=NOP
+ writeReg(RegDioMapping1, MAP_DIO0_LORA_RXDONE|MAP_DIO1_LORA_RXTOUT|MAP_DIO2_LORA_NOP);
+ // clear all radio IRQ flags
+ writeReg(LORARegIrqFlags, 0xFF);
+ // enable required radio IRQs
+ writeReg(LORARegIrqFlagsMask, ~rxlorairqmask[rxmode]);
+
+ // enable antenna switch for RX
+ hal_pin_rxtx(0);
+
+ // now instruct the radio to receive
+ if (rxmode == RXMODE_SINGLE) { // single rx
+ hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time
+ opmode(OPMODE_RX_SINGLE);
+ } else { // continous rx (scan or rssi)
+ opmode(OPMODE_RX);
+ }
+}
+
+static void rxfsk (u1_t rxmode) {
+ // only single rx (no continuous scanning, no noise sampling)
+ ASSERT( rxmode == RXMODE_SINGLE );
+ // select FSK modem (from sleep mode)
+ //writeReg(RegOpMode, 0x00); // (not LoRa)
+ opmodeFSK();
+ ASSERT((readReg(RegOpMode) & OPMODE_LORA) == 0);
+ // enter standby mode (warm up))
+ opmode(OPMODE_STANDBY);
+ // configure frequency
+ configChannel();
+ // set LNA gain
+ //writeReg(RegLna, 0x20|0x03); // max gain, boost enable
+ writeReg(RegLna, LNA_RX_GAIN);
+ // configure receiver
+ writeReg(FSKRegRxConfig, 0x1E); // AFC auto, AGC, trigger on preamble?!?
+ // set receiver bandwidth
+ writeReg(FSKRegRxBw, 0x0B); // 50kHz SSb
+ // set AFC bandwidth
+ writeReg(FSKRegAfcBw, 0x12); // 83.3kHz SSB
+ // set preamble detection
+ writeReg(FSKRegPreambleDetect, 0xAA); // enable, 2 bytes, 10 chip errors
+ // set sync config
+ writeReg(FSKRegSyncConfig, 0x12); // no auto restart, preamble 0xAA, enable, fill FIFO, 3 bytes sync
+ // set packet config
+ writeReg(FSKRegPacketConfig1, 0xD8); // var-length, whitening, crc, no auto-clear, no adr filter
+ writeReg(FSKRegPacketConfig2, 0x40); // packet mode
+ // set sync value
+ writeReg(FSKRegSyncValue1, 0xC1);
+ writeReg(FSKRegSyncValue2, 0x94);
+ writeReg(FSKRegSyncValue3, 0xC1);
+ // set preamble timeout
+ writeReg(FSKRegRxTimeout2, 0xFF);//(LMIC.rxsyms+1)/2);
+ // set bitrate
+ writeReg(FSKRegBitrateMsb, 0x02); // 50kbps
+ writeReg(FSKRegBitrateLsb, 0x80);
+ // set frequency deviation
+ writeReg(FSKRegFdevMsb, 0x01); // +/- 25kHz
+ writeReg(FSKRegFdevLsb, 0x99);
+
+ // configure DIO mapping DIO0=PayloadReady DIO1=NOP DIO2=TimeOut
+ writeReg(RegDioMapping1, MAP_DIO0_FSK_READY|MAP_DIO1_FSK_NOP|MAP_DIO2_FSK_TIMEOUT);
+
+ // enable antenna switch for RX
+ hal_pin_rxtx(0);
+
+ // now instruct the radio to receive
+ hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time
+ opmode(OPMODE_RX); // no single rx mode available in FSK
+}
+
+static void startrx (u1_t rxmode) {
+ ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP );
+ if(getSf(LMIC.rps) == FSK) { // FSK modem
+ rxfsk(rxmode);
+ } else { // LoRa modem
+ rxlora(rxmode);
+ }
+ // the radio will go back to STANDBY mode as soon as the RX is finished
+ // or timed out, and the corresponding IRQ will inform us about completion.
+}
+
+// get random seed from wideband noise rssi
+void radio_init (void) {
+ hal_disableIRQs();
+
+ // manually reset radio
+#ifdef CFG_sx1276_radio
+ hal_pin_rst(0); // drive RST pin low
+#else
+ hal_pin_rst(1); // drive RST pin high
+#endif
+ hal_waitUntil(os_getTime()+ms2osticks(1)); // wait >100us
+ hal_pin_rst(2); // configure RST pin floating!
+ hal_waitUntil(os_getTime()+ms2osticks(5)); // wait 5ms
+
+ opmode(OPMODE_SLEEP);
+ // some sanity checks, e.g., read version number
+ u1_t v = readReg(RegVersion);
+#ifdef CFG_sx1276_radio
+ ASSERT(v == 0x12 );
+#elif CFG_sx1272_radio
+ ASSERT(v == 0x22);
+#else
+#error Missing CFG_sx1272_radio/CFG_sx1276_radio
+#endif
+ // seed 15-byte randomness via noise rssi
+ rxlora(RXMODE_RSSI);
+ while( (readReg(RegOpMode) & OPMODE_MASK) != OPMODE_RX ); // continuous rx
+ for(int i=1; i<16; i++) {
+ for(int j=0; j<8; j++) {
+ u1_t b; // wait for two non-identical subsequent least-significant bits
+ while( (b = readReg(LORARegRssiWideband) & 0x01) == (readReg(LORARegRssiWideband) & 0x01) );
+ randbuf[i] = (randbuf[i] << 1) | b;
+ }
+ }
+ randbuf[0] = 16; // set initial index
+
+#ifdef CFG_sx1276_radio
+ // chain calibration
+ writeReg(RegPaConfig, 0);
+
+ // Launch Rx chain calibration for LF band
+ writeReg(FSKRegImageCal, (readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_MASK)|RF_IMAGECAL_IMAGECAL_START);
+ while((readReg(FSKRegImageCal)&RF_IMAGECAL_IMAGECAL_RUNNING) == RF_IMAGECAL_IMAGECAL_RUNNING){ ; }
+
+ // Sets a Frequency in HF band
+ u4_t frf = 868000000;
+ writeReg(RegFrfMsb, (u1_t)(frf>>16));
+ writeReg(RegFrfMid, (u1_t)(frf>> 8));
+ writeReg(RegFrfLsb, (u1_t)(frf>> 0));
+
+ // Launch Rx chain calibration for HF band
+ writeReg(FSKRegImageCal, (readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_MASK)|RF_IMAGECAL_IMAGECAL_START);
+ while((readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_RUNNING) == RF_IMAGECAL_IMAGECAL_RUNNING) { ; }
+#endif /* CFG_sx1276_radio */
+
+ opmode(OPMODE_SLEEP);
+ hal_enableIRQs();
+}
+
+// return next random byte derived from seed buffer
+// (buf[0] holds index of next byte to be returned)
+u1_t radio_rand1 (void) {
+ u1_t i = randbuf[0];
+ ASSERT( i != 0 );
+ if( i==16 ) {
+ os_aes(AES_ENC, randbuf, 16); // encrypt seed with any key
+ i = 0;
+ }
+ u1_t v = randbuf[i++];
+ randbuf[0] = i;
+ return v;
+}
+
+u1_t radio_rssi (void) {
+ hal_disableIRQs();
+ u1_t r = readReg(LORARegRssiValue);
+ hal_enableIRQs();
+ return r;
+}
+
+static const u2_t LORA_RXDONE_FIXUP[] = {
+ [FSK] = us2osticks(0), // ( 0 ticks)
+ [SF7] = us2osticks(0), // ( 0 ticks)
+ [SF8] = us2osticks(1648), // ( 54 ticks)
+ [SF9] = us2osticks(3265), // ( 107 ticks)
+ [SF10] = us2osticks(7049), // ( 231 ticks)
+ [SF11] = us2osticks(13641), // ( 447 ticks)
+ [SF12] = us2osticks(31189), // (1022 ticks)
+};
+
+// called by hal ext IRQ handler
+// (radio goes to stanby mode after tx/rx operations)
+void radio_irq_handler (u1_t dio) {
+ ostime_t now = os_getTime();
+ if( (readReg(RegOpMode) & OPMODE_LORA) != 0) { // LORA modem
+ u1_t flags = readReg(LORARegIrqFlags);
+ if( flags & IRQ_LORA_TXDONE_MASK ) {
+ // save exact tx time
+ LMIC.txend = now - us2osticks(43); // TXDONE FIXUP
+ } else if( flags & IRQ_LORA_RXDONE_MASK ) {
+ // save exact rx time
+ LMIC.rxtime = now - LORA_RXDONE_FIXUP[getSf(LMIC.rps)];
+ // read the PDU and inform the MAC that we received something
+ LMIC.dataLen = (readReg(LORARegModemConfig1) & SX1272_MC1_IMPLICIT_HEADER_MODE_ON) ?
+ readReg(LORARegPayloadLength) : readReg(LORARegRxNbBytes);
+ // set FIFO read address pointer
+ writeReg(LORARegFifoAddrPtr, readReg(LORARegFifoRxCurrentAddr));
+ // now read the FIFO
+ readBuf(RegFifo, LMIC.frame, LMIC.dataLen);
+ // read rx quality parameters
+ LMIC.rxq.snr = readReg(LORARegPktSnrValue); // SNR [dB] * 4
+ LMIC.rxq.rssi = readReg(LORARegPktRssiValue) - 125 + 64; // RSSI [dBm] (-196...+63)
+ } else if( flags & IRQ_LORA_RXTOUT_MASK ) {
+ // indicate timeout
+ LMIC.dataLen = 0;
+ }
+ // mask all radio IRQs
+ writeReg(LORARegIrqFlagsMask, 0xFF);
+ // clear radio IRQ flags
+ writeReg(LORARegIrqFlags, 0xFF);
+ } else { // FSK modem
+ u1_t flags1 = readReg(FSKRegIrqFlags1);
+ u1_t flags2 = readReg(FSKRegIrqFlags2);
+ if( flags2 & IRQ_FSK2_PACKETSENT_MASK ) {
+ // save exact tx time
+ LMIC.txend = now;
+ } else if( flags2 & IRQ_FSK2_PAYLOADREADY_MASK ) {
+ // save exact rx time
+ LMIC.rxtime = now;
+ // read the PDU and inform the MAC that we received something
+ LMIC.dataLen = readReg(FSKRegPayloadLength);
+ // now read the FIFO
+ readBuf(RegFifo, LMIC.frame, LMIC.dataLen);
+ // read rx quality parameters
+ LMIC.rxq.snr = 0; // determine snr
+ LMIC.rxq.rssi = 0; // determine rssi
+ } else if( flags1 & IRQ_FSK1_TIMEOUT_MASK ) {
+ // indicate timeout
+ LMIC.dataLen = 0;
+ } else {
+ while(1);
+ }
+ }
+ // go from stanby to sleep
+ opmode(OPMODE_SLEEP);
+ // run os job (use preset func ptr)
+ os_setCallback(&LMIC.osjob, LMIC.osjob.func);
+}
+
+void os_radio (u1_t mode) {
+ hal_disableIRQs();
+ switch (mode) {
+ case RADIO_RST:
+ // put radio to sleep
+ opmode(OPMODE_SLEEP);
+ break;
+
+ case RADIO_TX:
+ // transmit frame now
+ starttx(); // buf=LMIC.frame, len=LMIC.dataLen
+ break;
+
+ case RADIO_RX:
+ // receive frame now (exactly at rxtime)
+ startrx(RXMODE_SINGLE); // buf=LMIC.frame, time=LMIC.rxtime, timeout=LMIC.rxsyms
+ break;
+
+ case RADIO_RXON:
+ // start scanning for beacon now
+ startrx(RXMODE_SCAN); // buf=LMIC.frame
+ break;
+ }
+ hal_enableIRQs();
+}
+
+#endif // USE_SMTC_RADIO_DRIVER