├── .gitignore ├── .gitmodules ├── README.md ├── app ├── linkerscript.ld └── src │ ├── core │ ├── gpio.zig │ ├── rcc.zig │ ├── system.zig │ ├── usart.zig │ └── vector.zig │ ├── main.zig │ ├── startup.zig │ └── vector_table.zig ├── bootloader ├── linkerscript.ld └── src │ ├── core │ ├── gpio.zig │ └── rcc.zig │ ├── main.zig │ ├── startup.zig │ └── vector_table.zig ├── build.zig ├── debug.txt ├── shared ├── collections.zig ├── core │ ├── comm │ │ └── usart.zig │ ├── gpio.zig │ ├── memory_map.zig │ ├── rcc.zig │ ├── ringbuffer.zig │ └── system.zig └── shared.zig └── tools └── updater.zig /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | zig-out/ 3 | build.zig.zon 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dependencies/serial"] 2 | path = dependencies/serial 3 | url = https://github.com/ZigEmbeddedGroup/serial.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stm32-zig 2 | 3 | A **bare-metal firmware project** for STM32 microcontrollers written in **Zig**, inspired by the educational approaches of [lowbyteproductions/bare-metal-series](https://github.com/lowbyteproductions/bare-metal-series) and [cpq/bare-metal-programming-guide](https://github.com/cpq/bare-metal-programming-guide). This project demonstrates lightweight and efficient firmware development without relying on external frameworks or vendor-specific HALs. 4 | 5 | ## Goals 6 | - **Bare-metal programming**: Direct interaction with STM32 hardware using Zig. 7 | - **Minimal dependencies**: Focus on understanding low-level microcontroller programming. 8 | - **Write firmware and device drivers**: First start with STM32F4 with USART, IC2, and SPI then move to RP for BT, WiFi, ETH, potentially (HDMI) 9 | - **Simple RTOS**: Split CPU cycles into threads/tasks and manage functions plus shared resources if necessary (Raw Concurrency) 10 | - **Minimal Abstractions**: Stay close to the hardware and keep abstraction surface area smal and portable 11 | - **Learn to Debug**: GDB/LLDB or other methods to inspect problems 12 | 13 | ## Prerequisites 14 | - STM32 development board (tested with STM32F4, adaptable to other series). 15 | - ARM GCC/LLVM toolchain or equivalent for flashing/debugging. 16 | -------------------------------------------------------------------------------- /app/linkerscript.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the libopencm3 project. 3 | * 4 | * Copyright (C) 2009 Uwe Hermann 5 | * Copyright (C) 2011 Stephen Caudle 6 | * 7 | * This library is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this library. If not, see . 19 | */ 20 | 21 | /* Define memory regions. */ 22 | MEMORY 23 | { 24 | rom (rx) : ORIGIN = 0x08000000, LENGTH = 512K 25 | ram (rwx) : ORIGIN = 0x20000000, LENGTH = 96K 26 | } 27 | 28 | /* Enforce emmition of the vector table. */ 29 | EXTERN (vector_table) 30 | 31 | /* Define the entry point of the output file. */ 32 | ENTRY(reset_handler) 33 | 34 | /* Define sections. */ 35 | SECTIONS 36 | { 37 | /* .bootloader : { */ 38 | /* KEEP (*(.bootloader_section)) */ 39 | /* . = ALIGN(4); */ 40 | /* } >rom */ 41 | 42 | .text : { 43 | KEEP (*(.bootloader_section)) 44 | *(.vectors) /* Vector table */ 45 | . = ALIGN(16); 46 | 47 | KEEP (*(.firmware_info)) 48 | KEEP (*(.firmware_signature)) 49 | 50 | *(.text*) /* Program code */ 51 | . = ALIGN(4); 52 | *(.rodata*) /* Read-only data */ 53 | . = ALIGN(4); 54 | } >rom 55 | 56 | /* C++ Static constructors/destructors, also used for __attribute__ 57 | * ((constructor)) and the likes */ 58 | .preinit_array : { 59 | . = ALIGN(4); 60 | __preinit_array_start = .; 61 | KEEP (*(.preinit_array)) 62 | __preinit_array_end = .; 63 | } >rom 64 | .init_array : { 65 | . = ALIGN(4); 66 | __init_array_start = .; 67 | KEEP (*(SORT(.init_array.*))) 68 | KEEP (*(.init_array)) 69 | __init_array_end = .; 70 | } >rom 71 | .fini_array : { 72 | . = ALIGN(4); 73 | __fini_array_start = .; 74 | KEEP (*(.fini_array)) 75 | KEEP (*(SORT(.fini_array.*))) 76 | __fini_array_end = .; 77 | } >rom 78 | 79 | /* 80 | * Another section used by C++ stuff, appears when using newlib with 81 | * 64bit (long long) printf support 82 | */ 83 | .ARM.extab : { 84 | *(.ARM.extab*) 85 | } >rom 86 | .ARM.exidx : { 87 | __exidx_start = .; 88 | *(.ARM.exidx*) 89 | __exidx_end = .; 90 | } >rom 91 | 92 | . = ALIGN(4); 93 | _etext = .; 94 | 95 | /* ram, but not cleared on reset, eg boot/app comms */ 96 | .noinit (NOLOAD) : { 97 | *(.noinit*) 98 | } >ram 99 | . = ALIGN(4); 100 | 101 | .data : { 102 | _data = .; 103 | *(.data*) /* Read-write initialized data */ 104 | *(.ramtext*) /* "text" functions to run in ram */ 105 | . = ALIGN(4); 106 | _edata = .; 107 | } >ram AT >rom 108 | _data_loadaddr = LOADADDR(.data); 109 | 110 | .bss : { 111 | _sbss = .; 112 | *(.bss*) /* Read-write zero initialized data */ 113 | *(COMMON) 114 | . = ALIGN(4); 115 | _ebss = .; 116 | } >ram 117 | 118 | /* 119 | * The .eh_frame section appears to be used for C++ exception handling. 120 | * You may need to fix this if you're using C++. 121 | */ 122 | /DISCARD/ : { *(.eh_frame) } 123 | 124 | . = ALIGN(4); 125 | end = .; 126 | } 127 | 128 | PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram)); 129 | -------------------------------------------------------------------------------- /app/src/core/gpio.zig: -------------------------------------------------------------------------------- 1 | const map = @import("memory_map.zig"); 2 | pub const GPIOA_BASE: u32 = map.AHB1 + 0x0000; 3 | pub const GPIOB_BASE: u32 = map.AHB1 + 0x0400; 4 | pub const GPIOC_BASE: u32 = map.AHB1 + 0x0800; 5 | pub const GPIOD_BASE: u32 = map.AHB1 + 0x0C00; 6 | pub const GPIOE_BASE: u32 = map.AHB1 + 0x1000; 7 | pub const GPIOF_BASE: u32 = map.AHB1 + 0x1400; 8 | pub const GPIOG_BASE: u32 = map.AHB1 + 0x1800; 9 | pub const GPIOH_BASE: u32 = map.AHB1 + 0x1C00; 10 | -------------------------------------------------------------------------------- /app/src/core/rcc.zig: -------------------------------------------------------------------------------- 1 | const MAP = @import("memory_map.zig"); 2 | -------------------------------------------------------------------------------- /app/src/core/system.zig: -------------------------------------------------------------------------------- 1 | const map = @import("shared").MAP; 2 | const sys = @import("shared").SYS; 3 | 4 | const SCB_SHP3: *volatile u32 = @ptrFromInt(map.SCB.SHPR3); 5 | 6 | var tick_counter: u32 = 0; 7 | 8 | 9 | /// CLKSOURCE = 1 (processor clock), TICKINT = 1 (enable interrupt), ENABLE = 1 (enable systick 10 | pub fn systick_setup(ticks: u32, clk_src: u32, tick_int: u32, enable: u32) void { 11 | const systick: *volatile sys.Systick = @ptrFromInt(map.SCS.STCSR); 12 | systick.*.RVR = ticks; 13 | systick.*.CVR = 0; 14 | systick.*.CSR = (clk_src << 2) | (tick_int << 1) | (enable << 0); 15 | } 16 | 17 | 18 | pub fn systick_handler() callconv(.C) void { 19 | tick_counter +%= 1; 20 | } 21 | 22 | pub fn get_ticks() u32 { 23 | return @as(*volatile u32, @ptrCast(&tick_counter)).*; 24 | } 25 | 26 | pub fn delay(milliseconds: u32) void { 27 | const start = get_ticks(); 28 | // `-%` is to wrap 29 | while ((get_ticks() -% start) < milliseconds) {} 30 | } 31 | 32 | -------------------------------------------------------------------------------- /app/src/core/usart.zig: -------------------------------------------------------------------------------- 1 | const map = @import("shared").MAP; 2 | const gpio = @import("shared").GPIO; 3 | const rcc = @import("shared").RCC; 4 | const usart = @import("shared").COMM.USART; 5 | 6 | const FREQ: u32 = 16_000_000; 7 | 8 | const Usart2: *volatile usart.Usart = @ptrFromInt(map.USART2); 9 | const Rcc: *volatile rcc.RCC = @ptrFromInt(map.RCC_BASE); 10 | const GpioA: *volatile gpio.Port = @ptrFromInt(map.GPIOA_BASE + 0x00); 11 | 12 | 13 | pub fn setup() void { 14 | // Alternate Function 15 | const af: u8 = 7; 16 | const tx_pin: u32 = 2; 17 | const rx_pin: u32 = 3; 18 | 19 | 20 | 21 | // Turn on respective USART2 bus registers 22 | Rcc.APB1ENR |= (1 << 17); 23 | 24 | GpioA.set_mode(@intCast(tx_pin), gpio.MODE.AF); 25 | GpioA.set_af(@intCast(tx_pin), af); 26 | GpioA.set_mode(@intCast(rx_pin), gpio.MODE.AF); 27 | GpioA.set_af(rx_pin, af); 28 | 29 | // Disable 30 | Usart2.CR1 = 0; 31 | 32 | Usart2.BRR = FREQ / 115200; 33 | 34 | // Enable 35 | Usart2.CR1 |= usart.CR1.UE | usart.CR1.TE | usart.CR1.RE; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/core/vector.zig: -------------------------------------------------------------------------------- 1 | const MAP = @import("shared").MAP; 2 | 3 | const VTOR: *volatile u32 = @ptrFromInt(MAP.SCB.VTOR); 4 | 5 | /// 6 | pub fn setup(boot_size: u32) void { 7 | VTOR.* = boot_size; 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main.zig: -------------------------------------------------------------------------------- 1 | const map = @import("shared").MAP; 2 | const gpio = @import("shared").GPIO; 3 | const rcc = @import("shared").RCC; 4 | const USART = @import("shared").COMM.USART; 5 | 6 | const system = @import("core/system.zig"); 7 | const vector = @import("core/vector.zig"); 8 | const usart = @import("core/usart.zig"); 9 | const vector_table = @import("vector_table.zig"); 10 | comptime { 11 | @import("startup.zig").export_start_symbol(); 12 | @import("vector_table.zig").export_vector_table(); 13 | 14 | asm( 15 | \\.section .bootloader_section 16 | \\.incbin "zig-out/bin/bootloader.bin" 17 | ); 18 | } 19 | 20 | const BOOT_SIZE: u32 = 0x8000; 21 | const SYS_CLK_FREQ = 16_000_000; 22 | 23 | const GpioA: *volatile gpio.Port = @ptrFromInt(map.GPIOA_BASE + 0x00); 24 | const Rcc: *volatile rcc.RCC = @ptrFromInt(map.RCC_BASE); 25 | const Usart2: *volatile USART.Usart = @ptrFromInt(map.USART2); 26 | 27 | export fn main() callconv(.C) noreturn { 28 | vector.setup(BOOT_SIZE); 29 | const desired_ticks: u32 = SYS_CLK_FREQ / 1000; 30 | system.systick_setup(desired_ticks, 1, 1, 1); 31 | usart.setup(); 32 | 33 | 34 | 35 | Rcc.*.AHB1ENR |= 0x01; 36 | 37 | 38 | // Configure GPIOA Pin 5 39 | GpioA.set_mode(5, .OUTPUT); 40 | 41 | while (true) { 42 | GpioA.*.ODR ^= (1 << 5); // Toggle Pin 5 (bit 5). 43 | // 44 | system.delay(1000); 45 | 46 | Usart2.write_buffer("Hello\n"); 47 | // while () { 48 | // } 49 | } 50 | } 51 | 52 | // 0xE000ED10 53 | -------------------------------------------------------------------------------- /app/src/startup.zig: -------------------------------------------------------------------------------- 1 | extern fn main() noreturn; 2 | 3 | pub fn export_start_symbol() void { 4 | @export(reset_handler, .{ 5 | .name = "_start", 6 | }); 7 | } 8 | 9 | /// Change Process State Interrupts Enable 10 | /// Enable interrupts (clear PRIMASK.PM) 11 | /// https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Instruction-Set/Miscellaneous-instructions/CPS 12 | pub inline fn enable_interrupts() void { 13 | asm volatile ("CPSIE i" ::: "memory"); 14 | } 15 | 16 | /// Change Process State Interrupts Disable 17 | /// Disable interrupts except NMI (set PRIMASK.PM) 18 | /// https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Instruction-Set/Miscellaneous-instructions/CPS 19 | pub inline fn disable_interrupts() void { 20 | asm volatile ("CPSID i" ::: "memory"); 21 | } 22 | 23 | pub fn reset_handler() callconv(.C) noreturn { 24 | const startup_symbols = struct { 25 | extern var _sbss: u8; 26 | extern var _ebss: u8; 27 | extern var _data: u8; 28 | extern var _edata: u8; 29 | extern const _data_loadaddr: u8; 30 | }; 31 | 32 | // Ensure Interrupts are cleared 33 | disable_interrupts(); 34 | { 35 | const bss_start: [*]u8 = @ptrCast(&startup_symbols._sbss); 36 | const bss_end: [*]u8 = @ptrCast(&startup_symbols._ebss); 37 | const bss_len = @intFromPtr(bss_end) - @intFromPtr(bss_start); 38 | 39 | // Init all global values to 0 40 | @memset(bss_start[0..bss_len], 0); 41 | } 42 | 43 | { 44 | const data_start: [*]u8 = @ptrCast(&startup_symbols._data); 45 | const data_end: [*]u8 = @ptrCast(&startup_symbols._edata); 46 | const data_len = @intFromPtr(data_end) - @intFromPtr(data_start); 47 | const data_src: [*]const u8 = @ptrCast(&startup_symbols._data_loadaddr); 48 | 49 | @memcpy(data_start[0..data_len], data_src[0..data_len]); 50 | } 51 | 52 | 53 | // Re-enable interrupts before calling main() 54 | enable_interrupts(); 55 | 56 | main(); 57 | } 58 | -------------------------------------------------------------------------------- /app/src/vector_table.zig: -------------------------------------------------------------------------------- 1 | pub fn export_vector_table() void { 2 | @export(vector_table, .{ 3 | .name = "vector_table", 4 | .section = ".vectors", 5 | .linkage = .strong, 6 | }); 7 | } 8 | 9 | 10 | // For the linkerscript 11 | extern var _stack: anyopaque; 12 | extern var _data_loadaddr: anyopaque; 13 | extern var _data: anyopaque; 14 | extern var _edata: anyopaque; 15 | extern var _ebss: anyopaque; 16 | 17 | 18 | const InterruptHandlerFn = *const fn () callconv(.C) void; 19 | 20 | fn default_handler() callconv(.C) noreturn { 21 | while (true) {} 22 | } 23 | 24 | fn null_handler() callconv(.C) void {} 25 | 26 | const reset_handler = @import("startup.zig").reset_handler; 27 | 28 | pub const VectorTable = extern struct { 29 | initial_sp_value: *anyopaque, // Reserved (0x0000 0000) 30 | reset: InterruptHandlerFn = reset_handler, // Reset (0x0000 0004) 31 | nmi: InterruptHandlerFn = null_handler, // Non maskable interrupt. The RCC Clock Security System (CSS) is linked to the NMI vector (0x0000 0008) 32 | hard_fault: InterruptHandlerFn = default_handler, // All class of fault (0x0000_000C) 33 | mem_manage: InterruptHandlerFn = default_handler, // Memory management (0x0000_0010) 34 | bus_fault: InterruptHandlerFn = default_handler, // Pre-fetch fault, memory access fault (0x0000_0014) 35 | usage_fault: InterruptHandlerFn = default_handler, // Undefined instruction of illegal state (0x0000_0018) 36 | rsvp1: [4]u32 = undefined, 37 | sv_call: InterruptHandlerFn = default_handler, // System service call via SWI instruction (0x0000_002C) 38 | debug_monitor: InterruptHandlerFn = default_handler, // Debug Monitor (0x0000_0030) 39 | rsvp2: u32 = undefined, 40 | pend_sv: InterruptHandlerFn = default_handler, // Pendable request for system service (0x0000_0038) 41 | sys_tick: InterruptHandlerFn = @import("core/system.zig").systick_handler, // System tick timer (0x0000_003C) 42 | wwdg: InterruptHandlerFn = default_handler, // Window Watchdog interrupt (0x0000_0040) 43 | pvd: InterruptHandlerFn = default_handler, // PVD through EXTI line detection interrupt (0x0000_0044) 44 | tamp_stamp: InterruptHandlerFn = default_handler, // Tamper and TimeStamp interrupts through the EXTI line (0x0000_0048) 45 | rtc_wkup: InterruptHandlerFn = default_handler, // RTC Wakeup interrupt through the EXTI line (0x0000_004C) 46 | flash: InterruptHandlerFn = default_handler, // Flash Global interrupt (0x0000_0050) 47 | rcc: InterruptHandlerFn = default_handler, // RCC Global interrupt (0x0000_0054) 48 | extl0: InterruptHandlerFn = default_handler, // EXTL Line0 interrupt (0x0000_0058) 49 | extl1: InterruptHandlerFn = default_handler, // EXTL Line1 interrupt (0x000_005C) 50 | extl2: InterruptHandlerFn = default_handler, // EXTL Line2 interrupt (0x0000_0060) 51 | extl3: InterruptHandlerFn = default_handler, // EXTL Line3 interrupt (0x0000_0064) 52 | extl4: InterruptHandlerFn = default_handler, // EXTL Line4 interrupt (0x0000_0068) 53 | dma1_stream0: InterruptHandlerFn = default_handler, // DMA1 Stream0 global interrupt (0x0000_006C) 54 | dma1_stream1: InterruptHandlerFn = default_handler, // DMA1 Stream1 global interrupt (0x0000_0070) 55 | dma1_stream2: InterruptHandlerFn = default_handler, // DMA1 Stream2 global interrupt (0x0000_0074) 56 | dma1_stream3: InterruptHandlerFn = default_handler, // DMA1 Stream3 global interrupt (0x0000_0078) 57 | dma1_stream4: InterruptHandlerFn = default_handler, // DMA1 Stream4 global interrupt (0x0000_007C) 58 | dma1_stream5: InterruptHandlerFn = default_handler, // DMA1 Stream5 global interrupt (0x0000_0080) 59 | dma1_stream6: InterruptHandlerFn = default_handler, // DMA1 Stream6 global interrupt (0x0000_0084) 60 | adc: InterruptHandlerFn = default_handler, // ADC1, ADC2, ADC3 global interrupts (0x0000_0088) 61 | can1_tx: InterruptHandlerFn = default_handler, // CAN1 TX interrupt (0x0000_008C) 62 | can1_rx0: InterruptHandlerFn = default_handler, // CAN1 RX0 interrupts (0x0000_0090) 63 | can1_rx1: InterruptHandlerFn = default_handler, // CAN1 RX1 interrupts (0x0000_0094) 64 | can1_sce: InterruptHandlerFn = default_handler, // CAN1 SCE interrupt (0x0000_0098) 65 | exitl9_5: InterruptHandlerFn = default_handler, // EXTL Line [9:5] interrupts (0x0000_009C) 66 | tim1_brk_tim9: InterruptHandlerFn = default_handler, // TIM1 Break interrupt and TIM9 global interrupt (0x0000_00A0) 67 | tim1_up_tim10: InterruptHandlerFn = default_handler, // TIM1 Update interrupt and TIM10 global interrupt (0x0000_00A4) 68 | tim1_trg_com_tim11: InterruptHandlerFn = default_handler, // TIM1 Trigger and Communication interrupts and TIM11 global interrupt (0x0000_00A8) 69 | tim1_cc: InterruptHandlerFn = default_handler, // TIM1 Capture Compare interrupt (0x0000_00AC) 70 | tim2: InterruptHandlerFn = default_handler, // TIM1 global interrupt (0x0000_00B0) 71 | tim3: InterruptHandlerFn = default_handler, // TIM2 global interrupt (0x0000_00B4) 72 | tim4: InterruptHandlerFn = default_handler, // TIM3 global interrupt (0x0000_00B8) 73 | i2c1_ev: InterruptHandlerFn = default_handler, // I^2C1 event interrupt (0x0000_00BC) 74 | i2c1_er: InterruptHandlerFn = default_handler, // I^2C1 error interrupt (0x0000_00C0) 75 | i2c2_ev: InterruptHandlerFn = default_handler, // I^2C2 event interrupt (0x0000_00C4) 76 | i2c2_er: InterruptHandlerFn = default_handler, // I^2C2 error interrupt (0x0000_00C8) 77 | spi1: InterruptHandlerFn = default_handler, // SPI1 global interrupt (0x0000_00CC) 78 | spi2: InterruptHandlerFn = default_handler, // SPI2 global interrupt (0x0000_00D0) 79 | usart1: InterruptHandlerFn = default_handler, // USART1 global interrupt (0x0000_00D4) 80 | usart2: InterruptHandlerFn = default_handler, // USART2 global interrupt (0x0000_00D8) 81 | usart3: InterruptHandlerFn = default_handler, // USART3 global interrupt (0x0000_00DC) 82 | extil15_10: InterruptHandlerFn = default_handler, // EXTI Line[15:10] interrupts (0x0000_00E0) 83 | rtc_alarm: InterruptHandlerFn = default_handler, // RTC Alarms (A and B) through EXTI line interrupt (0x0000_00E4) 84 | otg_fs_wkup: InterruptHandlerFn = default_handler, // USB On-The-Go FS Wakeup through EXTI line interrupt (0x0000_00E8) 85 | tim8_brk_tim12: InterruptHandlerFn = default_handler, // TIM8 Break interrupt and TIM12 global interrupt (0x0000_00EC) 86 | tim8_up_tim13: InterruptHandlerFn = default_handler, // TIM8 Update interrupt and TIM13 global interrupt (0x0000_00F0) 87 | tim8_trg_com_tim14: InterruptHandlerFn = default_handler, // TIM8 Trigger and Communication interrupts and TIM14 global interrupt (0x0000_00F4) 88 | tim8_cc: InterruptHandlerFn = default_handler, // TIM8 Capture Compare interrupt (0x0000_00F8) 89 | dma1_stream7: InterruptHandlerFn = default_handler, // DMA1 Stream7 global interrupt (0x0000_00FC) 90 | fsmc: InterruptHandlerFn = default_handler, // FSMC global interrupt (0x0000_0100) 91 | sdio: InterruptHandlerFn = default_handler, // SDIO global interrupt (0x0000_0104) 92 | tim5: InterruptHandlerFn = default_handler, // TIM5 global interrupt (0x0000_0108) 93 | spi3: InterruptHandlerFn = default_handler, // SPI3 global interrupt (0x0000_010C) 94 | usart4: InterruptHandlerFn = default_handler, // USART4 global interrupt (0x0000_0110) 95 | usart5: InterruptHandlerFn = default_handler, // USART5 global interrupt (0x0000_0114) 96 | tim6_dac: InterruptHandlerFn = default_handler, // TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts (0x0000_0118) 97 | tim7: InterruptHandlerFn = default_handler, // TIM7 global interrupt (0x0000_011C) 98 | dma2_stream0: InterruptHandlerFn = default_handler, // DMA2 Stream0 global interrupt (0x0000_0120) 99 | dma2_stream1: InterruptHandlerFn = default_handler, // DMA2 Stream1 global interrupt (0x0000_0124) 100 | dma2_stream2: InterruptHandlerFn = default_handler, // DMA2 Stream2 global interrupt (0x0000_0128) 101 | dma2_stream3: InterruptHandlerFn = default_handler, // DMA2 Stream3 global interrupt (0x0000_012C) 102 | dma2_stream4: InterruptHandlerFn = default_handler, // DMA2 Stream4 global interrupt (0x0000_0130) 103 | eth: InterruptHandlerFn = default_handler, // Ethernet global interrupt (0x0000_0134) 104 | eth_wkup: InterruptHandlerFn = default_handler, // Ethernet Wakeup through EXTI line interrupt (0x0000_0138) 105 | can2_tx: InterruptHandlerFn = default_handler, // CAN2 TX interrupts (0x0000_013C) 106 | can2_rx0: InterruptHandlerFn = default_handler, // CAN2 RX0 interrupts (0x0000_0140) 107 | can2_rx1: InterruptHandlerFn = default_handler, // CAN2 RX1 interrupt (0x0000_0144) 108 | can2_sce: InterruptHandlerFn = default_handler, // CAN2 SCE interrupt (0x0000_0148) 109 | otg_fs: InterruptHandlerFn = default_handler, // USB On the Go FS global interrupt (0x0000_014C) 110 | dma2_stream5: InterruptHandlerFn = default_handler, // DMA2 Stream5 global interrupt (0x0000_0150) 111 | dma2_stream6: InterruptHandlerFn = default_handler, // DMA2 Stream6 global interrupt (0x0000_0154) 112 | dma2_stream7: InterruptHandlerFn = default_handler, // DMA2 Stream7 global interrupt (0x0000_0158) 113 | usart6: InterruptHandlerFn = default_handler, // USART6 global interrupt (0x0000_015C) 114 | i2c3_ev: InterruptHandlerFn = default_handler, // I^2C3 event interrupt (0x0000_0160) 115 | i2c3_er: InterruptHandlerFn = default_handler, // I^2C3 error interrupt (0x0000_0164) 116 | otg_hs_epi_out: InterruptHandlerFn = default_handler, // USB On-The-Go HS End Point 1 out interrupt (0x0000_0168) 117 | otg_hs_epi_in: InterruptHandlerFn = default_handler, // USB On-The-Go HS End Point 1 in interrupt (0x0000_016C) 118 | otg_hs_wkup: InterruptHandlerFn = default_handler, // USB On-The-Go HS Wakeup through EXTI line interrupt (0x0000_0170) 119 | otg_hs: InterruptHandlerFn = default_handler, // USB On-The-Go HS Wakeup global interrupt (0x0000_0174) 120 | dcmi: InterruptHandlerFn = default_handler, // DCMI global interrupt (0x0000_0178) 121 | cryp: InterruptHandlerFn = default_handler, // CRYP crypto global interrupt (0x0000_017C) 122 | hash_rng: InterruptHandlerFn = default_handler, // HASH and Rng global interrupt (0x0000_0180) 123 | fpu: InterruptHandlerFn = default_handler, // FPU global interrupt (0x0000_0184) 124 | }; 125 | 126 | pub var vector_table: VectorTable = .{ .initial_sp_value = &_stack, .reset = reset_handler, }; 127 | -------------------------------------------------------------------------------- /bootloader/linkerscript.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the libopencm3 project. 3 | * 4 | * Copyright (C) 2009 Uwe Hermann 5 | * Copyright (C) 2011 Stephen Caudle 6 | * 7 | * This library is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this library. If not, see . 19 | */ 20 | 21 | /* Define memory regions. */ 22 | MEMORY 23 | { 24 | rom (rx) : ORIGIN = 0x08000000, LENGTH = 32K 25 | ram (rwx) : ORIGIN = 0x20000000, LENGTH = 96K 26 | } 27 | 28 | /* Enforce emmition of the vector table. */ 29 | EXTERN (vector_table) 30 | 31 | /* Define the entry point of the output file. */ 32 | ENTRY(reset_handler) 33 | 34 | /* Define sections. */ 35 | SECTIONS 36 | { 37 | .text : { 38 | *(.vectors) /* Vector table */ 39 | . = ALIGN(16); 40 | 41 | KEEP (*(.firmware_info)) 42 | KEEP (*(.firmware_signature)) 43 | 44 | *(.text*) /* Program code */ 45 | . = ALIGN(4); 46 | *(.rodata*) /* Read-only data */ 47 | . = ALIGN(4); 48 | } >rom 49 | 50 | /* C++ Static constructors/destructors, also used for __attribute__ 51 | * ((constructor)) and the likes */ 52 | .preinit_array : { 53 | . = ALIGN(4); 54 | __preinit_array_start = .; 55 | KEEP (*(.preinit_array)) 56 | __preinit_array_end = .; 57 | } >rom 58 | .init_array : { 59 | . = ALIGN(4); 60 | __init_array_start = .; 61 | KEEP (*(SORT(.init_array.*))) 62 | KEEP (*(.init_array)) 63 | __init_array_end = .; 64 | } >rom 65 | .fini_array : { 66 | . = ALIGN(4); 67 | __fini_array_start = .; 68 | KEEP (*(.fini_array)) 69 | KEEP (*(SORT(.fini_array.*))) 70 | __fini_array_end = .; 71 | } >rom 72 | 73 | /* 74 | * Another section used by C++ stuff, appears when using newlib with 75 | * 64bit (long long) printf support 76 | */ 77 | .ARM.extab : { 78 | *(.ARM.extab*) 79 | } >rom 80 | .ARM.exidx : { 81 | __exidx_start = .; 82 | *(.ARM.exidx*) 83 | __exidx_end = .; 84 | } >rom 85 | 86 | . = ALIGN(4); 87 | _etext = .; 88 | 89 | /* ram, but not cleared on reset, eg boot/app comms */ 90 | .noinit (NOLOAD) : { 91 | *(.noinit*) 92 | } >ram 93 | . = ALIGN(4); 94 | 95 | .data : { 96 | _data = .; 97 | *(.data*) /* Read-write initialized data */ 98 | *(.ramtext*) /* "text" functions to run in ram */ 99 | . = ALIGN(4); 100 | _edata = .; 101 | } >ram AT >rom 102 | _data_loadaddr = LOADADDR(.data); 103 | 104 | .bss : { 105 | _sbss = .; 106 | *(.bss*) /* Read-write zero initialized data */ 107 | *(COMMON) 108 | . = ALIGN(4); 109 | _ebss = .; 110 | } >ram 111 | 112 | /* 113 | * The .eh_frame section appears to be used for C++ exception handling. 114 | * You may need to fix this if you're using C++. 115 | */ 116 | /DISCARD/ : { *(.eh_frame) } 117 | 118 | . = ALIGN(4); 119 | end = .; 120 | } 121 | 122 | PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram)); 123 | -------------------------------------------------------------------------------- /bootloader/src/core/gpio.zig: -------------------------------------------------------------------------------- 1 | const map = @import("memory_map.zig"); 2 | pub const GPIOA_BASE: u32 = map.AHB1 + 0x0000; 3 | pub const GPIOB_BASE: u32 = map.AHB1 + 0x0400; 4 | pub const GPIOC_BASE: u32 = map.AHB1 + 0x0800; 5 | pub const GPIOD_BASE: u32 = map.AHB1 + 0x0C00; 6 | pub const GPIOE_BASE: u32 = map.AHB1 + 0x1000; 7 | pub const GPIOF_BASE: u32 = map.AHB1 + 0x1400; 8 | pub const GPIOG_BASE: u32 = map.AHB1 + 0x1800; 9 | pub const GPIOH_BASE: u32 = map.AHB1 + 0x1C00; 10 | -------------------------------------------------------------------------------- /bootloader/src/core/rcc.zig: -------------------------------------------------------------------------------- 1 | const MAP = @import("memory_map.zig"); 2 | -------------------------------------------------------------------------------- /bootloader/src/main.zig: -------------------------------------------------------------------------------- 1 | const MAP = @import("shared").MAP; 2 | const vector = @import("vector_table.zig"); 3 | comptime { 4 | @import("startup.zig").export_start_symbol(); 5 | @import("vector_table.zig").export_vector_table(); 6 | } 7 | 8 | const BOOTLOADER_SIZE: u32 = 0x8000; 9 | const FIRMWARE_START: u32 = BOOTLOADER_SIZE + MAP.FLASH_BASE; 10 | 11 | 12 | fn jump_to_main() void { 13 | const main_vector_table: *vector.VectorTable = @ptrFromInt(FIRMWARE_START); 14 | main_vector_table.reset(); 15 | } 16 | 17 | export fn main() callconv(.C) void { 18 | jump_to_main(); 19 | } 20 | -------------------------------------------------------------------------------- /bootloader/src/startup.zig: -------------------------------------------------------------------------------- 1 | extern fn main() noreturn; 2 | 3 | pub fn export_start_symbol() void { 4 | @export(reset_handler, .{ 5 | .name = "_start", 6 | }); 7 | } 8 | 9 | /// Change Process State Interrupts Enable 10 | /// Enable interrupts (clear PRIMASK.PM) 11 | /// https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Instruction-Set/Miscellaneous-instructions/CPS 12 | pub inline fn enable_interrupts() void { 13 | asm volatile ("CPSIE i" ::: "memory"); 14 | } 15 | 16 | /// Change Process State Interrupts Disable 17 | /// Disable interrupts except NMI (set PRIMASK.PM) 18 | /// https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Instruction-Set/Miscellaneous-instructions/CPS 19 | pub inline fn disable_interrupts() void { 20 | asm volatile ("CPSID i" ::: "memory"); 21 | } 22 | 23 | pub fn reset_handler() callconv(.C) noreturn { 24 | const startup_symbols = struct { 25 | extern var _sbss: u8; 26 | extern var _ebss: u8; 27 | extern var _data: u8; 28 | extern var _edata: u8; 29 | extern const _data_loadaddr: u8; 30 | }; 31 | 32 | // Ensure Interrupts are cleared 33 | disable_interrupts(); 34 | { 35 | const bss_start: [*]u8 = @ptrCast(&startup_symbols._sbss); 36 | const bss_end: [*]u8 = @ptrCast(&startup_symbols._ebss); 37 | const bss_len = @intFromPtr(bss_end) - @intFromPtr(bss_start); 38 | 39 | // Init all global values to 0 40 | @memset(bss_start[0..bss_len], 0); 41 | } 42 | 43 | { 44 | const data_start: [*]u8 = @ptrCast(&startup_symbols._data); 45 | const data_end: [*]u8 = @ptrCast(&startup_symbols._edata); 46 | const data_len = @intFromPtr(data_end) - @intFromPtr(data_start); 47 | const data_src: [*]const u8 = @ptrCast(&startup_symbols._data_loadaddr); 48 | 49 | @memcpy(data_start[0..data_len], data_src[0..data_len]); 50 | } 51 | 52 | 53 | // Re-enable interrupts before calling main() 54 | enable_interrupts(); 55 | 56 | main(); 57 | } 58 | -------------------------------------------------------------------------------- /bootloader/src/vector_table.zig: -------------------------------------------------------------------------------- 1 | pub fn export_vector_table() void { 2 | @export(vector_table, .{ 3 | .name = "vector_table", 4 | .section = ".vectors", 5 | .linkage = .strong, 6 | }); 7 | } 8 | 9 | 10 | // For the linkerscript 11 | extern var _stack: anyopaque; 12 | extern var _data_loadaddr: anyopaque; 13 | extern var _data: anyopaque; 14 | extern var _edata: anyopaque; 15 | extern var _ebss: anyopaque; 16 | 17 | 18 | const InterruptHandlerFn = *const fn () callconv(.C) void; 19 | 20 | fn default_handler() callconv(.C) noreturn { 21 | while (true) {} 22 | } 23 | 24 | fn null_handler() callconv(.C) noreturn { 25 | while (true) {} 26 | } 27 | 28 | const reset_handler = @import("startup.zig").reset_handler; 29 | 30 | pub const VectorTable = extern struct { 31 | initial_sp_value: *anyopaque, // Reserved (0x0000 0000) 32 | reset: InterruptHandlerFn = reset_handler, // Reset (0x0000 0004) 33 | nmi: InterruptHandlerFn = null_handler, // Non maskable interrupt. The RCC Clock Security System (CSS) is linked to the NMI vector (0x0000 0008) 34 | hard_fault: InterruptHandlerFn = default_handler, // All class of fault (0x0000_000C) 35 | mem_manage: InterruptHandlerFn = default_handler, // Memory management (0x0000_0010) 36 | bus_fault: InterruptHandlerFn = default_handler, // Pre-fetch fault, memory access fault (0x0000_0014) 37 | rsvp1: [4]u32 = undefined, 38 | usage_fault: InterruptHandlerFn = default_handler, // Undefined instruction of illegal state (0x0000_0018) 39 | sv_call: InterruptHandlerFn = default_handler, // System service call via SWI instruction (0x0000_002C) 40 | debug_monitor: InterruptHandlerFn = default_handler, // Debug Monitor (0x0000_0030) 41 | rsvp2: u32 = undefined, 42 | pend_sv: InterruptHandlerFn = default_handler, // Pendable request for system service (0x0000_0038) 43 | sys_tick: InterruptHandlerFn = default_handler, // System tick timer (0x0000_003C) 44 | wwdg: InterruptHandlerFn = default_handler, // Window Watchdog interrupt (0x0000_0040) 45 | pvd: InterruptHandlerFn = default_handler, // PVD through EXTI line detection interrupt (0x0000_0044) 46 | tamp_stamp: InterruptHandlerFn = default_handler, // Tamper and TimeStamp interrupts through the EXTI line (0x0000_0048) 47 | rtc_wkup: InterruptHandlerFn = default_handler, // RTC Wakeup interrupt through the EXTI line (0x0000_004C) 48 | flash: InterruptHandlerFn = default_handler, // Flash Global interrupt (0x0000_0050) 49 | rcc: InterruptHandlerFn = default_handler, // RCC Global interrupt (0x0000_0054) 50 | extl0: InterruptHandlerFn = default_handler, // EXTL Line0 interrupt (0x0000_0058) 51 | extl1: InterruptHandlerFn = default_handler, // EXTL Line1 interrupt (0x0000_005C) 52 | extl2: InterruptHandlerFn = default_handler, // EXTL Line2 interrupt (0x0000_0060) 53 | extl3: InterruptHandlerFn = default_handler, // EXTL Line3 interrupt (0x0000_0064) 54 | extl4: InterruptHandlerFn = default_handler, // EXTL Line4 interrupt (0x0000_0068) 55 | dma1_stream0: InterruptHandlerFn = default_handler, // DMA1 Stream0 global interrupt (0x0000_006C) 56 | dma1_stream1: InterruptHandlerFn = default_handler, // DMA1 Stream1 global interrupt (0x0000_0070) 57 | dma1_stream2: InterruptHandlerFn = default_handler, // DMA1 Stream2 global interrupt (0x0000_0074) 58 | dma1_stream3: InterruptHandlerFn = default_handler, // DMA1 Stream3 global interrupt (0x0000_0078) 59 | dma1_stream4: InterruptHandlerFn = default_handler, // DMA1 Stream4 global interrupt (0x0000_007C) 60 | dma1_stream5: InterruptHandlerFn = default_handler, // DMA1 Stream5 global interrupt (0x0000_0080) 61 | dma1_stream6: InterruptHandlerFn = default_handler, // DMA1 Stream6 global interrupt (0x0000_0084) 62 | adc: InterruptHandlerFn = default_handler, // ADC1, ADC2, ADC3 global interrupts (0x0000_0088) 63 | can1_tx: InterruptHandlerFn = default_handler, // CAN1 TX interrupt (0x0000_008C) 64 | can1_rx0: InterruptHandlerFn = default_handler, // CAN1 RX0 interrupts (0x0000_0090) 65 | can1_rx1: InterruptHandlerFn = default_handler, // CAN1 RX1 interrupts (0x0000_0094) 66 | can1_sce: InterruptHandlerFn = default_handler, // CAN1 SCE interrupt (0x0000_0098) 67 | exitl9_5: InterruptHandlerFn = default_handler, // EXTL Line [9:5] interrupts (0x0000_009C) 68 | tim1_brk_tim9: InterruptHandlerFn = default_handler, // TIM1 Break interrupt and TIM9 global interrupt (0x0000_00A0) 69 | tim1_up_tim10: InterruptHandlerFn = default_handler, // TIM1 Update interrupt and TIM10 global interrupt (0x0000_00A4) 70 | tim1_trg_com_tim11: InterruptHandlerFn = default_handler, // TIM1 Trigger and Communication interrupts and TIM11 global interrupt (0x0000_00A8) 71 | tim1_cc: InterruptHandlerFn = default_handler, // TIM1 Capture Compare interrupt (0x0000_00AC) 72 | tim2: InterruptHandlerFn = default_handler, // TIM1 global interrupt (0x0000_00B0) 73 | tim3: InterruptHandlerFn = default_handler, // TIM2 global interrupt (0x0000_00B4) 74 | tim4: InterruptHandlerFn = default_handler, // TIM3 global interrupt (0x0000_00B8) 75 | i2c1_ev: InterruptHandlerFn = default_handler, // I^2C1 event interrupt (0x0000_00BC) 76 | i2c1_er: InterruptHandlerFn = default_handler, // I^2C1 error interrupt (0x0000_00C0) 77 | i2c2_ev: InterruptHandlerFn = default_handler, // I^2C2 event interrupt (0x0000_00C4) 78 | i2c2_er: InterruptHandlerFn = default_handler, // I^2C2 error interrupt (0x0000_00C8) 79 | spi1: InterruptHandlerFn = default_handler, // SPI1 global interrupt (0x0000_00CC) 80 | spi2: InterruptHandlerFn = default_handler, // SPI2 global interrupt (0x0000_00D0) 81 | usart1: InterruptHandlerFn = default_handler, // USART1 global interrupt (0x0000_00D4) 82 | usart2: InterruptHandlerFn = default_handler, // USART2 global interrupt (0x0000_00D8) 83 | usart3: InterruptHandlerFn = default_handler, // USART3 global interrupt (0x0000_00DC) 84 | extil15_10: InterruptHandlerFn = default_handler, // EXTI Line[15:10] interrupts (0x0000_00E0) 85 | rtc_alarm: InterruptHandlerFn = default_handler, // RTC Alarms (A and B) through EXTI line interrupt (0x0000_00E4) 86 | otg_fs_wkup: InterruptHandlerFn = default_handler, // USB On-The-Go FS Wakeup through EXTI line interrupt (0x0000_00E8) 87 | tim8_brk_tim12: InterruptHandlerFn = default_handler, // TIM8 Break interrupt and TIM12 global interrupt (0x0000_00EC) 88 | tim8_up_tim13: InterruptHandlerFn = default_handler, // TIM8 Update interrupt and TIM13 global interrupt (0x0000_00F0) 89 | tim8_trg_com_tim14: InterruptHandlerFn = default_handler, // TIM8 Trigger and Communication interrupts and TIM14 global interrupt (0x0000_00F4) 90 | tim8_cc: InterruptHandlerFn = default_handler, // TIM8 Capture Compare interrupt (0x0000_00F8) 91 | dma1_stream7: InterruptHandlerFn = default_handler, // DMA1 Stream7 global interrupt (0x0000_00FC) 92 | fsmc: InterruptHandlerFn = default_handler, // FSMC global interrupt (0x0000_0100) 93 | sdio: InterruptHandlerFn = default_handler, // SDIO global interrupt (0x0000_0104) 94 | tim5: InterruptHandlerFn = default_handler, // TIM5 global interrupt (0x0000_0108) 95 | spi3: InterruptHandlerFn = default_handler, // SPI3 global interrupt (0x0000_010C) 96 | usart4: InterruptHandlerFn = default_handler, // USART4 global interrupt (0x0000_0110) 97 | usart5: InterruptHandlerFn = default_handler, // USART5 global interrupt (0x0000_0114) 98 | tim6_dac: InterruptHandlerFn = default_handler, // TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts (0x0000_0118) 99 | tim7: InterruptHandlerFn = default_handler, // TIM7 global interrupt (0x0000_011C) 100 | dma2_stream0: InterruptHandlerFn = default_handler, // DMA2 Stream0 global interrupt (0x0000_0120) 101 | dma2_stream1: InterruptHandlerFn = default_handler, // DMA2 Stream1 global interrupt (0x0000_0124) 102 | dma2_stream2: InterruptHandlerFn = default_handler, // DMA2 Stream2 global interrupt (0x0000_0128) 103 | dma2_stream3: InterruptHandlerFn = default_handler, // DMA2 Stream3 global interrupt (0x0000_012C) 104 | dma2_stream4: InterruptHandlerFn = default_handler, // DMA2 Stream4 global interrupt (0x0000_0130) 105 | eth: InterruptHandlerFn = default_handler, // Ethernet global interrupt (0x0000_0134) 106 | eth_wkup: InterruptHandlerFn = default_handler, // Ethernet Wakeup through EXTI line interrupt (0x0000_0138) 107 | can2_tx: InterruptHandlerFn = default_handler, // CAN2 TX interrupts (0x0000_013C) 108 | can2_rx0: InterruptHandlerFn = default_handler, // CAN2 RX0 interrupts (0x0000_0140) 109 | can2_rx1: InterruptHandlerFn = default_handler, // CAN2 RX1 interrupt (0x0000_0144) 110 | can2_sce: InterruptHandlerFn = default_handler, // CAN2 SCE interrupt (0x0000_0148) 111 | otg_fs: InterruptHandlerFn = default_handler, // USB On the Go FS global interrupt (0x0000_014C) 112 | dma2_stream5: InterruptHandlerFn = default_handler, // DMA2 Stream5 global interrupt (0x0000_0150) 113 | dma2_stream6: InterruptHandlerFn = default_handler, // DMA2 Stream6 global interrupt (0x0000_0154) 114 | dma2_stream7: InterruptHandlerFn = default_handler, // DMA2 Stream7 global interrupt (0x0000_0158) 115 | usart6: InterruptHandlerFn = default_handler, // USART6 global interrupt (0x0000_015C) 116 | i2c3_ev: InterruptHandlerFn = default_handler, // I^2C3 event interrupt (0x0000_0160) 117 | i2c3_er: InterruptHandlerFn = default_handler, // I^2C3 error interrupt (0x0000_0164) 118 | otg_hs_epi_out: InterruptHandlerFn = default_handler, // USB On-The-Go HS End Point 1 out interrupt (0x0000_0168) 119 | otg_hs_epi_in: InterruptHandlerFn = default_handler, // USB On-The-Go HS End Point 1 in interrupt (0x0000_016C) 120 | otg_hs_wkup: InterruptHandlerFn = default_handler, // USB On-The-Go HS Wakeup through EXTI line interrupt (0x0000_0170) 121 | otg_hs: InterruptHandlerFn = default_handler, // USB On-The-Go HS Wakeup global interrupt (0x0000_0174) 122 | dcmi: InterruptHandlerFn = default_handler, // DCMI global interrupt (0x0000_0178) 123 | cryp: InterruptHandlerFn = default_handler, // CRYP crypto global interrupt (0x0000_017C) 124 | hash_rng: InterruptHandlerFn = default_handler, // HASH and Rng global interrupt (0x0000_0180) 125 | fpu: InterruptHandlerFn = default_handler, // FPU global interrupt (0x0000_0184) 126 | }; 127 | 128 | const vector_table: VectorTable = .{ .initial_sp_value = &_stack, .reset = reset_handler }; 129 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | // Although this function looks imperative, note that its job is to 4 | // declaratively construct a build graph that will be executed by an external 5 | // runner. 6 | pub fn build(b: *std.Build) void { 7 | var target = b.resolveTargetQuery(.{ 8 | .cpu_arch = .thumb, // 9 | .os_tag = .freestanding, 10 | .abi = .eabihf, 11 | .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m4 }, 12 | .cpu_features_add = std.Target.arm.featureSet(&[_]std.Target.arm.Feature{std.Target.arm.Feature.v7em}) 13 | }); 14 | 15 | const optimize = b.standardOptimizeOption(.{}); 16 | 17 | const shared_lib = b.addModule("shared", .{ 18 | .root_source_file = b.path("shared/shared.zig"), 19 | .target = target, 20 | .optimize = optimize 21 | }); 22 | 23 | 24 | const bootloader = b.addExecutable(.{ 25 | .name = "bootloader.elf", 26 | .root_source_file = b.path("bootloader/src/main.zig"), 27 | .target = target, 28 | .optimize = optimize, 29 | .link_libc = false, 30 | .linkage = .static, 31 | .single_threaded = true, 32 | }); 33 | 34 | 35 | bootloader.root_module.addImport("shared", shared_lib); 36 | bootloader.setLinkerScript(b.path("bootloader/linkerscript.ld")); 37 | bootloader.addLibraryPath(.{ .cwd_relative = "/usr/arm-none-eabi/lib" }); 38 | bootloader.addLibraryPath(.{ .cwd_relative = "/usr/lib/gcc/arm-none-eabi/13.2.0/thumb/v7e-m+fp/hard/" }); 39 | bootloader.link_gc_sections = true; 40 | bootloader.link_function_sections = true; 41 | bootloader.link_data_sections = true; 42 | 43 | 44 | b.installArtifact(bootloader); 45 | const copy_bin = b.addObjCopy(bootloader.getEmittedBin(), .{ .format = .bin, .pad_to = 0x8000 }); 46 | const bootloader_bin_install = b.addInstallBinFile(copy_bin.getOutput(), "bootloader.bin"); 47 | copy_bin.step.dependOn(&bootloader.step); 48 | b.default_step.dependOn(©_bin.step); 49 | 50 | 51 | // Firmware Section 52 | 53 | const firmware = b.addExecutable(.{ 54 | .name = "firmware.elf", 55 | .root_source_file = b.path("app/src/main.zig"), 56 | .target = target, 57 | .optimize = optimize, 58 | .link_libc = false, 59 | .linkage = .static, 60 | .single_threaded = true, 61 | }); 62 | 63 | 64 | firmware.root_module.addImport("shared", shared_lib); 65 | firmware.setLinkerScript(b.path("app/linkerscript.ld")); 66 | firmware.addLibraryPath(.{ .cwd_relative = "/usr/arm-none-eabi/lib" }); 67 | firmware.addLibraryPath(.{ .cwd_relative = "/usr/lib/gcc/arm-none-eabi/13.2.0/thumb/v7e-m+fp/hard/" }); 68 | firmware.link_gc_sections = true; 69 | firmware.link_function_sections = true; 70 | firmware.link_data_sections = true; 71 | 72 | 73 | const firmware_elf_install = b.addInstallArtifact(firmware, .{}); 74 | 75 | firmware_elf_install.step.dependOn(&bootloader_bin_install.step); 76 | 77 | const firmware_bin = b.addObjCopy(firmware.getEmittedBin(), .{ .format = .bin }); 78 | const firmware_bin_install = b.addInstallBinFile(firmware_bin.getOutput(), "firmware.bin"); 79 | 80 | firmware_bin_install.step.dependOn(&firmware_elf_install.step); 81 | 82 | 83 | b.default_step.dependOn(&firmware_bin_install.step); 84 | 85 | const remote_debug = b.addSystemCommand(&.{ 86 | "openocd","-f", "/usr/share/openocd/scripts/interface/stlink-v2.cfg", "-f", "/usr/share/openocd/scripts/target/stm32f4x.cfg", 87 | }); 88 | 89 | b.step("debug", "Start OpenOCD GDB server on port :3333").dependOn(&remote_debug.step); 90 | 91 | 92 | const flash = b.addSystemCommand(&.{ 93 | "sudo", "st-flash", "--reset", "write", "zig-out/bin/firmware.bin", "0x8000000" 94 | }); 95 | 96 | flash.step.dependOn(b.default_step); 97 | b.step("flash", "Flash firmware").dependOn(&flash.step); 98 | 99 | const dump = b.addSystemCommand(&.{ "llvm-objdump", "-D", "zig-out/bin/firmware.elf" }); 100 | 101 | b.step("dump", "Dump Object File").dependOn(&dump.step); 102 | 103 | target = b.standardTargetOptions(.{}); 104 | 105 | 106 | const shared_static_lib = b.addStaticLibrary(.{ 107 | .name = "shared", 108 | .root_source_file = b.path("shared/shared.zig"), 109 | .target = target, 110 | .optimize = optimize 111 | }); 112 | 113 | b.installArtifact(shared_static_lib); 114 | 115 | const lib_unit_tests = b.addTest(.{ 116 | .root_source_file = b.path("shared/core/ringbuffer.zig"), 117 | .target = target, 118 | .optimize = optimize 119 | }); 120 | 121 | const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); 122 | 123 | const exe_unit_tests = b.addTest(.{ 124 | .root_source_file = b.path("shared/core/ringbuffer.zig"), 125 | .target = target, 126 | .optimize = optimize 127 | }); 128 | 129 | const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); 130 | 131 | 132 | const test_step = b.step("test", "Run unit tests"); 133 | test_step.dependOn(&run_lib_unit_tests.step); 134 | test_step.dependOn(&run_exe_unit_tests.step); 135 | 136 | 137 | const updater_exe = b.addExecutable(.{ 138 | .name = "updater", 139 | .target = b.resolveTargetQuery(.{}), 140 | .root_source_file = b.path("tools/updater.zig"), 141 | }); 142 | 143 | const serial = b.dependency("serial", .{ 144 | // .root_source_file = b.path("dependencies/serial/src/serial.zig"), 145 | // .target = b.resolveTargetQuery(.{}), 146 | // .optimize = .Debug, 147 | }); 148 | const collections_lib = b.addModule("shared", .{ 149 | .root_source_file = b.path("shared/collections.zig"), 150 | .target = target, 151 | .optimize = optimize 152 | }); 153 | 154 | 155 | updater_exe.root_module.addImport("shared", collections_lib); 156 | updater_exe.root_module.addImport("serial", serial.module("serial")); 157 | // updater_exe.addIn 158 | 159 | b.installArtifact(updater_exe); 160 | 161 | const run_updater = b.addRunArtifact(updater_exe); 162 | 163 | const run_updater_step = b.step("update", "Update the firmware"); 164 | run_updater_step.dependOn(&run_updater.step); 165 | } 166 | -------------------------------------------------------------------------------- /shared/collections.zig: -------------------------------------------------------------------------------- 1 | pub const collections = @import("core/ringbuffer.zig"); 2 | -------------------------------------------------------------------------------- /shared/core/comm/usart.zig: -------------------------------------------------------------------------------- 1 | pub const Usart = struct { 2 | const Self = @This(); 3 | /// Status Register 4 | /// Offset: 0x00 5 | SR: u32, 6 | /// Data Register 7 | /// Offset: 0x04 8 | DR: u32, 9 | /// Baud Rate Register 10 | /// Offset: 0x08 11 | BRR: u32, 12 | /// Control 1 Register | usart.CR1 for settings 13 | /// Offset: 0x0C 14 | CR1: u32, 15 | /// Control 2 Register 16 | /// Offset: 0x10 17 | CR2: u32, 18 | /// Control 3 Register 19 | /// Offset: 0x14 20 | CR3: u32, 21 | /// Guard Time & Prescaler Register 22 | /// Offset: 0x18 23 | GTPR: u32, 24 | 25 | 26 | pub inline fn read_ready(self: Self) bool { 27 | const RXNE: u32 = (1 << 5); // Read Data Register Not Empty 28 | // 0: Data is not received | 1: Received data is read to be read 29 | if ((self.SR & RXNE) == 1) { 30 | return true; 31 | } else { 32 | return false; 33 | } 34 | } 35 | 36 | pub inline fn read_byte(self: Self) u8 { 37 | return (self.DR & 255); 38 | } 39 | 40 | pub inline fn write_byte(self: *volatile Self, byte: u8) void { 41 | self.DR = byte; 42 | const TXE: u32 = (1 << 7); // Transmit Data Register Empty 43 | // 0: Data not transferd | 1: Data is transfered 44 | // Wait for transmission to end when TXE == 1 45 | while (self.SR & TXE == 0) asm volatile ("nop"); 46 | } 47 | 48 | pub inline fn write_buffer(self: *volatile Self, buffer: []const u8) void { 49 | for (buffer) |byte| { 50 | self.write_byte(byte); 51 | } 52 | } 53 | }; 54 | 55 | /// Control Register Settings 56 | pub const CR1 = enum { 57 | /// Oversampling mode 58 | pub const OVER8: u32 = (1 << 15); 59 | /// Usart Enable 60 | pub const UE: u32 = (1 << 13); 61 | /// Word length 62 | pub const M: u32 = (1 << 12); 63 | /// Wakeup method 64 | pub const WAKE: u32 = (1 << 11); 65 | /// Parity Control Enable 66 | pub const PCE: u32 = (1 << 10); 67 | /// Parity Selection 68 | pub const PS: u32 = (1 << 9); 69 | /// PEIE Interrupt Enable 70 | pub const PEIE: u32 = (1 << 8); 71 | /// TXE Interrupt Enable 72 | pub const TXEIE: u32 = (1 << 7); 73 | /// Transmisson Complete Interrupt Enable 74 | pub const TCIE: u32 = (1 << 6); 75 | /// RXNE Interrupt Enable 76 | pub const RXNEIE: u32 = (1 << 5); 77 | /// Idle Interrupt Enable 78 | pub const IDLEIE: u32 = (1 << 4); 79 | /// Transmitter Enable 80 | pub const TE: u32 = (1 << 3); 81 | /// Receiver Enable 82 | pub const RE: u32 = (1 << 2); 83 | /// Receiver Wakeup 84 | pub const RWU: u32 = (1 << 1); 85 | 86 | /// Send Break 87 | pub const SBK: u32 = (1 << 0); 88 | }; 89 | -------------------------------------------------------------------------------- /shared/core/gpio.zig: -------------------------------------------------------------------------------- 1 | const map = @import("memory_map.zig"); 2 | pub const GPIOA_BASE: u32 = map.AHB1 + 0x0000; 3 | pub const GPIOB_BASE: u32 = map.AHB1 + 0x0400; 4 | pub const GPIOC_BASE: u32 = map.AHB1 + 0x0800; 5 | pub const GPIOD_BASE: u32 = map.AHB1 + 0x0C00; 6 | pub const GPIOE_BASE: u32 = map.AHB1 + 0x1000; 7 | pub const GPIOF_BASE: u32 = map.AHB1 + 0x1400; 8 | pub const GPIOG_BASE: u32 = map.AHB1 + 0x1800; 9 | pub const GPIOH_BASE: u32 = map.AHB1 + 0x1C00; 10 | 11 | 12 | 13 | /// Represents the layout of a GPIO Port and its registers 14 | pub const Port = struct { 15 | /// Mode Register 16 | MODER: u32, 17 | /// Output Type Register 18 | OTYPER: u32, 19 | /// Output Speed Register 20 | OSPEEDR: u32, 21 | /// Output Pull-up/Pull-down Register 22 | PUPDR: u32, 23 | /// Input Data Register 24 | IDR: u32, 25 | /// Output Data Register 26 | ODR: u32, 27 | /// Bit Set/Reset Register 28 | BSRR: u32, 29 | /// Lock Register 30 | LCKR: u32, 31 | /// Alternate Function Low Register 32 | /// AFR[0] Handles pins 0..7 33 | /// AFR[1] Handles pins 8..15 34 | AFR: [2]u32, 35 | 36 | /// 37 | pub inline fn set_mode(self: *volatile Port, pin: u8, mode: MODE) void { 38 | _ = 0b00000011_00000011; 39 | _ = 0b00000000_11111111; 40 | self.MODER &= ~(@as(u32, 3) << (pin) * 2); 41 | self.MODER |= (@intFromEnum(mode) & @as(u32, 3)) << (pin * 2); 42 | } 43 | 44 | /// Set Alternate Function 45 | pub inline fn set_af(self: *volatile Port, pin: u8, af_num: u8) void { 46 | // Note to self, anything pin below 7 or 0b0111 will result to 0 if (pin >> 3) and anything above will result to 1 47 | // Put another way (pin / 2^3) 48 | // Everything in the () is to find the 4bit block we want and the clear it 49 | self.AFR[pin >> 3] &= ~(@as(u32, 15) << (pin & 7) * 4); // and shift into place by 2 (2^2) 50 | 51 | // Select the 4bit block and add the alternate function value (no overwriting) 52 | self.AFR[pin >> 3] |= @as(u32, af_num) << ((pin & 7) * 4); 53 | } 54 | }; 55 | 56 | pub const MODE = enum(u8) { 57 | INPUT, 58 | OUTPUT, 59 | AF, 60 | ANALOG, 61 | }; 62 | 63 | -------------------------------------------------------------------------------- /shared/core/memory_map.zig: -------------------------------------------------------------------------------- 1 | /// Code | ARMv7-M 2 | /// Address: 0x0000_0000 3 | pub const CODE_BASE: u32 = 0x0000_0000; 4 | pub const FLASH_BASE: u32 = 0x0800_0000; 5 | pub const SYS_MEM_BASE:u32 = 0x1FFF_0000; 6 | pub const PERIPH_BASE: u32 = 0x4000_0000; 7 | /// Advanced Peripheral Bus 1 8 | /// Address: 0x4000_0000 9 | pub const APB1: u32 = PERIPH_BASE; 10 | /// Advanced Peripheral Bus 2 11 | /// Address: 0x4001_0000 12 | pub const APB2: u32 = PERIPH_BASE + 0x1_0000; 13 | /// Advanced High Peripheral Bus 1 14 | /// Address: 0x4002_0000 15 | pub const AHB1: u32 = PERIPH_BASE + 0x2_0000; 16 | /// Advanced High Peripheral Bus 2 17 | /// Address: 0x5000_0000 18 | pub const AHB2: u32 = PERIPH_BASE + 0x1000_0000; 19 | /// Advanced High Peripheral Bus 3 20 | /// Address: 0x6000_0000 21 | pub const AHB3: u32 = PERIPH_BASE + 0x2000_0000; 22 | 23 | 24 | // RCC Branch 25 | 26 | /// Reset Clock Control Base 27 | /// Address: 0x4002_3800 28 | pub const RCC_BASE: u32 = AHB1 + 0x3800; 29 | /// RCC clock control register 30 | /// Address: 0x4002_3800 31 | pub const RCC_CR: u32 = RCC_BASE + 0x00; 32 | /// RCC Reset Register 33 | /// Address: 0x4002_3810 34 | pub const RCC_AHB1_RSTR: u32 = RCC_BASE + 0x10; 35 | /// RCC Reset Register 36 | /// Address: 0x4002_3814 37 | pub const RCC_AHB2_RSTR: u32 = RCC_BASE + 0x14; 38 | /// RCC Reset Register 39 | /// Address: 0x4002_3814 40 | pub const RCC_AHB3_RSTR: u32 = RCC_BASE + 0x18; 41 | /// RCC Reset Register 42 | /// Address: 0x4002_3820 43 | pub const RCC_APB1_RSTR: u32 = RCC_BASE + 0x20; 44 | /// RCC Reset Register 45 | /// Address: 0x4002_3824 46 | pub const RCC_APB2_RSTR: u32 = RCC_BASE + 0x24; 47 | /// RCC Enable Register 48 | /// Address: 0x4002_3830 49 | pub const RCC_AHB1_ENR: u32 = RCC_BASE + 0x30; 50 | /// RCC Enable Register 51 | /// Address: 0x4002_3834 52 | pub const RCC_AHB2_ENR: u32 = RCC_BASE + 0x34; 53 | /// RCC Enable Register 54 | /// Address: 0x4002_3838 55 | pub const RCC_APB3_ENR: u32 = RCC_BASE + 0x38; 56 | /// RCC Enable Register 57 | /// Address: 0x4002_3840 58 | pub const RCC_APB1_ENR: u32 = RCC_BASE + 0x40; 59 | /// RCC Enable Register 60 | /// Address: 0x4002_3844 61 | pub const RCC_APB2_ENR: u32 = RCC_BASE + 0x44; 62 | 63 | // GPIO 64 | 65 | /// General Purpose I/O A Base 66 | /// Address: 0x4002_0000 67 | pub const GPIOA_BASE: u32 = AHB1 + 0x0000; 68 | /// General Purpose I/O B Base 69 | /// Address: 0x4002_0400 70 | pub const GPIOB_BASE: u32 = AHB1 + 0x0400; 71 | /// General Purpose I/O C Base 72 | /// Address: 0x4002_0800 73 | pub const GPIOC_BASE: u32 = AHB1 + 0x0800; 74 | /// General Purpose I/O D Base 75 | /// Address: 0x4002_0C00 76 | pub const GPIOD_BASE: u32 = AHB1 + 0x0C00; 77 | /// General Purpose I/O E Base 78 | /// Address: 0x4002_1000 79 | pub const GPIOE_BASE: u32 = AHB1 + 0x1000; 80 | /// General Purpose I/O F Base 81 | /// Address: 0x4002_1400 82 | pub const GPIOF_BASE: u32 = AHB1 + 0x1400; 83 | /// General Purpose I/O G Base 84 | /// Address: 0x4002_1800 85 | pub const GPIOG_BASE: u32 = AHB1 + 0x1800; 86 | /// General Purpose I/O H Base 87 | /// Address: 0x4002_1C00 88 | pub const GPIOH_BASE: u32 = AHB1 + 0x1C00; 89 | 90 | 91 | /// Cortex-M4 Internal Peripheral | ARMv7-M 92 | /// Private Peripheral BUS Internal | ARMv7-M 93 | /// Address: 0xE000_0000 94 | pub const PPBI_BASE: u32 = 0xE000_0000; 95 | /// Cortex-M4 External Peripheral | ARMv7-M 96 | /// Private Peripheral BUS External | ARMv7-M 97 | /// Address: 0xE004_0000 98 | pub const PPBE_BASE: u32 = 0xE004_0000; 99 | /// System Control Space | ARMv7-M 100 | /// Address: 0xE000_E000 101 | pub const SCS_BASE: u32 = PPBI_BASE + 0xE000; 102 | /// System Control Block | ARMv7-M 103 | /// Address: 0xE000_ED00 104 | pub const SCB_BASE: u32 = SCS_BASE + 0x0D00; 105 | 106 | 107 | 108 | /// System Control Space | ARMv7-M 109 | pub const SCS = struct { 110 | /// SysTick Control and Status Register | ARMv7-M 111 | /// Address: 0xE000_E010 112 | pub const STCSR: u32 = SCS_BASE + 0x10; 113 | /// SysTick Reload Value Register | ARMv7-M 114 | /// Address: 0xE000_E014 115 | pub const STRVR: u32 = SCS_BASE + 0x14; 116 | /// SysTick Current Value Register | ARMv7-M 117 | /// Address: 0xE000_E018 118 | pub const STCVR: u32 = SCS_BASE + 0x18; 119 | /// SysTick Calibration Register | ARMv7-M 120 | /// Address: 0xE000_E01C 121 | pub const STCR: u32 = SCS_BASE + 0x1C; 122 | }; 123 | 124 | /// System Control Block | ARMv7-M 125 | pub const SCB = struct { 126 | /// CPUID Base Register | ARMv7-M 127 | /// https://developer.arm.com/documentation/100235/0004/the-cortex-m33-peripherals/system-control-block/cpuid-base-register?lang=en 128 | /// Address: 0xE000_ED00 129 | pub const CPUID: u32 = SCB_BASE + 0x00; 130 | /// Interrupt Control and State Register | ARMv7-M 131 | /// Address: 0xE000_ED04 132 | pub const ICSR: u32 = SCB_BASE + 0x04; 133 | /// Vector Table Offset Register | ARMv7-M 134 | /// https://developer.arm.com/documentation/100235/0004/the-cortex-m33-peripherals/system-control-block/vector-table-offset-register?lang=en 135 | /// Address: 0xE000_ED08 136 | pub const VTOR: u32 = SCB_BASE + 0x08; 137 | /// Application Interrupt and Reset Control Register | ARMv7-M 138 | /// Address: 0xE000_ED0C 139 | pub const AIRCR: u32 = SCB_BASE + 0x0C; 140 | /// System Control Register | ARMv7-M 141 | /// Address: 0xE000_ED10 142 | pub const SCR: u32 = SCB_BASE + 0x10; 143 | /// Configuration Control Register | ARMv7-M 144 | /// Address: 0xE000_ED14 145 | pub const CCR: u32 = SCB_BASE + 0x14; 146 | /// System Handler Priority Register 1 | ARMv7-M 147 | /// MemManage, BusFault, UsageFault, SecureFault handlers 148 | /// https://developer.arm.com/documentation/100235/0004/the-cortex-m33-peripherals/system-control-block/system-handler-priority-registers?lang=en 149 | /// Address: 0xE000_ED18 150 | pub const SHPR1: u32 = SCB_BASE + 0x18; 151 | /// System Handler Priority Register 2 | ARMv7-M 152 | /// SVCall handler 153 | /// https://developer.arm.com/documentation/100235/0004/the-cortex-m33-peripherals/system-control-block/system-handler-priority-registers?lang=en 154 | /// Address: 0xE000_ED1C 155 | pub const SHPR2: u32 = SCB_BASE + 0x1C; 156 | /// System Handler Priority Register 3 | ARMv7-M 157 | /// PendSV, SysTick handlers 158 | /// https://developer.arm.com/documentation/100235/0004/the-cortex-m33-peripherals/system-control-block/system-handler-priority-registers?lang=en 159 | /// Address: 0xE000_ED20 160 | pub const SHPR3: u32 = SCB_BASE + 0x20; 161 | }; 162 | 163 | /// Universial Synchronous/Asynchronous Receive/Transmitter 1 164 | /// Address: 0x4001_0000 165 | pub const TIM1: u32 = APB2 + 0x0000; 166 | /// Address: 0x4001_0400 167 | pub const TIM8: u32 = APB2 + 0x4000; 168 | /// Universial Synchronous/Asynchronous Receive/Transmitter 1 169 | /// Address: 0x4001_1000 170 | pub const USART1: u32 = APB2 + 0x1000; 171 | /// Universial Synchronous/Asynchronous Receive/Transmitter 6 172 | /// Address: 0x4001_1400 173 | pub const USART6: u32 = APB2 + 0x1400; 174 | 175 | 176 | /// Universial Synchronous/Asynchronous Receive/Transmitter 1 177 | /// Address: 0x4000_4400 178 | pub const USART2: u32 = APB1 + 0x4400; 179 | /// Universial Synchronous/Asynchronous Receive/Transmitter 1 180 | /// Address: 0x4000_4800 181 | pub const USART3: u32 = APB1 + 0x4800; 182 | /// Universial Synchronous/Asynchronous Receive/Transmitter 1 183 | /// Address: 0x4000_4C00 184 | pub const USART4: u32 = APB1 + 0x4C00; 185 | /// Universial Synchronous/Asynchronous Receive/Transmitter 1 186 | /// Address: 0x4000_5000 187 | pub const USART5: u32 = APB1 + 0x5000; 188 | -------------------------------------------------------------------------------- /shared/core/rcc.zig: -------------------------------------------------------------------------------- 1 | const MAP = @import("memory_map.zig"); 2 | 3 | pub const RCC = struct { 4 | /// Control Register 5 | /// Offset: 0x00 6 | CR: u32, 7 | /// PLL Config Register 8 | /// Offset: 0x04 9 | PLLCFGR: u32, 10 | /// Clock Config Register 11 | /// Offset: 0x08 12 | CFGR: u32, 13 | /// Interrupt Register 14 | /// Offset: 0x0C 15 | CIR: u32, 16 | /// AHB1 Reset Register 17 | /// Offset: 0x10 18 | AHB1RSTR: u32, 19 | /// AHB2 Reset Register 20 | /// Offset: 0x14 21 | AHB2RSTR: u32, 22 | /// AHB3 Reset Register 23 | /// Offset: 0x18 24 | AHB3RSTR: u32, 25 | /// Offset: 0x1C 26 | RESERVED0: u32, 27 | /// APB1 Reset Register 28 | /// Offset: 0x20 29 | APB1RSTR: u32, 30 | /// APB2 Reset Register 31 | /// Offset: 0x24 32 | APB2RSTR: u32, 33 | /// Offset: 0x28 34 | /// Offset: 0x2C 35 | RESERVED1: [2]u32, 36 | /// AHB1 Enable Register 37 | /// Offset: 0x30 38 | AHB1ENR: u32, 39 | /// AHB2 Enable Register 40 | /// Offset: 0x34 41 | AHB2ENR: u32, 42 | /// AHB3 Enable Register 43 | /// Offset: 0x38 44 | AHB3ENR: u32, 45 | /// Offset: 0x3C 46 | RESERVED2: u32, 47 | /// APB1 Enable Register 48 | /// Offset: 0x40 49 | APB1ENR: u32, 50 | /// APB2 Enable Register 51 | /// Offset: 0x44 52 | APB2ENR: u32, 53 | /// Offset: 0x48 54 | /// Offset: 0x4C 55 | RESERVED3: [2]u32, 56 | /// AHB1 Low Power Mode Enable Register 57 | /// Offset: 0x50 58 | AHB1LPENR: u32, 59 | /// AHB2 Low Power Mode Enable Register 60 | /// Offset: 0x54 61 | AHB2LPENR: u32, 62 | /// AHB3 Low Power Mode Enable Register 63 | /// Offset: 0x58 64 | AHB3LPENR: u32, 65 | /// Offset: 0x5C 66 | RESERVED4: u32, 67 | /// APB1 Low Power Mode Enable Register 68 | /// Offset: 0x60 69 | APB1LPENR: u32, 70 | /// APB2 Low Power Mode Enable Register 71 | /// Offset: 0x64 72 | APB2LPENR: u32, 73 | /// Offset: 0x68 74 | /// Offset: 0x6C 75 | RESERVED5: [2]u32, 76 | /// Backup Domain Control Register 77 | /// Offset: 0x70 78 | BDCR: u32, 79 | /// Control & Status Register 80 | /// Offset: 0x74 81 | CSR: u32, 82 | /// Offset: 0x78 83 | /// Offset: 0x7C 84 | RESERVED6: [2]u32, 85 | /// Spread Spectrum Clock Generation Register 86 | /// Offset: 0x80 87 | SSCGR: u32, 88 | /// PLLI2S Config Register 89 | /// Offset: 0x84 90 | PLLI2SCFGR: u32, 91 | /// PLL Config Register 92 | /// Offset: 0x84 93 | PLLSAICFGR: u32, 94 | /// Dedicated Clock Config Register 95 | DCKCFGR: u32, 96 | }; 97 | -------------------------------------------------------------------------------- /shared/core/ringbuffer.zig: -------------------------------------------------------------------------------- 1 | pub fn RingBuffer(comptime T: type, size: comptime_int) type { 2 | return struct { 3 | const Self = @This(); 4 | 5 | read_index: usize, 6 | write_index: usize, 7 | buffer: [size]T, 8 | length: usize, 9 | /// For resetting the write head 10 | /// Since this is Zig &= or some wrapping operation could be used, but I wanted to represent a C way of doing things 11 | mask: usize, 12 | 13 | pub fn init() Self { 14 | return Self{ 15 | .read_index = 0, 16 | .write_index = 0, 17 | .buffer = undefined, 18 | .length = 0, 19 | .mask = size - 1, 20 | }; 21 | } 22 | 23 | // 0b0000 = 0b1000 & 0b0111; 24 | pub fn blocking_write(self: *Self, item: T) ?void { 25 | const next_write_index = (self.write_index+1) & self.mask; 26 | 27 | if (next_write_index == self.read_index) { 28 | // Write head cannot be equal or ahead of Read Head 29 | // Invalidated ring buffer 30 | return null; 31 | } 32 | 33 | 34 | self.buffer[self.write_index] = item; 35 | self.write_index = next_write_index; 36 | self.length += 1; 37 | } 38 | 39 | /// Return old data, could also just discard 40 | pub fn non_blocking_write(self: *Self, item: T) void { 41 | const next_write_index = (self.write_index+1) & self.mask; 42 | // var old_value: ?T = null; 43 | 44 | // Overwrite data in read_index 45 | if (next_write_index == self.write_index) { 46 | // old_value = self.buffer[self.read_index]; 47 | self.read_index = (self.read_index + 1) & self.mask; 48 | // self.write_index = ne 49 | } 50 | 51 | self.buffer[self.write_index] = item; 52 | self.write_index = next_write_index; 53 | 54 | if (self.length < self.buffer.len) { 55 | self.length += 1; 56 | } 57 | } 58 | 59 | 60 | pub fn read(self: *Self) ?T { 61 | if (self.read_index == self.write_index) { 62 | // Buffer empty 63 | return null; 64 | } 65 | 66 | const value = self.buffer[self.read_index]; 67 | self.read_index = (self.read_index + 1) & self.mask; 68 | self.length -= 1; 69 | return value; 70 | } 71 | 72 | 73 | /// Reset indices to 0 and drop buffer 74 | pub fn clear(self: *Self) void { 75 | self.write_index = 0; 76 | self.read_index = 0; 77 | self.buffer = undefined; 78 | } 79 | }; 80 | } 81 | 82 | 83 | 84 | 85 | 86 | const std = @import("std"); 87 | const testing = std.testing; 88 | const mem = std.mem; 89 | const print = std.debug.print; 90 | 91 | 92 | 93 | // Expected behavior: Write and Read integer 94 | test "write" { 95 | var r = RingBuffer(u8, 8).init(); 96 | 97 | _ = r.blocking_write(32); 98 | 99 | try testing.expect(32 == r.read()); 100 | 101 | r.clear(); 102 | 103 | _ = r.blocking_write(32); 104 | 105 | // print("{any}\n", .{r.buffer}); 106 | try testing.expect(32 == r.read()); 107 | 108 | // Expected behavior: Write and Read non-integer 109 | // test "insertion non-integer" { 110 | 111 | var ri = RingBuffer([]const u8, 8).init(); 112 | 113 | _ = ri.blocking_write("hello"); 114 | 115 | // print("{any}\n", .{ring.buffer}); 116 | try testing.expectEqual("hello", ri.read().?); 117 | 118 | // Expected behavior: Write and Read non-integer 119 | // test "insertion Type" { 120 | 121 | const Packet = struct { byte: u8 }; 122 | 123 | var rin = RingBuffer(Packet, 8).init(); 124 | 125 | _ = rin.blocking_write(Packet{.byte = 'U'}); 126 | 127 | try testing.expectEqualDeep(Packet{.byte = 'U'}, rin.read().?); 128 | 129 | 130 | // Expected behavior: Write and Read integer 131 | // test "insertion non-blocking" { 132 | 133 | var ring = RingBuffer(u8, 8).init(); 134 | 135 | _ = ring.non_blocking_write(32); 136 | 137 | // print("{any}\n", .{ring.buffer}); 138 | try testing.expect(32 == ring.read()); 139 | 140 | 141 | 142 | // Expected behavior: Write and Read non-integer 143 | // test "insertion non-blocking non-integer" { 144 | 145 | var rb = RingBuffer([]const u8, 8).init(); 146 | 147 | _ = rb.non_blocking_write("hello"); 148 | 149 | // print("{any}\n", .{ring.buffer}); 150 | try testing.expectEqual("hello", rb.read().?); 151 | 152 | 153 | // Expected behavior: Write and Read non-integer 154 | // test "insertion non-blocking Type" { 155 | 156 | var rbg = RingBuffer(Packet, 8).init(); 157 | 158 | _ = rbg.non_blocking_write(Packet{.byte = 'U'}); 159 | 160 | try testing.expectEqualDeep(Packet{.byte = 'U'}, rbg.read().?); 161 | } 162 | 163 | test "overflow" { 164 | var ring = RingBuffer(usize, 16).init(); 165 | 166 | for (0..18) |i| { 167 | _ = ring.blocking_write(@intCast(i)); 168 | } 169 | 170 | // print("{any}\n", .{ring.buffer}); 171 | 172 | try testing.expect(ring.buffer[9] == 9); 173 | try testing.expect(ring.buffer[0] == 16); 174 | try testing.expect(ring.buffer[1] == 17); 175 | try testing.expect(ring.buffer[2] == 2); 176 | } 177 | 178 | // Expected behavior: 179 | test "insertion at overflow - blocking" { 180 | var ring = RingBuffer(usize, 16).init(); 181 | 182 | for (0..18) |i| { 183 | _ = ring.blocking_write(@intCast(i)); 184 | } 185 | 186 | try testing.expect(ring.buffer[9] == 9); 187 | try testing.expect(ring.buffer[0] == 16); 188 | try testing.expect(ring.buffer[1] == 17); 189 | try testing.expect(ring.buffer[2] == 2); 190 | } 191 | 192 | // Expected behavior: 193 | test "insertion at overflow - non-blocking" { 194 | var ring = RingBuffer(usize, 16).init(); 195 | 196 | for (0..31) |i| { 197 | _ = ring.non_blocking_write(@intCast(i)); 198 | 199 | if (i == 22) { 200 | for (0..5) |_| { 201 | _ = ring.read(); 202 | } 203 | _ = ring.non_blocking_write(@intCast(100)); 204 | } 205 | } 206 | 207 | print("{any}\n", .{ring.read().?}); 208 | // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 209 | // 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 210 | try testing.expect(ring.buffer[5] == 22); 211 | try testing.expect(ring.read().? == 21); 212 | // try testing.expect(ring.buffer[1] == 17); 213 | // try testing.expect(ring.buffer[2] == 2); 214 | } 215 | 216 | test "read from empty" {} 217 | 218 | test "index integrity after operations" {} 219 | -------------------------------------------------------------------------------- /shared/core/system.zig: -------------------------------------------------------------------------------- 1 | pub const Systick = struct { 2 | /// SysTick Control and Status Register 3 | /// Offset: 0x00 4 | CSR: u32, 5 | /// SysTick Reload Value Register 6 | /// Offset: 0x04 7 | RVR: u32, 8 | /// SysTick Current Value Register 9 | /// Offset: 0x08 10 | CVR: u32, 11 | /// SysTick Calibration Register 12 | /// Offset: 0x0C 13 | CR: u32, 14 | }; 15 | -------------------------------------------------------------------------------- /shared/shared.zig: -------------------------------------------------------------------------------- 1 | pub const collections = @import("core/ringbuffer.zig"); 2 | pub const MAP = @import("core/memory_map.zig"); 3 | pub const GPIO = @import("core/gpio.zig"); 4 | pub const RCC = @import("core/rcc.zig"); 5 | pub const SYS = @import("core/system.zig"); 6 | pub const COMM = struct { 7 | pub const USART = @import("core/comm/usart.zig"); 8 | }; 9 | 10 | pub fn PIN(bank: u8, num: u8) u16 { 11 | return (((bank - 'A') << 8) | (num)); 12 | } 13 | 14 | pub fn PINNO(pin: u16) u16 { 15 | return pin & 0xFF; 16 | } 17 | 18 | pub fn PINBANK(pin: u16) u16 { 19 | return pin >> 8; 20 | } 21 | -------------------------------------------------------------------------------- /tools/updater.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Ringbuffer = @import("shared").collections.RingBuffer; 3 | const assert = std.debug.assert; 4 | const zig_serial = @import("serial"); 5 | 6 | // Constants for the packet protocol 7 | const PACKET_LENGTH_BYTES: u32 = 1; 8 | const PACKET_DATA_BYTES: u32 = 16; 9 | const PACKET_CRC_BYTES: u32 = 1; 10 | const PACKET_CRC_INDEX: u32 = PACKET_LENGTH_BYTES + PACKET_DATA_BYTES; 11 | const PACKET_LEN: u32 = PACKET_LENGTH_BYTES + PACKET_DATA_BYTES + PACKET_CRC_BYTES; 12 | 13 | const PACKET_ACK_DATA0: u8 = 0x15; 14 | const PACKET_RETX_DATA0: u8 = 0x19; 15 | 16 | 17 | // Details about the serial port connection 18 | const serial_path = "/dev/ttyACM0"; 19 | const baud_rate = 115200; 20 | 21 | const ACK_DEFAULT: Packet = Packet.init(1, [_]u8{PACKET_ACK_DATA0} ++ [_]u8{0xFF} ** 15, null); 22 | const RETX_DEFAULT: Packet = Packet.init(1, [_]u8{PACKET_RETX_DATA0} ++ [_]u8{0xFF} ** 15, null); 23 | 24 | fn crc8(data: []const u8) u8 { 25 | var crc: u8 = 0; 26 | for (data) |byte| { 27 | crc ^= byte; 28 | for (0..8) |_| { 29 | if (crc & 0x80 != 0) { 30 | crc = (crc << 1) ^ 0x07; 31 | } else { 32 | crc <<= 1; 33 | } 34 | } 35 | } 36 | return crc; 37 | } 38 | 39 | /// | pack_len (1 * u8) | data (16 * u8) | CRC (1 * u8) | 40 | const Packet = struct { 41 | const Self = @This(); 42 | length: u8, 43 | data: [PACKET_DATA_BYTES]u8, 44 | /// Cyclical Redudancy Check (basically a validation hash) 45 | crc: u8, 46 | /// Retransmission packet 47 | retx: ?*const Packet, 48 | /// Acknowledgement packet 49 | ack: ?*const Packet, 50 | 51 | pub fn init(length: u8, data: [PACKET_DATA_BYTES]u8, crc: ?u8) Packet { 52 | var new_crc: u8 = 0; 53 | if (crc == null) { 54 | var crc_buf = [_]u8{length} ++ data; 55 | @memcpy(crc_buf[1..], data[0..PACKET_DATA_BYTES]); 56 | new_crc = crc8(&crc_buf); 57 | } 58 | 59 | return Packet { 60 | .length = length, 61 | .data = data, 62 | .crc = crc orelse new_crc, 63 | .retx = null, 64 | .ack = null, 65 | }; 66 | } 67 | 68 | pub fn to_buffer(self: Self, buffer: *[PACKET_LEN]u8) void { 69 | // assert(self.length == PACKET_LEN); 70 | buffer[0] = self.length; 71 | @memcpy(buffer[1..PACKET_DATA_BYTES+1], self.data[0..]); 72 | buffer[PACKET_CRC_INDEX] = self.crc; 73 | } 74 | 75 | pub fn from_buffer(buffer: *const [PACKET_LEN]u8) Self { 76 | var data: [PACKET_DATA_BYTES]u8 = undefined; 77 | @memcpy(data[0..], buffer[1..17]); 78 | 79 | var new = Packet { 80 | .length = buffer[0], 81 | .data = data, 82 | .crc = buffer[PACKET_LEN-1], 83 | .retx = if (Self.single_byte_packet(buffer, PACKET_RETX_DATA0)) &RETX_DEFAULT else null, 84 | .ack = if (Self.single_byte_packet(buffer, PACKET_ACK_DATA0)) &ACK_DEFAULT else null, 85 | }; 86 | 87 | if (buffer[1] == PACKET_ACK_DATA0) { 88 | new.ack = &Packet.init(1, [_]u8{PACKET_ACK_DATA0} ++ [_]u8{0xFF} ** 15, null); 89 | } else if (buffer[1] == PACKET_RETX_DATA0) { 90 | new.retx = &Packet.init(1, [_]u8{PACKET_RETX_DATA0} ++ [_]u8{0xFF} ** 15, null); 91 | } 92 | 93 | return new; 94 | } 95 | 96 | pub fn single_byte_packet(buffer: *const [PACKET_LEN]u8, byte: u8) bool { 97 | if (buffer[0] != 1) return false; 98 | if (buffer[1] != byte) return false; 99 | for (1..PACKET_DATA_BYTES+1) |i| { 100 | if (buffer[i] != 0xFF) return false; 101 | } 102 | return true; 103 | } 104 | 105 | }; 106 | 107 | 108 | 109 | var packets: [100]Packet = undefined; 110 | var rx_buffer = Ringbuffer(u8, 32).init(); 111 | 112 | pub fn main() !void { 113 | // var buffer: [PACKET_LEN]u8 = undefined; 114 | // var pack = Packet.init(1, [_]u8{PACKET_RETX_DATA0} ++ [_]u8{0xFF} ** 15, null); 115 | // 116 | // pack.retx = &Packet.init(1, [_]u8{PACKET_RETX_DATA0} ++ [_]u8{0xFF} ** 15, null); 117 | // pack.ack = &Packet.init(1, [_]u8{PACKET_ACK_DATA0} ++ [_]u8{0xFF} ** 15, null); 118 | // 119 | // pack.retx.?.*.to_buffer(&buffer); 120 | 121 | 122 | 123 | var serial = try std.fs.cwd().openFile(serial_path, .{ .mode = .read_write }) ; 124 | defer serial.close(); 125 | 126 | try zig_serial.configureSerialPort(serial, zig_serial.SerialConfig{ 127 | .baud_rate = 115200, 128 | .word_size = .eight, 129 | .parity = .none, 130 | .stop_bits = .one, 131 | .handshake = .none, 132 | }); 133 | 134 | std.debug.print("Waiting for data...\n", .{}); 135 | 136 | while (true) { 137 | const data = try serial.reader().readByte(); 138 | rx_buffer.non_blocking_write(data); 139 | 140 | if (rx_buffer.length < PACKET_LEN) continue; 141 | 142 | var temp_pack_buf: [PACKET_LEN]u8 = undefined; 143 | 144 | for (0..PACKET_LEN) |i| { 145 | // std.debug.print("Received {any} packet through UART\n", .{byte}); 146 | temp_pack_buf[i] = rx_buffer.read().?; 147 | } 148 | 149 | const rx_packet = Packet.from_buffer(temp_pack_buf[0..]); 150 | 151 | // std.debug.print("{}\n", .{rx_packet}); 152 | if (rx_packet.crc != 211) { //crc8(temp_pack_buf[0..17]) 153 | std.debug.print("Need to re-transmit: {any}\n", .{temp_pack_buf}); 154 | } 155 | 156 | if (rx_packet.retx) { 157 | std.debug.print("Retransmission requested: {any}\n", .{temp_pack_buf}); 158 | } 159 | 160 | if (rx_packet.ack == null) { 161 | unreachable; 162 | } 163 | 164 | 165 | } 166 | } 167 | --------------------------------------------------------------------------------