More ADS-B TX experimentation

Lots of junk added in Numbers Station regarding voice files
Removed warnings caused by unfinished ADS-B function
This commit is contained in:
furrtek 2017-05-01 10:42:09 +01:00
parent 790ec34ac0
commit bebec9ccf7
12 changed files with 224 additions and 172 deletions

View File

@ -23,6 +23,7 @@
// Color bitmaps generated with:
// Gimp image > indexed colors (16), then "xxd -i *.bmp"
//TODO: Waveform widget as FFT view in scanner
//BUG: Replay freezes when SD card not present
//BUG: RDS doesn't stop baseband when stopping tx ?
//BUG: Check AFSK transmit end, skips last bits ?

View File

@ -76,6 +76,7 @@ void generate_frame_emergency(uint8_t * const adsb_frame, const uint32_t ICAO_ad
ADSB_generate_CRC(adsb_frame);
}
/*
void generate_frame_pos(uint8_t * const adsb_frame, const uint32_t ICAO_address, const uint32_t altitude,
const float latitude, const float longitude) {
uint8_t c, time_parity;
@ -103,9 +104,9 @@ void generate_frame_pos(uint8_t * const adsb_frame, const uint32_t ICAO_address,
//rlat = delta_lat * ((yz / 524288.0) + int(lat / delta_lat));
//delta_lon = 360.0 / (NL(rlat) - time_parity);
//xz = 524288.0 * (mod(lon, delta_lon) / delta_lon); // Round to int !
/*if (time_parity) {
if (time_parity) {
A = sign(rlat0)[NL(rlat0) - NL(rlat1)];
}*/
}
// int xz and yz, then:
// xz >>= 2;
// yz >>= 2;
@ -115,13 +116,15 @@ void generate_frame_pos(uint8_t * const adsb_frame, const uint32_t ICAO_address,
adsb_frame[5] = ((altitude_coded & 0x7F0) >> 3) | 1;
adsb_frame[6] = ((altitude_coded & 0x00F) << 4) | (LAT >> 15); // Then 0, even/odd, and the 2 LAT-CPR MSBs
}
*/
void ADSB_generate_CRC(uint8_t * const in_frame) {
uint8_t adsb_crc[14]; // Temp buffer
uint8_t b, c, s, bitn;
const uint32_t crc_poly = 0x1205FFF;
in_frame[11] = 0x00; // Clear CRC
// Clear CRC
in_frame[11] = 0x00;
in_frame[12] = 0x00;
in_frame[13] = 0x00;
@ -140,7 +143,6 @@ void ADSB_generate_CRC(uint8_t * const in_frame) {
// Insert CRC in frame
memcpy(&in_frame[11], &adsb_crc[11], 3);
}
} /* namespace adsb */

View File

