ratel
ratel copied to clipboard
Rework main and loop logic
As mentioned in the FOSDEM presentation something should probably be done around the proc main() {.exportc.}
and noMain
which are currently required to get the really small code sizes that Ratel enjoys. After a nice conversation with @yglukhov about this and some preliminary testing it seems that what is actually causing the extra size even without global memory and such is the use of volatile
in NimMain
and friends. The reason why it uses volatile
is to make sure that parts of the code isn't inlined as it tries to set the stack frame to scan for the mark and sweep pass of the GC. This is however not required for ARC which we're using at the moment. It might be required by ORC however. I will probably make a PR to the main Nim repository in the coming days which adds in a check for the C code generation that avoids creating the volatile
part. I manually wrote a small NimMain
which did the same things for my module, but avoided those and the code size was the same as it is currently. I'm basically just writing this issue to track progress on this issue. This would allow the example code to simply be written as:
import board
import board / [times, serial, progmem]
Serial.init(9600.Hz)
Serial.send p"Hello world\n"
Led.output()
while true:
Led.high()
Serial.send p"Led is on\n"
delayMs(1000)
Led.low()
Serial.send p"Led is off\n"
delayMs(1000)
The second part of this issue is that while true
loop. Some platform (the ESP8266 for example) has a hardware watchdog in order to make sure that they don't accidentally enter an infinite loop. This means that code like the above would run until the watchdog fired and then the board would reboot (assuming that delayMs
didn't feed the watchdog and sleep the system in a more logical way, which it probably would). I plan on adding a loop
construct which would essentially do the same thing as the while true
loop, but have small template calls which would bind to templates in the board definition and allow it to insert logic before the loop, and before and after each loop iteration. This should provide the necessary flexibility to implement such boards, but comments are appreciated if you see other issues with this. The final resulting code would then look like this:
import board
import board / [times, serial, progmem]
Serial.init(9600.Hz)
Serial.send p"Hello world\n"
Led.output()
loop:
Led.high()
Serial.send p"Led is on\n"
delayMs(1000)
Led.low()
Serial.send p"Led is off\n"
delayMs(1000)
With NimMain
working properly it should also allow modules to have their own initialisation logic in the global scope, if e.g. a board requires some particular things to be done on boot. These should then of course be put behind a compile-time switch if the user wished to do these things manually instead.
Nice ideas. It's interesting to see how advanced macros can really help in the embedded world!
The second part of this issue is that
while true
loop. Some platform (the ESP8266 for example) has a hardware watchdog in order to make sure that they don't accidentally enter an infinite loop. This means that code like the above would run until the watchdog fired and then the board would reboot (assuming thatdelayMs
didn't feed the watchdog and sleep the system in a more logical way, which it probably would). I plan on adding aloop
construct which would essentially do the same thing as thewhile true
loop, but have small template calls which would bind to templates in the board definition and allow it to insert logic before the loop, and before and after each loop iteration. This should provide the necessary flexibility to implement such boards, but comments are appreciated if you see other issues with this. The final resulting code would then look like this:import board import board / [times, serial, progmem] Serial.init(9600.Hz) Serial.send p"Hello world\n" Led.output() loop: Led.high() Serial.send p"Led is on\n" delayMs(1000) Led.low() Serial.send p"Led is off\n" delayMs(1000)
I'm not sure this is a good idea. For the simple reason of hiding of the expected and distinguishing (among different MCUs) behavior. In other words I find it much clearer to write resetWatchdog(watchdog_id_or_object_instance)
(this call could actually be standardized in Ratel along with some DEFAULT_WATCHDOG_ID
) at the end of the while loop than to use loop
.
But maybe I'm missing something important which would defend such "extreme" level of abstraction :wink:.
What do you think?
The reason I didn't want to do this is to increase the potential for sharing code between controllers. The Arduino Uno for example doesn't require a watchdog reset, so a programmer running code on that controller would simply never think to include a resetWatchdog
call at the end of their loop (even if it would just be a noop). This means that code written for the Arduino Uno wouldn't be able to run on the ESP8266 without modification. The idea is that loop
(and the various delay
procedures) would feed the watchdog. This way the above snippet should run on both the Arduino Uno and the ESP8266. If you wrote code specifically for the ESP8266 and wanted more granular control over the watchdog you would change your loop to something like loop(noWatchdogReset):
and then call resetWatchdog
manually. Flags which aren't implemented for the chosen board are simply ignored, so this should still compile and run fine on the Arduino Uno.
Thanks for explanation @PMunch . I've given it some thought and think you're right - at least this lib could/should explore how such level of abstraction would pay off in practice.
So, yeah, let's do that!
Btw. I've finally found some time to take a look at your presentation on FOSDEM - nicely explained, thanks!
Thanks for listening to my talk :) Hopefully I'll be able to get my suggestions implemented this weekend!