* 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
Stephen D 2024-03-23 07:05:45 -03:00 zatwierdzone przez GitHub
rodzic 0b32abff31
commit 3d2bdbf246
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
30 zmienionych plików z 1023 dodań i 9 usunięć

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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);
}

Wyświetl plik

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

Wyświetl plik

@ -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);
}
}

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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);
}
}

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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,
};

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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[];

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -1,6 +1,8 @@
#ifndef __DELAY_H
#define __DELAY_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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,
};

Wyświetl plik

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

Wyświetl plik

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