From 31bb2f02aad88ce0c563c4c8e4328c66387f6638 Mon Sep 17 00:00:00 2001 From: Jake <jake.read@cba.mit.edu> Date: Wed, 15 Jun 2022 09:58:58 -0400 Subject: [PATCH] squish sub --- .../src/osape-d21/osape/osap/endpoint.cpp | 253 +++++++++ .../src/osape-d21/osape/osap/endpoint.h | 79 +++ .../src/osape-d21/osape/osap/osap.cpp | 96 ++++ .../fab-step/src/osape-d21/osape/osap/osap.h | 45 ++ .../src/osape-d21/osape/osap/osapLoop.cpp | 304 +++++++++++ .../src/osape-d21/osape/osap/osapLoop.h | 24 + .../src/osape-d21/osape/osap/osapUtils.cpp | 124 +++++ .../src/osape-d21/osape/osap/osapUtils.h | 23 + .../fab-step/src/osape-d21/osape/osap/ts.cpp | 86 ++++ .../fab-step/src/osape-d21/osape/osap/ts.h | 127 +++++ .../src/osape-d21/osape/osap/vertex.cpp | 128 +++++ .../src/osape-d21/osape/osap/vertex.h | 92 ++++ .../src/osape-d21/osape/utils/cobs.cpp | 70 +++ .../fab-step/src/osape-d21/osape/utils/cobs.h | 24 + .../fab-step/src/osape-d21/peripheralNums.h | 18 + .../osape-d21/vertices/ucbus/ucBusDrop.cpp | 484 ++++++++++++++++++ .../src/osape-d21/vertices/ucbus/ucBusDrop.h | 47 ++ .../osape-d21/vertices/ucbus/ucBusHead.cpp | 375 ++++++++++++++ .../src/osape-d21/vertices/ucbus/ucBusHead.h | 44 ++ .../osape-d21/vertices/ucbus/ucBusMacros.h | 125 +++++ .../vertices/ucbus/ucbusDipConfig.cpp | 61 +++ .../osape-d21/vertices/ucbus/ucbusDipConfig.h | 36 ++ .../osape-d21/vertices/ucbus/vt_ucBusDrop.cpp | 111 ++++ .../osape-d21/vertices/ucbus/vt_ucBusDrop.h | 33 ++ .../osape-d21/vertices/ucbus/vt_ucBusHead.cpp | 82 +++ .../osape-d21/vertices/ucbus/vt_ucBusHead.h | 33 ++ .../src/osape-d21/vertices/vertexConfig.h | 10 + .../src/osape-d21/vertices/vt_usbSerial.cpp | 112 ++++ .../src/osape-d21/vertices/vt_usbSerial.h | 37 ++ 29 files changed, 3083 insertions(+) create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/endpoint.cpp create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/endpoint.h create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/osap.cpp create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/osap.h create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/osapLoop.cpp create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/osapLoop.h create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/osapUtils.cpp create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/osapUtils.h create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/ts.cpp create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/ts.h create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/vertex.cpp create mode 100644 firmware/fab-step/src/osape-d21/osape/osap/vertex.h create mode 100644 firmware/fab-step/src/osape-d21/osape/utils/cobs.cpp create mode 100644 firmware/fab-step/src/osape-d21/osape/utils/cobs.h create mode 100644 firmware/fab-step/src/osape-d21/peripheralNums.h create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusDrop.cpp create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusDrop.h create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusHead.cpp create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusHead.h create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusMacros.h create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/ucbusDipConfig.cpp create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/ucbusDipConfig.h create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusDrop.cpp create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusDrop.h create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusHead.cpp create mode 100644 firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusHead.h create mode 100644 firmware/fab-step/src/osape-d21/vertices/vertexConfig.h create mode 100644 firmware/fab-step/src/osape-d21/vertices/vt_usbSerial.cpp create mode 100644 firmware/fab-step/src/osape-d21/vertices/vt_usbSerial.h diff --git a/firmware/fab-step/src/osape-d21/osape/osap/endpoint.cpp b/firmware/fab-step/src/osape-d21/osape/osap/endpoint.cpp new file mode 100644 index 0000000..d713219 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/endpoint.cpp @@ -0,0 +1,253 @@ +/* +osap/endpoint.cpp + +network : software interface + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2021 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#include "endpoint.h" +#include "osapUtils.h" +#include "../../../indicators.h" // indicators are circuit specific, should live 3 levels up +#include "../../../syserror.h" // syserror is also circuit specific, ibid + +uint8_t ack[VT_SLOTSIZE]; + +// handle, return true to clear out. false to wait one turn +// item->data[ptr] == PK_DEST here +// item->data[ptr + 1, ptr + 2] = segsize ! +EP_ONDATA_RESPONSES endpointHandler(endpoint_t* ep, uint8_t od, stackItem* item, uint16_t ptr){ + ptr += 3; + switch(item->data[ptr]){ + case EP_SS_ACKLESS: // ah right, these were 'single segment' msgs... yikes + { + // endpoints can REJECT, ACCEPT or ask for a WAIT for new data... + // with an ackless write, WAIT is risky... but here we are + // NOTE: previous code had *ackless* using 'ptr + 1' offsets, but should be the same + // as *acked* code, which uses 'ptr + 2' offset ... swapped back here 2021-07-07, ? + // was rarely using ackless... so I presume this is it + EP_ONDATA_RESPONSES resp = ep->onData(&(item->data[ptr + 2]), item->len - ptr - 2); + switch(resp){ + case EP_ONDATA_REJECT: + // nothing to do: msg will be deleted one level up + break; + case EP_ONDATA_ACCEPT: + // data OK, copy it in: + memcpy(ep->data, &(item->data[ptr + 2]), item->len - ptr - 2); + ep->dataLen = item->len - ptr - 2; + break; + case EP_ONDATA_WAIT: + // nothing to do, msg will be awaited one level up + break; + } // end resp switch + return resp; // return the response one level up... + } // end ackless case closure + break; + case EP_SS_ACKED: + { + // if there's not any space for an ack, we won't be able to ack, ask to wait + if(!stackEmptySlot(ep->vt, VT_STACK_ORIGIN)) return EP_ONDATA_WAIT; + // otherwise carry on to the handler, + EP_ONDATA_RESPONSES resp = ep->onData(&(item->data[ptr + 2]), item->len - ptr - 2); + switch(resp){ + case EP_ONDATA_WAIT: + // again: will mirror this reponse up, wait will happen there + break; + case EP_ONDATA_ACCEPT: + // this means we copy the data in, it's the new endpoint data: + memcpy(ep->data, &(item->data[ptr + 2]), item->len - ptr - 2); + ep->dataLen = item->len - ptr - 2; + // carry on to generate the response (no break) + case EP_ONDATA_REJECT: + { + // for reject *or* accept, we acknowledge that we got the data: + // now generate are reply, + uint16_t wptr = 0; + if(!reverseRoute(item->data, ptr - 4, ack, &wptr)){ + // if we can't reverse it, bail, but issue same response to + // osapLoop.cpp + return resp; + } + // the ack, + ack[wptr ++] = EP_SS_ACK; + // the ack ID is here in prv packet + ack[wptr ++] = item->data[ptr + 1]; + stackLoadSlot(ep->vt, VT_STACK_ORIGIN, ack, wptr); + } + break; // end accept / reject + default: + break; + } + return resp; // mirror response to osapLoop.cpp + } // end acked case closure + break; + case EP_QUERY: + // if can generate new message, + if(stackEmptySlot(ep->vt, VT_STACK_ORIGIN)){ + // run the 'beforeQuery' call, which doesn't need to return anything: + ep->beforeQuery(); + uint16_t wptr = 0; + // if the route can't be reversed, trouble: + if(!reverseRoute(item->data, ptr - 4, ack, &wptr)) { + sysError("on a query, can't reverse a route, rming msg"); + return EP_ONDATA_REJECT; + } else { + ack[wptr ++] = EP_QUERY_RESP; // reply is response + ack[wptr ++] = item->data[ptr + 1]; // has ID matched to request + memcpy(&(ack[wptr]), ep->data, ep->dataLen); + wptr += ep->dataLen; + stackLoadSlot(ep->vt, VT_STACK_ORIGIN, ack, wptr); + return EP_ONDATA_ACCEPT; + } + } else { + // no space to ack w/ a query, pls wait + return EP_ONDATA_WAIT; + } + case EP_SS_ACK: + // upd8 tx state on associated route + { + for(uint8_t r = 0; r < ep->numRoutes; r ++){ + // this is where the ackId is in the packet, we match to routes on that (for speed) + if(item->data[ptr + 1] == ep->routes[r].ackId){ + switch(ep->routes[r].state){ + case EP_TX_AWAITING_ACK: // awaiting -> captured -> idle, + ep->routes[r].state = EP_TX_IDLE; + break; + case EP_TX_AWAITING_AND_FRESH: // awaiting -> captured -> fresh again + ep->routes[r].state = EP_TX_FRESH; + break; + case EP_TX_FRESH: + case EP_TX_IDLE: + default: + // these are all nonsense states for receipt of an ack, + break; + } + } + } + } + return EP_ONDATA_ACCEPT; + case EP_QUERY_RESP: + // not yet having embedded query function + default: + // not recognized, bail city, get it outta here, + return EP_ONDATA_REJECT; + } +} + +// add a route to an endpoint +boolean endpointAddRoute(endpoint_t* ep, uint8_t* path, uint16_t pathLen, EP_ROUTE_MODES mode){ + // guard against more-than-allowed routes + if(ep->numRoutes >= ENDPOINT_MAX_ROUTES) return false; + // handle for the route we're going to modify (and increment # of active routes) + endpoint_route_t* rt = &(ep->routes[ep->numRoutes ++]); + // load the path -> the path + memcpy(rt->path, path, pathLen); + rt->pathLen = pathLen; + rt->ackMode = mode; + // done + return true; +} + +void endpointWrite(endpoint_t* ep, uint8_t* data, uint16_t len){ + // copy data in, + if(len > VT_SLOTSIZE) return; // no lol + memcpy(ep->data, data, len); + ep->dataLen = len; + // set route freshness + for(uint8_t r = 0; r < ep->numRoutes; r ++){ + if(ep->routes[r].state == EP_TX_AWAITING_ACK){ + ep->routes[r].state = EP_TX_AWAITING_AND_FRESH; + } else { + ep->routes[r].state = EP_TX_FRESH; + } + } +} + +boolean endpointAllClear(endpoint_t* ep){ + for(uint8_t r = 0; r < ep->numRoutes; r ++){ + if(ep->routes[r].state != EP_TX_IDLE){ + return false; + } + } + return true; +} + +uint8_t EPOut[VT_SLOTSIZE]; + +// check tx states, +void endpointLoop(endpoint_t* ep, unsigned long now){ + // we want to pluck 'em out on round-robin... + uint8_t r = ep->lastRouteServiced; + for(uint8_t i = 0; i < ep->numRoutes; i ++){ + r ++; + if(r >= ep->numRoutes) r = 0; + endpoint_route_t* rt = &(ep->routes[r]); + switch(rt->state){ + case EP_TX_IDLE: + // no-op + break; + case EP_TX_FRESH: + // foruml8 pck & tx it + if(stackEmptySlot(ep->vt, VT_STACK_ORIGIN)){ + // load it w/ data, + #warning slow-load code, should write str8 to stack + // write ptr in the head, + uint16_t wptr = 0; + EPOut[wptr ++] = PK_PTR; + // the path next, + memcpy(&(EPOut[wptr]), rt->path, rt->pathLen); + wptr += rt->pathLen; + // destination key, segment size + EPOut[wptr ++] = PK_DEST; + ts_writeUint16(rt->segSize, EPOut, &wptr); + // mode-key, + if(rt->ackMode == EP_ROUTE_ACKLESS){ + EPOut[wptr ++] = EP_SS_ACKLESS; + } else if(rt->ackMode == EP_ROUTE_ACKED){ + EPOut[wptr ++] = EP_SS_ACKED; + EPOut[wptr ++] = ep->nextAckId; + rt->ackId = ep->nextAckId; + ep->nextAckId ++; // increment and wrap: only one ID per endpoint per tx, for demux + } + // check against write into stray memory + if(ep->dataLen + wptr >= VT_SLOTSIZE){ + ERROR(1, "write-to-endpoint exceeds slotsize"); + return; + } + // the data, + memcpy(&(EPOut[wptr]), ep->data, ep->dataLen); + wptr += ep->dataLen; + // that's a packet? we load it into stack, we're done + rt->txTime = now; + stackLoadSlot(ep->vt, VT_STACK_ORIGIN, EPOut, wptr); + // transition state: + if(rt->ackMode == EP_ROUTE_ACKLESS) rt->state = EP_TX_IDLE; + if(rt->ackMode == EP_ROUTE_ACKED) rt->state = EP_TX_AWAITING_ACK; + // and track, so that we do *this recently serviced* thing *last* on next round + ep->lastRouteServiced = r; + } else { + // no space... await... + } + case EP_TX_AWAITING_ACK: + // check timeout & transition to idle state + if(rt->txTime + rt->timeoutLength > now){ + rt->state = EP_TX_IDLE; + } + break; + case EP_TX_AWAITING_AND_FRESH: + // check timeout & transition to fresh state + if(rt->txTime + rt->timeoutLength > now){ + rt->state = EP_TX_FRESH; + } + break; + default: + break; + } + } +} diff --git a/firmware/fab-step/src/osape-d21/osape/osap/endpoint.h b/firmware/fab-step/src/osape-d21/osape/osap/endpoint.h new file mode 100644 index 0000000..0293e34 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/endpoint.h @@ -0,0 +1,79 @@ +/* +osap/endpoint.h + +network : software interface + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2021 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#ifndef ENDPOINT_H_ +#define ENDPOINT_H_ + +#include "vertex.h" + +typedef struct vertex_t vertex_t; +typedef struct stackItem stackItem; + +// endpoints have *routes* which they tx to... + +#define ENDPOINT_MAX_ROUTES 4 + +enum EP_ROUTE_MODES { EP_ROUTE_ACKLESS, EP_ROUTE_ACKED }; +enum EP_ROUTE_STATES { EP_TX_IDLE, EP_TX_FRESH, EP_TX_AWAITING_ACK, EP_TX_AWAITING_AND_FRESH }; + +struct endpoint_route_t { + uint8_t path[64]; + uint8_t pathLen = 0; + uint8_t ackId = 0; // this'll limit the # of simultaneously tx'd-to routes to 255, considering each needs unique ackid + EP_ROUTE_STATES state = EP_TX_IDLE; + unsigned long txTime = 0; + unsigned long timeoutLength = 1000; // ms + // properties likely to be exposed to mvc + EP_ROUTE_MODES ackMode = EP_ROUTE_ACKLESS; + uint16_t segSize = 256; +}; + +// endpoint handler responses must be one of these enum - + +enum EP_ONDATA_RESPONSES { EP_ONDATA_REJECT, EP_ONDATA_ACCEPT, EP_ONDATA_WAIT }; + +struct endpoint_t { + vertex_t* vt; + // local data store & length, + uint8_t data[VT_SLOTSIZE]; + uint16_t dataLen = 0; + // callbacks: on new data & before a query is written out + EP_ONDATA_RESPONSES (*onData)(uint8_t* data, uint16_t len) = nullptr; + boolean (*beforeQuery)(void) = nullptr; + // routes, for tx-ing to: + endpoint_route_t routes[ENDPOINT_MAX_ROUTES]; + uint16_t numRoutes = 0; + uint16_t lastRouteServiced = 0; + uint8_t nextAckId = 77; +}; + +// route adder: +// vertex_t* ep is a mistake: osapBuildEndpoint is broken, and returns a vertex... +// we *should* have a better cpp API for this, but don't, that's next go-round +boolean endpointAddRoute(endpoint_t* ep, uint8_t* path, uint16_t pathLen, EP_ROUTE_MODES mode); + +// endpoint writer... +void endpointWrite(endpoint_t* ep, uint8_t* data, uint16_t len); + +// endpoint check-tx-state-machine +void endpointLoop(endpoint_t* ep, unsigned long now); + +// endpoint api-to-check-all-clear: + +boolean endpointAllClear(endpoint_t* ep); + +// a master handler: +EP_ONDATA_RESPONSES endpointHandler(endpoint_t* ep, uint8_t od, stackItem* item, uint16_t ptr); + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/osape/osap/osap.cpp b/firmware/fab-step/src/osape-d21/osape/osap/osap.cpp new file mode 100644 index 0000000..ee04c13 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/osap.cpp @@ -0,0 +1,96 @@ +/* +osap/osap.cpp + +osap root / vertex factory + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2021 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the squidworks and ponyo +projects. Copyright is retained and must be preserved. The work is provided as +is; no warranty is provided, and users accept all liability. +*/ + +#include "osap.h" +#include "osapLoop.h" + +vertex_t _root; +vertex_t* osap = &_root; + +void loopDefault(void){ + // ... noop +} + +void osapSetup(String name){ + _root.type = VT_TYPE_ROOT; + _root.name = name; + _root.indice = 0; + _root.loop = loopDefault; + stackReset(&_root); +} + +void osapLoop(void){ + osapRecursor(&_root); +} + +boolean osapAddVertex(vertex_t* vt) { + if (_root.numChildren >= VT_MAXCHILDREN) { + return false; + } else { + stackReset(vt); + vt->indice = _root.numChildren; + vt->parent = &_root; + _root.children[_root.numChildren++] = vt; + return true; + } +} + +boolean osapAddEndpoint(endpoint_t* ep){ + return osapAddVertex(ep->vt); +} + +EP_ONDATA_RESPONSES onDataDefault(uint8_t* data, uint16_t len){ + return EP_ONDATA_ACCEPT; +} + +boolean beforeQueryDefault(void){ + return true; +} + +endpoint_t* osapBuildEndpoint(String name, EP_ONDATA_RESPONSES (*onData)(uint8_t* data, uint16_t len), boolean (*beforeQuery)(void)){ + vertex_t* vt = new vertex_t; // allocates new to heap someplace, + vt->type = VT_TYPE_ENDPOINT; + vt->name = name; + stackReset(vt); + // add this to the system, + if(osapAddVertex(vt)){ + endpoint_t* ep = new endpoint_t; + if(onData != nullptr){ + ep->onData = onData; + } else { + ep->onData = onDataDefault; + } + if(beforeQuery != nullptr){ + ep->beforeQuery = beforeQuery; + } else { + ep->beforeQuery = beforeQueryDefault; + } + // endpoints don't get loop fns + vt->loop = loopDefault; + vt->ep = ep; + ep->vt = vt; + return ep; + } else { + delete vt; + return nullptr; + } +} + +endpoint_t* osapBuildEndpoint(String name){ + return osapBuildEndpoint(name, nullptr, nullptr); +} + +endpoint_t* osapBuildEndpoint(String name, EP_ONDATA_RESPONSES (*onData)(uint8_t* data, uint16_t len)){ + return osapBuildEndpoint(name, onData, nullptr); +} \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/osape/osap/osap.h b/firmware/fab-step/src/osape-d21/osape/osap/osap.h new file mode 100644 index 0000000..9a5e702 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/osap.h @@ -0,0 +1,45 @@ +/* +osap/osap.h + +osap root / vertex factory + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2021 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the squidworks and ponyo +projects. Copyright is retained and must be preserved. The work is provided as +is; no warranty is provided, and users accept all liability. +*/ + +#ifndef OSAP_H_ +#define OSAP_H_ + +#include "vertex.h" +#include "endpoint.h" + +// vertex factory & root vertex + +extern vertex_t* osap; + +void osapSetup(String name); +void osapLoop(void); + +boolean osapAddVertex(vertex_t* vertex); +boolean osapAddEndpoint(endpoint_t* endpoint); + +endpoint_t* osapBuildEndpoint( + String name, + EP_ONDATA_RESPONSES (*onData)(uint8_t* data, uint16_t len), + boolean (*beforeQuery)(void) +); + +endpoint_t* osapBuildEndpoint( + String name +); + +endpoint_t* osapBuildEndpoint(String name, + EP_ONDATA_RESPONSES (*onData)(uint8_t* data, uint16_t len) +); + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/osape/osap/osapLoop.cpp b/firmware/fab-step/src/osape-d21/osape/osap/osapLoop.cpp new file mode 100644 index 0000000..d891e0a --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/osapLoop.cpp @@ -0,0 +1,304 @@ +/* +osap/osapLoop.cpp + +main osap op: whips data vertex-to-vertex + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2021 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#include "osapLoop.h" +#include "osapUtils.h" +#include "../../../indicators.h" +#include "../../../syserror.h" + +//#define LOOP_DEBUG + +// recurse down vertex's children, +// ... would be breadth-first, ideally +void osapRecursor(vertex_t* vt){ + osapHandler(vt); + for(uint8_t child = 0; child < vt->numChildren; child ++){ + osapRecursor(vt->children[child]); + }; +} + +void osapHandler(vertex_t* vt) { + //sysError("handler " + vt->name); + // time is now + unsigned long now = millis(); + //unsigned long at0; + //unsigned long at1; + + // run vertex's own loop code (with reference to self) + if(vt->loop != nullptr) vt->loop(); + if(vt->ep != nullptr) endpointLoop(vt->ep, now); + + // handle origin stack, destination stack, in same manner + for(uint8_t od = 0; od < 2; od ++){ + // try one handle per stack item, per loop: + stackItem* items[vt->stackSize]; + uint8_t count = stackGetItems(vt, od, items, vt->stackSize); + // want to track out-of-order issues to the loop. + // if(count) at0 = items[0]->arrivalTime; // pretty sure we can delete this as of 2021-12-31 + for(uint8_t i = 0; i < count; i ++){ + // the item, and ptr + stackItem* item = items[i]; + uint16_t ptr = 0; + // check timeouts, + if(item->arrivalTime + TIMES_STALE_MSG < now){ + sysError("T/O indice " + String(vt->indice) + " " + String(item->data[ptr + 1]) + " " + String(item->arrivalTime)); + stackClearSlot(vt, od, item); + continue; + } + // check for decent ptr walk, + if(!ptrLoop(item->data, &ptr)){ + sysError("main loop bad ptr walk, from vt->indice: " + String(vt->indice) + " o/d: " + String(od) + " len: " + String(item->len)); + logPacket(item->data, item->len); + stackClearSlot(vt, od, item); // clears the msg + continue; + } + // check FIFO order, + // at1 = item->arrivalTime; // pretty sure we can delete this as of 2021-12-31 + // if(at1 - at0 < 0) sysError("out of order " + String(at1) + " " + String(at0)); + // at1 = at0; + // handle it, + osapSwitch(vt, od, item, ptr, now); + } + } // end lp over origin / destination stacks +} + +void osapSwitch(vertex_t* vt, uint8_t od, stackItem* item, uint16_t ptr, unsigned long now){ + // switch at pck[ptr + 1] + ptr ++; + uint8_t* pck = item->data; + uint16_t len = item->len; + // do things, + switch(pck[ptr]){ + case PK_DEST: // instruction indicates pck is at vertex of destination, we try handler to rx data + switch(vt->type){ + case VT_TYPE_ROOT: + case VT_TYPE_MODULE: + case VT_TYPE_VPORT: + case VT_TYPE_VBUS: + // all four: we are ignoring dms, wipe it: + stackClearSlot(vt, od, item); + break; + case VT_TYPE_ENDPOINT: + { + EP_ONDATA_RESPONSES resp = endpointHandler(vt->ep, od, item, ptr); + switch(resp){ + case EP_ONDATA_REJECT: + case EP_ONDATA_ACCEPT: // in either case, msg is handled / out of stack, we can clear it + stackClearSlot(vt, od, item); + break; + case EP_ONDATA_WAIT: // not handled: want to wait in the stack, so update time & come back next + item->arrivalTime = now; + break; + default: + sysError("on endpoint dest. handle, unknown ondata response"); + stackClearSlot(vt, od, item); + } // end response switch + } + break; + default: + // vertex has a 'type' we don't recognize, + // best I can do is deletion + sysError("unknown vertext type, when handling msg-at-destination"); + stackClearSlot(vt, od, item); + break; + } // end vt typeswitch + break; // end PK_DEST case + case PK_SIB_KEY: { // instruction to pass to this sibling, + // need the indice, + uint16_t si; + ptr ++; + ts_readUint16(&si, pck, &ptr); + // can't do this if no parent, + if(vt->parent == nullptr){ + sysError("no parent for sib traverse"); + stackClearSlot(vt, od, item); + break; + } + // nor if no target, + if(si >= vt->parent->numChildren){ + sysError("sib traverse oob"); + stackClearSlot(vt, od, item); + break; + } + // now we can copy it in, only if there's space ahead to move it into + if(stackEmptySlot(vt->parent->children[si], VT_STACK_DESTINATION)){ + #ifdef LOOP_DEBUG + sysError("sib copy"); + #endif + ptr -= 4; // write in reversed instruction (reverse ptr to PK_PTR here) + pck[ptr ++] = PK_SIB_KEY; // overwrite with instruction that would return to us, + ts_writeUint16(vt->indice, pck, &ptr); + pck[ptr] = PK_PTR; + // copy-in, set fullness, update time + stackLoadSlot(vt->parent->children[si], VT_STACK_DESTINATION, pck, len); + // now og is clear + stackClearSlot(vt, od, item); + // and we can finish here + break; + } else { + // destination stack at target is full, msg stays in place, next loop checks + } + } + break; // end sib-fwd case, + case PK_PARENT_KEY: + if(vt->parent == nullptr){ + ERROR(1, "requests traverse to parent from top level"); + stackClearSlot(vt, od, item); + } else if(stackEmptySlot(vt->parent, VT_STACK_DESTINATION)){ + #ifdef LOOP_DEBUG + sysError("copy to parent"); + #endif + ptr -= 1; // write in reversed instruction + pck[ptr ++] = PK_CHILD_KEY; + ts_writeUint16(vt->indice, pck, &ptr); + pck[ptr ++] = PK_PTR; + // copy-in, set fullness and update time + stackLoadSlot(vt->parent, VT_STACK_DESTINATION, pck, len); + stackClearSlot(vt, od, item); + } + break; + case PK_CHILD_KEY: + // find child + uint16_t ci; + ptr ++; + ts_readUint16(&ci, pck, &ptr); + // can't do it w/o the child, + if(vt->numChildren <= ci){ + ERROR(1, "no child at this indice " + String(ci)); + stackClearSlot(vt, od, item); + } else if (stackEmptySlot(vt->children[ci], VT_STACK_DESTINATION)){ + #ifdef LOOP_DEBUG + sysError("copy to child"); + #endif + ptr -= 4; + pck[ptr ++] = PK_PARENT_KEY; + ts_writeUint16(0, pck, &ptr); // parent 'index' used bc packet length should be symmetric + pck[ptr ++] = PK_PTR; + // do the copy-in, set fullness, etc + stackLoadSlot(vt->children[ci], VT_STACK_DESTINATION, pck, len); + stackClearSlot(vt, od, item); + } + break; + case PK_PFWD_KEY: + if(vt->type != VT_TYPE_VPORT || vt->cts == nullptr || vt->send == nullptr){ + sysError("pfwd to non-vport vertex"); + stackClearSlot(vt, od, item); + } else { + if(vt->cts(0)){ // walk ptr fwds, transmit, and clear the msg + pck[ptr - 1] = PK_PFWD_KEY; + pck[ptr] = PK_PTR; + vt->send(pck, len, 0); + stackClearSlot(vt, od, item); + } else { + // failed to pfwd this turn, code will return here next go-round + } + } + break; + case PK_BFWD_KEY: + if(vt->type != VT_TYPE_VBUS || vt->cts == nullptr || vt->send == nullptr){ + sysError("bfwd to non-vbus vertex"); + logPacket(item->data, item->len); + stackClearSlot(vt, od, item); + } else { + // need tx rxaddr, for which drop on bus to tx to, + uint16_t rxAddr; + ptr ++; + ts_readUint16(&rxAddr, pck, &ptr); + if(vt->cts(rxAddr)){ // walk ptr fwds, transmit, and clear the msg + #ifdef LOOP_DEBUG + sysError("busf " + String(rxAddr)); + #endif + //sysError("be " + String(item->arrivalTime)); + ptr -= 4; + pck[ptr ++] = PK_BFWD_KEY; + ts_writeUint16(vt->ownRxAddr, pck, &ptr); + pck[ptr] = PK_PTR; + //logPacket(pck, len); + vt->send(pck, len, rxAddr); + stackClearSlot(vt, od, item); + } else { + // failed to bfwd this turn, code will return here next go-round + } + } + break; + case PK_SCOPE_REQ_KEY: + { + // so we want to write a brief packet in here, and we should do it str8 into the same item, + // we need to reverse the route into a temp object: + uint8_t route[VT_SLOTSIZE]; + uint16_t wptr = 0; + if(!reverseRoute(item->data, ptr - 1, route, &wptr)){ + ERROR(1, "reverse route badness at scope request"); + stackClearSlot(vt, od, item); + } + // because reverse route assumes dest, segsize / checksum, we actually want... + wptr -= 3; + // now we can write in: + // recall that item->data is the stack-item's little msg block... + memcpy(item->data, route, wptr); + // and write response key + item->data[wptr ++] = PK_SCOPE_RES_KEY; + // id from the REQ, should actually be untouched, right? + wptr ++; + // want 2 of these + uint16_t rptr = wptr; + // collect new timeTag: vertex keeps 'last-time-scoped' state + // here we write-in to the response our *previous* time-scoped, and replace that w/ this requests' tag + uint32_t newScopeTimeTag; + ts_readUint32(&newScopeTimeTag, item->data, &rptr); + ts_writeUint32(vt->scopeTimeTag, item->data, &wptr); + vt->scopeTimeTag = newScopeTimeTag; + // our type + item->data[wptr ++] = vt->type; + // our own indice, our # of siblings, our # of children: + ts_writeUint16(vt->indice, item->data, &wptr); + if(vt->parent != nullptr){ + ts_writeUint16(vt->parent->numChildren, item->data, &wptr); + } else { + ts_writeUint16(0, item->data, &wptr); + } + ts_writeUint16(vt->numChildren, item->data, &wptr); + // and our name... + ts_writeString(vt->name, item->data, &wptr); + /* + switch(vt->type){ + case VT_TYPE_ENDPOINT: + ts_writeString("embedded-endpoint", item->data, &wptr); + break; + case VT_TYPE_ROOT: + ts_writeString("embedded-root", item->data, &wptr); + break; + case VT_TYPE_VPORT: + ts_writeString("embedded-vport", item->data, &wptr); + break; + default: + ts_writeString("embedded-vertex", item->data, &wptr); + break; + } + */ + // ok then, we can reset this item, basically: + item->len = wptr; + item->arrivalTime = now; + // osap will pick it up next loop, ship it back. + } + break; + case PK_SCOPE_RES_KEY: + case PK_LLESCAPE_KEY: + default: + sysError("unrecognized ptr here at " + String(ptr) + ": " + String(pck[ptr])); + logPacket(pck, len); + stackClearSlot(vt, od, item); + break; + } // end main switch +} \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/osape/osap/osapLoop.h b/firmware/fab-step/src/osape-d21/osape/osap/osapLoop.h new file mode 100644 index 0000000..099fbdb --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/osapLoop.h @@ -0,0 +1,24 @@ +/* +osap/osapLoop.h + +main osap op: whips data vertex-to-vertex + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2021 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#ifndef OSAP_LOOP_H_ +#define OSAP_LOOP_H_ + +#include "vertex.h" + +void osapRecursor(vertex_t* vt); +void osapHandler(vertex_t* vt); +void osapSwitch(vertex_t* vt, uint8_t od, stackItem* item, uint16_t ptr, unsigned long now); + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/osape/osap/osapUtils.cpp b/firmware/fab-step/src/osape-d21/osape/osap/osapUtils.cpp new file mode 100644 index 0000000..ddc1266 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/osapUtils.cpp @@ -0,0 +1,124 @@ +/* +osap/osapUtils.cpp + +common routines + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2021 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#include "osapUtils.h" +#include "ts.h" +#include "../../../syserror.h" + +boolean ptrLoop(uint8_t* pck, uint16_t* pt){ + uint16_t ptr = *pt; + for(uint8_t i = 0; i < 16; i ++){ + //sysError(String(ptr)); + switch(pck[ptr]){ + case PK_PTR: // var is here + *pt = ptr; + return true; + case PK_SIB_KEY: + ptr += PK_SIB_INC; + break; + case PK_PARENT_KEY: + ptr += PK_PARENT_INC; + break; + case PK_CHILD_KEY: + ptr += PK_CHILD_INC; + break; + case PK_PFWD_KEY: + ptr += PK_PFWD_INC; + break; + case PK_BFWD_KEY: + ptr += PK_BFWD_INC; + break; + default: + return false; + } + } + // case where no ptr after 16 hops, + return false; +} + +// pck: bytes, rptr: read ptr (should start at pck[rptr] = PTR / 88) +// repl: reply bytes, replyPtr ... ptr after setup +boolean reverseRoute(uint8_t* pck, uint16_t rptr, uint8_t* repl, uint16_t* replyPtr){ + // so we should have here that + if(pck[rptr] != PK_PTR){ + sysError("rr: pck[ptr] != pk_ptr"); + return false; + } + // the tail is identical: dest & segsize following + for(uint8_t i = 3; i > 0; i --){ + repl[rptr + 4 - i] = pck[rptr + 4 - i]; + } + // so we have a readptr (ptr) and writeptr (wptr) + // we write *from the tail back* and read *from the tip in* + uint16_t end = rptr; + uint16_t wptr = rptr + 1; + rptr = 0; + // sequentially, at most 16 ops + for(uint8_t i = 0; i < 16; i ++){ + // end case: + if(rptr >= end){ + if(rptr != end){ + sysError("rr: rptr overruns end"); + return false; + } + // start is pointer, + repl[0] = PK_PTR; + // end is past ptr, + dest key, + checksum (2), + 1 so + // first write repl[rptr] = nextByte + *replyPtr = rptr + 4; + return true; + } + // switch each, + switch(pck[rptr]){ + case PK_PTR: // var is here + sysError("rr: find pck_ptr during walk"); + return false; + case PK_SIB_KEY: + wptr -= PK_SIB_INC; + for(uint8_t j = 0; j < PK_SIB_INC; j ++){ + repl[wptr + j] = pck[rptr ++]; + } + break; + case PK_PARENT_KEY: + wptr -= PK_PARENT_INC; + for(uint8_t j = 0; j < PK_PARENT_INC; j ++){ + repl[wptr + j] = pck[rptr ++]; + } + break; + case PK_CHILD_KEY: + wptr -= PK_CHILD_INC; + for(uint8_t j = 0; j < PK_CHILD_INC; j ++){ + repl[wptr + j] = pck[rptr ++]; + } + break; + case PK_PFWD_KEY: + wptr -= PK_PFWD_INC; + for(uint8_t j = 0; j < PK_PFWD_INC; j ++){ + repl[wptr + j] = pck[rptr ++]; + } + break; + case PK_BFWD_KEY: + wptr -= PK_BFWD_INC; + for(uint8_t j = 0; j < PK_BFWD_INC; j ++){ + repl[wptr + j] = pck[rptr ++]; + } + break; + default: + sysError("rr: default switch"); + return false; + } + } + // if reach end of ptr walk but no exit, badness + return false; +} \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/osape/osap/osapUtils.h b/firmware/fab-step/src/osape-d21/osape/osap/osapUtils.h new file mode 100644 index 0000000..a8870bd --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/osapUtils.h @@ -0,0 +1,23 @@ +/* +osap/osapUtils.h + +common routines + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2021 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#ifndef OSAP_UTILS_H_ +#define OSAP_UTILS_H_ + +#include <Arduino.h> + +boolean ptrLoop(uint8_t* pck, uint16_t* ptr); +boolean reverseRoute(uint8_t* pck, uint16_t rptr, uint8_t* repl, uint16_t* replyPtr); + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/osape/osap/ts.cpp b/firmware/fab-step/src/osape-d21/osape/osap/ts.cpp new file mode 100644 index 0000000..ffa05e0 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/ts.cpp @@ -0,0 +1,86 @@ +/* +osap/ts.cpp + +typeset / keys / writing / reading + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#include "ts.h" + +void ts_writeBoolean(boolean val, unsigned char* buf, uint16_t* ptr){ + if(val){ + buf[(*ptr) ++] = 1; + } else { + buf[(*ptr) ++] = 0; + } +} + +void ts_readUint16(uint16_t* val, unsigned char* buf, uint16_t* ptr){ + *val = buf[(*ptr) + 1] << 8 | buf[(*ptr)]; + *ptr += 2; +} + +void ts_writeUint8(uint8_t val, unsigned char* buf, uint16_t* ptr){ + buf[(*ptr) ++] = val; +} + +void ts_writeUint16(uint16_t val, unsigned char* buf, uint16_t* ptr){ + buf[(*ptr) ++] = val & 255; + buf[(*ptr) ++] = (val >> 8) & 255; +} + +void ts_readUint32(uint32_t* val, unsigned char* buf, uint16_t* ptr){ + *val = buf[(*ptr) + 3] << 24 | buf[(*ptr) + 2] << 16 | buf[(*ptr) + 1] << 8 | buf[(*ptr)]; + *ptr += 4; +} + +void ts_writeUint32(uint32_t val, unsigned char* buf, uint16_t* ptr){ + buf[(*ptr) ++] = val & 255; + buf[(*ptr) ++] = (val >> 8) & 255; + buf[(*ptr) ++] = (val >> 16) & 255; + buf[(*ptr) ++] = (val >> 24) & 255; +} + +void ts_writeFloat32(float val, volatile unsigned char* buf, uint16_t* ptr){ + chunk_float32 chunk; + chunk.f = val; + buf[(*ptr) ++] = chunk.bytes[0]; + buf[(*ptr) ++] = chunk.bytes[1]; + buf[(*ptr) ++] = chunk.bytes[2]; + buf[(*ptr) ++] = chunk.bytes[3]; +} + +void ts_writeFloat64(double val, volatile unsigned char* buf, uint16_t* ptr){ + chunk_float64 chunk; + chunk.f = val; + buf[(*ptr) ++] = chunk.bytes[0]; + buf[(*ptr) ++] = chunk.bytes[1]; + buf[(*ptr) ++] = chunk.bytes[2]; + buf[(*ptr) ++] = chunk.bytes[3]; + buf[(*ptr) ++] = chunk.bytes[4]; + buf[(*ptr) ++] = chunk.bytes[5]; + buf[(*ptr) ++] = chunk.bytes[6]; + buf[(*ptr) ++] = chunk.bytes[7]; +} + +void ts_writeString(String* val, unsigned char* buf, uint16_t* ptr){ + uint32_t len = val->length(); + buf[(*ptr) ++] = len & 255; + buf[(*ptr) ++] = (len >> 8) & 255; + buf[(*ptr) ++] = (len >> 16) & 255; + buf[(*ptr) ++] = (len >> 24) & 255; + val->getBytes(&buf[*ptr], len + 1); + *ptr += len; +} + +void ts_writeString(String val, unsigned char* buf, uint16_t* ptr){ + ts_writeString(&val, buf, ptr); +} + diff --git a/firmware/fab-step/src/osape-d21/osape/osap/ts.h b/firmware/fab-step/src/osape-d21/osape/osap/ts.h new file mode 100644 index 0000000..fefa860 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/ts.h @@ -0,0 +1,127 @@ +/* +osap/ts.h + +typeset / keys / writing / reading + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#ifndef TS_H_ +#define TS_H_ + +#include <Arduino.h> + +// -------------------------------------------------------- Vertex Type Keys +// will likely use these in the netrunner: + +#define VT_TYPE_ROOT 22 +#define VT_TYPE_MODULE 23 +#define VT_TYPE_ENDPOINT 24 +#define VT_TYPE_VPORT 44 +#define VT_TYPE_VBUS 45 // maybe bus-drop / bus-head / bus-cohost are differentiated + +// -------------------------------------------------------- TIMES + +#define TIMES_STALE_MSG 800 + +// -------------------------------------------------------- Routing (Packet) Keys + +#define PK_PTR 88 +#define PK_DEST 99 +#define PK_SIB_KEY 15 +#define PK_SIB_INC 3 +#define PK_PARENT_KEY 16 +#define PK_PARENT_INC 3 +#define PK_CHILD_KEY 14 +#define PK_CHILD_INC 3 +#define PK_PFWD_KEY 11 +#define PK_PFWD_INC 1 +#define PK_BFWD_KEY 12 +#define PK_BFWD_INC 3 +#define PK_SCOPE_REQ_KEY 21 +#define PK_SCOPE_REQ_INC 1 +#define PK_SCOPE_RES_KEY 22 +#define PK_SCOPE_RES_INC 1 +#define PK_LLESCAPE_KEY 44 +#define PK_LLESCAPE_INC 1 + +// -------------------------------------------------------- Endpoint Keys + +#define EP_SS_ACK 101 // the ack +#define EP_SS_ACKLESS 121 // single segment, no ack +#define EP_SS_ACKED 122 // single segment, request ack +#define EP_QUERY 131 // query request +#define EP_QUERY_RESP 132 // reply to query request + +// -------------------------------------------------------- BUS ACTION KEYS (outside OSAP scope) + +#define UB_AK_SETPOS 102 +#define UB_AK_GOTOPOS 105 + +// -------------------------------------------------------- Type Keys + +#define TK_BOOL 2 + +#define TK_UINT8 4 +#define TK_INT8 5 +#define TK_UINT16 6 +#define TK_INT16 7 +#define TK_UINT32 8 +#define TK_INT32 9 +#define TK_UINT64 10 +#define TK_INT64 11 + +#define TK_FLOAT16 24 +#define TK_FLOAT32 26 +#define TK_FLOAT64 28 + +// -------------------------------------------------------- Chunks + +union chunk_float32 { + uint8_t bytes[4]; + float f; +}; + +union chunk_float64 { + uint8_t bytes[8]; + double f; +}; + +union chunk_int32 { + uint8_t bytes[4]; + int32_t i; +}; + +union chunk_uint32 { + uint8_t bytes[4]; + uint32_t u; +}; + +// -------------------------------------------------------- Reading and Writing + +void ts_writeBoolean(boolean val, unsigned char* buf, uint16_t* ptr); + +void ts_readUint16(uint16_t* val, uint8_t* buf, uint16_t* ptr); + +void ts_writeUint8(uint8_t val, unsigned char* buf, uint16_t* ptr); + +void ts_writeUint16(uint16_t val, unsigned char* buf, uint16_t* ptr); + +void ts_readUint32(uint32_t* val, unsigned char* buf, uint16_t* ptr); + +void ts_writeUint32(uint32_t val, unsigned char* buf, uint16_t* ptr); + +void ts_writeFloat32(float val, volatile unsigned char* buf, uint16_t* ptr); + +void ts_writeFloat64(double val, volatile unsigned char* buf, uint16_t* ptr); + +void ts_writeString(String* val, unsigned char* buf, uint16_t* ptr); +void ts_writeString(String val, unsigned char* buf, uint16_t* ptr); + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/osape/osap/vertex.cpp b/firmware/fab-step/src/osape-d21/osape/osap/vertex.cpp new file mode 100644 index 0000000..987a03c --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/vertex.cpp @@ -0,0 +1,128 @@ +/* +osap/vertex.cpp + +graph vertex + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#include "vertex.h" +#include "../../../syserror.h" + +void stackReset(vertex_t* vt){ + // clear all elements & write next ptrs in linear order + for(uint8_t od = 0; od < 2; od ++){ + // set lengths, etc, + for(uint8_t s = 0; s < vt->stackSize; s ++){ + vt->stack[od][s].arrivalTime = 0; + vt->stack[od][s].len = 0; + vt->stack[od][s].indice = s; + } + // set next ptrs, + for(uint8_t s = 0; s < vt->stackSize - 1; s ++){ + vt->stack[od][s].next = &(vt->stack[od][s + 1]); + } + vt->stack[od][vt->stackSize - 1].next = &(vt->stack[od][0]); + // set previous ptrs, + for(uint8_t s = 1; s < vt->stackSize; s ++){ + vt->stack[od][s].previous = &(vt->stack[od][s - 1]); + } + vt->stack[od][0].previous = &(vt->stack[od][vt->stackSize - 1]); + // 1st element is 0th on startup, + vt->queueStart[od] = &(vt->stack[od][0]); + // first free = tail at init, + vt->firstFree[od] = &(vt->stack[od][0]); + } +} + +// -------------------------------------------------------- ORIGIN SIDE +// true if there's any space in the stack, +boolean stackEmptySlot(vertex_t* vt, uint8_t od){ + if(od > 1) return false; + // if 1st free has ptr to next item, not full + if(vt->firstFree[od]->next->len != 0){ + return false; + } else { + return true; + } +} + +// loads data into stack +void stackLoadSlot(vertex_t* vt, uint8_t od, uint8_t* data, uint16_t len){ + if(od > 1) return; // bad od, lost data + // copy into first free element, + memcpy(vt->firstFree[od]->data, data, len); + vt->firstFree[od]->len = len; + vt->firstFree[od]->arrivalTime = millis(); + //sysError("load " + String(vt->firstFree[od]->indice) + " " + String(vt->firstFree[od]->arrivalTime)); + // now firstFree is next, + vt->firstFree[od] = vt->firstFree[od]->next; +} + +// -------------------------------------------------------- EXIT SIDE +// return count of items occupying stack, and list of ptrs to them, +uint8_t stackGetItems(vertex_t* vt, uint8_t od, stackItem** items, uint8_t maxItems){ + if(od > 1) return 0; + // when queueStart == firstFree element, we have nothing for you + if(vt->firstFree[od] == vt->queueStart[od]) return 0; + // starting at queue begin, + uint8_t count = 0; + stackItem* item = vt->queueStart[od]; + for(uint8_t s = 0; s < maxItems; s ++){ + items[s] = item; + count ++; + if(item->next->len > 0){ + item = item->next; + } else { + return count; + } + } + return count; +} + +// clear the item, +void stackClearSlot(vertex_t* vt, uint8_t od, stackItem* item){ + // item is 0-len, etc + item->len = 0; + // is this + uint8_t indice = item->indice; + // if was queueStart, queueStart now at next, + if(vt->queueStart[od] == item){ + vt->queueStart[od] = item->next; + // and wouldn't have to do any of the below? + } else { + // pull from chain, now is free of associations, + // these ops are *always two up* + item->previous->next = item->next; + item->next->previous = item->previous; + + // now, insert this where old firstFree was + vt->firstFree[od]->previous->next = item; + item->previous = vt->firstFree[od]->previous; + + item->next = vt->firstFree[od]; + vt->firstFree[od]->previous = item; + // and the item is the new firstFree element, + vt->firstFree[od] = item; + // and we can call, + } + + // clear flowcontrol conditions + switch(od){ + case VT_STACK_ORIGIN: + if(vt->onOriginStackClear != nullptr) vt->onOriginStackClear(indice); + break; + case VT_STACK_DESTINATION: + if(vt->onDestinationStackClear != nullptr) vt->onDestinationStackClear(indice); + break; + default: // pretty unlikely + sysError("stack clear slot, od > 1, que?"); + break; + } +} \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/osape/osap/vertex.h b/firmware/fab-step/src/osape-d21/osape/osap/vertex.h new file mode 100644 index 0000000..bb18816 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/osap/vertex.h @@ -0,0 +1,92 @@ +/* +osap/vertex.h + +graph vertex + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2020 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#ifndef VERTEX_H_ +#define VERTEX_H_ + +#include <Arduino.h> +#include "ts.h" +#include "../../vertices/vertexConfig.h" // vertex config is build dependent, define in <folder-containing-osape>/vertices/vertexConfig.h +#include "endpoint.h" + +#define VT_STACK_ORIGIN 0 +#define VT_STACK_DESTINATION 1 + +// https://stackoverflow.com/questions/1813991/c-structure-with-pointer-to-self + +// linked ringbuffer item +struct stackItem { + uint8_t data[VT_SLOTSIZE]; + uint16_t len = 0; + unsigned long arrivalTime = 0; + uint8_t indice; // actual physical position in the stack + stackItem* next = nullptr; + stackItem* previous = nullptr; +}; + +// we have the vertex type, +typedef struct vertex_t vertex_t; +// which are typed by existence of some other object, +typedef struct endpoint_t endpoint_t; +typedef struct vport_t vport_t; +typedef struct vbus_t vbus_t; +typedef struct root_t root_t; + +struct vertex_t { + // a loop code, run once per turn + void (*loop)() = nullptr; + + // a type, a position, a name + uint8_t type = 0; + uint16_t indice = 0; + String name; + // a time tag, for when we git scoped (need for graph traversals) + uint32_t scopeTimeTag = 0; + // addnl' properties, can possess: + endpoint_t* ep = nullptr; + vport_t* vp = nullptr; + vbus_t* vb = nullptr; + root_t* rt = nullptr; + // stacks; + // origin stack[0] destination stack[1] + // destination stack is for messages delivered to this vertex, + stackItem stack[2][VT_STACKSIZE]; + uint8_t stackSize = VT_STACKSIZE; // should be variable + //uint8_t lastStackHandled[2] = { 0, 0 }; + stackItem* queueStart[2] = { nullptr, nullptr }; // data is read from the tail + stackItem* firstFree[2] = { nullptr, nullptr }; // data is loaded into the head + // parent & children (other vertices) + vertex_t* parent = nullptr; + vertex_t* children[VT_MAXCHILDREN]; // I think this is OK on storage: just pointers + uint16_t numChildren = 0; + // vertex-as-vport-interface + boolean (*cts)(uint8_t drop) = nullptr; + void (*send)(uint8_t* data, uint16_t len, uint8_t rxAddr) = nullptr; + uint16_t ownRxAddr = 0; + // to notify for clear-out callbacks / flowcontrol etc + void (*onOriginStackClear)(uint8_t slot) = nullptr; + void (*onDestinationStackClear)(uint8_t slot) = nullptr; +}; + +void stackReset(vertex_t* vt); + +// ORIGIN SIDE +boolean stackEmptySlot(vertex_t* vt, uint8_t od); +void stackLoadSlot(vertex_t* vt, uint8_t od, uint8_t* data, uint16_t len); + +// EXIT SIDE +uint8_t stackGetItems(vertex_t* vt, uint8_t od, stackItem** items, uint8_t maxItems); +void stackClearSlot(vertex_t* vt, uint8_t od, stackItem* item); + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/osape/utils/cobs.cpp b/firmware/fab-step/src/osape-d21/osape/utils/cobs.cpp new file mode 100644 index 0000000..81cc05b --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/utils/cobs.cpp @@ -0,0 +1,70 @@ +/* +utils/cobs.cpp + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#include "cobs.h" +// str8 crib from +// https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing + +/** COBS encode data to buffer + @param data Pointer to input data to encode + @param length Number of bytes to encode + @param buffer Pointer to encoded output buffer + @return Encoded buffer length in bytes + @note doesn't write stop delimiter +*/ +size_t cobsEncode(const void *data, size_t length, uint8_t *buffer){ + + uint8_t *encode = buffer; // Encoded byte pointer + uint8_t *codep = encode++; // Output code pointer + uint8_t code = 1; // Code value + + for (const uint8_t *byte = (const uint8_t *)data; length--; ++byte){ + if (*byte) // Byte not zero, write it + *encode++ = *byte, ++code; + + if (!*byte || code == 0xff){ // Input is zero or block completed, restart + *codep = code, code = 1, codep = encode; + if (!*byte || length) + ++encode; + } + } + *codep = code; // Write final code value + return encode - buffer; +} + +/** COBS decode data from buffer + @param buffer Pointer to encoded input bytes + @param length Number of bytes to decode + @param data Pointer to decoded output data + @return Number of bytes successfully decoded + @note Stops decoding if delimiter byte is found +*/ +size_t cobsDecode(const uint8_t *buffer, size_t length, void *data){ + + const uint8_t *byte = buffer; // Encoded input byte pointer + uint8_t *decode = (uint8_t *)data; // Decoded output byte pointer + + for (uint8_t code = 0xff, block = 0; byte < buffer + length; --block){ + if (block) // Decode block byte + *decode++ = *byte++; + else + { + if (code != 0xff) // Encoded zero, write it + *decode++ = 0; + block = code = *byte++; // Next block length + if (code == 0x00) // Delimiter code found + break; + } + } + + return decode - (uint8_t *)data; +} diff --git a/firmware/fab-step/src/osape-d21/osape/utils/cobs.h b/firmware/fab-step/src/osape-d21/osape/utils/cobs.h new file mode 100644 index 0000000..b47070c --- /dev/null +++ b/firmware/fab-step/src/osape-d21/osape/utils/cobs.h @@ -0,0 +1,24 @@ +/* +utils/cobs.h + +consistent overhead byte stuffing implementation + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#ifndef UTIL_COBS_H_ +#define UTIL_COBS_H_ + +#include <Arduino.h> + +size_t cobsEncode(const void *data, size_t length, uint8_t *buffer); + +size_t cobsDecode(const uint8_t *buffer, size_t length, void *data); + +#endif diff --git a/firmware/fab-step/src/osape-d21/peripheralNums.h b/firmware/fab-step/src/osape-d21/peripheralNums.h new file mode 100644 index 0000000..eed9f18 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/peripheralNums.h @@ -0,0 +1,18 @@ +#ifndef PERIPHERAL_NUMS_H_ +#define PERIPHERAL_NUMS_H_ + +#define PERIPHERAL_A 0 +#define PERIPHERAL_B 1 +#define PERIPHERAL_C 2 +#define PERIPHERAL_D 3 +#define PERIPHERAL_E 4 +#define PERIPHERAL_F 5 +#define PERIPHERAL_G 6 +#define PERIPHERAL_H 7 +#define PERIPHERAL_I 8 +#define PERIPHERAL_K 9 +#define PERIPHERAL_L 10 +#define PERIPHERAL_M 11 +#define PERIPHERAL_N 12 + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusDrop.cpp b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusDrop.cpp new file mode 100644 index 0000000..ebb12b8 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusDrop.cpp @@ -0,0 +1,484 @@ +/* +osap/drivers/ucBusDrop.cpp + +beginnings of a uart-based clock / bus combo protocol + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the squidworks and ponyo +projects. Copyright is retained and must be preserved. The work is provided as +is; no warranty is provided, and users accept all liability. +*/ + +#include "ucBusDrop.h" + +#ifdef UCBUS_IS_DROP + +#include "../../../indicators.h" +#include "../../../syserror.h" +#include "../../peripheralNums.h" +#include "ucBusDipConfig.h" +#include "../../osape/utils/cobs.h" + +// recieve buffers +uint8_t recieveBuffer[UB_CH_COUNT][UB_BUFSIZE]; +volatile uint16_t recieveBufferWp[UB_CH_COUNT]; +// tracking did-last-msg have token, +volatile boolean lastWordHadToken[UB_CH_COUNT]; + +// stash buffers (have to ferry data from rx buffer -> here immediately on rx, else next word can overwrite) +uint8_t inBuffer[UB_CH_COUNT][UB_BUFSIZE]; +volatile uint16_t inBufferLen[UB_CH_COUNT]; + +// output buffer +uint8_t outBuffer[UB_CH_COUNT][UB_BUFSIZE]; +volatile uint16_t outBufferRp[UB_CH_COUNT]; +volatile uint16_t outBufferLen[UB_CH_COUNT]; + +// receive word +UCBUS_HEADER_Type inHeader = { .bytes = { 0,0 } }; +volatile uint8_t inWordWp = 0; +uint8_t inWord[UB_HEAD_BYTES_PER_WORD]; + +// outgoing word +UCBUS_HEADER_Type outHeader = { .bytes = { 0,0 } }; +uint8_t outWord[UB_DROP_BYTES_PER_WORD]; +volatile uint8_t outWordRp = 0; + +// reciprocal buffer space, for flowcontrol +volatile uint8_t rcrxb[UB_CH_COUNT]; + +// our physical bus address, +volatile uint8_t id = 0; + +// available time count, in bus tick units +volatile uint16_t timeTick = 0; +volatile uint64_t timeBlink = 0; +uint16_t blinkTime = 1000; + +// baudrate +uint32_t ub_baud_val = 0; + +// we need to track interrupt states as well as setting the flags in the micro, +// since the D21 fires only one ISR for all of the flags; +volatile boolean txcISR = false; +volatile boolean dreISR = false; + +#define DRE_ISR_ON UB_SER_USART.INTENSET.reg = SERCOM_USART_INTENSET_DRE; dreISR = true +#define DRE_ISR_OFF UB_SER_USART.INTENCLR.reg = SERCOM_USART_INTENCLR_DRE; dreISR = false +#define TXC_ISR_ON UB_SER_USART.INTENSET.reg = SERCOM_USART_INTENSET_TXC; txcISR = true +#define TXC_ISR_OFF UB_SER_USART.INTENCLR.reg = SERCOM_USART_INTENCLR_TXC; txcISR = false + +#ifdef UCBUS_IS_D51 +// ------------------------------------ D51 SPECIFIC +// hardware init (file scoped) +void setupBusDropUART(void){ + // set driver output LO to start: tri-state + UB_DE_PORT.DIRSET.reg = UB_DE_BM; + UB_DRIVER_DISABLE; + // set receiver output on, forever: LO to set on + UB_RE_PORT.DIRSET.reg = UB_RE_BM; + UB_RE_PORT.OUTCLR.reg = UB_RE_BM; + // termination resistor should be set only on one drop, + // or none and physically with a 'tail' cable, or something? + UB_TE_PORT.DIRSET.reg = UB_TE_BM; + if(dip_readPin1()){ + UB_TE_PORT.OUTCLR.reg = UB_TE_BM; + } else { + UB_TE_PORT.OUTSET.reg = UB_TE_BM; + } + // rx pin setup + UB_COMPORT.DIRCLR.reg = UB_RXBM; + UB_COMPORT.PINCFG[UB_RXPIN].bit.PMUXEN = 1; + if(UB_RXPIN % 2){ + UB_COMPORT.PMUX[UB_RXPIN >> 1].reg |= PORT_PMUX_PMUXO(UB_RXPERIPHERAL); + } else { + UB_COMPORT.PMUX[UB_RXPIN >> 1].reg |= PORT_PMUX_PMUXE(UB_RXPERIPHERAL); + } + // tx + UB_COMPORT.DIRCLR.reg = UB_TXBM; + UB_COMPORT.PINCFG[UB_TXPIN].bit.PMUXEN = 1; + if(UB_TXPIN % 2){ + UB_COMPORT.PMUX[UB_TXPIN >> 1].reg |= PORT_PMUX_PMUXO(UB_TXPERIPHERAL); + } else { + UB_COMPORT.PMUX[UB_TXPIN >> 1].reg |= PORT_PMUX_PMUXE(UB_TXPERIPHERAL); + } + // ok, clocks, first line au manuel + // unmask clocks + MCLK->APBAMASK.bit.SERCOM1_ = 1; + GCLK->GENCTRL[UB_GCLKNUM_PICK].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DFLL) | GCLK_GENCTRL_GENEN; + while(GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL(UB_GCLKNUM_PICK)); + GCLK->PCHCTRL[UB_SERCOM_CLK].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(UB_GCLKNUM_PICK); + // then, sercom + while(UB_SER_USART.SYNCBUSY.bit.ENABLE); + UB_SER_USART.CTRLA.bit.ENABLE = 0; + while(UB_SER_USART.SYNCBUSY.bit.SWRST); + UB_SER_USART.CTRLA.bit.SWRST = 1; + while(UB_SER_USART.SYNCBUSY.bit.SWRST); + while(UB_SER_USART.SYNCBUSY.bit.SWRST || UB_SER_USART.SYNCBUSY.bit.ENABLE); + // ctrla + UB_SER_USART.CTRLA.reg = SERCOM_USART_CTRLA_MODE(1) | SERCOM_USART_CTRLA_DORD; + UB_SER_USART.CTRLA.reg |= SERCOM_USART_CTRLA_RXPO(UB_RXPO) | SERCOM_USART_CTRLA_TXPO(0); + //UB_SER_USART.CTRLA.reg |= SERCOM_USART_CTRLA_FORM(1); // enable even parity + // ctrlb + while(UB_SER_USART.SYNCBUSY.bit.CTRLB); + UB_SER_USART.CTRLB.reg = SERCOM_USART_CTRLB_RXEN | SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_CHSIZE(0); + // enable interrupts + NVIC_EnableIRQ(SERCOM1_2_IRQn); // rx interrupts + NVIC_EnableIRQ(SERCOM1_1_IRQn); // transmit complete interrupt + NVIC_EnableIRQ(SERCOM1_0_IRQn); // data register empty interrupts + // set baud + UB_SER_USART.BAUD.reg = ub_baud_val; + // and finally, a kickoff + while(UB_SER_USART.SYNCBUSY.bit.ENABLE); + UB_SER_USART.CTRLA.bit.ENABLE = 1; + // enable rx interrupt, disable dre, txc + UB_SER_USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC; + UB_SER_USART.INTENCLR.reg = SERCOM_USART_INTENCLR_DRE | SERCOM_USART_INTENCLR_TXC; + // to enable tx complete, + //UB_SER_USART.INTENSET.reg = SERCOM_USART_INTENSET_TXC; // now watch transmit complete +} + +// DRE handler +void SERCOM1_0_Handler(void){ + ucBusDrop_dreISR(); +} + +// TXC handler +void SERCOM1_1_Handler(void){ + ucBusDrop_txcISR(); +} + +void SERCOM1_2_Handler(void){ + ucBusDrop_rxISR(); +} +// ------------------------------------ END D51 SPECIFIC +#endif + +#ifdef UCBUS_IS_D21 +// ------------------------------------ D21 SPECIFIC +void setupBusDropUART(void){ + // ------------------------------------------ USART PIN CONFIG + // setup pins as output or inputs, + UB_PORT.DIRSET.reg = UB_TXBM; + UB_PORT.DIRCLR.reg = UB_RXBM; + // pincfg using wrconfig write, s/o + // https://community.atmel.com/forum/sam-d21-spi-interface-bare-code + PORT_WRCONFIG_Type wrconfig; // make new write config object, + wrconfig.bit.WRPMUX = 1; // it will write to pmux + wrconfig.bit.WRPINCFG = 1; // it will write to pinconfig + wrconfig.bit.PMUX = MUX_PA16C_SERCOM1_PAD0; // with this pmux setting + // (putting 16 on c, for ser1) + wrconfig.bit.PMUXEN = 1; // enabling pin muxing + wrconfig.bit.HWSEL = 1; // writing to the upper half of the pins + // and (below) writing these pins, masked and + // shifted into the lower half + wrconfig.bit.PINMASK = (uint16_t)((UB_TXBM | UB_RXBM) >> 16); + UB_PORT.WRCONFIG.reg = wrconfig.reg; // here's the one-shot write, using prep above + // ------------------------------------------ Transmit Driver / Recieve + // Driver Enable + UB_DE_SETUP; + UB_RE_SETUP; + // ------------------------------------------ SPI CONFIG + // now, lettuce unmask the peripheral SER1 + PM->APBCMASK.reg |= PM_APBCMASK_SERCOM1; + // hook the peripheral up to our main CPU clock, which is running at 48mHz + // on the D21 + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | + GCLK_CLKCTRL_ID_SERCOM1_CORE; + while (GCLK->STATUS.bit.SYNCBUSY); + // now we can setup the actual sercom, first do a reset for posterity and + // await complete + UB_SER_USART.CTRLA.bit.SWRST = 1; + while (UB_SER_USART.SYNCBUSY.bit.SWRST); + // pinout: TX on SERx-0, RX on SERx-2 + UB_SER_USART.CTRLA.reg = SERCOM_USART_CTRLA_DORD | // lsb first + SERCOM_USART_CTRLA_MODE(1) | // internal clock + SERCOM_USART_CTRLA_TXPO(0) | // tx on SERx-0 + SERCOM_USART_CTRLA_RXPO(UB_RXPO); // rx on SERx-3 + // enable reciever, transmit, + UB_SER_USART.CTRLB.reg = SERCOM_USART_CTRLB_RXEN | SERCOM_USART_CTRLB_TXEN; + // set BAUD: + UB_SER_USART.BAUD.reg = SERCOM_USART_BAUD_BAUD(ub_baud_val); + // we will use interrupts, + NVIC_EnableIRQ(SERCOM1_IRQn); + // rx interrupt always + UB_SER_USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC; + UB_SER_USART.INTENCLR.reg = SERCOM_USART_INTENCLR_DRE | SERCOM_USART_INTENCLR_TXC; + // UB_SER_USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC; + // ok I think that's it? + UB_SER_USART.CTRLA.bit.ENABLE = 1; + while (UB_SER_USART.SYNCBUSY.bit.ENABLE); +} + +void SERCOM1_Handler(void) { + if (UB_SER_USART.INTFLAG.reg & SERCOM_USART_INTFLAG_RXC) { + ucBusDrop_rxISR(); + } + if (UB_SER_USART.INTFLAG.reg & SERCOM_USART_INTFLAG_DRE && dreISR) { + ucBusDrop_dreISR(); + } + if (UB_SER_USART.INTFLAG.reg & SERCOM_USART_INTFLAG_TXC && txcISR){ + ucBusDrop_txcISR(); + } +} // ------------------------------------------------------ END SERCOM ISR +// ------------------------------------ END D21 SPECIFIC +#endif + +void ucBusDrop_setup(boolean useDipPick, uint8_t ID) { + #ifdef UCBUS_IS_D51 + dip_setup(); + if(useDipPick){ + // set our id, + id = dip_readLowerFive(); // should read lower 4, now that cha / chb + } else { + id = ID; + } + #endif + #ifdef UCBUS_IS_D21 + id = ID; + #endif + if(id > 31){ id = 31; } // max 31 drops, logical addresses 1 - 31 + if(id == 0){ id = 1; } // 0 'tap' is the clk reset, bump up... maybe cause confusion: instead could flash err light + // setup input / etc buffers + for(uint8_t ch = 0; ch < UB_CH_COUNT; ch ++){ + recieveBufferWp[ch] = 0; + inBufferLen[ch] = 0; + outBufferRp[ch] = 0; + outBufferLen[ch] = 0; + rcrxb[ch] = 0; + } + // pick baud, via top level config.h + // baud bb baud + // 63019 for a very safe 115200 + // 54351 for a go-karting 512000 + // 43690 for a trotting pace of 1MHz + // 21845 for the E30 2MHz + // 0 for max-speed 3MHz + switch(UCBUS_BAUD){ + case 1: + ub_baud_val = 43690; + break; + case 2: + ub_baud_val = 21845; + break; + case 3: + ub_baud_val = 0; + break; + default: + ub_baud_val = 43690; + } + // start the hardware + setupBusDropUART(); +} + +uint16_t ucBusDrop_getOwnID(void){ + return id; +} + +void ucBusDrop_rxISR(void){ + // ------------------------------------------------------ DATA INGEST + // get the data + uint8_t data = UB_SER_USART.DATA.reg; + inWord[inWordWp ++] = data; + // tracking delineation + if(inWordWp >= UB_HEAD_BYTES_PER_WORD){ + // always reset, never overwrite inWord[] tail + inWordWp = 0; + // is lastchar the rarechar ? + if(inWord[UB_HEAD_BYTES_PER_WORD - 1] == UCBUS_RARECHAR){ + // carry on, + } else { + // restart on appearance of rarechar + for(uint8_t b = 0; b < UB_HEAD_BYTES_PER_WORD; b ++){ + if(inWord[b] == UCBUS_RARECHAR){ + inWordWp = UB_HEAD_BYTES_PER_WORD - 1 - b; + // in case the above ^ causes some wrapping case (?) don't think it does though + if(inWordWp >= UB_HEAD_BYTES_PER_WORD) inWordWp = 0; + return; + } + } + } + } else { + // was just data byte, bail for now + return; + } + // ------------------------------------------------------ TERMINAL BYTE CASE + // blink on count-of-words: + timeTick ++; + timeBlink ++; + if(timeBlink >= blinkTime){ + CLKLIGHT_TOGGLE; + timeBlink = 0; + } + // extract the header, + inHeader.bytes[0] = inWord[0]; + inHeader.bytes[1] = inWord[1]; + // now, check for our-rx: + if(inHeader.bits.DROPTAP == id){ // -------------------- OUR TAP, TX CASE + // read-in fc states, + rcrxb[0] = inHeader.bits.CH0FC; + rcrxb[1] = inHeader.bits.CH1FC; + // reset out header, + outHeader.bytes[0] = 0; + outHeader.bytes[1] = 0; + // write outgoing flowcontrol terms: if we have unread buffers on these chs, zero space avail: + outHeader.bits.CH0FC = (inBufferLen[0] ? 0 : 1); + outHeader.bits.CH1FC = (inBufferLen[1] ? 0 : 1); + // write also our drop tap... + outHeader.bits.DROPTAP = id; + // check about tx state, + for(uint8_t ch = 0; ch < UB_CH_COUNT; ch ++){ + if(outBufferLen[ch] && rcrxb[ch] > 0){ + // can tx this ch, + uint8_t numTx = outBufferLen[ch] - outBufferRp[ch]; + if(numTx > UB_DATA_BYTES_PER_WORD) numTx = UB_DATA_BYTES_PER_WORD; + // can fill ch-output, + outHeader.bits.CHSELECT = ch; + outHeader.bits.TOKENS = numTx; + // fill bytes, + uint8_t* outB = outBuffer[ch]; + uint16_t outBRp = outBufferRp[ch]; + for(uint8_t b = 0; b < numTx; b ++){ + outWord[b + 2] = outB[outBRp + b]; // fill from ob[2], ob[0] and ob[1] are header + } + outBufferRp[ch] += numTx; + // if numTx < data bytes / frame, packet terminates this word, we reset + if(numTx < UB_DATA_BYTES_PER_WORD){ + outBufferLen[ch] = 0; + outBufferRp[ch] = 0; + } + break; // don't check next ch, + } + } + // stuff header -> word + outWord[0] = outHeader.bytes[0]; + outWord[1] = outHeader.bytes[1]; + // now setup the transmit action: + // set driver on, ship 1st byte, tx rest on DRE edges + outWordRp = 1; // next is [1] + UB_DRIVER_ENABLE; + UB_SER_USART.DATA.reg = outWord[0]; + DRE_ISR_ON; + } // ---------------------------------------------------- END TX CASE + + // ------------------------------------------------------ BEGIN RX TERMS + // the ch that head tx'd to + uint8_t rxCh = inHeader.bits.CHSELECT; + // and # bytes tx'd here + uint8_t numToken = inHeader.bits.TOKENS; + // check for broken numToken count, + if(numToken > UB_DATA_BYTES_PER_WORD) { ERROR(1, "ucbus-drop outsize numToken rx"); return; } + // don't overfill recieve buffer: + if(recieveBufferWp[rxCh] + numToken > UB_BUFSIZE){ + recieveBufferWp[rxCh] = 0; + ERROR(1, "ucbus-drop rx overfull buffer"); + return; + } + // so let's see, if we have any we write them in: + if(numToken > 0){ + uint8_t* rxB = recieveBuffer[rxCh]; + uint16_t rxBWp = recieveBufferWp[rxCh]; + for(uint8_t i = 0; i < numToken; i ++){ + rxB[rxBWp + i] = inWord[2 + i]; + } + recieveBufferWp[rxCh] += numToken; + // set in-packet state, + lastWordHadToken[rxCh] = true; + } + // to find the edge, if we have numToken < numDataBytes and have at least one previous + // token in stream, we have pckt edge + if((numToken < UB_DATA_BYTES_PER_WORD) && lastWordHadToken[rxCh]){ + // reset token edge + lastWordHadToken[rxCh] = false; + // pckt edge on this ch, shift recieveBuffer -> inBuffer and reset write pointer + // unfortunately we have to do this literal-swap thing (some memcpy coming up here), + // but should be able to use a pointer-swapping approach later. here we check if the pck + // is actually for us, then if we can accept it (fc not violated) and then swap it in: + if(recieveBuffer[rxCh][0] == id || recieveBuffer[rxCh][0] == 0){ + // we should accept this, can we? + if(inBufferLen[rxCh] != 0){ // failed to clear before new arrival, FC has failed + recieveBufferWp[rxCh] = 0; + ERROR(0, "ucbus-drop rx fc fails"); + return; + } // end check-for-overwrite + // copy from rxbuffer to inbuffer, it's ours... now FC will go lo, head should not tx *to us* + // before it is cleared with ucBusDrop_readB() + memcpy(inBuffer[rxCh], recieveBuffer[rxCh], recieveBufferWp[rxCh]); + inBufferLen[rxCh] = recieveBufferWp[rxCh]; + recieveBufferWp[rxCh] = 0; + // if CH0, fire "RT" on-rx interrupt, this is where we should want RTOS in the future + if(rxCh == 0){ + ucBusDrop_onPacketARx(&(inBuffer[0][1]), inBufferLen[0] - 1); + // assuming the interrupt is the exit for time being, + inBufferLen[0] = 0; + } + //DEBUG1PIN_OFF; + } else { + // packet wasn't for us, ignore + recieveBufferWp[rxCh] = 0; + } + } // ---------------------------------------------------- END RX TERMS + + // finally (and a bit yikes) we call the onRxISR on *every* word, that's our + // synced system clock: fair warning though, we're firing this pretty late + // esp. if we have also this time transmitted, read in a packet, etc... yikes + ucBusDrop_onRxISR(); +} // end rx-isr + +void ucBusDrop_dreISR(void){ + UB_SER_USART.DATA.reg = outWord[outWordRp ++]; + if(outWordRp >= UB_DROP_BYTES_PER_WORD){ + DRE_ISR_OFF; // clear tx-empty int. + TXC_ISR_ON; // set tx-complete int. + } +} + +void ucBusDrop_txcISR(void){ + UB_SER_USART.INTFLAG.reg = SERCOM_USART_INTFLAG_TXC; // clear flag (so interrupt not called again) + TXC_ISR_OFF; + UB_DRIVER_DISABLE; +} + +// -------------------------------------------------------- ASYNC API + +boolean ucBusDrop_ctrB(void){ + // clear to read a packet when this buffer occupied... + return (inBufferLen[1] > 0); +} + +size_t ucBusDrop_readB(uint8_t *dest){ + if(!ucBusDrop_ctrB()) return 0; + // to read-out, we rm the 0th byte which is addr information + size_t len = inBufferLen[1] - 1; + memcpy(dest, &(inBuffer[1][1]), len); + inBufferLen[1] = 0; // now it's empty + return len; +} + +boolean ucBusDrop_ctsB(void){ + if(outBufferLen[1] == 0 && rcrxb[1] > 0){ + return true; + } else { + return false; + } +} + +void ucBusDrop_transmitB(uint8_t *data, uint16_t len){ + if(!ucBusDrop_ctsB()) return; + // we don't need to decriment our count of the remote rcrxb here + // because we get an update from the head on their actual rcrxb *each time we are tapped* + // however, we cannot tx more than the bufsize, bruh + if(len > UB_BUFSIZE) return; + // copy it into the outBuffer, + memcpy(&(outBuffer[1]), data, len); + // needs to be interrupt safe: transmit could start between these lines + __disable_irq(); + outBufferLen[1] = len; + outBufferRp[1] = 0; + __enable_irq(); +} + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusDrop.h b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusDrop.h new file mode 100644 index 0000000..5861b98 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusDrop.h @@ -0,0 +1,47 @@ +/* +osap/drivers/ucBusDrop.h + +beginnings of a uart-based clock / bus combo protocol + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the squidworks and ponyo +projects. Copyright is retained and must be preserved. The work is provided as +is; no warranty is provided, and users accept all liability. +*/ + +#ifndef UCBUS_DROP_H_ +#define UCBUS_DROP_H_ + +#include "../../../config.h" + +#ifdef UCBUS_IS_DROP + +#include <Arduino.h> +#include "ucBusMacros.h" + +// setup +void ucBusDrop_setup(boolean useDipPick, uint8_t ID); +uint16_t ucBusDrop_getOwnID(void); + +// isrs +void ucBusDrop_rxISR(void); +void ucBusDrop_dreISR(void); +void ucBusDrop_txcISR(void); + +// handlers (define in main.cpp, these are application interfaces) +void ucBusDrop_onRxISR(void); +void ucBusDrop_onPacketARx(uint8_t* inBufferA, volatile uint16_t len); + +// the api, eh +boolean ucBusDrop_ctrB(void); +size_t ucBusDrop_readB(uint8_t *dest); + +// drop cannot tx to channel A +boolean ucBusDrop_ctsB(void); // true if tx buffer empty, +void ucBusDrop_transmitB(uint8_t *data, uint16_t len); + +#endif +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusHead.cpp b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusHead.cpp new file mode 100644 index 0000000..f55b30d --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusHead.cpp @@ -0,0 +1,375 @@ +/* +osap/drivers/ucBusHead.cpp + +uart-based clock / bus combo protocol + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the squidworks and ponyo +projects. Copyright is retained and must be preserved. The work is provided as +is; no warranty is provided, and users accept all liability. +*/ + +#include "ucBusHead.h" + +#ifdef UCBUS_IS_HEAD + +#include <Arduino.h> + +#include "../../../indicators.h" +#include "../../../syserror.h" +#include "../../peripheralNums.h" +#include "../../d51ClockBoss.h" +#include "../../osape/utils/cobs.h" + +// input buffers / space +uint8_t inBuffer[UB_CH_COUNT][UB_MAX_DROPS][UB_BUFSIZE]; // per-drop incoming bytes: 0 will be empty always, no drop here +volatile uint16_t inBufferWp[UB_CH_COUNT][UB_MAX_DROPS]; // per-drop incoming write pointer +volatile uint16_t inBufferLen[UB_CH_COUNT][UB_MAX_DROPS]; // per-drop incoming bytes, len of, set when EOP detected +volatile boolean lastWordHadToken[UB_CH_COUNT][UB_MAX_DROPS]; + +// transmit buffers +uint8_t outBuffer[UB_CH_COUNT][UB_BUFSIZE]; +volatile uint16_t outBufferRp[UB_CH_COUNT]; +volatile uint16_t outBufferLen[UB_CH_COUNT]; + +// flow control, per ch per drop +volatile uint8_t rcrxb[UB_CH_COUNT][UB_MAX_DROPS]; // if 0 donot tx on this ch / this drop + +// currently 'tapped' drop - we loop thru bus drops, +volatile uint8_t currentDropTap = 1; // drop we are currently 'txing' to / drop that will reply on this cycle +volatile uint8_t lastDropTap = 1; + +// outgoing word / stuff info +volatile UCBUS_HEADER_Type outHeader = { .bytes = { 0, 0 } }; +uint8_t outWord[UB_HEAD_BYTES_PER_WORD]; // this goes on-the-line, +volatile uint8_t outWordRp = 0; + +// incoming word +volatile UCBUS_HEADER_Type inHeader = { .bytes = { 0, 0 } }; +uint8_t inWord[UB_DROP_BYTES_PER_WORD]; +uint8_t inWordWp = 0; + +// baudrate +uint32_t ub_baud_val = 0; + +// uart init (file scoped) +void setupBusHeadUART(void){ + // driver output is always on at head, set HI to enable + UB_DE_PORT.DIRSET.reg = UB_DE_BM; + UB_DE_PORT.OUTSET.reg = UB_DE_BM; + // receive output is always on at head, set LO to enable + UB_RE_PORT.DIRSET.reg = UB_RE_BM; + UB_RE_PORT.OUTCLR.reg = UB_RE_BM; + // termination resistor for receipt on bus head is always on, set LO to enable + UB_TE_PORT.DIRSET.reg = UB_TE_BM; + UB_TE_PORT.OUTCLR.reg = UB_TE_BM; + // rx pin setup + UB_COMPORT.DIRCLR.reg = UB_RXBM; + UB_COMPORT.PINCFG[UB_RXPIN].bit.PMUXEN = 1; + if(UB_RXPIN % 2){ + UB_COMPORT.PMUX[UB_RXPIN >> 1].reg |= PORT_PMUX_PMUXO(UB_RXPERIPHERAL); + } else { + UB_COMPORT.PMUX[UB_RXPIN >> 1].reg |= PORT_PMUX_PMUXE(UB_RXPERIPHERAL); + } + // tx + UB_COMPORT.DIRCLR.reg = UB_TXBM; + UB_COMPORT.PINCFG[UB_TXPIN].bit.PMUXEN = 1; + if(UB_TXPIN % 2){ + UB_COMPORT.PMUX[UB_TXPIN >> 1].reg |= PORT_PMUX_PMUXO(UB_TXPERIPHERAL); + } else { + UB_COMPORT.PMUX[UB_TXPIN >> 1].reg |= PORT_PMUX_PMUXE(UB_TXPERIPHERAL); + } + // ok, clocks, first line au manuel + // unmask clocks + MCLK->APBAMASK.bit.SERCOM1_ = 1; + GCLK->GENCTRL[UB_GCLKNUM_PICK].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DFLL) | GCLK_GENCTRL_GENEN; + while(GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL(UB_GCLKNUM_PICK)); + GCLK->PCHCTRL[UB_SERCOM_CLK].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(UB_GCLKNUM_PICK); + // then, sercom: disable and then perform software reset + while(UB_SER_USART.SYNCBUSY.bit.ENABLE); + UB_SER_USART.CTRLA.bit.ENABLE = 0; + while(UB_SER_USART.SYNCBUSY.bit.SWRST); + UB_SER_USART.CTRLA.bit.SWRST = 1; + while(UB_SER_USART.SYNCBUSY.bit.SWRST); + while(UB_SER_USART.SYNCBUSY.bit.SWRST || UB_SER_USART.SYNCBUSY.bit.ENABLE); + // ok, CTRLA: + UB_SER_USART.CTRLA.reg = SERCOM_USART_CTRLA_MODE(1) | SERCOM_USART_CTRLA_DORD; // data order (1: lsb first) and mode (?) + UB_SER_USART.CTRLA.reg |= SERCOM_USART_CTRLA_RXPO(UB_RXPO) | SERCOM_USART_CTRLA_TXPO(0); // rx and tx pinout options + //UB_SER_USART.CTRLA.reg |= SERCOM_USART_CTRLA_FORM(1); // turn on parity: parity is even by default (set in CTRLB), leave that + // CTRLB has sync bit, + while(UB_SER_USART.SYNCBUSY.bit.CTRLB); + // recieve enable, txenable, character size 8bit, + UB_SER_USART.CTRLB.reg = SERCOM_USART_CTRLB_RXEN | SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_CHSIZE(0); + // CTRLC: setup 32 bit on read and write: + // UBH_SER_USART.CTRLC.reg = SERCOM_USART_CTRLC_DATA32B(3); + // enable interrupts + NVIC_EnableIRQ(SERCOM1_2_IRQn); // rx interrupts + NVIC_EnableIRQ(SERCOM1_1_IRQn); // transmit complete interrupt + NVIC_EnableIRQ(SERCOM1_0_IRQn); // data register empty interrupts + // set baud + UB_SER_USART.BAUD.reg = ub_baud_val; + // and finally, a kickoff + while(UB_SER_USART.SYNCBUSY.bit.ENABLE); + UB_SER_USART.CTRLA.bit.ENABLE = 1; + // enable the RXC interrupt, disable TXC, DRE + UB_SER_USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC; + UB_SER_USART.INTENCLR.reg = SERCOM_USART_INTENCLR_DRE | SERCOM_USART_INTENCLR_TXC; +} + +// TX Handler, for second bytes initiated by timer, +// void SERCOM1_0_Handler(void){ +// ucBusHead_txISR(); +// } + +// startup, +void ucBusHead_setup(void){ + // clear buffers to begin, + for(uint8_t ch = 0; ch < UB_CH_COUNT; ch ++){ + outBufferLen[ch] = 0; + outBufferRp[ch] = 0; + for(uint8_t d = 0; d < UB_MAX_DROPS; d ++){ + inBufferLen[ch][d] = 0; // zero all input buffers, write-in pointers + inBufferWp[ch][d] = 0; + rcrxb[ch][d] = 0; // assume zero space to tx to all drops until they report otherwise + lastWordHadToken[ch][d] = false; + } + } + // pick baud, via top level config.h + // baud bb baud + // 63019 for a very safe 115200 + // 54351 for a go-karting 512000 + // 43690 for a trotting pace of 1MHz + // 21845 for the E30 2MHz + // 0 for max-speed 3MHz + switch(UCBUS_BAUD){ + case 1: + ub_baud_val = 43690; + break; + case 2: + ub_baud_val = 21845; + break; + case 3: + ub_baud_val = 0; + break; + default: + ub_baud_val = 43690; + } + // start the uart, + setupBusHeadUART(); + // ! alert ! need to setup timer in main.cpp +} + +void ucBusHead_timerISR(void){ + // increment / wrap time division for drops + currentDropTap ++; + if(currentDropTap > UB_MAX_DROPS){ // recall that tapping '0' should operate the clock reset, addr 0 doesn't exist + currentDropTap = 1; + } + // reset the outgoing header, + outHeader.bytes[0] = 0; + outHeader.bytes[1] = 0; + // write in drop tap, flowcontrol rules + outHeader.bits.CH0FC = (inBufferLen[0][currentDropTap] ? 0 : 1); + outHeader.bits.CH1FC = (inBufferLen[1][currentDropTap] ? 0 : 1); + outHeader.bits.DROPTAP = currentDropTap; + // now we check if we can tx on either channel, + for(uint8_t ch = 0; ch < UB_CH_COUNT; ch ++){ + // do we have ahn pck to be tx'ing, and is flowcontrol condition met + // FC: outBuffer[x][0] is the 'addr' we are tx'ing to, so indexes relevant rcrxb as well + // ! and, when we broadcast (addr '0') we ignore FC rules, so: + if(outBufferLen[ch] > 0 && (rcrxb[ch][outBuffer[ch][0]] | (outBuffer[ch][0] == 0))){ + // ch has incomplete-tx of some packet + // count them, max we will transmit is from word length: + uint8_t numTx = outBufferLen[ch] - outBufferRp[ch]; + if(numTx > UB_DATA_BYTES_PER_WORD) numTx = UB_DATA_BYTES_PER_WORD; + // we can write the 2nd header byte (ch select and # of words) + outHeader.bits.CHSELECT = ch; + outHeader.bits.TOKENS = numTx; + // fill bytes, + uint8_t *outB = outBuffer[ch]; + uint16_t outBRp = outBufferRp[ch]; + for(uint8_t b = 0; b < numTx; b ++){ + outWord[b + 2] = outB[outBRp + b]; + } + outBufferRp[ch] += numTx; + // if numTx < data words per packet, packet will terminate this frame, we can reset + // recipient uses the tailing '0' token-d byte to delineate packets (COBS for words) + if(numTx < UB_DATA_BYTES_PER_WORD) { + // flow control: we have tx'd to whichever drop... the head recieves updates from drops + // for rcrxb, but they're potentially spaced 1/64 turns of this ISR, + // so we need to update our accounting of their space-available-to-receive. + // recall also that rcrxb is parallel per channel *and* per drop + rcrxb[ch][outBuffer[ch][0]] = 0; // 0 space available here now, + outBufferLen[ch] = 0; // reset also the outgoing buffer, + outBufferRp[ch] = 0; // and it's read-out ptr + } + break; // don't check the next ch, outword occupied by this + } + } + // stuff header -> outWord + outWord[0] = outHeader.bytes[0]; + outWord[1] = outHeader.bytes[1]; + // insert rarechar + outWord[UB_HEAD_BYTES_PER_WORD - 1] = UCBUS_RARECHAR; + // now we transmit: + UB_SER_USART.DATA.reg = outWord[0]; + outWordRp = 1; // next up, + UB_SER_USART.INTENSET.reg = SERCOM_USART_INTENSET_DRE; +} + +// data register empty: bang next byte in +void SERCOM1_0_Handler(void){ + UB_SER_USART.DATA.reg = outWord[outWordRp ++]; + if(outWordRp >= UB_HEAD_BYTES_PER_WORD){ // if we've transmitted them all, + UB_SER_USART.INTENCLR.reg = SERCOM_USART_INTENCLR_DRE; // clear tx-data-empty interrupt + UB_SER_USART.INTENSET.reg = SERCOM_USART_INTENSET_TXC; // set tx-complete interrupt + } +} + +// transmit complete interrupt: delimit incoming words +void SERCOM1_1_Handler(void){ + UB_SER_USART.INTFLAG.bit.TXC = 1; + UB_SER_USART.INTENCLR.reg = SERCOM_USART_INTENCLR_TXC; + // this means the latest word transmit is done, next byte on the line should be 1st in + // upstream pckt + lastDropTap = currentDropTap; + inWordWp = 0; +} + +// rx handler, for incoming +void SERCOM1_2_Handler(void){ + ucBusHead_rxISR(); +} + +void ucBusHead_rxISR(void){ + // shift the byte -> incoming, + inWord[inWordWp ++] = UB_SER_USART.DATA.reg; + if(inWordWp >= UB_DROP_BYTES_PER_WORD){ + // that's ^ word delineation, so our drop tap should be: + uint8_t rxDrop = lastDropTap; + // check that, + inHeader.bytes[0] = inWord[0]; + inHeader.bytes[1] = inWord[1]; + if(inHeader.bits.DROPTAP != rxDrop){ return; } // bail on mismatch, was a bad / misaligned word + // update our buffer states, + rcrxb[0][rxDrop] = inHeader.bits.CH0FC; + rcrxb[1][rxDrop] = inHeader.bits.CH1FC; + // the ch that drop tx'd on + uint8_t rxCh = inHeader.bits.CHSELECT; + // has anything? + uint8_t numToken = inHeader.bits.TOKENS; + // check for broken numToken count, + if(numToken > UB_DATA_BYTES_PER_WORD) { ERROR(1, "ucbus-head outsize numToken rx"); return; } + // if we are filling this buffer, but it's already occupied, fc has failed and we + if(inBufferLen[rxCh][rxDrop] != 0){ ERROR(0, "ucbus-head rx FC broken"); return; } + // donot write past buffer size, + if(inBufferWp[rxCh][rxDrop] + numToken > UB_BUFSIZE){ + inBufferWp[rxCh][rxDrop] = 0; + ERROR(0, "ucbus-head rx packet too-long"); + return; + } + // shift bytes into rx buffer + uint8_t * inB = inBuffer[rxCh][rxDrop]; + uint16_t inBWp = inBufferWp[rxCh][rxDrop]; + for(uint8_t i = 0; i < numToken; i ++){ + inB[inBWp + i] = inWord[2 + i]; + } + inBufferWp[rxCh][rxDrop] += numToken; + // to find packet edge, if we have numToken > numDataBytes and at least + // one other in the stream, we have pckt edge + if(numToken > 0) lastWordHadToken[rxCh][rxDrop] = true; + if(numToken < UB_DATA_BYTES_PER_WORD && lastWordHadToken[rxCh][rxDrop]){ + // packet edge, reset token edge + lastWordHadToken[rxCh][rxDrop] = false; + // pckt edge is here, set fullness, otherwise we're done, + // application responsible for shifting it out and + // inBufferLen is what we read to determine FC condition + inBufferLen[rxCh][rxDrop] = inBufferWp[rxCh][rxDrop]; + inBufferWp[rxCh][rxDrop] = 0; + } + } +} + +// -------------------------------------------------------- API + +// clear to read ? channel select ? +#warning TODO: bus head read per-ch: yep, should be a or b, +boolean ucBusHead_ctr(uint8_t drop){ + // called once per loop, so here's where this debug goes: + //(rcrxb[1] > 0) ? DEBUG2PIN_OFF : DEBUG2PIN_ON; // for psu-breakout, + //(rcrxb[2] > 0) ? DEBUG3PIN_OFF : DEBUG3PIN_ON; // pin off is light on + if(drop >= UB_MAX_DROPS) return false; + if(inBufferLen[1][drop] > 0){ + return true; + } else { + return false; + } +} + +#warning TODO: bus head osap-read-in per-ch ? currently fixed to chb osap reads +size_t ucBusHead_read(uint8_t drop, uint8_t *dest){ + if(!ucBusHead_ctr(drop)) return 0; + size_t len = inBufferLen[1][drop]; + memcpy(dest, inBuffer[1][drop], len); + __disable_irq(); // again... do we need these ? big brain time + inBufferLen[1][drop] = 0; + inBufferWp[1][drop] = 0; + __enable_irq(); + return len; +} + +boolean ucBusHead_ctsA(void){ + if(outBufferLen[0] == 0){ + // only condition is that our transmit buffer is zero, are not currently tx'ing on this channel + return true; + } else { + return false; + } +} + +boolean ucBusHead_ctsB(uint8_t drop){ + // escape states + if(outBufferLen[1] == 0 && rcrxb[1][drop] > 0){ + return true; + } else { + return false; + } +} + +#warning TODO: we have this awkward +1 in the buffer / segsize, vs what the app. sees... +void ucBusHead_transmitA(uint8_t *data, uint16_t len){ + if(!ucBusHead_ctsA()) return; + if(len > UB_BUFSIZE + 1) return; // none over buf size + // 1st byte: broadcast identifier + outBuffer[0][0] = 0; + // copy in @ 1th byte + // we *shouldn't* have to guard against the memcpy, god bless, since + // the bus shouldn't be touching this so long as our outBufferLen is 0, + // which - we are guarded against that w/ the flowcontrol check above + memcpy(&(outBuffer[0][1]), data, len); + // len set + __disable_irq(); + outBufferLen[0] = len + 1; + outBufferRp[0] = 0; + __enable_irq(); +} + +void ucBusHead_transmitB(uint8_t *data, uint16_t len, uint8_t drop){ + if(!ucBusHead_ctsB(drop)) return; + if(len > UB_BUFSIZE + 1) return; // same as above + __disable_irq(); + // 1st byte: drop identifier + outBuffer[1][0] = drop; + // copy in @ 1th byte + memcpy(&(outBuffer[1][1]), data, len); + // length set + outBufferLen[1] = len + 1; // + 1 for the addr... + // read-out ptr reset + outBufferRp[1] = 0; + __enable_irq(); +} + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusHead.h b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusHead.h new file mode 100644 index 0000000..abc8e13 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusHead.h @@ -0,0 +1,44 @@ +/* +osap/drivers/ucBusHead.h + +beginnings of a uart-based clock / bus combo protocol + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the squidworks and ponyo +projects. Copyright is retained and must be preserved. The work is provided as +is; no warranty is provided, and users accept all liability. +*/ + +#ifndef UCBUS_HEAD_H_ +#define UCBUS_HEAD_H_ + +#include "../../../config.h" + +#ifdef UCBUS_IS_HEAD + +#include <Arduino.h> +#include "ucBusMacros.h" + +// setup, +void ucBusHead_setup(void); + +// need to call the main timer isr at some rate, +void ucBusHead_timerISR(void); +void ucBusHead_rxISR(void); +void ucBusHead_txISR(void); + +// ub interface, +boolean ucBusHead_ctr(uint8_t drop); // is there ahn packet to read at this drop +size_t ucBusHead_read(uint8_t drop, uint8_t *dest); // get 'them bytes fam +//size_t ucBusHead_readPtr(uint8_t* drop, uint8_t** dest, unsigned long *pat); // vport interface, get next to handle... +//void ucBusHead_clearPtr(uint8_t drop); +boolean ucBusHead_ctsA(void); // return true if TX complete / buffer ready +boolean ucBusHead_ctsB(uint8_t drop); +void ucBusHead_transmitA(uint8_t *data, uint16_t len); // ship bytes: broadcast to all +void ucBusHead_transmitB(uint8_t *data, uint16_t len, uint8_t drop); // ship bytes: 0-14: individual drop, 15: broadcast + +#endif +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusMacros.h b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusMacros.h new file mode 100644 index 0000000..64bc5be --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucBusMacros.h @@ -0,0 +1,125 @@ +/* +ucBusMacros.h + +config / utes for the uart-clocked bus + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2021 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the squidworks and ponyo +projects. Copyright is retained and must be preserved. The work is provided as +is; no warranty is provided, and users accept all liability. +*/ + + +#ifndef UCBUS_MACROS_H_ +#define UCBUS_MACROS_H_ + +#include "../../../config.h" +#include <Arduino.h> + +// ---------------------------------------------- INFO + +/* + assuming for now there is one bus PHY per micro, + this is for shared hardware config *and* macros to operate + / read / write on the bus +*/ + +// ---------------------------------------------- BUFFER / DROP SIZES / RATES +// the channel count: 2 +#define UB_CH_COUNT 2 +// the size of each buffer: also the maximum segment size +#define UB_BUFSIZE 256 +// max. # of drops on the bus, just swapping from top level config.h +#define UB_MAX_DROPS UCBUS_MAX_DROPS +// with a fixed 2-byte header, we can have some max # of data bytes, +// this is *probably* going to stay at 10, but might fluxuate a little +#define UB_DATA_BYTES_PER_WORD 12 +#define UB_HEAD_BYTES_PER_WORD (UB_DATA_BYTES_PER_WORD + 3) // + 2 header, + 1 rare character +#define UB_DROP_BYTES_PER_WORD (UB_DATA_BYTES_PER_WORD + 2) // + 2 header + +// ---------------------------------------------- DATA WORDS -> INFO + +typedef union { + struct { + uint8_t CH0FC:1; // bit: channel 0 reported flowcontrol (1: full, 0: cts) + uint8_t CH1FC:1; // bit: channel 1 reported flowcontrol + uint8_t DROPTAP:6; // 0-63: time division drop + uint8_t CHSELECT:1; // bit: channel select: 1 for ch1, 0 ch0 + uint8_t RESERVED:3; // not currently used, + uint8_t TOKENS:4; // 0-15: how many bytes in word are real data bytes + } bits; + uint8_t bytes[2]; +} UCBUS_HEADER_Type; + +#define UCBUS_RARECHAR 0b10101010 + +// ---------------------------------------------- PORT / PIN CONFIGS +#ifdef UCBUS_IS_D51 +// ------------------------------------ D51 HAL +#define UB_SER_USART SERCOM1->USART +#define UB_SERCOM_CLK SERCOM1_GCLK_ID_CORE +#define UB_GCLKNUM_PICK 7 +#define UB_COMPORT PORT->Group[0] +#define UB_TXPIN 16 // x-0 +#define UB_TXBM (uint32_t)(1 << UB_TXPIN) +#define UB_RXPIN 18 // x-2 +#define UB_RXBM (uint32_t)(1 << UB_RXPIN) +#define UB_RXPO 2 // RX on SER-2 +#define UB_TXPERIPHERAL PERIPHERAL_C +#define UB_RXPERIPHERAL PERIPHERAL_C + +// the data enable / reciever enable pins were modified between module circuit +// revisions: the board w/ an SMT JTAG header is "the OG" module, +// these are from board-level config +#ifdef IS_OG_MODULE +#define UB_DE_PIN 16 // driver output enable: set HI to enable, LO to tri-state the driver +#define UB_DE_PORT PORT->Group[1] +#define UB_RE_PIN 19 // receiver output enable, set LO to enable the RO, set HI to tri-state RO +#define UB_RE_PORT PORT->Group[0] +#else +#define UB_DE_PIN 19 // driver output enable: set HI to enable, LO to tri-state the driver +#define UB_DE_PORT PORT->Group[0] +#define UB_RE_PIN 9 // receiver output enable, set LO to enable the RO, set HI to tri-state RO +#define UB_RE_PORT PORT->Group[1] +#endif + +#define UB_TE_PIN 17 // termination enable, drive LO to enable to internal termination resistor, HI to disable +#define UB_TE_PORT PORT->Group[0] +#define UB_TE_BM (uint32_t)(1 << UB_TE_PIN) +#define UB_RE_BM (uint32_t)(1 << UB_RE_PIN) +#define UB_DE_BM (uint32_t)(1 << UB_DE_PIN) + +#define UB_DRIVER_ENABLE UB_DE_PORT.OUTSET.reg = UB_DE_BM +#define UB_DRIVER_DISABLE UB_DE_PORT.OUTCLR.reg = UB_DE_BM +// ------------------------------------ END D51 HAL +#endif + +#ifdef UCBUS_IS_D21 +// ------------------------------------ D21 HAL +#define UB_SER_USART SERCOM1->USART +#define UB_PORT PORT->Group[0] +#define UB_TXPIN 16 +#define UB_TXBM (uint32_t)(1 << UB_TXPIN) +#define UB_RXPIN 19 +#define UB_RXBM (uint32_t)(1 << UB_RXPIN) +#define UB_RXPO 3 // RX is on SER1-3 +#define UB_TXPERIPHERAL PERIPHERAL_C +#define UB_RXPERIPHERAL PERIPHERAL_C +// data enable, recieve enable pins +#define UB_DEPIN 17 +#define UB_DEBM (uint32_t)(1 << UB_DEPIN) +#define UB_REPIN 18 +#define UB_REBM (uint32_t)(1 << UB_REPIN) +#define UB_DRIVER_ENABLE UB_PORT.OUTSET.reg = UB_DEBM +#define UB_DRIVER_DISABLE UB_PORT.OUTCLR.reg = UB_DEBM +#define UB_DE_SETUP UB_PORT.DIRSET.reg = UB_DEBM; UB_DRIVER_DISABLE +#define UB_RECIEVE_ENABLE UB_PORT.OUTCLR.reg = UB_REBM +#define UB_RECIEVE_DISABLE UB_PORT.OUTSET.reg = UB_REBM +#define UB_RE_SETUP UB_PORT.DIRSET.reg = UB_REBM; UB_RECIEVE_ENABLE +// ------------------------------------ END D21 HAL +#endif + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/ucbusDipConfig.cpp b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucbusDipConfig.cpp new file mode 100644 index 0000000..08742fd --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucbusDipConfig.cpp @@ -0,0 +1,61 @@ +// DIPs +#include "ucBusDipConfig.h" + +#ifdef UCBUS_IS_D51 +#ifdef UCBUS_IS_DROP + +void dip_setup(void){ + // set direction in, + DIP_PORT.DIRCLR.reg = D_BM(D0_PIN) | D_BM(D1_PIN) | D_BM(D2_PIN) | D_BM(D3_PIN) | D_BM(D4_PIN) | D_BM(D5_PIN) | D_BM(D6_PIN) | D_BM(D7_PIN); + // enable in, + DIP_PORT.PINCFG[D0_PIN].bit.INEN = 1; + DIP_PORT.PINCFG[D1_PIN].bit.INEN = 1; + DIP_PORT.PINCFG[D2_PIN].bit.INEN = 1; + DIP_PORT.PINCFG[D3_PIN].bit.INEN = 1; + DIP_PORT.PINCFG[D4_PIN].bit.INEN = 1; + DIP_PORT.PINCFG[D5_PIN].bit.INEN = 1; + DIP_PORT.PINCFG[D6_PIN].bit.INEN = 1; + DIP_PORT.PINCFG[D7_PIN].bit.INEN = 1; + // enable pull, + DIP_PORT.PINCFG[D0_PIN].bit.PULLEN = 1; + DIP_PORT.PINCFG[D1_PIN].bit.PULLEN = 1; + DIP_PORT.PINCFG[D2_PIN].bit.PULLEN = 1; + DIP_PORT.PINCFG[D3_PIN].bit.PULLEN = 1; + DIP_PORT.PINCFG[D4_PIN].bit.PULLEN = 1; + DIP_PORT.PINCFG[D5_PIN].bit.PULLEN = 1; + DIP_PORT.PINCFG[D6_PIN].bit.PULLEN = 1; + DIP_PORT.PINCFG[D7_PIN].bit.PULLEN = 1; + // 'pull' references the value set in the 'out' register, so to pulldown: + DIP_PORT.OUTCLR.reg = D_BM(D0_PIN) | D_BM(D1_PIN) | D_BM(D2_PIN) | D_BM(D3_PIN) | D_BM(D4_PIN) | D_BM(D5_PIN) | D_BM(D6_PIN) | D_BM(D7_PIN); +} + +uint8_t dip_readLowerFive(void){ + uint32_t bits[5] = {0,0,0,0,0}; + if(DIP_PORT.IN.reg & D_BM(D7_PIN)) { bits[0] = 1; } + if(DIP_PORT.IN.reg & D_BM(D6_PIN)) { bits[1] = 1; } + if(DIP_PORT.IN.reg & D_BM(D5_PIN)) { bits[2] = 1; } + if(DIP_PORT.IN.reg & D_BM(D4_PIN)) { bits[3] = 1; } + if(DIP_PORT.IN.reg & D_BM(D3_PIN)) { bits[4] = 1; } + /* + bits[0] = (DIP_PORT.IN.reg & D_BM(D7_PIN)) >> D7_PIN; + bits[1] = (DIP_PORT.IN.reg & D_BM(D6_PIN)) >> D6_PIN; + bits[2] = (DIP_PORT.IN.reg & D_BM(D5_PIN)) >> D5_PIN; + bits[3] = (DIP_PORT.IN.reg & D_BM(D4_PIN)) >> D4_PIN; + bits[4] = (DIP_PORT.IN.reg & D_BM(D3_PIN)) >> D3_PIN; + */ + // not sure why I wrote this as uint32 (?) + uint32_t word = 0; + word = word | (bits[4] << 4) | (bits[3] << 3) | (bits[2] << 2) | (bits[1] << 1) | (bits[0] << 0); + return (uint8_t)word; +} + +boolean dip_readPin0(void){ + return DIP_PORT.IN.reg & D_BM(D0_PIN); +} + +boolean dip_readPin1(void){ + return DIP_PORT.IN.reg & D_BM(D1_PIN); +} + +#endif +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/ucbusDipConfig.h b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucbusDipConfig.h new file mode 100644 index 0000000..9632a03 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/ucbusDipConfig.h @@ -0,0 +1,36 @@ +// DIP switch HAL macros +// pardon the mis-labeling: on board, and in the schem, these are 1-8, +// here they will be 0-7 + +// note: these are 'on' hi by default, from the factory. +// to set low, need to turn the internal pulldown on + +#ifndef UCBUS_DIP_CONFIG_H_ +#define UCBUS_DIP_CONFIG_H_ + +#include "../../../config.h" + +#ifdef UCBUS_IS_D51 +#ifdef UCBUS_IS_DROP + +#include <Arduino.h> + +#define D0_PIN 5 +#define D1_PIN 4 +#define D2_PIN 3 +#define D3_PIN 2 +#define D4_PIN 1 +#define D5_PIN 0 +#define D6_PIN 31 +#define D7_PIN 30 +#define DIP_PORT PORT->Group[1] +#define D_BM(val) ((uint32_t)(1 << val)) + +void dip_setup(void); +uint8_t dip_readLowerFive(void); // id, five bits, 0: clock reset, 1:31: drop ids, +boolean dip_readPin0(void); // bus-head (hi) or bus-drop (lo) (not used: firmware config drop or head) +boolean dip_readPin1(void); // if bus-drop, te-enable (hi) or no (lo) + +#endif +#endif +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusDrop.cpp b/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusDrop.cpp new file mode 100644 index 0000000..b6200bd --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusDrop.cpp @@ -0,0 +1,111 @@ +/* +osap/vport_ucbus_drop.cpp + +virtual port, bus drop, ucbus + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2020 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#include "vt_ucBusDrop.h" + +#ifdef UCBUS_IS_DROP + +#include "../../../indicators.h" +#include "../../../syserror.h" +#include "ucBusDrop.h" + +vertex_t _vt_ucBusDrop; +vertex_t* vt_ucBusDrop = &_vt_ucBusDrop; + +// badness, direct write in future +uint8_t _tempBuffer[1024]; + +void vt_ucBusDrop_setup(boolean useDipPick, uint8_t ID){ + _vt_ucBusDrop.type = VT_TYPE_VBUS; + _vt_ucBusDrop.name = "ucBusDrop"; + _vt_ucBusDrop.loop = &vt_ucBusDrop_loop; + _vt_ucBusDrop.cts = &vt_ucBusDrop_cts; + _vt_ucBusDrop.send = &vt_ucBusDrop_send; + stackReset(&_vt_ucBusDrop); + // start it: use DIP + ucBusDrop_setup(useDipPick, ID); + _vt_ucBusDrop.ownRxAddr = ucBusDrop_getOwnID(); +} + +void vt_ucBusDrop_loop(void){ + // will want to shift(?) from ucbus inbuffer to vertex origin stack + if(ucBusDrop_ctrB()){ + // find a slot, + uint8_t slot = 0; + if(stackEmptySlot(&_vt_ucBusDrop, VT_STACK_ORIGIN)){ + // copy in to origin stack + uint16_t len = ucBusDrop_readB(_tempBuffer); + stackLoadSlot(&_vt_ucBusDrop, VT_STACK_ORIGIN, _tempBuffer, len); + } else { + // no empty space, will wait in bus + } + } +} + +boolean vt_ucBusDrop_cts(uint8_t rxAddr){ + // immediately clear? & transmit only to head + if(rxAddr == 0 && ucBusDrop_ctsB()){ + return true; + } else { + return false; + } +} + +void vt_ucBusDrop_send(uint8_t* data, uint16_t len, uint8_t rxAddr){ + // can't tx not-to-the-head, will drop pck + if(rxAddr != 0) return; + // if the bus is ready, drop it, + if(ucBusDrop_ctsB()){ + ucBusDrop_transmitB(data, len); + } else { + sysError("ubd tx while not clear"); // should be a check immediately beforehand ... + } +} + +/* +void VPort_UCBus_Drop::read(uint8_t** pck, pckm_t* pckm){ + unsigned long pat = 0; + pckm->vpa = this; + pckm->len = ucBusDrop->read_b_ptr(pck, &pat); + pckm->at = pat; + pckm->location = 0; + pckm->txAddr = 0; // the head transmitted to us, + pckm->rxAddr = ucBusDrop->id + 1; + return; +} +*/ + +/* +// used to have a vertex local outgoing buffer, this can be the vertex's destination buffer afaiak +void VPort_UCBus_Drop::init(void){ + ucBusDrop->init(true, 0); + for(uint8_t i = 0; i < UBD_OUTBUFFER_COUNT; i ++){ + _outBufferLen[i] = 0; + } +} + +void VPort_UCBus_Drop::loop(void){ + // check our transmit buffer, + if(ucBusDrop->cts()){ + for(uint8_t i = 0; i < UBD_OUTBUFFER_COUNT; i ++){ + if(_outBufferLen[i] > 0){ + ucBusDrop->transmit(_outBuffer[i], _outBufferLen[i]); + _outBufferLen[i] = 0; + } + } + } +} +*/ + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusDrop.h b/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusDrop.h new file mode 100644 index 0000000..5ce6718 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusDrop.h @@ -0,0 +1,33 @@ +/* +osap/vport_ucbus_drop.h + +virtual port, bus drop, ucbus + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2020 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#ifndef VPORT_UCBUS_HEAD_H_ +#define VPORT_UCBUS_HEAD_H_ + +#include "../../../config.h" + +#ifdef UCBUS_IS_DROP + +#include <Arduino.h> +#include "../../osape/osap/vertex.h" + +void vt_ucBusDrop_setup(boolean useDipPick, uint8_t ID); +void vt_ucBusDrop_loop(); +boolean vt_ucBusDrop_cts(uint8_t rxAddr); +void vt_ucBusDrop_send(uint8_t* data, uint16_t len, uint8_t rxAddr); + +extern vertex_t* vt_ucBusDrop; + +#endif +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusHead.cpp b/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusHead.cpp new file mode 100644 index 0000000..09742a3 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusHead.cpp @@ -0,0 +1,82 @@ +/* +osap/vt_ucBusHead.cpp + +virtual port, bus head / host + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#include "vt_ucBusHead.h" + +#ifdef UCBUS_IS_HEAD + +#include "../../../indicators.h" +#include "../../../syserror.h" +#include "ucBusHead.h" + +// uuuuh, local thing and extern, not sure abt this +// file scoped object, global ptr +vertex_t _vt_ucBusHead; +vertex_t* vt_ucBusHead = &_vt_ucBusHead; + +// locally, track which drop we shifted in a packet from last +uint8_t _lastDropHandled = 0; + +// badness, should remove w/ direct copy in API eventually +uint8_t _tempBuffer[1024]; + +void vt_ucBusHead_setup(void) { + _vt_ucBusHead.type = VT_TYPE_VBUS; + _vt_ucBusHead.name = "ucBusHead"; + _vt_ucBusHead.loop = &vt_ucBusHead_loop; + _vt_ucBusHead.cts = &vt_ucBusHead_cts; + _vt_ucBusHead.send = &vt_ucBusHead_send; + stackReset(&_vt_ucBusHead); + // start ucbus + ucBusHead_setup(); // todo: rewrite as c object, not class +} + +void vt_ucBusHead_loop() { + // we need to shift items from the bus into the origin stack here + // we can shift multiple in per turn, if stack space exists + uint8_t drop = _lastDropHandled; + for (uint8_t i = 1; i < UB_MAX_DROPS; i++) { + drop++; + if (drop >= UB_MAX_DROPS) { + drop = 1; + } + if (ucBusHead_ctr(drop)) { + // find a stack slot, + uint8_t slot = 0; + if (stackEmptySlot(&_vt_ucBusHead, VT_STACK_ORIGIN)) { + // copy it in, + uint16_t len = ucBusHead_read(drop, _tempBuffer); + stackLoadSlot(&_vt_ucBusHead, VT_STACK_ORIGIN, _tempBuffer, len); + } else { + // no more empty spaces this turn, continue + return; + } + } + } +} + +boolean vt_ucBusHead_cts(uint8_t rxAddr) { + // mapping rxAddr in osap space (where 0 is head) to ucbus drop-id space... + return ucBusHead_ctsB(rxAddr); +} + +void vt_ucBusHead_send(uint8_t* data, uint16_t len, uint8_t rxAddr) { + if (rxAddr == 0) { + sysError("attempt to busf from head to self"); + return; + } + ucBusHead_transmitB(data, len, rxAddr); +} + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusHead.h b/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusHead.h new file mode 100644 index 0000000..4e88789 --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/ucbus/vt_ucBusHead.h @@ -0,0 +1,33 @@ +/* +osap/vt_ucBusHead.h + +virtual port, bus head, ucbus + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2020 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#ifndef VPORT_UCBUS_HEAD_H_ +#define VPORT_UCBUS_HEAD_H_ + +#include "../../../config.h" + +#ifdef UCBUS_IS_HEAD + +#include <Arduino.h> +#include "../../osape/osap/vertex.h" + +void vt_ucBusHead_setup(void); +void vt_ucBusHead_loop(); +boolean vt_ucBusHead_cts(uint8_t rxAddr); +void vt_ucBusHead_send(uint8_t* data, uint16_t len, uint8_t rxAddr); + +extern vertex_t* vt_ucBusHead; + +#endif +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/vertexConfig.h b/firmware/fab-step/src/osape-d21/vertices/vertexConfig.h new file mode 100644 index 0000000..8345ebc --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/vertexConfig.h @@ -0,0 +1,10 @@ +// vertex config for D21... + +#ifndef VERTEX_CONFIG_H_ +#define VERTEX_CONFIG_H_ + +#define VT_SLOTSIZE 256 +#define VT_STACKSIZE 2 // must be >= 2 for ringbuffer operation +#define VT_MAXCHILDREN 16 + +#endif \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/vt_usbSerial.cpp b/firmware/fab-step/src/osape-d21/vertices/vt_usbSerial.cpp new file mode 100644 index 0000000..69c08ce --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/vt_usbSerial.cpp @@ -0,0 +1,112 @@ +/* +osap/vt_usbSerial.cpp + +serial port, virtualized +does single-ended flowcontrol (from pc -> here) + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#include "vt_usbSerial.h" +//#include "../osape/osap/ts.h" +//#include "../../drivers/indicators.h" +#include "../osape/utils/cobs.h" +#include "../../syserror.h" + +vertex_t _vt_usbSerial; +vertex_t* vt_usbSerial = &_vt_usbSerial; + +// incoming +uint8_t _inBuffer[VPUSB_SPACE_SIZE]; +uint8_t _pwp = 0; // packet to write into +uint16_t _bwp = 0; // byte to write at + +// acks left to transmit +uint8_t _acksAwaiting = 0; + +// outgoing +uint8_t _encodedOut[VPUSB_SPACE_SIZE]; +uint8_t _encodedIn[VPUSB_SPACE_SIZE]; + +void vt_usbSerial_setup(void){ + //vt_usbSerial = &vt; + // configure self, + _vt_usbSerial.type = VT_TYPE_VPORT; + _vt_usbSerial.name = "usbSerial"; + _vt_usbSerial.loop = &vt_usbSerial_loop; + _vt_usbSerial.cts = &vt_usbSerial_cts; + _vt_usbSerial.send = &vt_usbSerial_send; + _vt_usbSerial.onOriginStackClear = &vt_usbSerial_onOriginStackClear; + stackReset(&_vt_usbSerial); + // start arduino serial object + Serial.begin(9600); +} + +void vt_usbSerial_loop(){ + // want to count through previous occupied-ness states, and on falling edge + // of stack education, ack... + // ack if necessary (if didn't tx ack out on reciprocal send last) + if(_acksAwaiting){ + vt_usbSerial_send(_encodedOut, 0, 0); + } + // then check about new messages: + while(Serial.available()){ + _inBuffer[_bwp] = Serial.read(); + if(_inBuffer[_bwp] == 0){ + // end of COBS-encoded frame, + // decode into packet slot, record length (to mark fullness) and record arrival time + // check if space in origin stack, + uint8_t slot = 0; + if(stackEmptySlot(&_vt_usbSerial, VT_STACK_ORIGIN)){ + // decode into decodebuf, load into stack + // cobsDecode returns the length of the decoded packet + uint16_t len = cobsDecode(_inBuffer, _bwp, _encodedIn); + stackLoadSlot(&_vt_usbSerial, VT_STACK_ORIGIN, _encodedIn, len); + // reset byte write pointer, and find the next empty packet write space + _bwp = 0; + } else { + sysError("! serial no space !"); + } + // reset for next write, + _bwp = 0; + } else { + _bwp ++; + } + } +} + +// to clear packets out... for us to track flowcontrol +void vt_usbSerial_onOriginStackClear(uint8_t slot){ + // this is all, + _acksAwaiting ++; +} + +// there's at the moment no usb -> up flowcontrol +boolean vt_usbSerial_cts(uint8_t drop){ + return true; +} + +uint8_t _shift[VPUSB_SPACE_SIZE]; + +void vt_usbSerial_send(uint8_t* data, uint16_t len, uint8_t rxAddr){ + // damn, this is not fast: shifting one byte in for acks, + // probably faster than sending seperate packet though + _shift[0] = _acksAwaiting; + _acksAwaiting = 0; + memcpy(&(_shift[1]), data, len); + // now encode out, + size_t encLen = cobsEncode(_shift, len + 1, _encodedOut); + if(Serial.availableForWrite()){ + _encodedOut[encLen] = 0; // write in final trailing zero + Serial.write(_encodedOut, encLen + 1); + Serial.flush(); + } else { + sysError("on write, serial not available"); + } +} \ No newline at end of file diff --git a/firmware/fab-step/src/osape-d21/vertices/vt_usbSerial.h b/firmware/fab-step/src/osape-d21/vertices/vt_usbSerial.h new file mode 100644 index 0000000..e6fea5e --- /dev/null +++ b/firmware/fab-step/src/osape-d21/vertices/vt_usbSerial.h @@ -0,0 +1,37 @@ +/* +osap/vt_usbSerial.h + +virtual port, p2p + +Jake Read at the Center for Bits and Atoms +(c) Massachusetts Institute of Technology 2019 + +This work may be reproduced, modified, distributed, performed, and +displayed for any purpose, but must acknowledge the osap project. +Copyright is retained and must be preserved. The work is provided as is; +no warranty is provided, and users accept all liability. +*/ + +#ifndef VPORT_USBSERIAL_H_ +#define VPORT_USBSERIAL_H_ + +#include <Arduino.h> +#include "../osape/osap/vertex.h" // this is a 'vertex' definition, extending osap for device specific COM + +#define VPUSB_NUM_SPACES 8 +#define VPUSB_SPACE_SIZE 512 + +// uuuuh classes are cancelled? + +void vt_usbSerial_setup(void); +void vt_usbSerial_loop(void); +boolean vt_usbSerial_cts(uint8_t drop); +void vt_usbSerial_send(uint8_t* data, uint16_t len, uint8_t rxAddr); +void vt_usbSerial_onOriginStackClear(uint8_t slot); + +// tells linker that the thing exists & to... find it later? +// is declared in this cpp file + +extern vertex_t* vt_usbSerial; + +#endif -- GitLab