Skip to content

Commit faf93df

Browse files
authored
Merge pull request #51 from UncleGrumpy/pico2_updates
2 parents 309dbe5 + 577b6fb commit faf93df

File tree

5 files changed

+192
-77
lines changed

5 files changed

+192
-77
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
### Added
1717
- Added dialyzer task to simplify running dialyzer on AtomVM applications.
18+
- Added support for rp2350 devices to allow for default detection of the device mount path.
19+
- Added configuration paramenter for setting the path to picotool for the pico_flash task.
20+
21+
### Changed
22+
- The `uf2create` task now creates `universal` format uf2 files by default, suitable for both
23+
rp2040 or rp2350 devices.
24+
- The `pico_flash` task now checks that a device is an RP2 platform before resetting to `BOOTSEL`
25+
mode, preventing interference with other MCUs that may be attached to the host system.
26+
- The `pico_flash` task now aborts on all errors rather than trying to continue after a failure.
1827

1928
## [0.7.5] (2025.05.27)
2029

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ The following table enumerates the properties that may be defined in your projec
449449
|-----|------|-------------|
450450
| `path` | `string()` | Path to pico device |
451451
| `reset` | `string()` | Path to serial device to reset before flashing |
452+
| `picotool` | `string()` | Path to picotool executable |
452453

453454
Example:
454455

@@ -458,6 +459,7 @@ Alternatively, the following environment variables may be used to control the ab
458459

459460
* `ATOMVM_REBAR3_PLUGIN_PICO_MOUNT_PATH` | `ATOMVM_PICO_MOUNT_PATH`
460461
* `ATOMVM_REBAR3_PLUGIN_PICO_RESET_DEV` | `ATOMVM_PICO_RESET_DEV`
462+
* `ATOMVM_REBAR3_PLUGIN_PICOTOOL` | `ATOMVM_PICOTOOL`
461463

462464
Any setting specified on the command line take precedence over entries in `rebar.config`, which in turn take precedence over environment variable settings, which in turn take precedence over the default values specified above.
463465

src/atomvm_pico_flash_provider.erl

Lines changed: 106 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
{path, $p, "path", string,
3535
"Path to pico device (Defaults Linux: /run/media/${USER}/RPI-RP2, MacOS: /Volumes/RPI-RP2)"},
3636
{reset, $r, "reset", string,
37-
"Path to serial device to reset before flashing (Defaults Linux: /dev/ttyACM0, MacOS: /dev/cu.usbmodem14*)"}
37+
"Path to serial device to reset before flashing (Defaults Linux: /dev/ttyACM0, MacOS: /dev/cu.usbmodem14*)"},
38+
{picotool, $t, "picotool", string, "Path to picotool utility (Default is to search in PATH)"}
3839
]).
3940

4041
%%
@@ -72,13 +73,13 @@ do(State) ->
7273
ok = do_flash(
7374
rebar_state:project_apps(State),
7475
maps:get(path, Opts),
75-
maps:get(reset, Opts)
76+
maps:get(reset, Opts),
77+
maps:get(picotool, Opts)
7678
),
7779
{ok, State}
7880
catch
7981
_C:E:_S ->
80-
rebar_api:error("An error occurred in the ~p task. Error=~p~n", [?PROVIDER, E]),
81-
{error, E}
82+
rebar_api:abort("An error occurred in the ~p task. Error=~p~n", [?PROVIDER, E])
8283
end.
8384

8485
-spec format_error(any()) -> iolist().
@@ -104,14 +105,27 @@ env_opts() ->
104105
#{
105106
path => os:getenv(
106107
"ATOMVM_REBAR3_PLUGIN_PICO_MOUNT_PATH",
107-
get_default_mount()
108+
os:getenv("ATOMVM_PICO_MOUNT_PATH", "")
108109
),
109110
reset => os:getenv(
110111
"ATOMVM_REBAR3_PLUGIN_PICO_RESET_DEV",
111-
get_reset_base()
112+
get_reset_dev()
113+
),
114+
picotool => os:getenv(
115+
"ATOMVM_REBAR3_PLUGIN_PICOTOOL",
116+
os:getenv("ATOMVM_PICOTOOL", find_picotool())
112117
)
113118
}.
114119

