diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..19bf960 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.27) + +# Initialize the Pico SDK +include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake) +project(mlem) +pico_sdk_init() +add_executable(mlem) + +if (STDIO_USB) + pico_enable_stdio_uart(mlem 0) + pico_enable_stdio_usb(mlem 1) +endif() + +if (STDIO_UART) + pico_enable_stdio_uart(mlem 1) + pico_enable_stdio_usb(mlem 0) +endif() + +# Adjust libraries as needed +target_link_libraries(mlem pico_stdlib pico_cyw43_arch_none ${CMAKE_SOURCE_DIR}/zig-out/mlem.o) + +# Generate binary +pico_add_extra_outputs(mlem) diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..18ee1d9 --- /dev/null +++ b/build.zig @@ -0,0 +1,240 @@ +const std = @import("std"); + +// Choose your board +const Board = + // .pico; + .pico_w; +// .pico2; +// .pico2_w; + +// Choose whether Stdio goes to USB or UART +const StdioUsb = true; +const PicoStdlibDefine = if (StdioUsb) "LIB_PICO_STDIO_USB" else "LIB_PICO_STDIO_UART"; + +// Pico SDK path can be specified here for your convenience +const PicoSDKPath: ?[]const u8 = null; + +// arm-none-eabi toolchain path may be specified here as well +const ARMNoneEabiPath: ?[]const u8 = null; + +pub fn build(b: *std.Build) anyerror!void { + + // ------------------ + const target = std.Target.Query{ + .abi = .eabi, + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = cpu_model_by_board(Board) }, + .os_tag = .freestanding, + }; + + const optimize = b.standardOptimizeOption(.{}); + + const lib = b.addObject(.{ + .name = "zig-pico", + .root_source_file = b.path("src/main.zig"), + .target = b.resolveTargetQuery(target), + .optimize = optimize, + }); + + // get and perform basic verification on the pico sdk path + // if the sdk path contains the pico_sdk_init.cmake file then we know its correct + const pico_sdk_path = + if (PicoSDKPath) |sdk_path| sdk_path else std.process.getEnvVarOwned(b.allocator, "PICO_SDK_PATH") catch null orelse { + std.log.err("The Pico SDK path must be set either through the PICO_SDK_PATH environment variable or at the top of build.zig.", .{}); + return; + }; + + const pico_init_cmake_path = b.pathJoin(&.{ pico_sdk_path, "pico_sdk_init.cmake" }); + std.fs.cwd().access(pico_init_cmake_path, .{}) catch { + std.log.err( + \\Provided Pico SDK path does not contain the file pico_sdk_init.cmake + \\Tried: {s} + \\Are you sure you entered the path correctly?" + , .{pico_init_cmake_path}); + return; + }; + + // default arm-none-eabi includes + lib.linkLibC(); + + // Standard libary headers may be in different locations on different platforms + const arm_header_location = blk: { + if (ARMNoneEabiPath) |path| { + break :blk path; + } + + if (std.process.getEnvVarOwned(b.allocator, "ARM_NONE_EABI_PATH") catch null) |path| { + break :blk path; + } + + const unix_path = "/usr/arm-none-eabi/include"; + if (std.fs.accessAbsolute(unix_path, .{})) |_| { + break :blk unix_path; + } else |err| err catch {}; + + break :blk error.StandardHeaderLocationNotSpecified; + } catch |err| { + err catch {}; + std.log.err( + \\Could not determine ARM Toolchain include directory. + \\Please set the ARM_NONE_EABI_PATH environment variable with the correct path + \\or set the ARMNoneEabiPath variable at the top of build.zig + , .{}); + return; + }; + lib.addSystemIncludePath(.{ .cwd_relative = arm_header_location }); + + // Sort out the platform specifics + const IsRP2040 = Board == .pico or Board == .pico_w; + const Platform = comptime platform_by_board(Board); + + // find the board header + const board_header = blk: { + const header_file = @tagName(Board) ++ ".h"; + const _board_header = b.pathJoin(&.{ pico_sdk_path, "src/boards/include/boards", header_file }); + + std.fs.cwd().access(_board_header, .{}) catch { + std.log.err("Could not find the header file for board '{s}'\n", .{@tagName(Board)}); + return; + }; + + break :blk header_file; + }; + + // Autogenerate the header file like the pico sdk would + const cmsys_exception_prefix = if (IsRP2040) "" else "//"; + const header_str = try std.fmt.allocPrint(b.allocator, + \\#include "{s}/src/boards/include/boards/{s}" + \\{s}#include "{s}/src/rp2_common/cmsis/include/cmsis/rename_exceptions.h" + , .{ pico_sdk_path, board_header, cmsys_exception_prefix, pico_sdk_path }); + + // Write and include the generated header + const config_autogen_step = b.addWriteFile("pico/config_autogen.h", header_str); + lib.step.dependOn(&config_autogen_step.step); + lib.addIncludePath(config_autogen_step.getDirectory()); + + // requires running cmake at least once + lib.addSystemIncludePath(b.path("build/generated/pico_base")); + + // PICO SDK includes + // Find all folders called include in the Pico SDK + { + const pico_sdk_src = try std.fmt.allocPrint(b.allocator, "{s}/src", .{pico_sdk_path}); + var dir = try std.fs.cwd().openDir(pico_sdk_src, .{ + .iterate = true, + .no_follow = true, + }); + + const allowed_paths = [_][]const u8{ @tagName(Platform), "rp2_common", "common" }; + + var walker = try dir.walk(b.allocator); + defer walker.deinit(); + while (try walker.next()) |entry| { + if (std.mem.eql(u8, entry.basename, "include")) { + for (allowed_paths) |path| { + if (std.mem.indexOf(u8, entry.path, path)) |_| { + const pico_sdk_include = try std.fmt.allocPrint(b.allocator, "{s}/src/{s}", .{ pico_sdk_path, entry.path }); + lib.addIncludePath(.{ .cwd_relative = pico_sdk_include }); + continue; + } + } + } + } + } + + // Define the platform specific macros + define_platform_specific_macros(lib, Platform); + + // Define UART or USB constant for headers + lib.root_module.addCMacro(PicoStdlibDefine, "1"); + + // required for pico_w wifi + lib.root_module.addCMacro("PICO_CYW43_ARCH_THREADSAFE_BACKGROUND", "1"); + const cyw43_include = try std.fmt.allocPrint(b.allocator, "{s}/lib/cyw43-driver/src", .{pico_sdk_path}); + lib.addIncludePath(.{ .cwd_relative = cyw43_include }); + + // required by cyw43 + const lwip_include = try std.fmt.allocPrint(b.allocator, "{s}/lib/lwip/src/include", .{pico_sdk_path}); + lib.addIncludePath(.{ .cwd_relative = lwip_include }); + + // options headers + lib.addIncludePath(b.path("config/")); + + const compiled = lib.getEmittedBin(); + const install_step = b.addInstallFile(compiled, "mlem.o"); + install_step.step.dependOn(&lib.step); + + // create build directory + if (std.fs.cwd().makeDir("build")) |_| {} else |err| { + if (err != error.PathAlreadyExists) return err; + } + + const uart_or_usb = if (StdioUsb) "-DSTDIO_USB=1" else "-DSTDIO_UART=1"; + const cmake_pico_sdk_path = b.fmt("-DPICO_SDK_PATH={s}", .{pico_sdk_path}); + const cmake_argv = [_][]const u8{ "cmake", "-B", "./build", "-S .", "-DPICO_BOARD=" ++ @tagName(Board), "-DPICO_PLATFORM=" ++ @tagName(Platform), cmake_pico_sdk_path, uart_or_usb }; + const cmake_step = b.addSystemCommand(&cmake_argv); + cmake_step.step.dependOn(&install_step.step); + + const make_argv = [_][]const u8{ "cmake", "--build", "./build", "--parallel" }; + const make_step = b.addSystemCommand(&make_argv); + make_step.step.dependOn(&cmake_step.step); + + const uf2_create_step = b.addInstallFile(b.path("build/mlem.uf2"), "firmware.uf2"); + uf2_create_step.step.dependOn(&make_step.step); + + const uf2_step = b.step("uf2", "Create firmware.uf2"); + uf2_step.dependOn(&uf2_create_step.step); + + const elf_create_step = b.addInstallFile(b.path("build/mlem.elf"), "firmware.elf"); + elf_create_step.step.dependOn(&make_step.step); + + const elf_step = b.step("elf", "Create firmware.elf"); + elf_step.dependOn(&elf_create_step.step); + + const copy_step = b.step("copy", "Copy firmware"); + copy_step.dependOn(uf2_step); + copy_step.dependOn(elf_step); + b.default_step = copy_step; + + // b.default_step = uf2_step; +} + +// ------------------ Board support + +pub fn cpu_model_by_board(board: @Type(.enum_literal)) *const std.Target.Cpu.Model { + return switch (board) { + .pico, .pico_w => &std.Target.arm.cpu.cortex_m0plus, + .pico2, .pico2_w => &std.Target.arm.cpu.cortex_m33, + else => @compileError("Unknown board type"), + }; +} + +pub fn platform_by_board(board: @Type(.enum_literal)) @Type(.enum_literal) { + return switch (board) { + .pico, .pico_w => .rp2040, + .pico2, .pico2_w => .rp2350, + else => @compileError("Unknown platform"), + }; +} + +pub fn define_platform_specific_macros(compile: *std.Build.Step.Compile, platform: @Type(.enum_literal)) void { + switch (platform) { + .rp2040 => { + compile.root_module.addCMacro("PICO_RP2040", "1"); + compile.root_module.addCMacro("PICO_32BIT", "1"); + compile.root_module.addCMacro("PICO_ARM", "1"); + compile.root_module.addCMacro("PICO_CMSIS_DEVICE", "RP2040"); + compile.root_module.addCMacro("PICO_DEFAULT_FLASH_SIZE_BYTES", "\"2 * 1024 * 1024\""); + }, + .rp2350 => { + compile.root_module.addCMacro("PICO_RP2350", "1"); + compile.root_module.addCMacro("PICO_32BIT", "1"); + compile.root_module.addCMacro("PICO_ARM", "1"); + compile.root_module.addCMacro("PICO_PIO_VERSION", "1"); + compile.root_module.addCMacro("NUM_DOORBELLS", "1"); + compile.root_module.addCMacro("PICO_CMSIS_DEVICE", "RP2350"); + compile.root_module.addCMacro("PICO_DEFAULT_FLASH_SIZE_BYTES", "\"4 * 1024 * 1024\""); + }, + else => @compileError("Unknown platform"), + } +} diff --git a/config/lwipopts.h b/config/lwipopts.h new file mode 100644 index 0000000..997ed7b --- /dev/null +++ b/config/lwipopts.h @@ -0,0 +1,89 @@ +#ifndef __LWIPOPTS_H__ +#define __LWIPOPTS_H__ + +// Common settings used in most of the pico_w examples +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) + +// allow override in some examples +#ifndef NO_SYS +#define NO_SYS 1 +#endif +// allow override in some examples +#ifndef LWIP_SOCKET +#define LWIP_SOCKET 0 +#endif +#if PICO_CYW43_ARCH_POLL +#define MEM_LIBC_MALLOC 1 +#else +// MEM_LIBC_MALLOC is incompatible with non polling versions +#define MEM_LIBC_MALLOC 0 +#endif +#define MEM_ALIGNMENT 4 +#define MEM_SIZE 4000 +#define MEMP_NUM_TCP_SEG 32 +#define MEMP_NUM_ARP_QUEUE 10 +#define PBUF_POOL_SIZE 24 +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_ICMP 1 +#define LWIP_RAW 1 +#define TCP_WND (8 * TCP_MSS) +#define TCP_MSS 1460 +#define TCP_SND_BUF (8 * TCP_MSS) +#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_HOSTNAME 1 +#define LWIP_NETCONN 0 +#define MEM_STATS 0 +#define SYS_STATS 0 +#define MEMP_STATS 0 +#define LINK_STATS 0 +// #define ETH_PAD_SIZE 2 +#define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_DHCP 1 +#define LWIP_IPV4 1 +#define LWIP_TCP 1 +#define LWIP_UDP 1 +#define LWIP_DNS 1 +#define LWIP_TCP_KEEPALIVE 1 +#define LWIP_NETIF_TX_SINGLE_PBUF 1 +#define DHCP_DOES_ARP_CHECK 0 +#define LWIP_DHCP_DOES_ACD_CHECK 0 + +#ifndef NDEBUG +#define LWIP_DEBUG 1 +#define LWIP_STATS 1 +#define LWIP_STATS_DISPLAY 1 +#endif + +#define ETHARP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define INET_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define RAW_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define SYS_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define PPP_DEBUG LWIP_DBG_OFF +#define SLIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF + +#endif /* __LWIPOPTS_H__ */ diff --git a/config/pico/version.h b/config/pico/version.h new file mode 100644 index 0000000..098413e --- /dev/null +++ b/config/pico/version.h @@ -0,0 +1,22 @@ +// This file should look something like this: +// version.h seems not strictly necessary + +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// --------------------------------------- +// THIS FILE IS AUTOGENERATED; DO NOT EDIT +// --------------------------------------- + +// #ifndef _PICO_VERSION_H +// #define _PICO_VERSION_H + +// #define PICO_SDK_VERSION_MAJOR 1 +// #define PICO_SDK_VERSION_MINOR 5 +// #define PICO_SDK_VERSION_REVISION 1 +// #define PICO_SDK_VERSION_STRING "1.5.1" + +// #endif diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..b258068 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,37 @@ +pub const p = @cImport({ + @cInclude("pico.h"); + @cInclude("stdio.h"); + @cInclude("pico/stdlib.h"); + // PICO W specific header + @cInclude("pico/cyw43_arch.h"); +}); +const std = @import("std"); +const BUTTON_PIN = 15; +const GPIO_IN = false; + +// Basically the pico_w blink sample +export fn main() c_int { + _ = p.stdio_init_all(); + if (p.cyw43_arch_init() != 0) { + return -1; + } + + p.gpio_init(BUTTON_PIN); + p.gpio_set_dir(BUTTON_PIN, GPIO_IN); + + // while (true) { + // p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true); + // p.sleep_ms(500); + // p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false); + // p.sleep_ms(1000); + // } + // _ = p.printf("Hello world\n"); + + while (true) { + while (p.gpio_get(BUTTON_PIN)) { + p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true); + p.sleep_ms(50); + } + p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false); + } +}