diff --git a/examples/raspi/Makefile b/examples/raspi/Makefile new file mode 100644 index 00000000..9ffaabd3 --- /dev/null +++ b/examples/raspi/Makefile @@ -0,0 +1,61 @@ +# Makefile +# Caution: requires bcm2835 library to be already installed +# http://www.airspayce.com/mikem/bcm2835/ +BCM2835 = 1.59 + +all: /usr/local/lib/libbcm2835.a arduino-lmic.a + +include Makefile.env + +arduino-lmic.a: raspi.o radio.o oslmic.o lmic.o hal.o aes.o \ + lmic_as923.o lmic_au921.o lmic_eu868.o lmic_in866.o lmic_us915.o \ + lmic_us_like.o lmic_eu_like.o + $(AR) rcs $@ $^ + +raspi.o: $(LMICBASE)/raspi/raspi.cpp + $(CXX) $(CFLAGS) -c $^ $(INCLUDE) + +radio.o: $(LMICBASE)/lmic/radio.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) + +oslmic.o: $(LMICBASE)/lmic/oslmic.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) + +lmic.o: $(LMICBASE)/lmic/lmic.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) + +hal.o: $(LMICBASE)/hal/hal.cpp + $(CXX) $(CFLAGS) -c $^ $(INCLUDE) + +aes.o: $(LMICBASE)/aes/lmic.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) -o aes.o + +lmic_as923.o: $(LMICBASE)/lmic/lmic_as923.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) + +lmic_au921.o: $(LMICBASE)/lmic/lmic_au921.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) + +lmic_eu868.o: $(LMICBASE)/lmic/lmic_eu868.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) + +lmic_in866.o: $(LMICBASE)/lmic/lmic_in866.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) + +lmic_us915.o: $(LMICBASE)/lmic/lmic_us915.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) + +lmic_us_like.o: $(LMICBASE)/lmic/lmic_us_like.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) + +lmic_eu_like.o: $(LMICBASE)/lmic/lmic_eu_like.c + $(CC) $(CFLAGS) -c $^ $(INCLUDE) + +/usr/local/lib/libbcm2835.a: + [ -f bcm2835-$(BCM2835).tar.gz ] || wget http://www.airspayce.com/mikem/bcm2835/bcm2835-$(BCM2835).tar.gz + if [ ! -f $@ ]; then \ + set -e; \ + tar xvfz bcm2835-$(BCM2835).tar.gz; \ + cd bcm2835-$(BCM2835); \ + ./configure && make && sudo make install; \ + fi diff --git a/examples/raspi/Makefile.env b/examples/raspi/Makefile.env new file mode 100644 index 00000000..aa640858 --- /dev/null +++ b/examples/raspi/Makefile.env @@ -0,0 +1,24 @@ +# Makefile +# Caution: requires bcm2835 library to be already installed +# http://www.airspayce.com/mikem/bcm2835/ + +CC = gcc -std=c99 +CXX = g++ -std=c++11 +CFLAGS = -DRASPBERRY_PI -DBCM2835_NO_DELAY_COMPATIBILITY -D__BASEFILE__=\"$*\" -D_GNU_SOURCE +LIBS = -lbcm2835 +LMICBASE = $(LIBDIR)../../src +INCLUDE = -I$(LMICBASE) + +%.o: %.c + $(CC) $(CFLAGS) -c $(INCLUDE) $< + +%.o: %.cpp + $(CXX) $(CFLAGS) -c $(INCLUDE) $< + +%: %.o $(LIBDIR)arduino-lmic.a + $(CXX) $^ $(LIBS) -o $@ + +clean: + rm -rf *.o *.a $(BIN) + +.SUFFIXES: # no implicit rules diff --git a/examples/raspi/README.txt b/examples/raspi/README.txt new file mode 100644 index 00000000..16292ed1 --- /dev/null +++ b/examples/raspi/README.txt @@ -0,0 +1,3 @@ + +raspberry pi examples +origin: https://github.com/hallard/arduino-lmic/tree/rpi/examples/raspi diff --git a/examples/raspi/get_deveui/Makefile b/examples/raspi/get_deveui/Makefile new file mode 100644 index 00000000..b14fcc75 --- /dev/null +++ b/examples/raspi/get_deveui/Makefile @@ -0,0 +1,9 @@ +# Sample for ttn-abp example on Raspberry Pi +# Caution: requires bcm2835 library to be already installed +# http://www.airspayce.com/mikem/bcm2835/ + +BIN=get_deveui + +LIBDIR=../ +all: $(BIN) +include ../Makefile.env diff --git a/examples/raspi/get_deveui/get_deveui.c b/examples/raspi/get_deveui/get_deveui.c new file mode 100644 index 00000000..c35fa0da --- /dev/null +++ b/examples/raspi/get_deveui/get_deveui.c @@ -0,0 +1,119 @@ +// get_deveui.c +// +// Example program to get LoraWAN device EUI from MAC Address +// Use the Makefile in this directory: +// cd example/raspi/get_deveui +// make +// sudo ./get_deveui +// +// written by Charles-Henri Hallard (hallard.me) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main (int argc, const char * argv[]) +{ + struct ifaddrs *ifaddr=NULL; + struct ifaddrs *ifa = NULL; + int family = 0; + int i = 0; + int all=0; + + if (argc==2 && !strncasecmp(argv[1],"all", 3) ) { + all=1; + } else { + fprintf(stdout, "Use \"%s all\" to see all interfaces and details\n", __BASEFILE__ ); + } + + // get linked list of the network interfaces + if (getifaddrs(&ifaddr) == -1) { + perror("getifaddrs"); + + } else { + // Loop thru interfaces list + for ( ifa=ifaddr; ifa!=NULL; ifa=ifa->ifa_next) { + // Ethernet + if ( (ifa->ifa_addr) && (ifa->ifa_addr->sa_family==AF_PACKET) ) { + // Not loopback interface + if (! (ifa->ifa_flags & IFF_LOOPBACK)) { + char fname[128]; + int fd; + int up=0; + struct sockaddr_ll *s = (struct sockaddr_ll*)ifa->ifa_addr; + + // Get interface status + // Interface can be up with no cable connected and to be sure + // It's up, active and connected we need to get operstate + // + // if up + cable if up + NO cable if down + cable + // ============= ========== ================== + // carrier:1 carrier:0 carrier:Invalid + // dormant:0 dormant:0 dormant:Invalid + // operstate:up operstate:down operstate :own + sprintf(fname, "/sys/class/net/%s/operstate", ifa->ifa_name); + + if ( (fd = open( fname, O_RDONLY)) > 0 ){ + char buf[2]; + if ( read(fd, buf, 2) > 0 ) { + if ( buf[0]=='u' && buf[1]=='p' ) { + up=1; + } + close(fd); + } else { + perror("read()"); + } + } else { + perror(fname); + } + + //active interface or all wanted + if (all || up) { + char deveui[8]; + char *p = deveui; + // deveui is LSB to we reverse it so TTN display + // will remain the same as MAC address + // MAC is 6 bytes, devEUI 8, set first 2 ones + // with an arbitrary value + *p++ = 0x00; + *p++ = 0x04; + // Then next 6 bytes are mac address still reversed + for ( i=0; i<6 ; i++) { + *p++ = s->sll_addr[5-i]; + } + + // Device Info + fprintf(stdout, "// %s %s %s ", ifa->ifa_name, ifa->ifa_flags&IFF_UP?"Up":"Down", up?"Linked":"No Link"); + // Raw format for ttnctl + p = deveui+8; + fprintf(stdout, "TTN Dashboard DEVEUI format "); + for ( i=0 ; i<8 ; i++) { + fprintf( stdout, "%02X", *--p); + } + + p = deveui; + // LSB format for application code + fprintf(stdout, "\nstatic const u1_t PROGMEM DEVEUI[8]={"); + for ( i=0 ; i<8 ; i++) { + fprintf( stdout, " 0x%02x%c", *p++, i<7?',':' '); + } + fprintf( stdout, "}; // %s\n", ifa->ifa_name); + + } + } + } + } + + // Free our Linked list + freeifaddrs(ifaddr); + } + return 0; +} + diff --git a/examples/raspi/raw/Makefile b/examples/raspi/raw/Makefile new file mode 100644 index 00000000..c0993d3c --- /dev/null +++ b/examples/raspi/raw/Makefile @@ -0,0 +1,9 @@ +# Sample for ttn-abp example on Raspberry Pi +# Caution: requires bcm2835 library to be already installed +# http://www.airspayce.com/mikem/bcm2835/ + +BIN=raw + +LIBDIR=../ +all: $(BIN) +include ../Makefile.env diff --git a/examples/raspi/raw/raw.cpp b/examples/raspi/raw/raw.cpp new file mode 100644 index 00000000..ea9e13d7 --- /dev/null +++ b/examples/raspi/raw/raw.cpp @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example transmits data on hardcoded channel and receives data + * when not transmitting. Running this sketch on two nodes should allow + * them to communicate. + *******************************************************************************/ + +#include +#include + +#include + +#if !defined(DISABLE_INVERT_IQ_ON_RX) +#error This example requires DISABLE_INVERT_IQ_ON_RX to be set. Update \ + config.h in the lmic library to set it. +#endif + +// How often to send a packet. Note that this sketch bypasses the normal +// LMIC duty cycle limiting, so when you change anything in this sketch +// (payload length, frequency, spreading factor), be sure to check if +// this interval should not also be increased. +// See this spreadsheet for an easy airtime and duty cycle calculator: +// https://docs.google.com/spreadsheets/d/1voGAtQAjC1qBmaVuP1ApNKs1ekgUjavHuVQIXyYSvNc +#define TX_INTERVAL 2000 + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = 6, + .rxtx = LMIC_UNUSED_PIN, + .rst = 5, + .dio = {2, 3, 4}, +}; + + +// These callbacks are only used in over-the-air activation, so they are +// left empty here (we cannot leave them out completely unless +// DISABLE_JOIN is set in config.h, otherwise the linker will complain). +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + +void onEvent (ev_t ev) { +} + +osjob_t txjob; +osjob_t timeoutjob; +static void tx_func (osjob_t* job); + +// Transmit the given string and call the given function afterwards +void tx(const char *str, osjobcb_t func) { + os_radio(RADIO_RST); // Stop RX first + delay(1); // Wait a bit, without this os_radio below asserts, apparently because the state hasn't changed yet + LMIC.dataLen = 0; + while (*str) + LMIC.frame[LMIC.dataLen++] = *str++; + LMIC.osjob.func = func; + os_radio(RADIO_TX); + Serial.println("TX"); +} + +// Enable rx mode and call func when a packet is received +void rx(osjobcb_t func) { + LMIC.osjob.func = func; + LMIC.rxtime = os_getTime(); // RX _now_ + // Enable "continuous" RX (e.g. without a timeout, still stops after + // receiving a packet) + os_radio(RADIO_RXON); + Serial.println("RX"); +} + +static void rxtimeout_func(osjob_t *job) { + digitalWrite(LED_BUILTIN, LOW); // off +} + +static void rx_func (osjob_t* job) { + // Blink once to confirm reception and then keep the led on + digitalWrite(LED_BUILTIN, LOW); // off + delay(10); + digitalWrite(LED_BUILTIN, HIGH); // on + + // Timeout RX (i.e. update led status) after 3 periods without RX + os_setTimedCallback(&timeoutjob, os_getTime() + ms2osticks(3*TX_INTERVAL), rxtimeout_func); + + // Reschedule TX so that it should not collide with the other side's + // next TX + os_setTimedCallback(&txjob, os_getTime() + ms2osticks(TX_INTERVAL/2), tx_func); + + Serial.print("Got "); + Serial.print(LMIC.dataLen); + Serial.println(" bytes"); + Serial.write(LMIC.frame, LMIC.dataLen); + Serial.println(); + + // Restart RX + rx(rx_func); +} + +static void txdone_func (osjob_t* job) { + rx(rx_func); +} + +// log text to USART and toggle LED +static void tx_func (osjob_t* job) { + // say hello + tx("Hello, world!", txdone_func); + // reschedule job every TX_INTERVAL (plus a bit of random to prevent + // systematic collisions), unless packets are received, then rx_func + // will reschedule at half this time. + os_setTimedCallback(job, os_getTime() + ms2osticks(TX_INTERVAL + random(500)), tx_func); +} + +// application entry point +void setup() { + printf("Starting\n"); + + pinMode(LED_BUILTIN, OUTPUT); + + // initialize runtime env + os_init(); + + // Set up these settings once, and use them for both TX and RX + +#if defined(CFG_eu868) + // Use a frequency in the g3 which allows 10% duty cycling. + LMIC.freq = 869525000; +#elif defined(CFG_us915) + LMIC.freq = 902300000; +#endif + + // Maximum TX power + LMIC.txpow = 27; + // Use a medium spread factor. This can be increased up to SF12 for + // better range, but then the interval should be (significantly) + // lowered to comply with duty cycle limits as well. + LMIC.datarate = DR_SF9; + // This sets CR 4/5, BW125 (except for DR_SF7B, which uses BW250) + LMIC.rps = updr2rps(LMIC.datarate); + + printf("Started\n"); + + // setup initial job + os_setCallback(&txjob, tx_func); +} + +int main(void) { + + setup(); + + while(1) { + // execute scheduled jobs and events + os_runloop_once(); + } +} diff --git a/examples/raspi/spi_scan/Makefile b/examples/raspi/spi_scan/Makefile new file mode 100644 index 00000000..46d92b82 --- /dev/null +++ b/examples/raspi/spi_scan/Makefile @@ -0,0 +1,9 @@ +# Sample for ttn-abp example on Raspberry Pi +# Caution: requires bcm2835 library to be already installed +# http://www.airspayce.com/mikem/bcm2835/ + +BIN=spi_scan + +LIBDIR=../ +all: $(BIN) +include ../Makefile.env diff --git a/examples/raspi/spi_scan/spi_scan.c b/examples/raspi/spi_scan/spi_scan.c new file mode 100644 index 00000000..0bdb4e1e --- /dev/null +++ b/examples/raspi/spi_scan/spi_scan.c @@ -0,0 +1,106 @@ +// spi_scan.cpp +// +// Example program showing how to detect multiple module RH_RF69/RH_RF95 on Raspberry Pi +// Requires bcm2835 library to be already installed +// http://www.airspayce.com/mikem/bcm2835/ +// Use the Makefile in this directory: +// cd example/raspi/spi_scan +// make +// sudo ./spi_scan +// +// Will check for RFM92/95/96/98 or RFM69/RFM69HCW/RFM69W modules on SPI BUS +// scan with CS = GPIO6, CE0, CEA and GPIO26 +// So it should detect the following boards +// LoRasPi board => https://github.com/hallard/LoRasPI +// RasPI Lora Gateway Board iC880A and LinkLab Lora => https://github.com/ch2i/iC880A-Raspberry-PI +// Raspberri PI Lora Gateway => https://github.com/hallard/RPI-Lora-Gateway +// Dragino Raspberry PI hat => https://github.com/dragino/Lora +// +// Contributed by Charles-Henri Hallard (hallard.me) + +#include +#include + +uint8_t readRegister(uint8_t cs_pin, uint8_t addr) +{ + char spibuf[2]; + spibuf[0] = addr & 0x7F; + spibuf[1] = 0x00; + + bcm2835_gpio_write(cs_pin,0); + bcm2835_spi_transfernb( spibuf, spibuf, sizeof(spibuf) ); + bcm2835_gpio_write(cs_pin,1); + return spibuf[1]; +} + +void getModuleName(uint8_t version) +{ + printf(" => "); + if (version==00 || version==0xFF ) + printf("Nothing!\n"); + else if (version == 0x12) + printf("SX1276 RF95/96"); + else if (version == 0x22) + printf("SX1272 RF92"); + else if (version == 0x24) + printf("SX1231 RFM69"); + else + printf("Unknown"); + + if (version!=00 && version!=0xFF ) + printf(" (V=0x%02X)\n", version); +} + +void readModuleVersion(uint8_t cs_pin) +{ + uint8_t version; + + // RFM9x version is reg 0x42 + printf("Checking register(0x42) with CS=GPIO%02d", cs_pin); + getModuleName( readRegister( cs_pin, 0x42) ); + + // RFM69 version is reg 0x10 + printf("Checking register(0x10) with CS=GPIO%02d", cs_pin); + getModuleName ( readRegister( cs_pin, 0x10) ) ; +} + +int main(int argc, char **argv) +{ + if (!bcm2835_init()) { + printf("bcm2835_init failed. Are you running as root??\n"); + + } else if (!bcm2835_spi_begin()) { + printf("bcm2835_spi_begin failed\n"); + + } else { + // List of all CS line where module can be connected + // GPIO6, GPIO8/CE0, GPIO7/CE1, GPIO26 + uint8_t CS_pins[] = {6, 7, 8, 26}; + uint8_t i; + + // Init SPI + bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST); // The default + bcm2835_spi_setDataMode(BCM2835_SPI_MODE0); // The default + bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_65536); // The default + + // We control CS line manually don't assert CEx line! + bcm2835_spi_chipSelect(BCM2835_SPI_CS_NONE); + + // Drive all CS line as output and set them High to avoid any conflict + for ( i=0; i +#include +#include +#include + +#include +#include + +#include +#include + +// +// For normal use, we require that you edit the sketch to replace FILLMEIN +// with values assigned by the TTN console. However, for regression tests, +// we want to be able to compile these scripts. The regression tests define +// COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non- +// working but innocuous value. +// +#ifdef COMPILE_REGRESSION_TEST +# define FILLMEIN 0 +#else +# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!" +# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN) +#endif + +// LoRaWAN NwkSKey, network session key +static const PROGMEM u1_t NWKSKEY[16] = { 0xC3, 0x24, 0x64, 0x98, 0xDE, 0x56, 0x5D, 0x8C, 0x55, 0x88, 0x7C, 0x05, 0x86, 0xF9, 0x82, 0x26 }; + +// LoRaWAN AppSKey, application session key +static const u1_t PROGMEM APPSKEY[16] = { 0x15, 0xF6, 0xF4, 0xD4, 0x2A, 0x95, 0xB0, 0x97, 0x53, 0x27, 0xB7, 0xC1, 0x45, 0x6E, 0xC5, 0x45 }; + +// LoRaWAN end-device address (DevAddr) +// See http://thethingsnetwork.org/wiki/AddressSpace +// The library converts the address to network byte order as needed. +static const u4_t DEVADDR = 0x1234 ; // <-- Change this address for every node! + +// These callbacks are only used in over-the-air activation, so they are +// left empty here (we cannot leave them out completely unless +// DISABLE_JOIN is set in arduino-lmic/project_config/lmic_project_config.h, +// otherwise the linker will complain). +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + +static uint8_t mydata[] = "Hello, world!"; +static osjob_t sendjob; + +// Schedule TX every this many seconds (might become longer due to duty +// cycle limitations). +const unsigned TX_INTERVAL = 60; + +// LoRasPi board +// see https://github.com/hallard/LoRasPI +//#define RF_LED_PIN RPI_V2_GPIO_P1_16 // Led on GPIO23 so P1 connector pin #16 +//#define RF_CS_PIN RPI_V2_GPIO_P1_24 // Slave Select on CE0 so P1 connector pin #24 +//#define RF_DIO0_PIN RPI_V2_GPIO_P1_22 // IRQ on GPIO25 so P1 connector pin #22 +//#define RF_RST_PIN RPI_V2_GPIO_P1_15 // RST on GPIO22 so P1 connector pin #15 + +// Raspberri PI Lora Gateway for multiple modules +// see https://github.com/hallard/RPI-Lora-Gateway +// Module 1 on board RFM95 868 MHz (example) +//#define RF_LED_PIN RPI_V2_GPIO_P1_07 // Led on GPIO4 so P1 connector pin #7 +//#define RF_CS_PIN RPI_V2_GPIO_P1_24 // Slave Select on CE0 so P1 connector pin #24 +//#define RF_DIO0_PIN RPI_V2_GPIO_P1_22 // IRQ on GPIO25 so P1 connector pin #22 +//#define RF_RST_PIN RPI_V2_GPIO_P1_29 // Reset on GPIO5 so P1 connector pin #29 + +// Dragino Raspberry PI hat (no onboard led) +// see https://github.com/dragino/Lora +//#define RF_CS_PIN RPI_V2_GPIO_P1_22 // Slave Select on GPIO25 so P1 connector pin #22 +//#define RF_DIO0_PIN RPI_V2_GPIO_P1_07 // IRQ on GPIO4 so P1 connector pin #7 +//#define RF_RST_PIN RPI_V2_GPIO_P1_11 // Reset on GPIO17 so P1 connector pin #11 + +// Chistera-Pi (no onboard led) +// see https://www.framboise314.fr/chistera-pi-lora-a-portee-de-main-grace-a-snootlab/ +#define RF_CS_PIN RPI_V2_GPIO_P1_24 // Slave Select on CE0/GPIO8 so P1 connector pin #24 +#define RF_DIO0_PIN RPI_V2_GPIO_P1_07 // IRQ on GPIO4 so P1 connector pin #7 +#define RF_DIO1_PIN RPI_V2_GPIO_P1_16 // IRQ on GPIO23 so P1 connector pin #7 +#define RF_RST_PIN RPI_V2_GPIO_P1_11 // Reset on GPIO17 so P1 connector pin #11 + +#ifndef RF_DIO0_PIN +#define RF_DIO0_PIN LMIC_UNUSED_PIN +#endif +#ifndef RF_DIO1_PIN +#define RF_DIO1_PIN LMIC_UNUSED_PIN +#endif +#ifndef RF_DIO2_PIN +#define RF_DIO2_PIN LMIC_UNUSED_PIN +#endif + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = RF_CS_PIN, + .rxtx = LMIC_UNUSED_PIN, + .rst = RF_RST_PIN, + .dio = {RF_DIO0_PIN,RF_DIO1_PIN,RF_DIO2_PIN}, +}; + +void do_send(osjob_t* j); + +void onEvent (ev_t ev) { + Serial.print(os_getTime()); + Serial.print(": "); + switch(ev) { + case EV_SCAN_TIMEOUT: + Serial.println(F("EV_SCAN_TIMEOUT")); + break; + case EV_BEACON_FOUND: + Serial.println(F("EV_BEACON_FOUND")); + break; + case EV_BEACON_MISSED: + Serial.println(F("EV_BEACON_MISSED")); + break; + case EV_BEACON_TRACKED: + Serial.println(F("EV_BEACON_TRACKED")); + break; + case EV_JOINING: + Serial.println(F("EV_JOINING")); + break; + case EV_JOINED: + Serial.println(F("EV_JOINED")); + break; + /* + || This event is defined but not used in the code. No + || point in wasting codespace on it. + || + || case EV_RFU1: + || Serial.println(F("EV_RFU1")); + || break; + */ + case EV_JOIN_FAILED: + Serial.println(F("EV_JOIN_FAILED")); + break; + case EV_REJOIN_FAILED: + Serial.println(F("EV_REJOIN_FAILED")); + break; + case EV_TXCOMPLETE: + Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); + if (LMIC.txrxFlags & TXRX_ACK) + Serial.println(F("Received ack")); + if (LMIC.dataLen) { + Serial.println(F("Received ")); + Serial.println(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + // Schedule next transmission + os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); + break; + case EV_LOST_TSYNC: + Serial.println(F("EV_LOST_TSYNC")); + break; + case EV_RESET: + Serial.println(F("EV_RESET")); + break; + case EV_RXCOMPLETE: + // data received in ping slot + Serial.println(F("EV_RXCOMPLETE")); + break; + case EV_LINK_DEAD: + Serial.println(F("EV_LINK_DEAD")); + break; + case EV_LINK_ALIVE: + Serial.println(F("EV_LINK_ALIVE")); + break; + /* + || This event is defined but not used in the code. No + || point in wasting codespace on it. + || + || case EV_SCAN_FOUND: + || Serial.println(F("EV_SCAN_FOUND")); + || break; + */ + case EV_TXSTART: + Serial.println(F("EV_TXSTART")); + break; + default: + Serial.print(F("Unknown event: ")); + Serial.println((unsigned) ev); + break; + } +} + +void do_send(osjob_t* j){ + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + Serial.println(F("OP_TXRXPEND, not sending")); + } else { + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0); + Serial.println(F("Packet queued")); + } + // Next TX is scheduled after TX_COMPLETE event. +} + +void setup() { +// pinMode(13, OUTPUT); + while (!Serial); // wait for Serial to be initialized + Serial.begin(115200); + delay(100); // per sample code on RF_95 test + Serial.println(F("Starting")); + + #ifdef VCC_ENABLE + // For Pinoccio Scout boards + pinMode(VCC_ENABLE, OUTPUT); + digitalWrite(VCC_ENABLE, HIGH); + delay(1000); + #endif + + // LMIC init + os_init(); + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + // Set static session parameters. Instead of dynamically establishing a session + // by joining the network, precomputed session parameters are be provided. + #ifdef PROGMEM + // On AVR, these values are stored in flash and only copied to RAM + // once. Copy them to a temporary buffer here, LMIC_setSession will + // copy them into a buffer of its own again. + uint8_t appskey[sizeof(APPSKEY)]; + uint8_t nwkskey[sizeof(NWKSKEY)]; + memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); + memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); + LMIC_setSession (0x13, DEVADDR, nwkskey, appskey); + #else + // If not running an AVR with PROGMEM, just use the arrays directly + LMIC_setSession (0x13, DEVADDR, NWKSKEY, APPSKEY); + #endif + + #if defined(CFG_eu868) + // Set up the channels used by the Things Network, which corresponds + // to the defaults of most gateways. Without this, only three base + // channels from the LoRaWAN specification are used, which certainly + // works, so it is good for debugging, but can overload those + // frequencies, so be sure to configure the full frequency range of + // your network here (unless your network autoconfigures them). + // Setting up channels should happen after LMIC_setSession, as that + // configures the minimal channel set. + LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band + LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band + // TTN defines an additional channel at 869.525Mhz using SF9 for class B + // devices' ping slots. LMIC does not have an easy way to define set this + // frequency and support for class B is spotty and untested, so this + // frequency is not configured here. + #elif defined(CFG_us915) + // NA-US channels 0-71 are configured automatically + // but only one group of 8 should (a subband) should be active + // TTN recommends the second sub band, 1 in a zero based count. + // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json + LMIC_selectSubBand(1); + #endif + + // Disable link check validation + LMIC_setLinkCheckMode(0); + + // TTN uses SF9 for its RX2 window. + LMIC.dn2Dr = DR_SF9; + + // Set data rate and transmit power for uplink + LMIC_setDrTxpow(DR_SF7,14); + + // Start job + do_send(&sendjob); +} + +void loop() { + unsigned long now; + now = millis(); + if ((now & 512) != 0) { + digitalWrite(13, HIGH); + } + else { + digitalWrite(13, LOW); + } + + os_runloop_once(); + +} + +/// arduino emulation on rpi + +volatile sig_atomic_t force_exit = 0; +SerialSimulator Serial; + +void sig_handler(int sig) +{ + printf("\nBreak received, exiting!\n"); + force_exit=true; +} + +int main(void) +{ + // caught CTRL-C to do clean-up + signal(SIGINT, sig_handler); + + printf("%s Starting\n", __BASEFILE__); + + // Init GPIO bcm + if (!bcm2835_init()) { + fprintf( stderr, "bcm2835_init() Failed\n\n" ); + return 1; + } + + // Show board config + //printConfig(RF_LED_PIN); + printKeys(); + + // Light off on board LED + //pinMode(RF_LED_PIN, OUTPUT); + //digitalWrite(RF_LED_PIN, HIGH); + + setup(); + while(!force_exit) { + loop(); + + // We're on a multitasking OS let some time for others + // Without this one CPU is 99% and with this one just 3% + // On a Raspberry PI 3 + usleep(1000); + } + + // We're here because we need to exit, do it clean + + // Light off on board LED + //digitalWrite(RF_LED_PIN, LOW); + + // module CS line High + digitalWrite(lmic_pins.nss, HIGH); + printf( "\n%s, done my job!\n", __BASEFILE__ ); + bcm2835_close(); + return 0; +} diff --git a/examples/raspi/ttn-otaa-sensors/Makefile b/examples/raspi/ttn-otaa-sensors/Makefile new file mode 100644 index 00000000..56a62281 --- /dev/null +++ b/examples/raspi/ttn-otaa-sensors/Makefile @@ -0,0 +1,9 @@ +# Sample for ttn-abp example on Raspberry Pi +# Caution: requires bcm2835 library to be already installed +# http://www.airspayce.com/mikem/bcm2835/ + +BIN=ttn-otaa-sensors + +LIBDIR=../ +all: $(BIN) +include ../Makefile.env diff --git a/examples/raspi/ttn-otaa-sensors/bmp180.cpp b/examples/raspi/ttn-otaa-sensors/bmp180.cpp new file mode 100644 index 00000000..4ec21e49 --- /dev/null +++ b/examples/raspi/ttn-otaa-sensors/bmp180.cpp @@ -0,0 +1,377 @@ +/* + SFE_BMP180.cpp + Bosch BMP180 pressure sensor library for the Arduino microcontroller + Mike Grusin, SparkFun Electronics + + Uses floating-point equations from the Weather Station Data Logger project + http://wmrx00.sourceforge.net/ + http://wmrx00.sourceforge.net/Arduino/BMP085-Calcs.pdf + + Forked from BMP085 library by M.Grusin + + version 1.0 2013/09/20 initial version + Verison 1.1.2 - Updated for Arduino 1.6.4 5/2015 + Version 1.1.3 - Updated for Raspberry PI by Charles-Henri Hallard (hallard.me) + + Our example code uses the "beerware" license. You can do anything + you like with this code. No really, anything. If you find it useful, + buy me a (root) beer someday. + + August 2016 - Charles-Henri Hallard : Addapted to Raspberry PI + + Requires bcm2835 library to be already installed + http://www.airspayce.com/mikem/bcm2835/ + +*/ + +#include "bmp180.h" + +SFE_BMP180::SFE_BMP180() +// Base library type +{ +} + +char SFE_BMP180::begin() +// Initialize library for subsequent pressure measurements +{ + double c3,c4,b1; + + // Be sure to set I2C address + bcm2835_i2c_setSlaveAddress (BMP180_ADDR); + + // The BMP180 includes factory calibration data stored on the device. + // Each device has different numbers, these must be retrieved and + // used in the calculations when taking pressure measurements. + + // Retrieve calibration data from device: + + if (readInt(0xAA,AC1) && + readInt(0xAC,AC2) && + readInt(0xAE,AC3) && + readUInt(0xB0,AC4) && + readUInt(0xB2,AC5) && + readUInt(0xB4,AC6) && + readInt(0xB6,VB1) && + readInt(0xB8,VB2) && + readInt(0xBA,MB) && + readInt(0xBC,MC) && + readInt(0xBE,MD)) + { + + // All reads completed successfully! + + // If you need to check your math using known numbers, + // you can uncomment one of these examples. + // (The correct results are commented in the below functions.) + + // Example from Bosch datasheet + // AC1 = 408; AC2 = -72; AC3 = -14383; AC4 = 32741; AC5 = 32757; AC6 = 23153; + // B1 = 6190; B2 = 4; MB = -32768; MC = -8711; MD = 2868; + + // Example from http://wmrx00.sourceforge.net/Arduino/BMP180-Calcs.pdf + // AC1 = 7911; AC2 = -934; AC3 = -14306; AC4 = 31567; AC5 = 25671; AC6 = 18974; + // VB1 = 5498; VB2 = 46; MB = -32768; MC = -11075; MD = 2432; + + /* + Serial.print("AC1: "); Serial.println(AC1); + Serial.print("AC2: "); Serial.println(AC2); + Serial.print("AC3: "); Serial.println(AC3); + Serial.print("AC4: "); Serial.println(AC4); + Serial.print("AC5: "); Serial.println(AC5); + Serial.print("AC6: "); Serial.println(AC6); + Serial.print("VB1: "); Serial.println(VB1); + Serial.print("VB2: "); Serial.println(VB2); + Serial.print("MB: "); Serial.println(MB); + Serial.print("MC: "); Serial.println(MC); + Serial.print("MD: "); Serial.println(MD); + */ + + // Compute floating-point polynominals: + + c3 = 160.0 * pow(2,-15) * AC3; + c4 = pow(10,-3) * pow(2,-15) * AC4; + b1 = pow(160,2) * pow(2,-30) * VB1; + c5 = (pow(2,-15) / 160) * AC5; + c6 = AC6; + mc = (pow(2,11) / pow(160,2)) * MC; + md = MD / 160.0; + x0 = AC1; + x1 = 160.0 * pow(2,-13) * AC2; + x2 = pow(160,2) * pow(2,-25) * VB2; + y0 = c4 * pow(2,15); + y1 = c4 * c3; + y2 = c4 * b1; + p0 = (3791.0 - 8.0) / 1600.0; + p1 = 1.0 - 7357.0 * pow(2,-20); + p2 = 3038.0 * 100.0 * pow(2,-36); + + /* + Serial.println(); + Serial.print("c3: "); Serial.println(c3); + Serial.print("c4: "); Serial.println(c4); + Serial.print("c5: "); Serial.println(c5); + Serial.print("c6: "); Serial.println(c6); + Serial.print("b1: "); Serial.println(b1); + Serial.print("mc: "); Serial.println(mc); + Serial.print("md: "); Serial.println(md); + Serial.print("x0: "); Serial.println(x0); + Serial.print("x1: "); Serial.println(x1); + Serial.print("x2: "); Serial.println(x2); + Serial.print("y0: "); Serial.println(y0); + Serial.print("y1: "); Serial.println(y1); + Serial.print("y2: "); Serial.println(y2); + Serial.print("p0: "); Serial.println(p0); + Serial.print("p1: "); Serial.println(p1); + Serial.print("p2: "); Serial.println(p2); + */ + + // Success! + return(1); + } + else + { + // Error reading calibration data; bad component or connection? + return(0); + } +} + + +char SFE_BMP180::readInt(char address, int16_t &value) +// Read a signed integer (two bytes) from device +// address: register to start reading (plus subsequent register) +// value: external variable to store data (function modifies value) +{ + unsigned char data[2]; + + data[0] = address; + if (readBytes(data,2)) + { + value = (int16_t)((data[0]<<8)|data[1]); + //if (*value & 0x8000) *value |= 0xFFFF0000; // sign extend if negative + return(1); + } + value = 0; + return(0); +} + + +char SFE_BMP180::readUInt(char address, uint16_t &value) +// Read an unsigned integer (two bytes) from device +// address: register to start reading (plus subsequent register) +// value: external variable to store data (function modifies value) +{ + unsigned char data[2]; + + data[0] = address; + if (readBytes(data,2)) + { + value = (((uint16_t)data[0]<<8)|(uint16_t)data[1]); + return(1); + } + value = 0; + return(0); +} + + +char SFE_BMP180::readBytes(unsigned char *values, char length) +// Read an array of bytes from device +// values: external array to hold data. Put starting register in values[0]. +// length: number of bytes to read +{ + _error = bcm2835_i2c_write((const char *) values, 1); + if (_error == BCM2835_I2C_REASON_OK ) + { + _error = bcm2835_i2c_read( (char*) values, length); + if (_error == BCM2835_I2C_REASON_OK ) { + return(1); + } + } + return(0); +} + +char SFE_BMP180::writeBytes(unsigned char *values, char length) +// Write an array of bytes to device +// values: external array of data to write. Put starting register in values[0]. +// length: number of bytes to write +{ + _error = bcm2835_i2c_write( (const char*)values, length); + if (_error == BCM2835_I2C_REASON_OK ) { + return(1); + } + return(0); +} + + +char SFE_BMP180::startTemperature(void) +// Begin a temperature reading. +// Will return delay in ms to wait, or 0 if I2C error +{ + unsigned char data[2], result; + + data[0] = BMP180_REG_CONTROL; + data[1] = BMP180_COMMAND_TEMPERATURE; + result = writeBytes(data, 2); + if (result) // good write? + return(5); // return the delay in ms (rounded up) to wait before retrieving data + else + return(0); // or return 0 if there was a problem communicating with the BMP +} + + +char SFE_BMP180::getTemperature(double &T) +// Retrieve a previously-started temperature reading. +// Requires begin() to be called once prior to retrieve calibration parameters. +// Requires startTemperature() to have been called prior and sufficient time elapsed. +// T: external variable to hold result. +// Returns 1 if successful, 0 if I2C error. +{ + unsigned char data[2]; + char result; + double tu, a; + + data[0] = BMP180_REG_RESULT; + + result = readBytes(data, 2); + if (result) // good read, calculate temperature + { + tu = (data[0] * 256.0) + data[1]; + + //example from Bosch datasheet + //tu = 27898; + + //example from http://wmrx00.sourceforge.net/Arduino/BMP085-Calcs.pdf + //tu = 0x69EC; + + a = c5 * (tu - c6); + T = a + (mc / (a + md)); + + /* + Serial.println(); + Serial.print("tu: "); Serial.println(tu); + Serial.print("a: "); Serial.println(a); + Serial.print("T: "); Serial.println(*T); + */ + } + return(result); +} + + +char SFE_BMP180::startPressure(char oversampling) +// Begin a pressure reading. +// Oversampling: 0 to 3, higher numbers are slower, higher-res outputs. +// Will return delay in ms to wait, or 0 if I2C error. +{ + unsigned char data[2], result, delay; + + data[0] = BMP180_REG_CONTROL; + + switch (oversampling) + { + case 0: + data[1] = BMP180_COMMAND_PRESSURE0; + delay = 5; + break; + case 1: + data[1] = BMP180_COMMAND_PRESSURE1; + delay = 8; + break; + case 2: + data[1] = BMP180_COMMAND_PRESSURE2; + delay = 14; + break; + case 3: + data[1] = BMP180_COMMAND_PRESSURE3; + delay = 26; + break; + default: + data[1] = BMP180_COMMAND_PRESSURE0; + delay = 5; + break; + } + result = writeBytes(data, 2); + if (result) // good write? + return(delay); // return the delay in ms (rounded up) to wait before retrieving data + else + return(0); // or return 0 if there was a problem communicating with the BMP +} + + +char SFE_BMP180::getPressure(double &P, double &T) +// Retrieve a previously started pressure reading, calculate abolute pressure in mbars. +// Requires begin() to be called once prior to retrieve calibration parameters. +// Requires startPressure() to have been called prior and sufficient time elapsed. +// Requires recent temperature reading to accurately calculate pressure. + +// P: external variable to hold pressure. +// T: previously-calculated temperature. +// Returns 1 for success, 0 for I2C error. + +// Note that calculated pressure value is absolute mbars, to compensate for altitude call sealevel(). +{ + unsigned char data[3]; + char result; + double pu,s,x,y,z; + + data[0] = BMP180_REG_RESULT; + + result = readBytes(data, 3); + if (result) // good read, calculate pressure + { + pu = (data[0] * 256.0) + data[1] + (data[2]/256.0); + + //example from Bosch datasheet + //pu = 23843; + + //example from http://wmrx00.sourceforge.net/Arduino/BMP085-Calcs.pdf, pu = 0x982FC0; + //pu = (0x98 * 256.0) + 0x2F + (0xC0/256.0); + + s = T - 25.0; + x = (x2 * pow(s,2)) + (x1 * s) + x0; + y = (y2 * pow(s,2)) + (y1 * s) + y0; + z = (pu - x) / y; + P = (p2 * pow(z,2)) + (p1 * z) + p0; + + /* + Serial.println(); + Serial.print("pu: "); Serial.println(pu); + Serial.print("T: "); Serial.println(*T); + Serial.print("s: "); Serial.println(s); + Serial.print("x: "); Serial.println(x); + Serial.print("y: "); Serial.println(y); + Serial.print("z: "); Serial.println(z); + Serial.print("P: "); Serial.println(*P); + */ + } + return(result); +} + + +double SFE_BMP180::sealevel(double P, double A) +// Given a pressure P (mb) taken at a specific altitude (meters), +// return the equivalent pressure (mb) at sea level. +// This produces pressure readings that can be used for weather measurements. +{ + return(P/pow(1-(A/44330.0),5.255)); +} + + +double SFE_BMP180::altitude(double P, double P0) +// Given a pressure measurement P (mb) and the pressure at a baseline P0 (mb), +// return altitude (meters) above baseline. +{ + return(44330.0*(1-pow(P/P0,1/5.255))); +} + + +char SFE_BMP180::getError(void) + // If any library command fails, you can retrieve an extended + // error code using this command. Errors are from the wire library: + // 0 = Success + // 1 = Data too long to fit in transmit buffer + // 2 = Received NACK on transmit of address + // 3 = Received NACK on transmit of data + // 4 = Other error +{ + return(_error); +} + diff --git a/examples/raspi/ttn-otaa-sensors/bmp180.h b/examples/raspi/ttn-otaa-sensors/bmp180.h new file mode 100644 index 00000000..529032e1 --- /dev/null +++ b/examples/raspi/ttn-otaa-sensors/bmp180.h @@ -0,0 +1,127 @@ +/* + SFE_BMP180.h + Bosch BMP180 pressure sensor library for the Arduino microcontroller + Mike Grusin, SparkFun Electronics + + Uses floating-point equations from the Weather Station Data Logger project + http://wmrx00.sourceforge.net/ + http://wmrx00.sourceforge.net/Arduino/BMP085-Calcs.pdf + + Forked from BMP085 library by M.Grusin + + version 1.0 2013/09/20 initial version + Verison 1.1.2 - Updated for Arduino 1.6.4 5/2015 + Version 1.1.3 - Updated for Raspberry PI by Charles-Henri Hallard (hallard.me) + + Our example code uses the "beerware" license. You can do anything + you like with this code. No really, anything. If you find it useful, + buy me a (root) beer someday. + + August 2016 - Charles-Henri Hallard : Addapted to Raspberry PI + + Requires bcm2835 library to be already installed + http://www.airspayce.com/mikem/bcm2835/ + +*/ + +#ifndef SFE_BMP180_h +#define SFE_BMP180_h + +#include +#include +#include + +class SFE_BMP180 +{ + public: + SFE_BMP180(); // base type + + char begin(); + // call pressure.begin() to initialize BMP180 before use + // returns 1 if success, 0 if failure (bad component or I2C bus shorted?) + + char startTemperature(void); + // command BMP180 to start a temperature measurement + // returns (number of ms to wait) for success, 0 for fail + + char getTemperature(double &T); + // return temperature measurement from previous startTemperature command + // places returned value in T variable (deg C) + // returns 1 for success, 0 for fail + + char startPressure(char oversampling); + // command BMP180 to start a pressure measurement + // oversampling: 0 - 3 for oversampling value + // returns (number of ms to wait) for success, 0 for fail + + char getPressure(double &P, double &T); + // return absolute pressure measurement from previous startPressure command + // note: requires previous temperature measurement in variable T + // places returned value in P variable (mbar) + // returns 1 for success, 0 for fail + + double sealevel(double P, double A); + // convert absolute pressure to sea-level pressure (as used in weather data) + // P: absolute pressure (mbar) + // A: current altitude (meters) + // returns sealevel pressure in mbar + + double altitude(double P, double P0); + // convert absolute pressure to altitude (given baseline pressure; sea-level, runway, etc.) + // P: absolute pressure (mbar) + // P0: fixed baseline pressure (mbar) + // returns signed altitude in meters + + char getError(void); + // If any library command fails, you can retrieve an extended + // error code using this command. Errors are from the wire library: + // 0 = Success + // 1 = Data too long to fit in transmit buffer + // 2 = Received NACK on transmit of address + // 3 = Received NACK on transmit of data + // 4 = Other error + + private: + + char readInt(char address, int16_t &value); + // read an signed int (16 bits) from a BMP180 register + // address: BMP180 register address + // value: external signed int for returned value (16 bits) + // returns 1 for success, 0 for fail, with result in value + + char readUInt(char address, uint16_t &value); + // read an unsigned int (16 bits) from a BMP180 register + // address: BMP180 register address + // value: external unsigned int for returned value (16 bits) + // returns 1 for success, 0 for fail, with result in value + + char readBytes(unsigned char *values, char length); + // read a number of bytes from a BMP180 register + // values: array of char with register address in first location [0] + // length: number of bytes to read back + // returns 1 for success, 0 for fail, with read bytes in values[] array + + char writeBytes(unsigned char *values, char length); + // write a number of bytes to a BMP180 register (and consecutive subsequent registers) + // values: array of char with register address in first location [0] + // length: number of bytes to write + // returns 1 for success, 0 for fail + + int16_t AC1,AC2,AC3,VB1,VB2,MB,MC,MD; + uint16_t AC4,AC5,AC6; + double c5,c6,mc,md,x0,x1,x2,y0,y1,y2,p0,p1,p2; + char _error; +}; + +#define BMP180_ADDR 0x77 // 7-bit address + +#define BMP180_REG_CONTROL 0xF4 +#define BMP180_REG_RESULT 0xF6 + +#define BMP180_COMMAND_TEMPERATURE 0x2E +#define BMP180_COMMAND_PRESSURE0 0x34 +#define BMP180_COMMAND_PRESSURE1 0x74 +#define BMP180_COMMAND_PRESSURE2 0xB4 +#define BMP180_COMMAND_PRESSURE3 0xF4 + +#endif diff --git a/examples/raspi/ttn-otaa-sensors/si7021.c b/examples/raspi/ttn-otaa-sensors/si7021.c new file mode 100644 index 00000000..cda39c16 --- /dev/null +++ b/examples/raspi/ttn-otaa-sensors/si7021.c @@ -0,0 +1,221 @@ +/* + si7021.c + SI7021 Temperature and Humidity sensor for Raspberry PI + Charles-Henri Hallard from http://ch2i.eu + + + version 1.0 2013/09/20 initial version + Verison 1.1.2 - Updated for Arduino 1.6.4 5/2015 + Version 1.1.3 - Updated for Raspberry PI by Charles-Henri Hallard (hallard.me) + + Our example code uses the "beerware" license. You can do anything + you like with this code. No really, anything. If you find it useful, + buy me a (root) beer someday. + + August 2016 - Charles-Henri Hallard : Addapted to Raspberry PI + + Requires bcm2835 library to be already installed + http://www.airspayce.com/mikem/bcm2835/ + +*/ + +#include "si7021.h" + +/* ====================================================================== +Function: si7021_checkCRC +Purpose : check the CRC of received data +Input : value read from sensor +Output : CRC read from sensor +Comments: 0 if okay +====================================================================== */ +uint8_t si7021_checkCRC(uint16_t data, uint8_t check) +{ + uint32_t remainder, divisor; + + //Pad with 8 bits because we have to add in the check value + remainder = (uint32_t)data << 8; + + // From: http://www.nongnu.org/avr-libc/user-manual/group__util__crc.html + // POLYNOMIAL = 0x0131 = x^8 + x^5 + x^4 + 1 : http://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks + // 0x988000 is the 0x0131 polynomial shifted to farthest left of three bytes + divisor = (uint32_t) 0x988000; + + // Add the check value + remainder |= check; + + // Operate on only 16 positions of max 24. + // The remaining 8 are our remainder and should be zero when we're done. + for (uint8_t i = 0 ; i < 16 ; i++) { + //Check if there is a one in the left position + if( remainder & (uint32_t)1<<(23 - i) ) + remainder ^= divisor; + + //Rotate the divisor max 16 times so that we have 8 bits left of a remainder + divisor >>= 1; + } + return ((uint8_t) remainder); +} + +/* ====================================================================== +Function: si7021_StartConv +Purpose : return temperature or humidity measured +Input : data type SI7021_READ_HUM or SI7021_READ_TEMP +Output : BCM2835_I2C_REASON_OK if okay +Comments: internal values of temp and rh are set +====================================================================== */ +uint8_t si7021_StartConv(si7021_e datatype, int16_t * value) +{ + double data; + uint16_t raw ; + uint8_t checksum; + uint8_t error; + uint8_t buf[3]; + + buf[0] = datatype == SI7021_READ_HUM ? SI7021_MEASURE_HUM : SI7021_MEASURE_TEMP; + error = bcm2835_i2c_write((const char *) buf, 1); + if (error != BCM2835_I2C_REASON_OK ) + return error; + + // Wait for data to become available + // always use time out in loop to avoid + // potential lockup (here 90ms (6*15ms)) + usleep(100000); + + error = bcm2835_i2c_read( (char *) buf, sizeof(buf)); + if (error != BCM2835_I2C_REASON_OK ) { + return error; + } + + // read raw value + raw = ( buf[0] << 8) | buf[1] ; + checksum = buf[2]; + + // Check CRC of data received + if(si7021_checkCRC(raw, checksum) != 0) { + printf("CRC Error %02X\n", checksum); + return -1; + } + + if (datatype == SI7021_READ_HUM) { + // Convert value to Humidity percent (*100) + // for 43.21%rh value will be 4321 + data = -600 + (( raw * 12500 ) / 65536 ) ; + + // Datasheet says doing this check + if (data>10000) data = 10000; + if (data<0) data = 0; + + // save value + *value = (int16_t) data; + + } else { + // Convert value to Temperature (*100) + // for 23.45C value will be 2345 + data = (( raw * 17572) / 65536) - 4685; + + // save value + *value = (int16_t) data; + } + + return error; +} + + +/* ====================================================================== +Function: si7021_readRegister +Purpose : read the user register from the sensor +Input : user register value filled by function +Output : BCM2835_I2C_REASON_OK if okay +Comments: - +====================================================================== */ +uint8_t si7021_readRegister(uint8_t * value) +{ + uint8_t error ; + uint8_t buf[1]; + + buf[0] = SI7021_READ_REG; + error = bcm2835_i2c_write((const char *) buf, 1); + if (error != BCM2835_I2C_REASON_OK ) + return error; + + error = bcm2835_i2c_read( (char*) value, 1); + printf("Error=%d Read Buf[0] = %02X\n", error, *value); + + return error; +} + +/* ====================================================================== +Function: si7021_readValues +Purpose : read temperature and humidity from SI7021 sensor +Input : - +Output : 0 if okay +Comments: - +====================================================================== */ +uint8_t si7021_readValues(int16_t * temp, int16_t * hum) +{ + uint8_t error = 0; + + // start humidity conversion + error |= si7021_StartConv(SI7021_READ_HUM, temp); + + // start temperature conversion + error |= si7021_StartConv(SI7021_READ_TEMP, hum); + + return error; +} + +/* ====================================================================== +Function: si7021_setResolution +Purpose : Sets the sensor resolution to one of four levels +Input : see #define is .h file, default is SI7021_RESOLUTION_14T_12RH +Output : temperature or humidity +Comments: BCM2835_I2C_REASON_OK if okay +====================================================================== */ +uint8_t si7021_setResolution(uint8_t res) +{ + uint8_t buf[2]; + uint8_t reg; + uint8_t error; + + // Get the current register value + error = si7021_readRegister(®); + if ( error == BCM2835_I2C_REASON_OK ) { + // remove resolution bits + reg &= SI7021_RESOLUTION_MASK ; + + // Write the new resolution bits but clear unused before + buf[0] = SI7021_WRITE_REG; + buf[1] = reg | ( res &= ~SI7021_RESOLUTION_MASK); + error = bcm2835_i2c_write( (char *) buf, 2); + } + return error; +} + + +/* ====================================================================== +Function: si7021_getID +Purpose : get the device ID +Input : - +Output : device ID +Comments: Return 0 if an error occured reading device ID +====================================================================== */ +uint8_t si7021_getID( void ) +{ + uint8_t id=0; + uint8_t error; + uint8_t buf[2]; + + buf[0] = SI7021_READ_2ND_ID_1; + buf[1] = SI7021_READ_2ND_ID_2; + error = bcm2835_i2c_write( (char *) buf, 2); + if ( error == BCM2835_I2C_REASON_OK ) { + error = bcm2835_i2c_read( (char*) buf, 1); + if ( error == BCM2835_I2C_REASON_OK ) { + id = buf[0]; + } + } + return id; + } + + + diff --git a/examples/raspi/ttn-otaa-sensors/si7021.h b/examples/raspi/ttn-otaa-sensors/si7021.h new file mode 100644 index 00000000..1e261bb4 --- /dev/null +++ b/examples/raspi/ttn-otaa-sensors/si7021.h @@ -0,0 +1,75 @@ +/* + si7021.c + SI7021 Temperature and Humidity sensor for Raspberry PI + Charles-Henri Hallard from http://ch2i.eu + + version 1.0 2013/09/20 initial version + Verison 1.1.2 - Updated for Arduino 1.6.4 5/2015 + Version 1.1.3 - Updated for Raspberry PI by Charles-Henri Hallard (hallard.me) + + Our example code uses the "beerware" license. You can do anything + you like with this code. No really, anything. If you find it useful, + buy me a (root) beer someday. + + August 2016 - Charles-Henri Hallard : Addapted to Raspberry PI + + Requires bcm2835 library to be already installed + http://www.airspayce.com/mikem/bcm2835/ + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +// device ID identifier +#define ID_SI7021 0x15 +#define ID_HTU21D 0x32 + +// ====================================== +// SI7021 sensor +// ====================================== +#define SI7021_I2C_ADDRESS 0x40 // I2C address for the sensor +#define SI7021_MEASURE_TEMP0 0xE0 // Can be read only after a RH conversion done +#define SI7021_MEASURE_TEMP 0xE3 // Default hold +#define SI7021_MEASURE_HUM 0xE5 // Default hold +#define SI7021_MEASURE_NOHOLD 0x80 // NO HOLD Bit flag +#define SI7021_WRITE_REG 0xE6 +#define SI7021_READ_REG 0xE7 +#define SI7021_SOFT_RESET 0xFE +#define SI7021_READ_1ST_ID_1 0xFA +#define SI7021_READ_1ST_ID_2 0x0F +#define SI7021_READ_2ND_ID_1 0xFC +#define SI7021_READ_2ND_ID_2 0xC9 +#define SI7021_READ_FW_REV_1 0x84 +#define SI7021_READ_FW_REV_2 0xB8 + +// SI7021 Sensor resolution +// default at power up is SI7021_RESOLUTION_14T_12RH +#define SI7021_RESOLUTION_14T_12RH 0x00 // 12 bits RH / 14 bits Temp +#define SI7021_RESOLUTION_13T_10RH 0x80 // 10 bits RH / 13 bits Temp +#define SI7021_RESOLUTION_12T_08RH 0x01 // 8 bits RH / 12 bits Temp +#define SI7021_RESOLUTION_11T_11RH 0x81 // 11 bits RH / 11 bits Temp + +#define SI7021_RESOLUTION_MASK 0B01111110 + +// The type of measure we want to trigger on sensor +typedef enum { + SI7021_READ_TEMP, + SI7021_READ_HUM +} +si7021_e; + +// SI7021 temperature / humidity sensor related +uint8_t si7021_StartConv(si7021_e datatype, int16_t * value); +uint8_t si7021_readValues(int32_t * value); +uint8_t si7021_setResolution(uint8_t res); +uint8_t si7021_getID(void); + +double getHumidity(void); +double getTemperature(void); diff --git a/examples/raspi/ttn-otaa-sensors/ttn-otaa-sensors.cpp b/examples/raspi/ttn-otaa-sensors/ttn-otaa-sensors.cpp new file mode 100644 index 00000000..566a978c --- /dev/null +++ b/examples/raspi/ttn-otaa-sensors/ttn-otaa-sensors.cpp @@ -0,0 +1,419 @@ +/******************************************************************************* + * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example sends a valid LoRaWAN packet with payload containing + * value from SI7021 or HTU21D sensor Pressure from BMP180 sensor + * using frequency and encryption settings matching those of + * the The Things Network. + * + * This uses OTAA (Over-the-air activation), where where a DevEUI and + * application key is configured, which are used in an over-the-air + * activation procedure where a DevAddr and session keys are + * assigned/generated for use with all further communication. + * + * Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in + * g1, 0.1% in g2), but not the TTN fair usage policy (which is probably + * violated by this sketch when left running for longer)! + + * To use this sketch, first register your application and device with + * the things network, to set or generate an AppEUI, DevEUI and AppKey. + * Multiple devices can use the same AppEUI, but each device has its own + * DevEUI and AppKey. + * + * Do not forget to define the radio type correctly in LMIC src/config.h. + * + * This sample has been written by Charles-Henri Hallard (hallard.me) + * It's based on original ttn-otaa sample code + * + * Requires bcm2835 library to be already installed + * http://www.airspayce.com/mikem/bcm2835/ + * use the Makefile in this directory: + * cd examples/raspi/ttn-otaa-sensors + * make + * sudo ./ttn-otaa-sensors + * + *******************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "si7021.c" +#include "bmp180.cpp" + +// This EUI must be in little-endian format, so least-significant-byte +// first. When copying an EUI from ttnctl output, this means to reverse +// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,0x70. +static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);} + +// This should also be in little endian format, see above. +static const u1_t PROGMEM DEVEUI[8]={ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +// Here on Raspi we use part of MAC Address do define devEUI so +// This one above is not used, but you can still old method +// reverting the comments on the 2 following line +//void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);} +void os_getDevEui (u1_t* buf) { getDevEuiFromMac(buf); } + +// This key should be in big endian format (or, since it is not really a +// number but a block of memory, endianness does not really apply). In +// practice, a key taken from ttnctl can be copied as-is. +// The key shown here is the semtech default key. +static const u1_t PROGMEM APPKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; +void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);} + +// 4 int16_t values +static uint8_t mydata[4*sizeof(int16_t)] ; +static osjob_t sendjob; + +// Schedule TX every this many seconds (might become longer due to duty) +// cycle limitations). +const unsigned TX_INTERVAL = 120; + +//Flag for Ctrl-C +volatile sig_atomic_t force_exit = 0; + +// BMP180 library object +SFE_BMP180 bmp180; +#define ALTITUDE 98 // Chasseuil-du-Poitou FR +//#define ALTITUDE 118 // Montamise FR +//#define ALTITUDE 0 // To get sea-leval altitude +static char dev[16]=""; + + +// LoRasPi board +// see https://github.com/hallard/LoRasPI +#define RF_LED_PIN RPI_V2_GPIO_P1_16 // Led on GPIO23 so P1 connector pin #16 +#define RF_CS_PIN RPI_V2_GPIO_P1_24 // Slave Select on CE0 so P1 connector pin #24 +#define RF_IRQ_PIN RPI_V2_GPIO_P1_22 // IRQ on GPIO25 so P1 connector pin #22 +#define RF_RST_PIN RPI_V2_GPIO_P1_15 // RST on GPIO22 so P1 connector pin #15 + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = RF_CS_PIN, + .rxtx = LMIC_UNUSED_PIN, + .rst = RF_RST_PIN, + .dio = {LMIC_UNUSED_PIN, LMIC_UNUSED_PIN, LMIC_UNUSED_PIN}, +}; + +/* ====================================================================== +Function: getBMP180Values +Purpose : Return temperature and pressure from BPM180 sensor +Input : Altitude +Output : 0 if error otherwise + pressure * 10 filled with absolute pressure or sea-leval + temperature * 100 filled with temperature +Comments: if altitude = 0 then return sea level pressure + value are returned as int16_t to preserve paylaod size +====================================================================== */ +uint8_t getBMP180Values(double altitude, int16_t * pressure, int16_t * temperature) +{ + char status; + double T,P,p0; + + // You must first get a temperature measurement to perform a pressure reading. + + // Start a temperature measurement: + // If request is successful, the number of ms to wait is returned. + // If request is unsuccessful, 0 is returned. + status = bmp180.startTemperature(); + if (status != 0) { + // Wait for the measurement to complete: + usleep( status * 1000); + + // Retrieve the completed temperature measurement: + // Note that the measurement is stored in the variable T. + // Use '&T' to provide the address of T to the function. + // Function returns 1 if successful, 0 if failure. + status = bmp180.getTemperature(T); + if (status != 0) { + // Start a pressure measurement: + // The parameter is the oversampling setting, from 0 to 3 (highest res, longest wait). + // If request is successful, the number of ms to wait is returned. + // If request is unsuccessful, 0 is returned. + + // Print out the measurement: + //printf("BMP180 Temperature: %.2f deg C\n", T); + *temperature = (int16_t) (T*100); + + status = bmp180.startPressure(3); + if (status != 0) { + // Wait for the measurement to complete: + usleep(status*1000); + + // Retrieve the completed pressure measurement: + // Note that the measurement is stored in the variable P. + // Use '&P' to provide the address of P. + // Note also that the function requires the previous temperature measurement (T). + // (If temperature is stable, you can do one temperature measurement for a number of pressure measurements.) + // Function returns 1 if successful, 0 if failure. + status = bmp180.getPressure(P,T); + + if (status != 0) { + // Print out the measurement: + //printf("absolute pressure: %.1f mb\n", P ); + + // The pressure sensor returns abolute pressure, which varies with altitude. + // To remove the effects of altitude, use the sealevel function and your current altitude. + // This number is commonly used in weather reports. + // Parameters: P = absolute pressure in mb, ALTITUDE = current altitude in m. + // Result: p0 sea-level compensated pressure in mb + p0 = bmp180.sealevel(P, altitude); + //printf("relative (sea-level) pressure: %.1f mb\n", p0); + + // On the other hand, if you want to determine your altitude from the pressure reading, + // use the altitude function along with a baseline pressure (sea-level or other). + // Parameters: P = absolute pressure in mb, p0 = baseline pressure in mb. + // Result: altitude in m. + //printf("computed altitude: %.1f meters\n", bmp180.altitude(P,p0)); + if ( altitude==0) { + *pressure = (int16_t) (P*10) ; + } else { + *pressure = (int16_t) (p0*10); + } + return 1; + } else { + printf("error retrieving pressure measurement\n"); + } + } else { + printf("error starting pressure measurement\n"); + } + } else { + printf("error retrieving temperature measurement\n"); + } + } else { + printf("error starting temperature measurement\n"); + } + + return 0; +} + + +/* ====================================================================== +Function: do_send +Purpose : Measures sensors values and Send a LoraWAN packet +Input : osjob_t * +Output : - +Comments: - +====================================================================== */ +void do_send(osjob_t* j) { + int16_t bmp_pres, bmp_temp, si_temp, si_hum; + char strTime[16]; + getSystemTime(strTime , sizeof(strTime)); + printf("%s: ", strTime); + + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + printf("OP_TXRXPEND, not sending\n"); + } else { + // Clear our payload buffer + memset(mydata,0,sizeof(mydata)); + + printf("Packet queued => "); + // select BMP180 I2C device + bcm2835_i2c_setSlaveAddress (BMP180_ADDR); + if ( getBMP180Values(ALTITUDE, &bmp_pres, &bmp_temp) != 0 ) { + printf("BMP180:%.2fC %.1fmb", bmp_temp/100.0f, bmp_pres/10.0f); + mydata[0] = bmp_temp >> 8; // MSB + mydata[1] = bmp_temp & 0xFF; // LSB + mydata[2] = bmp_pres >> 8; // MSB + mydata[3] = bmp_pres & 0xFF; // LSB + } + + // select SI7021 I2C device + bcm2835_i2c_setSlaveAddress (SI7021_I2C_ADDRESS); + printf(" %s:", dev); + if (si7021_StartConv(SI7021_READ_TEMP, &si_temp)==BCM2835_I2C_REASON_OK ) { + printf("%.2fC ", si_temp /100.0f); + mydata[4] = si_temp >> 8; // MSB + mydata[5] = si_temp & 0xFF; // LSB + } + if ( si7021_StartConv(SI7021_READ_HUM, &si_hum)==BCM2835_I2C_REASON_OK ) { + printf("%.1f%%rh", si_hum / 100.0f); + mydata[6] = si_hum >> 8; // MSB + mydata[7] = si_hum & 0xFF; // LSB + } + + printf("\n"); + + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(1, mydata, sizeof(mydata), 0); + } + // Next TX is scheduled after TX_COMPLETE event. +} + +/* ====================================================================== +Function: onEvent +Purpose : callback fired by LMIC stack for event management +Input : ev_t +Output : - +Comments: - +====================================================================== */ +void onEvent (ev_t ev) { + + char strTime[16]; + getSystemTime(strTime , sizeof(strTime)); + printf("%s: ", strTime); + + switch(ev) { + + case EV_JOINED: + printf("EV_JOINED\n"); + + // Disable link check validation (automatically enabled + // during join, but not supported by TTN at this time). + LMIC_setLinkCheckMode(0); + break; + + case EV_TXCOMPLETE: + printf("EV_TXCOMPLETE (includes waiting for RX windows)\n"); + if (LMIC.txrxFlags & TXRX_ACK) + printf("%s Received ack\n", strTime); + if (LMIC.dataLen) { + printf("%s Received %d bytes of payload\n", strTime, LMIC.dataLen); + } + // Light Off LED + digitalWrite(RF_LED_PIN, LOW); + // Schedule next transmission + os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); + break; + + case EV_SCAN_TIMEOUT: printf("EV_SCAN_TIMEOUT\n"); break; + case EV_BEACON_FOUND: printf("EV_BEACON_FOUND\n"); break; + case EV_BEACON_MISSED: printf("EV_BEACON_MISSED\n"); break; + case EV_BEACON_TRACKED: printf("EV_BEACON_TRACKED\n");break; + case EV_JOINING: printf("EV_JOINING\n"); break; + case EV_RFU1: printf("EV_RFU1\n"); break; + case EV_JOIN_FAILED: printf("EV_JOIN_FAILED\n"); break; + case EV_REJOIN_FAILED: printf("EV_REJOIN_FAILED\n"); break; + case EV_LOST_TSYNC: printf("EV_LOST_TSYNC\n"); break; + case EV_RESET: printf("EV_RESET\n"); break; + case EV_RXCOMPLETE: printf("EV_RXCOMPLETE\n"); break; + case EV_LINK_DEAD: printf("EV_LINK_DEAD\n"); break; + case EV_LINK_ALIVE: printf("EV_LINK_ALIVE\n"); break; + default: + printf("Unknown event\n"); + break; + } +} + + +/* ====================================================================== +Function: sig_handler +Purpose : Intercept CTRL-C keyboard to close application +Input : signal received +Output : - +Comments: - +====================================================================== */ +void sig_handler(int sig) +{ + printf("\nBreak received, exiting!\n"); + force_exit=true; +} + +/* ====================================================================== +Function: main +Purpose : Main routine +Input : - +Output : - +Comments: - +====================================================================== */ +int main () +{ + // caught CTRL-C to do clean-up + signal(SIGINT, sig_handler); + + printf("%s Starting\n", __BASEFILE__); + + // Display Hardware RFM95 configuration + printConfig(RF_LED_PIN); + printKeys(); + + // Init GPIO bcm + if (!bcm2835_init()) { + fprintf( stderr, "bcm2835_init() Failed\n\n" ); + return 1; + } + + // Light on LED + pinMode(RF_LED_PIN, OUTPUT); + digitalWrite(RF_LED_PIN, HIGH); + + if (!bcm2835_i2c_begin()) { + fprintf( stderr, "bcm2835_i2c_begin() failed. Are you running as root??\n"); + return 1; + } + + // Set I2C speed to 100KHz + bcm2835_i2c_set_baudrate(100000); + + // Init BMP180 device and check it's here + printf("Checking BMP180 device..."); + if (bmp180.begin()) { + printf("found\n"); + } else { + printf("fail, check wiring\n\n"); + } + + // select SI7021 I2C device + printf("Checking SI7021 or HTU21D device..."); + bcm2835_i2c_setSlaveAddress (SI7021_I2C_ADDRESS); + uint8_t id = si7021_getID(); + // Highest resolution and check device is here + if ( id == ID_SI7021 ) { + strcpy(dev, "SI7021"); + printf("%s found\n", dev ); + + } else if ( id == ID_HTU21D ) { + strcpy(dev, "HTU21D"); + printf("%s found\n", dev); + } else { + strcpy(dev, "Error"); + printf("No SI7021 or HTU21D detected, check wiring\n\n"); + } + + // LMIC init + os_init(); + + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + // Start job (sending automatically starts OTAA too) + // Then on transmit will reset it's own send + do_send(&sendjob); + + // Main loop until CTRL-C + while (!force_exit) { + + os_runloop_once(); + + // We're on a multitasking OS let some time for others + // Without this one CPU is 99% and with this one just 3% + // On a Raspberry PI 3 + usleep(1000); + } + + // We're here because we need to exit, do it clean + + // Light off on board LED + digitalWrite(RF_LED_PIN, LOW); + + // Release I2C and BCM2835 + bcm2835_i2c_end(); + bcm2835_close(); + return 0; +} diff --git a/examples/raspi/ttn-otaa/Makefile b/examples/raspi/ttn-otaa/Makefile new file mode 100644 index 00000000..c5bf9331 --- /dev/null +++ b/examples/raspi/ttn-otaa/Makefile @@ -0,0 +1,9 @@ +# Sample for ttn-abp example on Raspberry Pi +# Caution: requires bcm2835 library to be already installed +# http://www.airspayce.com/mikem/bcm2835/ + +BIN=ttn-otaa + +LIBDIR=../ +all: $(BIN) +include ../Makefile.env diff --git a/examples/raspi/ttn-otaa/ttn-otaa.cpp b/examples/raspi/ttn-otaa/ttn-otaa.cpp new file mode 100644 index 00000000..5d7490a2 --- /dev/null +++ b/examples/raspi/ttn-otaa/ttn-otaa.cpp @@ -0,0 +1,279 @@ +/******************************************************************************* + * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example sends a valid LoRaWAN packet with payload "Hello, + * world!", using frequency and encryption settings matching those of + * the The Things Network. + * + * This uses OTAA (Over-the-air activation), where where a DevEUI and + * application key is configured, which are used in an over-the-air + * activation procedure where a DevAddr and session keys are + * assigned/generated for use with all further communication. + * + * Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in + * g1, 0.1% in g2), but not the TTN fair usage policy (which is probably + * violated by this sketch when left running for longer)! + + * To use this sketch, first register your application and device with + * the things network, to set or generate an AppEUI, DevEUI and AppKey. + * Multiple devices can use the same AppEUI, but each device has its own + * DevEUI and AppKey. + * + * Do not forget to define the radio type correctly in config.h. + * + *******************************************************************************/ + +#include +#include +#include +#include + +#include +#include + +#include + +// This EUI must be in little-endian format, so least-significant-byte + +// first. When copying an EUI from ttnctl output, this means to reverse +// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,0x70. +static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);} + +// This should also be in little endian format, see above. +static const u1_t PROGMEM DEVEUI[8]={ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +// Here on Raspi we use part of MAC Address do define devEUI so +// This one above is not used, but you can still old method +// reverting the comments on the 2 following line +//void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);} +void os_getDevEui (u1_t* buf) { getDevEuiFromMac(buf); } + +// This key should be in big endian format (or, since it is not really a +// number but a block of memory, endianness does not really apply). In +// practice, a key taken from ttnctl can be copied as-is. +// The key shown here is the semtech default key. +static const u1_t PROGMEM APPKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; +void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);} + +static uint8_t mydata[] = "Raspi LMIC!"; +static osjob_t sendjob; + +// Schedule TX every this many seconds (might become longer due to duty) +// cycle limitations). +const unsigned TX_INTERVAL = 120; + +//Flag for Ctrl-C +volatile sig_atomic_t force_exit = 0; + +// LoRasPi board +// see https://github.com/hallard/LoRasPI +//#define RF_LED_PIN RPI_V2_GPIO_P1_16 // Led on GPIO23 so P1 connector pin #16 +//#define RF_CS_PIN RPI_V2_GPIO_P1_24 // Slave Select on CE0 so P1 connector pin #24 +//#define RF_DIO0_PIN RPI_V2_GPIO_P1_22 // IRQ on GPIO25 so P1 connector pin #22 +//#define RF_RST_PIN RPI_V2_GPIO_P1_15 // RST on GPIO22 so P1 connector pin #15 + +// Raspberri PI Lora Gateway for multiple modules +// see https://github.com/hallard/RPI-Lora-Gateway +// Module 1 on board RFM95 868 MHz (example) +//#define RF_LED_PIN RPI_V2_GPIO_P1_07 // Led on GPIO4 so P1 connector pin #7 +//#define RF_CS_PIN RPI_V2_GPIO_P1_24 // Slave Select on CE0 so P1 connector pin #24 +//#define RF_DIO0_PIN RPI_V2_GPIO_P1_22 // IRQ on GPIO25 so P1 connector pin #22 +//#define RF_RST_PIN RPI_V2_GPIO_P1_29 // Reset on GPIO5 so P1 connector pin #29 + +// Dragino Raspberry PI hat (no onboard led) +// see https://github.com/dragino/Lora +//#define RF_CS_PIN RPI_V2_GPIO_P1_22 // Slave Select on GPIO25 so P1 connector pin #22 +//#define RF_DIO0_PIN RPI_V2_GPIO_P1_07 // IRQ on GPIO4 so P1 connector pin #7 +//#define RF_RST_PIN RPI_V2_GPIO_P1_11 // Reset on GPIO17 so P1 connector pin #11 + +// Chistera-Pi (no onboard led) +// see https://www.framboise314.fr/chistera-pi-lora-a-portee-de-main-grace-a-snootlab/ +#define RF_CS_PIN RPI_V2_GPIO_P1_24 // Slave Select on CE0/GPIO8 so P1 connector pin #24 +#define RF_DIO0_PIN RPI_V2_GPIO_P1_07 // IRQ on GPIO4 so P1 connector pin #7 +#define RF_DIO1_PIN RPI_V2_GPIO_P1_16 // IRQ on GPIO23 so P1 connector pin #7 +#define RF_RST_PIN RPI_V2_GPIO_P1_11 // Reset on GPIO17 so P1 connector pin #11 + +#ifndef RF_DIO0_PIN +#define RF_DIO0_PIN LMIC_UNUSED_PIN +#endif +#ifndef RF_DIO1_PIN +#define RF_DIO1_PIN LMIC_UNUSED_PIN +#endif +#ifndef RF_DIO2_PIN +#define RF_DIO2_PIN LMIC_UNUSED_PIN +#endif + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = RF_CS_PIN, + .rxtx = LMIC_UNUSED_PIN, + .rst = RF_RST_PIN, + .dio = {RF_DIO0_PIN,RF_DIO1_PIN,RF_DIO2_PIN}, +}; + +#ifndef RF_LED_PIN +#define RF_LED_PIN NOT_A_PIN +#endif + +void do_send(osjob_t* j) { + char strTime[16]; + getSystemTime(strTime , sizeof(strTime)); + printf("%s: ", strTime); + + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + printf("OP_TXRXPEND, not sending\n"); + } else { + digitalWrite(RF_LED_PIN, HIGH); + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0); + printf("Packet queued\n"); + } + // Next TX is scheduled after TX_COMPLETE event. +} + +void onEvent (ev_t ev) { + char strTime[16]; + getSystemTime(strTime , sizeof(strTime)); + printf("%s: ", strTime); + + switch(ev) { + case EV_SCAN_TIMEOUT: + printf("EV_SCAN_TIMEOUT\n"); + break; + case EV_BEACON_FOUND: + printf("EV_BEACON_FOUND\n"); + break; + case EV_BEACON_MISSED: + printf("EV_BEACON_MISSED\n"); + break; + case EV_BEACON_TRACKED: + printf("EV_BEACON_TRACKED\n"); + break; + case EV_JOINING: + printf("EV_JOINING\n"); + break; + case EV_JOINED: + printf("EV_JOINED\n"); + digitalWrite(RF_LED_PIN, LOW); + // Disable link check validation (automatically enabled + // during join, but not supported by TTN at this time). + LMIC_setLinkCheckMode(0); + break; + case EV_RFU1: + printf("EV_RFU1\n"); + break; + case EV_JOIN_FAILED: + printf("EV_JOIN_FAILED\n"); + break; + case EV_REJOIN_FAILED: + printf("EV_REJOIN_FAILED\n"); + break; + case EV_TXCOMPLETE: + printf("EV_TXCOMPLETE (includes waiting for RX windows)\n"); + if (LMIC.txrxFlags & TXRX_ACK) + printf("%s Received ack\n", strTime); + if (LMIC.dataLen) { + printf("%s Received %d bytes of payload\n", strTime, LMIC.dataLen); + } + digitalWrite(RF_LED_PIN, LOW); + // Schedule next transmission + os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); + break; + case EV_LOST_TSYNC: + printf("EV_LOST_TSYNC\n"); + break; + case EV_RESET: + printf("EV_RESET\n"); + break; + case EV_RXCOMPLETE: + // data received in ping slot + printf("EV_RXCOMPLETE\n"); + break; + case EV_LINK_DEAD: + printf("EV_LINK_DEAD\n"); + break; + case EV_LINK_ALIVE: + printf("EV_LINK_ALIVE\n"); + break; + default: + printf("Unknown event\n"); + break; + } +} + +/* ====================================================================== +Function: sig_handler +Purpose : Intercept CTRL-C keyboard to close application +Input : signal received +Output : - +Comments: - +====================================================================== */ +void sig_handler(int sig) +{ + printf("\nBreak received, exiting!\n"); + force_exit=true; +} + +/* ====================================================================== +Function: main +Purpose : not sure ;) +Input : command line parameters +Output : - +Comments: - +====================================================================== */ +int main(void) +{ + // caught CTRL-C to do clean-up + signal(SIGINT, sig_handler); + + printf("%s Starting\n", __BASEFILE__); + + // Init GPIO bcm + if (!bcm2835_init()) { + fprintf( stderr, "bcm2835_init() Failed\n\n" ); + return 1; + } + + // Show board config + printConfig(RF_LED_PIN); + printKeys(); + + // Light off on board LED + pinMode(RF_LED_PIN, OUTPUT); + digitalWrite(RF_LED_PIN, HIGH); + + // LMIC init + os_init(); + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + // Start job (sending automatically starts OTAA too) + do_send(&sendjob); + + while(!force_exit) { + os_runloop_once(); + + // We're on a multitasking OS let some time for others + // Without this one CPU is 99% and with this one just 3% + // On a Raspberry PI 3 + usleep(1000); + } + + // We're here because we need to exit, do it clean + + // Light off on board LED + digitalWrite(RF_LED_PIN, LOW); + + // module CS line High + digitalWrite(lmic_pins.nss, HIGH); + printf( "\n%s, done my job!\n", __BASEFILE__ ); + bcm2835_close(); + return 0; +} diff --git a/src/hal/hal.cpp b/src/hal/hal.cpp index 098cf517..fc8c9785 100644 --- a/src/hal/hal.cpp +++ b/src/hal/hal.cpp @@ -10,8 +10,12 @@ * This the HAL to run LMIC on top of the Arduino environment. *******************************************************************************/ +#ifdef RASPBERRY_PI +#include "raspi/raspi.h" +#else #include #include +#endif // include all the lmic header files, including ../lmic/hal.h #include "../lmic.h" // include the C++ hal.h @@ -53,6 +57,13 @@ static void hal_io_init () { if (plmic_pins->rst != LMIC_UNUSED_PIN) { // initialize RST to floating pinMode(plmic_pins->rst, INPUT); + +#ifdef RASPBERRY_PI + // Enable pull down an rising edge detection on this one + bcm2835_gpio_set_pud(plmic_pins->rst, BCM2835_GPIO_PUD_DOWN); + bcm2835_gpio_ren(plmic_pins->rst); +#endif + } hal_interrupt_init(); @@ -94,6 +105,15 @@ static bool dio_states[NUM_DIO] = {0}; static void hal_io_check() { uint8_t i; for (i = 0; i < NUM_DIO; ++i) { +#ifdef RASPBERRY_PI + // Rising edge fired ? + if (bcm2835_gpio_eds(lmic_pins.dio[i])) { + // Now clear the eds flag by setting it to 1 + bcm2835_gpio_set_eds(lmic_pins.dio[i]); + // Handle pseudo interrupt + radio_irq_handler(i); + } +#else if (plmic_pins->dio[i] == LMIC_UNUSED_PIN) continue; @@ -102,6 +122,7 @@ static void hal_io_check() { if (dio_states[i]) radio_irq_handler(i); } +#endif } } @@ -164,7 +185,12 @@ static void hal_spi_trx(u1_t cmd, u1_t* buf, size_t len, bit_t is_read) { if ((spi_freq = plmic_pins->spi_freq) == 0) spi_freq = LMIC_SPI_FREQ; +#ifdef RASPBERRY_PI + // Clock divider / 32 = 8MHz + SPISettings settings(BCM2835_SPI_CLOCK_DIVIDER_32 , BCM2835_SPI_BIT_ORDER_MSBFIRST, BCM2835_SPI_MODE0); +#else SPISettings settings(spi_freq, MSBFIRST, SPI_MODE0); +#endif SPI.beginTransaction(settings); digitalWrite(nss, 0); diff --git a/src/lmic/config.h b/src/lmic/config.h index 32e30a92..6b49afe6 100644 --- a/src/lmic/config.h +++ b/src/lmic/config.h @@ -149,6 +149,12 @@ # error "You may define at most one of USE_ORIGINAL_AES and USE_IDEETRON_AES" #endif +// Force Original AES on RPI +#if defined(RASPBERRY_PI) && defined(USE_IDEETRON_AES) +#undef USE_IDEETRON_AES +#define USE_ORIGINAL_AES +#endif + // LMIC_DISABLE_DR_LEGACY // turn off legacy DR_* symbols that vary by bandplan. // Older code uses these for configuration. EU868_DR_*, US915_DR_* diff --git a/src/lmic/lmic.c b/src/lmic/lmic.c index 2a907f2e..4acedd2e 100644 --- a/src/lmic/lmic.c +++ b/src/lmic/lmic.c @@ -1203,7 +1203,7 @@ static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { // (again note that hsym is half a sumbol time, so no /2 needed) LMIC.rxtime = LMIC.txend + delay + PAMBL_SYMS * hsym - LMIC.rxsyms * hsym; - LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": sched Rx12 %"LMIC_PRId_ostime_t"\n", os_getTime(), LMIC.rxtime - RX_RAMPUP); + LMIC_X_DEBUG_PRINTF("%" LMIC_PRId_ostime_t ": sched Rx12 %" LMIC_PRId_ostime_t "\n", os_getTime(), LMIC.rxtime - RX_RAMPUP); os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } @@ -2229,7 +2229,7 @@ static void engineUpdate (void) { e_.eui = MAIN::CDEV->getEui(), e_.info = osticks2ms(txbeg-now), e_.info2 = LMIC.seqnoUp-1)); - LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": next engine update in %"LMIC_PRId_ostime_t"\n", now, txbeg-TX_RAMPUP); + LMIC_X_DEBUG_PRINTF("%" LMIC_PRId_ostime_t ": next engine update in %" LMIC_PRId_ostime_t "\n", now, txbeg-TX_RAMPUP); os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate)); } diff --git a/src/raspi/raspi.cpp b/src/raspi/raspi.cpp new file mode 100644 index 00000000..5ec0e781 --- /dev/null +++ b/src/raspi/raspi.cpp @@ -0,0 +1,355 @@ +// raspi.cpp +// +// Routines for implementing Arduino-LIMC on Raspberry Pi +// using BCM2835 library for GPIO +// This code has been grabbed from excellent RadioHead Library + +#ifdef RASPBERRY_PI +#include +#include +#include +#include "raspi.h" + +//Initialize the values for sanity +static uint64_t epochMilli ; +static uint64_t epochMicro ; + +void SPIClass::begin() { + initialiseEpoch(); + + if (!bcm2835_spi_begin()) { + printf( "bcm2835_spi_begin() failed. Are you running as root??\n"); + } else { + // LMIC Library code control CS line + bcm2835_spi_chipSelect(BCM2835_SPI_CS_NONE); + } +} + +void SPIClass::end() { + //End the SPI + bcm2835_spi_end(); +} + +void SPIClass::beginTransaction(SPISettings settings) { + //Set SPI clock divider + bcm2835_spi_setClockDivider(settings.divider); + //Set the SPI bit Order + bcm2835_spi_setBitOrder(settings.bitOrder); + //Set SPI data mode + bcm2835_spi_setDataMode(settings.dataMode); + + uint8_t cs = lmic_pins.nss; + // This one was really tricky and spent some time to find + // it. When SPI transaction is done bcm2835 can setup CE0/CE1 + // pins as ALT0 function which may cause chip unselected or + // selected depending on chip. And if there are more than 1, + // then it can also interfere with other chip communication so + // what we do here is to ensure ou CE0 and CE1 are output HIGH so + // no other interference is happening if other chip are connected + bcm2835_gpio_fsel ( 7, BCM2835_GPIO_FSEL_OUTP ); + bcm2835_gpio_fsel ( 8, BCM2835_GPIO_FSEL_OUTP ); + bcm2835_gpio_write( 7, HIGH ); + bcm2835_gpio_write( 8, HIGH ); + + // CS line as output + if ( cs!=7 && cs!=8) { + bcm2835_gpio_fsel( cs, BCM2835_GPIO_FSEL_OUTP ); + bcm2835_gpio_write( cs, HIGH); + } +} + +void SPIClass::endTransaction() { +} + +byte SPIClass::transfer(byte _data) { + byte data; + data= bcm2835_spi_transfer((uint8_t)_data); + return data; +} + +void pinMode(unsigned char pin, unsigned char mode) { + if (pin == LMIC_UNUSED_PIN) { + return; + } + if (mode == OUTPUT) { + bcm2835_gpio_fsel(pin,BCM2835_GPIO_FSEL_OUTP); + } else { + bcm2835_gpio_fsel(pin,BCM2835_GPIO_FSEL_INPT); + } +} + +void digitalWrite(unsigned char pin, unsigned char value) { + if (pin == LMIC_UNUSED_PIN) { + return; + } + bcm2835_gpio_write(pin, value); +} + +unsigned char digitalRead(unsigned char pin) { + if (pin == LMIC_UNUSED_PIN) { + return 0; + } + return bcm2835_gpio_lev(pin); +} + +//Initialize a timestamp for millis/micros calculation +// Grabbed from WiringPi +void initialiseEpoch() { + struct timeval tv ; + gettimeofday (&tv, NULL) ; + epochMilli = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ; + epochMicro = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)(tv.tv_usec) ; + pinMode(lmic_pins.nss, OUTPUT); + digitalWrite(lmic_pins.nss, HIGH); +} + +unsigned int millis() { + struct timeval tv ; + uint64_t now ; + gettimeofday (&tv, NULL) ; + now = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ; + return (uint32_t)(now - epochMilli) ; +} + +unsigned int micros() { + struct timeval tv ; + uint64_t now ; + gettimeofday (&tv, NULL) ; + now = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)tv.tv_usec ; + return (uint32_t)(now - epochMicro) ; +} + +char * getSystemTime(char * time_buff, int len) { + time_t t; + struct tm* tm_info; + + t = time(NULL); + tm_info = localtime(&t); + if (tm_info) { + if (strftime(time_buff, len, "%H:%M:%S", tm_info)) { + } else { + strncpy(time_buff, "strftime() ERR", len); + } + } else { + strncpy(time_buff, "localtime() ERR", len); + } + return time_buff; +} + +void printConfig(const uint8_t led) { + printf( "RFM95 device configuration\n" ); + if (lmic_pins.nss ==LMIC_UNUSED_PIN ) { + printf( "!! CS pin is not defined !!\n" ); + } else { + printf( "CS=GPIO%d", lmic_pins.nss ); + } + + printf( " RST=" ); + if (lmic_pins.rst==LMIC_UNUSED_PIN ) { + printf( "Unused" ); + } else { + printf( "GPIO%d", lmic_pins.rst ); + } + + printf( " LED=" ); + if ( led==LMIC_UNUSED_PIN ) { + printf( "Unused" ); + } else { + printf( "GPIO%d", led ); + } + + // DIO + for (uint8_t i=0; i<3 ; i++) { + printf( " DIO%d=", i ); + if (lmic_pins.dio[i]==LMIC_UNUSED_PIN ) { + printf( "Unused" ); + } else { + printf( "GPIO%d", lmic_pins.dio[i] ); + } + } + printf( "\n" ); +} + +// Display a Key +// ============= +void printKey(const char * name, const uint8_t * key, uint8_t len, bool lsb) +{ + uint8_t start=lsb?len:0; + uint8_t end = lsb?0:len; + const uint8_t * p ; + + printf("%s : ", name); + for (uint8_t i=0; iifa_next) { + // Ethernet + if ( (ifa->ifa_addr) && (ifa->ifa_addr->sa_family==AF_PACKET) ) { + // Not loopback interface + if (! (ifa->ifa_flags & IFF_LOOPBACK)) { + char fname[128]; + int fd; + int up=0; + struct sockaddr_ll *s = (struct sockaddr_ll*)ifa->ifa_addr; + + // Get interface status + // Interface can be up with no cable connected and to be sure + // It's up, active and connected we need to get operstate + // + // if up + cable if up + NO cable if down + cable + // ============= ========== ================== + // carrier:1 carrier:0 carrier:Invalid + // dormant:0 dormant:0 dormant:Invalid + // operstate:up operstate:down operstate :own + sprintf(fname, "/sys/class/net/%s/operstate", ifa->ifa_name); + if ( (fd = open( fname, O_RDONLY)) > 0 ){ + char buf[2]; + if ( read(fd, buf, 2) > 0 ) { + // only first active interface "up" + if ( buf[0]=='u' && buf[1]=='p' ) { + uint8_t * p = pdeveui; + // deveui is LSB to we reverse it so TTN display + // will remain the same as MAC address + // MAC is 6 bytes, devEUI 8, set first 2 ones + // with an arbitrary value + *p++ = 0x00; + *p++ = 0x04; + // Then next 6 bytes are mac address still reversed + for ( i=0; i<6 ; i++) { + *p++ = s->sll_addr[5-i]; + } + + gotit = true; + close(fd); + break; + } + } + close(fd); + } + } + } + } + // Free our Linked list + freeifaddrs(ifaddr); + } + + // just in case of error put deveui to 0102030405060708 + if (!gotit) { + for (i=1; i<=8; i++) { + *pdeveui++=i; + } + } + return gotit; +} + +void SerialSimulator::begin(int baud) { + // No implementation neccesary - Serial emulation on Linux = standard console + // Initialize a timestamp for millis calculation - we do this here as well in case SPI + // isn't used for some reason + initialiseEpoch(); +} + +size_t SerialSimulator::println(void) { + fprintf(stdout, "\n"); +} + +size_t SerialSimulator::println(const char* s) { + fprintf( stdout, "%s\n",s); +} + +size_t SerialSimulator::print(const char* s) { + fprintf( stdout, "%s",s); +} + +size_t SerialSimulator::println(u2_t n) { + fprintf(stdout, "%d\n", n); +} + +size_t SerialSimulator::print(ostime_t n) { + fprintf(stdout, "%d\n", n); +} + +size_t SerialSimulator::print(unsigned int n, int base) { + if (base == DEC) + fprintf(stdout, "%d", n); + else if (base == HEX) + fprintf(stdout, "%02x", n); + else if (base == OCT) + fprintf(stdout, "%o", n); + // TODO: BIN +} + +size_t SerialSimulator::print(char ch) { + fprintf(stdout, "%c", ch); +} + +size_t SerialSimulator::println(char ch) { + fprintf(stdout, "%c\n", ch); +} + +size_t SerialSimulator::print(unsigned int i) { + fprintf(stdout, "%d", i); +} + +size_t SerialSimulator::println(unsigned int i) { + fprintf(stdout, "%d\n", i); +} + +size_t SerialSimulator::print(unsigned char ch, int base) { + return print((unsigned int)ch, base); +} + +size_t SerialSimulator::println(unsigned char ch, int base) { + print((unsigned int)ch, base); + fprintf( stdout, "\n"); +} + +size_t SerialSimulator::write(char ch) { + fprintf( stdout, "%c", ch); +} + +size_t SerialSimulator::write(unsigned char* s, size_t len) { + for (int i=0; i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "hal/hal.h" + +#ifndef NULL + #define NULL 0 +#endif + +#ifndef OUTPUT + #define OUTPUT BCM2835_GPIO_FSEL_OUTP +#endif + +#ifndef INPUT + #define INPUT BCM2835_GPIO_FSEL_INPT +#endif + +#ifndef NOT_A_PIN + #define NOT_A_PIN 0xFF +#endif + +#ifndef LED_BUILTIN + #define LED_BUILTIN NOT_A_PIN +#endif + +// We don't have IRQ on Raspberry PI +#define interrupts() {} +#define noInterrupts() {} + +// Delay macros +#define delay(x) bcm2835_delay(x) +#define delayMicroseconds(m) bcm2835_delayMicroseconds(m) + +// No memcpy_P/PROGMEM on Raspberry PI +#ifndef memcpy_P +#define memcpy_P memcpy +#endif + +#ifndef PROGMEM +#define PROGMEM +#endif + +// F() Macro +#define F(s) s + +#define random(x) (rand() % x) + +typedef unsigned char byte; + +class SPISettings +{ + public: + SPISettings(uint16_t divider, uint8_t bitOrder, uint8_t dataMode) { + init(divider, bitOrder, dataMode); + } + SPISettings() { + init(BCM2835_SPI_CLOCK_DIVIDER_256, BCM2835_SPI_BIT_ORDER_MSBFIRST, BCM2835_SPI_MODE0); + } + private: + void init(uint16_t divider, uint8_t bitOrder, uint8_t dataMode) { + this->divider = divider ; + this->bitOrder = bitOrder; + this->dataMode = dataMode; + } + + uint16_t divider ; + uint8_t bitOrder ; + uint8_t dataMode ; + friend class SPIClass; +}; + +class SPIClass { + public: + static byte transfer(byte _data); + // SPI Configuration methods + static void begin(); // Default + static void end(); + static void beginTransaction(SPISettings settings); + static void endTransaction(); + static void setBitOrder(uint8_t); + static void setDataMode(uint8_t); + static void setClockDivider(uint16_t); +}; + +extern SPIClass SPI; + +class SerialSimulator { + public: + #define DEC 10 + #define HEX 16 + #define OCT 8 + #define BIN 2 + + operator bool() { return true; } + + // TODO: move these from being inlined + static void begin(int baud); + static size_t println(void); + static size_t println(const char* s); + static size_t print(const char* s); + static size_t println(u2_t n); + static size_t print(ostime_t n); + static size_t print(unsigned int n, int base = DEC); + static size_t print(char ch); + static size_t println(char ch); + static size_t print(unsigned int i); + static size_t println(unsigned int i); + static size_t print(unsigned char ch, int base = DEC); + static size_t println(unsigned char ch, int base = DEC); + static size_t write(char ch); + static size_t write(unsigned char * s, size_t len); + static void flush(void); + +}; +extern SerialSimulator Serial; + +#ifdef __cplusplus +extern "C"{ +#endif + +void printConfig(const uint8_t led) ; +void printKey(const char * name, const uint8_t * key, uint8_t len, bool lsb); +void printKeys() ; +bool getDevEuiFromMac(uint8_t *); +char * getSystemTime(char * time_buff, int len); +void pinMode(unsigned char, unsigned char); +void digitalWrite(unsigned char, unsigned char); +unsigned char digitalRead(unsigned char) ; +void initialiseEpoch(); +unsigned int millis(); +unsigned int micros(); + +#ifdef __cplusplus +} +#endif + +#endif // RASPI_h +#endif // RASPBERRY_PI +