Portapack应用开发教程 (十六) Debug程序

2023-12-12 06:38

本文主要是介绍Portapack应用开发教程 (十六) Debug程序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

截止到第15篇为止,我已经开发了很多APP了。

比较重要的包括:GPS发射、模拟视频接收、蓝牙接收、NRF24L01接收、SSTV接收、APRS接收

我本来还想做NOAA接收。但是我觉得开发步骤都差不多。都是一些有关无线电通信的数字信号处理的开发。

我现在想做一些不同的东西。跟硬件更相关的东西。比如如何和芯片交互,这样将来要自己加一些硬件模块也会更方便。

这个debug功能其实原版的Portapack固件里就是有的,后来在havoc被删掉了,我看了Mayhem代码。它把debug功能加回来了。所以我也把它加了回来。

Debug是一个界面,里面也有各个子菜单,可以用来测试按钮和芯片。芯片的测试方法主要是读芯片寄存器。

包括2个中频芯片、时钟芯片和声卡芯片。

我们可以看看这个声卡芯片的信息。当我打开麦克风和关闭麦克风时,寄存器输出是不一样的。

说明这个debug功能确实是有效的。

但是我不想研究声卡芯片,因为它是portapack板子上的芯片。我更想研究hackrf板子上的芯片,因为这样我可以和hackrf的原生硬件的固件做比较。

我想先着手研究那两个中频芯片,然后试试能否把hackrf上的那个flash芯片(w25q80bv)也加入到debug程序来,作为练手。

下面我们先看看debug的代码。

ui_debug.cpp


/* DebugPeripheralsMenuView **********************************************/DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav) {add_items({{ "RFFC5072",    ui::Color::dark_cyan(),	&bitmap_icon_peripherals_details,	[&nav](){ nav.push<RegistersView>("RFFC5072", RegistersWidgetConfig { 31, 16 },[](const size_t register_number) { return radio::debug::first_if::register_read(register_number); }); } },{ "MAX2837",     ui::Color::dark_cyan(),	&bitmap_icon_peripherals_details,	[&nav](){ nav.push<RegistersView>("MAX2837", RegistersWidgetConfig { 32, 10 },[](const size_t register_number) { return radio::debug::second_if::register_read(register_number); }); } },{ "Si5351C",     ui::Color::dark_cyan(),	&bitmap_icon_peripherals_details,	[&nav](){ nav.push<RegistersView>("Si5351C", RegistersWidgetConfig { 96, 8 },[](const size_t register_number) { return portapack::clock_generator.read_register(register_number); }); } },{ audio::debug::codec_name(), ui::Color::dark_cyan(),	&bitmap_icon_peripherals_details,	[&nav](){ nav.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig { audio::debug::reg_count(), audio::debug::reg_bits() },[](const size_t register_number) { return audio::debug::reg_read(register_number); }); } },});set_max_rows(2); // allow wider buttons
}

目前一共可以看到4个芯片的信息。最下面两个分别是时钟和声卡芯片,可以看到它们都封装过了。不如最上面两个芯片来得简洁明了。

ui_debug.hpp

#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_menu.hpp"
#include "ui_navigation.hpp"#include "rffc507x.hpp"
#include "max2837.hpp"
#include "portapack.hpp"#include <functional>
#include <utility>

头文件里也说明了这一点。rffc507x和max2837的两个头文件都是直接引用的,不像其他芯片估计已经包含在别的头文件里了。

如果我们再看看portapack固件代码里有关这两颗芯片的部分,以及hackrf原生固件的对应部分,我们可以发现,这两种固件都分别写了两颗中频芯片的相关代码,只不过一个是c++写的,另一个是c写的。我们可以对比一下,看看一些比较熟悉的功能是用什么样的代码实现的。进而理解portapack底层开发和hackrf固件开发的知识。 

相关的文件夹和文件如下:

portapack/firmware/application/hw/ max2837.cpp rffc507x.cpp rffc507x_spi.cpp

hackrf/firmware/common/ max2837.c rffc5071.c rffc5071_spi.c

我找了个hackrf的架构图,帮助你对这些芯片的功能有所了解,这样对后面看代码有帮助。

