Regz rewrite (#103)

* wip

* remove tools

* reorganized

* move hardware into their own repos

* snake_case

* use FileSource for board/chip/cpu descriptors

* are_globally_enabled -> globally_enabled

* rearrange
wch-ch32v003
Matt Knight 2 years ago committed by GitHub
parent 9ccde9ff37
commit 97ca5497da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,71 +9,12 @@ pub const microzig = @import("src/main.zig");
// alias for packages // alias for packages
pub const addEmbeddedExecutable = microzig.addEmbeddedExecutable; pub const addEmbeddedExecutable = microzig.addEmbeddedExecutable;
pub const boards = microzig.boards; pub const boards = microzig.boards;
pub const chips = microzig.chips;
pub const Backing = microzig.Backing; pub const Backing = microzig.Backing;
pub fn build(b: *std.build.Builder) !void { pub fn build(b: *std.build.Builder) !void {
// Standard optimization options allow the person running `zig build -Doptimize=...` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const test_step = b.step("test", "Builds and runs the library test suite"); const test_step = b.step("test", "Builds and runs the library test suite");
const BuildConfig = struct { name: []const u8, backing: Backing, supports_uart_test: bool = true }; _ = optimize;
const all_backings = [_]BuildConfig{ _ = test_step;
//BuildConfig{ .name = "boards.arduino_nano", .backing = Backing{ .board = boards.arduino_nano } },
//BuildConfig{ .name = "boards.arduino_uno", .backing = Backing{ .board = boards.arduino_uno } },
BuildConfig{ .name = "boards.mbed_lpc1768", .backing = Backing{ .board = boards.mbed_lpc1768 } },
//BuildConfig{ .name = "chips.atmega328p", .backing = Backing{ .chip = chips.atmega328p } },
BuildConfig{ .name = "chips.lpc1768", .backing = Backing{ .chip = chips.lpc1768 } },
//BuildConfig{ .name = "chips.stm32f103x8", .backing = Backing{ .chip = chips.stm32f103x8 } },
BuildConfig{ .name = "boards.stm32f3discovery", .backing = Backing{ .board = boards.stm32f3discovery } },
BuildConfig{ .name = "boards.stm32f4discovery", .backing = Backing{ .board = boards.stm32f4discovery } },
BuildConfig{ .name = "boards.stm32f429idiscovery", .backing = Backing{ .board = boards.stm32f429idiscovery }, .supports_uart_test = false },
BuildConfig{ .name = "chips.gd32vf103x8", .backing = Backing{ .chip = chips.gd32vf103x8 } },
BuildConfig{ .name = "boards.longan_nano", .backing = Backing{ .board = boards.longan_nano } },
};
const Test = struct { name: []const u8, source: []const u8, uses_uart: bool = false, on_riscv32: bool = true, on_avr: bool = true };
const all_tests = [_]Test{
Test{ .name = "minimal", .source = "tests/minimal.zig" },
Test{ .name = "blinky", .source = "tests/blinky.zig" },
Test{ .name = "uart-sync", .source = "tests/uart-sync.zig", .uses_uart = true, .on_avr = false },
// Note: this example uses the systick interrupt and therefore only for arm microcontrollers
Test{ .name = "interrupt", .source = "tests/interrupt.zig", .on_riscv32 = false, .on_avr = true },
};
const filter = b.option(std.Target.Cpu.Arch, "filter-target", "Filters for a certain cpu target");
for (all_backings) |cfg| {
for (all_tests) |tst| {
if (tst.uses_uart and !cfg.supports_uart_test) continue;
if ((cfg.backing.getTarget().cpu_arch.?) == .avr and tst.on_avr == false) continue;
if (!tst.on_riscv32) continue;
var exe = microzig.addEmbeddedExecutable(
b,
b.fmt("test-{s}-{s}.elf", .{ tst.name, cfg.name }),
tst.source,
cfg.backing,
.{ .optimize = optimize },
);
exe.addDriver(microzig.drivers.button);
if (filter == null or exe.inner.target.cpu_arch.? == filter.?) {
exe.inner.install();
test_step.dependOn(&exe.inner.step);
const bin = b.addInstallRaw(
exe.inner,
b.fmt("test-{s}-{s}.bin", .{ tst.name, cfg.name }),
.{},
);
b.getInstallStep().dependOn(&bin.step);
}
}
}
} }

@ -0,0 +1 @@
pub const experimental = @import("core/experimental.zig");

@ -0,0 +1,12 @@
//! These are experimental generic interfaces. We want to see more use and
//! discussion of them before committing them to microzig's "official" API.
//!
//! They are bound to have breaking changes in the future, or even disappear,
//! so use at your own risk.
pub const clock = @import("experimental/clock.zig");
pub const debug = @import("experimental/debug.zig");
pub const gpio = @import("experimental/gpio.zig");
pub const i2c = @import("experimental/i2c.zig");
pub const Pin = @import("experimental/pin.zig").Pin;
pub const spi = @import("experimental/spi.zig");
pub const uart = @import("experimental/uart.zig");

@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const micro = @import("microzig.zig"); const micro = @import("microzig");
const chip = @import("chip"); const chip = @import("chip");
/// An enumeration of clock sources. /// An enumeration of clock sources.

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const micro = @import("microzig.zig"); const micro = @import("microzig");
pub fn busySleep(comptime limit: comptime_int) void { pub fn busy_sleep(comptime limit: comptime_int) void {
if (limit <= 0) @compileError("limit must be non-negative!"); if (limit <= 0) @compileError("limit must be non-negative!");
comptime var bits = 0; comptime var bits = 0;
@ -18,13 +18,13 @@ pub fn busySleep(comptime limit: comptime_int) void {
} }
const DebugErr = error{}; const DebugErr = error{};
fn writerWrite(ctx: void, string: []const u8) DebugErr!usize { fn writer_write(ctx: void, string: []const u8) DebugErr!usize {
_ = ctx; _ = ctx;
write(string); write(string);
return string.len; return string.len;
} }
const DebugWriter = std.io.Writer(void, DebugErr, writerWrite); const DebugWriter = std.io.Writer(void, DebugErr, writer_write);
pub fn write(string: []const u8) void { pub fn write(string: []const u8) void {
if (!micro.config.has_board) if (!micro.config.has_board)
@ -32,7 +32,7 @@ pub fn write(string: []const u8) void {
if (!@hasDecl(micro.board, "debugWrite")) if (!@hasDecl(micro.board, "debugWrite"))
return; return;
micro.board.debugWrite(string); micro.board.debug_write(string);
} }
pub fn writer() DebugWriter { pub fn writer() DebugWriter {

@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const micro = @import("microzig.zig"); const micro = @import("microzig");
const chip = @import("chip"); const chip = @import("chip");
pub const Mode = enum { pub const Mode = enum {
@ -37,12 +37,12 @@ pub fn Gpio(comptime pin: type, comptime config: anytype) type {
fn init() void { fn init() void {
switch (mode) { switch (mode) {
.input, .generic, .input_output => { .input, .generic, .input_output => {
setDirection(.input, undefined); set_direction(.input, undefined);
}, },
.output => { .output => {
if (comptime !@hasField(@TypeOf(config), "initial_state")) if (comptime !@hasField(@TypeOf(config), "initial_state"))
@compileError("An output pin requires initial_state to be either .high or .low"); @compileError("An output pin requires initial_state to be either .high or .low");
setDirection(.output, switch (config.initial_state) { set_direction(.output, switch (config.initial_state) {
.low => State.low, .low => State.low,
.high => State.high, .high => State.high,
else => @compileError("An output pin requires initial_state to be either .high or .low"), else => @compileError("An output pin requires initial_state to be either .high or .low"),
@ -51,8 +51,8 @@ pub fn Gpio(comptime pin: type, comptime config: anytype) type {
.open_drain => { .open_drain => {
if (comptime !@hasField(@TypeOf(config), "initial_state")) if (comptime !@hasField(@TypeOf(config), "initial_state"))
@compileError("An open_drain pin requires initial_state to be either .floating or .driven"); @compileError("An open_drain pin requires initial_state to be either .floating or .driven");
setDirection(.input, undefined); set_direction(.input, undefined);
setDrive(switch (config.initial_state) { set_drive(switch (config.initial_state) {
.floating => Drive.disabled, .floating => Drive.disabled,
.driven => Drive.enabled, .driven => Drive.enabled,
else => @compileError("An open_drain pin requires initial_state to be either .floating or .driven"), else => @compileError("An open_drain pin requires initial_state to be either .floating or .driven"),
@ -61,7 +61,7 @@ pub fn Gpio(comptime pin: type, comptime config: anytype) type {
.alternate_function => { .alternate_function => {
if (comptime @hasDecl(chip.gpio, "AlternateFunction")) { if (comptime @hasDecl(chip.gpio, "AlternateFunction")) {
const alternate_function = @as(chip.gpio.AlternateFunction, config.alternate_function); const alternate_function = @as(chip.gpio.AlternateFunction, config.alternate_function);
setAlternateFunction(alternate_function); set_alternate_function(alternate_function);
} else { } else {
@compileError("Alternate Function not supported yet"); @compileError("Alternate Function not supported yet");
} }
@ -78,10 +78,10 @@ pub fn Gpio(comptime pin: type, comptime config: anytype) type {
chip.gpio.write(pin.source_pin, state); chip.gpio.write(pin.source_pin, state);
} }
fn setToHigh() void { fn set_to_high() void {
write(.high); write(.high);
} }
fn setToLow() void { fn set_to_low() void {
write(.low); write(.low);
} }
fn toggle() void { fn toggle() void {
@ -96,7 +96,7 @@ pub fn Gpio(comptime pin: type, comptime config: anytype) type {
} }
// bi-di: // bi-di:
fn setDirection(dir: Direction, output_state: State) void { fn set_direction(dir: Direction, output_state: State) void {
switch (dir) { switch (dir) {
.output => { .output => {
chip.gpio.setOutput(pin.source_pin); chip.gpio.setOutput(pin.source_pin);
@ -105,7 +105,7 @@ pub fn Gpio(comptime pin: type, comptime config: anytype) type {
.input => chip.gpio.setInput(pin.source_pin), .input => chip.gpio.setInput(pin.source_pin),
} }
} }
fn getDirection() Direction { fn get_direction() Direction {
if (chip.gpio.isOutput(pin.source_pin)) { if (chip.gpio.isOutput(pin.source_pin)) {
return .output; return .output;
} else { } else {
@ -114,16 +114,16 @@ pub fn Gpio(comptime pin: type, comptime config: anytype) type {
} }
// open drain // open drain
fn setDrive(drive: Drive) void { fn set_drive(drive: Drive) void {
_ = drive; _ = drive;
@compileError("open drain not implemented yet!"); @compileError("open drain not implemented yet!");
} }
fn getDrive() Drive { fn get_drive() Drive {
@compileError("open drain not implemented yet!"); @compileError("open drain not implemented yet!");
} }
// alternate function // alternate function
fn setAlternateFunction(af: chip.gpio.AlternateFunction) void { fn set_alternate_function(af: chip.gpio.AlternateFunction) void {
chip.gpio.setAlternateFunction(pin.source_pin, af); chip.gpio.setAlternateFunction(pin.source_pin, af);
} }
}; };

@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const micro = @import("microzig.zig"); const micro = @import("microzig");
const chip = @import("chip"); const chip = @import("chip");
pub fn I2CController(comptime index: usize, comptime pins: Pins) type { pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
@ -19,7 +19,7 @@ pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
state: SystemI2CController.ReadState, state: SystemI2CController.ReadState,
pub const Reader = std.io.Reader(*Self, ReadError, readSome); pub const Reader = std.io.Reader(*Self, ReadError, read_some);
/// NOTE that some platforms, notably most (all?) STM32 microcontrollers, /// NOTE that some platforms, notably most (all?) STM32 microcontrollers,
/// allow only a single read call per transfer. /// allow only a single read call per transfer.
@ -27,7 +27,7 @@ pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
return Reader{ .context = self }; return Reader{ .context = self };
} }
fn readSome(self: *Self, buffer: []u8) ReadError!usize { fn read_some(self: *Self, buffer: []u8) ReadError!usize {
try self.state.readNoEof(buffer); try self.state.readNoEof(buffer);
return buffer.len; return buffer.len;
} }
@ -40,7 +40,7 @@ pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
/// RESTART to a new transfer, invalidating this object. /// RESTART to a new transfer, invalidating this object.
/// Note that some platforms set the repeated START condition /// Note that some platforms set the repeated START condition
/// on the first read or write call. /// on the first read or write call.
pub fn restartTransfer(self: *Self, comptime new_direction: Direction) !Transfer(new_direction) { pub fn restart_transfer(self: *Self, comptime new_direction: Direction) !Transfer(new_direction) {
return Transfer(direction){ .state = try self.state.restartTransfer(new_direction) }; return Transfer(direction){ .state = try self.state.restartTransfer(new_direction) };
} }
}, },
@ -49,7 +49,7 @@ pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
state: SystemI2CController.WriteState, state: SystemI2CController.WriteState,
pub const Writer = std.io.Writer(*Self, WriteError, writeSome); pub const Writer = std.io.Writer(*Self, WriteError, write_some);
/// NOTE that some platforms, notably most (all?) STM32 microcontrollers, /// NOTE that some platforms, notably most (all?) STM32 microcontrollers,
/// will not immediately write all bytes, but postpone that /// will not immediately write all bytes, but postpone that
@ -58,7 +58,7 @@ pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
return Writer{ .context = self }; return Writer{ .context = self };
} }
fn writeSome(self: *Self, buffer: []const u8) WriteError!usize { fn write_some(self: *Self, buffer: []const u8) WriteError!usize {
try self.state.writeAll(buffer); try self.state.writeAll(buffer);
return buffer.len; return buffer.len;
} }
@ -71,7 +71,7 @@ pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
/// RESTART to a new transfer, invalidating this object. /// RESTART to a new transfer, invalidating this object.
/// Note that some platforms set the repeated START condition /// Note that some platforms set the repeated START condition
/// on the first read or write call. /// on the first read or write call.
pub fn restartTransfer(self: *Self, comptime new_direction: Direction) !Transfer(new_direction) { pub fn restart_transfer(self: *Self, comptime new_direction: Direction) !Transfer(new_direction) {
return switch (new_direction) { return switch (new_direction) {
.read => Transfer(new_direction){ .state = try self.state.restartRead() }, .read => Transfer(new_direction){ .state = try self.state.restartRead() },
.write => Transfer(new_direction){ .state = try self.state.restartWrite() }, .write => Transfer(new_direction){ .state = try self.state.restartWrite() },
@ -84,7 +84,7 @@ pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
/// START a new transfer. /// START a new transfer.
/// Note that some platforms set the START condition /// Note that some platforms set the START condition
/// on the first read or write call. /// on the first read or write call.
pub fn startTransfer(self: Device, comptime direction: Direction) !Transfer(direction) { pub fn start_transfer(self: Device, comptime direction: Direction) !Transfer(direction) {
return switch (direction) { return switch (direction) {
.read => Transfer(direction){ .state = try SystemI2CController.ReadState.start(self.address) }, .read => Transfer(direction){ .state = try SystemI2CController.ReadState.start(self.address) },
.write => Transfer(direction){ .state = try SystemI2CController.WriteState.start(self.address) }, .write => Transfer(direction){ .state = try SystemI2CController.WriteState.start(self.address) },
@ -92,12 +92,12 @@ pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
} }
/// Shorthand for 'register-based' devices /// Shorthand for 'register-based' devices
pub fn writeRegister(self: Device, register_address: u8, byte: u8) ReadError!void { pub fn write_register(self: Device, register_address: u8, byte: u8) ReadError!void {
try self.writeRegisters(register_address, &.{byte}); try self.writeRegisters(register_address, &.{byte});
} }
/// Shorthand for 'register-based' devices /// Shorthand for 'register-based' devices
pub fn writeRegisters(self: Device, register_address: u8, buffer: []u8) ReadError!void { pub fn write_registers(self: Device, register_address: u8, buffer: []u8) ReadError!void {
var wt = try self.startTransfer(.write); var wt = try self.startTransfer(.write);
defer wt.stop() catch {}; defer wt.stop() catch {};
try wt.writer().writeByte(register_address); try wt.writer().writeByte(register_address);
@ -105,14 +105,14 @@ pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
} }
/// Shorthand for 'register-based' devices /// Shorthand for 'register-based' devices
pub fn readRegister(self: Device, register_address: u8) ReadError!u8 { pub fn read_register(self: Device, register_address: u8) ReadError!u8 {
var buffer: [1]u8 = undefined; var buffer: [1]u8 = undefined;
try self.readRegisters(register_address, &buffer); try self.readRegisters(register_address, &buffer);
return buffer[0]; return buffer[0];
} }
/// Shorthand for 'register-based' devices /// Shorthand for 'register-based' devices
pub fn readRegisters(self: Device, register_address: u8, buffer: []u8) ReadError!void { pub fn read_registers(self: Device, register_address: u8, buffer: []u8) ReadError!void {
var rt = write_and_restart: { var rt = write_and_restart: {
var wt = try self.startTransfer(.write); var wt = try self.startTransfer(.write);
errdefer wt.stop() catch {}; errdefer wt.stop() catch {};

@ -1,7 +1,8 @@
const std = @import("std"); const std = @import("std");
const micro = @import("microzig.zig"); const micro = @import("microzig");
const chip = @import("chip"); const chip = @import("chip");
const board = @import("board"); const board = @import("board");
const hal = @import("hal");
/// Returns a type that will manage the Pin defined by `spec`. /// Returns a type that will manage the Pin defined by `spec`.
/// Spec is either the pin as named in the datasheet of the chip /// Spec is either the pin as named in the datasheet of the chip
@ -17,15 +18,15 @@ pub fn Pin(comptime spec: []const u8) type {
const chip_namespace = "chip:"; const chip_namespace = "chip:";
// Pins can be namespaced with "board:" for board and "chip:" for chip // Pins can be namespaced with "board:" for board and "chip:" for chip
// These namespaces are not passed to chip.parsePin // These namespaces are not passed to hal.parse_pin()
const pin = if (std.mem.startsWith(u8, spec, board_namespace)) const pin = if (std.mem.startsWith(u8, spec, board_namespace))
chip.parsePin(@field(board.pin_map, spec[board_namespace.len..])) hal.parse_pin(@field(board.pin_map, spec[board_namespace.len..]))
else if (std.mem.startsWith(u8, spec, chip_namespace)) else if (std.mem.startsWith(u8, spec, chip_namespace))
chip.parsePin(spec[chip_namespace.len..]) hal.parse_pin(spec[chip_namespace.len..])
else if (micro.config.has_board and @hasField(@TypeOf(board.pin_map), spec)) else if (micro.config.has_board and @hasField(@TypeOf(board.pin_map), spec))
chip.parsePin(@field(board.pin_map, spec)) hal.parse_pin(@field(board.pin_map, spec))
else else
chip.parsePin(spec); hal.parse_pin(spec);
return struct { return struct {
pub const name = if (std.mem.startsWith(u8, spec, board_namespace)) pub const name = if (std.mem.startsWith(u8, spec, board_namespace))
@ -40,7 +41,7 @@ pub fn Pin(comptime spec: []const u8) type {
pub const source_pin = pin; pub const source_pin = pin;
pub fn route(target: pin.Targets) void { pub fn route(target: pin.Targets) void {
chip.routePin(source_pin, target); hal.route_pin(source_pin, target);
} }
}; };
} }

@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const micro = @import("microzig.zig"); const micro = @import("microzig");
const chip = @import("chip"); const chip = @import("chip");
/// The SPI bus with the given environment-specific number. /// The SPI bus with the given environment-specific number.
@ -23,30 +23,30 @@ pub fn SpiBus(comptime index: usize) type {
device: SelfSpiDevice, device: SelfSpiDevice,
fn transceiveByte(self: *SelfTransfer, write_byte: u8, read_pointer: *u8) !void { fn transceive_byte(self: *SelfTransfer, write_byte: u8, read_pointer: *u8) !void {
try self.device.internal.transceiveByte(write_byte, read_pointer); try self.device.internal.transceiveByte(write_byte, read_pointer);
} }
pub const Writer = std.io.Writer(*SelfTransfer, WriteError, writeSome); pub const Writer = std.io.Writer(*SelfTransfer, WriteError, write_some);
/// Return a standard Writer (which ignores the bytes read). /// Return a standard Writer (which ignores the bytes read).
pub fn writer(self: *SelfTransfer) Writer { pub fn writer(self: *SelfTransfer) Writer {
return Writer{ .context = self }; return Writer{ .context = self };
} }
fn writeSome(self: *SelfTransfer, buffer: []const u8) WriteError!usize { fn write_some(self: *SelfTransfer, buffer: []const u8) WriteError!usize {
try self.device.internal.writeAll(buffer); try self.device.internal.writeAll(buffer);
return buffer.len; return buffer.len;
} }
pub const Reader = std.io.Reader(*SelfTransfer, ReadError, readSome); pub const Reader = std.io.Reader(*SelfTransfer, ReadError, read_some);
/// Return a standard Reader (which writes arbitrary bytes). /// Return a standard Reader (which writes arbitrary bytes).
pub fn reader(self: *SelfTransfer) Reader { pub fn reader(self: *SelfTransfer) Reader {
return Reader{ .context = self }; return Reader{ .context = self };
} }
fn readSome(self: *SelfTransfer, buffer: []u8) ReadError!usize { fn read_some(self: *SelfTransfer, buffer: []u8) ReadError!usize {
try self.device.internal.readInto(buffer); try self.device.internal.readInto(buffer);
return buffer.len; return buffer.len;
} }
@ -58,7 +58,7 @@ pub fn SpiBus(comptime index: usize) type {
}; };
/// start a new transfer, selecting using the CS pin /// start a new transfer, selecting using the CS pin
pub fn beginTransfer(self: SelfSpiDevice) !Transfer { pub fn begin_transfer(self: SelfSpiDevice) !Transfer {
self.internal.switchToDevice(cs_pin, config); self.internal.switchToDevice(cs_pin, config);
self.internal.beginTransfer(cs_pin, config); self.internal.beginTransfer(cs_pin, config);
return Transfer{ .device = self }; return Transfer{ .device = self };
@ -74,12 +74,12 @@ pub fn SpiBus(comptime index: usize) type {
} }
/// Shorthand for 'register-based' devices /// Shorthand for 'register-based' devices
pub fn writeRegister(self: SelfSpiDevice, register_address: u8, byte: u8) ReadError!void { pub fn write_register(self: SelfSpiDevice, register_address: u8, byte: u8) ReadError!void {
try self.writeRegisters(register_address, &.{byte}); try self.writeRegisters(register_address, &.{byte});
} }
/// Shorthand for 'register-based' devices /// Shorthand for 'register-based' devices
pub fn writeRegisters(self: SelfSpiDevice, register_address: u8, buffer: []u8) ReadError!void { pub fn write_registers(self: SelfSpiDevice, register_address: u8, buffer: []u8) ReadError!void {
var transfer = try self.beginTransfer(); var transfer = try self.beginTransfer();
defer transfer.end(); defer transfer.end();
// write auto-increment, starting at given register // write auto-increment, starting at given register
@ -88,14 +88,14 @@ pub fn SpiBus(comptime index: usize) type {
} }
/// Shorthand for 'register-based' devices /// Shorthand for 'register-based' devices
pub fn readRegister(self: SelfSpiDevice, register_address: u8) ReadError!u8 { pub fn read_register(self: SelfSpiDevice, register_address: u8) ReadError!u8 {
var buffer: [1]u8 = undefined; var buffer: [1]u8 = undefined;
try self.readRegisters(register_address, &buffer); try self.readRegisters(register_address, &buffer);
return buffer[0]; return buffer[0];
} }
/// Shorthand for 'register-based' devices /// Shorthand for 'register-based' devices
pub fn readRegisters(self: SelfSpiDevice, register_address: u8, buffer: []u8) ReadError!void { pub fn read_registers(self: SelfSpiDevice, register_address: u8, buffer: []u8) ReadError!void {
var transfer = try self.beginTransfer(); var transfer = try self.beginTransfer();
defer transfer.end(); defer transfer.end();
// read auto-increment, starting at given register // read auto-increment, starting at given register

@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const micro = @import("microzig.zig"); const micro = @import("microzig");
const chip = @import("chip"); const chip = @import("chip");
pub fn Uart(comptime index: usize, comptime pins: Pins) type { pub fn Uart(comptime index: usize, comptime pins: Pins) type {
@ -19,7 +19,7 @@ pub fn Uart(comptime index: usize, comptime pins: Pins) type {
/// If the UART is already initialized, try to return a handle to it, /// If the UART is already initialized, try to return a handle to it,
/// else initialize with the given config. /// else initialize with the given config.
pub fn getOrInit(config: Config) InitError!Self { pub fn get_or_init(config: Config) InitError!Self {
if (!@hasDecl(SystemUart, "getOrInit")) { if (!@hasDecl(SystemUart, "getOrInit")) {
// fallback to reinitializing the UART // fallback to reinitializing the UART
return init(config); return init(config);
@ -29,11 +29,11 @@ pub fn Uart(comptime index: usize, comptime pins: Pins) type {
}; };
} }
pub fn canRead(self: Self) bool { pub fn can_read(self: Self) bool {
return self.internal.canRead(); return self.internal.canRead();
} }
pub fn canWrite(self: Self) bool { pub fn can_write(self: Self) bool {
return self.internal.canWrite(); return self.internal.canWrite();
} }
@ -45,16 +45,16 @@ pub fn Uart(comptime index: usize, comptime pins: Pins) type {
return Writer{ .context = self }; return Writer{ .context = self };
} }
pub const Reader = std.io.Reader(Self, ReadError, readSome); pub const Reader = std.io.Reader(Self, ReadError, read_some);
pub const Writer = std.io.Writer(Self, WriteError, writeSome); pub const Writer = std.io.Writer(Self, WriteError, write_some);
fn readSome(self: Self, buffer: []u8) ReadError!usize { fn read_some(self: Self, buffer: []u8) ReadError!usize {
for (buffer) |*c| { for (buffer) |*c| {
c.* = self.internal.rx(); c.* = self.internal.rx();
} }
return buffer.len; return buffer.len;
} }
fn writeSome(self: Self, buffer: []const u8) WriteError!usize { fn write_some(self: Self, buffer: []const u8) WriteError!usize {
for (buffer) |c| { for (buffer) |c| {
self.internal.tx(c); self.internal.tx(c);
} }

@ -1,7 +1,7 @@
//! This file is meant as a dependency for the "app" package. //! This file is meant as a dependency for the "app" package.
//! It will only re-export all symbols from "root" under the name "microzig" //! It will only re-export all symbols from "root" under the name "microzig"
//! So we have a flattened and simplified dependency tree. //! So we have a flattened and simplified dependency tree.
//! //!
//! Each config/module of microzig will also just depend on this file, so we can use //! Each config/module of microzig will also just depend on this file, so we can use
//! the same benefits there. //! the same benefits there.

@ -0,0 +1 @@
pub const experimental = @import("drivers/experimental.zig");

@ -0,0 +1,7 @@
//! These are experimental driver interfaces. We want to see more use and
//! discussion of them before committing them to microzig's "official" API.
//!
//! They are bound to have breaking changes in the future, or even disappear,
//! so use at your own risk.
pub const button = @import("experimental/button.zig");
pub const quadrature = @import("experimental/quadrature.zig");

@ -19,7 +19,7 @@ pub fn Button(
comptime gpio: type, comptime gpio: type,
/// The active state for the button. Use `.high` for active-high, `.low` for active-low. /// The active state for the button. Use `.high` for active-high, `.low` for active-low.
comptime active_state: micro.gpio.State, comptime active_state: micro.gpio.State,
/// Optional filter depth for debouncing. If `null` is passed, 16 samples are used to debounce the button, /// Optional filter depth for debouncing. If `null` is passed, 16 samples are used to debounce the button,
/// otherwise the given number of samples is used. /// otherwise the given number of samples is used.
comptime filter_depth: ?comptime_int, comptime filter_depth: ?comptime_int,
) type { ) type {
@ -60,7 +60,7 @@ pub fn Button(
/// Returns `true` when the button is pressed. /// Returns `true` when the button is pressed.
/// Will only be updated when `poll` is regularly called. /// Will only be updated when `poll` is regularly called.
pub fn isPressed(self: *Self) bool { pub fn is_pressed(self: *Self) bool {
return (self.debounce != 0); return (self.debounce != 0);
} }
}; };

@ -15,7 +15,7 @@ pub fn disable(comptime interrupt: anytype) void {
} }
/// Returns true when the given interrupt is unmasked. /// Returns true when the given interrupt is unmasked.
pub fn isEnabled(comptime interrupt: anytype) bool { pub fn is_enabled(comptime interrupt: anytype) bool {
_ = interrupt; _ = interrupt;
@compileError("not implemented yet!"); @compileError("not implemented yet!");
} }
@ -33,15 +33,15 @@ pub fn cli() void {
} }
/// Returns true, when interrupts are globally enabled via `sei()`. /// Returns true, when interrupts are globally enabled via `sei()`.
pub fn areGloballyEnabled() bool { pub fn globally_enabled() bool {
@compileError("not implemented yet!"); @compileError("not implemented yet!");
} }
/// Enters a critical section and disables interrupts globally. /// Enters a critical section and disables interrupts globally.
/// Call `.leave()` on the return value to restore the previous state. /// Call `.leave()` on the return value to restore the previous state.
pub fn enterCriticalSection() CriticalSection { pub fn enter_critical_section() CriticalSection {
var section = CriticalSection{ var section = CriticalSection{
.enable_on_leave = areGloballyEnabled(), .enable_on_leave = globally_enabled(),
}; };
cli(); cli();
return section; return section;
@ -59,3 +59,18 @@ const CriticalSection = struct {
} }
} }
}; };
// TODO: update with arch specifics
pub const Handler = extern union {
C: *const fn () callconv(.C) void,
Naked: *const fn () callconv(.Naked) void,
// Interrupt is not supported on arm
};
pub const unhandled = Handler{
.C = struct {
fn tmp() callconv(.C) noreturn {
@panic("unhandled interrupt");
}
}.tmp,
};

@ -1,12 +1,11 @@
const std = @import("std"); const std = @import("std");
pub const LinkerScriptStep = @import("modules/LinkerScriptStep.zig"); pub const LinkerScriptStep = @import("modules/LinkerScriptStep.zig");
pub const boards = @import("modules/boards.zig");
pub const chips = @import("modules/chips.zig");
pub const cpus = @import("modules/cpus.zig"); pub const cpus = @import("modules/cpus.zig");
pub const Board = @import("modules/Board.zig"); pub const Board = @import("modules/Board.zig");
pub const Chip = @import("modules/Chip.zig"); pub const Chip = @import("modules/Chip.zig");
pub const Cpu = @import("modules/Cpu.zig"); pub const Cpu = @import("modules/Cpu.zig");
pub const MemoryRegion = @import("modules/MemoryRegion.zig");
const LibExeObjStep = std.build.LibExeObjStep; const LibExeObjStep = std.build.LibExeObjStep;
@ -14,7 +13,7 @@ pub const Backing = union(enum) {
board: Board, board: Board,
chip: Chip, chip: Chip,
pub fn getTarget(self: @This()) std.zig.CrossTarget { pub fn get_target(self: @This()) std.zig.CrossTarget {
return switch (self) { return switch (self) {
.board => |brd| brd.chip.cpu.target, .board => |brd| brd.chip.cpu.target,
.chip => |chip| chip.cpu.target, .chip => |chip| chip.cpu.target,
@ -29,10 +28,7 @@ fn root() []const u8 {
} }
pub const BuildOptions = struct { pub const BuildOptions = struct {
// a hal module is a module with ergonomic wrappers for registers for a optimize: std.builtin.OptimizeMode = .Debug,
// given mcu, it's only dependency can be microzig
hal_module_path: ?std.build.FileSource = null,
optimize: std.builtin.OptimizeMode = std.builtin.OptimizeMode.Debug,
}; };
pub const EmbeddedExecutable = struct { pub const EmbeddedExecutable = struct {
@ -42,28 +38,6 @@ pub const EmbeddedExecutable = struct {
exe.inner.addModule(name, module); exe.inner.addModule(name, module);
} }
pub fn addDriver(exe: *EmbeddedExecutable, driver: Driver) void {
var dependencies = std.ArrayList(std.Build.ModuleDependency).init(exe.inner.builder.allocator);
for (driver.dependencies) |dep| {
dependencies.append(.{
.name = dep,
.module = exe.inner.builder.modules.get(dep).?,
}) catch @panic("OOM");
}
// TODO: this is not perfect but should work for now
exe.inner.addAnonymousModule(driver.name, .{
.source_file = driver.source_file,
.dependencies = dependencies.toOwnedSlice() catch @panic("OOM"),
});
const app_module = exe.inner.modules.get("app").?;
const driver_module = exe.inner.modules.get(driver.name).?;
app_module.dependencies.put(driver.name, driver_module) catch @panic("OOM");
}
pub fn install(exe: *EmbeddedExecutable) void { pub fn install(exe: *EmbeddedExecutable) void {
exe.inner.install(); exe.inner.install();
} }
@ -99,7 +73,7 @@ pub fn addEmbeddedExecutable(
source: []const u8, source: []const u8,
backing: Backing, backing: Backing,
options: BuildOptions, options: BuildOptions,
) EmbeddedExecutable { ) *EmbeddedExecutable {
const has_board = (backing == .board); const has_board = (backing == .board);
const chip = switch (backing) { const chip = switch (backing) {
.chip => |c| c, .chip => |c| c,
@ -111,13 +85,16 @@ pub fn addEmbeddedExecutable(
var hasher = std.hash.SipHash128(1, 2).init("abcdefhijklmnopq"); var hasher = std.hash.SipHash128(1, 2).init("abcdefhijklmnopq");
hasher.update(chip.name); hasher.update(chip.name);
hasher.update(chip.path); // TODO: this will likely crash for generated sources, need to
// properly hook this up to the build cache api
hasher.update(chip.source.getPath(builder));
hasher.update(chip.cpu.name); hasher.update(chip.cpu.name);
hasher.update(chip.cpu.path); hasher.update(chip.cpu.source.getPath(builder));
if (backing == .board) { if (backing == .board) {
hasher.update(backing.board.name); hasher.update(backing.board.name);
hasher.update(backing.board.path); // TODO: see above
hasher.update(backing.board.source.getPath(builder));
} }
var mac: [16]u8 = undefined; var mac: [16]u8 = undefined;
@ -155,10 +132,10 @@ pub fn addEmbeddedExecutable(
var writer = config_file.writer(); var writer = config_file.writer();
writer.print("pub const has_board = {};\n", .{has_board}) catch unreachable; writer.print("pub const has_board = {};\n", .{has_board}) catch unreachable;
if (has_board) if (has_board)
writer.print("pub const board_name = .@\"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(backing.board.name)}) catch unreachable; writer.print("pub const board_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(backing.board.name)}) catch unreachable;
writer.print("pub const chip_name = .@\"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(chip.name)}) catch unreachable; writer.print("pub const chip_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(chip.name)}) catch unreachable;
writer.print("pub const cpu_name = .@\"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(chip.cpu.name)}) catch unreachable; writer.print("pub const cpu_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(chip.cpu.name)}) catch unreachable;
writer.print("pub const end_of_stack = 0x{X:0>8};\n\n", .{first_ram.offset + first_ram.length}) catch unreachable; writer.print("pub const end_of_stack = 0x{X:0>8};\n\n", .{first_ram.offset + first_ram.length}) catch unreachable;
} }
@ -173,21 +150,28 @@ pub fn addEmbeddedExecutable(
}); });
const chip_module = builder.createModule(.{ const chip_module = builder.createModule(.{
.source_file = .{ .path = chip.path }, .source_file = chip.source,
.dependencies = &.{ .dependencies = &.{
.{ .name = "microzig", .module = microzig }, .{ .name = "microzig", .module = microzig },
}, },
}); });
const cpu_module = builder.createModule(.{ const cpu_module = builder.createModule(.{
.source_file = .{ .path = chip.cpu.path }, .source_file = chip.cpu.source,
.dependencies = &.{ .dependencies = &.{
.{ .name = "microzig", .module = microzig }, .{ .name = "microzig", .module = microzig },
}, },
}); });
var exe = EmbeddedExecutable{ const exe = builder.allocator.create(EmbeddedExecutable) catch unreachable;
.inner = builder.addExecutable(.{ .name = name, .root_source_file = .{ .path = root_path ++ "core/microzig.zig" }, .target = chip.cpu.target, .optimize = options.optimize }),
exe.* = EmbeddedExecutable{
.inner = builder.addExecutable(.{
.name = name,
.root_source_file = .{ .path = root_path ++ "microzig.zig" },
.target = chip.cpu.target,
.optimize = options.optimize,
}),
}; };
exe.inner.strip = false; // we always want debug symbols, stripping brings us no benefit on embedded exe.inner.strip = false; // we always want debug symbols, stripping brings us no benefit on embedded
@ -206,14 +190,15 @@ pub fn addEmbeddedExecutable(
exe.inner.bundle_compiler_rt = (exe.inner.target.cpu_arch.? != .avr); // don't bundle compiler_rt for AVR as it doesn't compile right now exe.inner.bundle_compiler_rt = (exe.inner.target.cpu_arch.? != .avr); // don't bundle compiler_rt for AVR as it doesn't compile right now
// these modules will be re-exported from core/microzig.zig // these modules will be re-exported from core/microzig.zig
exe.inner.addModule("microzig-config", config_module); exe.inner.addModule("config", config_module);
exe.inner.addModule("chip", chip_module); exe.inner.addModule("chip", chip_module);
exe.inner.addModule("cpu", cpu_module); exe.inner.addModule("cpu", cpu_module);
exe.inner.addModule("hal", builder.createModule(.{ exe.inner.addModule("hal", builder.createModule(.{
.source_file = if (options.hal_module_path) |hal_module_path| .source_file = if (chip.hal) |hal_module_path|
hal_module_path hal_module_path
else .{ .path = root_path ++ "core/empty.zig" }, else
.{ .path = root_path ++ "core/empty.zig" },
.dependencies = &.{ .dependencies = &.{
.{ .name = "microzig", .module = microzig }, .{ .name = "microzig", .module = microzig },
}, },
@ -222,7 +207,7 @@ pub fn addEmbeddedExecutable(
switch (backing) { switch (backing) {
.board => |board| { .board => |board| {
exe.inner.addModule("board", builder.createModule(.{ exe.inner.addModule("board", builder.createModule(.{
.source_file = .{ .path = board.path }, .source_file = board.source,
.dependencies = &.{ .dependencies = &.{
.{ .name = "microzig", .module = microzig }, .{ .name = "microzig", .module = microzig },
}, },
@ -240,24 +225,3 @@ pub fn addEmbeddedExecutable(
return exe; return exe;
} }
pub const Driver = struct {
name: []const u8,
source_file: std.build.FileSource,
dependencies: []const []const u8,
};
// Generic purpose drivers shipped with microzig
pub const drivers = struct {
pub const quadrature = Driver{
.name = "microzig.quadrature",
.source_file = .{ .path = root_path ++ "drivers/quadrature.zig" },
.dependencies = &.{"microzig"},
};
pub const button = Driver{
.name = "microzig.button",
.source_file = .{ .path = root_path ++ "drivers/button.zig" },
.dependencies = &.{"microzig"},
};
};

@ -13,43 +13,27 @@ pub const app = @import("app");
/// Contains build-time generated configuration options for microzig. /// Contains build-time generated configuration options for microzig.
/// Contains a CPU target description, chip, board and cpu information /// Contains a CPU target description, chip, board and cpu information
/// and so on. /// and so on.
pub const config = @import("microzig-config"); pub const config = @import("config");
/// Provides access to the low level features of the current microchip. /// Provides access to the low level features of the current microchip.
pub const chip = @import("chip"); pub const chip = struct {
const inner = @import("chip");
pub const types = inner.types;
pub usingnamespace @field(inner.devices, config.chip_name);
};
/// Provides access to board features or is `void` when no board is present. /// Provides access to board features or is `void` when no board is present.
pub const board = if (config.has_board) @import("board") else void; pub const board = if (config.has_board) @import("board") else void;
/// Provides access to the low level features of the CPU. /// Provides access to the low level features of the CPU.
pub const cpu = @import("cpu"); pub const cpu = @import("cpu");
pub const mmio = @import("mmio.zig");
pub const interrupt = @import("interrupt.zig");
pub const hal = @import("hal"); pub const hal = @import("hal");
/// Module that helps with interrupt handling. pub const core = @import("core.zig");
pub const interrupts = @import("interrupts.zig"); pub const drivers = @import("drivers.zig");
/// Module that provides clock related functions
pub const clock = @import("clock.zig");
pub const gpio = @import("gpio.zig");
pub const Gpio = gpio.Gpio;
pub const pin = @import("pin.zig");
pub const Pin = pin.Pin;
pub const uart = @import("uart.zig");
pub const Uart = uart.Uart;
pub const spi = @import("spi.zig");
pub const SpiBus = spi.SpiBus;
pub const i2c = @import("i2c.zig");
pub const I2CController = i2c.I2CController;
pub const debug = @import("debug.zig");
pub const mmio = @import("mmio.zig");
const options_override = if (@hasDecl(app, "std_options")) app.std_options else struct {}; const options_override = if (@hasDecl(app, "std_options")) app.std_options else struct {};
pub const std_options = struct { pub const std_options = struct {
@ -117,7 +101,7 @@ pub fn microzig_panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usiz
/// Hangs the processor and will stop doing anything useful. Use with caution! /// Hangs the processor and will stop doing anything useful. Use with caution!
pub fn hang() noreturn { pub fn hang() noreturn {
while (true) { while (true) {
interrupts.cli(); interrupt.cli();
// "this loop has side effects, don't optimize the endless loop away please. thanks!" // "this loop has side effects, don't optimize the endless loop away please. thanks!"
asm volatile ("" ::: "memory"); asm volatile ("" ::: "memory");
@ -215,7 +199,7 @@ pub const sections = struct {
extern const microzig_data_load_start: anyopaque; extern const microzig_data_load_start: anyopaque;
}; };
pub fn initializeSystemMemories() void { pub fn initialize_system_memories() void {
@setCold(true); @setCold(true);
// fill .bss with zeroes // fill .bss with zeroes

@ -1,10 +1,8 @@
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert;
pub fn mmio(addr: usize, comptime size: u8, comptime PackedT: type) *volatile MMIO(size, PackedT) { pub fn Mmio(comptime PackedT: type) type {
return @intToPtr(*volatile MMIO(size, PackedT), addr); const size = @bitSizeOf(PackedT);
}
pub fn MMIO(comptime size: u8, comptime PackedT: type) type {
if ((size % 8) != 0) if ((size % 8) != 0)
@compileError("size must be divisible by 8!"); @compileError("size must be divisible by 8!");
@ -28,13 +26,13 @@ pub fn MMIO(comptime size: u8, comptime PackedT: type) type {
} }
pub inline fn write(addr: *volatile Self, val: PackedT) void { pub inline fn write(addr: *volatile Self, val: PackedT) void {
// This is a workaround for a compiler bug related to miscompilation comptime {
// If the tmp var is not used, result location will fuck things up assert(@bitSizeOf(PackedT) == @bitSizeOf(IntT));
var tmp = @bitCast(IntT, val); }
addr.writeRaw(tmp); addr.write_raw(@bitCast(IntT, val));
} }
pub fn writeRaw(addr: *volatile Self, val: IntT) void { pub fn write_raw(addr: *volatile Self, val: IntT) void {
addr.raw = val; addr.raw = val;
} }

@ -1,5 +1,6 @@
const std = @import("std");
const Chip = @import("Chip.zig"); const Chip = @import("Chip.zig");
name: []const u8, name: []const u8,
path: []const u8, source: std.build.FileSource,
chip: Chip, chip: Chip,

@ -1,7 +1,44 @@
const std = @import("std");
const FileSource = std.build.FileSource;
const MemoryRegion = @import("MemoryRegion.zig"); const MemoryRegion = @import("MemoryRegion.zig");
const Cpu = @import("Cpu.zig"); const Cpu = @import("Cpu.zig");
const Chip = @This();
name: []const u8, name: []const u8,
path: []const u8, source: FileSource,
cpu: Cpu, cpu: Cpu,
hal: ?FileSource = null,
json_register_schema: ?FileSource = null,
memory_regions: []const MemoryRegion, memory_regions: []const MemoryRegion,
pub fn from_standard_paths(comptime root_dir: []const u8, args: struct {
name: []const u8,
cpu: Cpu,
memory_regions: []const MemoryRegion,
}) Chip {
return Chip{
.name = args.name,
.cpu = args.cpu,
.memory_regions = args.memory_regions,
.source = .{
.path = std.fmt.comptimePrint("{s}/chips/{s}.zig", .{
root_dir,
args.name,
}),
},
.hal = .{
.path = std.fmt.comptimePrint("{s}/chips/{s}.zig", .{
root_dir,
args.name,
}),
},
.json_register_schema = .{
.path = std.fmt.comptimePrint("{s}/chips/{s}.json", .{
root_dir,
args.name,
}),
},
};
}

@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
name: []const u8, name: []const u8,
path: []const u8, source: std.build.FileSource,
target: std.zig.CrossTarget, target: std.zig.CrossTarget,

@ -16,9 +16,9 @@ pub fn create(builder: *Builder, chip: Chip) !*LinkerscriptStep {
var hasher = std.hash.SipHash128(1, 2).init("abcdefhijklmnopq"); var hasher = std.hash.SipHash128(1, 2).init("abcdefhijklmnopq");
hasher.update(chip.name); hasher.update(chip.name);
hasher.update(chip.path); hasher.update(chip.source.getPath(builder));
hasher.update(chip.cpu.name); hasher.update(chip.cpu.name);
hasher.update(chip.cpu.path); hasher.update(chip.cpu.source.getPath(builder));
var mac: [16]u8 = undefined; var mac: [16]u8 = undefined;
hasher.final(&mac); hasher.final(&mac);

@ -1,57 +0,0 @@
const std = @import("std");
const chips = @import("chips.zig");
const Board = @import("Board.zig");
fn root() []const u8 {
return std.fs.path.dirname(@src().file) orelse unreachable;
}
const root_path = root() ++ "/";
pub const arduino_nano = Board{
.name = "Arduino Nano",
.path = root_path ++ "boards/arduino-nano/arduino-nano.zig",
.chip = chips.atmega328p,
};
pub const arduino_uno = Board{
.name = "Arduino Uno",
.path = root_path ++ "boards/arduino-uno/arduino-uno.zig",
.chip = chips.atmega328p,
};
pub const mbed_lpc1768 = Board{
.name = "mbed LPC1768",
.path = root_path ++ "boards/mbed-lpc1768/mbed-lpc1768.zig",
.chip = chips.lpc1768,
};
pub const stm32f3discovery = Board{
.name = "STM32F3DISCOVERY",
.path = root_path ++ "boards/stm32f3discovery/stm32f3discovery.zig",
.chip = chips.stm32f303vc,
};
pub const stm32f4discovery = Board{
.name = "STM32F4DISCOVERY",
.path = root_path ++ "boards/stm32f4discovery/stm32f4discovery.zig",
.chip = chips.stm32f407vg,
};
pub const stm3240geval = Board{
.name = "STM3240GEVAL",
.path = root_path ++ "boards/stm3240geval/stm3240geval.zig",
.chip = chips.stm32f407vg,
};
pub const stm32f429idiscovery = Board{
.name = "STM32F429IDISCOVERY",
.path = root_path ++ "boards/stm32f429idiscovery/stm32f429idiscovery.zig",
.chip = chips.stm32f429zit6u,
};
pub const longan_nano = Board{
.name = "Longan Nano",
.path = root_path ++ "boards/longan-nano/longan-nano.zig",
.chip = chips.gd32vf103xb,
};

@ -1,33 +0,0 @@
pub const chip = @import("chip");
pub const clock_frequencies = .{
.cpu = 16_000_000,
};
pub const pin_map = .{
// Port A
.D0 = "PD0",
.D1 = "PD1",
.D2 = "PD2",
.D3 = "PD3",
.D4 = "PD4",
.D5 = "PD5",
.D6 = "PD6",
.D7 = "PD7",
// Port B
.D8 = "PB0",
.D9 = "PB1",
.D10 = "PB2",
.D11 = "PB3",
.D12 = "PB4",
.D13 = "PB5",
// Port C (Analog)
.A0 = "PC0",
.A1 = "PC1",
.A2 = "PC2",
.A3 = "PC3",
.A4 = "PC4",
.A5 = "PC5",
.A6 = "ADC6",
.A7 = "ADC7",
};

@ -1,32 +0,0 @@
pub const chip = @import("chip");
pub const clock_frequencies = .{
.cpu = 16_000_000,
};
pub const pin_map = .{
// Port D
.D0 = "PD0",
.D1 = "PD1",
.D2 = "PD2",
.D3 = "PD3",
.D4 = "PD4",
.D5 = "PD5",
.D6 = "PD6",
.D7 = "PD7",
// Port B
.D8 = "PB0",
.D9 = "PB1",
.D10 = "PB2",
.D11 = "PB3",
.D12 = "PB4",
// LED_BUILTIN
.D13 = "PB5",
// Port C (Analog)
.A0 = "PC0",
.A1 = "PC1",
.A2 = "PC2",
.A3 = "PC3",
.A4 = "PC4",
.A5 = "PC5",
};

@ -1,112 +0,0 @@
pub const chip = @import("chip");
pub const micro = @import("microzig");
pub const cpu_frequency = 8_000_000; // 8 MHz
pub const pin_map = .{
// Port A
.PA0 = "PA0",
.PA1 = "PA1",
.PA2 = "PA2",
.PA3 = "PA3",
.PA4 = "PA4",
.PA5 = "PA5",
.PA6 = "PA6",
.PA7 = "PA7",
.PA8 = "PA8",
.PA9 = "PA9",
.PA10 = "PA10",
.PA11 = "PA11",
.PA12 = "PA12",
.PA13 = "PA13",
// Port B
.PB0 = "PB0",
.PB1 = "PB1",
.PB2 = "PB2",
.PB3 = "PB3",
.PB4 = "PB4",
.PB5 = "PB5",
.PB6 = "PB6",
.PB7 = "PB7",
.PB8 = "PB8",
.PB9 = "PB9",
.PB10 = "PB10",
.PB11 = "PB11",
.PB12 = "PB12",
.PB13 = "PB13",
.PB14 = "PB14",
.PB15 = "PB15",
// Port C
.PC0 = "PC0",
.PC1 = "PC1",
.PC2 = "PC2",
.PC3 = "PC3",
.PC4 = "PC4",
.PC5 = "PC5",
.PC6 = "PC6",
.PC7 = "PC7",
.PC8 = "PC8",
.PC9 = "PC9",
.PC10 = "PC10",
.PC11 = "PC11",
.PC12 = "PC12",
.PC13 = "PC13",
.PC14 = "PC14",
.PC15 = "PC15",
// Port D
.PD0 = "PD0",
.PD1 = "PD1",
.PD2 = "PD2",
.PD3 = "PD3",
.PD4 = "PD4",
.PD5 = "PD5",
.PD6 = "PD6",
.PD7 = "PD7",
.PD8 = "PD8",
.PD9 = "PD9",
.PD10 = "PD10",
.PD11 = "PD11",
.PD12 = "PD12",
.PD13 = "PD13",
.PD14 = "PD14",
.PD15 = "PD15",
// Port E
.PE0 = "PE0",
.PE1 = "PE1",
.PE2 = "PE2",
.PE3 = "PE3",
.PE4 = "PE4",
.PE5 = "PE5",
.PE6 = "PE6",
.PE7 = "PE7",
.PE8 = "PE8",
.PE9 = "PE9",
.PE10 = "PE10",
.PE11 = "PE11",
.PE12 = "PE12",
.PE13 = "PE13",
.PE14 = "PE14",
.PE15 = "PE15",
// Colors LED
// LCD_COLOR_WHITE 0xFFFF
// LCD_COLOR_BLACK 0x0000
// LCD_COLOR_GREY 0xF7DE
// LCD_COLOR_BLUE 0x001F
// LCD_COLOR_BLUE2 0x051F
// LCD_COLOR_RED 0xF800
// LCD_COLOR_MAGENTA 0xF81F
// LCD_COLOR_GREEN 0x07E0
// LCD_COLOR_CYAN 0x7FFF
// LCD_COLOR_YELLOW 0xFFE0
};
pub fn debugWrite(string: []const u8) void {
_ = string;
// TODO: implement
}

@ -1,84 +0,0 @@
pub const chip = @import("chip");
pub const micro = @import("microzig");
pub const clock_frequencies = .{
.cpu = 100_000_000, // 100 Mhz
};
pub fn debugWrite(string: []const u8) void {
const clk_pin = micro.Pin("DIP5");
const dat_pin = micro.Pin("DIP6");
const clk = micro.Gpio(clk_pin, .{ .mode = .output, .initial_state = .low });
const dat = micro.Gpio(dat_pin, .{ .mode = .output, .initial_state = .low });
clk.init();
dat.init();
micro.debug.busySleep(1_000);
for (string) |c| {
comptime var i: usize = 128;
inline while (i > 0) : (i = i >> 1) {
if ((c & i) != 0) {
dat.write(.high);
} else {
dat.write(.low);
}
clk.write(.high);
micro.debug.busySleep(1_000);
clk.write(.low);
micro.debug.busySleep(1_000);
}
}
dat.write(.low);
clk.write(.low);
}
pub const pin_map = .{
// Onboard-LEDs
.@"LED-1" = "P1.18",
.@"LED-2" = "P1.20",
.@"LED-3" = "P1.21",
.@"LED-4" = "P1.23",
.@"LED_LINK" = "P1.25",
.@"LED_SPEED" = "P1.26",
// Ethernet
.@"TD+" = "P1.0",
.@"TD-" = "P1.1",
.@"RD+" = "P1.9",
.@"RD-" = "P1.10",
// USB
.@"D+" = "P0.29",
.@"D-" = "P0.30",
// GPIO pins
.@"DIP5" = "P0.9",
.@"DIP6" = "P0.8",
.@"DIP7" = "P0.7",
.@"DIP8" = "P0.6",
.@"DIP9" = "P0.0",
.@"DIP10" = "P0.1",
.@"DIP11" = "P0.18",
.@"DIP12" = "P0.17",
.@"DIP13" = "P0.15",
.@"DIP14" = "P0.16",
.@"DIP15" = "P0.23",
.@"DIP16" = "P0.24",
.@"DIP17" = "P0.25",
.@"DIP18" = "P0.26",
.@"DIP19" = "P1.30",
.@"DIP20" = "P1.31",
.@"DIP21" = "P2.5",
.@"DIP22" = "P2.4",
.@"DIP23" = "P2.3",
.@"DIP24" = "P2.2",
.@"DIP25" = "P2.1",
.@"DIP26" = "P2.0",
.@"DIP27" = "P0.11",
.@"DIP28" = "P0.10",
.@"DIP29" = "P0.5",
.@"DIP30" = "P0.4",
};

@ -1,13 +0,0 @@
pub const chip = @import("chip");
pub const micro = @import("microzig");
pub const pin_map = .{
// LD1 green
.@"LD1" = "PG6",
// LD2 orange
.@"LD2" = "PG8",
// LD3 red
.@"LD3" = "PI9",
// LD4 blue
.@"LD4" = "PC7",
};

@ -1,38 +0,0 @@
pub const chip = @import("chip");
pub const micro = @import("microzig");
pub const cpu_frequency = 8_000_000;
pub const pin_map = .{
// circle of LEDs, connected to GPIOE bits 8..15
// NW blue
.@"LD4" = "PE8",
// N red
.@"LD3" = "PE9",
// NE orange
.@"LD5" = "PE10",
// E green
.@"LD7" = "PE11",
// SE blue
.@"LD9" = "PE12",
// S red
.@"LD10" = "PE13",
// SW orange
.@"LD8" = "PE14",
// W green
.@"LD6" = "PE15",
};
pub fn debugWrite(string: []const u8) void {
const uart1 = micro.Uart(1, .{}).getOrInit(.{
.baud_rate = 9600,
.data_bits = .eight,
.parity = null,
.stop_bits = .one,
}) catch unreachable;
const writer = uart1.writer();
_ = writer.write(string) catch unreachable;
uart1.internal.txflush();
}

@ -1,15 +0,0 @@
pub const chip = @import("chip");
pub const micro = @import("microzig");
pub const cpu_frequency = 16_000_000;
pub const pin_map = .{
// LEDs, connected to GPIOG bits 13, 14
// green
.@"LD3" = "PG13",
// red
.@"LD4" = "PG14",
// User button
.@"B1" = "PA0",
};

@ -1,17 +0,0 @@
pub const chip = @import("chip");
pub const micro = @import("microzig");
pub const pin_map = .{
// LED cross, connected to GPIOD bits 12..15
// N orange
.@"LD3" = "PD13",
// E red
.@"LD5" = "PD14",
// S blue
.@"LD6" = "PD15",
// W green
.@"LD4" = "PD12",
// User button
.@"B2" = "PA0",
};

@ -1,116 +0,0 @@
const std = @import("std");
const cpus = @import("cpus.zig");
const Chip = @import("Chip.zig");
const MemoryRegion = @import("MemoryRegion.zig");
fn root() []const u8 {
return std.fs.path.dirname(@src().file) orelse unreachable;
}
const root_path = root() ++ "/";
pub const atmega328p = Chip{
.name = "ATmega328p",
.path = root_path ++ "chips/atmega328p/atmega328p.zig",
.cpu = cpus.avr5,
.memory_regions = &.{
MemoryRegion{ .offset = 0x000000, .length = 32 * 1024, .kind = .flash },
MemoryRegion{ .offset = 0x800100, .length = 2048, .kind = .ram },
},
};
pub const lpc1768 = Chip{
.name = "NXP LPC1768",
.path = root_path ++ "chips/lpc1768/lpc1768.zig",
.cpu = cpus.cortex_m3,
.memory_regions = &.{
MemoryRegion{ .offset = 0x00000000, .length = 512 * 1024, .kind = .flash },
MemoryRegion{ .offset = 0x10000000, .length = 32 * 1024, .kind = .ram },
MemoryRegion{ .offset = 0x2007C000, .length = 32 * 1024, .kind = .ram },
},
};
pub const gd32vf103xb = Chip{
.name = "GD32VF103xB",
.path = root_path ++ "chips/gd32vf103/gd32vf103.zig",
.cpu = cpus.riscv32_imac,
.memory_regions = &.{
MemoryRegion{ .offset = 0x08000000, .length = 128 * 1024, .kind = .flash },
MemoryRegion{ .offset = 0x20000000, .length = 32 * 1024, .kind = .ram },
},
};
pub const gd32vf103x8 = Chip{
.name = "GD32VF103x8",
.path = root_path ++ "chips/gd32vf103/gd32vf103.zig",
.cpu = cpus.riscv32_imac,
.memory_regions = &.{
MemoryRegion{ .offset = 0x08000000, .length = 64 * 1024, .kind = .flash },
MemoryRegion{ .offset = 0x20000000, .length = 20 * 1024, .kind = .ram },
},
};
pub const stm32f103x8 = Chip{
.name = "STM32F103x8",
.path = root_path ++ "chips/stm32f103/stm32f103.zig",
.cpu = cpus.cortex_m3,
.memory_regions = &.{
MemoryRegion{ .offset = 0x08000000, .length = 64 * 1024, .kind = .flash },
MemoryRegion{ .offset = 0x20000000, .length = 20 * 1024, .kind = .ram },
},
};
pub const stm32f303vc = Chip{
.name = "STM32F303VC",
.path = root_path ++ "chips/stm32f303/stm32f303.zig",
.cpu = cpus.cortex_m4,
.memory_regions = &.{
MemoryRegion{ .offset = 0x08000000, .length = 256 * 1024, .kind = .flash },
MemoryRegion{ .offset = 0x20000000, .length = 40 * 1024, .kind = .ram },
},
};
pub const stm32f407vg = Chip{
.name = "STM32F407VG",
.path = root_path ++ "chips/stm32f407/stm32f407.zig",
.cpu = cpus.cortex_m4,
.memory_regions = &.{
MemoryRegion{ .offset = 0x08000000, .length = 1024 * 1024, .kind = .flash },
MemoryRegion{ .offset = 0x20000000, .length = 128 * 1024, .kind = .ram },
// CCM RAM
MemoryRegion{ .offset = 0x10000000, .length = 64 * 1024, .kind = .ram },
},
};
pub const stm32f429zit6u = Chip{
.name = "STM32F429ZIT6U",
.path = root_path ++ "chips/stm32f429/stm32f429.zig",
.cpu = cpus.cortex_m4,
.memory_regions = &.{
MemoryRegion{ .offset = 0x08000000, .length = 2048 * 1024, .kind = .flash },
MemoryRegion{ .offset = 0x20000000, .length = 192 * 1024, .kind = .ram },
// CCM RAM
MemoryRegion{ .offset = 0x10000000, .length = 64 * 1024, .kind = .ram },
},
};
pub const nrf52832 = Chip{
.name = "nRF52832",
.path = root_path ++ "chips/nrf52/nrf52.zig",
.cpu = cpus.cortex_m4,
.memory_regions = &.{
MemoryRegion{ .offset = 0x00000000, .length = 0x80000, .kind = .flash },
MemoryRegion{ .offset = 0x20000000, .length = 0x10000, .kind = .ram },
},
};
pub const atsame51j20a = Chip{
.name = "ATSAME51J20A",
.path = root_path ++ "chips/atsame51j20a/atsame51j20a.zig",
.cpu = cpus.cortex_m4,
.memory_regions = &.{
// SAM D5x/E5x Family Data Sheet page 53
MemoryRegion{ .offset = 0x00000000, .length = 1024 * 1024, .kind = .flash },
MemoryRegion{ .offset = 0x20000000, .length = 256 * 1024, .kind = .ram },
},
};

@ -1,191 +0,0 @@
const std = @import("std");
const micro = @import("microzig");
pub usingnamespace @import("registers.zig");
const regz = @import("registers.zig").registers;
pub const cpu = micro.cpu;
const Port = enum(u8) {
B = 1,
C = 2,
D = 3,
};
pub const clock = struct {
pub const Domain = enum {
cpu,
};
};
pub fn parsePin(comptime spec: []const u8) type {
const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme.";
if (spec.len != 3)
@compileError(invalid_format_msg);
if (spec[0] != 'P')
@compileError(invalid_format_msg);
return struct {
pub const port: Port = std.meta.stringToEnum(Port, spec[1..2]) orelse @compileError(invalid_format_msg);
pub const pin: u3 = std.fmt.parseInt(u3, spec[2..3], 10) catch @compileError(invalid_format_msg);
};
}
pub const gpio = struct {
fn regs(comptime desc: type) type {
return struct {
// io address
const pin_addr: u5 = 3 * @enumToInt(desc.port) + 0x00;
const dir_addr: u5 = 3 * @enumToInt(desc.port) + 0x01;
const port_addr: u5 = 3 * @enumToInt(desc.port) + 0x02;
// ram mapping
const pin = @intToPtr(*volatile u8, 0x20 + @as(usize, pin_addr));
const dir = @intToPtr(*volatile u8, 0x20 + @as(usize, dir_addr));
const port = @intToPtr(*volatile u8, 0x20 + @as(usize, port_addr));
};
}
pub fn setOutput(comptime pin: type) void {
cpu.sbi(regs(pin).dir_addr, pin.pin);
}
pub fn setInput(comptime pin: type) void {
cpu.cbi(regs(pin).dir_addr, pin.pin);
}
pub fn read(comptime pin: type) micro.gpio.State {
return if ((regs(pin).pin.* & (1 << pin.pin)) != 0)
.high
else
.low;
}
pub fn write(comptime pin: type, state: micro.gpio.State) void {
if (state == .high) {
cpu.sbi(regs(pin).port_addr, pin.pin);
} else {
cpu.cbi(regs(pin).port_addr, pin.pin);
}
}
pub fn toggle(comptime pin: type) void {
cpu.sbi(regs(pin).pin_addr, pin.pin);
}
};
pub const uart = struct {
pub const DataBits = enum {
five,
six,
seven,
eight,
nine,
};
pub const StopBits = enum {
one,
two,
};
pub const Parity = enum {
odd,
even,
};
};
pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type {
if (index != 0) @compileError("Atmega328p only has a single uart!");
if (pins.tx != null or pins.rx != null)
@compileError("Atmega328p has fixed pins for uart!");
return struct {
const Self = @This();
fn computeDivider(baud_rate: u32) !u12 {
const pclk = micro.clock.get().cpu;
const divider = ((pclk + (8 * baud_rate)) / (16 * baud_rate)) - 1;
return std.math.cast(u12, divider) orelse return error.UnsupportedBaudRate;
}
fn computeBaudRate(divider: u12) u32 {
return micro.clock.get().cpu / (16 * @as(u32, divider) + 1);
}
pub fn init(config: micro.uart.Config) !Self {
const ucsz: u3 = switch (config.data_bits) {
.five => 0b000,
.six => 0b001,
.seven => 0b010,
.eight => 0b011,
.nine => return error.UnsupportedWordSize, // 0b111
};
const upm: u2 = if (config.parity) |parity| switch (parity) {
.even => @as(u2, 0b10), // even
.odd => @as(u2, 0b11), // odd
} else 0b00; // parity disabled
const usbs: u1 = switch (config.stop_bits) {
.one => 0b0,
.two => 0b1,
};
const umsel: u2 = 0b00; // Asynchronous USART
// baud is computed like this:
// f(osc)
// BAUD = ----------------
// 16 * (UBRRn + 1)
const ubrr_val = try computeDivider(config.baud_rate);
regz.USART0.UCSR0A.modify(.{
.MPCM0 = 0,
.U2X0 = 0,
});
regz.USART0.UCSR0B.write(.{
.TXB80 = 0, // we don't care about these btw
.RXB80 = 0, // we don't care about these btw
.UCSZ02 = @truncate(u1, (ucsz & 0x04) >> 2),
.TXEN0 = 1,
.RXEN0 = 1,
.UDRIE0 = 0, // no interrupts
.TXCIE0 = 0, // no interrupts
.RXCIE0 = 0, // no interrupts
});
regz.USART0.UCSR0C.write(.{
.UCPOL0 = 0, // async mode
.UCSZ0 = @truncate(u2, (ucsz & 0x03) >> 0),
.USBS0 = usbs,
.UPM0 = upm,
.UMSEL0 = umsel,
});
regz.USART0.UBRR0.modify(ubrr_val);
return Self{};
}
pub fn canWrite(self: Self) bool {
_ = self;
return (regz.USART0.UCSR0A.read().UDRE0 == 1);
}
pub fn tx(self: Self, ch: u8) void {
while (!self.canWrite()) {} // Wait for Previous transmission
regz.USART0.UDR0.* = ch; // Load the data to be transmitted
}
pub fn canRead(self: Self) bool {
_ = self;
return (regz.USART0.UCSR0A.read().RXC0 == 1);
}
pub fn rx(self: Self) u8 {
while (!self.canRead()) {} // Wait till the data is received
return regz.USART0.UDR0.*; // Read received data
}
};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,333 +0,0 @@
pub const std = @import("std");
pub const cpu = @import("cpu");
pub const micro = @import("microzig");
pub const chip = @import("registers.zig");
const regs = chip.registers;
pub usingnamespace chip;
pub const chip_name = "ATSAME51J20A";
pub const clock_frequencies = .{
// On any reset the synchronous clocks start to their initial state:
// * DFLL48M is enabled and configured to run at 48 MHz
// * Generic Generator 0 uses DFLL48M as source and generates GCLK_MAIN
// * CPU and BUS clocks are undivided
// i.e. GENCTRL0 = 0x00000106
.cpu = 48_000_000,
};
/// Get access to the pin specified by `spec`.
///
/// - `spec`: P{port}{pin}
/// - `port`: A, B
/// - `pin`: 0..31
pub fn parsePin(comptime spec: []const u8) type {
const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme.";
if (spec[0] != 'P')
@compileError(invalid_format_msg);
if (spec[1] < 'A' or spec[1] > 'B') // J = 64 Pins; 2 Ports
@compileError("Unknown port '" ++ spec[1..2] ++ "'. Supported ports: A, B.");
return struct {
// Try to parse the given pin number as u5, i.e. a value in '0'..'31'.
const pin_number: u5 = @import("std").fmt.parseInt(u5, spec[2..], 10) catch @compileError(invalid_format_msg);
const pin_mask: u32 = (1 << pin_number);
// Port is either 'A' or 'B'.
const port_number: usize = if (spec[1] == 'A') 0 else 1;
const gpio_port = @field(regs.PORT, "GROUP");
};
}
pub const gpio = struct {
// See SAM D5x/E5x Family Data Sheet page 807.
/// Configure the given pin as output with input disabled.
pub fn setOutput(comptime pin: type) void {
// To use pin Pxy as an output, write bit y of the DIR register to '1'. This
// can also be done by writing bit y int the DIRSET register to '1' - this
// will avoid disturbing the configuration of other pins (datasheet p. 803).
pin.gpio_port[pin.port_number].DIRSET = pin.pin_mask;
// Disable input for the given pin.
pin.gpio_port[pin.port_number].PINCFG[pin.pin_number].modify(.{ .INEN = 0 });
}
/// Configure the given pin as input.
pub fn setInput(comptime pin: type) void {
// To use pin Pxy as an input, bit y in the DIR register must be written to '0'.
// This can also be done by writing bit y in the DIRCLR register to '1'.
pin.gpio_port[pin.port_number].DIRCLR = pin.pin_mask;
// The input value can be read from bit y in register IN as soon as the INEN bit in the pin
// configuratation register is written to '1' to enable the pins input buffer.
pin.gpio_port[pin.port_number].PINCFG[pin.pin_number].modify(.{ .INEN = 1 });
}
/// Configure the given pin as input with pull-up.
pub fn setInputPullUp(comptime pin: type) void {
setInput(pin);
pin.gpio_port[pin.port_number].PINCFG[pin.pin_number].modify(.{ .PULLEN = 1 });
// When enabling input with pull-up, bit y must be set to '1'.
write(pin, .high);
}
/// Configure the given pin as input with pull-down.
pub fn setInputPullDown(comptime pin: type) void {
setInput(pin);
pin.gpio_port[pin.port_number].PINCFG[pin.pin_number].modify(.{ .PULLEN = 1 });
// When enabling input with pull-down, bit y must be set to '0'.
write(pin, .low);
}
/// Check if the given pin is configured as output.
/// Returns true on success, false otherwise.
pub fn isOutput(comptime pin: type) bool {
return (pin.gpio_port[pin.port_number].DIR & pin.pin_mask) != 0;
}
pub fn read(comptime pin: type) micro.gpio.State {
return if ((pin.gpio_port[pin.port_number].IN & pin.pin_mask) != 0)
micro.gpio.State.high
else
micro.gpio.State.low;
}
pub fn write(comptime pin: type, state: micro.gpio.State) void {
switch (state) {
.high => pin.gpio_port[pin.port_number].OUTSET = pin.pin_mask,
.low => pin.gpio_port[pin.port_number].OUTCLR = pin.pin_mask,
}
}
pub fn toggle(comptime pin: type) void {
pin.gpio_port[pin.port_number].OUTTGL = pin.pin_mask;
}
};
// #############################################################################
// Nonvolotile Memory Controller - NVMCTRL
// #############################################################################
pub fn nvmctrlInit() void {
regs.NVMCTRL.CTRLA.modify(.{
.RWS = 5, // Number of wait states for a read operation
.AUTOWS = 1, // Enable wait states
});
}
// #############################################################################
// Generic Clock Controller - GCLK
// #############################################################################
pub fn gclk2Init() void {
regs.GCLK.GENCTRL[2].modify(.{
.DIV = 1,
.SRC = 6, // DFLL48M generator clock source
.GENEN = 1, // Enable generator
});
while (regs.GCLK.SYNCBUSY.read().GENCTRL & 2 != 0) {
// wait for sync
}
}
// #############################################################################
// UART
// #############################################################################
/// Calculate the BAUD register value based on the the expected output frequency
/// `fbaud` and the baud reference frequency `fref` (see data sheet p. 830).
pub fn asyncArithmeticBaudToRegister(fbaud: u32, fref: u32) u16 {
const fb = @intToFloat(f64, fbaud);
const fr = @intToFloat(f64, fref);
const res = 65536.0 * (1.0 - 16.0 * (fb / fr));
return @floatToInt(u16, res);
}
/// Unique definitions for the chip, used by the microzig.uart.Config struct.
pub const uart = struct {
/// USART character size (p. 859).
pub const DataBits = enum(u3) {
eight = 0,
nine = 1,
five = 5,
six = 6,
seven = 7,
};
/// USART stop bits (p. 859).
pub const StopBits = enum(u1) {
one = 0,
tow = 1,
};
/// USART parity mode (p. 858).
pub const Parity = enum(u1) {
even = 0,
odd = 1,
};
};
/// Instantiate a new USART interface.
///
/// * `index` - SERCOM{index} should be used for UART
/// * `pins` - Not supported. Please use `.{ .tx = null, .rx = null }`
pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type {
if (pins.tx != null or pins.rx != null)
@compileError("SAMD/E5x doesn't support custom pins");
return struct {
const UARTn = switch (index) {
5 => regs.SERCOM5.USART_INT,
else => @compileError("Currently only SERCOM5:USART_INT supported."),
};
const Self = @This();
pub fn init(config: micro.uart.Config) !Self {
switch (index) {
5 => {
gclk2Init();
regs.GCLK.PCHCTRL[35].modify(.{
.GEN = 2, // Generic clock generator 2 (see p. 156)
.CHEN = 1, // Enable peripheral channel
});
// When the APB clock is not provided to a module, its
// registers cannot be read or written.
regs.MCLK.APBDMASK.modify(.{ .SERCOM5_ = 1 });
// Enable the peripheral multiplexer selection.
regs.PORT.GROUP[1].PINCFG[16].modify(.{ .PMUXEN = 1 });
regs.PORT.GROUP[1].PINCFG[17].modify(.{ .PMUXEN = 1 });
// Multiplex PB16 and PB17 to peripheral function C, i.e.
// SERCOM5 (see page 32 and 823).
regs.PORT.GROUP[1].PMUX[8].modify(.{ .PMUXE = 2, .PMUXO = 2 });
},
else => unreachable,
}
// Some of the registers are enable-protected, meaning they can only
// be written when the USART is disabled.
UARTn.CTRLA.modify(.{ .ENABLE = 0 });
// Wait until synchronized.
while (UARTn.SYNCBUSY.read().ENABLE != 0) {}
// Select USART with internal clock (0x1).
UARTn.CTRLA.modify(.{
.MODE = 1, // Select USART with internal clock (0x01)
.CMODE = 0, // Select asynchronous communication mode (0x00)
// Pin selection (data sheet p. 854)
.RXPO = 1, // SERCOM PAD[1] is used for data reception
.TXPO = 0, // SERCOM PAD[0] is used for data transmition
.DORD = 1, // Configure data order (MSB = 0, LSB = 1)
.IBON = 1, // Immediate buffer overflow notification
.SAMPR = 0, // 16x over-sampling using arithmetic baud rate generation
});
// Configure parity mode.
if (config.parity != null) {
// Enable parity mode.
UARTn.CTRLA.modify(.{ .FORM = 1 }); // USART frame with parity
UARTn.CTRLB.modify(.{ .PMODE = @enumToInt(config.parity.?) });
} else {
// Disable parity mode.
UARTn.CTRLA.modify(.{ .FORM = 0 }); // USART frame
}
// Write the Baud register (internal clock mode) to generate the
// desired baud rate.
UARTn.BAUD.* = asyncArithmeticBaudToRegister(config.baud_rate, 48_000_000); //@intCast(u16, config.baud_rate);
UARTn.CTRLB.modify(.{
.CHSIZE = @enumToInt(config.data_bits), // Configure the character size filed.
.SBMODE = @enumToInt(config.stop_bits), // Configure the number of stop bits.
.RXEN = 1, // Enable the receiver
.TXEN = 1, // Enable the transmitter
});
//UARTn.INTENSET.modify(.{ .DRE = 1, .TXC = 1, .RXC = 1, .CTSIC = 1 });
while (UARTn.SYNCBUSY.raw != 0) {}
// Enable the peripheral.
UARTn.CTRLA.modify(.{ .ENABLE = 1 });
while (UARTn.SYNCBUSY.raw != 0) {}
return Self{};
}
pub fn canWrite(self: Self) bool {
_ = self;
// The DRE flag ist set when DATA is empty and ready to be written.
// The DATA register should only be written to when INTFLAG.DRE is set.
return UARTn.INTFLAG.read().DRE == 1;
}
pub fn tx(self: Self, ch: u8) void {
while (!self.canWrite()) {} // Wait for Previous transmission
UARTn.DATA.* = ch; // Load the data to be transmitted
}
pub fn canRead(self: Self) bool {
_ = self;
// The RXC flag ist set when there are unread data in DATA.
return UARTn.INTFLAG.read().RXC == 1;
}
pub fn rx(self: Self) u8 {
while (!self.canRead()) {} // Wait till the data is received
return @intCast(u8, UARTn.DATA.*); // Read received data
}
};
}
// #############################################################################
// Crypto
// #############################################################################
pub fn enableTrng() void {
// Enable the TRNG bus clock.
regs.MCLK.APBCMASK.modify(.{ .TRNG_ = 1 });
}
pub const crypto = struct {
pub const random = struct {
/// Fill the given slice with random data.
pub fn getBlock(buffer: []u8) void {
var rand: u32 = undefined;
var i: usize = 0;
while (i < buffer.len) : (i += 1) {
if (i % 4 == 0) {
// Get a fresh 32 bit integer every 4th iteration.
rand = getWord();
}
// The shift value is always between 0 and 24, i.e. int cast will always succeed.
buffer[i] = @intCast(u8, (rand >> @intCast(u5, (8 * (i % 4)))) & 0xff);
}
}
/// Get a real 32 bit random integer.
///
/// In most cases you'll want to use `getBlock` instead.
pub fn getWord() u32 {
regs.TRNG.CTRLA.modify(.{ .ENABLE = 1 });
while (regs.TRNG.INTFLAG.read().DATARDY == 0) {
// a new random number is generated every
// 84 CLK_TRNG_APB clock cycles (p. 1421).
}
regs.TRNG.CTRLA.modify(.{ .ENABLE = 0 });
return regs.TRNG.DATA.*;
}
/// Get a real 8 bit random integer.
///
/// In most cases you'll want to use `getBlock` instead.
pub fn getByte() u8 {
return @intCast(u8, getWord() & 0xFF);
}
};
};

File diff suppressed because it is too large Load Diff

@ -1,114 +0,0 @@
pub const cpu = @import("cpu");
pub const micro = @import("microzig");
pub const chip = @import("registers.zig");
const regs = chip.registers;
pub usingnamespace chip;
pub const clock_frequencies = .{
.cpu = 8_000_000, // 8 MHz
};
pub fn parsePin(comptime spec: []const u8) type {
const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme.";
if (spec[0] != 'P')
@compileError(invalid_format_msg);
if (spec[1] < 'A' or spec[1] > 'E')
@compileError(invalid_format_msg);
return struct {
const pin_number: comptime_int = @import("std").fmt.parseInt(u2, spec[2..], 10) catch @compileError(invalid_format_msg);
// 'A'...'E'
const gpio_port_name = spec[1..2];
const gpio_port = @field(regs, "GPIO" ++ gpio_port_name);
const suffix = @import("std").fmt.comptimePrint("{d}", .{pin_number});
};
}
fn setRegField(reg: anytype, comptime field_name: anytype, value: anytype) void {
var temp = reg.read();
@field(temp, field_name) = value;
reg.write(temp);
}
pub const gpio = struct {
pub fn setOutput(comptime pin: type) void {
_ = pin;
// TODO: check if pin is already configured as output
}
pub fn setInput(comptime pin: type) void {
_ = pin;
// TODO: check if pin is already configured as input
}
pub fn read(comptime pin: type) micro.gpio.State {
_ = pin;
// TODO: check if pin is configured as input
return .low;
}
pub fn write(comptime pin: type, state: micro.gpio.State) void {
_ = pin;
_ = state;
// TODO: check if pin is configured as output
}
};
pub const uart = struct {
pub const DataBits = enum(u2) {
five = 0,
six = 1,
seven = 2,
eight = 3,
};
pub const StopBits = enum(u1) {
one = 0,
two = 1,
};
pub const Parity = enum(u2) {
odd = 0,
even = 1,
mark = 2,
space = 3,
};
};
pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type {
if (pins.tx != null or pins.rx != null)
@compileError("TODO: custom pins are not currently supported");
return struct {
const UARTn = switch (index) {
0 => regs.UART3,
1 => regs.UART4,
else => @compileError("GD32VF103 has 2 UARTs available."),
};
const Self = @This();
pub fn init(config: micro.uart.Config) !Self {
_ = config;
return Self{};
}
pub fn canWrite(self: Self) bool {
_ = self;
return false;
}
pub fn tx(self: Self, ch: u8) void {
_ = ch;
while (!self.canWrite()) {} // Wait for Previous transmission
}
pub fn canRead(self: Self) bool {
_ = self;
return false;
}
pub fn rx(self: Self) u8 {
while (!self.canRead()) {} // Wait till the data is received
return 1; // Read received data
}
};
}

File diff suppressed because it is too large Load Diff

@ -1,205 +0,0 @@
const std = @import("std");
const micro = @import("microzig");
const chip = @import("registers.zig");
const regs = chip.registers;
pub usingnamespace chip;
pub const clock = struct {
pub const Domain = enum {
cpu,
};
};
pub const clock_frequencies = .{
.cpu = 100_000_000, // 100 Mhz
};
pub const PinTarget = enum(u2) {
func00 = 0b00,
func01 = 0b01,
func10 = 0b10,
func11 = 0b11,
};
pub fn parsePin(comptime spec: []const u8) type {
const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}.{Pin}\" scheme.";
if (spec[0] != 'P')
@compileError(invalid_format_msg);
const index = std.mem.indexOfScalar(u8, spec, '.') orelse @compileError(invalid_format_msg);
const _port: comptime_int = std.fmt.parseInt(u3, spec[1..index], 10) catch @compileError(invalid_format_msg);
const _pin: comptime_int = std.fmt.parseInt(u5, spec[index + 1 ..], 10) catch @compileError(invalid_format_msg);
const sel_reg_name = std.fmt.comptimePrint("PINSEL{d}", .{(2 * _port + _pin / 16)});
const _regs = struct {
const name_suffix = std.fmt.comptimePrint("{d}", .{_port});
const pinsel_reg = @field(regs.PINCONNECT, sel_reg_name);
const pinsel_field = std.fmt.comptimePrint("P{d}_{d}", .{ _port, _pin });
const dir = @field(regs.GPIO, "DIR" ++ name_suffix);
const pin = @field(regs.GPIO, "PIN" ++ name_suffix);
const set = @field(regs.GPIO, "SET" ++ name_suffix);
const clr = @field(regs.GPIO, "CLR" ++ name_suffix);
const mask = @field(regs.GPIO, "MASK" ++ name_suffix);
};
return struct {
pub const port: u3 = _port;
pub const pin: u5 = _pin;
pub const regs = _regs;
const gpio_mask: u32 = (1 << pin);
pub const Targets = PinTarget;
};
}
pub fn routePin(comptime pin: type, function: PinTarget) void {
var val = pin.regs.pinsel_reg.read();
@field(val, pin.regs.pinsel_field) = @enumToInt(function);
pin.regs.pinsel_reg.write(val);
}
pub const gpio = struct {
pub fn setOutput(comptime pin: type) void {
pin.regs.dir.raw |= pin.gpio_mask;
}
pub fn setInput(comptime pin: type) void {
pin.regs.dir.raw &= ~pin.gpio_mask;
}
pub fn read(comptime pin: type) micro.gpio.State {
return if ((pin.regs.pin.raw & pin.gpio_mask) != 0)
micro.gpio.State.high
else
micro.gpio.State.low;
}
pub fn write(comptime pin: type, state: micro.gpio.State) void {
if (state == .high) {
pin.regs.set.raw = pin.gpio_mask;
} else {
pin.regs.clr.raw = pin.gpio_mask;
}
}
};
pub const uart = struct {
pub const DataBits = enum(u2) {
five = 0,
six = 1,
seven = 2,
eight = 3,
};
pub const StopBits = enum(u1) {
one = 0,
two = 1,
};
pub const Parity = enum(u2) {
odd = 0,
even = 1,
mark = 2,
space = 3,
};
pub const CClkDiv = enum(u2) {
four = 0,
one = 1,
two = 2,
eight = 3,
};
};
pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type {
if (pins.tx != null or pins.rx != null)
@compileError("TODO: custom pins are not currently supported");
return struct {
const UARTn = switch (index) {
0 => regs.UART0,
1 => regs.UART1,
2 => regs.UART2,
3 => regs.UART3,
else => @compileError("LPC1768 has 4 UARTs available."),
};
const Self = @This();
pub fn init(config: micro.uart.Config) !Self {
micro.debug.write("0");
switch (index) {
0 => {
regs.SYSCON.PCONP.modify(.{ .PCUART0 = 1 });
regs.SYSCON.PCLKSEL0.modify(.{ .PCLK_UART0 = @enumToInt(uart.CClkDiv.four) });
},
1 => {
regs.SYSCON.PCONP.modify(.{ .PCUART1 = 1 });
regs.SYSCON.PCLKSEL0.modify(.{ .PCLK_UART1 = @enumToInt(uart.CClkDiv.four) });
},
2 => {
regs.SYSCON.PCONP.modify(.{ .PCUART2 = 1 });
regs.SYSCON.PCLKSEL1.modify(.{ .PCLK_UART2 = @enumToInt(uart.CClkDiv.four) });
},
3 => {
regs.SYSCON.PCONP.modify(.{ .PCUART3 = 1 });
regs.SYSCON.PCLKSEL1.modify(.{ .PCLK_UART3 = @enumToInt(uart.CClkDiv.four) });
},
else => unreachable,
}
micro.debug.write("1");
UARTn.LCR.modify(.{
// 8N1
.WLS = @enumToInt(config.data_bits),
.SBS = @enumToInt(config.stop_bits),
.PE = if (config.parity != null) @as(u1, 1) else @as(u1, 0),
.PS = if (config.parity) |p| @enumToInt(p) else @enumToInt(uart.Parity.odd),
.BC = 0,
.DLAB = 1,
});
micro.debug.write("2");
// TODO: UARTN_FIFOS_ARE_DISA is not available in all uarts
//UARTn.FCR.modify(.{ .FIFOEN = .UARTN_FIFOS_ARE_DISA });
micro.debug.writer().print("clock: {} baud: {} ", .{
micro.clock.get().cpu,
config.baud_rate,
}) catch {};
const pclk = micro.clock.get().cpu / 4;
const divider = (pclk / (16 * config.baud_rate));
const regval = std.math.cast(u16, divider) orelse return error.UnsupportedBaudRate;
UARTn.DLL.modify(.{ .DLLSB = @truncate(u8, regval >> 0x00) });
UARTn.DLM.modify(.{ .DLMSB = @truncate(u8, regval >> 0x08) });
UARTn.LCR.modify(.{ .DLAB = 0 });
return Self{};
}
pub fn canWrite(self: Self) bool {
_ = self;
return (UARTn.LSR.read().THRE == 1);
}
pub fn tx(self: Self, ch: u8) void {
while (!self.canWrite()) {} // Wait for Previous transmission
UARTn.THR.raw = ch; // Load the data to be transmitted
}
pub fn canRead(self: Self) bool {
_ = self;
return (UARTn.LSR.read().RDR == 1);
}
pub fn rx(self: Self) u8 {
while (!self.canRead()) {} // Wait till the data is received
return UARTn.RBR.read().RBR; // Read received data
}
};
}

File diff suppressed because it is too large Load Diff

@ -1,3 +0,0 @@
pub const cpu = @import("cpu");
pub const registers = @import("registers.zig");
pub const VectorTable = registers.VectorTable;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,2 +0,0 @@
pub const cpu = @import("cpu");
pub usingnamespace @import("registers.zig");

File diff suppressed because it is too large Load Diff

@ -1,598 +0,0 @@
//! For now we keep all clock settings on the chip defaults.
//! This code currently assumes the STM32F303xB / STM32F303xC clock configuration.
//! TODO: Do something useful for other STM32f30x chips.
//!
//! Specifically, TIM6 is running on an 8 MHz clock,
//! HSI = 8 MHz is the SYSCLK after reset
//! default AHB prescaler = /1 (= values 0..7):
//!
//! ```
//! regs.RCC.CFGR.modify(.{ .HPRE = 0 });
//! ```
//!
//! so also HCLK = 8 MHz.
//! And with the default APB1 prescaler = /2:
//!
//! ```
//! regs.RCC.CFGR.modify(.{ .PPRE1 = 4 });
//! ```
//!
//! results in PCLK1,
//! and the resulting implicit factor *2 for TIM2/3/4/6/7
//! makes TIM6 run at 8MHz/2*2 = 8 MHz.
//!
//! The above default configuration makes U(S)ART2..5
//! (which use PCLK1 without that implicit *2 factor)
//! run at 4 MHz by default.
//!
//! USART1 uses PCLK2, which uses the APB2 prescaler on HCLK,
//! default APB2 prescaler = /1:
//!
//! ```
//! regs.RCC.CFGR.modify(.{ .PPRE2 = 0 });
//! ```
//!
//! and therefore USART1 runs on 8 MHz.
const std = @import("std");
const runtime_safety = std.debug.runtime_safety;
const micro = @import("microzig");
const chip = @import("registers.zig");
const regs = chip.registers;
pub usingnamespace chip;
pub const cpu = @import("cpu");
pub const clock = struct {
pub const Domain = enum {
cpu,
ahb,
apb1,
apb2,
};
};
// Default clock frequencies after reset, see top comment for calculation
pub const clock_frequencies = .{
.cpu = 8_000_000,
.ahb = 8_000_000,
.apb1 = 8_000_000,
.apb2 = 8_000_000,
};
pub fn parsePin(comptime spec: []const u8) type {
const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme.";
if (spec[0] != 'P')
@compileError(invalid_format_msg);
if (spec[1] < 'A' or spec[1] > 'H')
@compileError(invalid_format_msg);
const pin_number: comptime_int = std.fmt.parseInt(u4, spec[2..], 10) catch @compileError(invalid_format_msg);
return struct {
/// 'A'...'H'
const gpio_port_name = spec[1..2];
const gpio_port = @field(regs, "GPIO" ++ gpio_port_name);
const suffix = std.fmt.comptimePrint("{d}", .{pin_number});
};
}
fn setRegField(reg: anytype, comptime field_name: anytype, value: anytype) void {
var temp = reg.read();
@field(temp, field_name) = value;
reg.write(temp);
}
pub const gpio = struct {
pub fn setOutput(comptime pin: type) void {
setRegField(regs.RCC.AHBENR, "IOP" ++ pin.gpio_port_name ++ "EN", 1);
setRegField(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b01);
}
pub fn setInput(comptime pin: type) void {
setRegField(regs.RCC.AHBENR, "IOP" ++ pin.gpio_port_name ++ "EN", 1);
setRegField(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b00);
}
pub fn read(comptime pin: type) micro.gpio.State {
const idr_reg = pin.gpio_port.IDR;
const reg_value = @field(idr_reg.read(), "IDR" ++ pin.suffix); // TODO extract to getRegField()?
return @intToEnum(micro.gpio.State, reg_value);
}
pub fn write(comptime pin: type, state: micro.gpio.State) void {
switch (state) {
.low => setRegField(pin.gpio_port.BRR, "BR" ++ pin.suffix, 1),
.high => setRegField(pin.gpio_port.BSRR, "BS" ++ pin.suffix, 1),
}
}
};
pub const uart = struct {
pub const DataBits = enum(u4) {
seven = 7,
eight = 8,
};
/// uses the values of USART_CR2.STOP
pub const StopBits = enum(u2) {
one = 0b00,
half = 0b01,
two = 0b10,
one_and_half = 0b11,
};
/// uses the values of USART_CR1.PS
pub const Parity = enum(u1) {
even = 0,
odd = 1,
};
};
pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type {
if (!(index == 1)) @compileError("TODO: only USART1 is currently supported");
if (pins.tx != null or pins.rx != null)
@compileError("TODO: custom pins are not currently supported");
return struct {
parity_read_mask: u8,
const Self = @This();
pub fn init(config: micro.uart.Config) !Self {
// The following must all be written when the USART is disabled (UE=0).
if (regs.USART1.CR1.read().UE == 1)
@panic("Trying to initialize USART1 while it is already enabled");
// LATER: Alternatively, set UE=0 at this point? Then wait for something?
// Or add a destroy() function which disables the USART?
// enable the USART1 clock
regs.RCC.APB2ENR.modify(.{ .USART1EN = 1 });
// enable GPIOC clock
regs.RCC.AHBENR.modify(.{ .IOPCEN = 1 });
// set PC4+PC5 to alternate function 7, USART1_TX + USART1_RX
regs.GPIOC.MODER.modify(.{ .MODER4 = 0b10, .MODER5 = 0b10 });
regs.GPIOC.AFRL.modify(.{ .AFRL4 = 7, .AFRL5 = 7 });
// clear USART1 configuration to its default
regs.USART1.CR1.raw = 0;
regs.USART1.CR2.raw = 0;
regs.USART1.CR3.raw = 0;
// set word length
// Per the reference manual, M[1:0] means
// - 00: 8 bits (7 data + 1 parity, or 8 data), probably the chip default
// - 01: 9 bits (8 data + 1 parity)
// - 10: 7 bits (7 data)
// So M1==1 means "7-bit mode" (in which
// "the Smartcard mode, LIN master mode and Auto baud rate [...] are not supported");
// and M0==1 means 'the 9th bit (not the 8th bit) is the parity bit'.
const m1: u1 = if (config.data_bits == .seven and config.parity == null) 1 else 0;
const m0: u1 = if (config.data_bits == .eight and config.parity != null) 1 else 0;
// Note that .padding0 = bit 28 = .M1 (.svd file bug?), and .M == .M0.
regs.USART1.CR1.modify(.{ .padding0 = m1, .M = m0 });
// set parity
if (config.parity) |parity| {
regs.USART1.CR1.modify(.{ .PCE = 1, .PS = @enumToInt(parity) });
} else regs.USART1.CR1.modify(.{ .PCE = 0 }); // no parity, probably the chip default
// set number of stop bits
regs.USART1.CR2.modify(.{ .STOP = @enumToInt(config.stop_bits) });
// set the baud rate
// TODO: Do not use the _board_'s frequency, but the _U(S)ARTx_ frequency
// from the chip, which can be affected by how the board configures the chip.
// In our case, these are accidentally the same at chip reset,
// if the board doesn't configure e.g. an HSE external crystal.
// TODO: Do some checks to see if the baud rate is too high (or perhaps too low)
// TODO: Do a rounding div, instead of a truncating div?
const usartdiv = @intCast(u16, @divTrunc(micro.clock.get().apb1, config.baud_rate));
regs.USART1.BRR.raw = usartdiv;
// Above, ignore the BRR struct fields DIV_Mantissa and DIV_Fraction,
// those seem to be for another chipset; .svd file bug?
// TODO: We assume the default OVER8=0 configuration above.
// enable USART1, and its transmitter and receiver
regs.USART1.CR1.modify(.{ .UE = 1 });
regs.USART1.CR1.modify(.{ .TE = 1 });
regs.USART1.CR1.modify(.{ .RE = 1 });
// For code simplicity, at cost of one or more register reads,
// we read back the actual configuration from the registers,
// instead of using the `config` values.
return readFromRegisters();
}
pub fn getOrInit(config: micro.uart.Config) !Self {
if (regs.USART1.CR1.read().UE == 1) {
// UART1 already enabled, don't reinitialize and disturb things;
// instead read and use the actual configuration.
return readFromRegisters();
} else return init(config);
}
fn readFromRegisters() Self {
const cr1 = regs.USART1.CR1.read();
// As documented in `init()`, M0==1 means 'the 9th bit (not the 8th bit) is the parity bit'.
// So we always mask away the 9th bit, and if parity is enabled and it is in the 8th bit,
// then we also mask away the 8th bit.
return Self{ .parity_read_mask = if (cr1.PCE == 1 and cr1.M == 0) 0x7F else 0xFF };
}
pub fn canWrite(self: Self) bool {
_ = self;
return switch (regs.USART1.ISR.read().TXE) {
1 => true,
0 => false,
};
}
pub fn tx(self: Self, ch: u8) void {
while (!self.canWrite()) {} // Wait for Previous transmission
regs.USART1.TDR.modify(ch);
}
pub fn txflush(_: Self) void {
while (regs.USART1.ISR.read().TC == 0) {}
}
pub fn canRead(self: Self) bool {
_ = self;
return switch (regs.USART1.ISR.read().RXNE) {
1 => true,
0 => false,
};
}
pub fn rx(self: Self) u8 {
while (!self.canRead()) {} // Wait till the data is received
const data_with_parity_bit: u9 = regs.USART1.RDR.read().RDR;
return @intCast(u8, data_with_parity_bit & self.parity_read_mask);
}
};
}
const enable_stm32f303_debug = false;
fn debugPrint(comptime format: []const u8, args: anytype) void {
if (enable_stm32f303_debug) {
micro.debug.writer().print(format, args) catch {};
}
}
/// This implementation does not use AUTOEND=1
pub fn I2CController(comptime index: usize, comptime pins: micro.i2c.Pins) type {
if (!(index == 1)) @compileError("TODO: only I2C1 is currently supported");
if (pins.scl != null or pins.sda != null)
@compileError("TODO: custom pins are not currently supported");
return struct {
const Self = @This();
pub fn init(config: micro.i2c.Config) !Self {
// CONFIGURE I2C1
// connected to APB1, MCU pins PB6 + PB7 = I2C1_SCL + I2C1_SDA,
// if GPIO port B is configured for alternate function 4 for these PB pins.
// 1. Enable the I2C CLOCK and GPIO CLOCK
regs.RCC.APB1ENR.modify(.{ .I2C1EN = 1 });
regs.RCC.AHBENR.modify(.{ .IOPBEN = 1 });
debugPrint("I2C1 configuration step 1 complete\r\n", .{});
// 2. Configure the I2C PINs for ALternate Functions
// a) Select Alternate Function in MODER Register
regs.GPIOB.MODER.modify(.{ .MODER6 = 0b10, .MODER7 = 0b10 });
// b) Select Open Drain Output
regs.GPIOB.OTYPER.modify(.{ .OT6 = 1, .OT7 = 1 });
// c) Select High SPEED for the PINs
regs.GPIOB.OSPEEDR.modify(.{ .OSPEEDR6 = 0b11, .OSPEEDR7 = 0b11 });
// d) Select Pull-up for both the Pins
regs.GPIOB.PUPDR.modify(.{ .PUPDR6 = 0b01, .PUPDR7 = 0b01 });
// e) Configure the Alternate Function in AFR Register
regs.GPIOB.AFRL.modify(.{ .AFRL6 = 4, .AFRL7 = 4 });
debugPrint("I2C1 configuration step 2 complete\r\n", .{});
// 3. Reset the I2C
regs.I2C1.CR1.modify(.{ .PE = 0 });
while (regs.I2C1.CR1.read().PE == 1) {}
// DO NOT regs.RCC.APB1RSTR.modify(.{ .I2C1RST = 1 });
debugPrint("I2C1 configuration step 3 complete\r\n", .{});
// 4-6. Configure I2C1 timing, based on 8 MHz I2C clock, run at 100 kHz
// (Not using https://controllerstech.com/stm32-i2c-configuration-using-registers/
// but copying an example from the reference manual, RM0316 section 28.4.9.)
if (config.target_speed != 100_000) @panic("TODO: Support speeds other than 100 kHz");
regs.I2C1.TIMINGR.modify(.{
.PRESC = 1,
.SCLL = 0x13,
.SCLH = 0xF,
.SDADEL = 0x2,
.SCLDEL = 0x4,
});
debugPrint("I2C1 configuration steps 4-6 complete\r\n", .{});
// 7. Program the I2C_CR1 register to enable the peripheral
regs.I2C1.CR1.modify(.{ .PE = 1 });
debugPrint("I2C1 configuration step 7 complete\r\n", .{});
return Self{};
}
pub const WriteState = struct {
address: u7,
buffer: [255]u8 = undefined,
buffer_size: u8 = 0,
pub fn start(address: u7) !WriteState {
return WriteState{ .address = address };
}
pub fn writeAll(self: *WriteState, bytes: []const u8) !void {
debugPrint("I2C1 writeAll() with {d} byte(s); buffer={any}\r\n", .{ bytes.len, self.buffer[0..self.buffer_size] });
std.debug.assert(self.buffer_size < 255);
for (bytes) |b| {
self.buffer[self.buffer_size] = b;
self.buffer_size += 1;
if (self.buffer_size == 255) {
try self.sendBuffer(1);
}
}
}
fn sendBuffer(self: *WriteState, reload: u1) !void {
debugPrint("I2C1 sendBuffer() with {d} byte(s); RELOAD={d}; buffer={any}\r\n", .{ self.buffer_size, reload, self.buffer[0..self.buffer_size] });
if (self.buffer_size == 0) @panic("write of 0 bytes not supported");
std.debug.assert(reload == 0 or self.buffer_size == 255); // see TODOs below
// As master, initiate write from address, 7 bit address
regs.I2C1.CR2.modify(.{
.ADD10 = 0,
.SADD1 = self.address,
.RD_WRN = 0, // write
.NBYTES = self.buffer_size,
.RELOAD = reload,
});
if (reload == 0) {
regs.I2C1.CR2.modify(.{ .START = 1 });
} else {
// TODO: The RELOAD=1 path is untested but doesn't seem to work yet,
// even though we make sure that we set NBYTES=255 per the docs.
}
for (self.buffer[0..self.buffer_size]) |b| {
// wait for empty transmit buffer
while (regs.I2C1.ISR.read().TXE == 0) {
debugPrint("I2C1 waiting for ready to send (TXE=0)\r\n", .{});
}
debugPrint("I2C1 ready to send (TXE=1)\r\n", .{});
// Write data byte
regs.I2C1.TXDR.modify(.{ .TXDATA = b });
}
self.buffer_size = 0;
debugPrint("I2C1 data written\r\n", .{});
if (reload == 1) {
// TODO: The RELOAD=1 path is untested but doesn't seem to work yet,
// the following loop never seems to finish.
while (regs.I2C1.ISR.read().TCR == 0) {
debugPrint("I2C1 waiting transmit complete (TCR=0)\r\n", .{});
}
debugPrint("I2C1 transmit complete (TCR=1)\r\n", .{});
} else {
while (regs.I2C1.ISR.read().TC == 0) {
debugPrint("I2C1 waiting for transmit complete (TC=0)\r\n", .{});
}
debugPrint("I2C1 transmit complete (TC=1)\r\n", .{});
}
}
pub fn stop(self: *WriteState) !void {
try self.sendBuffer(0);
// Communication STOP
debugPrint("I2C1 STOPping\r\n", .{});
regs.I2C1.CR2.modify(.{ .STOP = 1 });
while (regs.I2C1.ISR.read().BUSY == 1) {}
debugPrint("I2C1 STOPped\r\n", .{});
}
pub fn restartRead(self: *WriteState) !ReadState {
try self.sendBuffer(0);
return ReadState{ .address = self.address };
}
pub fn restartWrite(self: *WriteState) !WriteState {
try self.sendBuffer(0);
return WriteState{ .address = self.address };
}
};
pub const ReadState = struct {
address: u7,
read_allowed: if (runtime_safety) bool else void = if (runtime_safety) true else {},
pub fn start(address: u7) !ReadState {
return ReadState{ .address = address };
}
/// Fails with ReadError if incorrect number of bytes is received.
pub fn readNoEof(self: *ReadState, buffer: []u8) !void {
if (runtime_safety and !self.read_allowed) @panic("second read call not allowed");
std.debug.assert(buffer.len < 256); // TODO: use RELOAD to read more data
// As master, initiate read from accelerometer, 7 bit address
regs.I2C1.CR2.modify(.{
.ADD10 = 0,
.SADD1 = self.address,
.RD_WRN = 1, // read
.NBYTES = @intCast(u8, buffer.len),
});
debugPrint("I2C1 prepared for read of {} byte(s) from 0b{b:0<7}\r\n", .{ buffer.len, self.address });
// Communication START
regs.I2C1.CR2.modify(.{ .START = 1 });
debugPrint("I2C1 RXNE={}\r\n", .{regs.I2C1.ISR.read().RXNE});
debugPrint("I2C1 STARTed\r\n", .{});
debugPrint("I2C1 RXNE={}\r\n", .{regs.I2C1.ISR.read().RXNE});
if (runtime_safety) self.read_allowed = false;
for (buffer) |_, i| {
// Wait for data to be received
while (regs.I2C1.ISR.read().RXNE == 0) {
debugPrint("I2C1 waiting for data (RXNE=0)\r\n", .{});
}
debugPrint("I2C1 data ready (RXNE=1)\r\n", .{});
// Read first data byte
buffer[i] = regs.I2C1.RXDR.read().RXDATA;
}
debugPrint("I2C1 data: {any}\r\n", .{buffer});
}
pub fn stop(_: *ReadState) !void {
// Communication STOP
regs.I2C1.CR2.modify(.{ .STOP = 1 });
while (regs.I2C1.ISR.read().BUSY == 1) {}
debugPrint("I2C1 STOPped\r\n", .{});
}
pub fn restartRead(self: *ReadState) !ReadState {
debugPrint("I2C1 no action for restart\r\n", .{});
return ReadState{ .address = self.address };
}
pub fn restartWrite(self: *ReadState) !WriteState {
debugPrint("I2C1 no action for restart\r\n", .{});
return WriteState{ .address = self.address };
}
};
};
}
/// An STM32F303 SPI bus
pub fn SpiBus(comptime index: usize) type {
if (!(index == 1)) @compileError("TODO: only SPI1 is currently supported");
return struct {
const Self = @This();
/// Initialize and enable the bus.
pub fn init(config: micro.spi.BusConfig) !Self {
_ = config; // unused for now
// CONFIGURE SPI1
// connected to APB2, MCU pins PA5 + PA7 + PA6 = SPC + SDI + SDO,
// if GPIO port A is configured for alternate function 5 for these PA pins.
// Enable the GPIO CLOCK
regs.RCC.AHBENR.modify(.{ .IOPAEN = 1 });
// Configure the I2C PINs for ALternate Functions
// - Select Alternate Function in MODER Register
regs.GPIOA.MODER.modify(.{ .MODER5 = 0b10, .MODER6 = 0b10, .MODER7 = 0b10 });
// - Select High SPEED for the PINs
regs.GPIOA.OSPEEDR.modify(.{ .OSPEEDR5 = 0b11, .OSPEEDR6 = 0b11, .OSPEEDR7 = 0b11 });
// - Configure the Alternate Function in AFR Register
regs.GPIOA.AFRL.modify(.{ .AFRL5 = 5, .AFRL6 = 5, .AFRL7 = 5 });
// Enable the SPI1 CLOCK
regs.RCC.APB2ENR.modify(.{ .SPI1EN = 1 });
regs.SPI1.CR1.modify(.{
.MSTR = 1,
.SSM = 1,
.SSI = 1,
.RXONLY = 0,
.SPE = 1,
});
// the following configuration is assumed in `transceiveByte()`
regs.SPI1.CR2.raw = 0;
regs.SPI1.CR2.modify(.{
.DS = 0b0111, // 8-bit data frames, seems default via '0b0000 is interpreted as 0b0111'
.FRXTH = 1, // RXNE event after 1 byte received
});
return Self{};
}
/// Switch this SPI bus to the given device.
pub fn switchToDevice(_: Self, comptime cs_pin: type, config: micro.spi.DeviceConfig) void {
_ = config; // for future use
regs.SPI1.CR1.modify(.{
.CPOL = 1, // TODO: make configurable
.CPHA = 1, // TODO: make configurable
.BR = 0b111, // 1/256 the of PCLK TODO: make configurable
.LSBFIRST = 0, // MSB first TODO: make configurable
});
gpio.setOutput(cs_pin);
}
/// Begin a transfer to the given device. (Assumes `switchToDevice()` was called.)
pub fn beginTransfer(_: Self, comptime cs_pin: type, config: micro.spi.DeviceConfig) void {
_ = config; // for future use
gpio.write(cs_pin, .low); // select the given device, TODO: support inverse CS devices
debugPrint("enabled SPI1\r\n", .{});
}
/// The basic operation in the current simplistic implementation:
/// send+receive a single byte.
/// Writing `null` writes an arbitrary byte (`undefined`), and
/// reading into `null` ignores the value received.
pub fn transceiveByte(_: Self, optional_write_byte: ?u8, optional_read_pointer: ?*u8) !void {
// SPIx_DR's least significant byte is `@bitCast([dr_byte_size]u8, ...)[0]`
const dr_byte_size = @sizeOf(@TypeOf(regs.SPI1.DR.raw));
// wait unril ready for write
while (regs.SPI1.SR.read().TXE == 0) {
debugPrint("SPI1 TXE == 0\r\n", .{});
}
debugPrint("SPI1 TXE == 1\r\n", .{});
// write
const write_byte = if (optional_write_byte) |b| b else undefined; // dummy value
@bitCast([dr_byte_size]u8, regs.SPI1.DR.*)[0] = write_byte;
debugPrint("Sent: {X:2}.\r\n", .{write_byte});
// wait until read processed
while (regs.SPI1.SR.read().RXNE == 0) {
debugPrint("SPI1 RXNE == 0\r\n", .{});
}
debugPrint("SPI1 RXNE == 1\r\n", .{});
// read
var data_read = regs.SPI1.DR.raw;
_ = regs.SPI1.SR.read(); // clear overrun flag
const dr_lsb = @bitCast([dr_byte_size]u8, data_read)[0];
debugPrint("Received: {X:2} (DR = {X:8}).\r\n", .{ dr_lsb, data_read });
if (optional_read_pointer) |read_pointer| read_pointer.* = dr_lsb;
}
/// Write all given bytes on the bus, not reading anything back.
pub fn writeAll(self: Self, bytes: []const u8) !void {
for (bytes) |b| {
try self.transceiveByte(b, null);
}
}
/// Read bytes to fill the given buffer exactly, writing arbitrary bytes (`undefined`).
pub fn readInto(self: Self, buffer: []u8) !void {
for (buffer) |_, i| {
try self.transceiveByte(null, &buffer[i]);
}
}
pub fn endTransfer(_: Self, comptime cs_pin: type, config: micro.spi.DeviceConfig) void {
_ = config; // for future use
// no delay should be needed here, since we know SPIx_SR's TXE is 1
debugPrint("(disabling SPI1)\r\n", .{});
gpio.write(cs_pin, .high); // deselect the given device, TODO: support inverse CS devices
// HACK: wait long enough to make any device end an ongoing transfer
var i: u8 = 255; // with the default clock, this seems to delay ~185 microseconds
while (i > 0) : (i -= 1) {
asm volatile ("nop");
}
}
};
}

File diff suppressed because it is too large Load Diff

@ -1,625 +0,0 @@
//! For now we keep all clock settings on the chip defaults.
//! This code currently assumes the STM32F405xx / STM32F407xx clock configuration.
//! TODO: Do something useful for other STM32F40x chips.
//!
//! Specifically, TIM6 is running on a 16 MHz clock,
//! HSI = 16 MHz is the SYSCLK after reset
//! default AHB prescaler = /1 (= values 0..7):
//!
//! ```
//! regs.RCC.CFGR.modify(.{ .HPRE = 0 });
//! ```
//!
//! so also HCLK = 16 MHz.
//! And with the default APB1 prescaler = /1:
//!
//! ```
//! regs.RCC.CFGR.modify(.{ .PPRE1 = 0 });
//! ```
//!
//! results in PCLK1 = 16 MHz.
//!
//! The above default configuration makes U(S)ART2..5
//! receive a 16 MHz clock by default.
//!
//! USART1 and USART6 use PCLK2, which uses the APB2 prescaler on HCLK,
//! default APB2 prescaler = /1:
//!
//! ```
//! regs.RCC.CFGR.modify(.{ .PPRE2 = 0 });
//! ```
//!
//! and therefore USART1 and USART6 receive a 16 MHz clock.
//!
const std = @import("std");
const micro = @import("microzig");
const chip = @import("registers.zig");
const regs = chip.registers;
pub usingnamespace chip;
pub const clock = struct {
pub const Domain = enum {
cpu,
ahb,
apb1,
apb2,
};
};
// Default clock frequencies after reset, see top comment for calculation
pub const clock_frequencies = .{
.cpu = 16_000_000,
.ahb = 16_000_000,
.apb1 = 16_000_000,
.apb2 = 16_000_000,
};
pub fn parsePin(comptime spec: []const u8) type {
const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme.";
if (spec[0] != 'P')
@compileError(invalid_format_msg);
if (spec[1] < 'A' or spec[1] > 'I')
@compileError(invalid_format_msg);
return struct {
const pin_number: comptime_int = std.fmt.parseInt(u4, spec[2..], 10) catch @compileError(invalid_format_msg);
/// 'A'...'I'
const gpio_port_name = spec[1..2];
const gpio_port = @field(regs, "GPIO" ++ gpio_port_name);
const suffix = std.fmt.comptimePrint("{d}", .{pin_number});
};
}
fn setRegField(reg: anytype, comptime field_name: anytype, value: anytype) void {
var temp = reg.read();
@field(temp, field_name) = value;
reg.write(temp);
}
pub const gpio = struct {
pub const AlternateFunction = enum(u4) {
af0,
af1,
af2,
af3,
af4,
af5,
af6,
af7,
af8,
af9,
af10,
af11,
af12,
af13,
af14,
af15,
};
pub fn setOutput(comptime pin: type) void {
setRegField(regs.RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1);
setRegField(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b01);
}
pub fn setInput(comptime pin: type) void {
setRegField(regs.RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1);
setRegField(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b00);
}
pub fn setAlternateFunction(comptime pin: type, af: AlternateFunction) void {
setRegField(regs.RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1);
setRegField(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b10);
if (pin.pin_number < 8) {
setRegField(@field(pin.gpio_port, "AFRL"), "AFRL" ++ pin.suffix, @enumToInt(af));
} else {
setRegField(@field(pin.gpio_port, "AFRH"), "AFRH" ++ pin.suffix, @enumToInt(af));
}
}
pub fn read(comptime pin: type) micro.gpio.State {
const idr_reg = pin.gpio_port.IDR;
const reg_value = @field(idr_reg.read(), "IDR" ++ pin.suffix); // TODO extract to getRegField()?
return @intToEnum(micro.gpio.State, reg_value);
}
pub fn write(comptime pin: type, state: micro.gpio.State) void {
switch (state) {
.low => setRegField(pin.gpio_port.BSRR, "BR" ++ pin.suffix, 1),
.high => setRegField(pin.gpio_port.BSRR, "BS" ++ pin.suffix, 1),
}
}
};
pub const uart = struct {
pub const DataBits = enum {
seven,
eight,
nine,
};
/// uses the values of USART_CR2.STOP
pub const StopBits = enum(u2) {
one = 0b00,
half = 0b01,
two = 0b10,
one_and_half = 0b11,
};
/// uses the values of USART_CR1.PS
pub const Parity = enum(u1) {
even = 0,
odd = 1,
};
const PinDirection = std.meta.FieldEnum(micro.uart.Pins);
/// Checks if a pin is valid for a given uart index and direction
pub fn isValidPin(comptime pin: type, comptime index: usize, comptime direction: PinDirection) bool {
const pin_name = pin.name;
return switch (direction) {
.tx => switch (index) {
1 => std.mem.eql(u8, pin_name, "PA9") or std.mem.eql(u8, pin_name, "PB6"),
2 => std.mem.eql(u8, pin_name, "PA2") or std.mem.eql(u8, pin_name, "PD5"),
3 => std.mem.eql(u8, pin_name, "PB10") or std.mem.eql(u8, pin_name, "PC10") or std.mem.eql(u8, pin_name, "PD8"),
4 => std.mem.eql(u8, pin_name, "PA0") or std.mem.eql(u8, pin_name, "PC10"),
5 => std.mem.eql(u8, pin_name, "PC12"),
6 => std.mem.eql(u8, pin_name, "PC6") or std.mem.eql(u8, pin_name, "PG14"),
else => unreachable,
},
// Valid RX pins for the UARTs
.rx => switch (index) {
1 => std.mem.eql(u8, pin_name, "PA10") or std.mem.eql(u8, pin_name, "PB7"),
2 => std.mem.eql(u8, pin_name, "PA3") or std.mem.eql(u8, pin_name, "PD6"),
3 => std.mem.eql(u8, pin_name, "PB11") or std.mem.eql(u8, pin_name, "PC11") or std.mem.eql(u8, pin_name, "PD9"),
4 => std.mem.eql(u8, pin_name, "PA1") or std.mem.eql(u8, pin_name, "PC11"),
5 => std.mem.eql(u8, pin_name, "PD2"),
6 => std.mem.eql(u8, pin_name, "PC7") or std.mem.eql(u8, pin_name, "PG9"),
else => unreachable,
},
};
}
};
pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type {
if (index < 1 or index > 6) @compileError("Valid USART index are 1..6");
const usart_name = std.fmt.comptimePrint("USART{d}", .{index});
const tx_pin =
if (pins.tx) |tx|
if (uart.isValidPin(tx, index, .tx))
tx
else
@compileError(std.fmt.comptimePrint("Tx pin {s} is not valid for UART{}", .{ tx.name, index }))
else switch (index) {
// Provide default tx pins if no pin is specified
1 => micro.Pin("PA9"),
2 => micro.Pin("PA2"),
3 => micro.Pin("PB10"),
4 => micro.Pin("PA0"),
5 => micro.Pin("PC12"),
6 => micro.Pin("PC6"),
else => unreachable,
};
const rx_pin =
if (pins.rx) |rx|
if (uart.isValidPin(rx, index, .rx))
rx
else
@compileError(std.fmt.comptimePrint("Rx pin {s} is not valid for UART{}", .{ rx.name, index }))
else switch (index) {
// Provide default rx pins if no pin is specified
1 => micro.Pin("PA10"),
2 => micro.Pin("PA3"),
3 => micro.Pin("PB11"),
4 => micro.Pin("PA1"),
5 => micro.Pin("PD2"),
6 => micro.Pin("PC7"),
else => unreachable,
};
// USART1..3 are AF7, USART 4..6 are AF8
const alternate_function = if (index <= 3) .af7 else .af8;
const tx_gpio = micro.Gpio(tx_pin, .{
.mode = .alternate_function,
.alternate_function = alternate_function,
});
const rx_gpio = micro.Gpio(rx_pin, .{
.mode = .alternate_function,
.alternate_function = alternate_function,
});
return struct {
parity_read_mask: u8,
const Self = @This();
pub fn init(config: micro.uart.Config) !Self {
// The following must all be written when the USART is disabled (UE=0).
if (@field(regs, usart_name).CR1.read().UE == 1)
@panic("Trying to initialize " ++ usart_name ++ " while it is already enabled");
// LATER: Alternatively, set UE=0 at this point? Then wait for something?
// Or add a destroy() function which disables the USART?
// enable the USART clock
const clk_enable_reg = switch (index) {
1, 6 => regs.RCC.APB2ENR,
2...5 => regs.RCC.APB1ENR,
else => unreachable,
};
setRegField(clk_enable_reg, usart_name ++ "EN", 1);
tx_gpio.init();
rx_gpio.init();
// clear USART configuration to its default
@field(regs, usart_name).CR1.raw = 0;
@field(regs, usart_name).CR2.raw = 0;
@field(regs, usart_name).CR3.raw = 0;
// Return error for unsupported combinations
if (config.data_bits == .nine and config.parity != null) {
// TODO: should we consider this an unsupported word size or unsupported parity?
return error.UnsupportedWordSize;
} else if (config.data_bits == .seven and config.parity == null) {
// TODO: should we consider this an unsupported word size or unsupported parity?
return error.UnsupportedWordSize;
}
// set word length
// Per the reference manual, M means
// - 0: 1 start bit, 8 data bits (7 data + 1 parity, or 8 data), n stop bits, the chip default
// - 1: 1 start bit, 9 data bits (8 data + 1 parity, or 9 data), n stop bits
const m: u1 = if (config.data_bits == .nine or (config.data_bits == .eight and config.parity != null)) 1 else 0;
@field(regs, usart_name).CR1.modify(.{ .M = m });
// set parity
if (config.parity) |parity| {
@field(regs, usart_name).CR1.modify(.{ .PCE = 1, .PS = @enumToInt(parity) });
} // otherwise, no need to set no parity since we reset Control Registers above, and it's the default
// set number of stop bits
@field(regs, usart_name).CR2.modify(.{ .STOP = @enumToInt(config.stop_bits) });
// set the baud rate
// Despite the reference manual talking about fractional calculation and other buzzwords,
// it is actually just a simple divider. Just ignore DIV_Mantissa and DIV_Fraction and
// set the result of the division as the lower 16 bits of BRR.
// TODO: We assume the default OVER8=0 configuration above (i.e. 16x oversampling).
// TODO: Do some checks to see if the baud rate is too high (or perhaps too low)
// TODO: Do a rounding div, instead of a truncating div?
const clocks = micro.clock.get();
const bus_frequency = switch (index) {
1, 6 => clocks.apb2,
2...5 => clocks.apb1,
else => unreachable,
};
const usartdiv = @intCast(u16, @divTrunc(bus_frequency, config.baud_rate));
@field(regs, usart_name).BRR.raw = usartdiv;
// enable USART, and its transmitter and receiver
@field(regs, usart_name).CR1.modify(.{ .UE = 1 });
@field(regs, usart_name).CR1.modify(.{ .TE = 1 });
@field(regs, usart_name).CR1.modify(.{ .RE = 1 });
// For code simplicity, at cost of one or more register reads,
// we read back the actual configuration from the registers,
// instead of using the `config` values.
return readFromRegisters();
}
pub fn getOrInit(config: micro.uart.Config) !Self {
if (@field(regs, usart_name).CR1.read().UE == 1) {
// UART1 already enabled, don't reinitialize and disturb things;
// instead read and use the actual configuration.
return readFromRegisters();
} else return init(config);
}
fn readFromRegisters() Self {
const cr1 = @field(regs, usart_name).CR1.read();
// As documented in `init()`, M0==1 means 'the 9th bit (not the 8th bit) is the parity bit'.
// So we always mask away the 9th bit, and if parity is enabled and it is in the 8th bit,
// then we also mask away the 8th bit.
return Self{ .parity_read_mask = if (cr1.PCE == 1 and cr1.M == 0) 0x7F else 0xFF };
}
pub fn canWrite(self: Self) bool {
_ = self;
return switch (@field(regs, usart_name).SR.read().TXE) {
1 => true,
0 => false,
};
}
pub fn tx(self: Self, ch: u8) void {
while (!self.canWrite()) {} // Wait for Previous transmission
@field(regs, usart_name).DR.modify(ch);
}
pub fn txflush(_: Self) void {
while (@field(regs, usart_name).SR.read().TC == 0) {}
}
pub fn canRead(self: Self) bool {
_ = self;
return switch (@field(regs, usart_name).SR.read().RXNE) {
1 => true,
0 => false,
};
}
pub fn rx(self: Self) u8 {
while (!self.canRead()) {} // Wait till the data is received
const data_with_parity_bit: u9 = @field(regs, usart_name).DR.read();
return @intCast(u8, data_with_parity_bit & self.parity_read_mask);
}
};
}
pub const i2c = struct {
const PinLine = std.meta.FieldEnum(micro.i2c.Pins);
/// Checks if a pin is valid for a given i2c index and line
pub fn isValidPin(comptime pin: type, comptime index: usize, comptime line: PinLine) bool {
const pin_name = pin.name;
return switch (line) {
.scl => switch (index) {
1 => std.mem.eql(u8, pin_name, "PB6") or std.mem.eql(u8, pin_name, "PB8"),
2 => std.mem.eql(u8, pin_name, "PB10") or std.mem.eql(u8, pin_name, "PF1") or std.mem.eql(u8, pin_name, "PH4"),
3 => std.mem.eql(u8, pin_name, "PA8") or std.mem.eql(u8, pin_name, "PH7"),
else => unreachable,
},
// Valid RX pins for the UARTs
.sda => switch (index) {
1 => std.mem.eql(u8, pin_name, "PB7") or std.mem.eql(u8, pin_name, "PB9"),
2 => std.mem.eql(u8, pin_name, "PB11") or std.mem.eql(u8, pin_name, "PF0") or std.mem.eql(u8, pin_name, "PH5"),
3 => std.mem.eql(u8, pin_name, "PC9") or std.mem.eql(u8, pin_name, "PH8"),
else => unreachable,
},
};
}
};
pub fn I2CController(comptime index: usize, comptime pins: micro.i2c.Pins) type {
if (index < 1 or index > 3) @compileError("Valid I2C index are 1..3");
const i2c_name = std.fmt.comptimePrint("I2C{d}", .{index});
const scl_pin =
if (pins.scl) |scl|
if (uart.isValidPin(scl, index, .scl))
scl
else
@compileError(std.fmt.comptimePrint("SCL pin {s} is not valid for I2C{}", .{ scl.name, index }))
else switch (index) {
// Provide default scl pins if no pin is specified
1 => micro.Pin("PB6"),
2 => micro.Pin("PB10"),
3 => micro.Pin("PA8"),
else => unreachable,
};
const sda_pin =
if (pins.sda) |sda|
if (uart.isValidPin(sda, index, .sda))
sda
else
@compileError(std.fmt.comptimePrint("SDA pin {s} is not valid for UART{}", .{ sda.name, index }))
else switch (index) {
// Provide default sda pins if no pin is specified
1 => micro.Pin("PB7"),
2 => micro.Pin("PB11"),
3 => micro.Pin("PC9"),
else => unreachable,
};
const scl_gpio = micro.Gpio(scl_pin, .{
.mode = .alternate_function,
.alternate_function = .af4,
});
const sda_gpio = micro.Gpio(sda_pin, .{
.mode = .alternate_function,
.alternate_function = .af4,
});
// Base field of the specific I2C peripheral
const i2c_base = @field(regs, i2c_name);
return struct {
const Self = @This();
pub fn init(config: micro.i2c.Config) !Self {
// Configure I2C
// 1. Enable the I2C CLOCK and GPIO CLOCK
regs.RCC.APB1ENR.modify(.{ .I2C1EN = 1 });
regs.RCC.AHB1ENR.modify(.{ .GPIOBEN = 1 });
// 2. Configure the I2C PINs
// This takes care of setting them alternate function mode with the correct AF
scl_gpio.init();
sda_gpio.init();
// TODO: the stuff below will probably use the microzig gpio API in the future
const scl = scl_pin.source_pin;
const sda = sda_pin.source_pin;
// Select Open Drain Output
setRegField(@field(scl.gpio_port, "OTYPER"), "OT" ++ scl.suffix, 1);
setRegField(@field(sda.gpio_port, "OTYPER"), "OT" ++ sda.suffix, 1);
// Select High Speed
setRegField(@field(scl.gpio_port, "OSPEEDR"), "OSPEEDR" ++ scl.suffix, 0b10);
setRegField(@field(sda.gpio_port, "OSPEEDR"), "OSPEEDR" ++ sda.suffix, 0b10);
// Activate Pull-up
setRegField(@field(scl.gpio_port, "PUPDR"), "PUPDR" ++ scl.suffix, 0b01);
setRegField(@field(sda.gpio_port, "PUPDR"), "PUPDR" ++ sda.suffix, 0b01);
// 3. Reset the I2C
i2c_base.CR1.modify(.{ .PE = 0 });
while (i2c_base.CR1.read().PE == 1) {}
// 4. Configure I2C timing
const bus_frequency_hz = micro.clock.get().apb1;
const bus_frequency_mhz: u6 = @intCast(u6, @divExact(bus_frequency_hz, 1_000_000));
if (bus_frequency_mhz < 2 or bus_frequency_mhz > 50) {
return error.InvalidBusFrequency;
}
// .FREQ is set to the bus frequency in Mhz
i2c_base.CR2.modify(.{ .FREQ = bus_frequency_mhz });
switch (config.target_speed) {
10_000...100_000 => {
// CCR is bus_freq / (target_speed * 2). We use floor to avoid exceeding the target speed.
const ccr = @intCast(u12, @divFloor(bus_frequency_hz, config.target_speed * 2));
i2c_base.CCR.modify(.{ .CCR = ccr });
// Trise is bus frequency in Mhz + 1
i2c_base.TRISE.modify(bus_frequency_mhz + 1);
},
100_001...400_000 => {
// TODO: handle fast mode
return error.InvalidSpeed;
},
else => return error.InvalidSpeed,
}
// 5. Program the I2C_CR1 register to enable the peripheral
i2c_base.CR1.modify(.{ .PE = 1 });
return Self{};
}
pub const WriteState = struct {
address: u7,
buffer: [255]u8 = undefined,
buffer_size: u8 = 0,
pub fn start(address: u7) !WriteState {
return WriteState{ .address = address };
}
pub fn writeAll(self: *WriteState, bytes: []const u8) !void {
std.debug.assert(self.buffer_size < 255);
for (bytes) |b| {
self.buffer[self.buffer_size] = b;
self.buffer_size += 1;
if (self.buffer_size == 255) {
try self.sendBuffer();
}
}
}
fn sendBuffer(self: *WriteState) !void {
if (self.buffer_size == 0) @panic("write of 0 bytes not supported");
// Wait for the bus to be free
while (i2c_base.SR2.read().BUSY == 1) {}
// Send start
i2c_base.CR1.modify(.{ .START = 1 });
// Wait for the end of the start condition, master mode selected, and BUSY bit set
while ((i2c_base.SR1.read().SB == 0 or
i2c_base.SR2.read().MSL == 0 or
i2c_base.SR2.read().BUSY == 0))
{}
// Write the address to bits 7..1, bit 0 stays at 0 to indicate write operation
i2c_base.DR.modify(@intCast(u8, self.address) << 1);
// Wait for address confirmation
while (i2c_base.SR1.read().ADDR == 0) {}
// Read SR2 to clear address condition
_ = i2c_base.SR2.read();
for (self.buffer[0..self.buffer_size]) |b| {
// Write data byte
i2c_base.DR.modify(b);
// Wait for transfer finished
while (i2c_base.SR1.read().BTF == 0) {}
}
self.buffer_size = 0;
}
pub fn stop(self: *WriteState) !void {
try self.sendBuffer();
// Communication STOP
i2c_base.CR1.modify(.{ .STOP = 1 });
while (i2c_base.SR2.read().BUSY == 1) {}
}
pub fn restartRead(self: *WriteState) !ReadState {
try self.sendBuffer();
return ReadState{ .address = self.address };
}
pub fn restartWrite(self: *WriteState) !WriteState {
try self.sendBuffer();
return WriteState{ .address = self.address };
}
};
pub const ReadState = struct {
address: u7,
pub fn start(address: u7) !ReadState {
return ReadState{ .address = address };
}
/// Fails with ReadError if incorrect number of bytes is received.
pub fn readNoEof(self: *ReadState, buffer: []u8) !void {
std.debug.assert(buffer.len < 256);
// Send start and enable ACK
i2c_base.CR1.modify(.{ .START = 1, .ACK = 1 });
// Wait for the end of the start condition, master mode selected, and BUSY bit set
while ((i2c_base.SR1.read().SB == 0 or
i2c_base.SR2.read().MSL == 0 or
i2c_base.SR2.read().BUSY == 0))
{}
// Write the address to bits 7..1, bit 0 set to 1 to indicate read operation
i2c_base.DR.modify((@intCast(u8, self.address) << 1) | 1);
// Wait for address confirmation
while (i2c_base.SR1.read().ADDR == 0) {}
// Read SR2 to clear address condition
_ = i2c_base.SR2.read();
for (buffer) |_, i| {
if (i == buffer.len - 1) {
// Disable ACK
i2c_base.CR1.modify(.{ .ACK = 0 });
}
// Wait for data to be received
while (i2c_base.SR1.read().RxNE == 0) {}
// Read data byte
buffer[i] = i2c_base.DR.read();
}
}
pub fn stop(_: *ReadState) !void {
// Communication STOP
i2c_base.CR1.modify(.{ .STOP = 1 });
while (i2c_base.SR2.read().BUSY == 1) {}
}
pub fn restartRead(self: *ReadState) !ReadState {
return ReadState{ .address = self.address };
}
pub fn restartWrite(self: *ReadState) !WriteState {
return WriteState{ .address = self.address };
}
};
};
}

File diff suppressed because it is too large Load Diff

@ -1,94 +0,0 @@
//! For now we keep all clock settings on the chip defaults.
//! This code should work with all the STM32F42xx line
//!
//! Specifically, TIM6 is running on a 16 MHz clock,
//! HSI = 16 MHz is the SYSCLK after reset
//! default AHB prescaler = /1 (= values 0..7):
//!
//! ```
//! regs.RCC.CFGR.modify(.{ .HPRE = 0 });
//! ```
//!
//! so also HCLK = 16 MHz.
//! And with the default APB1 prescaler = /1:
//!
//! ```
//! regs.RCC.CFGR.modify(.{ .PPRE1 = 0 });
//! ```
//!
//! results in PCLK1 = 16 MHz.
//!
//! TODO: add more clock calculations when adding Uart
const std = @import("std");
const micro = @import("microzig");
const chip = @import("registers.zig");
const regs = chip.registers;
pub usingnamespace chip;
pub const clock = struct {
pub const Domain = enum {
cpu,
ahb,
apb1,
apb2,
};
};
// Default clock frequencies after reset, see top comment for calculation
pub const clock_frequencies = .{
.cpu = 16_000_000,
.ahb = 16_000_000,
.apb1 = 16_000_000,
.apb2 = 16_000_000,
};
pub fn parsePin(comptime spec: []const u8) type {
const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme.";
if (spec[0] != 'P')
@compileError(invalid_format_msg);
if (spec[1] < 'A' or spec[1] > 'K')
@compileError(invalid_format_msg);
const pin_number: comptime_int = std.fmt.parseInt(u4, spec[2..], 10) catch @compileError(invalid_format_msg);
return struct {
/// 'A'...'K'
const gpio_port_name = spec[1..2];
const gpio_port = @field(regs, "GPIO" ++ gpio_port_name);
const suffix = std.fmt.comptimePrint("{d}", .{pin_number});
};
}
fn setRegField(reg: anytype, comptime field_name: anytype, value: anytype) void {
var temp = reg.read();
@field(temp, field_name) = value;
reg.write(temp);
}
pub const gpio = struct {
pub fn setOutput(comptime pin: type) void {
setRegField(regs.RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1);
setRegField(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b01);
}
pub fn setInput(comptime pin: type) void {
setRegField(regs.RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1);
setRegField(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b00);
}
pub fn read(comptime pin: type) micro.gpio.State {
const idr_reg = pin.gpio_port.IDR;
const reg_value = @field(idr_reg.read(), "IDR" ++ pin.suffix); // TODO extract to getRegField()?
return @intToEnum(micro.gpio.State, reg_value);
}
pub fn write(comptime pin: type, state: micro.gpio.State) void {
switch (state) {
.low => setRegField(pin.gpio_port.BSRR, "BR" ++ pin.suffix, 1),
.high => setRegField(pin.gpio_port.BSRR, "BS" ++ pin.suffix, 1),
}
}
};

@ -1,15 +1,15 @@
const std = @import("std"); const std = @import("std");
const Cpu = @import("Cpu.zig"); const Cpu = @import("Cpu.zig");
fn root() []const u8 { fn root_dir() []const u8 {
return std.fs.path.dirname(@src().file) orelse unreachable; return std.fs.path.dirname(@src().file) orelse ".";
} }
const root_path = root() ++ "/";
pub const avr5 = Cpu{ pub const avr5 = Cpu{
.name = "AVR5", .name = "AVR5",
.path = root_path ++ "cpus/avr/avr5.zig", .source = .{
.path = std.fmt.comptimePrint("{s}/cpus/avr5.zig", .{root_dir()}),
},
.target = std.zig.CrossTarget{ .target = std.zig.CrossTarget{
.cpu_arch = .avr, .cpu_arch = .avr,
.cpu_model = .{ .explicit = &std.Target.avr.cpu.avr5 }, .cpu_model = .{ .explicit = &std.Target.avr.cpu.avr5 },
@ -20,7 +20,9 @@ pub const avr5 = Cpu{
pub const cortex_m0 = Cpu{ pub const cortex_m0 = Cpu{
.name = "ARM Cortex-M0", .name = "ARM Cortex-M0",
.path = root_path ++ "cpus/cortex-m/cortex-m.zig", .source = .{
.path = std.fmt.comptimePrint("{s}/cpus/cortex-m.zig", .{root_dir()}),
},
.target = std.zig.CrossTarget{ .target = std.zig.CrossTarget{
.cpu_arch = .thumb, .cpu_arch = .thumb,
.cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0 }, .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0 },
@ -31,7 +33,9 @@ pub const cortex_m0 = Cpu{
pub const cortex_m0plus = Cpu{ pub const cortex_m0plus = Cpu{
.name = "ARM Cortex-M0+", .name = "ARM Cortex-M0+",
.path = root_path ++ "cpus/cortex-m/cortex-m.zig", .source = .{
.path = std.fmt.comptimePrint("{s}/cpus/cortex-m.zig", .{root_dir()}),
},
.target = std.zig.CrossTarget{ .target = std.zig.CrossTarget{
.cpu_arch = .thumb, .cpu_arch = .thumb,
.cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0plus }, .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0plus },
@ -42,7 +46,9 @@ pub const cortex_m0plus = Cpu{
pub const cortex_m3 = Cpu{ pub const cortex_m3 = Cpu{
.name = "ARM Cortex-M3", .name = "ARM Cortex-M3",
.path = root_path ++ "cpus/cortex-m/cortex-m.zig", .source = .{
.path = std.fmt.comptimePrint("{s}/cpus/cortex-m.zig", .{root_dir()}),
},
.target = std.zig.CrossTarget{ .target = std.zig.CrossTarget{
.cpu_arch = .thumb, .cpu_arch = .thumb,
.cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m3 }, .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m3 },
@ -53,7 +59,9 @@ pub const cortex_m3 = Cpu{
pub const cortex_m4 = Cpu{ pub const cortex_m4 = Cpu{
.name = "ARM Cortex-M4", .name = "ARM Cortex-M4",
.path = root_path ++ "cpus/cortex-m/cortex-m.zig", .source = .{
.path = std.fmt.comptimePrint("{s}/cpus/cortex-m.zig", .{root_dir()}),
},
.target = std.zig.CrossTarget{ .target = std.zig.CrossTarget{
.cpu_arch = .thumb, .cpu_arch = .thumb,
.cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m4 }, .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m4 },
@ -64,7 +72,9 @@ pub const cortex_m4 = Cpu{
pub const riscv32_imac = Cpu{ pub const riscv32_imac = Cpu{
.name = "RISC-V 32-bit", .name = "RISC-V 32-bit",
.path = root_path ++ "cpus/rv32-imac/riscv32.zig", .source = .{
.path = std.fmt.comptimePrint("{s}/cpus/riscv32.zig", .{root_dir()}),
},
.target = std.zig.CrossTarget{ .target = std.zig.CrossTarget{
.cpu_arch = .riscv32, .cpu_arch = .riscv32,
.cpu_model = .{ .explicit = &std.Target.riscv.cpu.sifive_e21 }, .cpu_model = .{ .explicit = &std.Target.riscv.cpu.sifive_e21 },

@ -73,7 +73,7 @@ pub const vector_table = blk: {
break :blk T._start; break :blk T._start;
}; };
fn makeIsrHandler(comptime name: []const u8, comptime func: anytype) type { fn make_isr_handler(comptime name: []const u8, comptime func: anytype) type {
const calling_convention = switch (@typeInfo(@TypeOf(func))) { const calling_convention = switch (@typeInfo(@TypeOf(func))) {
.Fn => |info| info.calling_convention, .Fn => |info| info.calling_convention,
else => @compileError("Declarations in 'interrupts' namespace must all be functions. '" ++ name ++ "' is not a function"), else => @compileError("Declarations in 'interrupts' namespace must all be functions. '" ++ name ++ "' is not a function"),

@ -76,7 +76,7 @@ pub const startup_logic = struct {
} }
}; };
fn isValidField(field_name: []const u8) bool { fn is_valid_field(field_name: []const u8) bool {
return !std.mem.startsWith(u8, field_name, "reserved") and return !std.mem.startsWith(u8, field_name, "reserved") and
!std.mem.eql(u8, field_name, "initial_stack_pointer") and !std.mem.eql(u8, field_name, "initial_stack_pointer") and
!std.mem.eql(u8, field_name, "reset"); !std.mem.eql(u8, field_name, "reset");
@ -105,7 +105,7 @@ pub var vector_table: VectorTable = blk: {
if (!@hasField(VectorTable, decl.name)) { if (!@hasField(VectorTable, decl.name)) {
var msg: []const u8 = "There is no such interrupt as '" ++ decl.name ++ "'. Declarations in 'interrupts' must be one of:\n"; var msg: []const u8 = "There is no such interrupt as '" ++ decl.name ++ "'. Declarations in 'interrupts' must be one of:\n";
inline for (std.meta.fields(VectorTable)) |field| { inline for (std.meta.fields(VectorTable)) |field| {
if (isValidField(field.name)) { if (is_valid_field(field.name)) {
msg = msg ++ " " ++ field.name ++ "\n"; msg = msg ++ " " ++ field.name ++ "\n";
} }
} }
@ -113,10 +113,10 @@ pub var vector_table: VectorTable = blk: {
@compileError(msg); @compileError(msg);
} }
if (!isValidField(decl.name)) if (!is_valid_field(decl.name))
@compileError("You are not allowed to specify '" ++ decl.name ++ "' in the vector table, for your sins you must now pay a $5 fine to the ZSF: https://github.com/sponsors/ziglang"); @compileError("You are not allowed to specify '" ++ decl.name ++ "' in the vector table, for your sins you must now pay a $5 fine to the ZSF: https://github.com/sponsors/ziglang");
@field(tmp, decl.name) = createInterruptVector(function); @field(tmp, decl.name) = create_interrupt_vector(function);
} }
} }
break :blk tmp; break :blk tmp;
@ -129,7 +129,7 @@ else if (@hasDecl(microzig.hal, "InterruptVector"))
else else
microzig.chip.InterruptVector; microzig.chip.InterruptVector;
fn createInterruptVector( fn create_interrupt_vector(
comptime function: anytype, comptime function: anytype,
) InterruptVector { ) InterruptVector {
const calling_convention = @typeInfo(@TypeOf(function)).Fn.calling_convention; const calling_convention = @typeInfo(@TypeOf(function)).Fn.calling_convention;

@ -21,7 +21,7 @@ pub fn wfe() void {
asm volatile ("csrs 0x810, 0x1"); asm volatile ("csrs 0x810, 0x1");
} }
pub fn pmpOpenAllSpace() void { pub fn pmp_open_all_space() void {
// Config entry0 addr to all 1s to make the range cover all space // Config entry0 addr to all 1s to make the range cover all space
asm volatile ("li x6, 0xffffffff" ::: "x6"); asm volatile ("li x6, 0xffffffff" ::: "x6");
asm volatile ("csrw pmpaddr0, x6"); asm volatile ("csrw pmpaddr0, x6");
@ -30,7 +30,7 @@ pub fn pmpOpenAllSpace() void {
asm volatile ("csrw pmpcfg0, x6"); asm volatile ("csrw pmpcfg0, x6");
} }
pub fn switchM2uMode() void { pub fn switch_m2u_mode() void {
asm volatile ("la x6, 1f " ::: "x6"); asm volatile ("la x6, 1f " ::: "x6");
asm volatile ("csrw mepc, x6"); asm volatile ("csrw mepc, x6");
asm volatile ("mret"); asm volatile ("mret");

@ -1,10 +0,0 @@
# microzig tools
## `svd2zig.py`
Converts CMSIS SVD files into zig source code. Requires [cmsis_svd](https://github.com/posborne/cmsis-svd/) installed (can be done with `pip install -U cmsis-svd`).
## `linkerscript-gen.zig`
Compiled on-demand by microzig to generate a linkerscript from a board/chip/cpu bundle.

@ -1,410 +0,0 @@
#!/usr/bin/env python3
import sys
import subprocess
import re
import textwrap
from cmsis_svd.parser import SVDParser
import cmsis_svd.model as model
def cleanup_description(description):
if description is None:
return ''
return ' '.join(description.replace('\n', ' ').split())
def comment_description(description):
if description is None:
return None
description = ' '.join(description.replace('\n', ' ').split())
return textwrap.fill(description, width=80, initial_indent='/// ', subsequent_indent='/// ')
def escape_field(field):
if not field[0].isdigit() and re.match(r'^[A-Za-z0-9_]+$', field):
return field
else:
return f"@\"{field}\""
class MMIOFileGenerator:
def __init__(self, f):
self.f = f
def generate_padding(self, count, name, offset):
while count > 0:
self.write_line(f"{name}{offset + count}: u1 = 0,")
count = count - 1
def generate_enumerated_field(self, field):
'''
returns something like:
name: enum(u<size>){ // foo description
name = value, // desc: ...
name = value, // desc:
_, // non-exhustive
},
'''
description = comment_description(field.description)
self.write_line(description)
self.write_line(f"{field.name}: u{field.bit_width} = 0,")
# TODO: turn enums back on later
#self.write_line(f"{field.name}:enum(u{field.bit_width}){{")
#total_fields_with_values = 0
#for e in field.enumerated_values:
# e.description = cleanup_description(e.description)
# if e.value is None or e.name == "RESERVED":
# # reserved fields doesn't have a value so we have to comment them out
# escaped_field_name = escape_field(e.name)
# self.write_line(f"// {escaped_field_name}, // {e.description}")
# else:
# total_fields_with_values = total_fields_with_values + 1
# escaped_field_name = escape_field(e.name)
# if e.name != e.description:
# self.write_line(f"/// {e.description}")
# self.write_line(f"{escaped_field_name} = {e.value},")
## if the fields doesn't use all possible values make the enum non-exhaustive
#if total_fields_with_values < 2**field.bit_width:
# self.write_line("_, // non-exhaustive")
#self.write_line("},")
return field.bit_offset + field.bit_width
def generate_register_field(self, field):
'''
returns something like:
name: u<size>, // foo description
'''
description = comment_description(field.description)
if field.name != field.description:
self.write_line(description)
self.write_line(f"{field.name}:u{field.bit_width} = 0,")
return field.bit_offset + field.bit_width
def generate_fields(self, fields, size):
last_offset = 0
reserved_index = 0
for field in sorted(fields, key=lambda f: f.bit_offset):
# workaround for NXP SVD which has overleaping reserved fields
if field.name == "RESERVED":
self.write_line(f"// RESERVED: u{field.bit_width}, // {field.description}")
continue
if last_offset != field.bit_offset:
reserved_size = field.bit_offset - last_offset
self.generate_padding(reserved_size, "reserved", reserved_index)
reserved_index += reserved_size
if field.is_enumerated_type:
last_offset = self.generate_enumerated_field(field)
else:
last_offset = self.generate_register_field(field)
if size is not None and size != last_offset:
self.generate_padding(size - last_offset, "padding", 0)
def generate_register_declaration_manual(self, name, description, address_offset, fields, size):
num_fields = len(fields)
description = comment_description(description)
self.write_line("")
self.write_line(description)
if num_fields == 0 or (num_fields == 1 and fields[0].bit_width == 32 and name == fields[0].name):
# TODO: hardcoded 32 bit here
self.write_line(f"pub const {name} = @intToPtr(*volatile u{size}, Address + 0x{address_offset:08x});")
else:
self.write_line(f"pub const {name} = mmio(Address + 0x{address_offset:08x}, {size}, packed struct{{")
self.generate_fields(fields, size)
self.write_line("});")
def generate_register_declaration(self, register):
size = register.size
if size == None:
size = 32 # hack for now...
num_fields = len(register.fields)
description = comment_description(register.description)
self.write_line("")
self.write_line(description)
if num_fields == 0 or (num_fields == 1 and register.fields[0].bit_width == 32 and register.name == register.fields[0].name):
# TODO: hardcoded 32 bit here
self.write_line(f"pub const {register.name} = @intToPtr(*volatile u{size}, Address + 0x{register.address_offset:08x});")
else:
self.write_line(f"pub const {register.name} = mmio(Address + 0x{register.address_offset:08x}, {size}, packed struct{{")
self.generate_fields(register.fields, size)
self.write_line("});")
def generate_register_cluster(self, cluster):
if cluster.derived_from is not None:
raise Exception("TODO: derived_from")
self.write_line("")
self.write_line(f"pub const {cluster.name} = struct {{")
for register in cluster._register:
if isinstance(register, model.SVDRegisterArray):
self.generate_register_array(register)
elif isinstance(register, model.SVDRegister):
self.generate_register_declaration(register)
self.write_line("};")
def generate_register_cluster_array(self, cluster):
max_fields = int(cluster.dim_increment / 4)
if len(cluster._register) > max_fields:
raise Exception("not enough room for fields")
name = cluster.name.replace("[%s]", "")
self.write_line(f"pub const {name} = @intToPtr(*volatile [{cluster.dim}]packed struct {{")
for register in cluster._register:
size = register.size
if size == None:
size = 32 # hack for now...
last_offset = 0
reserved_index = 0
if size != 32:
raise Exception("TODO: handle registers that are not 32-bit")
description = comment_description(register.description)
self.write_line("")
self.write_line(description)
if len(register.fields) == 0 or (len(register.fields) == 1 and register.fields[0].bit_width == size and register.name == register.fields[0].name):
self.write_line(f"{register.name}: u{size},")
else:
self.write_line(register.name + f": MMIO({size}, packed struct {{")
self.generate_fields(register.fields, size)
self.write_line("}),")
for i in range(0, max_fields - len(cluster._register)):
self.write_line(f" padding{i}: u32,")
# TODO: this would be cleaner, but we'll probably run into packed struct bugs
#num_bits = size * (max_fields - len(cluster._register))
#self.write_line(f"_: u{num_bits},")
self.write_line(f" }}, Address + 0x{cluster.address_offset:08x});")
# TODO: calculate size in here, fine since everything we're working with rn is 32 bit
def generate_register_array(self, register_array):
description = comment_description(register_array.description)
if register_array.dim_indices.start != 0 or ("%s" in register_array.name and not "[%s]" in register_array.name):
for i in register_array.dim_indices:
fmt = register_array.name.replace("%s", "{}")
name = fmt.format(i)
self.generate_register_declaration_manual(name,
register_array.description,
register_array.address_offset + (i * register_array.dim_increment),
register_array._fields,
32)
return
if register_array.dim_increment != 4:
raise Exception("TODO register " + register_array.name + " dim_increment != 4" + ", it is " + str(register_array.dim_increment))
name = register_array.name.replace("%s", "").replace("[]", "")
self.write_line(description)
num_fields = len(register_array._fields)
# TODO: hardcoded 32 bit here
if num_fields == 0 or (num_fields == 1 and register_array._fields[0].bit_width == 32 and name == register_array._fields[0].name):
# TODO: hardcoded 32 bit here
self.write_line(f"pub const {name} = @intToPtr(*volatile [{register_array.dim}]u32, Address + 0x{register_array.address_offset:08x});")
else:
self.write_line(f"pub const {name} = @intToPtr(*volatile [{register_array.dim}]MMIO(32, packed struct {{")
self.generate_fields(register_array._fields, 32)
self.write_line(f"}}), Address + 0x{register_array.address_offset:08x});")
def generate_peripheral_declaration(self, peripheral):
description = comment_description(peripheral.description)
self.write_line("")
self.write_line(description)
self.write_line(f"pub const {peripheral.name} = extern struct {{")
self.write_line(f"pub const Address: u32 = 0x{peripheral.base_address:08x};")
for register in sorted(peripheral._lookup_possibly_derived_attribute('registers'), key=lambda f: f.address_offset):
self.generate_register_declaration(register)
for register_array in sorted(peripheral._lookup_possibly_derived_attribute('register_arrays'), key=lambda f: f.address_offset):
self.generate_register_array(register_array)
for cluster in sorted(peripheral._lookup_possibly_derived_attribute('clusters'), key=lambda f: f.address_offset):
if isinstance(cluster, model.SVDRegisterCluster):
self.generate_register_cluster(cluster)
elif isinstance(cluster, model.SVDRegisterClusterArray):
#self.generate_register_cluster_array(cluster)
pass
else:
raise Exception("unhandled cluster type")
self.write_line("};")
# TODO: descriptions on system interrupts/exceptions, turn on system interrupts/exceptions
def generate_startup_and_interrupts(self, interrupts):
self.write_line("")
self.write_line(
"""
const std = @import(\"std\");
const root = @import(\"root\");
const cpu = @import(\"cpu\");
const config = @import(\"microzig-config\");
const InterruptVector = extern union {
C: fn () callconv(.C) void,
Naked: fn () callconv(.Naked) void,
// Interrupt is not supported on arm
};
fn makeUnhandledHandler(comptime str: []const u8) InterruptVector {
return InterruptVector{
.C = struct {
fn unhandledInterrupt() callconv(.C) noreturn {
@panic(\"unhandled interrupt: \" ++ str);
}
}.unhandledInterrupt,
};
}
pub const VectorTable = extern struct {
initial_stack_pointer: u32 = config.end_of_stack,
Reset: InterruptVector = InterruptVector{ .C = cpu.startup_logic._start },
NMI: InterruptVector = makeUnhandledHandler(\"NMI\"),
HardFault: InterruptVector = makeUnhandledHandler(\"HardFault\"),
MemManage: InterruptVector = makeUnhandledHandler(\"MemManage\"),
BusFault: InterruptVector = makeUnhandledHandler(\"BusFault\"),
UsageFault: InterruptVector = makeUnhandledHandler(\"UsageFault\"),
reserved: [4]u32 = .{ 0, 0, 0, 0 },
SVCall: InterruptVector = makeUnhandledHandler(\"SVCall\"),
DebugMonitor: InterruptVector = makeUnhandledHandler(\"DebugMonitor\"),
reserved1: u32 = 0,
PendSV: InterruptVector = makeUnhandledHandler(\"PendSV\"),
SysTick: InterruptVector = makeUnhandledHandler(\"SysTick\"),\n
""")
reserved_count = 2
expected_next_value = 0
for interrupt in interrupts:
if expected_next_value > interrupt.value:
raise Exception("out of order interrupt list")
while expected_next_value < interrupt.value:
self.write_line(f" reserved{reserved_count}: u32 = 0,")
expected_next_value += 1
reserved_count += 1
if interrupt.description is not None:
description = comment_description(interrupt.description)
self.write_line(description)
self.write_line(f" {interrupt.name}: InterruptVector = makeUnhandledHandler(\"{interrupt.name}\"),")
expected_next_value += 1
self.write_line("};")
self.write_line(
"""
fn isValidField(field_name: []const u8) bool {
return !std.mem.startsWith(u8, field_name, "reserved") and
!std.mem.eql(u8, field_name, "initial_stack_pointer") and
!std.mem.eql(u8, field_name, "reset");
}
export const vectors: VectorTable linksection(\"microzig_flash_start\") = blk: {
var temp: VectorTable = .{};
if (@hasDecl(root, \"vector_table\")) {
const vector_table = root.vector_table;
if (@typeInfo(vector_table) != .Struct)
@compileLog(\"root.vector_table must be a struct\");
inline for (@typeInfo(vector_table).Struct.decls) |decl| {
const calling_convention = @typeInfo(@TypeOf(@field(vector_table, decl.name))).Fn.calling_convention;
const handler = @field(vector_table, decl.name);
if (!@hasField(VectorTable, decl.name)) {
var msg: []const u8 = \"There is no such interrupt as '\" ++ decl.name ++ \"', declarations in 'root.vector_table' must be one of:\\n\";
inline for (std.meta.fields(VectorTable)) |field| {
if (isValidField(field.name)) {
msg = msg ++ \" \" ++ field.name ++ \"\\n\";
}
}
@compileError(msg);
}
if (!isValidField(decl.name))
@compileError(\"You are not allowed to specify \'\" ++ decl.name ++ \"\' in the vector table, for your sins you must now pay a $5 fine to the ZSF: https://github.com/sponsors/ziglang\");
@field(temp, decl.name) = switch (calling_convention) {
.C => .{ .C = handler },
.Naked => .{ .Naked = handler },
// for unspecified calling convention we are going to generate small wrapper
.Unspecified => .{
.C = struct {
fn wrapper() callconv(.C) void {
if (calling_convention == .Unspecified) // TODO: workaround for some weird stage1 bug
@call(.{ .modifier = .always_inline }, handler, .{});
}
}.wrapper,
},
else => @compileError(\"unsupported calling convention for function \" ++ decl.name),
};
}
}
break :blk temp;
};
""")
def generate_file(self, device):
self.write_line("// generated using svd2zig.py\n// DO NOT EDIT")
self.write_line(f"// based on {device.name} version {device.version}")
self.write_line("const microzig_mmio = @import(\"microzig-mmio\");")
self.write_line("const mmio = microzig_mmio.mmio;")
self.write_line("const MMIO = microzig_mmio.MMIO;")
self.write_line(f"const Name = \"{device.name}\";")
interrupts = {}
for peripheral in device.peripherals:
self.generate_peripheral_declaration(peripheral)
if peripheral.interrupts != None:
for interrupt in peripheral.interrupts:
if interrupt.value in interrupts and interrupts[interrupt.value].description != interrupt.description:
interrupts[interrupt.value].description += "; " + interrupt.description
else:
interrupts[interrupt.value] = interrupt
interrupts = list(map(lambda i: i[1], sorted(interrupts.items())))
for interrupt in interrupts:
if interrupt.description is not None:
interrupt.description = ' '.join(interrupt.description.split())
self.generate_startup_and_interrupts(interrupts)
def write_line(self, line):
self.f.write(line + "\n")
def main():
parser = SVDParser.for_packaged_svd(sys.argv[1], sys.argv[2] + '.svd')
device = parser.get_device()
generator = MMIOFileGenerator(sys.stdout)
generator.generate_file(device)
if __name__ == "__main__":
main()

@ -0,0 +1,3 @@
= MicroZig Test Area
These are minimal tests to validate MicroZig behavior.
Loading…
Cancel
Save