本文主要是介绍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程序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!