Skip to content
This repository was archived by the owner on Feb 17, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
278 changes: 278 additions & 0 deletions src/hals/STM32F103/clocks.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
const std = @import("std");

// const pll = @import("pll.zig");
const assert = std.debug.assert;
const comptimePrint = std.fmt.comptimePrint;

const microzig = @import("microzig");
const peripherals = microzig.chip.peripherals;
const RCC = peripherals.RCC;
const FLASH = peripherals.FLASH;

const MHz = 1_000_000;

pub const SysConfig = struct {
pub const Source = enum {
HSI,
HSE,
PLL,
};

source: Source,
freq: u32,
};

pub const PLLConfig = struct {
pub const Source = enum {
HSI_DIV_2,
HSE,
HSE_DIV_2,
};

source: Source,
freq: u32,
};

pub const GlobalConfiguration = struct {
sys: ?SysConfig = null,
hsi_trim: ?u5 = null,
// frequency of external oscillator. Usually 8 MHz
hse_freq: ?u32 = null,
ahb_freq: ?u32 = null,
apb1_freq: ?u32 = null,
apb2_freq: ?u32 = null,
pll: ?PLLConfig = null,

pub fn apply(comptime config: GlobalConfiguration) void {
const sys = config.sys orelse .{ .source = .HSI, .freq = 8 * MHz };

comptime var pll_config: ?PLLConfig = null;
comptime var pll_mul: u32 = 0;

comptime var hsi_enabled = false;
comptime var hse_enabled = false;

comptime var hse_freq: u32 = config.hse_freq orelse 8 * MHz;

comptime var ahb_freq: u32 = config.ahb_freq orelse sys.freq;
comptime var ahb_div: u32 = sys.freq / ahb_freq;

comptime var apb1_freq: u32 = config.apb1_freq orelse if (ahb_freq <= 36 * MHz) ahb_freq else 36 * MHz;
comptime var apb1_div = ahb_freq / apb1_freq;

comptime var apb2_freq: u32 = config.apb2_freq orelse if (ahb_freq <= 72 * MHz) ahb_freq else 72 * MHz;
comptime var apb2_div: u32 = ahb_freq / apb2_freq;

// Do a comptime validation of config
comptime {
if (sys.freq > 72 * MHz) {
@compileError(comptimePrint("Sys frequency is too high. Max frequency: 72 MHz, got {} MHz", .{sys.freq / MHz}));
}

if (hse_freq < 4 * MHz or hse_freq > 16 * MHz) {
@compileError(comptimePrint("Invalid HSE oscillator: {}. Valid range is from 4 MHz to 16 MHz", .{hse_freq / MHz}));
}

if (config.pll) |pll| {
pll_config = pll;
if (pll.freq > 72 * MHz) {
@compileError(comptimePrint("PLL frequency is too high. Max frequency: 72 MHz, got {} MHz", .{pll.freq / MHz}));
}

var source_freq = 0;
switch (pll.source) {
.HSI_DIV_2 => {
source_freq = 4 * MHz;
hsi_enabled = true;
},
.HSE_DIV_2 => {
source_freq = hse_freq / 2;
hse_enabled = true;
},
.HSE => {
source_freq = hse_freq;
hse_enabled = true;
},
}

pll_mul = pll.freq / source_freq;
if (!isValidPLLmul(pll_mul)) {
@compileError(comptimePrint("Invalid PLL multiplier {}", .{pll_mul}));
}
}

switch (sys.source) {
.HSI => {
if (sys.freq != 8 * MHz) {
@compileError(comptimePrint("Incompatible sys frequency {} MHz with HSI source 8 MHz", .{sys.freq / MHz}));
}
hsi_enabled = true;
},
.HSE => {
if (sys.freq != hse_freq) {
@compileError(comptimePrint("Incompatible sys frequency {} MHz with HSE source {} MHz", .{ sys.freq / MHz, hse_freq / MHz }));
}
hse_enabled = true;
},
.PLL => {
if (config.pll) |pll| {
if (pll.freq != sys.freq) {
@compileError(comptimePrint("Incompatible sys frequency {} MHz with PLL source {} MHz", .{ sys.freq / MHz, pll.freq / MHz }));
}
} else {
pll_config = .{ .source = .HSE, .freq = sys.freq };
pll_mul = sys.freq / hse_freq;
hse_enabled = true;
}
},
}

if (ahb_freq > 72 * MHz) {
@compileError(comptimePrint("AHB frequency is too high. Max frequency: 72 MHz, got {} MHz", .{ahb_freq / MHz}));
}

// for some reason 32 is not a valid prescaler
if (!isValidPrescaler(ahb_div, 512) or ahb_div == 32) {
@compileError(comptimePrint("Invalid frequency for AHB: {} Hz.", .{ahb_freq}));
}

if (apb1_freq > 36 * MHz) {
@compileError(comptimePrint("APB1 frequency is too high. Max frequency: 36 MHz, got {} MHz", .{apb1_freq / MHz}));
}

if (!isValidPrescaler(apb1_div, 16)) {
@compileError(comptimePrint("Invalid frequency for APB1: {}.\nValid prescalers: 1, 2, 4, 18 and 16. Got {}", .{ apb1_freq, apb1_div }));
}

if (apb2_freq > 72 * MHz) {
@compileError(comptimePrint("APB2 frequency is too high. Max frequency: 72 MHz, got {} MHz", .{apb2_freq / MHz}));
}

if (!isValidPrescaler(apb2_div, 16)) {
@compileError(comptimePrint("Invalid frequency for APB2: {}.\nValid prescalers: 1, 2, 4, 8 and 16. Got {}", .{ apb2_freq, apb2_div }));
}
}

// Apply config
FLASH.ACR.modify(.{ .PRFTBE = 1 });
if (sys.freq <= 24 * MHz) {
FLASH.ACR.modify(.{ .LATENCY = 0b000 });
} else if (sys.freq <= 48 * MHz) {
FLASH.ACR.modify(.{ .LATENCY = 0b001 });
} else {
FLASH.ACR.modify(.{ .LATENCY = 0b010 });
}

// NOTE: HSI has to be enabled until sys clock is changed

if (config.hsi_trim) |trim| {
RCC.CR.modify(.{ .HSITRIM = trim });
}

if (hse_enabled) {
RCC.CR.modify(.{ .HSEON = 1 });
while (RCC.CR.read().HSERDY != 1) {}
} else {
RCC.CR.modify(.{ .HSEON = 0 });
while (RCC.CR.read().HSERDY != 0) {}
}

if (pll_config) |pll| {
// we need to turn off the pll to configure it
RCC.CR.modify(.{ .PLLON = 0 });
while (RCC.CR.read().PLLRDY != 0) {}

switch (pll.source) {
.HSI_DIV_2 => {
RCC.CFGR.modify(.{ .PLLSRC = 0, .PLLMUL = getPLLmul(pll_mul) });
},
.HSE => {
RCC.CFGR.modify(.{ .PLLSRC = 1, .PLLXTPRE = 0, .PLLMUL = getPLLmul(pll_mul) });
},
.HSE_DIV_2 => {
RCC.CFGR.modify(.{ .PLLSRC = 1, .PLLXTPRE = 1, .PLLMUL = getPLLmul(pll_mul) });
},
}

RCC.CR.modify(.{ .PLLON = 1 });
while (RCC.CR.read().PLLRDY != 1) {}
} else {
RCC.CR.modify(.{ .PLLON = 0 });
while (RCC.CR.read().PLLRDY != 0) {}
}

switch (sys.source) {
.HSI => {
while (RCC.CR.read().HSIRDY != 1) {}
},
.HSE => {
while (RCC.CR.read().HSERDY != 1) {}
},
.PLL => {
while (RCC.CR.read().PLLRDY != 1) {}
},
}

// Set the highest APBx dividers in order to ensure that we do not go through
// a non-spec phase whatever we decrease or increase HCLK.
RCC.CFGR.modify(.{
.PPRE1 = 0b111,
.PPRE2 = 0b111,
});

RCC.CFGR.modify(.{ .HPRE = getHPREdiv(ahb_div) });

// HACK: Ummmmmmm what?
const source_num = @as(u2, @intFromEnum(@as(SysConfig.Source, sys.source)));
RCC.CFGR.modify(.{ .SW = source_num });
while (RCC.CFGR.read().SWS != source_num) {}

RCC.CFGR.modify(.{
.PPRE1 = getAPPREdiv(apb1_div),
.PPRE2 = getAPPREdiv(apb2_div),
});

if (!hsi_enabled) {
RCC.CR.modify(.{ .HSION = 0 });
while (RCC.CR.read().HSIRDY != 0) {}
}
}
};

