You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
7.3 KiB
C++
219 lines
7.3 KiB
C++
1 year ago
|
/**
|
||
|
* @file conversion.cpp
|
||
|
* @brief Manages algorithm application (converts input samples to output).
|
||
|
*
|
||
|
* Copyright (C) 2021 Clyne Sullivan
|
||
|
*
|
||
|
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||
|
* the GNU General Public License along with this program.
|
||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "conversion.hpp"
|
||
|
|
||
|
#include "periph/adc.hpp"
|
||
|
#include "periph/dac.hpp"
|
||
|
#include "elfload.hpp"
|
||
|
#include "error.hpp"
|
||
|
#include "runstatus.hpp"
|
||
|
#include "samples.hpp"
|
||
|
|
||
|
// MSG_* things below are macros rather than constexpr
|
||
|
// to ensure inlining.
|
||
|
|
||
|
#define MSG_CONVFIRST (1)
|
||
|
#define MSG_CONVSECOND (2)
|
||
|
#define MSG_CONVFIRST_MEASURE (3)
|
||
|
#define MSG_CONVSECOND_MEASURE (4)
|
||
|
|
||
|
#define MSG_FOR_FIRST(msg) (msg & 1)
|
||
|
#define MSG_FOR_MEASURE(msg) (msg > 2)
|
||
|
|
||
|
__attribute__((section(".convdata")))
|
||
|
thread_t *ConversionManager::m_thread_monitor = nullptr;
|
||
|
thread_t *ConversionManager::m_thread_runner = nullptr;
|
||
|
|
||
|
__attribute__((section(".stacks")))
|
||
|
std::array<char, 1024> ConversionManager::m_thread_monitor_stack = {};
|
||
|
__attribute__((section(".stacks")))
|
||
|
std::array<char, THD_WORKING_AREA_SIZE(128)> ConversionManager::m_thread_runner_entry_stack = {};
|
||
|
__attribute__((section(".convdata")))
|
||
|
std::array<char, CONVERSION_THREAD_STACK_SIZE> ConversionManager::m_thread_runner_stack = {};
|
||
|
|
||
|
std::array<msg_t, 2> ConversionManager::m_mailbox_buffer;
|
||
|
mailbox_t ConversionManager::m_mailbox = _MAILBOX_DATA(m_mailbox, m_mailbox_buffer.data(), m_mailbox_buffer.size());
|
||
|
|
||
|
void ConversionManager::begin()
|
||
|
{
|
||
|
m_thread_monitor = chThdCreateStatic(m_thread_monitor_stack.data(),
|
||
|
m_thread_monitor_stack.size(),
|
||
|
NORMALPRIO + 1,
|
||
|
threadMonitor,
|
||
|
nullptr);
|
||
|
auto runner_stack_end = &m_thread_runner_stack[CONVERSION_THREAD_STACK_SIZE];
|
||
|
m_thread_runner = chThdCreateStatic(m_thread_runner_entry_stack.data(),
|
||
|
m_thread_runner_entry_stack.size(),
|
||
|
HIGHPRIO,
|
||
|
threadRunnerEntry,
|
||
|
runner_stack_end);
|
||
|
}
|
||
|
|
||
|
void ConversionManager::start()
|
||
|
{
|
||
|
Samples::Out.clear();
|
||
|
ADC::start(Samples::In.data(), Samples::In.size(), adcReadHandler);
|
||
|
DAC::start(0, Samples::Out.data(), Samples::Out.size());
|
||
|
}
|
||
|
|
||
|
void ConversionManager::startMeasurement()
|
||
|
{
|
||
|
ADC::setOperation(adcReadHandlerMeasure);
|
||
|
}
|
||
|
|
||
|
void ConversionManager::stop()
|
||
|
{
|
||
|
DAC::stop(0);
|
||
|
ADC::stop();
|
||
|
}
|
||
|
|
||
|
thread_t *ConversionManager::getMonitorHandle()
|
||
|
{
|
||
|
return m_thread_monitor;
|
||
|
}
|
||
|
|
||
|
void ConversionManager::abort(bool fpu_stacked)
|
||
|
{
|
||
|
ELFManager::unload();
|
||
|
EM.add(Error::ConversionAborted);
|
||
|
//run_status = RunStatus::Recovering;
|
||
|
|
||
|
// Confirm that the exception return thread is the algorithm...
|
||
|
uint32_t *psp;
|
||
|
asm("mrs %0, psp" : "=r" (psp));
|
||
|
|
||
|
bool isRunnerStack =
|
||
|
(uint32_t)psp >= reinterpret_cast<uint32_t>(m_thread_runner_stack.data()) &&
|
||
|
(uint32_t)psp <= reinterpret_cast<uint32_t>(m_thread_runner_stack.data() +
|
||
|
m_thread_runner_stack.size());
|
||
|
|
||
|
if (isRunnerStack)
|
||
|
{
|
||
|
// If it is, we can force the algorithm to exit by "resetting" its thread.
|
||
|
// We do this by rebuilding the thread's stacked exception return.
|
||
|
auto newpsp = reinterpret_cast<uint32_t *>(m_thread_runner_stack.data() +
|
||
|
m_thread_runner_stack.size() -
|
||
|
(fpu_stacked ? 26 : 8) * sizeof(uint32_t));
|
||
|
// Set the LR register to the thread's entry point.
|
||
|
newpsp[5] = reinterpret_cast<uint32_t>(threadRunner);
|
||
|
// Overwrite the instruction we'll return to with "bx lr" (jump to address in LR).
|
||
|
newpsp[6] = psp[6];
|
||
|
*reinterpret_cast<uint16_t *>(newpsp[6]) = 0x4770; // "bx lr"
|
||
|
// Keep PSR contents (bit set forces Thumb mode, just in case).
|
||
|
newpsp[7] = psp[7] | (1 << 24);
|
||
|
// Set the new stack pointer.
|
||
|
asm("msr psp, %0" :: "r" (newpsp));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ConversionManager::threadMonitor(void *)
|
||
|
{
|
||
|
while (1) {
|
||
|
msg_t message;
|
||
|
msg_t fetch = chMBFetchTimeout(&m_mailbox, &message, TIME_INFINITE);
|
||
|
if (fetch == MSG_OK)
|
||
|
chMsgSend(m_thread_runner, message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ConversionManager::threadRunnerEntry(void *stack)
|
||
|
{
|
||
|
ELFManager::unload();
|
||
|
port_unprivileged_jump(reinterpret_cast<uint32_t>(threadRunner),
|
||
|
reinterpret_cast<uint32_t>(stack));
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".convcode")))
|
||
|
void ConversionManager::threadRunner(void *)
|
||
|
{
|
||
|
while (1) {
|
||
|
// Sleep until we receive a mailbox message.
|
||
|
msg_t message;
|
||
|
asm("svc 0; mov %0, r0" : "=r" (message));
|
||
|
|
||
|
if (message != 0) {
|
||
|
auto samples = MSG_FOR_FIRST(message) ? Samples::In.data()
|
||
|
: Samples::In.middata();
|
||
|
auto size = Samples::In.size() / 2;
|
||
|
|
||
|
auto entry = ELFManager::loadedElf();
|
||
|
if (entry) {
|
||
|
// Below, we remember the stack pointer just in case the
|
||
|
// loaded algorithm messes things up.
|
||
|
uint32_t sp;
|
||
|
|
||
|
if (!MSG_FOR_MEASURE(message)) {
|
||
|
asm("mov %0, sp" : "=r" (sp));
|
||
|
samples = entry(samples, size);
|
||
|
asm("mov sp, %0" :: "r" (sp));
|
||
|
volatile auto testRead = *samples;
|
||
|
} else {
|
||
|
// Start execution timer:
|
||
|
asm("mov %0, sp; eor r0, r0; svc 2" : "=r" (sp));
|
||
|
samples = entry(samples, size);
|
||
|
// Stop execution timer:
|
||
|
asm("mov r0, #1; svc 2; mov sp, %0" :: "r" (sp));
|
||
|
volatile auto testRead = *samples;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Update the sample out buffer with the transformed samples.
|
||
|
if (samples != nullptr) {
|
||
|
if (MSG_FOR_FIRST(message))
|
||
|
Samples::Out.modify(samples, size);
|
||
|
else
|
||
|
Samples::Out.midmodify(samples, size);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ConversionManager::adcReadHandler(adcsample_t *buffer, size_t)
|
||
|
{
|
||
|
chSysLockFromISR();
|
||
|
|
||
|
// If previous request hasn't been handled, then we're going too slow.
|
||
|
// We'll need to abort.
|
||
|
if (chMBGetUsedCountI(&m_mailbox) > 1) {
|
||
|
chMBResetI(&m_mailbox);
|
||
|
chMBResumeX(&m_mailbox);
|
||
|
chSysUnlockFromISR();
|
||
|
abort();
|
||
|
} else {
|
||
|
// Mark the modified samples as 'fresh' or ready for manipulation.
|
||
|
if (buffer == Samples::In.data()) {
|
||
|
Samples::In.setModified();
|
||
|
chMBPostI(&m_mailbox, MSG_CONVFIRST);
|
||
|
} else {
|
||
|
Samples::In.setMidmodified();
|
||
|
chMBPostI(&m_mailbox, MSG_CONVSECOND);
|
||
|
}
|
||
|
chSysUnlockFromISR();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ConversionManager::adcReadHandlerMeasure(adcsample_t *buffer, size_t)
|
||
|
{
|
||
|
chSysLockFromISR();
|
||
|
if (buffer == Samples::In.data()) {
|
||
|
Samples::In.setModified();
|
||
|
chMBPostI(&m_mailbox, MSG_CONVFIRST_MEASURE);
|
||
|
} else {
|
||
|
Samples::In.setMidmodified();
|
||
|
chMBPostI(&m_mailbox, MSG_CONVSECOND_MEASURE);
|
||
|
}
|
||
|
chSysUnlockFromISR();
|
||
|
|
||
|
ADC::setOperation(adcReadHandler);
|
||
|
}
|
||
|
|