/** * @file main.cpp * @brief Program entry point. * * Copyright (C) 2020 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 . */ #include "ch.h" #include "hal.h" static_assert(sizeof(adcsample_t) == sizeof(uint16_t)); static_assert(sizeof(dacsample_t) == sizeof(uint16_t)); #include "adc.hpp" #include "cordic.hpp" #include "dac.hpp" #include "elf_load.hpp" #include "error.hpp" #include "samplebuffer.hpp" #include "sclock.hpp" #include "usbserial.hpp" #include constexpr unsigned int MAX_ELF_FILE_SIZE = 8 * 1024; enum class RunStatus : char { Idle = '1', Running, Recovering }; static RunStatus run_status = RunStatus::Idle; #define MSG_CONVFIRST (1) #define MSG_CONVSECOND (2) #define MSG_CONVFIRST_MEASURE (3) #define MSG_CONVSECOND_MEASURE (4) #define MSG_FOR_FIRST(m) (m & 1) #define MSG_FOR_MEASURE(m) (m > 2) static ErrorManager EM; static msg_t conversionMBBuffer[2]; static MAILBOX_DECL(conversionMB, conversionMBBuffer, 2); // Thread for LED status and wakeup hold static THD_WORKING_AREA(monitorThreadWA, 128); static THD_FUNCTION(monitorThread, arg); // Thread for managing the conversion task static THD_WORKING_AREA(conversionThreadMonitorWA, 128); static THD_FUNCTION(conversionThreadMonitor, arg); // Thread for unprivileged algorithm execution static THD_WORKING_AREA(conversionThreadWA, 128); static THD_FUNCTION(conversionThread, arg); __attribute__((section(".convdata"))) static THD_WORKING_AREA(conversionThreadUPWA, 256); static thread_t *conversionThreadHandle = nullptr; __attribute__((section(".convdata"))) static thread_t *conversionThreadMonitorHandle = nullptr; __attribute__((section(".convdata"))) static time_measurement_t conversion_time_measurement; __attribute__((section(".convdata"))) static SampleBuffer samplesIn (reinterpret_cast(0x38000000)); // 16k __attribute__((section(".convdata"))) static SampleBuffer samplesOut (reinterpret_cast(0x30004000)); // 16k static SampleBuffer samplesSigGen (reinterpret_cast(0x30000000)); // 16k static unsigned char elf_file_store[MAX_ELF_FILE_SIZE]; __attribute__((section(".convdata"))) static ELF::Entry elf_entry = nullptr; static void signal_operate(adcsample_t *buffer, size_t count); static void signal_operate_measure(adcsample_t *buffer, size_t count); static void main_loop(); int main() { // Initialize the RTOS halInit(); chSysInit(); palSetLineMode(LINE_BUTTON, PAL_MODE_INPUT); // Enable FPU SCB->CPACR |= 0xF << 20; // Set up MPU for user algorithm // Region 2: Data for algorithm manager thread mpuConfigureRegion(MPU_REGION_2, 0x20000000, MPU_RASR_ATTR_AP_RW_RW | MPU_RASR_ATTR_NON_CACHEABLE | MPU_RASR_SIZE_4K | MPU_RASR_ENABLE); // Region 3: Code for algorithm manager thread mpuConfigureRegion(MPU_REGION_3, 0x08080000, MPU_RASR_ATTR_AP_RO_RO | MPU_RASR_ATTR_NON_CACHEABLE | MPU_RASR_SIZE_4K | MPU_RASR_ENABLE); // Region 4: Algorithm code mpuConfigureRegion(MPU_REGION_4, 0x00000000, MPU_RASR_ATTR_AP_RW_RW | MPU_RASR_ATTR_NON_CACHEABLE | MPU_RASR_SIZE_64K | MPU_RASR_ENABLE); ADC::begin(); DAC::begin(); SClock::begin(); USBSerial::begin(); math::init(); SClock::setRate(SClock::Rate::R32K); ADC::setRate(SClock::Rate::R32K); chTMObjectInit(&conversion_time_measurement); chThdCreateStatic(monitorThreadWA, sizeof(monitorThreadWA), NORMALPRIO, monitorThread, nullptr); conversionThreadMonitorHandle = chThdCreateStatic( conversionThreadMonitorWA, sizeof(conversionThreadMonitorWA), NORMALPRIO, conversionThreadMonitor, nullptr); conversionThreadHandle = chThdCreateStatic( conversionThreadWA, sizeof(conversionThreadWA), NORMALPRIO + 1, conversionThread, nullptr); main_loop(); } void main_loop() { 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 switch (cmd[0]) { case 'a': USBSerial::write(samplesIn.bytedata(), samplesIn.bytesize()); break; case 'A': USBSerial::read(samplesIn.bytedata(), samplesIn.bytesize()); break; case 'B': if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle) && EM.assert(USBSerial::read(&cmd[1], 2) == 2, Error::BadParamSize)) { unsigned int count = (cmd[1] | (cmd[2] << 8)) * 2; if (EM.assert(count <= MAX_SAMPLE_BUFFER_SIZE, Error::BadParam)) { samplesIn.setSize(count); samplesOut.setSize(count); } } break; case 'd': USBSerial::write(samplesOut.bytedata(), samplesOut.bytesize()); break; case 'D': 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)) { samplesSigGen.setSize(count); USBSerial::read(samplesSigGen.bytedata(), samplesSigGen.bytesize()); } } break; // 'E' - Reads in and loads the compiled conversion code binary from USB. case 'E': 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 < sizeof(elf_file_store), Error::BadUserCodeSize)) { USBSerial::read(elf_file_store, size); elf_entry = ELF::load(elf_file_store); EM.assert(elf_entry != nullptr, Error::BadUserCodeLoad); } } break; // 'e' - Unloads the currently loaded conversion code case 'e': elf_entry = nullptr; break; // 'i' - Sends an identifying string to confirm that this is the stmdsp device. case 'i': USBSerial::write(reinterpret_cast("stmdsp"), 6); break; // 'I' - Sends the current run status. case 'I': { unsigned char buf[2] = { static_cast(run_status), static_cast(EM.pop()) }; USBSerial::write(buf, sizeof(buf)); } break; // 'M' - Begins continuous sampling, but measures the execution time of the first // sample processing. This duration can be later read through 'm'. case 'M': if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle)) { run_status = RunStatus::Running; samplesOut.clear(); ADC::start(samplesIn.data(), samplesIn.size(), signal_operate_measure); DAC::start(0, samplesOut.data(), samplesOut.size()); } break; // 'm' - Returns the last measured sample processing time, presumably in processor // ticks. case 'm': USBSerial::write(reinterpret_cast(&conversion_time_measurement.last), sizeof(rtcnt_t)); break; // 'R' - Begin continuous sampling/conversion of the ADC. Samples will go through // the conversion code, and will be sent out over the DAC. case 'R': if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle)) { run_status = RunStatus::Running; samplesOut.clear(); ADC::start(samplesIn.data(), samplesIn.size(), signal_operate); DAC::start(0, samplesOut.data(), samplesOut.size()); } break; case 'r': 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(cmd[1]); SClock::setRate(r); ADC::setRate(r); } } break; // 'S' - Stops the continuous sampling/conversion. case 'S': if (run_status == RunStatus::Running) { DAC::stop(0); ADC::stop(); run_status = RunStatus::Idle; } break; case 's': if (auto samps = samplesOut.modified(); samps != nullptr) { unsigned char buf[2] = { static_cast(samplesOut.size() / 2 & 0xFF), static_cast(((samplesOut.size() / 2) >> 8) & 0xFF) }; USBSerial::write(buf, 2); unsigned int total = samplesOut.bytesize() / 2; unsigned int offset = 0; unsigned char unused; while (total > 512) { USBSerial::write(reinterpret_cast(samps) + offset, 512); while (USBSerial::read(&unused, 1) == 0); offset += 512; total -= 512; } USBSerial::write(reinterpret_cast(samps) + offset, total); while (USBSerial::read(&unused, 1) == 0); } else { USBSerial::write(reinterpret_cast("\0\0"), 2); } break; case 'W': DAC::start(1, samplesSigGen.data(), samplesSigGen.size()); break; case 'w': DAC::stop(1); break; default: break; } } } chThdSleepMicroseconds(100); } } void conversion_abort() { elf_entry = nullptr; DAC::stop(0); ADC::stop(); EM.add(Error::ConversionAborted); chMBReset(&conversionMB); run_status = RunStatus::Idle; } THD_FUNCTION(conversionThreadMonitor, arg) { (void)arg; while (1) { // Recover from algorithm fault if necessary //if (run_status == RunStatus::Recovering) // conversion_abort(); msg_t message; if (chMBFetchTimeout(&conversionMB, &message, TIME_INFINITE) == MSG_OK) chMsgSend(conversionThreadHandle, message); } } __attribute__((section(".convcode"))) static void convThdMain(); THD_FUNCTION(conversionThread, arg) { (void)arg; elf_entry = nullptr; port_unprivileged_jump(reinterpret_cast(convThdMain), reinterpret_cast(conversionThreadUPWA) + 256); } void convThdMain() { while (1) { msg_t message; asm("svc 0; mov %0, r0" : "=r" (message)); if (message != 0) { auto samples = MSG_FOR_FIRST(message) ? samplesIn.data() : samplesIn.middata(); auto size = samplesIn.size() / 2; if (elf_entry) { if (!MSG_FOR_MEASURE(message)) { samples = elf_entry(samples, size); } else { //chTMStartMeasurementX(&conversion_time_measurement); samples = elf_entry(samples, size); //chTMStopMeasurementX(&conversion_time_measurement); } } if (MSG_FOR_FIRST(message)) samplesOut.modify(samples, size); else samplesOut.midmodify(samples, size); } } } void signal_operate(adcsample_t *buffer, size_t) { chSysLockFromISR(); if (chMBGetUsedCountI(&conversionMB) > 1) { chSysUnlockFromISR(); conversion_abort(); } else { chMBPostI(&conversionMB, buffer == samplesIn.data() ? MSG_CONVFIRST : MSG_CONVSECOND); chSysUnlockFromISR(); } } void signal_operate_measure(adcsample_t *buffer, [[maybe_unused]] size_t count) { chSysLockFromISR(); chMBPostI(&conversionMB, buffer == samplesIn.data() ? MSG_CONVFIRST_MEASURE : MSG_CONVSECOND_MEASURE); chSysUnlockFromISR(); ADC::setOperation(signal_operate); } THD_FUNCTION(monitorThread, arg) { (void)arg; bool erroron = false; 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 && palReadLine(LINE_BUTTON)) { palSetLine(LINE_LED_RED); palSetLine(LINE_LED_YELLOW); asm("cpsid i"); while (!palReadLine(LINE_BUTTON)) asm("nop"); asm("cpsie i"); palClearLine(LINE_LED_RED); palClearLine(LINE_LED_YELLOW); chThdSleepMilliseconds(500); } if (auto err = EM.hasError(); err ^ erroron) { erroron = err; if (err) palSetLine(LINE_LED_RED); else palClearLine(LINE_LED_RED); } } } extern "C" { __attribute__((naked)) void port_syscall(struct port_extctx *ctxp, uint32_t n) { switch (n) { case 0: { chSysLock(); chMsgWaitS(); auto msg = chMsgGet(conversionThreadMonitorHandle); chMsgReleaseS(conversionThreadMonitorHandle, MSG_OK); //chSchDoYieldS(); chSysUnlock(); ctxp->r0 = msg; } break; default: while (1); break; } asm("svc 0"); while (1); } __attribute__((naked)) void HardFault_Handler() { // Below not working (yet) while (1); // 1. Get the stack pointer uint32_t *stack; uint32_t lr; asm("\ tst lr, #4; \ ite eq; \ mrseq %0, msp; \ mrsne %0, psp; \ mov %1, lr; \ " : "=r" (stack), "=r" (lr)); // 2. Only attempt to recover from failed algorithm code if ((lr & 4) == 0 || run_status != RunStatus::Running) while (1); // 3. Post the failure and unload algorithm elf_entry = nullptr; EM.add(Error::ConversionAborted); run_status = RunStatus::Recovering; // 4. Make this exception return to point after algorithm exec. stack[6] = stack[5]; stack[7] |= (1 << 24); // Ensure Thumb mode stays enabled asm("mov lr, %0; bx lr" :: "r" (lr)); } } // extern "C"