@ -33,8 +33,8 @@ namespace adsb {
void make_frame_mode_s(uint8_t * const adsb_frame, const uint32_t ICAO_address);
void generate_frame_id(uint8_t * const adsb_frame, const uint32_t ICAO_address, std::string & callsign);
void generate_frame_pos(uint8_t * const adsb_frame, const uint32_t ICAO_address, const uint32_t altitude,
const float latitude, const float longitude);
//void generate_frame_pos(uint8_t * const adsb_frame, const uint32_t ICAO_address, const uint32_t altitude,
// const float latitude, const float longitude);
void generate_frame_emergency(uint8_t * const adsb_frame, const uint32_t ICAO_address, const uint8_t code);
void ADSB_generate_CRC(uint8_t * const in_message);

View File

@ -127,13 +127,25 @@ static void to_string_hex_internal(char* p, const uint64_t n, const int32_t l) {
}
}
std::string to_string_hex(const uint64_t n, const int32_t l) {
std::string to_string_hex(const uint64_t n, int32_t l) {
char p[32];
l = std::min(l, 31L);
to_string_hex_internal(p, n, l - 1);
p[l] = 0;
return p;
}
std::string to_string_hex_array(uint8_t * const array, const int32_t l) {
std::string str_return = "";
uint8_t bytes;
for (bytes = 0; bytes < l; bytes++)
str_return += to_string_hex(array[bytes], 2);
return str_return;
}
std::string to_string_datetime(const rtc::RTC& value) {
return to_string_dec_uint(value.year(), 4, '0') + "/" +
to_string_dec_uint(value.month(), 2, '0') + "/" +

View File

@ -34,6 +34,7 @@ std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
std::string to_string_dec_uint(const uint32_t n, const int32_t l = 0, const char fill = 0);
std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0);
std::string to_string_hex(const uint64_t n, const int32_t l = 0);
std::string to_string_hex_array(uint8_t * const array, const int32_t l = 0);
std::string to_string_short_freq(const uint64_t f, const int32_t l = 4);

View File

@ -38,7 +38,8 @@ using namespace portapack;
namespace ui {
void ADSBTxView::focus() {
sym_icao.focus();
//sym_icao.focus();
tx_view.focus();
}
ADSBTxView::~ADSBTxView() {
@ -59,21 +60,15 @@ void ADSBTxView::generate_frame() {
memset(adsb_bin, 0, 112);
// Convert to binary (1 bit per byte, faster for baseband code)
// Convert to binary (1 byte per bit, faster for baseband code)
for (c = 0; c < 112; c++) {
if ((adsb_frame[c >> 3] << (c & 7)) & 0x80)
adsb_bin[c] = 0xFF;
adsb_bin[c] = 1;
}
// Display for debug
str_debug = "";
for (c = 0; c < 7; c++)
str_debug += to_string_hex(adsb_frame[c], 2);
text_frame_a.set(str_debug);
str_debug = "";
for (c = 0; c < 7; c++)
str_debug += to_string_hex(adsb_frame[c + 7], 2);
text_frame_b.set(str_debug);
// Display in hex for debug
text_frame_a.set(to_string_hex_array(&adsb_frame[0], 7));
text_frame_b.set(to_string_hex_array(&adsb_frame[7], 7));
button_callsign.set_text(callsign);
}
@ -81,25 +76,25 @@ void ADSBTxView::generate_frame() {
bool ADSBTxView::start_tx() {
generate_frame();
transmitter_model.set_tuning_frequency(434000000); // FOR TESTING - DEBUG
transmitter_model.set_sampling_rate(2000000U);
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
memcpy(shared_memory.bb_data.data, adsb_bin, 112);
baseband::set_adsb();
transmitter_model.set_tuning_frequency(434000000); // FOR TESTING - DEBUG
transmitter_model.set_sampling_rate(4000000U);
transmitter_model.set_rf_amp(true);
transmitter_model.set_vga(40);
transmitter_model.set_baseband_bandwidth(2500000);
transmitter_model.enable();
return false; // DEBUG
}
void ADSBTxView::on_txdone(const int n) {
void ADSBTxView::on_txdone(const bool v) {
//size_t sr;
if (n == 200) {
if (v) {
transmitter_model.disable();
tx_view.set_transmitting(false);
//progress.set_value(0);
} else {
//progress.set_value(n);

View File

@ -61,7 +61,7 @@ private:
bool start_tx();
void generate_frame();
void generate_frame_pos();
void on_txdone(const int n);
void on_txdone(const bool v);
const Style style_val {
.font = font::fixed_8x16,
@ -166,7 +166,7 @@ private:
Message::ID::TXDone,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXDoneMessage*>(p);
this->on_txdone(message.progress);
this->on_txdone(message.done);
}
};
};

View File

@ -34,6 +34,8 @@ using namespace portapack;
namespace ui {
// TODO: This app takes way too much space, find a way to shrink/simplify or make it an SD card module (loadable)
void NumbersStationView::focus() {
if (file_error)
nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT, nullptr);
@ -46,29 +48,34 @@ NumbersStationView::~NumbersStationView() {
baseband::shutdown();
}
void NumbersStationView::on_tuning_frequency_changed(rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
NumbersStationView::wav_file_t * NumbersStationView::get_wav(uint32_t index) {
return &current_voice->available_wavs[index];
}
void NumbersStationView::prepare_audio() {
uint8_t code;
wav_file_t * wav_file;
/*if (sample_counter >= sample_duration) {
if (sample_counter >= sample_length) {
if (segment == ANNOUNCE) {
if (!announce_loop) {
code_index = 0;
segment = MESSAGE;
} else {
reader->open("/numbers/announce.wav");
sample_duration = sound_sizes[10];
wav_file = get_wav(11);
reader->open(current_voice->dir + file_names[wav_file->index].name + ".wav");
sample_length = wav_file->length;
announce_loop--;
}
}
if (segment == MESSAGE) {
code = symfield_code.get_sym(code_index);
if (code_index == 25)
if (code_index == 25) {
transmitter_model.disable();
return;
}
code = symfield_code.get_sym(code_index);
if (code >= 10) {
memset(audio_buffer, 0, 1024);
@ -78,10 +85,12 @@ void NumbersStationView::prepare_audio() {
pause = 33075; // P: 0.75s @ 44100Hz
} else if (code == 12) {
transmitter_model.disable();
return;
}
} else {
reader->open("/numbers/" + file_names[code] + ".wav");
sample_duration = sound_sizes[code];
wav_file = get_wav(code);
reader->open(current_voice->dir + file_names[code].name + ".wav");
sample_length = wav_file->length;
}
code_index++;
}
@ -102,17 +111,17 @@ void NumbersStationView::prepare_audio() {
if (pause >= 1024) {
pause -= 1024;
} else {
sample_counter = sample_duration;
sample_counter = sample_length;
pause = 0;
}
}
baseband::set_fifo_data(audio_buffer);*/
baseband::set_fifo_data(audio_buffer);
}
void NumbersStationView::start_tx() {
//sample_duration = sound_sizes[10]; // Announce
sample_counter = sample_duration;
//sample_length = sound_sizes[10]; // Announce
sample_counter = sample_length;
code_index = 0;
announce_loop = 2;
@ -126,7 +135,7 @@ void NumbersStationView::start_tx() {
transmitter_model.enable();
baseband::set_audiotx_data(
(1536000 / 44100) - 1,
(1536000 / 44100) - 1, // TODO: Read wav file's samplerate
12000,
1,
false,
@ -135,28 +144,37 @@ void NumbersStationView::start_tx() {
}
void NumbersStationView::on_tick_second() {
if (check_armed.value()) {
armed_blink = not armed_blink;
if (armed_blink)
check_armed.set_style(&style_red);
else
check_armed.set_style(&style());
check_armed.set_dirty();
}
armed_blink = not armed_blink;
if (armed_blink)
check_armed.set_style(&style_red);
else
check_armed.set_style(&style());
check_armed.set_dirty();
}
void NumbersStationView::on_voice_changed(size_t index) {
std::string flags_string = "";
std::string code_list = "";
if (voices[index].accent) {
flags_string = "^";
}
if (voices[index].announce) {
flags_string += "A";
}
text_voice_flags.set(flags_string);
for (const auto& wavs : voices[index].available_wavs)
code_list += wavs.code;
for (uint32_t c = 0; c < 25; c++)
symfield_code.set_symbol_list(c, code_list);
current_voice = &voices[index];
}
bool NumbersStationView::check_wav_validity(const std::string dir, const std::string file) {
if (reader->open("/numbers/" + dir + "/" + file)) {
// Check format (mono, 8 bits)
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8))
return true;
else
return false;
} else
return false;
}
NumbersStationView::NumbersStationView(
@ -167,8 +185,9 @@ NumbersStationView::NumbersStationView(
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t voice_options;
uint32_t c, i, ia;
voice_t temp_voice { };
bool valid;
uint32_t c;
//uint8_t y, m, d, dayofweek;
reader = std::make_unique<WAVFileReader>();
@ -179,42 +198,39 @@ NumbersStationView::NumbersStationView(
file_error = true;
return;
}
// This is awfully repetitive
for (const auto& dir : directory_list) {
i = 0;
c = 0;
for (const auto& file_name : file_names) {
valid = false;
if (reader->open("/numbers/" + dir.string() + "/" + file_name + ".wav")) {
// Check format (mono, 8 bits)
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8))
valid = true;
valid = check_wav_validity(dir.string(), file_name.name + ".wav");
if ((!valid) && (file_name.required)) {
temp_voice.available_wavs.clear();
break; // Invalid required file means invalid voice
} else if (valid) {
temp_voice.available_wavs.push_back({ file_name.code, c++, 0, 0 }); // TODO: Needs length and samplerate
}
if (!valid) {
if (i < 10)
i = 0; // Missingno, invalid voice
break;
}
i++;
}
if (i) {
// Voice ok, are there accent files ?
ia = 0;
if (!temp_voice.available_wavs.empty()) {
// Voice can be used, are there accent files ?
c = 0;
for (const auto& file_name : file_names) {
valid = false;
if (reader->open("/numbers/" + dir.string() + "/" + file_name + "a.wav")) {
// Check format (mono, 8 bits)
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8))
valid = true;
valid = check_wav_validity(dir.string(), file_name.name + "a.wav");
if ((!valid) && (file_name.required)) {
c = 0;
break; // Invalid required file means accents can't be used
} else if (valid) {
c++;
}
if (!valid)
break;
ia++;
}
voices.push_back({ dir.string(), (ia >= 10), (i == 11) });
temp_voice.accent = c ? true : false;
temp_voice.dir = dir.string();
voices.push_back(temp_voice);
}
}
if (!voices.size()) {
if (voices.empty()) {
file_error = true;
return;
}
@ -254,9 +270,6 @@ NumbersStationView::NumbersStationView(
}
};
for (c = 0; c < 25; c++)
symfield_code.set_symbol_list(c, "0123456789pPE");
// DEBUG
symfield_code.set_sym(0, 10);
symfield_code.set_sym(1, 3);

View File

@ -42,6 +42,11 @@ class NumbersStationView : public View {
public:
NumbersStationView(NavigationView& nav);
~NumbersStationView();
NumbersStationView(const NumbersStationView&) = delete;
NumbersStationView(NumbersStationView&&) = delete;
NumbersStationView& operator=(const NumbersStationView&) = delete;
NumbersStationView& operator=(NumbersStationView&&) = delete;
void focus() override;
@ -64,60 +69,66 @@ private:
.foreground = Color::red()
};
typedef struct {
char code;
uint32_t index;
uint32_t length;
uint32_t samplerate;
} wav_file_t;
struct voice_t {
std::string dir;
std::vector<wav_file_t> available_wavs;
bool accent;
bool announce;
};
std::vector<voice_t> voices { };
voice_t * current_voice { };
struct wav_file_list_t {
std::string name;
bool required;
char code;
};
const std::vector<wav_file_list_t> file_names = {
{ "0", true, '0' },
{ "1", true, '1' },
{ "2", true, '2' },
{ "3", true, '3' },
{ "4", true, '4' },
{ "5", true, '5' },
{ "6", true, '6' },
{ "7", true, '7' },
{ "8", true, '8' },
{ "9", true, '9' },
{ "announce", false, 'A' }
};
segments segment { IDLE };
bool armed { false };
bool file_error { false };
std::vector<voice_t> voices;
const std::vector<std::string> file_names = {
{ "0" },
{ "1" },
{ "2" },
{ "3" },
{ "4" },
{ "5" },
{ "6" },
{ "7" },
{ "8" },
{ "9" },
{ "announce" }
};
// const uint8_t month_table[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
// const char * day_of_week[7] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
std::unique_ptr<WAVFileReader> reader { };
uint8_t code_index, announce_loop;
uint32_t sample_counter;
uint32_t sample_duration;
int8_t audio_buffer[1024];
uint32_t pause = 0;
uint8_t code_index { 0 }, announce_loop { 0 };
uint32_t sample_counter { 0 };
uint32_t sample_length { 0 };
int8_t audio_buffer[1024] { };
uint32_t pause { 0 };
bool armed_blink { false };
SignalToken signal_token_tick_second { };
wav_file_t * get_wav(uint32_t index);
bool check_wav_validity(const std::string dir, const std::string file);
void on_voice_changed(size_t index);
void on_tick_second();
void on_tuning_frequency_changed(rf::Frequency f);
void on_tick_second();
void prepare_audio();
void start_tx();
// Schedule: save on sd card
// For each day of the week, max 8 messages ?
// For each message: Normal, accent. Can chose accent on first or last digit
// Prelude with number of repeats
// Message 1 with number of repeats
// Interlude ?
// Message 2 with number of repeats
// End
// Frequency list and sequence
Labels labels {
{ { 2 * 8, 5 * 8 }, "Voice: Flags:", Color::light_grey() },
{ { 1 * 8, 8 * 8 }, "Code:", Color::light_grey() }

View File

@ -29,57 +29,67 @@
void ADSBTXProcessor::execute(const buffer_c8_t& buffer) {
// This is called at 2M/2048 = 977Hz
// This is called at 4M/2048 = 1953Hz
// One pulse = 500ns = 2 samples
// One bit = 2 pulses = 1us = 4 samples
if (!configured) return;
for (size_t i = 0; i < buffer.count; i++) {
// Synthesis at 2M
/*if (preamble == true) {
if (bit_pos >= 16) {
bit_pos = 0;
preamble = false;
bit_part = 0;
} else {
cur_bit = (preamble_parts << bit_pos) >> 15;
bit_pos++;
}
}*/
//if (preamble == false) {
if (!bit_part) {
if (bit_pos >= 112) {
// Stop
message.progress = 200;
shared_memory.application_queue.push(message);
configured = false;
cur_bit = 0;
} else {
cur_bit = 0; //shared_memory.tx_data[bit_pos];
bit_pos++;
bit_part = 1;
if (!sample) {
sample = 3;
if (active) {
if (preamble) {
if (bit_pos >= 16) {
preamble = false;
bit_pos = 0;
} else {
cur_bit = (preamble_parts << bit_pos) >> 15;
bit_pos++;
}
}
if (!preamble) {
if (bit_pos >= 112) {
active = false; // Stop
cur_bit = 0;
} else {
cur_bit = shared_memory.bb_data.data[bit_pos];
bit_pos++;
}
}
} else {
bit_part = 0;
cur_bit ^= 1;
//cur_bit = 0;
if (bit_pos == 8192) { // ?
configured = false;
message.done = true;
shared_memory.application_queue.push(message);
}
bit_pos++;
}
//}
} else
sample--;
// 8D48: 10001101 01001000
// 1001010110100110 0110010110010101
if (sample == 1)
cur_bit ^= 1; // Invert
delta = tone_sample * fm_delta;
tone_sample += 128;
if (cur_bit) {
phase = (phase + 0x1FE00); // What ?
sphase = phase + (64 << 18);
phase += delta;
sphase = phase + (64 << 24);
re = (sine_table_i8[(sphase & 0x03FC0000) >> 18]);
im = (sine_table_i8[(phase & 0x03FC0000) >> 18]);
re = (sine_table_i8[(sphase & 0xFF000000U) >> 24]);
im = (sine_table_i8[(phase & 0xFF000000U) >> 24]);
} else {
re = 0;
im = 0;
}
buffer.p[i] = {(int8_t)re, (int8_t)im};
buffer.p[i] = {re, im};
}
}
@ -87,11 +97,14 @@ void ADSBTXProcessor::on_message(const Message* const p) {
const auto message = *reinterpret_cast<const ADSBConfigureMessage*>(p);
if (message.id == Message::ID::ADSBConfigure) {
bit_part = 0;
bit_pos = 0;
sample = 0;
cur_bit = 0;
preamble = true;
active = true;
configured = true;
fm_delta = 50000 * (0xFFFFFFULL / 4000000); // Fixed bw for now
}
}

View File

@ -26,6 +26,8 @@
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#define TEST_F2D(f) (uint32_t)((f) * ((1ULL << 32) / 4000000))
class ADSBTXProcessor : public BasebandProcessor {
public:
void execute(const buffer_c8_t& buffer) override;
@ -35,18 +37,20 @@ public:
private:
bool configured = false;
BasebandThread baseband_thread { 2000000, this, NORMALPRIO + 20, baseband::Direction::Transmit }; // 2280000
BasebandThread baseband_thread { 4000000, this, NORMALPRIO + 20, baseband::Direction::Transmit };
const uint16_t preamble_parts = 0b1010000101000000;
uint8_t bit_part;
bool preamble;
int8_t re, im;
uint16_t bit_pos = 0;
uint8_t cur_bit = 0;
uint32_t phase, sphase;
int32_t sig, frq;
bool preamble { }, active { };
uint16_t bit_pos { 0 };
uint8_t cur_bit { 0 };
uint32_t sample { 0 };
uint32_t tone_phase { 0 };
uint32_t fm_delta { 0 };
uint32_t phase { 0 }, sphase { 0 };
int32_t tone_sample { 0 }, delta { 0 };
int8_t re { }, im { };
TXDoneMessage message;
TXDoneMessage message { };
};
#endif

Binary file not shown.