Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions examples/nordic/nrf5x/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

HALs and register definitions for nrf5x devices

## SoftDevice examples

- `pca10040_softdevice_preflashed_blinky`:
app-only build for a target with SoftDevice-shifted flash/RAM. Use this when
SoftDevice is already flashed on the chip.
- `nrf52840_mdk_softdevice_merged_blinky`:
emits both app artifacts and a merged Intel HEX that includes the app image
and `s140` SoftDevice image.
- `nrf52840_dongle_softdevice_merged_blinky`:
same merged approach for the nRF52840 Dongle with `s140`.

Build one SoftDevice example:

```sh
zig build -Dexample=nrf52840_mdk_softdevice_merged_blinky
```

## Renode supports:

- nrf52840 development kit
124 changes: 103 additions & 21 deletions examples/nordic/nrf5x/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,88 @@ pub fn build(b: *std.Build) void {
const pca10040 = mb.ports.nrf5x.boards.nordic.pca10040;
const microbit_v1 = mb.ports.nrf5x.boards.bbc.microbit_v1;
const microbit_v2 = mb.ports.nrf5x.boards.bbc.microbit_v2;
const pca10040_s132 = mb.ports.nrf5x.softdevice.boards.nordic.pca10040_s132;
const nrf52840_dongle_s140 = mb.ports.nrf5x.softdevice.boards.nordic.nrf52840_dongle_s140;
const nrf52840_mdk_s140 = mb.ports.nrf5x.softdevice.boards.nordic.nrf52840_mdk_s140;

// Install SoftDevice hex files into zig-out/firmware/ for easy flashing.
const install_step = b.getInstallStep();
const sd_files = mb.ports.nrf5x.softdevice.files;
inline for (.{
.{ sd_files.s112, "s112_nrf52_7.2.0_softdevice.hex" },
.{ sd_files.s113, "s113_nrf52_7.2.0_softdevice.hex" },
.{ sd_files.s122, "s122_nrf52_8.0.0_softdevice.hex" },
.{ sd_files.s132, "s132_nrf52_7.2.0_softdevice.hex" },
.{ sd_files.s140, "s140_nrf52_7.2.0_softdevice.hex" },
}) |entry| {
install_step.dependOn(&b.addInstallFileWithDir(entry[0], .{ .custom = "firmware" }, entry[1]).step);
}

const available_examples = [_]Example{
.{ .target = nrf52840_dongle, .name = "nrf52840_dongle_blinky", .file = "src/blinky.zig" },

.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_blinky", .file = "src/blinky.zig" },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_uart", .file = "src/uart.zig" },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_bus_scan", .file = "src/i2c_bus_scan.zig" },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_accel", .file = "src/i2c_accel.zig" },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_hall_effect", .file = "src/i2c_hall_effect.zig" },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_position_sensor", .file = "src/i2c_position_sensor.zig" },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_temp", .file = "src/i2c_temp.zig" },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_rtt_log", .file = "src/rtt_log.zig" },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_semihosting", .file = "src/semihosting.zig" },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_spi_master", .file = "src/spi_master.zig" },

.{ .target = pca10040, .name = "pca10040_blinky", .file = "src/blinky.zig" },
.{ .target = pca10040, .name = "pca10040_uart", .file = "src/uart.zig" },
.{ .target = pca10040, .name = "pca10040_i2c_bus_scan", .file = "src/i2c_bus_scan.zig" },
.{ .target = pca10040, .name = "pca10040_i2c_temp", .file = "src/i2c_temp.zig" },
.{ .target = pca10040, .name = "pca10040_spi_master", .file = "src/spi_master.zig" },

.{ .target = microbit_v1, .name = "microbit_v1_display", .file = "src/microbit/display.zig" },
.{ .target = microbit_v2, .name = "microbit_v2_display", .file = "src/microbit/display.zig" },
.{ .target = nrf52840_dongle, .name = "nrf52840_dongle_blinky", .file = "src/blinky.zig", .softdevice_hex = null },

.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_blinky", .file = "src/blinky.zig", .softdevice_hex = null },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_uart", .file = "src/uart.zig", .softdevice_hex = null },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_bus_scan", .file = "src/i2c_bus_scan.zig", .softdevice_hex = null },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_accel", .file = "src/i2c_accel.zig", .softdevice_hex = null },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_hall_effect", .file = "src/i2c_hall_effect.zig", .softdevice_hex = null },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_position_sensor", .file = "src/i2c_position_sensor.zig", .softdevice_hex = null },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_temp", .file = "src/i2c_temp.zig", .softdevice_hex = null },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_rtt_log", .file = "src/rtt_log.zig", .softdevice_hex = null },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_semihosting", .file = "src/semihosting.zig", .softdevice_hex = null },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_spi_master", .file = "src/spi_master.zig", .softdevice_hex = null },

