]> Git Repo - pico-vscode.git/blob - scripts/pico_project.py
Add elf2uf2 and pioasm for sdk 1.5.1
[pico-vscode.git] / scripts / pico_project.py
1 #!/usr/bin/env python3
2
3 #
4 # Copyright (c) 2020-2023 Raspberry Pi (Trading) Ltd.
5 #
6 # SPDX-License-Identifier: BSD-3-Clause
7 #
8
9 import argparse
10 from copy import copy
11 import os
12 from pyexpat import features
13 import shutil
14 from pathlib import Path
15 import string
16 import sys
17 import subprocess
18 import platform
19 import shlex
20 import csv
21
22 ENABLE_TK_GUI = False
23
24 if ENABLE_TK_GUI:
25     import tkinter as tk
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
30
31 CMAKELIST_FILENAME = 'CMakeLists.txt'
32 CMAKECACHE_FILENAME = 'CMakeCache.txt'
33
34 COMPILER_NAME = 'arm-none-eabi-gcc'
35
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'
42
43 CONFIG_UNSET="Not set"
44
45 # Standard libraries for all builds
46 # And any more to string below, space separator
47 STANDARD_LIBRARIES = 'pico_stdlib'
48
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
51 GUI_TEXT = 0
52 C_FILE = 1
53 H_FILE = 2
54 LIB_NAME = 3
55 ANCILLARY_FILE = 4
56
57 features_list = {
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"),
66 }
67
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"),
74 }
75
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")
80 }
81
82 debugger_list = ["DebugProbe (CMSIS-DAP)", "SWD (Pi host)"]
83 debugger_config_list = ["cmsis-dap.cfg", "raspberrypi-swd.cfg"]
84
85 DEFINES = 0
86 INITIALISERS = 1
87 # Could add an extra item that shows how to use some of the available functions for the feature
88 #EXAMPLE = 2
89
90 # This also contains example code for the standard library (see stdlib_examples_list)
91 code_fragments_per_feature = {
92     'uart' : [
93                ("// UART defines",
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" ),
101
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);", "" )
108             ],
109     'spi' : [
110               ( "// SPI Defines",
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",
115                 "#define PIN_CS   17",
116                 "#define PIN_SCK  18",
117                 "#define PIN_MOSI 19" ),
118
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);", "")
128             ],
129     'i2c' : [
130               (
131                 "// I2C defines",
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",
135                 "#define I2C_SDA 8",
136                 "#define I2C_SCL 9",
137               ),
138               (
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);"
145               )
146             ],
147     "gpio" : [
148               (
149                 "// GPIO defines",
150                 "// Example uses GPIO 2",
151                 "#define GPIO 2"
152               ),
153               (
154                 "// GPIO initialisation.",
155                 "// We will make this GPIO an input, and pull it up by default",
156                 "gpio_init(GPIO);",
157                 "gpio_set_dir(GPIO, GPIO_IN);",
158                 "gpio_pull_up(GPIO);","",
159               )
160             ],
161     "interp" :[
162                (),
163                (
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);",
171                )
172               ],
173
174     "timer"  : [
175                 (
176                  "int64_t alarm_callback(alarm_id_t id, void *user_data) {",
177                  "    // Put your timeout handler code in here",
178                  "    return 0;",
179                  "}"
180                 ),
181                 (
182                  "// Timer example code - This example fires off the callback after 2000ms",
183                  "add_alarm_in_ms(2000, alarm_callback, NULL, false);"
184                 )
185               ],
186
187     "watchdog":[ (),
188                 (
189                     "// Watchdog example code",
190                     "if (watchdog_caused_reboot()) {",
191                     "    // Whatever action you may take if a watchdog caused a reboot",
192                     "}","",
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();",
198                 )
199               ],
200
201     "div"    : [ (),
202                  (
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));"
217                  )
218                 ]
219 }
220
221 configuration_dictionary = list(dict())
222
223 isMac = False
224 isWindows = False
225 compilerPath = Path("/usr/bin/arm-none-eabi-gcc")
226
227 def relativeSDKPath(sdkVersion):
228     return f"/.pico-sdk/sdk/{sdkVersion}"
229
230 def relativeToolchainPath(toolchainVersion):
231     return f"/.pico-sdk/toolchain/{toolchainVersion}"
232
233 def relativeToolsPath(sdkVersion):
234     return f"/.pico-sdk/tools/{sdkVersion}"
235
236 def cmakeSdkPath(sdkVersion):
237     return f"${{USERHOME}}{relativeSDKPath(sdkVersion)}"
238
239 def cmakeToolchainPath(toolchainVersion):
240     return f"${{USERHOME}}{relativeToolchainPath(toolchainVersion)}"
241
242 def cmakeToolsPath(sdkVersion):
243     return f"${{USERHOME}}{relativeToolsPath(sdkVersion)}"
244
245 def propertiesSdkPath(sdkVersion):
246     if isWindows:
247         return f"${{env:USERPROFILE}}{relativeSDKPath(sdkVersion)}"
248     else:
249         return f"${{env:HOME}}{relativeSDKPath(sdkVersion)}"
250
251 def codeSdkPath(sdkVersion):
252     return f"${{userHome}}{relativeSDKPath(sdkVersion)}"
253
254 def propertiesToolchainPath(toolchainVersion):
255     if isWindows:
256         return f"${{env:USERPROFILE}}{relativeToolchainPath(toolchainVersion)}"
257     else:
258         return f"${{env:HOME}}{relativeToolchainPath(toolchainVersion)}"
259
260 def codeToolchainPath(toolchainVersion):
261     return f"${{userHome}}{relativeToolchainPath(toolchainVersion)}"
262
263 def GetBackground():
264     return 'white'
265
266 def GetButtonBackground():
267     return 'white'
268
269 def GetTextColour():
270     return 'black'
271
272 def GetButtonTextColour():
273     return '#c51a4a'
274
275 if ENABLE_TK_GUI:
276     def RunGUI(sdkpath, args):
277         root = tk.Tk()
278         style = ttk.Style(root)
279         style.theme_use('default')
280
281         style.configure("TButton", padding=6, relief="groove", border=2, foreground=GetButtonTextColour(), background=GetButtonBackground())
282         style.configure("TLabel", foreground=GetTextColour(), background=GetBackground() )
283         style.configure("TCheckbutton", foreground=GetTextColour(), background=GetBackground())
284         style.configure("TRadiobutton", foreground=GetTextColour(), background=GetBackground() )
285         style.configure("TLabelframe", foreground=GetTextColour(), background=GetBackground() )
286         style.configure("TLabelframe.Label", foreground=GetTextColour(), background=GetBackground() )
287         style.configure("TCombobox", foreground=GetTextColour(), background=GetBackground() )
288         style.configure("TListbox", foreground=GetTextColour(), background=GetBackground() )
289
290         style.map("TCheckbutton", background = [('disabled', GetBackground())])
291         style.map("TRadiobutton", background = [('disabled', GetBackground())])
292         style.map("TButton", background = [('disabled', GetBackground())])
293         style.map("TLabel", background = [('background', GetBackground())])
294         style.map("TComboBox", background = [('readonly', GetBackground())])
295
296         app = ProjectWindow(root, sdkpath, args)
297
298         app.configure(background=GetBackground())
299
300         root.mainloop()
301         sys.exit(0)
302
303 if ENABLE_TK_GUI:
304     def RunWarning(message):
305         mb.showwarning('Raspberry Pi Pico Project Generator', message)
306         sys.exit(0)
307
308 import threading
309
310 if ENABLE_TK_GUI:
311     def thread_function(text, command, ok):
312         l = shlex.split(command)
313         proc = subprocess.Popen(l, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
314         for line in iter(proc.stdout.readline,''):
315             if not line:
316                 if ok:
317                     ok["state"] = tk.NORMAL
318                 return
319             text.insert(tk.END, line)
320             text.see(tk.END)
321
322 # Function to run an OS command and display the output in a new modal window
323 if ENABLE_TK_GUI:
324     class DisplayWindow(tk.Toplevel):
325         def __init__(self, parent, title):
326             tk.Toplevel.__init__(self, parent)
327             self.parent = parent
328             self.init_window(title)
329
330         def init_window(self, title):
331             self.title(title)
332
333             frame = tk.Frame(self, borderwidth=5, relief=tk.RIDGE)
334             frame.pack(fill=tk.X, expand=True, side=tk.TOP)
335
336             scrollbar = tk.Scrollbar(frame)
337             self.text = tk.Text(frame, bg='gray14', fg='gray99')
338             scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
339             self.text.pack(side=tk.LEFT, fill=tk.Y)
340             scrollbar.config(command=self.text.yview)
341             self.text.config(yscrollcommand=scrollbar.set)
342
343             frame1 = tk.Frame(self, borderwidth=1)
344             frame1.pack(fill=tk.X, expand=True, side=tk.BOTTOM)
345             self.OKButton = ttk.Button(frame1, text="OK", command=self.OK)
346             self.OKButton["state"] = tk.DISABLED
347             self.OKButton.pack()
348
349             # make dialog modal
350             self.transient(self.parent)
351             self.grab_set()
352
353         def OK(self):
354             self.grab_release()
355             self.destroy()
356
357     def RunCommandInWindow(parent, command):
358         w = DisplayWindow(parent, command)
359         x = threading.Thread(target=thread_function, args=(w.text, command, w.OKButton))
360         x.start()
361         parent.wait_window(w)
362
363     class EditBoolWindow(sd.Dialog):
364
365         def __init__(self, parent, configitem, current):
366             self.parent = parent
367             self.config_item = configitem
368             self.current = current
369             sd.Dialog.__init__(self, parent, "Edit boolean configuration")
370
371
372         def body(self, master):
373             self.configure(background=GetBackground())
374             ttk.Label(self, text=self.config_item['name']).pack()
375             self.result = tk.StringVar()
376             self.result.set(self.current)
377             ttk.Radiobutton(master, text="True", variable=self.result, value="True").pack(anchor=tk.W)
378             ttk.Radiobutton(master, text="False", variable=self.result, value="False").pack(anchor=tk.W)
379             ttk.Radiobutton(master, text=CONFIG_UNSET, variable=self.result, value=CONFIG_UNSET).pack(anchor=tk.W)
380
381         def get(self):
382             return self.result.get()
383
384     class EditIntWindow(sd.Dialog):
385
386         def __init__(self, parent, configitem, current):
387             self.parent = parent
388             self.config_item = configitem
389             self.current = current
390             sd.Dialog.__init__(self, parent, "Edit integer configuration")
391
392         def body(self, master):
393             self.configure(background=GetBackground())
394             str = self.config_item['name'] + "  Max = " + self.config_item['max'] + "  Min = " + self.config_item['min']
395             ttk.Label(self, text=str).pack()
396             self.input =  tk.Entry(self)
397             self.input.pack(pady=4)
398             self.input.insert(0, self.current)
399             ttk.Button(self, text=CONFIG_UNSET, command=self.unset).pack(pady=5)
400
401         def validate(self):
402             self.result = self.input.get()
403             # Check for numeric entry
404             return True
405
406         def unset(self):
407             self.result = CONFIG_UNSET
408             self.destroy()
409
410         def get(self):
411             return self.result
412
413     class EditEnumWindow(sd.Dialog):
414         def __init__(self, parent, configitem, current):
415             self.parent = parent
416             self.config_item = configitem
417             self.current = current
418             sd.Dialog.__init__(self, parent, "Edit Enumeration configuration")
419
420         def body(self, master):
421             #self.configure(background=GetBackground())
422             values = self.config_item['enumvalues'].split('|')
423             values.insert(0,'Not set')
424             self.input =  ttk.Combobox(self, values=values, state='readonly')
425             self.input.set(self.current)
426             self.input.pack(pady=12)
427
428         def validate(self):
429             self.result = self.input.get()
430             return True
431
432         def get(self):
433             return self.result
434
435
436     class ConfigurationWindow(tk.Toplevel):
437
438         def __init__(self, parent, initial_config):
439             tk.Toplevel.__init__(self, parent)
440             self.master = parent
441             self.results = initial_config
442             self.init_window(self)
443
444         def init_window(self, args):
445             self.configure(background=GetBackground())
446             self.title("Advanced Configuration")
447             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)
448             ttk.Label(self, text="Name").grid(row=1, column=0, sticky=tk.W)
449             ttk.Label(self, text="Type").grid(row=1, column=1, sticky=tk.W)
450             ttk.Label(self, text="Min").grid(row=1, column=2, sticky=tk.W)
451             ttk.Label(self, text="Max").grid(row=1, column=3, sticky=tk.W)
452             ttk.Label(self, text="Default").grid(row=1, column=4, sticky=tk.W)
453             ttk.Label(self, text="User").grid(row=1, column=5, sticky=tk.W)
454
455             okButton = ttk.Button(self, text="OK", command=self.ok)
456             cancelButton = ttk.Button(self, text="Cancel", command=self.cancel)
457
458             self.namelist = tk.Listbox(self, selectmode=tk.SINGLE)
459             self.typelist = tk.Listbox(self, selectmode=tk.SINGLE)
460             self.minlist = tk.Listbox(self, selectmode=tk.SINGLE)
461             self.maxlist = tk.Listbox(self, selectmode=tk.SINGLE)
462             self.defaultlist = tk.Listbox(self, selectmode=tk.SINGLE)
463             self.valuelist = tk.Listbox(self, selectmode=tk.SINGLE)
464
465             self.descriptionText = tk.Text(self, state=tk.DISABLED, height=2)
466
467             ## Make a list of our list boxes to make it all easier to handle
468             self.listlist = [self.namelist, self.typelist, self.minlist, self.maxlist, self.defaultlist, self.valuelist]
469
470             scroll = tk.Scrollbar(self, orient=tk.VERTICAL, command=self.yview)
471
472             for box in self.listlist:
473                 box.config(width=0)
474                 box.config(yscrollcommand=scroll.set)
475                 box.bind("<MouseWheel>", self.mousewheel)
476                 box.bind("<Button-4>", self.mousewheel)
477                 box.bind("<Button-5>", self.mousewheel)
478                 box.bind("<<ListboxSelect>>", self.changeSelection)
479                 box.bind("<Double-Button>", self.doubleClick)
480                 box.config(exportselection=False)
481                 box.bind("<Down>", self.OnEntryUpDown)
482                 box.bind("<Up>", self.OnEntryUpDown)
483
484             scroll.grid(column=7, sticky=tk.N + tk.S)
485
486             i = 0
487             for box in self.listlist:
488                 box.grid(row=2, column=i, padx=0, sticky=tk.W + tk.E)
489                 i+=1
490
491             self.descriptionText.grid(row = 3, column=0, columnspan=4, sticky=tk.W + tk.E)
492             cancelButton.grid(column=5, row = 3, padx=5)
493             okButton.grid(column=4, row = 3, sticky=tk.E, padx=5)
494
495             # populate the list box with our config options
496             for conf in configuration_dictionary:
497                 self.namelist.insert(tk.END, conf['name'])
498                 s = conf['type']
499                 if s == "":
500                     s = "int"
501                 self.typelist.insert(tk.END, s)
502                 self.maxlist.insert(tk.END, conf['max'])
503                 self.minlist.insert(tk.END, conf['min'])
504                 self.defaultlist.insert(tk.END, conf['default'])
505
506                 # see if this config has a setting, our results member has this predefined from init
507                 val = self.results.get(conf['name'], CONFIG_UNSET)
508                 self.valuelist.insert(tk.END, val)
509                 if val != CONFIG_UNSET:
510                     self.valuelist.itemconfig(self.valuelist.size() - 1, {'bg':'green'})
511
512         def yview(self, *args):
513             for box in self.listlist:
514                 box.yview(*args)
515
516         def mousewheel(self, event):
517             if (event.num == 4):    # Linux encodes wheel as 'buttons' 4 and 5
518                 delta = -1
519             elif (event.num == 5):
520                 delta = 1
521             else:                   # Windows & OSX
522                 delta = event.delta
523
524             for box in self.listlist:
525                 box.yview("scroll", delta, "units")
526             return "break"
527
528         def changeSelection(self, evt):
529             box = evt.widget
530             sellist = box.curselection()
531
532             if sellist:
533                 index = int(sellist[0])
534                 config = self.namelist.get(index)
535                 # Now find the description for that config in the dictionary
536                 for conf in configuration_dictionary:
537                     if conf['name'] == config:
538                         self.descriptionText.config(state=tk.NORMAL)
539                         self.descriptionText.delete(1.0,tk.END)
540                         str = config + "\n" + conf['description']
541                         self.descriptionText.insert(1.0, str)
542                         self.descriptionText.config(state=tk.DISABLED)
543                         break
544                 # Set all the other list boxes to the same index
545                 for b in self.listlist:
546                     if b != box:
547                         b.selection_clear(0, tk.END)
548                         b.selection_set(index)
549
550         def OnEntryUpDown(self, event):
551             box = event.widget
552             selection = box.curselection()
553
554             if selection:
555                 index = int(selection[0])
556                 if event.keysym == 'Up':
557                     index -= 1
558                 elif event.keysym == 'Down':
559                     index += 1
560
561                 if 0 <= index < box.size():
562                     for b in self.listlist:
563                             b.selection_clear(0, tk.END)
564                             b.selection_set(index)
565                             b.see(index)
566
567         def doubleClick(self, evt):
568             box = evt.widget
569             index = int(box.curselection()[0])
570             config = self.namelist.get(index)
571             # Get the associated dict entry from our list of configs
572             for conf in configuration_dictionary:
573                 if conf['name'] == config:
574                     if (conf['type'] == 'bool'):
575                         result = EditBoolWindow(self, conf, self.valuelist.get(index)).get()
576                     elif (conf['type'] == 'int' or conf['type'] == ""): # "" defaults to int
577                         result = EditIntWindow(self, conf, self.valuelist.get(index)).get()
578                     elif conf['type'] == 'enum':
579                         result = EditEnumWindow(self, conf, self.valuelist.get(index)).get()
580
581                     # Update the valuelist with our new item
582                     self.valuelist.delete(index)
583                     self.valuelist.insert(index, result)
584                     if result != CONFIG_UNSET:
585                         self.valuelist.itemconfig(index, {'bg':'green'})
586                     break
587
588         def ok(self):
589             # Get the selections, and create a list of them
590             for i, val in enumerate(self.valuelist.get(0, tk.END)):
591                 if val != CONFIG_UNSET:
592                     self.results[self.namelist.get(i)] = val
593                 else:
594                     self.results.pop(self.namelist.get(i), None)
595
596             self.destroy()
597
598         def cancel(self):
599             self.destroy()
600
601         def get(self):
602             return self.results
603
604     class WirelessSettingsWindow(sd.Dialog):
605
606         def __init__(self, parent):
607             sd.Dialog.__init__(self, parent, "Wireless settings")
608             self.parent = parent
609
610         def body(self, master):
611             self.configure(background=GetBackground())
612             master.configure(background=GetBackground())
613             self.ssid = tk.StringVar()
614             self.password = tk.StringVar()
615
616             a = ttk.Label(master, text='SSID :', background=GetBackground())
617             a.grid(row=0, column=0, sticky=tk.E)
618             a.configure(background=GetBackground())
619             ttk.Entry(master, textvariable=self.ssid).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5)
620
621             ttk.Label(master, text='Password :').grid(row=1, column=0, sticky=tk.E)
622             ttk.Entry(master, textvariable=self.password).grid(row=1, column=1, sticky=tk.W+tk.E, padx=5)
623
624             self.transient(self.parent)
625             self.grab_set()
626
627         def ok(self):
628             self.grab_release()
629             self.destroy()
630
631         def cancel(self):
632             self.destroy()
633
634         def get(self):
635             return (self.ssid.get(), self.password.get())
636
637     # Our main window
638     class ProjectWindow(tk.Frame):
639
640         def __init__(self, parent, sdkpath, args):
641             tk.Frame.__init__(self, parent)
642             self.master = parent
643             self.sdkpath = sdkpath
644             self.init_window(args)
645             self.configs = dict()
646             self.ssid = str()
647             self.password = str()
648
649         def setState(self, thing, state):
650             for child in thing.winfo_children():
651                 child.configure(state=state)
652
653         def boardtype_change_callback(self, event):
654             boardtype = self.boardtype.get()
655             if boardtype == "pico_w":
656                 self.setState(self.picowSubframe, "enabled")
657             else:
658                 self.setState(self.picowSubframe, "disabled")
659
660         def wirelessSettings(self):
661             result = WirelessSettingsWindow(self)
662             self.ssid, self.password = result.get()
663
664         def init_window(self, args):
665             self.master.title("Raspberry Pi Pico Project Generator")
666             self.master.configure(bg=GetBackground())
667
668             optionsRow = 0
669
670             mainFrame = tk.Frame(self, bg=GetBackground()).grid(row=optionsRow, column=0, columnspan=6, rowspan=12)
671
672             # Need to keep a reference to the image or it will not appear.
673             self.logo = tk.PhotoImage(file=GetFilePath("logo_alpha.gif"))
674             logowidget = ttk.Label(mainFrame, image=self.logo, borderwidth=0, relief="solid").grid(row=0,column=0, columnspan=5, pady=10)
675
676             optionsRow += 2
677
678             namelbl = ttk.Label(mainFrame, text='Project Name :').grid(row=optionsRow, column=0, sticky=tk.E)
679             self.projectName = tk.StringVar()
680
681             if args.name != None:
682                 self.projectName.set(args.name)
683             else:
684                 self.projectName.set('ProjectName')
685
686             nameEntry = ttk.Entry(mainFrame, textvariable=self.projectName).grid(row=optionsRow, column=1, sticky=tk.W+tk.E, padx=5)
687
688             optionsRow += 1
689
690             locationlbl = ttk.Label(mainFrame, text='Location :').grid(row=optionsRow, column=0, sticky=tk.E)
691             self.locationName = tk.StringVar()
692             self.locationName.set(os.getcwd())
693             locationEntry = ttk.Entry(mainFrame, textvariable=self.locationName).grid(row=optionsRow, column=1, columnspan=3, sticky=tk.W+tk.E, padx=5)
694             locationBrowse = ttk.Button(mainFrame, text='Browse', command=self.browse).grid(row=3, column=4)
695
696             optionsRow += 1
697
698             ttk.Label(mainFrame, text = "Board Type :").grid(row=optionsRow, column=0, padx=4, sticky=tk.E)
699             self.boardtype = ttk.Combobox(mainFrame, values=boardtype_list, )
700             self.boardtype.grid(row=4, column=1, padx=4, sticky=tk.W+tk.E)
701             self.boardtype.set('pico')
702             self.boardtype.bind('<<ComboboxSelected>>',self.boardtype_change_callback)
703             optionsRow += 1
704
705             # Features section
706             featuresframe = ttk.LabelFrame(mainFrame, text="Library Options", relief=tk.RIDGE, borderwidth=2)
707             featuresframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=5, ipadx=5, padx=5, pady=5, sticky=tk.E+tk.W)
708
709             s = (len(features_list)/3)
710
711             self.feature_checkbox_vars = []
712             row = 0
713             col = 0
714             for i in features_list:
715                 var = tk.StringVar(value='') # Off by default for the moment
716                 c = features_list[i][GUI_TEXT]
717                 cb = ttk.Checkbutton(featuresframe, text = c, var=var, onvalue=i, offvalue='')
718                 cb.grid(row=row, column=col, padx=15, pady=2, ipadx=1, ipady=1, sticky=tk.E+tk.W)
719                 self.feature_checkbox_vars.append(var)
720                 row+=1
721                 if row >= s:
722                     col+=1
723                     row = 0
724
725             optionsRow += 5
726
727             # PicoW options section
728             self.picowSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Pico Wireless Options")
729             self.picowSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
730             self.pico_wireless = tk.StringVar()
731
732             col = 0
733             row = 0
734             for i in picow_options_list:
735                 rb = ttk.Radiobutton(self.picowSubframe, text=picow_options_list[i][GUI_TEXT], variable=self.pico_wireless, val=i)
736                 rb.grid(row=row, column=col,  padx=15, pady=1, sticky=tk.E+tk.W)
737                 col+=1
738                 if col == 3:
739                     col=0
740                     row+=1
741
742             # DOnt actually need any settings at the moment.
743             # ttk.Button(self.picowSubframe, text='Settings', command=self.wirelessSettings).grid(row=0, column=4, padx=5, pady=2, sticky=tk.E)
744
745             self.setState(self.picowSubframe, "disabled")
746
747             optionsRow += 3
748
749             # output options section
750             ooptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Console Options")
751             ooptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
752
753             self.wantUART = tk.IntVar()
754             self.wantUART.set(args.uart)
755             ttk.Checkbutton(ooptionsSubframe, text="Console over UART", variable=self.wantUART).grid(row=0, column=0, padx=4, sticky=tk.W)
756
757             self.wantUSB = tk.IntVar()
758             self.wantUSB.set(args.usb)
759             ttk.Checkbutton(ooptionsSubframe, text="Console over USB (Disables other USB use)", variable=self.wantUSB).grid(row=0, column=1, padx=4, sticky=tk.W)
760
761             optionsRow += 2
762
763             # Code options section
764             coptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Code Options")
765             coptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=3, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
766
767             self.wantExamples = tk.IntVar()
768             self.wantExamples.set(args.examples)
769             ttk.Checkbutton(coptionsSubframe, text="Add examples for Pico library", variable=self.wantExamples).grid(row=0, column=0, padx=4, sticky=tk.W)
770
771             self.wantRunFromRAM = tk.IntVar()
772             self.wantRunFromRAM.set(args.runFromRAM)
773             ttk.Checkbutton(coptionsSubframe, text="Run from RAM", variable=self.wantRunFromRAM).grid(row=0, column=1, padx=4, sticky=tk.W)
774
775             self.wantCPP = tk.IntVar()
776             self.wantCPP.set(args.cpp)
777             ttk.Checkbutton(coptionsSubframe, text="Generate C++", variable=self.wantCPP).grid(row=0, column=3, padx=4, sticky=tk.W)
778
779             ttk.Button(coptionsSubframe, text="Advanced...", command=self.config).grid(row=0, column=4, sticky=tk.E)
780
781             self.wantCPPExceptions = tk.IntVar()
782             self.wantCPPExceptions.set(args.cppexceptions)
783             ttk.Checkbutton(coptionsSubframe, text="Enable C++ exceptions", variable=self.wantCPPExceptions).grid(row=1, column=0, padx=4, sticky=tk.W)
784
785             self.wantCPPRTTI = tk.IntVar()
786             self.wantCPPRTTI.set(args.cpprtti)
787             ttk.Checkbutton(coptionsSubframe, text="Enable C++ RTTI", variable=self.wantCPPRTTI).grid(row=1, column=1, padx=4, sticky=tk.W)
788
789             optionsRow += 3
790
791             # Build Options section
792
793             boptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Build Options")
794             boptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
795
796             self.wantBuild = tk.IntVar()
797             self.wantBuild.set(args.build)
798             ttk.Checkbutton(boptionsSubframe, text="Run build after generation", variable=self.wantBuild).grid(row=0, column=0, padx=4, sticky=tk.W)
799
800             self.wantOverwrite = tk.IntVar()
801             self.wantOverwrite.set(args.overwrite)
802             ttk.Checkbutton(boptionsSubframe, text="Overwrite existing projects", variable=self.wantOverwrite).grid(row=0, column=1, padx=4, sticky=tk.W)
803
804             optionsRow += 2
805             
806             # IDE Options section
807
808             vscodeoptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="IDE Options")
809             vscodeoptionsSubframe.grid_columnconfigure(2, weight=1)
810             vscodeoptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
811
812             self.wantVSCode = tk.IntVar()
813             if args.project is None:
814                 self.wantVSCode.set(False)
815             else:
816                 self.wantVSCode.set('vscode' in args.project)
817             ttk.Checkbutton(vscodeoptionsSubframe, text="Create VSCode project", variable=self.wantVSCode).grid(row=0, column=0, padx=4, sticky=tk.W)
818
819             ttk.Label(vscodeoptionsSubframe, text = "     Debugger:").grid(row=0, column=1, padx=4, sticky=tk.W)
820
821             self.debugger = ttk.Combobox(vscodeoptionsSubframe, values=debugger_list, state="readonly")
822             self.debugger.grid(row=0, column=2, padx=4, sticky=tk.EW)
823             self.debugger.current(args.debugger)
824
825             optionsRow += 2
826
827             # OK, Cancel, Help section
828             # creating buttons
829             QuitButton = ttk.Button(mainFrame, text="Quit", command=self.quit).grid(row=optionsRow, column=4, stick=tk.E, padx=10, pady=5)
830             OKButton = ttk.Button(mainFrame, text="OK", command=self.OK).grid(row=optionsRow, column=3, padx=4, pady=5, sticky=tk.E)
831
832             # TODO help not implemented yet
833             # HelpButton = ttk.Button(mainFrame, text="Help", command=self.help).grid(row=optionsRow, column=0, pady=5)
834
835             # You can set a default path here, replace the string with whereever you want.
836             # self.locationName.set('/home/pi/pico_projects')
837
838         def GetFeatures(self):
839             features = []
840
841             i = 0
842             for cb in self.feature_checkbox_vars:
843                 s = cb.get()
844                 if s != '':
845                     features.append(s)
846
847             picow_extra = self.pico_wireless.get()
848
849             if picow_extra != 'picow_none':
850                 features.append(picow_extra)
851
852             return features
853
854         def quit(self):
855             # TODO Check if we want to exit here
856             sys.exit(0)
857
858         def OK(self):
859             # OK, grab all the settings from the page, then call the generators
860             projectPath = self.locationName.get()
861             features = self.GetFeatures()
862             projects = list()
863             if (self.wantVSCode.get()):
864                 projects.append("vscode")
865
866             params={
867                     'sdkPath'       : self.sdkpath,
868                     'projectRoot'   : Path(projectPath),
869                     'projectName'   : self.projectName.get(),
870                     'wantGUI'       : True,
871                     'wantOverwrite' : self.wantOverwrite.get(),
872                     'wantBuild'     : self.wantBuild.get(),
873                     'boardtype'     : self.boardtype.get(),
874                     'features'      : features,
875                     'projects'      : projects,
876                     'configs'       : self.configs,
877                     'wantRunFromRAM': self.wantRunFromRAM.get(),
878                     'wantExamples'  : self.wantExamples.get(),
879                     'wantUART'      : self.wantUART.get(),
880                     'wantUSB'       : self.wantUSB.get(),
881                     'wantCPP'       : self.wantCPP.get(),
882                     'debugger'      : self.debugger.current(),
883                     'exceptions'    : self.wantCPPExceptions.get(),
884                     'rtti'          : self.wantCPPRTTI.get(),
885                     'ssid'          : self.ssid,
886                     'password'      : self.password,
887                     }
888
889             DoEverything(self, params)
890
891         def browse(self):
892             name = fd.askdirectory()
893             self.locationName.set(name)
894
895         def help(self):
896             print("Help TODO")
897
898         def config(self):
899             # Run the configuration window
900             self.configs = ConfigurationWindow(self, self.configs).get()
901
902 def CheckPrerequisites():
903     global isMac, isWindows
904     isMac = (platform.system() == 'Darwin')
905     isWindows = (platform.system() == 'Windows')
906
907     # Do we have a compiler?
908     return shutil.which(COMPILER_NAME, 1, os.environ["Path" if isWindows else "PATH"])
909
910
911 def CheckSDKPath(gui):
912     sdkPath = os.getenv('PICO_SDK_PATH')
913
914     if sdkPath == None:
915         m = 'Unable to locate the Raspberry Pi Pico SDK, PICO_SDK_PATH is not set'
916         if gui and ENABLE_TK_GUI:
917             RunWarning(m)
918         else:
919             print(m)
920     elif not os.path.isdir(sdkPath):
921         m = 'Unable to locate the Raspberry Pi Pico SDK, PICO_SDK_PATH does not point to a directory'
922         if gui and ENABLE_TK_GUI:
923             RunWarning(m)
924         else:
925             print(m)
926         sdkPath = None
927
928     return sdkPath
929
930 def GetFilePath(filename):
931     if os.path.islink(__file__):
932         script_file = os.readlink(__file__)
933     else:
934         script_file = __file__
935     return os.path.join(os.path.dirname(script_file), filename)
936
937 def ParseCommandLine():
938     debugger_flags = ', '.join('{} = {}'.format(i, v) for i, v in enumerate(debugger_list))
939     parser = argparse.ArgumentParser(description='Pico Project generator')
940     parser.add_argument("name", nargs="?", help="Name of the project")
941     parser.add_argument("-t", "--tsv", help="Select an alternative pico_configs.tsv file", default=GetFilePath("pico_configs.tsv"))
942     parser.add_argument("-o", "--output", help="Set an alternative CMakeList.txt filename", default="CMakeLists.txt")
943     parser.add_argument("-x", "--examples", action='store_true', help="Add example code for the Pico standard library")
944     parser.add_argument("-l", "--list", action='store_true', help="List available features")
945     parser.add_argument("-c", "--configs", action='store_true', help="List available project configuration items")
946     parser.add_argument("-f", "--feature", action='append', help="Add feature to generated project")
947     parser.add_argument("-over", "--overwrite", action='store_true', help="Overwrite any existing project AND files")
948     parser.add_argument("-b", "--build", action='store_true', help="Build after project created")
949     parser.add_argument("-g", "--gui", action='store_true', help="Run a GUI version of the project generator")
950     parser.add_argument("-p", "--project", action='append', help="Generate projects files for IDE. Options are: vscode")
951     parser.add_argument("-r", "--runFromRAM", action='store_true', help="Run the program from RAM rather than flash")
952     parser.add_argument("-uart", "--uart", action='store_true', default=1, help="Console output to UART (default)")
953     parser.add_argument("-nouart", "--nouart", action='store_true', default=0, help="Disable console output to UART")
954     parser.add_argument("-usb", "--usb", action='store_true', help="Console output to USB (disables other USB functionality")
955     parser.add_argument("-cpp", "--cpp", action='store_true', default=0, help="Generate C++ code")
956     parser.add_argument("-cpprtti", "--cpprtti", action='store_true', default=0, help="Enable C++ RTTI (Uses more memory)")
957     parser.add_argument("-cppex", "--cppexceptions", action='store_true', default=0, help="Enable C++ exceptions (Uses more memory)")
958     parser.add_argument("-d", "--debugger", type=int, help="Select debugger ({})".format(debugger_flags), default=0)
959     parser.add_argument("-board", "--boardtype", action="store", default='pico', help="Select board type (see --boardlist for available boards)")
960     parser.add_argument("-bl", "--boardlist", action="store_true", help="List available board types")
961     parser.add_argument("-cp", "--cpath", help="Override default VSCode compiler path")
962     parser.add_argument("-root", "--projectRoot", help="Override default project root where the new project will be created")
963     parser.add_argument("-sdkVersion", "--sdkVersion", help="Pico SDK version to use (required)")
964     parser.add_argument("-tcVersion", "--toolchainVersion", help="ARM Embeded Toolchain version to use (required)")
965     parser.add_argument("-np", "--ninjaPath", help="Ninja path")
966     parser.add_argument("-cmp", "--cmakePath", help="CMake path")
967     parser.add_argument("-cupy", "--customPython", action='store_true', help="Custom python path used to execute the script.")
968
969     return parser.parse_args()
970
971
972 def GenerateMain(folder, projectName, features, cpp):
973
974     if cpp:
975         filename = Path(folder) / (projectName + '.cpp')
976     else:
977         filename = Path(folder) / (projectName + '.c')
978
979     file = open(filename, 'w')
980
981     main = ('#include <stdio.h>\n'
982             '#include "pico/stdlib.h"\n'
983             )
984     file.write(main)
985
986     if (features):
987
988         # Add any includes
989         for feat in features:
990             if (feat in features_list):
991                 o = f'#include "{features_list[feat][H_FILE]}"\n'
992                 file.write(o)
993             if (feat in stdlib_examples_list):
994                 o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n'
995                 file.write(o)
996             if (feat in picow_options_list):
997                 o = f'#include "{picow_options_list[feat][H_FILE]}"\n'
998                 file.write(o)
999
1000         file.write('\n')
1001
1002         # Add any defines
1003         for feat in features:
1004             if (feat in code_fragments_per_feature):
1005                 for s in code_fragments_per_feature[feat][DEFINES]:
1006                     file.write(s)
1007                     file.write('\n')
1008                 file.write('\n')
1009
1010     main = ('\n\n'
1011             'int main()\n'
1012             '{\n'
1013             '    stdio_init_all();\n\n'
1014             )
1015
1016     if (features):
1017         # Add any initialisers
1018         indent = 4
1019         for feat in features:
1020             if (feat in code_fragments_per_feature):
1021                 for s in code_fragments_per_feature[feat][INITIALISERS]:
1022                     main += (" " * indent)
1023                     main += s
1024                     main += '\n'
1025             main += '\n'
1026
1027     main += ('    puts("Hello, world!");\n\n'
1028              '    return 0;\n'
1029              '}\n'
1030             )
1031
1032     file.write(main)
1033
1034     file.close()
1035
1036
1037 def GenerateCMake(folder, params):
1038    
1039     filename = Path(folder) / CMAKELIST_FILENAME
1040     projectName = params['projectName']
1041     board_type = params['boardtype']
1042
1043     # OK, for the path, CMake will accept forward slashes on Windows, and thats
1044     # seemingly a bit easier to handle than the backslashes
1045     p = str(params['sdkPath']).replace('\\','/')
1046
1047     cmake_header1 = (f"# Generated Cmake Pico project file\n\n"
1048                  "cmake_minimum_required(VERSION 3.13)\n\n"
1049                  "set(CMAKE_C_STANDARD 11)\n"
1050                  "set(CMAKE_CXX_STANDARD 17)\n\n"
1051                  "# Initialise pico_sdk from installed location\n"
1052                  "# (note this can come from environment, CMake cache etc)\n\n"
1053                  "# == DO NEVER EDIT THE NEXT LINES for RaspberryPiPico VS Code Extension to work == \n"
1054                  "if(WIN32)\n"
1055                  "   set(USERHOME $ENV{USERPROFILE})\n"
1056                  "else()\n"
1057                  "    set(USERHOME $ENV{HOME})\n"
1058                  "endif()\n"
1059                  f"set(PICO_SDK_PATH {cmakeSdkPath(params['sdkVersion'])})\n"
1060                  f"set(PICO_TOOLCHAIN_PATH {cmakeToolchainPath(params['toolchainVersion'])})\n"
1061                  "if(WIN32)\n"
1062                  f"    set(pico-sdk-tools_DIR {cmakeToolsPath(params['sdkVersion'])})\n"
1063                  "    include(${pico-sdk-tools_DIR}/pico-sdk-tools-config.cmake)\n"
1064                  "    include(${pico-sdk-tools_DIR}/pico-sdk-tools-config-version.cmake)\n"
1065                  "endif()\n"
1066                  "# ====================================================================================\n"
1067                  f"set(PICO_BOARD {board_type} CACHE STRING \"Board type\")\n\n"
1068                  "# Pull in Raspberry Pi Pico SDK (must be before project)\n"
1069                  "include(pico_sdk_import.cmake)\n\n"
1070                  "if (PICO_SDK_VERSION_STRING VERSION_LESS \"1.4.0\")\n"
1071                  "  message(FATAL_ERROR \"Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}\")\n"
1072                  "endif()\n\n"
1073                  f"project({projectName} C CXX ASM)\n"
1074                 )
1075     
1076     cmake_header3 = (
1077                 "\n# Initialise the Raspberry Pi Pico SDK\n"
1078                 "pico_sdk_init()\n\n"
1079                 "# Add executable. Default name is the project name, version 0.1\n\n"
1080                 )
1081
1082
1083     file = open(filename, 'w')
1084
1085     file.write(cmake_header1)
1086
1087     if params['exceptions']:
1088         file.write("\nset(PICO_CXX_ENABLE_EXCEPTIONS 1)\n")
1089
1090     if params['rtti']:
1091         file.write("\nset(PICO_CXX_ENABLE_RTTI 1)\n")
1092
1093     file.write(cmake_header3)
1094
1095     # add the preprocessor defines for overall configuration
1096     if params['configs']:
1097         file.write('# Add any PICO_CONFIG entries specified in the Advanced settings\n')
1098         for c, v in params['configs'].items():
1099             if v == "True":
1100                 v = "1"
1101             elif v == "False":
1102                 v = "0"
1103             file.write(f'add_compile_definitions({c} = {v})\n')
1104         file.write('\n')
1105
1106     # No GUI/command line to set a different executable name at this stage
1107     executableName = projectName
1108
1109     if params['wantCPP']:
1110         file.write(f'add_executable({projectName} {projectName}.cpp )\n\n')
1111     else:
1112         file.write(f'add_executable({projectName} {projectName}.c )\n\n')
1113
1114     file.write(f'pico_set_program_name({projectName} "{executableName}")\n')
1115     file.write(f'pico_set_program_version({projectName} "0.1")\n\n')
1116
1117     if params['wantRunFromRAM']:
1118         file.write(f'# no_flash means the target is to run from RAM\n')
1119         file.write(f'pico_set_binary_type({projectName} no_flash)\n\n')
1120
1121     # Console output destinations
1122     if params['wantUART']:
1123         file.write(f'pico_enable_stdio_uart({projectName} 1)\n')
1124     else:
1125         file.write(f'pico_enable_stdio_uart({projectName} 0)\n')
1126
1127     if params['wantUSB']:
1128         file.write(f'pico_enable_stdio_usb({projectName} 1)\n\n')
1129     else:
1130         file.write(f'pico_enable_stdio_usb({projectName} 0)\n\n')
1131
1132     # If we need wireless, check for SSID and password
1133     # removed for the moment as these settings are currently only needed for the pico-examples
1134     # but may be required in here at a later date.
1135     if False:
1136         if 'ssid' in params or 'password' in params:
1137             file.write('# Add any wireless access point information\n')
1138             file.write(f'target_compile_definitions({projectName} PRIVATE\n')
1139             if 'ssid' in params:
1140                 file.write(f'WIFI_SSID=\" {params["ssid"]} \"\n')
1141             else:
1142                 file.write(f'WIFI_SSID=\"${WIFI_SSID}\"')
1143
1144             if 'password' in params:
1145                 file.write(f'WIFI_PASSWORD=\"{params["password"]}\"\n')
1146             else:
1147                 file.write(f'WIFI_PASSWORD=\"${WIFI_PASSWORD}\"')
1148             file.write(')\n\n')
1149
1150     # Standard libraries
1151     file.write('# Add the standard library to the build\n')
1152     file.write(f'target_link_libraries({projectName}\n')
1153     file.write("        " + STANDARD_LIBRARIES)
1154     file.write(')\n\n')
1155
1156     # Standard include directories
1157     file.write('# Add the standard include files to the build\n')
1158     file.write(f'target_include_directories({projectName} PRIVATE\n')
1159     file.write("  ${CMAKE_CURRENT_LIST_DIR}\n")
1160     file.write("  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required\n")
1161     file.write(')\n\n')
1162
1163     # Selected libraries/features
1164     if (params['features']):
1165         file.write('# Add any user requested libraries\n')
1166         file.write(f'target_link_libraries({projectName} \n')
1167         for feat in params['features']:
1168             if (feat in features_list):
1169                 file.write("        " + features_list[feat][LIB_NAME] + '\n')
1170             if (feat in picow_options_list):
1171                 file.write("        " + picow_options_list[feat][LIB_NAME] + '\n')
1172         file.write('        )\n\n')
1173
1174     file.write(f'pico_add_extra_outputs({projectName})\n\n')
1175
1176     file.close()
1177
1178
1179 # Generates the requested project files, if any
1180 def generateProjectFiles(projectPath, projectName, sdkPath, projects, debugger, sdkVersion, toolchainVersion, ninjaPath, cmakePath, customPython):
1181
1182     oldCWD = os.getcwd()
1183
1184     os.chdir(projectPath)
1185
1186     debugger = debugger_config_list[debugger]
1187     gdbPath =  Path(codeToolchainPath(toolchainVersion)+"/bin/arm-none-eabi-gdb").as_posix() if isWindows else "gdb-multiarch"
1188     # Need to escape windows files paths backslashes
1189     # TODO: env in currently not supported in compilerPath var
1190     #cPath = f"${{env:PICO_TOOLCHAIN_PATH_{envSuffix}}}" + os.path.sep + os.path.basename(str(compilerPath).replace('\\', '\\\\' ))
1191     cPath = compilerPath.as_posix()
1192
1193     # if this is a path in the .pico-sdk homedir tell the settings to use the homevar
1194     user_home = os.path.expanduser("~").replace("\\", "/")
1195     use_home_var = f"{user_home}/.pico-sdk" in ninjaPath
1196
1197     for p in projects :
1198         if p == 'vscode':
1199             launch = f'''{{
1200     "version": "0.2.0",
1201     "configurations": [
1202         {{
1203             "name": "Pico Debug (Cortex-Debug)",
1204             "cwd": "${{workspaceRoot}}",
1205             "executable": "${{command:raspberry-pi-pico.launchTargetPath}}",
1206             "request": "launch",
1207             "type": "cortex-debug",
1208             "servertype": "openocd",
1209             "gdbPath": "{gdbPath}",
1210             "device": "RP2040",
1211             "configFiles": [
1212                 "interface/{debugger}",
1213                 "target/rp2040.cfg"
1214             ],
1215             "svdFile": "{codeSdkPath(sdkVersion)}/src/rp2040/hardware_regs/rp2040.svd",
1216             "runToEntryPoint": "main",
1217             // Give restart the same functionality as runToEntryPoint - main
1218             "postRestartCommands": [
1219                 "break main",
1220                 "continue"
1221             ],
1222             "openOCDLaunchCommands": [
1223                 "adapter speed 1000"
1224             ],
1225             "preLaunchTask": "Compile Project"
1226         }},
1227         {{
1228             "name": "Pico Debug (Cortex-Debug with external OpenOCD)",
1229             "cwd": "${{workspaceRoot}}",
1230             "executable": "${{command:raspberry-pi-pico.launchTargetPath}}",
1231             "request": "launch",
1232             "type": "cortex-debug",
1233             "servertype": "external",
1234             "gdbTarget": "localhost:3333",
1235             "gdbPath": "{gdbPath}",
1236             "device": "RP2040",
1237             "svdFile": "{codeSdkPath(sdkVersion)}/src/rp2040/hardware_regs/rp2040.svd",
1238             "runToEntryPoint": "main",
1239             // Give restart the same functionality as runToEntryPoint - main
1240             "postRestartCommands": [
1241                 "break main",
1242                 "continue"
1243             ],
1244             "preLaunchTask": "Compile Project"
1245         }},
1246         {{
1247             "name": "Pico Debug (C++ Debugger)",
1248             "type": "cppdbg",
1249             "request": "launch",
1250             "cwd": "${{workspaceRoot}}",
1251             "program": "${{command:raspberry-pi-pico.launchTargetPath}}",
1252             "MIMode": "gdb",
1253             "miDebuggerPath": "{gdbPath}",
1254             "miDebuggerServerAddress": "localhost:3333",
1255             "debugServerPath": "openocd",
1256             "debugServerArgs": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \\"adapter speed 1000\\"",
1257             "serverStarted": "Listening on port .* for gdb connections",
1258             "filterStderr": true,
1259             "stopAtEntry": true,
1260             "hardwareBreakpoints": {{
1261                 "require": true,
1262                 "limit": 4
1263             }},
1264             "preLaunchTask": "Flash",
1265             "svdPath": "{codeSdkPath(sdkVersion)}/src/rp2040/hardware_regs/rp2040.svd"
1266         }},
1267     ]
1268 }}
1269 '''
1270
1271             properties = f'''{{
1272     "configurations": [
1273         {{
1274             "name": "Pico",
1275             "includePath": [
1276                 "${{workspaceFolder}}/**",
1277                 "{propertiesSdkPath(sdkVersion)}/**"
1278             ],
1279             "defines": [],
1280             "compilerPath": "{cPath}",
1281             "cStandard": "c17",
1282             "cppStandard": "c++14",
1283             "intelliSenseMode": "linux-gcc-arm"
1284         }}
1285     ],
1286     "version": 4
1287 }}
1288 '''
1289
1290             pythonExe = sys.executable.replace("\\", "/").replace(user_home, "${HOME}") if use_home_var else sys.executable
1291
1292             # settings
1293             settings = f'''{{
1294     "cmake.statusbar.visibility": "hidden",
1295     "cmake.configureOnEdit": false,
1296     "cmake.automaticReconfigure": false,
1297     "cmake.configureOnOpen": false,
1298     "cmake.generator": "Ninja",
1299     "cmake.cmakePath": "{cmakePath}",
1300     "raspberry-pi-pico.cmakeAutoConfigure": true,
1301     "raspberry-pi-pico.cmakePath": "{cmakePath.replace(user_home, "${HOME}") if use_home_var else cmakePath}",
1302     "raspberry-pi-pico.ninjaPath": "{ninjaPath.replace(user_home, "${HOME}") if use_home_var else ninjaPath}"'''
1303
1304             if customPython:
1305                 settings += f''',
1306     "raspberry-pi-pico.python3Path": "{pythonExe}"'''
1307                 
1308             settings += '\n}\n'
1309
1310             # extensions
1311             extensions = f'''{{
1312     "recommendations": [
1313         "marus25.cortex-debug",
1314         "ms-vscode.cpptools",
1315         "ms-vscode.cpptools-extension-pack",
1316         "ms-vscode.vscode-serial-monitor",
1317         "paulober.raspberry-pi-pico",
1318     ]
1319 }}
1320 '''
1321             tasks = f'''{{
1322     "version": "2.0.0",
1323     "tasks": [
1324         {{
1325             "label": "Compile Project",
1326             "type": "shell",
1327             "command": "{ninjaPath.replace(user_home, "${userHome}") if use_home_var else ninjaPath}",
1328             "args": ["-C", "${{workspaceFolder}}/build"],
1329             "group": "build",
1330             "presentation": {{
1331                 "reveal": "always",
1332                 "panel": "dedicated"
1333             }},
1334             "problemMatcher": "$gcc"
1335         }}
1336     ]
1337 }}
1338 '''
1339
1340             # Create a build folder, and run our cmake project build from it
1341             if not os.path.exists(VSCODE_FOLDER):
1342                 os.mkdir(VSCODE_FOLDER)
1343
1344             os.chdir(VSCODE_FOLDER)
1345
1346             file = open(VSCODE_TASKS_FILENAME, 'w')
1347             file.write(tasks)
1348             file.close()
1349
1350             filename = VSCODE_LAUNCH_FILENAME
1351             file = open(filename, 'w')
1352             file.write(launch)
1353             file.close()
1354
1355             file = open(VSCODE_C_PROPERTIES_FILENAME, 'w')
1356             file.write(properties)
1357             file.close()
1358
1359             file = open(VSCODE_SETTINGS_FILENAME, 'w')
1360             file.write(settings)
1361             file.close()
1362
1363             file = open(VSCODE_EXTENSIONS_FILENAME, 'w')
1364             file.write(extensions)
1365             file.close()
1366
1367         else :
1368             print('Unknown project type requested')
1369
1370     os.chdir(oldCWD)
1371
1372
1373 def LoadConfigurations():
1374     try:
1375         with open(args.tsv) as tsvfile:
1376             reader = csv.DictReader(tsvfile, dialect='excel-tab')
1377             for row in reader:
1378                 configuration_dictionary.append(row)
1379     except:
1380         print("No Pico configurations file found. Continuing without")
1381
1382 def LoadBoardTypes(sdkPath):
1383     # Scan the boards folder for all header files, extract filenames, and make a list of the results
1384     # default folder is <PICO_SDK_PATH>/src/boards/include/boards/*
1385     # If the PICO_BOARD_HEADER_DIRS environment variable is set, use that as well
1386
1387     loc = sdkPath / "src/boards/include/boards"
1388     boards=[]
1389     for x in Path(loc).iterdir():
1390         if x.suffix == '.h':
1391             boards.append(x.stem)
1392
1393     loc = os.getenv('PICO_BOARD_HEADER_DIRS')
1394
1395     if loc != None:
1396         for x in Path(loc).iterdir():
1397             if x.suffix == '.h':
1398                 boards.append(x.stem)
1399
1400     return boards
1401
1402 def DoEverything(parent, params):
1403
1404     if not os.path.exists(params['projectRoot']):
1405         if params['wantGUI'] and ENABLE_TK_GUI:
1406             mb.showerror('Raspberry Pi Pico Project Generator', 'Invalid project path. Select a valid path and try again')
1407             return
1408         else:
1409             print('Invalid project path')
1410             sys.exit(-1)
1411
1412     oldCWD = os.getcwd()
1413     os.chdir(params['projectRoot'])
1414
1415     # Create our project folder as subfolder
1416     os.makedirs(params['projectName'], exist_ok=True)
1417
1418     os.chdir(params['projectName'])
1419
1420     projectPath = params['projectRoot'] / params['projectName']
1421
1422     # First check if there is already a project in the folder
1423     # If there is we abort unless the overwrite flag it set
1424     if os.path.exists(CMAKELIST_FILENAME):
1425         if not params['wantOverwrite'] :
1426             if params['wantGUI'] and ENABLE_TK_GUI:
1427                 # We can ask the user if they want to overwrite
1428                 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')
1429                 if y != 'yes':
1430                     return
1431             else:
1432                 print('There already appears to be a project in this folder. Use the --overwrite option to overwrite the existing project')
1433                 sys.exit(-1)
1434
1435         # We should really confirm the user wants to overwrite
1436         #print('Are you sure you want to overwrite the existing project files? (y/N)')
1437         #c = input().split(" ")[0]
1438         #if c != 'y' and c != 'Y' :
1439         #    sys.exit(0)
1440
1441     # Copy the SDK finder cmake file to our project folder
1442     # Can be found here <PICO_SDK_PATH>/external/pico_sdk_import.cmake
1443     shutil.copyfile(params['sdkPath'] / 'external' / 'pico_sdk_import.cmake', projectPath / 'pico_sdk_import.cmake' )
1444
1445     if params['features']:
1446         features_and_examples = params['features'][:]
1447     else:
1448         features_and_examples= []
1449
1450     if params['wantExamples']:
1451         features_and_examples = list(stdlib_examples_list.keys()) + features_and_examples
1452
1453     GenerateMain('.', params['projectName'], features_and_examples, params['wantCPP'])
1454
1455     GenerateCMake('.', params)
1456
1457     # If we have any ancilliary files, copy them to our project folder
1458     # Currently only the picow with lwIP support needs an extra file, so just check that list
1459     for feat in features_and_examples:
1460         if feat in picow_options_list:
1461             if picow_options_list[feat][ANCILLARY_FILE] != "":
1462                 shutil.copy(sourcefolder + "/" + picow_options_list[feat][ANCILLARY_FILE], projectPath / picow_options_list[feat][ANCILLARY_FILE])
1463
1464     # Create a build folder, and run our cmake project build from it
1465     if not os.path.exists('build'):
1466         os.mkdir('build')
1467
1468     os.chdir('build')
1469
1470     # If we are overwriting a previous project, we should probably clear the folder, but that might delete something the users thinks is important, so
1471     # for the moment, just delete the CMakeCache.txt file as certain changes may need that to be recreated.
1472
1473     if os.path.exists(CMAKECACHE_FILENAME):
1474         os.remove(CMAKECACHE_FILENAME)
1475
1476     cpus = os.cpu_count()
1477     if cpus == None:
1478         cpus = 1
1479
1480     if isWindows:
1481         if shutil.which("ninja") or (params["ninjaPath"] != None and params["ninjaPath"] != ""):
1482             # When installing SDK version 1.5.0 on windows with installer pico-setup-windows-x64-standalone.exe, ninja is used 
1483             cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug -G Ninja ..'
1484             makeCmd = params['ninjaPath'] + ' '        
1485         else:
1486             # Everything else assume nmake
1487             cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug -G "NMake Makefiles" ..'
1488             makeCmd = 'nmake '
1489     else:
1490         # Ninja now works OK under Linux, so if installed use it by default. It's faster.
1491         if shutil.which("ninja") or (params["ninjaPath"] != None and params["ninjaPath"] != ""):
1492             cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug -G Ninja ..'
1493             makeCmd = params['ninjaPath'] + ' '
1494         else:
1495             cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug ..'
1496             makeCmd = 'make -j' + str(cpus)
1497
1498     if params['wantGUI'] and ENABLE_TK_GUI:
1499         RunCommandInWindow(parent, cmakeCmd)
1500     else:
1501         os.system(cmakeCmd)
1502
1503     if params['projects']:
1504         generateProjectFiles(
1505             projectPath, 
1506             params['projectName'], 
1507             params['sdkPath'], 
1508             params['projects'], 
1509             params['debugger'], 
1510             params["sdkVersion"], 
1511             params["toolchainVersion"], 
1512             params["ninjaPath"], 
1513             params["cmakePath"],
1514             params["customPython"])
1515
1516     if params['wantBuild']:
1517         if params['wantGUI'] and ENABLE_TK_GUI:
1518             RunCommandInWindow(parent, makeCmd)
1519         else:
1520             os.system(makeCmd)
1521             print('\nIf the application has built correctly, you can now transfer it to the Raspberry Pi Pico board')
1522
1523     os.chdir(oldCWD)
1524
1525
1526 ###################################################################################
1527 # main execution starteth here
1528
1529 sourcefolder = os.path.dirname(os.path.abspath(__file__))
1530
1531 args = ParseCommandLine()
1532
1533 if args.nouart:
1534     args.uart = False
1535
1536 if args.debugger > len(debugger_list) - 1:
1537     args.debugger = 0
1538
1539 # Check we have everything we need to compile etc
1540 c = CheckPrerequisites()
1541
1542 ## TODO Do both warnings in the same error message so user does have to keep coming back to find still more to do
1543
1544 if c == None:
1545     m = f'Unable to find the `{COMPILER_NAME}` compiler\n'
1546     m +='You will need to install an appropriate compiler to build a Raspberry Pi Pico project\n'
1547     m += 'See the Raspberry Pi Pico documentation for how to do this on your particular platform\n'
1548
1549     if args.gui and ENABLE_TK_GUI:
1550         RunWarning(m)
1551     else:
1552         print(m)
1553     sys.exit(-1)
1554
1555 if args.name == None and not args.gui and not args.list and not args.configs and not args.boardlist:
1556     print("No project name specfied\n")
1557     sys.exit(-1)
1558
1559 # Check if we were provided a compiler path, and override the default if so
1560 if args.cpath:
1561     compilerPath = Path(args.cpath)
1562 elif args.toolchainVersion:
1563     compilerPath = Path(propertiesToolchainPath(args.toolchainVersion)+"/bin/"+COMPILER_NAME)
1564 else:
1565     compilerPath = Path(c)
1566
1567 # load/parse any configuration dictionary we may have
1568 LoadConfigurations()
1569
1570 p = CheckSDKPath(args.gui)
1571
1572 if p == None:
1573     sys.exit(-1)
1574
1575 sdkPath = Path(p)
1576
1577 boardtype_list = LoadBoardTypes(sdkPath)
1578 boardtype_list.sort()
1579
1580 if args.gui and ENABLE_TK_GUI:
1581     RunGUI(sdkPath, args) # does not return, only exits
1582
1583 projectRoot = Path(os.getcwd()) if not args.projectRoot else Path(args.projectRoot)
1584
1585 if args.list or args.configs or args.boardlist:
1586     if args.list:
1587         print("Available project features:\n")
1588         for feat in features_list:
1589             print(feat.ljust(6), '\t', features_list[feat][GUI_TEXT])
1590         print('\n')
1591
1592     if args.configs:
1593         print("Available project configuration items:\n")
1594         for conf in configuration_dictionary:
1595             print(conf['name'].ljust(40), '\t', conf['description'])
1596         print('\n')
1597
1598     if args.boardlist:
1599         print("Available board types:\n")
1600         for board in boardtype_list:
1601             print(board)
1602         print('\n')
1603
1604     sys.exit(0)
1605 else :
1606     params={
1607         'sdkPath'       : sdkPath,
1608         'projectRoot'   : projectRoot,
1609         'projectName'   : args.name,
1610         'wantGUI'       : False,
1611         'wantOverwrite' : args.overwrite,
1612         'boardtype'     : args.boardtype,
1613         'wantBuild'     : args.build,
1614         'features'      : args.feature,
1615         'projects'      : args.project,
1616         'configs'       : (),
1617         'wantRunFromRAM': args.runFromRAM,
1618         'wantExamples'  : args.examples,
1619         'wantUART'      : args.uart,
1620         'wantUSB'       : args.usb,
1621         'wantCPP'       : args.cpp,
1622         'debugger'      : args.debugger,
1623         'exceptions'    : args.cppexceptions,
1624         'rtti'          : args.cpprtti,
1625         'ssid'          : '',
1626         'password'      : '',
1627         'sdkVersion'    : args.sdkVersion,
1628         'toolchainVersion': args.toolchainVersion,
1629         'ninjaPath'     : args.ninjaPath,
1630         'cmakePath'     : args.cmakePath,
1631         'customPython'  : args.customPython
1632         }
1633
1634     DoEverything(None, params)
1635     sys.exit(0)
This page took 0.124478 seconds and 4 git commands to generate.