commit 5bfce94bbc34cfd698ea18725123bda166a0af01 Author: Peter McGoron Date: Tue Aug 10 16:52:12 2021 -0400 init 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(); +}