From 5f60b004f7385bf26f272614cc8ef6d88b0f77d2 Mon Sep 17 00:00:00 2001 From: furrtek Date: Fri, 20 Nov 2015 07:59:09 +0100 Subject: [PATCH] Dynamic baseband module loading from SD card --- firmware/Makefile | 21 +- firmware/application/Makefile | 2 + firmware/application/m4_startup.cpp | 57 +- firmware/application/m4_startup.hpp | 4 + firmware/application/main.cpp | 14 +- firmware/application/modules.h | 2 + firmware/application/ui_alphanum.cpp | 173 +++ firmware/application/ui_alphanum.hpp | 83 ++ firmware/application/ui_debug.cpp | 20 + firmware/application/ui_debug.hpp | 7 +- firmware/application/ui_lcr.cpp | 20 +- firmware/application/ui_loadmodule.cpp | 89 ++ firmware/application/ui_loadmodule.hpp | 56 + firmware/application/ui_navigation.cpp | 29 +- firmware/application/ui_rds.cpp | 134 --- firmware/application/ui_receiver.cpp | 5 +- firmware/baseband-tx.bin | Bin 0 -> 20608 bytes firmware/baseband-tx/Makefile | 275 +++++ firmware/baseband-tx/audio_dma.cpp | 238 ++++ firmware/baseband-tx/audio_dma.hpp | 43 + .../baseband-tx/audio_stats_collector.hpp | 95 ++ firmware/baseband-tx/baseband_dma.cpp | 203 ++++ firmware/baseband-tx/baseband_dma.hpp | 53 + firmware/baseband-tx/baseband_processor.cpp | 122 ++ firmware/baseband-tx/baseband_processor.hpp | 76 ++ .../baseband-tx/baseband_stats_collector.hpp | 96 ++ firmware/baseband-tx/block_decimator.hpp | 100 ++ firmware/baseband-tx/channel_decimator.cpp | 84 ++ firmware/baseband-tx/channel_decimator.hpp | 79 ++ .../baseband-tx/channel_stats_collector.hpp | 66 ++ firmware/baseband-tx/chconf.h | 546 +++++++++ firmware/baseband-tx/clock_recovery.cpp | 22 + firmware/baseband-tx/clock_recovery.hpp | 178 +++ firmware/baseband-tx/description | 1 + firmware/baseband-tx/dsp_decimate.cpp | 403 +++++++ firmware/baseband-tx/dsp_decimate.hpp | 242 ++++ firmware/baseband-tx/dsp_demodulate.cpp | 111 ++ firmware/baseband-tx/dsp_demodulate.hpp | 70 ++ firmware/baseband-tx/dsp_fir_taps.cpp | 22 + firmware/baseband-tx/dsp_fir_taps.hpp | 205 ++++ firmware/baseband-tx/dsp_iir.hpp | 75 ++ firmware/baseband-tx/dsp_iir_config.hpp | 37 + firmware/baseband-tx/dsp_squelch.cpp | 45 + firmware/baseband-tx/dsp_squelch.hpp | 45 + firmware/baseband-tx/event_m4.cpp | 30 + firmware/baseband-tx/event_m4.hpp | 46 + firmware/baseband-tx/fxpt_atan2.cpp | 152 +++ firmware/baseband-tx/fxpt_atan2.hpp | 29 + firmware/baseband-tx/gpdma_lli.hpp | 225 ++++ firmware/baseband-tx/halconf.h | 313 +++++ firmware/baseband-tx/irq_ipc_m4.cpp | 54 + firmware/baseband-tx/irq_ipc_m4.hpp | 28 + firmware/baseband-tx/linear_resampler.hpp | 69 ++ firmware/baseband-tx/main.cpp | 561 +++++++++ firmware/baseband-tx/main.cpp.orig | 1003 +++++++++++++++++ firmware/baseband-tx/matched_filter.cpp | 89 ++ firmware/baseband-tx/matched_filter.hpp | 101 ++ firmware/baseband-tx/mcuconf.h | 47 + firmware/baseband-tx/name | 1 + firmware/baseband-tx/packet_builder.cpp | 22 + firmware/baseband-tx/packet_builder.hpp | 120 ++ firmware/baseband-tx/proc_fsk_lcr.cpp | 92 ++ firmware/baseband-tx/proc_fsk_lcr.hpp | 44 + firmware/baseband-tx/proc_jammer.cpp | 105 ++ firmware/baseband-tx/proc_jammer.hpp | 44 + firmware/baseband-tx/rssi.cpp | 74 ++ firmware/baseband-tx/rssi.hpp | 43 + firmware/baseband-tx/rssi_dma.cpp | 182 +++ firmware/baseband-tx/rssi_dma.hpp | 51 + firmware/baseband-tx/rssi_stats_collector.hpp | 75 ++ firmware/baseband-tx/symbol_coding.hpp | 44 + firmware/baseband-tx/touch_dma.cpp | 129 +++ firmware/baseband-tx/touch_dma.hpp | 52 + firmware/baseband.bin | Bin 0 -> 29832 bytes firmware/baseband/description | 1 + firmware/baseband/main.cpp | 432 +------ firmware/baseband/name | 1 + firmware/common/message.hpp | 12 + firmware/common/sine_table.hpp | 2 +- firmware/common/ui.hpp | 8 + firmware/portapack-h1-firmware.bin | Bin 400240 -> 403112 bytes firmware/tools/make_baseband_file.py | 92 ++ 82 files changed, 8041 insertions(+), 580 deletions(-) create mode 100644 firmware/application/modules.h create mode 100644 firmware/application/ui_alphanum.cpp create mode 100644 firmware/application/ui_alphanum.hpp create mode 100644 firmware/application/ui_loadmodule.cpp create mode 100644 firmware/application/ui_loadmodule.hpp create mode 100644 firmware/baseband-tx.bin create mode 100755 firmware/baseband-tx/Makefile create mode 100644 firmware/baseband-tx/audio_dma.cpp create mode 100644 firmware/baseband-tx/audio_dma.hpp create mode 100644 firmware/baseband-tx/audio_stats_collector.hpp create mode 100644 firmware/baseband-tx/baseband_dma.cpp create mode 100644 firmware/baseband-tx/baseband_dma.hpp create mode 100644 firmware/baseband-tx/baseband_processor.cpp create mode 100644 firmware/baseband-tx/baseband_processor.hpp create mode 100644 firmware/baseband-tx/baseband_stats_collector.hpp create mode 100644 firmware/baseband-tx/block_decimator.hpp create mode 100644 firmware/baseband-tx/channel_decimator.cpp create mode 100644 firmware/baseband-tx/channel_decimator.hpp create mode 100644 firmware/baseband-tx/channel_stats_collector.hpp create mode 100755 firmware/baseband-tx/chconf.h create mode 100644 firmware/baseband-tx/clock_recovery.cpp create mode 100644 firmware/baseband-tx/clock_recovery.hpp create mode 100644 firmware/baseband-tx/description create mode 100644 firmware/baseband-tx/dsp_decimate.cpp create mode 100644 firmware/baseband-tx/dsp_decimate.hpp create mode 100644 firmware/baseband-tx/dsp_demodulate.cpp create mode 100644 firmware/baseband-tx/dsp_demodulate.hpp create mode 100644 firmware/baseband-tx/dsp_fir_taps.cpp create mode 100644 firmware/baseband-tx/dsp_fir_taps.hpp create mode 100644 firmware/baseband-tx/dsp_iir.hpp create mode 100644 firmware/baseband-tx/dsp_iir_config.hpp create mode 100644 firmware/baseband-tx/dsp_squelch.cpp create mode 100644 firmware/baseband-tx/dsp_squelch.hpp create mode 100644 firmware/baseband-tx/event_m4.cpp create mode 100644 firmware/baseband-tx/event_m4.hpp create mode 100644 firmware/baseband-tx/fxpt_atan2.cpp create mode 100644 firmware/baseband-tx/fxpt_atan2.hpp create mode 100644 firmware/baseband-tx/gpdma_lli.hpp create mode 100755 firmware/baseband-tx/halconf.h create mode 100644 firmware/baseband-tx/irq_ipc_m4.cpp create mode 100644 firmware/baseband-tx/irq_ipc_m4.hpp create mode 100644 firmware/baseband-tx/linear_resampler.hpp create mode 100755 firmware/baseband-tx/main.cpp create mode 100755 firmware/baseband-tx/main.cpp.orig create mode 100644 firmware/baseband-tx/matched_filter.cpp create mode 100644 firmware/baseband-tx/matched_filter.hpp create mode 100755 firmware/baseband-tx/mcuconf.h create mode 100644 firmware/baseband-tx/name create mode 100644 firmware/baseband-tx/packet_builder.cpp create mode 100644 firmware/baseband-tx/packet_builder.hpp create mode 100644 firmware/baseband-tx/proc_fsk_lcr.cpp create mode 100644 firmware/baseband-tx/proc_fsk_lcr.hpp create mode 100644 firmware/baseband-tx/proc_jammer.cpp create mode 100644 firmware/baseband-tx/proc_jammer.hpp create mode 100644 firmware/baseband-tx/rssi.cpp create mode 100644 firmware/baseband-tx/rssi.hpp create mode 100644 firmware/baseband-tx/rssi_dma.cpp create mode 100644 firmware/baseband-tx/rssi_dma.hpp create mode 100644 firmware/baseband-tx/rssi_stats_collector.hpp create mode 100644 firmware/baseband-tx/symbol_coding.hpp create mode 100644 firmware/baseband-tx/touch_dma.cpp create mode 100644 firmware/baseband-tx/touch_dma.hpp create mode 100644 firmware/baseband.bin create mode 100644 firmware/baseband/description create mode 100644 firmware/baseband/name create mode 100755 firmware/tools/make_baseband_file.py diff --git a/firmware/Makefile b/firmware/Makefile index d735d3d7..337a656d 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -22,6 +22,7 @@ PATH_BOOTSTRAP=bootstrap PATH_APPLICATION=application PATH_BASEBAND=baseband +PATH_BASEBAND_TX=baseband-tx TARGET=portapack-h1-firmware @@ -29,8 +30,10 @@ TARGET_BOOTSTRAP=$(PATH_BOOTSTRAP)/bootstrap TARGET_HACKRF_FIRMWARE=hackrf_one_usb_ram TARGET_APPLICATION=$(PATH_APPLICATION)/build/application TARGET_BASEBAND=$(PATH_BASEBAND)/build/baseband +TARGET_BASEBAND_TX=$(PATH_BASEBAND_TX)/build/baseband-tx MAKE_SPI_IMAGE=tools/make_spi_image.py +MAKE_MODULES_FILE=tools/make_baseband_file.py DFU_HACKRF=hackrf_one_usb_ram.dfu LICENSE=../LICENSE @@ -39,7 +42,10 @@ GIT_REVISION=$(shell git log -n 1 --format=%h) CP=arm-none-eabi-objcopy -all: $(TARGET).bin +MODULES = $(PATH_BASEBAND) \ + $(PATH_BASEBAND_TX) + +all: $(TARGET).bin modules release: $(TARGET).bin $(DFU_HACKRF) $(LICENSE) # TODO: Bad hack to fix location of LICENSE file for tar. @@ -48,12 +54,15 @@ release: $(TARGET).bin $(DFU_HACKRF) $(LICENSE) zip -9 -q $(TARGET)-$(GIT_REVISION).zip $(TARGET).bin $(DFU_HACKRF) LICENSE rm -f LICENSE -program: $(TARGET).bin +program: $(TARGET).bin modules dfu-util --device 1fc9:000c --download hackrf_one_usb_ram.dfu --reset sleep 1s hackrf_spiflash -w $(TARGET).bin + +modules: + $(MAKE_MODULES_FILE) $(MODULES) -$(TARGET).bin: $(MAKE_SPI_IMAGE) $(TARGET_BOOTSTRAP).bin $(TARGET_HACKRF_FIRMWARE).dfu $(TARGET_BASEBAND).bin $(TARGET_APPLICATION).bin +$(TARGET).bin: modules $(MAKE_SPI_IMAGE) $(TARGET_BOOTSTRAP).bin $(TARGET_HACKRF_FIRMWARE).dfu $(TARGET_BASEBAND).bin $(TARGET_BASEBAND_TX).bin $(TARGET_APPLICATION).bin $(MAKE_SPI_IMAGE) $(TARGET_BOOTSTRAP).bin $(TARGET_HACKRF_FIRMWARE).dfu $(TARGET_BASEBAND).bin $(TARGET_APPLICATION).bin $(TARGET).bin $(TARGET_BOOTSTRAP).bin: $(TARGET_BOOTSTRAP).elf @@ -61,12 +70,18 @@ $(TARGET_BOOTSTRAP).bin: $(TARGET_BOOTSTRAP).elf $(TARGET_BASEBAND).bin: $(TARGET_BASEBAND).elf $(CP) -O binary $(TARGET_BASEBAND).elf $(TARGET_BASEBAND).bin + +$(TARGET_BASEBAND_TX).bin: $(TARGET_BASEBAND_TX).elf + $(CP) -O binary $(TARGET_BASEBAND_TX).elf $(TARGET_BASEBAND_TX).bin $(TARGET_APPLICATION).bin: $(TARGET_APPLICATION).elf $(CP) -O binary $(TARGET_APPLICATION).elf $(TARGET_APPLICATION).bin $(TARGET_BASEBAND).elf: always_check @$(MAKE) -s -e GIT_REVISION=$(GIT_REVISION) -C $(PATH_BASEBAND) + +$(TARGET_BASEBAND_TX).elf: always_check + @$(MAKE) -s -e GIT_REVISION=$(GIT_REVISION) -C $(PATH_BASEBAND_TX) $(TARGET_APPLICATION).elf: always_check @$(MAKE) -s -e GIT_REVISION=$(GIT_REVISION) -C $(PATH_APPLICATION) diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 38c2c6f7..2f5790a0 100755 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -149,6 +149,7 @@ CPPSRC = main.cpp \ encoder.cpp \ lcd_ili9341.cpp \ ui.cpp \ + ui_alphanum.cpp \ ui_text.cpp \ ui_widget.cpp \ ui_painter.cpp \ @@ -170,6 +171,7 @@ CPPSRC = main.cpp \ ui_console.cpp \ ui_receiver.cpp \ ui_spectrum.cpp \ + ui_loadmodule.cpp \ receiver_model.cpp \ transmitter_model.cpp \ spectrum_color_lut.cpp \ diff --git a/firmware/application/m4_startup.cpp b/firmware/application/m4_startup.cpp index 6b9df47b..5924a1fb 100644 --- a/firmware/application/m4_startup.cpp +++ b/firmware/application/m4_startup.cpp @@ -22,12 +22,14 @@ #include "m4_startup.hpp" #include "hal.h" - +#include "lpc43xx_cpp.hpp" #include "message.hpp" #include "portapack_shared_memory.hpp" #include +char * modhash; + /* TODO: OK, this is cool, but how do I put the M4 to sleep so I can switch to * a different image? Other than asking the old image to sleep while the M0 * makes changes? @@ -37,6 +39,7 @@ */ void m4_init(const portapack::spi_flash::region_t from, const portapack::memory::region_t to) { /* Initialize M4 code RAM */ + // DEBUG std::memcpy(reinterpret_cast(to.base()), from.base(), from.size); /* M4 core is assumed to be sleeping with interrupts off, so we can mess @@ -48,6 +51,58 @@ void m4_init(const portapack::spi_flash::region_t from, const portapack::memory: LPC_RGU->RESET_CTRL[0] = (1 << 13); } +int m4_load_image(void) { + uint32_t mod_size; + UINT bw; + uint8_t i; + char md5sum[16]; + FILINFO modinfo; + FIL modfile; + DIR rootdir; + FRESULT res; + + // Scan SD card root directory for files with the right md5 fingerprint at the right location + f_opendir(&rootdir, "/"); + for (;;) { + res = f_readdir(&rootdir, &modinfo); + if (res != FR_OK || modinfo.fname[0] == 0) break; + if (!(modinfo.fattrib & AM_DIR)) { + f_open(&modfile, modinfo.fname, FA_OPEN_EXISTING | FA_READ); + f_lseek(&modfile, 26); + f_read(&modfile, &md5sum, 16, &bw); + for (i = 0; i < 16; i++) { + if (md5sum[i] != modhash[i]) break; + } + if (i == 16) { + f_lseek(&modfile, 6); + f_read(&modfile, &mod_size, 4, &bw); + f_lseek(&modfile, 256); + f_read(&modfile, reinterpret_cast(portapack::memory::map::m4_code.base()), mod_size, &bw); + LPC_RGU->RESET_CTRL[0] = (1 << 13); + f_close(&modfile); + return 1; + } + f_close(&modfile); + } + } + + return 0; +} + +void m4_switch(const char * hash) { + modhash = const_cast(hash); + + // Ask M4 to enter loop in RAM + BasebandConfiguration baseband_switch { + .mode = 0xFF, + .sampling_rate = 0, + .decimation_factor = 1, + }; + + BasebandConfigurationMessage message { baseband_switch }; + shared_memory.baseband_queue.push(message); +} + void m4_request_shutdown() { ShutdownMessage shutdown_message; shared_memory.baseband_queue.push(shutdown_message); diff --git a/firmware/application/m4_startup.hpp b/firmware/application/m4_startup.hpp index ce22770e..c158e3a8 100644 --- a/firmware/application/m4_startup.hpp +++ b/firmware/application/m4_startup.hpp @@ -24,10 +24,14 @@ #include +#include "ff.h" #include "memory_map.hpp" #include "spi_image.hpp" +#include "ui_navigation.hpp" void m4_init(const portapack::spi_flash::region_t from, const portapack::memory::region_t to); void m4_request_shutdown(); +void m4_switch(const char * hash); +int m4_load_image(void); #endif/*__M4_STARTUP_H__*/ diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index ef3301e6..b1923558 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -19,8 +19,12 @@ * Boston, MA 02110-1301, USA. */ -//Reims ANFR 822519 -//TODO: UC/LC update buttons in keyboard view +//TODO: Reset baseband if module not found (instead of lockup in RAM loop) +//TODO: Module name/filename in modules.hpp to indicate requirement in case it's not found +//TODO: LCD backlight PWM +//TODO: BUG: Crash after TX stop +//TODO: Check bw setting in LCR TX +//TODO: BUG: Crash after PSN entry in RDS TX //TODO: Dynamically load baseband code depending on mode (disable M4 & interrupts, load, reset) //TODO: Bodet :) //TODO: Whistler @@ -29,14 +33,8 @@ //TODO: Persistent playdead ! //TODO: LCR EC=A,J,N //TODO: LCR full message former (see norm) -//TODO: See if receive still works -//TODO: LCR repeats -//TODO: LCR shared memory semaphore for doing/done //TODO: LCR address scan -//TODO: LCR text showing status in LCRView //TODO: AFSK NRZI -//TODO: AFSK volume -//TODO: AFSK channel bandwidth //TODO: TX power #include "ch.h" diff --git a/firmware/application/modules.h b/firmware/application/modules.h new file mode 100644 index 00000000..d9c11546 --- /dev/null +++ b/firmware/application/modules.h @@ -0,0 +1,2 @@ +const char md5_baseband[16] = {0xca,0x05,0xc3,0xbf,0x78,0x10,0xad,0xac,0x2a,0x2b,0x31,0x19,0xf9,0xe8,0x91,0x26,}; +const char md5_baseband_tx[16] = {0xe7,0x28,0x33,0x67,0x45,0x37,0x1e,0x13,0x3c,0x85,0xb5,0x91,0x51,0xaa,0xed,0x4f,}; diff --git a/firmware/application/ui_alphanum.cpp b/firmware/application/ui_alphanum.cpp new file mode 100644 index 00000000..9c433868 --- /dev/null +++ b/firmware/application/ui_alphanum.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_alphanum.hpp" + +#include "ch.h" + +#include "ff.h" +#include "portapack.hpp" +#include "radio.hpp" + +#include "hackrf_hal.hpp" +#include "portapack_shared_memory.hpp" + +#include + +using namespace hackrf::one; + +namespace ui { + +AlphanumView::AlphanumView( + NavigationView& nav, + char txt[], + uint8_t max_len +) { + _max_len = max_len; + _lowercase = false; + + static constexpr Style style_alpha { + .font = font::fixed_8x16, + .background = Color::red(), + .foreground = Color::black(), + }; + + static constexpr Style style_num { + .font = font::fixed_8x16, + .background = Color::yellow(), + .foreground = Color::black(), + }; + + txtidx = 0; + memcpy(txtinput, txt, max_len+1); + + add_child(&text_input); + + const auto button_fn = [this](Button& button) { + this->on_button(button); + }; + + size_t n = 0; + for(auto& button : buttons) { + add_child(&button); + button.on_select = button_fn; + button.set_parent_rect({ + static_cast((n % 5) * button_w), + static_cast((n / 5) * button_h + 18), + button_w, button_h + }); + if ((n < 10) || (n == 39)) + button.set_style(&style_num); + else + button.set_style(&style_alpha); + n++; + } + set_uppercase(); + + add_child(&button_lowercase); + button_lowercase.on_select = [this, &nav, txt, max_len](Button&) { + if (_lowercase == true) { + _lowercase = false; + button_lowercase.set_text("UC"); + set_uppercase(); + } else { + _lowercase = true; + button_lowercase.set_text("LC"); + set_lowercase(); + } + }; + + add_child(&button_done); + button_done.on_select = [this, &nav, txt, max_len](Button&) { + memcpy(txt, txtinput, max_len+1); + on_changed(this->value()); + nav.pop(); + }; + + update_text(); +} + +void AlphanumView::set_uppercase() { + const char* const key_caps = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ. !<"; + + size_t n = 0; + for(auto& button : buttons) { + add_child(&button); + const std::string label { + key_caps[n] + }; + button.set_text(label); + n++; + } +} + + +void AlphanumView::set_lowercase() { + const char* const key_caps = "0123456789abcdefghijklmnopqrstuvwxyz. !<"; + + size_t n = 0; + for(auto& button : buttons) { + add_child(&button); + const std::string label { + key_caps[n] + }; + button.set_text(label); + n++; + } +} + +void AlphanumView::focus() { + button_done.focus(); +} + +char * AlphanumView::value() { + return txtinput; +} + +void AlphanumView::on_button(Button& button) { + const auto s = button.text(); + if( s == "<" ) { + char_delete(); + } else { + char_add(s[0]); + } + update_text(); +} + +void AlphanumView::char_add(const char c) { + if (txtidx < _max_len) { + txtinput[txtidx] = c; + txtidx++; + } +} + +void AlphanumView::char_delete() { + if (txtidx) { + txtidx--; + txtinput[txtidx] = ' '; + } +} + +void AlphanumView::update_text() { + text_input.set(txtinput); +} + +} diff --git a/firmware/application/ui_alphanum.hpp b/firmware/application/ui_alphanum.hpp new file mode 100644 index 00000000..1b44dab8 --- /dev/null +++ b/firmware/application/ui_alphanum.hpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui.hpp" +#include "ui_widget.hpp" +#include "ui_painter.hpp" +#include "ui_menu.hpp" +#include "ui_navigation.hpp" +#include "ui_font_fixed_8x16.hpp" +#include "clock_manager.hpp" +#include "message.hpp" +#include "rf_path.hpp" +#include "max2837.hpp" +#include "volume.hpp" +#include "transmitter_model.hpp" + +namespace ui { + +class AlphanumView : public View { +public: + std::function on_changed; + + AlphanumView(NavigationView& nav, char txt[], uint8_t max_len); + + void focus() override; + + char * value(); + + uint8_t txtidx; + + void char_add(const char c); + void char_delete(); + +private: + uint8_t _max_len; + bool _lowercase = false; + static constexpr size_t button_w = 240 / 5; + static constexpr size_t button_h = 28; + char txtinput[9]; + + void set_lowercase(); + void set_uppercase(); + + Text text_input { + { 88, 0, 240, 16 } + }; + + std::array buttons; + + Button button_lowercase { + { 88+64+16, 270, 32, 24 }, + "UC" + }; + + Button button_done { + { 88, 270, 64, 24 }, + "Done" + }; + + void on_button(Button& button); + + void update_text(); +}; + +} /* namespace ui */ diff --git a/firmware/application/ui_debug.cpp b/firmware/application/ui_debug.cpp index 42952a46..11cdad55 100644 --- a/firmware/application/ui_debug.cpp +++ b/firmware/application/ui_debug.cpp @@ -144,10 +144,30 @@ void DebugSDView::paint(Painter& painter) { DebugSDView::DebugSDView(NavigationView& nav) { add_children({ { &text_title, + &text_modules, &button_makefile, &button_done } }); + FIL fdst; + char buffer[256]; + uint8_t mods_version, mods_count; + UINT bw; + + const auto open_result = f_open(&fdst, "ppmods.bin", FA_OPEN_EXISTING | FA_READ); + if (open_result == FR_OK) { + f_read(&fdst, &mods_version, 1, &bw); + if (mods_version == 1) { + f_read(&fdst, &mods_count, 1, &bw); + f_read(&fdst, buffer, 8, &bw); + f_read(&fdst, buffer, 16, &bw); + buffer[16] = 0; + text_modules.set(buffer); + } else { + text_modules.set("Bad version"); + } + } + button_makefile.on_select = [this](Button&){ FATFS fs; /* Work area (file system object) for logical drives */ FIL fdst; /* File objects */ diff --git a/firmware/application/ui_debug.hpp b/firmware/application/ui_debug.hpp index fe165880..41aad942 100644 --- a/firmware/application/ui_debug.hpp +++ b/firmware/application/ui_debug.hpp @@ -155,7 +155,12 @@ public: private: Text text_title { { 32, 16, 128, 16 }, - "SD card debug", + "SD card debug" + }; + + Text text_modules { + { 8, 32, 28 * 8, 16 }, + "TESTTESTTESTTESTTESTTESTTEST" }; Button button_makefile { diff --git a/firmware/application/ui_lcr.cpp b/firmware/application/ui_lcr.cpp index d2e07780..f086a7fd 100644 --- a/firmware/application/ui_lcr.cpp +++ b/firmware/application/ui_lcr.cpp @@ -192,7 +192,8 @@ LCRView::LCRView( memset(litteral, 0, 5*8); memset(rgsb, 0, 5); - strcpy(rgsb, RGSB_list[0]); + strcpy(rgsb, RGSB_list[29]); + button_setrgsb.set_text(rgsb); add_children({ { &text_recap, @@ -282,10 +283,10 @@ LCRView::LCRView( make_frame(); shared_memory.afsk_samples_per_bit = 228000/portapack::persistent_memory::afsk_bitrate(); - shared_memory.afsk_phase_inc_mark = portapack::persistent_memory::afsk_mark_freq()*(65536*1024)/2280; - shared_memory.afsk_phase_inc_space = portapack::persistent_memory::afsk_space_freq()*(65536*1024)/2280; + shared_memory.afsk_phase_inc_mark = portapack::persistent_memory::afsk_mark_freq()*(0x10000*256)/2280; + shared_memory.afsk_phase_inc_space = portapack::persistent_memory::afsk_space_freq()*(0x10000*256)/2280; - shared_memory.afsk_fmmod = portapack::persistent_memory::afsk_bw()*33; + shared_memory.afsk_fmmod = portapack::persistent_memory::afsk_bw() * 8; memset(shared_memory.lcrdata, 0, 256); memcpy(shared_memory.lcrdata, lcrframe_f, 256); @@ -297,8 +298,10 @@ LCRView::LCRView( [this,&transmitter_model](Message* const p) { const auto message = static_cast(p); if (message->n > 0) { - char str[8] = "0... "; - str[0] = hexify(message->n); + char str[8]; + strcpy(str, to_string_dec_int(message->n).c_str()); + strcat(str, "/"); + strcat(str, to_string_dec_int(shared_memory.afsk_repeat).c_str()); text_status.set(str); } else { text_status.set("Done ! "); @@ -307,7 +310,10 @@ LCRView::LCRView( } ); - text_status.set("0... "); + char str[8]; + strcpy(str, "0/"); + strcat(str, to_string_dec_int(shared_memory.afsk_repeat).c_str()); + text_status.set(str); transmitter_model.enable(); }; diff --git a/firmware/application/ui_loadmodule.cpp b/firmware/application/ui_loadmodule.cpp new file mode 100644 index 00000000..a49ab463 --- /dev/null +++ b/firmware/application/ui_loadmodule.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_loadmodule.hpp" + +#include "ch.h" + +#include "ff.h" +#include "hackrf_gpio.hpp" +#include "portapack.hpp" + +#include "hackrf_hal.hpp" + +#include +#include + +using namespace hackrf::one; + +namespace ui { + +void LoadModuleView::focus() { + button_ok.focus(); +} + +void LoadModuleView::paint(Painter& painter) { + +} + +void LoadModuleView::on_hide() { + auto& message_map = context().message_map(); + message_map.unregister_handler(Message::ID::ReadyForSwitch); +} + +void LoadModuleView::on_show() { + auto& message_map = context().message_map(); + message_map.register_handler(Message::ID::ReadyForSwitch, + [this](Message* const p) { + const auto message = static_cast(p); + if (m4_load_image()) { + text_info.set("Module loaded :)"); + _mod_loaded = true; + } else { + text_info.set("Module not found :("); + _mod_loaded = false; + } + } + ); + + m4_switch(_hash); +} + +LoadModuleView::LoadModuleView( + NavigationView& nav, + const char * hash, + View* new_view +) +{ + add_children({ { + &text_info, + &button_ok + } }); + + _hash = hash; + + button_ok.on_select = [this,&nav,new_view](Button&){ + nav.pop(); + if (_mod_loaded == true) nav.push(new_view); + }; +} + +} /* namespace ui */ diff --git a/firmware/application/ui_loadmodule.hpp b/firmware/application/ui_loadmodule.hpp new file mode 100644 index 00000000..f3bc99c3 --- /dev/null +++ b/firmware/application/ui_loadmodule.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui.hpp" +#include "ui_widget.hpp" +#include "ui_painter.hpp" +#include "ui_menu.hpp" +#include "ui_navigation.hpp" +#include "m4_startup.hpp" +#include "ui_font_fixed_8x16.hpp" + +namespace ui { + +class LoadModuleView : public View { +public: + LoadModuleView(NavigationView& nav, const char * hash, View* new_view); + + void on_show() override; + void on_hide() override; + void focus() override; + void paint(Painter& painter) override; + +private: + const char * _hash; + bool _mod_loaded = false; + + Text text_info { + { 8, 64, 224, 16 }, + "Searching module..." + }; + + Button button_ok { + { 88, 128, 64, 32 }, + "OK" + }; +}; + +} /* namespace ui */ diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 7713d027..67141ddf 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -35,10 +35,14 @@ #include "ui_lcr.hpp" #include "ui_whistle.hpp" #include "ui_jammer.hpp" +#include "ui_loadmodule.hpp" #include "portapack.hpp" #include "m4_startup.hpp" #include "spi_image.hpp" + +#include "modules.h" + using namespace portapack; namespace ui { @@ -104,17 +108,20 @@ void NavigationView::focus() { /* SystemMenuView ********************************************************/ SystemMenuView::SystemMenuView(NavigationView& nav) { - add_items<10>({ { - { "Play dead", ui::Color::red(), [&nav](){ nav.push(new PlayDeadView { nav, false }); } }, - { "Receiver", ui::Color::white(), [&nav](){ nav.push(new ReceiverView { nav, receiver_model }); } }, - { "Jammer", ui::Color::white(), [&nav](){ nav.push(new JammerView { nav, transmitter_model }); } }, - { "Whistle", ui::Color::white(), [&nav](){ nav.push(new WhistleView { nav, transmitter_model }); } }, - { "RDS TX", ui::Color::yellow(), [&nav](){ nav.push(new RDSView { nav, transmitter_model }); } }, - { "LCR TX", ui::Color::orange(), [&nav](){ nav.push(new LCRView { nav, transmitter_model }); } }, - { "Setup", ui::Color::white(), [&nav](){ nav.push(new SetupMenuView { nav }); } }, - { "About", ui::Color::white(), [&nav](){ nav.push(new AboutView { nav }); } }, - { "Debug", ui::Color::white(), [&nav](){ nav.push(new DebugMenuView { nav }); } }, - { "HackRF", ui::Color::white(), [&nav](){ nav.push(new HackRFFirmwareView { nav }); } }, + add_items<11>({ { + { "Play dead", ui::Color::red(), [&nav](){ nav.push(new PlayDeadView { nav, false }); } }, + { "Receiver", ui::Color::cyan(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new ReceiverView { nav, receiver_model } }); } }, + //{ "Nordic/BTLE RX", ui::Color::cyan(), [&nav](){ nav.push(new NotImplementedView { nav }); } }, + { "Jammer", ui::Color::white(), [&nav](){ nav.push(new JammerView { nav, transmitter_model }); } }, + { "Audio file TX", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } }, + //{ "Encoder TX", ui::Color::green(), [&nav](){ nav.push(new NotImplementedView { nav }); } }, + { "Whistle", ui::Color::purple(), [&nav](){ nav.push(new WhistleView { nav, transmitter_model }); } }, + { "RDS TX", ui::Color::yellow(), [&nav](){ nav.push(new RDSView { nav, transmitter_model }); } }, + { "TEDI/LCR TX", ui::Color::orange(), [&nav](){ nav.push(new LCRView { nav, transmitter_model }); } }, + { "Setup", ui::Color::white(), [&nav](){ nav.push(new SetupMenuView { nav }); } }, + { "About", ui::Color::white(), [&nav](){ nav.push(new AboutView { nav }); } }, + { "Debug", ui::Color::white(), [&nav](){ nav.push(new DebugMenuView { nav }); } }, + { "HackRF", ui::Color::white(), [&nav](){ nav.push(new HackRFFirmwareView { nav }); } }, } }); } diff --git a/firmware/application/ui_rds.cpp b/firmware/application/ui_rds.cpp index ae2ac33f..86a65903 100644 --- a/firmware/application/ui_rds.cpp +++ b/firmware/application/ui_rds.cpp @@ -36,140 +36,6 @@ using namespace hackrf::one; namespace ui { - -AlphanumView::AlphanumView( - NavigationView& nav, - char txt[], - uint8_t max_len -) { - _max_len = max_len; - _lowercase = false; - - static constexpr Style style_alpha { - .font = font::fixed_8x16, - .background = Color::red(), - .foreground = Color::black(), - }; - - static constexpr Style style_num { - .font = font::fixed_8x16, - .background = Color::yellow(), - .foreground = Color::black(), - }; - - txtidx = 0; - memcpy(txtinput, txt, max_len+1); - - add_child(&text_input); - - const auto button_fn = [this](Button& button) { - this->on_button(button); - }; - - size_t n = 0; - for(auto& button : buttons) { - add_child(&button); - button.on_select = button_fn; - button.set_parent_rect({ - static_cast((n % 5) * button_w), - static_cast((n / 5) * button_h + 18), - button_w, button_h - }); - if ((n < 10) || (n == 39)) - button.set_style(&style_num); - else - button.set_style(&style_alpha); - n++; - } - set_uppercase(); - - add_child(&button_lowercase); - button_lowercase.on_select = [this, &nav, txt, max_len](Button&) { - if (_lowercase == true) { - _lowercase = false; - button_lowercase.set_text("UC"); - set_uppercase(); - } else { - _lowercase = true; - button_lowercase.set_text("LC"); - set_lowercase(); - } - }; - - add_child(&button_done); - button_done.on_select = [this, &nav, txt, max_len](Button&) { - memcpy(txt, txtinput, max_len+1); - on_changed(this->value()); - nav.pop(); - }; - - update_text(); -} - -void AlphanumView::set_uppercase() { - const char* const key_caps = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ. !<"; - - size_t n = 0; - for(auto& button : buttons) { - add_child(&button); - const std::string label { - key_caps[n] - }; - button.set_text(label); - n++; - } -} - - -void AlphanumView::set_lowercase() { - const char* const key_caps = "0123456789abcdefghijklmnopqrstuvwxyz. !<"; - - size_t n = 0; - for(auto& button : buttons) { - add_child(&button); - const std::string label { - key_caps[n] - }; - button.set_text(label); - n++; - } -} - -void AlphanumView::focus() { - button_done.focus(); -} - -char * AlphanumView::value() { - return txtinput; -} - -void AlphanumView::on_button(Button& button) { - const auto s = button.text(); - if( s == "<" ) { - char_delete(); - } else { - char_add(s[0]); - } - update_text(); -} - -void AlphanumView::char_add(const char c) { - if (txtidx < _max_len) { - txtinput[txtidx] = c; - txtidx++; - } -} - -void AlphanumView::char_delete() { - if (txtidx) { - txtidx--; - txtinput[txtidx] = ' '; - } -} - -void AlphanumView::update_text() { - text_input.set(txtinput); -} void RDSView::focus() { button_setpsn.focus(); diff --git a/firmware/application/ui_receiver.cpp b/firmware/application/ui_receiver.cpp index a4d85698..41ba3251 100644 --- a/firmware/application/ui_receiver.cpp +++ b/firmware/application/ui_receiver.cpp @@ -24,12 +24,13 @@ #include "ui_spectrum.hpp" #include "ui_console.hpp" +#include "ff.h" + #include "portapack.hpp" using namespace portapack; #include "ais_baseband.hpp" - -#include "ff.h" +#include "m4_startup.hpp" namespace ui { diff --git a/firmware/baseband-tx.bin b/firmware/baseband-tx.bin new file mode 100644 index 0000000000000000000000000000000000000000..8b80d410f606359fdd11fca33c0918f04e16857e GIT binary patch literal 20608 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 +#include +#include + +#include "hal.h" +#include "gpdma.hpp" + +using namespace lpc43xx; + +#include "portapack_dma.hpp" + +namespace audio { +namespace dma { + +constexpr uint32_t gpdma_ahb_master_peripheral = 1; +constexpr uint32_t gpdma_ahb_master_memory = 0; +constexpr uint32_t gpdma_ahb_master_lli_fetch = 0; + +constexpr uint32_t gpdma_rx_peripheral = 0x9; /* I2S0 DMA request 1 */ +constexpr uint32_t gpdma_rx_src_peripheral = gpdma_rx_peripheral; +constexpr uint32_t gpdma_rx_dest_peripheral = gpdma_rx_peripheral; + +constexpr uint32_t gpdma_tx_peripheral = 0xa; /* I2S0 DMA request 2 */ +constexpr uint32_t gpdma_tx_src_peripheral = gpdma_tx_peripheral; +constexpr uint32_t gpdma_tx_dest_peripheral = gpdma_tx_peripheral; + +constexpr gpdma::channel::LLIPointer lli_pointer(const void* lli) { + return { + .lm = gpdma_ahb_master_lli_fetch, + .r = 0, + .lli = reinterpret_cast(lli), + }; +} + +constexpr gpdma::channel::Control control_tx(const size_t transfer_bytes) { + return { + .transfersize = gpdma::buffer_words(transfer_bytes, 4), + .sbsize = 4, /* Burst size: 32 */ + .dbsize = 4, /* Burst size: 32 */ + .swidth = 2, /* Source transfer width: word (32 bits) */ + .dwidth = 2, /* Destination transfer width: word (32 bits) */ + .s = gpdma_ahb_master_memory, + .d = gpdma_ahb_master_peripheral, + .si = 1, + .di = 0, + .prot1 = 0, + .prot2 = 0, + .prot3 = 0, + .i = 1, + }; +} + +constexpr gpdma::channel::Config config_tx() { + return { + .e = 0, + .srcperipheral = gpdma_tx_src_peripheral, + .destperipheral = gpdma_tx_dest_peripheral, + .flowcntrl = gpdma::FlowControl::MemoryToPeripheral_DMAControl, + .ie = 1, + .itc = 1, + .l = 0, + .a = 0, + .h = 0, + }; +} + +constexpr gpdma::channel::Control control_rx(const size_t transfer_bytes) { + return { + .transfersize = gpdma::buffer_words(transfer_bytes, 4), + .sbsize = 4, /* Burst size: 32 */ + .dbsize = 4, /* Burst size: 32 */ + .swidth = 2, /* Source transfer width: word (32 bits) */ + .dwidth = 2, /* Destination transfer width: word (32 bits) */ + .s = gpdma_ahb_master_peripheral, + .d = gpdma_ahb_master_memory, + .si = 0, + .di = 1, + .prot1 = 0, + .prot2 = 0, + .prot3 = 0, + .i = 1, + }; +} + +constexpr gpdma::channel::Config config_rx() { + return { + .e = 0, + .srcperipheral = gpdma_rx_src_peripheral, + .destperipheral = gpdma_rx_dest_peripheral, + .flowcntrl = gpdma::FlowControl::PeripheralToMemory_DMAControl, + .ie = 1, + .itc = 1, + .l = 0, + .a = 0, + .h = 0, + }; +} + +/* TODO: Clean up terminology around "buffer", "transfer", "samples" */ + +constexpr size_t buffer_samples_log2n = 7; +constexpr size_t buffer_samples = (1 << buffer_samples_log2n); +constexpr size_t transfers_per_buffer_log2n = 2; +constexpr size_t transfers_per_buffer = (1 << transfers_per_buffer_log2n); +constexpr size_t transfer_samples = buffer_samples / transfers_per_buffer; +constexpr size_t transfers_mask = transfers_per_buffer - 1; + +constexpr size_t buffer_bytes = buffer_samples * sizeof(sample_t); +constexpr size_t transfer_bytes = transfer_samples * sizeof(sample_t); + +static std::array buffer_tx; +static std::array buffer_rx; + +static std::array lli_tx_loop; +static std::array lli_rx_loop; + +static constexpr auto& gpdma_channel_i2s0_tx = gpdma::channels[portapack::i2s0_tx_gpdma_channel_number]; +static constexpr auto& gpdma_channel_i2s0_rx = gpdma::channels[portapack::i2s0_rx_gpdma_channel_number]; + +static volatile const gpdma::channel::LLI* tx_next_lli = nullptr; +static volatile const gpdma::channel::LLI* rx_next_lli = nullptr; + +static void tx_transfer_complete() { + tx_next_lli = gpdma_channel_i2s0_tx.next_lli(); +} + +static void tx_error() { + disable(); +} + +static void rx_transfer_complete() { + rx_next_lli = gpdma_channel_i2s0_rx.next_lli(); +} + +static void rx_error() { + disable(); +} + +void init() { + gpdma_channel_i2s0_tx.set_handlers(tx_transfer_complete, tx_error); + gpdma_channel_i2s0_rx.set_handlers(rx_transfer_complete, rx_error); + + // LPC_GPDMA->SYNC |= (1 << gpdma_rx_peripheral); + // LPC_GPDMA->SYNC |= (1 << gpdma_tx_peripheral); +} + +static void configure_tx() { + const auto peripheral = reinterpret_cast(&LPC_I2S0->TXFIFO); + const auto control_value = control_tx(transfer_bytes); + for(size_t i=0; i(&buffer_tx[i * transfer_samples]); + lli_tx_loop[i].srcaddr = memory; + lli_tx_loop[i].destaddr = peripheral; + lli_tx_loop[i].lli = lli_pointer(&lli_tx_loop[(i + 1) % lli_tx_loop.size()]); + lli_tx_loop[i].control = control_value; + } +} + +static void configure_rx() { + const auto peripheral = reinterpret_cast(&LPC_I2S0->RXFIFO); + const auto control_value = control_rx(transfer_bytes); + for(size_t i=0; i(&buffer_rx[i * transfer_samples]); + lli_rx_loop[i].srcaddr = peripheral; + lli_rx_loop[i].destaddr = memory; + lli_rx_loop[i].lli = lli_pointer(&lli_rx_loop[(i + 1) % lli_rx_loop.size()]); + lli_rx_loop[i].control = control_value; + } +} + +void configure() { + configure_tx(); + configure_rx(); +} + +void enable() { + const auto gpdma_config_tx = config_tx(); + const auto gpdma_config_rx = config_rx(); + + gpdma_channel_i2s0_tx.configure(lli_tx_loop[0], gpdma_config_tx); + gpdma_channel_i2s0_rx.configure(lli_rx_loop[0], gpdma_config_rx); + + gpdma_channel_i2s0_tx.enable(); + gpdma_channel_i2s0_rx.enable(); +} + +void disable() { + gpdma_channel_i2s0_tx.disable_force(); + gpdma_channel_i2s0_rx.disable_force(); +} + +buffer_t tx_empty_buffer() { + const auto next_lli = tx_next_lli; + if( next_lli ) { + const size_t next_index = next_lli - &lli_tx_loop[0]; + const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask; + return { reinterpret_cast(lli_tx_loop[free_index].srcaddr), transfer_samples }; + } else { + return { nullptr, 0 }; + } +} + +buffer_t rx_empty_buffer() { + const auto next_lli = rx_next_lli; + if( next_lli ) { + const size_t next_index = next_lli - &lli_rx_loop[0]; + const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask; + return { reinterpret_cast(lli_rx_loop[free_index].srcaddr), transfer_samples }; + } else { + return { nullptr, 0 }; + } +} + +} /* namespace dma */ +} /* namespace audio */ diff --git a/firmware/baseband-tx/audio_dma.hpp b/firmware/baseband-tx/audio_dma.hpp new file mode 100644 index 00000000..ec85f080 --- /dev/null +++ b/firmware/baseband-tx/audio_dma.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __AUDIO_DMA_H__ +#define __AUDIO_DMA_H__ + +#include + +#include "audio.hpp" + +namespace audio { +namespace dma { + +void init(); +void configure(); +void enable(); +void disable(); + +audio::buffer_t tx_empty_buffer(); +audio::buffer_t rx_empty_buffer(); + +} /* namespace dma */ +} /* namespace audio */ + +#endif/*__AUDIO_DMA_H__*/ diff --git a/firmware/baseband-tx/audio_stats_collector.hpp b/firmware/baseband-tx/audio_stats_collector.hpp new file mode 100644 index 00000000..189fff80 --- /dev/null +++ b/firmware/baseband-tx/audio_stats_collector.hpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __AUDIO_STATS_COLLECTOR_H__ +#define __AUDIO_STATS_COLLECTOR_H__ + +#include "buffer.hpp" +#include "message.hpp" +#include "utility.hpp" + +#include +#include + +class AudioStatsCollector { +public: + template + void feed(buffer_s16_t src, Callback callback) { + consume_audio_buffer(src); + + if( update_stats(src.count, src.sampling_rate) ) { + callback(statistics); + } + } + + template + void mute(const size_t sample_count, const size_t sampling_rate, Callback callback) { + if( update_stats(sample_count, sampling_rate) ) { + callback(statistics); + } + } + +private: + static constexpr float update_interval { 0.1f }; + uint64_t squared_sum { 0 }; + uint32_t max_squared { 0 }; + size_t count { 0 }; + + AudioStatistics statistics; + + void consume_audio_buffer(buffer_s16_t src) { + auto src_p = src.p; + const auto src_end = &src.p[src.count]; + while(src_p < src_end) { + const auto sample = *(src_p++); + const uint64_t sample_squared = sample * sample; + squared_sum += sample_squared; + if( sample_squared > max_squared ) { + max_squared = sample_squared; + } + } + } + + bool update_stats(const size_t sample_count, const size_t sampling_rate) { + count += sample_count; + + const size_t samples_per_update = sampling_rate * update_interval; + + if( count >= samples_per_update ) { + const float squared_sum_f = squared_sum; + const float max_squared_f = max_squared; + const float squared_avg_f = squared_sum_f / count; + statistics.rms_db = complex16_mag_squared_to_dbv_norm(squared_avg_f); + statistics.max_db = complex16_mag_squared_to_dbv_norm(max_squared_f); + statistics.count = count; + + squared_sum = 0; + max_squared = 0; + count = 0; + + return true; + } else { + return false; + } + } +}; + +#endif/*__AUDIO_STATS_COLLECTOR_H__*/ diff --git a/firmware/baseband-tx/baseband_dma.cpp b/firmware/baseband-tx/baseband_dma.cpp new file mode 100644 index 00000000..aa119dc6 --- /dev/null +++ b/firmware/baseband-tx/baseband_dma.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "baseband_dma.hpp" +#include "portapack_shared_memory.hpp" + +#include +#include +#include + +#include "hal.h" +#include "gpdma.hpp" + +using namespace lpc43xx; + +#include "portapack_dma.hpp" + +namespace baseband { +namespace dma { + + int quitt = 0; + +constexpr uint32_t gpdma_ahb_master_sgpio = 0; +constexpr uint32_t gpdma_ahb_master_memory = 1; +constexpr uint32_t gpdma_ahb_master_lli_fetch = 0; + +constexpr uint32_t gpdma_src_peripheral = 0x0; +constexpr uint32_t gpdma_dest_peripheral = 0x0; + +constexpr gpdma::channel::LLIPointer lli_pointer(const void* lli) { + return { + .lm = gpdma_ahb_master_lli_fetch, + .r = 0, + .lli = reinterpret_cast(lli), + }; +} + +constexpr gpdma::channel::Control control(const baseband::Direction direction, const size_t buffer_words) { + return { + .transfersize = buffer_words, + .sbsize = 0, /* Burst size: 1 */ + .dbsize = 0, /* Burst size: 1 */ + .swidth = 2, /* Source transfer width: word (32 bits) */ + .dwidth = 2, /* Destination transfer width: word (32 bits) */ + .s = (direction == baseband::Direction::Transmit) ? gpdma_ahb_master_memory : gpdma_ahb_master_sgpio, + .d = (direction == baseband::Direction::Transmit) ? gpdma_ahb_master_sgpio : gpdma_ahb_master_memory, + .si = (direction == baseband::Direction::Transmit) ? 1U : 0U, + .di = (direction == baseband::Direction::Transmit) ? 0U : 1U, + .prot1 = 0, + .prot2 = 0, + .prot3 = 0, + .i = 1, + }; +} + +constexpr gpdma::channel::Config config(const baseband::Direction direction) { + return { + .e = 0, + .srcperipheral = gpdma_src_peripheral, + .destperipheral = gpdma_dest_peripheral, + .flowcntrl = (direction == baseband::Direction::Transmit) + ? gpdma::FlowControl::MemoryToPeripheral_DMAControl + : gpdma::FlowControl::PeripheralToMemory_DMAControl, + .ie = 1, + .itc = 1, + .l = 0, + .a = 0, + .h = 0, + }; +} + +constexpr size_t buffer_samples_log2n = 13; +constexpr size_t buffer_samples = (1 << buffer_samples_log2n); +constexpr size_t transfers_per_buffer_log2n = 2; +constexpr size_t transfers_per_buffer = (1 << transfers_per_buffer_log2n); +constexpr size_t transfer_samples = buffer_samples / transfers_per_buffer; +constexpr size_t transfers_mask = transfers_per_buffer - 1; + +constexpr size_t buffer_bytes = buffer_samples * sizeof(baseband::sample_t); +constexpr size_t transfer_bytes = transfer_samples * sizeof(baseband::sample_t); + +constexpr size_t msg_count = transfers_per_buffer - 1; + +static std::array lli_loop; +static constexpr auto& gpdma_channel_sgpio = gpdma::channels[portapack::sgpio_gpdma_channel_number]; + +//static Mailbox mailbox; +//static std::array messages; +static Semaphore semaphore; + +static volatile const gpdma::channel::LLI* next_lli = nullptr; + +void transfer_complete() { + next_lli = gpdma_channel_sgpio.next_lli(); + quitt = 0; + /* TODO: Is Mailbox the proper synchronization mechanism for this? */ + //chMBPostI(&mailbox, 0); + chSemSignalI(&semaphore); +} + +static void dma_error() { + disable(); +} + +void init() { + //chMBInit(&mailbox, messages.data(), messages.size()); + chSemInit(&semaphore, 0); + gpdma_channel_sgpio.set_handlers(transfer_complete, dma_error); + + // LPC_GPDMA->SYNC |= (1 << gpdma_src_peripheral); + // LPC_GPDMA->SYNC |= (1 << gpdma_dest_peripheral); +} + +void configure( + baseband::sample_t* const buffer_base, + const baseband::Direction direction +) { + const auto peripheral = reinterpret_cast(&LPC_SGPIO->REG_SS[0]); + const auto control_value = control(direction, gpdma::buffer_words(transfer_bytes, 4)); + for(size_t i=0; i(&buffer_base[i * transfer_samples]); + lli_loop[i].srcaddr = (direction == Direction::Transmit) ? memory : peripheral; + lli_loop[i].destaddr = (direction == Direction::Transmit) ? peripheral : memory; + lli_loop[i].lli = lli_pointer(&lli_loop[(i + 1) % lli_loop.size()]); + lli_loop[i].control = control_value; + } +} + +void enable(const baseband::Direction direction) { + const auto gpdma_config = config(direction); + gpdma_channel_sgpio.configure(lli_loop[0], gpdma_config); + + //chMBReset(&mailbox); + chSemReset(&semaphore, 0); + + gpdma_channel_sgpio.enable(); +} + +bool is_enabled() { + return gpdma_channel_sgpio.is_enabled(); +} + +void disable() { + gpdma_channel_sgpio.disable_force(); +} + +baseband::buffer_t wait_for_rx_buffer() { + //msg_t msg; + //const auto status = chMBFetch(&mailbox, &msg, TIME_INFINITE); + const auto status = chSemWait(&semaphore); + if (quitt) return { nullptr, 0 }; + if( status == RDY_OK ) { + const auto next = next_lli; + if( next ) { + const size_t next_index = next - &lli_loop[0]; + const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask; + return { reinterpret_cast(lli_loop[free_index].destaddr), transfer_samples }; + } else { + return { nullptr, 0 }; + } + } else { + return { nullptr, 0 }; + } +} + +baseband::buffer_t wait_for_tx_buffer() { + //msg_t msg; + //const auto status = chMBFetch(&mailbox, &msg, TIME_INFINITE); + const auto status = chSemWait(&semaphore); + if( status == RDY_OK ) { + const auto next = next_lli; + if( next ) { + const size_t next_index = next - &lli_loop[0]; + const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask; + return { reinterpret_cast(lli_loop[free_index].srcaddr), transfer_samples }; + } else { + return { nullptr, 0 }; + } + } else { + return { nullptr, 0 }; + } +} + +} /* namespace dma */ +} /* namespace baseband */ diff --git a/firmware/baseband-tx/baseband_dma.hpp b/firmware/baseband-tx/baseband_dma.hpp new file mode 100644 index 00000000..99e32de8 --- /dev/null +++ b/firmware/baseband-tx/baseband_dma.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __BASEBAND_DMA_H__ +#define __BASEBAND_DMA_H__ + +#include +#include + +#include "complex.hpp" +#include "baseband.hpp" + +namespace baseband { +namespace dma { + +using Handler = void (*)(); + +void init(); +void configure( + baseband::sample_t* const buffer_base, + const baseband::Direction direction +); + +void enable(const baseband::Direction direction); +bool is_enabled(); + +void disable(); + +baseband::buffer_t wait_for_rx_buffer(); +baseband::buffer_t wait_for_tx_buffer(); + +} /* namespace dma */ +} /* namespace baseband */ + +#endif/*__BASEBAND_DMA_H__*/ diff --git a/firmware/baseband-tx/baseband_processor.cpp b/firmware/baseband-tx/baseband_processor.cpp new file mode 100644 index 00000000..83e6d857 --- /dev/null +++ b/firmware/baseband-tx/baseband_processor.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "baseband_processor.hpp" + +#include "portapack_shared_memory.hpp" + +#include "dsp_fft.hpp" + +#include "audio_dma.hpp" + +#include "message.hpp" +#include "event_m4.hpp" +#include "utility.hpp" + +#include +#include + +void BasebandProcessor::update_spectrum() { + // Called from idle thread (after EVT_MASK_SPECTRUM is flagged) + if( channel_spectrum_request_update ) { + /* Decimated buffer is full. Compute spectrum. */ + channel_spectrum_request_update = false; + fft_c_preswapped(channel_spectrum); + + ChannelSpectrumMessage spectrum_message; + for(size_t i=0; i .magnitude, or something more (less!) accurate. */ + spectrum_message.spectrum.db_count = spectrum_message.spectrum.db.size(); + spectrum_message.spectrum.sampling_rate = channel_spectrum_sampling_rate; + spectrum_message.spectrum.channel_filter_pass_frequency = channel_filter_pass_frequency; + spectrum_message.spectrum.channel_filter_stop_frequency = channel_filter_stop_frequency; + shared_memory.application_queue.push(spectrum_message); + } +} + +void BasebandProcessor::feed_channel_stats(const buffer_c16_t channel) { + channel_stats.feed( + channel, + [this](const ChannelStatistics statistics) { + this->post_channel_stats_message(statistics); + } + ); +} + +void BasebandProcessor::feed_channel_spectrum( + const buffer_c16_t channel, + const uint32_t filter_pass_frequency, + const uint32_t filter_stop_frequency +) { + channel_filter_pass_frequency = filter_pass_frequency; + channel_filter_stop_frequency = filter_stop_frequency; + channel_spectrum_decimator.feed( + channel, + [this](const buffer_c16_t data) { + this->post_channel_spectrum_message(data); + } + ); +} + +void BasebandProcessor::fill_audio_buffer(const buffer_s16_t audio) { + auto audio_buffer = audio::dma::tx_empty_buffer();; + for(size_t i=0; ipost_audio_stats_message(statistics); + } + ); +} + +void BasebandProcessor::post_audio_stats_message(const AudioStatistics statistics) { + audio_stats_message.statistics = statistics; + shared_memory.application_queue.push(audio_stats_message); +} diff --git a/firmware/baseband-tx/baseband_processor.hpp b/firmware/baseband-tx/baseband_processor.hpp new file mode 100644 index 00000000..a1a22889 --- /dev/null +++ b/firmware/baseband-tx/baseband_processor.hpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __BASEBAND_PROCESSOR_H__ +#define __BASEBAND_PROCESSOR_H__ + +#include "dsp_types.hpp" +#include "complex.hpp" + +#include "block_decimator.hpp" +#include "channel_stats_collector.hpp" +#include "audio_stats_collector.hpp" + +#include +#include +#include + +class BasebandProcessor { +public: + virtual ~BasebandProcessor() = default; + + virtual void execute(buffer_c8_t buffer) = 0; + + void update_spectrum(); + +protected: + void feed_channel_stats(const buffer_c16_t channel); + + void feed_channel_spectrum( + const buffer_c16_t channel, + const uint32_t filter_pass_frequency, + const uint32_t filter_stop_frequency + ); + + void fill_audio_buffer(const buffer_s16_t audio); + + volatile bool channel_spectrum_request_update { false }; + std::array, 256> channel_spectrum; + uint32_t channel_spectrum_sampling_rate { 0 }; + uint32_t channel_filter_pass_frequency { 0 }; + uint32_t channel_filter_stop_frequency { 0 }; + +private: + BlockDecimator<256> channel_spectrum_decimator { 4 }; + + ChannelStatsCollector channel_stats; + ChannelStatisticsMessage channel_stats_message; + + AudioStatsCollector audio_stats; + AudioStatisticsMessage audio_stats_message; + + void post_channel_stats_message(const ChannelStatistics statistics); + void post_channel_spectrum_message(const buffer_c16_t data); + void feed_audio_stats(const buffer_s16_t audio); + void post_audio_stats_message(const AudioStatistics statistics); +}; + +#endif/*__BASEBAND_PROCESSOR_H__*/ diff --git a/firmware/baseband-tx/baseband_stats_collector.hpp b/firmware/baseband-tx/baseband_stats_collector.hpp new file mode 100644 index 00000000..a44c2936 --- /dev/null +++ b/firmware/baseband-tx/baseband_stats_collector.hpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __BASEBAND_STATS_COLLECTOR_H__ +#define __BASEBAND_STATS_COLLECTOR_H__ + +#include "ch.h" + +#include "dsp_types.hpp" +#include "message.hpp" +#include "utility_m4.hpp" + +#include +#include + +class BasebandStatsCollector { +public: + BasebandStatsCollector( + const Thread* const thread_idle, + const Thread* const thread_main, + const Thread* const thread_rssi, + const Thread* const thread_baseband + ) : thread_idle { thread_idle }, + thread_main { thread_main }, + thread_rssi { thread_rssi }, + thread_baseband { thread_baseband } + { + } + + template + void process(buffer_c8_t buffer, Callback callback) { + samples += buffer.count; + + const size_t report_samples = buffer.sampling_rate * report_interval; + const auto report_delta = samples - samples_last_report; + if( report_delta >= report_samples ) { + BasebandStatistics statistics; + + const auto idle_ticks = thread_idle->total_ticks; + statistics.idle_ticks = (idle_ticks - last_idle_ticks); + last_idle_ticks = idle_ticks; + + const auto main_ticks = thread_main->total_ticks; + statistics.main_ticks = (main_ticks - last_main_ticks); + last_main_ticks = main_ticks; + + const auto rssi_ticks = thread_rssi->total_ticks; + statistics.rssi_ticks = (rssi_ticks - last_rssi_ticks); + last_rssi_ticks = rssi_ticks; + + const auto baseband_ticks = thread_baseband->total_ticks; + statistics.baseband_ticks = (baseband_ticks - last_baseband_ticks); + last_baseband_ticks = baseband_ticks; + + statistics.saturation = m4_flag_saturation(); + clear_m4_flag_saturation(); + + callback(statistics); + + samples_last_report = samples; + } + } + +private: + static constexpr float report_interval { 1.0f }; + size_t samples { 0 }; + size_t samples_last_report { 0 }; + const Thread* const thread_idle; + uint32_t last_idle_ticks { 0 }; + const Thread* const thread_main; + uint32_t last_main_ticks { 0 }; + const Thread* const thread_rssi; + uint32_t last_rssi_ticks { 0 }; + const Thread* const thread_baseband; + uint32_t last_baseband_ticks { 0 }; +}; + +#endif/*__BASEBAND_STATS_COLLECTOR_H__*/ diff --git a/firmware/baseband-tx/block_decimator.hpp b/firmware/baseband-tx/block_decimator.hpp new file mode 100644 index 00000000..dbca0ebd --- /dev/null +++ b/firmware/baseband-tx/block_decimator.hpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __BLOCK_DECIMATOR_H__ +#define __BLOCK_DECIMATOR_H__ + +#include +#include +#include + +#include "dsp_types.hpp" +#include "complex.hpp" + +template +class BlockDecimator { +public: + constexpr BlockDecimator( + const size_t factor + ) : factor_ { factor } + { + } + + void set_input_sampling_rate(const uint32_t new_sampling_rate) { + if( new_sampling_rate != input_sampling_rate() ) { + input_sampling_rate_ = new_sampling_rate; + reset_state(); + } + } + + uint32_t input_sampling_rate() const { + return input_sampling_rate_; + } + + void set_factor(const size_t new_factor) { + if( new_factor != factor() ) { + factor_ = new_factor; + reset_state(); + } + } + + size_t factor() const { + return factor_; + } + + uint32_t output_sampling_rate() const { + return input_sampling_rate() / factor(); + } + + template + void feed(const buffer_c16_t src, BlockCallback callback) { + /* NOTE: Input block size must be >= factor */ + + set_input_sampling_rate(src.sampling_rate); + + while( src_i < src.count ) { + buffer[dst_i++] = src.p[src_i]; + if( dst_i == buffer.size() ) { + callback({ buffer.data(), buffer.size(), output_sampling_rate() }); + reset_state(); + dst_i = 0; + } + + src_i += factor(); + } + + src_i -= src.count; + } + +private: + std::array buffer; + uint32_t input_sampling_rate_ { 0 }; + size_t factor_ { 1 }; + size_t src_i { 0 }; + size_t dst_i { 0 }; + + void reset_state() { + src_i = 0; + dst_i = 0; + } +}; + +#endif/*__BLOCK_DECIMATOR_H__*/ diff --git a/firmware/baseband-tx/channel_decimator.cpp b/firmware/baseband-tx/channel_decimator.cpp new file mode 100644 index 00000000..a7e7edf7 --- /dev/null +++ b/firmware/baseband-tx/channel_decimator.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "channel_decimator.hpp" + +buffer_c16_t ChannelDecimator::execute_decimation(buffer_c8_t buffer) { + const buffer_c16_t work_baseband_buffer { + work_baseband.data(), + work_baseband.size() + }; + + const buffer_s16_t work_audio_buffer { + (int16_t*)work_baseband.data(), + sizeof(work_baseband) / sizeof(int16_t) + }; + + /* 3.072MHz complex[2048], [-128, 127] + * -> Shift by -fs/4 + * -> 3rd order CIC: -0.1dB @ 0.028fs, -1dB @ 0.088fs, -60dB @ 0.468fs + * -0.1dB @ 86kHz, -1dB @ 270kHz, -60dB @ 1.44MHz + * -> gain of 256 + * -> decimation by 2 + * -> 1.544MHz complex[1024], [-32768, 32512] */ + const auto stage_0_out = translate.execute(buffer, work_baseband_buffer); + + //if( fs_over_4_downconvert ) { + // // TODO: + //} else { + // Won't work until cic_0 will accept input type of buffer_c8_t. + // stage_0_out = cic_0.execute(buffer, work_baseband_buffer); + //} + + /* 1.536MHz complex[1024], [-32768, 32512] + * -> 3rd order CIC: -0.1dB @ 0.028fs, -1dB @ 0.088fs, -60dB @ 0.468fs + * -0.1dB @ 43kHz, -1dB @ 136kHz, -60dB @ 723kHz + * -> gain of 8 + * -> decimation by 2 + * -> 768kHz complex[512], [-8192, 8128] */ + auto cic_1_out = cic_1.execute(stage_0_out, work_baseband_buffer); + if( decimation_factor == DecimationFactor::By4 ) { + return cic_1_out; + } + + /* 768kHz complex[512], [-32768, 32512] + * -> 3rd order CIC decimation by 2, gain of 1 + * -> 384kHz complex[256], [-32768, 32512] */ + auto cic_2_out = cic_2.execute(cic_1_out, work_baseband_buffer); + if( decimation_factor == DecimationFactor::By8 ) { + return cic_2_out; + } + + /* 384kHz complex[256], [-32768, 32512] + * -> 3rd order CIC decimation by 2, gain of 1 + * -> 192kHz complex[128], [-32768, 32512] */ + auto cic_3_out = cic_3.execute(cic_2_out, work_baseband_buffer); + if( decimation_factor == DecimationFactor::By16 ) { + return cic_3_out; + } + + /* 192kHz complex[128], [-32768, 32512] + * -> 3rd order CIC decimation by 2, gain of 1 + * -> 96kHz complex[64], [-32768, 32512] */ + auto cic_4_out = cic_4.execute(cic_3_out, work_baseband_buffer); + + return cic_4_out; +} diff --git a/firmware/baseband-tx/channel_decimator.hpp b/firmware/baseband-tx/channel_decimator.hpp new file mode 100644 index 00000000..3e20c0df --- /dev/null +++ b/firmware/baseband-tx/channel_decimator.hpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __CHANNEL_DECIMATOR_H__ +#define __CHANNEL_DECIMATOR_H__ + +#include "buffer.hpp" +#include "complex.hpp" + +#include "dsp_decimate.hpp" + +#include + +class ChannelDecimator { +public: + enum class DecimationFactor { + By4, + By8, + By16, + By32, + }; + + constexpr ChannelDecimator( + ) : decimation_factor { DecimationFactor::By32 } + { + } + + constexpr ChannelDecimator( + const DecimationFactor decimation_factor + ) : decimation_factor { decimation_factor } + { + } + + void set_decimation_factor(const DecimationFactor f) { + decimation_factor = f; + } + + buffer_c16_t execute(buffer_c8_t buffer) { + auto decimated = execute_decimation(buffer); + + return decimated; + } + +private: + std::array work_baseband; + + //const bool fs_over_4_downconvert = true; + + dsp::decimate::TranslateByFSOver4AndDecimateBy2CIC3 translate; + //dsp::decimate::DecimateBy2CIC3 cic_0; + dsp::decimate::DecimateBy2CIC3 cic_1; + dsp::decimate::DecimateBy2CIC3 cic_2; + dsp::decimate::DecimateBy2CIC3 cic_3; + dsp::decimate::DecimateBy2CIC3 cic_4; + + DecimationFactor decimation_factor; + + buffer_c16_t execute_decimation(buffer_c8_t buffer); +}; + +#endif/*__CHANNEL_DECIMATOR_H__*/ diff --git a/firmware/baseband-tx/channel_stats_collector.hpp b/firmware/baseband-tx/channel_stats_collector.hpp new file mode 100644 index 00000000..8c91ac5b --- /dev/null +++ b/firmware/baseband-tx/channel_stats_collector.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __CHANNEL_STATS_COLLECTOR_H__ +#define __CHANNEL_STATS_COLLECTOR_H__ + +#include "dsp_types.hpp" +#include "message.hpp" +#include "utility.hpp" + +#include +#include + +#include + +class ChannelStatsCollector { +public: + template + void feed(buffer_c16_t src, Callback callback) { + auto src_p = src.p; + while(src_p < &src.p[src.count]) { + const uint32_t sample = *__SIMD32(src_p)++; + const uint32_t mag_sq = __SMUAD(sample, sample); + if( mag_sq > max_squared ) { + max_squared = mag_sq; + } + } + count += src.count; + + const size_t samples_per_update = src.sampling_rate * update_interval; + + if( count >= samples_per_update ) { + const float max_squared_f = max_squared; + const int32_t max_db = complex16_mag_squared_to_dbv_norm(max_squared_f); + callback({ max_db, count }); + + max_squared = 0; + count = 0; + } + } + +private: + static constexpr float update_interval { 0.1f }; + uint32_t max_squared { 0 }; + size_t count { 0 }; +}; + +#endif/*__CHANNEL_STATS_COLLECTOR_H__*/ diff --git a/firmware/baseband-tx/chconf.h b/firmware/baseband-tx/chconf.h new file mode 100755 index 00000000..8045cf4f --- /dev/null +++ b/firmware/baseband-tx/chconf.h @@ -0,0 +1,546 @@ +/* + ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio + Copyright (C) 2014 Jared Boone, ShareBrained Technology + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file templates/chconf.h + * @brief Configuration file template. + * @details A copy of this file must be placed in each project directory, it + * contains the application specific kernel settings. + * + * @addtogroup config + * @details Kernel related settings and hooks. + * @{ + */ + +#ifndef _CHCONF_H_ +#define _CHCONF_H_ + +/*===========================================================================*/ +/** + * @name Kernel parameters and options + * @{ + */ +/*===========================================================================*/ + +/** + * @brief System tick frequency. + * @details Frequency of the system timer that drives the system ticks. This + * setting also defines the system tick time unit. + */ +#if !defined(CH_FREQUENCY) || defined(__DOXYGEN__) +#define CH_FREQUENCY 1000 +#endif + +/** + * @brief Round robin interval. + * @details This constant is the number of system ticks allowed for the + * threads before preemption occurs. Setting this value to zero + * disables the preemption for threads with equal priority and the + * round robin becomes cooperative. Note that higher priority + * threads can still preempt, the kernel is always preemptive. + * + * @note Disabling the round robin preemption makes the kernel more compact + * and generally faster. + */ +#if !defined(CH_TIME_QUANTUM) || defined(__DOXYGEN__) +#define CH_TIME_QUANTUM 0 +#endif + +/** + * @brief Managed RAM size. + * @details Size of the RAM area to be managed by the OS. If set to zero + * then the whole available RAM is used. The core memory is made + * available to the heap allocator and/or can be used directly through + * the simplified core memory allocator. + * + * @note In order to let the OS manage the whole RAM the linker script must + * provide the @p __heap_base__ and @p __heap_end__ symbols. + * @note Requires @p CH_USE_MEMCORE. + */ +#if !defined(CH_MEMCORE_SIZE) || defined(__DOXYGEN__) +#define CH_MEMCORE_SIZE 0 +#endif + +/** + * @brief Idle thread automatic spawn suppression. + * @details When this option is activated the function @p chSysInit() + * does not spawn the idle thread automatically. The application has + * then the responsibility to do one of the following: + * - Spawn a custom idle thread at priority @p IDLEPRIO. + * - Change the main() thread priority to @p IDLEPRIO then enter + * an endless loop. In this scenario the @p main() thread acts as + * the idle thread. + * . + * @note Unless an idle thread is spawned the @p main() thread must not + * enter a sleep state. + */ +#if !defined(CH_NO_IDLE_THREAD) || defined(__DOXYGEN__) +#define CH_NO_IDLE_THREAD FALSE +#endif + +/** @} */ + +/*===========================================================================*/ +/** + * @name Performance options + * @{ + */ +/*===========================================================================*/ + +/** + * @brief OS optimization. + * @details If enabled then time efficient rather than space efficient code + * is used when two possible implementations exist. + * + * @note This is not related to the compiler optimization options. + * @note The default is @p TRUE. + */ +#if !defined(CH_OPTIMIZE_SPEED) || defined(__DOXYGEN__) +#define CH_OPTIMIZE_SPEED TRUE +#endif + +/** @} */ + +/*===========================================================================*/ +/** + * @name Subsystem options + * @{ + */ +/*===========================================================================*/ + +/** + * @brief Threads registry APIs. + * @details If enabled then the registry APIs are included in the kernel. + * + * @note The default is @p TRUE. + */ +#if !defined(CH_USE_REGISTRY) || defined(__DOXYGEN__) +#define CH_USE_REGISTRY TRUE +#endif + +/** + * @brief Threads synchronization APIs. + * @details If enabled then the @p chThdWait() function is included in + * the kernel. + * + * @note The default is @p TRUE. + */ +#if !defined(CH_USE_WAITEXIT) || defined(__DOXYGEN__) +#define CH_USE_WAITEXIT TRUE +#endif + +/** + * @brief Semaphores APIs. + * @details If enabled then the Semaphores APIs are included in the kernel. + * + * @note The default is @p TRUE. + */ +#if !defined(CH_USE_SEMAPHORES) || defined(__DOXYGEN__) +#define CH_USE_SEMAPHORES TRUE +#endif + +/** + * @brief Semaphores queuing mode. + * @details If enabled then the threads are enqueued on semaphores by + * priority rather than in FIFO order. + * + * @note The default is @p FALSE. Enable this if you have special requirements. + * @note Requires @p CH_USE_SEMAPHORES. + */ +#if !defined(CH_USE_SEMAPHORES_PRIORITY) || defined(__DOXYGEN__) +#define CH_USE_SEMAPHORES_PRIORITY FALSE +#endif + +/** + * @brief Atomic semaphore API. + * @details If enabled then the semaphores the @p chSemSignalWait() API + * is included in the kernel. + * + * @note The default is @p TRUE. + * @note Requires @p CH_USE_SEMAPHORES. + */ +#if !defined(CH_USE_SEMSW) || defined(__DOXYGEN__) +#define CH_USE_SEMSW TRUE +#endif + +/** + * @brief Mutexes APIs. + * @details If enabled then the mutexes APIs are included in the kernel. + * + * @note The default is @p TRUE. + */ +#if !defined(CH_USE_MUTEXES) || defined(__DOXYGEN__) +#define CH_USE_MUTEXES TRUE +#endif + +/** + * @brief Conditional Variables APIs. + * @details If enabled then the conditional variables APIs are included + * in the kernel. + * + * @note The default is @p TRUE. + * @note Requires @p CH_USE_MUTEXES. + */ +#if !defined(CH_USE_CONDVARS) || defined(__DOXYGEN__) +#define CH_USE_CONDVARS TRUE +#endif + +/** + * @brief Conditional Variables APIs with timeout. + * @details If enabled then the conditional variables APIs with timeout + * specification are included in the kernel. + * + * @note The default is @p TRUE. + * @note Requires @p CH_USE_CONDVARS. + */ +#if !defined(CH_USE_CONDVARS_TIMEOUT) || defined(__DOXYGEN__) +#define CH_USE_CONDVARS_TIMEOUT TRUE +#endif + +/** + * @brief Events Flags APIs. + * @details If enabled then the event flags APIs are included in the kernel. + * + * @note The default is @p TRUE. + */ +#if !defined(CH_USE_EVENTS) || defined(__DOXYGEN__) +#define CH_USE_EVENTS TRUE +#endif + +/** + * @brief Events Flags APIs with timeout. + * @details If enabled then the events APIs with timeout specification + * are included in the kernel. + * + * @note The default is @p TRUE. + * @note Requires @p CH_USE_EVENTS. + */ +#if !defined(CH_USE_EVENTS_TIMEOUT) || defined(__DOXYGEN__) +#define CH_USE_EVENTS_TIMEOUT TRUE +#endif + +/** + * @brief Synchronous Messages APIs. + * @details If enabled then the synchronous messages APIs are included + * in the kernel. + * + * @note The default is @p TRUE. + */ +#if !defined(CH_USE_MESSAGES) || defined(__DOXYGEN__) +#define CH_USE_MESSAGES TRUE +#endif + +/** + * @brief Synchronous Messages queuing mode. + * @details If enabled then messages are served by priority rather than in + * FIFO order. + * + * @note The default is @p FALSE. Enable this if you have special requirements. + * @note Requires @p CH_USE_MESSAGES. + */ +#if !defined(CH_USE_MESSAGES_PRIORITY) || defined(__DOXYGEN__) +#define CH_USE_MESSAGES_PRIORITY FALSE +#endif + +/** + * @brief Mailboxes APIs. + * @details If enabled then the asynchronous messages (mailboxes) APIs are + * included in the kernel. + * + * @note The default is @p TRUE. + * @note Requires @p CH_USE_SEMAPHORES. + */ +#if !defined(CH_USE_MAILBOXES) || defined(__DOXYGEN__) +#define CH_USE_MAILBOXES TRUE +#endif + +/** + * @brief I/O Queues APIs. + * @details If enabled then the I/O queues APIs are included in the kernel. + * + * @note The default is @p TRUE. + */ +#if !defined(CH_USE_QUEUES) || defined(__DOXYGEN__) +#define CH_USE_QUEUES TRUE +#endif + +/** + * @brief Core Memory Manager APIs. + * @details If enabled then the core memory manager APIs are included + * in the kernel. + * + * @note The default is @p TRUE. + */ +#if !defined(CH_USE_MEMCORE) || defined(__DOXYGEN__) +#define CH_USE_MEMCORE TRUE +#endif + +/** + * @brief Heap Allocator APIs. + * @details If enabled then the memory heap allocator APIs are included + * in the kernel. + * + * @note The default is @p TRUE. + * @note Requires @p CH_USE_MEMCORE and either @p CH_USE_MUTEXES or + * @p CH_USE_SEMAPHORES. + * @note Mutexes are recommended. + */ +#if !defined(CH_USE_HEAP) || defined(__DOXYGEN__) +#define CH_USE_HEAP TRUE +#endif + +/** + * @brief C-runtime allocator. + * @details If enabled the the heap allocator APIs just wrap the C-runtime + * @p malloc() and @p free() functions. + * + * @note The default is @p FALSE. + * @note Requires @p CH_USE_HEAP. + * @note The C-runtime may or may not require @p CH_USE_MEMCORE, see the + * appropriate documentation. + */ +#if !defined(CH_USE_MALLOC_HEAP) || defined(__DOXYGEN__) +#define CH_USE_MALLOC_HEAP FALSE +#endif + +/** + * @brief Memory Pools Allocator APIs. + * @details If enabled then the memory pools allocator APIs are included + * in the kernel. + * + * @note The default is @p TRUE. + */ +#if !defined(CH_USE_MEMPOOLS) || defined(__DOXYGEN__) +#define CH_USE_MEMPOOLS TRUE +#endif + +/** + * @brief Dynamic Threads APIs. + * @details If enabled then the dynamic threads creation APIs are included + * in the kernel. + * + * @note The default is @p TRUE. + * @note Requires @p CH_USE_WAITEXIT. + * @note Requires @p CH_USE_HEAP and/or @p CH_USE_MEMPOOLS. + */ +#if !defined(CH_USE_DYNAMIC) || defined(__DOXYGEN__) +#define CH_USE_DYNAMIC TRUE +#endif + +/** @} */ + +/*===========================================================================*/ +/** + * @name Debug options + * @{ + */ +/*===========================================================================*/ + +/** + * @brief Debug option, system state check. + * @details If enabled the correct call protocol for system APIs is checked + * at runtime. + * + * @note The default is @p FALSE. + */ +#if !defined(CH_DBG_SYSTEM_STATE_CHECK) || defined(__DOXYGEN__) +#define CH_DBG_SYSTEM_STATE_CHECK TRUE +#endif + +/** + * @brief Debug option, parameters checks. + * @details If enabled then the checks on the API functions input + * parameters are activated. + * + * @note The default is @p FALSE. + */ +#if !defined(CH_DBG_ENABLE_CHECKS) || defined(__DOXYGEN__) +#define CH_DBG_ENABLE_CHECKS TRUE +#endif + +/** + * @brief Debug option, consistency checks. + * @details If enabled then all the assertions in the kernel code are + * activated. This includes consistency checks inside the kernel, + * runtime anomalies and port-defined checks. + * + * @note The default is @p FALSE. + */ +#if !defined(CH_DBG_ENABLE_ASSERTS) || defined(__DOXYGEN__) +#define CH_DBG_ENABLE_ASSERTS TRUE +#endif + +/** + * @brief Debug option, trace buffer. + * @details If enabled then the context switch circular trace buffer is + * activated. + * + * @note The default is @p FALSE. + */ +#if !defined(CH_DBG_ENABLE_TRACE) || defined(__DOXYGEN__) +#define CH_DBG_ENABLE_TRACE FALSE +#endif + +/** + * @brief Debug option, stack checks. + * @details If enabled then a runtime stack check is performed. + * + * @note The default is @p FALSE. + * @note The stack check is performed in a architecture/port dependent way. + * It may not be implemented or some ports. + * @note The default failure mode is to halt the system with the global + * @p panic_msg variable set to @p NULL. + */ +#if !defined(CH_DBG_ENABLE_STACK_CHECK) || defined(__DOXYGEN__) +#define CH_DBG_ENABLE_STACK_CHECK TRUE +#endif + +/** + * @brief Debug option, stacks initialization. + * @details If enabled then the threads working area is filled with a byte + * value when a thread is created. This can be useful for the + * runtime measurement of the used stack. + * + * @note The default is @p FALSE. + */ +#if !defined(CH_DBG_FILL_THREADS) || defined(__DOXYGEN__) +#define CH_DBG_FILL_THREADS TRUE +#endif + +/** + * @brief Debug option, threads profiling. + * @details If enabled then a field is added to the @p Thread structure that + * counts the system ticks occurred while executing the thread. + * + * @note The default is @p TRUE. + * @note This debug option is defaulted to TRUE because it is required by + * some test cases into the test suite. + */ +#if !defined(CH_DBG_THREADS_PROFILING) || defined(__DOXYGEN__) +#define CH_DBG_THREADS_PROFILING TRUE +#endif + +/** @} */ + +/*===========================================================================*/ +/** + * @name Kernel hooks + * @{ + */ +/*===========================================================================*/ + +/** + * @brief Threads descriptor structure extension. + * @details User fields added to the end of the @p Thread structure. + */ +#if !defined(THREAD_EXT_FIELDS) || defined(__DOXYGEN__) +#define THREAD_EXT_FIELDS \ + /* Add threads custom fields here.*/ \ + uint32_t switches; \ + uint32_t start_ticks; \ + uint32_t total_ticks; +#endif + +/** + * @brief Threads initialization hook. + * @details User initialization code added to the @p chThdInit() API. + * + * @note It is invoked from within @p chThdInit() and implicitly from all + * the threads creation APIs. + */ +#if !defined(THREAD_EXT_INIT_HOOK) || defined(__DOXYGEN__) +#define THREAD_EXT_INIT_HOOK(tp) { \ + /* Add threads initialization code here.*/ \ + tp->switches = 0; \ + tp->start_ticks = 0; \ + tp->total_ticks = 0; \ +} +#endif + +/** + * @brief Threads finalization hook. + * @details User finalization code added to the @p chThdExit() API. + * + * @note It is inserted into lock zone. + * @note It is also invoked when the threads simply return in order to + * terminate. + */ +#if !defined(THREAD_EXT_EXIT_HOOK) || defined(__DOXYGEN__) +#define THREAD_EXT_EXIT_HOOK(tp) { \ + /* Add threads finalization code here.*/ \ +} +#endif + +/** + * @brief Context switch hook. + * @details This hook is invoked just before switching between threads. + */ +#if !defined(THREAD_CONTEXT_SWITCH_HOOK) || defined(__DOXYGEN__) +#define THREAD_CONTEXT_SWITCH_HOOK(ntp, otp) { \ + /* System halt code here.*/ \ + otp->switches++; \ + ntp->start_ticks = *((volatile uint32_t*)0x400C4008); \ + otp->total_ticks += (ntp->start_ticks - otp->start_ticks); \ +} +#endif + +/** + * @brief Idle Loop hook. + * @details This hook is continuously invoked by the idle thread loop. + */ +#if !defined(IDLE_LOOP_HOOK) || defined(__DOXYGEN__) +#define IDLE_LOOP_HOOK() { \ + /* Idle loop code here.*/ \ +} +#endif + +/** + * @brief System tick event hook. + * @details This hook is invoked in the system tick handler immediately + * after processing the virtual timers queue. + */ +#if !defined(SYSTEM_TICK_EVENT_HOOK) || defined(__DOXYGEN__) +#define SYSTEM_TICK_EVENT_HOOK() { \ + /* System tick event code here.*/ \ +} +#endif + +/** + * @brief System halt hook. + * @details This hook is invoked in case to a system halting error before + * the system is halted. + */ +#if !defined(SYSTEM_HALT_HOOK) || defined(__DOXYGEN__) +#define SYSTEM_HALT_HOOK() { \ + /* System halt code here.*/ \ +} +#endif + +/** @} */ + +/*===========================================================================*/ +/* Port-specific settings (override port settings defaulted in chcore.h). */ +/*===========================================================================*/ + +/* NOTE: When changing this option you also have to enable or disable the FPU + in the project options.*/ +#define CORTEX_USE_FPU TRUE +#define CORTEX_ENABLE_WFI_IDLE TRUE + +#endif /* _CHCONF_H_ */ + +/** @} */ diff --git a/firmware/baseband-tx/clock_recovery.cpp b/firmware/baseband-tx/clock_recovery.cpp new file mode 100644 index 00000000..17cb5c70 --- /dev/null +++ b/firmware/baseband-tx/clock_recovery.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "clock_recovery.hpp" diff --git a/firmware/baseband-tx/clock_recovery.hpp b/firmware/baseband-tx/clock_recovery.hpp new file mode 100644 index 00000000..bd17255d --- /dev/null +++ b/firmware/baseband-tx/clock_recovery.hpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __CLOCK_RECOVERY_H__ +#define __CLOCK_RECOVERY_H__ + +#include +#include +#include + +#include "linear_resampler.hpp" + +namespace clock_recovery { + +class GardnerTimingErrorDetector { +public: + static constexpr size_t samples_per_symbol { 2 }; + + /* + Expects retimed samples at a rate of twice the expected symbol rate. + Calculates timing error, sends symbol and error to handler. + */ + template + void operator()( + const float in, + SymbolHandler symbol_handler + ) { + /* NOTE: Algorithm is sensitive to input magnitude. Timing error value + * will scale proportionally. Best practice is to use error sign only. + */ + t[2] = t[1]; + t[1] = t[0]; + t[0] = in; + + if( symbol_phase == 0 ) { + const auto symbol = t[0]; + const float lateness = (t[0] - t[2]) * t[1]; + symbol_handler(symbol, lateness); + } + + symbol_phase = (symbol_phase + 1) % samples_per_symbol; + } + +private: + std::array t { { 0.0f, 0.0f, 0.0f } }; + size_t symbol_phase { 0 }; +}; + +class LinearErrorFilter { +public: + LinearErrorFilter( + const float filter_alpha = 0.95f, + const float error_weight = -1.0f + ) : filter_alpha { filter_alpha }, + error_weight { error_weight } + { + } + + float operator()( + const float error + ) { + error_filtered = filter_alpha * error_filtered + (1.0f - filter_alpha) * error; + return error_filtered * error_weight; + } + +private: + const float filter_alpha; + const float error_weight; + float error_filtered { 0.0f }; +}; + +class FixedErrorFilter { +public: + FixedErrorFilter( + ) { + } + + FixedErrorFilter( + const float weight + ) : weight_ { weight } + { + } + + float operator()( + const float lateness + ) const { + return (lateness < 0.0f) ? weight() : -weight(); + } + + float weight() const { + return weight_; + } + +private: + float weight_ { 1.0f / 16.0f }; +}; + +template +class ClockRecovery { +public: + ClockRecovery( + const float sampling_rate, + const float symbol_rate, + ErrorFilter error_filter, + std::function symbol_handler + ) : symbol_handler { symbol_handler } + { + configure(sampling_rate, symbol_rate, error_filter); + } + + ClockRecovery( + std::function symbol_handler + ) : symbol_handler { symbol_handler } + { + } + + void configure( + const float sampling_rate, + const float symbol_rate, + ErrorFilter error_filter + ) { + resampler.configure(sampling_rate, symbol_rate * timing_error_detector.samples_per_symbol); + error_filter = error_filter; + } + + void operator()( + const float baseband_sample + ) { + resampler(baseband_sample, + [this](const float interpolated_sample) { + this->resampler_callback(interpolated_sample); + } + ); + } + +private: + dsp::interpolation::LinearResampler resampler; + GardnerTimingErrorDetector timing_error_detector; + ErrorFilter error_filter; + std::function symbol_handler; + + void resampler_callback(const float interpolated_sample) { + timing_error_detector(interpolated_sample, + [this](const float symbol, const float lateness) { + this->symbol_callback(symbol, lateness); + } + ); + } + + void symbol_callback(const float symbol, const float lateness) { + symbol_handler(symbol); + + const float adjustment = error_filter(lateness); + resampler.advance(adjustment); + } +}; + +} /* namespace clock_recovery */ + +#endif/*__CLOCK_RECOVERY_H__*/ diff --git a/firmware/baseband-tx/description b/firmware/baseband-tx/description new file mode 100644 index 00000000..f2606500 --- /dev/null +++ b/firmware/baseband-tx/description @@ -0,0 +1 @@ +More specific stuff for testing :o diff --git a/firmware/baseband-tx/dsp_decimate.cpp b/firmware/baseband-tx/dsp_decimate.cpp new file mode 100644 index 00000000..e8fd1824 --- /dev/null +++ b/firmware/baseband-tx/dsp_decimate.cpp @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "dsp_decimate.hpp" + +#include + +namespace dsp { +namespace decimate { + +buffer_c16_t TranslateByFSOver4AndDecimateBy2CIC3::execute(buffer_c8_t src, buffer_c16_t dst) { + /* Translates incoming complex samples by -fs/4, + * decimates by two using a non-recursive third-order CIC filter. + */ + + /* Derivation of algorithm: + * Original CIC filter (decimating by two): + * D_I0 = i3 * 1 + i2 * 3 + i1 * 3 + i0 * 1 + * D_Q0 = q3 * 1 + q2 * 3 + q1 * 3 + q0 * 1 + * + * D_I1 = i5 * 1 + i4 * 3 + i3 * 3 + i2 * 1 + * D_Q1 = q5 * 1 + q4 * 3 + q3 * 3 + q2 * 1 + * + * Translate -fs/4, phased 180 degrees, accomplished by complex multiplication + * of complex length-4 sequence: + * + * Substitute: + * i0 = -i0, q0 = -q0 + * i1 = -q1, q1 = i1 + * i2 = i2, q2 = q2 + * i3 = q3, q3 = -i3 + * i4 = -i4, q4 = -q4 + * i5 = -q5, q5 = i5 + * + * Resulting taps (with decimation by 2, four samples in, two samples out): + * D_I0 = q3 * 1 + i2 * 3 + -q1 * 3 + -i0 * 1 + * D_Q0 = -i3 * 1 + q2 * 3 + i1 * 3 + -q0 * 1 + * + * D_I1 = -q5 * 1 + -i4 * 3 + q3 * 3 + i2 * 1 + * D_Q1 = i5 * 1 + -q4 * 3 + -i3 * 3 + q2 * 1 + */ + + // 6 cycles per complex input sample, not including loop overhead. + uint32_t q1_i0 = _q1_i0; + uint32_t q0_i1 = _q0_i1; + /* 3:1 Scaled by 32 to normalize output to +/-32768-ish. */ + constexpr uint32_t scale_factor = 32; + const uint32_t k_3_1 = 0x00030001 * scale_factor; + uint32_t* src_p = reinterpret_cast(&src.p[0]); + uint32_t* const src_end = reinterpret_cast(&src.p[src.count]); + uint32_t* dst_p = reinterpret_cast(&dst.p[0]); + while(src_p < src_end) { + const uint32_t q3_i3_q2_i2 = *(src_p++); // 3 + const uint32_t q5_i5_q4_i4 = *(src_p++); + + const uint32_t i2_i3 = __SXTB16(q3_i3_q2_i2, 16); // 1: (q3_i3_q2_i2 ror 16)[23:16]:(q3_i3_q2_i2 ror 16)[7:0] + const uint32_t q3_q2 = __SXTB16(q3_i3_q2_i2, 8); // 1: (q3_i3_q2_i2 ror 8)[23:16]:(q3_i3_q2_i2 ror 8)[7:0] + const uint32_t i2_q3 = __PKHTB(i2_i3, q3_q2, 16); // 1: Rn[31:16]:(Rm>>16)[15:0] + const uint32_t i3_q2 = __PKHBT(q3_q2, i2_i3, 16); // 1:(Rm<<16)[31:16]:Rn[15:0] + + // D_I0 = 3 * (i2 - q1) + (q3 - i0) + const uint32_t i2_m_q1_q3_m_i0 = __QSUB16(i2_q3, q1_i0); // 1: Rn[31:16]-Rm[31:16]:Rn[15:0]-Rm[15:0] + const uint32_t d_i0 = __SMUAD(k_3_1, i2_m_q1_q3_m_i0); // 1: Rm[15:0]*Rs[15:0]+Rm[31:16]*Rs[31:16] + + // D_Q0 = 3 * (q2 + i1) - (i3 + q0) + const uint32_t i3_p_q0_q2_p_i1 = __QADD16(i3_q2, q0_i1); // 1: Rn[31:16]+Rm[31:16]:Rn[15:0]+Rm[15:0] + const uint32_t d_q0 = __SMUSDX(i3_p_q0_q2_p_i1, k_3_1); // 1: Rm[15:0]*Rs[31:16]–Rm[31:16]*RsX[15:0] + const uint32_t d_q0_i0 = __PKHBT(d_i0, d_q0, 16); // 1: (Rm<<16)[31:16]:Rn[15:0] + + const uint32_t i5_i4 = __SXTB16(q5_i5_q4_i4, 0); // 1: (q5_i5_q4_i4 ror 0)[23:16]:(q5_i5_q4_i4 ror 0)[7:0] + const uint32_t q4_q5 = __SXTB16(q5_i5_q4_i4, 24); // 1: (q5_i5_q4_i4 ror 24)[23:16]:(q5_i5_q4_i4 ror 24)[7:0] + const uint32_t q4_i5 = __PKHTB(q4_q5, i5_i4, 16); // 1: Rn[31:16]:(Rm>>16)[15:0] + const uint32_t q5_i4 = __PKHBT(i5_i4, q4_q5, 16); // 1: (Rm<<16)[31:16]:Rn[15:0] + + // D_I1 = (i2 - q5) + 3 * (q3 - i4) + const uint32_t i2_m_q5_q3_m_i4 = __QSUB16(i2_q3, q5_i4); // 1: Rn[31:16]-Rm[31:16]:Rn[15:0]-Rm[15:0] + const uint32_t d_i1 = __SMUADX(i2_m_q5_q3_m_i4, k_3_1); // 1: Rm[15:0]*Rs[31:16]+Rm[31:16]*Rs[15:0] + + // D_Q1 = (i5 + q2) - 3 * (q4 + i3) + const uint32_t q4_p_i3_i5_p_q2 = __QADD16(q4_i5, i3_q2); // 1: Rn[31:16]+Rm[31:16]:Rn[15:0]+Rm[15:0] + const uint32_t d_q1 = __SMUSD(k_3_1, q4_p_i3_i5_p_q2); // 1: Rm[15:0]*Rs[15:0]–Rm[31:16]*Rs[31:16] + const uint32_t d_q1_i1 = __PKHBT(d_i1, d_q1, 16); // 1: (Rm<<16)[31:16]:Rn[15:0] + *(dst_p++) = d_q0_i0; // 3 + *(dst_p++) = d_q1_i1; + + q1_i0 = q5_i4; + q0_i1 = q4_i5; + } + _q1_i0 = q1_i0; + _q0_i1 = q0_i1; + + return { dst.p, src.count / 2, src.sampling_rate / 2 }; +} + +buffer_c16_t DecimateBy2CIC3::execute( + buffer_c16_t src, + buffer_c16_t dst +) { + /* Complex non-recursive 3rd-order CIC filter (taps 1,3,3,1). + * Gain of 8. + * Consumes 16 bytes (4 s16:s16 samples) per loop iteration, + * Produces 8 bytes (2 s16:s16 samples) per loop iteration. + */ + uint32_t t1 = _iq0; + uint32_t t2 = _iq1; + uint32_t t3, t4; + const uint32_t taps = 0x00000003; + auto s = src.p; + auto d = dst.p; + const auto d_end = &dst.p[src.count / 2]; + uint32_t i, q; + while(d < d_end) { + i = __SXTH(t1, 0); /* 1: I0 */ + q = __SXTH(t1, 16); /* 1: Q0 */ + i = __SMLABB(t2, taps, i); /* 1: I1*3 + I0 */ + q = __SMLATB(t2, taps, q); /* 1: Q1*3 + Q0 */ + + t3 = *__SIMD32(s)++; /* 3: Q2:I2 */ + t4 = *__SIMD32(s)++; /* Q3:I3 */ + + i = __SMLABB(t3, taps, i); /* 1: I2*3 + I1*3 + I0 */ + q = __SMLATB(t3, taps, q); /* 1: Q2*3 + Q1*3 + Q0 */ + int32_t si0 = __SXTAH(i, t4, 0); /* 1: I3 + Q2*3 + Q1*3 + Q0 */ + int32_t sq0 = __SXTAH(q, t4, 16); /* 1: Q3 + Q2*3 + Q1*3 + Q0 */ + i = __BFI(si0 / 8, sq0 / 8, 16, 16); /* 1: D2_Q0:D2_I0 */ + *__SIMD32(d)++ = i; /* D2_Q0:D2_I0 */ + + i = __SXTH(t3, 0); /* 1: I2 */ + q = __SXTH(t3, 16); /* 1: Q2 */ + i = __SMLABB(t4, taps, i); /* 1: I3*3 + I2 */ + q = __SMLATB(t4, taps, q); /* 1: Q3*3 + Q2 */ + + t1 = *__SIMD32(s)++; /* 3: Q4:I4 */ + t2 = *__SIMD32(s)++; /* Q5:I5 */ + + i = __SMLABB(t1, taps, i); /* 1: I4*3 + I3*3 + I2 */ + q = __SMLATB(t1, taps, q); /* 1: Q4*3 + Q3*3 + Q2 */ + int32_t si1 = __SXTAH(i, t2, 0) ; /* 1: I5 + Q4*3 + Q3*3 + Q2 */ + int32_t sq1 = __SXTAH(q, t2, 16); /* 1: Q5 + Q4*3 + Q3*3 + Q2 */ + i = __BFI(si1 / 8, sq1 / 8, 16, 16); /* 1: D2_Q1:D2_I1 */ + *__SIMD32(d)++ = i; /* D2_Q1:D2_I1 */ + } + _iq0 = t1; + _iq1 = t2; + + return { dst.p, src.count / 2, src.sampling_rate / 2 }; +} + +buffer_s16_t FIR64AndDecimateBy2Real::execute( + buffer_s16_t src, + buffer_s16_t dst +) { + /* int16_t input (sample count "n" must be multiple of 4) + * -> int16_t output, decimated by 2. + * taps are normalized to 1 << 16 == 1.0. + */ + auto src_p = src.p; + auto dst_p = dst.p; + int32_t n = src.count; + for(; n>0; n-=2) { + z[taps_count-2] = *(src_p++); + z[taps_count-1] = *(src_p++); + + int32_t t = 0; + for(size_t j=0; j int16_t output, decimated by decimation_factor. + * taps are normalized to 1 << 16 == 1.0. + */ + const auto output_sampling_rate = src.sampling_rate / decimation_factor_; + const size_t output_samples = src.count / decimation_factor_; + + sample_t* dst_p = dst.p; + const buffer_c16_t result { dst.p, output_samples, output_sampling_rate }; + + const sample_t* src_p = src.p; + size_t outer_count = output_samples; + while(outer_count > 0) { + /* Put new samples into delay buffer */ + auto z_new_p = &samples_[taps_count_ - decimation_factor_]; + for(size_t i=0; i 0) { + const auto tap0 = *__SIMD32(t_p)++; + const auto sample0 = *__SIMD32(z_p)++; + const auto tap1 = *__SIMD32(t_p)++; + const auto sample1 = *__SIMD32(z_p)++; + t_real = __SMLSLD(sample0, tap0, t_real); + t_imag = __SMLALDX(sample0, tap0, t_imag); + t_real = __SMLSLD(sample1, tap1, t_real); + t_imag = __SMLALDX(sample1, tap1, t_imag); + + const auto tap2 = *__SIMD32(t_p)++; + const auto sample2 = *__SIMD32(z_p)++; + const auto tap3 = *__SIMD32(t_p)++; + const auto sample3 = *__SIMD32(z_p)++; + t_real = __SMLSLD(sample2, tap2, t_real); + t_imag = __SMLALDX(sample2, tap2, t_imag); + t_real = __SMLSLD(sample3, tap3, t_real); + t_imag = __SMLALDX(sample3, tap3, t_imag); + + const auto tap4 = *__SIMD32(t_p)++; + const auto sample4 = *__SIMD32(z_p)++; + const auto tap5 = *__SIMD32(t_p)++; + const auto sample5 = *__SIMD32(z_p)++; + t_real = __SMLSLD(sample4, tap4, t_real); + t_imag = __SMLALDX(sample4, tap4, t_imag); + t_real = __SMLSLD(sample5, tap5, t_real); + t_imag = __SMLALDX(sample5, tap5, t_imag); + + const auto tap6 = *__SIMD32(t_p)++; + const auto sample6 = *__SIMD32(z_p)++; + const auto tap7 = *__SIMD32(t_p)++; + const auto sample7 = *__SIMD32(z_p)++; + t_real = __SMLSLD(sample6, tap6, t_real); + t_imag = __SMLALDX(sample6, tap6, t_imag); + t_real = __SMLSLD(sample7, tap7, t_real); + t_imag = __SMLALDX(sample7, tap7, t_imag); + + loop_count--; + } + + /* TODO: Re-evaluate whether saturation is performed, normalization, + * all that jazz. + */ + const int32_t r = t_real >> 16; + const int32_t i = t_imag >> 16; + const int32_t r_sat = __SSAT(r, 16); + const int32_t i_sat = __SSAT(i, 16); + *__SIMD32(dst_p)++ = __PKHBT( + r_sat, + i_sat, + 16 + ); + + /* Shift sample buffer left/down by decimation factor. */ + const size_t unroll_factor = 4; + size_t shift_count = (taps_count_ - decimation_factor_) / unroll_factor; + + sample_t* t = &samples_[0]; + const sample_t* s = &samples_[decimation_factor_]; + + while(shift_count > 0) { + *__SIMD32(t)++ = *__SIMD32(s)++; + *__SIMD32(t)++ = *__SIMD32(s)++; + *__SIMD32(t)++ = *__SIMD32(s)++; + *__SIMD32(t)++ = *__SIMD32(s)++; + shift_count--; + } + + shift_count = (taps_count_ - decimation_factor_) % unroll_factor; + while(shift_count > 0) { + *(t++) = *(s++); + shift_count--; + } + + outer_count--; + } + + return result; +} + +buffer_s16_t DecimateBy2CIC4Real::execute( + buffer_s16_t src, + buffer_s16_t dst +) { + auto src_p = src.p; + auto dst_p = dst.p; + int32_t n = src.count; + for(; n>0; n-=2) { + /* TODO: Probably a lot of room to optimize... */ + z[0] = z[2]; + z[1] = z[3]; + z[2] = z[4]; + z[3] = *(src_p++); + z[4] = *(src_p++); + + int32_t t = z[0] + z[1] * 4 + z[2] * 6 + z[3] * 4 + z[4]; + *(dst_p++) = t / 16; + } + + return { dst.p, src.count / 2, src.sampling_rate / 2 }; +} +#if 0 +buffer_c16_t DecimateBy2HBF5Complex::execute( + buffer_c16_t const src, + buffer_c16_t const dst +) { + auto src_p = src.p; + auto dst_p = dst.p; + int32_t n = src.count; + for(; n>0; n-=2) { + /* TODO: Probably a lot of room to optimize... */ + z[0] = z[2]; + //z[1] = z[3]; + z[2] = z[4]; + //z[3] = z[5]; + z[4] = z[6]; + z[5] = z[7]; + z[6] = z[8]; + z[7] = z[9]; + z[8] = z[10]; + z[9] = *(src_p++); + z[10] = *(src_p++); + + int32_t t_real { z[5].real * 256 }; + int32_t t_imag { z[5].imag * 256 }; + t_real += (z[ 0].real + z[10].real) * 3; + t_imag += (z[ 0].imag + z[10].imag) * 3; + t_real -= (z[ 2].real + z[ 8].real) * 25; + t_imag -= (z[ 2].imag + z[ 8].imag) * 25; + t_real += (z[ 4].real + z[ 6].real) * 150; + t_imag += (z[ 4].imag + z[ 6].imag) * 150; + *(dst_p++) = { t_real / 256, t_imag / 256 }; + } + + return { dst.p, src.count / 2, src.sampling_rate / 2 }; +} + +buffer_c16_t DecimateBy2HBF7Complex::execute( + buffer_c16_t const src, + buffer_c16_t const dst +) { + auto src_p = src.p; + auto dst_p = dst.p; + int32_t n = src.count; + for(; n>0; n-=2) { + /* TODO: Probably a lot of room to optimize... */ + z[0] = z[2]; + //z[1] = z[3]; + z[2] = z[4]; + //z[3] = z[5]; + z[4] = z[6]; + z[5] = z[7]; + z[6] = z[8]; + z[7] = z[9]; + z[8] = z[10]; + z[9] = *(src_p++); + z[10] = *(src_p++); + + int32_t t_real { z[5].real * 512 }; + int32_t t_imag { z[5].imag * 512 }; + t_real += (z[ 0].real + z[10].real) * 7; + t_imag += (z[ 0].imag + z[10].imag) * 7; + t_real -= (z[ 2].real + z[ 8].real) * 53; + t_imag -= (z[ 2].imag + z[ 8].imag) * 53; + t_real += (z[ 4].real + z[ 6].real) * 302; + t_imag += (z[ 4].imag + z[ 6].imag) * 302; + *(dst_p++) = { t_real / 512, t_imag / 512 }; + } + + return { dst.p, src.count / 2, src.sampling_rate / 2 }; +} +#endif +} /* namespace decimate */ +} /* namespace dsp */ diff --git a/firmware/baseband-tx/dsp_decimate.hpp b/firmware/baseband-tx/dsp_decimate.hpp new file mode 100644 index 00000000..9cc90b88 --- /dev/null +++ b/firmware/baseband-tx/dsp_decimate.hpp @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __DSP_DECIMATE_H__ +#define __DSP_DECIMATE_H__ + +#include +#include +#include +#include + +#include "utility.hpp" + +#include "dsp_types.hpp" + +namespace dsp { +namespace decimate { + +class TranslateByFSOver4AndDecimateBy2CIC3 { +public: + buffer_c16_t execute( + buffer_c8_t src, + buffer_c16_t dst + ); + +private: + uint32_t _q1_i0 { 0 }; + uint32_t _q0_i1 { 0 }; +}; + +class DecimateBy2CIC3 { +public: + buffer_c16_t execute( + buffer_c16_t src, + buffer_c16_t dst + ); + +private: + uint32_t _iq0 { 0 }; + uint32_t _iq1 { 0 }; +}; + +class FIR64AndDecimateBy2Real { +public: + static constexpr size_t taps_count = 64; + + FIR64AndDecimateBy2Real( + const std::array& taps + ) : taps(taps) + { + } + + buffer_s16_t execute( + buffer_s16_t src, + buffer_s16_t dst + ); + +private: + std::array z; + const std::array& taps; +}; + +class FIRAndDecimateComplex { +public: + using sample_t = complex16_t; + using tap_t = complex16_t; + + using taps_t = tap_t[]; + + /* NOTE! Current code makes an assumption that block of samples to be + * processed will be a multiple of the taps_count. + */ + FIRAndDecimateComplex( + ) : taps_count_ { 0 }, + decimation_factor_ { 1 } + { + } + + template + void configure( + const T& taps, + const size_t decimation_factor + ) { + samples_ = std::make_unique(taps.size()); + taps_reversed_ = std::make_unique(taps.size()); + taps_count_ = taps.size(); + decimation_factor_ = decimation_factor; + std::reverse_copy(taps.cbegin(), taps.cend(), &taps_reversed_[0]); + } + + buffer_c16_t execute( + buffer_c16_t src, + buffer_c16_t dst + ); + +private: + using samples_t = sample_t[]; + + std::unique_ptr samples_; + std::unique_ptr taps_reversed_; + size_t taps_count_; + size_t decimation_factor_; +}; + +class DecimateBy2CIC4Real { +public: + buffer_s16_t execute( + buffer_s16_t src, + buffer_s16_t dst + ); + +private: + int16_t z[5]; +}; +#if 0 +class DecimateBy2HBF5Complex { +public: + buffer_c16_t execute( + buffer_c16_t const src, + buffer_c16_t const dst + ); + +private: + complex16_t z[11]; +}; + +class DecimateBy2HBF7Complex { +public: + buffer_c16_t execute( + buffer_c16_t const src, + buffer_c16_t const dst + ); + +private: + complex16_t z[11]; +}; +#endif +/* From http://www.dspguru.com/book/export/html/3 + +Here are several basic techniques to fake circular buffers: + +Split the calculation: You can split any FIR calculation into its "pre-wrap" +and "post-wrap" parts. By splitting the calculation into these two parts, you +essentially can do the circular logic only once, rather than once per tap. +(See fir_double_z in FirAlgs.c above.) + +Duplicate the delay line: For a FIR with N taps, use a delay line of size 2N. +Copy each sample to its proper location, as well as at location-plus-N. +Therefore, the FIR calculation's MAC loop can be done on a flat buffer of N +points, starting anywhere within the first set of N points. The second set of +N delayed samples provides the "wrap around" comparable to a true circular +buffer. (See fir_double_z in FirAlgs.c above.) + +Duplicate the coefficients: This is similar to the above, except that the +duplication occurs in terms of the coefficients, not the delay line. +Compared to the previous method, this has a calculation advantage of not +having to store each incoming sample twice, and it also has a memory +advantage when the same coefficient set will be used on multiple delay lines. +(See fir_double_h in FirAlgs.c above.) + +Use block processing: In block processing, you use a delay line which is a +multiple of the number of taps. You therefore only have to move the data +once per block to implement the delay-line mechanism. When the block size +becomes "large", the overhead of a moving the delay line once per block +becomes negligible. +*/ + +#if 0 +template +class FIRAndDecimateBy2Complex { +public: + FIR64AndDecimateBy2Complex( + const std::array& taps + ) : taps { taps } + { + } + + buffer_c16_t execute( + buffer_c16_t const src, + buffer_c16_t const dst + ) { + /* int16_t input (sample count "n" must be multiple of 4) + * -> int16_t output, decimated by 2. + * taps are normalized to 1 << 16 == 1.0. + */ + + return { dst.p, src.count / 2 }; + } + +private: + std::array z; + const std::array& taps; + + complex process_one(const size_t start_offset) { + const auto split = &z[start_offset]; + const auto end = &z[z.size()]; + auto tap = &taps[0]; + + complex t { 0, 0 }; + + auto p = split; + while(p < end) { + const auto t = *(tap++); + const auto c = *(p++); + t.real += c.real * t; + t.imag += c.imag * t; + } + + p = &z[0]; + while(p < split) { + const auto t = *(tap++); + const auto c = *(p++); + t.real += c.real * t; + t.imag += c.imag * t; + } + + return { t.real / 65536, t.imag / 65536 }; + } +}; +#endif +} /* namespace decimate */ +} /* namespace dsp */ + +#endif/*__DSP_DECIMATE_H__*/ diff --git a/firmware/baseband-tx/dsp_demodulate.cpp b/firmware/baseband-tx/dsp_demodulate.cpp new file mode 100644 index 00000000..4ee6ed68 --- /dev/null +++ b/firmware/baseband-tx/dsp_demodulate.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "dsp_demodulate.hpp" + +#include "complex.hpp" +#include "fxpt_atan2.hpp" +#include "utility_m4.hpp" + +#include + +namespace dsp { +namespace demodulate { + +buffer_s16_t AM::execute( + buffer_c16_t src, + buffer_s16_t dst +) { + /* Intermediate maximum value: 46341 (when input is -32768,-32768). */ + /* Normalized to maximum 32767 for int16_t representation. */ + + const auto src_p = src.p; + const auto src_end = &src.p[src.count]; + auto dst_p = dst.p; + while(src_p < src_end) { + // const auto s = *(src_p++); + // const uint32_t r_sq = s.real() * s.real(); + // const uint32_t i_sq = s.imag() * s.imag(); + // const uint32_t mag_sq = r_sq + i_sq; + const uint32_t sample0 = *__SIMD32(src_p)++; + const uint32_t sample1 = *__SIMD32(src_p)++; + const uint32_t mag_sq0 = __SMUAD(sample0, sample0); + const uint32_t mag_sq1 = __SMUAD(sample1, sample1); + const int32_t mag0_int = __builtin_sqrtf(mag_sq0); + const int32_t mag0_sat = __SSAT(mag0_int, 16); + const int32_t mag1_int = __builtin_sqrtf(mag_sq1); + const int32_t mag1_sat = __SSAT(mag1_int, 16); + *__SIMD32(dst_p)++ = __PKHBT( + mag0_sat, + mag1_sat, + 16 + ); + } + + return { dst.p, src.count, src.sampling_rate }; +} +/* +static inline float angle_approx_4deg0(const complex32_t t) { + const auto x = static_cast(t.imag()) / static_cast(t.real()); + return 16384.0f * x; +} +*/ +static inline float angle_approx_0deg27(const complex32_t t) { + const auto x = static_cast(t.imag()) / static_cast(t.real()); + return x / (1.0f + 0.28086f * x * x); +} +/* +static inline float angle_precise(const complex32_t t) { + return atan2f(t.imag(), t.real()); +} +*/ +buffer_s16_t FM::execute( + buffer_c16_t src, + buffer_s16_t dst +) { + auto z = z_; + + const auto src_p = src.p; + const auto src_end = &src.p[src.count]; + auto dst_p = dst.p; + while(src_p < src_end) { + const auto s0 = *__SIMD32(src_p)++; + const auto s1 = *__SIMD32(src_p)++; + const auto t0 = multiply_conjugate_s16_s32(s0, z); + const auto t1 = multiply_conjugate_s16_s32(s1, s0); + z = s1; + const int32_t theta0_int = angle_approx_0deg27(t0) * k; + const int32_t theta0_sat = __SSAT(theta0_int, 16); + const int32_t theta1_int = angle_approx_0deg27(t1) * k; + const int32_t theta1_sat = __SSAT(theta1_int, 16); + *__SIMD32(dst_p)++ = __PKHBT( + theta0_sat, + theta1_sat, + 16 + ); + } + z_ = z; + + return { dst.p, src.count, src.sampling_rate }; +} + +} +} diff --git a/firmware/baseband-tx/dsp_demodulate.hpp b/firmware/baseband-tx/dsp_demodulate.hpp new file mode 100644 index 00000000..c5f871e1 --- /dev/null +++ b/firmware/baseband-tx/dsp_demodulate.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __DSP_DEMODULATE_H__ +#define __DSP_DEMODULATE_H__ + +#include "dsp_types.hpp" + +namespace dsp { +namespace demodulate { + +class AM { +public: + buffer_s16_t execute( + buffer_c16_t src, + buffer_s16_t dst + ); +}; + +class FM { +public: + /* + * angle: -pi to pi. output range: -32768 to 32767. + * Maximum delta-theta (output of atan2) at maximum deviation frequency: + * delta_theta_max = 2 * pi * deviation / sampling_rate + */ + constexpr FM( + const float sampling_rate, + const float deviation_hz + ) : z_ { 0 }, + k { static_cast(32767.0f / (2.0 * pi * deviation_hz / sampling_rate)) } + { + } + + buffer_s16_t execute( + buffer_c16_t src, + buffer_s16_t dst + ); + + void configure(const float sampling_rate, const float deviation_hz) { + k = static_cast(32767.0f / (2.0 * pi * deviation_hz / sampling_rate)); + } + +private: + complex16_t::rep_type z_; + float k; +}; + +} /* namespace demodulate */ +} /* namespace dsp */ + +#endif/*__DSP_DEMODULATE_H__*/ diff --git a/firmware/baseband-tx/dsp_fir_taps.cpp b/firmware/baseband-tx/dsp_fir_taps.cpp new file mode 100644 index 00000000..b8bed3f2 --- /dev/null +++ b/firmware/baseband-tx/dsp_fir_taps.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "dsp_fir_taps.hpp" diff --git a/firmware/baseband-tx/dsp_fir_taps.hpp b/firmware/baseband-tx/dsp_fir_taps.hpp new file mode 100644 index 00000000..9839a9de --- /dev/null +++ b/firmware/baseband-tx/dsp_fir_taps.hpp @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __DSP_FIR_TAPS_H__ +#define __DSP_FIR_TAPS_H__ + +#include +#include + +#include "complex.hpp" + +template +struct fir_taps_real { + float pass_frequency_normalized; + float stop_frequency_normalized; + std::array taps; +}; + +/* 3kHz/6.7kHz @ 96kHz. sum(abs(taps)): 89429 */ +constexpr fir_taps_real<64> taps_64_lp_031_070_tfilter { + .pass_frequency_normalized = 0.031f, + .stop_frequency_normalized = 0.070f, + .taps = { { + 56, 58, 81, 100, 113, 112, 92, 49, + -21, -120, -244, -389, -543, -692, -819, -903, + -923, -861, -698, -424, -34, 469, 1073, 1756, + 2492, 3243, 3972, 4639, 5204, 5634, 5903, 5995, + 5903, 5634, 5204, 4639, 3972, 3243, 2492, 1756, + 1073, 469, -34, -424, -698, -861, -923, -903, + -819, -692, -543, -389, -244, -120, -21, 49, + 92, 112, 113, 100, 81, 58, 56, 0, + } }, +}; + +/* 4kHz/7.5kHz @ 96kHz. sum(abs(taps)): 96783 */ +constexpr fir_taps_real<64> taps_64_lp_042_078_tfilter { + .pass_frequency_normalized = 0.042f, + .stop_frequency_normalized = 0.078f, + .taps = { { + -19, 39, 72, 126, 197, 278, 360, 432, + 478, 485, 438, 327, 152, -82, -359, -651, + -922, -1132, -1236, -1192, -968, -545, 81, 892, + 1852, 2906, 3984, 5012, 5910, 6609, 7053, 7205, + 7053, 6609, 5910, 5012, 3984, 2906, 1852, 892, + 81, -545, -968, -1192, -1236, -1132, -922, -651, + -359, -82, 152, 327, 438, 485, 478, 432, + 360, 278, 197, 126, 72, 39, -19, 0, + } }, +}; + +/* 5kHz/8.5kHz @ 96kHz. sum(abs(taps)): 101312 */ +constexpr fir_taps_real<64> taps_64_lp_052_089_tfilter { + .pass_frequency_normalized = 0.052f, + .stop_frequency_normalized = 0.089f, + .taps = { { + -65, -88, -129, -163, -178, -160, -100, 9, + 160, 340, 523, 675, 758, 738, 591, 313, + -76, -533, -987, -1355, -1544, -1472, -1077, -335, + 738, 2078, 3579, 5104, 6502, 7627, 8355, 8608, + 8355, 7627, 6502, 5104, 3579, 2078, 738, -335, + -1077, -1472, -1544, -1355, -987, -533, -76, 313, + 591, 738, 758, 675, 523, 340, 160, 9, + -100, -160, -178, -163, -129, -88, -65, 0, + } }, +}; + +/* 6kHz/9.6kHz @ 96kHz. sum(abs(taps)): 105088 */ +constexpr fir_taps_real<64> taps_64_lp_063_100_tfilter { + .pass_frequency_normalized = 0.063f, + .stop_frequency_normalized = 0.100f, + .taps = { { + 43, 21, -2, -54, -138, -245, -360, -453, + -493, -451, -309, -73, 227, 535, 776, 876, + 773, 443, -86, -730, -1357, -1801, -1898, -1515, + -585, 869, 2729, 4794, 6805, 8490, 9611, 10004, + 9611, 8490, 6805, 4794, 2729, 869, -585, -1515, + -1898, -1801, -1357, -730, -86, 443, 773, 876, + 776, 535, 227, -73, -309, -451, -493, -453, + -360, -245, -138, -54, -2, 21, 43, 0, + } }, +}; + +/* 7kHz/10.4kHz @ 96kHz: sum(abs(taps)): 110157 */ +constexpr fir_taps_real<64> taps_64_lp_073_108_tfilter { + .pass_frequency_normalized = 0.073f, + .stop_frequency_normalized = 0.108f, + .taps = { { + 79, 145, 241, 334, 396, 394, 306, 130, + -109, -360, -550, -611, -494, -197, 229, 677, + 1011, 1096, 846, 257, -570, -1436, -2078, -2225, + -1670, -327, 1726, 4245, 6861, 9146, 10704, 11257, + 10704, 9146, 6861, 4245, 1726, -327, -1670, -2225, + -2078, -1436, -570, 257, 846, 1096, 1011, 677, + 229, -197, -494, -611, -550, -360, -109, 130, + 306, 394, 396, 334, 241, 145, 79, 0, + } }, +}; + +/* 8kHz/11.5kHz @ 96kHz. sum(abs(taps)): 112092 */ +constexpr fir_taps_real<64> taps_64_lp_083_120_tfilter { + .pass_frequency_normalized = 0.083f, + .stop_frequency_normalized = 0.120f, + .taps = { { + -63, -72, -71, -21, 89, 248, 417, 537, + 548, 407, 124, -237, -563, -723, -621, -238, + 337, 919, 1274, 1201, 617, -382, -1514, -2364, + -2499, -1600, 414, 3328, 6651, 9727, 11899, 12682, + 11899, 9727, 6651, 3328, 414, -1600, -2499, -2364, + -1514, -382, 617, 1201, 1274, 919, 337, -238, + -621, -723, -563, -237, 124, 407, 548, 537, + 417, 248, 89, -21, -71, -72, -63, 0, + } }, +}; + +/* 9kHz/12.4kHz @ 96kHz. sum(abs(taps)): 116249 */ +constexpr fir_taps_real<64> taps_64_lp_094_129_tfilter { + .pass_frequency_normalized = 0.094f, + .stop_frequency_normalized = 0.129f, + .taps = { { + 5, -93, -198, -335, -449, -478, -378, -144, + 166, 444, 563, 440, 82, -395, -788, -892, + -589, 73, 859, 1421, 1431, 734, -530, -1919, + -2798, -2555, -837, 2274, 6220, 10103, 12941, 13981, + 12941, 10103, 6220, 2274, -837, -2555, -2798, -1919, + -530, 734, 1431, 1421, 859, 73, -589, -892, + -788, -395, 82, 440, 563, 444, 166, -144, + -378, -478, -449, -335, -198, -93, 5, 0, + } }, +}; + +/* 10kHz/13.4kHz @ 96kHz. sum(abs(taps)): 118511 */ +constexpr fir_taps_real<64> taps_64_lp_104_140_tfilter { + .pass_frequency_normalized = 0.104f, + .stop_frequency_normalized = 0.140f, + .taps = { { + 89, 159, 220, 208, 84, -147, -412, -597, + -588, -345, 58, 441, 595, 391, -128, -730, + -1080, -914, -198, 793, 1558, 1594, 678, -942, + -2546, -3187, -2084, 992, 5515, 10321, 13985, 15353, + 13985, 10321, 5515, 992, -2084, -3187, -2546, -942, + 678, 1594, 1558, 793, -198, -914, -1080, -730, + -128, 391, 595, 441, 58, -345, -588, -597, + -412, -147, 84, 208, 220, 159, 89, 0, + } }, +}; + +/* Wideband FM channel filter + * 103kHz/128kHz @ 768kHz + */ +constexpr fir_taps_real<64> taps_64_lp_130_169_tfilter { + .pass_frequency_normalized = 0.130f, + .stop_frequency_normalized = 0.169f, + .taps = { { + 100, 127, 62, -157, -470, -707, -678, -332, + 165, 494, 400, -85, -610, -729, -253, 535, + 1026, 734, -263, -1264, -1398, -332, 1316, 2259, + 1447, -988, -3474, -3769, -385, 6230, 13607, 18450, + 18450, 13607, 6230, -385, -3769, -3474, -988, 1447, + 2259, 1316, -332, -1398, -1264, -263, 734, 1026, + 535, -253, -729, -610, -85, 400, 494, 165, + -332, -678, -707, -470, -157, 62, 127, 100, + } }, +}; + +/* Wideband audio filter */ +/* 96kHz int16_t input + * -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop + * -> 48kHz int16_t output, gain of 1.0 (I think). + * Padded to multiple of four taps for unrolled FIR code. + * sum(abs(taps)): 125270 + */ +constexpr fir_taps_real<64> taps_64_lp_156_198 { + .pass_frequency_normalized = 0.156f, + .stop_frequency_normalized = 0.196f, + .taps = { { + -27, 166, 104, -36, -174, -129, 109, 287, + 148, -232, -430, -130, 427, 597, 49, -716, + -778, 137, 1131, 957, -493, -1740, -1121, 1167, + 2733, 1252, -2633, -4899, -1336, 8210, 18660, 23254, + 18660, 8210, -1336, -4899, -2633, 1252, 2733, 1167, + -1121, -1740, -493, 957, 1131, 137, -778, -716, + 49, 597, 427, -130, -430, -232, 148, 287, + 109, -129, -174, -36, 104, 166, -27, 0, + } }, +}; + +#endif/*__DSP_FIR_TAPS_H__*/ diff --git a/firmware/baseband-tx/dsp_iir.hpp b/firmware/baseband-tx/dsp_iir.hpp new file mode 100644 index 00000000..8fb12f05 --- /dev/null +++ b/firmware/baseband-tx/dsp_iir.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __DSP_IIR_H__ +#define __DSP_IIR_H__ + +#include + +#include "dsp_types.hpp" + +struct iir_biquad_config_t { + const std::array b; + const std::array a; +}; + +class IIRBiquadFilter { +public: + // http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt + + // Assume all coefficients are normalized so that a0=1.0 + constexpr IIRBiquadFilter( + const iir_biquad_config_t& config + ) : config(config) + { + } + + void execute(buffer_s16_t buffer_in, buffer_s16_t buffer_out) { + // TODO: Assert that buffer_out.count == buffer_in.count. + for(size_t i=0; i x { { 0.0f, 0.0f, 0.0f } }; + std::array y { { 0.0f, 0.0f, 0.0f } }; + + float execute_sample(const float in) { + x[0] = x[1]; + x[1] = x[2]; + x[2] = in; + + y[0] = y[1]; + y[1] = y[2]; + y[2] = config.b[0] * x[2] + config.b[1] * x[1] + config.b[2] * x[0] + - config.a[1] * y[1] - config.a[2] * y[0]; + + return y[2]; + } +}; + +#endif/*__DSP_IIR_H__*/ diff --git a/firmware/baseband-tx/dsp_iir_config.hpp b/firmware/baseband-tx/dsp_iir_config.hpp new file mode 100644 index 00000000..e20a51fc --- /dev/null +++ b/firmware/baseband-tx/dsp_iir_config.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __DSP_IIR_CONFIG_H__ +#define __DSP_IIR_CONFIG_H__ + +#include "dsp_iir.hpp" + +constexpr iir_biquad_config_t audio_hpf_config { + { 0.93346032f, -1.86687724f, 0.93346032f }, + { 1.0f , -1.97730264f, 0.97773668f } +}; + +constexpr iir_biquad_config_t non_audio_hpf_config { + { 0.51891061f, -0.95714180f, 0.51891061f }, + { 1.0f , -0.79878302f, 0.43960231f } +}; + +#endif/*__DSP_IIR_CONFIG_H__*/ diff --git a/firmware/baseband-tx/dsp_squelch.cpp b/firmware/baseband-tx/dsp_squelch.cpp new file mode 100644 index 00000000..4cfe651a --- /dev/null +++ b/firmware/baseband-tx/dsp_squelch.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "dsp_squelch.hpp" + +#include +#include + +bool FMSquelch::execute(buffer_s16_t audio) { + // TODO: No hard-coded array size. + std::array squelch_energy_buffer; + const buffer_s16_t squelch_energy { + squelch_energy_buffer.data(), + squelch_energy_buffer.size() + }; + non_audio_hpf.execute(audio, squelch_energy); + + uint64_t max_squared = 0; + for(const auto sample : squelch_energy_buffer) { + const uint64_t sample_squared = sample * sample; + if( sample_squared > max_squared ) { + max_squared = sample_squared; + } + } + + return (max_squared < (threshold * threshold)); +} diff --git a/firmware/baseband-tx/dsp_squelch.hpp b/firmware/baseband-tx/dsp_squelch.hpp new file mode 100644 index 00000000..06cb2cdd --- /dev/null +++ b/firmware/baseband-tx/dsp_squelch.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __DSP_SQUELCH_H__ +#define __DSP_SQUELCH_H__ + +#include "buffer.hpp" +#include "dsp_iir.hpp" +#include "dsp_iir_config.hpp" + +#include +#include + +class FMSquelch { +public: + bool execute(buffer_s16_t audio); + +private: + static constexpr size_t N = 32; + static constexpr int16_t threshold = 3072; + + // nyquist = 48000 / 2.0 + // scipy.signal.iirdesign(wp=8000 / nyquist, ws= 4000 / nyquist, gpass=1, gstop=18, ftype='ellip') + IIRBiquadFilter non_audio_hpf { non_audio_hpf_config }; +}; + +#endif/*__DSP_SQUELCH_H__*/ diff --git a/firmware/baseband-tx/event_m4.cpp b/firmware/baseband-tx/event_m4.cpp new file mode 100644 index 00000000..50b0c0b7 --- /dev/null +++ b/firmware/baseband-tx/event_m4.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "event_m4.hpp" + +#include "ch.h" + +Thread* thread_event_loop = nullptr; + +void events_initialize(Thread* const event_loop_thread) { + thread_event_loop = event_loop_thread; +} diff --git a/firmware/baseband-tx/event_m4.hpp b/firmware/baseband-tx/event_m4.hpp new file mode 100644 index 00000000..be113ca8 --- /dev/null +++ b/firmware/baseband-tx/event_m4.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __EVENT_M4_H__ +#define __EVENT_M4_H__ + +#include "ch.h" + +constexpr auto EVT_MASK_BASEBAND = EVENT_MASK(0); +constexpr auto EVT_MASK_SPECTRUM = EVENT_MASK(1); + +void events_initialize(Thread* const event_loop_thread); + +extern Thread* thread_event_loop; + +inline void events_flag(const eventmask_t events) { + if( thread_event_loop ) { + chEvtSignal(thread_event_loop, events); + } +} + +inline void events_flag_isr(const eventmask_t events) { + if( thread_event_loop ) { + chEvtSignalI(thread_event_loop, events); + } +} + +#endif/*__EVENT_M4_H__*/ diff --git a/firmware/baseband-tx/fxpt_atan2.cpp b/firmware/baseband-tx/fxpt_atan2.cpp new file mode 100644 index 00000000..a3531f79 --- /dev/null +++ b/firmware/baseband-tx/fxpt_atan2.cpp @@ -0,0 +1,152 @@ +/* + * fxpt_atan2.c + * + * Copyright (C) 2012, Xo Wang + * + * Hacked up to be a bit more ARM-friendly by: + * Copyright (C) 2013 Jared Boone, ShareBrained Technology, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include +#include + +/** + * Convert floating point to Q15 (1.0.15 fixed point) format. + * + * @param d floating-point value within range -1 to (1 - (2**-15)), inclusive + * @return Q15 value representing d; same range + */ +/* +static inline int16_t q15_from_double(const double d) { + return lrint(d * 32768); +} +*/ +/** + * Negative absolute value. Used to avoid undefined behavior for most negative + * integer (see C99 standard 7.20.6.1.2 and footnote 265 for the description of + * abs/labs/llabs behavior). + * + * @param i 16-bit signed integer + * @return negative absolute value of i; defined for all values of i + */ + /* +static inline int16_t s16_nabs(const int16_t j) { +#if (((int16_t)-1) >> 1) == ((int16_t)-1) + // signed right shift sign-extends (arithmetic) + const int16_t negSign = ~(j >> 15); // splat sign bit into all 16 and complement + // if j is positive (negSign is -1), xor will invert j and sub will add 1 + // otherwise j is unchanged + return (j ^ negSign) - negSign; +#else + return (j < 0 ? j : -j); +#endif +} +*/ +/** + * Q15 (1.0.15 fixed point) multiplication. Various common rounding modes are in + * the function definition for reference (and preference). + * + * @param j 16-bit signed integer representing -1 to (1 - (2**-15)), inclusive + * @param k same format as j + * @return product of j and k, in same format + */ +static inline int16_t q15_mul(const int16_t j, const int16_t k) { + const int32_t intermediate = j * k; +#if 0 // don't round + return intermediate >> 15; +#elif 0 // biased rounding + return (intermediate + 0x4000) >> 15; +#else // unbiased rounding + return (intermediate + ((intermediate & 0x7FFF) == 0x4000 ? 0 : 0x4000)) >> 15; +#endif +} + +/** + * Q15 (1.0.15 fixed point) division (non-saturating). Be careful when using + * this function, as it does not behave well when the result is out-of-range. + * + * Value is not defined if numerator is greater than or equal to denominator. + * + * @param numer 16-bit signed integer representing -1 to (1 - (2**-15)) + * @param denom same format as numer; must be greater than numerator + * @return numer / denom in same format as numer and denom + */ +static inline int16_t q15_div(const int16_t numer, const int16_t denom) { + return (static_cast(numer) << 15) / denom; +} + +/** + * 16-bit fixed point four-quadrant arctangent. Given some Cartesian vector + * (x, y), find the angle subtended by the vector and the positive x-axis. + * + * The value returned is in units of 1/65536ths of one turn. This allows the use + * of the full 16-bit unsigned range to represent a turn. e.g. 0x0000 is 0 + * radians, 0x8000 is pi radians, and 0xFFFF is (65535 / 32768) * pi radians. + * + * Because the magnitude of the input vector does not change the angle it + * represents, the inputs can be in any signed 16-bit fixed-point format. + * + * @param y y-coordinate in signed 16-bit + * @param x x-coordinate in signed 16-bit + * @return angle in (val / 32768) * pi radian increments from 0x0000 to 0xFFFF + */ + +static inline int16_t nabs(const int16_t j) { + //return -abs(x); + return (j < 0 ? j : -j); +} + +int16_t fxpt_atan2(const int16_t y, const int16_t x) { + static const int16_t k1 = 2847; + static const int16_t k2 = 11039; + if (x == y) { // x/y or y/x would return -1 since 1 isn't representable + if (y > 0) { // 1/8 + return 8192; + } else if (y < 0) { // 5/8 + return 40960; + } else { // x = y = 0 + return 0; + } + } + const int16_t nabs_y = nabs(y); + const int16_t nabs_x = nabs(x); + if (nabs_x < nabs_y) { // octants 1, 4, 5, 8 + const int16_t y_over_x = q15_div(y, x); + const int16_t correction = q15_mul(k1, nabs(y_over_x)); + const int16_t unrotated = q15_mul(k2 + correction, y_over_x); + if (x > 0) { // octants 1, 8 + return unrotated; + } else { // octants 4, 5 + return 32768 + unrotated; + } + } else { // octants 2, 3, 6, 7 + const int16_t x_over_y = q15_div(x, y); + const int16_t correction = q15_mul(k1, nabs(x_over_y)); + const int16_t unrotated = q15_mul(k2 + correction, x_over_y); + if (y > 0) { // octants 2, 3 + return 16384 - unrotated; + } else { // octants 6, 7 + return 49152 - unrotated; + } + } +} diff --git a/firmware/baseband-tx/fxpt_atan2.hpp b/firmware/baseband-tx/fxpt_atan2.hpp new file mode 100644 index 00000000..b57d14e2 --- /dev/null +++ b/firmware/baseband-tx/fxpt_atan2.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __FXPT_ATAN2_H__ +#define __FXPT_ATAN2_H__ + +#include + +int16_t fxpt_atan2(const int16_t y, const int16_t x); + +#endif/*__FXPT_ATAN2_H__*/ diff --git a/firmware/baseband-tx/gpdma_lli.hpp b/firmware/baseband-tx/gpdma_lli.hpp new file mode 100644 index 00000000..3bde2371 --- /dev/null +++ b/firmware/baseband-tx/gpdma_lli.hpp @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include +#include + +#include "gpdma.hpp" + +namespace lpc43xx { +namespace gpdma { +namespace lli { + +enum class ChainType : uint8_t { + Loop = 0, + OneShot = 1, +}; + +enum class Interrupt : uint8_t { + All = 0, + Last = 1, +}; + +struct ChainConfig { + ChainType type; + size_t length; + Interrupt interrupt; +}; + +enum class BurstSize : uint8_t { + Transfer1 = 0, + Transfer4 = 1, + Transfer8 = 2, + Transfer16 = 3, + Transfer32 = 4, + Transfer64 = 5, + Transfer128 = 6, + Transfer256 = 7, +}; + +enum class TransferWidth : uint8_t { + Byte = 0, + HalfWord = 1, + Word = 2, +}; + +enum class Increment : uint8_t { + No = 0, + Yes = 1, +}; + +using PeripheralIndex = uint8_t; + +struct Endpoint { + PeripheralIndex peripheral; + BurstSize burst_size; + TransferWidth transfer_size; + Increment increment; +}; + +struct ChannelConfig { + ChainConfig chain; + FlowControl flow_control; + Endpoint source; + Endpoint destination; + + constexpr gpdma::channel::Control control( + const size_t transfer_size, + const bool last + ) { + return { + .transfersize = transfer_size, + .sbsize = toUType(source.burst_size), + .dbsize = toUType(destination.burst_size), + .swidth = toUType(source.transfer_size), + .dwidth = toUType(destination.transfer_size), + .s = source_endpoint_type(flow_control), + .d = destination_endpoint_type(flow_control), + .si = toUType(source.increment), + .di = toUType(destination.increment), + .prot1 = 0, + .prot2 = 0, + .prot3 = 0, + .i = ((chain.interrupt == Interrupt::All) || last) ? 1U : 0U, + }; + } + + constexpr gpdma::channel::Config config() { + return { + .e = 0, + .srcperipheral = source.peripheral, + .destperipheral = destination.peripheral, + .flowcntrl = flow_control, + .ie = 1, + .itc = 1, + .l = 0, + .a = 0, + .h = 0, + }; + }; +}; + +constexpr ChannelConfig channel_config_baseband_tx { + { ChainType::Loop, 4, Interrupt::All }, + gpdma::FlowControl::MemoryToPeripheral_DMAControl, + { 0x00, BurstSize::Transfer1, TransferWidth::Word, Increment::Yes }, + { 0x00, BurstSize::Transfer1, TransferWidth::Word, Increment::No }, +}; + +constexpr ChannelConfig channel_config_baseband_rx { + { ChainType::Loop, 4, Interrupt::All }, + gpdma::FlowControl::PeripheralToMemory_DMAControl, + { 0x00, BurstSize::Transfer1, TransferWidth::Word, Increment::No }, + { 0x00, BurstSize::Transfer1, TransferWidth::Word, Increment::Yes }, +}; + +constexpr ChannelConfig channel_config_audio_tx { + { ChainType::Loop, 4, Interrupt::All }, + gpdma::FlowControl::MemoryToPeripheral_DMAControl, + { 0x0a, BurstSize::Transfer32, TransferWidth::Word, Increment::Yes }, + { 0x0a, BurstSize::Transfer32, TransferWidth::Word, Increment::No }, +}; + +constexpr ChannelConfig channel_config_audio_rx { + { ChainType::Loop, 4, Interrupt::All }, + gpdma::FlowControl::PeripheralToMemory_DMAControl, + { 0x09, BurstSize::Transfer32, TransferWidth::Word, Increment::No }, + { 0x09, BurstSize::Transfer32, TransferWidth::Word, Increment::Yes }, +}; + +constexpr ChannelConfig channel_config_rssi { + { ChainType::Loop, 4, Interrupt::All }, + gpdma::FlowControl::PeripheralToMemory_DMAControl, + { 0x0e, BurstSize::Transfer1, TransferWidth::Byte, Increment::No }, + { 0x0e, BurstSize::Transfer1, TransferWidth::Word, Increment::Yes }, +}; + +class Chain { +public: + using chain_t = std::vector; + using chain_p = std::unique_ptr; + + Chain(const ChannelConfig& cc) : + chain(std::make_unique(cc.chain.length)) + { + set_lli_sequential(cc.chain_type); + set_source_address()... + } + +private: + chain_p chain; + + void set_source_peripheral(void* const address) { + set_source_address(address, 0); + } + + void set_destination_peripheral(void* const address) { + set_destination_address(address, 0); + } + + void set_source_address(void* const address, const size_t increment) { + size_t offset = 0; + for(auto& item : *chain) { + item.srcaddr = (uint32_t)address + offset; + offset += increment; + } + } + + void set_destination_address(void* const address, const size_t increment) { + size_t offset = 0; + for(auto& item : *chain) { + item.destaddr = (uint32_t)address + offset; + offset += increment; + } + } + + void set_control(const gpdma::channel::Control control) { + for(auto& item : *chain) { + item.control = control; + } + } + + void set_lli_sequential(ChainType chain_type) { + for(auto& item : *chain) { + item.lli = lli_pointer(&item + 1); + } + if( chain_type == ChainType::Loop ) { + chain[chain->size() - 1].lli = lli_pointer(&chain[0]); + } else { + chain[chain->size() - 1].lli = lli_pointer(nullptr); + } + } + + gpdma::channel::LLIPointer lli_pointer(const void* lli) { + return { + .lm = 0, + .r = 0, + .lli = reinterpret_cast(lli), + }; + } +}; + +} /* namespace lli */ +} /* namespace gpdma */ +} /* namespace lpc43xx */ diff --git a/firmware/baseband-tx/halconf.h b/firmware/baseband-tx/halconf.h new file mode 100755 index 00000000..658d5869 --- /dev/null +++ b/firmware/baseband-tx/halconf.h @@ -0,0 +1,313 @@ +/* + ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio + Copyright (C) 2014 Jared Boone, ShareBrained Technology + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file templates/halconf.h + * @brief HAL configuration header. + * @details HAL configuration file, this file allows to enable or disable the + * various device drivers from your application. You may also use + * this file in order to override the device drivers default settings. + * + * @addtogroup HAL_CONF + * @{ + */ + +#ifndef _HALCONF_H_ +#define _HALCONF_H_ + +#include "mcuconf.h" + +/** + * @brief Enables the TM subsystem. + */ +#if !defined(HAL_USE_TM) || defined(__DOXYGEN__) +#define HAL_USE_TM FALSE +#endif + +/** + * @brief Enables the PAL subsystem. + */ +#if !defined(HAL_USE_PAL) || defined(__DOXYGEN__) +#define HAL_USE_PAL FALSE +#endif + +/** + * @brief Enables the ADC subsystem. + */ +#if !defined(HAL_USE_ADC) || defined(__DOXYGEN__) +#define HAL_USE_ADC FALSE +#endif + +/** + * @brief Enables the CAN subsystem. + */ +#if !defined(HAL_USE_CAN) || defined(__DOXYGEN__) +#define HAL_USE_CAN FALSE +#endif + +/** + * @brief Enables the EXT subsystem. + */ +#if !defined(HAL_USE_EXT) || defined(__DOXYGEN__) +#define HAL_USE_EXT FALSE +#endif + +/** + * @brief Enables the GPT subsystem. + */ +#if !defined(HAL_USE_GPT) || defined(__DOXYGEN__) +#define HAL_USE_GPT FALSE +#endif + +/** + * @brief Enables the I2C subsystem. + */ +#if !defined(HAL_USE_I2C) || defined(__DOXYGEN__) +#define HAL_USE_I2C FALSE +#endif + +/** + * @brief Enables the ICU subsystem. + */ +#if !defined(HAL_USE_ICU) || defined(__DOXYGEN__) +#define HAL_USE_ICU FALSE +#endif + +/** + * @brief Enables the MAC subsystem. + */ +#if !defined(HAL_USE_MAC) || defined(__DOXYGEN__) +#define HAL_USE_MAC FALSE +#endif + +/** + * @brief Enables the MMC_SPI subsystem. + */ +#if !defined(HAL_USE_MMC_SPI) || defined(__DOXYGEN__) +#define HAL_USE_MMC_SPI FALSE +#endif + +/** + * @brief Enables the PWM subsystem. + */ +#if !defined(HAL_USE_PWM) || defined(__DOXYGEN__) +#define HAL_USE_PWM FALSE +#endif + +/** + * @brief Enables the RTC subsystem. + */ +#if !defined(HAL_USE_RTC) || defined(__DOXYGEN__) +#define HAL_USE_RTC FALSE +#endif + +/** + * @brief Enables the SDC subsystem. + */ +#if !defined(HAL_USE_SDC) || defined(__DOXYGEN__) +#define HAL_USE_SDC FALSE +#endif + +/** + * @brief Enables the SERIAL subsystem. + */ +#if !defined(HAL_USE_SERIAL) || defined(__DOXYGEN__) +#define HAL_USE_SERIAL FALSE +#endif + +/** + * @brief Enables the SERIAL over USB subsystem. + */ +#if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) +#define HAL_USE_SERIAL_USB FALSE +#endif + +/** + * @brief Enables the SPI subsystem. + */ +#if !defined(HAL_USE_SPI) || defined(__DOXYGEN__) +#define HAL_USE_SPI FALSE +#endif + +/** + * @brief Enables the UART subsystem. + */ +#if !defined(HAL_USE_UART) || defined(__DOXYGEN__) +#define HAL_USE_UART FALSE +#endif + +/** + * @brief Enables the USB subsystem. + */ +#if !defined(HAL_USE_USB) || defined(__DOXYGEN__) +#define HAL_USE_USB FALSE +#endif + +/*===========================================================================*/ +/* ADC driver related settings. */ +/*===========================================================================*/ + +/** + * @brief Enables synchronous APIs. + * @note Disabling this option saves both code and data space. + */ +#if !defined(ADC_USE_WAIT) || defined(__DOXYGEN__) +#define ADC_USE_WAIT TRUE +#endif + +/** + * @brief Enables the @p adcAcquireBus() and @p adcReleaseBus() APIs. + * @note Disabling this option saves both code and data space. + */ +#if !defined(ADC_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__) +#define ADC_USE_MUTUAL_EXCLUSION TRUE +#endif + +/*===========================================================================*/ +/* CAN driver related settings. */ +/*===========================================================================*/ + +/** + * @brief Sleep mode related APIs inclusion switch. + */ +#if !defined(CAN_USE_SLEEP_MODE) || defined(__DOXYGEN__) +#define CAN_USE_SLEEP_MODE TRUE +#endif + +/*===========================================================================*/ +/* I2C driver related settings. */ +/*===========================================================================*/ + +/** + * @brief Enables the mutual exclusion APIs on the I2C bus. + */ +#if !defined(I2C_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__) +#define I2C_USE_MUTUAL_EXCLUSION TRUE +#endif + +/*===========================================================================*/ +/* MAC driver related settings. */ +/*===========================================================================*/ + +/** + * @brief Enables an event sources for incoming packets. + */ +#if !defined(MAC_USE_ZERO_COPY) || defined(__DOXYGEN__) +#define MAC_USE_ZERO_COPY FALSE +#endif + +/** + * @brief Enables an event sources for incoming packets. + */ +#if !defined(MAC_USE_EVENTS) || defined(__DOXYGEN__) +#define MAC_USE_EVENTS TRUE +#endif + +/*===========================================================================*/ +/* MMC_SPI driver related settings. */ +/*===========================================================================*/ + +/** + * @brief Delays insertions. + * @details If enabled this options inserts delays into the MMC waiting + * routines releasing some extra CPU time for the threads with + * lower priority, this may slow down the driver a bit however. + * This option is recommended also if the SPI driver does not + * use a DMA channel and heavily loads the CPU. + */ +#if !defined(MMC_NICE_WAITING) || defined(__DOXYGEN__) +#define MMC_NICE_WAITING TRUE +#endif + +/*===========================================================================*/ +/* SDC driver related settings. */ +/*===========================================================================*/ + +/** + * @brief Number of initialization attempts before rejecting the card. + * @note Attempts are performed at 10mS intervals. + */ +#if !defined(SDC_INIT_RETRY) || defined(__DOXYGEN__) +#define SDC_INIT_RETRY 100 +#endif + +/** + * @brief Include support for MMC cards. + * @note MMC support is not yet implemented so this option must be kept + * at @p FALSE. + */ +#if !defined(SDC_MMC_SUPPORT) || defined(__DOXYGEN__) +#define SDC_MMC_SUPPORT FALSE +#endif + +/** + * @brief Delays insertions. + * @details If enabled this options inserts delays into the MMC waiting + * routines releasing some extra CPU time for the threads with + * lower priority, this may slow down the driver a bit however. + */ +#if !defined(SDC_NICE_WAITING) || defined(__DOXYGEN__) +#define SDC_NICE_WAITING TRUE +#endif + +/*===========================================================================*/ +/* SERIAL driver related settings. */ +/*===========================================================================*/ + +/** + * @brief Default bit rate. + * @details Configuration parameter, this is the baud rate selected for the + * default configuration. + */ +#if !defined(SERIAL_DEFAULT_BITRATE) || defined(__DOXYGEN__) +#define SERIAL_DEFAULT_BITRATE 38400 +#endif + +/** + * @brief Serial buffers size. + * @details Configuration parameter, you can change the depth of the queue + * buffers depending on the requirements of your application. + * @note The default is 64 bytes for both the transmission and receive + * buffers. + */ +#if !defined(SERIAL_BUFFERS_SIZE) || defined(__DOXYGEN__) +#define SERIAL_BUFFERS_SIZE 16 +#endif + +/*===========================================================================*/ +/* SPI driver related settings. */ +/*===========================================================================*/ + +/** + * @brief Enables synchronous APIs. + * @note Disabling this option saves both code and data space. + */ +#if !defined(SPI_USE_WAIT) || defined(__DOXYGEN__) +#define SPI_USE_WAIT TRUE +#endif + +/** + * @brief Enables the @p spiAcquireBus() and @p spiReleaseBus() APIs. + * @note Disabling this option saves both code and data space. + */ +#if !defined(SPI_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__) +#define SPI_USE_MUTUAL_EXCLUSION TRUE +#endif + +#endif /* _HALCONF_H_ */ + +/** @} */ diff --git a/firmware/baseband-tx/irq_ipc_m4.cpp b/firmware/baseband-tx/irq_ipc_m4.cpp new file mode 100644 index 00000000..07a75335 --- /dev/null +++ b/firmware/baseband-tx/irq_ipc_m4.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "irq_ipc_m4.hpp" + +#include "ch.h" +#include "hal.h" + +#include "event_m4.hpp" + +#include "lpc43xx_cpp.hpp" +using namespace lpc43xx; + +void m0apptxevent_interrupt_enable() { + nvicEnableVector(M0CORE_IRQn, CORTEX_PRIORITY_MASK(LPC43XX_M0APPTXEVENT_IRQ_PRIORITY)); +} + +void m0apptxevent_interrupt_disable() { + nvicDisableVector(M0CORE_IRQn); +} + +extern "C" { + +CH_IRQ_HANDLER(MAPP_IRQHandler) { + CH_IRQ_PROLOGUE(); + + chSysLockFromIsr(); + events_flag_isr(EVT_MASK_BASEBAND); + chSysUnlockFromIsr(); + + creg::m0apptxevent::clear(); + + CH_IRQ_EPILOGUE(); +} + +} diff --git a/firmware/baseband-tx/irq_ipc_m4.hpp b/firmware/baseband-tx/irq_ipc_m4.hpp new file mode 100644 index 00000000..61328685 --- /dev/null +++ b/firmware/baseband-tx/irq_ipc_m4.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __IRQ_IPC_M4_H__ +#define __IRQ_IPC_M4_H__ + +void m0apptxevent_interrupt_enable(); +void m0apptxevent_interrupt_disable(); + +#endif/*__IRQ_IPC_M4_H__*/ diff --git a/firmware/baseband-tx/linear_resampler.hpp b/firmware/baseband-tx/linear_resampler.hpp new file mode 100644 index 00000000..d889a2c2 --- /dev/null +++ b/firmware/baseband-tx/linear_resampler.hpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __LINEAR_RESAMPLER_H__ +#define __LINEAR_RESAMPLER_H__ + +namespace dsp { +namespace interpolation { + +class LinearResampler { +public: + void configure( + const float input_rate, + const float output_rate + ) { + phase_increment = calculate_increment(input_rate, output_rate); + } + + template + void operator()( + const float sample, + InterpolatedSampleHandler interpolated_sample_handler + ) { + const float sample_delta = sample - last_sample; + while( phase < 1.0f ) { + const float interpolated_value = last_sample + phase * sample_delta; + interpolated_sample_handler(interpolated_value); + phase += phase_increment; + } + last_sample = sample; + phase -= 1.0f; + } + + void advance(const float fraction) { + phase += (fraction * phase_increment); + } + +private: + float last_sample { 0.0f }; + float phase { 0.0f }; + float phase_increment { 0.0f }; + + static constexpr float calculate_increment(const float input_rate, const float output_rate) { + return input_rate / output_rate; + } +}; + +} /* namespace interpolation */ +} /* namespace dsp */ + +#endif/*__LINEAR_RESAMPLER_H__*/ diff --git a/firmware/baseband-tx/main.cpp b/firmware/baseband-tx/main.cpp new file mode 100755 index 00000000..c5c6de20 --- /dev/null +++ b/firmware/baseband-tx/main.cpp @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ch.h" +#include "test.h" + +#include "lpc43xx_cpp.hpp" + +#include "portapack_shared_memory.hpp" +#include "portapack_dma.hpp" + +#include "gpdma.hpp" + +#include "baseband.hpp" +#include "baseband_dma.hpp" + +#include "event_m4.hpp" + +#include "irq_ipc_m4.hpp" + +#include "touch_dma.hpp" + +#include "dsp_decimate.hpp" +#include "dsp_demodulate.hpp" +#include "dsp_fft.hpp" +#include "dsp_fir_taps.hpp" +#include "dsp_iir.hpp" +#include "dsp_iir_config.hpp" +#include "dsp_squelch.hpp" + +#include "channel_decimator.hpp" +#include "baseband_processor.hpp" +#include "proc_fsk_lcr.hpp" +#include "proc_jammer.hpp" + +#include "clock_recovery.hpp" +#include "packet_builder.hpp" + +#include "message_queue.hpp" + +#include "utility.hpp" + +#include "debug.hpp" + +#include "audio.hpp" +#include "audio_dma.hpp" + +#include "gcc.hpp" + +#include +#include +#include +#include +#include +#include + +static baseband::Direction direction = baseband::Direction::Receive; + +class ThreadBase { +public: + constexpr ThreadBase( + const char* const name + ) : name { name } + { + } + + static msg_t fn(void* arg) { + auto obj = static_cast(arg); + chRegSetThreadName(obj->name); + obj->run(); + + return 0; + } + + virtual void run() = 0; + +private: + const char* const name; +}; + +class BasebandThread : public ThreadBase { +public: + BasebandThread( + ) : ThreadBase { "baseband" } + { + } + + Thread* start(const tprio_t priority) { + return chThdCreateStatic(wa, sizeof(wa), + priority, ThreadBase::fn, + this + ); + } + + Thread* thread_main { nullptr }; + BasebandProcessor* baseband_processor { nullptr }; + BasebandConfiguration baseband_configuration; + +private: + WORKING_AREA(wa, 2048); + + void run() override { + + while(true) { + if (direction == baseband::Direction::Transmit) { + const auto buffer_tmp = baseband::dma::wait_for_tx_buffer(); + + const buffer_c8_t buffer { + buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate + }; + + if( baseband_processor ) { + baseband_processor->execute(buffer); + } + } else { + const auto buffer_tmp = baseband::dma::wait_for_rx_buffer(); + + const buffer_c8_t buffer { + buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate + }; + + if( baseband_processor ) { + baseband_processor->execute(buffer); + } + } + } + } +}; + +#define SAMPLES_PER_BIT 192 +#define FILTER_SIZE 576 +#define SAMPLE_BUFFER_SIZE SAMPLES_PER_BIT + FILTER_SIZE + +static int32_t waveform_biphase[] = { + 165,167,168,168,167,166,163,160, + 157,152,147,141,134,126,118,109, + 99,88,77,66,53,41,27,14, + 0,-14,-29,-44,-59,-74,-89,-105, + -120,-135,-150,-165,-179,-193,-206,-218, + -231,-242,-252,-262,-271,-279,-286,-291, + -296,-299,-301,-302,-302,-300,-297,-292, + -286,-278,-269,-259,-247,-233,-219,-202, + -185,-166,-145,-124,-101,-77,-52,-26, + 0,27,56,85,114,144,175,205, + 236,266,296,326,356,384,412,439, + 465,490,513,535,555,574,590,604, + 616,626,633,637,639,638,633,626, + 616,602,586,565,542,515,485,451, + 414,373,329,282,232,178,121,62, + 0,-65,-132,-202,-274,-347,-423,-500, + -578,-656,-736,-815,-894,-973,-1051,-1128, + -1203,-1276,-1347,-1415,-1479,-1540,-1596,-1648, + -1695,-1736,-1771,-1799,-1820,-1833,-1838,-1835, + -1822,-1800,-1767,-1724,-1670,-1605,-1527,-1437, + -1334,-1217,-1087,-943,-785,-611,-423,-219, + 0,235,487,755,1040,1341,1659,1994, + 2346,2715,3101,3504,3923,4359,4811,5280, + 5764,6264,6780,7310,7856,8415,8987,9573, + 10172,10782,11404,12036,12678,13329,13989,14656, + 15330,16009,16694,17382,18074,18767,19461,20155, + 20848,21539,22226,22909,23586,24256,24918,25571, + 26214,26845,27464,28068,28658,29231,29787,30325, + 30842,31339,31814,32266,32694,33097,33473,33823, + 34144,34437,34699,34931,35131,35299,35434,35535, + 35602,35634,35630,35591,35515,35402,35252,35065, + 34841,34579,34279,33941,33566,33153,32702,32214, + 31689,31128,30530,29897,29228,28525,27788,27017, + 26214,25379,24513,23617,22693,21740,20761,19755, + 18725,17672,16597,15501,14385,13251,12101,10935, + 9755,8563,7360,6148,4927,3701,2470,1235, + 0,-1235,-2470,-3701,-4927,-6148,-7360,-8563, + -9755,-10935,-12101,-13251,-14385,-15501,-16597,-17672, + -18725,-19755,-20761,-21740,-22693,-23617,-24513,-25379, + -26214,-27017,-27788,-28525,-29228,-29897,-30530,-31128, + -31689,-32214,-32702,-33153,-33566,-33941,-34279,-34579, + -34841,-35065,-35252,-35402,-35515,-35591,-35630,-35634, + -35602,-35535,-35434,-35299,-35131,-34931,-34699,-34437, + -34144,-33823,-33473,-33097,-32694,-32266,-31814,-31339, + -30842,-30325,-29787,-29231,-28658,-28068,-27464,-26845, + -26214,-25571,-24918,-24256,-23586,-22909,-22226,-21539, + -20848,-20155,-19461,-18767,-18074,-17382,-16694,-16009, + -15330,-14656,-13989,-13329,-12678,-12036,-11404,-10782, + -10172,-9573,-8987,-8415,-7856,-7310,-6780,-6264, + -5764,-5280,-4811,-4359,-3923,-3504,-3101,-2715, + -2346,-1994,-1659,-1341,-1040,-755,-487,-235, + 0,219,423,611,785,943,1087,1217, + 1334,1437,1527,1605,1670,1724,1767,1800, + 1822,1835,1838,1833,1820,1799,1771,1736, + 1695,1648,1596,1540,1479,1415,1347,1276, + 1203,1128,1051,973,894,815,736,656, + 578,500,423,347,274,202,132,65, + 0,-62,-121,-178,-232,-282,-329,-373, + -414,-451,-485,-515,-542,-565,-586,-602, + -616,-626,-633,-638,-639,-637,-633,-626, + -616,-604,-590,-574,-555,-535,-513,-490, + -465,-439,-412,-384,-356,-326,-296,-266, + -236,-205,-175,-144,-114,-85,-56,-27, + 0,26,52,77,101,124,145,166, + 185,202,219,233,247,259,269,278, + 286,292,297,300,302,302,301,299, + 296,291,286,279,271,262,252,242, + 231,218,206,193,179,165,150,135, + 120,105,89,74,59,44,29,14, + 0,-14,-27,-41,-53,-66,-77,-88, + -99,-109,-118,-126,-134,-141,-147,-152, + -157,-160,-163,-166,-167,-168,-168,-167 +}; + +class RDSProcessor : public BasebandProcessor { +public: + void execute(buffer_c8_t buffer) override { + + for (size_t i = 0; i= 9) { + s = 0; + if(sample_count >= SAMPLES_PER_BIT) { + cur_bit = (shared_memory.rdsdata[(bit_pos / 26) & 15]>>(25-(bit_pos % 26))) & 1; + prev_output = cur_output; + cur_output = prev_output ^ cur_bit; + + int32_t *src = waveform_biphase; + int idx = in_sample_index; + + for(int j=0; j= SAMPLE_BUFFER_SIZE) idx = 0; + } + + in_sample_index += SAMPLES_PER_BIT; + if (in_sample_index >= SAMPLE_BUFFER_SIZE) in_sample_index -= SAMPLE_BUFFER_SIZE; + + bit_pos++; + sample_count = 0; + } + + sample = sample_buffer[out_sample_index]; + sample_buffer[out_sample_index] = 0; + out_sample_index++; + if (out_sample_index >= SAMPLE_BUFFER_SIZE) out_sample_index = 0; + + //AM @ 228k/4=57kHz + switch (mphase) { + case 0: + case 2: sample = 0; break; + case 1: break; + case 3: sample = -sample; break; + } + mphase++; + if (mphase >= 4) mphase = 0; + + sample_count++; + } else { + s++; + } + + //FM + frq = (sample>>16) * 386760; + + phase = (phase + frq); + sphase = phase + (256<<16); + + //re = sintab[(sphase & 0x03FF0000)>>16]; + //im = sintab[(phase & 0x03FF0000)>>16]; + + buffer.p[i] = {(int8_t)re,(int8_t)im}; + } + } + +private: + int8_t re, im; + uint8_t mphase, s; + uint32_t bit_pos; + int32_t sample_buffer[SAMPLE_BUFFER_SIZE] = {0}; + int32_t val; + uint8_t prev_output = 0; + uint8_t cur_output = 0; + uint8_t cur_bit = 0; + int sample_count = SAMPLES_PER_BIT; + int in_sample_index = 0; + int32_t sample; + int out_sample_index = SAMPLE_BUFFER_SIZE-1; + uint32_t phase, sphase; + int32_t sig, frq, frq_im, rdsc; + int32_t k; +}; + +class ToneProcessor : public BasebandProcessor { +public: + void execute(buffer_c8_t buffer) override { + + for (size_t i = 0; i= 9) { + s = 0; + aphase += 353205; // DEBUG + //sample = sintab[(aphase & 0x03FF0000)>>16]; + } else { + s++; + } + + //sample = sintab[(aphase & 0x03FF0000)>>16]; + + //FM + frq = sample * 500; // DEBUG + + phase = (phase + frq); + sphase = phase + (256<<16); + + //re = sintab[(sphase & 0x03FF0000)>>16]; + //im = sintab[(phase & 0x03FF0000)>>16]; + + buffer.p[i] = {(int8_t)re,(int8_t)im}; + } + } + +private: + int8_t re, im; + uint8_t s; + uint32_t sample_count; + uint32_t aphase, phase, sphase; + int32_t sample, sig, frq; +}; + +extern "C" { + +void __late_init(void) { + /* + * System initializations. + * - HAL initialization, this also initializes the configured device drivers + * and performs the board-specific initializations. + * - Kernel initialization, the main() function becomes a thread and the + * RTOS is active. + */ + halInit(); + + /* After this call, scheduler, systick, heap, etc. are available. */ + /* By doing chSysInit() here, it runs before C++ constructors, which may + * require the heap. + */ + chSysInit(); +} + +} + +static BasebandThread baseband_thread; + +static void init() { + i2s::i2s0::configure( + audio::i2s0_config_tx, + audio::i2s0_config_rx, + audio::i2s0_config_dma + ); + + audio::dma::init(); + audio::dma::configure(); + audio::dma::enable(); + + i2s::i2s0::tx_start(); + i2s::i2s0::rx_start(); + + LPC_CREG->DMAMUX = portapack::gpdma_mux; + gpdma::controller.enable(); + nvicEnableVector(DMA_IRQn, CORTEX_PRIORITY_MASK(LPC_DMA_IRQ_PRIORITY)); + + baseband::dma::init(); + + touch::dma::init(); + + const auto thread_main = chThdSelf(); + + baseband_thread.thread_main = thread_main; + + baseband_thread.start(NORMALPRIO + 20); +} + +static void shutdown() { + // TODO: Is this complete? + + nvicDisableVector(DMA_IRQn); + + m0apptxevent_interrupt_disable(); + + chSysDisable(); + + systick_stop(); +} + +static void halt() { + port_disable(); + while(true) { + port_wait_for_interrupt(); + } +} + +class EventDispatcher { +public: + MessageHandlerMap& message_handlers() { + return message_map; + } + + void run() { + while(is_running) { + const auto events = wait(); + dispatch(events); + } + } + + void request_stop() { + is_running = false; + } + +private: + MessageHandlerMap message_map; + + bool is_running = true; + + eventmask_t wait() { + return chEvtWaitAny(ALL_EVENTS); + } + + void dispatch(const eventmask_t events) { + if( events & EVT_MASK_BASEBAND ) { + handle_baseband_queue(); + } + + if( events & EVT_MASK_SPECTRUM ) { + handle_spectrum(); + } + } + + void handle_baseband_queue() { + std::array message_buffer; + while(Message* const message = shared_memory.baseband_queue.pop(message_buffer)) { + message_map.send(message); + } + } + + void handle_spectrum() { + if( baseband_thread.baseband_processor ) { + baseband_thread.baseband_processor->update_spectrum(); + } + } +}; + +const auto baseband_buffer = + new std::array(); + +int main(void) { + init(); + + events_initialize(chThdSelf()); + m0apptxevent_interrupt_enable(); + + EventDispatcher event_dispatcher; + auto& message_handlers = event_dispatcher.message_handlers(); + + message_handlers.register_handler(Message::ID::BasebandConfiguration, + [&message_handlers](const Message* const p) { + auto message = reinterpret_cast(p); + if( message->configuration.mode != baseband_thread.baseband_configuration.mode ) { + + if( baseband_thread.baseband_processor ) { + i2s::i2s0::tx_mute(); + baseband::dma::disable(); + } + + // TODO: Timing problem around disabling DMA and nulling and deleting old processor + auto old_p = baseband_thread.baseband_processor; + baseband_thread.baseband_processor = nullptr; + delete old_p; + + switch(message->configuration.mode) { + case 15: + direction = baseband::Direction::Transmit; + baseband_thread.baseband_processor = new RDSProcessor(); + break; + + case 16: + direction = baseband::Direction::Transmit; + baseband_thread.baseband_processor = new LCRFSKProcessor(); + break; + + case 17: + direction = baseband::Direction::Transmit; + baseband_thread.baseband_processor = new ToneProcessor(); + break; + + case 18: + direction = baseband::Direction::Transmit; + baseband_thread.baseband_processor = new JammerProcessor(); + break; + + default: + break; + } + + if( baseband_thread.baseband_processor ) + baseband::dma::enable(direction); + } + + baseband::dma::configure( + baseband_buffer->data(), + direction + ); + + baseband_thread.baseband_configuration = message->configuration; + } + ); + + message_handlers.register_handler(Message::ID::Shutdown, + [&event_dispatcher](const Message* const) { + event_dispatcher.request_stop(); + } + ); + + /* TODO: Ensure DMAs are configured to point at first LLI in chain. */ + + touch::dma::allocate(); + touch::dma::enable(); + + baseband::dma::configure( + baseband_buffer->data(), + direction + ); + + //baseband::dma::allocate(4, 2048); + + event_dispatcher.run(); + + shutdown(); + + ShutdownMessage shutdown_message; + shared_memory.application_queue.push(shutdown_message); + + halt(); + + return 0; +} diff --git a/firmware/baseband-tx/main.cpp.orig b/firmware/baseband-tx/main.cpp.orig new file mode 100755 index 00000000..6dd555b5 --- /dev/null +++ b/firmware/baseband-tx/main.cpp.orig @@ -0,0 +1,1003 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ch.h" +#include "test.h" + +#include "lpc43xx_cpp.hpp" + +#include "portapack_shared_memory.hpp" +#include "portapack_dma.hpp" +#include "gpdma.hpp" + +#include "baseband.hpp" +#include "baseband_dma.hpp" + +#include "event_m4.hpp" + +#include "irq_ipc_m4.hpp" + +#include "rssi.hpp" +#include "rssi_dma.hpp" + +#include "touch_dma.hpp" + +#include "dsp_decimate.hpp" +#include "dsp_demodulate.hpp" +#include "dsp_fir_taps.hpp" +#include "dsp_iir.hpp" +#include "dsp_iir_config.hpp" +#include "dsp_squelch.hpp" + +#include "baseband_stats_collector.hpp" +#include "rssi_stats_collector.hpp" + +#include "channel_decimator.hpp" +#include "baseband_processor.hpp" +#include "proc_am_audio.hpp" +#include "proc_nfm_audio.hpp" +#include "proc_wfm_audio.hpp" +#include "proc_ais.hpp" +#include "proc_wideband_spectrum.hpp" +#include "proc_tpms.hpp" + +#include "clock_recovery.hpp" +#include "packet_builder.hpp" + +#include "message_queue.hpp" + +#include "utility.hpp" + +#include "debug.hpp" + +#include "audio.hpp" +#include "audio_dma.hpp" + +#include "gcc.hpp" + +#include +#include +#include +#include +#include +#include + +class ThreadBase { +public: + constexpr ThreadBase( + const char* const name + ) : name { name } + { + } + + static msg_t fn(void* arg) { + auto obj = static_cast(arg); + chRegSetThreadName(obj->name); + obj->run(); + + return 0; + } + + virtual void run() = 0; + +private: + const char* const name; +}; + +class BasebandThread : public ThreadBase { +public: + BasebandThread( + ) : ThreadBase { "baseband" } + { + } + + Thread* start(const tprio_t priority) { + return chThdCreateStatic(wa, sizeof(wa), + priority, ThreadBase::fn, + this + ); + } + +<<<<<<< HEAD + void fill_audio_buffer(const buffer_s16_t audio) { + auto audio_buffer = audio::dma::tx_empty_buffer(); + for(size_t i=0; i>>>>>> upstream/master + +private: + WORKING_AREA(wa, 2048); + + void run() override { + BasebandStatsCollector stats { + chSysGetIdleThread(), + thread_main, + thread_rssi, + chThdSelf() + }; + + while(true) { + // TODO: Place correct sampling rate into buffer returned here: + const auto buffer_tmp = baseband::dma::wait_for_rx_buffer(); + const buffer_c8_t buffer { + buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate + }; + + if( baseband_processor ) { + baseband_processor->execute(buffer); + } + + stats.process(buffer, + [](const BasebandStatistics statistics) { + const BasebandStatisticsMessage message { statistics }; + shared_memory.application_queue.push(message); + } + ); + } + } +}; + +class RSSIThread : public ThreadBase { +public: + RSSIThread( + ) : ThreadBase { "rssi" } + { + } + + Thread* start(const tprio_t priority) { + return chThdCreateStatic(wa, sizeof(wa), + priority, ThreadBase::fn, + this + ); + } + + uint32_t sampling_rate { 400000 }; + +private: + WORKING_AREA(wa, 128); + + void run() override { + RSSIStatisticsCollector stats; + + while(true) { + // TODO: Place correct sampling rate into buffer returned here: + const auto buffer_tmp = rf::rssi::dma::wait_for_buffer(); + const rf::rssi::buffer_t buffer { + buffer_tmp.p, buffer_tmp.count, sampling_rate + }; + + stats.process( + buffer, + [](const RSSIStatistics statistics) { + const RSSIStatisticsMessage message { statistics }; + shared_memory.application_queue.push(message); + } + ); + } + } +}; + +<<<<<<< HEAD +static const int8_t sintab[1024] = { +0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 9, 10, 11, 12, 12, 13, 14, 15, 16, 16, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 25, 26, 26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 44, 45, 46, 46, 47, 48, 49, 49, 50, 51, 51, 52, 53, 54, 54, 55, 56, 56, 57, 58, 58, 59, 60, 61, 61, 62, 63, 63, 64, 65, 65, 66, 67, 67, 68, 69, 69, 70, 71, 71, 72, 72, 73, 74, 74, 75, 76, 76, 77, 78, 78, 79, 79, 80, 81, 81, 82, 82, 83, 84, 84, 85, 85, 86, 86, 87, 88, 88, 89, 89, 90, 90, 91, 91, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 102, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 106, 107, 107, 108, 108, 109, 109, 109, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 118, 119, 119, 119, 120, 120, 120, 120, 121, 121, 121, 121, 122, 122, 122, 122, 122, 123, 123, 123, 123, 123, 124, 124, 124, 124, 124, 124, 125, 125, 125, 125, 125, 125, 125, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, +127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 125, 125, 125, 125, 125, 125, 125, 124, 124, 124, 124, 124, 124, 123, 123, 123, 123, 123, 122, 122, 122, 122, 122, 121, 121, 121, 121, 120, 120, 120, 120, 119, 119, 119, 118, 118, 118, 118, 117, 117, 117, 116, 116, 116, 115, 115, 115, 114, 114, 114, 113, 113, 113, 112, 112, 112, 111, 111, 111, 110, 110, 109, 109, 109, 108, 108, 107, 107, 106, 106, 106, 105, 105, 104, 104, 103, 103, 102, 102, 102, 101, 101, 100, 100, 99, 99, 98, 98, 97, 97, 96, 96, 95, 95, 94, 94, 93, 93, 92, 91, 91, 90, 90, 89, 89, 88, 88, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81, 80, 79, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 71, 70, 69, 69, 68, 67, 67, 66, 65, 65, 64, 63, 63, 62, 61, 61, 60, 59, 58, 58, 57, 56, 56, 55, 54, 54, 53, 52, 51, 51, 50, 49, 49, 48, 47, 46, 46, 45, 44, 44, 43, 42, 41, 41, 40, 39, 38, 38, 37, 36, 35, 35, 34, 33, 32, 32, 31, 30, 29, 29, 28, 27, 26, 26, 25, 24, 23, 22, 22, 21, 20, 19, 19, 18, 17, 16, 16, 15, 14, 13, 12, 12, 11, 10, 9, 9, 8, 7, 6, 5, 5, 4, 3, 2, 2, 1, 0, -1, -2, -2, -3, -4, -5, -5, -6, -7, -8, -9, -9, -10, -11, -12, -12, -13, -14, -15, -16, -16, -17, -18, -19, -19, -20, -21, -22, -22, -23, -24, -25, -26, -26, -27, -28, -29, -29, -30, -31, +-32, -32, -33, -34, -35, -35, -36, -37, -38, -38, -39, -40, -41, -41, -42, -43, -44, -44, -45, -46, -46, -47, -48, -49, -49, -50, -51, -51, -52, -53, -54, -54, -55, -56, -56, -57, -58, -58, -59, -60, -61, -61, -62, -63, -63, -64, -65, -65, -66, -67, -67, -68, -69, -69, -70, -71, -71, -72, -72, -73, -74, -74, -75, -76, -76, -77, -78, -78, -79, -79, -80, -81, -81, -82, -82, -83, -84, -84, -85, -85, -86, -86, -87, -88, -88, -89, -89, -90, -90, -91, -91, -92, -93, -93, -94, -94, -95, -95, -96, -96, -97, -97, -98, -98, -99, -99, -100, -100, -101, -101, -102, -102, -102, -103, -103, -104, -104, -105, -105, -106, -106, -106, -107, -107, -108, -108, -109, -109, -109, -110, -110, -111, -111, -111, -112, -112, -112, -113, -113, -113, -114, -114, -114, -115, -115, -115, -116, -116, -116, -117, -117, -117, -118, -118, -118, -118, -119, -119, -119, -120, -120, -120, -120, -121, -121, -121, -121, -122, -122, -122, -122, -122, -123, -123, -123, -123, -123, -124, -124, -124, -124, -124, -124, -125, -125, -125, -125, -125, -125, -125, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, +-126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -125, -125, -125, -125, -125, -125, -125, -124, -124, -124, -124, -124, -124, -123, -123, -123, -123, -123, -122, -122, -122, -122, -122, -121, -121, -121, -121, -120, -120, -120, -120, -119, -119, -119, -118, -118, -118, -118, -117, -117, -117, -116, -116, -116, -115, -115, -115, -114, -114, -114, -113, -113, -113, -112, -112, -112, -111, -111, -111, -110, -110, -109, -109, -109, -108, -108, -107, -107, -106, -106, -106, -105, -105, -104, -104, -103, -103, -102, -102, -102, -101, -101, -100, -100, -99, -99, -98, -98, -97, -97, -96, -96, -95, -95, -94, -94, -93, -93, -92, -91, -91, -90, -90, -89, -89, -88, -88, -87, -86, -86, -85, -85, -84, -84, -83, -82, -82, -81, -81, -80, -79, -79, -78, -78, -77, -76, -76, -75, -74, -74, -73, -72, -72, -71, -71, -70, -69, -69, -68, -67, -67, -66, -65, -65, -64, -63, -63, -62, -61, -61, -60, -59, -58, -58, -57, -56, -56, -55, -54, -54, -53, -52, -51, -51, -50, -49, -49, -48, -47, -46, -46, -45, -44, -44, -43, -42, -41, -41, -40, -39, -38, -38, -37, -36, -35, -35, -34, -33, -32, -32, -31, -30, -29, -29, -28, -27, -26, -26, -25, -24, -23, -22, -22, -21, -20, -19, -19, -18, -17, -16, -16, -15, -14, -13, -12, -12, -11, -10, -9, -9, -8, -7, -6, -5, -5, -4, -3, -2, -2, -1 +}; + +#define SAMPLES_PER_BIT 192 +#define FILTER_SIZE 576 +#define SAMPLE_BUFFER_SIZE SAMPLES_PER_BIT + FILTER_SIZE + +static int32_t waveform_biphase[] = { + 165,167,168,168,167,166,163,160, + 157,152,147,141,134,126,118,109, + 99,88,77,66,53,41,27,14, + 0,-14,-29,-44,-59,-74,-89,-105, + -120,-135,-150,-165,-179,-193,-206,-218, + -231,-242,-252,-262,-271,-279,-286,-291, + -296,-299,-301,-302,-302,-300,-297,-292, + -286,-278,-269,-259,-247,-233,-219,-202, + -185,-166,-145,-124,-101,-77,-52,-26, + 0,27,56,85,114,144,175,205, + 236,266,296,326,356,384,412,439, + 465,490,513,535,555,574,590,604, + 616,626,633,637,639,638,633,626, + 616,602,586,565,542,515,485,451, + 414,373,329,282,232,178,121,62, + 0,-65,-132,-202,-274,-347,-423,-500, + -578,-656,-736,-815,-894,-973,-1051,-1128, + -1203,-1276,-1347,-1415,-1479,-1540,-1596,-1648, + -1695,-1736,-1771,-1799,-1820,-1833,-1838,-1835, + -1822,-1800,-1767,-1724,-1670,-1605,-1527,-1437, + -1334,-1217,-1087,-943,-785,-611,-423,-219, + 0,235,487,755,1040,1341,1659,1994, + 2346,2715,3101,3504,3923,4359,4811,5280, + 5764,6264,6780,7310,7856,8415,8987,9573, + 10172,10782,11404,12036,12678,13329,13989,14656, + 15330,16009,16694,17382,18074,18767,19461,20155, + 20848,21539,22226,22909,23586,24256,24918,25571, + 26214,26845,27464,28068,28658,29231,29787,30325, + 30842,31339,31814,32266,32694,33097,33473,33823, + 34144,34437,34699,34931,35131,35299,35434,35535, + 35602,35634,35630,35591,35515,35402,35252,35065, + 34841,34579,34279,33941,33566,33153,32702,32214, + 31689,31128,30530,29897,29228,28525,27788,27017, + 26214,25379,24513,23617,22693,21740,20761,19755, + 18725,17672,16597,15501,14385,13251,12101,10935, + 9755,8563,7360,6148,4927,3701,2470,1235, + 0,-1235,-2470,-3701,-4927,-6148,-7360,-8563, + -9755,-10935,-12101,-13251,-14385,-15501,-16597,-17672, + -18725,-19755,-20761,-21740,-22693,-23617,-24513,-25379, + -26214,-27017,-27788,-28525,-29228,-29897,-30530,-31128, + -31689,-32214,-32702,-33153,-33566,-33941,-34279,-34579, + -34841,-35065,-35252,-35402,-35515,-35591,-35630,-35634, + -35602,-35535,-35434,-35299,-35131,-34931,-34699,-34437, + -34144,-33823,-33473,-33097,-32694,-32266,-31814,-31339, + -30842,-30325,-29787,-29231,-28658,-28068,-27464,-26845, + -26214,-25571,-24918,-24256,-23586,-22909,-22226,-21539, + -20848,-20155,-19461,-18767,-18074,-17382,-16694,-16009, + -15330,-14656,-13989,-13329,-12678,-12036,-11404,-10782, + -10172,-9573,-8987,-8415,-7856,-7310,-6780,-6264, + -5764,-5280,-4811,-4359,-3923,-3504,-3101,-2715, + -2346,-1994,-1659,-1341,-1040,-755,-487,-235, + 0,219,423,611,785,943,1087,1217, + 1334,1437,1527,1605,1670,1724,1767,1800, + 1822,1835,1838,1833,1820,1799,1771,1736, + 1695,1648,1596,1540,1479,1415,1347,1276, + 1203,1128,1051,973,894,815,736,656, + 578,500,423,347,274,202,132,65, + 0,-62,-121,-178,-232,-282,-329,-373, + -414,-451,-485,-515,-542,-565,-586,-602, + -616,-626,-633,-638,-639,-637,-633,-626, + -616,-604,-590,-574,-555,-535,-513,-490, + -465,-439,-412,-384,-356,-326,-296,-266, + -236,-205,-175,-144,-114,-85,-56,-27, + 0,26,52,77,101,124,145,166, + 185,202,219,233,247,259,269,278, + 286,292,297,300,302,302,301,299, + 296,291,286,279,271,262,252,242, + 231,218,206,193,179,165,150,135, + 120,105,89,74,59,44,29,14, + 0,-14,-27,-41,-53,-66,-77,-88, + -99,-109,-118,-126,-134,-141,-147,-152, + -157,-160,-163,-166,-167,-168,-168,-167 +}; + +/* +class RDSProcessor : public BasebandProcessor { +public: + void execute(buffer_c8_t buffer) override { + + for (size_t i = 0; i= 9) { + s = 0; + if(sample_count >= SAMPLES_PER_BIT) { + cur_bit = (shared_memory.rdsdata[(bit_pos / 26) & 15]>>(25-(bit_pos % 26))) & 1; + prev_output = cur_output; + cur_output = prev_output ^ cur_bit; + + int32_t *src = waveform_biphase; + int idx = in_sample_index; + + for(int j=0; j= SAMPLE_BUFFER_SIZE) idx = 0; + } + + in_sample_index += SAMPLES_PER_BIT; + if (in_sample_index >= SAMPLE_BUFFER_SIZE) in_sample_index -= SAMPLE_BUFFER_SIZE; + + bit_pos++; + sample_count = 0; + } + + sample = sample_buffer[out_sample_index]; + sample_buffer[out_sample_index] = 0; + out_sample_index++; + if (out_sample_index >= SAMPLE_BUFFER_SIZE) out_sample_index = 0; + + //AM @ 228k/4=57kHz + switch (mphase) { + case 0: + case 2: sample = 0; break; + case 1: break; + case 3: sample = -sample; break; + } + mphase++; + if (mphase >= 4) mphase = 0; + + sample_count++; + } else { + s++; + } + + //FM + frq = (sample>>16) * 386760; + + phase = (phase + frq); + sphase = phase + (256<<16); + + re = sintab[(sphase & 0x03FF0000)>>16]; + im = sintab[(phase & 0x03FF0000)>>16]; + + buffer.p[i] = {(int8_t)re,(int8_t)im}; + } + } + +private: + int8_t re, im; + uint8_t mphase, s; + uint32_t bit_pos; + int32_t sample_buffer[SAMPLE_BUFFER_SIZE] = {0}; + int32_t val; + uint8_t prev_output = 0; + uint8_t cur_output = 0; + uint8_t cur_bit = 0; + int sample_count = SAMPLES_PER_BIT; + int in_sample_index = 0; + int32_t sample; + int out_sample_index = SAMPLE_BUFFER_SIZE-1; + uint32_t phase, sphase; + int32_t sig, frq, frq_im, rdsc; + int32_t k; +};*/ + +class LCRFSKProcessor : public BasebandProcessor { +public: + void execute(buffer_c8_t buffer) override { + + for (size_t i = 0; i= 9) { + s = 0; + + if (sample_count >= shared_memory.afsk_samples_per_bit) { + if (shared_memory.afsk_transmit_done == false) + cur_byte = shared_memory.lcrdata[byte_pos]; + if (!cur_byte) { + if (shared_memory.afsk_repeat) { + shared_memory.afsk_repeat--; + bit_pos = 0; + byte_pos = 0; + cur_byte = shared_memory.lcrdata[0]; + if( message.is_free() ) { + message.n = shared_memory.afsk_repeat; + shared_memory.application_queue.push(&message); + } + } else { + if( message.is_free() ) { + message.n = 0; + shared_memory.afsk_transmit_done = true; + shared_memory.application_queue.push(&message); + } + cur_byte = 0; + } + } + + gbyte = 0; + gbyte = cur_byte << 1; + gbyte |= 1; + + cur_bit = (gbyte >> (9-bit_pos)) & 1; + + if (bit_pos == 9) { + bit_pos = 0; + byte_pos++; + } else { + bit_pos++; + } + + //aphase = 0x2FFFFFF; + + sample_count = 0; + } else { + sample_count++; + } + if (cur_bit) + aphase += shared_memory.afsk_phase_inc_mark; //(353205) + else + aphase += shared_memory.afsk_phase_inc_space; //(647542) + + sample = sintab[(aphase & 0x03FF0000)>>16]; + } else { + s++; + } + + sample = sintab[(aphase & 0x03FF0000)>>16]; + + //FM + frq = sample * shared_memory.afsk_fmmod; + + phase = (phase + frq); + sphase = phase + (256<<16); + + re = sintab[(sphase & 0x03FF0000)>>16]; + im = sintab[(phase & 0x03FF0000)>>16]; + + buffer.p[i] = {(int8_t)re,(int8_t)im}; + } + } + +private: + int8_t re, im; + uint8_t s; + uint8_t bit_pos, byte_pos = 0; + char cur_byte = 0; + uint16_t gbyte; + uint8_t cur_bit = 0; + uint32_t sample_count; + uint32_t aphase, phase, sphase; + int32_t sample, sig, frq; + TXDoneMessage message; +}; + +/*class ToneProcessor : public BasebandProcessor { +public: + void execute(buffer_c8_t buffer) override { + + for (size_t i = 0; i= 9) { + s = 0; + aphase += 353205; // DEBUG + sample = sintab[(aphase & 0x03FF0000)>>16]; + } else { + s++; + } + + sample = sintab[(aphase & 0x03FF0000)>>16]; + + //FM + frq = sample * 500; // DEBUG + + phase = (phase + frq); + sphase = phase + (256<<16); + + re = sintab[(sphase & 0x03FF0000)>>16]; + im = sintab[(phase & 0x03FF0000)>>16]; + + buffer.p[i] = {(int8_t)re,(int8_t)im}; + } + } + +private: + int8_t re, im; + uint8_t s; + uint32_t sample_count; + uint32_t aphase, phase, sphase; + int32_t sample, sig, frq; +};*/ + +#define POLY_MASK_32 0xB4BCD35C + +class JammerProcessor : public BasebandProcessor { +public: + void execute(buffer_c8_t buffer) override { + + for (size_t i = 0; i 3000000) { + s = 0; + feedback = lfsr & 1; + lfsr >>= 1; + if (feedback == 1) + lfsr ^= POLY_MASK_32; + } else { + s++; + } + + aphase += lfsr;*/ + + /*if (s >= 10) { + s = 0; + aphase += 353205; // DEBUG + } else { + s++; + } + + sample = sintab[(aphase & 0x03FF0000)>>16];*/ + + // Duration timer + // + if (s >= 10000) { //shared_memory.jammer_ranges[ir].duration + s = 0; + for (;;) { + ir++; + if (ir > 15) ir = 0; + if (shared_memory.jammer_ranges[ir].active == true) break; + } + jammer_bw = shared_memory.jammer_ranges[ir].width; + + if( message.is_free() ) { + message.freq = shared_memory.jammer_ranges[ir].center; + shared_memory.application_queue.push(&message); + } + } else { + s++; + } + + // Ramp + /*if (r >= 10) { + if (sample < 128) + sample++; + else + sample = -127; + r = 0; + } else { + r++; + }*/ + + // Phase + if (r >= 70) { + aphase += ((aphase>>4) ^ 0x4573) << 14; + r = 0; + } else { + r++; + } + + aphase += 35320; + sample = sintab[(aphase & 0x03FF0000)>>16]; + + //FM + frq = sample * jammer_bw; // Bandwidth + + //65536 -> 0.6M + //131072 -> 1.2M + + phase = (phase + frq); + sphase = phase + (256<<16); + + re = sintab[(sphase & 0x03FF0000)>>16]; + im = sintab[(phase & 0x03FF0000)>>16]; + + buffer.p[i] = {(int8_t)re,(int8_t)im}; + } + } + +private: + int32_t lfsr32 = 0xABCDE; + uint32_t s; + int8_t r, ir, re, im; + int64_t jammer_bw, jammer_center; + int feedback; + int32_t lfsr; + uint32_t sample_count; + uint32_t aphase, phase, sphase; + int32_t sample, frq; + RetuneMessage message; +}; + +static BasebandProcessor* baseband_processor { nullptr }; +static BasebandConfiguration baseband_configuration; + +static baseband::Direction direction = baseband::Direction::Transmit; + +static WORKING_AREA(baseband_thread_wa, 8192); + +static __attribute__((noreturn)) msg_t baseband_fn(void *arg) { + (void)arg; + chRegSetThreadName("baseband"); + + BasebandStatsCollector stats; + BasebandStatisticsMessage message; + + while(true) { + if (direction == baseband::Direction::Transmit) { + const auto buffer_tmp = baseband::dma::wait_for_tx_buffer(); + + const buffer_c8_t buffer { + buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate + }; + + if( baseband_processor ) { + baseband_processor->execute(buffer); + } + + stats.process(buffer, + [&message](const BasebandStatistics statistics) { + if( message.is_free() ) { + message.statistics = statistics; + shared_memory.application_queue.push(&message); + } + } + ); + } else { + const auto buffer_tmp = baseband::dma::wait_for_rx_buffer(); + + const buffer_c8_t buffer { + buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate + }; + + if( baseband_processor ) { + baseband_processor->execute(buffer); + } + + stats.process(buffer, + [&message](const BasebandStatistics statistics) { + if( message.is_free() ) { + message.statistics = statistics; + shared_memory.application_queue.push(&message); + } + } + ); + } + } +} + +static WORKING_AREA(rssi_thread_wa, 128); +static __attribute__((noreturn)) msg_t rssi_fn(void *arg) { + (void)arg; + chRegSetThreadName("rssi"); + + RSSIStatisticsCollector stats; + RSSIStatisticsMessage message; + + while(true) { + // TODO: Place correct sampling rate into buffer returned here: + const auto buffer_tmp = rf::rssi::dma::wait_for_buffer(); + const rf::rssi::buffer_t buffer { + buffer_tmp.p, buffer_tmp.count, 400000 + }; + + stats.process( + buffer, + [&message](const RSSIStatistics statistics) { + if( message.is_free() ) { + message.statistics = statistics; + shared_memory.application_queue.push(&message); + } + } + ); + } +} + +======= +>>>>>>> upstream/master +extern "C" { + +void __late_init(void) { + /* + * System initializations. + * - HAL initialization, this also initializes the configured device drivers + * and performs the board-specific initializations. + * - Kernel initialization, the main() function becomes a thread and the + * RTOS is active. + */ + halInit(); + + /* After this call, scheduler, systick, heap, etc. are available. */ + /* By doing chSysInit() here, it runs before C++ constructors, which may + * require the heap. + */ + chSysInit(); +} + +} + +static BasebandThread baseband_thread; +static RSSIThread rssi_thread; + +static void init() { + i2s::i2s0::configure( + audio::i2s0_config_tx, + audio::i2s0_config_rx, + audio::i2s0_config_dma + ); + + audio::dma::init(); + audio::dma::configure(); + audio::dma::enable(); + + i2s::i2s0::tx_start(); + i2s::i2s0::rx_start(); + + LPC_CREG->DMAMUX = portapack::gpdma_mux; + gpdma::controller.enable(); + nvicEnableVector(DMA_IRQn, CORTEX_PRIORITY_MASK(LPC_DMA_IRQ_PRIORITY)); + + baseband::dma::init(); + + rf::rssi::init(); + touch::dma::init(); + +<<<<<<< HEAD + chThdCreateStatic(baseband_thread_wa, sizeof(baseband_thread_wa), + baseband_thread_priority, baseband_fn, + nullptr + ); + + chThdCreateStatic(rssi_thread_wa, sizeof(rssi_thread_wa), + rssi_thread_priority, rssi_fn, + nullptr + ); +======= + const auto thread_main = chThdSelf(); + + const auto thread_rssi = rssi_thread.start(NORMALPRIO + 10); + + baseband_thread.thread_main = thread_main; + baseband_thread.thread_rssi = thread_rssi; + + baseband_thread.start(NORMALPRIO + 20); +} + +static void shutdown() { + // TODO: Is this complete? + + nvicDisableVector(DMA_IRQn); + + m0apptxevent_interrupt_disable(); + + chSysDisable(); + + systick_stop(); +} + +static void halt() { + port_disable(); + while(true) { + port_wait_for_interrupt(); + } +>>>>>>> upstream/master +} + +class EventDispatcher { +public: + MessageHandlerMap& message_handlers() { + return message_map; + } + + void run() { + while(is_running) { + const auto events = wait(); + dispatch(events); + } + } + + void request_stop() { + is_running = false; + } + +private: + MessageHandlerMap message_map; + + bool is_running = true; + + eventmask_t wait() { + return chEvtWaitAny(ALL_EVENTS); + } + + void dispatch(const eventmask_t events) { + if( events & EVT_MASK_BASEBAND ) { + handle_baseband_queue(); + } + + if( events & EVT_MASK_SPECTRUM ) { + handle_spectrum(); + } + } + + void handle_baseband_queue() { + std::array message_buffer; + while(Message* const message = shared_memory.baseband_queue.pop(message_buffer)) { + message_map.send(message); + } + } + + void handle_spectrum() { + if( baseband_thread.baseband_processor ) { + baseband_thread.baseband_processor->update_spectrum(); + } + } +}; + +<<<<<<< HEAD +static void m0apptxevent_interrupt_enable() { + nvicEnableVector(M0CORE_IRQn, CORTEX_PRIORITY_MASK(LPC43XX_M0APPTXEVENT_IRQ_PRIORITY)); +} + +extern "C" { + +CH_IRQ_HANDLER(MAPP_IRQHandler) { + CH_IRQ_PROLOGUE(); + + chSysLockFromIsr(); + events_flag_isr(EVT_MASK_BASEBAND); + chSysUnlockFromIsr(); + + creg::m0apptxevent::clear(); + + CH_IRQ_EPILOGUE(); +} + +} + +std::array baseband_buffer; +======= +static constexpr auto direction = baseband::Direction::Receive; +>>>>>>> upstream/master + +int main(void) { + init(); + + events_initialize(chThdSelf()); + m0apptxevent_interrupt_enable(); + + EventDispatcher event_dispatcher; + auto& message_handlers = event_dispatcher.message_handlers(); + + //const auto baseband_buffer = new std::array(); + + message_handlers.register_handler(Message::ID::BasebandConfiguration, + [&message_handlers](const Message* const p) { + auto message = reinterpret_cast(p); + if( message->configuration.mode != baseband_thread.baseband_configuration.mode ) { + + if( baseband_thread.baseband_processor ) { + i2s::i2s0::tx_mute(); + baseband::dma::disable(); + rf::rssi::stop(); + } + +<<<<<<< HEAD + switch(message->configuration.mode) { + case 1: + direction = baseband::Direction::Receive; + baseband_processor = new NarrowbandAMAudio(); + break; + + case 2: + direction = baseband::Direction::Receive; + baseband_processor = new NarrowbandFMAudio(); + break; + + case 3: + direction = baseband::Direction::Receive; + baseband_processor = new WidebandFMAudio(); + break; + + case 4: + direction = baseband::Direction::Receive; + baseband_processor = new FSKProcessor(message_handlers); + break; + + /*case 15: + direction = baseband::Direction::Transmit; + baseband_processor = new RDSProcessor(); + break;*/ + + case 16: + direction = baseband::Direction::Transmit; + baseband_processor = new LCRFSKProcessor(); + break; + + /*case 17: + direction = baseband::Direction::Transmit; + baseband_processor = new ToneProcessor(); + break;*/ + + case 18: + direction = baseband::Direction::Transmit; + baseband_processor = new JammerProcessor(); + break; +======= + // TODO: Timing problem around disabling DMA and nulling and deleting old processor + auto old_p = baseband_thread.baseband_processor; + baseband_thread.baseband_processor = nullptr; + delete old_p; + + switch(message->configuration.mode) { + case 0: + baseband_thread.baseband_processor = new NarrowbandAMAudio(); + break; + + case 1: + baseband_thread.baseband_processor = new NarrowbandFMAudio(); + break; + + case 2: + baseband_thread.baseband_processor = new WidebandFMAudio(); + break; +>>>>>>> upstream/master + + case 3: + baseband_thread.baseband_processor = new AISProcessor(); + break; + + case 4: + baseband_thread.baseband_processor = new WidebandSpectrum(); + break; + + case 5: + baseband_thread.baseband_processor = new TPMSProcessor(); + break; + + default: + break; + } + + if( baseband_thread.baseband_processor ) { + if( direction == baseband::Direction::Receive ) { + rf::rssi::start(); + } + baseband::dma::enable(direction); + } + } + + baseband_thread.baseband_configuration = message->configuration; + } +<<<<<<< HEAD + + baseband::dma::configure( + baseband_buffer.data(), + direction + ); +======= + ); +>>>>>>> upstream/master + + message_handlers.register_handler(Message::ID::Shutdown, + [&event_dispatcher](const Message* const) { + event_dispatcher.request_stop(); + } + ); + + /* TODO: Ensure DMAs are configured to point at first LLI in chain. */ + + rf::rssi::dma::allocate(4, 400); + + touch::dma::allocate(); + touch::dma::enable(); + + baseband::dma::configure( + baseband_buffer.data(), + direction + ); + //baseband::dma::allocate(4, 2048);d + + event_dispatcher.run(); + + shutdown(); + + ShutdownMessage shutdown_message; + shared_memory.application_queue.push(shutdown_message); + + halt(); + + return 0; +} diff --git a/firmware/baseband-tx/matched_filter.cpp b/firmware/baseband-tx/matched_filter.cpp new file mode 100644 index 00000000..5040349a --- /dev/null +++ b/firmware/baseband-tx/matched_filter.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "matched_filter.hpp" + +namespace dsp { +namespace matched_filter { + +bool MatchedFilter::execute_once( + const sample_t input +) { + samples_[taps_count_ - decimation_factor_ + decimation_phase] = input; + + advance_decimation_phase(); + if( is_new_decimation_cycle() ) { + float sr_tr = 0.0f; + float si_tr = 0.0f; + float si_ti = 0.0f; + float sr_ti = 0.0f; + for(size_t n=0; n 0) { + *t++ = *s++; + *t++ = *s++; + *t++ = *s++; + *t++ = *s++; + shift_count--; + } + + shift_count = (taps_count_ - decimation_factor_) % unroll_factor; + while(shift_count > 0) { + *t++ = *s++; + shift_count--; + } +} + +} /* namespace matched_filter */ +} /* namespace dsp */ diff --git a/firmware/baseband-tx/matched_filter.hpp b/firmware/baseband-tx/matched_filter.hpp new file mode 100644 index 00000000..741f5018 --- /dev/null +++ b/firmware/baseband-tx/matched_filter.hpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __MATCHED_FILTER_H__ +#define __MATCHED_FILTER_H__ + +#include "utility.hpp" + +#include + +#include +#include +#include + +#include +#include + +namespace dsp { +namespace matched_filter { + +// This filter contains "magic" (optimizations) that expect the taps to +// combine a low-pass filter with a complex sinusoid that performs shifting of +// the input signal to 0Hz/DC. This also means that the taps length must be +// a multiple of the complex sinusoid period. + +class MatchedFilter { +public: + using sample_t = std::complex; + using tap_t = std::complex; + + using taps_t = tap_t[]; + + template + MatchedFilter( + const T& taps, + size_t decimation_factor = 1 + ) { + configure(taps, decimation_factor); + } + + template + void configure( + const T& taps, + size_t decimation_factor + ) { + samples_ = std::make_unique(taps.size()); + taps_reversed_ = std::make_unique(taps.size()); + taps_count_ = taps.size(); + decimation_factor_ = decimation_factor; + std::reverse_copy(taps.cbegin(), taps.cend(), &taps_reversed_[0]); + } + + bool execute_once(const sample_t input); + + float get_output() const { + return output; + } + +private: + using samples_t = sample_t[]; + + std::unique_ptr samples_; + std::unique_ptr taps_reversed_; + size_t taps_count_ { 0 }; + size_t decimation_factor_ { 1 }; + size_t decimation_phase { 0 }; + float output; + + void shift_by_decimation_factor(); + + void advance_decimation_phase() { + decimation_phase = (decimation_phase + 1) % decimation_factor_; + } + + bool is_new_decimation_cycle() const { + return (decimation_phase == 0); + } +}; + +} /* namespace matched_filter */ +} /* namespace dsp */ + +#endif/*__MATCHED_FILTER_H__*/ diff --git a/firmware/baseband-tx/mcuconf.h b/firmware/baseband-tx/mcuconf.h new file mode 100755 index 00000000..eae37545 --- /dev/null +++ b/firmware/baseband-tx/mcuconf.h @@ -0,0 +1,47 @@ +/* + ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio + Copyright (C) 2014 Jared Boone, ShareBrained Technology + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + * LPC43xx drivers configuration. + * The following settings override the default settings present in + * the various device driver implementation headers. + * Note that the settings for each driver only have effect if the whole + * driver is enabled in halconf.h. + * + * IRQ priorities: + * 7...0 Lowest...Highest. + */ + +/* NOTE: Beware setting IRQ priorities < "2": + * dbg_check_enter_isr "#SV8 means that probably you have some IRQ set at a + * priority level above the kernel level (level 0 or 1 usually) so it is able + * to preempt the kernel and mess things up. + */ + +/* + * DMA driver system settings. + */ + +//#define LPC_ADC0_IRQ_PRIORITY 2 +#define LPC_DMA_IRQ_PRIORITY 3 +//#define LPC_ADC1_IRQ_PRIORITY 4 + +#define LPC43XX_M0APPTXEVENT_IRQ_PRIORITY 4 + +/* M4 is initialized by M0, which has already started PLL1 */ +#define LPC43XX_M4_CLK 200000000 +#define LPC43XX_M4_CLK_SRC 0x09 \ No newline at end of file diff --git a/firmware/baseband-tx/name b/firmware/baseband-tx/name new file mode 100644 index 00000000..ff5e208f --- /dev/null +++ b/firmware/baseband-tx/name @@ -0,0 +1 @@ +Second module diff --git a/firmware/baseband-tx/packet_builder.cpp b/firmware/baseband-tx/packet_builder.cpp new file mode 100644 index 00000000..0fa5ec20 --- /dev/null +++ b/firmware/baseband-tx/packet_builder.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "packet_builder.hpp" diff --git a/firmware/baseband-tx/packet_builder.hpp b/firmware/baseband-tx/packet_builder.hpp new file mode 100644 index 00000000..edf50357 --- /dev/null +++ b/firmware/baseband-tx/packet_builder.hpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PACKET_BUILDER_H__ +#define __PACKET_BUILDER_H__ + +#include +#include +#include +#include + +#include "bit_pattern.hpp" + +template +class PacketBuilder { +public: + using PayloadType = std::bitset<1024>; + using PayloadHandlerFunc = std::function; + + PacketBuilder( + const PreambleMatcher preamble_matcher, + const UnstuffMatcher unstuff_matcher, + const EndMatcher end_matcher, + const PayloadHandlerFunc payload_handler + ) : payload_handler { payload_handler }, + preamble(preamble_matcher), + unstuff(unstuff_matcher), + end(end_matcher) + { + } + + void configure( + const PreambleMatcher preamble_matcher, + const UnstuffMatcher unstuff_matcher + ) { + preamble = preamble_matcher; + unstuff = unstuff_matcher; + + reset_state(); + } + + void execute( + const uint_fast8_t symbol + ) { + bit_history.add(symbol); + + switch(state) { + case State::Preamble: + if( preamble(bit_history, bits_received) ) { + state = State::Payload; + } + break; + + case State::Payload: + if( !unstuff(bit_history, bits_received) ) { + payload[bits_received++] = symbol; + } + + if( end(bit_history, bits_received) ) { + payload_handler(payload, bits_received); + reset_state(); + } else { + if( packet_truncated() ) { + reset_state(); + } + } + break; + + default: + reset_state(); + break; + } + } + +private: + enum State { + Preamble, + Payload, + }; + + bool packet_truncated() const { + return bits_received >= payload.size(); + } + + const PayloadHandlerFunc payload_handler; + + BitHistory bit_history; + PreambleMatcher preamble; + UnstuffMatcher unstuff; + EndMatcher end; + + size_t bits_received { 0 }; + State state { State::Preamble }; + PayloadType payload; + + void reset_state() { + bits_received = 0; + state = State::Preamble; + } +}; + +#endif/*__PACKET_BUILDER_H__*/ diff --git a/firmware/baseband-tx/proc_fsk_lcr.cpp b/firmware/baseband-tx/proc_fsk_lcr.cpp new file mode 100644 index 00000000..478c21cd --- /dev/null +++ b/firmware/baseband-tx/proc_fsk_lcr.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "proc_fsk_lcr.hpp" +#include "portapack_shared_memory.hpp" + +#include + +void LCRFSKProcessor::execute(buffer_c8_t buffer) { + + for (size_t i = 0; i= 9) { + s = 0; + + if (sample_count >= shared_memory.afsk_samples_per_bit) { + if (shared_memory.afsk_transmit_done == false) + cur_byte = shared_memory.lcrdata[byte_pos]; + if (!cur_byte) { + if (shared_memory.afsk_repeat) { + shared_memory.afsk_repeat--; + bit_pos = 0; + byte_pos = 0; + cur_byte = shared_memory.lcrdata[0]; + message.n = shared_memory.afsk_repeat; + shared_memory.application_queue.push(message); + } else { + message.n = 0; + shared_memory.afsk_transmit_done = true; + shared_memory.application_queue.push(message); + cur_byte = 0; + } + } + + gbyte = 0; + gbyte = cur_byte << 1; + gbyte |= 1; + + cur_bit = (gbyte >> (9-bit_pos)) & 1; + + if (bit_pos == 9) { + bit_pos = 0; + byte_pos++; + } else { + bit_pos++; + } + + sample_count = 0; + } else { + sample_count++; + } + if (cur_bit) + aphase += shared_memory.afsk_phase_inc_mark; + else + aphase += shared_memory.afsk_phase_inc_space; + } else { + s++; + } + + //sample = sine_table_f32[(aphase & 0x00FF0000)>>16]; + + //FM + frq = sample * shared_memory.afsk_fmmod; + + phase = (phase + frq); + sphase = phase + (256<<16); + + //re = sine_table_f32[(sphase & 0x00FF0000)>>16]; + //im = sine_table_f32[(phase & 0x00FF0000)>>16]; + + buffer.p[i] = {(int8_t)re,(int8_t)im}; + } +} diff --git a/firmware/baseband-tx/proc_fsk_lcr.hpp b/firmware/baseband-tx/proc_fsk_lcr.hpp new file mode 100644 index 00000000..247a2a7f --- /dev/null +++ b/firmware/baseband-tx/proc_fsk_lcr.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PROC_FSK_LCR_H__ +#define __PROC_FSK_LCR_H__ + +#include "baseband_processor.hpp" + +class LCRFSKProcessor : public BasebandProcessor { +public: + void execute(buffer_c8_t buffer) override; + +private: + int8_t re, im; + uint8_t s; + uint8_t bit_pos, byte_pos = 0; + char cur_byte = 0; + uint16_t gbyte; + uint8_t cur_bit = 0; + uint32_t sample_count; + uint32_t aphase, phase, sphase; + int32_t sample, sig, frq; + TXDoneMessage message; +}; + +#endif diff --git a/firmware/baseband-tx/proc_jammer.cpp b/firmware/baseband-tx/proc_jammer.cpp new file mode 100644 index 00000000..a8d4b0eb --- /dev/null +++ b/firmware/baseband-tx/proc_jammer.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "proc_jammer.hpp" +#include "portapack_shared_memory.hpp" +#include "sine_table.hpp" + +#include + +#define POLY_MASK_32 0xB4BCD35C + + +void JammerProcessor::execute(buffer_c8_t buffer) { + for (size_t i = 0; i 3000000) { + s = 0; + feedback = lfsr & 1; + lfsr >>= 1; + if (feedback == 1) + lfsr ^= POLY_MASK_32; + } else { + s++; + } + + aphase += lfsr;*/ + + /*if (s >= 10) { + s = 0; + aphase += 353205; // DEBUG + } else { + s++; + } + + sample = sintab[(aphase & 0x03FF0000)>>16];*/ + + // Duration timer + // + if (s >= 10000) { //shared_memory.jammer_ranges[ir].duration + s = 0; + for (;;) { + ir++; + if (ir > 15) ir = 0; + if (shared_memory.jammer_ranges[ir].active == true) break; + } + jammer_bw = shared_memory.jammer_ranges[ir].width / 4; + + message.freq = shared_memory.jammer_ranges[ir].center; + shared_memory.application_queue.push(message); + } else { + s++; + } + + // Ramp + /*if (r >= 10) { + if (sample < 128) + sample++; + else + sample = -127; + r = 0; + } else { + r++; + }*/ + + // Phase + if (r >= 70) { + aphase += ((aphase>>4) ^ 0x4573) << 14; + r = 0; + } else { + r++; + } + + aphase += 8830; + sample = sine_table_f32[(aphase & 0x00FF0000)>>16]; + + //FM + frq = sample * jammer_bw; // Bandwidth + + phase = (phase + frq); + sphase = phase + (256<<16); + + re = sine_table_f32[(sphase & 0x00FF0000)>>16]; + im = sine_table_f32[(phase & 0x00FF0000)>>16]; + + buffer.p[i] = {(int8_t)re,(int8_t)im}; + } +}; diff --git a/firmware/baseband-tx/proc_jammer.hpp b/firmware/baseband-tx/proc_jammer.hpp new file mode 100644 index 00000000..9a75940a --- /dev/null +++ b/firmware/baseband-tx/proc_jammer.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PROC_JAMMER_H__ +#define __PROC_JAMMER_H__ + +#include "baseband_processor.hpp" + +class JammerProcessor : public BasebandProcessor { +public: + void execute(buffer_c8_t buffer) override; + +private: + int32_t lfsr32 = 0xABCDE; + uint32_t s; + int8_t r, ir, re, im; + int64_t jammer_bw, jammer_center; + int feedback; + int32_t lfsr; + uint32_t sample_count; + uint32_t aphase, phase, sphase; + int32_t sample, frq; + RetuneMessage message; +}; + +#endif diff --git a/firmware/baseband-tx/rssi.cpp b/firmware/baseband-tx/rssi.cpp new file mode 100644 index 00000000..2e9cfa32 --- /dev/null +++ b/firmware/baseband-tx/rssi.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "rssi.hpp" + +#include + +#include "adc.hpp" +#include "rssi_dma.hpp" +#include "utility.hpp" + +#include "hal.h" +using namespace lpc43xx; + +#include "hackrf_hal.hpp" +using namespace hackrf::one; + +#include "portapack_adc.hpp" + +namespace rf { +namespace rssi { + +constexpr uint8_t adc1_sel = (1 << portapack::adc1_rssi_input); +const auto adc1_interrupt_mask = flp2(adc1_sel); + +constexpr adc::CR adc1_cr { + .sel = adc1_sel, + .clkdiv = 49, /* 400kHz sample rate, 2.5us/sample @ 200MHz PCLK */ + .resolution = 9, /* Ten clocks */ + .edge = 0, +}; +constexpr adc::Config adc1_config { + .cr = adc1_cr, +}; + +void init() { + adc1.clock_enable(); + adc1.interrupts_disable(); + adc1.power_up(adc1_config); + adc1.interrupts_enable(adc1_interrupt_mask); + + dma::init(); +} + +void start() { + dma::enable(); + adc1.start_burst(); +} + +void stop() { + dma::disable(); + adc1.stop_burst(); +} + +} /* namespace rssi */ +} /* namespace rf */ diff --git a/firmware/baseband-tx/rssi.hpp b/firmware/baseband-tx/rssi.hpp new file mode 100644 index 00000000..d340f338 --- /dev/null +++ b/firmware/baseband-tx/rssi.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __RSSI_H__ +#define __RSSI_H__ + +#include +#include + +#include "buffer.hpp" + +namespace rf { +namespace rssi { + +using sample_t = uint8_t; +using buffer_t = buffer_t; + +void init(); +void start(); +void stop(); + +} /* namespace rssi */ +} /* namespace rf */ + +#endif/*__RSSI_H__*/ diff --git a/firmware/baseband-tx/rssi_dma.cpp b/firmware/baseband-tx/rssi_dma.cpp new file mode 100644 index 00000000..5b060604 --- /dev/null +++ b/firmware/baseband-tx/rssi_dma.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "rssi_dma.hpp" + +#include +#include +#include + +#include "hal.h" +#include "gpdma.hpp" + +using namespace lpc43xx; + +#include "portapack_dma.hpp" +#include "portapack_adc.hpp" + +namespace rf { +namespace rssi { +namespace dma { + +/* TODO: SO MUCH REPEATED CODE IN touch_dma.cpp!!! */ + +static constexpr auto& gpdma_channel = gpdma::channels[portapack::adc1_gpdma_channel_number]; + +constexpr uint32_t gpdma_ahb_master_peripheral = 1; +constexpr uint32_t gpdma_ahb_master_memory = 0; +constexpr uint32_t gpdma_ahb_master_lli_fetch = 0; + +constexpr uint32_t gpdma_peripheral = 0xe; +constexpr uint32_t gpdma_src_peripheral = gpdma_peripheral; +constexpr uint32_t gpdma_dest_peripheral = gpdma_peripheral; + +constexpr gpdma::channel::LLIPointer lli_pointer(const void* lli) { + return { + .lm = gpdma_ahb_master_lli_fetch, + .r = 0, + .lli = reinterpret_cast(lli), + }; +} + +constexpr gpdma::channel::Control control(const size_t number_of_transfers) { + return { + .transfersize = number_of_transfers, + .sbsize = 0, /* Burst size: 1 transfer */ + .dbsize = 0, /* Burst size: 1 transfer */ + .swidth = 0, /* Source transfer width: byte (8 bits) */ + .dwidth = 2, /* Destination transfer width: word (32 bits) */ + .s = gpdma_ahb_master_peripheral, + .d = gpdma_ahb_master_memory, + .si = 0, + .di = 1, + .prot1 = 0, + .prot2 = 0, + .prot3 = 0, + .i = 1, + }; +} + +constexpr gpdma::channel::Config config() { + return { + .e = 0, + .srcperipheral = gpdma_src_peripheral, + .destperipheral = gpdma_dest_peripheral, + .flowcntrl = gpdma::FlowControl::PeripheralToMemory_DMAControl, + .ie = 1, + .itc = 1, + .l = 0, + .a = 0, + .h = 0, + }; +} + +struct buffers_config_t { + size_t count; + size_t items_per_buffer; +}; + +static buffers_config_t buffers_config; + +static sample_t *samples { nullptr }; +static gpdma::channel::LLI *lli { nullptr }; + +static Semaphore semaphore; +static volatile const gpdma::channel::LLI* next_lli = nullptr; + +static void transfer_complete() { + next_lli = gpdma_channel.next_lli(); + chSemSignalI(&semaphore); +} + +static void dma_error() { + disable(); +} + +void init() { + chSemInit(&semaphore, 0); + gpdma_channel.set_handlers(transfer_complete, dma_error); + + // LPC_GPDMA->SYNC |= (1 << gpdma_peripheral); +} + +void allocate(size_t buffer_count, size_t items_per_buffer) { + buffers_config = { + .count = buffer_count, + .items_per_buffer = items_per_buffer, + }; + + const auto peripheral = reinterpret_cast(&LPC_ADC1->DR[portapack::adc1_rssi_input]) + 1; + const auto control_value = control(gpdma::buffer_words(buffers_config.items_per_buffer, 1)); + + samples = new sample_t[buffers_config.count * buffers_config.items_per_buffer]; + lli = new gpdma::channel::LLI[buffers_config.count]; + + for(size_t i=0; i(&samples[i * buffers_config.items_per_buffer]); + lli[i].srcaddr = peripheral; + lli[i].destaddr = memory; + lli[i].lli = lli_pointer(&lli[(i + 1) % buffers_config.count]); + lli[i].control = control_value; + } +} + +void free() { + delete samples; + delete lli; +} + +void enable() { + const auto gpdma_config = config(); + gpdma_channel.configure(lli[0], gpdma_config); + + chSemReset(&semaphore, 0); + gpdma_channel.enable(); +} + +bool is_enabled() { + return gpdma_channel.is_enabled(); +} + +void disable() { + gpdma_channel.disable_force(); +} + +rf::rssi::buffer_t wait_for_buffer() { + const auto status = chSemWait(&semaphore); + if( status == RDY_OK ) { + const auto next = next_lli; + if( next ) { + const size_t next_index = next - &lli[0]; + const size_t free_index = (next_index + buffers_config.count - 2) % buffers_config.count; + return { reinterpret_cast(lli[free_index].destaddr), buffers_config.items_per_buffer }; + } else { + return { nullptr, 0 }; + } + } else { + // TODO: Should I return here, or loop if RDY_RESET? + return { nullptr, 0 }; + } +} + +} /* namespace dma */ +} /* namespace rssi */ +} /* namespace rf */ diff --git a/firmware/baseband-tx/rssi_dma.hpp b/firmware/baseband-tx/rssi_dma.hpp new file mode 100644 index 00000000..e543dcf6 --- /dev/null +++ b/firmware/baseband-tx/rssi_dma.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __RSSI_DMA_H__ +#define __RSSI_DMA_H__ + +#include + +#include "rssi.hpp" + +namespace rf { +namespace rssi { +namespace dma { + +using Handler = void (*)(); + +void init(); + +void allocate(size_t buffer_count, size_t items_per_buffer); +void free(); + +void enable(); +bool is_enabled(); + +void disable(); + +rf::rssi::buffer_t wait_for_buffer(); + +} /* namespace dma */ +} /* namespace rssi */ +} /* namespace rf */ + +#endif/*__RSSI_DMA_H__*/ diff --git a/firmware/baseband-tx/rssi_stats_collector.hpp b/firmware/baseband-tx/rssi_stats_collector.hpp new file mode 100644 index 00000000..6ab945bf --- /dev/null +++ b/firmware/baseband-tx/rssi_stats_collector.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __RSSI_STATS_COLLECTOR_H__ +#define __RSSI_STATS_COLLECTOR_H__ + +#include "rssi.hpp" +#include "message.hpp" + +#include +#include + +class RSSIStatisticsCollector { +public: + template + void process(rf::rssi::buffer_t buffer, Callback callback) { + auto p = buffer.p; + if( p == nullptr ) { + return; + } + + if( statistics.count == 0 ) { + const auto value_0 = *p; + statistics.min = value_0; + statistics.max = value_0; + } + + const auto end = &p[buffer.count]; + while(p < end) { + const uint32_t value = *(p++); + + if( statistics.min > value ) { + statistics.min = value; + } + if( statistics.max < value ) { + statistics.max = value; + } + + statistics.accumulator += value; + } + statistics.count += buffer.count; + + const size_t samples_per_update = buffer.sampling_rate * update_interval; + + if( statistics.count >= samples_per_update ) { + callback(statistics); + statistics.accumulator = 0; + statistics.count = 0; + } + } + +private: + static constexpr float update_interval { 0.1f }; + RSSIStatistics statistics; +}; + +#endif/*__RSSI_STATS_COLLECTOR_H__*/ diff --git a/firmware/baseband-tx/symbol_coding.hpp b/firmware/baseband-tx/symbol_coding.hpp new file mode 100644 index 00000000..03a42455 --- /dev/null +++ b/firmware/baseband-tx/symbol_coding.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SYMBOL_CODING_H__ +#define __SYMBOL_CODING_H__ + +#include +#include + +namespace symbol_coding { + +class NRZIDecoder { +public: + uint_fast8_t operator()(const uint_fast8_t symbol) { + const auto out = (~(symbol ^ last)) & 1; + last = symbol; + return out; + } + +private: + uint_fast8_t last { 0 }; +}; + +} /* namespace symbol_coding */ + +#endif/*__SYMBOL_CODING_H__*/ diff --git a/firmware/baseband-tx/touch_dma.cpp b/firmware/baseband-tx/touch_dma.cpp new file mode 100644 index 00000000..aa120ff3 --- /dev/null +++ b/firmware/baseband-tx/touch_dma.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "touch_dma.hpp" + +#include +#include +#include + +#include "hal.h" +#include "gpdma.hpp" +using namespace lpc43xx; + +#include "portapack_dma.hpp" +#include "portapack_adc.hpp" +#include "portapack_shared_memory.hpp" + +namespace touch { +namespace dma { + + +/* TODO: SO MUCH REPEATED CODE FROM rssi_dma.cpp!!! */ + +static constexpr auto& gpdma_channel = gpdma::channels[portapack::adc0_gpdma_channel_number]; + +constexpr uint32_t gpdma_ahb_master_peripheral = 1; +constexpr uint32_t gpdma_ahb_master_memory = 0; +constexpr uint32_t gpdma_ahb_master_lli_fetch = 0; + +constexpr uint32_t gpdma_src_peripheral = 0xd; +constexpr uint32_t gpdma_dest_peripheral = 0xd; + +constexpr gpdma::channel::LLIPointer lli_pointer(const void* lli) { + return { + .lm = gpdma_ahb_master_lli_fetch, + .r = 0, + .lli = reinterpret_cast(lli), + }; +} + +constexpr gpdma::channel::Control control(const size_t number_of_transfers) { + return { + .transfersize = number_of_transfers, + .sbsize = 2, /* Burst size: 8 transfers */ + .dbsize = 2, /* Burst size: 8 transfers */ + .swidth = 2, /* Source transfer width: word (32 bits) */ + .dwidth = 2, /* Destination transfer width: word (32 bits) */ + .s = gpdma_ahb_master_peripheral, + .d = gpdma_ahb_master_memory, + .si = 1, + .di = 1, + .prot1 = 0, + .prot2 = 0, + .prot3 = 0, + .i = 0, + }; +} + +constexpr gpdma::channel::Config config() { + return { + .e = 0, + .srcperipheral = gpdma_src_peripheral, + .destperipheral = gpdma_dest_peripheral, + .flowcntrl = gpdma::FlowControl::PeripheralToMemory_DMAControl, + .ie = 0, + .itc = 0, + .l = 0, + .a = 0, + .h = 0, + }; +} + +static gpdma::channel::LLI lli; + +constexpr size_t channels_per_sample = 8; +//constexpr size_t samples_per_frame = 40; +//constexpr size_t channel_samples_per_frame = channels_per_sample * samples_per_frame; + +void init() { +} + +void allocate() { + //samples = new sample_t[channel_samples_per_frame]; + //lli = new gpdma::channel::LLI; + lli.srcaddr = reinterpret_cast(&LPC_ADC0->DR[0]); + lli.destaddr = reinterpret_cast(&shared_memory.touch_adc_frame.dr[0]); + lli.lli = lli_pointer(&lli); + lli.control = control(channels_per_sample); +} + +void free() { + //delete samples; + //delete lli; +} + +void enable() { + const auto gpdma_config = config(); + gpdma_channel.configure(lli, gpdma_config); + gpdma_channel.enable(); +} + +bool is_enabled() { + return gpdma_channel.is_enabled(); +} + +void disable() { + gpdma_channel.disable_force(); +} + +} /* namespace dma */ +} /* namespace touch */ diff --git a/firmware/baseband-tx/touch_dma.hpp b/firmware/baseband-tx/touch_dma.hpp new file mode 100644 index 00000000..777230e4 --- /dev/null +++ b/firmware/baseband-tx/touch_dma.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __TOUCH_DMA_H__ +#define __TOUCH_DMA_H__ + +#include "buffer.hpp" + +#include + +namespace touch { +namespace dma { + +using sample_t = uint32_t; +using buffer_t = buffer_t; + +using Handler = void (*)(); + +void init(); + +void allocate(); +void free(); + +void enable(); +bool is_enabled(); + +void disable(); + +buffer_t wait_for_buffer(); + +} /* namespace dma */ +} /* namespace touch */ + +#endif/*__TOUCH_DMA_H__*/ diff --git a/firmware/baseband.bin b/firmware/baseband.bin new file mode 100644 index 0000000000000000000000000000000000000000..c972722a57fd13f0595a0644afe3de482da1c8ef GIT binary patch literal 29832 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 H3HiSOn^d)! literal 0 HcmV?d00001 diff --git a/firmware/baseband/description b/firmware/baseband/description new file mode 100644 index 00000000..517d61c1 --- /dev/null +++ b/firmware/baseband/description @@ -0,0 +1 @@ +Basic RX/TX stuff for testing :) diff --git a/firmware/baseband/main.cpp b/firmware/baseband/main.cpp index 65a3d682..785e50a4 100755 --- a/firmware/baseband/main.cpp +++ b/firmware/baseband/main.cpp @@ -216,398 +216,6 @@ private: } }; - -static const int8_t sintab[1024] = { -0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 9, 10, 11, 12, 12, 13, 14, 15, 16, 16, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 25, 26, 26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 44, 45, 46, 46, 47, 48, 49, 49, 50, 51, 51, 52, 53, 54, 54, 55, 56, 56, 57, 58, 58, 59, 60, 61, 61, 62, 63, 63, 64, 65, 65, 66, 67, 67, 68, 69, 69, 70, 71, 71, 72, 72, 73, 74, 74, 75, 76, 76, 77, 78, 78, 79, 79, 80, 81, 81, 82, 82, 83, 84, 84, 85, 85, 86, 86, 87, 88, 88, 89, 89, 90, 90, 91, 91, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 102, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 106, 107, 107, 108, 108, 109, 109, 109, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 118, 119, 119, 119, 120, 120, 120, 120, 121, 121, 121, 121, 122, 122, 122, 122, 122, 123, 123, 123, 123, 123, 124, 124, 124, 124, 124, 124, 125, 125, 125, 125, 125, 125, 125, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, -127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 125, 125, 125, 125, 125, 125, 125, 124, 124, 124, 124, 124, 124, 123, 123, 123, 123, 123, 122, 122, 122, 122, 122, 121, 121, 121, 121, 120, 120, 120, 120, 119, 119, 119, 118, 118, 118, 118, 117, 117, 117, 116, 116, 116, 115, 115, 115, 114, 114, 114, 113, 113, 113, 112, 112, 112, 111, 111, 111, 110, 110, 109, 109, 109, 108, 108, 107, 107, 106, 106, 106, 105, 105, 104, 104, 103, 103, 102, 102, 102, 101, 101, 100, 100, 99, 99, 98, 98, 97, 97, 96, 96, 95, 95, 94, 94, 93, 93, 92, 91, 91, 90, 90, 89, 89, 88, 88, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81, 80, 79, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 71, 70, 69, 69, 68, 67, 67, 66, 65, 65, 64, 63, 63, 62, 61, 61, 60, 59, 58, 58, 57, 56, 56, 55, 54, 54, 53, 52, 51, 51, 50, 49, 49, 48, 47, 46, 46, 45, 44, 44, 43, 42, 41, 41, 40, 39, 38, 38, 37, 36, 35, 35, 34, 33, 32, 32, 31, 30, 29, 29, 28, 27, 26, 26, 25, 24, 23, 22, 22, 21, 20, 19, 19, 18, 17, 16, 16, 15, 14, 13, 12, 12, 11, 10, 9, 9, 8, 7, 6, 5, 5, 4, 3, 2, 2, 1, 0, -1, -2, -2, -3, -4, -5, -5, -6, -7, -8, -9, -9, -10, -11, -12, -12, -13, -14, -15, -16, -16, -17, -18, -19, -19, -20, -21, -22, -22, -23, -24, -25, -26, -26, -27, -28, -29, -29, -30, -31, --32, -32, -33, -34, -35, -35, -36, -37, -38, -38, -39, -40, -41, -41, -42, -43, -44, -44, -45, -46, -46, -47, -48, -49, -49, -50, -51, -51, -52, -53, -54, -54, -55, -56, -56, -57, -58, -58, -59, -60, -61, -61, -62, -63, -63, -64, -65, -65, -66, -67, -67, -68, -69, -69, -70, -71, -71, -72, -72, -73, -74, -74, -75, -76, -76, -77, -78, -78, -79, -79, -80, -81, -81, -82, -82, -83, -84, -84, -85, -85, -86, -86, -87, -88, -88, -89, -89, -90, -90, -91, -91, -92, -93, -93, -94, -94, -95, -95, -96, -96, -97, -97, -98, -98, -99, -99, -100, -100, -101, -101, -102, -102, -102, -103, -103, -104, -104, -105, -105, -106, -106, -106, -107, -107, -108, -108, -109, -109, -109, -110, -110, -111, -111, -111, -112, -112, -112, -113, -113, -113, -114, -114, -114, -115, -115, -115, -116, -116, -116, -117, -117, -117, -118, -118, -118, -118, -119, -119, -119, -120, -120, -120, -120, -121, -121, -121, -121, -122, -122, -122, -122, -122, -123, -123, -123, -123, -123, -124, -124, -124, -124, -124, -124, -125, -125, -125, -125, -125, -125, -125, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, --126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -125, -125, -125, -125, -125, -125, -125, -124, -124, -124, -124, -124, -124, -123, -123, -123, -123, -123, -122, -122, -122, -122, -122, -121, -121, -121, -121, -120, -120, -120, -120, -119, -119, -119, -118, -118, -118, -118, -117, -117, -117, -116, -116, -116, -115, -115, -115, -114, -114, -114, -113, -113, -113, -112, -112, -112, -111, -111, -111, -110, -110, -109, -109, -109, -108, -108, -107, -107, -106, -106, -106, -105, -105, -104, -104, -103, -103, -102, -102, -102, -101, -101, -100, -100, -99, -99, -98, -98, -97, -97, -96, -96, -95, -95, -94, -94, -93, -93, -92, -91, -91, -90, -90, -89, -89, -88, -88, -87, -86, -86, -85, -85, -84, -84, -83, -82, -82, -81, -81, -80, -79, -79, -78, -78, -77, -76, -76, -75, -74, -74, -73, -72, -72, -71, -71, -70, -69, -69, -68, -67, -67, -66, -65, -65, -64, -63, -63, -62, -61, -61, -60, -59, -58, -58, -57, -56, -56, -55, -54, -54, -53, -52, -51, -51, -50, -49, -49, -48, -47, -46, -46, -45, -44, -44, -43, -42, -41, -41, -40, -39, -38, -38, -37, -36, -35, -35, -34, -33, -32, -32, -31, -30, -29, -29, -28, -27, -26, -26, -25, -24, -23, -22, -22, -21, -20, -19, -19, -18, -17, -16, -16, -15, -14, -13, -12, -12, -11, -10, -9, -9, -8, -7, -6, -5, -5, -4, -3, -2, -2, -1 -}; - -#define SAMPLES_PER_BIT 192 -#define FILTER_SIZE 576 -#define SAMPLE_BUFFER_SIZE SAMPLES_PER_BIT + FILTER_SIZE - -static int32_t waveform_biphase[] = { - 165,167,168,168,167,166,163,160, - 157,152,147,141,134,126,118,109, - 99,88,77,66,53,41,27,14, - 0,-14,-29,-44,-59,-74,-89,-105, - -120,-135,-150,-165,-179,-193,-206,-218, - -231,-242,-252,-262,-271,-279,-286,-291, - -296,-299,-301,-302,-302,-300,-297,-292, - -286,-278,-269,-259,-247,-233,-219,-202, - -185,-166,-145,-124,-101,-77,-52,-26, - 0,27,56,85,114,144,175,205, - 236,266,296,326,356,384,412,439, - 465,490,513,535,555,574,590,604, - 616,626,633,637,639,638,633,626, - 616,602,586,565,542,515,485,451, - 414,373,329,282,232,178,121,62, - 0,-65,-132,-202,-274,-347,-423,-500, - -578,-656,-736,-815,-894,-973,-1051,-1128, - -1203,-1276,-1347,-1415,-1479,-1540,-1596,-1648, - -1695,-1736,-1771,-1799,-1820,-1833,-1838,-1835, - -1822,-1800,-1767,-1724,-1670,-1605,-1527,-1437, - -1334,-1217,-1087,-943,-785,-611,-423,-219, - 0,235,487,755,1040,1341,1659,1994, - 2346,2715,3101,3504,3923,4359,4811,5280, - 5764,6264,6780,7310,7856,8415,8987,9573, - 10172,10782,11404,12036,12678,13329,13989,14656, - 15330,16009,16694,17382,18074,18767,19461,20155, - 20848,21539,22226,22909,23586,24256,24918,25571, - 26214,26845,27464,28068,28658,29231,29787,30325, - 30842,31339,31814,32266,32694,33097,33473,33823, - 34144,34437,34699,34931,35131,35299,35434,35535, - 35602,35634,35630,35591,35515,35402,35252,35065, - 34841,34579,34279,33941,33566,33153,32702,32214, - 31689,31128,30530,29897,29228,28525,27788,27017, - 26214,25379,24513,23617,22693,21740,20761,19755, - 18725,17672,16597,15501,14385,13251,12101,10935, - 9755,8563,7360,6148,4927,3701,2470,1235, - 0,-1235,-2470,-3701,-4927,-6148,-7360,-8563, - -9755,-10935,-12101,-13251,-14385,-15501,-16597,-17672, - -18725,-19755,-20761,-21740,-22693,-23617,-24513,-25379, - -26214,-27017,-27788,-28525,-29228,-29897,-30530,-31128, - -31689,-32214,-32702,-33153,-33566,-33941,-34279,-34579, - -34841,-35065,-35252,-35402,-35515,-35591,-35630,-35634, - -35602,-35535,-35434,-35299,-35131,-34931,-34699,-34437, - -34144,-33823,-33473,-33097,-32694,-32266,-31814,-31339, - -30842,-30325,-29787,-29231,-28658,-28068,-27464,-26845, - -26214,-25571,-24918,-24256,-23586,-22909,-22226,-21539, - -20848,-20155,-19461,-18767,-18074,-17382,-16694,-16009, - -15330,-14656,-13989,-13329,-12678,-12036,-11404,-10782, - -10172,-9573,-8987,-8415,-7856,-7310,-6780,-6264, - -5764,-5280,-4811,-4359,-3923,-3504,-3101,-2715, - -2346,-1994,-1659,-1341,-1040,-755,-487,-235, - 0,219,423,611,785,943,1087,1217, - 1334,1437,1527,1605,1670,1724,1767,1800, - 1822,1835,1838,1833,1820,1799,1771,1736, - 1695,1648,1596,1540,1479,1415,1347,1276, - 1203,1128,1051,973,894,815,736,656, - 578,500,423,347,274,202,132,65, - 0,-62,-121,-178,-232,-282,-329,-373, - -414,-451,-485,-515,-542,-565,-586,-602, - -616,-626,-633,-638,-639,-637,-633,-626, - -616,-604,-590,-574,-555,-535,-513,-490, - -465,-439,-412,-384,-356,-326,-296,-266, - -236,-205,-175,-144,-114,-85,-56,-27, - 0,26,52,77,101,124,145,166, - 185,202,219,233,247,259,269,278, - 286,292,297,300,302,302,301,299, - 296,291,286,279,271,262,252,242, - 231,218,206,193,179,165,150,135, - 120,105,89,74,59,44,29,14, - 0,-14,-27,-41,-53,-66,-77,-88, - -99,-109,-118,-126,-134,-141,-147,-152, - -157,-160,-163,-166,-167,-168,-168,-167 -}; - -/* -class RDSProcessor : public BasebandProcessor { -public: - void execute(buffer_c8_t buffer) override { - - for (size_t i = 0; i= 9) { - s = 0; - if(sample_count >= SAMPLES_PER_BIT) { - cur_bit = (shared_memory.rdsdata[(bit_pos / 26) & 15]>>(25-(bit_pos % 26))) & 1; - prev_output = cur_output; - cur_output = prev_output ^ cur_bit; - - int32_t *src = waveform_biphase; - int idx = in_sample_index; - - for(int j=0; j= SAMPLE_BUFFER_SIZE) idx = 0; - } - - in_sample_index += SAMPLES_PER_BIT; - if (in_sample_index >= SAMPLE_BUFFER_SIZE) in_sample_index -= SAMPLE_BUFFER_SIZE; - - bit_pos++; - sample_count = 0; - } - - sample = sample_buffer[out_sample_index]; - sample_buffer[out_sample_index] = 0; - out_sample_index++; - if (out_sample_index >= SAMPLE_BUFFER_SIZE) out_sample_index = 0; - - //AM @ 228k/4=57kHz - switch (mphase) { - case 0: - case 2: sample = 0; break; - case 1: break; - case 3: sample = -sample; break; - } - mphase++; - if (mphase >= 4) mphase = 0; - - sample_count++; - } else { - s++; - } - - //FM - frq = (sample>>16) * 386760; - - phase = (phase + frq); - sphase = phase + (256<<16); - - re = sintab[(sphase & 0x03FF0000)>>16]; - im = sintab[(phase & 0x03FF0000)>>16]; - - buffer.p[i] = {(int8_t)re,(int8_t)im}; - } - } - -private: - int8_t re, im; - uint8_t mphase, s; - uint32_t bit_pos; - int32_t sample_buffer[SAMPLE_BUFFER_SIZE] = {0}; - int32_t val; - uint8_t prev_output = 0; - uint8_t cur_output = 0; - uint8_t cur_bit = 0; - int sample_count = SAMPLES_PER_BIT; - int in_sample_index = 0; - int32_t sample; - int out_sample_index = SAMPLE_BUFFER_SIZE-1; - uint32_t phase, sphase; - int32_t sig, frq, frq_im, rdsc; - int32_t k; -};*/ - -class LCRFSKProcessor : public BasebandProcessor { -public: - void execute(buffer_c8_t buffer) override { - - for (size_t i = 0; i= 9) { - s = 0; - - if (sample_count >= shared_memory.afsk_samples_per_bit) { - if (shared_memory.afsk_transmit_done == false) - cur_byte = shared_memory.lcrdata[byte_pos]; - if (!cur_byte) { - if (shared_memory.afsk_repeat) { - shared_memory.afsk_repeat--; - bit_pos = 0; - byte_pos = 0; - cur_byte = shared_memory.lcrdata[0]; - message.n = shared_memory.afsk_repeat; - shared_memory.application_queue.push(message); - } else { - message.n = 0; - shared_memory.afsk_transmit_done = true; - shared_memory.application_queue.push(message); - cur_byte = 0; - } - } - - gbyte = 0; - gbyte = cur_byte << 1; - gbyte |= 1; - - cur_bit = (gbyte >> (9-bit_pos)) & 1; - - if (bit_pos == 9) { - bit_pos = 0; - byte_pos++; - } else { - bit_pos++; - } - - //aphase = 0x2FFFFFF; - - sample_count = 0; - } else { - sample_count++; - } - if (cur_bit) - aphase += shared_memory.afsk_phase_inc_mark; //(353205) - else - aphase += shared_memory.afsk_phase_inc_space; //(647542) - - sample = sintab[(aphase & 0x03FF0000)>>16]; - } else { - s++; - } - - sample = sintab[(aphase & 0x03FF0000)>>16]; - - //FM - frq = sample * shared_memory.afsk_fmmod; - - phase = (phase + frq); - sphase = phase + (256<<16); - - re = sintab[(sphase & 0x03FF0000)>>16]; - im = sintab[(phase & 0x03FF0000)>>16]; - - buffer.p[i] = {(int8_t)re,(int8_t)im}; - } - } - -private: - int8_t re, im; - uint8_t s; - uint8_t bit_pos, byte_pos = 0; - char cur_byte = 0; - uint16_t gbyte; - uint8_t cur_bit = 0; - uint32_t sample_count; - uint32_t aphase, phase, sphase; - int32_t sample, sig, frq; - TXDoneMessage message; -}; - -/*class ToneProcessor : public BasebandProcessor { -public: - void execute(buffer_c8_t buffer) override { - - for (size_t i = 0; i= 9) { - s = 0; - aphase += 353205; // DEBUG - sample = sintab[(aphase & 0x03FF0000)>>16]; - } else { - s++; - } - - sample = sintab[(aphase & 0x03FF0000)>>16]; - - //FM - frq = sample * 500; // DEBUG - - phase = (phase + frq); - sphase = phase + (256<<16); - - re = sintab[(sphase & 0x03FF0000)>>16]; - im = sintab[(phase & 0x03FF0000)>>16]; - - buffer.p[i] = {(int8_t)re,(int8_t)im}; - } - } - -private: - int8_t re, im; - uint8_t s; - uint32_t sample_count; - uint32_t aphase, phase, sphase; - int32_t sample, sig, frq; -};*/ - - -#define POLY_MASK_32 0xB4BCD35C - -class JammerProcessor : public BasebandProcessor { -public: - void execute(buffer_c8_t buffer) override { - - for (size_t i = 0; i 3000000) { - s = 0; - feedback = lfsr & 1; - lfsr >>= 1; - if (feedback == 1) - lfsr ^= POLY_MASK_32; - } else { - s++; - } - - aphase += lfsr;*/ - - /*if (s >= 10) { - s = 0; - aphase += 353205; // DEBUG - } else { - s++; - } - - sample = sintab[(aphase & 0x03FF0000)>>16];*/ - - // Duration timer - // - if (s >= 10000) { //shared_memory.jammer_ranges[ir].duration - s = 0; - for (;;) { - ir++; - if (ir > 15) ir = 0; - if (shared_memory.jammer_ranges[ir].active == true) break; - } - jammer_bw = shared_memory.jammer_ranges[ir].width; - - message.freq = shared_memory.jammer_ranges[ir].center; - shared_memory.application_queue.push(message); - } else { - s++; - } - - // Ramp - /*if (r >= 10) { - if (sample < 128) - sample++; - else - sample = -127; - r = 0; - } else { - r++; - }*/ - - // Phase - if (r >= 70) { - aphase += ((aphase>>4) ^ 0x4573) << 14; - r = 0; - } else { - r++; - } - - aphase += 35320; - sample = sintab[(aphase & 0x03FF0000)>>16]; - - //FM - frq = sample * jammer_bw; // Bandwidth - - //65536 -> 0.6M - //131072 -> 1.2M - - phase = (phase + frq); - sphase = phase + (256<<16); - - re = sintab[(sphase & 0x03FF0000)>>16]; - im = sintab[(phase & 0x03FF0000)>>16]; - - buffer.p[i] = {(int8_t)re,(int8_t)im}; - } - } - -private: - int32_t lfsr32 = 0xABCDE; - uint32_t s; - int8_t r, ir, re, im; - int64_t jammer_bw, jammer_center; - int feedback; - int32_t lfsr; - uint32_t sample_count; - uint32_t aphase, phase, sphase; - int32_t sample, frq; - RetuneMessage message; -}; - extern "C" { void __late_init(void) { @@ -736,6 +344,23 @@ private: const auto baseband_buffer = new std::array(); + +char ram_loop[32]; +typedef int (*fn_ptr)(void); +fn_ptr loop_ptr; + +void ram_loop_fn(void) { + while(1) {} +} + +void wait_for_switch(void) { + memcpy(&ram_loop[0], reinterpret_cast(&ram_loop_fn), 32); + loop_ptr = reinterpret_cast(&ram_loop[0]); + ReadyForSwitchMessage message { true }; + shared_memory.application_queue.push(message); + (*loop_ptr)(); + return; +} int main(void) { init(); @@ -791,26 +416,9 @@ int main(void) { direction = baseband::Direction::Receive; baseband_thread.baseband_processor = new TPMSProcessor(); break; - - /*case 15: - direction = baseband::Direction::Transmit; - baseband_thread.baseband_processor = new RDSProcessor(); - break;*/ - - case 16: - direction = baseband::Direction::Transmit; - baseband_thread.baseband_processor = new LCRFSKProcessor(); - break; - - /*case 17: - direction = baseband::Direction::Transmit; - baseband_thread.baseband_processor = new ToneProcessor(); - break;*/ - - case 18: - direction = baseband::Direction::Transmit; - baseband_thread.baseband_processor = new JammerProcessor(); - break; + + case 0xFF: + wait_for_switch(); default: break; diff --git a/firmware/baseband/name b/firmware/baseband/name new file mode 100644 index 00000000..8d194a1d --- /dev/null +++ b/firmware/baseband/name @@ -0,0 +1 @@ +First module diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 20449e30..039660a0 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -49,6 +49,7 @@ public: TXDone = 9, SDCardStatus = 10, Retune = 11, + ReadyForSwitch = 12, MAX }; @@ -265,6 +266,17 @@ public: int n = 0; }; +class ReadyForSwitchMessage : public Message { +public: + ReadyForSwitchMessage( + bool ok + ) : Message { ID::ReadyForSwitch } + { + } + + const bool ok = false; +}; + class RetuneMessage : public Message { public: RetuneMessage( diff --git a/firmware/common/sine_table.hpp b/firmware/common/sine_table.hpp index 84979dfe..c749d008 100644 --- a/firmware/common/sine_table.hpp +++ b/firmware/common/sine_table.hpp @@ -39,7 +39,7 @@ constexpr size_t sine_table_f32_period_log2 = 8; constexpr size_t sine_table_f32_period = 1 << sine_table_f32_period_log2; constexpr uint32_t sine_table_f32_index_mask = sine_table_f32_period - 1; -constexpr std::array sine_table_f32 { { +static constexpr std::array sine_table_f32 { { 0.00000000e+00, 2.45412285e-02, 4.90676743e-02, 7.35645636e-02, 9.80171403e-02, 1.22410675e-01, 1.46730474e-01, 1.70961889e-01, 1.95090322e-01, diff --git a/firmware/common/ui.hpp b/firmware/common/ui.hpp index a61e3d08..89324944 100644 --- a/firmware/common/ui.hpp +++ b/firmware/common/ui.hpp @@ -79,6 +79,10 @@ struct Color { static constexpr Color blue() { return { 0, 0, 255 }; } + + static constexpr Color cyan() { + return { 0, 128, 255 }; + } static constexpr Color white() { return { 255, 255, 255 }; @@ -87,6 +91,10 @@ struct Color { static constexpr Color grey() { return { 127, 127, 127 }; } + + static constexpr Color purple() { + return { 204, 0, 102 }; + } }; #if 0 enum class CardinalDirection : uint8_t { diff --git a/firmware/portapack-h1-firmware.bin b/firmware/portapack-h1-firmware.bin index 9241323ffc7a1093f24c5596659346c21087c375..bd6b5d2455cc1f14e80c5d1f36577b4d26de0508 100644 GIT binary patch delta 50631 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�VTNgcAvzb1q8fTEB2^5P8-9qi@f6 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 delta 50379 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 ... +""" + +def read_image(path): + f = open(path, 'rb') + data = f.read() + f.close() + return data + +def write_file(data, path): + f = open(path, 'wb') + f.write(data) + f.close() + +if len(sys.argv) == 1: + print(usage_message) + sys.exit(-1) + +data = bytearray() +h_data = bytearray() +name = bytearray() +info = bytearray() +description = bytearray() +data_default_byte = bytearray((0,)) +m = md5.new() + +sys.argv = sys.argv[1:] + +# Format for module file: +# Magic (4), Version (2), Length (4), Name (16), MD5 (16), Description (214) +# Unpadded module binary... + +for args in sys.argv: + data = read_image(args + '/build/' + args + '.bin') + info = 'PPM ' + info += struct.pack('H', 1) + info += struct.pack('I', len(data)) + name = read_image(args + '/name') + if len(name) > 16: + name = name[0:15] + pad_size = 16 - len(name) + name += (data_default_byte * pad_size) + info += name + m.update(data) + digest = m.digest() + pad_size = 16 - len(digest) + digest += (data_default_byte * pad_size) + info += digest + description = read_image(args + '/description') + if len(description) > 214: + description = description[0:213] + pad_size = 214 - len(description) + description += (data_default_byte * pad_size) + info += description + data = info + data + write_file(data, args + '.bin') + md5sum = '' + for byte in digest: + md5sum += '0x' + format(byte, '02x') + ',' + h_data += 'const char md5_' + args.replace('-','_') + '[16] = {' + md5sum + '};\n' + +write_file(h_data, 'application/modules.h')