Add support for Horus 4FSK v2 mode with custom data fields for BMP280 sensor. Add support for CW and 4FSK v1+v2 (50 baud) on HF/VHF using the Si5351 clock generator.

main
Mikael Nousiainen 2021-09-14 22:47:39 +03:00
rodzic fa46e8a30c
commit 9486cd3370
23 zmienionych plików z 1291 dodań i 239 usunięć

Wyświetl plik

@ -40,41 +40,67 @@ The RS41ng firmware is just one example of what can be achieved with the RS41 ha
## Why does the RS41ng firmware exist?
The motivation to develop this firmware is to provide cleaner, customizable and
more modular codebase for developing RS41 radiosonde-based experiments.
The motivation to develop this firmware is to provide a clean, customizable and
modular codebase for developing RS41 radiosonde-based experiments.
The main features this firmware aims to implement are:
* Support for transmitting multiple modes consecutively with custom, rotating comment messages
* Support for standard 1200-baud APRS
* Support for [Horus 4FSK mode](https://github.com/projecthorus/horusdemodlib/wiki) that has improved performance compared to APRS or RTTY
* Support for morse code (CW)
* Support for additional digital modes on HF/VHF amateur radio bands using an external Si5351 clock generator connected to the external I²C bus
* Support for custom sensors via the external I²C bus
* Enhanced support for the internal Si4032 radio transmitter via PWM-based tone generation (and ultimately DMA-based symbol timing, if possible)
* Extensibility to allow easy addition of new digital modes
See the feature list below.
## Features
* APRS on 70 cm amateur radio band using the internal Si4032 radio transmitter
* Bell 202 frequencies are generated via hardware PWM, but the symbol timing is created in a loop with delay
* There is also code available to use DMA transfers for symbol timing to achieve greater accuracy, but I have not been able to get the timings working correctly
* Horus 4FSK on 70 cm amateur radio band using the internal Si4032 radio transmitter
* The Horus 4FSK mode has significantly [improved performance compared to APRS or RTTY](https://github.com/projecthorus/horusdemodlib/wiki)
* Use [horus-gui](https://github.com/projecthorus/horus-gui) software to receive the 4FSK mode and to submit packets to [Habhub](http://habhub.org/) high-altitude balloon tracking platform
* See [horus-gui installation and usage instructions](https://github.com/projecthorus/horusdemodlib/wiki/1.1-Horus-GUI-Reception-Guide-(Windows-Linux-OSX))
* Based on [horusdemodlib](https://github.com/projecthorus/horusdemodlib) library that is responsible for demodulating the signal
* Morse code (CW) on on 70 cm amateur radio band using the internal Si4032 radio transmitter
* Digital mode beacons on HF/VHF frequencies using a Si5351 clock generator connected to the external I²C bus of the RS41 radiosonde
* The JTEncode library provides JT65/JT9/JT4/FT8/WSPR/FSQ beacon transmissions. I've decoded FT8 and WSPR successfully.
The main features the RS41ng firmware are:
* Support for multiple transmission modes:
* Standard 1200-baud APRS
* [Horus 4FSK v1 and v2 modes](https://github.com/projecthorus/horusdemodlib/wiki) that has improved performance compared to APRS or RTTY
* Morse code (CW)
* JT65/JT9/JT4/FT8/WSPR/FSQ digital modes on HF/VHF amateur radio bands using an external Si5351 clock generator connected to the external I²C bus
* Support for transmitting multiple modes consecutively with custom, rotating comment messages (see `config.c`)
* Support for GPS-based scheduling is available for transmission modes that require specific timing for transmissions
* Support for custom sensors via the external I²C bus
* Enhanced support for the internal Si4032 radio transmitter via PWM-based tone generation (and ultimately DMA-based symbol timing, if possible)
* Extensibility to allow easy addition of new transmission modes and new sensors
### Transmission modes
On the internal Si4032 transmitter:
* APRS (1200 baud)
* Horus 4FSK v1 and v2 (100 baud)
* Morse code (CW)
On an external Si5351 clock generator connected to the external I²C bus of the RS41 radiosonde:
* Horus 4FSK v1 and v2 (50 baud, because the Si5351 frequency changes are slow)
* JT65/JT9/JT4/FT8/WSPR/FSQ mode beacon transmissions using the JTEncode library. I've decoded FT8, WSPR and FSQ modes successfully.
* GPS-based scheduling is available for modes that require specific timing for transmissions
* External I²C bus sensor drivers
* Bosch BMP280 barometric pressure / temperature / humidity sensor
* Morse code (CW)
#### Notes about APRS
* Bell 202 frequencies are generated via hardware PWM, but the symbol timing is created in a loop with delay
* There is also code available to use DMA transfers for symbol timing to achieve greater accuracy, but I have not been able to get the timings working correctly
#### Notes about Horus 4FSK
* The Horus 4FSK v1 and v2 modes have significantly [improved performance compared to APRS or RTTY](https://github.com/projecthorus/horusdemodlib/wiki).
* Use [horus-gui](https://github.com/projecthorus/horus-gui) software to receive the 4FSK mode and to submit packets to [Habhub](http://habhub.org/) high-altitude balloon tracking platform.
* See [horus-gui installation and usage instructions](https://github.com/projecthorus/horusdemodlib/wiki/1.1-Horus-GUI-Reception-Guide-(Windows-Linux-OSX)) and [horusdemodlib](https://github.com/projecthorus/horusdemodlib) library that is responsible for demodulating the signal.
### External sensors
It is possible to connect external sensors to the I²C bus of the RS41 radiosonde.
The following sensors are currently supported:
* Bosch BMP280 barometric pressure / temperature / humidity sensor
Sensor driver code contributions are welcome!
### Planned features
* Support for more I²C sensors
* Continuous transmission mode for Horus 4FSK
* Configurable transmission frequencies and schedules based on location / altitude
* Morse code (CW) on Si5351 (HF + 2m)
* Support for more I²C sensors
* RTTY on both Si4032 (70 cm, non-standard shift) and Si5351 (HF + 2m) with configurable shift
* Investigate possibility to implement 1200 bps Bell 202 modulation (and
possibly also 300 bps Bell 103 modulation) for APRS using Si5351,

Wyświetl plik

@ -0,0 +1,19 @@
#include "horus_common.h"
uint16_t calculate_crc16_checksum(char *string, int len)
{
uint16_t crc = 0xffff;
char i;
int ptr = 0;
while (ptr < len) {
ptr++;
crc = crc ^ (*(string++) << 8);
for (i = 0; i < 8; i++) {
if (crc & 0x8000)
crc = (uint16_t)((crc << 1) ^ 0x1021);
else
crc <<= 1;
}
}
return crc;
}

Wyświetl plik

@ -0,0 +1,8 @@
#ifndef __HORUS_COMMON_H
#define __HORUS_COMMON_H
#include <stdint.h>
uint16_t calculate_crc16_checksum(char *string, int len);
#endif

Wyświetl plik

@ -66,15 +66,20 @@
#define INTERLEAVER
#define SCRAMBLER
static char uw[] = {'$','$'};
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);
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 ----------------------------------------------------------*/
@ -89,20 +94,21 @@ void scramble(unsigned char *inout, int nbytes);
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 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;
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;
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);
@ -127,24 +133,26 @@ int horus_l2_get_num_tx_data_bytes(int num_payload_data_bytes) {
*/
int horus_l2_encode_tx_packet(unsigned char *output_tx_data,
unsigned char *input_payload_data,
int num_payload_data_bytes)
unsigned char *input_payload_data,
int num_payload_data_bytes)
{
int num_tx_data_bytes, num_payload_data_bits;
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;
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;
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;
num_payload_data_bits = num_payload_data_bytes * 8;
ninbit = 0;
ingolay = 0;
ningolay = 0;
@ -155,7 +163,7 @@ int horus_l2_encode_tx_packet(unsigned char *output_tx_data,
/* extract input data bit */
ninbyte = ninbit/8;
ninbyte = ninbit / 8;
shift = 7 - (ninbit % 8);
inbit = (input_payload_data[ninbyte] >> shift) & 0x1;
#ifdef DEBUG1
@ -173,12 +181,11 @@ int horus_l2_encode_tx_packet(unsigned char *output_tx_data,
if (ningolay % 12) {
ingolay <<= 1;
}
else {
} else {
#ifdef DEBUG0
fprintf(stderr, " ningolay: %d ingolay: 0x%04x\n", ningolay, ingolay);
#endif
golayparity = get_syndrome(ingolay<<11);
golayparity = get_syndrome(ingolay << 11);
ingolay = 0;
#ifdef DEBUG0
@ -187,18 +194,17 @@ int horus_l2_encode_tx_packet(unsigned char *output_tx_data,
/* write parity bits to output data */
for (i=0; i<11; i++) {
golayparitybit = (golayparity >> (10-i)) & 0x1;
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",
fprintf(stderr, " i: %d golayparitybit: %d paritybyte: 0x%02x\n",
i, golayparitybit, paritybyte);
#endif
nparitybits++;
if (nparitybits % 8) {
paritybyte <<= 1;
}
else {
paritybyte <<= 1;
} else {
/* OK we have a full byte ready */
*pout = paritybyte;
#ifdef DEBUG0
@ -220,7 +226,7 @@ int horus_l2_encode_tx_packet(unsigned char *output_tx_data,
if (ningolay % 12) {
ingolay >>= 1;
golayparity = get_syndrome(ingolay<<12);
golayparity = get_syndrome(ingolay << 12);
#ifdef DEBUG0
fprintf(stderr, " ningolay: %d ingolay: 0x%04x\n", ningolay, ingolay);
fprintf(stderr, " golayparity: 0x%04x\n", golayparity);
@ -228,20 +234,19 @@ int horus_l2_encode_tx_packet(unsigned char *output_tx_data,
/* write parity bits to output data */
for (i=0; i<11; i++) {
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",
fprintf(stderr, " i: %d golayparitybit: %d paritybyte: 0x%02x\n",
i, golayparitybit, paritybyte);
#endif
nparitybits++;
if (nparitybits % 8) {
paritybyte <<= 1;
}
else {
} else {
/* OK we have a full byte ready */
*pout++ = (unsigned char)paritybyte;
*pout++ = (unsigned char) paritybyte;
#ifdef DEBUG0
fprintf(stderr," Write paritybyte: 0x%02x\n", paritybyte);
#endif
@ -249,34 +254,34 @@ int horus_l2_encode_tx_packet(unsigned char *output_tx_data,
}
}
}
/* and final, partially complete, parity byte */
if (nparitybits % 8) {
paritybyte <<= 7 - (nparitybits % 8); // use MS bits first
*pout++ = (unsigned char)paritybyte;
*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",
#ifdef DEBUG0
fprintf(stderr, "\npout - output_tx_data: %ld num_tx_data_bytes: %d\n",
pout - output_tx_data, num_tx_data_bytes);
#endif
#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);
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);
scramble(&output_tx_data[sizeof(uw)], num_tx_data_bytes - 2);
#endif
return num_tx_data_bytes;
@ -297,13 +302,13 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
/* optional scrambler and interleaver - we dont interleave UW */
#ifdef SCRAMBLER
#ifdef SCRAMBLER
scramble(&input_rx_data[sizeof(uw)], num_tx_data_bytes-2);
#endif
#endif
#ifdef INTERLEAVER
#ifdef INTERLEAVER
interleave(&input_rx_data[sizeof(uw)], num_tx_data_bytes-2, 1);
#endif
#endif
pin = input_rx_data + sizeof(uw) + num_payload_data_bytes;
@ -316,9 +321,9 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
ningolay = 0;
nparitybits = 0;
paritybyte = *pin++;
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr," Read paritybyte: 0x%02x\n", paritybyte);
#endif
#endif
pout = output_payload_data;
noutbits = 0;
outbyte = 0;
@ -330,10 +335,10 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
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",
#ifdef DEBUG1
fprintf(stderr, "inbit %d ninbyte: %d inbyte: 0x%02x inbit: %d\n",
ninbit, ninbyte, input_rx_data[ninbyte], inbit);
#endif
#endif
ninbit++;
/* build up golay codeword */
@ -345,9 +350,9 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
/* when we get 12 data bits start reading parity bits */
if ((ningolay % 12) == 0) {
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, " ningolay: %d ingolay: 0x%04x\n", ningolay, ingolay>>1);
#endif
#endif
for (i=0; i<11; i++) {
shift = 7 - (nparitybits % 8);
golayparitybit = (paritybyte >> shift) & 0x1;
@ -358,25 +363,25 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
if ((nparitybits % 8) == 0) {
/* OK grab a new byte */
paritybyte = *pin++;
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr," Read paritybyte: 0x%02x\n", paritybyte);
#endif
#endif
}
}
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, " golay code word: 0x%04x\n", ingolay);
fprintf(stderr, " golay decode...: 0x%04x\n", golay23_decode(ingolay));
#endif
#endif
/* write decoded/error corrected bits to output payload data */
outdata = golay23_decode(ingolay) >> 11;
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, " outdata...: 0x%04x\n", outdata);
#endif
#endif
for(i=0; i<12; i++) {
for(i=0; i<12; i++) {
shift = 11 - i;
outbit = (outdata >> shift) & 0x1;
outbyte |= outbit;
@ -385,9 +390,9 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
outbyte <<= 1;
}
else {
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, " output payload byte: 0x%02x\n", outbyte);
#endif
#endif
*pout++ = outbyte;
outbyte = 0;
}
@ -398,9 +403,9 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
} /* while(.... */
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, "finishing up .....\n");
#endif
#endif
/* Complete final Golay decode */
@ -416,30 +421,30 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
if ((nparitybits % 8) == 0) {
/* OK grab a new byte */
paritybyte = *pin++;
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr," Read paritybyte: 0x%02x\n", paritybyte);
#endif
#endif
}
}
ingolay >>= 1;
int codeword = (ingolay<<12) + golayparity;
#ifdef DEBUG0
#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
#endif
outdata = golay23_decode(codeword) >> 11;
#ifdef DEBUG0
#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
#endif
/* write final byte */
int ntogo = num_payload_data_bits - noutbits;
for(i=0; i<ntogo; i++) {
for(i=0; i<ntogo; i++) {
shift = ntogo - i;
outbit = (outdata >> shift) & 0x1;
outbyte |= outbit;
@ -448,19 +453,19 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
outbyte <<= 1;
}
else {
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, " output payload byte: 0x%02x\n", outbyte);
#endif
#endif
*pout++ = outbyte;
outbyte = 0;
}
}
}
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, "\npin - output_payload_data: %ld num_payload_data_bytes: %d\n",
pout - output_payload_data, num_payload_data_bytes);
#endif
#endif
assert(pout == (output_payload_data + num_payload_data_bytes));
@ -470,68 +475,69 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
#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
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;
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
/* 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);
uint16_t imax = sizeof(primes) / sizeof(uint16_t);
while ((primes[i] < nbits) && (i < imax))
i++;
b = primes[i-1];
b = primes[i - 1];
for(n=0; n<nbits; n++) {
for (n = 0; n < nbits; n++) {
/*
"On the Analysis and Design of Good Algebraic Interleavers", Xie et al,eq (5)
*/
i = n;
j = (b*i) % nbits;
j = (b * i) % nbits;
if (dir) {
uint16_t tmp = j;
j = i;
i = tmp;
}
#ifdef DEBUG0
printf("i: %d j: %d\n",i, j);
#endif
/* read bit i and write to bit j postion */
ibyte = i/8;
ishift = i%8;
ibyte = i / 8;
ishift = i % 8;
ibit = (inout[ibyte] >> ishift) & 0x1;
jbyte = j/8;
jshift = j%8;
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
@ -540,6 +546,7 @@ void interleave(unsigned char *inout, int nbytes, int dir)
printf("%02d 0x%02x\n", i, inout[i]);
#endif
}
#endif
@ -554,8 +561,8 @@ int main(void) {
/* copy of input for later comp */
for(i=0; i<nbytes; i++)
inout[i] = incopy[i] = rand() & 0xff;
inout[i] = incopy[i] = rand() & 0xff;
interleave(inout, nbytes, 0); /* interleave */
memcpy(inter, inout, nbytes); /* snap shot of interleaved bytes */
interleave(inout, nbytes, 1); /* de-interleave */
@ -563,7 +570,7 @@ int main(void) {
/* all ones in last col means it worked! */
for(i=0; i<nbytes; i++) {
printf("%d 0x%02x 0x%02x 0x%02x %d\n",
printf("%d 0x%02x 0x%02x 0x%02x %d\n",
i, incopy[i], inter[i], inout[i], incopy[i] == inout[i]);
assert(incopy[i] == inout[i]);
}
@ -580,21 +587,21 @@ int main(void) {
void scramble(unsigned char *inout, int nbytes)
{
int nbits = nbytes*8;
int nbits = nbytes * 8;
int i, ibit, ibits, ibyte, ishift, mask;
uint16_t scrambler = 0x4a80; /* init additive scrambler at start of every frame */
uint16_t scrambler_out;
/* in place modification of each bit */
for(i=0; i<nbits; i++) {
for (i = 0; i < nbits; i++) {
scrambler_out = ((scrambler & 0x2) >> 1) ^ (scrambler & 0x1);
/* modify i-th bit by xor-ing with scrambler output sequence */
ibyte = i/8;
ishift = i%8;
ibyte = i / 8;
ishift = i % 8;
ibit = (inout[ibyte] >> ishift) & 0x1;
ibits = ibit ^ scrambler_out; // xor ibit with scrambler output
@ -608,7 +615,7 @@ void scramble(unsigned char *inout, int nbytes)
scrambler |= scrambler_out << 14;
#ifdef DEBUG0
printf("i: %02d ibyte: %d ishift: %d ibit: %d ibits: %d scrambler_out: %d\n",
printf("i: %02d ibyte: %d ishift: %d ibit: %d ibits: %d scrambler_out: %d\n",
i, ibyte, ishift, ibit, ibits, scrambler_out);
#endif
@ -620,6 +627,7 @@ void scramble(unsigned char *inout, int nbytes)
printf("%02d 0x%02x\n", i, inout[i]);
#endif
}
#endif
#ifdef HORUS_L2_UNITTEST
@ -642,11 +650,11 @@ int test_sending_bytes(int nbytes, float ber, int error_pattern) {
horus_l2_encode_tx_packet(tx, input_payload, sizeof(input_payload));
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, "\nTx Data:\n");
for(i=0; i<num_tx_data_bytes; i++)
fprintf(stderr, " %02d 0x%02x\n", i, tx[i]);
#endif
#endif
/* insert random bit errors */
@ -657,13 +665,13 @@ int test_sending_bytes(int nbytes, float ber, int error_pattern) {
r = (float)rand()/RAND_MAX;
if (r < ber) {
unsigned char mask = (1<<b);
#ifdef DEBUG1
#ifdef DEBUG1
fprintf("mask: 0x%x tx[%d] = 0x%x ", mask, i, tx[i]);
#endif
#endif
tx[i] ^= mask;
#ifdef DEBUG1
#ifdef DEBUG1
fprintf("0x%x\n", tx[i]);
#endif
#endif
nbiterrors++;
}
}
@ -687,37 +695,37 @@ int test_sending_bytes(int nbytes, float ber, int error_pattern) {
bn++;
if ((bn % 12) == 0) {
unsigned char mask = (1<<(7-b));
#ifdef DEBUG1
#ifdef DEBUG1
fprintf("mask: 0x%x tx[%d] = 0x%x ", mask, i, tx[i]);
#endif
#endif
tx[i] ^= mask;
#ifdef DEBUG1
#ifdef DEBUG1
fprintf("0x%x\n", tx[i]);
#endif
#endif
nbiterrors++;
}
}
}
}
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, "\nTx Data after errors:\n");
for(i=0; i<num_tx_data_bytes; i++)
fprintf(stderr, " %02d 0x%02x\n", i, tx[i]);
#endif
#endif
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, "nbiterrors: %d BER: %3.2f\n", nbiterrors, (float)nbiterrors/(num_tx_data_bytes*8));
#endif
#endif
golay23_init();
horus_l2_decode_rx_packet(output_payload, tx, sizeof(input_payload));
#ifdef DEBUG0
#ifdef DEBUG0
fprintf(stderr, "\nOutput Payload:\n");
for(i=0; i<sizeof(input_payload); i++)
fprintf(stderr, " %02d 0x%02x\n", i, output_payload[i]);
#endif
#endif
/* count bit errors */
@ -727,7 +735,7 @@ int test_sending_bytes(int nbytes, float ber, int error_pattern) {
for(b=0; b<8; b++)
nerr += (error_pattern>>b) & 0x1;
}
return nerr;
}
@ -735,7 +743,7 @@ int test_sending_bytes(int nbytes, float ber, int error_pattern) {
/* Horus binary packet */
struct horus_packet_v1
struct TBinaryPacket
{
uint8_t PayloadID;
uint16_t Counter;
@ -753,7 +761,7 @@ struct horus_packet_v1
} __attribute__ ((packed));
int main(void) {
int nbytes = sizeof(struct horus_packet_v1);
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));
@ -777,13 +785,12 @@ int main(void) {
#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 horus_packet_v1);
struct horus_packet_v1 input_payload;
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;
@ -794,7 +801,7 @@ int main(void) {
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;
@ -830,31 +837,31 @@ int main(void) {
assert(num_tx_data_bytes == 45);
#define READ_FILE /* overwrite tx[] above, that's OK */
#ifdef READ_FILE
#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
#endif
golay23_init();
horus_l2_decode_rx_packet(output_payload, rx, nbytes);
#ifdef HEX_DUMP
#ifdef HEX_DUMP
fprintf(stderr, "\nOutput Payload:\n");
for(i=0; i<nbytes; i++)
fprintf(stderr, " %02d 0x%02x 0x%02x\n", i, output_payload[i], rx[i+2]);
#endif
#endif
struct horus_packet_v1 h;
struct TBinaryPacket h;
assert(sizeof(h) == nbytes);
memcpy(&h, output_payload, nbytes);
uint16_t crc_rx = gen_crc16(output_payload, nbytes-2);
char crc_str[80];
if (crc_rx == h.Checksum) {
sprintf(crc_str, "CRC OK");
} else {
@ -863,20 +870,20 @@ int main(void) {
fprintf(stderr, "%d,%d,%02d:%02d:%02d,%f,%f,%d,%d,%d,%d,%d,%04x %s\n",
h.PayloadID, h.Counter, h.Hours, h.Minutes, h.Seconds,
h.Latitude, h.Longitude, h.Altitude, h.Speed, h.Sats, h.Temp,
h.Latitude, h.Longitude, h.Altitude, h.Speed, h.Sats, h.Temp,
h.BattVoltage, h.Checksum, crc_str);
/* Hex ASCII file output */
#define WRITE_HEX_FILE /* overwrite tx[] above, that's OK */
#ifdef WRITE_HEX_FILE
#define WRITE_HEX_FILE /* overwrite tx[] above, that's OK */
#ifdef WRITE_HEX_FILE
FILE *fh = fopen("../octave/horus_rx_bits_hex.txt","wt");
assert(fh != NULL);
for(i=0; i<nbytes; i++) {
fprintf(fh, "%02X", (unsigned int)output_payload[i]);
}
fclose(fh);
#endif
#endif
return 0;
}
@ -939,6 +946,7 @@ int main(void) {
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#define X22 0x00400000 /* vector representation of X^{22} */
#define X11 0x00000800 /* vector representation of X^{11} */
#define MASK12 0xfffff800 /* auxiliary vector for testing */
@ -1024,12 +1032,12 @@ int32_t get_syndrome(int32_t pattern)
int32_t aux = X22;
if (pattern >= X11)
while (pattern & MASK12) {
while (!(aux & pattern))
aux = aux >> 1;
pattern ^= (aux/X11) * GENPOL;
}
return(pattern);
while (pattern & MASK12) {
while (!(aux & pattern))
aux = aux >> 1;
pattern ^= (aux / X11) * GENPOL;
}
return (pattern);
}
#ifdef HORUS_L2_RX
@ -1169,15 +1177,15 @@ int golay23_count_errors(int recd_codeword, int corrected_codeword)
// from http://stackoverflow.com/questions/10564491/function-to-calculate-a-crc16-checksum
unsigned short gen_crc16(unsigned char* data_p, unsigned char length){
unsigned short gen_crc16(unsigned char *data_p, unsigned char length)
{
unsigned char x;
unsigned short crc = 0xFFFF;
while (length--){
while (length--) {
x = crc >> 8 ^ *data_p++;
x ^= x>>4;
crc = (crc << 8) ^ ((unsigned short)(x << 12)) ^ ((unsigned short)(x <<5)) ^ ((unsigned short)x);
x ^= x >> 4;
crc = (crc << 8) ^ ((unsigned short) (x << 12)) ^ ((unsigned short) (x << 5)) ^ ((unsigned short) x);
}
return crc;
}

Wyświetl plik

@ -13,11 +13,11 @@ 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);
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);
unsigned char *input_rx_data,
int num_payload_data_bytes);
#endif

Wyświetl plik

@ -1,25 +1,8 @@
#include <string.h>
#include "horus_packet_v1.h"
#include "horus_common.h"
volatile uint16_t horus_packet_counter = 0;
static uint16_t calculate_crc16_checksum(char *string, int len)
{
uint16_t crc = 0xffff;
char i;
int ptr = 0;
while (ptr < len) {
ptr++;
crc = crc ^ (*(string++) << 8);
for (i = 0; i < 8; i++) {
if (crc & 0x8000)
crc = (uint16_t)((crc << 1) ^ 0x1021);
else
crc <<= 1;
}
}
return crc;
}
volatile uint16_t horus_v1_packet_counter = 0;
size_t horus_packet_v1_create(uint8_t *payload, size_t length, telemetry_data *data, uint8_t payload_id)
{
@ -34,13 +17,13 @@ size_t horus_packet_v1_create(uint8_t *payload, size_t length, telemetry_data *d
uint8_t volts_scaled = (uint8_t)(255 * (float) data->battery_voltage_millivolts / 5000.0f);
horus_packet_counter++;
horus_v1_packet_counter++;
// Assemble a binary packet
horus_packet_v1 horus_packet;
horus_packet.PayloadID = payload_id % 256;
horus_packet.Counter = horus_packet_counter;
horus_packet.PayloadID = payload_id;
horus_packet.Counter = horus_v1_packet_counter;
horus_packet.Hours = gps_data->hours;
horus_packet.Minutes = gps_data->minutes;
horus_packet.Seconds = gps_data->seconds;

Wyświetl plik

@ -11,16 +11,16 @@
// Refer: https://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Structure_002dPacking-Pragmas.html
#pragma pack(push, 1)
typedef struct _horus_packet_v1 {
uint8_t PayloadID;
uint16_t Counter;
uint8_t Hours;
uint8_t Minutes;
uint8_t Seconds;
float Latitude; // Latitude in degrees TODO: ?
float Longitude; // Longitude in degrees TODO: ?
uint8_t PayloadID; // Payload ID (0-255)
uint16_t Counter; // Sequence number
uint8_t Hours; // Time of day, Hours
uint8_t Minutes; // Time of day, Minutes
uint8_t Seconds; // Time of day, Seconds
float Latitude; // Latitude in degrees
float Longitude; // Longitude in degrees
uint16_t Altitude; // Altitude in meters
uint8_t Speed; // Speed in Knots (1-255 knots) -- TODO: It seems this is actually km/h?
uint8_t Sats;
uint8_t Speed; // Speed in km/h (although spec says: Knots (1-255 knots))
uint8_t Sats; // Number of GPS satellites visible
int8_t Temp; // Temperature in Celsius, as a signed value (-128 to +128, though sensor limited to -64 to +64 deg C)
uint8_t BattVoltage; // 0 = 0v, 255 = 5.0V, linear steps in-between.
uint16_t Checksum; // CRC16-CCITT Checksum.

Wyświetl plik

@ -0,0 +1,61 @@
#include <string.h>
#include "horus_packet_v2.h"
#include "horus_common.h"
volatile uint16_t horus_v2_packet_counter = 0;
size_t horus_packet_v2_create(uint8_t *payload, size_t length, telemetry_data *data, uint16_t payload_id)
{
if (length < sizeof(horus_packet_v2)) {
return 0;
}
gps_data *gps_data = &data->gps;
float float_lat = (float) gps_data->latitude_degrees_1000000 / 10000000.0f;
float float_lon = (float) gps_data->longitude_degrees_1000000 / 10000000.0f;
uint8_t volts_scaled = (uint8_t)(255 * (float) data->battery_voltage_millivolts / 5000.0f);
horus_v2_packet_counter++;
// Assemble a binary packet
horus_packet_v2 horus_packet;
horus_packet.PayloadID = payload_id;
horus_packet.Counter = horus_v2_packet_counter;
horus_packet.Hours = gps_data->hours;
horus_packet.Minutes = gps_data->minutes;
horus_packet.Seconds = gps_data->seconds;
horus_packet.Latitude = float_lat;
horus_packet.Longitude = float_lon;
horus_packet.Altitude = (uint16_t)(gps_data->altitude_mm / 1000);
horus_packet.Speed = (uint8_t)((float) gps_data->ground_speed_cm_per_second * 0.036);
horus_packet.BattVoltage = volts_scaled;
horus_packet.Sats = gps_data->satellites_visible;
horus_packet.Temp = (int8_t) ((float) data->internal_temperature_celsius_100 / 100.0f);
// Add onto the sats_raw value to indicate if the GPS is in regular tracking (+100)
// or power optimized tracker (+200) modes.
if (gps_data->power_safe_mode_state == POWER_SAFE_MODE_STATE_TRACKING) {
horus_packet.Sats += 100;
} else if (gps_data->power_safe_mode_state == POWER_SAFE_MODE_STATE_POWER_OPTIMIZED_TRACKING) {
horus_packet.Sats += 200;
}
int16_t ext_temp_celsius_10 = (int16_t) (data->temperature_celsius_100 / 10.0f);
memcpy(horus_packet.CustomData, &ext_temp_celsius_10, sizeof(ext_temp_celsius_10));
uint8_t ext_humidity_percentage = (uint8_t) (data->humidity_percentage_100 / 100.0f);
memcpy(horus_packet.CustomData + sizeof(ext_temp_celsius_10), &ext_humidity_percentage, sizeof(ext_humidity_percentage));
uint16_t ext_pressure_mbar = (uint8_t) (data->pressure_mbar_100 / 100.0f);
memcpy(horus_packet.CustomData + sizeof(ext_temp_celsius_10) + sizeof(ext_humidity_percentage), &ext_pressure_mbar, sizeof(ext_pressure_mbar));
horus_packet.Checksum = (uint16_t) calculate_crc16_checksum((char *) &horus_packet, sizeof(horus_packet) - 2);
memcpy(payload, &horus_packet, sizeof(horus_packet));
return sizeof(horus_packet);
}

Wyświetl plik

@ -0,0 +1,33 @@
#ifndef __HORUS_PACKET_V2_H
#define __HORUS_PACKET_V2_H
#include <stdint.h>
#include <stdlib.h>
#include "telemetry.h"
// Horus Binary v2 Packet Format
// See: https://github.com/projecthorus/horusdemodlib/wiki/5-Customising-a-Horus-Binary-v2-Packet
// Note that we need to pack this to 1-byte alignment, hence the #pragma flags below
// Refer: https://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Structure_002dPacking-Pragmas.html
#pragma pack(push, 1)
typedef struct _horus_packet_v2 {
uint16_t PayloadID; // Payload ID (0-65535)
uint16_t Counter; // Sequence number
uint8_t Hours; // Time of day, Hours
uint8_t Minutes; // Time of day, Minutes
uint8_t Seconds; // Time of day, Seconds
float Latitude; // Latitude in degrees
float Longitude; // Longitude in degrees
uint16_t Altitude; // Altitude in meters
uint8_t Speed; // Speed in km/h
uint8_t Sats; // Number of GPS satellites visible
int8_t Temp; // Temperature in Celsius, as a signed value (-128 to +128, though sensor limited to -64 to +64 deg C)
uint8_t BattVoltage; // 0 = 0v, 255 = 5.0V, linear steps in-between.
uint8_t CustomData[9]; // Custom data, see: https://github.com/projecthorus/horusdemodlib/wiki/5-Customising-a-Horus-Binary-v2-Packet#interpreting-the-custom-data-section
uint16_t Checksum; // CRC16-CCITT Checksum.
} horus_packet_v2; // __attribute__ ((packed)); // Doesn't work?
#pragma pack(pop)
size_t horus_packet_v2_create(uint8_t *payload, size_t length, telemetry_data *data, uint16_t payload_id);
#endif

Wyświetl plik

@ -51,8 +51,7 @@ volatile bool system_initialized = false;
* Maximum length: 64 characters.
*/
char *cw_message_templates[] = {
"$cs",
"$loc6",
"$cs TEST $loc6 $altm $tiC",
NULL
};
@ -74,6 +73,7 @@ char *aprs_comment_templates[] = {
* Maximum length: 130 characters.
*/
char *fsq_comment_templates[] = {
"TEST $loc6 $altm $tiC",
// " $lat $lon, $alt m, $cl m/s, $gs km/h, $he deg - " FSQ_COMMENT,
// " $loc12, $teC $hu% $prmb $hh:$mm:$ss @ $tow ms - " FSQ_COMMENT,
NULL

Wyświetl plik

@ -31,7 +31,7 @@
#define LOCATOR_PAIR_COUNT_FULL 6 // max. 6 (12 characters WWL)
// Delay after transmission for modes that do not use time synchronization
#define RADIO_POST_TRANSMIT_DELAY_MS 5000
#define RADIO_POST_TRANSMIT_DELAY_MS 1000
// Threshold for time-synchronized modes regarding how far from scheduled transmission time the transmission is still allowed
#define RADIO_TIME_SYNC_THRESHOLD_MS 2000
@ -48,12 +48,14 @@
#define RADIO_SI4032_TX_CW true
#define RADIO_SI4032_TX_APRS true
#define RADIO_SI4032_TX_HORUS_V1 true
#define RADIO_SI4032_TX_HORUS_V2 true
// Transmit frequencies for the Si4032 transmitter modes
#define RADIO_SI4032_TX_FREQUENCY_CW 432500000
#define RADIO_SI4032_TX_FREQUENCY_APRS_1200 432500000
// Use a frequency offset to place FSK tones slightly above the defined frequency for SSB reception
#define RADIO_SI4032_TX_FREQUENCY_HORUS_V1 432501000
#define RADIO_SI4032_TX_FREQUENCY_HORUS_V2 432501000
/**
* External Si5351 radio chip transmission configuration
@ -64,19 +66,25 @@
#define RADIO_SI5351_TX_POWER 3
// Which modes to transmit using an externally connected Si5351 chip in the I²C bus
#define RADIO_SI5351_TX_CW true
#define RADIO_SI5351_TX_HORUS_V1 true
#define RADIO_SI5351_TX_HORUS_V2 true
#define RADIO_SI5351_TX_JT9 false
#define RADIO_SI5351_TX_JT65 false
#define RADIO_SI5351_TX_JT4 false
#define RADIO_SI5351_TX_WSPR false
#define RADIO_SI5351_TX_FSQ false
#define RADIO_SI5351_TX_FSQ true
#define RADIO_SI5351_TX_FT8 false
// Transmit frequencies for the Si5351 transmitter modes
#define RADIO_SI5351_TX_FREQUENCY_CW 3595000UL
#define RADIO_SI5351_TX_FREQUENCY_HORUS_V1 3608000UL
#define RADIO_SI5351_TX_FREQUENCY_HORUS_V2 3608000UL
#define RADIO_SI5351_TX_FREQUENCY_JT9 14085000UL // Was: 14078700UL
#define RADIO_SI5351_TX_FREQUENCY_JT65 14085000UL // Was: 14078300UL
#define RADIO_SI5351_TX_FREQUENCY_JT4 14085000UL // Was: 14078500UL
#define RADIO_SI5351_TX_FREQUENCY_WSPR 14085000UL // Was: 14097200UL
#define RADIO_SI5351_TX_FREQUENCY_FSQ 14085000UL // Was: 7105350UL // Base freq is 1350 Hz higher than dial freq in USB
#define RADIO_SI5351_TX_FREQUENCY_FSQ 3608350UL // Was: 7105350UL // Base freq is 1350 Hz higher than dial freq in USB
#define RADIO_SI5351_TX_FREQUENCY_FT8 14085000UL // Was: 14075000UL
/**
@ -113,7 +121,7 @@
#define APRS_DESTINATION_SSID '0'
// Schedule transmission every N seconds, counting from beginning of an hour (based on GPS time). Set to zero to disable time sync.
#define APRS_TIME_SYNC_SECONDS 1
#define APRS_TIME_SYNC_SECONDS 0
// Delay transmission for an N second offset after the scheduled time.
#define APRS_TIME_SYNC_OFFSET_SECONDS 0
@ -121,17 +129,36 @@
* Horus V1 4FSK mode settings
*/
// Use Horus payload ID 0 for tests (4FSKTEST)
// Use Horus payload ID 0 for Horus V1 tests (4FSKTEST)
#define HORUS_V1_PAYLOAD_ID 0
#define HORUS_V1_BAUD_RATE 100
#define HORUS_V1_FREQUENCY_OFFSET 0
#define HORUS_V1_BAUD_RATE_SI4032 100
#define HORUS_V1_BAUD_RATE_SI5351 50
#define HORUS_V1_PREAMBLE_LENGTH 16
#define HORUS_V1_FREQUENCY_OFFSET_SI4032 0
#define HORUS_V1_TONE_SPACING_HZ 270
// Schedule transmission every N seconds, counting from beginning of an hour (based on GPS time). Set to zero to disable time sync.
#define HORUS_V1_TIME_SYNC_SECONDS 1
#define HORUS_V1_TIME_SYNC_SECONDS 0
// Delay transmission for an N second offset after the scheduled time.
#define HORUS_V1_TIME_SYNC_OFFSET_SECONDS 0
/**
* Horus V2 4FSK mode settings
*/
// Use Horus payload ID 256 for Horus V2 tests (4FSKTEST-V2)
#define HORUS_V2_PAYLOAD_ID 256
#define HORUS_V2_BAUD_RATE_SI4032 100
#define HORUS_V2_BAUD_RATE_SI5351 50
#define HORUS_V2_PREAMBLE_LENGTH 16
#define HORUS_V2_FREQUENCY_OFFSET_SI4032 0
#define HORUS_V2_TONE_SPACING_HZ 270
// Schedule transmission every N seconds, counting from beginning of an hour (based on GPS time). Set to zero to disable time sync.
#define HORUS_V2_TIME_SYNC_SECONDS 0
// Delay transmission for an N second offset after the scheduled time.
#define HORUS_V2_TIME_SYNC_OFFSET_SECONDS 0
/**
* CW settings
*/
@ -140,7 +167,7 @@
#define CW_SPEED_WPM 20
// Schedule transmission every N seconds, counting from beginning of an hour (based on GPS time). Set to zero to disable time sync.
#define CW_TIME_SYNC_SECONDS 1
#define CW_TIME_SYNC_SECONDS 0
// Delay transmission for an N second offset after the scheduled time.
#define CW_TIME_SYNC_OFFSET_SECONDS 0
@ -159,9 +186,8 @@
* FSQ settings
*/
#define FSQ_CALLSIGN_FROM CALLSIGN
#define FSQ_COMMENT "RS41ng radiosonde firmware test"
#define FSQ_SUBMODE RADIO_DATA_MODE_FSQ_6
#define FSQ_SUBMODE RADIO_DATA_MODE_FSQ_3
#define FSQ_TIME_SYNC_SECONDS 0
#define FSQ_TIME_SYNC_OFFSET_SECONDS 0

Wyświetl plik

@ -0,0 +1,400 @@
/*
* si5351mcu - Si5351 library for Arduino MCU tuned for size and click-less
*
* Copyright (C) 2017 Pavel Milanes <pavelmc@gmail.com>
*
* Many chunk of codes are derived-from/copied from other libs
* all GNU GPL licenced:
* - Linux Kernel (www.kernel.org)
* - Hans Summers libs and demo code (qrp-labs.com)
* - Etherkit (NT7S) Si5351 libs on github
* - DK7IH example.
* - Jerry Gaffke integer routines for the bitx20 group
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "si5351mcu.h"
/*****************************************************************************
* This is the default init procedure, it set the Si5351 with this params:
* XTAL 27.000 Mhz
*****************************************************************************/
void Si5351mcu::init(i2c_port *i2c_port, uint8_t i2c_addr)
{
// init with the default freq
init(i2c_port, i2c_addr, int_xtal);
}
/*****************************************************************************
* This is the custom init procedure, it's used to pass a custom xtal
* and has the duty of init the I2C protocol handshake
*****************************************************************************/
void Si5351mcu::init(i2c_port *i2c_port, uint8_t i2c_addr, uint32_t nxtal)
{
port = i2c_port;
addr = i2c_addr;
// set the new base xtal freq
base_xtal = int_xtal = nxtal;
// power off all the outputs
off();
}
/*****************************************************************************
* This function set the freq of the corresponding clock.
*
* In my tests my Si5351 can work between 7,8 Khz and ~225 Mhz [~250 MHz with
* overclocking] as usual YMMV
*
* Click noise:
* - The lib has a reset programmed [aka: click noise] every time it needs to
* change the output divider of a particular MSynth, if you move in big steps
* this can lead to an increased rate of click noise per tuning step.
* - If you move at a pace of a few Hz each time the output divider will
* change at a low rate, hence less click noise per tuning step.
* - The output divider moves [change] faster at high frequencies, so at HF the
* click noise is at the real minimum possible.
*
* [See the README.md file for other details]
****************************************************************************/
void Si5351mcu::setFreq(uint8_t clk, uint32_t freq)
{
uint8_t a, R = 1, pll_stride = 0, msyn_stride = 0;
uint32_t b, c, f, fvco, outdivider;
uint32_t MSx_P1, MSNx_P1, MSNx_P2, MSNx_P3;
// Overclock option
#ifdef SI_OVERCLOCK
// user a overclock setting for the VCO, max value in my hardware
// was 1.05 to 1.1 GHz, as usual YMMV [See README.md for details]
outdivider = SI_OVERCLOCK / freq;
#else
// normal VCO from the datasheet and AN
// With 900 MHz beeing the maximum internal PLL-Frequency
outdivider = 900000000 / freq;
#endif
// use additional Output divider ("R")
while (outdivider > 900) {
R = R * 2;
outdivider = outdivider / 2;
}
// finds the even divider which delivers the intended Frequency
if (outdivider % 2) outdivider--;
// Calculate the PLL-Frequency (given the even divider)
fvco = outdivider * R * freq;
// Convert the Output Divider to the bit-setting required in register 44
switch (R) {
case 1:
R = 0;
break;
case 2:
R = 16;
break;
case 4:
R = 32;
break;
case 8:
R = 48;
break;
case 16:
R = 64;
break;
case 32:
R = 80;
break;
case 64:
R = 96;
break;
case 128:
R = 112;
break;
}
// we have now the integer part of the output msynth
// the b & c is fixed below
MSx_P1 = 128 * outdivider - 512;
// calc the a/b/c for the PLL Msynth
/***************************************************************************
* We will use integer only on the b/c relation, and will >> 5 (/32) both
* to fit it on the 1048 k limit of C and keep the relation
* the most accurate possible, this works fine with xtals from
* 24 to 28 Mhz.
*
* This will give errors of about +/- 2 Hz maximum
* as per my test and simulations in the worst case, well below the
* XTAl ppm error...
*
* This will free more than 1K of the final eeprom
*
****************************************************************************/
a = fvco / int_xtal;
b = (fvco % int_xtal) >> 5; // Integer part of the fraction
// scaled to match "c" limits
c = int_xtal >> 5; // "c" scaled to match it's limits
// in the register
// f is (128*b)/c to mimic the Floor(128*(b/c)) from the datasheet
f = (128 * b) / c;
// build the registers to write
MSNx_P1 = 128 * a + f - 512;
MSNx_P2 = 128 * b - f * c;
MSNx_P3 = c;
// PLLs and CLK# registers are allocated with a stride, we handle that with
// the stride var to make code smaller
if (clk > 0) pll_stride = 8;
// HEX makes it easier to human read on bit shifts
uint8_t reg_bank_26[] = {
(uint8_t) ((MSNx_P3 & 0xFF00) >> 8), // Bits [15:8] of MSNx_P3 in register 26
(uint8_t) (MSNx_P3 & 0xFF),
(uint8_t) ((MSNx_P1 & 0x030000L) >> 16),
(uint8_t) ((MSNx_P1 & 0xFF00) >> 8), // Bits [15:8] of MSNx_P1 in register 29
(uint8_t) (MSNx_P1 & 0xFF), // Bits [7:0] of MSNx_P1 in register 30
(uint8_t) (((MSNx_P3 & 0x0F0000L) >> 12) | ((MSNx_P2 & 0x0F0000) >> 16)), // Parts of MSNx_P3 and MSNx_P1
(uint8_t) ((MSNx_P2 & 0xFF00) >> 8), // Bits [15:8] of MSNx_P2 in register 32
(uint8_t) (MSNx_P2 & 0xFF) // Bits [7:0] of MSNx_P2 in register 33
};
// We could do this here - but move it next to the reg_bank_42 write
// i2cWriteBurst(26 + pll_stride, reg_bank_26, sizeof(reg_bank_26));
// Write the output divider msynth only if we need to, in this way we can
// speed up the frequency changes almost by half the time most of the time
// and the main goal is to avoid the nasty click noise on freq change
if (omsynth[clk] != outdivider || o_Rdiv[clk] != R) {
// CLK# registers are exactly 8 * clk# bytes stride from a base register.
msyn_stride = clk * 8;
// keep track of the change
omsynth[clk] = (uint16_t) outdivider;
o_Rdiv[clk] = R; // cache it now, before we OR mask up R for special divide by 4
// See datasheet, special trick when MSx == 4
// MSx_P1 is always 0 if outdivider == 4, from the above equations, so there is
// no need to set it to 0. ... MSx_P1 = 128 * outdivider - 512;
//
// See para 4.1.3 on the datasheet.
//
if (outdivider == 4) {
R |= 0x0C; // bit set OR mask for MSYNTH divide by 4, for reg 44 {3:2]
}
// HEX makes it easier to human read on bit shifts
uint8_t reg_bank_42[] = {
0, // Bits [15:8] of MS0_P3 (always 0) in register 42
1, // Bits [7:0] of MS0_P3 (always 1) in register 43
(uint8_t) (((MSx_P1 & 0x030000L) >> 16) |
R), // Bits [17:16] of MSx_P1 in bits [1:0] and R in [7:4] | [3:2]
(uint8_t) ((MSx_P1 & 0xFF00) >> 8), // Bits [15:8] of MSx_P1 in register 45
(uint8_t) (MSx_P1 & 0xFF), // Bits [7:0] of MSx_P1 in register 46
0, // Bits [19:16] of MS0_P2 and MS0_P3 are always 0
0, // Bits [15:8] of MS0_P2 are always 0
0 // Bits [7:0] of MS0_P2 are always 0
};
// Get the two write bursts as close together as possible,
// to attempt to reduce any more click glitches. This is
// at the expense of only 24 increased bytes compilation size in AVR 328.
// Everything is already precalculated above, reducing any delay,
// by not doing calculations between the burst writes.
i2cWriteBurst(26 + pll_stride, reg_bank_26, sizeof(reg_bank_26));
i2cWriteBurst(42 + msyn_stride, reg_bank_42, sizeof(reg_bank_42));
//
// https://www.silabs.com/documents/public/application-notes/Si5350-Si5351%20FAQ.pdf
//
// 11.1 "The Int, R and N register values inside the Interpolative Dividers are updated
// when the LSB of R is written via I2C." - Q. does this mean reg 44 or 49 (misprint ?) ???
//
// 10.1 "All outputs are within +/- 500ps of one another at power up (if pre-programmed)
// or if PLLA and PLLB are reset simultaneously via register 177."
//
// 17.1 "The PLL can track any abrupt input frequency changes of 3–4% without losing
// lock to it. Any input frequency changes greater than this amount will not
// necessarily track from the input to the output
// must reset the so called "PLL", in fact the output msynth
reset();
} else {
i2cWriteBurst(26 + pll_stride, reg_bank_26, sizeof(reg_bank_26));
}
}
/*****************************************************************************
* Reset of the PLLs and multisynths output enable
*
* This must be called to soft reset the PLLs and cycle the output of the
* multisynths: this is the "click" noise source in the RF spectrum.
*
* So it must be avoided at all costs, so this lib just call it at the
* initialization of the PLLs and when a correction is applied
*
* If you are concerned with accuracy you can implement a reset every
* other Mhz to be sure it get exactly on spot.
****************************************************************************/
void Si5351mcu::reset(void)
{
// This soft-resets PLL A & B (32 + 128) in just one step
i2cWrite(177, 0xA0);
}
/*****************************************************************************
* Function to disable all outputs
*
* The PLL are kept running, just the m-synths are powered off.
*
* This allows to keep the chip warm and exactly on freq the next time you
* enable an output.
****************************************************************************/
void Si5351mcu::off(void)
{
// This disable all the CLK outputs
for (char i = 0; i < SICHANNELS; i++) {
disable(i);
}
}
/*****************************************************************************
* Function to set the correction in Hz over the Si5351 XTAL.
*
* This will call a reset of the PLLs and multi-synths so it will produce a
* click every time it's called
****************************************************************************/
void Si5351mcu::correction(int32_t diff)
{
// apply some corrections to the xtal
int_xtal = base_xtal + diff;
// reset the PLLs to apply the correction
reset();
}
/*****************************************************************************
* This function enables the selected output
*
* Beware: ZERO is clock output enabled, in register 16+CLK
*****************************************************************************/
void Si5351mcu::enable(uint8_t clk)
{
// var to handle the mask of the registers value
uint8_t m = SICLK0_R;
if (clk > 0) {
m = SICLK12_R;
}
// write the register value
i2cWrite(16 + clk, m + clkpower[clk]);
// 1 & 2 are mutually exclusive
if (clk == 1) disable(2);
if (clk == 2) disable(1);
// update the status of the clk
clkOn[clk] = 1;
}
/*****************************************************************************
* This function disables the selected output
*
* Beware: ONE is clock output disabled, in register 16+CLK
* *****************************************************************************/
void Si5351mcu::disable(uint8_t clk)
{
// send
i2cWrite(16 + clk, 0x80);
// update the status of the clk
clkOn[clk] = 0;
}
/****************************************************************************
* Set the power output for each output independently
***************************************************************************/
void Si5351mcu::setPower(uint8_t clk, uint8_t power)
{
// set the power to the correct var
clkpower[clk] = power;
// now enable the output to get it applied
enable(clk);
}
/****************************************************************************
* method to send multi-byte burst register data.
***************************************************************************/
uint8_t Si5351mcu::i2cWriteBurst(const uint8_t start_register,
const uint8_t *data,
const uint8_t numbytes)
{
// This method saves the massive overhead of having to keep opening
// and closing the I2C bus for consecutive register writes. It
// also saves numbytes - 1 writes for register address selection.
// All of the bytes queued up in the above write() calls are buffered
// up and will be sent to the slave in one "burst", on the call to
// endTransmission(). This also sends the I2C STOP to the Slave.
return i2c_write_bytes(port, addr, start_register, numbytes, (uint8_t *) data);
// returns non zero on error
}
/****************************************************************************
* function to send the register data to the Si5351, arduino way.
***************************************************************************/
void Si5351mcu::i2cWrite(const uint8_t regist, const uint8_t value)
{
// Using the "burst" method instead of
// doing it longhand saves a few bytes
i2cWriteBurst(regist, &value, 1);
}
/****************************************************************************
* Read i2C register, returns -1 on error or timeout
***************************************************************************/
int16_t Si5351mcu::i2cRead(const uint8_t regist)
{
uint8_t value;
if (i2c_read_byte(port, addr, regist, &value) != HAL_OK) {
return -1;
}
return value;
}

Wyświetl plik

@ -0,0 +1,174 @@
/*
* si5351mcu - Si5351 library for Arduino MCU tuned for size and click-less
*
* Copyright (C) 2017 Pavel Milanes <pavelmc@gmail.com>
*
* Many chunk of codes are derived-from/copied from other libs
* all GNU GPL licenced:
* - Linux Kernel (www.kernel.org)
* - Hans Summers libs and demo code (qrp-labs.com)
* - Etherkit (NT7S) Si5351 libs on github
* - DK7IH example.
* - Jerry Gaffke integer routines for the bitx20 group
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/****************************************************************************
* This lib tricks:
*
* CLK0 will use PLLA
* CLK1 will use PLLB
* CLK2 will use PLLB
*
* Lib defaults
* - XTAL is 27 Mhz.
* - Always put the internal 8pF across the xtal legs to GND
* - lowest power output (2mA)
* - After the init all outputs are off, you need to enable them in your code.
*
* The correction procedure is not for the PPM as other libs, this
* is just +/- Hz to the XTAL freq, you may get a click noise after
* applying a correction
*
* The init procedure is mandatory as it set the Xtal (the default or a custom
* one) and prepare the Wire (I2C) library for operation.
****************************************************************************/
#ifndef SI5351MCU_H
#define SI5351MCU_H
#include <cstdint>
#include "hal/i2c.h"
// default I2C address of the Si5351A - other variants may differ
#define SIADDR 0x60
// The number of output channels - 3 for Si5351A 10 pin
#define SICHANNELS 3
// register's power modifiers
#define SIOUT_2mA 0
#define SIOUT_4mA 1
#define SIOUT_6mA 2
#define SIOUT_8mA 3
// registers base (2mA by default)
#define SICLK0_R 76 // 0b01001100
#define SICLK12_R 108 // 0b01101100
class Si5351mcu {
private:
i2c_port *port;
uint8_t addr;
// base xtal freq, over this we apply the correction factor
// by default 25 MHz
uint32_t base_xtal = 25000000L;
// this is the work value, with the correction applied
// via the correction() procedure
uint32_t int_xtal = base_xtal;
// clk# power holders (2ma by default)
uint8_t clkpower[SICHANNELS] = {0};
// local var to keep track of when to reset the "pll"
/*********************************************************
* BAD CONCEPT on the datasheet and AN:
*
* The chip has a soft-reset for PLL A & B but in
* practice the PLL does not need to be reseted.
*
* Test shows that if you fix the Msynth output
* dividers and move any of the VCO from bottom to top
* the frequency moves smooth and clean, no reset needed
*
* The reset is needed when you changes the value of the
* Msynth output divider, even so it's not always needed
* so we use this var to keep track of all three and only
* reset the "PLL" when this value changes to be sure
*
* It's a word (16 bit) because the final max value is 900
*********************************************************/
uint16_t omsynth[SICHANNELS] = {0};
uint8_t o_Rdiv[SICHANNELS] = {0};
public:
// var to check the clock state
bool clkOn[SICHANNELS] = {0}; // This should not really be public - use isEnabled()
public:
// default init procedure
void init(i2c_port *, uint8_t);
// custom init procedure (XTAL in Hz);
void init(i2c_port *, uint8_t, uint32_t);
// reset all PLLs
void reset(void);
// set CLKx(0..2) to freq (Hz)
void setFreq(uint8_t, uint32_t);
// pass a correction factor
void correction(int32_t);
// enable some CLKx output
void enable(uint8_t);
// disable some CLKx output
void disable(uint8_t);
// disable all outputs
void off(void);
// set power output to a specific clk
void setPower(uint8_t, uint8_t);
// used to talk with the chip, via Arduino Wire lib
//
// declared as static, since they do not reference any this-> class attributes
//
void i2cWrite(const uint8_t reg, const uint8_t val);
uint8_t i2cWriteBurst(const uint8_t start_register, const uint8_t *data, const uint8_t numbytes);
int16_t i2cRead(const uint8_t reg);
inline const bool isEnabled(const uint8_t channel)
{
return channel < SICHANNELS && clkOn[channel] != 0;
};
inline const uint8_t getPower(const uint8_t channel)
{
return channel < SICHANNELS ? clkpower[channel] : 0;
};
inline const uint32_t getXtalBase(void)
{
return base_xtal;
};
inline const uint32_t getXtalCurrent(void)
{
return int_xtal;
};
};
#endif //SI5351MCU_H

Wyświetl plik

@ -46,7 +46,7 @@ void i2c_init()
I2C_InitTypeDef i2c_init;
I2C_StructInit(&i2c_init);
i2c_init.I2C_ClockSpeed = 10000;
i2c_init.I2C_ClockSpeed = 100000;
i2c_init.I2C_Mode = I2C_Mode_I2C;
i2c_init.I2C_DutyCycle = I2C_DutyCycle_2;
i2c_init.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;

Wyświetl plik

@ -17,6 +17,7 @@
#include "radio_payload_cw.h"
#include "radio_payload_aprs.h"
#include "radio_payload_horus_v1.h"
#include "radio_payload_horus_v2.h"
#include "radio_payload_wspr.h"
#include "radio_payload_jtencode.h"
#include "radio_payload_fsq.h"
@ -54,10 +55,59 @@ radio_transmit_entry radio_transmit_schedule[] = {
.time_sync_seconds_offset = HORUS_V1_TIME_SYNC_OFFSET_SECONDS,
.frequency = RADIO_SI4032_TX_FREQUENCY_HORUS_V1,
.tx_power = RADIO_SI4032_TX_POWER,
.symbol_rate = HORUS_V1_BAUD_RATE,
.symbol_rate = HORUS_V1_BAUD_RATE_SI4032,
.payload_encoder = &radio_horus_v1_payload_encoder,
.fsk_encoder_api = &mfsk_fsk_encoder_api,
},
{
.enabled = RADIO_SI4032_TX_HORUS_V2,
.radio_type = RADIO_TYPE_SI4032,
.data_mode = RADIO_DATA_MODE_HORUS_V2,
.time_sync_seconds = HORUS_V2_TIME_SYNC_SECONDS,
.time_sync_seconds_offset = HORUS_V2_TIME_SYNC_OFFSET_SECONDS,
.frequency = RADIO_SI4032_TX_FREQUENCY_HORUS_V2,
.tx_power = RADIO_SI4032_TX_POWER,
.symbol_rate = HORUS_V2_BAUD_RATE_SI4032,
.payload_encoder = &radio_horus_v2_payload_encoder,
.fsk_encoder_api = &mfsk_fsk_encoder_api,
},
{
.enabled = RADIO_SI5351_TX_CW,
.radio_type = RADIO_TYPE_SI5351,
.data_mode = RADIO_DATA_MODE_CW,
.time_sync_seconds = CW_TIME_SYNC_SECONDS,
.time_sync_seconds_offset = CW_TIME_SYNC_OFFSET_SECONDS,
.frequency = RADIO_SI5351_TX_FREQUENCY_CW,
.tx_power = RADIO_SI5351_TX_POWER,
.symbol_rate = MORSE_WPM_TO_SYMBOL_RATE(CW_SPEED_WPM),
.payload_encoder = &radio_cw_payload_encoder,
.fsk_encoder_api = &morse_fsk_encoder_api,
},
{
.enabled = RADIO_SI5351_TX_HORUS_V1,
.radio_type = RADIO_TYPE_SI5351,
.data_mode = RADIO_DATA_MODE_HORUS_V1,
.time_sync_seconds = HORUS_V1_TIME_SYNC_SECONDS,
.time_sync_seconds_offset = HORUS_V1_TIME_SYNC_OFFSET_SECONDS,
.frequency = RADIO_SI5351_TX_FREQUENCY_HORUS_V1,
.tx_power = RADIO_SI5351_TX_POWER,
.symbol_rate = HORUS_V1_BAUD_RATE_SI5351,
.payload_encoder = &radio_horus_v1_payload_encoder,
.fsk_encoder_api = &mfsk_fsk_encoder_api,
},
{
.enabled = RADIO_SI5351_TX_HORUS_V2,
.radio_type = RADIO_TYPE_SI5351,
.data_mode = RADIO_DATA_MODE_HORUS_V2,
.time_sync_seconds = HORUS_V2_TIME_SYNC_SECONDS,
.time_sync_seconds_offset = HORUS_V2_TIME_SYNC_OFFSET_SECONDS,
.frequency = RADIO_SI5351_TX_FREQUENCY_HORUS_V2,
.tx_power = RADIO_SI5351_TX_POWER,
.symbol_rate = HORUS_V2_BAUD_RATE_SI5351,
.payload_encoder = &radio_horus_v2_payload_encoder,
.fsk_encoder_api = &mfsk_fsk_encoder_api,
},
/*
{
.enabled = RADIO_SI5351_TX_WSPR,
.radio_type = RADIO_TYPE_SI5351,
@ -113,6 +163,7 @@ radio_transmit_entry radio_transmit_schedule[] = {
.payload_encoder = &radio_jt65_payload_encoder,
.fsk_encoder_api = &jtencode_fsk_encoder_api,
},
*/
{
.enabled = RADIO_SI5351_TX_FSQ,
.radio_type = RADIO_TYPE_SI5351,
@ -264,10 +315,21 @@ static bool radio_start_transmit(radio_transmit_entry *entry)
entry->fsk_encoder_api->set_data(&entry->fsk_encoder, radio_current_payload_length, radio_current_payload);
break;
case RADIO_DATA_MODE_HORUS_V1:
mfsk_encoder_new(&entry->fsk_encoder, MFSK_4, entry->symbol_rate, 100);
mfsk_encoder_new(&entry->fsk_encoder, MFSK_4, entry->symbol_rate, HORUS_V1_TONE_SPACING_HZ * 100);
radio_shared_state.radio_current_symbol_rate = entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder);
entry->fsk_encoder_api->get_tones(&entry->fsk_encoder, &radio_shared_state.radio_current_fsk_tone_count,
&radio_shared_state.radio_current_fsk_tones);
radio_shared_state.radio_current_tone_spacing_hz_100 = entry->fsk_encoder_api->get_tone_spacing(
&entry->fsk_encoder);
entry->fsk_encoder_api->set_data(&entry->fsk_encoder, radio_current_payload_length, radio_current_payload);
break;
case RADIO_DATA_MODE_HORUS_V2:
mfsk_encoder_new(&entry->fsk_encoder, MFSK_4, entry->symbol_rate, HORUS_V2_TONE_SPACING_HZ * 100);
radio_shared_state.radio_current_symbol_rate = entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder);
entry->fsk_encoder_api->get_tones(&entry->fsk_encoder, &radio_shared_state.radio_current_fsk_tone_count,
&radio_shared_state.radio_current_fsk_tones);
radio_shared_state.radio_current_tone_spacing_hz_100 = entry->fsk_encoder_api->get_tone_spacing(
&entry->fsk_encoder);
entry->fsk_encoder_api->set_data(&entry->fsk_encoder, radio_current_payload_length, radio_current_payload);
break;
case RADIO_DATA_MODE_WSPR:
@ -381,6 +443,7 @@ static bool radio_stop_transmit(radio_transmit_entry *entry)
bell_encoder_destroy(&entry->fsk_encoder);
break;
case RADIO_DATA_MODE_HORUS_V1:
case RADIO_DATA_MODE_HORUS_V2:
mfsk_encoder_destroy(&entry->fsk_encoder);
break;
case RADIO_DATA_MODE_WSPR:
@ -473,6 +536,8 @@ void radio_handle_timer_tick()
void radio_handle_data_timer_tick()
{
radio_handle_data_timer_si4032();
radio_handle_data_timer_si5351();
}
bool radio_handle_time_sync()
@ -643,6 +708,7 @@ void radio_init()
entry->messages = aprs_comment_templates;
break;
case RADIO_DATA_MODE_HORUS_V1:
case RADIO_DATA_MODE_HORUS_V2:
// No messages
break;
case RADIO_DATA_MODE_FT8:

Wyświetl plik

@ -17,6 +17,7 @@ typedef enum _radio_data_mode {
RADIO_DATA_MODE_RTTY,
RADIO_DATA_MODE_APRS_1200,
RADIO_DATA_MODE_HORUS_V1,
RADIO_DATA_MODE_HORUS_V2,
RADIO_DATA_MODE_WSPR,
RADIO_DATA_MODE_FT8,
RADIO_DATA_MODE_JT65,

Wyświetl plik

@ -0,0 +1,39 @@
#include <stdint.h>
#include "codecs/horus/horus_packet_v2.h"
#include "codecs/horus/horus_l2.h"
#include "config.h"
#include "telemetry.h"
#include "radio_payload_horus_v2.h"
#include "log.h"
uint16_t radio_horus_v2_encode(uint8_t *payload, uint16_t length, telemetry_data *telemetry_data, char *message)
{
horus_packet_v2 horus_packet;
size_t packet_length = horus_packet_v2_create((uint8_t *) &horus_packet, sizeof(horus_packet),
telemetry_data, HORUS_V2_PAYLOAD_ID);
#ifdef SEMIHOSTING_ENABLE
log_info("Horus V2 packet: ");
log_bytes_hex((int) packet_length, (char *) &horus_packet);
log_info("\n");
#endif
// Preamble to help the decoder lock-on after a quiet period.
for (int i = 0; i < HORUS_V2_PREAMBLE_LENGTH; i++) {
payload[i] = 0x1b;
}
// Encode the packet, and write into the mfsk buffer.
int encoded_length = horus_l2_encode_tx_packet(
(unsigned char *) payload + HORUS_V2_PREAMBLE_LENGTH,
(unsigned char *) &horus_packet, (int) packet_length);
return encoded_length + HORUS_V2_PREAMBLE_LENGTH;
}
payload_encoder radio_horus_v2_payload_encoder = {
.encode = radio_horus_v2_encode,
};

Wyświetl plik

@ -0,0 +1,8 @@
#ifndef __RADIO_PAYLOAD_HORUS_V2_H
#define __RADIO_PAYLOAD_HORUS_V2_H
#include "payload.h"
extern payload_encoder radio_horus_v2_payload_encoder;
#endif

Wyświetl plik

@ -65,9 +65,10 @@ bool radio_start_transmit_si4032(radio_transmit_entry *entry, radio_module_state
radio_si4032_fill_pwm_buffer(0, PWM_TIMER_DMA_BUFFER_SIZE, pwm_timer_dma_buffer);
}
break;
case RADIO_DATA_MODE_HORUS_V1: {
case RADIO_DATA_MODE_HORUS_V1:
case RADIO_DATA_MODE_HORUS_V2: {
fsk_tone *idle_tone = mfsk_get_idle_tone(&entry->fsk_encoder);
frequency_offset = (uint16_t) idle_tone->index + HORUS_V1_FREQUENCY_OFFSET;
frequency_offset = (uint16_t) idle_tone->index + HORUS_V1_FREQUENCY_OFFSET_SI4032;
modulation_type = SI4032_MODULATION_TYPE_OOK;
use_direct_mode = false;
@ -111,6 +112,7 @@ bool radio_start_transmit_si4032(radio_transmit_entry *entry, radio_module_state
}
break;
case RADIO_DATA_MODE_HORUS_V1:
case RADIO_DATA_MODE_HORUS_V2:
system_disable_tick();
shared_state->radio_interrupt_transmit_active = true;
break;
@ -241,7 +243,8 @@ inline void radio_handle_data_timer_si4032()
radio_shared_state.radio_symbol_count_interrupt++;
break;
}
case RADIO_DATA_MODE_HORUS_V1: {
case RADIO_DATA_MODE_HORUS_V1:
case RADIO_DATA_MODE_HORUS_V2: {
fsk_encoder_api *fsk_encoder_api = radio_current_transmit_entry->fsk_encoder_api;
fsk_encoder *fsk_enc = &radio_current_transmit_entry->fsk_encoder;
int8_t tone_index;
@ -255,7 +258,7 @@ inline void radio_handle_data_timer_si4032()
break;
}
si4032_set_frequency_offset_small(tone_index + HORUS_V1_FREQUENCY_OFFSET);
si4032_set_frequency_offset_small(tone_index + HORUS_V1_FREQUENCY_OFFSET_SI4032);
radio_shared_state.radio_symbol_count_interrupt++;
break;
@ -277,6 +280,7 @@ bool radio_stop_transmit_si4032(radio_transmit_entry *entry, radio_module_state
break;
case RADIO_DATA_MODE_RTTY:
case RADIO_DATA_MODE_HORUS_V1:
case RADIO_DATA_MODE_HORUS_V2:
data_timer_uninit();
break;
case RADIO_DATA_MODE_APRS_1200:
@ -307,6 +311,7 @@ bool radio_stop_transmit_si4032(radio_transmit_entry *entry, radio_module_state
}
break;
case RADIO_DATA_MODE_HORUS_V1:
case RADIO_DATA_MODE_HORUS_V2:
system_enable_tick();
break;
default:

Wyświetl plik

@ -1,17 +1,67 @@
#include <stdint.h>
#include "hal/system.h"
#include "hal/datatimer.h"
#include "si5351_handler.h"
#include "radio_si5351.h"
#include "log.h"
#define CW_SYMBOL_RATE_MULTIPLIER 4
static bool use_fast_si5351 = false;
static volatile bool radio_si5351_state_change = false;
static volatile uint64_t radio_si5351_freq = 0;
static volatile bool radio_si5351_frequency_not_set = false;
bool radio_start_transmit_si5351(radio_transmit_entry *entry, radio_module_state *shared_state)
{
si5351_set_drive_strength(SI5351_CLOCK_CLK0, entry->tx_power);
si5351_set_frequency(SI5351_CLOCK_CLK0, ((uint64_t) entry->frequency) * 100ULL);
si5351_output_enable(SI5351_CLOCK_CLK0, true);
bool set_frequency_early = true;
radio_si5351_frequency_not_set = false;
switch (entry->data_mode) {
case RADIO_DATA_MODE_CW:
data_timer_init(entry->symbol_rate * CW_SYMBOL_RATE_MULTIPLIER);
set_frequency_early = false;
break;
case RADIO_DATA_MODE_HORUS_V1:
data_timer_init(entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder));
//use_fast_si5351 = true;
break;
default:
break;
}
if (use_fast_si5351) {
si5351_set_drive_strength_fast(SI5351_CLOCK_CLK0, entry->tx_power);
if (set_frequency_early) {
si5351_set_frequency_fast(SI5351_CLOCK_CLK0, ((uint64_t) entry->frequency) * 100ULL);
si5351_output_enable_fast(SI5351_CLOCK_CLK0, true);
}
} else {
si5351_set_drive_strength(SI5351_CLOCK_CLK0, entry->tx_power);
if (set_frequency_early) {
si5351_set_frequency(SI5351_CLOCK_CLK0, ((uint64_t) entry->frequency) * 100ULL);
si5351_output_enable(SI5351_CLOCK_CLK0, true);
}
}
switch (entry->data_mode) {
case RADIO_DATA_MODE_CW:
system_disable_tick();
shared_state->radio_interrupt_transmit_active = true;
// Setting the frequency turns on the output
radio_si5351_frequency_not_set = true;
break;
case RADIO_DATA_MODE_HORUS_V1:
system_disable_tick();
shared_state->radio_interrupt_transmit_active = true;
break;
default:
shared_state->radio_interrupt_transmit_active = false;
}
return true;
}
@ -20,6 +70,8 @@ bool radio_transmit_symbol_si5351(radio_transmit_entry *entry, radio_module_stat
switch (entry->data_mode) {
case RADIO_DATA_MODE_CW:
return false;
case RADIO_DATA_MODE_HORUS_V1:
return false;
default: {
int8_t next_tone_index = entry->fsk_encoder_api->next_tone(&entry->fsk_encoder);
if (next_tone_index < 0) {
@ -41,7 +93,7 @@ bool radio_transmit_symbol_si5351(radio_transmit_entry *entry, radio_module_stat
void radio_handle_main_loop_si5351(radio_transmit_entry *entry, radio_module_state *shared_state)
{
if (entry->radio_type != RADIO_TYPE_SI5351) {
if (entry->radio_type != RADIO_TYPE_SI5351 || shared_state->radio_interrupt_transmit_active) {
return;
}
@ -51,8 +103,105 @@ void radio_handle_main_loop_si5351(radio_transmit_entry *entry, radio_module_sta
}
}
inline void radio_handle_data_timer_si5351()
{
static int cw_symbol_rate_multiplier = CW_SYMBOL_RATE_MULTIPLIER;
if (radio_current_transmit_entry->radio_type != RADIO_TYPE_SI5351 || !radio_shared_state.radio_interrupt_transmit_active) {
return;
}
switch (radio_current_transmit_entry->data_mode) {
case RADIO_DATA_MODE_CW: {
cw_symbol_rate_multiplier--;
if (cw_symbol_rate_multiplier > 0) {
break;
}
cw_symbol_rate_multiplier = CW_SYMBOL_RATE_MULTIPLIER;
fsk_encoder_api *fsk_encoder_api = radio_current_transmit_entry->fsk_encoder_api;
fsk_encoder *fsk_enc = &radio_current_transmit_entry->fsk_encoder;
int8_t tone_index;
tone_index = fsk_encoder_api->next_tone(fsk_enc);
if (tone_index < 0) {
si5351_output_enable(SI5351_CLOCK_CLK0, false);
log_info("CW TX finished\n");
radio_shared_state.radio_interrupt_transmit_active = false;
radio_shared_state.radio_transmission_finished = true;
system_enable_tick();
break;
}
bool enable = tone_index != 0;
if (enable && radio_si5351_frequency_not_set) {
si5351_set_frequency(SI5351_CLOCK_CLK0, ((uint64_t) radio_current_transmit_entry->frequency) * 100ULL);
radio_si5351_frequency_not_set = false;
}
si5351_output_enable(SI5351_CLOCK_CLK0, enable);
radio_shared_state.radio_symbol_count_interrupt++;
break;
}
case RADIO_DATA_MODE_HORUS_V1: {
fsk_encoder_api *fsk_encoder_api = radio_current_transmit_entry->fsk_encoder_api;
fsk_encoder *fsk_enc = &radio_current_transmit_entry->fsk_encoder;
int8_t tone_index;
tone_index = fsk_encoder_api->next_tone(fsk_enc);
if (tone_index < 0) {
log_info("Horus V1 TX finished\n");
radio_shared_state.radio_interrupt_transmit_active = false;
radio_shared_state.radio_transmission_finished = true;
system_enable_tick();
break;
}
uint64_t frequency =
((uint64_t) radio_current_transmit_entry->frequency) * 100UL + (tone_index * radio_shared_state.radio_current_tone_spacing_hz_100);
si5351_set_frequency(SI5351_CLOCK_CLK0, frequency);
radio_shared_state.radio_symbol_count_interrupt++;
break;
}
default:
break;
}
}
bool radio_stop_transmit_si5351(radio_transmit_entry *entry, radio_module_state *shared_state)
{
si5351_output_enable(SI5351_CLOCK_CLK0, false);
switch (entry->data_mode) {
case RADIO_DATA_MODE_CW:
break;
case RADIO_DATA_MODE_HORUS_V1:
// use_fast_si5351 = true;
break;
default:
break;
}
if (use_fast_si5351) {
si5351_output_enable_fast(SI5351_CLOCK_CLK0, false);
} else {
si5351_output_enable(SI5351_CLOCK_CLK0, false);
}
switch (entry->data_mode) {
case RADIO_DATA_MODE_CW:
data_timer_uninit();
system_enable_tick();
break;
case RADIO_DATA_MODE_HORUS_V1:
data_timer_uninit();
system_enable_tick();
break;
default:
break;
}
return true;
}

Wyświetl plik

@ -6,6 +6,7 @@
bool radio_start_transmit_si5351(radio_transmit_entry *entry, radio_module_state *shared_state);
bool radio_transmit_symbol_si5351(radio_transmit_entry *entry, radio_module_state *shared_state);
void radio_handle_main_loop_si5351(radio_transmit_entry *entry, radio_module_state *shared_state);
void radio_handle_data_timer_si5351();
bool radio_stop_transmit_si5351(radio_transmit_entry *entry, radio_module_state *shared_state);
#endif

Wyświetl plik

@ -1,9 +1,9 @@
#include "drivers/si5351/si5351.h"
#include "drivers/si5351fast/si5351mcu.h"
#include "si5351_handler.h"
// TODO: click-free impl: https://github.com/pavelmc/Si5351mcu
Si5351 *si5351;
Si5351mcu si5351_fast;
bool si5351_handler_init()
{
@ -12,9 +12,11 @@ bool si5351_handler_init()
// Change the 2nd parameter in init if using a reference oscillator other than 25 MHz
bool si5351_found = si5351->init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
if (!si5351_found) {
// TODO
return si5351_found;
}
// si5351_fast.init(&DEFAULT_I2C_PORT, SIADDR);
return si5351_found;
}
@ -51,3 +53,42 @@ void si5351_set_drive_strength(si5351_clock_id clock, uint8_t drive)
si5351->drive_strength((enum si5351_clock) clock, si5351_drive);
}
bool si5351_set_frequency_fast(si5351_clock_id clock, uint64_t frequency_hz_100)
{
si5351_fast.setFreq((uint8_t) clock, frequency_hz_100 / 100L);
return true;
}
void si5351_output_enable_fast(si5351_clock_id clock, bool enabled)
{
if (enabled) {
si5351_fast.enable((uint8_t) clock);
} else {
si5351_fast.disable((uint8_t) clock);
}
}
void si5351_set_drive_strength_fast(si5351_clock_id clock, uint8_t drive)
{
int si5351_drive;
switch (drive) {
case 0:
si5351_drive = SIOUT_2mA;
break;
case 1:
si5351_drive = SIOUT_4mA;
break;
case 2:
si5351_drive = SIOUT_6mA;
break;
case 3:
si5351_drive = SIOUT_8mA;
break;
default:
si5351_drive = SIOUT_2mA;
}
si5351_fast.setPower(si5351_drive, (uint8_t) clock);
}

Wyświetl plik

@ -2,6 +2,7 @@
#define __SI5351_HANDLER_H
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
@ -22,6 +23,9 @@ bool si5351_handler_init();
bool si5351_set_frequency(si5351_clock_id clock, uint64_t frequency_hz_100);
void si5351_output_enable(si5351_clock_id clock, bool enabled);
void si5351_set_drive_strength(si5351_clock_id clock, uint8_t drive);
bool si5351_set_frequency_fast(si5351_clock_id clock, uint64_t frequency_hz_100);
void si5351_output_enable_fast(si5351_clock_id clock, bool enabled);
void si5351_set_drive_strength_fast(si5351_clock_id clock, uint8_t drive);
#ifdef __cplusplus
}