以接收信号作为例子,从左下角天线这里开始,经过了滤波器,其实没多大用,你可以当做是直接透传上去到达rffc5072,这个芯片的作用是对原始信号进行混频,把30MHz~6GHz这么大范围的无线电信号都变频到2.4GHz左右,接下来到达MAX2837芯片,它再把刚刚的那个2.4GHz左右的信号下变频到基带也就是0Hz附近,再往下就可以用MAX5864芯片来采样了,它就是一个ADC芯片,再往后到达CPLD,其实CPLD没做什么工作,直接传给了NXP的单片机,单片机再经过usb2.0传给电脑。

我们现在说的这些固件,无论是hackrf原生固件还是portapack固件,都是在nxp这个单片机内部运行的,它除了处理这些数据和传给电脑这两种工作外,还可以控制板子上的其它芯片,让它们在所需要的模式下工作,这些操作就通过我们下面要讲的代码实现的。其实可以理解为nxp的单片机就是飞控里的stm32,而别的芯片就是类似mpu6050这样的芯片。单片机可以处理从其它芯片得到的数据,也可以配置其它芯片的寄存器,让它们在不同模式下工作。

 

我下面找了几段代码,分别来自两种固件。写法不同,但功能相同。

比如下面这两段,第一个函数gpio读bit,第二个函数是传输bit,第三个函数利用第二个函数传输bits,第四个函数调用了传输bits。

rffc507x_spi.cpp


inline bit_t SPI::read_bit() {return gpio_rffc5072_data.read() & 1;
}inline bit_t SPI::transfer_bit(const bit_t bit_out) {gpio_rffc5072_clock.clear();write_bit(bit_out);const bit_t bit_in = read_bit();gpio_rffc5072_clock.set();return bit_in;
}data_t SPI::transfer_bits(const data_t data_out, const size_t count) {data_t data_in = 0;for(size_t i=0; i<count; i++) {data_in = (data_in << 1) | transfer_bit((data_out >> (count - i - 1)) & 1);}return data_in;
}data_t SPI::transfer_word(const Direction direction, const address_t address, const data_t data_out) {select(true);const data_t address_word =((direction == Direction::Read) ? (1 << 7) : 0)| (address & 0x7f);direction_out();transfer_bits(address_word, 9);if( direction == Direction::Read ) {direction_in();transfer_bits(0, 2);}const data_t data_in = transfer_bits(data_out, 16);if( direction == Direction::Write ) {direction_in();}select(false);transfer_bits(0, 2);return data_in;
}

下面这一段的几个函数跟上面几个差不多,只是写法不同。第一个函数也是用gpio读数据。第二个函数在交换bit,第三个在利用第二个函数交换word,第四个调用了第三个函数。

rffc5071_spi.c


static bool rffc5071_spi_data_in(spi_bus_t* const bus) {const rffc5071_spi_config_t* const config = bus->config;return gpio_read(config->gpio_data);
}static uint32_t rffc5071_spi_exchange_bit(spi_bus_t* const bus, const uint32_t bit) {rffc5071_spi_data_out(bus, bit);rffc5071_spi_sck(bus);return rffc5071_spi_data_in(bus) ? 1 : 0;
}static uint32_t rffc5071_spi_exchange_word(spi_bus_t* const bus, const uint32_t data, const size_t count) {size_t bits = count;const uint32_t msb = 1UL << (count - 1);uint32_t t = data;while (bits--) {t = (t << 1) | rffc5071_spi_exchange_bit(bus, t & msb);}return t & ((1UL << count) - 1);
}void rffc5071_spi_transfer(spi_bus_t* const bus, void* const _data, const size_t count) {if( count != 2 ) {return;}uint16_t* const data = _data;const bool direction_read = (data[0] >> 7) & 1;/** The device requires two clocks while ENX is high before a serial* transaction.  This is not clearly documented.*/rffc5071_spi_sck(bus);rffc5071_spi_sck(bus);rffc5071_spi_target_select(bus);data[0] = rffc5071_spi_exchange_word(bus, data[0], 9);if( direction_read ) {rffc5071_spi_direction_in(bus);rffc5071_spi_sck(bus);}data[1] = rffc5071_spi_exchange_word(bus, data[1], 16);rffc5071_spi_serial_delay(bus);rffc5071_spi_target_unselect(bus);rffc5071_spi_direction_out(bus);/** The device requires a clock while ENX is high after a serial* transaction.  This is not clearly documented.*/rffc5071_spi_sck(bus);
}

下面两段代码在设置rffc5071的中心频率。

注意看注释,都是在比较vco是否大于3.2ghz。如果大于3.2ghz,prescaler divider(fbkdiv)就是4,否则就是2。

并且都会设置pllcpl,一个是_map.r.lf.pllcpl另一个是set_RFFC5071_PLLCPL。

注意第一段程序判断标准是经过log2运算的,也就是说如果vco大于3.2ghz,prescaller divider就是4,这时候它的log2值就是2,不是说它本身是2。else里prescaller dividier其实本身是2,这时它的log2值是1。

rffc507x.cpp


void RFFC507x::set_frequency(const rf::Frequency lo_frequency) {const SynthConfig synth_config = SynthConfig::calculate(lo_frequency);/* Boost charge pump leakage if VCO frequency > 3.2GHz, indicated by* prescaler divider set to 4 (log2=2) instead of 2 (log2=1).*/if( synth_config.prescaler_divider_log2 == 2 ) {_map.r.lf.pllcpl = 3;} else {_map.r.lf.pllcpl = 2;}flush_one(Register::LF);_map.r.p2_freq1.p2n = synth_config.n_divider_q24 >> 24;_map.r.p2_freq1.p2lodiv = synth_config.lo_divider_log2;_map.r.p2_freq1.p2presc = synth_config.prescaler_divider_log2;_map.r.p2_freq2.p2nmsb = (synth_config.n_divider_q24 >> 8) & 0xffff;_map.r.p2_freq3.p2nlsb = synth_config.n_divider_q24 & 0xff;_dirty[Register::P2_FREQ1] = 1;_dirty[Register::P2_FREQ2] = 1;_dirty[Register::P2_FREQ3] = 1;flush();
}

 rffc5071.c


/* configure frequency synthesizer in integer mode (lo in MHz) */
uint64_t rffc5071_config_synth_int(rffc5071_driver_t* const drv, uint16_t lo) {uint8_t lodiv;uint16_t fvco;uint8_t fbkdiv;uint16_t n;uint64_t tune_freq_hz;uint16_t p1nmsb;uint8_t p1nlsb;/* Calculate n_lo */uint8_t n_lo = 0;uint16_t x = LO_MAX / lo;while ((x > 1) && (n_lo < 5)) {n_lo++;x >>= 1;}lodiv = 1 << n_lo;fvco = lodiv * lo;/* higher divider and charge pump current required above* 3.2GHz. Programming guide says these values (fbkdiv, n,* maybe pump?) can be changed back after enable in order to* improve phase noise, since the VCO will already be stable* and will be unaffected. */if (fvco > 3200) {fbkdiv = 4;set_RFFC5071_PLLCPL(drv, 3);} else {fbkdiv = 2;set_RFFC5071_PLLCPL(drv, 2);}uint64_t tmp_n = ((uint64_t)fvco << 29ULL) / (fbkdiv*REF_FREQ) ;n = tmp_n >> 29ULL;p1nmsb = (tmp_n >> 13ULL) & 0xffff;p1nlsb = (tmp_n >> 5ULL) & 0xff;tune_freq_hz = (REF_FREQ * (tmp_n >> 5ULL) * fbkdiv * FREQ_ONE_MHZ)/ (lodiv * (1 << 24ULL));/* Path 2 */set_RFFC5071_P2LODIV(drv, n_lo);set_RFFC5071_P2N(drv, n);set_RFFC5071_P2PRESC(drv, fbkdiv >> 1);set_RFFC5071_P2NMSB(drv, p1nmsb);set_RFFC5071_P2NLSB(drv, p1nlsb);rffc5071_regs_commit(drv);return tune_freq_hz;
}/* !!!!!!!!!!! hz is currently ignored !!!!!!!!!!! */
uint64_t rffc5071_set_frequency(rffc5071_driver_t* const drv, uint16_t mhz) {uint32_t tune_freq;rffc5071_disable(drv);tune_freq = rffc5071_config_synth_int(drv, mhz);rffc5071_enable(drv);return tune_freq;
}

下面两段程序也分别摘自上面两个文件。都是在实现寄存器读写的功能。靠下面的2个寄存器读写函数都在调用靠上面的spi读写函数,而spi读写函数都是在调用spi bus也就是总线相关的函数。 


