Skip to content

Commit

Permalink
Rewrite documentation, implement board-specific build actions
Browse files Browse the repository at this point in the history
  • Loading branch information
PMunch committed Mar 6, 2022
1 parent 7c849d0 commit 055530e
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 65 deletions.
113 changes: 109 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,111 @@
# Ratel
## Next-generation, zero-cost abstraction microconroller programming in Nim
### Next-generation, zero-cost abstraction microconroller programming in Nim

This README is a bit light at the moment, please visit
[the website](https://ratel.peterme.net) to see the getting started guide and
documentation!
## Getting started
Getting started with Ratel is fairly simply. First you need to have [Nim](https://nim-lang.org/) installed along with the toolchain for
the controller you want to compile for. This guide is written with the Arduino Uno in mind, so for that board this would be the AVR toolchain. If you
have written code for Arduino before chances are good you already have these tools installed. Once that is done you need to install
Ratel itself:

```
nimble install ratel
```

If your board is not included in the official distribution you need to grab support for that as well. You should be able to search for all Ratel
based packages in the [package directory](https://nimble.directory/search?query=ratel). In order to compile and build for your board you
will also need the toolchain to compile C code and upload that to your board as well. The details of this should be found with the board support
library.

Once you have Ratel and your board support installed you need to set up your project. In this tutorial we'll be building for an Arduino Uno, but
this process is pretty much the same no matter the board. Simply create a `config.nims` file in your folder with the content:

```nim
import boardConf
board "unor3"
avr.any.gcc.path = "/usr/bin"
avr.any.gcc.exe = "avr-gcc"
avr.any.gcc.linkerexe = "avr-gcc"
```

This does a couple of things, starting with including the `boardConf` module from Ratel. Then it calls `board` which tells
Ratel that the board we want to build for is the Arduino Uno rev. 3. This will automatically include a set of sane defaults for compiling for this
micro-controller, along with some procedures that we'll get back to later.

The last three lines are simply telling Nim where to find the specific compiler we need to use for the CPU/OS combination we're using.
These could also be placed in your [global configuration](https://nim-lang.org/docs/nimc.html#compiler-usage-configuration-files)
as they will only be applied when compiling for these platforms.

## Writing our code
Now that our project is all set up we need to write some code, the sample from the front page is a good start. Simply save the following code in
a file with the `.nim` extension named the same as the folder it's in. If you're using Nim on the devel branch you can leave out the `main` proc and simply
have everything in the global scope.

```nim
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)
```

To write your own code have a browse through the documentation and check out any Ratel modules in Nimble.

## Compiling and uploading
With the project set up and the code written it is time to compile. When we imported the board configuration earlier we got some tasks for doing
this loaded into our configuration. To run these we use the `ratel` binary. So to build simply run:

```
ratel build
```

If you missed the sentence earlier about putting your file in a folder of the same name this will fail, but fret not, simply pass the file to
build with the `-f` flag. This command should have created a binary file in the same folder with the same name as your file but without
the extension. In order to check how big the resulting binary is we can simply run:

```
ratel size
```

Or if you're of the curious kind:

```
ratel sizeDetails
```

The size breakdown you get from this should be familiar to you if you have done any kind of programming with Arduino, it's the same one which is
written out in the terminal before uploading. It should look something like this:

```
AVR Memory Usage
----------------
Device: atmega328p
Program: 298 bytes (0.9% Full)
(.text + .data + .bootloader)
Data: 0 bytes (0.0% Full)
(.data + .bss + .noinit)
```

Now the final step of the process is to upload our code to the controller. You can of course do this manually with `avrdude` but Ratel
comes with a task for this as well:

```
ratel upload --device=/dev/ttyACM0
```

For me the board is connected to the USB port at `/dev/ttyACM0` but this might be different for you.

And that should be it! Your board should now be flashing an LED and printing to the serial terminal. To view the output you can either use the
serial terminal that comes with Arduino if you have that installed, or you can use a number of terminal applications. The easiest might even be to run
`tail -f /dev/ttyACM0`.
9 changes: 7 additions & 2 deletions ratel.nimble
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# Package

version = "0.1.0"
version = "0.2.0"
author = "Peter Munch-Ellingsen"
description = "Nim for microcontrollers"
license = "MIT"
srcDir = "src"
installExt = @["nim"]
bin = @["ratel"]


# Dependencies

requires "nim >= 1.6.2"
requires "nim >= 1.6.4"
requires "nimscripter"
requires "compiler"
requires "cligen"
22 changes: 22 additions & 0 deletions src/private/boards/teensy/boardConf.nim
Original file line number Diff line number Diff line change
@@ -1 +1,23 @@
include "../helpers/avrBoardConf.nim"

proc build*(file: string) =
exec "nim c -d:danger --os:any " & file

proc upload*(file: string) =
exec "avr-objcopy -O ihex -R .eeprom " & file & " " & file & ".hex"
exec "teensy-loader-cli --mcu=TEENSY2 -v -w " & file & ".hex"
exec "rm " & file & ".hex"

proc size*(file: string) =
exec "avr-size -C --mcu=atmega32u4 " & file

proc sizeDetails*(file: string) =
size(file)
echo ".data section:"
exec "avr-nm -S --size-sort " & file & " | grep \" [Dd] \" || echo \"empty\""
echo ""
echo ".bss section:"
exec "avr-nm -S --size-sort " & file & " | grep \" [Bb] \" || echo \"empty\""
echo ""
echo ".text section:"
exec "avr-nm -S --size-sort " & file & " | grep \" [Tt] \" || echo \"empty\""
20 changes: 20 additions & 0 deletions src/private/boards/unor3/boardConf.nim
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
include "../helpers/avrBoardConf.nim"

proc build*(file: string) =
exec "nim c -d:danger --os:any " & file

proc upload*(device: string, file: string) =
exec "avrdude -F -V -c arduino -p atmega328p -P " & device & " -b 115200 -U flash:w:" & file & ":e"

proc size*(file: string) =
exec "avr-size -C --mcu=atmega328p " & file

proc sizeDetails*(file: string) =
size(file)
echo ".data section:"
exec "avr-nm -S --size-sort " & file & " | grep \" [Dd] \" || echo \"empty\""
echo ""
echo ".bss section:"
exec "avr-nm -S --size-sort " & file & " | grep \" [Bb] \" || echo \"empty\""
echo ""
echo ".text section:"
exec "avr-nm -S --size-sort " & file & " | grep \" [Tt] \" || echo \"empty\""
75 changes: 75 additions & 0 deletions src/ratel.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import strutils, tables, os, options
import nimscripter, nimscripter / vmops
import cligen

var
keys*: Table[string, string]
defines*: seq[string]

proc switch*(key: string, value = "") =
if key == "define":
defines.add value.strip(chars = {'"'})
else:
keys[key] = value.strip(chars = {'"'})

proc getSetting*(key: string): string =
keys[key]

proc isDefined*(key: string): bool =
defines.contains key

proc runCommand(command: string, arguments: varargs[string]) =
addVMops(tasks)
addCallable(tasks):
proc build(file: string)
proc getSize(file: string)

exportCode(tasks):
template `--`(key, val: untyped) =
switch(strip(astToStr(key)), strip(astToStr(val)))

template `--`(key: untyped) =
switch(strip(astToStr(key)))

exportTo(tasks, switch, getSetting, isDefined)

var searchPaths: seq[string]
# TODO: figure out paths on Windows
let nimblePath = getEnv("NIMBLE_PATH")
let nimblePaths = if nimblePath.len == 0:
@[getHomeDir() & "/.nimble/pkgs/", getHomeDir() & "/.nimble/pkgs2/", "/opt/nimble/pkgs/", "/opt/nimble/pkgs2/"]
else:
@[nimblePath]
for path in nimblePaths:
for kind, path in path.walkDir:
if kind == pcDir: searchPaths.add path

var
addins = implNimscriptModule(tasks)
interpreter = loadScript("config.nims".NimScriptPath, addins, modules = @["strutils"], searchPaths = searchPaths)

case arguments.len:
of 0: interpreter.get.invokeDynamic(command)
of 1: interpreter.get.invokeDynamic(command, arguments[0])
of 2: interpreter.get.invokeDynamic(command, arguments[0], arguments[1], returnType = void)
of 3: interpreter.get.invokeDynamic(command, arguments[0], arguments[1], arguments[2], returnType = void)
of 4: interpreter.get.invokeDynamic(command, arguments[0], arguments[1], arguments[2], arguments[3], returnType = void)
else: interpreter.get.invokeDynamic(command, arguments)

proc build(file = getCurrentDir().lastPathPart().addFileExt(".nim")) =
runCommand("build", file)

proc upload(device = "autodetect", file = getCurrentDir().lastPathPart()) =
if device != "autodetect":
runCommand("upload", device, file)
else:
runCommand("upload", file)

proc size(file = getCurrentDir().lastPathPart()) =
runCommand("size", file)

proc sizeDetails(file = getCurrentDir().lastPathPart()) =
runCommand("sizeDetails", file)

dispatchMulti([build], [upload], [size], [sizeDetails])

Loading

0 comments on commit 055530e

Please sign in to comment.