kopia lustrzana https://github.com/mikaelnousiainen/RS41ng
CATS support (#86)
* FIFO mode working * fifo underflow check * fix TX LED. Not perfect - comes on for longer than we're actually TXing for, because it includes the time to fill the buffer and probably some overhead. But good enough for now * move to big FIFO buffer. Had to implement a recommendation from the errata * increase timeout * a bit of cleanup I missed * encoding CATS packets inline. Still need to support GPS and NodeInfo whiskers * cleanup * additional whiskers * fix GPS and revert some settings * revert config * add balloon node info + more precise altitude * address feedback * Add comment about CATS packet parity array size * bug fixes --------- Co-authored-by: Stephen D <webmaster@scd31.com> Co-authored-by: Mikael Nousiainen <mikaelnousiainen@users.noreply.github.com>main
rodzic
0b32abff31
commit
3d2bdbf246
14
README.md
14
README.md
|
@ -148,7 +148,17 @@ On an external Si5351 clock generator connected to the external I²C bus of the
|
|||
* 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.
|
||||
* In order to use Horus 4FSK mode on a flight, you will need to request a new Horus 4FSK payload ID in GitHub according to the instructions at: https://github.com/projecthorus/horusdemodlib/wiki#how-do-i-transmit-it
|
||||
* In order to use Horus 4FSK mode on a flight, you will need to request a new Horus 4FSK payload ID in GitHub according to the instructions at: https://github.com/projecthorus/horusdemodlib/wiki#how-do-i-transmit-it
|
||||
|
||||
#### Notes about CATS (DFM-17 only)
|
||||
|
||||
* CATS is a [modern packet radio standard](https://cats.radio/) designed for communication and telemetry. Due to its increased efficiency over APRS, it allows for fast beacon times (1 Hz or more) without congesting the network.
|
||||
* To receive CATS, you can either use an [I-Gate board](https://www.tindie.com/products/hamcats/cats-i-gate-board/) on a Raspberry Pi, or just a standard RTL-SDR dongle.
|
||||
* See [here](https://gitlab.scd31.com/cats/igate) for the I-Gate board software.
|
||||
* See [here](https://gitlab.scd31.com/cats/sdr-igate) for the SDR software.
|
||||
* In either case, CATS packets that are received get forwarded to FELINET, and relayed to APRS-IS. This means your CATS packets will show up on [aprs.fi](https://aprs.fi)
|
||||
* If you're relying on APRS gating, be sure to set an SSID below 100 or the APRS network may reject it.
|
||||
* For more information, be sure to check [the standard](https://gitlab.scd31.com/cats/cats-standard/-/blob/master/standard.pdf).
|
||||
|
||||
### External sensors (RS41 only)
|
||||
|
||||
|
@ -607,6 +617,8 @@ rtl_fm -f 432500000 -M fm -s 250k -r 48000 -g 22 - | ./aprs -
|
|||
* Various authors with smaller contributions from GitHub pull requests
|
||||
* Original codebase: DF8OE and other authors of the [RS41HUP](https://github.com/df8oe/RS41HUP) project
|
||||
* Horus 4FSK code adapted from [darksidelemm fork of RS41HUP](https://github.com/darksidelemm/RS41HUP) project
|
||||
* CATS code adapted from [CATS reference implementation](https://gitlab.scd31.com/cats/ham-cats/)
|
||||
* LDPC encoder adapted from [libCATS](https://github.com/CamK06/libCATS)
|
||||
|
||||
# Additional documentation
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include "cats.h"
|
||||
#include "crc.h"
|
||||
#include "whiten.h"
|
||||
#include "ldpc.h"
|
||||
#include "interleaver.h"
|
||||
|
||||
cats_packet cats_create(uint8_t *data)
|
||||
{
|
||||
cats_packet c = {.data = data, .len = 0};
|
||||
return c;
|
||||
}
|
||||
|
||||
// This makes changes to packet->data.
|
||||
// Don't call it more than once, and don't use
|
||||
// the packet after calling!
|
||||
size_t cats_fully_encode(cats_packet packet, uint8_t *out)
|
||||
{
|
||||
|
||||
// 0. CRC
|
||||
int new_len = cats_append_crc(packet.data, packet.len);
|
||||
// 1. whiten
|
||||
cats_whiten(packet.data, new_len);
|
||||
// 2. ldpc
|
||||
new_len = cats_ldpc_encode(packet.data, new_len);
|
||||
// 3. length
|
||||
out[0] = new_len;
|
||||
out[1] = new_len >> 8;
|
||||
// 4. interleave
|
||||
cats_interleave(out + 2, packet.data, new_len);
|
||||
|
||||
return new_len + 2;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef __CATS_H
|
||||
#define __CATS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct _cats_packet {
|
||||
uint8_t *data;
|
||||
size_t len;
|
||||
} cats_packet;
|
||||
|
||||
cats_packet cats_create(uint8_t *payload);
|
||||
size_t cats_fully_encode(cats_packet packet, uint8_t *out);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,32 @@
|
|||
#include "crc.h"
|
||||
|
||||
uint16_t calc_crc(uint8_t *data, size_t length);
|
||||
|
||||
size_t cats_append_crc(uint8_t *data, size_t len)
|
||||
{
|
||||
uint16_t crc = calc_crc(data, len);
|
||||
data[len++] = crc;
|
||||
data[len++] = crc >> 8;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/69850602/converting-c-to-java-for-crc16-ibm-sdlc
|
||||
uint16_t calc_crc(uint8_t *data, size_t length)
|
||||
{
|
||||
uint8_t *ptr = data;
|
||||
uint8_t crcbyte1 = 0xFF;
|
||||
uint8_t crcbyte2 = 0xFF;
|
||||
for (int i = 0; i < length; i++) {
|
||||
uint8_t r1 = *ptr++ ^ crcbyte2;
|
||||
r1 = (r1 << 4) ^ r1;
|
||||
crcbyte2 = (r1 << 4) | (r1 >> 4);
|
||||
crcbyte2 = (crcbyte2 & 0x0F ) ^ crcbyte1;
|
||||
crcbyte1 = r1;
|
||||
r1 = (r1 << 3) | (r1 >> 5);
|
||||
crcbyte2 = crcbyte2 ^ (r1 & 0xF8);
|
||||
crcbyte1 = crcbyte1 ^ (r1 & 0x07);
|
||||
}
|
||||
|
||||
return ~((crcbyte1 << 8) | crcbyte2);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef __CATS_CRC_H
|
||||
#define __CATS_CRC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
size_t cats_append_crc(uint8_t *data, size_t len);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,45 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#include "interleaver.h"
|
||||
|
||||
static inline bool get_bit(uint8_t *arr, size_t i);
|
||||
static inline void set_bit(uint8_t *arr, size_t i, bool value);
|
||||
|
||||
void cats_interleave(uint8_t *dest, uint8_t *src, size_t len)
|
||||
{
|
||||
size_t bit_len = len * 8;
|
||||
|
||||
size_t out_i = 0;
|
||||
for(int i = 0; i < 32; i++) {
|
||||
for(int j = 0; j < bit_len; j += 32) {
|
||||
if(i + j >= bit_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
set_bit(dest, out_i, get_bit(src, i + j));
|
||||
out_i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool get_bit(uint8_t *arr, size_t i)
|
||||
{
|
||||
size_t byte_idx = i / 8;
|
||||
int bit_idx = 7 - (i % 8);
|
||||
|
||||
return (arr[byte_idx] >> bit_idx) & 1;
|
||||
}
|
||||
|
||||
|
||||
static inline void set_bit(uint8_t *arr, size_t i, bool value)
|
||||
{
|
||||
size_t byte_idx = i / 8;
|
||||
int bit_idx = 7 - (i % 8);
|
||||
|
||||
if(value) {
|
||||
arr[byte_idx] |= 1 << bit_idx;
|
||||
}
|
||||
else {
|
||||
arr[byte_idx] &= ~(1 << bit_idx);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef __CATS_INTERLEAVER_H
|
||||
#define __CATS_INTERLEAVER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
void cats_interleave(uint8_t *dest, uint8_t *src, size_t len);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,100 @@
|
|||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ldpc.h"
|
||||
#include "ldpc_matrices.h"
|
||||
|
||||
#define GET_BIT(byte, bit) (((byte) & (1 << (7-(bit)))) != 0)
|
||||
#define SET_BIT(byte, bit) (byte |= 1 << 7-bit)
|
||||
#define CLEAR_BIT(byte, bit) (byte &= ~(1 << 7-bit))
|
||||
#define FLIP_BIT(byte, bit) (byte ^= (1 << (7 - bit)))
|
||||
|
||||
cats_ldpc_code_t *cats_ldpc_pick_code(int len)
|
||||
{
|
||||
if(len >= 128) {
|
||||
return &tm2048;
|
||||
}
|
||||
else if(len >= 32) {
|
||||
return &tc512;
|
||||
}
|
||||
else if(len >= 16) {
|
||||
return &tc256;
|
||||
}
|
||||
else {
|
||||
return &tc128;
|
||||
}
|
||||
}
|
||||
|
||||
// For more information on this, see the CATS standard
|
||||
// https://gitlab.scd31.com/cats/cats-standard
|
||||
// Section 5.2
|
||||
// returns # of bytes written to parity_out
|
||||
size_t cats_ldpc_encode_chunk(uint8_t *data, cats_ldpc_code_t *code, uint8_t *parity_out)
|
||||
{
|
||||
// Code parameters
|
||||
int data_length_bits = code->data_length_bits;
|
||||
int parity_length_bits = code->code_length_bits - code->data_length_bits;
|
||||
int circ_size = code->circulant_size;
|
||||
const uint64_t* gc = code->matrix;
|
||||
int row_len = parity_length_bits / 64;
|
||||
|
||||
memset(parity_out, 0x00, parity_length_bits / 8);
|
||||
|
||||
for (int offset = 0; offset < circ_size; offset++) {
|
||||
for (int crow = 0; crow < data_length_bits / circ_size; crow++) {
|
||||
int bit = crow * circ_size + offset;
|
||||
if(GET_BIT(data[bit / 8], bit % 8)) {
|
||||
for (int idx = 0; idx < row_len; idx++) {
|
||||
uint64_t circ = gc[(crow * row_len) + idx];
|
||||
for(int j = 0; j < 8; j++) {
|
||||
parity_out[idx*8 + j] ^= (uint8_t)(circ >> ((7 - j) * 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int block = 0; block < parity_length_bits / circ_size; block++) {
|
||||
uint8_t* parityblock = &parity_out[block * circ_size / 8];
|
||||
uint8_t carry = parityblock[0] >> 7;
|
||||
for (int x = (circ_size / 8)-1; x >= 0; x--) {
|
||||
uint8_t c = parityblock[x] >> 7;
|
||||
parityblock[x] = (parityblock[x] << 1) | carry;
|
||||
carry = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parity_length_bits / 8;
|
||||
}
|
||||
|
||||
size_t cats_ldpc_encode(uint8_t *data, size_t len)
|
||||
{
|
||||
// Didn't implement the 512-byte LDPC variant - unnecessary and uses a lot of space
|
||||
// This means we can only encode up to 511 bytes
|
||||
assert(len < 512);
|
||||
|
||||
// A 128 byte array is needed to support CATS packets up to 511 bytes.
|
||||
uint8_t parity[128];
|
||||
|
||||
// Split data into blocks and encode each block
|
||||
int i = 0;
|
||||
while(i < len) {
|
||||
cats_ldpc_code_t *code = cats_ldpc_pick_code(len - i);
|
||||
int data_length_bits = code->data_length_bits;
|
||||
int data_length = data_length_bits / 8;
|
||||
|
||||
uint8_t chunk[code->code_length_bits / 8];
|
||||
memset(chunk, 0xAA, data_length);
|
||||
memcpy(chunk, data + i, (len-i < data_length) ? (len - i) : data_length);
|
||||
|
||||
size_t parity_len = cats_ldpc_encode_chunk(chunk, code, parity);
|
||||
memcpy(data + len + i, parity, parity_len); // Parity
|
||||
|
||||
i += parity_len;
|
||||
}
|
||||
|
||||
int new_len = (len*2) + (i-len) + 2; // (Data+Parity) + (Padded parity) + length
|
||||
data[new_len - 2] = len;
|
||||
data[new_len - 1] = len >> 8;
|
||||
return new_len;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef __CATS_LDPC_H
|
||||
#define __CATS_LDPC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
size_t cats_ldpc_encode(uint8_t *data, size_t len);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,95 @@
|
|||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "ldpc_matrices.h"
|
||||
|
||||
cats_ldpc_code_t tc128 = {
|
||||
.code_length_bits = 128,
|
||||
.data_length_bits = 64,
|
||||
.punctured_bits = 0,
|
||||
.bf_working_len = 128 + 0,
|
||||
.circulant_size = 128/8,
|
||||
.matrix = tc128_matrix,
|
||||
.matrix_len = 4
|
||||
};
|
||||
|
||||
cats_ldpc_code_t tc256 = {
|
||||
.code_length_bits = 256,
|
||||
.data_length_bits = 128,
|
||||
.punctured_bits = 0,
|
||||
.bf_working_len = 256 + 0,
|
||||
.circulant_size = 256/8,
|
||||
.matrix = tc256_matrix,
|
||||
.matrix_len = 8
|
||||
};
|
||||
|
||||
cats_ldpc_code_t tc512 = {
|
||||
.code_length_bits = 512,
|
||||
.data_length_bits = 256,
|
||||
.punctured_bits = 0,
|
||||
.bf_working_len = 512 + 0,
|
||||
.circulant_size = 512/8,
|
||||
.matrix = tc512_matrix,
|
||||
.matrix_len = 16
|
||||
};
|
||||
|
||||
cats_ldpc_code_t tm2048 = {
|
||||
.code_length_bits = 2048,
|
||||
.data_length_bits = 1024,
|
||||
.punctured_bits = 512,
|
||||
.bf_working_len = 2048 + 512,
|
||||
.circulant_size = 512/4,
|
||||
.matrix = tm2048_matrix,
|
||||
.matrix_len = 128
|
||||
};
|
||||
|
||||
const uint64_t tc128_matrix[] = {
|
||||
0x0E69166BEF4C0BC2, 0x7766137EBB248418, 0xC480FEB9CD53A713, 0x4EAA22FA465EEA11
|
||||
};
|
||||
|
||||
const uint64_t tc256_matrix[] = {
|
||||
0x73F5E8390220CE51, 0x36ED68E9F39EB162, 0xBAC812C0BCD24379, 0x4786D9285A09095C,
|
||||
0x7DF83F76A5FF4C38, 0x8E6C0D4E025EB712, 0xBAA37B3260CB31C5, 0xD0F66A31FAF511BC
|
||||
};
|
||||
|
||||
const uint64_t tc512_matrix[] = {
|
||||
0x1D21794A22761FAE, 0x59945014257E130D, 0x74D6054003794014, 0x2DADEB9CA25EF12E,
|
||||
0x60E0B6623C5CE512, 0x4D2C81ECC7F469AB, 0x20678DBFB7523ECE, 0x2B54B906A9DBE98C,
|
||||
0xF6739BCF54273E77, 0x167BDA120C6C4774, 0x4C071EFF5E32A759, 0x3138670C095C39B5,
|
||||
0x28706BD045300258, 0x2DAB85F05B9201D0, 0x8DFDEE2D9D84CA88, 0xB371FAE63A4EB07E
|
||||
};
|
||||
|
||||
const uint64_t tm2048_matrix[] = {
|
||||
0xCFA794F49FA5A0D8, 0x8BB31D8FCA7EA8BB, 0xA7AE7EE8A68580E3, 0xE922F9E13359B284,
|
||||
0x91F72AE8F2D6BF78, 0x30A1F83B3CDBD463, 0xCE95C0EC1F609370, 0xD7E791C870229C1E,
|
||||
0x71EF3FDF60E28784, 0x78934DB285DEC9DC, 0x0E95C103008B6BCD, 0xD2DAF85CAE732210,
|
||||
0x8326EE83C1FBA56F, 0xDD15B2DDB31FE7F2, 0x3BA0BB43F83C67BD, 0xA1F6AEE46AEF4E62,
|
||||
0x565083780CA89ACA, 0xA70CCFB4A888AE35, 0x1210FAD0EC9602CC, 0x8C96B0A86D3996A3,
|
||||
0xC0B07FDDA73454C2, 0x5295F72BD5004E80, 0xACCF973FC30261C9, 0x90525AA0CBA006BD,
|
||||
0x9F079F09A405F7F8, 0x7AD98429096F2A7E, 0xEB8C9B13B84C06E4, 0x2843A47689A9C528,
|
||||
0xDAAA1A175F598DCF, 0xDBAD426CA43AD479, 0x1BA78326E75F38EB, 0x6ED09A45303A6425,
|
||||
0x48F42033B7B9A051, 0x49DC839C90291E98, 0x9B2CEBE50A7C2C26, 0x4FC6E7D674063589,
|
||||
0xF5B6DEAEBF72106B, 0xA9E6676564C17134, 0x6D5954558D235191, 0x50AAF88D7008E634,
|
||||
0x1FA962FBAB864A5F, 0x867C9D6CF4E087AA, 0x5D7AA674BA4B1D8C, 0xD7AE9186F1D3B23B,
|
||||
0x047F112791EE97B6, 0x3FB7B58FF3B94E95, 0x93BE39A6365C66B8, 0x77AD316965A72F5B,
|
||||
0x1B58F88E49C00DC6, 0xB35855BFF228A088, 0x5C8ED47B61EEC66B, 0x5004FB6E65CBECF3,
|
||||
0x77789998FE80925E, 0x0237F570E04C5F5B, 0xED677661EB7FC382, 0x5AB5D5D968C0808C,
|
||||
0x2BDB828B19593F41, 0x671B8D0D41DF136C, 0xCB47553C9B3F0EA0, 0x16CC1554C35E6A7D,
|
||||
0x97587FEA91D2098E, 0x126EA73CC78658A6, 0xADE19711208186CA, 0x95C7417A15690C45,
|
||||
0xBE9C169D889339D9, 0x654C976A85CFD9F7, 0x47C4148E3B4712DA, 0xA3BAD1AD71873D3A,
|
||||
0x1CD630C342C5EBB9, 0x183ADE9BEF294E8E, 0x7014C077A5F96F75, 0xBE566C866964D01C,
|
||||
0xE72AC43A35AD2166, 0x72EBB3259B77F9BB, 0x18DA8B09194FA1F0, 0xE876A080C9D6A39F,
|
||||
0x809B168A3D88E8E9, 0x3D995CE5232C2DC2, 0xC7CFA44A363F628A, 0x668D46C398CAF96F,
|
||||
0xD57DBB24AE27ACA1, 0x716F8EA1B8AA1086, 0x7B7796F4A86F1FD5, 0x4C7576AD01C68953,
|
||||
0xE75BE79902448236, 0x8F069658F7AAAFB0, 0x975F3AF795E78D25, 0x5871C71B4F4B77F6,
|
||||
0x65CD9C359BB2A82D, 0x5353E007166BDD41, 0x2C5447314DB027B1, 0x0B130071AD0398D1,
|
||||
0xDE19BC7A6BBCF6A0, 0xFF021AABF12920A5, 0x58BAED484AF89E29, 0xD4DBC170CEF1D369,
|
||||
0x4C330B2D11E15B5C, 0xB3815E09605338A6, 0x75E3D1A3541E0E28, 0x4F6556D68D3C8A9E,
|
||||
0xE5BB3B297DB62CD2, 0x907F09996967A0F4, 0xFF33AEEE2C8A4A52, 0xFCCF5C39D355C39C,
|
||||
0x5FE5F09ABA6BCCE0, 0x2A73401E5F87EAC2, 0xD75702F4F57670DF, 0xA70B1C002F523EEA,
|
||||
0x6CE1CE2E05D420CB, 0x867EC0166B8E53A9, 0x9DF9801A1C33058D, 0xD116A0AE7278BBB9,
|
||||
0x4CF0B0C792DD8FDB, 0x3ECEAE6F2B7F663D, 0x106A1C296E47C14C, 0x1498B045D57DEFB5,
|
||||
0x968F6D8C790263C3, 0x53CF307EF90C1F21, 0x66E6B632F6614E58, 0x267EF096C37718A3,
|
||||
0x3D46E5D10E993EB6, 0xDF81518F885EDA1B, 0x6FF518FD48BB8E9D, 0xDBED4AC0F4F5EB89,
|
||||
0xBCC64D21A65DB379, 0xABE2E4DC21F109FF, 0x2EC0CE7B5D40973D, 0x13ECF713B01C6F10
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef __CATS_LDPC_MATRICES_H
|
||||
#define __CATS_LDPC_MATRICES_H
|
||||
|
||||
typedef struct cats_ldpc_code {
|
||||
// Code length in bits (data+parity)
|
||||
int code_length_bits;
|
||||
// Data length in bits
|
||||
int data_length_bits;
|
||||
int punctured_bits;
|
||||
int bf_working_len;
|
||||
size_t circulant_size;
|
||||
size_t matrix_len;
|
||||
const uint64_t* matrix;
|
||||
} cats_ldpc_code_t;
|
||||
|
||||
extern cats_ldpc_code_t tc128;
|
||||
extern cats_ldpc_code_t tc256;
|
||||
extern cats_ldpc_code_t tc512;
|
||||
extern cats_ldpc_code_t tm2048;
|
||||
|
||||
extern const uint64_t tc128_matrix[];
|
||||
extern const uint64_t tc256_matrix[];
|
||||
extern const uint64_t tc512_matrix[];
|
||||
extern const uint64_t tm2048_matrix[];
|
||||
|
||||
#endif
|
|
@ -0,0 +1,151 @@
|
|||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "whisker.h"
|
||||
|
||||
// Hardware IDs defined here: https://cats.radio/pages/hardware-ids.html
|
||||
// It's just a way to tell where CATS packets come from. It doesn't affect anything beyond this
|
||||
#ifdef RS41
|
||||
#define HARDWARE_ID 0x7d00
|
||||
#endif
|
||||
#ifdef DFM17
|
||||
#define HARDWARE_ID 0x7d01
|
||||
#endif
|
||||
|
||||
#define CATS_IDENT_TYPE 0x00
|
||||
#define CATS_GPS_TYPE 0x02
|
||||
#define CATS_COMMENT_TYPE 0x03
|
||||
#define CATS_NODE_INFO_TYPE 0x09
|
||||
|
||||
#define CATS_NODE_INFO_HARDWARE_ID_PRESENT 1
|
||||
#define CATS_NODE_INFO_SOFTWARE_ID_PRESENT 2
|
||||
#define CATS_NODE_INFO_ANTENNA_GAIN_PRESENT 16
|
||||
#define CATS_NODE_INFO_TX_POWER_PRESENT 32
|
||||
#define CATS_NODE_INFO_VOLTAGE_PRESENT 64
|
||||
#define CATS_NODE_INFO_TX_TEMP_PRESENT 128
|
||||
|
||||
// corresponds to (21 / 4 = 5.25) dBi antenna gain
|
||||
#define CATS_ANTENNA_GAIN 21
|
||||
|
||||
// Software IDs are basically just a version number.
|
||||
// Can increment this whenever something big changes, and then
|
||||
// you can track what version of your software is being used on CATS
|
||||
#define SOFTWARE_ID 0x00
|
||||
|
||||
static inline void cats_push_f16(cats_packet *p, float val)
|
||||
{
|
||||
union {
|
||||
float f;
|
||||
uint32_t u;
|
||||
} fu = { .f = val };
|
||||
|
||||
assert(sizeof fu.f == sizeof fu.u);
|
||||
|
||||
uint32_t f32 = fu.u;
|
||||
uint32_t sign = (f32 >> 16) & 0x8000;
|
||||
uint32_t exponent = ((f32 >> 23) - 127 + 15) & 0x1F;
|
||||
uint32_t mantissa = f32 & 0x007FFFFF;
|
||||
uint32_t f16 = sign | (exponent << 10) | (mantissa >> 13);
|
||||
|
||||
p->data[p->len++] = f16;
|
||||
p->data[p->len++] = f16 >> 8;
|
||||
}
|
||||
|
||||
static inline void cats_push_f32(cats_packet *p, float val)
|
||||
{
|
||||
union {
|
||||
float f;
|
||||
uint32_t u;
|
||||
} fu = { .f = val };
|
||||
|
||||
assert(sizeof fu.f == sizeof fu.u);
|
||||
|
||||
uint32_t f32 = fu.u;
|
||||
p->data[p->len++] = f32;
|
||||
p->data[p->len++] = f32 >> 8;
|
||||
p->data[p->len++] = f32 >> 16;
|
||||
p->data[p->len++] = f32 >> 24;
|
||||
}
|
||||
|
||||
static inline void cats_push_u16(cats_packet *p, uint16_t val)
|
||||
{
|
||||
for(int i = 0; i < 2; i++) {
|
||||
p->data[p->len++] = val >> (i * 8);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void cats_push_u32(cats_packet *p, uint32_t val)
|
||||
{
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
p->data[p->len++] = val >> (i * 8);
|
||||
}
|
||||
}
|
||||
|
||||
void cats_append_identification_whisker(cats_packet *packet, char *callsign, uint8_t ssid, uint16_t icon)
|
||||
{
|
||||
packet->data[packet->len++] = CATS_IDENT_TYPE;
|
||||
size_t callsign_len = strlen(callsign);
|
||||
packet->data[packet->len++] = callsign_len + 3; // len
|
||||
packet->data[packet->len++] = icon & 0xFF; // icon
|
||||
packet->data[packet->len++] = (icon >> 8) & 0xFF; // icon
|
||||
memcpy(packet->data + packet->len, callsign, callsign_len); //callsign
|
||||
packet->len += callsign_len;
|
||||
packet->data[packet->len++] = ssid; // ssid
|
||||
}
|
||||
|
||||
void cats_append_gps_whisker(cats_packet *packet, gps_data gps)
|
||||
{
|
||||
packet->data[packet->len++] = CATS_GPS_TYPE;
|
||||
packet->data[packet->len++] = 14; // len
|
||||
int32_t lat_converted = gps.latitude_degrees_1000000 * (1LL<<31) / 90LL / 10000000LL;
|
||||
int32_t lon_converted = gps.longitude_degrees_1000000 * (1LL<<31) / 180LL / 10000000LL;
|
||||
cats_push_u32(packet, lat_converted); // lat
|
||||
cats_push_u32(packet, lon_converted); // lon
|
||||
cats_push_f16(packet, gps.altitude_mm / 1000.0); // alt
|
||||
packet->data[packet->len++] = 0; // max error = 0m
|
||||
packet->data[packet->len++] = gps.heading_degrees_100000 / 100000.0 * 128.0 / 180.0; // heading
|
||||
cats_push_f16(packet, gps.ground_speed_cm_per_second / 100.0); // horizontal speed
|
||||
}
|
||||
|
||||
void cats_append_comment_whisker(cats_packet *packet, char *comment)
|
||||
{
|
||||
packet->data[packet->len++] = CATS_COMMENT_TYPE;
|
||||
size_t comment_len = strlen(comment);
|
||||
assert(comment_len <= 255);
|
||||
packet->data[packet->len++] = comment_len;
|
||||
memcpy(packet->data + packet->len, comment, comment_len);
|
||||
packet->len += comment_len;
|
||||
}
|
||||
|
||||
void cats_append_node_info_whisker(cats_packet *packet, telemetry_data *data)
|
||||
{
|
||||
uint8_t *d = packet->data;
|
||||
size_t *l = &packet->len;
|
||||
|
||||
bool has_altitude = GPS_HAS_FIX(data->gps);
|
||||
float altitude = data->gps.altitude_mm / 1000.0;
|
||||
|
||||
d[(*l)++] = CATS_NODE_INFO_TYPE;
|
||||
d[(*l)++] = has_altitude ? 14 : 10; // len
|
||||
// bitmask
|
||||
d[(*l)++] = 0;
|
||||
d[(*l)++] = (CATS_IS_BALLOON << 2) | (has_altitude << 1);
|
||||
d[(*l)++] =
|
||||
CATS_NODE_INFO_HARDWARE_ID_PRESENT |
|
||||
CATS_NODE_INFO_SOFTWARE_ID_PRESENT |
|
||||
CATS_NODE_INFO_ANTENNA_GAIN_PRESENT |
|
||||
CATS_NODE_INFO_TX_POWER_PRESENT |
|
||||
CATS_NODE_INFO_VOLTAGE_PRESENT |
|
||||
CATS_NODE_INFO_TX_TEMP_PRESENT;
|
||||
|
||||
cats_push_u16(packet, HARDWARE_ID);
|
||||
d[(*l)++] = SOFTWARE_ID;
|
||||
d[(*l)++] = CATS_ANTENNA_GAIN; // vertical antenna -> ~5.25 dBi gain
|
||||
d[(*l)++] = CATS_REPORTED_TX_POWER_DBM * 4.0; // TX power
|
||||
d[(*l)++] = data->battery_voltage_millivolts / 100; // voltage
|
||||
d[(*l)++] = data->internal_temperature_celsius_100 / 100; // transmitter temperature
|
||||
if(has_altitude) {
|
||||
cats_push_f32(packet, altitude);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef __WHISKER_H
|
||||
#define __WHISKER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cats.h"
|
||||
#include "../../gps.h"
|
||||
#include "../../telemetry.h"
|
||||
|
||||
void cats_append_identification_whisker(cats_packet *packet, char *callsign, uint8_t ssid, uint16_t icon);
|
||||
void cats_append_gps_whisker(cats_packet *packet, gps_data gps);
|
||||
void cats_append_comment_whisker(cats_packet *packet, char *message);
|
||||
void cats_append_node_info_whisker(cats_packet *packet, telemetry_data *data);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,36 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#include "whiten.h"
|
||||
|
||||
uint8_t lfsr_byte(uint16_t *state);
|
||||
void lfsr(uint16_t *state);
|
||||
|
||||
void cats_whiten(uint8_t *data, uint8_t len)
|
||||
{
|
||||
uint16_t state = 0xE9CF;
|
||||
|
||||
for(int i = 0; i < len; i++) {
|
||||
uint8_t b = lfsr_byte(&state);
|
||||
data[i] ^= b;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t lfsr_byte(uint16_t *state)
|
||||
{
|
||||
uint8_t out = 0;
|
||||
for(int i = 7; i >= 0; i--) {
|
||||
out |= (*state & 1) << i;
|
||||
lfsr(state);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void lfsr(uint16_t *state)
|
||||
{
|
||||
bool lsb = *state & 1;
|
||||
*state >>= 1;
|
||||
if(lsb) {
|
||||
*state ^= 0xB400;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef __CATS_WHITEN_H
|
||||
#define __CATS_WHITEN_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void cats_whiten(uint8_t *data, uint8_t len);
|
||||
|
||||
#endif
|
|
@ -38,6 +38,18 @@ typedef struct _fsk_encoder_api {
|
|||
*/
|
||||
uint32_t (*get_symbol_delay)(fsk_encoder *encoder);
|
||||
|
||||
/**
|
||||
* @param encoder
|
||||
* @return Pointer to packet data
|
||||
*/
|
||||
uint8_t *(*get_data)(fsk_encoder *encoder);
|
||||
|
||||
/**
|
||||
* @param encoder
|
||||
* @return Length of packet data
|
||||
*/
|
||||
uint16_t (*get_data_len)(fsk_encoder *encoder);
|
||||
|
||||
void (*set_data)(fsk_encoder *encoder, uint16_t data_length, uint8_t *data);
|
||||
|
||||
int8_t (*next_tone)(fsk_encoder *encoder);
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "raw.h"
|
||||
|
||||
typedef struct _raw_encoder {
|
||||
uint8_t *data;
|
||||
uint16_t len;
|
||||
} raw_encoder;
|
||||
|
||||
void raw_encoder_new(fsk_encoder *encoder)
|
||||
{
|
||||
encoder->priv = malloc(sizeof(raw_encoder));
|
||||
}
|
||||
|
||||
void raw_encoder_destroy(fsk_encoder *encoder)
|
||||
{
|
||||
if (encoder->priv != NULL) {
|
||||
free(encoder->priv);
|
||||
encoder->priv = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void raw_encoder_set_data(fsk_encoder *encoder, uint16_t data_length, uint8_t *data)
|
||||
{
|
||||
raw_encoder *raw = (raw_encoder *) encoder->priv;
|
||||
raw->data = data;
|
||||
raw->len = data_length;
|
||||
}
|
||||
|
||||
uint8_t *raw_encoder_get_data(fsk_encoder *encoder)
|
||||
{
|
||||
raw_encoder *raw = (raw_encoder *) encoder->priv;
|
||||
return raw->data;
|
||||
}
|
||||
|
||||
uint16_t raw_encoder_get_data_len(fsk_encoder *encoder)
|
||||
{
|
||||
raw_encoder *raw = (raw_encoder *) encoder->priv;
|
||||
return raw->len;
|
||||
}
|
||||
|
||||
fsk_encoder_api raw_fsk_encoder_api = {
|
||||
.set_data = raw_encoder_set_data,
|
||||
.get_data = raw_encoder_get_data,
|
||||
.get_data_len = raw_encoder_get_data_len,
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef __RAW_H
|
||||
#define __RAW_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "codecs/fsk/fsk.h"
|
||||
|
||||
void raw_encoder_new(fsk_encoder *encoder);
|
||||
void raw_encoder_destroy(fsk_encoder *encoder);
|
||||
|
||||
extern fsk_encoder_api raw_fsk_encoder_api;
|
||||
|
||||
#endif
|
|
@ -90,6 +90,11 @@ char *aprs_comment_templates[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
char *cats_comment_templates[] = {
|
||||
CATS_COMMENT,
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
* FSQ mode comment message templates.
|
||||
* Maximum length: 130 characters.
|
||||
|
|
28
src/config.h
28
src/config.h
|
@ -188,6 +188,8 @@
|
|||
#define RADIO_SI4063_TX_HORUS_V1_COUNT 1
|
||||
#define RADIO_SI4063_TX_HORUS_V2 true
|
||||
#define RADIO_SI4063_TX_HORUS_V2_COUNT 6
|
||||
#define RADIO_SI4063_TX_CATS false
|
||||
#define RADIO_SI4063_TX_CATS_COUNT 1
|
||||
|
||||
// Continuous transmit mode can be enabled for *either* Horus V1 or V2, but not both. This disables all other transmission modes.
|
||||
// The continuous mode transmits Horus 4FSK preamble between transmissions
|
||||
|
@ -202,6 +204,7 @@
|
|||
// Use a frequency offset to place FSK tones slightly above the defined frequency for SSB reception
|
||||
#define RADIO_SI4063_TX_FREQUENCY_HORUS_V1 432501000
|
||||
#define RADIO_SI4063_TX_FREQUENCY_HORUS_V2 432501000
|
||||
#define RADIO_SI4063_TX_FREQUENCY_CATS 430500000
|
||||
|
||||
/**
|
||||
* RS41 only: External Si5351 radio chip transmission configuration
|
||||
|
@ -335,6 +338,31 @@
|
|||
// Delay transmission for an N second offset, counting from the scheduled time set with TIME_SYNC_SECONDS.
|
||||
#define HORUS_V2_TIME_SYNC_OFFSET_SECONDS 0
|
||||
|
||||
/**
|
||||
* CATS mode settings
|
||||
*/
|
||||
// CATS is a new digital mode, vaguely meant to be a better APRS (but also much more powerful)
|
||||
// While it offers a number of advantages, probably the best one for balloons is the rapid beacon rate.
|
||||
// The protocol is meant to allow for a much higher channel capacity, so beaconing every second is totally fine.
|
||||
// For more information, see here: https://cats.radio/
|
||||
#define CATS_CALLSIGN CALLSIGN
|
||||
#define CATS_SSID 29 // 0 - 255
|
||||
// Balloon. See the CATS standard for more options
|
||||
// https://gitlab.scd31.com/cats/cats-standard/-/blob/master/standard.pdf
|
||||
#define CATS_ICON 13
|
||||
#define CATS_COMMENT "I am a radiosonde. Hear me meow!"
|
||||
#define CATS_REPORTED_TX_POWER_DBM 20
|
||||
// You probably want this to be true
|
||||
// Set to false if you're using your radiosonde for something other than a balloon payload
|
||||
// We don't want non-balloons showing up as balloons on FELINET!
|
||||
#define CATS_IS_BALLOON true
|
||||
|
||||
// Schedule transmission every N seconds, counting from beginning of an hour (based on GPS time). Set to zero to disable time sync.
|
||||
// See the README file for more detailed documentation about time sync and its offset setting
|
||||
#define CATS_TIME_SYNC_SECONDS 0
|
||||
// Delay transmission for an N second offset, counting from the scheduled time set with TIME_SYNC_SECONDS.
|
||||
#define CATS_TIME_SYNC_OFFSET_SECONDS 0
|
||||
|
||||
/**
|
||||
* CW settings
|
||||
*/
|
||||
|
|
|
@ -30,6 +30,7 @@ extern volatile bool system_initialized;
|
|||
extern char *cw_message_templates[];
|
||||
extern char *pip_message_templates[];
|
||||
extern char *aprs_comment_templates[];
|
||||
extern char *cats_comment_templates[];
|
||||
extern char *fsq_comment_templates[];
|
||||
extern char *ftjt_message_templates[];
|
||||
|
||||
|
|
|
@ -37,14 +37,19 @@
|
|||
#define SI4063_COMMAND_POWER_UP 0x02
|
||||
#define SI4063_COMMAND_SET_PROPERTY 0x11
|
||||
#define SI4063_COMMAND_GPIO_PIN_CFG 0x13
|
||||
#define SI4063_COMMAND_FIFO_INFO 0x15
|
||||
#define SI4063_COMMAND_GET_INT_STATUS 0x20
|
||||
#define SI4063_COMMAND_START_TX 0x31
|
||||
#define SI4063_COMMAND_REQUEST_DEVICE_STATE 0x33
|
||||
#define SI4063_COMMAND_CHANGE_STATE 0x34
|
||||
#define SI4063_COMMAND_GET_ADC_READING 0x14
|
||||
#define SI4063_COMMAND_READ_CMD_BUFF 0x44
|
||||
#define SI4063_COMMAND_WRITE_TX_FIFO 0x66
|
||||
|
||||
#define SI4063_STATE_SLEEP 0x01
|
||||
#define SI4063_STATE_SPI_ACTIVE 0x02
|
||||
#define SI4063_STATE_READY 0x03
|
||||
#define SI4063_STATE_READY2 0x04
|
||||
#define SI4063_STATE_TX_TUNE 0x05
|
||||
#define SI4063_STATE_TX 0x07
|
||||
|
||||
|
@ -218,10 +223,91 @@ void si4063_disable_tx()
|
|||
si4063_set_state(SI4063_STATE_SLEEP);
|
||||
}
|
||||
|
||||
// Returns number of bytes sent from *data
|
||||
// If less than len, remaining bytes will need to be used to top up the buffer
|
||||
uint16_t si4063_start_tx(uint8_t *data, int len)
|
||||
{
|
||||
// Clear fifo underflow interrupt
|
||||
si4063_fifo_underflow();
|
||||
|
||||
// Clear TX FIFO
|
||||
uint8_t fifo_clear_data[] = {1};
|
||||
si4063_send_command(SI4063_COMMAND_FIFO_INFO, 1, fifo_clear_data);
|
||||
si4063_wait_for_cts();
|
||||
|
||||
// Add our data to the TX FIFO
|
||||
int fifo_len = len;
|
||||
if(fifo_len > 64) {
|
||||
fifo_len = 64;
|
||||
}
|
||||
si4063_send_command(SI4063_COMMAND_WRITE_TX_FIFO, fifo_len, data);
|
||||
|
||||
// Start transmitting
|
||||
uint8_t tx_cmd[] = {
|
||||
0, // channel
|
||||
SI4063_STATE_SLEEP << 4,
|
||||
len >> 8,
|
||||
len & 0xFF,
|
||||
0 // delay
|
||||
};
|
||||
si4063_send_command(SI4063_COMMAND_START_TX, sizeof(tx_cmd), tx_cmd);
|
||||
si4063_wait_for_cts();
|
||||
|
||||
return fifo_len;
|
||||
}
|
||||
|
||||
// Add additional bytes to the si4063's FIFO buffer
|
||||
// Needed for large packets that don't fit in its buffer.
|
||||
// Keep refilling it while transmitting
|
||||
// Returns number of bytes taken from *data
|
||||
// If less than len, you will need to keep calling this
|
||||
uint16_t si4063_refill_buffer(uint8_t *data, int len)
|
||||
{
|
||||
uint8_t response[2] = {0, 0};
|
||||
|
||||
// Check how many bytes we have free
|
||||
si4063_send_command(SI4063_COMMAND_FIFO_INFO, 0, NULL);
|
||||
si4063_read_response(sizeof(response), response);
|
||||
|
||||
uint8_t free_space = response[1];
|
||||
if(free_space < len) {
|
||||
len = free_space;
|
||||
}
|
||||
|
||||
si4063_send_command(SI4063_COMMAND_WRITE_TX_FIFO, len, data);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Wait for our buffer to be emptied, and for the si4063 to leave TX mode
|
||||
// If timeout, we force it to sleep
|
||||
int si4063_wait_for_tx_complete(int timeout_ms)
|
||||
{
|
||||
for(int i = 0; i < timeout_ms; i++) {
|
||||
uint8_t status = 0;
|
||||
si4063_send_command(SI4063_COMMAND_REQUEST_DEVICE_STATE, 0, NULL);
|
||||
si4063_read_response(1, &status);
|
||||
|
||||
if(status == SI4063_STATE_SLEEP ||
|
||||
status == SI4063_STATE_READY ||
|
||||
status == SI4063_STATE_READY2 ||
|
||||
status == SI4063_STATE_SPI_ACTIVE) {
|
||||
return HAL_OK;
|
||||
}
|
||||
|
||||
delay_ms(1);
|
||||
}
|
||||
|
||||
si4063_disable_tx();
|
||||
si4063_wait_for_cts();
|
||||
|
||||
return HAL_ERROR_TIMEOUT;
|
||||
}
|
||||
|
||||
static int si4063_get_outdiv(const uint32_t frequency_hz)
|
||||
{
|
||||
// Select the output divider according to the recommended ranges in the Si406x datasheet
|
||||
if (frequency_hz < 177000000UL) {
|
||||
if (frequency_hz < 177000000UL) {
|
||||
return 24;
|
||||
} else if (frequency_hz < 239000000UL) {
|
||||
return 16;
|
||||
|
@ -253,6 +339,18 @@ static int si4063_get_band(const uint32_t frequency_hz)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Also clears status
|
||||
bool si4063_fifo_underflow()
|
||||
{
|
||||
uint8_t data[] = {0xFF, 0xFF, ~0x20}; // Clear underflow status
|
||||
si4063_send_command(SI4063_COMMAND_GET_INT_STATUS, sizeof(data), data);
|
||||
uint8_t response[7];
|
||||
si4063_read_response(sizeof(response), response);
|
||||
|
||||
bool fifo_underflow_pending = response[6] & 0x20;
|
||||
return fifo_underflow_pending;
|
||||
}
|
||||
|
||||
void si4063_set_tx_frequency(const uint32_t frequency_hz)
|
||||
{
|
||||
uint8_t outdiv, band;
|
||||
|
@ -307,6 +405,26 @@ void si4063_set_tx_frequency(const uint32_t frequency_hz)
|
|||
si4063_set_frequency_deviation(current_deviation_hz);
|
||||
}
|
||||
|
||||
void si4063_set_data_rate(const uint32_t rate_bps)
|
||||
{
|
||||
int rate = rate_bps * 10;
|
||||
// Set TX_NCO_MODE to our crystal frequency, as recommended by the data sheet for rates <= 200kbps
|
||||
// Set MODEM_DATA_RATE to rate_bps * 10 (will get downsampled because NCO_MODE defaults to 10x)
|
||||
uint8_t data[] = {
|
||||
0x20, // Group
|
||||
0x07, // Set 7 properties
|
||||
0x03, // Start from MODEM_DATA_RATE
|
||||
(rate >> 16) & 0xFF,
|
||||
(rate >> 8) & 0xFF,
|
||||
rate & 0xFF,
|
||||
(SI4063_CLOCK >> 24) & 0xFF,
|
||||
(SI4063_CLOCK >> 16) & 0xFF,
|
||||
(SI4063_CLOCK >> 8) & 0xFF,
|
||||
SI4063_CLOCK & 0xFF,
|
||||
};
|
||||
si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data);
|
||||
}
|
||||
|
||||
void si4063_set_tx_power(uint8_t power)
|
||||
{
|
||||
uint8_t data[] = {
|
||||
|
@ -388,6 +506,10 @@ void si4063_set_modulation_type(si4063_modulation_type type)
|
|||
// Direct Async Mode with FSK modulation
|
||||
data[3] |= 0x02;
|
||||
break;
|
||||
case SI4063_MODULATION_TYPE_FIFO_FSK:
|
||||
// FIFO with FSK modulation
|
||||
data[3] = 0x02;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
@ -469,6 +591,7 @@ void si4063_configure()
|
|||
0x03, // 0x03 = GLOBAL_CONFIG
|
||||
0x40 | // 0x40 = Reserved, needs to be set to 1
|
||||
0x20 | // 0x20 = Fast sequencer mode
|
||||
0x10 | // 129-byte FIFO
|
||||
0x00 // High-performance mode
|
||||
};
|
||||
|
||||
|
@ -535,6 +658,31 @@ void si4063_configure()
|
|||
|
||||
si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data);
|
||||
}
|
||||
|
||||
{
|
||||
/*
|
||||
2. Detailed Errata Descriptions
|
||||
2.1 If Configured to Skip Sync and Preamble on Transmit, the TX Data from the FIFO is Corrupted
|
||||
Description of Errata
|
||||
If preamble and sync word are excluded from the transmitted data (PREMABLE_TX_LENGTH = 0 and SYNC_CONFIG: SKIP_TX = 1), data from the FIFO is not transmitted correctly.
|
||||
Affected Conditions / Impacts
|
||||
Some number of missed bytes will occur at the beginning of the packet and some number of repeated bytes at the end of the packet.
|
||||
Workaround
|
||||
Set PKT_FIELD_1_CRC_CONFIG: CRC_START to 1. This will trigger the packet handler and result in transmitting the correct data,
|
||||
while still not sending a CRC unless enabled in a FIELD configuration. A fix has been identified and will be included in a future release
|
||||
*/
|
||||
|
||||
// In other words, without this, the FIFO buffer gets corrupted while TXing, because we're not using
|
||||
// the preamble/sync word stuff
|
||||
// To be clear - we're not doing any CRC stuff! This is just the recommended workaround
|
||||
uint8_t data[] = {
|
||||
0x12, // Group
|
||||
0x01, // Set 1 property
|
||||
0x10, // PKT_FIELD_1_CRC_CONFIG
|
||||
0x80, // CRC_START
|
||||
};
|
||||
si4063_send_command(SI4063_COMMAND_SET_PROPERTY, sizeof(data), data);
|
||||
}
|
||||
}
|
||||
|
||||
// Not used yet, for future use
|
||||
|
|
|
@ -8,12 +8,18 @@ typedef enum _si4063_modulation_type {
|
|||
SI4063_MODULATION_TYPE_CW = 0,
|
||||
SI4063_MODULATION_TYPE_OOK,
|
||||
SI4063_MODULATION_TYPE_FSK,
|
||||
SI4063_MODULATION_TYPE_FIFO_FSK,
|
||||
} si4063_modulation_type;
|
||||
|
||||
void si4063_enable_tx();
|
||||
void si4063_inhibit_tx();
|
||||
void si4063_disable_tx();
|
||||
uint16_t si4063_start_tx(uint8_t *data, int len);
|
||||
uint16_t si4063_refill_buffer(uint8_t *data, int len);
|
||||
int si4063_wait_for_tx_complete(int timeout_ms);
|
||||
bool si4063_fifo_underflow();
|
||||
void si4063_set_tx_frequency(uint32_t frequency_hz);
|
||||
void si4063_set_data_rate(const uint32_t rate_bps);
|
||||
void si4063_set_tx_power(uint8_t power);
|
||||
void si4063_set_frequency_offset(uint16_t offset);
|
||||
void si4063_set_frequency_deviation(uint32_t deviation);
|
||||
|
|
|
@ -27,9 +27,9 @@
|
|||
#define GPS_FIX_TIME_ONLY 5
|
||||
|
||||
#if GPS_REQUIRE_3D_FIX
|
||||
#define GPS_HAS_FIX(gps_data) (gps_data.fix_ok && (gps_data.fix == GPS_FIX_3D))
|
||||
#define GPS_HAS_FIX(gps_data) ((gps_data).fix_ok && ((gps_data).fix == GPS_FIX_3D))
|
||||
#else
|
||||
#define GPS_HAS_FIX(gps_data) (gps_data.fix_ok && (gps_data.fix == GPS_FIX_2D || gps_data.fix == GPS_FIX_3D))
|
||||
#define GPS_HAS_FIX(gps_data) ((gps_data).fix_ok && ((gps_data).fix == GPS_FIX_2D || (gps_data).fix == GPS_FIX_3D))
|
||||
#endif
|
||||
|
||||
typedef struct _gps_data {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef __DELAY_H
|
||||
#define __DELAY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
40
src/radio.c
40
src/radio.c
|
@ -10,6 +10,7 @@
|
|||
#include "codecs/morse/morse.h"
|
||||
#include "codecs/bell/bell.h"
|
||||
#include "codecs/mfsk/mfsk.h"
|
||||
#include "codecs/raw/raw.h"
|
||||
#include "codecs/jtencode/jtencode.h"
|
||||
#include "drivers/ubxg6010/ubxg6010.h"
|
||||
#include "radio_internal.h"
|
||||
|
@ -25,6 +26,7 @@
|
|||
#include "radio_payload_aprs_weather.h"
|
||||
#include "radio_payload_horus_v1.h"
|
||||
#include "radio_payload_horus_v2.h"
|
||||
#include "radio_payload_cats.h"
|
||||
#include "radio_payload_wspr.h"
|
||||
#include "radio_payload_jtencode.h"
|
||||
#include "radio_payload_fsq.h"
|
||||
|
@ -297,6 +299,20 @@ radio_transmit_entry radio_transmit_schedule[] = {
|
|||
.fsk_encoder_api = &mfsk_fsk_encoder_api,
|
||||
},
|
||||
#endif
|
||||
#if RADIO_SI4063_TX_CATS
|
||||
{
|
||||
.enabled = RADIO_SI4063_TX_CATS,
|
||||
.radio_type = RADIO_TYPE_SI4063,
|
||||
.data_mode = RADIO_DATA_MODE_CATS,
|
||||
.transmit_count = RADIO_SI4063_TX_CATS_COUNT,
|
||||
.time_sync_seconds = CATS_TIME_SYNC_SECONDS,
|
||||
.time_sync_seconds_offset = CATS_TIME_SYNC_OFFSET_SECONDS,
|
||||
.frequency = RADIO_SI4063_TX_FREQUENCY_CATS,
|
||||
.tx_power = RADIO_SI4063_TX_POWER,
|
||||
.payload_encoder = &radio_cats_payload_encoder,
|
||||
.fsk_encoder_api = &raw_fsk_encoder_api,
|
||||
},
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
@ -489,6 +505,7 @@ radio_module_state radio_shared_state = {
|
|||
.radio_dma_transfer_active = false,
|
||||
.radio_manual_transmit_active = false,
|
||||
.radio_interrupt_transmit_active = false,
|
||||
.radio_fifo_transmit_active = false,
|
||||
|
||||
.radio_current_fsk_tones = NULL,
|
||||
.radio_current_fsk_tone_count = 0,
|
||||
|
@ -619,6 +636,12 @@ static bool radio_start_transmit(radio_transmit_entry *entry)
|
|||
&entry->fsk_encoder);
|
||||
entry->fsk_encoder_api->set_data(&entry->fsk_encoder, radio_current_payload_length, radio_current_payload);
|
||||
break;
|
||||
case RADIO_DATA_MODE_CATS:
|
||||
enable_gps_during_transmit = true;
|
||||
|
||||
raw_encoder_new(&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:
|
||||
case RADIO_DATA_MODE_FT8:
|
||||
case RADIO_DATA_MODE_JT65:
|
||||
|
@ -685,12 +708,12 @@ static bool radio_start_transmit(radio_transmit_entry *entry)
|
|||
return false;
|
||||
}
|
||||
|
||||
log_info("TX start\n");
|
||||
|
||||
if (leds_enabled) {
|
||||
set_red_led(true);
|
||||
}
|
||||
|
||||
log_info("TX start\n");
|
||||
|
||||
radio_shared_state.radio_transmission_active = true;
|
||||
|
||||
return true;
|
||||
|
@ -737,6 +760,7 @@ static bool radio_stop_transmit(radio_transmit_entry *entry)
|
|||
|
||||
radio_shared_state.radio_manual_transmit_active = false;
|
||||
radio_shared_state.radio_interrupt_transmit_active = false;
|
||||
radio_shared_state.radio_fifo_transmit_active = false;
|
||||
radio_shared_state.radio_dma_transfer_active = false;
|
||||
|
||||
switch (entry->data_mode) {
|
||||
|
@ -753,6 +777,9 @@ static bool radio_stop_transmit(radio_transmit_entry *entry)
|
|||
case RADIO_DATA_MODE_HORUS_V2:
|
||||
mfsk_encoder_destroy(&entry->fsk_encoder);
|
||||
break;
|
||||
case RADIO_DATA_MODE_CATS:
|
||||
raw_encoder_destroy(&entry->fsk_encoder);
|
||||
break;
|
||||
case RADIO_DATA_MODE_WSPR:
|
||||
case RADIO_DATA_MODE_FT8:
|
||||
case RADIO_DATA_MODE_JT65:
|
||||
|
@ -954,7 +981,7 @@ void radio_handle_main_loop()
|
|||
log_info("Lat: %ld *1M, Lon: %ld *1M, Alt: %ld m\n",
|
||||
current_telemetry_data.gps.latitude_degrees_1000000 / 10,
|
||||
current_telemetry_data.gps.longitude_degrees_1000000 / 10,
|
||||
(current_telemetry_data.gps.altitude_mm / 1000) * 3280 / 1000);
|
||||
(current_telemetry_data.gps.altitude_mm / 1000));
|
||||
#endif
|
||||
|
||||
radio_reset_transmit_delay_counter();
|
||||
|
@ -988,7 +1015,9 @@ void radio_handle_main_loop()
|
|||
}
|
||||
}
|
||||
|
||||
if (radio_shared_state.radio_transmission_active && radio_shared_state.radio_transmit_next_symbol_flag) {
|
||||
if (radio_shared_state.radio_transmission_active
|
||||
&& radio_shared_state.radio_transmit_next_symbol_flag
|
||||
&& !radio_shared_state.radio_fifo_transmit_active) {
|
||||
radio_shared_state.radio_transmit_next_symbol_flag = false;
|
||||
|
||||
if (!radio_shared_state.radio_manual_transmit_active && !radio_shared_state.radio_interrupt_transmit_active) {
|
||||
|
@ -1047,6 +1076,9 @@ void radio_init()
|
|||
case RADIO_DATA_MODE_HORUS_V2:
|
||||
// No messages
|
||||
break;
|
||||
case RADIO_DATA_MODE_CATS:
|
||||
entry->messages = cats_comment_templates;
|
||||
break;
|
||||
case RADIO_DATA_MODE_FT8:
|
||||
case RADIO_DATA_MODE_JT65:
|
||||
case RADIO_DATA_MODE_JT9:
|
||||
|
|
|
@ -29,6 +29,7 @@ typedef enum _radio_data_mode {
|
|||
RADIO_DATA_MODE_FSQ_3,
|
||||
RADIO_DATA_MODE_FSQ_4_5,
|
||||
RADIO_DATA_MODE_FSQ_6,
|
||||
RADIO_DATA_MODE_CATS,
|
||||
} radio_data_mode;
|
||||
|
||||
typedef struct _radio_transmit_entry {
|
||||
|
@ -70,6 +71,7 @@ typedef struct _radio_module_state {
|
|||
volatile bool radio_dma_transfer_active;
|
||||
volatile bool radio_manual_transmit_active;
|
||||
volatile bool radio_interrupt_transmit_active;
|
||||
volatile bool radio_fifo_transmit_active;
|
||||
|
||||
fsk_tone *radio_current_fsk_tones;
|
||||
int8_t radio_current_fsk_tone_count;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "telemetry.h"
|
||||
#include "payload.h"
|
||||
#include "log.h"
|
||||
#include "codecs/cats/cats.h"
|
||||
#include "codecs/cats/whisker.h"
|
||||
|
||||
#define CATS_PREAMBLE_BYTE 0x55
|
||||
#define CATS_PREAMBLE_LENGTH 4
|
||||
#define CATS_SYNC_WORD 0xABCDEF12
|
||||
#define CATS_SYNC_WORD_LENGTH 4
|
||||
|
||||
uint16_t radio_cats_encode(uint8_t *payload, uint16_t length, telemetry_data *telemetry_data, char *message)
|
||||
{
|
||||
uint8_t *cur = payload;
|
||||
|
||||
// preamble
|
||||
for(int i = 0; i < CATS_PREAMBLE_LENGTH; i++) {
|
||||
*(cur++) = CATS_PREAMBLE_BYTE;
|
||||
}
|
||||
|
||||
// sync word
|
||||
for(int i = CATS_SYNC_WORD_LENGTH - 1; i >= 0; i--) {
|
||||
*(cur++) = (CATS_SYNC_WORD >> (i * 8));
|
||||
}
|
||||
|
||||
uint8_t *data = malloc(length);
|
||||
cats_packet packet = cats_create(data);
|
||||
cats_append_identification_whisker(&packet, CATS_CALLSIGN, CATS_SSID, CATS_ICON);
|
||||
cats_append_comment_whisker(&packet, message);
|
||||
|
||||
if(GPS_HAS_FIX(telemetry_data->gps) &&
|
||||
(telemetry_data->gps.latitude_degrees_1000000 != 0 ||
|
||||
telemetry_data->gps.longitude_degrees_1000000 != 0)) {
|
||||
cats_append_gps_whisker(&packet, telemetry_data->gps);
|
||||
}
|
||||
|
||||
cats_append_node_info_whisker(&packet, telemetry_data);
|
||||
|
||||
int len = cats_fully_encode(packet, cur);
|
||||
free(data);
|
||||
|
||||
return (uint16_t)(CATS_PREAMBLE_LENGTH + CATS_SYNC_WORD_LENGTH + len);
|
||||
}
|
||||
|
||||
payload_encoder radio_cats_payload_encoder = {
|
||||
.encode = radio_cats_encode,
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef __RADIO_PAYLOAD_CATS_H
|
||||
#define __RADIO_PAYLOAD_CATS_H
|
||||
|
||||
#include "payload.h"
|
||||
|
||||
extern payload_encoder radio_cats_payload_encoder;
|
||||
|
||||
#endif
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#define SI4063_DEVIATION_HZ_RTTY 200.0
|
||||
#define SI4063_DEVIATION_HZ_APRS 2600.0
|
||||
#define SI4063_DEVIATION_HZ_CATS 4800.0
|
||||
|
||||
#define CW_SYMBOL_RATE_MULTIPLIER 4
|
||||
|
||||
|
@ -28,8 +29,10 @@ bool radio_start_transmit_si4063(radio_transmit_entry *entry, radio_module_state
|
|||
{
|
||||
uint16_t frequency_offset;
|
||||
uint32_t frequency_deviation = 0;
|
||||
uint32_t data_rate = 0;
|
||||
si4063_modulation_type modulation_type;
|
||||
bool use_direct_mode;
|
||||
bool use_fifo_mode = false;
|
||||
|
||||
switch (entry->data_mode) {
|
||||
case RADIO_DATA_MODE_CW:
|
||||
|
@ -62,6 +65,14 @@ bool radio_start_transmit_si4063(radio_transmit_entry *entry, radio_module_state
|
|||
data_timer_init(entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder));
|
||||
break;
|
||||
}
|
||||
case RADIO_DATA_MODE_CATS:
|
||||
frequency_offset = 0;
|
||||
frequency_deviation = SI4063_DEVIATION_HZ_CATS;
|
||||
modulation_type = SI4063_MODULATION_TYPE_FIFO_FSK;
|
||||
use_direct_mode = false;
|
||||
use_fifo_mode = true;
|
||||
data_rate = 9600;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -72,7 +83,12 @@ bool radio_start_transmit_si4063(radio_transmit_entry *entry, radio_module_state
|
|||
si4063_set_modulation_type(modulation_type);
|
||||
si4063_set_frequency_deviation(frequency_deviation);
|
||||
|
||||
si4063_enable_tx();
|
||||
if (use_fifo_mode) {
|
||||
si4063_set_data_rate(data_rate);
|
||||
}
|
||||
else {
|
||||
si4063_enable_tx();
|
||||
}
|
||||
|
||||
if (use_direct_mode) {
|
||||
spi_uninit();
|
||||
|
@ -96,6 +112,9 @@ bool radio_start_transmit_si4063(radio_transmit_entry *entry, radio_module_state
|
|||
system_disable_tick();
|
||||
shared_state->radio_interrupt_transmit_active = true;
|
||||
break;
|
||||
case RADIO_DATA_MODE_CATS:
|
||||
shared_state->radio_fifo_transmit_active = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -170,6 +189,41 @@ static void radio_handle_main_loop_manual_si4063(radio_transmit_entry *entry, ra
|
|||
system_enable_tick();
|
||||
}
|
||||
|
||||
void radio_handle_fifo_si4063(radio_transmit_entry *entry, radio_module_state *shared_state) {
|
||||
log_debug("Start FIFO TX\n");
|
||||
fsk_encoder_api *fsk_encoder_api = entry->fsk_encoder_api;
|
||||
fsk_encoder *fsk_enc = &entry->fsk_encoder;
|
||||
|
||||
uint8_t *data = fsk_encoder_api->get_data(fsk_enc);
|
||||
uint16_t len = fsk_encoder_api->get_data_len(fsk_enc);
|
||||
|
||||
uint16_t written = si4063_start_tx(data, len);
|
||||
data += written;
|
||||
len -= written;
|
||||
|
||||
while(len > 0) {
|
||||
uint16_t written = si4063_refill_buffer(data, len);
|
||||
data += written;
|
||||
len -= written;
|
||||
|
||||
if(si4063_fifo_underflow()) {
|
||||
log_info("FIFO underflow - Aborting\n");
|
||||
shared_state->radio_transmission_finished = true;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int err = si4063_wait_for_tx_complete(1000);
|
||||
if(err != HAL_OK) {
|
||||
log_info("Error waiting for tx complete: %d\n", err);
|
||||
}
|
||||
|
||||
log_debug("Finished FIFO TX\n");
|
||||
|
||||
shared_state->radio_transmission_finished = true;
|
||||
}
|
||||
|
||||
void radio_handle_main_loop_si4063(radio_transmit_entry *entry, radio_module_state *shared_state)
|
||||
{
|
||||
if (entry->radio_type != RADIO_TYPE_SI4063 || shared_state->radio_interrupt_transmit_active) {
|
||||
|
@ -181,6 +235,11 @@ void radio_handle_main_loop_si4063(radio_transmit_entry *entry, radio_module_sta
|
|||
return;
|
||||
}
|
||||
|
||||
if (shared_state->radio_fifo_transmit_active) {
|
||||
radio_handle_fifo_si4063(entry, shared_state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (radio_si4063_state_change) {
|
||||
radio_si4063_state_change = false;
|
||||
pwm_timer_set_frequency(radio_si4063_freq);
|
||||
|
@ -266,6 +325,8 @@ bool radio_stop_transmit_si4063(radio_transmit_entry *entry, radio_module_state
|
|||
case RADIO_DATA_MODE_HORUS_V2:
|
||||
data_timer_uninit();
|
||||
break;
|
||||
case RADIO_DATA_MODE_CATS:
|
||||
break;
|
||||
case RADIO_DATA_MODE_APRS_1200:
|
||||
use_direct_mode = true;
|
||||
break;
|
||||
|
@ -293,6 +354,8 @@ bool radio_stop_transmit_si4063(radio_transmit_entry *entry, radio_module_state
|
|||
case RADIO_DATA_MODE_HORUS_V2:
|
||||
system_enable_tick();
|
||||
break;
|
||||
case RADIO_DATA_MODE_CATS:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue