diff --git a/FSK4_Mod.ino b/FSK4_Mod.ino new file mode 100644 index 0000000..6678854 --- /dev/null +++ b/FSK4_Mod.ino @@ -0,0 +1,104 @@ +// MFSK Modulation + +#include + +uint32_t fsk4_base = 0, fsk4_baseHz = 0; +uint32_t fsk4_shift = 0, fsk4_shiftHz = 0; +uint32_t fsk4_bitDuration = 0; +uint32_t fsk4_tones[4]; +uint32_t fsk4_tonesHz[4]; + + +int16_t fsk4_setup(PhysicalLayer* phy, float base, uint32_t shift, uint16_t rate){ + // save configuration + fsk4_baseHz = base; + fsk4_shiftHz = shift; + + + // calculate duration of 1 bit + fsk4_bitDuration = (uint32_t)1000000/rate; + + // calculate module carrier frequency resolution + uint32_t step = round(phy->getFreqStep()); + + // check minimum shift value + if(shift < step / 2) { + return 0; + } + + // round shift to multiples of frequency step size + if(shift % step < (step / 2)) { + fsk4_shift = shift / step; + } else { + fsk4_shift = (shift / step) + 1; + } + + // Write resultant tones into arrays for quick lookup when modulating. + fsk4_tones[0] = 0; + fsk4_tones[1] = fsk4_shift; + fsk4_tones[2] = fsk4_shift*2; + fsk4_tones[3] = fsk4_shift*3; + + + // calculate 24-bit frequency + fsk4_base = (base * 1000000.0) / phy->getFreqStep(); + + Serial.println(fsk4_base); + + // configure for direct mode + return(phy->startDirect()); + +} + +int16_t fsk4_transmitDirect(PhysicalLayer* phy, uint32_t freq) { + return(phy->transmitDirect(freq)); +} + +void fsk4_tone(PhysicalLayer* phy, uint8_t i) { + uint32_t start = Module::micros(); + fsk4_transmitDirect(phy, fsk4_base + fsk4_tones[i]); + //delayMicroseconds(fsk4_bitDuration); + while(Module::micros() - start < fsk4_bitDuration) { + Module::yield(); + } +} + +void fsk4_idle(PhysicalLayer* phy){ + fsk4_tone(phy, 0); +} + +void fsk4_standby(PhysicalLayer* phy){ + phy->standby(); +} + +void fsk4_preamble(PhysicalLayer* phy, uint8_t len){ + int k; + for (k=0; k> 6; + // Modulate + fsk4_tone(phy, symbol); + // Shift to next symbol. + b = b << 2; + } + + return(1); +} + +void fsk4_write(PhysicalLayer* phy, uint8_t* buff, size_t len){ + size_t n = 0; + for(size_t i = 0; i < len; i++) { + n += fsk4_writebyte(phy, buff[i]); + } + fsk4_standby(phy); + return(n); +} diff --git a/README.md b/README.md index 168ad55..82e2f8d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,27 @@ -# horusbinary_radiolib -Horus Binary 4FSK Transmit Example, using RadioLib +# Horus Binary 4FSK Transmit Example, using RadioLib + +This repository contains a worked example of generating and transmitting [Horus Binary](https://github.com/projecthorus/horusdemodlib/wiki) v1 and v2 high-altitude balloon telemetry packets on an Arduino-compatible platform, using [RadioLib](https://github.com/jgromes/RadioLib). + +This is not a complete high-altitude-balloon tracker codebase, just an example of generating and transmitting Horus Binary packets, for integration into other codebases. + +This example uses a Semtech SX1278-compatible radio module (in my case, a HopeRF RFM98W), connected to an Arduino-compatible microcontroller (I used a Seeduino Mega 2560 because it would do 3.3v logic levels). In my case, the module has the usual SPI connections, and the following other pins are used: +* NSS pin: 10 +* DIO0 pin: 2 +* RESET pin: 9 +* DIO1 pin: 3 + + +### Contacts +* Mark Jessop + +## Dependencies +* Arduino IDE (or other compiler that can compile Arduino projects) +* RadioLib Library - https://github.com/jgromes/RadioLib + +## Compiling this Example +It should be possible to just open the horusbinary_radiolib.ino file in the Arduino IDE, select your target platform, and compile/flash. + +Note that I am using some AVR-specific CRC16 libraries (`util/crc16.h`), which will probably need to be replaced for use on other platforms (refer `util.ino`). + +## TODO List +* Split out Horus-specific code into separate files, for easier integration into other codebases. \ No newline at end of file diff --git a/horus_l2.cpp b/horus_l2.cpp new file mode 100644 index 0000000..82b6013 --- /dev/null +++ b/horus_l2.cpp @@ -0,0 +1,1184 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: horus_l2.c + AUTHOR......: David Rowe + DATE CREATED: Dec 2015 + + Horus telemetry layer 2 processing. Takes an array of 8 bit payload + data, generates parity bits for a (23,12) Golay code, interleaves + data and parity bits, pre-pends a Unique Word for modem sync. + Caller is responsible for providing storage for output packet. + + [ ] code based interleaver + [ ] test correction of 1,2 & 3 error patterms + + 1/ Unit test on a PC: + + $ gcc horus_l2.c -o horus_l2 -Wall -DHORUS_L2_UNITTEST + $ ./horus_l2 + + test 0: 22 bytes of payload data BER: 0.00 errors: 0 + test 0: 22 bytes of payload data BER: 0.01 errors: 0 + test 0: 22 bytes of payload data BER: 0.05 errors: 0 + test 0: 22 bytes of payload data BER: 0.10 errors: 7 + + This indicates it's correcting all channel errors for 22 bytes of + payload data, at bit error rate (BER) of 0, 0.01, 0.05. It falls + over at a BER of 0.10 which is expected. + + 2/ To build with just the tx function, ie for linking with the payload + firmware: + + $ gcc horus_l2.c -c -Wall + + By default the RX side is #ifdef-ed out, leaving the minimal amount + of code for tx. + + 3/ Generate some tx_bits as input for testing with fsk_horus: + + $ gcc horus_l2.c -o horus_l2 -Wall -DGEN_TX_BITS -DSCRAMBLER + $ ./horus_l2 + $ more ../octave/horus_tx_bits_binary.txt + + 4/ Unit testing interleaver: + + $ gcc horus_l2.c -o horus_l2 -Wall -DINTERLEAVER -DTEST_INTERLEAVER -DSCRAMBLER + + 5/ Compile for use as decoder called by fsk_horus.m and fsk_horus_stream.m: + + $ gcc horus_l2.c -o horus_l2 -Wall -DDEC_RX_BITS -DHORUS_L2_RX + +\*---------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include "horus_l2.h" + +#ifdef HORUS_L2_UNITTEST +#define HORUS_L2_RX +#endif + +#define RUN_TIME_TABLES + +#define INTERLEAVER +#define SCRAMBLER + +static char uw[] = {'$','$'}; + +/* Function Prototypes ------------------------------------------------*/ + +int32_t get_syndrome(int32_t pattern); +void golay23_init(void); +int golay23_decode(int received_codeword); +unsigned short gen_crc16(unsigned char* data_p, unsigned char length); +void interleave(unsigned char *inout, int nbytes, int dir); +void scramble(unsigned char *inout, int nbytes); + +/* Functions ----------------------------------------------------------*/ + +/* + We are using a Golay (23,12) code which has a codeword 23 bits + long. The tx packet format is: + + | Unique Word | payload data bits | parity bits | + + This function works out how much storage the caller of + horus_l2_encode_tx_packet() will need to store the tx packet + */ + +int horus_l2_get_num_tx_data_bytes(int num_payload_data_bytes) { + int num_payload_data_bits, num_golay_codewords; + int num_tx_data_bits, num_tx_data_bytes; + + num_payload_data_bits = num_payload_data_bytes*8; + num_golay_codewords = num_payload_data_bits/12; + if (num_payload_data_bits % 12) /* round up to 12 bits, may mean some unused bits */ + num_golay_codewords++; + + num_tx_data_bits = sizeof(uw)*8 + num_payload_data_bits + num_golay_codewords*11; + num_tx_data_bytes = num_tx_data_bits/8; + if (num_tx_data_bits % 8) /* round up to nearest byte, may mean some unused bits */ + num_tx_data_bytes++; + + #ifdef DEBUG0 + fprintf(stderr, "\nnum_payload_data_bytes: %d\n", num_payload_data_bytes); + fprintf(stderr, "num_golay_codewords...: %d\n", num_golay_codewords); + fprintf(stderr, "num_tx_data_bits......: %d\n", num_tx_data_bits); + fprintf(stderr, "num_tx_data_bytes.....: %d\n\n", num_tx_data_bytes); + #endif + + return num_tx_data_bytes; +} + + +/* + Takes an array of payload data bytes, prepends a unique word and appends + parity bits. + + The encoder will run on the payload on a small 8-bit uC. As we are + memory constrained so we do a lot of burrowing for bits out of + packed arrays, and don't use a LUT for Golay encoding. Hopefully it + will run fast enough. This was quite difficult to get going, + suspect there is a better way to write this. Oh well, have to start + somewhere. + */ + +int horus_l2_encode_tx_packet(unsigned char *output_tx_data, + unsigned char *input_payload_data, + int num_payload_data_bytes) +{ + int num_tx_data_bytes, num_payload_data_bits; + unsigned char *pout = output_tx_data; + int ninbit, ningolay, nparitybits; + int32_t ingolay, paritybyte, inbit, golayparity; + int ninbyte, shift, golayparitybit, i; + + num_tx_data_bytes = horus_l2_get_num_tx_data_bytes(num_payload_data_bytes); + memcpy(pout, uw, sizeof(uw)); pout += sizeof(uw); + memcpy(pout, input_payload_data, num_payload_data_bytes); pout += num_payload_data_bytes; + + /* Read input bits one at a time. Fill input Golay codeword. Find output Golay codeword. + Write this to parity bits. Write parity bytes when we have 8 parity bits. Bits are + written MSB first. */ + + num_payload_data_bits = num_payload_data_bytes*8; + ninbit = 0; + ingolay = 0; + ningolay = 0; + paritybyte = 0; + nparitybits = 0; + + while (ninbit < num_payload_data_bits) { + + /* extract input data bit */ + + ninbyte = ninbit/8; + shift = 7 - (ninbit % 8); + inbit = (input_payload_data[ninbyte] >> shift) & 0x1; + #ifdef DEBUG1 + fprintf(stderr, "inbit %d ninbyte: %d inbyte: 0x%02x inbit: %d\n", + ninbit, ninbyte, input_payload_data[ninbyte], inbit); + #endif + ninbit++; + + /* build up input golay codeword */ + + ingolay = ingolay | inbit; + ningolay++; + + /* when we get 12 bits do a Golay encode */ + + if (ningolay % 12) { + ingolay <<= 1; + } + else { + #ifdef DEBUG0 + fprintf(stderr, " ningolay: %d ingolay: 0x%04x\n", ningolay, ingolay); + #endif + golayparity = get_syndrome(ingolay<<11); + ingolay = 0; + + #ifdef DEBUG0 + fprintf(stderr, " golayparity: 0x%04x\n", golayparity); + #endif + + /* write parity bits to output data */ + + for (i=0; i<11; i++) { + golayparitybit = (golayparity >> (10-i)) & 0x1; + paritybyte = paritybyte | golayparitybit; + #ifdef DEBUG0 + fprintf(stderr, " i: %d golayparitybit: %d paritybyte: 0x%02x\n", + i, golayparitybit, paritybyte); + #endif + nparitybits++; + if (nparitybits % 8) { + paritybyte <<= 1; + } + else { + /* OK we have a full byte ready */ + *pout = paritybyte; + #ifdef DEBUG0 + fprintf(stderr," Write paritybyte: 0x%02x\n", paritybyte); + #endif + pout++; + paritybyte = 0; + } + } + } + } /* while(.... */ + + + /* Complete final Golay encode, we may have partially finished ingolay, paritybyte */ + + #ifdef DEBUG0 + fprintf(stderr, "finishing up .....\n"); + #endif + + if (ningolay % 12) { + ingolay >>= 1; + golayparity = get_syndrome(ingolay<<12); + #ifdef DEBUG0 + fprintf(stderr, " ningolay: %d ingolay: 0x%04x\n", ningolay, ingolay); + fprintf(stderr, " golayparity: 0x%04x\n", golayparity); + #endif + + /* write parity bits to output data */ + + for (i=0; i<11; i++) { + golayparitybit = (golayparity >> (10 - i)) & 0x1; + paritybyte = paritybyte | golayparitybit; + #ifdef DEBUG1 + fprintf(stderr, " i: %d golayparitybit: %d paritybyte: 0x%02x\n", + i, golayparitybit, paritybyte); + #endif + nparitybits++; + if (nparitybits % 8) { + paritybyte <<= 1; + } + else { + /* OK we have a full byte ready */ + *pout++ = (unsigned char)paritybyte; + #ifdef DEBUG0 + fprintf(stderr," Write paritybyte: 0x%02x\n", paritybyte); + #endif + paritybyte = 0; + } + } + } + + /* and final, partially complete, parity byte */ + + if (nparitybits % 8) { + paritybyte <<= 7 - (nparitybits % 8); // use MS bits first + *pout++ = (unsigned char)paritybyte; + #ifdef DEBUG0 + fprintf(stderr," Write last paritybyte: 0x%02x nparitybits: %d \n", paritybyte, nparitybits); + #endif + } + + #ifdef DEBUG0 + fprintf(stderr, "\npout - output_tx_data: %ld num_tx_data_bytes: %d\n", + pout - output_tx_data, num_tx_data_bytes); + #endif + assert(pout == (output_tx_data + num_tx_data_bytes)); + + /* optional interleaver - we dont interleave UW */ + + #ifdef INTERLEAVER + interleave(&output_tx_data[sizeof(uw)], num_tx_data_bytes-2, 0); + #endif + + /* optional scrambler to prevent long strings of the same symbol + which upsets the modem - we dont scramble UW */ + + #ifdef SCRAMBLER + scramble(&output_tx_data[sizeof(uw)], num_tx_data_bytes-2); + #endif + + return num_tx_data_bytes; +} + + +#ifdef HORUS_L2_RX +void horus_l2_decode_rx_packet(unsigned char *output_payload_data, + unsigned char *input_rx_data, + int num_payload_data_bytes) +{ + int num_payload_data_bits; + unsigned char *pout = output_payload_data; + unsigned char *pin = input_rx_data; + int ninbit, ingolay, ningolay, paritybyte, nparitybits; + int ninbyte, shift, inbit, golayparitybit, i, outbit, outbyte, noutbits, outdata; + int num_tx_data_bytes = horus_l2_get_num_tx_data_bytes(num_payload_data_bytes); + + /* optional scrambler and interleaver - we dont interleave UW */ + + #ifdef SCRAMBLER + scramble(&input_rx_data[sizeof(uw)], num_tx_data_bytes-2); + #endif + + #ifdef INTERLEAVER + interleave(&input_rx_data[sizeof(uw)], num_tx_data_bytes-2, 1); + #endif + + pin = input_rx_data + sizeof(uw) + num_payload_data_bytes; + + /* Read input data bits one at a time. When we have 12 read 11 parity bits. Golay decode. + Write decoded (output data) bits every time we have 8 of them. */ + + num_payload_data_bits = num_payload_data_bytes*8; + ninbit = 0; + ingolay = 0; + ningolay = 0; + nparitybits = 0; + paritybyte = *pin++; + #ifdef DEBUG0 + fprintf(stderr," Read paritybyte: 0x%02x\n", paritybyte); + #endif + pout = output_payload_data; + noutbits = 0; + outbyte = 0; + + while (ninbit < num_payload_data_bits) { + + /* extract input data bit */ + + ninbyte = ninbit/8 + sizeof(uw); + shift = 7 - (ninbit % 8); + inbit = (input_rx_data[ninbyte] >> shift) & 0x1; + #ifdef DEBUG1 + fprintf(stderr, "inbit %d ninbyte: %d inbyte: 0x%02x inbit: %d\n", + ninbit, ninbyte, input_rx_data[ninbyte], inbit); + #endif + ninbit++; + + /* build up golay codeword */ + + ingolay = ingolay | inbit; + ningolay++; + ingolay <<= 1; + + /* when we get 12 data bits start reading parity bits */ + + if ((ningolay % 12) == 0) { + #ifdef DEBUG0 + fprintf(stderr, " ningolay: %d ingolay: 0x%04x\n", ningolay, ingolay>>1); + #endif + for (i=0; i<11; i++) { + shift = 7 - (nparitybits % 8); + golayparitybit = (paritybyte >> shift) & 0x1; + ingolay |= golayparitybit; + if (i != 10) + ingolay <<=1; + nparitybits++; + if ((nparitybits % 8) == 0) { + /* OK grab a new byte */ + paritybyte = *pin++; + #ifdef DEBUG0 + fprintf(stderr," Read paritybyte: 0x%02x\n", paritybyte); + #endif + } + } + + #ifdef DEBUG0 + fprintf(stderr, " golay code word: 0x%04x\n", ingolay); + fprintf(stderr, " golay decode...: 0x%04x\n", golay23_decode(ingolay)); + #endif + + /* write decoded/error corrected bits to output payload data */ + + outdata = golay23_decode(ingolay) >> 11; + #ifdef DEBUG0 + fprintf(stderr, " outdata...: 0x%04x\n", outdata); + #endif + + for(i=0; i<12; i++) { + shift = 11 - i; + outbit = (outdata >> shift) & 0x1; + outbyte |= outbit; + noutbits++; + if (noutbits % 8) { + outbyte <<= 1; + } + else { + #ifdef DEBUG0 + fprintf(stderr, " output payload byte: 0x%02x\n", outbyte); + #endif + *pout++ = outbyte; + outbyte = 0; + } + } + + ingolay = 0; + } + } /* while(.... */ + + + #ifdef DEBUG0 + fprintf(stderr, "finishing up .....\n"); + #endif + + /* Complete final Golay decode */ + + int golayparity = 0; + if (ningolay % 12) { + for (i=0; i<11; i++) { + shift = 7 - (nparitybits % 8); + golayparitybit = (paritybyte >> shift) & 0x1; + golayparity |= golayparitybit; + if (i != 10) + golayparity <<=1; + nparitybits++; + if ((nparitybits % 8) == 0) { + /* OK grab a new byte */ + paritybyte = *pin++; + #ifdef DEBUG0 + fprintf(stderr," Read paritybyte: 0x%02x\n", paritybyte); + #endif + } + } + + ingolay >>= 1; + int codeword = (ingolay<<12) + golayparity; + #ifdef DEBUG0 + fprintf(stderr, " ningolay: %d ingolay: 0x%04x\n", ningolay, ingolay); + fprintf(stderr, " golay code word: 0x%04x\n", codeword); + fprintf(stderr, " golay decode...: 0x%04x\n", golay23_decode(codeword)); + #endif + + outdata = golay23_decode(codeword) >> 11; + #ifdef DEBUG0 + fprintf(stderr, " outdata...: 0x%04x\n", outdata); + fprintf(stderr, " num_payload_data_bits: %d noutbits: %d\n", num_payload_data_bits, noutbits); + #endif + + /* write final byte */ + + int ntogo = num_payload_data_bits - noutbits; + for(i=0; i> shift) & 0x1; + outbyte |= outbit; + noutbits++; + if (noutbits % 8) { + outbyte <<= 1; + } + else { + #ifdef DEBUG0 + fprintf(stderr, " output payload byte: 0x%02x\n", outbyte); + #endif + *pout++ = outbyte; + outbyte = 0; + } + } + } + + #ifdef DEBUG0 + fprintf(stderr, "\npin - output_payload_data: %ld num_payload_data_bytes: %d\n", + pout - output_payload_data, num_payload_data_bytes); + #endif + + assert(pout == (output_payload_data + num_payload_data_bytes)); + +} +#endif + +#ifdef INTERLEAVER + +uint16_t primes[] = { + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, + 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, + 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, + 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 379, 383, 389, 757, 761, 769, 773 +}; + +void interleave(unsigned char *inout, int nbytes, int dir) +{ + uint16_t nbits = (uint16_t)nbytes*8; + uint32_t i, j, n, ibit, ibyte, ishift, jbyte, jshift; + uint32_t b; + unsigned char out[nbytes]; + + + memset(out, 0, nbytes); + + /* b chosen to be co-prime with nbits, I'm cheating by just finding the + nearest prime to nbits. It also uses storage, is run on every call, + and has an upper limit. Oh Well, still seems to interleave OK. */ + i = 1; + uint16_t imax = sizeof(primes)/sizeof(uint16_t); + while ((primes[i] < nbits) && (i < imax)) + i++; + b = primes[i-1]; + + for(n=0; n> ishift) & 0x1; + + jbyte = j/8; + jshift = j%8; + + /* write jbit to ibit position */ + + out[jbyte] |= ibit << jshift; // replace with i-th bit + //out[ibyte] |= ibit << ishift; // replace with i-th bit + } + + memcpy(inout, out, nbytes); + + #ifdef DEBUG0 + printf("\nInterleaver Out:\n"); + for (i=0; i> 1) ^ (scrambler & 0x1); + + /* modify i-th bit by xor-ing with scrambler output sequence */ + + ibyte = i/8; + ishift = i%8; + ibit = (inout[ibyte] >> ishift) & 0x1; + ibits = ibit ^ scrambler_out; // xor ibit with scrambler output + + mask = 1 << ishift; + inout[ibyte] &= ~mask; // clear i-th bit + inout[ibyte] |= ibits << ishift; // set to scrambled value + + /* update scrambler */ + + scrambler >>= 1; + scrambler |= scrambler_out << 14; + + #ifdef DEBUG0 + printf("i: %02d ibyte: %d ishift: %d ibit: %d ibits: %d scrambler_out: %d\n", + i, ibyte, ishift, ibit, ibits, scrambler_out); + #endif + + } + + #ifdef DEBUG0 + printf("\nScrambler Out:\n"); + for (i=0; i>b) & 0x1; + } + + return nerr; +} + +/* unit test designed to run on a PC */ + +/* Horus binary packet */ + +struct TBinaryPacket +{ + uint8_t PayloadID; + uint16_t Counter; + uint8_t Hours; + uint8_t Minutes; + uint8_t Seconds; + float Latitude; + float Longitude; + uint16_t Altitude; + uint8_t Speed; // Speed in Knots (1-255 knots) + uint8_t Sats; + int8_t Temp; // Twos Complement Temp value. + uint8_t BattVoltage; // 0 = 0.5v, 255 = 2.0V, linear steps in-between. + uint16_t Checksum; // CRC16-CCITT Checksum. +} __attribute__ ((packed)); + +int main(void) { + int nbytes = sizeof(struct TBinaryPacket); + printf("test 0: BER: 0.00 ...........: %d\n", test_sending_bytes(nbytes, 0.00, 0)); + printf("test 1: BER: 0.01 ...........: %d\n", test_sending_bytes(nbytes, 0.01, 0)); + printf("test 2: BER: 0.05 ...........: %d\n", test_sending_bytes(nbytes, 0.05, 0)); + + /* we expect this always to fail, as chance of > 3 errors/codeword is high */ + + printf("test 3: BER: 0.10 ...........: %d\n", test_sending_bytes(nbytes, 0.10, 0)); + + /* -DINTERLEAVER will make this puppy pass */ + + printf("test 4: 8 bit burst error....: %d\n", test_sending_bytes(nbytes, 0.00, 1)); + + /* Insert 2 errors in every codeword, the maximum correction + capability of a Golay (23,12) code. note this one will fail + with -DINTERLEAVER, as we can't guarantee <= 3 errors per + codeword after interleaving */ + + printf("test 5: 1 error every 12 bits: %d\n", test_sending_bytes(nbytes, 0.00, 2)); + return 0; +} +#endif + + + +#ifdef GEN_TX_BITS +/* generate a file of tx_bits to modulate using fsk_horus.m for modem simulations */ + +int main(void) { + int nbytes = sizeof(struct TBinaryPacket); + struct TBinaryPacket input_payload; + int num_tx_data_bytes = horus_l2_get_num_tx_data_bytes(nbytes); + unsigned char tx[num_tx_data_bytes]; + int i; + + /* all zeros is nastiest sequence for demod before scrambling */ + + memset(&input_payload, 0, nbytes); + input_payload.Checksum = gen_crc16((unsigned char*)&input_payload, nbytes-2); + + horus_l2_encode_tx_packet(tx, (unsigned char*)&input_payload, nbytes); + + FILE *f = fopen("../octave/horus_tx_bits_binary.txt","wt"); + assert(f != NULL); + int b, tx_bit; + for(i=0; i> (7-b)) & 0x1; /* msb first */ + fprintf(f,"%d ", tx_bit); + } + } + fclose(f); + + return 0; +} +#endif + + +#ifdef DEC_RX_BITS + +/* Decode a binary file rx_bytes, e.g. from fsk_horus.m */ + +int main(void) { + int nbytes = 22; + unsigned char output_payload[nbytes]; + int num_tx_data_bytes = horus_l2_get_num_tx_data_bytes(nbytes); + + /* real world data horus payload generated when running tx above */ + unsigned char rx[45] = { + 0x24,0x24,0x01,0x0b,0x00,0x00,0x05,0x3b,0xf2,0xa7,0x0b,0xc2,0x1b, + 0xaa,0x0a,0x43,0x7e,0x00,0x05,0x00,0x25,0xc0,0xce,0xbb,0x36,0x69, + 0x50,0x00,0x41,0xb0,0xa6,0x5e,0x91,0xa2,0xa3,0xf8,0x1d,0x00,0x00, + 0x0c,0x76,0xc6,0x05,0xb0,0xb8}; + int i, ret; + + assert(num_tx_data_bytes == 45); + + #define READ_FILE /* overwrite tx[] above, that's OK */ + #ifdef READ_FILE + FILE *f = fopen("../octave/horus_rx_bits_binary.bin","rb"); + assert(f != NULL); + ret = fread(rx, sizeof(char), num_tx_data_bytes, f); + assert(ret == num_tx_data_bytes); + fclose(f); + #endif + + golay23_init(); + horus_l2_decode_rx_packet(output_payload, rx, nbytes); + + #ifdef HEX_DUMP + fprintf(stderr, "\nOutput Payload:\n"); + for(i=0; i +#include +#include +#define X22 0x00400000 /* vector representation of X^{22} */ +#define X11 0x00000800 /* vector representation of X^{11} */ +#define MASK12 0xfffff800 /* auxiliary vector for testing */ +#define GENPOL 0x00000c75 /* generator polinomial, g(x) */ + +/* Global variables: + * + * pattern = error pattern, or information, or received vector + * encoding_table[] = encoding table + * decoding_table[] = decoding table + * data = information bits, i(x) + * codeword = code bits = x^{11}i(x) + (x^{11}i(x) mod g(x)) + * numerr = number of errors = Hamming weight of error polynomial e(x) + * position[] = error positions in the vector representation of e(x) + * recd = representation of corrupted received polynomial r(x) = c(x) + e(x) + * decerror = number of decoding errors + * a[] = auxiliary array to generate correctable error patterns + */ + +#ifdef HORUS_L2_RX +static int inited = 0; + +#ifdef RUN_TIME_TABLES +static int encoding_table[4096], decoding_table[2048]; +#else +#include "golayenctable.h" +#include "golaydectable.h" +#endif + +#ifdef RUN_TIME_TABLES +static int arr2int(int a[], int r) +/* + * Convert a binary vector of Hamming weight r, and nonzero positions in + * array a[1]...a[r], to a long integer \sum_{i=1}^r 2^{a[i]-1}. + */ +{ + int i; + long mul, result = 0, temp; + + for (i=1; i<=r; i++) { + mul = 1; + temp = a[i]-1; + while (temp--) + mul = mul << 1; + result += mul; + } + return(result); +} +#endif +#endif + +#ifdef HORUS_L2_RX +void nextcomb(int n, int r, int a[]) +/* + * Calculate next r-combination of an n-set. + */ +{ + int i, j; + + a[r]++; + if (a[r] <= n) + return; + j = r - 1; + while (a[j] == n - r + j) + j--; + for (i = r; i >= j; i--) + a[i] = a[j] + i - j + 1; + return; +} +#endif + +int32_t get_syndrome(int32_t pattern) +/* + * Compute the syndrome corresponding to the given pattern, i.e., the + * remainder after dividing the pattern (when considering it as the vector + * representation of a polynomial) by the generator polynomial, GENPOL. + * In the program this pattern has several meanings: (1) pattern = infomation + * bits, when constructing the encoding table; (2) pattern = error pattern, + * when constructing the decoding table; and (3) pattern = received vector, to + * obtain its syndrome in decoding. + */ +{ + int32_t aux = X22; + + if (pattern >= X11) + while (pattern & MASK12) { + while (!(aux & pattern)) + aux = aux >> 1; + pattern ^= (aux/X11) * GENPOL; + } + return(pattern); +} + +#ifdef HORUS_L2_RX + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: golay23_init() + AUTHOR......: David Rowe + DATE CREATED: 3 March 2013 + + Call this once when you start your program to init the Golay tables. + +\*---------------------------------------------------------------------------*/ + +void golay23_init(void) { +#ifdef RUN_TIME_TABLES + int i; + long temp; + int a[4]; + int pattern; + + /* + * --------------------------------------------------------------------- + * Generate ENCODING TABLE + * + * An entry to the table is an information vector, a 32-bit integer, + * whose 12 least significant positions are the information bits. The + * resulting value is a codeword in the (23,12,7) Golay code: A 32-bit + * integer whose 23 least significant bits are coded bits: Of these, the + * 12 most significant bits are information bits and the 11 least + * significant bits are redundant bits (systematic encoding). + * --------------------------------------------------------------------- + */ + for (pattern = 0; pattern < 4096; pattern++) { + temp = pattern << 11; /* multiply information by X^{11} */ + encoding_table[pattern] = temp + get_syndrome(temp);/* add redundancy */ + } + + /* + * --------------------------------------------------------------------- + * Generate DECODING TABLE + * + * An entry to the decoding table is a syndrome and the resulting value + * is the most likely error pattern. First an error pattern is generated. + * Then its syndrome is calculated and used as a pointer to the table + * where the error pattern value is stored. + * --------------------------------------------------------------------- + * + * (1) Error patterns of WEIGHT 1 (SINGLE ERRORS) + */ + decoding_table[0] = 0; + decoding_table[1] = 1; + temp = 1; + for (i=2; i<= 23; i++) { + temp *= 2; + decoding_table[get_syndrome(temp)] = temp; + } + /* + * (2) Error patterns of WEIGHT 2 (DOUBLE ERRORS) + */ + a[1] = 1; a[2] = 2; + temp = arr2int(a,2); + decoding_table[get_syndrome(temp)] = temp; + for (i=1; i<253; i++) { + nextcomb(23,2,a); + temp = arr2int(a,2); + decoding_table[get_syndrome(temp)] = temp; + } + /* + * (3) Error patterns of WEIGHT 3 (TRIPLE ERRORS) + */ + a[1] = 1; a[2] = 2; a[3] = 3; + temp = arr2int(a,3); + decoding_table[get_syndrome(temp)] = temp; + for (i=1; i<1771; i++) { + nextcomb(23,3,a); + temp = arr2int(a,3); + decoding_table[get_syndrome(temp)] = temp; + } +#endif + inited = 1; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: golay23_encode() + AUTHOR......: David Rowe + DATE CREATED: 3 March 2013 + + Given 12 bits of data retiurns a 23 bit codeword for transmission + over the channel. + +\*---------------------------------------------------------------------------*/ + +int golay23_encode(int data) { + assert(inited); + assert(data <= 0xfff); + + //printf("data: 0x%x\n", data); + return encoding_table[data]; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: golay23_decode() + AUTHOR......: David Rowe + DATE CREATED: 3 March 2013 + + Given a 23 bit received codeword, returns the 12 bit corrected data. + +\*---------------------------------------------------------------------------*/ + +int golay23_decode(int received_codeword) { + assert(inited); + assert((received_codeword < (1<<23)) && (received_codeword >= 0)); + + //printf("syndrome: 0x%x\n", get_syndrome(received_codeword)); + return received_codeword ^= decoding_table[get_syndrome(received_codeword)]; +} + +int golay23_count_errors(int recd_codeword, int corrected_codeword) +{ + int errors = 0; + int diff, i; + + diff = recd_codeword ^ corrected_codeword; + for(i=0; i<23; i++) { + if (diff & 0x1) + errors++; + diff >>= 1; + } + + return errors; +} + +#endif + +// from http://stackoverflow.com/questions/10564491/function-to-calculate-a-crc16-checksum + +unsigned short gen_crc16(unsigned char* data_p, unsigned char length){ + unsigned char x; + unsigned short crc = 0xFFFF; + + while (length--){ + x = crc >> 8 ^ *data_p++; + x ^= x>>4; + crc = (crc << 8) ^ ((unsigned short)(x << 12)) ^ ((unsigned short)(x <<5)) ^ ((unsigned short)x); + } + return crc; +} + diff --git a/horus_l2.h b/horus_l2.h new file mode 100644 index 0000000..7ded29a --- /dev/null +++ b/horus_l2.h @@ -0,0 +1,23 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: horus_l2.h + AUTHOR......: David Rowe + DATE CREATED: Dec 2015 + +\*---------------------------------------------------------------------------*/ + +#ifndef __HORUS_L2__ +#define __HORUS_L2__ + +int horus_l2_get_num_tx_data_bytes(int num_payload_data_bytes); + +/* returns number of output bytes in output_tx_data */ +int horus_l2_encode_tx_packet(unsigned char *output_tx_data, + unsigned char *input_payload_data, + int num_payload_data_bytes); + +void horus_l2_decode_rx_packet(unsigned char *output_payload_data, + unsigned char *input_rx_data, + int num_payload_data_bytes); + +#endif diff --git a/horusbinary_radiolib.ino b/horusbinary_radiolib.ino new file mode 100644 index 0000000..327f47a --- /dev/null +++ b/horusbinary_radiolib.ino @@ -0,0 +1,271 @@ +/* + RadioLib Horus Binary FSK4 Packet Generation & Transmitter Example + + This example sends an example FSK-4 'Horus Binary' message using SX1278's + FSK modem. This example uses 'stock' RadioLib - and makes calls into the radio's + lower-level functions. + + This signal can be demodulated using a SSB demodulator (SDR or otherwise), and + horusdemodlib: https://github.com/projecthorus/horusdemodlib/wiki + + Other modules that can be used for FSK4: + (Untested, but work with RTTY to are likely to work here too) + - SX127x/RFM9x + - RF69 + - SX1231 + - CC1101 + - SX126x + - nRF24 + - Si443x/RFM2x + - SX128x + +*/ + +#include +#include "horus_l2.h" + + +// RADIO SETUP + +#define TX_FREQ 434.200 +#define FSK4_BAUD 100 +#define FSK4_SPACING 270 // NOTE: This results in a shift of 244 Hz due to the PLL Resolution of the SX127x + +// SX1278 has the following connections: +// NSS pin: 10 +// DIO0 pin: 2 +// RESET pin: 9 +// DIO1 pin: 3 +SX1278 radio = new Module(10, 2, 9, 3); + + +// Horus Binary Structures & Variables + +// Horus Binary Packet Structure - Version 1 +struct HorusBinaryPacketV1 +{ + uint8_t PayloadID; + uint16_t Counter; + uint8_t Hours; + uint8_t Minutes; + uint8_t Seconds; + float Latitude; + float Longitude; + uint16_t Altitude; + uint8_t Speed; // Speed in Knots (1-255 knots) + uint8_t Sats; + int8_t Temp; // Twos Complement Temp value. + uint8_t BattVoltage; // 0 = 0.5v, 255 = 5.0V, linear steps in-between. + uint16_t Checksum; // CRC16-CCITT Checksum. +} __attribute__ ((packed)); + +// Horus v2 Mode 1 (32-byte) Binary Packet +struct HorusBinaryPacketV2 +{ + uint16_t PayloadID; + uint16_t Counter; + uint8_t Hours; + uint8_t Minutes; + uint8_t Seconds; + float Latitude; + float Longitude; + uint16_t Altitude; + uint8_t Speed; // Speed in Knots (1-255 knots) + uint8_t Sats; + int8_t Temp; // Twos Complement Temp value. + uint8_t BattVoltage; // 0 = 0.5v, 255 = 2.0V, linear steps in-between. + // The following 9 bytes (up to the CRC) are user-customizable. The following just + // provides an example of how they could be used. + uint8_t dummy1; // unsigned int + float dummy2; // Float + uint8_t dummy3; // battery voltage test + uint8_t dummy4; // divide by 10 + uint16_t dummy5; // divide by 100 + uint16_t Checksum; // CRC16-CCITT Checksum. +} __attribute__ ((packed)); + +// Buffers and counters. +char rawbuffer [128]; // Buffer to temporarily store a raw binary packet. +char codedbuffer [128]; // Buffer to store an encoded binary packet +char debugbuffer[256]; // Buffer to store debug strings +uint16_t packet_count = 1; // Packet counter + + +int build_horus_binary_packet_v1(char *buffer){ + // Generate a Horus Binary v1 packet, and populate it with data. + // The assignments in this function should be replaced with real data + + struct HorusBinaryPacketV1 BinaryPacket; + + BinaryPacket.PayloadID = 0; // 0 = 4FSKTEST + BinaryPacket.Counter = packet_count; + BinaryPacket.Hours = 12; + BinaryPacket.Minutes = 34; + BinaryPacket.Seconds = 56; + BinaryPacket.Latitude = 0.0; + BinaryPacket.Longitude = 0.0; + BinaryPacket.Altitude = 0; + BinaryPacket.Speed = 0; + BinaryPacket.BattVoltage = 0; + BinaryPacket.Sats = 0; + BinaryPacket.Temp = 0; + + BinaryPacket.Checksum = (uint16_t)crc16((unsigned char*)&BinaryPacket,sizeof(BinaryPacket)-2); + + memcpy(buffer, &BinaryPacket, sizeof(BinaryPacket)); + + return sizeof(struct HorusBinaryPacketV1); +} + + +int build_horus_binary_packet_v2(char *buffer){ + // Generate a Horus Binary v2 packet, and populate it with data. + // The assignments in this function should be replaced with real data + + struct HorusBinaryPacketV2 BinaryPacketV2; + + BinaryPacketV2.PayloadID = 256; // 0 = 4FSKTEST-V2 + BinaryPacketV2.Counter = packet_count; + BinaryPacketV2.Hours = 12; + BinaryPacketV2.Minutes = 34; + BinaryPacketV2.Seconds = 56; + BinaryPacketV2.Latitude = 0.0; + BinaryPacketV2.Longitude = 0.0; + BinaryPacketV2.Altitude = 0; + BinaryPacketV2.Speed = 0; + BinaryPacketV2.BattVoltage = 0; + BinaryPacketV2.Sats = 0; + BinaryPacketV2.Temp = 0; + // Custom section. This is an example only, and the 9 bytes in this section can be used in other + // ways. Refer here for details: https://github.com/projecthorus/horusdemodlib/wiki/5-Customising-a-Horus-Binary-v2-Packet + BinaryPacketV2.dummy1 = 1; // uint8 + BinaryPacketV2.dummy2 = 1.23456; // float32 + BinaryPacketV2.dummy3 = 100; // uint8 - interpreted as a battery voltage 0-5V + BinaryPacketV2.dummy4 = 123; // uint8 - interpreted as a fixed-point value (div/10) + BinaryPacketV2.dummy5 = 1234; // uint16 - interpreted as a fixed-point value (div/100) + + BinaryPacketV2.Checksum = (uint16_t)crc16((unsigned char*)&BinaryPacketV2,sizeof(BinaryPacketV2)-2); + + memcpy(buffer, &BinaryPacketV2, sizeof(BinaryPacketV2)); + + return sizeof(struct HorusBinaryPacketV2); +} + + +void setup() { + Serial.begin(9600); + + // initialize SX1278 with default settings + Serial.print(F("[SX1278] Initializing ... ")); + int state = radio.beginFSK(); + + // when using one of the non-LoRa modules for FSK4 + // (RF69, CC1101, Si4432 etc.), use the basic begin() method + // int state = radio.begin(); + + if(state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + + + Serial.print(F("[FSK4] Initializing ... ")); + + // initialize FSK4 transmitter + // NOTE: FSK4 frequency shift will be rounded + // to the nearest multiple of frequency step size. + // The exact value depends on the module: + // SX127x/RFM9x - 61 Hz + // RF69 - 61 Hz + // CC1101 - 397 Hz + // SX126x - 1 Hz + // nRF24 - 1000000 Hz + // Si443x/RFM2x - 156 Hz + // SX128x - 198 Hz + + state = fsk4_setup(&radio, TX_FREQ, FSK4_SPACING, FSK4_BAUD); + + if(state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + +} + +void loop() { + + // Horus Binary V1 + Serial.println(F("Generating Horus Binary v1 Packet")); + + // Generate packet + int pkt_len = build_horus_binary_packet_v1(rawbuffer); + // Debugging + Serial.print(F("Uncoded Length (bytes): ")); + Serial.println(pkt_len); + Serial.print("Uncoded: "); + PrintHex(rawbuffer, pkt_len, debugbuffer); + Serial.println(debugbuffer); + + // Apply Encoding + int coded_len = horus_l2_encode_tx_packet((unsigned char*)codedbuffer,(unsigned char*)rawbuffer,pkt_len); + // Debugging + Serial.print(F("Encoded Length (bytes): ")); + Serial.println(coded_len); + Serial.print("Coded: "); + PrintHex(codedbuffer, coded_len, debugbuffer); + Serial.println(debugbuffer); + + // Transmit! + Serial.println(F("Transmitting Horus Binary v1 Packet")); + + // send out idle condition for 1000 ms + fsk4_idle(&radio); + delay(1000); + fsk4_preamble(&radio, 8); + fsk4_write(&radio, codedbuffer, coded_len); + + + // Horus Binary V2 + Serial.println(F("Generating Horus Binary v2 Packet")); + // Generate packet + pkt_len = build_horus_binary_packet_v2(rawbuffer); + // Debugging + Serial.print(F("Uncoded Length (bytes): ")); + Serial.println(pkt_len); + Serial.print("Uncoded: "); + PrintHex(rawbuffer, pkt_len, debugbuffer); + Serial.println(debugbuffer); + + // Apply Encoding + coded_len = horus_l2_encode_tx_packet((unsigned char*)codedbuffer,(unsigned char*)rawbuffer,pkt_len); + // Debugging + Serial.print(F("Encoded Length (bytes): ")); + Serial.println(coded_len); + Serial.print("Coded: "); + PrintHex(codedbuffer, coded_len, debugbuffer); + Serial.println(debugbuffer); + + // Transmit! + Serial.println(F("Transmitting Horus Binary v2 Packet")); + + // send out idle condition for 1000 ms + fsk4_idle(&radio); + delay(1000); + fsk4_preamble(&radio, 8); + fsk4_write(&radio, codedbuffer, coded_len); + + + Serial.println(F("done!")); + + delay(1000); + + packet_count++; + +} \ No newline at end of file diff --git a/util.ino b/util.ino new file mode 100644 index 0000000..8c65a97 --- /dev/null +++ b/util.ino @@ -0,0 +1,34 @@ +// Various utility functions + +#include + +// Fast CRC16 code, using Atmel's optimized libraries! +unsigned int crc16(unsigned char *string, unsigned int len) { + unsigned int i; + unsigned int crc; + crc = 0xFFFF; // Standard CCITT seed for CRC16. + // Calculate the sum, ignore $ sign's + for (i = 0; i < len; i++) { + crc = _crc_xmodem_update(crc,(uint8_t)string[i]); + } + return crc; +} + +void PrintHex(char *data, uint8_t length, char *tmp){ + // Print char data as hex + byte first ; + int j=0; + for (uint8_t i=0; i> 4) | 48; + if (first > 57) tmp[j] = first + (byte)39; + else tmp[j] = first ; + j++; + + first = ((uint8_t)data[i] & 0x0F) | 48; + if (first > 57) tmp[j] = first + (byte)39; + else tmp[j] = first; + j++; + } + tmp[length*2] = 0; +} \ No newline at end of file