fn isValidPrescaler(comptime d: u32, comptime max: u16) bool {
return d <= max and std.math.isPowerOfTwo(d);
}

fn isValidPLLmul(comptime m: u32) bool {
return m >= 2 and m <= 16;
}

fn getHPREdiv(comptime d: u32) u4 {
return switch (d) {
1 => 0b0000,
2 => 0b1000,
4 => 0b1001,
8 => 0b1011,
16 => 0b1011,
64 => 0b1100,
128 => 0b1101,
256 => 0b1101,
512 => 0b1111,
else => @panic("Invalid prescaler"),
};
}

fn getAPPREdiv(comptime d: u32) u3 {
return switch (d) {
1 => 0b000,
2 => 0b100,
4 => 0b101,
8 => 0b110,
16 => 0b111,
else => @panic("Invalid prescaler"),
};
}

fn getPLLmul(comptime m: u32) u4 {
return @as(u4, m - 2);
}
28 changes: 8 additions & 20 deletions src/hals/STM32F103/gpio.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ const std = @import("std");
const assert = std.debug.assert;

const microzig = @import("microzig");
pub const peripherals = microzig.chip.peripherals;
const peripherals = microzig.chip.peripherals;

const GPIOA = peripherals.GPIOA;
const GPIOB = peripherals.GPIOB;
const GPIOC = peripherals.GPIOC;
const GPIOD = peripherals.GPIOD;
const GPIOE = peripherals.GPIOE;
const GPIOF = peripherals.GPIOF;
const GPIOG = peripherals.GPIOG;
const GPIO = microzig.chip.types.peripherals.GPIOA;

const GPIO = @TypeOf(GPIOA);
// NOTE: Apparently GPIO ports are not one next to the other
const GPIOoffset = @intFromPtr(GPIOB) - @intFromPtr(GPIOA);

const log = std.log.scoped(.gpio);

Expand Down Expand Up @@ -94,19 +91,10 @@ pub const Pin = packed struct(u8) {
return @as(u16, 1) << gpio.number;
}

// NOTE: Im not sure I like this
// We could probably calculate an offset from GPIOA?
pub fn get_port(gpio: Pin) GPIO {
return switch (gpio.port) {
0 => GPIOA,
1 => GPIOB,
2 => GPIOC,
3 => GPIOD,
4 => GPIOE,
5 => GPIOF,
6 => GPIOG,
7 => @panic("The STM32 only has ports 0..6 (A..G)"),
};
pub fn get_port(gpio: Pin) *volatile GPIO {
assert(gpio.port < 7);
const port: *volatile GPIO = @ptrFromInt(@intFromPtr(GPIOA) + (GPIOoffset * gpio.port));
return port;
}

pub inline fn set_mode(gpio: Pin, mode: Mode) void {
Expand Down
8 changes: 7 additions & 1 deletion src/hals/STM32F103/hal.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
pub const pins = @import("pins.zig");
pub const clocks = @import("clocks.zig");

pub fn init() void {}
const microzig = @import("microzig");
const FLASH = microzig.chip.peripherals.FLASH;

pub fn init() void {
FLASH.ACR.modify(.{ .PRFTBE = 1 });
}