diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index 1ae9fec3..77e36f0b 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -1,659 +1,659 @@ -/* - * 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 "hackrf_hal.hpp" -#include "hackrf_gpio.hpp" -using namespace hackrf::one; - -#include "portapack_shared_memory.hpp" -#include "portapack_hal.hpp" -#include "portapack_io.hpp" - -#include "cpld_update.hpp" - -#include "message_queue.hpp" - -#include "si5351.hpp" -#include "clock_manager.hpp" - -#include "wm8731.hpp" -#include "radio.hpp" -#include "touch.hpp" -#include "touch_adc.hpp" - -#include "ui.hpp" -#include "ui_widget.hpp" -#include "ui_painter.hpp" -#include "ui_navigation.hpp" - -#include "receiver_model.hpp" - -#include "irq_ipc.hpp" -#include "irq_lcd_frame.hpp" -#include "irq_controls.hpp" - -#include "event.hpp" - -#include "i2c_pp.hpp" -#include "spi_pp.hpp" - -#include "m4_startup.hpp" - -#include "debug.hpp" -#include "led.hpp" - -#include "gcc.hpp" - -#include - -I2C i2c0(&I2CD0); -SPI ssp0(&SPID1); -SPI ssp1(&SPID2); - -wolfson::wm8731::WM8731 audio_codec { i2c0, portapack::wm8731_i2c_address }; - -/* From ChibiOS crt0.c: - * Two stacks available for Cortex-M, main stack or process stack. - * - * Thread mode: Used to execute application software. The processor - * enters Thread mode when it comes out of reset. - * Handler mode: Used to handle exceptions. The processor returns to - * Thread mode when it has finished all exception processing. - * - * ChibiOS configures the Cortex-M in dual-stack mode. (CONTROL[1]=1) - * When CONTROL[1]=1, PSP is used when the processor is in Thread mode. - * - * MSP is always used when the processor is in Handler mode. - * - * __main_stack_size__ : 0x2000???? - 0x2000???? = - * Used for exception handlers. Yes, really. - * __process_stack_size__ : 0x2000???? - 0x2000???? = - * Used by main(). - * - * After chSysInit(), the current instructions stream (usually main()) - * becomes the main thread. - */ - -#if 0 -static const SPIConfig ssp_config_w25q80bv = { - .end_cb = NULL, - .ssport = ?, - .sspad = ?, - .cr0 = - CR0_CLOCKRATE() - | ? - | ? - , - .cpsr = ?, -}; - -static spi_bus_t ssp0 = { - .obj = &SPID1, - .config = &ssp_config_w25q80bv, - .start = spi_chibi_start, - .stop = spi_chibi_stop, - .transfer = spi_chibi_transfer, - .transfer_gather = spi_chibi_transfer_gather, -}; -#endif - -/* ChibiOS initialization sequence: - * ResetHandler: - * Initialize FPU (if present) - * Initialize stacks (fill with pattern) - * __early_init() - * Enable extra processor exceptions for debugging - * Init data segment (flash -> data) - * Initialize BSS (fill with 0) - * __late_init() - * reset_peripherals() - * halInit() - * hal_lld_init() - * Init timer 3 as cycle counter - * Init RIT as SysTick - * palInit() - * gptInit() - * i2cInit() - * sdcInit() - * spiInit() - * rtcInit() - * boardInit() - * chSysInit() - * Constructors - * main() - * Destructors - * _default_exit() (default is infinite loop) - */ - -si5351::Si5351 clock_generator { - i2c0, si5351_i2c_address -}; - -ClockManager clock_manager { - i2c0, clock_generator -}; - -ReceiverModel receiver_model { - clock_manager -}; - -class Power { -public: - void init() { - /* VAA powers: - * MAX5864 analog section. - * MAX2837 registers and other functions. - * RFFC5072 analog section. - * - * Beware that power applied to pins of the MAX2837 may - * show up on VAA and start powering other components on the - * VAA net. So turn on VAA before driving pins from MCU to - * MAX2837. - */ - /* Turn on VAA */ - gpio_vaa_disable.clear(); - gpio_vaa_disable.output(); - - /* 1V8 powers CPLD internals. - */ - /* Turn on 1V8 */ - gpio_1v8_enable.set(); - gpio_1v8_enable.output(); - - /* Set VREGMODE for switching regulator on HackRF One */ - gpio_vregmode.set(); - gpio_vregmode.output(); - } - -private: -}; - -static Power power; - -static void init() { - for(const auto& pin : pins) { - pin.init(); - } - - /* Configure other pins */ - LPC_SCU->SFSI2C0 = - (1U << 3) - | (1U << 11) - ; - - power.init(); - - gpio_max5864_select.set(); - gpio_max5864_select.output(); - - gpio_max2837_select.set(); - gpio_max2837_select.output(); - - led_usb.setup(); - led_rx.setup(); - led_tx.setup(); - - clock_manager.init(); - clock_manager.run_at_full_speed(); - - clock_manager.start_audio_pll(); - audio_codec.init(); - - clock_manager.enable_first_if_clock(); - clock_manager.enable_second_if_clock(); - clock_manager.enable_codec_clocks(); - radio::init(); - - touch::adc::init(); -} - -extern "C" { - -void __late_init(void) { - - reset(); - - /* - * 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(); -} - -} - -extern "C" { - -CH_IRQ_HANDLER(RTC_IRQHandler) { - CH_IRQ_PROLOGUE(); - - chSysLockFromIsr(); - events_flag_isr(EVT_MASK_RTC_TICK); - chSysUnlockFromIsr(); - - rtc::interrupt::clear_all(); - - CH_IRQ_EPILOGUE(); -} - -} - -static bool ui_dirty = true; - -void ui::dirty_event() { - ui_dirty = true; -} - -class EventDispatcher { -public: - EventDispatcher( - ui::Widget* const top_widget, - ui::Painter& painter, - ui::Context& context - ) : top_widget { top_widget }, - painter { painter }, - context { context } - { - // touch_manager.on_started = [this](const ui::TouchEvent event) { - // this->context.focus_manager.update(this->top_widget, event); - // }; - - touch_manager.on_event = [this](const ui::TouchEvent event) { - this->on_touch_event(event); - }; - } - - eventmask_t wait() { - return chEvtWaitAny(ALL_EVENTS); - } - - void dispatch(const eventmask_t events) { - if( events & EVT_MASK_APPLICATION ) { - handle_application_queue(); - } - - if( events & EVT_MASK_RTC_TICK ) { - handle_rtc_tick(); - } - - if( events & EVT_MASK_LCD_FRAME_SYNC ) { - handle_lcd_frame_sync(); - } - - if( events & EVT_MASK_SD_CARD_PRESENT ) { - handle_sd_card_detect(); - } - - if( events & EVT_MASK_SWITCHES ) { - handle_switches(); - } - - if( events & EVT_MASK_ENCODER ) { - handle_encoder(); - } - - if( events & EVT_MASK_TOUCH ) { - handle_touch(); - } - } - -private: - touch::Manager touch_manager; - ui::Widget* const top_widget; - ui::Painter& painter; - ui::Context& context; - uint32_t encoder_last = 0; - - void handle_application_queue() { - while( !shared_memory.application_queue.is_empty() ) { - auto message = shared_memory.application_queue.pop(); - - auto& fn = context.message_map[message->id]; - if( fn ) { - fn(message); - } - - message->state = Message::State::Free; - } - } - - void handle_rtc_tick() { - /* - if( shared_memory.application_queue.push(&rssi_request) ) { - led_rx.on(); - } - */ - /* - if( callback_second_tick ) { - rtc::RTC datetime; - rtcGetTime(&RTCD1, &datetime); - - callback_second_tick(datetime); - } - */ - //static std::function callback_fifos_state; - //static std::function callback_cpu_ticks; - /* - if( callback_fifos_state ) { - callback_fifos_state(shared_memory.application_queue.len(), baseband_queue.len()); - } - */ - /* - if( callback_cpu_ticks ) { - //const auto thread_self = chThdSelf(); - const auto thread = chSysGetIdleThread(); - //const auto ticks = chThdGetTicks(thread); - - callback_cpu_ticks(thread->total_ticks); - } - */ - - /* - callback_fifos_state = [&system_view](size_t app_n, size_t baseband_n) { - system_view.status_view.text_app_fifo_n.set( - ui::to_string_dec_uint(app_n, 3) - ); - system_view.status_view.text_baseband_fifo_n.set( - ui::to_string_dec_uint(baseband_n, 3) - ); - }; - */ - /* - callback_cpu_ticks = [&system_view](systime_t ticks) { - static systime_t last_ticks = 0; - const auto delta_ticks = ticks - last_ticks; - last_ticks = ticks; - - const auto text_pct = ui::to_string_dec_uint(delta_ticks / 2000000, 3) + "% idle"; - system_view.status_view.text_ticks.set( - text_pct - ); - }; - */ - } -/* - void paint_widget(ui::Widget* const w) { - if( w->visible() ) { - if( w->dirty() ) { - w->paint(painter); - // Force-paint all children. - for(const auto child : w->children()) { - child->set_dirty(); - paint_widget(child); - } - w->set_clean(); - } else { - // Selectively paint all children. - for(const auto child : w->children()) { - paint_widget(child); - } - } - } - } -*/ - void paint_widget(ui::Widget* const w) { - if( w->hidden() ) { - // Mark widget (and all children) as invisible. - w->visible(false); - } else { - // Mark this widget as visible and recurse. - w->visible(true); - - if( w->dirty() ) { - w->paint(painter); - // Force-paint all children. - for(const auto child : w->children()) { - child->set_dirty(); - paint_widget(child); - } - w->set_clean(); - } else { - // Selectively paint all children. - for(const auto child : w->children()) { - paint_widget(child); - } - } - } - } - - static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event) { - if( !w->hidden() ) { - // To achieve reverse depth ordering (last object drawn is - // considered "top"), descend first. - for(const auto child : w->children()) { - const auto touched_widget = touch_widget(child, event); - if( touched_widget ) { - return touched_widget; - } - } - - const auto r = w->screen_rect(); - if( r.contains(event.point) ) { - if( w->on_touch(event) ) { - // This widget responded. Return it up the call stack. - return w; - } - } - } - return nullptr; - } - - ui::Widget* captured_widget { nullptr }; - - void on_touch_event(ui::TouchEvent event) { - /* TODO: Capture widget receiving the Start event, send Move and - * End events to the same widget. - */ - /* Capture Start widget. - * If touch is over Start widget at Move event, then the widget - * should be highlighted. If the touch is not over the Start - * widget at Move event, widget should un-highlight. - * If touch is over Start widget at End event, then the widget - * action should occur. - */ - if( event.type == ui::TouchEvent::Type::Start ) { - captured_widget = touch_widget(this->top_widget, event); - } - - if( captured_widget ) { - captured_widget->on_touch(event); - } - } - - void handle_lcd_frame_sync() { - if( ui_dirty ) { - paint_widget(top_widget); - ui_dirty = false; - } - } - - void handle_sd_card_detect() { - - } - - void handle_switches() { - const auto switches_state = get_switches_state(); - for(size_t i=0; i(i); - if( !event_bubble_key(event) ) { - context.focus_manager.update(top_widget, event); - } - } - } - } - - void handle_encoder() { - const uint32_t encoder_now = get_encoder_position(); - const int32_t delta = static_cast(encoder_now - encoder_last); - encoder_last = encoder_now; - const auto event = static_cast(delta); - event_bubble_encoder(event); - } - - void handle_touch() { - touch_manager.feed(get_touch_frame()); - } - - bool event_bubble_key(const ui::KeyEvent event) { - auto target = context.focus_manager.focus_widget(); - while( (target != nullptr) && !target->on_key(event) ) { - target = target->parent(); - } - - /* Return true if event was consumed. */ - return (target != nullptr); - } - - void event_bubble_encoder(const ui::EncoderEvent event) { - auto target = context.focus_manager.focus_widget(); - while( (target != nullptr) && !target->on_encoder(event) ) { - target = target->parent(); - } - } -}; - -/////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////// - -/* Thinking things through a bit: - - main() produces UI events. - Touch events: - Hit test entire screen hierarchy and send to hit widget. - If modal view shown, block UI events destined outside. - Navigation events: - Move from current focus widget to "nearest" focusable widget. - If current view is modal, don't allow events to bubble outside - of modal view. - System events: - Power off from WWDT provides enough time to flush changes to - VBAT RAM? - SD card events? Insert/eject/error. - - - View stack: - Views that are hidden should deconstruct their widgets? - Views that are shown after being hidden will reconstruct their - widgets from data in their model? - Hence, hidden views will not eat up memory beyond their model? - Beware loops where the stack can get wildly deep? - Breaking out data models from views should allow some amount of - power-off persistence in the VBAT RAM area. In fact, the data - models could be instantiated there? But then, how to protect - from corruption if power is pulled? Does WWDT provide enough - warning to flush changes? - - Navigation... - If you move off the left side of the screen, move to breadcrumb - "back" item, no matter where you're coming from? -*/ - -/* -message_handlers[Message::ID::FSKPacket] = [](const Message* const p) { - const auto message = static_cast(p); - fsk_packet(message); -}; - -message_handlers[Message::ID::TestResults] = [&system_view](const Message* const p) { - const auto message = static_cast(p); - char c[10]; - c[0] = message->results.translate_by_fs_over_4_and_decimate_by_2_cic3 ? '+' : '-'; - c[1] = message->results.fir_cic3_decim_2_s16_s16 ? '+' : '-'; - c[2] = message->results.fir_64_and_decimate_by_2_complex ? '+' : '-'; - c[3] = message->results.fxpt_atan2 ? '+' : '-'; - c[4] = message->results.multiply_conjugate_s16_s32 ? '+' : '-'; - c[5] = 0; - system_view.status_view.portapack.set(c); -}; -*/ - -portapack::IO portapack::io { - portapack::gpio_dir, - portapack::gpio_lcd_rd, - portapack::gpio_lcd_wr, - portapack::gpio_io_stbx, - portapack::gpio_addr, - portapack::gpio_lcd_te, - portapack::gpio_unused, -}; - -int main(void) { - init(); - - if( !cpld_update_if_necessary() ) { - chSysHalt(); - } - - init_message_queues(); - - portapack::io.init(); - ui::Context context; - context.display.init(); - - sdcStart(&SDCD1, nullptr); - - rtc::interrupt::enable_second_inc(); - nvicEnableVector(RTC_IRQn, CORTEX_PRIORITY_MASK(LPC_RTC_IRQ_PRIORITY)); - - controls_init(); - - lcd_frame_sync_configure(); - - events_initialize(chThdSelf()); - - ui::SystemView system_view { - context, - { 0, 0, 240, 320 } - }; - ui::Painter painter { context.display }; - EventDispatcher event_dispatcher { &system_view, painter, context }; - -context.message_map[Message::ID::FSKPacket] = [](const Message* const p) { - const auto message = static_cast(p); - (void)message; - led_usb.toggle(); -}; - - m4txevent_interrupt_enable(); - m4_init(); - - while(true) { - const auto events = event_dispatcher.wait(); - event_dispatcher.dispatch(events); - } - - return 0; -} +/* + * 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 "hackrf_hal.hpp" +#include "hackrf_gpio.hpp" +using namespace hackrf::one; + +#include "portapack_shared_memory.hpp" +#include "portapack_hal.hpp" +#include "portapack_io.hpp" + +#include "cpld_update.hpp" + +#include "message_queue.hpp" + +#include "si5351.hpp" +#include "clock_manager.hpp" + +#include "wm8731.hpp" +#include "radio.hpp" +#include "touch.hpp" +#include "touch_adc.hpp" + +#include "ui.hpp" +#include "ui_widget.hpp" +#include "ui_painter.hpp" +#include "ui_navigation.hpp" + +#include "receiver_model.hpp" + +#include "irq_ipc.hpp" +#include "irq_lcd_frame.hpp" +#include "irq_controls.hpp" + +#include "event.hpp" + +#include "i2c_pp.hpp" +#include "spi_pp.hpp" + +#include "m4_startup.hpp" + +#include "debug.hpp" +#include "led.hpp" + +#include "gcc.hpp" + +#include + +I2C i2c0(&I2CD0); +SPI ssp0(&SPID1); +SPI ssp1(&SPID2); + +wolfson::wm8731::WM8731 audio_codec { i2c0, portapack::wm8731_i2c_address }; + +/* From ChibiOS crt0.c: + * Two stacks available for Cortex-M, main stack or process stack. + * + * Thread mode: Used to execute application software. The processor + * enters Thread mode when it comes out of reset. + * Handler mode: Used to handle exceptions. The processor returns to + * Thread mode when it has finished all exception processing. + * + * ChibiOS configures the Cortex-M in dual-stack mode. (CONTROL[1]=1) + * When CONTROL[1]=1, PSP is used when the processor is in Thread mode. + * + * MSP is always used when the processor is in Handler mode. + * + * __main_stack_size__ : 0x2000???? - 0x2000???? = + * Used for exception handlers. Yes, really. + * __process_stack_size__ : 0x2000???? - 0x2000???? = + * Used by main(). + * + * After chSysInit(), the current instructions stream (usually main()) + * becomes the main thread. + */ + +#if 0 +static const SPIConfig ssp_config_w25q80bv = { + .end_cb = NULL, + .ssport = ?, + .sspad = ?, + .cr0 = + CR0_CLOCKRATE() + | ? + | ? + , + .cpsr = ?, +}; + +static spi_bus_t ssp0 = { + .obj = &SPID1, + .config = &ssp_config_w25q80bv, + .start = spi_chibi_start, + .stop = spi_chibi_stop, + .transfer = spi_chibi_transfer, + .transfer_gather = spi_chibi_transfer_gather, +}; +#endif + +/* ChibiOS initialization sequence: + * ResetHandler: + * Initialize FPU (if present) + * Initialize stacks (fill with pattern) + * __early_init() + * Enable extra processor exceptions for debugging + * Init data segment (flash -> data) + * Initialize BSS (fill with 0) + * __late_init() + * reset_peripherals() + * halInit() + * hal_lld_init() + * Init timer 3 as cycle counter + * Init RIT as SysTick + * palInit() + * gptInit() + * i2cInit() + * sdcInit() + * spiInit() + * rtcInit() + * boardInit() + * chSysInit() + * Constructors + * main() + * Destructors + * _default_exit() (default is infinite loop) + */ + +si5351::Si5351 clock_generator { + i2c0, si5351_i2c_address +}; + +ClockManager clock_manager { + i2c0, clock_generator +}; + +ReceiverModel receiver_model { + clock_manager +}; + +class Power { +public: + void init() { + /* VAA powers: + * MAX5864 analog section. + * MAX2837 registers and other functions. + * RFFC5072 analog section. + * + * Beware that power applied to pins of the MAX2837 may + * show up on VAA and start powering other components on the + * VAA net. So turn on VAA before driving pins from MCU to + * MAX2837. + */ + /* Turn on VAA */ + gpio_vaa_disable.clear(); + gpio_vaa_disable.output(); + + /* 1V8 powers CPLD internals. + */ + /* Turn on 1V8 */ + gpio_1v8_enable.set(); + gpio_1v8_enable.output(); + + /* Set VREGMODE for switching regulator on HackRF One */ + gpio_vregmode.set(); + gpio_vregmode.output(); + } + +private: +}; + +static Power power; + +static void init() { + for(const auto& pin : pins) { + pin.init(); + } + + /* Configure other pins */ + LPC_SCU->SFSI2C0 = + (1U << 3) + | (1U << 11) + ; + + power.init(); + + gpio_max5864_select.set(); + gpio_max5864_select.output(); + + gpio_max2837_select.set(); + gpio_max2837_select.output(); + + led_usb.setup(); + led_rx.setup(); + led_tx.setup(); + + clock_manager.init(); + clock_manager.run_at_full_speed(); + + clock_manager.start_audio_pll(); + audio_codec.init(); + + clock_manager.enable_first_if_clock(); + clock_manager.enable_second_if_clock(); + clock_manager.enable_codec_clocks(); + radio::init(); + + touch::adc::init(); +} + +extern "C" { + +void __late_init(void) { + + reset(); + + /* + * 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(); +} + +} + +extern "C" { + +CH_IRQ_HANDLER(RTC_IRQHandler) { + CH_IRQ_PROLOGUE(); + + chSysLockFromIsr(); + events_flag_isr(EVT_MASK_RTC_TICK); + chSysUnlockFromIsr(); + + rtc::interrupt::clear_all(); + + CH_IRQ_EPILOGUE(); +} + +} + +static bool ui_dirty = true; + +void ui::dirty_event() { + ui_dirty = true; +} + +class EventDispatcher { +public: + EventDispatcher( + ui::Widget* const top_widget, + ui::Painter& painter, + ui::Context& context + ) : top_widget { top_widget }, + painter { painter }, + context { context } + { + // touch_manager.on_started = [this](const ui::TouchEvent event) { + // this->context.focus_manager.update(this->top_widget, event); + // }; + + touch_manager.on_event = [this](const ui::TouchEvent event) { + this->on_touch_event(event); + }; + } + + eventmask_t wait() { + return chEvtWaitAny(ALL_EVENTS); + } + + void dispatch(const eventmask_t events) { + if( events & EVT_MASK_APPLICATION ) { + handle_application_queue(); + } + + if( events & EVT_MASK_RTC_TICK ) { + handle_rtc_tick(); + } + + if( events & EVT_MASK_LCD_FRAME_SYNC ) { + handle_lcd_frame_sync(); + } + + if( events & EVT_MASK_SD_CARD_PRESENT ) { + handle_sd_card_detect(); + } + + if( events & EVT_MASK_SWITCHES ) { + handle_switches(); + } + + if( events & EVT_MASK_ENCODER ) { + handle_encoder(); + } + + if( events & EVT_MASK_TOUCH ) { + handle_touch(); + } + } + +private: + touch::Manager touch_manager; + ui::Widget* const top_widget; + ui::Painter& painter; + ui::Context& context; + uint32_t encoder_last = 0; + + void handle_application_queue() { + while( !shared_memory.application_queue.is_empty() ) { + auto message = shared_memory.application_queue.pop(); + + auto& fn = context.message_map[message->id]; + if( fn ) { + fn(message); + } + + message->state = Message::State::Free; + } + } + + void handle_rtc_tick() { + /* + if( shared_memory.application_queue.push(&rssi_request) ) { + led_rx.on(); + } + */ + /* + if( callback_second_tick ) { + rtc::RTC datetime; + rtcGetTime(&RTCD1, &datetime); + + callback_second_tick(datetime); + } + */ + //static std::function callback_fifos_state; + //static std::function callback_cpu_ticks; + /* + if( callback_fifos_state ) { + callback_fifos_state(shared_memory.application_queue.len(), baseband_queue.len()); + } + */ + /* + if( callback_cpu_ticks ) { + //const auto thread_self = chThdSelf(); + const auto thread = chSysGetIdleThread(); + //const auto ticks = chThdGetTicks(thread); + + callback_cpu_ticks(thread->total_ticks); + } + */ + + /* + callback_fifos_state = [&system_view](size_t app_n, size_t baseband_n) { + system_view.status_view.text_app_fifo_n.set( + ui::to_string_dec_uint(app_n, 3) + ); + system_view.status_view.text_baseband_fifo_n.set( + ui::to_string_dec_uint(baseband_n, 3) + ); + }; + */ + /* + callback_cpu_ticks = [&system_view](systime_t ticks) { + static systime_t last_ticks = 0; + const auto delta_ticks = ticks - last_ticks; + last_ticks = ticks; + + const auto text_pct = ui::to_string_dec_uint(delta_ticks / 2000000, 3) + "% idle"; + system_view.status_view.text_ticks.set( + text_pct + ); + }; + */ + } +/* + void paint_widget(ui::Widget* const w) { + if( w->visible() ) { + if( w->dirty() ) { + w->paint(painter); + // Force-paint all children. + for(const auto child : w->children()) { + child->set_dirty(); + paint_widget(child); + } + w->set_clean(); + } else { + // Selectively paint all children. + for(const auto child : w->children()) { + paint_widget(child); + } + } + } + } +*/ + void paint_widget(ui::Widget* const w) { + if( w->hidden() ) { + // Mark widget (and all children) as invisible. + w->visible(false); + } else { + // Mark this widget as visible and recurse. + w->visible(true); + + if( w->dirty() ) { + w->paint(painter); + // Force-paint all children. + for(const auto child : w->children()) { + child->set_dirty(); + paint_widget(child); + } + w->set_clean(); + } else { + // Selectively paint all children. + for(const auto child : w->children()) { + paint_widget(child); + } + } + } + } + + static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event) { + if( !w->hidden() ) { + // To achieve reverse depth ordering (last object drawn is + // considered "top"), descend first. + for(const auto child : w->children()) { + const auto touched_widget = touch_widget(child, event); + if( touched_widget ) { + return touched_widget; + } + } + + const auto r = w->screen_rect(); + if( r.contains(event.point) ) { + if( w->on_touch(event) ) { + // This widget responded. Return it up the call stack. + return w; + } + } + } + return nullptr; + } + + ui::Widget* captured_widget { nullptr }; + + void on_touch_event(ui::TouchEvent event) { + /* TODO: Capture widget receiving the Start event, send Move and + * End events to the same widget. + */ + /* Capture Start widget. + * If touch is over Start widget at Move event, then the widget + * should be highlighted. If the touch is not over the Start + * widget at Move event, widget should un-highlight. + * If touch is over Start widget at End event, then the widget + * action should occur. + */ + if( event.type == ui::TouchEvent::Type::Start ) { + captured_widget = touch_widget(this->top_widget, event); + } + + if( captured_widget ) { + captured_widget->on_touch(event); + } + } + + void handle_lcd_frame_sync() { + if( ui_dirty ) { + paint_widget(top_widget); + ui_dirty = false; + } + } + + void handle_sd_card_detect() { + + } + + void handle_switches() { + const auto switches_state = get_switches_state(); + for(size_t i=0; i(i); + if( !event_bubble_key(event) ) { + context.focus_manager.update(top_widget, event); + } + } + } + } + + void handle_encoder() { + const uint32_t encoder_now = get_encoder_position(); + const int32_t delta = static_cast(encoder_now - encoder_last); + encoder_last = encoder_now; + const auto event = static_cast(delta); + event_bubble_encoder(event); + } + + void handle_touch() { + touch_manager.feed(get_touch_frame()); + } + + bool event_bubble_key(const ui::KeyEvent event) { + auto target = context.focus_manager.focus_widget(); + while( (target != nullptr) && !target->on_key(event) ) { + target = target->parent(); + } + + /* Return true if event was consumed. */ + return (target != nullptr); + } + + void event_bubble_encoder(const ui::EncoderEvent event) { + auto target = context.focus_manager.focus_widget(); + while( (target != nullptr) && !target->on_encoder(event) ) { + target = target->parent(); + } + } +}; + +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// + +/* Thinking things through a bit: + + main() produces UI events. + Touch events: + Hit test entire screen hierarchy and send to hit widget. + If modal view shown, block UI events destined outside. + Navigation events: + Move from current focus widget to "nearest" focusable widget. + If current view is modal, don't allow events to bubble outside + of modal view. + System events: + Power off from WWDT provides enough time to flush changes to + VBAT RAM? + SD card events? Insert/eject/error. + + + View stack: + Views that are hidden should deconstruct their widgets? + Views that are shown after being hidden will reconstruct their + widgets from data in their model? + Hence, hidden views will not eat up memory beyond their model? + Beware loops where the stack can get wildly deep? + Breaking out data models from views should allow some amount of + power-off persistence in the VBAT RAM area. In fact, the data + models could be instantiated there? But then, how to protect + from corruption if power is pulled? Does WWDT provide enough + warning to flush changes? + + Navigation... + If you move off the left side of the screen, move to breadcrumb + "back" item, no matter where you're coming from? +*/ + +/* +message_handlers[Message::ID::FSKPacket] = [](const Message* const p) { + const auto message = static_cast(p); + fsk_packet(message); +}; + +message_handlers[Message::ID::TestResults] = [&system_view](const Message* const p) { + const auto message = static_cast(p); + char c[10]; + c[0] = message->results.translate_by_fs_over_4_and_decimate_by_2_cic3 ? '+' : '-'; + c[1] = message->results.fir_cic3_decim_2_s16_s16 ? '+' : '-'; + c[2] = message->results.fir_64_and_decimate_by_2_complex ? '+' : '-'; + c[3] = message->results.fxpt_atan2 ? '+' : '-'; + c[4] = message->results.multiply_conjugate_s16_s32 ? '+' : '-'; + c[5] = 0; + system_view.status_view.portapack.set(c); +}; +*/ + +portapack::IO portapack::io { + portapack::gpio_dir, + portapack::gpio_lcd_rd, + portapack::gpio_lcd_wr, + portapack::gpio_io_stbx, + portapack::gpio_addr, + portapack::gpio_lcd_te, + portapack::gpio_unused, +}; + +int main(void) { + init(); + + if( !cpld_update_if_necessary() ) { + chSysHalt(); + } + + init_message_queues(); + + portapack::io.init(); + ui::Context context; + context.display.init(); + + sdcStart(&SDCD1, nullptr); + + rtc::interrupt::enable_second_inc(); + nvicEnableVector(RTC_IRQn, CORTEX_PRIORITY_MASK(LPC_RTC_IRQ_PRIORITY)); + + controls_init(); + + lcd_frame_sync_configure(); + + events_initialize(chThdSelf()); + + ui::SystemView system_view { + context, + { 0, 0, 240, 320 } + }; + ui::Painter painter { context.display }; + EventDispatcher event_dispatcher { &system_view, painter, context }; + +context.message_map[Message::ID::FSKPacket] = [](const Message* const p) { + const auto message = static_cast(p); + (void)message; + led_usb.toggle(); +}; + + m4txevent_interrupt_enable(); + m4_init(); + + while(true) { + const auto events = event_dispatcher.wait(); + event_dispatcher.dispatch(events); + } + + return 0; +} diff --git a/firmware/baseband/main.cpp b/firmware/baseband/main.cpp index 798db50b..d0b49f24 100755 --- a/firmware/baseband/main.cpp +++ b/firmware/baseband/main.cpp @@ -1,983 +1,983 @@ -/* - * 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_dma.hpp" - -#include "event_m4.hpp" - -#include "rssi.hpp" -#include "rssi_dma.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 "block_decimator.hpp" -#include "clock_recovery.hpp" -#include "access_code_correlator.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 - -constexpr auto baseband_thread_priority = NORMALPRIO + 20; -constexpr auto rssi_thread_priority = NORMALPRIO + 10; - -static float complex16_mag_squared_to_dbv_norm(const float c16_mag_squared) { - constexpr float mag2_max = -32768.0f * -32768.0f + -32768.0f * -32768.0f; - constexpr float mag2_log10_max = std::log10(mag2_max); - constexpr float mag2_to_db_factor = 20.0f / 2.0f; - return (std::log10(c16_mag_squared) - mag2_log10_max) * mag2_to_db_factor; -} - -class BasebandStatsCollector { -public: - 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 ) { - const auto idle_ticks = chSysGetIdleThread()->total_ticks; - statistics.idle_ticks = (idle_ticks - last_idle_ticks); - last_idle_ticks = idle_ticks; - - const auto baseband_ticks = chThdSelf()->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 }; - BasebandStatistics statistics; - size_t samples { 0 }; - size_t samples_last_report { 0 }; - uint32_t last_idle_ticks { 0 }; - uint32_t last_baseband_ticks { 0 }; -}; - -class RSSIStatisticsCollector { -public: - template - void process(rf::rssi::buffer_t buffer, Callback callback) { - auto p = buffer.p; - if( p == nullptr ) { - return; - } - - 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; - const auto value_0 = *p; - statistics.min = value_0; - statistics.max = value_0; - } - } - -private: - static constexpr float update_interval { 0.1f }; - RSSIStatistics statistics; -}; - -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 float max_db_f = complex16_mag_squared_to_dbv_norm(max_squared_f); - const int32_t max_db = max_db_f; - const ChannelStatistics statistics { - .max_db = max_db, - .count = count, - }; - callback(statistics); - - max_squared = 0; - count = 0; - } - } - -private: - static constexpr float update_interval { 0.1f }; - uint32_t max_squared { 0 }; - size_t count { 0 }; -}; - -class AudioStatsCollector { -public: - template - void feed(buffer_s16_t src, Callback callback) { - 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; - } - } - count += src.count; - - const size_t samples_per_update = src.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; - const int32_t rms_db = complex16_mag_squared_to_dbv_norm(squared_avg_f); - const int32_t max_db = complex16_mag_squared_to_dbv_norm(max_squared_f); - const AudioStatistics statistics { - .rms_db = rms_db, - .max_db = max_db, - .count = count, - }; - callback(statistics); - - squared_sum = 0; - max_squared = 0; - count = 0; - } - } - -private: - static constexpr float update_interval { 0.1f }; - uint64_t squared_sum { 0 }; - uint32_t max_squared { 0 }; - size_t count { 0 }; -}; - -class ChannelDecimator { -public: - enum class DecimationFactor { - By4, - By8, - By16, - By32, - }; - - ChannelDecimator( - DecimationFactor f - ) : decimation_factor { f } - { - } - - 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 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) - }; - - //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 { DecimationFactor::By32 }; - - buffer_c16_t execute_decimation(buffer_c8_t buffer) { - /* 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; - } -}; - -static volatile bool channel_spectrum_request_update { false }; -static std::array channel_spectrum; -static uint32_t channel_spectrum_bandwidth { 0 }; - -class BasebandProcessor { -public: - virtual ~BasebandProcessor() = default; - - virtual void execute(buffer_c8_t buffer) = 0; - -protected: - BlockDecimator<256> channel_spectrum_decimator { 4 }; - - ChannelStatsCollector channel_stats; - ChannelStatisticsMessage channel_stats_message; - - void feed_channel_stats(const buffer_c16_t channel) { - channel_stats.feed( - channel, - [this](const ChannelStatistics statistics) { - this->post_channel_stats_message(statistics); - } - ); - } - - void post_channel_stats_message(const ChannelStatistics statistics) { - if( channel_stats_message.is_free() ) { - channel_stats_message.statistics = statistics; - shared_memory.application_queue.push(&channel_stats_message); - } - } - - void feed_channel_spectrum(const buffer_c16_t channel) { - channel_spectrum_decimator.feed( - channel, - [this](const buffer_c16_t data) { - this->post_channel_spectrum_message(data); - } - ); - } - - void post_channel_spectrum_message(const buffer_c16_t data) { - if( !channel_spectrum_request_update ) { - channel_spectrum_request_update = true; - std::copy(&data.p[0], &data.p[data.count], channel_spectrum.begin()); - channel_spectrum_bandwidth = data.sampling_rate * 2; - events_flag(EVT_MASK_SPECTRUM); - } - } - - AudioStatsCollector audio_stats; - AudioStatisticsMessage audio_stats_message; - - void feed_audio_stats(const buffer_s16_t audio) { - audio_stats.feed( - audio, - [this](const AudioStatistics statistics) { - this->post_audio_stats_message(statistics); - } - ); - } - - void post_audio_stats_message(const AudioStatistics statistics) { - if( audio_stats_message.is_free() ) { - audio_stats_message.statistics = statistics; - shared_memory.application_queue.push(&audio_stats_message); - } - } - - void fill_audio_buffer(const buffer_s16_t audio) { - auto audio_buffer = audio::dma::tx_empty_buffer();; - for(size_t i=0; i[64] - * -> FIR filter, 48kHz int16_t[32] */ - auto channel = channel_filter.execute(decimator_out, work_baseband_buffer); - - // TODO: Feed channel_stats post-decimation data? - feed_channel_stats(channel); - feed_channel_spectrum(channel); - - const buffer_s16_t work_audio_buffer { - (int16_t*)decimator_out.p, - sizeof(*decimator_out.p) * decimator_out.count - }; - - /* 48kHz complex[32] - * -> AM demodulation - * -> 48kHz int16_t[32] */ - auto audio = demod.execute(channel, work_audio_buffer); - - audio_hpf.execute(audio); - feed_audio_stats(audio); - fill_audio_buffer(audio); - } - -private: - ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By32 }; - dsp::decimate::FIRAndDecimateBy2Complex<64> channel_filter { taps_64_lp_031_070_tfilter }; - dsp::demodulate::AM demod; - IIRBiquadFilter audio_hpf { - { 0.93346032f, -1.86687724f, 0.93346032f }, - { 1.0f , -1.97730264f, 0.97773668f } - }; -}; - -class NarrowbandFMAudio : public BasebandProcessor { -public: - void execute(buffer_c8_t buffer) override { - /* Called every 2048/3072000 second -- 1500Hz. */ - - auto decimator_out = decimator.execute(buffer); - - const buffer_c16_t work_baseband_buffer { - (complex16_t*)decimator_out.p, - sizeof(*decimator_out.p) * decimator_out.count - }; - - /* 96kHz complex[64] - * -> FIR filter, <6kHz (0.063fs) pass, gain 1.0 - * -> 48kHz int16_t[32] */ - auto channel = channel_filter.execute(decimator_out, work_baseband_buffer); - - // TODO: Feed channel_stats post-decimation data? - feed_channel_stats(channel); - feed_channel_spectrum(channel); - - const buffer_s16_t work_audio_buffer { - (int16_t*)decimator_out.p, - sizeof(*decimator_out.p) * decimator_out.count - }; - - /* 48kHz complex[32] - * -> FM demodulation - * -> 48kHz int16_t[32] */ - auto audio = demod.execute(channel, work_audio_buffer); - - audio_hpf.execute(audio); - feed_audio_stats(audio); - fill_audio_buffer(audio); - } - -private: - ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By32 }; - dsp::decimate::FIRAndDecimateBy2Complex<64> channel_filter { taps_64_lp_042_078_tfilter }; - dsp::demodulate::FM demod { 48000, 7500 }; - - IIRBiquadFilter audio_hpf { - { 0.93346032f, -1.86687724f, 0.93346032f }, - { 1.0f , -1.97730264f, 0.97773668f } - }; -}; - -class WidebandFMAudio : public BasebandProcessor { -public: - void execute(buffer_c8_t buffer) override { - auto decimator_out = decimator.execute(buffer); - - const buffer_s16_t work_audio_buffer { - (int16_t*)decimator_out.p, - sizeof(*decimator_out.p) * decimator_out.count - }; - - auto channel = decimator_out; - - // TODO: Feed channel_stats post-decimation data? - feed_channel_stats(channel); - //feed_channel_spectrum(channel); - - /* 768kHz complex[512] - * -> FM demodulation - * -> 768kHz int16_t[512] */ - /* TODO: To improve adjacent channel rejection, implement complex channel filter: - * pass < +/- 100kHz, stop > +/- 200kHz - */ - - auto audio_oversampled = demod.execute(decimator_out, work_audio_buffer); - - /* 768kHz int16_t[512] - * -> 4th order CIC decimation by 2, gain of 1 - * -> 384kHz int16_t[256] */ - auto audio_8fs = audio_dec_1.execute(audio_oversampled, work_audio_buffer); - - /* 384kHz int16_t[256] - * -> 4th order CIC decimation by 2, gain of 1 - * -> 192kHz int16_t[128] */ - auto audio_4fs = audio_dec_2.execute(audio_8fs, work_audio_buffer); - - /* 192kHz int16_t[128] - * -> 4th order CIC decimation by 2, gain of 1 - * -> 96kHz int16_t[64] */ - auto audio_2fs = audio_dec_3.execute(audio_4fs, work_audio_buffer); - - /* 96kHz int16_t[64] - * -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop, gain of 1 - * -> 48kHz int16_t[32] */ - auto audio = audio_filter.execute(audio_2fs, work_audio_buffer); - - /* -> 48kHz int16_t[32] */ - audio_hpf.execute(audio); - feed_audio_stats(audio); - fill_audio_buffer(audio); - } - -private: - ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By4 }; - - //dsp::decimate::FIRAndDecimateBy2Complex<64> channel_filter { taps_64_lp_031_070_tfilter }; - dsp::demodulate::FM demod { 768000, 75000 }; - dsp::decimate::DecimateBy2CIC4Real audio_dec_1; - dsp::decimate::DecimateBy2CIC4Real audio_dec_2; - dsp::decimate::DecimateBy2CIC4Real audio_dec_3; - dsp::decimate::FIR64AndDecimateBy2Real audio_filter { taps_64_lp_156_198 }; - - IIRBiquadFilter audio_hpf { - { 0.93346032f, -1.86687724f, 0.93346032f }, - { 1.0f , -1.97730264f, 0.97773668f } - }; -}; - -class FSKProcessor : public BasebandProcessor { -public: - FSKProcessor( - MessageHandlerMap& message_handlers - ) : message_handlers { message_handlers } - { - message_handlers[Message::ID::FSKConfiguration] = [this](const Message* const p) { - auto m = reinterpret_cast(p); - this->configure(m->configuration); - }; - } - - ~FSKProcessor() { - message_handlers[Message::ID::FSKConfiguration] = nullptr; - } - - void configure(const FSKConfiguration new_configuration) { - clock_recovery.configure(new_configuration.symbol_rate, 76800); - access_code_correlator.configure( - new_configuration.access_code, - new_configuration.access_code_length, - new_configuration.access_code_tolerance - ); - packet_builder.configure(new_configuration.packet_length); - } - - void execute(buffer_c8_t buffer) override { - /* 2.4576MHz, 2048 samples */ - - auto decimator_out = decimator.execute(buffer); - - /* 153.6kHz, 128 samples */ - - const buffer_c16_t work_baseband_buffer { - (complex16_t*)decimator_out.p, - decimator_out.count - }; - - /* 153.6kHz complex[128] - * -> FIR filter, 76.8kHz int16_t[64] */ - auto channel = channel_filter.execute(decimator_out, work_baseband_buffer); - - /* 76.8kHz, 64 samples */ - feed_channel_stats(channel); - feed_channel_spectrum(channel); - - const auto symbol_handler_fn = [this](const float value) { - const uint_fast8_t symbol = (value >= 0.0f) ? 1 : 0; - const bool access_code_found = this->access_code_correlator.execute(symbol); - this->consume_symbol(symbol, access_code_found); - }; - - // 76.8k - - const buffer_s16_t work_demod_buffer { - (int16_t*)decimator_out.p, - decimator_out.count * sizeof(*decimator_out.p) / sizeof(int16_t) - }; - - auto demodulated = demod.execute(channel, work_demod_buffer); - - mute_audio(); - - for(size_t i=0; i channel_filter { taps_64_lp_031_070_tfilter }; - dsp::demodulate::FM demod { 76800, 9600 * 2 }; - - ClockRecovery clock_recovery; - AccessCodeCorrelator access_code_correlator; - PacketBuilder packet_builder; - - FSKPacketMessage message; - MessageHandlerMap& message_handlers; - - void consume_symbol( - const uint_fast8_t symbol, - const bool access_code_found - ) { - const auto payload_handler_fn = [this]( - const std::bitset<256>& payload, - const size_t bits_received - ) { - this->payload_handler(payload, bits_received); - }; - - packet_builder.execute( - symbol, - access_code_found, - payload_handler_fn - ); - } - - void payload_handler( - const std::bitset<256>& payload, - const size_t bits_received - ) { - if( message.is_free() ) { - message.packet.payload = payload; - message.packet.bits_received = bits_received; - shared_memory.application_queue.push(&message); - } - } -}; - -static BasebandProcessor* baseband_processor { nullptr }; -static BasebandConfiguration baseband_configuration; - -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) { - // 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, - [&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); - } - } - ); - } -} - -extern "C" { - -void __late_init(void) { - /* 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 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(); - - 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 - ); -} - -static inline float magnitude_squared(const std::complex c) { - const auto r = c.real(); - const auto r2 = r * r; - const auto i = c.imag(); - const auto i2 = i * i; - return r2 + i2; -} - -class EventDispatcher { -public: - MessageHandlerMap& message_handlers() { - return message_map; - } - - 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(); - } - } - -private: - MessageHandlerMap message_map; - - ChannelSpectrumMessage spectrum_message; - std::array spectrum_db; - - void handle_baseband_queue() { - while( !shared_memory.baseband_queue.is_empty() ) { - auto message = shared_memory.baseband_queue.pop(); - - auto& fn = message_map[message->id]; - if( fn ) { - fn(message); - } - - message->state = Message::State::Free; - } - } - - void handle_spectrum() { - if( channel_spectrum_request_update ) { - /* Decimated buffer is full. Compute spectrum. */ - std::array, 256> samples_swapped; - fft_swap(channel_spectrum, samples_swapped); - channel_spectrum_request_update = false; - fft_c_preswapped(samples_swapped); - if( spectrum_message.is_free() ) { - for(size_t i=0; i .magnitude, or something more (less!) accurate. */ - spectrum_message.spectrum.db = &spectrum_db; - //spectrum_message.spectrum.db_count = 256; - spectrum_message.spectrum.bandwidth = channel_spectrum_bandwidth; - shared_memory.application_queue.push(&spectrum_message); - } - } - } -}; - -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(); -} - -} - -//#define TEST_DSP 1 - -#if defined(TEST_DSP) -#include "test_dsp.h" -#endif - -static constexpr auto direction = baseband::Direction::Receive; - -int main(void) { - -#if defined(TEST_DSP) - static TestResultsMessage test_results_message; - test_results_message.results = test_dsp(); - application_queue.push(&test_results_message); - while(1); -#else - - init(); - - events_initialize(chThdSelf()); - m0apptxevent_interrupt_enable(); - - EventDispatcher event_dispatcher; - auto& message_handlers = event_dispatcher.message_handlers(); - - message_handlers[Message::ID::BasebandConfiguration] = [&message_handlers](const Message* const p) { - auto message = reinterpret_cast(p); - if( message->configuration.mode != baseband_configuration.mode ) { - - // TODO: Timing problem around disabling DMA and nulling and deleting old processor - auto old_p = baseband_processor; - baseband_processor = nullptr; - delete old_p; - - switch(message->configuration.mode) { - case 0: - baseband_processor = new NarrowbandAMAudio(); - break; - - case 1: - baseband_processor = new NarrowbandFMAudio(); - break; - - case 2: - baseband_processor = new WidebandFMAudio(); - break; - - case 3: - baseband_processor = new FSKProcessor(message_handlers); - break; - - default: - break; - } - - if( baseband_processor ) { - if( direction == baseband::Direction::Receive ) { - rf::rssi::start(); - } - baseband::dma::enable(direction); - } else { - baseband::dma::disable(); - rf::rssi::stop(); - } - } - - baseband_configuration = message->configuration; - }; - - /* TODO: Ensure DMAs are configured to point at first LLI in chain. */ - - if( direction == baseband::Direction::Receive ) { - rf::rssi::dma::allocate(4, 400); - } - - touch::dma::allocate(); - touch::dma::enable(); - - const auto baseband_buffer = - new std::array(); - baseband::dma::configure( - baseband_buffer->data(), - direction - ); - //baseband::dma::allocate(4, 2048); - - while(true) { - const auto events = event_dispatcher.wait(); - event_dispatcher.dispatch(events); - } -#endif - return 0; -} +/* + * 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_dma.hpp" + +#include "event_m4.hpp" + +#include "rssi.hpp" +#include "rssi_dma.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 "block_decimator.hpp" +#include "clock_recovery.hpp" +#include "access_code_correlator.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 + +constexpr auto baseband_thread_priority = NORMALPRIO + 20; +constexpr auto rssi_thread_priority = NORMALPRIO + 10; + +static float complex16_mag_squared_to_dbv_norm(const float c16_mag_squared) { + constexpr float mag2_max = -32768.0f * -32768.0f + -32768.0f * -32768.0f; + constexpr float mag2_log10_max = std::log10(mag2_max); + constexpr float mag2_to_db_factor = 20.0f / 2.0f; + return (std::log10(c16_mag_squared) - mag2_log10_max) * mag2_to_db_factor; +} + +class BasebandStatsCollector { +public: + 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 ) { + const auto idle_ticks = chSysGetIdleThread()->total_ticks; + statistics.idle_ticks = (idle_ticks - last_idle_ticks); + last_idle_ticks = idle_ticks; + + const auto baseband_ticks = chThdSelf()->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 }; + BasebandStatistics statistics; + size_t samples { 0 }; + size_t samples_last_report { 0 }; + uint32_t last_idle_ticks { 0 }; + uint32_t last_baseband_ticks { 0 }; +}; + +class RSSIStatisticsCollector { +public: + template + void process(rf::rssi::buffer_t buffer, Callback callback) { + auto p = buffer.p; + if( p == nullptr ) { + return; + } + + 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; + const auto value_0 = *p; + statistics.min = value_0; + statistics.max = value_0; + } + } + +private: + static constexpr float update_interval { 0.1f }; + RSSIStatistics statistics; +}; + +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 float max_db_f = complex16_mag_squared_to_dbv_norm(max_squared_f); + const int32_t max_db = max_db_f; + const ChannelStatistics statistics { + .max_db = max_db, + .count = count, + }; + callback(statistics); + + max_squared = 0; + count = 0; + } + } + +private: + static constexpr float update_interval { 0.1f }; + uint32_t max_squared { 0 }; + size_t count { 0 }; +}; + +class AudioStatsCollector { +public: + template + void feed(buffer_s16_t src, Callback callback) { + 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; + } + } + count += src.count; + + const size_t samples_per_update = src.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; + const int32_t rms_db = complex16_mag_squared_to_dbv_norm(squared_avg_f); + const int32_t max_db = complex16_mag_squared_to_dbv_norm(max_squared_f); + const AudioStatistics statistics { + .rms_db = rms_db, + .max_db = max_db, + .count = count, + }; + callback(statistics); + + squared_sum = 0; + max_squared = 0; + count = 0; + } + } + +private: + static constexpr float update_interval { 0.1f }; + uint64_t squared_sum { 0 }; + uint32_t max_squared { 0 }; + size_t count { 0 }; +}; + +class ChannelDecimator { +public: + enum class DecimationFactor { + By4, + By8, + By16, + By32, + }; + + ChannelDecimator( + DecimationFactor f + ) : decimation_factor { f } + { + } + + 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 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) + }; + + //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 { DecimationFactor::By32 }; + + buffer_c16_t execute_decimation(buffer_c8_t buffer) { + /* 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; + } +}; + +static volatile bool channel_spectrum_request_update { false }; +static std::array channel_spectrum; +static uint32_t channel_spectrum_bandwidth { 0 }; + +class BasebandProcessor { +public: + virtual ~BasebandProcessor() = default; + + virtual void execute(buffer_c8_t buffer) = 0; + +protected: + BlockDecimator<256> channel_spectrum_decimator { 4 }; + + ChannelStatsCollector channel_stats; + ChannelStatisticsMessage channel_stats_message; + + void feed_channel_stats(const buffer_c16_t channel) { + channel_stats.feed( + channel, + [this](const ChannelStatistics statistics) { + this->post_channel_stats_message(statistics); + } + ); + } + + void post_channel_stats_message(const ChannelStatistics statistics) { + if( channel_stats_message.is_free() ) { + channel_stats_message.statistics = statistics; + shared_memory.application_queue.push(&channel_stats_message); + } + } + + void feed_channel_spectrum(const buffer_c16_t channel) { + channel_spectrum_decimator.feed( + channel, + [this](const buffer_c16_t data) { + this->post_channel_spectrum_message(data); + } + ); + } + + void post_channel_spectrum_message(const buffer_c16_t data) { + if( !channel_spectrum_request_update ) { + channel_spectrum_request_update = true; + std::copy(&data.p[0], &data.p[data.count], channel_spectrum.begin()); + channel_spectrum_bandwidth = data.sampling_rate * 2; + events_flag(EVT_MASK_SPECTRUM); + } + } + + AudioStatsCollector audio_stats; + AudioStatisticsMessage audio_stats_message; + + void feed_audio_stats(const buffer_s16_t audio) { + audio_stats.feed( + audio, + [this](const AudioStatistics statistics) { + this->post_audio_stats_message(statistics); + } + ); + } + + void post_audio_stats_message(const AudioStatistics statistics) { + if( audio_stats_message.is_free() ) { + audio_stats_message.statistics = statistics; + shared_memory.application_queue.push(&audio_stats_message); + } + } + + void fill_audio_buffer(const buffer_s16_t audio) { + auto audio_buffer = audio::dma::tx_empty_buffer();; + for(size_t i=0; i[64] + * -> FIR filter, 48kHz int16_t[32] */ + auto channel = channel_filter.execute(decimator_out, work_baseband_buffer); + + // TODO: Feed channel_stats post-decimation data? + feed_channel_stats(channel); + feed_channel_spectrum(channel); + + const buffer_s16_t work_audio_buffer { + (int16_t*)decimator_out.p, + sizeof(*decimator_out.p) * decimator_out.count + }; + + /* 48kHz complex[32] + * -> AM demodulation + * -> 48kHz int16_t[32] */ + auto audio = demod.execute(channel, work_audio_buffer); + + audio_hpf.execute(audio); + feed_audio_stats(audio); + fill_audio_buffer(audio); + } + +private: + ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By32 }; + dsp::decimate::FIRAndDecimateBy2Complex<64> channel_filter { taps_64_lp_031_070_tfilter }; + dsp::demodulate::AM demod; + IIRBiquadFilter audio_hpf { + { 0.93346032f, -1.86687724f, 0.93346032f }, + { 1.0f , -1.97730264f, 0.97773668f } + }; +}; + +class NarrowbandFMAudio : public BasebandProcessor { +public: + void execute(buffer_c8_t buffer) override { + /* Called every 2048/3072000 second -- 1500Hz. */ + + auto decimator_out = decimator.execute(buffer); + + const buffer_c16_t work_baseband_buffer { + (complex16_t*)decimator_out.p, + sizeof(*decimator_out.p) * decimator_out.count + }; + + /* 96kHz complex[64] + * -> FIR filter, <6kHz (0.063fs) pass, gain 1.0 + * -> 48kHz int16_t[32] */ + auto channel = channel_filter.execute(decimator_out, work_baseband_buffer); + + // TODO: Feed channel_stats post-decimation data? + feed_channel_stats(channel); + feed_channel_spectrum(channel); + + const buffer_s16_t work_audio_buffer { + (int16_t*)decimator_out.p, + sizeof(*decimator_out.p) * decimator_out.count + }; + + /* 48kHz complex[32] + * -> FM demodulation + * -> 48kHz int16_t[32] */ + auto audio = demod.execute(channel, work_audio_buffer); + + audio_hpf.execute(audio); + feed_audio_stats(audio); + fill_audio_buffer(audio); + } + +private: + ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By32 }; + dsp::decimate::FIRAndDecimateBy2Complex<64> channel_filter { taps_64_lp_042_078_tfilter }; + dsp::demodulate::FM demod { 48000, 7500 }; + + IIRBiquadFilter audio_hpf { + { 0.93346032f, -1.86687724f, 0.93346032f }, + { 1.0f , -1.97730264f, 0.97773668f } + }; +}; + +class WidebandFMAudio : public BasebandProcessor { +public: + void execute(buffer_c8_t buffer) override { + auto decimator_out = decimator.execute(buffer); + + const buffer_s16_t work_audio_buffer { + (int16_t*)decimator_out.p, + sizeof(*decimator_out.p) * decimator_out.count + }; + + auto channel = decimator_out; + + // TODO: Feed channel_stats post-decimation data? + feed_channel_stats(channel); + //feed_channel_spectrum(channel); + + /* 768kHz complex[512] + * -> FM demodulation + * -> 768kHz int16_t[512] */ + /* TODO: To improve adjacent channel rejection, implement complex channel filter: + * pass < +/- 100kHz, stop > +/- 200kHz + */ + + auto audio_oversampled = demod.execute(decimator_out, work_audio_buffer); + + /* 768kHz int16_t[512] + * -> 4th order CIC decimation by 2, gain of 1 + * -> 384kHz int16_t[256] */ + auto audio_8fs = audio_dec_1.execute(audio_oversampled, work_audio_buffer); + + /* 384kHz int16_t[256] + * -> 4th order CIC decimation by 2, gain of 1 + * -> 192kHz int16_t[128] */ + auto audio_4fs = audio_dec_2.execute(audio_8fs, work_audio_buffer); + + /* 192kHz int16_t[128] + * -> 4th order CIC decimation by 2, gain of 1 + * -> 96kHz int16_t[64] */ + auto audio_2fs = audio_dec_3.execute(audio_4fs, work_audio_buffer); + + /* 96kHz int16_t[64] + * -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop, gain of 1 + * -> 48kHz int16_t[32] */ + auto audio = audio_filter.execute(audio_2fs, work_audio_buffer); + + /* -> 48kHz int16_t[32] */ + audio_hpf.execute(audio); + feed_audio_stats(audio); + fill_audio_buffer(audio); + } + +private: + ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By4 }; + + //dsp::decimate::FIRAndDecimateBy2Complex<64> channel_filter { taps_64_lp_031_070_tfilter }; + dsp::demodulate::FM demod { 768000, 75000 }; + dsp::decimate::DecimateBy2CIC4Real audio_dec_1; + dsp::decimate::DecimateBy2CIC4Real audio_dec_2; + dsp::decimate::DecimateBy2CIC4Real audio_dec_3; + dsp::decimate::FIR64AndDecimateBy2Real audio_filter { taps_64_lp_156_198 }; + + IIRBiquadFilter audio_hpf { + { 0.93346032f, -1.86687724f, 0.93346032f }, + { 1.0f , -1.97730264f, 0.97773668f } + }; +}; + +class FSKProcessor : public BasebandProcessor { +public: + FSKProcessor( + MessageHandlerMap& message_handlers + ) : message_handlers { message_handlers } + { + message_handlers[Message::ID::FSKConfiguration] = [this](const Message* const p) { + auto m = reinterpret_cast(p); + this->configure(m->configuration); + }; + } + + ~FSKProcessor() { + message_handlers[Message::ID::FSKConfiguration] = nullptr; + } + + void configure(const FSKConfiguration new_configuration) { + clock_recovery.configure(new_configuration.symbol_rate, 76800); + access_code_correlator.configure( + new_configuration.access_code, + new_configuration.access_code_length, + new_configuration.access_code_tolerance + ); + packet_builder.configure(new_configuration.packet_length); + } + + void execute(buffer_c8_t buffer) override { + /* 2.4576MHz, 2048 samples */ + + auto decimator_out = decimator.execute(buffer); + + /* 153.6kHz, 128 samples */ + + const buffer_c16_t work_baseband_buffer { + (complex16_t*)decimator_out.p, + decimator_out.count + }; + + /* 153.6kHz complex[128] + * -> FIR filter, 76.8kHz int16_t[64] */ + auto channel = channel_filter.execute(decimator_out, work_baseband_buffer); + + /* 76.8kHz, 64 samples */ + feed_channel_stats(channel); + feed_channel_spectrum(channel); + + const auto symbol_handler_fn = [this](const float value) { + const uint_fast8_t symbol = (value >= 0.0f) ? 1 : 0; + const bool access_code_found = this->access_code_correlator.execute(symbol); + this->consume_symbol(symbol, access_code_found); + }; + + // 76.8k + + const buffer_s16_t work_demod_buffer { + (int16_t*)decimator_out.p, + decimator_out.count * sizeof(*decimator_out.p) / sizeof(int16_t) + }; + + auto demodulated = demod.execute(channel, work_demod_buffer); + + mute_audio(); + + for(size_t i=0; i channel_filter { taps_64_lp_031_070_tfilter }; + dsp::demodulate::FM demod { 76800, 9600 * 2 }; + + ClockRecovery clock_recovery; + AccessCodeCorrelator access_code_correlator; + PacketBuilder packet_builder; + + FSKPacketMessage message; + MessageHandlerMap& message_handlers; + + void consume_symbol( + const uint_fast8_t symbol, + const bool access_code_found + ) { + const auto payload_handler_fn = [this]( + const std::bitset<256>& payload, + const size_t bits_received + ) { + this->payload_handler(payload, bits_received); + }; + + packet_builder.execute( + symbol, + access_code_found, + payload_handler_fn + ); + } + + void payload_handler( + const std::bitset<256>& payload, + const size_t bits_received + ) { + if( message.is_free() ) { + message.packet.payload = payload; + message.packet.bits_received = bits_received; + shared_memory.application_queue.push(&message); + } + } +}; + +static BasebandProcessor* baseband_processor { nullptr }; +static BasebandConfiguration baseband_configuration; + +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) { + // 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, + [&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); + } + } + ); + } +} + +extern "C" { + +void __late_init(void) { + /* 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 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(); + + 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 + ); +} + +static inline float magnitude_squared(const std::complex c) { + const auto r = c.real(); + const auto r2 = r * r; + const auto i = c.imag(); + const auto i2 = i * i; + return r2 + i2; +} + +class EventDispatcher { +public: + MessageHandlerMap& message_handlers() { + return message_map; + } + + 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(); + } + } + +private: + MessageHandlerMap message_map; + + ChannelSpectrumMessage spectrum_message; + std::array spectrum_db; + + void handle_baseband_queue() { + while( !shared_memory.baseband_queue.is_empty() ) { + auto message = shared_memory.baseband_queue.pop(); + + auto& fn = message_map[message->id]; + if( fn ) { + fn(message); + } + + message->state = Message::State::Free; + } + } + + void handle_spectrum() { + if( channel_spectrum_request_update ) { + /* Decimated buffer is full. Compute spectrum. */ + std::array, 256> samples_swapped; + fft_swap(channel_spectrum, samples_swapped); + channel_spectrum_request_update = false; + fft_c_preswapped(samples_swapped); + if( spectrum_message.is_free() ) { + for(size_t i=0; i .magnitude, or something more (less!) accurate. */ + spectrum_message.spectrum.db = &spectrum_db; + //spectrum_message.spectrum.db_count = 256; + spectrum_message.spectrum.bandwidth = channel_spectrum_bandwidth; + shared_memory.application_queue.push(&spectrum_message); + } + } + } +}; + +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(); +} + +} + +//#define TEST_DSP 1 + +#if defined(TEST_DSP) +#include "test_dsp.h" +#endif + +static constexpr auto direction = baseband::Direction::Receive; + +int main(void) { + +#if defined(TEST_DSP) + static TestResultsMessage test_results_message; + test_results_message.results = test_dsp(); + application_queue.push(&test_results_message); + while(1); +#else + + init(); + + events_initialize(chThdSelf()); + m0apptxevent_interrupt_enable(); + + EventDispatcher event_dispatcher; + auto& message_handlers = event_dispatcher.message_handlers(); + + message_handlers[Message::ID::BasebandConfiguration] = [&message_handlers](const Message* const p) { + auto message = reinterpret_cast(p); + if( message->configuration.mode != baseband_configuration.mode ) { + + // TODO: Timing problem around disabling DMA and nulling and deleting old processor + auto old_p = baseband_processor; + baseband_processor = nullptr; + delete old_p; + + switch(message->configuration.mode) { + case 0: + baseband_processor = new NarrowbandAMAudio(); + break; + + case 1: + baseband_processor = new NarrowbandFMAudio(); + break; + + case 2: + baseband_processor = new WidebandFMAudio(); + break; + + case 3: + baseband_processor = new FSKProcessor(message_handlers); + break; + + default: + break; + } + + if( baseband_processor ) { + if( direction == baseband::Direction::Receive ) { + rf::rssi::start(); + } + baseband::dma::enable(direction); + } else { + baseband::dma::disable(); + rf::rssi::stop(); + } + } + + baseband_configuration = message->configuration; + }; + + /* TODO: Ensure DMAs are configured to point at first LLI in chain. */ + + if( direction == baseband::Direction::Receive ) { + rf::rssi::dma::allocate(4, 400); + } + + touch::dma::allocate(); + touch::dma::enable(); + + const auto baseband_buffer = + new std::array(); + baseband::dma::configure( + baseband_buffer->data(), + direction + ); + //baseband::dma::allocate(4, 2048); + + while(true) { + const auto events = event_dispatcher.wait(); + event_dispatcher.dispatch(events); + } +#endif + return 0; +}