diff --git a/README.md b/README.md index 1884cd88..c62b393a 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ ## See the [Wiki](https://github.com/jgromes/RadioLib/wiki) for further information. See the [GitHub Pages](https://jgromes.github.io/RadioLib) for detailed and up-to-date API reference. -RadioLib allows its users to integrate all sorts of different wireless communication modules into a single consistent system. -Want to add a Bluetooth interface to your ZigBee network? Sure thing! Need to connect LoRa network to the Internet with a GSM module? RadioLib has got your back! +RadioLib allows its users to integrate all sorts of different wireless communication modules, protocols and even digital modes into a single consistent system. +Want to add a Bluetooth interface to your LoRa network? Sure thing! Do you just want to go really old-school and play around with radio teletype, slow-scan TV, or even Hellschreiber using nothing but a cheap radio module? Why not! RadioLib was originally created as a driver for [__RadioShield__](https://github.com/jgromes/RadioShield), but it can be used to control as many different wireless modules as you like - or at least as many as your Arduino can handle! @@ -27,13 +27,14 @@ RadioLib was originally created as a driver for [__RadioShield__](https://github * __SX1231__ FSK/OOK radio module * __XBee__ modules (S2B) -### Supported protocols: +### Supported protocols and digital modes: * __MQTT__ for modules: ESP8266 * __HTTP__ for modules: ESP8266 -* __RTTY__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x -* __Morse Code__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x * __AX.25__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, RFM2x and Si443x -* __SSTV__ for modules: SX127x, RFM9x, SX126x, RF69 and SX1231 +* [__RTTY__](https://www.sigidwiki.com/wiki/RTTY) for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x +* [__Morse Code__](https://www.sigidwiki.com/wiki/Morse_Code_(CW)) for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x +* [__SSTV__](https://www.sigidwiki.com/wiki/SSTV) for modules: SX127x, RFM9x, SX126x, RF69 and SX1231 +* [__Hellschreiber__](https://www.sigidwiki.com/wiki/Hellschreiber) for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x ### Supported platforms: * __Arduino AVR__ - tested with hardware on Uno, Mega and Leonardo diff --git a/examples/Hellschreiber/Hellschreiber_Transmit/Hellschreiber_Transmit.ino b/examples/Hellschreiber/Hellschreiber_Transmit/Hellschreiber_Transmit.ino new file mode 100644 index 00000000..c1d74d31 --- /dev/null +++ b/examples/Hellschreiber/Hellschreiber_Transmit/Hellschreiber_Transmit.ino @@ -0,0 +1,117 @@ +/* + RadioLib Hellschreiber Transmit Example + + This example sends Hellschreiber message using + SX1278's FSK modem. + + Other modules that can be used for Hellschreiber: + - SX127x/RFM9x + - RF69 + - SX1231 + - CC1101 + - SX126x + - nRF24 + - Si443x/RFM2x + - SX128x +*/ + +// include the library +#include + +// SX1278 has the following connections: +// NSS pin: 10 +// DIO0 pin: 2 +// RESET pin: 9 +// DIO1 pin: 3 +SX1278 fsk = new Module(10, 2, 9, 3); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//SX1278 fsk = RadioShield.ModuleA; + +// create Hellschreiber client instance using the FSK module +HellClient hell(&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(); + + // when using one of the non-LoRa modules for Morse code + // (RF69, CC1101, Si4432 etc.), use the basic begin() method + // int state = fsk.begin(); + + if(state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // initialize Hellschreiber client + Serial.print(F("[Hell] Initializing ... ")); + // base frequency: 434.0 MHz + // speed: 122.5 Baud ("Feld Hell") + state = hell.begin(434.0); + if(state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } +} + +void loop() { + Serial.print(F("[Hell] Sending Hellschreiber data ... ")); + + // HellClient supports all methods of the Serial class + // NOTE: Lower case letter will be capitalized. + + // Arduino String class + String aStr = "Arduino String"; + hell.print(aStr); + + // character array (C-String) + hell.print("C-String"); + + // string saved in flash + hell.print(F("Flash String")); + + // character + hell.print('c'); + + // byte + // formatting DEC/HEX/OCT/BIN is supported for + // any integer type (byte/int/long) + hell.print(255, HEX); + + // integer number + int i = 1000; + hell.print(i); + + // floating point number + // NOTE: println() has no effect on the transmission, + // and is only kept for compatibility reasons. + float f = -3.1415; + hell.println(f, 3); + + // custom glyph - must be a 7 byte array of rows 7 pixels long + uint8_t customGlyph[] = { 0b0000000, 0b0010100, 0b0010100, 0b0000000, 0b0100010, 0b0011100, 0b0000000 }; + hell.printGlyph(customGlyph); + + Serial.println(F("done!")); + + // wait for a second before transmitting again + delay(1000); +} diff --git a/keywords.txt b/keywords.txt index bcc233c4..21e58818 100644 --- a/keywords.txt +++ b/keywords.txt @@ -52,6 +52,7 @@ PagerClient KEYWORD1 AX25Client KEYWORD1 AX25Frame KEYWORD1 SSTVClient KEYWORD1 +HellClient KEYWORD1 # SSTV modes Scottie1 KEYWORD1 @@ -221,6 +222,9 @@ range KEYWORD2 startRanging KEYWORD2 getRangingResult KEYWORD2 +# Hellschreiber +printGlyph KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### diff --git a/src/RadioLib.h b/src/RadioLib.h index d4911d51..04ea18e7 100644 --- a/src/RadioLib.h +++ b/src/RadioLib.h @@ -86,6 +86,7 @@ // physical layer protocols #include "protocols/PhysicalLayer/PhysicalLayer.h" #include "protocols/AX25/AX25.h" +#include "protocols/Hellschreiber/Hellschreiber.h" #include "protocols/Morse/Morse.h" #include "protocols/RTTY/RTTY.h" #include "protocols/SSTV/SSTV.h" diff --git a/src/protocols/Hellschreiber/Hellschreiber.cpp b/src/protocols/Hellschreiber/Hellschreiber.cpp new file mode 100644 index 00000000..ffc30ee3 --- /dev/null +++ b/src/protocols/Hellschreiber/Hellschreiber.cpp @@ -0,0 +1,271 @@ +#include "Hellschreiber.h" + +HellClient::HellClient(PhysicalLayer* phy) { + _phy = phy; +} + +int16_t HellClient::begin(float base, float rate) { + // calculate 24-bit frequency + _base = (base * 1000000.0) / _phy->getFreqStep(); + + // calculate "pixel" duration + _pixelDuration = 1000000.0/rate; + + // set module frequency deviation to 0 + int16_t state = _phy->setFrequencyDeviation(0); + + return(state); +} + +size_t HellClient::printGlyph(uint8_t* buff) { + // print the character + for(uint8_t mask = 0x40; mask >= 0x01; mask >>= 1) { + for(int8_t i = HELL_FONT_HEIGHT - 1; i >= 0; i--) { + uint32_t start = micros(); + if(buff[i] & mask) { + _phy->transmitDirect(_base); + } else { + _phy->standby(); + } + while(micros() - start < _pixelDuration); + } + } + + // make sure transmitter is off + _phy->standby(); + + return(1); +} + +size_t HellClient::write(const char* str) { + if(str == NULL) { + return(0); + } + return(HellClient::write((uint8_t *)str, strlen(str))); +} + +size_t HellClient::write(uint8_t* buff, size_t len) { + size_t n = 0; + for(size_t i = 0; i < len; i++) { + n += HellClient::write(buff[i]); + } + return(n); +} + +size_t HellClient::write(uint8_t b) { + // convert to position in font buffer + uint8_t pos = b; + if((pos >= ' ') && (pos <= '_')) { + pos -= ' '; + } else if((pos >= 'a') && (pos <= 'z')) { + pos -= (2*' '); + } else { + return(0); + } + + // fetch character from flash + uint8_t buff[HELL_FONT_WIDTH]; + buff[0] = 0x00; + for(uint8_t i = 0; i < HELL_FONT_WIDTH - 2; i++) { + buff[i + 1] = pgm_read_byte(&HellFont[pos][i]); + } + buff[HELL_FONT_WIDTH - 1] = 0x00; + + // print the character + return(printGlyph(buff)); +} + +size_t HellClient::print(__FlashStringHelper* fstr) { + PGM_P p = reinterpret_cast(fstr); + size_t n = 0; + while(true) { + char c = pgm_read_byte(p++); + if(c == '\0') { + break; + } + n += HellClient::write(c); + } + return n; +} + +size_t HellClient::print(const String& str) { + return(HellClient::write((uint8_t*)str.c_str(), str.length())); +} + +size_t HellClient::print(const char* str) { + return(HellClient::write((uint8_t*)str, strlen(str))); +} + +size_t HellClient::print(char c) { + return(HellClient::write(c)); +} + +size_t HellClient::print(unsigned char b, int base) { + return(HellClient::print((unsigned long)b, base)); +} + +size_t HellClient::print(int n, int base) { + return(HellClient::print((long)n, base)); +} + +size_t HellClient::print(unsigned int n, int base) { + return(HellClient::print((unsigned long)n, base)); +} + +size_t HellClient::print(long n, int base) { + if(base == 0) { + return(HellClient::write(n)); + } else if(base == DEC) { + if (n < 0) { + int t = HellClient::print('-'); + n = -n; + return(HellClient::printNumber(n, DEC) + t); + } + return(HellClient::printNumber(n, DEC)); + } else { + return(HellClient::printNumber(n, base)); + } +} + +size_t HellClient::print(unsigned long n, int base) { + if(base == 0) { + return(HellClient::write(n)); + } else { + return(HellClient::printNumber(n, base)); + } +} + +size_t HellClient::print(double n, int digits) { + return(HellClient::printFloat(n, digits)); +} + +size_t HellClient::println(void) { + return(0); +} + +size_t HellClient::println(__FlashStringHelper* fstr) { + size_t n = HellClient::print(fstr); + n += HellClient::println(); + return(n); +} + +size_t HellClient::println(const String& str) { + size_t n = HellClient::print(str); + n += HellClient::println(); + return(n); +} + +size_t HellClient::println(const char* str) { + size_t n = HellClient::print(str); + n += HellClient::println(); + return(n); +} + +size_t HellClient::println(char c) { + size_t n = HellClient::print(c); + n += HellClient::println(); + return(n); +} + +size_t HellClient::println(unsigned char b, int base) { + size_t n = HellClient::print(b, base); + n += HellClient::println(); + return(n); +} + +size_t HellClient::println(int num, int base) { + size_t n = HellClient::print(num, base); + n += HellClient::println(); + return(n); +} + +size_t HellClient::println(unsigned int num, int base) { + size_t n = HellClient::print(num, base); + n += HellClient::println(); + return(n); +} + +size_t HellClient::println(long num, int base) { + size_t n = HellClient::print(num, base); + n += HellClient::println(); + return(n); +} + +size_t HellClient::println(unsigned long num, int base) { + size_t n = HellClient::print(num, base); + n += HellClient::println(); + return(n); +} + +size_t HellClient::println(double d, int digits) { + size_t n = HellClient::print(d, digits); + n += HellClient::println(); + return(n); +} + +size_t HellClient::printNumber(unsigned long n, uint8_t base) { + char buf[8 * sizeof(long) + 1]; + char *str = &buf[sizeof(buf) - 1]; + + *str = '\0'; + + if(base < 2) { + base = 10; + } + + do { + char c = n % base; + n /= base; + + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } while(n); + + return(HellClient::write(str)); +} + +size_t HellClient::printFloat(double number, uint8_t digits) { + size_t n = 0; + + char code[] = {0x00, 0x00, 0x00, 0x00}; + if (isnan(number)) strcpy(code, "nan"); + if (isinf(number)) strcpy(code, "inf"); + if (number > 4294967040.0) strcpy(code, "ovf"); // constant determined empirically + if (number <-4294967040.0) strcpy(code, "ovf"); // constant determined empirically + + if(code[0] != 0x00) { + return(HellClient::write(code)); + } + + // Handle negative numbers + if (number < 0.0) { + n += HellClient::print('-'); + number = -number; + } + + // Round correctly so that print(1.999, 2) prints as "2.00" + double rounding = 0.5; + for(uint8_t i = 0; i < digits; ++i) { + rounding /= 10.0; + } + number += rounding; + + // Extract the integer part of the number and print it + unsigned long int_part = (unsigned long)number; + double remainder = number - (double)int_part; + n += HellClient::print(int_part); + + // Print the decimal point, but only if there are digits beyond + if(digits > 0) { + n += HellClient::print('.'); + } + + // Extract digits from the remainder one at a time + while(digits-- > 0) { + remainder *= 10.0; + unsigned int toPrint = (unsigned int)(remainder); + n += HellClient::print(toPrint); + remainder -= toPrint; + } + + return n; +} diff --git a/src/protocols/Hellschreiber/Hellschreiber.h b/src/protocols/Hellschreiber/Hellschreiber.h new file mode 100644 index 00000000..9b14c27e --- /dev/null +++ b/src/protocols/Hellschreiber/Hellschreiber.h @@ -0,0 +1,151 @@ +#ifndef _RADIOLIB_HELLSCHREIBER_H +#define _RADIOLIB_HELLSCHREIBER_H + +#include "../../TypeDef.h" +#include "../PhysicalLayer/PhysicalLayer.h" + +#define HELL_FONT_WIDTH 7 +#define HELL_FONT_HEIGHT 7 + +// font definition: characters are stored in rows, +// least significant byte of each character is the first row +// Hellschreiber use 7x7 characters, but this simplified font uses only 5x5 - the extra bytes aren't stored +static const uint8_t HellFont[64][HELL_FONT_WIDTH - 2] PROGMEM = { + { 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b0000000 }, // space + { 0b0001000, 0b0001000, 0b0001000, 0b0000000, 0b0001000 }, // ! + { 0b0010100, 0b0010100, 0b0000000, 0b0000000, 0b0000000 }, // " + { 0b0010100, 0b0111110, 0b0010100, 0b0111110, 0b0010100 }, // # + { 0b0111110, 0b0101000, 0b0111110, 0b0001010, 0b0111110 }, // $ + { 0b0110010, 0b0110100, 0b0001000, 0b0010110, 0b0100110 }, // % + { 0b0010000, 0b0101000, 0b0010000, 0b0101000, 0b0110100 }, // & + { 0b0001000, 0b0001000, 0b0000000, 0b0000000, 0b0000000 }, // ' + { 0b0000100, 0b0001000, 0b0001000, 0b0001000, 0b0000100 }, // ( + { 0b0010000, 0b0001000, 0b0001000, 0b0001000, 0b0010000 }, // ) + { 0b0010100, 0b0001000, 0b0010100, 0b0000000, 0b0000000 }, // * + { 0b0001000, 0b0001000, 0b0111110, 0b0001000, 0b0001000 }, // + + { 0b0001000, 0b0010000, 0b0000000, 0b0000000, 0b0000000 }, // ยด + { 0b0000000, 0b0000000, 0b0111110, 0b0000000, 0b0000000 }, // - + { 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b0001000 }, // . + { 0b0000010, 0b0000100, 0b0001000, 0b0010000, 0b0100000 }, // / + { 0b0011100, 0b0100110, 0b0101010, 0b0110010, 0b0011100 }, // 0 + { 0b0011000, 0b0001000, 0b0001000, 0b0001000, 0b0001000 }, // 1 + { 0b0011000, 0b0100100, 0b0001000, 0b0010000, 0b0111100 }, // 2 + { 0b0111100, 0b0000100, 0b0011100, 0b0000100, 0b0111100 }, // 3 + { 0b0100100, 0b0100100, 0b0111100, 0b0000100, 0b0000100 }, // 4 + { 0b0011100, 0b0100000, 0b0111100, 0b0000100, 0b0111100 }, // 5 + { 0b0111100, 0b0100000, 0b0111100, 0b0100100, 0b0111100 }, // 6 + { 0b0111100, 0b0000100, 0b0001000, 0b0010000, 0b0100000 }, // 7 + { 0b0111100, 0b0100100, 0b0011000, 0b0100100, 0b0111100 }, // 8 + { 0b0111100, 0b0100100, 0b0111100, 0b0000100, 0b0111100 }, // 9 + { 0b0000000, 0b0001000, 0b0000000, 0b0000000, 0b0001000 }, // : + { 0b0000000, 0b0001000, 0b0000000, 0b0001000, 0b0001000 }, // ; + { 0b0000100, 0b0001000, 0b0010000, 0b0001000, 0b0000100 }, // < + { 0b0000000, 0b0111110, 0b0000000, 0b0111110, 0b0000000 }, // = + { 0b0010000, 0b0001000, 0b0000100, 0b0001000, 0b0010000 }, // > + { 0b0011100, 0b0000100, 0b0001000, 0b0000000, 0b0001000 }, // ? + { 0b0011100, 0b0100010, 0b0101110, 0b0101010, 0b0001100 }, // @ + { 0b0111110, 0b0100010, 0b0111110, 0b0100010, 0b0100010 }, // A + { 0b0111100, 0b0010010, 0b0011110, 0b0010010, 0b0111100 }, // B + { 0b0011110, 0b0110000, 0b0100000, 0b0110000, 0b0011110 }, // C + { 0b0111100, 0b0100010, 0b0100010, 0b0100010, 0b0111100 }, // D + { 0b0111110, 0b0100000, 0b0111100, 0b0100000, 0b0111110 }, // E + { 0b0111110, 0b0100000, 0b0111100, 0b0100000, 0b0100000 }, // F + { 0b0111110, 0b0100000, 0b0101110, 0b0100010, 0b0111110 }, // G + { 0b0100010, 0b0100010, 0b0111110, 0b0100010, 0b0100010 }, // H + { 0b0011100, 0b0001000, 0b0001000, 0b0001000, 0b0011100 }, // I + { 0b0111100, 0b0001000, 0b0001000, 0b0101000, 0b0111000 }, // J + { 0b0100100, 0b0101000, 0b0110000, 0b0101000, 0b0100100 }, // K + { 0b0100000, 0b0100000, 0b0100000, 0b0100000, 0b0111100 }, // L + { 0b0100010, 0b0110110, 0b0101010, 0b0100010, 0b0100010 }, // M + { 0b0100010, 0b0110010, 0b0101010, 0b0100110, 0b0100010 }, // N + { 0b0011100, 0b0100010, 0b0100010, 0b0100010, 0b0011100 }, // O + { 0b0111110, 0b0100010, 0b0111110, 0b0100000, 0b0100000 }, // P + { 0b0111110, 0b0100010, 0b0100010, 0b0100110, 0b0111110 }, // Q + { 0b0111110, 0b0100010, 0b0111110, 0b0100100, 0b0100010 }, // R + { 0b0111110, 0b0100000, 0b0111110, 0b0000010, 0b0111110 }, // S + { 0b0111110, 0b0001000, 0b0001000, 0b0001000, 0b0001000 }, // T + { 0b0100010, 0b0100010, 0b0100010, 0b0100010, 0b0111110 }, // U + { 0b0100010, 0b0100010, 0b0010100, 0b0010100, 0b0001000 }, // V + { 0b0100010, 0b0100010, 0b0101010, 0b0110110, 0b0100010 }, // W + { 0b0100010, 0b0010100, 0b0001000, 0b0010100, 0b0100010 }, // X + { 0b0100010, 0b0010100, 0b0001000, 0b0001000, 0b0001000 }, // Y + { 0b0111110, 0b0000100, 0b0001000, 0b0010000, 0b0111110 }, // Z + { 0b0001100, 0b0001000, 0b0001000, 0b0001000, 0b0001100 }, // [ + { 0b0100000, 0b0010000, 0b0001000, 0b0000100, 0b0000010 }, // backslash + { 0b0011000, 0b0001000, 0b0001000, 0b0001000, 0b0011000 }, // ] + { 0b0001000, 0b0010100, 0b0000000, 0b0000000, 0b0000000 }, // ^ + { 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b0111110 } // _ +}; + +/*! + \class HellClient + + \brief Client for Hellschreiber transmissions. +*/ +class HellClient { + public: + /*! + \brief Default constructor. + + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + HellClient(PhysicalLayer* phy); + + // basic methods + + /*! + \brief Initialization method. + + \param base Base RF frequency to be used in MHz. + + \param rate Baud rate to be used during transmission. Defaults to 122.5 ("Feld Hell") + */ + int16_t begin(float base, float rate = 122.5); + + /*! + \brief Method to "print" a buffer of pixels, this is exposed to allow users to send custom characters. + + \param buff Buffer of pixels to send, in a 7x7 pixel array. + */ + size_t printGlyph(uint8_t* buff); + + size_t write(const char* str); + size_t write(uint8_t* buff, size_t len); + size_t write(uint8_t b); + + size_t print(__FlashStringHelper*); + size_t print(const String &); + size_t print(const char[]); + size_t print(char); + size_t print(unsigned char, int = DEC); + size_t print(int, int = DEC); + size_t print(unsigned int, int = DEC); + size_t print(long, int = DEC); + size_t print(unsigned long, int = DEC); + size_t print(double, int = 2); + + size_t println(void); + size_t println(__FlashStringHelper*); + size_t println(const String &s); + size_t println(const char[]); + size_t println(char); + size_t println(unsigned char, int = DEC); + size_t println(int, int = DEC); + size_t println(unsigned int, int = DEC); + size_t println(long, int = DEC); + size_t println(unsigned long, int = DEC); + size_t println(double, int = 2); + +#ifndef RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* _phy; + + uint32_t _base; + uint32_t _pixelDuration; + + size_t printNumber(unsigned long, uint8_t); + size_t printFloat(double, uint8_t); +}; + +#endif