You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
microzig/thoughts.md

6.8 KiB

microzig Design Meeting

Package structure

microzig package exports all functions and types available in the HAL as well as microzig.mcu which will provide access to the MCU. All functions in microzig which are not generic will be inline-forwarded to the board or mcu package.

root
 |- std
 |- microzig
 |   |- mcu (should be specified if "board" does not exit)
 |   |- build_options (defines if "mcu" and/or "board" exists)
 |   \- board (must export ".mcu")
 |       \- mcu
 \- mcu (user decision)

Minimal Root file

Declaring panic in the root file will cause builtin to reference the proper package which will then instantiate microzig which will reference mcu package which will instantiate reset (or similar) which will invoke microzig.main() which will invoke root.main(). Oh boy 😆

const micro = @import("micro");

pub const panic = micro.panic; // this will instantiate microzig

comptime { _ = micro };  // this should not be necessary

pub fn main() void {

}

microzig.mcu

microzig exports a symbol mcu which will provide access to the MCU, CPU and board features

// mcu.zig in microzig.zig
const config = @import("build_options");

usingnamespace if(config.has_board)
	@import("board").mcu
else
	@import("mcu");

pub const has_board = config.has_board;
pub const board = @import("board");

Interrupt API

All interrupt handlers are static and will be collected from root.interrupt_handlers. Each symbol will be verified if it's a valid interrupt vector. A symbol can be a function fn() void, or a anonymous enum literal .hang or .reset.

microzig.interrupts.enable and microzig.interrupts.disable will allow masking/unmasking those interrupts, .cli() and .sei() will globally disable/enable interrupts

microzig.interrupts also provides some handling of critical sections

pub fn main() void {
  micro.interrupts.enable(.WDT);  // enables the WDT interrupt
  micro.interrupts.disable(.WDT); // enables the WDT interrupt
  micro.interrupts.enable(.WTF); // yields compile error "WTF is not a valid interrupt"
  micro.interrupts.enable(.PCINT0); // yields compile error "PCINT0 has no defined interrupt handler"
  micro.interrupts.enable(.NMI); // yields compile error "NMI cannot be masked"
  micro.interrupts.enableAll();
  micro.interrupts.batchEnable(.{ .NMI, .WDT  });
  
  micro.interrupts.cli(); // set interrupt enabled (global enable)
  
  { // critical section
    var crit = micro.interrupts.enterCriticalSection();
    defer crit.leave();
  }
}

var TIMER2_OVF_RUNTIME: fn()void = foo;

pub const interrupt_handlers = struct {
	
  // AVR 
	pub fn TIMER2_OVF() void { TIMER2_OVF_RUNTIME(); }
  pub fn WDT() void { }
  
  pub const PCINT1 = .reset;
  pub const PCINT2 = .hang;
  
  // cortex-mX exceptions
  pub fn NMI() void { }
  pub fn HardFault() void {}

  // LPC 1768 interrupt/irq
  pub fn SSP1() void { }
};

Timer API

microzig should allow having a general purpose timer mechanism

pub var cpu_frequency = 16.0 * micro.clock.mega_hertz;

pub const sleep_mode = .timer; // timer, busyloop, whatever

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

  while(true) {
    led.toggle();
    micro.sleep(100_000); // sleep 100ms
  }
}

GPIO API

microzig.Pin parses a pin definition and returns a type that encodes all relevant info and functions to route that pin. microzig.Gpio is a GPIO port/pin configuration that allows modifying pin levels.


// micro.Pin returns a type containing all relevant pin information
const status_led_pin = micro.Pin("PA3");

// generate a runtime possible pin that cannot be used in all APIs
var generic_pic: micro.RuntimePin.init(status_led_pin);

// 4 Bit IEEE-488 bit banging register
const serial_out = micro.GpioOutputRegister(.{
  micro.Pin("PA0"),
  micro.Pin("PA1"),
  micro.Pin("PA3"), // whoopsies, i miswired, let the software fix that
  micro.Pin("PA2"),
});

pub fn bitBang(nibble: u4) void {
  serial_out.write(nibble);
}

pub fn main() !void {

  // Route all gpio pins from the bit bang register
	inline for(serial_out.pins) |pin| {
  	pin.route(".gpio");
  }
  serial_out.init();

	// route that pin to UART.RXD
  status_led_pin.route(.uart0_rxd); 
  
  //var uart_read_dma_channel = micro.Dma.init(.{.channel = 1});
  
  const status_led = micro.Gpio(status_led_pin, .{
    .mode          = .output,       // { input, output, input_output, open_drain, generic }
    .initial_state = .unspecificed, // { unspecified, low, high, floating, driven }
  });
  status_led.init();
  
  switch(status_led.mode) {
  	// only reading API is available
  	.input => {
    	_ = status_led.read();
    },
    
    // reading and writing is available
    .output => {
    	_ = status_led.read();
      status_led.write(.high);
      
      // "subvariant" of the write 
      status_led.toggle();
      status_led.setToHigh();
      status_led.setToLow();
    },
    
    // reading, writing and changing direction is available
    .input_output => {
      status_led.setDirection(.input, undefined);
    	_ = status_led.read();
      status_led.setDirection(.output, .high); // reqires a defined state after setting the direction
      status_led.write(.high);
    },
    
    // reading and setDive is available
    .open_drain => {
      status_led.setDrive(.disabled);
    	_ = status_led.read();
      status_led.setDrive(.enabled);
    },
    
    // will have all available APIs enabled
    .generic => {},
  }
  
 	// AVR:   PORTA[3]   => "PA3"
  // NXP:   PORT[1][3] => "P1.3"
  // PICO:  PORT[1]    => "P3"
  // STM:   PORT[A][3] => "A3"
  // ESP32: PORT[1]    => "P3"

UART example

const std = @import("std");
const micro = @import("µzig");

// if const it can be comptime-optimized
pub var cpu_frequency = 100.0 * micro.clock.mega_hertz;

// if this is enabled, a event loop will run
// in microzig.main() that allows using `async`/`await` "just like that" *grin*
pub const io_mode = .evented;

pub fn main() !void {
  var debug_port = micro.Uart.init(0, .{
    .baud_rate = 9600,
    .stop_bits = .@"2",
    .parity = .none, // { none, even, odd, mark, space }
    .data_bits = .@"8", // 5, 6, 7, 8, or 9 data bits
    //.in_dma_channel = 0,
    //.out_dma_channel = 0,
  });
 
  debug_port.configureDMA(???);
  
  try debug_port.writer().writeAll("Hello, World!");
  
  var line_buffer: [64]u8 = undefined;
  const len = try debug_port.reader().readUntilDelimiter(&line_buffer, '\n');
}

Initialization

somewhere inside micro.zig

extern fn reset() noreturn {
  if(@hasDecl(mcu, "mcu_reset")) {
  	mcu.mcu_reset();
    @unreachable();
  }
	// zeroing bss, copying .data, setting stack address
  
  if(@hasDecl(root, "early_main")) // idk about the name
    root.early_main();
  else {
  	mcu.init();
  
  	if(@hasDecl(root, "main"))
  		root.main();
		else
  		@compileError("main or entry_main missing")
	}
}