mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-08-14 06:47:42 +00:00
Initial firmware commit.
This commit is contained in:
267
firmware/baseband/Makefile
Executable file
267
firmware/baseband/Makefile
Executable file
@@ -0,0 +1,267 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
# Build global options
|
||||
# NOTE: Can be overridden externally.
|
||||
#
|
||||
|
||||
# Compiler options here.
|
||||
ifeq ($(USE_OPT),)
|
||||
USE_OPT = -mthumb \
|
||||
-O3 -ggdb3 \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-fno-builtin \
|
||||
-falign-functions=16 \
|
||||
-fno-math-errno \
|
||||
--specs=nano.specs
|
||||
#-fomit-frame-pointer
|
||||
endif
|
||||
|
||||
# C specific options here (added to USE_OPT).
|
||||
ifeq ($(USE_COPT),)
|
||||
USE_COPT = -std=gnu99
|
||||
endif
|
||||
|
||||
# C++ specific options here (added to USE_OPT).
|
||||
ifeq ($(USE_CPPOPT),)
|
||||
USE_CPPOPT = -std=c++11 -fno-rtti -fno-exceptions
|
||||
endif
|
||||
|
||||
# Enable this if you want the linker to remove unused code and data
|
||||
ifeq ($(USE_LINK_GC),)
|
||||
USE_LINK_GC = yes
|
||||
endif
|
||||
|
||||
# Linker extra options here.
|
||||
ifeq ($(USE_LDOPT),)
|
||||
USE_LDOPT =
|
||||
endif
|
||||
|
||||
# Enable this if you want link time optimizations (LTO)
|
||||
ifeq ($(USE_LTO),)
|
||||
USE_LTO = no
|
||||
endif
|
||||
|
||||
# If enabled, this option allows to compile the application in THUMB mode.
|
||||
ifeq ($(USE_THUMB),)
|
||||
USE_THUMB = yes
|
||||
endif
|
||||
|
||||
# Enable this if you want to see the full log while compiling.
|
||||
ifeq ($(USE_VERBOSE_COMPILE),)
|
||||
USE_VERBOSE_COMPILE = no
|
||||
endif
|
||||
|
||||
#
|
||||
# Build global options
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Architecture or project specific options
|
||||
#
|
||||
|
||||
# Enables the use of FPU on Cortex-M4 (no, softfp, hard).
|
||||
ifeq ($(USE_FPU),)
|
||||
USE_FPU = hard
|
||||
endif
|
||||
|
||||
#
|
||||
# Architecture or project specific options
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Project, sources and paths
|
||||
#
|
||||
|
||||
# Define project name here
|
||||
PROJECT = baseband
|
||||
|
||||
# Imported source files and paths
|
||||
CHIBIOS = ../chibios
|
||||
CHIBIOS_PORTAPACK = ../chibios-portapack
|
||||
include $(CHIBIOS_PORTAPACK)/boards/GSG_HACKRF_ONE/board.mk
|
||||
include $(CHIBIOS_PORTAPACK)/os/hal/platforms/LPC43xx_M4/platform.mk
|
||||
include $(CHIBIOS)/os/hal/hal.mk
|
||||
include $(CHIBIOS_PORTAPACK)/os/ports/GCC/ARMCMx/LPC43xx_M4/port.mk
|
||||
include $(CHIBIOS)/os/kernel/kernel.mk
|
||||
|
||||
include $(CHIBIOS)/test/test.mk
|
||||
|
||||
# Define linker script file here
|
||||
LDSCRIPT= $(PORTLD)/LPC43xx_M4.ld
|
||||
|
||||
# C sources that can be compiled in ARM or THUMB mode depending on the global
|
||||
# setting.
|
||||
CSRC = $(PORTSRC) \
|
||||
$(KERNSRC) \
|
||||
$(TESTSRC) \
|
||||
$(HALSRC) \
|
||||
$(PLATFORMSRC) \
|
||||
$(BOARDSRC)
|
||||
|
||||
|
||||
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
|
||||
# setting.
|
||||
CPPSRC = main.cpp \
|
||||
message_queue.cpp \
|
||||
event_m4.cpp \
|
||||
gpdma.cpp \
|
||||
baseband_dma.cpp \
|
||||
portapack_shared_memory.cpp \
|
||||
dsp_decimate.cpp \
|
||||
dsp_demodulate.cpp \
|
||||
clock_recovery.cpp \
|
||||
access_code_correlator.cpp \
|
||||
packet_builder.cpp \
|
||||
dsp_fft.cpp \
|
||||
dsp_fir_taps.cpp \
|
||||
fxpt_atan2.cpp \
|
||||
rssi.cpp \
|
||||
rssi_dma.cpp \
|
||||
audio.cpp \
|
||||
audio_dma.cpp \
|
||||
touch_dma.cpp \
|
||||
../common/utility.cpp \
|
||||
../common/debug.cpp \
|
||||
../common/gcc.cpp
|
||||
|
||||
# C sources to be compiled in ARM mode regardless of the global setting.
|
||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||
# option that results in lower performance and larger code size.
|
||||
ACSRC =
|
||||
|
||||
# C++ sources to be compiled in ARM mode regardless of the global setting.
|
||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||
# option that results in lower performance and larger code size.
|
||||
ACPPSRC =
|
||||
|
||||
# C sources to be compiled in THUMB mode regardless of the global setting.
|
||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||
# option that results in lower performance and larger code size.
|
||||
TCSRC =
|
||||
|
||||
# C sources to be compiled in THUMB mode regardless of the global setting.
|
||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||
# option that results in lower performance and larger code size.
|
||||
TCPPSRC =
|
||||
|
||||
# List ASM source files here
|
||||
ASMSRC = $(PORTASM)
|
||||
|
||||
INCDIR = ../common $(PORTINC) $(KERNINC) $(TESTINC) \
|
||||
$(HALINC) $(PLATFORMINC) $(BOARDINC) \
|
||||
$(CHIBIOS)/os/various
|
||||
|
||||
#
|
||||
# Project, sources and paths
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Compiler settings
|
||||
#
|
||||
|
||||
MCU = cortex-m4
|
||||
|
||||
#TRGT = arm-elf-
|
||||
TRGT = arm-none-eabi-
|
||||
CC = $(TRGT)gcc
|
||||
CPPC = $(TRGT)g++
|
||||
# Enable loading with g++ only if you need C++ runtime support.
|
||||
# NOTE: You can use C++ even without C++ support if you are careful. C++
|
||||
# runtime support makes code size explode.
|
||||
#LD = $(TRGT)gcc
|
||||
LD = $(TRGT)g++
|
||||
CP = $(TRGT)objcopy
|
||||
AS = $(TRGT)gcc -x assembler-with-cpp
|
||||
OD = $(TRGT)objdump
|
||||
SZ = $(TRGT)size
|
||||
HEX = $(CP) -O ihex
|
||||
BIN = $(CP) -O binary
|
||||
|
||||
# ARM-specific options here
|
||||
AOPT =
|
||||
|
||||
# THUMB-specific options here
|
||||
TOPT = -mthumb -DTHUMB
|
||||
|
||||
# Define C warning options here
|
||||
CWARN = -Wall -Wextra -Wstrict-prototypes
|
||||
|
||||
# Define C++ warning options here
|
||||
CPPWARN = -Wall -Wextra
|
||||
|
||||
#
|
||||
# Compiler settings
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Start of default section
|
||||
#
|
||||
|
||||
# List all default C defines here, like -D_DEBUG=1
|
||||
# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI?
|
||||
# NOTE: _RANDOM_TCC to kill a GCC 4.9.3 error with std::max argument types
|
||||
DDEFS = -DLPC43XX -DLPC43XX_M4 -D__NEWLIB__ -DHACKRF_ONE \
|
||||
-DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0
|
||||
|
||||
# List all default ASM defines here, like -D_DEBUG=1
|
||||
DADEFS =
|
||||
|
||||
# List all default directories to look for include files here
|
||||
DINCDIR =
|
||||
|
||||
# List the default directory to look for the libraries here
|
||||
DLIBDIR =
|
||||
|
||||
# List all default libraries here
|
||||
DLIBS =
|
||||
|
||||
#
|
||||
# End of default section
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Start of user section
|
||||
#
|
||||
|
||||
# List all user C define here, like -D_DEBUG=1
|
||||
UDEFS =
|
||||
|
||||
# Define ASM defines here
|
||||
UADEFS =
|
||||
|
||||
# List all user directories here
|
||||
UINCDIR =
|
||||
|
||||
# List the user directory to look for the libraries here
|
||||
ULIBDIR =
|
||||
|
||||
# List all user libraries here
|
||||
ULIBS =
|
||||
|
||||
#
|
||||
# End of user defines
|
||||
##############################################################################
|
||||
|
||||
RULESPATH = $(CHIBIOS)/os/ports/GCC/ARMCMx
|
||||
include $(RULESPATH)/rules.mk
|
44
firmware/baseband/access_code_correlator.cpp
Normal file
44
firmware/baseband/access_code_correlator.cpp
Normal file
@@ -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.
|
||||
*/
|
||||
|
||||
#include "access_code_correlator.hpp"
|
||||
|
||||
void AccessCodeCorrelator::configure(
|
||||
const uint32_t new_code,
|
||||
const size_t new_code_length,
|
||||
const size_t new_maximum_hamming_distance
|
||||
) {
|
||||
if( new_code_length <= 32 ) {
|
||||
code = new_code;
|
||||
mask = mask_value(new_code_length);
|
||||
maximum_hamming_distance = new_maximum_hamming_distance;
|
||||
}
|
||||
}
|
||||
|
||||
bool AccessCodeCorrelator::execute(
|
||||
const uint_fast8_t in
|
||||
) {
|
||||
history = (history << 1) | (in & 1);
|
||||
const auto delta_bits = (history ^ code) & mask;
|
||||
//const size_t count = __builtin_popcountll(delta_bits);
|
||||
const size_t count = __builtin_popcountl(delta_bits);
|
||||
return (count <= maximum_hamming_distance);
|
||||
}
|
49
firmware/baseband/access_code_correlator.hpp
Normal file
49
firmware/baseband/access_code_correlator.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 __ACCESS_CODE_CORRELATOR_H__
|
||||
#define __ACCESS_CODE_CORRELATOR_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
class AccessCodeCorrelator {
|
||||
public:
|
||||
void configure(
|
||||
const uint32_t new_code,
|
||||
const size_t new_code_length,
|
||||
const size_t new_maximum_hamming_distance
|
||||
);
|
||||
|
||||
bool execute(const uint_fast8_t in);
|
||||
|
||||
private:
|
||||
uint32_t code { 0 };
|
||||
uint32_t mask { 0 };
|
||||
uint32_t history { 0 };
|
||||
size_t maximum_hamming_distance { 0 };
|
||||
|
||||
static constexpr uint32_t mask_value(const size_t n) {
|
||||
return static_cast<uint32_t>((1ULL << n) - 1ULL);
|
||||
}
|
||||
};
|
||||
|
||||
#endif/*__ACCESS_CODE_CORRELATOR_H__*/
|
238
firmware/baseband/audio_dma.cpp
Normal file
238
firmware/baseband/audio_dma.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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 "audio_dma.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
#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<uint32_t>(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<sample_t, buffer_samples> buffer_tx;
|
||||
static std::array<sample_t, buffer_samples> buffer_rx;
|
||||
|
||||
static std::array<gpdma::channel::LLI, transfers_per_buffer> lli_tx_loop;
|
||||
static std::array<gpdma::channel::LLI, transfers_per_buffer> 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<uint32_t>(&LPC_I2S0->TXFIFO);
|
||||
const auto control_value = control_tx(transfer_bytes);
|
||||
for(size_t i=0; i<lli_tx_loop.size(); i++) {
|
||||
const auto memory = reinterpret_cast<uint32_t>(&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<uint32_t>(&LPC_I2S0->RXFIFO);
|
||||
const auto control_value = control_rx(transfer_bytes);
|
||||
for(size_t i=0; i<lli_rx_loop.size(); i++) {
|
||||
const auto memory = reinterpret_cast<uint32_t>(&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<sample_t*>(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<sample_t*>(lli_rx_loop[free_index].srcaddr), transfer_samples };
|
||||
} else {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace dma */
|
||||
} /* namespace audio */
|
43
firmware/baseband/audio_dma.hpp
Normal file
43
firmware/baseband/audio_dma.hpp
Normal file
@@ -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 <cstdint>
|
||||
|
||||
#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__*/
|
180
firmware/baseband/baseband_dma.cpp
Normal file
180
firmware/baseband/baseband_dma.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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 <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
#include "hal.h"
|
||||
#include "gpdma.hpp"
|
||||
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include "portapack_dma.hpp"
|
||||
|
||||
namespace baseband {
|
||||
namespace dma {
|
||||
|
||||
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<uint32_t>(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<gpdma::channel::LLI, transfers_per_buffer> lli_loop;
|
||||
static constexpr auto& gpdma_channel_sgpio = gpdma::channels[portapack::sgpio_gpdma_channel_number];
|
||||
|
||||
//static Mailbox mailbox;
|
||||
//static std::array<msg_t, msg_count> messages;
|
||||
static Semaphore semaphore;
|
||||
|
||||
static volatile const gpdma::channel::LLI* next_lli = nullptr;
|
||||
|
||||
static void transfer_complete() {
|
||||
next_lli = gpdma_channel_sgpio.next_lli();
|
||||
/* 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<uint32_t>(&LPC_SGPIO->REG_SS[0]);
|
||||
const auto control_value = control(direction, gpdma::buffer_words(transfer_bytes, 4));
|
||||
for(size_t i=0; i<lli_loop.size(); i++) {
|
||||
const auto memory = reinterpret_cast<uint32_t>(&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( 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<sample_t*>(lli_loop[free_index].destaddr), transfer_samples };
|
||||
} else {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
} else {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace dma */
|
||||
} /* namespace baseband */
|
52
firmware/baseband/baseband_dma.hpp
Normal file
52
firmware/baseband/baseband_dma.hpp
Normal file
@@ -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 __BASEBAND_DMA_H__
|
||||
#define __BASEBAND_DMA_H__
|
||||
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
#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();
|
||||
|
||||
} /* namespace dma */
|
||||
} /* namespace baseband */
|
||||
|
||||
#endif/*__BASEBAND_DMA_H__*/
|
90
firmware/baseband/block_decimator.hpp
Normal file
90
firmware/baseband/block_decimator.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
#include "complex.hpp"
|
||||
|
||||
template<size_t N>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void set_factor(const size_t new_factor) {
|
||||
if( new_factor != factor ) {
|
||||
factor = new_factor;
|
||||
reset_state();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t output_sampling_rate() const {
|
||||
return input_sampling_rate / factor;
|
||||
}
|
||||
|
||||
template<typename BlockCallback>
|
||||
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<complex16_t, N> 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__*/
|
546
firmware/baseband/chconf.h
Executable file
546
firmware/baseband/chconf.h
Executable file
@@ -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_ */
|
||||
|
||||
/** @} */
|
31
firmware/baseband/clock_recovery.cpp
Normal file
31
firmware/baseband/clock_recovery.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
void ClockRecovery::configure(
|
||||
const uint32_t symbol_rate,
|
||||
const uint32_t sampling_rate
|
||||
) {
|
||||
phase_increment = phase_increment_u32(
|
||||
fractional_symbol_rate(symbol_rate, sampling_rate)
|
||||
);
|
||||
}
|
86
firmware/baseband/clock_recovery.hpp
Normal file
86
firmware/baseband/clock_recovery.hpp
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 <cstdint>
|
||||
|
||||
class ClockRecovery {
|
||||
public:
|
||||
void configure(
|
||||
const uint32_t symbol_rate,
|
||||
const uint32_t sampling_rate
|
||||
);
|
||||
|
||||
template<typename SymbolHandler>
|
||||
void execute(
|
||||
const float in,
|
||||
SymbolHandler symbol_handler
|
||||
) {
|
||||
const bool phase_0 = (phase_last >> 31) & (!(phase >> 31));
|
||||
const bool phase_180 = (!(phase_last >> 31)) & (phase >> 31);
|
||||
phase_last = phase;
|
||||
phase += phase_increment + phase_adjustment;
|
||||
|
||||
if( phase_0 || phase_180 ) {
|
||||
t2 = t1;
|
||||
t1 = t0;
|
||||
t0 = in;
|
||||
}
|
||||
|
||||
if( phase_0 ) {
|
||||
symbol_handler(t0);
|
||||
|
||||
const float error = (t0 - t2) * t1;
|
||||
// + error == late == decrease/slow phase
|
||||
// - error == early == increase/fast phase
|
||||
|
||||
error_filtered = 0.75f * error_filtered + 0.25f * error;
|
||||
|
||||
// Correct phase (don't change frequency!)
|
||||
phase_adjustment = -phase_increment * error_filtered / 200.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t phase { 0 };
|
||||
uint32_t phase_last { 0 };
|
||||
uint32_t phase_adjustment { 0 };
|
||||
uint32_t phase_increment { 0 };
|
||||
float t0 { 0 };
|
||||
float t1 { 0 };
|
||||
float t2 { 0 };
|
||||
float error_filtered { 0 };
|
||||
|
||||
static constexpr float fractional_symbol_rate(
|
||||
const uint32_t symbol_rate,
|
||||
const uint32_t sampling_rate
|
||||
) {
|
||||
return float(symbol_rate) / float(sampling_rate);
|
||||
}
|
||||
|
||||
static constexpr uint32_t phase_increment_u32(const float fractional_symbol_rate) {
|
||||
return 4294967296.0f * fractional_symbol_rate;
|
||||
}
|
||||
};
|
||||
|
||||
#endif/*__CLOCK_RECOVERY_H__*/
|
460
firmware/baseband/dsp_decimate.cpp
Normal file
460
firmware/baseband/dsp_decimate.cpp
Normal file
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
* 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 <hal.h>
|
||||
|
||||
namespace dsp {
|
||||
namespace decimate {
|
||||
|
||||
buffer_c16_t TranslateByFSOver4AndDecimateBy2CIC3::execute(buffer_c8_t src, buffer_c16_t dst) {
|
||||
/* Translates incoming complex<int8_t> 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<uint32_t*>(&src.p[0]);
|
||||
uint32_t* const src_end = reinterpret_cast<uint32_t*>(&src.p[src.count]);
|
||||
uint32_t* dst_p = reinterpret_cast<uint32_t*>(&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<taps_count; j+=4) {
|
||||
t += z[j+0] * taps[j+0];
|
||||
t += z[j+1] * taps[j+1];
|
||||
t += z[j+2] * taps[j+2];
|
||||
t += z[j+3] * taps[j+3];
|
||||
|
||||
z[j+0] = z[j+0+2];
|
||||
z[j+1] = z[j+1+2];
|
||||
z[j+2] = z[j+2+2];
|
||||
z[j+3] = z[j+3+2];
|
||||
}
|
||||
*(dst_p++) = t / 65536;
|
||||
}
|
||||
|
||||
return { dst.p, src.count / 2, src.sampling_rate / 2 };
|
||||
}
|
||||
#if 0
|
||||
size_t fir_and_decimate_by_2_complex(
|
||||
const complex16_t* const src_start,
|
||||
const size_t src_count,
|
||||
complex16_t* const dst_start,
|
||||
complex16_t* const z,
|
||||
const complex16_t* const taps,
|
||||
const size_t taps_count
|
||||
) {
|
||||
/* 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_start;
|
||||
const auto src_end = &src_start[src_count];
|
||||
auto dst_p = dst_start;
|
||||
|
||||
auto z_p = &z[0];
|
||||
|
||||
while(src_p < src_end) {
|
||||
/* Put two new samples into delay buffer */
|
||||
*__SIMD32(z_p)++ = *__SIMD32(src_p)++;
|
||||
*__SIMD32(z_p)++ = *__SIMD32(src_p)++;
|
||||
|
||||
int64_t t_real = 0;
|
||||
int64_t t_imag = 0;
|
||||
|
||||
auto t_p = &taps[0];
|
||||
|
||||
const auto z_end = &z[taps_count];
|
||||
while(z_p < z_end) {
|
||||
const auto tap0 = *__SIMD32(t_p)++;
|
||||
const auto sample0 = *__SIMD32(z_p)++;
|
||||
t_real = __SMLSLD(sample0, tap0, t_real);
|
||||
t_imag = __SMLALDX(sample0, tap0, t_imag);
|
||||
|
||||
const auto tap1 = *__SIMD32(t_p)++;
|
||||
const auto sample1 = *__SIMD32(z_p)++;
|
||||
t_real = __SMLSLD(sample1, tap1, t_real);
|
||||
t_imag = __SMLALDX(sample1, tap1, t_imag);
|
||||
}
|
||||
|
||||
z_p = &z[0];
|
||||
|
||||
const auto t_end = &taps[taps_count];
|
||||
while(t_p < t_end) {
|
||||
const auto tap0 = *__SIMD32(t_p)++;
|
||||
const auto sample0 = *__SIMD32(z_p)++;
|
||||
t_real = __SMLSLD(sample0, tap0, t_real);
|
||||
t_imag = __SMLALDX(sample0, tap0, t_imag);
|
||||
|
||||
const auto tap1 = *__SIMD32(t_p)++;
|
||||
const auto sample1 = *__SIMD32(z_p)++;
|
||||
t_real = __SMLSLD(sample1, tap1, t_real);
|
||||
t_imag = __SMLALDX(sample1, tap1, t_imag);
|
||||
}
|
||||
|
||||
if( z_p == z_end ) {
|
||||
z_p = &z[0];
|
||||
}
|
||||
|
||||
/* TODO: No rounding taking place here, so might be adding a bit of
|
||||
* noise. Enough to be significant?
|
||||
*/
|
||||
*__SIMD32(dst_p)++ = __PKHBT(
|
||||
t_real / 131072,
|
||||
t_imag / 131072,
|
||||
16
|
||||
);
|
||||
/*
|
||||
*__SIMD32(dst_p)++ = __PKHBT(
|
||||
__SSAT((t_real / 131072), 16),
|
||||
__SSAT((t_imag / 131072), 16),
|
||||
16
|
||||
);
|
||||
*/
|
||||
}
|
||||
|
||||
return src_count / 2;
|
||||
}
|
||||
#endif
|
||||
size_t fir_and_decimate_by_2_complex_fast(
|
||||
const complex16_t* const src_start,
|
||||
const size_t src_count,
|
||||
complex16_t* const dst_start,
|
||||
complex16_t* const z,
|
||||
const complex16_t* const taps,
|
||||
const size_t taps_count
|
||||
) {
|
||||
/* 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_start;
|
||||
auto dst_p = dst_start;
|
||||
auto z_new_p = &z[0];
|
||||
auto t_p = &taps[taps_count * 2];
|
||||
|
||||
while(src_p < &src_start[src_count]) {
|
||||
/* Put two new samples into delay buffer */
|
||||
*__SIMD32(z_new_p)++ = *__SIMD32(src_p)++;
|
||||
*__SIMD32(z_new_p)++ = *__SIMD32(src_p)++;
|
||||
|
||||
t_p -= (taps_count + 2);
|
||||
if( z_new_p == &z[taps_count] ) {
|
||||
z_new_p = &z[0];
|
||||
t_p = &taps[taps_count];
|
||||
}
|
||||
|
||||
int64_t t_real = 0;
|
||||
int64_t t_imag = 0;
|
||||
|
||||
auto z_p = &z[0];
|
||||
while(z_p < &z[taps_count]) {
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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
|
||||
);
|
||||
}
|
||||
|
||||
return src_count / 2;
|
||||
}
|
||||
|
||||
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 */
|
242
firmware/baseband/dsp_decimate.hpp
Normal file
242
firmware/baseband/dsp_decimate.hpp
Normal file
@@ -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 <cstdint>
|
||||
#include <array>
|
||||
|
||||
#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<int16_t, taps_count>& taps
|
||||
) : taps { taps }
|
||||
{
|
||||
}
|
||||
|
||||
buffer_s16_t execute(
|
||||
buffer_s16_t src,
|
||||
buffer_s16_t dst
|
||||
);
|
||||
|
||||
private:
|
||||
std::array<int16_t, taps_count + 2> z;
|
||||
const std::array<int16_t, taps_count>& taps;
|
||||
};
|
||||
|
||||
size_t fir_and_decimate_by_2_complex(
|
||||
const complex16_t* const src_start,
|
||||
const size_t src_count,
|
||||
complex16_t* const dst_start,
|
||||
complex16_t* const z,
|
||||
const complex16_t* const taps,
|
||||
const size_t taps_count
|
||||
);
|
||||
|
||||
size_t fir_and_decimate_by_2_complex_fast(
|
||||
const complex16_t* const src_start,
|
||||
const size_t src_count,
|
||||
complex16_t* const dst_start,
|
||||
complex16_t* const z,
|
||||
const complex16_t* const taps,
|
||||
const size_t taps_count
|
||||
);
|
||||
|
||||
template<size_t taps_count>
|
||||
class FIRAndDecimateBy2Complex {
|
||||
public:
|
||||
/* NOTE! Current code makes an assumption that block of samples to be
|
||||
* processed will be a multiple of the taps_count.
|
||||
*/
|
||||
FIRAndDecimateBy2Complex(
|
||||
const std::array<int16_t, taps_count>& real_taps
|
||||
) {
|
||||
for(size_t i=0; i<taps_count; i++) {
|
||||
taps[ i] = real_taps[i];
|
||||
taps[taps_count + i] = real_taps[i];
|
||||
}
|
||||
}
|
||||
|
||||
buffer_c16_t execute(
|
||||
buffer_c16_t src,
|
||||
buffer_c16_t dst
|
||||
) {
|
||||
const auto dst_count = fir_and_decimate_by_2_complex_fast(src.p, src.count, dst.p, z.data(), taps.data(), taps_count);
|
||||
return { dst.p, dst_count, src.sampling_rate / 2 };
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<complex16_t, taps_count * 2> taps;
|
||||
std::array<complex16_t, taps_count> z;
|
||||
};
|
||||
|
||||
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<size_t N>
|
||||
class FIRAndDecimateBy2Complex {
|
||||
public:
|
||||
FIR64AndDecimateBy2Complex(
|
||||
const std::array<int16_t, N>& 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<complex16_t, N> z;
|
||||
const std::array<int16_t, N>& taps;
|
||||
|
||||
complex<int16_t> process_one(const size_t start_offset) {
|
||||
const auto split = &z[start_offset];
|
||||
const auto end = &z[z.size()];
|
||||
auto tap = &taps[0];
|
||||
|
||||
complex<int32_t> 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__*/
|
110
firmware/baseband/dsp_demodulate.cpp
Normal file
110
firmware/baseband/dsp_demodulate.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 <hal.h>
|
||||
|
||||
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<float>(t.imag()) / static_cast<float>(t.real());
|
||||
return 16384.0f * x;
|
||||
}
|
||||
*/
|
||||
static inline float angle_approx_0deg27(const complex32_t t) {
|
||||
const auto x = static_cast<float>(t.imag()) / static_cast<float>(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 };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
66
firmware/baseband/dsp_demodulate.hpp
Normal file
66
firmware/baseband/dsp_demodulate.hpp
Normal file
@@ -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 __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<float>(32767.0f / (2.0 * pi * deviation_hz / sampling_rate)) }
|
||||
{
|
||||
}
|
||||
|
||||
buffer_s16_t execute(
|
||||
buffer_c16_t src,
|
||||
buffer_s16_t dst
|
||||
);
|
||||
|
||||
private:
|
||||
complex16_t::rep_type z_;
|
||||
const float k;
|
||||
};
|
||||
|
||||
} /* namespace demodulate */
|
||||
} /* namespace dsp */
|
||||
|
||||
#endif/*__DSP_DEMODULATE_H__*/
|
22
firmware/baseband/dsp_fir_taps.cpp
Normal file
22
firmware/baseband/dsp_fir_taps.cpp
Normal file
@@ -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"
|
285
firmware/baseband/dsp_fir_taps.hpp
Normal file
285
firmware/baseband/dsp_fir_taps.hpp
Normal file
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* 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 <cstdint>
|
||||
#include <array>
|
||||
|
||||
#include "complex.hpp"
|
||||
|
||||
/* 3kHz/6.7kHz @ 96kHz. sum(abs(taps)): 89429 */
|
||||
constexpr std::array<int16_t, 64> taps_64_lp_031_070_tfilter { {
|
||||
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 std::array<int16_t, 64> taps_64_lp_042_078_tfilter { {
|
||||
-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 std::array<int16_t, 64> taps_64_lp_052_089_tfilter { {
|
||||
-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 std::array<int16_t, 64> taps_64_lp_063_100_tfilter { {
|
||||
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 std::array<int16_t, 64> taps_64_lp_073_108_tfilter { {
|
||||
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 std::array<int16_t, 64> taps_64_lp_083_120_tfilter { {
|
||||
-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 std::array<int16_t, 64> taps_64_lp_094_129_tfilter { {
|
||||
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 std::array<int16_t, 64> taps_64_lp_104_140_tfilter { {
|
||||
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 std::array<int16_t, 64> taps_64_lp_130_169_tfilter { {
|
||||
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 std::array<int16_t, 64> taps_64_lp_156_198 { {
|
||||
-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,
|
||||
} };
|
||||
|
||||
/* Narrowband FM filter */
|
||||
/* 96kHz int16_t input
|
||||
* -> FIR filter, <4kHz (0.042fs) pass, Hamming window.
|
||||
* -> 48kHz int16_t output, gain of 1.0.
|
||||
* Padded to multiple of four taps for unrolled FIR code.
|
||||
* sum(abs(taps)): 81493
|
||||
*/
|
||||
constexpr std::array<int16_t, 64> taps_64_lp_042_hamming { {
|
||||
51, 57, 61, 65, 63, 55, 35, 0,
|
||||
-52, -122, -207, -304, -402, -491, -556, -582,
|
||||
-552, -453, -271, 0, 363, 815, 1343, 1930,
|
||||
2553, 3183, 3788, 4338, 4802, 5154, 5374, 5449,
|
||||
5374, 5154, 4802, 4338, 3788, 3183, 2553, 1930,
|
||||
1343, 815, 363, 0, -271, -453, -552, -582,
|
||||
-556, -491, -402, -304, -207, -122, -52, 0,
|
||||
35, 55, 63, 65, 61, 57, 51, 0
|
||||
} };
|
||||
|
||||
/* Narrowband FM filter */
|
||||
/* 96kHz int16_t input
|
||||
* -> FIR filter, <6kHz (0.063fs) pass, Hamming window.
|
||||
* -> 48kHz int16_t output, gain of 1.0.
|
||||
* Padded to multiple of four taps for unrolled FIR code.
|
||||
* sum(abs(taps)): 92477
|
||||
*/
|
||||
constexpr std::array<int16_t, 64> taps_64_lp_063_hamming { {
|
||||
-20, -40, -59, -75, -83, -78, -52, 0,
|
||||
77, 173, 272, 352, 386, 348, 221, 0,
|
||||
-300, -644, -974, -1219, -1304, -1158, -730, 0,
|
||||
1016, 2261, 3641, 5034, 6305, 7325, 7985, 8213,
|
||||
7985, 7325, 6305, 5034, 3641, 2261, 1016, 0,
|
||||
-730, -1158, -1304, -1219, -974, -644, -300, 0,
|
||||
221, 348, 386, 352, 272, 173, 77, 0,
|
||||
-52, -78, -83, -75, -59, -40, -20, 0
|
||||
} };
|
||||
|
||||
/* sum(abs(taps)): 85241 */
|
||||
constexpr std::array<int16_t, 64> taps_64_lp_063_blackman { {
|
||||
0, 0, -2, -6, -11, -14, -11, 0,
|
||||
24, 62, 110, 157, 188, 184, 125, 0,
|
||||
-194, -441, -704, -926, -1035, -957, -626, 0,
|
||||
925, 2109, 3466, 4872, 6182, 7249, 7946, 8189,
|
||||
7946, 7249, 6182, 4872, 3466, 2109, 925, 0,
|
||||
-626, -957, -1035, -926, -704, -441, -194, 0,
|
||||
125, 184, 188, 157, 110, 62, 24, 0,
|
||||
-11, -14, -11, -6, -2, 0, 0, 0
|
||||
} };
|
||||
|
||||
/* Narrowband FM filter */
|
||||
/* 96kHz int16_t input
|
||||
* -> FIR filter, <8kHz (0.083fs) pass, Hamming window.
|
||||
* -> 48kHz int16_t output, gain of 1.0.
|
||||
* Padded to multiple of four taps for unrolled FIR code.
|
||||
* sum(abs(taps)): 99180
|
||||
*/
|
||||
constexpr std::array<int16_t, 64> taps_64_lp_083_hamming { {
|
||||
-26, 0, 32, 65, 90, 95, 67, 0,
|
||||
-100, -211, -294, -304, -208, 0, 288, 582,
|
||||
782, 785, 524, 0, -702, -1412, -1901, -1931,
|
||||
-1322, 0, 1962, 4340, 6795, 8932, 10388, 10904,
|
||||
10388, 8932, 6795, 4340, 1962, 0, -1322, -1931,
|
||||
-1901, -1412, -702, 0, 524, 785, 782, 582,
|
||||
288, 0, -208, -304, -294, -211, -100, 0,
|
||||
67, 95, 90, 65, 32, 0, -26, 0
|
||||
} };
|
||||
|
||||
/* sum(abs(taps)): 92568 */
|
||||
constexpr std::array<int16_t, 64> taps_64_lp_083_blackman { {
|
||||
0, 0, 1, 5, 12, 17, 15, 0,
|
||||
-32, -77, -119, -136, -101, 0, 164, 355,
|
||||
508, 541, 381, 0, -560, -1173, -1636, -1717,
|
||||
-1209, 0, 1876, 4221, 6695, 8883, 10388, 10924,
|
||||
10388, 8883, 6695, 4221, 1876, 0, -1209, -1717,
|
||||
-1636, -1173, -560, 0, 381, 541, 508, 355,
|
||||
164, 0, -101, -136, -119, -77, -32, 0,
|
||||
15, 17, 12, 5, 1, 0, 0, 0
|
||||
} };
|
||||
|
||||
/* Narrowband FM filter */
|
||||
/* 96kHz int16_t input
|
||||
* -> FIR filter, <10kHz (0.104fs) pass, Hamming window.
|
||||
* -> 48kHz int16_t output, gain of 1.0.
|
||||
* Padded to multiple of four taps for unrolled FIR code.
|
||||
* sum(abs(taps)): 106396
|
||||
*/
|
||||
constexpr std::array<int16_t, 64> taps_64_lp_104_hamming { {
|
||||
53, 40, 8, -37, -83, -106, -82, 0,
|
||||
123, 236, 272, 175, -54, -347, -571, -583,
|
||||
-299, 235, 833, 1215, 1116, 422, -728, -1934,
|
||||
-2625, -2254, -512, 2509, 6285, 9975, 12663, 13646,
|
||||
12663, 9975, 6285, 2509, -512, -2254, -2625, -1934,
|
||||
-728, 422, 1116, 1215, 833, 235, -299, -583,
|
||||
-571, -347, -54, 175, 272, 236, 123, 0,
|
||||
-82, -106, -83, -37, 8, 40, 53, 0
|
||||
} };
|
||||
|
||||
/* sum(abs(taps)): 99426 */
|
||||
constexpr std::array<int16_t, 64> taps_64_lp_104_blackman { {
|
||||
0, 0, 0, -3, -11, -19, -18, 0,
|
||||
39, 85, 110, 78, -26, -184, -325, -355,
|
||||
-194, 161, 605, 926, 889, 350, -626, -1717,
|
||||
-2397, -2109, -489, 2436, 6184, 9906, 12645, 13652,
|
||||
12645, 9906, 6184, 2436, -489, -2109, -2397, -1717,
|
||||
-626, 350, 889, 926, 605, 161, -194, -355,
|
||||
-325, -184, -26, 78, 110, 85, 39, 0,
|
||||
-18, -19, -11, -3, 0, 0, 0, 0
|
||||
} };
|
||||
|
||||
/* Narrowband AM audio filter */
|
||||
/* 96kHz int16_t input
|
||||
* -> FIR filter, <3kHz (0.031fs) pass, >6kHz (0.063fs) stop
|
||||
* -> 48kHz int16_t output, gain of 1.0 (I think).
|
||||
* Padded to multiple of four taps for unrolled FIR code.
|
||||
* sum(abs(taps)): 94053
|
||||
*/
|
||||
/* TODO: Review this filter, it's very quick and dirty. */
|
||||
constexpr std::array<int16_t, 64> taps_64_lp_031_063 { {
|
||||
-254, 255, 244, 269, 302, 325, 325, 290,
|
||||
215, 99, -56, -241, -442, -643, -820, -950,
|
||||
-1009, -974, -828, -558, -160, 361, 992, 1707,
|
||||
2477, 3264, 4027, 4723, 5312, 5761, 6042, 6203,
|
||||
6042, 5761, 5312, 4723, 4027, 3264, 2477, 1707,
|
||||
992, 361, -160, -558, -828, -974, -1009, -950,
|
||||
-820, -643, -442, -241, -56, 99, 215, 290,
|
||||
325, 325, 302, 269, 244, 255, -254, 0,
|
||||
} };
|
||||
|
||||
#endif/*__DSP_FIR_TAPS_H__*/
|
68
firmware/baseband/dsp_iir.hpp
Normal file
68
firmware/baseband/dsp_iir.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 <array>
|
||||
|
||||
#include "dsp_types.hpp"
|
||||
|
||||
class IIRBiquadFilter {
|
||||
public:
|
||||
// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
|
||||
|
||||
// Assume all coefficients are normalized so that a0=1.0
|
||||
constexpr IIRBiquadFilter(
|
||||
std::array<float, 3> b,
|
||||
std::array<float, 3> a
|
||||
) : b(b),
|
||||
a(a)
|
||||
{
|
||||
}
|
||||
|
||||
void execute(buffer_s16_t buffer) {
|
||||
for(size_t i=0; i<buffer.count; i++) {
|
||||
buffer.p[i] = execute_sample(buffer.p[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const std::array<float, 3> b;
|
||||
const std::array<float, 3> a;
|
||||
std::array<float, 3> x { { 0.0f, 0.0f, 0.0f } };
|
||||
std::array<float, 3> 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] = b[0] * x[2] + b[1] * x[1] + b[2] * x[0]
|
||||
- a[1] * y[1] - a[2] * y[0];
|
||||
|
||||
return y[2];
|
||||
}
|
||||
};
|
||||
|
||||
#endif/*__DSP_IIR_H__*/
|
30
firmware/baseband/event_m4.cpp
Normal file
30
firmware/baseband/event_m4.cpp
Normal file
@@ -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;
|
||||
}
|
46
firmware/baseband/event_m4.hpp
Normal file
46
firmware/baseband/event_m4.hpp
Normal file
@@ -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__*/
|
152
firmware/baseband/fxpt_atan2.cpp
Normal file
152
firmware/baseband/fxpt_atan2.cpp
Normal file
@@ -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 <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* 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<int32_t>(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;
|
||||
}
|
||||
}
|
||||
}
|
29
firmware/baseband/fxpt_atan2.hpp
Normal file
29
firmware/baseband/fxpt_atan2.hpp
Normal file
@@ -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 <cstdint>
|
||||
|
||||
int16_t fxpt_atan2(const int16_t y, const int16_t x);
|
||||
|
||||
#endif/*__FXPT_ATAN2_H__*/
|
225
firmware/baseband/gpdma_lli.hpp
Normal file
225
firmware/baseband/gpdma_lli.hpp
Normal file
@@ -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 <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#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<gpdma::channel::LLI>;
|
||||
using chain_p = std::unique_ptr<chain_t>;
|
||||
|
||||
Chain(const ChannelConfig& cc) :
|
||||
chain(std::make_unique<chain_t>(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<uint32_t>(lli),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace lli */
|
||||
} /* namespace gpdma */
|
||||
} /* namespace lpc43xx */
|
313
firmware/baseband/halconf.h
Executable file
313
firmware/baseband/halconf.h
Executable file
@@ -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_ */
|
||||
|
||||
/** @} */
|
986
firmware/baseband/main.cpp
Executable file
986
firmware/baseband/main.cpp
Executable file
@@ -0,0 +1,986 @@
|
||||
/*
|
||||
* 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 <cstdint>
|
||||
#include <cstddef>
|
||||
#include <complex>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <bitset>
|
||||
|
||||
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<typename Callback>
|
||||
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<typename Callback>
|
||||
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<typename Callback>
|
||||
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<typename Callback>
|
||||
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<complex16_t, 1024> 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<int8_t>[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<int16_t>[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<int16_t>[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<int16_t>[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<int16_t>[512], [-32768, 32512]
|
||||
* -> 3rd order CIC decimation by 2, gain of 1
|
||||
* -> 384kHz complex<int16_t>[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<int16_t>[256], [-32768, 32512]
|
||||
* -> 3rd order CIC decimation by 2, gain of 1
|
||||
* -> 192kHz complex<int16_t>[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<int16_t>[128], [-32768, 32512]
|
||||
* -> 3rd order CIC decimation by 2, gain of 1
|
||||
* -> 96kHz complex<int16_t>[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<complex16_t, 256> 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<audio_buffer.count; i++) {
|
||||
audio_buffer.p[i].left = audio_buffer.p[i].right = audio.p[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class NarrowbandAMAudio : public BasebandProcessor {
|
||||
public:
|
||||
void execute(buffer_c8_t buffer) override {
|
||||
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<int16_t>[64]
|
||||
* -> FIR filter, <?kHz (0.???fs) 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<int16_t>[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<int16_t>[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<int16_t>[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<int16_t>[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<const FSKConfigurationMessage*>(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<int16_t>[128]
|
||||
* -> FIR filter, <?kHz (?fs) pass, gain 1.0
|
||||
* -> 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);
|
||||
|
||||
for(size_t i=0; i<demodulated.count; i++) {
|
||||
clock_recovery.execute(demodulated.p[i], symbol_handler_fn);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By16 };
|
||||
dsp::decimate::FIRAndDecimateBy2Complex<64> 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();
|
||||
i2s::i2s0::tx_unmute();
|
||||
|
||||
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<float> 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<uint8_t, 256> 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<std::complex<float>, 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<spectrum_db.size(); i++) {
|
||||
const auto mag2 = magnitude_squared(samples_swapped[i]);
|
||||
const float db = complex16_mag_squared_to_dbv_norm(mag2);
|
||||
constexpr float mag_scale = 5.0f;
|
||||
const unsigned int v = (db * mag_scale) + 255.0f;
|
||||
spectrum_db[i] = std::max(0U, std::min(255U, v));
|
||||
}
|
||||
|
||||
/* TODO: Rename .db -> .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<const BasebandConfigurationMessage*>(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::sample_t, 8192>();
|
||||
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;
|
||||
}
|
||||
|
||||
void debug_indicate_error_init() {
|
||||
// TODO: Indicate error, but don't import all of PAL (with init)
|
||||
// led_rx.off();
|
||||
// led_tx.off();
|
||||
}
|
||||
|
||||
void debug_indicate_error_update() {
|
||||
// TODO: Indicate error, but don't import all of PAL (with init)
|
||||
// led_rx.toggle();
|
||||
// led_tx.toggle();
|
||||
}
|
43
firmware/baseband/mcuconf.h
Executable file
43
firmware/baseband/mcuconf.h
Executable file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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
|
34
firmware/baseband/packet_builder.cpp
Normal file
34
firmware/baseband/packet_builder.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
void PacketBuilder::configure(size_t new_payload_length) {
|
||||
if( new_payload_length <= payload.size() ) {
|
||||
payload_length = new_payload_length;
|
||||
reset_state();
|
||||
}
|
||||
}
|
||||
|
||||
void PacketBuilder::reset_state() {
|
||||
bits_received = 0;
|
||||
state = State::AccessCodeSearch;
|
||||
}
|
75
firmware/baseband/packet_builder.hpp
Normal file
75
firmware/baseband/packet_builder.hpp
Normal file
@@ -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 __PACKET_BUILDER_H__
|
||||
#define __PACKET_BUILDER_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <bitset>
|
||||
|
||||
class PacketBuilder {
|
||||
public:
|
||||
void configure(size_t new_payload_length);
|
||||
|
||||
template<typename PayloadHandler>
|
||||
void execute(
|
||||
const uint_fast8_t symbol,
|
||||
const bool access_code_found,
|
||||
PayloadHandler payload_handler
|
||||
) {
|
||||
switch(state) {
|
||||
case State::AccessCodeSearch:
|
||||
if( access_code_found ) {
|
||||
state = State::Payload;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Payload:
|
||||
if( bits_received < payload_length ) {
|
||||
payload[bits_received++] = symbol;
|
||||
} else {
|
||||
payload_handler(payload, bits_received);
|
||||
reset_state();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
reset_state();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
enum State {
|
||||
AccessCodeSearch,
|
||||
Payload,
|
||||
};
|
||||
|
||||
size_t payload_length { 0 };
|
||||
size_t bits_received { 0 };
|
||||
State state { State::AccessCodeSearch };
|
||||
std::bitset<256> payload;
|
||||
|
||||
void reset_state();
|
||||
};
|
||||
|
||||
#endif/*__PACKET_BUILDER_H__*/
|
91
firmware/baseband/rssi.cpp
Normal file
91
firmware/baseband/rssi.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 <cstdint>
|
||||
|
||||
#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 uint32_t adc1_sel = (1U << portapack::adc1_rssi_input);
|
||||
const auto adc1_interrupt_mask = 0;
|
||||
|
||||
//constexpr uint32_t adc1_clkdiv = base_apb3_clk_f / adc::clock_rate_max;
|
||||
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,
|
||||
};
|
||||
|
||||
// volatile size_t rssi_buffer_available_count = 0;
|
||||
// volatile size_t rssi_buffer_error_count = 0;
|
||||
|
||||
// static void rssi_buffer_available() {
|
||||
// rssi_buffer_available_count++;
|
||||
// }
|
||||
|
||||
// static void rssi_buffer_error() {
|
||||
// rssi_buffer_error_count++;
|
||||
// }
|
||||
|
||||
void init() {
|
||||
adc1.clock_enable();
|
||||
//adc1.interrupts_disable();
|
||||
adc1.power_up(adc1_config);
|
||||
|
||||
/* An interrupt must be enabled within peripheral to issue request to
|
||||
*GPDMA */
|
||||
adc1.interrupts_enable(adc1_interrupt_mask);
|
||||
|
||||
dma::init();
|
||||
// dma::set_handlers(rssi_buffer_available, rssi_buffer_error);
|
||||
}
|
||||
|
||||
void start() {
|
||||
dma::enable();
|
||||
adc1.start_burst();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
dma::disable();
|
||||
adc1.stop_burst();
|
||||
}
|
||||
|
||||
} /* namespace rssi */
|
||||
} /* namespace rf */
|
43
firmware/baseband/rssi.hpp
Normal file
43
firmware/baseband/rssi.hpp
Normal file
@@ -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 <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
#include "buffer.hpp"
|
||||
|
||||
namespace rf {
|
||||
namespace rssi {
|
||||
|
||||
using sample_t = uint8_t;
|
||||
using buffer_t = buffer_t<sample_t>;
|
||||
|
||||
void init();
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
} /* namespace rssi */
|
||||
} /* namespace rf */
|
||||
|
||||
#endif/*__RSSI_H__*/
|
182
firmware/baseband/rssi_dma.cpp
Normal file
182
firmware/baseband/rssi_dma.cpp
Normal file
@@ -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 <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
#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<uint32_t>(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<uint32_t>(&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<buffers_config.count; i++) {
|
||||
const auto memory = reinterpret_cast<uint32_t>(&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<sample_t*>(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 */
|
51
firmware/baseband/rssi_dma.hpp
Normal file
51
firmware/baseband/rssi_dma.hpp
Normal file
@@ -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 <cstddef>
|
||||
|
||||
#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__*/
|
129
firmware/baseband/touch_dma.cpp
Normal file
129
firmware/baseband/touch_dma.cpp
Normal file
@@ -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 <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
#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<uint32_t>(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<uint32_t>(&LPC_ADC0->DR[0]);
|
||||
lli.destaddr = reinterpret_cast<uint32_t>(&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 */
|
52
firmware/baseband/touch_dma.hpp
Normal file
52
firmware/baseband/touch_dma.hpp
Normal file
@@ -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 <cstddef>
|
||||
|
||||
namespace touch {
|
||||
namespace dma {
|
||||
|
||||
using sample_t = uint32_t;
|
||||
using buffer_t = buffer_t<sample_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__*/
|
Reference in New Issue
Block a user