From 05498c2598a99e94b1250753e3535b5ab7f6802f Mon Sep 17 00:00:00 2001 From: jgromes Date: Wed, 29 May 2019 10:52:43 +0200 Subject: [PATCH] [Pager][WIP] Added Pager files --- .../Pager/Pager_Transmit/Pager_Transmit.ino | 78 ++++++++ keywords.txt | 2 + src/RadioLib.h | 1 + src/protocols/Pager.cpp | 184 ++++++++++++++++++ src/protocols/Pager.h | 50 +++++ 5 files changed, 315 insertions(+) create mode 100644 examples/Pager/Pager_Transmit/Pager_Transmit.ino create mode 100644 src/protocols/Pager.cpp create mode 100644 src/protocols/Pager.h diff --git a/examples/Pager/Pager_Transmit/Pager_Transmit.ino b/examples/Pager/Pager_Transmit/Pager_Transmit.ino new file mode 100644 index 00000000..7ba86e48 --- /dev/null +++ b/examples/Pager/Pager_Transmit/Pager_Transmit.ino @@ -0,0 +1,78 @@ +/* + RadioLib Pager (POCSAG) Transmit Example + + This example sends POCSAG messages using SX1278's + FSK modem. + + Other modules that can be used for POCSAG: + - SX127x/RFM9x + - RF69 + - SX1231 + - CC1101 + - SX126x +*/ + +// include the library +#include + +// SX1278 module is in slot A on the shield +SX1278 fsk = RadioShield.ModuleA; + +// create Pager client instance using the FSK module +PagerClient pager(&fsk); + +void setup() { + Serial.begin(9600); + + // initialize SX1278 + Serial.print(F("[SX1278] Initializing ... ")); + // carrier frequency: 434.0 MHz + // bit rate: 48.0 kbps + // frequency deviation: 50.0 kHz + // Rx bandwidth: 125.0 kHz + // output power: 13 dBm + // current limit: 100 mA + // sync word: 0x2D 0x01 + int state = fsk.beginFSK(); + if(state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // initalize Pager client + Serial.print(F("[Pager] Initializing ... ")); + // base (center) frequency: 434.0 MHz + // speed: 1200 bps + state = pager.begin(434.0, 1200); + if(state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } +} + +void loop() { + // transmit numeric (BCD) message to the destination pager + Serial.print(F("[Pager] Transmitting message ... ")); + int state = pager.transmit("0123456789*U -()", 0x01234567); + // NOTE: Only characters 0123456789*U-() and space + // can be sent in a BCD message! To send ASCII + // characters, you have to set encoding to ASCII. + /* + int state = pager.transmit("Hello World!", 0x01234567, ASCII); + */ + if (state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + } + + // wait for a second before transmitting again + delay(1000); +} diff --git a/keywords.txt b/keywords.txt index aa731e66..9004e9d1 100644 --- a/keywords.txt +++ b/keywords.txt @@ -38,6 +38,7 @@ MQTTClient KEYWORD1 HTTPClient KEYWORD1 RTTYClient KEYWORD1 MorseClient KEYWORD1 +PagerClient KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -210,6 +211,7 @@ ERR_FRAME_NO_RESPONSE LITERAL1 ASCII LITERAL1 ASCII_EXTENDED LITERAL1 ITA2 LITERAL1 +BCD LITERAL1 ERR_INVALID_RTTY_SHIFT LITERAL1 ERR_UNSUPPORTED_ENCODING LITERAL1 diff --git a/src/RadioLib.h b/src/RadioLib.h index 58b56fff..fb40667b 100644 --- a/src/RadioLib.h +++ b/src/RadioLib.h @@ -62,6 +62,7 @@ #include "protocols/PhysicalLayer.h" #include "protocols/Morse.h" #include "protocols/RTTY.h" +#include "protocols/Pager.h" // transport layer protocols #include "protocols/TransportLayer.h" diff --git a/src/protocols/Pager.cpp b/src/protocols/Pager.cpp new file mode 100644 index 00000000..c02504cb --- /dev/null +++ b/src/protocols/Pager.cpp @@ -0,0 +1,184 @@ +#include "Pager.h" + +PagerClient::PagerClient(PhysicalLayer* phy) { + _phy = phy; +} + +int16_t PagerClient::begin(float base, uint16_t speed) { + // calculate duration of 1 bit in us + _bitDuration = (uint32_t)1000000/speed; + + // calculate 24-bit frequency + _base = (base * (uint32_t(1) << _phy->getDivExponent())) / _phy->getCrystalFreq(); + + // calculate module carrier frequency resolution + uint16_t step = round((_phy->getCrystalFreq() * 1000000) / (uint32_t(1) << _phy->getDivExponent())); + + // calculate raw frequency shift + _shift = FREQ_SHIFT_HZ/step; + + // set module frequency deviation to 0 + int16_t state = _phy->setFrequencyDeviation(0); + + return(state); +} + +int16_t PagerClient::transmit(String& str, uint32_t addr, uint8_t encoding) { + return(PagerClient::transmit(str.c_str(), addr, encoding)); +} + +int16_t PagerClient::transmit(const char* str, uint32_t addr, uint8_t encoding) { + return(PagerClient::transmit((uint8_t*)str, strlen(str), addr, encoding)); +} + +int16_t PagerClient::transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding) { + // get symbol bit length based on encoding + uint8_t symbolLength = 0; + if(encoding == BCD) { + symbolLength = 4; + + // replace ASCII characters with BCD representations + for(size_t i = 0; i < len; i++) { + switch(data[i]) { + case '*': + data[i] = 0x0A; + break; + case 'U': + data[i] = 0x0B; + break; + case ' ': + data[i] = 0x0C; + break; + case '-': + data[i] = 0x0D; + break; + case ')': + data[i] = 0x0E; + break; + case '(': + data[i] = 0x0F; + break; + default: + data[i] -= '0'; + break; + } + } + + } else if(encoding == ASCII) { + symbolLength = 7; + } else { + return(ERR_UNKNOWN); + } + + // get target position in batch (3 LSB from address determine frame position in batch) + uint8_t framePos = addr & 0b0000000000000111; + + // get address that will be written into address frame + uint16_t frameAddr = (addr & 0b1111111111111000) >> 3; + + // calculate the number of 20-bit data blocks + size_t numDataBlocks = (len * symbolLength) / MESSAGE_BITS_LENGTH; + if((len * symbolLength) % MESSAGE_BITS_LENGTH > 0) { + numDataBlocks += 1; + } + + // calculate number of batches + size_t numBatches = (1 + framePos + numDataBlocks) / 16 + 1; + if((1 + numDataBlocks) % 16 == 0) { + numBatches -= 1; + } + + // calculate message length in 32-bit code words + size_t msgLen = PREAMBLE_LENGTH + (1 + 16) * numBatches; + + // build the message + uint32_t* msg = new uint32_t[msgLen]; + // TODO: BCD padding? + memset(msg, 0x00, msgLen); + + // set preamble + for(size_t i = 0; i < PREAMBLE_LENGTH; i++) { + msg[i] = PREAMBLE_CODE_WORD; + } + + // set frame synchronization code words + for(size_t i = 0; i < numBatches; i++) { + msg[PREAMBLE_LENGTH + i*(1 + 16)] = FRAME_SYNC_CODE_WORD; + } + + // set unused code words to idle + for(size_t i = 0; i < framePos; i++) { + msg[PREAMBLE_LENGTH + 1 + i] = IDLE_CODE_WORD; + } + + // write address code word + msg[PREAMBLE_LENGTH + 1 + framePos] = addParity(encodeBCH((addr << 1) | ADDRESS_CODE_WORD)); + + // split the data into 20-bit blocks + size_t bitPos = MESSAGE_BITS_LENGTH; + size_t blockPos = PREAMBLE_LENGTH + 1; + for(size_t i = 0; i < len; i++) { + // check if the next data symbol fits into the remaining space in current 20-bit block + if(bitPos >= symbolLength) { + // insert the whole data symbol into current block + msg[blockPos] |= (uint32_t)data[i] << (bitPos - symbolLength); + bitPos -= symbolLength; + } else { + // split the symbol between two blocks + uint8_t msbPart = data[i]; + size_t lsbLen = symbolLength - bitPos; + msg[blockPos] |= msbPart >> lsbLen; + blockPos++; + bitPos = MESSAGE_BITS_LENGTH; + uint8_t lsbPart = data[i] & ((1 << lsbLen) - 1); + msg[blockPos] |= (uint32_t)lsbPart << (bitPos - lsbLen); + bitPos -= lsbLen; + } + } + + // write message code words + + // transmit the message + PagerClient::write(msg, msgLen); + + delete[] msg; + + // turn transmitter off + _phy->standby(); + + return(ERR_NONE); +} + +void PagerClient::write(uint32_t* data, size_t len) { + // write code words from buffer + for(size_t i = 0; i < len; i++) { + PagerClient::write(data[i]); + } +} + +void PagerClient::write(uint32_t codeWord) { + // write single code word + for(uint8_t i = 0; i <= 31; i++) { + uint32_t mask = (uint32_t)0x01 << i; + if(codeWord & mask) { + // send 1 + uint32_t start = micros(); + _phy->transmitDirect(_base + _shift); + while(micros() - start < _bitDuration); + } else { + // send 0 + uint32_t start = micros(); + _phy->transmitDirect(_base - _shift); + while(micros() - start < _bitDuration); + } + } +} + +uint32_t PagerClient::encodeBCH(uint32_t data) { + return(data); +} + + +uint32_t PagerClient::addParity(uint32_t msg) { + return(msg); +} diff --git a/src/protocols/Pager.h b/src/protocols/Pager.h new file mode 100644 index 00000000..f0d92cb3 --- /dev/null +++ b/src/protocols/Pager.h @@ -0,0 +1,50 @@ +#ifndef _RADIOLIB_PAGER_H +#define _RADIOLIB_PAGER_H + +#include "TypeDef.h" +#include "PhysicalLayer.h" + +// supported encoding schemes +#define ASCII 0 +#define BCD 1 + +#define PREAMBLE_LENGTH 18 +#define MESSAGE_BITS_LENGTH 20 + +#define FREQ_SHIFT_HZ 4500 + +#define PREAMBLE_CODE_WORD 0xAAAAAAAA +#define FRAME_SYNC_CODE_WORD 0x7CD215D8 +#define IDLE_CODE_WORD 0x7A89C197 +#define ADDRESS_CODE_WORD 0 +#define MESSAGE_CODE_WORD 1 + +#define BCH_GENERATOR_POLYNOMIAL 0b11101101001 // x^10 + x^9 + x^8 + x^6 + x^5 + x^3 + 1 + +class PagerClient { + public: + PagerClient(PhysicalLayer* phy); + + // basic methods + int16_t begin(float base, uint16_t speed); + int16_t transmit(String& str, uint32_t addr, uint8_t encoding = BCD); + int16_t transmit(const char* str, uint32_t addr, uint8_t encoding = BCD); + int16_t transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding = BCD); + + // TODO: add receiving + option to listen to all packets + + private: + PhysicalLayer* _phy; + + uint32_t _base; + uint16_t _shift; + uint16_t _bitDuration; + + void write(uint32_t* data, size_t len); + void write(uint32_t b); + + uint32_t encodeBCH(uint32_t data); + uint32_t addParity(uint32_t msg); +}; + +#endif