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