4 # Copyright (c) 2020-2023 Raspberry Pi (Trading) Ltd.
6 # SPDX-License-Identifier: BSD-3-Clause
12 from pyexpat import features
14 from pathlib import Path
26 from tkinter import messagebox as mb
27 from tkinter import filedialog as fd
28 from tkinter import simpledialog as sd
29 from tkinter import ttk
31 CMAKELIST_FILENAME = 'CMakeLists.txt'
32 CMAKECACHE_FILENAME = 'CMakeCache.txt'
34 COMPILER_NAME = 'arm-none-eabi-gcc'
36 VSCODE_LAUNCH_FILENAME = 'launch.json'
37 VSCODE_C_PROPERTIES_FILENAME = 'c_cpp_properties.json'
38 VSCODE_SETTINGS_FILENAME ='settings.json'
39 VSCODE_EXTENSIONS_FILENAME ='extensions.json'
40 VSCODE_TASKS_FILENAME ='tasks.json'
41 VSCODE_FOLDER='.vscode'
43 CONFIG_UNSET="Not set"
45 # Standard libraries for all builds
46 # And any more to string below, space separator
47 STANDARD_LIBRARIES = 'pico_stdlib'
49 # Indexed on feature name, tuple contains the C file, the H file and the CMake project name for the feature.
50 # Some lists may contain an extra/ancillary file needed for that feature
58 'spi' : ("SPI", "spi.c", "hardware/spi.h", "hardware_spi"),
59 'i2c' : ("I2C interface", "i2c.c", "hardware/i2c.h", "hardware_i2c"),
60 'dma' : ("DMA support", "dma.c", "hardware/dma.h", "hardware_dma"),
61 'pio' : ("PIO interface", "pio.c", "hardware/pio.h", "hardware_pio"),
62 'interp' : ("HW interpolation", "interp.c", "hardware/interp.h", "hardware_interp"),
63 'timer' : ("HW timer", "timer.c", "hardware/timer.h", "hardware_timer"),
64 'watchdog' : ("HW watchdog", "watch.c", "hardware/watchdog.h", "hardware_watchdog"),
65 'clocks' : ("HW clocks", "clocks.c", "hardware/clocks.h", "hardware_clocks"),
68 picow_options_list = {
69 'picow_none' : ("None", "", "", "", ""),
70 'picow_led' : ("PicoW onboard LED", "", "pico/cyw43_arch.h", "pico_cyw43_arch_none", ""),
71 'picow_poll' : ("Polled lwIP", "", "pico/cyw43_arch.h", "pico_cyw43_arch_lwip_poll", "lwipopts.h"),
72 'picow_background' :("Background lwIP", "", "pico/cyw43_arch.h", "pico_cyw43_arch_lwip_threadsafe_background", "lwipopts.h"),
73 # 'picow_freertos' : ("Full lwIP (FreeRTOS)", "", "pico/cyw43_arch.h", "pico_cyw43_arch_lwip_sys_freertos", "lwipopts.h"),
76 stdlib_examples_list = {
77 'uart': ("UART", "uart.c", "hardware/uart.h", "hardware_uart"),
78 'gpio' : ("GPIO interface", "gpio.c", "hardware/gpio.h", "hardware_gpio"),
79 'div' : ("Low level HW Divider", "divider.c", "hardware/divider.h", "hardware_divider")
82 debugger_list = ["DebugProbe (CMSIS-DAP)", "SWD (Pi host)"]
83 debugger_config_list = ["cmsis-dap.cfg", "raspberrypi-swd.cfg"]
87 # Could add an extra item that shows how to use some of the available functions for the feature
90 # This also contains example code for the standard library (see stdlib_examples_list)
91 code_fragments_per_feature = {
94 "// By default the stdout UART is `uart0`, so we will use the second one",
95 "#define UART_ID uart1",
96 "#define BAUD_RATE 9600", "",
97 "// Use pins 4 and 5 for UART1",
98 "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments",
99 "#define UART_TX_PIN 4",
100 "#define UART_RX_PIN 5" ),
102 ( "// Set up our UART",
103 "uart_init(UART_ID, BAUD_RATE);",
104 "// Set the TX and RX pins by using the function select on the GPIO",
105 "// Set datasheet for more information on function select",
106 "gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);",
107 "gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);", "" )
111 "// We are going to use SPI 0, and allocate it to the following GPIO pins",
112 "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments",
113 "#define SPI_PORT spi0",
114 "#define PIN_MISO 16",
116 "#define PIN_SCK 18",
117 "#define PIN_MOSI 19" ),
119 ( "// SPI initialisation. This example will use SPI at 1MHz.",
120 "spi_init(SPI_PORT, 1000*1000);",
121 "gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);",
122 "gpio_set_function(PIN_CS, GPIO_FUNC_SIO);",
123 "gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);",
124 "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);", "",
125 "// Chip select is active-low, so we'll initialise it to a driven-high state",
126 "gpio_set_dir(PIN_CS, GPIO_OUT);",
127 "gpio_put(PIN_CS, 1);", "")
132 "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.",
133 "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments",
134 "#define I2C_PORT i2c0",
139 "// I2C Initialisation. Using it at 400Khz.",
140 "i2c_init(I2C_PORT, 400*1000);","",
141 "gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);",
142 "gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);",
143 "gpio_pull_up(I2C_SDA);",
144 "gpio_pull_up(I2C_SCL);"
150 "// Example uses GPIO 2",
154 "// GPIO initialisation.",
155 "// We will make this GPIO an input, and pull it up by default",
157 "gpio_set_dir(GPIO, GPIO_IN);",
158 "gpio_pull_up(GPIO);","",
164 "// Interpolator example code",
165 "interp_config cfg = interp_default_config();",
166 "// Now use the various interpolator library functions for your use case",
167 "// e.g. interp_config_clamp(&cfg, true);",
168 "// interp_config_shift(&cfg, 2);",
169 "// Then set the config ",
170 "interp_set_config(interp0, 0, &cfg);",
176 "int64_t alarm_callback(alarm_id_t id, void *user_data) {",
177 " // Put your timeout handler code in here",
182 "// Timer example code - This example fires off the callback after 2000ms",
183 "add_alarm_in_ms(2000, alarm_callback, NULL, false);"
189 "// Watchdog example code",
190 "if (watchdog_caused_reboot()) {",
191 " // Whatever action you may take if a watchdog caused a reboot",
193 "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot",
194 "// second arg is pause on debug which means the watchdog will pause when stepping through code",
195 "watchdog_enable(100, 1);","",
196 "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot"
197 "watchdog_update();",
203 "// Example of using the HW divider. The pico_divider library provides a more user friendly set of APIs ",
204 "// over the divider (and support for 64 bit divides), and of course by default regular C language integer",
205 "// divisions are redirected thru that library, meaning you can just use C level `/` and `%` operators and",
206 "// gain the benefits of the fast hardware divider.",
207 "int32_t dividend = 123456;",
208 "int32_t divisor = -321;",
209 "// This is the recommended signed fast divider for general use.",
210 "divmod_result_t result = hw_divider_divmod_s32(dividend, divisor);",
211 "printf(\"%d/%d = %d remainder %d\\n\", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result));",
212 "// This is the recommended unsigned fast divider for general use.",
213 "int32_t udividend = 123456;",
214 "int32_t udivisor = 321;",
215 "divmod_result_t uresult = hw_divider_divmod_u32(udividend, udivisor);",
216 "printf(\"%d/%d = %d remainder %d\\n\", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult));"
221 configuration_dictionary = list(dict())
225 compilerPath = Path("/usr/bin/arm-none-eabi-gcc")
227 def relativeSDKPath(sdkVersion):
228 return f"/.pico-sdk/sdk/{sdkVersion}"
230 def relativeToolchainPath(toolchainVersion):
231 return f"/.pico-sdk/toolchain/{toolchainVersion}"
233 def cmakeSdkPath(sdkVersion):
234 return f"${{USERHOME}}{relativeSDKPath(sdkVersion)}"
236 def cmakeToolchainPath(toolchainVersion):
237 return f"${{USERHOME}}{relativeToolchainPath(toolchainVersion)}"
239 def propertiesSdkPath(sdkVersion):
241 return f"${{env:USERPROFILE}}{relativeSDKPath(sdkVersion)}"
243 return f"${{env:HOME}}{relativeSDKPath(sdkVersion)}"
245 def codeSdkPath(sdkVersion):
246 return f"${{userHome}}{relativeSDKPath(sdkVersion)}"
248 def propertiesToolchainPath(toolchainVersion):
250 return f"${{env:USERPROFILE}}{relativeToolchainPath(toolchainVersion)}"
252 return f"${{env:HOME}}{relativeToolchainPath(toolchainVersion)}"
254 def codeToolchainPath(toolchainVersion):
255 return f"${{userHome}}{relativeToolchainPath(toolchainVersion)}"
260 def GetButtonBackground():
266 def GetButtonTextColour():
270 def RunGUI(sdkpath, args):
272 style = ttk.Style(root)
273 style.theme_use('default')
275 style.configure("TButton", padding=6, relief="groove", border=2, foreground=GetButtonTextColour(), background=GetButtonBackground())
276 style.configure("TLabel", foreground=GetTextColour(), background=GetBackground() )
277 style.configure("TCheckbutton", foreground=GetTextColour(), background=GetBackground())
278 style.configure("TRadiobutton", foreground=GetTextColour(), background=GetBackground() )
279 style.configure("TLabelframe", foreground=GetTextColour(), background=GetBackground() )
280 style.configure("TLabelframe.Label", foreground=GetTextColour(), background=GetBackground() )
281 style.configure("TCombobox", foreground=GetTextColour(), background=GetBackground() )
282 style.configure("TListbox", foreground=GetTextColour(), background=GetBackground() )
284 style.map("TCheckbutton", background = [('disabled', GetBackground())])
285 style.map("TRadiobutton", background = [('disabled', GetBackground())])
286 style.map("TButton", background = [('disabled', GetBackground())])
287 style.map("TLabel", background = [('background', GetBackground())])
288 style.map("TComboBox", background = [('readonly', GetBackground())])
290 app = ProjectWindow(root, sdkpath, args)
292 app.configure(background=GetBackground())
298 def RunWarning(message):
299 mb.showwarning('Raspberry Pi Pico Project Generator', message)
305 def thread_function(text, command, ok):
306 l = shlex.split(command)
307 proc = subprocess.Popen(l, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
308 for line in iter(proc.stdout.readline,''):
311 ok["state"] = tk.NORMAL
313 text.insert(tk.END, line)
316 # Function to run an OS command and display the output in a new modal window
318 class DisplayWindow(tk.Toplevel):
319 def __init__(self, parent, title):
320 tk.Toplevel.__init__(self, parent)
322 self.init_window(title)
324 def init_window(self, title):
327 frame = tk.Frame(self, borderwidth=5, relief=tk.RIDGE)
328 frame.pack(fill=tk.X, expand=True, side=tk.TOP)
330 scrollbar = tk.Scrollbar(frame)
331 self.text = tk.Text(frame, bg='gray14', fg='gray99')
332 scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
333 self.text.pack(side=tk.LEFT, fill=tk.Y)
334 scrollbar.config(command=self.text.yview)
335 self.text.config(yscrollcommand=scrollbar.set)
337 frame1 = tk.Frame(self, borderwidth=1)
338 frame1.pack(fill=tk.X, expand=True, side=tk.BOTTOM)
339 self.OKButton = ttk.Button(frame1, text="OK", command=self.OK)
340 self.OKButton["state"] = tk.DISABLED
344 self.transient(self.parent)
351 def RunCommandInWindow(parent, command):
352 w = DisplayWindow(parent, command)
353 x = threading.Thread(target=thread_function, args=(w.text, command, w.OKButton))
355 parent.wait_window(w)
357 class EditBoolWindow(sd.Dialog):
359 def __init__(self, parent, configitem, current):
361 self.config_item = configitem
362 self.current = current
363 sd.Dialog.__init__(self, parent, "Edit boolean configuration")
366 def body(self, master):
367 self.configure(background=GetBackground())
368 ttk.Label(self, text=self.config_item['name']).pack()
369 self.result = tk.StringVar()
370 self.result.set(self.current)
371 ttk.Radiobutton(master, text="True", variable=self.result, value="True").pack(anchor=tk.W)
372 ttk.Radiobutton(master, text="False", variable=self.result, value="False").pack(anchor=tk.W)
373 ttk.Radiobutton(master, text=CONFIG_UNSET, variable=self.result, value=CONFIG_UNSET).pack(anchor=tk.W)
376 return self.result.get()
378 class EditIntWindow(sd.Dialog):
380 def __init__(self, parent, configitem, current):
382 self.config_item = configitem
383 self.current = current
384 sd.Dialog.__init__(self, parent, "Edit integer configuration")
386 def body(self, master):
387 self.configure(background=GetBackground())
388 str = self.config_item['name'] + " Max = " + self.config_item['max'] + " Min = " + self.config_item['min']
389 ttk.Label(self, text=str).pack()
390 self.input = tk.Entry(self)
391 self.input.pack(pady=4)
392 self.input.insert(0, self.current)
393 ttk.Button(self, text=CONFIG_UNSET, command=self.unset).pack(pady=5)
396 self.result = self.input.get()
397 # Check for numeric entry
401 self.result = CONFIG_UNSET
407 class EditEnumWindow(sd.Dialog):
408 def __init__(self, parent, configitem, current):
410 self.config_item = configitem
411 self.current = current
412 sd.Dialog.__init__(self, parent, "Edit Enumeration configuration")
414 def body(self, master):
415 #self.configure(background=GetBackground())
416 values = self.config_item['enumvalues'].split('|')
417 values.insert(0,'Not set')
418 self.input = ttk.Combobox(self, values=values, state='readonly')
419 self.input.set(self.current)
420 self.input.pack(pady=12)
423 self.result = self.input.get()
430 class ConfigurationWindow(tk.Toplevel):
432 def __init__(self, parent, initial_config):
433 tk.Toplevel.__init__(self, parent)
435 self.results = initial_config
436 self.init_window(self)
438 def init_window(self, args):
439 self.configure(background=GetBackground())
440 self.title("Advanced Configuration")
441 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)
442 ttk.Label(self, text="Name").grid(row=1, column=0, sticky=tk.W)
443 ttk.Label(self, text="Type").grid(row=1, column=1, sticky=tk.W)
444 ttk.Label(self, text="Min").grid(row=1, column=2, sticky=tk.W)
445 ttk.Label(self, text="Max").grid(row=1, column=3, sticky=tk.W)
446 ttk.Label(self, text="Default").grid(row=1, column=4, sticky=tk.W)
447 ttk.Label(self, text="User").grid(row=1, column=5, sticky=tk.W)
449 okButton = ttk.Button(self, text="OK", command=self.ok)
450 cancelButton = ttk.Button(self, text="Cancel", command=self.cancel)
452 self.namelist = tk.Listbox(self, selectmode=tk.SINGLE)
453 self.typelist = tk.Listbox(self, selectmode=tk.SINGLE)
454 self.minlist = tk.Listbox(self, selectmode=tk.SINGLE)
455 self.maxlist = tk.Listbox(self, selectmode=tk.SINGLE)
456 self.defaultlist = tk.Listbox(self, selectmode=tk.SINGLE)
457 self.valuelist = tk.Listbox(self, selectmode=tk.SINGLE)
459 self.descriptionText = tk.Text(self, state=tk.DISABLED, height=2)
461 ## Make a list of our list boxes to make it all easier to handle
462 self.listlist = [self.namelist, self.typelist, self.minlist, self.maxlist, self.defaultlist, self.valuelist]
464 scroll = tk.Scrollbar(self, orient=tk.VERTICAL, command=self.yview)
466 for box in self.listlist:
468 box.config(yscrollcommand=scroll.set)
469 box.bind("<MouseWheel>", self.mousewheel)
470 box.bind("<Button-4>", self.mousewheel)
471 box.bind("<Button-5>", self.mousewheel)
472 box.bind("<<ListboxSelect>>", self.changeSelection)
473 box.bind("<Double-Button>", self.doubleClick)
474 box.config(exportselection=False)
475 box.bind("<Down>", self.OnEntryUpDown)
476 box.bind("<Up>", self.OnEntryUpDown)
478 scroll.grid(column=7, sticky=tk.N + tk.S)
481 for box in self.listlist:
482 box.grid(row=2, column=i, padx=0, sticky=tk.W + tk.E)
485 self.descriptionText.grid(row = 3, column=0, columnspan=4, sticky=tk.W + tk.E)
486 cancelButton.grid(column=5, row = 3, padx=5)
487 okButton.grid(column=4, row = 3, sticky=tk.E, padx=5)
489 # populate the list box with our config options
490 for conf in configuration_dictionary:
491 self.namelist.insert(tk.END, conf['name'])
495 self.typelist.insert(tk.END, s)
496 self.maxlist.insert(tk.END, conf['max'])
497 self.minlist.insert(tk.END, conf['min'])
498 self.defaultlist.insert(tk.END, conf['default'])
500 # see if this config has a setting, our results member has this predefined from init
501 val = self.results.get(conf['name'], CONFIG_UNSET)
502 self.valuelist.insert(tk.END, val)
503 if val != CONFIG_UNSET:
504 self.valuelist.itemconfig(self.valuelist.size() - 1, {'bg':'green'})
506 def yview(self, *args):
507 for box in self.listlist:
510 def mousewheel(self, event):
511 if (event.num == 4): # Linux encodes wheel as 'buttons' 4 and 5
513 elif (event.num == 5):
515 else: # Windows & OSX
518 for box in self.listlist:
519 box.yview("scroll", delta, "units")
522 def changeSelection(self, evt):
524 sellist = box.curselection()
527 index = int(sellist[0])
528 config = self.namelist.get(index)
529 # Now find the description for that config in the dictionary
530 for conf in configuration_dictionary:
531 if conf['name'] == config:
532 self.descriptionText.config(state=tk.NORMAL)
533 self.descriptionText.delete(1.0,tk.END)
534 str = config + "\n" + conf['description']
535 self.descriptionText.insert(1.0, str)
536 self.descriptionText.config(state=tk.DISABLED)
538 # Set all the other list boxes to the same index
539 for b in self.listlist:
541 b.selection_clear(0, tk.END)
542 b.selection_set(index)
544 def OnEntryUpDown(self, event):
546 selection = box.curselection()
549 index = int(selection[0])
550 if event.keysym == 'Up':
552 elif event.keysym == 'Down':
555 if 0 <= index < box.size():
556 for b in self.listlist:
557 b.selection_clear(0, tk.END)
558 b.selection_set(index)
561 def doubleClick(self, evt):
563 index = int(box.curselection()[0])
564 config = self.namelist.get(index)
565 # Get the associated dict entry from our list of configs
566 for conf in configuration_dictionary:
567 if conf['name'] == config:
568 if (conf['type'] == 'bool'):
569 result = EditBoolWindow(self, conf, self.valuelist.get(index)).get()
570 elif (conf['type'] == 'int' or conf['type'] == ""): # "" defaults to int
571 result = EditIntWindow(self, conf, self.valuelist.get(index)).get()
572 elif conf['type'] == 'enum':
573 result = EditEnumWindow(self, conf, self.valuelist.get(index)).get()
575 # Update the valuelist with our new item
576 self.valuelist.delete(index)
577 self.valuelist.insert(index, result)
578 if result != CONFIG_UNSET:
579 self.valuelist.itemconfig(index, {'bg':'green'})
583 # Get the selections, and create a list of them
584 for i, val in enumerate(self.valuelist.get(0, tk.END)):
585 if val != CONFIG_UNSET:
586 self.results[self.namelist.get(i)] = val
588 self.results.pop(self.namelist.get(i), None)
598 class WirelessSettingsWindow(sd.Dialog):
600 def __init__(self, parent):
601 sd.Dialog.__init__(self, parent, "Wireless settings")
604 def body(self, master):
605 self.configure(background=GetBackground())
606 master.configure(background=GetBackground())
607 self.ssid = tk.StringVar()
608 self.password = tk.StringVar()
610 a = ttk.Label(master, text='SSID :', background=GetBackground())
611 a.grid(row=0, column=0, sticky=tk.E)
612 a.configure(background=GetBackground())
613 ttk.Entry(master, textvariable=self.ssid).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5)
615 ttk.Label(master, text='Password :').grid(row=1, column=0, sticky=tk.E)
616 ttk.Entry(master, textvariable=self.password).grid(row=1, column=1, sticky=tk.W+tk.E, padx=5)
618 self.transient(self.parent)
629 return (self.ssid.get(), self.password.get())
632 class ProjectWindow(tk.Frame):
634 def __init__(self, parent, sdkpath, args):
635 tk.Frame.__init__(self, parent)
637 self.sdkpath = sdkpath
638 self.init_window(args)
639 self.configs = dict()
641 self.password = str()
643 def setState(self, thing, state):
644 for child in thing.winfo_children():
645 child.configure(state=state)
647 def boardtype_change_callback(self, event):
648 boardtype = self.boardtype.get()
649 if boardtype == "pico_w":
650 self.setState(self.picowSubframe, "enabled")
652 self.setState(self.picowSubframe, "disabled")
654 def wirelessSettings(self):
655 result = WirelessSettingsWindow(self)
656 self.ssid, self.password = result.get()
658 def init_window(self, args):
659 self.master.title("Raspberry Pi Pico Project Generator")
660 self.master.configure(bg=GetBackground())
664 mainFrame = tk.Frame(self, bg=GetBackground()).grid(row=optionsRow, column=0, columnspan=6, rowspan=12)
666 # Need to keep a reference to the image or it will not appear.
667 self.logo = tk.PhotoImage(file=GetFilePath("logo_alpha.gif"))
668 logowidget = ttk.Label(mainFrame, image=self.logo, borderwidth=0, relief="solid").grid(row=0,column=0, columnspan=5, pady=10)
672 namelbl = ttk.Label(mainFrame, text='Project Name :').grid(row=optionsRow, column=0, sticky=tk.E)
673 self.projectName = tk.StringVar()
675 if args.name != None:
676 self.projectName.set(args.name)
678 self.projectName.set('ProjectName')
680 nameEntry = ttk.Entry(mainFrame, textvariable=self.projectName).grid(row=optionsRow, column=1, sticky=tk.W+tk.E, padx=5)
684 locationlbl = ttk.Label(mainFrame, text='Location :').grid(row=optionsRow, column=0, sticky=tk.E)
685 self.locationName = tk.StringVar()
686 self.locationName.set(os.getcwd())
687 locationEntry = ttk.Entry(mainFrame, textvariable=self.locationName).grid(row=optionsRow, column=1, columnspan=3, sticky=tk.W+tk.E, padx=5)
688 locationBrowse = ttk.Button(mainFrame, text='Browse', command=self.browse).grid(row=3, column=4)
692 ttk.Label(mainFrame, text = "Board Type :").grid(row=optionsRow, column=0, padx=4, sticky=tk.E)
693 self.boardtype = ttk.Combobox(mainFrame, values=boardtype_list, )
694 self.boardtype.grid(row=4, column=1, padx=4, sticky=tk.W+tk.E)
695 self.boardtype.set('pico')
696 self.boardtype.bind('<<ComboboxSelected>>',self.boardtype_change_callback)
700 featuresframe = ttk.LabelFrame(mainFrame, text="Library Options", relief=tk.RIDGE, borderwidth=2)
701 featuresframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=5, ipadx=5, padx=5, pady=5, sticky=tk.E+tk.W)
703 s = (len(features_list)/3)
705 self.feature_checkbox_vars = []
708 for i in features_list:
709 var = tk.StringVar(value='') # Off by default for the moment
710 c = features_list[i][GUI_TEXT]
711 cb = ttk.Checkbutton(featuresframe, text = c, var=var, onvalue=i, offvalue='')
712 cb.grid(row=row, column=col, padx=15, pady=2, ipadx=1, ipady=1, sticky=tk.E+tk.W)
713 self.feature_checkbox_vars.append(var)
721 # PicoW options section
722 self.picowSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Pico Wireless Options")
723 self.picowSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
724 self.pico_wireless = tk.StringVar()
728 for i in picow_options_list:
729 rb = ttk.Radiobutton(self.picowSubframe, text=picow_options_list[i][GUI_TEXT], variable=self.pico_wireless, val=i)
730 rb.grid(row=row, column=col, padx=15, pady=1, sticky=tk.E+tk.W)
736 # DOnt actually need any settings at the moment.
737 # ttk.Button(self.picowSubframe, text='Settings', command=self.wirelessSettings).grid(row=0, column=4, padx=5, pady=2, sticky=tk.E)
739 self.setState(self.picowSubframe, "disabled")
743 # output options section
744 ooptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Console Options")
745 ooptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
747 self.wantUART = tk.IntVar()
748 self.wantUART.set(args.uart)
749 ttk.Checkbutton(ooptionsSubframe, text="Console over UART", variable=self.wantUART).grid(row=0, column=0, padx=4, sticky=tk.W)
751 self.wantUSB = tk.IntVar()
752 self.wantUSB.set(args.usb)
753 ttk.Checkbutton(ooptionsSubframe, text="Console over USB (Disables other USB use)", variable=self.wantUSB).grid(row=0, column=1, padx=4, sticky=tk.W)
757 # Code options section
758 coptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Code Options")
759 coptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=3, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
761 self.wantExamples = tk.IntVar()
762 self.wantExamples.set(args.examples)
763 ttk.Checkbutton(coptionsSubframe, text="Add examples for Pico library", variable=self.wantExamples).grid(row=0, column=0, padx=4, sticky=tk.W)
765 self.wantRunFromRAM = tk.IntVar()
766 self.wantRunFromRAM.set(args.runFromRAM)
767 ttk.Checkbutton(coptionsSubframe, text="Run from RAM", variable=self.wantRunFromRAM).grid(row=0, column=1, padx=4, sticky=tk.W)
769 self.wantCPP = tk.IntVar()
770 self.wantCPP.set(args.cpp)
771 ttk.Checkbutton(coptionsSubframe, text="Generate C++", variable=self.wantCPP).grid(row=0, column=3, padx=4, sticky=tk.W)
773 ttk.Button(coptionsSubframe, text="Advanced...", command=self.config).grid(row=0, column=4, sticky=tk.E)
775 self.wantCPPExceptions = tk.IntVar()
776 self.wantCPPExceptions.set(args.cppexceptions)
777 ttk.Checkbutton(coptionsSubframe, text="Enable C++ exceptions", variable=self.wantCPPExceptions).grid(row=1, column=0, padx=4, sticky=tk.W)
779 self.wantCPPRTTI = tk.IntVar()
780 self.wantCPPRTTI.set(args.cpprtti)
781 ttk.Checkbutton(coptionsSubframe, text="Enable C++ RTTI", variable=self.wantCPPRTTI).grid(row=1, column=1, padx=4, sticky=tk.W)
785 # Build Options section
787 boptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Build Options")
788 boptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
790 self.wantBuild = tk.IntVar()
791 self.wantBuild.set(args.build)
792 ttk.Checkbutton(boptionsSubframe, text="Run build after generation", variable=self.wantBuild).grid(row=0, column=0, padx=4, sticky=tk.W)
794 self.wantOverwrite = tk.IntVar()
795 self.wantOverwrite.set(args.overwrite)
796 ttk.Checkbutton(boptionsSubframe, text="Overwrite existing projects", variable=self.wantOverwrite).grid(row=0, column=1, padx=4, sticky=tk.W)
800 # IDE Options section
802 vscodeoptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="IDE Options")
803 vscodeoptionsSubframe.grid_columnconfigure(2, weight=1)
804 vscodeoptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
806 self.wantVSCode = tk.IntVar()
807 if args.project is None:
808 self.wantVSCode.set(False)
810 self.wantVSCode.set('vscode' in args.project)
811 ttk.Checkbutton(vscodeoptionsSubframe, text="Create VSCode project", variable=self.wantVSCode).grid(row=0, column=0, padx=4, sticky=tk.W)
813 ttk.Label(vscodeoptionsSubframe, text = " Debugger:").grid(row=0, column=1, padx=4, sticky=tk.W)
815 self.debugger = ttk.Combobox(vscodeoptionsSubframe, values=debugger_list, state="readonly")
816 self.debugger.grid(row=0, column=2, padx=4, sticky=tk.EW)
817 self.debugger.current(args.debugger)
821 # OK, Cancel, Help section
823 QuitButton = ttk.Button(mainFrame, text="Quit", command=self.quit).grid(row=optionsRow, column=4, stick=tk.E, padx=10, pady=5)
824 OKButton = ttk.Button(mainFrame, text="OK", command=self.OK).grid(row=optionsRow, column=3, padx=4, pady=5, sticky=tk.E)
826 # TODO help not implemented yet
827 # HelpButton = ttk.Button(mainFrame, text="Help", command=self.help).grid(row=optionsRow, column=0, pady=5)
829 # You can set a default path here, replace the string with whereever you want.
830 # self.locationName.set('/home/pi/pico_projects')
832 def GetFeatures(self):
836 for cb in self.feature_checkbox_vars:
841 picow_extra = self.pico_wireless.get()
843 if picow_extra != 'picow_none':
844 features.append(picow_extra)
849 # TODO Check if we want to exit here
853 # OK, grab all the settings from the page, then call the generators
854 projectPath = self.locationName.get()
855 features = self.GetFeatures()
857 if (self.wantVSCode.get()):
858 projects.append("vscode")
861 'sdkPath' : self.sdkpath,
862 'projectRoot' : Path(projectPath),
863 'projectName' : self.projectName.get(),
865 'wantOverwrite' : self.wantOverwrite.get(),
866 'wantBuild' : self.wantBuild.get(),
867 'boardtype' : self.boardtype.get(),
868 'features' : features,
869 'projects' : projects,
870 'configs' : self.configs,
871 'wantRunFromRAM': self.wantRunFromRAM.get(),
872 'wantExamples' : self.wantExamples.get(),
873 'wantUART' : self.wantUART.get(),
874 'wantUSB' : self.wantUSB.get(),
875 'wantCPP' : self.wantCPP.get(),
876 'debugger' : self.debugger.current(),
877 'exceptions' : self.wantCPPExceptions.get(),
878 'rtti' : self.wantCPPRTTI.get(),
880 'password' : self.password,
883 DoEverything(self, params)
886 name = fd.askdirectory()
887 self.locationName.set(name)
893 # Run the configuration window
894 self.configs = ConfigurationWindow(self, self.configs).get()
896 def CheckPrerequisites():
897 global isMac, isWindows
898 isMac = (platform.system() == 'Darwin')
899 isWindows = (platform.system() == 'Windows')
901 # Do we have a compiler?
902 return shutil.which(COMPILER_NAME, 1, os.environ["Path" if isWindows else "PATH"])
905 def CheckSDKPath(gui):
906 sdkPath = os.getenv('PICO_SDK_PATH')
909 m = 'Unable to locate the Raspberry Pi Pico SDK, PICO_SDK_PATH is not set'
910 if gui and ENABLE_TK_GUI:
914 elif not os.path.isdir(sdkPath):
915 m = 'Unable to locate the Raspberry Pi Pico SDK, PICO_SDK_PATH does not point to a directory'
916 if gui and ENABLE_TK_GUI:
924 def GetFilePath(filename):
925 if os.path.islink(__file__):
926 script_file = os.readlink(__file__)
928 script_file = __file__
929 return os.path.join(os.path.dirname(script_file), filename)
931 def ParseCommandLine():
932 debugger_flags = ', '.join('{} = {}'.format(i, v) for i, v in enumerate(debugger_list))
933 parser = argparse.ArgumentParser(description='Pico Project generator')
934 parser.add_argument("name", nargs="?", help="Name of the project")
935 parser.add_argument("-t", "--tsv", help="Select an alternative pico_configs.tsv file", default=GetFilePath("pico_configs.tsv"))
936 parser.add_argument("-o", "--output", help="Set an alternative CMakeList.txt filename", default="CMakeLists.txt")
937 parser.add_argument("-x", "--examples", action='store_true', help="Add example code for the Pico standard library")
938 parser.add_argument("-l", "--list", action='store_true', help="List available features")
939 parser.add_argument("-c", "--configs", action='store_true', help="List available project configuration items")
940 parser.add_argument("-f", "--feature", action='append', help="Add feature to generated project")
941 parser.add_argument("-over", "--overwrite", action='store_true', help="Overwrite any existing project AND files")
942 parser.add_argument("-b", "--build", action='store_true', help="Build after project created")
943 parser.add_argument("-g", "--gui", action='store_true', help="Run a GUI version of the project generator")
944 parser.add_argument("-p", "--project", action='append', help="Generate projects files for IDE. Options are: vscode")
945 parser.add_argument("-r", "--runFromRAM", action='store_true', help="Run the program from RAM rather than flash")
946 parser.add_argument("-uart", "--uart", action='store_true', default=1, help="Console output to UART (default)")
947 parser.add_argument("-nouart", "--nouart", action='store_true', default=0, help="Disable console output to UART")
948 parser.add_argument("-usb", "--usb", action='store_true', help="Console output to USB (disables other USB functionality")
949 parser.add_argument("-cpp", "--cpp", action='store_true', default=0, help="Generate C++ code")
950 parser.add_argument("-cpprtti", "--cpprtti", action='store_true', default=0, help="Enable C++ RTTI (Uses more memory)")
951 parser.add_argument("-cppex", "--cppexceptions", action='store_true', default=0, help="Enable C++ exceptions (Uses more memory)")
952 parser.add_argument("-d", "--debugger", type=int, help="Select debugger ({})".format(debugger_flags), default=0)
953 parser.add_argument("-board", "--boardtype", action="store", default='pico', help="Select board type (see --boardlist for available boards)")
954 parser.add_argument("-bl", "--boardlist", action="store_true", help="List available board types")
955 parser.add_argument("-cp", "--cpath", help="Override default VSCode compiler path")
956 parser.add_argument("-root", "--projectRoot", help="Override default project root where the new project will be created")
957 parser.add_argument("-sdkVersion", "--sdkVersion", help="Pico SDK version to use (required)")
958 parser.add_argument("-tcVersion", "--toolchainVersion", help="ARM Embeded Toolchain version to use (required)")
959 parser.add_argument("-np", "--ninjaPath", help="Ninja path")
960 parser.add_argument("-cmp", "--cmakePath", help="CMake path")
961 parser.add_argument("-cupy", "--customPython", action='store_true', help="Custom python path used to execute the script.")
963 return parser.parse_args()
966 def GenerateMain(folder, projectName, features, cpp):
969 filename = Path(folder) / (projectName + '.cpp')
971 filename = Path(folder) / (projectName + '.c')
973 file = open(filename, 'w')
975 main = ('#include <stdio.h>\n'
976 '#include "pico/stdlib.h"\n'
983 for feat in features:
984 if (feat in features_list):
985 o = f'#include "{features_list[feat][H_FILE]}"\n'
987 if (feat in stdlib_examples_list):
988 o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n'
990 if (feat in picow_options_list):
991 o = f'#include "{picow_options_list[feat][H_FILE]}"\n'
997 for feat in features:
998 if (feat in code_fragments_per_feature):
999 for s in code_fragments_per_feature[feat][DEFINES]:
1007 ' stdio_init_all();\n\n'
1011 # Add any initialisers
1013 for feat in features:
1014 if (feat in code_fragments_per_feature):
1015 for s in code_fragments_per_feature[feat][INITIALISERS]:
1016 main += (" " * indent)
1021 main += (' puts("Hello, world!");\n\n'
1031 def GenerateCMake(folder, params):
1033 filename = Path(folder) / CMAKELIST_FILENAME
1034 projectName = params['projectName']
1035 board_type = params['boardtype']
1037 # OK, for the path, CMake will accept forward slashes on Windows, and thats
1038 # seemingly a bit easier to handle than the backslashes
1039 p = str(params['sdkPath']).replace('\\','/')
1041 cmake_header1 = (f"# Generated Cmake Pico project file\n\n"
1042 "cmake_minimum_required(VERSION 3.13)\n\n"
1043 "set(CMAKE_C_STANDARD 11)\n"
1044 "set(CMAKE_CXX_STANDARD 17)\n\n"
1045 "# Initialise pico_sdk from installed location\n"
1046 "# (note this can come from environment, CMake cache etc)\n\n"
1047 "# == DO NEVER EDIT THE NEXT LINES for RaspberryPiPico VS Code Extension to work == \n"
1049 " set(USERHOME $ENV{USERPROFILE})\n"
1051 " set(USERHOME $ENV{HOME})\n"
1053 f"set(PICO_SDK_PATH {cmakeSdkPath(params['sdkVersion'])})\n"
1054 f"set(PICO_TOOLCHAIN_PATH {cmakeToolchainPath(params['toolchainVersion'])})\n"
1055 "# ====================================================================================\n"
1056 f"set(PICO_BOARD {board_type} CACHE STRING \"Board type\")\n\n"
1057 "# Pull in Raspberry Pi Pico SDK (must be before project)\n"
1058 "include(pico_sdk_import.cmake)\n\n"
1059 "if (PICO_SDK_VERSION_STRING VERSION_LESS \"1.4.0\")\n"
1060 " message(FATAL_ERROR \"Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}\")\n"
1062 f"project({projectName} C CXX ASM)\n"
1066 "\n# Initialise the Raspberry Pi Pico SDK\n"
1067 "pico_sdk_init()\n\n"
1068 "# Add executable. Default name is the project name, version 0.1\n\n"
1072 file = open(filename, 'w')
1074 file.write(cmake_header1)
1076 if params['exceptions']:
1077 file.write("\nset(PICO_CXX_ENABLE_EXCEPTIONS 1)\n")
1080 file.write("\nset(PICO_CXX_ENABLE_RTTI 1)\n")
1082 file.write(cmake_header3)
1084 # add the preprocessor defines for overall configuration
1085 if params['configs']:
1086 file.write('# Add any PICO_CONFIG entries specified in the Advanced settings\n')
1087 for c, v in params['configs'].items():
1092 file.write(f'add_compile_definitions({c} = {v})\n')
1095 # No GUI/command line to set a different executable name at this stage
1096 executableName = projectName
1098 if params['wantCPP']:
1099 file.write(f'add_executable({projectName} {projectName}.cpp )\n\n')
1101 file.write(f'add_executable({projectName} {projectName}.c )\n\n')
1103 file.write(f'pico_set_program_name({projectName} "{executableName}")\n')
1104 file.write(f'pico_set_program_version({projectName} "0.1")\n\n')
1106 if params['wantRunFromRAM']:
1107 file.write(f'# no_flash means the target is to run from RAM\n')
1108 file.write(f'pico_set_binary_type({projectName} no_flash)\n\n')
1110 # Console output destinations
1111 if params['wantUART']:
1112 file.write(f'pico_enable_stdio_uart({projectName} 1)\n')
1114 file.write(f'pico_enable_stdio_uart({projectName} 0)\n')
1116 if params['wantUSB']:
1117 file.write(f'pico_enable_stdio_usb({projectName} 1)\n\n')
1119 file.write(f'pico_enable_stdio_usb({projectName} 0)\n\n')
1121 # If we need wireless, check for SSID and password
1122 # removed for the moment as these settings are currently only needed for the pico-examples
1123 # but may be required in here at a later date.
1125 if 'ssid' in params or 'password' in params:
1126 file.write('# Add any wireless access point information\n')
1127 file.write(f'target_compile_definitions({projectName} PRIVATE\n')
1128 if 'ssid' in params:
1129 file.write(f'WIFI_SSID=\" {params["ssid"]} \"\n')
1131 file.write(f'WIFI_SSID=\"${WIFI_SSID}\"')
1133 if 'password' in params:
1134 file.write(f'WIFI_PASSWORD=\"{params["password"]}\"\n')
1136 file.write(f'WIFI_PASSWORD=\"${WIFI_PASSWORD}\"')
1139 # Standard libraries
1140 file.write('# Add the standard library to the build\n')
1141 file.write(f'target_link_libraries({projectName}\n')
1142 file.write(" " + STANDARD_LIBRARIES)
1145 # Standard include directories
1146 file.write('# Add the standard include files to the build\n')
1147 file.write(f'target_include_directories({projectName} PRIVATE\n')
1148 file.write(" ${CMAKE_CURRENT_LIST_DIR}\n")
1149 file.write(" ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required\n")
1152 # Selected libraries/features
1153 if (params['features']):
1154 file.write('# Add any user requested libraries\n')
1155 file.write(f'target_link_libraries({projectName} \n')
1156 for feat in params['features']:
1157 if (feat in features_list):
1158 file.write(" " + features_list[feat][LIB_NAME] + '\n')
1159 if (feat in picow_options_list):
1160 file.write(" " + picow_options_list[feat][LIB_NAME] + '\n')
1161 file.write(' )\n\n')
1163 file.write(f'pico_add_extra_outputs({projectName})\n\n')
1168 # Generates the requested project files, if any
1169 def generateProjectFiles(projectPath, projectName, sdkPath, projects, debugger, sdkVersion, toolchainVersion, ninjaPath, cmakePath, customPython):
1171 oldCWD = os.getcwd()
1173 os.chdir(projectPath)
1175 debugger = debugger_config_list[debugger]
1176 gdbPath = Path(codeToolchainPath(toolchainVersion)+"/bin/arm-none-eabi-gdb").as_posix() if isWindows else "gdb-multiarch"
1177 # Need to escape windows files paths backslashes
1178 # TODO: env in currently not supported in compilerPath var
1179 #cPath = f"${{env:PICO_TOOLCHAIN_PATH_{envSuffix}}}" + os.path.sep + os.path.basename(str(compilerPath).replace('\\', '\\\\' ))
1180 cPath = compilerPath.as_posix()
1182 # if this is a path in the .pico-sdk homedir tell the settings to use the homevar
1183 user_home = os.path.expanduser("~").replace("\\", "/")
1184 use_home_var = f"{user_home}/.pico-sdk" in ninjaPath
1192 "name": "Pico Debug (Cortex-Debug)",
1193 "cwd": "${{workspaceRoot}}",
1194 "executable": "${{command:raspberry-pi-pico.launchTargetPath}}",
1195 "request": "launch",
1196 "type": "cortex-debug",
1197 "servertype": "openocd",
1198 "gdbPath": "{gdbPath}",
1201 "interface/{debugger}",
1204 "svdFile": "{codeSdkPath(sdkVersion)}/src/rp2040/hardware_regs/rp2040.svd",
1205 "runToEntryPoint": "main",
1206 // Give restart the same functionality as runToEntryPoint - main
1207 "postRestartCommands": [
1211 "openOCDLaunchCommands": [
1212 "adapter speed 1000"
1214 "preLaunchTask": "Compile Project"
1217 "name": "Pico Debug (Cortex-Debug with external OpenOCD)",
1218 "cwd": "${{workspaceRoot}}",
1219 "executable": "${{command:raspberry-pi-pico.launchTargetPath}}",
1220 "request": "launch",
1221 "type": "cortex-debug",
1222 "servertype": "external",
1223 "gdbTarget": "localhost:3333",
1224 "gdbPath": "{gdbPath}",
1226 "svdFile": "{codeSdkPath(sdkVersion)}/src/rp2040/hardware_regs/rp2040.svd",
1227 "runToEntryPoint": "main",
1228 // Give restart the same functionality as runToEntryPoint - main
1229 "postRestartCommands": [
1233 "preLaunchTask": "Compile Project"
1236 "name": "Pico Debug (C++ Debugger)",
1238 "request": "launch",
1239 "cwd": "${{workspaceRoot}}",
1240 "program": "${{command:raspberry-pi-pico.launchTargetPath}}",
1242 "miDebuggerPath": "{gdbPath}",
1243 "miDebuggerServerAddress": "localhost:3333",
1244 "debugServerPath": "openocd",
1245 "debugServerArgs": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \\"adapter speed 1000\\"",
1246 "serverStarted": "Listening on port .* for gdb connections",
1247 "filterStderr": true,
1248 "stopAtEntry": true,
1249 "hardwareBreakpoints": {{
1253 "preLaunchTask": "Flash",
1254 "svdPath": "{codeSdkPath(sdkVersion)}/src/rp2040/hardware_regs/rp2040.svd"
1265 "${{workspaceFolder}}/**",
1266 "{propertiesSdkPath(sdkVersion)}/**"
1269 "compilerPath": "{cPath}",
1271 "cppStandard": "c++14",
1272 "intelliSenseMode": "linux-gcc-arm"
1279 pythonExe = sys.executable.replace("\\", "/").replace(user_home, "${HOME}") if use_home_var else sys.executable
1283 "cmake.statusbar.visibility": "hidden",
1284 "cmake.configureOnEdit": false,
1285 "cmake.automaticReconfigure": false,
1286 "cmake.configureOnOpen": false,
1287 "cmake.generator": "Ninja",
1288 "cmake.cmakePath": "{cmakePath}",
1289 "raspberry-pi-pico.cmakeAutoConfigure": true,
1290 "raspberry-pi-pico.cmakePath": "{cmakePath.replace(user_home, "${HOME}") if use_home_var else cmakePath}",
1291 "raspberry-pi-pico.ninjaPath": "{ninjaPath.replace(user_home, "${HOME}") if use_home_var else ninjaPath}"'''
1295 "raspberry-pi-pico.python3Path": "{pythonExe}"'''
1301 "recommendations": [
1302 "marus25.cortex-debug",
1303 "ms-vscode.cpptools",
1304 "ms-vscode.cpptools-extension-pack",
1305 "ms-vscode.vscode-serial-monitor",
1306 "paulober.raspberry-pi-pico",
1314 "label": "Compile Project",
1316 "command": "{ninjaPath.replace(user_home, "${userHome}") if use_home_var else ninjaPath}",
1317 "args": ["-C", "${{workspaceFolder}}/build"],
1321 "panel": "dedicated"
1323 "problemMatcher": "$gcc"
1329 # Create a build folder, and run our cmake project build from it
1330 if not os.path.exists(VSCODE_FOLDER):
1331 os.mkdir(VSCODE_FOLDER)
1333 os.chdir(VSCODE_FOLDER)
1335 file = open(VSCODE_TASKS_FILENAME, 'w')
1339 filename = VSCODE_LAUNCH_FILENAME
1340 file = open(filename, 'w')
1344 file = open(VSCODE_C_PROPERTIES_FILENAME, 'w')
1345 file.write(properties)
1348 file = open(VSCODE_SETTINGS_FILENAME, 'w')
1349 file.write(settings)
1352 file = open(VSCODE_EXTENSIONS_FILENAME, 'w')
1353 file.write(extensions)
1357 print('Unknown project type requested')
1362 def LoadConfigurations():
1364 with open(args.tsv) as tsvfile:
1365 reader = csv.DictReader(tsvfile, dialect='excel-tab')
1367 configuration_dictionary.append(row)
1369 print("No Pico configurations file found. Continuing without")
1371 def LoadBoardTypes(sdkPath):
1372 # Scan the boards folder for all header files, extract filenames, and make a list of the results
1373 # default folder is <PICO_SDK_PATH>/src/boards/include/boards/*
1374 # If the PICO_BOARD_HEADER_DIRS environment variable is set, use that as well
1376 loc = sdkPath / "src/boards/include/boards"
1378 for x in Path(loc).iterdir():
1379 if x.suffix == '.h':
1380 boards.append(x.stem)
1382 loc = os.getenv('PICO_BOARD_HEADER_DIRS')
1385 for x in Path(loc).iterdir():
1386 if x.suffix == '.h':
1387 boards.append(x.stem)
1391 def DoEverything(parent, params):
1393 if not os.path.exists(params['projectRoot']):
1394 if params['wantGUI'] and ENABLE_TK_GUI:
1395 mb.showerror('Raspberry Pi Pico Project Generator', 'Invalid project path. Select a valid path and try again')
1398 print('Invalid project path')
1401 oldCWD = os.getcwd()
1402 os.chdir(params['projectRoot'])
1404 # Create our project folder as subfolder
1405 os.makedirs(params['projectName'], exist_ok=True)
1407 os.chdir(params['projectName'])
1409 projectPath = params['projectRoot'] / params['projectName']
1411 # First check if there is already a project in the folder
1412 # If there is we abort unless the overwrite flag it set
1413 if os.path.exists(CMAKELIST_FILENAME):
1414 if not params['wantOverwrite'] :
1415 if params['wantGUI'] and ENABLE_TK_GUI:
1416 # We can ask the user if they want to overwrite
1417 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')
1421 print('There already appears to be a project in this folder. Use the --overwrite option to overwrite the existing project')
1424 # We should really confirm the user wants to overwrite
1425 #print('Are you sure you want to overwrite the existing project files? (y/N)')
1426 #c = input().split(" ")[0]
1427 #if c != 'y' and c != 'Y' :
1430 # Copy the SDK finder cmake file to our project folder
1431 # Can be found here <PICO_SDK_PATH>/external/pico_sdk_import.cmake
1432 shutil.copyfile(params['sdkPath'] / 'external' / 'pico_sdk_import.cmake', projectPath / 'pico_sdk_import.cmake' )
1434 if params['features']:
1435 features_and_examples = params['features'][:]
1437 features_and_examples= []
1439 if params['wantExamples']:
1440 features_and_examples = list(stdlib_examples_list.keys()) + features_and_examples
1442 GenerateMain('.', params['projectName'], features_and_examples, params['wantCPP'])
1444 GenerateCMake('.', params)
1446 # If we have any ancilliary files, copy them to our project folder
1447 # Currently only the picow with lwIP support needs an extra file, so just check that list
1448 for feat in features_and_examples:
1449 if feat in picow_options_list:
1450 if picow_options_list[feat][ANCILLARY_FILE] != "":
1451 shutil.copy(sourcefolder + "/" + picow_options_list[feat][ANCILLARY_FILE], projectPath / picow_options_list[feat][ANCILLARY_FILE])
1453 # Create a build folder, and run our cmake project build from it
1454 if not os.path.exists('build'):
1459 # If we are overwriting a previous project, we should probably clear the folder, but that might delete something the users thinks is important, so
1460 # for the moment, just delete the CMakeCache.txt file as certain changes may need that to be recreated.
1462 if os.path.exists(CMAKECACHE_FILENAME):
1463 os.remove(CMAKECACHE_FILENAME)
1465 cpus = os.cpu_count()
1470 if shutil.which("ninja") or (params["ninjaPath"] != None and params["ninjaPath"] != ""):
1471 # When installing SDK version 1.5.0 on windows with installer pico-setup-windows-x64-standalone.exe, ninja is used
1472 cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug -G Ninja ..'
1473 makeCmd = params['ninjaPath'] + ' '
1475 # Everything else assume nmake
1476 cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug -G "NMake Makefiles" ..'
1479 # Ninja now works OK under Linux, so if installed use it by default. It's faster.
1480 if shutil.which("ninja") or (params["ninjaPath"] != None and params["ninjaPath"] != ""):
1481 cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug -G Ninja ..'
1482 makeCmd = params['ninjaPath'] + ' '
1484 cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug ..'
1485 makeCmd = 'make -j' + str(cpus)
1487 if params['wantGUI'] and ENABLE_TK_GUI:
1488 RunCommandInWindow(parent, cmakeCmd)
1492 if params['projects']:
1493 generateProjectFiles(
1495 params['projectName'],
1499 params["sdkVersion"],
1500 params["toolchainVersion"],
1501 params["ninjaPath"],
1502 params["cmakePath"],
1503 params["customPython"])
1505 if params['wantBuild']:
1506 if params['wantGUI'] and ENABLE_TK_GUI:
1507 RunCommandInWindow(parent, makeCmd)
1510 print('\nIf the application has built correctly, you can now transfer it to the Raspberry Pi Pico board')
1515 ###################################################################################
1516 # main execution starteth here
1518 sourcefolder = os.path.dirname(os.path.abspath(__file__))
1520 args = ParseCommandLine()
1525 if args.debugger > len(debugger_list) - 1:
1528 # Check we have everything we need to compile etc
1529 c = CheckPrerequisites()
1531 ## TODO Do both warnings in the same error message so user does have to keep coming back to find still more to do
1534 m = f'Unable to find the `{COMPILER_NAME}` compiler\n'
1535 m +='You will need to install an appropriate compiler to build a Raspberry Pi Pico project\n'
1536 m += 'See the Raspberry Pi Pico documentation for how to do this on your particular platform\n'
1538 if args.gui and ENABLE_TK_GUI:
1544 if args.name == None and not args.gui and not args.list and not args.configs and not args.boardlist:
1545 print("No project name specfied\n")
1548 # Check if we were provided a compiler path, and override the default if so
1550 compilerPath = Path(args.cpath)
1551 elif args.toolchainVersion:
1552 compilerPath = Path(propertiesToolchainPath(args.toolchainVersion)+"/bin/"+COMPILER_NAME)
1554 compilerPath = Path(c)
1556 # load/parse any configuration dictionary we may have
1557 LoadConfigurations()
1559 p = CheckSDKPath(args.gui)
1566 boardtype_list = LoadBoardTypes(sdkPath)
1567 boardtype_list.sort()
1569 if args.gui and ENABLE_TK_GUI:
1570 RunGUI(sdkPath, args) # does not return, only exits
1572 projectRoot = Path(os.getcwd()) if not args.projectRoot else Path(args.projectRoot)
1574 if args.list or args.configs or args.boardlist:
1576 print("Available project features:\n")
1577 for feat in features_list:
1578 print(feat.ljust(6), '\t', features_list[feat][GUI_TEXT])
1582 print("Available project configuration items:\n")
1583 for conf in configuration_dictionary:
1584 print(conf['name'].ljust(40), '\t', conf['description'])
1588 print("Available board types:\n")
1589 for board in boardtype_list:
1596 'sdkPath' : sdkPath,
1597 'projectRoot' : projectRoot,
1598 'projectName' : args.name,
1600 'wantOverwrite' : args.overwrite,
1601 'boardtype' : args.boardtype,
1602 'wantBuild' : args.build,
1603 'features' : args.feature,
1604 'projects' : args.project,
1606 'wantRunFromRAM': args.runFromRAM,
1607 'wantExamples' : args.examples,
1608 'wantUART' : args.uart,
1609 'wantUSB' : args.usb,
1610 'wantCPP' : args.cpp,
1611 'debugger' : args.debugger,
1612 'exceptions' : args.cppexceptions,
1613 'rtti' : args.cpprtti,
1616 'sdkVersion' : args.sdkVersion,
1617 'toolchainVersion': args.toolchainVersion,
1618 'ninjaPath' : args.ninjaPath,
1619 'cmakePath' : args.cmakePath,
1620 'customPython' : args.customPython
1623 DoEverything(None, params)