reorganized source, wip
parent
d24ed15843
commit
123cc4c756
@ -1,3 +1,3 @@
|
||||
source [find interface/stlink.cfg]
|
||||
source [find target/stm32h7x.cfg]
|
||||
source [find target/stm32l4x.cfg]
|
||||
|
||||
|
@ -0,0 +1,291 @@
|
||||
#include "communication.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#include "periph/adc.hpp"
|
||||
#include "periph/dac.hpp"
|
||||
#include "periph/usbserial.hpp"
|
||||
#include "elfload.hpp"
|
||||
#include "error.hpp"
|
||||
#include "conversion.hpp"
|
||||
#include "runstatus.hpp"
|
||||
#include "samples.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
|
||||
__attribute__((section(".stacks")))
|
||||
std::array<char, 4096> CommunicationManager::m_thread_stack = {};
|
||||
|
||||
void CommunicationManager::begin()
|
||||
{
|
||||
chThdCreateStatic(m_thread_stack.data(),
|
||||
m_thread_stack.size(),
|
||||
NORMALPRIO,
|
||||
threadComm,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
static void writeADCBuffer(unsigned char *);
|
||||
static void setBufferSize(unsigned char *);
|
||||
static void updateGenerator(unsigned char *);
|
||||
static void loadAlgorithm(unsigned char *);
|
||||
static void readStatus(unsigned char *);
|
||||
static void startConversionMeasure(unsigned char *);
|
||||
static void startConversion(unsigned char *);
|
||||
static void stopConversion(unsigned char *);
|
||||
static void startGenerator(unsigned char *);
|
||||
static void readADCBuffer(unsigned char *);
|
||||
static void readDACBuffer(unsigned char *);
|
||||
static void unloadAlgorithm(unsigned char *);
|
||||
static void readIdentifier(unsigned char *);
|
||||
static void readExecTime(unsigned char *);
|
||||
static void sampleRate(unsigned char *);
|
||||
static void readConversionResults(unsigned char *);
|
||||
static void readConversionInput(unsigned char *);
|
||||
static void readMessage(unsigned char *);
|
||||
static void stopGenerator(unsigned char *);
|
||||
|
||||
static const std::array<std::pair<char, void (*)(unsigned char *)>, 19> commandTable {{
|
||||
{'A', writeADCBuffer},
|
||||
{'B', setBufferSize},
|
||||
{'D', updateGenerator},
|
||||
{'E', loadAlgorithm},
|
||||
{'I', readStatus},
|
||||
{'M', startConversionMeasure},
|
||||
{'R', startConversion},
|
||||
{'S', stopConversion},
|
||||
{'W', startGenerator},
|
||||
{'a', readADCBuffer},
|
||||
{'d', readDACBuffer},
|
||||
{'e', unloadAlgorithm},
|
||||
{'i', readIdentifier},
|
||||
{'m', readExecTime},
|
||||
{'r', sampleRate},
|
||||
{'s', readConversionResults},
|
||||
{'t', readConversionInput},
|
||||
{'u', readMessage},
|
||||
{'w', stopGenerator}
|
||||
}};
|
||||
|
||||
void CommunicationManager::threadComm(void *)
|
||||
{
|
||||
while (1) {
|
||||
if (USBSerial::isActive()) {
|
||||
// Attempt to receive a command packet
|
||||
if (unsigned char cmd[3]; USBSerial::read(&cmd[0], 1) > 0) {
|
||||
// Packet received, first byte represents the desired command/action
|
||||
auto func = std::find_if(commandTable.cbegin(), commandTable.cend(),
|
||||
[&cmd](const auto& f) { return f.first == cmd[0]; });
|
||||
if (func != commandTable.cend())
|
||||
func->second(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
chThdSleepMicroseconds(100);
|
||||
}
|
||||
}
|
||||
|
||||
void writeADCBuffer(unsigned char *)
|
||||
{
|
||||
USBSerial::read(Samples::In.bytedata(), Samples::In.bytesize());
|
||||
}
|
||||
|
||||
void setBufferSize(unsigned char *cmd)
|
||||
{
|
||||
if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle) &&
|
||||
EM.assert(USBSerial::read(&cmd[1], 2) == 2, Error::BadParamSize))
|
||||
{
|
||||
// count is multiplied by two since this command receives size of buffer
|
||||
// for each algorithm application.
|
||||
unsigned int count = (cmd[1] | (cmd[2] << 8)) * 2;
|
||||
if (EM.assert(count <= MAX_SAMPLE_BUFFER_SIZE, Error::BadParam)) {
|
||||
Samples::In.setSize(count);
|
||||
Samples::Out.setSize(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateGenerator(unsigned char *cmd)
|
||||
{
|
||||
if (EM.assert(USBSerial::read(&cmd[1], 2) == 2, Error::BadParamSize)) {
|
||||
unsigned int count = cmd[1] | (cmd[2] << 8);
|
||||
if (EM.assert(count <= MAX_SAMPLE_BUFFER_SIZE, Error::BadParam)) {
|
||||
if (run_status == RunStatus::Idle) {
|
||||
Samples::Generator.setSize(count);
|
||||
USBSerial::read(
|
||||
reinterpret_cast<uint8_t *>(Samples::Generator.data()),
|
||||
Samples::Generator.bytesize());
|
||||
} else if (run_status == RunStatus::Running) {
|
||||
int more;
|
||||
do {
|
||||
chThdSleepMicroseconds(10);
|
||||
more = DAC::sigGenWantsMore();
|
||||
} while (more == -1);
|
||||
|
||||
USBSerial::read(reinterpret_cast<uint8_t *>(
|
||||
more == 0 ? Samples::Generator.data() : Samples::Generator.middata()),
|
||||
Samples::Generator.bytesize() / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadAlgorithm(unsigned char *cmd)
|
||||
{
|
||||
if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle) &&
|
||||
EM.assert(USBSerial::read(&cmd[1], 2) == 2, Error::BadParamSize))
|
||||
{
|
||||
// Only load the binary if it can fit in the memory reserved for it.
|
||||
unsigned int size = cmd[1] | (cmd[2] << 8);
|
||||
if (EM.assert(size < MAX_ELF_FILE_SIZE, Error::BadUserCodeSize)) {
|
||||
USBSerial::read(ELFManager::fileBuffer(), size);
|
||||
auto entry = ELFManager::loadFromInternalBuffer();
|
||||
EM.assert(entry != nullptr, Error::BadUserCodeLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void readStatus(unsigned char *)
|
||||
{
|
||||
unsigned char buf[2] = {
|
||||
static_cast<unsigned char>(run_status),
|
||||
static_cast<unsigned char>(EM.pop())
|
||||
};
|
||||
|
||||
USBSerial::write(buf, sizeof(buf));
|
||||
}
|
||||
|
||||
void startConversionMeasure(unsigned char *)
|
||||
{
|
||||
if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle)) {
|
||||
run_status = RunStatus::Running;
|
||||
ConversionManager::startMeasured();
|
||||
}
|
||||
}
|
||||
|
||||
void startConversion(unsigned char *)
|
||||
{
|
||||
if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle)) {
|
||||
run_status = RunStatus::Running;
|
||||
ConversionManager::start();
|
||||
}
|
||||
}
|
||||
|
||||
void stopConversion(unsigned char *)
|
||||
{
|
||||
if (run_status == RunStatus::Running) {
|
||||
ConversionManager::stop();
|
||||
run_status = RunStatus::Idle;
|
||||
}
|
||||
}
|
||||
|
||||
void startGenerator(unsigned char *)
|
||||
{
|
||||
DAC::start(1, Samples::Generator.data(), Samples::Generator.size());
|
||||
}
|
||||
|
||||
void readADCBuffer(unsigned char *)
|
||||
{
|
||||
USBSerial::write(Samples::In.bytedata(), Samples::In.bytesize());
|
||||
}
|
||||
|
||||
void readDACBuffer(unsigned char *)
|
||||
{
|
||||
|
||||
USBSerial::write(Samples::Out.bytedata(), Samples::Out.bytesize());
|
||||
}
|
||||
|
||||
void unloadAlgorithm(unsigned char *)
|
||||
{
|
||||
ELFManager::unload();
|
||||
}
|
||||
|
||||
void readIdentifier(unsigned char *)
|
||||
{
|
||||
#if defined(TARGET_PLATFORM_H7)
|
||||
USBSerial::write(reinterpret_cast<const uint8_t *>("stmdsph"), 7);
|
||||
#else
|
||||
USBSerial::write(reinterpret_cast<const uint8_t *>("stmdspl"), 7);
|
||||
#endif
|
||||
}
|
||||
|
||||
void readExecTime(unsigned char *)
|
||||
{
|
||||
USBSerial::write(reinterpret_cast<uint8_t *>(&conversion_time_measurement.last),
|
||||
sizeof(rtcnt_t));
|
||||
}
|
||||
|
||||
void sampleRate(unsigned char *cmd)
|
||||
{
|
||||
if (EM.assert(USBSerial::read(&cmd[1], 1) == 1, Error::BadParamSize)) {
|
||||
if (cmd[1] == 0xFF) {
|
||||
unsigned char r = SClock::getRate();
|
||||
USBSerial::write(&r, 1);
|
||||
} else {
|
||||
auto r = static_cast<SClock::Rate>(cmd[1]);
|
||||
SClock::setRate(r);
|
||||
ADC::setRate(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void readConversionResults(unsigned char *)
|
||||
{
|
||||
if (auto samps = Samples::Out.modified(); samps != nullptr) {
|
||||
unsigned char buf[2] = {
|
||||
static_cast<unsigned char>(Samples::Out.size() / 2 & 0xFF),
|
||||
static_cast<unsigned char>(((Samples::Out.size() / 2) >> 8) & 0xFF)
|
||||
};
|
||||
USBSerial::write(buf, 2);
|
||||
unsigned int total = Samples::Out.bytesize() / 2;
|
||||
unsigned int offset = 0;
|
||||
unsigned char unused;
|
||||
while (total > 512) {
|
||||
USBSerial::write(reinterpret_cast<uint8_t *>(samps) + offset, 512);
|
||||
while (USBSerial::read(&unused, 1) == 0);
|
||||
offset += 512;
|
||||
total -= 512;
|
||||
}
|
||||
USBSerial::write(reinterpret_cast<uint8_t *>(samps) + offset, total);
|
||||
while (USBSerial::read(&unused, 1) == 0);
|
||||
} else {
|
||||
USBSerial::write(reinterpret_cast<const uint8_t *>("\0\0"), 2);
|
||||
}
|
||||
}
|
||||
|
||||
void readConversionInput(unsigned char *)
|
||||
{
|
||||
if (auto samps = Samples::In.modified(); samps != nullptr) {
|
||||
unsigned char buf[2] = {
|
||||
static_cast<unsigned char>(Samples::In.size() / 2 & 0xFF),
|
||||
static_cast<unsigned char>(((Samples::In.size() / 2) >> 8) & 0xFF)
|
||||
};
|
||||
USBSerial::write(buf, 2);
|
||||
unsigned int total = Samples::In.bytesize() / 2;
|
||||
unsigned int offset = 0;
|
||||
unsigned char unused;
|
||||
while (total > 512) {
|
||||
USBSerial::write(reinterpret_cast<uint8_t *>(samps) + offset, 512);
|
||||
while (USBSerial::read(&unused, 1) == 0);
|
||||
offset += 512;
|
||||
total -= 512;
|
||||
}
|
||||
USBSerial::write(reinterpret_cast<uint8_t *>(samps) + offset, total);
|
||||
while (USBSerial::read(&unused, 1) == 0);
|
||||
} else {
|
||||
USBSerial::write(reinterpret_cast<const uint8_t *>("\0\0"), 2);
|
||||
}
|
||||
}
|
||||
|
||||
void readMessage(unsigned char *)
|
||||
{
|
||||
//USBSerial::write(reinterpret_cast<uint8_t *>(userMessageBuffer), userMessageSize);
|
||||
}
|
||||
|
||||
void stopGenerator(unsigned char *)
|
||||
{
|
||||
DAC::stop(1);
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @file communication.hpp
|
||||
* @brief Manages communication with the host computer.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_COMMUNICATION_HPP
|
||||
#define STMDSP_COMMUNICATION_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
class CommunicationManager
|
||||
{
|
||||
public:
|
||||
static void begin();
|
||||
|
||||
private:
|
||||
static void threadComm(void *);
|
||||
|
||||
static std::array<char, 4096> m_thread_stack;
|
||||
};
|
||||
|
||||
#endif // STMDSP_COMMUNICATION_HPP
|
||||
|
@ -0,0 +1,206 @@
|
||||
#include "conversion.hpp"
|
||||
|
||||
#include "periph/adc.hpp"
|
||||
#include "periph/dac.hpp"
|
||||
#include "elfload.hpp"
|
||||
#include "error.hpp"
|
||||
#include "runstatus.hpp"
|
||||
#include "samples.hpp"
|
||||
|
||||
constexpr msg_t MSG_CONVFIRST = 1;
|
||||
constexpr msg_t MSG_CONVSECOND = 2;
|
||||
constexpr msg_t MSG_CONVFIRST_MEASURE = 3;
|
||||
constexpr msg_t MSG_CONVSECOND_MEASURE = 4;
|
||||
|
||||
constexpr auto MSG_FOR_FIRST = [](msg_t m) { return m & 1; };
|
||||
constexpr auto MSG_FOR_MEASURE = [](msg_t m) { return m > 2; };
|
||||
|
||||
time_measurement_t conversion_time_measurement;
|
||||
|
||||
__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, 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::startMeasured()
|
||||
{
|
||||
Samples::Out.clear();
|
||||
ADC::start(Samples::In.data(), Samples::In.size(), adcReadHandlerMeasure);
|
||||
DAC::start(0, Samples::Out.data(), Samples::Out.size());
|
||||
}
|
||||
|
||||
void ConversionManager::stop()
|
||||
{
|
||||
DAC::stop(0);
|
||||
ADC::stop();
|
||||
}
|
||||
|
||||
thread_t *ConversionManager::getMonitorHandle()
|
||||
{
|
||||
return m_thread_monitor;
|
||||
}
|
||||
|
||||
void ConversionManager::abort()
|
||||
{
|
||||
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() -
|
||||
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));
|
||||
} 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));
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @file conversion.hpp
|
||||
* @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/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_CONVERSION_HPP
|
||||
#define STMDSP_CONVERSION_HPP
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
constexpr unsigned int CONVERSION_THREAD_STACK_SIZE =
|
||||
#if defined(TARGET_PLATFORM_H7)
|
||||
62 * 1024;
|
||||
#else
|
||||
15 * 1024;
|
||||
#endif
|
||||
|
||||
class ConversionManager
|
||||
{
|
||||
public:
|
||||
static void begin();
|
||||
|
||||
// Begins sample conversion.
|
||||
static void start();
|
||||
// Begins conversion with execution time measured.
|
||||
static void startMeasured();
|
||||
// Stops conversion.
|
||||
static void stop();
|
||||
|
||||
static thread_t *getMonitorHandle();
|
||||
|
||||
// Internal only: Aborts a running conversion.
|
||||
static void abort();
|
||||
|
||||
private:
|
||||
static void threadMonitor(void *);
|
||||
static void threadRunnerEntry(void *stack);
|
||||
|
||||
static void threadRunner(void *);
|
||||
static void adcReadHandler(adcsample_t *buffer, size_t);
|
||||
static void adcReadHandlerMeasure(adcsample_t *buffer, size_t);
|
||||
|
||||
static thread_t *m_thread_monitor;
|
||||
static thread_t *m_thread_runner;
|
||||
|
||||
static std::array<char, 1024> m_thread_monitor_stack;
|
||||
static std::array<char, 128> m_thread_runner_entry_stack;
|
||||
static std::array<char, CONVERSION_THREAD_STACK_SIZE> m_thread_runner_stack;
|
||||
|
||||
static std::array<msg_t, 2> m_mailbox_buffer;
|
||||
static mailbox_t m_mailbox;
|
||||
};
|
||||
|
||||
// Stores the measured execution time.
|
||||
extern time_measurement_t conversion_time_measurement;
|
||||
|
||||
#endif // STMDSP_CONVERSION_HPP
|
||||
|
@ -1,26 +0,0 @@
|
||||
/**
|
||||
* @file elf_load.hpp
|
||||
* @brief Loads ELF binary data into memory for execution.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef ELF_LOAD_HPP_
|
||||
#define ELF_LOAD_HPP_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ELF
|
||||
{
|
||||
using Entry = uint16_t *(*)(uint16_t *, size_t);
|
||||
|
||||
Entry load(void *elf_data);
|
||||
}
|
||||
|
||||
#endif // ELF_LOAD_HPP_
|
||||
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file elfload.hpp
|
||||
* @brief Loads ELF binary data into memory for execution.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef ELF_LOAD_HPP_
|
||||
#define ELF_LOAD_HPP_
|
||||
|
||||
#include "samplebuffer.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
constexpr unsigned int MAX_ELF_FILE_SIZE = 16 * 1024;
|
||||
|
||||
class ELFManager
|
||||
{
|
||||
public:
|
||||
using EntryFunc = Sample *(*)(Sample *, size_t);
|
||||
|
||||
static EntryFunc loadFromInternalBuffer();
|
||||
|
||||
static EntryFunc loadedElf() {
|
||||
return m_entry;
|
||||
}
|
||||
|
||||
static unsigned char *fileBuffer() {
|
||||
return m_file_buffer.data();
|
||||
}
|
||||
|
||||
static void unload() {
|
||||
m_entry = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
static EntryFunc m_entry;
|
||||
|
||||
static std::array<unsigned char, MAX_ELF_FILE_SIZE> m_file_buffer;
|
||||
};
|
||||
|
||||
#endif // ELF_LOAD_HPP_
|
||||
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @file error.cpp
|
||||
* @brief Tracks and reports non-critical errors.
|
||||
*
|
||||
* 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 "error.hpp"
|
||||
|
||||
ErrorManager EM;
|
||||
|
||||
void ErrorManager::add(Error error)
|
||||
{
|
||||
if (m_index < m_queue.size())
|
||||
m_queue[m_index++] = error;
|
||||
}
|
||||
|
||||
bool ErrorManager::assert(bool condition, Error error)
|
||||
{
|
||||
if (!condition)
|
||||
add(error);
|
||||
return condition;
|
||||
}
|
||||
|
||||
bool ErrorManager::hasError()
|
||||
{
|
||||
return m_index > 0;
|
||||
}
|
||||
|
||||
Error ErrorManager::pop()
|
||||
{
|
||||
return m_index == 0 ? Error::None : m_queue[--m_index];
|
||||
}
|
||||
|
@ -0,0 +1,138 @@
|
||||
#include "handlers.hpp"
|
||||
|
||||
#include "adc.hpp"
|
||||
#include "conversion.hpp"
|
||||
#include "cordic.hpp"
|
||||
#include "runstatus.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((naked))
|
||||
void port_syscall(struct port_extctx *ctxp, uint32_t n)
|
||||
{
|
||||
switch (n) {
|
||||
|
||||
// Sleeps the current thread until a message is received.
|
||||
// Used the algorithm runner to wait for new data.
|
||||
case 0:
|
||||
{
|
||||
chSysLock();
|
||||
chMsgWaitS();
|
||||
auto monitor = ConversionManager::getMonitorHandle();
|
||||
auto msg = chMsgGet(monitor);
|
||||
chMsgReleaseS(monitor, MSG_OK);
|
||||
chSysUnlock();
|
||||
ctxp->r0 = msg;
|
||||
}
|
||||
break;
|
||||
|
||||
// Provides access to advanced math functions.
|
||||
// A service call like this is required for some hardware targets that
|
||||
// provide hardware-accelerated math computations (e.g. CORDIC).
|
||||
case 1:
|
||||
{
|
||||
using mathcall = void (*)();
|
||||
static mathcall funcs[3] = {
|
||||
reinterpret_cast<mathcall>(cordic::sin),
|
||||
reinterpret_cast<mathcall>(cordic::cos),
|
||||
reinterpret_cast<mathcall>(cordic::tan),
|
||||
};
|
||||
#if defined(PLATFORM_H7)
|
||||
asm("vmov.f64 d0, %0, %1" :: "r" (ctxp->r1), "r" (ctxp->r2));
|
||||
if (ctxp->r0 < 3) {
|
||||
funcs[ctxp->r0]();
|
||||
asm("vmov.f64 %0, %1, d0" : "=r" (ctxp->r1), "=r" (ctxp->r2));
|
||||
} else {
|
||||
asm("eor r0, r0; vmov.f64 d0, r0, r0");
|
||||
}
|
||||
#else
|
||||
asm("vmov.f32 s0, %0" :: "r" (ctxp->r1));
|
||||
if (ctxp->r0 < 3) {
|
||||
funcs[ctxp->r0]();
|
||||
asm("vmov.f32 %0, s0" : "=r" (ctxp->r1));
|
||||
} else {
|
||||
asm("eor r0, r0; vmov.f32 s0, r0");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
// Starts or stops precise cycle time measurement.
|
||||
// Used to measure algorithm execution time.
|
||||
case 2:
|
||||
if (ctxp->r0 == 0) {
|
||||
chTMStartMeasurementX(&conversion_time_measurement);
|
||||
} else {
|
||||
chTMStopMeasurementX(&conversion_time_measurement);
|
||||
// Subtract measurement overhead from the result.
|
||||
// Running an empty algorithm ("bx lr") takes 196 cycles as of 2/4/21.
|
||||
// Only measures algorithm code time (loading args/storing result takes 9 cycles).
|
||||
constexpr rtcnt_t measurement_overhead = 196 - 1;
|
||||
if (conversion_time_measurement.last > measurement_overhead)
|
||||
conversion_time_measurement.last -= measurement_overhead;
|
||||
}
|
||||
break;
|
||||
|
||||
// Reads one of the analog inputs made available for algorithm run-time input.
|
||||
case 3:
|
||||
ctxp->r0 = ADC::readAlt(ctxp->r0);
|
||||
break;
|
||||
|
||||
//case 4:
|
||||
// {
|
||||
// const char *str = reinterpret_cast<const char *>(ctxp->r0);
|
||||
// auto src = str;
|
||||
// auto dst = userMessageBuffer;
|
||||
// while (*src)
|
||||
// *dst++ = *src++;
|
||||
// *dst = '\0';
|
||||
// userMessageSize = src - str;
|
||||
// }
|
||||
// break;
|
||||
default:
|
||||
while (1);
|
||||
break;
|
||||
}
|
||||
|
||||
asm("svc 0");
|
||||
while (1);
|
||||
}
|
||||
|
||||
__attribute__((naked))
|
||||
void MemManage_Handler()
|
||||
{
|
||||
// 1. Get the stack pointer.
|
||||
uint32_t lr;
|
||||
asm("mov %0, lr" : "=r" (lr));
|
||||
|
||||
// 2. Recover from the fault.
|
||||
ConversionManager::abort();
|
||||
|
||||
// 3. Return.
|
||||
asm("mov lr, %0; bx lr" :: "r" (lr));
|
||||
}
|
||||
|
||||
__attribute__((naked))
|
||||
void HardFault_Handler()
|
||||
{
|
||||
// Get the stack pointer.
|
||||
//uint32_t *stack;
|
||||
uint32_t lr;
|
||||
asm("mov %0, lr" : "=r" (lr));
|
||||
/*asm("\
|
||||
tst lr, #4; \
|
||||
ite eq; \
|
||||
mrseq %0, msp; \
|
||||
mrsne %0, psp; \
|
||||
mov %1, lr; \
|
||||
" : "=r" (stack), "=r" (lr));*/
|
||||
|
||||
// If coming from the algorithm, attempt to recover; otherwise, give up.
|
||||
if (run_status != RunStatus::Running && (lr & 4) != 0)
|
||||
MemManage_Handler();
|
||||
|
||||
while (1);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @file handlers.hpp
|
||||
* @brief Interrupt service routine handlers.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_HANDLERS_HPP
|
||||
#define STMDSP_HANDLERS_HPP
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((naked))
|
||||
void port_syscall(struct port_extctx *ctxp, uint32_t n);
|
||||
|
||||
__attribute__((naked))
|
||||
void MemManage_Handler();
|
||||
|
||||
__attribute__((naked))
|
||||
void HardFault_Handler();
|
||||
|
||||
}
|
||||
|
||||
#endif // STMDSP_HANDLERS_HPP
|
||||
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @file monitor.cpp
|
||||
* @brief Manages the device monitoring thread (status LEDs, etc.).
|
||||
*
|
||||
* 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 "monitor.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "runstatus.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
void Monitor::begin()
|
||||
{
|
||||
chThdCreateStatic(m_thread_stack.data(),
|
||||
m_thread_stack.size(),
|
||||
LOWPRIO,
|
||||
threadMonitor,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void Monitor::threadMonitor(void *)
|
||||
{
|
||||
palSetLineMode(LINE_BUTTON, PAL_MODE_INPUT_PULLUP);
|
||||
auto readButton = [] {
|
||||
#ifdef TARGET_PLATFORM_L4
|
||||
return !palReadLine(LINE_BUTTON);
|
||||
#else
|
||||
return palReadLine(LINE_BUTTON);
|
||||
#endif
|
||||
};
|
||||
|
||||
palClearLine(LINE_LED_RED);
|
||||
palClearLine(LINE_LED_YELLOW);
|
||||
|
||||
while (1) {
|
||||
bool isidle = run_status == RunStatus::Idle;
|
||||
auto led = isidle ? LINE_LED_GREEN : LINE_LED_YELLOW;
|
||||
auto delay = isidle ? 500 : 250;
|
||||
|
||||
palSetLine(led);
|
||||
chThdSleepMilliseconds(delay);
|
||||
palClearLine(led);
|
||||
chThdSleepMilliseconds(delay);
|
||||
|
||||
if (run_status == RunStatus::Idle && readButton()) {
|
||||
palSetLine(LINE_LED_RED);
|
||||
palSetLine(LINE_LED_YELLOW);
|
||||
chSysLock();
|
||||
while (readButton())
|
||||
asm("nop");
|
||||
while (!readButton())
|
||||
asm("nop");
|
||||
chSysUnlock();
|
||||
palClearLine(LINE_LED_RED);
|
||||
palClearLine(LINE_LED_YELLOW);
|
||||
chThdSleepMilliseconds(500);
|
||||
}
|
||||
|
||||
static bool erroron = false;
|
||||
if (auto err = EM.hasError(); err ^ erroron) {
|
||||
erroron = err;
|
||||
if (err)
|
||||
palSetLine(LINE_LED_RED);
|
||||
else
|
||||
palClearLine(LINE_LED_RED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((section(".stacks")))
|
||||
std::array<char, 256> Monitor::m_thread_stack = {};
|
||||
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @file monitor.hpp
|
||||
* @brief Manages the device monitoring thread (status LEDs, etc.).
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_MONITOR_HPP
|
||||
#define STMDSP_MONITOR_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
class Monitor
|
||||
{
|
||||
public:
|
||||
static void begin();
|
||||
|
||||
private:
|
||||
static void threadMonitor(void *);
|
||||
|
||||
static std::array<char, 256> m_thread_stack;
|
||||
};
|
||||
|
||||
#endif // STMDSP_MONITOR_HPP
|
||||
|
@ -0,0 +1,11 @@
|
||||
// Run status
|
||||
//
|
||||
enum class RunStatus : char
|
||||
{
|
||||
Idle = '1',
|
||||
Running,
|
||||
Recovering
|
||||
};
|
||||
|
||||
extern RunStatus run_status;
|
||||
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file samples.cpp
|
||||
* @brief Provides sample buffers for inputs and outputs.
|
||||
*
|
||||
* 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 "samples.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
static_assert(sizeof(adcsample_t) == sizeof(uint16_t));
|
||||
static_assert(sizeof(dacsample_t) == sizeof(uint16_t));
|
||||
|
||||
#if defined(TARGET_PLATFORM_H7)
|
||||
__attribute__((section(".convdata")))
|
||||
SampleBuffer Samples::In (reinterpret_cast<Sample *>(0x38000000)); // 16k
|
||||
__attribute__((section(".convdata")))
|
||||
SampleBuffer Samples::Out (reinterpret_cast<Sample *>(0x30004000)); // 16k
|
||||
SampleBuffer Samples::SigGen (reinterpret_cast<Sample *>(0x30000000)); // 16k
|
||||
#else
|
||||
__attribute__((section(".convdata")))
|
||||
SampleBuffer Samples::In (reinterpret_cast<Sample *>(0x20008000)); // 16k
|
||||
__attribute__((section(".convdata")))
|
||||
SampleBuffer Samples::Out (reinterpret_cast<Sample *>(0x2000C000)); // 16k
|
||||
SampleBuffer Samples::Generator (reinterpret_cast<Sample *>(0x20010000)); // 16k
|
||||
#endif
|
||||
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @file samples.hpp
|
||||
* @brief Provides sample buffers for inputs and outputs.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_SAMPLES_HPP
|
||||
#define STMDSP_SAMPLES_HPP
|
||||
|
||||
#include "samplebuffer.hpp"
|
||||
|
||||
class Samples
|
||||
{
|
||||
public:
|
||||
static SampleBuffer In;
|
||||
static SampleBuffer Out;
|
||||
static SampleBuffer Generator;
|
||||
};
|
||||
|
||||
#endif // STMDSP_SAMPLES_HPP
|
||||
|
Loading…
Reference in New Issue