void RFFC507x::write(const address_t reg_num, const spi::reg_t value) {_bus.write(reg_num, value);
}spi::reg_t RFFC507x::read(const address_t reg_num) {return _bus.read(reg_num);
}void RFFC507x::write(const Register reg, const spi::reg_t value) {write(toUType(reg), value);
}spi::reg_t RFFC507x::read(const Register reg) {return read(toUType(reg));
}
static uint16_t rffc5071_spi_read(rffc5071_driver_t* const drv, uint8_t r) {(void)drv;uint16_t data[] = { 0x80 | (r & 0x7f), 0xffff };spi_bus_transfer(drv->bus, data, 2);return data[1];
}static void rffc5071_spi_write(rffc5071_driver_t* const drv, uint8_t r, uint16_t v) {(void)drv;uint16_t data[] = { 0x00 | (r & 0x7f), v };spi_bus_transfer(drv->bus, data, 2);
}uint16_t rffc5071_reg_read(rffc5071_driver_t* const drv, uint8_t r)
{/* Readback register is not cached. */if (r == RFFC5071_READBACK_REG)return rffc5071_spi_read(drv, r);/* Discard uncommited write when reading. This shouldn't* happen, and probably has not been tested. */if ((drv->regs_dirty >> r) & 0x1) {drv->regs[r] = rffc5071_spi_read(drv, r);};return drv->regs[r];
}void rffc5071_reg_write(rffc5071_driver_t* const drv, uint8_t r, uint16_t v)
{drv->regs[r] = v;rffc5071_spi_write(drv, r, v);RFFC5071_REG_SET_CLEAN(drv, r);
}

再来看看max2837,前面说它的功能是把2.4GHz的信号变频到基带,实际它不是正好2.4GHz而是有一定范围的从2.3GHz~2.7GHz。不同频率处理方法不一样。

两个固件里分别是用下面这个函数实现的。

max2837.cpp


bool MAX2837::set_frequency(const rf::Frequency lo_frequency) {/* TODO: This is a sad implementation. Refactor. */if( lo::band[0].contains(lo_frequency) ) {_map.r.syn_int_div.LOGEN_BSW = 0b00;	/* 2300 - 2399.99MHz */_map.r.rxrf_1.LNAband = 0;				/* 2.3 - 2.5GHz */} else if( lo::band[1].contains(lo_frequency)  ) {_map.r.syn_int_div.LOGEN_BSW = 0b01;	/* 2400 - 2499.99MHz */_map.r.rxrf_1.LNAband = 0;				/* 2.3 - 2.5GHz */} else if( lo::band[2].contains(lo_frequency) ) {_map.r.syn_int_div.LOGEN_BSW = 0b10;	/* 2500 - 2599.99MHz */_map.r.rxrf_1.LNAband = 1;				/* 2.5 - 2.7GHz */} else if( lo::band[3].contains(lo_frequency) ) {_map.r.syn_int_div.LOGEN_BSW = 0b11;	/* 2600 - 2700Hz */_map.r.rxrf_1.LNAband = 1;				/* 2.5 - 2.7GHz */} else {return false;}_dirty[Register::SYN_INT_DIV] = 1;_dirty[Register::RXRF_1] = 1;const uint64_t div_q20 = (lo_frequency * (1 << 20)) / pll_factor;_map.r.syn_int_div.SYN_INTDIV = div_q20 >> 20;_dirty[Register::SYN_INT_DIV] = 1;_map.r.syn_fr_div_2.SYN_FRDIV_19_10 = (div_q20 >> 10) & 0x3ff;_dirty[Register::SYN_FR_DIV_2] = 1;/* flush to commit high FRDIV first, as low FRDIV commits the change */flush();_map.r.syn_fr_div_1.SYN_FRDIV_9_0 = (div_q20 & 0x3ff);_dirty[Register::SYN_FR_DIV_1] = 1;flush();return true;
}

max2837.c


