├── .buildkite └── pipeline.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.adoc ├── build.zig ├── src ├── boards │ ├── STM3240G_EVAL.zig │ ├── STM32F3DISCOVERY.zig │ ├── STM32F429IDISCOVERY.zig │ └── STM32F4DISCOVERY.zig ├── chips │ ├── STM32F103.json │ ├── STM32F303.json │ ├── STM32F407.json │ ├── STM32F429.json │ ├── STM32L0x1.svd │ ├── STM32L0x2.svd │ └── STM32L0x3.svd └── hals │ ├── STM32F103.zig │ ├── STM32F103 │ ├── gpio.zig │ ├── hal.zig │ └── pins.zig │ ├── STM32F303.zig │ ├── STM32F407.zig │ └── STM32F429.zig └── test ├── programs └── minimal.zig └── stm32f103.robot /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - group: Build and Test 3 | steps: 4 | - command: zig build 5 | - label: 🔨 Test 6 | command: renode-test test/stm32f103.robot 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | zig-out 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/microzig"] 2 | path = deps/microzig 3 | url = https://github.com/ZigEmbeddedGroup/microzig.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Zig Embedded Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = WE MOVED BACK TO A MONOREPO HERE: https://github.com/ZigEmbeddedGroup/microzig 2 | 3 | == Future contributions go the main repository 4 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const microzig = @import("root").dependencies.imports.microzig; // HACK: Please import MicroZig always under the name `microzig`. Otherwise the RP2040 module will fail to be properly imported. 3 | 4 | fn root() []const u8 { 5 | return comptime (std.fs.path.dirname(@src().file) orelse "."); 6 | } 7 | const build_root = root(); 8 | 9 | const KiB = 1024; 10 | 11 | //////////////////////////////////////// 12 | // MicroZig Gen 2 Interface // 13 | //////////////////////////////////////// 14 | 15 | pub fn build(b: *std.Build) !void { 16 | _ = b; 17 | // Dummy func to make package manager happy 18 | } 19 | 20 | pub const chips = struct { 21 | pub const stm32f103x8 = .{ 22 | .preferred_format = .elf, 23 | .chip = .{ 24 | .name = "STM32F103", 25 | .cpu = .cortex_m3, 26 | .memory_regions = &.{ 27 | .{ .offset = 0x08000000, .length = 64 * KiB, .kind = .flash }, 28 | .{ .offset = 0x20000000, .length = 20 * KiB, .kind = .ram }, 29 | }, 30 | .register_definition = .{ 31 | .json = .{ .cwd_relative = build_root ++ "/src/chips/STM32F103.json" }, 32 | }, 33 | }, 34 | .hal = .{ 35 | .source_file = .{ .cwd_relative = build_root ++ "/src/hals/STM32F103/hal.zig" }, 36 | }, 37 | }; 38 | 39 | pub const stm32f303vc = .{ 40 | .preferred_format = .elf, 41 | .chip = .{ 42 | .name = "STM32F303", 43 | .cpu = .cortex_m4, 44 | .memory_regions = &.{ 45 | .{ .offset = 0x08000000, .length = 256 * KiB, .kind = .flash }, 46 | .{ .offset = 0x20000000, .length = 40 * KiB, .kind = .ram }, 47 | }, 48 | .register_definition = .{ 49 | .json = .{ .cwd_relative = build_root ++ "/src/chips/STM32F303.json" }, 50 | }, 51 | }, 52 | }; 53 | 54 | pub const stm32f407vg = .{ 55 | .preferred_format = .elf, 56 | .chip = .{ 57 | .name = "STM32F407", 58 | .cpu = .cortex_m4, 59 | .memory_regions = &.{ 60 | .{ .offset = 0x08000000, .length = 1024 * KiB, .kind = .flash }, 61 | .{ .offset = 0x20000000, .length = 128 * KiB, .kind = .ram }, 62 | .{ .offset = 0x10000000, .length = 64 * KiB, .kind = .ram }, // CCM RAM 63 | }, 64 | .register_definition = .{ 65 | .json = .{ .cwd_relative = build_root ++ "/src/chips/STM32F407.json" }, 66 | }, 67 | }, 68 | }; 69 | 70 | pub const stm32f429zit6u = .{ 71 | .preferred_format = .elf, 72 | .chip = .{ 73 | .name = "STM32F429", 74 | .cpu = .cortex_m4, 75 | .memory_regions = &.{ 76 | .{ .offset = 0x08000000, .length = 2048 * KiB, .kind = .flash }, 77 | .{ .offset = 0x20000000, .length = 192 * KiB, .kind = .ram }, 78 | .{ .offset = 0x10000000, .length = 64 * KiB, .kind = .ram }, // CCM RAM 79 | }, 80 | .register_definition = .{ 81 | .json = .{ .cwd_relative = build_root ++ "/src/chips/STM32F429.json" }, 82 | }, 83 | }, 84 | }; 85 | 86 | // All STM32L0x1 series MCUs differ only in memory size. So we create a comptime function 87 | // to generate all MCU variants as per https://www.st.com/en/microcontrollers-microprocessors/stm32l0x1.html 88 | fn stm32l0x1(comptime rom_size: u64, comptime ram_size: u64) microzig.Target { 89 | return microzig.Target{ 90 | .preferred_format = .elf, 91 | .chip = .{ 92 | .name = "STM32L0x1", 93 | .cpu = .cortex_m0plus, 94 | .memory_regions = &.{ 95 | .{ .offset = 0x08000000, .length = rom_size, .kind = .flash }, 96 | .{ .offset = 0x20000000, .length = ram_size, .kind = .ram }, 97 | }, 98 | .register_definition = .{ 99 | .svd = .{ .cwd_relative = build_root ++ "/src/chips/STM32L0x1.svd" }, 100 | }, 101 | }, 102 | }; 103 | } 104 | 105 | pub const stm32l011x3 = stm32l0x1(8 * KiB, 2 * KiB); 106 | 107 | pub const stm32l011x4 = stm32l0x1(16 * KiB, 2 * KiB); 108 | pub const stm32l021x4 = stm32l0x1(16 * KiB, 2 * KiB); 109 | pub const stm32l031x4 = stm32l0x1(16 * KiB, 8 * KiB); 110 | 111 | pub const stm32l031x6 = stm32l0x1(32 * KiB, 8 * KiB); 112 | pub const stm32l041x6 = stm32l0x1(32 * KiB, 8 * KiB); 113 | pub const stm32l051x6 = stm32l0x1(32 * KiB, 8 * KiB); 114 | 115 | pub const stm32l051x8 = stm32l0x1(64 * KiB, 8 * KiB); 116 | pub const stm32l071x8 = stm32l0x1(64 * KiB, 20 * KiB); 117 | 118 | pub const stm32l071xb = stm32l0x1(128 * KiB, 20 * KiB); 119 | pub const stm32l081cb = stm32l0x1(128 * KiB, 20 * KiB); 120 | 121 | pub const stm32l071xz = stm32l0x1(192 * KiB, 20 * KiB); 122 | pub const stm32l081xz = stm32l0x1(192 * KiB, 20 * KiB); 123 | 124 | // All STM32L0x2 series MCUs differ only in memory size. So we create a comptime function 125 | // to generate all MCU variants as per https://www.st.com/en/microcontrollers-microprocessors/stm32l0x2.html 126 | fn stm32l0x2(comptime rom_size: u64, comptime ram_size: u64) microzig.Target { 127 | return microzig.Target{ 128 | .preferred_format = .elf, 129 | .chip = .{ 130 | .name = "STM32L0x2", 131 | .cpu = .cortex_m0plus, 132 | .memory_regions = &.{ 133 | .{ .offset = 0x08000000, .length = rom_size, .kind = .flash }, 134 | .{ .offset = 0x20000000, .length = ram_size, .kind = .ram }, 135 | }, 136 | .register_definition = .{ 137 | .svd = .{ .cwd_relative = build_root ++ "/src/chips/STM32L0x2.svd" }, 138 | }, 139 | }, 140 | }; 141 | } 142 | 143 | pub const stm32l052x6 = stm32l0x2(32 * KiB, 8 * KiB); 144 | 145 | pub const stm32l052x8 = stm32l0x2(64 * KiB, 8 * KiB); 146 | pub const stm32l062x8 = stm32l0x2(64 * KiB, 8 * KiB); 147 | pub const stm32l072v8 = stm32l0x2(64 * KiB, 20 * KiB); 148 | 149 | pub const stm32l072xb = stm32l0x2(128 * KiB, 20 * KiB); 150 | pub const stm32l082xb = stm32l0x2(128 * KiB, 20 * KiB); 151 | 152 | pub const stm32l072xz = stm32l0x2(192 * KiB, 20 * KiB); 153 | pub const stm32l082xz = stm32l0x2(192 * KiB, 20 * KiB); 154 | 155 | // All STM32L0x2 series MCUs differ only in memory size. So we create a comptime function 156 | // to generate all MCU variants as per https://www.st.com/en/microcontrollers-microprocessors/stm32l0x3.html 157 | fn stm32l0x3(comptime rom_size: u64, comptime ram_size: u64) microzig.Target { 158 | return microzig.Target{ 159 | .preferred_format = .elf, 160 | .chip = .{ 161 | .name = "STM32L0x3", 162 | .cpu = .cortex_m0plus, 163 | .memory_regions = &.{ 164 | .{ .offset = 0x08000000, .length = rom_size, .kind = .flash }, 165 | .{ .offset = 0x20000000, .length = ram_size, .kind = .ram }, 166 | }, 167 | .register_definition = .{ 168 | .svd = .{ .cwd_relative = build_root ++ "/src/chips/STM32L0x3.svd" }, 169 | }, 170 | }, 171 | }; 172 | } 173 | 174 | pub const stm32l053x6 = stm32l0x2(32 * KiB, 8 * KiB); 175 | 176 | pub const stm32l053x8 = stm32l0x2(64 * KiB, 8 * KiB); 177 | pub const stm32l063x8 = stm32l0x2(64 * KiB, 8 * KiB); 178 | 179 | pub const stm32l073v8 = stm32l0x2(64 * KiB, 20 * KiB); 180 | pub const stm32l083v8 = stm32l0x2(64 * KiB, 20 * KiB); 181 | 182 | pub const stm32l073xb = stm32l0x2(128 * KiB, 20 * KiB); 183 | pub const stm32l083xb = stm32l0x2(128 * KiB, 20 * KiB); 184 | 185 | pub const stm32l073xz = stm32l0x2(192 * KiB, 20 * KiB); 186 | pub const stm32l083xz = stm32l0x2(192 * KiB, 20 * KiB); 187 | }; 188 | 189 | pub const boards = struct { 190 | pub const stm32f3discovery = .{ 191 | .preferred_format = .elf, 192 | .chip = chips.stm32f303vc.chip, 193 | .board = .{ 194 | .name = "STM32F3DISCOVERY", 195 | .source_file = .{ .path = build_root ++ "/src/boards/STM32F3DISCOVERY.zig" }, 196 | }, 197 | }; 198 | 199 | pub const stm32f4discovery = .{ 200 | .preferred_format = .elf, 201 | .chip = chips.stm32f407vg.chip, 202 | .board = .{ 203 | .name = "STM32F4DISCOVERY", 204 | .source_file = .{ .path = build_root ++ "/src/boards/STM32F4DISCOVERY.zig" }, 205 | }, 206 | }; 207 | 208 | pub const stm3240geval = .{ 209 | .preferred_format = .elf, 210 | .chip = chips.stm32f407vg.chip, 211 | .board = .{ 212 | .name = "STM3240G_EVAL", 213 | .source_file = .{ .path = build_root ++ "/src/boards/STM3240G_EVAL.zig" }, 214 | }, 215 | }; 216 | 217 | pub const stm32f429idiscovery = .{ 218 | .preferred_format = .elf, 219 | .chip = chips.stm32f429zit6u.chip, 220 | .board = .{ 221 | .name = "STM32F429IDISCOVERY", 222 | .source_file = .{ .path = build_root ++ "/src/boards/STM32F429IDISCOVERY.zig" }, 223 | }, 224 | }; 225 | }; 226 | 227 | // pub fn build(b: *std.build.Builder) void { 228 | // _ = b; 229 | // const optimize = b.standardOptimizeOption(.{}); 230 | // inline for (@typeInfo(boards).Struct.decls) |decl| { 231 | // if (!decl.is_pub) 232 | // continue; 233 | 234 | // const exe = microzig.addEmbeddedExecutable(b, .{ 235 | // .name = @field(boards, decl.name).name ++ ".minimal", 236 | // .source_file = .{ 237 | // .path = "test/programs/minimal.zig", 238 | // }, 239 | // .backing = .{ .board = @field(boards, decl.name) }, 240 | // .optimize = optimize, 241 | // }); 242 | // exe.installArtifact(b); 243 | // } 244 | 245 | // inline for (@typeInfo(chips).Struct.decls) |decl| { 246 | // if (!decl.is_pub) 247 | // continue; 248 | 249 | // const exe = microzig.addEmbeddedExecutable(b, .{ 250 | // .name = @field(chips, decl.name).name ++ ".minimal", 251 | // .source_file = .{ 252 | // .path = "test/programs/minimal.zig", 253 | // }, 254 | // .backing = .{ .chip = @field(chips, decl.name) }, 255 | // .optimize = optimize, 256 | // }); 257 | // exe.installArtifact(b); 258 | // } 259 | // } 260 | -------------------------------------------------------------------------------- /src/boards/STM3240G_EVAL.zig: -------------------------------------------------------------------------------- 1 | pub const pin_map = .{ 2 | // LD1 green 3 | .LD1 = "PG6", 4 | // LD2 orange 5 | .LD2 = "PG8", 6 | // LD3 red 7 | .LD3 = "PI9", 8 | // LD4 blue 9 | .LD4 = "PC7", 10 | }; 11 | -------------------------------------------------------------------------------- /src/boards/STM32F3DISCOVERY.zig: -------------------------------------------------------------------------------- 1 | pub const micro = @import("microzig"); 2 | 3 | pub const cpu_frequency = 8_000_000; 4 | 5 | pub const pin_map = .{ 6 | // circle of LEDs, connected to GPIOE bits 8..15 7 | 8 | // NW blue 9 | .LD4 = "PE8", 10 | // N red 11 | .LD3 = "PE9", 12 | // NE orange 13 | .LD5 = "PE10", 14 | // E green 15 | .LD7 = "PE11", 16 | // SE blue 17 | .LD9 = "PE12", 18 | // S red 19 | .LD10 = "PE13", 20 | // SW orange 21 | .LD8 = "PE14", 22 | // W green 23 | .LD6 = "PE15", 24 | }; 25 | 26 | pub fn debug_write(string: []const u8) void { 27 | const uart1 = micro.core.experimental.Uart(1, .{}).get_or_init(.{ 28 | .baud_rate = 9600, 29 | .data_bits = .eight, 30 | .parity = null, 31 | .stop_bits = .one, 32 | }) catch unreachable; 33 | 34 | const writer = uart1.writer(); 35 | _ = writer.write(string) catch unreachable; 36 | uart1.internal.txflush(); 37 | } 38 | -------------------------------------------------------------------------------- /src/boards/STM32F429IDISCOVERY.zig: -------------------------------------------------------------------------------- 1 | pub const cpu_frequency = 16_000_000; 2 | 3 | pub const pin_map = .{ 4 | // LEDs, connected to GPIOG bits 13, 14 5 | // green 6 | .LD3 = "PG13", 7 | // red 8 | .LD4 = "PG14", 9 | 10 | // User button 11 | .B1 = "PA0", 12 | }; 13 | -------------------------------------------------------------------------------- /src/boards/STM32F4DISCOVERY.zig: -------------------------------------------------------------------------------- 1 | pub const pin_map = .{ 2 | // LED cross, connected to GPIOD bits 12..15 3 | // N orange 4 | .LD3 = "PD13", 5 | // E red 6 | .LD5 = "PD14", 7 | // S blue 8 | .LD6 = "PD15", 9 | // W green 10 | .LD4 = "PD12", 11 | 12 | // User button 13 | .B2 = "PA0", 14 | }; 15 | -------------------------------------------------------------------------------- /src/hals/STM32F103.zig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZigEmbeddedGroup/stmicro-stm32/1ed4bf056c0057a414432711b195070040aba374/src/hals/STM32F103.zig -------------------------------------------------------------------------------- /src/hals/STM32F103/gpio.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | 4 | const microzig = @import("microzig"); 5 | pub const peripherals = microzig.chip.peripherals; 6 | 7 | const GPIOA = peripherals.GPIOA; 8 | const GPIOB = peripherals.GPIOB; 9 | const GPIOC = peripherals.GPIOC; 10 | const GPIOD = peripherals.GPIOD; 11 | const GPIOE = peripherals.GPIOE; 12 | const GPIOF = peripherals.GPIOF; 13 | const GPIOG = peripherals.GPIOG; 14 | 15 | const GPIO = @TypeOf(GPIOA); 16 | 17 | const log = std.log.scoped(.gpio); 18 | 19 | pub const Function = enum {}; 20 | 21 | pub const Mode = union(enum) { 22 | input: InputMode, 23 | output: OutputMode, 24 | }; 25 | 26 | pub const InputMode = enum(u2) { 27 | analog, 28 | floating, 29 | pull, 30 | reserved, 31 | }; 32 | 33 | pub const OutputMode = enum(u2) { 34 | general_purpose_push_pull, 35 | general_purpose_open_drain, 36 | alternate_function_push_pull, 37 | alternate_function_open_drain, 38 | }; 39 | 40 | pub const Speed = enum(u2) { 41 | reserved, 42 | max_10MHz, 43 | max_2MHz, 44 | max_50MHz, 45 | }; 46 | 47 | pub const IrqLevel = enum(u2) { 48 | low, 49 | high, 50 | fall, 51 | rise, 52 | }; 53 | 54 | pub const IrqCallback = fn (gpio: u32, events: u32) callconv(.C) void; 55 | 56 | pub const Enabled = enum { 57 | disabled, 58 | enabled, 59 | }; 60 | 61 | pub const Pull = enum { 62 | up, 63 | down, 64 | }; 65 | 66 | // NOTE: With this current setup, every time we want to do anythting we go through a switch 67 | // Do we want this? 68 | pub const Pin = packed struct(u8) { 69 | number: u4, 70 | port: u3, 71 | padding: u1, 72 | 73 | pub fn init(port: u3, number: u4) Pin { 74 | return Pin{ 75 | .number = number, 76 | .port = port, 77 | .padding = 0, 78 | }; 79 | } 80 | inline fn write_pin_config(gpio: Pin, config: u32) void { 81 | const port = gpio.get_port(); 82 | if (gpio.number <= 7) { 83 | const offset = @as(u5, gpio.number) << 2; 84 | port.CRL.raw &= ~(@as(u32, 0b1111) << offset); 85 | port.CRL.raw |= config << offset; 86 | } else { 87 | const offset = (@as(u5, gpio.number) - 8) << 2; 88 | port.CRH.raw &= ~(@as(u32, 0b1111) << offset); 89 | port.CRH.raw |= config << offset; 90 | } 91 | } 92 | 93 | fn mask(gpio: Pin) u16 { 94 | return @as(u16, 1) << gpio.number; 95 | } 96 | 97 | // NOTE: Im not sure I like this 98 | // We could probably calculate an offset from GPIOA? 99 | pub fn get_port(gpio: Pin) GPIO { 100 | return switch (gpio.port) { 101 | 0 => GPIOA, 102 | 1 => GPIOB, 103 | 2 => GPIOC, 104 | 3 => GPIOD, 105 | 4 => GPIOE, 106 | 5 => GPIOF, 107 | 6 => GPIOG, 108 | 7 => @panic("The STM32 only has ports 0..6 (A..G)"), 109 | }; 110 | } 111 | 112 | pub inline fn set_mode(gpio: Pin, mode: Mode) void { 113 | switch (mode) { 114 | .input => |in| gpio.set_input_mode(in), 115 | .output => |out| gpio.set_output_mode(out, .max_2MHz), 116 | } 117 | } 118 | 119 | pub inline fn set_input_mode(gpio: Pin, mode: InputMode) void { 120 | const m_mode = @as(u32, @intFromEnum(mode)); 121 | const config: u32 = m_mode << 2; 122 | gpio.write_pin_config(config); 123 | } 124 | 125 | pub inline fn set_output_mode(gpio: Pin, mode: OutputMode, speed: Speed) void { 126 | const s_speed = @as(u32, @intFromEnum(speed)); 127 | const m_mode = @as(u32, @intFromEnum(mode)); 128 | const config: u32 = s_speed + (m_mode << 2); 129 | gpio.write_pin_config(config); 130 | } 131 | 132 | pub inline fn set_pull(gpio: Pin, pull: Pull) void { 133 | var port = gpio.get_port(); 134 | switch (pull) { 135 | .up => port.BSRR.raw = gpio.mask(), 136 | .down => port.BRR.raw = gpio.mask(), 137 | } 138 | } 139 | 140 | pub inline fn read(gpio: Pin) u1 { 141 | const port = gpio.get_port(); 142 | return if (port.IDR.raw & gpio.mask() != 0) 143 | 1 144 | else 145 | 0; 146 | } 147 | 148 | pub inline fn put(gpio: Pin, value: u1) void { 149 | var port = gpio.get_port(); 150 | switch (value) { 151 | 0 => port.BSRR.raw = gpio.mask() << 16, 152 | 1 => port.BSRR.raw = gpio.mask(), 153 | } 154 | } 155 | 156 | pub inline fn toggle(gpio: Pin) void { 157 | var port = gpio.get_port(); 158 | port.ODR.raw ^= gpio.mask(); 159 | } 160 | }; 161 | -------------------------------------------------------------------------------- /src/hals/STM32F103/hal.zig: -------------------------------------------------------------------------------- 1 | pub const pins = @import("pins.zig"); 2 | 3 | pub fn init() void {} 4 | -------------------------------------------------------------------------------- /src/hals/STM32F103/pins.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | const comptimePrint = std.fmt.comptimePrint; 4 | const StructField = std.builtin.Type.StructField; 5 | 6 | const microzig = @import("microzig"); 7 | 8 | const RCC = microzig.chip.peripherals.RCC; 9 | 10 | const gpio = @import("gpio.zig"); 11 | // const pwm = @import("pwm.zig"); 12 | // const adc = @import("adc.zig"); 13 | // const resets = @import("resets.zig"); 14 | 15 | pub const Pin = enum { 16 | PIN0, 17 | PIN1, 18 | PIN2, 19 | PIN3, 20 | PIN4, 21 | PIN5, 22 | PIN6, 23 | PIN7, 24 | PIN8, 25 | PIN9, 26 | PIN10, 27 | PIN11, 28 | PIN12, 29 | PIN13, 30 | PIN14, 31 | PIN15, 32 | pub const Configuration = struct { 33 | name: ?[]const u8 = null, 34 | // function: Function = .SIO, 35 | mode: ?gpio.Mode = null, 36 | speed: ?gpio.Speed = null, 37 | pull: ?gpio.Pull = null, 38 | // input/output enable 39 | // schmitt trigger 40 | // hysteresis 41 | 42 | pub fn get_mode(comptime config: Configuration) gpio.Mode { 43 | return if (config.mode) |mode| 44 | mode 45 | // else if (comptime config.function.is_pwm()) 46 | // .out 47 | // else if (comptime config.function.is_uart_tx()) 48 | // .out 49 | // else if (comptime config.function.is_uart_rx()) 50 | // .in 51 | // else if (comptime config.function.is_adc()) 52 | // .in 53 | else 54 | @panic("TODO"); 55 | } 56 | }; 57 | }; 58 | 59 | pub fn GPIO(comptime port: u3, comptime num: u4, comptime mode: gpio.Mode) type { 60 | return switch (mode) { 61 | .input => struct { 62 | const pin = gpio.Pin.init(port, num); 63 | 64 | pub inline fn read(self: @This()) u1 { 65 | _ = self; 66 | return pin.read(); 67 | } 68 | }, 69 | .output => struct { 70 | const pin = gpio.Pin.init(port, num); 71 | 72 | pub inline fn put(self: @This(), value: u1) void { 73 | _ = self; 74 | pin.put(value); 75 | } 76 | 77 | pub inline fn toggle(self: @This()) void { 78 | _ = self; 79 | pin.toggle(); 80 | } 81 | }, 82 | }; 83 | } 84 | 85 | pub fn Pins(comptime config: GlobalConfiguration) type { 86 | comptime { 87 | var fields: []const StructField = &.{}; 88 | for (@typeInfo(GlobalConfiguration).Struct.fields) |port_field| { 89 | if (@field(config, port_field.name)) |port_config| { 90 | for (@typeInfo(Port.Configuration).Struct.fields) |field| { 91 | if (@field(port_config, field.name)) |pin_config| { 92 | var pin_field = StructField{ 93 | .is_comptime = false, 94 | .default_value = null, 95 | 96 | // initialized below: 97 | .name = undefined, 98 | .type = undefined, 99 | .alignment = undefined, 100 | }; 101 | 102 | pin_field.name = pin_config.name orelse field.name; 103 | pin_field.type = GPIO(@intFromEnum(@field(Port, port_field.name)), @intFromEnum(@field(Pin, field.name)), pin_config.mode orelse .{ .input = .{.floating} }); 104 | pin_field.alignment = @alignOf(field.type); 105 | 106 | fields = fields ++ &[_]StructField{pin_field}; 107 | } 108 | } 109 | } 110 | } 111 | 112 | return @Type(.{ 113 | .Struct = .{ 114 | .layout = .Auto, 115 | .is_tuple = false, 116 | .fields = fields, 117 | .decls = &.{}, 118 | }, 119 | }); 120 | } 121 | } 122 | 123 | pub const Port = enum { 124 | GPIOA, 125 | GPIOB, 126 | GPIOC, 127 | GPIOD, 128 | GPIOE, 129 | GPIOF, 130 | GPIOG, 131 | pub const Configuration = struct { 132 | PIN0: ?Pin.Configuration = null, 133 | PIN1: ?Pin.Configuration = null, 134 | PIN2: ?Pin.Configuration = null, 135 | PIN3: ?Pin.Configuration = null, 136 | PIN4: ?Pin.Configuration = null, 137 | PIN5: ?Pin.Configuration = null, 138 | PIN6: ?Pin.Configuration = null, 139 | PIN7: ?Pin.Configuration = null, 140 | PIN8: ?Pin.Configuration = null, 141 | PIN9: ?Pin.Configuration = null, 142 | PIN10: ?Pin.Configuration = null, 143 | PIN11: ?Pin.Configuration = null, 144 | PIN12: ?Pin.Configuration = null, 145 | PIN13: ?Pin.Configuration = null, 146 | PIN14: ?Pin.Configuration = null, 147 | PIN15: ?Pin.Configuration = null, 148 | 149 | comptime { 150 | const pin_field_count = @typeInfo(Pin).Enum.fields.len; 151 | const config_field_count = @typeInfo(Configuration).Struct.fields.len; 152 | if (pin_field_count != config_field_count) 153 | @compileError(comptimePrint("{} {}", .{ pin_field_count, config_field_count })); 154 | } 155 | }; 156 | }; 157 | 158 | pub const GlobalConfiguration = struct { 159 | GPIOA: ?Port.Configuration = null, 160 | GPIOB: ?Port.Configuration = null, 161 | GPIOC: ?Port.Configuration = null, 162 | GPIOD: ?Port.Configuration = null, 163 | GPIOE: ?Port.Configuration = null, 164 | GPIOF: ?Port.Configuration = null, 165 | GPIOG: ?Port.Configuration = null, 166 | 167 | comptime { 168 | const port_field_count = @typeInfo(Port).Enum.fields.len; 169 | const config_field_count = @typeInfo(GlobalConfiguration).Struct.fields.len; 170 | if (port_field_count != config_field_count) 171 | @compileError(comptimePrint("{} {}", .{ port_field_count, config_field_count })); 172 | } 173 | 174 | pub fn apply(comptime config: GlobalConfiguration) Pins(config) { 175 | inline for (@typeInfo(GlobalConfiguration).Struct.fields) |port_field| { 176 | if (@field(config, port_field.name)) |port_config| { 177 | comptime var input_gpios: u16 = 0; 178 | comptime var output_gpios: u16 = 0; 179 | comptime { 180 | inline for (@typeInfo(Port.Configuration).Struct.fields) |field| 181 | if (@field(port_config, field.name)) |pin_config| { 182 | const gpio_num = @intFromEnum(@field(Pin, field.name)); 183 | 184 | switch (pin_config.get_mode()) { 185 | .input => input_gpios |= 1 << gpio_num, 186 | .output => output_gpios |= 1 << gpio_num, 187 | } 188 | }; 189 | } 190 | 191 | // TODO: ensure only one instance of an input function exists 192 | const used_gpios = comptime input_gpios | output_gpios; 193 | 194 | if (used_gpios != 0) { 195 | const offset = @intFromEnum(@field(Port, port_field.name)) + 2; 196 | const bit = @as(u32, 1 << offset); 197 | RCC.APB2ENR.raw |= bit; 198 | // Delay after setting 199 | _ = RCC.APB2ENR.raw & bit; 200 | } 201 | 202 | inline for (@typeInfo(Port.Configuration).Struct.fields) |field| { 203 | if (@field(port_config, field.name)) |pin_config| { 204 | var pin = gpio.Pin.init(@intFromEnum(@field(Port, port_field.name)), @intFromEnum(@field(Pin, field.name))); 205 | pin.set_mode(pin_config.mode.?); 206 | } 207 | } 208 | 209 | if (input_gpios != 0) { 210 | inline for (@typeInfo(Port.Configuration).Struct.fields) |field| 211 | if (@field(port_config, field.name)) |pin_config| { 212 | var pin = gpio.Pin.init(@intFromEnum(@field(Port, port_field.name)), @intFromEnum(@field(Pin, field.name))); 213 | const pull = pin_config.pull orelse continue; 214 | if (comptime pin_config.get_mode() != .input) 215 | @compileError("Only input pins can have pull up/down enabled"); 216 | 217 | pin.set_pull(pull); 218 | }; 219 | } 220 | } 221 | } 222 | 223 | // fields in the Pins(config) type should be zero sized, so we just 224 | // default build them all (wasn't sure how to do that cleanly in 225 | // `Pins()` 226 | var ret: Pins(config) = undefined; 227 | inline for (@typeInfo(Pins(config)).Struct.fields) |field| { 228 | if (field.default_value) |default_value| { 229 | @field(ret, field.name) = @as(*const field.field_type, @ptrCast(default_value)).*; 230 | } else { 231 | @field(ret, field.name) = .{}; 232 | } 233 | } 234 | return ret; 235 | // validate selected function 236 | } 237 | }; 238 | -------------------------------------------------------------------------------- /src/hals/STM32F303.zig: -------------------------------------------------------------------------------- 1 | //! For now we keep all clock settings on the chip defaults. 2 | //! This code currently assumes the STM32F303xB / STM32F303xC clock configuration. 3 | //! TODO: Do something useful for other STM32f30x chips. 4 | //! 5 | //! Specifically, TIM6 is running on an 8 MHz clock, 6 | //! HSI = 8 MHz is the SYSCLK after reset 7 | //! default AHB prescaler = /1 (= values 0..7): 8 | //! 9 | //! ``` 10 | //! RCC.CFGR.modify(.{ .HPRE = 0 }); 11 | //! ``` 12 | //! 13 | //! so also HCLK = 8 MHz. 14 | //! And with the default APB1 prescaler = /2: 15 | //! 16 | //! ``` 17 | //! RCC.CFGR.modify(.{ .PPRE1 = 4 }); 18 | //! ``` 19 | //! 20 | //! results in PCLK1, 21 | //! and the resulting implicit factor *2 for TIM2/3/4/6/7 22 | //! makes TIM6 run at 8MHz/2*2 = 8 MHz. 23 | //! 24 | //! The above default configuration makes U(S)ART2..5 25 | //! (which use PCLK1 without that implicit *2 factor) 26 | //! run at 4 MHz by default. 27 | //! 28 | //! USART1 uses PCLK2, which uses the APB2 prescaler on HCLK, 29 | //! default APB2 prescaler = /1: 30 | //! 31 | //! ``` 32 | //! RCC.CFGR.modify(.{ .PPRE2 = 0 }); 33 | //! ``` 34 | //! 35 | //! and therefore USART1 runs on 8 MHz. 36 | 37 | const std = @import("std"); 38 | const runtime_safety = std.debug.runtime_safety; 39 | 40 | const micro = @import("microzig"); 41 | const SPI1 = micro.peripherals.SPI1; 42 | const RCC = micro.peripherals.RCC; 43 | const USART1 = micro.peripherals.USART1; 44 | const GPIOA = micro.peripherals.GPIOA; 45 | const GPIOB = micro.peripherals.GPIOB; 46 | const GPIOC = micro.peripherals.GPIOC; 47 | const I2C1 = micro.peripherals.I2C1; 48 | 49 | pub const cpu = @import("cpu"); 50 | 51 | pub const clock = struct { 52 | pub const Domain = enum { 53 | cpu, 54 | ahb, 55 | apb1, 56 | apb2, 57 | }; 58 | }; 59 | 60 | // Default clock frequencies after reset, see top comment for calculation 61 | pub const clock_frequencies = .{ 62 | .cpu = 8_000_000, 63 | .ahb = 8_000_000, 64 | .apb1 = 8_000_000, 65 | .apb2 = 8_000_000, 66 | }; 67 | 68 | pub fn parse_pin(comptime spec: []const u8) type { 69 | const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme."; 70 | 71 | if (spec[0] != 'P') 72 | @compileError(invalid_format_msg); 73 | if (spec[1] < 'A' or spec[1] > 'H') 74 | @compileError(invalid_format_msg); 75 | 76 | const pin_number: comptime_int = std.fmt.parseInt(u4, spec[2..], 10) catch @compileError(invalid_format_msg); 77 | 78 | return struct { 79 | /// 'A'...'H' 80 | const gpio_port_name = spec[1..2]; 81 | const gpio_port = @field(micro.peripherals, "GPIO" ++ gpio_port_name); 82 | const suffix = std.fmt.comptimePrint("{d}", .{pin_number}); 83 | }; 84 | } 85 | 86 | fn set_reg_field(reg: anytype, comptime field_name: anytype, value: anytype) void { 87 | var temp = reg.read(); 88 | @field(temp, field_name) = value; 89 | reg.write(temp); 90 | } 91 | 92 | pub const gpio = struct { 93 | pub fn set_output(comptime pin: type) void { 94 | set_reg_field(RCC.AHBENR, "IOP" ++ pin.gpio_port_name ++ "EN", 1); 95 | set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b01); 96 | } 97 | 98 | pub fn set_input(comptime pin: type) void { 99 | set_reg_field(RCC.AHBENR, "IOP" ++ pin.gpio_port_name ++ "EN", 1); 100 | set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b00); 101 | } 102 | 103 | pub fn read(comptime pin: type) micro.gpio.State { 104 | const idr_reg = pin.gpio_port.IDR; 105 | const reg_value = @field(idr_reg.read(), "IDR" ++ pin.suffix); // TODO extract to getRegField()? 106 | return @as(micro.gpio.State, @enumFromInt(reg_value)); 107 | } 108 | 109 | pub fn write(comptime pin: type, state: micro.gpio.State) void { 110 | switch (state) { 111 | .low => set_reg_field(pin.gpio_port.BRR, "BR" ++ pin.suffix, 1), 112 | .high => set_reg_field(pin.gpio_port.BSRR, "BS" ++ pin.suffix, 1), 113 | } 114 | } 115 | }; 116 | 117 | pub const uart = struct { 118 | pub const DataBits = enum(u4) { 119 | seven = 7, 120 | eight = 8, 121 | }; 122 | 123 | /// uses the values of USART_CR2.STOP 124 | pub const StopBits = enum(u2) { 125 | one = 0b00, 126 | half = 0b01, 127 | two = 0b10, 128 | one_and_half = 0b11, 129 | }; 130 | 131 | /// uses the values of USART_CR1.PS 132 | pub const Parity = enum(u1) { 133 | even = 0, 134 | odd = 1, 135 | }; 136 | }; 137 | 138 | pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type { 139 | if (!(index == 1)) @compileError("TODO: only USART1 is currently supported"); 140 | if (pins.tx != null or pins.rx != null) 141 | @compileError("TODO: custom pins are not currently supported"); 142 | 143 | return struct { 144 | parity_read_mask: u8, 145 | 146 | const Self = @This(); 147 | 148 | pub fn init(config: micro.uart.Config) !Self { 149 | // The following must all be written when the USART is disabled (UE=0). 150 | if (USART1.CR1.read().UE == 1) 151 | @panic("Trying to initialize USART1 while it is already enabled"); 152 | // LATER: Alternatively, set UE=0 at this point? Then wait for something? 153 | // Or add a destroy() function which disables the USART? 154 | 155 | // enable the USART1 clock 156 | RCC.APB2ENR.modify(.{ .USART1EN = 1 }); 157 | // enable GPIOC clock 158 | RCC.AHBENR.modify(.{ .IOPCEN = 1 }); 159 | // set PC4+PC5 to alternate function 7, USART1_TX + USART1_RX 160 | GPIOC.MODER.modify(.{ .MODER4 = 0b10, .MODER5 = 0b10 }); 161 | GPIOC.AFRL.modify(.{ .AFRL4 = 7, .AFRL5 = 7 }); 162 | 163 | // clear USART1 configuration to its default 164 | USART1.CR1.raw = 0; 165 | USART1.CR2.raw = 0; 166 | USART1.CR3.raw = 0; 167 | 168 | // set word length 169 | // Per the reference manual, M[1:0] means 170 | // - 00: 8 bits (7 data + 1 parity, or 8 data), probably the chip default 171 | // - 01: 9 bits (8 data + 1 parity) 172 | // - 10: 7 bits (7 data) 173 | // So M1==1 means "7-bit mode" (in which 174 | // "the Smartcard mode, LIN master mode and Auto baud rate [...] are not supported"); 175 | // and M0==1 means 'the 9th bit (not the 8th bit) is the parity bit'. 176 | const m1: u1 = if (config.data_bits == .seven and config.parity == null) 1 else 0; 177 | const m0: u1 = if (config.data_bits == .eight and config.parity != null) 1 else 0; 178 | // Note that .padding0 = bit 28 = .M1 (.svd file bug?), and .M == .M0. 179 | USART1.CR1.modify(.{ .padding0 = m1, .M = m0 }); 180 | 181 | // set parity 182 | if (config.parity) |parity| { 183 | USART1.CR1.modify(.{ .PCE = 1, .PS = @intFromEnum(parity) }); 184 | } else USART1.CR1.modify(.{ .PCE = 0 }); // no parity, probably the chip default 185 | 186 | // set number of stop bits 187 | USART1.CR2.modify(.{ .STOP = @intFromEnum(config.stop_bits) }); 188 | 189 | // set the baud rate 190 | // TODO: Do not use the _board_'s frequency, but the _U(S)ARTx_ frequency 191 | // from the chip, which can be affected by how the board configures the chip. 192 | // In our case, these are accidentally the same at chip reset, 193 | // if the board doesn't configure e.g. an HSE external crystal. 194 | // TODO: Do some checks to see if the baud rate is too high (or perhaps too low) 195 | // TODO: Do a rounding div, instead of a truncating div? 196 | const usartdiv = @as(u16, @intCast(@divTrunc(micro.clock.get().apb1, config.baud_rate))); 197 | USART1.BRR.raw = usartdiv; 198 | // Above, ignore the BRR struct fields DIV_Mantissa and DIV_Fraction, 199 | // those seem to be for another chipset; .svd file bug? 200 | // TODO: We assume the default OVER8=0 configuration above. 201 | 202 | // enable USART1, and its transmitter and receiver 203 | USART1.CR1.modify(.{ .UE = 1 }); 204 | USART1.CR1.modify(.{ .TE = 1 }); 205 | USART1.CR1.modify(.{ .RE = 1 }); 206 | 207 | // For code simplicity, at cost of one or more register reads, 208 | // we read back the actual configuration from the registers, 209 | // instead of using the `config` values. 210 | return read_from_registers(); 211 | } 212 | 213 | pub fn get_or_init(config: micro.uart.Config) !Self { 214 | if (USART1.CR1.read().UE == 1) { 215 | // UART1 already enabled, don't reinitialize and disturb things; 216 | // instead read and use the actual configuration. 217 | return read_from_registers(); 218 | } else return init(config); 219 | } 220 | 221 | fn read_from_registers() Self { 222 | const cr1 = USART1.CR1.read(); 223 | // As documented in `init()`, M0==1 means 'the 9th bit (not the 8th bit) is the parity bit'. 224 | // So we always mask away the 9th bit, and if parity is enabled and it is in the 8th bit, 225 | // then we also mask away the 8th bit. 226 | return Self{ .parity_read_mask = if (cr1.PCE == 1 and cr1.M == 0) 0x7F else 0xFF }; 227 | } 228 | 229 | pub fn can_write(self: Self) bool { 230 | _ = self; 231 | return switch (USART1.ISR.read().TXE) { 232 | 1 => true, 233 | 0 => false, 234 | }; 235 | } 236 | 237 | pub fn tx(self: Self, ch: u8) void { 238 | while (!self.can_write()) {} // Wait for Previous transmission 239 | USART1.TDR.modify(ch); 240 | } 241 | 242 | pub fn txflush(_: Self) void { 243 | while (USART1.ISR.read().TC == 0) {} 244 | } 245 | 246 | pub fn can_read(self: Self) bool { 247 | _ = self; 248 | return switch (USART1.ISR.read().RXNE) { 249 | 1 => true, 250 | 0 => false, 251 | }; 252 | } 253 | 254 | pub fn rx(self: Self) u8 { 255 | while (!self.can_read()) {} // Wait till the data is received 256 | const data_with_parity_bit: u9 = USART1.RDR.read().RDR; 257 | return @as(u8, @intCast(data_with_parity_bit & self.parity_read_mask)); 258 | } 259 | }; 260 | } 261 | 262 | const enable_stm32f303_debug = false; 263 | 264 | fn debug_print(comptime format: []const u8, args: anytype) void { 265 | if (enable_stm32f303_debug) { 266 | micro.debug.writer().print(format, args) catch {}; 267 | } 268 | } 269 | 270 | /// This implementation does not use AUTOEND=1 271 | pub fn I2CController(comptime index: usize, comptime pins: micro.i2c.Pins) type { 272 | if (!(index == 1)) @compileError("TODO: only I2C1 is currently supported"); 273 | if (pins.scl != null or pins.sda != null) 274 | @compileError("TODO: custom pins are not currently supported"); 275 | 276 | return struct { 277 | const Self = @This(); 278 | 279 | pub fn init(config: micro.i2c.Config) !Self { 280 | // CONFIGURE I2C1 281 | // connected to APB1, MCU pins PB6 + PB7 = I2C1_SCL + I2C1_SDA, 282 | // if GPIO port B is configured for alternate function 4 for these PB pins. 283 | 284 | // 1. Enable the I2C CLOCK and GPIO CLOCK 285 | RCC.APB1ENR.modify(.{ .I2C1EN = 1 }); 286 | RCC.AHBENR.modify(.{ .IOPBEN = 1 }); 287 | debug_print("I2C1 configuration step 1 complete\r\n", .{}); 288 | // 2. Configure the I2C PINs for ALternate Functions 289 | // a) Select Alternate Function in MODER Register 290 | GPIOB.MODER.modify(.{ .MODER6 = 0b10, .MODER7 = 0b10 }); 291 | // b) Select Open Drain Output 292 | GPIOB.OTYPER.modify(.{ .OT6 = 1, .OT7 = 1 }); 293 | // c) Select High SPEED for the PINs 294 | GPIOB.OSPEEDR.modify(.{ .OSPEEDR6 = 0b11, .OSPEEDR7 = 0b11 }); 295 | // d) Select Pull-up for both the Pins 296 | GPIOB.PUPDR.modify(.{ .PUPDR6 = 0b01, .PUPDR7 = 0b01 }); 297 | // e) Configure the Alternate Function in AFR Register 298 | GPIOB.AFRL.modify(.{ .AFRL6 = 4, .AFRL7 = 4 }); 299 | debug_print("I2C1 configuration step 2 complete\r\n", .{}); 300 | 301 | // 3. Reset the I2C 302 | I2C1.CR1.modify(.{ .PE = 0 }); 303 | while (I2C1.CR1.read().PE == 1) {} 304 | // DO NOT RCC.APB1RSTR.modify(.{ .I2C1RST = 1 }); 305 | debug_print("I2C1 configuration step 3 complete\r\n", .{}); 306 | 307 | // 4-6. Configure I2C1 timing, based on 8 MHz I2C clock, run at 100 kHz 308 | // (Not using https://controllerstech.com/stm32-i2c-configuration-using-registers/ 309 | // but copying an example from the reference manual, RM0316 section 28.4.9.) 310 | if (config.target_speed != 100_000) @panic("TODO: Support speeds other than 100 kHz"); 311 | I2C1.TIMINGR.modify(.{ 312 | .PRESC = 1, 313 | .SCLL = 0x13, 314 | .SCLH = 0xF, 315 | .SDADEL = 0x2, 316 | .SCLDEL = 0x4, 317 | }); 318 | debug_print("I2C1 configuration steps 4-6 complete\r\n", .{}); 319 | 320 | // 7. Program the I2C_CR1 register to enable the peripheral 321 | I2C1.CR1.modify(.{ .PE = 1 }); 322 | debug_print("I2C1 configuration step 7 complete\r\n", .{}); 323 | 324 | return Self{}; 325 | } 326 | 327 | pub const WriteState = struct { 328 | address: u7, 329 | buffer: [255]u8 = undefined, 330 | buffer_size: u8 = 0, 331 | 332 | pub fn start(address: u7) !WriteState { 333 | return WriteState{ .address = address }; 334 | } 335 | 336 | pub fn write_all(self: *WriteState, bytes: []const u8) !void { 337 | debug_print("I2C1 writeAll() with {d} byte(s); buffer={any}\r\n", .{ bytes.len, self.buffer[0..self.buffer_size] }); 338 | 339 | std.debug.assert(self.buffer_size < 255); 340 | for (bytes) |b| { 341 | self.buffer[self.buffer_size] = b; 342 | self.buffer_size += 1; 343 | if (self.buffer_size == 255) { 344 | try self.send_buffer(1); 345 | } 346 | } 347 | } 348 | 349 | fn send_buffer(self: *WriteState, reload: u1) !void { 350 | debug_print("I2C1 sendBuffer() with {d} byte(s); RELOAD={d}; buffer={any}\r\n", .{ self.buffer_size, reload, self.buffer[0..self.buffer_size] }); 351 | if (self.buffer_size == 0) @panic("write of 0 bytes not supported"); 352 | 353 | std.debug.assert(reload == 0 or self.buffer_size == 255); // see TODOs below 354 | 355 | // As master, initiate write from address, 7 bit address 356 | I2C1.CR2.modify(.{ 357 | .ADD10 = 0, 358 | .SADD1 = self.address, 359 | .RD_WRN = 0, // write 360 | .NBYTES = self.buffer_size, 361 | .RELOAD = reload, 362 | }); 363 | if (reload == 0) { 364 | I2C1.CR2.modify(.{ .START = 1 }); 365 | } else { 366 | // TODO: The RELOAD=1 path is untested but doesn't seem to work yet, 367 | // even though we make sure that we set NBYTES=255 per the docs. 368 | } 369 | for (self.buffer[0..self.buffer_size]) |b| { 370 | // wait for empty transmit buffer 371 | while (I2C1.ISR.read().TXE == 0) { 372 | debug_print("I2C1 waiting for ready to send (TXE=0)\r\n", .{}); 373 | } 374 | debug_print("I2C1 ready to send (TXE=1)\r\n", .{}); 375 | // Write data byte 376 | I2C1.TXDR.modify(.{ .TXDATA = b }); 377 | } 378 | self.buffer_size = 0; 379 | debug_print("I2C1 data written\r\n", .{}); 380 | if (reload == 1) { 381 | // TODO: The RELOAD=1 path is untested but doesn't seem to work yet, 382 | // the following loop never seems to finish. 383 | while (I2C1.ISR.read().TCR == 0) { 384 | debug_print("I2C1 waiting transmit complete (TCR=0)\r\n", .{}); 385 | } 386 | debug_print("I2C1 transmit complete (TCR=1)\r\n", .{}); 387 | } else { 388 | while (I2C1.ISR.read().TC == 0) { 389 | debug_print("I2C1 waiting for transmit complete (TC=0)\r\n", .{}); 390 | } 391 | debug_print("I2C1 transmit complete (TC=1)\r\n", .{}); 392 | } 393 | } 394 | 395 | pub fn stop(self: *WriteState) !void { 396 | try self.send_buffer(0); 397 | // Communication STOP 398 | debug_print("I2C1 STOPping\r\n", .{}); 399 | I2C1.CR2.modify(.{ .STOP = 1 }); 400 | while (I2C1.ISR.read().BUSY == 1) {} 401 | debug_print("I2C1 STOPped\r\n", .{}); 402 | } 403 | 404 | pub fn restart_read(self: *WriteState) !ReadState { 405 | try self.send_buffer(0); 406 | return ReadState{ .address = self.address }; 407 | } 408 | pub fn restart_write(self: *WriteState) !WriteState { 409 | try self.send_buffer(0); 410 | return WriteState{ .address = self.address }; 411 | } 412 | }; 413 | 414 | pub const ReadState = struct { 415 | address: u7, 416 | read_allowed: if (runtime_safety) bool else void = if (runtime_safety) true else {}, 417 | 418 | pub fn start(address: u7) !ReadState { 419 | return ReadState{ .address = address }; 420 | } 421 | 422 | /// Fails with ReadError if incorrect number of bytes is received. 423 | pub fn read_no_eof(self: *ReadState, buffer: []u8) !void { 424 | if (runtime_safety and !self.read_allowed) @panic("second read call not allowed"); 425 | std.debug.assert(buffer.len < 256); // TODO: use RELOAD to read more data 426 | 427 | // As master, initiate read from accelerometer, 7 bit address 428 | I2C1.CR2.modify(.{ 429 | .ADD10 = 0, 430 | .SADD1 = self.address, 431 | .RD_WRN = 1, // read 432 | .NBYTES = @as(u8, @intCast(buffer.len)), 433 | }); 434 | debug_print("I2C1 prepared for read of {} byte(s) from 0b{b:0<7}\r\n", .{ buffer.len, self.address }); 435 | 436 | // Communication START 437 | I2C1.CR2.modify(.{ .START = 1 }); 438 | debug_print("I2C1 RXNE={}\r\n", .{I2C1.ISR.read().RXNE}); 439 | debug_print("I2C1 STARTed\r\n", .{}); 440 | debug_print("I2C1 RXNE={}\r\n", .{I2C1.ISR.read().RXNE}); 441 | 442 | if (runtime_safety) self.read_allowed = false; 443 | 444 | for (buffer, 0..) |_, i| { 445 | // Wait for data to be received 446 | while (I2C1.ISR.read().RXNE == 0) { 447 | debug_print("I2C1 waiting for data (RXNE=0)\r\n", .{}); 448 | } 449 | debug_print("I2C1 data ready (RXNE=1)\r\n", .{}); 450 | 451 | // Read first data byte 452 | buffer[i] = I2C1.RXDR.read().RXDATA; 453 | } 454 | debug_print("I2C1 data: {any}\r\n", .{buffer}); 455 | } 456 | 457 | pub fn stop(_: *ReadState) !void { 458 | // Communication STOP 459 | I2C1.CR2.modify(.{ .STOP = 1 }); 460 | while (I2C1.ISR.read().BUSY == 1) {} 461 | debug_print("I2C1 STOPped\r\n", .{}); 462 | } 463 | 464 | pub fn restart_read(self: *ReadState) !ReadState { 465 | debug_print("I2C1 no action for restart\r\n", .{}); 466 | return ReadState{ .address = self.address }; 467 | } 468 | pub fn restart_write(self: *ReadState) !WriteState { 469 | debug_print("I2C1 no action for restart\r\n", .{}); 470 | return WriteState{ .address = self.address }; 471 | } 472 | }; 473 | }; 474 | } 475 | 476 | /// An STM32F303 SPI bus 477 | pub fn SpiBus(comptime index: usize) type { 478 | if (!(index == 1)) @compileError("TODO: only SPI1 is currently supported"); 479 | 480 | return struct { 481 | const Self = @This(); 482 | 483 | /// Initialize and enable the bus. 484 | pub fn init(config: micro.spi.BusConfig) !Self { 485 | _ = config; // unused for now 486 | 487 | // CONFIGURE SPI1 488 | // connected to APB2, MCU pins PA5 + PA7 + PA6 = SPC + SDI + SDO, 489 | // if GPIO port A is configured for alternate function 5 for these PA pins. 490 | 491 | // Enable the GPIO CLOCK 492 | RCC.AHBENR.modify(.{ .IOPAEN = 1 }); 493 | 494 | // Configure the I2C PINs for ALternate Functions 495 | // - Select Alternate Function in MODER Register 496 | GPIOA.MODER.modify(.{ .MODER5 = 0b10, .MODER6 = 0b10, .MODER7 = 0b10 }); 497 | // - Select High SPEED for the PINs 498 | GPIOA.OSPEEDR.modify(.{ .OSPEEDR5 = 0b11, .OSPEEDR6 = 0b11, .OSPEEDR7 = 0b11 }); 499 | // - Configure the Alternate Function in AFR Register 500 | GPIOA.AFRL.modify(.{ .AFRL5 = 5, .AFRL6 = 5, .AFRL7 = 5 }); 501 | 502 | // Enable the SPI1 CLOCK 503 | RCC.APB2ENR.modify(.{ .SPI1EN = 1 }); 504 | 505 | SPI1.CR1.modify(.{ 506 | .MSTR = 1, 507 | .SSM = 1, 508 | .SSI = 1, 509 | .RXONLY = 0, 510 | .SPE = 1, 511 | }); 512 | // the following configuration is assumed in `transceiveByte()` 513 | SPI1.CR2.raw = 0; 514 | SPI1.CR2.modify(.{ 515 | .DS = 0b0111, // 8-bit data frames, seems default via '0b0000 is interpreted as 0b0111' 516 | .FRXTH = 1, // RXNE event after 1 byte received 517 | }); 518 | 519 | return Self{}; 520 | } 521 | 522 | /// Switch this SPI bus to the given device. 523 | pub fn switch_to_device(_: Self, comptime cs_pin: type, config: micro.spi.DeviceConfig) void { 524 | _ = config; // for future use 525 | 526 | SPI1.CR1.modify(.{ 527 | .CPOL = 1, // TODO: make configurable 528 | .CPHA = 1, // TODO: make configurable 529 | .BR = 0b111, // 1/256 the of PCLK TODO: make configurable 530 | .LSBFIRST = 0, // MSB first TODO: make configurable 531 | }); 532 | gpio.set_output(cs_pin); 533 | } 534 | 535 | /// Begin a transfer to the given device. (Assumes `switchToDevice()` was called.) 536 | pub fn begin_transfer(_: Self, comptime cs_pin: type, config: micro.spi.DeviceConfig) void { 537 | _ = config; // for future use 538 | gpio.write(cs_pin, .low); // select the given device, TODO: support inverse CS devices 539 | debug_print("enabled SPI1\r\n", .{}); 540 | } 541 | 542 | /// The basic operation in the current simplistic implementation: 543 | /// send+receive a single byte. 544 | /// Writing `null` writes an arbitrary byte (`undefined`), and 545 | /// reading into `null` ignores the value received. 546 | pub fn transceive_byte(_: Self, optional_write_byte: ?u8, optional_read_pointer: ?*u8) !void { 547 | 548 | // SPIx_DR's least significant byte is `@bitCast([dr_byte_size]u8, ...)[0]` 549 | const dr_byte_size = @sizeOf(@TypeOf(SPI1.DR.raw)); 550 | 551 | // wait unril ready for write 552 | while (SPI1.SR.read().TXE == 0) { 553 | debug_print("SPI1 TXE == 0\r\n", .{}); 554 | } 555 | debug_print("SPI1 TXE == 1\r\n", .{}); 556 | 557 | // write 558 | const write_byte = if (optional_write_byte) |b| b else undefined; // dummy value 559 | @as([dr_byte_size]u8, @bitCast(SPI1.DR.*))[0] = write_byte; 560 | debug_print("Sent: {X:2}.\r\n", .{write_byte}); 561 | 562 | // wait until read processed 563 | while (SPI1.SR.read().RXNE == 0) { 564 | debug_print("SPI1 RXNE == 0\r\n", .{}); 565 | } 566 | debug_print("SPI1 RXNE == 1\r\n", .{}); 567 | 568 | // read 569 | var data_read = SPI1.DR.raw; 570 | _ = SPI1.SR.read(); // clear overrun flag 571 | const dr_lsb = @as([dr_byte_size]u8, @bitCast(data_read))[0]; 572 | debug_print("Received: {X:2} (DR = {X:8}).\r\n", .{ dr_lsb, data_read }); 573 | if (optional_read_pointer) |read_pointer| read_pointer.* = dr_lsb; 574 | } 575 | 576 | /// Write all given bytes on the bus, not reading anything back. 577 | pub fn write_all(self: Self, bytes: []const u8) !void { 578 | for (bytes) |b| { 579 | try self.transceive_byte(b, null); 580 | } 581 | } 582 | 583 | /// Read bytes to fill the given buffer exactly, writing arbitrary bytes (`undefined`). 584 | pub fn read_into(self: Self, buffer: []u8) !void { 585 | for (buffer, 0..) |_, i| { 586 | try self.transceive_byte(null, &buffer[i]); 587 | } 588 | } 589 | 590 | pub fn end_transfer(_: Self, comptime cs_pin: type, config: micro.spi.DeviceConfig) void { 591 | _ = config; // for future use 592 | // no delay should be needed here, since we know SPIx_SR's TXE is 1 593 | debug_print("(disabling SPI1)\r\n", .{}); 594 | gpio.write(cs_pin, .high); // deselect the given device, TODO: support inverse CS devices 595 | // HACK: wait long enough to make any device end an ongoing transfer 596 | var i: u8 = 255; // with the default clock, this seems to delay ~185 microseconds 597 | while (i > 0) : (i -= 1) { 598 | asm volatile ("nop"); 599 | } 600 | } 601 | }; 602 | } 603 | -------------------------------------------------------------------------------- /src/hals/STM32F407.zig: -------------------------------------------------------------------------------- 1 | //! For now we keep all clock settings on the chip defaults. 2 | //! This code currently assumes the STM32F405xx / STM32F407xx clock configuration. 3 | //! TODO: Do something useful for other STM32F40x chips. 4 | //! 5 | //! Specifically, TIM6 is running on a 16 MHz clock, 6 | //! HSI = 16 MHz is the SYSCLK after reset 7 | //! default AHB prescaler = /1 (= values 0..7): 8 | //! 9 | //! ``` 10 | //! RCC.CFGR.modify(.{ .HPRE = 0 }); 11 | //! ``` 12 | //! 13 | //! so also HCLK = 16 MHz. 14 | //! And with the default APB1 prescaler = /1: 15 | //! 16 | //! ``` 17 | //! RCC.CFGR.modify(.{ .PPRE1 = 0 }); 18 | //! ``` 19 | //! 20 | //! results in PCLK1 = 16 MHz. 21 | //! 22 | //! The above default configuration makes U(S)ART2..5 23 | //! receive a 16 MHz clock by default. 24 | //! 25 | //! USART1 and USART6 use PCLK2, which uses the APB2 prescaler on HCLK, 26 | //! default APB2 prescaler = /1: 27 | //! 28 | //! ``` 29 | //! RCC.CFGR.modify(.{ .PPRE2 = 0 }); 30 | //! ``` 31 | //! 32 | //! and therefore USART1 and USART6 receive a 16 MHz clock. 33 | //! 34 | 35 | const std = @import("std"); 36 | const micro = @import("microzig"); 37 | const peripherals = micro.peripherals; 38 | const RCC = peripherals.RCC; 39 | 40 | pub const clock = struct { 41 | pub const Domain = enum { 42 | cpu, 43 | ahb, 44 | apb1, 45 | apb2, 46 | }; 47 | }; 48 | 49 | // Default clock frequencies after reset, see top comment for calculation 50 | pub const clock_frequencies = .{ 51 | .cpu = 16_000_000, 52 | .ahb = 16_000_000, 53 | .apb1 = 16_000_000, 54 | .apb2 = 16_000_000, 55 | }; 56 | 57 | pub fn parse_pin(comptime spec: []const u8) type { 58 | const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme."; 59 | 60 | if (spec[0] != 'P') 61 | @compileError(invalid_format_msg); 62 | if (spec[1] < 'A' or spec[1] > 'I') 63 | @compileError(invalid_format_msg); 64 | 65 | return struct { 66 | const pin_number: comptime_int = std.fmt.parseInt(u4, spec[2..], 10) catch @compileError(invalid_format_msg); 67 | /// 'A'...'I' 68 | const gpio_port_name = spec[1..2]; 69 | const gpio_port = @field(peripherals, "GPIO" ++ gpio_port_name); 70 | const suffix = std.fmt.comptimePrint("{d}", .{pin_number}); 71 | }; 72 | } 73 | 74 | fn set_reg_field(reg: anytype, comptime field_name: anytype, value: anytype) void { 75 | var temp = reg.read(); 76 | @field(temp, field_name) = value; 77 | reg.write(temp); 78 | } 79 | 80 | pub const gpio = struct { 81 | pub const AlternateFunction = enum(u4) { 82 | af0, 83 | af1, 84 | af2, 85 | af3, 86 | af4, 87 | af5, 88 | af6, 89 | af7, 90 | af8, 91 | af9, 92 | af10, 93 | af11, 94 | af12, 95 | af13, 96 | af14, 97 | af15, 98 | }; 99 | 100 | pub fn set_output(comptime pin: type) void { 101 | set_reg_field(RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); 102 | set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b01); 103 | } 104 | 105 | pub fn set_input(comptime pin: type) void { 106 | set_reg_field(RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); 107 | set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b00); 108 | } 109 | 110 | pub fn set_alternate_function(comptime pin: type, af: AlternateFunction) void { 111 | set_reg_field(RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); 112 | set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b10); 113 | if (pin.pin_number < 8) { 114 | set_reg_field(@field(pin.gpio_port, "AFRL"), "AFRL" ++ pin.suffix, @intFromEnum(af)); 115 | } else { 116 | set_reg_field(@field(pin.gpio_port, "AFRH"), "AFRH" ++ pin.suffix, @intFromEnum(af)); 117 | } 118 | } 119 | 120 | pub fn read(comptime pin: type) micro.gpio.State { 121 | const idr_reg = pin.gpio_port.IDR; 122 | const reg_value = @field(idr_reg.read(), "IDR" ++ pin.suffix); // TODO extract to getRegField()? 123 | return @as(micro.gpio.State, @enumFromInt(reg_value)); 124 | } 125 | 126 | pub fn write(comptime pin: type, state: micro.gpio.State) void { 127 | switch (state) { 128 | .low => set_reg_field(pin.gpio_port.BSRR, "BR" ++ pin.suffix, 1), 129 | .high => set_reg_field(pin.gpio_port.BSRR, "BS" ++ pin.suffix, 1), 130 | } 131 | } 132 | }; 133 | 134 | pub const uart = struct { 135 | pub const DataBits = enum { 136 | seven, 137 | eight, 138 | nine, 139 | }; 140 | 141 | /// uses the values of USART_CR2.STOP 142 | pub const StopBits = enum(u2) { 143 | one = 0b00, 144 | half = 0b01, 145 | two = 0b10, 146 | one_and_half = 0b11, 147 | }; 148 | 149 | /// uses the values of USART_CR1.PS 150 | pub const Parity = enum(u1) { 151 | even = 0, 152 | odd = 1, 153 | }; 154 | 155 | const PinDirection = std.meta.FieldEnum(micro.uart.Pins); 156 | 157 | /// Checks if a pin is valid for a given uart index and direction 158 | pub fn is_valid_pin(comptime pin: type, comptime index: usize, comptime direction: PinDirection) bool { 159 | const pin_name = pin.name; 160 | 161 | return switch (direction) { 162 | .tx => switch (index) { 163 | 1 => std.mem.eql(u8, pin_name, "PA9") or std.mem.eql(u8, pin_name, "PB6"), 164 | 2 => std.mem.eql(u8, pin_name, "PA2") or std.mem.eql(u8, pin_name, "PD5"), 165 | 3 => std.mem.eql(u8, pin_name, "PB10") or std.mem.eql(u8, pin_name, "PC10") or std.mem.eql(u8, pin_name, "PD8"), 166 | 4 => std.mem.eql(u8, pin_name, "PA0") or std.mem.eql(u8, pin_name, "PC10"), 167 | 5 => std.mem.eql(u8, pin_name, "PC12"), 168 | 6 => std.mem.eql(u8, pin_name, "PC6") or std.mem.eql(u8, pin_name, "PG14"), 169 | else => unreachable, 170 | }, 171 | // Valid RX pins for the UARTs 172 | .rx => switch (index) { 173 | 1 => std.mem.eql(u8, pin_name, "PA10") or std.mem.eql(u8, pin_name, "PB7"), 174 | 2 => std.mem.eql(u8, pin_name, "PA3") or std.mem.eql(u8, pin_name, "PD6"), 175 | 3 => std.mem.eql(u8, pin_name, "PB11") or std.mem.eql(u8, pin_name, "PC11") or std.mem.eql(u8, pin_name, "PD9"), 176 | 4 => std.mem.eql(u8, pin_name, "PA1") or std.mem.eql(u8, pin_name, "PC11"), 177 | 5 => std.mem.eql(u8, pin_name, "PD2"), 178 | 6 => std.mem.eql(u8, pin_name, "PC7") or std.mem.eql(u8, pin_name, "PG9"), 179 | else => unreachable, 180 | }, 181 | }; 182 | } 183 | }; 184 | 185 | pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type { 186 | if (index < 1 or index > 6) @compileError("Valid USART index are 1..6"); 187 | 188 | const usart_name = std.fmt.comptimePrint("USART{d}", .{index}); 189 | const tx_pin = 190 | if (pins.tx) |tx| 191 | if (uart.is_valid_pin(tx, index, .tx)) 192 | tx 193 | else 194 | @compileError(std.fmt.comptimePrint("Tx pin {s} is not valid for UART{}", .{ tx.name, index })) 195 | else switch (index) { 196 | // Provide default tx pins if no pin is specified 197 | 1 => micro.Pin("PA9"), 198 | 2 => micro.Pin("PA2"), 199 | 3 => micro.Pin("PB10"), 200 | 4 => micro.Pin("PA0"), 201 | 5 => micro.Pin("PC12"), 202 | 6 => micro.Pin("PC6"), 203 | else => unreachable, 204 | }; 205 | 206 | const rx_pin = 207 | if (pins.rx) |rx| 208 | if (uart.is_valid_pin(rx, index, .rx)) 209 | rx 210 | else 211 | @compileError(std.fmt.comptimePrint("Rx pin {s} is not valid for UART{}", .{ rx.name, index })) 212 | else switch (index) { 213 | // Provide default rx pins if no pin is specified 214 | 1 => micro.Pin("PA10"), 215 | 2 => micro.Pin("PA3"), 216 | 3 => micro.Pin("PB11"), 217 | 4 => micro.Pin("PA1"), 218 | 5 => micro.Pin("PD2"), 219 | 6 => micro.Pin("PC7"), 220 | else => unreachable, 221 | }; 222 | 223 | // USART1..3 are AF7, USART 4..6 are AF8 224 | const alternate_function = if (index <= 3) .af7 else .af8; 225 | 226 | const tx_gpio = micro.Gpio(tx_pin, .{ 227 | .mode = .alternate_function, 228 | .alternate_function = alternate_function, 229 | }); 230 | const rx_gpio = micro.Gpio(rx_pin, .{ 231 | .mode = .alternate_function, 232 | .alternate_function = alternate_function, 233 | }); 234 | 235 | return struct { 236 | parity_read_mask: u8, 237 | 238 | const Self = @This(); 239 | 240 | pub fn init(config: micro.uart.Config) !Self { 241 | // The following must all be written when the USART is disabled (UE=0). 242 | if (@field(peripherals, usart_name).CR1.read().UE == 1) 243 | @panic("Trying to initialize " ++ usart_name ++ " while it is already enabled"); 244 | // LATER: Alternatively, set UE=0 at this point? Then wait for something? 245 | // Or add a destroy() function which disables the USART? 246 | 247 | // enable the USART clock 248 | const clk_enable_reg = switch (index) { 249 | 1, 6 => RCC.APB2ENR, 250 | 2...5 => RCC.APB1ENR, 251 | else => unreachable, 252 | }; 253 | set_reg_field(clk_enable_reg, usart_name ++ "EN", 1); 254 | 255 | tx_gpio.init(); 256 | rx_gpio.init(); 257 | 258 | // clear USART configuration to its default 259 | @field(peripherals, usart_name).CR1.raw = 0; 260 | @field(peripherals, usart_name).CR2.raw = 0; 261 | @field(peripherals, usart_name).CR3.raw = 0; 262 | 263 | // Return error for unsupported combinations 264 | if (config.data_bits == .nine and config.parity != null) { 265 | // TODO: should we consider this an unsupported word size or unsupported parity? 266 | return error.UnsupportedWordSize; 267 | } else if (config.data_bits == .seven and config.parity == null) { 268 | // TODO: should we consider this an unsupported word size or unsupported parity? 269 | return error.UnsupportedWordSize; 270 | } 271 | 272 | // set word length 273 | // Per the reference manual, M means 274 | // - 0: 1 start bit, 8 data bits (7 data + 1 parity, or 8 data), n stop bits, the chip default 275 | // - 1: 1 start bit, 9 data bits (8 data + 1 parity, or 9 data), n stop bits 276 | const m: u1 = if (config.data_bits == .nine or (config.data_bits == .eight and config.parity != null)) 1 else 0; 277 | @field(peripherals, usart_name).CR1.modify(.{ .M = m }); 278 | 279 | // set parity 280 | if (config.parity) |parity| { 281 | @field(peripherals, usart_name).CR1.modify(.{ .PCE = 1, .PS = @intFromEnum(parity) }); 282 | } // otherwise, no need to set no parity since we reset Control Registers above, and it's the default 283 | 284 | // set number of stop bits 285 | @field(peripherals, usart_name).CR2.modify(.{ .STOP = @intFromEnum(config.stop_bits) }); 286 | 287 | // set the baud rate 288 | // Despite the reference manual talking about fractional calculation and other buzzwords, 289 | // it is actually just a simple divider. Just ignore DIV_Mantissa and DIV_Fraction and 290 | // set the result of the division as the lower 16 bits of BRR. 291 | // TODO: We assume the default OVER8=0 configuration above (i.e. 16x oversampling). 292 | // TODO: Do some checks to see if the baud rate is too high (or perhaps too low) 293 | // TODO: Do a rounding div, instead of a truncating div? 294 | const clocks = micro.clock.get(); 295 | const bus_frequency = switch (index) { 296 | 1, 6 => clocks.apb2, 297 | 2...5 => clocks.apb1, 298 | else => unreachable, 299 | }; 300 | const usartdiv = @as(u16, @intCast(@divTrunc(bus_frequency, config.baud_rate))); 301 | @field(peripherals, usart_name).BRR.raw = usartdiv; 302 | 303 | // enable USART, and its transmitter and receiver 304 | @field(peripherals, usart_name).CR1.modify(.{ .UE = 1 }); 305 | @field(peripherals, usart_name).CR1.modify(.{ .TE = 1 }); 306 | @field(peripherals, usart_name).CR1.modify(.{ .RE = 1 }); 307 | 308 | // For code simplicity, at cost of one or more register reads, 309 | // we read back the actual configuration from the registers, 310 | // instead of using the `config` values. 311 | return read_from_registers(); 312 | } 313 | 314 | pub fn get_or_init(config: micro.uart.Config) !Self { 315 | if (@field(peripherals, usart_name).CR1.read().UE == 1) { 316 | // UART1 already enabled, don't reinitialize and disturb things; 317 | // instead read and use the actual configuration. 318 | return read_from_registers(); 319 | } else return init(config); 320 | } 321 | 322 | fn read_from_registers() Self { 323 | const cr1 = @field(peripherals, usart_name).CR1.read(); 324 | // As documented in `init()`, M0==1 means 'the 9th bit (not the 8th bit) is the parity bit'. 325 | // So we always mask away the 9th bit, and if parity is enabled and it is in the 8th bit, 326 | // then we also mask away the 8th bit. 327 | return Self{ .parity_read_mask = if (cr1.PCE == 1 and cr1.M == 0) 0x7F else 0xFF }; 328 | } 329 | 330 | pub fn can_write(self: Self) bool { 331 | _ = self; 332 | return switch (@field(peripherals, usart_name).SR.read().TXE) { 333 | 1 => true, 334 | 0 => false, 335 | }; 336 | } 337 | 338 | pub fn tx(self: Self, ch: u8) void { 339 | while (!self.can_write()) {} // Wait for Previous transmission 340 | @field(peripherals, usart_name).DR.modify(ch); 341 | } 342 | 343 | pub fn txflush(_: Self) void { 344 | while (@field(peripherals, usart_name).SR.read().TC == 0) {} 345 | } 346 | 347 | pub fn can_read(self: Self) bool { 348 | _ = self; 349 | return switch (@field(peripherals, usart_name).SR.read().RXNE) { 350 | 1 => true, 351 | 0 => false, 352 | }; 353 | } 354 | 355 | pub fn rx(self: Self) u8 { 356 | while (!self.can_read()) {} // Wait till the data is received 357 | const data_with_parity_bit: u9 = @field(peripherals, usart_name).DR.read(); 358 | return @as(u8, @intCast(data_with_parity_bit & self.parity_read_mask)); 359 | } 360 | }; 361 | } 362 | 363 | pub const i2c = struct { 364 | const PinLine = std.meta.FieldEnum(micro.i2c.Pins); 365 | 366 | /// Checks if a pin is valid for a given i2c index and line 367 | pub fn is_valid_pin(comptime pin: type, comptime index: usize, comptime line: PinLine) bool { 368 | const pin_name = pin.name; 369 | 370 | return switch (line) { 371 | .scl => switch (index) { 372 | 1 => std.mem.eql(u8, pin_name, "PB6") or std.mem.eql(u8, pin_name, "PB8"), 373 | 2 => std.mem.eql(u8, pin_name, "PB10") or std.mem.eql(u8, pin_name, "PF1") or std.mem.eql(u8, pin_name, "PH4"), 374 | 3 => std.mem.eql(u8, pin_name, "PA8") or std.mem.eql(u8, pin_name, "PH7"), 375 | else => unreachable, 376 | }, 377 | // Valid RX pins for the UARTs 378 | .sda => switch (index) { 379 | 1 => std.mem.eql(u8, pin_name, "PB7") or std.mem.eql(u8, pin_name, "PB9"), 380 | 2 => std.mem.eql(u8, pin_name, "PB11") or std.mem.eql(u8, pin_name, "PF0") or std.mem.eql(u8, pin_name, "PH5"), 381 | 3 => std.mem.eql(u8, pin_name, "PC9") or std.mem.eql(u8, pin_name, "PH8"), 382 | else => unreachable, 383 | }, 384 | }; 385 | } 386 | }; 387 | 388 | pub fn I2CController(comptime index: usize, comptime pins: micro.i2c.Pins) type { 389 | if (index < 1 or index > 3) @compileError("Valid I2C index are 1..3"); 390 | 391 | const i2c_name = std.fmt.comptimePrint("I2C{d}", .{index}); 392 | const scl_pin = 393 | if (pins.scl) |scl| 394 | if (uart.is_valid_pin(scl, index, .scl)) 395 | scl 396 | else 397 | @compileError(std.fmt.comptimePrint("SCL pin {s} is not valid for I2C{}", .{ scl.name, index })) 398 | else switch (index) { 399 | // Provide default scl pins if no pin is specified 400 | 1 => micro.Pin("PB6"), 401 | 2 => micro.Pin("PB10"), 402 | 3 => micro.Pin("PA8"), 403 | else => unreachable, 404 | }; 405 | 406 | const sda_pin = 407 | if (pins.sda) |sda| 408 | if (uart.is_valid_pin(sda, index, .sda)) 409 | sda 410 | else 411 | @compileError(std.fmt.comptimePrint("SDA pin {s} is not valid for UART{}", .{ sda.name, index })) 412 | else switch (index) { 413 | // Provide default sda pins if no pin is specified 414 | 1 => micro.Pin("PB7"), 415 | 2 => micro.Pin("PB11"), 416 | 3 => micro.Pin("PC9"), 417 | else => unreachable, 418 | }; 419 | 420 | const scl_gpio = micro.Gpio(scl_pin, .{ 421 | .mode = .alternate_function, 422 | .alternate_function = .af4, 423 | }); 424 | const sda_gpio = micro.Gpio(sda_pin, .{ 425 | .mode = .alternate_function, 426 | .alternate_function = .af4, 427 | }); 428 | 429 | // Base field of the specific I2C peripheral 430 | const i2c_base = @field(peripherals, i2c_name); 431 | 432 | return struct { 433 | const Self = @This(); 434 | 435 | pub fn init(config: micro.i2c.Config) !Self { 436 | // Configure I2C 437 | 438 | // 1. Enable the I2C CLOCK and GPIO CLOCK 439 | RCC.APB1ENR.modify(.{ .I2C1EN = 1 }); 440 | RCC.AHB1ENR.modify(.{ .GPIOBEN = 1 }); 441 | 442 | // 2. Configure the I2C PINs 443 | // This takes care of setting them alternate function mode with the correct AF 444 | scl_gpio.init(); 445 | sda_gpio.init(); 446 | 447 | // TODO: the stuff below will probably use the microzig gpio API in the future 448 | const scl = scl_pin.source_pin; 449 | const sda = sda_pin.source_pin; 450 | // Select Open Drain Output 451 | set_reg_field(@field(scl.gpio_port, "OTYPER"), "OT" ++ scl.suffix, 1); 452 | set_reg_field(@field(sda.gpio_port, "OTYPER"), "OT" ++ sda.suffix, 1); 453 | // Select High Speed 454 | set_reg_field(@field(scl.gpio_port, "OSPEEDR"), "OSPEEDR" ++ scl.suffix, 0b10); 455 | set_reg_field(@field(sda.gpio_port, "OSPEEDR"), "OSPEEDR" ++ sda.suffix, 0b10); 456 | // Activate Pull-up 457 | set_reg_field(@field(scl.gpio_port, "PUPDR"), "PUPDR" ++ scl.suffix, 0b01); 458 | set_reg_field(@field(sda.gpio_port, "PUPDR"), "PUPDR" ++ sda.suffix, 0b01); 459 | 460 | // 3. Reset the I2C 461 | i2c_base.CR1.modify(.{ .PE = 0 }); 462 | while (i2c_base.CR1.read().PE == 1) {} 463 | 464 | // 4. Configure I2C timing 465 | const bus_frequency_hz = micro.clock.get().apb1; 466 | const bus_frequency_mhz: u6 = @as(u6, @intCast(@divExact(bus_frequency_hz, 1_000_000))); 467 | 468 | if (bus_frequency_mhz < 2 or bus_frequency_mhz > 50) { 469 | return error.InvalidBusFrequency; 470 | } 471 | 472 | // .FREQ is set to the bus frequency in Mhz 473 | i2c_base.CR2.modify(.{ .FREQ = bus_frequency_mhz }); 474 | 475 | switch (config.target_speed) { 476 | 10_000...100_000 => { 477 | // CCR is bus_freq / (target_speed * 2). We use floor to avoid exceeding the target speed. 478 | const ccr = @as(u12, @intCast(@divFloor(bus_frequency_hz, config.target_speed * 2))); 479 | i2c_base.CCR.modify(.{ .CCR = ccr }); 480 | // Trise is bus frequency in Mhz + 1 481 | i2c_base.TRISE.modify(bus_frequency_mhz + 1); 482 | }, 483 | 100_001...400_000 => { 484 | // TODO: handle fast mode 485 | return error.InvalidSpeed; 486 | }, 487 | else => return error.InvalidSpeed, 488 | } 489 | 490 | // 5. Program the I2C_CR1 register to enable the peripheral 491 | i2c_base.CR1.modify(.{ .PE = 1 }); 492 | 493 | return Self{}; 494 | } 495 | 496 | pub const WriteState = struct { 497 | address: u7, 498 | buffer: [255]u8 = undefined, 499 | buffer_size: u8 = 0, 500 | 501 | pub fn start(address: u7) !WriteState { 502 | return WriteState{ .address = address }; 503 | } 504 | 505 | pub fn write_all(self: *WriteState, bytes: []const u8) !void { 506 | std.debug.assert(self.buffer_size < 255); 507 | for (bytes) |b| { 508 | self.buffer[self.buffer_size] = b; 509 | self.buffer_size += 1; 510 | if (self.buffer_size == 255) { 511 | try self.send_buffer(); 512 | } 513 | } 514 | } 515 | 516 | fn send_buffer(self: *WriteState) !void { 517 | if (self.buffer_size == 0) @panic("write of 0 bytes not supported"); 518 | 519 | // Wait for the bus to be free 520 | while (i2c_base.SR2.read().BUSY == 1) {} 521 | 522 | // Send start 523 | i2c_base.CR1.modify(.{ .START = 1 }); 524 | 525 | // Wait for the end of the start condition, master mode selected, and BUSY bit set 526 | while ((i2c_base.SR1.read().SB == 0 or 527 | i2c_base.SR2.read().MSL == 0 or 528 | i2c_base.SR2.read().BUSY == 0)) 529 | {} 530 | 531 | // Write the address to bits 7..1, bit 0 stays at 0 to indicate write operation 532 | i2c_base.DR.modify(@as(u8, @intCast(self.address)) << 1); 533 | 534 | // Wait for address confirmation 535 | while (i2c_base.SR1.read().ADDR == 0) {} 536 | 537 | // Read SR2 to clear address condition 538 | _ = i2c_base.SR2.read(); 539 | 540 | for (self.buffer[0..self.buffer_size]) |b| { 541 | // Write data byte 542 | i2c_base.DR.modify(b); 543 | // Wait for transfer finished 544 | while (i2c_base.SR1.read().BTF == 0) {} 545 | } 546 | self.buffer_size = 0; 547 | } 548 | 549 | pub fn stop(self: *WriteState) !void { 550 | try self.send_buffer(); 551 | // Communication STOP 552 | i2c_base.CR1.modify(.{ .STOP = 1 }); 553 | while (i2c_base.SR2.read().BUSY == 1) {} 554 | } 555 | 556 | pub fn restart_read(self: *WriteState) !ReadState { 557 | try self.send_buffer(); 558 | return ReadState{ .address = self.address }; 559 | } 560 | pub fn restart_write(self: *WriteState) !WriteState { 561 | try self.send_buffer(); 562 | return WriteState{ .address = self.address }; 563 | } 564 | }; 565 | 566 | pub const ReadState = struct { 567 | address: u7, 568 | 569 | pub fn start(address: u7) !ReadState { 570 | return ReadState{ .address = address }; 571 | } 572 | 573 | /// Fails with ReadError if incorrect number of bytes is received. 574 | pub fn read_no_eof(self: *ReadState, buffer: []u8) !void { 575 | std.debug.assert(buffer.len < 256); 576 | 577 | // Send start and enable ACK 578 | i2c_base.CR1.modify(.{ .START = 1, .ACK = 1 }); 579 | 580 | // Wait for the end of the start condition, master mode selected, and BUSY bit set 581 | while ((i2c_base.SR1.read().SB == 0 or 582 | i2c_base.SR2.read().MSL == 0 or 583 | i2c_base.SR2.read().BUSY == 0)) 584 | {} 585 | 586 | // Write the address to bits 7..1, bit 0 set to 1 to indicate read operation 587 | i2c_base.DR.modify((@as(u8, @intCast(self.address)) << 1) | 1); 588 | 589 | // Wait for address confirmation 590 | while (i2c_base.SR1.read().ADDR == 0) {} 591 | 592 | // Read SR2 to clear address condition 593 | _ = i2c_base.SR2.read(); 594 | 595 | for (buffer, 0..) |_, i| { 596 | if (i == buffer.len - 1) { 597 | // Disable ACK 598 | i2c_base.CR1.modify(.{ .ACK = 0 }); 599 | } 600 | 601 | // Wait for data to be received 602 | while (i2c_base.SR1.read().RxNE == 0) {} 603 | 604 | // Read data byte 605 | buffer[i] = i2c_base.DR.read(); 606 | } 607 | } 608 | 609 | pub fn stop(_: *ReadState) !void { 610 | // Communication STOP 611 | i2c_base.CR1.modify(.{ .STOP = 1 }); 612 | while (i2c_base.SR2.read().BUSY == 1) {} 613 | } 614 | 615 | pub fn restart_read(self: *ReadState) !ReadState { 616 | return ReadState{ .address = self.address }; 617 | } 618 | pub fn restart_write(self: *ReadState) !WriteState { 619 | return WriteState{ .address = self.address }; 620 | } 621 | }; 622 | }; 623 | } 624 | -------------------------------------------------------------------------------- /src/hals/STM32F429.zig: -------------------------------------------------------------------------------- 1 | //! For now we keep all clock settings on the chip defaults. 2 | //! This code should work with all the STM32F42xx line 3 | //! 4 | //! Specifically, TIM6 is running on a 16 MHz clock, 5 | //! HSI = 16 MHz is the SYSCLK after reset 6 | //! default AHB prescaler = /1 (= values 0..7): 7 | //! 8 | //! ``` 9 | //! RCC.CFGR.modify(.{ .HPRE = 0 }); 10 | //! ``` 11 | //! 12 | //! so also HCLK = 16 MHz. 13 | //! And with the default APB1 prescaler = /1: 14 | //! 15 | //! ``` 16 | //! RCC.CFGR.modify(.{ .PPRE1 = 0 }); 17 | //! ``` 18 | //! 19 | //! results in PCLK1 = 16 MHz. 20 | //! 21 | //! TODO: add more clock calculations when adding Uart 22 | 23 | const std = @import("std"); 24 | const micro = @import("microzig"); 25 | const peripherals = micro.peripherals; 26 | const RCC = peripherals.RCC; 27 | 28 | pub const clock = struct { 29 | pub const Domain = enum { 30 | cpu, 31 | ahb, 32 | apb1, 33 | apb2, 34 | }; 35 | }; 36 | 37 | // Default clock frequencies after reset, see top comment for calculation 38 | pub const clock_frequencies = .{ 39 | .cpu = 16_000_000, 40 | .ahb = 16_000_000, 41 | .apb1 = 16_000_000, 42 | .apb2 = 16_000_000, 43 | }; 44 | 45 | pub fn parsePin(comptime spec: []const u8) type { 46 | const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme."; 47 | 48 | if (spec[0] != 'P') 49 | @compileError(invalid_format_msg); 50 | if (spec[1] < 'A' or spec[1] > 'K') 51 | @compileError(invalid_format_msg); 52 | 53 | const pin_number: comptime_int = std.fmt.parseInt(u4, spec[2..], 10) catch @compileError(invalid_format_msg); 54 | 55 | return struct { 56 | /// 'A'...'K' 57 | const gpio_port_name = spec[1..2]; 58 | const gpio_port = @field(peripherals, "GPIO" ++ gpio_port_name); 59 | const suffix = std.fmt.comptimePrint("{d}", .{pin_number}); 60 | }; 61 | } 62 | 63 | fn setRegField(reg: anytype, comptime field_name: anytype, value: anytype) void { 64 | var temp = reg.read(); 65 | @field(temp, field_name) = value; 66 | reg.write(temp); 67 | } 68 | 69 | pub const gpio = struct { 70 | pub fn setOutput(comptime pin: type) void { 71 | setRegField(RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); 72 | setRegField(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b01); 73 | } 74 | 75 | pub fn setInput(comptime pin: type) void { 76 | setRegField(RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); 77 | setRegField(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b00); 78 | } 79 | 80 | pub fn read(comptime pin: type) micro.gpio.State { 81 | const idr_reg = pin.gpio_port.IDR; 82 | const reg_value = @field(idr_reg.read(), "IDR" ++ pin.suffix); // TODO extract to getRegField()? 83 | return @as(micro.gpio.State, @enumFromInt(reg_value)); 84 | } 85 | 86 | pub fn write(comptime pin: type, state: micro.gpio.State) void { 87 | switch (state) { 88 | .low => setRegField(pin.gpio_port.BSRR, "BR" ++ pin.suffix, 1), 89 | .high => setRegField(pin.gpio_port.BSRR, "BS" ++ pin.suffix, 1), 90 | } 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /test/programs/minimal.zig: -------------------------------------------------------------------------------- 1 | const micro = @import("microzig"); 2 | 3 | pub fn main() void { 4 | // This function will contain the application logic. 5 | } 6 | -------------------------------------------------------------------------------- /test/stm32f103.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Suite Setup Setup 3 | Suite Teardown Teardown 4 | Test Teardown Test Teardown 5 | Resource ${RENODEKEYWORDS} 6 | 7 | *** Test Cases *** 8 | Should Print Help 9 | ${x}= Execute Command help 10 | Should Contain ${x} Available commands: 11 | --------------------------------------------------------------------------------