Compare commits

...

4 Commits

Author SHA1 Message Date
gullradriel f3aee44e3b update submodule (#3187)
* update submodule
* fix iwyu CI errors
* fix sd over usb with hackrf submodule si5351c refactored functions
2026-05-21 17:48:53 +02:00
未来方舟 165c190d92 Use html comments to make som text only visible by PR creator (#3186)
* Use html comments to make som text only visible by PR creator

* reformat a bit
2026-05-21 15:51:56 +02:00
Totoo 8f9246c1ea Enhance transmitter model and UI for better handling of TX and RF ampstates (#3185) 2026-05-21 15:50:47 +02:00
Pezsma f1d52ae78f expanding channels (#3183) 2026-05-21 15:50:10 +02:00
8 changed files with 244 additions and 103 deletions
+44 -22
View File
@@ -1,58 +1,80 @@
<!--
👋 Thank you for your contribution!
⚠️ Before you start, please review our Contributing Guidelines:
https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines
-->
## Brief description of what you did
<!--
Replace this line with a description of your changes. What problem does this PR solve? What does it add or fix?
Don't forget to describe how maintainers can test your modifications!
-->
<!-- Replace this line with a description of your changes. What problem does this PR solve? What does it add or fix?
Don't forget to describe how maintainers can test your modifications! -->
---
## Proof that your changes work
<!--
1. PRs without proof will not be merged. Please provide evidence for every section below that applies to your change.
2. Trusted contributors: If you have a track record of merged PRs that were properly tested and documented, maintainers may waive one or more of the proof sections below at their discretion. You are still encouraged to include whatever evidence you have, but sections may be marked `N/A — trusted contributor` with a short note.
3. First-time contributors must provide full proof. See the Contributing Guidelines(https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines) and Trusted Contributors (https://github.com/portapack-mayhem/mayhem-firmware/wiki/Trusted-Contributors) articles for details.
-->
> ⚠️ **PRs without proof will not be merged.** Please provide evidence for every section below that applies to your change.
>
> 🛈 **Trusted contributors:** If you have a track record of merged PRs that were properly tested and documented, maintainers may waive one or more of the proof sections below at their discretion. You are still encouraged to include whatever evidence you have, but sections may be marked `N/A — trusted contributor` with a short note. First-time contributors must provide full proof. See the [Contributing Guidelines](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines) and [Trusted Contributors](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Trusted-Contributors) articles for details.
### 🖥️ Proof it compiles
<!--
Attach a log snippet showing a successful build from the final firmware size/space summary in the build output. Example: `Space remaining in flash ROM: 19552 bytes ( 1.9 % )`.
Paste log below.
-->
<!-- Paste log below -->
### 📱 Proof of testing on a real device
<!--
Attach photos, screenshots, or a short video of your changes running on actual PortaPack hardware. Emulator or simulator output is not a substitute for real-device testing.
Paste photo / video / screenshot below
-->
Attach photos, screenshots, or a short video of your changes running on actual PortaPack hardware. **Emulator or simulator output is not a substitute for real-device testing.**
<!-- Paste photo / video / screenshot below -->
### 📡 Proof against a real emitter/receiver (if applicable)
<!--
1. If your PR involves RX/TX, protocol decoding, signal generation, or any RF-related functionality, you **must** demonstrate it working against a real emitter or receiver. Include photos, videos, logs, or waterfall/spectrum screenshots.
2. If this section does not apply to your PR, write `N/A` below and briefly explain why.
3. Paste RF evidence, or write N/A with a short justification
-->
If your PR involves RX/TX, protocol decoding, signal generation, or any RF-related functionality, you **must** demonstrate it working against a real emitter or receiver. Include photos, videos, logs, or waterfall/spectrum screenshots.
If this section does not apply to your PR, write `N/A` below and briefly explain why.
<!-- Paste RF evidence, or write N/A with a short justification -->
---
## 📚 Wiki documentation commitment
**By submitting this PR, you acknowledge that once it is merged you are responsible for creating or updating the corresponding page on the [project wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki).**
<!--
By submitting this PR, you acknowledge that once it is merged you are responsible for creating or updating the corresponding page on the project wiki(https://github.com/portapack-mayhem/mayhem-firmware/wiki).
Your wiki article should include:
- A **screenshot of the main app screen** (strongly recommended)
- A clear **description** of what the app does
- An explanation of the **controls** and UI elements
- Any known **limitations**, quirks, or caveats
- A screenshot of the main app screen (strongly recommended)
- A clear description of what the app does
- An explanation of the controls and UI elements
- Any known limitations, quirks, or caveats
- Anything else a user should know to use the app effectively (dependencies, required hardware, file formats, etc.)
- If you are modifying an existing app, make sure the wiki page reflects your changes.
-->
- [ ] I will (or already) create(d) wiki document for my newly added feature
If you are modifying an existing app, make sure the wiki page reflects your changes.
---
+62 -53
View File
@@ -116,7 +116,7 @@ static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
break;
default:
freq_hz = UINT64_MAX;
freq_hz = 2'402'000'000ull; // Default to channel 37 frequency if invalid channel number
}
return freq_hz;
@@ -254,7 +254,7 @@ void BLETxView::send_packet() {
}
void BLETxView::start() {
if (file_path.empty()) {
if (file_path.empty() || num_packets == 0) {
file_error();
check_loop.set_value(false);
return;
@@ -266,6 +266,8 @@ void BLETxView::start() {
button_play.set_bitmap(&bitmap_stop);
is_running = true;
advCount = 0;
send_packet();
}
@@ -296,55 +298,61 @@ void BLETxView::on_timer() {
void BLETxView::on_tx_progress(const bool done, uint32_t progress) {
// TODO: make use of progress variable
(void)progress;
if (!is_active() || num_packets == 0) {
return;
}
if (done) {
if (is_active()) {
transmitter_model.disable();
if (auto_channel) {
switch (advCount) {
case 0:
channel_number = 37;
break;
case 1:
channel_number = 38;
break;
case 2:
channel_number = 39;
break;
}
field_frequency.set_value(get_freq_by_channel_number(channel_number));
advCount++;
if (advCount == 3) {
channel_number = 37;
packet_counter--;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
if (packet_counter > 0) packet_counter--;
packetDone = true;
advCount = 0;
} else {
channel_number = 37 + advCount;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
send_packet();
}
} else if (all_channels) {
advCount++;
if (advCount == 40) {
channel_number = 0;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
if (packet_counter > 0) packet_counter--;
packetDone = true;
advCount = 0;
} else {
channel_number = advCount;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
send_packet();
advCount++;
}
} else {
packet_counter--;
if (packet_counter > 0) packet_counter--;
packetDone = true;
}
}
}
// Reached end of current packet repeats.
if (packet_counter == 0) {
// Done sending all packets.
if (current_packet == (num_packets - 1)) {
current_packet = 0;
// Reached end of current packet repeats.
if (packet_counter == 0) {
// Done sending all packets.
current_packet++;
if (current_packet >= num_packets) {
current_packet = 0;
// If looping, restart from beginning.
if (check_loop.value()) {
update_current_packet(packets[current_packet], current_packet);
} else {
stop();
}
} else {
current_packet++;
// If looping, restart from beginning.
if (check_loop.value()) {
update_current_packet(packets[current_packet], current_packet);
} else {
stop();
}
} else {
update_current_packet(packets[current_packet], current_packet);
}
}
}
@@ -385,16 +393,20 @@ BLETxView::BLETxView(NavigationView& nav)
};
options_channel.on_change = [this](size_t, int32_t i) {
// If we selected Auto don't do anything and Auto will handle changing.
if (i == 40) {
auto_channel = true;
return;
auto_channel = (i == BLT_CHAN_AUTO);
all_channels = (i == BLT_CHAN_ALL);
advCount = 0;
if (auto_channel) {
channel_number = 37;
} else if (all_channels) {
channel_number = 0;
} else {
auto_channel = false;
channel_number = i;
}
field_frequency.set_value(get_freq_by_channel_number(i));
channel_number = i;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
};
options_speed.on_change = [this](size_t, int32_t i) {
@@ -403,7 +415,7 @@ BLETxView::BLETxView(NavigationView& nav)
};
options_speed.set_selected_index(0);
options_channel.set_selected_index(3);
options_channel.set_by_value(BLT_CHAN_AUTO);
check_rand_mac.set_value(false);
check_rand_mac.on_select = [this](Checkbox&, bool v) {
@@ -413,8 +425,7 @@ BLETxView::BLETxView(NavigationView& nav)
button_open.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
on_file_changed(new_file_path);
this->on_file_changed(new_file_path);
nav_.set_on_pop([this]() { button_play.focus(); });
};
};
@@ -427,7 +438,7 @@ BLETxView::BLETxView(NavigationView& nav)
64,
ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& buffer) {
on_save_file(buffer);
this->on_save_file(buffer);
});
};
@@ -506,10 +517,10 @@ void BLETxView::on_file_changed(const fs::path& new_file_path) {
}
do {
readUntil(data_file, packets[num_packets].macAddress, mac_address_size_str, ' ');
readUntil(data_file, packets[num_packets].advertisementData, max_packet_size_str, ' ');
readUntil(data_file, packets[num_packets].packetType, max_packet_type_str, ' ');
readUntil(data_file, packets[num_packets].packetCount, max_packet_repeat_str, '\n');
readUntil(data_file, packets[num_packets].macAddress, sizeof(packets[num_packets].macAddress), ' ');
readUntil(data_file, packets[num_packets].advertisementData, sizeof(packets[num_packets].advertisementData), ' ');
readUntil(data_file, packets[num_packets].packetType, sizeof(packets[num_packets].packetType), ' ');
readUntil(data_file, packets[num_packets].packetCount, sizeof(packets[num_packets].packetCount), '\n');
uint64_t macAddressSize = strlen(packets[num_packets].macAddress);
uint64_t advertisementDataSize = strlen(packets[num_packets].advertisementData);
@@ -542,10 +553,8 @@ void BLETxView::on_file_changed(const fs::path& new_file_path) {
num_packets++;
} while (num_packets < max_num_packets);
update_current_packet(packets[0], 0);
data_file.close();
update_current_packet(packets[0], 0);
}
}
@@ -570,6 +579,9 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
std::vector<std::string> strings = splitIntoStrings(packet.advertisementData);
packet_counter = packet.packet_count;
current_packet = currentIndex;
text_packet_index.set(to_string_dec_uint(current_packet));
text_packets_sent.set(to_string_dec_uint(packet.packet_count));
@@ -578,9 +590,6 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
progressbar.set_max(packet.packet_count);
packet_counter = packet.packet_count;
current_packet = currentIndex;
dataFile.create(dataTempFilePath);
for (const std::string& str : strings) {
+49 -7
View File
@@ -45,6 +45,9 @@
#include <string>
#include <memory>
#define BLT_CHAN_AUTO 40
#define BLT_CHAN_ALL 41
class BLELoggerTx {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
@@ -94,7 +97,7 @@ struct BLETxPacket {
char advertisementData[63];
char packetCount[11];
char packetType[17];
uint32_t packet_count;
uint32_t packet_count; // repeat the same packet count times
PKT_TYPE pduType;
};
@@ -145,6 +148,7 @@ class BLETxView : public View {
std::filesystem::path packet_save_path{bletx_dir / u"BLETX_????.TXT"};
uint8_t channel_number = 37;
bool auto_channel = false;
bool all_channels = false;
char randomMac[13] = "010203040506";
@@ -156,10 +160,10 @@ class BLETxView : public View {
int16_t auto_channel_period{6};
bool repeatLoop = false;
uint32_t packet_counter{0};
uint32_t num_packets{0};
uint32_t current_packet{0};
uint8_t packetTxCount{0};
uint32_t packet_counter{0}; // the counter (decr) of the actual packet as it can be sent multiple times
uint32_t num_packets{0}; // how many packets in the file
uint32_t current_packet{0}; // the actual packet's num, i'm sending if i have multiple packets
uint8_t packetTxCount{0}; // not used
bool packetDone = false;
bool random_mac = false;
bool file_override = false;
@@ -241,10 +245,48 @@ class BLETxView : public View {
OptionsField options_channel{
{11 * 8, 6 * 8},
5,
{{"Ch.37 ", 37},
{{"Ch.0", 0},
{"Ch.1", 1},
{"Ch.2", 2},
{"Ch.3", 3},
{"Ch.4", 4},
{"Ch.5", 5},
{"Ch.6", 6},
{"Ch.7", 7},
{"Ch.8", 8},
{"Ch.9", 9},
{"Ch.10", 10},
{"Ch.11", 11},
{"Ch.12", 12},
{"Ch.13", 13},
{"Ch.14", 14},
{"Ch.15", 15},
{"Ch.16", 16},
{"Ch.17", 17},
{"Ch.18", 18},
{"Ch.19", 19},
{"Ch.20", 20},
{"Ch.21", 21},
{"Ch.22", 22},
{"Ch.23", 23},
{"Ch.24", 24},
{"Ch.25", 25},
{"Ch.26", 26},
{"Ch.27", 27},
{"Ch.28", 28},
{"Ch.29", 29},
{"Ch.30", 30},
{"Ch.31", 31},
{"Ch.32", 32},
{"Ch.33", 33},
{"Ch.34", 34},
{"Ch.35", 35},
{"Ch.36", 36},
{"Ch.37", 37},
{"Ch.38", 38},
{"Ch.39", 39},
{"Auto", 40}}};
{"Auto", BLT_CHAN_AUTO},
{"All", BLT_CHAN_ALL}}};
OptionsField options_adv_type{
{17 * 8, 6 * 8},
+14 -2
View File
@@ -72,19 +72,31 @@ void TransmitterModel::set_channel_bandwidth(uint32_t v) {
}
uint8_t TransmitterModel::tx_gain() const {
if (persistent_memory::config_tx_disabled()) {
return 0;
}
return std::min(settings_.tx_gain_db, portapack::persistent_memory::config_tx_gain_max_db());
}
void TransmitterModel::set_tx_gain(uint8_t v_db) {
settings_.tx_gain_db = v_db;
uint8_t newgain = v_db;
if (persistent_memory::config_tx_disabled()) {
newgain = 0;
} else if (v_db > portapack::persistent_memory::config_tx_gain_max_db()) {
newgain = portapack::persistent_memory::config_tx_gain_max_db();
}
settings_.tx_gain_db = newgain;
update_tx_gain();
}
bool TransmitterModel::rf_amp() const {
return settings_.rf_amp && !portapack::persistent_memory::config_tx_amp_disabled();
return settings_.rf_amp && !portapack::persistent_memory::config_tx_amp_disabled() && !portapack::persistent_memory::config_tx_disabled();
}
void TransmitterModel::set_rf_amp(bool enabled) {
if (enabled && (portapack::persistent_memory::config_tx_amp_disabled() || portapack::persistent_memory::config_tx_disabled())) {
return;
}
settings_.rf_amp = enabled;
update_rf_amp();
}
+63 -7
View File
@@ -39,7 +39,13 @@ using namespace portapack;
namespace ui {
/* Gets a style indicating total TX gain level. */
static const Style* get_style_for_gain(uint8_t tot_gain) {
static const Style* get_style_for_gain(uint8_t tot_gain, uint8_t gain) {
if (persistent_memory::config_tx_disabled()) {
return Theme::getInstance()->fg_dark; // disabled color
}
if (gain == persistent_memory::config_tx_gain_max_db() && gain != max2837::tx::gain_db_range.maximum) { // only if limited
return Theme::getInstance()->fg_cyan;
}
if (tot_gain > POWER_THRESHOLD_HIGH) return Theme::getInstance()->fg_red;
if (tot_gain > POWER_THRESHOLD_MED)
@@ -50,7 +56,21 @@ static const Style* get_style_for_gain(uint8_t tot_gain) {
return nullptr; // Uses default.
}
static const Style* get_style_for_amp(uint8_t tot_gain) {
if (persistent_memory::config_tx_disabled() || persistent_memory::config_tx_amp_disabled()) {
return Theme::getInstance()->fg_dark; // disabled color
}
if (tot_gain > POWER_THRESHOLD_HIGH) return Theme::getInstance()->fg_red;
if (tot_gain > POWER_THRESHOLD_MED)
return Theme::getInstance()->fg_orange;
if (tot_gain > POWER_THRESHOLD_LOW)
return Theme::getInstance()->fg_yellow;
return nullptr; // Uses default.
}
/* TransmitterView *******************************************************/
void TransmitterView::paint(Painter& painter) {
@@ -95,12 +115,12 @@ void TransmitterView::on_tx_amp_changed(bool rf_amp) {
void TransmitterView::update_gainlevel_styles() {
int8_t tot_gain = transmitter_model.tx_gain() + (transmitter_model.rf_amp() ? 14 : 0);
auto style = get_style_for_gain(tot_gain);
auto style = get_style_for_gain(tot_gain, transmitter_model.tx_gain());
auto styleamp = get_style_for_amp(tot_gain);
field_gain.set_style(style);
text_gain.set_style(style);
field_amp.set_style(style);
text_amp.set_style(style);
field_amp.set_style(styleamp);
text_amp.set_style(styleamp);
}
void TransmitterView::set_transmitting(const bool transmitting) {
@@ -199,6 +219,24 @@ TransmitterView::TransmitterView(
on_start();
}
};
if (persistent_memory::config_tx_amp_disabled() || persistent_memory::config_tx_disabled()) { // amp disabled / tx disabled
field_amp.set_value(0);
field_amp.set_focusable(false);
}
if (persistent_memory::config_tx_disabled()) { // tx disabled
field_gain.set_value(0);
field_gain.set_focusable(false);
} else {
if (persistent_memory::config_tx_gain_max_db() < max2837::tx::gain_db_range.maximum) {
field_gain.set_range(max2837::tx::gain_db_range.minimum, persistent_memory::config_tx_gain_max_db());
if (field_gain.value() > persistent_memory::config_tx_gain_max_db()) {
field_gain.set_value(persistent_memory::config_tx_gain_max_db());
}
}
}
update_gainlevel_styles();
}
TransmitterView::~TransmitterView() {
@@ -252,16 +290,34 @@ TransmitterView2::TransmitterView2(Point pos, bool short_ui) {
update_gainlevel_styles();
};
if (persistent_memory::config_tx_amp_disabled() || persistent_memory::config_tx_disabled()) { // amp disabled / tx disabled
field_amp.set_value(0);
field_amp.set_focusable(false);
}
if (persistent_memory::config_tx_disabled()) { // tx disabled
field_gain.set_value(0);
field_gain.set_focusable(false);
} else {
if (persistent_memory::config_tx_gain_max_db() < max2837::tx::gain_db_range.maximum) {
field_gain.set_range(max2837::tx::gain_db_range.minimum, persistent_memory::config_tx_gain_max_db());
if (field_gain.value() > persistent_memory::config_tx_gain_max_db()) {
field_gain.set_value(persistent_memory::config_tx_gain_max_db());
}
}
}
update_gainlevel_styles();
}
void TransmitterView2::update_gainlevel_styles() {
int8_t tot_gain = transmitter_model.tx_gain() + (transmitter_model.rf_amp() ? 14 : 0);
auto style = get_style_for_gain(tot_gain);
auto style = get_style_for_gain(tot_gain, transmitter_model.tx_gain());
auto styleamp = get_style_for_amp(tot_gain);
text_labels.set_style(style);
field_gain.set_style(style);
field_amp.set_style(style);
field_amp.set_style(styleamp);
}
} /* namespace ui */
+1 -1
View File
@@ -157,7 +157,7 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
// call nav with flash
auto open_view = nav->push<ui::FlashUtilityView>();
chprintf(chp, "Flashing started\r\n");
chThdSleepMilliseconds(50); // to give display some time to paint the screen
chThdSleepMilliseconds(150); // to give display some time to paint the screen
open_view->wait_till_loaded(); // also wait for first frame sync
if (!open_view->flash_firmware(path.native())) {
chprintf(chp, "error\r\n");
+10 -10
View File
@@ -148,8 +148,8 @@ static struct gpio gpio_h1r9_hw_sync_enable = GPIO(5, 5);
#endif
// clang-format on
/* i2c0, i2c1, and i2c_config_si5351c_fast_clock are defined in
* hackrf/firmware/common/i2c_bus.c and si5351c.c; use those instead of
/* i2c0, i2c1, and i2c_config_fast_clock are defined in
* hackrf/firmware/common/i2c_bus.c and i2c_lpc.c; use those instead of
* redefining them here to avoid multiple-definition link errors. */
si5351c_driver_t clock_gen = {
@@ -553,7 +553,7 @@ void cpu_clock_init(void) {
/* use IRC as clock source for APB3 */
CGU_BASE_APB3_CLK = CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_IRC);
i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock);
i2c_bus_start(clock_gen.bus, &i2c_config_fast_clock);
si5351c_init(&clock_gen);
si5351c_disable_all_outputs(&clock_gen);
@@ -561,8 +561,7 @@ void cpu_clock_init(void) {
si5351c_power_down_all_clocks(&clock_gen);
si5351c_set_crystal_configuration(&clock_gen);
si5351c_enable_xo_and_ms_fanout(&clock_gen);
si5351c_configure_pll_sources(&clock_gen, PLL_SOURCE_XTAL);
si5351c_configure_pll_multisynth(&clock_gen, PLL_SOURCE_XTAL);
si5351c_configure_pll_multisynth(&clock_gen, SI5351C_INPUT_XTAL);
/*
* Clocks on HackRF One r9:
@@ -615,14 +614,15 @@ void cpu_clock_init(void) {
/* Set to 10 MHz, the common rate between Jawbreaker and HackRF One. */
sample_rate_set(10000000 * (fp_40_24_t)FP_ONE_HZ, true);
si5351c_set_clock_source(&clock_gen, PLL_SOURCE_XTAL);
si5351c_configure_clock_control(&clock_gen);
si5351c_change_input(&clock_gen, SI5351C_INPUT_XTAL);
// soft reset
si5351c_reset_pll(&clock_gen, SI5351C_PLL_BOTH);
si5351c_reset_plls(&clock_gen, SI5351C_PLL_MASK_BOTH);
si5351c_enable_clock_outputs(&clock_gen);
// FIXME disable I2C
/* Kick I2C0 down to 400kHz when we switch over to APB1 clock = 204MHz */
i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock);
i2c_bus_start(clock_gen.bus, &i2c_config_fast_clock);
/*
* 12MHz clock is entering LPC XTAL1/OSC input now.
@@ -784,9 +784,9 @@ clock_source_t activate_best_clock_source(void) {
/* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */
}
si5351c_set_clock_source(
si5351c_change_input(
&clock_gen,
(source == CLOCK_SOURCE_HACKRF) ? PLL_SOURCE_XTAL : PLL_SOURCE_CLKIN);
(source == CLOCK_SOURCE_HACKRF) ? SI5351C_INPUT_XTAL : SI5351C_INPUT_CLKIN);
hackrf_ui()->set_clock_source(source);
return source;
}
+1 -1
Submodule hackrf updated: 0bf478ed25...f526ddf4eb