120+
%% @private
121+
find_picotool() ->
122+
case os:find_executable("picotool") of
123+
false ->
124+
"";
125+
Picotool ->
126+
Picotool
127+
end.
128+
115129
%% @private
116130
get_stty_file_flag() ->
117131
{_Fam, System} = os:type(),
@@ -123,78 +137,103 @@ get_stty_file_flag() ->
123137
end.
124138

125139
%% @private
126-
get_reset_base() ->
140+
get_reset_dev() ->
127141
{_Fam, System} = os:type(),
128142
Base =
129143
case System of
130144
linux ->
131-
"/dev/ttyACM*";
145+
filelib:wildcard("/dev/serial/by-id/usb-Raspberry_Pi_Pico_????????????????-if00");
132146
darwin ->
133-
"/dev/cu.usbmodem14*";
147+
Bin = darwin_ioreg:pico_callout_devices(),
148+
lists:foldl(fun(Path, Acc) -> [binary_to_list(Path) | Acc] end, [], Bin);
134149
_Other ->
135-
""
150+
[]
136151
end,
137152
os:getenv("ATOMVM_PICO_RESET_DEV", Base).
138153

139-
%% @private
140-
get_default_mount() ->
154+
%%% @private
155+
find_mounted_pico() ->
141156
{_Fam, System} = os:type(),
142-
Default =
157+
Wildcard =
143158
case System of
144159
linux ->
145-
"/run/media/" ++ os:getenv("USER") ++ "/RPI-RP2";
160+
"/run/media/" ++ os:getenv("USER") ++ "/{RPI-RP2,RP2350}";
146161
darwin ->
147-
"/Volumes/RPI-RP2";
162+
"/Volumes/{RPI-RP2,RP2350}";
148163
_Other ->
149164
""
150165
end,
151-
os:getenv("ATOMVM_PICO_MOUNT_PATH", Default).
166+
case filelib:wildcard(Wildcard) of
167+
[Pico | _] = Path ->
168+
rebar_api:debug("Found pico device, using ~p from devices list: ~p", [Pico, Path]),
169+
{ok, Pico};
170+
_ ->
171+
not_found
172+
end.
152173

153174
%% @private
154175
wait_for_mount(Mount, Count) when Count < 30 ->
155-
try
156-
case file:read_link_info(Mount) of
157-
{ok, #file_info{type = directory} = _Info} ->
158-
ok;
159-
_ ->
160-
timer:sleep(1000),
161-
wait_for_mount(Mount, Count + 1)
162-
end
163-
catch
164-
_ ->
165-
timer:sleep(1000),
166-
wait_for_mount(Mount, Count + 1)
176+
case Mount of
177+
"" ->
178+
case find_mounted_pico() of
179+
not_found ->
180+
timer:sleep(1000),
181+
wait_for_mount(Mount, Count + 1);
182+
{ok, Pico} ->
183+
case file:read_link_info(Pico) of
184+
{ok, #file_info{type = directory} = _Info} ->
185+
{ok, Pico};
186+
Err ->
187+
rebar_api:abort("Pico mount point is not a directory (~p)", [Err])
188+
end
189+
end;
190+
Path ->
191+
case file:read_link_info(Path) of
192+
{ok, #file_info{type = directory} = _Info} ->
193+
{ok, Path};
194+
_ ->
195+
timer:sleep(1000),
196+
wait_for_mount(Mount, Count + 1)
197+
end
167198
end;
168-
wait_for_mount(Mount, 30) ->
169-
rebar_api:error("Pico not mounted at ~s after 30 seconds. giving up...", [Mount]),
170-
erlang:throw(mount_error).
199+
wait_for_mount(_Mount, 30) ->
200+
rebar_api:abort("Pico not mounted after 30 seconds. giving up...", []).
171201

172202
%% @private
173-
check_pico_mount(Mount) ->
174-
try
175-
case file:read_link_info(Mount) of
176-
{ok, #file_info{type = directory} = _Info} ->
177-
rebar_api:debug("Pico mounted at ~s.", [Mount]),
178-
ok;
179-
_ ->
180-
rebar_api:error("Pico not mounted at ~s.", [Mount]),
181-
erlang:throw(no_device)
182-
end
183-
catch
184-
_ ->
185-
rebar_api:error("Pico not mounted at ~s.", [Mount]),
186-
erlang:throw(no_device)
203+
get_pico_mount(Mount) ->
204+
case Mount of
205+
"" ->
206+
case find_mounted_pico() of
207+
not_found ->
208+
rebar_api:info("Waiting for an RP2 device to mount...", []),
209+
wait_for_mount(Mount, 0);
210+
{ok, Pico} ->
211+
{ok, Pico}
212+
end;
213+
Path ->
214+
case file:read_link_info(Path) of
215+
{ok, #file_info{type = directory} = _Info} ->
216+
rebar_api:debug("Pico mounted at ~s.", [Mount]),
217+
{ok, Path};
218+
_ ->
219+
rebar_api:info("Waiting for the device at path ~s to mount...", [
220+
string:trim(Mount)
221+
]),
222+
wait_for_mount(Mount, 0)
223+
end
187224
end.
188225

189226
%% @private
190-
needs_reset(ResetBase) ->
191-
case filelib:wildcard(ResetBase) of
227+
needs_reset(ResetDev) ->
228+
case ResetDev of
192229
[] ->
193230
false;
194231
[ResetPort | _T] ->
195232
case file:read_link_info(ResetPort) of
196233
{ok, #file_info{type = device} = _Info} ->
197234
{true, ResetPort};
235+
{ok, #file_info{type = symlink} = _Info} ->
236+
{true, ResetPort};
198237
_ ->
199238
false
200239
end;
@@ -203,31 +242,30 @@ needs_reset(ResetBase) ->
203242
end.
204243

205244
%% @private
206-
do_reset(ResetPort) ->
245+
do_reset(ResetPort, Picotool) ->
207246
Flag = get_stty_file_flag(),
208247
BootselMode = lists:join(" ", [
209248
"stty", Flag, ResetPort, "1200"
210249
]),
211-
rebar_api:debug("Resetting device at path ~s", [ResetPort]),
250+
rebar_api:info("Resetting device at path ~s", [ResetPort]),
212251
ResetStatus = os:cmd(BootselMode),
213252
case ResetStatus of
214253
"" ->
215254
ok;
216255
Error ->
217-
case os:find_executable(picotool) of
218-
false ->
219-
rebar_api:error(
220-
"Warning: ~s~nUnable to locate 'picotool', close the serial monitor before flashing, or install picotool for automatic disconnect and BOOTSEL mode.",
256+
case Picotool of
257+
"" ->
258+
rebar_api:abort(
259+
"Warning: ~s~nUnable to locate 'picotool', close the serial monitor before flashing, or supply the path to picotool if it is not installed in your PATH for automatic disconnect and BOOTSEL mode.",
221260
[Error]
222-
),
223-
erlang:throw(reset_error);
224-
Picotool ->
261+
);
262+
RP2tool ->
225263
rebar_api:warn(
226264
"Warning: ~s~nFor faster flashing remember to disconnect serial monitor first.",
227265
[Error]
228266
),
229267
DevReset = lists:join(" ", [
230-
Picotool, "reboot", "-f", "-u"
268+
RP2tool, "reboot", "-f", "-u"
231269
]),
232270
rebar_api:warn("Disconnecting serial monitor with: `~s' in 5 seconds...", [
233271
DevReset
@@ -239,8 +277,7 @@ do_reset(ResetPort) ->
239277
"The device was asked to reboot into BOOTSEL mode." ->
240278
ok;
241279
BootError ->
242-
rebar_api:error("Failed to prepare pico for flashing: ~s", [BootError]),
243-
erlang:throw(picoflash_reboot_error)
280+
rebar_api:abort("Failed to prepare pico for flashing: ~s", [BootError])
244281
end
245282
end
246283
end.
@@ -259,31 +296,28 @@ get_uf2_appname(ProjectApps) ->
259296
binary_to_list(rebar_app_info:name(App)).
260297

261298
%% @private
262-
do_flash(ProjectApps, PicoPath, ResetBase) ->
263-
case needs_reset(ResetBase) of
299+
do_flash(ProjectApps, PicoPath, ResetDev, Picotool) ->
300+
case needs_reset(ResetDev) of
264301
false ->
265-
rebar_api:debug("No Pico found matching ~s.", [ResetBase]),
302+
rebar_api:debug("No Pico reset device found matching ~s.", [ResetDev]),
266303
ok;
267304
{true, ResetPort} ->
268305
rebar_api:debug("Pico at ~s needs reset...", [ResetPort]),
269-
do_reset(ResetPort),
270-
rebar_api:info("Waiting for the device at path ~s to settle and mount...", [
271-
string:trim(PicoPath)
272-
]),
306+
do_reset(ResetPort, Picotool),
307+
rebar_api:info("Waiting for the device at path ~s to settle and mount...", [PicoPath]),
273308
wait_for_mount(PicoPath, 0)
274309
end,
275-
check_pico_mount(PicoPath),
310+
{ok, Path} = get_pico_mount(PicoPath),
276311
TargetUF2 = get_uf2_file(ProjectApps),
277312
App = get_uf2_appname(ProjectApps),
278313
File = App ++ ".uf2",
279-
Dest = filename:join(PicoPath, File),
280-
rebar_api:info("Copying ~s", [File]),
314+
Dest = filename:join(Path, File),
315+
rebar_api:info("Copying ~s to ~s", [File, Path]),
281316
case file:copy(TargetUF2, Dest) of
282317
{ok, _Size} ->
283318
ok;
284319
CopyError ->
285-
rebar_api:error("Failed to copy application file ~s to pico: ~s", [File, CopyError]),
286-
erlang:throw(picoflash_copy_error)
320+
rebar_api:abort("Failed to copy application file ~s to pico: ~s", [File, CopyError])
287321
end,
288-
rebar_api:info("Successfully loaded ~s application to device at path ~s.", [App, PicoPath]),
322+
rebar_api:info("Successfully loaded ~s application to the device.", [App]),
289323
ok.

src/atomvm_uf2create_provider.erl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@
3030
-define(DEPS, [packbeam]).
3131
-define(OPTS, [
3232
{family_id, $f, "family_id", string,
33-
"Device family or flavor of uf2 file to create (default rp2040)"},
33+
"Device family or flavor of uf2 file to create (default universal)"},
3434
{output, $o, "output", string, "Output path/name"},
3535
{start, $s, "start", string, "Start address for the uf2 binary (default 0x10180000)"},
3636
{input, $i, "input", string, "Input avm file to convert to uf2"}
3737
]).
3838

3939
-define(DEFAULT_OPTS, #{
4040
start => os:getenv("ATOMVM_PICO_APP_START", "0x10180000"),
41-
family_id => os:getenv("ATOMVM_PICO_UF2_FAMILY", rp2040)
41+
family_id => os:getenv("ATOMVM_PICO_UF2_FAMILY", universal)
4242
}).
4343

4444
%%
@@ -84,10 +84,10 @@ do(State) ->
8484
catch
8585
C:E:S ->
8686
rebar_api:error(
87-
"An error occurred in the ~p task. Class=~p Error=~p Stacktrace=~p~n", [
88-
?PROVIDER, C, E, S
89-
]
87+
"An error occurred in the ~p task. Error=~p",
88+
[?PROVIDER, E]
9089
),
90+
rebar_api:debug("Class=~p, Error=~p~nSTACKTRACE:~n~p~n", [C, E, S]),
9191
{error, E}
9292
end.
9393

0 commit comments

Comments
 (0)