Skip to content
Open
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
2 changes: 2 additions & 0 deletions examples/nordic/nrf5x/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub fn build(b: *std.Build) void {
.{ .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 = nrf52840_mdk, .name = "nrf52840_mdk_usb_hid", .file = "src/usb_hid.zig" },
.{ .target = nrf52840_mdk, .name = "nrf52840_mdk_usb_cdc", .file = "src/usb_cdc.zig" },

.{ .target = pca10040, .name = "pca10040_blinky", .file = "src/blinky.zig" },
.{ .target = pca10040, .name = "pca10040_uart", .file = "src/uart.zig" },
Expand Down
127 changes: 127 additions & 0 deletions examples/nordic/nrf5x/src/usb_cdc.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//! Adapted from
//! examples/raspberrypi/rp2xxx/src/usb_cdc.zig

const std = @import("std");
const microzig = @import("microzig");
const nrf = microzig.hal;
const board = microzig.board;
const usb = microzig.core.usb;
const time = nrf.time;
const USBD = nrf.usbd.USBD;
const clocks = nrf.clocks;
const uart = nrf.uart.num(0);

const USB_Serial = usb.drivers.CDC;

pub const panic = microzig.panic;

pub const std_options = microzig.std_options(.{
.log_level = .debug,
.log_scope_levels = &.{
.{ .scope = .usb_dev, .level = .warn },
.{ .scope = .usb_ctrl, .level = .warn },
.{ .scope = .usb_cdc, .level = .warn },
},
.logFn = nrf.uart.log,
});

comptime {
_ = microzig.export_startup();
}

var usb_device: USBD = undefined;

// Generate a device controller with descriptor and handlers setup for CDC (USB_Serial)
var usb_controller: usb.DeviceController(.{
.bcd_usb = USBD.max_supported_bcd_usb,
.device_triple = .unspecified,
.vendor = USBD.default_vendor_id,
.product = USBD.default_product_id,
.bcd_device = .v1_00,
.serial = "someserial",
.max_supported_packet_size = USBD.max_supported_packet_size,
.configurations = &.{.{
.attributes = .{ .self_powered = false },
.max_current_ma = 50,
.Drivers = struct { serial: USB_Serial },
}},
}, .{.{
.serial = .{ .itf_notifi = "Board CDC", .itf_data = "Board CDC Data" },
}}) = .init;

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

uart.apply(.{
.tx_pin = board.uart_tx,
.rx_pin = board.uart_rx,
});

nrf.uart.init_logger(uart);

clocks.hfxo.start();
usb_device = .init();

var old: u64 = 0;
var new: u64 = 0;

var i: u32 = 0;

while (true) {
// You can now poll for USB events
usb_device.poll(&usb_controller);

// Ensure that the host as finished enumerating our USB device
if (usb_controller.drivers()) |drivers| {
new = time.get_time_since_boot().to_us();
if (new - old > 500_000) {
old = new;
board.led1.toggle();
i += 1;
std.log.info("cdc test: {}", .{i});

usb_cdc_write(&drivers.serial, "This is very very long text sent from nRF52 by USB CDC to your device: {}\r\n", .{i});
}

// read and print host command if present
const message = usb_cdc_read(&drivers.serial);
if (message.len > 0) {
usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message});
}
}
}
}

var usb_tx_buff: [1024]u8 = undefined;

/// Transfer data to host
/// NOTE: After each USB chunk transfer, we have to call the USB task so that bus TX events can be
/// handled
pub fn usb_cdc_write(serial: *USB_Serial, comptime fmt: []const u8, args: anytype) void {
var tx = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{};

while (tx.len > 0) {
tx = tx[serial.write(tx)..];
usb_device.poll(&usb_controller);
}
// Short messages are not sent right away; instead, they accumulate in a buffer, so we have to force a flush to send them
while (!serial.flush())
usb_device.poll(&usb_controller);
}

var usb_rx_buff: [1024]u8 = undefined;

/// Receive data from host
/// NOTE: Read code was not tested extensively. In case of issues, try to call USB task before every
/// read operation
pub fn usb_cdc_read(serial: *USB_Serial) []const u8 {
var rx_len: usize = 0;

while (true) {
const len = serial.read(usb_rx_buff[rx_len..]);
rx_len += len;
if (len == 0) break;
}

return usb_rx_buff[0..rx_len];
}
188 changes: 188 additions & 0 deletions examples/nordic/nrf5x/src/usb_hid.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
//! Adapted from
//! examples/raspberrypi/rp2xxx/src/usb_cdc.zig

const std = @import("std");
const microzig = @import("microzig");
const nrf = microzig.hal;
const board = microzig.board;
const usb = microzig.core.usb;
const time = nrf.time;
const USBD = nrf.usbd.USBD;
const clocks = nrf.clocks;
const uart = nrf.uart.num(0);

pub const panic = microzig.panic;

pub const std_options = microzig.std_options(.{
.log_level = .debug,
.log_scope_levels = &.{
.{ .scope = .usb_dev, .level = .warn },
.{ .scope = .usb_ctrl, .level = .warn },
.{ .scope = .usb_hid_int_driver, .level = .warn },
},
.logFn = nrf.uart.log,
});

comptime {
_ = microzig.export_startup();
}

pub const Modifiers = packed struct(u8) {
lctrl: bool,
lshift: bool,
lalt: bool,
lgui: bool,
rctrl: bool,
rshift: bool,
ralt: bool,
rgui: bool,

pub const none: @This() = @bitCast(@as(u8, 0));
};

