Xylos (CCIR tones) TX, jammer update, SD card mod load

Xylos TX (CCIR tones) ;)
Jammer update, still buggy and inefficient
SD card module loader update
This commit is contained in:
furrtek
2016-01-03 07:24:30 +01:00
parent 5a5a4c3525
commit 802ac496e9
28 changed files with 2272 additions and 1143 deletions
+3
View File
@@ -172,6 +172,9 @@ CPPSRC = main.cpp \
ui_receiver.cpp \
ui_spectrum.cpp \
ui_loadmodule.cpp \
ui_afskrx.cpp \
ui_sigfrx.cpp \
ui_xylos.cpp \
receiver_model.cpp \
transmitter_model.cpp \
spectrum_color_lut.cpp \
+6 -1
View File
@@ -19,8 +19,13 @@
* Boston, MA 02110-1301, USA.
*/
//TODO: check jammer bandwidths
//TODO: GSM channel detector
//TODO: wait_for_switch() in baseband-tx !
//TODO: AFSK receiver
//TODO: SIGFOX RX/TX
//TODO: Reset baseband if module not found (instead of lockup in RAM loop)
//TODO: Module name/filename in modules.hpp to indicate requirement in case it's not found
//TODO: Module name/filename in modules.hpp to indicate requirement in case it's not found ui_loadmodule
//TODO: LCD backlight PWM
//TODO: BUG: Crash after TX stop
//TODO: Check bw setting in LCR TX
+2 -2
View File
@@ -1,2 +1,2 @@
const char md5_baseband[16] = {0xca,0x05,0xc3,0xbf,0x78,0x10,0xad,0xac,0x2a,0x2b,0x31,0x19,0xf9,0xe8,0x91,0x26,};
const char md5_baseband_tx[16] = {0xe7,0x28,0x33,0x67,0x45,0x37,0x1e,0x13,0x3c,0x85,0xb5,0x91,0x51,0xaa,0xed,0x4f,};
const char md5_baseband[16] = {0xce,0x87,0x2b,0x2c,0x9e,0x74,0xe8,0x1c,0x1c,0xe9,0xfc,0xc2,0x40,0xc3,0x32,0xd5,};
const char md5_baseband_tx[16] = {0x1b,0xef,0x34,0x50,0x45,0xd7,0xae,0x7c,0xb5,0x4f,0x0c,0x5a,0x80,0xa0,0xbc,0x05,};
File diff suppressed because it is too large Load Diff
+144
View File
@@ -0,0 +1,144 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_afskrx.hpp"
#include "ui_spectrum.hpp"
#include "ui_console.hpp"
#include "ff.h"
#include "portapack.hpp"
using namespace portapack;
#include "ui_receiver.hpp"
namespace ui {
AFSKRXView::AFSKRXView(
NavigationView& nav,
ReceiverModel& receiver_model
) : receiver_model(receiver_model)
{
add_children({ {
&button_done,
&text_rx
} } );
button_done.on_select = [&nav](Button&){
nav.pop();
};
receiver_model.set_baseband_configuration({
.mode = 6,
.sampling_rate = 3072000,
.decimation_factor = 4,
});
receiver_model.set_baseband_bandwidth(1750000);
}
AFSKRXView::~AFSKRXView() {
receiver_model.disable();
}
void AFSKRXView::on_show() {
auto& message_map = context().message_map();
message_map.register_handler(Message::ID::AFSKData,
[this](Message* const p) {
const auto message = static_cast<const AFSKDataMessage*>(p);
this->on_data_afsk(*message);
}
);
receiver_model.enable();
}
void AFSKRXView::on_hide() {
auto& message_map = context().message_map();
message_map.unregister_handler(Message::ID::AFSKData);
}
void AFSKRXView::on_data_afsk(const AFSKDataMessage& message) {
Coord oy,ny;
//text_rx.set(to_string_dec_int(abs(message.data), 8, '0'));
portapack::display.fill_rectangle({0,160-64,240,128},{32,32,32});
oy = 160;
for (int c=0;c<128;c++) {
ny = 160-(message.data[c]>>10);
portapack::display.draw_line({static_cast<Coord>(c),oy},{static_cast<Coord>(c+1),ny},{255,127,0});
oy = ny;
}
/*auto payload = message.packet.payload;
auto payload_length = message.packet.bits_received;
std::string hex_data;
std::string hex_error;
uint8_t byte_data = 0;
uint8_t byte_error = 0;
for(size_t i=0; i<payload_length; i+=2) {
const auto bit_data = payload[i+1];
const auto bit_error = (payload[i+0] == payload[i+1]);
byte_data <<= 1;
byte_data |= bit_data ? 1 : 0;
byte_error <<= 1;
byte_error |= bit_error ? 1 : 0;
if( ((i >> 1) & 7) == 7 ) {
hex_data += to_string_hex(byte_data, 2);
hex_error += to_string_hex(byte_error, 2);
}
}
auto console = reinterpret_cast<Console*>(widget_content.get());
console->writeln(hex_data.substr(0, 240 / 8));
if( !f_error(&fil_tpms) ) {
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
std::string timestamp =
to_string_dec_uint(datetime.year(), 4) +
to_string_dec_uint(datetime.month(), 2, '0') +
to_string_dec_uint(datetime.day(), 2, '0') +
to_string_dec_uint(datetime.hour(), 2, '0') +
to_string_dec_uint(datetime.minute(), 2, '0') +
to_string_dec_uint(datetime.second(), 2, '0');
const auto tuning_frequency = receiver_model.tuning_frequency();
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
const auto tuning_frequency_str = to_string_dec_uint(tuning_frequency, 10);
std::string log = timestamp + " " + tuning_frequency_str + " FSK 38.4 19.2 " + hex_data + "/" + hex_error + "\r\n";
f_puts(log.c_str(), &fil_tpms);
f_sync(&fil_tpms);
}*/
}
void AFSKRXView::focus() {
button_done.focus();
}
} /* namespace ui */
+76
View File
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_AFSKRX_H__
#define __UI_AFSKRX_H__
#include "ui.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_widget.hpp"
#include "ui_console.hpp"
#include "utility.hpp"
#include "receiver_model.hpp"
#include <cstddef>
#include <cstdint>
#include <algorithm>
#include <functional>
namespace ui {
class AFSKRXView : public View {
public:
AFSKRXView(NavigationView& nav, ReceiverModel& receiver_model);
~AFSKRXView();
void focus() override;
void on_show() override;
void on_hide() override;
private:
ReceiverModel& receiver_model;
std::unique_ptr<Widget> widget_content;
Button button_done {
{ 4 * 8, 0 * 16, 3 * 8, 16 },
" < "
};
Text text_rx {
{ 1 * 8, 2 * 16, 28 * 8, 16 },
"Ready..."
};
void on_tuning_frequency_changed(rf::Frequency f);
void on_edit_frequency();
void on_data_afsk(const AFSKDataMessage& message);
};
} /* namespace ui */
#endif/*__UI_RECEIVER_H__*/
+2 -2
View File
@@ -283,8 +283,8 @@ LCRView::LCRView(
make_frame();
shared_memory.afsk_samples_per_bit = 228000/portapack::persistent_memory::afsk_bitrate();
shared_memory.afsk_phase_inc_mark = portapack::persistent_memory::afsk_mark_freq()*(0x10000*256)/2280;
shared_memory.afsk_phase_inc_space = portapack::persistent_memory::afsk_space_freq()*(0x10000*256)/2280;
shared_memory.afsk_phase_inc_mark = portapack::persistent_memory::afsk_mark_freq()*(0x40000*256)/2280;
shared_memory.afsk_phase_inc_space = portapack::persistent_memory::afsk_space_freq()*(0x40000*256)/2280;
shared_memory.afsk_fmmod = portapack::persistent_memory::afsk_bw() * 8;
+14 -50
View File
@@ -36,6 +36,9 @@
#include "ui_whistle.hpp"
#include "ui_jammer.hpp"
#include "ui_loadmodule.hpp"
#include "ui_afskrx.hpp"
#include "ui_xylos.hpp"
#include "ui_sigfrx.hpp"
#include "portapack.hpp"
#include "m4_startup.hpp"
@@ -108,16 +111,19 @@ void NavigationView::focus() {
/* SystemMenuView ********************************************************/
SystemMenuView::SystemMenuView(NavigationView& nav) {
add_items<11>({ {
add_items<10>({ {
{ "Play dead", ui::Color::red(), [&nav](){ nav.push(new PlayDeadView { nav, false }); } },
{ "Receiver", ui::Color::cyan(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new ReceiverView { nav, receiver_model } }); } },
{ "Receiver", ui::Color::cyan(), [&nav](){ nav.push(new ReceiverView { nav, receiver_model }); } },
//{ "Nordic/BTLE RX", ui::Color::cyan(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
{ "Jammer", ui::Color::white(), [&nav](){ nav.push(new JammerView { nav, transmitter_model }); } },
{ "Audio file TX", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
//{ "Audio file TX", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
//{ "Encoder TX", ui::Color::green(), [&nav](){ nav.push(new NotImplementedView { nav }); } },
{ "Whistle", ui::Color::purple(), [&nav](){ nav.push(new WhistleView { nav, transmitter_model }); } },
{ "RDS TX", ui::Color::yellow(), [&nav](){ nav.push(new RDSView { nav, transmitter_model }); } },
{ "TEDI/LCR TX", ui::Color::orange(), [&nav](){ nav.push(new LCRView { nav, transmitter_model }); } },
//{ "SIGFOX RX", ui::Color::orange(), [&nav](){ nav.push(new SIGFRXView { nav, receiver_model }); } },
//{ "RDS TX", ui::Color::yellow(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new RDSView { nav, transmitter_model }}; } },
{ "Xylos TX", ui::Color::orange(), [&nav](){ nav.push(new XylosView { nav, transmitter_model }); } },
//{ "AFSK RX", ui::Color::cyan(), [&nav](){ nav.push(new AFSKRXView { nav, receiver_model }); } },
{ "TEDI/LCR TX", ui::Color::yellow(), [&nav](){ nav.push(new LCRView { nav, transmitter_model }); } },
{ "Setup", ui::Color::white(), [&nav](){ nav.push(new SetupMenuView { nav }); } },
{ "About", ui::Color::white(), [&nav](){ nav.push(new AboutView { nav }); } },
{ "Debug", ui::Color::white(), [&nav](){ nav.push(new DebugMenuView { nav }); } },
@@ -161,6 +167,8 @@ SystemView::SystemView(
//if (persistent_memory::playing_dead() == 0x59)
// navigation_view.push(new PlayDeadView { navigation_view, true });
//else
// navigation_view.push(new BMPView { navigation_view });
navigation_view.push(new BMPView { navigation_view });
}
@@ -187,51 +195,7 @@ BMPView::BMPView(NavigationView& nav) {
}
void BMPView::paint(Painter& painter) {
uint32_t pixel_data;
uint8_t p, by, c, count;
ui::Color linebuffer[185];
ui::Coord px = 0, py = 302;
ui::Color palette[16];
// RLE_4 BMP loader with hardcoded size and no delta :(
pixel_data = splash_bmp[0x0A];
p = 0;
for (c = 0x36; c < (0x36+(16*4)); c+=4) {
palette[p++] = ui::Color(splash_bmp[c+2], splash_bmp[c+1], splash_bmp[c]);
}
do {
by = splash_bmp[pixel_data++];
if (by) {
count = by;
by = splash_bmp[pixel_data++];
for (c = 0; c < count; c+=2) {
linebuffer[px++] = palette[by >> 4];
if (px < 185) linebuffer[px++] = palette[by & 15];
}
if (pixel_data & 1) pixel_data++;
} else {
by = splash_bmp[pixel_data++];
if (by == 0) {
portapack::display.render_line({27, py}, 185, linebuffer);
py--;
px = 0;
} else if (by == 1) {
break;
} else if (by == 2) {
// Delta
} else {
count = by;
for (c = 0; c < count; c+=2) {
by = splash_bmp[pixel_data++];
linebuffer[px++] = palette[by >> 4];
if (px < 185) linebuffer[px++] = palette[by & 15];
}
if (pixel_data & 1) pixel_data++;
}
}
} while (1);
portapack::display.drawBMP({(240-185)/2, 0}, splash_bmp);
}
/* PlayDeadView **********************************************************/
+246
View File
@@ -0,0 +1,246 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_xylos.hpp"
#include "ch.h"
#include "evtimer.h"
#include "ff.h"
#include "hackrf_gpio.hpp"
#include "portapack.hpp"
#include "radio.hpp"
#include "hackrf_hal.hpp"
#include "portapack_shared_memory.hpp"
#include "portapack_persistent_memory.hpp"
#include <cstring>
#include <stdio.h>
using namespace hackrf::one;
namespace ui {
void XylosView::focus() {
city_code.focus();
}
XylosView::~XylosView() {
transmitter_model.disable();
}
void XylosView::paint(Painter& painter) {
}
void XylosView::upd_message() {
uint8_t c;
ccirmessage[0] = '0';
ccirmessage[1] = '0';
ccirmessage[2] = '0';
ccirmessage[3] = '0';
ccirmessage[4] = (city_code.value() / 10) + 0x30;
ccirmessage[5] = (city_code.value() % 10) + 0x30;
ccirmessage[6] = family_code.value() + 0x30;
if (checkbox_wcsubfamily.value() == false)
ccirmessage[7] = subfamily_code.value() + 0x30;
else
ccirmessage[7] = 'A';
if (checkbox_wcid.value() == false) {
ccirmessage[8] = (receiver_code.value() / 10) + 0x30;
ccirmessage[9] = (receiver_code.value() % 10) + 0x30;
} else {
ccirmessage[8] = 'A';
ccirmessage[9] = 'A';
}
ccirmessage[10] = 'B';
ccirmessage[11] = options_ra.selected_index() + 0x30;
ccirmessage[12] = options_rb.selected_index() + 0x30;
ccirmessage[13] = options_rc.selected_index() + 0x30;
ccirmessage[14] = '0';
ccirmessage[15] = 'B';
ccirmessage[16] = '0';
ccirmessage[17] = '0';
ccirmessage[18] = '0';
ccirmessage[19] = '0';
// Stuffing
for (c=1; c<20; c++) {
if (ccirmessage[c] == ccirmessage[c-1]) ccirmessage[c] = 'E';
}
ccirmessage[20] = 0;
text_debug.set(ccirmessage);
}
XylosView::XylosView(
NavigationView& nav,
TransmitterModel& transmitter_model
) : transmitter_model(transmitter_model)
{
static constexpr Style style_val {
.font = font::fixed_8x16,
.background = Color::green(),
.foreground = Color::black(),
};
static constexpr Style style_cancel {
.font = font::fixed_8x16,
.background = Color::red(),
.foreground = Color::black(),
};
transmitter_model.set_modulation(19);
add_children({ {
&text_title,
&text_city,
&city_code,
&text_family,
&family_code,
&text_subfamily,
&subfamily_code,
&checkbox_wcsubfamily,
&text_receiver,
&receiver_code,
&checkbox_wcid,
&text_freq,
&options_freq,
&text_ra,
&options_ra,
&text_rb,
&options_rb,
&text_rc,
&options_rc,
&text_progress,
&text_debug,
&button_transmit,
&button_exit
} });
city_code.set_value(18);
family_code.set_value(1);
subfamily_code.set_value(1);
receiver_code.set_value(1);
options_freq.set_selected_index(5);
checkbox_wcsubfamily.set_value(true);
checkbox_wcid.set_value(true);
city_code.on_change = [this](int32_t v) {
(void)v;
XylosView::upd_message();
};
family_code.on_change = [this](int32_t v) {
(void)v;
XylosView::upd_message();
};
subfamily_code.on_change = [this](int32_t v) {
(void)v;
XylosView::upd_message();
};
receiver_code.on_change = [this](int32_t v) {
(void)v;
XylosView::upd_message();
};
checkbox_wcsubfamily.on_select = [this](Checkbox&) {
XylosView::upd_message();
};
checkbox_wcid.on_select = [this](Checkbox&) {
XylosView::upd_message();
};
options_ra.on_change = [this](size_t n, OptionsField::value_t v) {
(void)n;
(void)v;
XylosView::upd_message();
};
options_rb.on_change = [this](size_t n, OptionsField::value_t v) {
(void)n;
(void)v;
XylosView::upd_message();
};
options_rc.on_change = [this](size_t n, OptionsField::value_t v) {
(void)n;
(void)v;
XylosView::upd_message();
};
button_transmit.set_style(&style_val);
button_transmit.on_select = [this,&transmitter_model](Button&) {
if (txing == false) {
upd_message();
auto& message_map = context().message_map();
message_map.unregister_handler(Message::ID::TXDone);
message_map.register_handler(Message::ID::TXDone,
[this,&transmitter_model](Message* const p) {
uint8_t c;
char progress[21];
const auto message = static_cast<const TXDoneMessage*>(p);
if (message->n == 25) {
transmitter_model.disable();
for (c=0;c<20;c++)
progress[c] = ' ';
progress[20] = 0;
text_progress.set(progress);
txing = false;
button_transmit.set_style(&style_val);
button_transmit.set_text("START");
} else {
for (c=0;c<message->n;c++)
progress[c] = ' ';
progress[c] = '.';
progress[++c] = 0;
text_progress.set(progress);
}
}
);
shared_memory.xylos_transmit_done = false;
memcpy(shared_memory.xylosdata, ccirmessage, 21);
transmitter_model.set_tuning_frequency(87700000); //xylos_freqs[options_freq.selected_index()]);
txing = true;
button_transmit.set_style(&style_cancel);
button_transmit.set_text("Wait");
transmitter_model.enable();
}
};
button_exit.on_select = [&nav](Button&){
nav.pop();
};
}
} /* namespace ui */
+191
View File
@@ -0,0 +1,191 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_menu.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "clock_manager.hpp"
#include "message.hpp"
#include "rf_path.hpp"
#include "max2837.hpp"
#include "volume.hpp"
#include "transmitter_model.hpp"
namespace ui {
class XylosView : public View {
public:
XylosView(NavigationView& nav, TransmitterModel& transmitter_model);
~XylosView();
void upd_message();
void focus() override;
void paint(Painter& painter) override;
private:
bool txing = false;
const rf::Frequency xylos_freqs[6] = { 31325000, 31387500, 31437500, 31475000, 31687500, 31975000 };
char ccirmessage[21];
TransmitterModel& transmitter_model;
Text text_title {
{ 1 * 8, 1 * 16, 11, 16 },
"BH Xylos TX"
};
Text text_city {
{ 4 * 8, 3 * 16, 11 * 8, 16 },
"Code ville:"
};
NumberField city_code {
{ 16 * 8, 3 * 16 },
2,
{ 0, 99 },
1,
' '
};
Text text_family {
{ 7 * 8, 4 * 16, 8 * 8, 16 },
"Famille:"
};
NumberField family_code {
{ 16 * 8, 4 * 16 },
2,
{ 0, 9 },
1,
' '
};
Text text_subfamily {
{ 2 * 8, 5 * 16 + 4, 13 * 8, 16 },
"Sous-famille:"
};
NumberField subfamily_code {
{ 16 * 8, 5 * 16 + 4 },
2,
{ 0, 9 },
1,
' '
};
Checkbox checkbox_wcsubfamily {
{ 20 * 8, 5 * 16},
"Toutes"
};
Text text_receiver {
{ 2 * 8, 7 * 16 + 6, 13 * 8, 16 },
"ID recepteur:"
};
NumberField receiver_code {
{ 16 * 8, 7 * 16 + 6 },
2,
{ 0, 99 },
1,
' '
};
Checkbox checkbox_wcid {
{ 20 * 8, 7 * 16 + 4},
"Tous"
};
Text text_freq {
{ 5 * 8, 9 * 16, 10 * 8, 16 },
"Frequence:"
};
OptionsField options_freq {
{ 16 * 8, 9 * 16 },
6,
{
{ "31.3250", 0 },
{ "31.3875", 1 },
{ "31.4375", 2 },
{ "31.4750", 3 },
{ "31.6875", 4 },
{ "31.9750", 5 }
}
};
Text text_ra {
{ 12, 11 * 16, 8 * 8, 16 },
"Relais 1"
};
OptionsField options_ra {
{ 16, 12 * 16 },
3,
{
{ "Ignorer", 0 },
{ " OFF ", 1 },
{ " ON ", 2 }
}
};
Text text_rb {
{ 88, 11 * 16, 8 * 8, 16 },
"Relais 2"
};
OptionsField options_rb {
{ 92, 12 * 16 },
3,
{
{ "Ignorer", 0 },
{ " OFF ", 1 },
{ " ON ", 2 }
}
};
Text text_rc {
{ 164, 11 * 16, 8 * 8, 16 },
"Relais 3"
};
OptionsField options_rc {
{ 168, 12 * 16 },
3,
{
{ "Ignorer", 0 },
{ " OFF ", 1 },
{ " ON ", 2 }
}
};
Text text_progress {
{ 5 * 8, 13 * 16, 20 * 8, 16 },
" "
};
Text text_debug {
{ 5 * 8, 14 * 16, 20 * 8, 16 },
"--------------------"
};
Button button_transmit {
{ 2 * 8, 16 * 16, 64, 32 },
"START"
};
Button button_exit {
{ 21 * 8, 16 * 16, 64, 32 },
"Exit"
};
};
} /* namespace ui */