1492 lines
58 KiB
Python
Executable File
1492 lines
58 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
#
|
|
# Copyright (c) 2020-2023 Raspberry Pi (Trading) Ltd.
|
|
#
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
#
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
from pathlib import Path
|
|
import sys
|
|
import subprocess
|
|
import platform
|
|
import shlex
|
|
import csv
|
|
|
|
# TODO: conditional import of tkinter if --gui option is used
|
|
try:
|
|
import tkinter as tk
|
|
from tkinter import \
|
|
messagebox as mb, \
|
|
filedialog as fd, \
|
|
simpledialog as sd, \
|
|
ttk
|
|
|
|
except ImportError:
|
|
print("[\033[91mERROR\033[0m] tkinter module not found")
|
|
sys.exit(1)
|
|
|
|
|
|
class ExitCodes:
|
|
"""Exit codes per reason; range starts at 0xA00 to avoid clashing with OS reserved exit codes"""
|
|
SUCCESS = 0
|
|
INVALID_PROJECT_PATH = -0xA01
|
|
EXISTING_PROJECT_NO_OVERWRITE = -0xA02
|
|
NO_COMPILER_FOUND = -0xA03
|
|
NO_PROJECT_NAME = -0xA04
|
|
PICO_SDK_NOT_FOUND = -0xA05
|
|
|
|
CMAKELIST_FILENAME = 'CMakeLists.txt'
|
|
CMAKECACHE_FILENAME = 'CMakeCache.txt'
|
|
|
|
COMPILER_NAME = 'arm-none-eabi-gcc'
|
|
|
|
VSCODE_LAUNCH_FILENAME = 'launch.json'
|
|
VSCODE_C_PROPERTIES_FILENAME = 'c_cpp_properties.json'
|
|
VSCODE_SETTINGS_FILENAME ='settings.json'
|
|
VSCODE_EXTENSIONS_FILENAME ='extensions.json'
|
|
VSCODE_FOLDER='.vscode'
|
|
|
|
CONFIG_UNSET="Not set"
|
|
|
|
# Standard libraries for all builds
|
|
# And any more to string below, space separator
|
|
STANDARD_LIBRARIES = 'pico_stdlib'
|
|
|
|
# Indexed on feature name, tuple contains the C file, the H file and the CMake project name for the feature.
|
|
# Some lists may contain an extra/ancillary file needed for that feature
|
|
GUI_TEXT = 0
|
|
C_FILE = 1
|
|
H_FILE = 2
|
|
LIB_NAME = 3
|
|
ANCILLARY_FILE = 4
|
|
|
|
features_list = {
|
|
'spi' : ("SPI", "spi.c", "hardware/spi.h", "hardware_spi"),
|
|
'i2c' : ("I2C interface", "i2c.c", "hardware/i2c.h", "hardware_i2c"),
|
|
'dma' : ("DMA support", "dma.c", "hardware/dma.h", "hardware_dma"),
|
|
'pio' : ("PIO interface", "pio.c", "hardware/pio.h", "hardware_pio"),
|
|
'interp' : ("HW interpolation", "interp.c", "hardware/interp.h", "hardware_interp"),
|
|
'timer' : ("HW timer", "timer.c", "hardware/timer.h", "hardware_timer"),
|
|
'watch' : ("HW watchdog", "watch.c", "hardware/watchdog.h", "hardware_watchdog"),
|
|
'clocks' : ("HW clocks", "clocks.c", "hardware/clocks.h", "hardware_clocks"),
|
|
}
|
|
|
|
picow_options_list = {
|
|
'picow_none' : ("None", "", "", "", ""),
|
|
'picow_led' : ("PicoW onboard LED", "", "pico/cyw43_arch.h", "pico_cyw43_arch_none", ""),
|
|
'picow_poll' : ("Polled lwIP", "", "pico/cyw43_arch.h", "pico_cyw43_arch_lwip_poll", "lwipopts.h"),
|
|
'picow_background' :("Background lwIP", "", "pico/cyw43_arch.h", "pico_cyw43_arch_lwip_threadsafe_background", "lwipopts.h"),
|
|
# 'picow_freertos' : ("Full lwIP (FreeRTOS)", "", "pico/cyw43_arch.h", "pico_cyw43_arch_lwip_sys_freertos", "lwipopts.h"),
|
|
}
|
|
|
|
stdlib_examples_list = {
|
|
'uart': ("UART", "uart.c", "hardware/uart.h", "hardware_uart"),
|
|
'gpio' : ("GPIO interface", "gpio.c", "hardware/gpio.h", "hardware_gpio"),
|
|
'div' : ("Low level HW Divider", "divider.c", "hardware/divider.h", "hardware_divider")
|
|
}
|
|
|
|
debugger_list = ["SWD", "PicoProbe", "CMSIS-DAP Debug Probe"]
|
|
debugger_config_list = ["raspberrypi-swd.cfg", "picoprobe.cfg", "cmsis-dap.cfg"]
|
|
debug_server_args_list = ["", "", "\"-c\", \"adapter speed 5000\" "]
|
|
|
|
DEFINES = 0
|
|
INITIALISERS = 1
|
|
# Could add an extra item that shows how to use some of the available functions for the feature
|
|
#EXAMPLE = 2
|
|
|
|
# This also contains example code for the standard library (see stdlib_examples_list)
|
|
code_fragments_per_feature = {
|
|
'uart' : [
|
|
("// UART defines",
|
|
"// By default the stdout UART is `uart0`, so we will use the second one",
|
|
"#define UART_ID uart1",
|
|
"#define BAUD_RATE 9600", "",
|
|
"// 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",
|
|
"#define UART_TX_PIN 4",
|
|
"#define 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);", "" )
|
|
],
|
|
'spi' : [
|
|
( "// SPI Defines",
|
|
"// 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",
|
|
"#define SPI_PORT spi0",
|
|
"#define PIN_MISO 16",
|
|
"#define PIN_CS 17",
|
|
"#define PIN_SCK 18",
|
|
"#define 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);", "")
|
|
],
|
|
'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",
|
|
"#define I2C_PORT i2c0",
|
|
"#define I2C_SDA 8",
|
|
"#define 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);"
|
|
)
|
|
],
|
|
"gpio" : [
|
|
(
|
|
"// GPIO defines",
|
|
"// Example uses GPIO 2",
|
|
"#define 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);","",
|
|
)
|
|
],
|
|
"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);",
|
|
)
|
|
],
|
|
|
|
"timer" : [
|
|
(
|
|
"int64_t alarm_callback(alarm_id_t id, void *user_data) {",
|
|
" // 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);"
|
|
)
|
|
],
|
|
|
|
"watchdog":[ (),
|
|
(
|
|
"// Watchdog example code",
|
|
"if (watchdog_caused_reboot()) {",
|
|
" // 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.",
|
|
"int32_t dividend = 123456;",
|
|
"int32_t divisor = -321;",
|
|
"// This is the recommended signed fast divider for general use.",
|
|
"divmod_result_t 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.",
|
|
"int32_t udividend = 123456;",
|
|
"int32_t udivisor = 321;",
|
|
"divmod_result_t uresult = hw_divider_divmod_u32(udividend, udivisor);",
|
|
"printf(\"%d/%d = %d remainder %d\\n\", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult));"
|
|
)
|
|
]
|
|
}
|
|
|
|
configuration_dictionary = list(dict())
|
|
|
|
isMac = False
|
|
isWindows = False
|
|
compilerPath = Path("/usr/bin/arm-none-eabi-gcc")
|
|
|
|
def GetBackground():
|
|
return 'white'
|
|
|
|
def GetButtonBackground():
|
|
return 'white'
|
|
|
|
def GetTextColour():
|
|
return 'black'
|
|
|
|
def GetButtonTextColour():
|
|
return '#c51a4a'
|
|
|
|
def RunGUI(sdkpath, args):
|
|
root = tk.Tk()
|
|
style = ttk.Style(root)
|
|
style.theme_use('default')
|
|
|
|
ttk.Style().configure("TButton", padding=6, relief="groove", border=2, foreground=GetButtonTextColour(), background=GetButtonBackground())
|
|
ttk.Style().configure("TLabel", foreground=GetTextColour(), background=GetBackground() )
|
|
ttk.Style().configure("TCheckbutton", foreground=GetTextColour(), background=GetBackground())
|
|
ttk.Style().configure("TRadiobutton", foreground=GetTextColour(), background=GetBackground() )
|
|
ttk.Style().configure("TLabelframe", foreground=GetTextColour(), background=GetBackground() )
|
|
ttk.Style().configure("TLabelframe.Label", foreground=GetTextColour(), background=GetBackground() )
|
|
ttk.Style().configure("TCombobox", foreground=GetTextColour(), background=GetBackground() )
|
|
ttk.Style().configure("TListbox", foreground=GetTextColour(), background=GetBackground() )
|
|
|
|
ttk.Style().map("TCheckbutton", background = [('disabled', GetBackground())])
|
|
ttk.Style().map("TRadiobutton", background = [('disabled', GetBackground())])
|
|
ttk.Style().map("TButton", background = [('disabled', GetBackground())])
|
|
ttk.Style().map("TLabel", background = [('background', GetBackground())])
|
|
|
|
app = ProjectWindow(root, sdkpath, args)
|
|
|
|
app.configure(background=GetBackground())
|
|
|
|
root.mainloop()
|
|
sys.exit(ExitCodes.SUCCESS)
|
|
|
|
def RunWarning(message):
|
|
mb.showwarning('Raspberry Pi Pico Project Generator', message)
|
|
sys.exit(ExitCodes.SUCCESS)
|
|
|
|
import threading
|
|
|
|
def thread_function(text, command, ok):
|
|
l = shlex.split(command)
|
|
proc = subprocess.Popen(l, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
for line in iter(proc.stdout.readline,''):
|
|
if not line:
|
|
if ok:
|
|
ok["state"] = tk.NORMAL
|
|
return
|
|
text.insert(tk.END, line)
|
|
text.see(tk.END)
|
|
|
|
# Function to run an OS command and display the output in a new modal window
|
|
class DisplayWindow(tk.Toplevel):
|
|
def __init__(self, parent, title):
|
|
tk.Toplevel.__init__(self, parent)
|
|
self.parent = parent
|
|
self.init_window(title)
|
|
|
|
def init_window(self, title):
|
|
self.title(title)
|
|
|
|
frame = tk.Frame(self, borderwidth=5, relief=tk.RIDGE)
|
|
frame.pack(fill=tk.X, expand=True, side=tk.TOP)
|
|
|
|
scrollbar = tk.Scrollbar(frame)
|
|
self.text = tk.Text(frame, bg='gray14', fg='gray99')
|
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
self.text.pack(side=tk.LEFT, fill=tk.Y)
|
|
scrollbar.config(command=self.text.yview)
|
|
self.text.config(yscrollcommand=scrollbar.set)
|
|
|
|
frame1 = tk.Frame(self, borderwidth=1)
|
|
frame1.pack(fill=tk.X, expand=True, side=tk.BOTTOM)
|
|
self.OKButton = ttk.Button(frame1, text="OK", command=self.OK)
|
|
self.OKButton["state"] = tk.DISABLED
|
|
self.OKButton.pack()
|
|
|
|
# make dialog modal
|
|
self.transient(self.parent)
|
|
self.grab_set()
|
|
|
|
def OK(self):
|
|
self.grab_release()
|
|
self.destroy()
|
|
|
|
def RunCommandInWindow(parent, command):
|
|
w = DisplayWindow(parent, command)
|
|
x = threading.Thread(target=thread_function, args=(w.text, command, w.OKButton))
|
|
x.start()
|
|
parent.wait_window(w)
|
|
|
|
class EditBoolWindow(sd.Dialog):
|
|
|
|
def __init__(self, parent, configitem, current):
|
|
self.parent = parent
|
|
self.config_item = configitem
|
|
self.current = current
|
|
sd.Dialog.__init__(self, parent, "Edit boolean configuration")
|
|
|
|
|
|
def body(self, master):
|
|
self.configure(background=GetBackground())
|
|
ttk.Label(self, text=self.config_item['name']).pack()
|
|
self.result = tk.StringVar()
|
|
self.result.set(self.current)
|
|
ttk.Radiobutton(master, text="True", variable=self.result, value="True").pack(anchor=tk.W)
|
|
ttk.Radiobutton(master, text="False", variable=self.result, value="False").pack(anchor=tk.W)
|
|
ttk.Radiobutton(master, text=CONFIG_UNSET, variable=self.result, value=CONFIG_UNSET).pack(anchor=tk.W)
|
|
|
|
def get(self):
|
|
return self.result.get()
|
|
|
|
class EditIntWindow(sd.Dialog):
|
|
|
|
def __init__(self, parent, configitem, current):
|
|
self.parent = parent
|
|
self.config_item = configitem
|
|
self.current = current
|
|
sd.Dialog.__init__(self, parent, "Edit integer configuration")
|
|
|
|
def body(self, master):
|
|
self.configure(background=GetBackground())
|
|
str = self.config_item['name'] + " Max = " + self.config_item['max'] + " Min = " + self.config_item['min']
|
|
ttk.Label(self, text=str).pack()
|
|
self.input = tk.Entry(self)
|
|
self.input.pack(pady=4)
|
|
self.input.insert(0, self.current)
|
|
ttk.Button(self, text=CONFIG_UNSET, command=self.unset).pack(pady=5)
|
|
|
|
def validate(self):
|
|
self.result = self.input.get()
|
|
# Check for numeric entry
|
|
return True
|
|
|
|
def unset(self):
|
|
self.result = CONFIG_UNSET
|
|
self.destroy()
|
|
|
|
def get(self):
|
|
return self.result
|
|
|
|
class EditEnumWindow(sd.Dialog):
|
|
def __init__(self, parent, configitem, current):
|
|
self.parent = parent
|
|
self.config_item = configitem
|
|
self.current = current
|
|
sd.Dialog.__init__(self, parent, "Edit Enumeration configuration")
|
|
|
|
def body(self, master):
|
|
#self.configure(background=GetBackground())
|
|
values = self.config_item['enumvalues'].split('|')
|
|
values.insert(0,'Not set')
|
|
self.input = ttk.Combobox(self, values=values, state='readonly')
|
|
self.input.set(self.current)
|
|
self.input.pack(pady=12)
|
|
|
|
def validate(self):
|
|
self.result = self.input.get()
|
|
return True
|
|
|
|
def get(self):
|
|
return self.result
|
|
|
|
|
|
class ConfigurationWindow(tk.Toplevel):
|
|
|
|
def __init__(self, parent, initial_config):
|
|
tk.Toplevel.__init__(self, parent)
|
|
self.master = parent
|
|
self.results = initial_config
|
|
self.init_window(self)
|
|
|
|
def init_window(self, args):
|
|
self.configure(background=GetBackground())
|
|
self.title("Advanced Configuration")
|
|
ttk.Label(self, text="Select the advanced options you wish to enable or change. Note that you really should understand the implications of changing these items before using them!").grid(row=0, column=0, columnspan=5)
|
|
ttk.Label(self, text="Name").grid(row=1, column=0, sticky=tk.W)
|
|
ttk.Label(self, text="Type").grid(row=1, column=1, sticky=tk.W)
|
|
ttk.Label(self, text="Min").grid(row=1, column=2, sticky=tk.W)
|
|
ttk.Label(self, text="Max").grid(row=1, column=3, sticky=tk.W)
|
|
ttk.Label(self, text="Default").grid(row=1, column=4, sticky=tk.W)
|
|
ttk.Label(self, text="User").grid(row=1, column=5, sticky=tk.W)
|
|
|
|
okButton = ttk.Button(self, text="OK", command=self.ok)
|
|
cancelButton = ttk.Button(self, text="Cancel", command=self.cancel)
|
|
|
|
self.namelist = tk.Listbox(self, selectmode=tk.SINGLE)
|
|
self.typelist = tk.Listbox(self, selectmode=tk.SINGLE)
|
|
self.minlist = tk.Listbox(self, selectmode=tk.SINGLE)
|
|
self.maxlist = tk.Listbox(self, selectmode=tk.SINGLE)
|
|
self.defaultlist = tk.Listbox(self, selectmode=tk.SINGLE)
|
|
self.valuelist = tk.Listbox(self, selectmode=tk.SINGLE)
|
|
|
|
self.descriptionText = tk.Text(self, state=tk.DISABLED, height=2)
|
|
|
|
## Make a list of our list boxes to make it all easier to handle
|
|
self.listlist = [self.namelist, self.typelist, self.minlist, self.maxlist, self.defaultlist, self.valuelist]
|
|
|
|
scroll = tk.Scrollbar(self, orient=tk.VERTICAL, command=self.yview)
|
|
|
|
for box in self.listlist:
|
|
box.config(width=0)
|
|
box.config(yscrollcommand=scroll.set)
|
|
box.bind("<MouseWheel>", self.mousewheel)
|
|
box.bind("<Button-4>", self.mousewheel)
|
|
box.bind("<Button-5>", self.mousewheel)
|
|
box.bind("<<ListboxSelect>>", self.changeSelection)
|
|
box.bind("<Double-Button>", self.doubleClick)
|
|
box.config(exportselection=False)
|
|
box.bind("<Down>", self.OnEntryUpDown)
|
|
box.bind("<Up>", self.OnEntryUpDown)
|
|
|
|
scroll.grid(column=7, sticky=tk.N + tk.S)
|
|
|
|
i = 0
|
|
for box in self.listlist:
|
|
box.grid(row=2, column=i, padx=0, sticky=tk.W + tk.E)
|
|
i+=1
|
|
|
|
self.descriptionText.grid(row = 3, column=0, columnspan=4, sticky=tk.W + tk.E)
|
|
cancelButton.grid(column=5, row = 3, padx=5)
|
|
okButton.grid(column=4, row = 3, sticky=tk.E, padx=5)
|
|
|
|
# populate the list box with our config options
|
|
for conf in configuration_dictionary:
|
|
self.namelist.insert(tk.END, conf['name'])
|
|
s = conf['type']
|
|
if s == "":
|
|
s = "int"
|
|
self.typelist.insert(tk.END, s)
|
|
self.maxlist.insert(tk.END, conf['max'])
|
|
self.minlist.insert(tk.END, conf['min'])
|
|
self.defaultlist.insert(tk.END, conf['default'])
|
|
|
|
# see if this config has a setting, our results member has this predefined from init
|
|
val = self.results.get(conf['name'], CONFIG_UNSET)
|
|
self.valuelist.insert(tk.END, val)
|
|
if val != CONFIG_UNSET:
|
|
self.valuelist.itemconfig(self.valuelist.size() - 1, {'bg':'green'})
|
|
|
|
def yview(self, *args):
|
|
for box in self.listlist:
|
|
box.yview(*args)
|
|
|
|
def mousewheel(self, event):
|
|
if (event.num == 4): # Linux encodes wheel as 'buttons' 4 and 5
|
|
delta = -1
|
|
elif (event.num == 5):
|
|
delta = 1
|
|
else: # Windows & OSX
|
|
delta = event.delta
|
|
|
|
for box in self.listlist:
|
|
box.yview("scroll", delta, "units")
|
|
return "break"
|
|
|
|
def changeSelection(self, evt):
|
|
box = evt.widget
|
|
sellist = box.curselection()
|
|
|
|
if sellist:
|
|
index = int(sellist[0])
|
|
config = self.namelist.get(index)
|
|
# Now find the description for that config in the dictionary
|
|
for conf in configuration_dictionary:
|
|
if conf['name'] == config:
|
|
self.descriptionText.config(state=tk.NORMAL)
|
|
self.descriptionText.delete(1.0,tk.END)
|
|
str = config + "\n" + conf['description']
|
|
self.descriptionText.insert(1.0, str)
|
|
self.descriptionText.config(state=tk.DISABLED)
|
|
break
|
|
# Set all the other list boxes to the same index
|
|
for b in self.listlist:
|
|
if b != box:
|
|
b.selection_clear(0, tk.END)
|
|
b.selection_set(index)
|
|
|
|
def OnEntryUpDown(self, event):
|
|
box = event.widget
|
|
selection = box.curselection()
|
|
|
|
if selection:
|
|
index = int(selection[0])
|
|
if event.keysym == 'Up':
|
|
index -= 1
|
|
elif event.keysym == 'Down':
|
|
index += 1
|
|
|
|
if 0 <= index < box.size():
|
|
for b in self.listlist:
|
|
b.selection_clear(0, tk.END)
|
|
b.selection_set(index)
|
|
b.see(index)
|
|
|
|
def doubleClick(self, evt):
|
|
box = evt.widget
|
|
index = int(box.curselection()[0])
|
|
config = self.namelist.get(index)
|
|
# Get the associated dict entry from our list of configs
|
|
for conf in configuration_dictionary:
|
|
if conf['name'] == config:
|
|
if (conf['type'] == 'bool'):
|
|
result = EditBoolWindow(self, conf, self.valuelist.get(index)).get()
|
|
elif (conf['type'] == 'int' or conf['type'] == ""): # "" defaults to int
|
|
result = EditIntWindow(self, conf, self.valuelist.get(index)).get()
|
|
elif conf['type'] == 'enum':
|
|
result = EditEnumWindow(self, conf, self.valuelist.get(index)).get()
|
|
|
|
# Update the valuelist with our new item
|
|
self.valuelist.delete(index)
|
|
self.valuelist.insert(index, result)
|
|
if result != CONFIG_UNSET:
|
|
self.valuelist.itemconfig(index, {'bg':'green'})
|
|
break
|
|
|
|
def ok(self):
|
|
# Get the selections, and create a list of them
|
|
for i, val in enumerate(self.valuelist.get(0, tk.END)):
|
|
if val != CONFIG_UNSET:
|
|
self.results[self.namelist.get(i)] = val
|
|
else:
|
|
self.results.pop(self.namelist.get(i), None)
|
|
|
|
self.destroy()
|
|
|
|
def cancel(self):
|
|
self.destroy()
|
|
|
|
def get(self):
|
|
return self.results
|
|
|
|
class WirelessSettingsWindow(sd.Dialog):
|
|
|
|
def __init__(self, parent):
|
|
sd.Dialog.__init__(self, parent, "Wireless settings")
|
|
self.parent = parent
|
|
|
|
def body(self, master):
|
|
self.configure(background=GetBackground())
|
|
master.configure(background=GetBackground())
|
|
self.ssid = tk.StringVar()
|
|
self.password = tk.StringVar()
|
|
|
|
a = ttk.Label(master, text='SSID :', background=GetBackground())
|
|
a.grid(row=0, column=0, sticky=tk.E)
|
|
a.configure(background=GetBackground())
|
|
ttk.Entry(master, textvariable=self.ssid).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5)
|
|
|
|
ttk.Label(master, text='Password :').grid(row=1, column=0, sticky=tk.E)
|
|
ttk.Entry(master, textvariable=self.password).grid(row=1, column=1, sticky=tk.W+tk.E, padx=5)
|
|
|
|
self.transient(self.parent)
|
|
self.grab_set()
|
|
|
|
def ok(self):
|
|
self.grab_release()
|
|
self.destroy()
|
|
|
|
def cancel(self):
|
|
self.destroy()
|
|
|
|
def get(self):
|
|
return (self.ssid.get(), self.password.get())
|
|
|
|
# Our main window
|
|
class ProjectWindow(tk.Frame):
|
|
|
|
def __init__(self, parent, sdkpath, args):
|
|
tk.Frame.__init__(self, parent)
|
|
self.master = parent
|
|
self.sdkpath = sdkpath
|
|
self.init_window(args)
|
|
self.configs = dict()
|
|
self.ssid = str()
|
|
self.password = str()
|
|
|
|
def setState(self, thing, state):
|
|
for child in thing.winfo_children():
|
|
child.configure(state=state)
|
|
|
|
def boardtype_change_callback(self, event):
|
|
boardtype = self.boardtype.get()
|
|
if boardtype == "pico_w":
|
|
self.setState(self.picowSubframe, "enabled")
|
|
else:
|
|
self.setState(self.picowSubframe, "disabled")
|
|
|
|
def wirelessSettings(self):
|
|
result = WirelessSettingsWindow(self)
|
|
self.ssid, self.password = result.get()
|
|
|
|
def init_window(self, args):
|
|
self.master.title("Raspberry Pi Pico Project Generator")
|
|
self.master.configure(bg=GetBackground())
|
|
|
|
optionsRow = 0
|
|
|
|
mainFrame = tk.Frame(self, bg=GetBackground()).grid(row=optionsRow, column=0, columnspan=6, rowspan=12)
|
|
|
|
# Need to keep a reference to the image or it will not appear.
|
|
self.logo = tk.PhotoImage(file=GetFilePath("logo_alpha.gif"))
|
|
logowidget = ttk.Label(mainFrame, image=self.logo, borderwidth=0, relief="solid").grid(row=0,column=0, columnspan=5, pady=10)
|
|
|
|
optionsRow += 2
|
|
|
|
namelbl = ttk.Label(mainFrame, text='Project Name :').grid(row=optionsRow, column=0, sticky=tk.E)
|
|
self.projectName = tk.StringVar()
|
|
|
|
if args.name != None:
|
|
self.projectName.set(args.name)
|
|
else:
|
|
self.projectName.set('ProjectName')
|
|
|
|
nameEntry = ttk.Entry(mainFrame, textvariable=self.projectName).grid(row=optionsRow, column=1, sticky=tk.W+tk.E, padx=5)
|
|
|
|
optionsRow += 1
|
|
|
|
locationlbl = ttk.Label(mainFrame, text='Location :').grid(row=optionsRow, column=0, sticky=tk.E)
|
|
self.locationName = tk.StringVar()
|
|
self.locationName.set(os.getcwd() if not args.projectRoot else args.projectRoot)
|
|
locationEntry = ttk.Entry(mainFrame, textvariable=self.locationName).grid(row=optionsRow, column=1, columnspan=3, sticky=tk.W+tk.E, padx=5)
|
|
locationBrowse = ttk.Button(mainFrame, text='Browse', command=self.browse).grid(row=3, column=4)
|
|
|
|
optionsRow += 1
|
|
|
|
ttk.Label(mainFrame, text = "Board Type :").grid(row=optionsRow, column=0, padx=4, sticky=tk.E)
|
|
self.boardtype = ttk.Combobox(mainFrame, values=boardtype_list, )
|
|
self.boardtype.grid(row=4, column=1, padx=4, sticky=tk.W+tk.E)
|
|
self.boardtype.set('pico')
|
|
self.boardtype.bind('<<ComboboxSelected>>',self.boardtype_change_callback)
|
|
optionsRow += 1
|
|
|
|
# Features section
|
|
featuresframe = ttk.LabelFrame(mainFrame, text="Library Options", relief=tk.RIDGE, borderwidth=2)
|
|
featuresframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=5, ipadx=5, padx=5, pady=5, sticky=tk.E+tk.W)
|
|
|
|
s = (len(features_list)/3)
|
|
|
|
self.feature_checkbox_vars = []
|
|
row = 0
|
|
col = 0
|
|
for i in features_list:
|
|
var = tk.StringVar(value='') # Off by default for the moment
|
|
c = features_list[i][GUI_TEXT]
|
|
cb = ttk.Checkbutton(featuresframe, text = c, var=var, onvalue=i, offvalue='')
|
|
cb.grid(row=row, column=col, padx=15, pady=2, ipadx=1, ipady=1, sticky=tk.E+tk.W)
|
|
self.feature_checkbox_vars.append(var)
|
|
row+=1
|
|
if row >= s:
|
|
col+=1
|
|
row = 0
|
|
|
|
optionsRow += 5
|
|
|
|
# PicoW options section
|
|
self.picowSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Pico Wireless Options")
|
|
self.picowSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
|
|
self.pico_wireless = tk.StringVar()
|
|
|
|
col = 0
|
|
row = 0
|
|
for i in picow_options_list:
|
|
rb = ttk.Radiobutton(self.picowSubframe, text=picow_options_list[i][GUI_TEXT], variable=self.pico_wireless, val=i)
|
|
rb.grid(row=row, column=col, padx=15, pady=1, sticky=tk.E+tk.W)
|
|
col+=1
|
|
if col == 3:
|
|
col=0
|
|
row+=1
|
|
|
|
# DOnt actually need any settings at the moment.
|
|
# ttk.Button(self.picowSubframe, text='Settings', command=self.wirelessSettings).grid(row=0, column=4, padx=5, pady=2, sticky=tk.E)
|
|
|
|
self.setState(self.picowSubframe, "disabled")
|
|
|
|
optionsRow += 3
|
|
|
|
# output options section
|
|
ooptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Console Options")
|
|
ooptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
|
|
|
|
self.wantUART = tk.IntVar()
|
|
self.wantUART.set(args.uart)
|
|
ttk.Checkbutton(ooptionsSubframe, text="Console over UART", variable=self.wantUART).grid(row=0, column=0, padx=4, sticky=tk.W)
|
|
|
|
self.wantUSB = tk.IntVar()
|
|
self.wantUSB.set(args.usb)
|
|
ttk.Checkbutton(ooptionsSubframe, text="Console over USB (Disables other USB use)", variable=self.wantUSB).grid(row=0, column=1, padx=4, sticky=tk.W)
|
|
|
|
optionsRow += 2
|
|
|
|
# Code options section
|
|
coptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Code Options")
|
|
coptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=3, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
|
|
|
|
self.wantExamples = tk.IntVar()
|
|
self.wantExamples.set(args.examples)
|
|
ttk.Checkbutton(coptionsSubframe, text="Add examples for Pico library", variable=self.wantExamples).grid(row=0, column=0, padx=4, sticky=tk.W)
|
|
|
|
self.wantRunFromRAM = tk.IntVar()
|
|
self.wantRunFromRAM.set(args.runFromRAM)
|
|
ttk.Checkbutton(coptionsSubframe, text="Run from RAM", variable=self.wantRunFromRAM).grid(row=0, column=1, padx=4, sticky=tk.W)
|
|
|
|
self.wantCPP = tk.IntVar()
|
|
self.wantCPP.set(args.cpp)
|
|
ttk.Checkbutton(coptionsSubframe, text="Generate C++", variable=self.wantCPP).grid(row=0, column=3, padx=4, sticky=tk.W)
|
|
|
|
ttk.Button(coptionsSubframe, text="Advanced...", command=self.config).grid(row=0, column=4, sticky=tk.E)
|
|
|
|
self.wantCPPExceptions = tk.IntVar()
|
|
self.wantCPPExceptions.set(args.cppexceptions)
|
|
ttk.Checkbutton(coptionsSubframe, text="Enable C++ exceptions", variable=self.wantCPPExceptions).grid(row=1, column=0, padx=4, sticky=tk.W)
|
|
|
|
self.wantCPPRTTI = tk.IntVar()
|
|
self.wantCPPRTTI.set(args.cpprtti)
|
|
ttk.Checkbutton(coptionsSubframe, text="Enable C++ RTTI", variable=self.wantCPPRTTI).grid(row=1, column=1, padx=4, sticky=tk.W)
|
|
|
|
optionsRow += 3
|
|
|
|
# Build Options section
|
|
|
|
boptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Build Options")
|
|
boptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
|
|
|
|
self.wantBuild = tk.IntVar()
|
|
self.wantBuild.set(args.build)
|
|
ttk.Checkbutton(boptionsSubframe, text="Run build after generation", variable=self.wantBuild).grid(row=0, column=0, padx=4, sticky=tk.W)
|
|
|
|
self.wantOverwrite = tk.IntVar()
|
|
self.wantOverwrite.set(args.overwrite)
|
|
ttk.Checkbutton(boptionsSubframe, text="Overwrite existing projects", variable=self.wantOverwrite).grid(row=0, column=1, padx=4, sticky=tk.W)
|
|
|
|
optionsRow += 2
|
|
|
|
# IDE Options section
|
|
|
|
vscodeoptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="IDE Options")
|
|
vscodeoptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
|
|
|
|
self.wantVSCode = tk.IntVar()
|
|
if args.project is None:
|
|
self.wantVSCode.set(False)
|
|
else:
|
|
self.wantVSCode.set('vscode' in args.project)
|
|
ttk.Checkbutton(vscodeoptionsSubframe, text="Create VSCode project", variable=self.wantVSCode).grid(row=0, column=0, padx=4, sticky=tk.W)
|
|
|
|
ttk.Label(vscodeoptionsSubframe, text = " Debugger:").grid(row=0, column=1, padx=4, sticky=tk.W)
|
|
|
|
self.debugger = ttk.Combobox(vscodeoptionsSubframe, values=debugger_list, state="readonly")
|
|
self.debugger.grid(row=0, column=2, padx=4, sticky=tk.W)
|
|
self.debugger.current(args.debugger)
|
|
|
|
optionsRow += 2
|
|
|
|
# OK, Cancel, Help section
|
|
# creating buttons
|
|
QuitButton = ttk.Button(mainFrame, text="Quit", command=self.quit).grid(row=optionsRow, column=4, stick=tk.E, padx=10, pady=5)
|
|
OKButton = ttk.Button(mainFrame, text="OK", command=self.OK).grid(row=optionsRow, column=3, padx=4, pady=5, sticky=tk.E)
|
|
|
|
# TODO help not implemented yet
|
|
# HelpButton = ttk.Button(mainFrame, text="Help", command=self.help).grid(row=optionsRow, column=0, pady=5)
|
|
|
|
# You can set a default path here, replace the string with whereever you want.
|
|
# self.locationName.set('/home/pi/pico_projects')
|
|
|
|
def GetFeatures(self):
|
|
features = []
|
|
|
|
i = 0
|
|
for cb in self.feature_checkbox_vars:
|
|
s = cb.get()
|
|
if s != '':
|
|
features.append(s)
|
|
|
|
picow_extra = self.pico_wireless.get()
|
|
|
|
if picow_extra != 'picow_none':
|
|
features.append(picow_extra)
|
|
|
|
return features
|
|
|
|
def quit(self):
|
|
# TODO Check if we want to exit here
|
|
sys.exit(ExitCodes.SUCCESS)
|
|
|
|
def OK(self):
|
|
# OK, grab all the settings from the page, then call the generators
|
|
projectPath = self.locationName.get()
|
|
features = self.GetFeatures()
|
|
projects = list()
|
|
if (self.wantVSCode.get()):
|
|
projects.append("vscode")
|
|
|
|
params={
|
|
'sdkPath' : self.sdkpath,
|
|
'projectRoot' : Path(projectPath),
|
|
'projectName' : self.projectName.get(),
|
|
'wantGUI' : True,
|
|
'wantOverwrite' : self.wantOverwrite.get(),
|
|
'wantBuild' : self.wantBuild.get(),
|
|
'boardtype' : self.boardtype.get(),
|
|
'features' : features,
|
|
'projects' : projects,
|
|
'configs' : self.configs,
|
|
'wantRunFromRAM': self.wantRunFromRAM.get(),
|
|
'wantExamples' : self.wantExamples.get(),
|
|
'wantUART' : self.wantUART.get(),
|
|
'wantUSB' : self.wantUSB.get(),
|
|
'wantCPP' : self.wantCPP.get(),
|
|
'debugger' : self.debugger.current(),
|
|
'exceptions' : self.wantCPPExceptions.get(),
|
|
'rtti' : self.wantCPPRTTI.get(),
|
|
'ssid' : self.ssid,
|
|
'password' : self.password,
|
|
}
|
|
|
|
DoEverything(self, params)
|
|
|
|
def browse(self):
|
|
name = fd.askdirectory()
|
|
self.locationName.set(name)
|
|
|
|
def help(self):
|
|
print("Help TODO")
|
|
|
|
def config(self):
|
|
# Run the configuration window
|
|
self.configs = ConfigurationWindow(self, self.configs).get()
|
|
|
|
def CheckPrerequisites():
|
|
global isMac, isWindows
|
|
isMac = (platform.system() == 'Darwin')
|
|
isWindows = (platform.system() == 'Windows')
|
|
|
|
# Do we have a compiler?
|
|
return shutil.which(COMPILER_NAME)
|
|
|
|
|
|
def CheckSDKPath(gui):
|
|
sdkPath = os.getenv('PICO_SDK_PATH')
|
|
|
|
if sdkPath == None:
|
|
m = 'Unable to locate the Raspberry Pi Pico SDK, PICO_SDK_PATH is not set'
|
|
if (gui):
|
|
RunWarning(m)
|
|
else:
|
|
print(m)
|
|
elif not os.path.isdir(sdkPath):
|
|
m = 'Unable to locate the Raspberry Pi Pico SDK, PICO_SDK_PATH does not point to a directory'
|
|
if (gui):
|
|
RunWarning(m)
|
|
else:
|
|
print(m)
|
|
sdkPath = None
|
|
|
|
return sdkPath
|
|
|
|
def GetFilePath(filename):
|
|
if os.path.islink(__file__):
|
|
script_file = os.readlink(__file__)
|
|
else:
|
|
script_file = __file__
|
|
return os.path.join(os.path.dirname(script_file), filename)
|
|
|
|
def ParseCommandLine():
|
|
debugger_flags = ', '.join('{} = {}'.format(i, v) for i, v in enumerate(debugger_list))
|
|
parser = argparse.ArgumentParser(description='Pico Project generator')
|
|
parser.add_argument("name", nargs="?", help="Name of the project")
|
|
parser.add_argument("-t", "--tsv", help="Select an alternative pico_configs.tsv file", default=GetFilePath("pico_configs.tsv"))
|
|
parser.add_argument("-o", "--output", help="Set an alternative CMakeList.txt filename", default="CMakeLists.txt")
|
|
parser.add_argument("-x", "--examples", action='store_true', help="Add example code for the Pico standard library")
|
|
parser.add_argument("-l", "--list", action='store_true', help="List available features")
|
|
parser.add_argument("-c", "--configs", action='store_true', help="List available project configuration items")
|
|
parser.add_argument("-f", "--feature", action='append', help="Add feature to generated project")
|
|
parser.add_argument("-over", "--overwrite", action='store_true', help="Overwrite any existing project AND files")
|
|
parser.add_argument("-b", "--build", action='store_true', help="Build after project created")
|
|
parser.add_argument("-g", "--gui", action='store_true', help="Run a GUI version of the project generator")
|
|
parser.add_argument("-p", "--project", action='append', help="Generate projects files for IDE. Options are: vscode")
|
|
parser.add_argument("-r", "--runFromRAM", action='store_true', help="Run the program from RAM rather than flash")
|
|
parser.add_argument("-uart", "--uart", action='store_true', default=1, help="Console output to UART (default)")
|
|
parser.add_argument("-nouart", "--nouart", action='store_true', default=0, help="Disable console output to UART")
|
|
parser.add_argument("-usb", "--usb", action='store_true', help="Console output to USB (disables other USB functionality")
|
|
parser.add_argument("-cpp", "--cpp", action='store_true', default=0, help="Generate C++ code")
|
|
parser.add_argument("-cpprtti", "--cpprtti", action='store_true', default=0, help="Enable C++ RTTI (Uses more memory)")
|
|
parser.add_argument("-cppex", "--cppexceptions", action='store_true', default=0, help="Enable C++ exceptions (Uses more memory)")
|
|
parser.add_argument("-d", "--debugger", type=int, help="Select debugger ({})".format(debugger_flags), default=0)
|
|
parser.add_argument("-board", "--boardtype", action="store", default='pico', help="Select board type (see --boardlist for available boards)")
|
|
parser.add_argument("-bl", "--boardlist", action="store_true", help="List available board types")
|
|
parser.add_argument("-cp", "--cpath", help="Override default VSCode compiler path")
|
|
parser.add_argument("-root", "--projectRoot", help="Override default project root where the new project will be created")
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def GenerateMain(folder, projectName, features, cpp):
|
|
|
|
if cpp:
|
|
filename = Path(folder) / (projectName + '.cpp')
|
|
else:
|
|
filename = Path(folder) / (projectName + '.c')
|
|
|
|
file = open(filename, 'w')
|
|
|
|
main = ('#include <stdio.h>\n'
|
|
'#include "pico/stdlib.h"\n'
|
|
)
|
|
file.write(main)
|
|
|
|
if (features):
|
|
|
|
# Add any includes
|
|
for feat in features:
|
|
if (feat in features_list):
|
|
o = f'#include "{features_list[feat][H_FILE]}"\n'
|
|
file.write(o)
|
|
if (feat in stdlib_examples_list):
|
|
o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n'
|
|
file.write(o)
|
|
if (feat in picow_options_list):
|
|
o = f'#include "{picow_options_list[feat][H_FILE]}"\n'
|
|
file.write(o)
|
|
|
|
file.write('\n')
|
|
|
|
# Add any defines
|
|
for feat in features:
|
|
if (feat in code_fragments_per_feature):
|
|
for s in code_fragments_per_feature[feat][DEFINES]:
|
|
file.write(s)
|
|
file.write('\n')
|
|
file.write('\n')
|
|
|
|
main = ('\n\n'
|
|
'int main()\n'
|
|
'{\n'
|
|
' stdio_init_all();\n\n'
|
|
)
|
|
|
|
if (features):
|
|
# 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)
|
|
main += s
|
|
main += '\n'
|
|
main += '\n'
|
|
|
|
main += (' puts("Hello, world!");\n\n'
|
|
' return 0;\n'
|
|
'}\n'
|
|
)
|
|
|
|
file.write(main)
|
|
|
|
file.close()
|
|
|
|
|
|
def GenerateCMake(folder, params):
|
|
|
|
filename = Path(folder) / CMAKELIST_FILENAME
|
|
projectName = params['projectName']
|
|
board_type = params['boardtype']
|
|
|
|
# OK, for the path, CMake will accept forward slashes on Windows, and thats
|
|
# seemingly a bit easier to handle than the backslashes
|
|
p = str(params['sdkPath']).replace('\\','/')
|
|
sdk_path = f'"{p}"'
|
|
|
|
cmake_header1 = (f"# Generated Cmake Pico project file\n\n"
|
|
"cmake_minimum_required(VERSION 3.13)\n\n"
|
|
"set(CMAKE_C_STANDARD 11)\n"
|
|
"set(CMAKE_CXX_STANDARD 17)\n\n"
|
|
"# Initialise pico_sdk from installed location\n"
|
|
"# (note this can come from environment, CMake cache etc)\n"
|
|
f"set(PICO_SDK_PATH {sdk_path})\n\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"
|
|
"if (PICO_SDK_VERSION_STRING VERSION_LESS \"1.4.0\")\n"
|
|
" message(FATAL_ERROR \"Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}\")\n"
|
|
"endif()\n\n"
|
|
f"project({projectName} C CXX ASM)\n"
|
|
)
|
|
|
|
cmake_header3 = (
|
|
"\n# Initialise the Raspberry Pi Pico SDK\n"
|
|
"pico_sdk_init()\n\n"
|
|
"# Add executable. Default name is the project name, version 0.1\n\n"
|
|
)
|
|
|
|
|
|
file = open(filename, 'w')
|
|
|
|
file.write(cmake_header1)
|
|
|
|
if params['exceptions']:
|
|
file.write("\nset(PICO_CXX_ENABLE_EXCEPTIONS 1)\n")
|
|
|
|
if params['rtti']:
|
|
file.write("\nset(PICO_CXX_ENABLE_RTTI 1)\n")
|
|
|
|
file.write(cmake_header3)
|
|
|
|
# add the preprocessor defines for overall configuration
|
|
if params['configs']:
|
|
file.write('# Add any PICO_CONFIG entries specified in the Advanced settings\n')
|
|
for c, v in params['configs'].items():
|
|
if v == "True":
|
|
v = "1"
|
|
elif v == "False":
|
|
v = "0"
|
|
file.write(f'add_compile_definitions({c} = {v})\n')
|
|
file.write('\n')
|
|
|
|
# No GUI/command line to set a different executable name at this stage
|
|
executableName = projectName
|
|
|
|
if params['wantCPP']:
|
|
file.write(f'add_executable({projectName} {projectName}.cpp )\n\n')
|
|
else:
|
|
file.write(f'add_executable({projectName} {projectName}.c )\n\n')
|
|
|
|
file.write(f'pico_set_program_name({projectName} "{executableName}")\n')
|
|
file.write(f'pico_set_program_version({projectName} "0.1")\n\n')
|
|
|
|
if params['wantRunFromRAM']:
|
|
file.write(f'# no_flash means the target is to run from RAM\n')
|
|
file.write(f'pico_set_binary_type({projectName} no_flash)\n\n')
|
|
|
|
# Console output destinations
|
|
if params['wantUART']:
|
|
file.write(f'pico_enable_stdio_uart({projectName} 1)\n')
|
|
else:
|
|
file.write(f'pico_enable_stdio_uart({projectName} 0)\n')
|
|
|
|
if params['wantUSB']:
|
|
file.write(f'pico_enable_stdio_usb({projectName} 1)\n\n')
|
|
else:
|
|
file.write(f'pico_enable_stdio_usb({projectName} 0)\n\n')
|
|
|
|
# If we need wireless, check for SSID and password
|
|
# removed for the moment as these settings are currently only needed for the pico-examples
|
|
# but may be required in here at a later date.
|
|
if False:
|
|
if 'ssid' in params or 'password' in params:
|
|
file.write('# Add any wireless access point information\n')
|
|
file.write(f'target_compile_definitions({projectName} PRIVATE\n')
|
|
if 'ssid' in params:
|
|
file.write(f'WIFI_SSID=\" {params["ssid"]} \"\n')
|
|
else:
|
|
file.write(f'WIFI_SSID=\"${WIFI_SSID}\"')
|
|
|
|
if 'password' in params:
|
|
file.write(f'WIFI_PASSWORD=\"{params["password"]}\"\n')
|
|
else:
|
|
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)
|
|
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}/.. # for our common lwipopts or any other standard includes, if required\n")
|
|
file.write(')\n\n')
|
|
|
|
# Selected libraries/features
|
|
if (params['features']):
|
|
file.write('# Add any user requested libraries\n')
|
|
file.write(f'target_link_libraries({projectName} \n')
|
|
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')
|
|
|
|
file.write(f'pico_add_extra_outputs({projectName})\n\n')
|
|
|
|
file.close()
|
|
|
|
|
|
# Generates the requested project files, if any
|
|
def generateProjectFiles(projectPath, projectName, sdkPath, projects, debugger):
|
|
|
|
oldCWD = os.getcwd()
|
|
|
|
os.chdir(projectPath)
|
|
|
|
deb = debugger_config_list[debugger]
|
|
server_args = debug_server_args_list[debugger]
|
|
|
|
for p in projects :
|
|
if p == 'vscode':
|
|
v1 = ('{\n'
|
|
' // Use IntelliSense to learn about possible attributes.\n'
|
|
' // Hover to view descriptions of existing attributes.\n'
|
|
' // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n'
|
|
' "version": "0.2.0",\n'
|
|
' "configurations": [\n'
|
|
' {\n'
|
|
' "name": "Cortex Debug",\n'
|
|
' "cwd": "${workspaceRoot}",\n'
|
|
' "executable": "${command:cmake.launchTargetPath}",\n'
|
|
' "request": "launch",\n'
|
|
' "type": "cortex-debug",\n'
|
|
' "servertype": "openocd",\n'
|
|
' "gdbPath": "gdb-multiarch",\n'
|
|
' "serverArgs": [\n'
|
|
f' {server_args}\n'
|
|
' ],\n'
|
|
' "device": "RP2040",\n'
|
|
' "configFiles": [\n' + \
|
|
f' "interface/{deb}",\n' + \
|
|
' "target/rp2040.cfg"\n' + \
|
|
' ],\n' + \
|
|
' "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd",\n'
|
|
' "runToEntryPoint": "main",\n'
|
|
' // Give restart the same functionality as runToEntryPoint - main\n'
|
|
' "postRestartCommands": [\n'
|
|
' "break main",\n'
|
|
' "continue"\n'
|
|
' ]\n'
|
|
' }\n'
|
|
' ]\n'
|
|
'}\n')
|
|
|
|
c1 = ('{\n'
|
|
' "configurations": [\n'
|
|
' {\n'
|
|
' "name": "Linux",\n'
|
|
' "includePath": [\n'
|
|
' "${workspaceFolder}/**",\n'
|
|
' "${env:PICO_SDK_PATH}/**"\n'
|
|
' ],\n'
|
|
' "defines": [],\n'
|
|
f' "compilerPath": "{compilerPath}",\n'
|
|
' "cStandard": "gnu17",\n'
|
|
' "cppStandard": "gnu++14",\n'
|
|
' "intelliSenseMode": "linux-gcc-arm",\n'
|
|
' "configurationProvider" : "ms-vscode.cmake-tools"\n'
|
|
' }\n'
|
|
' ],\n'
|
|
' "version": 4\n'
|
|
'}\n')
|
|
|
|
s1 = ( '{\n'
|
|
' "cmake.configureOnOpen": false,\n'
|
|
' "cmake.statusbar.advanced": {\n'
|
|
' "debug" : {\n'
|
|
' "visibility": "hidden"\n'
|
|
' },\n'
|
|
' "launch" : {\n'
|
|
' "visibility": "hidden"\n'
|
|
' },\n'
|
|
' "build" : {\n'
|
|
' "visibility": "hidden"\n'
|
|
' },\n'
|
|
' "buildTarget" : {\n'
|
|
' "visibility": "hidden"\n'
|
|
' },\n'
|
|
' },\n'
|
|
'}\n')
|
|
|
|
e1 = ( '{\n'
|
|
' "recommendations": [\n'
|
|
' "marus25.cortex-debug",\n'
|
|
' "ms-vscode.cmake-tools",\n'
|
|
' "ms-vscode.cpptools"\n'
|
|
' ]\n'
|
|
'}\n')
|
|
|
|
# Create a build folder, and run our cmake project build from it
|
|
if not os.path.exists(VSCODE_FOLDER):
|
|
os.mkdir(VSCODE_FOLDER)
|
|
|
|
os.chdir(VSCODE_FOLDER)
|
|
|
|
filename = VSCODE_LAUNCH_FILENAME
|
|
file = open(filename, 'w')
|
|
file.write(v1)
|
|
file.close()
|
|
|
|
file = open(VSCODE_C_PROPERTIES_FILENAME, 'w')
|
|
file.write(c1)
|
|
file.close()
|
|
|
|
file = open(VSCODE_SETTINGS_FILENAME, 'w')
|
|
file.write(s1)
|
|
file.close()
|
|
|
|
file = open(VSCODE_EXTENSIONS_FILENAME, 'w')
|
|
file.write(e1)
|
|
file.close()
|
|
|
|
else :
|
|
print('Unknown project type requested')
|
|
|
|
os.chdir(oldCWD)
|
|
|
|
|
|
def LoadConfigurations():
|
|
try:
|
|
with open(args.tsv) as tsvfile:
|
|
reader = csv.DictReader(tsvfile, dialect='excel-tab')
|
|
for row in reader:
|
|
configuration_dictionary.append(row)
|
|
except:
|
|
print("No Pico configurations file found. Continuing without")
|
|
|
|
def LoadBoardTypes(sdkPath):
|
|
# Scan the boards folder for all header files, extract filenames, and make a list of the results
|
|
# default folder is <PICO_SDK_PATH>/src/boards/include/boards/*
|
|
# If the PICO_BOARD_HEADER_DIRS environment variable is set, use that as well
|
|
|
|
loc = sdkPath / "src/boards/include/boards"
|
|
boards=[]
|
|
for x in Path(loc).iterdir():
|
|
if x.suffix == '.h':
|
|
boards.append(x.stem)
|
|
|
|
loc = os.getenv('PICO_BOARD_HEADER_DIRS')
|
|
|
|
if loc != None:
|
|
for x in Path(loc).iterdir():
|
|
if x.suffix == '.h':
|
|
boards.append(x.stem)
|
|
|
|
return boards
|
|
|
|
def DoEverything(parent, params):
|
|
|
|
if not os.path.exists(params['projectRoot']):
|
|
if params['wantGUI']:
|
|
mb.showerror('Raspberry Pi Pico Project Generator', 'Invalid project path. Select a valid path and try again')
|
|
return
|
|
else:
|
|
print('Invalid project path')
|
|
sys.exit(ExitCodes.INVALID_PROJECT_PATH)
|
|
|
|
oldCWD = os.getcwd()
|
|
os.chdir(params['projectRoot'])
|
|
|
|
# Create our project folder as subfolder
|
|
os.makedirs(params['projectName'], exist_ok=True)
|
|
|
|
os.chdir(params['projectName'])
|
|
|
|
projectPath = params['projectRoot'] / params['projectName']
|
|
|
|
# First check if there is already a project in the folder
|
|
# If there is we abort unless the overwrite flag it set
|
|
if os.path.exists(CMAKELIST_FILENAME):
|
|
if not params['wantOverwrite'] :
|
|
if params['wantGUI']:
|
|
# We can ask the user if they want to overwrite
|
|
y = mb.askquestion('Raspberry Pi Pico Project Generator', 'There already appears to be a project in this folder. \nPress Yes to overwrite project files, or Cancel to chose another folder')
|
|
if y != 'yes':
|
|
return
|
|
else:
|
|
print('There already appears to be a project in this folder. Use the --overwrite option to overwrite the existing project')
|
|
sys.exit(ExitCodes.EXISTING_PROJECT_NO_OVERWRITE)
|
|
|
|
# We should really confirm the user wants to overwrite
|
|
#print('Are you sure you want to overwrite the existing project files? (y/N)')
|
|
#c = input().split(" ")[0]
|
|
#if c != 'y' and c != 'Y' :
|
|
# sys.exit(ExitCodes.SUCCESS)
|
|
|
|
# Copy the SDK finder cmake file to our project folder
|
|
# Can be found here <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
|
shutil.copyfile(params['sdkPath'] / 'external' / 'pico_sdk_import.cmake', projectPath / 'pico_sdk_import.cmake' )
|
|
|
|
if params['features']:
|
|
features_and_examples = params['features'][:]
|
|
else:
|
|
features_and_examples= []
|
|
|
|
if params['wantExamples']:
|
|
features_and_examples = list(stdlib_examples_list.keys()) + features_and_examples
|
|
|
|
GenerateMain('.', params['projectName'], features_and_examples, params['wantCPP'])
|
|
|
|
GenerateCMake('.', params)
|
|
|
|
# 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
|
|
for feat in features_and_examples:
|
|
if feat in picow_options_list:
|
|
if picow_options_list[feat][ANCILLARY_FILE] != "":
|
|
shutil.copy(sourcefolder + "/" + picow_options_list[feat][ANCILLARY_FILE], projectPath / picow_options_list[feat][ANCILLARY_FILE])
|
|
|
|
# Create a build folder, and run our cmake project build from it
|
|
if not os.path.exists('build'):
|
|
os.mkdir('build')
|
|
|
|
os.chdir('build')
|
|
|
|
# If we are overwriting a previous project, we should probably clear the folder, but that might delete something the users thinks is important, so
|
|
# for the moment, just delete the CMakeCache.txt file as certain changes may need that to be recreated.
|
|
|
|
if os.path.exists(CMAKECACHE_FILENAME):
|
|
os.remove(CMAKECACHE_FILENAME)
|
|
|
|
cpus = os.cpu_count()
|
|
if cpus == None:
|
|
cpus = 1
|
|
|
|
if isWindows:
|
|
# Had a special case report, when using MinGW, need to check if using nmake or mingw32-make.
|
|
if shutil.which("mingw32-make"):
|
|
# Assume MinGW environment
|
|
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles" ..'
|
|
makeCmd = 'mingw32-make '
|
|
|
|
else:
|
|
# Everything else assume nmake
|
|
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug -G "NMake Makefiles" ..'
|
|
makeCmd = 'nmake '
|
|
else:
|
|
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug ..'
|
|
makeCmd = 'make -j' + str(cpus)
|
|
|
|
if params['wantGUI']:
|
|
RunCommandInWindow(parent, cmakeCmd)
|
|
else:
|
|
os.system(cmakeCmd)
|
|
|
|
if params['projects']:
|
|
generateProjectFiles(projectPath, params['projectName'], params['sdkPath'], params['projects'], params['debugger'])
|
|
|
|
if params['wantBuild']:
|
|
if params['wantGUI']:
|
|
RunCommandInWindow(parent, makeCmd)
|
|
else:
|
|
os.system(makeCmd)
|
|
print('\nIf the application has built correctly, you can now transfer it to the Raspberry Pi Pico board')
|
|
|
|
os.chdir(oldCWD)
|
|
|
|
|
|
###################################################################################
|
|
# main execution starteth here
|
|
|
|
sourcefolder = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
args = ParseCommandLine()
|
|
|
|
if args.nouart:
|
|
args.uart = False
|
|
|
|
if args.debugger > len(debugger_list) - 1:
|
|
args.debugger = 0
|
|
|
|
# Check we have everything we need to compile etc
|
|
c = CheckPrerequisites()
|
|
|
|
## TODO Do both warnings in the same error message so user does have to keep coming back to find still more to do
|
|
|
|
if c == None:
|
|
m = f'Unable to find the `{COMPILER_NAME}` compiler\n'
|
|
m +='You will need to install an appropriate compiler to build a Raspberry Pi Pico project\n'
|
|
m += 'See the Raspberry Pi Pico documentation for how to do this on your particular platform\n'
|
|
|
|
if (args.gui):
|
|
RunWarning(m)
|
|
else:
|
|
print(m)
|
|
sys.exit(ExitCodes.NO_COMPILER_FOUND)
|
|
|
|
if args.name == None and not args.gui and not args.list and not args.configs and not args.boardlist:
|
|
print("No project name specfied\n")
|
|
sys.exit(ExitCodes.NO_PROJECT_NAME)
|
|
|
|
# Check if we were provided a compiler path, and override the default if so
|
|
if args.cpath:
|
|
compilerPath = Path(args.cpath)
|
|
else:
|
|
compilerPath = Path(c)
|
|
|
|
# load/parse any configuration dictionary we may have
|
|
LoadConfigurations()
|
|
|
|
p = CheckSDKPath(args.gui)
|
|
|
|
if p == None:
|
|
sys.exit(ExitCodes.PICO_SDK_NOT_FOUND)
|
|
|
|
sdkPath = Path(p)
|
|
|
|
boardtype_list = LoadBoardTypes(sdkPath)
|
|
boardtype_list.sort()
|
|
|
|
if args.gui:
|
|
RunGUI(sdkPath, args) # does not return, only exits
|
|
|
|
projectRoot = Path(os.getcwd()) if not args.projectRoot else Path(args.projectRoot)
|
|
|
|
if args.list or args.configs or args.boardlist:
|
|
if args.list:
|
|
print("Available project features:\n")
|
|
for feat in features_list:
|
|
print(feat.ljust(6), '\t', features_list[feat][GUI_TEXT])
|
|
print('\n')
|
|
|
|
if args.configs:
|
|
print("Available project configuration items:\n")
|
|
for conf in configuration_dictionary:
|
|
print(conf['name'].ljust(40), '\t', conf['description'])
|
|
print('\n')
|
|
|
|
if args.boardlist:
|
|
print("Available board types:\n")
|
|
for board in boardtype_list:
|
|
print(board)
|
|
print('\n')
|
|
|
|
sys.exit(ExitCodes.SUCCESS)
|
|
else :
|
|
params={
|
|
'sdkPath' : sdkPath,
|
|
'projectRoot' : projectRoot,
|
|
'projectName' : args.name,
|
|
'wantGUI' : False,
|
|
'wantOverwrite' : args.overwrite,
|
|
'boardtype' : args.boardtype,
|
|
'wantBuild' : args.build,
|
|
'features' : args.feature,
|
|
'projects' : args.project,
|
|
'configs' : (),
|
|
'wantRunFromRAM': args.runFromRAM,
|
|
'wantExamples' : args.examples,
|
|
'wantUART' : args.uart,
|
|
'wantUSB' : args.usb,
|
|
'wantCPP' : args.cpp,
|
|
'debugger' : args.debugger,
|
|
'exceptions' : args.cppexceptions,
|
|
'rtti' : args.cpprtti,
|
|
'ssid' : '',
|
|
'password' : '',
|
|
}
|
|
|
|
DoEverything(None, params)
|