void max2837_set_frequency(max2837_driver_t* const drv, uint32_t freq)
{uint8_t band;uint8_t lna_band;uint32_t div_frac;uint32_t div_int;uint32_t div_rem;uint32_t div_cmp;int i;/* Select band. Allow tuning outside specified bands. */if (freq < 2400000000U) {band = MAX2837_LOGEN_BSW_2_3;lna_band = MAX2837_LNAband_2_4;}else if (freq < 2500000000U) {band = MAX2837_LOGEN_BSW_2_4;lna_band = MAX2837_LNAband_2_4;}else if (freq < 2600000000U) {band = MAX2837_LOGEN_BSW_2_5;lna_band = MAX2837_LNAband_2_6;}else {band = MAX2837_LOGEN_BSW_2_6;lna_band = MAX2837_LNAband_2_6;}/* ASSUME 40MHz PLL. Ratio = F*(4/3)/40,000,000 = F/30,000,000 */div_int = freq / 30000000;div_rem = freq % 30000000;div_frac = 0;div_cmp = 30000000;for( i = 0; i < 20; i++) {div_frac <<= 1;div_cmp >>= 1;if (div_rem > div_cmp) {div_frac |= 0x1;div_rem -= div_cmp;}}/* Band settings */set_MAX2837_LOGEN_BSW(drv, band);set_MAX2837_LNAband(drv, lna_band);/* Write order matters here, so commit INT and FRAC_HI before* committing FRAC_LO, which is the trigger for VCO* auto-select. TODO - it's cleaner this way, but it would be* faster to explicitly commit the registers explicitly so the* dirty bits aren't scanned twice. */set_MAX2837_SYN_INT(drv, div_int);set_MAX2837_SYN_FRAC_HI(drv, (div_frac >> 10) & 0x3ff);max2837_regs_commit(drv);set_MAX2837_SYN_FRAC_LO(drv, div_frac & 0x3ff);max2837_regs_commit(drv);
}

不用太深入去看,只要熟悉两个固件不同的风格,基本了解怎样用c++来实现c语言版本的固件功能就行。

接下来就可以看c语言版本的flash芯片的对应代码,以及想办法把它加入ui_debug.cpp里显示了。

这篇关于Portapack应用开发教程 (十六) Debug程序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/483602

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Goland debug失效详细解决步骤(合集)

《Golanddebug失效详细解决步骤(合集)》今天用Goland开发时,打断点,以debug方式运行,发现程序并没有断住,程序跳过了断点,直接运行结束,网上搜寻了大量文章,最后得以解决,特此在这... 目录Bug:Goland debug失效详细解决步骤【合集】情况一:Go或Goland架构不对情况二:

Ubuntu固定虚拟机ip地址的方法教程

《Ubuntu固定虚拟机ip地址的方法教程》本文详细介绍了如何在Ubuntu虚拟机中固定IP地址,包括检查和编辑`/etc/apt/sources.list`文件、更新网络配置文件以及使用Networ... 1、由于虚拟机网络是桥接,所以ip地址会不停地变化,接下来我们就讲述ip如何固定 2、如果apt安

PyCharm 接入 DeepSeek最新完整教程

《PyCharm接入DeepSeek最新完整教程》文章介绍了DeepSeek-V3模型的性能提升以及如何在PyCharm中接入和使用DeepSeek进行代码开发,本文通过图文并茂的形式给大家介绍的... 目录DeepSeek-V3效果演示创建API Key在PyCharm中下载Continue插件配置Con

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创

Spring Boot整合log4j2日志配置的详细教程

《SpringBoot整合log4j2日志配置的详细教程》:本文主要介绍SpringBoot项目中整合Log4j2日志框架的步骤和配置,包括常用日志框架的比较、配置参数介绍、Log4j2配置详解... 目录前言一、常用日志框架二、配置参数介绍1. 日志级别2. 输出形式3. 日志格式3.1 PatternL

MySQL8.2.0安装教程分享

《MySQL8.2.0安装教程分享》这篇文章详细介绍了如何在Windows系统上安装MySQL数据库软件,包括下载、安装、配置和设置环境变量的步骤... 目录mysql的安装图文1.python访问网址2javascript.点击3.进入Downloads向下滑动4.选择Community Server5.

CentOS系统Maven安装教程分享

《CentOS系统Maven安装教程分享》本文介绍了如何在CentOS系统中安装Maven,并提供了一个简单的实际应用案例,安装Maven需要先安装Java和设置环境变量,Maven可以自动管理项目的... 目录准备工作下载并安装Maven常见问题及解决方法实际应用案例总结Maven是一个流行的项目管理工具

本地私有化部署DeepSeek模型的详细教程

《本地私有化部署DeepSeek模型的详细教程》DeepSeek模型是一种强大的语言模型,本地私有化部署可以让用户在自己的环境中安全、高效地使用该模型,避免数据传输到外部带来的安全风险,同时也能根据自... 目录一、引言二、环境准备(一)硬件要求(二)软件要求(三)创建虚拟环境三、安装依赖库四、获取 Dee