pub const Code = enum(u8) {
// Codes taken from https://gist.github.com/mildsunrise/4e231346e2078f440969cdefb6d4caa3
// zig fmt: off
reserved = 0x00, error_roll_over, post_fail, error_undefined,
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
top_1, top_2, top_3, top_4, top_5, top_6, top_7, top_8, top_9, top_0,
enter, escape, delete, tab, space,
@"-", @"=", @"[", @"]", @"\\", @"non_us_#", @";", @"'", @"`", @",", @".", @"/",
caps_lock,
f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12,
print_screen, scroll_lock, pause, insert, home, page_up, delete_forward, end, page_down,
right_arrow, left_arrow, down_arrow, up_arrow, num_lock,
kpad_div, kpad_mul, kpad_sub, kpad_add, kpad_enter,
kpad_1, kpad_2, kpad_3, kpad_4, kpad_5, kpad_6, kpad_7, kpad_8, kpad_9, kpad_0,
kpad_delete, @"non_us_\\", application, power, @"kpad_=",
f13, f14, f15, f16, f17, f18, f19, f20, f21, f22, f23, f24,
lctrl = 224, lshift, lalt, lgui, rctrl, rshift, ralt, rgui,
// zig fmt: on
_,
};

pub const KeyboardInReport = extern struct {
modifiers: Modifiers,
reserved: u8 = 0,
keys: [6]Code,

comptime {
std.debug.assert(@sizeOf(@This()) == 8);
}

pub const empty: @This() = .{ .modifiers = .none, .keys = @splat(.reserved) };
};

pub const KeyboardOutReport = packed struct(u8) {
num_lock: bool,
caps_lock: bool,
scroll_lock: bool,
padding: u5 = 0,
};

const Keyboard = usb.drivers.hid.InterruptDriver(.{
.subclass = .Boot,
.protocol = .Boot,
.report_descriptor = &.{
.{ .global_usage_page = .generic_desktop },
.local_usage_enum(.{ .generic_desktop = .keyboard }),
.{ .main_collection = .Application },
// Input: modifier key bitmap
.{ .data = .{
.usage = .{ .global_page = .keyboard },
.usage_range = .{ 0xE0, 0xE7 },
.count = 8,
.Child = bool,
.dir = .In,
.type = .dynamic,
} },
// Reserved 8 bits
.{ .data_static = .{ .In, u8 } },
// Output: indicator LEDs
.{ .data = .{
.usage = .{ .global_page = .led },
.usage_range = .{ 1, 5 },
.count = 5,
.Child = bool,
.dir = .Out,
.type = .dynamic,
} },
// Padding
.{ .data_static = .{ .Out, u3 } },
// Input: up to 6 pressed key codes
.{ .data = .{
.usage = .{ .global_page = .keyboard },
.usage_range = .{ 0x00, 0xff },
.count = 6,
.Child = u8,
.dir = .In,
.type = .selector,
} },
// End
.main_collection_end,
},
.InReport = KeyboardInReport,
.OutReport = KeyboardOutReport,
});

var usb_device: USBD = undefined;

var usb_controller: usb.DeviceController(.{
.bcd_usb = USBD.max_supported_bcd_usb,
.device_triple = .unspecified,
.vendor = USBD.default_vendor_id,
.product = USBD.default_product_id,
.bcd_device = .v1_00,
.serial = "someserial",
.max_supported_packet_size = USBD.max_supported_packet_size,
.configurations = &.{.{
.attributes = .{ .self_powered = false },
.max_current_ma = 50,
.Drivers = struct { keyboard: Keyboard },
}},
}, .{.{
.keyboard = .{ .itf_string = "Boot Keyboard", .poll_interval = 1 },
}}) = .init;

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

uart.apply(.{
.tx_pin = board.uart_tx,
.rx_pin = board.uart_rx,
});

nrf.uart.init_logger(uart);

clocks.hfxo.start();
usb_device = .init();

var old: u64 = time.get_time_since_boot().to_us();
var new: u64 = 0;
const message: []const Code = &.{ .h, .e, .l, .l, .o, .space, .w, .o, .r, .l, .d, .caps_lock, .enter };
var idx: usize = 0;

while (true) {
// You can now poll for USB events
usb_device.poll(&usb_controller);

if (usb_controller.drivers()) |drivers| {
new = time.get_time_since_boot().to_us();
if (new - old > 2_000_000) {
old = new;
idx = 0;
} else {
std.log.info("report {}", .{idx});
idx += @intFromBool(if (idx & 1 == 0 and idx < 2 * message.len)
drivers.keyboard.send_report(
&.{ .modifiers = .none, .keys = .{message[@intCast(idx / 2)]} ++ .{.reserved} ** 5 },
)
else
drivers.keyboard.send_report(&.empty));
}

if (drivers.keyboard.receive_report()) |report|
board.led1.put(@intFromBool(report.caps_lock));
}
}
}
2 changes: 2 additions & 0 deletions port/nordic/nrf5x/src/hal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub const i2cdma = @import("hal/i2cdma.zig");
pub const spim = @import("hal/spim.zig");
pub const time = @import("hal/time.zig");
pub const uart = @import("hal/uart.zig");
pub const usbd = @import("hal/usbd.zig");
pub const drivers = @import("hal/drivers.zig");
// TODO: adc, timers, pwm, rng, rtc alarms, interrupts, wdt, wifi, nfc, bt, zigbee

Expand All @@ -28,6 +29,7 @@ test "hal tests" {
_ = spim;
_ = time;
_ = uart;
_ = usbd;

_ = drivers;
}
Loading
Loading