// SoftDevice examples: the app code is identical — the difference is in
// how the SoftDevice binary gets onto the chip. "preflashed" assumes the
// SoftDevice is already on the device; "merged" produces a single hex
// containing both the SoftDevice and the app.
.{
.target = pca10040_s132,
.name = "pca10040_softdevice_preflashed_blinky",
.file = "src/softdevice_preflashed_blinky.zig",
.softdevice_hex = null,
.is_softdevice = true,
},
.{
.target = nrf52840_mdk_s140,
.name = "nrf52840_mdk_softdevice_merged_blinky",
.file = "src/softdevice_merged_blinky.zig",
.softdevice_hex = mb.ports.nrf5x.softdevice.files.s140,
.is_softdevice = true,
},
.{
.target = nrf52840_dongle_s140,
.name = "nrf52840_dongle_softdevice_merged_blinky",
.file = "src/softdevice_merged_blinky.zig",
.softdevice_hex = mb.ports.nrf5x.softdevice.files.s140,
.is_softdevice = true,
},

// SoftDevice API examples for PCA10040 (nRF52832 + S132).
// The softdevice module is automatically wired via the HAL.
.{
.target = pca10040_s132,
.name = "pca10040_softdevice_init",
.file = "src/softdevice_init.zig",
.softdevice_hex = null,
.is_softdevice = true,
},
.{
.target = pca10040_s132,
.name = "pca10040_softdevice_beacon",
.file = "src/softdevice_beacon.zig",
.softdevice_hex = null,
.is_softdevice = true,
},

.{ .target = pca10040, .name = "pca10040_blinky", .file = "src/blinky.zig", .softdevice_hex = null },
.{ .target = pca10040, .name = "pca10040_uart", .file = "src/uart.zig", .softdevice_hex = null },
.{ .target = pca10040, .name = "pca10040_i2c_bus_scan", .file = "src/i2c_bus_scan.zig", .softdevice_hex = null },
.{ .target = pca10040, .name = "pca10040_i2c_temp", .file = "src/i2c_temp.zig", .softdevice_hex = null },
.{ .target = pca10040, .name = "pca10040_spi_master", .file = "src/spi_master.zig", .softdevice_hex = null },

.{ .target = microbit_v1, .name = "microbit_v1_display", .file = "src/microbit/display.zig", .softdevice_hex = null },
.{ .target = microbit_v2, .name = "microbit_v2_display", .file = "src/microbit/display.zig", .softdevice_hex = null },
};

for (available_examples) |example| {
Expand Down Expand Up @@ -68,11 +127,34 @@ pub fn build(b: *std.Build) void {

// For debugging, we also always install the firmware as an ELF file
mb.install_firmware(fw, .{ .format = .elf });

// For preflashed SoftDevice targets, also emit a .hex so users can
// flash the app via nrfjprog or a J-Link probe.
if (example.softdevice_hex == null and example.is_softdevice) {
mb.install_firmware(fw, .{ .format = .hex });
}

if (example.softdevice_hex) |softdevice_hex| {
const app_hex = fw.get_emitted_bin(.hex);
const merged_hex = mb.ports.nrf5x.merge_hex(
app_hex,
softdevice_hex,
b.fmt("{s}_merged.hex", .{example.name}),
);
const install_merged = b.addInstallFileWithDir(
merged_hex,
.{ .custom = "firmware" },
b.fmt("{s}_merged.hex", .{example.name}),
);
b.getInstallStep().dependOn(&install_merged.step);
}
}
}

const Example = struct {
target: *const microzig.Target,
name: []const u8,
file: []const u8,
softdevice_hex: ?std.Build.LazyPath,
is_softdevice: bool = false,
};
90 changes: 90 additions & 0 deletions examples/nordic/nrf5x/src/softdevice_beacon.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// BLE beacon example for PCA10040.
//
// Enables the SoftDevice, configures BLE advertising, and broadcasts a
// non-connectable beacon with a device name. LED1 blinks while advertising.
// LED4 is lit on fault or error.
const microzig = @import("microzig");
const board = microzig.board;
const nrf = microzig.hal;
const sd = nrf.softdevice;
const time = nrf.time;

const gap = sd.gap;
const ble = sd.ble;
const sdm = sd.sdm;

pub fn main() void {
board.init();

// 1. Enable SoftDevice
const clock_cfg: sdm.ClockLfCfg = .{
.source = .rc,
.rc_ctiv = 16,
.rc_temp_ctiv = 2,
.accuracy = .ppm_500,
};
sdm.enable(&clock_cfg, fault_handler) catch return signal_error();

// 2. Enable BLE stack
var ram_base: u32 = 0x20002800; // must match SoftDevice profile ram_start
ble.enable(&ram_base) catch return signal_error();

// 3. Set device name
const name = "nRF52 Beacon";
var perm = gap.ConnSecMode.open();
gap.device_name_set(&perm, name, name.len) catch return signal_error();

// 4. Build advertising data: flags + complete local name
var adv_buf = build_adv_data(name);

// 5. Configure advertising set
var adv_handle: u8 = 0xFF; // BLE_GAP_ADV_SET_HANDLE_NOT_SET
var adv_data: gap.AdvData = .{
.adv_data = gap.Data.from_slice(&adv_buf),
};
const adv_params: gap.AdvParams = .{
.properties = .{
.type_ = .nonconnectable_nonscannable_undirected,
},
.interval = 160, // 100ms (units of 0.625ms)
.duration = 0, // advertise indefinitely
};
gap.adv_set_configure(&adv_handle, &adv_data, &adv_params) catch return signal_error();

// 6. Start advertising
gap.adv_start(adv_handle, ble.conn_cfg_tag_default) catch return signal_error();

// 7. Blink LED1 while advertising
while (true) {
board.led1.toggle();
time.sleep_ms(1000);
}
}

fn build_adv_data(name: []const u8) [31]u8 {
var buf: [31]u8 = .{0} ** 31;
var pos: usize = 0;

// AD structure: flags
buf[pos] = 2; // length
buf[pos + 1] = gap.ad_type.flags;
buf[pos + 2] = gap.adv_flag.le_only_general_disc_mode;
pos += 3;

// AD structure: complete local name
const name_len: u8 = @intCast(name.len);
buf[pos] = name_len + 1; // length (type byte + name)
buf[pos + 1] = gap.ad_type.complete_local_name;
@memcpy(buf[pos + 2 .. pos + 2 + name_len], name);

return buf;
}

fn signal_error() void {
board.led4.put(board.led_active_state);
while (true) {}
}

fn fault_handler(_: sdm.FaultId, _: u32, _: u32) callconv(.c) void {
signal_error();
}
38 changes: 38 additions & 0 deletions examples/nordic/nrf5x/src/softdevice_init.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SoftDevice initialization example for PCA10040.
//
// Enables the SoftDevice BLE stack, then blinks LED1 to confirm it's running.
// LED4 is lit on fault. This demonstrates the minimal SoftDevice setup.
const microzig = @import("microzig");
const board = microzig.board;
const nrf = microzig.hal;
const sd = nrf.softdevice;
const time = nrf.time;

pub fn main() void {
board.init();

// Enable SoftDevice with default LF clock (internal RC oscillator).
const clock_cfg: sd.sdm.ClockLfCfg = .{
.source = .rc,
.rc_ctiv = 16, // calibration interval (4s * 16 = 64s)
.rc_temp_ctiv = 2, // calibrate on temperature change every 2 intervals
.accuracy = .ppm_500,
};

sd.sdm.enable(&clock_cfg, fault_handler) catch {
// Signal error on LED4
board.led4.put(board.led_active_state);
while (true) {}
};

// SoftDevice is running — blink LED1
while (true) {
board.led1.toggle();
time.sleep_ms(500);
}
}

fn fault_handler(_: sd.sdm.FaultId, _: u32, _: u32) callconv(.c) void {
board.led4.put(board.led_active_state);
while (true) {}
}
17 changes: 17 additions & 0 deletions examples/nordic/nrf5x/src/softdevice_merged_blinky.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SoftDevice "merged" example: the build produces a combined Intel HEX that
// contains both the SoftDevice binary and the application. Flash the
// *_merged.hex to program everything in one shot. The app code is identical
// to the preflashed variant — only the build configuration differs.
const microzig = @import("microzig");
const board = microzig.board;
const nrf = microzig.hal;
const time = nrf.time;

pub fn main() void {
board.init();

while (true) {
board.led1.toggle();
time.sleep_ms(250);
}
}
16 changes: 16 additions & 0 deletions examples/nordic/nrf5x/src/softdevice_preflashed_blinky.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SoftDevice "preflashed" example: assumes the SoftDevice is already on the
// chip. The build system adjusts the linker memory map so the application is
// placed after the SoftDevice region. Flash this .hex/.elf on its own.
const microzig = @import("microzig");
const board = microzig.board;
const nrf = microzig.hal;
const time = nrf.time;

pub fn main() void {
board.init();

while (true) {
board.led1.toggle();
time.sleep_ms(250);
}
}
23 changes: 23 additions & 0 deletions port/nordic/nrf5x/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

HALs and register definitions for nrf5x devices

## SoftDevice integration

The nRF5x port now exposes SoftDevice-aware targets under `mb.ports.nrf5x.softdevice`.

Available profiles:

- `s132` for nRF52832
- `s140` for nRF52833, nRF52840

SoftDevice binaries are sourced from the official Nordic nRF5 SDK v17.1.0
package via Zig package manager.

Example target names:

- `mb.ports.nrf5x.softdevice.boards.nordic.pca10040_s132`
- `mb.ports.nrf5x.softdevice.boards.nordic.nrf52840_dongle_s140`
- `mb.ports.nrf5x.softdevice.boards.nordic.nrf52840_mdk_s140`
- `mb.ports.nrf5x.softdevice.boards.bbc.microbit_v2_s140`

You can merge app HEX + SoftDevice HEX with:

- `mb.ports.nrf5x.merge_hex(app_hex, softdevice_hex, "output.hex")`

## Renode supports:

- nrf52840 development kit
Loading
Loading