sforkowany z mirror/RS41ng
Add support for Horus 4FSK modulation. Increase APRS preamble size to improve reception. Fix slowness in I2C initialization. Fix incorrect GPS settings for airborne usage. Additional minor code clean-up and refactoring.
rodzic
f2ff6cb8a7
commit
d12c923594
14
README.md
14
README.md
|
@ -4,15 +4,19 @@
|
|||
|
||||
This is a custom, amateur radio-oriented firmware for Vaisala RS41 radiosondes.
|
||||
Some of the code is based on an earlier RS41 firmware project called [RS41HUP](https://github.com/df8oe/RS41HUP),
|
||||
but most of it has been rewritten from scratch.
|
||||
but most of it has been rewritten from scratch. The Horus 4FSK code has been adapted from
|
||||
the [darksidelemm fork of RS41HUP](https://github.com/darksidelemm/RS41HUP).
|
||||
|
||||
The motivation to develop this firmware is to provide cleaner, customizable and
|
||||
more modular codebase for developing RS41 radiosonde-based experiments.
|
||||
|
||||
The main features this firmware aims to implement are:
|
||||
* Enhanced support for the internal Si4032 radio transmitter via PWM-based tone generation (and ultimately DMA-based symbol timing, if possible)
|
||||
* Support for transmitting multiple modes consecutively with custom, rotating comment messages
|
||||
* Support for standard 1200-baud APRS
|
||||
* Support for [Horus 4FSK mode](https://github.com/projecthorus/horusdemodlib/wiki) that has improved performance compared to APRS or RTTY
|
||||
* Support for additional digital modes on HF/VHF amateur radio bands using an external Si5351 clock generator connected to the external I²C bus
|
||||
* Support for custom sensors via the external I²C bus
|
||||
* Enhanced support for the internal Si4032 radio transmitter via PWM-based tone generation (and ultimately DMA-based symbol timing, if possible)
|
||||
* Extensibility to allow easy addition of new digital modes
|
||||
|
||||
## Features
|
||||
|
@ -20,6 +24,11 @@ The main features this firmware aims to implement are:
|
|||
* APRS on 70cm amateur radio band using the internal Si4032 radio transmitter
|
||||
* Bell 202 frequencies are generated via hardware PWM, but the symbol timing is created in a loop with delay
|
||||
* There is also code available to use DMA transfers for symbol timing to achieve greater accuracy, but I have not been able to get the timings working correctly
|
||||
* Horus 4FSK on 70cm amateur radio band using the internal Si4032 radio transmitter
|
||||
* The Horus 4FSK mode has significantly [improved performance compared to APRS or RTTY](https://github.com/projecthorus/horusdemodlib/wiki)
|
||||
* Use [horus-gui](https://github.com/projecthorus/horus-gui) software to receive the 4FSK mode and to submit packets to [Habhub](http://habhub.org/) high-altitude balloon tracking platform
|
||||
* See [horus-gui installation and usage instructions](https://github.com/projecthorus/horusdemodlib/wiki/1.1-Horus-GUI-Reception-Guide-(Windows-Linux-OSX))
|
||||
* Based on [horusdemodlib](https://github.com/projecthorus/horusdemodlib) library that is responsible for demodulating the signal
|
||||
* Digital mode beacons on HF/VHF frequencies using a Si5351 clock generator connected to the external I²C bus of the RS41 radiosonde
|
||||
* The JTEncode library provides JT65/JT9/JT4/FT8/WSPR/FSQ beacon transmissions. I've decoded FT8 and WSPR successfully.
|
||||
* GPS-based scheduling is available for modes that require specific timing for transmissions
|
||||
|
@ -225,4 +234,5 @@ rtl_fm -f 432500000 -M fm -s 250k -r 48000 -g 22 - | ./aprs -
|
|||
# Authors
|
||||
|
||||
* 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
|
||||
* Mikael Nousiainen OH3BHX <oh3bhx@sral.fi>
|
||||
|
|
|
@ -64,10 +64,10 @@ size_t aprs_generate_position(uint8_t *payload, size_t length, telemetry_data *d
|
|||
symbol,
|
||||
heading_degrees,
|
||||
ground_speed_knots,
|
||||
altitude_feet,
|
||||
(int) altitude_feet,
|
||||
aprs_packet_counter,
|
||||
data->gps.satellites_visible,
|
||||
data->internal_temperature_celsius_100 / 100,
|
||||
(int) data->internal_temperature_celsius_100 / 100,
|
||||
data->battery_voltage_millivolts,
|
||||
(int16_t) ((float) data->gps.climb_cm_per_second / 100.0f),
|
||||
comment
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include "codecs/fsk/fsk.h"
|
||||
|
||||
#define BELL_FLAG_FIELD_COUNT_1200 25
|
||||
#define BELL_FLAG_FIELD_COUNT_1200 45
|
||||
#define BELL_FLAG_FIELD_COUNT_300 45
|
||||
|
||||
void bell_encoder_new(fsk_encoder *encoder, uint32_t symbol_rate, uint16_t flag_field_count, fsk_tone *tones);
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,23 @@
|
|||
/*---------------------------------------------------------------------------*\
|
||||
|
||||
FILE........: horus_l2.h
|
||||
AUTHOR......: David Rowe
|
||||
DATE CREATED: Dec 2015
|
||||
|
||||
\*---------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef __HORUS_L2__
|
||||
#define __HORUS_L2__
|
||||
|
||||
int horus_l2_get_num_tx_data_bytes(int num_payload_data_bytes);
|
||||
|
||||
/* returns number of output bytes in output_tx_data */
|
||||
int horus_l2_encode_tx_packet(unsigned char *output_tx_data,
|
||||
unsigned char *input_payload_data,
|
||||
int num_payload_data_bytes);
|
||||
|
||||
void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
|
||||
unsigned char *input_rx_data,
|
||||
int num_payload_data_bytes);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,75 @@
|
|||
#include <string.h>
|
||||
#include "horus_packet_v1.h"
|
||||
|
||||
volatile uint16_t horus_packet_counter = 0;
|
||||
|
||||
static uint16_t calculate_crc16_checksum(char *string, int len)
|
||||
{
|
||||
uint16_t crc = 0xffff;
|
||||
char i;
|
||||
int ptr = 0;
|
||||
while (ptr < len) {
|
||||
ptr++;
|
||||
crc = crc ^ (*(string++) << 8);
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (crc & 0x8000)
|
||||
crc = (uint16_t)((crc << 1) ^ 0x1021);
|
||||
else
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
size_t horus_packet_v1_create(uint8_t *payload, size_t length, telemetry_data *data, uint8_t payload_id)
|
||||
{
|
||||
if (length < sizeof(horus_packet_v1)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
gps_data *gps_data = &data->gps;
|
||||
|
||||
float float_lat = (float) gps_data->latitude_degrees_1000000 / 10000000.0f;
|
||||
float float_lon = (float) gps_data->longitude_degrees_1000000 / 10000000.0f;
|
||||
|
||||
uint8_t volts_scaled = (uint8_t)(255 * (float) data->battery_voltage_millivolts / 5000.0f);
|
||||
|
||||
horus_packet_counter++;
|
||||
|
||||
// Assemble a binary packet
|
||||
horus_packet_v1 horus_packet;
|
||||
|
||||
horus_packet.PayloadID = payload_id % 256;
|
||||
horus_packet.Counter = horus_packet_counter;
|
||||
horus_packet.Hours = gps_data->hours;
|
||||
horus_packet.Minutes = gps_data->minutes;
|
||||
horus_packet.Seconds = gps_data->seconds;
|
||||
horus_packet.Latitude = float_lat;
|
||||
horus_packet.Longitude = float_lon;
|
||||
horus_packet.Altitude = (uint16_t)(gps_data->altitude_mm / 1000);
|
||||
horus_packet.Speed = (uint8_t)((float) gps_data->ground_speed_cm_per_second * 0.036);
|
||||
|
||||
// Temporary pDOP info, to determine suitable pDOP limits.
|
||||
// float pDop = (float)gps_data.pDOP/10.0;
|
||||
// if (pDop>255.0){
|
||||
// pDop = 255.0;
|
||||
// }
|
||||
// mfsk_buffer.Speed = (uint8_t)pDop;
|
||||
horus_packet.BattVoltage = volts_scaled;
|
||||
horus_packet.Sats = gps_data->satellites_visible;
|
||||
horus_packet.Temp = (int8_t) ((float) data->internal_temperature_celsius_100 / 100.0f);
|
||||
|
||||
// Add onto the sats_raw value to indicate if the GPS is in regular tracking (+100)
|
||||
// or power optimized tracker (+200) modes.
|
||||
if (gps_data->power_safe_mode_state == POWER_SAFE_MODE_STATE_TRACKING) {
|
||||
horus_packet.Sats += 100;
|
||||
} else if (gps_data->power_safe_mode_state == POWER_SAFE_MODE_STATE_POWER_OPTIMIZED_TRACKING) {
|
||||
horus_packet.Sats += 200;
|
||||
}
|
||||
|
||||
horus_packet.Checksum = (uint16_t) calculate_crc16_checksum((char *) &horus_packet, sizeof(horus_packet) - 2);
|
||||
|
||||
memcpy(payload, &horus_packet, sizeof(horus_packet));
|
||||
|
||||
return sizeof(horus_packet);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef __HORUS_PACKET_V1_H
|
||||
#define __HORUS_PACKET_V1_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "telemetry.h"
|
||||
|
||||
// Horus Binary v1 Packet Format
|
||||
// See: https://github.com/projecthorus/horusdemodlib/wiki/4-Packet-Format-Details#horus-binary-v1-22-bytes
|
||||
// Note that we need to pack this to 1-byte alignment, hence the #pragma flags below
|
||||
// Refer: https://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Structure_002dPacking-Pragmas.html
|
||||
#pragma pack(push, 1)
|
||||
typedef struct _horus_packet_v1 {
|
||||
uint8_t PayloadID;
|
||||
uint16_t Counter;
|
||||
uint8_t Hours;
|
||||
uint8_t Minutes;
|
||||
uint8_t Seconds;
|
||||
float Latitude; // Latitude in degrees TODO: ?
|
||||
float Longitude; // Longitude in degrees TODO: ?
|
||||
uint16_t Altitude; // Altitude in meters
|
||||
uint8_t Speed; // Speed in Knots (1-255 knots) -- TODO: It seems this is actually km/h?
|
||||
uint8_t Sats;
|
||||
int8_t Temp; // Temperature in Celsius, as a signed value (-128 to +128, though sensor limited to -64 to +64 deg C)
|
||||
uint8_t BattVoltage; // 0 = 0v, 255 = 5.0V, linear steps in-between.
|
||||
uint16_t Checksum; // CRC16-CCITT Checksum.
|
||||
} horus_packet_v1; // __attribute__ ((packed)); // Doesn't work?
|
||||
#pragma pack(pop)
|
||||
|
||||
size_t horus_packet_v1_create(uint8_t *payload, size_t length, telemetry_data *data, uint8_t payload_id);
|
||||
|
||||
#endif
|
|
@ -6,7 +6,6 @@
|
|||
#include "lib/JTEncode.h"
|
||||
|
||||
#include "jtencode.h"
|
||||
#include "log.h"
|
||||
|
||||
// Some of the code is based on JTEncode examples:
|
||||
//
|
||||
|
|
|
@ -245,7 +245,7 @@ void JTEncode::fsq_encode(const char * from_call, const char * message, uint8_t
|
|||
{
|
||||
char * tx_message;
|
||||
uint16_t symbol_pos = 0;
|
||||
uint8_t i, fch, vcode1, vcode2, tone;
|
||||
uint8_t i, fch, vcode1, vcode2;
|
||||
uint8_t cur_tone = 0;
|
||||
|
||||
// Clear out the transmit buffer
|
||||
|
@ -332,7 +332,7 @@ void JTEncode::fsq_dir_encode(const char * from_call, const char * to_call, cons
|
|||
{
|
||||
char * tx_message;
|
||||
uint16_t symbol_pos = 0;
|
||||
uint8_t i, fch, vcode1, vcode2, tone, from_call_crc;
|
||||
uint8_t i, fch, vcode1, vcode2, from_call_crc;
|
||||
uint8_t cur_tone = 0;
|
||||
|
||||
// Generate a CRC on from_call
|
||||
|
@ -421,8 +421,6 @@ void JTEncode::fsq_dir_encode(const char * from_call, const char * to_call, cons
|
|||
uint8_t ft8_buffer_s[FT8_BIT_COUNT];
|
||||
void JTEncode::ft8_encode(const char * msg, uint8_t * symbols)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
char message[19];
|
||||
memset(message, 0, 19);
|
||||
strcpy(message, msg);
|
||||
|
@ -845,7 +843,7 @@ void JTEncode::ft8_bit_packing(char* message, uint8_t* codeword)
|
|||
memset(qa, 0, 10);
|
||||
memset(qb, 0, 10);
|
||||
|
||||
uint8_t i, j, x, i0;
|
||||
uint8_t i, j, x, i0 = 0;
|
||||
uint32_t ireg = 0;
|
||||
|
||||
// See if this is a telemetry message
|
||||
|
@ -1011,7 +1009,7 @@ void JTEncode::jt65_interleave(uint8_t * s)
|
|||
uint8_t jt9_buffer_d[JT9_BIT_COUNT];
|
||||
void JTEncode::jt9_interleave(uint8_t * s)
|
||||
{
|
||||
uint8_t i, j;
|
||||
uint8_t i;
|
||||
|
||||
// Do the interleave
|
||||
for(i = 0; i < JT9_BIT_COUNT; i++)
|
||||
|
@ -1020,8 +1018,9 @@ void JTEncode::jt9_interleave(uint8_t * s)
|
|||
#if defined(__arm__)
|
||||
jt9_buffer_d[jt9i[i]] = s[i];
|
||||
#else
|
||||
j = pgm_read_byte(&jt9i[i]);
|
||||
jt9_buffer_d[j] = s[i];
|
||||
uint8_t j;
|
||||
j = pgm_read_byte(&jt9i[i]);
|
||||
jt9_buffer_d[j] = s[i];
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -1372,9 +1371,10 @@ uint8_t JTEncode::crc8(const char * text)
|
|||
{
|
||||
uint8_t crc = '\0';
|
||||
uint8_t ch;
|
||||
uint8_t len = (uint8_t) strlen(text);
|
||||
|
||||
int i;
|
||||
for(i = 0; i < strlen(text); i++)
|
||||
for(i = 0; i < len; i++)
|
||||
{
|
||||
ch = text[i];
|
||||
//#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mfsk.h"
|
||||
|
||||
typedef struct _mfsk_encoder {
|
||||
mfsk_type type;
|
||||
uint32_t symbol_rate;
|
||||
uint32_t tone_spacing_hz_100;
|
||||
uint8_t max_nibble_index;
|
||||
|
||||
uint16_t data_length;
|
||||
uint8_t *data;
|
||||
|
||||
uint16_t current_byte_index;
|
||||
uint8_t current_nibble_index;
|
||||
uint8_t current_byte;
|
||||
|
||||
int8_t idle_tone_index;
|
||||
int8_t current_tone_index;
|
||||
|
||||
fsk_tone tones[16];
|
||||
} mfsk_encoder;
|
||||
|
||||
#define MFSK_2_IDLE_TONE 0
|
||||
#define MFSK_4_IDLE_TONE 3
|
||||
#define MFSK_16_IDLE_TONE 3
|
||||
|
||||
void mfsk_encoder_new(fsk_encoder *encoder, mfsk_type type, uint32_t symbol_rate, uint32_t tone_spacing_hz_100)
|
||||
{
|
||||
encoder->priv = malloc(sizeof(mfsk_encoder));
|
||||
memset(encoder->priv, 0, sizeof(mfsk_encoder));
|
||||
|
||||
mfsk_encoder *mfsk = (mfsk_encoder *) encoder->priv;
|
||||
mfsk->type = type;
|
||||
mfsk->symbol_rate = symbol_rate;
|
||||
mfsk->tone_spacing_hz_100 = tone_spacing_hz_100;
|
||||
|
||||
switch (mfsk->type) {
|
||||
case MFSK_2:
|
||||
mfsk->idle_tone_index = MFSK_2_IDLE_TONE;
|
||||
mfsk->max_nibble_index = 8;
|
||||
break;
|
||||
case MFSK_4:
|
||||
mfsk->idle_tone_index = MFSK_4_IDLE_TONE;
|
||||
mfsk->max_nibble_index = 4;
|
||||
break;
|
||||
case MFSK_16:
|
||||
mfsk->idle_tone_index = MFSK_16_IDLE_TONE;
|
||||
mfsk->max_nibble_index = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < type; i++) {
|
||||
mfsk->tones[i].index = (int8_t) i;
|
||||
mfsk->tones[i].frequency_hz_100 = i * tone_spacing_hz_100;
|
||||
}
|
||||
}
|
||||
|
||||
void mfsk_encoder_destroy(fsk_encoder *encoder)
|
||||
{
|
||||
if (encoder->priv != NULL) {
|
||||
free(encoder->priv);
|
||||
encoder->priv = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
fsk_tone *mfsk_get_idle_tone(fsk_encoder *encoder)
|
||||
{
|
||||
mfsk_encoder *mfsk = (mfsk_encoder *) encoder->priv;
|
||||
return &mfsk->tones[mfsk->idle_tone_index];
|
||||
}
|
||||
|
||||
void mfsk_encoder_set_data(fsk_encoder *encoder, uint16_t data_length, uint8_t *data)
|
||||
{
|
||||
mfsk_encoder *mfsk = (mfsk_encoder *) encoder->priv;
|
||||
|
||||
mfsk->data = data;
|
||||
mfsk->data_length = data_length;
|
||||
|
||||
mfsk->current_tone_index = mfsk->idle_tone_index;
|
||||
mfsk->current_byte_index = 0;
|
||||
mfsk->current_nibble_index = 0;
|
||||
mfsk->current_byte = data[0];
|
||||
}
|
||||
|
||||
void mfsk_encoder_get_tones(fsk_encoder *encoder, int8_t *tone_count, fsk_tone **tones)
|
||||
{
|
||||
mfsk_encoder *mfsk = (mfsk_encoder *) encoder->priv;
|
||||
|
||||
*tone_count = mfsk->type;
|
||||
*tones = mfsk->tones;
|
||||
}
|
||||
|
||||
uint32_t mfsk_encoder_get_tone_spacing(fsk_encoder *encoder)
|
||||
{
|
||||
mfsk_encoder *mfsk = (mfsk_encoder *) encoder->priv;
|
||||
return mfsk->tone_spacing_hz_100;
|
||||
}
|
||||
|
||||
uint32_t mfsk_encoder_get_symbol_rate(fsk_encoder *encoder)
|
||||
{
|
||||
mfsk_encoder *mfsk = (mfsk_encoder *) encoder->priv;
|
||||
return mfsk->symbol_rate;
|
||||
}
|
||||
|
||||
uint32_t mfsk_encoder_get_symbol_delay(fsk_encoder *encoder)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t mfsk_encoder_next_tone(fsk_encoder *encoder)
|
||||
{
|
||||
mfsk_encoder *mfsk = (mfsk_encoder *) encoder->priv;
|
||||
|
||||
if (mfsk->current_byte_index >= mfsk->data_length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mfsk->current_nibble_index == mfsk->max_nibble_index) {
|
||||
// Reached the end of the byte
|
||||
mfsk->current_nibble_index = 0;
|
||||
mfsk->current_byte_index++;
|
||||
if (mfsk->current_byte_index >= mfsk->data_length) {
|
||||
return -1;
|
||||
}
|
||||
mfsk->current_byte = mfsk->data[mfsk->current_byte_index];
|
||||
}
|
||||
|
||||
int8_t symbol;
|
||||
|
||||
switch (mfsk->type) {
|
||||
case MFSK_2:
|
||||
// Get the current symbol (the MSB).
|
||||
if (((uint8_t) mfsk->current_byte & 0x80) >> 7) {
|
||||
symbol = 2;
|
||||
} else {
|
||||
symbol = 0;
|
||||
}
|
||||
|
||||
// Shift it left to the nibble we are up to.
|
||||
mfsk->current_byte = mfsk->current_byte << 1;
|
||||
break;
|
||||
case MFSK_4:
|
||||
// Get the current symbol (the 2 bits we need).
|
||||
symbol = (int8_t) ((mfsk->current_byte & 0xC0) >> 6);
|
||||
|
||||
// Shift it left to the nibble we are up to.
|
||||
mfsk->current_byte = mfsk->current_byte << 2;
|
||||
break;
|
||||
case MFSK_16:
|
||||
// Get the current symbol (the 4 bits we need).
|
||||
symbol = (int8_t) ((mfsk->current_byte & 0xF0) >> 4);
|
||||
|
||||
// Shift it left to the nibble we are up to.
|
||||
mfsk->current_byte = mfsk->current_byte << 4;
|
||||
break;
|
||||
default:
|
||||
symbol = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
mfsk->current_nibble_index++;
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
fsk_encoder_api mfsk_fsk_encoder_api = {
|
||||
.get_tones = mfsk_encoder_get_tones,
|
||||
.get_tone_spacing = mfsk_encoder_get_tone_spacing,
|
||||
.get_symbol_rate = mfsk_encoder_get_symbol_rate,
|
||||
.get_symbol_delay = mfsk_encoder_get_symbol_delay,
|
||||
.set_data = mfsk_encoder_set_data,
|
||||
.next_tone = mfsk_encoder_next_tone,
|
||||
};
|
||||
|
||||
#ifdef TEST
|
||||
int mfsk_test_bits(char *buffer)
|
||||
{
|
||||
// Add some test bits into the MFSK buffer, compatible with the first 200 test bits produced by fsk_get_test_bits from codec2-dev.
|
||||
// Decode using (with GQRX outputting data to UDP): nc -l -u localhost 7355 | ./fsk_demod 4 48000 100 - - | ./fsk_put_test_bits -
|
||||
sprintf(buffer,
|
||||
"\x1b\x1b\x1b\x1b\xd8\x2c\xa2\x56\x3f\x4c\x08\xa2\xba\x6f\x84\x07\x6d\x82\xca\x25\x63\xf4\xc0\x8a\x2b\xa6\xf8\x40\x76");
|
||||
return 29;
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef __MFSK_H
|
||||
#define __MFSK_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "codecs/fsk/fsk.h"
|
||||
|
||||
typedef enum _mfsk_type {
|
||||
MFSK_2 = 2,
|
||||
MFSK_4 = 4,
|
||||
MFSK_16 = 16,
|
||||
} mfsk_type;
|
||||
|
||||
void mfsk_encoder_new(fsk_encoder *encoder, mfsk_type type, uint32_t symbol_rate, uint32_t tone_spacing_hz_100);
|
||||
void mfsk_encoder_destroy(fsk_encoder *encoder);
|
||||
fsk_tone *mfsk_get_idle_tone(fsk_encoder *encoder);
|
||||
void mfsk_encoder_set_data(fsk_encoder *encoder, uint16_t data_length, uint8_t *data);
|
||||
void mfsk_encoder_get_tones(fsk_encoder *encoder, int8_t *tone_count, fsk_tone **tones);
|
||||
uint32_t mfsk_encoder_get_tone_spacing(fsk_encoder *encoder);
|
||||
uint32_t mfsk_encoder_get_symbol_rate(fsk_encoder *encoder);
|
||||
uint32_t mfsk_encoder_get_symbol_delay(fsk_encoder *encoder);
|
||||
int8_t mfsk_encoder_next_tone(fsk_encoder *encoder);
|
||||
|
||||
extern fsk_encoder_api mfsk_fsk_encoder_api;
|
||||
|
||||
#endif
|
28
src/config.c
28
src/config.c
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* The tracker firmware will transmit each of the message templates
|
||||
* defined here one by one, starting again from the beginning once the last message is transmitted.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "config.h"
|
||||
|
||||
|
@ -45,18 +50,33 @@ volatile bool system_initialized = false;
|
|||
* $he - Heading in degrees (up to 3 chars)
|
||||
*/
|
||||
|
||||
/**
|
||||
* APRS mode comment messages.
|
||||
* Maximum length: depends on the packet contents, but keeping this under 100 characters is usually safe.
|
||||
* Note that many hardware APRS receivers show a limited number of APRS comment characters, such as 43 or 67 chars.
|
||||
*/
|
||||
char *aprs_comment_templates[] = {
|
||||
" B$bu $teC $hu% $prmb $hh:$mm:$ss @ $tow ms - RS41ng radiosonde firmware test",
|
||||
" $loc12 - RS41ng radiosonde firmware test",
|
||||
// " B$bu $teC $hu% $prmb $hh:$mm:$ss @ $tow ms - " APRS_COMMENT,
|
||||
// " B$bu $teC $hu% $prmb - " APRS_COMMENT,
|
||||
// " B$bu $loc12 $hh:$mm:$ss - " APRS_COMMENT,
|
||||
" $loc12 - " APRS_COMMENT,
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
* FSQ mode comment message templates.
|
||||
* Maximum length: 130 characters.
|
||||
*/
|
||||
char *fsq_comment_templates[] = {
|
||||
" $lat $lon, $alt m, $cl m/s, $gs km/h, $he deg - RS41ng radiosonde firmware test",
|
||||
" $loc12, $teC $hu% $prmb $hh:$mm:$ss @ $tow ms - RS41ng radiosonde firmware test",
|
||||
" $lat $lon, $alt m, $cl m/s, $gs km/h, $he deg - " FSQ_COMMENT,
|
||||
" $loc12, $teC $hu% $prmb $hh:$mm:$ss @ $tow ms - " FSQ_COMMENT,
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
* FTx/JTxx mode message templates.
|
||||
* Maximum length: 13 characters allowed by the protocols.
|
||||
*/
|
||||
char *ftjt_message_templates[] = {
|
||||
"$cs $loc4",
|
||||
"$loc12",
|
||||
|
|
48
src/config.h
48
src/config.h
|
@ -8,35 +8,50 @@
|
|||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define CALLSIGN "OH3BHX"
|
||||
|
||||
#define RADIO_PAYLOAD_MAX_LENGTH 256
|
||||
#define RADIO_SYMBOL_DATA_MAX_LENGTH 512
|
||||
#define RADIO_PAYLOAD_MESSAGE_MAX_LENGTH 128
|
||||
// Set the tracker amateur radio call sign here
|
||||
#define CALLSIGN "MYCALL"
|
||||
|
||||
#define LEDS_ENABLE true
|
||||
|
||||
#define SENSOR_BMP280_ENABLE false
|
||||
|
||||
#define RADIO_SI5351_ENABLE true
|
||||
#define RADIO_SI5351_ENABLE false
|
||||
|
||||
#define RADIO_POST_TRANSMIT_DELAY_MS 5000
|
||||
#define RADIO_TIME_SYNC_THRESHOLD_MS 1500
|
||||
#define RADIO_TIME_SYNC_THRESHOLD_MS 2000
|
||||
|
||||
/**
|
||||
* Si4032 transmit power: 0..7
|
||||
* 0 = -1dBm, 1 = 2dBm, 2 = 5dBm, 3 = 8dBm, 4 = 11dBm, 5 = 14dBm, 6 = 17dBm, 7 = 20dBm
|
||||
*/
|
||||
#define RADIO_SI4032_TX_POWER 7
|
||||
|
||||
// Which modes to transmit using the built-in Si4032 transmitter chip
|
||||
#define RADIO_SI4032_TX_CW false
|
||||
#define RADIO_SI4032_TX_RTTY false
|
||||
#define RADIO_SI4032_TX_APRS true
|
||||
#define RADIO_SI4032_TX_HORUS_V1 true
|
||||
|
||||
// Transmit frequencies for the Si4032 transmitter modes
|
||||
#define RADIO_SI4032_TX_FREQUENCY_CW 432060000
|
||||
#define RADIO_SI4032_TX_FREQUENCY_RTTY 432060000
|
||||
#define RADIO_SI4032_TX_FREQUENCY_APRS_1200 432500000
|
||||
// Use a frequency offset to place FSK tones slightly above the defined frequency for SSB reception
|
||||
#define RADIO_SI4032_TX_FREQUENCY_HORUS_V1 432501000
|
||||
|
||||
/**
|
||||
* Si5351 transmit power: 0..3
|
||||
* Si5351 drive strength: 0 = 2mA, 1 = 4mA, 2 = 6mA, 3 = 8mA
|
||||
*/
|
||||
#define RADIO_SI5351_TX_POWER 3
|
||||
|
||||
// Which modes to transmit using an externally connected Si5351 chip in the I²C bus
|
||||
#define RADIO_SI5351_TX_JT9 false
|
||||
#define RADIO_SI5351_TX_JT65 false
|
||||
#define RADIO_SI5351_TX_JT4 false
|
||||
#define RADIO_SI5351_TX_WSPR false
|
||||
#define RADIO_SI5351_TX_FSQ false
|
||||
#define RADIO_SI5351_TX_FT8 false
|
||||
|
||||
// Transmit frequencies for the Si5351 transmitter modes
|
||||
#define RADIO_SI5351_TX_FREQUENCY_JT9 14085000UL // Was: 14078700UL
|
||||
#define RADIO_SI5351_TX_FREQUENCY_JT65 14085000UL // Was: 14078300UL
|
||||
#define RADIO_SI5351_TX_FREQUENCY_JT4 14085000UL // Was: 14078500UL
|
||||
|
@ -46,12 +61,15 @@
|
|||
|
||||
#define LOCATOR_PAIR_COUNT_FULL 6 // max. 6 (12 characters WWL)
|
||||
|
||||
// WSPR settings
|
||||
#define WSPR_CALLSIGN CALLSIGN
|
||||
#define WSPR_LOCATOR_FIXED_ENABLED false
|
||||
#define WSPR_LOCATOR_FIXED "AA00"
|
||||
#define WSPR_DBM 10
|
||||
|
||||
// FSQ settings
|
||||
#define FSQ_CALLSIGN_FROM CALLSIGN
|
||||
#define FSQ_COMMENT "RS41ng radiosonde firmware test"
|
||||
|
||||
/**
|
||||
* APRS SSID:
|
||||
|
@ -79,11 +97,17 @@
|
|||
// See APRS symbol table documentation in: http://www.aprs.org/symbols/symbolsX.txt
|
||||
#define APRS_SYMBOL_TABLE '/' // '/' denotes primary and '\\' denotes alternate APRS symbol table
|
||||
#define APRS_SYMBOL 'O'
|
||||
#define APRS_COMMENT " RS41ng radiosonde firmware test"
|
||||
#define APRS_COMMENT "RS41ng radiosonde firmware test"
|
||||
#define APRS_RELAYS "WIDE1-1,WIDE2-1"
|
||||
#define APRS_DESTINATION "APZ41N"
|
||||
#define APRS_DESTINATION_SSID '0'
|
||||
|
||||
// Use Horus payload ID 0 for tests (4FSKTEST)
|
||||
#define HORUS_V1_PAYLOAD_ID 0
|
||||
#define HORUS_V1_BAUD_RATE 100
|
||||
#define HORUS_V1_FREQUENCY_OFFSET 0
|
||||
#define HORUS_V1_PREAMBLE_LENGTH 16
|
||||
|
||||
// TODO: RTTY and CW settings (once modes are implemented)
|
||||
|
||||
#define RTTY_LOCATOR_PAIR_COUNT 4 // max. 6 (12 characters WWL)
|
||||
|
@ -91,6 +115,10 @@
|
|||
|
||||
#define CW_LOCATOR_PAIR_COUNT 4 // max. 6 (12 characters WWL)
|
||||
|
||||
#define RADIO_PAYLOAD_MAX_LENGTH 256
|
||||
#define RADIO_SYMBOL_DATA_MAX_LENGTH 512
|
||||
#define RADIO_PAYLOAD_MESSAGE_MAX_LENGTH 128
|
||||
|
||||
extern bool leds_enabled;
|
||||
extern bool bmp280_enabled;
|
||||
extern bool si5351_enabled;
|
||||
|
|
|
@ -75,10 +75,15 @@ void si4032_set_tx_power(uint8_t power)
|
|||
*/
|
||||
void si4032_set_frequency_offset(uint16_t offset)
|
||||
{
|
||||
si4032_write(0x73, 0);
|
||||
si4032_write(0x73, offset);
|
||||
si4032_write(0x74, 0);
|
||||
}
|
||||
|
||||
inline void si4032_set_frequency_offset_small(uint8_t offset)
|
||||
{
|
||||
si4032_write(0x73, offset);
|
||||
}
|
||||
|
||||
void si4032_set_frequency_deviation(uint8_t deviation)
|
||||
{
|
||||
// The frequency deviation can be calculated: Fd = 625 Hz x fd[8:0].
|
||||
|
|
|
@ -18,6 +18,7 @@ void si4032_use_direct_mode(bool use);
|
|||
void si4032_set_tx_frequency(float frequency_mhz);
|
||||
void si4032_set_tx_power(uint8_t power);
|
||||
void si4032_set_frequency_offset(uint16_t offset);
|
||||
void si4032_set_frequency_offset_small(uint8_t offset);
|
||||
void si4032_set_frequency_deviation(uint8_t deviation);
|
||||
void si4032_set_modulation_type(si4032_modulation_type type);
|
||||
int32_t si4032_read_temperature_celsius_100();
|
||||
|
|
|
@ -118,6 +118,16 @@ typedef struct {
|
|||
uint32_t headAcc; //Heading accuracy estimate (both motion and vehicle) [1e-5 deg]
|
||||
} uBloxNAVVELNEDPayload;
|
||||
|
||||
typedef struct {
|
||||
uint32_t iTOW; //GPS Millisecond Time of Week [- ms]
|
||||
uint8_t gpsFix; //GPS Fix State
|
||||
uint8_t flags; // LSB = gpsFixOK - the only valid way of determining if a fix is actually OK.
|
||||
uint8_t fixStat;
|
||||
uint8_t flags2; // Power Save Mode State
|
||||
uint32_t ttff;
|
||||
uint32_t msss;
|
||||
} uBloxNAVSTATUSPayload;
|
||||
|
||||
typedef struct {
|
||||
uint8_t portID; //Port Identifier Number (see Serial [- -]
|
||||
uint8_t reserved1; //Reserved [- -]
|
||||
|
@ -205,6 +215,7 @@ typedef union {
|
|||
uBloxNAVTIMEGPSPayload navtimegps;
|
||||
uBloxNAVTIMEUTCPayload navtimeutc;
|
||||
uBloxNAVVELNEDPayload navvelned;
|
||||
uBloxNAVSTATUSPayload navstatus;
|
||||
uBloxACKACKayload ackack;
|
||||
uBloxCFGRSTPayload cfgrst;
|
||||
uBloxCFGRXMPayload cfgrxm;
|
||||
|
@ -216,7 +227,7 @@ typedef struct __attribute__((packed)) {
|
|||
ubloxPacketData data;
|
||||
} uBloxPacket;
|
||||
|
||||
gps_data current_gps_data;
|
||||
gps_data ubxg6010_current_gps_data;
|
||||
|
||||
volatile bool gps_initialized = false;
|
||||
volatile bool ack_received = false;
|
||||
|
@ -305,8 +316,8 @@ bool ubxg6010_send_packet_and_wait_for_ack(uBloxPacket *packet)
|
|||
bool ubxg6010_get_current_gps_data(gps_data *data)
|
||||
{
|
||||
system_disable_irq();
|
||||
memcpy(data, ¤t_gps_data, sizeof(gps_data));
|
||||
current_gps_data.updated = false;
|
||||
memcpy(data, &ubxg6010_current_gps_data, sizeof(gps_data));
|
||||
ubxg6010_current_gps_data.updated = false;
|
||||
system_enable_irq();
|
||||
|
||||
return data->updated;
|
||||
|
@ -425,16 +436,18 @@ uBloxPacket msgcfgnav5 = {
|
|||
.messageId=0x24,
|
||||
.payloadSize=sizeof(uBloxCFGNAV5Payload)
|
||||
},
|
||||
// NOTE: Dynamic model needs to be set to one of the Airborne modes to support high altitudes!
|
||||
// Notes from darksidelemm RS41HUP fork: Tweaked the PDOP limits a bit, to make it a bit more likely to report a position.
|
||||
.data.cfgnav5={
|
||||
.mask=0b0000001111111111, // Configure all settings
|
||||
.dynModel=4, // Dynamic model: Airborne with <2g Acceleration -- TODO: 7
|
||||
.fixMode=3, // Fix mode: 3D only -- TODO: 2
|
||||
.dynModel=6, // Dynamic model: Airborne with <1g Acceleration
|
||||
.fixMode=2, // Fix mode: 3D only
|
||||
.fixedAlt=0, // Fixed altitude (mean sea level) for 2D fix mode.
|
||||
.fixedAltVar=10000, // Fixed altitude variance for 2D mode.
|
||||
.minElev=5, // Minimum Elevation for a GNSS satellite to be used in NAV (degrees)
|
||||
.drLimit=0, // Maximum time to perform dead reckoning (linear extrapolation) in case of GPS signal loss (seconds)
|
||||
.pDop=25, // Position DOP Mask to use
|
||||
.tDop=25, // Time DOP Mask to use
|
||||
.pDop=100, // Position DOP Mask to use (was 25)
|
||||
.tDop=100, // Time DOP Mask to use (was 25)
|
||||
.pAcc=100, // Position Accuracy Mask (m)
|
||||
.tAcc=300, // Time Accuracy Mask (m)
|
||||
.staticHoldThresh=0, // Static hold threshold (cm/s)
|
||||
|
@ -449,7 +462,7 @@ bool ubxg6010_init()
|
|||
{
|
||||
bool success;
|
||||
|
||||
memset(¤t_gps_data, 0, sizeof(gps_data));
|
||||
memset(&ubxg6010_current_gps_data, 0, sizeof(gps_data));
|
||||
|
||||
gps_initialized = false;
|
||||
|
||||
|
@ -483,6 +496,9 @@ bool ubxg6010_init()
|
|||
return false;
|
||||
}
|
||||
|
||||
// Rate of 1 for message: 0x01 0x02 Geodetic Position Solution
|
||||
msgcfgmsg.data.cfgmsg.msgID = 0x02;
|
||||
msgcfgmsg.data.cfgmsg.rate = 1;
|
||||
log_info("GPS: Requesting update messages from GPS chip\n");
|
||||
success = ubxg6010_send_packet_and_wait_for_ack(&msgcfgmsg);
|
||||
if (!success) {
|
||||
|
@ -496,7 +512,7 @@ bool ubxg6010_init()
|
|||
}
|
||||
|
||||
// Rate of 1 for message: 0x01 0x06 Navigation Solution Information
|
||||
msgcfgmsg.data.cfgmsg.msgID = 0x6;
|
||||
msgcfgmsg.data.cfgmsg.msgID = 0x06;
|
||||
msgcfgmsg.data.cfgmsg.rate = 1;
|
||||
log_info("GPS: Requesting update messages from GPS chip\n");
|
||||
success = ubxg6010_send_packet_and_wait_for_ack(&msgcfgmsg);
|
||||
|
@ -531,6 +547,15 @@ bool ubxg6010_init()
|
|||
return false;
|
||||
}
|
||||
|
||||
// Configure rate of 2 for message: 0x01 0x03 Receiver Navigation Status
|
||||
msgcfgmsg.data.cfgmsg.msgID = 0x03;
|
||||
msgcfgmsg.data.cfgmsg.rate = 1;
|
||||
log_info("GPS: Requesting update messages from GPS chip\n");
|
||||
success = ubxg6010_send_packet_and_wait_for_ack(&msgcfgmsg);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Is this message supported: Configure rate of 1 for message: 0x01 0x07 Position Velocity Time Solution
|
||||
/*msgcfgmsg.data.cfgmsg.msgID = 0x07;
|
||||
success = ubxg6010_send_packet_and_wait_for_ack(&msgcfgmsg);
|
||||
|
@ -587,7 +612,7 @@ static void ubxg6010_handle_packet(uBloxPacket *pkt)
|
|||
uBloxChecksum *checksum = (uBloxChecksum *) (((uint8_t *) &pkt->data) + pkt->header.payloadSize);
|
||||
|
||||
if (cksum.ck_a != checksum->ck_a || cksum.ck_b != checksum->ck_b) {
|
||||
current_gps_data.bad_packets += 1;
|
||||
ubxg6010_current_gps_data.bad_packets += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -595,62 +620,69 @@ static void ubxg6010_handle_packet(uBloxPacket *pkt)
|
|||
|
||||
if (pkt->header.messageClass == 0x01 && pkt->header.messageId == 0x07) {
|
||||
// TODO: It seems NAV PVT message is not supported by UBXG6010, confirm this
|
||||
current_gps_data.ok_packets += 1;
|
||||
current_gps_data.time_of_week_millis = pkt->data.navpvt.iTOW;
|
||||
current_gps_data.year = pkt->data.navpvt.year;
|
||||
current_gps_data.month = pkt->data.navpvt.month;
|
||||
current_gps_data.day = pkt->data.navpvt.day;
|
||||
current_gps_data.hours = pkt->data.navpvt.hour;
|
||||
current_gps_data.minutes = pkt->data.navpvt.min;
|
||||
current_gps_data.seconds = pkt->data.navpvt.sec;
|
||||
ubxg6010_current_gps_data.ok_packets += 1;
|
||||
ubxg6010_current_gps_data.time_of_week_millis = pkt->data.navpvt.iTOW;
|
||||
ubxg6010_current_gps_data.year = pkt->data.navpvt.year;
|
||||
ubxg6010_current_gps_data.month = pkt->data.navpvt.month;
|
||||
ubxg6010_current_gps_data.day = pkt->data.navpvt.day;
|
||||
ubxg6010_current_gps_data.hours = pkt->data.navpvt.hour;
|
||||
ubxg6010_current_gps_data.minutes = pkt->data.navpvt.min;
|
||||
ubxg6010_current_gps_data.seconds = pkt->data.navpvt.sec;
|
||||
|
||||
current_gps_data.fix = pkt->data.navpvt.fixType;
|
||||
current_gps_data.latitude_degrees_1000000 = pkt->data.navpvt.lat;
|
||||
current_gps_data.longitude_degrees_1000000 = pkt->data.navpvt.lon;
|
||||
current_gps_data.altitude_mm = pkt->data.navpvt.hMSL;
|
||||
current_gps_data.satellites_visible = pkt->data.navpvt.numSV;
|
||||
current_gps_data.ground_speed_cm_per_second = pkt->data.navpvt.gSpeed;
|
||||
current_gps_data.heading_degrees_100000 = pkt->data.navpvt.headMot;
|
||||
current_gps_data.climb_cm_per_second = -pkt->data.navpvt.velD;
|
||||
ubxg6010_current_gps_data.fix = pkt->data.navpvt.fixType;
|
||||
ubxg6010_current_gps_data.latitude_degrees_1000000 = pkt->data.navpvt.lat;
|
||||
ubxg6010_current_gps_data.longitude_degrees_1000000 = pkt->data.navpvt.lon;
|
||||
ubxg6010_current_gps_data.altitude_mm = pkt->data.navpvt.hMSL;
|
||||
ubxg6010_current_gps_data.satellites_visible = pkt->data.navpvt.numSV;
|
||||
ubxg6010_current_gps_data.ground_speed_cm_per_second = pkt->data.navpvt.gSpeed;
|
||||
ubxg6010_current_gps_data.heading_degrees_100000 = pkt->data.navpvt.headMot;
|
||||
ubxg6010_current_gps_data.climb_cm_per_second = -pkt->data.navpvt.velD;
|
||||
|
||||
current_gps_data.updated = true;
|
||||
ubxg6010_current_gps_data.updated = true;
|
||||
} else if (pkt->header.messageClass == 0x01 && pkt->header.messageId == 0x12) {
|
||||
current_gps_data.ok_packets += 1;
|
||||
current_gps_data.time_of_week_millis = pkt->data.navvelned.iTOW;
|
||||
current_gps_data.ground_speed_cm_per_second = pkt->data.navvelned.gSpeed;
|
||||
current_gps_data.heading_degrees_100000 = pkt->data.navvelned.headMot;
|
||||
current_gps_data.climb_cm_per_second = -pkt->data.navvelned.velD;
|
||||
ubxg6010_current_gps_data.ok_packets += 1;
|
||||
ubxg6010_current_gps_data.time_of_week_millis = pkt->data.navvelned.iTOW;
|
||||
ubxg6010_current_gps_data.ground_speed_cm_per_second = pkt->data.navvelned.gSpeed;
|
||||
ubxg6010_current_gps_data.heading_degrees_100000 = pkt->data.navvelned.headMot;
|
||||
ubxg6010_current_gps_data.climb_cm_per_second = -pkt->data.navvelned.velD;
|
||||
|
||||
current_gps_data.updated = true;
|
||||
ubxg6010_current_gps_data.updated = true;
|
||||
} else if (pkt->header.messageClass == 0x01 && pkt->header.messageId == 0x02) {
|
||||
current_gps_data.ok_packets += 1;
|
||||
current_gps_data.time_of_week_millis = pkt->data.navposllh.iTOW;
|
||||
current_gps_data.latitude_degrees_1000000 = pkt->data.navposllh.lat;
|
||||
current_gps_data.longitude_degrees_1000000 = pkt->data.navposllh.lon;
|
||||
current_gps_data.altitude_mm = pkt->data.navposllh.hMSL;
|
||||
ubxg6010_current_gps_data.ok_packets += 1;
|
||||
ubxg6010_current_gps_data.time_of_week_millis = pkt->data.navposllh.iTOW;
|
||||
ubxg6010_current_gps_data.latitude_degrees_1000000 = pkt->data.navposllh.lat;
|
||||
ubxg6010_current_gps_data.longitude_degrees_1000000 = pkt->data.navposllh.lon;
|
||||
ubxg6010_current_gps_data.altitude_mm = pkt->data.navposllh.hMSL;
|
||||
|
||||
current_gps_data.updated = true;
|
||||
ubxg6010_current_gps_data.updated = true;
|
||||
} else if (pkt->header.messageClass == 0x01 && pkt->header.messageId == 0x03) {
|
||||
ubxg6010_current_gps_data.ok_packets += 1;
|
||||
ubxg6010_current_gps_data.fix_ok = pkt->data.navstatus.flags & 0x01;
|
||||
ubxg6010_current_gps_data.power_safe_mode_state = pkt->data.navstatus.flags2 & 0x03;
|
||||
|
||||
ubxg6010_current_gps_data.updated = true;
|
||||
} else if (pkt->header.messageClass == 0x01 && pkt->header.messageId == 0x06) {
|
||||
current_gps_data.time_of_week_millis = pkt->data.navsol.iTOW;
|
||||
current_gps_data.week = pkt->data.navsol.week;
|
||||
current_gps_data.fix = pkt->data.navsol.gpsFix;
|
||||
current_gps_data.satellites_visible = pkt->data.navsol.numSV;
|
||||
ubxg6010_current_gps_data.time_of_week_millis = pkt->data.navsol.iTOW;
|
||||
ubxg6010_current_gps_data.week = pkt->data.navsol.week;
|
||||
ubxg6010_current_gps_data.fix = pkt->data.navsol.gpsFix;
|
||||
ubxg6010_current_gps_data.satellites_visible = pkt->data.navsol.numSV;
|
||||
ubxg6010_current_gps_data.position_dilution_of_precision = pkt->data.navsol.pDOP;
|
||||
|
||||
current_gps_data.updated = true;
|
||||
ubxg6010_current_gps_data.updated = true;
|
||||
} else if (pkt->header.messageClass == 0x01 && pkt->header.messageId == 0x20) {
|
||||
current_gps_data.time_of_week_millis = pkt->data.navtimegps.iTOW;
|
||||
current_gps_data.week = pkt->data.navtimegps.week;
|
||||
ubxg6010_current_gps_data.time_of_week_millis = pkt->data.navtimegps.iTOW;
|
||||
ubxg6010_current_gps_data.week = pkt->data.navtimegps.week;
|
||||
|
||||
current_gps_data.updated = true;
|
||||
ubxg6010_current_gps_data.updated = true;
|
||||
} else if (pkt->header.messageClass == 0x01 && pkt->header.messageId == 0x21) {
|
||||
current_gps_data.year = pkt->data.navtimeutc.year;
|
||||
current_gps_data.month = pkt->data.navtimeutc.month;
|
||||
current_gps_data.day = pkt->data.navtimeutc.day;
|
||||
current_gps_data.hours = pkt->data.navtimeutc.hour;
|
||||
current_gps_data.minutes = pkt->data.navtimeutc.min;
|
||||
current_gps_data.seconds = pkt->data.navtimeutc.sec;
|
||||
ubxg6010_current_gps_data.year = pkt->data.navtimeutc.year;
|
||||
ubxg6010_current_gps_data.month = pkt->data.navtimeutc.month;
|
||||
ubxg6010_current_gps_data.day = pkt->data.navtimeutc.day;
|
||||
ubxg6010_current_gps_data.hours = pkt->data.navtimeutc.hour;
|
||||
ubxg6010_current_gps_data.minutes = pkt->data.navtimeutc.min;
|
||||
ubxg6010_current_gps_data.seconds = pkt->data.navtimeutc.sec;
|
||||
|
||||
current_gps_data.updated = true;
|
||||
ubxg6010_current_gps_data.updated = true;
|
||||
} else if (pkt->header.messageClass == 0x05 && pkt->header.messageId == 0x01) {
|
||||
ack_received = true;
|
||||
} else if (pkt->header.messageClass == 0x05 && pkt->header.messageId == 0x00) {
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define POWER_SAFE_MODE_STATE_ACQUISITION 0
|
||||
#define POWER_SAFE_MODE_STATE_TRACKING 1
|
||||
#define POWER_SAFE_MODE_STATE_POWER_OPTIMIZED_TRACKING 2
|
||||
#define POWER_SAFE_MODE_STATE_INACTIVE 3
|
||||
|
||||
typedef struct _gps_data {
|
||||
bool updated;
|
||||
|
||||
|
@ -24,8 +29,12 @@ typedef struct _gps_data {
|
|||
int32_t climb_cm_per_second;
|
||||
uint8_t satellites_visible;
|
||||
uint8_t fix;
|
||||
bool fix_ok;
|
||||
uint16_t ok_packets;
|
||||
uint16_t bad_packets;
|
||||
|
||||
uint8_t power_safe_mode_state;
|
||||
uint16_t position_dilution_of_precision; // pDOP
|
||||
} gps_data;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
#include <stm32f10x_rcc.h>
|
||||
#include <stm32f10x_tim.h>
|
||||
#include <misc.h>
|
||||
|
||||
#include "datatimer.h"
|
||||
|
||||
void (*system_handle_data_timer_tick)() = NULL;
|
||||
|
||||
void data_timer_init(uint32_t baud_rate)
|
||||
{
|
||||
// Timer frequency = TIM_CLK/(TIM_PSC+1)/(TIM_ARR + 1)
|
||||
// TIM_CLK =
|
||||
// TIM_PSC = Prescaler
|
||||
// TIM_ARR = Period
|
||||
|
||||
TIM_DeInit(TIM2);
|
||||
|
||||
TIM_TimeBaseInitTypeDef tim_init;
|
||||
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
|
||||
RCC_APB2PeriphResetCmd(RCC_APB1Periph_TIM2, DISABLE);
|
||||
|
||||
tim_init.TIM_Prescaler = 24 - 1; // tick every 1/1000000 s
|
||||
tim_init.TIM_CounterMode = TIM_CounterMode_Up;
|
||||
tim_init.TIM_Period = (uint16_t) ((1000000 / baud_rate) - 1);
|
||||
tim_init.TIM_ClockDivision = TIM_CKD_DIV1;
|
||||
tim_init.TIM_RepetitionCounter = 0;
|
||||
|
||||
TIM_TimeBaseInit(TIM2, &tim_init);
|
||||
|
||||
// No interrupts necessary for data timer, as it is only used for triggering DMA transfers
|
||||
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
|
||||
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
|
||||
|
||||
NVIC_InitTypeDef nvic_init;
|
||||
nvic_init.NVIC_IRQChannel = TIM2_IRQn;
|
||||
nvic_init.NVIC_IRQChannelPreemptionPriority = 0;
|
||||
nvic_init.NVIC_IRQChannelSubPriority = 1;
|
||||
nvic_init.NVIC_IRQChannelCmd = ENABLE;
|
||||
NVIC_Init(&nvic_init);
|
||||
|
||||
TIM_Cmd(TIM2, ENABLE);
|
||||
}
|
||||
|
||||
void data_timer_uninit()
|
||||
{
|
||||
TIM_Cmd(TIM2, DISABLE);
|
||||
|
||||
NVIC_InitTypeDef nvic_init;
|
||||
nvic_init.NVIC_IRQChannel = TIM2_IRQn;
|
||||
nvic_init.NVIC_IRQChannelPreemptionPriority = 0;
|
||||
nvic_init.NVIC_IRQChannelSubPriority = 1;
|
||||
nvic_init.NVIC_IRQChannelCmd = DISABLE;
|
||||
NVIC_Init(&nvic_init);
|
||||
|
||||
TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
|
||||
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
|
||||
}
|
||||
|
||||
void TIM2_IRQHandler(void)
|
||||
{
|
||||
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
|
||||
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
|
||||
|
||||
system_handle_data_timer_tick();
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef __DATATIMER_H
|
||||
#define __DATATIMER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void data_timer_init(uint32_t baud_rate);
|
||||
void data_timer_uninit();
|
||||
|
||||
extern void (*system_handle_data_timer_tick)();
|
||||
|
||||
#endif
|
|
@ -41,6 +41,8 @@ void i2c_init()
|
|||
// NOTE: I2C chip reset is necessary here!
|
||||
I2C_DeInit(I2C_PORT);
|
||||
|
||||
delay_ms(2);
|
||||
|
||||
I2C_InitTypeDef i2c_init;
|
||||
I2C_StructInit(&i2c_init);
|
||||
|
||||
|
@ -52,7 +54,11 @@ void i2c_init()
|
|||
|
||||
I2C_Init(I2C_PORT, &i2c_init);
|
||||
|
||||
while (I2C_GetFlagStatus(I2C_PORT, I2C_FLAG_BUSY));
|
||||
// This loop may get stuck if there is nothing connected in the I²C port pins
|
||||
int count = 1000;
|
||||
while (I2C_GetFlagStatus(I2C_PORT, I2C_FLAG_BUSY) && count--) {
|
||||
delay_ms(1);
|
||||
}
|
||||
|
||||
I2C_Cmd(I2C_PORT, ENABLE);
|
||||
}
|
||||
|
|
|
@ -57,6 +57,11 @@ void pwm_data_timer_dma_request_enable(bool enabled)
|
|||
TIM_DMACmd(TIM2, TIM_DMA_Update, enabled ? ENABLE : DISABLE);
|
||||
}
|
||||
|
||||
void pwm_data_timer_uninit()
|
||||
{
|
||||
TIM_Cmd(TIM2, DISABLE);
|
||||
}
|
||||
|
||||
void pwm_timer_init(uint32_t frequency_hz_100)
|
||||
{
|
||||
TIM_DeInit(TIM15);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
void pwm_data_timer_init();
|
||||
void pwm_data_timer_dma_request_enable(bool enabled);
|
||||
void pwm_data_timer_uninit();
|
||||
|
||||
void pwm_timer_init(uint32_t frequency_hz_100);
|
||||
void pwm_timer_pwm_enable(bool enabled);
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
#include "log.h"
|
||||
|
||||
void log_bytes(int length, char *data)
|
||||
{
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = data[i];
|
||||
if (c >= 0x20 && c <= 0x7e) {
|
||||
log_info("%c", c);
|
||||
} else {
|
||||
log_info(" [%02X] ", c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void log_bytes_hex(int length, char *data)
|
||||
{
|
||||
for (int i = 0; i < length; i++) {
|
||||
log_info("%02X ", data[i]);
|
||||
}
|
||||
}
|
|
@ -23,4 +23,7 @@
|
|||
|
||||
#endif
|
||||
|
||||
void log_bytes(int length, char *data);
|
||||
void log_bytes_hex(int length, char *data);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "hal/spi.h"
|
||||
#include "hal/usart_gps.h"
|
||||
#include "hal/delay.h"
|
||||
#include "hal/datatimer.h"
|
||||
#include "drivers/ubxg6010/ubxg6010.h"
|
||||
#include "drivers/si4032/si4032.h"
|
||||
#include "bmp280_handler.h"
|
||||
|
@ -48,7 +49,10 @@ void handle_timer_tick()
|
|||
int main(void)
|
||||
{
|
||||
bool success;
|
||||
|
||||
// Set up interrupt handlers
|
||||
system_handle_timer_tick = handle_timer_tick;
|
||||
system_handle_data_timer_tick = radio_handle_data_timer_tick;
|
||||
usart_gps_handle_incoming_byte = ubxg6010_handle_incoming_byte;
|
||||
|
||||
log_info("System init\n");
|
||||
|
|
95
src/radio.c
95
src/radio.c
|
@ -7,22 +7,24 @@
|
|||
#include "hal/delay.h"
|
||||
#include "hal/usart_gps.h"
|
||||
#include "codecs/bell/bell.h"
|
||||
#include "codecs/mfsk/mfsk.h"
|
||||
#include "codecs/jtencode/jtencode.h"
|
||||
#include "drivers/ubxg6010/ubxg6010.h"
|
||||
#include "radio_internal.h"
|
||||
#include "radio_si4032.h"
|
||||
#include "radio_si5351.h"
|
||||
#include "radio_payload_aprs.h"
|
||||
#include "radio_payload_horus_v1.h"
|
||||
#include "radio_payload_wspr.h"
|
||||
#include "radio_payload_jtencode.h"
|
||||
#include "radio_payload_fsq.h"
|
||||
|
||||
radio_transmit_entry radio_transmit_schedule[] = {
|
||||
{
|
||||
.enabled = true,
|
||||
.enabled = RADIO_SI4032_TX_APRS,
|
||||
.radio_type = RADIO_TYPE_SI4032,
|
||||
.data_mode = RADIO_DATA_MODE_APRS_1200,
|
||||
.time_sync_seconds = 0,
|
||||
.time_sync_seconds = 1,
|
||||
.time_sync_seconds_offset = 0,
|
||||
.frequency = RADIO_SI4032_TX_FREQUENCY_APRS_1200,
|
||||
.tx_power = RADIO_SI4032_TX_POWER,
|
||||
|
@ -31,7 +33,31 @@ radio_transmit_entry radio_transmit_schedule[] = {
|
|||
.fsk_encoder_api = &bell_fsk_encoder_api,
|
||||
},
|
||||
{
|
||||
.enabled = false,
|
||||
.enabled = RADIO_SI4032_TX_HORUS_V1,
|
||||
.radio_type = RADIO_TYPE_SI4032,
|
||||
.data_mode = RADIO_DATA_MODE_HORUS_V1,
|
||||
.time_sync_seconds = 1,
|
||||
.time_sync_seconds_offset = 0,
|
||||
.frequency = RADIO_SI4032_TX_FREQUENCY_HORUS_V1,
|
||||
.tx_power = RADIO_SI4032_TX_POWER,
|
||||
.symbol_rate = HORUS_V1_BAUD_RATE,
|
||||
.payload_encoder = &radio_horus_v1_payload_encoder,
|
||||
.fsk_encoder_api = &mfsk_fsk_encoder_api,
|
||||
},
|
||||
{
|
||||
.enabled = RADIO_SI5351_TX_WSPR,
|
||||
.radio_type = RADIO_TYPE_SI5351,
|
||||
.time_sync_seconds = 120,
|
||||
.time_sync_seconds_offset = 1,
|
||||
.data_mode = RADIO_DATA_MODE_WSPR,
|
||||
.frequency = RADIO_SI5351_TX_FREQUENCY_WSPR,
|
||||
.tx_power = RADIO_SI5351_TX_POWER,
|
||||
.payload_encoder = &radio_wspr_payload_encoder,
|
||||
.fsk_encoder_api = &jtencode_fsk_encoder_api,
|
||||
.jtencode_mode_type = JTENCODE_MODE_WSPR,
|
||||
},
|
||||
{
|
||||
.enabled = RADIO_SI5351_TX_FT8,
|
||||
.radio_type = RADIO_TYPE_SI5351,
|
||||
.data_mode = RADIO_DATA_MODE_FT8,
|
||||
.time_sync_seconds = 15,
|
||||
|
@ -43,7 +69,7 @@ radio_transmit_entry radio_transmit_schedule[] = {
|
|||
.jtencode_mode_type = JTENCODE_MODE_FT8,
|
||||
},
|
||||
{
|
||||
.enabled = false,
|
||||
.enabled = RADIO_SI5351_TX_JT9,
|
||||
.radio_type = RADIO_TYPE_SI5351,
|
||||
.data_mode = RADIO_DATA_MODE_JT9,
|
||||
.time_sync_seconds = 60,
|
||||
|
@ -55,7 +81,7 @@ radio_transmit_entry radio_transmit_schedule[] = {
|
|||
.jtencode_mode_type = JTENCODE_MODE_JT9,
|
||||
},
|
||||
{
|
||||
.enabled = false,
|
||||
.enabled = RADIO_SI5351_TX_JT4,
|
||||
.radio_type = RADIO_TYPE_SI5351,
|
||||
.data_mode = RADIO_DATA_MODE_JT4,
|
||||
.time_sync_seconds = 60,
|
||||
|
@ -67,7 +93,7 @@ radio_transmit_entry radio_transmit_schedule[] = {
|
|||
.jtencode_mode_type = JTENCODE_MODE_JT4,
|
||||
},
|
||||
{
|
||||
.enabled = false,
|
||||
.enabled = RADIO_SI5351_TX_JT65,
|
||||
.radio_type = RADIO_TYPE_SI5351,
|
||||
.data_mode = RADIO_DATA_MODE_JT65,
|
||||
.time_sync_seconds = 60,
|
||||
|
@ -79,7 +105,7 @@ radio_transmit_entry radio_transmit_schedule[] = {
|
|||
.jtencode_mode_type = JTENCODE_MODE_JT65,
|
||||
},
|
||||
{
|
||||
.enabled = true,
|
||||
.enabled = RADIO_SI5351_TX_FSQ,
|
||||
.radio_type = RADIO_TYPE_SI5351,
|
||||
.data_mode = RADIO_DATA_MODE_FSQ_6,
|
||||
.time_sync_seconds = 0,
|
||||
|
@ -90,18 +116,6 @@ radio_transmit_entry radio_transmit_schedule[] = {
|
|||
.fsk_encoder_api = &jtencode_fsk_encoder_api,
|
||||
.jtencode_mode_type = JTENCODE_MODE_FSQ_6,
|
||||
},
|
||||
{
|
||||
.enabled = false,
|
||||
.radio_type = RADIO_TYPE_SI5351,
|
||||
.time_sync_seconds = 120,
|
||||
.time_sync_seconds_offset = 1,
|
||||
.data_mode = RADIO_DATA_MODE_WSPR,
|
||||
.frequency = RADIO_SI5351_TX_FREQUENCY_WSPR,
|
||||
.tx_power = RADIO_SI5351_TX_POWER,
|
||||
.payload_encoder = &radio_wspr_payload_encoder,
|
||||
.fsk_encoder_api = &jtencode_fsk_encoder_api,
|
||||
.jtencode_mode_type = JTENCODE_MODE_WSPR,
|
||||
},
|
||||
{
|
||||
.end = true,
|
||||
}
|
||||
|
@ -141,6 +155,7 @@ radio_module_state radio_shared_state = {
|
|||
.radio_symbol_count_loop = 0,
|
||||
.radio_dma_transfer_active = false,
|
||||
.radio_manual_transmit_active = false,
|
||||
.radio_interrupt_transmit_active = false,
|
||||
|
||||
.radio_current_fsk_tones = NULL,
|
||||
.radio_current_fsk_tone_count = 0,
|
||||
|
@ -190,14 +205,9 @@ static bool radio_start_transmit(radio_transmit_entry *entry)
|
|||
|
||||
#ifdef SEMIHOSTING_ENABLE
|
||||
log_info("Payload: ");
|
||||
for (uint16_t i = 0; i < radio_current_payload_length; i++) {
|
||||
char c = radio_current_payload[i];
|
||||
if (c >= 0x20 && c <= 0x7e) {
|
||||
log_info("%c", c);
|
||||
} else {
|
||||
log_info(" [%02X] ", c);
|
||||
}
|
||||
}
|
||||
log_bytes_hex(radio_current_payload_length, (char *) radio_current_payload);
|
||||
log_info("\n ");
|
||||
log_bytes(radio_current_payload_length, (char *) radio_current_payload);
|
||||
log_info("\n");
|
||||
#endif
|
||||
|
||||
|
@ -214,6 +224,13 @@ static bool radio_start_transmit(radio_transmit_entry *entry)
|
|||
&radio_shared_state.radio_current_fsk_tones);
|
||||
entry->fsk_encoder_api->set_data(&entry->fsk_encoder, radio_current_payload_length, radio_current_payload);
|
||||
break;
|
||||
case RADIO_DATA_MODE_HORUS_V1:
|
||||
mfsk_encoder_new(&entry->fsk_encoder, MFSK_4, entry->symbol_rate, 100);
|
||||
radio_shared_state.radio_current_symbol_rate = entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder);
|
||||
entry->fsk_encoder_api->get_tones(&entry->fsk_encoder, &radio_shared_state.radio_current_fsk_tone_count,
|
||||
&radio_shared_state.radio_current_fsk_tones);
|
||||
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:
|
||||
|
@ -231,7 +248,8 @@ static bool radio_start_transmit(radio_transmit_entry *entry)
|
|||
strlcpy(locator, current_telemetry_data.locator, 4 + 1);
|
||||
}
|
||||
|
||||
success = jtencode_encoder_new(&entry->fsk_encoder, sizeof(radio_current_symbol_data), radio_current_symbol_data,
|
||||
success = jtencode_encoder_new(&entry->fsk_encoder, sizeof(radio_current_symbol_data),
|
||||
radio_current_symbol_data,
|
||||
entry->jtencode_mode_type, WSPR_CALLSIGN, locator, WSPR_DBM, FSQ_CALLSIGN_FROM);
|
||||
if (!success) {
|
||||
return false;
|
||||
|
@ -311,6 +329,7 @@ static bool radio_stop_transmit(radio_transmit_entry *entry)
|
|||
radio_reset_transmit_state();
|
||||
|
||||
radio_shared_state.radio_manual_transmit_active = false;
|
||||
radio_shared_state.radio_interrupt_transmit_active = false;
|
||||
radio_shared_state.radio_dma_transfer_active = false;
|
||||
|
||||
switch (entry->data_mode) {
|
||||
|
@ -321,6 +340,9 @@ static bool radio_stop_transmit(radio_transmit_entry *entry)
|
|||
case RADIO_DATA_MODE_APRS_1200:
|
||||
bell_encoder_destroy(&entry->fsk_encoder);
|
||||
break;
|
||||
case RADIO_DATA_MODE_HORUS_V1:
|
||||
mfsk_encoder_destroy(&entry->fsk_encoder);
|
||||
break;
|
||||
case RADIO_DATA_MODE_WSPR:
|
||||
case RADIO_DATA_MODE_FT8:
|
||||
case RADIO_DATA_MODE_JT65:
|
||||
|
@ -386,7 +408,8 @@ static void radio_next_transmit_entry()
|
|||
|
||||
void radio_handle_timer_tick()
|
||||
{
|
||||
if (!radio_module_initialized || radio_shared_state.radio_dma_transfer_active || radio_shared_state.radio_manual_transmit_active) {
|
||||
if (!radio_module_initialized || radio_shared_state.radio_dma_transfer_active
|
||||
|| radio_shared_state.radio_manual_transmit_active || radio_shared_state.radio_interrupt_transmit_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -407,6 +430,11 @@ void radio_handle_timer_tick()
|
|||
}
|
||||
}
|
||||
|
||||
void radio_handle_data_timer_tick()
|
||||
{
|
||||
radio_handle_data_timer_si4032();
|
||||
}
|
||||
|
||||
bool radio_handle_time_sync()
|
||||
{
|
||||
// TODO: How to poll GPS time?
|
||||
|
@ -447,7 +475,7 @@ bool radio_handle_time_sync()
|
|||
|
||||
bool is_scheduled_time = time_sync_period_millis < RADIO_TIME_SYNC_THRESHOLD_MS;
|
||||
|
||||
//log_info("time with offset: %lu, sync period: %lu, scheduled: %d\n", time_with_offset_millis, time_sync_period_millis, is_scheduled_time);
|
||||
log_debug("Time with offset: %lu, sync period: %lu, scheduled: %d\n", time_with_offset_millis, time_sync_period_millis, is_scheduled_time);
|
||||
|
||||
if (!is_scheduled_time) {
|
||||
log_info("Time: %lu, GPS fix: %d - Waiting for time sync at every %d seconds with offset of %d\n", time_millis,
|
||||
|
@ -527,7 +555,7 @@ void radio_handle_main_loop()
|
|||
if (radio_shared_state.radio_transmission_active && radio_shared_state.radio_transmit_next_symbol_flag) {
|
||||
radio_shared_state.radio_transmit_next_symbol_flag = false;
|
||||
|
||||
if (!radio_shared_state.radio_manual_transmit_active) {
|
||||
if (!radio_shared_state.radio_manual_transmit_active && !radio_shared_state.radio_interrupt_transmit_active) {
|
||||
bool success = radio_transmit_symbol(radio_current_transmit_entry);
|
||||
if (success) {
|
||||
if (first_symbol) {
|
||||
|
@ -571,6 +599,9 @@ void radio_init()
|
|||
case RADIO_DATA_MODE_APRS_1200:
|
||||
entry->messages = aprs_comment_templates;
|
||||
break;
|
||||
case RADIO_DATA_MODE_HORUS_V1:
|
||||
// No messages
|
||||
break;
|
||||
case RADIO_DATA_MODE_FT8:
|
||||
case RADIO_DATA_MODE_JT65:
|
||||
case RADIO_DATA_MODE_JT9:
|
||||
|
@ -589,7 +620,7 @@ void radio_init()
|
|||
default:
|
||||
break;
|
||||
}
|
||||
if (entry-> messages != NULL) {
|
||||
if (entry->messages != NULL) {
|
||||
for (entry->message_count = 0; entry->messages[entry->message_count] != NULL; entry->message_count++);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
void radio_init();
|
||||
void radio_handle_timer_tick();
|
||||
void radio_handle_data_timer_tick();
|
||||
void radio_handle_main_loop();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -16,6 +16,7 @@ typedef enum _radio_data_mode {
|
|||
RADIO_DATA_MODE_CW = 1,
|
||||
RADIO_DATA_MODE_RTTY,
|
||||
RADIO_DATA_MODE_APRS_1200,
|
||||
RADIO_DATA_MODE_HORUS_V1,
|
||||
RADIO_DATA_MODE_WSPR,
|
||||
RADIO_DATA_MODE_FT8,
|
||||
RADIO_DATA_MODE_JT65,
|
||||
|
@ -64,6 +65,7 @@ typedef struct _radio_module_state {
|
|||
|
||||
volatile bool radio_dma_transfer_active;
|
||||
volatile bool radio_manual_transmit_active;
|
||||
volatile bool radio_interrupt_transmit_active;
|
||||
|
||||
fsk_tone *radio_current_fsk_tones;
|
||||
int8_t radio_current_fsk_tone_count;
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
#include "log.h"
|
||||
#include "radio_payload_aprs.h"
|
||||
|
||||
uint8_t aprs_packet[RADIO_PAYLOAD_MAX_LENGTH];
|
||||
|
||||
uint16_t radio_aprs_encode(uint8_t *payload, uint16_t length, telemetry_data *telemetry_data, char *message)
|
||||
{
|
||||
uint8_t aprs_packet[RADIO_PAYLOAD_MAX_LENGTH];
|
||||
|
||||
aprs_generate_position(aprs_packet, sizeof(aprs_packet), telemetry_data,
|
||||
APRS_SYMBOL_TABLE, APRS_SYMBOL, false, message);
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include "codecs/horus/horus_packet_v1.h"
|
||||
#include "codecs/horus/horus_l2.h"
|
||||
#include "config.h"
|
||||
#include "telemetry.h"
|
||||
#include "radio_payload_horus_v1.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
uint16_t radio_horus_v1_encode(uint8_t *payload, uint16_t length, telemetry_data *telemetry_data, char *message)
|
||||
{
|
||||
horus_packet_v1 horus_packet;
|
||||
|
||||
size_t packet_length = horus_packet_v1_create((uint8_t *) &horus_packet, sizeof(horus_packet),
|
||||
telemetry_data, HORUS_V1_PAYLOAD_ID);
|
||||
|
||||
#ifdef SEMIHOSTING_ENABLE
|
||||
log_info("Horus V1 packet: ");
|
||||
log_bytes_hex((int) packet_length, (char *) &horus_packet);
|
||||
log_info("\n");
|
||||
#endif
|
||||
|
||||
// Preamble to help the decoder lock-on after a quiet period.
|
||||
for (int i = 0; i < HORUS_V1_PREAMBLE_LENGTH; i++) {
|
||||
payload[i] = 0x1b;
|
||||
}
|
||||
|
||||
// Encode the packet, and write into the mfsk buffer.
|
||||
int encoded_length = horus_l2_encode_tx_packet(
|
||||
(unsigned char *) payload + HORUS_V1_PREAMBLE_LENGTH,
|
||||
(unsigned char *) &horus_packet, (int) packet_length);
|
||||
|
||||
return encoded_length + HORUS_V1_PREAMBLE_LENGTH;
|
||||
}
|
||||
|
||||
payload_encoder radio_horus_v1_payload_encoder = {
|
||||
.encode = radio_horus_v1_encode,
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef __RADIO_PAYLOAD_HORUS_V1_H
|
||||
#define __RADIO_PAYLOAD_HORUS_V1_H
|
||||
|
||||
#include "payload.h"
|
||||
|
||||
extern payload_encoder radio_horus_v1_payload_encoder;
|
||||
|
||||
#endif
|
|
@ -5,10 +5,12 @@
|
|||
#include "hal/spi.h"
|
||||
#include "hal/pwm.h"
|
||||
#include "hal/delay.h"
|
||||
#include "hal/datatimer.h"
|
||||
#include "drivers/si4032/si4032.h"
|
||||
#include "log.h"
|
||||
|
||||
#include "radio_si4032.h"
|
||||
#include "codecs/mfsk/mfsk.h"
|
||||
|
||||
/**
|
||||
* I have attempted to implement Bell 202 frequency generation using hardware DMA and PWM, but have failed to generate
|
||||
|
@ -43,7 +45,7 @@ bool radio_start_transmit_si4032(radio_transmit_entry *entry, radio_module_state
|
|||
case RADIO_DATA_MODE_CW:
|
||||
frequency_offset = 1;
|
||||
modulation_type = SI4032_MODULATION_TYPE_OOK;
|
||||
use_direct_mode = true;
|
||||
use_direct_mode = false;
|
||||
break;
|
||||
case RADIO_DATA_MODE_RTTY:
|
||||
frequency_offset = 0;
|
||||
|
@ -55,9 +57,19 @@ bool radio_start_transmit_si4032(radio_transmit_entry *entry, radio_module_state
|
|||
modulation_type = SI4032_MODULATION_TYPE_FSK;
|
||||
use_direct_mode = true;
|
||||
if (si4032_use_dma) {
|
||||
pwm_data_timer_init();
|
||||
radio_si4032_fill_pwm_buffer(0, PWM_TIMER_DMA_BUFFER_SIZE, pwm_timer_dma_buffer);
|
||||
}
|
||||
break;
|
||||
case RADIO_DATA_MODE_HORUS_V1: {
|
||||
fsk_tone *idle_tone = mfsk_get_idle_tone(&entry->fsk_encoder);
|
||||
frequency_offset = (uint16_t) idle_tone->index + HORUS_V1_FREQUENCY_OFFSET;
|
||||
modulation_type = SI4032_MODULATION_TYPE_OOK;
|
||||
use_direct_mode = false;
|
||||
|
||||
data_timer_init(entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -87,6 +99,10 @@ bool radio_start_transmit_si4032(radio_transmit_entry *entry, radio_module_state
|
|||
shared_state->radio_manual_transmit_active = true;
|
||||
}
|
||||
break;
|
||||
case RADIO_DATA_MODE_HORUS_V1:
|
||||
system_disable_tick();
|
||||
shared_state->radio_interrupt_transmit_active = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -147,7 +163,7 @@ static void radio_handle_main_loop_manual_si4032(radio_transmit_entry *entry, ra
|
|||
pwm_timer_set_frequency(precalculated_pwm_periods[tone_index]);
|
||||
shared_state->radio_symbol_count_loop++;
|
||||
delay_us(symbol_delay_bell_202_1200bps_us);
|
||||
};
|
||||
}
|
||||
|
||||
radio_si4032_state_change = false;
|
||||
shared_state->radio_transmission_finished = true;
|
||||
|
@ -162,7 +178,7 @@ static void radio_handle_main_loop_manual_si4032(radio_transmit_entry *entry, ra
|
|||
|
||||
void radio_handle_main_loop_si4032(radio_transmit_entry *entry, radio_module_state *shared_state)
|
||||
{
|
||||
if (entry->radio_type != RADIO_TYPE_SI4032) {
|
||||
if (entry->radio_type != RADIO_TYPE_SI4032 || shared_state->radio_interrupt_transmit_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -178,15 +194,44 @@ void radio_handle_main_loop_si4032(radio_transmit_entry *entry, radio_module_sta
|
|||
}
|
||||
}
|
||||
|
||||
inline void radio_handle_data_timer_si4032()
|
||||
{
|
||||
if (radio_current_transmit_entry->radio_type != RADIO_TYPE_SI4032 || !radio_shared_state.radio_interrupt_transmit_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (radio_current_transmit_entry->data_mode) {
|
||||
case RADIO_DATA_MODE_HORUS_V1: {
|
||||
fsk_encoder_api *fsk_encoder_api = radio_current_transmit_entry->fsk_encoder_api;
|
||||
fsk_encoder *fsk_enc = &radio_current_transmit_entry->fsk_encoder;
|
||||
int8_t tone_index;
|
||||
|
||||
tone_index = fsk_encoder_api->next_tone(fsk_enc);
|
||||
if (tone_index < 0) {
|
||||
log_info("Horus V1 TX finished\n");
|
||||
radio_shared_state.radio_interrupt_transmit_active = false;
|
||||
radio_shared_state.radio_transmission_finished = true;
|
||||
system_enable_tick();
|
||||
break;
|
||||
}
|
||||
|
||||
si4032_set_frequency_offset_small(tone_index + HORUS_V1_FREQUENCY_OFFSET);
|
||||
radio_shared_state.radio_symbol_count_interrupt++;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool radio_stop_transmit_si4032(radio_transmit_entry *entry, radio_module_state *shared_state)
|
||||
{
|
||||
bool use_direct_mode = false;
|
||||
|
||||
switch (entry->data_mode) {
|
||||
case RADIO_DATA_MODE_CW:
|
||||
use_direct_mode = true;
|
||||
break;
|
||||
case RADIO_DATA_MODE_RTTY:
|
||||
case RADIO_DATA_MODE_HORUS_V1:
|
||||
use_direct_mode = false;
|
||||
break;
|
||||
case RADIO_DATA_MODE_APRS_1200:
|
||||
|
@ -208,9 +253,14 @@ bool radio_stop_transmit_si4032(radio_transmit_entry *entry, radio_module_state
|
|||
switch (entry->data_mode) {
|
||||
case RADIO_DATA_MODE_APRS_1200:
|
||||
if (si4032_use_dma) {
|
||||
pwm_data_timer_uninit();
|
||||
system_enable_tick();
|
||||
}
|
||||
break;
|
||||
case RADIO_DATA_MODE_HORUS_V1:
|
||||
data_timer_uninit();
|
||||
system_enable_tick();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
bool radio_start_transmit_si4032(radio_transmit_entry *entry, radio_module_state *shared_state);
|
||||
bool radio_transmit_symbol_si4032(radio_transmit_entry *entry, radio_module_state *shared_state);
|
||||
void radio_handle_main_loop_si4032(radio_transmit_entry *entry, radio_module_state *shared_state);
|
||||
void radio_handle_data_timer_si4032();
|
||||
bool radio_stop_transmit_si4032(radio_transmit_entry *entry, radio_module_state *shared_state);
|
||||
void radio_init_si4032();
|
||||
|
||||
|
|
|
@ -21,6 +21,6 @@ typedef struct _telemetry_data {
|
|||
char locator[LOCATOR_PAIR_COUNT_FULL * 2 + 1];
|
||||
} telemetry_data;
|
||||
|
||||
void telemetry_collect();
|
||||
void telemetry_collect(telemetry_data *data);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -41,23 +41,23 @@ size_t template_replace(char *dest, size_t dest_len, char *src, telemetry_data *
|
|||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$bu", replacement);
|
||||
|
||||
snprintf(replacement, sizeof(replacement), "%d", data->temperature_celsius_100 / 100);
|
||||
snprintf(replacement, sizeof(replacement), "%d", (int) data->temperature_celsius_100 / 100);
|
||||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$te", replacement);
|
||||
|
||||
snprintf(replacement, sizeof(replacement), "%d", data->internal_temperature_celsius_100 / 100);
|
||||
snprintf(replacement, sizeof(replacement), "%d", (int) data->internal_temperature_celsius_100 / 100);
|
||||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$ti", replacement);
|
||||
|
||||
snprintf(replacement, sizeof(replacement), "%d", data->humidity_percentage_100 / 100);
|
||||
snprintf(replacement, sizeof(replacement), "%d", (int) data->humidity_percentage_100 / 100);
|
||||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$hu", replacement);
|
||||
|
||||
snprintf(replacement, sizeof(replacement), "%d", data->pressure_mbar_100 / 100);
|
||||
snprintf(replacement, sizeof(replacement), "%d", (int) data->pressure_mbar_100 / 100);
|
||||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$pr", replacement);
|
||||
|
||||
snprintf(replacement, sizeof(replacement), "%u", data->gps.time_of_week_millis);
|
||||
snprintf(replacement, sizeof(replacement), "%u", (unsigned int) data->gps.time_of_week_millis);
|
||||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$tow", replacement);
|
||||
|
||||
|
@ -77,15 +77,15 @@ size_t template_replace(char *dest, size_t dest_len, char *src, telemetry_data *
|
|||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$sv", replacement);
|
||||
|
||||
snprintf(replacement, sizeof(replacement), "%05d", data->gps.latitude_degrees_1000000 / 10000);
|
||||
snprintf(replacement, sizeof(replacement), "%05d", (int) data->gps.latitude_degrees_1000000 / 10000);
|
||||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$lat", replacement);
|
||||
|
||||
snprintf(replacement, sizeof(replacement), "%05d", data->gps.longitude_degrees_1000000 / 10000);
|
||||
snprintf(replacement, sizeof(replacement), "%05d", (int) data->gps.longitude_degrees_1000000 / 10000);
|
||||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$lon", replacement);
|
||||
|
||||
snprintf(replacement, sizeof(replacement), "%d", data->gps.altitude_mm / 1000);
|
||||
snprintf(replacement, sizeof(replacement), "%d", (int) data->gps.altitude_mm / 1000);
|
||||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$alt", replacement);
|
||||
|
||||
|
@ -93,11 +93,11 @@ size_t template_replace(char *dest, size_t dest_len, char *src, telemetry_data *
|
|||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$gs", replacement);
|
||||
|
||||
snprintf(replacement, sizeof(replacement), "%d", data->gps.climb_cm_per_second / 100);
|
||||
snprintf(replacement, sizeof(replacement), "%d", (int) data->gps.climb_cm_per_second / 100);
|
||||
strlcpy(temp, dest, dest_len);
|
||||
str_replace(dest, dest_len, temp, "$cl", replacement);
|
||||
|
||||
snprintf(replacement, sizeof(replacement), "%03d", data->gps.heading_degrees_100000 / 100000);
|
||||
snprintf(replacement, sizeof(replacement), "%03d", (int) data->gps.heading_degrees_100000 / 100000);
|
||||
strlcpy(temp, dest, dest_len);
|
||||
size_t len = str_replace(dest, dest_len, temp, "$he", replacement);
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue