Skip to content

Instantly share code, notes, and snippets.

@AlexanderBrevig
Last active June 7, 2017 10:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AlexanderBrevig/4cc95b91a585c506a08d3364b7eb0725 to your computer and use it in GitHub Desktop.
Save AlexanderBrevig/4cc95b91a585c506a08d3364b7eb0725 to your computer and use it in GitHub Desktop.
Just some thoughts from a former library author

IDE

I've got a few wishes for the next IDE so I'll just summarize them here as bullet points. These are my personal "nice to have" and not at all meant as "must have". Here goes:

  • A very clean canvas for writing code, I like tabs for handling multiple files in the sketch
  • Some kind of intellisense and a good auto format / smart insert
  • Based on https://electron.atom.io/
  • I would like to see a left side simple "explorer" with the following: [sample image below]
    • Entry for an attached schematic if any is included in the project folder
    • Entry for an attached readme if any is included in the project folder
    • Entry for a help (readme) document per #included library, if they provide one

oryng sample made with sublime

Build system

I think if we use electron and by implication Node.js, we should simply define the "build system API" and then every core and board contributer makes a compliant JS build system front end for their chosen underlying build system. This would be pipelined such that we always pass it through our own preprocessor (generating declarations and other meta data) before passing it to the final specific build unit.

Part of this build system could be to include the sentiment of a dependency injector for testing. This way, we as developers could have every instance of f.ex Serial become a MockSerial that could expose testing utility methods for use in a test bench. It could also be used to parse the current core for implementations of Serial and prefer the one furthest up the hierarchy. This could mean that one could transparently, for say an STM32F103 use a library called SerialErrataFix which, made clear by its name, works around a known silicon bug. Then the sketch could still #include<Serial.h> but the build unit would see #include<SerialErrataFix.h>.

Conventions

Buffer allocation

As Paul so excellently pointed out, there's a constant request to be able to modify especially the Serial buffers. I think we should solve that particular problem in a way that trickles down to every library that needs a buffer to do its thing. Over the years I've written several libraries in need of per-user allocation myself and neither passing down a bufferSize:int not having a templated <bufferSize:int> seems good to me.

The API should concider two things:

  1. Most users won't make use of the API
  2. It needs to read well and be very clear what's going on. Bonus if it's easily parsable so the build unit can alert the user statically if they try to allocate 3k on an attiny.

Off the cuff, it could be either something like:

  • Serial mySpecialSerial = Serial.Make().withBuffer(128).withCTS(12).withRX(0).withTX(1).begin(115200);
  • Serial mySpecialSerial = Serial BUFFER(128); //BUFFER macro simply makes it <128>()
  • Buffer mySerialMemory = RingBuffer(128); Serial mySpecialSerial(mySerialMemory, 0 , 1); // alternativley expose mySpecialSerial.setMemory(mySerialMemory);
  • #define SERIAL1_BUFFER 128 \n #include<Serial.h> \n /*business as usual, now Serial1 has 128 buffer */

I could go on for ages. I don't know what's easy, or really - if anything is easy. Hernando should share his intuition what's easiest for a beginner to understand (though a beginner probably won't write this themselves, they should be able to read it).

Type safe enums

I stronly feel we should change to type safe enums. F.ex digitalWrite(13, OUTPUT) would work now, it would "output" [in a beginners mind this may be a totally OK name for outputting light from an LED] but it might not next version. This should generate a compile time error that clearly states it's the wrong type. This would also allow for a good intellisense to filter out and suggest appropriate enums.

Either we can enforce c++14 (might be 11 even? don't remember right now) or we can use a type safe enum hack but that's dirty and adds a new parse step to our preprocessor. And another burden to library authors.

Callbacks

Again Paul touched on this and we should come up with a few guidelines.

  • Do we like callbacks?
  • Do we want them to be set by a method? myObject.onThatEvent(myOnThatEventHandler);
  • Do we want them to be weakly linked? void onMyClassThatEventHandler(MyClassObject source, MyClassEvent event);
  • Do we want to handle all events in the core and then provide a lazy way to handle them? if (Oryng.hasEvent(MyClassEvent)) { /*use it*/ }

Interrupts

De we define a set of Oryng interrupts and make them extensible? This will definilty be a power feature, and I'm uncertain if a beginner should ever feel the need to use this.

The one exception might a an intervaled timer, but then I propose the fictitious IntervaledTimer class should internally use the Oryng low level core [which would have this interrupt abstraction layer] to configure the appropriate timer peripheral.

Another way to go is to have interrupt dependable libraries be a per-board implementation burden. If the IntervaledTimer is placed in the AVR core, then it must support all AVRs.

If it's placed in only the STM32F103 then the next STM32F board must implement this class in order to be 'Oryng compliant'.

Again; there's so many ways of going about this and I feel we should think hard. I imagine anything from a description model, where we do something like:

IntervaledTimer t; t.setIntervalMs(1000); Oryng.configure(t); Now, depending on if we want callbacks, it could be part of the description model.

It would eventually get passed to the per-board implementation of Oryng and presumably its onConfigure(Peripheral *p) and then it's a matter of parsing the description tree per core and set up a peripheral accordingly.

A more frienly version would be to have every peripheral call Oryng.configure(this) in its begin() override. I'm getting ahead of myself here, sorry. Just wanted to write something down.

Framework and Libraries

Do we want to stay as close to C++ as possible?

I vote no. There's so many things that can be done to improve the users experience over a true c++ one. Generating declarations is only one of them. Of cource, we should compile it in the end as C++ :)

Conventions based API for cross-device compliance

I very much would like us to have a well defined API for the most common peripherals. We don't need to go all the way of having interfaces for everyone to implement, but they should be in place in some places. The existing Stream and Print are good examples of very useful interfaces. I think there's a need for more though:

  • MotorController
  • TemperatureSensor
  • XSensor (replace X with the most common ones)

A library available in the IDE should be compliant with what we've defined as the API, and it should contain a README for use in the IDE as a learning resource and guide.

analogWrite

I'm one of those who actually would like this to be renamed to pwmWrite. It's common now that modern µCUs have true analog outputs with decent DACs for providing a true DC voltage. This is a huge API break, and I understand if it's not even up for discussion.

Documentation for new core and new board

I think we should make a good walk through of how to add a new core and a new board to the eco system. A clear outline of the various things needed to make my favorite microcontroller become a first class citizen of the Oryng community.

Multithreading

My thought on this matter is that we should not support true preemptive multi threading. It's hard, and it always will be. However, a standard way of creating protothreads would be nice. One example could be using a keyword and the c++14 user suffix: every (1000ms) {/*blinky?*/}. This would of course either make use of a rather nasty and static #define, or it could be part of the Oryng preprocessor.

A bit of a crazy idea: Oryng God object

In some of my previous sample codes I've used an object called Oryng. This is probably because of my history as a games programmer where the Kernel is often the only global object.

For us this would be the main object to talk to in regards to the current board.

Instead of including say, a sleep library, we could implement sleep as part of the global API on this object.

Each core, even each board, could then modify its behaviour using the strategy pattern.

Another idea further down, is that this object could receive messages to control the core. As such, one could do something like this: Oryng slave = Oryng(Serial1); slave.digitalWrite(13, HIGH); and the Oryng connected to Serial1 would set it's digital pin 13 HIGH.

Cores

A core should implemnt only the lowest level of what's needed to get the libraries running. Currently I think too many libraries are per-core specific.

It will add overhead in runtime (only slight, maybe none if we do it smart - this API is for library developers mostly), but will make it much faster to crate a new core for that one µCU you really love.

I think the core API should be close to the metal, and maybe even be a way for people to speed up their projects if they need to. REGISTER_WRITE(PIN_13_IO, PIN_13, OUTPUT); REGISTER_WRITE(PIN_13_PORT, PIN_13, 1); should then translate to a direct port registrer set, and still be understandable to the dedicated beginner (who for some reason is reading a sketch that needed direct port settings). One such example could be a software synth for one of the smaller AVRs.

If you read thorugh all this, you're a hero!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment