Skip to content

Commit ec4fd6d

Browse files
committed
Enhancements to pico_flash task
Improves compatibility with RP2350 devices, detecting path and reset devices correctly so setting explicit path to the mount point is no longer necessary for these devices. The provider also now confirms that the target is an RP2 device before resetting to `BOOTSEL` mode, preventing interference with non RP2 devices that may be attached to the host system. Closes #44 Signed-off-by: Winford <winford@object.stream>
1 parent 60b99fc commit ec4fd6d

File tree

3 files changed

+150
-54
lines changed

3 files changed

+150
-54
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ 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.
1819

1920
### Changed
2021
- The `uf2create` task now creates `universal` format uf2 files by default, suitable for both
2122
rp2040 or rp2350 devices.
23+
- The `pico_flash` task now checks that a device is an RP2 platform before resetting to `BOOTSEL`
24+
mode, preventing interference with other MCUs that may be attached to the host system.
2225

2326
## [0.7.5] (2025.05.27)
2427

src/atomvm_pico_flash_provider.erl

Lines changed: 77 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ env_opts() ->
104104
#{
105105
path => os:getenv(
106106
"ATOMVM_REBAR3_PLUGIN_PICO_MOUNT_PATH",
107-
get_default_mount()
107+
os:getenv("ATOMVM_PICO_MOUNT_PATH", "")
108108
),
109109
reset => os:getenv(
110110
"ATOMVM_REBAR3_PLUGIN_PICO_RESET_DEV",
111-
get_reset_base()
111+
get_reset_dev()
112112
)
113113
}.
114114

@@ -123,78 +123,103 @@ get_stty_file_flag() ->
123123
end.
124124

125125
%% @private
126-
get_reset_base() ->
126+
get_reset_dev() ->
127127
{_Fam, System} = os:type(),
128128
Base =
129129
case System of
130130
linux ->
131-
"/dev/ttyACM*";
131+
filelib:wildcard("/dev/serial/by-id/usb-Raspberry_Pi_Pico_????????????????-if00");
132132
darwin ->
133-
"/dev/cu.usbmodem14*";
133+
Bin = darwin_ioreg:pico_callout_devices(),
134+
lists:foldl(fun(Path, Acc) -> [binary_to_list(Path) | Acc] end, [], Bin);
134135
_Other ->
135-
""
136+
[]
136137
end,
137138
os:getenv("ATOMVM_PICO_RESET_DEV", Base).
138139

139-
%% @private
140-
get_default_mount() ->
140+
%%% @private
141+
find_mounted_pico() ->
141142
{_Fam, System} = os:type(),
142-
Default =
143+
Wildcard =
143144
case System of
144145
linux ->
145-
"/run/media/" ++ os:getenv("USER") ++ "/RPI-RP2";
146+
"/run/media/" ++ os:getenv("USER") ++ "/{RPI-RP2,RP2350}";
146147
darwin ->
147-
"/Volumes/RPI-RP2";
148+
"/Volumes/{RPI-RP2,RP2350}";
148149
_Other ->
149150
""
150151
end,
151-
os:getenv("ATOMVM_PICO_MOUNT_PATH", Default).
152+
case filelib:wildcard(Wildcard) of
153+
[Pico | _] = Path ->
154+
rebar_api:debug("Found pico device, using ~p from devices list: ~p", [Pico, Path]),
155+
{ok, Pico};
156+
_ ->
157+
not_found
158+
end.
152159

153160
%% @private
154161
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)
162+
case Mount of
163+
"" ->
164+
case find_mounted_pico() of
165+
not_found ->
166+
timer:sleep(1000),
167+
wait_for_mount(Mount, Count + 1);
168+
{ok, Pico} ->
169+
case file:read_link_info(Pico) of
170+
{ok, #file_info{type = directory} = _Info} ->
171+
{ok, Pico};
172+
Err ->
173+
rebar_api:abort("Pico mount point is not a directory (~p)", [Err])
174+
end
175+
end;
176+
Path ->
177+
case file:read_link_info(Path) of
178+
{ok, #file_info{type = directory} = _Info} ->
179+
{ok, Path};
180+
_ ->
181+
timer:sleep(1000),
182+
wait_for_mount(Mount, Count + 1)
183+
end
167184
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).
185+
wait_for_mount(_Mount, 30) ->
186+
rebar_api:abort("Pico not mounted after 30 seconds. giving up...", []).
171187

172188
%% @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)
189+
get_pico_mount(Mount) ->
190+
case Mount of
191+
"" ->
192+
case find_mounted_pico() of
193+
not_found ->
194+
rebar_api:info("Waiting for an RP2 device to mount...", []),
195+
wait_for_mount(Mount, 0);
196+
{ok, Pico} ->
197+
{ok, Pico}
198+
end;
199+
Path ->
200+
case file:read_link_info(Path) of
201+
{ok, #file_info{type = directory} = _Info} ->
202+
rebar_api:debug("Pico mounted at ~s.", [Mount]),
203+
{ok, Path};
204+
_ ->
205+
rebar_api:info("Waiting for the device at path ~s to mount...", [
206+
string:trim(Mount)
207+
]),
208+
wait_for_mount(Mount, 0)
209+
end
187210
end.
188211

189212
%% @private
190-
needs_reset(ResetBase) ->
191-
case filelib:wildcard(ResetBase) of
213+
needs_reset(ResetDev) ->
214+
case ResetDev of
192215
[] ->
193216
false;
194217
[ResetPort | _T] ->
195218
case file:read_link_info(ResetPort) of
196219
{ok, #file_info{type = device} = _Info} ->
197220
{true, ResetPort};
221+
{ok, #file_info{type = symlink} = _Info} ->
222+
{true, ResetPort};
198223
_ ->
199224
false
200225
end;
@@ -208,7 +233,7 @@ do_reset(ResetPort) ->
208233
BootselMode = lists:join(" ", [
209234
"stty", Flag, ResetPort, "1200"
210235
]),
211-
rebar_api:debug("Resetting device at path ~s", [ResetPort]),
236+
rebar_api:info("Resetting device at path ~s", [ResetPort]),
212237
ResetStatus = os:cmd(BootselMode),
213238
case ResetStatus of
214239
"" ->
@@ -259,31 +284,29 @@ get_uf2_appname(ProjectApps) ->
259284
binary_to_list(rebar_app_info:name(App)).
260285

261286
%% @private
262-
do_flash(ProjectApps, PicoPath, ResetBase) ->
263-
case needs_reset(ResetBase) of
287+
do_flash(ProjectApps, PicoPath, ResetDev) ->
288+
case needs_reset(ResetDev) of
264289
false ->
265-
rebar_api:debug("No Pico found matching ~s.", [ResetBase]),
290+
rebar_api:debug("No Pico reset device found matching ~s.", [ResetDev]),
266291
ok;
267292
{true, ResetPort} ->
268293
rebar_api:debug("Pico at ~s needs reset...", [ResetPort]),
269294
do_reset(ResetPort),
270-
rebar_api:info("Waiting for the device at path ~s to settle and mount...", [
271-
string:trim(PicoPath)
272-
]),
295+
rebar_api:info("Waiting for the device at path ~s to settle and mount...", [PicoPath]),
273296
wait_for_mount(PicoPath, 0)
274297
end,
275-
check_pico_mount(PicoPath),
298+
{ok, Path} = get_pico_mount(PicoPath),
276299
TargetUF2 = get_uf2_file(ProjectApps),
277300
App = get_uf2_appname(ProjectApps),
278301
File = App ++ ".uf2",
279-
Dest = filename:join(PicoPath, File),
280-
rebar_api:info("Copying ~s", [File]),
302+
Dest = filename:join(Path, File),
303+
rebar_api:info("Copying ~s to ~s", [File, Path]),
281304
case file:copy(TargetUF2, Dest) of
282305
{ok, _Size} ->
283306
ok;
284307
CopyError ->
285308
rebar_api:error("Failed to copy application file ~s to pico: ~s", [File, CopyError]),
286309
erlang:throw(picoflash_copy_error)
287310
end,
288-
rebar_api:info("Successfully loaded ~s application to device at path ~s.", [App, PicoPath]),
311+
rebar_api:info("Successfully loaded ~s application to the device.", [App]),
289312
ok.

src/darwin_ioreg.erl

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
%
2+
% This file is part of atomvm_rebar3_plugin.
3+
%
4+
% Copyright 2025 Paul Guyot <pguyot@kallisys.net>
5+
%
6+
% Licensed under the Apache License, Version 2.0 (the "License");
7+
% you may not use this file except in compliance with the License.
8+
% You may obtain a copy of the License at
9+
%
10+
% http://www.apache.org/licenses/LICENSE-2.0
11+
%
12+
% Unless required by applicable law or agreed to in writing, software
13+
% distributed under the License is distributed on an "AS IS" BASIS,
14+
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
% See the License for the specific language governing permissions and
16+
% limitations under the License.
17+
%
18+
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
%
20+
21+
-module(darwin_ioreg).
22+
23+
-export([
24+
pico_callout_devices/0
25+
]).
26+
27+
% Parse ioreg output to find out the callout devices attached to Pico.
28+
% No pico connected:
29+
% 1> darwin_ioreg:pico_callout_devices().
30+
% []
31+
% One pico connected:
32+
% 2> darwin_ioreg:pico_callout_devices().
33+
% [<<"/dev/cu.usbmodem14401">>]
34+
% Two picos connected:
35+
% 3> darwin_ioreg:pico_callout_devices().
36+
% [<<"/dev/cu.usbmodem14401">>,<<"/dev/cu.usbmodem14301">>]
37+
-spec pico_callout_devices() -> [unicode:unicode_binary()].
38+
pico_callout_devices() ->
39+
ResultStr = os:cmd("ioreg -r -n IOSerialBSDClient -t"),
40+
ResultBin = list_to_binary(ResultStr),
41+
ResultLines = binary:split(ResultBin, <<"\n">>, [global]),
42+
parse(ResultLines, []).
43+
44+
parse([<<"+-o Root ", _/binary>> | T], Acc) ->
45+
parse_tree(T, root, Acc);
46+
parse([<<" ", Rest/binary>> | T], Acc) ->
47+
parse([Rest | T], Acc);
48+
parse([<<>> | T], Acc) ->
49+
parse(T, Acc);
50+
parse([], Acc) ->
51+
Acc.
52+
53+
parse_tree([<<" ", Rest/binary>> | T], IsPico, Acc) ->
54+
parse_tree([Rest | T], IsPico, Acc);
55+
parse_tree([<<"+-o Pico@", _/binary>> | T], root, Acc) ->
56+
parse_tree(T, true, Acc);
57+
parse_tree([<<"+-o IOSerialBSDClient ", _/binary>> | T], IsPico, Acc) ->
58+
parse_ioserialbsdclient(T, IsPico, Acc);
59+
parse_tree([<<"+-o ", _/binary>> | T], IsPico, Acc) ->
60+
parse_tree(T, IsPico, Acc).
61+
62+
parse_ioserialbsdclient([<<" ", Rest/binary>> | T], IsPico, Acc) ->
63+
parse_ioserialbsdclient([Rest | T], IsPico, Acc);
64+
parse_ioserialbsdclient([<<"\"IOCalloutDevice\" = \"", Rest/binary>> | T], true, Acc) ->
65+
[CallOut | _] = binary:split(Rest, <<"\"">>),
66+
parse_ioserialbsdclient(T, true, [CallOut | Acc]);
67+
parse_ioserialbsdclient([<<"}">> | T], _IsPico, Acc) ->
68+
parse(T, Acc);
69+
parse_ioserialbsdclient([_Other | T], IsPico, Acc) ->
70+
parse_ioserialbsdclient(T, IsPico, Acc).

0 commit comments

Comments
 (0)