Initial firmware commit.

This commit is contained in:
Jared Boone
2015-07-08 08:39:24 -07:00
parent 626e863257
commit dc6fee8370
357 changed files with 83134 additions and 0 deletions

267
firmware/baseband/Makefile Executable file
View 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

View 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);
}

View 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__*/

View 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 */

View 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__*/

View 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 */

View 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__*/

View 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
View 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_ */
/** @} */

View 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)
);
}

View 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__*/

View 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 */

View 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__*/

View 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 };
}
}
}

View 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__*/

View 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"

View 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__*/

View 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__*/

View 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;
}

View 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__*/

View 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;
}
}
}

View 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__*/

View 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
View 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
View 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
View 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

View 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;
}

View 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__*/

View 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 */

View 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__*/

View 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 */

View 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__*/

View 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 */

View 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__*/