[Hell] Added Hellschreiber support

This commit is contained in:
jgromes 2020-04-14 09:33:39 +02:00
parent 1592831e0c
commit abad47bdae
6 changed files with 551 additions and 6 deletions

View file

@ -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

View file

@ -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 <RadioLib.h>
// 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);
}

View file

@ -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)
#######################################

View file

@ -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"

View file

@ -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<PGM_P>(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;
}

View file

@ -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