From 776f89457ed7f1fbd40e2c33dced6ded1bcd85eb Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:11:44 +0100 Subject: [PATCH 1/8] Basic swift functionality Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/pico_project.py | 463 ++++++++++++++++++++++++++++---- src/utils/swiftUtil.mts | 18 ++ src/webview/activityBar.mts | 2 +- src/webview/newProjectPanel.mts | 46 ++++ web/main.js | 2 + 5 files changed, 485 insertions(+), 46 deletions(-) create mode 100644 src/utils/swiftUtil.mts diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 89e0f92..0508195 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -387,6 +387,265 @@ def GDB_NAME(): ], } +code_fragments_per_feature_swift = { + 'uart' : [ + ( + "// UART defines", + "// By default the stdout UART is `uart0`, so we will use the second one", + "let UART_ID = \"uart1\"", + "let BAUD_RATE = 115200", "", + "// Use pins 4 and 5 for UART1", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + "let UART_TX_PIN = 4", + "let UART_RX_PIN = 5" + ), + ( + "// Set up our UART", + "uart_init(UART_ID, BAUD_RATE)", + "// Set the TX and RX pins by using the function select on the GPIO", + "// Set datasheet for more information on function select", + "gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART)", + "gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART)", + "", + "// Use some the various UART functions to send out data", + "// In a default system, printf will also output via the default UART", + "", + # here should be following + # "// Send out a character without any conversions", + #"uart_putc_raw(UART_ID, 'A');", + #"", + #"// Send out a character but do CR/LF conversions", + #"uart_putc(UART_ID, 'B');", + # "", + "// Send out a string, with CR/LF conversions", + "uart_puts(UART_ID, \" Hello, UART!\\n\")", + "", + "// For more examples of UART use see https://github.com/raspberrypi/pico-examples/tree/master/uart" + ) + ], + 'spi' : [ + ( + "// SPI Constants", + "// We are going to use SPI 0, and allocate it to the following GPIO pins", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + "let SPI_PORT = \"spi0\"", + "let PIN_MISO = 16", + "let PIN_CS = 17", + "let PIN_SCK = 18", + "let PIN_MOSI = 19" + ), + ( + "// SPI initialisation. This example will use SPI at 1MHz.", + "spi_init(SPI_PORT, 1000*1000)", + "gpio_set_function(PIN_MISO, GPIO_FUNC_SPI)", + "gpio_set_function(PIN_CS, GPIO_FUNC_SIO)", + "gpio_set_function(PIN_SCK, GPIO_FUNC_SPI)", + "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI)", "", + "// Chip select is active-low, so we'll initialise it to a driven-high state", + "gpio_set_dir(PIN_CS, GPIO_OUT)", + "gpio_put(PIN_CS, 1)", + "// For more examples of SPI use see https://github.com/raspberrypi/pico-examples/tree/master/spi" + ) + ], + 'i2c' : [ + ( + "// I2C defines", + "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + "let I2C_PORT = \"i2c0\"", + "let I2C_SDA = 8", + "let I2C_SCL = 9", + ), + ( + "// I2C Initialisation. Using it at 400Khz.", + "i2c_init(I2C_PORT, 400*1000)","", + "gpio_set_function(I2C_SDA, GPIO_FUNC_I2C)", + "gpio_set_function(I2C_SCL, GPIO_FUNC_I2C)", + "gpio_pull_up(I2C_SDA)", + "gpio_pull_up(I2C_SCL)", + "// For more examples of I2C use see https://github.com/raspberrypi/pico-examples/tree/master/i2c", + ) + ], + "dma" : [ + ( + '// Data will be copied from src to dst', + 'let src = "Hello, world! (from DMA)"', + 'var dst = [UInt8](repeating: 0, count: src.count)', + ), + ( + '// Get a free channel, panic() if there are none', + 'let chan = dma_claim_unused_channel(true)', + '', + '// 8 bit transfers. Both read and write address increment after each', + '// transfer (each pointing to a location in src or dst respectively).', + '// No DREQ is selected, so the DMA transfers as fast as it can.', + '', + 'let c = dma_channel_get_default_config(chan)', + 'channel_config_set_transfer_data_size(&c, DMA_SIZE_8)', + 'channel_config_set_read_increment(&c, true)', + 'channel_config_set_write_increment(&c, true)', + '', + 'dma_channel_configure(', + ' chan, // Channel to be configured', + ' &c, // The configuration we just created', + ' dst, // The initial write address', + ' src, // The initial read address', + ' count_of(src), // Number of transfers; in this case each is 1 byte.', + ' true // Start immediately.', + ')', + '', + '// We could choose to go and do something else whilst the DMA is doing its', + '// thing. In this case the processor has nothing else to do, so we just', + '// wait for the DMA to finish.', + 'dma_channel_wait_for_finish_blocking(chan)', + '', + '// The DMA has now copied our text from the transmit buffer (src) to the', + '// receive buffer (dst), so we can print it out from there.', + 'puts(dst)', + ) + ], + + "pio" : [ + ( + '#include \"blink.pio.h\"', + 'static func blink_pin_forever(pio: PIO, sm: uint, offset: uint, pin: uint, freq: uint) {', + ' blink_program_init(pio, sm, offset, pin)', + ' pio_sm_set_enabled(pio, sm, true)', + '', + ' printf("Blinking pin %d at %d Hz\\n", pin, freq)', + '', + ' // PIO counter program takes 3 more cycles in total than we pass as', + ' // input (wait for n + 1; mov; jmp)', + ' pio.txf[sm] = (125000000 / (2 * freq)) - 3', + '}', + ), + ( + '// PIO Blinking example', + 'let pio = pio0', + 'let offset = pio_add_program(pio, &blink_program)', + 'printf("Loaded program at %d\\n", offset)', + '', + '#ifdef PICO_DEFAULT_LED_PIN', + 'blink_pin_forever(pio, 0, offset, PICO_DEFAULT_LED_PIN, 3)', + '#else', + 'blink_pin_forever(pio, 0, offset, 6, 3)', + '#endif', + '// For more pio examples see https://github.com/raspberrypi/pico-examples/tree/master/pio', + ) + ], + + "clocks" : [ + (), + ( + 'printf("System Clock Frequency is %d Hz\\n", clock_get_hz(clk_sys))', + 'printf("USB Clock Frequency is %d Hz\\n", clock_get_hz(clk_usb))', + '// For more examples of clocks use see https://github.com/raspberrypi/pico-examples/tree/master/clocks', + ) + ], + + "gpio" : [ + ( + "// GPIO constants", + "// Example uses GPIO 2", + "let GPIO = 2" + ), + ( + "// GPIO initialisation.", + "// We will make this GPIO an input, and pull it up by default", + "gpio_init(GPIO)", + "gpio_set_dir(GPIO, GPIO_IN)", + "gpio_pull_up(GPIO)", + "// See https://github.com/raspberrypi/pico-examples/tree/master/gpio for other gpio examples, including using interrupts", + ) + ], + "interp" :[ + (), + ( + "// Interpolator example code", + "interp_config cfg = interp_default_config()", + "// Now use the various interpolator library functions for your use case", + "// e.g. interp_config_clamp(&cfg, true)", + "// interp_config_shift(&cfg, 2)", + "// Then set the config ", + "interp_set_config(interp0, 0, &cfg)", + "// For examples of interpolator use see https://github.com/raspberrypi/pico-examples/tree/master/interp" + ) + ], + + "timer" : [ + ( + "func alarm_callback(id: alarm_id_t, user_data: void): int64_t {", + " // Put your timeout handler code in here", + " return 0", + "}" + ), + ( + "// Timer example code - This example fires off the callback after 2000ms", + "add_alarm_in_ms(2000, alarm_callback, NULL, false)", + "// For more examples of timer use see https://github.com/raspberrypi/pico-examples/tree/master/timer" + ) + ], + + "watchdog":[ (), + ( + "// Watchdog example code", + "if watchdog_caused_reboot() {", + " printf(\"Rebooted by Watchdog!\\n\")", + " // Whatever action you may take if a watchdog caused a reboot", + "}","", + "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot", + "// second arg is pause on debug which means the watchdog will pause when stepping through code", + "watchdog_enable(100, 1)","", + "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot", + "watchdog_update()", + ) + ], + + "div" : [ (), + ( + "// Example of using the HW divider. The pico_divider library provides a more user friendly set of APIs ", + "// over the divider (and support for 64 bit divides), and of course by default regular C language integer", + "// divisions are redirected thru that library, meaning you can just use C level `/` and `%` operators and", + "// gain the benefits of the fast hardware divider.", + "let dividend = 123456", + "let divisor = -321", + "// This is the recommended signed fast divider for general use.", + "let result = hw_divider_divmod_s32(dividend, divisor)", + "printf(\"%d/%d = %d remainder %d\\n\", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result))", + "// This is the recommended unsigned fast divider for general use.", + "let udividend = 123456", + "let udivisor = 321", + "let uresult = hw_divider_divmod_u32(udividend, udivisor)", + "printf(\"%d/%d = %d remainder %d\\n\", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult))", + "// See https://github.com/raspberrypi/pico-examples/tree/master/divider for more complex use" + ) + ], + + "picow_led":[ (), + ( + "// Example to turn on the Pico W LED", + "cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1)" + ) + ], + + "picow_wifi":[ (), + ( + '// Enable wifi station', + 'cyw43_arch_enable_sta_mode()\n', + 'printf("Connecting to Wi-Fi...\\n")', + 'if cyw43_arch_wifi_connect_timeout_ms("Your Wi-Fi SSID", "Your Wi-Fi Password", CYW43_AUTH_WPA2_AES_PSK, 30000) {', + ' printf("failed to connect.\\n")', + ' return 1', + '} else {', + ' printf("Connected.\\n")', + ' // Read the ip address in a human readable way', + ' let ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr)', + ' printf("IP address %d.%d.%d.%d\\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3])', + '}', + ) + ] +} + # Add wifi example for poll and background modes code_fragments_per_feature["picow_poll"] = code_fragments_per_feature["picow_wifi"] code_fragments_per_feature["picow_background"] = code_fragments_per_feature[ @@ -639,22 +898,35 @@ def ParseCommandLine(): "--userHome", help="Full path to user's home directory", ) + parser.add_argument("-swift", "--swift", action='store_true', help="Use Swift as the language for the project") return parser.parse_args() -def GenerateMain(folder, projectName, features, cpp, wantEntryProjName): +def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): executableName = projectName if wantEntryProjName else "main" if cpp: - filename = Path(folder) / (executableName + ".cpp") + filename = Path(folder) / (executableName + '.cpp') + elif swift: + filename = Path(folder) / (executableName + '.swift') else: filename = Path(folder) / (executableName + ".c") - file = open(filename, "w") + file = open(filename, 'w') + bridging_file = None - main = "#include \n" '#include "pico/stdlib.h"\n' - file.write(main) + if swift: + # write bridging header + bridging_file = open(Path(folder) / "BridgingHeader.h", 'w') + bridging_file.write("#pragma once\n\n") + bridging_file.write("#include \n") + bridging_file.write("#include \"pico/stdlib.h\"\n") + else: + main = ('#include \n' + '#include "pico/stdlib.h"\n' + ) + file.write(main) if features: @@ -664,64 +936,114 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName): if len(features_list[feat][H_FILE]) == 0: continue o = f'#include "{features_list[feat][H_FILE]}"\n' - file.write(o) - if feat in stdlib_examples_list: + if swift: + bridging_file.write(o) + else: + file.write(o) + if (feat in stdlib_examples_list): if len(stdlib_examples_list[feat][H_FILE]) == 0: continue o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n' - file.write(o) - if feat in picow_options_list: + if swift: + bridging_file.write(o) + else: + file.write(o) + if (feat in picow_options_list): if len(picow_options_list[feat][H_FILE]) == 0: continue o = f'#include "{picow_options_list[feat][H_FILE]}"\n' - file.write(o) + if swift: + bridging_file.write(o) + else: + file.write(o) file.write("\n") + frags = (code_fragments_per_feature if not swift else code_fragments_per_feature_swift) + # Add any defines for feat in features: - if feat in code_fragments_per_feature: - for s in code_fragments_per_feature[feat][DEFINES]: + if (feat in frags): + for s in frags[feat][DEFINES]: + if swift and s.startswith("#include"): + bridging_file.write(s) + bridging_file.write('\n') file.write(s) file.write("\n") file.write("\n") - main = "\n\n" "int main()\n" "{\n" " stdio_init_all();\n\n" + main = None + if swift: + main = ('\n\n' + '@main\n' + 'struct Main {\n' + ' \n' + ' static func main() {\n' + ' stdio_init_all();\n\n' + ) + else: + main = ('\n\n' + 'int main()\n' + '{\n' + ' stdio_init_all();\n\n' + ) if any([feat in picow_options_list and feat != "picow_none" for feat in features]): - main += ( - " // Initialise the Wi-Fi chip\n" - " if (cyw43_arch_init()) {\n" + if swift: + main += ( + ' // Initialise the Wi-Fi chip\n' + ' if cyw43_arch_init() {\n' + ' printf("Wi-Fi init failed\\n")\n' + ' return -1\n' + ' }\n\n') + else: + main += ( + ' // Initialise the Wi-Fi chip\n' + ' if (cyw43_arch_init()) {\n' ' printf("Wi-Fi init failed\\n");\n' " return -1;\n" " }\n\n" ) if features: + frags = (code_fragments_per_feature if not swift else code_fragments_per_feature_swift) # Add any initialisers indent = 4 for feat in features: - if feat in code_fragments_per_feature: - for s in code_fragments_per_feature[feat][INITIALISERS]: - main += " " * indent + if (feat in frags): + for s in frags[feat][INITIALISERS]: + main += (" " * indent) main += s main += "\n" main += "\n" - main += ( - " while (true) {\n" - ' printf("Hello, world!\\n");\n' - " sleep_ms(1000);\n" - " }\n" - "}\n" - ) - + if swift: + main += (' while true {\n' + ' printf("Hello, world!\\n")\n' + ' sleep_ms(1000)\n' + ' }\n' + ' }\n' + '}\n' + ) + else: + main += (' while (true) {\n' + ' printf("Hello, world!\\n");\n' + ' sleep_ms(1000);\n' + ' }\n' + '}\n' + ) + file.write(main) + bridging_file.close() file.close() def GenerateCMake(folder, params): + if (params["wantConvert"] or params['wantCPP']) and params["useSwift"]: + print("Invalid combination of options - Swift and C++ are not compatible and Swift can't be used when converting - exiting") + exit(20) + filename = Path(folder) / CMAKELIST_FILENAME projectName = params["projectName"] board_type = params["boardtype"] @@ -755,11 +1077,11 @@ def GenerateCMake(folder, params): ) cmake_header2 = ( - f'set(PICO_BOARD {board_type} CACHE STRING "Board type")\n\n' - "# Pull in Raspberry Pi Pico SDK (must be before project)\n" - "include(pico_sdk_import.cmake)\n\n" - f"project({projectName} C CXX ASM)\n" - ) + f"set(PICO_BOARD {board_type} CACHE STRING \"Board type\")\n\n" + "# Pull in Raspberry Pi Pico SDK (must be before project)\n" + "include(pico_sdk_import.cmake)\n\n" + f"project({projectName} {"" if params["useSwift"] else "C CXX ASM"})\n" + ) cmake_header3 = ( "\n# Initialise the Raspberry Pi Pico SDK\n" @@ -838,6 +1160,28 @@ def GenerateCMake(folder, params): file.write(cmake_header3) + if params["useSwift"]: + cmake_if_apple = ( + "if(APPLE)\n" + " execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + "else()\n" + " execute_process(COMMAND which swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + "endif()\n" + ) + cmake_swift_target = ( + "set(SWIFT_TARGET \"armv6m-none-none-eabi\") # RP2040\n\n" + "if(PICO_PLATFORM STREQUAL \"rp2350-arm-s\")\n" + " message(STATUS \"PICO_PLATFORM is set to rp2350-arm-s, using armv7em\")\n" + " set(SWIFT_TARGET \"armv7em-none-none-eabi\")\n" + "elseif(PICO_PLATFORM STREQUAL \"rp2350-riscv\")\n" + " # Untested, gives PICO-SDK errors when building\n" + " message(WARNING \"PICO_PLATFORM is set to rp2350-riscv, using riscv32 (untested). It is recommended to use rp2350-arm-s.\")\n" + " set(SWIFT_TARGET \"riscv32-none-none-eabi\")\n" + "endif()\n" + ) + file.write(cmake_if_apple) + file.write(cmake_swift_target) + # add the preprocessor defines for overall configuration if params["configs"]: file.write("# Add any PICO_CONFIG entries specified in the Advanced settings\n") @@ -851,8 +1195,32 @@ def GenerateCMake(folder, params): entry_point_file_name = projectName if params["wantEntryProjName"] else "main" - if params["wantCPP"]: - file.write(f"add_executable({projectName} {entry_point_file_name}.cpp )\n\n") + if params['wantCPP']: + file.write(f'add_executable({projectName} {entry_point_file_name}.cpp )\n\n') + elif params['useSwift']: + file.write(f'add_executable({projectName})\n\n') + + main_file_name = f"{entry_point_file_name}.swift" + cmake_custom_swift_command = ( + "add_custom_command(\n" + " OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n" + " COMMAND\n" + " ${SWIFTC}\n" + " -target ${SWIFT_TARGET} -Xcc -mfloat-abi=soft -Xcc -fshort-enums\n" + " -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library\n" + " $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" + " $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" + " -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" + f" ${{CMAKE_CURRENT_LIST_DIR}}/{entry_point_file_name}.swift\n" + " -c -o ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n" + " DEPENDS\n" + " ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" + f" ${{CMAKE_CURRENT_LIST_DIR}}/{entry_point_file_name}.swift\n" + ")\n" + ) + file.write(cmake_custom_swift_command) + + file.write(f"add_custom_target({projectName}-swiftcode DEPENDS ${{CMAKE_CURRENT_BINARY_DIR}}/_swiftcode.o)\n\n") else: file.write(f"add_executable({projectName} {entry_point_file_name}.c )\n\n") @@ -904,7 +1272,9 @@ def GenerateCMake(folder, params): file.write("# Add the standard library to the build\n") file.write(f"target_link_libraries({projectName}\n") file.write(" " + STANDARD_LIBRARIES) - file.write(")\n\n") + if params["useSwift"]: + file.write(" ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n") + file.write(')\n\n') # Standard include directories file.write("# Add the standard include files to the build\n") @@ -912,6 +1282,9 @@ def GenerateCMake(folder, params): file.write(" " + "${CMAKE_CURRENT_LIST_DIR}\n") file.write(")\n\n") + if params["useSwift"]: + file.write(f'add_dependencies({projectName} {projectName}-swiftcode)\n') + # Selected libraries/features if params["features"]: file.write("# Add any user requested libraries\n") @@ -941,6 +1314,7 @@ def generateProjectFiles( cmakePath, openOCDVersion, useCmakeTools, + useSwift, ): oldCWD = os.getcwd() @@ -951,6 +1325,7 @@ def generateProjectFiles( if not os.path.isfile(".gitignore"): file = open(".gitignore", "w") file.write("build\n") + file.write(".DS_Store\n") file.close() debugger = debugger_config_list[debugger] @@ -1167,6 +1542,9 @@ def generateProjectFiles( ] } + if useSwift: + extensions["recommendations"].append("sswg.swift-lang") + tasks = f"""{{ "version": "2.0.0", "tasks": [ @@ -1345,14 +1723,8 @@ def DoEverything(params): if "uart" not in features_and_examples: features_and_examples.append("uart") - if not (params["wantConvert"]): - GenerateMain( - projectPath, - params["projectName"], - features_and_examples, - params["wantCPP"], - params["wantEntryProjName"], - ) + if not (params['wantConvert']): + GenerateMain(projectPath, params['projectName'], features_and_examples, params['wantCPP'], params['wantEntryProjName'], params['useSwift']) # If we have any ancilliary files, copy them to our project folder # Currently only the picow with lwIP support needs an extra file, so just check that list @@ -1406,8 +1778,8 @@ def DoEverything(params): params["ninjaPath"], params["cmakePath"], params["openOCDVersion"], - params["useCmakeTools"], - ) + params['useCmakeTools'], + params['useSwift']) os.chdir(oldCWD) @@ -1483,6 +1855,7 @@ def DoEverything(params): "openOCDVersion": args.openOCDVersion, "exampleLibs": args.exampleLibs if args.exampleLibs is not None else [], "useCmakeTools": args.useCmakeTools, + "useSwift": args.swift, } DoEverything(params) diff --git a/src/utils/swiftUtil.mts b/src/utils/swiftUtil.mts new file mode 100644 index 0000000..b2d7745 --- /dev/null +++ b/src/utils/swiftUtil.mts @@ -0,0 +1,18 @@ +import { exec } from "child_process"; +import { promisify } from "util"; + +const execAsync = promisify(exec); + +export default async function checkSwiftRequirements(): Promise { + return true; + // check if swift is installed + try { + await execAsync("swift --version"); + + // TODO: check swift version + + return true; + } catch (error) { + return false; + } +} diff --git a/src/webview/activityBar.mts b/src/webview/activityBar.mts index 3b3f4e7..1e99ff7 100644 --- a/src/webview/activityBar.mts +++ b/src/webview/activityBar.mts @@ -41,7 +41,7 @@ const COMMON_COMMANDS_PARENT_LABEL = "General"; const PROJECT_COMMANDS_PARENT_LABEL = "Project"; const DOCUMENTATION_COMMANDS_PARENT_LABEL = "Documentation"; -const NEW_C_CPP_PROJECT_LABEL = "New C/C++ Project"; +const NEW_C_CPP_PROJECT_LABEL = "New Pico Project"; const NEW_MICROPYTHON_PROJECT_LABEL = "New MicroPython Project"; const IMPORT_PROJECT_LABEL = "Import Project"; const EXAMPLE_PROJECT_LABEL = "New Project From Example"; diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index d938486..5d94248 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -13,6 +13,7 @@ import { ColorThemeKind, ProgressLocation, type Progress, + env, } from "vscode"; import { type ExecOptions, exec } from "child_process"; import { HOME_VAR } from "../settings.mjs"; @@ -61,6 +62,7 @@ import { import { unknownErrorToString } from "../utils/errorHelper.mjs"; import type { Progress as GotProgress } from "got"; import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; +import checkSwiftRequirements from "../utils/swiftUtil.mjs"; export const NINJA_AUTO_INSTALL_DISABLED = false; // process.platform === "linux" && process.arch === "arm64"; @@ -114,6 +116,7 @@ interface SubmitMessageValue extends ImportProjectMessageValue { runFromRAM: boolean; entryPointProjectName: boolean; cpp: boolean; + swift: boolean; cppRtti: boolean; cppExceptions: boolean; } @@ -159,6 +162,7 @@ enum CodeOption { runFromRAM = "Run the program from RAM rather than flash", entryPointProjectName = "Use project name as entry point file name", cpp = "Generate C++ code", + swift = "Generate Swift code", cppRtti = "Enable C++ RTTI (Uses more memory)", cppExceptions = "Enable C++ exceptions (Uses more memory)", } @@ -243,6 +247,8 @@ function enumToParam( return "-e"; case CodeOption.cpp: return "-cpp"; + case CodeOption.swift: + return "-swift"; case CodeOption.cppRtti: return "-cpprtti"; case CodeOption.cppExceptions: @@ -1212,6 +1218,39 @@ export class NewProjectPanel { if (example === undefined && !this._isProjectImport) { const theData = data as SubmitMessageValue; + if (theData.swift) { + const swiftResult = await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Checking Swift installation", + cancellable: false, + }, + async () => checkSwiftRequirements() + ); + + if (!swiftResult) { + progress.report({ + message: "Failed", + increment: 100, + }); + void window + .showErrorMessage( + "Swift is required for Swift project generation. Please install Swift.", + "Install" + ) + .then(selected => { + if (selected) { + env.openExternal( + // TODO: check url + Uri.parse("https://swift.org/download/#releases") + ); + } + }); + + return; + } + } + await this._settings.setEntryPointNamingPref( theData.entryPointProjectName ); @@ -1244,6 +1283,7 @@ export class NewProjectPanel { ? CodeOption.entryPointProjectName : null, theData.cpp ? CodeOption.cpp : null, + theData.swift ? CodeOption.swift : null, theData.cppRtti ? CodeOption.cppRtti : null, theData.cppExceptions ? CodeOption.cppExceptions : null, ].filter(option => option !== null), @@ -2083,6 +2123,12 @@ export class NewProjectPanel { +
  • +
    + + +
    +
  • diff --git a/web/main.js b/web/main.js index f54de8e..0b9107f 100644 --- a/web/main.js +++ b/web/main.js @@ -318,6 +318,7 @@ var exampleSupportedBoards = []; const runFromRAMCodeGen = document.getElementById('run-from-ram-code-gen-cblist').checked; const nameEntryPointProjectName = document.getElementById('entry-project-name-code-gen-cblist').checked; const cppCodeGen = document.getElementById('cpp-code-gen-cblist').checked; + const swiftCodeGen = document.getElementById('swift-code-gen-cblist').checked; const cppRttiCodeGen = document.getElementById('cpp-rtti-code-gen-cblist').checked; const cppExceptionsCodeGen = document.getElementById('cpp-exceptions-code-gen-cblist').checked; @@ -358,6 +359,7 @@ var exampleSupportedBoards = []; runFromRAM: runFromRAMCodeGen, entryPointProjectName: nameEntryPointProjectName, cpp: cppCodeGen, + swift: swiftCodeGen, cppRtti: cppRttiCodeGen, cppExceptions: cppExceptionsCodeGen, From cf3af601df053169ac43e60093eb4f2dae25dc6f Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:58:15 +0100 Subject: [PATCH 2/8] Fix cmake on linux and macOS Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/pico_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 0508195..172d50c 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -1208,7 +1208,7 @@ def GenerateCMake(folder, params): " ${SWIFTC}\n" " -target ${SWIFT_TARGET} -Xcc -mfloat-abi=soft -Xcc -fshort-enums\n" " -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library\n" - " $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" + f" $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" f" ${{CMAKE_CURRENT_LIST_DIR}}/{entry_point_file_name}.swift\n" From 94700e34b92eb9742f723334844d2af581100ec3 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:17:33 +0100 Subject: [PATCH 3/8] Update some swift examples and cmake config Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/pico_project.py | 738 ++++++++++++++++++++++------------------ 1 file changed, 400 insertions(+), 338 deletions(-) diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 172d50c..123ef1c 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -388,262 +388,259 @@ def GDB_NAME(): } code_fragments_per_feature_swift = { - 'uart' : [ - ( - "// UART defines", - "// By default the stdout UART is `uart0`, so we will use the second one", - "let UART_ID = \"uart1\"", - "let BAUD_RATE = 115200", "", - "// Use pins 4 and 5 for UART1", - "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - "let UART_TX_PIN = 4", - "let UART_RX_PIN = 5" - ), - ( - "// Set up our UART", - "uart_init(UART_ID, BAUD_RATE)", - "// Set the TX and RX pins by using the function select on the GPIO", - "// Set datasheet for more information on function select", - "gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART)", - "gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART)", - "", - "// Use some the various UART functions to send out data", - "// In a default system, printf will also output via the default UART", - "", - # here should be following - # "// Send out a character without any conversions", - #"uart_putc_raw(UART_ID, 'A');", - #"", - #"// Send out a character but do CR/LF conversions", - #"uart_putc(UART_ID, 'B');", - # "", - "// Send out a string, with CR/LF conversions", - "uart_puts(UART_ID, \" Hello, UART!\\n\")", - "", - "// For more examples of UART use see https://github.com/raspberrypi/pico-examples/tree/master/uart" - ) - ], - 'spi' : [ - ( - "// SPI Constants", - "// We are going to use SPI 0, and allocate it to the following GPIO pins", - "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - "let SPI_PORT = \"spi0\"", - "let PIN_MISO = 16", - "let PIN_CS = 17", - "let PIN_SCK = 18", - "let PIN_MOSI = 19" - ), - ( - "// SPI initialisation. This example will use SPI at 1MHz.", - "spi_init(SPI_PORT, 1000*1000)", - "gpio_set_function(PIN_MISO, GPIO_FUNC_SPI)", - "gpio_set_function(PIN_CS, GPIO_FUNC_SIO)", - "gpio_set_function(PIN_SCK, GPIO_FUNC_SPI)", - "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI)", "", - "// Chip select is active-low, so we'll initialise it to a driven-high state", - "gpio_set_dir(PIN_CS, GPIO_OUT)", - "gpio_put(PIN_CS, 1)", - "// For more examples of SPI use see https://github.com/raspberrypi/pico-examples/tree/master/spi" - ) - ], - 'i2c' : [ - ( - "// I2C defines", - "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.", - "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - "let I2C_PORT = \"i2c0\"", - "let I2C_SDA = 8", - "let I2C_SCL = 9", - ), - ( - "// I2C Initialisation. Using it at 400Khz.", - "i2c_init(I2C_PORT, 400*1000)","", - "gpio_set_function(I2C_SDA, GPIO_FUNC_I2C)", - "gpio_set_function(I2C_SCL, GPIO_FUNC_I2C)", - "gpio_pull_up(I2C_SDA)", - "gpio_pull_up(I2C_SCL)", - "// For more examples of I2C use see https://github.com/raspberrypi/pico-examples/tree/master/i2c", - ) - ], - "dma" : [ - ( - '// Data will be copied from src to dst', - 'let src = "Hello, world! (from DMA)"', - 'var dst = [UInt8](repeating: 0, count: src.count)', - ), - ( - '// Get a free channel, panic() if there are none', - 'let chan = dma_claim_unused_channel(true)', - '', - '// 8 bit transfers. Both read and write address increment after each', - '// transfer (each pointing to a location in src or dst respectively).', - '// No DREQ is selected, so the DMA transfers as fast as it can.', - '', - 'let c = dma_channel_get_default_config(chan)', - 'channel_config_set_transfer_data_size(&c, DMA_SIZE_8)', - 'channel_config_set_read_increment(&c, true)', - 'channel_config_set_write_increment(&c, true)', - '', - 'dma_channel_configure(', - ' chan, // Channel to be configured', - ' &c, // The configuration we just created', - ' dst, // The initial write address', - ' src, // The initial read address', - ' count_of(src), // Number of transfers; in this case each is 1 byte.', - ' true // Start immediately.', - ')', - '', - '// We could choose to go and do something else whilst the DMA is doing its', - '// thing. In this case the processor has nothing else to do, so we just', - '// wait for the DMA to finish.', - 'dma_channel_wait_for_finish_blocking(chan)', - '', - '// The DMA has now copied our text from the transmit buffer (src) to the', - '// receive buffer (dst), so we can print it out from there.', - 'puts(dst)', - ) - ], - - "pio" : [ - ( - '#include \"blink.pio.h\"', - 'static func blink_pin_forever(pio: PIO, sm: uint, offset: uint, pin: uint, freq: uint) {', - ' blink_program_init(pio, sm, offset, pin)', - ' pio_sm_set_enabled(pio, sm, true)', - '', - ' printf("Blinking pin %d at %d Hz\\n", pin, freq)', - '', - ' // PIO counter program takes 3 more cycles in total than we pass as', - ' // input (wait for n + 1; mov; jmp)', - ' pio.txf[sm] = (125000000 / (2 * freq)) - 3', - '}', - ), - ( - '// PIO Blinking example', - 'let pio = pio0', - 'let offset = pio_add_program(pio, &blink_program)', - 'printf("Loaded program at %d\\n", offset)', - '', - '#ifdef PICO_DEFAULT_LED_PIN', - 'blink_pin_forever(pio, 0, offset, PICO_DEFAULT_LED_PIN, 3)', - '#else', - 'blink_pin_forever(pio, 0, offset, 6, 3)', - '#endif', - '// For more pio examples see https://github.com/raspberrypi/pico-examples/tree/master/pio', - ) - ], - - "clocks" : [ - (), - ( - 'printf("System Clock Frequency is %d Hz\\n", clock_get_hz(clk_sys))', - 'printf("USB Clock Frequency is %d Hz\\n", clock_get_hz(clk_usb))', - '// For more examples of clocks use see https://github.com/raspberrypi/pico-examples/tree/master/clocks', - ) - ], - - "gpio" : [ - ( - "// GPIO constants", - "// Example uses GPIO 2", - "let GPIO = 2" - ), - ( - "// GPIO initialisation.", - "// We will make this GPIO an input, and pull it up by default", - "gpio_init(GPIO)", - "gpio_set_dir(GPIO, GPIO_IN)", - "gpio_pull_up(GPIO)", - "// See https://github.com/raspberrypi/pico-examples/tree/master/gpio for other gpio examples, including using interrupts", - ) - ], - "interp" :[ - (), - ( - "// Interpolator example code", - "interp_config cfg = interp_default_config()", - "// Now use the various interpolator library functions for your use case", - "// e.g. interp_config_clamp(&cfg, true)", - "// interp_config_shift(&cfg, 2)", - "// Then set the config ", - "interp_set_config(interp0, 0, &cfg)", - "// For examples of interpolator use see https://github.com/raspberrypi/pico-examples/tree/master/interp" - ) - ], - - "timer" : [ - ( - "func alarm_callback(id: alarm_id_t, user_data: void): int64_t {", - " // Put your timeout handler code in here", - " return 0", - "}" - ), - ( - "// Timer example code - This example fires off the callback after 2000ms", - "add_alarm_in_ms(2000, alarm_callback, NULL, false)", - "// For more examples of timer use see https://github.com/raspberrypi/pico-examples/tree/master/timer" - ) - ], - - "watchdog":[ (), - ( - "// Watchdog example code", - "if watchdog_caused_reboot() {", - " printf(\"Rebooted by Watchdog!\\n\")", - " // Whatever action you may take if a watchdog caused a reboot", - "}","", - "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot", - "// second arg is pause on debug which means the watchdog will pause when stepping through code", - "watchdog_enable(100, 1)","", - "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot", - "watchdog_update()", - ) - ], - - "div" : [ (), - ( - "// Example of using the HW divider. The pico_divider library provides a more user friendly set of APIs ", - "// over the divider (and support for 64 bit divides), and of course by default regular C language integer", - "// divisions are redirected thru that library, meaning you can just use C level `/` and `%` operators and", - "// gain the benefits of the fast hardware divider.", - "let dividend = 123456", - "let divisor = -321", - "// This is the recommended signed fast divider for general use.", - "let result = hw_divider_divmod_s32(dividend, divisor)", - "printf(\"%d/%d = %d remainder %d\\n\", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result))", - "// This is the recommended unsigned fast divider for general use.", - "let udividend = 123456", - "let udivisor = 321", - "let uresult = hw_divider_divmod_u32(udividend, udivisor)", - "printf(\"%d/%d = %d remainder %d\\n\", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult))", - "// See https://github.com/raspberrypi/pico-examples/tree/master/divider for more complex use" - ) - ], - - "picow_led":[ (), - ( - "// Example to turn on the Pico W LED", - "cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1)" - ) - ], - - "picow_wifi":[ (), - ( - '// Enable wifi station', - 'cyw43_arch_enable_sta_mode()\n', - 'printf("Connecting to Wi-Fi...\\n")', - 'if cyw43_arch_wifi_connect_timeout_ms("Your Wi-Fi SSID", "Your Wi-Fi Password", CYW43_AUTH_WPA2_AES_PSK, 30000) {', - ' printf("failed to connect.\\n")', - ' return 1', - '} else {', - ' printf("Connected.\\n")', - ' // Read the ip address in a human readable way', - ' let ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr)', - ' printf("IP address %d.%d.%d.%d\\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3])', - '}', - ) - ] + "uart": [ + ( + "// UART defines", + "// By default the stdout UART is `uart0`, so we will use the second one", + 'let UART_ID = "uart1"', + "let BAUD_RATE = 115200", + "", + "// Use pins 4 and 5 for UART1", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + "let UART_TX_PIN = 4", + "let UART_RX_PIN = 5", + ), + ( + "// Set up our UART", + "uart_init(UART_ID, BAUD_RATE)", + "// Set the TX and RX pins by using the function select on the GPIO", + "// Set datasheet for more information on function select", + "gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART)", + "gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART)", + "", + "// Use some the various UART functions to send out data", + "// In a default system, printf will also output via the default UART", + "", + # here should be following + # "// Send out a character without any conversions", + # "uart_putc_raw(UART_ID, 'A');", + # "", + # "// Send out a character but do CR/LF conversions", + # "uart_putc(UART_ID, 'B');", + # "", + "// Send out a string, with CR/LF conversions", + 'uart_puts(UART_ID, " Hello, UART!\\n")', + "", + "// For more examples of UART use see https://github.com/raspberrypi/pico-examples/tree/master/uart", + ), + ], + "spi": [ + ( + "// SPI Constants", + "// We are going to use SPI 0, and allocate it to the following GPIO pins", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + 'let SPI_PORT = "spi0"', + "let PIN_MISO = 16", + "let PIN_CS = 17", + "let PIN_SCK = 18", + "let PIN_MOSI = 19", + ), + ( + "// SPI initialisation. This example will use SPI at 1MHz.", + "spi_init(SPI_PORT, 1000*1000)", + "gpio_set_function(PIN_MISO, GPIO_FUNC_SPI)", + "gpio_set_function(PIN_CS, GPIO_FUNC_SIO)", + "gpio_set_function(PIN_SCK, GPIO_FUNC_SPI)", + "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI)", + "", + "// Chip select is active-low, so we'll initialise it to a driven-high state", + "gpio_set_dir(PIN_CS, GPIO_OUT)", + "gpio_put(PIN_CS, 1)", + "// For more examples of SPI use see https://github.com/raspberrypi/pico-examples/tree/master/spi", + ), + ], + "i2c": [ + ( + "// I2C defines", + "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + 'let I2C_PORT = "i2c0"', + "let I2C_SDA = 8", + "let I2C_SCL = 9", + ), + ( + "// I2C Initialisation. Using it at 400Khz.", + "i2c_init(I2C_PORT, 400*1000)", + "", + "gpio_set_function(I2C_SDA, GPIO_FUNC_I2C)", + "gpio_set_function(I2C_SCL, GPIO_FUNC_I2C)", + "gpio_pull_up(I2C_SDA)", + "gpio_pull_up(I2C_SCL)", + "// For more examples of I2C use see https://github.com/raspberrypi/pico-examples/tree/master/i2c", + ), + ], + "dma": [ + ( + "// Data will be copied from src to dst", + 'let src = "Hello, world! (from DMA)"', + "var dst = [UInt8](repeating: 0, count: src.count)", + ), + ( + "// Get a free channel, panic() if there are none", + "let chan = dma_claim_unused_channel(true)", + "", + "// 8 bit transfers. Both read and write address increment after each", + "// transfer (each pointing to a location in src or dst respectively).", + "// No DREQ is selected, so the DMA transfers as fast as it can.", + "", + "let c = dma_channel_get_default_config(chan)", + "channel_config_set_transfer_data_size(&c, DMA_SIZE_8)", + "channel_config_set_read_increment(&c, true)", + "channel_config_set_write_increment(&c, true)", + "", + "dma_channel_configure(", + " chan, // Channel to be configured", + " &c, // The configuration we just created", + " dst, // The initial write address", + " src, // The initial read address", + " count_of(src), // Number of transfers; in this case each is 1 byte.", + " true // Start immediately.", + ")", + "", + "// We could choose to go and do something else whilst the DMA is doing its", + "// thing. In this case the processor has nothing else to do, so we just", + "// wait for the DMA to finish.", + "dma_channel_wait_for_finish_blocking(chan)", + "", + "// The DMA has now copied our text from the transmit buffer (src) to the", + "// receive buffer (dst), so we can print it out from there.", + "puts(dst)", + ), + ], + "pio": [ + ( + '#include "blink.pio.h"', + "static func blink_pin_forever(pio: PIO, sm: uint, offset: uint, pin: uint, freq: uint) {", + " blink_program_init(pio, sm, offset, pin)", + " pio_sm_set_enabled(pio, sm, true)", + "", + ' printf("Blinking pin %d at %d Hz\\n", pin, freq)', + "", + " // PIO counter program takes 3 more cycles in total than we pass as", + " // input (wait for n + 1; mov; jmp)", + " pio.txf[sm] = (125000000 / (2 * freq)) - 3", + "}", + ), + ( + "// PIO Blinking example", + "let pio = pio0", + "let offset = pio_add_program(pio, &blink_program)", + 'printf("Loaded program at %d\\n", offset)', + "", + "#ifdef PICO_DEFAULT_LED_PIN", + "blink_pin_forever(pio, 0, offset, PICO_DEFAULT_LED_PIN, 3)", + "#else", + "blink_pin_forever(pio, 0, offset, 6, 3)", + "#endif", + "// For more pio examples see https://github.com/raspberrypi/pico-examples/tree/master/pio", + ), + ], + "clocks": [ + (), + ( + 'printf("System Clock Frequency is %d Hz\\n", clock_get_hz(clk_sys))', + 'printf("USB Clock Frequency is %d Hz\\n", clock_get_hz(clk_usb))', + "// For more examples of clocks use see https://github.com/raspberrypi/pico-examples/tree/master/clocks", + ), + ], + "gpio": [ + ("// GPIO constants", "// Example uses GPIO 2", "let GPIO = 2"), + ( + "// GPIO initialisation.", + "// We will make this GPIO an input, and pull it up by default", + "gpio_init(GPIO)", + "gpio_set_dir(GPIO, GPIO_IN)", + "gpio_pull_up(GPIO)", + "// See https://github.com/raspberrypi/pico-examples/tree/master/gpio for other gpio examples, including using interrupts", + ), + ], + "interp": [ + (), + ( + "// Interpolator example code", + "interp_config cfg = interp_default_config()", + "// Now use the various interpolator library functions for your use case", + "// e.g. interp_config_clamp(&cfg, true)", + "// interp_config_shift(&cfg, 2)", + "// Then set the config ", + "interp_set_config(interp0, 0, &cfg)", + "// For examples of interpolator use see https://github.com/raspberrypi/pico-examples/tree/master/interp", + ), + ], + "timer": [ + ( + "func alarm_callback(id: alarm_id_t, user_data: void): int64_t {", + " // Put your timeout handler code in here", + " return 0", + "}", + ), + ( + "// Timer example code - This example fires off the callback after 2000ms", + "add_alarm_in_ms(2000, alarm_callback, NULL, false)", + "// For more examples of timer use see https://github.com/raspberrypi/pico-examples/tree/master/timer", + ), + ], + "watchdog": [ + (), + ( + "// Watchdog example code", + "if watchdog_caused_reboot() {", + ' printf("Rebooted by Watchdog!\\n")', + " // Whatever action you may take if a watchdog caused a reboot", + "}", + "", + "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot", + "// second arg is pause on debug which means the watchdog will pause when stepping through code", + "watchdog_enable(100, 1)", + "", + "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot", + "watchdog_update()", + ), + ], + "div": [ + (), + ( + "// Example of using the HW divider. The pico_divider library provides a more user friendly set of APIs ", + "// over the divider (and support for 64 bit divides), and of course by default regular C language integer", + "// divisions are redirected thru that library, meaning you can just use C level `/` and `%` operators and", + "// gain the benefits of the fast hardware divider.", + "let dividend = 123456", + "let divisor = -321", + "// This is the recommended signed fast divider for general use.", + "let result = hw_divider_divmod_s32(dividend, divisor)", + 'printf("%d/%d = %d remainder %d\\n", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result))', + "// This is the recommended unsigned fast divider for general use.", + "let udividend = 123456", + "let udivisor = 321", + "let uresult = hw_divider_divmod_u32(udividend, udivisor)", + 'printf("%d/%d = %d remainder %d\\n", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult))', + "// See https://github.com/raspberrypi/pico-examples/tree/master/divider for more complex use", + ), + ], + "picow_led": [ + (), + ( + "// Example to turn on the Pico W LED", + "cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1)", + ), + ], + "picow_wifi": [ + (), + ( + "// Enable wifi station", + "cyw43_arch_enable_sta_mode()\n", + 'printf("Connecting to Wi-Fi...\\n")', + 'if cyw43_arch_wifi_connect_timeout_ms("Your Wi-Fi SSID", "Your Wi-Fi Password", CYW43_AUTH_WPA2_AES_PSK, 30000) {', + ' printf("failed to connect.\\n")', + " return 1", + "} else {", + ' printf("Connected.\\n")', + " // Read the ip address in a human readable way", + " let ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr)", + ' printf("IP address %d.%d.%d.%d\\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3])', + "}", + ), + ], } # Add wifi example for poll and background modes @@ -898,7 +895,12 @@ def ParseCommandLine(): "--userHome", help="Full path to user's home directory", ) - parser.add_argument("-swift", "--swift", action='store_true', help="Use Swift as the language for the project") + parser.add_argument( + "-swift", + "--swift", + action="store_true", + help="Use Swift as the language for the project", + ) return parser.parse_args() @@ -907,25 +909,23 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): executableName = projectName if wantEntryProjName else "main" if cpp: - filename = Path(folder) / (executableName + '.cpp') + filename = Path(folder) / (executableName + ".cpp") elif swift: - filename = Path(folder) / (executableName + '.swift') + filename = Path(folder) / (executableName + ".swift") else: filename = Path(folder) / (executableName + ".c") - file = open(filename, 'w') + file = open(filename, "w") bridging_file = None if swift: # write bridging header - bridging_file = open(Path(folder) / "BridgingHeader.h", 'w') + bridging_file = open(Path(folder) / "BridgingHeader.h", "w") bridging_file.write("#pragma once\n\n") bridging_file.write("#include \n") - bridging_file.write("#include \"pico/stdlib.h\"\n") + bridging_file.write('#include "pico/stdlib.h"\n') else: - main = ('#include \n' - '#include "pico/stdlib.h"\n' - ) + main = "#include \n" '#include "pico/stdlib.h"\n' file.write(main) if features: @@ -940,7 +940,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): bridging_file.write(o) else: file.write(o) - if (feat in stdlib_examples_list): + if feat in stdlib_examples_list: if len(stdlib_examples_list[feat][H_FILE]) == 0: continue o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n' @@ -948,7 +948,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): bridging_file.write(o) else: file.write(o) - if (feat in picow_options_list): + if feat in picow_options_list: if len(picow_options_list[feat][H_FILE]) == 0: continue o = f'#include "{picow_options_list[feat][H_FILE]}"\n' @@ -959,80 +959,89 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): file.write("\n") - frags = (code_fragments_per_feature if not swift else code_fragments_per_feature_swift) + frags = ( + code_fragments_per_feature + if not swift + else code_fragments_per_feature_swift + ) # Add any defines for feat in features: - if (feat in frags): + if feat in frags: for s in frags[feat][DEFINES]: if swift and s.startswith("#include"): bridging_file.write(s) - bridging_file.write('\n') + bridging_file.write("\n") file.write(s) file.write("\n") file.write("\n") main = None if swift: - main = ('\n\n' - '@main\n' - 'struct Main {\n' - ' \n' - ' static func main() {\n' - ' stdio_init_all();\n\n' - ) - else: - main = ('\n\n' - 'int main()\n' - '{\n' - ' stdio_init_all();\n\n' - ) + main = ( + "\n\n" + "@main\n" + "struct Main {\n" + " \n" + " static func main() {\n" + " stdio_init_all();\n\n" + ) + else: + main = "\n\n" "int main()\n" "{\n" " stdio_init_all();\n\n" if any([feat in picow_options_list and feat != "picow_none" for feat in features]): if swift: main += ( - ' // Initialise the Wi-Fi chip\n' - ' if cyw43_arch_init() {\n' - ' printf("Wi-Fi init failed\\n")\n' - ' return -1\n' - ' }\n\n') + " // Initialise the Wi-Fi chip\n" + " if cyw43_arch_init() {\n" + ' printf("Wi-Fi init failed\\n")\n' + " return -1\n" + " }\n\n" + ) else: main += ( - ' // Initialise the Wi-Fi chip\n' - ' if (cyw43_arch_init()) {\n' - ' printf("Wi-Fi init failed\\n");\n' - " return -1;\n" - " }\n\n" - ) + " // Initialise the Wi-Fi chip\n" + " if (cyw43_arch_init()) {\n" + ' printf("Wi-Fi init failed\\n");\n' + " return -1;\n" + " }\n\n" + ) if features: - frags = (code_fragments_per_feature if not swift else code_fragments_per_feature_swift) + frags = ( + code_fragments_per_feature + if not swift + else code_fragments_per_feature_swift + ) # Add any initialisers indent = 4 for feat in features: - if (feat in frags): + if feat in frags: for s in frags[feat][INITIALISERS]: - main += (" " * indent) + main += " " * indent main += s main += "\n" main += "\n" if swift: - main += (' while true {\n' - ' printf("Hello, world!\\n")\n' - ' sleep_ms(1000)\n' - ' }\n' - ' }\n' - '}\n' - ) + main += ( + " while true {\n" + " // print depends on stdio.h - putchar() in the background\n" + ' print("Hello, world!")\n' + " sleep_ms(1000)\n" + " }\n" + " }\n" + "}\n" + ) else: - main += (' while (true) {\n' - ' printf("Hello, world!\\n");\n' - ' sleep_ms(1000);\n' - ' }\n' - '}\n' - ) - + main += ( + " while (true) {\n" + ' printf("Hello, world!\\n");\n' + " sleep_ms(1000);\n" + " }\n" + "}\n" + ) + file.write(main) bridging_file.close() @@ -1040,8 +1049,10 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): def GenerateCMake(folder, params): - if (params["wantConvert"] or params['wantCPP']) and params["useSwift"]: - print("Invalid combination of options - Swift and C++ are not compatible and Swift can't be used when converting - exiting") + if (params["wantConvert"] or params["wantCPP"]) and params["useSwift"]: + print( + "Invalid combination of options - Swift and C++ are not compatible and Swift can't be used when converting - exiting" + ) exit(20) filename = Path(folder) / CMAKELIST_FILENAME @@ -1077,11 +1088,11 @@ def GenerateCMake(folder, params): ) cmake_header2 = ( - f"set(PICO_BOARD {board_type} CACHE STRING \"Board type\")\n\n" - "# Pull in Raspberry Pi Pico SDK (must be before project)\n" - "include(pico_sdk_import.cmake)\n\n" - f"project({projectName} {"" if params["useSwift"] else "C CXX ASM"})\n" - ) + f'set(PICO_BOARD {board_type} CACHE STRING "Board type")\n\n' + "# Pull in Raspberry Pi Pico SDK (must be before project)\n" + "include(pico_sdk_import.cmake)\n\n" + f"project({projectName} {"" if params["useSwift"] else "C CXX ASM"})\n" + ) cmake_header3 = ( "\n# Initialise the Raspberry Pi Pico SDK\n" @@ -1169,14 +1180,18 @@ def GenerateCMake(folder, params): "endif()\n" ) cmake_swift_target = ( - "set(SWIFT_TARGET \"armv6m-none-none-eabi\") # RP2040\n\n" - "if(PICO_PLATFORM STREQUAL \"rp2350-arm-s\")\n" - " message(STATUS \"PICO_PLATFORM is set to rp2350-arm-s, using armv7em\")\n" - " set(SWIFT_TARGET \"armv7em-none-none-eabi\")\n" - "elseif(PICO_PLATFORM STREQUAL \"rp2350-riscv\")\n" - " # Untested, gives PICO-SDK errors when building\n" - " message(WARNING \"PICO_PLATFORM is set to rp2350-riscv, using riscv32 (untested). It is recommended to use rp2350-arm-s.\")\n" - " set(SWIFT_TARGET \"riscv32-none-none-eabi\")\n" + 'set(SWIFT_TARGET "armv6m-none-none-eabi") # RP2040\n\n' + 'if(PICO_PLATFORM STREQUAL "rp2350-arm-s")\n' + ' message(STATUS "PICO_PLATFORM is set to rp2350-arm-s, using armv7em")\n' + ' set(SWIFT_TARGET "armv7em-none-none-eabi")\n' + ' list(APPEND CLANG_ARCH_ABI_FLAGS "-Xcc" "-mfloat-abi=soft")\n' + 'elseif(PICO_PLATFORM STREQUAL "rp2040")\n' + ' message(STATUS "PICO_PLATFORM is set to RP2040, using armv6m")\n' + ' list(APPEND CLANG_ARCH_ABI_FLAGS "-Xcc" "-mfloat-abi=soft")\n' + 'elseif(PICO_PLATFORM STREQUAL "rp2350-riscv")\n' + ' message(STATUS "PICO_PLATFORM is set to rp2350-riscv, using riscv32.")\n' + ' set(SWIFT_TARGET "riscv32-none-none-eabi")\n' + ' list(APPEND CLANG_ARCH_ABI_FLAGS "-Xcc" "-march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb" "-Xcc" "-mabi=ilp32")\n' "endif()\n" ) file.write(cmake_if_apple) @@ -1195,13 +1210,50 @@ def GenerateCMake(folder, params): entry_point_file_name = projectName if params["wantEntryProjName"] else "main" - if params['wantCPP']: - file.write(f'add_executable({projectName} {entry_point_file_name}.cpp )\n\n') - elif params['useSwift']: - file.write(f'add_executable({projectName})\n\n') - + if params["wantCPP"]: + file.write(f"add_executable({projectName} {entry_point_file_name}.cpp )\n\n") + elif params["useSwift"]: + file.write(f"add_executable({projectName})\n\n") + main_file_name = f"{entry_point_file_name}.swift" cmake_custom_swift_command = ( + "# Gather compile definitions from all dependencies\n\n" + 'set_property(GLOBAL PROPERTY visited_targets "")\n' + 'set_property(GLOBAL PROPERTY compilerdefs_list "")\n\n' + "function(gather_compile_definitions_recursive target)\n" + " # Get the current value of visited_targets\n" + " get_property(visited_targets GLOBAL PROPERTY visited_targets)\n\n" + " # make sure we don't visit the same target twice\n" + " # and that we don't visit the special generator expressions\n" + ' if (${target} MATCHES "\\$<" OR ${target} MATCHES "::@" OR ${target} IN_LIST visited_targets)\n' + " return()\n" + " endif()\n\n" + " # Append the target to visited_targets\n" + " list(APPEND visited_targets ${target})\n" + ' set_property(GLOBAL PROPERTY visited_targets "${visited_targets}")\n\n' + " # Get the current value of compilerdefs_list\n" + " get_property(compilerdefs_list GLOBAL PROPERTY compilerdefs_list)\n\n" + " get_target_property(target_definitions ${target} INTERFACE_COMPILE_DEFINITIONS)\n" + " if (target_definitions)\n" + " # Append the target definitions to compilerdefs_list\n" + " list(APPEND compilerdefs_list ${target_definitions})\n" + ' set_property(GLOBAL PROPERTY compilerdefs_list "${compilerdefs_list}")\n' + " endif()\n\n" + " get_target_property(target_linked_libs ${target} INTERFACE_LINK_LIBRARIES)\n" + " if (target_linked_libs)\n" + " foreach(linked_target ${target_linked_libs})\n" + " # Recursively gather compile definitions from dependencies\n" + " gather_compile_definitions_recursive(${linked_target})\n" + " endforeach()\n" + " endif()\n" + "endfunction()\n\n" + f"gather_compile_definitions_recursive({projectName})\n" + "get_property(COMPILE_DEFINITIONS GLOBAL PROPERTY compilerdefs_list)\n\n" + "# Parse compiler definitions into a format that swiftc can understand\n" + "list(REMOVE_DUPLICATES COMPILE_DEFINITIONS)\n" + 'list(PREPEND COMPILE_DEFINITIONS "")\n' + f'string(REPLACE "$" "$" COMPILE_DEFINITIONS "${{COMPILE_DEFINITIONS}}")\n' + 'string(REPLACE ";" ";-Xcc;-D" COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS}")\n' "add_custom_command(\n" " OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n" " COMMAND\n" @@ -1220,7 +1272,9 @@ def GenerateCMake(folder, params): ) file.write(cmake_custom_swift_command) - file.write(f"add_custom_target({projectName}-swiftcode DEPENDS ${{CMAKE_CURRENT_BINARY_DIR}}/_swiftcode.o)\n\n") + file.write( + f"add_custom_target({projectName}-swiftcode DEPENDS ${{CMAKE_CURRENT_BINARY_DIR}}/_swiftcode.o)\n\n" + ) else: file.write(f"add_executable({projectName} {entry_point_file_name}.c )\n\n") @@ -1274,7 +1328,7 @@ def GenerateCMake(folder, params): file.write(" " + STANDARD_LIBRARIES) if params["useSwift"]: file.write(" ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n") - file.write(')\n\n') + file.write(")\n\n") # Standard include directories file.write("# Add the standard include files to the build\n") @@ -1283,7 +1337,7 @@ def GenerateCMake(folder, params): file.write(")\n\n") if params["useSwift"]: - file.write(f'add_dependencies({projectName} {projectName}-swiftcode)\n') + file.write(f"add_dependencies({projectName} {projectName}-swiftcode)\n") # Selected libraries/features if params["features"]: @@ -1723,8 +1777,15 @@ def DoEverything(params): if "uart" not in features_and_examples: features_and_examples.append("uart") - if not (params['wantConvert']): - GenerateMain(projectPath, params['projectName'], features_and_examples, params['wantCPP'], params['wantEntryProjName'], params['useSwift']) + if not (params["wantConvert"]): + GenerateMain( + projectPath, + params["projectName"], + features_and_examples, + params["wantCPP"], + params["wantEntryProjName"], + params["useSwift"], + ) # If we have any ancilliary files, copy them to our project folder # Currently only the picow with lwIP support needs an extra file, so just check that list @@ -1778,8 +1839,9 @@ def DoEverything(params): params["ninjaPath"], params["cmakePath"], params["openOCDVersion"], - params['useCmakeTools'], - params['useSwift']) + params["useCmakeTools"], + params["useSwift"], + ) os.chdir(oldCWD) From da4bd522ced262f0e57c5ba85cb3d170340de11b Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:25:23 +0100 Subject: [PATCH 4/8] Update swift examples and memory alignment support Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/bridge.c | 38 ++++++++++++ scripts/bridge.h | 38 ++++++++++++ scripts/pico_project.py | 129 +++++++++++++++++++++++++--------------- src/utils/download.mts | 23 ++++++- 4 files changed, 179 insertions(+), 49 deletions(-) create mode 100644 scripts/bridge.c create mode 100644 scripts/bridge.h diff --git a/scripts/bridge.c b/scripts/bridge.c new file mode 100644 index 0000000..cb7296f --- /dev/null +++ b/scripts/bridge.c @@ -0,0 +1,38 @@ +#include +#include + +/** + * @brief Allocates aligned memory in accordance with POSIX standards. + * + * The `posix_memalign` function allocates a block of memory with the specified alignment + * and size. The allocated memory is stored in the location pointed to by `memptr`. The + * alignment must be a power of two and a multiple of `sizeof(void *)`. This function is + * typically used for ensuring memory alignment for hardware or performance requirements. + * + * @param[out] memptr A pointer to the memory location where the aligned memory will be stored. + * This parameter must not be NULL. + * @param[in] alignment The alignment boundary in bytes. Must be a power of two and a multiple + * of `sizeof(void *)`. + * @param[in] size The size of the memory block to allocate in bytes. + * + * @return int Returns 0 on success. On failure, returns: + * - `EINVAL` if the alignment is invalid (not a power of two or not a multiple of `sizeof(void *)`). + * - `ENOMEM` if memory allocation fails. + * + * @note The caller is responsible for freeing the allocated memory using `free()` when it is no longer needed. + */ +int posix_memalign(void **memptr, size_t alignment, size_t size) { + // Validate alignment requirements + if ((alignment % sizeof(void *) != 0) || (alignment & (alignment - 1)) != 0) { + return EINVAL; // Invalid alignment + } + + // Use memalign to allocate memory + void *ptr = memalign(alignment, size); + if (ptr == NULL) { + return ENOMEM; // Memory allocation failure + } + + *memptr = ptr; // Set the memory pointer + return 0; // Success +} diff --git a/scripts/bridge.h b/scripts/bridge.h new file mode 100644 index 0000000..2647a58 --- /dev/null +++ b/scripts/bridge.h @@ -0,0 +1,38 @@ +#pragma once + +/** + * @file bridge.h + * @brief Simplifies the usage of specific SDK features in Swift by providing wrapper functions. + * + * This header acts as a bridge between C and Swift, exposing certain features of the Pico SDK + * in a simplified manner. For example it includes helper functions for accessing predefined + * UART instances (`uart0` and `uart1`), making them easily callable from Swift. + */ + +#ifdef _HARDWARE_STRUCTS_UART_H // Ensure that UART hardware structs are included before compiling + +/** + * @brief Retrieves the instance for UART0. + * + * This function provides access to the pre-defined `uart0` instance, + * allowing straightforward interaction with the UART hardware from Swift. + * + * @return Pointer to the `uart0` instance. + */ +uart_inst_t* get_uart0(void) { + return uart0; +} + +/** + * @brief Retrieves the instance for UART1. + * + * This function provides access to the pre-defined `uart1` instance, + * allowing straightforward interaction with the UART hardware from Swift. + * + * @return Pointer to the `uart1` instance. + */ +uart_inst_t* get_uart1(void) { + return uart1; +} + +#endif diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 123ef1c..9044c6b 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -392,13 +392,13 @@ def GDB_NAME(): ( "// UART defines", "// By default the stdout UART is `uart0`, so we will use the second one", - 'let UART_ID = "uart1"', - "let BAUD_RATE = 115200", + "let UART_ID = get_uart1()", + "let BAUD_RATE: UInt32 = 115200", "", "// Use pins 4 and 5 for UART1", "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - "let UART_TX_PIN = 4", - "let UART_RX_PIN = 5", + "let UART_TX_PIN: UInt32 = 4", + "let UART_RX_PIN: UInt32 = 5", ), ( "// Set up our UART", @@ -430,10 +430,10 @@ def GDB_NAME(): "// We are going to use SPI 0, and allocate it to the following GPIO pins", "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", 'let SPI_PORT = "spi0"', - "let PIN_MISO = 16", - "let PIN_CS = 17", - "let PIN_SCK = 18", - "let PIN_MOSI = 19", + "let PIN_MISO: UInt32 = 16", + "let PIN_CS: UInt32 = 17", + "let PIN_SCK: UInt32 = 18", + "let PIN_MOSI: UInt32 = 19", ), ( "// SPI initialisation. This example will use SPI at 1MHz.", @@ -455,8 +455,8 @@ def GDB_NAME(): "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.", "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", 'let I2C_PORT = "i2c0"', - "let I2C_SDA = 8", - "let I2C_SCL = 9", + "let I2C_SDA: UInt32 = 8", + "let I2C_SCL: UInt32 = 9", ), ( "// I2C Initialisation. Using it at 400Khz.", @@ -509,37 +509,47 @@ def GDB_NAME(): ], "pio": [ ( - '#include "blink.pio.h"', - "static func blink_pin_forever(pio: PIO, sm: uint, offset: uint, pin: uint, freq: uint) {", + "func blink_pin_forever(_ pio: PIO, sm: UInt32, offset: UInt32, pin: UInt32, freq: UInt32) {", " blink_program_init(pio, sm, offset, pin)", " pio_sm_set_enabled(pio, sm, true)", "", - ' printf("Blinking pin %d at %d Hz\\n", pin, freq)', + ' print("Blinking pin \\(pin) at \\(freq) Hz")', "", " // PIO counter program takes 3 more cycles in total than we pass as", " // input (wait for n + 1; mov; jmp)", - " pio.txf[sm] = (125000000 / (2 * freq)) - 3", + " let value = (125000000 / (2 * freq)) - 3", + " switch sm {", + " case 0:", + " pio.pointee.txf.0 = value", + " case 1:", + " pio.pointee.txf.1 = value", + " case 2:", + " pio.pointee.txf.2 = value", + " case 3:", + " pio.pointee.txf.3 = value", + " default:", + " // There are 4 state machines available", + ' fatalError("Invalid state machine index")', + " }", "}", ), ( "// PIO Blinking example", - "let pio = pio0", + "guard let pio = get_pio0() else {", + ' fatalError("PIO0 not available")', + "}", "let offset = pio_add_program(pio, &blink_program)", - 'printf("Loaded program at %d\\n", offset)', + 'print("Loaded program at \\(offset)")', "", - "#ifdef PICO_DEFAULT_LED_PIN", - "blink_pin_forever(pio, 0, offset, PICO_DEFAULT_LED_PIN, 3)", - "#else", - "blink_pin_forever(pio, 0, offset, 6, 3)", - "#endif", + "blink_pin_forever(pio, sm: 0, offset: UInt32(offset), pin: UInt32(PICO_DEFAULT_LED_PIN), freq: 3)", "// For more pio examples see https://github.com/raspberrypi/pico-examples/tree/master/pio", ), ], "clocks": [ (), ( - 'printf("System Clock Frequency is %d Hz\\n", clock_get_hz(clk_sys))', - 'printf("USB Clock Frequency is %d Hz\\n", clock_get_hz(clk_usb))', + 'print("System Clock Frequency is \\(clock_get_hz(clk_sys)) Hz")', + 'print("USB Clock Frequency is \\(clock_get_hz(clk_usb)) Hz")', "// For more examples of clocks use see https://github.com/raspberrypi/pico-examples/tree/master/clocks", ), ], @@ -608,12 +618,12 @@ def GDB_NAME(): "let divisor = -321", "// This is the recommended signed fast divider for general use.", "let result = hw_divider_divmod_s32(dividend, divisor)", - 'printf("%d/%d = %d remainder %d\\n", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result))', + 'print("\\(dividend)/\\(divisor) = \\(to_quotient_s32(result)) remainder \\(to_remainder_s32(result))")', "// This is the recommended unsigned fast divider for general use.", "let udividend = 123456", "let udivisor = 321", "let uresult = hw_divider_divmod_u32(udividend, udivisor)", - 'printf("%d/%d = %d remainder %d\\n", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult))', + 'printf("\\(udividend)/\\(udivisor) = \\(to_quotient_u32(uresult)) remainder \\(to_remainder_u32(uresult))")', "// See https://github.com/raspberrypi/pico-examples/tree/master/divider for more complex use", ), ], @@ -629,15 +639,15 @@ def GDB_NAME(): ( "// Enable wifi station", "cyw43_arch_enable_sta_mode()\n", - 'printf("Connecting to Wi-Fi...\\n")', + 'print("Connecting to Wi-Fi...")', 'if cyw43_arch_wifi_connect_timeout_ms("Your Wi-Fi SSID", "Your Wi-Fi Password", CYW43_AUTH_WPA2_AES_PSK, 30000) {', - ' printf("failed to connect.\\n")', + ' print("failed to connect.")', " return 1", "} else {", - ' printf("Connected.\\n")', + ' print("Connected.")', " // Read the ip address in a human readable way", " let ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr)", - ' printf("IP address %d.%d.%d.%d\\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3])', + ' print("IP address \\(ip_address[0]).\\(ip_address[1]).\\(ip_address[2]).\\(ip_address[3])")', "}", ), ], @@ -701,6 +711,9 @@ def codeSdkPath(sdkVersion): return f"${{userHome}}{relativeSDKPath(sdkVersion)}" +CODE_BASIC_TOOLCHAIN_PATH = "${userHome}/.pico-sdk/toolchain" + + def codeOpenOCDPath(openocdVersion): return f"${{userHome}}{relativeOpenOCDPath(openocdVersion)}" @@ -936,7 +949,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): if len(features_list[feat][H_FILE]) == 0: continue o = f'#include "{features_list[feat][H_FILE]}"\n' - if swift: + if swift and bridging_file: bridging_file.write(o) else: file.write(o) @@ -944,7 +957,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): if len(stdlib_examples_list[feat][H_FILE]) == 0: continue o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n' - if swift: + if swift and bridging_file: bridging_file.write(o) else: file.write(o) @@ -952,7 +965,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): if len(picow_options_list[feat][H_FILE]) == 0: continue o = f'#include "{picow_options_list[feat][H_FILE]}"\n' - if swift: + if swift and bridging_file: bridging_file.write(o) else: file.write(o) @@ -969,7 +982,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): for feat in features: if feat in frags: for s in frags[feat][DEFINES]: - if swift and s.startswith("#include"): + if swift and bridging_file and s.startswith("#include"): bridging_file.write(s) bridging_file.write("\n") file.write(s) @@ -1044,7 +1057,10 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): file.write(main) - bridging_file.close() + if bridging_file: + bridging_file.write("// always include last\n") + bridging_file.write("#include \n\n") + bridging_file.close() file.close() @@ -1211,9 +1227,17 @@ def GenerateCMake(folder, params): entry_point_file_name = projectName if params["wantEntryProjName"] else "main" if params["wantCPP"]: - file.write(f"add_executable({projectName} {entry_point_file_name}.cpp )\n\n") + file.write(f"add_executable({projectName} {entry_point_file_name}.cpp)\n\n") elif params["useSwift"]: - file.write(f"add_executable({projectName})\n\n") + file.write( + f"add_executable({projectName} ${{USERHOME}}/.pico-sdk/toolchain/bridge.c)\n\n" + ) + + # Standard libraries + file.write("# Add the standard library to the build\n") + file.write(f"target_link_libraries({projectName}\n") + file.write(" " + STANDARD_LIBRARIES) + file.write(")\n\n") main_file_name = f"{entry_point_file_name}.swift" cmake_custom_swift_command = ( @@ -1225,7 +1249,7 @@ def GenerateCMake(folder, params): " get_property(visited_targets GLOBAL PROPERTY visited_targets)\n\n" " # make sure we don't visit the same target twice\n" " # and that we don't visit the special generator expressions\n" - ' if (${target} MATCHES "\\$<" OR ${target} MATCHES "::@" OR ${target} IN_LIST visited_targets)\n' + ' if (${target} MATCHES "\\\\$<" OR ${target} MATCHES "::@" OR ${target} IN_LIST visited_targets)\n' " return()\n" " endif()\n\n" " # Append the target to visited_targets\n" @@ -1263,11 +1287,11 @@ def GenerateCMake(folder, params): f" $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" - f" ${{CMAKE_CURRENT_LIST_DIR}}/{entry_point_file_name}.swift\n" + f" ${{CMAKE_CURRENT_LIST_DIR}}/{main_file_name}\n" " -c -o ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n" " DEPENDS\n" " ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" - f" ${{CMAKE_CURRENT_LIST_DIR}}/{entry_point_file_name}.swift\n" + f" ${{CMAKE_CURRENT_LIST_DIR}}/{main_file_name}\n" ")\n" ) file.write(cmake_custom_swift_command) @@ -1322,18 +1346,26 @@ def GenerateCMake(folder, params): file.write(f'WIFI_PASSWORD="${WIFI_PASSWORD}"') file.write(")\n\n") - # Standard libraries - file.write("# Add the standard library to the build\n") - file.write(f"target_link_libraries({projectName}\n") - file.write(" " + STANDARD_LIBRARIES) - if params["useSwift"]: - file.write(" ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n") - file.write(")\n\n") + if not params["useSwift"]: + # Standard libraries + file.write("# Add the standard library to the build\n") + file.write(f"target_link_libraries({projectName}\n") + file.write(" " + STANDARD_LIBRARIES) + file.write(")\n\n") + else: + file.write("# Add the swift artifact to the build\n") + file.write(f"target_link_libraries({projectName}\n") + file.write(" ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n") + file.write(")\n\n") # Standard include directories file.write("# Add the standard include files to the build\n") file.write(f"target_include_directories({projectName} PRIVATE\n") - file.write(" " + "${CMAKE_CURRENT_LIST_DIR}\n") + file.write(" ${CMAKE_CURRENT_LIST_DIR}\n") + if params["useSwift"]: + # bridge.h includes serveral helper functions + # for swift-c-pico-sdk interop + file.write(" ${USERHOME}/.pico-sdk/toolchain\n") file.write(")\n\n") if params["useSwift"]: @@ -1494,7 +1526,8 @@ def generateProjectFiles( "name": "Pico", "includePath": [ "${{workspaceFolder}}/**", - "{codeSdkPath(sdkVersion)}/**" + "{codeSdkPath(sdkVersion)}/**"{f""", + "{CODE_BASIC_TOOLCHAIN_PATH}\"""" if useSwift else ""} ], "forcedInclude": [ "{codeSdkPath(sdkVersion)}/src/common/{base_headers_folder_name}/include/pico.h", diff --git a/src/utils/download.mts b/src/utils/download.mts index 5be48ed..e5d1490 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -96,6 +96,15 @@ const CMAKE_PLATFORMS: { [key: string]: string } = { // Compute and cache the home directory const homeDirectory: string = homedir(); +export function buildBasicToolchainPath(absolute: boolean): string { + const relPath = joinPosix(homeDirectory, ".pico-sdk", "toolchain"); + if (absolute) { + return joinPosix(homeDirectory.replaceAll("\\", "/"), relPath); + } else { + return joinPosix("${USERHOME}", relPath); + } +} + export function buildToolchainPath(version: string): string { return joinPosix(homeDirectory, ".pico-sdk", "toolchain", version); } @@ -111,7 +120,6 @@ export function buildSDKPath(version: string): string { } export function buildCMakeIncPath(absolute: boolean): string { - // TODO: maybe replace . with _ const relPath = joinPosix(".pico-sdk", "cmake"); if (absolute) { return joinPosix(homeDirectory.replaceAll("\\", "/"), relPath); @@ -941,6 +949,19 @@ export async function downloadAndInstallToolchain( return false; } + // Install bridge.h and bridge.c files - overwrite if already there + await mkdir(buildBasicToolchainPath(true), { recursive: true }); + // includes helper stuff for swift code - pico-sdk c interop + copyFileSync( + joinPosix(getScriptsRoot(), "bridge.h"), + joinPosix(buildCMakeIncPath(true), "bridge.h") + ); + // includes helper stuff for the swift compiler (workaround) + copyFileSync( + joinPosix(getScriptsRoot(), "bridge.c"), + joinPosix(buildCMakeIncPath(true), "bridge.c") + ); + const archiveFileName = `${toolchain.version}.${artifactExt}`; return downloadAndInstallArchive( From aac35edd7da470f1b16f06d856126c50cb608b1f Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 5 Dec 2024 02:04:18 +0100 Subject: [PATCH 5/8] Better swift support + examples Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/PicoSDK.swift | 17 ++++++ scripts/bridge.h | 56 ++++++++++++++++++++ scripts/pico_project.py | 93 ++++++++++++++++++++++++--------- src/utils/download.mts | 8 +++ src/webview/newProjectPanel.mts | 2 +- 5 files changed, 151 insertions(+), 25 deletions(-) create mode 100644 scripts/PicoSDK.swift diff --git a/scripts/PicoSDK.swift b/scripts/PicoSDK.swift new file mode 100644 index 0000000..ea40c7e --- /dev/null +++ b/scripts/PicoSDK.swift @@ -0,0 +1,17 @@ +// This file contains a swift wrapper for some Pico SDK C style stuff +// Upercase library names can be used to only include helpers for available APIs + +#if PICO_STDLIB +public struct PicoGPIO { + /// Enum for GPIO direction, mirroring the C enum + public enum Direction: UInt8 { + case input = 0 + case output = 1 + } + + /// Swift-friendly wrapper for gpio_set_dir + public static func setDirection(pin: UInt32, direction: Direction) { + gpio_set_dir(pin, direction.rawValue != 0) + } +} +#endif diff --git a/scripts/bridge.h b/scripts/bridge.h index 2647a58..2bb02ca 100644 --- a/scripts/bridge.h +++ b/scripts/bridge.h @@ -1,5 +1,57 @@ #pragma once +#ifndef PICO_DEFAULT_LED_PIN +#define PICO_DEFAULT_LED_PIN 6 +#endif + +#ifdef _HARDWARE_STRUCTS_SPI_H +#ifdef spi0 +spi_inst_t* get_spi0(void) { + return spi0; +} +#endif + +#ifdef spi1 +spi_inst_t* get_spi1(void) { + return spi1; +} +#endif +#endif + +#ifdef _HARDWARE_STRUCTS_I2C_H +#ifdef i2c0 +i2c_inst_t* get_i2c0(void) { + return i2c0; +} +#endif + +#ifdef i2c1 +i2c_inst_t* get_i2c1(void) { + return i2c1; +} +#endif +#endif + +#ifdef _HARDWARE_STRUCTS_PIO_H +#ifdef pio0 +pio_hw_t* get_pio0(void) { + return pio0; +} +#endif + +#ifdef pio1 +pio_hw_t* get_pio1(void) { + return pio1; +} +#endif + +#ifdef pio2 +pio_hw_t* get_pio2(void) { + return pio2; +} +#endif +#endif + /** * @file bridge.h * @brief Simplifies the usage of specific SDK features in Swift by providing wrapper functions. @@ -11,6 +63,7 @@ #ifdef _HARDWARE_STRUCTS_UART_H // Ensure that UART hardware structs are included before compiling +#ifdef uart0 /** * @brief Retrieves the instance for UART0. * @@ -22,7 +75,9 @@ uart_inst_t* get_uart0(void) { return uart0; } +#endif +#ifdef uart1 /** * @brief Retrieves the instance for UART1. * @@ -34,5 +89,6 @@ uart_inst_t* get_uart0(void) { uart_inst_t* get_uart1(void) { return uart1; } +#endif #endif diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 9044c6b..4e90e7f 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -429,7 +429,7 @@ def GDB_NAME(): "// SPI Constants", "// We are going to use SPI 0, and allocate it to the following GPIO pins", "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - 'let SPI_PORT = "spi0"', + "let SPI_PORT = get_spi0()", "let PIN_MISO: UInt32 = 16", "let PIN_CS: UInt32 = 17", "let PIN_SCK: UInt32 = 18", @@ -444,8 +444,8 @@ def GDB_NAME(): "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI)", "", "// Chip select is active-low, so we'll initialise it to a driven-high state", - "gpio_set_dir(PIN_CS, GPIO_OUT)", - "gpio_put(PIN_CS, 1)", + "PicoGPIO.setDirection(pin: PIN_CS, direction: .output) // Use swift wrapper", + "gpio_put(PIN_CS, true)", "// For more examples of SPI use see https://github.com/raspberrypi/pico-examples/tree/master/spi", ), ], @@ -454,7 +454,7 @@ def GDB_NAME(): "// I2C defines", "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.", "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - 'let I2C_PORT = "i2c0"', + "let I2C_PORT = get_i2c0()", "let I2C_SDA: UInt32 = 8", "let I2C_SCL: UInt32 = 9", ), @@ -477,24 +477,24 @@ def GDB_NAME(): ), ( "// Get a free channel, panic() if there are none", - "let chan = dma_claim_unused_channel(true)", + "let chan = UInt32(dma_claim_unused_channel(true))", "", "// 8 bit transfers. Both read and write address increment after each", "// transfer (each pointing to a location in src or dst respectively).", "// No DREQ is selected, so the DMA transfers as fast as it can.", "", - "let c = dma_channel_get_default_config(chan)", + "var c = dma_channel_get_default_config(chan)", "channel_config_set_transfer_data_size(&c, DMA_SIZE_8)", "channel_config_set_read_increment(&c, true)", "channel_config_set_write_increment(&c, true)", "", "dma_channel_configure(", - " chan, // Channel to be configured", - " &c, // The configuration we just created", - " dst, // The initial write address", - " src, // The initial read address", - " count_of(src), // Number of transfers; in this case each is 1 byte.", - " true // Start immediately.", + " chan, // Channel to be configured", + " &c, // The configuration we just created", + " &dst, // The initial write address", + " src, // The initial read address", + " UInt32(src.utf8.count), // Number of transfers; in this case each is 1 byte.", + " true // Start immediately.", ")", "", "// We could choose to go and do something else whilst the DMA is doing its", @@ -538,7 +538,7 @@ def GDB_NAME(): "guard let pio = get_pio0() else {", ' fatalError("PIO0 not available")', "}", - "let offset = pio_add_program(pio, &blink_program)", + "let offset = pio_add_program(pio, [blink_program])", 'print("Loaded program at \\(offset)")', "", "blink_pin_forever(pio, sm: 0, offset: UInt32(offset), pin: UInt32(PICO_DEFAULT_LED_PIN), freq: 3)", @@ -586,7 +586,7 @@ def GDB_NAME(): ), ( "// Timer example code - This example fires off the callback after 2000ms", - "add_alarm_in_ms(2000, alarm_callback, NULL, false)", + "add_alarm_in_ms(2000, alarm_callback, nil, false)", "// For more examples of timer use see https://github.com/raspberrypi/pico-examples/tree/master/timer", ), ], @@ -951,6 +951,8 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): o = f'#include "{features_list[feat][H_FILE]}"\n' if swift and bridging_file: bridging_file.write(o) + if feat == "pio": + bridging_file.write('#include "blink.pio.h"') else: file.write(o) if feat in stdlib_examples_list: @@ -997,7 +999,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): "struct Main {\n" " \n" " static func main() {\n" - " stdio_init_all();\n\n" + " stdio_init_all()\n\n" ) else: main = "\n\n" "int main()\n" "{\n" " stdio_init_all();\n\n" @@ -1177,6 +1179,20 @@ def GenerateCMake(folder, params): file.write(cmake_header1) file.write(cmake_header_us) + if params["useSwift"]: + cmake_if_apple = ( + "if(APPLE)\n" + " execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + " execute_process(COMMAND dirname ${SWIFTC} OUTPUT_VARIABLE SWIFTC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + "elseif(WIN32)\n" + " execute_process(COMMAND where.exe swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + ' string(REGEX REPLACE "(.*)\\\\\\\\[^\\\\\\\\]*$" "\\\\1" SWIFTC_DIR "${SWIFTC}")\n' + "else()\n" + " execute_process(COMMAND which swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + " execute_process(COMMAND dirname ${SWIFTC} OUTPUT_VARIABLE SWIFTC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + "endif()\n" + ) + file.write(cmake_if_apple) file.write(cmake_header2) if params["exceptions"]: @@ -1188,13 +1204,6 @@ def GenerateCMake(folder, params): file.write(cmake_header3) if params["useSwift"]: - cmake_if_apple = ( - "if(APPLE)\n" - " execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" - "else()\n" - " execute_process(COMMAND which swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" - "endif()\n" - ) cmake_swift_target = ( 'set(SWIFT_TARGET "armv6m-none-none-eabi") # RP2040\n\n' 'if(PICO_PLATFORM STREQUAL "rp2350-arm-s")\n' @@ -1209,8 +1218,12 @@ def GenerateCMake(folder, params): ' set(SWIFT_TARGET "riscv32-none-none-eabi")\n' ' list(APPEND CLANG_ARCH_ABI_FLAGS "-Xcc" "-march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb" "-Xcc" "-mabi=ilp32")\n' "endif()\n" + 'set(SWIFT_EMBEDDED_UNICODE_LIB "${SWIFTC_DIR}/../lib/swift/embedded/${SWIFT_TARGET}/libswiftUnicodeDataTables.a")\n\n' + "# Link the Swift Unicode Data Tables library\n" + "if(NOT EXISTS ${SWIFT_EMBEDDED_UNICODE_LIB})\n" + ' message(FATAL_ERROR "Required Swift library not found: ${SWIFT_EMBEDDED_LIB}")\n' + "endif()\n\n" ) - file.write(cmake_if_apple) file.write(cmake_swift_target) # add the preprocessor defines for overall configuration @@ -1237,6 +1250,12 @@ def GenerateCMake(folder, params): file.write("# Add the standard library to the build\n") file.write(f"target_link_libraries({projectName}\n") file.write(" " + STANDARD_LIBRARIES) + if params["features"]: + for feat in params["features"]: + if feat in features_list: + file.write(" " + features_list[feat][LIB_NAME] + "\n") + if feat in picow_options_list: + file.write(" " + picow_options_list[feat][LIB_NAME] + "\n") file.write(")\n\n") main_file_name = f"{entry_point_file_name}.swift" @@ -1273,6 +1292,22 @@ def GenerateCMake(folder, params): "endfunction()\n\n" f"gather_compile_definitions_recursive({projectName})\n" "get_property(COMPILE_DEFINITIONS GLOBAL PROPERTY compilerdefs_list)\n\n" + "# ==== BEGIN Generate compile definitions from linked libraries\n" + "# Gather libraries linked to project\n" + f"get_target_property({projectName}_LIBRARIES {projectName} INTERFACE_LINK_LIBRARIES)\n" + f"if(NOT {projectName}_LIBRARIES)\n" + f' set({projectName}_LIBRARIES "") # Default to an empty list if none found\n' + "endif()\n\n" + 'set(SWIFTC_LIB_DEFINITIONS "")\n' + f"foreach(LIBRARY IN LISTS {projectName}_LIBRARIES)\n" + " # Convert it to uppercase\n" + " string(TOUPPER ${LIBRARY} LIB_NAME_UPPER)\n" + ' list(APPEND SWIFTC_LIB_DEFINITIONS "-D" "${LIB_NAME_UPPER}")\n' + "endforeach()\n\n" + "# Convert the list to a string for the Swift command\n" + 'string(REPLACE " " ";" SWIFTC_DEFINITIONS_STR "${SWIFTC_LIB_DEFINITIONS}")\n' + 'message(STATUS "SWIFTC_LIB_DEFINITIONS: ${SWIFTC_DEFINITIONS_STR}")\n' + "# ==== END Generate compile definitions from linked libraries\n\n" "# Parse compiler definitions into a format that swiftc can understand\n" "list(REMOVE_DUPLICATES COMPILE_DEFINITIONS)\n" 'list(PREPEND COMPILE_DEFINITIONS "")\n' @@ -1284,13 +1319,16 @@ def GenerateCMake(folder, params): " ${SWIFTC}\n" " -target ${SWIFT_TARGET} -Xcc -mfloat-abi=soft -Xcc -fshort-enums\n" " -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library\n" + " ${SWIFTC_DEFINITIONS_STR}\n" f" $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" + " ${USERHOME}/.pico-sdk/sdk/PicoSDK.swift\n" f" ${{CMAKE_CURRENT_LIST_DIR}}/{main_file_name}\n" " -c -o ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n" " DEPENDS\n" " ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" + " ${USERHOME}/.pico-sdk/sdk/PicoSDK.swift\n" f" ${{CMAKE_CURRENT_LIST_DIR}}/{main_file_name}\n" ")\n" ) @@ -1353,6 +1391,13 @@ def GenerateCMake(folder, params): file.write(" " + STANDARD_LIBRARIES) file.write(")\n\n") else: + file.write("# Link Swift Unicode data tables to the build\n") + file.write(f"target_link_options({projectName} PRIVATE\n") + file.write( + " -Wl,--whole-archive ${SWIFT_EMBEDDED_UNICODE_LIB} -Wl,--no-whole-archive\n" + ) + file.write(")\n\n") + file.write("# Add the swift artifact to the build\n") file.write(f"target_link_libraries({projectName}\n") file.write(" ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n") @@ -1372,7 +1417,7 @@ def GenerateCMake(folder, params): file.write(f"add_dependencies({projectName} {projectName}-swiftcode)\n") # Selected libraries/features - if params["features"]: + if params["features"] and not params["useSwift"]: file.write("# Add any user requested libraries\n") file.write(f"target_link_libraries({projectName} \n") for feat in params["features"]: diff --git a/src/utils/download.mts b/src/utils/download.mts index e5d1490..118150f 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -485,6 +485,14 @@ export async function downloadAndInstallSDK( const targetDirectory = buildSDKPath(version); + // Copy swift wrapper + const sdkRoot = dirname(targetDirectory); + await mkdir(sdkRoot, { recursive: true }); + copyFileSync( + joinPosix(getScriptsRoot(), "PicoSDK.swift"), + joinPosix(sdkRoot, "PicoSDK.swift") + ); + // Check if the SDK is already installed if ( existsSync(targetDirectory) && diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index 5d94248..d2b6505 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -1811,7 +1811,7 @@ export class NewProjectPanel {
    ` : `

    - Warning: Project Import Wizard may not work for all projects, and will often require manual correction after the import + Warning: Project Import Wizard may not work for all projects, and will often require manual correction after the import. Swift Projects aren't supported at the moment.

    ` }
    From 04c16e94ffc28df0d7ea681a0ed866f80757a0ae Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 5 Dec 2024 02:47:26 +0100 Subject: [PATCH 6/8] Fix toolchain dir mkdirp Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/utils/download.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/download.mts b/src/utils/download.mts index 118150f..852c321 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -97,7 +97,7 @@ const CMAKE_PLATFORMS: { [key: string]: string } = { const homeDirectory: string = homedir(); export function buildBasicToolchainPath(absolute: boolean): string { - const relPath = joinPosix(homeDirectory, ".pico-sdk", "toolchain"); + const relPath = joinPosix(".pico-sdk", "toolchain"); if (absolute) { return joinPosix(homeDirectory.replaceAll("\\", "/"), relPath); } else { From 9549e1ef712a3a664edaeaa215b80bda70c5fef8 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:02:57 +0100 Subject: [PATCH 7/8] Finalize alpha Swift support Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- README.md | 41 +++++++++++++++++++++++++++++++++ scripts/bridge.h | 14 +++++++++++ scripts/pico_project.py | 21 +++++++++-------- src/commands/newProject.mts | 2 +- src/webview/newProjectPanel.mts | 7 +++++- web/main.js | 30 ++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6e05f1e..b5bdae1 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ For comprehensive setup instructions, refer to the [Getting Started guide](https ### Project Setup and Management - **Project Generator**: Easily create and configure new projects with support for advanced Pico features like I2C and PIO. The generator targets the Ninja build system and allows customization during project creation. +- **Swift Language Support**: Develop Pico projects using the Swift programming language by leveraging the latest **experimental** Swift toolchain. Generate Swift-enabled projects directly from the extension. - **Quick Project Setup**: Initiate new Pico projects directly from the Explorer view, when no workspace is open. - **MicroPython Support**: Create and develop MicroPython-based Pico projects with support provided through the [MicroPico](https://github.com/paulober/MicroPico) extension. @@ -87,6 +88,46 @@ For optimal functionality, consider enabling: When prompted, select the `Pico` kit in CMake Tools, and set your build and launch targets accordingly. Use CMake Tools for compilation, but continue using this extension for debugging, as CMake Tools debugging is not compatible with Pico. +## Swift Support + +The Pico VS Code extension supports Swift, enabling you to develop Raspberry Pi Pico projects using the Swift programming language. To enable Swift support, follow these steps: + +### 1. Install the Swift Experimental Toolchain + +Download and install the latest Swift experimental toolchain for your platform: + +- **Linux**: [Install Swift for Linux](https://www.swift.org/install/linux/#platforms) +- **macOS**: [Install Swift for macOS](https://www.swift.org/install/macos) + +> **Note:** Windows is not currently supported. + +### 2. Configure the Swift Toolchain + +#### **For Linux:** +Ensure the `swiftc` executable is included in your system's `PATH`. Once added, restart VS Code for the changes to take effect. + +#### **For macOS:** +If the build fails or the Swift toolchain isn’t detected, force the toolchain selection by adding the following line to your `~/.zprofile`: + +- **For system-wide installation:** + ```zsh + export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist) + ``` + +- **For user-specific installation:** + ```zsh + export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw $HOME/Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist) + ``` + +Then, restart your terminal and reopen VS Code. + +### 3. Create a New Pico Project with Swift + +1. Open VS Code. +2. Use the **"Generate Swift Code"** option to create a new Pico project with Swift support enabled. +> Note: At the moment, Swift support is only available for new projects based on Pico 2 and Pico SDK v2.1.0. +3. Start building your Swift-powered Pico project! + ## VS Code Profiles If you work with multiple microcontroller toolchains, consider installing this extension into a [VS Code Profile](https://code.visualstudio.com/docs/editor/profiles) to avoid conflicts with other toolchains. Follow these steps: diff --git a/scripts/bridge.h b/scripts/bridge.h index 2bb02ca..18a3dd6 100644 --- a/scripts/bridge.h +++ b/scripts/bridge.h @@ -4,6 +4,20 @@ #define PICO_DEFAULT_LED_PIN 6 #endif +#ifdef _HARDWARE_STRUCTS_INTERP_H +#ifdef interp0 +interp_hw_t* get_interp0(void) { + return interp0; +} +#endif + +#ifdef interp1 +interp_hw_t* get_interp1(void) { + return interp1; +} +#endif +#endif + #ifdef _HARDWARE_STRUCTS_SPI_H #ifdef spi0 spi_inst_t* get_spi0(void) { diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 4e90e7f..821ec22 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -559,7 +559,8 @@ def GDB_NAME(): "// GPIO initialisation.", "// We will make this GPIO an input, and pull it up by default", "gpio_init(GPIO)", - "gpio_set_dir(GPIO, GPIO_IN)", + # "gpio_set_dir(GPIO, GPIO_IN)", + "PicoGPIO.setDirection(pin: GPIO, direction: .input)", "gpio_pull_up(GPIO)", "// See https://github.com/raspberrypi/pico-examples/tree/master/gpio for other gpio examples, including using interrupts", ), @@ -568,18 +569,18 @@ def GDB_NAME(): (), ( "// Interpolator example code", - "interp_config cfg = interp_default_config()", + "var cfg = interp_default_config()", "// Now use the various interpolator library functions for your use case", "// e.g. interp_config_clamp(&cfg, true)", "// interp_config_shift(&cfg, 2)", "// Then set the config ", - "interp_set_config(interp0, 0, &cfg)", + "interp_set_config(get_interp0(), 0, &cfg)", "// For examples of interpolator use see https://github.com/raspberrypi/pico-examples/tree/master/interp", ), ], "timer": [ ( - "func alarm_callback(id: alarm_id_t, user_data: void): int64_t {", + "func alarm_callback(id: alarm_id_t, user_data: UnsafeMutableRawPointer?) -> Int64 {", " // Put your timeout handler code in here", " return 0", "}", @@ -595,13 +596,13 @@ def GDB_NAME(): ( "// Watchdog example code", "if watchdog_caused_reboot() {", - ' printf("Rebooted by Watchdog!\\n")', + ' print("Rebooted by Watchdog!")', " // Whatever action you may take if a watchdog caused a reboot", "}", "", "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot", "// second arg is pause on debug which means the watchdog will pause when stepping through code", - "watchdog_enable(100, 1)", + "watchdog_enable(100, true)", "", "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot", "watchdog_update()", @@ -1182,11 +1183,10 @@ def GenerateCMake(folder, params): if params["useSwift"]: cmake_if_apple = ( "if(APPLE)\n" - " execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + " execute_process(COMMAND xcrun --toolchain swift -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" " execute_process(COMMAND dirname ${SWIFTC} OUTPUT_VARIABLE SWIFTC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)\n" "elseif(WIN32)\n" - " execute_process(COMMAND where.exe swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" - ' string(REGEX REPLACE "(.*)\\\\\\\\[^\\\\\\\\]*$" "\\\\1" SWIFTC_DIR "${SWIFTC}")\n' + ' message(FATAL_ERROR "Embedded Swift is currently not supported on Windows")\n' "else()\n" " execute_process(COMMAND which swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" " execute_process(COMMAND dirname ${SWIFTC} OUTPUT_VARIABLE SWIFTC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)\n" @@ -1938,6 +1938,9 @@ def DoEverything(params): if args.debugger > len(debugger_list) - 1: args.debugger = 0 + if args.swift and args.cpp: + args.swift = False + if "RISCV" in args.toolchainVersion: if "COREV" in args.toolchainVersion: COMPILER_TRIPLE = COREV_TRIPLE diff --git a/src/commands/newProject.mts b/src/commands/newProject.mts index 45c01a7..ed690ff 100644 --- a/src/commands/newProject.mts +++ b/src/commands/newProject.mts @@ -19,7 +19,7 @@ export default class NewProjectCommand extends CommandWithArgs { private readonly _logger: Logger = new Logger("NewProjectCommand"); private readonly _extensionUri: Uri; private static readonly micropythonOption = "MicroPython"; - private static readonly cCppOption = "C/C++"; + private static readonly cCppOption = "C/C++/Swift"; public static readonly id = "newProject"; diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index d2b6505..45b190b 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -2123,12 +2123,17 @@ export class NewProjectPanel {
  • + ${ + !isWindows + ? `
  • -
  • + ` + : "" + }
  • diff --git a/web/main.js b/web/main.js index 0b9107f..201a368 100644 --- a/web/main.js +++ b/web/main.js @@ -750,6 +750,36 @@ var exampleSupportedBoards = []; }); } + const swiftCodeGen = document.getElementById('swift-code-gen-cblist'); + if (swiftCodeGen) { + swiftCodeGen.addEventListener('change', function (event) { + const checked = event.currentTarget.checked; + if (!checked) { + return; + } + + const cppCodeGen = document.getElementById('cpp-code-gen-cblist'); + if (cppCodeGen) { + cppCodeGen.checked = false; + } + }); + } + + const cppCodeGen = document.getElementById('cpp-code-gen-cblist'); + if (cppCodeGen) { + cppCodeGen.addEventListener('change', function (event) { + const checked = event.currentTarget.checked; + if (!checked) { + return; + } + + const swiftCodeGen = document.getElementById('swift-code-gen-cblist'); + if (swiftCodeGen) { + swiftCodeGen.checked = false; + } + }); + } + const ninjaVersionRadio = document.getElementsByName('ninja-version-radio'); if (ninjaVersionRadio.length > 0) ninjaVersionRadio[0].checked = true; From 8f576329c2b9b2c438018fc776ad5950e7aa8a5e Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:39:26 +0100 Subject: [PATCH 8/8] Fix invalid escape Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/pico_project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 821ec22..291e444 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -1320,8 +1320,8 @@ def GenerateCMake(folder, params): " -target ${SWIFT_TARGET} -Xcc -mfloat-abi=soft -Xcc -fshort-enums\n" " -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library\n" " ${SWIFTC_DEFINITIONS_STR}\n" - f" $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" - " $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" + f" $$\\( echo '$' | tr '\\;' '\\\\n' | sed -e 's/\\\\\\(.*\\\\\\)/-Xcc -I\\\\1/g' \\)\n" + " $$\\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\\(.*\\\\\\)/-Xcc -I\\\\1/g' \\)\n" " -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" " ${USERHOME}/.pico-sdk/sdk/PicoSDK.swift\n" f" ${{CMAKE_CURRENT_LIST_DIR}}/{main_file_name}\n"