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