From eb273e694040e29bb230e480392ed8e57915cce6 Mon Sep 17 00:00:00 2001 From: Adrian Kummerlaender Date: Mon, 11 Oct 2021 23:44:30 +0200 Subject: Add article on Teensy development --- ...reproducible_development_environment_teensy.org | 195 +++++++++++++++++++++ ...reproducible_development_environment_teensy.org | 1 + ...reproducible_development_environment_teensy.org | 1 + ...reproducible_development_environment_teensy.org | 1 + tags/nix/2018-06-23_nixos_gpu_assorted_topics.md | 1 + ...reproducible_development_environment_teensy.org | 1 + 6 files changed, 200 insertions(+) create mode 100644 articles/2021-10-11_reproducible_development_environment_teensy.org create mode 120000 tags/cpp/2021-10-11_reproducible_development_environment_teensy.org create mode 120000 tags/development/2021-10-11_reproducible_development_environment_teensy.org create mode 120000 tags/english/2021-10-11_reproducible_development_environment_teensy.org create mode 120000 tags/nix/2018-06-23_nixos_gpu_assorted_topics.md create mode 120000 tags/nix/2021-10-11_reproducible_development_environment_teensy.org diff --git a/articles/2021-10-11_reproducible_development_environment_teensy.org b/articles/2021-10-11_reproducible_development_environment_teensy.org new file mode 100644 index 0000000..e0fd626 --- /dev/null +++ b/articles/2021-10-11_reproducible_development_environment_teensy.org @@ -0,0 +1,195 @@ +* Reproducible development environment for Teensy +So for a change of scenery I recently started to mess around with microcontrollers again. +Since the last time that I had any real contact with this area was probably around a decade ago --- programming an [[https://www.dlr.de/rm/en/desktopdefault.aspx/tabid-14006/#gallery/34068][ASURO]] robot --- I started basically from scratch. +Driven by the goal of building and programming a fancy mechanical keyboard (as it seems to be the trendy thing to do) I chose the Arduino-compatible [[https://www.pjrc.com/store/teensy40.html][Teensy 4.0]] +board. While I appreciate the rich and accessible software ecosystem for this platform, I don't really want to use some special IDE, applying amongst other things[fn:0] +weird non-standard preprocessing to my code. In this vein it would also be nice to use my accustomed [[https://nixos.org][Nix-based]] toolchain which leads me to this article. + +Roughly following what [[https://rzetterberg.github.io/teensy-development-on-nixos.html][others did]] for Teensy 3.1 while adapting it to Teensy 4.0 and Nix flakes it is simple to build and flash +some basic C++ programs onto a USB-attached board. The adapted version of the Arduino library is available on [[https://github.com/PaulStoffregen/cores][Github]] and can +be compiled into a shared library using flags + +#+BEGIN_SRC make +MCU = IMXRT1062 +MCU_DEF = ARDUINO_TEENSY40 + +OPTIONS = -DF_CPU=600000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH +OPTIONS += -D__$(MCU)__ -DARDUINO=10813 -DTEENSYDUINO=154 -D$(MCU_DEF) + +CPU_OPTIONS = -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 -mthumb + +CPPFLAGS = -Wall -g -O2 $(CPU_OPTIONS) -MMD $(OPTIONS) -ffunction-sections -fdata-sections +CXXFLAGS = -felide-constructors -fno-exceptions -fpermissive -fno-rtti -Wno-error=narrowing -I@TEENSY_INCLUDE@ +#+END_SRC + +included into a run-of-the-mill Makefile and relying on the =arm-none-eabi-gcc= compiler. Correspondingly, the +derivation for the core library [[http://code.kummerlaender.eu/teensy-env/tree/core.nix?id=44c1837717f748b891df1a6c88a72ec3a51470ce][=core.nix=]] is straight forward. It clones a given version of the library repository, +jumps to the =teensy4= directory, deletes the example =main.cpp= file to exclude it from the library and applies a Makefile +adapted from the default one. For the result only headers, common flags and the linker script =IMXRT1062.ld= +are exported. + +As existing Arduino /sketches/ commonly consist of a single C++ file (ignoring some non-standard stuff for later) most +builds can be handled generically by a mapping of =*.cpp= files into flashable =*.hex= files. This is realized by the following +function based on the =teensy-core= derivation and a [[http://code.kummerlaender.eu/teensy-env/tree/Makefile.default?id=44c1837717f748b891df1a6c88a72ec3a51470ce][default makefile]]: + +#+BEGIN_SRC nix +build = name: source: pkgs.stdenv.mkDerivation rec { + inherit name; + + src = source; + + buildInputs = with pkgs; [ + gcc-arm-embedded + teensy-core + ]; + + buildPhase = '' + export CC=arm-none-eabi-gcc + export CXX=arm-none-eabi-g++ + export OBJCOPY=arm-none-eabi-objcopy + export SIZE=arm-none-eabi-size + + cp ${./Makefile.default} Makefile + export TEENSY_PATH=${teensy-core} + make + ''; + + installPhase = '' + mkdir $out + cp *.hex $out/ + ''; +}; +#+END_SRC + +The derivation yielded by =build "test" ./test= results in a =result= directory containing a =*.hex= file for each +C++ file contained in the =test= directory. Adding a =loader= function to be used in convenient =nix flake run= +commands + +#+BEGIN_SRC nix +loader = name: path: pkgs.writeScript name '' + #!/bin/sh + ${pkgs.teensy-loader-cli}/bin/teensy-loader-cli --mcu=TEENSY40 -w ${path} +''; +#+END_SRC + +a reproducible build of the canonical /blink/ example[fn:1] is realized using: + +#+BEGIN_SRC sh +nix flake clone git+https://code.kummerlaender.eu/teensy-env --dest . +nix run .#flash-blink +#+END_SRC + +Expanding on this, the =teensy-env= flake also provides convenient =image(With)= functions for building +programs that depend on additional Arduino libraries such as for controlling servos. E.g. the build +of a program =test.cpp= placed in a =src= folder + +#+BEGIN_SRC cpp +#include +#include + +extern "C" int main(void) { + Servo servo; + // Servo connected to PWM-capable pin 1 + servo.attach(1); + while (true) { + // Match potentiometer connected to analog pin 7 + servo.write(map(analogRead(7), 0, 1023, 0, 180)); + delay(20); + } +} +#+END_SRC + +is fully described by the flake: + +#+BEGIN_SRC nix +{ + description = "Servo Test"; + + inputs = { + teensy-env.url = git+https://code.kummerlaender.eu/teensy-env; + }; + + outputs = { self, teensy-env }: let + image = teensy-env.custom.imageWith + (with teensy-env.custom.teensy-extras; [ servo ]); + + in { + defaultPackage.x86_64-linux = image.build "servotest" ./src; + }; +} +#+END_SRC + +At first I expected the build of [[http://www.ulisp.com/][uLisp]][fn:2] to proceed equally smoothly as this implementation of Lisp +for microcontrollers is provided as a single [[https://raw.githubusercontent.com/technoblogy/ulisp-arm/master/ulisp-arm.ino][=ulisp-arm.ino=]] file. However, the =*.ino= extension +is not just for show here as beyond even the replacement of =main= by =loop= and =setup= --- which +would be easy to fix --- it relies on further non-standard preprocessing offered by the +Arduino toolchain. I quickly aborted my efforts towards patching in e.g. the forward-declarations +which are automagically added during the build (is it really such a hurdle to at least declare stuff before +referring to it… oh well) and instead followed a less pure approach using =arduino-cli= to access +the actual Arduino preprocessor. + +#+BEGIN_SRC sh +arduino-cli core install arduino:samd +arduino-cli compile --fqbn arduino:samd:arduino_zero_native --preprocess ulisp-arm.ino > ulisp-arm.cpp +#+END_SRC + +The problematic line w.r.t. to reproducible builds in Nix is the installation of the =arduino:samd= toolchain +which requires network access and wants to install stuff to home. Pulling in arbitrary stuff over the +network is of course not something one wants to do in an isolated and hopefully reproducible build +environment which is why this kind of stuff is heavily restricted in common Nix derivations. Luckily +it is possible to misuse (?) a fixed-output derivation to describe the preprocessing of =ulisp-arm.ino= +into a standard C++ =ulisp-arm.cpp= compilable using the GCC toolchain. + +The relevant file [[https://code.kummerlaender.eu/teensy-env/tree/ulisp.nix?id=44c1837717f748b891df1a6c88a72ec3a51470ce][=ulisp.nix=]] pulls in the uLisp source from Github and calls =arduino-cli= to install +its toolchain to a temporary home folder followed by preprocessing the source into the derivation's +output. The relevant lines for turning this into a fixed-output derivation are + +#+BEGIN_SRC nix +outputHashMode = "flat"; +outputHashAlgo = "sha256"; +outputHash = "mutVLBFSpTXgUzu594zZ3akR/Z7e9n5SytU6WoQ6rKA="; +#+END_SRC + +to declare the hash of the resulting file. After this point building and flashing uLisp using the =teensy-env= +flake works the same as for any C++ program. The two additional /SPI/ and /Wire/ library dependencies are +added easily using =imageWith=: + +#+BEGIN_SRC nix +teensy-ulisp = let + ulisp-source = import ./ulisp.nix { inherit pkgs; }; + ulisp-deps = with teensy-extras; [ spi wire ]; +in (imageWith ulisp-deps).build + "teensy-ulisp" + (pkgs.linkFarmFromDrvs "ulisp" [ ulisp-source ]); +#+END_SRC + +So we are now able to build and flash uLisp onto a conveniently attached Teensy 4.0 board using only: + +#+BEGIN_SRC sh +nix flake clone git+https://code.kummerlaender.eu/teensy-env --dest . +nix run .#flash-ulisp +#+END_SRC + +Connecting finally via serial terminal =screen /dev/ttyACM0 9600= we end up in a LISP environment where we +can play around with the microcontroller at our leisure without reflashing. + +#+BEGIN_SRC lisp +59999> (* 21 2) +42 + +59999> (defun blink (&optional x) + (pinmode 13 t) + (digitalwrite 13 x) + (delay 1000) + (blink (not x))) + +59966> (blink) +#+END_SRC + +As always, the code of everything discussed here is available via Git on [[https://code.kummerlaender.eu/teensy-env][code.kummerlaender.eu]]. +While I only focused on Teensy 4.0 it should be easy to adapt to other versions by changing the +compiler flags using [[https://github.com/PaulStoffregen/cores][PaulStoffregen/cores]] as a reference. + +[fn:0] e.g. forcing me to patch my XMonad [[http://code.kummerlaender.eu/nixos_home/tree/gui/conf/xmonad.hs][config]] to even get a usable UI… +[fn:1] Simply flashing the on-board LED periodically +[fn:2] Interactive development using a Lisp REPL on a microcontroller, how much more can you really ask for? diff --git a/tags/cpp/2021-10-11_reproducible_development_environment_teensy.org b/tags/cpp/2021-10-11_reproducible_development_environment_teensy.org new file mode 120000 index 0000000..e2c6e77 --- /dev/null +++ b/tags/cpp/2021-10-11_reproducible_development_environment_teensy.org @@ -0,0 +1 @@ +../../articles/2021-10-11_reproducible_development_environment_teensy.org \ No newline at end of file diff --git a/tags/development/2021-10-11_reproducible_development_environment_teensy.org b/tags/development/2021-10-11_reproducible_development_environment_teensy.org new file mode 120000 index 0000000..e2c6e77 --- /dev/null +++ b/tags/development/2021-10-11_reproducible_development_environment_teensy.org @@ -0,0 +1 @@ +../../articles/2021-10-11_reproducible_development_environment_teensy.org \ No newline at end of file diff --git a/tags/english/2021-10-11_reproducible_development_environment_teensy.org b/tags/english/2021-10-11_reproducible_development_environment_teensy.org new file mode 120000 index 0000000..e2c6e77 --- /dev/null +++ b/tags/english/2021-10-11_reproducible_development_environment_teensy.org @@ -0,0 +1 @@ +../../articles/2021-10-11_reproducible_development_environment_teensy.org \ No newline at end of file diff --git a/tags/nix/2018-06-23_nixos_gpu_assorted_topics.md b/tags/nix/2018-06-23_nixos_gpu_assorted_topics.md new file mode 120000 index 0000000..1ae287a --- /dev/null +++ b/tags/nix/2018-06-23_nixos_gpu_assorted_topics.md @@ -0,0 +1 @@ +../../articles/2018-06-23_nixos_gpu_assorted_topics.md \ No newline at end of file diff --git a/tags/nix/2021-10-11_reproducible_development_environment_teensy.org b/tags/nix/2021-10-11_reproducible_development_environment_teensy.org new file mode 120000 index 0000000..e2c6e77 --- /dev/null +++ b/tags/nix/2021-10-11_reproducible_development_environment_teensy.org @@ -0,0 +1 @@ +../../articles/2021-10-11_reproducible_development_environment_teensy.org \ No newline at end of file -- cgit v1.2.3