transfer files from mbed-os block device (i.e. SD card) over LoRa radio.
radio chip selection
Radio chip driver is not included, allowing choice of radio device.
If you're using SX1272 or SX1276, then import sx127x driver into your program.
if you're using SX1261 or SX1262, then import sx126x driver into your program.
if you're using SX1280, then import sx1280 driver into your program.
If you're using NAmote72 or Murata discovery, then you must import only sx127x driver.
This program uses mbed serial terminal at 115200 8N1.
Files (on SD Card) are transferred between two LoRa devices.
Use ? question mark to see available commands on serial terminal.
Send file to other side using send /fs/somefile
, then use cmp /fs/somefile
to compare the file on the other side of radio link with local file with same name.
Use cmp
to test radio link.rm
command to delete files must not include /fs/
directory prefix, filename only.
send
and cmp
require /fs/
directory.
sx1280 thruput
bytes/sec | sf12 | sf11 | sf10 | sf9 | sf8 | sf7 | sf6 | sf5 |
1600KHz | 276 | 482 | 1081 | 1887 | 3277 | 5365 | 6400 | 12000 |
800KHz | 529 | 970 | 1720 | 2800 | 4766 | 6500 | ||
400KHz | 1465 | 2265 | 4033 |
sx126x thruput
bytes/sec | sf12 | sf11 | sf10 | sf9 | sf8 | sf7 | sf6 | sf5 |
500KHz | 207 | 381 | 706 | 1257 | 2154 | 3570 | 5608 | |
250KHz | 103 | 193 | 344 | 641 | 1114 | 1887 | 3064 | |
125KHz | 97 | 173 | 324 | 547 | 972 | 1607 |
sx127x thruput
sx126x recommended at 500KHz bandwidth.
bytes/sec | sf12 | sf11 | sf10 | sf9 | sf8 | sf7 |
500KHz | 111 | 207 | 385 | 697 | 1220 | 2050 |
250KHz | 48.7 | 103 | 192 | 345 | 632 | 1115 |
125KHz | 24.2 | 44 | 97 | 151 | 313 | 552 |
62.5KHz | 12.2 | 22.1 | 39.4 | 86 | 157 | 276 |
SD Card
SD card driver must be enabled in mbed_app.json
with the line "target.components_add": ["SD"]
Pins for SD card connection can be overridden as shown in mbed_app.json
.
The default pin configuration for SD card is found in mbed_lib.json
in mbed-os/components/storage/blockdevice/COMPONENT_SD/
.
If no pin configuration exists in this mbed_lib.json
, or needs to be changed, the included mbed_app.json
is example pin declaration for SD card.
FAT filesystem is only used in this project for demonstration purpose; for easily copying files to SD card.
However, for production projects, LittleFileSystem needs to be used instead for reliability and wear leveling.
Platforms such as K64F have SD card slot already.
See all boards with arduino shield connector and SD card here. (arduino connector needed for LoRa radio board)
...but if you want to use nucleo board:
main.cpp
- Committer:
- Wayne Roberts
- Date:
- 2019-05-31
- Revision:
- 0:c3ecf7b252a3
- Child:
- 1:319ef808aaa4
File content as of revision 0:c3ecf7b252a3:
#include <stdio.h> #include <errno.h> #include "lorachip.h" // how long extra to keep receiver open to wait for ack/nak, due to task performed by receiver rxdone handling #define TX_RX_TURNAROUND_MS 70 #if defined(SX127x_H) #define BW_KHZ 500 #define SPREADING_FACTOR 7 #define CF_HZ 915000000 #define TX_DBM 20 #define SPI_HZ 5000000 #elif defined(SX126x_H) #define BW_KHZ 500 #define SPREADING_FACTOR 5 #define CF_HZ 915000000 #define TX_DBM (Radio::chipType == CHIP_TYPE_SX1262 ? 20 : 14) #define SPI_HZ 10000000 #elif defined(SX128x_H) #define BW_KHZ 1600 #define SPREADING_FACTOR 5 #define CF_HZ 2487000000 #define TX_DBM 2 #define SPI_HZ 10000000 #endif #define MAX_PKT_SIZE 250 EventQueue queue(32 * EVENTS_EVENT_SIZE); Thread th; Timer t; typedef struct { uint8_t foobar : 2; // 0,1 uint8_t rxContStart : 1; // 2 uint8_t callRetry : 1; // 3 uint8_t txDonePending : 1; // 4 txDone needs to be serviced uint8_t endOfFile : 1; // 5 uint8_t atTxDoneRxSingle : 1; // 6 uint8_t verbose : 1; // 7 } flags_t; volatile flags_t flags; //#include "LittleFileSystem.h" //LittleFileSystem fs("fs"); #include "FATFileSystem.h" FATFileSystem fs("fs"); /* FAT FS not recommended for production */ /* use Little FS for production */ //BlockDevice *bd = BlockDevice::get_default_instance(); #include "SDBlockDevice.h" SDBlockDevice *bd = new SDBlockDevice(MBED_CONF_SD_SPI_MOSI, MBED_CONF_SD_SPI_MISO, MBED_CONF_SD_SPI_CLK, MBED_CONF_SD_SPI_CS, 5000000); RawSerial pc(USBTX, USBRX); //#define RX_DBG_PIN PC_10 #ifdef RX_DBG_PIN DigitalInOut rx_dbg(RX_DBG_PIN); #endif /* RX_DBG_PIN */ volatile unsigned txMinDelay_us; // minimum time between RX done to TX start volatile unsigned add_txDelay_retry; // how much extra to defer on each retry #define MAX_ATTEMPTS 15 volatile uint8_t attemptsRemaining; volatile int prevRxSeq; volatile uint8_t seq; volatile uint8_t tx_buf_len; volatile unsigned xferStartAt; volatile unsigned rxDoneAt; volatile unsigned byteCnt; int fd; // descriptor of transferring file volatile int sendData_readLen; lora_t current; uint16_t myAddr; #define BROADCAST_ADDRESS 0xffff uint16_t peerAddr = BROADCAST_ADDRESS; uint8_t file_buf[MAX_PKT_SIZE]; typedef enum { /* 0 */ STATE_NONE = 0, /* 1 */ STATE_RX_COMPARE, /* 2 */ STATE_SEND_FILE, /* 3 */ STATE_FILE_END, /* 4 */ STATE_GET_FILE } state_e; volatile state_e state; char printBuf[2048]; volatile uint16_t printBuf_in; volatile uint16_t printBuf_out; /* Prints into circular buffer to prevent impact on execution speed. * (aka non-blocking printf) * Characters are sent in main loop, where time is less important. */ int vspfunc(const char *format, ...) { va_list aptr; int ret; char buffer[256]; va_start(aptr, format); ret = vsprintf(buffer, format, aptr); va_end(aptr); if (ret < 0) { pc.printf("vsprintf failed\r\n"); return ret; } if ((printBuf_in + (unsigned)ret) >= sizeof(printBuf)) { int len_b, len_a = sizeof(printBuf) - printBuf_in; memcpy(printBuf + printBuf_in, buffer, len_a); len_b = ret - len_a; memcpy(printBuf, buffer + len_a, len_b); printBuf_in = len_b; } else { memcpy(printBuf + printBuf_in, buffer, ret); printBuf_in += ret; } return(ret); } volatile unsigned txDoneAt; static inline void rxCont(void) { Radio::Rx(0); #ifdef RX_DBG_PIN rx_dbg.output(); rx_dbg.write(0); #endif /* RX_DBG_PIN */ } void txDoneCB_botHalf() { unsigned now; if (flags.atTxDoneRxSingle) { Radio::Rx(1000); #ifdef RX_DBG_PIN rx_dbg.output(); rx_dbg.write(0); #endif /* RX_DBG_PIN */ now = t.read_us(); if (flags.verbose) vspfunc("rxSingle%u ", now - txDoneAt); } else { rxCont(); Radio::Rx(0); now = t.read_us(); if (flags.verbose) vspfunc("rxCont%u ", now - txDoneAt); } flags.txDonePending = false; } void txDoneCB_topHalf() { txDoneAt = t.read_us(); flags.txDonePending = true; } static uint16_t crc16( uint8_t *buffer, uint16_t length ) { uint16_t i; // The CRC calculation follows CCITT const uint16_t polynom = 0x1021; // CRC initial value uint16_t crc = 0x0000; if( buffer == NULL ) { return 0; } for( i = 0; i < length; ++i ) { uint16_t j; crc ^= ( uint16_t ) buffer[i] << 8; for( j = 0; j < 8; ++j ) { crc = ( crc & 0x8000 ) ? ( crc << 1 ) ^ polynom : ( crc << 1 ); } } return crc; } static inline unsigned rf_send(bool anytime, unsigned extratxdelay) { unsigned sinceRxDone = 0; unsigned target = txMinDelay_us + extratxdelay; Radio::Standby(); if (!anytime) { do { sinceRxDone = t.read_us() - rxDoneAt; } while (sinceRxDone < target); } #ifdef RX_DBG_PIN rx_dbg.input(); if (flags.verbose) { if (rx_dbg.read()) vspfunc("\e[41mnotrxing\e[0m%u ", sinceRxDone); else vspfunc("\e[32mprxok\e[0m%u ", sinceRxDone); } #endif /* RX_DBG_PIN */ Radio::Send(tx_buf_len, 0, 0, 0); return sinceRxDone; } static void sendAck(bool ack, unsigned addTxDelay) { uint16_t crc; tx_buf_len = 0; Radio::radio.tx_buf[tx_buf_len++] = Radio::radio.rx_buf[0]; Radio::radio.tx_buf[tx_buf_len++] = ack ? 'Y' : 'N'; crc = crc16(Radio::radio.tx_buf, tx_buf_len); Radio::radio.tx_buf[tx_buf_len++] = crc; Radio::radio.tx_buf[tx_buf_len++] = crc >> 8; flags.atTxDoneRxSingle = false; attemptsRemaining = MAX_ATTEMPTS; /*sinceRxDone = */rf_send(false, addTxDelay); if (flags.verbose) vspfunc("sentAck'%c' %u ", Radio::radio.tx_buf[1], addTxDelay); } void retry(bool anytime) { if (attemptsRemaining == 0) return; if (--attemptsRemaining > 0) { flags.atTxDoneRxSingle = true; rf_send(anytime, 0); vspfunc("retry%u '%c'\r\n", attemptsRemaining, Radio::radio.tx_buf[1]); } else { vspfunc("retry end\r\n"); state = STATE_NONE; rxCont(); } } void sendError(const char* str) { uint16_t crc; char* dest; tx_buf_len = 0; Radio::radio.tx_buf[tx_buf_len++] = Radio::radio.rx_buf[0]; Radio::radio.tx_buf[tx_buf_len++] = 'E'; dest = (char*)(&Radio::radio.tx_buf[tx_buf_len]); strcpy(dest, str); tx_buf_len += strlen(str); crc = crc16(Radio::radio.tx_buf, tx_buf_len); Radio::radio.tx_buf[tx_buf_len++] = crc; Radio::radio.tx_buf[tx_buf_len++] = crc >> 8; flags.atTxDoneRxSingle = false; rf_send(false, 0); } void readData() { sendData_readLen = read(fd, file_buf, sizeof(file_buf)); if (sendData_readLen < (int)sizeof(file_buf)) { /* this is last data */ flags.endOfFile = true; } } void sendData() { uint16_t crc; tx_buf_len = 0; Radio::radio.tx_buf[tx_buf_len++] = ++seq; Radio::radio.tx_buf[tx_buf_len++] = 'D'; if (sendData_readLen > 0) { memcpy(Radio::radio.tx_buf + tx_buf_len, file_buf, sendData_readLen); tx_buf_len += sendData_readLen; byteCnt += sendData_readLen; } crc = crc16(Radio::radio.tx_buf, tx_buf_len); Radio::radio.tx_buf[tx_buf_len++] = crc; Radio::radio.tx_buf[tx_buf_len++] = crc >> 8; flags.atTxDoneRxSingle = true; attemptsRemaining = MAX_ATTEMPTS; /*sinceRxDone = */rf_send(false, 0); if (flags.verbose) vspfunc("sendTotal:%u ", byteCnt); } volatile uint8_t rxSize; void rxHandler(uint8_t* rxBuf) { static const char* pendingError = NULL; uint16_t calc_crc, rx_crc; static uint8_t sameCnt; calc_crc = crc16(rxBuf, rxSize-2); rx_crc = rxBuf[rxSize-1]; rx_crc <<= 8; rx_crc += rxBuf[rxSize-2]; if (rx_crc != calc_crc) { /* when received crc fails, only send NAK when this received message isnt a NAK or ACK */ if (rxBuf[1] == 'Z' || rxBuf[1] == 'D' || rxBuf[1] == 'F') { sendAck(false, 0); vspfunc("naking '%c' %u\r\n", rxBuf[1], rxBuf[0]); } vspfunc("\e[41mcalc_crc:%04x, rx_crc:%04x\e[0m\r\n", calc_crc, rx_crc); rf_send(false, 0); return; } if (flags.verbose) { vspfunc(" rxseq%d->%u '%c' ", prevRxSeq, rxBuf[0], rxBuf[1]); } if (rxBuf[1] == 'E') { rxBuf[rxSize-2] = 0; // null terminate error msg vspfunc("from remote \"%s\" ", rxBuf + 2); } switch (state) { case STATE_NONE: if (flags.verbose) vspfunc("STATE_NONE "); if (rxBuf[1] == 'R') { uint16_t rxAddr; rxBuf[rxSize-2] = 0; // null terminate file name rxAddr = rxBuf[3]; rxAddr <<= 8; rxAddr += rxBuf[2]; vspfunc(" addr %04x ", rxAddr); if (myAddr == rxAddr || BROADCAST_ADDRESS == rxAddr) { if (flags.verbose) vspfunc(" open-read %s, ", rxBuf + 4); fd = open((char*)(rxBuf + 4), O_RDONLY); if (flags.verbose) vspfunc("fd:%d ", fd); if (fd < 0) { vspfunc("open %s failed\n", (char*)(rxBuf + 2)); sendError(strerror(errno)); break; } state = STATE_SEND_FILE; xferStartAt = t.read_us(); byteCnt = 0; seq = 0; flags.endOfFile = false; attemptsRemaining = MAX_ATTEMPTS; readData(); sendData(); } else vspfunc("ignore 'R'\r\n"); } else if (rxBuf[1] == 'F') { uint16_t rxAddr; rxBuf[rxSize-2] = 0; // null terminate file name rxAddr = rxBuf[3]; rxAddr <<= 8; rxAddr += rxBuf[2]; vspfunc(" addr %04x ", rxAddr); if (myAddr == rxAddr || BROADCAST_ADDRESS == rxAddr) { vspfunc("open-write %s ", rxBuf + 4); fd = open((char*)(rxBuf + 4), O_WRONLY | O_CREAT | O_TRUNC); vspfunc("fd:%d ", fd); if (fd < 0) { sendError(strerror(errno)); } else { sendAck(true, 0); xferStartAt = t.read_us(); byteCnt = 0; state = STATE_GET_FILE; sameCnt = 0; } } else vspfunc("ignore 'F'\r\n", rxAddr); } else if (rxBuf[1] == 'Z') { /* probably previous ack wasnt received of just completed transfer */ sendAck(true, 0); } break; case STATE_GET_FILE: vspfunc("STATE_GET_FILE fd%d ", fd); if (rxBuf[1] == 'D') { if (prevRxSeq+1 == rxBuf[0]) { uint8_t len = rxSize - 4; int fop_len = write(fd, rxBuf + 2, len); if (fop_len < 0) { sendError(strerror(errno)); vspfunc(strerror(errno)); } else if (fop_len < len) { sendError("short-write"); vspfunc("write %d vs %d", fop_len, len); } else { byteCnt += fop_len; sendAck(true, 0); vspfunc("ok total %u ", byteCnt); } } else if (prevRxSeq == rxBuf[0]) { vspfunc("\e[43mrepeat %u\e[0m", rxBuf[0]); /* ignore do nothing, ack to get next */ sendAck(true, 0); } else { vspfunc("\e[41msequence %u %u\e[0m", prevRxSeq, rxBuf[0]); sendError("sequence"); state = STATE_NONE; close(fd); } } else if (rxBuf[1] == 'Z') { unsigned duration_us; float secs; sendAck(true, 0); duration_us = t.read_us() - xferStartAt; secs = duration_us / 1000000.0; close(fd); vspfunc("EOF total:%u = %.1f bytes/sec\r\n", byteCnt , byteCnt / secs); state = STATE_NONE; } else if (rxBuf[1] == 'F' && rxBuf[0] == 0) { /* check filename? */ if (prevRxSeq == rxBuf[0]) sameCnt++; else sameCnt = 0; vspfunc("didnt-get-F-ack fd:%d ", fd); sendAck(true, sameCnt * add_txDelay_retry); } break; case STATE_SEND_FILE: if (flags.verbose) vspfunc("STATE_SEND_FILE "); if (rxBuf[1] == 'Y') { if (flags.endOfFile) { uint16_t crc; tx_buf_len = 0; Radio::radio.tx_buf[tx_buf_len++] = ++seq; Radio::radio.tx_buf[tx_buf_len++] = 'Z'; crc = crc16(Radio::radio.tx_buf, tx_buf_len); Radio::radio.tx_buf[tx_buf_len++] = crc; Radio::radio.tx_buf[tx_buf_len++] = crc >> 8; flags.atTxDoneRxSingle = true; attemptsRemaining = MAX_ATTEMPTS; /*sinceRxDone = */rf_send(false, 0); close(fd); state = STATE_FILE_END; } else { readData(); sendData(); } } else if (rxBuf[1] == 'R') { /* peer didnt get ack for 'R' */ retry(false); } else if (rxBuf[1] == 'N') { /* NAK, peer crc fail */ retry(false); } else if (rxBuf[1] == 'E') { state = STATE_NONE; close(fd); rxCont(); vspfunc("xfer-ended\r\n"); } break; case STATE_FILE_END: vspfunc("STATE_FILE_END "); if (rxBuf[1] == 'Y') { unsigned duration_us = t.read_us() - xferStartAt; float secs = duration_us / 1000000.0; vspfunc("total:%u = %.1f bytes/sec\r\n", byteCnt, byteCnt / secs); state = STATE_NONE; rxCont(); } else vspfunc("badack:%c ", rxBuf[1]); break; case STATE_RX_COMPARE: if (flags.verbose) vspfunc("STATE_RX_COMPARE "); if (pendingError) { sendError(pendingError); vspfunc("sentErr:%s ", pendingError); pendingError = NULL; state = STATE_NONE; break; } if (rxBuf[1] == 'D') { if (prevRxSeq == -1 || rxBuf[0] > prevRxSeq) { int ret, rxLen = rxSize - 4; sameCnt = 0; ret = read(fd, file_buf, rxLen); if (ret < 0) { pendingError = "read fail"; vspfunc("readFail"); } else if (ret < rxLen) { pendingError = "read short"; vspfunc("readShort"); } else if (memcmp(file_buf, rxBuf + 2, ret) == 0) { sendAck(true, 0); byteCnt += ret; if (flags.verbose) vspfunc("cmpTotal:%u ", byteCnt); } else { int n; pendingError = "memcmp fail"; vspfunc("memcmp"); vspfunc("\r\nrxBuf:\r\n"); for (n = 0; n < ret; n++) { if (rxBuf[n+2] != file_buf[n]) vspfunc("\e[41m"); vspfunc("%02x ", rxBuf[n+2]); if (rxBuf[n+2] != file_buf[n]) vspfunc("\e[0m"); if ((n & 0xf) == 0xf) vspfunc("\r\n"); } vspfunc("\r\nfile_buf:\r\n"); for (n = 0; n < ret; n++) { if (rxBuf[n+2] != file_buf[n]) vspfunc("\e[41m"); vspfunc("%02x ", file_buf[n]); if (rxBuf[n+2] != file_buf[n]) vspfunc("\e[0m"); if ((n & 0xf) == 0xf) vspfunc("\r\n"); } vspfunc("\r\n"); } } else { if (rxBuf[0] == prevRxSeq) sameCnt++; else sameCnt = 0; sendAck(true, sameCnt * add_txDelay_retry); vspfunc("repeat "); } } else if (rxBuf[1] == 'Z') { unsigned duration_us = t.read_us() - xferStartAt; float secs = duration_us / 1000000.0; vspfunc("EOF total:%u = %.1f bytes/sec\r\n", byteCnt, byteCnt / secs); close(fd); sendAck(true, 0); state = STATE_NONE; } else if (rxBuf[1] == 'E') { sendAck(true, 0); state = STATE_NONE; } break; } // ..switch (state) prevRxSeq = rxBuf[0]; if (flags.verbose) vspfunc("\r\n"); } void rxDoneCB(uint8_t size, float rssi, float snr) { rxDoneAt = t.read_us(); if (rxSize != 0) vspfunc("\e[41mrxoverrun\e[0m "); else { rxSize = size; if (flags.verbose) vspfunc("rxDone "); } /* cant TX here because irq flags cleared after returning */ } void rxTimeoutCB() { vspfunc("\e[33mrxTimeout "); if (state != STATE_NONE) flags.callRetry = true; else { vspfunc("spurious"); flags.rxContStart = true; } vspfunc("\e[0m "); } const RadioEvents_t rev = { /* Dio0_top_half */ NULL, /* TxDone_topHalf */ txDoneCB_topHalf, // called in ISR context /* TxDone_botHalf */ txDoneCB_botHalf, // called from service() /* TxTimeout */ NULL, /* RxDone */ rxDoneCB, // called from service() /* RxTimeout */ rxTimeoutCB, // called from service() /* RxError */ NULL, /* FhssChangeChannel */NULL, /* CadDone */ NULL }; char pcbuf[64]; volatile int pcbuf_len; volatile uint8_t pcbuf_idx; typedef struct { const char* const cmd; void (*handler)(uint8_t args_at); const char* const arg_descr; const char* const description; } menu_item_t; void cmd_help(uint8_t); void cmd_print_status(uint8_t idx) { pc.printf("state%u ", state); if (flags.atTxDoneRxSingle) pc.printf("flags.atTxDoneRxSingle "); if (flags.endOfFile) pc.printf("flags.endOfFile "); print_lora_status(); } void cmd_stby(uint8_t argsAt) { Radio::Standby(); #ifdef RX_DBG_PIN rx_dbg.input(); #endif /* RX_DBG_PIN */ pc.printf("standby\r\n"); } void cmd_verbose(uint8_t args_at) { flags.verbose ^= 1; pc.printf("verbose:%u\r\n", flags.verbose); } void cmd_pt(uint8_t args_at) { char foo[32]; unsigned cnt, n; if (sscanf(pcbuf + args_at, "%u", &cnt) == 1) { for (n = 0; n < cnt; n++) { foo[n] = 'a' + n; } for (n = 0; n < cnt; n++) { vspfunc("in%u: test %s\r\n", printBuf_in, foo); //vspfunc("in%u: test\r\n", printBuf_in); } } else vspfunc("give count number\r\n"); } void cmd_peerAddr(uint8_t args_at) { unsigned a; if (sscanf(pcbuf + args_at, "%x", &a) == 1) { peerAddr = a; } pc.printf("peerAddr:%04x\r\n", peerAddr); } void cmd_myAddr(uint8_t args_at) { unsigned a; if (sscanf(pcbuf + args_at, "%x", &a) == 1) { myAddr = a; } pc.printf("myAddr:%04x\r\n", myAddr); } void cmd_ls(uint8_t args_at) { int err; // Display the root directory pc.printf("Opening the root directory... "); fflush(stdout); DIR *d = opendir("/fs/"); pc.printf("%s\r\n", (!d ? "Fail :(" : "OK")); if (!d) { error("error: %s (%d)\r\n", strerror(errno), -errno); } pc.printf("root directory:\r\n"); while (true) { struct stat st; struct dirent *e = readdir(d); if (!e) { break; } fs.stat(e->d_name, &st); pc.printf(" %s\t%lu\r\n", e->d_name, st.st_size); } //pc.printf("Closing the root directory... "); fflush(stdout); err = closedir(d); pc.printf("%s\r\n", (err < 0 ? "Fail :(" : "OK")); if (err < 0) { error("error: %s (%d)\r\n", strerror(errno), -errno); } } void cmd_md(uint8_t args_at) { unsigned us; if (sscanf(pcbuf + args_at, "%u", &us) == 1) { txMinDelay_us = us; } pc.printf("txMinDelay_us:%u\r\n", txMinDelay_us); } void cmd_cat(uint8_t argsAt) { unsigned ofs; uint8_t buf[16]; int ret, fd = open(pcbuf + argsAt, O_RDONLY); if (fd < 0) { pc.printf("open %s failed\n", pcbuf + argsAt); return; } ofs = 0; do { int n; ret = read(fd, buf, sizeof(buf)); if (ret < 0) { pc.printf("read failed\n"); return; } pc.printf("%04x: ", ofs); for (n = 0; n < ret; n++) pc.printf("%02x ", buf[n]); pc.printf(" "); for (n = 0; n < ret; n++) { if (buf[n] >= ' ') pc.putc(buf[n]); else pc.putc('.'); } pc.putc('\r'); pc.putc('\n'); ofs += ret; if (pcbuf_len < 0) { pcbuf_len = 0; break; } if ((ofs & 0xff) == 0) { uint8_t x = pcbuf_idx; pc.printf("hitkey:"); while (x == pcbuf_idx && pcbuf_len >= 0) ; pc.printf("\r\n"); } } while (ret > 0); close(fd); } void cmd_rm(uint8_t args_at) { int ret = fs.remove(pcbuf + args_at); if (ret < 0) perror("unlink"); else pc.printf("delete %s ok\r\n", pcbuf + args_at); } void cmd_rx(uint8_t argsAt) { rxCont(); pc.printf("rx start\r\n"); } void cmd_send(uint8_t argsAt) { char* dest; uint8_t txlen; uint16_t crc; fd = open(pcbuf + argsAt, O_RDONLY); if (fd < 0) { pc.printf("open %s failed\n", pcbuf + argsAt); return; } txlen = strlen(pcbuf + argsAt); seq = 0; prevRxSeq = -1; Radio::radio.tx_buf[0] = seq; Radio::radio.tx_buf[1] = 'F'; Radio::radio.tx_buf[2] = peerAddr; Radio::radio.tx_buf[3] = peerAddr >> 8; dest = (char*)(&Radio::radio.tx_buf[4]); strcpy(dest, pcbuf + argsAt); tx_buf_len = txlen + 4; crc = crc16(Radio::radio.tx_buf, tx_buf_len); Radio::radio.tx_buf[tx_buf_len++] = crc; Radio::radio.tx_buf[tx_buf_len++] = crc >> 8; byteCnt = 0; attemptsRemaining = MAX_ATTEMPTS; flags.endOfFile = false; state = STATE_SEND_FILE; flags.atTxDoneRxSingle = true; // receiver acks (F) xferStartAt = t.read_us(); rf_send(true, 0); } void cmd_cmp(uint8_t argsAt) { char* dest; uint8_t txlen; uint16_t crc; Radio::Standby(); close(fd); fd = open(pcbuf + argsAt, O_RDONLY); if (fd < 0) { pc.printf("open %s failed\n", pcbuf + argsAt); return; } txlen = strlen(pcbuf + argsAt); seq = 0; prevRxSeq = -1; Radio::radio.tx_buf[0] = seq; Radio::radio.tx_buf[1] = 'R'; Radio::radio.tx_buf[2] = peerAddr; Radio::radio.tx_buf[3] = peerAddr >> 8; dest = (char*)(&Radio::radio.tx_buf[4]); strcpy(dest, pcbuf + argsAt); tx_buf_len = txlen + 4; crc = crc16(Radio::radio.tx_buf, tx_buf_len); Radio::radio.tx_buf[tx_buf_len++] = crc; Radio::radio.tx_buf[tx_buf_len++] = crc >> 8; byteCnt = 0; flags.endOfFile = false; attemptsRemaining = MAX_ATTEMPTS; flags.atTxDoneRxSingle = false; xferStartAt = t.read_us(); rf_send(true, 0); state = STATE_RX_COMPARE; pc.printf("->STATE_RX_COMPARE\r\n"); } void cmd_um(uint8_t args_at) { int err; // Tidy up pc.printf("Unmounting... "); fflush(stdout); err = fs.unmount(); pc.printf("%s\n", (err < 0 ? "Fail :(" : "OK")); if (err < 0) { error("error: %s (%d)\n", strerror(-err), err); } } void cmd_op(uint8_t argsAt) { int i; if (sscanf(pcbuf + argsAt, "%d", &i) == 1) Radio::set_tx_dbm(i); tx_dbm_print(); pc.printf("\r\n"); } const menu_item_t menu_items[] = { /* after first character, command names must be [A-Za-z] */ { "stby", cmd_stby, "", "radio standby" }, { "sf", cmd_sf, "<sf>", "set sf" }, { "bw", cmd_bw, "<KHz>", "set bw" }, { "rx", cmd_rx, "", "radio rx" }, { "v", cmd_verbose, "", "toggle verbose printing" }, { "frf", cmd_frf, "<MHz>", "radio center frequency" }, { "op", cmd_op, "<dBm>", "tx dbm" }, { "ls", cmd_ls, "", "directory list" }, { "paddr", cmd_peerAddr, "<hex>", "get/set peer address" }, { "maddr", cmd_myAddr, "<hex>", "get/set my address" }, { "pt", cmd_pt, "", "print test" }, { "um", cmd_um, "", "unmount" }, { "md", cmd_md, "<microseconds>", "set minimum delay between rxDone and tx start" }, { "cat", cmd_cat, "<filename>", "print file content" }, { "send", cmd_send, "<filename>", "send file over radio" }, { "rm", cmd_rm, "<filename>", "delete local file (without directory)" }, { "cmp", cmd_cmp, "<filename>", "compare remote file against local" }, { ".", cmd_print_status, "","print status"}, { "?", cmd_help, "","this list of commands"}, { NULL, NULL, NULL, NULL } }; void cmd_help(uint8_t args_at) { int i; for (i = 0; menu_items[i].cmd != NULL ; i++) { pc.printf("%s%s\t%s\r\n", menu_items[i].cmd, menu_items[i].arg_descr, menu_items[i].description); } } void console() { int i; uint8_t user_cmd_len; if (pcbuf_len < 0) { pc.printf("abort\r\n"); Radio::Standby(); wait(0.02); rxCont(); pcbuf_len = 0; state = STATE_NONE; return; } if (pcbuf_len == 0) { return; } pc.printf("\r\n"); /* get end of user-entered command */ user_cmd_len = 1; // first character can be any character for (i = 1; i <= pcbuf_len; i++) { if (pcbuf[i] < 'A' || (pcbuf[i] > 'Z' && pcbuf[i] < 'a') || pcbuf[i] > 'z') { user_cmd_len = i; break; } } for (i = 0; menu_items[i].cmd != NULL ; i++) { int mi_len = strlen(menu_items[i].cmd); if (menu_items[i].handler && user_cmd_len == mi_len && (strncmp(pcbuf, menu_items[i].cmd, mi_len) == 0)) { while (pcbuf[mi_len] == ' ') // skip past spaces mi_len++; menu_items[i].handler(mi_len); break; } } pcbuf_len = 0; pc.printf("> "); fflush(stdout); } void rx_callback() { static uint8_t prev_len = 0;; char c = pc.getc(); if (c == 8) { if (pcbuf_idx > 0) { pc.putc(8); pc.putc(' '); pc.putc(8); pcbuf_idx--; } } else if (c == 3) { // ctrl-C pcbuf_len = -1; queue.call(console); } else if (c == '\r') { if (pcbuf_idx == 0) { pcbuf_len = prev_len; } else { pcbuf[pcbuf_idx] = 0; // null terminate prev_len = pcbuf_idx; pcbuf_idx = 0; pcbuf_len = prev_len; } queue.call(console); } else if (pcbuf_idx < sizeof(pcbuf)) { pcbuf[pcbuf_idx++] = c; pc.putc(c); } } void set_symb_timeout() { float symbPeriod_ms = (1 << current.sf) / (float)current.bwKHz; float rxtx_turnaround_symbols = TX_RX_TURNAROUND_MS / symbPeriod_ms; pc.printf("sp:%.3f, toSymbs:%.1f\r\n", symbPeriod_ms, rxtx_turnaround_symbols); #if defined(SX126x_H) if (rxtx_turnaround_symbols > (254-8)) { rxtx_turnaround_symbols = 254-8; pc.printf("\e[41msymbTo hit top\e[0m\r\n"); } #endif rxtx_turnaround_symbols = ceil(rxtx_turnaround_symbols); pc.printf("turnSymb:%f\r\n", rxtx_turnaround_symbols); Radio::SetLoRaSymbolTimeout(rxtx_turnaround_symbols + 8); } int main() { #ifdef RX_DBG_PIN rx_dbg.input(); rx_dbg.mode(PullUp); #endif /* RX_DBG_PIN */ pc.attach(rx_callback); pc.baud(115200); pc.printf("--- Mbed OS filesystem example ---\n"); Radio::Init(&rev, SPI_HZ); radio_readChip(); Radio::Standby(); wait(0.05); Radio::LoRaModemConfig(BW_KHZ, SPREADING_FACTOR, 1); Radio::LoRaPacketConfig(8, false, true, false); // preambleLen, fixLen, crcOn, invIQ Radio::SetChannel(CF_HZ); Radio::set_tx_dbm(TX_DBM); current.bwKHz = BW_KHZ; current.sf = SPREADING_FACTOR; set_symb_timeout(); t.start(); wait(0.05); rxCont(); flags.verbose = true; // Try to mount the filesystem pc.printf("Mounting the filesystem... "); fflush(stdout); int err = fs.mount(bd); pc.printf("%s\r\n", (err ? "Fail :(" : "OK\r\n")); if (err) { // Reformat if we can't mount the filesystem // this should only happen on the first boot pc.printf("No filesystem found, formatting... "); fflush(stdout); err = fs.reformat(bd); pc.printf("%s\r\n", (err ? "Fail :(" : "OK\r\n")); if (err) { error("error: %s (%d)\r\n", strerror(-err), err); } } txMinDelay_us = 2000; add_txDelay_retry = 400; th.start(callback(&queue, &EventQueue::dispatch_forever)); for (;;) { Radio::service(); if (rxSize != 0) { rxHandler(Radio::radio.rx_buf); rxSize = 0; } if (flags.rxContStart) { rxCont(); flags.rxContStart = false; } if (flags.callRetry) { retry(true); flags.callRetry = false; } /* servicing txDone is higher priority than printing characters */ while (!flags.txDonePending && printBuf_in != printBuf_out) { pc.putc(printBuf[printBuf_out]); if (++printBuf_out == sizeof(printBuf)) printBuf_out = 0; } } // ..for (;;) }