Files
mayhem-firmware/firmware/application/apps/ui_debug.cpp
T
Pezsma cafe62564e Gpio modify (#3229)
* Refactor GPIO configuration for PRALINE and non-PRALINE setups

- Updated PinMap structure to include gpio_mode for better flexibility.
- Added new GPIO mappings for SGPIO pins with appropriate configurations.
- Commented out unused GPIO definitions in hackrf_gpio.hpp to improve code clarity.
- Adjusted GPIO initialization for control pins to utilize the new PinMap structure.
- Ensured compatibility for both PRALINE and non-PRALINE configurations by using preprocessor directives.

* Refactor GPIO handling and remove LED abstraction

- Updated GPIO class to support logical polarity, enabling/disabling features based on their configured state.
- Replaced direct GPIO manipulation in power control functions with new GPIO methods for better readability and maintainability.
- Removed the LED class and its associated functionality, as it was deemed unnecessary for the current implementation.
- Adjusted GPIO initialization for various components, ensuring correct polarity settings for VAA and power enable pins.
- Cleaned up unused includes and commented-out code in hackrf_gpio.hpp.

* Refactor GPIO LED control methods to use setActive() and setInactive() for improved clarity

* Refactor GPIO control methods to use setActive() and setInactive() for improved clarity and consistency

* copilot

* Update GPIO control logic and pin definitions for clarity and consistency

* Refactor GPIO methods for improved naming consistency and clarity
2026-06-19 07:48:55 +02:00

3694 lines
131 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
*
* 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_debug.hpp"
#include "debug.hpp"
#include "ch.h"
#include "hal.h"
#include "radio.hpp"
#include "string_format.hpp"
#include "crc.hpp"
#include "audio.hpp"
#include "ui_sd_card_debug.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_painter.hpp"
#include "ui_external_items_menu_loader.hpp"
#include "ui_debug_max17055.hpp"
#include "ui_external_module_view.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
#include "irq_controls.hpp"
#ifdef PRALINE
#include "max2831.hpp"
using namespace max2831;
#endif
namespace ui {
/* DebugMemoryView *******************************************************/
DebugMemoryView::DebugMemoryView(NavigationView& nav) {
add_children({&text_title,
&text_label_m0_core_free,
&text_label_m0_core_free_value,
&text_label_m0_heap_fragmented_free,
&text_label_m0_heap_fragmented_free_value,
&text_label_m0_heap_fragments,
&text_label_m0_heap_fragments_value,
&button_done});
const auto m0_core_free = chCoreStatus();
text_label_m0_core_free_value.set(to_string_dec_uint(m0_core_free, 5));
size_t m0_fragmented_free_space = 0;
const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space);
text_label_m0_heap_fragmented_free_value.set(to_string_dec_uint(m0_fragmented_free_space, 5));
text_label_m0_heap_fragments_value.set(to_string_dec_uint(m0_fragments, 5));
button_done.on_select = [&nav](Button&) { nav.pop(); };
}
void DebugMemoryView::focus() {
button_done.focus();
}
/* RegistersWidget *******************************************************/
RegistersWidget::RegistersWidget(
RegistersWidgetConfig&& config)
: Widget{}, config(std::move(config)), page_number(0) {
}
void RegistersWidget::update() {
set_dirty();
}
void RegistersWidget::paint(Painter& painter) {
const Coord left = (size().width() - config.row_width()) / 2;
draw_legend(left, painter);
draw_values(left, painter);
}
void RegistersWidget::draw_legend(const Coord left, Painter& painter) {
const auto pos = screen_pos();
const std::string spaces(config.legend_length(), ' ');
for (uint32_t i = 0; i < config.registers_per_page; i += config.registers_per_row()) {
uint32_t r = page_number * config.registers_per_page + i;
const Point offset{
left, static_cast<int>((i / config.registers_per_row()) * row_height)};
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(r, config.legend_length());
painter.draw_string(
pos + offset,
style().invert(),
text);
}
}
void RegistersWidget::draw_values(
const Coord left,
Painter& painter) {
const auto pos = screen_pos();
const std::string spaces(config.value_length(), ' ');
for (uint32_t i = 0; i < config.registers_per_page; i++) {
uint32_t r = page_number * config.registers_per_page + i;
const Point offset = {
static_cast<int>(left + config.legend_width() + 8 + (i % config.registers_per_row()) * (config.value_width() + 8)),
static_cast<int>((i / config.registers_per_row()) * row_height)};
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(reg_read(r), config.value_length());
painter.draw_string(
pos + offset,
style(),
text);
}
}
uint32_t RegistersWidget::reg_read(const uint32_t register_number) {
if (register_number < config.registers_count) {
switch (config.chip_type) {
case CT_PMEM:
return portapack::persistent_memory::pmem_data_word(register_number / 4) >> (register_number % 4 * 8);
case CT_RFFC5072:
return radio::debug::first_if::register_read(register_number);
case CT_MAX283X:
return radio::debug::second_if::register_read(register_number);
case CT_SI5351:
return portapack::clock_generator.read_register(register_number);
case CT_MAX17055: {
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
return dev->read_register(register_number);
}
case CT_AUDIO:
return audio::debug::reg_read(register_number);
#ifdef PRALINE
case CT_FPGA:
return radio::debug::fpga::register_read(register_number);
#endif
case CT_SGPIO:
return radio::debug::sgpio::register_read(register_number);
}
}
return 0xFFFF;
}
void RegistersWidget::reg_write(const uint32_t register_number, const uint32_t value) {
if (register_number < config.registers_count) {
switch (config.chip_type) {
case CT_PMEM:
break;
case CT_RFFC5072:
radio::debug::first_if::register_write(register_number, value);
break;
case CT_MAX283X:
radio::debug::second_if::register_write(register_number, value);
break;
case CT_SI5351:
portapack::clock_generator.write_register(register_number, value);
break;
case CT_MAX17055: {
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
dev->write_register(register_number, value);
break;
}
case CT_AUDIO:
audio::debug::reg_write(register_number, value);
break;
#ifdef PRALINE
case CT_FPGA:
radio::debug::fpga::register_write(register_number, value);
break;
#endif
case CT_SGPIO:
// SGPIO registers are read-only for debug purposes
break;
}
}
}
/* RegistersView *********************************************************/
RegistersView::RegistersView(
NavigationView& nav,
const std::string& title,
RegistersWidgetConfig&& config)
: registers_widget{std::move(config)} {
add_children({
&text_title,
&registers_widget,
&button_update,
&button_done,
&labels,
&field_write_reg_num,
&field_write_data_val,
&button_write,
});
button_update.on_select = [this](Button&) {
this->registers_widget.update();
};
button_done.on_select = [&nav](Button&) { nav.pop(); };
registers_widget.set_parent_rect({0, 48, screen_width, 192});
registers_widget.set_page(0);
text_title.set_parent_rect({(screen_width - static_cast<int>(title.size()) * 8) / 2, 16,
static_cast<int>(title.size()) * 8, 16});
text_title.set(title);
field_write_reg_num.on_change = [this](SymField&) {
field_write_data_val.set_value(this->registers_widget.reg_read(field_write_reg_num.to_integer()));
field_write_data_val.set_dirty();
};
const auto value = registers_widget.reg_read(0);
field_write_data_val.set_value(value);
button_write.set_style(Theme::getInstance()->fg_red);
button_write.on_select = [this](Button&) {
this->registers_widget.reg_write(field_write_reg_num.to_integer(), field_write_data_val.to_integer());
this->registers_widget.update();
};
}
void RegistersView::focus() {
button_done.focus();
}
bool RegistersView::on_encoder(const EncoderEvent delta) {
registers_widget.set_page(std::max(0ul, std::min(registers_widget.page_count() - 1, registers_widget.page() + delta)));
registers_widget.update();
return true;
}
/* ControlsSwitchesWidget ************************************************/
void ControlsSwitchesWidget::on_show() {
display.fill_rectangle(
screen_rect(),
Theme::getInstance()->bg_darkest->background);
}
bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
key_event_mask = 1 << toUType(key);
long_press_key_event_mask = key_is_long_pressed(key) ? key_event_mask : 0;
return true;
}
bool ControlsSwitchesWidget::on_encoder(const EncoderEvent delta) {
last_delta = delta;
return true;
}
void ControlsSwitchesWidget::paint(Painter& painter) {
const auto pos = screen_pos();
const std::array<Rect, 9> button_rects{{
{64, 32, 16, 16}, // Right
{0, 32, 16, 16}, // Left
{32, 64, 16, 16}, // Down
{32, 0, 16, 16}, // Up
{32, 32, 16, 16}, // Select
{96, 0, 16, 16}, // Dfu
{16, 96, 16, 16}, // Encoder phase 0
{48, 96, 16, 16}, // Encoder phase 1
{96, 64, 16, 16}, // Touch
}};
for (const auto r : button_rects) {
painter.fill_rectangle(r + pos, Theme::getInstance()->fg_blue->foreground);
}
if (get_touch_frame().touch)
painter.fill_rectangle(button_rects[8] + pos, Theme::getInstance()->fg_yellow->foreground);
const std::array<Rect, 8> raw_rects{{
{64 + 1, 32 + 1, 16 - 2, 16 - 2}, // Right
{0 + 1, 32 + 1, 16 - 2, 16 - 2}, // Left
{32 + 1, 64 + 1, 16 - 2, 16 - 2}, // Down
{32 + 1, 0 + 1, 16 - 2, 16 - 2}, // Up
{32 + 1, 32 + 1, 16 - 2, 16 - 2}, // Select
{96 + 1, 0 + 1, 16 - 2, 16 - 2}, // Dfu
{16 + 1, 96 + 1, 16 - 2, 16 - 2}, // Encoder phase 0
{48 + 1, 96 + 1, 16 - 2, 16 - 2}, // Encoder phase 1
}};
auto switches_raw = control::debug::switches();
for (const auto r : raw_rects) {
if (switches_raw & 1)
painter.fill_rectangle(r + pos, Theme::getInstance()->fg_yellow->foreground);
switches_raw >>= 1;
}
const std::array<Rect, 6> debounced_rects{{
{64 + 2, 32 + 2, 16 - 4, 16 - 4}, // Right
{0 + 2, 32 + 2, 16 - 4, 16 - 4}, // Left
{32 + 2, 64 + 2, 16 - 4, 16 - 4}, // Down
{32 + 2, 0 + 2, 16 - 4, 16 - 4}, // Up
{32 + 2, 32 + 2, 16 - 4, 16 - 4}, // Select
{96 + 2, 0 + 2, 16 - 4, 16 - 4}, // Dfu
}};
auto switches_debounced = get_switches_state().to_ulong();
for (const auto r : debounced_rects) {
if (switches_debounced & 1)
painter.fill_rectangle(r + pos, Theme::getInstance()->fg_green->foreground);
switches_debounced >>= 1;
}
const std::array<Rect, 6> events_rects{{
{64 + 3, 32 + 3, 16 - 6, 16 - 6}, // Right
{0 + 3, 32 + 3, 16 - 6, 16 - 6}, // Left
{32 + 3, 64 + 3, 16 - 6, 16 - 6}, // Down
{32 + 3, 0 + 3, 16 - 6, 16 - 6}, // Up
{32 + 3, 32 + 3, 16 - 6, 16 - 6}, // Select
{96 + 3, 0 + 3, 16 - 6, 16 - 6}, // Dfu
}};
auto switches_event = key_event_mask;
for (const auto r : events_rects) {
if (switches_event & 1)
painter.fill_rectangle(r + pos, Theme::getInstance()->fg_red->foreground);
switches_event >>= 1;
}
switches_event = long_press_key_event_mask;
for (const auto r : events_rects) {
if (switches_event & 1)
painter.fill_rectangle(r + pos, Theme::getInstance()->fg_cyan->foreground);
switches_event >>= 1;
}
painter.draw_string({5 * 8, 12 * 16}, *Theme::getInstance()->fg_light, to_string_dec_int(last_delta, 3));
}
void ControlsSwitchesWidget::on_frame_sync() {
set_dirty();
}
/* DebugControlsView *****************************************************/
DebugControlsView::DebugControlsView(NavigationView& nav) {
add_children({
&labels,
&switches_widget,
&options_switches_mode,
&button_done,
});
button_done.on_select = [&nav](Button&) {
set_switches_long_press_config(0);
nav.pop();
};
options_switches_mode.on_change = [this](size_t, OptionsField::value_t v) {
(void)v;
set_switches_long_press_config(options_switches_mode.selected_index_value());
};
}
void DebugControlsView::focus() {
switches_widget.focus();
}
#ifdef PRALINE
/* RadioDiagnosticsView **************************************************/
RadioDiagnosticsView::RadioDiagnosticsView(NavigationView& nav)
: nav_(nav) {
add_children({
&text_title,
&text_lbl_rffc,
&text_rffc_status,
&text_lbl_max,
&text_max_status,
&text_lbl_adc,
&text_adc_status,
&text_lbl_fpga,
&text_fpga_status,
&text_lbl_sgpio,
&text_sgpio_status,
&text_lbl_clock,
&text_clock_status,
&text_regs_title,
&text_lbl_rffc_reg,
&text_rffc_reg,
&text_lbl_max_reg,
&text_max_reg,
&text_lbl_fpga_reg,
&text_fpga_reg,
&text_lbl_sgpio_reg,
&text_sgpio_reg,
&text_test_result,
&button_refresh,
&button_done,
});
// Set title colors
text_title.set_style(Theme::getInstance()->fg_yellow);
text_regs_title.set_style(Theme::getInstance()->fg_yellow);
#ifdef PRALINE
text_lbl_fpga.set("FPGA (iCE40):");
#else
text_lbl_fpga.set("CPLD:");
#endif
button_refresh.on_select = [this](Button&) {
update_status();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
// Initial status update
update_status();
}
void RadioDiagnosticsView::focus() {
button_refresh.focus();
}
void RadioDiagnosticsView::update_status() {
// Read RFFC5072 register 0 to check if it responds
uint32_t rffc_reg0 = radio::debug::first_if::register_read(0);
bool rffc_ok = (rffc_reg0 != 0x0000) && (rffc_reg0 != 0xFFFF);
text_rffc_status.set(rffc_ok ? "OK" : "FAIL");
text_rffc_status.set_style(rffc_ok ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
text_rffc_reg.set(to_string_hex(rffc_reg0, 4));
// Read MAX283x register 0 to check if it responds
uint32_t max_reg0 = radio::debug::second_if::register_read(0);
bool max_ok = (max_reg0 != 0x0000) && (max_reg0 != 0x3FFF);
text_max_status.set(max_ok ? "OK" : "FAIL");
text_max_status.set_style(max_ok ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
text_max_reg.set(to_string_hex(max_reg0, 4));
// MAX5864 has no readback - assume OK if other SPI works
text_adc_status.set("(no readback)");
text_adc_status.set_style(Theme::getInstance()->fg_medium);
#ifdef PRALINE
// Read FPGA control register
uint32_t fpga_ctrl = radio::debug::fpga::register_read(1);
bool fpga_ok = (fpga_ctrl != 0xFF); // 0xFF = not responding
bool dc_block = (fpga_ctrl & 0x01) != 0;
text_fpga_status.set(fpga_ok ? (dc_block ? "OK DC_BLK" : "OK NO_DC") : "FAIL");
text_fpga_status.set_style(fpga_ok ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
// Show all FPGA registers
uint32_t fpga_r2 = radio::debug::fpga::register_read(2);
uint32_t fpga_r3 = radio::debug::fpga::register_read(3);
text_fpga_reg.set("C:" + to_string_hex(fpga_ctrl, 2) +
" D:" + to_string_hex(fpga_r2, 2) +
" T:" + to_string_hex(fpga_r3, 2));
#else
text_fpga_status.set("(CPLD)");
text_fpga_status.set_style(Theme::getInstance()->fg_medium);
text_fpga_reg.set("N/A");
#endif
// Check SGPIO status
uint32_t sgpio_enable = radio::debug::sgpio::register_read(0); // CTRL_ENABLE
uint32_t sgpio_status = radio::debug::sgpio::register_read(4); // STATUS_1
bool sgpio_ok = (sgpio_enable != 0);
text_sgpio_status.set(sgpio_ok ? "ENABLED" : "DISABLED");
text_sgpio_status.set_style(sgpio_ok ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_orange);
text_sgpio_reg.set("EN:" + to_string_hex(sgpio_enable, 4) +
" ST:" + to_string_hex(sgpio_status, 4));
// Clock status - check if Si5351 is configured
// We can't easily read back clock status, so just show assumed state
text_clock_status.set("(assumed OK)");
text_clock_status.set_style(Theme::getInstance()->fg_medium);
// Summary
bool all_ok = rffc_ok && max_ok;
#ifdef PRALINE
all_ok = all_ok && fpga_ok;
#endif
if (all_ok) {
text_test_result.set("Peripherals responding. Try RX app.");
text_test_result.set_style(Theme::getInstance()->fg_green);
} else {
text_test_result.set("Check failed peripherals above.");
text_test_result.set_style(Theme::getInstance()->fg_red);
}
}
#ifdef PRALINE
PralineRadioDebugView::PralineRadioDebugView(NavigationView& nav) {
add_children({&text_title, &text_lbl_lock, &text_lock_status,
&text_lbl_clk5, &text_clk5_status,
&text_lbl_spi, &text_spi_status, &text_lbl_fpga_ctrl, &text_fpga_ctrl,
&text_lbl_vaa, &text_vaa_status, &text_status_msg,
&button_refresh, &button_toggle_clk5, &button_done});
button_refresh.on_select = [this](Button&) { this->refresh(); };
button_toggle_clk5.on_select = [this](Button&) { this->toggle_clk5(); };
button_done.on_select = [&nav](Button&) { nav.pop(); };
refresh();
}
void PralineRadioDebugView::focus() {
button_refresh.focus();
}
void PralineRadioDebugView::toggle_clk5() {
// Si5351 Register 3 is the Output Enable mask. Bit 5 = CLK5.
// 0 = Enabled, 1 = Disabled.
uint8_t reg3 = portapack::clock_manager.si5351_read_register(3);
reg3 ^= 0x20; // Toggle Bit 5 (CLK5)
portapack::clock_manager.si5351_write_register(3, reg3);
refresh();
}
void PralineRadioDebugView::refresh() {
// 1. Check Mixer Lock Detect (GPIO6[25] / PD_11) [cite: 16, 17]
uint32_t gpio6_state = LPC_GPIO->PIN[6];
bool locked = (gpio6_state >> 25) & 1;
text_lock_status.set(locked ? "LOCKED (OK)" : "UNLOCKED!");
text_lock_status.set_style(locked ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
// 2. Check Si5351 CLK5 Status (Reg 3, Bit 5) - 0 = ON, 1 = OFF
uint8_t si_reg3 = portapack::clock_manager.si5351_read_register(3);
bool clk5_on = !(si_reg3 & 0x20);
text_clk5_status.set(clk5_on ? "ON (40MHz)" : "OFF");
text_clk5_status.set_style(clk5_on ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
// 3. Check SPI Bit Mode (SSP1 CR0)
// DSS (Data Size Select) is Bit 3:0. 0x8 = 9-bit (MAX2831), 0xF = 16-bit (Classic)
uint32_t cr0 = LPC_SSP1->CR0;
uint8_t dss = cr0 & 0x0F;
if (dss == 0x08)
text_spi_status.set("9-Bit (Pro)");
else
text_spi_status.set("Other (Err)");
text_spi_status.set_style((dss == 0x08) ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
// 4. Check FPGA Register 1 (DC Block status) [cite: 12, 13]
uint8_t fpga_r1 = radio::debug::fpga::register_read(1);
text_fpga_ctrl.set(to_string_hex(fpga_r1, 2));
// 5. Check VAA RF Power (GPIO4[1] / P8_1) - Active LOW [cite: 16, 17]
uint32_t gpio4_state = LPC_GPIO->PIN[4];
bool vaa_on = !((gpio4_state >> 1) & 1);
text_vaa_status.set(vaa_on ? "ON" : "OFF");
text_vaa_status.set_style(vaa_on ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
// Diagnostic Summary
if (!locked && clk5_on && vaa_on) {
text_status_msg.set("Clock/Pwr OK. PLL not locked. Check tuning registers.");
text_status_msg.set_style(Theme::getInstance()->fg_red);
} else if (!clk5_on) {
text_status_msg.set("Mixer Clock is OFF. PLL cannot lock.");
text_status_msg.set_style(Theme::getInstance()->fg_red);
} else {
text_status_msg.set("Hardware Link Active.");
text_status_msg.set_style(Theme::getInstance()->fg_green);
}
}
#endif
#ifdef PRALINE
/* WFMAudioDebugView *************************************************/
WFMAudioDebugView::WFMAudioDebugView(NavigationView& nav)
: nav_(nav) {
add_children({
&text_title,
&text_lbl_clk0,
&text_clk0,
&text_lbl_fpga_dec,
&text_fpga_dec,
&text_lbl_post_fpga,
&text_post_fpga,
&text_section1,
&text_lbl_reg8,
&text_reg8,
&text_lbl_lpf_bw,
&text_lpf_bw,
&text_section2,
&text_lbl_fpga_r1,
&text_fpga_r1,
&text_lbl_dc_q,
&text_dc_q,
&text_section3,
&text_lbl_expected,
&text_expected,
&text_lbl_deemph,
&text_deemph,
&text_status,
&text_status2,
&button_refresh,
&button_toggle_q,
&button_done,
});
text_title.set_style(Theme::getInstance()->fg_yellow);
text_section1.set_style(Theme::getInstance()->fg_yellow);
text_section2.set_style(Theme::getInstance()->fg_yellow);
text_section3.set_style(Theme::getInstance()->fg_yellow);
button_refresh.on_select = [this](Button&) {
refresh();
};
button_toggle_q.on_select = [this](Button&) {
uint32_t current = radio::debug::fpga::register_read(1);
uint8_t new_val = current ^ 0x02; // Toggle Q_INVERT bit
radio::debug::fpga::register_write(1, new_val);
radio::invalidate_spi_config();
refresh();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
refresh();
}
void WFMAudioDebugView::focus() {
button_refresh.focus();
}
void WFMAudioDebugView::refresh() {
// === Si5351 CLK0 Sample Rate ===
// Read MS0 parameters to calculate frequency
uint8_t reg44 = portapack::clock_manager.si5351_read_register(44);
uint8_t reg45 = portapack::clock_manager.si5351_read_register(45);
uint8_t reg46 = portapack::clock_manager.si5351_read_register(46);
uint8_t r_div_encoded = (reg44 >> 4) & 0x07;
uint32_t r_div = 1 << r_div_encoded;
uint32_t p1 = ((uint32_t)(reg44 & 0x03) << 16) | ((uint32_t)reg45 << 8) | reg46;
uint32_t ms_div = (p1 + 512) / 128;
// PLL A is 800 MHz
uint32_t clk0_khz = 800000 / ms_div / r_div;
uint32_t clk0_mhz_int = clk0_khz / 1000;
uint32_t clk0_khz_frac = clk0_khz % 1000;
text_clk0.set(to_string_dec_uint(clk0_mhz_int) + "." +
to_string_dec_uint(clk0_khz_frac / 100) +
to_string_dec_uint((clk0_khz_frac / 10) % 10) +
to_string_dec_uint(clk0_khz_frac % 10) + " MHz");
if (clk0_khz >= 3000 && clk0_khz <= 3200) {
text_clk0.set_style(Theme::getInstance()->fg_green);
} else {
text_clk0.set_style(Theme::getInstance()->fg_red);
}
// === FPGA Decimation ===
uint8_t fpga_decim = radio::debug::fpga::register_read(2);
uint32_t fpga_div = 1 << fpga_decim;
text_fpga_dec.set("/" + to_string_dec_uint(fpga_div) + " (n=" + to_string_dec_uint(fpga_decim) + ")");
// === Post-FPGA Rate ===
uint32_t post_fpga_khz = clk0_khz / fpga_div;
text_post_fpga.set(to_string_dec_uint(post_fpga_khz) + " kHz");
// For WFM, post-FPGA should be >= 384 kHz for proper audio decimation
if (post_fpga_khz >= 384) {
text_post_fpga.set_style(Theme::getInstance()->fg_green);
} else {
text_post_fpga.set_style(Theme::getInstance()->fg_orange);
}
// === MAX2831 LPF ===
uint32_t reg8 = radio::debug::second_if::register_read(8);
text_reg8.set("0x" + to_string_hex(reg8, 4));
uint8_t lpf_coarse = reg8 & 0x03;
const char* lpf_names[] = {"7.5 MHz", "8.5 MHz", "15 MHz", "18 MHz"};
text_lpf_bw.set(lpf_names[lpf_coarse]);
// 7.5 MHz is minimum, OK for mono WFM but tight for stereo
if (lpf_coarse >= 1) {
text_lpf_bw.set_style(Theme::getInstance()->fg_green);
} else {
text_lpf_bw.set_style(Theme::getInstance()->fg_orange);
}
// === FPGA Control Register ===
uint32_t fpga_ctrl = radio::debug::fpga::register_read(1);
text_fpga_r1.set("0x" + to_string_hex(fpga_ctrl, 2));
bool dc_block = fpga_ctrl & 0x01;
bool q_invert = fpga_ctrl & 0x02;
uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03;
text_dc_q.set(std::string(dc_block ? "DC:ON" : "DC:OFF") +
" Q:" + std::string(q_invert ? "INV" : "NOR") +
" QS:" + to_string_dec_uint(quarter_shift));
if (dc_block) {
text_dc_q.set_style(Theme::getInstance()->fg_green);
} else {
text_dc_q.set_style(Theme::getInstance()->fg_orange);
}
// === Expected Audio Rate ===
// WFM typically: 3072 kHz / 64 = 48 kHz audio
// Or: 3072 kHz → /8 (channel) → 384 kHz → /8 (audio) → 48 kHz
uint32_t expected_audio = post_fpga_khz / 64; // Simplified assumption
text_expected.set(to_string_dec_uint(expected_audio) + " kHz (est)");
if (expected_audio >= 44 && expected_audio <= 50) {
text_expected.set_style(Theme::getInstance()->fg_green);
} else {
text_expected.set_style(Theme::getInstance()->fg_red);
}
// === De-emphasis Status ===
// We can't directly read the M4 de-emphasis config, but we can indicate what SHOULD be set
// 75µs for USA, 50µs for Europe
text_deemph.set("Chk M4 cfg.");
text_deemph.set_style(Theme::getInstance()->fg_orange);
// === Status Summary ===
bool sample_rate_ok = (clk0_khz >= 3000 && clk0_khz <= 3200);
bool lpf_ok = (lpf_coarse <= 0x0F); // check against the 4-bit max
bool dc_ok = dc_block;
if (sample_rate_ok && lpf_ok && dc_ok) {
text_status.set("Hardware config looks OK.");
text_status.set_style(Theme::getInstance()->fg_green);
text_status2.set("If ringy: Chk d-emph in M4!");
text_status2.set_style(Theme::getInstance()->fg_orange);
} else {
std::string issues = "Issues: ";
if (!sample_rate_ok) issues += "SampleRate ";
if (!lpf_ok) issues += "LPF ";
if (!dc_ok) issues += "DC_Block ";
text_status.set(issues);
text_status.set_style(Theme::getInstance()->fg_red);
text_status2.set("Fix before checking audio.");
text_status2.set_style(Theme::getInstance()->fg_red);
}
}
#endif
/* BasebandStatusView ******************************************************/
BasebandStatusView::BasebandStatusView(NavigationView& nav)
: nav_(nav) {
add_children({
&text_title,
&text_lbl_marker,
&text_marker,
&text_lbl_loops,
&text_loops,
&text_lbl_wait,
&text_wait,
&text_lbl_xfr,
&text_xfr,
&text_lbl_missed,
&text_missed,
&text_status_line1,
&text_status_line2,
&text_status_line3,
&button_refresh,
&button_done,
});
// Set title color
text_title.set_style(Theme::getInstance()->fg_yellow);
button_refresh.on_select = [this](Button&) {
update();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
// Initial update
update();
}
void BasebandStatusView::focus() {
button_refresh.focus();
}
void BasebandStatusView::update() {
// Read counters from shared memory
uint8_t marker = shared_memory.m4_streaming_marker;
uint32_t loops = shared_memory.m4_baseband_loops;
uint32_t wait = shared_memory.m4_dma_wait_count;
uint32_t xfr = shared_memory.m4_dma_xfr_count;
uint16_t missed = shared_memory.m4_buffer_missed;
// Display counter values
text_marker.set(to_string_hex(marker, 2));
text_marker.set_style((marker == 0xAA) ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
text_loops.set(to_string_dec_uint(loops));
text_wait.set(to_string_dec_uint(wait));
text_xfr.set(to_string_dec_uint(xfr));
text_missed.set(to_string_dec_uint(missed));
// Status interpretation
if (marker == 0x00) {
text_status_line1.set("Thread NOT started!");
text_status_line2.set("M4 baseband crash.");
text_status_line3.set("Check thread race condition.");
text_status_line1.set_style(Theme::getInstance()->fg_red);
text_status_line2.set_style(Theme::getInstance()->fg_red);
text_status_line3.set_style(Theme::getInstance()->fg_red);
} else if (marker == 0xAA && loops == 0) {
text_status_line1.set("Thread started but");
text_status_line2.set("not looping yet.");
text_status_line3.set("Wait a moment...");
text_status_line1.set_style(Theme::getInstance()->fg_orange);
text_status_line2.set_style(Theme::getInstance()->fg_orange);
text_status_line3.set_style(Theme::getInstance()->fg_orange);
} else if (marker == 0xAA && xfr == 0) {
text_status_line1.set("Thread looping " + to_string_dec_uint(loops) + "x");
text_status_line2.set("But DMA NOT firing!");
text_status_line3.set("Check SGPIO14 enable.");
text_status_line1.set_style(Theme::getInstance()->fg_orange);
text_status_line2.set_style(Theme::getInstance()->fg_orange);
text_status_line3.set_style(Theme::getInstance()->fg_orange);
} else if (xfr > 0) {
text_status_line1.set("DMA WORKING!");
text_status_line2.set("Xfr: " + to_string_dec_uint(xfr));
text_status_line3.set("Data flowing to baseband.");
text_status_line1.set_style(Theme::getInstance()->fg_green);
text_status_line2.set_style(Theme::getInstance()->fg_green);
text_status_line3.set_style(Theme::getInstance()->fg_green);
}
}
/* SGPIOLiveMonitorView ****************************************************/
SGPIOLiveMonitorView::SGPIOLiveMonitorView(NavigationView& nav)
: nav_(nav) {
add_children({
&text_title,
&text_lbl_ctrl,
&text_ctrl,
&text_lbl_in,
&text_in,
&text_lbl_ss,
&text_ss,
&text_lbl_status,
&text_status,
&text_lbl_out,
&text_out,
&text_lbl_oen,
&text_oen,
&text_diag_line1,
&text_diag_line2,
&text_diag_line3,
&text_diag_line4,
&button_refresh,
&button_done,
});
// Set title color
text_title.set_style(Theme::getInstance()->fg_yellow);
button_refresh.on_select = [this](Button&) {
update();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
// Initial update
update();
}
void SGPIOLiveMonitorView::focus() {
button_refresh.focus();
}
void SGPIOLiveMonitorView::update() {
// Read SGPIO registers via radio debug namespace
uint32_t ctrl = radio::debug::sgpio::register_read(0); // CTRL_ENABLE
uint32_t in_reg = radio::debug::sgpio::register_read(1); // GPIO_INREG
uint32_t status = radio::debug::sgpio::register_read(4); // STATUS_1
// Read registers directly from LPC_SGPIO peripheral
uint32_t reg_ss = LPC_SGPIO->REG_SS[0];
uint32_t out_reg = LPC_SGPIO->GPIO_OUTREG;
uint32_t oen_reg = LPC_SGPIO->GPIO_OENREG;
// Display register values
text_ctrl.set(to_string_hex(ctrl, 4));
text_in.set(to_string_hex(in_reg, 8));
text_ss.set(to_string_hex(reg_ss, 8));
text_status.set(to_string_hex(status, 4));
text_out.set(to_string_hex(out_reg, 4));
text_oen.set(to_string_hex(oen_reg, 4));
// Diagnostics based on register values
bool gpio_changing = (in_reg & 0xFF) != 0; // Check data pins
bool regss_active = (reg_ss != 0);
bool disable_high = (out_reg & (1U << 10)) != 0; // Bit 10 = DISABLE signal
bool sgpio8_high = (in_reg & (1U << 8)) != 0; // Bit 8 = SGPIO8 clock
bool sgpio8_output = (oen_reg & (1U << 8)) != 0; // Bit 8 = SGPIO8 direction (should be INPUT=0)
// Line 1: SGPIO8 direction check (CRITICAL - must be INPUT)
if (sgpio8_output) {
text_diag_line1.set("SGPIO8 OUTPUT! (bus conflict)");
text_diag_line1.set_style(Theme::getInstance()->fg_red);
} else if (disable_high) {
text_diag_line1.set("DISABLE=HIGH! FPGA stopped!");
text_diag_line1.set_style(Theme::getInstance()->fg_red);
} else {
text_diag_line1.set("SGPIO8=IN, DISABLE=LOW");
text_diag_line1.set_style(Theme::getInstance()->fg_green);
}
// Line 2: Clock signal status (snapshot - can't detect toggling)
if (disable_high) {
text_diag_line2.set("Clock N/A (FPGA disabled)");
text_diag_line2.set_style(Theme::getInstance()->fg_medium);
} else if (sgpio8_high) {
text_diag_line2.set("SGPIO8=HIGH (snapshot)");
text_diag_line2.set_style(Theme::getInstance()->fg_green);
} else {
text_diag_line2.set("SGPIO8=LOW (snapshot)");
text_diag_line2.set_style(Theme::getInstance()->fg_green);
}
// Line 3: REG_SS[0] capture status
if (!regss_active && !disable_high) {
text_diag_line3.set("REG_SS[0]=0 (NOT CAPTURING!)");
text_diag_line3.set_style(Theme::getInstance()->fg_red);
} else if (regss_active) {
text_diag_line3.set("REG_SS[0] has data");
text_diag_line3.set_style(Theme::getInstance()->fg_green);
} else {
text_diag_line3.set("Capture N/A (FPGA disabled)");
text_diag_line3.set_style(Theme::getInstance()->fg_medium);
}
// Line 4: Summary based on key indicators
if (sgpio8_output) {
text_diag_line4.set("FIX: Set SGPIO8 to INPUT!");
text_diag_line4.set_style(Theme::getInstance()->fg_red);
} else if (disable_high) {
text_diag_line4.set("FIX: Clear DISABLE bit!");
text_diag_line4.set_style(Theme::getInstance()->fg_red);
} else if (regss_active && gpio_changing) {
text_diag_line4.set("SGPIO capturing data");
text_diag_line4.set_style(Theme::getInstance()->fg_green);
} else if (!regss_active && gpio_changing) {
text_diag_line4.set("Data present, check slices");
text_diag_line4.set_style(Theme::getInstance()->fg_orange);
} else if (!regss_active) {
text_diag_line4.set("No data activity");
text_diag_line4.set_style(Theme::getInstance()->fg_orange);
} else {
text_diag_line4.set("Check DMA config");
text_diag_line4.set_style(Theme::getInstance()->fg_green);
}
}
/* RadioRxTestView ********************************************************/
RadioRxTestView::RadioRxTestView(NavigationView& nav)
: nav_(nav) {
add_children({
&labels,
&console,
&button_init,
&button_rx,
&button_freq,
&button_sgpio,
&button_full,
&button_step,
&button_done,
});
button_init.on_select = [this](Button&) {
run_init_test();
};
button_rx.on_select = [this](Button&) {
run_rx_mode_test();
};
button_freq.on_select = [this](Button&) {
run_freq_test();
};
button_sgpio.on_select = [this](Button&) {
run_sgpio_test();
};
button_full.on_select = [this](Button&) {
run_full_test();
};
button_step.on_select = [this](Button&) {
run_step_test();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
log("Ready. Press buttons to test.");
log("Init->RX->Freq->SGPIO");
}
void RadioRxTestView::focus() {
button_full.focus();
}
void RadioRxTestView::log(const std::string& msg) {
console.writeln(msg);
}
void RadioRxTestView::log_registers(const std::string& label) {
// RFFC5072 register 0
uint32_t rffc_r0 = radio::debug::first_if::register_read(0);
// MAX283x registers 0, 3, 4 (key freq regs)
uint32_t max_r0 = radio::debug::second_if::register_read(0);
uint32_t max_r3 = radio::debug::second_if::register_read(3);
uint32_t max_r4 = radio::debug::second_if::register_read(4);
// SGPIO
uint32_t sgpio_en = radio::debug::sgpio::register_read(0);
uint32_t sgpio_data = radio::debug::sgpio::register_read(5);
log(label);
log(" RFFC:" + to_string_hex(rffc_r0, 4));
log(" MAX r0:" + to_string_hex(max_r0, 4) +
" r3:" + to_string_hex(max_r3, 4) +
" r4:" + to_string_hex(max_r4, 4));
log(" SGPIO en:" + to_string_hex(sgpio_en, 4) +
" dat:" + to_string_hex(sgpio_data, 8));
#ifdef PRALINE
uint32_t fpga_ctrl = radio::debug::fpga::register_read(1);
log(" FPGA ctrl:" + to_string_hex(fpga_ctrl, 2));
#endif
}
void RadioRxTestView::run_init_test() {
console.clear(true);
log("=== INIT TEST ===");
log("radio::init()...");
radio::init();
radio_initialized_ = true;
log("set_baseband_rate(8M)...");
radio::set_baseband_rate(8000000);
// Read Si5351 status to check PLL lock
uint8_t si_status = portapack::clock_manager.si5351_read_status();
log("Si5351 status: " + to_string_hex(si_status, 2));
if (si_status & 0x20) {
log(" WARNING: PLL A unlocked!");
} else {
log(" PLL A locked OK");
}
// Read crystal cap register
uint8_t xtal_cap = portapack::clock_manager.si5351_read_register(183);
log("Crystal cap: " + to_string_hex(xtal_cap, 2));
log_registers("[After init]");
log("Init+clocks done.");
}
void RadioRxTestView::run_rx_mode_test() {
console.clear(true);
log("=== RX MODE TEST ===");
if (!radio_initialized_) {
log("ERROR: Run Init first!");
return;
}
log_registers("[Before RX mode]");
log("Calling set_direction(Receive)...");
radio::set_direction(rf::Direction::Receive);
log_registers("[After RX mode]");
// Check MAX283x mode register
uint32_t max_r0 = radio::debug::second_if::register_read(0);
log("MAX r0 after RX: " + to_string_hex(max_r0, 4));
log("RX mode set.");
}
void RadioRxTestView::run_freq_test() {
console.clear(true);
log("=== FREQ TEST ===");
if (!radio_initialized_) {
log("ERROR: Run Init first!");
return;
}
log_registers("[Before freq set]");
log("Setting " + to_string_dec_uint(test_frequency_ / 1000000) + " MHz...");
bool result = radio::set_tuning_frequency(test_frequency_);
log_registers("[After freq set]");
log(result ? "Freq set OK" : "Freq set FAILED");
// Show expected vs actual for MAX2831 freq regs
// For 433 MHz with MAX2831: F_LO = 40M * (N + F/2^20) / 2
// N = 43, F = ~629146 for ~433 MHz
log("(Expected: N~43 in r3, F_hi in r4)");
}
void RadioRxTestView::run_sgpio_test() {
console.clear(true);
log("=== SGPIO TEST (FIXED) ===");
// CRITICAL FIX: Set DISABLE=HIGH first (reference HackRF pattern)
LPC_SGPIO->GPIO_OENREG = (1U << 10) | (1U << 11); // SGPIO10,11 outputs
LPC_SGPIO->GPIO_OUTREG = (1U << 10); // DISABLE=HIGH during config
log("Set DISABLE=HIGH");
// Small delay for signals to settle
for (volatile int i = 0; i < 10000; i++) {
}
// NOW enable streaming (DISABLE=LOW)
LPC_SGPIO->GPIO_OUTREG = 0; // DISABLE=LOW, DIRECTION=LOW (RX)
log("Set DISABLE=LOW (streaming)");
for (volatile int i = 0; i < 10000; i++) {
}
// Read raw GPIO_INREG multiple times
uint32_t g[4];
for (int i = 0; i < 4; i++) {
g[i] = LPC_SGPIO->GPIO_INREG;
for (volatile int j = 0; j < 10000; j++) {
}
}
log("GPIO_IN:");
log(" " + to_string_hex(g[0], 8) + " " + to_string_hex(g[1], 8));
log(" " + to_string_hex(g[2], 8) + " " + to_string_hex(g[3], 8));
bool changing = (g[0] != g[1]) || (g[1] != g[2]) || (g[2] != g[3]);
if (changing) {
log("PASS: Data changing!");
} else if (g[0] == 0) {
log("FAIL: All zeros");
} else if (g[0] == 0x00000FFF) {
log("FAIL: 0xFFF = pull-ups");
log("FPGA not driving data");
} else {
log("FAIL: Static " + to_string_hex(g[0], 8));
}
uint32_t out = LPC_SGPIO->GPIO_OUTREG;
log("HOST_DIS=" + to_string_dec_uint((out >> 10) & 1));
}
void RadioRxTestView::run_full_test() {
console.clear(true);
log("=== FULL RX TEST ===");
// Step 1: Init
log("[1/6] Init radio...");
radio::init();
radio_initialized_ = true;
// Step 2: Set sample rate (configures Si5351 clocks!)
log("[2/6] Set 8M sample rate...");
radio::set_baseband_rate(8000000);
// Step 3: RX mode
log("[3/6] Set RX mode...");
radio::set_direction(rf::Direction::Receive);
// Step 4: Frequency - use 2437 MHz (WiFi ch6) which is in MAX2831 range
uint32_t wifi_freq = 2437000000;
log("[4/6] Set 2437 MHz (WiFi)...");
bool freq_ok = radio::set_tuning_frequency(wifi_freq);
log(freq_ok ? " Freq OK" : " Freq FAIL");
// Step 5: Configure SGPIO outputs with correct DISABLE sequence
log("[5/6] Configure SGPIO...");
// CRITICAL FIX: Set DISABLE=HIGH first
LPC_SGPIO->GPIO_OENREG = (1U << 10) | (1U << 11); // SGPIO10,11 as outputs
LPC_SGPIO->GPIO_OUTREG = (1U << 10); // DISABLE=HIGH during config
log(" DISABLE=HIGH");
// Delay for settle
for (volatile int i = 0; i < 10000; i++) {
}
// NOW enable streaming (DISABLE=LOW)
LPC_SGPIO->GPIO_OUTREG = 0; // DISABLE=LOW, DIRECTION=LOW (RX)
log(" DISABLE=LOW (streaming)");
// Step 6: Check raw GPIO pins
log("[6/6] Check GPIO pins...");
// Delay for stabilization
for (volatile int i = 0; i < 200000; i++) {
}
// Read raw GPIO_INREG multiple times
uint32_t g1 = LPC_SGPIO->GPIO_INREG;
for (volatile int i = 0; i < 10000; i++) {
}
uint32_t g2 = LPC_SGPIO->GPIO_INREG;
for (volatile int i = 0; i < 10000; i++) {
}
uint32_t g3 = LPC_SGPIO->GPIO_INREG;
for (volatile int i = 0; i < 10000; i++) {
}
uint32_t g4 = LPC_SGPIO->GPIO_INREG;
log("GPIO_IN readings:");
log(" " + to_string_hex(g1, 8) + " " + to_string_hex(g2, 8));
log(" " + to_string_hex(g3, 8) + " " + to_string_hex(g4, 8));
bool gpio_changing = (g1 != g2) || (g2 != g3) || (g3 != g4);
bool gpio_not_zero = (g1 != 0);
bool gpio_not_fff = (g1 != 0x00000FFF);
// Check Si5351 output enable register (reg 3)
// Bits 0-7: CLK0-7 output enable (0=enabled, 1=disabled)
// We want CLK0 and CLK1 enabled (bits 0,1 = 0)
log("---");
if (gpio_changing) {
log("=== PASS: Data flowing! ===");
} else if (gpio_not_fff && gpio_not_zero) {
log("=== PARTIAL: Static data ===");
log("FPGA outputs but no clock?");
} else if (!gpio_not_zero) {
log("=== FAIL: All zeros ===");
log("FPGA not driving outputs");
} else {
log("=== FAIL: All FFF (pull-ups) ===");
log("FPGA outputs high-Z");
log("Check: Si5351 CLK0/CLK1");
}
log_registers("[Final]");
}
bool RadioRxTestView::check_gpio_changing() {
uint32_t g[4];
for (int i = 0; i < 4; i++) {
g[i] = LPC_SGPIO->GPIO_INREG;
for (volatile int j = 0; j < 10000; j++) {
}
}
return (g[0] != g[1]) || (g[1] != g[2]) || (g[2] != g[3]);
}
void RadioRxTestView::run_step_test() {
console.clear(true);
log("=== STEP TEST (FIXED) ===");
log("Correct DISABLE sequence");
// Ensure radio is initialized
if (!radio_initialized_) {
log("Init radio...");
radio::init();
radio_initialized_ = true;
radio::set_baseband_rate(8000000);
radio::set_direction(rf::Direction::Receive);
radio::set_tuning_frequency(2437000000);
}
// Step 0: Baseline with DISABLE=HIGH first
log("[0] Baseline (DISABLE=HIGH)");
LPC_SGPIO->CTRL_ENABLE = 0; // Disable all slices
LPC_SGPIO->GPIO_OENREG = 0x0C00; // Bits 10, 11 outputs
LPC_SGPIO->GPIO_OUTREG = (1U << 10); // DISABLE=HIGH first!
for (volatile int i = 0; i < 100000; i++) {
}
// Now enable streaming to check baseline
LPC_SGPIO->GPIO_OUTREG = 0x0000; // DISABLE=LOW
for (volatile int i = 0; i < 100000; i++) {
}
bool step0 = check_gpio_changing();
log(step0 ? " PASS: Data changing" : " FAIL: Data static");
if (!step0) {
log("ABORT: Baseline broken");
return;
}
// NOW disable streaming for configuration
log("[Config] Set DISABLE=HIGH");
LPC_SGPIO->GPIO_OUTREG = (1U << 10); // DISABLE=HIGH
for (volatile int i = 0; i < 100000; i++) {
}
// Step 1: OUT_MUX_CFG data pins - test individually
log("[1] OUT_MUX_CFG[0-7] data");
uint32_t data_out_mux = (9U << 0) | (0U << 4); // DOUT_DOUTM8A, GPIO_OE
for (size_t i = 0; i < 8; i++) {
uint32_t before = LPC_SGPIO->GPIO_INREG;
LPC_SGPIO->OUT_MUX_CFG[i] = data_out_mux;
for (volatile int j = 0; j < 50000; j++) {
}
uint32_t after = LPC_SGPIO->GPIO_INREG;
bool ok = check_gpio_changing();
log(" [" + to_string_dec_uint(i) + "] " +
to_string_hex(before & 0xFF, 2) + "->" +
to_string_hex(after & 0xFF, 2) +
(ok ? " OK" : " FAIL"));
if (!ok) {
log("CULPRIT: OUT_MUX_CFG[" + to_string_dec_uint(i) + "]");
return;
}
}
log(" All data pins PASS");
// Step 2: OUT_MUX_CFG control pins - SKIP PIN 10 (HOST_DISABLE)
log("[2] OUT_MUX_CFG ctrl pins");
log(" (skipping pin 10 - breaks)");
struct {
int pin;
uint32_t val;
} ctrl_pins[] = {
{8, (0U << 0) | (0U << 4)},
{9, (0U << 0) | (0U << 4)},
// {10, (4U << 0) | (0U << 4)}, // SKIP - causes failure
{11, (4U << 0) | (0U << 4)},
{14, (0U << 0) | (0U << 4)}};
for (auto& p : ctrl_pins) {
uint32_t before = LPC_SGPIO->GPIO_INREG;
LPC_SGPIO->OUT_MUX_CFG[p.pin] = p.val;
for (volatile int i = 0; i < 50000; i++) {
}
uint32_t after = LPC_SGPIO->GPIO_INREG;
bool ok = check_gpio_changing();
log(" [" + to_string_dec_uint(p.pin) + "] " +
to_string_hex(before & 0xFF, 2) + "->" +
to_string_hex(after & 0xFF, 2) +
(ok ? " OK" : " FAIL"));
if (!ok) {
log("CULPRIT: OUT_MUX_CFG[" + to_string_dec_uint(p.pin) + "]");
return;
}
}
log(" All ctrl pins PASS");
// Step 3: Set GPIO_OENREG for RX
log("[3] GPIO_OENREG full RX");
LPC_SGPIO->GPIO_OENREG = 0x0C00; // Keep same as baseline
for (volatile int i = 0; i < 100000; i++) {
}
bool step3 = check_gpio_changing();
log(step3 ? " PASS" : " FAIL: Data stopped!");
if (!step3) {
log("CULPRIT: GPIO_OENREG");
return;
}
// Step 3.5: Configure slice D as clock source (CRITICAL!)
log("[3.5] Slice D clock source");
const uint32_t slice_d = 3;
// SGPIO_MUX_CFG: External clock from SGPIO8, qualifier from SGPIO9
LPC_SGPIO->SGPIO_MUX_CFG[slice_d] = (1U << 0) | (0U << 1) | (0U << 3) | (3U << 5) | (1U << 7) | (0U << 9) | (0U << 11) | (0U << 12);
// SLICE_MUX_CFG: 1 bit per clock, CLKGEN_MODE=1 (external clock!) <- FIX
LPC_SGPIO->SLICE_MUX_CFG[slice_d] = (0U << 0) | (0U << 1) | (0U << 2) | (0U << 3) | (1U << 4) | (0U << 6) | (0U << 8);
LPC_SGPIO->PRESET[slice_d] = 0;
LPC_SGPIO->COUNT[slice_d] = 0;
LPC_SGPIO->POS[slice_d] = (0x1F << 0) | (0x1F << 8);
LPC_SGPIO->REG[slice_d] = 0x11111111;
LPC_SGPIO->REG_SS[slice_d] = 0x11111111;
// Enable slice D counter
LPC_SGPIO->CTRL_ENABLE = (1U << slice_d);
for (volatile int i = 0; i < 100000; i++) {
}
bool step3_5 = check_gpio_changing();
log(step3_5 ? " PASS" : " FAIL: Data stopped!");
if (!step3_5) {
log("CULPRIT: Slice D config");
return;
}
// Step 4: SGPIO_MUX_CFG slice A
log("[4] SGPIO_MUX_CFG[A]");
LPC_SGPIO->SGPIO_MUX_CFG[0] = (1U << 0) | (0U << 1) | (3U << 3) | (3U << 5) | (1U << 7) | (0U << 9) | (0U << 11) | (0U << 12); // Clock from slice D (bit3-4=3), external pin SGPIO8, qualifier SGPIO9
for (volatile int i = 0; i < 100000; i++) {
}
bool step4 = check_gpio_changing();
log(step4 ? " PASS" : " FAIL: Data stopped!");
if (!step4) {
log("CULPRIT: SGPIO_MUX_CFG[A]");
return;
}
// Step 5: SLICE_MUX_CFG slice A
log("[5] SLICE_MUX_CFG[A]");
LPC_SGPIO->SLICE_MUX_CFG[0] = (0U << 0) | (0U << 1) | (1U << 2) | (0U << 3) | (1U << 4) | (3U << 6) | (0U << 8); // CLKGEN_MODE=1 (external clock!), PARALLEL_MODE 1 byte
for (volatile int i = 0; i < 100000; i++) {
}
bool step5 = check_gpio_changing();
log(step5 ? " PASS" : " FAIL: Data stopped!");
if (!step5) {
log("CULPRIT: SLICE_MUX_CFG[A]");
return;
}
// Step 6: Slice A registers
log("[6] Slice A registers");
LPC_SGPIO->PRESET[0] = 0;
LPC_SGPIO->COUNT[0] = 0;
LPC_SGPIO->POS[0] = (0x1F << 0) | (0x1F << 8); // pos, pos_reset
LPC_SGPIO->REG[0] = 0;
LPC_SGPIO->REG_SS[0] = 0;
for (volatile int i = 0; i < 100000; i++) {
}
bool step6 = check_gpio_changing();
log(step6 ? " PASS" : " FAIL: Data stopped!");
if (!step6) {
log("CULPRIT: Slice A registers");
return;
}
// Step 7: Enable slice A counter (keep slice D enabled) - still with DISABLE=HIGH
log("[7] Enable slices D+A");
LPC_SGPIO->CTRL_ENABLE = (1U << 3) | (1U << 0); // Slice D + Slice A
for (volatile int i = 0; i < 100000; i++) {
}
// Check STATUS_1 BEFORE enabling streaming
uint32_t status_pre = LPC_SGPIO->STATUS_1;
log(" STATUS_1 (pre): " + to_string_hex(status_pre, 4));
// Step 8: Enable streaming (DISABLE=LOW) - THIS IS THE CRITICAL TEST
log("[8] Enable streaming (DISABLE=LOW)");
LPC_SGPIO->GPIO_OUTREG = 0; // DISABLE=LOW
for (volatile int i = 0; i < 100000; i++) {
}
// Check if slices become active
uint32_t status_post = LPC_SGPIO->STATUS_1;
uint32_t regss = LPC_SGPIO->REG_SS[0];
uint32_t count_a = LPC_SGPIO->COUNT[0];
log(" STATUS_1 (post): " + to_string_hex(status_post, 4));
log(" REG_SS[0]: " + to_string_hex(regss, 8));
log(" COUNT[0]: " + to_string_hex(count_a, 8));
bool step8 = check_gpio_changing();
log(step8 ? " GPIO still changing" : " GPIO stopped!");
if ((status_post & 1) && regss != 0) {
log("=== SUCCESS! Slice A capturing! ===");
} else if (status_post & 1) {
log("=== PARTIAL: Slice A active but REG_SS=0 ===");
} else {
log("=== FAIL: Slice A not active ===");
log("Expected: STATUS_1 bit 0 = 1");
log("Actual: STATUS_1 bit 0 = " + to_string_dec_uint(status_post & 1));
}
}
/* SGPIO8ClockDetectorView ***********************************************/
SGPIO8ClockDetectorView::SGPIO8ClockDetectorView(NavigationView& nav)
: nav_(nav) {
add_children({
&text_title,
&text_lbl_samples,
&text_samples,
&text_lbl_toggles,
&text_toggles,
&text_status,
&button_sample,
&button_done,
});
text_title.set_style(Theme::getInstance()->fg_yellow);
button_sample.on_select = [this](Button&) {
sample_sgpio8();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
// Auto-sample on load
sample_sgpio8();
}
void SGPIO8ClockDetectorView::focus() {
button_sample.focus();
}
void SGPIO8ClockDetectorView::sample_sgpio8() {
// Sample SGPIO8 (bit 8 of GPIO_INREG) as fast as possible
// NOTE: Software sampling cannot accurately measure clock frequency
// This only detects presence/absence of clock activity
const int num_samples = 2000;
uint8_t samples[num_samples];
// Sample as fast as possible
for (int i = 0; i < num_samples; i++) {
samples[i] = (LPC_SGPIO->GPIO_INREG >> 8) & 1;
}
// Count toggles (transitions 0→1 or 1→0)
int toggles = 0;
for (int i = 1; i < num_samples; i++) {
if (samples[i] != samples[i - 1]) {
toggles++;
}
}
// Display first 20 samples
std::string sample_str;
for (int i = 0; i < 20 && i < num_samples; i++) {
sample_str += (samples[i] ? "1" : "0");
}
text_samples.set(sample_str);
// Display toggle count
text_toggles.set(to_string_dec_uint(toggles) + " / " +
to_string_dec_uint(num_samples - 1) + " transitions");
// Status interpretation - just presence detection
if (toggles > 100) {
text_status.set("CLOCK ACTIVE");
text_status.set_style(Theme::getInstance()->fg_green);
} else if (toggles > 0) {
text_status.set("SOME ACTIVITY (" + to_string_dec_uint(toggles) + ")");
text_status.set_style(Theme::getInstance()->fg_orange);
} else {
text_status.set("NO CLOCK - Stuck " +
std::string(samples[0] ? "HIGH" : "LOW"));
text_status.set_style(Theme::getInstance()->fg_red);
}
}
/* Si5351DebugView *******************************************************/
Si5351DebugView::Si5351DebugView(NavigationView& nav)
: nav_(nav) {
add_children({&text_title,
&text_status_label,
&text_status_value,
&text_pll_a_label,
&text_pll_a_status,
&text_pll_b_label,
&text_pll_b_status,
&text_sys_init_label,
&text_sys_init_status,
&text_xtal_cap_label,
&text_xtal_cap_value,
&text_clk0_label,
&text_clk0_status,
&text_clk0_freq_value,
&text_clk0_div_value,
&text_clk1_label,
&text_clk1_status,
&text_clk4_label,
&text_clk4_status,
&text_clk5_label,
&text_clk5_status,
&button_refresh,
&button_reset_pll,
&button_done});
text_title.set_style(Theme::getInstance()->fg_yellow);
button_refresh.on_select = [this](Button&) {
refresh_status();
};
button_reset_pll.on_select = [this](Button&) {
reset_pll();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
// Auto-refresh on load
refresh_status();
}
void Si5351DebugView::focus() {
button_refresh.focus();
}
void Si5351DebugView::refresh_status() {
// Read device status register (reg 0)
uint8_t status = portapack::clock_manager.si5351_read_status();
text_status_value.set("0x" + to_string_hex(status, 2));
// Decode status bits
bool pll_a_locked = !(status & 0x20); // Bit 5: LOL_A (Loss of Lock A)
bool pll_b_locked = !(status & 0x40); // Bit 6: LOL_B (Loss of Lock B)
bool sys_init = (status & 0x80); // Bit 7: SYS_INIT
// bool los_clkin = (status & 0x10); // Bit 4: LOS (Loss of Signal) (quoted as it's computed but unused)
// PLL A status
if (pll_a_locked) {
text_pll_a_status.set("LOCKED");
text_pll_a_status.set_style(Theme::getInstance()->fg_green);
} else {
text_pll_a_status.set("UNLOCKED");
text_pll_a_status.set_style(Theme::getInstance()->fg_red);
}
// PLL B status
if (pll_b_locked) {
text_pll_b_status.set("LOCKED");
text_pll_b_status.set_style(Theme::getInstance()->fg_green);
} else {
text_pll_b_status.set("UNLOCKED (unused)");
text_pll_b_status.set_style(Theme::getInstance()->fg_orange);
}
// SYS_INIT status
if (sys_init) {
text_sys_init_status.set("IN PROGRESS");
text_sys_init_status.set_style(Theme::getInstance()->fg_orange);
} else {
text_sys_init_status.set("COMPLETE");
text_sys_init_status.set_style(Theme::getInstance()->fg_green);
}
// Read crystal load capacitance (reg 183)
uint8_t xtal_cap = portapack::clock_manager.si5351_read_register(183);
text_xtal_cap_value.set("0x" + to_string_hex(xtal_cap, 2) +
" (" + to_string_dec_uint((xtal_cap >> 6) & 0x03) + ")");
// Read clock output enables (reg 16-23 control, reg 3 for output enable mask)
uint8_t output_enable_mask = portapack::clock_manager.si5351_read_register(3);
// CLK0 (bit 0 of reg 3, reg 16 for control)
uint8_t clk0_ctrl = portapack::clock_manager.si5351_read_register(16);
bool clk0_enabled = !(output_enable_mask & 0x01) && !(clk0_ctrl & 0x80);
text_clk0_status.set(clk0_enabled ? "ON" : "OFF");
text_clk0_status.set_style(clk0_enabled ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_red);
// Read MS0 multisynth parameters (registers 42-49) to calculate actual frequency
// Si5351 MS0 Register Layout:
// Reg 42: P3[15:8]
// Reg 43: P3[7:0]
// Reg 44: bits 6:4 = R_DIV[2:0], bits 1:0 = P1[17:16]
// Reg 45: P1[15:8]
// Reg 46: P1[7:0]
// Reg 47: bits 7:4 = P3[19:16], bits 3:0 = P2[19:16]
// Reg 48: P2[15:8]
// Reg 49: P2[7:0]
uint8_t reg44 = portapack::clock_manager.si5351_read_register(44);
uint8_t reg45 = portapack::clock_manager.si5351_read_register(45);
uint8_t reg46 = portapack::clock_manager.si5351_read_register(46);
// Decode R divider from bits 6:4 of register 44
uint8_t r_div_encoded = (reg44 >> 4) & 0x07;
uint32_t r_div = 1 << r_div_encoded; // R = 2^r_div_encoded
// Decode P1 (18-bit value): bits 1:0 of reg44 = P1[17:16], reg45 = P1[15:8], reg46 = P1[7:0]
uint32_t p1 = ((uint32_t)(reg44 & 0x03) << 16) | ((uint32_t)reg45 << 8) | reg46;
// Calculate divider: a = (P1 + 512) / 128 for integer dividers (b=0)
// Expected for 8 MHz: P1=5888 (0x1700), a=50
uint32_t ms_div = (p1 + 512) / 128; // Integer divider value
// Calculate frequency: f_out = 800 or 800 MHz / ms_div / r_div
uint32_t vco_khz = (clk0_ctrl & 0x20) ? 800000 : 800000; // PLLB vs PLLA
uint32_t freq_khz = vco_khz / ms_div / r_div; // Result in kHz
// Show P1 value and R45 for debugging
text_clk0_freq_value.set("F:" + to_string_dec_uint(freq_khz / 1000) + "MHz (P1:" + to_string_hex(p1, 4) + ")");
text_clk0_div_value.set("DIV: MS=" + to_string_dec_uint(ms_div) +
" R=" + to_string_dec_uint(r_div));
// Color code based on expected 8 MHz
if (freq_khz >= 7900 && freq_khz <= 8100) {
text_clk0_freq_value.set_style(Theme::getInstance()->fg_green);
text_clk0_div_value.set_style(Theme::getInstance()->fg_green);
} else {
text_clk0_freq_value.set_style(Theme::getInstance()->fg_red);
text_clk0_div_value.set_style(Theme::getInstance()->fg_red);
}
// CLK1 (bit 1 of reg 3, reg 17 for control)
uint8_t clk1_ctrl = portapack::clock_manager.si5351_read_register(17);
bool clk1_enabled = !(output_enable_mask & 0x02) && !(clk1_ctrl & 0x80);
text_clk1_status.set(clk1_enabled ? "ON" : "OFF");
text_clk1_status.set_style(clk1_enabled ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_red);
// CLK4 (MAX2831 reference - bit 4 of reg 3, reg 20 for control)
uint8_t clk4_ctrl = portapack::clock_manager.si5351_read_register(20);
bool clk4_enabled = !(output_enable_mask & 0x10) && !(clk4_ctrl & 0x80);
text_clk4_status.set(clk4_enabled ? "ON (40MHz)" : "OFF");
text_clk4_status.set_style(clk4_enabled ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_red);
// CLK5 (RFFC5072 reference - bit 5 of reg 3, reg 21 for control)
uint8_t clk5_ctrl = portapack::clock_manager.si5351_read_register(21);
bool clk5_enabled = !(output_enable_mask & 0x20) && !(clk5_ctrl & 0x80);
text_clk5_status.set(clk5_enabled ? "ON (40MHz)" : "OFF");
text_clk5_status.set_style(clk5_enabled ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_red);
}
void Si5351DebugView::reset_pll() {
// Reset both PLLs (write to reg 177)
portapack::clock_manager.si5351_read_register(177); // Read first
portapack::clock_manager.si5351_write_register(177, 0xAC); // Reset both PLLs
// Small delay for PLL to settle
chThdSleepMilliseconds(10);
// Refresh status to show new lock state
refresh_status();
}
#ifdef PRALINE
/* SignalPathStatusView *************************************************/
SignalPathStatusView::SignalPathStatusView(NavigationView& nav)
: nav_(nav) {
add_children({
&text_title,
&text_lbl_max_enable,
&text_max_enable,
&text_lbl_max_mode,
&text_max_mode,
&text_lbl_rf_path,
&text_rf_path,
&text_lbl_filter,
&text_filter,
&text_lbl_mixer,
&text_mixer,
&text_lbl_rf_amp,
&text_rf_amp,
&text_lbl_lna,
&text_lna,
&text_lbl_vga,
&text_vga,
&text_lbl_fpga_decim,
&text_fpga_decim,
&text_lbl_fpga_ctrl_dc_q,
&text_fpga_ctrl_dc_q,
&text_lbl_fpga_ctrl_qs,
&text_fpga_ctrl_qs,
&text_status,
&button_refresh,
&button_toggle_q,
&button_done,
});
text_title.set_style(Theme::getInstance()->fg_yellow);
button_refresh.on_select = [this](Button&) {
refresh_status();
};
button_toggle_q.on_select = [this](Button&) {
static bool q_override = false;
q_override = !q_override;
uint8_t ctrl_reg = 0x01; // DC_BLOCK always on
if (q_override) {
ctrl_reg |= 0x02; // Force Q_INVERT ON
}
radio::debug::fpga::register_write(1, ctrl_reg);
radio::invalidate_spi_config();
refresh_status();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
// Initial update
refresh_status();
}
void SignalPathStatusView::focus() {
button_refresh.focus();
}
void SignalPathStatusView::refresh_status() {
// Get cached state from radio driver
rf::Direction direction = radio::debug::get_cached_direction();
bool rf_amp = radio::debug::get_cached_rf_amp();
int_fast8_t cached_lna = radio::debug::get_cached_lna_gain();
int_fast8_t cached_vga = radio::debug::get_cached_vga_gain();
// Get current band.
auto current_band = radio::debug::rf_path_info::get_current_band();
switch (current_band) {
case rf::path::Band::Low:
text_filter.set("LOW PASS");
text_filter.set_style(Theme::getInstance()->fg_green);
text_mixer.set("ENABLED");
text_mixer.set_style(Theme::getInstance()->fg_green);
break;
case rf::path::Band::Mid:
text_filter.set("BYPASS");
text_filter.set_style(Theme::getInstance()->fg_green);
text_mixer.set("DISABLED");
text_mixer.set_style(Theme::getInstance()->fg_orange);
break;
case rf::path::Band::High:
text_filter.set("HIGH PASS");
text_filter.set_style(Theme::getInstance()->fg_green);
text_mixer.set("ENABLED");
text_mixer.set_style(Theme::getInstance()->fg_green);
break;
default:
text_filter.set("UNKNOWN");
text_filter.set_style(Theme::getInstance()->fg_red);
text_mixer.set("UNKNOWN");
text_mixer.set_style(Theme::getInstance()->fg_red);
}
// Read actual register values to verify
uint32_t max_r11 = radio::debug::second_if::register_read(11);
// Decode actual LNA gain from register (bits 6:5)
uint8_t lna_bits = (max_r11 >> 5) & 0x03;
int actual_lna_db;
switch (lna_bits) {
case 0:
actual_lna_db = 0;
break; // -33 dB from max
case 2:
actual_lna_db = 17;
break; // -16 dB from max
case 3:
actual_lna_db = 33;
break; // Maximum
default:
actual_lna_db = -1;
break; // Invalid
}
// Decode actual VGA gain from register (bits 4:0)
uint8_t vga_bits = (max_r11 >> 0) & 0x1F;
int actual_vga_db = vga_bits * 2; // 0-31 → 0-62 dB
// MAX2831 Enable/Mode (GPIO-controlled, show cached state)
bool rx_mode = (direction == rf::Direction::Receive);
text_max_enable.set("ENABLED cached");
text_max_enable.set_style(Theme::getInstance()->fg_green);
text_max_mode.set(rx_mode ? "RX cached" : "TX cached");
text_max_mode.set_style(rx_mode ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_orange);
// RF path direction
text_rf_path.set(rx_mode ? "RECEIVE" : "TRANSMIT");
text_rf_path.set_style(rx_mode ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_orange);
// RF amp (GPIO-controlled, show cached state)
text_rf_amp.set(rf_amp ? "ON cached" : "OFF cached");
text_rf_amp.set_style(rf_amp ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_orange);
// LNA gain - show both cached and actual
if (actual_lna_db == cached_lna) {
text_lna.set(to_string_dec_uint(actual_lna_db) + " dB");
text_lna.set_style(Theme::getInstance()->fg_green);
} else if (actual_lna_db >= 0) {
// MAX2831 has discrete steps: 0, 17, 33 dB
// Allow ±8 dB tolerance for rounding
int diff = (actual_lna_db > cached_lna) ? (actual_lna_db - cached_lna) : (cached_lna - actual_lna_db);
if (diff <= 8) {
// Within rounding tolerance - show as OK with note
text_lna.set(to_string_dec_uint(actual_lna_db) + " dB (req:" +
to_string_dec_uint(cached_lna) + ")");
text_lna.set_style(Theme::getInstance()->fg_green);
} else {
// Genuine mismatch
text_lna.set(to_string_dec_uint(actual_lna_db) + " dB (!" +
to_string_dec_uint(cached_lna) + ")");
text_lna.set_style(Theme::getInstance()->fg_red);
}
} else {
text_lna.set("INVALID");
text_lna.set_style(Theme::getInstance()->fg_red);
}
// VGA gain - show both cached and actual
if (actual_vga_db == cached_vga) {
text_vga.set(to_string_dec_uint(actual_vga_db) + " dB");
text_vga.set_style(Theme::getInstance()->fg_green);
} else {
text_vga.set(to_string_dec_uint(actual_vga_db) + " dB (!" +
to_string_dec_uint(cached_vga) + ")");
text_vga.set_style(Theme::getInstance()->fg_red);
}
// FPGA decimation register
uint8_t fpga_decim = radio::debug::fpga::register_read(2);
text_fpga_decim.set("n=" + to_string_dec_uint(fpga_decim) +
" (/" + to_string_dec_uint(1 << fpga_decim) + ")");
// FPGA Register Ctrl Info
uint32_t fpga_ctrl = radio::debug::fpga::register_read(1);
// text_fpga_ctrl_dc_q.set(to_string_hex(fpga_ctrl, 2));
// text_fpga_ctrl_qs.set(to_string_hex(fpga_ctrl, 2));
// Decode bits
bool dc_block = fpga_ctrl & 0x01;
bool q_invert = fpga_ctrl & 0x02;
uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03;
// Display human-readable
std::string fpga_status_dc_q = "DC:" + std::string(dc_block ? "ON" : "OFF") +
" Q:" + std::string(q_invert ? "INV" : "NOR");
std::string fpga_status_qs = "QS:" + to_string_dec_uint(quarter_shift);
text_fpga_ctrl_dc_q.set(fpga_status_dc_q);
text_fpga_ctrl_qs.set(fpga_status_qs);
// Summary status
// Summary status - update to account for rounding tolerance
int lna_diff = (actual_lna_db > cached_lna) ? (actual_lna_db - cached_lna) : (cached_lna - actual_lna_db);
bool lna_ok = (actual_lna_db == cached_lna) || (lna_diff <= 8);
bool gains_match = lna_ok && (actual_vga_db == cached_vga);
if (rx_mode && gains_match) {
text_status.set("RX mode, gains verified!");
text_status.set_style(Theme::getInstance()->fg_green);
} else if (rx_mode && !gains_match) {
text_status.set("RX mode, gain MISMATCH!");
text_status.set_style(Theme::getInstance()->fg_red);
} else if (gains_match) {
text_status.set("TX mode, gains verified ✓");
text_status.set_style(Theme::getInstance()->fg_orange);
} else {
text_status.set("TX mode, gain MISMATCH!");
text_status.set_style(Theme::getInstance()->fg_red);
}
}
#endif
#ifdef PRALINE
/* SystemDiagnosticsView *************************************************/
SystemDiagnosticsView::SystemDiagnosticsView(NavigationView& nav) {
add_children({
&text_title,
&text_lbl_sample,
&text_sample_rate,
&text_lbl_band,
&text_band,
&button_sample_2m,
&button_sample_4m,
&button_sample_8m,
&button_sample_20m,
&text_lbl_bb_filter,
&text_bb_filter,
&text_lbl_reg8,
&text_reg8,
&text_lbl_gpio,
&text_lbl_lpf,
&text_gpio_lpf,
&text_lbl_mix,
&text_gpio_mix,
&text_lbl_amp,
&text_gpio_amp,
&text_lbl_fpga,
&text_lbl_fpga_ctrl,
&text_fpga_ctrl,
&text_lbl_fpga_decode,
&button_toggle_q,
&button_toggle_dc,
&button_refresh,
&button_done,
});
text_title.set_style(Theme::getInstance()->fg_yellow);
text_lbl_gpio.set_style(Theme::getInstance()->fg_yellow);
text_lbl_fpga.set_style(Theme::getInstance()->fg_yellow);
// Sample rate buttons
button_sample_2m.on_select = [this](Button&) {
set_sample_rate(2000000);
};
button_sample_4m.on_select = [this](Button&) {
set_sample_rate(4000000);
};
button_sample_8m.on_select = [this](Button&) {
set_sample_rate(8000000);
};
button_sample_20m.on_select = [this](Button&) {
set_sample_rate(20000000);
};
// Q inversion toggle
button_toggle_q.on_select = [this](Button&) {
uint32_t current = radio::debug::fpga::register_read(1);
uint8_t new_val = current ^ 0x02; // Toggle Q_INVERT bit
radio::debug::fpga::register_write(1, new_val);
radio::invalidate_spi_config();
refresh();
};
// DC block toggle
button_toggle_dc.on_select = [this](Button&) {
uint32_t current = radio::debug::fpga::register_read(1);
uint8_t new_val = current ^ 0x01; // Toggle DC_BLOCK bit
radio::debug::fpga::register_write(1, new_val);
radio::invalidate_spi_config();
refresh();
};
button_refresh.on_select = [this](Button&) {
refresh();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
refresh();
}
void SystemDiagnosticsView::focus() {
button_refresh.focus();
}
void SystemDiagnosticsView::set_sample_rate(uint32_t rate) {
portapack::clock_manager.set_sampling_frequency(rate);
refresh();
}
void SystemDiagnosticsView::read_gpio_states() {
// Read actual GPIO port states
uint32_t gpio3_state = LPC_GPIO->PIN[3]; // GPIO3 for mixer
uint32_t gpio4_state = LPC_GPIO->PIN[4]; // GPIO4 for LPF and amp
bool mix_n_actual = (gpio3_state >> 2) & 1; // GPIO3[2]
bool lpf_actual = (gpio4_state >> 8) & 1; // GPIO4[8]
bool amp_actual = (gpio4_state >> 9) & 1; // GPIO4[9]
// Mixer is active LOW, so invert for display
bool mixer_enabled = !mix_n_actual;
// Display with GPIO pin numbers
text_gpio_lpf.set(
std::string(lpf_actual ? "ON" : "OFF") +
" (GPIO4[8]=" + to_string_dec_uint(lpf_actual ? 1 : 0) + ")");
text_gpio_mix.set(
std::string(mixer_enabled ? "ENABLED" : "BYPASSED") +
" (GPIO3[2]=" + to_string_dec_uint(mix_n_actual ? 1 : 0) + ")");
text_gpio_amp.set(
std::string(amp_actual ? "ON" : "OFF") +
" (GPIO4[9]=" + to_string_dec_uint(amp_actual ? 1 : 0) + ")");
// Color code
text_gpio_lpf.set_style(lpf_actual ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
text_gpio_mix.set_style(mixer_enabled ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
text_gpio_amp.set_style(amp_actual ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_orange);
}
void SystemDiagnosticsView::refresh() {
// Sample rate
uint32_t sample_rate = portapack::clock_manager.get_sampling_frequency();
if (sample_rate >= 1000000) {
text_sample_rate.set(to_string_dec_uint(sample_rate / 1000000) + " MSS");
} else {
text_sample_rate.set(to_string_dec_uint(sample_rate / 1000) + " kSS");
}
// Baseband filter bandwidth
uint32_t reg8 = radio::debug::second_if::register_read(8);
text_reg8.set("0x" + to_string_hex(reg8, 4));
uint8_t lpf_coarse = reg8 & 0x03; // Bits 1:0
const char* bw_names[] = {"7.5 MHz", "8.5 MHz", "15 MHz", "18 MHz"};
text_bb_filter.set(bw_names[lpf_coarse]);
// Color code - green if >= 8 MHz, red otherwise
if (lpf_coarse >= 1) {
text_bb_filter.set_style(Theme::getInstance()->fg_green);
} else {
text_bb_filter.set_style(Theme::getInstance()->fg_red);
}
// Read actual GPIO pin states
read_gpio_states();
uint32_t gpio6_pin = LPC_GPIO->PIN[6];
bool rffc_locked = (gpio6_pin >> 25) & 1;
// Display it by changing one of the existing fields temporarily
// For example, modify the band display to show lock status:
auto current_band = radio::debug::rf_path_info::get_current_band();
switch (current_band) {
case rf::path::Band::Low:
text_band.set("LOW(0-2320MHz)|" + std::string(rffc_locked ? "LCK)" : "ULCK)"));
text_band.set_style(rffc_locked ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
break;
case rf::path::Band::Mid:
text_band.set("MID(2320-2740MHz)");
text_band.set_style(Theme::getInstance()->fg_green);
break;
case rf::path::Band::High:
text_band.set("HIGH(2740-7250MHz)");
text_band.set_style(Theme::getInstance()->fg_green);
break;
}
// FPGA control register
uint32_t fpga_ctrl = radio::debug::fpga::register_read(1);
text_fpga_ctrl.set("0x" + to_string_hex(fpga_ctrl, 2));
// Decode FPGA register bits
bool dc_block = fpga_ctrl & 0x01;
bool q_invert = fpga_ctrl & 0x02;
uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03;
text_lbl_fpga_decode.set(
"|DC:" + std::string(dc_block ? "ON" : "OFF") +
" Q:" + std::string(q_invert ? "INV" : "NOR") +
" QS:" + to_string_dec_uint(quarter_shift));
}
#endif
#ifdef PRALINE
/* Si5351PLLADebugView *******************************************************/
Si5351PLLADebugView::Si5351PLLADebugView(NavigationView& nav)
: nav_(nav) {
add_children({&text_title, &text_lbl_raw, &text_r26_27, &text_r28_30, &text_r31_33,
&text_lbl_decoded, &text_lbl_p1, &text_p1, &text_lbl_p2, &text_p2,
&text_lbl_p3, &text_p3, &text_lbl_calc, &text_lbl_mult, &text_mult,
&text_lbl_vco, &text_vco, &text_status, &button_refresh, &button_done});
text_title.set_style(Theme::getInstance()->fg_yellow);
text_lbl_raw.set_style(Theme::getInstance()->fg_yellow);
text_lbl_decoded.set_style(Theme::getInstance()->fg_yellow);
text_lbl_calc.set_style(Theme::getInstance()->fg_yellow);
button_refresh.on_select = [this](Button&) { refresh(); };
button_done.on_select = [&nav](Button&) { nav.pop(); };
refresh();
}
void Si5351PLLADebugView::focus() {
button_refresh.focus();
}
void Si5351PLLADebugView::refresh() {
// Read PLL A parameters
uint8_t r26 = portapack::clock_manager.si5351_read_register(26);
uint8_t r27 = portapack::clock_manager.si5351_read_register(27);
uint8_t r28 = portapack::clock_manager.si5351_read_register(28);
uint8_t r29 = portapack::clock_manager.si5351_read_register(29);
uint8_t r30 = portapack::clock_manager.si5351_read_register(30);
uint8_t r31 = portapack::clock_manager.si5351_read_register(31);
uint8_t r32 = portapack::clock_manager.si5351_read_register(32);
uint8_t r33 = portapack::clock_manager.si5351_read_register(33);
// Display Raw registers
text_r26_27.set("R26-27 (P3 LO): " + to_string_hex(r26, 2) + " " + to_string_hex(r27, 2));
text_r28_30.set("R28-30 (P1): " + to_string_hex(r28, 2) + " " + to_string_hex(r29, 2) + " " + to_string_hex(r30, 2));
text_r31_33.set("R31-33 (P3H|P2):" + to_string_hex(r31, 2) + " " + to_string_hex(r32, 2) + " " + to_string_hex(r33, 2));
// Decode parameters
uint32_t pll_p1 = ((uint32_t)(r28 & 0x03) << 16) | ((uint32_t)r29 << 8) | r30;
uint32_t pll_p2 = ((uint32_t)(r31 & 0x0F) << 16) | ((uint32_t)r32 << 8) | r33;
uint32_t pll_p3 = ((uint32_t)(r31 >> 4) << 16) | ((uint32_t)r26 << 8) | r27;
text_p1.set(to_string_dec_uint(pll_p1) + " (0x" + to_string_hex(pll_p1, 5) + ")");
text_p2.set(to_string_dec_uint(pll_p2) + " (0x" + to_string_hex(pll_p2, 5) + ")");
text_p3.set(to_string_dec_uint(pll_p3) + " (0x" + to_string_hex(pll_p3, 5) + ")");
// Calculate Multiplier: M = (128 * P3 + P1 * 128 + 512 + P2) / (128 * P3)
// Simplified as: Multiplier = ((P1 + 512) / 128) + (P2 / P3)
uint32_t a = (pll_p1 + 512) / 128;
if (pll_p3 > 0) {
text_mult.set(to_string_dec_uint(a) + " + " + to_string_dec_uint(pll_p2) + "/" + to_string_dec_uint(pll_p3));
} else {
text_mult.set("ERR: P3=0");
}
// Calculate VCO Frequency (f_vco = f_xtal * Multiplier)
// The HackRF crystal (f_xtal) is 25 MHz.
if (pll_p3 > 0) {
uint64_t vco_num = (uint64_t)pll_p2 + (uint64_t)pll_p3 * (pll_p1 + 512);
uint64_t vco_den = 128ULL * pll_p3;
uint32_t vco_khz = (uint32_t)((25000ULL * vco_num) / vco_den);
text_vco.set(to_string_dec_uint(vco_khz / 1000) + "." + to_string_dec_uint(vco_khz % 1000, 3) + " MHz");
// Si5351 VCO range is 600-900 MHz
if (vco_khz >= 600000 && vco_khz <= 900000) {
text_vco.set_style(Theme::getInstance()->fg_green);
text_status.set("VCO within valid range.");
} else {
text_vco.set_style(Theme::getInstance()->fg_red);
text_status.set("VCO OUT OF RANGE (600-900)!");
}
}
}
#endif
#ifdef PRALINE
Si5351PLLBDebugView::Si5351PLLBDebugView(NavigationView& nav)
: nav_(nav) {
add_children({&text_title,
&text_lbl_raw, &text_r34_35, &text_r36_38, &text_r39_41,
&text_lbl_decoded, &text_lbl_p1, &text_p1, &text_lbl_p2, &text_p2, &text_lbl_p3, &text_p3,
&text_lbl_calc, &text_lbl_vco, &text_vco,
&text_status, &button_refresh, &button_done});
text_title.set_style(Theme::getInstance()->fg_yellow);
text_lbl_raw.set_style(Theme::getInstance()->fg_yellow);
text_lbl_decoded.set_style(Theme::getInstance()->fg_yellow);
text_lbl_calc.set_style(Theme::getInstance()->fg_yellow);
button_refresh.on_select = [this](Button&) { refresh(); };
button_done.on_select = [&nav](Button&) { nav.pop(); };
refresh();
}
void Si5351PLLBDebugView::focus() {
button_refresh.focus();
}
void Si5351PLLBDebugView::refresh() {
uint8_t r34 = portapack::clock_manager.si5351_read_register(34); // P3[15:8]
uint8_t r35 = portapack::clock_manager.si5351_read_register(35); // P3[7:0]
uint8_t r36 = portapack::clock_manager.si5351_read_register(36); // P1[17:16]
uint8_t r37 = portapack::clock_manager.si5351_read_register(37); // P1[15:8]
uint8_t r38 = portapack::clock_manager.si5351_read_register(38); // P1[7:0]
uint8_t r39 = portapack::clock_manager.si5351_read_register(39); // P3[19:16] | P2[19:16]
uint8_t r40 = portapack::clock_manager.si5351_read_register(40); // P2[15:8]
uint8_t r41 = portapack::clock_manager.si5351_read_register(41); // P2[7:0]
text_r34_35.set("R34-35: " + to_string_hex(r34, 2) + " " + to_string_hex(r35, 2));
text_r36_38.set("R36-38: " + to_string_hex(r36, 2) + " " + to_string_hex(r37, 2) + " " + to_string_hex(r38, 2));
text_r39_41.set("R39-41: " + to_string_hex(r39, 2) + " " + to_string_hex(r40, 2) + " " + to_string_hex(r41, 2));
uint32_t p1 = ((uint32_t)(r36 & 0x03) << 16) | ((uint32_t)r37 << 8) | r38;
uint32_t p2 = ((uint32_t)(r39 & 0x0F) << 16) | ((uint32_t)r40 << 8) | r41;
uint32_t p3 = ((uint32_t)(r39 >> 4) << 16) | ((uint32_t)r34 << 8) | r35;
text_p1.set(to_string_dec_uint(p1) + " (0x" + to_string_hex(p1, 5) + ")");
text_p2.set(to_string_dec_uint(p2) + " (0x" + to_string_hex(p2, 5) + ")");
text_p3.set(to_string_dec_uint(p3) + " (0x" + to_string_hex(p3, 5) + ")");
if (p3 > 0) {
uint64_t vco_num = (uint64_t)p2 + (uint64_t)p3 * (p1 + 512);
uint64_t vco_den = 128ULL * p3;
uint32_t vco_khz = (uint32_t)((25000ULL * vco_num) / vco_den);
text_vco.set(to_string_dec_uint(vco_khz / 1000) + "." + to_string_dec_uint(vco_khz % 1000, 3) + " MHz");
bool ok = (vco_khz >= 600000 && vco_khz <= 900000);
text_vco.set_style(ok ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
text_status.set(ok ? "PLL B VCO OK" : "VCO OUT OF RANGE!");
}
}
#endif
#ifdef PRALINE
/* Si5351MultiSynthDebugView *************************************************/
Si5351MultiSynthDebugView::Si5351MultiSynthDebugView(NavigationView& nav)
: nav_(nav) {
add_children({
&text_title,
&text_lbl_clk_ctrl,
&text_clk_ctrl,
&text_lbl_ms_int,
&text_ms_int,
&text_lbl_raw,
&text_lbl_r42_43,
&text_r42_43,
&text_lbl_r44_46,
&text_r44_46,
&text_lbl_r47_49,
&text_r47_49,
&text_lbl_decoded,
&text_lbl_p1,
&text_p1,
&text_lbl_p2,
&text_p2,
&text_lbl_p3,
&text_p3,
&text_lbl_rdiv,
&text_rdiv,
&text_lbl_calc,
&text_lbl_div,
&text_div,
&text_lbl_freq,
&text_freq,
&text_status,
&button_refresh,
&button_reset,
&button_frac,
&button_done,
});
text_title.set_style(Theme::getInstance()->fg_yellow);
text_lbl_raw.set_style(Theme::getInstance()->fg_yellow);
text_lbl_decoded.set_style(Theme::getInstance()->fg_yellow);
text_lbl_calc.set_style(Theme::getInstance()->fg_yellow);
button_refresh.on_select = [this](Button&) {
refresh();
};
button_reset.on_select = [this](Button&) {
force_pll_reset();
};
button_frac.on_select = [this](Button&) {
force_fractional_mode();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
refresh();
}
void Si5351MultiSynthDebugView::focus() {
button_refresh.focus();
}
void Si5351MultiSynthDebugView::force_pll_reset() {
// Reset PLL A (bit 5)
portapack::clock_manager.si5351_write_register(177, 0x20);
// Wait for PLL to settle
chThdSleepMilliseconds(10);
refresh();
}
void Si5351MultiSynthDebugView::force_fractional_mode() {
// Force CLK0 to fractional mode
// Read current control register
uint8_t clk0_ctrl = portapack::clock_manager.si5351_read_register(16);
// Clear MS_INT bit (bit 6) to enable fractional mode
clk0_ctrl &= ~0x40;
// Write back
portapack::clock_manager.si5351_write_register(16, clk0_ctrl);
// Reset PLL to apply
portapack::clock_manager.si5351_write_register(177, 0x20);
chThdSleepMilliseconds(10);
refresh();
}
void Si5351MultiSynthDebugView::refresh() {
// === Clock Control Register 16 (CLK0) ===
uint8_t clk0_ctrl = portapack::clock_manager.si5351_read_register(16);
text_clk_ctrl.set("0x" + to_string_hex(clk0_ctrl, 2) +
" (" + to_string_bin(clk0_ctrl, 8) + ")");
// Decode MS_INT bit (bit 6)
bool ms_int = (clk0_ctrl >> 6) & 1;
if (ms_int) {
text_ms_int.set("1:INT MODE!");
text_ms_int.set_style(Theme::getInstance()->fg_red);
} else {
text_ms_int.set("0:Fract Mode");
text_ms_int.set_style(Theme::getInstance()->fg_green);
}
// === Read Raw MS0 Registers (42-49) ===
uint8_t r42 = portapack::clock_manager.si5351_read_register(42);
uint8_t r43 = portapack::clock_manager.si5351_read_register(43);
uint8_t r44 = portapack::clock_manager.si5351_read_register(44);
uint8_t r45 = portapack::clock_manager.si5351_read_register(45);
uint8_t r46 = portapack::clock_manager.si5351_read_register(46);
uint8_t r47 = portapack::clock_manager.si5351_read_register(47);
uint8_t r48 = portapack::clock_manager.si5351_read_register(48);
uint8_t r49 = portapack::clock_manager.si5351_read_register(49);
// Display raw registers
text_r42_43.set(to_string_hex(r42, 2) + " " + to_string_hex(r43, 2) +
" (P3[15:0])");
text_r44_46.set(to_string_hex(r44, 2) + " " + to_string_hex(r45, 2) +
" " + to_string_hex(r46, 2) + " (R|P1)");
text_r47_49.set(to_string_hex(r47, 2) + " " + to_string_hex(r48, 2) +
" " + to_string_hex(r49, 2) + " (P3|P2)");
// === Decode P1, P2, P3 ===
// Si5351 MS Register Layout:
// Reg 42: P3[15:8]
// Reg 43: P3[7:0]
// Reg 44: bits 6:4 = R_DIV[2:0], bits 1:0 = P1[17:16]
// Reg 45: P1[15:8]
// Reg 46: P1[7:0]
// Reg 47: bits 7:4 = P3[19:16], bits 3:0 = P2[19:16]
// Reg 48: P2[15:8]
// Reg 49: P2[7:0]
// Decode R_DIV
uint8_t r_div_encoded = (r44 >> 4) & 0x07;
uint32_t r_div = 1 << r_div_encoded;
text_rdiv.set("/" + to_string_dec_uint(r_div) + " (enc=" + to_string_dec_uint(r_div_encoded) + ")");
// Decode P1 (18-bit)
uint32_t p1 = ((uint32_t)(r44 & 0x03) << 16) | ((uint32_t)r45 << 8) | r46;
text_p1.set(to_string_dec_uint(p1) + " (0x" + to_string_hex(p1, 5) + ")");
// Decode P2 (20-bit)
uint32_t p2 = ((uint32_t)(r47 & 0x0F) << 16) | ((uint32_t)r48 << 8) | r49;
text_p2.set(to_string_dec_uint(p2) + " (0x" + to_string_hex(p2, 5) + ")");
// Decode P3 (20-bit)
uint32_t p3 = ((uint32_t)(r47 >> 4) << 16) | ((uint32_t)r42 << 8) | r43;
text_p3.set(to_string_dec_uint(p3) + " (0x" + to_string_hex(p3, 5) + ")");
// Color code P2/P3 based on whether fractional is being used
if (p2 == 0 && p3 == 1) {
text_p2.set_style(Theme::getInstance()->fg_orange);
text_p3.set_style(Theme::getInstance()->fg_orange);
} else if (p3 > 1) {
text_p2.set_style(Theme::getInstance()->fg_green);
text_p3.set_style(Theme::getInstance()->fg_green);
} else {
// Default/neutral style when P2/P3 don't match known patterns
text_p2.set_style(Theme::getInstance()->fg_light);
text_p3.set_style(Theme::getInstance()->fg_light);
}
// === Calculate Output Frequency ===
// === Calculate Multisynth Divider ===
// Correct Si5351 formula:
// MS_DIV = (P2+P3 × (P1 + 512)) / (128 × P3)
// MS_DIV = P2/(128*P3) + P1+512/(128*P3)
// x = (P1 + 512) / 128
// y = P2/128
// z = P3
// f_out = f_vco / MS_DIV / R_DIV
// a = floor((P1 + 512) / 128)
// k = (P1 + 512) - 128*a
// b = b = (P2 + c*k) / 128
// c = P3
// In the case of integer division: b=0, c=1, so MS_DIV = a
uint64_t ms_div_numerator = (uint64_t)p2 + (uint64_t)p3 * (p1 + 512);
uint64_t ms_div_denominator = 128ULL * p3;
uint32_t x = (p1 + 512) / 128;
uint32_t y = p2 / 128;
uint32_t z = p3;
// For display, show the full fractional value
// MS_DIV = (a+b)/c
if (p3 > 1 && p2 > 0) {
std::string div_str = "(" + to_string_dec_uint(x) + "+" + to_string_dec_uint(y) + ")/" + to_string_dec_uint(z);
text_div.set(div_str);
} else {
std::string div_str = to_string_dec_uint(x);
text_div.set(div_str);
}
// === Calculate Output Frequency ===
// f_vco = 800,000,000 (PLL A)
// f_out in kHz = 800,000,000 / MS_DIV / r_div / 1000
// = 800,000 × ms_div_denominator / ms_div_numerator / r_div
uint32_t freq_khz = 0;
if (ms_div_numerator > 0) {
freq_khz = (uint32_t)((800000ULL * ms_div_denominator) / ms_div_numerator / r_div);
}
uint32_t freq_mhz = freq_khz / 1000;
uint32_t freq_frac = freq_khz % 1000;
text_freq.set(to_string_dec_uint(freq_mhz) + "." +
to_string_dec_uint(freq_frac / 100) +
to_string_dec_uint((freq_frac / 10) % 10) +
to_string_dec_uint(freq_frac % 10) + " MHz");
// Color code based on expected ~24.576 MHz for WFM stereo
if (freq_khz >= 24500 && freq_khz <= 24700) {
text_freq.set_style(Theme::getInstance()->fg_green);
} else if (freq_khz >= 24000 && freq_khz <= 26000) {
text_freq.set_style(Theme::getInstance()->fg_orange);
} else {
text_freq.set_style(Theme::getInstance()->fg_red);
}
// === Status Summary ===
// Expected values for 24.576 MHz (3.072 MHz * 8 decimation):
// VCO = 800 MHz
// Target freq = 49.152 MHz (before R_DIV=/2)
// MS_DIV = 800M / 49.152M = 16.276...
// a = 16, b = 53, c = 192
// P1 = 128*16 + floor(128*53/192) - 512 = 2048 + 35 - 512 = 1571
// P2 = 128*53 - 192*35 = 6784 - 6720 = 64
// P3 = 192
if (ms_int) {
text_status.set("ERROR: Integer mode! P2/P3 ignored!");
text_status.set_style(Theme::getInstance()->fg_red);
} else if (p2 == 0 && p3 == 1) {
text_status.set("WARN P2:0,P3:1 INT Equiv");
text_status.set_style(Theme::getInstance()->fg_orange);
} else if (p3 == 192 && p2 == 64) {
text_status.set("GOOD Exp 3.072M values!");
text_status.set_style(Theme::getInstance()->fg_green);
} else if (freq_khz >= 24500 && freq_khz <= 24700) {
text_status.set("OK: Freq in range");
text_status.set_style(Theme::getInstance()->fg_green);
} else {
text_status.set("CHECK: P2=" + to_string_dec_uint(p2) +
" P3=" + to_string_dec_uint(p3));
text_status.set_style(Theme::getInstance()->fg_orange);
}
}
#endif
#ifdef PRALINE
PralineClockDebugView::PralineClockDebugView(NavigationView& nav)
: View(),
rows{
{&t0_id, &t0_ma, &t0_mode, &t0_src, &t0_ph, &t0_st},
{&t1_id, &t1_ma, &t1_mode, &t1_src, &t1_ph, &t1_st},
{&t2_id, &t2_ma, &t2_mode, &t2_src, &t2_ph, &t2_st},
{&t3_id, &t3_ma, &t3_mode, &t3_src, &t3_ph, &t3_st},
{&t4_id, &t4_ma, &t4_mode, &t4_src, &t4_ph, &t4_st},
{&t5_id, &t5_ma, &t5_mode, &t5_src, &t5_ph, &t5_st},
{&t6_id, &t6_ma, &t6_mode, &t6_src, &t6_ph, &t6_st},
{&t7_id, &t7_ma, &t7_mode, &t7_src, &t7_ph, &t7_st}} {
add_children({&text_title, &text_lbl_pll, &text_pll_status,
&text_lbl_afe, &text_afe_rate, &text_lbl_n, &text_n_val,
&text_header,
&t0_id, &t0_ma, &t0_mode, &t0_src, &t0_ph, &t0_st,
&t1_id, &t1_ma, &t1_mode, &t1_src, &t1_ph, &t1_st,
&t2_id, &t2_ma, &t2_mode, &t2_src, &t2_ph, &t2_st,
&t3_id, &t3_ma, &t3_mode, &t3_src, &t3_ph, &t3_st,
&t4_id, &t4_ma, &t4_mode, &t4_src, &t4_ph, &t4_st,
&t5_id, &t5_ma, &t5_mode, &t5_src, &t5_ph, &t5_st,
&t6_id, &t6_ma, &t6_mode, &t6_src, &t6_ph, &t6_st,
&t7_id, &t7_ma, &t7_mode, &t7_src, &t7_ph, &t7_st,
&button_refresh, &button_done});
button_refresh.on_select = [this](Button&) { this->refresh(); };
button_done.on_select = [&nav](Button&) { nav.pop(); };
refresh();
}
void PralineClockDebugView::focus() {
button_refresh.focus();
}
void PralineClockDebugView::refresh() {
// 1. System Status
uint8_t status = portapack::clock_manager.si5351_read_status();
bool pll_a = !(status & 0x20);
bool pll_b = !(status & 0x40);
text_pll_status.set(std::string(pll_a ? "A:OK " : "A:ERR ") + (pll_b ? "B:OK" : "B:ERR"));
text_pll_status.set_style((pll_a && pll_b) ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
// 2. AFE & Decimation Info
uint32_t base_rate = portapack::clock_manager.get_sampling_frequency();
uint8_t n = portapack::clock_manager.get_resampling_n();
text_afe_rate.set(to_string_dec_uint(base_rate << n) + " Hz");
text_n_val.set(to_string_dec_uint(n));
// 3. Clock Table Decoding
uint8_t output_en = portapack::clock_manager.si5351_read_register(3);
const char* ma_lookup[] = {"2m", "4m", "6m", "8m"};
for (size_t i = 0; i < 8; i++) {
uint8_t ctrl = portapack::clock_manager.si5351_read_register(16 + i);
// mA (Bits 1:0)
rows[i].ma->set(ma_lookup[ctrl & 0x03]);
// Mode (Bit 6: 1=Integer, 0=Fractional)
rows[i].mode->set((ctrl & 0x40) ? "INT" : "FRAC");
rows[i].mode->set_style((ctrl & 0x40) ? Theme::getInstance()->fg_blue : Theme::getInstance()->fg_yellow);
// PLL Source (Bit 5: 0=PLLA, 1=PLLB)
rows[i].src->set((ctrl & 0x20) ? "PLLB" : "PLLA");
rows[i].src->set_style((ctrl & 0x20) ? Theme::getInstance()->fg_blue : Theme::getInstance()->fg_green);
// Phase (Bit 4: 1=Inverted, 0=Normal)
// Use 0x10 (Bit 4)
rows[i].phase->set((ctrl & 0x10) ? "INVRT" : "NORM ");
rows[i].phase->set_style((ctrl & 0x10) ? Theme::getInstance()->fg_orange : Theme::getInstance()->fg_light);
// Status (Powered On and Output Enabled)
bool is_on = !(ctrl & 0x80) && !(output_en & (1 << i));
rows[i].stat->set(is_on ? "ON" : "OFF");
rows[i].stat->set_style(is_on ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
}
}
#endif
#ifdef PRALINE
/* GPIODebugView *************************************************/
GPIODebugView::GPIODebugView(NavigationView& nav) {
add_children({
&text_lbl_gpio4,
&text_lbl_mixr1,
&text_mixr1,
&text_lbl_pin4,
&text_pin4,
&text_lbl_set4,
&text_set4,
&text_lbl_lpf_bit,
&text_lpf_dir,
&text_lpf_pin,
&text_lpf_set,
&button_lpf_toggle,
&button_lpf_on,
&button_lpf_off,
&text_lbl_amp_bit,
&text_amp_dir,
&text_amp_pin,
&text_amp_set,
&button_amp_toggle,
&button_amp_on,
&button_amp_off,
&text_lbl_gpio3,
&text_lbl_mix_bit,
&text_mix_dir,
&text_mix_pin,
&text_mix_set,
&button_refresh,
&button_done,
});
text_lbl_gpio4.set_style(Theme::getInstance()->fg_yellow);
text_lbl_gpio3.set_style(Theme::getInstance()->fg_yellow);
// LPF control buttons
button_lpf_toggle.on_select = [this](Button&) {
// Read current state
uint32_t current = LPC_GPIO->PIN[4];
bool current_state = (current >> 8) & 1;
// Toggle
if (current_state) {
LPC_GPIO->CLR[4] = (1 << 8); // Clear bit 8
} else {
LPC_GPIO->SET[4] = (1 << 8); // Set bit 8
}
refresh();
};
button_lpf_on.on_select = [this](Button&) {
LPC_GPIO->SET[4] = (1 << 8); // Force ON
refresh();
};
button_lpf_off.on_select = [this](Button&) {
LPC_GPIO->CLR[4] = (1 << 8); // Force OFF
refresh();
};
// RF Amp control buttons
button_amp_toggle.on_select = [this](Button&) {
uint32_t current = LPC_GPIO->PIN[4];
bool current_state = (current >> 9) & 1;
if (current_state) {
LPC_GPIO->CLR[4] = (1 << 9);
} else {
LPC_GPIO->SET[4] = (1 << 9);
}
refresh();
};
button_amp_on.on_select = [this](Button&) {
LPC_GPIO->SET[4] = (1 << 9); // Force ON
refresh();
};
button_amp_off.on_select = [this](Button&) {
LPC_GPIO->CLR[4] = (1 << 9); // Force OFF
refresh();
};
button_refresh.on_select = [this](Button&) {
refresh();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
refresh();
}
void GPIODebugView::focus() {
button_refresh.focus();
}
void GPIODebugView::refresh() {
// Read GPIO4 registers
uint32_t gpio4_dir = LPC_GPIO->DIR[4]; // Direction: 1=output, 0=input
uint32_t gpio4_pin = LPC_GPIO->PIN[4]; // Actual pin state
uint32_t gpio4_set = LPC_GPIO->SET[4]; // What we're trying to output
// Display full registers
// text_dir4.set("0x" + to_string_hex(gpio4_dir, 8));
text_pin4.set("0x" + to_string_hex(gpio4_pin, 8));
text_set4.set("0x" + to_string_hex(gpio4_set, 8));
// Extract bit 8 (LPF)
bool lpf_dir = (gpio4_dir >> 8) & 1;
bool lpf_pin = (gpio4_pin >> 8) & 1;
bool lpf_set = (gpio4_set >> 8) & 1;
text_lpf_dir.set("DIR: " + std::string(lpf_dir ? "OUT" : "IN"));
text_lpf_pin.set("PIN: " + std::string(lpf_pin ? "1" : "0"));
text_lpf_set.set("SET: " + std::string(lpf_set ? "1" : "0"));
// Color code
text_lpf_dir.set_style(lpf_dir ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
text_lpf_pin.set_style(lpf_pin ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
// Extract bit 9 (RF Amp)
bool amp_dir = (gpio4_dir >> 9) & 1;
bool amp_pin = (gpio4_pin >> 9) & 1;
bool amp_set = (gpio4_set >> 9) & 1;
text_amp_dir.set("DIR: " + std::string(amp_dir ? "OUT" : "IN"));
text_amp_pin.set("PIN: " + std::string(amp_pin ? "1" : "0"));
text_amp_set.set("SET: " + std::string(amp_set ? "1" : "0"));
text_amp_dir.set_style(amp_dir ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
text_amp_pin.set_style(amp_pin ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
// Read GPIO3 (Mixer) - bit 2
uint32_t gpio3_dir = LPC_GPIO->DIR[3];
uint32_t gpio3_pin = LPC_GPIO->PIN[3];
uint32_t gpio3_set = LPC_GPIO->SET[3];
bool mix_dir = (gpio3_dir >> 2) & 1;
bool mix_pin = (gpio3_pin >> 2) & 1;
bool mix_set = (gpio3_set >> 2) & 1;
text_mix_dir.set("DIR: " + std::string(mix_dir ? "OUT" : "IN"));
text_mix_pin.set("PIN: " + std::string(mix_pin ? "1" : "0"));
text_mix_set.set("SET: " + std::string(mix_set ? "1" : "0"));
text_mix_dir.set_style(mix_dir ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
text_mix_pin.set_style(mix_pin ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
// Read GPIO5 (Mixer R1) - bit 2
uint32_t gpio3_state = LPC_GPIO->PIN[3]; // GPIO3 for mixer
bool mix_n_actual = (gpio3_state >> 2) & 1; // GPIO3[2]
uint32_t gpio5_state = LPC_GPIO->PIN[5];
bool mix_r10_pin = (gpio5_state >> 6) & 1; // GPIO5[6] = P2_6
// Mixer is active LOW, so invert for display
bool mixer_enabled = !mix_n_actual;
// Append to existing mixer display:
text_mixr1.set(
std::string(mixer_enabled ? "ENABLED" : "BYPASSED") +
" P6_3=" + to_string_dec_uint(mix_n_actual ? 1 : 0) +
" P2_6=" + to_string_dec_uint(mix_r10_pin ? 1 : 0));
text_mixr1.set_style(mixer_enabled ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
}
#endif
#ifdef PRALINE
/* RFFC5072StatusView *************************************************/
RFFC5072StatusView::RFFC5072StatusView(NavigationView& nav)
: nav_(nav) {
add_children({
&text_gpio4,
&text_ctrl,
&text_lbl_enabled,
&text_enabled,
&text_lbl_freq,
&text_freq,
&text_lbl_path,
&text_path,
&text_lbl_mixer,
&text_mixer,
&text_lbl_r0,
&text_r0,
&text_lbl_r1,
&text_r1,
&text_lbl_r2,
&text_r2,
&text_lbl_n,
&text_n,
&text_lbl_lodiv,
&text_lodiv,
&text_lbl_calc,
&text_calc,
&text_status,
&text_status2,
&text_status3,
&text_regs_status,
&button_refresh,
&button_force,
&button_done,
});
button_refresh.on_select = [this](Button&) {
refresh_status();
};
button_force.on_select = [this](Button&) {
// Force ENX to OUTPUT and drive LOW
// LPC_GPIO->DIR[2] |= (1 << 13); // Set as OUTPUT
// LPC_GPIO->CLR[2] = (1 << 13); // Drive LOW (enabled)
// refresh_status();
// Disable RFFC5072
// uint32_t r0 = radio::debug::first_if::register_read(0);
// radio::debug::first_if::register_write(0, r0 & ~0x0010); // Clear ENBL
// Wait 1ms
// chThdSleepMilliseconds(1);
// Re-enable - this triggers new calibration
// radio::debug::first_if::register_write(0, r0 | 0x0010); // Set ENBL
// Wait for calibration
// chThdSleepMilliseconds(10);
// refresh_status();
// Force lodiv=4 (log2=2) instead of lodiv=2 (log2=1)
// This gives VCO = LO × 4 = 2595 × 4 = 10380 MHz - TOO HIGH!
// Actually, we need lodiv=1 which gives VCO = 2595 MHz - TOO LOW (below 2700)
// Let's try a different approach: manually write registers for VCO ~ 3500 MHz
// LO = 3500/2 = 1750 MHz (not useful for FM, but tests if VCO can lock)
// VCO = 3500 MHz, lodiv=2, presc=2, f_ref=40
// N = (VCO × presc) / f_ref = (3500 × 2) / 40 = 175
// Write P2_FREQ1: N=175, lodiv=1 (log2), presc=1 (log2)
// uint16_t p2_freq1 = (175 << 7) | (1 << 4) | (1 << 2);
// radio::debug::first_if::register_write(15, p2_freq1);
// Clear fractional part
// radio::debug::first_if::register_write(16, 0);
// radio::debug::first_if::register_write(17, 0);
// Trigger recalibration by toggling ENBL
// uint32_t r0 = radio::debug::first_if::register_read(0);
// radio::debug::first_if::register_write(0, r0 & ~0x0010);
// chThdSleepMilliseconds(1);
// radio::debug::first_if::register_write(0, r0 | 0x0010);
// chThdSleepMilliseconds(20);
// refresh_status();
// Test SPI SDATA direction switching
// PRALINE: SDATA = P9_2 = GPIO4[14]
// Check current direction
uint32_t dir_before = LPC_GPIO->DIR[4];
bool sdata_output_before = (dir_before >> 14) & 1;
// Try a register read
uint32_t dummy = radio::debug::first_if::register_read(0);
(void)dummy;
// Check direction after read
uint32_t dir_after = LPC_GPIO->DIR[4];
bool sdata_output_after = (dir_after >> 14) & 1;
// Read the actual SDATA pin state
uint32_t pin_state = LPC_GPIO->PIN[4];
bool sdata_pin = (pin_state >> 14) & 1;
text_status.set("SDATA: dir_b=" + to_string_dec_uint(sdata_output_before) +
" dir_a=" + to_string_dec_uint(sdata_output_after) +
" pin=" + to_string_dec_uint(sdata_pin) + " ");
// If both are 1 (OUTPUT), the read direction switch isn't happening
// if (sdata_output_before && sdata_output_after) {
// text_status2.set("ERROR: SDATA stuck as OUTPUT! ");
// text_status2.set_style(Theme::getInstance()->fg_red);
//} else {
// text_status2.set("SDATA direction OK ");
// text_status2.set_style(Theme::getInstance()->fg_green);
//}
// Test: Write a known pattern to register 0, then read back
// Register 0 (DEV_CTRL) default = 0xBEFA
// Step 1: Read current value
uint32_t before = radio::debug::first_if::register_read(0);
// Step 2: Write a different value (change ENBL bit to toggle)
uint32_t test_val = before ^ 0x0010; // Toggle ENBL bit
radio::debug::first_if::register_write(0, test_val);
// Step 3: Read back
uint32_t after = radio::debug::first_if::register_read(0);
// Step 4: Restore original
radio::debug::first_if::register_write(0, before);
// Display results
text_status.set("WR TEST: " + to_string_hex(before, 4) +
"->" + to_string_hex(test_val, 4) +
" rb:" + to_string_hex(after, 4));
// If after == before (not test_val), reads are broken
// If after == test_val, reads work
if (after == test_val) {
text_status2.set("READ-AFTER-WRITE: PASS! ");
text_status2.set_style(Theme::getInstance()->fg_green);
} else if (after == before) {
text_status2.set("READ-AFTER-WRITE: FAIL (no change) ");
text_status2.set_style(Theme::getInstance()->fg_red);
} else {
text_status2.set("READ-AFTER-WRITE: CORRUPT " + to_string_hex(after, 4) + " ");
text_status2.set_style(Theme::getInstance()->fg_red);
}
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
// Initial update
refresh_status();
}
void RFFC5072StatusView::focus() {
button_refresh.focus();
}
void RFFC5072StatusView::refresh_status() {
// === DIAGNOSTIC: Capture initial GPIO state ===
uint32_t gpio2_initial = LPC_GPIO->PIN[2];
uint32_t dir2_initial = LPC_GPIO->DIR[2];
bool enx_initial = (gpio2_initial >> 13) & 1;
// === READ RAW GPIO STATES FOR DISPLAY ===
uint32_t gpio2_dir = LPC_GPIO->DIR[2];
uint32_t gpio2_pin = LPC_GPIO->PIN[2];
bool enx_is_output = (gpio2_dir >> 13) & 1;
bool resetx_is_output = (gpio2_dir >> 14) & 1;
// === LOCK DETECT ===
uint32_t gpio6_pin = LPC_GPIO->PIN[6];
bool rffc_locked = (gpio6_pin >> 25) & 1;
// === FPGA REGISTERS (non-SPI) ===
uint8_t fpga_reg1 = radio::debug::fpga::register_read(1);
uint8_t fpga_reg2 = radio::debug::fpga::register_read(2);
uint8_t fpga_reg3 = radio::debug::fpga::register_read(3);
text_regs_status.set("FPGA R1:" + to_string_hex(fpga_reg1, 2) +
" R2:" + to_string_hex(fpga_reg2, 2) +
" R3:" + to_string_hex(fpga_reg3, 2));
// === CONTROL PINS ===
bool enx = (gpio2_pin >> 13) & 1;
bool resetx = (gpio2_pin >> 14) & 1;
text_ctrl.set("ENX: " + std::string(enx ? "DIS" : "EN") +
" O:" + std::string(enx_is_output ? "Y" : "N") +
" | RSTX: " + std::string(resetx ? "H" : "L") +
" O:" + std::string(resetx_is_output ? "Y" : "N"));
text_ctrl.set_style((enx == 0 && resetx == 1) ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_red);
// === DIAGNOSTIC: Check BEFORE first RFFC5072 SPI read ===
uint32_t gpio2_before_spi = LPC_GPIO->PIN[2];
bool enx_before_spi = (gpio2_before_spi >> 13) & 1;
// === RFFC5072 REGISTERS (SPI reads - this is where corruption happens) ===
uint32_t r0 = radio::debug::first_if::register_read(0);
// === DIAGNOSTIC: Check AFTER first read ===
uint32_t gpio2_after_r0 = LPC_GPIO->PIN[2];
bool enx_after_r0 = (gpio2_after_r0 >> 13) & 1;
uint32_t r15 = radio::debug::first_if::register_read(15);
// === DIAGNOSTIC: Check AFTER second read ===
uint32_t gpio2_after_r15 = LPC_GPIO->PIN[2];
bool enx_after_r15 = (gpio2_after_r15 >> 13) & 1;
uint32_t r16 = radio::debug::first_if::register_read(16);
// === DIAGNOSTIC: Check AFTER third read ===
uint32_t gpio2_final = LPC_GPIO->PIN[2];
uint32_t dir2_final = LPC_GPIO->DIR[2];
bool enx_final = (gpio2_final >> 13) & 1;
// === Display register values ===
text_r0.set(to_string_hex(r0, 4));
text_r1.set(to_string_hex(r15, 4) + " (R15)");
text_r2.set(to_string_hex(r16, 4) + " (R16)");
bool enabled = (r0 & 0x0010) != 0;
text_enabled.set(enabled ? "ENABLED" : "DISABLED");
text_enabled.set_style(enabled ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_red);
// === Decode frequency info (keeping existing code) ===
uint16_t n_int = (r15 >> 7) & 0x1FF;
uint8_t lodiv_sel = (r15 >> 4) & 0x07;
uint8_t presc_sel = (r15 >> 2) & 0x03;
text_n.set(to_string_dec_uint(n_int));
uint16_t lodiv_val = 1u << lodiv_sel;
uint16_t presc_val = 1u << presc_sel;
text_lodiv.set("/" + to_string_dec_uint(lodiv_val) +
" (P:/" + to_string_dec_uint(presc_val) + ")");
const uint32_t f_ref_mhz = 40;
uint32_t f_vco_mhz = (f_ref_mhz * n_int) / presc_val;
uint32_t f_lo_mhz = f_vco_mhz / lodiv_val;
bool vco_ok = (f_vco_mhz >= 2700) && (f_vco_mhz <= 5400);
bool lo_ok = (f_lo_mhz >= 85) && (f_lo_mhz <= 4200);
bool in_bypass_range = (f_lo_mhz >= 2320) && (f_lo_mhz <= 2740);
if (in_bypass_range) {
text_calc.set(to_string_dec_uint(f_lo_mhz) + " MHz (MID)");
text_calc.set_style(Theme::getInstance()->fg_orange);
} else {
text_calc.set(to_string_dec_uint(f_lo_mhz) + " MHz");
text_calc.set_style(lo_ok ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_red);
}
text_freq.set(to_string_dec_uint(f_vco_mhz) + " MHz VCO");
text_freq.set_style(vco_ok ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_red);
bool path2_active = (r0 & 0x0020) != 0;
text_path.set(path2_active ? "PATH2" : "PATH1");
text_mixer.set(path2_active ? "ACTIVE" : "INACTIVE");
text_mixer.set_style(path2_active ? Theme::getInstance()->fg_green
: Theme::getInstance()->fg_orange);
// === DIAGNOSTIC STATUS (replaces normal status) ===
// Read register 31 with readsel=0 (device ID)
radio::debug::first_if::register_write(0, (r0 & 0xFFF0) | 0x0000); // readsel=0
uint32_t device_id = radio::debug::first_if::register_read(31);
// Read calibration status (readback register 1)
// First, set DEV_CTRL.readsel = 1, then read READBACK register
uint32_t dev_ctrl_orig = radio::debug::first_if::register_read(0); // Save original
// Write DEV_CTRL with readsel=1 (bits 3:0)
radio::debug::first_if::register_write(0, (dev_ctrl_orig & 0xFFF0) | 0x0001);
// Now read the READBACK register (register address for readback)
uint32_t cal_status = radio::debug::first_if::register_read(31); // READBACK is at reg 31
// Decode calibration status:
// Bit 15: lock (should be 1)
// Bits 14:8: ct_cal (coarse tune calibration value, 0-127)
// Bits 7:1: cp_cal (charge pump calibration value)
// Bit 0: ctfail (1 = calibration FAILED)
bool lock_bit = (cal_status >> 15) & 1;
uint8_t ct_cal = (cal_status >> 8) & 0x7F;
uint8_t cp_cal = (cal_status >> 1) & 0x7F;
bool ct_fail = cal_status & 1;
// Add to refresh_status():
uint32_t r6 = radio::debug::first_if::register_read(6);
uint32_t r5 = radio::debug::first_if::register_read(5);
uint32_t r3 = radio::debug::first_if::register_read(3); // VCO_CTRL
// Check SDATA (GPIO4[14]) direction
uint32_t gpio4_dir = LPC_GPIO->DIR[4];
bool sdata_is_output = (gpio4_dir >> 14) & 1;
text_gpio4.set("GPIO4 DIR: " + to_string_hex(gpio4_dir, 8) +
" SDATA=" + std::string(sdata_is_output ? "OUT" : "IN"));
// Display these values
text_status2.set("CAL ct=" + to_string_dec_uint(ct_cal) +
" cp=" + to_string_dec_uint(cp_cal) +
(ct_fail ? " FAIL!" : " OK") +
" lck_b=" + to_string_dec_uint(lock_bit));
text_status3.set("R3:" + to_string_hex(r3, 4) +
" R5:" + to_string_hex(r5, 4) +
" R6:" + to_string_hex(r6, 4));
if (enx_initial != enx_final || dir2_initial != dir2_final) {
// ENX or DIR changed - report which operation caused it
std::string diag = "CHG: ";
if (enx_initial != enx_before_spi) diag += "pre ";
if (enx_before_spi != enx_after_r0) diag += "R0 ";
if (enx_after_r0 != enx_after_r15) diag += "R15 ";
if (enx_after_r15 != enx_final) diag += "R16 ";
diag += std::to_string(enx_initial) + "->" + std::to_string(enx_final);
if (dir2_initial != dir2_final) {
diag += " DIR!";
}
text_status.set(diag);
text_status.set_style(Theme::getInstance()->fg_red);
// Blink LED
/*for (int i = 0; i < 3; i++) {
hackrf::one::led_rx.setActive();
chThdSleepMilliseconds(100);
hackrf::one::led_rx.setInactive();
chThdSleepMilliseconds(100);
}*/
} else {
// No change - normal status
if (!rffc_locked) {
text_status.set("ID 0x" + to_string_hex(device_id, 4) + " PLL UNLOCKED!");
text_status.set_style(Theme::getInstance()->fg_red);
} else if (enx == 1) {
text_status.set("ID 0x" + to_string_hex(device_id, 4) + " DSBLD,ENX=1!");
text_status.set_style(Theme::getInstance()->fg_red);
} else {
text_status.set("ID 0x" + to_string_hex(device_id, 4) + " Passed!");
text_status.set_style(Theme::getInstance()->fg_green);
}
}
}
/* RFFCTuningDebugView *************************************************/
RFFCTuningDebugView::RFFCTuningDebugView(NavigationView& nav) {
add_children({
&text_title,
&text_lbl_called,
&text_called,
&text_lbl_req,
&text_req,
&text_lbl_exp_n,
&text_exp_n,
&text_lbl_act_n,
&text_act_n,
&text_lbl_exp_div,
&text_exp_div,
&text_lbl_act_div,
&text_act_div,
&text_lbl_calc,
&text_calc,
&text_lbl_calc_lo,
&text_calc_lo,
&text_lbl_calc_vco,
&text_calc_vco,
&text_lbl_vco,
&text_vco,
&text_lbl_n_q24,
&text_n_q24,
&text_status,
&button_refresh,
&button_done,
});
button_refresh.on_select = [this](Button&) {
refresh();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
refresh();
}
void RFFCTuningDebugView::focus() {
button_refresh.focus();
}
void RFFCTuningDebugView::refresh() {
// Get expected values from last tuning attempt
auto tuning = radio::debug::first_if::get_tuning_info();
// Show if set_frequency was ever called
if (tuning.was_called) {
text_called.set("YES");
text_called.set_style(Theme::getInstance()->fg_green);
text_req.set(to_string_dec_uint(tuning.requested_freq_mhz) + " MHz");
text_exp_n.set(to_string_dec_uint(tuning.expected_n));
uint16_t exp_lo = 1 << tuning.expected_lodiv;
uint16_t exp_pr = 1 << tuning.expected_presc;
text_exp_div.set(to_string_dec_uint(exp_lo) + " / " + to_string_dec_uint(exp_pr));
} else {
text_called.set("NO");
text_called.set_style(Theme::getInstance()->fg_red);
text_req.set("---");
text_exp_n.set("---");
text_exp_div.set("---");
}
// Read actual hardware values
uint32_t r15 = radio::debug::first_if::register_read(15);
uint16_t act_n = (r15 >> 7) & 0x1FF;
uint8_t act_lo_sel = (r15 >> 4) & 0x07;
uint8_t act_pr_sel = (r15 >> 2) & 0x03;
uint16_t act_lo = 1 << act_lo_sel;
uint16_t act_pr = 1 << act_pr_sel;
text_act_n.set(to_string_dec_uint(act_n));
text_act_div.set(to_string_dec_uint(act_lo) + " / " + to_string_dec_uint(act_pr));
// Calculate what this produces
uint32_t calc_vco = (40 * act_n) / act_pr;
uint32_t calc_lo = calc_vco / act_lo;
text_calc.set(to_string_dec_uint(calc_lo) + " MHz");
text_vco.set(to_string_dec_uint(tuning.calculated_vco_mhz) + " MHz");
text_calc_lo.set(to_string_dec_uint(tuning.calc_lo_freq_mhz) + " MHz");
text_calc_vco.set(to_string_dec_uint(tuning.calc_vco_inside_mhz) + " MHz");
text_n_q24.set(to_string_dec_uint(tuning.calc_n_q24 >> 24)); // Show integer part
// Status comparison
if (!tuning.was_called) {
text_status.set("RFFC Freq set NEVER called!");
text_status.set_style(Theme::getInstance()->fg_red);
} else if (act_n == tuning.expected_n) {
text_status.set("MATCH! Hardware as expected!");
text_status.set_style(Theme::getInstance()->fg_green);
} else {
text_status.set("MISMATCH! Exp:" + to_string_dec_uint(tuning.expected_n) +
" Act:" + to_string_dec_uint(act_n));
text_status.set_style(Theme::getInstance()->fg_red);
}
}
/* MAX2831DebugView *************************************************/
MAX2831DebugView::MAX2831DebugView(NavigationView& nav) {
add_children({
&text_title,
&text_lbl_called,
&text_called,
&text_lbl_valid,
&text_valid,
&text_lbl_req,
&text_req,
&text_lbl_calc_n,
&text_calc_n,
&text_lbl_calc_frac,
&text_calc_frac,
&text_spacer,
&text_lbl_r3,
&text_r3,
&text_lbl_r4,
&text_r4,
&text_lbl_act_n,
&text_act_n,
&text_lbl_act_frac,
&text_act_frac,
&text_lbl_calc_freq,
&text_calc_freq,
&text_status,
&button_refresh,
&button_done,
});
text_spacer.set_style(Theme::getInstance()->fg_yellow);
button_refresh.on_select = [this](Button&) {
refresh();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
refresh();
}
void MAX2831DebugView::focus() {
button_refresh.focus();
}
void MAX2831DebugView::refresh() {
auto info = get_max2831_info();
// Show if set_frequency was called
if (info.set_frequency_called) {
text_called.set("YES");
text_called.set_style(Theme::getInstance()->fg_green);
if (info.frequency_valid) {
text_valid.set("YES (2.3-2.6G)");
text_valid.set_style(Theme::getInstance()->fg_green);
} else {
text_valid.set("NO - OUT OF RANGE!");
text_valid.set_style(Theme::getInstance()->fg_red);
}
text_req.set(to_string_dec_uint(info.requested_freq_mhz) + " MHz");
text_calc_n.set(to_string_dec_uint(info.calculated_n));
text_calc_frac.set(to_string_hex(info.calculated_frac, 5));
} else {
text_called.set("NO");
text_called.set_style(Theme::getInstance()->fg_red);
text_valid.set("---");
text_req.set("---");
text_calc_n.set("---");
text_calc_frac.set("---");
}
// Read actual hardware registers
uint32_t r3 = radio::debug::second_if::register_read(3);
uint32_t r4 = radio::debug::second_if::register_read(4);
text_r3.set(to_string_hex(r3, 4));
text_r4.set(to_string_hex(r4, 4));
// Decode actual values from registers
uint16_t act_n = r3 & 0xFF;
uint32_t act_frac_lo = (r3 >> 8) & 0x3F;
uint32_t act_frac_hi = r4 & 0x3FFF;
uint32_t act_frac = (act_frac_hi << 6) | act_frac_lo;
text_act_n.set(to_string_dec_uint(act_n));
text_act_frac.set(to_string_hex(act_frac, 5));
// Calculate actual frequency from registers
// F_LO = 20 MHz × (N + Frac/2^20)
// For display, show integer part only
uint32_t calc_freq_mhz = 20 * act_n;
// Add fractional contribution (approximate)
uint32_t frac_contribution = (act_frac * 20) >> 20;
calc_freq_mhz += frac_contribution;
text_calc_freq.set(to_string_dec_uint(calc_freq_mhz) + " MHz");
// Status
if (!info.set_frequency_called) {
text_status.set("MAX2831 set_frequency\nNEVER called!");
text_status.set_style(Theme::getInstance()->fg_red);
} else if (!info.frequency_valid) {
text_status.set("Freq " + to_string_dec_uint(info.requested_freq_mhz) +
" MHz OUT OF RANGE!\n(need 2300-2600)");
text_status.set_style(Theme::getInstance()->fg_red);
} else if (act_n == info.calculated_n && act_frac == info.calculated_frac) {
text_status.set("MATCH!\nHardware = Expected");
text_status.set_style(Theme::getInstance()->fg_green);
} else {
text_status.set("MISMATCH!\nN: exp=" + to_string_dec_uint(info.calculated_n) +
" act=" + to_string_dec_uint(act_n));
text_status.set_style(Theme::getInstance()->fg_red);
}
}
#endif
#endif
/* DebugPeripheralsMenuView **********************************************/
DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav)
: nav_(nav) {
set_max_rows(2); // allow wider buttons
}
void DebugPeripheralsMenuView::on_populate() {
#ifdef PRALINE
const char* max283x = "MAX2831";
#else
const char* max283x = hackrf_r9 ? "MAX2839" : "MAX2837";
#endif
const char* si5351x = hackrf_r9 ? "Si5351A" : "Si5351C";
add_items({
{"RFFC5072", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 31, 16}); }},
#ifdef PRALINE
{max283x, Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this, max283x]() { nav_.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 16, 16, 14}); }},
{"FPGA", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>("FPGA (iCE40)", RegistersWidgetConfig{CT_FPGA, 6, 6, 8}); }},
#else
{max283x, Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this, max283x]() { nav_.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 32, 32, 10}); }},
#endif
{"SGPIO", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>("SGPIO", RegistersWidgetConfig{CT_SGPIO, 6, 6, 16}); }},
{si5351x, Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this, si5351x]() { nav_.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
{audio::debug::codec_name(), Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }},
});
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) {
add_item(
{"MAX17055", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>("MAX17055", RegistersWidgetConfig{CT_MAX17055, 256, 16, 16}); }});
}
set_max_rows(2); // allow wider buttons
}
/* DebugReboot **********************************************/
DebugReboot::DebugReboot(NavigationView& nav) {
(void)nav;
LPC_RGU->RESET_CTRL[0] = (1 << 0);
while (1)
__WFE();
}
void DebugReboot::on_populate() {
}
/* DebugMenuView *********************************************************/
DebugMenuView::DebugMenuView(NavigationView& nav)
: nav_(nav) {
set_max_rows(2); // allow wider buttons
}
#ifdef PRALINE
/* PralineDebugMenuView *************************************************/
PralineDebugMenuView::PralineDebugMenuView(NavigationView& nav)
: nav_(nav) {
set_max_rows(2); // Allows wider buttons for descriptive titles
}
void PralineDebugMenuView::on_populate() {
if (portapack::persistent_memory::show_gui_return_icon()) {
add_items({{"..", ui::Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}});
}
add_items({
{"WFM Audio", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<WFMAudioDebugView>(); }},
{"Clocks", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<ui::PralineClockDebugView>(); }},
{"MSynth", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<Si5351MultiSynthDebugView>(); }},
{"Radio Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<RadioDiagnosticsView>(); }},
{"Radio Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<PralineRadioDebugView>(); }},
{"Signal Path", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<SignalPathStatusView>(); }},
{"System Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<SystemDiagnosticsView>(); }},
{"PLL A", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<Si5351PLLADebugView>(); }},
{"PLL B", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<Si5351PLLBDebugView>(); }},
{"GPIO", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<GPIODebugView>(); }},
{"RFFC Status", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<RFFC5072StatusView>(); }},
{"RFFC Tuning", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<RFFCTuningDebugView>(); }},
{"MAX2831", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<MAX2831DebugView>(); }},
{"Si5351", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<Si5351DebugView>(); }},
{"SGPIO Clk", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<SGPIO8ClockDetectorView>(); }},
{"Baseband", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<BasebandStatusView>(); }},
{"SGPIO Live", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<SGPIOLiveMonitorView>(); }},
{"RX Test", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<RadioRxTestView>(); }},
});
}
#endif
void DebugMenuView::on_populate() {
if (portapack::persistent_memory::show_gui_return_icon()) {
add_items({{"..", ui::Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}});
}
add_items({
#ifdef PRALINE
{"Pro Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_setup, [this]() { nav_.push<PralineDebugMenuView>(); }},
#endif
{"Buttons Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_controls, [this]() { nav_.push<DebugControlsView>(); }},
{"M0 Stack Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { stack_dump(); }},
{"Memory Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryDumpView>(); }},
{"Peripherals", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<DebugPeripheralsMenuView>(); }},
{"Pers. Memory", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugPmemView>(); }},
{"SD Card", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_sdcard, [this]() { nav_.push<SDCardDebugView>(); }},
{"Touch Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_notepad, [this]() { nav_.push<DebugScreenTest>(); }},
{"Reboot", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_setup, [this]() { nav_.push<DebugReboot>(); }},
{"Ext Module", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<ExternalModuleView>(); }},
});
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) {
add_item(
{"Battery", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_batt_icon, [this]() { nav_.push<BatteryCapacityView>(); }});
}
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav_)) {
add_item(gridItem);
};
}
/* DebugMemoryDumpView *********************************************************/
DebugMemoryDumpView::DebugMemoryDumpView(NavigationView& nav) {
add_children({
&button_dump,
&button_read,
&button_write,
&button_done,
&labels,
&field_starting_address,
&field_byte_count,
&field_rw_address,
&field_data_value,
});
button_done.on_select = [&nav](Button&) { nav.pop(); };
button_dump.on_select = [this](Button&) {
if (field_byte_count.to_integer() != 0)
memory_dump((uint32_t*)field_starting_address.to_integer(), ((uint32_t)field_byte_count.to_integer() + 3) / 4, false);
};
button_read.on_select = [this](Button&) {
field_data_value.set_value(*(uint32_t*)field_rw_address.to_integer());
field_data_value.set_dirty();
};
button_write.set_style(Theme::getInstance()->fg_red);
button_write.on_select = [this](Button&) {
*(uint32_t*)field_rw_address.to_integer() = (uint32_t)field_data_value.to_integer();
};
}
void DebugMemoryDumpView::focus() {
button_done.focus();
}
/* DebugPmemView *********************************************************/
DebugPmemView::DebugPmemView(NavigationView& nav)
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
add_children({&registers_widget, &text_checksum, &text_checksum2, &button_ok});
registers_widget.set_parent_rect({0, 32, screen_width, 192});
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size(), 3) + " CRC: " + to_string_hex(portapack::persistent_memory::pmem_stored_checksum(), 8));
text_checksum2.set("Calculated CRC: " + to_string_hex(portapack::persistent_memory::pmem_calculated_checksum(), 8));
button_ok.on_select = [&nav](Button&) {
nav.pop();
};
update();
}
bool DebugPmemView::on_encoder(const EncoderEvent delta) {
registers_widget.set_page(std::max(0ul, std::min((uint32_t)page_count - 1, registers_widget.page() + delta)));
update();
return true;
}
void DebugPmemView::focus() {
button_ok.focus();
}
void DebugPmemView::update() {
registers_widget.update();
}
/* DebugScreenTest ****************************************************/
DebugScreenTest::DebugScreenTest(NavigationView& nav)
: nav_{nav} {
set_focusable(true);
srand(LPC_RTC->CTIME0);
}
bool DebugScreenTest::on_key(const KeyEvent key) {
Painter painter;
switch (key) {
case KeyEvent::Select:
nav_.pop();
break;
case KeyEvent::Down:
painter.fill_rectangle({0, 0, screen_width, screen_height}, rand());
break;
case KeyEvent::Left:
pen_color = rand();
break;
default:
break;
}
return true;
}
bool DebugScreenTest::on_encoder(EncoderEvent delta) {
pen_size = clip<int32_t>(pen_size + delta, 1, screen_width);
return true;
}
bool DebugScreenTest::on_touch(const TouchEvent event) {
Painter painter;
pen_pos = event.point;
painter.fill_rectangle({pen_pos.x() - pen_size / 2, pen_pos.y() - pen_size / 2, pen_size, pen_size}, pen_color);
return true;
}
void DebugScreenTest::paint(Painter& painter) {
painter.fill_rectangle({0, 16, screen_width, screen_height - 16}, Theme::getInstance()->bg_darkest->foreground);
painter.draw_string({10 * 8, screen_height / 2}, *Theme::getInstance()->bg_darkest, "Use Stylus");
pen_color = rand();
}
/* DebugLCRView *******************************************************/
/*DebugLCRView::DebugLCRView(NavigationView& nav, std::string lcr_string) {
std::string debug_text;
add_children({
&console,
&button_exit
});
for(const auto c : lcr_string) {
if ((c < 32) || (c > 126))
debug_text += "[" + to_string_dec_uint(c) + "]";
else
debug_text += c;
}
debug_text += "\n\n";
debug_text += "Length: " + to_string_dec_uint(lcr_string.length()) + '\n';
debug_text += "Checksum: " + to_string_dec_uint(lcr_string.back()) + '\n';
console.write(debug_text);
button_exit.on_select = [this, &nav](Button&){
nav.pop();
};
}
void DebugLCRView::focus() {
button_exit.focus();
}*/
} /* namespace ui */