sforkowany z mirror/RS41ng
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.
rodzic
fa46e8a30c
commit
9486cd3370
78
README.md
78
README.md
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
48
src/config.h
48
src/config.h
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
|
|
70
src/radio.c
70
src/radio.c
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue