From 5bfce94bbc34cfd698ea18725123bda166a0af01 Mon Sep 17 00:00:00 2001 From: Peter McGoron Date: Tue, 10 Aug 2021 16:52:12 -0400 Subject: [PATCH] init --- COPYING.md | 124 ++++++++++++++++++ Dagon.ino | 51 ++++++++ DueTimer.cpp | 318 +++++++++++++++++++++++++++++++++++++++++++++++ DueTimer.h | 122 ++++++++++++++++++ README.md | 1 + comm.cpp | 164 ++++++++++++++++++++++++ dagon.h | 43 +++++++ libscomp_amalg.c | 110 ++++++++++++++++ libscomp_amalg.h | 65 ++++++++++ spi.cpp | 52 ++++++++ 10 files changed, 1050 insertions(+) create mode 100644 COPYING.md create mode 100644 Dagon.ino create mode 100644 DueTimer.cpp create mode 100644 DueTimer.h create mode 100644 README.md create mode 100644 comm.cpp create mode 100644 dagon.h create mode 100644 libscomp_amalg.c create mode 100644 libscomp_amalg.h create mode 100644 spi.cpp diff --git a/COPYING.md b/COPYING.md new file mode 100644 index 0000000..f0b8dee --- /dev/null +++ b/COPYING.md @@ -0,0 +1,124 @@ +# Creative Commons Legal Code --- CC0 1.0 Universal # + +**CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE +LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN +ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS +INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES +REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS +PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM +THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED +HEREUNDER.** + +## Statement of Purpose ## + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +### 1. Copyright and Related Rights. ### + +A Work made available under CC0 may be protected by copyright and related +or neighboring rights ("Copyright and Related Rights"). Copyright and +Related Rights include, but are not limited to, the following: + +1. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; +2. moral rights retained by the original author(s) and/or performer(s); +3. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; +4. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; +5. rights protecting the extraction, dissemination, use and reuse of data + in a Work; +6. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +7. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +### 2. Waiver. ### + +To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably +and unconditionally waives, abandons, and surrenders all of Affirmer's +Copyright and Related Rights and associated claims and causes of action, +whether now known or unknown (including existing as well as future claims +and causes of action), in the Work (i) in all territories worldwide, (ii) +for the maximum duration provided by applicable law or treaty (including +future time extensions), (iii) in any current or future medium and for +any number of copies, and (iv) for any purpose whatsoever, including +without limitation commercial, advertising or promotional purposes (the +"Waiver"). Affirmer makes the Waiver for the benefit of each member +of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal +or equitable action to disrupt the quiet enjoyment of the Work by the +public as contemplated by Affirmer's express Statement of Purpose. + +### 3. Public License Fallback. ### + +Should any part of the Waiver for any reason be judged legally +invalid or ineffective under applicable law, then the Waiver shall +be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent +the Waiver is so judged Affirmer hereby grants to each affected person +a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright +and Related Rights in the Work (i) in all territories worldwide, (ii) +for the maximum duration provided by applicable law or treaty (including +future time extensions), (iii) in any current or future medium and for +any number of copies, and (iv) for any purpose whatsoever, including +without limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, +such partial invalidity or ineffectiveness shall not invalidate the +remainder of the License, and in such case Affirmer hereby affirms that +he or she will not (i) exercise any of his or her remaining Copyright +and Related Rights in the Work or (ii) assert any associated claims and +causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +### 4. Limitations and Disclaimers. ### + +1. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. +2. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. +3. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. +4. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/Dagon.ino b/Dagon.ino new file mode 100644 index 0000000..b1f58fb --- /dev/null +++ b/Dagon.ino @@ -0,0 +1,51 @@ +#include "dagon.h" + +static struct libscomp_input in; +static struct libscomp_cmd cmdarr[] = { + {"CON", dagon_con}, + {"IDN", dagon_idn}, + {"RMP", dagon_rmp}, + {"RST", dagon_rst}, + {"SET", dagon_set}, + {"VLT", dagon_vlt} +}; + +static struct libscomp_cmd_store store = { + cmdarr, sizeof cmdarr / sizeof cmdarr[0] +}; + +void setup() { + Serial.begin(115200); + while (!Serial); + spi_init(); + libscomp_reset(&in); + Serial.setTimeout(10); +} + +void loop() { + static char buf[4096]; + + struct libscomp_line l; + char *p = buf; + enum libscomp_input_r r; + + size_t vl = Serial.readBytes(buf, sizeof buf - 1); + buf[vl] = 0; + + do { + r = libscomp_read(&in, &p, &l); + switch (r) { + case LIBSCOMP_OVERFLOW: + serial_printf("ERROR\toverflow\n"); + break; + case LIBSCOMP_ARG_OVERFLOW: + serial_printf("ERROR\targument overflow\n"); + break; + case LIBSCOMP_COMPLETE: + if (libscomp_exec(&store, &l, nullptr) == LIBSCOMP_NOT_FOUND) + print_start(&l, "ERROR\tno known command\n"); + break; + // LIBSCOMP_MORE left out + } + } while (r != LIBSCOMP_MORE); +} diff --git a/DueTimer.cpp b/DueTimer.cpp new file mode 100644 index 0000000..1eacd72 --- /dev/null +++ b/DueTimer.cpp @@ -0,0 +1,318 @@ +/* + DueTimer.cpp - Implementation of Timers defined on DueTimer.h + For instructions, go to https://github.com/ivanseidel/DueTimer + + Created by Ivan Seidel Gomes, March, 2013. + Modified by Philipp Klaus, June 2013. + Thanks to stimmer (from Arduino forum), for coding the "timer soul" (Register stuff) + Released into the public domain. +*/ + +#include "DueTimer.h" + +const DueTimer::Timer DueTimer::Timers[NUM_TIMERS] = { + {TC0,0,TC0_IRQn}, + {TC0,1,TC1_IRQn}, + {TC0,2,TC2_IRQn}, + {TC1,0,TC3_IRQn}, + {TC1,1,TC4_IRQn}, + {TC1,2,TC5_IRQn}, +#if NUM_TIMERS > 6 + {TC2,0,TC6_IRQn}, + {TC2,1,TC7_IRQn}, + {TC2,2,TC8_IRQn}, +#endif +}; + +// Fix for compatibility with Servo library +#ifdef USING_SERVO_LIB + // Set callbacks as used, allowing DueTimer::getAvailable() to work + void (*DueTimer::callbacks[NUM_TIMERS])() = { + (void (*)()) 1, // Timer 0 - Occupied + (void (*)()) 0, // Timer 1 + (void (*)()) 1, // Timer 2 - Occupied + (void (*)()) 1, // Timer 3 - Occupied + (void (*)()) 1, // Timer 4 - Occupied + (void (*)()) 1, // Timer 5 - Occupied +#if NUM_TIMERS > 6 + (void (*)()) 0, // Timer 6 + (void (*)()) 0, // Timer 7 + (void (*)()) 0 // Timer 8 +#endif + }; +#else + void (*DueTimer::callbacks[NUM_TIMERS])() = {}; +#endif + +#if NUM_TIMERS > 6 +double DueTimer::_frequency[NUM_TIMERS] = {-1,-1,-1,-1,-1,-1,-1,-1,-1}; +#else +double DueTimer::_frequency[NUM_TIMERS] = {-1,-1,-1,-1,-1,-1}; +#endif + +/* + Initializing all timers, so you can use them like this: Timer0.start(); +*/ +DueTimer Timer(0); + +DueTimer Timer1(1); +// Fix for compatibility with Servo library +#ifndef USING_SERVO_LIB + DueTimer Timer0(0); + DueTimer Timer2(2); + DueTimer Timer3(3); + DueTimer Timer4(4); + DueTimer Timer5(5); +#endif +#if NUM_TIMERS > 6 +DueTimer Timer6(6); +DueTimer Timer7(7); +DueTimer Timer8(8); +#endif + +DueTimer::DueTimer(unsigned short _timer) : timer(_timer){ + /* + The constructor of the class DueTimer + */ +} + +DueTimer DueTimer::getAvailable(void){ + /* + Return the first timer with no callback set + */ + + for(int i = 0; i < NUM_TIMERS; i++){ + if(!callbacks[i]) + return DueTimer(i); + } + // Default, return Timer0; + return DueTimer(0); +} + +DueTimer& DueTimer::attachInterrupt(void (*isr)()){ + /* + Links the function passed as argument to the timer of the object + */ + + callbacks[timer] = isr; + + return *this; +} + +DueTimer& DueTimer::detachInterrupt(void){ + /* + Links the function passed as argument to the timer of the object + */ + + stop(); // Stop the currently running timer + + callbacks[timer] = NULL; + + return *this; +} + +DueTimer& DueTimer::start(double microseconds){ + /* + Start the timer + If a period is set, then sets the period and start the timer + */ + + if(microseconds > 0) + setPeriod(microseconds); + + if(_frequency[timer] <= 0) + setFrequency(1); + + NVIC_ClearPendingIRQ(Timers[timer].irq); + NVIC_EnableIRQ(Timers[timer].irq); + + TC_Start(Timers[timer].tc, Timers[timer].channel); + + return *this; +} + +DueTimer& DueTimer::stop(void){ + /* + Stop the timer + */ + + NVIC_DisableIRQ(Timers[timer].irq); + + TC_Stop(Timers[timer].tc, Timers[timer].channel); + + return *this; +} + +uint8_t DueTimer::bestClock(double frequency, uint32_t& retRC){ + /* + Pick the best Clock, thanks to Ogle Basil Hall! + + Timer Definition + TIMER_CLOCK1 MCK / 2 + TIMER_CLOCK2 MCK / 8 + TIMER_CLOCK3 MCK / 32 + TIMER_CLOCK4 MCK /128 + */ + const struct { + uint8_t flag; + uint8_t divisor; + } clockConfig[] = { + { TC_CMR_TCCLKS_TIMER_CLOCK1, 2 }, + { TC_CMR_TCCLKS_TIMER_CLOCK2, 8 }, + { TC_CMR_TCCLKS_TIMER_CLOCK3, 32 }, + { TC_CMR_TCCLKS_TIMER_CLOCK4, 128 } + }; + float ticks; + float error; + int clkId = 3; + int bestClock = 3; + float bestError = 9.999e99; + do + { + ticks = (float) SystemCoreClock / frequency / (float) clockConfig[clkId].divisor; + // error = abs(ticks - round(ticks)); + error = clockConfig[clkId].divisor * abs(ticks - round(ticks)); // Error comparison needs scaling + if (error < bestError) + { + bestClock = clkId; + bestError = error; + } + } while (clkId-- > 0); + ticks = (float) SystemCoreClock / frequency / (float) clockConfig[bestClock].divisor; + retRC = (uint32_t) round(ticks); + return clockConfig[bestClock].flag; +} + + +DueTimer& DueTimer::setFrequency(double frequency){ + /* + Set the timer frequency (in Hz) + */ + + // Prevent negative frequencies + if(frequency <= 0) { frequency = 1; } + + // Remember the frequency — see below how the exact frequency is reported instead + //_frequency[timer] = frequency; + + // Get current timer configuration + Timer t = Timers[timer]; + + uint32_t rc = 0; + uint8_t clock; + + // Tell the Power Management Controller to disable + // the write protection of the (Timer/Counter) registers: + pmc_set_writeprotect(false); + + // Enable clock for the timer + pmc_enable_periph_clk((uint32_t)t.irq); + + // Find the best clock for the wanted frequency + clock = bestClock(frequency, rc); + + switch (clock) { + case TC_CMR_TCCLKS_TIMER_CLOCK1: + _frequency[timer] = (double)SystemCoreClock / 2.0 / (double)rc; + break; + case TC_CMR_TCCLKS_TIMER_CLOCK2: + _frequency[timer] = (double)SystemCoreClock / 8.0 / (double)rc; + break; + case TC_CMR_TCCLKS_TIMER_CLOCK3: + _frequency[timer] = (double)SystemCoreClock / 32.0 / (double)rc; + break; + default: // TC_CMR_TCCLKS_TIMER_CLOCK4 + _frequency[timer] = (double)SystemCoreClock / 128.0 / (double)rc; + break; + } + + // Set up the Timer in waveform mode which creates a PWM + // in UP mode with automatic trigger on RC Compare + // and sets it up with the determined internal clock as clock input. + TC_Configure(t.tc, t.channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | clock); + // Reset counter and fire interrupt when RC value is matched: + TC_SetRC(t.tc, t.channel, rc); + // Enable the RC Compare Interrupt... + t.tc->TC_CHANNEL[t.channel].TC_IER=TC_IER_CPCS; + // ... and disable all others. + t.tc->TC_CHANNEL[t.channel].TC_IDR=~TC_IER_CPCS; + + return *this; +} + +DueTimer& DueTimer::setPeriod(double microseconds){ + /* + Set the period of the timer (in microseconds) + */ + + // Convert period in microseconds to frequency in Hz + double frequency = 1000000.0 / microseconds; + setFrequency(frequency); + return *this; +} + +double DueTimer::getFrequency(void) const { + /* + Get current time frequency + */ + + return _frequency[timer]; +} + +double DueTimer::getPeriod(void) const { + /* + Get current time period + */ + + return 1.0/getFrequency()*1000000; +} + + +/* + Implementation of the timer callbacks defined in + arduino-1.5.2/hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/sam3x8e.h +*/ +// Fix for compatibility with Servo library +#ifndef USING_SERVO_LIB +void TC0_Handler(void){ + TC_GetStatus(TC0, 0); + DueTimer::callbacks[0](); +} +#endif +void TC1_Handler(void){ + TC_GetStatus(TC0, 1); + DueTimer::callbacks[1](); +} +// Fix for compatibility with Servo library +#ifndef USING_SERVO_LIB +void TC2_Handler(void){ + TC_GetStatus(TC0, 2); + DueTimer::callbacks[2](); +} +void TC3_Handler(void){ + TC_GetStatus(TC1, 0); + DueTimer::callbacks[3](); +} +void TC4_Handler(void){ + TC_GetStatus(TC1, 1); + DueTimer::callbacks[4](); +} +void TC5_Handler(void){ + TC_GetStatus(TC1, 2); + DueTimer::callbacks[5](); +} +#endif +#if NUM_TIMERS > 6 +void TC6_Handler(void){ + TC_GetStatus(TC2, 0); + DueTimer::callbacks[6](); +} +void TC7_Handler(void){ + TC_GetStatus(TC2, 1); + DueTimer::callbacks[7](); +} +void TC8_Handler(void){ + TC_GetStatus(TC2, 2); + DueTimer::callbacks[8](); +} +#endif diff --git a/DueTimer.h b/DueTimer.h new file mode 100644 index 0000000..f0b54e6 --- /dev/null +++ b/DueTimer.h @@ -0,0 +1,122 @@ +/* + DueTimer.h - DueTimer header file, definition of methods and attributes... + For instructions, go to https://github.com/ivanseidel/DueTimer + + Created by Ivan Seidel Gomes, March, 2013. + Modified by Philipp Klaus, June 2013. + Released into the public domain. +*/ + +#include "Arduino.h" + +#if defined(_SAM3XA_) + +#ifndef DueTimer_h +#define DueTimer_h + +#include + +/* + This fixes compatibility for Arduono Servo Library. + Uncomment to make it compatible. + + Note that: + + Timers: 0,2,3,4,5 WILL NOT WORK, and will + neither be accessible by Timer0,... +*/ +// #define USING_SERVO_LIB true + +#ifdef USING_SERVO_LIB + #warning "HEY! You have set flag USING_SERVO_LIB. Timer0, 2,3,4 and 5 are not available" +#endif + + +#if defined TC2 +#define NUM_TIMERS 9 +#else +#define NUM_TIMERS 6 +#endif + +class DueTimer +{ +protected: + + // Represents the timer id (index for the array of Timer structs) + const unsigned short timer; + + // Stores the object timer frequency + // (allows to access current timer period and frequency): + static double _frequency[NUM_TIMERS]; + + // Picks the best clock to lower the error + static uint8_t bestClock(double frequency, uint32_t& retRC); + + // Make Interrupt handlers friends, so they can use callbacks + friend void TC0_Handler(void); + friend void TC1_Handler(void); + friend void TC2_Handler(void); + friend void TC3_Handler(void); + friend void TC4_Handler(void); + friend void TC5_Handler(void); +#if NUM_TIMERS > 6 + friend void TC6_Handler(void); + friend void TC7_Handler(void); + friend void TC8_Handler(void); +#endif + + static void (*callbacks[NUM_TIMERS])(); + + struct Timer + { + Tc *tc; + uint32_t channel; + IRQn_Type irq; + }; + + // Store timer configuration (static, as it's fixed for every object) + static const Timer Timers[NUM_TIMERS]; + +public: + + static DueTimer getAvailable(void); + + DueTimer(unsigned short _timer); + DueTimer& attachInterrupt(void (*isr)()); + DueTimer& detachInterrupt(void); + DueTimer& start(double microseconds = -1); + DueTimer& stop(void); + DueTimer& setFrequency(double frequency); + DueTimer& setPeriod(double microseconds); + + double getFrequency(void) const; + double getPeriod(void) const; + + inline __attribute__((always_inline)) bool operator== (const DueTimer& rhs) const + {return timer == rhs.timer; }; + inline __attribute__((always_inline)) bool operator!= (const DueTimer& rhs) const + {return timer != rhs.timer; }; +}; + +// Just to call Timer.getAvailable instead of Timer::getAvailable() : +extern DueTimer Timer; + +extern DueTimer Timer1; +// Fix for compatibility with Servo library +#ifndef USING_SERVO_LIB + extern DueTimer Timer0; + extern DueTimer Timer2; + extern DueTimer Timer3; + extern DueTimer Timer4; + extern DueTimer Timer5; +#endif +#if NUM_TIMERS > 6 +extern DueTimer Timer6; +extern DueTimer Timer7; +extern DueTimer Timer8; +#endif + +#endif + +#else + #error Oops! Trying to include DueTimer on another device? +#endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..75b836a --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Dagon is Arduino Due firmware for controlling a four-channel DAC. \ No newline at end of file diff --git a/comm.cpp b/comm.cpp new file mode 100644 index 0000000..98bcc85 --- /dev/null +++ b/comm.cpp @@ -0,0 +1,164 @@ +#include +#include +#include +#include "DueTimer.h" +#include "dagon.h" + +bool serial_vprintf(const char *fmt, va_list va) { + char buf[4096]; + + if (vsnprintf(buf, sizeof buf, fmt, va) >= sizeof buf) + return false; + Serial.print(buf); + return true; +} + +bool serial_printf(const char *fmt, ...) { + va_list va; + + va_start(va, fmt); + bool r = serial_vprintf(fmt, va); + va_end(va); + return r; +} + +bool print_start(struct libscomp_line *l, const char *fmt, ...) { + va_list va; + va_start(va, fmt); + + if (l->name) { + Serial.print(":"); + Serial.print(l->buf[0]); + Serial.print("\t"); + } + + bool b = serial_vprintf(fmt, va); + va_end(va); + return b; +} + +static long pstr(const char *s, long max) { + long v; + char *p; + + v = strtol(s, &p, 0); + if (*p || v < 0 || v >= max) + return -1; + return v; +} +#define inl __attribute__((always_inline)) + \ +#define mkhandler(nam, cond) static int nam(struct libscomp_line *) inl; \ +int dagon_##nam(struct libscomp_line *l, void *p unused) { \ + if (!([](size_t len) { return (cond); })(l->len - l->name - 1)) \ + return 1; \ + return nam(l); \ +} static int nam(struct libscomp_line *l) + +volatile struct dagon dg; + +mkhandler(idn, len == 0) { + print_start(l, "IDN\tDagon\t0\n"); + return LIBSCOMP_CMD_OK; +} + +#define V "%" PRIu32 +mkhandler(vlt, len == 0) { + print_start(l, "VLT\t" V "\t" V "\t" V "\t" V "\t", + dg.ch[0].volt, dg.ch[1].volt, dg.ch[2].volt, + dg.ch[3].volt); + return LIBSCOMP_CMD_OK; +} +#undef V + +// GNU extension (statement expression) +#define PARSE_INT(v, m, typ) ({ \ + long parse__v = pstr(v, m); \ + if (parse__v < 0) { \ + print_start(l, "ERROR\t%s invalid %s\n", v, typ); \ + return 1; \ + } \ + parse__v; \ +}) + +/* [:??] SET CH V */ +mkhandler(set, len == 2) { + long ch, v; + char *s; + const size_t arg1 = l->name + 1; + + ch = PARSE_INT(l->buf[arg1], DACS, "Channel"); + v = PARSE_INT(l->buf[arg1 + 1], V_MAX + 1, "Voltage"); + + dg.ch[ch].volt = v; + print_start(l, "SET\n"); + spi_set(v, ch); + return LIBSCOMP_CMD_OK; +} + +static DueTimer timers[DACS] = {Timer0,Timer1,Timer2,Timer3}; + +template +static void irq() { + uint32_t n = dg.ch[CH].volt + dg.ch[CH].step; + + if (n <= dg.ch[CH].volt || n >= dg.ch[CH].end) { + n = dg.ch[CH].end; + timers[CH].stop().detachInterrupt(); + } + dg.ch[CH].volt = n; + spi_set(n, CH); +} + +static void (*const irqs[DACS])() = { + irq<0>, irq<1>, irq<2>, irq<3> +}; + +/* [:??] RMP CH END START INTERVAL STEP [...] */ + +mkhandler(rmp, len % 5 == 0 && len >= 5) { + long ch, start, end, inter, step; + size_t arg = l->name + 1; + + while (arg < l->len) { + ch = PARSE_INT(l->buf[arg], DACS, "Channel"); + if (dg.ch[ch].used) { + print_start(l, "ERROR\tChannel %ld used\n", ch); + return 1; + } + end = PARSE_INT(l->buf[arg + 1], V_MAX + 1, "End voltage"); + start = PARSE_INT(l->buf[arg + 2], end, "Start voltage"); + inter = PARSE_INT(l->buf[arg + 3], LONG_MAX, "Time"); + if (inter == 0) { + print_start(l, "ERROR\tZero interval\n"); + return 1; + } + step = PARSE_INT(l->buf[arg + 4], V_MAX + 1, "Step voltage"); + if (step == 0) { + print_start(l, "ERROR\tZero step\n"); + return 1; + } + arg += 5; + dg.ch[ch].used = true; + dg.ch[ch].end = end; + dg.ch[ch].step = step; + + spi_write(start, ch); + timers[ch].attachInterrupt(irqs[ch]).start(inter); + } + return LIBSCOMP_CMD_OK; +} + + +#define b2s(b) ((b) ? "true" : "false") +mkhandler(con, len == 0) { + print_start(l, "CON\t%s\t%s\t%s\t%s\n", + b2s(dg.conn[0]), b2s(dg.conn[1]), + b2s(dg.conn[2]), b2s(dg.conn[3]) + ); + return LIBSCOMP_CMD_OK; +} + +mkhandler(rst, len == 0) { + spi_reset(); +} diff --git a/dagon.h b/dagon.h new file mode 100644 index 0000000..e72dd1e --- /dev/null +++ b/dagon.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include "libscomp_amalg.h" +#define DACS 4 +#define V_MAX 0x3FFFF +#define V0 0x1FFFF + +struct dagon_ch { + bool used; + uint32_t volt; + uint32_t end, step; +}; + +struct dagon { + bool conn[DACS]; + struct dagon_ch ch[DACS]; +}; + +extern volatile struct dagon dg; + +bool serial_vprintf(const char *fmt, va_list va); +bool serial_printf(const char *fmt, ...); +bool print_start(struct libscomp_line *l, const char *fmt, ...); + +#define unused __attribute__((unused)) +#define declhandler(nm) \ + int dagon_##nm(struct libscomp_line *, void *p unused) + +declhandler(con); +declhandler(idn); +declhandler(rmp); +declhandler(rst); +declhandler(set); +declhandler(vlt); + +uint32_t spi_write(uint32_t v, size_t dac); +uint32_t spi_transfer(uint32_t v, size_t dac); +void spi_init(); +void spi_reset(); + +#define spi_register(v) ((v) << 20) +#define spi_set(v,d) spi_write(((v) & 0x3FFFF) << 2 | spi_register(1), d) +#define spi_get(d) spi_transfer(spi_register(0b1001) << 20, d) diff --git a/libscomp_amalg.c b/libscomp_amalg.c new file mode 100644 index 0000000..113fe11 --- /dev/null +++ b/libscomp_amalg.c @@ -0,0 +1,110 @@ +#include "libscomp_amalg.h" + + +#ifdef LIBSCOMP_FREESTANDING +int strcmp(const char *s1, const char *s2) { + while (*s1 == *s2) { + if (!*s1) + break; + s1++; + s2++; + } + + return *s1 - *s2; +} +#else +# include +#endif + +struct libscomp_cmd *hg_bsearch(const char *name, + const struct libscomp_cmd_store *cmds) { + size_t lower = 0, upper = cmds->len-1; + size_t i; + int rel; + + while (upper >= lower) { + i = (upper - lower)/2 + lower; + rel = strcmp(name, cmds->arr[i].name); + /* x > cmds[i] */ + if (rel > 0) + lower = i + 1; + /* x < cmds[i] */ + else if (rel < 0) + upper = i - 1; + else + return &cmds->arr[i]; + } + return NULL; +} + +int libscomp_exec(const struct libscomp_cmd_store *cmds, + struct libscomp_line *ln, void *ptr) { + struct libscomp_cmd *cmd; + + cmd = hg_bsearch(ln->buf[ln->name], cmds); + if (!cmd) + return LIBSCOMP_NOT_FOUND; + return cmd->exec(ln,ptr); +} + + +enum { LIBSCOMP_READ, LIBSCOMP_DISCARD }; + +void libscomp_reset(struct libscomp_input *in) { + in->state = LIBSCOMP_READ; + in->len = 0; +} + +static enum libscomp_input_r libscomp_parse( + struct libscomp_input *in, struct libscomp_line *line) { + char *s = in->intbuf; + enum libscomp_input_r r = LIBSCOMP_ARG_OVERFLOW; + + line->name = (*s == ':'); + line->len = 0; + + while (line->len < LIBSCOMP_MAXARG) { + line->buf[line->len++] = s; + for (; *s && *s != '\t'; s++); + + if (!*s) { + r = LIBSCOMP_COMPLETE; + break; + } else { + *s = 0; + s++; + } + } + + libscomp_reset(in); + return r; +} + +enum libscomp_input_r libscomp_read(struct libscomp_input *in, + char **s, + struct libscomp_line *line) { + char c; + + while ((c = **s)) { + (*s)++; + + if (in->state == LIBSCOMP_DISCARD) { + if (c == '\n') { + libscomp_reset(in); + return LIBSCOMP_OVERFLOW; + } + } else { + switch (c) { + case '\n': + in->intbuf[in->len] = 0; + return libscomp_parse(in, line); + default: + in->intbuf[in->len++] = c; + if (in->len == LIBSCOMP_MAXBUF) + in->state = LIBSCOMP_DISCARD; + } + } + } + + return LIBSCOMP_MORE; +} diff --git a/libscomp_amalg.h b/libscomp_amalg.h new file mode 100644 index 0000000..635fd24 --- /dev/null +++ b/libscomp_amalg.h @@ -0,0 +1,65 @@ +#pragma once +#ifdef __cplusplus +extern "C" { +#endif +#define LIBSCOMP_VERSION "0.1.0" + +#include + +#define LIBSCOMP_MAXARG 32 +#define LIBSCOMP_MAXBUF 1024 + +struct libscomp_line { + size_t name; + char *buf[LIBSCOMP_MAXARG]; + size_t len; +}; + +struct libscomp_input { + int state; + + char intbuf[LIBSCOMP_MAXBUF]; + size_t len; +}; + +enum libscomp_input_r { + LIBSCOMP_MORE, + LIBSCOMP_OVERFLOW, + LIBSCOMP_ARG_OVERFLOW, + LIBSCOMP_COMPLETE +}; + +void libscomp_reset(struct libscomp_input *); +enum libscomp_input_r libscomp_read(struct libscomp_input *, + char **, + struct libscomp_line *); + + + +enum libscomp_cmd_r { + LIBSCOMP_NOT_FOUND = -1, + LIBSCOMP_CMD_OK = 0 +}; + +struct libscomp_cmd { + const char *name; + int (*exec)(struct libscomp_line *, void *); +}; + +struct libscomp_cmd_store { + struct libscomp_cmd *arr; + size_t len; +}; + +#if defined(__STDC_VERSION__) +#define libscomp_mkcmds(...) {(struct libscomp_cmd []){__VA_ARGS__}, \ + sizeof((struct libscomp_cmd []){__VA_ARGS__}) / \ + sizeof((struct libscomp_cmd []){__VA_ARGS__}[0])} +#endif +int libscomp_exec(const struct libscomp_cmd_store *cmds, + struct libscomp_line *ln, + void *ptr +); +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/spi.cpp b/spi.cpp new file mode 100644 index 0000000..af706e6 --- /dev/null +++ b/spi.cpp @@ -0,0 +1,52 @@ +#include +#include "dagon.h" + +static const uint32_t ctrlmsg = 0b10010 | (0b0010 << 20); +static const uint32_t readbit = 1 << 23; +static const int DACSS[DACS] = {4, 10, 12, 52}; + +uint32_t spi_write(uint32_t v, size_t dac) { + uint32_t r; + + digitalWrite(DACSS[dac], LOW); + r = SPI.transfer(v >> 16 & 0xFF) << 16; + r |= SPI.transfer(v >> 8 & 0xFF) << 8; + r |= SPI.transfer(v & 0xFF); + digitalWrite(DACSS[dac], HIGH); + return v; +} + +uint32_t spi_transfer(uint32_t v, size_t dac) { + spi_write(v, dac); + delayMicroseconds(1); + return spi_write(0, dac); +} + +void spi_init() { + SPI.begin(); + SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE1)); + + for (size_t i = 0; i < DACS; i++) { + dg.conn[i] = false; + dg.ch[i].used = false; + dg.ch[i].volt = V0; + for (size_t attempts = 0; attempts < 5; attempts++) { + uint32_t c; + + pinMode(DACSS[i], OUTPUT); + digitalWrite(DACSS[i], HIGH); + spi_write(ctrlmsg, i); + c = spi_transfer(ctrlmsg | readbit, i); + if (ctrlmsg & 0xFF == c & 0xFF) { + dg.conn[i] = true; + continue; + } + } + } +} + +void spi_reset() { + for (size_t i = 0; i < DACS; i++) + spi_write(i, 0b0100 | spi_register(0b0100)); + spi_init(); +}