Getting started with .NET nanoFramework
Raven targets .NET nanoFramework through a normal SDK-style .rvnproj. Its
target framework moniker (TFM) is netnano1.0. Select that TFM, add ordinary
PackageReference items, and build with MSBuild. The
Raven target profile suppresses host .NET references, selects nanoFramework's
core library, and packages the result for nanoCLR.
This is composition rather than a parallel project system. Restore uses normal
PackageReference resolution, build and clean use ordinary MSBuild targets,
VS Code invokes the build through preLaunchTask, and the resulting artifacts
flow into the official nanoFramework deployment and debugging tools.
Support remains experimental. Check the target support matrix before choosing Raven or nanoFramework features for a production device.
Prerequisites
Install:
- a .NET SDK supported by Raven;
- the Raven SDK;
- Mono on macOS or Linux, used by the current metadata-processor CLI; and
- the official nanoFramework firmware and deployment tool,
nanoff.
Install nanoff once:
dotnet tool install --global nanoff
Update and inspect an existing installation with:
dotnet tool update --global nanoff
nanoff --version
nanoff --help
Create a project
This project targets nanoFramework 2.0 preview packages so Raven generics can run on compatible v2 firmware:
<Project Sdk="Raven.Sdk">
<PropertyGroup>
<TargetFramework>netnano1.0</TargetFramework>
<AssemblyName>Blinky</AssemblyName>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nanoFramework.System.Device.Gpio"
Version="2.0.0-preview.18" />
</ItemGroup>
</Project>
Raven implicitly includes **/*.rvn. A minimal GPIO program can therefore be
placed directly in Program.rvn:
import System.Device.Gpio.*
import System.Threading.*
func Main() {
use gpio = GpioController()
use led = gpio.OpenPin(25, PinMode.Output)
loop {
led.Write(PinValue.High)
Thread.Sleep(500)
led.Write(PinValue.Low)
Thread.Sleep(500)
}
}
Pin numbering and electrical connections are board-specific. Pico W and Pico 2 W onboard LEDs use the wireless controller rather than ordinary GPIO 25, so this exact program requires an external LED on those boards.
Build and package
Build as an ordinary Raven project:
dotnet build Blinky.rvnproj --configuration Debug
Inside a Raven source checkout, an SDK-style build normally uses the
Raven.Sdk selected by global.json. A sample can override only the compiler
host with a freshly built checkout-local compiler:
dotnet build src/Raven.Compiler/Raven.Compiler.csproj \
--framework net10.0 \
--property WarningLevel=0
dotnet build path/to/App.rvnproj \
--property:RavenCompilerHost="$PWD/src/Raven.Compiler/bin/Debug/net10.0/rvnc.dll"
The nanoFramework Wi-Fi HTTP sample packages this workflow as
./build.sh --repo-compiler and ./deploy.sh --repo-compiler .... These flags
rebuild the compiler first and print the exact Raven compiler host path, which
avoids accidentally testing a stale compiler from an installed SDK.
For an executable project, the netnano1.0 target produces this directory:
bin/Debug/netnano1.0/
Blinky.dll Raven managed assembly
Blinky.pdb portable source symbols
Blinky.pe compact nanoFramework application
Blinky.pdbx CLR-to-nanoCLR token and IL map
Blinky.bin complete image for nanoff deployment
mscorlib.pe
mscorlib.pdbx
<dependency>.pe
<dependency>.pdbx
The target invokes the official nanoFramework metadata processor, discovers
compact peers for the evaluated package-reference closure, stages the .pe and
.pdbx files required by the debugger, and combines the core library,
dependencies, and application into the aligned .bin accepted by nanoff.
Set NanoFrameworkPackageOnBuild=false only when another build pipeline owns
metadata conversion. Library projects produce a compact .pe by default but do
not create an application .bin; set NanoFrameworkCreateDeploymentImage=true
only for a library that intentionally acts as the deployment entry assembly.
The build fails when the metadata processor, target core library, or a required compact dependency is unavailable. Keep nanoFramework package versions mutually compatible rather than mixing arbitrary stable and preview releases.
Install compatible firmware
Firmware installation and application deployment are separate operations. The device must run nanoCLR firmware whose native contracts match the managed packages. Raven's current samples use nanoFramework 2.0 preview packages, so they require compatible preview/v2 firmware.
Inspect the firmware targets currently published to the stable and preview feeds. Treat the result as repository state rather than a permanent board list:
nanoff --listtargets --platform rpi_pico
nanoff --listtargets --platform rpi_pico --preview
The flasher CLI and firmware repository now use the same Pico-family target names:
| Board | Firmware target |
|---|---|
| Pico | PICO_RP2040 |
| Pico W | PICO_RP2040_W |
| Pico 2 | PICO2_RP2350 |
| Pico 2 W | PICO2_RP2350_W |
Tool support for a name does not guarantee that a matching firmware image is available in the selected feed. Verify the exact board package in the official firmware repository rather than inferring compatibility from a CLI alias alone.
nanoff 2.5.162 has two relevant limitations observed during Raven's macOS
hardware validation: its preview lookup can fail to find the published RP2040
packages, and its UF2 application path can count one mounted RPI-RP2 volume
twice. If either occurs, install the official firmware UF2 directly and use the
nanoCLR wire protocol for application deployment.
For Pico-family firmware updates, hold BOOTSEL while connecting USB. The board
appears as an RPI-RP2 or RP2350 mass-storage device. Raven's Pico W hardware
validation uses the published PICO_RP2040_W package. The legacy
RP_PICO_W_RP2040 nanoCLR 2.0.0.29 build can load System.Device.Wifi but
still return no Wi-Fi adapters, so do not use successful assembly loading as
the compatibility test. Download and extract the current package, then copy
nanoCLR.uf2 to the mounted board as an ordinary UF2 firmware installation:
curl -fLO https://dl.cloudsmith.io/public/net-nanoframework/nanoframework-images-dev/raw/names/PICO_RP2040_W/versions/2.0.0-preview.42/PICO_RP2040_W-2.0.0-preview.42.zip
unzip PICO_RP2040_W-2.0.0-preview.42.zip
cp nanoCLR.uf2 /Volumes/RPI-RP2/
The final command is the macOS form; on other hosts copy or drag nanoCLR.uf2
to the board's BOOTSEL volume. Always select firmware for the exact board and
chip family. Unplug and reconnect normally after the volume disappears or the
copy completes.
Firmware normally changes only when selecting a different target or runtime version; it does not need to be reflashed for every application build.
Deploy directly with nanoff
The commands in this section use the build output directly and do not depend on a Raven sample deployment script.
BOOTSEL/UF2
With the board in BOOTSEL mode, nanoff can detect the chip family and convert
the application image without a firmware target name:
nanoff --platform rpi_pico \
--deploy \
--image bin/Debug/netnano1.0/Blinky.bin \
--uf2deploy
nanoff performs the UF2 conversion and locates the mounted board. With
nanoff 2.5.162 on macOS, prefer the wire-protocol path below if this command
incorrectly reports multiple BOOTSEL devices.
nanoCLR wire protocol
Once working nanoCLR firmware is running, discover its connection:
nanoff --listports
nanoff --listdevices -v d
nanoff --nanodevice \
--serialport /dev/ttyACM0 \
--devicedetails
Deploy without returning to BOOTSEL mode:
nanoff --nanodevice \
--serialport /dev/ttyACM0 \
--deploy \
--image bin/Debug/netnano1.0/Blinky.bin
Replace the example with the reported host port, such as COM3,
/dev/ttyACM0, or /dev/tty.usbmodem.... Automatic --listdevices discovery
can miss a Pico that responds when its port is supplied explicitly. A debugger
or serial monitor can hold the same port open, so close it before deployment.
Add -v d or -v diag when investigating discovery, firmware compatibility,
or deployment failures.
Visual Studio Code debugging
The official .NET nanoFramework VS Code
extension
provides the device picker, managed debugger interface, and
nanoframework Debug Adapter Protocol implementation. It consumes the .pe,
.pdbx, and portable .pdb files produced by Raven's build. Raven's portable
PDB records authored .rvn paths, while the Raven VS Code extension enables
breakpoints in Raven documents.
This is the intended division of responsibility: Raven builds and packages the
.rvnproj; the nanoFramework extension communicates with nanoCLR and presents
breakpoints, variables, watches, and call stacks.
The nanoFramework extension's Build Project and Deploy Project commands
currently discover .nfproj, not .rvnproj, projects. Build and deploy the
Raven project with the commands above, then attach the debugger with a
.vscode/launch.json configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "Raven nanoFramework: Attach",
"type": "nanoframework",
"request": "attach",
"device": "/dev/ttyACM0",
"program": "${workspaceFolder}/bin/Debug/netnano1.0",
"verbosity": "information"
}
]
}
The device must be running nanoCLR rather than sitting in BOOTSEL mode. Set
verbosity to debug when investigating connection, symbol-loading, or
breakpoint-binding failures. Rebuild before launching so the deployed .pe,
.pdbx, and portable .pdb come from the same compilation; Raven preserves a
matching CodeView identity in the final assembly so the debugger can reject
stale symbol files.
Place breakpoints on executable statements. The nanoFramework adapter resolves a non-executable source line to the next portable-PDB sequence point. Current adapter versions do not consistently move the VS Code breakpoint marker to the resolved line, so a stop on the following statement can look like a misplaced Raven breakpoint even though the source-to-IL mapping is correct. With debug verbosity enabled, the adapter logs the requested line, available sequence points, resolved nanoCLR IL offset, and whether the device accepted the breakpoint.
For a build-and-debug F5 workflow, connect the build through a standard VS Code task:
{
"version": "2.0.0",
"tasks": [
{
"label": "Raven nanoFramework: Build",
"type": "process",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/Blinky.rvnproj",
"--configuration",
"Debug"
],
"problemMatcher": "$msCompile",
"group": "build"
}
]
}
Then change the launch configuration to request: "launch", add
preLaunchTask: "Raven nanoFramework: Build", and set
deployAssemblies: true. An empty device lets the nanoFramework extension
reuse the last device, auto-select the only connected device, or show its device
picker. The checked-in
nanoframework-blinky sample
contains complete tasks.json and launch.json examples for both launch and
attach.
The current metadata processor writes its token map as JSON while the released
nanoFramework VS Code debugger expects the earlier XML PDBX contract. Raven's
netnano1.0 packaging target stages a debugger-compatible XML view without
changing the compact application or deployment image.
Connected-device validation covers deployment, debugger connection, portable
PDB loading, exact Raven source-breakpoint binding, and the corresponding stack
frame location for the Blinky application. Stepping through loop, generic
frames, unions and patterns, local values, and exception presentation remain
explicitly provisional.
The Blinky sample includes a separate Pico 1 WH over USB setup recipe with the tested GP15 build, explicit macOS USB-port deployment, baud rate, expected output, and current VS Code adapter limitation.
Future integrated commands
Raven's VS Code extension can later recognize netnano1.0 and generate this
configuration automatically. It should still expose distinct build, deploy,
launch-and-debug, attach-only, and serial-monitor commands rather than hiding
all device operations behind F5.
Official tooling references: